summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md18
-rw-r--r--blocks/3DSCOPE.svg9
-rw-r--r--blocks/ANDBLK.svg34
-rw-r--r--blocks/ASCOPE.svg31
-rw-r--r--blocks/BACHE.svg27
-rw-r--r--blocks/BARXY.svg23
-rw-r--r--blocks/BIGSOM_f.svg4
-rw-r--r--blocks/BPLATFORM.svg15
-rw-r--r--blocks/CCS.svg7
-rw-r--r--blocks/CEVENTSCOPE.svg39
-rw-r--r--blocks/CLOCK_c.svg13
-rw-r--r--blocks/CLOCK_f.svg12
-rw-r--r--blocks/CMSCOPE.svg31
-rw-r--r--blocks/CSCOPXY.svg150
-rw-r--r--blocks/CSCOPXY3D.svg162
-rw-r--r--blocks/CVS.svg5
-rw-r--r--blocks/Capacitor.svg10
-rw-r--r--blocks/ConstantVoltage.svg24
-rw-r--r--blocks/CurrentSensor.svg37
-rw-r--r--blocks/DEADBAND.svg6
-rw-r--r--blocks/DSCOPE.svg39
-rw-r--r--blocks/Diode.svg7
-rw-r--r--blocks/Flowmeter.svg37
-rw-r--r--blocks/Ground.svg7
-rw-r--r--blocks/Gyrator.svg14
-rw-r--r--blocks/HYSTHERESIS.svg8
-rw-r--r--blocks/INTEGRAL.svg14
-rw-r--r--blocks/INTEGRAL_m.svg14
-rw-r--r--blocks/IdealTransformer.svg23
-rw-r--r--blocks/Inductor.svg14
-rw-r--r--blocks/LOOKUP_f.svg39
-rw-r--r--blocks/NMOS.svg69
-rw-r--r--blocks/NPN.svg51
-rw-r--r--blocks/PMOS.svg65
-rw-r--r--blocks/PNP.svg51
-rw-r--r--blocks/PRODUCT.svg4
-rw-r--r--blocks/PULSE_SC.svg7
-rw-r--r--blocks/PerteDP.svg44
-rw-r--r--blocks/PotentialSensor.svg51
-rw-r--r--blocks/PuitP.svg44
-rw-r--r--blocks/QUANT_f.svg6
-rw-r--r--blocks/RAMP.svg7
-rw-r--r--blocks/Resistor.svg51
-rw-r--r--blocks/SATURATION.svg6
-rw-r--r--blocks/SELF_SWITCH.svg11
-rw-r--r--blocks/SINUS_f.svg15
-rw-r--r--blocks/SQUARE_WAVE_f.svg8
-rw-r--r--blocks/STEP_FUNCTION.svg7
-rw-r--r--blocks/SUM.svg4
-rw-r--r--blocks/SUMMATION.svg4
-rw-r--r--blocks/SUPER.svg49
-rw-r--r--blocks/SWITCH.svg11
-rw-r--r--blocks/SWITCH2_m.svg11
-rw-r--r--blocks/SampleCLK.svg14
-rw-r--r--blocks/Self_Switch_off.svg77
-rw-r--r--blocks/Self_Switch_on.svg73
-rw-r--r--blocks/SourceP.svg59
-rw-r--r--blocks/VanneReglante.svg58
-rw-r--r--blocks/VariableResistor.svg64
-rw-r--r--blocks/VirtualCLK0.svg13
-rw-r--r--blocks/VoltageSensor.svg35
-rw-r--r--blocks/sawtooth.svg6
-rw-r--r--config/keyhandler-commons.xml27
-rw-r--r--images/add.pngbin0 -> 1564 bytes
-rw-r--r--images/camera.pngbin0 -> 887 bytes
-rw-r--r--images/check.pngbin0 -> 253 bytes
-rw-r--r--images/close.pngbin0 -> 1910 bytes
-rw-r--r--images/connector.gifbin0 -> 954 bytes
-rw-r--r--images/copy.pngbin0 -> 728 bytes
-rw-r--r--images/cut.pngbin0 -> 781 bytes
-rw-r--r--images/delete2.pngbin0 -> 914 bytes
-rw-r--r--images/dot.gifbin0 -> 517 bytes
-rw-r--r--images/export1.pngbin0 -> 857 bytes
-rw-r--r--images/fit_to_size.pngbin0 -> 529 bytes
-rw-r--r--images/gradient_background.jpgbin0 -> 6164 bytes
-rw-r--r--images/green-dot.gifbin0 -> 326 bytes
-rw-r--r--images/grid.gifbin0 -> 58 bytes
-rw-r--r--images/group.pngbin0 -> 899 bytes
-rw-r--r--images/icons48/column.pngbin0 -> 1787 bytes
-rw-r--r--images/icons48/earth.pngbin0 -> 4520 bytes
-rw-r--r--images/icons48/gear.pngbin0 -> 4418 bytes
-rw-r--r--images/icons48/keys.pngbin0 -> 4295 bytes
-rw-r--r--images/icons48/mail_new.pngbin0 -> 3944 bytes
-rw-r--r--images/icons48/server.pngbin0 -> 3556 bytes
-rw-r--r--images/icons48/table.pngbin0 -> 1574 bytes
-rw-r--r--images/key.pngbin0 -> 300 bytes
-rw-r--r--images/loading.gifbin0 -> 10132 bytes
-rw-r--r--images/navigate_minus.pngbin0 -> 485 bytes
-rw-r--r--images/navigate_plus.pngbin0 -> 709 bytes
-rw-r--r--images/paste.pngbin0 -> 783 bytes
-rw-r--r--images/plus.pngbin0 -> 236 bytes
-rw-r--r--images/press32.pngbin0 -> 2261 bytes
-rw-r--r--images/print32.pngbin0 -> 2111 bytes
-rw-r--r--images/printer.pngbin0 -> 896 bytes
-rw-r--r--images/redo.pngbin0 -> 895 bytes
-rw-r--r--images/sidebar_bg.gifbin0 -> 80 bytes
-rw-r--r--images/spacer.gifbin0 -> 43 bytes
-rw-r--r--images/toolbar_bg.gifbin0 -> 155 bytes
-rw-r--r--images/undo.pngbin0 -> 879 bytes
-rw-r--r--images/view_1_1.pngbin0 -> 849 bytes
-rw-r--r--images/view_1_132.pngbin0 -> 2199 bytes
-rw-r--r--images/view_next.pngbin0 -> 918 bytes
-rw-r--r--images/view_previous.pngbin0 -> 912 bytes
-rw-r--r--images/wires-grid.gifbin0 -> 50 bytes
-rw-r--r--images/zoom_in.pngbin0 -> 858 bytes
-rw-r--r--images/zoom_in32.pngbin0 -> 2184 bytes
-rw-r--r--images/zoom_out.pngbin0 -> 847 bytes
-rw-r--r--images/zoom_out32.pngbin0 -> 2150 bytes
-rw-r--r--index.html1050
-rw-r--r--jquery/images/ui-bg_flat_75_ffffff_40x100.pngbin0 -> 178 bytes
-rw-r--r--jquery/images/ui-bg_glass_75_e6e6e6_1x400.pngbin0 -> 110 bytes
-rw-r--r--jquery/images/ui-icons_888888_256x240.pngbin0 -> 4369 bytes
-rw-r--r--jquery/jquery-1.8.2.js9440
-rw-r--r--jquery/jquery-ui.css470
-rw-r--r--license.txt59
-rw-r--r--palettes/ABS_VALUE.pngbin0 -> 859 bytes
-rw-r--r--palettes/AFFICH_m.pngbin0 -> 761 bytes
-rw-r--r--palettes/ANDBLK.pngbin0 -> 972 bytes
-rw-r--r--palettes/ANDLOG_f.pngbin0 -> 1429 bytes
-rw-r--r--palettes/AUTOMAT.pngbin0 -> 1535 bytes
-rw-r--r--palettes/BACKLASH.pngbin0 -> 1000 bytes
-rw-r--r--palettes/BARXY.pngbin0 -> 1970 bytes
-rw-r--r--palettes/BIGSOM_f.pngbin0 -> 982 bytes
-rw-r--r--palettes/BITCLEAR.pngbin0 -> 1095 bytes
-rw-r--r--palettes/BITSET.pngbin0 -> 1019 bytes
-rw-r--r--palettes/BOUNCE.pngbin0 -> 1185 bytes
-rw-r--r--palettes/BOUNCEXY.pngbin0 -> 1532 bytes
-rw-r--r--palettes/BPLATFORM.pngbin0 -> 1140 bytes
-rw-r--r--palettes/Bache.pngbin0 -> 1088 bytes
-rw-r--r--palettes/CANIMXY.pngbin0 -> 1532 bytes
-rw-r--r--palettes/CANIMXY3D.pngbin0 -> 1543 bytes
-rw-r--r--palettes/CBLOCK.pngbin0 -> 1183 bytes
-rw-r--r--palettes/CBLOCK4.pngbin0 -> 1172 bytes
-rw-r--r--palettes/CCS.pngbin0 -> 789 bytes
-rw-r--r--palettes/CEVENTSCOPE.pngbin0 -> 2021 bytes
-rw-r--r--palettes/CFSCOPE.pngbin0 -> 1943 bytes
-rw-r--r--palettes/CLINDUMMY_f.pngbin0 -> 1021 bytes
-rw-r--r--palettes/CLKFROM.pngbin0 -> 462 bytes
-rw-r--r--palettes/CLKGOTO.pngbin0 -> 465 bytes
-rw-r--r--palettes/CLKGotoTagVisibility.pngbin0 -> 1436 bytes
-rw-r--r--palettes/CLKINV_f.pngbin0 -> 313 bytes
-rw-r--r--palettes/CLKOUTV_f.pngbin0 -> 315 bytes
-rw-r--r--palettes/CLKSOMV_f.pngbin0 -> 1081 bytes
-rw-r--r--palettes/CLOCK_c.pngbin0 -> 1488 bytes
-rw-r--r--palettes/CLR.pngbin0 -> 1024 bytes
-rw-r--r--palettes/CLSS.pngbin0 -> 1494 bytes
-rw-r--r--palettes/CMAT3D.pngbin0 -> 2385 bytes
-rw-r--r--palettes/CMATVIEW.pngbin0 -> 2511 bytes
-rw-r--r--palettes/CMSCOPE.pngbin0 -> 2050 bytes
-rw-r--r--palettes/CONST.pngbin0 -> 512 bytes
-rw-r--r--palettes/CONSTRAINT2_c.pngbin0 -> 975 bytes
-rw-r--r--palettes/CONSTRAINT_c.pngbin0 -> 763 bytes
-rw-r--r--palettes/CONST_f.pngbin0 -> 512 bytes
-rw-r--r--palettes/CONST_m.pngbin0 -> 512 bytes
-rw-r--r--palettes/CONVERT.pngbin0 -> 1020 bytes
-rw-r--r--palettes/COSBLK_f.pngbin0 -> 863 bytes
-rw-r--r--palettes/CSCOPE.pngbin0 -> 2033 bytes
-rw-r--r--palettes/CSCOPXY.pngbin0 -> 1481 bytes
-rw-r--r--palettes/CSCOPXY3D.pngbin0 -> 1570 bytes
-rw-r--r--palettes/CUMSUM.pngbin0 -> 958 bytes
-rw-r--r--palettes/CURV_f.pngbin0 -> 829 bytes
-rw-r--r--palettes/CVS.pngbin0 -> 752 bytes
-rw-r--r--palettes/Capacitor.pngbin0 -> 397 bytes
-rw-r--r--palettes/ConstantVoltage.pngbin0 -> 442 bytes
-rw-r--r--palettes/Counter.pngbin0 -> 1219 bytes
-rw-r--r--palettes/CurrentSensor.pngbin0 -> 2717 bytes
-rw-r--r--palettes/DEADBAND.pngbin0 -> 801 bytes
-rw-r--r--palettes/DEBUG.pngbin0 -> 805 bytes
-rw-r--r--palettes/DELAYV_f.pngbin0 -> 1486 bytes
-rw-r--r--palettes/DELAY_f.pngbin0 -> 875 bytes
-rw-r--r--palettes/DEMUX.pngbin0 -> 855 bytes
-rw-r--r--palettes/DEMUX_f.pngbin0 -> 855 bytes
-rw-r--r--palettes/DERIV.pngbin0 -> 778 bytes
-rw-r--r--palettes/DFLIPFLOP.pngbin0 -> 1011 bytes
-rw-r--r--palettes/DIFF_f.pngbin0 -> 652 bytes
-rw-r--r--palettes/DLATCH.pngbin0 -> 1124 bytes
-rw-r--r--palettes/DLR.pngbin0 -> 1103 bytes
-rw-r--r--palettes/DLRADAPT_f.pngbin0 -> 1323 bytes
-rw-r--r--palettes/DLSS.pngbin0 -> 1532 bytes
-rw-r--r--palettes/DOLLAR.pngbin0 -> 760 bytes
-rw-r--r--palettes/DOLLAR_f.pngbin0 -> 760 bytes
-rw-r--r--palettes/DOLLAR_m.pngbin0 -> 760 bytes
-rw-r--r--palettes/Diode.pngbin0 -> 564 bytes
-rw-r--r--palettes/EDGE_TRIGGER.pngbin0 -> 1115 bytes
-rw-r--r--palettes/ENDBLK.pngbin0 -> 591 bytes
-rw-r--r--palettes/END_c.pngbin0 -> 726 bytes
-rw-r--r--palettes/ESELECT_f.pngbin0 -> 1199 bytes
-rw-r--r--palettes/EVTDLY_c.pngbin0 -> 1046 bytes
-rw-r--r--palettes/EVTGEN_f.pngbin0 -> 1118 bytes
-rw-r--r--palettes/EVTVARDLY.pngbin0 -> 1186 bytes
-rw-r--r--palettes/EXPBLK_m.pngbin0 -> 696 bytes
-rw-r--r--palettes/EXPRESSION.pngbin0 -> 1128 bytes
-rw-r--r--palettes/EXTRACT.pngbin0 -> 1025 bytes
-rw-r--r--palettes/EXTRACTBITS.pngbin0 -> 1122 bytes
-rw-r--r--palettes/EXTRACTOR.pngbin0 -> 946 bytes
-rw-r--r--palettes/EXTTRI.pngbin0 -> 1310 bytes
-rw-r--r--palettes/Extract_Activation.pngbin0 -> 1297 bytes
-rw-r--r--palettes/FROM.pngbin0 -> 449 bytes
-rw-r--r--palettes/FROMMO.pngbin0 -> 398 bytes
-rw-r--r--palettes/FROMWSB.pngbin0 -> 918 bytes
-rw-r--r--palettes/Flowmeter.pngbin0 -> 2746 bytes
-rw-r--r--palettes/GAINBLK.pngbin0 -> 863 bytes
-rw-r--r--palettes/GAINBLK_f.pngbin0 -> 863 bytes
-rw-r--r--palettes/GAIN_f.pngbin0 -> 863 bytes
-rw-r--r--palettes/GENERAL_f.pngbin0 -> 1042 bytes
-rw-r--r--palettes/GENSIN_f.pngbin0 -> 1702 bytes
-rw-r--r--palettes/GENSQR_f.pngbin0 -> 891 bytes
-rw-r--r--palettes/GOTO.pngbin0 -> 444 bytes
-rw-r--r--palettes/GOTOMO.pngbin0 -> 400 bytes
-rw-r--r--palettes/GotoTagVisibility.pngbin0 -> 1025 bytes
-rw-r--r--palettes/GotoTagVisibilityMO.pngbin0 -> 1520 bytes
-rw-r--r--palettes/Ground.pngbin0 -> 317 bytes
-rw-r--r--palettes/Gyrator.pngbin0 -> 845 bytes
-rw-r--r--palettes/HALT_f.pngbin0 -> 675 bytes
-rw-r--r--palettes/HYSTHERESIS.pngbin0 -> 830 bytes
-rw-r--r--palettes/IFTHEL_f.pngbin0 -> 1374 bytes
-rw-r--r--palettes/INIMPL_f.pngbin0 -> 255 bytes
-rw-r--r--palettes/INTEGRAL_f.pngbin0 -> 731 bytes
-rw-r--r--palettes/INTEGRAL_m.pngbin0 -> 942 bytes
-rw-r--r--palettes/INTMUL.pngbin0 -> 809 bytes
-rw-r--r--palettes/INTRP2BLK_f.pngbin0 -> 946 bytes
-rw-r--r--palettes/INTRPLBLK_f.pngbin0 -> 797 bytes
-rw-r--r--palettes/INVBLK.pngbin0 -> 684 bytes
-rw-r--r--palettes/IN_f.pngbin0 -> 303 bytes
-rw-r--r--palettes/ISELECT_m.pngbin0 -> 1104 bytes
-rw-r--r--palettes/IdealTransformer.pngbin0 -> 1036 bytes
-rw-r--r--palettes/Inductor.pngbin0 -> 464 bytes
-rw-r--r--palettes/JKFLIPFLOP.pngbin0 -> 963 bytes
-rw-r--r--palettes/LOGBLK_f.pngbin0 -> 792 bytes
-rw-r--r--palettes/LOGIC.pngbin0 -> 1007 bytes
-rw-r--r--palettes/LOGICAL_OP.pngbin0 -> 853 bytes
-rw-r--r--palettes/LOOKUP_f.pngbin0 -> 2062 bytes
-rw-r--r--palettes/MATBKSL.pngbin0 -> 842 bytes
-rw-r--r--palettes/MATCATH.pngbin0 -> 1041 bytes
-rw-r--r--palettes/MATCATV.pngbin0 -> 1095 bytes
-rw-r--r--palettes/MATDET.pngbin0 -> 659 bytes
-rw-r--r--palettes/MATDIAG.pngbin0 -> 850 bytes
-rw-r--r--palettes/MATDIV.pngbin0 -> 845 bytes
-rw-r--r--palettes/MATEIG.pngbin0 -> 684 bytes
-rw-r--r--palettes/MATEXPM.pngbin0 -> 822 bytes
-rw-r--r--palettes/MATINV.pngbin0 -> 732 bytes
-rw-r--r--palettes/MATLU.pngbin0 -> 637 bytes
-rw-r--r--palettes/MATMAGPHI.pngbin0 -> 1093 bytes
-rw-r--r--palettes/MATMUL.pngbin0 -> 914 bytes
-rw-r--r--palettes/MATPINV.pngbin0 -> 777 bytes
-rw-r--r--palettes/MATRESH.pngbin0 -> 982 bytes
-rw-r--r--palettes/MATSING.pngbin0 -> 846 bytes
-rw-r--r--palettes/MATSUM.pngbin0 -> 976 bytes
-rw-r--r--palettes/MATTRAN.pngbin0 -> 963 bytes
-rw-r--r--palettes/MATZCONJ.pngbin0 -> 860 bytes
-rw-r--r--palettes/MATZREIM.pngbin0 -> 970 bytes
-rw-r--r--palettes/MAXMIN.pngbin0 -> 861 bytes
-rw-r--r--palettes/MAX_f.pngbin0 -> 861 bytes
-rw-r--r--palettes/MBLOCK.pngbin0 -> 1288 bytes
-rw-r--r--palettes/MCLOCK_f.pngbin0 -> 1202 bytes
-rw-r--r--palettes/MFCLCK_f.pngbin0 -> 1168 bytes
-rw-r--r--palettes/MIN_f.pngbin0 -> 719 bytes
-rw-r--r--palettes/MUX.pngbin0 -> 765 bytes
-rw-r--r--palettes/MUX_f.pngbin0 -> 765 bytes
-rw-r--r--palettes/M_SWITCH.pngbin0 -> 1567 bytes
-rw-r--r--palettes/M_freq.pngbin0 -> 1427 bytes
-rw-r--r--palettes/Modulo_Count.pngbin0 -> 1395 bytes
-rw-r--r--palettes/NEGTOPOS_f.pngbin0 -> 718 bytes
-rw-r--r--palettes/NMOS.pngbin0 -> 751 bytes
-rw-r--r--palettes/NPN.pngbin0 -> 745 bytes
-rw-r--r--palettes/NRMSOM_f.pngbin0 -> 988 bytes
-rw-r--r--palettes/OUTIMPL_f.pngbin0 -> 257 bytes
-rw-r--r--palettes/OUT_f.pngbin0 -> 297 bytes
-rw-r--r--palettes/OpAmp.pngbin0 -> 1649 bytes
-rw-r--r--palettes/PDE.pngbin0 -> 882 bytes
-rw-r--r--palettes/PID.pngbin0 -> 682 bytes
-rw-r--r--palettes/PMOS.pngbin0 -> 766 bytes
-rw-r--r--palettes/PNP.pngbin0 -> 747 bytes
-rw-r--r--palettes/POSTONEG_f.pngbin0 -> 722 bytes
-rw-r--r--palettes/POWBLK_f.pngbin0 -> 687 bytes
-rw-r--r--palettes/PRODUCT.pngbin0 -> 1013 bytes
-rw-r--r--palettes/PROD_f.pngbin0 -> 803 bytes
-rw-r--r--palettes/PULSE_SC.pngbin0 -> 788 bytes
-rw-r--r--palettes/PerteDP.pngbin0 -> 454 bytes
-rw-r--r--palettes/PotentialSensor.pngbin0 -> 2578 bytes
-rw-r--r--palettes/PuitsP.pngbin0 -> 1079 bytes
-rw-r--r--palettes/QUANT_f.pngbin0 -> 759 bytes
-rw-r--r--palettes/RAMP.pngbin0 -> 790 bytes
-rw-r--r--palettes/RAND_m.pngbin0 -> 1410 bytes
-rw-r--r--palettes/RATELIMITER.pngbin0 -> 989 bytes
-rw-r--r--palettes/READAU_f.pngbin0 -> 1209 bytes
-rw-r--r--palettes/READC_f.pngbin0 -> 1669 bytes
-rw-r--r--palettes/REGISTER.pngbin0 -> 1272 bytes
-rw-r--r--palettes/RELATIONALOP.pngbin0 -> 670 bytes
-rw-r--r--palettes/RELAY_f.pngbin0 -> 1008 bytes
-rw-r--r--palettes/RFILE_f.pngbin0 -> 1459 bytes
-rw-r--r--palettes/RICC.pngbin0 -> 794 bytes
-rw-r--r--palettes/ROOTCOEF.pngbin0 -> 1052 bytes
-rw-r--r--palettes/Resistor.pngbin0 -> 505 bytes
-rw-r--r--palettes/SAMPHOLD_m.pngbin0 -> 857 bytes
-rw-r--r--palettes/SATURATION.pngbin0 -> 785 bytes
-rw-r--r--palettes/SAWTOOTH_f.pngbin0 -> 1282 bytes
-rw-r--r--palettes/SCALAR2VECTOR.pngbin0 -> 1475 bytes
-rw-r--r--palettes/SELECT_m.pngbin0 -> 1111 bytes
-rw-r--r--palettes/SELF_SWITCH.pngbin0 -> 1792 bytes
-rw-r--r--palettes/SELF_SWITCH_off.pngbin0 -> 1823 bytes
-rw-r--r--palettes/SELF_SWITCH_on.pngbin0 -> 1723 bytes
-rw-r--r--palettes/SHIFT.pngbin0 -> 1275 bytes
-rw-r--r--palettes/SIGNUM.pngbin0 -> 866 bytes
-rw-r--r--palettes/SINBLK_f.pngbin0 -> 760 bytes
-rw-r--r--palettes/SOM_f.pngbin0 -> 1007 bytes
-rw-r--r--palettes/SQRT.pngbin0 -> 896 bytes
-rw-r--r--palettes/SRFLIPFLOP.pngbin0 -> 1150 bytes
-rw-r--r--palettes/STEP_FUNCTION.pngbin0 -> 645 bytes
-rw-r--r--palettes/SUBMAT.pngbin0 -> 1006 bytes
-rw-r--r--palettes/SUMMATION.pngbin0 -> 1011 bytes
-rw-r--r--palettes/SUM_f.pngbin0 -> 752 bytes
-rw-r--r--palettes/SUPER_f.pngbin0 -> 801 bytes
-rw-r--r--palettes/SWITCH2_m.pngbin0 -> 1289 bytes
-rw-r--r--palettes/SWITCH_f.pngbin0 -> 1244 bytes
-rw-r--r--palettes/SampleCLK.pngbin0 -> 1383 bytes
-rw-r--r--palettes/Sigbuilder.pngbin0 -> 1315 bytes
-rw-r--r--palettes/SineVoltage.pngbin0 -> 1043 bytes
-rw-r--r--palettes/SourceP.pngbin0 -> 1209 bytes
-rw-r--r--palettes/Switch.pngbin0 -> 881 bytes
-rw-r--r--palettes/TANBLK_f.pngbin0 -> 753 bytes
-rw-r--r--palettes/TCLSS.pngbin0 -> 1406 bytes
-rw-r--r--palettes/TEXT_f.pngbin0 -> 110 bytes
-rw-r--r--palettes/TIME_DELAY.pngbin0 -> 1454 bytes
-rw-r--r--palettes/TIME_f.pngbin0 -> 1398 bytes
-rw-r--r--palettes/TKSCALE.pngbin0 -> 1064 bytes
-rw-r--r--palettes/TOWS_c.pngbin0 -> 1646 bytes
-rw-r--r--palettes/TRASH_f.pngbin0 -> 846 bytes
-rw-r--r--palettes/TrigFun.pngbin0 -> 1018 bytes
-rw-r--r--palettes/VARIABLE_DELAY.pngbin0 -> 1290 bytes
-rw-r--r--palettes/VVsourceAC.pngbin0 -> 1229 bytes
-rw-r--r--palettes/VanneReglante.pngbin0 -> 2132 bytes
-rw-r--r--palettes/VariableResistor.pngbin0 -> 1034 bytes
-rw-r--r--palettes/VirtualCLK0.pngbin0 -> 1485 bytes
-rw-r--r--palettes/VoltageSensor.pngbin0 -> 2731 bytes
-rw-r--r--palettes/VsourceAC.pngbin0 -> 1316 bytes
-rw-r--r--palettes/WFILE_f.pngbin0 -> 1477 bytes
-rw-r--r--palettes/WRITEAU_f.pngbin0 -> 1711 bytes
-rw-r--r--palettes/WRITEC_f.pngbin0 -> 1587 bytes
-rw-r--r--palettes/ZCROSS_f.pngbin0 -> 909 bytes
-rw-r--r--palettes/c_block.pngbin0 -> 1068 bytes
-rw-r--r--palettes/fortran_block.pngbin0 -> 1378 bytes
-rw-r--r--palettes/freq_div.pngbin0 -> 1415 bytes
-rw-r--r--palettes/generic_block3.pngbin0 -> 1439 bytes
-rw-r--r--palettes/palettes.xml849
-rw-r--r--palettes/scifunc_block_m.pngbin0 -> 1325 bytes
-rw-r--r--src/css/common.css152
-rw-r--r--src/css/explorer.css15
-rw-r--r--src/images/button.gifbin0 -> 137 bytes
-rw-r--r--src/images/close.gifbin0 -> 70 bytes
-rw-r--r--src/images/collapsed.gifbin0 -> 877 bytes
-rw-r--r--src/images/error.gifbin0 -> 907 bytes
-rw-r--r--src/images/expanded.gifbin0 -> 878 bytes
-rw-r--r--src/images/maximize.gifbin0 -> 843 bytes
-rw-r--r--src/images/minimize.gifbin0 -> 64 bytes
-rw-r--r--src/images/normalize.gifbin0 -> 845 bytes
-rw-r--r--src/images/point.gifbin0 -> 55 bytes
-rw-r--r--src/images/resize.gifbin0 -> 74 bytes
-rw-r--r--src/images/separator.gifbin0 -> 146 bytes
-rw-r--r--src/images/submenu.gifbin0 -> 56 bytes
-rw-r--r--src/images/transparent.gifbin0 -> 90 bytes
-rw-r--r--src/images/warning.gifbin0 -> 276 bytes
-rw-r--r--src/images/warning.pngbin0 -> 425 bytes
-rw-r--r--src/images/window-title.gifbin0 -> 275 bytes
-rw-r--r--src/images/window.gifbin0 -> 75 bytes
-rw-r--r--src/js/editor/mxDefaultKeyHandler.js126
-rw-r--r--src/js/editor/mxDefaultPopupMenu.js300
-rw-r--r--src/js/editor/mxDefaultToolbar.js567
-rw-r--r--src/js/editor/mxEditor.js3220
-rw-r--r--src/js/handler/mxCellHighlight.js271
-rw-r--r--src/js/handler/mxCellMarker.js419
-rw-r--r--src/js/handler/mxCellTracker.js149
-rw-r--r--src/js/handler/mxConnectionHandler.js1969
-rw-r--r--src/js/handler/mxConstraintHandler.js308
-rw-r--r--src/js/handler/mxEdgeHandler.js1529
-rw-r--r--src/js/handler/mxEdgeSegmentHandler.js284
-rw-r--r--src/js/handler/mxElbowEdgeHandler.js248
-rw-r--r--src/js/handler/mxGraphHandler.js916
-rw-r--r--src/js/handler/mxKeyHandler.js402
-rw-r--r--src/js/handler/mxPanningHandler.js390
-rw-r--r--src/js/handler/mxRubberband.js348
-rw-r--r--src/js/handler/mxSelectionCellsHandler.js260
-rw-r--r--src/js/handler/mxTooltipHandler.js317
-rw-r--r--src/js/handler/mxVertexHandler.js753
-rw-r--r--src/js/index.txt316
-rw-r--r--src/js/io/mxCellCodec.js170
-rw-r--r--src/js/io/mxChildChangeCodec.js149
-rw-r--r--src/js/io/mxCodec.js531
-rw-r--r--src/js/io/mxCodecRegistry.js137
-rw-r--r--src/js/io/mxDefaultKeyHandlerCodec.js88
-rw-r--r--src/js/io/mxDefaultPopupMenuCodec.js54
-rw-r--r--src/js/io/mxDefaultToolbarCodec.js301
-rw-r--r--src/js/io/mxEditorCodec.js246
-rw-r--r--src/js/io/mxGenericChangeCodec.js64
-rw-r--r--src/js/io/mxGraphCodec.js28
-rw-r--r--src/js/io/mxGraphViewCodec.js197
-rw-r--r--src/js/io/mxModelCodec.js80
-rw-r--r--src/js/io/mxObjectCodec.js983
-rw-r--r--src/js/io/mxRootChangeCodec.js83
-rw-r--r--src/js/io/mxStylesheetCodec.js210
-rw-r--r--src/js/io/mxTerminalChangeCodec.js42
-rw-r--r--src/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js206
-rw-r--r--src/js/layout/hierarchical/model/mxGraphHierarchyEdge.js174
-rw-r--r--src/js/layout/hierarchical/model/mxGraphHierarchyModel.js685
-rw-r--r--src/js/layout/hierarchical/model/mxGraphHierarchyNode.js210
-rw-r--r--src/js/layout/hierarchical/mxHierarchicalLayout.js623
-rw-r--r--src/js/layout/hierarchical/stage/mxCoordinateAssignment.js1836
-rw-r--r--src/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js25
-rw-r--r--src/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js674
-rw-r--r--src/js/layout/hierarchical/stage/mxMinimumCycleRemover.js131
-rw-r--r--src/js/layout/mxCircleLayout.js203
-rw-r--r--src/js/layout/mxCompactTreeLayout.js995
-rw-r--r--src/js/layout/mxCompositeLayout.js101
-rw-r--r--src/js/layout/mxEdgeLabelLayout.js165
-rw-r--r--src/js/layout/mxFastOrganicLayout.js591
-rw-r--r--src/js/layout/mxGraphLayout.js503
-rw-r--r--src/js/layout/mxParallelEdgeLayout.js198
-rw-r--r--src/js/layout/mxPartitionLayout.js240
-rw-r--r--src/js/layout/mxStackLayout.js381
-rw-r--r--src/js/model/mxCell.js806
-rw-r--r--src/js/model/mxCellPath.js163
-rw-r--r--src/js/model/mxGeometry.js277
-rw-r--r--src/js/model/mxGraphModel.js2622
-rw-r--r--src/js/mxClient.js643
-rw-r--r--src/js/shape/mxActor.js183
-rw-r--r--src/js/shape/mxArrow.js226
-rw-r--r--src/js/shape/mxCloud.js56
-rw-r--r--src/js/shape/mxConnector.js446
-rw-r--r--src/js/shape/mxCylinder.js319
-rw-r--r--src/js/shape/mxDoubleEllipse.js203
-rw-r--r--src/js/shape/mxEllipse.js132
-rw-r--r--src/js/shape/mxHexagon.js37
-rw-r--r--src/js/shape/mxImageShape.js405
-rw-r--r--src/js/shape/mxLabel.js427
-rw-r--r--src/js/shape/mxLine.js217
-rw-r--r--src/js/shape/mxMarker.js267
-rw-r--r--src/js/shape/mxPolyline.js146
-rw-r--r--src/js/shape/mxRectangleShape.js61
-rw-r--r--src/js/shape/mxRhombus.js172
-rw-r--r--src/js/shape/mxShape.js2045
-rw-r--r--src/js/shape/mxStencil.js1585
-rw-r--r--src/js/shape/mxStencilRegistry.js53
-rw-r--r--src/js/shape/mxStencilShape.js209
-rw-r--r--src/js/shape/mxSwimlane.js553
-rw-r--r--src/js/shape/mxText.js1811
-rw-r--r--src/js/shape/mxTriangle.js34
-rw-r--r--src/js/util/mxAnimation.js82
-rw-r--r--src/js/util/mxAutoSaveManager.js213
-rw-r--r--src/js/util/mxClipboard.js144
-rw-r--r--src/js/util/mxConstants.js1911
-rw-r--r--src/js/util/mxDictionary.js130
-rw-r--r--src/js/util/mxDivResizer.js151
-rw-r--r--src/js/util/mxDragSource.js594
-rw-r--r--src/js/util/mxEffects.js214
-rw-r--r--src/js/util/mxEvent.js1175
-rw-r--r--src/js/util/mxEventObject.js111
-rw-r--r--src/js/util/mxEventSource.js191
-rw-r--r--src/js/util/mxForm.js202
-rw-r--r--src/js/util/mxGuide.js364
-rw-r--r--src/js/util/mxImage.js40
-rw-r--r--src/js/util/mxImageBundle.js98
-rw-r--r--src/js/util/mxImageExport.js1412
-rw-r--r--src/js/util/mxLog.js410
-rw-r--r--src/js/util/mxMorphing.js239
-rw-r--r--src/js/util/mxMouseEvent.js241
-rw-r--r--src/js/util/mxObjectIdentity.js59
-rw-r--r--src/js/util/mxPanningManager.js262
-rw-r--r--src/js/util/mxPath.js314
-rw-r--r--src/js/util/mxPoint.js55
-rw-r--r--src/js/util/mxPopupMenu.js574
-rw-r--r--src/js/util/mxRectangle.js134
-rw-r--r--src/js/util/mxResources.js366
-rw-r--r--src/js/util/mxSession.js674
-rw-r--r--src/js/util/mxSvgCanvas2D.js1234
-rw-r--r--src/js/util/mxToolbar.js528
-rw-r--r--src/js/util/mxUndoManager.js229
-rw-r--r--src/js/util/mxUndoableEdit.js168
-rw-r--r--src/js/util/mxUrlConverter.js141
-rw-r--r--src/js/util/mxUtils.js3920
-rw-r--r--src/js/util/mxWindow.js1065
-rw-r--r--src/js/util/mxXmlCanvas2D.js715
-rw-r--r--src/js/util/mxXmlRequest.js425
-rw-r--r--src/js/view/mxCellEditor.js522
-rw-r--r--src/js/view/mxCellOverlay.js233
-rw-r--r--src/js/view/mxCellRenderer.js1480
-rw-r--r--src/js/view/mxCellState.js375
-rw-r--r--src/js/view/mxCellStatePreview.js223
-rw-r--r--src/js/view/mxConnectionConstraint.js42
-rw-r--r--src/js/view/mxEdgeStyle.js1302
-rw-r--r--src/js/view/mxGraph.js11176
-rw-r--r--src/js/view/mxGraphSelectionModel.js435
-rw-r--r--src/js/view/mxGraphView.js2545
-rw-r--r--src/js/view/mxLayoutManager.js375
-rw-r--r--src/js/view/mxMultiplicity.js257
-rw-r--r--src/js/view/mxOutline.js649
-rw-r--r--src/js/view/mxPerimeter.js484
-rw-r--r--src/js/view/mxPrintPreview.js801
-rw-r--r--src/js/view/mxSpaceManager.js460
-rw-r--r--src/js/view/mxStyleRegistry.js70
-rw-r--r--src/js/view/mxStylesheet.js266
-rw-r--r--src/js/view/mxSwimlaneManager.js449
-rw-r--r--src/js/view/mxTemporaryCellStates.js105
-rw-r--r--src/resources/editor.properties5
-rw-r--r--src/resources/graph.properties11
-rw-r--r--styles/Xcos-style.xml932
-rw-r--r--styles/new.xml974
506 files changed, 95360 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..a417400
--- /dev/null
+++ b/README.md
@@ -0,0 +1,18 @@
+# Xcos on Web
+Xcos is an open source graphic simulator available with Scilab. Scilab can be installed on all major Operating Systems
+
+The main aim of this project will be to port core functionalities of Xcos to a browser-only version that can be used without installing additional plugins or software.
+
+## Installation
+Host the contents of this repository on Apache2 and run index.html
+
+## License information
+This project has a Creative Commons Public License.
+
+For further information please refer to 'license.txt'-file
+
+## Support and Contact
+
+You can get support in the community mailing list and forums:
+
+ https://groups.google.com/forum/#!forum/xcos-on-web
diff --git a/blocks/3DSCOPE.svg b/blocks/3DSCOPE.svg
new file mode 100644
index 0000000..8e13deb
--- /dev/null
+++ b/blocks/3DSCOPE.svg
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg2847" xmlns="http://www.w3.org/2000/svg" height="30.443" width="32.392" version="1.1">
+ <g id="layer1" transform="translate(-1140.9469,-582.85472)">
+ <g id="g9544" transform="matrix(0,0.68137179,-0.68137179,0,1173.6044,577.49537)">
+ <path id="path8919" stroke-linejoin="round" style="stroke-dasharray:none;" d="m36.225,1.8575s22.499,10.384,12.115,37.859-18.605-37.858-20.336-37.858-19.901,9.1185-18.605,32.234c1.2977,23.148,14.062-29.421,14.062-29.421" stroke="#000" stroke-linecap="round" stroke-miterlimit="4" stroke-width="2.93525505" fill="none"/>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/ANDBLK.svg b/blocks/ANDBLK.svg
new file mode 100644
index 0000000..c55e06a
--- /dev/null
+++ b/blocks/ANDBLK.svg
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="44"
+ height="80"
+ id="svg2">
+ <defs
+ id="defs10" />
+ <g
+ transform="translate(-28,15.075736)"
+ id="layer1"
+ style="fill:#cc0000">
+ <path
+ d="M 50,45 V 63"
+ id="path3059"
+ style="stroke:#cc0000;stroke-width:1.69705629;stroke-linecap:butt;stroke-linejoin:miter" />
+ <path
+ d="m 60.128,6.3781 0,-19.506"
+ id="path3061"
+ style="stroke:#cc0000;stroke-width:1.74340975;stroke-linecap:butt;stroke-linejoin:miter" />
+ <path
+ d="m 39.872,7.1283 0,-20.257"
+ id="path3944"
+ style="stroke:#cc0000;stroke-width:1.74340975;stroke-linecap:butt;stroke-linejoin:miter" />
+ <path
+ d="M 70,5 68.571,5 31.429,5 30,5 30,6.4286 30,25.476 c 0,11.268 9,20 20,20 11,0 20,-8.7321 20,-20 L 70,6.428 70,5 z m -2.8571,2.8571 0,17.58916 0,0.02976 c 0,9.7607 -7.64,16.667 -17.143,16.667 -9.5029,0 -17.143,-7.3822 -17.143,-17.143 l 0,-17.143 34.286,0 0,8e-5 z"
+ id="path2884"
+ style="text-indent:0pt;text-align:start;text-transform:none;direction:ltr" />
+ </g>
+</svg>
diff --git a/blocks/ASCOPE.svg b/blocks/ASCOPE.svg
new file mode 100644
index 0000000..bd06723
--- /dev/null
+++ b/blocks/ASCOPE.svg
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg11300" xmlns="http://www.w3.org/2000/svg" height="44.822" width="41.062" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <title id="title3289">base scilan</title>
+ <defs id="defs3">
+ <radialGradient id="radialGradient20899" gradientUnits="userSpaceOnUse" cy="32.267" cx="23.994" gradientTransform="matrix(2.2986117,0,0,1.8027614,-75.446386,-24.554065)" r="19.089">
+ <stop id="stop2224" stop-color="#5187d6" offset="0"/>
+ <stop id="stop2227" stop-color="#1e4580" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient2854" y2="24.238" gradientUnits="userSpaceOnUse" x2="12.499" gradientTransform="matrix(0,-5.2061514,2.5688251,0,-79.265723,164.6207)" y1="12.538" x1="8.8208">
+ <stop id="stop2182" stop-color="#FFF" offset="0"/>
+ <stop id="stop2184" stop-color="#FFF" stop-opacity="0" offset="1"/>
+ </linearGradient>
+ </defs>
+ <g id="layer1" transform="translate(-2.8842799e-7,-3.1783548)">
+ <g id="layer1-0" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544" transform="translate(-2.0023665,2.2530854)">
+ <g id="g9570" transform="translate(0.34662852,-1.7289484)">
+ <g id="g28489" transform="translate(2.3098495,0.12996954)">
+ <g id="g20882" transform="translate(51.47752,-82.884097)">
+ <rect id="rect1314" stroke-linejoin="round" style="stroke-dasharray:none;" transform="matrix(0,-0.99999997,0.99999997,0,-49.042742,89.574337)" fill-rule="evenodd" stroke-dashoffset="0" rx="2.2025" ry="2.2025" height="37.631" width="48.319" stroke="#173562" stroke-linecap="round" stroke-miterlimit="4" y="2.4454" x="-44.453" stroke-width="1.4676" fill="url(#radialGradient20899)"/>
+ <path id="path28138" stroke-linejoin="round" d="M8.6382,35.758c27.557-0.173,27.904-0.173,27.904-0.173l0.17331-33.97" transform="translate(-49.042742,89.574337)" stroke="#000" stroke-linecap="round" stroke-width="1.46762753px" fill="none"/>
+ <path id="rect2178" opacity="0.43181817" d="m-45.868,133.4,12.954,0c1.0099-5.0153,1.5849-10.576,1.5849-16.442,0-12.211-2.4433-23.147-6.2788-30.45h-8.2599v46.892z" fill-rule="evenodd" fill="url(#linearGradient2854)"/>
+ <path id="path18808" stroke-linejoin="round" style="stroke-dasharray:none;" d="m-27.782,121.73-0.57149-0.2893-0.56662-0.28848-0.55952-0.28909-0.55222-0.2893-1.0738-0.57777-0.51996-0.2893-0.50637-0.28828-0.49217-0.28929-0.47533-0.28849-0.4597-0.28929-0.44064-0.2893-0.42156-0.28909-0.40027-0.28848-0.37957-0.2893-0.35726-0.28929-0.33372-0.28848-0.30979-0.2893-0.28524-0.28828-0.25927-0.28929-0.2329-0.2893-0.20612-0.28848-0.17913-0.2893-0.15053-0.28909-0.12274-0.28848-0.09433-0.28929-0.06513-0.2893-0.03713-0.28848-0.0071-0.2893,0.0213-0.28909,0.05072-0.28848,0.07932-0.2893,0.10772-0.28929,0.13633-0.28848,0.16473-0.2891,0.19192-0.28929,0.2195-0.28849,0.24568-0.28929,0.27185-0.28929,0.29619-0.28849,0.32175-0.28909,0.34569-0.28929,0.36761-0.28849,0.39073-0.28929,0.41041-0.28909,0.4311-0.28849,0.44936-0.2893,0.46762-0.28929,0.48344-0.28848,0.49927-0.2893,0.51267-0.28828,0.52623-0.28929,0.5372-0.2893,0.54674-0.28929,0.55566-0.28849,0.56338-0.28909,0.56905-0.28848,0.57291-0.28929,0.57616-0.2893,0.57758-0.28848,0.57778-0.2893,0.57696-0.28909,0.57291-0.28848,0.56905-0.2893,0.56337-0.28929,0.55628-0.28849,0.54775-0.2891,0.53721-0.28929,0.52625-0.28848,0.51347-0.2893,0.49927-0.28929,0.48486-0.28849,0.46762-0.28909,0.45017-0.28929,0.43191-0.28849,0.41122-0.28929,0.39073-0.2893,0.36923-0.28827,0.34549-0.2893,0.32257-0.28848,0.29802-0.2893,0.27266-0.28929,0.24649-0.28909,0.22032-0.28849,0.19334-0.28929,0.16473-0.2893,0.13795-0.28848,0.10854-0.28909,0.08075-0.28849,0.05152-0.28929,0.02229-0.28929-0.0065-0.2893-0.03551-0.28849-0.06431-0.28908-0.09352-0.28849-0.12111-0.2893-0.15073-0.28929" stroke="#ef2929" stroke-linecap="round" stroke-miterlimit="4" stroke-width="2.93525505" fill="none"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/BACHE.svg b/blocks/BACHE.svg
new file mode 100644
index 0000000..af85023
--- /dev/null
+++ b/blocks/BACHE.svg
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg11300" xmlns="http://www.w3.org/2000/svg" height="41.089" width="41.091" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <title id="title3289">base scilan</title>
+ <defs id="defs3">
+ <clipPath id="clipPath5612">
+ <rect id="rect5614" rx="11.749" ry="11.749" height="39.342" width="80.071" y="17.807" x="-186.1" fill="#00000a"/>
+ </clipPath>
+ <radialGradient id="radialGradient31624" gradientUnits="userSpaceOnUse" cy="35.878" cx="24.446" gradientTransform="matrix(2.928002,0,0,2.928002,-99.971948,-61.975014)" r="20.531">
+ <stop id="stop11522" stop-color="#FFF" offset="0"/>
+ <stop id="stop11524" stop-color="#dcdcdc" offset="1"/>
+ </radialGradient>
+ </defs>
+ <g id="layer1" transform="translate(1.2072443e-8,-6.910638)">
+ <g id="layer1-0" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544" transform="translate(-2.0023665,2.2530854)">
+ <g id="g31613" transform="translate(5.4543789,7.7672827)">
+ <g id="g5646" stroke-linejoin="bevel" stroke-dashoffset="0" transform="translate(0.34662852,-10.907711)" stroke-linecap="butt" stroke-miterlimit="10">
+ <rect id="rect11518" style="color:#000000;stroke-dasharray:none;" transform="matrix(0,-1,1,0,0,0)" fill-rule="evenodd" rx="8.0056" ry="8.0056" height="58.796" width="58.796" stroke="#2e3436" y="-4.4085" x="-57.793" stroke-width="1.4676" fill="url(#radialGradient31624)"/>
+ <rect id="rect11518-2" style="color:#000000;stroke-dasharray:none;" transform="matrix(0,-0.9977293,1.1929217,0,-2.2970371,-117.17148)" clip-path="url(#clipPath5612)" fill-rule="evenodd" rx="8.0056" ry="8.0056" height="58.796" width="58.796" stroke="#4e9a06" y="-11.301" x="-175.34" stroke-width="1.4676" fill="#73d216"/>
+ <rect id="rect11528" style="color:#000000;stroke-dasharray:none;" transform="matrix(0,-1,1,0,0,0)" rx="6.2266" ry="6.2266" height="55.325" width="55.325" stroke="#FFF" y="-3.0194" x="-56.057" stroke-width="1.4676" fill="none"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/BARXY.svg b/blocks/BARXY.svg
new file mode 100644
index 0000000..3bc0b38
--- /dev/null
+++ b/blocks/BARXY.svg
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="425.2px" height="425.199px" viewBox="0 0 425.2 425.199" enable-background="new 0 0 425.2 425.199" xml:space="preserve">
+<g>
+
+ <line fill="none" stroke="#FFFFFF" stroke-width="15" stroke-linecap="round" x1="39.986" y1="217.148" x2="363.417" y2="416.604"/>
+
+ <line fill="none" stroke="#ECEDED" stroke-width="15" stroke-linecap="round" x1="40.541" y1="214.482" x2="369.626" y2="379.954"/>
+ <line fill="none" stroke="#D9DADB" stroke-width="15" stroke-linecap="round" x1="39.987" y1="217.15" x2="376.828" y2="341.008"/>
+ <line fill="none" stroke="#C6C7C8" stroke-width="15" stroke-linecap="round" x1="39.986" y1="217.15" x2="382.73" y2="294.549"/>
+
+ <line fill="none" stroke="#B1B3B4" stroke-width="15" stroke-linecap="round" x1="40.142" y1="216.395" x2="386.122" y2="244.632"/>
+
+ <line fill="none" stroke="#9C9E9F" stroke-width="15" stroke-linecap="round" x1="39.986" y1="217.148" x2="386.225" y2="197.694"/>
+ <line fill="none" stroke="#87888A" stroke-width="15" stroke-linecap="round" x1="39.986" y1="217.148" x2="383.828" y2="152.07"/>
+
+ <line fill="none" stroke="#707173" stroke-width="15" stroke-linecap="round" x1="39.986" y1="217.148" x2="377.892" y2="100.252"/>
+ <line fill="none" stroke="#58585A" stroke-width="15" stroke-linecap="round" x1="39.985" y1="217.148" x2="369.561" y2="53.98"/>
+ <line fill="none" stroke="#1A171B" stroke-width="15" stroke-linecap="round" x1="39.985" y1="217.15" x2="358.949" y2="10.596"/>
+</g>
+</svg>
diff --git a/blocks/BIGSOM_f.svg b/blocks/BIGSOM_f.svg
new file mode 100644
index 0000000..f76cc1c
--- /dev/null
+++ b/blocks/BIGSOM_f.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="40" xmlns="http://www.w3.org/2000/svg" version="1.1" height="40">
+ <text y="30" x="14" font-size="30" font-family="serif" fill="black">Σ</text>
+</svg>
diff --git a/blocks/BPLATFORM.svg b/blocks/BPLATFORM.svg
new file mode 100644
index 0000000..734ca36
--- /dev/null
+++ b/blocks/BPLATFORM.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg2855" xmlns="http://www.w3.org/2000/svg" height="28.462" width="16.614" version="1.1">
+ <g id="layer1" transform="translate(-366.69289,-518.13104)">
+ <g id="layer1-0" transform="matrix(0,0.68137179,-0.68137179,0,392.99674,515.6967)">
+ <g id="g9544" transform="translate(2.4634854,6.2067135)">
+ <g id="g9534" stroke="#2e3436" stroke-miterlimit="4" transform="translate(-1.3123521,4.360673)">
+ <path id="path9523" stroke-linejoin="round" style="stroke-dasharray:none;" d="m-37.907-2.0708a5.5503,5.5503,0,1,1,-11.101,0,5.5503,5.5503,0,1,1,11.101,0z" transform="matrix(0,-0.9977947,0.9977947,0,11.023636,-27.516685)" stroke-linecap="round" stroke-width="2" fill="#eeeeec"/>
+ <rect id="rect9525" stroke-linejoin="round" style="stroke-dasharray:none;" transform="matrix(0,-1,1,0,0,0)" height="6.0094" width="22.388" stroke-linecap="round" y="18.855" x="-27.039" stroke-width="1.9956" fill="#eeeeec"/>
+ <path id="path9531" stroke-linejoin="miter" style="stroke-dasharray:none;" d="M25.74,17.271,29.303,13.707,32.867,17.271,36.43,13.707l3.5636,3.5635s2.9853-3.5213,3.202-4.3607" stroke-linecap="butt" stroke-width="1.99558938" fill="none"/>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/CCS.svg b/blocks/CCS.svg
new file mode 100644
index 0000000..dc6c316
--- /dev/null
+++ b/blocks/CCS.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="40" xmlns="http://www.w3.org/2000/svg" version="1.1" height="40">
+ <g id="layer1" stroke-linejoin="round" stroke="#C00" stroke-linecap="round" stroke-width="2px">
+ <path d="M2,20h36" fill="none"/>
+ <polygon points="38,20,30,15,30,25" fill="#C00"/>
+ </g>
+</svg>
diff --git a/blocks/CEVENTSCOPE.svg b/blocks/CEVENTSCOPE.svg
new file mode 100644
index 0000000..a489ba7
--- /dev/null
+++ b/blocks/CEVENTSCOPE.svg
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg11300" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="44.822" width="41.062" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <metadata id="metadata39">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <title id="title3289">base scilan</title>
+ <defs id="defs3">
+ <radialGradient id="radialGradient20899" gradientUnits="userSpaceOnUse" cy="32.267" cx="23.994" gradientTransform="matrix(2.2986117,0,0,1.8027614,-75.446386,-24.554065)" r="19.089">
+ <stop id="stop2224" stop-color="#5187d6" offset="0"/>
+ <stop id="stop2227" stop-color="#1e4580" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient2854" y2="24.238" gradientUnits="userSpaceOnUse" x2="12.499" gradientTransform="matrix(0,-5.2061514,2.5688251,0,-79.265723,164.6207)" y1="12.538" x1="8.8208">
+ <stop id="stop2182" stop-color="#FFF" offset="0"/>
+ <stop id="stop2184" stop-color="#FFF" stop-opacity="0" offset="1"/>
+ </linearGradient>
+ </defs>
+ <g id="layer1" transform="translate(-2.8842799e-7,-3.1783548)">
+ <g id="layer1-0" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544" transform="translate(-2.0023665,2.2530854)">
+ <g id="g9570" transform="translate(0.34662852,-1.7289484)">
+ <g id="g28489" transform="translate(2.3098495,0.12996954)">
+ <g id="g20882" transform="translate(51.47752,-82.884097)">
+ <rect id="rect1314" stroke-linejoin="round" style="stroke-dasharray:none;" transform="matrix(0,-0.99999997,0.99999997,0,-49.042742,89.574337)" fill-rule="evenodd" stroke-dashoffset="0" rx="2.2025" ry="2.2025" height="37.631" width="48.319" stroke="#173562" stroke-linecap="round" stroke-miterlimit="4" y="2.4454" x="-44.453" stroke-width="1.4676" fill="url(#radialGradient20899)"/>
+ <path id="path28138" stroke-linejoin="round" d="M8.6382,35.758c27.557-0.173,27.904-0.173,27.904-0.173l0.17331-33.97" transform="translate(-49.042742,89.574337)" stroke="#000" stroke-linecap="round" stroke-width="1.46762753px" fill="none"/>
+ <path id="rect2178" opacity="0.43181817" d="m-45.868,133.4,12.954,0c1.0099-5.0153,1.5849-10.576,1.5849-16.442,0-12.211-2.4433-23.147-6.2788-30.45h-8.2599v46.892z" fill-rule="evenodd" fill="url(#linearGradient2854)"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ <path id="path2188" stroke-linejoin="round" style="stroke-dasharray:none;" d="m13.665,29.974,4.7566-10.919,5.7799,10.589,6.8184-5.8677,0.06242-4.1123-6.3304,5.6237-6.5406-11.928-6.0101,14.643,1.4637,1.9713z" fill-rule="evenodd" stroke="#8ae234" stroke-linecap="round" stroke-miterlimit="10" stroke-width="0.91817689" fill="#73d216"/>
+</svg>
diff --git a/blocks/CLOCK_c.svg b/blocks/CLOCK_c.svg
new file mode 100644
index 0000000..9ade9ae
--- /dev/null
+++ b/blocks/CLOCK_c.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg2855" xmlns="http://www.w3.org/2000/svg" height="33.259" width="33.259" version="1.1">
+ <defs id="defs2857"></defs>
+ <g id="layer1" transform="translate(-358.3703,-515.73248)">
+ <g id="g31086" stroke-linecap="round" stroke-miterlimit="4" transform="matrix(0,0.68137177,-0.68137177,0,390.03482,516.42453)">
+ <path id="path35549-4" stroke-linejoin="round" style="stroke-dasharray:none;" d="m31.161,16.911a14.911,14.911,0,1,1,-29.821,0,14.911,14.911,0,1,1,29.821,0z" stroke-dashoffset="0" transform="matrix(1.5876104,0,0,1.5876104,-2.4081283,-4.7821171)" stroke="#C00" stroke-width="0.92442554" fill="none"/>
+ <path id="path34778" stroke-linejoin="round" style="stroke-dasharray:none;" d="m16.406,17.281a1.2188,1.2188,0,1,1,-2.4375,0,1.2188,1.2188,0,1,1,2.4375,0z" fill-rule="evenodd" transform="matrix(2.073295,0,0,2.073295,-7.310224,-13.13682)" stroke-dashoffset="0" stroke="#000" stroke-width="0.70787203" fill="#000"/>
+ <path id="path35559" stroke-linejoin="miter" style="stroke-dasharray:none;" d="M22.177,20.718,13.156,13.14" stroke="#000" stroke-width="2.93525505" fill="none"/>
+ <path id="path35561" stroke-linejoin="miter" style="stroke-dasharray:none;" d="m19.409,29.777,2.96-4.4933" stroke="#000" stroke-width="2.93525505" fill="none"/>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/CLOCK_f.svg b/blocks/CLOCK_f.svg
new file mode 100644
index 0000000..cc18ac3
--- /dev/null
+++ b/blocks/CLOCK_f.svg
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg2855" xmlns="http://www.w3.org/2000/svg" height="33.259" width="33.259" version="1.1">
+ <g id="layer1" transform="translate(-358.3703,-515.73248)">
+ <g id="g31086" stroke="#000" stroke-linecap="round" stroke-miterlimit="4" transform="matrix(0,0.68137177,-0.68137177,0,390.03482,516.42453)">
+ <path id="path35549-4" stroke-linejoin="round" style="stroke-dasharray:none;" d="m31.161,16.911a14.911,14.911,0,1,1,-29.821,0,14.911,14.911,0,1,1,29.821,0z" stroke-dashoffset="0" transform="matrix(1.5876104,0,0,1.5876104,-2.4081283,-4.7821171)" stroke-width="0.92442554" fill="none"/>
+ <path id="path34778" stroke-linejoin="round" style="stroke-dasharray:none;" d="m16.406,17.281a1.2188,1.2188,0,1,1,-2.4375,0,1.2188,1.2188,0,1,1,2.4375,0z" fill-rule="evenodd" transform="matrix(2.073295,0,0,2.073295,-7.310224,-13.13682)" stroke-dashoffset="0" stroke-width="0.70787203" fill="#000"/>
+ <path id="path35559" stroke-linejoin="miter" style="stroke-dasharray:none;" d="M22.177,20.718,13.156,13.14" stroke-width="2.93525505" fill="none"/>
+ <path id="path35561" stroke-linejoin="miter" style="stroke-dasharray:none;" d="m19.409,29.777,2.96-4.4933" stroke-width="2.93525505" fill="none"/>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/CMSCOPE.svg b/blocks/CMSCOPE.svg
new file mode 100644
index 0000000..bd06723
--- /dev/null
+++ b/blocks/CMSCOPE.svg
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg11300" xmlns="http://www.w3.org/2000/svg" height="44.822" width="41.062" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <title id="title3289">base scilan</title>
+ <defs id="defs3">
+ <radialGradient id="radialGradient20899" gradientUnits="userSpaceOnUse" cy="32.267" cx="23.994" gradientTransform="matrix(2.2986117,0,0,1.8027614,-75.446386,-24.554065)" r="19.089">
+ <stop id="stop2224" stop-color="#5187d6" offset="0"/>
+ <stop id="stop2227" stop-color="#1e4580" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient2854" y2="24.238" gradientUnits="userSpaceOnUse" x2="12.499" gradientTransform="matrix(0,-5.2061514,2.5688251,0,-79.265723,164.6207)" y1="12.538" x1="8.8208">
+ <stop id="stop2182" stop-color="#FFF" offset="0"/>
+ <stop id="stop2184" stop-color="#FFF" stop-opacity="0" offset="1"/>
+ </linearGradient>
+ </defs>
+ <g id="layer1" transform="translate(-2.8842799e-7,-3.1783548)">
+ <g id="layer1-0" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544" transform="translate(-2.0023665,2.2530854)">
+ <g id="g9570" transform="translate(0.34662852,-1.7289484)">
+ <g id="g28489" transform="translate(2.3098495,0.12996954)">
+ <g id="g20882" transform="translate(51.47752,-82.884097)">
+ <rect id="rect1314" stroke-linejoin="round" style="stroke-dasharray:none;" transform="matrix(0,-0.99999997,0.99999997,0,-49.042742,89.574337)" fill-rule="evenodd" stroke-dashoffset="0" rx="2.2025" ry="2.2025" height="37.631" width="48.319" stroke="#173562" stroke-linecap="round" stroke-miterlimit="4" y="2.4454" x="-44.453" stroke-width="1.4676" fill="url(#radialGradient20899)"/>
+ <path id="path28138" stroke-linejoin="round" d="M8.6382,35.758c27.557-0.173,27.904-0.173,27.904-0.173l0.17331-33.97" transform="translate(-49.042742,89.574337)" stroke="#000" stroke-linecap="round" stroke-width="1.46762753px" fill="none"/>
+ <path id="rect2178" opacity="0.43181817" d="m-45.868,133.4,12.954,0c1.0099-5.0153,1.5849-10.576,1.5849-16.442,0-12.211-2.4433-23.147-6.2788-30.45h-8.2599v46.892z" fill-rule="evenodd" fill="url(#linearGradient2854)"/>
+ <path id="path18808" stroke-linejoin="round" style="stroke-dasharray:none;" d="m-27.782,121.73-0.57149-0.2893-0.56662-0.28848-0.55952-0.28909-0.55222-0.2893-1.0738-0.57777-0.51996-0.2893-0.50637-0.28828-0.49217-0.28929-0.47533-0.28849-0.4597-0.28929-0.44064-0.2893-0.42156-0.28909-0.40027-0.28848-0.37957-0.2893-0.35726-0.28929-0.33372-0.28848-0.30979-0.2893-0.28524-0.28828-0.25927-0.28929-0.2329-0.2893-0.20612-0.28848-0.17913-0.2893-0.15053-0.28909-0.12274-0.28848-0.09433-0.28929-0.06513-0.2893-0.03713-0.28848-0.0071-0.2893,0.0213-0.28909,0.05072-0.28848,0.07932-0.2893,0.10772-0.28929,0.13633-0.28848,0.16473-0.2891,0.19192-0.28929,0.2195-0.28849,0.24568-0.28929,0.27185-0.28929,0.29619-0.28849,0.32175-0.28909,0.34569-0.28929,0.36761-0.28849,0.39073-0.28929,0.41041-0.28909,0.4311-0.28849,0.44936-0.2893,0.46762-0.28929,0.48344-0.28848,0.49927-0.2893,0.51267-0.28828,0.52623-0.28929,0.5372-0.2893,0.54674-0.28929,0.55566-0.28849,0.56338-0.28909,0.56905-0.28848,0.57291-0.28929,0.57616-0.2893,0.57758-0.28848,0.57778-0.2893,0.57696-0.28909,0.57291-0.28848,0.56905-0.2893,0.56337-0.28929,0.55628-0.28849,0.54775-0.2891,0.53721-0.28929,0.52625-0.28848,0.51347-0.2893,0.49927-0.28929,0.48486-0.28849,0.46762-0.28909,0.45017-0.28929,0.43191-0.28849,0.41122-0.28929,0.39073-0.2893,0.36923-0.28827,0.34549-0.2893,0.32257-0.28848,0.29802-0.2893,0.27266-0.28929,0.24649-0.28909,0.22032-0.28849,0.19334-0.28929,0.16473-0.2893,0.13795-0.28848,0.10854-0.28909,0.08075-0.28849,0.05152-0.28929,0.02229-0.28929-0.0065-0.2893-0.03551-0.28849-0.06431-0.28908-0.09352-0.28849-0.12111-0.2893-0.15073-0.28929" stroke="#ef2929" stroke-linecap="round" stroke-miterlimit="4" stroke-width="2.93525505" fill="none"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/CSCOPXY.svg b/blocks/CSCOPXY.svg
new file mode 100644
index 0000000..1caa482
--- /dev/null
+++ b/blocks/CSCOPXY.svg
@@ -0,0 +1,150 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ id="svg2873"
+ height="40"
+ width="40"
+ version="1.1"
+ inkscape:version="0.48.3.1 r9886"
+ sodipodi:docname="CSCOPXY.svg">
+ <metadata
+ id="metadata17">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="640"
+ inkscape:window-height="480"
+ id="namedview15"
+ showgrid="false"
+ inkscape:zoom="5.9"
+ inkscape:cx="20"
+ inkscape:cy="20"
+ inkscape:window-x="524"
+ inkscape:window-y="263"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg2873" />
+ <defs
+ id="defs2875">
+ <clipPath
+ id="clipPath37222">
+ <rect
+ id="rect37224"
+ stroke-linejoin="round"
+ style="stroke-dasharray:none;"
+ height="8.7626"
+ width="29.575"
+ stroke="#8ae234"
+ stroke-linecap="round"
+ stroke-miterlimit="10"
+ y="9.0136"
+ x="-67.328"
+ stroke-width="0.96708"
+ fill="#73d216" />
+ </clipPath>
+ <clipPath
+ id="clipPath37216">
+ <rect
+ id="rect37218"
+ stroke-linejoin="round"
+ style="stroke-dasharray:none;"
+ height="8.9499"
+ width="41.562"
+ stroke="#8ae234"
+ stroke-linecap="round"
+ stroke-miterlimit="10"
+ y="8.3582"
+ x="-79.131"
+ stroke-width="0.99985"
+ fill="#73d216" />
+ </clipPath>
+ </defs>
+ <g
+ id="layer1"
+ transform="translate(4,-1012.3622)">
+ <g
+ id="g37398"
+ transform="matrix(0,0.68137179,-0.68137179,0,33.767664,1008.7739)"
+ style="fill:none;stroke:#73d216;stroke-linecap:round;stroke-linejoin:round">
+ <path
+ id="path36671"
+ style="stroke-width:1.04385293;stroke-miterlimit:10;stroke-dasharray:none"
+ d="m -57.511,49.358 a 12.872,7.1445 0 1 1 -25.744,0 12.872,7.1445 0 1 1 25.744,0 z"
+ transform="matrix(0,-1.3706031,1.4422526,0,-36.442489,-76.260669)"
+ stroke-miterlimit="10"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path36700"
+ style="stroke-width:0.8847906;stroke-miterlimit:10;stroke-dasharray:none"
+ d="m -41.922,18.064 a 10.451,6.1998 0 1 1 -20.902,0 10.451,6.1998 0 1 1 20.902,0 z"
+ clip-path="url(#clipPath37222)"
+ transform="matrix(0,-1.6759951,1.6416401,0,26.234339,-67.572024)"
+ stroke-miterlimit="10"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path36702"
+ style="stroke-width:0.91477579;stroke-miterlimit:10;stroke-dasharray:none"
+ d="m -47.118,7.908 a 11.219,6.1998 0 1 1 -22.437,0 11.219,6.1998 0 1 1 22.437,0 z"
+ clip-path="url(#clipPath37216)"
+ transform="matrix(0,-1.563745,1.646025,0,0.06331757,-71.01866)"
+ stroke-miterlimit="10"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path37226"
+ d="m 13.995,37.71 0.17262,-35.009 0,0"
+ inkscape:connector-curvature="0"
+ style="stroke-width:1.46831942px" />
+ <path
+ id="path37396"
+ d="m 55.07,37.537 0.17331,-34.663 0,0"
+ inkscape:connector-curvature="0"
+ style="stroke-width:1.46762753px" />
+ </g>
+ </g>
+ <text
+ xml:space="preserve"
+ style="font-size:12.98749638px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="2.2853103"
+ y="11.432591"
+ id="text3002"
+ sodipodi:linespacing="125%"
+ transform="scale(0.99146695,1.0086065)"><tspan
+ sodipodi:role="line"
+ id="tspan3004"
+ x="2.2853103"
+ y="11.432591">x</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:10.85655975px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.9081794"
+ y="39.433331"
+ id="text3002-1"
+ sodipodi:linespacing="125%"
+ transform="scale(1.1816932,0.84624335)"><tspan
+ sodipodi:role="line"
+ id="tspan3004-1"
+ x="1.9081794"
+ y="39.433331">y</tspan></text>
+</svg>
diff --git a/blocks/CSCOPXY3D.svg b/blocks/CSCOPXY3D.svg
new file mode 100644
index 0000000..074d43e
--- /dev/null
+++ b/blocks/CSCOPXY3D.svg
@@ -0,0 +1,162 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:dc="http://purl.org/dc/elements/1.1/"
+ xmlns:cc="http://creativecommons.org/ns#"
+ xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ id="svg2873"
+ height="40"
+ width="40"
+ version="1.1"
+ inkscape:version="0.48.3.1 r9886"
+ sodipodi:docname="CSCOPXY3D.svg">
+ <metadata
+ id="metadata17">
+ <rdf:RDF>
+ <cc:Work
+ rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type
+ rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1"
+ objecttolerance="10"
+ gridtolerance="10"
+ guidetolerance="10"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:window-width="640"
+ inkscape:window-height="480"
+ id="namedview15"
+ showgrid="false"
+ inkscape:zoom="5.9"
+ inkscape:cx="20"
+ inkscape:cy="20"
+ inkscape:window-x="978"
+ inkscape:window-y="267"
+ inkscape:window-maximized="0"
+ inkscape:current-layer="svg2873" />
+ <defs
+ id="defs2875">
+ <clipPath
+ id="clipPath37222">
+ <rect
+ id="rect37224"
+ stroke-linejoin="round"
+ style="stroke-dasharray:none;"
+ height="8.7626"
+ width="29.575"
+ stroke="#8ae234"
+ stroke-linecap="round"
+ stroke-miterlimit="10"
+ y="9.0136"
+ x="-67.328"
+ stroke-width="0.96708"
+ fill="#73d216" />
+ </clipPath>
+ <clipPath
+ id="clipPath37216">
+ <rect
+ id="rect37218"
+ stroke-linejoin="round"
+ style="stroke-dasharray:none;"
+ height="8.9499"
+ width="41.562"
+ stroke="#8ae234"
+ stroke-linecap="round"
+ stroke-miterlimit="10"
+ y="8.3582"
+ x="-79.131"
+ stroke-width="0.99985"
+ fill="#73d216" />
+ </clipPath>
+ </defs>
+ <g
+ id="layer1"
+ transform="translate(4,-1012.3622)">
+ <g
+ id="g37398"
+ transform="matrix(0,0.68137179,-0.68137179,0,33.767664,1008.7739)"
+ style="fill:none;stroke:#73d216;stroke-linecap:round;stroke-linejoin:round">
+ <path
+ id="path36671"
+ style="stroke-width:1.04385293;stroke-miterlimit:10;stroke-dasharray:none"
+ d="m -57.511,49.358 a 12.872,7.1445 0 1 1 -25.744,0 12.872,7.1445 0 1 1 25.744,0 z"
+ transform="matrix(0,-1.3706031,1.4422526,0,-36.442489,-76.260669)"
+ stroke-miterlimit="10"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path36700"
+ style="stroke-width:0.8847906;stroke-miterlimit:10;stroke-dasharray:none"
+ d="m -41.922,18.064 a 10.451,6.1998 0 1 1 -20.902,0 10.451,6.1998 0 1 1 20.902,0 z"
+ clip-path="url(#clipPath37222)"
+ transform="matrix(0,-1.6759951,1.6416401,0,26.234339,-67.572024)"
+ stroke-miterlimit="10"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path36702"
+ style="stroke-width:0.91477579;stroke-miterlimit:10;stroke-dasharray:none"
+ d="m -47.118,7.908 a 11.219,6.1998 0 1 1 -22.437,0 11.219,6.1998 0 1 1 22.437,0 z"
+ clip-path="url(#clipPath37216)"
+ transform="matrix(0,-1.563745,1.646025,0,0.06331757,-71.01866)"
+ stroke-miterlimit="10"
+ inkscape:connector-curvature="0" />
+ <path
+ id="path37226"
+ d="m 13.995,37.71 0.17262,-35.009 0,0"
+ inkscape:connector-curvature="0"
+ style="stroke-width:1.46831942px" />
+ <path
+ id="path37396"
+ d="m 55.07,37.537 0.17331,-34.663 0,0"
+ inkscape:connector-curvature="0"
+ style="stroke-width:1.46762753px" />
+ </g>
+ </g>
+ <text
+ xml:space="preserve"
+ style="font-size:12.98749638px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="2.2853103"
+ y="11.432591"
+ id="text3002"
+ sodipodi:linespacing="125%"
+ transform="scale(0.99146695,1.0086065)"><tspan
+ sodipodi:role="line"
+ id="tspan3004"
+ x="2.2853103"
+ y="11.432591">x</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:12.30455589px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.5335083"
+ y="24.0536"
+ id="text3002-1"
+ sodipodi:linespacing="125%"
+ transform="scale(1.1202757,0.8926374)"><tspan
+ sodipodi:role="line"
+ id="tspan3004-1"
+ x="1.5335083"
+ y="24.0536">y</tspan></text>
+ <text
+ xml:space="preserve"
+ style="font-size:14.16885567px;font-style:normal;font-weight:normal;line-height:125%;letter-spacing:0px;word-spacing:0px;fill:#000000;fill-opacity:1;stroke:none;font-family:Sans"
+ x="1.793227"
+ y="38.364738"
+ id="text3002-1-6"
+ sodipodi:linespacing="125%"
+ transform="scale(1.0978146,0.91090062)"><tspan
+ sodipodi:role="line"
+ id="tspan3004-1-7"
+ x="1.793227"
+ y="38.364738">z</tspan></text>
+</svg>
diff --git a/blocks/CVS.svg b/blocks/CVS.svg
new file mode 100644
index 0000000..6207fde
--- /dev/null
+++ b/blocks/CVS.svg
@@ -0,0 +1,5 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg font-size="20" xmlns="http://www.w3.org/2000/svg" height="40" width="40" version="1.1" font-family="monospace" fill="#cc0000">
+ <text y="27" x="1">+</text>
+ <text y="27" x="30">-</text>
+</svg>
diff --git a/blocks/Capacitor.svg b/blocks/Capacitor.svg
new file mode 100644
index 0000000..f39a5c5
--- /dev/null
+++ b/blocks/Capacitor.svg
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg2" xmlns="http://www.w3.org/2000/svg" height="32" width="35" version="1.0">
+ <g id="layer1" stroke-linejoin="miter" stroke="#000" stroke-linecap="butt" fill="none">
+ <path id="path17478" style="stroke-dasharray:none;" d="M16,5.0011v21.999" stroke-miterlimit="4" stroke-width="1.5"/>
+ <path id="path17480" style="stroke-dasharray:none;" d="m19,27,0-21.999" stroke-miterlimit="4" stroke-width="1.5"/>
+ <path id="path17482" d="M16.113,16,0.0000169,16" stroke-width="1px"/>
+ <path id="path17484" d="M18.981,16h16.019" stroke-width="1px"/>
+ </g>
+</svg>
diff --git a/blocks/ConstantVoltage.svg b/blocks/ConstantVoltage.svg
new file mode 100644
index 0000000..a7f7cbd
--- /dev/null
+++ b/blocks/ConstantVoltage.svg
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg2" xmlns="http://www.w3.org/2000/svg" height="32" width="35" version="1.0" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs id="defs4">
+ <linearGradient id="linearGradient2983" y2="48.548" gradientUnits="userSpaceOnUse" x2="45.919" gradientTransform="matrix(0.3358898,0,0,0.3358898,-29.952657,5.204695)" y1="36.423" x1="34.893">
+ <stop id="stop1324" stop-color="#729fcf" offset="0"/>
+ <stop id="stop1326" stop-color="#5187d6" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient2985" y2="34.977" gradientUnits="userSpaceOnUse" x2="27.901" gradientTransform="matrix(0.3358898,0,0,0.3358898,-23.900645,9.763119)" y1="22.852" x1="16.875">
+ <stop id="stop7918" stop-color="#FFF" offset="0"/>
+ <stop id="stop7920" stop-color="#FFF" stop-opacity="0.34020618" offset="1"/>
+ </linearGradient>
+ </defs>
+ <g id="layer1" stroke-linecap="butt">
+ <path id="path17478" stroke-linejoin="miter" style="stroke-dasharray:none;" d="M16,5.0011v21.999" stroke="#000" stroke-miterlimit="4" stroke-width="1.5" fill="none"/>
+ <path id="path17480" stroke-linejoin="miter" style="stroke-dasharray:none;" d="m19.843,20.681,0-9.3617" stroke="#000" stroke-miterlimit="4" stroke-width="2.12939548" fill="none"/>
+ <path id="path17482" stroke-linejoin="miter" d="M16.113,16,0.0000169,16" stroke="#000" stroke-width="1px" fill="none"/>
+ <path id="path17484" stroke-linejoin="miter" d="M18.981,16h16.019" stroke="#000" stroke-width="1px" fill="none"/>
+ <g id="g10841" stroke-miterlimit="4" transform="matrix(0.44813805,0,0,0.44813805,19.10033,-0.87156096)">
+ <path id="text1314" stroke-linejoin="miter" style="stroke-dasharray:none;text-align:start;" d="m-14.659,22.373,0-3.0321,3.3517-0.0135,0-2.3497-3.3487,0-0.003-3.3455-2.3567,0.0037,0.002,3.3358-3.3554,0.02474-0.01199,2.3407,3.3704-0.0097,0.002,3.0366,2.35,0.009z" stroke="#3465a4" stroke-width="1" fill="#75a1d0"/>
+ <path id="path7076" opacity="0.40860219" stroke-linejoin="round" style="stroke-dasharray:none;text-align:start;" d="M-15,22.034-15,19h3.3598l-0.002-1.6877h-3.3545v-3.3572l-1.6777,0.006,0.003,3.3512-3.3677,0.006-0.009,1.6704,3.3805,0.003-0.005,3.0322,1.6724,0.0105z" stroke="url(#linearGradient2985)" stroke-width="0.336" fill="url(#linearGradient2983)"/>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/CurrentSensor.svg b/blocks/CurrentSensor.svg
new file mode 100644
index 0000000..4ab49af
--- /dev/null
+++ b/blocks/CurrentSensor.svg
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg3643" xmlns="http://www.w3.org/2000/svg" height="43.69" width="43.69" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs id="defs3647">
+ <radialGradient id="XMLID_52_" gradientUnits="userSpaceOnUse" cy="23.333" cx="165.06" gradientTransform="matrix(1,0,0,1.0103,0,-0.159801)" r="7.2848">
+ <stop id="stop812" stop-color="#ef3535" offset="0"/>
+ <stop id="stop2239" stop-color="#c91a1a" offset="0"/>
+ <stop id="stop814" stop-color="#ff4c4c" offset="1"/>
+ </radialGradient>
+ <radialGradient id="radialGradient7366" xlink:href="#XMLID_52_" gradientUnits="userSpaceOnUse" cy="6.8283" cx="8.7468" r="29.89"/>
+ <radialGradient id="radialGradient7368" gradientUnits="userSpaceOnUse" cy="10.045" cx="11.902" r="29.293">
+ <stop id="stop2147" stop-color="#fffffd" offset="0"/>
+ <stop id="stop2149" stop-color="#cbcbc9" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient7370" y2="25.884" gradientUnits="userSpaceOnUse" x2="22.218" y1="7.7893" x1="6.3422">
+ <stop id="stop10655" stop-color="#f3f4ff" offset="0"/>
+ <stop id="stop10657" stop-color="#9193af" offset="1"/>
+ </linearGradient>
+ <radialGradient id="radialGradient7372" xlink:href="#XMLID_52_" gradientUnits="userSpaceOnUse" cy="10.584" cx="11.329" r="15.532"/>
+ </defs>
+ <g id="layer1" transform="translate(2.1701242,-21.184893)">
+ <g id="g7350" transform="translate(-4.5,-3.5000001)">
+ <g id="g4268" transform="translate(0.34319025,23.976452)">
+ <path id="path27786" stroke-linejoin="round" style="stroke-dasharray:none;" d="m31.161,16.911a14.911,14.911,0,1,1,-29.821,0,14.911,14.911,0,1,1,29.821,0z" fill-rule="evenodd" transform="matrix(1.431529,0,0,1.431529,0.569459,-1.654618)" stroke-dashoffset="0" stroke="#a40000" stroke-linecap="round" stroke-miterlimit="4" stroke-width="0.6985538" fill="url(#radialGradient7366)"/>
+ <path id="path35549" stroke-linejoin="round" d="m31.161,16.911a14.911,14.911,0,1,1,-29.821,0,14.911,14.911,0,1,1,29.821,0z" fill-rule="evenodd" transform="matrix(1.163838,0,0,1.163838,4.824801,2.777556)" stroke-dashoffset="0" stroke="url(#linearGradient7370)" stroke-linecap="round" stroke-miterlimit="4" stroke-width="0.71139598" fill="url(#radialGradient7368)"/>
+ <path id="path10651" stroke-linejoin="round" d="m31.161,16.911a14.911,14.911,0,1,1,-29.821,0,14.911,14.911,0,1,1,29.821,0z" stroke-dashoffset="0" transform="matrix(1.357654,0,0,1.357654,1.769896,-0.493735)" stroke="url(#radialGradient7372)" stroke-linecap="round" stroke-miterlimit="4" stroke-width="0.73656511" fill="none"/>
+ <g id="g4258" stroke-width="1" stroke="#2e3436" stroke-linecap="round" transform="translate(-0.49003984,-0.32669323)">
+ <path id="path35559" stroke-linejoin="miter" style="stroke-dasharray:none;" d="m24.495,22.583,7.5777-9.0209" stroke-miterlimit="4" fill="none"/>
+ <path id="path4256" stroke-linejoin="round" style="stroke-dasharray:none;" d="m-78.668,39.378-5.2437-3.0274-5.2437-3.0274,5.2437-3.0274,5.2437-3.0274,0,6.0549,0,6.0549z" transform="matrix(0.42354759,0.05292161,-0.05292161,0.42354759,68.775627,3.4525942)" stroke-miterlimit="10" fill="#2e3436"/>
+ </g>
+ <text id="text4262" font-weight="normal" xml:space="preserve" font-size="10.41038227px" font-style="normal" y="36.024803" x="19.803373" font-family="Bitstream Vera Sans" fill="#000000"><tspan id="tspan4264" y="36.024803" x="19.803373" font-weight="bold">A</tspan></text>
+ </g>
+ <path id="path4266" stroke-linejoin="round" d="m23.93,29.705c0,7.8406,0.16334,7.8406,0.16334,7.8406" stroke="#000" stroke-linecap="round" stroke-width="1px" fill="none"/>
+ </g>
+ <path id="path34778" stroke-linejoin="round" style="stroke-dasharray:none;" d="m16.406,17.281a1.2188,1.2188,0,1,1,-2.4375,0,1.2188,1.2188,0,1,1,2.4375,0z" fill-rule="evenodd" transform="matrix(2.073295,0,0,2.073295,-11.813172,7.2008838)" stroke-dashoffset="0" stroke="#000" stroke-linecap="round" stroke-miterlimit="4" stroke-width="0.48232403" fill="#f3f3f3"/>
+ </g>
+</svg>
diff --git a/blocks/DEADBAND.svg b/blocks/DEADBAND.svg
new file mode 100644
index 0000000..9a72a73
--- /dev/null
+++ b/blocks/DEADBAND.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="40" xmlns="http://www.w3.org/2000/svg" version="1.1" height="40">
+ <g id="layer1">
+ <path stroke-linejoin="round" d="M2,30l10-10h16l10-10" stroke="#000" stroke-linecap="round" stroke-width="2px" fill="none"/>
+ </g>
+</svg>
diff --git a/blocks/DSCOPE.svg b/blocks/DSCOPE.svg
new file mode 100644
index 0000000..a489ba7
--- /dev/null
+++ b/blocks/DSCOPE.svg
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg11300" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="44.822" width="41.062" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <metadata id="metadata39">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <title id="title3289">base scilan</title>
+ <defs id="defs3">
+ <radialGradient id="radialGradient20899" gradientUnits="userSpaceOnUse" cy="32.267" cx="23.994" gradientTransform="matrix(2.2986117,0,0,1.8027614,-75.446386,-24.554065)" r="19.089">
+ <stop id="stop2224" stop-color="#5187d6" offset="0"/>
+ <stop id="stop2227" stop-color="#1e4580" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient2854" y2="24.238" gradientUnits="userSpaceOnUse" x2="12.499" gradientTransform="matrix(0,-5.2061514,2.5688251,0,-79.265723,164.6207)" y1="12.538" x1="8.8208">
+ <stop id="stop2182" stop-color="#FFF" offset="0"/>
+ <stop id="stop2184" stop-color="#FFF" stop-opacity="0" offset="1"/>
+ </linearGradient>
+ </defs>
+ <g id="layer1" transform="translate(-2.8842799e-7,-3.1783548)">
+ <g id="layer1-0" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544" transform="translate(-2.0023665,2.2530854)">
+ <g id="g9570" transform="translate(0.34662852,-1.7289484)">
+ <g id="g28489" transform="translate(2.3098495,0.12996954)">
+ <g id="g20882" transform="translate(51.47752,-82.884097)">
+ <rect id="rect1314" stroke-linejoin="round" style="stroke-dasharray:none;" transform="matrix(0,-0.99999997,0.99999997,0,-49.042742,89.574337)" fill-rule="evenodd" stroke-dashoffset="0" rx="2.2025" ry="2.2025" height="37.631" width="48.319" stroke="#173562" stroke-linecap="round" stroke-miterlimit="4" y="2.4454" x="-44.453" stroke-width="1.4676" fill="url(#radialGradient20899)"/>
+ <path id="path28138" stroke-linejoin="round" d="M8.6382,35.758c27.557-0.173,27.904-0.173,27.904-0.173l0.17331-33.97" transform="translate(-49.042742,89.574337)" stroke="#000" stroke-linecap="round" stroke-width="1.46762753px" fill="none"/>
+ <path id="rect2178" opacity="0.43181817" d="m-45.868,133.4,12.954,0c1.0099-5.0153,1.5849-10.576,1.5849-16.442,0-12.211-2.4433-23.147-6.2788-30.45h-8.2599v46.892z" fill-rule="evenodd" fill="url(#linearGradient2854)"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ <path id="path2188" stroke-linejoin="round" style="stroke-dasharray:none;" d="m13.665,29.974,4.7566-10.919,5.7799,10.589,6.8184-5.8677,0.06242-4.1123-6.3304,5.6237-6.5406-11.928-6.0101,14.643,1.4637,1.9713z" fill-rule="evenodd" stroke="#8ae234" stroke-linecap="round" stroke-miterlimit="10" stroke-width="0.91817689" fill="#73d216"/>
+</svg>
diff --git a/blocks/Diode.svg b/blocks/Diode.svg
new file mode 100644
index 0000000..773da8b
--- /dev/null
+++ b/blocks/Diode.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg2" xmlns="http://www.w3.org/2000/svg" height="70pt" width="165pt" version="1.0">
+ <g id="layer1">
+ <path id="path2239" stroke-linejoin="miter" style="stroke-dasharray:none;" d="m3.5526,44.371,69.191,0,0,38.587l59.816-38.587-59.816-37.256v37.257m59.212-37.257,0,75.844,0.66529-38.587,69.856,0" stroke="#000" stroke-linecap="round" stroke-miterlimit="4" stroke-width="3" fill="none"/>
+ </g>
+</svg>
diff --git a/blocks/Flowmeter.svg b/blocks/Flowmeter.svg
new file mode 100644
index 0000000..d6e7091
--- /dev/null
+++ b/blocks/Flowmeter.svg
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg3643" xmlns="http://www.w3.org/2000/svg" height="43.69" width="43.69" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs id="defs3647">
+ <radialGradient id="XMLID_52_" gradientUnits="userSpaceOnUse" cy="23.333" cx="165.06" gradientTransform="matrix(1,0,0,1.0103,0,-0.159801)" r="7.2848">
+ <stop id="stop812" stop-color="#ef3535" offset="0"/>
+ <stop id="stop2239" stop-color="#c91a1a" offset="0"/>
+ <stop id="stop814" stop-color="#ff4c4c" offset="1"/>
+ </radialGradient>
+ <radialGradient id="radialGradient7366" xlink:href="#XMLID_52_" gradientUnits="userSpaceOnUse" cy="6.8283" cx="8.7468" r="29.89"/>
+ <radialGradient id="radialGradient7368" gradientUnits="userSpaceOnUse" cy="10.045" cx="11.902" r="29.293">
+ <stop id="stop2147" stop-color="#fffffd" offset="0"/>
+ <stop id="stop2149" stop-color="#cbcbc9" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient7370" y2="25.884" gradientUnits="userSpaceOnUse" x2="22.218" y1="7.7893" x1="6.3422">
+ <stop id="stop10655" stop-color="#f3f4ff" offset="0"/>
+ <stop id="stop10657" stop-color="#9193af" offset="1"/>
+ </linearGradient>
+ <radialGradient id="radialGradient7372" xlink:href="#XMLID_52_" gradientUnits="userSpaceOnUse" cy="10.584" cx="11.329" r="15.532"/>
+ </defs>
+ <g id="layer1" transform="translate(2.1701242,-21.184893)">
+ <g id="g7350" transform="translate(-4.5,-3.5000001)">
+ <g id="g4268" transform="translate(0.34319025,23.976452)">
+ <path id="path27786" stroke-linejoin="round" style="stroke-dasharray:none;" d="m31.161,16.911a14.911,14.911,0,1,1,-29.821,0,14.911,14.911,0,1,1,29.821,0z" fill-rule="evenodd" transform="matrix(1.431529,0,0,1.431529,0.569459,-1.654618)" stroke-dashoffset="0" stroke="#a40000" stroke-linecap="round" stroke-miterlimit="4" stroke-width="0.6985538" fill="url(#radialGradient7366)"/>
+ <path id="path35549" stroke-linejoin="round" d="m31.161,16.911a14.911,14.911,0,1,1,-29.821,0,14.911,14.911,0,1,1,29.821,0z" fill-rule="evenodd" transform="matrix(1.163838,0,0,1.163838,4.824801,2.777556)" stroke-dashoffset="0" stroke="url(#linearGradient7370)" stroke-linecap="round" stroke-miterlimit="4" stroke-width="0.71139598" fill="url(#radialGradient7368)"/>
+ <path id="path10651" stroke-linejoin="round" d="m31.161,16.911a14.911,14.911,0,1,1,-29.821,0,14.911,14.911,0,1,1,29.821,0z" stroke-dashoffset="0" transform="matrix(1.357654,0,0,1.357654,1.769896,-0.493735)" stroke="url(#radialGradient7372)" stroke-linecap="round" stroke-miterlimit="4" stroke-width="0.73656511" fill="none"/>
+ <g id="g4258" stroke-width="1" stroke="#2e3436" stroke-linecap="round" transform="translate(-0.49003984,-0.32669323)">
+ <path id="path35559" stroke-linejoin="miter" style="stroke-dasharray:none;" d="m24.495,22.583,7.5777-9.0209" stroke-miterlimit="4" fill="none"/>
+ <path id="path4256" stroke-linejoin="round" style="stroke-dasharray:none;" d="m-78.668,39.378-5.2437-3.0274-5.2437-3.0274,5.2437-3.0274,5.2437-3.0274,0,6.0549,0,6.0549z" transform="matrix(0.42354759,0.05292161,-0.05292161,0.42354759,68.775627,3.4525942)" stroke-miterlimit="10" fill="#2e3436"/>
+ </g>
+ <text id="text4262" font-weight="normal" xml:space="preserve" font-size="10.41038227px" font-style="normal" y="36.024803" x="19.803373" font-family="Bitstream Vera Sans" fill="#000000"><tspan id="tspan4264" y="36.024803" x="19.803373" font-weight="bold">Q</tspan></text>
+ </g>
+ <path id="path4266" stroke-linejoin="round" d="m23.93,29.705c0,7.8406,0.16334,7.8406,0.16334,7.8406" stroke="#000" stroke-linecap="round" stroke-width="1px" fill="none"/>
+ </g>
+ <path id="path34778" stroke-linejoin="round" style="stroke-dasharray:none;" d="m16.406,17.281a1.2188,1.2188,0,1,1,-2.4375,0,1.2188,1.2188,0,1,1,2.4375,0z" fill-rule="evenodd" transform="matrix(2.073295,0,0,2.073295,-11.813172,7.2008838)" stroke-dashoffset="0" stroke="#000" stroke-linecap="round" stroke-miterlimit="4" stroke-width="0.48232403" fill="#f3f3f3"/>
+ </g>
+</svg>
diff --git a/blocks/Ground.svg b/blocks/Ground.svg
new file mode 100644
index 0000000..44a1b80
--- /dev/null
+++ b/blocks/Ground.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="40" xmlns="http://www.w3.org/2000/svg" version="1.1" height="40">
+ <g id="layer1" stroke="#000" stroke-width="2px" fill="none">
+ <path d="M15,35h10m-15-10h20m-27.5-10h35"/>
+ <path d="M20,0v15"/>
+ </g>
+</svg>
diff --git a/blocks/Gyrator.svg b/blocks/Gyrator.svg
new file mode 100644
index 0000000..1bf3b88
--- /dev/null
+++ b/blocks/Gyrator.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="40"
+ height="40">
+ <defs>
+ <g id="arrow">
+ <path d="M2,0 h36" fill="none" stroke="#3465a4"
+ stroke-width="2px" stroke-linejoin="round" stroke-linecap="round" />
+ <polygon points="38,0 30,-5 30,5" fill="#3465a4" stroke="#3465a4"
+ stroke-width="2px" stroke-linejoin="round" stroke-linecap="round" />
+ </g>
+ </defs>
+ <use xlink:href="#arrow" transform="translate(0, 10)" />
+ <use xlink:href="#arrow" transform="translate(40, 30) rotate(180)" />
+</svg>
diff --git a/blocks/HYSTHERESIS.svg b/blocks/HYSTHERESIS.svg
new file mode 100644
index 0000000..9f05af1
--- /dev/null
+++ b/blocks/HYSTHERESIS.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="40" xmlns="http://www.w3.org/2000/svg" version="1.1" height="40">
+ <g id="layer1" stroke="#000" stroke-width="2px">
+ <path stroke-linejoin="round" d="M2,30h20" stroke-linecap="round" fill="none"/>
+ <path stroke-linejoin="round" d="M18,10h20" stroke-linecap="round" fill="none"/>
+ <rect fill-opacity="0.1" height="20" width="10" y="10" x="15" fill="#000"/>
+ </g>
+</svg>
diff --git a/blocks/INTEGRAL.svg b/blocks/INTEGRAL.svg
new file mode 100644
index 0000000..6d5a898
--- /dev/null
+++ b/blocks/INTEGRAL.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg2" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="385" width="240" version="1.0" xmlns:cc="http://web.resource.org/cc/" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <metadata id="metadata7">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs id="defs5"></defs>
+ <path id="path1873" d="M137,156.68c2-28,9.75-78.68,10.25-82.43s3.5-19.75,13.25-31.75,25.75-16.25,25.75-16.25c0.25,0-4,0.75-4,11.5s12.75,18.25,20.75,18.25,22-7,22-19-16.5-19-23-19-17,0-26,3.5-29.38,16.5-37.88,33.5-13.12,37-17.62,67-9.5,77.37-11.5,105.37-11.25,80.5-11.75,84.25-3.5,19.75-13.25,31.75-25.75,16.25-25.75,16.25c-0.25,0,4-0.75,4-11.5s-12.75-18.25-20.75-18.25-22,7-22,19,16.5,19,23,19,17,0,26-3.5,29.375-16.5,37.88-33.5c8.5-17,13.12-37,17.62-67,2.02-13.5,11-85.19,13-107.19z" fill-rule="evenodd" fill="#000"/>
+</svg>
diff --git a/blocks/INTEGRAL_m.svg b/blocks/INTEGRAL_m.svg
new file mode 100644
index 0000000..6d5a898
--- /dev/null
+++ b/blocks/INTEGRAL_m.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg2" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="385" width="240" version="1.0" xmlns:cc="http://web.resource.org/cc/" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <metadata id="metadata7">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs id="defs5"></defs>
+ <path id="path1873" d="M137,156.68c2-28,9.75-78.68,10.25-82.43s3.5-19.75,13.25-31.75,25.75-16.25,25.75-16.25c0.25,0-4,0.75-4,11.5s12.75,18.25,20.75,18.25,22-7,22-19-16.5-19-23-19-17,0-26,3.5-29.38,16.5-37.88,33.5-13.12,37-17.62,67-9.5,77.37-11.5,105.37-11.25,80.5-11.75,84.25-3.5,19.75-13.25,31.75-25.75,16.25-25.75,16.25c-0.25,0,4-0.75,4-11.5s-12.75-18.25-20.75-18.25-22,7-22,19,16.5,19,23,19,17,0,26-3.5,29.375-16.5,37.88-33.5c8.5-17,13.12-37,17.62-67,2.02-13.5,11-85.19,13-107.19z" fill-rule="evenodd" fill="#000"/>
+</svg>
diff --git a/blocks/IdealTransformer.svg b/blocks/IdealTransformer.svg
new file mode 100644
index 0000000..362d8ab
--- /dev/null
+++ b/blocks/IdealTransformer.svg
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+ xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg"
+ version="1.0"
+ width="50"
+ height="50"
+ id="svg2">
+ <defs
+ id="defs8" />
+ <g
+ id="layer1"
+ style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter">
+ <path
+ d="M 5.5,5 16,5 c 0,0 5,2.6429774 5,5 0,2.357023 -5,5 -5,5 0,0 5,2.642977 5,5 0,2.357023 -5,5 -5,5 0,0 5,2.642977 5,5 0,2.357023 -5,5 -5,5 0,0 5,2.642977 5,5 0,2.357023 -5,5 -5,5 L 5.5,45"
+ id="path3124" />
+ <path
+ d="M 43,5 33,5 c 0,0 -5,2.6429774 -5,5 0,2.357023 5,5 5,5 0,0 -5,2.642977 -5,5 0,2.357023 5,5 5,5 0,0 -5,2.642977 -5,5 0,2.357023 5,5 5,5 0,0 -5,2.642977 -5,5 0,2.357023 5,5 5,5 l 10,0"
+ id="path3126" />
+ </g>
+</svg>
diff --git a/blocks/Inductor.svg b/blocks/Inductor.svg
new file mode 100644
index 0000000..f4b5168
--- /dev/null
+++ b/blocks/Inductor.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg2" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" sodipodi:docname="Inductor.svg" height="15" sodipodi:version="0.32" width="45" version="1.0" xmlns:cc="http://web.resource.org/cc/" xmlns:dc="http://purl.org/dc/elements/1.1/" sodipodi:docbase="C:\Documents and Settings\Julian\My Documents\My Pictures\Electronics\Vector Illustrations" xmlns:sodipodi="http://inkscape.sourceforge.net/DTD/sodipodi-0.dtd">
+ <metadata id="metadata1312">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <sodipodi:namedview id="base" bordercolor="#666666" pagecolor="#ffffff" gridtolerance="1px" borderopacity="1.0" showgrid="true" showguides="true"/>
+ <path id="path1318" stroke-linejoin="miter" d="M1,8.5h5.5s0-4,4-4,4,4,4,4,0-4,4-4,4,4,4,4,0-4,4-4,4,4,4,4,0-4,4-4,4,4,4,4h5.5" sodipodi:nodetypes="ccscscscscc" stroke="#000" stroke-linecap="butt" stroke-width="1px" fill="none"/>
+</svg>
diff --git a/blocks/LOOKUP_f.svg b/blocks/LOOKUP_f.svg
new file mode 100644
index 0000000..a489ba7
--- /dev/null
+++ b/blocks/LOOKUP_f.svg
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg11300" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="44.822" width="41.062" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <metadata id="metadata39">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <title id="title3289">base scilan</title>
+ <defs id="defs3">
+ <radialGradient id="radialGradient20899" gradientUnits="userSpaceOnUse" cy="32.267" cx="23.994" gradientTransform="matrix(2.2986117,0,0,1.8027614,-75.446386,-24.554065)" r="19.089">
+ <stop id="stop2224" stop-color="#5187d6" offset="0"/>
+ <stop id="stop2227" stop-color="#1e4580" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient2854" y2="24.238" gradientUnits="userSpaceOnUse" x2="12.499" gradientTransform="matrix(0,-5.2061514,2.5688251,0,-79.265723,164.6207)" y1="12.538" x1="8.8208">
+ <stop id="stop2182" stop-color="#FFF" offset="0"/>
+ <stop id="stop2184" stop-color="#FFF" stop-opacity="0" offset="1"/>
+ </linearGradient>
+ </defs>
+ <g id="layer1" transform="translate(-2.8842799e-7,-3.1783548)">
+ <g id="layer1-0" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544" transform="translate(-2.0023665,2.2530854)">
+ <g id="g9570" transform="translate(0.34662852,-1.7289484)">
+ <g id="g28489" transform="translate(2.3098495,0.12996954)">
+ <g id="g20882" transform="translate(51.47752,-82.884097)">
+ <rect id="rect1314" stroke-linejoin="round" style="stroke-dasharray:none;" transform="matrix(0,-0.99999997,0.99999997,0,-49.042742,89.574337)" fill-rule="evenodd" stroke-dashoffset="0" rx="2.2025" ry="2.2025" height="37.631" width="48.319" stroke="#173562" stroke-linecap="round" stroke-miterlimit="4" y="2.4454" x="-44.453" stroke-width="1.4676" fill="url(#radialGradient20899)"/>
+ <path id="path28138" stroke-linejoin="round" d="M8.6382,35.758c27.557-0.173,27.904-0.173,27.904-0.173l0.17331-33.97" transform="translate(-49.042742,89.574337)" stroke="#000" stroke-linecap="round" stroke-width="1.46762753px" fill="none"/>
+ <path id="rect2178" opacity="0.43181817" d="m-45.868,133.4,12.954,0c1.0099-5.0153,1.5849-10.576,1.5849-16.442,0-12.211-2.4433-23.147-6.2788-30.45h-8.2599v46.892z" fill-rule="evenodd" fill="url(#linearGradient2854)"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ <path id="path2188" stroke-linejoin="round" style="stroke-dasharray:none;" d="m13.665,29.974,4.7566-10.919,5.7799,10.589,6.8184-5.8677,0.06242-4.1123-6.3304,5.6237-6.5406-11.928-6.0101,14.643,1.4637,1.9713z" fill-rule="evenodd" stroke="#8ae234" stroke-linecap="round" stroke-miterlimit="10" stroke-width="0.91817689" fill="#73d216"/>
+</svg>
diff --git a/blocks/NMOS.svg b/blocks/NMOS.svg
new file mode 100644
index 0000000..b2cd485
--- /dev/null
+++ b/blocks/NMOS.svg
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg11300" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="48px" width="48px" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title3289">base scilan</title>
+ <defs id="defs3"></defs>
+ <metadata id="metadata4">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Mathieu Drouet / Take a sip</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:source>http://www.takeasip.net/</dc:source>
+ <cc:license rdf:resource="http://creativecommons.org/licenses/publicdomain/"/>
+ <dc:title>base scilan</dc:title>
+ </cc:Work>
+ <cc:License rdf:about="http://creativecommons.org/licenses/publicdomain/">
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g id="layer1">
+ <g id="layer1-0" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544" transform="translate(-2.0023665,2.2530854)">
+ <g id="g9570" transform="translate(0.34662852,-1.7289484)">
+ <g id="g28489" transform="translate(2.3098495,0.12996954)">
+ <g id="g5362">
+ <g id="layer1-8" transform="matrix(0,-1.4676275,1.4676275,0,-37.823485,137.74736)">
+ <g id="g5857" transform="translate(-0.49999954,-9.6375814e-8)">
+ <path id="path6316" stroke-linejoin="miter" d="m94.5,41.5h-19" stroke="#000" stroke-linecap="butt" stroke-width="1.20894098px" fill="none"/>
+ <path id="path6314" stroke-linejoin="miter" d="m9,24,6,0,0-10,0-11" transform="translate(55.500003,17.5)" stroke="#000" stroke-linecap="butt" stroke-width="1px" fill="none"/>
+ <rect id="rect6830" stroke-linejoin="miter" style="stroke-dasharray:none;" height="5" width="3" stroke="#000" stroke-linecap="square" stroke-miterlimit="4" y="39" x="72.5" stroke-width="1" fill="#babdb6"/>
+ <rect id="rect6832" stroke-linejoin="miter" style="stroke-dasharray:none;" height="5" width="3" stroke="#000" stroke-linecap="square" stroke-miterlimit="4" y="39" x="72.5" stroke-width="1" fill="#babdb6"/>
+ <rect id="rect6834" stroke-linejoin="miter" style="stroke-dasharray:none;" height="5" width="3" stroke="#000" stroke-linecap="square" stroke-miterlimit="4" y="25.729" x="72.5" stroke-width="1" fill="#babdb6"/>
+ <rect id="rect6836" stroke-linejoin="miter" style="stroke-dasharray:none;" height="5" width="3" stroke="#000" stroke-linecap="square" stroke-miterlimit="4" y="52.271" x="72.5" stroke-width="1" fill="#babdb6"/>
+ <path id="path6838" stroke-linejoin="miter" d="m93.5,54.771h-18" stroke="#000" stroke-linecap="butt" stroke-width="1.06066012px" fill="none"/>
+ <path id="path6840" stroke-linejoin="miter" d="m93.5,54.771h-18" stroke="#000" stroke-linecap="butt" stroke-width="1.06066012px" fill="none"/>
+ <path id="path6844" stroke-linejoin="miter" d="m93.5,28.229h-18" stroke="#000" stroke-linecap="butt" stroke-width="1.06066012px" fill="none"/>
+ <g id="layer1-3" transform="translate(16.425243,1.3543909)">
+ <g id="layer1-0-7" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544-9" transform="translate(-2.0023665,2.2530854)">
+ <g id="g9570-0" transform="translate(0.34662852,-1.7289484)">
+ <g id="g28489-2" transform="translate(2.3098495,0.12996954)">
+ <g id="g5362-3">
+ <g id="layer1-8-9" transform="matrix(0,-1.4676275,1.4676275,0,-37.823485,137.74736)">
+ <g id="g5857-9" transform="translate(-0.49999954,-9.6375814e-8)">
+ <path id="path6318-2" stroke-linejoin="miter" style="stroke-dasharray:none;" d="m-22-16,2.5981,4.5,2.5981,4.5h-5.196l-5.1962-1E-7,2.598-4.5,2.598-4.5z" transform="matrix(0.57976926,-0.31487821,-0.31487821,-0.57976926,133.13811,45.02375)" stroke="#000" stroke-linecap="square" stroke-miterlimit="4" stroke-width="1.51570737" fill="#000"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/NPN.svg b/blocks/NPN.svg
new file mode 100644
index 0000000..a8da10e
--- /dev/null
+++ b/blocks/NPN.svg
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg11300" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="48px" width="48px" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title3289">base scilan</title>
+ <defs id="defs3"></defs>
+ <metadata id="metadata4">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Mathieu Drouet / Take a sip</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:source>http://www.takeasip.net/</dc:source>
+ <cc:license rdf:resource="http://creativecommons.org/licenses/publicdomain/"/>
+ <dc:title>base scilan</dc:title>
+ </cc:Work>
+ <cc:License rdf:about="http://creativecommons.org/licenses/publicdomain/">
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g id="layer1">
+ <g id="layer1-0" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544" transform="translate(-2.0023665,2.2530854)">
+ <g id="g9570" transform="translate(0.34662852,-1.7289484)">
+ <g id="g28489" transform="translate(2.3098495,0.12996954)">
+ <g id="g5362">
+ <g id="layer1-8" transform="matrix(0,-1.4676275,1.4676275,0,-37.823485,137.74736)">
+ <g id="g5857" stroke="#000" transform="translate(-0.49999954,-9.6375814e-8)">
+ <path id="path6318" stroke-linejoin="miter" style="stroke-dasharray:none;" fill="#000" transform="matrix(0,-0.65975795,0.65975795,0,84.118309,35.557123)" stroke-linecap="square" stroke-miterlimit="4" stroke-width="1.51570742000000003" d="m-22-16,2.5981,4.5,2.5981,4.5h-5.196l-5.1962-1E-7,2.598-4.5,2.598-4.5z"/>
+ <path id="path6838" stroke-linejoin="miter" d="m93.5,54.771h-13" stroke-linecap="butt" stroke-width="0.90138775px" fill="none"/>
+ <path id="path6840" stroke-linejoin="miter" d="m93.5,54.771h-13" stroke-linecap="butt" stroke-width="0.90138775px" fill="none"/>
+ <path id="path6844" stroke-linejoin="miter" d="m93.5,28.229h-13" stroke-linecap="butt" stroke-width="0.90138775px" fill="none"/>
+ <path id="path7318" stroke-linejoin="miter" d="m64.5,40.5h6" stroke-linecap="butt" stroke-width="1px" fill="none"/>
+ <path id="path7320" stroke-linejoin="miter" d="m70.5,30.5,0,20" stroke-linecap="butt" stroke-width="1.34839976px" fill="none"/>
+ <path id="path7324" stroke-linejoin="miter" d="m25,37-9-14,0,0" transform="translate(55.500003,17.5)" stroke-linecap="round" stroke-width="1px" fill="none"/>
+ <path id="path7844" stroke-linejoin="round" style="stroke-dasharray:none;" d="m71.5,40.5,9-12,0,0,0,0" stroke-linecap="round" stroke-miterlimit="4" stroke-width="1.00000002000000010" fill="none"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/PMOS.svg b/blocks/PMOS.svg
new file mode 100644
index 0000000..4a67cad
--- /dev/null
+++ b/blocks/PMOS.svg
@@ -0,0 +1,65 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg11300" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="48px" width="48px" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title3289">base scilan</title>
+ <defs id="defs3"></defs>
+ <metadata id="metadata4">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Mathieu Drouet / Take a sip</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:source>http://www.takeasip.net/</dc:source>
+ <cc:license rdf:resource="http://creativecommons.org/licenses/publicdomain/"/>
+ <dc:title>base scilan</dc:title>
+ </cc:Work>
+ <cc:License rdf:about="http://creativecommons.org/licenses/publicdomain/">
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g id="layer1">
+ <g id="layer1-0" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544" transform="translate(-2.0023665,2.2530854)">
+ <g id="g9570" transform="translate(0.34662852,-1.7289484)">
+ <g id="g28489" transform="translate(2.3098495,0.12996954)">
+ <g id="g5362">
+ <g id="layer1-8" transform="matrix(0,-1.4676275,1.4676275,0,-37.823485,137.74736)">
+ <g id="g5857" transform="translate(-0.49999954,-9.6375814e-8)">
+ <path id="path6316" stroke-linejoin="miter" d="m94.5,41.5h-19" stroke="#000" stroke-linecap="butt" stroke-width="1.20894098px" fill="none"/>
+ <path id="path6314" stroke-linejoin="miter" d="m9,24,6,0,0-10,0-11" transform="translate(55.500003,17.5)" stroke="#000" stroke-linecap="butt" stroke-width="1px" fill="none"/>
+ <rect id="rect6830" stroke-linejoin="miter" style="stroke-dasharray:none;" height="5" width="3" stroke="#000" stroke-linecap="square" stroke-miterlimit="4" y="39" x="72.5" stroke-width="1" fill="#babdb6"/>
+ <rect id="rect6832" stroke-linejoin="miter" style="stroke-dasharray:none;" height="5" width="3" stroke="#000" stroke-linecap="square" stroke-miterlimit="4" y="39" x="72.5" stroke-width="1" fill="#babdb6"/>
+ <rect id="rect6834" stroke-linejoin="miter" style="stroke-dasharray:none;" height="5" width="3" stroke="#000" stroke-linecap="square" stroke-miterlimit="4" y="25.729" x="72.5" stroke-width="1" fill="#babdb6"/>
+ <rect id="rect6836" stroke-linejoin="miter" style="stroke-dasharray:none;" height="5" width="3" stroke="#000" stroke-linecap="square" stroke-miterlimit="4" y="52.271" x="72.5" stroke-width="1" fill="#babdb6"/>
+ <path id="path6838" stroke-linejoin="miter" d="m93.5,54.771h-18" stroke="#000" stroke-linecap="butt" stroke-width="1.06066012px" fill="none"/>
+ <path id="path6840" stroke-linejoin="miter" d="m93.5,54.771h-18" stroke="#000" stroke-linecap="butt" stroke-width="1.06066012px" fill="none"/>
+ <path id="path6844" stroke-linejoin="miter" d="m93.5,28.229h-18" stroke="#000" stroke-linecap="butt" stroke-width="1.06066012px" fill="none"/>
+ <g id="layer1-3" transform="translate(-32.999999,-0.68501239)">
+ <g id="layer1-0-8" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544-4" transform="translate(-2.0023665,2.2530854)">
+ <g id="g9570-8" transform="translate(0.34662852,-1.7289484)">
+ <g id="g28489-0" transform="translate(2.3098495,0.12996954)">
+ <g id="g5362-4">
+ <path id="path6318-8" stroke-linejoin="miter" style="stroke-dasharray:none;" d="m-22-16,2.5981,4.5,2.5981,4.5h-5.196l-5.1962-1E-7,2.598-4.5,2.598-4.5z" transform="matrix(-0.46212385,0.85088519,-0.85088519,-0.46212385,31.020611,-98.466165)" stroke="#000" stroke-linecap="square" stroke-miterlimit="4" stroke-width="1.51570737" fill="#000"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/PNP.svg b/blocks/PNP.svg
new file mode 100644
index 0000000..5c8250b
--- /dev/null
+++ b/blocks/PNP.svg
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg11300" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="48px" width="48px" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title3289">base scilan</title>
+ <defs id="defs3"></defs>
+ <metadata id="metadata4">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Mathieu Drouet / Take a sip</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:source>http://www.takeasip.net/</dc:source>
+ <cc:license rdf:resource="http://creativecommons.org/licenses/publicdomain/"/>
+ <dc:title>base scilan</dc:title>
+ </cc:Work>
+ <cc:License rdf:about="http://creativecommons.org/licenses/publicdomain/">
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g id="layer1">
+ <g id="layer1-0" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544" transform="translate(-2.0023665,2.2530854)">
+ <g id="g9570" transform="translate(0.34662852,-1.7289484)">
+ <g id="g28489" transform="translate(2.3098495,0.12996954)">
+ <g id="g5362">
+ <g id="layer1-8" transform="matrix(0,-1.4676275,1.4676275,0,-37.823485,137.74736)">
+ <g id="g5857" stroke="#000" transform="translate(-0.49999954,-9.6375814e-8)">
+ <path id="path7324" stroke-linejoin="miter" d="m25,37-9-14,0,0" transform="translate(55.500003,17.5)" stroke-linecap="round" stroke-width="1px" fill="none"/>
+ <path id="path6318" stroke-linejoin="miter" style="stroke-dasharray:none;" fill="#000" transform="matrix(-0.57976918,-0.31487816,0.31487816,-0.57976918,67.39727,36.477133)" stroke-linecap="square" stroke-miterlimit="4" stroke-width="1.51570737" d="m-22-16,2.5981,4.5,2.5981,4.5h-5.196l-5.1962-1E-7,2.598-4.5,2.598-4.5z"/>
+ <path id="path6838" stroke-linejoin="miter" d="m93.5,54.771h-13" stroke-linecap="butt" stroke-width="0.90138775px" fill="none"/>
+ <path id="path6840" stroke-linejoin="miter" d="m93.5,54.771h-13" stroke-linecap="butt" stroke-width="0.90138775px" fill="none"/>
+ <path id="path6844" stroke-linejoin="miter" d="m93.5,28.229h-13" stroke-linecap="butt" stroke-width="0.90138775px" fill="none"/>
+ <path id="path7318" stroke-linejoin="miter" d="m64.5,40.5h6" stroke-linecap="butt" stroke-width="1px" fill="none"/>
+ <path id="path7320" stroke-linejoin="miter" d="m70.5,30.5,0,20" stroke-linecap="butt" stroke-width="1.34839976px" fill="none"/>
+ <path id="path7844" stroke-linejoin="round" style="stroke-dasharray:none;" d="m71.5,40.5,9-12,0,0,0,0" stroke-linecap="round" stroke-miterlimit="4" stroke-width="1.00000002000000010" fill="none"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/PRODUCT.svg b/blocks/PRODUCT.svg
new file mode 100644
index 0000000..2f90302
--- /dev/null
+++ b/blocks/PRODUCT.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="40" xmlns="http://www.w3.org/2000/svg" version="1.1" height="40">
+ <text y="30" x="14" font-size="30" font-family="serif" fill="black">Π</text>
+</svg>
diff --git a/blocks/PULSE_SC.svg b/blocks/PULSE_SC.svg
new file mode 100644
index 0000000..4648b2c
--- /dev/null
+++ b/blocks/PULSE_SC.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="40" xmlns="http://www.w3.org/2000/svg" version="1.1" height="40">
+ <g id="layer1">
+ <path stroke-width="2px" d="M0,30h10v-20h5v20h15v-20h5v20h5" stroke="#000" fill="none"/>
+ </g>
+</svg>
+
diff --git a/blocks/PerteDP.svg b/blocks/PerteDP.svg
new file mode 100644
index 0000000..ede7c63
--- /dev/null
+++ b/blocks/PerteDP.svg
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg11300" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="48" width="80" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title3289">base scilan</title>
+ <defs id="defs3">
+ <linearGradient id="linearGradient10197" y2="44.441" gradientUnits="userSpaceOnUse" x2="32.368" y1="22.083" x1="32.159">
+ <stop id="stop2310" stop-color="#73d216" offset="0"/>
+ <stop id="stop2312" stop-color="#4e9a06" offset="1"/>
+ </linearGradient>
+ </defs>
+ <metadata id="metadata4">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Mathieu Drouet / Take a sip</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:source>http://www.takeasip.net/</dc:source>
+ <cc:license rdf:resource="http://creativecommons.org/licenses/publicdomain/"/>
+ <dc:title>base scilan</dc:title>
+ </cc:Work>
+ <cc:License rdf:about="http://creativecommons.org/licenses/publicdomain/">
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g id="layer1">
+ <g id="layer1-0" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544" transform="translate(-2.0023665,2.2530854)">
+ <g id="g9570" transform="translate(0.34662852,-1.7289484)">
+ <g id="g28489" stroke-linejoin="round" stroke-linecap="round" stroke-miterlimit="10" transform="translate(2.3098495,0.12996954)">
+ <rect id="rect9409" ry="5.6691" style="stroke-dasharray:none;" rx="0" transform="matrix(0,-1,1,0,0,0)" height="25.501" width="95.105" stroke="#000" y="9.3328" x="-44.059" stroke-width="1.4676" fill="url(#linearGradient10197)"/>
+ <rect id="rect10224" opacity="0.7" ry="0" style="stroke-dasharray:none;" transform="matrix(0,-1.4676275,1.4676275,0,-12.140004,57.027843)" height="15.116" width="62.943" stroke="#eeeeec" y="15.761" x="9.8016" stroke-width="1" fill="none"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/PotentialSensor.svg b/blocks/PotentialSensor.svg
new file mode 100644
index 0000000..fbfc84c
--- /dev/null
+++ b/blocks/PotentialSensor.svg
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg3643" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="43.69" width="43.69" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <metadata id="metadata3649">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <dc:title/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <defs id="defs3647">
+ <radialGradient id="XMLID_52_" cx="165.06" gradientUnits="userSpaceOnUse" cy="23.333" r="7.2848" gradientTransform="matrix(1,0,0,1.0103,0,-0.159801)">
+ <stop id="stop812" stop-color="#EF3535" offset="0"/>
+ <stop id="stop2239" stop-color="#c91a1a" offset="0"/>
+ <stop id="stop814" stop-color="#ff4c4c" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient7364" y2="52.091" gradientUnits="userSpaceOnUse" x2="9.8855" gradientTransform="matrix(3.123841,0,0,0.969691,-31.88758,-19.59492)" y1="37.197" x1="8.9156">
+ <stop id="stop2154" stop-color="#9aa29a" offset="0"/>
+ <stop id="stop2156" stop-color="#b5beb5" offset="1"/>
+ </linearGradient>
+ <radialGradient id="radialGradient7366" xlink:href="#XMLID_52_" gradientUnits="userSpaceOnUse" cy="6.8283" cx="8.7468" r="29.89"/>
+ <radialGradient id="radialGradient7368" gradientUnits="userSpaceOnUse" cy="10.045" cx="11.902" r="29.293">
+ <stop id="stop2147" stop-color="#fffffd" offset="0"/>
+ <stop id="stop2149" stop-color="#cbcbc9" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient7370" y2="25.884" gradientUnits="userSpaceOnUse" x2="22.218" y1="7.7893" x1="6.3422">
+ <stop id="stop10655" stop-color="#f3f4ff" offset="0"/>
+ <stop id="stop10657" stop-color="#9193af" offset="1"/>
+ </linearGradient>
+ <radialGradient id="radialGradient7372" xlink:href="#XMLID_52_" gradientUnits="userSpaceOnUse" cy="10.584" cx="11.329" r="15.532"/>
+ </defs>
+ <g id="layer1" transform="translate(2.1701242,-21.184893)">
+ <g id="g7350" transform="translate(-4.5,-3.5000001)">
+ <g id="g4268" transform="translate(0.34319025,23.976452)">
+ <path id="path14341" style="color:#000000;" d="M18.588,1.4037,4.2268,18.097,5.4855,19.34,18.588,1.4037z" fill-rule="evenodd" fill="url(#linearGradient7364)"/>
+ <path id="path18921" d="M18.467,1.3138,5.6606,19.073,7.4901,20.688,18.467,1.3138z" fill-rule="evenodd" fill="#fefefe"/>
+ <path id="path27786" stroke-linejoin="round" style="stroke-dasharray:none;" d="m31.161,16.911c0,8.235-6.6758,14.911-14.911,14.911-8.235,0-14.911-6.6758-14.911-14.911,0-8.235,6.6758-14.911,14.911-14.911,8.235,0,14.911,6.6758,14.911,14.911z" fill-rule="evenodd" transform="matrix(1.431529,0,0,1.431529,0.569459,-1.654618)" stroke-dashoffset="0" stroke="#a40000" stroke-linecap="round" stroke-miterlimit="4" stroke-width="0.6985538" fill="url(#radialGradient7366)"/>
+ <path id="path35549" stroke-linejoin="round" d="m31.161,16.911c0,8.235-6.6758,14.911-14.911,14.911-8.235,0-14.911-6.6758-14.911-14.911,0-8.235,6.6758-14.911,14.911-14.911,8.235,0,14.911,6.6758,14.911,14.911z" fill-rule="evenodd" transform="matrix(1.163838,0,0,1.163838,4.824801,2.777556)" stroke-dashoffset="0" stroke="url(#linearGradient7370)" stroke-linecap="round" stroke-miterlimit="4" stroke-width="0.71139598" fill="url(#radialGradient7368)"/>
+ <path id="path10651" stroke-linejoin="round" d="m31.161,16.911c0,8.235-6.6758,14.911-14.911,14.911-8.235,0-14.911-6.6758-14.911-14.911,0-8.235,6.6758-14.911,14.911-14.911,8.235,0,14.911,6.6758,14.911,14.911z" transform="matrix(1.357654,0,0,1.357654,1.769896,-0.493735)" stroke-dashoffset="0" stroke="url(#radialGradient7372)" stroke-linecap="round" stroke-miterlimit="4" stroke-width="0.73656511" fill="none"/>
+ <g id="g4258" stroke-width="1" stroke="#2e3436" stroke-linecap="round" transform="translate(-0.49003984,-0.32669323)">
+ <path id="path35559" stroke-linejoin="miter" style="stroke-dasharray:none;" d="m24.495,22.583,7.5777-9.0209" stroke-miterlimit="4" fill="none"/>
+ <path id="path4256" stroke-linejoin="round" style="stroke-dasharray:none;" d="m-78.668,39.378-5.2437-3.0274-5.2437-3.0274,5.2437-3.0274,5.2437-3.0274,0,6.0549,0,6.0549z" transform="matrix(0.42354759,0.05292161,-0.05292161,0.42354759,68.775627,3.4525942)" stroke-miterlimit="10" fill="#2e3436"/>
+ </g>
+ </g>
+ <path id="path4266" stroke-linejoin="round" d="m23.93,29.705c0,7.8406,0.16334,7.8406,0.16334,7.8406" stroke="#000" stroke-linecap="round" stroke-width="1px" fill="none"/>
+ </g>
+ <path id="path34778" stroke-linejoin="round" style="stroke-dasharray:none;" d="m16.406,17.281c0,0.6731-0.54565,1.2188-1.2188,1.2188-0.6731,0-1.2188-0.54565-1.2188-1.2188,0-0.6731,0.54565-1.2188,1.2188-1.2188,0.6731,0,1.2188,0.54565,1.2188,1.2188z" fill-rule="evenodd" transform="matrix(2.073295,0,0,2.073295,-11.813172,7.2008838)" stroke-dashoffset="0" stroke="#000" stroke-linecap="round" stroke-miterlimit="4" stroke-width="0.48232403" fill="#f3f3f3"/>
+ </g>
+</svg>
diff --git a/blocks/PuitP.svg b/blocks/PuitP.svg
new file mode 100644
index 0000000..9687763
--- /dev/null
+++ b/blocks/PuitP.svg
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg11300" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="48" width="48" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <metadata id="metadata24">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <title id="title3289">base scilan</title>
+ <defs id="defs3">
+ <linearGradient id="linearGradient2894" x1="11.492" gradientUnits="userSpaceOnUse" y1="1.6538" gradientTransform="matrix(0.67122955,0,0,0.66402459,133.88177,40.710976)" x2="17.199" y2="26.729">
+ <stop id="stop2669" stop-color="#FFF" offset="0"/>
+ <stop id="stop2671" stop-color="#fcfcff" stop-opacity="0" offset="1"/>
+ </linearGradient>
+ </defs>
+ <g id="layer1">
+ <g id="layer1-0" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544" transform="translate(-2.0023665,2.2530854)">
+ <g id="g9570" transform="translate(0.34662852,-1.7289484)">
+ <g id="g28489" transform="translate(2.3098495,0.12996954)">
+ <g id="g5362">
+ <g id="layer1-8" transform="matrix(0,-1.4676275,1.4676275,0,-37.823485,137.74736)">
+ <g id="g5857" transform="translate(-0.49999954,-9.6375814e-8)">
+ <g id="g2887" transform="translate(-11.166083,6.0340061e-7)">
+ <rect id="rect6240" stroke-linejoin="miter" style="stroke-dasharray:none;" height="21.668" width="21.668" stroke="#000" stroke-linecap="square" stroke-miterlimit="4" y="30.666" x="79.832" stroke-width="1" fill="#73d216"/>
+ <text id="text10145" font-size="23.03343391px" font-family="Bitstream Vera Sans" xml:space="preserve" font-style="normal" stroke="#2e3436" y="49.5" x="84.5" font-weight="normal" fill="#2e3436"><tspan id="tspan10147" y="49.5" x="84.5">P</tspan></text>
+ <g id="layer1-01" transform="translate(-57.109827,-13.041713)">
+ <path id="path2443" opacity="0.53142856" fill-rule="evenodd" fill="url(#linearGradient2894)" d="m138.61,44.542c-0.28414,0-0.8546,0.14094-0.8546,0.76031l0.0641,12.24c9.5958-0.71392,7.6698-6.1182,19.456-8.8254l-0.034-3.2176c-0.0425-0.83376-0.29284-0.91292-0.8743-0.90802l-17.757-0.04897z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ <path id="path2834" stroke-linejoin="miter" d="M0.015767,24h12.512" stroke="#000" stroke-linecap="butt" stroke-width="1px" fill="none"/>
+ <path id="path2836" transform="translate(35.89844,34.102605)" fill="#000" d="m-22.504-10.103-4.4277,2.5563,0-5.1126,4.4277,2.5563z"/>
+</svg>
diff --git a/blocks/QUANT_f.svg b/blocks/QUANT_f.svg
new file mode 100644
index 0000000..64a2544
--- /dev/null
+++ b/blocks/QUANT_f.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="40" xmlns="http://www.w3.org/2000/svg" version="1.1" height="40">
+ <g id="layer1">
+ <path stroke-linejoin="round" d="M4,33h8v-8h8v-8h8v-8h8" stroke="#000" stroke-linecap="round" stroke-width="2px" fill="none"/>
+ </g>
+</svg>
diff --git a/blocks/RAMP.svg b/blocks/RAMP.svg
new file mode 100644
index 0000000..6a25f6b
--- /dev/null
+++ b/blocks/RAMP.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg width="40" xmlns="http://www.w3.org/2000/svg" version="1.1" height="40">
+ <g id="layer1">
+ <path stroke-width="2px" d="M0,30h10l30-30" stroke="#000" fill="none"/>
+ </g>
+</svg>
diff --git a/blocks/Resistor.svg b/blocks/Resistor.svg
new file mode 100644
index 0000000..d471e63
--- /dev/null
+++ b/blocks/Resistor.svg
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg11300" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="48px" width="48px" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title3289">base scilan</title>
+ <defs id="defs3">
+ <linearGradient id="linearGradient12223" y2="23.083" gradientUnits="userSpaceOnUse" x2="14.152" gradientTransform="matrix(2.0651184,0,0,0.92966997,63.912973,20.040373)" y1="23.083" x1="0.94344">
+ <stop id="stop2699" stop-color="#babdb6" offset="0"/>
+ <stop id="stop2701" stop-color="#555753" offset="1"/>
+ </linearGradient>
+ </defs>
+ <metadata id="metadata4">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Mathieu Drouet / Take a sip</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:source>http://www.takeasip.net/</dc:source>
+ <cc:license rdf:resource="http://creativecommons.org/licenses/publicdomain/"/>
+ <dc:title>base scilan</dc:title>
+ </cc:Work>
+ <cc:License rdf:about="http://creativecommons.org/licenses/publicdomain/">
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g id="layer1">
+ <g id="layer1-0" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544" transform="translate(-2.0023665,2.2530854)">
+ <g id="g9570" transform="translate(0.34662852,-1.7289484)">
+ <g id="g28489" transform="translate(2.3098495,0.12996954)">
+ <g id="g5362">
+ <g id="layer1-8" transform="matrix(0,-1.4676275,1.4676275,0,-37.823485,137.74736)">
+ <g id="g5857" transform="translate(-0.49999954,-9.6375814e-8)">
+ <path id="path12219" stroke-linejoin="miter" d="M61.811,41.453c35.378,0.047,35.378,0.047,35.378,0.047" stroke="#000" stroke-linecap="butt" stroke-width="1px" fill="none"/>
+ <rect id="rect6286" stroke-linejoin="miter" style="stroke-dasharray:none;" height="10.915" width="24.247" stroke="#000" stroke-linecap="square" stroke-miterlimit="4" y="36.042" x="67.377" stroke-width="1" fill="url(#linearGradient12223)"/>
+ <path id="path6288" opacity="0.5" style="color:#000000;" fill-rule="nonzero" display="block" fill="#f7f7f7" d="m68.124,36.667,0,6.5696,23.499-1.7364,0-4.8882-23.499,0.055z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/SATURATION.svg b/blocks/SATURATION.svg
new file mode 100644
index 0000000..7cecac1
--- /dev/null
+++ b/blocks/SATURATION.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="40" xmlns="http://www.w3.org/2000/svg" version="1.1" height="40">
+ <g id="layer1">
+ <path stroke-linejoin="round" d="M2,30h8l20-20h8" stroke="#000" stroke-linecap="round" stroke-width="2px" fill="none"/>
+ </g>
+</svg>
diff --git a/blocks/SELF_SWITCH.svg b/blocks/SELF_SWITCH.svg
new file mode 100644
index 0000000..0344da7
--- /dev/null
+++ b/blocks/SELF_SWITCH.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="40" stroke="#000" xmlns="http://www.w3.org/2000/svg" version="1.1" height="40">
+ <g id="base" stroke-linejoin="round" stroke-linecap="round" stroke-width="2px" fill="none">
+ <path d="M2,30h9l16-10"/>
+ <path d="M30,30h8"/>
+ </g>
+ <g id="command">
+ <polygon fill-opacity="0.1" width="1px" points="18,5,22,5,20,10" fill="#000"/>
+ <path stroke-width="1px" stroke-dasharray="1,1" d="M20,10v28"/>
+ </g>
+</svg>
diff --git a/blocks/SINUS_f.svg b/blocks/SINUS_f.svg
new file mode 100644
index 0000000..7437e12
--- /dev/null
+++ b/blocks/SINUS_f.svg
@@ -0,0 +1,15 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1"
+ id="svg2847" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" sodipodi:docname="3DSCOPE.svg" inkscape:version="0.48.2 r9819" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="32.392px"
+ height="30.443px" viewBox="0 0 32.392 30.443" enable-background="new 0 0 32.392 30.443" xml:space="preserve">
+<sodipodi:namedview id="namedview2999" inkscape:window-maximized="1" inkscape:window-y="-8" inkscape:window-x="-8" inkscape:window-height="850" inkscape:window-width="1440" showgrid="false" inkscape:current-layer="svg2847" inkscape:cy="7.8580735" inkscape:cx="1.5302837" inkscape:zoom="10.36363" inkscape:pageshadow="2" inkscape:pageopacity="0" borderopacity="1" bordercolor="#666666" pagecolor="#ffffff" inkscape:snap-grids="true" objecttolerance="10" gridtolerance="10" guidetolerance="10">
+ </sodipodi:namedview>
+<g id="g9544" transform="matrix(0,0.68137179,-0.68137179,0,-5.746,-4.58742)">
+</g>
+<path id="path3013" inkscape:connector-curvature="0" sodipodi:nodetypes="cssssc" fill="none" stroke="#000000" stroke-width="1.7" stroke-linecap="round" d="
+ M1.93,15.969C3.377,7.092,5.686-5.005,7.43,3.811c1.737,8.781,3.398,34.543,6.272,22.965c2.857-11.507,4.926-34.212,7.044-22.771
+ c2.202,11.89,4.494,36.138,8.009,20.745c2.799-12.254,2.412-10.035,2.412-10.035"/>
+</svg>
diff --git a/blocks/SQUARE_WAVE_f.svg b/blocks/SQUARE_WAVE_f.svg
new file mode 100644
index 0000000..71a0d6c
--- /dev/null
+++ b/blocks/SQUARE_WAVE_f.svg
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" id="Calque_2" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32.392px" height="30.443px" viewBox="0 0 32.392 30.443" enable-background="new 0 0 32.392 30.443" xml:space="preserve">
+<polyline fill="none" stroke="#000000" stroke-width="2" points="2.848,15.815 2.848,1.68 9.597,1.68 9.597,28.517 16.339,28.517
+ 16.302,1.68 23,1.68 23,28.638 29.543,28.659 29.543,15.429 "/>
+</svg>
diff --git a/blocks/STEP_FUNCTION.svg b/blocks/STEP_FUNCTION.svg
new file mode 100644
index 0000000..464c385
--- /dev/null
+++ b/blocks/STEP_FUNCTION.svg
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="40" xmlns="http://www.w3.org/2000/svg" version="1.1" height="40">
+ <g id="layer1">
+ <path stroke-width="2px" d="M0,30h20v-20h20" stroke="#000" fill="none"/>
+ </g>
+</svg>
+
diff --git a/blocks/SUM.svg b/blocks/SUM.svg
new file mode 100644
index 0000000..f76cc1c
--- /dev/null
+++ b/blocks/SUM.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="40" xmlns="http://www.w3.org/2000/svg" version="1.1" height="40">
+ <text y="30" x="14" font-size="30" font-family="serif" fill="black">Σ</text>
+</svg>
diff --git a/blocks/SUMMATION.svg b/blocks/SUMMATION.svg
new file mode 100644
index 0000000..f76cc1c
--- /dev/null
+++ b/blocks/SUMMATION.svg
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="40" xmlns="http://www.w3.org/2000/svg" version="1.1" height="40">
+ <text y="30" x="14" font-size="30" font-family="serif" fill="black">Σ</text>
+</svg>
diff --git a/blocks/SUPER.svg b/blocks/SUPER.svg
new file mode 100644
index 0000000..5c05a34
--- /dev/null
+++ b/blocks/SUPER.svg
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg11300" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="48px" width="48px" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title3289">base scilan</title>
+ <metadata id="metadata4">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Mathieu Drouet / Take a sip</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:source>http://www.takeasip.net/</dc:source>
+ <cc:license rdf:resource="http://creativecommons.org/licenses/publicdomain/"/>
+ <dc:title>base scilan</dc:title>
+ </cc:Work>
+ <cc:License rdf:about="http://creativecommons.org/licenses/publicdomain/">
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g id="layer1">
+ <g id="layer1-0" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544" transform="translate(-2.0023665,2.2530854)">
+ <g id="layer1-1" transform="matrix(0,-1.4676275,1.4676275,0,-27.467942,220.66709)">
+ <g id="layer1-0-9" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544-0" transform="translate(-2.0023665,2.2530854)">
+ <g id="g17368" transform="translate(0,1.5593554)">
+ <g id="g17390" stroke-linejoin="round" transform="translate(0,1.4676267)" stroke="#2e3436" stroke-linecap="round" stroke-miterlimit="4">
+ <path id="path17362" style="stroke-dasharray:none;" d="m26.846-180.26,0,67.511" transform="translate(-7.1596787e-8,-1.5593549)" stroke-width="1.46762753" fill="none"/>
+ <rect id="rect17387" style="stroke-dasharray:none;" stroke-dashoffset="0" transform="matrix(0,-1,1,0,0,0)" height="33.755" width="24.95" y="27.58" x="148.06" stroke-width="1.4676" fill="none"/>
+ <g id="g17364" stroke-dashoffset="0" transform="translate(0,-5.7787914)" fill="#eeeeec">
+ <rect id="rect17339" style="stroke-dasharray:none;" transform="matrix(0,-1,1,0,0,0)" height="13.209" width="13.209" y="20.242" x="124.49" stroke-width="1.4676"/>
+ <rect id="rect17354" style="stroke-dasharray:none;" transform="matrix(0,-1,1,0,0,0)" height="13.129" width="13.129" y="20.282" x="148.19" stroke-width="1.5473"/>
+ </g>
+ <rect id="rect17356" style="stroke-dasharray:none;" stroke-dashoffset="0" transform="matrix(0,-1,1,0,0,0)" height="13.453" width="13.453" y="55.465" x="153.81" stroke-width="1.223" fill="#eeeeec"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/SWITCH.svg b/blocks/SWITCH.svg
new file mode 100644
index 0000000..0344da7
--- /dev/null
+++ b/blocks/SWITCH.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="40" stroke="#000" xmlns="http://www.w3.org/2000/svg" version="1.1" height="40">
+ <g id="base" stroke-linejoin="round" stroke-linecap="round" stroke-width="2px" fill="none">
+ <path d="M2,30h9l16-10"/>
+ <path d="M30,30h8"/>
+ </g>
+ <g id="command">
+ <polygon fill-opacity="0.1" width="1px" points="18,5,22,5,20,10" fill="#000"/>
+ <path stroke-width="1px" stroke-dasharray="1,1" d="M20,10v28"/>
+ </g>
+</svg>
diff --git a/blocks/SWITCH2_m.svg b/blocks/SWITCH2_m.svg
new file mode 100644
index 0000000..0344da7
--- /dev/null
+++ b/blocks/SWITCH2_m.svg
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="40" stroke="#000" xmlns="http://www.w3.org/2000/svg" version="1.1" height="40">
+ <g id="base" stroke-linejoin="round" stroke-linecap="round" stroke-width="2px" fill="none">
+ <path d="M2,30h9l16-10"/>
+ <path d="M30,30h8"/>
+ </g>
+ <g id="command">
+ <polygon fill-opacity="0.1" width="1px" points="18,5,22,5,20,10" fill="#000"/>
+ <path stroke-width="1px" stroke-dasharray="1,1" d="M20,10v28"/>
+ </g>
+</svg>
diff --git a/blocks/SampleCLK.svg b/blocks/SampleCLK.svg
new file mode 100644
index 0000000..19d7fa0
--- /dev/null
+++ b/blocks/SampleCLK.svg
@@ -0,0 +1,14 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="40" xmlns="http://www.w3.org/2000/svg" version="1.1" height="40">
+ <g id="layer">
+ <path id="horizontals" stroke-linejoin="round" d="M6,1h28m-33,13h38m-36,13h33m-30,12h28" stroke="#C00" stroke-linecap="round" stroke-width="2px" fill="none"/>
+ <path id="LeftBorder" stroke-linejoin="round" d="M6,1l-5,13,2,13,3,12" stroke="#C00" stroke-linecap="round" stroke-width="2px" fill="none"/>
+ <path id="RightBorder" stroke-linejoin="round" d="M34,1l5,13-2,13-3,12" stroke="#C00" stroke-linecap="round" stroke-width="2px" fill="none"/>
+ <g id="clock">
+ <circle r="5" stroke="#C00" cy="33" cx="20" stroke-width="0.5px" fill="none"/>
+ <circle cy="33" cx="20" r="0.75" fill="#000"/>
+ <path stroke-linejoin="round" d="M20,33l-2-2" stroke="#000" stroke-linecap="round" stroke-width="0.5" fill="none"/>
+ <path stroke-linejoin="round" d="M20,33l3-3" stroke="#000" stroke-linecap="round" stroke-width="0.5" fill="none"/>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/Self_Switch_off.svg b/blocks/Self_Switch_off.svg
new file mode 100644
index 0000000..df37948
--- /dev/null
+++ b/blocks/Self_Switch_off.svg
@@ -0,0 +1,77 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1"
+ id="svg11300" inkscape:version="0.46" inkscape:export-ydpi="90.000000" inkscape:export-xdpi="90.000000" sodipodi:docbase="/home/jimmac/src/cvs/tango-icon-theme/scalable/actions" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" sodipodi:version="0.32" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" sodipodi:docname="system-shutdown.svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" inkscape:output_extension="org.inkscape.output.svg.inkscape" inkscape:export-filename="/home/jimmac/Desktop/wi-fi.png" xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="80px" height="79.583px"
+ viewBox="-10.667 -16.583 80 79.583" enable-background="new -10.667 -16.583 80 79.583" xml:space="preserve">
+<defs>
+
+
+ <inkscape:perspective sodipodi:type="inkscape:persp3d" inkscape:persp3d-origin="24 : 16 : 1" inkscape:vp_z="48 : 24 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_x="0 : 24 : 1" id="perspective62">
+ </inkscape:perspective>
+</defs>
+<sodipodi:namedview stroke="#ef2929" fill="#fce94f" pagecolor="#ffffff" bordercolor="#666666" borderopacity="0.25490196" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1" inkscape:cx="-112.52652" inkscape:cy="17.878701" inkscape:current-layer="layer1" showgrid="false" inkscape:grid-bbox="true" inkscape:document-units="px" inkscape:showpageshadow="false" inkscape:window-width="872" inkscape:window-height="754" inkscape:window-x="268" inkscape:window-y="94" id="base">
+ </sodipodi:namedview>
+<g id="layer1" inkscape:label="Layer 1" inkscape:groupmode="layer">
+
+ <radialGradient id="rect11518_1_" cx="81.7412" cy="193.0215" r="39.4779" gradientTransform="matrix(1.9951 0 0 -1.8554 -133.6792 406.2393)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#FBFBFB"/>
+ <stop offset="1" style="stop-color:#DCDCDC"/>
+ </radialGradient>
+
+ <path id="rect11518" fill="url(#rect11518_1_)" stroke="#9B9B9B" stroke-width="1" stroke-linejoin="bevel" stroke-miterlimit="10" d="
+ M1.372-14.864h56.053c5.795,0,10.489,4.696,10.489,10.489v55.77c0,5.793-4.694,10.49-10.489,10.49H1.372
+ c-5.792,0-10.488-4.697-10.488-10.49v-55.77C-9.116-10.168-4.42-14.864,1.372-14.864z"/>
+ <path id="rect11528" fill="none" stroke="#FFFFFF" stroke-width="1" stroke-linejoin="bevel" stroke-miterlimit="10" d="
+ M1.316-12.877h56.167c4.504,0,8.158,3.653,8.158,8.158v56.331c0,4.506-3.654,8.156-8.158,8.156H1.316
+ c-4.506,0-8.158-3.65-8.158-8.156V-4.72C-6.842-9.225-3.19-12.877,1.316-12.877z"/>
+
+ <linearGradient id="rect11592_1_" gradientUnits="userSpaceOnUse" x1="225.0801" y1="55.1973" x2="225.0801" y2="-19.6924" gradientTransform="matrix(0.9843 0 0 -0.9579 -192.1539 34.693)">
+ <stop offset="0" style="stop-color:#FBFBFB"/>
+ <stop offset="1" style="stop-color:#DCDCDC"/>
+ </linearGradient>
+ <linearGradient id="rect11592_2_" gradientUnits="userSpaceOnUse" x1="-62.2075" y1="440.248" x2="-50.5009" y2="410.248">
+ <stop offset="0" style="stop-color:#FFFFFF"/>
+ <stop offset="1" style="stop-color:#D1D1D1"/>
+ </linearGradient>
+
+ <path id="rect11592" fill="url(#rect11592_1_)" stroke="url(#rect11592_2_)" stroke-width="1" stroke-linejoin="bevel" stroke-miterlimit="10" d="
+ M10.496-7.356h37.807c2.441,0,4.418,1.978,4.418,4.419v54.048c0,2.441-1.977,4.42-4.418,4.42H10.496
+ c-2.44,0-4.418-1.979-4.418-4.42V-2.937C6.078-5.378,8.056-7.356,10.496-7.356z"/>
+
+ <radialGradient id="rect7580_1_" cx="57.7598" cy="201.8398" r="11.5878" gradientTransform="matrix(2.4079 0 0 -1.8611 -109.6827 418.4173)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#525252"/>
+ <stop offset="1" style="stop-color:#000000"/>
+ </radialGradient>
+ <linearGradient id="rect7580_2_" gradientUnits="userSpaceOnUse" x1="-50.7026" y1="415.7471" x2="-57.8541" y2="428.1275">
+ <stop offset="0" style="stop-color:#FFFFFF"/>
+ <stop offset="1" style="stop-color:#D1D1D1"/>
+ </linearGradient>
+
+ <path id="rect7580" fill="url(#rect7580_1_)" stroke="url(#rect7580_2_)" stroke-width="1" stroke-linejoin="bevel" stroke-miterlimit="10" d="
+ M21.151,4.841h17.175c1.69,0,3.06,1.37,3.06,3.06v34.073c0,1.689-1.369,3.059-3.06,3.059H21.151c-1.689,0-3.059-1.369-3.059-3.059
+ V7.901C18.092,6.211,19.462,4.841,21.151,4.841z"/>
+ <path id="rect7626" fill="#8A8A8A" stroke="#595959" stroke-width="1" stroke-linejoin="bevel" stroke-miterlimit="10" d="
+ M20.979,13.725V9.506c0-1.127,0.913-2.039,2.04-2.039h13.171c1.127,0,2.039,0.912,2.039,2.039v4.219
+ c0,1.125-0.912,2.041-2.039,2.041H23.019C21.892,15.766,20.979,14.85,20.979,13.725z"/>
+
+ <linearGradient id="rect7594_1_" gradientUnits="userSpaceOnUse" x1="205.0234" y1="-85.3086" x2="205.0234" y2="-40.8807" gradientTransform="matrix(1.0592 0 0 0.8081 -187.5606 80.1279)">
+ <stop offset="0" style="stop-color:#FFFFFF"/>
+ <stop offset="0.4602" style="stop-color:#E3E3E3"/>
+ <stop offset="0.6197" style="stop-color:#DADADA;stop-opacity:0.6706"/>
+ <stop offset="1" style="stop-color:#D1D1D1;stop-opacity:0.3429"/>
+ </linearGradient>
+ <radialGradient id="rect7594_2_" cx="-57.8164" cy="419.5859" r="4.4775" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#FFFFFF"/>
+ <stop offset="0.2159" style="stop-color:#FFFFFF"/>
+ <stop offset="0.5" style="stop-color:#838383"/>
+ <stop offset="1" style="stop-color:#838383;stop-opacity:0"/>
+ </radialGradient>
+
+ <path id="rect7594" fill="url(#rect7594_1_)" stroke="url(#rect7594_2_)" stroke-width="1" stroke-linejoin="bevel" stroke-miterlimit="10" d="
+ M21.037,40.96V13.955c0-0.939,0.76-1.699,1.699-1.699h13.738c0.938,0,1.699,0.76,1.699,1.699V40.96
+ c0,0.938-0.761,1.699-1.699,1.699H22.736C21.797,42.659,21.037,41.898,21.037,40.96z"/>
+</g>
+<text transform="matrix(1 0 0 1 40.252 54.958)" font-family="'MyriadPro-Regular'" font-size="21.1506">off</text>
+</svg>
diff --git a/blocks/Self_Switch_on.svg b/blocks/Self_Switch_on.svg
new file mode 100644
index 0000000..50cd1ba
--- /dev/null
+++ b/blocks/Self_Switch_on.svg
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 14.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 43363) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1"
+ id="svg11300" inkscape:version="0.46" inkscape:export-ydpi="90.000000" inkscape:export-xdpi="90.000000" sodipodi:docbase="/home/jimmac/src/cvs/tango-icon-theme/scalable/actions" xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" sodipodi:version="0.32" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" sodipodi:docname="system-shutdown.svg" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" inkscape:output_extension="org.inkscape.output.svg.inkscape" inkscape:export-filename="/home/jimmac/Desktop/wi-fi.png" xmlns:svg="http://www.w3.org/2000/svg"
+ xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" width="80px" height="79.583px"
+ viewBox="0 0 80 79.583" enable-background="new 0 0 80 79.583" xml:space="preserve">
+<defs>
+
+
+ <inkscape:perspective sodipodi:type="inkscape:persp3d" inkscape:persp3d-origin="24 : 16 : 1" inkscape:vp_z="48 : 24 : 1" inkscape:vp_y="0 : 1000 : 0" inkscape:vp_x="0 : 24 : 1" id="perspective62">
+ </inkscape:perspective>
+</defs>
+<sodipodi:namedview stroke="#ef2929" fill="#fce94f" pagecolor="#ffffff" bordercolor="#666666" borderopacity="0.25490196" inkscape:pageopacity="0.0" inkscape:pageshadow="2" inkscape:zoom="1" inkscape:cx="-112.52652" inkscape:cy="17.878701" inkscape:current-layer="layer1" showgrid="false" inkscape:grid-bbox="true" inkscape:document-units="px" inkscape:showpageshadow="false" inkscape:window-width="872" inkscape:window-height="754" inkscape:window-x="268" inkscape:window-y="94" id="base">
+ </sodipodi:namedview>
+<g id="layer1" inkscape:label="Layer 1" inkscape:groupmode="layer">
+
+ <radialGradient id="rect11518_1_" cx="212.9746" cy="-226.6719" r="39.4809" gradientTransform="matrix(1.9951 0 0 -1.8554 -384.1325 -356.1771)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#FBFBFB"/>
+ <stop offset="1" style="stop-color:#DCDCDC"/>
+ </radialGradient>
+ <path id="rect11518" fill="url(#rect11518_1_)" stroke="#9B9B9B" stroke-linejoin="bevel" stroke-miterlimit="10" d="M12.747,1.417
+ H68.8c5.796,0,10.489,4.696,10.489,10.489v55.77c0,5.793-4.693,10.49-10.489,10.49H12.747c-5.792,0-10.488-4.697-10.488-10.49
+ v-55.77C2.259,6.113,6.955,1.417,12.747,1.417z"/>
+ <path id="rect11528" fill="none" stroke="#FFFFFF" stroke-linejoin="bevel" stroke-miterlimit="10" d="M12.691,3.404h56.167
+ c4.504,0,8.158,3.653,8.158,8.158v56.331c0,4.506-3.654,8.156-8.158,8.156H12.691c-4.506,0-8.158-3.65-8.158-8.156V11.562
+ C4.533,7.056,8.185,3.404,12.691,3.404z"/>
+
+ <linearGradient id="rect11592_1_" gradientUnits="userSpaceOnUse" x1="627.2578" y1="-757.7251" x2="627.2578" y2="-832.617" gradientTransform="matrix(0.9843 0 0 -0.9579 -576.6366 -727.7244)">
+ <stop offset="0" style="stop-color:#FBFBFB"/>
+ <stop offset="1" style="stop-color:#DCDCDC"/>
+ </linearGradient>
+ <linearGradient id="rect11592_2_" gradientUnits="userSpaceOnUse" x1="-194.8071" y1="440.2476" x2="-183.1005" y2="410.2476">
+ <stop offset="0" style="stop-color:#FFFFFF"/>
+ <stop offset="1" style="stop-color:#D1D1D1"/>
+ </linearGradient>
+ <path id="rect11592" fill="url(#rect11592_1_)" stroke="url(#rect11592_2_)" stroke-linejoin="bevel" stroke-miterlimit="10" d="
+ M21.871,8.925h37.807c2.441,0,4.418,1.978,4.418,4.419v54.048c0,2.44-1.977,4.421-4.418,4.421H21.871
+ c-2.44,0-4.418-1.98-4.418-4.421V13.344C17.453,10.903,19.431,8.925,21.871,8.925z"/>
+
+ <radialGradient id="rect7580_1_" cx="143.7637" cy="-216.5679" r="11.5867" gradientTransform="matrix(2.4079 0 0 -1.8611 -305.3967 -343.9993)" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#525252"/>
+ <stop offset="1" style="stop-color:#000000"/>
+ </radialGradient>
+ <linearGradient id="rect7580_2_" gradientUnits="userSpaceOnUse" x1="-183.3022" y1="415.7476" x2="-190.4538" y2="428.128">
+ <stop offset="0" style="stop-color:#FFFFFF"/>
+ <stop offset="1" style="stop-color:#D1D1D1"/>
+ </linearGradient>
+ <path id="rect7580" fill="url(#rect7580_1_)" stroke="url(#rect7580_2_)" stroke-linejoin="bevel" stroke-miterlimit="10" d="
+ M32.526,21.122h17.175c1.689,0,3.061,1.37,3.061,3.06v34.072c0,1.688-1.369,3.061-3.061,3.061H32.526
+ c-1.689,0-3.059-1.369-3.059-3.061V24.182C29.467,22.492,30.837,21.122,32.526,21.122z"/>
+ <path id="rect7626" fill="#8A8A8A" stroke="#595959" stroke-linejoin="bevel" stroke-miterlimit="10" d="M34.394,50.64h13.17
+ c1.127,0,2.039,0.916,2.039,2.041v4.22c0,1.127-0.912,2.039-2.039,2.039h-13.17c-1.127,0-2.04-0.912-2.04-2.039v-4.22
+ C32.354,51.556,33.267,50.64,34.394,50.64z"/>
+
+ <linearGradient id="rect7594_1_" gradientUnits="userSpaceOnUse" x1="569.3838" y1="-1048.9258" x2="569.3838" y2="-1004.4979" gradientTransform="matrix(1.0592 0 0 -0.8081 -562.1112 -792.4204)">
+ <stop offset="0" style="stop-color:#FFFFFF"/>
+ <stop offset="0.4602" style="stop-color:#E3E3E3"/>
+ <stop offset="0.6197" style="stop-color:#DADADA;stop-opacity:0.6706"/>
+ <stop offset="1" style="stop-color:#D1D1D1;stop-opacity:0.3429"/>
+ </linearGradient>
+ <radialGradient id="rect7594_2_" cx="-190.416" cy="419.5854" r="4.4775" gradientUnits="userSpaceOnUse">
+ <stop offset="0" style="stop-color:#FFFFFF"/>
+ <stop offset="0.2159" style="stop-color:#FFFFFF"/>
+ <stop offset="0.5" style="stop-color:#838383"/>
+ <stop offset="1" style="stop-color:#838383;stop-opacity:0"/>
+ </radialGradient>
+ <path id="rect7594" fill="url(#rect7594_1_)" stroke="url(#rect7594_2_)" stroke-linejoin="bevel" stroke-miterlimit="10" d="
+ M34.111,23.747H47.85c0.938,0,1.698,0.761,1.698,1.699v27.006c0,0.938-0.761,1.698-1.698,1.698H34.111
+ c-0.939,0-1.699-0.761-1.699-1.698V25.446C32.412,24.508,33.172,23.747,34.111,23.747z"/>
+</g>
+<text transform="matrix(1 0 0 1 51.543 21.1235)" font-family="'MyriadPro-Regular'" font-size="21.1506">on</text>
+</svg>
diff --git a/blocks/SourceP.svg b/blocks/SourceP.svg
new file mode 100644
index 0000000..ff73c6c
--- /dev/null
+++ b/blocks/SourceP.svg
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg11300" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="48px" width="48px" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title3289">base scilan</title>
+ <defs id="defs3">
+ <linearGradient id="linearGradient2845" y2="26.729" gradientUnits="userSpaceOnUse" x2="17.199" gradientTransform="matrix(0.67122955,0,0,0.66402459,133.88177,40.710976)" y1="1.6538" x1="11.492">
+ <stop id="stop2669" stop-color="#FFF" offset="0"/>
+ <stop id="stop2671" stop-color="#fcfcff" stop-opacity="0.0000000" offset="1"/>
+ </linearGradient>
+ </defs>
+ <metadata id="metadata4">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Mathieu Drouet / Take a sip</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:source>http://www.takeasip.net/</dc:source>
+ <cc:license rdf:resource="http://creativecommons.org/licenses/publicdomain/"/>
+ <dc:title>base scilan</dc:title>
+ </cc:Work>
+ <cc:License rdf:about="http://creativecommons.org/licenses/publicdomain/">
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g id="layer1">
+ <g id="layer1-0" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544" transform="translate(-2.0023665,2.2530854)">
+ <g id="g9570" transform="translate(0.34662852,-1.7289484)">
+ <g id="g28489" transform="translate(2.3098495,0.12996954)">
+ <g id="g5362">
+ <g id="layer1-8" transform="matrix(0,-1.4676275,1.4676275,0,-37.823485,137.74736)">
+ <g id="g5857" transform="translate(-0.49999954,-9.6375814e-8)">
+ <g id="g2837" transform="translate(11.833914,0)">
+ <g id="g12890" transform="translate(-22.999997,6.0340061e-7)">
+ <rect id="rect6240" stroke-linejoin="miter" style="stroke-dasharray:none;" height="21.668" width="21.668" stroke="#000" stroke-linecap="square" stroke-miterlimit="4" y="30.666" x="79.832" stroke-width="1" fill="#73d216"/>
+ <text id="text10145" font-size="23.03343391px" font-weight="normal" xml:space="preserve" font-style="normal" stroke="#2e3436" y="49.5" x="84.5" font-family="Bitstream Vera Sans" fill="#2e3436"><tspan id="tspan10147" y="49.5" x="84.5">S</tspan></text>
+ <g id="layer1-01" transform="translate(-57.109827,-13.041713)">
+ <path id="path2443" opacity="0.53142856" d="m138.61,44.542c-0.28414,0-0.8546,0.14094-0.8546,0.76031l0.0641,12.24c9.5958-0.71392,7.6698-6.1182,19.456-8.8254l-0.034-3.2176c-0.0425-0.83376-0.29284-0.91292-0.8743-0.90802l-17.757-0.04897z" fill="url(#linearGradient2845)" fill-rule="evenodd"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ <path id="path2834" stroke-linejoin="miter" d="m34.503,24h12.512" stroke="#000" stroke-linecap="butt" stroke-width="1px" fill="none"/>
+ <path id="path2836" fill="#000" d="m47.882,24-4.4277,2.5563,0-5.1126,4.4277,2.5563z"/>
+</svg>
diff --git a/blocks/VanneReglante.svg b/blocks/VanneReglante.svg
new file mode 100644
index 0000000..1c4cbb8
--- /dev/null
+++ b/blocks/VanneReglante.svg
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg11300" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="48px" width="48px" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <defs id="defs3">
+ <linearGradient id="linearGradient2308">
+ <stop id="stop2310" stop-color="#73d216" offset="0"/>
+ <stop id="stop2312" stop-color="#4e9a06" offset="1"/>
+ </linearGradient>
+ <radialGradient id="radialGradient41523" xlink:href="#linearGradient2308" gradientUnits="userSpaceOnUse" cy="11.13" cx="16.762" gradientTransform="matrix(1,0,0,1.0143566,0,-0.15979618)" r="5.1606"/>
+ <radialGradient id="radialGradient41559" xlink:href="#linearGradient2308" gradientUnits="userSpaceOnUse" cy="23.852" cx="16.558" gradientTransform="matrix(1,0,0,1.6724318,0,-16.038632)" r="10.621"/>
+ <radialGradient id="radialGradient41567" xlink:href="#linearGradient2308" gradientUnits="userSpaceOnUse" cy="24" cx="24" gradientTransform="matrix(1,0,0,0.94526472,0,1.3136468)" r="21.402"/>
+ <radialGradient id="radialGradient41611" xlink:href="#linearGradient2308" gradientUnits="userSpaceOnUse" cy="32.798" cx="22.292" gradientTransform="matrix(0,0.79622462,0.28794563,0,10.370068,0.08587213)" r="16.956"/>
+ </defs>
+ <metadata id="metadata4">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title/>
+ </cc:Agent>
+ </dc:creator>
+ <dc:source/>
+ <cc:license rdf:resource=""/>
+ <dc:title/>
+ <dc:subject>
+ <rdf:Bag/>
+ </dc:subject>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g id="layer1">
+ <g id="g11414" transform="matrix(-2.0469267,0,0,1.1246724,57.892685,-2.8253313)">
+ <g id="g18457" stroke-miterlimit="4" transform="translate(-0.20425633,-1.244093)">
+ <path id="path8643" stroke-linejoin="round" style="color:#000000;stroke-dasharray:none;" d="m19.201,16.179,2.3942,0-4.7984,18.852-4.8672-18.846,2.4761,0,4.7952-0.0058z" fill-rule="evenodd" stroke-dashoffset="0" stroke="#2e3436" stroke-linecap="round" stroke-width="0.65907651000000000" fill="url(#radialGradient41611)"/>
+ <path id="path8658" opacity="0.48128339" stroke-linejoin="miter" style="color:#000000;stroke-dasharray:none;" d="m18.879,16.822,2.0608,0-4.1761,16.36-4.2071-16.375,2.112,0,4.2104,0.01507z" stroke-dashoffset="0" stroke="#FFF" stroke-linecap="butt" stroke-width="0.65907651" fill="none"/>
+ <g id="layer1-3" style="stroke-dasharray:none;" transform="matrix(0.25142998,0,0,0.45579233,5.3974763,24.234636)" stroke-width="1.94690073" fill="url(#radialGradient41567)">
+ <g id="g11414-2" style="stroke-dasharray:none;" transform="matrix(-2.0469267,0,0,1.1246724,57.892685,-2.8253313)" stroke-miterlimit="4" stroke-width="1.28315651" fill="url(#radialGradient41567)">
+ <g id="g11444-9" style="stroke-dasharray:none;" stroke-width="1.28315651" stroke-miterlimit="4" fill="url(#radialGradient41567)">
+ <path id="path8643-7" stroke-linejoin="round" style="color:#000000;stroke-dasharray:none;" d="m26.538,32.491,0,8.4825l-19.96-17,19.954-17.244v8.7725l0.0062,16.989z" fill-rule="evenodd" stroke-dashoffset="0" stroke="#2e3436" stroke-linecap="round" stroke-miterlimit="4" stroke-width="1.28315651"/>
+ <path id="path8645-3" opacity="0.5080214" style="color:#000000;" d="M25.988,7.978c-2.562,16.969,8.117,30.658-18.441,15.986l18.441-15.986z" fill-rule="evenodd"/>
+ <path id="path8658-4" opacity="0.48128339" stroke-linejoin="miter" style="color:#000000;stroke-dasharray:none;" d="m25.857,31.352,0,7.3014l-17.322-14.795,17.338-14.906v7.4826l-0.01596,14.917z" stroke-dashoffset="0" stroke="#FFF" stroke-linecap="butt" stroke-miterlimit="4" stroke-width="1.28315651"/>
+ </g>
+ </g>
+ </g>
+ <g id="g11414-1" style="stroke-dasharray:none;" transform="matrix(0.51371336,0,0,0.51371334,13.595848,22.920723)" stroke-width="1.28296554" fill="url(#radialGradient41559)">
+ <g id="g11444-3" style="stroke-dasharray:none;" stroke-width="1.28296554" stroke-miterlimit="4" fill="url(#radialGradient41559)">
+ <path id="path8643-6" stroke-linejoin="round" style="color:#000000;stroke-dasharray:none;" d="m26.538,32.491,0,8.4825l-19.96-17,19.954-17.244v8.7725l0.0062,16.989z" fill-rule="evenodd" stroke-dashoffset="0" stroke="#2e3436" stroke-linecap="round" stroke-miterlimit="4" stroke-width="1.28296554"/>
+ <path id="path8645-5" opacity="0.5080214" style="color:#000000;" d="M25.988,7.978c-2.562,16.969,8.117,30.658-18.441,15.986l18.441-15.986z" fill-rule="evenodd"/>
+ <path id="path8658-7" opacity="0.48128339" stroke-linejoin="miter" style="color:#000000;stroke-dasharray:none;" d="m25.857,31.352,0,7.3014l-17.322-14.795,17.338-14.906v7.4826l-0.01596,14.917z" stroke-dashoffset="0" stroke="#FFF" stroke-linecap="butt" stroke-miterlimit="4" stroke-width="1.28296554"/>
+ </g>
+ </g>
+ <path id="path17942" stroke-linejoin="round" style="stroke-dasharray:none;" d="m16.762,6.2254c2.6681,0,4.831,4.3922,4.831,9.8102h-9.6621c0-5.418,2.1629-9.8102,4.831-9.8102z" stroke-dashoffset="0" stroke="#2e3436" stroke-linecap="round" stroke-width="0.65907651000000000" fill="url(#radialGradient41523)"/>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/VariableResistor.svg b/blocks/VariableResistor.svg
new file mode 100644
index 0000000..5783810
--- /dev/null
+++ b/blocks/VariableResistor.svg
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg11300" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://www.w3.org/2000/svg" height="48px" width="48px" version="1.1" xmlns:cc="http://creativecommons.org/ns#" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:dc="http://purl.org/dc/elements/1.1/">
+ <title id="title3289">base scilan</title>
+ <defs id="defs3">
+ <linearGradient id="linearGradient12223" y2="23.083" gradientUnits="userSpaceOnUse" x2="14.152" gradientTransform="matrix(2.0651184,0,0,0.92966997,63.912973,20.040373)" y1="23.083" x1="0.94344">
+ <stop id="stop2699" stop-color="#babdb6" offset="0"/>
+ <stop id="stop2701" stop-color="#555753" offset="1"/>
+ </linearGradient>
+ <linearGradient id="linearGradient11733" y2="-11.5" gradientUnits="userSpaceOnUse" x2="-16.028" y1="-11.5" x1="-27.972">
+ <stop id="stop2699-8" stop-color="#babdb6" offset="0"/>
+ <stop id="stop2701-3" stop-color="#555753" offset="1"/>
+ </linearGradient>
+ </defs>
+ <metadata id="metadata4">
+ <rdf:RDF>
+ <cc:Work rdf:about="">
+ <dc:format>image/svg+xml</dc:format>
+ <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Mathieu Drouet / Take a sip</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:source>http://www.takeasip.net/</dc:source>
+ <cc:license rdf:resource="http://creativecommons.org/licenses/publicdomain/"/>
+ <dc:title>base scilan</dc:title>
+ </cc:Work>
+ <cc:License rdf:about="http://creativecommons.org/licenses/publicdomain/">
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Reproduction"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#Distribution"/>
+ <cc:permits rdf:resource="http://creativecommons.org/ns#DerivativeWorks"/>
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g id="layer1">
+ <g id="layer1-0" transform="matrix(0,0.68137179,-0.68137179,0,39.302853,7.826163)">
+ <g id="g9544" transform="translate(-2.0023665,2.2530854)">
+ <g id="g9570" transform="translate(0.34662852,-1.7289484)">
+ <g id="g28489" transform="translate(2.3098495,0.12996954)">
+ <g id="g5362">
+ <g id="layer1-8" transform="matrix(0,-1.4676275,1.4676275,0,-37.823485,137.74736)">
+ <g id="g5857" transform="translate(-0.49999954,-9.6375814e-8)">
+ <g id="g2866">
+ <path id="path12219" stroke-linejoin="miter" d="M61.811,41.453c35.378,0.047,35.378,0.047,35.378,0.047" stroke="#000" stroke-linecap="butt" stroke-width="1px" fill="none"/>
+ <rect id="rect6286" stroke-linejoin="miter" style="stroke-dasharray:none;" height="10.915" width="24.247" stroke="#000" stroke-linecap="square" stroke-miterlimit="4" y="36.042" x="67.377" stroke-width="1" fill="url(#linearGradient12223)"/>
+ <path id="path6288" opacity="0.5" style="color:#000000;" d="m68.124,36.667,0,6.5696,23.499-1.7364,0-4.8882-23.499,0.055z" fill-rule="nonzero" display="block" fill="#f7f7f7"/>
+ <g id="layer1-7" transform="translate(-18.499998,3.4999997)">
+ <g id="g9570-2" transform="matrix(0,0.68137179,-0.68137179,0,38.945721,6.6979899)">
+ <g id="g28489-3" transform="translate(2.3098495,0.12996954)">
+ <path id="path13951" stroke-linejoin="miter" style="stroke-dasharray:none;" fill="url(#linearGradient11733)" transform="matrix(0,1.5664233,-1.5664233,0,9.7613055,-46.467835)" stroke="#000" stroke-linecap="square" stroke-miterlimit="4" stroke-width="0.85296386" d="m-22-16,2.5981,4.5,2.5981,4.5h-5.196l-5.1962-1E-7,2.598-4.5,2.598-4.5z"/>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/VirtualCLK0.svg b/blocks/VirtualCLK0.svg
new file mode 100644
index 0000000..9ade9ae
--- /dev/null
+++ b/blocks/VirtualCLK0.svg
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg2855" xmlns="http://www.w3.org/2000/svg" height="33.259" width="33.259" version="1.1">
+ <defs id="defs2857"></defs>
+ <g id="layer1" transform="translate(-358.3703,-515.73248)">
+ <g id="g31086" stroke-linecap="round" stroke-miterlimit="4" transform="matrix(0,0.68137177,-0.68137177,0,390.03482,516.42453)">
+ <path id="path35549-4" stroke-linejoin="round" style="stroke-dasharray:none;" d="m31.161,16.911a14.911,14.911,0,1,1,-29.821,0,14.911,14.911,0,1,1,29.821,0z" stroke-dashoffset="0" transform="matrix(1.5876104,0,0,1.5876104,-2.4081283,-4.7821171)" stroke="#C00" stroke-width="0.92442554" fill="none"/>
+ <path id="path34778" stroke-linejoin="round" style="stroke-dasharray:none;" d="m16.406,17.281a1.2188,1.2188,0,1,1,-2.4375,0,1.2188,1.2188,0,1,1,2.4375,0z" fill-rule="evenodd" transform="matrix(2.073295,0,0,2.073295,-7.310224,-13.13682)" stroke-dashoffset="0" stroke="#000" stroke-width="0.70787203" fill="#000"/>
+ <path id="path35559" stroke-linejoin="miter" style="stroke-dasharray:none;" d="M22.177,20.718,13.156,13.14" stroke="#000" stroke-width="2.93525505" fill="none"/>
+ <path id="path35561" stroke-linejoin="miter" style="stroke-dasharray:none;" d="m19.409,29.777,2.96-4.4933" stroke="#000" stroke-width="2.93525505" fill="none"/>
+ </g>
+ </g>
+</svg>
diff --git a/blocks/VoltageSensor.svg b/blocks/VoltageSensor.svg
new file mode 100644
index 0000000..04ead6b
--- /dev/null
+++ b/blocks/VoltageSensor.svg
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+<svg id="svg3643" xmlns="http://www.w3.org/2000/svg" height="43.69" width="43.69" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink">
+ <defs id="defs3647">
+ <radialGradient id="XMLID_52_" gradientUnits="userSpaceOnUse" cy="23.333" cx="165.06" gradientTransform="matrix(1,0,0,1.0103,0,-0.159801)" r="7.2848">
+ <stop id="stop812" stop-color="#ef3535" offset="0"/>
+ <stop id="stop2239" stop-color="#c91a1a" offset="0"/>
+ <stop id="stop814" stop-color="#ff4c4c" offset="1"/>
+ </radialGradient>
+ <radialGradient id="radialGradient7366" xlink:href="#XMLID_52_" gradientUnits="userSpaceOnUse" cy="6.8283" cx="8.7468" r="29.89"/>
+ <radialGradient id="radialGradient7368" gradientUnits="userSpaceOnUse" cy="10.045" cx="11.902" r="29.293">
+ <stop id="stop2147" stop-color="#fffffd" offset="0"/>
+ <stop id="stop2149" stop-color="#cbcbc9" offset="1"/>
+ </radialGradient>
+ <linearGradient id="linearGradient7370" y2="25.884" gradientUnits="userSpaceOnUse" x2="22.218" y1="7.7893" x1="6.3422">
+ <stop id="stop10655" stop-color="#f3f4ff" offset="0"/>
+ <stop id="stop10657" stop-color="#9193af" offset="1"/>
+ </linearGradient>
+ <radialGradient id="radialGradient7372" xlink:href="#XMLID_52_" gradientUnits="userSpaceOnUse" cy="10.584" cx="11.329" r="15.532"/>
+ </defs>
+ <g id="layer1" transform="translate(2.1701242,-21.184893)">
+ <g id="g7350" transform="translate(-4.5,-3.5000001)">
+ <path id="path27786" stroke-linejoin="round" style="stroke-dasharray:none;" d="m31.161,16.911a14.911,14.911,0,1,1,-29.821,0,14.911,14.911,0,1,1,29.821,0z" fill-rule="evenodd" transform="matrix(1.431529,0,0,1.431529,0.91264925,22.321834)" stroke-dashoffset="0" stroke="#a40000" stroke-linecap="round" stroke-miterlimit="4" stroke-width="0.6985538" fill="url(#radialGradient7366)"/>
+ <path id="path35549" stroke-linejoin="round" d="m31.161,16.911a14.911,14.911,0,1,1,-29.821,0,14.911,14.911,0,1,1,29.821,0z" fill-rule="evenodd" transform="matrix(1.163838,0,0,1.163838,5.1679912,26.754008)" stroke-dashoffset="0" stroke="url(#linearGradient7370)" stroke-linecap="round" stroke-miterlimit="4" stroke-width="0.71139598" fill="url(#radialGradient7368)"/>
+ <path id="path10651" stroke-linejoin="round" d="m31.161,16.911a14.911,14.911,0,1,1,-29.821,0,14.911,14.911,0,1,1,29.821,0z" stroke-dashoffset="0" transform="matrix(1.357654,0,0,1.357654,2.1130862,23.482717)" stroke="url(#radialGradient7372)" stroke-linecap="round" stroke-miterlimit="4" stroke-width="0.73656511" fill="none"/>
+ <g id="g4258" stroke-width="1" stroke="#2e3436" stroke-linecap="round" transform="translate(-0.14684959,23.649759)">
+ <path id="path35559" stroke-linejoin="miter" style="stroke-dasharray:none;" d="m24.495,22.583,7.5777-9.0209" stroke-miterlimit="4" fill="none"/>
+ <path id="path4256" stroke-linejoin="round" style="stroke-dasharray:none;" d="m-78.668,39.378-5.2437-3.0274-5.2437-3.0274,5.2437-3.0274,5.2437-3.0274,0,6.0549,0,6.0549z" transform="matrix(0.42354759,0.05292161,-0.05292161,0.42354759,68.775627,3.4525942)" stroke-miterlimit="10" fill="#2e3436"/>
+ </g>
+ <text id="text4262" font-weight="normal" xml:space="preserve" font-size="10.41038227px" font-style="normal" y="60.001255" x="20.146564" font-family="Bitstream Vera Sans" fill="#000000"><tspan id="tspan4264" y="60.001255" x="20.146564" font-weight="bold">V</tspan></text>
+ <path id="path4266" stroke-linejoin="round" d="m23.93,29.705c0,7.8406,0.16334,7.8406,0.16334,7.8406" stroke="#000" stroke-linecap="round" stroke-width="1px" fill="none"/>
+ </g>
+ <path id="path34778" stroke-linejoin="round" style="stroke-dasharray:none;" d="m16.406,17.281a1.2188,1.2188,0,1,1,-2.4375,0,1.2188,1.2188,0,1,1,2.4375,0z" fill-rule="evenodd" transform="matrix(2.073295,0,0,2.073295,-11.813172,7.2008838)" stroke-dashoffset="0" stroke="#000" stroke-linecap="round" stroke-miterlimit="4" stroke-width="0.48232403" fill="#f3f3f3"/>
+ </g>
+</svg>
diff --git a/blocks/sawtooth.svg b/blocks/sawtooth.svg
new file mode 100644
index 0000000..16e2fc9
--- /dev/null
+++ b/blocks/sawtooth.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
+<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="32.392px" height="30.443px" viewBox="0 0 32.392 30.443" enable-background="new 0 0 32.392 30.443" xml:space="preserve">
+<polyline fill="none" stroke="#000000" stroke-width="2" points="2.848,15.815 9.597,1.68 9.597,28.517 23,1.68 23,28.638 29.543,15.429"/>
+</svg>
diff --git a/config/keyhandler-commons.xml b/config/keyhandler-commons.xml
new file mode 100644
index 0000000..1e2c159
--- /dev/null
+++ b/config/keyhandler-commons.xml
@@ -0,0 +1,27 @@
+<mxEditor>
+ <mxDefaultKeyHandler as="keyHandler">
+ <add as="8" action="collapse"/>
+ <add as="13" action="expand"/>
+ <add as="33" action="exitGroup"/>
+ <add as="34" action="enterGroup"/>
+ <add as="35" action="refresh"/>
+ <add as="36" action="home"/>
+ <add as="37" action="selectPrevious"/>
+ <add as="38" action="selectParent"/>
+ <add as="40" action="selectChild"/>
+ <add as="39" action="selectNext"/>
+ <add as="46" action="delete"/>
+ <add as="65" control="1" action="selectAll"/>
+ <add as="90" control="1" action="undo"/>
+ <add as="89" control="1" action="redo"/>
+ <add as="88" control="1" action="cut"/>
+ <add as="67" control="1" action="copy"/>
+ <add as="86" control="1" action="paste"/>
+ <add as="71" control="1" action="group"/>
+ <add as="85" control="1" action="ungroup"/>
+ <add as="113" action="edit"/>
+ <add as="123" action="showProperties"/>
+ <add as="107" action="zoomIn"/>
+ <add as="109" action="zoomOut"/>
+ </mxDefaultKeyHandler>
+</mxEditor>
diff --git a/images/add.png b/images/add.png
new file mode 100644
index 0000000..bf5f8ed
--- /dev/null
+++ b/images/add.png
Binary files differ
diff --git a/images/camera.png b/images/camera.png
new file mode 100644
index 0000000..aecc94d
--- /dev/null
+++ b/images/camera.png
Binary files differ
diff --git a/images/check.png b/images/check.png
new file mode 100644
index 0000000..ce81bce
--- /dev/null
+++ b/images/check.png
Binary files differ
diff --git a/images/close.png b/images/close.png
new file mode 100644
index 0000000..4de4396
--- /dev/null
+++ b/images/close.png
Binary files differ
diff --git a/images/connector.gif b/images/connector.gif
new file mode 100644
index 0000000..326e061
--- /dev/null
+++ b/images/connector.gif
Binary files differ
diff --git a/images/copy.png b/images/copy.png
new file mode 100644
index 0000000..a987d43
--- /dev/null
+++ b/images/copy.png
Binary files differ
diff --git a/images/cut.png b/images/cut.png
new file mode 100644
index 0000000..52bf944
--- /dev/null
+++ b/images/cut.png
Binary files differ
diff --git a/images/delete2.png b/images/delete2.png
new file mode 100644
index 0000000..be78c61
--- /dev/null
+++ b/images/delete2.png
Binary files differ
diff --git a/images/dot.gif b/images/dot.gif
new file mode 100644
index 0000000..08b9947
--- /dev/null
+++ b/images/dot.gif
Binary files differ
diff --git a/images/export1.png b/images/export1.png
new file mode 100644
index 0000000..b8a01b8
--- /dev/null
+++ b/images/export1.png
Binary files differ
diff --git a/images/fit_to_size.png b/images/fit_to_size.png
new file mode 100644
index 0000000..4de46b0
--- /dev/null
+++ b/images/fit_to_size.png
Binary files differ
diff --git a/images/gradient_background.jpg b/images/gradient_background.jpg
new file mode 100644
index 0000000..7dbf35b
--- /dev/null
+++ b/images/gradient_background.jpg
Binary files differ
diff --git a/images/green-dot.gif b/images/green-dot.gif
new file mode 100644
index 0000000..acaf7b2
--- /dev/null
+++ b/images/green-dot.gif
Binary files differ
diff --git a/images/grid.gif b/images/grid.gif
new file mode 100644
index 0000000..a82a20d
--- /dev/null
+++ b/images/grid.gif
Binary files differ
diff --git a/images/group.png b/images/group.png
new file mode 100644
index 0000000..585ad79
--- /dev/null
+++ b/images/group.png
Binary files differ
diff --git a/images/icons48/column.png b/images/icons48/column.png
new file mode 100644
index 0000000..5ae2c24
--- /dev/null
+++ b/images/icons48/column.png
Binary files differ
diff --git a/images/icons48/earth.png b/images/icons48/earth.png
new file mode 100644
index 0000000..4493880
--- /dev/null
+++ b/images/icons48/earth.png
Binary files differ
diff --git a/images/icons48/gear.png b/images/icons48/gear.png
new file mode 100644
index 0000000..647d897
--- /dev/null
+++ b/images/icons48/gear.png
Binary files differ
diff --git a/images/icons48/keys.png b/images/icons48/keys.png
new file mode 100644
index 0000000..41828e4
--- /dev/null
+++ b/images/icons48/keys.png
Binary files differ
diff --git a/images/icons48/mail_new.png b/images/icons48/mail_new.png
new file mode 100644
index 0000000..16c6662
--- /dev/null
+++ b/images/icons48/mail_new.png
Binary files differ
diff --git a/images/icons48/server.png b/images/icons48/server.png
new file mode 100644
index 0000000..9621c6e
--- /dev/null
+++ b/images/icons48/server.png
Binary files differ
diff --git a/images/icons48/table.png b/images/icons48/table.png
new file mode 100644
index 0000000..d4df646
--- /dev/null
+++ b/images/icons48/table.png
Binary files differ
diff --git a/images/key.png b/images/key.png
new file mode 100644
index 0000000..e66758a
--- /dev/null
+++ b/images/key.png
Binary files differ
diff --git a/images/loading.gif b/images/loading.gif
new file mode 100644
index 0000000..7bb834d
--- /dev/null
+++ b/images/loading.gif
Binary files differ
diff --git a/images/navigate_minus.png b/images/navigate_minus.png
new file mode 100644
index 0000000..71edaf9
--- /dev/null
+++ b/images/navigate_minus.png
Binary files differ
diff --git a/images/navigate_plus.png b/images/navigate_plus.png
new file mode 100644
index 0000000..b5b7e87
--- /dev/null
+++ b/images/navigate_plus.png
Binary files differ
diff --git a/images/paste.png b/images/paste.png
new file mode 100644
index 0000000..fd628d9
--- /dev/null
+++ b/images/paste.png
Binary files differ
diff --git a/images/plus.png b/images/plus.png
new file mode 100644
index 0000000..24a84bb
--- /dev/null
+++ b/images/plus.png
Binary files differ
diff --git a/images/press32.png b/images/press32.png
new file mode 100644
index 0000000..f00e3f7
--- /dev/null
+++ b/images/press32.png
Binary files differ
diff --git a/images/print32.png b/images/print32.png
new file mode 100644
index 0000000..0cca86c
--- /dev/null
+++ b/images/print32.png
Binary files differ
diff --git a/images/printer.png b/images/printer.png
new file mode 100644
index 0000000..6004816
--- /dev/null
+++ b/images/printer.png
Binary files differ
diff --git a/images/redo.png b/images/redo.png
new file mode 100644
index 0000000..3eae59c
--- /dev/null
+++ b/images/redo.png
Binary files differ
diff --git a/images/sidebar_bg.gif b/images/sidebar_bg.gif
new file mode 100644
index 0000000..67e8244
--- /dev/null
+++ b/images/sidebar_bg.gif
Binary files differ
diff --git a/images/spacer.gif b/images/spacer.gif
new file mode 100644
index 0000000..35d42e8
--- /dev/null
+++ b/images/spacer.gif
Binary files differ
diff --git a/images/toolbar_bg.gif b/images/toolbar_bg.gif
new file mode 100644
index 0000000..87b9374
--- /dev/null
+++ b/images/toolbar_bg.gif
Binary files differ
diff --git a/images/undo.png b/images/undo.png
new file mode 100644
index 0000000..4ba0ffb
--- /dev/null
+++ b/images/undo.png
Binary files differ
diff --git a/images/view_1_1.png b/images/view_1_1.png
new file mode 100644
index 0000000..88657a1
--- /dev/null
+++ b/images/view_1_1.png
Binary files differ
diff --git a/images/view_1_132.png b/images/view_1_132.png
new file mode 100644
index 0000000..e9a1b72
--- /dev/null
+++ b/images/view_1_132.png
Binary files differ
diff --git a/images/view_next.png b/images/view_next.png
new file mode 100644
index 0000000..b4094f0
--- /dev/null
+++ b/images/view_next.png
Binary files differ
diff --git a/images/view_previous.png b/images/view_previous.png
new file mode 100644
index 0000000..b385b44
--- /dev/null
+++ b/images/view_previous.png
Binary files differ
diff --git a/images/wires-grid.gif b/images/wires-grid.gif
new file mode 100644
index 0000000..ad888a2
--- /dev/null
+++ b/images/wires-grid.gif
Binary files differ
diff --git a/images/zoom_in.png b/images/zoom_in.png
new file mode 100644
index 0000000..ad6abb9
--- /dev/null
+++ b/images/zoom_in.png
Binary files differ
diff --git a/images/zoom_in32.png b/images/zoom_in32.png
new file mode 100644
index 0000000..438ff0f
--- /dev/null
+++ b/images/zoom_in32.png
Binary files differ
diff --git a/images/zoom_out.png b/images/zoom_out.png
new file mode 100644
index 0000000..0566f26
--- /dev/null
+++ b/images/zoom_out.png
Binary files differ
diff --git a/images/zoom_out32.png b/images/zoom_out32.png
new file mode 100644
index 0000000..8edb765
--- /dev/null
+++ b/images/zoom_out32.png
Binary files differ
diff --git a/index.html b/index.html
new file mode 100644
index 0000000..26791e2
--- /dev/null
+++ b/index.html
@@ -0,0 +1,1050 @@
+<html>
+<head>
+ <meta content="text/html;charset=utf-8" http-equiv="Content-Type">
+ <meta content="utf-8" http-equiv="encoding">
+ <title>Xcos</title>
+ <style type="text/css" media="screen">
+ BODY {
+ font-family: Arial;
+ }
+ H1 {
+ font-size: 18px;
+ }
+ H2 {
+ font-size: 16px;
+ }
+ </style>
+
+ <!-- Sets the basepath for the library if not in same directory -->
+ <script type="text/javascript">
+ mxBasePath = 'src';
+ </script>
+
+ <!-- Loads and initializes the library -->
+ <script type="text/javascript" src="src/js/mxClient.js"></script>
+
+ <link rel="stylesheet" href="jquery/jquery-ui.css">
+ <script src="jquery/jquery-1.8.2.js"></script>
+ <!-- Example code -->
+ <script type="text/javascript">
+ // Program starts here. Creates a sample graph in the
+ // DOM node with the specified ID. This function is invoked
+ // from the onLoad event handler of the document (see below).
+ function main(container, outline, toolbar, sidebar, status)
+ {
+ // Checks if the browser is supported
+ if (!mxClient.isBrowserSupported())
+ {
+ // Displays an error message if the browser is not supported.
+ mxUtils.error('Browser is not supported!', 200, false);
+ }
+ else
+ {
+ // Assigns some global constants for general behaviour, eg. minimum
+ // size (in pixels) of the active region for triggering creation of
+ // new connections, the portion (100%) of the cell area to be used
+ // for triggering new connections, as well as some fading options for
+ // windows and the rubberband selection.
+ mxConstants.MIN_HOTSPOT_SIZE = 16;
+ mxConstants.DEFAULT_HOTSPOT = 1;
+
+ // Enables guides
+ mxGraphHandler.prototype.guidesEnabled = true;
+
+ // Alt disables guides
+ mxGuide.prototype.isEnabledForEvent = function(evt)
+ {
+ return !mxEvent.isAltDown(evt);
+ };
+
+ // Enables snapping waypoints to terminals
+ mxEdgeHandler.prototype.snapToTerminals = true;
+
+ // Workaround for Internet Explorer ignoring certain CSS directives
+ if (mxClient.IS_QUIRKS)
+ {
+ document.body.style.overflow = 'hidden';
+ new mxDivResizer(container);
+ new mxDivResizer(outline);
+ new mxDivResizer(toolbar);
+ new mxDivResizer(sidebar);
+ new mxDivResizer(status);
+ }
+
+ // Creates a wrapper editor with a graph inside the given container.
+ // The editor is used to create certain functionality for the
+ // graph, such as the rubberband selection, but most parts
+ // of the UI are custom in this example.
+ var editor = new mxEditor();
+ var graph = editor.graph;
+ var model = graph.getModel();
+
+ // Disable highlight of cells when dragging from toolbar
+ graph.setDropEnabled(false);
+
+ // Uses the port icon while connections are previewed
+ graph.connectionHandler.getConnectImage = function(state)
+ {
+ return new mxImage(state.style[mxConstants.STYLE_IMAGE], 16, 16);
+ };
+
+ // Centers the port icon on the target port
+ graph.connectionHandler.targetConnectImage = true;
+
+ // Does not allow dangling edges
+ graph.setAllowDanglingEdges(false);
+
+ // Sets the graph container and configures the editor
+ editor.setGraphContainer(container);
+ var config = mxUtils.load(
+ 'config/keyhandler-commons.xml').
+ getDocumentElement();
+ editor.configure(config);
+
+ // Disables drag-and-drop into non-swimlanes.
+ graph.isValidDropTarget = function(cell, cells, evt)
+ {
+ return this.isSwimlane(cell);
+ };
+
+ // Disables drilling into non-swimlanes.
+ graph.isValidRoot = function(cell)
+ {
+ return this.isValidDropTarget(cell);
+ }
+
+ // Does not allow selection of locked cells
+ graph.isCellSelectable = function(cell)
+ {
+ return !this.isCellLocked(cell);
+ };
+
+ // Returns a shorter label if the cell is collapsed and no
+ // label for expanded groups
+ graph.getLabel = function(cell)
+ {
+ var tmp = mxGraph.prototype.getLabel.apply(this, arguments); // "supercall"
+
+ if (this.isCellLocked(cell))
+ {
+ // Returns an empty label but makes sure an HTML
+ // element is created for the label (for event
+ // processing wrt the parent label)
+ return '';
+ }
+ else if (this.isCellCollapsed(cell))
+ {
+ var index = tmp.indexOf('</h1>');
+
+ if (index > 0)
+ {
+ tmp = tmp.substring(0, index+5);
+ }
+ }
+
+ return tmp;
+ }
+
+
+ // Disables HTML labels for swimlanes to avoid conflict
+ // for the event processing on the child cells. HTML
+ // labels consume events before underlying cells get the
+ // chance to process those events.
+ //
+ // NOTE: Use of HTML labels is only recommended if the specific
+ // features of such labels are required, such as special label
+ // styles or interactive form fields. Otherwise non-HTML labels
+ // should be used by not overidding the following function.
+ // See also: configureStylesheet.
+ graph.isHtmlLabel = function(cell)
+ {
+ return !this.isSwimlane(cell);
+ }
+
+ // To disable the folding icon, use the following code:
+ graph.isCellFoldable = function(cell)
+ {
+ return false;
+ }
+
+ // Shows a "modal" window when double clicking a vertex.
+ graph.dblClick = function(evt, cell)
+ {
+ // Do not fire a DOUBLE_CLICK event here as mxEditor will
+ // consume the event and start the in-place editor.
+ if (this.isEnabled() &&
+ !mxEvent.isConsumed(evt) &&
+ cell != null &&
+ this.isCellEditable(cell))
+ {
+ if (this.model.isEdge(cell) ||
+ !this.isHtmlLabel(cell))
+ {
+ this.startEditingAtCell(cell);
+ }
+ else
+ {
+ var content = document.createElement('div');
+ content.innerHTML = this.convertValueToString(cell);
+ showModalWindow(this, 'Properties', content, 400, 300);
+ }
+ }
+
+ // Disables any default behaviour for the double click
+ mxEvent.consume(evt);
+ };
+
+ // Enables new connections
+ graph.setConnectable(true);
+
+ // Adds all required styles to the graph (see below)
+ configureStylesheet(graph);
+
+ // Adds sidebar icons.
+ addIcons(graph,sidebar);
+
+ // Creates a new DIV that is used as a toolbar and adds
+ // toolbar buttons.
+ var spacer = document.createElement('div');
+ spacer.style.display = 'inline';
+ spacer.style.padding = '8px';
+
+
+ // Defines a new export action
+ editor.addAction('toggle', function(editor, cell)
+ {
+ var toggle = document.getElementById("toggleBlocks");
+ var button = document.getElementById("toggle");
+ toggle.click();
+ console.log(toggle.innerHTML);
+ button.innerHTML = '';
+ if(toggle.innerHTML == 'Expand All'){
+ createButtonImage(button, 'images/navigate_plus.png')
+ }
+ else if(toggle.innerHTML == 'Collapse All'){
+ createButtonImage(button, 'images/navigate_minus.png')
+ }
+ var titleName = document.createTextNode(toggle.innerHTML);
+ button.appendChild(titleName);
+ });
+
+ addToolbarButton(editor, toolbar, 'toggle', 'Expand All', 'images/navigate_plus.png');
+ toolbar.appendChild(spacer.cloneNode(true));
+
+
+ addToolbarButton(editor, toolbar, 'delete', 'Delete', 'images/delete2.png');
+ toolbar.appendChild(spacer.cloneNode(true));
+
+ addToolbarButton(editor, toolbar, 'cut', 'Cut', 'images/cut.png');
+ addToolbarButton(editor, toolbar, 'copy', 'Copy', 'images/copy.png');
+ addToolbarButton(editor, toolbar, 'paste', 'Paste', 'images/paste.png');
+
+ toolbar.appendChild(spacer.cloneNode(true));
+
+ addToolbarButton(editor, toolbar, 'undo', '', 'images/undo.png');
+ addToolbarButton(editor, toolbar, 'redo', '', 'images/redo.png');
+
+ toolbar.appendChild(spacer.cloneNode(true));
+
+ addToolbarButton(editor, toolbar, 'show', 'Show', 'images/camera.png');
+ addToolbarButton(editor, toolbar, 'print', 'Print', 'images/printer.png');
+
+ toolbar.appendChild(spacer.cloneNode(true));
+
+ // Defines a new export action
+ editor.addAction('export', function(editor, cell)
+ {
+ var textarea = document.createElement('textarea');
+ textarea.style.width = '400px';
+ textarea.style.height = '400px';
+ var enc = new mxCodec(mxUtils.createXmlDocument());
+ var node = enc.encode(editor.graph.getModel());
+ textarea.value = mxUtils.getPrettyXml(node);
+ showModalWindow(graph, 'XML', textarea, 410, 440);
+ });
+
+ addToolbarButton(editor, toolbar, 'export', 'Export', 'images/export1.png');
+
+ // ---
+
+ // Adds toolbar buttons into the status bar at the bottom
+ // of the window.
+
+ addToolbarButton(editor, status, 'zoomIn', '', 'images/zoom_in.png', true);
+ addToolbarButton(editor, status, 'zoomOut', '', 'images/zoom_out.png', true);
+ addToolbarButton(editor, status, 'actualSize', '', 'images/view_1_1.png', true);
+ addToolbarButton(editor, status, 'fit', '', 'images/fit_to_size.png', true);
+
+ // Creates the outline (navigator, overview) for moving
+ // around the graph in the top, right corner of the window.
+ var outln = new mxOutline(graph, outline);
+
+ // To show the images in the outline, uncomment the following code
+ //outln.outline.labelsVisible = true;
+ //outln.outline.setHtmlLabels(true);
+
+ // Fades-out the splash screen after the UI has been loaded.
+ var splash = document.getElementById('splash');
+ if (splash != null)
+ {
+ try
+ {
+ mxEvent.release(splash);
+ mxEffects.fadeOut(splash, 100, true);
+ }
+ catch (e)
+ {
+
+ // mxUtils is not available (library not loaded)
+ splash.parentNode.removeChild(splash);
+ }
+ }
+ }
+ };
+
+ function createButtonImage(button, image){
+ if (image != null)
+ {
+ var img = document.createElement('img');
+ img.setAttribute('src', image);
+ img.style.width = '16px';
+ img.style.height = '16px';
+ img.style.verticalAlign = 'middle';
+ img.style.marginRight = '2px';
+ button.appendChild(img);
+ }
+ }
+
+ function addIcons(graph, sidebar){
+ var req = mxUtils.load('palettes/palettes.xml');
+ var root = req.getDocumentElement();
+ var x = root.getElementsByTagName('node')[0];
+ var categories = x.getElementsByTagName('node');
+ for (var i = 0, nodeLength = categories.length; i < nodeLength; i++) {
+ var categoryName = categories[i].getAttribute('name');
+ var title = document.createElement('h3');
+ title.setAttribute('class', 'accordion-header ui-accordion-header ui-helper-reset ui-state-default ui-accordion-icons ui-corner-all');
+ var span = document.createElement('span');
+ span.setAttribute('class', 'ui-accordion-header-icon ui-icon ui-icon-triangle-1-e');
+ var titleName = document.createTextNode(categoryName);
+ title.appendChild(span);
+ title.appendChild(titleName);
+ sidebar.appendChild(title);
+ var newImages = document.createElement('div');
+ newImages.setAttribute('class', 'ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom');
+ var blocks = categories[i].getElementsByTagName('block');
+ for (var j = 0, blockLength = blocks.length; j < blockLength; j++) {
+ var name = blocks[j].getAttribute('name');
+ var icon = blocks[j].getElementsByTagName('icon')[0];
+ var iconPath = icon.getAttribute('path');
+ addSidebarIcon(graph, newImages, name, iconPath);
+ }
+ sidebar.appendChild(newImages);
+ }
+ }
+
+ function getImgHTML(name){
+ return '<img src="'+'blocks/'+name+'.svg'+'" height="80" width="80">';
+ }
+
+ function addToolbarButton(editor, toolbar, action, label, image, isTransparent)
+ {
+ var button = document.createElement('button');
+ button.style.fontSize = '10';
+ createButtonImage(button, image);
+ if (isTransparent)
+ {
+ button.style.background = 'transparent';
+ button.style.color = '#FFFFFF';
+ button.style.border = 'none';
+ }
+ mxEvent.addListener(button, 'click', function(evt)
+ {
+ editor.execute(action);
+ });
+ mxUtils.write(button, label);
+ button.setAttribute('id',action);
+ toolbar.appendChild(button);
+ };
+
+ function showModalWindow(graph, title, content, width, height)
+ {
+ var background = document.createElement('div');
+ background.style.position = 'absolute';
+ background.style.left = '0px';
+ background.style.top = '0px';
+ background.style.right = '0px';
+ background.style.bottom = '0px';
+ background.style.background = 'black';
+ mxUtils.setOpacity(background, 50);
+ document.body.appendChild(background);
+
+ if (mxClient.IS_IE)
+ {
+ new mxDivResizer(background);
+ }
+
+ var x = Math.max(0, document.body.scrollWidth/2-width/2);
+ var y = Math.max(10, (document.body.scrollHeight || document.documentElement.scrollHeight)/2-height*2/3);
+ var wnd = new mxWindow(title, content, x, y, width, height, false, true);
+ wnd.setClosable(true);
+
+ // Fades the background out after after the window has been closed
+ wnd.addListener(mxEvent.DESTROY, function(evt)
+ {
+ graph.setEnabled(true);
+ mxEffects.fadeOut(background, 50, true, 10, 30, true);
+ });
+
+ graph.setEnabled(false);
+ graph.tooltipHandler.hide();
+ wnd.setVisible(true);
+ };
+
+ function addSidebarIcon(graph, sidebar, name, image)
+ {
+
+ // Function that is executed when the image is dropped on
+ // the graph. The cell argument points to the cell under
+ // the mousepointer if there is one.
+ var funct = function(graph, evt, cell, x, y)
+ {
+ var parent = graph.getDefaultParent();
+ var model = graph.getModel();
+ var v1 = null;
+ model.beginUpdate();
+ try
+ {
+ var label = getImgHTML(name); //will not exist for all blocks
+ if(name == 'CMSCOPE'){
+ v1 = graph.insertVertex(parent, null, label, x, y, 80, 80,'CMSCOPE');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT'], ['CONTROL'], [], []);
+ }
+ else if(name == 'CONST_m'){
+ v1 = graph.insertVertex(parent, null, '1', x, y, 80, 80,'CONST_m');
+ createPorts(graph, v1, [], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'CONVERT'){
+ v1 = graph.insertVertex(parent, null, 'Convert to', x, y, 80, 80,'CONVERT');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'CSCOPXY'){
+ v1 = graph.insertVertex(parent, null, label, x, y, 80, 80,'CSCOPXY');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT'], ['CONTROL'], [], []);
+ }
+ else if(name == 'DEMUX'){
+ v1 = graph.insertVertex(parent, null, '', x, y, 80, 80,'DEMUX');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT','EXPLICIT'], []);
+ }
+ else if(name == 'IN_f'){
+ v1 = graph.insertVertex(parent, null, '1', x, y, 80, 80,'IN_f');
+ createPorts(graph, v1, [], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'LOGICAL_OP'){
+ v1 = graph.insertVertex(parent, null, 'AND', x, y, 80, 80,'LOGICAL_OP');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'MUX'){
+ v1 = graph.insertVertex(parent, null, '', x, y, 80, 80,'MUX');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'NRMSOM_f'){
+ v1 = graph.insertVertex(parent, null, '', x, y, 80, 80,'NRMSOM_f');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'OUT_f'){
+ v1 = graph.insertVertex(parent, null, '1', x, y, 80, 80,'OUT_f');
+ createPorts(graph, v1, ['EXPLICIT'], [], [], []);
+ }
+ else if(name == 'RELATIONALOP'){
+ v1 = graph.insertVertex(parent, null, '<FONT SIZE="6"><</FONT>', x, y, 80, 80,'RELATIONALOP');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'SWITCH2_m'){
+ v1 = graph.insertVertex(parent, null, label, x, y, 80, 80,'SWITCH2_m');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT','EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+
+ // CONTINUOUS
+
+ else if(name == 'CLINDUMMY_f'){
+ v1 = graph.insertVertex(parent, null, 'DUMMY<BR>CLSS', x, y, 80, 80,'CLINDUMMY_f');
+ createPorts(graph, v1, [], [], [], []);
+ }
+ else if(name == 'CLR'){
+ v1 = graph.insertVertex(parent, null, '1<BR><HR>1 + s', x, y, 80, 80,'CLR');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'CLSS'){
+ v1 = graph.insertVertex(parent, null, '<TABLE><TR><TD ALIGN="RIGHT">xd</TD><TD>=</TD><TD>Ax+Bu</TD></TR><TR><TD ALIGN="RIGHT">y</TD><TD>=</TD><TD>Cx+Du</TD></TR></TABLE>', x, y, 130, 80,'CLSS');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'DERIV'){
+ v1 = graph.insertVertex(parent, null, 'du / dt', x, y, 80, 80,'DERIV');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'INTEGRAL_f'){
+ v1 = graph.insertVertex(parent, null, '1/s', x, y, 80, 80,'INTEGRAL_f');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'INTEGRAL_m'){
+ v1 = graph.insertVertex(parent, null, label, x, y, 80, 80,'INTEGRAL_m');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'PID'){
+ v1 = graph.insertVertex(parent, null, 'PID', x, y, 80, 80,'PID');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'TCLSS'){
+ v1 = graph.insertVertex(parent, null, 'Jump<BR>(A,B,C,D)', x, y, 80, 80,'TCLSS');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT'], ['CONTROL'], ['EXPLICIT'], []);
+ }
+ else if(name == 'TIME_DELAY'){
+ v1 = graph.insertVertex(parent, null, 'Continuous<BR>fix delay', x, y, 80, 80,'TIME_DELAY');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'VARIABLE_DELAY'){
+ v1 = graph.insertVertex(parent, null, 'Variable<BR>delay', x, y, 80, 80,'VARIABLE_DELAY');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'PDE'){
+ v1 = graph.insertVertex(parent, null, 'PDE', x, y, 80, 80,'PDE');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT','EXPLICIT','EXPLICIT','EXPLICIT'], [], ['EXPLICIT','EXPLICIT'], []);
+ }
+
+ // DISCONTINUOUS
+
+ else if(name == 'BACKLASH'){
+ v1 = graph.insertVertex(parent, null, 'Backlash', x, y, 80, 80,'BACKLASH');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'DEADBAND'){
+ v1 = graph.insertVertex(parent, null, label, x, y, 80, 80,'DEADBAND');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'DELAYV_f'){
+ v1 = graph.insertVertex(parent, null, 'Variable<BR>delay', x, y, 80, 80,'DELAYV_f');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT'], ['CONTROL'], ['EXPLICIT'], ['COMMAND','COMMAND']);
+ }
+ else if(name == 'HYSTHERESIS'){
+ v1 = graph.insertVertex(parent, null, label, x, y, 80, 80,'HYSTHERESIS');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'RATELIMITER'){
+ v1 = graph.insertVertex(parent, null, 'Rate limiter', x, y, 80, 80,'RATELIMITER');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'QUANT_f'){
+ v1 = graph.insertVertex(parent, null, label, x, y, 80, 80,'QUANT_f');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'SATURATION'){
+ v1 = graph.insertVertex(parent, null, label, x, y, 80, 80,'SATURATION');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+
+ // DISCRETE
+ else if(name == 'AUTOMAT'){
+ v1 = graph.insertVertex(parent, null, 'Automaton<BR>nM=2, nX=1', x, y, 80, 80,'AUTOMAT');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT'], [], ['EXPLICIT','EXPLICIT'], ['COMMAND']);
+ }
+ else if(name == 'DELAY_f'){
+ v1 = graph.insertVertex(parent, null, 'Delay', x, y, 80, 80,'DELAYV_f');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'DLR'){
+ v1 = graph.insertVertex(parent, null, '1<BR><HR>1 + z', x, y, 80, 80,'DLR');
+ createPorts(graph, v1, ['EXPLICIT'], ['CONTROL'], ['EXPLICIT'], []);
+ }
+ else if(name == 'DLRADAPT_f'){
+ v1 = graph.insertVertex(parent, null, 'N(z,p)<BR><HR>D(z,p)', x, y, 80, 80,'DLRADAPT_f');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT'], ['CONTROL'], ['EXPLICIT'], []);
+ }
+ else if(name == 'DLSS'){
+ v1 = graph.insertVertex(parent, null, '<TABLE><TR><TD ALIGN="RIGHT">x</TD><TD ALIGN="CENTER">+=</TD><TD>Ax+Bu</TD></TR><TR><TD ALIGN="RIGHT">y</TD><TD ALIGN="CENTER">=</TD><TD>Cx+Du</TD></TR></TABLE>', x, y, 80, 80,'DLSS');
+ createPorts(graph, v1, ['EXPLICIT'], ['CONTROL'], ['EXPLICIT'], []);
+ }
+ else if(name == 'DOLLAR_f'){
+ v1 = graph.insertVertex(parent, null, '1/z', x, y, 80, 80,'DOLLAR_f');
+ createPorts(graph, v1, ['EXPLICIT'], ['CONTROL'], ['EXPLICIT'], []);
+ }
+ else if(name == 'DOLLAR'){
+ v1 = graph.insertVertex(parent, null, '1/z', x, y, 80, 80,'DOLLAR');
+ createPorts(graph, v1, ['EXPLICIT'], ['CONTROL'], ['EXPLICIT'], []);
+ }
+ else if(name == 'DOLLAR_m'){
+ v1 = graph.insertVertex(parent, null, '1/z', x, y, 80, 80,'DOLLAR_m');
+ createPorts(graph, v1, ['EXPLICIT'], ['CONTROL'], ['EXPLICIT'], []);
+ }
+ else if(name == 'SAMPHOLD_m'){
+ v1 = graph.insertVertex(parent, null, 'S / H', x, y, 80, 80,'SAMPHOLD_m');
+ createPorts(graph, v1, ['EXPLICIT'], ['CONTROL'], ['EXPLICIT'], []);
+ }
+ else if(name == 'REGISTER'){
+ v1 = graph.insertVertex(parent, null, 'Shift<BR>register', x, y, 80, 80,'REGISTER');
+ createPorts(graph, v1, ['EXPLICIT'], ['CONTROL'], ['EXPLICIT'], []);
+ }
+
+ // EVENTS
+ else if(name == 'CLOCK_c'){
+ v1 = graph.insertVertex(parent, null, label, x, y, 80, 80,'CLOCK_c');
+ createPorts(graph, v1, [], [], [], ['COMMAND']);
+ }
+ else if(name == 'SampleCLK'){
+ v1 = graph.insertVertex(parent, null, label, x, y, 80, 80,'SampleCLK');
+ createPorts(graph, v1, [], [], [], ['COMMAND']);
+ }
+ else if(name == 'VirtualCLK0'){
+ v1 = graph.insertVertex(parent, null, label, x, y, 80, 80,'VirtualCLK0');
+ createPorts(graph, v1, [], ['CONTROL'], [], []);
+ }
+ else if(name == 'ANDBLK'){
+ v1 = graph.insertVertex(parent, null, label, x, y, 80, 80,'ANDBLK');
+ createPorts(graph, v1, [], ['CONTROL','CONTROL'], [], ['COMMAND']);
+ }
+ else if(name == 'ANDLOG_f'){
+ v1 = graph.insertVertex(parent, null, 'LOGICAL<BR>AND', x, y, 80, 80,'ANDLOG_f');
+ createPorts(graph, v1, [], ['CONTROL','CONTROL'], ['EXPLICIT'], []);
+ }
+ else if(name == 'CEVENTSCOPE'){
+ v1 = graph.insertVertex(parent, null, label, x, y, 80, 80,'CEVENTSCOPE');
+ createPorts(graph, v1, [], ['CONTROL'], [], []);
+ }
+ else if(name == 'CLKFROM'){
+ v1 = graph.insertVertex(parent, null, 'A', x, y, 80, 80,'CLKFROM');
+ createPorts(graph, v1, [], [], [], ['COMMAND']);
+ }
+ else if(name == 'CLKGOTO'){
+ v1 = graph.insertVertex(parent, null, 'A', x, y, 80, 80,'CLKGOTO');
+ createPorts(graph, v1, [], ['CONTROL'], [], []);
+ }
+ else if(name == 'CLKGotoTagVisibility'){
+ v1 = graph.insertVertex(parent, null, '{A}', x, y, 80, 80,'CLKGotoTagVisibility');
+ createPorts(graph, v1, [], [], [], []);
+ }
+ else if(name == 'CLKOUTV_f'){
+ v1 = graph.insertVertex(parent, null, '1', x, y, 80, 80,'CLKOUTV_f');
+ createPorts(graph, v1, [], ['CONTROL'], [], []);
+ }
+ else if(name == 'CLKSOMV_f'){
+ v1 = graph.insertVertex(parent, null, '+', x, y, 80, 80,'CLKSOMV_f');
+ createPorts(graph, v1, [], ['CONTROL','CONTROL','CONTROL'], [], ['COMMAND']);
+ }
+ else if(name == 'EDGE_TRIGGER'){
+ v1 = graph.insertVertex(parent, null, 'Edge<BR>trigger', x, y, 80, 80,'EDGE_TRIGGER');
+ createPorts(graph, v1, ['EXPLICIT'], [], [], ['COMMAND']);
+ }
+ else if(name == 'ENDBLK'){
+ v1 = graph.insertVertex(parent, null, 'END', x, y, 80, 80,'ENDBLK');
+ createPorts(graph, v1, [], [], [], []);
+ }
+ else if(name == 'END_c'){
+ v1 = graph.insertVertex(parent, null, 'END', x, y, 80, 80,'END_c');
+ createPorts(graph, v1, [], ['CONTROL'], [], ['COMMAND']);
+ }
+ else if(name == 'ESELECT_f'){
+ v1 = graph.insertVertex(parent, null, 'Event select', x, y, 80, 80,'ESELECT_f');
+ createPorts(graph, v1, ['EXPLICIT'], ['CONTROL'], [], ['COMMAND','COMMAND']);
+ }
+ else if(name == 'EVTDLY_c'){
+ v1 = graph.insertVertex(parent, null, 'Delay: 0.1', x, y, 80, 80,'EVTDLY_c');
+ createPorts(graph, v1, [], ['CONTROL'], [], ['COMMAND']);
+ }
+ else if(name == 'EVTGEN_f'){
+ v1 = graph.insertVertex(parent, null, 'Event at<BR>time 0', x, y, 80, 80,'EVTGEN_f');
+ createPorts(graph, v1, [], [], [], ['COMMAND']);
+ }
+ else if(name == 'EVTVARDLY'){
+ v1 = graph.insertVertex(parent, null, 'Event<BR>delay', x, y, 80, 80,'EVTVARDLY');
+ createPorts(graph, v1, ['EXPLICIT'], ['CONTROL'], [], ['COMMAND']);
+ }
+ else if(name == 'Extract_Activation'){
+ v1 = graph.insertVertex(parent, null, 'Extract<BR>activation', x, y, 80, 80,'Extract_Activation');
+ createPorts(graph, v1, ['EXPLICIT'], [], [], ['COMMAND']);
+ }
+ else if(name == 'HALT_f'){
+ v1 = graph.insertVertex(parent, null, 'HALT', x, y, 80, 80,'HALT_f');
+ createPorts(graph, v1, [], ['CONTROL'], [], []);
+ }
+ else if(name == 'IFTHEL_f'){
+ v1 = graph.insertVertex(parent, null, 'if in>0<BR>then else', x, y, 80, 80,'IFTHEL_f');
+ createPorts(graph, v1, ['EXPLICIT'], ['CONTROL'], [], ['COMMAND','COMMAND']);
+ }
+ else if(name == 'M_freq'){
+ v1 = graph.insertVertex(parent, null, 'Multiple<BR>frequency', x, y, 80, 80,'M_freq');
+ createPorts(graph, v1, [], ['CONTROL'], [], ['COMMAND','COMMAND','COMMAND']);
+ }
+ else if(name == 'MCLOCK_f'){
+ v1 = graph.insertVertex(parent, null, '2freq clock<BR>f/n f', x, y, 80, 80,'MCLOCK_f');
+ createPorts(graph, v1, [], [], [], ['COMMAND','COMMAND']);
+ }
+ else if(name == 'MFCLCK_f'){
+ v1 = graph.insertVertex(parent, null, 'M. freq<BR>clock', x, y, 80, 80,'MFCLCK_f');
+ createPorts(graph, v1, [], ['CONTROL'], [], ['COMMAND','COMMAND']);
+ }
+ else if(name == 'freq_div'){
+ v1 = graph.insertVertex(parent, null, 'Frequency<BR>division', x, y, 80, 80,'freq_div');
+ createPorts(graph, v1, [], ['CONTROL'], [], ['COMMAND']);
+ }
+ // LOOKUP TABLES
+ else if(name == 'INTRP2BLK_f'){
+ v1 = graph.insertVertex(parent, null, 'Interp 2', x, y, 80, 80,'INTRP2BLK_f');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'INTRPLBLK_f'){
+ v1 = graph.insertVertex(parent, null, 'Interp', x, y, 80, 80,'INTRPLBLK_f');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'LOOKUP_f'){
+ v1 = graph.insertVertex(parent, null, label, x, y, 80, 80,'LOOKUP_f');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ // MATHEMATICAL OPERATIONS
+ else if(name == 'ABS_VALUE'){
+ v1 = graph.insertVertex(parent, null, 'ABS', x, y, 80, 80,'ABS_VALUE');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'BIGSOM_f'){
+ v1 = graph.insertVertex(parent, null, label, x, y, 80, 80,'BIGSOM_f');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'COSBLK_f'){
+ v1 = graph.insertVertex(parent, null, 'COS', x, y, 80, 80,'COSBLK_f');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'EXPBLK_m'){
+ v1 = graph.insertVertex(parent, null, 'a^u', x, y, 80, 80,'EXPBLK_m');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'GAINBLK_f'){
+ v1 = graph.insertVertex(parent, null, '1', x, y, 80, 80,'GAINBLK_f');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'GAINBLK'){
+ v1 = graph.insertVertex(parent, null, '1', x, y, 80, 80,'GAINBLK');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'GAIN_f'){
+ v1 = graph.insertVertex(parent, null, '1', x, y, 80, 80,'GAIN_f');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'INVBLK'){
+ v1 = graph.insertVertex(parent, null, '1/u', x, y, 80, 80,'INVBLK');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'LOGBLK_f'){
+ v1 = graph.insertVertex(parent, null, 'LOG', x, y, 80, 80,'LOGBLK_f');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'MATMAGPHI'){
+ v1 = graph.insertVertex(parent, null, 'Mag & Phi', x, y, 80, 80,'MATMAGPHI');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT','EXPLICIT'], []);
+ }
+ else if(name == 'MATZREIM'){
+ v1 = graph.insertVertex(parent, null, 'Re & Im', x, y, 80, 80,'MATZREIM');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT','EXPLICIT'], []);
+ }
+ else if(name == 'MAXMIN'){
+ v1 = graph.insertVertex(parent, null, 'MAX', x, y, 80, 80,'MAXMIN');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'MAX_f'){
+ v1 = graph.insertVertex(parent, null, 'MAX', x, y, 80, 80,'MAX_f');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'MIN_f'){
+ v1 = graph.insertVertex(parent, null, 'MIN', x, y, 80, 80,'MIN_f');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'POWBLK_f'){
+ v1 = graph.insertVertex(parent, null, 'u^a', x, y, 80, 80,'POWBLK_f');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'PRODUCT'){
+ v1 = graph.insertVertex(parent, null, '<TABLE><TR><TD>*</TD><TD ROWSPAN="2"><FONT SIZE="6">âˆ</FONT><TD></TR><TR><TD>÷</TD><TD/></TR></TABLE>', x, y, 80, 80,'PRODUCT');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'PROD_f'){
+ v1 = graph.insertVertex(parent, null, 'X', x, y, 80, 80,'PROD_f');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'SIGNUM'){
+ v1 = graph.insertVertex(parent, null, 'SIGN', x, y, 80, 80,'SIGNUM');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'SINBLK_f'){
+ v1 = graph.insertVertex(parent, null, 'SIN', x, y, 80, 80,'SINBLK_f');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'SQRT'){
+ v1 = graph.insertVertex(parent, null, 'SQRT', x, y, 80, 80,'SQRT');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'SUMMATION'){
+ v1 = graph.insertVertex(parent, null, '<TABLE><TR><TD>+</TD> <TD ROWSPAN="2"><FONT SIZE="6">∑</FONT><TD></TR><TR><TD>-</TD> <TD/></TR></TABLE>', x, y, 80, 80,'SUMMATION');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'SUM_f'){
+ v1 = graph.insertVertex(parent, null, '+', x, y, 80, 80,'SUM_f');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT','EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'SOM_f'){
+ v1 = graph.insertVertex(parent, null, '+', x, y, 80, 80,'SOM_f');
+ createPorts(graph, v1, ['EXPLICIT','EXPLICIT','EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'TANBLK_f'){
+ v1 = graph.insertVertex(parent, null, 'TAN', x, y, 80, 80,'TANBLK_f');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ else if(name == 'TrigFun'){
+ v1 = graph.insertVertex(parent, null, 'Trig function', x, y, 80, 80,'TrigFun');
+ createPorts(graph, v1, ['EXPLICIT'], [], ['EXPLICIT'], []);
+ }
+ // MATRIX
+
+
+ else if(name == 'OpAmp'){
+ v1 = graph.insertVertex(parent, null, '<table><tr><td>+</td><td></td></tr><tr><td></td><td>OP</td></tr><tr><td>-</td><td></td></tr></table>', x, y, 80, 80,'OpAmp');
+ createPorts(graph, v1, ['IMPLICIT','IMPLICIT'], [], ['IMPLICIT'], []);
+ }
+ v1.setConnectable(false);
+
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+
+ graph.setSelectionCell(v1);
+ }
+
+ var para = document.createElement('p');
+ var blockFigure = document.createElement('figure');
+ var img = document.createElement('img');
+ img.setAttribute('src', image);
+ var caption = document.createElement('figcaption');
+ var blockName = document.createTextNode(name);
+ caption.appendChild(blockName);
+ blockFigure.appendChild(img);
+ blockFigure.appendChild(caption);
+ para.appendChild(blockFigure);
+ sidebar.appendChild(para);
+
+ var dragElt = document.createElement('div');
+ dragElt.style.border = 'dashed black 1px';
+ dragElt.style.width = '80px';
+ dragElt.style.height = '80px';
+
+ // Creates the image which is used as the drag icon (preview)
+ var ds = mxUtils.makeDraggable(img, graph, funct, dragElt, 0, 0, true, true);
+ ds.setGuidesEnabled(true);
+ };
+
+ function createPorts(graph, block, left, top, right, bottom){
+ createInputPorts(graph, block, left, top);
+ createOutputPorts(graph, block, right, bottom);
+ }
+
+ function createInputPorts(graph, block, leftArray, topArray){
+ var topNumber = topArray.length;
+ var leftNumber = leftArray.length;
+ if(leftNumber != 0){
+ for(var i=1; i<=leftNumber; i++){
+ var x = 0;
+ var y = (i/(leftNumber+1)).toFixed(4);
+ var portType = leftArray[i-1];
+ createInputPort(graph, block, x, y, portType, 'left');
+ }
+ }
+ if(topNumber != 0){
+ for(var i=1; i<=topNumber; i++){
+ var x = (i/(topNumber+1)).toFixed(4);
+ var y = 0;
+ var portType = topArray[i-1];
+ createInputPort(graph, block, x, y, portType, 'top');
+ }
+ }
+ };
+
+ function createOutputPorts(graph, block, rightArray, bottomArray){
+ var bottomNumber = bottomArray.length;
+ var rightNumber = rightArray.length;
+ if(rightNumber != 0){
+ for(var i=1; i<=rightNumber; i++){
+ var x = 1;
+ var y = (i/(rightNumber+1)).toFixed(4);
+ var portType = rightArray[i-1];
+ createOutputPort(graph, block, x, y, portType, 'right');
+ }
+ }
+ if(bottomNumber != 0){
+ for(var i=1; i<=bottomNumber; i++){
+ var x = (i/(bottomNumber+1)).toFixed(4);
+ var y = 1;
+ var portType = bottomArray[i-1];
+ createOutputPort(graph, block, x, y, portType, 'bottom');
+ }
+ }
+ };
+
+ function createInputPort(graph, block, x, y, portType, position){
+ var port = null;
+ if(portType == 'COMMAND'){
+ port = graph.insertVertex(block, null, 'INPUT_COMMAND', x, y, 10, 10,'CommandPort', true);
+ }
+ else if(portType == 'CONTROL'){
+ port = graph.insertVertex(block, null, 'INPUT_CONTROL', x, y, 10, 10, 'ControlPort', true);
+ }
+ else if(portType == 'IMPLICIT'){
+ port = graph.insertVertex(block, null, 'INPUT_IMPLICIT', x, y, 10, 10, 'ImplicitInputPort', true);
+ }
+ else if(portType == 'EXPLICIT'){
+ port = graph.insertVertex(block, null, 'INPUT_EXPLICIT', x, y, 10, 10,'ExplicitInputPort', true);
+ }
+ if(port != null){
+ if(position == 'top'){
+ port.geometry.offset = new mxPoint(-6, -10);
+ }
+ else if(position == 'left'){
+ port.geometry.offset = new mxPoint(-10, -6);
+ }
+ }
+ };
+
+ function createOutputPort(graph, block, x, y, portType, position){
+ var port = null;
+ if(portType == 'COMMAND'){
+ port = graph.insertVertex(block, null, 'OUTPUT_COMMAND', x, y, 10, 10, 'CommandPort', true);
+ }
+ else if(portType == 'CONTROL'){
+ port = graph.insertVertex(block, null, 'OUTPUT_CONTROL', x, y, 10, 10, 'ControlPort', true);
+ }
+ else if(portType == 'IMPLICIT'){
+ port = graph.insertVertex(block, null, 'OUTPUT_IMPLICIT', x, y, 10, 10,'ImplicitOutputPort',true);
+ }
+ else if(portType == 'EXPLICIT'){
+ port = graph.insertVertex(block, null, 'OUTPUT_EXPLICIT', x, y, 10, 10, 'ExplicitOutputPort', true);
+ }
+ if(port != null){
+ if(position == 'bottom'){
+ port.geometry.offset = new mxPoint(-6, 0);
+ }
+ if(position == 'right'){
+ port.geometry.offset = new mxPoint(0, -6);
+ }
+ }
+ };
+
+ function configureStylesheet(graph)
+ {
+ var req = mxUtils.load('styles/Xcos-style.xml');
+ var root = req.getDocumentElement();
+ var dec = new mxCodec(root.ownerDocument);
+ dec.decode(root, graph.stylesheet);
+ };
+ </script>
+</head>
+
+<!-- Page passes the container for the graph to the program -->
+<body onload="main(document.getElementById('graphContainer'),
+ document.getElementById('outlineContainer'),
+ document.getElementById('toolbarContainer'),
+ document.getElementById('sidebarContainer'),
+ document.getElementById('statusContainer'));" style="margin:0px;">
+
+ <!-- Creates a container for the splash screen -->
+ <div id="splash"
+ style="position:absolute;top:0px;left:0px;width:100%;height:100%;background:white;z-index:1;">
+ <center id="splash" style="padding-top:230px;">
+ <img src="images/loading.gif">
+ </center>
+ </div>
+
+ <!-- Creates a container for the sidebar -->
+ <div id="toolbarContainer"
+ style="position:absolute;white-space:nowrap;overflow:hidden;top:0px;left:0px;max-height:24px;height:36px;right:0px;padding:6px;background-image:url('images/toolbar_bg.gif');">
+ </div>
+
+ <!-- Creates a container for the toolboox -->
+ <div id="sidebarContainer" class="ui-accordion ui-widget ui-helper-reset"
+ style="position:absolute;overflow:scroll;top:36px;left:0px;bottom:36px;max-width:266px;width:270px;padding-top:10px;padding-left:4px;">
+ </div>
+
+ <!-- Creates a container for the graph -->
+ <div id="graphContainer"
+ style="position:absolute;overflow:hidden;top:36px;left:270px;bottom:36px;right:0px;background-image:url('images/grid.gif');cursor:default;">
+ </div>
+
+ <!-- Creates a container for the outline -->
+ <div id="outlineContainer"
+ style="position:absolute;overflow:hidden;top:36px;right:0px;width:200px;height:140px;background:transparent;border-style:solid;border-color:black;">
+ </div>
+
+ <!-- Creates a container for the sidebar -->
+ <div id="statusContainer"
+ style="text-align:right;position:absolute;overflow:hidden;bottom:0px;left:0px;max-height:24px;height:36px;right:0px;color:white;padding:6px;background-image:url('images/toolbar_bg.gif');">
+ <div style="font-size:10pt;float:left;">
+ <a href="http://fossee.in/" target="_tab">FOSSEE</a>
+ </div>
+ </div>
+
+ <!-- Secret -->
+ <p class="accordion-expand-holder" style="display:none">
+ <a id='toggleBlocks' class="accordion-expand-all">Expand All</a>
+ </p>
+
+</body>
+ <!-- It's good if this part happens after the entire page has loaded-->
+ <script type="text/javascript">
+ //Find out more here: http://stackoverflow.com/questions/12843418/jquery-ui-accordion-expand-collapse-all
+ $(window).load(function(){
+ var headers = $('#sidebarContainer .accordion-header');
+ var contentAreas = $('#sidebarContainer .ui-accordion-content ').hide();
+ var expandLink = $('.accordion-expand-all');
+
+ // add the accordion functionality
+ headers.click(function() {
+ var panel = $(this).next();
+ var isOpen = panel.is(':visible');
+
+ // open or close as necessary
+ panel[isOpen? 'slideUp': 'slideDown']()
+ // trigger the correct custom event
+ .trigger(isOpen? 'hide': 'show');
+
+ // stop the link from causing a pagescroll
+ return false;
+ });
+
+ // hook up the expand/collapse all
+ expandLink.click(function(){
+ var isAllOpen = $(this).data('isAllOpen');
+
+ contentAreas[isAllOpen? 'hide': 'show']()
+ .trigger(isAllOpen? 'hide': 'show');
+ });
+
+ // when panels open or close, check to see if they're all open
+ contentAreas.on({
+ // whenever we open a panel, check to see if they're all open
+ // if all open, swap the button to collapser
+ show: function(){
+ var isAllOpen = !contentAreas.is(':hidden');
+ if(isAllOpen){
+ expandLink.text('Collapse All')
+ .data('isAllOpen', true);
+ }
+ },
+ // whenever we close a panel, check to see if they're all open
+ // if not all open, swap the button to expander
+ hide: function(){
+ var isAllOpen = !contentAreas.is(':hidden');
+ if(!isAllOpen){
+ expandLink.text('Expand All')
+ .data('isAllOpen', false);
+ }
+ }
+ });
+ });
+ </script>
+</html> \ No newline at end of file
diff --git a/jquery/images/ui-bg_flat_75_ffffff_40x100.png b/jquery/images/ui-bg_flat_75_ffffff_40x100.png
new file mode 100644
index 0000000..ac8b229
--- /dev/null
+++ b/jquery/images/ui-bg_flat_75_ffffff_40x100.png
Binary files differ
diff --git a/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png b/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png
new file mode 100644
index 0000000..86c2baa
--- /dev/null
+++ b/jquery/images/ui-bg_glass_75_e6e6e6_1x400.png
Binary files differ
diff --git a/jquery/images/ui-icons_888888_256x240.png b/jquery/images/ui-icons_888888_256x240.png
new file mode 100644
index 0000000..5ba708c
--- /dev/null
+++ b/jquery/images/ui-icons_888888_256x240.png
Binary files differ
diff --git a/jquery/jquery-1.8.2.js b/jquery/jquery-1.8.2.js
new file mode 100644
index 0000000..12c7797
--- /dev/null
+++ b/jquery/jquery-1.8.2.js
@@ -0,0 +1,9440 @@
+/*!
+ * jQuery JavaScript Library v1.8.2
+ * http://jquery.com/
+ *
+ * Includes Sizzle.js
+ * http://sizzlejs.com/
+ *
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://jquery.org/license
+ *
+ * Date: Thu Sep 20 2012 21:13:05 GMT-0400 (Eastern Daylight Time)
+ */
+(function( window, undefined ) {
+var
+ // A central reference to the root jQuery(document)
+ rootjQuery,
+
+ // The deferred used on DOM ready
+ readyList,
+
+ // Use the correct document accordingly with window argument (sandbox)
+ document = window.document,
+ location = window.location,
+ navigator = window.navigator,
+
+ // Map over jQuery in case of overwrite
+ _jQuery = window.jQuery,
+
+ // Map over the $ in case of overwrite
+ _$ = window.$,
+
+ // Save a reference to some core methods
+ core_push = Array.prototype.push,
+ core_slice = Array.prototype.slice,
+ core_indexOf = Array.prototype.indexOf,
+ core_toString = Object.prototype.toString,
+ core_hasOwn = Object.prototype.hasOwnProperty,
+ core_trim = String.prototype.trim,
+
+ // Define a local copy of jQuery
+ jQuery = function( selector, context ) {
+ // The jQuery object is actually just the init constructor 'enhanced'
+ return new jQuery.fn.init( selector, context, rootjQuery );
+ },
+
+ // Used for matching numbers
+ core_pnum = /[\-+]?(?:\d*\.|)\d+(?:[eE][\-+]?\d+|)/.source,
+
+ // Used for detecting and trimming whitespace
+ core_rnotwhite = /\S/,
+ core_rspace = /\s+/,
+
+ // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)
+ rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
+
+ // A simple way to check for HTML strings
+ // Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
+ rquickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/,
+
+ // Match a standalone tag
+ rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
+
+ // JSON RegExp
+ rvalidchars = /^[\],:{}\s]*$/,
+ rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
+ rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
+ rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d\d*\.|)\d+(?:[eE][\-+]?\d+|)/g,
+
+ // Matches dashed string for camelizing
+ rmsPrefix = /^-ms-/,
+ rdashAlpha = /-([\da-z])/gi,
+
+ // Used by jQuery.camelCase as callback to replace()
+ fcamelCase = function( all, letter ) {
+ return ( letter + "" ).toUpperCase();
+ },
+
+ // The ready event handler and self cleanup method
+ DOMContentLoaded = function() {
+ if ( document.addEventListener ) {
+ document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+ jQuery.ready();
+ } else if ( document.readyState === "complete" ) {
+ // we're here because readyState === "complete" in oldIE
+ // which is good enough for us to call the dom ready!
+ document.detachEvent( "onreadystatechange", DOMContentLoaded );
+ jQuery.ready();
+ }
+ },
+
+ // [[Class]] -> type pairs
+ class2type = {};
+
+jQuery.fn = jQuery.prototype = {
+ constructor: jQuery,
+ init: function( selector, context, rootjQuery ) {
+ var match, elem, ret, doc;
+
+ // Handle $(""), $(null), $(undefined), $(false)
+ if ( !selector ) {
+ return this;
+ }
+
+ // Handle $(DOMElement)
+ if ( selector.nodeType ) {
+ this.context = this[0] = selector;
+ this.length = 1;
+ return this;
+ }
+
+ // Handle HTML strings
+ if ( typeof selector === "string" ) {
+ if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
+ // Assume that strings that start and end with <> are HTML and skip the regex check
+ match = [ null, selector, null ];
+
+ } else {
+ match = rquickExpr.exec( selector );
+ }
+
+ // Match html or make sure no context is specified for #id
+ if ( match && (match[1] || !context) ) {
+
+ // HANDLE: $(html) -> $(array)
+ if ( match[1] ) {
+ context = context instanceof jQuery ? context[0] : context;
+ doc = ( context && context.nodeType ? context.ownerDocument || context : document );
+
+ // scripts is true for back-compat
+ selector = jQuery.parseHTML( match[1], doc, true );
+ if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
+ this.attr.call( selector, context, true );
+ }
+
+ return jQuery.merge( this, selector );
+
+ // HANDLE: $(#id)
+ } else {
+ elem = document.getElementById( match[2] );
+
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE and Opera return items
+ // by name instead of ID
+ if ( elem.id !== match[2] ) {
+ return rootjQuery.find( selector );
+ }
+
+ // Otherwise, we inject the element directly into the jQuery object
+ this.length = 1;
+ this[0] = elem;
+ }
+
+ this.context = document;
+ this.selector = selector;
+ return this;
+ }
+
+ // HANDLE: $(expr, $(...))
+ } else if ( !context || context.jquery ) {
+ return ( context || rootjQuery ).find( selector );
+
+ // HANDLE: $(expr, context)
+ // (which is just equivalent to: $(context).find(expr)
+ } else {
+ return this.constructor( context ).find( selector );
+ }
+
+ // HANDLE: $(function)
+ // Shortcut for document ready
+ } else if ( jQuery.isFunction( selector ) ) {
+ return rootjQuery.ready( selector );
+ }
+
+ if ( selector.selector !== undefined ) {
+ this.selector = selector.selector;
+ this.context = selector.context;
+ }
+
+ return jQuery.makeArray( selector, this );
+ },
+
+ // Start with an empty selector
+ selector: "",
+
+ // The current version of jQuery being used
+ jquery: "1.8.2",
+
+ // The default length of a jQuery object is 0
+ length: 0,
+
+ // The number of elements contained in the matched element set
+ size: function() {
+ return this.length;
+ },
+
+ toArray: function() {
+ return core_slice.call( this );
+ },
+
+ // Get the Nth element in the matched element set OR
+ // Get the whole matched element set as a clean array
+ get: function( num ) {
+ return num == null ?
+
+ // Return a 'clean' array
+ this.toArray() :
+
+ // Return just the object
+ ( num < 0 ? this[ this.length + num ] : this[ num ] );
+ },
+
+ // Take an array of elements and push it onto the stack
+ // (returning the new matched element set)
+ pushStack: function( elems, name, selector ) {
+
+ // Build a new jQuery matched element set
+ var ret = jQuery.merge( this.constructor(), elems );
+
+ // Add the old object onto the stack (as a reference)
+ ret.prevObject = this;
+
+ ret.context = this.context;
+
+ if ( name === "find" ) {
+ ret.selector = this.selector + ( this.selector ? " " : "" ) + selector;
+ } else if ( name ) {
+ ret.selector = this.selector + "." + name + "(" + selector + ")";
+ }
+
+ // Return the newly-formed element set
+ return ret;
+ },
+
+ // Execute a callback for every element in the matched set.
+ // (You can seed the arguments with an array of args, but this is
+ // only used internally.)
+ each: function( callback, args ) {
+ return jQuery.each( this, callback, args );
+ },
+
+ ready: function( fn ) {
+ // Add the callback
+ jQuery.ready.promise().done( fn );
+
+ return this;
+ },
+
+ eq: function( i ) {
+ i = +i;
+ return i === -1 ?
+ this.slice( i ) :
+ this.slice( i, i + 1 );
+ },
+
+ first: function() {
+ return this.eq( 0 );
+ },
+
+ last: function() {
+ return this.eq( -1 );
+ },
+
+ slice: function() {
+ return this.pushStack( core_slice.apply( this, arguments ),
+ "slice", core_slice.call(arguments).join(",") );
+ },
+
+ map: function( callback ) {
+ return this.pushStack( jQuery.map(this, function( elem, i ) {
+ return callback.call( elem, i, elem );
+ }));
+ },
+
+ end: function() {
+ return this.prevObject || this.constructor(null);
+ },
+
+ // For internal use only.
+ // Behaves like an Array's method, not like a jQuery method.
+ push: core_push,
+ sort: [].sort,
+ splice: [].splice
+};
+
+// Give the init function the jQuery prototype for later instantiation
+jQuery.fn.init.prototype = jQuery.fn;
+
+jQuery.extend = jQuery.fn.extend = function() {
+ var options, name, src, copy, copyIsArray, clone,
+ target = arguments[0] || {},
+ i = 1,
+ length = arguments.length,
+ deep = false;
+
+ // Handle a deep copy situation
+ if ( typeof target === "boolean" ) {
+ deep = target;
+ target = arguments[1] || {};
+ // skip the boolean and the target
+ i = 2;
+ }
+
+ // Handle case when target is a string or something (possible in deep copy)
+ if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
+ target = {};
+ }
+
+ // extend jQuery itself if only one argument is passed
+ if ( length === i ) {
+ target = this;
+ --i;
+ }
+
+ for ( ; i < length; i++ ) {
+ // Only deal with non-null/undefined values
+ if ( (options = arguments[ i ]) != null ) {
+ // Extend the base object
+ for ( name in options ) {
+ src = target[ name ];
+ copy = options[ name ];
+
+ // Prevent never-ending loop
+ if ( target === copy ) {
+ continue;
+ }
+
+ // Recurse if we're merging plain objects or arrays
+ if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
+ if ( copyIsArray ) {
+ copyIsArray = false;
+ clone = src && jQuery.isArray(src) ? src : [];
+
+ } else {
+ clone = src && jQuery.isPlainObject(src) ? src : {};
+ }
+
+ // Never move original objects, clone them
+ target[ name ] = jQuery.extend( deep, clone, copy );
+
+ // Don't bring in undefined values
+ } else if ( copy !== undefined ) {
+ target[ name ] = copy;
+ }
+ }
+ }
+ }
+
+ // Return the modified object
+ return target;
+};
+
+jQuery.extend({
+ noConflict: function( deep ) {
+ if ( window.$ === jQuery ) {
+ window.$ = _$;
+ }
+
+ if ( deep && window.jQuery === jQuery ) {
+ window.jQuery = _jQuery;
+ }
+
+ return jQuery;
+ },
+
+ // Is the DOM ready to be used? Set to true once it occurs.
+ isReady: false,
+
+ // A counter to track how many items to wait for before
+ // the ready event fires. See #6781
+ readyWait: 1,
+
+ // Hold (or release) the ready event
+ holdReady: function( hold ) {
+ if ( hold ) {
+ jQuery.readyWait++;
+ } else {
+ jQuery.ready( true );
+ }
+ },
+
+ // Handle when the DOM is ready
+ ready: function( wait ) {
+
+ // Abort if there are pending holds or we're already ready
+ if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
+ return;
+ }
+
+ // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
+ if ( !document.body ) {
+ return setTimeout( jQuery.ready, 1 );
+ }
+
+ // Remember that the DOM is ready
+ jQuery.isReady = true;
+
+ // If a normal DOM Ready event fired, decrement, and wait if need be
+ if ( wait !== true && --jQuery.readyWait > 0 ) {
+ return;
+ }
+
+ // If there are functions bound, to execute
+ readyList.resolveWith( document, [ jQuery ] );
+
+ // Trigger any bound ready events
+ if ( jQuery.fn.trigger ) {
+ jQuery( document ).trigger("ready").off("ready");
+ }
+ },
+
+ // See test/unit/core.js for details concerning isFunction.
+ // Since version 1.3, DOM methods and functions like alert
+ // aren't supported. They return false on IE (#2968).
+ isFunction: function( obj ) {
+ return jQuery.type(obj) === "function";
+ },
+
+ isArray: Array.isArray || function( obj ) {
+ return jQuery.type(obj) === "array";
+ },
+
+ isWindow: function( obj ) {
+ return obj != null && obj == obj.window;
+ },
+
+ isNumeric: function( obj ) {
+ return !isNaN( parseFloat(obj) ) && isFinite( obj );
+ },
+
+ type: function( obj ) {
+ return obj == null ?
+ String( obj ) :
+ class2type[ core_toString.call(obj) ] || "object";
+ },
+
+ isPlainObject: function( obj ) {
+ // Must be an Object.
+ // Because of IE, we also have to check the presence of the constructor property.
+ // Make sure that DOM nodes and window objects don't pass through, as well
+ if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
+ return false;
+ }
+
+ try {
+ // Not own constructor property must be Object
+ if ( obj.constructor &&
+ !core_hasOwn.call(obj, "constructor") &&
+ !core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
+ return false;
+ }
+ } catch ( e ) {
+ // IE8,9 Will throw exceptions on certain host objects #9897
+ return false;
+ }
+
+ // Own properties are enumerated firstly, so to speed up,
+ // if last one is own, then all properties are own.
+
+ var key;
+ for ( key in obj ) {}
+
+ return key === undefined || core_hasOwn.call( obj, key );
+ },
+
+ isEmptyObject: function( obj ) {
+ var name;
+ for ( name in obj ) {
+ return false;
+ }
+ return true;
+ },
+
+ error: function( msg ) {
+ throw new Error( msg );
+ },
+
+ // data: string of html
+ // context (optional): If specified, the fragment will be created in this context, defaults to document
+ // scripts (optional): If true, will include scripts passed in the html string
+ parseHTML: function( data, context, scripts ) {
+ var parsed;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ if ( typeof context === "boolean" ) {
+ scripts = context;
+ context = 0;
+ }
+ context = context || document;
+
+ // Single tag
+ if ( (parsed = rsingleTag.exec( data )) ) {
+ return [ context.createElement( parsed[1] ) ];
+ }
+
+ parsed = jQuery.buildFragment( [ data ], context, scripts ? null : [] );
+ return jQuery.merge( [],
+ (parsed.cacheable ? jQuery.clone( parsed.fragment ) : parsed.fragment).childNodes );
+ },
+
+ parseJSON: function( data ) {
+ if ( !data || typeof data !== "string") {
+ return null;
+ }
+
+ // Make sure leading/trailing whitespace is removed (IE can't handle it)
+ data = jQuery.trim( data );
+
+ // Attempt to parse using the native JSON parser first
+ if ( window.JSON && window.JSON.parse ) {
+ return window.JSON.parse( data );
+ }
+
+ // Make sure the incoming data is actual JSON
+ // Logic borrowed from http://json.org/json2.js
+ if ( rvalidchars.test( data.replace( rvalidescape, "@" )
+ .replace( rvalidtokens, "]" )
+ .replace( rvalidbraces, "")) ) {
+
+ return ( new Function( "return " + data ) )();
+
+ }
+ jQuery.error( "Invalid JSON: " + data );
+ },
+
+ // Cross-browser xml parsing
+ parseXML: function( data ) {
+ var xml, tmp;
+ if ( !data || typeof data !== "string" ) {
+ return null;
+ }
+ try {
+ if ( window.DOMParser ) { // Standard
+ tmp = new DOMParser();
+ xml = tmp.parseFromString( data , "text/xml" );
+ } else { // IE
+ xml = new ActiveXObject( "Microsoft.XMLDOM" );
+ xml.async = "false";
+ xml.loadXML( data );
+ }
+ } catch( e ) {
+ xml = undefined;
+ }
+ if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
+ jQuery.error( "Invalid XML: " + data );
+ }
+ return xml;
+ },
+
+ noop: function() {},
+
+ // Evaluates a script in a global context
+ // Workarounds based on findings by Jim Driscoll
+ // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
+ globalEval: function( data ) {
+ if ( data && core_rnotwhite.test( data ) ) {
+ // We use execScript on Internet Explorer
+ // We use an anonymous function so that context is window
+ // rather than jQuery in Firefox
+ ( window.execScript || function( data ) {
+ window[ "eval" ].call( window, data );
+ } )( data );
+ }
+ },
+
+ // Convert dashed to camelCase; used by the css and data modules
+ // Microsoft forgot to hump their vendor prefix (#9572)
+ camelCase: function( string ) {
+ return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
+ },
+
+ nodeName: function( elem, name ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
+ },
+
+ // args is for internal usage only
+ each: function( obj, callback, args ) {
+ var name,
+ i = 0,
+ length = obj.length,
+ isObj = length === undefined || jQuery.isFunction( obj );
+
+ if ( args ) {
+ if ( isObj ) {
+ for ( name in obj ) {
+ if ( callback.apply( obj[ name ], args ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.apply( obj[ i++ ], args ) === false ) {
+ break;
+ }
+ }
+ }
+
+ // A special, fast, case for the most common use of each
+ } else {
+ if ( isObj ) {
+ for ( name in obj ) {
+ if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) {
+ break;
+ }
+ }
+ } else {
+ for ( ; i < length; ) {
+ if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) {
+ break;
+ }
+ }
+ }
+ }
+
+ return obj;
+ },
+
+ // Use native String.trim function wherever possible
+ trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
+ function( text ) {
+ return text == null ?
+ "" :
+ core_trim.call( text );
+ } :
+
+ // Otherwise use our own trimming functionality
+ function( text ) {
+ return text == null ?
+ "" :
+ ( text + "" ).replace( rtrim, "" );
+ },
+
+ // results is for internal usage only
+ makeArray: function( arr, results ) {
+ var type,
+ ret = results || [];
+
+ if ( arr != null ) {
+ // The window, strings (and functions) also have 'length'
+ // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
+ type = jQuery.type( arr );
+
+ if ( arr.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( arr ) ) {
+ core_push.call( ret, arr );
+ } else {
+ jQuery.merge( ret, arr );
+ }
+ }
+
+ return ret;
+ },
+
+ inArray: function( elem, arr, i ) {
+ var len;
+
+ if ( arr ) {
+ if ( core_indexOf ) {
+ return core_indexOf.call( arr, elem, i );
+ }
+
+ len = arr.length;
+ i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
+
+ for ( ; i < len; i++ ) {
+ // Skip accessing in sparse arrays
+ if ( i in arr && arr[ i ] === elem ) {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ },
+
+ merge: function( first, second ) {
+ var l = second.length,
+ i = first.length,
+ j = 0;
+
+ if ( typeof l === "number" ) {
+ for ( ; j < l; j++ ) {
+ first[ i++ ] = second[ j ];
+ }
+
+ } else {
+ while ( second[j] !== undefined ) {
+ first[ i++ ] = second[ j++ ];
+ }
+ }
+
+ first.length = i;
+
+ return first;
+ },
+
+ grep: function( elems, callback, inv ) {
+ var retVal,
+ ret = [],
+ i = 0,
+ length = elems.length;
+ inv = !!inv;
+
+ // Go through the array, only saving the items
+ // that pass the validator function
+ for ( ; i < length; i++ ) {
+ retVal = !!callback( elems[ i ], i );
+ if ( inv !== retVal ) {
+ ret.push( elems[ i ] );
+ }
+ }
+
+ return ret;
+ },
+
+ // arg is for internal usage only
+ map: function( elems, callback, arg ) {
+ var value, key,
+ ret = [],
+ i = 0,
+ length = elems.length,
+ // jquery objects are treated as arrays
+ isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ;
+
+ // Go through the array, translating each of the items to their
+ if ( isArray ) {
+ for ( ; i < length; i++ ) {
+ value = callback( elems[ i ], i, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+
+ // Go through every key on the object,
+ } else {
+ for ( key in elems ) {
+ value = callback( elems[ key ], key, arg );
+
+ if ( value != null ) {
+ ret[ ret.length ] = value;
+ }
+ }
+ }
+
+ // Flatten any nested arrays
+ return ret.concat.apply( [], ret );
+ },
+
+ // A global GUID counter for objects
+ guid: 1,
+
+ // Bind a function to a context, optionally partially applying any
+ // arguments.
+ proxy: function( fn, context ) {
+ var tmp, args, proxy;
+
+ if ( typeof context === "string" ) {
+ tmp = fn[ context ];
+ context = fn;
+ fn = tmp;
+ }
+
+ // Quick check to determine if target is callable, in the spec
+ // this throws a TypeError, but we will just return undefined.
+ if ( !jQuery.isFunction( fn ) ) {
+ return undefined;
+ }
+
+ // Simulated bind
+ args = core_slice.call( arguments, 2 );
+ proxy = function() {
+ return fn.apply( context, args.concat( core_slice.call( arguments ) ) );
+ };
+
+ // Set the guid of unique handler to the same of original handler, so it can be removed
+ proxy.guid = fn.guid = fn.guid || jQuery.guid++;
+
+ return proxy;
+ },
+
+ // Multifunctional method to get and set values of a collection
+ // The value/s can optionally be executed if it's a function
+ access: function( elems, fn, key, value, chainable, emptyGet, pass ) {
+ var exec,
+ bulk = key == null,
+ i = 0,
+ length = elems.length;
+
+ // Sets many values
+ if ( key && typeof key === "object" ) {
+ for ( i in key ) {
+ jQuery.access( elems, fn, i, key[i], 1, emptyGet, value );
+ }
+ chainable = 1;
+
+ // Sets one value
+ } else if ( value !== undefined ) {
+ // Optionally, function values get executed if exec is true
+ exec = pass === undefined && jQuery.isFunction( value );
+
+ if ( bulk ) {
+ // Bulk operations only iterate when executing function values
+ if ( exec ) {
+ exec = fn;
+ fn = function( elem, key, value ) {
+ return exec.call( jQuery( elem ), value );
+ };
+
+ // Otherwise they run against the entire set
+ } else {
+ fn.call( elems, value );
+ fn = null;
+ }
+ }
+
+ if ( fn ) {
+ for (; i < length; i++ ) {
+ fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass );
+ }
+ }
+
+ chainable = 1;
+ }
+
+ return chainable ?
+ elems :
+
+ // Gets
+ bulk ?
+ fn.call( elems ) :
+ length ? fn( elems[0], key ) : emptyGet;
+ },
+
+ now: function() {
+ return ( new Date() ).getTime();
+ }
+});
+
+jQuery.ready.promise = function( obj ) {
+ if ( !readyList ) {
+
+ readyList = jQuery.Deferred();
+
+ // Catch cases where $(document).ready() is called after the browser event has already occurred.
+ // we once tried to use readyState "interactive" here, but it caused issues like the one
+ // discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
+ if ( document.readyState === "complete" ) {
+ // Handle it asynchronously to allow scripts the opportunity to delay ready
+ setTimeout( jQuery.ready, 1 );
+
+ // Standards-based browsers support DOMContentLoaded
+ } else if ( document.addEventListener ) {
+ // Use the handy event callback
+ document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
+
+ // A fallback to window.onload, that will always work
+ window.addEventListener( "load", jQuery.ready, false );
+
+ // If IE event model is used
+ } else {
+ // Ensure firing before onload, maybe late but safe also for iframes
+ document.attachEvent( "onreadystatechange", DOMContentLoaded );
+
+ // A fallback to window.onload, that will always work
+ window.attachEvent( "onload", jQuery.ready );
+
+ // If IE and not a frame
+ // continually check to see if the document is ready
+ var top = false;
+
+ try {
+ top = window.frameElement == null && document.documentElement;
+ } catch(e) {}
+
+ if ( top && top.doScroll ) {
+ (function doScrollCheck() {
+ if ( !jQuery.isReady ) {
+
+ try {
+ // Use the trick by Diego Perini
+ // http://javascript.nwbox.com/IEContentLoaded/
+ top.doScroll("left");
+ } catch(e) {
+ return setTimeout( doScrollCheck, 50 );
+ }
+
+ // and execute any waiting functions
+ jQuery.ready();
+ }
+ })();
+ }
+ }
+ }
+ return readyList.promise( obj );
+};
+
+// Populate the class2type map
+jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
+ class2type[ "[object " + name + "]" ] = name.toLowerCase();
+});
+
+// All jQuery objects should point back to these
+rootjQuery = jQuery(document);
+// String to Object options format cache
+var optionsCache = {};
+
+// Convert String-formatted options into Object-formatted ones and store in cache
+function createOptions( options ) {
+ var object = optionsCache[ options ] = {};
+ jQuery.each( options.split( core_rspace ), function( _, flag ) {
+ object[ flag ] = true;
+ });
+ return object;
+}
+
+/*
+ * Create a callback list using the following parameters:
+ *
+ * options: an optional list of space-separated options that will change how
+ * the callback list behaves or a more traditional option object
+ *
+ * By default a callback list will act like an event callback list and can be
+ * "fired" multiple times.
+ *
+ * Possible options:
+ *
+ * once: will ensure the callback list can only be fired once (like a Deferred)
+ *
+ * memory: will keep track of previous values and will call any callback added
+ * after the list has been fired right away with the latest "memorized"
+ * values (like a Deferred)
+ *
+ * unique: will ensure a callback can only be added once (no duplicate in the list)
+ *
+ * stopOnFalse: interrupt callings when a callback returns false
+ *
+ */
+jQuery.Callbacks = function( options ) {
+
+ // Convert options from String-formatted to Object-formatted if needed
+ // (we check in cache first)
+ options = typeof options === "string" ?
+ ( optionsCache[ options ] || createOptions( options ) ) :
+ jQuery.extend( {}, options );
+
+ var // Last fire value (for non-forgettable lists)
+ memory,
+ // Flag to know if list was already fired
+ fired,
+ // Flag to know if list is currently firing
+ firing,
+ // First callback to fire (used internally by add and fireWith)
+ firingStart,
+ // End of the loop when firing
+ firingLength,
+ // Index of currently firing callback (modified by remove if needed)
+ firingIndex,
+ // Actual callback list
+ list = [],
+ // Stack of fire calls for repeatable lists
+ stack = !options.once && [],
+ // Fire callbacks
+ fire = function( data ) {
+ memory = options.memory && data;
+ fired = true;
+ firingIndex = firingStart || 0;
+ firingStart = 0;
+ firingLength = list.length;
+ firing = true;
+ for ( ; list && firingIndex < firingLength; firingIndex++ ) {
+ if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
+ memory = false; // To prevent further calls using add
+ break;
+ }
+ }
+ firing = false;
+ if ( list ) {
+ if ( stack ) {
+ if ( stack.length ) {
+ fire( stack.shift() );
+ }
+ } else if ( memory ) {
+ list = [];
+ } else {
+ self.disable();
+ }
+ }
+ },
+ // Actual Callbacks object
+ self = {
+ // Add a callback or a collection of callbacks to the list
+ add: function() {
+ if ( list ) {
+ // First, we save the current length
+ var start = list.length;
+ (function add( args ) {
+ jQuery.each( args, function( _, arg ) {
+ var type = jQuery.type( arg );
+ if ( type === "function" && ( !options.unique || !self.has( arg ) ) ) {
+ list.push( arg );
+ } else if ( arg && arg.length && type !== "string" ) {
+ // Inspect recursively
+ add( arg );
+ }
+ });
+ })( arguments );
+ // Do we need to add the callbacks to the
+ // current firing batch?
+ if ( firing ) {
+ firingLength = list.length;
+ // With memory, if we're not firing then
+ // we should call right away
+ } else if ( memory ) {
+ firingStart = start;
+ fire( memory );
+ }
+ }
+ return this;
+ },
+ // Remove a callback from the list
+ remove: function() {
+ if ( list ) {
+ jQuery.each( arguments, function( _, arg ) {
+ var index;
+ while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
+ list.splice( index, 1 );
+ // Handle firing indexes
+ if ( firing ) {
+ if ( index <= firingLength ) {
+ firingLength--;
+ }
+ if ( index <= firingIndex ) {
+ firingIndex--;
+ }
+ }
+ }
+ });
+ }
+ return this;
+ },
+ // Control if a given callback is in the list
+ has: function( fn ) {
+ return jQuery.inArray( fn, list ) > -1;
+ },
+ // Remove all callbacks from the list
+ empty: function() {
+ list = [];
+ return this;
+ },
+ // Have the list do nothing anymore
+ disable: function() {
+ list = stack = memory = undefined;
+ return this;
+ },
+ // Is it disabled?
+ disabled: function() {
+ return !list;
+ },
+ // Lock the list in its current state
+ lock: function() {
+ stack = undefined;
+ if ( !memory ) {
+ self.disable();
+ }
+ return this;
+ },
+ // Is it locked?
+ locked: function() {
+ return !stack;
+ },
+ // Call all callbacks with the given context and arguments
+ fireWith: function( context, args ) {
+ args = args || [];
+ args = [ context, args.slice ? args.slice() : args ];
+ if ( list && ( !fired || stack ) ) {
+ if ( firing ) {
+ stack.push( args );
+ } else {
+ fire( args );
+ }
+ }
+ return this;
+ },
+ // Call all the callbacks with the given arguments
+ fire: function() {
+ self.fireWith( this, arguments );
+ return this;
+ },
+ // To know if the callbacks have already been called at least once
+ fired: function() {
+ return !!fired;
+ }
+ };
+
+ return self;
+};
+jQuery.extend({
+
+ Deferred: function( func ) {
+ var tuples = [
+ // action, add listener, listener list, final state
+ [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
+ [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
+ [ "notify", "progress", jQuery.Callbacks("memory") ]
+ ],
+ state = "pending",
+ promise = {
+ state: function() {
+ return state;
+ },
+ always: function() {
+ deferred.done( arguments ).fail( arguments );
+ return this;
+ },
+ then: function( /* fnDone, fnFail, fnProgress */ ) {
+ var fns = arguments;
+ return jQuery.Deferred(function( newDefer ) {
+ jQuery.each( tuples, function( i, tuple ) {
+ var action = tuple[ 0 ],
+ fn = fns[ i ];
+ // deferred[ done | fail | progress ] for forwarding actions to newDefer
+ deferred[ tuple[1] ]( jQuery.isFunction( fn ) ?
+ function() {
+ var returned = fn.apply( this, arguments );
+ if ( returned && jQuery.isFunction( returned.promise ) ) {
+ returned.promise()
+ .done( newDefer.resolve )
+ .fail( newDefer.reject )
+ .progress( newDefer.notify );
+ } else {
+ newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
+ }
+ } :
+ newDefer[ action ]
+ );
+ });
+ fns = null;
+ }).promise();
+ },
+ // Get a promise for this deferred
+ // If obj is provided, the promise aspect is added to the object
+ promise: function( obj ) {
+ return obj != null ? jQuery.extend( obj, promise ) : promise;
+ }
+ },
+ deferred = {};
+
+ // Keep pipe for back-compat
+ promise.pipe = promise.then;
+
+ // Add list-specific methods
+ jQuery.each( tuples, function( i, tuple ) {
+ var list = tuple[ 2 ],
+ stateString = tuple[ 3 ];
+
+ // promise[ done | fail | progress ] = list.add
+ promise[ tuple[1] ] = list.add;
+
+ // Handle state
+ if ( stateString ) {
+ list.add(function() {
+ // state = [ resolved | rejected ]
+ state = stateString;
+
+ // [ reject_list | resolve_list ].disable; progress_list.lock
+ }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
+ }
+
+ // deferred[ resolve | reject | notify ] = list.fire
+ deferred[ tuple[0] ] = list.fire;
+ deferred[ tuple[0] + "With" ] = list.fireWith;
+ });
+
+ // Make the deferred a promise
+ promise.promise( deferred );
+
+ // Call given func if any
+ if ( func ) {
+ func.call( deferred, deferred );
+ }
+
+ // All done!
+ return deferred;
+ },
+
+ // Deferred helper
+ when: function( subordinate /* , ..., subordinateN */ ) {
+ var i = 0,
+ resolveValues = core_slice.call( arguments ),
+ length = resolveValues.length,
+
+ // the count of uncompleted subordinates
+ remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
+
+ // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
+ deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
+
+ // Update function for both resolve and progress values
+ updateFunc = function( i, contexts, values ) {
+ return function( value ) {
+ contexts[ i ] = this;
+ values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
+ if( values === progressValues ) {
+ deferred.notifyWith( contexts, values );
+ } else if ( !( --remaining ) ) {
+ deferred.resolveWith( contexts, values );
+ }
+ };
+ },
+
+ progressValues, progressContexts, resolveContexts;
+
+ // add listeners to Deferred subordinates; treat others as resolved
+ if ( length > 1 ) {
+ progressValues = new Array( length );
+ progressContexts = new Array( length );
+ resolveContexts = new Array( length );
+ for ( ; i < length; i++ ) {
+ if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
+ resolveValues[ i ].promise()
+ .done( updateFunc( i, resolveContexts, resolveValues ) )
+ .fail( deferred.reject )
+ .progress( updateFunc( i, progressContexts, progressValues ) );
+ } else {
+ --remaining;
+ }
+ }
+ }
+
+ // if we're not waiting on anything, resolve the master
+ if ( !remaining ) {
+ deferred.resolveWith( resolveContexts, resolveValues );
+ }
+
+ return deferred.promise();
+ }
+});
+jQuery.support = (function() {
+
+ var support,
+ all,
+ a,
+ select,
+ opt,
+ input,
+ fragment,
+ eventName,
+ i,
+ isSupported,
+ clickFn,
+ div = document.createElement("div");
+
+ // Preliminary tests
+ div.setAttribute( "className", "t" );
+ div.innerHTML = " <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
+
+ all = div.getElementsByTagName("*");
+ a = div.getElementsByTagName("a")[ 0 ];
+ a.style.cssText = "top:1px;float:left;opacity:.5";
+
+ // Can't get basic test support
+ if ( !all || !all.length ) {
+ return {};
+ }
+
+ // First batch of supports tests
+ select = document.createElement("select");
+ opt = select.appendChild( document.createElement("option") );
+ input = div.getElementsByTagName("input")[ 0 ];
+
+ support = {
+ // IE strips leading whitespace when .innerHTML is used
+ leadingWhitespace: ( div.firstChild.nodeType === 3 ),
+
+ // Make sure that tbody elements aren't automatically inserted
+ // IE will insert them into empty tables
+ tbody: !div.getElementsByTagName("tbody").length,
+
+ // Make sure that link elements get serialized correctly by innerHTML
+ // This requires a wrapper element in IE
+ htmlSerialize: !!div.getElementsByTagName("link").length,
+
+ // Get the style information from getAttribute
+ // (IE uses .cssText instead)
+ style: /top/.test( a.getAttribute("style") ),
+
+ // Make sure that URLs aren't manipulated
+ // (IE normalizes it by default)
+ hrefNormalized: ( a.getAttribute("href") === "/a" ),
+
+ // Make sure that element opacity exists
+ // (IE uses filter instead)
+ // Use a regex to work around a WebKit issue. See #5145
+ opacity: /^0.5/.test( a.style.opacity ),
+
+ // Verify style float existence
+ // (IE uses styleFloat instead of cssFloat)
+ cssFloat: !!a.style.cssFloat,
+
+ // Make sure that if no value is specified for a checkbox
+ // that it defaults to "on".
+ // (WebKit defaults to "" instead)
+ checkOn: ( input.value === "on" ),
+
+ // Make sure that a selected-by-default option has a working selected property.
+ // (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
+ optSelected: opt.selected,
+
+ // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
+ getSetAttribute: div.className !== "t",
+
+ // Tests for enctype support on a form(#6743)
+ enctype: !!document.createElement("form").enctype,
+
+ // Makes sure cloning an html5 element does not cause problems
+ // Where outerHTML is undefined, this still works
+ html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>",
+
+ // jQuery.support.boxModel DEPRECATED in 1.8 since we don't support Quirks Mode
+ boxModel: ( document.compatMode === "CSS1Compat" ),
+
+ // Will be defined later
+ submitBubbles: true,
+ changeBubbles: true,
+ focusinBubbles: false,
+ deleteExpando: true,
+ noCloneEvent: true,
+ inlineBlockNeedsLayout: false,
+ shrinkWrapBlocks: false,
+ reliableMarginRight: true,
+ boxSizingReliable: true,
+ pixelPosition: false
+ };
+
+ // Make sure checked status is properly cloned
+ input.checked = true;
+ support.noCloneChecked = input.cloneNode( true ).checked;
+
+ // Make sure that the options inside disabled selects aren't marked as disabled
+ // (WebKit marks them as disabled)
+ select.disabled = true;
+ support.optDisabled = !opt.disabled;
+
+ // Test to see if it's possible to delete an expando from an element
+ // Fails in Internet Explorer
+ try {
+ delete div.test;
+ } catch( e ) {
+ support.deleteExpando = false;
+ }
+
+ if ( !div.addEventListener && div.attachEvent && div.fireEvent ) {
+ div.attachEvent( "onclick", clickFn = function() {
+ // Cloning a node shouldn't copy over any
+ // bound event handlers (IE does this)
+ support.noCloneEvent = false;
+ });
+ div.cloneNode( true ).fireEvent("onclick");
+ div.detachEvent( "onclick", clickFn );
+ }
+
+ // Check if a radio maintains its value
+ // after being appended to the DOM
+ input = document.createElement("input");
+ input.value = "t";
+ input.setAttribute( "type", "radio" );
+ support.radioValue = input.value === "t";
+
+ input.setAttribute( "checked", "checked" );
+
+ // #11217 - WebKit loses check when the name is after the checked attribute
+ input.setAttribute( "name", "t" );
+
+ div.appendChild( input );
+ fragment = document.createDocumentFragment();
+ fragment.appendChild( div.lastChild );
+
+ // WebKit doesn't clone checked state correctly in fragments
+ support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
+
+ // Check if a disconnected checkbox will retain its checked
+ // value of true after appended to the DOM (IE6/7)
+ support.appendChecked = input.checked;
+
+ fragment.removeChild( input );
+ fragment.appendChild( div );
+
+ // Technique from Juriy Zaytsev
+ // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/
+ // We only care about the case where non-standard event systems
+ // are used, namely in IE. Short-circuiting here helps us to
+ // avoid an eval call (in setAttribute) which can cause CSP
+ // to go haywire. See: https://developer.mozilla.org/en/Security/CSP
+ if ( div.attachEvent ) {
+ for ( i in {
+ submit: true,
+ change: true,
+ focusin: true
+ }) {
+ eventName = "on" + i;
+ isSupported = ( eventName in div );
+ if ( !isSupported ) {
+ div.setAttribute( eventName, "return;" );
+ isSupported = ( typeof div[ eventName ] === "function" );
+ }
+ support[ i + "Bubbles" ] = isSupported;
+ }
+ }
+
+ // Run tests that need a body at doc ready
+ jQuery(function() {
+ var container, div, tds, marginDiv,
+ divReset = "padding:0;margin:0;border:0;display:block;overflow:hidden;",
+ body = document.getElementsByTagName("body")[0];
+
+ if ( !body ) {
+ // Return for frameset docs that don't have a body
+ return;
+ }
+
+ container = document.createElement("div");
+ container.style.cssText = "visibility:hidden;border:0;width:0;height:0;position:static;top:0;margin-top:1px";
+ body.insertBefore( container, body.firstChild );
+
+ // Construct the test element
+ div = document.createElement("div");
+ container.appendChild( div );
+
+ // Check if table cells still have offsetWidth/Height when they are set
+ // to display:none and there are still other visible table cells in a
+ // table row; if so, offsetWidth/Height are not reliable for use when
+ // determining if an element has been hidden directly using
+ // display:none (it is still safe to use offsets if a parent element is
+ // hidden; don safety goggles and see bug #4512 for more information).
+ // (only IE 8 fails this test)
+ div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
+ tds = div.getElementsByTagName("td");
+ tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
+ isSupported = ( tds[ 0 ].offsetHeight === 0 );
+
+ tds[ 0 ].style.display = "";
+ tds[ 1 ].style.display = "none";
+
+ // Check if empty table cells still have offsetWidth/Height
+ // (IE <= 8 fail this test)
+ support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
+
+ // Check box-sizing and margin behavior
+ div.innerHTML = "";
+ div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
+ support.boxSizing = ( div.offsetWidth === 4 );
+ support.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== 1 );
+
+ // NOTE: To any future maintainer, we've window.getComputedStyle
+ // because jsdom on node.js will break without it.
+ if ( window.getComputedStyle ) {
+ support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
+ support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
+
+ // Check if div with explicit width and no margin-right incorrectly
+ // gets computed margin-right based on width of container. For more
+ // info see bug #3333
+ // Fails in WebKit before Feb 2011 nightlies
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ marginDiv = document.createElement("div");
+ marginDiv.style.cssText = div.style.cssText = divReset;
+ marginDiv.style.marginRight = marginDiv.style.width = "0";
+ div.style.width = "1px";
+ div.appendChild( marginDiv );
+ support.reliableMarginRight =
+ !parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
+ }
+
+ if ( typeof div.style.zoom !== "undefined" ) {
+ // Check if natively block-level elements act like inline-block
+ // elements when setting their display to 'inline' and giving
+ // them layout
+ // (IE < 8 does this)
+ div.innerHTML = "";
+ div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
+ support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
+
+ // Check if elements with layout shrink-wrap their children
+ // (IE 6 does this)
+ div.style.display = "block";
+ div.style.overflow = "visible";
+ div.innerHTML = "<div></div>";
+ div.firstChild.style.width = "5px";
+ support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
+
+ container.style.zoom = 1;
+ }
+
+ // Null elements to avoid leaks in IE
+ body.removeChild( container );
+ container = div = tds = marginDiv = null;
+ });
+
+ // Null elements to avoid leaks in IE
+ fragment.removeChild( div );
+ all = a = select = opt = input = fragment = div = null;
+
+ return support;
+})();
+var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
+ rmultiDash = /([A-Z])/g;
+
+jQuery.extend({
+ cache: {},
+
+ deletedIds: [],
+
+ // Remove at next major release (1.9/2.0)
+ uuid: 0,
+
+ // Unique for each copy of jQuery on the page
+ // Non-digits removed to match rinlinejQuery
+ expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ),
+
+ // The following elements throw uncatchable exceptions if you
+ // attempt to add expando properties to them.
+ noData: {
+ "embed": true,
+ // Ban all objects except for Flash (which handle expandos)
+ "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000",
+ "applet": true
+ },
+
+ hasData: function( elem ) {
+ elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
+ return !!elem && !isEmptyDataObject( elem );
+ },
+
+ data: function( elem, name, data, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, ret,
+ internalKey = jQuery.expando,
+ getByName = typeof name === "string",
+
+ // We have to handle DOM nodes and JS objects differently because IE6-7
+ // can't GC object references properly across the DOM-JS boundary
+ isNode = elem.nodeType,
+
+ // Only DOM nodes need the global jQuery cache; JS object data is
+ // attached directly to the object so GC can occur automatically
+ cache = isNode ? jQuery.cache : elem,
+
+ // Only defining an ID for JS objects if its cache already exists allows
+ // the code to shortcut on the same path as a DOM node with no cache
+ id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
+
+ // Avoid doing any more work than we need to when trying to get data on an
+ // object that has no data at all
+ if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && getByName && data === undefined ) {
+ return;
+ }
+
+ if ( !id ) {
+ // Only DOM nodes need a new unique ID for each element since their data
+ // ends up in the global cache
+ if ( isNode ) {
+ elem[ internalKey ] = id = jQuery.deletedIds.pop() || jQuery.guid++;
+ } else {
+ id = internalKey;
+ }
+ }
+
+ if ( !cache[ id ] ) {
+ cache[ id ] = {};
+
+ // Avoids exposing jQuery metadata on plain JS objects when the object
+ // is serialized using JSON.stringify
+ if ( !isNode ) {
+ cache[ id ].toJSON = jQuery.noop;
+ }
+ }
+
+ // An object can be passed to jQuery.data instead of a key/value pair; this gets
+ // shallow copied over onto the existing cache
+ if ( typeof name === "object" || typeof name === "function" ) {
+ if ( pvt ) {
+ cache[ id ] = jQuery.extend( cache[ id ], name );
+ } else {
+ cache[ id ].data = jQuery.extend( cache[ id ].data, name );
+ }
+ }
+
+ thisCache = cache[ id ];
+
+ // jQuery data() is stored in a separate object inside the object's internal data
+ // cache in order to avoid key collisions between internal data and user-defined
+ // data.
+ if ( !pvt ) {
+ if ( !thisCache.data ) {
+ thisCache.data = {};
+ }
+
+ thisCache = thisCache.data;
+ }
+
+ if ( data !== undefined ) {
+ thisCache[ jQuery.camelCase( name ) ] = data;
+ }
+
+ // Check for both converted-to-camel and non-converted data property names
+ // If a data property was specified
+ if ( getByName ) {
+
+ // First Try to find as-is property data
+ ret = thisCache[ name ];
+
+ // Test for null|undefined property data
+ if ( ret == null ) {
+
+ // Try to find the camelCased property
+ ret = thisCache[ jQuery.camelCase( name ) ];
+ }
+ } else {
+ ret = thisCache;
+ }
+
+ return ret;
+ },
+
+ removeData: function( elem, name, pvt /* Internal Use Only */ ) {
+ if ( !jQuery.acceptData( elem ) ) {
+ return;
+ }
+
+ var thisCache, i, l,
+
+ isNode = elem.nodeType,
+
+ // See jQuery.data for more information
+ cache = isNode ? jQuery.cache : elem,
+ id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
+
+ // If there is already no cache entry for this object, there is no
+ // purpose in continuing
+ if ( !cache[ id ] ) {
+ return;
+ }
+
+ if ( name ) {
+
+ thisCache = pvt ? cache[ id ] : cache[ id ].data;
+
+ if ( thisCache ) {
+
+ // Support array or space separated string names for data keys
+ if ( !jQuery.isArray( name ) ) {
+
+ // try the string as a key before any manipulation
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+
+ // split the camel cased version by spaces unless a key with the spaces exists
+ name = jQuery.camelCase( name );
+ if ( name in thisCache ) {
+ name = [ name ];
+ } else {
+ name = name.split(" ");
+ }
+ }
+ }
+
+ for ( i = 0, l = name.length; i < l; i++ ) {
+ delete thisCache[ name[i] ];
+ }
+
+ // If there is no data left in the cache, we want to continue
+ // and let the cache object itself get destroyed
+ if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) {
+ return;
+ }
+ }
+ }
+
+ // See jQuery.data for more information
+ if ( !pvt ) {
+ delete cache[ id ].data;
+
+ // Don't destroy the parent cache unless the internal data object
+ // had been the only thing left in it
+ if ( !isEmptyDataObject( cache[ id ] ) ) {
+ return;
+ }
+ }
+
+ // Destroy the cache
+ if ( isNode ) {
+ jQuery.cleanData( [ elem ], true );
+
+ // Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
+ } else if ( jQuery.support.deleteExpando || cache != cache.window ) {
+ delete cache[ id ];
+
+ // When all else fails, null
+ } else {
+ cache[ id ] = null;
+ }
+ },
+
+ // For internal use only.
+ _data: function( elem, name, data ) {
+ return jQuery.data( elem, name, data, true );
+ },
+
+ // A method for determining if a DOM node can handle the data expando
+ acceptData: function( elem ) {
+ var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
+
+ // nodes accept data unless otherwise specified; rejection can be conditional
+ return !noData || noData !== true && elem.getAttribute("classid") === noData;
+ }
+});
+
+jQuery.fn.extend({
+ data: function( key, value ) {
+ var parts, part, attr, name, l,
+ elem = this[0],
+ i = 0,
+ data = null;
+
+ // Gets all values
+ if ( key === undefined ) {
+ if ( this.length ) {
+ data = jQuery.data( elem );
+
+ if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
+ attr = elem.attributes;
+ for ( l = attr.length; i < l; i++ ) {
+ name = attr[i].name;
+
+ if ( !name.indexOf( "data-" ) ) {
+ name = jQuery.camelCase( name.substring(5) );
+
+ dataAttr( elem, name, data[ name ] );
+ }
+ }
+ jQuery._data( elem, "parsedAttrs", true );
+ }
+ }
+
+ return data;
+ }
+
+ // Sets multiple values
+ if ( typeof key === "object" ) {
+ return this.each(function() {
+ jQuery.data( this, key );
+ });
+ }
+
+ parts = key.split( ".", 2 );
+ parts[1] = parts[1] ? "." + parts[1] : "";
+ part = parts[1] + "!";
+
+ return jQuery.access( this, function( value ) {
+
+ if ( value === undefined ) {
+ data = this.triggerHandler( "getData" + part, [ parts[0] ] );
+
+ // Try to fetch any internally stored data first
+ if ( data === undefined && elem ) {
+ data = jQuery.data( elem, key );
+ data = dataAttr( elem, key, data );
+ }
+
+ return data === undefined && parts[1] ?
+ this.data( parts[0] ) :
+ data;
+ }
+
+ parts[1] = value;
+ this.each(function() {
+ var self = jQuery( this );
+
+ self.triggerHandler( "setData" + part, parts );
+ jQuery.data( this, key, value );
+ self.triggerHandler( "changeData" + part, parts );
+ });
+ }, null, value, arguments.length > 1, null, false );
+ },
+
+ removeData: function( key ) {
+ return this.each(function() {
+ jQuery.removeData( this, key );
+ });
+ }
+});
+
+function dataAttr( elem, key, data ) {
+ // If nothing was found internally, try to fetch any
+ // data from the HTML5 data-* attribute
+ if ( data === undefined && elem.nodeType === 1 ) {
+
+ var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
+
+ data = elem.getAttribute( name );
+
+ if ( typeof data === "string" ) {
+ try {
+ data = data === "true" ? true :
+ data === "false" ? false :
+ data === "null" ? null :
+ // Only convert to a number if it doesn't change the string
+ +data + "" === data ? +data :
+ rbrace.test( data ) ? jQuery.parseJSON( data ) :
+ data;
+ } catch( e ) {}
+
+ // Make sure we set the data so it isn't changed later
+ jQuery.data( elem, key, data );
+
+ } else {
+ data = undefined;
+ }
+ }
+
+ return data;
+}
+
+// checks a cache object for emptiness
+function isEmptyDataObject( obj ) {
+ var name;
+ for ( name in obj ) {
+
+ // if the public data object is empty, the private is still empty
+ if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
+ continue;
+ }
+ if ( name !== "toJSON" ) {
+ return false;
+ }
+ }
+
+ return true;
+}
+jQuery.extend({
+ queue: function( elem, type, data ) {
+ var queue;
+
+ if ( elem ) {
+ type = ( type || "fx" ) + "queue";
+ queue = jQuery._data( elem, type );
+
+ // Speed up dequeue by getting out quickly if this is just a lookup
+ if ( data ) {
+ if ( !queue || jQuery.isArray(data) ) {
+ queue = jQuery._data( elem, type, jQuery.makeArray(data) );
+ } else {
+ queue.push( data );
+ }
+ }
+ return queue || [];
+ }
+ },
+
+ dequeue: function( elem, type ) {
+ type = type || "fx";
+
+ var queue = jQuery.queue( elem, type ),
+ startLength = queue.length,
+ fn = queue.shift(),
+ hooks = jQuery._queueHooks( elem, type ),
+ next = function() {
+ jQuery.dequeue( elem, type );
+ };
+
+ // If the fx queue is dequeued, always remove the progress sentinel
+ if ( fn === "inprogress" ) {
+ fn = queue.shift();
+ startLength--;
+ }
+
+ if ( fn ) {
+
+ // Add a progress sentinel to prevent the fx queue from being
+ // automatically dequeued
+ if ( type === "fx" ) {
+ queue.unshift( "inprogress" );
+ }
+
+ // clear up the last queue stop function
+ delete hooks.stop;
+ fn.call( elem, next, hooks );
+ }
+
+ if ( !startLength && hooks ) {
+ hooks.empty.fire();
+ }
+ },
+
+ // not intended for public consumption - generates a queueHooks object, or returns the current one
+ _queueHooks: function( elem, type ) {
+ var key = type + "queueHooks";
+ return jQuery._data( elem, key ) || jQuery._data( elem, key, {
+ empty: jQuery.Callbacks("once memory").add(function() {
+ jQuery.removeData( elem, type + "queue", true );
+ jQuery.removeData( elem, key, true );
+ })
+ });
+ }
+});
+
+jQuery.fn.extend({
+ queue: function( type, data ) {
+ var setter = 2;
+
+ if ( typeof type !== "string" ) {
+ data = type;
+ type = "fx";
+ setter--;
+ }
+
+ if ( arguments.length < setter ) {
+ return jQuery.queue( this[0], type );
+ }
+
+ return data === undefined ?
+ this :
+ this.each(function() {
+ var queue = jQuery.queue( this, type, data );
+
+ // ensure a hooks for this queue
+ jQuery._queueHooks( this, type );
+
+ if ( type === "fx" && queue[0] !== "inprogress" ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ },
+ dequeue: function( type ) {
+ return this.each(function() {
+ jQuery.dequeue( this, type );
+ });
+ },
+ // Based off of the plugin by Clint Helfers, with permission.
+ // http://blindsignals.com/index.php/2009/07/jquery-delay/
+ delay: function( time, type ) {
+ time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
+ type = type || "fx";
+
+ return this.queue( type, function( next, hooks ) {
+ var timeout = setTimeout( next, time );
+ hooks.stop = function() {
+ clearTimeout( timeout );
+ };
+ });
+ },
+ clearQueue: function( type ) {
+ return this.queue( type || "fx", [] );
+ },
+ // Get a promise resolved when queues of a certain type
+ // are emptied (fx is the type by default)
+ promise: function( type, obj ) {
+ var tmp,
+ count = 1,
+ defer = jQuery.Deferred(),
+ elements = this,
+ i = this.length,
+ resolve = function() {
+ if ( !( --count ) ) {
+ defer.resolveWith( elements, [ elements ] );
+ }
+ };
+
+ if ( typeof type !== "string" ) {
+ obj = type;
+ type = undefined;
+ }
+ type = type || "fx";
+
+ while( i-- ) {
+ tmp = jQuery._data( elements[ i ], type + "queueHooks" );
+ if ( tmp && tmp.empty ) {
+ count++;
+ tmp.empty.add( resolve );
+ }
+ }
+ resolve();
+ return defer.promise( obj );
+ }
+});
+var nodeHook, boolHook, fixSpecified,
+ rclass = /[\t\r\n]/g,
+ rreturn = /\r/g,
+ rtype = /^(?:button|input)$/i,
+ rfocusable = /^(?:button|input|object|select|textarea)$/i,
+ rclickable = /^a(?:rea|)$/i,
+ rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
+ getSetAttribute = jQuery.support.getSetAttribute;
+
+jQuery.fn.extend({
+ attr: function( name, value ) {
+ return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
+ },
+
+ removeAttr: function( name ) {
+ return this.each(function() {
+ jQuery.removeAttr( this, name );
+ });
+ },
+
+ prop: function( name, value ) {
+ return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
+ },
+
+ removeProp: function( name ) {
+ name = jQuery.propFix[ name ] || name;
+ return this.each(function() {
+ // try/catch handles cases where IE balks (such as removing a property on window)
+ try {
+ this[ name ] = undefined;
+ delete this[ name ];
+ } catch( e ) {}
+ });
+ },
+
+ addClass: function( value ) {
+ var classNames, i, l, elem,
+ setClass, c, cl;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).addClass( value.call(this, j, this.className) );
+ });
+ }
+
+ if ( value && typeof value === "string" ) {
+ classNames = value.split( core_rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+
+ if ( elem.nodeType === 1 ) {
+ if ( !elem.className && classNames.length === 1 ) {
+ elem.className = value;
+
+ } else {
+ setClass = " " + elem.className + " ";
+
+ for ( c = 0, cl = classNames.length; c < cl; c++ ) {
+ if ( setClass.indexOf( " " + classNames[ c ] + " " ) < 0 ) {
+ setClass += classNames[ c ] + " ";
+ }
+ }
+ elem.className = jQuery.trim( setClass );
+ }
+ }
+ }
+ }
+
+ return this;
+ },
+
+ removeClass: function( value ) {
+ var removes, className, elem, c, cl, i, l;
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( j ) {
+ jQuery( this ).removeClass( value.call(this, j, this.className) );
+ });
+ }
+ if ( (value && typeof value === "string") || value === undefined ) {
+ removes = ( value || "" ).split( core_rspace );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ elem = this[ i ];
+ if ( elem.nodeType === 1 && elem.className ) {
+
+ className = (" " + elem.className + " ").replace( rclass, " " );
+
+ // loop over each item in the removal list
+ for ( c = 0, cl = removes.length; c < cl; c++ ) {
+ // Remove until there is nothing to remove,
+ while ( className.indexOf(" " + removes[ c ] + " ") >= 0 ) {
+ className = className.replace( " " + removes[ c ] + " " , " " );
+ }
+ }
+ elem.className = value ? jQuery.trim( className ) : "";
+ }
+ }
+ }
+
+ return this;
+ },
+
+ toggleClass: function( value, stateVal ) {
+ var type = typeof value,
+ isBool = typeof stateVal === "boolean";
+
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function( i ) {
+ jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
+ });
+ }
+
+ return this.each(function() {
+ if ( type === "string" ) {
+ // toggle individual class names
+ var className,
+ i = 0,
+ self = jQuery( this ),
+ state = stateVal,
+ classNames = value.split( core_rspace );
+
+ while ( (className = classNames[ i++ ]) ) {
+ // check each className given, space separated list
+ state = isBool ? state : !self.hasClass( className );
+ self[ state ? "addClass" : "removeClass" ]( className );
+ }
+
+ } else if ( type === "undefined" || type === "boolean" ) {
+ if ( this.className ) {
+ // store className if set
+ jQuery._data( this, "__className__", this.className );
+ }
+
+ // toggle whole className
+ this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
+ }
+ });
+ },
+
+ hasClass: function( selector ) {
+ var className = " " + selector + " ",
+ i = 0,
+ l = this.length;
+ for ( ; i < l; i++ ) {
+ if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+ val: function( value ) {
+ var hooks, ret, isFunction,
+ elem = this[0];
+
+ if ( !arguments.length ) {
+ if ( elem ) {
+ hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
+
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
+ return ret;
+ }
+
+ ret = elem.value;
+
+ return typeof ret === "string" ?
+ // handle most common string cases
+ ret.replace(rreturn, "") :
+ // handle cases where value is null/undef or number
+ ret == null ? "" : ret;
+ }
+
+ return;
+ }
+
+ isFunction = jQuery.isFunction( value );
+
+ return this.each(function( i ) {
+ var val,
+ self = jQuery(this);
+
+ if ( this.nodeType !== 1 ) {
+ return;
+ }
+
+ if ( isFunction ) {
+ val = value.call( this, i, self.val() );
+ } else {
+ val = value;
+ }
+
+ // Treat null/undefined as ""; convert numbers to string
+ if ( val == null ) {
+ val = "";
+ } else if ( typeof val === "number" ) {
+ val += "";
+ } else if ( jQuery.isArray( val ) ) {
+ val = jQuery.map(val, function ( value ) {
+ return value == null ? "" : value + "";
+ });
+ }
+
+ hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
+
+ // If set returns undefined, fall back to normal setting
+ if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
+ this.value = val;
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ valHooks: {
+ option: {
+ get: function( elem ) {
+ // attributes.value is undefined in Blackberry 4.7 but
+ // uses .value. See #6932
+ var val = elem.attributes.value;
+ return !val || val.specified ? elem.value : elem.text;
+ }
+ },
+ select: {
+ get: function( elem ) {
+ var value, i, max, option,
+ index = elem.selectedIndex,
+ values = [],
+ options = elem.options,
+ one = elem.type === "select-one";
+
+ // Nothing was selected
+ if ( index < 0 ) {
+ return null;
+ }
+
+ // Loop through all the selected options
+ i = one ? index : 0;
+ max = one ? index + 1 : options.length;
+ for ( ; i < max; i++ ) {
+ option = options[ i ];
+
+ // Don't return options that are disabled or in a disabled optgroup
+ if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) &&
+ (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) {
+
+ // Get the specific value for the option
+ value = jQuery( option ).val();
+
+ // We don't need an array for one selects
+ if ( one ) {
+ return value;
+ }
+
+ // Multi-Selects return an array
+ values.push( value );
+ }
+ }
+
+ // Fixes Bug #2551 -- select.val() broken in IE after form.reset()
+ if ( one && !values.length && options.length ) {
+ return jQuery( options[ index ] ).val();
+ }
+
+ return values;
+ },
+
+ set: function( elem, value ) {
+ var values = jQuery.makeArray( value );
+
+ jQuery(elem).find("option").each(function() {
+ this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0;
+ });
+
+ if ( !values.length ) {
+ elem.selectedIndex = -1;
+ }
+ return values;
+ }
+ }
+ },
+
+ // Unused in 1.8, left in so attrFn-stabbers won't die; remove in 1.9
+ attrFn: {},
+
+ attr: function( elem, name, value, pass ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set attributes on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ if ( pass && jQuery.isFunction( jQuery.fn[ name ] ) ) {
+ return jQuery( elem )[ name ]( value );
+ }
+
+ // Fallback to prop when attributes are not supported
+ if ( typeof elem.getAttribute === "undefined" ) {
+ return jQuery.prop( elem, name, value );
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ // All attributes are lowercase
+ // Grab necessary hook if one is defined
+ if ( notxml ) {
+ name = name.toLowerCase();
+ hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook );
+ }
+
+ if ( value !== undefined ) {
+
+ if ( value === null ) {
+ jQuery.removeAttr( elem, name );
+ return;
+
+ } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ elem.setAttribute( name, value + "" );
+ return value;
+ }
+
+ } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+
+ ret = elem.getAttribute( name );
+
+ // Non-existent attributes return null, we normalize to undefined
+ return ret === null ?
+ undefined :
+ ret;
+ }
+ },
+
+ removeAttr: function( elem, value ) {
+ var propName, attrNames, name, isBool,
+ i = 0;
+
+ if ( value && elem.nodeType === 1 ) {
+
+ attrNames = value.split( core_rspace );
+
+ for ( ; i < attrNames.length; i++ ) {
+ name = attrNames[ i ];
+
+ if ( name ) {
+ propName = jQuery.propFix[ name ] || name;
+ isBool = rboolean.test( name );
+
+ // See #9699 for explanation of this approach (setting first, then removal)
+ // Do not do this for boolean attributes (see #10870)
+ if ( !isBool ) {
+ jQuery.attr( elem, name, "" );
+ }
+ elem.removeAttribute( getSetAttribute ? name : propName );
+
+ // Set corresponding property to false for boolean attributes
+ if ( isBool && propName in elem ) {
+ elem[ propName ] = false;
+ }
+ }
+ }
+ }
+ },
+
+ attrHooks: {
+ type: {
+ set: function( elem, value ) {
+ // We can't allow the type property to be changed (since it causes problems in IE)
+ if ( rtype.test( elem.nodeName ) && elem.parentNode ) {
+ jQuery.error( "type property can't be changed" );
+ } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
+ // Setting the type on a radio button after the value resets the value in IE6-9
+ // Reset value to it's default in case type is set after value
+ // This is for element creation
+ var val = elem.value;
+ elem.setAttribute( "type", value );
+ if ( val ) {
+ elem.value = val;
+ }
+ return value;
+ }
+ }
+ },
+ // Use the value property for back compat
+ // Use the nodeHook for button elements in IE6/7 (#1954)
+ value: {
+ get: function( elem, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.get( elem, name );
+ }
+ return name in elem ?
+ elem.value :
+ null;
+ },
+ set: function( elem, value, name ) {
+ if ( nodeHook && jQuery.nodeName( elem, "button" ) ) {
+ return nodeHook.set( elem, value, name );
+ }
+ // Does not return so that setAttribute is also used
+ elem.value = value;
+ }
+ }
+ },
+
+ propFix: {
+ tabindex: "tabIndex",
+ readonly: "readOnly",
+ "for": "htmlFor",
+ "class": "className",
+ maxlength: "maxLength",
+ cellspacing: "cellSpacing",
+ cellpadding: "cellPadding",
+ rowspan: "rowSpan",
+ colspan: "colSpan",
+ usemap: "useMap",
+ frameborder: "frameBorder",
+ contenteditable: "contentEditable"
+ },
+
+ prop: function( elem, name, value ) {
+ var ret, hooks, notxml,
+ nType = elem.nodeType;
+
+ // don't get/set properties on text, comment and attribute nodes
+ if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
+ return;
+ }
+
+ notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
+
+ if ( notxml ) {
+ // Fix name and attach hooks
+ name = jQuery.propFix[ name ] || name;
+ hooks = jQuery.propHooks[ name ];
+ }
+
+ if ( value !== undefined ) {
+ if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
+ return ret;
+
+ } else {
+ return ( elem[ name ] = value );
+ }
+
+ } else {
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
+ return ret;
+
+ } else {
+ return elem[ name ];
+ }
+ }
+ },
+
+ propHooks: {
+ tabIndex: {
+ get: function( elem ) {
+ // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
+ // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
+ var attributeNode = elem.getAttributeNode("tabindex");
+
+ return attributeNode && attributeNode.specified ?
+ parseInt( attributeNode.value, 10 ) :
+ rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
+ 0 :
+ undefined;
+ }
+ }
+ }
+});
+
+// Hook for boolean attributes
+boolHook = {
+ get: function( elem, name ) {
+ // Align boolean attributes with corresponding properties
+ // Fall back to attribute presence where some booleans are not supported
+ var attrNode,
+ property = jQuery.prop( elem, name );
+ return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
+ name.toLowerCase() :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ var propName;
+ if ( value === false ) {
+ // Remove boolean attributes when set to false
+ jQuery.removeAttr( elem, name );
+ } else {
+ // value is true since we know at this point it's type boolean and not false
+ // Set boolean attributes to the same name and set the DOM property
+ propName = jQuery.propFix[ name ] || name;
+ if ( propName in elem ) {
+ // Only set the IDL specifically if it already exists on the element
+ elem[ propName ] = true;
+ }
+
+ elem.setAttribute( name, name.toLowerCase() );
+ }
+ return name;
+ }
+};
+
+// IE6/7 do not support getting/setting some attributes with get/setAttribute
+if ( !getSetAttribute ) {
+
+ fixSpecified = {
+ name: true,
+ id: true,
+ coords: true
+ };
+
+ // Use this for any attribute in IE6/7
+ // This fixes almost every IE6/7 issue
+ nodeHook = jQuery.valHooks.button = {
+ get: function( elem, name ) {
+ var ret;
+ ret = elem.getAttributeNode( name );
+ return ret && ( fixSpecified[ name ] ? ret.value !== "" : ret.specified ) ?
+ ret.value :
+ undefined;
+ },
+ set: function( elem, value, name ) {
+ // Set the existing or create a new attribute node
+ var ret = elem.getAttributeNode( name );
+ if ( !ret ) {
+ ret = document.createAttribute( name );
+ elem.setAttributeNode( ret );
+ }
+ return ( ret.value = value + "" );
+ }
+ };
+
+ // Set width and height to auto instead of 0 on empty string( Bug #8150 )
+ // This is for removals
+ jQuery.each([ "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ set: function( elem, value ) {
+ if ( value === "" ) {
+ elem.setAttribute( name, "auto" );
+ return value;
+ }
+ }
+ });
+ });
+
+ // Set contenteditable to false on removals(#10429)
+ // Setting to empty string throws an error as an invalid value
+ jQuery.attrHooks.contenteditable = {
+ get: nodeHook.get,
+ set: function( elem, value, name ) {
+ if ( value === "" ) {
+ value = "false";
+ }
+ nodeHook.set( elem, value, name );
+ }
+ };
+}
+
+
+// Some attributes require a special call on IE
+if ( !jQuery.support.hrefNormalized ) {
+ jQuery.each([ "href", "src", "width", "height" ], function( i, name ) {
+ jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], {
+ get: function( elem ) {
+ var ret = elem.getAttribute( name, 2 );
+ return ret === null ? undefined : ret;
+ }
+ });
+ });
+}
+
+if ( !jQuery.support.style ) {
+ jQuery.attrHooks.style = {
+ get: function( elem ) {
+ // Return undefined in the case of empty string
+ // Normalize to lowercase since IE uppercases css property names
+ return elem.style.cssText.toLowerCase() || undefined;
+ },
+ set: function( elem, value ) {
+ return ( elem.style.cssText = value + "" );
+ }
+ };
+}
+
+// Safari mis-reports the default selected property of an option
+// Accessing the parent's selectedIndex property fixes it
+if ( !jQuery.support.optSelected ) {
+ jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, {
+ get: function( elem ) {
+ var parent = elem.parentNode;
+
+ if ( parent ) {
+ parent.selectedIndex;
+
+ // Make sure that it also works with optgroups, see #5701
+ if ( parent.parentNode ) {
+ parent.parentNode.selectedIndex;
+ }
+ }
+ return null;
+ }
+ });
+}
+
+// IE6/7 call enctype encoding
+if ( !jQuery.support.enctype ) {
+ jQuery.propFix.enctype = "encoding";
+}
+
+// Radios and checkboxes getter/setter
+if ( !jQuery.support.checkOn ) {
+ jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = {
+ get: function( elem ) {
+ // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
+ return elem.getAttribute("value") === null ? "on" : elem.value;
+ }
+ };
+ });
+}
+jQuery.each([ "radio", "checkbox" ], function() {
+ jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], {
+ set: function( elem, value ) {
+ if ( jQuery.isArray( value ) ) {
+ return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
+ }
+ }
+ });
+});
+var rformElems = /^(?:textarea|input|select)$/i,
+ rtypenamespace = /^([^\.]*|)(?:\.(.+)|)$/,
+ rhoverHack = /(?:^|\s)hover(\.\S+|)\b/,
+ rkeyEvent = /^key/,
+ rmouseEvent = /^(?:mouse|contextmenu)|click/,
+ rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
+ hoverHack = function( events ) {
+ return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
+ };
+
+/*
+ * Helper functions for managing events -- not part of the public interface.
+ * Props to Dean Edwards' addEvent library for many of the ideas.
+ */
+jQuery.event = {
+
+ add: function( elem, types, handler, data, selector ) {
+
+ var elemData, eventHandle, events,
+ t, tns, type, namespaces, handleObj,
+ handleObjIn, handlers, special;
+
+ // Don't attach events to noData or text/comment nodes (allow plain objects tho)
+ if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) {
+ return;
+ }
+
+ // Caller can pass in an object of custom data in lieu of the handler
+ if ( handler.handler ) {
+ handleObjIn = handler;
+ handler = handleObjIn.handler;
+ selector = handleObjIn.selector;
+ }
+
+ // Make sure that the handler has a unique ID, used to find/remove it later
+ if ( !handler.guid ) {
+ handler.guid = jQuery.guid++;
+ }
+
+ // Init the element's event structure and main handler, if this is the first
+ events = elemData.events;
+ if ( !events ) {
+ elemData.events = events = {};
+ }
+ eventHandle = elemData.handle;
+ if ( !eventHandle ) {
+ elemData.handle = eventHandle = function( e ) {
+ // Discard the second event of a jQuery.event.trigger() and
+ // when an event is called after a page has unloaded
+ return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ?
+ jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
+ undefined;
+ };
+ // Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
+ eventHandle.elem = elem;
+ }
+
+ // Handle multiple events separated by a space
+ // jQuery(...).bind("mouseover mouseout", fn);
+ types = jQuery.trim( hoverHack(types) ).split( " " );
+ for ( t = 0; t < types.length; t++ ) {
+
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = tns[1];
+ namespaces = ( tns[2] || "" ).split( "." ).sort();
+
+ // If event changes its type, use the special event handlers for the changed type
+ special = jQuery.event.special[ type ] || {};
+
+ // If selector defined, determine special event api type, otherwise given type
+ type = ( selector ? special.delegateType : special.bindType ) || type;
+
+ // Update special based on newly reset type
+ special = jQuery.event.special[ type ] || {};
+
+ // handleObj is passed to all event handlers
+ handleObj = jQuery.extend({
+ type: type,
+ origType: tns[1],
+ data: data,
+ handler: handler,
+ guid: handler.guid,
+ selector: selector,
+ needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
+ namespace: namespaces.join(".")
+ }, handleObjIn );
+
+ // Init the event handler queue if we're the first
+ handlers = events[ type ];
+ if ( !handlers ) {
+ handlers = events[ type ] = [];
+ handlers.delegateCount = 0;
+
+ // Only use addEventListener/attachEvent if the special events handler returns false
+ if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
+ // Bind the global event handler to the element
+ if ( elem.addEventListener ) {
+ elem.addEventListener( type, eventHandle, false );
+
+ } else if ( elem.attachEvent ) {
+ elem.attachEvent( "on" + type, eventHandle );
+ }
+ }
+ }
+
+ if ( special.add ) {
+ special.add.call( elem, handleObj );
+
+ if ( !handleObj.handler.guid ) {
+ handleObj.handler.guid = handler.guid;
+ }
+ }
+
+ // Add to the element's handler list, delegates in front
+ if ( selector ) {
+ handlers.splice( handlers.delegateCount++, 0, handleObj );
+ } else {
+ handlers.push( handleObj );
+ }
+
+ // Keep track of which events have ever been used, for event optimization
+ jQuery.event.global[ type ] = true;
+ }
+
+ // Nullify elem to prevent memory leaks in IE
+ elem = null;
+ },
+
+ global: {},
+
+ // Detach an event or set of events from an element
+ remove: function( elem, types, handler, selector, mappedTypes ) {
+
+ var t, tns, type, origType, namespaces, origCount,
+ j, events, special, eventType, handleObj,
+ elemData = jQuery.hasData( elem ) && jQuery._data( elem );
+
+ if ( !elemData || !(events = elemData.events) ) {
+ return;
+ }
+
+ // Once for each type.namespace in types; type may be omitted
+ types = jQuery.trim( hoverHack( types || "" ) ).split(" ");
+ for ( t = 0; t < types.length; t++ ) {
+ tns = rtypenamespace.exec( types[t] ) || [];
+ type = origType = tns[1];
+ namespaces = tns[2];
+
+ // Unbind all events (on this namespace, if provided) for the element
+ if ( !type ) {
+ for ( type in events ) {
+ jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
+ }
+ continue;
+ }
+
+ special = jQuery.event.special[ type ] || {};
+ type = ( selector? special.delegateType : special.bindType ) || type;
+ eventType = events[ type ] || [];
+ origCount = eventType.length;
+ namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
+
+ // Remove matching events
+ for ( j = 0; j < eventType.length; j++ ) {
+ handleObj = eventType[ j ];
+
+ if ( ( mappedTypes || origType === handleObj.origType ) &&
+ ( !handler || handler.guid === handleObj.guid ) &&
+ ( !namespaces || namespaces.test( handleObj.namespace ) ) &&
+ ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
+ eventType.splice( j--, 1 );
+
+ if ( handleObj.selector ) {
+ eventType.delegateCount--;
+ }
+ if ( special.remove ) {
+ special.remove.call( elem, handleObj );
+ }
+ }
+ }
+
+ // Remove generic event handler if we removed something and no more handlers exist
+ // (avoids potential for endless recursion during removal of special event handlers)
+ if ( eventType.length === 0 && origCount !== eventType.length ) {
+ if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
+ jQuery.removeEvent( elem, type, elemData.handle );
+ }
+
+ delete events[ type ];
+ }
+ }
+
+ // Remove the expando if it's no longer used
+ if ( jQuery.isEmptyObject( events ) ) {
+ delete elemData.handle;
+
+ // removeData also checks for emptiness and clears the expando if empty
+ // so use it instead of delete
+ jQuery.removeData( elem, "events", true );
+ }
+ },
+
+ // Events that are safe to short-circuit if no handlers are attached.
+ // Native DOM events should not be added, they may have inline handlers.
+ customEvent: {
+ "getData": true,
+ "setData": true,
+ "changeData": true
+ },
+
+ trigger: function( event, data, elem, onlyHandlers ) {
+ // Don't do events on text and comment nodes
+ if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) {
+ return;
+ }
+
+ // Event object or event type
+ var cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType,
+ type = event.type || event,
+ namespaces = [];
+
+ // focus/blur morphs to focusin/out; ensure we're not firing them right now
+ if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
+ return;
+ }
+
+ if ( type.indexOf( "!" ) >= 0 ) {
+ // Exclusive events trigger only for the exact event (no namespaces)
+ type = type.slice(0, -1);
+ exclusive = true;
+ }
+
+ if ( type.indexOf( "." ) >= 0 ) {
+ // Namespaced trigger; create a regexp to match event type in handle()
+ namespaces = type.split(".");
+ type = namespaces.shift();
+ namespaces.sort();
+ }
+
+ if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) {
+ // No jQuery handlers for this event type, and it can't have inline handlers
+ return;
+ }
+
+ // Caller can pass in an Event, Object, or just an event type string
+ event = typeof event === "object" ?
+ // jQuery.Event object
+ event[ jQuery.expando ] ? event :
+ // Object literal
+ new jQuery.Event( type, event ) :
+ // Just the event type (string)
+ new jQuery.Event( type );
+
+ event.type = type;
+ event.isTrigger = true;
+ event.exclusive = exclusive;
+ event.namespace = namespaces.join( "." );
+ event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)") : null;
+ ontype = type.indexOf( ":" ) < 0 ? "on" + type : "";
+
+ // Handle a global trigger
+ if ( !elem ) {
+
+ // TODO: Stop taunting the data cache; remove global events and always attach to document
+ cache = jQuery.cache;
+ for ( i in cache ) {
+ if ( cache[ i ].events && cache[ i ].events[ type ] ) {
+ jQuery.event.trigger( event, data, cache[ i ].handle.elem, true );
+ }
+ }
+ return;
+ }
+
+ // Clean up the event in case it is being reused
+ event.result = undefined;
+ if ( !event.target ) {
+ event.target = elem;
+ }
+
+ // Clone any incoming data and prepend the event, creating the handler arg list
+ data = data != null ? jQuery.makeArray( data ) : [];
+ data.unshift( event );
+
+ // Allow special events to draw outside the lines
+ special = jQuery.event.special[ type ] || {};
+ if ( special.trigger && special.trigger.apply( elem, data ) === false ) {
+ return;
+ }
+
+ // Determine event propagation path in advance, per W3C events spec (#9951)
+ // Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
+ eventPath = [[ elem, special.bindType || type ]];
+ if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
+
+ bubbleType = special.delegateType || type;
+ cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode;
+ for ( old = elem; cur; cur = cur.parentNode ) {
+ eventPath.push([ cur, bubbleType ]);
+ old = cur;
+ }
+
+ // Only add window if we got to document (e.g., not plain obj or detached DOM)
+ if ( old === (elem.ownerDocument || document) ) {
+ eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]);
+ }
+ }
+
+ // Fire handlers on the event path
+ for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) {
+
+ cur = eventPath[i][0];
+ event.type = eventPath[i][1];
+
+ handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
+ if ( handle ) {
+ handle.apply( cur, data );
+ }
+ // Note that this is a bare JS function and not a jQuery handler
+ handle = ontype && cur[ ontype ];
+ if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
+ event.preventDefault();
+ }
+ }
+ event.type = type;
+
+ // If nobody prevented the default action, do it now
+ if ( !onlyHandlers && !event.isDefaultPrevented() ) {
+
+ if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) &&
+ !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) {
+
+ // Call a native DOM method on the target with the same name name as the event.
+ // Can't use an .isFunction() check here because IE6/7 fails that test.
+ // Don't do default actions on window, that's where global variables be (#6170)
+ // IE<9 dies on focus/blur to hidden element (#1486)
+ if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) {
+
+ // Don't re-trigger an onFOO event when we call its FOO() method
+ old = elem[ ontype ];
+
+ if ( old ) {
+ elem[ ontype ] = null;
+ }
+
+ // Prevent re-triggering of the same event, since we already bubbled it above
+ jQuery.event.triggered = type;
+ elem[ type ]();
+ jQuery.event.triggered = undefined;
+
+ if ( old ) {
+ elem[ ontype ] = old;
+ }
+ }
+ }
+ }
+
+ return event.result;
+ },
+
+ dispatch: function( event ) {
+
+ // Make a writable jQuery.Event from the native event object
+ event = jQuery.event.fix( event || window.event );
+
+ var i, j, cur, ret, selMatch, matched, matches, handleObj, sel, related,
+ handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []),
+ delegateCount = handlers.delegateCount,
+ args = core_slice.call( arguments ),
+ run_all = !event.exclusive && !event.namespace,
+ special = jQuery.event.special[ event.type ] || {},
+ handlerQueue = [];
+
+ // Use the fix-ed jQuery.Event rather than the (read-only) native event
+ args[0] = event;
+ event.delegateTarget = this;
+
+ // Call the preDispatch hook for the mapped type, and let it bail if desired
+ if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
+ return;
+ }
+
+ // Determine handlers that should run if there are delegated events
+ // Avoid non-left-click bubbling in Firefox (#3861)
+ if ( delegateCount && !(event.button && event.type === "click") ) {
+
+ for ( cur = event.target; cur != this; cur = cur.parentNode || this ) {
+
+ // Don't process clicks (ONLY) on disabled elements (#6911, #8165, #11382, #11764)
+ if ( cur.disabled !== true || event.type !== "click" ) {
+ selMatch = {};
+ matches = [];
+ for ( i = 0; i < delegateCount; i++ ) {
+ handleObj = handlers[ i ];
+ sel = handleObj.selector;
+
+ if ( selMatch[ sel ] === undefined ) {
+ selMatch[ sel ] = handleObj.needsContext ?
+ jQuery( sel, this ).index( cur ) >= 0 :
+ jQuery.find( sel, this, null, [ cur ] ).length;
+ }
+ if ( selMatch[ sel ] ) {
+ matches.push( handleObj );
+ }
+ }
+ if ( matches.length ) {
+ handlerQueue.push({ elem: cur, matches: matches });
+ }
+ }
+ }
+ }
+
+ // Add the remaining (directly-bound) handlers
+ if ( handlers.length > delegateCount ) {
+ handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) });
+ }
+
+ // Run delegates first; they may want to stop propagation beneath us
+ for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) {
+ matched = handlerQueue[ i ];
+ event.currentTarget = matched.elem;
+
+ for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) {
+ handleObj = matched.matches[ j ];
+
+ // Triggered event must either 1) be non-exclusive and have no namespace, or
+ // 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
+ if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) {
+
+ event.data = handleObj.data;
+ event.handleObj = handleObj;
+
+ ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
+ .apply( matched.elem, args );
+
+ if ( ret !== undefined ) {
+ event.result = ret;
+ if ( ret === false ) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ }
+ }
+ }
+ }
+
+ // Call the postDispatch hook for the mapped type
+ if ( special.postDispatch ) {
+ special.postDispatch.call( this, event );
+ }
+
+ return event.result;
+ },
+
+ // Includes some event props shared by KeyEvent and MouseEvent
+ // *** attrChange attrName relatedNode srcElement are not normalized, non-W3C, deprecated, will be removed in 1.8 ***
+ props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
+
+ fixHooks: {},
+
+ keyHooks: {
+ props: "char charCode key keyCode".split(" "),
+ filter: function( event, original ) {
+
+ // Add which for key events
+ if ( event.which == null ) {
+ event.which = original.charCode != null ? original.charCode : original.keyCode;
+ }
+
+ return event;
+ }
+ },
+
+ mouseHooks: {
+ props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
+ filter: function( event, original ) {
+ var eventDoc, doc, body,
+ button = original.button,
+ fromElement = original.fromElement;
+
+ // Calculate pageX/Y if missing and clientX/Y available
+ if ( event.pageX == null && original.clientX != null ) {
+ eventDoc = event.target.ownerDocument || document;
+ doc = eventDoc.documentElement;
+ body = eventDoc.body;
+
+ event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
+ event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 );
+ }
+
+ // Add relatedTarget, if necessary
+ if ( !event.relatedTarget && fromElement ) {
+ event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
+ }
+
+ // Add which for click: 1 === left; 2 === middle; 3 === right
+ // Note: button is not normalized, so don't use it
+ if ( !event.which && button !== undefined ) {
+ event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
+ }
+
+ return event;
+ }
+ },
+
+ fix: function( event ) {
+ if ( event[ jQuery.expando ] ) {
+ return event;
+ }
+
+ // Create a writable copy of the event object and normalize some properties
+ var i, prop,
+ originalEvent = event,
+ fixHook = jQuery.event.fixHooks[ event.type ] || {},
+ copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
+
+ event = jQuery.Event( originalEvent );
+
+ for ( i = copy.length; i; ) {
+ prop = copy[ --i ];
+ event[ prop ] = originalEvent[ prop ];
+ }
+
+ // Fix target property, if necessary (#1925, IE 6/7/8 & Safari2)
+ if ( !event.target ) {
+ event.target = originalEvent.srcElement || document;
+ }
+
+ // Target should not be a text node (#504, Safari)
+ if ( event.target.nodeType === 3 ) {
+ event.target = event.target.parentNode;
+ }
+
+ // For mouse/key events, metaKey==false if it's undefined (#3368, #11328; IE6/7/8)
+ event.metaKey = !!event.metaKey;
+
+ return fixHook.filter? fixHook.filter( event, originalEvent ) : event;
+ },
+
+ special: {
+ load: {
+ // Prevent triggered image.load events from bubbling to window.load
+ noBubble: true
+ },
+
+ focus: {
+ delegateType: "focusin"
+ },
+ blur: {
+ delegateType: "focusout"
+ },
+
+ beforeunload: {
+ setup: function( data, namespaces, eventHandle ) {
+ // We only want to do this special case on windows
+ if ( jQuery.isWindow( this ) ) {
+ this.onbeforeunload = eventHandle;
+ }
+ },
+
+ teardown: function( namespaces, eventHandle ) {
+ if ( this.onbeforeunload === eventHandle ) {
+ this.onbeforeunload = null;
+ }
+ }
+ }
+ },
+
+ simulate: function( type, elem, event, bubble ) {
+ // Piggyback on a donor event to simulate a different one.
+ // Fake originalEvent to avoid donor's stopPropagation, but if the
+ // simulated event prevents default then we do the same on the donor.
+ var e = jQuery.extend(
+ new jQuery.Event(),
+ event,
+ { type: type,
+ isSimulated: true,
+ originalEvent: {}
+ }
+ );
+ if ( bubble ) {
+ jQuery.event.trigger( e, null, elem );
+ } else {
+ jQuery.event.dispatch.call( elem, e );
+ }
+ if ( e.isDefaultPrevented() ) {
+ event.preventDefault();
+ }
+ }
+};
+
+// Some plugins are using, but it's undocumented/deprecated and will be removed.
+// The 1.7 special event interface should provide all the hooks needed now.
+jQuery.event.handle = jQuery.event.dispatch;
+
+jQuery.removeEvent = document.removeEventListener ?
+ function( elem, type, handle ) {
+ if ( elem.removeEventListener ) {
+ elem.removeEventListener( type, handle, false );
+ }
+ } :
+ function( elem, type, handle ) {
+ var name = "on" + type;
+
+ if ( elem.detachEvent ) {
+
+ // #8545, #7054, preventing memory leaks for custom events in IE6-8 –
+ // detachEvent needed property on element, by name of that event, to properly expose it to GC
+ if ( typeof elem[ name ] === "undefined" ) {
+ elem[ name ] = null;
+ }
+
+ elem.detachEvent( name, handle );
+ }
+ };
+
+jQuery.Event = function( src, props ) {
+ // Allow instantiation without the 'new' keyword
+ if ( !(this instanceof jQuery.Event) ) {
+ return new jQuery.Event( src, props );
+ }
+
+ // Event object
+ if ( src && src.type ) {
+ this.originalEvent = src;
+ this.type = src.type;
+
+ // Events bubbling up the document may have been marked as prevented
+ // by a handler lower down the tree; reflect the correct value.
+ this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
+ src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
+
+ // Event type
+ } else {
+ this.type = src;
+ }
+
+ // Put explicitly provided properties onto the event object
+ if ( props ) {
+ jQuery.extend( this, props );
+ }
+
+ // Create a timestamp if incoming event doesn't have one
+ this.timeStamp = src && src.timeStamp || jQuery.now();
+
+ // Mark it as fixed
+ this[ jQuery.expando ] = true;
+};
+
+function returnFalse() {
+ return false;
+}
+function returnTrue() {
+ return true;
+}
+
+// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
+// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
+jQuery.Event.prototype = {
+ preventDefault: function() {
+ this.isDefaultPrevented = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+
+ // if preventDefault exists run it on the original event
+ if ( e.preventDefault ) {
+ e.preventDefault();
+
+ // otherwise set the returnValue property of the original event to false (IE)
+ } else {
+ e.returnValue = false;
+ }
+ },
+ stopPropagation: function() {
+ this.isPropagationStopped = returnTrue;
+
+ var e = this.originalEvent;
+ if ( !e ) {
+ return;
+ }
+ // if stopPropagation exists run it on the original event
+ if ( e.stopPropagation ) {
+ e.stopPropagation();
+ }
+ // otherwise set the cancelBubble property of the original event to true (IE)
+ e.cancelBubble = true;
+ },
+ stopImmediatePropagation: function() {
+ this.isImmediatePropagationStopped = returnTrue;
+ this.stopPropagation();
+ },
+ isDefaultPrevented: returnFalse,
+ isPropagationStopped: returnFalse,
+ isImmediatePropagationStopped: returnFalse
+};
+
+// Create mouseenter/leave events using mouseover/out and event-time checks
+jQuery.each({
+ mouseenter: "mouseover",
+ mouseleave: "mouseout"
+}, function( orig, fix ) {
+ jQuery.event.special[ orig ] = {
+ delegateType: fix,
+ bindType: fix,
+
+ handle: function( event ) {
+ var ret,
+ target = this,
+ related = event.relatedTarget,
+ handleObj = event.handleObj,
+ selector = handleObj.selector;
+
+ // For mousenter/leave call the handler if related is outside the target.
+ // NB: No relatedTarget if the mouse left/entered the browser window
+ if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
+ event.type = handleObj.origType;
+ ret = handleObj.handler.apply( this, arguments );
+ event.type = fix;
+ }
+ return ret;
+ }
+ };
+});
+
+// IE submit delegation
+if ( !jQuery.support.submitBubbles ) {
+
+ jQuery.event.special.submit = {
+ setup: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Lazy-add a submit handler when a descendant form may potentially be submitted
+ jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
+ // Node name check avoids a VML-related crash in IE (#9807)
+ var elem = e.target,
+ form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
+ if ( form && !jQuery._data( form, "_submit_attached" ) ) {
+ jQuery.event.add( form, "submit._submit", function( event ) {
+ event._submit_bubble = true;
+ });
+ jQuery._data( form, "_submit_attached", true );
+ }
+ });
+ // return undefined since we don't need an event listener
+ },
+
+ postDispatch: function( event ) {
+ // If form was submitted by the user, bubble the event up the tree
+ if ( event._submit_bubble ) {
+ delete event._submit_bubble;
+ if ( this.parentNode && !event.isTrigger ) {
+ jQuery.event.simulate( "submit", this.parentNode, event, true );
+ }
+ }
+ },
+
+ teardown: function() {
+ // Only need this for delegated form submit events
+ if ( jQuery.nodeName( this, "form" ) ) {
+ return false;
+ }
+
+ // Remove delegated handlers; cleanData eventually reaps submit handlers attached above
+ jQuery.event.remove( this, "._submit" );
+ }
+ };
+}
+
+// IE change delegation and checkbox/radio fix
+if ( !jQuery.support.changeBubbles ) {
+
+ jQuery.event.special.change = {
+
+ setup: function() {
+
+ if ( rformElems.test( this.nodeName ) ) {
+ // IE doesn't fire change on a check/radio until blur; trigger it on click
+ // after a propertychange. Eat the blur-change in special.change.handle.
+ // This still fires onchange a second time for check/radio after blur.
+ if ( this.type === "checkbox" || this.type === "radio" ) {
+ jQuery.event.add( this, "propertychange._change", function( event ) {
+ if ( event.originalEvent.propertyName === "checked" ) {
+ this._just_changed = true;
+ }
+ });
+ jQuery.event.add( this, "click._change", function( event ) {
+ if ( this._just_changed && !event.isTrigger ) {
+ this._just_changed = false;
+ }
+ // Allow triggered, simulated change events (#11500)
+ jQuery.event.simulate( "change", this, event, true );
+ });
+ }
+ return false;
+ }
+ // Delegated event; lazy-add a change handler on descendant inputs
+ jQuery.event.add( this, "beforeactivate._change", function( e ) {
+ var elem = e.target;
+
+ if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "_change_attached" ) ) {
+ jQuery.event.add( elem, "change._change", function( event ) {
+ if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
+ jQuery.event.simulate( "change", this.parentNode, event, true );
+ }
+ });
+ jQuery._data( elem, "_change_attached", true );
+ }
+ });
+ },
+
+ handle: function( event ) {
+ var elem = event.target;
+
+ // Swallow native change events from checkbox/radio, we already triggered them above
+ if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
+ return event.handleObj.handler.apply( this, arguments );
+ }
+ },
+
+ teardown: function() {
+ jQuery.event.remove( this, "._change" );
+
+ return !rformElems.test( this.nodeName );
+ }
+ };
+}
+
+// Create "bubbling" focus and blur events
+if ( !jQuery.support.focusinBubbles ) {
+ jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
+
+ // Attach a single capturing handler while someone wants focusin/focusout
+ var attaches = 0,
+ handler = function( event ) {
+ jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
+ };
+
+ jQuery.event.special[ fix ] = {
+ setup: function() {
+ if ( attaches++ === 0 ) {
+ document.addEventListener( orig, handler, true );
+ }
+ },
+ teardown: function() {
+ if ( --attaches === 0 ) {
+ document.removeEventListener( orig, handler, true );
+ }
+ }
+ };
+ });
+}
+
+jQuery.fn.extend({
+
+ on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
+ var origFn, type;
+
+ // Types can be a map of types/handlers
+ if ( typeof types === "object" ) {
+ // ( types-Object, selector, data )
+ if ( typeof selector !== "string" ) { // && selector != null
+ // ( types-Object, data )
+ data = data || selector;
+ selector = undefined;
+ }
+ for ( type in types ) {
+ this.on( type, selector, data, types[ type ], one );
+ }
+ return this;
+ }
+
+ if ( data == null && fn == null ) {
+ // ( types, fn )
+ fn = selector;
+ data = selector = undefined;
+ } else if ( fn == null ) {
+ if ( typeof selector === "string" ) {
+ // ( types, selector, fn )
+ fn = data;
+ data = undefined;
+ } else {
+ // ( types, data, fn )
+ fn = data;
+ data = selector;
+ selector = undefined;
+ }
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ } else if ( !fn ) {
+ return this;
+ }
+
+ if ( one === 1 ) {
+ origFn = fn;
+ fn = function( event ) {
+ // Can use an empty set, since event contains the info
+ jQuery().off( event );
+ return origFn.apply( this, arguments );
+ };
+ // Use same guid so caller can remove using origFn
+ fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
+ }
+ return this.each( function() {
+ jQuery.event.add( this, types, fn, data, selector );
+ });
+ },
+ one: function( types, selector, data, fn ) {
+ return this.on( types, selector, data, fn, 1 );
+ },
+ off: function( types, selector, fn ) {
+ var handleObj, type;
+ if ( types && types.preventDefault && types.handleObj ) {
+ // ( event ) dispatched jQuery.Event
+ handleObj = types.handleObj;
+ jQuery( types.delegateTarget ).off(
+ handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
+ handleObj.selector,
+ handleObj.handler
+ );
+ return this;
+ }
+ if ( typeof types === "object" ) {
+ // ( types-object [, selector] )
+ for ( type in types ) {
+ this.off( type, selector, types[ type ] );
+ }
+ return this;
+ }
+ if ( selector === false || typeof selector === "function" ) {
+ // ( types [, fn] )
+ fn = selector;
+ selector = undefined;
+ }
+ if ( fn === false ) {
+ fn = returnFalse;
+ }
+ return this.each(function() {
+ jQuery.event.remove( this, types, fn, selector );
+ });
+ },
+
+ bind: function( types, data, fn ) {
+ return this.on( types, null, data, fn );
+ },
+ unbind: function( types, fn ) {
+ return this.off( types, null, fn );
+ },
+
+ live: function( types, data, fn ) {
+ jQuery( this.context ).on( types, this.selector, data, fn );
+ return this;
+ },
+ die: function( types, fn ) {
+ jQuery( this.context ).off( types, this.selector || "**", fn );
+ return this;
+ },
+
+ delegate: function( selector, types, data, fn ) {
+ return this.on( types, selector, data, fn );
+ },
+ undelegate: function( selector, types, fn ) {
+ // ( namespace ) or ( selector, types [, fn] )
+ return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
+ },
+
+ trigger: function( type, data ) {
+ return this.each(function() {
+ jQuery.event.trigger( type, data, this );
+ });
+ },
+ triggerHandler: function( type, data ) {
+ if ( this[0] ) {
+ return jQuery.event.trigger( type, data, this[0], true );
+ }
+ },
+
+ toggle: function( fn ) {
+ // Save reference to arguments for access in closure
+ var args = arguments,
+ guid = fn.guid || jQuery.guid++,
+ i = 0,
+ toggler = function( event ) {
+ // Figure out which function to execute
+ var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
+ jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
+
+ // Make sure that clicks stop
+ event.preventDefault();
+
+ // and execute the function
+ return args[ lastToggle ].apply( this, arguments ) || false;
+ };
+
+ // link all the functions, so any of them can unbind this click handler
+ toggler.guid = guid;
+ while ( i < args.length ) {
+ args[ i++ ].guid = guid;
+ }
+
+ return this.click( toggler );
+ },
+
+ hover: function( fnOver, fnOut ) {
+ return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
+ }
+});
+
+jQuery.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( i, name ) {
+
+ // Handle event binding
+ jQuery.fn[ name ] = function( data, fn ) {
+ if ( fn == null ) {
+ fn = data;
+ data = null;
+ }
+
+ return arguments.length > 0 ?
+ this.on( name, null, data, fn ) :
+ this.trigger( name );
+ };
+
+ if ( rkeyEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks;
+ }
+
+ if ( rmouseEvent.test( name ) ) {
+ jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks;
+ }
+});
+/*!
+ * Sizzle CSS Selector Engine
+ * Copyright 2012 jQuery Foundation and other contributors
+ * Released under the MIT license
+ * http://sizzlejs.com/
+ */
+(function( window, undefined ) {
+
+var cachedruns,
+ assertGetIdNotName,
+ Expr,
+ getText,
+ isXML,
+ contains,
+ compile,
+ sortOrder,
+ hasDuplicate,
+ outermostContext,
+
+ baseHasDuplicate = true,
+ strundefined = "undefined",
+
+ expando = ( "sizcache" + Math.random() ).replace( ".", "" ),
+
+ Token = String,
+ document = window.document,
+ docElem = document.documentElement,
+ dirruns = 0,
+ done = 0,
+ pop = [].pop,
+ push = [].push,
+ slice = [].slice,
+ // Use a stripped-down indexOf if a native one is unavailable
+ indexOf = [].indexOf || function( elem ) {
+ var i = 0,
+ len = this.length;
+ for ( ; i < len; i++ ) {
+ if ( this[i] === elem ) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ // Augment a function for special use by Sizzle
+ markFunction = function( fn, value ) {
+ fn[ expando ] = value == null || value;
+ return fn;
+ },
+
+ createCache = function() {
+ var cache = {},
+ keys = [];
+
+ return markFunction(function( key, value ) {
+ // Only keep the most recent entries
+ if ( keys.push( key ) > Expr.cacheLength ) {
+ delete cache[ keys.shift() ];
+ }
+
+ return (cache[ key ] = value);
+ }, cache );
+ },
+
+ classCache = createCache(),
+ tokenCache = createCache(),
+ compilerCache = createCache(),
+
+ // Regex
+
+ // Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
+ whitespace = "[\\x20\\t\\r\\n\\f]",
+ // http://www.w3.org/TR/css3-syntax/#characters
+ characterEncoding = "(?:\\\\.|[-\\w]|[^\\x00-\\xa0])+",
+
+ // Loosely modeled on CSS identifier characters
+ // An unquoted value should be a CSS identifier (http://www.w3.org/TR/css3-selectors/#attribute-selectors)
+ // Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
+ identifier = characterEncoding.replace( "w", "w#" ),
+
+ // Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
+ operators = "([*^$|!~]?=)",
+ attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
+ "*(?:" + operators + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
+
+ // Prefer arguments not in parens/brackets,
+ // then attribute selectors and non-pseudos (denoted by :),
+ // then anything else
+ // These preferences are here to reduce the number of selectors
+ // needing tokenize in the PSEUDO preFilter
+ pseudos = ":(" + characterEncoding + ")(?:\\((?:(['\"])((?:\\\\.|[^\\\\])*?)\\2|([^()[\\]]*|(?:(?:" + attributes + ")|[^:]|\\\\.)*|.*))\\)|)",
+
+ // For matchExpr.POS and matchExpr.needsContext
+ pos = ":(even|odd|eq|gt|lt|nth|first|last)(?:\\(" + whitespace +
+ "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)",
+
+ // Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
+ rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
+
+ rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
+ rcombinators = new RegExp( "^" + whitespace + "*([\\x20\\t\\r\\n\\f>+~])" + whitespace + "*" ),
+ rpseudo = new RegExp( pseudos ),
+
+ // Easily-parseable/retrievable ID or TAG or CLASS selectors
+ rquickExpr = /^(?:#([\w\-]+)|(\w+)|\.([\w\-]+))$/,
+
+ rnot = /^:not/,
+ rsibling = /[\x20\t\r\n\f]*[+~]/,
+ rendsWithNot = /:not\($/,
+
+ rheader = /h\d/i,
+ rinputs = /input|select|textarea|button/i,
+
+ rbackslash = /\\(?!\\)/g,
+
+ matchExpr = {
+ "ID": new RegExp( "^#(" + characterEncoding + ")" ),
+ "CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
+ "NAME": new RegExp( "^\\[name=['\"]?(" + characterEncoding + ")['\"]?\\]" ),
+ "TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
+ "ATTR": new RegExp( "^" + attributes ),
+ "PSEUDO": new RegExp( "^" + pseudos ),
+ "POS": new RegExp( pos, "i" ),
+ "CHILD": new RegExp( "^:(only|nth|first|last)-child(?:\\(" + whitespace +
+ "*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
+ "*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
+ // For use in libraries implementing .is()
+ "needsContext": new RegExp( "^" + whitespace + "*[>+~]|" + pos, "i" )
+ },
+
+ // Support
+
+ // Used for testing something on an element
+ assert = function( fn ) {
+ var div = document.createElement("div");
+
+ try {
+ return fn( div );
+ } catch (e) {
+ return false;
+ } finally {
+ // release memory in IE
+ div = null;
+ }
+ },
+
+ // Check if getElementsByTagName("*") returns only elements
+ assertTagNameNoComments = assert(function( div ) {
+ div.appendChild( document.createComment("") );
+ return !div.getElementsByTagName("*").length;
+ }),
+
+ // Check if getAttribute returns normalized href attributes
+ assertHrefNotNormalized = assert(function( div ) {
+ div.innerHTML = "<a href='#'></a>";
+ return div.firstChild && typeof div.firstChild.getAttribute !== strundefined &&
+ div.firstChild.getAttribute("href") === "#";
+ }),
+
+ // Check if attributes should be retrieved by attribute nodes
+ assertAttributes = assert(function( div ) {
+ div.innerHTML = "<select></select>";
+ var type = typeof div.lastChild.getAttribute("multiple");
+ // IE8 returns a string for some attributes even when not present
+ return type !== "boolean" && type !== "string";
+ }),
+
+ // Check if getElementsByClassName can be trusted
+ assertUsableClassName = assert(function( div ) {
+ // Opera can't find a second classname (in 9.6)
+ div.innerHTML = "<div class='hidden e'></div><div class='hidden'></div>";
+ if ( !div.getElementsByClassName || !div.getElementsByClassName("e").length ) {
+ return false;
+ }
+
+ // Safari 3.2 caches class attributes and doesn't catch changes
+ div.lastChild.className = "e";
+ return div.getElementsByClassName("e").length === 2;
+ }),
+
+ // Check if getElementById returns elements by name
+ // Check if getElementsByName privileges form controls or returns elements by ID
+ assertUsableName = assert(function( div ) {
+ // Inject content
+ div.id = expando + 0;
+ div.innerHTML = "<a name='" + expando + "'></a><div name='" + expando + "'></div>";
+ docElem.insertBefore( div, docElem.firstChild );
+
+ // Test
+ var pass = document.getElementsByName &&
+ // buggy browsers will return fewer than the correct 2
+ document.getElementsByName( expando ).length === 2 +
+ // buggy browsers will return more than the correct 0
+ document.getElementsByName( expando + 0 ).length;
+ assertGetIdNotName = !document.getElementById( expando );
+
+ // Cleanup
+ docElem.removeChild( div );
+
+ return pass;
+ });
+
+// If slice is not available, provide a backup
+try {
+ slice.call( docElem.childNodes, 0 )[0].nodeType;
+} catch ( e ) {
+ slice = function( i ) {
+ var elem,
+ results = [];
+ for ( ; (elem = this[i]); i++ ) {
+ results.push( elem );
+ }
+ return results;
+ };
+}
+
+function Sizzle( selector, context, results, seed ) {
+ results = results || [];
+ context = context || document;
+ var match, elem, xml, m,
+ nodeType = context.nodeType;
+
+ if ( !selector || typeof selector !== "string" ) {
+ return results;
+ }
+
+ if ( nodeType !== 1 && nodeType !== 9 ) {
+ return [];
+ }
+
+ xml = isXML( context );
+
+ if ( !xml && !seed ) {
+ if ( (match = rquickExpr.exec( selector )) ) {
+ // Speed-up: Sizzle("#ID")
+ if ( (m = match[1]) ) {
+ if ( nodeType === 9 ) {
+ elem = context.getElementById( m );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ if ( elem && elem.parentNode ) {
+ // Handle the case where IE, Opera, and Webkit return items
+ // by name instead of ID
+ if ( elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ } else {
+ return results;
+ }
+ } else {
+ // Context is not a document
+ if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
+ contains( context, elem ) && elem.id === m ) {
+ results.push( elem );
+ return results;
+ }
+ }
+
+ // Speed-up: Sizzle("TAG")
+ } else if ( match[2] ) {
+ push.apply( results, slice.call(context.getElementsByTagName( selector ), 0) );
+ return results;
+
+ // Speed-up: Sizzle(".CLASS")
+ } else if ( (m = match[3]) && assertUsableClassName && context.getElementsByClassName ) {
+ push.apply( results, slice.call(context.getElementsByClassName( m ), 0) );
+ return results;
+ }
+ }
+ }
+
+ // All others
+ return select( selector.replace( rtrim, "$1" ), context, results, seed, xml );
+}
+
+Sizzle.matches = function( expr, elements ) {
+ return Sizzle( expr, null, null, elements );
+};
+
+Sizzle.matchesSelector = function( elem, expr ) {
+ return Sizzle( expr, null, null, [ elem ] ).length > 0;
+};
+
+// Returns a function to use in pseudos for input types
+function createInputPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === type;
+ };
+}
+
+// Returns a function to use in pseudos for buttons
+function createButtonPseudo( type ) {
+ return function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return (name === "input" || name === "button") && elem.type === type;
+ };
+}
+
+// Returns a function to use in pseudos for positionals
+function createPositionalPseudo( fn ) {
+ return markFunction(function( argument ) {
+ argument = +argument;
+ return markFunction(function( seed, matches ) {
+ var j,
+ matchIndexes = fn( [], seed.length, argument ),
+ i = matchIndexes.length;
+
+ // Match elements found at the specified indexes
+ while ( i-- ) {
+ if ( seed[ (j = matchIndexes[i]) ] ) {
+ seed[j] = !(matches[j] = seed[j]);
+ }
+ }
+ });
+ });
+}
+
+/**
+ * Utility function for retrieving the text value of an array of DOM nodes
+ * @param {Array|Element} elem
+ */
+getText = Sizzle.getText = function( elem ) {
+ var node,
+ ret = "",
+ i = 0,
+ nodeType = elem.nodeType;
+
+ if ( nodeType ) {
+ if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
+ // Use textContent for elements
+ // innerText usage removed for consistency of new lines (see #11153)
+ if ( typeof elem.textContent === "string" ) {
+ return elem.textContent;
+ } else {
+ // Traverse its children
+ for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
+ ret += getText( elem );
+ }
+ }
+ } else if ( nodeType === 3 || nodeType === 4 ) {
+ return elem.nodeValue;
+ }
+ // Do not include comment or processing instruction nodes
+ } else {
+
+ // If no nodeType, this is expected to be an array
+ for ( ; (node = elem[i]); i++ ) {
+ // Do not traverse comment nodes
+ ret += getText( node );
+ }
+ }
+ return ret;
+};
+
+isXML = Sizzle.isXML = function( elem ) {
+ // documentElement is verified for cases where it doesn't yet exist
+ // (such as loading iframes in IE - #4833)
+ var documentElement = elem && (elem.ownerDocument || elem).documentElement;
+ return documentElement ? documentElement.nodeName !== "HTML" : false;
+};
+
+// Element contains another
+contains = Sizzle.contains = docElem.contains ?
+ function( a, b ) {
+ var adown = a.nodeType === 9 ? a.documentElement : a,
+ bup = b && b.parentNode;
+ return a === bup || !!( bup && bup.nodeType === 1 && adown.contains && adown.contains(bup) );
+ } :
+ docElem.compareDocumentPosition ?
+ function( a, b ) {
+ return b && !!( a.compareDocumentPosition( b ) & 16 );
+ } :
+ function( a, b ) {
+ while ( (b = b.parentNode) ) {
+ if ( b === a ) {
+ return true;
+ }
+ }
+ return false;
+ };
+
+Sizzle.attr = function( elem, name ) {
+ var val,
+ xml = isXML( elem );
+
+ if ( !xml ) {
+ name = name.toLowerCase();
+ }
+ if ( (val = Expr.attrHandle[ name ]) ) {
+ return val( elem );
+ }
+ if ( xml || assertAttributes ) {
+ return elem.getAttribute( name );
+ }
+ val = elem.getAttributeNode( name );
+ return val ?
+ typeof elem[ name ] === "boolean" ?
+ elem[ name ] ? name : null :
+ val.specified ? val.value : null :
+ null;
+};
+
+Expr = Sizzle.selectors = {
+
+ // Can be adjusted by the user
+ cacheLength: 50,
+
+ createPseudo: markFunction,
+
+ match: matchExpr,
+
+ // IE6/7 return a modified href
+ attrHandle: assertHrefNotNormalized ?
+ {} :
+ {
+ "href": function( elem ) {
+ return elem.getAttribute( "href", 2 );
+ },
+ "type": function( elem ) {
+ return elem.getAttribute("type");
+ }
+ },
+
+ find: {
+ "ID": assertGetIdNotName ?
+ function( id, context, xml ) {
+ if ( typeof context.getElementById !== strundefined && !xml ) {
+ var m = context.getElementById( id );
+ // Check parentNode to catch when Blackberry 4.6 returns
+ // nodes that are no longer in the document #6963
+ return m && m.parentNode ? [m] : [];
+ }
+ } :
+ function( id, context, xml ) {
+ if ( typeof context.getElementById !== strundefined && !xml ) {
+ var m = context.getElementById( id );
+
+ return m ?
+ m.id === id || typeof m.getAttributeNode !== strundefined && m.getAttributeNode("id").value === id ?
+ [m] :
+ undefined :
+ [];
+ }
+ },
+
+ "TAG": assertTagNameNoComments ?
+ function( tag, context ) {
+ if ( typeof context.getElementsByTagName !== strundefined ) {
+ return context.getElementsByTagName( tag );
+ }
+ } :
+ function( tag, context ) {
+ var results = context.getElementsByTagName( tag );
+
+ // Filter out possible comments
+ if ( tag === "*" ) {
+ var elem,
+ tmp = [],
+ i = 0;
+
+ for ( ; (elem = results[i]); i++ ) {
+ if ( elem.nodeType === 1 ) {
+ tmp.push( elem );
+ }
+ }
+
+ return tmp;
+ }
+ return results;
+ },
+
+ "NAME": assertUsableName && function( tag, context ) {
+ if ( typeof context.getElementsByName !== strundefined ) {
+ return context.getElementsByName( name );
+ }
+ },
+
+ "CLASS": assertUsableClassName && function( className, context, xml ) {
+ if ( typeof context.getElementsByClassName !== strundefined && !xml ) {
+ return context.getElementsByClassName( className );
+ }
+ }
+ },
+
+ relative: {
+ ">": { dir: "parentNode", first: true },
+ " ": { dir: "parentNode" },
+ "+": { dir: "previousSibling", first: true },
+ "~": { dir: "previousSibling" }
+ },
+
+ preFilter: {
+ "ATTR": function( match ) {
+ match[1] = match[1].replace( rbackslash, "" );
+
+ // Move the given value to match[3] whether quoted or unquoted
+ match[3] = ( match[4] || match[5] || "" ).replace( rbackslash, "" );
+
+ if ( match[2] === "~=" ) {
+ match[3] = " " + match[3] + " ";
+ }
+
+ return match.slice( 0, 4 );
+ },
+
+ "CHILD": function( match ) {
+ /* matches from matchExpr["CHILD"]
+ 1 type (only|nth|...)
+ 2 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
+ 3 xn-component of xn+y argument ([+-]?\d*n|)
+ 4 sign of xn-component
+ 5 x of xn-component
+ 6 sign of y-component
+ 7 y of y-component
+ */
+ match[1] = match[1].toLowerCase();
+
+ if ( match[1] === "nth" ) {
+ // nth-child requires argument
+ if ( !match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ // numeric x and y parameters for Expr.filter.CHILD
+ // remember that false/true cast respectively to 0/1
+ match[3] = +( match[3] ? match[4] + (match[5] || 1) : 2 * ( match[2] === "even" || match[2] === "odd" ) );
+ match[4] = +( ( match[6] + match[7] ) || match[2] === "odd" );
+
+ // other types prohibit arguments
+ } else if ( match[2] ) {
+ Sizzle.error( match[0] );
+ }
+
+ return match;
+ },
+
+ "PSEUDO": function( match ) {
+ var unquoted, excess;
+ if ( matchExpr["CHILD"].test( match[0] ) ) {
+ return null;
+ }
+
+ if ( match[3] ) {
+ match[2] = match[3];
+ } else if ( (unquoted = match[4]) ) {
+ // Only check arguments that contain a pseudo
+ if ( rpseudo.test(unquoted) &&
+ // Get excess from tokenize (recursively)
+ (excess = tokenize( unquoted, true )) &&
+ // advance to the next closing parenthesis
+ (excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
+
+ // excess is a negative index
+ unquoted = unquoted.slice( 0, excess );
+ match[0] = match[0].slice( 0, excess );
+ }
+ match[2] = unquoted;
+ }
+
+ // Return only captures needed by the pseudo filter method (type and argument)
+ return match.slice( 0, 3 );
+ }
+ },
+
+ filter: {
+ "ID": assertGetIdNotName ?
+ function( id ) {
+ id = id.replace( rbackslash, "" );
+ return function( elem ) {
+ return elem.getAttribute("id") === id;
+ };
+ } :
+ function( id ) {
+ id = id.replace( rbackslash, "" );
+ return function( elem ) {
+ var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
+ return node && node.value === id;
+ };
+ },
+
+ "TAG": function( nodeName ) {
+ if ( nodeName === "*" ) {
+ return function() { return true; };
+ }
+ nodeName = nodeName.replace( rbackslash, "" ).toLowerCase();
+
+ return function( elem ) {
+ return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
+ };
+ },
+
+ "CLASS": function( className ) {
+ var pattern = classCache[ expando ][ className ];
+ if ( !pattern ) {
+ pattern = classCache( className, new RegExp("(^|" + whitespace + ")" + className + "(" + whitespace + "|$)") );
+ }
+ return function( elem ) {
+ return pattern.test( elem.className || (typeof elem.getAttribute !== strundefined && elem.getAttribute("class")) || "" );
+ };
+ },
+
+ "ATTR": function( name, operator, check ) {
+ return function( elem, context ) {
+ var result = Sizzle.attr( elem, name );
+
+ if ( result == null ) {
+ return operator === "!=";
+ }
+ if ( !operator ) {
+ return true;
+ }
+
+ result += "";
+
+ return operator === "=" ? result === check :
+ operator === "!=" ? result !== check :
+ operator === "^=" ? check && result.indexOf( check ) === 0 :
+ operator === "*=" ? check && result.indexOf( check ) > -1 :
+ operator === "$=" ? check && result.substr( result.length - check.length ) === check :
+ operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
+ operator === "|=" ? result === check || result.substr( 0, check.length + 1 ) === check + "-" :
+ false;
+ };
+ },
+
+ "CHILD": function( type, argument, first, last ) {
+
+ if ( type === "nth" ) {
+ return function( elem ) {
+ var node, diff,
+ parent = elem.parentNode;
+
+ if ( first === 1 && last === 0 ) {
+ return true;
+ }
+
+ if ( parent ) {
+ diff = 0;
+ for ( node = parent.firstChild; node; node = node.nextSibling ) {
+ if ( node.nodeType === 1 ) {
+ diff++;
+ if ( elem === node ) {
+ break;
+ }
+ }
+ }
+ }
+
+ // Incorporate the offset (or cast to NaN), then check against cycle size
+ diff -= last;
+ return diff === first || ( diff % first === 0 && diff / first >= 0 );
+ };
+ }
+
+ return function( elem ) {
+ var node = elem;
+
+ switch ( type ) {
+ case "only":
+ case "first":
+ while ( (node = node.previousSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ if ( type === "first" ) {
+ return true;
+ }
+
+ node = elem;
+
+ /* falls through */
+ case "last":
+ while ( (node = node.nextSibling) ) {
+ if ( node.nodeType === 1 ) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ };
+ },
+
+ "PSEUDO": function( pseudo, argument ) {
+ // pseudo-class names are case-insensitive
+ // http://www.w3.org/TR/selectors/#pseudo-classes
+ // Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
+ // Remember that setFilters inherits from pseudos
+ var args,
+ fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
+ Sizzle.error( "unsupported pseudo: " + pseudo );
+
+ // The user may use createPseudo to indicate that
+ // arguments are needed to create the filter function
+ // just as Sizzle does
+ if ( fn[ expando ] ) {
+ return fn( argument );
+ }
+
+ // But maintain support for old signatures
+ if ( fn.length > 1 ) {
+ args = [ pseudo, pseudo, "", argument ];
+ return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
+ markFunction(function( seed, matches ) {
+ var idx,
+ matched = fn( seed, argument ),
+ i = matched.length;
+ while ( i-- ) {
+ idx = indexOf.call( seed, matched[i] );
+ seed[ idx ] = !( matches[ idx ] = matched[i] );
+ }
+ }) :
+ function( elem ) {
+ return fn( elem, 0, args );
+ };
+ }
+
+ return fn;
+ }
+ },
+
+ pseudos: {
+ "not": markFunction(function( selector ) {
+ // Trim the selector passed to compile
+ // to avoid treating leading and trailing
+ // spaces as combinators
+ var input = [],
+ results = [],
+ matcher = compile( selector.replace( rtrim, "$1" ) );
+
+ return matcher[ expando ] ?
+ markFunction(function( seed, matches, context, xml ) {
+ var elem,
+ unmatched = matcher( seed, null, xml, [] ),
+ i = seed.length;
+
+ // Match elements unmatched by `matcher`
+ while ( i-- ) {
+ if ( (elem = unmatched[i]) ) {
+ seed[i] = !(matches[i] = elem);
+ }
+ }
+ }) :
+ function( elem, context, xml ) {
+ input[0] = elem;
+ matcher( input, null, xml, results );
+ return !results.pop();
+ };
+ }),
+
+ "has": markFunction(function( selector ) {
+ return function( elem ) {
+ return Sizzle( selector, elem ).length > 0;
+ };
+ }),
+
+ "contains": markFunction(function( text ) {
+ return function( elem ) {
+ return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
+ };
+ }),
+
+ "enabled": function( elem ) {
+ return elem.disabled === false;
+ },
+
+ "disabled": function( elem ) {
+ return elem.disabled === true;
+ },
+
+ "checked": function( elem ) {
+ // In CSS3, :checked should return both checked and selected elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ var nodeName = elem.nodeName.toLowerCase();
+ return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
+ },
+
+ "selected": function( elem ) {
+ // Accessing this property makes selected-by-default
+ // options in Safari work properly
+ if ( elem.parentNode ) {
+ elem.parentNode.selectedIndex;
+ }
+
+ return elem.selected === true;
+ },
+
+ "parent": function( elem ) {
+ return !Expr.pseudos["empty"]( elem );
+ },
+
+ "empty": function( elem ) {
+ // http://www.w3.org/TR/selectors/#empty-pseudo
+ // :empty is only affected by element nodes and content nodes(including text(3), cdata(4)),
+ // not comment, processing instructions, or others
+ // Thanks to Diego Perini for the nodeName shortcut
+ // Greater than "@" means alpha characters (specifically not starting with "#" or "?")
+ var nodeType;
+ elem = elem.firstChild;
+ while ( elem ) {
+ if ( elem.nodeName > "@" || (nodeType = elem.nodeType) === 3 || nodeType === 4 ) {
+ return false;
+ }
+ elem = elem.nextSibling;
+ }
+ return true;
+ },
+
+ "header": function( elem ) {
+ return rheader.test( elem.nodeName );
+ },
+
+ "text": function( elem ) {
+ var type, attr;
+ // IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
+ // use getAttribute instead to test this case
+ return elem.nodeName.toLowerCase() === "input" &&
+ (type = elem.type) === "text" &&
+ ( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === type );
+ },
+
+ // Input types
+ "radio": createInputPseudo("radio"),
+ "checkbox": createInputPseudo("checkbox"),
+ "file": createInputPseudo("file"),
+ "password": createInputPseudo("password"),
+ "image": createInputPseudo("image"),
+
+ "submit": createButtonPseudo("submit"),
+ "reset": createButtonPseudo("reset"),
+
+ "button": function( elem ) {
+ var name = elem.nodeName.toLowerCase();
+ return name === "input" && elem.type === "button" || name === "button";
+ },
+
+ "input": function( elem ) {
+ return rinputs.test( elem.nodeName );
+ },
+
+ "focus": function( elem ) {
+ var doc = elem.ownerDocument;
+ return elem === doc.activeElement && (!doc.hasFocus || doc.hasFocus()) && !!(elem.type || elem.href);
+ },
+
+ "active": function( elem ) {
+ return elem === elem.ownerDocument.activeElement;
+ },
+
+ // Positional types
+ "first": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ 0 ];
+ }),
+
+ "last": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ length - 1 ];
+ }),
+
+ "eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ return [ argument < 0 ? argument + length : argument ];
+ }),
+
+ "even": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ for ( var i = 0; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "odd": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ for ( var i = 1; i < length; i += 2 ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ for ( var i = argument < 0 ? argument + length : argument; --i >= 0; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ }),
+
+ "gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
+ for ( var i = argument < 0 ? argument + length : argument; ++i < length; ) {
+ matchIndexes.push( i );
+ }
+ return matchIndexes;
+ })
+ }
+};
+
+function siblingCheck( a, b, ret ) {
+ if ( a === b ) {
+ return ret;
+ }
+
+ var cur = a.nextSibling;
+
+ while ( cur ) {
+ if ( cur === b ) {
+ return -1;
+ }
+
+ cur = cur.nextSibling;
+ }
+
+ return 1;
+}
+
+sortOrder = docElem.compareDocumentPosition ?
+ function( a, b ) {
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+ }
+
+ return ( !a.compareDocumentPosition || !b.compareDocumentPosition ?
+ a.compareDocumentPosition :
+ a.compareDocumentPosition(b) & 4
+ ) ? -1 : 1;
+ } :
+ function( a, b ) {
+ // The nodes are identical, we can exit early
+ if ( a === b ) {
+ hasDuplicate = true;
+ return 0;
+
+ // Fallback to using sourceIndex (in IE) if it's available on both nodes
+ } else if ( a.sourceIndex && b.sourceIndex ) {
+ return a.sourceIndex - b.sourceIndex;
+ }
+
+ var al, bl,
+ ap = [],
+ bp = [],
+ aup = a.parentNode,
+ bup = b.parentNode,
+ cur = aup;
+
+ // If the nodes are siblings (or identical) we can do a quick check
+ if ( aup === bup ) {
+ return siblingCheck( a, b );
+
+ // If no parents were found then the nodes are disconnected
+ } else if ( !aup ) {
+ return -1;
+
+ } else if ( !bup ) {
+ return 1;
+ }
+
+ // Otherwise they're somewhere else in the tree so we need
+ // to build up a full list of the parentNodes for comparison
+ while ( cur ) {
+ ap.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ cur = bup;
+
+ while ( cur ) {
+ bp.unshift( cur );
+ cur = cur.parentNode;
+ }
+
+ al = ap.length;
+ bl = bp.length;
+
+ // Start walking down the tree looking for a discrepancy
+ for ( var i = 0; i < al && i < bl; i++ ) {
+ if ( ap[i] !== bp[i] ) {
+ return siblingCheck( ap[i], bp[i] );
+ }
+ }
+
+ // We ended someplace up the tree so do a sibling check
+ return i === al ?
+ siblingCheck( a, bp[i], -1 ) :
+ siblingCheck( ap[i], b, 1 );
+ };
+
+// Always assume the presence of duplicates if sort doesn't
+// pass them to our comparison function (as in Google Chrome).
+[0, 0].sort( sortOrder );
+baseHasDuplicate = !hasDuplicate;
+
+// Document sorting and removing duplicates
+Sizzle.uniqueSort = function( results ) {
+ var elem,
+ i = 1;
+
+ hasDuplicate = baseHasDuplicate;
+ results.sort( sortOrder );
+
+ if ( hasDuplicate ) {
+ for ( ; (elem = results[i]); i++ ) {
+ if ( elem === results[ i - 1 ] ) {
+ results.splice( i--, 1 );
+ }
+ }
+ }
+
+ return results;
+};
+
+Sizzle.error = function( msg ) {
+ throw new Error( "Syntax error, unrecognized expression: " + msg );
+};
+
+function tokenize( selector, parseOnly ) {
+ var matched, match, tokens, type, soFar, groups, preFilters,
+ cached = tokenCache[ expando ][ selector ];
+
+ if ( cached ) {
+ return parseOnly ? 0 : cached.slice( 0 );
+ }
+
+ soFar = selector;
+ groups = [];
+ preFilters = Expr.preFilter;
+
+ while ( soFar ) {
+
+ // Comma and first run
+ if ( !matched || (match = rcomma.exec( soFar )) ) {
+ if ( match ) {
+ soFar = soFar.slice( match[0].length );
+ }
+ groups.push( tokens = [] );
+ }
+
+ matched = false;
+
+ // Combinators
+ if ( (match = rcombinators.exec( soFar )) ) {
+ tokens.push( matched = new Token( match.shift() ) );
+ soFar = soFar.slice( matched.length );
+
+ // Cast descendant combinators to space
+ matched.type = match[0].replace( rtrim, " " );
+ }
+
+ // Filters
+ for ( type in Expr.filter ) {
+ if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
+ // The last two arguments here are (context, xml) for backCompat
+ (match = preFilters[ type ]( match, document, true ))) ) {
+
+ tokens.push( matched = new Token( match.shift() ) );
+ soFar = soFar.slice( matched.length );
+ matched.type = type;
+ matched.matches = match;
+ }
+ }
+
+ if ( !matched ) {
+ break;
+ }
+ }
+
+ // Return the length of the invalid excess
+ // if we're just parsing
+ // Otherwise, throw an error or return tokens
+ return parseOnly ?
+ soFar.length :
+ soFar ?
+ Sizzle.error( selector ) :
+ // Cache the tokens
+ tokenCache( selector, groups ).slice( 0 );
+}
+
+function addCombinator( matcher, combinator, base ) {
+ var dir = combinator.dir,
+ checkNonElements = base && combinator.dir === "parentNode",
+ doneName = done++;
+
+ return combinator.first ?
+ // Check against closest ancestor/preceding element
+ function( elem, context, xml ) {
+ while ( (elem = elem[ dir ]) ) {
+ if ( checkNonElements || elem.nodeType === 1 ) {
+ return matcher( elem, context, xml );
+ }
+ }
+ } :
+
+ // Check against all ancestor/preceding elements
+ function( elem, context, xml ) {
+ // We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
+ if ( !xml ) {
+ var cache,
+ dirkey = dirruns + " " + doneName + " ",
+ cachedkey = dirkey + cachedruns;
+ while ( (elem = elem[ dir ]) ) {
+ if ( checkNonElements || elem.nodeType === 1 ) {
+ if ( (cache = elem[ expando ]) === cachedkey ) {
+ return elem.sizset;
+ } else if ( typeof cache === "string" && cache.indexOf(dirkey) === 0 ) {
+ if ( elem.sizset ) {
+ return elem;
+ }
+ } else {
+ elem[ expando ] = cachedkey;
+ if ( matcher( elem, context, xml ) ) {
+ elem.sizset = true;
+ return elem;
+ }
+ elem.sizset = false;
+ }
+ }
+ }
+ } else {
+ while ( (elem = elem[ dir ]) ) {
+ if ( checkNonElements || elem.nodeType === 1 ) {
+ if ( matcher( elem, context, xml ) ) {
+ return elem;
+ }
+ }
+ }
+ }
+ };
+}
+
+function elementMatcher( matchers ) {
+ return matchers.length > 1 ?
+ function( elem, context, xml ) {
+ var i = matchers.length;
+ while ( i-- ) {
+ if ( !matchers[i]( elem, context, xml ) ) {
+ return false;
+ }
+ }
+ return true;
+ } :
+ matchers[0];
+}
+
+function condense( unmatched, map, filter, context, xml ) {
+ var elem,
+ newUnmatched = [],
+ i = 0,
+ len = unmatched.length,
+ mapped = map != null;
+
+ for ( ; i < len; i++ ) {
+ if ( (elem = unmatched[i]) ) {
+ if ( !filter || filter( elem, context, xml ) ) {
+ newUnmatched.push( elem );
+ if ( mapped ) {
+ map.push( i );
+ }
+ }
+ }
+ }
+
+ return newUnmatched;
+}
+
+function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
+ if ( postFilter && !postFilter[ expando ] ) {
+ postFilter = setMatcher( postFilter );
+ }
+ if ( postFinder && !postFinder[ expando ] ) {
+ postFinder = setMatcher( postFinder, postSelector );
+ }
+ return markFunction(function( seed, results, context, xml ) {
+ // Positional selectors apply to seed elements, so it is invalid to follow them with relative ones
+ if ( seed && postFinder ) {
+ return;
+ }
+
+ var i, elem, postFilterIn,
+ preMap = [],
+ postMap = [],
+ preexisting = results.length,
+
+ // Get initial elements from seed or context
+ elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [], seed ),
+
+ // Prefilter to get matcher input, preserving a map for seed-results synchronization
+ matcherIn = preFilter && ( seed || !selector ) ?
+ condense( elems, preMap, preFilter, context, xml ) :
+ elems,
+
+ matcherOut = matcher ?
+ // If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
+ postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
+
+ // ...intermediate processing is necessary
+ [] :
+
+ // ...otherwise use results directly
+ results :
+ matcherIn;
+
+ // Find primary matches
+ if ( matcher ) {
+ matcher( matcherIn, matcherOut, context, xml );
+ }
+
+ // Apply postFilter
+ if ( postFilter ) {
+ postFilterIn = condense( matcherOut, postMap );
+ postFilter( postFilterIn, [], context, xml );
+
+ // Un-match failing elements by moving them back to matcherIn
+ i = postFilterIn.length;
+ while ( i-- ) {
+ if ( (elem = postFilterIn[i]) ) {
+ matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
+ }
+ }
+ }
+
+ // Keep seed and results synchronized
+ if ( seed ) {
+ // Ignore postFinder because it can't coexist with seed
+ i = preFilter && matcherOut.length;
+ while ( i-- ) {
+ if ( (elem = matcherOut[i]) ) {
+ seed[ preMap[i] ] = !(results[ preMap[i] ] = elem);
+ }
+ }
+ } else {
+ matcherOut = condense(
+ matcherOut === results ?
+ matcherOut.splice( preexisting, matcherOut.length ) :
+ matcherOut
+ );
+ if ( postFinder ) {
+ postFinder( null, results, matcherOut, xml );
+ } else {
+ push.apply( results, matcherOut );
+ }
+ }
+ });
+}
+
+function matcherFromTokens( tokens ) {
+ var checkContext, matcher, j,
+ len = tokens.length,
+ leadingRelative = Expr.relative[ tokens[0].type ],
+ implicitRelative = leadingRelative || Expr.relative[" "],
+ i = leadingRelative ? 1 : 0,
+
+ // The foundational matcher ensures that elements are reachable from top-level context(s)
+ matchContext = addCombinator( function( elem ) {
+ return elem === checkContext;
+ }, implicitRelative, true ),
+ matchAnyContext = addCombinator( function( elem ) {
+ return indexOf.call( checkContext, elem ) > -1;
+ }, implicitRelative, true ),
+ matchers = [ function( elem, context, xml ) {
+ return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
+ (checkContext = context).nodeType ?
+ matchContext( elem, context, xml ) :
+ matchAnyContext( elem, context, xml ) );
+ } ];
+
+ for ( ; i < len; i++ ) {
+ if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
+ matchers = [ addCombinator( elementMatcher( matchers ), matcher ) ];
+ } else {
+ // The concatenated values are (context, xml) for backCompat
+ matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
+
+ // Return special upon seeing a positional matcher
+ if ( matcher[ expando ] ) {
+ // Find the next relative operator (if any) for proper handling
+ j = ++i;
+ for ( ; j < len; j++ ) {
+ if ( Expr.relative[ tokens[j].type ] ) {
+ break;
+ }
+ }
+ return setMatcher(
+ i > 1 && elementMatcher( matchers ),
+ i > 1 && tokens.slice( 0, i - 1 ).join("").replace( rtrim, "$1" ),
+ matcher,
+ i < j && matcherFromTokens( tokens.slice( i, j ) ),
+ j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
+ j < len && tokens.join("")
+ );
+ }
+ matchers.push( matcher );
+ }
+ }
+
+ return elementMatcher( matchers );
+}
+
+function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
+ var bySet = setMatchers.length > 0,
+ byElement = elementMatchers.length > 0,
+ superMatcher = function( seed, context, xml, results, expandContext ) {
+ var elem, j, matcher,
+ setMatched = [],
+ matchedCount = 0,
+ i = "0",
+ unmatched = seed && [],
+ outermost = expandContext != null,
+ contextBackup = outermostContext,
+ // We must always have either seed elements or context
+ elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ),
+ // Nested matchers should use non-integer dirruns
+ dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.E);
+
+ if ( outermost ) {
+ outermostContext = context !== document && context;
+ cachedruns = superMatcher.el;
+ }
+
+ // Add elements passing elementMatchers directly to results
+ for ( ; (elem = elems[i]) != null; i++ ) {
+ if ( byElement && elem ) {
+ for ( j = 0; (matcher = elementMatchers[j]); j++ ) {
+ if ( matcher( elem, context, xml ) ) {
+ results.push( elem );
+ break;
+ }
+ }
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ cachedruns = ++superMatcher.el;
+ }
+ }
+
+ // Track unmatched elements for set filters
+ if ( bySet ) {
+ // They will have gone through all possible matchers
+ if ( (elem = !matcher && elem) ) {
+ matchedCount--;
+ }
+
+ // Lengthen the array for every element, matched or not
+ if ( seed ) {
+ unmatched.push( elem );
+ }
+ }
+ }
+
+ // Apply set filters to unmatched elements
+ matchedCount += i;
+ if ( bySet && i !== matchedCount ) {
+ for ( j = 0; (matcher = setMatchers[j]); j++ ) {
+ matcher( unmatched, setMatched, context, xml );
+ }
+
+ if ( seed ) {
+ // Reintegrate element matches to eliminate the need for sorting
+ if ( matchedCount > 0 ) {
+ while ( i-- ) {
+ if ( !(unmatched[i] || setMatched[i]) ) {
+ setMatched[i] = pop.call( results );
+ }
+ }
+ }
+
+ // Discard index placeholder values to get only actual matches
+ setMatched = condense( setMatched );
+ }
+
+ // Add matches to results
+ push.apply( results, setMatched );
+
+ // Seedless set matches succeeding multiple successful matchers stipulate sorting
+ if ( outermost && !seed && setMatched.length > 0 &&
+ ( matchedCount + setMatchers.length ) > 1 ) {
+
+ Sizzle.uniqueSort( results );
+ }
+ }
+
+ // Override manipulation of globals by nested matchers
+ if ( outermost ) {
+ dirruns = dirrunsUnique;
+ outermostContext = contextBackup;
+ }
+
+ return unmatched;
+ };
+
+ superMatcher.el = 0;
+ return bySet ?
+ markFunction( superMatcher ) :
+ superMatcher;
+}
+
+compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
+ var i,
+ setMatchers = [],
+ elementMatchers = [],
+ cached = compilerCache[ expando ][ selector ];
+
+ if ( !cached ) {
+ // Generate a function of recursive functions that can be used to check each element
+ if ( !group ) {
+ group = tokenize( selector );
+ }
+ i = group.length;
+ while ( i-- ) {
+ cached = matcherFromTokens( group[i] );
+ if ( cached[ expando ] ) {
+ setMatchers.push( cached );
+ } else {
+ elementMatchers.push( cached );
+ }
+ }
+
+ // Cache the compiled function
+ cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
+ }
+ return cached;
+};
+
+function multipleContexts( selector, contexts, results, seed ) {
+ var i = 0,
+ len = contexts.length;
+ for ( ; i < len; i++ ) {
+ Sizzle( selector, contexts[i], results, seed );
+ }
+ return results;
+}
+
+function select( selector, context, results, seed, xml ) {
+ var i, tokens, token, type, find,
+ match = tokenize( selector ),
+ j = match.length;
+
+ if ( !seed ) {
+ // Try to minimize operations if there is only one group
+ if ( match.length === 1 ) {
+
+ // Take a shortcut and set the context if the root selector is an ID
+ tokens = match[0] = match[0].slice( 0 );
+ if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
+ context.nodeType === 9 && !xml &&
+ Expr.relative[ tokens[1].type ] ) {
+
+ context = Expr.find["ID"]( token.matches[0].replace( rbackslash, "" ), context, xml )[0];
+ if ( !context ) {
+ return results;
+ }
+
+ selector = selector.slice( tokens.shift().length );
+ }
+
+ // Fetch a seed set for right-to-left matching
+ for ( i = matchExpr["POS"].test( selector ) ? -1 : tokens.length - 1; i >= 0; i-- ) {
+ token = tokens[i];
+
+ // Abort if we hit a combinator
+ if ( Expr.relative[ (type = token.type) ] ) {
+ break;
+ }
+ if ( (find = Expr.find[ type ]) ) {
+ // Search, expanding context for leading sibling combinators
+ if ( (seed = find(
+ token.matches[0].replace( rbackslash, "" ),
+ rsibling.test( tokens[0].type ) && context.parentNode || context,
+ xml
+ )) ) {
+
+ // If seed is empty or no tokens remain, we can return early
+ tokens.splice( i, 1 );
+ selector = seed.length && tokens.join("");
+ if ( !selector ) {
+ push.apply( results, slice.call( seed, 0 ) );
+ return results;
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Compile and execute a filtering function
+ // Provide `match` to avoid retokenization if we modified the selector above
+ compile( selector, match )(
+ seed,
+ context,
+ xml,
+ results,
+ rsibling.test( selector )
+ );
+ return results;
+}
+
+if ( document.querySelectorAll ) {
+ (function() {
+ var disconnectedMatch,
+ oldSelect = select,
+ rescape = /'|\\/g,
+ rattributeQuotes = /\=[\x20\t\r\n\f]*([^'"\]]*)[\x20\t\r\n\f]*\]/g,
+
+ // qSa(:focus) reports false when true (Chrome 21),
+ // A support test would require too much code (would include document ready)
+ rbuggyQSA = [":focus"],
+
+ // matchesSelector(:focus) reports false when true (Chrome 21),
+ // matchesSelector(:active) reports false when true (IE9/Opera 11.5)
+ // A support test would require too much code (would include document ready)
+ // just skip matchesSelector for :active
+ rbuggyMatches = [ ":active", ":focus" ],
+ matches = docElem.matchesSelector ||
+ docElem.mozMatchesSelector ||
+ docElem.webkitMatchesSelector ||
+ docElem.oMatchesSelector ||
+ docElem.msMatchesSelector;
+
+ // Build QSA regex
+ // Regex strategy adopted from Diego Perini
+ assert(function( div ) {
+ // Select is set to empty string on purpose
+ // This is to test IE's treatment of not explictly
+ // setting a boolean content attribute,
+ // since its presence should be enough
+ // http://bugs.jquery.com/ticket/12359
+ div.innerHTML = "<select><option selected=''></option></select>";
+
+ // IE8 - Some boolean attributes are not treated correctly
+ if ( !div.querySelectorAll("[selected]").length ) {
+ rbuggyQSA.push( "\\[" + whitespace + "*(?:checked|disabled|ismap|multiple|readonly|selected|value)" );
+ }
+
+ // Webkit/Opera - :checked should return selected option elements
+ // http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
+ // IE8 throws error here (do not put tests after this one)
+ if ( !div.querySelectorAll(":checked").length ) {
+ rbuggyQSA.push(":checked");
+ }
+ });
+
+ assert(function( div ) {
+
+ // Opera 10-12/IE9 - ^= $= *= and empty values
+ // Should not select anything
+ div.innerHTML = "<p test=''></p>";
+ if ( div.querySelectorAll("[test^='']").length ) {
+ rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:\"\"|'')" );
+ }
+
+ // FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
+ // IE8 throws error here (do not put tests after this one)
+ div.innerHTML = "<input type='hidden'/>";
+ if ( !div.querySelectorAll(":enabled").length ) {
+ rbuggyQSA.push(":enabled", ":disabled");
+ }
+ });
+
+ // rbuggyQSA always contains :focus, so no need for a length check
+ rbuggyQSA = /* rbuggyQSA.length && */ new RegExp( rbuggyQSA.join("|") );
+
+ select = function( selector, context, results, seed, xml ) {
+ // Only use querySelectorAll when not filtering,
+ // when this is not xml,
+ // and when no QSA bugs apply
+ if ( !seed && !xml && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
+ var groups, i,
+ old = true,
+ nid = expando,
+ newContext = context,
+ newSelector = context.nodeType === 9 && selector;
+
+ // qSA works strangely on Element-rooted queries
+ // We can work around this by specifying an extra ID on the root
+ // and working up from there (Thanks to Andrew Dupont for the technique)
+ // IE 8 doesn't work on object elements
+ if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
+ groups = tokenize( selector );
+
+ if ( (old = context.getAttribute("id")) ) {
+ nid = old.replace( rescape, "\\$&" );
+ } else {
+ context.setAttribute( "id", nid );
+ }
+ nid = "[id='" + nid + "'] ";
+
+ i = groups.length;
+ while ( i-- ) {
+ groups[i] = nid + groups[i].join("");
+ }
+ newContext = rsibling.test( selector ) && context.parentNode || context;
+ newSelector = groups.join(",");
+ }
+
+ if ( newSelector ) {
+ try {
+ push.apply( results, slice.call( newContext.querySelectorAll(
+ newSelector
+ ), 0 ) );
+ return results;
+ } catch(qsaError) {
+ } finally {
+ if ( !old ) {
+ context.removeAttribute("id");
+ }
+ }
+ }
+ }
+
+ return oldSelect( selector, context, results, seed, xml );
+ };
+
+ if ( matches ) {
+ assert(function( div ) {
+ // Check to see if it's possible to do matchesSelector
+ // on a disconnected node (IE 9)
+ disconnectedMatch = matches.call( div, "div" );
+
+ // This should fail with an exception
+ // Gecko does not error, returns false instead
+ try {
+ matches.call( div, "[test!='']:sizzle" );
+ rbuggyMatches.push( "!=", pseudos );
+ } catch ( e ) {}
+ });
+
+ // rbuggyMatches always contains :active and :focus, so no need for a length check
+ rbuggyMatches = /* rbuggyMatches.length && */ new RegExp( rbuggyMatches.join("|") );
+
+ Sizzle.matchesSelector = function( elem, expr ) {
+ // Make sure that attribute selectors are quoted
+ expr = expr.replace( rattributeQuotes, "='$1']" );
+
+ // rbuggyMatches always contains :active, so no need for an existence check
+ if ( !isXML( elem ) && !rbuggyMatches.test( expr ) && (!rbuggyQSA || !rbuggyQSA.test( expr )) ) {
+ try {
+ var ret = matches.call( elem, expr );
+
+ // IE 9's matchesSelector returns false on disconnected nodes
+ if ( ret || disconnectedMatch ||
+ // As well, disconnected nodes are said to be in a document
+ // fragment in IE 9
+ elem.document && elem.document.nodeType !== 11 ) {
+ return ret;
+ }
+ } catch(e) {}
+ }
+
+ return Sizzle( expr, null, null, [ elem ] ).length > 0;
+ };
+ }
+ })();
+}
+
+// Deprecated
+Expr.pseudos["nth"] = Expr.pseudos["eq"];
+
+// Back-compat
+function setFilters() {}
+Expr.filters = setFilters.prototype = Expr.pseudos;
+Expr.setFilters = new setFilters();
+
+// Override sizzle attribute retrieval
+Sizzle.attr = jQuery.attr;
+jQuery.find = Sizzle;
+jQuery.expr = Sizzle.selectors;
+jQuery.expr[":"] = jQuery.expr.pseudos;
+jQuery.unique = Sizzle.uniqueSort;
+jQuery.text = Sizzle.getText;
+jQuery.isXMLDoc = Sizzle.isXML;
+jQuery.contains = Sizzle.contains;
+
+
+})( window );
+var runtil = /Until$/,
+ rparentsprev = /^(?:parents|prev(?:Until|All))/,
+ isSimple = /^.[^:#\[\.,]*$/,
+ rneedsContext = jQuery.expr.match.needsContext,
+ // methods guaranteed to produce a unique set when starting from a unique set
+ guaranteedUnique = {
+ children: true,
+ contents: true,
+ next: true,
+ prev: true
+ };
+
+jQuery.fn.extend({
+ find: function( selector ) {
+ var i, l, length, n, r, ret,
+ self = this;
+
+ if ( typeof selector !== "string" ) {
+ return jQuery( selector ).filter(function() {
+ for ( i = 0, l = self.length; i < l; i++ ) {
+ if ( jQuery.contains( self[ i ], this ) ) {
+ return true;
+ }
+ }
+ });
+ }
+
+ ret = this.pushStack( "", "find", selector );
+
+ for ( i = 0, l = this.length; i < l; i++ ) {
+ length = ret.length;
+ jQuery.find( selector, this[i], ret );
+
+ if ( i > 0 ) {
+ // Make sure that the results are unique
+ for ( n = length; n < ret.length; n++ ) {
+ for ( r = 0; r < length; r++ ) {
+ if ( ret[r] === ret[n] ) {
+ ret.splice(n--, 1);
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ has: function( target ) {
+ var i,
+ targets = jQuery( target, this ),
+ len = targets.length;
+
+ return this.filter(function() {
+ for ( i = 0; i < len; i++ ) {
+ if ( jQuery.contains( this, targets[i] ) ) {
+ return true;
+ }
+ }
+ });
+ },
+
+ not: function( selector ) {
+ return this.pushStack( winnow(this, selector, false), "not", selector);
+ },
+
+ filter: function( selector ) {
+ return this.pushStack( winnow(this, selector, true), "filter", selector );
+ },
+
+ is: function( selector ) {
+ return !!selector && (
+ typeof selector === "string" ?
+ // If this is a positional/relative selector, check membership in the returned set
+ // so $("p:first").is("p:last") won't return true for a doc with two "p".
+ rneedsContext.test( selector ) ?
+ jQuery( selector, this.context ).index( this[0] ) >= 0 :
+ jQuery.filter( selector, this ).length > 0 :
+ this.filter( selector ).length > 0 );
+ },
+
+ closest: function( selectors, context ) {
+ var cur,
+ i = 0,
+ l = this.length,
+ ret = [],
+ pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
+ jQuery( selectors, context || this.context ) :
+ 0;
+
+ for ( ; i < l; i++ ) {
+ cur = this[i];
+
+ while ( cur && cur.ownerDocument && cur !== context && cur.nodeType !== 11 ) {
+ if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) {
+ ret.push( cur );
+ break;
+ }
+ cur = cur.parentNode;
+ }
+ }
+
+ ret = ret.length > 1 ? jQuery.unique( ret ) : ret;
+
+ return this.pushStack( ret, "closest", selectors );
+ },
+
+ // Determine the position of an element within
+ // the matched set of elements
+ index: function( elem ) {
+
+ // No argument, return index in parent
+ if ( !elem ) {
+ return ( this[0] && this[0].parentNode ) ? this.prevAll().length : -1;
+ }
+
+ // index in selector
+ if ( typeof elem === "string" ) {
+ return jQuery.inArray( this[0], jQuery( elem ) );
+ }
+
+ // Locate the position of the desired element
+ return jQuery.inArray(
+ // If it receives a jQuery object, the first element is used
+ elem.jquery ? elem[0] : elem, this );
+ },
+
+ add: function( selector, context ) {
+ var set = typeof selector === "string" ?
+ jQuery( selector, context ) :
+ jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
+ all = jQuery.merge( this.get(), set );
+
+ return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ?
+ all :
+ jQuery.unique( all ) );
+ },
+
+ addBack: function( selector ) {
+ return this.add( selector == null ?
+ this.prevObject : this.prevObject.filter(selector)
+ );
+ }
+});
+
+jQuery.fn.andSelf = jQuery.fn.addBack;
+
+// A painfully simple check to see if an element is disconnected
+// from a document (should be improved, where feasible).
+function isDisconnected( node ) {
+ return !node || !node.parentNode || node.parentNode.nodeType === 11;
+}
+
+function sibling( cur, dir ) {
+ do {
+ cur = cur[ dir ];
+ } while ( cur && cur.nodeType !== 1 );
+
+ return cur;
+}
+
+jQuery.each({
+ parent: function( elem ) {
+ var parent = elem.parentNode;
+ return parent && parent.nodeType !== 11 ? parent : null;
+ },
+ parents: function( elem ) {
+ return jQuery.dir( elem, "parentNode" );
+ },
+ parentsUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "parentNode", until );
+ },
+ next: function( elem ) {
+ return sibling( elem, "nextSibling" );
+ },
+ prev: function( elem ) {
+ return sibling( elem, "previousSibling" );
+ },
+ nextAll: function( elem ) {
+ return jQuery.dir( elem, "nextSibling" );
+ },
+ prevAll: function( elem ) {
+ return jQuery.dir( elem, "previousSibling" );
+ },
+ nextUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "nextSibling", until );
+ },
+ prevUntil: function( elem, i, until ) {
+ return jQuery.dir( elem, "previousSibling", until );
+ },
+ siblings: function( elem ) {
+ return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
+ },
+ children: function( elem ) {
+ return jQuery.sibling( elem.firstChild );
+ },
+ contents: function( elem ) {
+ return jQuery.nodeName( elem, "iframe" ) ?
+ elem.contentDocument || elem.contentWindow.document :
+ jQuery.merge( [], elem.childNodes );
+ }
+}, function( name, fn ) {
+ jQuery.fn[ name ] = function( until, selector ) {
+ var ret = jQuery.map( this, fn, until );
+
+ if ( !runtil.test( name ) ) {
+ selector = until;
+ }
+
+ if ( selector && typeof selector === "string" ) {
+ ret = jQuery.filter( selector, ret );
+ }
+
+ ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret;
+
+ if ( this.length > 1 && rparentsprev.test( name ) ) {
+ ret = ret.reverse();
+ }
+
+ return this.pushStack( ret, name, core_slice.call( arguments ).join(",") );
+ };
+});
+
+jQuery.extend({
+ filter: function( expr, elems, not ) {
+ if ( not ) {
+ expr = ":not(" + expr + ")";
+ }
+
+ return elems.length === 1 ?
+ jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] :
+ jQuery.find.matches(expr, elems);
+ },
+
+ dir: function( elem, dir, until ) {
+ var matched = [],
+ cur = elem[ dir ];
+
+ while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
+ if ( cur.nodeType === 1 ) {
+ matched.push( cur );
+ }
+ cur = cur[dir];
+ }
+ return matched;
+ },
+
+ sibling: function( n, elem ) {
+ var r = [];
+
+ for ( ; n; n = n.nextSibling ) {
+ if ( n.nodeType === 1 && n !== elem ) {
+ r.push( n );
+ }
+ }
+
+ return r;
+ }
+});
+
+// Implement the identical functionality for filter and not
+function winnow( elements, qualifier, keep ) {
+
+ // Can't pass null or undefined to indexOf in Firefox 4
+ // Set to 0 to skip string check
+ qualifier = qualifier || 0;
+
+ if ( jQuery.isFunction( qualifier ) ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ var retVal = !!qualifier.call( elem, i, elem );
+ return retVal === keep;
+ });
+
+ } else if ( qualifier.nodeType ) {
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( elem === qualifier ) === keep;
+ });
+
+ } else if ( typeof qualifier === "string" ) {
+ var filtered = jQuery.grep(elements, function( elem ) {
+ return elem.nodeType === 1;
+ });
+
+ if ( isSimple.test( qualifier ) ) {
+ return jQuery.filter(qualifier, filtered, !keep);
+ } else {
+ qualifier = jQuery.filter( qualifier, filtered );
+ }
+ }
+
+ return jQuery.grep(elements, function( elem, i ) {
+ return ( jQuery.inArray( elem, qualifier ) >= 0 ) === keep;
+ });
+}
+function createSafeFragment( document ) {
+ var list = nodeNames.split( "|" ),
+ safeFrag = document.createDocumentFragment();
+
+ if ( safeFrag.createElement ) {
+ while ( list.length ) {
+ safeFrag.createElement(
+ list.pop()
+ );
+ }
+ }
+ return safeFrag;
+}
+
+var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
+ "header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
+ rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
+ rleadingWhitespace = /^\s+/,
+ rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
+ rtagName = /<([\w:]+)/,
+ rtbody = /<tbody/i,
+ rhtml = /<|&#?\w+;/,
+ rnoInnerhtml = /<(?:script|style|link)/i,
+ rnocache = /<(?:script|object|embed|option|style)/i,
+ rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
+ rcheckableType = /^(?:checkbox|radio)$/,
+ // checked="checked" or checked
+ rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
+ rscriptType = /\/(java|ecma)script/i,
+ rcleanScript = /^\s*<!(?:\[CDATA\[|\-\-)|[\]\-]{2}>\s*$/g,
+ wrapMap = {
+ option: [ 1, "<select multiple='multiple'>", "</select>" ],
+ legend: [ 1, "<fieldset>", "</fieldset>" ],
+ thead: [ 1, "<table>", "</table>" ],
+ tr: [ 2, "<table><tbody>", "</tbody></table>" ],
+ td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
+ col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
+ area: [ 1, "<map>", "</map>" ],
+ _default: [ 0, "", "" ]
+ },
+ safeFragment = createSafeFragment( document ),
+ fragmentDiv = safeFragment.appendChild( document.createElement("div") );
+
+wrapMap.optgroup = wrapMap.option;
+wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
+wrapMap.th = wrapMap.td;
+
+// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
+// unless wrapped in a div with non-breaking characters in front of it.
+if ( !jQuery.support.htmlSerialize ) {
+ wrapMap._default = [ 1, "X<div>", "</div>" ];
+}
+
+jQuery.fn.extend({
+ text: function( value ) {
+ return jQuery.access( this, function( value ) {
+ return value === undefined ?
+ jQuery.text( this ) :
+ this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
+ }, null, value, arguments.length );
+ },
+
+ wrapAll: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapAll( html.call(this, i) );
+ });
+ }
+
+ if ( this[0] ) {
+ // The elements to wrap the target around
+ var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
+
+ if ( this[0].parentNode ) {
+ wrap.insertBefore( this[0] );
+ }
+
+ wrap.map(function() {
+ var elem = this;
+
+ while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
+ elem = elem.firstChild;
+ }
+
+ return elem;
+ }).append( this );
+ }
+
+ return this;
+ },
+
+ wrapInner: function( html ) {
+ if ( jQuery.isFunction( html ) ) {
+ return this.each(function(i) {
+ jQuery(this).wrapInner( html.call(this, i) );
+ });
+ }
+
+ return this.each(function() {
+ var self = jQuery( this ),
+ contents = self.contents();
+
+ if ( contents.length ) {
+ contents.wrapAll( html );
+
+ } else {
+ self.append( html );
+ }
+ });
+ },
+
+ wrap: function( html ) {
+ var isFunction = jQuery.isFunction( html );
+
+ return this.each(function(i) {
+ jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
+ });
+ },
+
+ unwrap: function() {
+ return this.parent().each(function() {
+ if ( !jQuery.nodeName( this, "body" ) ) {
+ jQuery( this ).replaceWith( this.childNodes );
+ }
+ }).end();
+ },
+
+ append: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 ) {
+ this.appendChild( elem );
+ }
+ });
+ },
+
+ prepend: function() {
+ return this.domManip(arguments, true, function( elem ) {
+ if ( this.nodeType === 1 || this.nodeType === 11 ) {
+ this.insertBefore( elem, this.firstChild );
+ }
+ });
+ },
+
+ before: function() {
+ if ( !isDisconnected( this[0] ) ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this );
+ });
+ }
+
+ if ( arguments.length ) {
+ var set = jQuery.clean( arguments );
+ return this.pushStack( jQuery.merge( set, this ), "before", this.selector );
+ }
+ },
+
+ after: function() {
+ if ( !isDisconnected( this[0] ) ) {
+ return this.domManip(arguments, false, function( elem ) {
+ this.parentNode.insertBefore( elem, this.nextSibling );
+ });
+ }
+
+ if ( arguments.length ) {
+ var set = jQuery.clean( arguments );
+ return this.pushStack( jQuery.merge( this, set ), "after", this.selector );
+ }
+ },
+
+ // keepData is for internal use only--do not document
+ remove: function( selector, keepData ) {
+ var elem,
+ i = 0;
+
+ for ( ; (elem = this[i]) != null; i++ ) {
+ if ( !selector || jQuery.filter( selector, [ elem ] ).length ) {
+ if ( !keepData && elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ jQuery.cleanData( [ elem ] );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ }
+ }
+
+ return this;
+ },
+
+ empty: function() {
+ var elem,
+ i = 0;
+
+ for ( ; (elem = this[i]) != null; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName("*") );
+ }
+
+ // Remove any remaining nodes
+ while ( elem.firstChild ) {
+ elem.removeChild( elem.firstChild );
+ }
+ }
+
+ return this;
+ },
+
+ clone: function( dataAndEvents, deepDataAndEvents ) {
+ dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
+ deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
+
+ return this.map( function () {
+ return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
+ });
+ },
+
+ html: function( value ) {
+ return jQuery.access( this, function( value ) {
+ var elem = this[0] || {},
+ i = 0,
+ l = this.length;
+
+ if ( value === undefined ) {
+ return elem.nodeType === 1 ?
+ elem.innerHTML.replace( rinlinejQuery, "" ) :
+ undefined;
+ }
+
+ // See if we can take a shortcut and just use innerHTML
+ if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
+ ( jQuery.support.htmlSerialize || !rnoshimcache.test( value ) ) &&
+ ( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
+ !wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
+
+ value = value.replace( rxhtmlTag, "<$1></$2>" );
+
+ try {
+ for (; i < l; i++ ) {
+ // Remove element nodes and prevent memory leaks
+ elem = this[i] || {};
+ if ( elem.nodeType === 1 ) {
+ jQuery.cleanData( elem.getElementsByTagName( "*" ) );
+ elem.innerHTML = value;
+ }
+ }
+
+ elem = 0;
+
+ // If using innerHTML throws an exception, use the fallback method
+ } catch(e) {}
+ }
+
+ if ( elem ) {
+ this.empty().append( value );
+ }
+ }, null, value, arguments.length );
+ },
+
+ replaceWith: function( value ) {
+ if ( !isDisconnected( this[0] ) ) {
+ // Make sure that the elements are removed from the DOM before they are inserted
+ // this can help fix replacing a parent with child elements
+ if ( jQuery.isFunction( value ) ) {
+ return this.each(function(i) {
+ var self = jQuery(this), old = self.html();
+ self.replaceWith( value.call( this, i, old ) );
+ });
+ }
+
+ if ( typeof value !== "string" ) {
+ value = jQuery( value ).detach();
+ }
+
+ return this.each(function() {
+ var next = this.nextSibling,
+ parent = this.parentNode;
+
+ jQuery( this ).remove();
+
+ if ( next ) {
+ jQuery(next).before( value );
+ } else {
+ jQuery(parent).append( value );
+ }
+ });
+ }
+
+ return this.length ?
+ this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ) :
+ this;
+ },
+
+ detach: function( selector ) {
+ return this.remove( selector, true );
+ },
+
+ domManip: function( args, table, callback ) {
+
+ // Flatten any nested arrays
+ args = [].concat.apply( [], args );
+
+ var results, first, fragment, iNoClone,
+ i = 0,
+ value = args[0],
+ scripts = [],
+ l = this.length;
+
+ // We can't cloneNode fragments that contain checked, in WebKit
+ if ( !jQuery.support.checkClone && l > 1 && typeof value === "string" && rchecked.test( value ) ) {
+ return this.each(function() {
+ jQuery(this).domManip( args, table, callback );
+ });
+ }
+
+ if ( jQuery.isFunction(value) ) {
+ return this.each(function(i) {
+ var self = jQuery(this);
+ args[0] = value.call( this, i, table ? self.html() : undefined );
+ self.domManip( args, table, callback );
+ });
+ }
+
+ if ( this[0] ) {
+ results = jQuery.buildFragment( args, this, scripts );
+ fragment = results.fragment;
+ first = fragment.firstChild;
+
+ if ( fragment.childNodes.length === 1 ) {
+ fragment = first;
+ }
+
+ if ( first ) {
+ table = table && jQuery.nodeName( first, "tr" );
+
+ // Use the original fragment for the last item instead of the first because it can end up
+ // being emptied incorrectly in certain situations (#8070).
+ // Fragments from the fragment cache must always be cloned and never used in place.
+ for ( iNoClone = results.cacheable || l - 1; i < l; i++ ) {
+ callback.call(
+ table && jQuery.nodeName( this[i], "table" ) ?
+ findOrAppend( this[i], "tbody" ) :
+ this[i],
+ i === iNoClone ?
+ fragment :
+ jQuery.clone( fragment, true, true )
+ );
+ }
+ }
+
+ // Fix #11809: Avoid leaking memory
+ fragment = first = null;
+
+ if ( scripts.length ) {
+ jQuery.each( scripts, function( i, elem ) {
+ if ( elem.src ) {
+ if ( jQuery.ajax ) {
+ jQuery.ajax({
+ url: elem.src,
+ type: "GET",
+ dataType: "script",
+ async: false,
+ global: false,
+ "throws": true
+ });
+ } else {
+ jQuery.error("no ajax");
+ }
+ } else {
+ jQuery.globalEval( ( elem.text || elem.textContent || elem.innerHTML || "" ).replace( rcleanScript, "" ) );
+ }
+
+ if ( elem.parentNode ) {
+ elem.parentNode.removeChild( elem );
+ }
+ });
+ }
+ }
+
+ return this;
+ }
+});
+
+function findOrAppend( elem, tag ) {
+ return elem.getElementsByTagName( tag )[0] || elem.appendChild( elem.ownerDocument.createElement( tag ) );
+}
+
+function cloneCopyEvent( src, dest ) {
+
+ if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
+ return;
+ }
+
+ var type, i, l,
+ oldData = jQuery._data( src ),
+ curData = jQuery._data( dest, oldData ),
+ events = oldData.events;
+
+ if ( events ) {
+ delete curData.handle;
+ curData.events = {};
+
+ for ( type in events ) {
+ for ( i = 0, l = events[ type ].length; i < l; i++ ) {
+ jQuery.event.add( dest, type, events[ type ][ i ] );
+ }
+ }
+ }
+
+ // make the cloned public data object a copy from the original
+ if ( curData.data ) {
+ curData.data = jQuery.extend( {}, curData.data );
+ }
+}
+
+function cloneFixAttributes( src, dest ) {
+ var nodeName;
+
+ // We do not need to do anything for non-Elements
+ if ( dest.nodeType !== 1 ) {
+ return;
+ }
+
+ // clearAttributes removes the attributes, which we don't want,
+ // but also removes the attachEvent events, which we *do* want
+ if ( dest.clearAttributes ) {
+ dest.clearAttributes();
+ }
+
+ // mergeAttributes, in contrast, only merges back on the
+ // original attributes, not the events
+ if ( dest.mergeAttributes ) {
+ dest.mergeAttributes( src );
+ }
+
+ nodeName = dest.nodeName.toLowerCase();
+
+ if ( nodeName === "object" ) {
+ // IE6-10 improperly clones children of object elements using classid.
+ // IE10 throws NoModificationAllowedError if parent is null, #12132.
+ if ( dest.parentNode ) {
+ dest.outerHTML = src.outerHTML;
+ }
+
+ // This path appears unavoidable for IE9. When cloning an object
+ // element in IE9, the outerHTML strategy above is not sufficient.
+ // If the src has innerHTML and the destination does not,
+ // copy the src.innerHTML into the dest.innerHTML. #10324
+ if ( jQuery.support.html5Clone && (src.innerHTML && !jQuery.trim(dest.innerHTML)) ) {
+ dest.innerHTML = src.innerHTML;
+ }
+
+ } else if ( nodeName === "input" && rcheckableType.test( src.type ) ) {
+ // IE6-8 fails to persist the checked state of a cloned checkbox
+ // or radio button. Worse, IE6-7 fail to give the cloned element
+ // a checked appearance if the defaultChecked value isn't also set
+
+ dest.defaultChecked = dest.checked = src.checked;
+
+ // IE6-7 get confused and end up setting the value of a cloned
+ // checkbox/radio button to an empty string instead of "on"
+ if ( dest.value !== src.value ) {
+ dest.value = src.value;
+ }
+
+ // IE6-8 fails to return the selected option to the default selected
+ // state when cloning options
+ } else if ( nodeName === "option" ) {
+ dest.selected = src.defaultSelected;
+
+ // IE6-8 fails to set the defaultValue to the correct value when
+ // cloning other types of input fields
+ } else if ( nodeName === "input" || nodeName === "textarea" ) {
+ dest.defaultValue = src.defaultValue;
+
+ // IE blanks contents when cloning scripts
+ } else if ( nodeName === "script" && dest.text !== src.text ) {
+ dest.text = src.text;
+ }
+
+ // Event data gets referenced instead of copied if the expando
+ // gets copied too
+ dest.removeAttribute( jQuery.expando );
+}
+
+jQuery.buildFragment = function( args, context, scripts ) {
+ var fragment, cacheable, cachehit,
+ first = args[ 0 ];
+
+ // Set context from what may come in as undefined or a jQuery collection or a node
+ // Updated to fix #12266 where accessing context[0] could throw an exception in IE9/10 &
+ // also doubles as fix for #8950 where plain objects caused createDocumentFragment exception
+ context = context || document;
+ context = !context.nodeType && context[0] || context;
+ context = context.ownerDocument || context;
+
+ // Only cache "small" (1/2 KB) HTML strings that are associated with the main document
+ // Cloning options loses the selected state, so don't cache them
+ // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment
+ // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache
+ // Lastly, IE6,7,8 will not correctly reuse cached fragments that were created from unknown elems #10501
+ if ( args.length === 1 && typeof first === "string" && first.length < 512 && context === document &&
+ first.charAt(0) === "<" && !rnocache.test( first ) &&
+ (jQuery.support.checkClone || !rchecked.test( first )) &&
+ (jQuery.support.html5Clone || !rnoshimcache.test( first )) ) {
+
+ // Mark cacheable and look for a hit
+ cacheable = true;
+ fragment = jQuery.fragments[ first ];
+ cachehit = fragment !== undefined;
+ }
+
+ if ( !fragment ) {
+ fragment = context.createDocumentFragment();
+ jQuery.clean( args, context, fragment, scripts );
+
+ // Update the cache, but only store false
+ // unless this is a second parsing of the same content
+ if ( cacheable ) {
+ jQuery.fragments[ first ] = cachehit && fragment;
+ }
+ }
+
+ return { fragment: fragment, cacheable: cacheable };
+};
+
+jQuery.fragments = {};
+
+jQuery.each({
+ appendTo: "append",
+ prependTo: "prepend",
+ insertBefore: "before",
+ insertAfter: "after",
+ replaceAll: "replaceWith"
+}, function( name, original ) {
+ jQuery.fn[ name ] = function( selector ) {
+ var elems,
+ i = 0,
+ ret = [],
+ insert = jQuery( selector ),
+ l = insert.length,
+ parent = this.length === 1 && this[0].parentNode;
+
+ if ( (parent == null || parent && parent.nodeType === 11 && parent.childNodes.length === 1) && l === 1 ) {
+ insert[ original ]( this[0] );
+ return this;
+ } else {
+ for ( ; i < l; i++ ) {
+ elems = ( i > 0 ? this.clone(true) : this ).get();
+ jQuery( insert[i] )[ original ]( elems );
+ ret = ret.concat( elems );
+ }
+
+ return this.pushStack( ret, name, insert.selector );
+ }
+ };
+});
+
+function getAll( elem ) {
+ if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ return elem.getElementsByTagName( "*" );
+
+ } else if ( typeof elem.querySelectorAll !== "undefined" ) {
+ return elem.querySelectorAll( "*" );
+
+ } else {
+ return [];
+ }
+}
+
+// Used in clean, fixes the defaultChecked property
+function fixDefaultChecked( elem ) {
+ if ( rcheckableType.test( elem.type ) ) {
+ elem.defaultChecked = elem.checked;
+ }
+}
+
+jQuery.extend({
+ clone: function( elem, dataAndEvents, deepDataAndEvents ) {
+ var srcElements,
+ destElements,
+ i,
+ clone;
+
+ if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
+ clone = elem.cloneNode( true );
+
+ // IE<=8 does not properly clone detached, unknown element nodes
+ } else {
+ fragmentDiv.innerHTML = elem.outerHTML;
+ fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
+ }
+
+ if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
+ (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
+ // IE copies events bound via attachEvent when using cloneNode.
+ // Calling detachEvent on the clone will also remove the events
+ // from the original. In order to get around this, we use some
+ // proprietary methods to clear the events. Thanks to MooTools
+ // guys for this hotness.
+
+ cloneFixAttributes( elem, clone );
+
+ // Using Sizzle here is crazy slow, so we use getElementsByTagName instead
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ // Weird iteration because IE will replace the length property
+ // with an element if you are cloning the body and one of the
+ // elements on the page has a name or id of "length"
+ for ( i = 0; srcElements[i]; ++i ) {
+ // Ensure that the destination node is not null; Fixes #9587
+ if ( destElements[i] ) {
+ cloneFixAttributes( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ // Copy the events from the original to the clone
+ if ( dataAndEvents ) {
+ cloneCopyEvent( elem, clone );
+
+ if ( deepDataAndEvents ) {
+ srcElements = getAll( elem );
+ destElements = getAll( clone );
+
+ for ( i = 0; srcElements[i]; ++i ) {
+ cloneCopyEvent( srcElements[i], destElements[i] );
+ }
+ }
+ }
+
+ srcElements = destElements = null;
+
+ // Return the cloned set
+ return clone;
+ },
+
+ clean: function( elems, context, fragment, scripts ) {
+ var i, j, elem, tag, wrap, depth, div, hasBody, tbody, len, handleScript, jsTags,
+ safe = context === document && safeFragment,
+ ret = [];
+
+ // Ensure that context is a document
+ if ( !context || typeof context.createDocumentFragment === "undefined" ) {
+ context = document;
+ }
+
+ // Use the already-created safe fragment if context permits
+ for ( i = 0; (elem = elems[i]) != null; i++ ) {
+ if ( typeof elem === "number" ) {
+ elem += "";
+ }
+
+ if ( !elem ) {
+ continue;
+ }
+
+ // Convert html string into DOM nodes
+ if ( typeof elem === "string" ) {
+ if ( !rhtml.test( elem ) ) {
+ elem = context.createTextNode( elem );
+ } else {
+ // Ensure a safe container in which to render the html
+ safe = safe || createSafeFragment( context );
+ div = context.createElement("div");
+ safe.appendChild( div );
+
+ // Fix "XHTML"-style tags in all browsers
+ elem = elem.replace(rxhtmlTag, "<$1></$2>");
+
+ // Go to html and back, then peel off extra wrappers
+ tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase();
+ wrap = wrapMap[ tag ] || wrapMap._default;
+ depth = wrap[0];
+ div.innerHTML = wrap[1] + elem + wrap[2];
+
+ // Move to the right depth
+ while ( depth-- ) {
+ div = div.lastChild;
+ }
+
+ // Remove IE's autoinserted <tbody> from table fragments
+ if ( !jQuery.support.tbody ) {
+
+ // String was a <table>, *may* have spurious <tbody>
+ hasBody = rtbody.test(elem);
+ tbody = tag === "table" && !hasBody ?
+ div.firstChild && div.firstChild.childNodes :
+
+ // String was a bare <thead> or <tfoot>
+ wrap[1] === "<table>" && !hasBody ?
+ div.childNodes :
+ [];
+
+ for ( j = tbody.length - 1; j >= 0 ; --j ) {
+ if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) {
+ tbody[ j ].parentNode.removeChild( tbody[ j ] );
+ }
+ }
+ }
+
+ // IE completely kills leading whitespace when innerHTML is used
+ if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
+ div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild );
+ }
+
+ elem = div.childNodes;
+
+ // Take out of fragment container (we need a fresh div each time)
+ div.parentNode.removeChild( div );
+ }
+ }
+
+ if ( elem.nodeType ) {
+ ret.push( elem );
+ } else {
+ jQuery.merge( ret, elem );
+ }
+ }
+
+ // Fix #11356: Clear elements from safeFragment
+ if ( div ) {
+ elem = div = safe = null;
+ }
+
+ // Reset defaultChecked for any radios and checkboxes
+ // about to be appended to the DOM in IE 6/7 (#8060)
+ if ( !jQuery.support.appendChecked ) {
+ for ( i = 0; (elem = ret[i]) != null; i++ ) {
+ if ( jQuery.nodeName( elem, "input" ) ) {
+ fixDefaultChecked( elem );
+ } else if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ jQuery.grep( elem.getElementsByTagName("input"), fixDefaultChecked );
+ }
+ }
+ }
+
+ // Append elements to a provided document fragment
+ if ( fragment ) {
+ // Special handling of each script element
+ handleScript = function( elem ) {
+ // Check if we consider it executable
+ if ( !elem.type || rscriptType.test( elem.type ) ) {
+ // Detach the script and store it in the scripts array (if provided) or the fragment
+ // Return truthy to indicate that it has been handled
+ return scripts ?
+ scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) :
+ fragment.appendChild( elem );
+ }
+ };
+
+ for ( i = 0; (elem = ret[i]) != null; i++ ) {
+ // Check if we're done after handling an executable script
+ if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) {
+ // Append to fragment and handle embedded scripts
+ fragment.appendChild( elem );
+ if ( typeof elem.getElementsByTagName !== "undefined" ) {
+ // handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration
+ jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript );
+
+ // Splice the scripts into ret after their former ancestor and advance our index beyond them
+ ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
+ i += jsTags.length;
+ }
+ }
+ }
+ }
+
+ return ret;
+ },
+
+ cleanData: function( elems, /* internal */ acceptData ) {
+ var data, id, elem, type,
+ i = 0,
+ internalKey = jQuery.expando,
+ cache = jQuery.cache,
+ deleteExpando = jQuery.support.deleteExpando,
+ special = jQuery.event.special;
+
+ for ( ; (elem = elems[i]) != null; i++ ) {
+
+ if ( acceptData || jQuery.acceptData( elem ) ) {
+
+ id = elem[ internalKey ];
+ data = id && cache[ id ];
+
+ if ( data ) {
+ if ( data.events ) {
+ for ( type in data.events ) {
+ if ( special[ type ] ) {
+ jQuery.event.remove( elem, type );
+
+ // This is a shortcut to avoid jQuery.event.remove's overhead
+ } else {
+ jQuery.removeEvent( elem, type, data.handle );
+ }
+ }
+ }
+
+ // Remove cache only if it was not already removed by jQuery.event.remove
+ if ( cache[ id ] ) {
+
+ delete cache[ id ];
+
+ // IE does not allow us to delete expando properties from nodes,
+ // nor does it have a removeAttribute function on Document nodes;
+ // we must handle all of these cases
+ if ( deleteExpando ) {
+ delete elem[ internalKey ];
+
+ } else if ( elem.removeAttribute ) {
+ elem.removeAttribute( internalKey );
+
+ } else {
+ elem[ internalKey ] = null;
+ }
+
+ jQuery.deletedIds.push( id );
+ }
+ }
+ }
+ }
+ }
+});
+// Limit scope pollution from any deprecated API
+(function() {
+
+var matched, browser;
+
+// Use of jQuery.browser is frowned upon.
+// More details: http://api.jquery.com/jQuery.browser
+// jQuery.uaMatch maintained for back-compat
+jQuery.uaMatch = function( ua ) {
+ ua = ua.toLowerCase();
+
+ var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
+ /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
+ /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
+ /(msie) ([\w.]+)/.exec( ua ) ||
+ ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
+ [];
+
+ return {
+ browser: match[ 1 ] || "",
+ version: match[ 2 ] || "0"
+ };
+};
+
+matched = jQuery.uaMatch( navigator.userAgent );
+browser = {};
+
+if ( matched.browser ) {
+ browser[ matched.browser ] = true;
+ browser.version = matched.version;
+}
+
+// Chrome is Webkit, but Webkit is also Safari.
+if ( browser.chrome ) {
+ browser.webkit = true;
+} else if ( browser.webkit ) {
+ browser.safari = true;
+}
+
+jQuery.browser = browser;
+
+jQuery.sub = function() {
+ function jQuerySub( selector, context ) {
+ return new jQuerySub.fn.init( selector, context );
+ }
+ jQuery.extend( true, jQuerySub, this );
+ jQuerySub.superclass = this;
+ jQuerySub.fn = jQuerySub.prototype = this();
+ jQuerySub.fn.constructor = jQuerySub;
+ jQuerySub.sub = this.sub;
+ jQuerySub.fn.init = function init( selector, context ) {
+ if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
+ context = jQuerySub( context );
+ }
+
+ return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
+ };
+ jQuerySub.fn.init.prototype = jQuerySub.fn;
+ var rootjQuerySub = jQuerySub(document);
+ return jQuerySub;
+};
+
+})();
+var curCSS, iframe, iframeDoc,
+ ralpha = /alpha\([^)]*\)/i,
+ ropacity = /opacity=([^)]*)/,
+ rposition = /^(top|right|bottom|left)$/,
+ // swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
+ // see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
+ rdisplayswap = /^(none|table(?!-c[ea]).+)/,
+ rmargin = /^margin/,
+ rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
+ rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
+ rrelNum = new RegExp( "^([-+])=(" + core_pnum + ")", "i" ),
+ elemdisplay = {},
+
+ cssShow = { position: "absolute", visibility: "hidden", display: "block" },
+ cssNormalTransform = {
+ letterSpacing: 0,
+ fontWeight: 400
+ },
+
+ cssExpand = [ "Top", "Right", "Bottom", "Left" ],
+ cssPrefixes = [ "Webkit", "O", "Moz", "ms" ],
+
+ eventsToggle = jQuery.fn.toggle;
+
+// return a css property mapped to a potentially vendor prefixed property
+function vendorPropName( style, name ) {
+
+ // shortcut for names that are not vendor prefixed
+ if ( name in style ) {
+ return name;
+ }
+
+ // check for vendor prefixed names
+ var capName = name.charAt(0).toUpperCase() + name.slice(1),
+ origName = name,
+ i = cssPrefixes.length;
+
+ while ( i-- ) {
+ name = cssPrefixes[ i ] + capName;
+ if ( name in style ) {
+ return name;
+ }
+ }
+
+ return origName;
+}
+
+function isHidden( elem, el ) {
+ elem = el || elem;
+ return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
+}
+
+function showHide( elements, show ) {
+ var elem, display,
+ values = [],
+ index = 0,
+ length = elements.length;
+
+ for ( ; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+ values[ index ] = jQuery._data( elem, "olddisplay" );
+ if ( show ) {
+ // Reset the inline display of this element to learn if it is
+ // being hidden by cascaded rules or not
+ if ( !values[ index ] && elem.style.display === "none" ) {
+ elem.style.display = "";
+ }
+
+ // Set elements which have been overridden with display: none
+ // in a stylesheet to whatever the default browser style is
+ // for such an element
+ if ( elem.style.display === "" && isHidden( elem ) ) {
+ values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
+ }
+ } else {
+ display = curCSS( elem, "display" );
+
+ if ( !values[ index ] && display !== "none" ) {
+ jQuery._data( elem, "olddisplay", display );
+ }
+ }
+ }
+
+ // Set the display of most of the elements in a second loop
+ // to avoid the constant reflow
+ for ( index = 0; index < length; index++ ) {
+ elem = elements[ index ];
+ if ( !elem.style ) {
+ continue;
+ }
+ if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
+ elem.style.display = show ? values[ index ] || "" : "none";
+ }
+ }
+
+ return elements;
+}
+
+jQuery.fn.extend({
+ css: function( name, value ) {
+ return jQuery.access( this, function( elem, name, value ) {
+ return value !== undefined ?
+ jQuery.style( elem, name, value ) :
+ jQuery.css( elem, name );
+ }, name, value, arguments.length > 1 );
+ },
+ show: function() {
+ return showHide( this, true );
+ },
+ hide: function() {
+ return showHide( this );
+ },
+ toggle: function( state, fn2 ) {
+ var bool = typeof state === "boolean";
+
+ if ( jQuery.isFunction( state ) && jQuery.isFunction( fn2 ) ) {
+ return eventsToggle.apply( this, arguments );
+ }
+
+ return this.each(function() {
+ if ( bool ? state : isHidden( this ) ) {
+ jQuery( this ).show();
+ } else {
+ jQuery( this ).hide();
+ }
+ });
+ }
+});
+
+jQuery.extend({
+ // Add in style property hooks for overriding the default
+ // behavior of getting and setting a style property
+ cssHooks: {
+ opacity: {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ // We should always get a number back from opacity
+ var ret = curCSS( elem, "opacity" );
+ return ret === "" ? "1" : ret;
+
+ }
+ }
+ }
+ },
+
+ // Exclude the following css properties to add px
+ cssNumber: {
+ "fillOpacity": true,
+ "fontWeight": true,
+ "lineHeight": true,
+ "opacity": true,
+ "orphans": true,
+ "widows": true,
+ "zIndex": true,
+ "zoom": true
+ },
+
+ // Add in properties whose names you wish to fix before
+ // setting or getting the value
+ cssProps: {
+ // normalize float css property
+ "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
+ },
+
+ // Get and set the style property on a DOM Node
+ style: function( elem, name, value, extra ) {
+ // Don't set styles on text and comment nodes
+ if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
+ return;
+ }
+
+ // Make sure that we're working with the right name
+ var ret, type, hooks,
+ origName = jQuery.camelCase( name ),
+ style = elem.style;
+
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
+
+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // Check if we're setting a value
+ if ( value !== undefined ) {
+ type = typeof value;
+
+ // convert relative number strings (+= or -=) to relative numbers. #7345
+ if ( type === "string" && (ret = rrelNum.exec( value )) ) {
+ value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
+ // Fixes bug #9237
+ type = "number";
+ }
+
+ // Make sure that NaN and null values aren't set. See: #7116
+ if ( value == null || type === "number" && isNaN( value ) ) {
+ return;
+ }
+
+ // If a number was passed in, add 'px' to the (except for certain CSS properties)
+ if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
+ value += "px";
+ }
+
+ // If a hook was provided, use that value, otherwise just set the specified value
+ if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
+ // Wrapped to prevent IE from throwing errors when 'invalid' values are provided
+ // Fixes bug #5509
+ try {
+ style[ name ] = value;
+ } catch(e) {}
+ }
+
+ } else {
+ // If a hook was provided get the non-computed value from there
+ if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
+ return ret;
+ }
+
+ // Otherwise just get the value from the style object
+ return style[ name ];
+ }
+ },
+
+ css: function( elem, name, numeric, extra ) {
+ var val, num, hooks,
+ origName = jQuery.camelCase( name );
+
+ // Make sure that we're working with the right name
+ name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
+
+ // gets hook for the prefixed version
+ // followed by the unprefixed version
+ hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
+
+ // If a hook was provided get the computed value from there
+ if ( hooks && "get" in hooks ) {
+ val = hooks.get( elem, true, extra );
+ }
+
+ // Otherwise, if a way to get the computed value exists, use that
+ if ( val === undefined ) {
+ val = curCSS( elem, name );
+ }
+
+ //convert "normal" to computed value
+ if ( val === "normal" && name in cssNormalTransform ) {
+ val = cssNormalTransform[ name ];
+ }
+
+ // Return, converting to number if forced or a qualifier was provided and val looks numeric
+ if ( numeric || extra !== undefined ) {
+ num = parseFloat( val );
+ return numeric || jQuery.isNumeric( num ) ? num || 0 : val;
+ }
+ return val;
+ },
+
+ // A method for quickly swapping in/out CSS properties to get correct calculations
+ swap: function( elem, options, callback ) {
+ var ret, name,
+ old = {};
+
+ // Remember the old values, and insert the new ones
+ for ( name in options ) {
+ old[ name ] = elem.style[ name ];
+ elem.style[ name ] = options[ name ];
+ }
+
+ ret = callback.call( elem );
+
+ // Revert the old values
+ for ( name in options ) {
+ elem.style[ name ] = old[ name ];
+ }
+
+ return ret;
+ }
+});
+
+// NOTE: To any future maintainer, we've window.getComputedStyle
+// because jsdom on node.js will break without it.
+if ( window.getComputedStyle ) {
+ curCSS = function( elem, name ) {
+ var ret, width, minWidth, maxWidth,
+ computed = window.getComputedStyle( elem, null ),
+ style = elem.style;
+
+ if ( computed ) {
+
+ ret = computed[ name ];
+ if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
+ ret = jQuery.style( elem, name );
+ }
+
+ // A tribute to the "awesome hack by Dean Edwards"
+ // Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
+ // Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
+ // this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
+ if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
+ width = style.width;
+ minWidth = style.minWidth;
+ maxWidth = style.maxWidth;
+
+ style.minWidth = style.maxWidth = style.width = ret;
+ ret = computed.width;
+
+ style.width = width;
+ style.minWidth = minWidth;
+ style.maxWidth = maxWidth;
+ }
+ }
+
+ return ret;
+ };
+} else if ( document.documentElement.currentStyle ) {
+ curCSS = function( elem, name ) {
+ var left, rsLeft,
+ ret = elem.currentStyle && elem.currentStyle[ name ],
+ style = elem.style;
+
+ // Avoid setting ret to empty string here
+ // so we don't default to auto
+ if ( ret == null && style && style[ name ] ) {
+ ret = style[ name ];
+ }
+
+ // From the awesome hack by Dean Edwards
+ // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
+
+ // If we're not dealing with a regular pixel number
+ // but a number that has a weird ending, we need to convert it to pixels
+ // but not position css attributes, as those are proportional to the parent element instead
+ // and we can't measure the parent instead because it might trigger a "stacking dolls" problem
+ if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
+
+ // Remember the original values
+ left = style.left;
+ rsLeft = elem.runtimeStyle && elem.runtimeStyle.left;
+
+ // Put in the new values to get a computed value out
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = elem.currentStyle.left;
+ }
+ style.left = name === "fontSize" ? "1em" : ret;
+ ret = style.pixelLeft + "px";
+
+ // Revert the changed values
+ style.left = left;
+ if ( rsLeft ) {
+ elem.runtimeStyle.left = rsLeft;
+ }
+ }
+
+ return ret === "" ? "auto" : ret;
+ };
+}
+
+function setPositiveNumber( elem, value, subtract ) {
+ var matches = rnumsplit.exec( value );
+ return matches ?
+ Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
+ value;
+}
+
+function augmentWidthOrHeight( elem, name, extra, isBorderBox ) {
+ var i = extra === ( isBorderBox ? "border" : "content" ) ?
+ // If we already have the right measurement, avoid augmentation
+ 4 :
+ // Otherwise initialize for horizontal or vertical properties
+ name === "width" ? 1 : 0,
+
+ val = 0;
+
+ for ( ; i < 4; i += 2 ) {
+ // both box models exclude margin, so add it if we want it
+ if ( extra === "margin" ) {
+ // we use jQuery.css instead of curCSS here
+ // because of the reliableMarginRight CSS hook!
+ val += jQuery.css( elem, extra + cssExpand[ i ], true );
+ }
+
+ // From this point on we use curCSS for maximum performance (relevant in animations)
+ if ( isBorderBox ) {
+ // border-box includes padding, so remove it if we want content
+ if ( extra === "content" ) {
+ val -= parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
+ }
+
+ // at this point, extra isn't border nor margin, so remove border
+ if ( extra !== "margin" ) {
+ val -= parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+ }
+ } else {
+ // at this point, extra isn't content, so add padding
+ val += parseFloat( curCSS( elem, "padding" + cssExpand[ i ] ) ) || 0;
+
+ // at this point, extra isn't content nor padding, so add border
+ if ( extra !== "padding" ) {
+ val += parseFloat( curCSS( elem, "border" + cssExpand[ i ] + "Width" ) ) || 0;
+ }
+ }
+ }
+
+ return val;
+}
+
+function getWidthOrHeight( elem, name, extra ) {
+
+ // Start with offset property, which is equivalent to the border-box value
+ var val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
+ valueIsBorderBox = true,
+ isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box";
+
+ // some non-html elements return undefined for offsetWidth, so check for null/undefined
+ // svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
+ // MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
+ if ( val <= 0 || val == null ) {
+ // Fall back to computed then uncomputed css if necessary
+ val = curCSS( elem, name );
+ if ( val < 0 || val == null ) {
+ val = elem.style[ name ];
+ }
+
+ // Computed unit is not pixels. Stop here and return.
+ if ( rnumnonpx.test(val) ) {
+ return val;
+ }
+
+ // we need the check for style in case a browser which returns unreliable values
+ // for getComputedStyle silently falls back to the reliable elem.style
+ valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
+
+ // Normalize "", auto, and prepare for extra
+ val = parseFloat( val ) || 0;
+ }
+
+ // use the active box-sizing model to add/subtract irrelevant styles
+ return ( val +
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra || ( isBorderBox ? "border" : "content" ),
+ valueIsBorderBox
+ )
+ ) + "px";
+}
+
+
+// Try to determine the default display value of an element
+function css_defaultDisplay( nodeName ) {
+ if ( elemdisplay[ nodeName ] ) {
+ return elemdisplay[ nodeName ];
+ }
+
+ var elem = jQuery( "<" + nodeName + ">" ).appendTo( document.body ),
+ display = elem.css("display");
+ elem.remove();
+
+ // If the simple way fails,
+ // get element's real default display by attaching it to a temp iframe
+ if ( display === "none" || display === "" ) {
+ // Use the already-created iframe if possible
+ iframe = document.body.appendChild(
+ iframe || jQuery.extend( document.createElement("iframe"), {
+ frameBorder: 0,
+ width: 0,
+ height: 0
+ })
+ );
+
+ // Create a cacheable copy of the iframe document on first call.
+ // IE and Opera will allow us to reuse the iframeDoc without re-writing the fake HTML
+ // document to it; WebKit & Firefox won't allow reusing the iframe document.
+ if ( !iframeDoc || !iframe.createElement ) {
+ iframeDoc = ( iframe.contentWindow || iframe.contentDocument ).document;
+ iframeDoc.write("<!doctype html><html><body>");
+ iframeDoc.close();
+ }
+
+ elem = iframeDoc.body.appendChild( iframeDoc.createElement(nodeName) );
+
+ display = curCSS( elem, "display" );
+ document.body.removeChild( iframe );
+ }
+
+ // Store the correct default display
+ elemdisplay[ nodeName ] = display;
+
+ return display;
+}
+
+jQuery.each([ "height", "width" ], function( i, name ) {
+ jQuery.cssHooks[ name ] = {
+ get: function( elem, computed, extra ) {
+ if ( computed ) {
+ // certain elements can have dimension info if we invisibly show them
+ // however, it must have a current display style that would benefit from this
+ if ( elem.offsetWidth === 0 && rdisplayswap.test( curCSS( elem, "display" ) ) ) {
+ return jQuery.swap( elem, cssShow, function() {
+ return getWidthOrHeight( elem, name, extra );
+ });
+ } else {
+ return getWidthOrHeight( elem, name, extra );
+ }
+ }
+ },
+
+ set: function( elem, value, extra ) {
+ return setPositiveNumber( elem, value, extra ?
+ augmentWidthOrHeight(
+ elem,
+ name,
+ extra,
+ jQuery.support.boxSizing && jQuery.css( elem, "boxSizing" ) === "border-box"
+ ) : 0
+ );
+ }
+ };
+});
+
+if ( !jQuery.support.opacity ) {
+ jQuery.cssHooks.opacity = {
+ get: function( elem, computed ) {
+ // IE uses filters for opacity
+ return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
+ ( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
+ computed ? "1" : "";
+ },
+
+ set: function( elem, value ) {
+ var style = elem.style,
+ currentStyle = elem.currentStyle,
+ opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
+ filter = currentStyle && currentStyle.filter || style.filter || "";
+
+ // IE has trouble with opacity if it does not have layout
+ // Force it by setting the zoom level
+ style.zoom = 1;
+
+ // if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
+ if ( value >= 1 && jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
+ style.removeAttribute ) {
+
+ // Setting style.filter to null, "" & " " still leave "filter:" in the cssText
+ // if "filter:" is present at all, clearType is disabled, we want to avoid this
+ // style.removeAttribute is IE Only, but so apparently is this code path...
+ style.removeAttribute( "filter" );
+
+ // if there there is no filter style applied in a css rule, we are done
+ if ( currentStyle && !currentStyle.filter ) {
+ return;
+ }
+ }
+
+ // otherwise, set new filter values
+ style.filter = ralpha.test( filter ) ?
+ filter.replace( ralpha, opacity ) :
+ filter + " " + opacity;
+ }
+ };
+}
+
+// These hooks cannot be added until DOM ready because the support test
+// for it is not run until after DOM ready
+jQuery(function() {
+ if ( !jQuery.support.reliableMarginRight ) {
+ jQuery.cssHooks.marginRight = {
+ get: function( elem, computed ) {
+ // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
+ // Work around by temporarily setting element display to inline-block
+ return jQuery.swap( elem, { "display": "inline-block" }, function() {
+ if ( computed ) {
+ return curCSS( elem, "marginRight" );
+ }
+ });
+ }
+ };
+ }
+
+ // Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
+ // getComputedStyle returns percent when specified for top/left/bottom/right
+ // rather than make the css module depend on the offset module, we just check for it here
+ if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
+ jQuery.each( [ "top", "left" ], function( i, prop ) {
+ jQuery.cssHooks[ prop ] = {
+ get: function( elem, computed ) {
+ if ( computed ) {
+ var ret = curCSS( elem, prop );
+ // if curCSS returns percentage, fallback to offset
+ return rnumnonpx.test( ret ) ? jQuery( elem ).position()[ prop ] + "px" : ret;
+ }
+ }
+ };
+ });
+ }
+
+});
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.hidden = function( elem ) {
+ return ( elem.offsetWidth === 0 && elem.offsetHeight === 0 ) || (!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || curCSS( elem, "display" )) === "none");
+ };
+
+ jQuery.expr.filters.visible = function( elem ) {
+ return !jQuery.expr.filters.hidden( elem );
+ };
+}
+
+// These hooks are used by animate to expand properties
+jQuery.each({
+ margin: "",
+ padding: "",
+ border: "Width"
+}, function( prefix, suffix ) {
+ jQuery.cssHooks[ prefix + suffix ] = {
+ expand: function( value ) {
+ var i,
+
+ // assumes a single number if not a string
+ parts = typeof value === "string" ? value.split(" ") : [ value ],
+ expanded = {};
+
+ for ( i = 0; i < 4; i++ ) {
+ expanded[ prefix + cssExpand[ i ] + suffix ] =
+ parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
+ }
+
+ return expanded;
+ }
+ };
+
+ if ( !rmargin.test( prefix ) ) {
+ jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
+ }
+});
+var r20 = /%20/g,
+ rbracket = /\[\]$/,
+ rCRLF = /\r?\n/g,
+ rinput = /^(?:color|date|datetime|datetime-local|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i,
+ rselectTextarea = /^(?:select|textarea)/i;
+
+jQuery.fn.extend({
+ serialize: function() {
+ return jQuery.param( this.serializeArray() );
+ },
+ serializeArray: function() {
+ return this.map(function(){
+ return this.elements ? jQuery.makeArray( this.elements ) : this;
+ })
+ .filter(function(){
+ return this.name && !this.disabled &&
+ ( this.checked || rselectTextarea.test( this.nodeName ) ||
+ rinput.test( this.type ) );
+ })
+ .map(function( i, elem ){
+ var val = jQuery( this ).val();
+
+ return val == null ?
+ null :
+ jQuery.isArray( val ) ?
+ jQuery.map( val, function( val, i ){
+ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }) :
+ { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
+ }).get();
+ }
+});
+
+//Serialize an array of form elements or a set of
+//key/values into a query string
+jQuery.param = function( a, traditional ) {
+ var prefix,
+ s = [],
+ add = function( key, value ) {
+ // If value is a function, invoke it and return its value
+ value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
+ s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
+ };
+
+ // Set traditional to true for jQuery <= 1.3.2 behavior.
+ if ( traditional === undefined ) {
+ traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
+ }
+
+ // If an array was passed in, assume that it is an array of form elements.
+ if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
+ // Serialize the form elements
+ jQuery.each( a, function() {
+ add( this.name, this.value );
+ });
+
+ } else {
+ // If traditional, encode the "old" way (the way 1.3.2 or older
+ // did it), otherwise encode params recursively.
+ for ( prefix in a ) {
+ buildParams( prefix, a[ prefix ], traditional, add );
+ }
+ }
+
+ // Return the resulting serialization
+ return s.join( "&" ).replace( r20, "+" );
+};
+
+function buildParams( prefix, obj, traditional, add ) {
+ var name;
+
+ if ( jQuery.isArray( obj ) ) {
+ // Serialize array item.
+ jQuery.each( obj, function( i, v ) {
+ if ( traditional || rbracket.test( prefix ) ) {
+ // Treat each array item as a scalar.
+ add( prefix, v );
+
+ } else {
+ // If array item is non-scalar (array or object), encode its
+ // numeric index to resolve deserialization ambiguity issues.
+ // Note that rack (as of 1.0.0) can't currently deserialize
+ // nested arrays properly, and attempting to do so may cause
+ // a server error. Possible fixes are to modify rack's
+ // deserialization algorithm or to provide an option or flag
+ // to force array serialization to be shallow.
+ buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
+ }
+ });
+
+ } else if ( !traditional && jQuery.type( obj ) === "object" ) {
+ // Serialize object item.
+ for ( name in obj ) {
+ buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
+ }
+
+ } else {
+ // Serialize scalar item.
+ add( prefix, obj );
+ }
+}
+var
+ // Document location
+ ajaxLocParts,
+ ajaxLocation,
+
+ rhash = /#.*$/,
+ rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
+ // #7653, #8125, #8152: local protocol detection
+ rlocalProtocol = /^(?:about|app|app\-storage|.+\-extension|file|res|widget):$/,
+ rnoContent = /^(?:GET|HEAD)$/,
+ rprotocol = /^\/\//,
+ rquery = /\?/,
+ rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi,
+ rts = /([?&])_=[^&]*/,
+ rurl = /^([\w\+\.\-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,
+
+ // Keep a copy of the old load method
+ _load = jQuery.fn.load,
+
+ /* Prefilters
+ * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
+ * 2) These are called:
+ * - BEFORE asking for a transport
+ * - AFTER param serialization (s.data is a string if s.processData is true)
+ * 3) key is the dataType
+ * 4) the catchall symbol "*" can be used
+ * 5) execution will start with transport dataType and THEN continue down to "*" if needed
+ */
+ prefilters = {},
+
+ /* Transports bindings
+ * 1) key is the dataType
+ * 2) the catchall symbol "*" can be used
+ * 3) selection will start with transport dataType and THEN go to "*" if needed
+ */
+ transports = {},
+
+ // Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
+ allTypes = ["*/"] + ["*"];
+
+// #8138, IE may throw an exception when accessing
+// a field from window.location if document.domain has been set
+try {
+ ajaxLocation = location.href;
+} catch( e ) {
+ // Use the href attribute of an A element
+ // since IE will modify it given document.location
+ ajaxLocation = document.createElement( "a" );
+ ajaxLocation.href = "";
+ ajaxLocation = ajaxLocation.href;
+}
+
+// Segment location into parts
+ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
+
+// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
+function addToPrefiltersOrTransports( structure ) {
+
+ // dataTypeExpression is optional and defaults to "*"
+ return function( dataTypeExpression, func ) {
+
+ if ( typeof dataTypeExpression !== "string" ) {
+ func = dataTypeExpression;
+ dataTypeExpression = "*";
+ }
+
+ var dataType, list, placeBefore,
+ dataTypes = dataTypeExpression.toLowerCase().split( core_rspace ),
+ i = 0,
+ length = dataTypes.length;
+
+ if ( jQuery.isFunction( func ) ) {
+ // For each dataType in the dataTypeExpression
+ for ( ; i < length; i++ ) {
+ dataType = dataTypes[ i ];
+ // We control if we're asked to add before
+ // any existing element
+ placeBefore = /^\+/.test( dataType );
+ if ( placeBefore ) {
+ dataType = dataType.substr( 1 ) || "*";
+ }
+ list = structure[ dataType ] = structure[ dataType ] || [];
+ // then we add to the structure accordingly
+ list[ placeBefore ? "unshift" : "push" ]( func );
+ }
+ }
+ };
+}
+
+// Base inspection function for prefilters and transports
+function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR,
+ dataType /* internal */, inspected /* internal */ ) {
+
+ dataType = dataType || options.dataTypes[ 0 ];
+ inspected = inspected || {};
+
+ inspected[ dataType ] = true;
+
+ var selection,
+ list = structure[ dataType ],
+ i = 0,
+ length = list ? list.length : 0,
+ executeOnly = ( structure === prefilters );
+
+ for ( ; i < length && ( executeOnly || !selection ); i++ ) {
+ selection = list[ i ]( options, originalOptions, jqXHR );
+ // If we got redirected to another dataType
+ // we try there if executing only and not done already
+ if ( typeof selection === "string" ) {
+ if ( !executeOnly || inspected[ selection ] ) {
+ selection = undefined;
+ } else {
+ options.dataTypes.unshift( selection );
+ selection = inspectPrefiltersOrTransports(
+ structure, options, originalOptions, jqXHR, selection, inspected );
+ }
+ }
+ }
+ // If we're only executing or nothing was selected
+ // we try the catchall dataType if not done already
+ if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) {
+ selection = inspectPrefiltersOrTransports(
+ structure, options, originalOptions, jqXHR, "*", inspected );
+ }
+ // unnecessary when only executing (prefilters)
+ // but it'll be ignored by the caller in that case
+ return selection;
+}
+
+// A special extend for ajax options
+// that takes "flat" options (not to be deep extended)
+// Fixes #9887
+function ajaxExtend( target, src ) {
+ var key, deep,
+ flatOptions = jQuery.ajaxSettings.flatOptions || {};
+ for ( key in src ) {
+ if ( src[ key ] !== undefined ) {
+ ( flatOptions[ key ] ? target : ( deep || ( deep = {} ) ) )[ key ] = src[ key ];
+ }
+ }
+ if ( deep ) {
+ jQuery.extend( true, target, deep );
+ }
+}
+
+jQuery.fn.load = function( url, params, callback ) {
+ if ( typeof url !== "string" && _load ) {
+ return _load.apply( this, arguments );
+ }
+
+ // Don't do a request if no elements are being requested
+ if ( !this.length ) {
+ return this;
+ }
+
+ var selector, type, response,
+ self = this,
+ off = url.indexOf(" ");
+
+ if ( off >= 0 ) {
+ selector = url.slice( off, url.length );
+ url = url.slice( 0, off );
+ }
+
+ // If it's a function
+ if ( jQuery.isFunction( params ) ) {
+
+ // We assume that it's the callback
+ callback = params;
+ params = undefined;
+
+ // Otherwise, build a param string
+ } else if ( params && typeof params === "object" ) {
+ type = "POST";
+ }
+
+ // Request the remote document
+ jQuery.ajax({
+ url: url,
+
+ // if "type" variable is undefined, then "GET" method will be used
+ type: type,
+ dataType: "html",
+ data: params,
+ complete: function( jqXHR, status ) {
+ if ( callback ) {
+ self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
+ }
+ }
+ }).done(function( responseText ) {
+
+ // Save response for use in complete callback
+ response = arguments;
+
+ // See if a selector was specified
+ self.html( selector ?
+
+ // Create a dummy div to hold the results
+ jQuery("<div>")
+
+ // inject the contents of the document in, removing the scripts
+ // to avoid any 'Permission Denied' errors in IE
+ .append( responseText.replace( rscript, "" ) )
+
+ // Locate the specified elements
+ .find( selector ) :
+
+ // If not, just inject the full result
+ responseText );
+
+ });
+
+ return this;
+};
+
+// Attach a bunch of functions for handling common AJAX events
+jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){
+ jQuery.fn[ o ] = function( f ){
+ return this.on( o, f );
+ };
+});
+
+jQuery.each( [ "get", "post" ], function( i, method ) {
+ jQuery[ method ] = function( url, data, callback, type ) {
+ // shift arguments if data argument was omitted
+ if ( jQuery.isFunction( data ) ) {
+ type = type || callback;
+ callback = data;
+ data = undefined;
+ }
+
+ return jQuery.ajax({
+ type: method,
+ url: url,
+ data: data,
+ success: callback,
+ dataType: type
+ });
+ };
+});
+
+jQuery.extend({
+
+ getScript: function( url, callback ) {
+ return jQuery.get( url, undefined, callback, "script" );
+ },
+
+ getJSON: function( url, data, callback ) {
+ return jQuery.get( url, data, callback, "json" );
+ },
+
+ // Creates a full fledged settings object into target
+ // with both ajaxSettings and settings fields.
+ // If target is omitted, writes into ajaxSettings.
+ ajaxSetup: function( target, settings ) {
+ if ( settings ) {
+ // Building a settings object
+ ajaxExtend( target, jQuery.ajaxSettings );
+ } else {
+ // Extending ajaxSettings
+ settings = target;
+ target = jQuery.ajaxSettings;
+ }
+ ajaxExtend( target, settings );
+ return target;
+ },
+
+ ajaxSettings: {
+ url: ajaxLocation,
+ isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
+ global: true,
+ type: "GET",
+ contentType: "application/x-www-form-urlencoded; charset=UTF-8",
+ processData: true,
+ async: true,
+ /*
+ timeout: 0,
+ data: null,
+ dataType: null,
+ username: null,
+ password: null,
+ cache: null,
+ throws: false,
+ traditional: false,
+ headers: {},
+ */
+
+ accepts: {
+ xml: "application/xml, text/xml",
+ html: "text/html",
+ text: "text/plain",
+ json: "application/json, text/javascript",
+ "*": allTypes
+ },
+
+ contents: {
+ xml: /xml/,
+ html: /html/,
+ json: /json/
+ },
+
+ responseFields: {
+ xml: "responseXML",
+ text: "responseText"
+ },
+
+ // List of data converters
+ // 1) key format is "source_type destination_type" (a single space in-between)
+ // 2) the catchall symbol "*" can be used for source_type
+ converters: {
+
+ // Convert anything to text
+ "* text": window.String,
+
+ // Text to html (true = no transformation)
+ "text html": true,
+
+ // Evaluate text as a json expression
+ "text json": jQuery.parseJSON,
+
+ // Parse text as xml
+ "text xml": jQuery.parseXML
+ },
+
+ // For options that shouldn't be deep extended:
+ // you can add your own custom options here if
+ // and when you create one that shouldn't be
+ // deep extended (see ajaxExtend)
+ flatOptions: {
+ context: true,
+ url: true
+ }
+ },
+
+ ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
+ ajaxTransport: addToPrefiltersOrTransports( transports ),
+
+ // Main method
+ ajax: function( url, options ) {
+
+ // If url is an object, simulate pre-1.5 signature
+ if ( typeof url === "object" ) {
+ options = url;
+ url = undefined;
+ }
+
+ // Force options to be an object
+ options = options || {};
+
+ var // ifModified key
+ ifModifiedKey,
+ // Response headers
+ responseHeadersString,
+ responseHeaders,
+ // transport
+ transport,
+ // timeout handle
+ timeoutTimer,
+ // Cross-domain detection vars
+ parts,
+ // To know if global events are to be dispatched
+ fireGlobals,
+ // Loop variable
+ i,
+ // Create the final options object
+ s = jQuery.ajaxSetup( {}, options ),
+ // Callbacks context
+ callbackContext = s.context || s,
+ // Context for global events
+ // It's the callbackContext if one was provided in the options
+ // and if it's a DOM node or a jQuery collection
+ globalEventContext = callbackContext !== s &&
+ ( callbackContext.nodeType || callbackContext instanceof jQuery ) ?
+ jQuery( callbackContext ) : jQuery.event,
+ // Deferreds
+ deferred = jQuery.Deferred(),
+ completeDeferred = jQuery.Callbacks( "once memory" ),
+ // Status-dependent callbacks
+ statusCode = s.statusCode || {},
+ // Headers (they are sent all at once)
+ requestHeaders = {},
+ requestHeadersNames = {},
+ // The jqXHR state
+ state = 0,
+ // Default abort message
+ strAbort = "canceled",
+ // Fake xhr
+ jqXHR = {
+
+ readyState: 0,
+
+ // Caches the header
+ setRequestHeader: function( name, value ) {
+ if ( !state ) {
+ var lname = name.toLowerCase();
+ name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
+ requestHeaders[ name ] = value;
+ }
+ return this;
+ },
+
+ // Raw string
+ getAllResponseHeaders: function() {
+ return state === 2 ? responseHeadersString : null;
+ },
+
+ // Builds headers hashtable if needed
+ getResponseHeader: function( key ) {
+ var match;
+ if ( state === 2 ) {
+ if ( !responseHeaders ) {
+ responseHeaders = {};
+ while( ( match = rheaders.exec( responseHeadersString ) ) ) {
+ responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
+ }
+ }
+ match = responseHeaders[ key.toLowerCase() ];
+ }
+ return match === undefined ? null : match;
+ },
+
+ // Overrides response content-type header
+ overrideMimeType: function( type ) {
+ if ( !state ) {
+ s.mimeType = type;
+ }
+ return this;
+ },
+
+ // Cancel the request
+ abort: function( statusText ) {
+ statusText = statusText || strAbort;
+ if ( transport ) {
+ transport.abort( statusText );
+ }
+ done( 0, statusText );
+ return this;
+ }
+ };
+
+ // Callback for when everything is done
+ // It is defined here because jslint complains if it is declared
+ // at the end of the function (which would be more logical and readable)
+ function done( status, nativeStatusText, responses, headers ) {
+ var isSuccess, success, error, response, modified,
+ statusText = nativeStatusText;
+
+ // Called once
+ if ( state === 2 ) {
+ return;
+ }
+
+ // State is "done" now
+ state = 2;
+
+ // Clear timeout if it exists
+ if ( timeoutTimer ) {
+ clearTimeout( timeoutTimer );
+ }
+
+ // Dereference transport for early garbage collection
+ // (no matter how long the jqXHR object will be used)
+ transport = undefined;
+
+ // Cache response headers
+ responseHeadersString = headers || "";
+
+ // Set readyState
+ jqXHR.readyState = status > 0 ? 4 : 0;
+
+ // Get response data
+ if ( responses ) {
+ response = ajaxHandleResponses( s, jqXHR, responses );
+ }
+
+ // If successful, handle type chaining
+ if ( status >= 200 && status < 300 || status === 304 ) {
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+
+ modified = jqXHR.getResponseHeader("Last-Modified");
+ if ( modified ) {
+ jQuery.lastModified[ ifModifiedKey ] = modified;
+ }
+ modified = jqXHR.getResponseHeader("Etag");
+ if ( modified ) {
+ jQuery.etag[ ifModifiedKey ] = modified;
+ }
+ }
+
+ // If not modified
+ if ( status === 304 ) {
+
+ statusText = "notmodified";
+ isSuccess = true;
+
+ // If we have data
+ } else {
+
+ isSuccess = ajaxConvert( s, response );
+ statusText = isSuccess.state;
+ success = isSuccess.data;
+ error = isSuccess.error;
+ isSuccess = !error;
+ }
+ } else {
+ // We extract error from statusText
+ // then normalize statusText and status for non-aborts
+ error = statusText;
+ if ( !statusText || status ) {
+ statusText = "error";
+ if ( status < 0 ) {
+ status = 0;
+ }
+ }
+ }
+
+ // Set data for the fake xhr object
+ jqXHR.status = status;
+ jqXHR.statusText = ( nativeStatusText || statusText ) + "";
+
+ // Success/Error
+ if ( isSuccess ) {
+ deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
+ } else {
+ deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
+ }
+
+ // Status-dependent callbacks
+ jqXHR.statusCode( statusCode );
+ statusCode = undefined;
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),
+ [ jqXHR, s, isSuccess ? success : error ] );
+ }
+
+ // Complete
+ completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
+
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
+ // Handle the global AJAX counter
+ if ( !( --jQuery.active ) ) {
+ jQuery.event.trigger( "ajaxStop" );
+ }
+ }
+ }
+
+ // Attach deferreds
+ deferred.promise( jqXHR );
+ jqXHR.success = jqXHR.done;
+ jqXHR.error = jqXHR.fail;
+ jqXHR.complete = completeDeferred.add;
+
+ // Status-dependent callbacks
+ jqXHR.statusCode = function( map ) {
+ if ( map ) {
+ var tmp;
+ if ( state < 2 ) {
+ for ( tmp in map ) {
+ statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ];
+ }
+ } else {
+ tmp = map[ jqXHR.status ];
+ jqXHR.always( tmp );
+ }
+ }
+ return this;
+ };
+
+ // Remove hash character (#7531: and string promotion)
+ // Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
+ // We also use the url parameter if available
+ s.url = ( ( url || s.url ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
+
+ // Extract dataTypes list
+ s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( core_rspace );
+
+ // A cross-domain request is in order when we have a protocol:host:port mismatch
+ if ( s.crossDomain == null ) {
+ parts = rurl.exec( s.url.toLowerCase() ) || false;
+ s.crossDomain = parts && ( parts.join(":") + ( parts[ 3 ] ? "" : parts[ 1 ] === "http:" ? 80 : 443 ) ) !==
+ ( ajaxLocParts.join(":") + ( ajaxLocParts[ 3 ] ? "" : ajaxLocParts[ 1 ] === "http:" ? 80 : 443 ) );
+ }
+
+ // Convert data if not already a string
+ if ( s.data && s.processData && typeof s.data !== "string" ) {
+ s.data = jQuery.param( s.data, s.traditional );
+ }
+
+ // Apply prefilters
+ inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
+
+ // If request was aborted inside a prefilter, stop there
+ if ( state === 2 ) {
+ return jqXHR;
+ }
+
+ // We can fire global events as of now if asked to
+ fireGlobals = s.global;
+
+ // Uppercase the type
+ s.type = s.type.toUpperCase();
+
+ // Determine if request has content
+ s.hasContent = !rnoContent.test( s.type );
+
+ // Watch for a new set of requests
+ if ( fireGlobals && jQuery.active++ === 0 ) {
+ jQuery.event.trigger( "ajaxStart" );
+ }
+
+ // More options handling for requests with no content
+ if ( !s.hasContent ) {
+
+ // If data is available, append data to url
+ if ( s.data ) {
+ s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data;
+ // #9682: remove data so that it's not used in an eventual retry
+ delete s.data;
+ }
+
+ // Get ifModifiedKey before adding the anti-cache parameter
+ ifModifiedKey = s.url;
+
+ // Add anti-cache in url if needed
+ if ( s.cache === false ) {
+
+ var ts = jQuery.now(),
+ // try replacing _= if it is there
+ ret = s.url.replace( rts, "$1_=" + ts );
+
+ // if nothing was replaced, add timestamp to the end
+ s.url = ret + ( ( ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" );
+ }
+ }
+
+ // Set the correct header, if data is being sent
+ if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
+ jqXHR.setRequestHeader( "Content-Type", s.contentType );
+ }
+
+ // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
+ if ( s.ifModified ) {
+ ifModifiedKey = ifModifiedKey || s.url;
+ if ( jQuery.lastModified[ ifModifiedKey ] ) {
+ jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ ifModifiedKey ] );
+ }
+ if ( jQuery.etag[ ifModifiedKey ] ) {
+ jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ ifModifiedKey ] );
+ }
+ }
+
+ // Set the Accepts header for the server, depending on the dataType
+ jqXHR.setRequestHeader(
+ "Accept",
+ s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
+ s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
+ s.accepts[ "*" ]
+ );
+
+ // Check for headers option
+ for ( i in s.headers ) {
+ jqXHR.setRequestHeader( i, s.headers[ i ] );
+ }
+
+ // Allow custom headers/mimetypes and early abort
+ if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
+ // Abort if not done already and return
+ return jqXHR.abort();
+
+ }
+
+ // aborting is no longer a cancellation
+ strAbort = "abort";
+
+ // Install callbacks on deferreds
+ for ( i in { success: 1, error: 1, complete: 1 } ) {
+ jqXHR[ i ]( s[ i ] );
+ }
+
+ // Get transport
+ transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
+
+ // If no transport, we auto-abort
+ if ( !transport ) {
+ done( -1, "No Transport" );
+ } else {
+ jqXHR.readyState = 1;
+ // Send global event
+ if ( fireGlobals ) {
+ globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
+ }
+ // Timeout
+ if ( s.async && s.timeout > 0 ) {
+ timeoutTimer = setTimeout( function(){
+ jqXHR.abort( "timeout" );
+ }, s.timeout );
+ }
+
+ try {
+ state = 1;
+ transport.send( requestHeaders, done );
+ } catch (e) {
+ // Propagate exception as error if not done
+ if ( state < 2 ) {
+ done( -1, e );
+ // Simply rethrow otherwise
+ } else {
+ throw e;
+ }
+ }
+ }
+
+ return jqXHR;
+ },
+
+ // Counter for holding the number of active queries
+ active: 0,
+
+ // Last-Modified header cache for next request
+ lastModified: {},
+ etag: {}
+
+});
+
+/* Handles responses to an ajax request:
+ * - sets all responseXXX fields accordingly
+ * - finds the right dataType (mediates between content-type and expected dataType)
+ * - returns the corresponding response
+ */
+function ajaxHandleResponses( s, jqXHR, responses ) {
+
+ var ct, type, finalDataType, firstDataType,
+ contents = s.contents,
+ dataTypes = s.dataTypes,
+ responseFields = s.responseFields;
+
+ // Fill responseXXX fields
+ for ( type in responseFields ) {
+ if ( type in responses ) {
+ jqXHR[ responseFields[type] ] = responses[ type ];
+ }
+ }
+
+ // Remove auto dataType and get content-type in the process
+ while( dataTypes[ 0 ] === "*" ) {
+ dataTypes.shift();
+ if ( ct === undefined ) {
+ ct = s.mimeType || jqXHR.getResponseHeader( "content-type" );
+ }
+ }
+
+ // Check if we're dealing with a known content-type
+ if ( ct ) {
+ for ( type in contents ) {
+ if ( contents[ type ] && contents[ type ].test( ct ) ) {
+ dataTypes.unshift( type );
+ break;
+ }
+ }
+ }
+
+ // Check to see if we have a response for the expected dataType
+ if ( dataTypes[ 0 ] in responses ) {
+ finalDataType = dataTypes[ 0 ];
+ } else {
+ // Try convertible dataTypes
+ for ( type in responses ) {
+ if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
+ finalDataType = type;
+ break;
+ }
+ if ( !firstDataType ) {
+ firstDataType = type;
+ }
+ }
+ // Or just use first one
+ finalDataType = finalDataType || firstDataType;
+ }
+
+ // If we found a dataType
+ // We add the dataType to the list if needed
+ // and return the corresponding response
+ if ( finalDataType ) {
+ if ( finalDataType !== dataTypes[ 0 ] ) {
+ dataTypes.unshift( finalDataType );
+ }
+ return responses[ finalDataType ];
+ }
+}
+
+// Chain conversions given the request and the original response
+function ajaxConvert( s, response ) {
+
+ var conv, conv2, current, tmp,
+ // Work with a copy of dataTypes in case we need to modify it for conversion
+ dataTypes = s.dataTypes.slice(),
+ prev = dataTypes[ 0 ],
+ converters = {},
+ i = 0;
+
+ // Apply the dataFilter if provided
+ if ( s.dataFilter ) {
+ response = s.dataFilter( response, s.dataType );
+ }
+
+ // Create converters map with lowercased keys
+ if ( dataTypes[ 1 ] ) {
+ for ( conv in s.converters ) {
+ converters[ conv.toLowerCase() ] = s.converters[ conv ];
+ }
+ }
+
+ // Convert to each sequential dataType, tolerating list modification
+ for ( ; (current = dataTypes[++i]); ) {
+
+ // There's only work to do if current dataType is non-auto
+ if ( current !== "*" ) {
+
+ // Convert response if prev dataType is non-auto and differs from current
+ if ( prev !== "*" && prev !== current ) {
+
+ // Seek a direct converter
+ conv = converters[ prev + " " + current ] || converters[ "* " + current ];
+
+ // If none found, seek a pair
+ if ( !conv ) {
+ for ( conv2 in converters ) {
+
+ // If conv2 outputs current
+ tmp = conv2.split(" ");
+ if ( tmp[ 1 ] === current ) {
+
+ // If prev can be converted to accepted input
+ conv = converters[ prev + " " + tmp[ 0 ] ] ||
+ converters[ "* " + tmp[ 0 ] ];
+ if ( conv ) {
+ // Condense equivalence converters
+ if ( conv === true ) {
+ conv = converters[ conv2 ];
+
+ // Otherwise, insert the intermediate dataType
+ } else if ( converters[ conv2 ] !== true ) {
+ current = tmp[ 0 ];
+ dataTypes.splice( i--, 0, current );
+ }
+
+ break;
+ }
+ }
+ }
+ }
+
+ // Apply converter (if not an equivalence)
+ if ( conv !== true ) {
+
+ // Unless errors are allowed to bubble, catch and return them
+ if ( conv && s["throws"] ) {
+ response = conv( response );
+ } else {
+ try {
+ response = conv( response );
+ } catch ( e ) {
+ return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
+ }
+ }
+ }
+ }
+
+ // Update prev for next iteration
+ prev = current;
+ }
+ }
+
+ return { state: "success", data: response };
+}
+var oldCallbacks = [],
+ rquestion = /\?/,
+ rjsonp = /(=)\?(?=&|$)|\?\?/,
+ nonce = jQuery.now();
+
+// Default jsonp settings
+jQuery.ajaxSetup({
+ jsonp: "callback",
+ jsonpCallback: function() {
+ var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( nonce++ ) );
+ this[ callback ] = true;
+ return callback;
+ }
+});
+
+// Detect, normalize options and install callbacks for jsonp requests
+jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
+
+ var callbackName, overwritten, responseContainer,
+ data = s.data,
+ url = s.url,
+ hasCallback = s.jsonp !== false,
+ replaceInUrl = hasCallback && rjsonp.test( url ),
+ replaceInData = hasCallback && !replaceInUrl && typeof data === "string" &&
+ !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") &&
+ rjsonp.test( data );
+
+ // Handle iff the expected data type is "jsonp" or we have a parameter to set
+ if ( s.dataTypes[ 0 ] === "jsonp" || replaceInUrl || replaceInData ) {
+
+ // Get callback name, remembering preexisting value associated with it
+ callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
+ s.jsonpCallback() :
+ s.jsonpCallback;
+ overwritten = window[ callbackName ];
+
+ // Insert callback into url or form data
+ if ( replaceInUrl ) {
+ s.url = url.replace( rjsonp, "$1" + callbackName );
+ } else if ( replaceInData ) {
+ s.data = data.replace( rjsonp, "$1" + callbackName );
+ } else if ( hasCallback ) {
+ s.url += ( rquestion.test( url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
+ }
+
+ // Use data converter to retrieve json after script execution
+ s.converters["script json"] = function() {
+ if ( !responseContainer ) {
+ jQuery.error( callbackName + " was not called" );
+ }
+ return responseContainer[ 0 ];
+ };
+
+ // force json dataType
+ s.dataTypes[ 0 ] = "json";
+
+ // Install callback
+ window[ callbackName ] = function() {
+ responseContainer = arguments;
+ };
+
+ // Clean-up function (fires after converters)
+ jqXHR.always(function() {
+ // Restore preexisting value
+ window[ callbackName ] = overwritten;
+
+ // Save back as free
+ if ( s[ callbackName ] ) {
+ // make sure that re-using the options doesn't screw things around
+ s.jsonpCallback = originalSettings.jsonpCallback;
+
+ // save the callback name for future use
+ oldCallbacks.push( callbackName );
+ }
+
+ // Call if it was a function and we have a response
+ if ( responseContainer && jQuery.isFunction( overwritten ) ) {
+ overwritten( responseContainer[ 0 ] );
+ }
+
+ responseContainer = overwritten = undefined;
+ });
+
+ // Delegate to script
+ return "script";
+ }
+});
+// Install script dataType
+jQuery.ajaxSetup({
+ accepts: {
+ script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
+ },
+ contents: {
+ script: /javascript|ecmascript/
+ },
+ converters: {
+ "text script": function( text ) {
+ jQuery.globalEval( text );
+ return text;
+ }
+ }
+});
+
+// Handle cache's special case and global
+jQuery.ajaxPrefilter( "script", function( s ) {
+ if ( s.cache === undefined ) {
+ s.cache = false;
+ }
+ if ( s.crossDomain ) {
+ s.type = "GET";
+ s.global = false;
+ }
+});
+
+// Bind script tag hack transport
+jQuery.ajaxTransport( "script", function(s) {
+
+ // This transport only deals with cross domain requests
+ if ( s.crossDomain ) {
+
+ var script,
+ head = document.head || document.getElementsByTagName( "head" )[0] || document.documentElement;
+
+ return {
+
+ send: function( _, callback ) {
+
+ script = document.createElement( "script" );
+
+ script.async = "async";
+
+ if ( s.scriptCharset ) {
+ script.charset = s.scriptCharset;
+ }
+
+ script.src = s.url;
+
+ // Attach handlers for all browsers
+ script.onload = script.onreadystatechange = function( _, isAbort ) {
+
+ if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
+
+ // Handle memory leak in IE
+ script.onload = script.onreadystatechange = null;
+
+ // Remove the script
+ if ( head && script.parentNode ) {
+ head.removeChild( script );
+ }
+
+ // Dereference the script
+ script = undefined;
+
+ // Callback if not abort
+ if ( !isAbort ) {
+ callback( 200, "success" );
+ }
+ }
+ };
+ // Use insertBefore instead of appendChild to circumvent an IE6 bug.
+ // This arises when a base node is used (#2709 and #4378).
+ head.insertBefore( script, head.firstChild );
+ },
+
+ abort: function() {
+ if ( script ) {
+ script.onload( 0, 1 );
+ }
+ }
+ };
+ }
+});
+var xhrCallbacks,
+ // #5280: Internet Explorer will keep connections alive if we don't abort on unload
+ xhrOnUnloadAbort = window.ActiveXObject ? function() {
+ // Abort all pending requests
+ for ( var key in xhrCallbacks ) {
+ xhrCallbacks[ key ]( 0, 1 );
+ }
+ } : false,
+ xhrId = 0;
+
+// Functions to create xhrs
+function createStandardXHR() {
+ try {
+ return new window.XMLHttpRequest();
+ } catch( e ) {}
+}
+
+function createActiveXHR() {
+ try {
+ return new window.ActiveXObject( "Microsoft.XMLHTTP" );
+ } catch( e ) {}
+}
+
+// Create the request object
+// (This is still attached to ajaxSettings for backward compatibility)
+jQuery.ajaxSettings.xhr = window.ActiveXObject ?
+ /* Microsoft failed to properly
+ * implement the XMLHttpRequest in IE7 (can't request local files),
+ * so we use the ActiveXObject when it is available
+ * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
+ * we need a fallback.
+ */
+ function() {
+ return !this.isLocal && createStandardXHR() || createActiveXHR();
+ } :
+ // For all other browsers, use the standard XMLHttpRequest object
+ createStandardXHR;
+
+// Determine support properties
+(function( xhr ) {
+ jQuery.extend( jQuery.support, {
+ ajax: !!xhr,
+ cors: !!xhr && ( "withCredentials" in xhr )
+ });
+})( jQuery.ajaxSettings.xhr() );
+
+// Create transport if the browser can provide an xhr
+if ( jQuery.support.ajax ) {
+
+ jQuery.ajaxTransport(function( s ) {
+ // Cross domain only allowed if supported through XMLHttpRequest
+ if ( !s.crossDomain || jQuery.support.cors ) {
+
+ var callback;
+
+ return {
+ send: function( headers, complete ) {
+
+ // Get a new xhr
+ var handle, i,
+ xhr = s.xhr();
+
+ // Open the socket
+ // Passing null username, generates a login popup on Opera (#2865)
+ if ( s.username ) {
+ xhr.open( s.type, s.url, s.async, s.username, s.password );
+ } else {
+ xhr.open( s.type, s.url, s.async );
+ }
+
+ // Apply custom fields if provided
+ if ( s.xhrFields ) {
+ for ( i in s.xhrFields ) {
+ xhr[ i ] = s.xhrFields[ i ];
+ }
+ }
+
+ // Override mime type if needed
+ if ( s.mimeType && xhr.overrideMimeType ) {
+ xhr.overrideMimeType( s.mimeType );
+ }
+
+ // X-Requested-With header
+ // For cross-domain requests, seeing as conditions for a preflight are
+ // akin to a jigsaw puzzle, we simply never set it to be sure.
+ // (it can always be set on a per-request basis or even using ajaxSetup)
+ // For same-domain requests, won't change header if already provided.
+ if ( !s.crossDomain && !headers["X-Requested-With"] ) {
+ headers[ "X-Requested-With" ] = "XMLHttpRequest";
+ }
+
+ // Need an extra try/catch for cross domain requests in Firefox 3
+ try {
+ for ( i in headers ) {
+ xhr.setRequestHeader( i, headers[ i ] );
+ }
+ } catch( _ ) {}
+
+ // Do send the request
+ // This may raise an exception which is actually
+ // handled in jQuery.ajax (so no try/catch here)
+ xhr.send( ( s.hasContent && s.data ) || null );
+
+ // Listener
+ callback = function( _, isAbort ) {
+
+ var status,
+ statusText,
+ responseHeaders,
+ responses,
+ xml;
+
+ // Firefox throws exceptions when accessing properties
+ // of an xhr when a network error occurred
+ // http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
+ try {
+
+ // Was never called and is aborted or complete
+ if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
+
+ // Only called once
+ callback = undefined;
+
+ // Do not keep as active anymore
+ if ( handle ) {
+ xhr.onreadystatechange = jQuery.noop;
+ if ( xhrOnUnloadAbort ) {
+ delete xhrCallbacks[ handle ];
+ }
+ }
+
+ // If it's an abort
+ if ( isAbort ) {
+ // Abort it manually if needed
+ if ( xhr.readyState !== 4 ) {
+ xhr.abort();
+ }
+ } else {
+ status = xhr.status;
+ responseHeaders = xhr.getAllResponseHeaders();
+ responses = {};
+ xml = xhr.responseXML;
+
+ // Construct response list
+ if ( xml && xml.documentElement /* #4958 */ ) {
+ responses.xml = xml;
+ }
+
+ // When requesting binary data, IE6-9 will throw an exception
+ // on any attempt to access responseText (#11426)
+ try {
+ responses.text = xhr.responseText;
+ } catch( _ ) {
+ }
+
+ // Firefox throws an exception when accessing
+ // statusText for faulty cross-domain requests
+ try {
+ statusText = xhr.statusText;
+ } catch( e ) {
+ // We normalize with Webkit giving an empty statusText
+ statusText = "";
+ }
+
+ // Filter status for non standard behaviors
+
+ // If the request is local and we have data: assume a success
+ // (success with no data won't get notified, that's the best we
+ // can do given current implementations)
+ if ( !status && s.isLocal && !s.crossDomain ) {
+ status = responses.text ? 200 : 404;
+ // IE - #1450: sometimes returns 1223 when it should be 204
+ } else if ( status === 1223 ) {
+ status = 204;
+ }
+ }
+ }
+ } catch( firefoxAccessException ) {
+ if ( !isAbort ) {
+ complete( -1, firefoxAccessException );
+ }
+ }
+
+ // Call complete if needed
+ if ( responses ) {
+ complete( status, statusText, responses, responseHeaders );
+ }
+ };
+
+ if ( !s.async ) {
+ // if we're in sync mode we fire the callback
+ callback();
+ } else if ( xhr.readyState === 4 ) {
+ // (IE6 & IE7) if it's in cache and has been
+ // retrieved directly we need to fire the callback
+ setTimeout( callback, 0 );
+ } else {
+ handle = ++xhrId;
+ if ( xhrOnUnloadAbort ) {
+ // Create the active xhrs callbacks list if needed
+ // and attach the unload handler
+ if ( !xhrCallbacks ) {
+ xhrCallbacks = {};
+ jQuery( window ).unload( xhrOnUnloadAbort );
+ }
+ // Add to list of active xhrs callbacks
+ xhrCallbacks[ handle ] = callback;
+ }
+ xhr.onreadystatechange = callback;
+ }
+ },
+
+ abort: function() {
+ if ( callback ) {
+ callback(0,1);
+ }
+ }
+ };
+ }
+ });
+}
+var fxNow, timerId,
+ rfxtypes = /^(?:toggle|show|hide)$/,
+ rfxnum = new RegExp( "^(?:([-+])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
+ rrun = /queueHooks$/,
+ animationPrefilters = [ defaultPrefilter ],
+ tweeners = {
+ "*": [function( prop, value ) {
+ var end, unit,
+ tween = this.createTween( prop, value ),
+ parts = rfxnum.exec( value ),
+ target = tween.cur(),
+ start = +target || 0,
+ scale = 1,
+ maxIterations = 20;
+
+ if ( parts ) {
+ end = +parts[2];
+ unit = parts[3] || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+
+ // We need to compute starting value
+ if ( unit !== "px" && start ) {
+ // Iteratively approximate from a nonzero starting point
+ // Prefer the current property, because this process will be trivial if it uses the same units
+ // Fallback to end or a simple constant
+ start = jQuery.css( tween.elem, prop, true ) || end || 1;
+
+ do {
+ // If previous iteration zeroed out, double until we get *something*
+ // Use a string for doubling factor so we don't accidentally see scale as unchanged below
+ scale = scale || ".5";
+
+ // Adjust and apply
+ start = start / scale;
+ jQuery.style( tween.elem, prop, start + unit );
+
+ // Update scale, tolerating zero or NaN from tween.cur()
+ // And breaking the loop if scale is unchanged or perfect, or if we've just had enough
+ } while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
+ }
+
+ tween.unit = unit;
+ tween.start = start;
+ // If a +=/-= token was provided, we're doing a relative animation
+ tween.end = parts[1] ? start + ( parts[1] + 1 ) * end : end;
+ }
+ return tween;
+ }]
+ };
+
+// Animations created synchronously will run synchronously
+function createFxNow() {
+ setTimeout(function() {
+ fxNow = undefined;
+ }, 0 );
+ return ( fxNow = jQuery.now() );
+}
+
+function createTweens( animation, props ) {
+ jQuery.each( props, function( prop, value ) {
+ var collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
+ index = 0,
+ length = collection.length;
+ for ( ; index < length; index++ ) {
+ if ( collection[ index ].call( animation, prop, value ) ) {
+
+ // we're done with this property
+ return;
+ }
+ }
+ });
+}
+
+function Animation( elem, properties, options ) {
+ var result,
+ index = 0,
+ tweenerIndex = 0,
+ length = animationPrefilters.length,
+ deferred = jQuery.Deferred().always( function() {
+ // don't match elem in the :animated selector
+ delete tick.elem;
+ }),
+ tick = function() {
+ var currentTime = fxNow || createFxNow(),
+ remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
+ percent = 1 - ( remaining / animation.duration || 0 ),
+ index = 0,
+ length = animation.tweens.length;
+
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( percent );
+ }
+
+ deferred.notifyWith( elem, [ animation, percent, remaining ]);
+
+ if ( percent < 1 && length ) {
+ return remaining;
+ } else {
+ deferred.resolveWith( elem, [ animation ] );
+ return false;
+ }
+ },
+ animation = deferred.promise({
+ elem: elem,
+ props: jQuery.extend( {}, properties ),
+ opts: jQuery.extend( true, { specialEasing: {} }, options ),
+ originalProperties: properties,
+ originalOptions: options,
+ startTime: fxNow || createFxNow(),
+ duration: options.duration,
+ tweens: [],
+ createTween: function( prop, end, easing ) {
+ var tween = jQuery.Tween( elem, animation.opts, prop, end,
+ animation.opts.specialEasing[ prop ] || animation.opts.easing );
+ animation.tweens.push( tween );
+ return tween;
+ },
+ stop: function( gotoEnd ) {
+ var index = 0,
+ // if we are going to the end, we want to run all the tweens
+ // otherwise we skip this part
+ length = gotoEnd ? animation.tweens.length : 0;
+
+ for ( ; index < length ; index++ ) {
+ animation.tweens[ index ].run( 1 );
+ }
+
+ // resolve when we played the last frame
+ // otherwise, reject
+ if ( gotoEnd ) {
+ deferred.resolveWith( elem, [ animation, gotoEnd ] );
+ } else {
+ deferred.rejectWith( elem, [ animation, gotoEnd ] );
+ }
+ return this;
+ }
+ }),
+ props = animation.props;
+
+ propFilter( props, animation.opts.specialEasing );
+
+ for ( ; index < length ; index++ ) {
+ result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
+ if ( result ) {
+ return result;
+ }
+ }
+
+ createTweens( animation, props );
+
+ if ( jQuery.isFunction( animation.opts.start ) ) {
+ animation.opts.start.call( elem, animation );
+ }
+
+ jQuery.fx.timer(
+ jQuery.extend( tick, {
+ anim: animation,
+ queue: animation.opts.queue,
+ elem: elem
+ })
+ );
+
+ // attach callbacks from options
+ return animation.progress( animation.opts.progress )
+ .done( animation.opts.done, animation.opts.complete )
+ .fail( animation.opts.fail )
+ .always( animation.opts.always );
+}
+
+function propFilter( props, specialEasing ) {
+ var index, name, easing, value, hooks;
+
+ // camelCase, specialEasing and expand cssHook pass
+ for ( index in props ) {
+ name = jQuery.camelCase( index );
+ easing = specialEasing[ name ];
+ value = props[ index ];
+ if ( jQuery.isArray( value ) ) {
+ easing = value[ 1 ];
+ value = props[ index ] = value[ 0 ];
+ }
+
+ if ( index !== name ) {
+ props[ name ] = value;
+ delete props[ index ];
+ }
+
+ hooks = jQuery.cssHooks[ name ];
+ if ( hooks && "expand" in hooks ) {
+ value = hooks.expand( value );
+ delete props[ name ];
+
+ // not quite $.extend, this wont overwrite keys already present.
+ // also - reusing 'index' from above because we have the correct "name"
+ for ( index in value ) {
+ if ( !( index in props ) ) {
+ props[ index ] = value[ index ];
+ specialEasing[ index ] = easing;
+ }
+ }
+ } else {
+ specialEasing[ name ] = easing;
+ }
+ }
+}
+
+jQuery.Animation = jQuery.extend( Animation, {
+
+ tweener: function( props, callback ) {
+ if ( jQuery.isFunction( props ) ) {
+ callback = props;
+ props = [ "*" ];
+ } else {
+ props = props.split(" ");
+ }
+
+ var prop,
+ index = 0,
+ length = props.length;
+
+ for ( ; index < length ; index++ ) {
+ prop = props[ index ];
+ tweeners[ prop ] = tweeners[ prop ] || [];
+ tweeners[ prop ].unshift( callback );
+ }
+ },
+
+ prefilter: function( callback, prepend ) {
+ if ( prepend ) {
+ animationPrefilters.unshift( callback );
+ } else {
+ animationPrefilters.push( callback );
+ }
+ }
+});
+
+function defaultPrefilter( elem, props, opts ) {
+ var index, prop, value, length, dataShow, tween, hooks, oldfire,
+ anim = this,
+ style = elem.style,
+ orig = {},
+ handled = [],
+ hidden = elem.nodeType && isHidden( elem );
+
+ // handle queue: false promises
+ if ( !opts.queue ) {
+ hooks = jQuery._queueHooks( elem, "fx" );
+ if ( hooks.unqueued == null ) {
+ hooks.unqueued = 0;
+ oldfire = hooks.empty.fire;
+ hooks.empty.fire = function() {
+ if ( !hooks.unqueued ) {
+ oldfire();
+ }
+ };
+ }
+ hooks.unqueued++;
+
+ anim.always(function() {
+ // doing this makes sure that the complete handler will be called
+ // before this completes
+ anim.always(function() {
+ hooks.unqueued--;
+ if ( !jQuery.queue( elem, "fx" ).length ) {
+ hooks.empty.fire();
+ }
+ });
+ });
+ }
+
+ // height/width overflow pass
+ if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
+ // Make sure that nothing sneaks out
+ // Record all 3 overflow attributes because IE does not
+ // change the overflow attribute when overflowX and
+ // overflowY are set to the same value
+ opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
+
+ // Set display property to inline-block for height/width
+ // animations on inline elements that are having width/height animated
+ if ( jQuery.css( elem, "display" ) === "inline" &&
+ jQuery.css( elem, "float" ) === "none" ) {
+
+ // inline-level elements accept inline-block;
+ // block-level elements need to be inline with layout
+ if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) {
+ style.display = "inline-block";
+
+ } else {
+ style.zoom = 1;
+ }
+ }
+ }
+
+ if ( opts.overflow ) {
+ style.overflow = "hidden";
+ if ( !jQuery.support.shrinkWrapBlocks ) {
+ anim.done(function() {
+ style.overflow = opts.overflow[ 0 ];
+ style.overflowX = opts.overflow[ 1 ];
+ style.overflowY = opts.overflow[ 2 ];
+ });
+ }
+ }
+
+
+ // show/hide pass
+ for ( index in props ) {
+ value = props[ index ];
+ if ( rfxtypes.exec( value ) ) {
+ delete props[ index ];
+ if ( value === ( hidden ? "hide" : "show" ) ) {
+ continue;
+ }
+ handled.push( index );
+ }
+ }
+
+ length = handled.length;
+ if ( length ) {
+ dataShow = jQuery._data( elem, "fxshow" ) || jQuery._data( elem, "fxshow", {} );
+ if ( hidden ) {
+ jQuery( elem ).show();
+ } else {
+ anim.done(function() {
+ jQuery( elem ).hide();
+ });
+ }
+ anim.done(function() {
+ var prop;
+ jQuery.removeData( elem, "fxshow", true );
+ for ( prop in orig ) {
+ jQuery.style( elem, prop, orig[ prop ] );
+ }
+ });
+ for ( index = 0 ; index < length ; index++ ) {
+ prop = handled[ index ];
+ tween = anim.createTween( prop, hidden ? dataShow[ prop ] : 0 );
+ orig[ prop ] = dataShow[ prop ] || jQuery.style( elem, prop );
+
+ if ( !( prop in dataShow ) ) {
+ dataShow[ prop ] = tween.start;
+ if ( hidden ) {
+ tween.end = tween.start;
+ tween.start = prop === "width" || prop === "height" ? 1 : 0;
+ }
+ }
+ }
+ }
+}
+
+function Tween( elem, options, prop, end, easing ) {
+ return new Tween.prototype.init( elem, options, prop, end, easing );
+}
+jQuery.Tween = Tween;
+
+Tween.prototype = {
+ constructor: Tween,
+ init: function( elem, options, prop, end, easing, unit ) {
+ this.elem = elem;
+ this.prop = prop;
+ this.easing = easing || "swing";
+ this.options = options;
+ this.start = this.now = this.cur();
+ this.end = end;
+ this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
+ },
+ cur: function() {
+ var hooks = Tween.propHooks[ this.prop ];
+
+ return hooks && hooks.get ?
+ hooks.get( this ) :
+ Tween.propHooks._default.get( this );
+ },
+ run: function( percent ) {
+ var eased,
+ hooks = Tween.propHooks[ this.prop ];
+
+ if ( this.options.duration ) {
+ this.pos = eased = jQuery.easing[ this.easing ](
+ percent, this.options.duration * percent, 0, 1, this.options.duration
+ );
+ } else {
+ this.pos = eased = percent;
+ }
+ this.now = ( this.end - this.start ) * eased + this.start;
+
+ if ( this.options.step ) {
+ this.options.step.call( this.elem, this.now, this );
+ }
+
+ if ( hooks && hooks.set ) {
+ hooks.set( this );
+ } else {
+ Tween.propHooks._default.set( this );
+ }
+ return this;
+ }
+};
+
+Tween.prototype.init.prototype = Tween.prototype;
+
+Tween.propHooks = {
+ _default: {
+ get: function( tween ) {
+ var result;
+
+ if ( tween.elem[ tween.prop ] != null &&
+ (!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
+ return tween.elem[ tween.prop ];
+ }
+
+ // passing any value as a 4th parameter to .css will automatically
+ // attempt a parseFloat and fallback to a string if the parse fails
+ // so, simple values such as "10px" are parsed to Float.
+ // complex values such as "rotate(1rad)" are returned as is.
+ result = jQuery.css( tween.elem, tween.prop, false, "" );
+ // Empty strings, null, undefined and "auto" are converted to 0.
+ return !result || result === "auto" ? 0 : result;
+ },
+ set: function( tween ) {
+ // use step hook for back compat - use cssHook if its there - use .style if its
+ // available and use plain properties where available
+ if ( jQuery.fx.step[ tween.prop ] ) {
+ jQuery.fx.step[ tween.prop ]( tween );
+ } else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
+ jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
+ } else {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+ }
+};
+
+// Remove in 2.0 - this supports IE8's panic based approach
+// to setting things on disconnected nodes
+
+Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
+ set: function( tween ) {
+ if ( tween.elem.nodeType && tween.elem.parentNode ) {
+ tween.elem[ tween.prop ] = tween.now;
+ }
+ }
+};
+
+jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
+ var cssFn = jQuery.fn[ name ];
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return speed == null || typeof speed === "boolean" ||
+ // special check for .toggle( handler, handler, ... )
+ ( !i && jQuery.isFunction( speed ) && jQuery.isFunction( easing ) ) ?
+ cssFn.apply( this, arguments ) :
+ this.animate( genFx( name, true ), speed, easing, callback );
+ };
+});
+
+jQuery.fn.extend({
+ fadeTo: function( speed, to, easing, callback ) {
+
+ // show any hidden elements after setting opacity to 0
+ return this.filter( isHidden ).css( "opacity", 0 ).show()
+
+ // animate to the value specified
+ .end().animate({ opacity: to }, speed, easing, callback );
+ },
+ animate: function( prop, speed, easing, callback ) {
+ var empty = jQuery.isEmptyObject( prop ),
+ optall = jQuery.speed( speed, easing, callback ),
+ doAnimation = function() {
+ // Operate on a copy of prop so per-property easing won't be lost
+ var anim = Animation( this, jQuery.extend( {}, prop ), optall );
+
+ // Empty animations resolve immediately
+ if ( empty ) {
+ anim.stop( true );
+ }
+ };
+
+ return empty || optall.queue === false ?
+ this.each( doAnimation ) :
+ this.queue( optall.queue, doAnimation );
+ },
+ stop: function( type, clearQueue, gotoEnd ) {
+ var stopQueue = function( hooks ) {
+ var stop = hooks.stop;
+ delete hooks.stop;
+ stop( gotoEnd );
+ };
+
+ if ( typeof type !== "string" ) {
+ gotoEnd = clearQueue;
+ clearQueue = type;
+ type = undefined;
+ }
+ if ( clearQueue && type !== false ) {
+ this.queue( type || "fx", [] );
+ }
+
+ return this.each(function() {
+ var dequeue = true,
+ index = type != null && type + "queueHooks",
+ timers = jQuery.timers,
+ data = jQuery._data( this );
+
+ if ( index ) {
+ if ( data[ index ] && data[ index ].stop ) {
+ stopQueue( data[ index ] );
+ }
+ } else {
+ for ( index in data ) {
+ if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
+ stopQueue( data[ index ] );
+ }
+ }
+ }
+
+ for ( index = timers.length; index--; ) {
+ if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
+ timers[ index ].anim.stop( gotoEnd );
+ dequeue = false;
+ timers.splice( index, 1 );
+ }
+ }
+
+ // start the next in the queue if the last step wasn't forced
+ // timers currently will call their complete callbacks, which will dequeue
+ // but only if they were gotoEnd
+ if ( dequeue || !gotoEnd ) {
+ jQuery.dequeue( this, type );
+ }
+ });
+ }
+});
+
+// Generate parameters to create a standard animation
+function genFx( type, includeWidth ) {
+ var which,
+ attrs = { height: type },
+ i = 0;
+
+ // if we include width, step value is 1 to do all cssExpand values,
+ // if we don't include width, step value is 2 to skip over Left and Right
+ includeWidth = includeWidth? 1 : 0;
+ for( ; i < 4 ; i += 2 - includeWidth ) {
+ which = cssExpand[ i ];
+ attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
+ }
+
+ if ( includeWidth ) {
+ attrs.opacity = attrs.width = type;
+ }
+
+ return attrs;
+}
+
+// Generate shortcuts for custom animations
+jQuery.each({
+ slideDown: genFx("show"),
+ slideUp: genFx("hide"),
+ slideToggle: genFx("toggle"),
+ fadeIn: { opacity: "show" },
+ fadeOut: { opacity: "hide" },
+ fadeToggle: { opacity: "toggle" }
+}, function( name, props ) {
+ jQuery.fn[ name ] = function( speed, easing, callback ) {
+ return this.animate( props, speed, easing, callback );
+ };
+});
+
+jQuery.speed = function( speed, easing, fn ) {
+ var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
+ complete: fn || !fn && easing ||
+ jQuery.isFunction( speed ) && speed,
+ duration: speed,
+ easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
+ };
+
+ opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
+ opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
+
+ // normalize opt.queue - true/undefined/null -> "fx"
+ if ( opt.queue == null || opt.queue === true ) {
+ opt.queue = "fx";
+ }
+
+ // Queueing
+ opt.old = opt.complete;
+
+ opt.complete = function() {
+ if ( jQuery.isFunction( opt.old ) ) {
+ opt.old.call( this );
+ }
+
+ if ( opt.queue ) {
+ jQuery.dequeue( this, opt.queue );
+ }
+ };
+
+ return opt;
+};
+
+jQuery.easing = {
+ linear: function( p ) {
+ return p;
+ },
+ swing: function( p ) {
+ return 0.5 - Math.cos( p*Math.PI ) / 2;
+ }
+};
+
+jQuery.timers = [];
+jQuery.fx = Tween.prototype.init;
+jQuery.fx.tick = function() {
+ var timer,
+ timers = jQuery.timers,
+ i = 0;
+
+ for ( ; i < timers.length; i++ ) {
+ timer = timers[ i ];
+ // Checks the timer has not already been removed
+ if ( !timer() && timers[ i ] === timer ) {
+ timers.splice( i--, 1 );
+ }
+ }
+
+ if ( !timers.length ) {
+ jQuery.fx.stop();
+ }
+};
+
+jQuery.fx.timer = function( timer ) {
+ if ( timer() && jQuery.timers.push( timer ) && !timerId ) {
+ timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
+ }
+};
+
+jQuery.fx.interval = 13;
+
+jQuery.fx.stop = function() {
+ clearInterval( timerId );
+ timerId = null;
+};
+
+jQuery.fx.speeds = {
+ slow: 600,
+ fast: 200,
+ // Default speed
+ _default: 400
+};
+
+// Back Compat <1.8 extension point
+jQuery.fx.step = {};
+
+if ( jQuery.expr && jQuery.expr.filters ) {
+ jQuery.expr.filters.animated = function( elem ) {
+ return jQuery.grep(jQuery.timers, function( fn ) {
+ return elem === fn.elem;
+ }).length;
+ };
+}
+var rroot = /^(?:body|html)$/i;
+
+jQuery.fn.offset = function( options ) {
+ if ( arguments.length ) {
+ return options === undefined ?
+ this :
+ this.each(function( i ) {
+ jQuery.offset.setOffset( this, options, i );
+ });
+ }
+
+ var docElem, body, win, clientTop, clientLeft, scrollTop, scrollLeft,
+ box = { top: 0, left: 0 },
+ elem = this[ 0 ],
+ doc = elem && elem.ownerDocument;
+
+ if ( !doc ) {
+ return;
+ }
+
+ if ( (body = doc.body) === elem ) {
+ return jQuery.offset.bodyOffset( elem );
+ }
+
+ docElem = doc.documentElement;
+
+ // Make sure it's not a disconnected DOM node
+ if ( !jQuery.contains( docElem, elem ) ) {
+ return box;
+ }
+
+ // If we don't have gBCR, just use 0,0 rather than error
+ // BlackBerry 5, iOS 3 (original iPhone)
+ if ( typeof elem.getBoundingClientRect !== "undefined" ) {
+ box = elem.getBoundingClientRect();
+ }
+ win = getWindow( doc );
+ clientTop = docElem.clientTop || body.clientTop || 0;
+ clientLeft = docElem.clientLeft || body.clientLeft || 0;
+ scrollTop = win.pageYOffset || docElem.scrollTop;
+ scrollLeft = win.pageXOffset || docElem.scrollLeft;
+ return {
+ top: box.top + scrollTop - clientTop,
+ left: box.left + scrollLeft - clientLeft
+ };
+};
+
+jQuery.offset = {
+
+ bodyOffset: function( body ) {
+ var top = body.offsetTop,
+ left = body.offsetLeft;
+
+ if ( jQuery.support.doesNotIncludeMarginInBodyOffset ) {
+ top += parseFloat( jQuery.css(body, "marginTop") ) || 0;
+ left += parseFloat( jQuery.css(body, "marginLeft") ) || 0;
+ }
+
+ return { top: top, left: left };
+ },
+
+ setOffset: function( elem, options, i ) {
+ var position = jQuery.css( elem, "position" );
+
+ // set position first, in-case top/left are set even on static elem
+ if ( position === "static" ) {
+ elem.style.position = "relative";
+ }
+
+ var curElem = jQuery( elem ),
+ curOffset = curElem.offset(),
+ curCSSTop = jQuery.css( elem, "top" ),
+ curCSSLeft = jQuery.css( elem, "left" ),
+ calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
+ props = {}, curPosition = {}, curTop, curLeft;
+
+ // need to be able to calculate position if either top or left is auto and position is either absolute or fixed
+ if ( calculatePosition ) {
+ curPosition = curElem.position();
+ curTop = curPosition.top;
+ curLeft = curPosition.left;
+ } else {
+ curTop = parseFloat( curCSSTop ) || 0;
+ curLeft = parseFloat( curCSSLeft ) || 0;
+ }
+
+ if ( jQuery.isFunction( options ) ) {
+ options = options.call( elem, i, curOffset );
+ }
+
+ if ( options.top != null ) {
+ props.top = ( options.top - curOffset.top ) + curTop;
+ }
+ if ( options.left != null ) {
+ props.left = ( options.left - curOffset.left ) + curLeft;
+ }
+
+ if ( "using" in options ) {
+ options.using.call( elem, props );
+ } else {
+ curElem.css( props );
+ }
+ }
+};
+
+
+jQuery.fn.extend({
+
+ position: function() {
+ if ( !this[0] ) {
+ return;
+ }
+
+ var elem = this[0],
+
+ // Get *real* offsetParent
+ offsetParent = this.offsetParent(),
+
+ // Get correct offsets
+ offset = this.offset(),
+ parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset();
+
+ // Subtract element margins
+ // note: when an element has margin: auto the offsetLeft and marginLeft
+ // are the same in Safari causing offset.left to incorrectly be 0
+ offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0;
+ offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0;
+
+ // Add offsetParent borders
+ parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0;
+ parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0;
+
+ // Subtract the two offsets
+ return {
+ top: offset.top - parentOffset.top,
+ left: offset.left - parentOffset.left
+ };
+ },
+
+ offsetParent: function() {
+ return this.map(function() {
+ var offsetParent = this.offsetParent || document.body;
+ while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) {
+ offsetParent = offsetParent.offsetParent;
+ }
+ return offsetParent || document.body;
+ });
+ }
+});
+
+
+// Create scrollLeft and scrollTop methods
+jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
+ var top = /Y/.test( prop );
+
+ jQuery.fn[ method ] = function( val ) {
+ return jQuery.access( this, function( elem, method, val ) {
+ var win = getWindow( elem );
+
+ if ( val === undefined ) {
+ return win ? (prop in win) ? win[ prop ] :
+ win.document.documentElement[ method ] :
+ elem[ method ];
+ }
+
+ if ( win ) {
+ win.scrollTo(
+ !top ? val : jQuery( win ).scrollLeft(),
+ top ? val : jQuery( win ).scrollTop()
+ );
+
+ } else {
+ elem[ method ] = val;
+ }
+ }, method, val, arguments.length, null );
+ };
+});
+
+function getWindow( elem ) {
+ return jQuery.isWindow( elem ) ?
+ elem :
+ elem.nodeType === 9 ?
+ elem.defaultView || elem.parentWindow :
+ false;
+}
+// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
+jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
+ jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
+ // margin is only for outerHeight, outerWidth
+ jQuery.fn[ funcName ] = function( margin, value ) {
+ var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
+ extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
+
+ return jQuery.access( this, function( elem, type, value ) {
+ var doc;
+
+ if ( jQuery.isWindow( elem ) ) {
+ // As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
+ // isn't a whole lot we can do. See pull request at this URL for discussion:
+ // https://github.com/jquery/jquery/pull/764
+ return elem.document.documentElement[ "client" + name ];
+ }
+
+ // Get document width or height
+ if ( elem.nodeType === 9 ) {
+ doc = elem.documentElement;
+
+ // Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
+ // unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
+ return Math.max(
+ elem.body[ "scroll" + name ], doc[ "scroll" + name ],
+ elem.body[ "offset" + name ], doc[ "offset" + name ],
+ doc[ "client" + name ]
+ );
+ }
+
+ return value === undefined ?
+ // Get width or height on the element, requesting but not forcing parseFloat
+ jQuery.css( elem, type, value, extra ) :
+
+ // Set width or height on the element
+ jQuery.style( elem, type, value, extra );
+ }, type, chainable ? margin : undefined, chainable, null );
+ };
+ });
+});
+// Expose jQuery to the global object
+window.jQuery = window.$ = jQuery;
+
+// Expose jQuery as an AMD module, but only for AMD loaders that
+// understand the issues with loading multiple versions of jQuery
+// in a page that all might call define(). The loader will indicate
+// they have special allowances for multiple jQuery versions by
+// specifying define.amd.jQuery = true. Register as a named module,
+// since jQuery can be concatenated with other files that may use define,
+// but not use a proper concatenation script that understands anonymous
+// AMD modules. A named AMD is safest and most robust way to register.
+// Lowercase jquery is used because AMD module names are derived from
+// file names, and jQuery is normally delivered in a lowercase file name.
+// Do this after creating the global so that if an AMD module wants to call
+// noConflict to hide this version of jQuery, it will work.
+if ( typeof define === "function" && define.amd && define.amd.jQuery ) {
+ define( "jquery", [], function () { return jQuery; } );
+}
+
+})( window );
diff --git a/jquery/jquery-ui.css b/jquery/jquery-ui.css
new file mode 100644
index 0000000..a76b9ab
--- /dev/null
+++ b/jquery/jquery-ui.css
@@ -0,0 +1,470 @@
+/*! jQuery UI - v1.9.0 - 2012-10-05
+* http://jqueryui.com
+* Includes: jquery.ui.core.css, jquery.ui.accordion.css, jquery.ui.autocomplete.css, jquery.ui.button.css, jquery.ui.datepicker.css, jquery.ui.dialog.css, jquery.ui.menu.css, jquery.ui.progressbar.css, jquery.ui.resizable.css, jquery.ui.selectable.css, jquery.ui.slider.css, jquery.ui.spinner.css, jquery.ui.tabs.css, jquery.ui.tooltip.css, jquery.ui.theme.css
+* Copyright 2012 jQuery Foundation and other contributors; Licensed MIT */
+
+/* Layout helpers
+----------------------------------*/
+.ui-helper-hidden { display: none; }
+.ui-helper-hidden-accessible { position: absolute !important; clip: rect(1px 1px 1px 1px); clip: rect(1px,1px,1px,1px); }
+.ui-helper-reset { margin: 0; padding: 0; border: 0; outline: 0; line-height: 1.3; text-decoration: none; font-size: 100%; list-style: none; }
+.ui-helper-clearfix:before, .ui-helper-clearfix:after { content: ""; display: table; }
+.ui-helper-clearfix:after { clear: both; }
+.ui-helper-clearfix { zoom: 1; }
+.ui-helper-zfix { width: 100%; height: 100%; top: 0; left: 0; position: absolute; opacity: 0; filter:Alpha(Opacity=0); }
+
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-disabled { cursor: default !important; }
+
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { display: block; text-indent: -99999px; overflow: hidden; background-repeat: no-repeat; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Overlays */
+.ui-widget-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }
+
+.ui-accordion .ui-accordion-header { display: block; cursor: pointer; position: relative; margin-top: 2px; padding: .5em .5em .5em .7em; zoom: 1; }
+.ui-accordion .ui-accordion-icons { padding-left: 2.2em; }
+.ui-accordion .ui-accordion-noicons { padding-left: .7em; }
+.ui-accordion .ui-accordion-icons .ui-accordion-icons { padding-left: 2.2em; }
+.ui-accordion .ui-accordion-header .ui-accordion-header-icon { position: absolute; left: .5em; top: 50%; margin-top: -8px; }
+.ui-accordion .ui-accordion-content { padding: 1em 2.2em; border-top: 0; overflow: auto; zoom: 1; }
+
+.ui-autocomplete { position: absolute; cursor: default; }
+
+/* workarounds */
+* html .ui-autocomplete { width:1px; } /* without this, the menu expands to 100% in IE6 */
+
+.ui-button { display: inline-block; position: relative; padding: 0; margin-right: .1em; cursor: pointer; text-align: center; zoom: 1; overflow: visible; } /* the overflow property removes extra width in IE */
+.ui-button, .ui-button:link, .ui-button:visited, .ui-button:hover, .ui-button:active { text-decoration: none; }
+.ui-button-icon-only { width: 2.2em; } /* to make room for the icon, a width needs to be set here */
+button.ui-button-icon-only { width: 2.4em; } /* button elements seem to need a little more width */
+.ui-button-icons-only { width: 3.4em; }
+button.ui-button-icons-only { width: 3.7em; }
+
+/*button text element */
+.ui-button .ui-button-text { display: block; line-height: 1.4; }
+.ui-button-text-only .ui-button-text { padding: .4em 1em; }
+.ui-button-icon-only .ui-button-text, .ui-button-icons-only .ui-button-text { padding: .4em; text-indent: -9999999px; }
+.ui-button-text-icon-primary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 1em .4em 2.1em; }
+.ui-button-text-icon-secondary .ui-button-text, .ui-button-text-icons .ui-button-text { padding: .4em 2.1em .4em 1em; }
+.ui-button-text-icons .ui-button-text { padding-left: 2.1em; padding-right: 2.1em; }
+/* no icon support for input elements, provide padding by default */
+input.ui-button { padding: .4em 1em; }
+
+/*button icon element(s) */
+.ui-button-icon-only .ui-icon, .ui-button-text-icon-primary .ui-icon, .ui-button-text-icon-secondary .ui-icon, .ui-button-text-icons .ui-icon, .ui-button-icons-only .ui-icon { position: absolute; top: 50%; margin-top: -8px; }
+.ui-button-icon-only .ui-icon { left: 50%; margin-left: -8px; }
+.ui-button-text-icon-primary .ui-button-icon-primary, .ui-button-text-icons .ui-button-icon-primary, .ui-button-icons-only .ui-button-icon-primary { left: .5em; }
+.ui-button-text-icon-secondary .ui-button-icon-secondary, .ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+.ui-button-text-icons .ui-button-icon-secondary, .ui-button-icons-only .ui-button-icon-secondary { right: .5em; }
+
+/*button sets*/
+.ui-buttonset { margin-right: 7px; }
+.ui-buttonset .ui-button { margin-left: 0; margin-right: -.3em; }
+
+/* workarounds */
+button.ui-button::-moz-focus-inner { border: 0; padding: 0; } /* reset extra padding in Firefox */
+
+.ui-datepicker { width: 17em; padding: .2em .2em 0; display: none; }
+.ui-datepicker .ui-datepicker-header { position:relative; padding:.2em 0; }
+.ui-datepicker .ui-datepicker-prev, .ui-datepicker .ui-datepicker-next { position:absolute; top: 2px; width: 1.8em; height: 1.8em; }
+.ui-datepicker .ui-datepicker-prev-hover, .ui-datepicker .ui-datepicker-next-hover { top: 1px; }
+.ui-datepicker .ui-datepicker-prev { left:2px; }
+.ui-datepicker .ui-datepicker-next { right:2px; }
+.ui-datepicker .ui-datepicker-prev-hover { left:1px; }
+.ui-datepicker .ui-datepicker-next-hover { right:1px; }
+.ui-datepicker .ui-datepicker-prev span, .ui-datepicker .ui-datepicker-next span { display: block; position: absolute; left: 50%; margin-left: -8px; top: 50%; margin-top: -8px; }
+.ui-datepicker .ui-datepicker-title { margin: 0 2.3em; line-height: 1.8em; text-align: center; }
+.ui-datepicker .ui-datepicker-title select { font-size:1em; margin:1px 0; }
+.ui-datepicker select.ui-datepicker-month-year {width: 100%;}
+.ui-datepicker select.ui-datepicker-month,
+.ui-datepicker select.ui-datepicker-year { width: 49%;}
+.ui-datepicker table {width: 100%; font-size: .9em; border-collapse: collapse; margin:0 0 .4em; }
+.ui-datepicker th { padding: .7em .3em; text-align: center; font-weight: bold; border: 0; }
+.ui-datepicker td { border: 0; padding: 1px; }
+.ui-datepicker td span, .ui-datepicker td a { display: block; padding: .2em; text-align: right; text-decoration: none; }
+.ui-datepicker .ui-datepicker-buttonpane { background-image: none; margin: .7em 0 0 0; padding:0 .2em; border-left: 0; border-right: 0; border-bottom: 0; }
+.ui-datepicker .ui-datepicker-buttonpane button { float: right; margin: .5em .2em .4em; cursor: pointer; padding: .2em .6em .3em .6em; width:auto; overflow:visible; }
+.ui-datepicker .ui-datepicker-buttonpane button.ui-datepicker-current { float:left; }
+
+/* with multiple calendars */
+.ui-datepicker.ui-datepicker-multi { width:auto; }
+.ui-datepicker-multi .ui-datepicker-group { float:left; }
+.ui-datepicker-multi .ui-datepicker-group table { width:95%; margin:0 auto .4em; }
+.ui-datepicker-multi-2 .ui-datepicker-group { width:50%; }
+.ui-datepicker-multi-3 .ui-datepicker-group { width:33.3%; }
+.ui-datepicker-multi-4 .ui-datepicker-group { width:25%; }
+.ui-datepicker-multi .ui-datepicker-group-last .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-group-middle .ui-datepicker-header { border-left-width:0; }
+.ui-datepicker-multi .ui-datepicker-buttonpane { clear:left; }
+.ui-datepicker-row-break { clear:both; width:100%; font-size:0em; }
+
+/* RTL support */
+.ui-datepicker-rtl { direction: rtl; }
+.ui-datepicker-rtl .ui-datepicker-prev { right: 2px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next { left: 2px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-prev:hover { right: 1px; left: auto; }
+.ui-datepicker-rtl .ui-datepicker-next:hover { left: 1px; right: auto; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane { clear:right; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button { float: left; }
+.ui-datepicker-rtl .ui-datepicker-buttonpane button.ui-datepicker-current { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group { float:right; }
+.ui-datepicker-rtl .ui-datepicker-group-last .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+.ui-datepicker-rtl .ui-datepicker-group-middle .ui-datepicker-header { border-right-width:0; border-left-width:1px; }
+
+/* IE6 IFRAME FIX (taken from datepicker 1.5.3 */
+.ui-datepicker-cover {
+ position: absolute; /*must have*/
+ z-index: -1; /*must have*/
+ filter: mask(); /*must have*/
+ top: -4px; /*must have*/
+ left: -4px; /*must have*/
+ width: 200px; /*must have*/
+ height: 200px; /*must have*/
+}
+.ui-dialog { position: absolute; padding: .2em; width: 300px; overflow: hidden; }
+.ui-dialog .ui-dialog-titlebar { padding: .4em 1em; position: relative; }
+.ui-dialog .ui-dialog-title { float: left; margin: .1em 16px .1em 0; }
+.ui-dialog .ui-dialog-titlebar-close { position: absolute; right: .3em; top: 50%; width: 19px; margin: -10px 0 0 0; padding: 1px; height: 18px; }
+.ui-dialog .ui-dialog-titlebar-close span { display: block; margin: 1px; }
+.ui-dialog .ui-dialog-titlebar-close:hover, .ui-dialog .ui-dialog-titlebar-close:focus { padding: 0; }
+.ui-dialog .ui-dialog-content { position: relative; border: 0; padding: .5em 1em; background: none; overflow: auto; zoom: 1; }
+.ui-dialog .ui-dialog-buttonpane { text-align: left; border-width: 1px 0 0 0; background-image: none; margin: .5em 0 0 0; padding: .3em 1em .5em .4em; }
+.ui-dialog .ui-dialog-buttonpane .ui-dialog-buttonset { float: right; }
+.ui-dialog .ui-dialog-buttonpane button { margin: .5em .4em .5em 0; cursor: pointer; }
+.ui-dialog .ui-resizable-se { width: 14px; height: 14px; right: 3px; bottom: 3px; }
+.ui-draggable .ui-dialog-titlebar { cursor: move; }
+
+.ui-menu { list-style:none; padding: 2px; margin: 0; display:block; outline: none; }
+.ui-menu .ui-menu { margin-top: -3px; position: absolute; }
+.ui-menu .ui-menu-item { margin: 0; padding: 0; zoom: 1; width: 100%; }
+.ui-menu .ui-menu-divider { margin: 5px -2px 5px -2px; height: 0; font-size: 0; line-height: 0; border-width: 1px 0 0 0; }
+.ui-menu .ui-menu-item a { text-decoration: none; display: block; padding: 2px .4em; line-height: 1.5; zoom: 1; font-weight: normal; }
+.ui-menu .ui-menu-item a.ui-state-focus,
+.ui-menu .ui-menu-item a.ui-state-active { font-weight: normal; margin: -1px; }
+
+.ui-menu .ui-state-disabled { font-weight: normal; margin: .4em 0 .2em; line-height: 1.5; }
+.ui-menu .ui-state-disabled a { cursor: default; }
+
+/* icon support */
+.ui-menu-icons { position: relative; }
+.ui-menu-icons .ui-menu-item a { position: relative; padding-left: 2em; }
+
+/* left-aligned */
+.ui-menu .ui-icon { position: absolute; top: .2em; left: .2em; }
+
+/* right-aligned */
+.ui-menu .ui-menu-icon { position: static; float: right; }
+
+.ui-progressbar { height:2em; text-align: left; overflow: hidden; }
+.ui-progressbar .ui-progressbar-value {margin: -1px; height:100%; }
+.ui-resizable { position: relative;}
+.ui-resizable-handle { position: absolute;font-size: 0.1px; display: block; }
+.ui-resizable-disabled .ui-resizable-handle, .ui-resizable-autohide .ui-resizable-handle { display: none; }
+.ui-resizable-n { cursor: n-resize; height: 7px; width: 100%; top: -5px; left: 0; }
+.ui-resizable-s { cursor: s-resize; height: 7px; width: 100%; bottom: -5px; left: 0; }
+.ui-resizable-e { cursor: e-resize; width: 7px; right: -5px; top: 0; height: 100%; }
+.ui-resizable-w { cursor: w-resize; width: 7px; left: -5px; top: 0; height: 100%; }
+.ui-resizable-se { cursor: se-resize; width: 12px; height: 12px; right: 1px; bottom: 1px; }
+.ui-resizable-sw { cursor: sw-resize; width: 9px; height: 9px; left: -5px; bottom: -5px; }
+.ui-resizable-nw { cursor: nw-resize; width: 9px; height: 9px; left: -5px; top: -5px; }
+.ui-resizable-ne { cursor: ne-resize; width: 9px; height: 9px; right: -5px; top: -5px;}
+.ui-selectable-helper { position: absolute; z-index: 100; border:1px dotted black; }
+
+.ui-slider { position: relative; text-align: left; }
+.ui-slider .ui-slider-handle { position: absolute; z-index: 2; width: 1.2em; height: 1.2em; cursor: default; }
+.ui-slider .ui-slider-range { position: absolute; z-index: 1; font-size: .7em; display: block; border: 0; background-position: 0 0; }
+
+.ui-slider-horizontal { height: .8em; }
+.ui-slider-horizontal .ui-slider-handle { top: -.3em; margin-left: -.6em; }
+.ui-slider-horizontal .ui-slider-range { top: 0; height: 100%; }
+.ui-slider-horizontal .ui-slider-range-min { left: 0; }
+.ui-slider-horizontal .ui-slider-range-max { right: 0; }
+
+.ui-slider-vertical { width: .8em; height: 100px; }
+.ui-slider-vertical .ui-slider-handle { left: -.3em; margin-left: 0; margin-bottom: -.6em; }
+.ui-slider-vertical .ui-slider-range { left: 0; width: 100%; }
+.ui-slider-vertical .ui-slider-range-min { bottom: 0; }
+.ui-slider-vertical .ui-slider-range-max { top: 0; }
+.ui-spinner { position:relative; display: inline-block; overflow: hidden; padding: 0; vertical-align: middle; }
+.ui-spinner-input { border: none; background: none; padding: 0; margin: .2em 0; vertical-align: middle; margin-left: .4em; margin-right: 22px; }
+.ui-spinner-button { width: 16px; height: 50%; font-size: .5em; padding: 0; margin: 0; z-index: 100; text-align: center; position: absolute; cursor: default; display: block; overflow: hidden; right: 0; }
+.ui-spinner a.ui-spinner-button { border-top: none; border-bottom: none; border-right: none; } /* more specificity required here to overide default borders */
+.ui-spinner .ui-icon { position: absolute; margin-top: -8px; top: 50%; left: 0; } /* vertical centre icon */
+.ui-spinner-up { top: 0; }
+.ui-spinner-down { bottom: 0; }
+
+/* TR overrides */
+span.ui-spinner { background: none; }
+.ui-spinner .ui-icon-triangle-1-s {
+ /* need to fix icons sprite */
+ background-position:-65px -16px;
+}
+
+.ui-tabs { position: relative; padding: .2em; zoom: 1; } /* position: relative prevents IE scroll bug (element with position: relative inside container with overflow: auto appear as "fixed") */
+.ui-tabs .ui-tabs-nav { margin: 0; padding: .2em .2em 0; }
+.ui-tabs .ui-tabs-nav li { list-style: none; float: left; position: relative; top: 0; margin: 1px .2em 0 0; border-bottom: 0; padding: 0; white-space: nowrap; }
+.ui-tabs .ui-tabs-nav li a { float: left; padding: .5em 1em; text-decoration: none; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-active { margin-bottom: -1px; padding-bottom: 1px; }
+.ui-tabs .ui-tabs-nav li.ui-tabs-active a, .ui-tabs .ui-tabs-nav li.ui-state-disabled a, .ui-tabs .ui-tabs-nav li.ui-tabs-loading a { cursor: text; }
+.ui-tabs .ui-tabs-nav li a, .ui-tabs-collapsible .ui-tabs-nav li.ui-tabs-active a { cursor: pointer; } /* first selector in group seems obsolete, but required to overcome bug in Opera applying cursor: text overall if defined elsewhere... */
+.ui-tabs .ui-tabs-panel { display: block; border-width: 0; padding: 1em 1.4em; background: none; }
+
+.ui-tooltip {
+ padding:8px;
+ position:absolute;
+ z-index:9999;
+ -o-box-shadow: 0 0 5px #aaa;
+ -moz-box-shadow: 0 0 5px #aaa;
+ -webkit-box-shadow: 0 0 5px #aaa;
+ box-shadow: 0 0 5px #aaa;
+}
+/* Fades and background-images don't work well together in IE6, drop the image */
+* html .ui-tooltip {
+ background-image: none;
+}
+body .ui-tooltip { border-width:2px; }
+
+/* Component containers
+----------------------------------*/
+.ui-widget { font-family: Verdana,Arial,sans-serif/*{ffDefault}*/; font-size: 1.1em/*{fsDefault}*/; }
+.ui-widget .ui-widget { font-size: 1em; }
+.ui-widget input, .ui-widget select, .ui-widget textarea, .ui-widget button { font-family: Verdana,Arial,sans-serif/*{ffDefault}*/; font-size: 1em; }
+.ui-widget-content { border: 1px solid #aaaaaa/*{borderColorContent}*/; background: #ffffff/*{bgColorContent}*/ url(images/ui-bg_flat_75_ffffff_40x100.png)/*{bgImgUrlContent}*/ 50%/*{bgContentXPos}*/ 50%/*{bgContentYPos}*/ repeat-x/*{bgContentRepeat}*/; color: #222222/*{fcContent}*/; }
+.ui-widget-content a { color: #222222/*{fcContent}*/; }
+.ui-widget-header { border: 1px solid #aaaaaa/*{borderColorHeader}*/; background: #cccccc/*{bgColorHeader}*/ url(images/ui-bg_highlight-soft_75_cccccc_1x100.png)/*{bgImgUrlHeader}*/ 50%/*{bgHeaderXPos}*/ 50%/*{bgHeaderYPos}*/ repeat-x/*{bgHeaderRepeat}*/; color: #222222/*{fcHeader}*/; font-weight: bold; }
+.ui-widget-header a { color: #222222/*{fcHeader}*/; }
+
+/* Interaction states
+----------------------------------*/
+.ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default { border: 1px solid #d3d3d3/*{borderColorDefault}*/; background: #e6e6e6/*{bgColorDefault}*/ url(images/ui-bg_glass_75_e6e6e6_1x400.png)/*{bgImgUrlDefault}*/ 50%/*{bgDefaultXPos}*/ 50%/*{bgDefaultYPos}*/ repeat-x/*{bgDefaultRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #555555/*{fcDefault}*/; }
+.ui-state-default a, .ui-state-default a:link, .ui-state-default a:visited { color: #555555/*{fcDefault}*/; text-decoration: none; }
+.ui-state-hover, .ui-widget-content .ui-state-hover, .ui-widget-header .ui-state-hover, .ui-state-focus, .ui-widget-content .ui-state-focus, .ui-widget-header .ui-state-focus { border: 1px solid #999999/*{borderColorHover}*/; background: #dadada/*{bgColorHover}*/ url(images/ui-bg_glass_75_dadada_1x400.png)/*{bgImgUrlHover}*/ 50%/*{bgHoverXPos}*/ 50%/*{bgHoverYPos}*/ repeat-x/*{bgHoverRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #212121/*{fcHover}*/; }
+.ui-state-hover a, .ui-state-hover a:hover { color: #212121/*{fcHover}*/; text-decoration: none; }
+.ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active { border: 1px solid #aaaaaa/*{borderColorActive}*/; background: #ffffff/*{bgColorActive}*/ url(images/ui-bg_glass_65_ffffff_1x400.png)/*{bgImgUrlActive}*/ 50%/*{bgActiveXPos}*/ 50%/*{bgActiveYPos}*/ repeat-x/*{bgActiveRepeat}*/; font-weight: normal/*{fwDefault}*/; color: #212121/*{fcActive}*/; }
+.ui-state-active a, .ui-state-active a:link, .ui-state-active a:visited { color: #212121/*{fcActive}*/; text-decoration: none; }
+
+/* Interaction Cues
+----------------------------------*/
+.ui-state-highlight, .ui-widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight {border: 1px solid #fcefa1/*{borderColorHighlight}*/; background: #fbf9ee/*{bgColorHighlight}*/ url(images/ui-bg_glass_55_fbf9ee_1x400.png)/*{bgImgUrlHighlight}*/ 50%/*{bgHighlightXPos}*/ 50%/*{bgHighlightYPos}*/ repeat-x/*{bgHighlightRepeat}*/; color: #363636/*{fcHighlight}*/; }
+.ui-state-highlight a, .ui-widget-content .ui-state-highlight a,.ui-widget-header .ui-state-highlight a { color: #363636/*{fcHighlight}*/; }
+.ui-state-error, .ui-widget-content .ui-state-error, .ui-widget-header .ui-state-error {border: 1px solid #cd0a0a/*{borderColorError}*/; background: #fef1ec/*{bgColorError}*/ url(images/ui-bg_glass_95_fef1ec_1x400.png)/*{bgImgUrlError}*/ 50%/*{bgErrorXPos}*/ 50%/*{bgErrorYPos}*/ repeat-x/*{bgErrorRepeat}*/; color: #cd0a0a/*{fcError}*/; }
+.ui-state-error a, .ui-widget-content .ui-state-error a, .ui-widget-header .ui-state-error a { color: #cd0a0a/*{fcError}*/; }
+.ui-state-error-text, .ui-widget-content .ui-state-error-text, .ui-widget-header .ui-state-error-text { color: #cd0a0a/*{fcError}*/; }
+.ui-priority-primary, .ui-widget-content .ui-priority-primary, .ui-widget-header .ui-priority-primary { font-weight: bold; }
+.ui-priority-secondary, .ui-widget-content .ui-priority-secondary, .ui-widget-header .ui-priority-secondary { opacity: .7; filter:Alpha(Opacity=70); font-weight: normal; }
+.ui-state-disabled, .ui-widget-content .ui-state-disabled, .ui-widget-header .ui-state-disabled { opacity: .35; filter:Alpha(Opacity=35); background-image: none; }
+
+/* Icons
+----------------------------------*/
+
+/* states and images */
+.ui-icon { width: 16px; height: 16px; background-image: url(images/ui-icons_222222_256x240.png)/*{iconsContent}*/; }
+.ui-widget-content .ui-icon {background-image: url(images/ui-icons_222222_256x240.png)/*{iconsContent}*/; }
+.ui-widget-header .ui-icon {background-image: url(images/ui-icons_222222_256x240.png)/*{iconsHeader}*/; }
+.ui-state-default .ui-icon { background-image: url(images/ui-icons_888888_256x240.png)/*{iconsDefault}*/; }
+.ui-state-hover .ui-icon, .ui-state-focus .ui-icon {background-image: url(images/ui-icons_454545_256x240.png)/*{iconsHover}*/; }
+.ui-state-active .ui-icon {background-image: url(images/ui-icons_454545_256x240.png)/*{iconsActive}*/; }
+.ui-state-highlight .ui-icon {background-image: url(images/ui-icons_2e83ff_256x240.png)/*{iconsHighlight}*/; }
+.ui-state-error .ui-icon, .ui-state-error-text .ui-icon {background-image: url(images/ui-icons_cd0a0a_256x240.png)/*{iconsError}*/; }
+
+/* positioning */
+.ui-icon-carat-1-n { background-position: 0 0; }
+.ui-icon-carat-1-ne { background-position: -16px 0; }
+.ui-icon-carat-1-e { background-position: -32px 0; }
+.ui-icon-carat-1-se { background-position: -48px 0; }
+.ui-icon-carat-1-s { background-position: -64px 0; }
+.ui-icon-carat-1-sw { background-position: -80px 0; }
+.ui-icon-carat-1-w { background-position: -96px 0; }
+.ui-icon-carat-1-nw { background-position: -112px 0; }
+.ui-icon-carat-2-n-s { background-position: -128px 0; }
+.ui-icon-carat-2-e-w { background-position: -144px 0; }
+.ui-icon-triangle-1-n { background-position: 0 -16px; }
+.ui-icon-triangle-1-ne { background-position: -16px -16px; }
+.ui-icon-triangle-1-e { background-position: -32px -16px; }
+.ui-icon-triangle-1-se { background-position: -48px -16px; }
+.ui-icon-triangle-1-s { background-position: -64px -16px; }
+.ui-icon-triangle-1-sw { background-position: -80px -16px; }
+.ui-icon-triangle-1-w { background-position: -96px -16px; }
+.ui-icon-triangle-1-nw { background-position: -112px -16px; }
+.ui-icon-triangle-2-n-s { background-position: -128px -16px; }
+.ui-icon-triangle-2-e-w { background-position: -144px -16px; }
+.ui-icon-arrow-1-n { background-position: 0 -32px; }
+.ui-icon-arrow-1-ne { background-position: -16px -32px; }
+.ui-icon-arrow-1-e { background-position: -32px -32px; }
+.ui-icon-arrow-1-se { background-position: -48px -32px; }
+.ui-icon-arrow-1-s { background-position: -64px -32px; }
+.ui-icon-arrow-1-sw { background-position: -80px -32px; }
+.ui-icon-arrow-1-w { background-position: -96px -32px; }
+.ui-icon-arrow-1-nw { background-position: -112px -32px; }
+.ui-icon-arrow-2-n-s { background-position: -128px -32px; }
+.ui-icon-arrow-2-ne-sw { background-position: -144px -32px; }
+.ui-icon-arrow-2-e-w { background-position: -160px -32px; }
+.ui-icon-arrow-2-se-nw { background-position: -176px -32px; }
+.ui-icon-arrowstop-1-n { background-position: -192px -32px; }
+.ui-icon-arrowstop-1-e { background-position: -208px -32px; }
+.ui-icon-arrowstop-1-s { background-position: -224px -32px; }
+.ui-icon-arrowstop-1-w { background-position: -240px -32px; }
+.ui-icon-arrowthick-1-n { background-position: 0 -48px; }
+.ui-icon-arrowthick-1-ne { background-position: -16px -48px; }
+.ui-icon-arrowthick-1-e { background-position: -32px -48px; }
+.ui-icon-arrowthick-1-se { background-position: -48px -48px; }
+.ui-icon-arrowthick-1-s { background-position: -64px -48px; }
+.ui-icon-arrowthick-1-sw { background-position: -80px -48px; }
+.ui-icon-arrowthick-1-w { background-position: -96px -48px; }
+.ui-icon-arrowthick-1-nw { background-position: -112px -48px; }
+.ui-icon-arrowthick-2-n-s { background-position: -128px -48px; }
+.ui-icon-arrowthick-2-ne-sw { background-position: -144px -48px; }
+.ui-icon-arrowthick-2-e-w { background-position: -160px -48px; }
+.ui-icon-arrowthick-2-se-nw { background-position: -176px -48px; }
+.ui-icon-arrowthickstop-1-n { background-position: -192px -48px; }
+.ui-icon-arrowthickstop-1-e { background-position: -208px -48px; }
+.ui-icon-arrowthickstop-1-s { background-position: -224px -48px; }
+.ui-icon-arrowthickstop-1-w { background-position: -240px -48px; }
+.ui-icon-arrowreturnthick-1-w { background-position: 0 -64px; }
+.ui-icon-arrowreturnthick-1-n { background-position: -16px -64px; }
+.ui-icon-arrowreturnthick-1-e { background-position: -32px -64px; }
+.ui-icon-arrowreturnthick-1-s { background-position: -48px -64px; }
+.ui-icon-arrowreturn-1-w { background-position: -64px -64px; }
+.ui-icon-arrowreturn-1-n { background-position: -80px -64px; }
+.ui-icon-arrowreturn-1-e { background-position: -96px -64px; }
+.ui-icon-arrowreturn-1-s { background-position: -112px -64px; }
+.ui-icon-arrowrefresh-1-w { background-position: -128px -64px; }
+.ui-icon-arrowrefresh-1-n { background-position: -144px -64px; }
+.ui-icon-arrowrefresh-1-e { background-position: -160px -64px; }
+.ui-icon-arrowrefresh-1-s { background-position: -176px -64px; }
+.ui-icon-arrow-4 { background-position: 0 -80px; }
+.ui-icon-arrow-4-diag { background-position: -16px -80px; }
+.ui-icon-extlink { background-position: -32px -80px; }
+.ui-icon-newwin { background-position: -48px -80px; }
+.ui-icon-refresh { background-position: -64px -80px; }
+.ui-icon-shuffle { background-position: -80px -80px; }
+.ui-icon-transfer-e-w { background-position: -96px -80px; }
+.ui-icon-transferthick-e-w { background-position: -112px -80px; }
+.ui-icon-folder-collapsed { background-position: 0 -96px; }
+.ui-icon-folder-open { background-position: -16px -96px; }
+.ui-icon-document { background-position: -32px -96px; }
+.ui-icon-document-b { background-position: -48px -96px; }
+.ui-icon-note { background-position: -64px -96px; }
+.ui-icon-mail-closed { background-position: -80px -96px; }
+.ui-icon-mail-open { background-position: -96px -96px; }
+.ui-icon-suitcase { background-position: -112px -96px; }
+.ui-icon-comment { background-position: -128px -96px; }
+.ui-icon-person { background-position: -144px -96px; }
+.ui-icon-print { background-position: -160px -96px; }
+.ui-icon-trash { background-position: -176px -96px; }
+.ui-icon-locked { background-position: -192px -96px; }
+.ui-icon-unlocked { background-position: -208px -96px; }
+.ui-icon-bookmark { background-position: -224px -96px; }
+.ui-icon-tag { background-position: -240px -96px; }
+.ui-icon-home { background-position: 0 -112px; }
+.ui-icon-flag { background-position: -16px -112px; }
+.ui-icon-calendar { background-position: -32px -112px; }
+.ui-icon-cart { background-position: -48px -112px; }
+.ui-icon-pencil { background-position: -64px -112px; }
+.ui-icon-clock { background-position: -80px -112px; }
+.ui-icon-disk { background-position: -96px -112px; }
+.ui-icon-calculator { background-position: -112px -112px; }
+.ui-icon-zoomin { background-position: -128px -112px; }
+.ui-icon-zoomout { background-position: -144px -112px; }
+.ui-icon-search { background-position: -160px -112px; }
+.ui-icon-wrench { background-position: -176px -112px; }
+.ui-icon-gear { background-position: -192px -112px; }
+.ui-icon-heart { background-position: -208px -112px; }
+.ui-icon-star { background-position: -224px -112px; }
+.ui-icon-link { background-position: -240px -112px; }
+.ui-icon-cancel { background-position: 0 -128px; }
+.ui-icon-plus { background-position: -16px -128px; }
+.ui-icon-plusthick { background-position: -32px -128px; }
+.ui-icon-minus { background-position: -48px -128px; }
+.ui-icon-minusthick { background-position: -64px -128px; }
+.ui-icon-close { background-position: -80px -128px; }
+.ui-icon-closethick { background-position: -96px -128px; }
+.ui-icon-key { background-position: -112px -128px; }
+.ui-icon-lightbulb { background-position: -128px -128px; }
+.ui-icon-scissors { background-position: -144px -128px; }
+.ui-icon-clipboard { background-position: -160px -128px; }
+.ui-icon-copy { background-position: -176px -128px; }
+.ui-icon-contact { background-position: -192px -128px; }
+.ui-icon-image { background-position: -208px -128px; }
+.ui-icon-video { background-position: -224px -128px; }
+.ui-icon-script { background-position: -240px -128px; }
+.ui-icon-alert { background-position: 0 -144px; }
+.ui-icon-info { background-position: -16px -144px; }
+.ui-icon-notice { background-position: -32px -144px; }
+.ui-icon-help { background-position: -48px -144px; }
+.ui-icon-check { background-position: -64px -144px; }
+.ui-icon-bullet { background-position: -80px -144px; }
+.ui-icon-radio-on { background-position: -96px -144px; }
+.ui-icon-radio-off { background-position: -112px -144px; }
+.ui-icon-pin-w { background-position: -128px -144px; }
+.ui-icon-pin-s { background-position: -144px -144px; }
+.ui-icon-play { background-position: 0 -160px; }
+.ui-icon-pause { background-position: -16px -160px; }
+.ui-icon-seek-next { background-position: -32px -160px; }
+.ui-icon-seek-prev { background-position: -48px -160px; }
+.ui-icon-seek-end { background-position: -64px -160px; }
+.ui-icon-seek-start { background-position: -80px -160px; }
+/* ui-icon-seek-first is deprecated, use ui-icon-seek-start instead */
+.ui-icon-seek-first { background-position: -80px -160px; }
+.ui-icon-stop { background-position: -96px -160px; }
+.ui-icon-eject { background-position: -112px -160px; }
+.ui-icon-volume-off { background-position: -128px -160px; }
+.ui-icon-volume-on { background-position: -144px -160px; }
+.ui-icon-power { background-position: 0 -176px; }
+.ui-icon-signal-diag { background-position: -16px -176px; }
+.ui-icon-signal { background-position: -32px -176px; }
+.ui-icon-battery-0 { background-position: -48px -176px; }
+.ui-icon-battery-1 { background-position: -64px -176px; }
+.ui-icon-battery-2 { background-position: -80px -176px; }
+.ui-icon-battery-3 { background-position: -96px -176px; }
+.ui-icon-circle-plus { background-position: 0 -192px; }
+.ui-icon-circle-minus { background-position: -16px -192px; }
+.ui-icon-circle-close { background-position: -32px -192px; }
+.ui-icon-circle-triangle-e { background-position: -48px -192px; }
+.ui-icon-circle-triangle-s { background-position: -64px -192px; }
+.ui-icon-circle-triangle-w { background-position: -80px -192px; }
+.ui-icon-circle-triangle-n { background-position: -96px -192px; }
+.ui-icon-circle-arrow-e { background-position: -112px -192px; }
+.ui-icon-circle-arrow-s { background-position: -128px -192px; }
+.ui-icon-circle-arrow-w { background-position: -144px -192px; }
+.ui-icon-circle-arrow-n { background-position: -160px -192px; }
+.ui-icon-circle-zoomin { background-position: -176px -192px; }
+.ui-icon-circle-zoomout { background-position: -192px -192px; }
+.ui-icon-circle-check { background-position: -208px -192px; }
+.ui-icon-circlesmall-plus { background-position: 0 -208px; }
+.ui-icon-circlesmall-minus { background-position: -16px -208px; }
+.ui-icon-circlesmall-close { background-position: -32px -208px; }
+.ui-icon-squaresmall-plus { background-position: -48px -208px; }
+.ui-icon-squaresmall-minus { background-position: -64px -208px; }
+.ui-icon-squaresmall-close { background-position: -80px -208px; }
+.ui-icon-grip-dotted-vertical { background-position: 0 -224px; }
+.ui-icon-grip-dotted-horizontal { background-position: -16px -224px; }
+.ui-icon-grip-solid-vertical { background-position: -32px -224px; }
+.ui-icon-grip-solid-horizontal { background-position: -48px -224px; }
+.ui-icon-gripsmall-diagonal-se { background-position: -64px -224px; }
+.ui-icon-grip-diagonal-se { background-position: -80px -224px; }
+
+
+/* Misc visuals
+----------------------------------*/
+
+/* Corner radius */
+.ui-corner-all, .ui-corner-top, .ui-corner-left, .ui-corner-tl { -moz-border-radius-topleft: 4px/*{cornerRadius}*/; -webkit-border-top-left-radius: 4px/*{cornerRadius}*/; -khtml-border-top-left-radius: 4px/*{cornerRadius}*/; border-top-left-radius: 4px/*{cornerRadius}*/; }
+.ui-corner-all, .ui-corner-top, .ui-corner-right, .ui-corner-tr { -moz-border-radius-topright: 4px/*{cornerRadius}*/; -webkit-border-top-right-radius: 4px/*{cornerRadius}*/; -khtml-border-top-right-radius: 4px/*{cornerRadius}*/; border-top-right-radius: 4px/*{cornerRadius}*/; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-left, .ui-corner-bl { -moz-border-radius-bottomleft: 4px/*{cornerRadius}*/; -webkit-border-bottom-left-radius: 4px/*{cornerRadius}*/; -khtml-border-bottom-left-radius: 4px/*{cornerRadius}*/; border-bottom-left-radius: 4px/*{cornerRadius}*/; }
+.ui-corner-all, .ui-corner-bottom, .ui-corner-right, .ui-corner-br { -moz-border-radius-bottomright: 4px/*{cornerRadius}*/; -webkit-border-bottom-right-radius: 4px/*{cornerRadius}*/; -khtml-border-bottom-right-radius: 4px/*{cornerRadius}*/; border-bottom-right-radius: 4px/*{cornerRadius}*/; }
+
+/* Overlays */
+.ui-widget-overlay { background: #aaaaaa/*{bgColorOverlay}*/ url(images/ui-bg_flat_0_aaaaaa_40x100.png)/*{bgImgUrlOverlay}*/ 50%/*{bgOverlayXPos}*/ 50%/*{bgOverlayYPos}*/ repeat-x/*{bgOverlayRepeat}*/; opacity: .3;filter:Alpha(Opacity=30)/*{opacityOverlay}*/; }
+.ui-widget-shadow { margin: -8px/*{offsetTopShadow}*/ 0 0 -8px/*{offsetLeftShadow}*/; padding: 8px/*{thicknessShadow}*/; background: #aaaaaa/*{bgColorShadow}*/ url(images/ui-bg_flat_0_aaaaaa_40x100.png)/*{bgImgUrlShadow}*/ 50%/*{bgShadowXPos}*/ 50%/*{bgShadowYPos}*/ repeat-x/*{bgShadowRepeat}*/; opacity: .3;filter:Alpha(Opacity=30)/*{opacityShadow}*/; -moz-border-radius: 8px/*{cornerRadiusShadow}*/; -khtml-border-radius: 8px/*{cornerRadiusShadow}*/; -webkit-border-radius: 8px/*{cornerRadiusShadow}*/; border-radius: 8px/*{cornerRadiusShadow}*/; } \ No newline at end of file
diff --git a/license.txt b/license.txt
new file mode 100644
index 0000000..4c63c80
--- /dev/null
+++ b/license.txt
@@ -0,0 +1,59 @@
+License
+
+THE WORK (AS DEFINED BELOW) IS PROVIDED UNDER THE TERMS OF THIS CREATIVE COMMONS PUBLIC LICENSE ("CCPL" OR "LICENSE"). THE WORK IS PROTECTED BY COPYRIGHT AND/OR OTHER APPLICABLE LAW. ANY USE OF THE WORK OTHER THAN AS AUTHORIZED UNDER THIS LICENSE OR COPYRIGHT LAW IS PROHIBITED.
+
+BY EXERCISING ANY RIGHTS TO THE WORK PROVIDED HERE, YOU ACCEPT AND AGREE TO BE BOUND BY THE TERMS OF THIS LICENSE. TO THE EXTENT THIS LICENSE MAY BE CONSIDERED TO BE A CONTRACT, THE LICENSOR GRANTS YOU THE RIGHTS CONTAINED HERE IN CONSIDERATION OF YOUR ACCEPTANCE OF SUCH TERMS AND CONDITIONS.
+
+1. Definitions
+
+"Adaptation" means a work based upon the Work, or upon the Work and other pre-existing works, such as a translation, adaptation, derivative work, arrangement of music or other alterations of a literary or artistic work, or phonogram or performance and includes cinematographic adaptations or any other form in which the Work may be recast, transformed, or adapted including in any form recognizably derived from the original, except that a work that constitutes a Collection will not be considered an Adaptation for the purpose of this License. For the avoidance of doubt, where the Work is a musical work, performance or phonogram, the synchronization of the Work in timed-relation with a moving image ("synching") will be considered an Adaptation for the purpose of this License.
+"Collection" means a collection of literary or artistic works, such as encyclopedias and anthologies, or performances, phonograms or broadcasts, or other works or subject matter other than works listed in Section 1(g) below, which, by reason of the selection and arrangement of their contents, constitute intellectual creations, in which the Work is included in its entirety in unmodified form along with one or more other contributions, each constituting separate and independent works in themselves, which together are assembled into a collective whole. A work that constitutes a Collection will not be considered an Adaptation (as defined above) for the purposes of this License.
+"Distribute" means to make available to the public the original and copies of the Work or Adaptation, as appropriate, through sale or other transfer of ownership.
+"License Elements" means the following high-level license attributes as selected by Licensor and indicated in the title of this License: Attribution, Noncommercial, ShareAlike.
+"Licensor" means the individual, individuals, entity or entities that offer(s) the Work under the terms of this License.
+"Original Author" means, in the case of a literary or artistic work, the individual, individuals, entity or entities who created the Work or if no individual or entity can be identified, the publisher; and in addition (i) in the case of a performance the actors, singers, musicians, dancers, and other persons who act, sing, deliver, declaim, play in, interpret or otherwise perform literary or artistic works or expressions of folklore; (ii) in the case of a phonogram the producer being the person or legal entity who first fixes the sounds of a performance or other sounds; and, (iii) in the case of broadcasts, the organization that transmits the broadcast.
+"Work" means the literary and/or artistic work offered under the terms of this License including without limitation any production in the literary, scientific and artistic domain, whatever may be the mode or form of its expression including digital form, such as a book, pamphlet and other writing; a lecture, address, sermon or other work of the same nature; a dramatic or dramatico-musical work; a choreographic work or entertainment in dumb show; a musical composition with or without words; a cinematographic work to which are assimilated works expressed by a process analogous to cinematography; a work of drawing, painting, architecture, sculpture, engraving or lithography; a photographic work to which are assimilated works expressed by a process analogous to photography; a work of applied art; an illustration, map, plan, sketch or three-dimensional work relative to geography, topography, architecture or science; a performance; a broadcast; a phonogram; a compilation of data to the extent it is protected as a copyrightable work; or a work performed by a variety or circus performer to the extent it is not otherwise considered a literary or artistic work.
+"You" means an individual or entity exercising rights under this License who has not previously violated the terms of this License with respect to the Work, or who has received express permission from the Licensor to exercise rights under this License despite a previous violation.
+"Publicly Perform" means to perform public recitations of the Work and to communicate to the public those public recitations, by any means or process, including by wire or wireless means or public digital performances; to make available to the public Works in such a way that members of the public may access these Works from a place and at a place individually chosen by them; to perform the Work to the public by any means or process and the communication to the public of the performances of the Work, including by public digital performance; to broadcast and rebroadcast the Work by any means including signs, sounds or images.
+"Reproduce" means to make copies of the Work by any means including without limitation by sound or visual recordings and the right of fixation and reproducing fixations of the Work, including storage of a protected performance or phonogram in digital form or other electronic medium.
+2. Fair Dealing Rights. Nothing in this License is intended to reduce, limit, or restrict any uses free from copyright or rights arising from limitations or exceptions that are provided for in connection with the copyright protection under copyright law or other applicable laws.
+
+3. License Grant. Subject to the terms and conditions of this License, Licensor hereby grants You a worldwide, royalty-free, non-exclusive, perpetual (for the duration of the applicable copyright) license to exercise the rights in the Work as stated below:
+
+to Reproduce the Work, to incorporate the Work into one or more Collections, and to Reproduce the Work as incorporated in the Collections;
+to create and Reproduce Adaptations provided that any such Adaptation, including any translation in any medium, takes reasonable steps to clearly label, demarcate or otherwise identify that changes were made to the original Work. For example, a translation could be marked "The original work was translated from English to Spanish," or a modification could indicate "The original work has been modified.";
+to Distribute and Publicly Perform the Work including as incorporated in Collections; and,
+to Distribute and Publicly Perform Adaptations.
+The above rights may be exercised in all media and formats whether now known or hereafter devised. The above rights include the right to make such modifications as are technically necessary to exercise the rights in other media and formats. Subject to Section 8(f), all rights not expressly granted by Licensor are hereby reserved, including but not limited to the rights described in Section 4(e).
+
+4. Restrictions. The license granted in Section 3 above is expressly made subject to and limited by the following restrictions:
+
+You may Distribute or Publicly Perform the Work only under the terms of this License. You must include a copy of, or the Uniform Resource Identifier (URI) for, this License with every copy of the Work You Distribute or Publicly Perform. You may not offer or impose any terms on the Work that restrict the terms of this License or the ability of the recipient of the Work to exercise the rights granted to that recipient under the terms of the License. You may not sublicense the Work. You must keep intact all notices that refer to this License and to the disclaimer of warranties with every copy of the Work You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Work, You may not impose any effective technological measures on the Work that restrict the ability of a recipient of the Work from You to exercise the rights granted to that recipient under the terms of the License. This Section 4(a) applies to the Work as incorporated in a Collection, but this does not require the Collection apart from the Work itself to be made subject to the terms of this License. If You create a Collection, upon notice from any Licensor You must, to the extent practicable, remove from the Collection any credit as required by Section 4(d), as requested. If You create an Adaptation, upon notice from any Licensor You must, to the extent practicable, remove from the Adaptation any credit as required by Section 4(d), as requested.
+You may Distribute or Publicly Perform an Adaptation only under: (i) the terms of this License; (ii) a later version of this License with the same License Elements as this License; (iii) a Creative Commons jurisdiction license (either this or a later license version) that contains the same License Elements as this License (e.g., Attribution-NonCommercial-ShareAlike 3.0 US) ("Applicable License"). You must include a copy of, or the URI, for Applicable License with every copy of each Adaptation You Distribute or Publicly Perform. You may not offer or impose any terms on the Adaptation that restrict the terms of the Applicable License or the ability of the recipient of the Adaptation to exercise the rights granted to that recipient under the terms of the Applicable License. You must keep intact all notices that refer to the Applicable License and to the disclaimer of warranties with every copy of the Work as included in the Adaptation You Distribute or Publicly Perform. When You Distribute or Publicly Perform the Adaptation, You may not impose any effective technological measures on the Adaptation that restrict the ability of a recipient of the Adaptation from You to exercise the rights granted to that recipient under the terms of the Applicable License. This Section 4(b) applies to the Adaptation as incorporated in a Collection, but this does not require the Collection apart from the Adaptation itself to be made subject to the terms of the Applicable License.
+You may not exercise any of the rights granted to You in Section 3 above in any manner that is primarily intended for or directed toward commercial advantage or private monetary compensation. The exchange of the Work for other copyrighted works by means of digital file-sharing or otherwise shall not be considered to be intended for or directed toward commercial advantage or private monetary compensation, provided there is no payment of any monetary compensation in con-nection with the exchange of copyrighted works.
+If You Distribute, or Publicly Perform the Work or any Adaptations or Collections, You must, unless a request has been made pursuant to Section 4(a), keep intact all copyright notices for the Work and provide, reasonable to the medium or means You are utilizing: (i) the name of the Original Author (or pseudonym, if applicable) if supplied, and/or if the Original Author and/or Licensor designate another party or parties (e.g., a sponsor institute, publishing entity, journal) for attribution ("Attribution Parties") in Licensor's copyright notice, terms of service or by other reasonable means, the name of such party or parties; (ii) the title of the Work if supplied; (iii) to the extent reasonably practicable, the URI, if any, that Licensor specifies to be associated with the Work, unless such URI does not refer to the copyright notice or licensing information for the Work; and, (iv) consistent with Section 3(b), in the case of an Adaptation, a credit identifying the use of the Work in the Adaptation (e.g., "French translation of the Work by Original Author," or "Screenplay based on original Work by Original Author"). The credit required by this Section 4(d) may be implemented in any reasonable manner; provided, however, that in the case of a Adaptation or Collection, at a minimum such credit will appear, if a credit for all contributing authors of the Adaptation or Collection appears, then as part of these credits and in a manner at least as prominent as the credits for the other contributing authors. For the avoidance of doubt, You may only use the credit required by this Section for the purpose of attribution in the manner set out above and, by exercising Your rights under this License, You may not implicitly or explicitly assert or imply any connection with, sponsorship or endorsement by the Original Author, Licensor and/or Attribution Parties, as appropriate, of You or Your use of the Work, without the separate, express prior written permission of the Original Author, Licensor and/or Attribution Parties.
+For the avoidance of doubt:
+
+Non-waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme cannot be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License;
+Waivable Compulsory License Schemes. In those jurisdictions in which the right to collect royalties through any statutory or compulsory licensing scheme can be waived, the Licensor reserves the exclusive right to collect such royalties for any exercise by You of the rights granted under this License if Your exercise of such rights is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(c) and otherwise waives the right to collect royalties through any statutory or compulsory licensing scheme; and,
+Voluntary License Schemes. The Licensor reserves the right to collect royalties, whether individually or, in the event that the Licensor is a member of a collecting society that administers voluntary licensing schemes, via that society, from any exercise by You of the rights granted under this License that is for a purpose or use which is otherwise than noncommercial as permitted under Section 4(c).
+Except as otherwise agreed in writing by the Licensor or as may be otherwise permitted by applicable law, if You Reproduce, Distribute or Publicly Perform the Work either by itself or as part of any Adaptations or Collections, You must not distort, mutilate, modify or take other derogatory action in relation to the Work which would be prejudicial to the Original Author's honor or reputation. Licensor agrees that in those jurisdictions (e.g. Japan), in which any exercise of the right granted in Section 3(b) of this License (the right to make Adaptations) would be deemed to be a distortion, mutilation, modification or other derogatory action prejudicial to the Original Author's honor and reputation, the Licensor will waive or not assert, as appropriate, this Section, to the fullest extent permitted by the applicable national law, to enable You to reasonably exercise Your right under Section 3(b) of this License (right to make Adaptations) but not otherwise.
+5. Representations, Warranties and Disclaimer
+
+UNLESS OTHERWISE MUTUALLY AGREED TO BY THE PARTIES IN WRITING AND TO THE FULLEST EXTENT PERMITTED BY APPLICABLE LAW, LICENSOR OFFERS THE WORK AS-IS AND MAKES NO REPRESENTATIONS OR WARRANTIES OF ANY KIND CONCERNING THE WORK, EXPRESS, IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT LIMITATION, WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NONINFRINGEMENT, OR THE ABSENCE OF LATENT OR OTHER DEFECTS, ACCURACY, OR THE PRESENCE OF ABSENCE OF ERRORS, WHETHER OR NOT DISCOVERABLE. SOME JURISDICTIONS DO NOT ALLOW THE EXCLUSION OF IMPLIED WARRANTIES, SO THIS EXCLUSION MAY NOT APPLY TO YOU.
+
+6. Limitation on Liability. EXCEPT TO THE EXTENT REQUIRED BY APPLICABLE LAW, IN NO EVENT WILL LICENSOR BE LIABLE TO YOU ON ANY LEGAL THEORY FOR ANY SPECIAL, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR EXEMPLARY DAMAGES ARISING OUT OF THIS LICENSE OR THE USE OF THE WORK, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
+
+7. Termination
+
+This License and the rights granted hereunder will terminate automatically upon any breach by You of the terms of this License. Individuals or entities who have received Adaptations or Collections from You under this License, however, will not have their licenses terminated provided such individuals or entities remain in full compliance with those licenses. Sections 1, 2, 5, 6, 7, and 8 will survive any termination of this License.
+Subject to the above terms and conditions, the license granted here is perpetual (for the duration of the applicable copyright in the Work). Notwithstanding the above, Licensor reserves the right to release the Work under different license terms or to stop distributing the Work at any time; provided, however that any such election will not serve to withdraw this License (or any other license that has been, or is required to be, granted under the terms of this License), and this License will continue in full force and effect unless terminated as stated above.
+8. Miscellaneous
+
+Each time You Distribute or Publicly Perform the Work or a Collection, the Licensor offers to the recipient a license to the Work on the same terms and conditions as the license granted to You under this License.
+Each time You Distribute or Publicly Perform an Adaptation, Licensor offers to the recipient a license to the original Work on the same terms and conditions as the license granted to You under this License.
+If any provision of this License is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this License, and without further action by the parties to this agreement, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable.
+No term or provision of this License shall be deemed waived and no breach consented to unless such waiver or consent shall be in writing and signed by the party to be charged with such waiver or consent.
+This License constitutes the entire agreement between the parties with respect to the Work licensed here. There are no understandings, agreements or representations with respect to the Work not specified here. Licensor shall not be bound by any additional provisions that may appear in any communication from You. This License may not be modified without the mutual written agreement of the Licensor and You.
+The rights granted under, and the subject matter referenced, in this License were drafted utilizing the terminology of the Berne Convention for the Protection of Literary and Artistic Works (as amended on September 28, 1979), the Rome Convention of 1961, the WIPO Copyright Treaty of 1996, the WIPO Performances and Phonograms Treaty of 1996 and the Universal Copyright Convention (as revised on July 24, 1971). These rights and subject matter take effect in the relevant jurisdiction in which the License terms are sought to be enforced according to the corresponding provisions of the implementation of those treaty provisions in the applicable national law. If the standard suite of rights granted under applicable copyright law includes additional rights not granted under this License, such additional rights are deemed to be included in the License; this License is not intended to restrict the license of any rights under applicable law.
+The software must be used only for good, not evil. \ No newline at end of file
diff --git a/palettes/ABS_VALUE.png b/palettes/ABS_VALUE.png
new file mode 100644
index 0000000..aa06345
--- /dev/null
+++ b/palettes/ABS_VALUE.png
Binary files differ
diff --git a/palettes/AFFICH_m.png b/palettes/AFFICH_m.png
new file mode 100644
index 0000000..460fece
--- /dev/null
+++ b/palettes/AFFICH_m.png
Binary files differ
diff --git a/palettes/ANDBLK.png b/palettes/ANDBLK.png
new file mode 100644
index 0000000..03bba59
--- /dev/null
+++ b/palettes/ANDBLK.png
Binary files differ
diff --git a/palettes/ANDLOG_f.png b/palettes/ANDLOG_f.png
new file mode 100644
index 0000000..ab41159
--- /dev/null
+++ b/palettes/ANDLOG_f.png
Binary files differ
diff --git a/palettes/AUTOMAT.png b/palettes/AUTOMAT.png
new file mode 100644
index 0000000..334d497
--- /dev/null
+++ b/palettes/AUTOMAT.png
Binary files differ
diff --git a/palettes/BACKLASH.png b/palettes/BACKLASH.png
new file mode 100644
index 0000000..ce2938e
--- /dev/null
+++ b/palettes/BACKLASH.png
Binary files differ
diff --git a/palettes/BARXY.png b/palettes/BARXY.png
new file mode 100644
index 0000000..32368d9
--- /dev/null
+++ b/palettes/BARXY.png
Binary files differ
diff --git a/palettes/BIGSOM_f.png b/palettes/BIGSOM_f.png
new file mode 100644
index 0000000..42deb67
--- /dev/null
+++ b/palettes/BIGSOM_f.png
Binary files differ
diff --git a/palettes/BITCLEAR.png b/palettes/BITCLEAR.png
new file mode 100644
index 0000000..c193233
--- /dev/null
+++ b/palettes/BITCLEAR.png
Binary files differ
diff --git a/palettes/BITSET.png b/palettes/BITSET.png
new file mode 100644
index 0000000..c9cde7d
--- /dev/null
+++ b/palettes/BITSET.png
Binary files differ
diff --git a/palettes/BOUNCE.png b/palettes/BOUNCE.png
new file mode 100644
index 0000000..04b4684
--- /dev/null
+++ b/palettes/BOUNCE.png
Binary files differ
diff --git a/palettes/BOUNCEXY.png b/palettes/BOUNCEXY.png
new file mode 100644
index 0000000..207e441
--- /dev/null
+++ b/palettes/BOUNCEXY.png
Binary files differ
diff --git a/palettes/BPLATFORM.png b/palettes/BPLATFORM.png
new file mode 100644
index 0000000..1145640
--- /dev/null
+++ b/palettes/BPLATFORM.png
Binary files differ
diff --git a/palettes/Bache.png b/palettes/Bache.png
new file mode 100644
index 0000000..b48a525
--- /dev/null
+++ b/palettes/Bache.png
Binary files differ
diff --git a/palettes/CANIMXY.png b/palettes/CANIMXY.png
new file mode 100644
index 0000000..207e441
--- /dev/null
+++ b/palettes/CANIMXY.png
Binary files differ
diff --git a/palettes/CANIMXY3D.png b/palettes/CANIMXY3D.png
new file mode 100644
index 0000000..3be2e3a
--- /dev/null
+++ b/palettes/CANIMXY3D.png
Binary files differ
diff --git a/palettes/CBLOCK.png b/palettes/CBLOCK.png
new file mode 100644
index 0000000..cf90bfa
--- /dev/null
+++ b/palettes/CBLOCK.png
Binary files differ
diff --git a/palettes/CBLOCK4.png b/palettes/CBLOCK4.png
new file mode 100644
index 0000000..4b78464
--- /dev/null
+++ b/palettes/CBLOCK4.png
Binary files differ
diff --git a/palettes/CCS.png b/palettes/CCS.png
new file mode 100644
index 0000000..91a53e9
--- /dev/null
+++ b/palettes/CCS.png
Binary files differ
diff --git a/palettes/CEVENTSCOPE.png b/palettes/CEVENTSCOPE.png
new file mode 100644
index 0000000..77341d7
--- /dev/null
+++ b/palettes/CEVENTSCOPE.png
Binary files differ
diff --git a/palettes/CFSCOPE.png b/palettes/CFSCOPE.png
new file mode 100644
index 0000000..71a5866
--- /dev/null
+++ b/palettes/CFSCOPE.png
Binary files differ
diff --git a/palettes/CLINDUMMY_f.png b/palettes/CLINDUMMY_f.png
new file mode 100644
index 0000000..9b4e88e
--- /dev/null
+++ b/palettes/CLINDUMMY_f.png
Binary files differ
diff --git a/palettes/CLKFROM.png b/palettes/CLKFROM.png
new file mode 100644
index 0000000..6513ead
--- /dev/null
+++ b/palettes/CLKFROM.png
Binary files differ
diff --git a/palettes/CLKGOTO.png b/palettes/CLKGOTO.png
new file mode 100644
index 0000000..a291b02
--- /dev/null
+++ b/palettes/CLKGOTO.png
Binary files differ
diff --git a/palettes/CLKGotoTagVisibility.png b/palettes/CLKGotoTagVisibility.png
new file mode 100644
index 0000000..57e435e
--- /dev/null
+++ b/palettes/CLKGotoTagVisibility.png
Binary files differ
diff --git a/palettes/CLKINV_f.png b/palettes/CLKINV_f.png
new file mode 100644
index 0000000..edc922f
--- /dev/null
+++ b/palettes/CLKINV_f.png
Binary files differ
diff --git a/palettes/CLKOUTV_f.png b/palettes/CLKOUTV_f.png
new file mode 100644
index 0000000..6cf24e3
--- /dev/null
+++ b/palettes/CLKOUTV_f.png
Binary files differ
diff --git a/palettes/CLKSOMV_f.png b/palettes/CLKSOMV_f.png
new file mode 100644
index 0000000..c4e7047
--- /dev/null
+++ b/palettes/CLKSOMV_f.png
Binary files differ
diff --git a/palettes/CLOCK_c.png b/palettes/CLOCK_c.png
new file mode 100644
index 0000000..45df9d1
--- /dev/null
+++ b/palettes/CLOCK_c.png
Binary files differ
diff --git a/palettes/CLR.png b/palettes/CLR.png
new file mode 100644
index 0000000..207f962
--- /dev/null
+++ b/palettes/CLR.png
Binary files differ
diff --git a/palettes/CLSS.png b/palettes/CLSS.png
new file mode 100644
index 0000000..e8cfa36
--- /dev/null
+++ b/palettes/CLSS.png
Binary files differ
diff --git a/palettes/CMAT3D.png b/palettes/CMAT3D.png
new file mode 100644
index 0000000..3f4d85a
--- /dev/null
+++ b/palettes/CMAT3D.png
Binary files differ
diff --git a/palettes/CMATVIEW.png b/palettes/CMATVIEW.png
new file mode 100644
index 0000000..c4fd3c7
--- /dev/null
+++ b/palettes/CMATVIEW.png
Binary files differ
diff --git a/palettes/CMSCOPE.png b/palettes/CMSCOPE.png
new file mode 100644
index 0000000..8f2dd2b
--- /dev/null
+++ b/palettes/CMSCOPE.png
Binary files differ
diff --git a/palettes/CONST.png b/palettes/CONST.png
new file mode 100644
index 0000000..8d4198d
--- /dev/null
+++ b/palettes/CONST.png
Binary files differ
diff --git a/palettes/CONSTRAINT2_c.png b/palettes/CONSTRAINT2_c.png
new file mode 100644
index 0000000..6d5141b
--- /dev/null
+++ b/palettes/CONSTRAINT2_c.png
Binary files differ
diff --git a/palettes/CONSTRAINT_c.png b/palettes/CONSTRAINT_c.png
new file mode 100644
index 0000000..fe00d60
--- /dev/null
+++ b/palettes/CONSTRAINT_c.png
Binary files differ
diff --git a/palettes/CONST_f.png b/palettes/CONST_f.png
new file mode 100644
index 0000000..8d4198d
--- /dev/null
+++ b/palettes/CONST_f.png
Binary files differ
diff --git a/palettes/CONST_m.png b/palettes/CONST_m.png
new file mode 100644
index 0000000..8d4198d
--- /dev/null
+++ b/palettes/CONST_m.png
Binary files differ
diff --git a/palettes/CONVERT.png b/palettes/CONVERT.png
new file mode 100644
index 0000000..22208cf
--- /dev/null
+++ b/palettes/CONVERT.png
Binary files differ
diff --git a/palettes/COSBLK_f.png b/palettes/COSBLK_f.png
new file mode 100644
index 0000000..d396ed9
--- /dev/null
+++ b/palettes/COSBLK_f.png
Binary files differ
diff --git a/palettes/CSCOPE.png b/palettes/CSCOPE.png
new file mode 100644
index 0000000..30db1c5
--- /dev/null
+++ b/palettes/CSCOPE.png
Binary files differ
diff --git a/palettes/CSCOPXY.png b/palettes/CSCOPXY.png
new file mode 100644
index 0000000..8e18cef
--- /dev/null
+++ b/palettes/CSCOPXY.png
Binary files differ
diff --git a/palettes/CSCOPXY3D.png b/palettes/CSCOPXY3D.png
new file mode 100644
index 0000000..ac16990
--- /dev/null
+++ b/palettes/CSCOPXY3D.png
Binary files differ
diff --git a/palettes/CUMSUM.png b/palettes/CUMSUM.png
new file mode 100644
index 0000000..5dcf063
--- /dev/null
+++ b/palettes/CUMSUM.png
Binary files differ
diff --git a/palettes/CURV_f.png b/palettes/CURV_f.png
new file mode 100644
index 0000000..057b81f
--- /dev/null
+++ b/palettes/CURV_f.png
Binary files differ
diff --git a/palettes/CVS.png b/palettes/CVS.png
new file mode 100644
index 0000000..1b41af9
--- /dev/null
+++ b/palettes/CVS.png
Binary files differ
diff --git a/palettes/Capacitor.png b/palettes/Capacitor.png
new file mode 100644
index 0000000..3d46380
--- /dev/null
+++ b/palettes/Capacitor.png
Binary files differ
diff --git a/palettes/ConstantVoltage.png b/palettes/ConstantVoltage.png
new file mode 100644
index 0000000..7431b2c
--- /dev/null
+++ b/palettes/ConstantVoltage.png
Binary files differ
diff --git a/palettes/Counter.png b/palettes/Counter.png
new file mode 100644
index 0000000..4f6ab1d
--- /dev/null
+++ b/palettes/Counter.png
Binary files differ
diff --git a/palettes/CurrentSensor.png b/palettes/CurrentSensor.png
new file mode 100644
index 0000000..b97f507
--- /dev/null
+++ b/palettes/CurrentSensor.png
Binary files differ
diff --git a/palettes/DEADBAND.png b/palettes/DEADBAND.png
new file mode 100644
index 0000000..e5799c3
--- /dev/null
+++ b/palettes/DEADBAND.png
Binary files differ
diff --git a/palettes/DEBUG.png b/palettes/DEBUG.png
new file mode 100644
index 0000000..fac6444
--- /dev/null
+++ b/palettes/DEBUG.png
Binary files differ
diff --git a/palettes/DELAYV_f.png b/palettes/DELAYV_f.png
new file mode 100644
index 0000000..cd8c8e2
--- /dev/null
+++ b/palettes/DELAYV_f.png
Binary files differ
diff --git a/palettes/DELAY_f.png b/palettes/DELAY_f.png
new file mode 100644
index 0000000..7e36f43
--- /dev/null
+++ b/palettes/DELAY_f.png
Binary files differ
diff --git a/palettes/DEMUX.png b/palettes/DEMUX.png
new file mode 100644
index 0000000..8f69ccd
--- /dev/null
+++ b/palettes/DEMUX.png
Binary files differ
diff --git a/palettes/DEMUX_f.png b/palettes/DEMUX_f.png
new file mode 100644
index 0000000..8f69ccd
--- /dev/null
+++ b/palettes/DEMUX_f.png
Binary files differ
diff --git a/palettes/DERIV.png b/palettes/DERIV.png
new file mode 100644
index 0000000..1f53768
--- /dev/null
+++ b/palettes/DERIV.png
Binary files differ
diff --git a/palettes/DFLIPFLOP.png b/palettes/DFLIPFLOP.png
new file mode 100644
index 0000000..5922bc2
--- /dev/null
+++ b/palettes/DFLIPFLOP.png
Binary files differ
diff --git a/palettes/DIFF_f.png b/palettes/DIFF_f.png
new file mode 100644
index 0000000..396bf12
--- /dev/null
+++ b/palettes/DIFF_f.png
Binary files differ
diff --git a/palettes/DLATCH.png b/palettes/DLATCH.png
new file mode 100644
index 0000000..5b580f1
--- /dev/null
+++ b/palettes/DLATCH.png
Binary files differ
diff --git a/palettes/DLR.png b/palettes/DLR.png
new file mode 100644
index 0000000..6d591b5
--- /dev/null
+++ b/palettes/DLR.png
Binary files differ
diff --git a/palettes/DLRADAPT_f.png b/palettes/DLRADAPT_f.png
new file mode 100644
index 0000000..557431d
--- /dev/null
+++ b/palettes/DLRADAPT_f.png
Binary files differ
diff --git a/palettes/DLSS.png b/palettes/DLSS.png
new file mode 100644
index 0000000..8d18fda
--- /dev/null
+++ b/palettes/DLSS.png
Binary files differ
diff --git a/palettes/DOLLAR.png b/palettes/DOLLAR.png
new file mode 100644
index 0000000..84bf03e
--- /dev/null
+++ b/palettes/DOLLAR.png
Binary files differ
diff --git a/palettes/DOLLAR_f.png b/palettes/DOLLAR_f.png
new file mode 100644
index 0000000..84bf03e
--- /dev/null
+++ b/palettes/DOLLAR_f.png
Binary files differ
diff --git a/palettes/DOLLAR_m.png b/palettes/DOLLAR_m.png
new file mode 100644
index 0000000..84bf03e
--- /dev/null
+++ b/palettes/DOLLAR_m.png
Binary files differ
diff --git a/palettes/Diode.png b/palettes/Diode.png
new file mode 100644
index 0000000..c97db57
--- /dev/null
+++ b/palettes/Diode.png
Binary files differ
diff --git a/palettes/EDGE_TRIGGER.png b/palettes/EDGE_TRIGGER.png
new file mode 100644
index 0000000..81f34cf
--- /dev/null
+++ b/palettes/EDGE_TRIGGER.png
Binary files differ
diff --git a/palettes/ENDBLK.png b/palettes/ENDBLK.png
new file mode 100644
index 0000000..54a5be4
--- /dev/null
+++ b/palettes/ENDBLK.png
Binary files differ
diff --git a/palettes/END_c.png b/palettes/END_c.png
new file mode 100644
index 0000000..98c1a4c
--- /dev/null
+++ b/palettes/END_c.png
Binary files differ
diff --git a/palettes/ESELECT_f.png b/palettes/ESELECT_f.png
new file mode 100644
index 0000000..d421399
--- /dev/null
+++ b/palettes/ESELECT_f.png
Binary files differ
diff --git a/palettes/EVTDLY_c.png b/palettes/EVTDLY_c.png
new file mode 100644
index 0000000..577808c
--- /dev/null
+++ b/palettes/EVTDLY_c.png
Binary files differ
diff --git a/palettes/EVTGEN_f.png b/palettes/EVTGEN_f.png
new file mode 100644
index 0000000..f7550e8
--- /dev/null
+++ b/palettes/EVTGEN_f.png
Binary files differ
diff --git a/palettes/EVTVARDLY.png b/palettes/EVTVARDLY.png
new file mode 100644
index 0000000..75fa575
--- /dev/null
+++ b/palettes/EVTVARDLY.png
Binary files differ
diff --git a/palettes/EXPBLK_m.png b/palettes/EXPBLK_m.png
new file mode 100644
index 0000000..c7341a2
--- /dev/null
+++ b/palettes/EXPBLK_m.png
Binary files differ
diff --git a/palettes/EXPRESSION.png b/palettes/EXPRESSION.png
new file mode 100644
index 0000000..9fbc05e
--- /dev/null
+++ b/palettes/EXPRESSION.png
Binary files differ
diff --git a/palettes/EXTRACT.png b/palettes/EXTRACT.png
new file mode 100644
index 0000000..06e4276
--- /dev/null
+++ b/palettes/EXTRACT.png
Binary files differ
diff --git a/palettes/EXTRACTBITS.png b/palettes/EXTRACTBITS.png
new file mode 100644
index 0000000..4ed0f79
--- /dev/null
+++ b/palettes/EXTRACTBITS.png
Binary files differ
diff --git a/palettes/EXTRACTOR.png b/palettes/EXTRACTOR.png
new file mode 100644
index 0000000..9a45cf4
--- /dev/null
+++ b/palettes/EXTRACTOR.png
Binary files differ
diff --git a/palettes/EXTTRI.png b/palettes/EXTTRI.png
new file mode 100644
index 0000000..2c5fb35
--- /dev/null
+++ b/palettes/EXTTRI.png
Binary files differ
diff --git a/palettes/Extract_Activation.png b/palettes/Extract_Activation.png
new file mode 100644
index 0000000..640f082
--- /dev/null
+++ b/palettes/Extract_Activation.png
Binary files differ
diff --git a/palettes/FROM.png b/palettes/FROM.png
new file mode 100644
index 0000000..14bd241
--- /dev/null
+++ b/palettes/FROM.png
Binary files differ
diff --git a/palettes/FROMMO.png b/palettes/FROMMO.png
new file mode 100644
index 0000000..5d1274d
--- /dev/null
+++ b/palettes/FROMMO.png
Binary files differ
diff --git a/palettes/FROMWSB.png b/palettes/FROMWSB.png
new file mode 100644
index 0000000..e0451a6
--- /dev/null
+++ b/palettes/FROMWSB.png
Binary files differ
diff --git a/palettes/Flowmeter.png b/palettes/Flowmeter.png
new file mode 100644
index 0000000..1ee7fe3
--- /dev/null
+++ b/palettes/Flowmeter.png
Binary files differ
diff --git a/palettes/GAINBLK.png b/palettes/GAINBLK.png
new file mode 100644
index 0000000..8fa10d5
--- /dev/null
+++ b/palettes/GAINBLK.png
Binary files differ
diff --git a/palettes/GAINBLK_f.png b/palettes/GAINBLK_f.png
new file mode 100644
index 0000000..8fa10d5
--- /dev/null
+++ b/palettes/GAINBLK_f.png
Binary files differ
diff --git a/palettes/GAIN_f.png b/palettes/GAIN_f.png
new file mode 100644
index 0000000..8fa10d5
--- /dev/null
+++ b/palettes/GAIN_f.png
Binary files differ
diff --git a/palettes/GENERAL_f.png b/palettes/GENERAL_f.png
new file mode 100644
index 0000000..bad61b6
--- /dev/null
+++ b/palettes/GENERAL_f.png
Binary files differ
diff --git a/palettes/GENSIN_f.png b/palettes/GENSIN_f.png
new file mode 100644
index 0000000..83afc34
--- /dev/null
+++ b/palettes/GENSIN_f.png
Binary files differ
diff --git a/palettes/GENSQR_f.png b/palettes/GENSQR_f.png
new file mode 100644
index 0000000..3e9f154
--- /dev/null
+++ b/palettes/GENSQR_f.png
Binary files differ
diff --git a/palettes/GOTO.png b/palettes/GOTO.png
new file mode 100644
index 0000000..f1cb7a1
--- /dev/null
+++ b/palettes/GOTO.png
Binary files differ
diff --git a/palettes/GOTOMO.png b/palettes/GOTOMO.png
new file mode 100644
index 0000000..6e4728f
--- /dev/null
+++ b/palettes/GOTOMO.png
Binary files differ
diff --git a/palettes/GotoTagVisibility.png b/palettes/GotoTagVisibility.png
new file mode 100644
index 0000000..b397415
--- /dev/null
+++ b/palettes/GotoTagVisibility.png
Binary files differ
diff --git a/palettes/GotoTagVisibilityMO.png b/palettes/GotoTagVisibilityMO.png
new file mode 100644
index 0000000..7a2eb73
--- /dev/null
+++ b/palettes/GotoTagVisibilityMO.png
Binary files differ
diff --git a/palettes/Ground.png b/palettes/Ground.png
new file mode 100644
index 0000000..19681b6
--- /dev/null
+++ b/palettes/Ground.png
Binary files differ
diff --git a/palettes/Gyrator.png b/palettes/Gyrator.png
new file mode 100644
index 0000000..55f0cf4
--- /dev/null
+++ b/palettes/Gyrator.png
Binary files differ
diff --git a/palettes/HALT_f.png b/palettes/HALT_f.png
new file mode 100644
index 0000000..dc8c23e
--- /dev/null
+++ b/palettes/HALT_f.png
Binary files differ
diff --git a/palettes/HYSTHERESIS.png b/palettes/HYSTHERESIS.png
new file mode 100644
index 0000000..ccee255
--- /dev/null
+++ b/palettes/HYSTHERESIS.png
Binary files differ
diff --git a/palettes/IFTHEL_f.png b/palettes/IFTHEL_f.png
new file mode 100644
index 0000000..4ffba86
--- /dev/null
+++ b/palettes/IFTHEL_f.png
Binary files differ
diff --git a/palettes/INIMPL_f.png b/palettes/INIMPL_f.png
new file mode 100644
index 0000000..4750c15
--- /dev/null
+++ b/palettes/INIMPL_f.png
Binary files differ
diff --git a/palettes/INTEGRAL_f.png b/palettes/INTEGRAL_f.png
new file mode 100644
index 0000000..e1fd0c8
--- /dev/null
+++ b/palettes/INTEGRAL_f.png
Binary files differ
diff --git a/palettes/INTEGRAL_m.png b/palettes/INTEGRAL_m.png
new file mode 100644
index 0000000..f560cf5
--- /dev/null
+++ b/palettes/INTEGRAL_m.png
Binary files differ
diff --git a/palettes/INTMUL.png b/palettes/INTMUL.png
new file mode 100644
index 0000000..8185aaf
--- /dev/null
+++ b/palettes/INTMUL.png
Binary files differ
diff --git a/palettes/INTRP2BLK_f.png b/palettes/INTRP2BLK_f.png
new file mode 100644
index 0000000..850a028
--- /dev/null
+++ b/palettes/INTRP2BLK_f.png
Binary files differ
diff --git a/palettes/INTRPLBLK_f.png b/palettes/INTRPLBLK_f.png
new file mode 100644
index 0000000..d67fa1f
--- /dev/null
+++ b/palettes/INTRPLBLK_f.png
Binary files differ
diff --git a/palettes/INVBLK.png b/palettes/INVBLK.png
new file mode 100644
index 0000000..e70af6c
--- /dev/null
+++ b/palettes/INVBLK.png
Binary files differ
diff --git a/palettes/IN_f.png b/palettes/IN_f.png
new file mode 100644
index 0000000..c05dfff
--- /dev/null
+++ b/palettes/IN_f.png
Binary files differ
diff --git a/palettes/ISELECT_m.png b/palettes/ISELECT_m.png
new file mode 100644
index 0000000..376e80a
--- /dev/null
+++ b/palettes/ISELECT_m.png
Binary files differ
diff --git a/palettes/IdealTransformer.png b/palettes/IdealTransformer.png
new file mode 100644
index 0000000..7342c1c
--- /dev/null
+++ b/palettes/IdealTransformer.png
Binary files differ
diff --git a/palettes/Inductor.png b/palettes/Inductor.png
new file mode 100644
index 0000000..3a9f355
--- /dev/null
+++ b/palettes/Inductor.png
Binary files differ
diff --git a/palettes/JKFLIPFLOP.png b/palettes/JKFLIPFLOP.png
new file mode 100644
index 0000000..af400ed
--- /dev/null
+++ b/palettes/JKFLIPFLOP.png
Binary files differ
diff --git a/palettes/LOGBLK_f.png b/palettes/LOGBLK_f.png
new file mode 100644
index 0000000..85965d4
--- /dev/null
+++ b/palettes/LOGBLK_f.png
Binary files differ
diff --git a/palettes/LOGIC.png b/palettes/LOGIC.png
new file mode 100644
index 0000000..ec4aa35
--- /dev/null
+++ b/palettes/LOGIC.png
Binary files differ
diff --git a/palettes/LOGICAL_OP.png b/palettes/LOGICAL_OP.png
new file mode 100644
index 0000000..09f5f4e
--- /dev/null
+++ b/palettes/LOGICAL_OP.png
Binary files differ
diff --git a/palettes/LOOKUP_f.png b/palettes/LOOKUP_f.png
new file mode 100644
index 0000000..ab43c1b
--- /dev/null
+++ b/palettes/LOOKUP_f.png
Binary files differ
diff --git a/palettes/MATBKSL.png b/palettes/MATBKSL.png
new file mode 100644
index 0000000..74a541c
--- /dev/null
+++ b/palettes/MATBKSL.png
Binary files differ
diff --git a/palettes/MATCATH.png b/palettes/MATCATH.png
new file mode 100644
index 0000000..28b7b58
--- /dev/null
+++ b/palettes/MATCATH.png
Binary files differ
diff --git a/palettes/MATCATV.png b/palettes/MATCATV.png
new file mode 100644
index 0000000..cf693c1
--- /dev/null
+++ b/palettes/MATCATV.png
Binary files differ
diff --git a/palettes/MATDET.png b/palettes/MATDET.png
new file mode 100644
index 0000000..c20b9c9
--- /dev/null
+++ b/palettes/MATDET.png
Binary files differ
diff --git a/palettes/MATDIAG.png b/palettes/MATDIAG.png
new file mode 100644
index 0000000..fd255ab
--- /dev/null
+++ b/palettes/MATDIAG.png
Binary files differ
diff --git a/palettes/MATDIV.png b/palettes/MATDIV.png
new file mode 100644
index 0000000..39f8b2b
--- /dev/null
+++ b/palettes/MATDIV.png
Binary files differ
diff --git a/palettes/MATEIG.png b/palettes/MATEIG.png
new file mode 100644
index 0000000..12fd878
--- /dev/null
+++ b/palettes/MATEIG.png
Binary files differ
diff --git a/palettes/MATEXPM.png b/palettes/MATEXPM.png
new file mode 100644
index 0000000..2951e52
--- /dev/null
+++ b/palettes/MATEXPM.png
Binary files differ
diff --git a/palettes/MATINV.png b/palettes/MATINV.png
new file mode 100644
index 0000000..b4bb390
--- /dev/null
+++ b/palettes/MATINV.png
Binary files differ
diff --git a/palettes/MATLU.png b/palettes/MATLU.png
new file mode 100644
index 0000000..863afce
--- /dev/null
+++ b/palettes/MATLU.png
Binary files differ
diff --git a/palettes/MATMAGPHI.png b/palettes/MATMAGPHI.png
new file mode 100644
index 0000000..a56b57a
--- /dev/null
+++ b/palettes/MATMAGPHI.png
Binary files differ
diff --git a/palettes/MATMUL.png b/palettes/MATMUL.png
new file mode 100644
index 0000000..90c71fd
--- /dev/null
+++ b/palettes/MATMUL.png
Binary files differ
diff --git a/palettes/MATPINV.png b/palettes/MATPINV.png
new file mode 100644
index 0000000..a17a7a7
--- /dev/null
+++ b/palettes/MATPINV.png
Binary files differ
diff --git a/palettes/MATRESH.png b/palettes/MATRESH.png
new file mode 100644
index 0000000..cbc0189
--- /dev/null
+++ b/palettes/MATRESH.png
Binary files differ
diff --git a/palettes/MATSING.png b/palettes/MATSING.png
new file mode 100644
index 0000000..ef53ffd
--- /dev/null
+++ b/palettes/MATSING.png
Binary files differ
diff --git a/palettes/MATSUM.png b/palettes/MATSUM.png
new file mode 100644
index 0000000..1b9a43b
--- /dev/null
+++ b/palettes/MATSUM.png
Binary files differ
diff --git a/palettes/MATTRAN.png b/palettes/MATTRAN.png
new file mode 100644
index 0000000..0c3a387
--- /dev/null
+++ b/palettes/MATTRAN.png
Binary files differ
diff --git a/palettes/MATZCONJ.png b/palettes/MATZCONJ.png
new file mode 100644
index 0000000..6251310
--- /dev/null
+++ b/palettes/MATZCONJ.png
Binary files differ
diff --git a/palettes/MATZREIM.png b/palettes/MATZREIM.png
new file mode 100644
index 0000000..f217649
--- /dev/null
+++ b/palettes/MATZREIM.png
Binary files differ
diff --git a/palettes/MAXMIN.png b/palettes/MAXMIN.png
new file mode 100644
index 0000000..18a800e
--- /dev/null
+++ b/palettes/MAXMIN.png
Binary files differ
diff --git a/palettes/MAX_f.png b/palettes/MAX_f.png
new file mode 100644
index 0000000..18a800e
--- /dev/null
+++ b/palettes/MAX_f.png
Binary files differ
diff --git a/palettes/MBLOCK.png b/palettes/MBLOCK.png
new file mode 100644
index 0000000..d22c2d4
--- /dev/null
+++ b/palettes/MBLOCK.png
Binary files differ
diff --git a/palettes/MCLOCK_f.png b/palettes/MCLOCK_f.png
new file mode 100644
index 0000000..bffc806
--- /dev/null
+++ b/palettes/MCLOCK_f.png
Binary files differ
diff --git a/palettes/MFCLCK_f.png b/palettes/MFCLCK_f.png
new file mode 100644
index 0000000..70a32be
--- /dev/null
+++ b/palettes/MFCLCK_f.png
Binary files differ
diff --git a/palettes/MIN_f.png b/palettes/MIN_f.png
new file mode 100644
index 0000000..9136794
--- /dev/null
+++ b/palettes/MIN_f.png
Binary files differ
diff --git a/palettes/MUX.png b/palettes/MUX.png
new file mode 100644
index 0000000..8d7936b
--- /dev/null
+++ b/palettes/MUX.png
Binary files differ
diff --git a/palettes/MUX_f.png b/palettes/MUX_f.png
new file mode 100644
index 0000000..8d7936b
--- /dev/null
+++ b/palettes/MUX_f.png
Binary files differ
diff --git a/palettes/M_SWITCH.png b/palettes/M_SWITCH.png
new file mode 100644
index 0000000..4b3f327
--- /dev/null
+++ b/palettes/M_SWITCH.png
Binary files differ
diff --git a/palettes/M_freq.png b/palettes/M_freq.png
new file mode 100644
index 0000000..ba07fca
--- /dev/null
+++ b/palettes/M_freq.png
Binary files differ
diff --git a/palettes/Modulo_Count.png b/palettes/Modulo_Count.png
new file mode 100644
index 0000000..b36ecaf
--- /dev/null
+++ b/palettes/Modulo_Count.png
Binary files differ
diff --git a/palettes/NEGTOPOS_f.png b/palettes/NEGTOPOS_f.png
new file mode 100644
index 0000000..47dadad
--- /dev/null
+++ b/palettes/NEGTOPOS_f.png
Binary files differ
diff --git a/palettes/NMOS.png b/palettes/NMOS.png
new file mode 100644
index 0000000..298a523
--- /dev/null
+++ b/palettes/NMOS.png
Binary files differ
diff --git a/palettes/NPN.png b/palettes/NPN.png
new file mode 100644
index 0000000..eb590e6
--- /dev/null
+++ b/palettes/NPN.png
Binary files differ
diff --git a/palettes/NRMSOM_f.png b/palettes/NRMSOM_f.png
new file mode 100644
index 0000000..13d25c7
--- /dev/null
+++ b/palettes/NRMSOM_f.png
Binary files differ
diff --git a/palettes/OUTIMPL_f.png b/palettes/OUTIMPL_f.png
new file mode 100644
index 0000000..2d7c6fc
--- /dev/null
+++ b/palettes/OUTIMPL_f.png
Binary files differ
diff --git a/palettes/OUT_f.png b/palettes/OUT_f.png
new file mode 100644
index 0000000..9711671
--- /dev/null
+++ b/palettes/OUT_f.png
Binary files differ
diff --git a/palettes/OpAmp.png b/palettes/OpAmp.png
new file mode 100644
index 0000000..83844f5
--- /dev/null
+++ b/palettes/OpAmp.png
Binary files differ
diff --git a/palettes/PDE.png b/palettes/PDE.png
new file mode 100644
index 0000000..fa49103
--- /dev/null
+++ b/palettes/PDE.png
Binary files differ
diff --git a/palettes/PID.png b/palettes/PID.png
new file mode 100644
index 0000000..43444cc
--- /dev/null
+++ b/palettes/PID.png
Binary files differ
diff --git a/palettes/PMOS.png b/palettes/PMOS.png
new file mode 100644
index 0000000..f6fd975
--- /dev/null
+++ b/palettes/PMOS.png
Binary files differ
diff --git a/palettes/PNP.png b/palettes/PNP.png
new file mode 100644
index 0000000..4bdcc86
--- /dev/null
+++ b/palettes/PNP.png
Binary files differ
diff --git a/palettes/POSTONEG_f.png b/palettes/POSTONEG_f.png
new file mode 100644
index 0000000..d8c4c72
--- /dev/null
+++ b/palettes/POSTONEG_f.png
Binary files differ
diff --git a/palettes/POWBLK_f.png b/palettes/POWBLK_f.png
new file mode 100644
index 0000000..d34448a
--- /dev/null
+++ b/palettes/POWBLK_f.png
Binary files differ
diff --git a/palettes/PRODUCT.png b/palettes/PRODUCT.png
new file mode 100644
index 0000000..daa15ac
--- /dev/null
+++ b/palettes/PRODUCT.png
Binary files differ
diff --git a/palettes/PROD_f.png b/palettes/PROD_f.png
new file mode 100644
index 0000000..6d2557b
--- /dev/null
+++ b/palettes/PROD_f.png
Binary files differ
diff --git a/palettes/PULSE_SC.png b/palettes/PULSE_SC.png
new file mode 100644
index 0000000..be441fe
--- /dev/null
+++ b/palettes/PULSE_SC.png
Binary files differ
diff --git a/palettes/PerteDP.png b/palettes/PerteDP.png
new file mode 100644
index 0000000..c13b018
--- /dev/null
+++ b/palettes/PerteDP.png
Binary files differ
diff --git a/palettes/PotentialSensor.png b/palettes/PotentialSensor.png
new file mode 100644
index 0000000..c121089
--- /dev/null
+++ b/palettes/PotentialSensor.png
Binary files differ
diff --git a/palettes/PuitsP.png b/palettes/PuitsP.png
new file mode 100644
index 0000000..6de79da
--- /dev/null
+++ b/palettes/PuitsP.png
Binary files differ
diff --git a/palettes/QUANT_f.png b/palettes/QUANT_f.png
new file mode 100644
index 0000000..94a98f7
--- /dev/null
+++ b/palettes/QUANT_f.png
Binary files differ
diff --git a/palettes/RAMP.png b/palettes/RAMP.png
new file mode 100644
index 0000000..4d99da2
--- /dev/null
+++ b/palettes/RAMP.png
Binary files differ
diff --git a/palettes/RAND_m.png b/palettes/RAND_m.png
new file mode 100644
index 0000000..cb037c9
--- /dev/null
+++ b/palettes/RAND_m.png
Binary files differ
diff --git a/palettes/RATELIMITER.png b/palettes/RATELIMITER.png
new file mode 100644
index 0000000..3857e1c
--- /dev/null
+++ b/palettes/RATELIMITER.png
Binary files differ
diff --git a/palettes/READAU_f.png b/palettes/READAU_f.png
new file mode 100644
index 0000000..1bf4d12
--- /dev/null
+++ b/palettes/READAU_f.png
Binary files differ
diff --git a/palettes/READC_f.png b/palettes/READC_f.png
new file mode 100644
index 0000000..2afa899
--- /dev/null
+++ b/palettes/READC_f.png
Binary files differ
diff --git a/palettes/REGISTER.png b/palettes/REGISTER.png
new file mode 100644
index 0000000..daa3057
--- /dev/null
+++ b/palettes/REGISTER.png
Binary files differ
diff --git a/palettes/RELATIONALOP.png b/palettes/RELATIONALOP.png
new file mode 100644
index 0000000..ff2ba4a
--- /dev/null
+++ b/palettes/RELATIONALOP.png
Binary files differ
diff --git a/palettes/RELAY_f.png b/palettes/RELAY_f.png
new file mode 100644
index 0000000..980ceb1
--- /dev/null
+++ b/palettes/RELAY_f.png
Binary files differ
diff --git a/palettes/RFILE_f.png b/palettes/RFILE_f.png
new file mode 100644
index 0000000..e2dd24e
--- /dev/null
+++ b/palettes/RFILE_f.png
Binary files differ
diff --git a/palettes/RICC.png b/palettes/RICC.png
new file mode 100644
index 0000000..44cc457
--- /dev/null
+++ b/palettes/RICC.png
Binary files differ
diff --git a/palettes/ROOTCOEF.png b/palettes/ROOTCOEF.png
new file mode 100644
index 0000000..79bd011
--- /dev/null
+++ b/palettes/ROOTCOEF.png
Binary files differ
diff --git a/palettes/Resistor.png b/palettes/Resistor.png
new file mode 100644
index 0000000..76b4af3
--- /dev/null
+++ b/palettes/Resistor.png
Binary files differ
diff --git a/palettes/SAMPHOLD_m.png b/palettes/SAMPHOLD_m.png
new file mode 100644
index 0000000..e4ca5a0
--- /dev/null
+++ b/palettes/SAMPHOLD_m.png
Binary files differ
diff --git a/palettes/SATURATION.png b/palettes/SATURATION.png
new file mode 100644
index 0000000..05a4aef
--- /dev/null
+++ b/palettes/SATURATION.png
Binary files differ
diff --git a/palettes/SAWTOOTH_f.png b/palettes/SAWTOOTH_f.png
new file mode 100644
index 0000000..dc8cc49
--- /dev/null
+++ b/palettes/SAWTOOTH_f.png
Binary files differ
diff --git a/palettes/SCALAR2VECTOR.png b/palettes/SCALAR2VECTOR.png
new file mode 100644
index 0000000..df3cd3f
--- /dev/null
+++ b/palettes/SCALAR2VECTOR.png
Binary files differ
diff --git a/palettes/SELECT_m.png b/palettes/SELECT_m.png
new file mode 100644
index 0000000..90b910b
--- /dev/null
+++ b/palettes/SELECT_m.png
Binary files differ
diff --git a/palettes/SELF_SWITCH.png b/palettes/SELF_SWITCH.png
new file mode 100644
index 0000000..58e3589
--- /dev/null
+++ b/palettes/SELF_SWITCH.png
Binary files differ
diff --git a/palettes/SELF_SWITCH_off.png b/palettes/SELF_SWITCH_off.png
new file mode 100644
index 0000000..7a8c530
--- /dev/null
+++ b/palettes/SELF_SWITCH_off.png
Binary files differ
diff --git a/palettes/SELF_SWITCH_on.png b/palettes/SELF_SWITCH_on.png
new file mode 100644
index 0000000..df7f4cc
--- /dev/null
+++ b/palettes/SELF_SWITCH_on.png
Binary files differ
diff --git a/palettes/SHIFT.png b/palettes/SHIFT.png
new file mode 100644
index 0000000..bc35f5b
--- /dev/null
+++ b/palettes/SHIFT.png
Binary files differ
diff --git a/palettes/SIGNUM.png b/palettes/SIGNUM.png
new file mode 100644
index 0000000..83698e9
--- /dev/null
+++ b/palettes/SIGNUM.png
Binary files differ
diff --git a/palettes/SINBLK_f.png b/palettes/SINBLK_f.png
new file mode 100644
index 0000000..e7daa78
--- /dev/null
+++ b/palettes/SINBLK_f.png
Binary files differ
diff --git a/palettes/SOM_f.png b/palettes/SOM_f.png
new file mode 100644
index 0000000..ea51ecd
--- /dev/null
+++ b/palettes/SOM_f.png
Binary files differ
diff --git a/palettes/SQRT.png b/palettes/SQRT.png
new file mode 100644
index 0000000..4085bf0
--- /dev/null
+++ b/palettes/SQRT.png
Binary files differ
diff --git a/palettes/SRFLIPFLOP.png b/palettes/SRFLIPFLOP.png
new file mode 100644
index 0000000..d84b569
--- /dev/null
+++ b/palettes/SRFLIPFLOP.png
Binary files differ
diff --git a/palettes/STEP_FUNCTION.png b/palettes/STEP_FUNCTION.png
new file mode 100644
index 0000000..3eb2c56
--- /dev/null
+++ b/palettes/STEP_FUNCTION.png
Binary files differ
diff --git a/palettes/SUBMAT.png b/palettes/SUBMAT.png
new file mode 100644
index 0000000..91abccb
--- /dev/null
+++ b/palettes/SUBMAT.png
Binary files differ
diff --git a/palettes/SUMMATION.png b/palettes/SUMMATION.png
new file mode 100644
index 0000000..a3168ab
--- /dev/null
+++ b/palettes/SUMMATION.png
Binary files differ
diff --git a/palettes/SUM_f.png b/palettes/SUM_f.png
new file mode 100644
index 0000000..602f369
--- /dev/null
+++ b/palettes/SUM_f.png
Binary files differ
diff --git a/palettes/SUPER_f.png b/palettes/SUPER_f.png
new file mode 100644
index 0000000..13ba1a1
--- /dev/null
+++ b/palettes/SUPER_f.png
Binary files differ
diff --git a/palettes/SWITCH2_m.png b/palettes/SWITCH2_m.png
new file mode 100644
index 0000000..0eb0491
--- /dev/null
+++ b/palettes/SWITCH2_m.png
Binary files differ
diff --git a/palettes/SWITCH_f.png b/palettes/SWITCH_f.png
new file mode 100644
index 0000000..9a4e7a2
--- /dev/null
+++ b/palettes/SWITCH_f.png
Binary files differ
diff --git a/palettes/SampleCLK.png b/palettes/SampleCLK.png
new file mode 100644
index 0000000..b3e9ff5
--- /dev/null
+++ b/palettes/SampleCLK.png
Binary files differ
diff --git a/palettes/Sigbuilder.png b/palettes/Sigbuilder.png
new file mode 100644
index 0000000..290f7c1
--- /dev/null
+++ b/palettes/Sigbuilder.png
Binary files differ
diff --git a/palettes/SineVoltage.png b/palettes/SineVoltage.png
new file mode 100644
index 0000000..86708cb
--- /dev/null
+++ b/palettes/SineVoltage.png
Binary files differ
diff --git a/palettes/SourceP.png b/palettes/SourceP.png
new file mode 100644
index 0000000..6bb51ed
--- /dev/null
+++ b/palettes/SourceP.png
Binary files differ
diff --git a/palettes/Switch.png b/palettes/Switch.png
new file mode 100644
index 0000000..a707afd
--- /dev/null
+++ b/palettes/Switch.png
Binary files differ
diff --git a/palettes/TANBLK_f.png b/palettes/TANBLK_f.png
new file mode 100644
index 0000000..0cb8642
--- /dev/null
+++ b/palettes/TANBLK_f.png
Binary files differ
diff --git a/palettes/TCLSS.png b/palettes/TCLSS.png
new file mode 100644
index 0000000..e2d2c15
--- /dev/null
+++ b/palettes/TCLSS.png
Binary files differ
diff --git a/palettes/TEXT_f.png b/palettes/TEXT_f.png
new file mode 100644
index 0000000..ea88f46
--- /dev/null
+++ b/palettes/TEXT_f.png
Binary files differ
diff --git a/palettes/TIME_DELAY.png b/palettes/TIME_DELAY.png
new file mode 100644
index 0000000..881fa36
--- /dev/null
+++ b/palettes/TIME_DELAY.png
Binary files differ
diff --git a/palettes/TIME_f.png b/palettes/TIME_f.png
new file mode 100644
index 0000000..669edfc
--- /dev/null
+++ b/palettes/TIME_f.png
Binary files differ
diff --git a/palettes/TKSCALE.png b/palettes/TKSCALE.png
new file mode 100644
index 0000000..cd9a1b4
--- /dev/null
+++ b/palettes/TKSCALE.png
Binary files differ
diff --git a/palettes/TOWS_c.png b/palettes/TOWS_c.png
new file mode 100644
index 0000000..145b071
--- /dev/null
+++ b/palettes/TOWS_c.png
Binary files differ
diff --git a/palettes/TRASH_f.png b/palettes/TRASH_f.png
new file mode 100644
index 0000000..38b56f7
--- /dev/null
+++ b/palettes/TRASH_f.png
Binary files differ
diff --git a/palettes/TrigFun.png b/palettes/TrigFun.png
new file mode 100644
index 0000000..ef022f7
--- /dev/null
+++ b/palettes/TrigFun.png
Binary files differ
diff --git a/palettes/VARIABLE_DELAY.png b/palettes/VARIABLE_DELAY.png
new file mode 100644
index 0000000..9970551
--- /dev/null
+++ b/palettes/VARIABLE_DELAY.png
Binary files differ
diff --git a/palettes/VVsourceAC.png b/palettes/VVsourceAC.png
new file mode 100644
index 0000000..40de6b9
--- /dev/null
+++ b/palettes/VVsourceAC.png
Binary files differ
diff --git a/palettes/VanneReglante.png b/palettes/VanneReglante.png
new file mode 100644
index 0000000..caf624d
--- /dev/null
+++ b/palettes/VanneReglante.png
Binary files differ
diff --git a/palettes/VariableResistor.png b/palettes/VariableResistor.png
new file mode 100644
index 0000000..03550cd
--- /dev/null
+++ b/palettes/VariableResistor.png
Binary files differ
diff --git a/palettes/VirtualCLK0.png b/palettes/VirtualCLK0.png
new file mode 100644
index 0000000..60f5359
--- /dev/null
+++ b/palettes/VirtualCLK0.png
Binary files differ
diff --git a/palettes/VoltageSensor.png b/palettes/VoltageSensor.png
new file mode 100644
index 0000000..21a2276
--- /dev/null
+++ b/palettes/VoltageSensor.png
Binary files differ
diff --git a/palettes/VsourceAC.png b/palettes/VsourceAC.png
new file mode 100644
index 0000000..a1394db
--- /dev/null
+++ b/palettes/VsourceAC.png
Binary files differ
diff --git a/palettes/WFILE_f.png b/palettes/WFILE_f.png
new file mode 100644
index 0000000..82fd95d
--- /dev/null
+++ b/palettes/WFILE_f.png
Binary files differ
diff --git a/palettes/WRITEAU_f.png b/palettes/WRITEAU_f.png
new file mode 100644
index 0000000..54766f5
--- /dev/null
+++ b/palettes/WRITEAU_f.png
Binary files differ
diff --git a/palettes/WRITEC_f.png b/palettes/WRITEC_f.png
new file mode 100644
index 0000000..6f8877a
--- /dev/null
+++ b/palettes/WRITEC_f.png
Binary files differ
diff --git a/palettes/ZCROSS_f.png b/palettes/ZCROSS_f.png
new file mode 100644
index 0000000..8081825
--- /dev/null
+++ b/palettes/ZCROSS_f.png
Binary files differ
diff --git a/palettes/c_block.png b/palettes/c_block.png
new file mode 100644
index 0000000..36b4643
--- /dev/null
+++ b/palettes/c_block.png
Binary files differ
diff --git a/palettes/fortran_block.png b/palettes/fortran_block.png
new file mode 100644
index 0000000..f7e31b8
--- /dev/null
+++ b/palettes/fortran_block.png
Binary files differ
diff --git a/palettes/freq_div.png b/palettes/freq_div.png
new file mode 100644
index 0000000..b3921b4
--- /dev/null
+++ b/palettes/freq_div.png
Binary files differ
diff --git a/palettes/generic_block3.png b/palettes/generic_block3.png
new file mode 100644
index 0000000..da7a276
--- /dev/null
+++ b/palettes/generic_block3.png
Binary files differ
diff --git a/palettes/palettes.xml b/palettes/palettes.xml
new file mode 100644
index 0000000..b091658
--- /dev/null
+++ b/palettes/palettes.xml
@@ -0,0 +1,849 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<category enable="true" name="Config">
+ <node xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="Category" enable="true" name="Palettes">
+ <node xsi:type="PreLoaded" enable="true" name="Commonly Used Blocks">
+ <block name="ANDBLK">
+ <icon variable="SCI" path="palettes/ANDBLK.png"/>
+ </block>
+ <block name="BIGSOM_f">
+ <icon variable="SCI" path="palettes/BIGSOM_f.png"/>
+ </block>
+ <block name="CMSCOPE">
+ <icon variable="SCI" path="palettes/CMSCOPE.png"/>
+ </block>
+ <block name="CONST_m">
+ <icon variable="SCI" path="palettes/CONST_m.png"/>
+ </block>
+ <block name="CONVERT">
+ <icon variable="SCI" path="palettes/CONVERT.png"/>
+ </block>
+ <block name="CSCOPXY">
+ <icon variable="SCI" path="palettes/CSCOPXY.png"/>
+ </block>
+ <block name="DEMUX">
+ <icon variable="SCI" path="palettes/DEMUX.png"/>
+ </block>
+ <block name="DOLLAR_f">
+ <icon variable="SCI" path="palettes/DOLLAR_f.png"/>
+ </block>
+ <block name="INTEGRAL_f">
+ <icon variable="SCI" path="palettes/INTEGRAL_f.png"/>
+ </block>
+ <block name="IN_f">
+ <icon variable="SCI" path="palettes/IN_f.png"/>
+ </block>
+ <block name="LOGICAL_OP">
+ <icon variable="SCI" path="palettes/LOGICAL_OP.png"/>
+ </block>
+ <block name="MUX">
+ <icon variable="SCI" path="palettes/MUX.png"/>
+ </block>
+ <block name="NRMSOM_f">
+ <icon variable="SCI" path="palettes/NRMSOM_f.png"/>
+ </block>
+ <block name="OUT_f">
+ <icon variable="SCI" path="palettes/OUT_f.png"/>
+ </block>
+ <block name="PRODUCT">
+ <icon variable="SCI" path="palettes/PRODUCT.png"/>
+ </block>
+ <block name="RELATIONALOP">
+ <icon variable="SCI" path="palettes/RELATIONALOP.png"/>
+ </block>
+ <block name="SATURATION">
+ <icon variable="SCI" path="palettes/SATURATION.png"/>
+ </block>
+ <block name="SWITCH2_m">
+ <icon variable="SCI" path="palettes/SWITCH2_m.png"/>
+ </block>
+ <block name="TEXT_f">
+ <icon variable="SCI" path="palettes/TEXT_f.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Continuous time systems">
+ <block name="CLINDUMMY_f">
+ <icon variable="SCI" path="palettes/CLINDUMMY_f.png"/>
+ </block>
+ <block name="CLR">
+ <icon variable="SCI" path="palettes/CLR.png"/>
+ </block>
+ <block name="CLSS">
+ <icon variable="SCI" path="palettes/CLSS.png"/>
+ </block>
+ <block name="DERIV">
+ <icon variable="SCI" path="palettes/DERIV.png"/>
+ </block>
+ <block name="INTEGRAL_f">
+ <icon variable="SCI" path="palettes/INTEGRAL_f.png"/>
+ </block>
+ <block name="INTEGRAL_m">
+ <icon variable="SCI" path="palettes/INTEGRAL_m.png"/>
+ </block>
+ <block name="PID">
+ <icon variable="SCI" path="palettes/PID.png"/>
+ </block>
+ <block name="TCLSS">
+ <icon variable="SCI" path="palettes/TCLSS.png"/>
+ </block>
+ <block name="TIME_DELAY">
+ <icon variable="SCI" path="palettes/TIME_DELAY.png"/>
+ </block>
+ <block name="VARIABLE_DELAY">
+ <icon variable="SCI" path="palettes/VARIABLE_DELAY.png"/>
+ </block>
+ <block name="PDE">
+ <icon variable="SCI" path="palettes/PDE.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Discontinuities">
+ <block name="BACKLASH">
+ <icon variable="SCI" path="palettes/BACKLASH.png"/>
+ </block>
+ <block name="DEADBAND">
+ <icon variable="SCI" path="palettes/DEADBAND.png"/>
+ </block>
+ <block name="DELAYV_f">
+ <icon variable="SCI" path="palettes/DELAYV_f.png"/>
+ </block>
+ <block name="HYSTHERESIS">
+ <icon variable="SCI" path="palettes/HYSTHERESIS.png"/>
+ </block>
+ <block name="RATELIMITER">
+ <icon variable="SCI" path="palettes/RATELIMITER.png"/>
+ </block>
+ <block name="QUANT_f">
+ <icon variable="SCI" path="palettes/QUANT_f.png"/>
+ </block>
+ <block name="SATURATION">
+ <icon variable="SCI" path="palettes/SATURATION.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Discrete time systems">
+ <block name="AUTOMAT">
+ <icon variable="SCI" path="palettes/AUTOMAT.png"/>
+ </block>
+ <block name="DELAYV_f">
+ <icon variable="SCI" path="palettes/DELAYV_f.png"/>
+ </block>
+ <block name="DELAY_f">
+ <icon variable="SCI" path="palettes/DELAY_f.png"/>
+ </block>
+ <block name="DLR">
+ <icon variable="SCI" path="palettes/DLR.png"/>
+ </block>
+ <block name="DLRADAPT_f">
+ <icon variable="SCI" path="palettes/DLRADAPT_f.png"/>
+ </block>
+ <block name="DLSS">
+ <icon variable="SCI" path="palettes/DLSS.png"/>
+ </block>
+ <block name="DOLLAR_f">
+ <icon variable="SCI" path="palettes/DOLLAR_f.png"/>
+ </block>
+ <block name="DOLLAR">
+ <icon variable="SCI" path="palettes/DOLLAR.png"/>
+ </block>
+ <block name="DOLLAR_m">
+ <icon variable="SCI" path="palettes/DOLLAR_m.png"/>
+ </block>
+ <block name="SAMPHOLD_m">
+ <icon variable="SCI" path="palettes/SAMPHOLD_m.png"/>
+ </block>
+ <block name="TCLSS">
+ <icon variable="SCI" path="palettes/TCLSS.png"/>
+ </block>
+ <block name="REGISTER">
+ <icon variable="SCI" path="palettes/REGISTER.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Lookup Tables">
+ <block name="INTRP2BLK_f">
+ <icon variable="SCI" path="palettes/INTRP2BLK_f.png"/>
+ </block>
+ <block name="INTRPLBLK_f">
+ <icon variable="SCI" path="palettes/INTRPLBLK_f.png"/>
+ </block>
+ <block name="LOOKUP_f">
+ <icon variable="SCI" path="palettes/LOOKUP_f.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Event handling">
+ <block name="CLOCK_c">
+ <icon variable="SCI" path="palettes/CLOCK_c.png"/>
+ </block>
+ <block name="SampleCLK">
+ <icon variable="SCI" path="palettes/SampleCLK.png"/>
+ </block>
+ <block name="VirtualCLK0">
+ <icon variable="SCI" path="palettes/VirtualCLK0.png"/>
+ </block>
+ <block name="ANDBLK">
+ <icon variable="SCI" path="palettes/ANDBLK.png"/>
+ </block>
+ <block name="ANDLOG_f">
+ <icon variable="SCI" path="palettes/ANDLOG_f.png"/>
+ </block>
+ <block name="CEVENTSCOPE">
+ <icon variable="SCI" path="palettes/CEVENTSCOPE.png"/>
+ </block>
+ <block name="CLKFROM">
+ <icon variable="SCI" path="palettes/CLKFROM.png"/>
+ </block>
+ <block name="CLKGOTO">
+ <icon variable="SCI" path="palettes/CLKGOTO.png"/>
+ </block>
+ <block name="CLKGotoTagVisibility">
+ <icon variable="SCI" path="palettes/CLKGotoTagVisibility.png"/>
+ </block>
+ <block name="CLKOUTV_f">
+ <icon variable="SCI" path="palettes/CLKOUTV_f.png"/>
+ </block>
+ <block name="CLKSOMV_f">
+ <icon variable="SCI" path="palettes/CLKSOMV_f.png"/>
+ </block>
+ <block name="EDGE_TRIGGER">
+ <icon variable="SCI" path="palettes/EDGE_TRIGGER.png"/>
+ </block>
+ <block name="ENDBLK">
+ <icon variable="SCI" path="palettes/ENDBLK.png"/>
+ </block>
+ <block name="END_c">
+ <icon variable="SCI" path="palettes/END_c.png"/>
+ </block>
+ <block name="ESELECT_f">
+ <icon variable="SCI" path="palettes/ESELECT_f.png"/>
+ </block>
+ <block name="EVTDLY_c">
+ <icon variable="SCI" path="palettes/EVTDLY_c.png"/>
+ </block>
+ <block name="EVTGEN_f">
+ <icon variable="SCI" path="palettes/EVTGEN_f.png"/>
+ </block>
+ <block name="EVTVARDLY">
+ <icon variable="SCI" path="palettes/EVTVARDLY.png"/>
+ </block>
+ <block name="Extract_Activation">
+ <icon variable="SCI" path="palettes/Extract_Activation.png"/>
+ </block>
+ <block name="HALT_f">
+ <icon variable="SCI" path="palettes/HALT_f.png"/>
+ </block>
+ <block name="IFTHEL_f">
+ <icon variable="SCI" path="palettes/IFTHEL_f.png"/>
+ </block>
+ <block name="M_freq">
+ <icon variable="SCI" path="palettes/M_freq.png"/>
+ </block>
+ <block name="MCLOCK_f">
+ <icon variable="SCI" path="palettes/MCLOCK_f.png"/>
+ </block>
+ <block name="MFCLCK_f">
+ <icon variable="SCI" path="palettes/MFCLCK_f.png"/>
+ </block>
+ <block name="freq_div">
+ <icon variable="SCI" path="palettes/freq_div.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Mathematical Operations">
+ <block name="ABS_VALUE">
+ <icon variable="SCI" path="palettes/ABS_VALUE.png"/>
+ </block>
+ <block name="BIGSOM_f">
+ <icon variable="SCI" path="palettes/BIGSOM_f.png"/>
+ </block>
+ <block name="COSBLK_f">
+ <icon variable="SCI" path="palettes/COSBLK_f.png"/>
+ </block>
+ <block name="EXPBLK_m">
+ <icon variable="SCI" path="palettes/EXPBLK_m.png"/>
+ </block>
+ <block name="GAINBLK_f">
+ <icon variable="SCI" path="palettes/GAINBLK_f.png"/>
+ </block>
+ <block name="GAINBLK">
+ <icon variable="SCI" path="palettes/GAINBLK.png"/>
+ </block>
+ <block name="GAIN_f">
+ <icon variable="SCI" path="palettes/GAIN_f.png"/>
+ </block>
+ <block name="INVBLK">
+ <icon variable="SCI" path="palettes/INVBLK.png"/>
+ </block>
+ <block name="LOGBLK_f">
+ <icon variable="SCI" path="palettes/LOGBLK_f.png"/>
+ </block>
+ <block name="MATMAGPHI">
+ <icon variable="SCI" path="palettes/MATMAGPHI.png"/>
+ </block>
+ <block name="MATZREIM">
+ <icon variable="SCI" path="palettes/MATZREIM.png"/>
+ </block>
+ <block name="MAXMIN">
+ <icon variable="SCI" path="palettes/MAXMIN.png"/>
+ </block>
+ <block name="MAX_f">
+ <icon variable="SCI" path="palettes/MAX_f.png"/>
+ </block>
+ <block name="MIN_f">
+ <icon variable="SCI" path="palettes/MIN_f.png"/>
+ </block>
+ <block name="POWBLK_f">
+ <icon variable="SCI" path="palettes/POWBLK_f.png"/>
+ </block>
+ <block name="PRODUCT">
+ <icon variable="SCI" path="palettes/PRODUCT.png"/>
+ </block>
+ <block name="PROD_f">
+ <icon variable="SCI" path="palettes/PROD_f.png"/>
+ </block>
+ <block name="SIGNUM">
+ <icon variable="SCI" path="palettes/SIGNUM.png"/>
+ </block>
+ <block name="SINBLK_f">
+ <icon variable="SCI" path="palettes/SINBLK_f.png"/>
+ </block>
+ <block name="SQRT">
+ <icon variable="SCI" path="palettes/SQRT.png"/>
+ </block>
+ <block name="SUMMATION">
+ <icon variable="SCI" path="palettes/SUMMATION.png"/>
+ </block>
+ <block name="SUM_f">
+ <icon variable="SCI" path="palettes/SUM_f.png"/>
+ </block>
+ <block name="SOM_f">
+ <icon variable="SCI" path="palettes/SOM_f.png"/>
+ </block>
+ <block name="TANBLK_f">
+ <icon variable="SCI" path="palettes/TANBLK_f.png"/>
+ </block>
+ <block name="TrigFun">
+ <icon variable="SCI" path="palettes/TrigFun.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Matrix">
+ <block name="CUMSUM">
+ <icon variable="SCI" path="palettes/CUMSUM.png"/>
+ </block>
+ <block name="EXTRACT">
+ <icon variable="SCI" path="palettes/EXTRACT.png"/>
+ </block>
+ <block name="EXTTRI">
+ <icon variable="SCI" path="palettes/EXTTRI.png"/>
+ </block>
+ <block name="MATBKSL">
+ <icon variable="SCI" path="palettes/MATBKSL.png"/>
+ </block>
+ <block name="MATCATH">
+ <icon variable="SCI" path="palettes/MATCATH.png"/>
+ </block>
+ <block name="MATCATV">
+ <icon variable="SCI" path="palettes/MATCATV.png"/>
+ </block>
+ <block name="MATDET">
+ <icon variable="SCI" path="palettes/MATDET.png"/>
+ </block>
+ <block name="MATDIAG">
+ <icon variable="SCI" path="palettes/MATDIAG.png"/>
+ </block>
+ <block name="MATDIV">
+ <icon variable="SCI" path="palettes/MATDIV.png"/>
+ </block>
+ <block name="MATEIG">
+ <icon variable="SCI" path="palettes/MATEIG.png"/>
+ </block>
+ <block name="MATEXPM">
+ <icon variable="SCI" path="palettes/MATEXPM.png"/>
+ </block>
+ <block name="MATINV">
+ <icon variable="SCI" path="palettes/MATINV.png"/>
+ </block>
+ <block name="MATLU">
+ <icon variable="SCI" path="palettes/MATLU.png"/>
+ </block>
+ <block name="MATMAGPHI">
+ <icon variable="SCI" path="palettes/MATMAGPHI.png"/>
+ </block>
+ <block name="MATMUL">
+ <icon variable="SCI" path="palettes/MATMUL.png"/>
+ </block>
+ <block name="MATPINV">
+ <icon variable="SCI" path="palettes/MATPINV.png"/>
+ </block>
+ <block name="MATRESH">
+ <icon variable="SCI" path="palettes/MATRESH.png"/>
+ </block>
+ <block name="MATSING">
+ <icon variable="SCI" path="palettes/MATSING.png"/>
+ </block>
+ <block name="MATSUM">
+ <icon variable="SCI" path="palettes/MATSUM.png"/>
+ </block>
+ <block name="MATTRAN">
+ <icon variable="SCI" path="palettes/MATTRAN.png"/>
+ </block>
+ <block name="MATZCONJ">
+ <icon variable="SCI" path="palettes/MATZCONJ.png"/>
+ </block>
+ <block name="MATZREIM">
+ <icon variable="SCI" path="palettes/MATZREIM.png"/>
+ </block>
+ <block name="RICC">
+ <icon variable="SCI" path="palettes/RICC.png"/>
+ </block>
+ <block name="ROOTCOEF">
+ <icon variable="SCI" path="palettes/ROOTCOEF.png"/>
+ </block>
+ <block name="SQRT">
+ <icon variable="SCI" path="palettes/SQRT.png"/>
+ </block>
+ <block name="SUBMAT">
+ <icon variable="SCI" path="palettes/SUBMAT.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Electrical">
+ <block name="CCS">
+ <icon variable="SCI" path="palettes/CCS.png"/>
+ </block>
+ <block name="CVS">
+ <icon variable="SCI" path="palettes/CVS.png"/>
+ </block>
+ <block name="Capacitor">
+ <icon variable="SCI" path="palettes/Capacitor.png"/>
+ </block>
+ <block name="ConstantVoltage">
+ <icon variable="SCI" path="palettes/ConstantVoltage.png"/>
+ </block>
+ <block name="CurrentSensor">
+ <icon variable="SCI" path="palettes/CurrentSensor.png"/>
+ </block>
+ <block name="Diode">
+ <icon variable="SCI" path="palettes/Diode.png"/>
+ </block>
+ <block name="Ground">
+ <icon variable="SCI" path="palettes/Ground.png"/>
+ </block>
+ <block name="Gyrator">
+ <icon variable="SCI" path="palettes/Gyrator.png"/>
+ </block>
+ <block name="IdealTransformer">
+ <icon variable="SCI" path="palettes/IdealTransformer.png"/>
+ </block>
+ <block name="Inductor">
+ <icon variable="SCI" path="palettes/Inductor.png"/>
+ </block>
+ <block name="NMOS">
+ <icon variable="SCI" path="palettes/NMOS.png"/>
+ </block>
+ <block name="NPN">
+ <icon variable="SCI" path="palettes/NPN.png"/>
+ </block>
+ <block name="OpAmp">
+ <icon variable="SCI" path="palettes/OpAmp.png"/>
+ </block>
+ <block name="PMOS">
+ <icon variable="SCI" path="palettes/PMOS.png"/>
+ </block>
+ <block name="PNP">
+ <icon variable="SCI" path="palettes/PNP.png"/>
+ </block>
+ <block name="PotentialSensor">
+ <icon variable="SCI" path="palettes/PotentialSensor.png"/>
+ </block>
+ <block name="Resistor">
+ <icon variable="SCI" path="palettes/Resistor.png"/>
+ </block>
+ <block name="SineVoltage">
+ <icon variable="SCI" path="palettes/SineVoltage.png"/>
+ </block>
+ <block name="Switch">
+ <icon variable="SCI" path="palettes/Switch.png"/>
+ </block>
+ <block name="VVsourceAC">
+ <icon variable="SCI" path="palettes/VVsourceAC.png"/>
+ </block>
+ <block name="VariableResistor">
+ <icon variable="SCI" path="palettes/VariableResistor.png"/>
+ </block>
+ <block name="VoltageSensor">
+ <icon variable="SCI" path="palettes/VoltageSensor.png"/>
+ </block>
+ <block name="VsourceAC">
+ <icon variable="SCI" path="palettes/VsourceAC.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Integer">
+ <block name="BITCLEAR">
+ <icon variable="SCI" path="palettes/BITCLEAR.png"/>
+ </block>
+ <block name="BITSET">
+ <icon variable="SCI" path="palettes/BITSET.png"/>
+ </block>
+ <block name="CONVERT">
+ <icon variable="SCI" path="palettes/CONVERT.png"/>
+ </block>
+ <block name="DFLIPFLOP">
+ <icon variable="SCI" path="palettes/DFLIPFLOP.png"/>
+ </block>
+ <block name="DLATCH">
+ <icon variable="SCI" path="palettes/DLATCH.png"/>
+ </block>
+ <block name="EXTRACTBITS">
+ <icon variable="SCI" path="palettes/EXTRACTBITS.png"/>
+ </block>
+ <block name="INTMUL">
+ <icon variable="SCI" path="palettes/INTMUL.png"/>
+ </block>
+ <block name="JKFLIPFLOP">
+ <icon variable="SCI" path="palettes/JKFLIPFLOP.png"/>
+ </block>
+ <block name="LOGIC">
+ <icon variable="SCI" path="palettes/LOGIC.png"/>
+ </block>
+ <block name="SHIFT">
+ <icon variable="SCI" path="palettes/SHIFT.png"/>
+ </block>
+ <block name="SRFLIPFLOP">
+ <icon variable="SCI" path="palettes/SRFLIPFLOP.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Port &amp; Subsystem">
+ <block name="CLKINV_f">
+ <icon variable="SCI" path="palettes/CLKINV_f.png"/>
+ </block>
+ <block name="CLKOUTV_f">
+ <icon variable="SCI" path="palettes/CLKOUTV_f.png"/>
+ </block>
+ <block name="IN_f">
+ <icon variable="SCI" path="palettes/IN_f.png"/>
+ </block>
+ <block name="INIMPL_f">
+ <icon variable="SCI" path="palettes/INIMPL_f.png"/>
+ </block>
+ <block name="OUTIMPL_f">
+ <icon variable="SCI" path="palettes/OUTIMPL_f.png"/>
+ </block>
+ <block name="OUT_f">
+ <icon variable="SCI" path="palettes/OUT_f.png"/>
+ </block>
+ <block name="SUPER_f">
+ <icon variable="SCI" path="palettes/SUPER_f.png"/>
+
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Zero crossing detection">
+ <block name="GENERAL_f">
+ <icon variable="SCI" path="palettes/GENERAL_f.png"/>
+ </block>
+ <block name="NEGTOPOS_f">
+ <icon variable="SCI" path="palettes/NEGTOPOS_f.png"/>
+ </block>
+ <block name="POSTONEG_f">
+ <icon variable="SCI" path="palettes/POSTONEG_f.png"/>
+ </block>
+ <block name="ZCROSS_f">
+ <icon variable="SCI" path="palettes/ZCROSS_f.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Signal Routing">
+ <block name="DEMUX">
+ <icon variable="SCI" path="palettes/DEMUX.png"/>
+ </block>
+ <block name="DEMUX_f">
+ <icon variable="SCI" path="palettes/DEMUX_f.png"/>
+ </block>
+ <block name="EXTRACTOR">
+ <icon variable="SCI" path="palettes/EXTRACTOR.png"/>
+ </block>
+ <block name="SCALAR2VECTOR">
+ <icon variable="SCI" path="palettes/SCALAR2VECTOR.png"/>
+ </block>
+ <block name="FROM">
+ <icon variable="SCI" path="palettes/FROM.png"/>
+ </block>
+ <block name="FROMMO">
+ <icon variable="SCI" path="palettes/FROMMO.png"/>
+ </block>
+ <block name="GOTO">
+ <icon variable="SCI" path="palettes/GOTO.png"/>
+ </block>
+ <block name="GOTOMO">
+ <icon variable="SCI" path="palettes/GOTOMO.png"/>
+ </block>
+ <block name="CLKFROM">
+ <icon variable="SCI" path="palettes/CLKFROM.png"/>
+ </block>
+ <block name="CLKGOTO">
+ <icon variable="SCI" path="palettes/CLKGOTO.png"/>
+ </block>
+ <block name="GotoTagVisibility">
+ <icon variable="SCI" path="palettes/GotoTagVisibility.png"/>
+ </block>
+ <block name="GotoTagVisibilityMO">
+ <icon variable="SCI" path="palettes/GotoTagVisibilityMO.png"/>
+ </block>
+ <block name="ISELECT_m">
+ <icon variable="SCI" path="palettes/ISELECT_m.png"/>
+ </block>
+ <block name="MUX">
+ <icon variable="SCI" path="palettes/MUX.png"/>
+ </block>
+ <block name="MUX_f">
+ <icon variable="SCI" path="palettes/MUX_f.png"/>
+ </block>
+ <block name="M_SWITCH">
+ <icon variable="SCI" path="palettes/M_SWITCH.png"/>
+ </block>
+ <block name="NRMSOM_f">
+ <icon variable="SCI" path="palettes/NRMSOM_f.png"/>
+ </block>
+ <block name="RELAY_f">
+ <icon variable="SCI" path="palettes/RELAY_f.png"/>
+ </block>
+ <block name="SELECT_m">
+ <icon variable="SCI" path="palettes/SELECT_m.png"/>
+ </block>
+ <block name="SWITCH2_m">
+ <icon variable="SCI" path="palettes/SWITCH2_m.png"/>
+ </block>
+ <block name="SWITCH_f">
+ <icon variable="SCI" path="palettes/SWITCH_f.png"/>
+ </block>
+ <block name="SELF_SWITCH">
+ <icon variable="SCI" path="palettes/SELF_SWITCH.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Signal Processing">
+ <block name="QUANT_f">
+ <icon variable="SCI" path="palettes/QUANT_f.png"/>
+ </block>
+ <block name="SAMPHOLD_m">
+ <icon variable="SCI" path="palettes/SAMPHOLD_m.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Implicit">
+ <block name="CONSTRAINT_c">
+ <icon variable="SCI" path="palettes/CONSTRAINT_c.png"/>
+ </block>
+ <block name="CONSTRAINT2_c">
+ <icon variable="SCI" path="palettes/CONSTRAINT2_c.png"/>
+ </block>
+ <block name="DIFF_f">
+ <icon variable="SCI" path="palettes/DIFF_f.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Annotations">
+ <block name="TEXT_f">
+ <icon variable="SCI" path="palettes/TEXT_f.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Sinks">
+ <block name="AFFICH_m">
+ <icon variable="SCI" path="palettes/AFFICH_m.png"/>
+ </block>
+ <block name="BARXY">
+ <icon variable="SCI" path="palettes/BARXY.png"/>
+ </block>
+ <block name="CANIMXY">
+ <icon variable="SCI" path="palettes/CANIMXY.png"/>
+ </block>
+ <block name="CANIMXY3D">
+ <icon variable="SCI" path="palettes/CANIMXY3D.png"/>
+ </block>
+ <block name="CFSCOPE">
+ <icon variable="SCI" path="palettes/CFSCOPE.png"/>
+ </block>
+ <block name="CLKOUTV_f">
+ <icon variable="SCI" path="palettes/CLKOUTV_f.png"/>
+ </block>
+ <block name="CMAT3D">
+ <icon variable="SCI" path="palettes/CMAT3D.png"/>
+ </block>
+ <block name="CMATVIEW">
+ <icon variable="SCI" path="palettes/CMATVIEW.png"/>
+ </block>
+ <block name="CMSCOPE">
+ <icon variable="SCI" path="palettes/CMSCOPE.png"/>
+ </block>
+ <block name="CSCOPE">
+ <icon variable="SCI" path="palettes/CSCOPE.png"/>
+ </block>
+ <block name="CSCOPXY">
+ <icon variable="SCI" path="palettes/CSCOPXY.png"/>
+ </block>
+ <block name="CSCOPXY3D">
+ <icon variable="SCI" path="palettes/CSCOPXY3D.png"/>
+ </block>
+ <block name="ENDBLK">
+ <icon variable="SCI" path="palettes/ENDBLK.png"/>
+ </block>
+ <block name="END_c">
+ <icon variable="SCI" path="palettes/END_c.png"/>
+ </block>
+ <block name="HALT_f">
+ <icon variable="SCI" path="palettes/HALT_f.png"/>
+ </block>
+ <block name="OUTIMPL_f">
+ <icon variable="SCI" path="palettes/OUTIMPL_f.png"/>
+ </block>
+ <block name="OUT_f">
+ <icon variable="SCI" path="palettes/OUT_f.png"/>
+ </block>
+ <block name="TOWS_c">
+ <icon variable="SCI" path="palettes/TOWS_c.png"/>
+ </block>
+ <block name="TRASH_f">
+ <icon variable="SCI" path="palettes/TRASH_f.png"/>
+ </block>
+ <block name="WRITEAU_f">
+ <icon variable="SCI" path="palettes/WRITEAU_f.png"/>
+ </block>
+ <block name="WRITEC_f">
+ <icon variable="SCI" path="palettes/WRITEC_f.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Sources">
+ <block name="CLKINV_f">
+ <icon variable="SCI" path="palettes/CLKINV_f.png"/>
+ </block>
+ <block name="CLOCK_c">
+ <icon variable="SCI" path="palettes/CLOCK_c.png"/>
+ </block>
+ <block name="CONST_m">
+ <icon variable="SCI" path="palettes/CONST_m.png"/>
+ </block>
+ <block name="CONST">
+ <icon variable="SCI" path="palettes/CONST.png"/>
+ </block>
+ <block name="CONST_f">
+ <icon variable="SCI" path="palettes/CONST_f.png"/>
+ </block>
+ <block name="CURV_f">
+ <icon variable="SCI" path="palettes/CURV_f.png"/>
+ </block>
+ <block name="Counter">
+ <icon variable="SCI" path="palettes/Counter.png"/>
+ </block>
+ <block name="FROMWSB">
+ <icon variable="SCI" path="palettes/FROMWSB.png"/>
+ </block>
+ <block name="GENSIN_f">
+ <icon variable="SCI" path="palettes/GENSIN_f.png"/>
+ </block>
+ <block name="GENSQR_f">
+ <icon variable="SCI" path="palettes/GENSQR_f.png"/>
+ </block>
+ <block name="INIMPL_f">
+ <icon variable="SCI" path="palettes/INIMPL_f.png"/>
+ </block>
+ <block name="IN_f">
+ <icon variable="SCI" path="palettes/IN_f.png"/>
+ </block>
+ <block name="Modulo_Count">
+ <icon variable="SCI" path="palettes/Modulo_Count.png"/>
+ </block>
+ <block name="RAMP">
+ <icon variable="SCI" path="palettes/RAMP.png"/>
+ </block>
+ <block name="RAND_m">
+ <icon variable="SCI" path="palettes/RAND_m.png"/>
+ </block>
+ <block name="READAU_f">
+ <icon variable="SCI" path="palettes/READAU_f.png"/>
+ </block>
+ <block name="READC_f">
+ <icon variable="SCI" path="palettes/READC_f.png"/>
+ </block>
+ <block name="RFILE_f">
+ <icon variable="SCI" path="palettes/RFILE_f.png"/>
+ </block>
+ <block name="SAWTOOTH_f">
+ <icon variable="SCI" path="palettes/SAWTOOTH_f.png"/>
+ </block>
+ <block name="STEP_FUNCTION">
+ <icon variable="SCI" path="palettes/STEP_FUNCTION.png"/>
+ </block>
+ <block name="PULSE_SC">
+ <icon variable="SCI" path="palettes/PULSE_SC.png"/>
+ </block>
+ <block name="SampleCLK">
+ <icon variable="SCI" path="palettes/SampleCLK.png"/>
+ </block>
+ <block name="Sigbuilder">
+ <icon variable="SCI" path="palettes/Sigbuilder.png"/>
+ </block>
+ <block name="TIME_f">
+ <icon variable="SCI" path="palettes/TIME_f.png"/>
+ </block>
+ <block name="TKSCALE">
+ <icon variable="SCI" path="palettes/TKSCALE.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Thermo-Hydraulics">
+ <block name="Bache">
+ <icon variable="SCI" path="palettes/Bache.png"/>
+ </block>
+ <block name="PerteDP">
+ <icon variable="SCI" path="palettes/PerteDP.png"/>
+ </block>
+ <block name="PuitsP">
+ <icon variable="SCI" path="palettes/PuitsP.png"/>
+ </block>
+ <block name="SourceP">
+ <icon variable="SCI" path="palettes/SourceP.png"/>
+ </block>
+ <block name="VanneReglante">
+ <icon variable="SCI" path="palettes/VanneReglante.png"/>
+ </block>
+ <block name="Flowmeter">
+ <icon variable="SCI" path="palettes/Flowmeter.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="Demonstrations Blocks">
+ <block name="BOUNCE">
+ <icon variable="SCI" path="palettes/BOUNCE.png"/>
+ </block>
+ <block name="BOUNCEXY">
+ <icon variable="SCI" path="palettes/BOUNCEXY.png"/>
+ </block>
+ <block name="BPLATFORM">
+ <icon variable="SCI" path="palettes/BPLATFORM.png"/>
+ </block>
+ </node>
+ <node xsi:type="PreLoaded" enable="true" name="User-Defined Functions">
+ <block name="CBLOCK">
+ <icon variable="SCI" path="palettes/CBLOCK.png"/>
+ </block>
+ <block name="CBLOCK4">
+ <icon variable="SCI" path="palettes/CBLOCK4.png"/>
+ </block>
+ <block name="DEBUG">
+ <icon variable="SCI" path="palettes/DEBUG.png"/>
+ </block>
+ <block name="EXPRESSION">
+ <icon variable="SCI" path="palettes/EXPRESSION.png"/>
+ </block>
+ <block name="MBLOCK">
+ <icon variable="SCI" path="palettes/MBLOCK.png"/>
+ </block>
+ <block name="PDE">
+ <icon variable="SCI" path="palettes/PDE.png"/>
+ </block>
+ <block name="SUPER_f">
+ <icon variable="SCI" path="palettes/SUPER_f.png"/>
+ </block>
+ <block name="c_block">
+ <icon variable="SCI" path="palettes/c_block.png"/>
+ </block>
+ <block name="fortran_block">
+ <icon variable="SCI" path="palettes/fortran_block.png"/>
+ </block>
+ <block name="generic_block3">
+ <icon variable="SCI" path="palettes/generic_block3.png"/>
+ </block>
+ <block name="scifunc_block_m">
+ <icon variable="SCI" path="palettes/scifunc_block_m.png"/>
+ </block>
+ </node>
+ </node>
+</category>
diff --git a/palettes/scifunc_block_m.png b/palettes/scifunc_block_m.png
new file mode 100644
index 0000000..bd180b3
--- /dev/null
+++ b/palettes/scifunc_block_m.png
Binary files differ
diff --git a/src/css/common.css b/src/css/common.css
new file mode 100644
index 0000000..5eb0b45
--- /dev/null
+++ b/src/css/common.css
@@ -0,0 +1,152 @@
+div.mxRubberband {
+ position: absolute;
+ overflow: hidden;
+ border-style: solid;
+ border-width: 1px;
+ border-color: #0000FF;
+ background: #0077FF;
+}
+textarea.mxCellEditor {
+ background: url('../images/transparent.gif');
+ border-style: solid;
+ border-color: black;
+ border-width: 0;
+ overflow: auto;
+}
+div.mxWindow {
+ -webkit-box-shadow: 3px 3px 12px #C0C0C0;
+ -moz-box-shadow: 3px 3px 12px #C0C0C0;
+ box-shadow: 3px 3px 12px #C0C0C0;
+ background: url('../images/window.gif');
+ border-style: outset;
+ border-width: 1px;
+ position: absolute;
+ overflow: hidden;
+ z-index: 1;
+}
+table.mxWindow {
+ border-collapse: collapse;
+ table-layout: fixed;
+ font-family: Arial;
+ font-size: 8pt;
+}
+td.mxWindowTitle {
+ background: url('../images/window-title.gif') repeat-x;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ text-align: center;
+ font-weight: bold;
+ overflow: hidden;
+ height: 13px;
+ padding: 2px;
+ padding-top: 4px;
+ padding-bottom: 6px;
+ color: black;
+}
+td.mxWindowPane {
+ vertical-align: top;
+ padding: 0px;
+}
+div.mxWindowPane {
+ overflow: hidden;
+}
+td.mxWindowPane td {
+ font-family: Arial;
+ font-size: 8pt;
+}
+td.mxWindowPane input, td.mxWindowPane select, td.mxWindowPane textarea, td.mxWindowPane radio {
+ border-color: #8C8C8C;
+ border-style: solid;
+ border-width: 1px;
+ font-family: Arial;
+ font-size: 8pt;
+ padding: 1px;
+}
+td.mxWindowPane button {
+ background: url('../images/button.gif') repeat-x;
+ font-family: Arial;
+ font-size: 8pt;
+ padding: 2px;
+ float: left;
+}
+img.mxToolbarItem {
+ margin-right: 6px;
+ margin-bottom: 6px;
+ border-width: 1px;
+}
+select.mxToolbarCombo {
+ vertical-align: top;
+ border-style: inset;
+ border-width: 2px;
+}
+div.mxToolbarComboContainer {
+ padding: 2px;
+}
+img.mxToolbarMode {
+ margin: 2px;
+ margin-right: 4px;
+ margin-bottom: 4px;
+ border-width: 0px;
+}
+img.mxToolbarModeSelected {
+ margin: 0px;
+ margin-right: 2px;
+ margin-bottom: 2px;
+ border-width: 2px;
+ border-style: inset;
+}
+div.mxTooltip {
+ -webkit-box-shadow: 3px 3px 12px #C0C0C0;
+ -moz-box-shadow: 3px 3px 12px #C0C0C0;
+ box-shadow: 3px 3px 12px #C0C0C0;
+ background: #FFFFCC;
+ border-style: solid;
+ border-width: 1px;
+ border-color: black;
+ font-family: Arial;
+ font-size: 8pt;
+ position: absolute;
+ cursor: default;
+ padding: 4px;
+ color: black;
+}
+div.mxPopupMenu {
+ -webkit-box-shadow: 3px 3px 12px #C0C0C0;
+ -moz-box-shadow: 3px 3px 12px #C0C0C0;
+ box-shadow: 3px 3px 12px #C0C0C0;
+ background: url('../images/window.gif');
+ position: absolute;
+ border-style: solid;
+ border-width: 1px;
+ border-color: black;
+ cursor: default;
+}
+table.mxPopupMenu {
+ border-collapse: collapse;
+ margin-top: 1px;
+ margin-bottom: 1px;
+}
+tr.mxPopupMenuItem {
+ color: black;
+ cursor: default;
+}
+td.mxPopupMenuItem.disabled {
+ opacity: 0.2;
+}
+td.mxPopupMenuItem.disabled {
+ _filter:alpha(opacity=20) !important;
+}
+tr.mxPopupMenuItemHover {
+ background-color: #000066;
+ color: #FFFFFF;
+}
+td.mxPopupMenuItem {
+ padding: 2px 30px 2px 10px;
+ white-space: nowrap;
+ font-family: Arial;
+ font-size: 8pt;
+}
+td.mxPopupMenuIcon {
+ background-color: #D0D0D0;
+ padding: 2px 4px 2px 4px;
+}
diff --git a/src/css/explorer.css b/src/css/explorer.css
new file mode 100644
index 0000000..dfbbd21
--- /dev/null
+++ b/src/css/explorer.css
@@ -0,0 +1,15 @@
+div.mxTooltip {
+ filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=4, OffY=4,
+ Color='#A2A2A2', Positive='true')
+}
+div.mxPopupMenu {
+ filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=4, OffY=4,
+ Color='#C0C0C0', Positive='true')
+}
+div.mxWindow {
+ filter:progid:DXImageTransform.Microsoft.DropShadow(OffX=4, OffY=4,
+ Color='#C0C0C0', Positive='true')
+}
+td.mxWindowTitle {
+ _height: 23px;
+}
diff --git a/src/images/button.gif b/src/images/button.gif
new file mode 100644
index 0000000..ad55cab
--- /dev/null
+++ b/src/images/button.gif
Binary files differ
diff --git a/src/images/close.gif b/src/images/close.gif
new file mode 100644
index 0000000..1069e94
--- /dev/null
+++ b/src/images/close.gif
Binary files differ
diff --git a/src/images/collapsed.gif b/src/images/collapsed.gif
new file mode 100644
index 0000000..0276444
--- /dev/null
+++ b/src/images/collapsed.gif
Binary files differ
diff --git a/src/images/error.gif b/src/images/error.gif
new file mode 100644
index 0000000..14e1aee
--- /dev/null
+++ b/src/images/error.gif
Binary files differ
diff --git a/src/images/expanded.gif b/src/images/expanded.gif
new file mode 100644
index 0000000..3767b0b
--- /dev/null
+++ b/src/images/expanded.gif
Binary files differ
diff --git a/src/images/maximize.gif b/src/images/maximize.gif
new file mode 100644
index 0000000..e27cf3e
--- /dev/null
+++ b/src/images/maximize.gif
Binary files differ
diff --git a/src/images/minimize.gif b/src/images/minimize.gif
new file mode 100644
index 0000000..1e95e7c
--- /dev/null
+++ b/src/images/minimize.gif
Binary files differ
diff --git a/src/images/normalize.gif b/src/images/normalize.gif
new file mode 100644
index 0000000..34a8d30
--- /dev/null
+++ b/src/images/normalize.gif
Binary files differ
diff --git a/src/images/point.gif b/src/images/point.gif
new file mode 100644
index 0000000..9074c39
--- /dev/null
+++ b/src/images/point.gif
Binary files differ
diff --git a/src/images/resize.gif b/src/images/resize.gif
new file mode 100644
index 0000000..ff558db
--- /dev/null
+++ b/src/images/resize.gif
Binary files differ
diff --git a/src/images/separator.gif b/src/images/separator.gif
new file mode 100644
index 0000000..5c1b895
--- /dev/null
+++ b/src/images/separator.gif
Binary files differ
diff --git a/src/images/submenu.gif b/src/images/submenu.gif
new file mode 100644
index 0000000..ffe7617
--- /dev/null
+++ b/src/images/submenu.gif
Binary files differ
diff --git a/src/images/transparent.gif b/src/images/transparent.gif
new file mode 100644
index 0000000..76040f2
--- /dev/null
+++ b/src/images/transparent.gif
Binary files differ
diff --git a/src/images/warning.gif b/src/images/warning.gif
new file mode 100644
index 0000000..705235f
--- /dev/null
+++ b/src/images/warning.gif
Binary files differ
diff --git a/src/images/warning.png b/src/images/warning.png
new file mode 100644
index 0000000..2f78789
--- /dev/null
+++ b/src/images/warning.png
Binary files differ
diff --git a/src/images/window-title.gif b/src/images/window-title.gif
new file mode 100644
index 0000000..231def8
--- /dev/null
+++ b/src/images/window-title.gif
Binary files differ
diff --git a/src/images/window.gif b/src/images/window.gif
new file mode 100644
index 0000000..6631c4f
--- /dev/null
+++ b/src/images/window.gif
Binary files differ
diff --git a/src/js/editor/mxDefaultKeyHandler.js b/src/js/editor/mxDefaultKeyHandler.js
new file mode 100644
index 0000000..3814e5e
--- /dev/null
+++ b/src/js/editor/mxDefaultKeyHandler.js
@@ -0,0 +1,126 @@
+/**
+ * $Id: mxDefaultKeyHandler.js,v 1.26 2010-01-02 09:45:15 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxDefaultKeyHandler
+ *
+ * Binds keycodes to actionnames in an editor. This aggregates an internal
+ * <handler> and extends the implementation of <mxKeyHandler.escape> to not
+ * only cancel the editing, but also hide the properties dialog and fire an
+ * <mxEditor.escape> event via <editor>. An instance of this class is created
+ * by <mxEditor> and stored in <mxEditor.keyHandler>.
+ *
+ * Example:
+ *
+ * Bind the delete key to the delete action in an existing editor.
+ *
+ * (code)
+ * var keyHandler = new mxDefaultKeyHandler(editor);
+ * keyHandler.bindAction(46, 'delete');
+ * (end)
+ *
+ * Codec:
+ *
+ * This class uses the <mxDefaultKeyHandlerCodec> to read configuration
+ * data into an existing instance. See <mxDefaultKeyHandlerCodec> for a
+ * description of the configuration format.
+ *
+ * Keycodes:
+ *
+ * See <mxKeyHandler>.
+ *
+ * An <mxEvent.ESCAPE> event is fired via the editor if the escape key is
+ * pressed.
+ *
+ * Constructor: mxDefaultKeyHandler
+ *
+ * Constructs a new default key handler for the <mxEditor.graph> in the
+ * given <mxEditor>. (The editor may be null if a prototypical instance for
+ * a <mxDefaultKeyHandlerCodec> is created.)
+ *
+ * Parameters:
+ *
+ * editor - Reference to the enclosing <mxEditor>.
+ */
+function mxDefaultKeyHandler(editor)
+{
+ if (editor != null)
+ {
+ this.editor = editor;
+ this.handler = new mxKeyHandler(editor.graph);
+
+ // Extends the escape function of the internal key
+ // handle to hide the properties dialog and fire
+ // the escape event via the editor instance
+ var old = this.handler.escape;
+
+ this.handler.escape = function(evt)
+ {
+ old.apply(this, arguments);
+ editor.hideProperties();
+ editor.fireEvent(new mxEventObject(mxEvent.ESCAPE, 'event', evt));
+ };
+ }
+};
+
+/**
+ * Variable: editor
+ *
+ * Reference to the enclosing <mxEditor>.
+ */
+mxDefaultKeyHandler.prototype.editor = null;
+
+/**
+ * Variable: handler
+ *
+ * Holds the <mxKeyHandler> for key event handling.
+ */
+mxDefaultKeyHandler.prototype.handler = null;
+
+/**
+ * Function: bindAction
+ *
+ * Binds the specified keycode to the given action in <editor>. The
+ * optional control flag specifies if the control key must be pressed
+ * to trigger the action.
+ *
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * action - Name of the action to execute in <editor>.
+ * control - Optional boolean that specifies if control must be pressed.
+ * Default is false.
+ */
+mxDefaultKeyHandler.prototype.bindAction = function (code, action, control)
+{
+ var keyHandler = mxUtils.bind(this, function()
+ {
+ this.editor.execute(action);
+ });
+
+ // Binds the function to control-down keycode
+ if (control)
+ {
+ this.handler.bindControlKey(code, keyHandler);
+ }
+
+ // Binds the function to the normal keycode
+ else
+ {
+ this.handler.bindKey(code, keyHandler);
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the <handler> associated with this object. This does normally
+ * not need to be called, the <handler> is destroyed automatically when the
+ * window unloads (in IE) by <mxEditor>.
+ */
+mxDefaultKeyHandler.prototype.destroy = function ()
+{
+ this.handler.destroy();
+ this.handler = null;
+};
diff --git a/src/js/editor/mxDefaultPopupMenu.js b/src/js/editor/mxDefaultPopupMenu.js
new file mode 100644
index 0000000..01c65b5
--- /dev/null
+++ b/src/js/editor/mxDefaultPopupMenu.js
@@ -0,0 +1,300 @@
+/**
+ * $Id: mxDefaultPopupMenu.js,v 1.29 2012-07-03 06:30:25 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxDefaultPopupMenu
+ *
+ * Creates popupmenus for mouse events. This object holds an XML node
+ * which is a description of the popup menu to be created. In
+ * <createMenu>, the configuration is applied to the context and
+ * the resulting menu items are added to the menu dynamically. See
+ * <createMenu> for a description of the configuration format.
+ *
+ * This class does not create the DOM nodes required for the popup menu, it
+ * only parses an XML description to invoke the respective methods on an
+ * <mxPopupMenu> each time the menu is displayed.
+ *
+ * Codec:
+ *
+ * This class uses the <mxDefaultPopupMenuCodec> to read configuration
+ * data into an existing instance, however, the actual parsing is done
+ * by this class during program execution, so the format is described
+ * below.
+ *
+ * Constructor: mxDefaultPopupMenu
+ *
+ * Constructs a new popupmenu-factory based on given configuration.
+ *
+ * Paramaters:
+ *
+ * config - XML node that contains the configuration data.
+ */
+function mxDefaultPopupMenu(config)
+{
+ this.config = config;
+};
+
+/**
+ * Variable: imageBasePath
+ *
+ * Base path for all icon attributes in the config. Default is null.
+ */
+mxDefaultPopupMenu.prototype.imageBasePath = null;
+
+/**
+ * Variable: config
+ *
+ * XML node used as the description of new menu items. This node is
+ * used in <createMenu> to dynamically create the menu items if their
+ * respective conditions evaluate to true for the given arguments.
+ */
+mxDefaultPopupMenu.prototype.config = null;
+
+/**
+ * Function: createMenu
+ *
+ * This function is called from <mxEditor> to add items to the
+ * given menu based on <config>. The config is a sequence of
+ * the following nodes and attributes.
+ *
+ * Child Nodes:
+ *
+ * add - Adds a new menu item. See below for attributes.
+ * separator - Adds a separator. No attributes.
+ * condition - Adds a custom condition. Name attribute.
+ *
+ * The add-node may have a child node that defines a function to be invoked
+ * before the action is executed (or instead of an action to be executed).
+ *
+ * Attributes:
+ *
+ * as - Resource key for the label (needs entry in property file).
+ * action - Name of the action to execute in enclosing editor.
+ * icon - Optional icon (relative/absolute URL).
+ * iconCls - Optional CSS class for the icon.
+ * if - Optional name of condition that must be true(see below).
+ * name - Name of custom condition. Only for condition nodes.
+ *
+ * Conditions:
+ *
+ * nocell - No cell under the mouse.
+ * ncells - More than one cell selected.
+ * notRoot - Drilling position is other than home.
+ * cell - Cell under the mouse.
+ * notEmpty - Exactly one cell with children under mouse.
+ * expandable - Exactly one expandable cell under mouse.
+ * collapsable - Exactly one collapsable cell under mouse.
+ * validRoot - Exactly one cell which is a possible root under mouse.
+ * swimlane - Exactly one cell which is a swimlane under mouse.
+ *
+ * Example:
+ *
+ * To add a new item for a given action to the popupmenu:
+ *
+ * (code)
+ * <mxDefaultPopupMenu as="popupHandler">
+ * <add as="delete" action="delete" icon="images/delete.gif" if="cell"/>
+ * </mxDefaultPopupMenu>
+ * (end)
+ *
+ * To add a new item for a custom function:
+ *
+ * (code)
+ * <mxDefaultPopupMenu as="popupHandler">
+ * <add as="action1"><![CDATA[
+ * function (editor, cell, evt)
+ * {
+ * editor.execute('action1', cell, 'myArg');
+ * }
+ * ]]></add>
+ * </mxDefaultPopupMenu>
+ * (end)
+ *
+ * The above example invokes action1 with an additional third argument via
+ * the editor instance. The third argument is passed to the function that
+ * defines action1. If the add-node has no action-attribute, then only the
+ * function defined in the text content is executed, otherwise first the
+ * function and then the action defined in the action-attribute is
+ * executed. The function in the text content has 3 arguments, namely the
+ * <mxEditor> instance, the <mxCell> instance under the mouse, and the
+ * native mouse event.
+ *
+ * Custom Conditions:
+ *
+ * To add a new condition for popupmenu items:
+ *
+ * (code)
+ * <condition name="condition1"><![CDATA[
+ * function (editor, cell, evt)
+ * {
+ * return cell != null;
+ * }
+ * ]]></condition>
+ * (end)
+ *
+ * The new condition can then be used in any item as follows:
+ *
+ * (code)
+ * <add as="action1" action="action1" icon="action1.gif" if="condition1"/>
+ * (end)
+ *
+ * The order in which the items and conditions appear is not significant as
+ * all connditions are evaluated before any items are created.
+ *
+ * Parameters:
+ *
+ * editor - Enclosing <mxEditor> instance.
+ * menu - <mxPopupMenu> that is used for adding items and separators.
+ * cell - Optional <mxCell> which is under the mousepointer.
+ * evt - Optional mouse event which triggered the menu.
+ */
+mxDefaultPopupMenu.prototype.createMenu = function(editor, menu, cell, evt)
+{
+ if (this.config != null)
+ {
+ var conditions = this.createConditions(editor, cell, evt);
+ var item = this.config.firstChild;
+
+ this.addItems(editor, menu, cell, evt, conditions, item, null);
+ }
+};
+
+/**
+ * Function: addItems
+ *
+ * Recursively adds the given items and all of its children into the given menu.
+ *
+ * Parameters:
+ *
+ * editor - Enclosing <mxEditor> instance.
+ * menu - <mxPopupMenu> that is used for adding items and separators.
+ * cell - Optional <mxCell> which is under the mousepointer.
+ * evt - Optional mouse event which triggered the menu.
+ * conditions - Array of names boolean conditions.
+ * item - XML node that represents the current menu item.
+ * parent - DOM node that represents the parent menu item.
+ */
+mxDefaultPopupMenu.prototype.addItems = function(editor, menu, cell, evt, conditions, item, parent)
+{
+ var addSeparator = false;
+
+ while (item != null)
+ {
+ if (item.nodeName == 'add')
+ {
+ var condition = item.getAttribute('if');
+
+ if (condition == null || conditions[condition])
+ {
+ var as = item.getAttribute('as');
+ as = mxResources.get(as) || as;
+ var funct = mxUtils.eval(mxUtils.getTextContent(item));
+ var action = item.getAttribute('action');
+ var icon = item.getAttribute('icon');
+ var iconCls = item.getAttribute('iconCls');
+
+ if (addSeparator)
+ {
+ menu.addSeparator(parent);
+ addSeparator = false;
+ }
+
+ if (icon != null && this.imageBasePath)
+ {
+ icon = this.imageBasePath + icon;
+ }
+
+ var row = this.addAction(menu, editor, as, icon, funct, action, cell, parent, iconCls);
+ this.addItems(editor, menu, cell, evt, conditions, item.firstChild, row);
+ }
+ }
+ else if (item.nodeName == 'separator')
+ {
+ addSeparator = true;
+ }
+
+ item = item.nextSibling;
+ }
+};
+
+/**
+ * Function: addAction
+ *
+ * Helper method to bind an action to a new menu item.
+ *
+ * Parameters:
+ *
+ * menu - <mxPopupMenu> that is used for adding items and separators.
+ * editor - Enclosing <mxEditor> instance.
+ * lab - String that represents the label of the menu item.
+ * icon - Optional URL that represents the icon of the menu item.
+ * action - Optional name of the action to execute in the given editor.
+ * funct - Optional function to execute before the optional action. The
+ * function takes an <mxEditor>, the <mxCell> under the mouse and the
+ * mouse event that triggered the call.
+ * cell - Optional <mxCell> to use as an argument for the action.
+ * parent - DOM node that represents the parent menu item.
+ * iconCls - Optional CSS class for the menu icon.
+ */
+mxDefaultPopupMenu.prototype.addAction = function(menu, editor, lab, icon, funct, action, cell, parent, iconCls)
+{
+ var clickHandler = function(evt)
+ {
+ if (typeof(funct) == 'function')
+ {
+ funct.call(editor, editor, cell, evt);
+ }
+
+ if (action != null)
+ {
+ editor.execute(action, cell, evt);
+ }
+ };
+
+ return menu.addItem(lab, icon, clickHandler, parent, iconCls);
+};
+
+/**
+ * Function: createConditions
+ *
+ * Evaluates the default conditions for the given context.
+ */
+mxDefaultPopupMenu.prototype.createConditions = function(editor, cell, evt)
+{
+ // Creates array with conditions
+ var model = editor.graph.getModel();
+ var childCount = model.getChildCount(cell);
+
+ // Adds some frequently used conditions
+ var conditions = [];
+ conditions['nocell'] = cell == null;
+ conditions['ncells'] = editor.graph.getSelectionCount() > 1;
+ conditions['notRoot'] = model.getRoot() !=
+ model.getParent(editor.graph.getDefaultParent());
+ conditions['cell'] = cell != null;
+
+ var isCell = cell != null && editor.graph.getSelectionCount() == 1;
+ conditions['nonEmpty'] = isCell && childCount > 0;
+ conditions['expandable'] = isCell && editor.graph.isCellFoldable(cell, false);
+ conditions['collapsable'] = isCell && editor.graph.isCellFoldable(cell, true);
+ conditions['validRoot'] = isCell && editor.graph.isValidRoot(cell);
+ conditions['emptyValidRoot'] = conditions['validRoot'] && childCount == 0;
+ conditions['swimlane'] = isCell && editor.graph.isSwimlane(cell);
+
+ // Evaluates dynamic conditions from config file
+ var condNodes = this.config.getElementsByTagName('condition');
+
+ for (var i=0; i<condNodes.length; i++)
+ {
+ var funct = mxUtils.eval(mxUtils.getTextContent(condNodes[i]));
+ var name = condNodes[i].getAttribute('name');
+
+ if (name != null && typeof(funct) == 'function')
+ {
+ conditions[name] = funct(editor, cell, evt);
+ }
+ }
+
+ return conditions;
+};
diff --git a/src/js/editor/mxDefaultToolbar.js b/src/js/editor/mxDefaultToolbar.js
new file mode 100644
index 0000000..3f8f901
--- /dev/null
+++ b/src/js/editor/mxDefaultToolbar.js
@@ -0,0 +1,567 @@
+/**
+ * $Id: mxDefaultToolbar.js,v 1.67 2012-04-11 07:00:52 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxDefaultToolbar
+ *
+ * Toolbar for the editor. This modifies the state of the graph
+ * or inserts new cells upon mouse clicks.
+ *
+ * Example:
+ *
+ * Create a toolbar with a button to copy the selection into the clipboard,
+ * and a combo box with one action to paste the selection from the clipboard
+ * into the graph.
+ *
+ * (code)
+ * var toolbar = new mxDefaultToolbar(container, editor);
+ * toolbar.addItem('Copy', null, 'copy');
+ *
+ * var combo = toolbar.addActionCombo('More actions...');
+ * toolbar.addActionOption(combo, 'Paste', 'paste');
+ * (end)
+ *
+ * Codec:
+ *
+ * This class uses the <mxDefaultToolbarCodec> to read configuration
+ * data into an existing instance. See <mxDefaultToolbarCodec> for a
+ * description of the configuration format.
+ *
+ * Constructor: mxDefaultToolbar
+ *
+ * Constructs a new toolbar for the given container and editor. The
+ * container and editor may be null if a prototypical instance for a
+ * <mxDefaultKeyHandlerCodec> is created.
+ *
+ * Parameters:
+ *
+ * container - DOM node that contains the toolbar.
+ * editor - Reference to the enclosing <mxEditor>.
+ */
+function mxDefaultToolbar(container, editor)
+{
+ this.editor = editor;
+
+ if (container != null &&
+ editor != null)
+ {
+ this.init(container);
+ }
+};
+
+/**
+ * Variable: editor
+ *
+ * Reference to the enclosing <mxEditor>.
+ */
+mxDefaultToolbar.prototype.editor = null;
+
+/**
+ * Variable: toolbar
+ *
+ * Holds the internal <mxToolbar>.
+ */
+mxDefaultToolbar.prototype.toolbar = null;
+
+/**
+ * Variable: resetHandler
+ *
+ * Reference to the function used to reset the <toolbar>.
+ */
+mxDefaultToolbar.prototype.resetHandler = null;
+
+/**
+ * Variable: spacing
+ *
+ * Defines the spacing between existing and new vertices in
+ * gridSize units when a new vertex is dropped on an existing
+ * cell. Default is 4 (40 pixels).
+ */
+mxDefaultToolbar.prototype.spacing = 4;
+
+/**
+ * Variable: connectOnDrop
+ *
+ * Specifies if elements should be connected if new cells are dropped onto
+ * connectable elements. Default is false.
+ */
+mxDefaultToolbar.prototype.connectOnDrop = false;
+
+/**
+ * Variable: init
+ *
+ * Constructs the <toolbar> for the given container and installs a listener
+ * that updates the <mxEditor.insertFunction> on <editor> if an item is
+ * selected in the toolbar. This assumes that <editor> is not null.
+ *
+ * Parameters:
+ *
+ * container - DOM node that contains the toolbar.
+ */
+mxDefaultToolbar.prototype.init = function(container)
+{
+ if (container != null)
+ {
+ this.toolbar = new mxToolbar(container);
+
+ // Installs the insert function in the editor if an item is
+ // selected in the toolbar
+ this.toolbar.addListener(mxEvent.SELECT,
+ mxUtils.bind(this, function(sender, evt)
+ {
+ var funct = evt.getProperty('function');
+
+ if (funct != null)
+ {
+ this.editor.insertFunction = mxUtils.bind(this, function()
+ {
+ funct.apply(this, arguments);
+ this.toolbar.resetMode();
+ });
+ }
+ else
+ {
+ this.editor.insertFunction = null;
+ }
+ })
+ );
+
+ // Resets the selected tool after a doubleclick or escape keystroke
+ this.resetHandler = mxUtils.bind(this, function()
+ {
+ if (this.toolbar != null)
+ {
+ this.toolbar.resetMode(true);
+ }
+ });
+
+ this.editor.graph.addListener(mxEvent.DOUBLE_CLICK, this.resetHandler);
+ this.editor.addListener(mxEvent.ESCAPE, this.resetHandler);
+ }
+};
+
+/**
+ * Function: addItem
+ *
+ * Adds a new item that executes the given action in <editor>. The title,
+ * icon and pressedIcon are used to display the toolbar item.
+ *
+ * Parameters:
+ *
+ * title - String that represents the title (tooltip) for the item.
+ * icon - URL of the icon to be used for displaying the item.
+ * action - Name of the action to execute when the item is clicked.
+ * pressed - Optional URL of the icon for the pressed state.
+ */
+mxDefaultToolbar.prototype.addItem = function(title, icon, action, pressed)
+{
+ var clickHandler = mxUtils.bind(this, function()
+ {
+ if (action != null && action.length > 0)
+ {
+ this.editor.execute(action);
+ }
+ });
+
+ return this.toolbar.addItem(title, icon, clickHandler, pressed);
+};
+
+/**
+ * Function: addSeparator
+ *
+ * Adds a vertical separator using the optional icon.
+ *
+ * Parameters:
+ *
+ * icon - Optional URL of the icon that represents the vertical separator.
+ * Default is <mxClient.imageBasePath> + '/separator.gif'.
+ */
+mxDefaultToolbar.prototype.addSeparator = function(icon)
+{
+ icon = icon || mxClient.imageBasePath + '/separator.gif';
+ this.toolbar.addSeparator(icon);
+};
+
+/**
+ * Function: addCombo
+ *
+ * Helper method to invoke <mxToolbar.addCombo> on <toolbar> and return the
+ * resulting DOM node.
+ */
+mxDefaultToolbar.prototype.addCombo = function()
+{
+ return this.toolbar.addCombo();
+};
+
+/**
+ * Function: addActionCombo
+ *
+ * Helper method to invoke <mxToolbar.addActionCombo> on <toolbar> using
+ * the given title and return the resulting DOM node.
+ *
+ * Parameters:
+ *
+ * title - String that represents the title of the combo.
+ */
+mxDefaultToolbar.prototype.addActionCombo = function(title)
+{
+ return this.toolbar.addActionCombo(title);
+};
+
+/**
+ * Function: addActionOption
+ *
+ * Binds the given action to a option with the specified label in the
+ * given combo. Combo is an object returned from an earlier call to
+ * <addCombo> or <addActionCombo>.
+ *
+ * Parameters:
+ *
+ * combo - DOM node that represents the combo box.
+ * title - String that represents the title of the combo.
+ * action - Name of the action to execute in <editor>.
+ */
+mxDefaultToolbar.prototype.addActionOption = function(combo, title, action)
+{
+ var clickHandler = mxUtils.bind(this, function()
+ {
+ this.editor.execute(action);
+ });
+
+ this.addOption(combo, title, clickHandler);
+};
+
+/**
+ * Function: addOption
+ *
+ * Helper method to invoke <mxToolbar.addOption> on <toolbar> and return
+ * the resulting DOM node that represents the option.
+ *
+ * Parameters:
+ *
+ * combo - DOM node that represents the combo box.
+ * title - String that represents the title of the combo.
+ * value - Object that represents the value of the option.
+ */
+mxDefaultToolbar.prototype.addOption = function(combo, title, value)
+{
+ return this.toolbar.addOption(combo, title, value);
+};
+
+/**
+ * Function: addMode
+ *
+ * Creates an item for selecting the given mode in the <editor>'s graph.
+ * Supported modenames are select, connect and pan.
+ *
+ * Parameters:
+ *
+ * title - String that represents the title of the item.
+ * icon - URL of the icon that represents the item.
+ * mode - String that represents the mode name to be used in
+ * <mxEditor.setMode>.
+ * pressed - Optional URL of the icon that represents the pressed state.
+ * funct - Optional JavaScript function that takes the <mxEditor> as the
+ * first and only argument that is executed after the mode has been
+ * selected.
+ */
+mxDefaultToolbar.prototype.addMode = function(title, icon, mode, pressed, funct)
+{
+ var clickHandler = mxUtils.bind(this, function()
+ {
+ this.editor.setMode(mode);
+
+ if (funct != null)
+ {
+ funct(this.editor);
+ }
+ });
+
+ return this.toolbar.addSwitchMode(title, icon, clickHandler, pressed);
+};
+
+/**
+ * Function: addPrototype
+ *
+ * Creates an item for inserting a clone of the specified prototype cell into
+ * the <editor>'s graph. The ptype may either be a cell or a function that
+ * returns a cell.
+ *
+ * Parameters:
+ *
+ * title - String that represents the title of the item.
+ * icon - URL of the icon that represents the item.
+ * ptype - Function or object that represents the prototype cell. If ptype
+ * is a function then it is invoked with no arguments to create new
+ * instances.
+ * pressed - Optional URL of the icon that represents the pressed state.
+ * insert - Optional JavaScript function that handles an insert of the new
+ * cell. This function takes the <mxEditor>, new cell to be inserted, mouse
+ * event and optional <mxCell> under the mouse pointer as arguments.
+ * toggle - Optional boolean that specifies if the item can be toggled.
+ * Default is true.
+ */
+mxDefaultToolbar.prototype.addPrototype = function(title, icon, ptype, pressed, insert, toggle)
+{
+ // Creates a wrapper function that is in charge of constructing
+ // the new cell instance to be inserted into the graph
+ var factory = function()
+ {
+ if (typeof(ptype) == 'function')
+ {
+ return ptype();
+ }
+ else if (ptype != null)
+ {
+ return ptype.clone();
+ }
+
+ return null;
+ };
+
+ // Defines the function for a click event on the graph
+ // after this item has been selected in the toolbar
+ var clickHandler = mxUtils.bind(this, function(evt, cell)
+ {
+ if (typeof(insert) == 'function')
+ {
+ insert(this.editor, factory(), evt, cell);
+ }
+ else
+ {
+ this.drop(factory(), evt, cell);
+ }
+
+ this.toolbar.resetMode();
+ mxEvent.consume(evt);
+ });
+
+ var img = this.toolbar.addMode(title, icon, clickHandler, pressed, null, toggle);
+
+ // Creates a wrapper function that calls the click handler without
+ // the graph argument
+ var dropHandler = function(graph, evt, cell)
+ {
+ clickHandler(evt, cell);
+ };
+
+ this.installDropHandler(img, dropHandler);
+
+ return img;
+};
+
+/**
+ * Function: drop
+ *
+ * Handles a drop from a toolbar item to the graph. The given vertex
+ * represents the new cell to be inserted. This invokes <insert> or
+ * <connect> depending on the given target cell.
+ *
+ * Parameters:
+ *
+ * vertex - <mxCell> to be inserted.
+ * evt - Mouse event that represents the drop.
+ * target - Optional <mxCell> that represents the drop target.
+ */
+mxDefaultToolbar.prototype.drop = function(vertex, evt, target)
+{
+ var graph = this.editor.graph;
+ var model = graph.getModel();
+
+ if (target == null ||
+ model.isEdge(target) ||
+ !this.connectOnDrop ||
+ !graph.isCellConnectable(target))
+ {
+ while (target != null &&
+ !graph.isValidDropTarget(target, [vertex], evt))
+ {
+ target = model.getParent(target);
+ }
+
+ this.insert(vertex, evt, target);
+ }
+ else
+ {
+ this.connect(vertex, evt, target);
+ }
+};
+
+/**
+ * Function: insert
+ *
+ * Handles a drop by inserting the given vertex into the given parent cell
+ * or the default parent if no parent is specified.
+ *
+ * Parameters:
+ *
+ * vertex - <mxCell> to be inserted.
+ * evt - Mouse event that represents the drop.
+ * parent - Optional <mxCell> that represents the parent.
+ */
+mxDefaultToolbar.prototype.insert = function(vertex, evt, target)
+{
+ var graph = this.editor.graph;
+
+ if (graph.canImportCell(vertex))
+ {
+ var x = mxEvent.getClientX(evt);
+ var y = mxEvent.getClientY(evt);
+ var pt = mxUtils.convertPoint(graph.container, x, y);
+
+ // Splits the target edge or inserts into target group
+ if (graph.isSplitEnabled() &&
+ graph.isSplitTarget(target, [vertex], evt))
+ {
+ return graph.splitEdge(target, [vertex], null, pt.x, pt.y);
+ }
+ else
+ {
+ return this.editor.addVertex(target, vertex, pt.x, pt.y);
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: connect
+ *
+ * Handles a drop by connecting the given vertex to the given source cell.
+ *
+ * vertex - <mxCell> to be inserted.
+ * evt - Mouse event that represents the drop.
+ * source - Optional <mxCell> that represents the source terminal.
+ */
+mxDefaultToolbar.prototype.connect = function(vertex, evt, source)
+{
+ var graph = this.editor.graph;
+ var model = graph.getModel();
+
+ if (source != null &&
+ graph.isCellConnectable(vertex) &&
+ graph.isEdgeValid(null, source, vertex))
+ {
+ var edge = null;
+
+ model.beginUpdate();
+ try
+ {
+ var geo = model.getGeometry(source);
+ var g = model.getGeometry(vertex).clone();
+
+ // Moves the vertex away from the drop target that will
+ // be used as the source for the new connection
+ g.x = geo.x + (geo.width - g.width) / 2;
+ g.y = geo.y + (geo.height - g.height) / 2;
+
+ var step = this.spacing * graph.gridSize;
+ var dist = model.getDirectedEdgeCount(source, true) * 20;
+
+ if (this.editor.horizontalFlow)
+ {
+ g.x += (g.width + geo.width) / 2 + step + dist;
+ }
+ else
+ {
+ g.y += (g.height + geo.height) / 2 + step + dist;
+ }
+
+ vertex.setGeometry(g);
+
+ // Fires two add-events with the code below - should be fixed
+ // to only fire one add event for both inserts
+ var parent = model.getParent(source);
+ graph.addCell(vertex, parent);
+ graph.constrainChild(vertex);
+
+ // Creates the edge using the editor instance and calls
+ // the second function that fires an add event
+ edge = this.editor.createEdge(source, vertex);
+
+ if (model.getGeometry(edge) == null)
+ {
+ var edgeGeometry = new mxGeometry();
+ edgeGeometry.relative = true;
+
+ model.setGeometry(edge, edgeGeometry);
+ }
+
+ graph.addEdge(edge, parent, source, vertex);
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+
+ graph.setSelectionCells([vertex, edge]);
+ graph.scrollCellToVisible(vertex);
+ }
+};
+
+/**
+ * Function: installDropHandler
+ *
+ * Makes the given img draggable using the given function for handling a
+ * drop event.
+ *
+ * Parameters:
+ *
+ * img - DOM node that represents the image.
+ * dropHandler - Function that handles a drop of the image.
+ */
+mxDefaultToolbar.prototype.installDropHandler = function (img, dropHandler)
+{
+ var sprite = document.createElement('img');
+ sprite.setAttribute('src', img.getAttribute('src'));
+
+ // Handles delayed loading of the images
+ var loader = mxUtils.bind(this, function(evt)
+ {
+ // Preview uses the image node with double size. Later this can be
+ // changed to use a separate preview and guides, but for this the
+ // dropHandler must use the additional x- and y-arguments and the
+ // dragsource which makeDraggable returns much be configured to
+ // use guides via mxDragSource.isGuidesEnabled.
+ sprite.style.width = (2 * img.offsetWidth) + 'px';
+ sprite.style.height = (2 * img.offsetHeight) + 'px';
+
+ mxUtils.makeDraggable(img, this.editor.graph, dropHandler,
+ sprite);
+ mxEvent.removeListener(sprite, 'load', loader);
+ });
+
+ if (mxClient.IS_IE)
+ {
+ loader();
+ }
+ else
+ {
+ mxEvent.addListener(sprite, 'load', loader);
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the <toolbar> associated with this object and removes all
+ * installed listeners. This does normally not need to be called, the
+ * <toolbar> is destroyed automatically when the window unloads (in IE) by
+ * <mxEditor>.
+ */
+mxDefaultToolbar.prototype.destroy = function ()
+{
+ if (this.resetHandler != null)
+ {
+ this.editor.graph.removeListener('dblclick', this.resetHandler);
+ this.editor.removeListener('escape', this.resetHandler);
+ this.resetHandler = null;
+ }
+
+ if (this.toolbar != null)
+ {
+ this.toolbar.destroy();
+ this.toolbar = null;
+ }
+};
diff --git a/src/js/editor/mxEditor.js b/src/js/editor/mxEditor.js
new file mode 100644
index 0000000..9c57a9c
--- /dev/null
+++ b/src/js/editor/mxEditor.js
@@ -0,0 +1,3220 @@
+/**
+ * $Id: mxEditor.js,v 1.231 2012-12-03 18:02:25 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxEditor
+ *
+ * Extends <mxEventSource> to implement a application wrapper for a graph that
+ * adds <actions>, I/O using <mxCodec>, auto-layout using <mxLayoutManager>,
+ * command history using <undoManager>, and standard dialogs and widgets, eg.
+ * properties, help, outline, toolbar, and popupmenu. It also adds <templates>
+ * to be used as cells in toolbars, auto-validation using the <validation>
+ * flag, attribute cycling using <cycleAttributeValues>, higher-level events
+ * such as <root>, and backend integration using <urlPost>, <urlImage>,
+ * <urlInit>, <urlNotify> and <urlPoll>.
+ *
+ * Actions:
+ *
+ * Actions are functions stored in the <actions> array under their names. The
+ * functions take the <mxEditor> as the first, and an optional <mxCell> as the
+ * second argument and are invoked using <execute>. Any additional arguments
+ * passed to execute are passed on to the action as-is.
+ *
+ * A list of built-in actions is available in the <addActions> description.
+ *
+ * Read/write Diagrams:
+ *
+ * To read a diagram from an XML string, for example from a textfield within the
+ * page, the following code is used:
+ *
+ * (code)
+ * var doc = mxUtils.parseXML(xmlString);
+ * var node = doc.documentElement;
+ * editor.readGraphModel(node);
+ * (end)
+ *
+ * For reading a diagram from a remote location, use the <open> method.
+ *
+ * To save diagrams in XML on a server, you can set the <urlPost> variable.
+ * This variable will be used in <getUrlPost> to construct a URL for the post
+ * request that is issued in the <save> method. The post request contains the
+ * XML representation of the diagram as returned by <writeGraphModel> in the
+ * xml parameter.
+ *
+ * On the server side, the post request is processed using standard
+ * technologies such as Java Servlets, CGI, .NET or ASP.
+ *
+ * Here are some examples of processing a post request in various languages.
+ *
+ * - Java: URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", "&#xa;")
+ *
+ * Note that the linefeeds should only be replaced if the XML is
+ * processed in Java, for example when creating an image, but not
+ * if the XML is passed back to the client-side.
+ *
+ * - .NET: HttpUtility.UrlDecode(context.Request.Params["xml"])
+ * - PHP: urldecode($_POST["xml"])
+ *
+ * Creating images:
+ *
+ * A backend (Java, PHP or C#) is required for creating images. The
+ * distribution contains an example for each backend (ImageHandler.java,
+ * ImageHandler.cs and graph.php). More information about using a backend
+ * to create images can be found in the readme.html files. Note that the
+ * preview is implemented using VML/SVG in the browser and does not require
+ * a backend. The backend is only required to creates images (bitmaps).
+ *
+ * Special characters:
+ *
+ * Note There are five characters that should always appear in XML content as
+ * escapes, so that they do not interact with the syntax of the markup. These
+ * are part of the language for all documents based on XML and for HTML.
+ *
+ * - &lt; (<)
+ * - &gt; (>)
+ * - &amp; (&)
+ * - &quot; (")
+ * - &apos; (')
+ *
+ * Although it is part of the XML language, &apos; is not defined in HTML.
+ * For this reason the XHTML specification recommends instead the use of
+ * &#39; if text may be passed to a HTML user agent.
+ *
+ * If you are having problems with special characters on the server-side then
+ * you may want to try the <escapePostData> flag.
+ *
+ * For converting decimal escape sequences inside strings, a user has provided
+ * us with the following function:
+ *
+ * (code)
+ * function html2js(text)
+ * {
+ * var entitySearch = /&#[0-9]+;/;
+ * var entity;
+ *
+ * while (entity = entitySearch.exec(text))
+ * {
+ * var charCode = entity[0].substring(2, entity[0].length -1);
+ * text = text.substring(0, entity.index)
+ * + String.fromCharCode(charCode)
+ * + text.substring(entity.index + entity[0].length);
+ * }
+ *
+ * return text;
+ * }
+ * (end)
+ *
+ * Otherwise try using hex escape sequences and the built-in unescape function
+ * for converting such strings.
+ *
+ * Local Files:
+ *
+ * For saving and opening local files, no standardized method exists that
+ * works across all browsers. The recommended way of dealing with local files
+ * is to create a backend that streams the XML data back to the browser (echo)
+ * as an attachment so that a Save-dialog is displayed on the client-side and
+ * the file can be saved to the local disk.
+ *
+ * For example, in PHP the code that does this looks as follows.
+ *
+ * (code)
+ * $xml = stripslashes($_POST["xml"]);
+ * header("Content-Disposition: attachment; filename=\"diagram.xml\"");
+ * echo($xml);
+ * (end)
+ *
+ * To open a local file, the file should be uploaded via a form in the browser
+ * and then opened from the server in the editor.
+ *
+ * Cell Properties:
+ *
+ * The properties displayed in the properties dialog are the attributes and
+ * values of the cell's user object, which is an XML node. The XML node is
+ * defined in the templates section of the config file.
+ *
+ * The templates are stored in <mxEditor.templates> and contain cells which
+ * are cloned at insertion time to create new vertices by use of drag and
+ * drop from the toolbar. Each entry in the toolbar for adding a new vertex
+ * must refer to an existing template.
+ *
+ * In the following example, the task node is a business object and only the
+ * mxCell node and its mxGeometry child contain graph information:
+ *
+ * (code)
+ * <Task label="Task" description="">
+ * <mxCell vertex="true">
+ * <mxGeometry as="geometry" width="72" height="32"/>
+ * </mxCell>
+ * </Task>
+ * (end)
+ *
+ * The idea is that the XML representation is inverse from the in-memory
+ * representation: The outer XML node is the user object and the inner node is
+ * the cell. This means the user object of the cell is the Task node with no
+ * children for the above example:
+ *
+ * (code)
+ * <Task label="Task" description=""/>
+ * (end)
+ *
+ * The Task node can have any tag name, attributes and child nodes. The
+ * <mxCodec> will use the XML hierarchy as the user object, while removing the
+ * "known annotations", such as the mxCell node. At save-time the cell data
+ * will be "merged" back into the user object. The user object is only modified
+ * via the properties dialog during the lifecycle of the cell.
+ *
+ * In the default implementation of <createProperties>, the user object's
+ * attributes are put into a form for editing. Attributes are changed using
+ * the <mxCellAttributeChange> action in the model. The dialog can be replaced
+ * by overriding the <createProperties> hook or by replacing the showProperties
+ * action in <actions>. Alternatively, the entry in the config file's popupmenu
+ * section can be modified to invoke a different action.
+ *
+ * If you want to displey the properties dialog on a doubleclick, you can set
+ * <mxEditor.dblClickAction> to showProperties as follows:
+ *
+ * (code)
+ * editor.dblClickAction = 'showProperties';
+ * (end)
+ *
+ * Popupmenu and Toolbar:
+ *
+ * The toolbar and popupmenu are typically configured using the respective
+ * sections in the config file, that is, the popupmenu is defined as follows:
+ *
+ * (code)
+ * <mxEditor>
+ * <mxDefaultPopupMenu as="popupHandler">
+ * <add as="cut" action="cut" icon="images/cut.gif"/>
+ * ...
+ * (end)
+ *
+ * New entries can be added to the toolbar by inserting an add-node into the
+ * above configuration. Existing entries may be removed and changed by
+ * modifying or removing the respective entries in the configuration.
+ * The configuration is read by the <mxDefaultPopupMenuCodec>, the format of the
+ * configuration is explained in <mxDefaultPopupMenu.decode>.
+ *
+ * The toolbar is defined in the mxDefaultToolbar section. Items can be added
+ * and removed in this section.
+ *
+ * (code)
+ * <mxEditor>
+ * <mxDefaultToolbar>
+ * <add as="save" action="save" icon="images/save.gif"/>
+ * <add as="Swimlane" template="swimlane" icon="images/swimlane.gif"/>
+ * ...
+ * (end)
+ *
+ * The format of the configuration is described in
+ * <mxDefaultToolbarCodec.decode>.
+ *
+ * Ids:
+ *
+ * For the IDs, there is an implicit behaviour in <mxCodec>: It moves the Id
+ * from the cell to the user object at encoding time and vice versa at decoding
+ * time. For example, if the Task node from above has an id attribute, then
+ * the <mxCell.id> of the corresponding cell will have this value. If there
+ * is no Id collision in the model, then the cell may be retrieved using this
+ * Id with the <mxGraphModel.getCell> function. If there is a collision, a new
+ * Id will be created for the cell using <mxGraphModel.createId>. At encoding
+ * time, this new Id will replace the value previously stored under the id
+ * attribute in the Task node.
+ *
+ * See <mxEditorCodec>, <mxDefaultToolbarCodec> and <mxDefaultPopupMenuCodec>
+ * for information about configuring the editor and user interface.
+ *
+ * Programmatically inserting cells:
+ *
+ * For inserting a new cell, say, by clicking a button in the document,
+ * the following code can be used. This requires an reference to the editor.
+ *
+ * (code)
+ * var userObject = new Object();
+ * var parent = editor.graph.getDefaultParent();
+ * var model = editor.graph.model;
+ * model.beginUpdate();
+ * try
+ * {
+ * editor.graph.insertVertex(parent, null, userObject, 20, 20, 80, 30);
+ * }
+ * finally
+ * {
+ * model.endUpdate();
+ * }
+ * (end)
+ *
+ * If a template cell from the config file should be inserted, then a clone
+ * of the template can be created as follows. The clone is then inserted using
+ * the add function instead of addVertex.
+ *
+ * (code)
+ * var template = editor.templates['task'];
+ * var clone = editor.graph.model.cloneCell(template);
+ * (end)
+ *
+ * Resources:
+ *
+ * resources/editor - Language resources for mxEditor
+ *
+ * Callback: onInit
+ *
+ * Called from within the constructor. In the callback,
+ * "this" refers to the editor instance.
+ *
+ * Cookie: mxgraph=seen
+ *
+ * Set when the editor is started. Never expires. Use
+ * <resetFirstTime> to reset this cookie. This cookie
+ * only exists if <onInit> is implemented.
+ *
+ * Event: mxEvent.OPEN
+ *
+ * Fires after a file was opened in <open>. The <code>filename</code> property
+ * contains the filename that was used. The same value is also available in
+ * <filename>.
+ *
+ * Event: mxEvent.SAVE
+ *
+ * Fires after the current file was saved in <save>. The <code>url</code>
+ * property contains the URL that was used for saving.
+ *
+ * Event: mxEvent.POST
+ *
+ * Fires if a successful response was received in <postDiagram>. The
+ * <code>request</code> property contains the <mxXmlRequest>, the
+ * <code>url</code> and <code>data</code> properties contain the URL and the
+ * data that were used in the post request.
+ *
+ * Event: mxEvent.ROOT
+ *
+ * Fires when the current root has changed, or when the title of the current
+ * root has changed. This event has no properties.
+ *
+ * Event: mxEvent.SESSION
+ *
+ * Fires when anything in the session has changed. The <code>session</code>
+ * property contains the respective <mxSession>.
+ *
+ * Event: mxEvent.BEFORE_ADD_VERTEX
+ *
+ * Fires before a vertex is added in <addVertex>. The <code>vertex</code>
+ * property contains the new vertex and the <code>parent</code> property
+ * contains its parent.
+ *
+ * Event: mxEvent.ADD_VERTEX
+ *
+ * Fires between begin- and endUpdate in <addVertex>. The <code>vertex</code>
+ * property contains the vertex that is being inserted.
+ *
+ * Event: mxEvent.AFTER_ADD_VERTEX
+ *
+ * Fires after a vertex was inserted and selected in <addVertex>. The
+ * <code>vertex</code> property contains the new vertex.
+ *
+ * Example:
+ *
+ * For starting an in-place edit after a new vertex has been added to the
+ * graph, the following code can be used.
+ *
+ * (code)
+ * editor.addListener(mxEvent.AFTER_ADD_VERTEX, function(sender, evt)
+ * {
+ * var vertex = evt.getProperty('vertex');
+ *
+ * if (editor.graph.isCellEditable(vertex))
+ * {
+ * editor.graph.startEditingAtCell(vertex);
+ * }
+ * });
+ * (end)
+ *
+ * Event: mxEvent.ESCAPE
+ *
+ * Fires when the escape key is pressed. The <code>event</code> property
+ * contains the key event.
+ *
+ * Constructor: mxEditor
+ *
+ * Constructs a new editor. This function invokes the <onInit> callback
+ * upon completion.
+ *
+ * Example:
+ *
+ * (code)
+ * var config = mxUtils.load('config/diagrameditor.xml').getDocumentElement();
+ * var editor = new mxEditor(config);
+ * (end)
+ *
+ * Parameters:
+ *
+ * config - Optional XML node that contains the configuration.
+ */
+function mxEditor(config)
+{
+ this.actions = [];
+ this.addActions();
+
+ // Executes the following only if a document has been instanciated.
+ // That is, don't execute when the editorcodec is setup.
+ if (document.body != null)
+ {
+ // Defines instance fields
+ this.cycleAttributeValues = [];
+ this.popupHandler = new mxDefaultPopupMenu();
+ this.undoManager = new mxUndoManager();
+
+ // Creates the graph and toolbar without the containers
+ this.graph = this.createGraph();
+ this.toolbar = this.createToolbar();
+
+ // Creates the global keyhandler (requires graph instance)
+ this.keyHandler = new mxDefaultKeyHandler(this);
+
+ // Configures the editor using the URI
+ // which was passed to the ctor
+ this.configure(config);
+
+ // Assigns the swimlaneIndicatorColorAttribute on the graph
+ this.graph.swimlaneIndicatorColorAttribute = this.cycleAttributeName;
+
+ // Initializes the session if the urlInit
+ // member field of this editor is set.
+ if (!mxClient.IS_LOCAL && this.urlInit != null)
+ {
+ this.session = this.createSession();
+ }
+
+ // Checks ifthe <onInit> hook has been set
+ if (this.onInit != null)
+ {
+ // Invokes the <onInit> hook
+ this.onInit();
+ }
+
+ // Automatic deallocation of memory
+ if (mxClient.IS_IE)
+ {
+ mxEvent.addListener(window, 'unload', mxUtils.bind(this, function()
+ {
+ this.destroy();
+ }));
+ }
+ }
+};
+
+/**
+ * Installs the required language resources at class
+ * loading time.
+ */
+if (mxLoadResources)
+{
+ mxResources.add(mxClient.basePath+'/resources/editor');
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxEditor.prototype = new mxEventSource();
+mxEditor.prototype.constructor = mxEditor;
+
+/**
+ * Group: Controls and Handlers
+ */
+
+/**
+ * Variable: askZoomResource
+ *
+ * Specifies the resource key for the zoom dialog. If the resource for this
+ * key does not exist then the value is used as the error message. Default
+ * is 'askZoom'.
+ */
+mxEditor.prototype.askZoomResource = (mxClient.language != 'none') ? 'askZoom' : '';
+
+/**
+ * Variable: lastSavedResource
+ *
+ * Specifies the resource key for the last saved info. If the resource for
+ * this key does not exist then the value is used as the error message.
+ * Default is 'lastSaved'.
+ */
+mxEditor.prototype.lastSavedResource = (mxClient.language != 'none') ? 'lastSaved' : '';
+
+/**
+ * Variable: currentFileResource
+ *
+ * Specifies the resource key for the current file info. If the resource for
+ * this key does not exist then the value is used as the error message.
+ * Default is 'lastSaved'.
+ */
+mxEditor.prototype.currentFileResource = (mxClient.language != 'none') ? 'currentFile' : '';
+
+/**
+ * Variable: propertiesResource
+ *
+ * Specifies the resource key for the properties window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'properties'.
+ */
+mxEditor.prototype.propertiesResource = (mxClient.language != 'none') ? 'properties' : '';
+
+/**
+ * Variable: tasksResource
+ *
+ * Specifies the resource key for the tasks window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'tasks'.
+ */
+mxEditor.prototype.tasksResource = (mxClient.language != 'none') ? 'tasks' : '';
+
+/**
+ * Variable: helpResource
+ *
+ * Specifies the resource key for the help window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'help'.
+ */
+mxEditor.prototype.helpResource = (mxClient.language != 'none') ? 'help' : '';
+
+/**
+ * Variable: outlineResource
+ *
+ * Specifies the resource key for the outline window title. If the
+ * resource for this key does not exist then the value is used as the
+ * error message. Default is 'outline'.
+ */
+mxEditor.prototype.outlineResource = (mxClient.language != 'none') ? 'outline' : '';
+
+/**
+ * Variable: outline
+ *
+ * Reference to the <mxWindow> that contains the outline. The <mxOutline>
+ * is stored in outline.outline.
+ */
+mxEditor.prototype.outline = null;
+
+/**
+ * Variable: graph
+ *
+ * Holds a <mxGraph> for displaying the diagram. The graph
+ * is created in <setGraphContainer>.
+ */
+mxEditor.prototype.graph = null;
+
+/**
+ * Variable: graphRenderHint
+ *
+ * Holds the render hint used for creating the
+ * graph in <setGraphContainer>. See <mxGraph>.
+ * Default is null.
+ */
+mxEditor.prototype.graphRenderHint = null;
+
+/**
+ * Variable: toolbar
+ *
+ * Holds a <mxDefaultToolbar> for displaying the toolbar. The
+ * toolbar is created in <setToolbarContainer>.
+ */
+mxEditor.prototype.toolbar = null;
+
+/**
+ * Variable: session
+ *
+ * Holds a <mxSession> instance associated with this editor.
+ */
+mxEditor.prototype.session = null;
+
+/**
+ * Variable: status
+ *
+ * DOM container that holds the statusbar. Default is null.
+ * Use <setStatusContainer> to set this value.
+ */
+mxEditor.prototype.status = null;
+
+/**
+ * Variable: popupHandler
+ *
+ * Holds a <mxDefaultPopupMenu> for displaying
+ * popupmenus.
+ */
+mxEditor.prototype.popupHandler = null;
+
+/**
+ * Variable: undoManager
+ *
+ * Holds an <mxUndoManager> for the command history.
+ */
+mxEditor.prototype.undoManager = null;
+
+/**
+ * Variable: keyHandler
+ *
+ * Holds a <mxDefaultKeyHandler> for handling keyboard events.
+ * The handler is created in <setGraphContainer>.
+ */
+mxEditor.prototype.keyHandler = null;
+
+/**
+ * Group: Actions and Options
+ */
+
+/**
+ * Variable: actions
+ *
+ * Maps from actionnames to actions, which are functions taking
+ * the editor and the cell as arguments. Use <addAction>
+ * to add or replace an action and <execute> to execute an action
+ * by name, passing the cell to be operated upon as the second
+ * argument.
+ */
+mxEditor.prototype.actions = null;
+
+/**
+ * Variable: dblClickAction
+ *
+ * Specifies the name of the action to be executed
+ * when a cell is double clicked. Default is edit.
+ *
+ * To handle a singleclick, use the following code.
+ *
+ * (code)
+ * editor.graph.addListener(mxEvent.CLICK, function(sender, evt)
+ * {
+ * var e = evt.getProperty('event');
+ * var cell = evt.getProperty('cell');
+ *
+ * if (cell != null && !e.isConsumed())
+ * {
+ * // Do something useful with cell...
+ * e.consume();
+ * }
+ * });
+ * (end)
+ */
+mxEditor.prototype.dblClickAction = 'edit';
+
+/**
+ * Variable: swimlaneRequired
+ *
+ * Specifies if new cells must be inserted
+ * into an existing swimlane. Otherwise, cells
+ * that are not swimlanes can be inserted as
+ * top-level cells. Default is false.
+ */
+mxEditor.prototype.swimlaneRequired = false;
+
+/**
+ * Variable: disableContextMenu
+ *
+ * Specifies if the context menu should be disabled in the graph container.
+ * Default is true.
+ */
+mxEditor.prototype.disableContextMenu = true;
+
+/**
+ * Group: Templates
+ */
+
+/**
+ * Variable: insertFunction
+ *
+ * Specifies the function to be used for inserting new
+ * cells into the graph. This is assigned from the
+ * <mxDefaultToolbar> if a vertex-tool is clicked.
+ */
+mxEditor.prototype.insertFunction = null;
+
+/**
+ * Variable: forcedInserting
+ *
+ * Specifies if a new cell should be inserted on a single
+ * click even using <insertFunction> if there is a cell
+ * under the mousepointer, otherwise the cell under the
+ * mousepointer is selected. Default is false.
+ */
+mxEditor.prototype.forcedInserting = false;
+
+/**
+ * Variable: templates
+ *
+ * Maps from names to protoype cells to be used
+ * in the toolbar for inserting new cells into
+ * the diagram.
+ */
+mxEditor.prototype.templates = null;
+
+/**
+ * Variable: defaultEdge
+ *
+ * Prototype edge cell that is used for creating
+ * new edges.
+ */
+mxEditor.prototype.defaultEdge = null;
+
+/**
+ * Variable: defaultEdgeStyle
+ *
+ * Specifies the edge style to be returned in <getEdgeStyle>.
+ * Default is null.
+ */
+mxEditor.prototype.defaultEdgeStyle = null;
+
+/**
+ * Variable: defaultGroup
+ *
+ * Prototype group cell that is used for creating
+ * new groups.
+ */
+mxEditor.prototype.defaultGroup = null;
+
+/**
+ * Variable: graphRenderHint
+ *
+ * Default size for the border of new groups. If null,
+ * then then <mxGraph.gridSize> is used. Default is
+ * null.
+ */
+mxEditor.prototype.groupBorderSize = null;
+
+/**
+ * Group: Backend Integration
+ */
+
+/**
+ * Variable: filename
+ *
+ * Contains the URL of the last opened file as a string.
+ * Default is null.
+ */
+mxEditor.prototype.filename = null;
+
+/**
+ * Variable: lineFeed
+ *
+ * Character to be used for encoding linefeeds in <save>. Default is '&#xa;'.
+ */
+mxEditor.prototype.linefeed = '&#xa;';
+
+/**
+ * Variable: postParameterName
+ *
+ * Specifies if the name of the post parameter that contains the diagram
+ * data in a post request to the server. Default is xml.
+ */
+mxEditor.prototype.postParameterName = 'xml';
+
+/**
+ * Variable: escapePostData
+ *
+ * Specifies if the data in the post request for saving a diagram
+ * should be converted using encodeURIComponent. Default is true.
+ */
+mxEditor.prototype.escapePostData = true;
+
+/**
+ * Variable: urlPost
+ *
+ * Specifies the URL to be used for posting the diagram
+ * to a backend in <save>.
+ */
+mxEditor.prototype.urlPost = null;
+
+/**
+ * Variable: urlImage
+ *
+ * Specifies the URL to be used for creating a bitmap of
+ * the graph in the image action.
+ */
+mxEditor.prototype.urlImage = null;
+
+/**
+ * Variable: urlInit
+ *
+ * Specifies the URL to be used for initializing the session.
+ */
+mxEditor.prototype.urlInit = null;
+
+/**
+ * Variable: urlNotify
+ *
+ * Specifies the URL to be used for notifying the backend
+ * in the session.
+ */
+mxEditor.prototype.urlNotify = null;
+
+/**
+ * Variable: urlPoll
+ *
+ * Specifies the URL to be used for polling in the session.
+ */
+mxEditor.prototype.urlPoll = null;
+
+/**
+ * Group: Autolayout
+ */
+
+/**
+ * Variable: horizontalFlow
+ *
+ * Specifies the direction of the flow
+ * in the diagram. This is used in the
+ * layout algorithms. Default is false,
+ * ie. vertical flow.
+ */
+mxEditor.prototype.horizontalFlow = false;
+
+/**
+ * Variable: layoutDiagram
+ *
+ * Specifies if the top-level elements in the
+ * diagram should be layed out using a vertical
+ * or horizontal stack depending on the setting
+ * of <horizontalFlow>. The spacing between the
+ * swimlanes is specified by <swimlaneSpacing>.
+ * Default is false.
+ *
+ * If the top-level elements are swimlanes, then
+ * the intra-swimlane layout is activated by
+ * the <layoutSwimlanes> switch.
+ */
+mxEditor.prototype.layoutDiagram = false;
+
+/**
+ * Variable: swimlaneSpacing
+ *
+ * Specifies the spacing between swimlanes if
+ * automatic layout is turned on in
+ * <layoutDiagram>. Default is 0.
+ */
+mxEditor.prototype.swimlaneSpacing = 0;
+
+/**
+ * Variable: maintainSwimlanes
+ *
+ * Specifies if the swimlanes should be kept at the same
+ * width or height depending on the setting of
+ * <horizontalFlow>. Default is false.
+ *
+ * For horizontal flows, all swimlanes
+ * have the same height and for vertical flows, all swimlanes
+ * have the same width. Furthermore, the swimlanes are
+ * automatically "stacked" if <layoutDiagram> is true.
+ */
+mxEditor.prototype.maintainSwimlanes = false;
+
+/**
+ * Variable: layoutSwimlanes
+ *
+ * Specifies if the children of swimlanes should
+ * be layed out, either vertically or horizontally
+ * depending on <horizontalFlow>.
+ * Default is false.
+ */
+mxEditor.prototype.layoutSwimlanes = false;
+
+/**
+ * Group: Attribute Cycling
+ */
+
+/**
+ * Variable: cycleAttributeValues
+ *
+ * Specifies the attribute values to be cycled when
+ * inserting new swimlanes. Default is an empty
+ * array.
+ */
+mxEditor.prototype.cycleAttributeValues = null;
+
+/**
+ * Variable: cycleAttributeIndex
+ *
+ * Index of the last consumed attribute index. If a new
+ * swimlane is inserted, then the <cycleAttributeValues>
+ * at this index will be used as the value for
+ * <cycleAttributeName>. Default is 0.
+ */
+mxEditor.prototype.cycleAttributeIndex = 0;
+
+/**
+ * Variable: cycleAttributeName
+ *
+ * Name of the attribute to be assigned a <cycleAttributeValues>
+ * when inserting new swimlanes. Default is fillColor.
+ */
+mxEditor.prototype.cycleAttributeName = 'fillColor';
+
+/**
+ * Group: Windows
+ */
+
+/**
+ * Variable: tasks
+ *
+ * Holds the <mxWindow> created in <showTasks>.
+ */
+mxEditor.prototype.tasks = null;
+
+/**
+ * Variable: tasksWindowImage
+ *
+ * Icon for the tasks window.
+ */
+mxEditor.prototype.tasksWindowImage = null;
+
+/**
+ * Variable: tasksTop
+ *
+ * Specifies the top coordinate of the tasks window in pixels.
+ * Default is 20.
+ */
+mxEditor.prototype.tasksTop = 20;
+
+/**
+ * Variable: help
+ *
+ * Holds the <mxWindow> created in <showHelp>.
+ */
+mxEditor.prototype.help = null;
+
+/**
+ * Variable: helpWindowImage
+ *
+ * Icon for the help window.
+ */
+mxEditor.prototype.helpWindowImage = null;
+
+/**
+ * Variable: urlHelp
+ *
+ * Specifies the URL to be used for the contents of the
+ * Online Help window. This is usually specified in the
+ * resources file under urlHelp for language-specific
+ * online help support.
+ */
+mxEditor.prototype.urlHelp = null;
+
+/**
+ * Variable: helpWidth
+ *
+ * Specifies the width of the help window in pixels.
+ * Default is 300.
+ */
+mxEditor.prototype.helpWidth = 300;
+
+/**
+ * Variable: helpWidth
+ *
+ * Specifies the width of the help window in pixels.
+ * Default is 260.
+ */
+mxEditor.prototype.helpHeight = 260;
+
+/**
+ * Variable: propertiesWidth
+ *
+ * Specifies the width of the properties window in pixels.
+ * Default is 240.
+ */
+mxEditor.prototype.propertiesWidth = 240;
+
+/**
+ * Variable: propertiesHeight
+ *
+ * Specifies the height of the properties window in pixels.
+ * If no height is specified then the window will be automatically
+ * sized to fit its contents. Default is null.
+ */
+mxEditor.prototype.propertiesHeight = null;
+
+/**
+ * Variable: movePropertiesDialog
+ *
+ * Specifies if the properties dialog should be automatically
+ * moved near the cell it is displayed for, otherwise the
+ * dialog is not moved. This value is only taken into
+ * account if the dialog is already visible. Default is false.
+ */
+mxEditor.prototype.movePropertiesDialog = false;
+
+/**
+ * Variable: validating
+ *
+ * Specifies if <mxGraph.validateGraph> should automatically be invoked after
+ * each change. Default is false.
+ */
+mxEditor.prototype.validating = false;
+
+/**
+ * Variable: modified
+ *
+ * True if the graph has been modified since it was last saved.
+ */
+mxEditor.prototype.modified = false;
+
+/**
+ * Function: isModified
+ *
+ * Returns <modified>.
+ */
+mxEditor.prototype.isModified = function ()
+{
+ return this.modified;
+};
+
+/**
+ * Function: setModified
+ *
+ * Sets <modified> to the specified boolean value.
+ */
+mxEditor.prototype.setModified = function (value)
+{
+ this.modified = value;
+};
+
+/**
+ * Function: addActions
+ *
+ * Adds the built-in actions to the editor instance.
+ *
+ * save - Saves the graph using <urlPost>.
+ * print - Shows the graph in a new print preview window.
+ * show - Shows the graph in a new window.
+ * exportImage - Shows the graph as a bitmap image using <getUrlImage>.
+ * refresh - Refreshes the graph's display.
+ * cut - Copies the current selection into the clipboard
+ * and removes it from the graph.
+ * copy - Copies the current selection into the clipboard.
+ * paste - Pastes the clipboard into the graph.
+ * delete - Removes the current selection from the graph.
+ * group - Puts the current selection into a new group.
+ * ungroup - Removes the selected groups and selects the children.
+ * undo - Undoes the last change on the graph model.
+ * redo - Redoes the last change on the graph model.
+ * zoom - Sets the zoom via a dialog.
+ * zoomIn - Zooms into the graph.
+ * zoomOut - Zooms out of the graph
+ * actualSize - Resets the scale and translation on the graph.
+ * fit - Changes the scale so that the graph fits into the window.
+ * showProperties - Shows the properties dialog.
+ * selectAll - Selects all cells.
+ * selectNone - Clears the selection.
+ * selectVertices - Selects all vertices.
+ * selectEdges = Selects all edges.
+ * edit - Starts editing the current selection cell.
+ * enterGroup - Drills down into the current selection cell.
+ * exitGroup - Moves up in the drilling hierachy
+ * home - Moves to the topmost parent in the drilling hierarchy
+ * selectPrevious - Selects the previous cell.
+ * selectNext - Selects the next cell.
+ * selectParent - Selects the parent of the selection cell.
+ * selectChild - Selects the first child of the selection cell.
+ * collapse - Collapses the currently selected cells.
+ * expand - Expands the currently selected cells.
+ * bold - Toggle bold text style.
+ * italic - Toggle italic text style.
+ * underline - Toggle underline text style.
+ * shadow - Toggle shadow text style.
+ * alignCellsLeft - Aligns the selection cells at the left.
+ * alignCellsCenter - Aligns the selection cells in the center.
+ * alignCellsRight - Aligns the selection cells at the right.
+ * alignCellsTop - Aligns the selection cells at the top.
+ * alignCellsMiddle - Aligns the selection cells in the middle.
+ * alignCellsBottom - Aligns the selection cells at the bottom.
+ * alignFontLeft - Sets the horizontal text alignment to left.
+ * alignFontCenter - Sets the horizontal text alignment to center.
+ * alignFontRight - Sets the horizontal text alignment to right.
+ * alignFontTop - Sets the vertical text alignment to top.
+ * alignFontMiddle - Sets the vertical text alignment to middle.
+ * alignFontBottom - Sets the vertical text alignment to bottom.
+ * toggleTasks - Shows or hides the tasks window.
+ * toggleHelp - Shows or hides the help window.
+ * toggleOutline - Shows or hides the outline window.
+ * toggleConsole - Shows or hides the console window.
+ */
+mxEditor.prototype.addActions = function ()
+{
+ this.addAction('save', function(editor)
+ {
+ editor.save();
+ });
+
+ this.addAction('print', function(editor)
+ {
+ var preview = new mxPrintPreview(editor.graph, 1);
+ preview.open();
+ });
+
+ this.addAction('show', function(editor)
+ {
+ mxUtils.show(editor.graph, null, 10, 10);
+ });
+
+ this.addAction('exportImage', function(editor)
+ {
+ var url = editor.getUrlImage();
+
+ if (url == null || mxClient.IS_LOCAL)
+ {
+ editor.execute('show');
+ }
+ else
+ {
+ var node = mxUtils.getViewXml(editor.graph, 1);
+ var xml = mxUtils.getXml(node, '\n');
+
+ mxUtils.submit(url, editor.postParameterName + '=' +
+ encodeURIComponent(xml), document, '_blank');
+ }
+ });
+
+ this.addAction('refresh', function(editor)
+ {
+ editor.graph.refresh();
+ });
+
+ this.addAction('cut', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ mxClipboard.cut(editor.graph);
+ }
+ });
+
+ this.addAction('copy', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ mxClipboard.copy(editor.graph);
+ }
+ });
+
+ this.addAction('paste', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ mxClipboard.paste(editor.graph);
+ }
+ });
+
+ this.addAction('delete', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.removeCells();
+ }
+ });
+
+ this.addAction('group', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.setSelectionCell(editor.groupCells());
+ }
+ });
+
+ this.addAction('ungroup', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.setSelectionCells(editor.graph.ungroupCells());
+ }
+ });
+
+ this.addAction('removeFromParent', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.removeCellsFromParent();
+ }
+ });
+
+ this.addAction('undo', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.undo();
+ }
+ });
+
+ this.addAction('redo', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.redo();
+ }
+ });
+
+ this.addAction('zoomIn', function(editor)
+ {
+ editor.graph.zoomIn();
+ });
+
+ this.addAction('zoomOut', function(editor)
+ {
+ editor.graph.zoomOut();
+ });
+
+ this.addAction('actualSize', function(editor)
+ {
+ editor.graph.zoomActual();
+ });
+
+ this.addAction('fit', function(editor)
+ {
+ editor.graph.fit();
+ });
+
+ this.addAction('showProperties', function(editor, cell)
+ {
+ editor.showProperties(cell);
+ });
+
+ this.addAction('selectAll', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.selectAll();
+ }
+ });
+
+ this.addAction('selectNone', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.clearSelection();
+ }
+ });
+
+ this.addAction('selectVertices', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.selectVertices();
+ }
+ });
+
+ this.addAction('selectEdges', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.selectEdges();
+ }
+ });
+
+ this.addAction('edit', function(editor, cell)
+ {
+ if (editor.graph.isEnabled() &&
+ editor.graph.isCellEditable(cell))
+ {
+ editor.graph.startEditingAtCell(cell);
+ }
+ });
+
+ this.addAction('toBack', function(editor, cell)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.orderCells(true);
+ }
+ });
+
+ this.addAction('toFront', function(editor, cell)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.orderCells(false);
+ }
+ });
+
+ this.addAction('enterGroup', function(editor, cell)
+ {
+ editor.graph.enterGroup(cell);
+ });
+
+ this.addAction('exitGroup', function(editor)
+ {
+ editor.graph.exitGroup();
+ });
+
+ this.addAction('home', function(editor)
+ {
+ editor.graph.home();
+ });
+
+ this.addAction('selectPrevious', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.selectPreviousCell();
+ }
+ });
+
+ this.addAction('selectNext', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.selectNextCell();
+ }
+ });
+
+ this.addAction('selectParent', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.selectParentCell();
+ }
+ });
+
+ this.addAction('selectChild', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.selectChildCell();
+ }
+ });
+
+ this.addAction('collapse', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.foldCells(true);
+ }
+ });
+
+ this.addAction('collapseAll', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ var cells = editor.graph.getChildVertices();
+ editor.graph.foldCells(true, false, cells);
+ }
+ });
+
+ this.addAction('expand', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.foldCells(false);
+ }
+ });
+
+ this.addAction('expandAll', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ var cells = editor.graph.getChildVertices();
+ editor.graph.foldCells(false, false, cells);
+ }
+ });
+
+ this.addAction('bold', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.toggleCellStyleFlags(
+ mxConstants.STYLE_FONTSTYLE,
+ mxConstants.FONT_BOLD);
+ }
+ });
+
+ this.addAction('italic', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.toggleCellStyleFlags(
+ mxConstants.STYLE_FONTSTYLE,
+ mxConstants.FONT_ITALIC);
+ }
+ });
+
+ this.addAction('underline', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.toggleCellStyleFlags(
+ mxConstants.STYLE_FONTSTYLE,
+ mxConstants.FONT_UNDERLINE);
+ }
+ });
+
+ this.addAction('shadow', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.toggleCellStyleFlags(
+ mxConstants.STYLE_FONTSTYLE,
+ mxConstants.FONT_SHADOW);
+ }
+ });
+
+ this.addAction('alignCellsLeft', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.alignCells(mxConstants.ALIGN_LEFT);
+ }
+ });
+
+ this.addAction('alignCellsCenter', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.alignCells(mxConstants.ALIGN_CENTER);
+ }
+ });
+
+ this.addAction('alignCellsRight', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.alignCells(mxConstants.ALIGN_RIGHT);
+ }
+ });
+
+ this.addAction('alignCellsTop', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.alignCells(mxConstants.ALIGN_TOP);
+ }
+ });
+
+ this.addAction('alignCellsMiddle', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.alignCells(mxConstants.ALIGN_MIDDLE);
+ }
+ });
+
+ this.addAction('alignCellsBottom', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.alignCells(mxConstants.ALIGN_BOTTOM);
+ }
+ });
+
+ this.addAction('alignFontLeft', function(editor)
+ {
+
+ editor.graph.setCellStyles(
+ mxConstants.STYLE_ALIGN,
+ mxConstants.ALIGN_LEFT);
+ });
+
+ this.addAction('alignFontCenter', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.setCellStyles(
+ mxConstants.STYLE_ALIGN,
+ mxConstants.ALIGN_CENTER);
+ }
+ });
+
+ this.addAction('alignFontRight', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.setCellStyles(
+ mxConstants.STYLE_ALIGN,
+ mxConstants.ALIGN_RIGHT);
+ }
+ });
+
+ this.addAction('alignFontTop', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.setCellStyles(
+ mxConstants.STYLE_VERTICAL_ALIGN,
+ mxConstants.ALIGN_TOP);
+ }
+ });
+
+ this.addAction('alignFontMiddle', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.setCellStyles(
+ mxConstants.STYLE_VERTICAL_ALIGN,
+ mxConstants.ALIGN_MIDDLE);
+ }
+ });
+
+ this.addAction('alignFontBottom', function(editor)
+ {
+ if (editor.graph.isEnabled())
+ {
+ editor.graph.setCellStyles(
+ mxConstants.STYLE_VERTICAL_ALIGN,
+ mxConstants.ALIGN_BOTTOM);
+ }
+ });
+
+ this.addAction('zoom', function(editor)
+ {
+ var current = editor.graph.getView().scale*100;
+ var scale = parseFloat(mxUtils.prompt(
+ mxResources.get(editor.askZoomResource) ||
+ editor.askZoomResource,
+ current))/100;
+
+ if (!isNaN(scale))
+ {
+ editor.graph.getView().setScale(scale);
+ }
+ });
+
+ this.addAction('toggleTasks', function(editor)
+ {
+ if (editor.tasks != null)
+ {
+ editor.tasks.setVisible(!editor.tasks.isVisible());
+ }
+ else
+ {
+ editor.showTasks();
+ }
+ });
+
+ this.addAction('toggleHelp', function(editor)
+ {
+ if (editor.help != null)
+ {
+ editor.help.setVisible(!editor.help.isVisible());
+ }
+ else
+ {
+ editor.showHelp();
+ }
+ });
+
+ this.addAction('toggleOutline', function(editor)
+ {
+ if (editor.outline == null)
+ {
+ editor.showOutline();
+ }
+ else
+ {
+ editor.outline.setVisible(!editor.outline.isVisible());
+ }
+ });
+
+ this.addAction('toggleConsole', function(editor)
+ {
+ mxLog.setVisible(!mxLog.isVisible());
+ });
+};
+
+/**
+ * Function: createSession
+ *
+ * Creates and returns and <mxSession> using <urlInit>, <urlPoll> and <urlNotify>.
+ */
+mxEditor.prototype.createSession = function ()
+{
+ // Routes any change events from the session
+ // through the editor and dispatches them as
+ // a session event.
+ var sessionChanged = mxUtils.bind(this, function(session)
+ {
+ this.fireEvent(new mxEventObject(mxEvent.SESSION, 'session', session));
+ });
+
+ return this.connect(this.urlInit, this.urlPoll,
+ this.urlNotify, sessionChanged);
+};
+
+/**
+ * Function: configure
+ *
+ * Configures the editor using the specified node. To load the
+ * configuration from a given URL the following code can be used to obtain
+ * the XML node.
+ *
+ * (code)
+ * var node = mxUtils.load(url).getDocumentElement();
+ * (end)
+ *
+ * Parameters:
+ *
+ * node - XML node that contains the configuration.
+ */
+mxEditor.prototype.configure = function (node)
+{
+ if (node != null)
+ {
+ // Creates a decoder for the XML data
+ // and uses it to configure the editor
+ var dec = new mxCodec(node.ownerDocument);
+ dec.decode(node, this);
+
+ // Resets the counters, modified state and
+ // command history
+ this.resetHistory();
+ }
+};
+
+/**
+ * Function: resetFirstTime
+ *
+ * Resets the cookie that is used to remember if the editor has already
+ * been used.
+ */
+mxEditor.prototype.resetFirstTime = function ()
+{
+ document.cookie =
+ 'mxgraph=seen; expires=Fri, 27 Jul 2001 02:47:11 UTC; path=/';
+};
+
+/**
+ * Function: resetHistory
+ *
+ * Resets the command history, modified state and counters.
+ */
+mxEditor.prototype.resetHistory = function ()
+{
+ this.lastSnapshot = new Date().getTime();
+ this.undoManager.clear();
+ this.ignoredChanges = 0;
+ this.setModified(false);
+};
+
+/**
+ * Function: addAction
+ *
+ * Binds the specified actionname to the specified function.
+ *
+ * Parameters:
+ *
+ * actionname - String that specifies the name of the action
+ * to be added.
+ * funct - Function that implements the new action. The first
+ * argument of the function is the editor it is used
+ * with, the second argument is the cell it operates
+ * upon.
+ *
+ * Example:
+ * (code)
+ * editor.addAction('test', function(editor, cell)
+ * {
+ * mxUtils.alert("test "+cell);
+ * });
+ * (end)
+ */
+mxEditor.prototype.addAction = function (actionname, funct)
+{
+ this.actions[actionname] = funct;
+};
+
+/**
+ * Function: execute
+ *
+ * Executes the function with the given name in <actions> passing the
+ * editor instance and given cell as the first and second argument. All
+ * additional arguments are passed to the action as well. This method
+ * contains a try-catch block and displays an error message if an action
+ * causes an exception. The exception is re-thrown after the error
+ * message was displayed.
+ *
+ * Example:
+ *
+ * (code)
+ * editor.execute("showProperties", cell);
+ * (end)
+ */
+mxEditor.prototype.execute = function (actionname, cell, evt)
+{
+ var action = this.actions[actionname];
+
+ if (action != null)
+ {
+ try
+ {
+ // Creates the array of arguments by replacing the actionname
+ // with the editor instance in the args of this function
+ var args = arguments;
+ args[0] = this;
+
+ // Invokes the function on the editor using the args
+ action.apply(this, args);
+ }
+ catch (e)
+ {
+ mxUtils.error('Cannot execute ' + actionname +
+ ': ' + e.message, 280, true);
+
+ throw e;
+ }
+ }
+ else
+ {
+ mxUtils.error('Cannot find action '+actionname, 280, true);
+ }
+};
+
+/**
+ * Function: addTemplate
+ *
+ * Adds the specified template under the given name in <templates>.
+ */
+mxEditor.prototype.addTemplate = function (name, template)
+{
+ this.templates[name] = template;
+};
+
+/**
+ * Function: getTemplate
+ *
+ * Returns the template for the given name.
+ */
+mxEditor.prototype.getTemplate = function (name)
+{
+ return this.templates[name];
+};
+
+/**
+ * Function: createGraph
+ *
+ * Creates the <graph> for the editor. The graph is created with no
+ * container and is initialized from <setGraphContainer>.
+ */
+mxEditor.prototype.createGraph = function ()
+{
+ var graph = new mxGraph(null, null, this.graphRenderHint);
+
+ // Enables rubberband, tooltips, panning
+ graph.setTooltips(true);
+ graph.setPanning(true);
+
+ // Overrides the dblclick method on the graph to
+ // invoke the dblClickAction for a cell and reset
+ // the selection tool in the toolbar
+ this.installDblClickHandler(graph);
+
+ // Installs the command history
+ this.installUndoHandler(graph);
+
+ // Installs the handlers for the root event
+ this.installDrillHandler(graph);
+
+ // Installs the handler for validation
+ this.installChangeHandler(graph);
+
+ // Installs the handler for calling the
+ // insert function and consume the
+ // event if an insert function is defined
+ this.installInsertHandler(graph);
+
+ // Redirects the function for creating the
+ // popupmenu items
+ graph.panningHandler.factoryMethod =
+ mxUtils.bind(this, function(menu, cell, evt)
+ {
+ return this.createPopupMenu(menu, cell, evt);
+ });
+
+ // Redirects the function for creating
+ // new connections in the diagram
+ graph.connectionHandler.factoryMethod =
+ mxUtils.bind(this, function(source, target)
+ {
+ return this.createEdge(source, target);
+ });
+
+ // Maintains swimlanes and installs autolayout
+ this.createSwimlaneManager(graph);
+ this.createLayoutManager(graph);
+
+ return graph;
+};
+
+/**
+ * Function: createSwimlaneManager
+ *
+ * Sets the graph's container using <mxGraph.init>.
+ */
+mxEditor.prototype.createSwimlaneManager = function (graph)
+{
+ var swimlaneMgr = new mxSwimlaneManager(graph, false);
+
+ swimlaneMgr.isHorizontal = mxUtils.bind(this, function()
+ {
+ return this.horizontalFlow;
+ });
+
+ swimlaneMgr.isEnabled = mxUtils.bind(this, function()
+ {
+ return this.maintainSwimlanes;
+ });
+
+ return swimlaneMgr;
+};
+
+/**
+ * Function: createLayoutManager
+ *
+ * Creates a layout manager for the swimlane and diagram layouts, that
+ * is, the locally defined inter- and intraswimlane layouts.
+ */
+mxEditor.prototype.createLayoutManager = function (graph)
+{
+ var layoutMgr = new mxLayoutManager(graph);
+
+ var self = this; // closure
+ layoutMgr.getLayout = function(cell)
+ {
+ var layout = null;
+ var model = self.graph.getModel();
+
+ if (model.getParent(cell) != null)
+ {
+ // Executes the swimlane layout if a child of
+ // a swimlane has been changed. The layout is
+ // lazy created in createSwimlaneLayout.
+ if (self.layoutSwimlanes &&
+ graph.isSwimlane(cell))
+ {
+ if (self.swimlaneLayout == null)
+ {
+ self.swimlaneLayout = self.createSwimlaneLayout();
+ }
+
+ layout = self.swimlaneLayout;
+ }
+
+ // Executes the diagram layout if the modified
+ // cell is a top-level cell. The layout is
+ // lazy created in createDiagramLayout.
+ else if (self.layoutDiagram &&
+ (graph.isValidRoot(cell) ||
+ model.getParent(model.getParent(cell)) == null))
+ {
+ if (self.diagramLayout == null)
+ {
+ self.diagramLayout = self.createDiagramLayout();
+ }
+
+ layout = self.diagramLayout;
+ }
+ }
+
+ return layout;
+ };
+
+ return layoutMgr;
+};
+
+/**
+ * Function: setGraphContainer
+ *
+ * Sets the graph's container using <mxGraph.init>.
+ */
+mxEditor.prototype.setGraphContainer = function (container)
+{
+ if (this.graph.container == null)
+ {
+ // Creates the graph instance inside the given container and render hint
+ //this.graph = new mxGraph(container, null, this.graphRenderHint);
+ this.graph.init(container);
+
+ // Install rubberband selection as the last
+ // action handler in the chain
+ this.rubberband = new mxRubberband(this.graph);
+
+ // Disables the context menu
+ if (this.disableContextMenu)
+ {
+ mxEvent.disableContextMenu(container);
+ }
+
+ // Workaround for stylesheet directives in IE
+ if (mxClient.IS_QUIRKS)
+ {
+ new mxDivResizer(container);
+ }
+ }
+};
+
+/**
+ * Function: installDblClickHandler
+ *
+ * Overrides <mxGraph.dblClick> to invoke <dblClickAction>
+ * on a cell and reset the selection tool in the toolbar.
+ */
+mxEditor.prototype.installDblClickHandler = function (graph)
+{
+ // Installs a listener for double click events
+ graph.addListener(mxEvent.DOUBLE_CLICK,
+ mxUtils.bind(this, function(sender, evt)
+ {
+ var cell = evt.getProperty('cell');
+
+ if (cell != null &&
+ graph.isEnabled() &&
+ this.dblClickAction != null)
+ {
+ this.execute(this.dblClickAction, cell);
+ evt.consume();
+ }
+ })
+ );
+};
+
+/**
+ * Function: installUndoHandler
+ *
+ * Adds the <undoManager> to the graph model and the view.
+ */
+mxEditor.prototype.installUndoHandler = function (graph)
+{
+ var listener = mxUtils.bind(this, function(sender, evt)
+ {
+ var edit = evt.getProperty('edit');
+ this.undoManager.undoableEditHappened(edit);
+ });
+
+ graph.getModel().addListener(mxEvent.UNDO, listener);
+ graph.getView().addListener(mxEvent.UNDO, listener);
+
+ // Keeps the selection state in sync
+ var undoHandler = function(sender, evt)
+ {
+ var changes = evt.getProperty('edit').changes;
+ graph.setSelectionCells(graph.getSelectionCellsForChanges(changes));
+ };
+
+ this.undoManager.addListener(mxEvent.UNDO, undoHandler);
+ this.undoManager.addListener(mxEvent.REDO, undoHandler);
+};
+
+/**
+ * Function: installDrillHandler
+ *
+ * Installs listeners for dispatching the <root> event.
+ */
+mxEditor.prototype.installDrillHandler = function (graph)
+{
+ var listener = mxUtils.bind(this, function(sender)
+ {
+ this.fireEvent(new mxEventObject(mxEvent.ROOT));
+ });
+
+ graph.getView().addListener(mxEvent.DOWN, listener);
+ graph.getView().addListener(mxEvent.UP, listener);
+};
+
+/**
+ * Function: installChangeHandler
+ *
+ * Installs the listeners required to automatically validate
+ * the graph. On each change of the root, this implementation
+ * fires a <root> event.
+ */
+mxEditor.prototype.installChangeHandler = function (graph)
+{
+ var listener = mxUtils.bind(this, function(sender, evt)
+ {
+ // Updates the modified state
+ this.setModified(true);
+
+ // Automatically validates the graph
+ // after each change
+ if (this.validating == true)
+ {
+ graph.validateGraph();
+ }
+
+ // Checks if the root has been changed
+ var changes = evt.getProperty('edit').changes;
+
+ for (var i = 0; i < changes.length; i++)
+ {
+ var change = changes[i];
+
+ if (change instanceof mxRootChange ||
+ (change instanceof mxValueChange &&
+ change.cell == this.graph.model.root) ||
+ (change instanceof mxCellAttributeChange &&
+ change.cell == this.graph.model.root))
+ {
+ this.fireEvent(new mxEventObject(mxEvent.ROOT));
+ break;
+ }
+ }
+ });
+
+ graph.getModel().addListener(mxEvent.CHANGE, listener);
+};
+
+/**
+ * Function: installInsertHandler
+ *
+ * Installs the handler for invoking <insertFunction> if
+ * one is defined.
+ */
+mxEditor.prototype.installInsertHandler = function (graph)
+{
+ var self = this; // closure
+ var insertHandler =
+ {
+ mouseDown: function(sender, me)
+ {
+ if (self.insertFunction != null &&
+ !me.isPopupTrigger() &&
+ (self.forcedInserting ||
+ me.getState() == null))
+ {
+ self.graph.clearSelection();
+ self.insertFunction(me.getEvent(), me.getCell());
+
+ // Consumes the rest of the events
+ // for this gesture (down, move, up)
+ this.isActive = true;
+ me.consume();
+ }
+ },
+
+ mouseMove: function(sender, me)
+ {
+ if (this.isActive)
+ {
+ me.consume();
+ }
+ },
+
+ mouseUp: function(sender, me)
+ {
+ if (this.isActive)
+ {
+ this.isActive = false;
+ me.consume();
+ }
+ }
+ };
+
+ graph.addMouseListener(insertHandler);
+};
+
+/**
+ * Function: createDiagramLayout
+ *
+ * Creates the layout instance used to layout the
+ * swimlanes in the diagram.
+ */
+mxEditor.prototype.createDiagramLayout = function ()
+{
+ var gs = this.graph.gridSize;
+ var layout = new mxStackLayout(this.graph, !this.horizontalFlow,
+ this.swimlaneSpacing, 2*gs, 2*gs);
+
+ // Overrides isIgnored to only take into account swimlanes
+ layout.isVertexIgnored = function(cell)
+ {
+ return !layout.graph.isSwimlane(cell);
+ };
+
+ return layout;
+};
+
+/**
+ * Function: createSwimlaneLayout
+ *
+ * Creates the layout instance used to layout the
+ * children of each swimlane.
+ */
+mxEditor.prototype.createSwimlaneLayout = function ()
+{
+ return new mxCompactTreeLayout(this.graph, this.horizontalFlow);
+};
+
+/**
+ * Function: createToolbar
+ *
+ * Creates the <toolbar> with no container.
+ */
+mxEditor.prototype.createToolbar = function ()
+{
+ return new mxDefaultToolbar(null, this);
+};
+
+/**
+ * Function: setToolbarContainer
+ *
+ * Initializes the toolbar for the given container.
+ */
+mxEditor.prototype.setToolbarContainer = function (container)
+{
+ this.toolbar.init(container);
+
+ // Workaround for stylesheet directives in IE
+ if (mxClient.IS_QUIRKS)
+ {
+ new mxDivResizer(container);
+ }
+};
+
+/**
+ * Function: setStatusContainer
+ *
+ * Creates the <status> using the specified container.
+ *
+ * This implementation adds listeners in the editor to
+ * display the last saved time and the current filename
+ * in the status bar.
+ *
+ * Parameters:
+ *
+ * container - DOM node that will contain the statusbar.
+ */
+mxEditor.prototype.setStatusContainer = function (container)
+{
+ if (this.status == null)
+ {
+ this.status = container;
+
+ // Prints the last saved time in the status bar
+ // when files are saved
+ this.addListener(mxEvent.SAVE, mxUtils.bind(this, function()
+ {
+ var tstamp = new Date().toLocaleString();
+ this.setStatus((mxResources.get(this.lastSavedResource) ||
+ this.lastSavedResource)+': '+tstamp);
+ }));
+
+ // Updates the statusbar to display the filename
+ // when new files are opened
+ this.addListener(mxEvent.OPEN, mxUtils.bind(this, function()
+ {
+ this.setStatus((mxResources.get(this.currentFileResource) ||
+ this.currentFileResource)+': '+this.filename);
+ }));
+
+ // Workaround for stylesheet directives in IE
+ if (mxClient.IS_QUIRKS)
+ {
+ new mxDivResizer(container);
+ }
+ }
+};
+
+/**
+ * Function: setStatus
+ *
+ * Display the specified message in the status bar.
+ *
+ * Parameters:
+ *
+ * message - String the specified the message to
+ * be displayed.
+ */
+mxEditor.prototype.setStatus = function (message)
+{
+ if (this.status != null && message != null)
+ {
+ this.status.innerHTML = message;
+ }
+};
+
+/**
+ * Function: setTitleContainer
+ *
+ * Creates a listener to update the inner HTML of the
+ * specified DOM node with the value of <getTitle>.
+ *
+ * Parameters:
+ *
+ * container - DOM node that will contain the title.
+ */
+mxEditor.prototype.setTitleContainer = function (container)
+{
+ this.addListener(mxEvent.ROOT, mxUtils.bind(this, function(sender)
+ {
+ container.innerHTML = this.getTitle();
+ }));
+
+ // Workaround for stylesheet directives in IE
+ if (mxClient.IS_QUIRKS)
+ {
+ new mxDivResizer(container);
+ }
+};
+
+/**
+ * Function: treeLayout
+ *
+ * Executes a vertical or horizontal compact tree layout
+ * using the specified cell as an argument. The cell may
+ * either be a group or the root of a tree.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to use in the compact tree layout.
+ * horizontal - Optional boolean to specify the tree's
+ * orientation. Default is true.
+ */
+mxEditor.prototype.treeLayout = function (cell, horizontal)
+{
+ if (cell != null)
+ {
+ var layout = new mxCompactTreeLayout(this.graph, horizontal);
+ layout.execute(cell);
+ }
+};
+
+/**
+ * Function: getTitle
+ *
+ * Returns the string value for the current root of the
+ * diagram.
+ */
+mxEditor.prototype.getTitle = function ()
+{
+ var title = '';
+ var graph = this.graph;
+ var cell = graph.getCurrentRoot();
+
+ while (cell != null &&
+ graph.getModel().getParent(
+ graph.getModel().getParent(cell)) != null)
+ {
+ // Append each label of a valid root
+ if (graph.isValidRoot(cell))
+ {
+ title = ' > ' +
+ graph.convertValueToString(cell) + title;
+ }
+
+ cell = graph.getModel().getParent(cell);
+ }
+
+ var prefix = this.getRootTitle();
+
+ return prefix + title;
+};
+
+/**
+ * Function: getRootTitle
+ *
+ * Returns the string value of the root cell in
+ * <mxGraph.model>.
+ */
+mxEditor.prototype.getRootTitle = function ()
+{
+ var root = this.graph.getModel().getRoot();
+ return this.graph.convertValueToString(root);
+};
+
+/**
+ * Function: undo
+ *
+ * Undo the last change in <graph>.
+ */
+mxEditor.prototype.undo = function ()
+{
+ this.undoManager.undo();
+};
+
+/**
+ * Function: redo
+ *
+ * Redo the last change in <graph>.
+ */
+mxEditor.prototype.redo = function ()
+{
+ this.undoManager.redo();
+};
+
+/**
+ * Function: groupCells
+ *
+ * Invokes <createGroup> to create a new group cell and the invokes
+ * <mxGraph.groupCells>, using the grid size of the graph as the spacing
+ * in the group's content area.
+ */
+mxEditor.prototype.groupCells = function ()
+{
+ var border = (this.groupBorderSize != null) ?
+ this.groupBorderSize :
+ this.graph.gridSize;
+ return this.graph.groupCells(this.createGroup(), border);
+};
+
+/**
+ * Function: createGroup
+ *
+ * Creates and returns a clone of <defaultGroup> to be used
+ * as a new group cell in <group>.
+ */
+mxEditor.prototype.createGroup = function ()
+{
+ var model = this.graph.getModel();
+
+ return model.cloneCell(this.defaultGroup);
+};
+
+/**
+ * Function: open
+ *
+ * Opens the specified file synchronously and parses it using
+ * <readGraphModel>. It updates <filename> and fires an <open>-event after
+ * the file has been opened. Exceptions should be handled as follows:
+ *
+ * (code)
+ * try
+ * {
+ * editor.open(filename);
+ * }
+ * catch (e)
+ * {
+ * mxUtils.error('Cannot open ' + filename +
+ * ': ' + e.message, 280, true);
+ * }
+ * (end)
+ *
+ * Parameters:
+ *
+ * filename - URL of the file to be opened.
+ */
+mxEditor.prototype.open = function (filename)
+{
+ if (filename != null)
+ {
+ var xml = mxUtils.load(filename).getXml();
+ this.readGraphModel(xml.documentElement);
+ this.filename = filename;
+
+ this.fireEvent(new mxEventObject(mxEvent.OPEN, 'filename', filename));
+ }
+};
+
+/**
+ * Function: readGraphModel
+ *
+ * Reads the specified XML node into the existing graph model and resets
+ * the command history and modified state.
+ */
+mxEditor.prototype.readGraphModel = function (node)
+{
+ var dec = new mxCodec(node.ownerDocument);
+ dec.decode(node, this.graph.getModel());
+ this.resetHistory();
+};
+
+/**
+ * Function: save
+ *
+ * Posts the string returned by <writeGraphModel> to the given URL or the
+ * URL returned by <getUrlPost>. The actual posting is carried out by
+ * <postDiagram>. If the URL is null then the resulting XML will be
+ * displayed using <mxUtils.popup>. Exceptions should be handled as
+ * follows:
+ *
+ * (code)
+ * try
+ * {
+ * editor.save();
+ * }
+ * catch (e)
+ * {
+ * mxUtils.error('Cannot save : ' + e.message, 280, true);
+ * }
+ * (end)
+ */
+mxEditor.prototype.save = function (url, linefeed)
+{
+ // Gets the URL to post the data to
+ url = url || this.getUrlPost();
+
+ // Posts the data if the URL is not empty
+ if (url != null && url.length > 0)
+ {
+ var data = this.writeGraphModel(linefeed);
+ this.postDiagram(url, data);
+
+ // Resets the modified flag
+ this.setModified(false);
+ }
+
+ // Dispatches a save event
+ this.fireEvent(new mxEventObject(mxEvent.SAVE, 'url', url));
+};
+
+/**
+ * Function: postDiagram
+ *
+ * Hook for subclassers to override the posting of a diagram
+ * represented by the given node to the given URL. This fires
+ * an asynchronous <post> event if the diagram has been posted.
+ *
+ * Example:
+ *
+ * To replace the diagram with the diagram in the response, use the
+ * following code.
+ *
+ * (code)
+ * editor.addListener(mxEvent.POST, function(sender, evt)
+ * {
+ * // Process response (replace diagram)
+ * var req = evt.getProperty('request');
+ * var root = req.getDocumentElement();
+ * editor.graph.readGraphModel(root)
+ * });
+ * (end)
+ */
+mxEditor.prototype.postDiagram = function (url, data)
+{
+ if (this.escapePostData)
+ {
+ data = encodeURIComponent(data);
+ }
+
+ mxUtils.post(url, this.postParameterName+'='+data,
+ mxUtils.bind(this, function(req)
+ {
+ this.fireEvent(new mxEventObject(mxEvent.POST,
+ 'request', req, 'url', url, 'data', data));
+ })
+ );
+};
+
+/**
+ * Function: writeGraphModel
+ *
+ * Hook to create the string representation of the diagram. The default
+ * implementation uses an <mxCodec> to encode the graph model as
+ * follows:
+ *
+ * (code)
+ * var enc = new mxCodec();
+ * var node = enc.encode(this.graph.getModel());
+ * return mxUtils.getXml(node, this.linefeed);
+ * (end)
+ *
+ * Parameters:
+ *
+ * linefeed - Optional character to be used as the linefeed. Default is
+ * <linefeed>.
+ */
+mxEditor.prototype.writeGraphModel = function (linefeed)
+{
+ linefeed = (linefeed != null) ? linefeed : this.linefeed;
+ var enc = new mxCodec();
+ var node = enc.encode(this.graph.getModel());
+
+ return mxUtils.getXml(node, linefeed);
+};
+
+/**
+ * Function: getUrlPost
+ *
+ * Returns the URL to post the diagram to. This is used
+ * in <save>. The default implementation returns <urlPost>,
+ * adding <code>?draft=true</code>.
+ */
+mxEditor.prototype.getUrlPost = function ()
+{
+ return this.urlPost;
+};
+
+/**
+ * Function: getUrlImage
+ *
+ * Returns the URL to create the image with. This is typically
+ * the URL of a backend which accepts an XML representation
+ * of a graph view to create an image. The function is used
+ * in the image action to create an image. This implementation
+ * returns <urlImage>.
+ */
+mxEditor.prototype.getUrlImage = function ()
+{
+ return this.urlImage;
+};
+
+/**
+ * Function: connect
+ *
+ * Creates and returns a session for the specified parameters, installing
+ * the onChange function as a change listener for the session.
+ */
+mxEditor.prototype.connect = function (urlInit, urlPoll, urlNotify, onChange)
+{
+ var session = null;
+
+ if (!mxClient.IS_LOCAL)
+ {
+ session = new mxSession(this.graph.getModel(),
+ urlInit, urlPoll, urlNotify);
+
+ // Resets the undo history if the session was initialized which is the
+ // case if the message carries a namespace to be used for new IDs.
+ session.addListener(mxEvent.RECEIVE,
+ mxUtils.bind(this, function(sender, evt)
+ {
+ var node = evt.getProperty('node');
+
+ if (node.getAttribute('namespace') != null)
+ {
+ this.resetHistory();
+ }
+ })
+ );
+
+ // Installs the listener for all events
+ // that signal a change of the session
+ session.addListener(mxEvent.DISCONNECT, onChange);
+ session.addListener(mxEvent.CONNECT, onChange);
+ session.addListener(mxEvent.NOTIFY, onChange);
+ session.addListener(mxEvent.GET, onChange);
+ session.start();
+ }
+
+ return session;
+};
+
+/**
+ * Function: swapStyles
+ *
+ * Swaps the styles for the given names in the graph's
+ * stylesheet and refreshes the graph.
+ */
+mxEditor.prototype.swapStyles = function (first, second)
+{
+ var style = this.graph.getStylesheet().styles[second];
+ this.graph.getView().getStylesheet().putCellStyle(
+ second, this.graph.getStylesheet().styles[first]);
+ this.graph.getStylesheet().putCellStyle(first, style);
+ this.graph.refresh();
+};
+
+/**
+ * Function: showProperties
+ *
+ * Creates and shows the properties dialog for the given
+ * cell. The content area of the dialog is created using
+ * <createProperties>.
+ */
+mxEditor.prototype.showProperties = function (cell)
+{
+ cell = cell || this.graph.getSelectionCell();
+
+ // Uses the root node for the properties dialog
+ // if not cell was passed in and no cell is
+ // selected
+ if (cell == null)
+ {
+ cell = this.graph.getCurrentRoot();
+
+ if (cell == null)
+ {
+ cell = this.graph.getModel().getRoot();
+ }
+ }
+
+ if (cell != null)
+ {
+ // Makes sure there is no in-place editor in the
+ // graph and computes the location of the dialog
+ this.graph.stopEditing(true);
+
+ var offset = mxUtils.getOffset(this.graph.container);
+ var x = offset.x+10;
+ var y = offset.y;
+
+ // Avoids moving the dialog if it is alredy open
+ if (this.properties != null && !this.movePropertiesDialog)
+ {
+ x = this.properties.getX();
+ y = this.properties.getY();
+ }
+
+ // Places the dialog near the cell for which it
+ // displays the properties
+ else
+ {
+ var bounds = this.graph.getCellBounds(cell);
+
+ if (bounds != null)
+ {
+ x += bounds.x+Math.min(200, bounds.width);
+ y += bounds.y;
+ }
+ }
+
+ // Hides the existing properties dialog and creates a new one with the
+ // contents created in the hook method
+ this.hideProperties();
+ var node = this.createProperties(cell);
+
+ if (node != null)
+ {
+ // Displays the contents in a window and stores a reference to the
+ // window for later hiding of the window
+ this.properties = new mxWindow(mxResources.get(this.propertiesResource) ||
+ this.propertiesResource, node, x, y, this.propertiesWidth, this.propertiesHeight, false);
+ this.properties.setVisible(true);
+ }
+ }
+};
+
+/**
+ * Function: isPropertiesVisible
+ *
+ * Returns true if the properties dialog is currently visible.
+ */
+mxEditor.prototype.isPropertiesVisible = function ()
+{
+ return this.properties != null;
+};
+
+/**
+ * Function: createProperties
+ *
+ * Creates and returns the DOM node that represents the contents
+ * of the properties dialog for the given cell. This implementation
+ * works for user objects that are XML nodes and display all the
+ * node attributes in a form.
+ */
+mxEditor.prototype.createProperties = function (cell)
+{
+ var model = this.graph.getModel();
+ var value = model.getValue(cell);
+
+ if (mxUtils.isNode(value))
+ {
+ // Creates a form for the user object inside
+ // the cell
+ var form = new mxForm('properties');
+
+ // Adds a readonly field for the cell id
+ var id = form.addText('ID', cell.getId());
+ id.setAttribute('readonly', 'true');
+
+ var geo = null;
+ var yField = null;
+ var xField = null;
+ var widthField = null;
+ var heightField = null;
+
+ // Adds fields for the location and size
+ if (model.isVertex(cell))
+ {
+ geo = model.getGeometry(cell);
+
+ if (geo != null)
+ {
+ yField = form.addText('top', geo.y);
+ xField = form.addText('left', geo.x);
+ widthField = form.addText('width', geo.width);
+ heightField = form.addText('height', geo.height);
+ }
+ }
+
+ // Adds a field for the cell style
+ var tmp = model.getStyle(cell);
+ var style = form.addText('Style', tmp || '');
+
+ // Creates textareas for each attribute of the
+ // user object within the cell
+ var attrs = value.attributes;
+ var texts = [];
+
+ for (var i = 0; i < attrs.length; i++)
+ {
+ // Creates a textarea with more lines for
+ // the cell label
+ var val = attrs[i].nodeValue;
+ texts[i] = form.addTextarea(attrs[i].nodeName, val,
+ (attrs[i].nodeName == 'label') ? 4 : 2);
+ }
+
+ // Adds an OK and Cancel button to the dialog
+ // contents and implements the respective
+ // actions below
+
+ // Defines the function to be executed when the
+ // OK button is pressed in the dialog
+ var okFunction = mxUtils.bind(this, function()
+ {
+ // Hides the dialog
+ this.hideProperties();
+
+ // Supports undo for the changes on the underlying
+ // XML structure / XML node attribute changes.
+ model.beginUpdate();
+ try
+ {
+ if (geo != null)
+ {
+ geo = geo.clone();
+
+ geo.x = parseFloat(xField.value);
+ geo.y = parseFloat(yField.value);
+ geo.width = parseFloat(widthField.value);
+ geo.height = parseFloat(heightField.value);
+
+ model.setGeometry(cell, geo);
+ }
+
+ // Applies the style
+ if (style.value.length > 0)
+ {
+ model.setStyle(cell, style.value);
+ }
+ else
+ {
+ model.setStyle(cell, null);
+ }
+
+ // Creates an undoable change for each
+ // attribute and executes it using the
+ // model, which will also make the change
+ // part of the current transaction
+ for (var i=0; i<attrs.length; i++)
+ {
+ var edit = new mxCellAttributeChange(
+ cell, attrs[i].nodeName,
+ texts[i].value);
+ model.execute(edit);
+ }
+
+ // Checks if the graph wants cells to
+ // be automatically sized and updates
+ // the size as an undoable step if
+ // the feature is enabled
+ if (this.graph.isAutoSizeCell(cell))
+ {
+ this.graph.updateCellSize(cell);
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ });
+
+ // Defines the function to be executed when the
+ // Cancel button is pressed in the dialog
+ var cancelFunction = mxUtils.bind(this, function()
+ {
+ // Hides the dialog
+ this.hideProperties();
+ });
+
+ form.addButtons(okFunction, cancelFunction);
+
+ return form.table;
+ }
+
+ return null;
+};
+
+/**
+ * Function: hideProperties
+ *
+ * Hides the properties dialog.
+ */
+mxEditor.prototype.hideProperties = function ()
+{
+ if (this.properties != null)
+ {
+ this.properties.destroy();
+ this.properties = null;
+ }
+};
+
+/**
+ * Function: showTasks
+ *
+ * Shows the tasks window. The tasks window is created using <createTasks>. The
+ * default width of the window is 200 pixels, the y-coordinate of the location
+ * can be specifies in <tasksTop> and the x-coordinate is right aligned with a
+ * 20 pixel offset from the right border. To change the location of the tasks
+ * window, the following code can be used:
+ *
+ * (code)
+ * var oldShowTasks = mxEditor.prototype.showTasks;
+ * mxEditor.prototype.showTasks = function()
+ * {
+ * oldShowTasks.apply(this, arguments); // "supercall"
+ *
+ * if (this.tasks != null)
+ * {
+ * this.tasks.setLocation(10, 10);
+ * }
+ * };
+ * (end)
+ */
+mxEditor.prototype.showTasks = function ()
+{
+ if (this.tasks == null)
+ {
+ var div = document.createElement('div');
+ div.style.padding = '4px';
+ div.style.paddingLeft = '20px';
+ var w = document.body.clientWidth;
+ var wnd = new mxWindow(
+ mxResources.get(this.tasksResource) ||
+ this.tasksResource,
+ div, w - 220, this.tasksTop, 200);
+ wnd.setClosable(true);
+ wnd.destroyOnClose = false;
+
+ // Installs a function to update the contents
+ // of the tasks window on every change of the
+ // model, selection or root.
+ var funct = mxUtils.bind(this, function(sender)
+ {
+ mxEvent.release(div);
+ div.innerHTML = '';
+ this.createTasks(div);
+ });
+
+ this.graph.getModel().addListener(mxEvent.CHANGE, funct);
+ this.graph.getSelectionModel().addListener(mxEvent.CHANGE, funct);
+ this.graph.addListener(mxEvent.ROOT, funct);
+
+ // Assigns the icon to the tasks window
+ if (this.tasksWindowImage != null)
+ {
+ wnd.setImage(this.tasksWindowImage);
+ }
+
+ this.tasks = wnd;
+ this.createTasks(div);
+ }
+
+ this.tasks.setVisible(true);
+};
+
+/**
+ * Function: refreshTasks
+ *
+ * Updates the contents of the tasks window using <createTasks>.
+ */
+mxEditor.prototype.refreshTasks = function (div)
+{
+ if (this.tasks != null)
+ {
+ var div = this.tasks.content;
+ mxEvent.release(div);
+ div.innerHTML = '';
+ this.createTasks(div);
+ }
+};
+
+/**
+ * Function: createTasks
+ *
+ * Updates the contents of the given DOM node to
+ * display the tasks associated with the current
+ * editor state. This is invoked whenever there
+ * is a possible change of state in the editor.
+ * Default implementation is empty.
+ */
+mxEditor.prototype.createTasks = function (div)
+{
+ // override
+};
+
+/**
+ * Function: showHelp
+ *
+ * Shows the help window. If the help window does not exist
+ * then it is created using an iframe pointing to the resource
+ * for the <code>urlHelp</code> key or <urlHelp> if the resource
+ * is undefined.
+ */
+mxEditor.prototype.showHelp = function (tasks)
+{
+ if (this.help == null)
+ {
+ var frame = document.createElement('iframe');
+ frame.setAttribute('src', mxResources.get('urlHelp') || this.urlHelp);
+ frame.setAttribute('height', '100%');
+ frame.setAttribute('width', '100%');
+ frame.setAttribute('frameBorder', '0');
+ frame.style.backgroundColor = 'white';
+
+ var w = document.body.clientWidth;
+ var h = (document.body.clientHeight || document.documentElement.clientHeight);
+
+ var wnd = new mxWindow(mxResources.get(this.helpResource) || this.helpResource,
+ frame, (w-this.helpWidth)/2, (h-this.helpHeight)/3, this.helpWidth, this.helpHeight);
+ wnd.setMaximizable(true);
+ wnd.setClosable(true);
+ wnd.destroyOnClose = false;
+ wnd.setResizable(true);
+
+ // Assigns the icon to the help window
+ if (this.helpWindowImage != null)
+ {
+ wnd.setImage(this.helpWindowImage);
+ }
+
+ // Workaround for ignored iframe height 100% in FF
+ if (mxClient.IS_NS)
+ {
+ var handler = function(sender)
+ {
+ var h = wnd.div.offsetHeight;
+ frame.setAttribute('height', (h-26)+'px');
+ };
+
+ wnd.addListener(mxEvent.RESIZE_END, handler);
+ wnd.addListener(mxEvent.MAXIMIZE, handler);
+ wnd.addListener(mxEvent.NORMALIZE, handler);
+ wnd.addListener(mxEvent.SHOW, handler);
+ }
+
+ this.help = wnd;
+ }
+
+ this.help.setVisible(true);
+};
+
+/**
+ * Function: showOutline
+ *
+ * Shows the outline window. If the window does not exist, then it is
+ * created using an <mxOutline>.
+ */
+mxEditor.prototype.showOutline = function ()
+{
+ var create = this.outline == null;
+
+ if (create)
+ {
+ var div = document.createElement('div');
+ div.style.overflow = 'hidden';
+ div.style.width = '100%';
+ div.style.height = '100%';
+ div.style.background = 'white';
+ div.style.cursor = 'move';
+
+ var wnd = new mxWindow(
+ mxResources.get(this.outlineResource) ||
+ this.outlineResource,
+ div, 600, 480, 200, 200, false);
+
+ // Creates the outline in the specified div
+ // and links it to the existing graph
+ var outline = new mxOutline(this.graph, div);
+ wnd.setClosable(true);
+ wnd.setResizable(true);
+ wnd.destroyOnClose = false;
+
+ wnd.addListener(mxEvent.RESIZE_END, function()
+ {
+ outline.update();
+ });
+
+ this.outline = wnd;
+ this.outline.outline = outline;
+ }
+
+ // Finally shows the outline
+ this.outline.setVisible(true);
+ this.outline.outline.update(true);
+};
+
+/**
+ * Function: setMode
+ *
+ * Puts the graph into the specified mode. The following modenames are
+ * supported:
+ *
+ * select - Selects using the left mouse button, new connections
+ * are disabled.
+ * connect - Selects using the left mouse button or creates new
+ * connections if mouse over cell hotspot. See <mxConnectionHandler>.
+ * pan - Pans using the left mouse button, new connections are disabled.
+ */
+mxEditor.prototype.setMode = function(modename)
+{
+ if (modename == 'select')
+ {
+ this.graph.panningHandler.useLeftButtonForPanning = false;
+ this.graph.setConnectable(false);
+ }
+ else if (modename == 'connect')
+ {
+ this.graph.panningHandler.useLeftButtonForPanning = false;
+ this.graph.setConnectable(true);
+ }
+ else if (modename == 'pan')
+ {
+ this.graph.panningHandler.useLeftButtonForPanning = true;
+ this.graph.setConnectable(false);
+ }
+};
+
+/**
+ * Function: createPopupMenu
+ *
+ * Uses <popupHandler> to create the menu in the graph's
+ * panning handler. The redirection is setup in
+ * <setToolbarContainer>.
+ */
+mxEditor.prototype.createPopupMenu = function (menu, cell, evt)
+{
+ this.popupHandler.createMenu(this, menu, cell, evt);
+};
+
+/**
+ * Function: createEdge
+ *
+ * Uses <defaultEdge> as the prototype for creating new edges
+ * in the connection handler of the graph. The style of the
+ * edge will be overridden with the value returned by
+ * <getEdgeStyle>.
+ */
+mxEditor.prototype.createEdge = function (source, target)
+{
+ // Clones the defaultedge prototype
+ var e = null;
+
+ if (this.defaultEdge != null)
+ {
+ var model = this.graph.getModel();
+ e = model.cloneCell(this.defaultEdge);
+ }
+ else
+ {
+ e = new mxCell('');
+ e.setEdge(true);
+
+ var geo = new mxGeometry();
+ geo.relative = true;
+ e.setGeometry(geo);
+ }
+
+ // Overrides the edge style
+ var style = this.getEdgeStyle();
+
+ if (style != null)
+ {
+ e.setStyle(style);
+ }
+
+ return e;
+};
+
+/**
+ * Function: getEdgeStyle
+ *
+ * Returns a string identifying the style of new edges.
+ * The function is used in <createEdge> when new edges
+ * are created in the graph.
+ */
+mxEditor.prototype.getEdgeStyle = function ()
+{
+ return this.defaultEdgeStyle;
+};
+
+/**
+ * Function: consumeCycleAttribute
+ *
+ * Returns the next attribute in <cycleAttributeValues>
+ * or null, if not attribute should be used in the
+ * specified cell.
+ */
+mxEditor.prototype.consumeCycleAttribute = function (cell)
+{
+ return (this.cycleAttributeValues != null &&
+ this.cycleAttributeValues.length > 0 &&
+ this.graph.isSwimlane(cell)) ?
+ this.cycleAttributeValues[this.cycleAttributeIndex++ %
+ this.cycleAttributeValues.length] : null;
+};
+
+/**
+ * Function: cycleAttribute
+ *
+ * Uses the returned value from <consumeCycleAttribute>
+ * as the value for the <cycleAttributeName> key in
+ * the given cell's style.
+ */
+mxEditor.prototype.cycleAttribute = function (cell)
+{
+ if (this.cycleAttributeName != null)
+ {
+ var value = this.consumeCycleAttribute(cell);
+
+ if (value != null)
+ {
+ cell.setStyle(cell.getStyle()+';'+
+ this.cycleAttributeName+'='+value);
+ }
+ }
+};
+
+/**
+ * Function: addVertex
+ *
+ * Adds the given vertex as a child of parent at the specified
+ * x and y coordinate and fires an <addVertex> event.
+ */
+mxEditor.prototype.addVertex = function (parent, vertex, x, y)
+{
+ var model = this.graph.getModel();
+
+ while (parent != null && !this.graph.isValidDropTarget(parent))
+ {
+ parent = model.getParent(parent);
+ }
+
+ parent = (parent != null) ? parent : this.graph.getSwimlaneAt(x, y);
+ var scale = this.graph.getView().scale;
+
+ var geo = model.getGeometry(vertex);
+ var pgeo = model.getGeometry(parent);
+
+ if (this.graph.isSwimlane(vertex) &&
+ !this.graph.swimlaneNesting)
+ {
+ parent = null;
+ }
+ else if (parent == null && this.swimlaneRequired)
+ {
+ return null;
+ }
+ else if (parent != null && pgeo != null)
+ {
+ // Keeps vertex inside parent
+ var state = this.graph.getView().getState(parent);
+
+ if (state != null)
+ {
+ x -= state.origin.x * scale;
+ y -= state.origin.y * scale;
+
+ if (this.graph.isConstrainedMoving)
+ {
+ var width = geo.width;
+ var height = geo.height;
+ var tmp = state.x+state.width;
+
+ if (x+width > tmp)
+ {
+ x -= x+width - tmp;
+ }
+
+ tmp = state.y+state.height;
+
+ if (y+height > tmp)
+ {
+ y -= y+height - tmp;
+ }
+ }
+ }
+ else if (pgeo != null)
+ {
+ x -= pgeo.x*scale;
+ y -= pgeo.y*scale;
+ }
+ }
+
+ geo = geo.clone();
+ geo.x = this.graph.snap(x / scale -
+ this.graph.getView().translate.x -
+ this.graph.gridSize/2);
+ geo.y = this.graph.snap(y / scale -
+ this.graph.getView().translate.y -
+ this.graph.gridSize/2);
+ vertex.setGeometry(geo);
+
+ if (parent == null)
+ {
+ parent = this.graph.getDefaultParent();
+ }
+
+ this.cycleAttribute(vertex);
+ this.fireEvent(new mxEventObject(mxEvent.BEFORE_ADD_VERTEX,
+ 'vertex', vertex, 'parent', parent));
+
+ model.beginUpdate();
+ try
+ {
+ vertex = this.graph.addCell(vertex, parent);
+
+ if (vertex != null)
+ {
+ this.graph.constrainChild(vertex);
+
+ this.fireEvent(new mxEventObject(mxEvent.ADD_VERTEX, 'vertex', vertex));
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+
+ if (vertex != null)
+ {
+ this.graph.setSelectionCell(vertex);
+ this.graph.scrollCellToVisible(vertex);
+ this.fireEvent(new mxEventObject(mxEvent.AFTER_ADD_VERTEX, 'vertex', vertex));
+ }
+
+ return vertex;
+};
+
+/**
+ * Function: destroy
+ *
+ * Removes the editor and all its associated resources. This does not
+ * normally need to be called, it is called automatically when the window
+ * unloads.
+ */
+mxEditor.prototype.destroy = function ()
+{
+ if (!this.destroyed)
+ {
+ this.destroyed = true;
+
+ if (this.tasks != null)
+ {
+ this.tasks.destroy();
+ }
+
+ if (this.outline != null)
+ {
+ this.outline.destroy();
+ }
+
+ if (this.properties != null)
+ {
+ this.properties.destroy();
+ }
+
+ if (this.keyHandler != null)
+ {
+ this.keyHandler.destroy();
+ }
+
+ if (this.rubberband != null)
+ {
+ this.rubberband.destroy();
+ }
+
+ if (this.toolbar != null)
+ {
+ this.toolbar.destroy();
+ }
+
+ if (this.graph != null)
+ {
+ this.graph.destroy();
+ }
+
+ this.status = null;
+ this.templates = null;
+ }
+};
diff --git a/src/js/handler/mxCellHighlight.js b/src/js/handler/mxCellHighlight.js
new file mode 100644
index 0000000..f967f00
--- /dev/null
+++ b/src/js/handler/mxCellHighlight.js
@@ -0,0 +1,271 @@
+/**
+ * $Id: mxCellHighlight.js,v 1.25 2012-09-27 14:43:40 boris Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellHighlight
+ *
+ * A helper class to highlight cells. Here is an example for a given cell.
+ *
+ * (code)
+ * var highlight = new mxCellHighlight(graph, '#ff0000', 2);
+ * highlight.highlight(graph.view.getState(cell)));
+ * (end)
+ *
+ * Constructor: mxCellHighlight
+ *
+ * Constructs a cell highlight.
+ */
+function mxCellHighlight(graph, highlightColor, strokeWidth)
+{
+ if (graph != null)
+ {
+ this.graph = graph;
+ this.highlightColor = (highlightColor != null) ? highlightColor : mxConstants.DEFAULT_VALID_COLOR;
+ this.strokeWidth = (strokeWidth != null) ? strokeWidth : mxConstants.HIGHLIGHT_STROKEWIDTH;
+
+ // Updates the marker if the graph changes
+ this.repaintHandler = mxUtils.bind(this, function()
+ {
+ this.repaint();
+ });
+
+ this.graph.getView().addListener(mxEvent.SCALE, this.repaintHandler);
+ this.graph.getView().addListener(mxEvent.TRANSLATE, this.repaintHandler);
+ this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.repaintHandler);
+ this.graph.getModel().addListener(mxEvent.CHANGE, this.repaintHandler);
+
+ // Hides the marker if the current root changes
+ this.resetHandler = mxUtils.bind(this, function()
+ {
+ this.hide();
+ });
+
+ this.graph.getView().addListener(mxEvent.DOWN, this.resetHandler);
+ this.graph.getView().addListener(mxEvent.UP, this.resetHandler);
+ }
+};
+
+/**
+ * Variable: keepOnTop
+ *
+ * Specifies if the highlights should appear on top of everything
+ * else in the overlay pane. Default is false.
+ */
+mxCellHighlight.prototype.keepOnTop = false;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellHighlight.prototype.graph = true;
+
+/**
+ * Variable: state
+ *
+ * Reference to the <mxCellState>.
+ */
+mxCellHighlight.prototype.state = null;
+
+/**
+ * Variable: spacing
+ *
+ * Specifies the spacing between the highlight for vertices and the vertex.
+ * Default is 2.
+ */
+mxCellHighlight.prototype.spacing = 2;
+
+/**
+ * Variable: resetHandler
+ *
+ * Holds the handler that automatically invokes reset if the highlight
+ * should be hidden.
+ */
+mxCellHighlight.prototype.resetHandler = null;
+
+/**
+ * Function: setHighlightColor
+ *
+ * Sets the color of the rectangle used to highlight drop targets.
+ *
+ * Parameters:
+ *
+ * color - String that represents the new highlight color.
+ */
+mxCellHighlight.prototype.setHighlightColor = function(color)
+{
+ this.highlightColor = color;
+
+ if (this.shape != null)
+ {
+ if (this.shape.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.shape.innerNode.setAttribute('stroke', color);
+ }
+ else if (this.shape.dialect == mxConstants.DIALECT_VML)
+ {
+ this.shape.node.strokecolor = color;
+ }
+ }
+};
+
+/**
+ * Function: drawHighlight
+ *
+ * Creates and returns the highlight shape for the given state.
+ */
+mxCellHighlight.prototype.drawHighlight = function()
+{
+ this.shape = this.createShape();
+ this.repaint();
+
+ if (!this.keepOnTop && this.shape.node.parentNode.firstChild != this.shape.node)
+ {
+ this.shape.node.parentNode.insertBefore(this.shape.node, this.shape.node.parentNode.firstChild);
+ }
+
+ // Workaround to force a repaint in AppleWebKit
+ if (this.graph.model.isEdge(this.state.cell))
+ {
+ mxUtils.repaintGraph(this.graph, this.shape.points[0]);
+ }
+};
+
+/**
+ * Function: createShape
+ *
+ * Creates and returns the highlight shape for the given state.
+ */
+mxCellHighlight.prototype.createShape = function()
+{
+ var shape = null;
+
+ if (this.graph.model.isEdge(this.state.cell))
+ {
+ shape = new mxPolyline(this.state.absolutePoints,
+ this.highlightColor, this.strokeWidth);
+ }
+ else
+ {
+ shape = new mxRectangleShape( new mxRectangle(),
+ null, this.highlightColor, this.strokeWidth);
+ }
+
+ shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ shape.init(this.graph.getView().getOverlayPane());
+ mxEvent.redirectMouseEvents(shape.node, this.graph, this.state);
+
+ return shape;
+};
+
+
+/**
+ * Function: repaint
+ *
+ * Updates the highlight after a change of the model or view.
+ */
+mxCellHighlight.prototype.repaint = function()
+{
+ if (this.state != null && this.shape != null)
+ {
+ if (this.graph.model.isEdge(this.state.cell))
+ {
+ this.shape.points = this.state.absolutePoints;
+ }
+ else
+ {
+ this.shape.bounds = new mxRectangle(this.state.x - this.spacing, this.state.y - this.spacing,
+ this.state.width + 2 * this.spacing, this.state.height + 2 * this.spacing);
+ }
+
+ // Uses cursor from shape in highlight
+ if (this.state.shape != null)
+ {
+ this.shape.setCursor(this.state.shape.getCursor());
+ }
+
+ var alpha = (!this.graph.model.isEdge(this.state.cell)) ? Number(this.state.style[mxConstants.STYLE_ROTATION] || '0') : 0;
+
+ // Event-transparency
+ if (this.shape.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.shape.node.setAttribute('style', 'pointer-events:none;');
+
+ if (alpha != 0)
+ {
+ var cx = this.state.getCenterX();
+ var cy = this.state.getCenterY();
+ var transform = 'rotate(' + alpha + ' ' + cx + ' ' + cy + ')';
+
+ this.shape.node.setAttribute('transform', transform);
+ }
+ }
+ else
+ {
+ this.shape.node.style.background = '';
+
+ if (alpha != 0)
+ {
+ this.shape.node.rotation = alpha;
+ }
+ }
+
+ this.shape.redraw();
+ }
+};
+
+/**
+ * Function: hide
+ *
+ * Resets the state of the cell marker.
+ */
+mxCellHighlight.prototype.hide = function()
+{
+ this.highlight(null);
+};
+
+/**
+ * Function: mark
+ *
+ * Marks the <markedState> and fires a <mark> event.
+ */
+mxCellHighlight.prototype.highlight = function(state)
+{
+ if (this.state != state)
+ {
+ if (this.shape != null)
+ {
+ this.shape.destroy();
+ this.shape = null;
+ }
+
+ this.state = state;
+
+ if (this.state != null)
+ {
+ this.drawHighlight();
+ }
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxCellHighlight.prototype.destroy = function()
+{
+ this.graph.getView().removeListener(this.repaintHandler);
+ this.graph.getModel().removeListener(this.repaintHandler);
+
+ this.graph.getView().removeListener(this.resetHandler);
+ this.graph.getModel().removeListener(this.resetHandler);
+
+ if (this.shape != null)
+ {
+ this.shape.destroy();
+ this.shape = null;
+ }
+};
diff --git a/src/js/handler/mxCellMarker.js b/src/js/handler/mxCellMarker.js
new file mode 100644
index 0000000..b336278
--- /dev/null
+++ b/src/js/handler/mxCellMarker.js
@@ -0,0 +1,419 @@
+/**
+ * $Id: mxCellMarker.js,v 1.30 2011-07-15 12:57:50 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellMarker
+ *
+ * A helper class to process mouse locations and highlight cells.
+ *
+ * Helper class to highlight cells. To add a cell marker to an existing graph
+ * for highlighting all cells, the following code is used:
+ *
+ * (code)
+ * var marker = new mxCellMarker(graph);
+ * graph.addMouseListener({
+ * mouseDown: function() {},
+ * mouseMove: function(sender, me)
+ * {
+ * marker.process(me);
+ * },
+ * mouseUp: function() {}
+ * });
+ * (end)
+ *
+ * Event: mxEvent.MARK
+ *
+ * Fires after a cell has been marked or unmarked. The <code>state</code>
+ * property contains the marked <mxCellState> or null if no state is marked.
+ *
+ * Constructor: mxCellMarker
+ *
+ * Constructs a new cell marker.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * validColor - Optional marker color for valid states. Default is
+ * <mxConstants.DEFAULT_VALID_COLOR>.
+ * invalidColor - Optional marker color for invalid states. Default is
+ * <mxConstants.DEFAULT_INVALID_COLOR>.
+ * hotspot - Portion of the width and hight where a state intersects a
+ * given coordinate pair. A value of 0 means always highlight. Default is
+ * <mxConstants.DEFAULT_HOTSPOT>.
+ */
+function mxCellMarker(graph, validColor, invalidColor, hotspot)
+{
+ if (graph != null)
+ {
+ this.graph = graph;
+ this.validColor = (validColor != null) ? validColor : mxConstants.DEFAULT_VALID_COLOR;
+ this.invalidColor = (validColor != null) ? invalidColor : mxConstants.DEFAULT_INVALID_COLOR;
+ this.hotspot = (hotspot != null) ? hotspot : mxConstants.DEFAULT_HOTSPOT;
+
+ this.highlight = new mxCellHighlight(graph);
+ }
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxCellMarker.prototype = new mxEventSource();
+mxCellMarker.prototype.constructor = mxCellMarker;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellMarker.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if the marker is enabled. Default is true.
+ */
+mxCellMarker.prototype.enabled = true;
+
+/**
+ * Variable: hotspot
+ *
+ * Specifies the portion of the width and height that should trigger
+ * a highlight. The area around the center of the cell to be marked is used
+ * as the hotspot. Possible values are between 0 and 1. Default is
+ * mxConstants.DEFAULT_HOTSPOT.
+ */
+mxCellMarker.prototype.hotspot = mxConstants.DEFAULT_HOTSPOT;
+
+/**
+ * Variable: hotspotEnabled
+ *
+ * Specifies if the hotspot is enabled. Default is false.
+ */
+mxCellMarker.prototype.hotspotEnabled = false;
+
+/**
+ * Variable: validColor
+ *
+ * Holds the valid marker color.
+ */
+mxCellMarker.prototype.validColor = null;
+
+/**
+ * Variable: invalidColor
+ *
+ * Holds the invalid marker color.
+ */
+mxCellMarker.prototype.invalidColor = null;
+
+/**
+ * Variable: currentColor
+ *
+ * Holds the current marker color.
+ */
+mxCellMarker.prototype.currentColor = null;
+
+/**
+ * Variable: validState
+ *
+ * Holds the marked <mxCellState> if it is valid.
+ */
+mxCellMarker.prototype.validState = null;
+
+/**
+ * Variable: markedState
+ *
+ * Holds the marked <mxCellState>.
+ */
+mxCellMarker.prototype.markedState = null;
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxCellMarker.prototype.setEnabled = function(enabled)
+{
+ this.enabled = enabled;
+};
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxCellMarker.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setHotspot
+ *
+ * Sets the <hotspot>.
+ */
+mxCellMarker.prototype.setHotspot = function(hotspot)
+{
+ this.hotspot = hotspot;
+};
+
+/**
+ * Function: getHotspot
+ *
+ * Returns the <hotspot>.
+ */
+mxCellMarker.prototype.getHotspot = function()
+{
+ return this.hotspot;
+};
+
+/**
+ * Function: setHotspotEnabled
+ *
+ * Specifies whether the hotspot should be used in <intersects>.
+ */
+mxCellMarker.prototype.setHotspotEnabled = function(enabled)
+{
+ this.hotspotEnabled = enabled;
+};
+
+/**
+ * Function: isHotspotEnabled
+ *
+ * Returns true if hotspot is used in <intersects>.
+ */
+mxCellMarker.prototype.isHotspotEnabled = function()
+{
+ return this.hotspotEnabled;
+};
+
+/**
+ * Function: hasValidState
+ *
+ * Returns true if <validState> is not null.
+ */
+mxCellMarker.prototype.hasValidState = function()
+{
+ return this.validState != null;
+};
+
+/**
+ * Function: getValidState
+ *
+ * Returns the <validState>.
+ */
+mxCellMarker.prototype.getValidState = function()
+{
+ return this.validState;
+};
+
+/**
+ * Function: getMarkedState
+ *
+ * Returns the <markedState>.
+ */
+mxCellMarker.prototype.getMarkedState = function()
+{
+ return this.markedState;
+};
+
+/**
+ * Function: reset
+ *
+ * Resets the state of the cell marker.
+ */
+mxCellMarker.prototype.reset = function()
+{
+ this.validState = null;
+
+ if (this.markedState != null)
+ {
+ this.markedState = null;
+ this.unmark();
+ }
+};
+
+/**
+ * Function: process
+ *
+ * Processes the given event and cell and marks the state returned by
+ * <getState> with the color returned by <getMarkerColor>. If the
+ * markerColor is not null, then the state is stored in <markedState>. If
+ * <isValidState> returns true, then the state is stored in <validState>
+ * regardless of the marker color. The state is returned regardless of the
+ * marker color and valid state.
+ */
+mxCellMarker.prototype.process = function(me)
+{
+ var state = null;
+
+ if (this.isEnabled())
+ {
+ state = this.getState(me);
+ var isValid = (state != null) ? this.isValidState(state) : false;
+ var color = this.getMarkerColor(me.getEvent(), state, isValid);
+
+ if (isValid)
+ {
+ this.validState = state;
+ }
+ else
+ {
+ this.validState = null;
+ }
+
+ if (state != this.markedState || color != this.currentColor)
+ {
+ this.currentColor = color;
+
+ if (state != null && this.currentColor != null)
+ {
+ this.markedState = state;
+ this.mark();
+ }
+ else if (this.markedState != null)
+ {
+ this.markedState = null;
+ this.unmark();
+ }
+ }
+ }
+
+ return state;
+};
+
+/**
+ * Function: markCell
+ *
+ * Marks the given cell using the given color, or <validColor> if no color is specified.
+ */
+mxCellMarker.prototype.markCell = function(cell, color)
+{
+ var state = this.graph.getView().getState(cell);
+
+ if (state != null)
+ {
+ this.currentColor = (color != null) ? color : this.validColor;
+ this.markedState = state;
+ this.mark();
+ }
+};
+
+/**
+ * Function: mark
+ *
+ * Marks the <markedState> and fires a <mark> event.
+ */
+mxCellMarker.prototype.mark = function()
+{
+ this.highlight.setHighlightColor(this.currentColor);
+ this.highlight.highlight(this.markedState);
+ this.fireEvent(new mxEventObject(mxEvent.MARK, 'state', this.markedState));
+};
+
+/**
+ * Function: unmark
+ *
+ * Hides the marker and fires a <mark> event.
+ */
+mxCellMarker.prototype.unmark = function()
+{
+ this.mark();
+};
+
+/**
+ * Function: isValidState
+ *
+ * Returns true if the given <mxCellState> is a valid state. If this
+ * returns true, then the state is stored in <validState>. The return value
+ * of this method is used as the argument for <getMarkerColor>.
+ */
+mxCellMarker.prototype.isValidState = function(state)
+{
+ return true;
+};
+
+/**
+ * Function: getMarkerColor
+ *
+ * Returns the valid- or invalidColor depending on the value of isValid.
+ * The given <mxCellState> is ignored by this implementation.
+ */
+mxCellMarker.prototype.getMarkerColor = function(evt, state, isValid)
+{
+ return (isValid) ? this.validColor : this.invalidColor;
+};
+
+/**
+ * Function: getState
+ *
+ * Uses <getCell>, <getStateToMark> and <intersects> to return the
+ * <mxCellState> for the given <mxMouseEvent>.
+ */
+mxCellMarker.prototype.getState = function(me)
+{
+ var view = this.graph.getView();
+ cell = this.getCell(me);
+ var state = this.getStateToMark(view.getState(cell));
+
+ return (state != null && this.intersects(state, me)) ? state : null;
+};
+
+/**
+ * Function: getCell
+ *
+ * Returns the <mxCell> for the given event and cell. This returns the
+ * given cell.
+ */
+mxCellMarker.prototype.getCell = function(me)
+{
+ return me.getCell();
+};
+
+/**
+ * Function: getStateToMark
+ *
+ * Returns the <mxCellState> to be marked for the given <mxCellState> under
+ * the mouse. This returns the given state.
+ */
+mxCellMarker.prototype.getStateToMark = function(state)
+{
+ return state;
+};
+
+/**
+ * Function: intersects
+ *
+ * Returns true if the given coordinate pair intersects the given state.
+ * This returns true if the <hotspot> is 0 or the coordinates are inside
+ * the hotspot for the given cell state.
+ */
+mxCellMarker.prototype.intersects = function(state, me)
+{
+ if (this.hotspotEnabled)
+ {
+ return mxUtils.intersectsHotspot(state, me.getGraphX(), me.getGraphY(),
+ this.hotspot, mxConstants.MIN_HOTSPOT_SIZE,
+ mxConstants.MAX_HOTSPOT_SIZE);
+ }
+
+ return true;
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxCellMarker.prototype.destroy = function()
+{
+ this.graph.getView().removeListener(this.resetHandler);
+ this.graph.getModel().removeListener(this.resetHandler);
+ this.highlight.destroy();
+};
diff --git a/src/js/handler/mxCellTracker.js b/src/js/handler/mxCellTracker.js
new file mode 100644
index 0000000..5adcd6a
--- /dev/null
+++ b/src/js/handler/mxCellTracker.js
@@ -0,0 +1,149 @@
+/**
+ * $Id: mxCellTracker.js,v 1.9 2011-08-28 09:49:46 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellTracker
+ *
+ * Event handler that highlights cells. Inherits from <mxCellMarker>.
+ *
+ * Example:
+ *
+ * (code)
+ * new mxCellTracker(graph, '#00FF00');
+ * (end)
+ *
+ * For detecting dragEnter, dragOver and dragLeave on cells, the following
+ * code can be used:
+ *
+ * (code)
+ * graph.addMouseListener(
+ * {
+ * cell: null,
+ * mouseDown: function(sender, me) { },
+ * mouseMove: function(sender, me)
+ * {
+ * var tmp = me.getCell();
+ *
+ * if (tmp != this.cell)
+ * {
+ * if (this.cell != null)
+ * {
+ * this.dragLeave(me.getEvent(), this.cell);
+ * }
+ *
+ * this.cell = tmp;
+ *
+ * if (this.cell != null)
+ * {
+ * this.dragEnter(me.getEvent(), this.cell);
+ * }
+ * }
+ *
+ * if (this.cell != null)
+ * {
+ * this.dragOver(me.getEvent(), this.cell);
+ * }
+ * },
+ * mouseUp: function(sender, me) { },
+ * dragEnter: function(evt, cell)
+ * {
+ * mxLog.debug('dragEnter', cell.value);
+ * },
+ * dragOver: function(evt, cell)
+ * {
+ * mxLog.debug('dragOver', cell.value);
+ * },
+ * dragLeave: function(evt, cell)
+ * {
+ * mxLog.debug('dragLeave', cell.value);
+ * }
+ * });
+ * (end)
+ *
+ * Constructor: mxCellTracker
+ *
+ * Constructs an event handler that highlights cells.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * color - Color of the highlight. Default is blue.
+ * funct - Optional JavaScript function that is used to override
+ * <mxCellMarker.getCell>.
+ */
+function mxCellTracker(graph, color, funct)
+{
+ mxCellMarker.call(this, graph, color);
+
+ this.graph.addMouseListener(this);
+
+ if (funct != null)
+ {
+ this.getCell = funct;
+ }
+
+ // Automatic deallocation of memory
+ if (mxClient.IS_IE)
+ {
+ mxEvent.addListener(window, 'unload', mxUtils.bind(this, function()
+ {
+ this.destroy();
+ }));
+ }
+};
+
+/**
+ * Extends mxCellMarker.
+ */
+mxCellTracker.prototype = new mxCellMarker();
+mxCellTracker.prototype.constructor = mxCellTracker;
+
+/**
+ * Function: mouseDown
+ *
+ * Ignores the event. The event is not consumed.
+ */
+mxCellTracker.prototype.mouseDown = function(sender, me) { };
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by highlighting the cell under the mousepointer if it
+ * is over the hotspot region of the cell.
+ */
+mxCellTracker.prototype.mouseMove = function(sender, me)
+{
+ if (this.isEnabled())
+ {
+ this.process(me);
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by reseting the highlight.
+ */
+mxCellTracker.prototype.mouseUp = function(sender, me)
+{
+ this.reset();
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the object and all its resources and DOM nodes. This doesn't
+ * normally need to be called. It is called automatically when the window
+ * unloads.
+ */
+mxCellTracker.prototype.destroy = function()
+{
+ if (!this.destroyed)
+ {
+ this.destroyed = true;
+
+ this.graph.removeMouseListener(this);
+ mxCellMarker.prototype.destroy.apply(this);
+ }
+};
diff --git a/src/js/handler/mxConnectionHandler.js b/src/js/handler/mxConnectionHandler.js
new file mode 100644
index 0000000..07daaf8
--- /dev/null
+++ b/src/js/handler/mxConnectionHandler.js
@@ -0,0 +1,1969 @@
+/**
+ * $Id: mxConnectionHandler.js,v 1.216 2012-12-07 15:17:37 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxConnectionHandler
+ *
+ * Graph event handler that creates new connections. Uses <mxTerminalMarker>
+ * for finding and highlighting the source and target vertices and
+ * <factoryMethod> to create the edge instance. This handler is built-into
+ * <mxGraph.connectionHandler> and enabled using <mxGraph.setConnectable>.
+ *
+ * Example:
+ *
+ * (code)
+ * new mxConnectionHandler(graph, function(source, target, style)
+ * {
+ * edge = new mxCell('', new mxGeometry());
+ * edge.setEdge(true);
+ * edge.setStyle(style);
+ * edge.geometry.relative = true;
+ * return edge;
+ * });
+ * (end)
+ *
+ * Here is an alternative solution that just sets a specific user object for
+ * new edges by overriding <insertEdge>.
+ *
+ * (code)
+ * mxConnectionHandlerInsertEdge = mxConnectionHandler.prototype.insertEdge;
+ * mxConnectionHandler.prototype.insertEdge = function(parent, id, value, source, target, style)
+ * {
+ * value = 'Test';
+ *
+ * return mxConnectionHandlerInsertEdge.apply(this, arguments);
+ * };
+ * (end)
+ *
+ * Using images to trigger connections:
+ *
+ * This handler uses mxTerminalMarker to find the source and target cell for
+ * the new connection and creates a new edge using <connect>. The new edge is
+ * created using <createEdge> which in turn uses <factoryMethod> or creates a
+ * new default edge.
+ *
+ * The handler uses a "highlight-paradigm" for indicating if a cell is being
+ * used as a source or target terminal, as seen in MS Visio and other products.
+ * In order to allow both, moving and connecting cells at the same time,
+ * <mxConstants.DEFAULT_HOTSPOT> is used in the handler to determine the hotspot
+ * of a cell, that is, the region of the cell which is used to trigger a new
+ * connection. The constant is a value between 0 and 1 that specifies the
+ * amount of the width and height around the center to be used for the hotspot
+ * of a cell and its default value is 0.5. In addition,
+ * <mxConstants.MIN_HOTSPOT_SIZE> defines the minimum number of pixels for the
+ * width and height of the hotspot.
+ *
+ * This solution, while standards compliant, may be somewhat confusing because
+ * there is no visual indicator for the hotspot and the highlight is seen to
+ * switch on and off while the mouse is being moved in and out. Furthermore,
+ * this paradigm does not allow to create different connections depending on
+ * the highlighted hotspot as there is only one hotspot per cell and it
+ * normally does not allow cells to be moved and connected at the same time as
+ * there is no clear indication of the connectable area of the cell.
+ *
+ * To come across these issues, the handle has an additional <createIcons> hook
+ * with a default implementation that allows to create one icon to be used to
+ * trigger new connections. If this icon is specified, then new connections can
+ * only be created if the image is clicked while the cell is being highlighted.
+ * The <createIcons> hook may be overridden to create more than one
+ * <mxImageShape> for creating new connections, but the default implementation
+ * supports one image and is used as follows:
+ *
+ * In order to display the "connect image" whenever the mouse is over the cell,
+ * an DEFAULT_HOTSPOT of 1 should be used:
+ *
+ * (code)
+ * mxConstants.DEFAULT_HOTSPOT = 1;
+ * (end)
+ *
+ * In order to avoid confusion with the highlighting, the highlight color
+ * should not be used with a connect image:
+ *
+ * (code)
+ * mxConstants.HIGHLIGHT_COLOR = null;
+ * (end)
+ *
+ * To install the image, the connectImage field of the mxConnectionHandler must
+ * be assigned a new <mxImage> instance:
+ *
+ * (code)
+ * mxConnectionHandler.prototype.connectImage = new mxImage('images/green-dot.gif', 14, 14);
+ * (end)
+ *
+ * This will use the green-dot.gif with a width and height of 14 pixels as the
+ * image to trigger new connections. In createIcons the icon field of the
+ * handler will be set in order to remember the icon that has been clicked for
+ * creating the new connection. This field will be available under selectedIcon
+ * in the connect method, which may be overridden to take the icon that
+ * triggered the new connection into account. This is useful if more than one
+ * icon may be used to create a connection.
+ *
+ * Group: Events
+ *
+ * Event: mxEvent.START
+ *
+ * Fires when a new connection is being created by the user. The <code>state</code>
+ * property contains the state of the source cell.
+ *
+ * Event: mxEvent.CONNECT
+ *
+ * Fires between begin- and endUpdate in <connect>. The <code>cell</code>
+ * property contains the inserted edge, the <code>event</code> and <code>target</code>
+ * properties contain the respective arguments that were passed to <connect> (where
+ * target corresponds to the dropTarget argument).
+ *
+ * Note that the target is the cell under the mouse where the mouse button was released.
+ * Depending on the logic in the handler, this doesn't necessarily have to be the target
+ * of the inserted edge. To print the source, target or any optional ports IDs that the
+ * edge is connected to, the following code can be used. To get more details about the
+ * actual connection point, <mxGraph.getConnectionConstraint> can be used. To resolve
+ * the port IDs, use <mxGraphModel.getCell>.
+ *
+ * (code)
+ * graph.connectionHandler.addListener(mxEvent.CONNECT, function(sender, evt)
+ * {
+ * var edge = evt.getProperty('cell');
+ * var source = graph.getModel().getTerminal(edge, true);
+ * var target = graph.getModel().getTerminal(edge, false);
+ *
+ * var style = graph.getCellStyle(edge);
+ * var sourcePortId = style[mxConstants.STYLE_SOURCE_PORT];
+ * var targetPortId = style[mxConstants.STYLE_TARGET_PORT];
+ *
+ * mxLog.show();
+ * mxLog.debug('connect', edge, source.id, target.id, sourcePortId, targetPortId);
+ * });
+ * (end)
+ *
+ * Event: mxEvent.RESET
+ *
+ * Fires when the <reset> method is invoked.
+ *
+ * Constructor: mxConnectionHandler
+ *
+ * Constructs an event handler that connects vertices using the specified
+ * factory method to create the new edges. Modify
+ * <mxConstants.ACTIVE_REGION> to setup the region on a cell which triggers
+ * the creation of a new connection or use connect icons as explained
+ * above.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * factoryMethod - Optional function to create the edge. The function takes
+ * the source and target <mxCell> as the first and second argument and an
+ * optional cell style from the preview as the third argument. It returns
+ * the <mxCell> that represents the new edge.
+ */
+function mxConnectionHandler(graph, factoryMethod)
+{
+ if (graph != null)
+ {
+ this.graph = graph;
+ this.factoryMethod = factoryMethod;
+ this.init();
+ }
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxConnectionHandler.prototype = new mxEventSource();
+mxConnectionHandler.prototype.constructor = mxConnectionHandler;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxConnectionHandler.prototype.graph = null;
+
+/**
+ * Variable: factoryMethod
+ *
+ * Function that is used for creating new edges. The function takes the
+ * source and target <mxCell> as the first and second argument and returns
+ * a new <mxCell> that represents the edge. This is used in <createEdge>.
+ */
+mxConnectionHandler.prototype.factoryMethod = true;
+
+/**
+ * Variable: moveIconFront
+ *
+ * Specifies if icons should be displayed inside the graph container instead
+ * of the overlay pane. This is used for HTML labels on vertices which hide
+ * the connect icon. This has precendence over <moveIconBack> when set
+ * to true. Default is false.
+ */
+mxConnectionHandler.prototype.moveIconFront = false;
+
+/**
+ * Variable: moveIconBack
+ *
+ * Specifies if icons should be moved to the back of the overlay pane. This can
+ * be set to true if the icons of the connection handler conflict with other
+ * handles, such as the vertex label move handle. Default is false.
+ */
+mxConnectionHandler.prototype.moveIconBack = false;
+
+/**
+ * Variable: connectImage
+ *
+ * <mxImage> that is used to trigger the creation of a new connection. This
+ * is used in <createIcons>. Default is null.
+ */
+mxConnectionHandler.prototype.connectImage = null;
+
+/**
+ * Variable: targetConnectImage
+ *
+ * Specifies if the connect icon should be centered on the target state
+ * while connections are being previewed. Default is false.
+ */
+mxConnectionHandler.prototype.targetConnectImage = false;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxConnectionHandler.prototype.enabled = true;
+
+/**
+ * Variable: select
+ *
+ * Specifies if new edges should be selected. Default is true.
+ */
+mxConnectionHandler.prototype.select = true;
+
+/**
+ * Variable: createTarget
+ *
+ * Specifies if <createTargetVertex> should be called if no target was under the
+ * mouse for the new connection. Setting this to true means the connection
+ * will be drawn as valid if no target is under the mouse, and
+ * <createTargetVertex> will be called before the connection is created between
+ * the source cell and the newly created vertex in <createTargetVertex>, which
+ * can be overridden to create a new target. Default is false.
+ */
+mxConnectionHandler.prototype.createTarget = false;
+
+/**
+ * Variable: marker
+ *
+ * Holds the <mxTerminalMarker> used for finding source and target cells.
+ */
+mxConnectionHandler.prototype.marker = null;
+
+/**
+ * Variable: constraintHandler
+ *
+ * Holds the <mxConstraintHandler> used for drawing and highlighting
+ * constraints.
+ */
+mxConnectionHandler.prototype.constraintHandler = null;
+
+/**
+ * Variable: error
+ *
+ * Holds the current validation error while connections are being created.
+ */
+mxConnectionHandler.prototype.error = null;
+
+/**
+ * Variable: waypointsEnabled
+ *
+ * Specifies if single clicks should add waypoints on the new edge. Default is
+ * false.
+ */
+mxConnectionHandler.prototype.waypointsEnabled = false;
+
+/**
+ * Variable: tapAndHoldEnabled
+ *
+ * Specifies if tap and hold should be used for starting connections on touch-based
+ * devices. Default is true.
+ */
+mxConnectionHandler.prototype.tapAndHoldEnabled = true;
+
+/**
+ * Variable: tapAndHoldDelay
+ *
+ * Specifies the time for a tap and hold. Default is 500 ms.
+ */
+mxConnectionHandler.prototype.tapAndHoldDelay = 500;
+
+/**
+ * Variable: tapAndHoldInProgress
+ *
+ * True if the timer for tap and hold events is running.
+ */
+mxConnectionHandler.prototype.tapAndHoldInProgress = false;
+
+/**
+ * Variable: tapAndHoldValid
+ *
+ * True as long as the timer is running and the touch events
+ * stay within the given <tapAndHoldTolerance>.
+ */
+mxConnectionHandler.prototype.tapAndHoldValid = false;
+
+/**
+ * Variable: tapAndHoldTolerance
+ *
+ * Specifies the tolerance for a tap and hold. Default is 4 pixels.
+ */
+mxConnectionHandler.prototype.tapAndHoldTolerance = 4;
+
+/**
+ * Variable: initialTouchX
+ *
+ * Holds the x-coordinate of the intial touch event for tap and hold.
+ */
+mxConnectionHandler.prototype.initialTouchX = 0;
+
+/**
+ * Variable: initialTouchY
+ *
+ * Holds the y-coordinate of the intial touch event for tap and hold.
+ */
+mxConnectionHandler.prototype.initialTouchY = 0;
+
+/**
+ * Variable: ignoreMouseDown
+ *
+ * Specifies if the connection handler should ignore the state of the mouse
+ * button when highlighting the source. Default is false, that is, the
+ * handler only highlights the source if no button is being pressed.
+ */
+mxConnectionHandler.prototype.ignoreMouseDown = false;
+
+/**
+ * Variable: first
+ *
+ * Holds the <mxPoint> where the mouseDown took place while the handler is
+ * active.
+ */
+mxConnectionHandler.prototype.first = null;
+
+/**
+ * Variable: connectIconOffset
+ *
+ * Holds the offset for connect icons during connection preview.
+ * Default is mxPoint(0, <mxConstants.TOOLTIP_VERTICAL_OFFSET>).
+ * Note that placing the icon under the mouse pointer with an
+ * offset of (0,0) will affect hit detection.
+ */
+mxConnectionHandler.prototype.connectIconOffset = new mxPoint(0, mxConstants.TOOLTIP_VERTICAL_OFFSET);
+
+/**
+ * Variable: edgeState
+ *
+ * Optional <mxCellState> that represents the preview edge while the
+ * handler is active. This is created in <createEdgeState>.
+ */
+mxConnectionHandler.prototype.edgeState = null;
+
+/**
+ * Variable: changeHandler
+ *
+ * Holds the change event listener for later removal.
+ */
+mxConnectionHandler.prototype.changeHandler = null;
+
+/**
+ * Variable: drillHandler
+ *
+ * Holds the drill event listener for later removal.
+ */
+mxConnectionHandler.prototype.drillHandler = null;
+
+/**
+ * Variable: mouseDownCounter
+ *
+ * Counts the number of mouseDown events since the start. The initial mouse
+ * down event counts as 1.
+ */
+mxConnectionHandler.prototype.mouseDownCounter = 0;
+
+/**
+ * Variable: movePreviewAway
+ *
+ * Switch to enable moving the preview away from the mousepointer. This is required in browsers
+ * where the preview cannot be made transparent to events and if the built-in hit detection on
+ * the HTML elements in the page should be used. Default is the value of <mxClient.IS_VML>.
+ */
+mxConnectionHandler.prototype.movePreviewAway = mxClient.IS_VML;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxConnectionHandler.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxConnectionHandler.prototype.setEnabled = function(enabled)
+{
+ this.enabled = enabled;
+};
+
+/**
+ * Function: isCreateTarget
+ *
+ * Returns <createTarget>.
+ */
+mxConnectionHandler.prototype.isCreateTarget = function()
+{
+ return this.createTarget;
+};
+
+/**
+ * Function: setCreateTarget
+ *
+ * Sets <createTarget>.
+ */
+mxConnectionHandler.prototype.setCreateTarget = function(value)
+{
+ this.createTarget = value;
+};
+
+/**
+ * Function: createShape
+ *
+ * Creates the preview shape for new connections.
+ */
+mxConnectionHandler.prototype.createShape = function()
+{
+ // Creates the edge preview
+ var shape = new mxPolyline([], mxConstants.INVALID_COLOR);
+ shape.isDashed = true;
+ shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ shape.init(this.graph.getView().getOverlayPane());
+
+ // Event-transparency
+ if (this.graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ // Sets event transparency on the internal shapes that represent
+ // the actual dashed line on the screen
+ shape.pipe.setAttribute('style', 'pointer-events:none;');
+ shape.innerNode.setAttribute('style', 'pointer-events:none;');
+ }
+ else
+ {
+ // Workaround no event transparency for preview in IE
+ // FIXME: 3,3 pixel offset for custom hit detection in IE
+ var getState = mxUtils.bind(this, function(evt)
+ {
+ var pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+
+ return this.graph.view.getState(this.graph.getCellAt(pt.x, pt.y));
+ });
+
+ // Redirects events on the shape to the graph
+ mxEvent.redirectMouseEvents(shape.node, this.graph, getState);
+ }
+
+ return shape;
+};
+
+/**
+ * Function: init
+ *
+ * Initializes the shapes required for this connection handler. This should
+ * be invoked if <mxGraph.container> is assigned after the connection
+ * handler has been created.
+ */
+mxConnectionHandler.prototype.init = function()
+{
+ this.graph.addMouseListener(this);
+ this.marker = this.createMarker();
+ this.constraintHandler = new mxConstraintHandler(this.graph);
+
+ // Redraws the icons if the graph changes
+ this.changeHandler = mxUtils.bind(this, function(sender)
+ {
+ if (this.iconState != null)
+ {
+ this.iconState = this.graph.getView().getState(this.iconState.cell);
+ }
+
+ if (this.iconState != null)
+ {
+ this.redrawIcons(this.icons, this.iconState);
+ }
+ else
+ {
+ this.destroyIcons(this.icons);
+ this.previous = null;
+ }
+
+ this.constraintHandler.reset();
+ });
+
+ this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
+ this.graph.getView().addListener(mxEvent.SCALE, this.changeHandler);
+ this.graph.getView().addListener(mxEvent.TRANSLATE, this.changeHandler);
+ this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.changeHandler);
+
+ // Removes the icon if we step into/up or start editing
+ this.drillHandler = mxUtils.bind(this, function(sender)
+ {
+ this.destroyIcons(this.icons);
+ });
+
+ this.graph.addListener(mxEvent.START_EDITING, this.drillHandler);
+ this.graph.getView().addListener(mxEvent.DOWN, this.drillHandler);
+ this.graph.getView().addListener(mxEvent.UP, this.drillHandler);
+};
+
+/**
+ * Function: isConnectableCell
+ *
+ * Returns true if the given cell is connectable. This is a hook to
+ * disable floating connections. This implementation returns true.
+ */
+mxConnectionHandler.prototype.isConnectableCell = function(cell)
+{
+ return true;
+};
+
+/**
+ * Function: createMarker
+ *
+ * Creates and returns the <mxCellMarker> used in <marker>.
+ */
+mxConnectionHandler.prototype.createMarker = function()
+{
+ var marker = new mxCellMarker(this.graph);
+ marker.hotspotEnabled = true;
+
+ // Overrides to return cell at location only if valid (so that
+ // there is no highlight for invalid cells)
+ marker.getCell = mxUtils.bind(this, function(evt, cell)
+ {
+ var cell = mxCellMarker.prototype.getCell.apply(marker, arguments);
+ this.error = null;
+
+ if (!this.isConnectableCell(cell))
+ {
+ return null;
+ }
+
+ if (cell != null)
+ {
+ if (this.isConnecting())
+ {
+ if (this.previous != null)
+ {
+ this.error = this.validateConnection(this.previous.cell, cell);
+
+ if (this.error != null && this.error.length == 0)
+ {
+ cell = null;
+
+ // Enables create target inside groups
+ if (this.isCreateTarget())
+ {
+ this.error = null;
+ }
+ }
+ }
+ }
+ else if (!this.isValidSource(cell))
+ {
+ cell = null;
+ }
+ }
+ else if (this.isConnecting() && !this.isCreateTarget() &&
+ !this.graph.allowDanglingEdges)
+ {
+ this.error = '';
+ }
+
+ return cell;
+ });
+
+ // Sets the highlight color according to validateConnection
+ marker.isValidState = mxUtils.bind(this, function(state)
+ {
+ if (this.isConnecting())
+ {
+ return this.error == null;
+ }
+ else
+ {
+ return mxCellMarker.prototype.isValidState.apply(marker, arguments);
+ }
+ });
+
+ // Overrides to use marker color only in highlight mode or for
+ // target selection
+ marker.getMarkerColor = mxUtils.bind(this, function(evt, state, isValid)
+ {
+ return (this.connectImage == null || this.isConnecting()) ?
+ mxCellMarker.prototype.getMarkerColor.apply(marker, arguments) :
+ null;
+ });
+
+ // Overrides to use hotspot only for source selection otherwise
+ // intersects always returns true when over a cell
+ marker.intersects = mxUtils.bind(this, function(state, evt)
+ {
+ if (this.connectImage != null || this.isConnecting())
+ {
+ return true;
+ }
+
+ return mxCellMarker.prototype.intersects.apply(marker, arguments);
+ });
+
+ return marker;
+};
+
+/**
+ * Function: start
+ *
+ * Starts a new connection for the given state and coordinates.
+ */
+mxConnectionHandler.prototype.start = function(state, x, y, edgeState)
+{
+ this.previous = state;
+ this.first = new mxPoint(x, y);
+ this.edgeState = (edgeState != null) ? edgeState : this.createEdgeState(null);
+
+ // Marks the source state
+ this.marker.currentColor = this.marker.validColor;
+ this.marker.markedState = state;
+ this.marker.mark();
+
+ this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous));
+};
+
+/**
+ * Function: isConnecting
+ *
+ * Returns true if the source terminal has been clicked and a new
+ * connection is currently being previewed.
+ */
+mxConnectionHandler.prototype.isConnecting = function()
+{
+ return this.first != null && this.shape != null;
+};
+
+/**
+ * Function: isValidSource
+ *
+ * Returns <mxGraph.isValidSource> for the given source terminal.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the source terminal.
+ */
+mxConnectionHandler.prototype.isValidSource = function(cell)
+{
+ return this.graph.isValidSource(cell);
+};
+
+/**
+ * Function: isValidTarget
+ *
+ * Returns true. The call to <mxGraph.isValidTarget> is implicit by calling
+ * <mxGraph.getEdgeValidationError> in <validateConnection>. This is an
+ * additional hook for disabling certain targets in this specific handler.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the target terminal.
+ */
+mxConnectionHandler.prototype.isValidTarget = function(cell)
+{
+ return true;
+};
+
+/**
+ * Function: validateConnection
+ *
+ * Returns the error message or an empty string if the connection for the
+ * given source target pair is not valid. Otherwise it returns null. This
+ * implementation uses <mxGraph.getEdgeValidationError>.
+ *
+ * Parameters:
+ *
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxConnectionHandler.prototype.validateConnection = function(source, target)
+{
+ if (!this.isValidTarget(target))
+ {
+ return '';
+ }
+
+ return this.graph.getEdgeValidationError(null, source, target);
+};
+
+/**
+ * Function: getConnectImage
+ *
+ * Hook to return the <mxImage> used for the connection icon of the given
+ * <mxCellState>. This implementation returns <connectImage>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose connect image should be returned.
+ */
+mxConnectionHandler.prototype.getConnectImage = function(state)
+{
+ return this.connectImage;
+};
+
+/**
+ * Function: isMoveIconToFrontForState
+ *
+ * Returns true if the state has a HTML label in the graph's container, otherwise
+ * it returns <moveIconFront>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose connect icons should be returned.
+ */
+mxConnectionHandler.prototype.isMoveIconToFrontForState = function(state)
+{
+ if (state.text != null && state.text.node.parentNode == this.graph.container)
+ {
+ return true;
+ }
+
+ return this.moveIconFront;
+};
+
+/**
+ * Function: createIcons
+ *
+ * Creates the array <mxImageShapes> that represent the connect icons for
+ * the given <mxCellState>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose connect icons should be returned.
+ */
+mxConnectionHandler.prototype.createIcons = function(state)
+{
+ var image = this.getConnectImage(state);
+
+ if (image != null && state != null)
+ {
+ this.iconState = state;
+ var icons = [];
+
+ // Cannot use HTML for the connect icons because the icon receives all
+ // mouse move events in IE, must use VML and SVG instead even if the
+ // connect-icon appears behind the selection border and the selection
+ // border consumes the events before the icon gets a chance
+ var bounds = new mxRectangle(0, 0, image.width, image.height);
+ var icon = new mxImageShape(bounds, image.src, null, null, 0);
+ icon.preserveImageAspect = false;
+
+ if (this.isMoveIconToFrontForState(state))
+ {
+ icon.dialect = mxConstants.DIALECT_STRICTHTML;
+ icon.init(this.graph.container);
+ }
+ else
+ {
+ icon.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_SVG :
+ mxConstants.DIALECT_VML;
+ icon.init(this.graph.getView().getOverlayPane());
+
+ // Move the icon back in the overlay pane
+ if (this.moveIconBack && icon.node.previousSibling != null)
+ {
+ icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild);
+ }
+ }
+
+ icon.node.style.cursor = mxConstants.CURSOR_CONNECT;
+
+ // Events transparency
+ var getState = mxUtils.bind(this, function()
+ {
+ return (this.currentState != null) ? this.currentState : state;
+ });
+
+ // Updates the local icon before firing the mouse down event.
+ var mouseDown = mxUtils.bind(this, function(evt)
+ {
+ if (!mxEvent.isConsumed(evt))
+ {
+ this.icon = icon;
+ this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+ new mxMouseEvent(evt, getState()));
+ }
+ });
+
+ mxEvent.redirectMouseEvents(icon.node, this.graph, getState, mouseDown);
+
+ icons.push(icon);
+ this.redrawIcons(icons, this.iconState);
+
+ return icons;
+ }
+
+ return null;
+};
+
+/**
+ * Function: redrawIcons
+ *
+ * Redraws the given array of <mxImageShapes>.
+ *
+ * Parameters:
+ *
+ * icons - Optional array of <mxImageShapes> to be redrawn.
+ */
+mxConnectionHandler.prototype.redrawIcons = function(icons, state)
+{
+ if (icons != null && icons[0] != null && state != null)
+ {
+ var pos = this.getIconPosition(icons[0], state);
+ icons[0].bounds.x = pos.x;
+ icons[0].bounds.y = pos.y;
+ icons[0].redraw();
+ }
+};
+
+/**
+ * Function: redrawIcons
+ *
+ * Redraws the given array of <mxImageShapes>.
+ *
+ * Parameters:
+ *
+ * icons - Optional array of <mxImageShapes> to be redrawn.
+ */
+mxConnectionHandler.prototype.getIconPosition = function(icon, state)
+{
+ var scale = this.graph.getView().scale;
+ var cx = state.getCenterX();
+ var cy = state.getCenterY();
+
+ if (this.graph.isSwimlane(state.cell))
+ {
+ var size = this.graph.getStartSize(state.cell);
+
+ cx = (size.width != 0) ? state.x + size.width * scale / 2 : cx;
+ cy = (size.height != 0) ? state.y + size.height * scale / 2 : cy;
+ }
+
+ return new mxPoint(cx - icon.bounds.width / 2,
+ cy - icon.bounds.height / 2);
+};
+
+/**
+ * Function: destroyIcons
+ *
+ * Destroys the given array of <mxImageShapes>.
+ *
+ * Parameters:
+ *
+ * icons - Optional array of <mxImageShapes> to be destroyed.
+ */
+mxConnectionHandler.prototype.destroyIcons = function(icons)
+{
+ if (icons != null)
+ {
+ this.iconState = null;
+
+ for (var i = 0; i < icons.length; i++)
+ {
+ icons[i].destroy();
+ }
+ }
+};
+
+/**
+ * Function: isStartEvent
+ *
+ * Returns true if the given mouse down event should start this handler. The
+ * This implementation returns true if the event does not force marquee
+ * selection, and the currentConstraint and currentFocus of the
+ * <constraintHandler> are not null, or <previous> and <error> are not null and
+ * <icons> is null or <icons> and <icon> are not null.
+ */
+mxConnectionHandler.prototype.isStartEvent = function(me)
+{
+ return !this.graph.isForceMarqueeEvent(me.getEvent()) &&
+ ((this.constraintHandler.currentFocus != null &&
+ this.constraintHandler.currentConstraint != null) ||
+ (this.previous != null && this.error == null &&
+ (this.icons == null || (this.icons != null && this.icon != null))));
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event by initiating a new connection.
+ */
+mxConnectionHandler.prototype.mouseDown = function(sender, me)
+{
+ this.mouseDownCounter++;
+
+ if (this.isEnabled() && this.graph.isEnabled() && !me.isConsumed() &&
+ !this.isConnecting() && this.isStartEvent(me))
+ {
+ if (this.constraintHandler.currentConstraint != null &&
+ this.constraintHandler.currentFocus != null &&
+ this.constraintHandler.currentPoint != null)
+ {
+ this.sourceConstraint = this.constraintHandler.currentConstraint;
+ this.previous = this.constraintHandler.currentFocus;
+ this.first = this.constraintHandler.currentPoint.clone();
+ }
+ else
+ {
+ // Stores the location of the initial mousedown
+ this.first = new mxPoint(me.getGraphX(), me.getGraphY());
+ }
+
+ this.edgeState = this.createEdgeState(me);
+ this.mouseDownCounter = 1;
+
+ if (this.waypointsEnabled && this.shape == null)
+ {
+ this.waypoints = null;
+ this.shape = this.createShape();
+ }
+
+ // Stores the starting point in the geometry of the preview
+ if (this.previous == null && this.edgeState != null)
+ {
+ var pt = this.graph.getPointForEvent(me.getEvent());
+ this.edgeState.cell.geometry.setTerminalPoint(pt, true);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous));
+
+ me.consume();
+ }
+ // Handles connecting via tap and hold
+ else if (mxClient.IS_TOUCH && this.tapAndHoldEnabled && !this.tapAndHoldInProgress &&
+ this.isEnabled() && this.graph.isEnabled() && !this.isConnecting())
+ {
+ this.tapAndHoldInProgress = true;
+ this.initialTouchX = me.getX();
+ this.initialTouchY = me.getY();
+ var state = this.graph.view.getState(this.marker.getCell(me));
+
+ var handler = function()
+ {
+ if (this.tapAndHoldValid)
+ {
+ this.tapAndHold(me, state);
+ }
+
+ this.tapAndHoldInProgress = false;
+ this.tapAndHoldValid = false;
+ };
+
+ if (this.tapAndHoldThread)
+ {
+ window.clearTimeout(this.tapAndHoldThread);
+ }
+
+ this.tapAndHoldThread = window.setTimeout(mxUtils.bind(this, handler), this.tapAndHoldDelay);
+ this.tapAndHoldValid = true;
+ }
+
+ this.selectedIcon = this.icon;
+ this.icon = null;
+};
+
+/**
+ * Function: tapAndHold
+ *
+ * Handles the <mxMouseEvent> by highlighting the <mxCellState>.
+ *
+ * Parameters:
+ *
+ * me - <mxMouseEvent> that represents the touch event.
+ * state - Optional <mxCellState> that is associated with the event.
+ */
+mxConnectionHandler.prototype.tapAndHold = function(me, state)
+{
+ if (state != null)
+ {
+ this.marker.currentColor = this.marker.validColor;
+ this.marker.markedState = state;
+ this.marker.mark();
+
+ this.first = new mxPoint(me.getGraphX(), me.getGraphY());
+ this.edgeState = this.createEdgeState(me);
+ this.previous = state;
+ this.fireEvent(new mxEventObject(mxEvent.START, 'state', this.previous));
+ }
+};
+
+/**
+ * Function: isImmediateConnectSource
+ *
+ * Returns true if a tap on the given source state should immediately start
+ * connecting. This implementation returns true if the state is not movable
+ * in the graph.
+ */
+mxConnectionHandler.prototype.isImmediateConnectSource = function(state)
+{
+ return !this.graph.isCellMovable(state.cell);
+};
+
+/**
+ * Function: createEdgeState
+ *
+ * Hook to return an <mxCellState> which may be used during the preview.
+ * This implementation returns null.
+ *
+ * Use the following code to create a preview for an existing edge style:
+ *
+ * [code]
+ * graph.connectionHandler.createEdgeState = function(me)
+ * {
+ * var edge = graph.createEdge(null, null, null, null, null, 'edgeStyle=elbowEdgeStyle');
+ *
+ * return new mxCellState(this.graph.view, edge, this.graph.getCellStyle(edge));
+ * };
+ * [/code]
+ */
+mxConnectionHandler.prototype.createEdgeState = function(me)
+{
+ return null;
+};
+
+/**
+ * Function: updateCurrentState
+ *
+ * Updates the current state for a given mouse move event by using
+ * the <marker>.
+ */
+mxConnectionHandler.prototype.updateCurrentState = function(me)
+{
+ var state = this.marker.process(me);
+ this.constraintHandler.update(me, this.first == null);
+ this.currentState = state;
+};
+
+/**
+ * Function: convertWaypoint
+ *
+ * Converts the given point from screen coordinates to model coordinates.
+ */
+mxConnectionHandler.prototype.convertWaypoint = function(point)
+{
+ var scale = this.graph.getView().getScale();
+ var tr = this.graph.getView().getTranslate();
+
+ point.x = point.x / scale - tr.x;
+ point.y = point.y / scale - tr.y;
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by updating the preview edge or by highlighting
+ * a possible source or target terminal.
+ */
+mxConnectionHandler.prototype.mouseMove = function(sender, me)
+{
+ if (this.tapAndHoldValid)
+ {
+ this.tapAndHoldValid =
+ Math.abs(this.initialTouchX - me.getX()) < this.tapAndHoldTolerance &&
+ Math.abs(this.initialTouchY - me.getY()) < this.tapAndHoldTolerance;
+ }
+
+ if (!me.isConsumed() && (this.ignoreMouseDown || this.first != null || !this.graph.isMouseDown))
+ {
+ // Handles special case when handler is disabled during highlight
+ if (!this.isEnabled() && this.currentState != null)
+ {
+ this.destroyIcons(this.icons);
+ this.currentState = null;
+ }
+
+ if (this.first != null || (this.isEnabled() && this.graph.isEnabled()))
+ {
+ this.updateCurrentState(me);
+ }
+
+ if (this.first != null)
+ {
+ var view = this.graph.getView();
+ var scale = view.scale;
+ var point = new mxPoint(this.graph.snap(me.getGraphX() / scale) * scale,
+ this.graph.snap(me.getGraphY() / scale) * scale);
+ var constraint = null;
+ var current = point;
+
+ // Uses the current point from the constraint handler if available
+ if (this.constraintHandler.currentConstraint != null &&
+ this.constraintHandler.currentFocus != null &&
+ this.constraintHandler.currentPoint != null)
+ {
+ constraint = this.constraintHandler.currentConstraint;
+ current = this.constraintHandler.currentPoint.clone();
+ }
+
+ var pt2 = this.first;
+
+ // Moves the connect icon with the mouse
+ if (this.selectedIcon != null)
+ {
+ var w = this.selectedIcon.bounds.width;
+ var h = this.selectedIcon.bounds.height;
+
+ if (this.currentState != null && this.targetConnectImage)
+ {
+ var pos = this.getIconPosition(this.selectedIcon, this.currentState);
+ this.selectedIcon.bounds.x = pos.x;
+ this.selectedIcon.bounds.y = pos.y;
+ }
+ else
+ {
+ var bounds = new mxRectangle(me.getGraphX() + this.connectIconOffset.x,
+ me.getGraphY() + this.connectIconOffset.y, w, h);
+ this.selectedIcon.bounds = bounds;
+ }
+
+ this.selectedIcon.redraw();
+ }
+
+ // Uses edge state to compute the terminal points
+ if (this.edgeState != null)
+ {
+ this.edgeState.absolutePoints = [null, (this.currentState != null) ? null : current];
+ this.graph.view.updateFixedTerminalPoint(this.edgeState, this.previous, true, this.sourceConstraint);
+
+ if (this.currentState != null)
+ {
+ if (constraint == null)
+ {
+ constraint = this.graph.getConnectionConstraint(this.edgeState, this.previous, false);
+ }
+
+ this.edgeState.setAbsoluteTerminalPoint(null, false);
+ this.graph.view.updateFixedTerminalPoint(this.edgeState, this.currentState, false, constraint);
+ }
+
+ // Scales and translates the waypoints to the model
+ var realPoints = null;
+
+ if (this.waypoints != null)
+ {
+ realPoints = [];
+
+ for (var i = 0; i < this.waypoints.length; i++)
+ {
+ var pt = this.waypoints[i].clone();
+ this.convertWaypoint(pt);
+ realPoints[i] = pt;
+ }
+ }
+
+ this.graph.view.updatePoints(this.edgeState, realPoints, this.previous, this.currentState);
+ this.graph.view.updateFloatingTerminalPoints(this.edgeState, this.previous, this.currentState);
+ current = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 1];
+ pt2 = this.edgeState.absolutePoints[0];
+ }
+ else
+ {
+ if (this.currentState != null)
+ {
+ if (this.constraintHandler.currentConstraint == null)
+ {
+ var tmp = this.getTargetPerimeterPoint(this.currentState, me);
+
+ if (tmp != null)
+ {
+ current = tmp;
+ }
+ }
+ }
+
+ // Computes the source perimeter point
+ if (this.sourceConstraint == null && this.previous != null)
+ {
+ var next = (this.waypoints != null && this.waypoints.length > 0) ?
+ this.waypoints[0] : current;
+ var tmp = this.getSourcePerimeterPoint(this.previous, next, me);
+
+ if (tmp != null)
+ {
+ pt2 = tmp;
+ }
+ }
+ }
+
+ // Makes sure the cell under the mousepointer can be detected
+ // by moving the preview shape away from the mouse. This
+ // makes sure the preview shape does not prevent the detection
+ // of the cell under the mousepointer even for slow gestures.
+ if (this.currentState == null && this.movePreviewAway)
+ {
+ var tmp = pt2;
+
+ if (this.edgeState != null && this.edgeState.absolutePoints.length > 2)
+ {
+ var tmp2 = this.edgeState.absolutePoints[this.edgeState.absolutePoints.length - 2];
+
+ if (tmp2 != null)
+ {
+ tmp = tmp2;
+ }
+ }
+
+ var dx = current.x - tmp.x;
+ var dy = current.y - tmp.y;
+
+ var len = Math.sqrt(dx * dx + dy * dy);
+
+ if (len == 0)
+ {
+ return;
+ }
+
+ current.x -= dx * 4 / len;
+ current.y -= dy * 4 / len;
+ }
+
+ // Creates the preview shape (lazy)
+ if (this.shape == null)
+ {
+ var dx = Math.abs(point.x - this.first.x);
+ var dy = Math.abs(point.y - this.first.y);
+
+ if (dx > this.graph.tolerance || dy > this.graph.tolerance)
+ {
+ this.shape = this.createShape();
+
+ // Revalidates current connection
+ this.updateCurrentState(me);
+ }
+ }
+
+ // Updates the points in the preview edge
+ if (this.shape != null)
+ {
+ if (this.edgeState != null)
+ {
+ this.shape.points = this.edgeState.absolutePoints;
+ }
+ else
+ {
+ var pts = [pt2];
+
+ if (this.waypoints != null)
+ {
+ pts = pts.concat(this.waypoints);
+ }
+
+ pts.push(current);
+ this.shape.points = pts;
+ }
+
+ this.drawPreview();
+ }
+
+ mxEvent.consume(me.getEvent());
+ me.consume();
+ }
+ else if(!this.isEnabled() || !this.graph.isEnabled())
+ {
+ this.constraintHandler.reset();
+ }
+ else if (this.previous != this.currentState && this.edgeState == null)
+ {
+ this.destroyIcons(this.icons);
+ this.icons = null;
+
+ // Sets the cursor on the current shape
+ if (this.currentState != null && this.error == null)
+ {
+ this.icons = this.createIcons(this.currentState);
+
+ if (this.icons == null)
+ {
+ this.currentState.setCursor(mxConstants.CURSOR_CONNECT);
+ me.consume();
+ }
+ }
+
+ this.previous = this.currentState;
+ }
+ else if (this.previous == this.currentState && this.currentState != null && this.icons == null &&
+ !this.graph.isMouseDown)
+ {
+ // Makes sure that no cursors are changed
+ me.consume();
+ }
+
+ if (this.constraintHandler.currentConstraint != null)
+ {
+ this.marker.reset();
+ }
+
+ if (!this.graph.isMouseDown && this.currentState != null && this.icons != null)
+ {
+ var hitsIcon = false;
+ var target = me.getSource();
+
+ for (var i = 0; i < this.icons.length && !hitsIcon; i++)
+ {
+ hitsIcon = target == this.icons[i].node || target.parentNode == this.icons[i].node;
+ }
+
+ if (!hitsIcon)
+ {
+ this.updateIcons(this.currentState, this.icons, me);
+ }
+ }
+ }
+ else
+ {
+ this.constraintHandler.reset();
+ }
+};
+
+/**
+ * Function: getTargetPerimeterPoint
+ *
+ * Returns the perimeter point for the given target state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the target cell state.
+ * me - <mxMouseEvent> that represents the mouse move.
+ */
+mxConnectionHandler.prototype.getTargetPerimeterPoint = function(state, me)
+{
+ var result = null;
+ var view = state.view;
+ var targetPerimeter = view.getPerimeterFunction(state);
+
+ if (targetPerimeter != null)
+ {
+ var next = (this.waypoints != null && this.waypoints.length > 0) ?
+ this.waypoints[this.waypoints.length - 1] :
+ new mxPoint(this.previous.getCenterX(), this.previous.getCenterY());
+ var tmp = targetPerimeter(view.getPerimeterBounds(state),
+ this.edgeState, next, false);
+
+ if (tmp != null)
+ {
+ result = tmp;
+ }
+ }
+ else
+ {
+ result = new mxPoint(state.getCenterX(), state.getCenterY());
+ }
+
+ return result;
+};
+
+/**
+ * Function: getSourcePerimeterPoint
+ *
+ * Hook to update the icon position(s) based on a mouseOver event. This is
+ * an empty implementation.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the target cell state.
+ * next - <mxPoint> that represents the next point along the previewed edge.
+ * me - <mxMouseEvent> that represents the mouse move.
+ */
+mxConnectionHandler.prototype.getSourcePerimeterPoint = function(state, next, me)
+{
+ var result = null;
+ var view = state.view;
+ var sourcePerimeter = view.getPerimeterFunction(state);
+
+ if (sourcePerimeter != null)
+ {
+ var tmp = sourcePerimeter(view.getPerimeterBounds(state), state, next, false);
+
+ if (tmp != null)
+ {
+ result = tmp;
+ }
+ }
+ else
+ {
+ result = new mxPoint(state.getCenterX(), state.getCenterY());
+ }
+
+ return result;
+};
+
+
+/**
+ * Function: updateIcons
+ *
+ * Hook to update the icon position(s) based on a mouseOver event. This is
+ * an empty implementation.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> under the mouse.
+ * icons - Array of currently displayed icons.
+ * me - <mxMouseEvent> that contains the mouse event.
+ */
+mxConnectionHandler.prototype.updateIcons = function(state, icons, me)
+{
+ // empty
+};
+
+/**
+ * Function: isStopEvent
+ *
+ * Returns true if the given mouse up event should stop this handler. The
+ * connection will be created if <error> is null. Note that this is only
+ * called if <waypointsEnabled> is true. This implemtation returns true
+ * if there is a cell state in the given event.
+ */
+mxConnectionHandler.prototype.isStopEvent = function(me)
+{
+ return me.getState() != null;
+};
+
+/**
+ * Function: addWaypoint
+ *
+ * Adds the waypoint for the given event to <waypoints>.
+ */
+mxConnectionHandler.prototype.addWaypointForEvent = function(me)
+{
+ var point = mxUtils.convertPoint(this.graph.container, me.getX(), me.getY());
+ var dx = Math.abs(point.x - this.first.x);
+ var dy = Math.abs(point.y - this.first.y);
+ var addPoint = this.waypoints != null || (this.mouseDownCounter > 1 &&
+ (dx > this.graph.tolerance || dy > this.graph.tolerance));
+
+ if (addPoint)
+ {
+ if (this.waypoints == null)
+ {
+ this.waypoints = [];
+ }
+
+ var scale = this.graph.view.scale;
+ var point = new mxPoint(this.graph.snap(me.getGraphX() / scale) * scale,
+ this.graph.snap(me.getGraphY() / scale) * scale);
+ this.waypoints.push(point);
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by inserting the new connection.
+ */
+mxConnectionHandler.prototype.mouseUp = function(sender, me)
+{
+ if (!me.isConsumed() && this.isConnecting())
+ {
+ if (this.waypointsEnabled && !this.isStopEvent(me))
+ {
+ this.addWaypointForEvent(me);
+ me.consume();
+
+ return;
+ }
+
+ // Inserts the edge if no validation error exists
+ if (this.error == null)
+ {
+ var source = (this.previous != null) ? this.previous.cell : null;
+ var target = null;
+
+ if (this.constraintHandler.currentConstraint != null &&
+ this.constraintHandler.currentFocus != null)
+ {
+ target = this.constraintHandler.currentFocus.cell;
+ }
+
+ if (target == null && this.marker.hasValidState())
+ {
+ target = this.marker.validState.cell;
+ }
+
+ this.connect(source, target, me.getEvent(), me.getCell());
+ }
+ else
+ {
+ // Selects the source terminal for self-references
+ if (this.previous != null && this.marker.validState != null &&
+ this.previous.cell == this.marker.validState.cell)
+ {
+ this.graph.selectCellForEvent(this.marker.source, evt);
+ }
+
+ // Displays the error message if it is not an empty string,
+ // for empty error messages, the event is silently dropped
+ if (this.error.length > 0)
+ {
+ this.graph.validationAlert(this.error);
+ }
+ }
+
+ // Redraws the connect icons and resets the handler state
+ this.destroyIcons(this.icons);
+ me.consume();
+ }
+
+ if (this.first != null)
+ {
+ this.reset();
+ }
+
+ this.tapAndHoldInProgress = false;
+ this.tapAndHoldValid = false;
+};
+
+/**
+ * Function: reset
+ *
+ * Resets the state of this handler.
+ */
+mxConnectionHandler.prototype.reset = function()
+{
+ if (this.shape != null)
+ {
+ this.shape.destroy();
+ this.shape = null;
+ }
+
+ this.destroyIcons(this.icons);
+ this.icons = null;
+ this.marker.reset();
+ this.constraintHandler.reset();
+ this.selectedIcon = null;
+ this.edgeState = null;
+ this.previous = null;
+ this.error = null;
+ this.sourceConstraint = null;
+ this.mouseDownCounter = 0;
+ this.first = null;
+ this.icon = null;
+
+ this.fireEvent(new mxEventObject(mxEvent.RESET));
+};
+
+/**
+ * Function: drawPreview
+ *
+ * Redraws the preview edge using the color and width returned by
+ * <getEdgeColor> and <getEdgeWidth>.
+ */
+mxConnectionHandler.prototype.drawPreview = function()
+{
+ var valid = this.error == null;
+ var color = this.getEdgeColor(valid);
+
+ if (this.shape.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.shape.innerNode.setAttribute('stroke', color);
+ }
+ else
+ {
+ this.shape.node.strokecolor = color;
+ }
+
+ this.shape.strokewidth = this.getEdgeWidth(valid);
+ this.shape.redraw();
+
+ // Workaround to force a repaint in AppleWebKit
+ mxUtils.repaintGraph(this.graph, this.shape.points[1]);
+};
+
+/**
+ * Function: getEdgeColor
+ *
+ * Returns the color used to draw the preview edge. This returns green if
+ * there is no edge validation error and red otherwise.
+ *
+ * Parameters:
+ *
+ * valid - Boolean indicating if the color for a valid edge should be
+ * returned.
+ */
+mxConnectionHandler.prototype.getEdgeColor = function(valid)
+{
+ return (valid) ? mxConstants.VALID_COLOR : mxConstants.INVALID_COLOR;
+};
+
+/**
+ * Function: getEdgeWidth
+ *
+ * Returns the width used to draw the preview edge. This returns 3 if
+ * there is no edge validation error and 1 otherwise.
+ *
+ * Parameters:
+ *
+ * valid - Boolean indicating if the width for a valid edge should be
+ * returned.
+ */
+mxConnectionHandler.prototype.getEdgeWidth = function(valid)
+{
+ return (valid) ? 3 : 1;
+};
+
+/**
+ * Function: connect
+ *
+ * Connects the given source and target using a new edge. This
+ * implementation uses <createEdge> to create the edge.
+ *
+ * Parameters:
+ *
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * evt - Mousedown event of the connect gesture.
+ * dropTarget - <mxCell> that represents the cell under the mouse when it was
+ * released.
+ */
+mxConnectionHandler.prototype.connect = function(source, target, evt, dropTarget)
+{
+ if (target != null || this.isCreateTarget() || this.graph.allowDanglingEdges)
+ {
+ // Uses the common parent of source and target or
+ // the default parent to insert the edge
+ var model = this.graph.getModel();
+ var edge = null;
+
+ model.beginUpdate();
+ try
+ {
+ if (source != null && target == null && this.isCreateTarget())
+ {
+ target = this.createTargetVertex(evt, source);
+
+ if (target != null)
+ {
+ dropTarget = this.graph.getDropTarget([target], evt, dropTarget);
+
+ // Disables edges as drop targets if the target cell was created
+ // FIXME: Should not shift if vertex was aligned (same in Java)
+ if (dropTarget == null || !this.graph.getModel().isEdge(dropTarget))
+ {
+ var pstate = this.graph.getView().getState(dropTarget);
+
+ if (pstate != null)
+ {
+ var tmp = model.getGeometry(target);
+ tmp.x -= pstate.origin.x;
+ tmp.y -= pstate.origin.y;
+ }
+ }
+ else
+ {
+ dropTarget = this.graph.getDefaultParent();
+ }
+
+ this.graph.addCell(target, dropTarget);
+ }
+ }
+
+ var parent = this.graph.getDefaultParent();
+
+ if (source != null && target != null &&
+ model.getParent(source) == model.getParent(target) &&
+ model.getParent(model.getParent(source)) != model.getRoot())
+ {
+ parent = model.getParent(source);
+
+ if ((source.geometry != null && source.geometry.relative) &&
+ (target.geometry != null && target.geometry.relative))
+ {
+ parent = model.getParent(parent);
+ }
+ }
+
+ // Uses the value of the preview edge state for inserting
+ // the new edge into the graph
+ var value = null;
+ var style = null;
+
+ if (this.edgeState != null)
+ {
+ value = this.edgeState.cell.value;
+ style = this.edgeState.cell.style;
+ }
+
+ edge = this.insertEdge(parent, null, value, source, target, style);
+
+ if (edge != null)
+ {
+ // Updates the connection constraints
+ this.graph.setConnectionConstraint(edge, source, true, this.sourceConstraint);
+ this.graph.setConnectionConstraint(edge, target, false, this.constraintHandler.currentConstraint);
+
+ // Uses geometry of the preview edge state
+ if (this.edgeState != null)
+ {
+ model.setGeometry(edge, this.edgeState.cell.geometry);
+ }
+
+ // Makes sure the edge has a non-null, relative geometry
+ var geo = model.getGeometry(edge);
+
+ if (geo == null)
+ {
+ geo = new mxGeometry();
+ geo.relative = true;
+
+ model.setGeometry(edge, geo);
+ }
+
+ // Uses scaled waypoints in geometry
+ if (this.waypoints != null && this.waypoints.length > 0)
+ {
+ var s = this.graph.view.scale;
+ var tr = this.graph.view.translate;
+ geo.points = [];
+
+ for (var i = 0; i < this.waypoints.length; i++)
+ {
+ var pt = this.waypoints[i];
+ geo.points.push(new mxPoint(pt.x / s - tr.x, pt.y / s - tr.y));
+ }
+ }
+
+ if (target == null)
+ {
+ var pt = this.graph.getPointForEvent(evt, false);
+ pt.x -= this.graph.panDx / this.graph.view.scale;
+ pt.y -= this.graph.panDy / this.graph.view.scale;
+ geo.setTerminalPoint(pt, false);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CONNECT,
+ 'cell', edge, 'event', evt, 'target', dropTarget));
+ }
+ }
+ catch (e)
+ {
+ mxLog.show();
+ mxLog.debug(e.message);
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+
+ if (this.select)
+ {
+ this.selectCells(edge, target);
+ }
+ }
+};
+
+/**
+ * Function: selectCells
+ *
+ * Selects the given edge after adding a new connection. The target argument
+ * contains the target vertex if one has been inserted.
+ */
+mxConnectionHandler.prototype.selectCells = function(edge, target)
+{
+ this.graph.setSelectionCell(edge);
+};
+
+/**
+ * Function: insertEdge
+ *
+ * Creates, inserts and returns the new edge for the given parameters. This
+ * implementation does only use <createEdge> if <factoryMethod> is defined,
+ * otherwise <mxGraph.insertEdge> will be used.
+ */
+mxConnectionHandler.prototype.insertEdge = function(parent, id, value, source, target, style)
+{
+ if (this.factoryMethod == null)
+ {
+ return this.graph.insertEdge(parent, id, value, source, target, style);
+ }
+ else
+ {
+ var edge = this.createEdge(value, source, target, style);
+ edge = this.graph.addEdge(edge, parent, source, target);
+
+ return edge;
+ }
+};
+
+/**
+ * Function: createTargetVertex
+ *
+ * Hook method for creating new vertices on the fly if no target was
+ * under the mouse. This is only called if <createTarget> is true and
+ * returns null.
+ *
+ * Parameters:
+ *
+ * evt - Mousedown event of the connect gesture.
+ * source - <mxCell> that represents the source terminal.
+ */
+mxConnectionHandler.prototype.createTargetVertex = function(evt, source)
+{
+ // Uses the first non-relative source
+ var geo = this.graph.getCellGeometry(source);
+
+ while (geo != null && geo.relative)
+ {
+ source = this.graph.getModel().getParent(source);
+ geo = this.graph.getCellGeometry(source);
+ }
+
+ var clone = this.graph.cloneCells([source])[0];
+ var geo = this.graph.getModel().getGeometry(clone);
+
+ if (geo != null)
+ {
+ var point = this.graph.getPointForEvent(evt);
+ geo.x = this.graph.snap(point.x - geo.width / 2) - this.graph.panDx / this.graph.view.scale;
+ geo.y = this.graph.snap(point.y - geo.height / 2) - this.graph.panDy / this.graph.view.scale;
+
+ // Aligns with source if within certain tolerance
+ if (this.first != null)
+ {
+ var sourceState = this.graph.view.getState(source);
+
+ if (sourceState != null)
+ {
+ var tol = this.getAlignmentTolerance();
+
+ if (Math.abs(this.graph.snap(this.first.x) -
+ this.graph.snap(point.x)) <= tol)
+ {
+ geo.x = sourceState.x;
+ }
+ else if (Math.abs(this.graph.snap(this.first.y) -
+ this.graph.snap(point.y)) <= tol)
+ {
+ geo.y = sourceState.y;
+ }
+ }
+ }
+ }
+
+ return clone;
+};
+
+/**
+ * Function: getAlignmentTolerance
+ *
+ * Returns the tolerance for aligning new targets to sources.
+ */
+mxConnectionHandler.prototype.getAlignmentTolerance = function()
+{
+ return (this.graph.isGridEnabled()) ?
+ this.graph.gridSize : this.graph.tolerance;
+};
+
+/**
+ * Function: createEdge
+ *
+ * Creates and returns a new edge using <factoryMethod> if one exists. If
+ * no factory method is defined, then a new default edge is returned. The
+ * source and target arguments are informal, the actual connection is
+ * setup later by the caller of this function.
+ *
+ * Parameters:
+ *
+ * value - Value to be used for creating the edge.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * style - Optional style from the preview edge.
+ */
+mxConnectionHandler.prototype.createEdge = function(value, source, target, style)
+{
+ var edge = null;
+
+ // Creates a new edge using the factoryMethod
+ if (this.factoryMethod != null)
+ {
+ edge = this.factoryMethod(source, target, style);
+ }
+
+ if (edge == null)
+ {
+ edge = new mxCell(value || '');
+ edge.setEdge(true);
+ edge.setStyle(style);
+
+ var geo = new mxGeometry();
+ geo.relative = true;
+ edge.setGeometry(geo);
+ }
+
+ return edge;
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes. This should be
+ * called on all instances. It is called automatically for the built-in
+ * instance created for each <mxGraph>.
+ */
+mxConnectionHandler.prototype.destroy = function()
+{
+ this.graph.removeMouseListener(this);
+
+ if (this.shape != null)
+ {
+ this.shape.destroy();
+ this.shape = null;
+ }
+
+ if (this.marker != null)
+ {
+ this.marker.destroy();
+ this.marker = null;
+ }
+
+ if (this.constraintHandler != null)
+ {
+ this.constraintHandler.destroy();
+ this.constraintHandler = null;
+ }
+
+ if (this.changeHandler != null)
+ {
+ this.graph.getModel().removeListener(this.changeHandler);
+ this.graph.getView().removeListener(this.changeHandler);
+ this.changeHandler = null;
+ }
+
+ if (this.drillHandler != null)
+ {
+ this.graph.removeListener(this.drillHandler);
+ this.graph.getView().removeListener(this.drillHandler);
+ this.drillHandler = null;
+ }
+};
diff --git a/src/js/handler/mxConstraintHandler.js b/src/js/handler/mxConstraintHandler.js
new file mode 100644
index 0000000..39b3ab6
--- /dev/null
+++ b/src/js/handler/mxConstraintHandler.js
@@ -0,0 +1,308 @@
+/**
+ * $Id: mxConstraintHandler.js,v 1.15 2012-11-01 16:13:41 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxConstraintHandler
+ *
+ * Handles constraints on connection targets. This class is in charge of
+ * showing fixed points when the mouse is over a vertex and handles constraints
+ * to establish new connections.
+ *
+ * Constructor: mxConstraintHandler
+ *
+ * Constructs an new constraint handler.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * factoryMethod - Optional function to create the edge. The function takes
+ * the source and target <mxCell> as the first and second argument and
+ * returns the <mxCell> that represents the new edge.
+ */
+function mxConstraintHandler(graph)
+{
+ this.graph = graph;
+};
+
+/**
+ * Variable: pointImage
+ *
+ * <mxImage> to be used as the image for fixed connection points.
+ */
+mxConstraintHandler.prototype.pointImage = new mxImage(mxClient.imageBasePath + '/point.gif', 5, 5);
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxConstraintHandler.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxConstraintHandler.prototype.enabled = true;
+
+/**
+ * Variable: highlightColor
+ *
+ * Specifies the color for the highlight. Default is <mxConstants.DEFAULT_VALID_COLOR>.
+ */
+mxConstraintHandler.prototype.highlightColor = mxConstants.DEFAULT_VALID_COLOR;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxConstraintHandler.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxConstraintHandler.prototype.setEnabled = function(enabled)
+{
+ this.enabled = enabled;
+};
+
+/**
+ * Function: reset
+ *
+ * Resets the state of this handler.
+ */
+mxConstraintHandler.prototype.reset = function()
+{
+ if (this.focusIcons != null)
+ {
+ for (var i = 0; i < this.focusIcons.length; i++)
+ {
+ this.focusIcons[i].destroy();
+ }
+
+ this.focusIcons = null;
+ }
+
+ if (this.focusHighlight != null)
+ {
+ this.focusHighlight.destroy();
+ this.focusHighlight = null;
+ }
+
+ this.currentConstraint = null;
+ this.currentFocusArea = null;
+ this.currentPoint = null;
+ this.currentFocus = null;
+ this.focusPoints = null;
+};
+
+/**
+ * Function: getTolerance
+ *
+ * Returns the tolerance to be used for intersecting connection points.
+ */
+mxConstraintHandler.prototype.getTolerance = function()
+{
+ return this.graph.getTolerance();
+};
+
+/**
+ * Function: getImageForConstraint
+ *
+ * Returns the tolerance to be used for intersecting connection points.
+ */
+mxConstraintHandler.prototype.getImageForConstraint = function(state, constraint, point)
+{
+ return this.pointImage;
+};
+
+/**
+ * Function: isEventIgnored
+ *
+ * Returns true if the given <mxMouseEvent> should be ignored in <update>. This
+ * implementation always returns false.
+ */
+mxConstraintHandler.prototype.isEventIgnored = function(me, source)
+{
+ return false;
+};
+
+/**
+ * Function: update
+ *
+ * Updates the state of this handler based on the given <mxMouseEvent>.
+ * Source is a boolean indicating if the cell is a source or target.
+ */
+mxConstraintHandler.prototype.update = function(me, source)
+{
+ if (this.isEnabled() && !this.isEventIgnored(me))
+ {
+ var tol = this.getTolerance();
+ var mouse = new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol);
+ var connectable = (me.getCell() != null) ? this.graph.isCellConnectable(me.getCell()) : false;
+
+ if ((this.currentFocusArea == null || (!mxUtils.intersects(this.currentFocusArea, mouse) ||
+ (me.getState() != null && this.currentFocus != null && connectable))))
+ {
+ this.currentFocusArea = null;
+
+ if (me.getState() != this.currentFocus)
+ {
+ this.currentFocus = null;
+ this.constraints = (me.getState() != null && connectable) ?
+ this.graph.getAllConnectionConstraints(me.getState(), source) : null;
+
+ // Only uses cells which have constraints
+ if (this.constraints != null)
+ {
+ this.currentFocus = me.getState();
+ this.currentFocusArea = new mxRectangle(me.getState().x, me.getState().y, me.getState().width, me.getState().height);
+
+ if (this.focusIcons != null)
+ {
+ for (var i = 0; i < this.focusIcons.length; i++)
+ {
+ this.focusIcons[i].destroy();
+ }
+
+ this.focusIcons = null;
+ this.focusPoints = null;
+ }
+
+ this.focusIcons = [];
+ this.focusPoints = [];
+
+ for (var i = 0; i < this.constraints.length; i++)
+ {
+ var cp = this.graph.getConnectionPoint(me.getState(), this.constraints[i]);
+ var img = this.getImageForConstraint(me.getState(), this.constraints[i], cp);
+
+ var src = img.src;
+ var bounds = new mxRectangle(cp.x - img.width / 2,
+ cp.y - img.height / 2, img.width, img.height);
+ var icon = new mxImageShape(bounds, src);
+ icon.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_SVG :
+ mxConstants.DIALECT_VML;
+ icon.init(this.graph.getView().getOverlayPane());
+
+ // Move the icon behind all other overlays
+ if (icon.node.previousSibling != null)
+ {
+ icon.node.parentNode.insertBefore(icon.node, icon.node.parentNode.firstChild);
+ }
+
+ var getState = mxUtils.bind(this, function()
+ {
+ return (this.currentFocus != null) ? this.currentFocus : me.getState();
+ });
+
+ icon.redraw();
+
+ mxEvent.redirectMouseEvents(icon.node, this.graph, getState);
+ this.currentFocusArea.add(icon.bounds);
+ this.focusIcons.push(icon);
+ this.focusPoints.push(cp);
+ }
+
+ this.currentFocusArea.grow(tol);
+ }
+ else if (this.focusIcons != null)
+ {
+ if (this.focusHighlight != null)
+ {
+ this.focusHighlight.destroy();
+ this.focusHighlight = null;
+ }
+
+ for (var i = 0; i < this.focusIcons.length; i++)
+ {
+ this.focusIcons[i].destroy();
+ }
+
+ this.focusIcons = null;
+ this.focusPoints = null;
+ }
+ }
+ }
+
+ this.currentConstraint = null;
+ this.currentPoint = null;
+
+ if (this.focusIcons != null && this.constraints != null &&
+ (me.getState() == null || this.currentFocus == me.getState()))
+ {
+ for (var i = 0; i < this.focusIcons.length; i++)
+ {
+ if (mxUtils.intersects(this.focusIcons[i].bounds, mouse))
+ {
+ this.currentConstraint = this.constraints[i];
+ this.currentPoint = this.focusPoints[i];
+
+ var tmp = this.focusIcons[i].bounds.clone();
+ tmp.grow((mxClient.IS_IE) ? 3 : 2);
+
+ if (mxClient.IS_IE)
+ {
+ tmp.width -= 1;
+ tmp.height -= 1;
+ }
+
+ if (this.focusHighlight == null)
+ {
+ var hl = new mxRectangleShape(tmp, null, this.highlightColor, 3);
+ hl.dialect = (this.graph.dialect == mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_SVG :
+ mxConstants.DIALECT_VML;
+ hl.init(this.graph.getView().getOverlayPane());
+ this.focusHighlight = hl;
+
+ var getState = mxUtils.bind(this, function()
+ {
+ return (this.currentFocus != null) ? this.currentFocus : me.getState();
+ });
+
+ mxEvent.redirectMouseEvents(hl.node, this.graph, getState/*, mouseDown*/);
+ }
+ else
+ {
+ this.focusHighlight.bounds = tmp;
+ this.focusHighlight.redraw();
+ }
+
+ break;
+ }
+ }
+ }
+
+ if (this.currentConstraint == null &&
+ this.focusHighlight != null)
+ {
+ this.focusHighlight.destroy();
+ this.focusHighlight = null;
+ }
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroy this handler.
+ */
+mxConstraintHandler.prototype.destroy = function()
+{
+ this.reset();
+}; \ No newline at end of file
diff --git a/src/js/handler/mxEdgeHandler.js b/src/js/handler/mxEdgeHandler.js
new file mode 100644
index 0000000..2028342
--- /dev/null
+++ b/src/js/handler/mxEdgeHandler.js
@@ -0,0 +1,1529 @@
+/**
+ * $Id: mxEdgeHandler.js,v 1.178 2012-09-12 09:16:23 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxEdgeHandler
+ *
+ * Graph event handler that reconnects edges and modifies control points and
+ * the edge label location. Uses <mxTerminalMarker> for finding and
+ * highlighting new source and target vertices. This handler is automatically
+ * created in <mxGraph.createHandler> for each selected edge.
+ *
+ * To enable adding/removing control points, the following code can be used:
+ *
+ * (code)
+ * mxEdgeHandler.prototype.addEnabled = true;
+ * mxEdgeHandler.prototype.removeEnabled = true;
+ * (end)
+ *
+ * Note: This experimental feature is not recommended for production use.
+ *
+ * Constructor: mxEdgeHandler
+ *
+ * Constructs an edge handler for the specified <mxCellState>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the cell to be handled.
+ */
+function mxEdgeHandler(state)
+{
+ if (state != null)
+ {
+ this.state = state;
+ this.init();
+ }
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxEdgeHandler.prototype.graph = null;
+
+/**
+ * Variable: state
+ *
+ * Reference to the <mxCellState> being modified.
+ */
+mxEdgeHandler.prototype.state = null;
+
+/**
+ * Variable: marker
+ *
+ * Holds the <mxTerminalMarker> which is used for highlighting terminals.
+ */
+mxEdgeHandler.prototype.marker = null;
+
+/**
+ * Variable: constraintHandler
+ *
+ * Holds the <mxConstraintHandler> used for drawing and highlighting
+ * constraints.
+ */
+mxEdgeHandler.prototype.constraintHandler = null;
+
+/**
+ * Variable: error
+ *
+ * Holds the current validation error while a connection is being changed.
+ */
+mxEdgeHandler.prototype.error = null;
+
+/**
+ * Variable: shape
+ *
+ * Holds the <mxShape> that represents the preview edge.
+ */
+mxEdgeHandler.prototype.shape = null;
+
+/**
+ * Variable: bends
+ *
+ * Holds the <mxShapes> that represent the points.
+ */
+mxEdgeHandler.prototype.bends = null;
+
+/**
+ * Variable: labelShape
+ *
+ * Holds the <mxShape> that represents the label position.
+ */
+mxEdgeHandler.prototype.labelShape = null;
+
+/**
+ * Variable: cloneEnabled
+ *
+ * Specifies if cloning by control-drag is enabled. Default is true.
+ */
+mxEdgeHandler.prototype.cloneEnabled = true;
+
+/**
+ * Variable: addEnabled
+ *
+ * Specifies if adding bends by shift-click is enabled. Default is false.
+ * Note: This experimental feature is not recommended for production use.
+ */
+mxEdgeHandler.prototype.addEnabled = false;
+
+/**
+ * Variable: removeEnabled
+ *
+ * Specifies if removing bends by shift-click is enabled. Default is false.
+ * Note: This experimental feature is not recommended for production use.
+ */
+mxEdgeHandler.prototype.removeEnabled = false;
+
+/**
+ * Variable: preferHtml
+ *
+ * Specifies if bends should be added to the graph container. This is updated
+ * in <init> based on whether the edge or one of its terminals has an HTML
+ * label in the container.
+ */
+mxEdgeHandler.prototype.preferHtml = false;
+
+/**
+ * Variable: allowHandleBoundsCheck
+ *
+ * Specifies if the bounds of handles should be used for hit-detection in IE
+ * Default is true.
+ */
+mxEdgeHandler.prototype.allowHandleBoundsCheck = true;
+
+/**
+ * Variable: snapToTerminals
+ *
+ * Specifies if waypoints should snap to the routing centers of terminals.
+ * Default is false.
+ */
+mxEdgeHandler.prototype.snapToTerminals = false;
+
+/**
+ * Variable: crisp
+ *
+ * Specifies if the edge handles should be rendered in crisp mode. Default is
+ * true.
+ */
+mxEdgeHandler.prototype.crisp = true;
+
+/**
+ * Variable: handleImage
+ *
+ * Optional <mxImage> to be used as handles. Default is null.
+ */
+mxEdgeHandler.prototype.handleImage = null;
+
+/**
+ * Variable: tolerance
+ *
+ * Optional tolerance for hit-detection in <getHandleForEvent>. Default is 0.
+ */
+mxEdgeHandler.prototype.tolerance = 0;
+
+/**
+ * Function: init
+ *
+ * Initializes the shapes required for this edge handler.
+ */
+mxEdgeHandler.prototype.init = function()
+{
+ this.graph = this.state.view.graph;
+ this.marker = this.createMarker();
+ this.constraintHandler = new mxConstraintHandler(this.graph);
+
+ // Clones the original points from the cell
+ // and makes sure at least one point exists
+ this.points = [];
+
+ // Uses the absolute points of the state
+ // for the initial configuration and preview
+ this.abspoints = this.getSelectionPoints(this.state);
+ this.shape = this.createSelectionShape(this.abspoints);
+ this.shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ this.shape.init(this.graph.getView().getOverlayPane());
+ this.shape.node.style.cursor = mxConstants.CURSOR_MOVABLE_EDGE;
+
+ // Event handling
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ mxEvent.addListener(this.shape.node, 'dblclick',
+ mxUtils.bind(this, function(evt)
+ {
+ this.graph.dblClick(evt, this.state.cell);
+ })
+ );
+ mxEvent.addListener(this.shape.node, md,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.addEnabled && this.isAddPointEvent(evt))
+ {
+ this.addPoint(this.state, evt);
+ }
+ else
+ {
+ this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+ new mxMouseEvent(evt, this.state));
+ }
+ })
+ );
+ mxEvent.addListener(this.shape.node, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ var cell = this.state.cell;
+
+ // Finds the cell under the mouse if the edge is being connected
+ // in which case the edge is never highlighted as it cannot
+ // be its own source or target terminal (transparent preview)
+ if (this.index != null)
+ {
+ var pt = mxUtils.convertPoint(this.graph.container,
+ mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+ cell = this.graph.getCellAt(pt.x, pt.y);
+
+ // Swimlane content area is transparent in this case
+ if (this.graph.isSwimlane(cell) && this.graph.hitsSwimlaneContent(cell, pt.x, pt.y))
+ {
+ cell = null;
+ }
+ }
+
+ this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, this.graph.getView().getState(cell)));
+ })
+ );
+ mxEvent.addListener(this.shape.node, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ this.graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt, this.state));
+ })
+ );
+
+ // Updates preferHtml
+ this.preferHtml = this.state.text != null &&
+ this.state.text.node.parentNode == this.graph.container;
+
+ if (!this.preferHtml)
+ {
+ // Checks source terminal
+ var sourceState = this.state.getVisibleTerminalState(true);
+
+ if (sourceState != null)
+ {
+ this.preferHtml = sourceState.text != null &&
+ sourceState.text.node.parentNode == this.graph.container;
+ }
+
+ if (!this.preferHtml)
+ {
+ // Checks target terminal
+ var targetState = this.state.getVisibleTerminalState(false);
+
+ if (targetState != null)
+ {
+ this.preferHtml = targetState.text != null &&
+ targetState.text.node.parentNode == this.graph.container;
+ }
+ }
+ }
+
+ // Creates bends for the non-routed absolute points
+ // or bends that don't correspond to points
+ if (this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells ||
+ mxGraphHandler.prototype.maxCells <= 0)
+ {
+ this.bends = this.createBends();
+ }
+
+ // Adds a rectangular handle for the label position
+ this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
+ this.labelShape = new mxRectangleShape(new mxRectangle(),
+ mxConstants.LABEL_HANDLE_FILLCOLOR,
+ mxConstants.HANDLE_STROKECOLOR);
+ this.initBend(this.labelShape);
+ this.labelShape.node.style.cursor = mxConstants.CURSOR_LABEL_HANDLE;
+ mxEvent.redirectMouseEvents(this.labelShape.node, this.graph, this.state);
+
+ this.redraw();
+};
+
+/**
+ * Function: isAddPointEvent
+ *
+ * Returns true if the given event is a trigger to add a new point. This
+ * implementation returns true if shift is pressed.
+ */
+mxEdgeHandler.prototype.isAddPointEvent = function(evt)
+{
+ return mxEvent.isShiftDown(evt);
+};
+
+/**
+ * Function: isRemovePointEvent
+ *
+ * Returns true if the given event is a trigger to remove a point. This
+ * implementation returns true if shift is pressed.
+ */
+mxEdgeHandler.prototype.isRemovePointEvent = function(evt)
+{
+ return mxEvent.isShiftDown(evt);
+};
+
+/**
+ * Function: getSelectionPoints
+ *
+ * Returns the list of points that defines the selection stroke.
+ */
+mxEdgeHandler.prototype.getSelectionPoints = function(state)
+{
+ return state.absolutePoints;
+};
+
+/**
+ * Function: createSelectionShape
+ *
+ * Creates the shape used to draw the selection border.
+ */
+mxEdgeHandler.prototype.createSelectionShape = function(points)
+{
+ var shape = new mxPolyline(points, this.getSelectionColor());
+ shape.strokewidth = this.getSelectionStrokeWidth();
+ shape.isDashed = this.isSelectionDashed();
+
+ return shape;
+};
+
+/**
+ * Function: getSelectionColor
+ *
+ * Returns <mxConstants.EDGE_SELECTION_COLOR>.
+ */
+mxEdgeHandler.prototype.getSelectionColor = function()
+{
+ return mxConstants.EDGE_SELECTION_COLOR;
+};
+
+/**
+ * Function: getSelectionStrokeWidth
+ *
+ * Returns <mxConstants.EDGE_SELECTION_STROKEWIDTH>.
+ */
+mxEdgeHandler.prototype.getSelectionStrokeWidth = function()
+{
+ return mxConstants.EDGE_SELECTION_STROKEWIDTH;
+};
+
+/**
+ * Function: isSelectionDashed
+ *
+ * Returns <mxConstants.EDGE_SELECTION_DASHED>.
+ */
+mxEdgeHandler.prototype.isSelectionDashed = function()
+{
+ return mxConstants.EDGE_SELECTION_DASHED;
+};
+
+/**
+ * Function: isConnectableCell
+ *
+ * Returns true if the given cell is connectable. This is a hook to
+ * disable floating connections. This implementation returns true.
+ */
+mxEdgeHandler.prototype.isConnectableCell = function(cell)
+{
+ return true;
+};
+
+/**
+ * Function: createMarker
+ *
+ * Creates and returns the <mxCellMarker> used in <marker>.
+ */
+mxEdgeHandler.prototype.createMarker = function()
+{
+ var marker = new mxCellMarker(this.graph);
+ var self = this; // closure
+
+ // Only returns edges if they are connectable and never returns
+ // the edge that is currently being modified
+ marker.getCell = function(me)
+ {
+ var cell = mxCellMarker.prototype.getCell.apply(this, arguments);
+
+ if (!self.isConnectableCell(cell))
+ {
+ return null;
+ }
+
+ var model = self.graph.getModel();
+
+ if (cell == self.state.cell || (cell != null &&
+ !self.graph.connectableEdges && model.isEdge(cell)))
+ {
+ cell = null;
+ }
+
+ return cell;
+ };
+
+ // Sets the highlight color according to validateConnection
+ marker.isValidState = function(state)
+ {
+ var model = self.graph.getModel();
+ var other = self.graph.view.getTerminalPort(state,
+ self.graph.view.getState(model.getTerminal(self.state.cell,
+ !self.isSource)), !self.isSource);
+ var otherCell = (other != null) ? other.cell : null;
+ var source = (self.isSource) ? state.cell : otherCell;
+ var target = (self.isSource) ? otherCell : state.cell;
+
+ // Updates the error message of the handler
+ self.error = self.validateConnection(source, target);
+
+ return self.error == null;
+ };
+
+ return marker;
+};
+
+/**
+ * Function: validateConnection
+ *
+ * Returns the error message or an empty string if the connection for the
+ * given source, target pair is not valid. Otherwise it returns null. This
+ * implementation uses <mxGraph.getEdgeValidationError>.
+ *
+ * Parameters:
+ *
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxEdgeHandler.prototype.validateConnection = function(source, target)
+{
+ return this.graph.getEdgeValidationError(this.state.cell, source, target);
+};
+
+/**
+ * Function: createBends
+ *
+ * Creates and returns the bends used for modifying the edge. This is
+ * typically an array of <mxRectangleShapes>.
+ */
+ mxEdgeHandler.prototype.createBends = function()
+ {
+ var cell = this.state.cell;
+ var bends = [];
+
+ for (var i = 0; i < this.abspoints.length; i++)
+ {
+ if (this.isHandleVisible(i))
+ {
+ var source = i == 0;
+ var target = i == this.abspoints.length - 1;
+ var terminal = source || target;
+
+ if (terminal || this.graph.isCellBendable(cell))
+ {
+ var bend = this.createHandleShape(i);
+ this.initBend(bend);
+
+ if (mxClient.IS_TOUCH)
+ {
+ bend.node.setAttribute('pointer-events', 'none');
+ }
+
+ if (this.isHandleEnabled(i))
+ {
+ if (mxClient.IS_TOUCH)
+ {
+ var getState = mxUtils.bind(this, function(evt)
+ {
+ var pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+
+ return this.graph.view.getState(this.graph.getCellAt(pt.x, pt.y));
+ });
+
+ mxEvent.redirectMouseEvents(bend.node, this.graph, getState);
+ }
+ else
+ {
+ bend.node.style.cursor = mxConstants.CURSOR_BEND_HANDLE;
+ mxEvent.redirectMouseEvents(bend.node, this.graph, this.state);
+ }
+ }
+
+ bends.push(bend);
+
+ if (!terminal)
+ {
+ this.points.push(new mxPoint(0,0));
+ bend.node.style.visibility = 'hidden';
+ }
+ }
+ }
+ }
+
+ return bends;
+};
+/**
+ * Function: isHandleEnabled
+ *
+ * Creates the shape used to display the given bend.
+ */
+mxEdgeHandler.prototype.isHandleEnabled = function(index)
+{
+ return true;
+};
+
+/**
+ * Function: isHandleVisible
+ *
+ * Returns true if the handle at the given index is visible.
+ */
+mxEdgeHandler.prototype.isHandleVisible = function(index)
+{
+ return true;
+};
+
+/**
+ * Function: createHandleShape
+ *
+ * Creates the shape used to display the given bend. Note that the index may be
+ * null for special cases, such as when called from
+ * <mxElbowEdgeHandler.createVirtualBend>.
+ */
+mxEdgeHandler.prototype.createHandleShape = function(index)
+{
+ if (this.handleImage != null)
+ {
+ return new mxImageShape(new mxRectangle(0, 0, this.handleImage.width, this.handleImage.height), this.handleImage.src);
+ }
+ else
+ {
+ var s = mxConstants.HANDLE_SIZE;
+
+ if (this.preferHtml)
+ {
+ s -= 1;
+ }
+
+ return new mxRectangleShape(new mxRectangle(0, 0, s, s), mxConstants.HANDLE_FILLCOLOR, mxConstants.HANDLE_STROKECOLOR);
+ }
+};
+
+/**
+ * Function: initBend
+ *
+ * Helper method to initialize the given bend.
+ *
+ * Parameters:
+ *
+ * bend - <mxShape> that represents the bend to be initialized.
+ */
+mxEdgeHandler.prototype.initBend = function(bend)
+{
+ bend.crisp = this.crisp;
+
+ if (this.preferHtml)
+ {
+ bend.dialect = mxConstants.DIALECT_STRICTHTML;
+ bend.init(this.graph.container);
+ }
+ else
+ {
+ bend.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ bend.init(this.graph.getView().getOverlayPane());
+ }
+};
+
+/**
+ * Function: getHandleForEvent
+ *
+ * Returns the index of the handle for the given event.
+ */
+mxEdgeHandler.prototype.getHandleForEvent = function(me)
+{
+ // Finds the handle that triggered the event
+ if (this.bends != null)
+ {
+ // Connection highlight may consume events before they reach sizer handle
+ var tol = this.tolerance;
+ var hit = (this.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ?
+ new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null;
+
+ for (var i = 0; i < this.bends.length; i++)
+ {
+ if (me.isSource(this.bends[i]) || (hit != null &&
+ this.bends[i].node.style.visibility != 'hidden' &&
+ mxUtils.intersects(this.bends[i].bounds, hit)))
+ {
+ return i;
+ }
+ }
+ }
+
+ if (me.isSource(this.labelShape) || me.isSource(this.state.text))
+ {
+ // Workaround for SELECT element not working in Webkit
+ if ((!mxClient.IS_SF && !mxClient.IS_GC) || me.getSource().nodeName != 'SELECT')
+ {
+ return mxEvent.LABEL_HANDLE;
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event by checking if a special element of the handler
+ * was clicked, in which case the index parameter is non-null. The
+ * indices may be one of <LABEL_HANDLE> or the number of the respective
+ * control point. The source and target points are used for reconnecting
+ * the edge.
+ */
+mxEdgeHandler.prototype.mouseDown = function(sender, me)
+{
+ var handle = null;
+
+ // Handles the case where the state in the event points to another
+ // cell if the cell has a HTML label which sits on top of the handles
+ // NOTE: Commented out. This should not be required as all HTML labels
+ // are in order an do not appear behind the handles.
+ //if (mxClient.IS_SVG || me.getState() == this.state)
+ {
+ handle = this.getHandleForEvent(me);
+ }
+
+ if (handle != null && !me.isConsumed() && this.graph.isEnabled() &&
+ !this.graph.isForceMarqueeEvent(me.getEvent()))
+ {
+ if (this.removeEnabled && this.isRemovePointEvent(me.getEvent()))
+ {
+ this.removePoint(this.state, handle);
+ }
+ else if (handle != mxEvent.LABEL_HANDLE || this.graph.isLabelMovable(me.getCell()))
+ {
+ this.start(me.getX(), me.getY(), handle);
+ }
+
+ me.consume();
+ }
+};
+
+/**
+ * Function: start
+ *
+ * Starts the handling of the mouse gesture.
+ */
+mxEdgeHandler.prototype.start = function(x, y, index)
+{
+ this.startX = x;
+ this.startY = y;
+
+ this.isSource = (this.bends == null) ? false : index == 0;
+ this.isTarget = (this.bends == null) ? false : index == this.bends.length - 1;
+ this.isLabel = index == mxEvent.LABEL_HANDLE;
+
+ if (this.isSource || this.isTarget)
+ {
+ var cell = this.state.cell;
+ var terminal = this.graph.model.getTerminal(cell, this.isSource);
+
+ if ((terminal == null && this.graph.isTerminalPointMovable(cell, this.isSource)) ||
+ (terminal != null && this.graph.isCellDisconnectable(cell, terminal, this.isSource)))
+ {
+ this.index = index;
+ }
+ }
+ else
+ {
+ this.index = index;
+ }
+};
+
+/**
+ * Function: clonePreviewState
+ *
+ * Returns a clone of the current preview state for the given point and terminal.
+ */
+mxEdgeHandler.prototype.clonePreviewState = function(point, terminal)
+{
+ return this.state.clone();
+};
+
+/**
+ * Function: getSnapToTerminalTolerance
+ *
+ * Returns the tolerance for the guides. Default value is
+ * gridSize * scale / 2.
+ */
+mxEdgeHandler.prototype.getSnapToTerminalTolerance = function()
+{
+ return this.graph.gridSize * this.graph.view.scale / 2;
+};
+
+/**
+ * Function: getPointForEvent
+ *
+ * Returns the point for the given event.
+ */
+mxEdgeHandler.prototype.getPointForEvent = function(me)
+{
+ var point = new mxPoint(me.getGraphX(), me.getGraphY());
+
+ var tt = this.getSnapToTerminalTolerance();
+ var view = this.graph.getView();
+ var overrideX = false;
+ var overrideY = false;
+
+ if (this.snapToTerminals && tt > 0)
+ {
+ function snapToPoint(pt)
+ {
+ if (pt != null)
+ {
+ var x = pt.x;
+
+ if (Math.abs(point.x - x) < tt)
+ {
+ point.x = x;
+ overrideX = true;
+ }
+
+ var y = pt.y;
+
+ if (Math.abs(point.y - y) < tt)
+ {
+ point.y = y;
+ overrideY = true;
+ }
+ }
+ }
+
+ // Temporary function
+ function snapToTerminal(terminal)
+ {
+ if (terminal != null)
+ {
+ snapToPoint.call(this, new mxPoint(view.getRoutingCenterX(terminal),
+ view.getRoutingCenterY(terminal)));
+ }
+ };
+
+ snapToTerminal.call(this, this.state.getVisibleTerminalState(true));
+ snapToTerminal.call(this, this.state.getVisibleTerminalState(false));
+
+ if (this.abspoints != null)
+ {
+ for (var i = 0; i < this.abspoints; i++)
+ {
+ if (i != this.index)
+ {
+ snapToPoint.call(this, this.abspoints[i]);
+ }
+ }
+ }
+ }
+
+ if (this.graph.isGridEnabledEvent(me.getEvent()))
+ {
+ var scale = view.scale;
+ var tr = view.translate;
+
+ if (!overrideX)
+ {
+ point.x = (this.graph.snap(point.x / scale - tr.x) + tr.x) * scale;
+ }
+
+ if (!overrideY)
+ {
+ point.y = (this.graph.snap(point.y / scale - tr.y) + tr.y) * scale;
+ }
+ }
+
+ return point;
+};
+
+/**
+ * Function: getPreviewTerminalState
+ *
+ * Updates the given preview state taking into account the state of the constraint handler.
+ */
+mxEdgeHandler.prototype.getPreviewTerminalState = function(me)
+{
+ this.constraintHandler.update(me, this.isSource);
+ this.marker.process(me);
+ var currentState = this.marker.getValidState();
+ var result = null;
+
+ if (this.constraintHandler.currentFocus != null &&
+ this.constraintHandler.currentConstraint != null)
+ {
+ this.marker.reset();
+ }
+
+ if (currentState != null)
+ {
+ result = currentState;
+ }
+ else if (this.constraintHandler.currentConstraint != null &&
+ this.constraintHandler.currentFocus != null)
+ {
+ result = this.constraintHandler.currentFocus;
+ }
+
+ return result;
+};
+
+/**
+ * Function: getPreviewPoints
+ *
+ * Updates the given preview state taking into account the state of the constraint handler.
+ */
+mxEdgeHandler.prototype.getPreviewPoints = function(point)
+{
+ var geometry = this.graph.getCellGeometry(this.state.cell);
+ var points = (geometry.points != null) ? geometry.points.slice() : null;
+
+ if (!this.isSource && !this.isTarget)
+ {
+ this.convertPoint(point, false);
+
+ if (points == null)
+ {
+ points = [point];
+ }
+ else
+ {
+ points[this.index - 1] = point;
+ }
+ }
+ else if (this.graph.resetEdgesOnConnect)
+ {
+ points = null;
+ }
+
+ return points;
+};
+
+/**
+ * Function: updatePreviewState
+ *
+ * Updates the given preview state taking into account the state of the constraint handler.
+ */
+mxEdgeHandler.prototype.updatePreviewState = function(edge, point, terminalState)
+{
+ // Computes the points for the edge style and terminals
+ var sourceState = (this.isSource) ? terminalState : this.state.getVisibleTerminalState(true);
+ var targetState = (this.isTarget) ? terminalState : this.state.getVisibleTerminalState(false);
+
+ var sourceConstraint = this.graph.getConnectionConstraint(edge, sourceState, true);
+ var targetConstraint = this.graph.getConnectionConstraint(edge, targetState, false);
+
+ var constraint = this.constraintHandler.currentConstraint;
+
+ if (constraint == null)
+ {
+ constraint = new mxConnectionConstraint();
+ }
+
+ if (this.isSource)
+ {
+ sourceConstraint = constraint;
+ }
+ else if (this.isTarget)
+ {
+ targetConstraint = constraint;
+ }
+
+ if (!this.isSource || sourceState != null)
+ {
+ edge.view.updateFixedTerminalPoint(edge, sourceState, true, sourceConstraint);
+ }
+
+ if (!this.isTarget || targetState != null)
+ {
+ edge.view.updateFixedTerminalPoint(edge, targetState, false, targetConstraint);
+ }
+
+ if ((this.isSource || this.isTarget) && terminalState == null)
+ {
+ edge.setAbsoluteTerminalPoint(point, this.isSource);
+
+ if (this.marker.getMarkedState() == null)
+ {
+ this.error = (this.graph.allowDanglingEdges) ? null : '';
+ }
+ }
+
+ edge.view.updatePoints(edge, this.points, sourceState, targetState);
+ edge.view.updateFloatingTerminalPoints(edge, sourceState, targetState);
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by updating the preview.
+ */
+mxEdgeHandler.prototype.mouseMove = function(sender, me)
+{
+ if (this.index != null && this.marker != null)
+ {
+ var point = this.getPointForEvent(me);
+
+ if (this.isLabel)
+ {
+ this.label.x = point.x;
+ this.label.y = point.y;
+ }
+ else
+ {
+ this.points = this.getPreviewPoints(point);
+ var terminalState = (this.isSource || this.isTarget) ? this.getPreviewTerminalState(me) : null;
+ var clone = this.clonePreviewState(point, (terminalState != null) ? terminalState.cell : null);
+ this.updatePreviewState(clone, point, terminalState);
+
+ // Sets the color of the preview to valid or invalid, updates the
+ // points of the preview and redraws
+ var color = (this.error == null) ? this.marker.validColor :
+ this.marker.invalidColor;
+ this.setPreviewColor(color);
+ this.abspoints = clone.absolutePoints;
+ this.active = true;
+ }
+
+ this.drawPreview();
+ mxEvent.consume(me.getEvent());
+ me.consume();
+ }
+ // Workaround for disabling the connect highlight when over handle
+ else if (mxClient.IS_IE && this.getHandleForEvent(me) != null)
+ {
+ me.consume(false);
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event to applying the previewed changes on the edge by
+ * using <moveLabel>, <connect> or <changePoints>.
+ */
+mxEdgeHandler.prototype.mouseUp = function(sender, me)
+{
+ if (this.index != null && this.marker != null)
+ {
+ var edge = this.state.cell;
+
+ // Ignores event if mouse has not been moved
+ if (me.getX() != this.startX || me.getY() != this.startY)
+ {
+ // Displays the reason for not carriying out the change
+ // if there is an error message with non-zero length
+ if (this.error != null)
+ {
+ if (this.error.length > 0)
+ {
+ this.graph.validationAlert(this.error);
+ }
+ }
+ else if (this.isLabel)
+ {
+ this.moveLabel(this.state, this.label.x, this.label.y);
+ }
+ else if (this.isSource || this.isTarget)
+ {
+ var terminal = null;
+
+ if (this.constraintHandler.currentConstraint != null &&
+ this.constraintHandler.currentFocus != null)
+ {
+ terminal = this.constraintHandler.currentFocus.cell;
+ }
+
+ if (terminal == null && this.marker.hasValidState())
+ {
+ terminal = this.marker.validState.cell;
+ }
+
+ if (terminal != null)
+ {
+ edge = this.connect(edge, terminal, this.isSource,
+ this.graph.isCloneEvent(me.getEvent()) && this.cloneEnabled &&
+ this.graph.isCellsCloneable(), me);
+ }
+ else if (this.graph.isAllowDanglingEdges())
+ {
+ var pt = this.abspoints[(this.isSource) ? 0 : this.abspoints.length - 1];
+ pt.x = pt.x / this.graph.view.scale - this.graph.view.translate.x;
+ pt.y = pt.y / this.graph.view.scale - this.graph.view.translate.y;
+
+ var pstate = this.graph.getView().getState(
+ this.graph.getModel().getParent(edge));
+
+ if (pstate != null)
+ {
+ pt.x -= pstate.origin.x;
+ pt.y -= pstate.origin.y;
+ }
+
+ pt.x -= this.graph.panDx / this.graph.view.scale;
+ pt.y -= this.graph.panDy / this.graph.view.scale;
+
+ // Destroys and rectreates this handler
+ this.changeTerminalPoint(edge, pt, this.isSource);
+ }
+ }
+ else if (this.active)
+ {
+ this.changePoints(edge, this.points);
+ }
+ else
+ {
+ this.graph.getView().invalidate(this.state.cell);
+ this.graph.getView().revalidate(this.state.cell);
+ }
+ }
+
+ // Resets the preview color the state of the handler if this
+ // handler has not been recreated
+ if (this.marker != null)
+ {
+ this.reset();
+
+ // Updates the selection if the edge has been cloned
+ if (edge != this.state.cell)
+ {
+ this.graph.setSelectionCell(edge);
+ }
+ }
+
+ me.consume();
+ }
+};
+
+/**
+ * Function: reset
+ *
+ * Resets the state of this handler.
+ */
+mxEdgeHandler.prototype.reset = function()
+{
+ this.error = null;
+ this.index = null;
+ this.label = null;
+ this.points = null;
+ this.active = false;
+ this.isLabel = false;
+ this.isSource = false;
+ this.isTarget = false;
+ this.marker.reset();
+ this.constraintHandler.reset();
+ this.setPreviewColor(mxConstants.EDGE_SELECTION_COLOR);
+ this.redraw();
+};
+
+/**
+ * Function: setPreviewColor
+ *
+ * Sets the color of the preview to the given value.
+ */
+mxEdgeHandler.prototype.setPreviewColor = function(color)
+{
+ if (this.shape != null && this.shape.node != null)
+ {
+ if (this.shape.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.shape.innerNode.setAttribute('stroke', color);
+ }
+ else
+ {
+ this.shape.node.strokecolor = color;
+ }
+ }
+};
+
+/**
+ * Function: convertPoint
+ *
+ * Converts the given point in-place from screen to unscaled, untranslated
+ * graph coordinates and applies the grid. Returns the given, modified
+ * point instance.
+ *
+ * Parameters:
+ *
+ * point - <mxPoint> to be converted.
+ * gridEnabled - Boolean that specifies if the grid should be applied.
+ */
+mxEdgeHandler.prototype.convertPoint = function(point, gridEnabled)
+{
+ var scale = this.graph.getView().getScale();
+ var tr = this.graph.getView().getTranslate();
+
+ if (gridEnabled)
+ {
+ point.x = this.graph.snap(point.x);
+ point.y = this.graph.snap(point.y);
+ }
+
+ point.x = Math.round(point.x / scale - tr.x);
+ point.y = Math.round(point.y / scale - tr.y);
+
+ var pstate = this.graph.getView().getState(
+ this.graph.getModel().getParent(this.state.cell));
+
+ if (pstate != null)
+ {
+ point.x -= pstate.origin.x;
+ point.y -= pstate.origin.y;
+ }
+
+ return point;
+};
+
+/**
+ * Function: moveLabel
+ *
+ * Changes the coordinates for the label of the given edge.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge.
+ * x - Integer that specifies the x-coordinate of the new location.
+ * y - Integer that specifies the y-coordinate of the new location.
+ */
+mxEdgeHandler.prototype.moveLabel = function(edgeState, x, y)
+{
+ var model = this.graph.getModel();
+ var geometry = model.getGeometry(edgeState.cell);
+
+ if (geometry != null)
+ {
+ geometry = geometry.clone();
+
+ // Resets the relative location stored inside the geometry
+ var pt = this.graph.getView().getRelativePoint(edgeState, x, y);
+ geometry.x = pt.x;
+ geometry.y = pt.y;
+
+ // Resets the offset inside the geometry to find the offset
+ // from the resulting point
+ var scale = this.graph.getView().scale;
+ geometry.offset = new mxPoint(0, 0);
+ var pt = this.graph.view.getPoint(edgeState, geometry);
+ geometry.offset = new mxPoint((x - pt.x) / scale, (y - pt.y) / scale);
+
+ model.setGeometry(edgeState.cell, geometry);
+ }
+};
+
+/**
+ * Function: connect
+ *
+ * Changes the terminal or terminal point of the given edge in the graph
+ * model.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge to be reconnected.
+ * terminal - <mxCell> that represents the new terminal.
+ * isSource - Boolean indicating if the new terminal is the source or
+ * target terminal.
+ * isClone - Boolean indicating if the new connection should be a clone of
+ * the old edge.
+ * me - <mxMouseEvent> that contains the mouse up event.
+ */
+mxEdgeHandler.prototype.connect = function(edge, terminal, isSource, isClone, me)
+{
+ var model = this.graph.getModel();
+ var parent = model.getParent(edge);
+
+ model.beginUpdate();
+ try
+ {
+ // Clones and adds the cell
+ if (isClone)
+ {
+ var clone = edge.clone();
+ model.add(parent, clone, model.getChildCount(parent));
+
+ var other = model.getTerminal(edge, !isSource);
+ this.graph.connectCell(clone, other, !isSource);
+
+ edge = clone;
+ }
+
+ var constraint = this.constraintHandler.currentConstraint;
+
+ if (constraint == null)
+ {
+ constraint = new mxConnectionConstraint();
+ }
+
+ this.graph.connectCell(edge, terminal, isSource, constraint);
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+
+ return edge;
+};
+
+/**
+ * Function: changeTerminalPoint
+ *
+ * Changes the terminal point of the given edge.
+ */
+mxEdgeHandler.prototype.changeTerminalPoint = function(edge, point, isSource)
+{
+ var model = this.graph.getModel();
+ var geo = model.getGeometry(edge);
+
+ if (geo != null)
+ {
+ model.beginUpdate();
+ try
+ {
+ geo = geo.clone();
+ geo.setTerminalPoint(point, isSource);
+ model.setGeometry(edge, geo);
+ this.graph.connectCell(edge, null, isSource, new mxConnectionConstraint());
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: changePoints
+ *
+ * Changes the control points of the given edge in the graph model.
+ */
+mxEdgeHandler.prototype.changePoints = function(edge, points)
+{
+ var model = this.graph.getModel();
+ var geo = model.getGeometry(edge);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+ geo.points = points;
+
+ model.setGeometry(edge, geo);
+ }
+};
+
+/**
+ * Function: addPoint
+ *
+ * Adds a control point for the given state and event.
+ */
+mxEdgeHandler.prototype.addPoint = function(state, evt)
+{
+ var geo = this.graph.getCellGeometry(state.cell);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+ var pt = mxUtils.convertPoint(this.graph.container, mxEvent.getClientX(evt),
+ mxEvent.getClientY(evt));
+ var index = mxUtils.findNearestSegment(state, pt.x, pt.y);
+ var gridEnabled = this.graph.isGridEnabledEvent(evt);
+ this.convertPoint(pt, gridEnabled);
+
+ if (geo.points == null)
+ {
+ geo.points = [pt];
+ }
+ else
+ {
+ geo.points.splice(index, 0, pt);
+ }
+
+ this.graph.getModel().setGeometry(state.cell, geo);
+ this.destroy();
+ this.init();
+ mxEvent.consume(evt);
+ }
+};
+
+/**
+ * Function: removePoint
+ *
+ * Removes the control point at the given index from the given state.
+ */
+mxEdgeHandler.prototype.removePoint = function(state, index)
+{
+ if (index > 0 && index < this.abspoints.length - 1)
+ {
+ var geo = this.graph.getCellGeometry(this.state.cell);
+
+ if (geo != null &&
+ geo.points != null)
+ {
+ geo = geo.clone();
+ geo.points.splice(index - 1, 1);
+ this.graph.getModel().setGeometry(state.cell, geo);
+ this.destroy();
+ this.init();
+ }
+ }
+};
+
+/**
+ * Function: getHandleFillColor
+ *
+ * Returns the fillcolor for the handle at the given index.
+ */
+mxEdgeHandler.prototype.getHandleFillColor = function(index)
+{
+ var isSource = index == 0;
+ var cell = this.state.cell;
+ var terminal = this.graph.getModel().getTerminal(cell, isSource);
+ var color = mxConstants.HANDLE_FILLCOLOR;
+
+ if ((terminal != null && !this.graph.isCellDisconnectable(cell, terminal, isSource)) ||
+ (terminal == null && !this.graph.isTerminalPointMovable(cell, isSource)))
+ {
+ color = mxConstants.LOCKED_HANDLE_FILLCOLOR;
+ }
+ else if (terminal != null && this.graph.isCellDisconnectable(cell, terminal, isSource))
+ {
+ color = mxConstants.CONNECT_HANDLE_FILLCOLOR;
+ }
+
+ return color;
+};
+
+/**
+ * Function: redraw
+ *
+ * Redraws the preview, and the bends- and label control points.
+ */
+mxEdgeHandler.prototype.redraw = function()
+{
+ this.abspoints = this.state.absolutePoints.slice();
+ var cell = this.state.cell;
+
+ // Updates the handle for the label position
+ var s = mxConstants.LABEL_HANDLE_SIZE;
+
+ this.label = new mxPoint(this.state.absoluteOffset.x, this.state.absoluteOffset.y);
+ this.labelShape.bounds = new mxRectangle(this.label.x - s / 2,
+ this.label.y - s / 2, s, s);
+ this.labelShape.redraw();
+
+ // Shows or hides the label handle depending on the label
+ var lab = this.graph.getLabel(cell);
+
+ if (lab != null && lab.length > 0 && this.graph.isLabelMovable(cell))
+ {
+ this.labelShape.node.style.visibility = 'visible';
+ }
+ else
+ {
+ this.labelShape.node.style.visibility = 'hidden';
+ }
+
+ if (this.bends != null && this.bends.length > 0)
+ {
+ var n = this.abspoints.length - 1;
+
+ var p0 = this.abspoints[0];
+ var x0 = this.abspoints[0].x;
+ var y0 = this.abspoints[0].y;
+
+ var b = this.bends[0].bounds;
+ this.bends[0].bounds = new mxRectangle(x0 - b.width / 2, y0 - b.height / 2, b.width, b.height);
+ this.bends[0].fill = this.getHandleFillColor(0);
+ this.bends[0].reconfigure();
+ this.bends[0].redraw();
+
+ var pe = this.abspoints[n];
+ var xn = this.abspoints[n].x;
+ var yn = this.abspoints[n].y;
+
+ var bn = this.bends.length - 1;
+ b = this.bends[bn].bounds;
+ this.bends[bn].bounds = new mxRectangle(xn - b.width / 2, yn - b.height / 2, b.width, b.height);
+ this.bends[bn].fill = this.getHandleFillColor(bn);
+ this.bends[bn].reconfigure();
+ this.bends[bn].redraw();
+
+ this.redrawInnerBends(p0, pe);
+ }
+
+ this.drawPreview();
+};
+
+/**
+ * Function: redrawInnerBends
+ *
+ * Updates and redraws the inner bends.
+ *
+ * Parameters:
+ *
+ * p0 - <mxPoint> that represents the location of the first point.
+ * pe - <mxPoint> that represents the location of the last point.
+ */
+mxEdgeHandler.prototype.redrawInnerBends = function(p0, pe)
+{
+ var g = this.graph.getModel().getGeometry(this.state.cell);
+ var pts = g.points;
+
+ if (pts != null)
+ {
+ if (this.points == null)
+ {
+ this.points = [];
+ }
+
+ for (var i = 1; i < this.bends.length-1; i++)
+ {
+ if (this.bends[i] != null)
+ {
+ if (this.abspoints[i] != null)
+ {
+ var x = this.abspoints[i].x;
+ var y = this.abspoints[i].y;
+
+ var b = this.bends[i].bounds;
+ this.bends[i].node.style.visibility = 'visible';
+ this.bends[i].bounds = new mxRectangle(x - b.width / 2, y - b.height / 2, b.width, b.height);
+ this.bends[i].redraw();
+
+ this.points[i - 1] = pts[i - 1];
+ }
+ else
+ {
+ this.bends[i].destroy();
+ this.bends[i] = null;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Function: drawPreview
+ *
+ * Redraws the preview.
+ */
+mxEdgeHandler.prototype.drawPreview = function()
+{
+ if (this.isLabel)
+ {
+ var s = mxConstants.LABEL_HANDLE_SIZE;
+
+ var bounds = new mxRectangle(this.label.x - s / 2, this.label.y - s / 2, s, s);
+ this.labelShape.bounds = bounds;
+ this.labelShape.redraw();
+ }
+ else
+ {
+ this.shape.points = this.abspoints;
+ this.shape.redraw();
+ }
+
+ // Workaround to force a repaint in AppleWebKit
+ mxUtils.repaintGraph(this.graph, this.shape.points[this.shape.points.length - 1]);
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes. This does
+ * normally not need to be called as handlers are destroyed automatically
+ * when the corresponding cell is deselected.
+ */
+mxEdgeHandler.prototype.destroy = function()
+{
+ if (this.marker != null)
+ {
+ this.marker.destroy();
+ this.marker = null;
+ }
+
+ if (this.shape != null)
+ {
+ this.shape.destroy();
+ this.shape = null;
+ }
+
+ if (this.labelShape != null)
+ {
+ this.labelShape.destroy();
+ this.labelShape = null;
+ }
+
+ if (this.constraintHandler != null)
+ {
+ this.constraintHandler.destroy();
+ this.constraintHandler = null;
+ }
+
+ // Destroy the control points for the bends
+ if (this.bends != null)
+ {
+ for (var i = 0; i < this.bends.length; i++)
+ {
+ if (this.bends[i] != null)
+ {
+ this.bends[i].destroy();
+ this.bends[i] = null;
+ }
+ }
+ }
+};
diff --git a/src/js/handler/mxEdgeSegmentHandler.js b/src/js/handler/mxEdgeSegmentHandler.js
new file mode 100644
index 0000000..e14fde0
--- /dev/null
+++ b/src/js/handler/mxEdgeSegmentHandler.js
@@ -0,0 +1,284 @@
+/**
+ * $Id: mxEdgeSegmentHandler.js,v 1.14 2012-12-17 13:22:49 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+function mxEdgeSegmentHandler(state)
+{
+ if (state != null)
+ {
+ this.state = state;
+ this.init();
+ }
+};
+
+/**
+ * Extends mxEdgeHandler.
+ */
+mxEdgeSegmentHandler.prototype = new mxElbowEdgeHandler();
+mxEdgeSegmentHandler.prototype.constructor = mxEdgeSegmentHandler;
+
+/**
+ * Function: getPreviewPoints
+ *
+ * Updates the given preview state taking into account the state of the constraint handler.
+ */
+mxEdgeSegmentHandler.prototype.getPreviewPoints = function(point)
+{
+ if (this.isSource || this.isTarget)
+ {
+ return mxElbowEdgeHandler.prototype.getPreviewPoints.apply(this, arguments);
+ }
+ else
+ {
+ this.convertPoint(point, false);
+ var pts = this.state.absolutePoints;
+ var last = pts[0].clone();
+ this.convertPoint(last, false);
+ var result = [];
+
+ for (var i = 1; i < pts.length; i++)
+ {
+ var pt = pts[i].clone();
+ this.convertPoint(pt, false);
+
+ if (i == this.index)
+ {
+ if (last.x == pt.x)
+ {
+ last.x = point.x;
+ pt.x = point.x;
+ }
+ else
+ {
+ last.y = point.y;
+ pt.y = point.y;
+ }
+ }
+
+ if (i < pts.length - 1)
+ {
+ result.push(pt);
+ }
+
+ last = pt;
+ }
+
+ if (result.length == 1)
+ {
+ var view = this.state.view;
+ var source = this.state.getVisibleTerminalState(true);
+ var target = this.state.getVisibleTerminalState(false);
+
+ if (target != null & source != null)
+ {
+ var dx = this.state.origin.x;
+ var dy = this.state.origin.y;
+
+ if (mxUtils.contains(target, result[0].x + dx, result[0].y + dy))
+ {
+ if (pts[1].y == pts[2].y)
+ {
+ result[0].y = view.getRoutingCenterY(source) - dy;
+ }
+ else
+ {
+ result[0].x = view.getRoutingCenterX(source) - dx;
+ }
+ }
+ else if (mxUtils.contains(source, result[0].x + dx, result[0].y + dy))
+ {
+ if (pts[1].y == pts[0].y)
+ {
+ result[0].y = view.getRoutingCenterY(target) - dy;
+ }
+ else
+ {
+ result[0].x = view.getRoutingCenterX(target) - dx;
+ }
+ }
+ }
+ }
+ else if (result.length == 0)
+ {
+ result = [point];
+ }
+
+ return result;
+ }
+};
+
+/**
+ * Function: createBends
+ *
+ * Adds custom bends for the center of each segment.
+ */
+mxEdgeSegmentHandler.prototype.createBends = function()
+{
+ var bends = [];
+
+ // Source
+ var bend = this.createHandleShape(0);
+
+ this.initBend(bend);
+ bend.node.style.cursor = mxConstants.CURSOR_BEND_HANDLE;
+ mxEvent.redirectMouseEvents(bend.node, this.graph, this.state);
+ bends.push(bend);
+
+ if (mxClient.IS_TOUCH)
+ {
+ bend.node.setAttribute('pointer-events', 'none');
+ }
+
+ var pts = this.state.absolutePoints;
+
+ // Waypoints (segment handles)
+ if (this.graph.isCellBendable(this.state.cell))
+ {
+ if (this.points == null)
+ {
+ this.points = [];
+ }
+
+ for (var i = 0; i < pts.length - 1; i++)
+ {
+ var bend = this.createVirtualBend();
+ bends.push(bend);
+ var horizontal = pts[i].x - pts[i + 1].x == 0;
+ bend.node.style.cursor = (horizontal) ? 'col-resize' : 'row-resize';
+ this.points.push(new mxPoint(0,0));
+
+ if (mxClient.IS_TOUCH)
+ {
+ bend.node.setAttribute('pointer-events', 'none');
+ }
+ }
+ }
+
+ // Target
+ var bend = this.createHandleShape(pts.length);
+
+ this.initBend(bend);
+ bend.node.style.cursor = mxConstants.CURSOR_BEND_HANDLE;
+ mxEvent.redirectMouseEvents(bend.node, this.graph, this.state);
+ bends.push(bend);
+
+ if (mxClient.IS_TOUCH)
+ {
+ bend.node.setAttribute('pointer-events', 'none');
+ }
+
+ return bends;
+};
+
+
+/**
+ * Function: redrawInnerBends
+ *
+ * Updates the position of the custom bends.
+ */
+mxEdgeSegmentHandler.prototype.redrawInnerBends = function(p0, pe)
+{
+ if (this.graph.isCellBendable(this.state.cell))
+ {
+ var s = mxConstants.HANDLE_SIZE;
+ var pts = this.state.absolutePoints;
+
+ if (pts != null && pts.length > 1)
+ {
+ for (var i = 0; i < this.state.absolutePoints.length - 1; i++)
+ {
+ if (this.bends[i + 1] != null)
+ {
+ var p0 = pts[i];
+ var pe = pts[i + 1];
+ var pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);
+ this.bends[i+1].bounds = new mxRectangle(pt.x - s / 2, pt.y - s / 2, s, s);
+ this.bends[i+1].reconfigure();
+ this.bends[i+1].redraw();
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Function: connect
+ *
+ * Calls <refresh> after <mxEdgeHandler.connect>.
+ */
+mxEdgeSegmentHandler.prototype.connect = function(edge, terminal, isSource, isClone, me)
+{
+ mxEdgeHandler.prototype.connect.apply(this, arguments);
+ this.refresh();
+};
+
+/**
+ * Function: changeTerminalPoint
+ *
+ * Calls <refresh> after <mxEdgeHandler.changeTerminalPoint>.
+ */
+mxEdgeSegmentHandler.prototype.changeTerminalPoint = function(edge, point, isSource)
+{
+ mxEdgeHandler.prototype.changeTerminalPoint.apply(this, arguments);
+ this.refresh();
+};
+
+/**
+ * Function: changePoints
+ *
+ * Changes the points of the given edge to reflect the current state of the handler.
+ */
+mxEdgeSegmentHandler.prototype.changePoints = function(edge, points)
+{
+ points = [];
+ var pts = this.abspoints;
+
+ if (pts.length > 1)
+ {
+ var pt0 = pts[0];
+ var pt1 = pts[1];
+
+ for (var i = 2; i < pts.length; i++)
+ {
+ var pt2 = pts[i];
+
+ if ((Math.round(pt0.x) != Math.round(pt1.x) ||
+ Math.round(pt1.x) != Math.round(pt2.x)) &&
+ (Math.round(pt0.y) != Math.round(pt1.y) ||
+ Math.round(pt1.y) != Math.round(pt2.y)))
+ {
+ pt0 = pt1;
+ pt1 = pt1.clone();
+ this.convertPoint(pt1, false);
+ points.push(pt1);
+ }
+
+ pt1 = pt2;
+ }
+ }
+
+ mxElbowEdgeHandler.prototype.changePoints.apply(this, arguments);
+ this.refresh();
+};
+
+/**
+ * Function: refresh
+ *
+ * Refreshes the bends of this handler.
+ */
+mxEdgeSegmentHandler.prototype.refresh = function()
+{
+ if (this.bends != null)
+ {
+ for (var i = 0; i < this.bends.length; i++)
+ {
+ if (this.bends[i] != null)
+ {
+ this.bends[i].destroy();
+ this.bends[i] = null;
+ }
+ }
+
+ this.bends = this.createBends();
+ }
+};
diff --git a/src/js/handler/mxElbowEdgeHandler.js b/src/js/handler/mxElbowEdgeHandler.js
new file mode 100644
index 0000000..85fbb06
--- /dev/null
+++ b/src/js/handler/mxElbowEdgeHandler.js
@@ -0,0 +1,248 @@
+/**
+ * $Id: mxElbowEdgeHandler.js,v 1.43 2012-01-06 13:06:01 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxElbowEdgeHandler
+ *
+ * Graph event handler that reconnects edges and modifies control points and
+ * the edge label location. Uses <mxTerminalMarker> for finding and
+ * highlighting new source and target vertices. This handler is automatically
+ * created in <mxGraph.createHandler>. It extends <mxEdgeHandler>.
+ *
+ * Constructor: mxEdgeHandler
+ *
+ * Constructs an edge handler for the specified <mxCellState>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the cell to be modified.
+ */
+function mxElbowEdgeHandler(state)
+{
+ if (state != null)
+ {
+ this.state = state;
+ this.init();
+ }
+};
+
+/**
+ * Extends mxEdgeHandler.
+ */
+mxElbowEdgeHandler.prototype = new mxEdgeHandler();
+mxElbowEdgeHandler.prototype.constructor = mxElbowEdgeHandler;
+
+/**
+ * Specifies if a double click on the middle handle should call
+ * <mxGraph.flipEdge>. Default is true.
+ */
+mxElbowEdgeHandler.prototype.flipEnabled = true;
+
+/**
+ * Variable: doubleClickOrientationResource
+ *
+ * Specifies the resource key for the tooltip to be displayed on the single
+ * control point for routed edges. If the resource for this key does not
+ * exist then the value is used as the error message. Default is
+ * 'doubleClickOrientation'.
+ */
+mxElbowEdgeHandler.prototype.doubleClickOrientationResource =
+ (mxClient.language != 'none') ? 'doubleClickOrientation' : '';
+
+/**
+ * Function: createBends
+ *
+ * Overrides <mxEdgeHandler.createBends> to create custom bends.
+ */
+ mxElbowEdgeHandler.prototype.createBends = function()
+ {
+ var bends = [];
+
+ // Source
+ var bend = this.createHandleShape(0);
+
+ this.initBend(bend);
+ bend.node.style.cursor = mxConstants.CURSOR_BEND_HANDLE;
+ mxEvent.redirectMouseEvents(bend.node, this.graph, this.state);
+ bends.push(bend);
+
+ if (mxClient.IS_TOUCH)
+ {
+ bend.node.setAttribute('pointer-events', 'none');
+ }
+
+ // Virtual
+ bends.push(this.createVirtualBend());
+ this.points.push(new mxPoint(0,0));
+
+ // Target
+ bend = this.createHandleShape(2);
+
+ this.initBend(bend);
+ bend.node.style.cursor = mxConstants.CURSOR_BEND_HANDLE;
+ mxEvent.redirectMouseEvents(bend.node, this.graph, this.state);
+ bends.push(bend);
+
+ if (mxClient.IS_TOUCH)
+ {
+ bend.node.setAttribute('pointer-events', 'none');
+ }
+
+ return bends;
+ };
+
+/**
+ * Function: createVirtualBend
+ *
+ * Creates a virtual bend that supports double clicking and calls
+ * <mxGraph.flipEdge>.
+ */
+mxElbowEdgeHandler.prototype.createVirtualBend = function()
+{
+ var bend = this.createHandleShape();
+ this.initBend(bend);
+
+ var crs = this.getCursorForBend();
+ bend.node.style.cursor = crs;
+
+ // Double-click changes edge style
+ var dblClick = mxUtils.bind(this, function(evt)
+ {
+ if (!mxEvent.isConsumed(evt) &&
+ this.flipEnabled)
+ {
+ this.graph.flipEdge(this.state.cell, evt);
+ mxEvent.consume(evt);
+ }
+ });
+
+ mxEvent.redirectMouseEvents(bend.node, this.graph, this.state,
+ null, null, null, dblClick);
+
+ if (!this.graph.isCellBendable(this.state.cell))
+ {
+ bend.node.style.visibility = 'hidden';
+ }
+
+ return bend;
+};
+
+/**
+ * Function: getCursorForBend
+ *
+ * Returns the cursor to be used for the bend.
+ */
+mxElbowEdgeHandler.prototype.getCursorForBend = function()
+{
+ return (this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.TopToBottom ||
+ this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_TOPTOBOTTOM ||
+ ((this.state.style[mxConstants.STYLE_EDGE] == mxEdgeStyle.ElbowConnector ||
+ this.state.style[mxConstants.STYLE_EDGE] == mxConstants.EDGESTYLE_ELBOW)&&
+ this.state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL)) ?
+ 'row-resize' : 'col-resize';
+};
+
+/**
+ * Function: getTooltipForNode
+ *
+ * Returns the tooltip for the given node.
+ */
+mxElbowEdgeHandler.prototype.getTooltipForNode = function(node)
+{
+ var tip = null;
+
+ if (this.bends != null &&
+ this.bends[1] != null &&
+ (node == this.bends[1].node ||
+ node.parentNode == this.bends[1].node))
+ {
+ tip = this.doubleClickOrientationResource;
+ tip = mxResources.get(tip) || tip; // translate
+ }
+
+ return tip;
+};
+
+/**
+ * Function: convertPoint
+ *
+ * Converts the given point in-place from screen to unscaled, untranslated
+ * graph coordinates and applies the grid.
+ *
+ * Parameters:
+ *
+ * point - <mxPoint> to be converted.
+ * gridEnabled - Boolean that specifies if the grid should be applied.
+ */
+mxElbowEdgeHandler.prototype.convertPoint = function(point, gridEnabled)
+{
+ var scale = this.graph.getView().getScale();
+ var tr = this.graph.getView().getTranslate();
+ var origin = this.state.origin;
+
+ if (gridEnabled)
+ {
+ point.x = this.graph.snap(point.x);
+ point.y = this.graph.snap(point.y);
+ }
+
+ point.x = Math.round(point.x / scale - tr.x - origin.x);
+ point.y = Math.round(point.y / scale - tr.y - origin.y);
+};
+
+/**
+ * Function: redrawInnerBends
+ *
+ * Updates and redraws the inner bends.
+ *
+ * Parameters:
+ *
+ * p0 - <mxPoint> that represents the location of the first point.
+ * pe - <mxPoint> that represents the location of the last point.
+ */
+mxElbowEdgeHandler.prototype.redrawInnerBends = function(p0, pe)
+{
+ var g = this.graph.getModel().getGeometry(this.state.cell);
+ var pts = g.points;
+
+ var pt = (pts != null) ? pts[0] : null;
+
+ if (pt == null)
+ {
+ pt = new mxPoint(p0.x + (pe.x - p0.x) / 2, p0.y + (pe.y - p0.y) / 2);
+ }
+ else
+ {
+ pt = new mxPoint(this.graph.getView().scale*(pt.x +
+ this.graph.getView().translate.x + this.state.origin.x),
+ this.graph.getView().scale*(pt.y + this.graph.getView().translate.y +
+ this.state.origin.y));
+ }
+
+ // Makes handle slightly bigger if the yellow label handle
+ // exists and intersects this green handle
+ var b = this.bends[1].bounds;
+ var w = b.width;
+ var h = b.height;
+
+ if (this.handleImage == null)
+ {
+ w = mxConstants.HANDLE_SIZE;
+ h = mxConstants.HANDLE_SIZE;
+ }
+
+ var bounds = new mxRectangle(pt.x - w / 2, pt.y - h / 2, w, h);
+
+ if (this.handleImage == null && this.labelShape.node.style.visibility != 'hidden' &&
+ mxUtils.intersects(bounds, this.labelShape.bounds))
+ {
+ w += 3;
+ h += 3;
+ bounds = new mxRectangle(pt.x - w / 2, pt.y - h / 2, w, h);
+ }
+
+ this.bends[1].bounds = bounds;
+ this.bends[1].reconfigure();
+ this.bends[1].redraw();
+};
diff --git a/src/js/handler/mxGraphHandler.js b/src/js/handler/mxGraphHandler.js
new file mode 100644
index 0000000..57e27a1
--- /dev/null
+++ b/src/js/handler/mxGraphHandler.js
@@ -0,0 +1,916 @@
+/**
+ * $Id: mxGraphHandler.js,v 1.129 2012-04-13 12:53:30 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphHandler
+ *
+ * Graph event handler that handles selection. Individual cells are handled
+ * separately using <mxVertexHandler> or one of the edge handlers. These
+ * handlers are created using <mxGraph.createHandler> in
+ * <mxGraphSelectionModel.cellAdded>.
+ *
+ * To avoid the container to scroll a moved cell into view, set
+ * <scrollAfterMove> to false.
+ *
+ * Constructor: mxGraphHandler
+ *
+ * Constructs an event handler that creates handles for the
+ * selection cells.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxGraphHandler(graph)
+{
+ this.graph = graph;
+ this.graph.addMouseListener(this);
+
+ // Repaints the handler after autoscroll
+ this.panHandler = mxUtils.bind(this, function()
+ {
+ this.updatePreviewShape();
+ });
+
+ this.graph.addListener(mxEvent.PAN, this.panHandler);
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphHandler.prototype.graph = null;
+
+/**
+ * Variable: maxCells
+ *
+ * Defines the maximum number of cells to paint subhandles
+ * for. Default is 50 for Firefox and 20 for IE. Set this
+ * to 0 if you want an unlimited number of handles to be
+ * displayed. This is only recommended if the number of
+ * cells in the graph is limited to a small number, eg.
+ * 500.
+ */
+mxGraphHandler.prototype.maxCells = (mxClient.IS_IE) ? 20 : 50;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxGraphHandler.prototype.enabled = true;
+
+/**
+ * Variable: highlightEnabled
+ *
+ * Specifies if drop targets under the mouse should be enabled. Default is
+ * true.
+ */
+mxGraphHandler.prototype.highlightEnabled = true;
+
+/**
+ * Variable: cloneEnabled
+ *
+ * Specifies if cloning by control-drag is enabled. Default is true.
+ */
+mxGraphHandler.prototype.cloneEnabled = true;
+
+/**
+ * Variable: moveEnabled
+ *
+ * Specifies if moving is enabled. Default is true.
+ */
+mxGraphHandler.prototype.moveEnabled = true;
+
+/**
+ * Variable: guidesEnabled
+ *
+ * Specifies if other cells should be used for snapping the right, center or
+ * left side of the current selection. Default is false.
+ */
+mxGraphHandler.prototype.guidesEnabled = false;
+
+/**
+ * Variable: guide
+ *
+ * Holds the <mxGuide> instance that is used for alignment.
+ */
+mxGraphHandler.prototype.guide = null;
+
+/**
+ * Variable: currentDx
+ *
+ * Stores the x-coordinate of the current mouse move.
+ */
+mxGraphHandler.prototype.currentDx = null;
+
+/**
+ * Variable: currentDy
+ *
+ * Stores the y-coordinate of the current mouse move.
+ */
+mxGraphHandler.prototype.currentDy = null;
+
+/**
+ * Variable: updateCursor
+ *
+ * Specifies if a move cursor should be shown if the mouse is ove a movable
+ * cell. Default is true.
+ */
+mxGraphHandler.prototype.updateCursor = true;
+
+/**
+ * Variable: selectEnabled
+ *
+ * Specifies if selecting is enabled. Default is true.
+ */
+mxGraphHandler.prototype.selectEnabled = true;
+
+/**
+ * Variable: removeCellsFromParent
+ *
+ * Specifies if cells may be moved out of their parents. Default is true.
+ */
+mxGraphHandler.prototype.removeCellsFromParent = true;
+
+/**
+ * Variable: connectOnDrop
+ *
+ * Specifies if drop events are interpreted as new connections if no other
+ * drop action is defined. Default is false.
+ */
+mxGraphHandler.prototype.connectOnDrop = false;
+
+/**
+ * Variable: scrollOnMove
+ *
+ * Specifies if the view should be scrolled so that a moved cell is
+ * visible. Default is true.
+ */
+mxGraphHandler.prototype.scrollOnMove = true;
+
+/**
+ * Variable: minimumSize
+ *
+ * Specifies the minimum number of pixels for the width and height of a
+ * selection border. Default is 6.
+ */
+mxGraphHandler.prototype.minimumSize = 6;
+
+/**
+ * Variable: previewColor
+ *
+ * Specifies the color of the preview shape. Default is black.
+ */
+mxGraphHandler.prototype.previewColor = 'black';
+
+/**
+ * Variable: htmlPreview
+ *
+ * Specifies if the graph container should be used for preview. If this is used
+ * then drop target detection relies entirely on <mxGraph.getCellAt> because
+ * the HTML preview does not "let events through". Default is false.
+ */
+mxGraphHandler.prototype.htmlPreview = false;
+
+/**
+ * Variable: shape
+ *
+ * Reference to the <mxShape> that represents the preview.
+ */
+mxGraphHandler.prototype.shape = null;
+
+/**
+ * Variable: scaleGrid
+ *
+ * Specifies if the grid should be scaled. Default is false.
+ */
+mxGraphHandler.prototype.scaleGrid = false;
+
+/**
+ * Variable: crisp
+ *
+ * Specifies if the move preview should be rendered in crisp mode if applicable.
+ * Default is true.
+ */
+mxGraphHandler.prototype.crisp = true;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns <enabled>.
+ */
+mxGraphHandler.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Sets <enabled>.
+ */
+mxGraphHandler.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: isCloneEnabled
+ *
+ * Returns <cloneEnabled>.
+ */
+mxGraphHandler.prototype.isCloneEnabled = function()
+{
+ return this.cloneEnabled;
+};
+
+/**
+ * Function: setCloneEnabled
+ *
+ * Sets <cloneEnabled>.
+ *
+ * Parameters:
+ *
+ * value - Boolean that specifies the new clone enabled state.
+ */
+mxGraphHandler.prototype.setCloneEnabled = function(value)
+{
+ this.cloneEnabled = value;
+};
+
+/**
+ * Function: isMoveEnabled
+ *
+ * Returns <moveEnabled>.
+ */
+mxGraphHandler.prototype.isMoveEnabled = function()
+{
+ return this.moveEnabled;
+};
+
+/**
+ * Function: setMoveEnabled
+ *
+ * Sets <moveEnabled>.
+ */
+mxGraphHandler.prototype.setMoveEnabled = function(value)
+{
+ this.moveEnabled = value;
+};
+
+/**
+ * Function: isSelectEnabled
+ *
+ * Returns <selectEnabled>.
+ */
+mxGraphHandler.prototype.isSelectEnabled = function()
+{
+ return this.selectEnabled;
+};
+
+/**
+ * Function: setSelectEnabled
+ *
+ * Sets <selectEnabled>.
+ */
+mxGraphHandler.prototype.setSelectEnabled = function(value)
+{
+ this.selectEnabled = value;
+};
+
+/**
+ * Function: isRemoveCellsFromParent
+ *
+ * Returns <removeCellsFromParent>.
+ */
+mxGraphHandler.prototype.isRemoveCellsFromParent = function()
+{
+ return this.removeCellsFromParent;
+};
+
+/**
+ * Function: setRemoveCellsFromParent
+ *
+ * Sets <removeCellsFromParent>.
+ */
+mxGraphHandler.prototype.setRemoveCellsFromParent = function(value)
+{
+ this.removeCellsFromParent = value;
+};
+
+/**
+ * Function: getInitialCellForEvent
+ *
+ * Hook to return initial cell for the given event.
+ */
+mxGraphHandler.prototype.getInitialCellForEvent = function(me)
+{
+ return me.getCell();
+};
+
+/**
+ * Function: isDelayedSelection
+ *
+ * Hook to return true for delayed selections.
+ */
+mxGraphHandler.prototype.isDelayedSelection = function(cell)
+{
+ return this.graph.isCellSelected(cell);
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event by selecing the given cell and creating a handle for
+ * it. By consuming the event all subsequent events of the gesture are
+ * redirected to this handler.
+ */
+mxGraphHandler.prototype.mouseDown = function(sender, me)
+{
+ if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() &&
+ !this.graph.isForceMarqueeEvent(me.getEvent()) && me.getState() != null)
+ {
+ var cell = this.getInitialCellForEvent(me);
+ this.cell = null;
+ this.delayedSelection = this.isDelayedSelection(cell);
+
+ if (this.isSelectEnabled() && !this.delayedSelection)
+ {
+ this.graph.selectCellForEvent(cell, me.getEvent());
+ }
+
+ if (this.isMoveEnabled())
+ {
+ var model = this.graph.model;
+ var geo = model.getGeometry(cell);
+
+ if (this.graph.isCellMovable(cell) && ((!model.isEdge(cell) || this.graph.getSelectionCount() > 1 ||
+ (geo.points != null && geo.points.length > 0) || model.getTerminal(cell, true) == null ||
+ model.getTerminal(cell, false) == null) || this.graph.allowDanglingEdges ||
+ (this.graph.isCloneEvent(me.getEvent()) && this.graph.isCellsCloneable())))
+ {
+ this.start(cell, me.getX(), me.getY());
+ }
+
+ this.cellWasClicked = true;
+
+ // Workaround for SELECT element not working in Webkit, this blocks moving
+ // of the cell if the select element is clicked in Safari which is needed
+ // because Safari doesn't seem to route the subsequent mouseUp event via
+ // this handler which leads to an inconsistent state (no reset called).
+ // Same for cellWasClicked which will block clearing the selection when
+ // clicking the background after clicking on the SELECT element in Safari.
+ if ((!mxClient.IS_SF && !mxClient.IS_GC) || me.getSource().nodeName != 'SELECT')
+ {
+ me.consume();
+ }
+ else if (mxClient.IS_SF && me.getSource().nodeName == 'SELECT')
+ {
+ this.cellWasClicked = false;
+ this.first = null;
+ }
+ }
+ }
+};
+
+/**
+ * Function: getGuideStates
+ *
+ * Creates an array of cell states which should be used as guides.
+ */
+mxGraphHandler.prototype.getGuideStates = function()
+{
+ var parent = this.graph.getDefaultParent();
+ var model = this.graph.getModel();
+
+ var filter = mxUtils.bind(this, function(cell)
+ {
+ return this.graph.view.getState(cell) != null &&
+ model.isVertex(cell) &&
+ model.getGeometry(cell) != null &&
+ !model.getGeometry(cell).relative;
+ });
+
+ return this.graph.view.getCellStates(model.filterDescendants(filter, parent));
+};
+
+/**
+ * Function: getCells
+ *
+ * Returns the cells to be modified by this handler. This implementation
+ * returns all selection cells that are movable, or the given initial cell if
+ * the given cell is not selected and movable. This handles the case of moving
+ * unselectable or unselected cells.
+ *
+ * Parameters:
+ *
+ * initialCell - <mxCell> that triggered this handler.
+ */
+mxGraphHandler.prototype.getCells = function(initialCell)
+{
+ if (!this.delayedSelection && this.graph.isCellMovable(initialCell))
+ {
+ return [initialCell];
+ }
+ else
+ {
+ return this.graph.getMovableCells(this.graph.getSelectionCells());
+ }
+};
+
+/**
+ * Function: getPreviewBounds
+ *
+ * Returns the <mxRectangle> used as the preview bounds for
+ * moving the given cells.
+ */
+mxGraphHandler.prototype.getPreviewBounds = function(cells)
+{
+ var bounds = this.graph.getView().getBounds(cells);
+
+ if (bounds != null)
+ {
+ if (bounds.width < this.minimumSize)
+ {
+ var dx = this.minimumSize - bounds.width;
+ bounds.x -= dx / 2;
+ bounds.width = this.minimumSize;
+ }
+
+ if (bounds.height < this.minimumSize)
+ {
+ var dy = this.minimumSize - bounds.height;
+ bounds.y -= dy / 2;
+ bounds.height = this.minimumSize;
+ }
+ }
+
+ return bounds;
+};
+
+/**
+ * Function: createPreviewShape
+ *
+ * Creates the shape used to draw the preview for the given bounds.
+ */
+mxGraphHandler.prototype.createPreviewShape = function(bounds)
+{
+ var shape = new mxRectangleShape(bounds, null, this.previewColor);
+ shape.isDashed = true;
+ shape.crisp = this.crisp;
+
+ if (this.htmlPreview)
+ {
+ shape.dialect = mxConstants.DIALECT_STRICTHTML;
+ shape.init(this.graph.container);
+ }
+ else
+ {
+ // Makes sure to use either VML or SVG shapes in order to implement
+ // event-transparency on the background area of the rectangle since
+ // HTML shapes do not let mouseevents through even when transparent
+ shape.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ shape.init(this.graph.getView().getOverlayPane());
+
+ // Event-transparency
+ if (shape.dialect == mxConstants.DIALECT_SVG)
+ {
+ shape.node.setAttribute('style', 'pointer-events:none;');
+ }
+ else
+ {
+ shape.node.style.background = '';
+ }
+ }
+
+ return shape;
+};
+
+/**
+ * Function: start
+ *
+ * Starts the handling of the mouse gesture.
+ */
+mxGraphHandler.prototype.start = function(cell, x, y)
+{
+ this.cell = cell;
+ this.first = mxUtils.convertPoint(this.graph.container, x, y);
+ this.cells = this.getCells(this.cell);
+ this.bounds = this.getPreviewBounds(this.cells);
+
+ if (this.guidesEnabled)
+ {
+ this.guide = new mxGuide(this.graph, this.getGuideStates());
+ }
+};
+
+/**
+ * Function: useGuidesForEvent
+ *
+ * Returns true if the guides should be used for the given <mxMouseEvent>.
+ * This implementation returns <mxGuide.isEnabledForEvent>.
+ */
+mxGraphHandler.prototype.useGuidesForEvent = function(me)
+{
+ return (this.guide != null) ? this.guide.isEnabledForEvent(me.getEvent()) : true;
+};
+
+
+/**
+ * Function: snap
+ *
+ * Snaps the given vector to the grid and returns the given mxPoint instance.
+ */
+mxGraphHandler.prototype.snap = function(vector)
+{
+ var scale = (this.scaleGrid) ? this.graph.view.scale : 1;
+
+ vector.x = this.graph.snap(vector.x / scale) * scale;
+ vector.y = this.graph.snap(vector.y / scale) * scale;
+
+ return vector;
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by highlighting possible drop targets and updating the
+ * preview.
+ */
+mxGraphHandler.prototype.mouseMove = function(sender, me)
+{
+ var graph = this.graph;
+
+ if (!me.isConsumed() && graph.isMouseDown && this.cell != null &&
+ this.first != null && this.bounds != null)
+ {
+ var point = mxUtils.convertPoint(graph.container, me.getX(), me.getY());
+ var dx = point.x - this.first.x;
+ var dy = point.y - this.first.y;
+ var tol = graph.tolerance;
+
+ if (this.shape!= null || Math.abs(dx) > tol || Math.abs(dy) > tol)
+ {
+ // Highlight is used for highlighting drop targets
+ if (this.highlight == null)
+ {
+ this.highlight = new mxCellHighlight(this.graph,
+ mxConstants.DROP_TARGET_COLOR, 3);
+ }
+
+ if (this.shape == null)
+ {
+ this.shape = this.createPreviewShape(this.bounds);
+ }
+
+ var gridEnabled = graph.isGridEnabledEvent(me.getEvent());
+ var hideGuide = true;
+
+ if (this.guide != null && this.useGuidesForEvent(me))
+ {
+ var delta = this.guide.move(this.bounds, new mxPoint(dx, dy), gridEnabled);
+ hideGuide = false;
+ dx = delta.x;
+ dy = delta.y;
+ }
+ else if (gridEnabled)
+ {
+ var trx = graph.getView().translate;
+ var scale = graph.getView().scale;
+
+ var tx = this.bounds.x - (graph.snap(this.bounds.x / scale - trx.x) + trx.x) * scale;
+ var ty = this.bounds.y - (graph.snap(this.bounds.y / scale - trx.y) + trx.y) * scale;
+ var v = this.snap(new mxPoint(dx, dy));
+
+ dx = v.x - tx;
+ dy = v.y - ty;
+ }
+
+ if (this.guide != null && hideGuide)
+ {
+ this.guide.hide();
+ }
+
+ // Constrained movement if shift key is pressed
+ if (graph.isConstrainedEvent(me.getEvent()))
+ {
+ if (Math.abs(dx) > Math.abs(dy))
+ {
+ dy = 0;
+ }
+ else
+ {
+ dx = 0;
+ }
+ }
+
+ this.currentDx = dx;
+ this.currentDy = dy;
+ this.updatePreviewShape();
+
+ var target = null;
+ var cell = me.getCell();
+
+ if (graph.isDropEnabled() && this.highlightEnabled)
+ {
+ // Contains a call to getCellAt to find the cell under the mouse
+ target = graph.getDropTarget(this.cells, me.getEvent(), cell);
+ }
+
+ // Checks if parent is dropped into child
+ var parent = target;
+ var model = graph.getModel();
+
+ while (parent != null && parent != this.cells[0])
+ {
+ parent = model.getParent(parent);
+ }
+
+ var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled();
+ var state = graph.getView().getState(target);
+ var highlight = false;
+
+ if (state != null && parent == null && (model.getParent(this.cell) != target || clone))
+ {
+ if (this.target != target)
+ {
+ this.target = target;
+ this.setHighlightColor(mxConstants.DROP_TARGET_COLOR);
+ }
+
+ highlight = true;
+ }
+ else
+ {
+ this.target = null;
+
+ if (this.connectOnDrop && cell != null && this.cells.length == 1 &&
+ graph.getModel().isVertex(cell) && graph.isCellConnectable(cell))
+ {
+ state = graph.getView().getState(cell);
+
+ if (state != null)
+ {
+ var error = graph.getEdgeValidationError(null, this.cell, cell);
+ var color = (error == null) ?
+ mxConstants.VALID_COLOR :
+ mxConstants.INVALID_CONNECT_TARGET_COLOR;
+ this.setHighlightColor(color);
+ highlight = true;
+ }
+ }
+ }
+
+ if (state != null && highlight)
+ {
+ this.highlight.highlight(state);
+ }
+ else
+ {
+ this.highlight.hide();
+ }
+ }
+
+ me.consume();
+
+ // Cancels the bubbling of events to the container so
+ // that the droptarget is not reset due to an mouseMove
+ // fired on the container with no associated state.
+ mxEvent.consume(me.getEvent());
+ }
+ else if ((this.isMoveEnabled() || this.isCloneEnabled()) && this.updateCursor &&
+ !me.isConsumed() && me.getState() != null && !graph.isMouseDown)
+ {
+ var cursor = graph.getCursorForCell(me.getCell());
+
+ if (cursor == null && graph.isEnabled() && graph.isCellMovable(me.getCell()))
+ {
+ if (graph.getModel().isEdge(me.getCell()))
+ {
+ cursor = mxConstants.CURSOR_MOVABLE_EDGE;
+ }
+ else
+ {
+ cursor = mxConstants.CURSOR_MOVABLE_VERTEX;
+ }
+ }
+
+ me.getState().setCursor(cursor);
+ me.consume();
+ }
+};
+
+/**
+ * Function: updatePreviewShape
+ *
+ * Updates the bounds of the preview shape.
+ */
+mxGraphHandler.prototype.updatePreviewShape = function()
+{
+ if (this.shape != null)
+ {
+ this.shape.bounds = new mxRectangle(this.bounds.x + this.currentDx - this.graph.panDx,
+ this.bounds.y + this.currentDy - this.graph.panDy, this.bounds.width, this.bounds.height);
+ this.shape.redraw();
+ }
+};
+
+/**
+ * Function: setHighlightColor
+ *
+ * Sets the color of the rectangle used to highlight drop targets.
+ *
+ * Parameters:
+ *
+ * color - String that represents the new highlight color.
+ */
+mxGraphHandler.prototype.setHighlightColor = function(color)
+{
+ if (this.highlight != null)
+ {
+ this.highlight.setHighlightColor(color);
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by applying the changes to the selection cells.
+ */
+mxGraphHandler.prototype.mouseUp = function(sender, me)
+{
+ if (!me.isConsumed())
+ {
+ var graph = this.graph;
+
+ if (this.cell != null && this.first != null && this.shape != null &&
+ this.currentDx != null && this.currentDy != null)
+ {
+ var scale = graph.getView().scale;
+ var clone = graph.isCloneEvent(me.getEvent()) && graph.isCellsCloneable() && this.isCloneEnabled();
+ var dx = this.currentDx / scale;
+ var dy = this.currentDy / scale;
+
+ var cell = me.getCell();
+
+ if (this.connectOnDrop && this.target == null && cell != null && graph.getModel().isVertex(cell) &&
+ graph.isCellConnectable(cell) && graph.isEdgeValid(null, this.cell, cell))
+ {
+ graph.connectionHandler.connect(this.cell, cell, me.getEvent());
+ }
+ else
+ {
+ var target = this.target;
+
+ if (graph.isSplitEnabled() && graph.isSplitTarget(target, this.cells, me.getEvent()))
+ {
+ graph.splitEdge(target, this.cells, null, dx, dy);
+ }
+ else
+ {
+ this.moveCells(this.cells, dx, dy, clone, this.target, me.getEvent());
+ }
+ }
+ }
+ else if (this.isSelectEnabled() && this.delayedSelection && this.cell != null)
+ {
+ this.selectDelayed(me);
+ }
+ }
+
+ // Consumes the event if a cell was initially clicked
+ if (this.cellWasClicked)
+ {
+ me.consume();
+ }
+
+ this.reset();
+};
+
+/**
+ * Function: selectDelayed
+ *
+ * Implements the delayed selection for the given mouse event.
+ */
+mxGraphHandler.prototype.selectDelayed = function(me)
+{
+ this.graph.selectCellForEvent(this.cell, me.getEvent());
+};
+
+/**
+ * Function: reset
+ *
+ * Resets the state of this handler.
+ */
+mxGraphHandler.prototype.reset = function()
+{
+ this.destroyShapes();
+ this.cellWasClicked = false;
+ this.delayedSelection = false;
+ this.currentDx = null;
+ this.currentDy = null;
+ this.guides = null;
+ this.first = null;
+ this.cell = null;
+ this.target = null;
+};
+
+/**
+ * Function: shouldRemoveCellsFromParent
+ *
+ * Returns true if the given cells should be removed from the parent for the specified
+ * mousereleased event.
+ */
+mxGraphHandler.prototype.shouldRemoveCellsFromParent = function(parent, cells, evt)
+{
+ if (this.graph.getModel().isVertex(parent))
+ {
+ var pState = this.graph.getView().getState(parent);
+ var pt = mxUtils.convertPoint(this.graph.container,
+ mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+
+ return pState != null && !mxUtils.contains(pState, pt.x, pt.y);
+ }
+
+ return false;
+};
+
+/**
+ * Function: moveCells
+ *
+ * Moves the given cells by the specified amount.
+ */
+mxGraphHandler.prototype.moveCells = function(cells, dx, dy, clone, target, evt)
+{
+ if (clone)
+ {
+ cells = this.graph.getCloneableCells(cells);
+ }
+
+ // Removes cells from parent
+ if (target == null && this.isRemoveCellsFromParent() &&
+ this.shouldRemoveCellsFromParent(this.graph.getModel().getParent(this.cell), cells, evt))
+ {
+ target = this.graph.getDefaultParent();
+ }
+
+ // Passes all selected cells in order to correctly clone or move into
+ // the target cell. The method checks for each cell if its movable.
+ cells = this.graph.moveCells(cells, dx - this.graph.panDx / this.graph.view.scale,
+ dy - this.graph.panDy / this.graph.view.scale, clone, target, evt);
+
+ if (this.isSelectEnabled() && this.scrollOnMove)
+ {
+ this.graph.scrollCellToVisible(cells[0]);
+ }
+
+ // Selects the new cells if cells have been cloned
+ if (clone)
+ {
+ this.graph.setSelectionCells(cells);
+ }
+};
+
+/**
+ * Function: destroyShapes
+ *
+ * Destroy the preview and highlight shapes.
+ */
+mxGraphHandler.prototype.destroyShapes = function()
+{
+ // Destroys the preview dashed rectangle
+ if (this.shape != null)
+ {
+ this.shape.destroy();
+ this.shape = null;
+ }
+
+ if (this.guide != null)
+ {
+ this.guide.destroy();
+ this.guide = null;
+ }
+
+ // Destroys the drop target highlight
+ if (this.highlight != null)
+ {
+ this.highlight.destroy();
+ this.highlight = null;
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxGraphHandler.prototype.destroy = function()
+{
+ this.graph.removeMouseListener(this);
+ this.graph.removeListener(this.panHandler);
+ this.destroyShapes();
+};
diff --git a/src/js/handler/mxKeyHandler.js b/src/js/handler/mxKeyHandler.js
new file mode 100644
index 0000000..cc07e51
--- /dev/null
+++ b/src/js/handler/mxKeyHandler.js
@@ -0,0 +1,402 @@
+/**
+ * $Id: mxKeyHandler.js,v 1.48 2012-03-30 08:30:41 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxKeyHandler
+ *
+ * Event handler that listens to keystroke events. This is not a singleton,
+ * however, it is normally only required once if the target is the document
+ * element (default).
+ *
+ * This handler installs a key event listener in the topmost DOM node and
+ * processes all events that originate from descandants of <mxGraph.container>
+ * or from the topmost DOM node. The latter means that all unhandled keystrokes
+ * are handled by this object regardless of the focused state of the <graph>.
+ *
+ * Example:
+ *
+ * The following example creates a key handler that listens to the delete key
+ * (46) and deletes the selection cells if the graph is enabled.
+ *
+ * (code)
+ * var keyHandler = new mxKeyHandler(graph);
+ * keyHandler.bindKey(46, function(evt)
+ * {
+ * if (graph.isEnabled())
+ * {
+ * graph.removeCells();
+ * }
+ * });
+ * (end)
+ *
+ * Keycodes:
+ *
+ * See http://tinyurl.com/yp8jgl or http://tinyurl.com/229yqw for a list of
+ * keycodes or install a key event listener into the document element and print
+ * the key codes of the respective events to the console.
+ *
+ * To support the Command key and the Control key on the Mac, the following
+ * code can be used.
+ *
+ * (code)
+ * keyHandler.getFunction = function(evt)
+ * {
+ * if (evt != null)
+ * {
+ * return (mxEvent.isControlDown(evt) || (mxClient.IS_MAC && evt.metaKey)) ? this.controlKeys[evt.keyCode] : this.normalKeys[evt.keyCode];
+ * }
+ *
+ * return null;
+ * };
+ * (end)
+ *
+ * Constructor: mxKeyHandler
+ *
+ * Constructs an event handler that executes functions bound to specific
+ * keystrokes.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the associated <mxGraph>.
+ * target - Optional reference to the event target. If null, the document
+ * element is used as the event target, that is, the object where the key
+ * event listener is installed.
+ */
+function mxKeyHandler(graph, target)
+{
+ if (graph != null)
+ {
+ this.graph = graph;
+ this.target = target || document.documentElement;
+
+ // Creates the arrays to map from keycodes to functions
+ this.normalKeys = [];
+ this.shiftKeys = [];
+ this.controlKeys = [];
+ this.controlShiftKeys = [];
+
+ // Installs the keystroke listener in the target
+ mxEvent.addListener(this.target, "keydown",
+ mxUtils.bind(this, function(evt)
+ {
+ this.keyDown(evt);
+ })
+ );
+
+ // Automatically deallocates memory in IE
+ if (mxClient.IS_IE)
+ {
+ mxEvent.addListener(window, 'unload',
+ mxUtils.bind(this, function()
+ {
+ this.destroy();
+ })
+ );
+ }
+ }
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the <mxGraph> associated with this handler.
+ */
+mxKeyHandler.prototype.graph = null;
+
+/**
+ * Variable: target
+ *
+ * Reference to the target DOM, that is, the DOM node where the key event
+ * listeners are installed.
+ */
+mxKeyHandler.prototype.target = null;
+
+/**
+ * Variable: normalKeys
+ *
+ * Maps from keycodes to functions for non-pressed control keys.
+ */
+mxKeyHandler.prototype.normalKeys = null;
+
+/**
+ * Variable: shiftKeys
+ *
+ * Maps from keycodes to functions for pressed shift keys.
+ */
+mxKeyHandler.prototype.shiftKeys = null;
+
+/**
+ * Variable: controlKeys
+ *
+ * Maps from keycodes to functions for pressed control keys.
+ */
+mxKeyHandler.prototype.controlKeys = null;
+
+/**
+ * Variable: controlShiftKeys
+ *
+ * Maps from keycodes to functions for pressed control and shift keys.
+ */
+mxKeyHandler.prototype.controlShiftKeys = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxKeyHandler.prototype.enabled = true;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation returns
+ * <enabled>.
+ */
+mxKeyHandler.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling by updating <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxKeyHandler.prototype.setEnabled = function(enabled)
+{
+ this.enabled = enabled;
+};
+
+/**
+ * Function: bindKey
+ *
+ * Binds the specified keycode to the given function. This binding is used
+ * if the control key is not pressed.
+ *
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindKey = function(code, funct)
+{
+ this.normalKeys[code] = funct;
+};
+
+/**
+ * Function: bindShiftKey
+ *
+ * Binds the specified keycode to the given function. This binding is used
+ * if the shift key is pressed.
+ *
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindShiftKey = function(code, funct)
+{
+ this.shiftKeys[code] = funct;
+};
+
+/**
+ * Function: bindControlKey
+ *
+ * Binds the specified keycode to the given function. This binding is used
+ * if the control key is pressed.
+ *
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindControlKey = function(code, funct)
+{
+ this.controlKeys[code] = funct;
+};
+
+/**
+ * Function: bindControlShiftKey
+ *
+ * Binds the specified keycode to the given function. This binding is used
+ * if the control and shift key are pressed.
+ *
+ * Parameters:
+ *
+ * code - Integer that specifies the keycode.
+ * funct - JavaScript function that takes the key event as an argument.
+ */
+mxKeyHandler.prototype.bindControlShiftKey = function(code, funct)
+{
+ this.controlShiftKeys[code] = funct;
+};
+
+/**
+ * Function: isControlDown
+ *
+ * Returns true if the control key is pressed. This uses <mxEvent.isControlDown>.
+ *
+ * Parameters:
+ *
+ * evt - Key event whose control key pressed state should be returned.
+ */
+mxKeyHandler.prototype.isControlDown = function(evt)
+{
+ return mxEvent.isControlDown(evt);
+};
+
+/**
+ * Function: getFunction
+ *
+ * Returns the function associated with the given key event or null if no
+ * function is associated with the given event.
+ *
+ * Parameters:
+ *
+ * evt - Key event whose associated function should be returned.
+ */
+mxKeyHandler.prototype.getFunction = function(evt)
+{
+ if (evt != null)
+ {
+ if (this.isControlDown(evt))
+ {
+ if (mxEvent.isShiftDown(evt))
+ {
+ return this.controlShiftKeys[evt.keyCode];
+ }
+ else
+ {
+ return this.controlKeys[evt.keyCode];
+ }
+ }
+ else
+ {
+ if (mxEvent.isShiftDown(evt))
+ {
+ return this.shiftKeys[evt.keyCode];
+ }
+ else
+ {
+ return this.normalKeys[evt.keyCode];
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: isGraphEvent
+ *
+ * Returns true if the event should be processed by this handler, that is,
+ * if the event source is either the target, one of its direct children, a
+ * descendant of the <mxGraph.container>, or the <mxGraph.cellEditor> of the
+ * <graph>.
+ *
+ * Parameters:
+ *
+ * evt - Key event that represents the keystroke.
+ */
+mxKeyHandler.prototype.isGraphEvent = function(evt)
+{
+ var source = mxEvent.getSource(evt);
+
+ // Accepts events from the target object or
+ // in-place editing inside graph
+ if ((source == this.target || source.parentNode == this.target) ||
+ (this.graph.cellEditor != null && source == this.graph.cellEditor.textarea))
+ {
+ return true;
+ }
+
+ // Accepts events from inside the container
+ var elt = source;
+
+ while (elt != null)
+ {
+ if (elt == this.graph.container)
+ {
+ return true;
+ }
+
+ elt = elt.parentNode;
+ }
+
+ return false;
+};
+
+/**
+ * Function: keyDown
+ *
+ * Handles the event by invoking the function bound to the respective
+ * keystroke if <mxGraph.isEnabled>, <isEnabled> and <isGraphEvent> all
+ * return true for the given event and <mxGraph.isEditing> returns false.
+ * If the graph is editing only the <enter> and <escape> cases are handled
+ * by calling the respective hooks.
+ *
+ * Parameters:
+ *
+ * evt - Key event that represents the keystroke.
+ */
+mxKeyHandler.prototype.keyDown = function(evt)
+{
+ if (this.graph.isEnabled() && !mxEvent.isConsumed(evt) &&
+ this.isGraphEvent(evt) && this.isEnabled())
+ {
+ // Cancels the editing if escape is pressed
+ if (evt.keyCode == 27 /* Escape */)
+ {
+ this.escape(evt);
+ }
+
+ // Invokes the function for the keystroke
+ else if (!this.graph.isEditing())
+ {
+ var boundFunction = this.getFunction(evt);
+
+ if (boundFunction != null)
+ {
+ boundFunction(evt);
+ mxEvent.consume(evt);
+ }
+ }
+ }
+};
+
+/**
+ * Function: escape
+ *
+ * Hook to process ESCAPE keystrokes. This implementation invokes
+ * <mxGraph.stopEditing> to cancel the current editing, connecting
+ * and/or other ongoing modifications.
+ *
+ * Parameters:
+ *
+ * evt - Key event that represents the keystroke. Possible keycode in this
+ * case is 27 (ESCAPE).
+ */
+mxKeyHandler.prototype.escape = function(evt)
+{
+ if (this.graph.isEscapeEnabled())
+ {
+ this.graph.escape(evt);
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its references into the DOM. This does
+ * normally not need to be called, it is called automatically when the
+ * window unloads (in IE).
+ */
+mxKeyHandler.prototype.destroy = function()
+{
+ this.target = null;
+};
diff --git a/src/js/handler/mxPanningHandler.js b/src/js/handler/mxPanningHandler.js
new file mode 100644
index 0000000..b388144
--- /dev/null
+++ b/src/js/handler/mxPanningHandler.js
@@ -0,0 +1,390 @@
+/**
+ * $Id: mxPanningHandler.js,v 1.79 2012-07-17 14:37:41 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxPanningHandler
+ *
+ * Event handler that pans and creates popupmenus. To use the left
+ * mousebutton for panning without interfering with cell moving and
+ * resizing, use <isUseLeftButton> and <isIgnoreCell>. For grid size
+ * steps while panning, use <useGrid>. This handler is built-into
+ * <mxGraph.panningHandler> and enabled using <mxGraph.setPanning>.
+ *
+ * Constructor: mxPanningHandler
+ *
+ * Constructs an event handler that creates a <mxPopupMenu>
+ * and pans the graph.
+ *
+ * Event: mxEvent.PAN_START
+ *
+ * Fires when the panning handler changes its <active> state to true. The
+ * <code>event</code> property contains the corresponding <mxMouseEvent>.
+ *
+ * Event: mxEvent.PAN
+ *
+ * Fires while handle is processing events. The <code>event</code> property contains
+ * the corresponding <mxMouseEvent>.
+ *
+ * Event: mxEvent.PAN_END
+ *
+ * Fires when the panning handler changes its <active> state to false. The
+ * <code>event</code> property contains the corresponding <mxMouseEvent>.
+ */
+function mxPanningHandler(graph, factoryMethod)
+{
+ if (graph != null)
+ {
+ this.graph = graph;
+ this.factoryMethod = factoryMethod;
+ this.graph.addMouseListener(this);
+ this.init();
+ }
+};
+
+/**
+ * Extends mxPopupMenu.
+ */
+mxPanningHandler.prototype = new mxPopupMenu();
+mxPanningHandler.prototype.constructor = mxPanningHandler;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxPanningHandler.prototype.graph = null;
+
+/**
+ * Variable: usePopupTrigger
+ *
+ * Specifies if the <isPopupTrigger> should also be used for panning. To
+ * avoid conflicts, the panning is only activated if the mouse was moved
+ * more than <mxGraph.tolerance>, otherwise, a single click is assumed
+ * and the popupmenu is displayed. Default is true.
+ */
+mxPanningHandler.prototype.usePopupTrigger = true;
+
+/**
+ * Variable: useLeftButtonForPanning
+ *
+ * Specifies if panning should be active for the left mouse button.
+ * Setting this to true may conflict with <mxRubberband>. Default is false.
+ */
+mxPanningHandler.prototype.useLeftButtonForPanning = false;
+
+/**
+ * Variable: selectOnPopup
+ *
+ * Specifies if cells should be selected if a popupmenu is displayed for
+ * them. Default is true.
+ */
+mxPanningHandler.prototype.selectOnPopup = true;
+
+/**
+ * Variable: clearSelectionOnBackground
+ *
+ * Specifies if cells should be deselected if a popupmenu is displayed for
+ * the diagram background. Default is true.
+ */
+mxPanningHandler.prototype.clearSelectionOnBackground = true;
+
+/**
+ * Variable: ignoreCell
+ *
+ * Specifies if panning should be active even if there is a cell under the
+ * mousepointer. Default is false.
+ */
+mxPanningHandler.prototype.ignoreCell = false;
+
+/**
+ * Variable: previewEnabled
+ *
+ * Specifies if the panning should be previewed. Default is true.
+ */
+mxPanningHandler.prototype.previewEnabled = true;
+
+/**
+ * Variable: useGrid
+ *
+ * Specifies if the panning steps should be aligned to the grid size.
+ * Default is false.
+ */
+mxPanningHandler.prototype.useGrid = false;
+
+/**
+ * Variable: panningEnabled
+ *
+ * Specifies if panning should be enabled. Default is true.
+ */
+mxPanningHandler.prototype.panningEnabled = true;
+
+/**
+ * Function: isPanningEnabled
+ *
+ * Returns <panningEnabled>.
+ */
+mxPanningHandler.prototype.isPanningEnabled = function()
+{
+ return this.panningEnabled;
+};
+
+/**
+ * Function: setPanningEnabled
+ *
+ * Sets <panningEnabled>.
+ */
+mxPanningHandler.prototype.setPanningEnabled = function(value)
+{
+ this.panningEnabled = value;
+};
+
+/**
+ * Function: init
+ *
+ * Initializes the shapes required for this vertex handler.
+ */
+mxPanningHandler.prototype.init = function()
+{
+ // Supercall
+ mxPopupMenu.prototype.init.apply(this);
+
+ // Hides the tooltip if the mouse is over
+ // the context menu
+ mxEvent.addListener(this.div, (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove',
+ mxUtils.bind(this, function(evt)
+ {
+ this.graph.tooltipHandler.hide();
+ })
+ );
+};
+
+/**
+ * Function: isPanningTrigger
+ *
+ * Returns true if the given event is a panning trigger for the optional
+ * given cell. This returns true if control-shift is pressed or if
+ * <usePopupTrigger> is true and the event is a popup trigger.
+ */
+mxPanningHandler.prototype.isPanningTrigger = function(me)
+{
+ var evt = me.getEvent();
+
+ return (this.useLeftButtonForPanning && (this.ignoreCell || me.getState() == null) &&
+ mxEvent.isLeftMouseButton(evt)) || (mxEvent.isControlDown(evt) &&
+ mxEvent.isShiftDown(evt)) || (this.usePopupTrigger &&
+ mxEvent.isPopupTrigger(evt));
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event by initiating the panning. By consuming the event all
+ * subsequent events of the gesture are redirected to this handler.
+ */
+mxPanningHandler.prototype.mouseDown = function(sender, me)
+{
+ if (!me.isConsumed() && this.isEnabled())
+ {
+ // Hides the popupmenu if is is being displayed
+ this.hideMenu();
+
+ this.dx0 = -this.graph.container.scrollLeft;
+ this.dy0 = -this.graph.container.scrollTop;
+
+ // Checks the event triggers to panning and popupmenu
+ this.popupTrigger = this.isPopupTrigger(me);
+ this.panningTrigger = this.isPanningEnabled() &&
+ this.isPanningTrigger(me);
+
+ // Stores the location of the trigger event
+ this.startX = me.getX();
+ this.startY = me.getY();
+
+ // Displays popup menu on Mac after the mouse was released
+ if (this.panningTrigger)
+ {
+ this.consumePanningTrigger(me);
+ }
+ }
+};
+
+/**
+ * Function: consumePanningTrigger
+ *
+ * Consumes the given <mxMouseEvent> if it was a panning trigger in
+ * <mouseDown>. The default is to invoke <mxMouseEvent.consume>. Note that this
+ * will block any further event processing. If you haven't disabled built-in
+ * context menus and require immediate selection of the cell on mouseDown in
+ * Safari and/or on the Mac, then use the following code:
+ *
+ * (code)
+ * mxPanningHandler.prototype.consumePanningTrigger = function(me)
+ * {
+ * if (me.evt.preventDefault)
+ * {
+ * me.evt.preventDefault();
+ * }
+ *
+ * // Stops event processing in IE
+ * me.evt.returnValue = false;
+ *
+ * // Sets local consumed state
+ * if (!mxClient.IS_SF && !mxClient.IS_MAC)
+ * {
+ * me.consumed = true;
+ * }
+ * };
+ * (end)
+ */
+mxPanningHandler.prototype.consumePanningTrigger = function(me)
+{
+ me.consume();
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by updating the panning on the graph.
+ */
+mxPanningHandler.prototype.mouseMove = function(sender, me)
+{
+ var dx = me.getX() - this.startX;
+ var dy = me.getY() - this.startY;
+
+ if (this.active)
+ {
+ if (this.previewEnabled)
+ {
+ // Applies the grid to the panning steps
+ if (this.useGrid)
+ {
+ dx = this.graph.snap(dx);
+ dy = this.graph.snap(dy);
+ }
+
+ this.graph.panGraph(dx + this.dx0, dy + this.dy0);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.PAN, 'event', me));
+ me.consume();
+ }
+ else if (this.panningTrigger)
+ {
+ var tmp = this.active;
+
+ // Panning is activated only if the mouse is moved
+ // beyond the graph tolerance
+ this.active = Math.abs(dx) > this.graph.tolerance ||
+ Math.abs(dy) > this.graph.tolerance;
+
+ if (!tmp && this.active)
+ {
+ this.fireEvent(new mxEventObject(mxEvent.PAN_START, 'event', me));
+ }
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by setting the translation on the view or showing the
+ * popupmenu.
+ */
+mxPanningHandler.prototype.mouseUp = function(sender, me)
+{
+ // Shows popup menu if mouse was not moved
+ var dx = Math.abs(me.getX() - this.startX);
+ var dy = Math.abs(me.getY() - this.startY);
+
+ if (this.active)
+ {
+ if (!this.graph.useScrollbarsForPanning || !mxUtils.hasScrollbars(this.graph.container))
+ {
+ dx = me.getX() - this.startX;
+ dy = me.getY() - this.startY;
+
+ // Applies the grid to the panning steps
+ if (this.useGrid)
+ {
+ dx = this.graph.snap(dx);
+ dy = this.graph.snap(dy);
+ }
+
+ var scale = this.graph.getView().scale;
+ var t = this.graph.getView().translate;
+
+ this.graph.panGraph(0, 0);
+ this.panGraph(t.x + dx / scale, t.y + dy / scale);
+ }
+
+ this.active = false;
+ this.fireEvent(new mxEventObject(mxEvent.PAN_END, 'event', me));
+ me.consume();
+ }
+ else if (this.popupTrigger)
+ {
+ if (dx < this.graph.tolerance && dy < this.graph.tolerance)
+ {
+ var cell = this.getCellForPopupEvent(me);
+
+ // Selects the cell for which the context menu is being displayed
+ if (this.graph.isEnabled() && this.selectOnPopup &&
+ cell != null && !this.graph.isCellSelected(cell))
+ {
+ this.graph.setSelectionCell(cell);
+ }
+ else if (this.clearSelectionOnBackground && cell == null)
+ {
+ this.graph.clearSelection();
+ }
+
+ // Hides the tooltip if there is one
+ this.graph.tooltipHandler.hide();
+ var origin = mxUtils.getScrollOrigin();
+ var point = new mxPoint(me.getX() + origin.x,
+ me.getY() + origin.y);
+
+ // Menu is shifted by 1 pixel so that the mouse up event
+ // is routed via the underlying shape instead of the DIV
+ this.popup(point.x + 1, point.y + 1, cell, me.getEvent());
+ me.consume();
+ }
+ }
+
+ this.panningTrigger = false;
+ this.popupTrigger = false;
+};
+
+/**
+ * Function: getCellForPopupEvent
+ *
+ * Hook to return the cell for the mouse up popup trigger handling.
+ */
+mxPanningHandler.prototype.getCellForPopupEvent = function(me)
+{
+ return me.getCell();
+};
+
+/**
+ * Function: panGraph
+ *
+ * Pans <graph> by the given amount.
+ */
+mxPanningHandler.prototype.panGraph = function(dx, dy)
+{
+ this.graph.getView().setTranslate(dx, dy);
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxPanningHandler.prototype.destroy = function()
+{
+ this.graph.removeMouseListener(this);
+
+ // Supercall
+ mxPopupMenu.prototype.destroy.apply(this);
+};
diff --git a/src/js/handler/mxRubberband.js b/src/js/handler/mxRubberband.js
new file mode 100644
index 0000000..f9e7187
--- /dev/null
+++ b/src/js/handler/mxRubberband.js
@@ -0,0 +1,348 @@
+/**
+ * $Id: mxRubberband.js,v 1.48 2012-04-13 12:53:30 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxRubberband
+ *
+ * Event handler that selects rectangular regions. This is not built-into
+ * <mxGraph>. To enable rubberband selection in a graph, use the following code.
+ *
+ * Example:
+ *
+ * (code)
+ * var rubberband = new mxRubberband(graph);
+ * (end)
+ *
+ * Constructor: mxRubberband
+ *
+ * Constructs an event handler that selects rectangular regions in the graph
+ * using rubberband selection.
+ */
+function mxRubberband(graph)
+{
+ if (graph != null)
+ {
+ this.graph = graph;
+ this.graph.addMouseListener(this);
+
+ // Repaints the marquee after autoscroll
+ this.panHandler = mxUtils.bind(this, function()
+ {
+ this.repaint();
+ });
+
+ this.graph.addListener(mxEvent.PAN, this.panHandler);
+
+ // Automatic deallocation of memory
+ if (mxClient.IS_IE)
+ {
+ mxEvent.addListener(window, 'unload',
+ mxUtils.bind(this, function()
+ {
+ this.destroy();
+ })
+ );
+ }
+ }
+};
+
+/**
+ * Variable: defaultOpacity
+ *
+ * Specifies the default opacity to be used for the rubberband div. Default
+ * is 20.
+ */
+mxRubberband.prototype.defaultOpacity = 20;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxRubberband.prototype.enabled = true;
+
+/**
+ * Variable: div
+ *
+ * Holds the DIV element which is currently visible.
+ */
+mxRubberband.prototype.div = null;
+
+/**
+ * Variable: sharedDiv
+ *
+ * Holds the DIV element which is used to display the rubberband.
+ */
+mxRubberband.prototype.sharedDiv = null;
+
+/**
+ * Variable: currentX
+ *
+ * Holds the value of the x argument in the last call to <update>.
+ */
+mxRubberband.prototype.currentX = 0;
+
+/**
+ * Variable: currentY
+ *
+ * Holds the value of the y argument in the last call to <update>.
+ */
+mxRubberband.prototype.currentY = 0;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation returns
+ * <enabled>.
+ */
+mxRubberband.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation updates
+ * <enabled>.
+ */
+mxRubberband.prototype.setEnabled = function(enabled)
+{
+ this.enabled = enabled;
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event by initiating a rubberband selection. By consuming the
+ * event all subsequent events of the gesture are redirected to this
+ * handler.
+ */
+mxRubberband.prototype.mouseDown = function(sender, me)
+{
+ if (!me.isConsumed() && this.isEnabled() && this.graph.isEnabled() &&
+ (this.graph.isForceMarqueeEvent(me.getEvent()) || me.getState() == null))
+ {
+ var offset = mxUtils.getOffset(this.graph.container);
+ var origin = mxUtils.getScrollOrigin(this.graph.container);
+ origin.x -= offset.x;
+ origin.y -= offset.y;
+ this.start(me.getX() + origin.x, me.getY() + origin.y);
+
+ // Workaround for rubberband stopping if the mouse leaves the
+ // graph container in Firefox.
+ if (mxClient.IS_NS && !mxClient.IS_SF && !mxClient.IS_GC)
+ {
+ var container = this.graph.container;
+
+ function createMouseEvent(evt)
+ {
+ var me = new mxMouseEvent(evt);
+ var pt = mxUtils.convertPoint(container, me.getX(), me.getY());
+
+ me.graphX = pt.x;
+ me.graphY = pt.y;
+
+ return me;
+ };
+
+ this.dragHandler = mxUtils.bind(this, function(evt)
+ {
+ this.mouseMove(this.graph, createMouseEvent(evt));
+ });
+
+ this.dropHandler = mxUtils.bind(this, function(evt)
+ {
+ this.mouseUp(this.graph, createMouseEvent(evt));
+ });
+
+ mxEvent.addListener(document, 'mousemove', this.dragHandler);
+ mxEvent.addListener(document, 'mouseup', this.dropHandler);
+ }
+
+ // Does not prevent the default for this event so that the
+ // event processing chain is still executed even if we start
+ // rubberbanding. This is required eg. in ExtJs to hide the
+ // current context menu. In mouseMove we'll make sure we're
+ // not selecting anything while we're rubberbanding.
+ me.consume(false);
+ }
+};
+
+/**
+ * Function: start
+ *
+ * Sets the start point for the rubberband selection.
+ */
+mxRubberband.prototype.start = function(x, y)
+{
+ this.first = new mxPoint(x, y);
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by updating therubberband selection.
+ */
+mxRubberband.prototype.mouseMove = function(sender, me)
+{
+ if (!me.isConsumed() && this.first != null)
+ {
+ var origin = mxUtils.getScrollOrigin(this.graph.container);
+ var offset = mxUtils.getOffset(this.graph.container);
+ origin.x -= offset.x;
+ origin.y -= offset.y;
+ var x = me.getX() + origin.x;
+ var y = me.getY() + origin.y;
+ var dx = this.first.x - x;
+ var dy = this.first.y - y;
+ var tol = this.graph.tolerance;
+
+ if (this.div != null || Math.abs(dx) > tol || Math.abs(dy) > tol)
+ {
+ if (this.div == null)
+ {
+ this.div = this.createShape();
+ }
+
+ // Clears selection while rubberbanding. This is required because
+ // the event is not consumed in mouseDown.
+ mxUtils.clearSelection();
+
+ this.update(x, y);
+ me.consume();
+ }
+ }
+};
+
+/**
+ * Function: createShape
+ *
+ * Creates the rubberband selection shape.
+ */
+mxRubberband.prototype.createShape = function()
+{
+ if (this.sharedDiv == null)
+ {
+ this.sharedDiv = document.createElement('div');
+ this.sharedDiv.className = 'mxRubberband';
+ mxUtils.setOpacity(this.sharedDiv, this.defaultOpacity);
+ }
+
+ this.graph.container.appendChild(this.sharedDiv);
+
+ return this.sharedDiv;
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by selecting the region of the rubberband using
+ * <mxGraph.selectRegion>.
+ */
+mxRubberband.prototype.mouseUp = function(sender, me)
+{
+ var execute = this.div != null;
+ this.reset();
+
+ if (execute)
+ {
+ var rect = new mxRectangle(this.x, this.y, this.width, this.height);
+ this.graph.selectRegion(rect, me.getEvent());
+ me.consume();
+ }
+};
+
+/**
+ * Function: reset
+ *
+ * Resets the state of the rubberband selection.
+ */
+mxRubberband.prototype.reset = function()
+{
+ if (this.div != null)
+ {
+ this.div.parentNode.removeChild(this.div);
+ }
+
+ if (this.dragHandler != null)
+ {
+ mxEvent.removeListener(document, 'mousemove', this.dragHandler);
+ this.dragHandler = null;
+ }
+
+ if (this.dropHandler != null)
+ {
+ mxEvent.removeListener(document, 'mouseup', this.dropHandler);
+ this.dropHandler = null;
+ }
+
+ this.currentX = 0;
+ this.currentY = 0;
+ this.first = null;
+ this.div = null;
+};
+
+/**
+ * Function: update
+ *
+ * Sets <currentX> and <currentY> and calls <repaint>.
+ */
+mxRubberband.prototype.update = function(x, y)
+{
+ this.currentX = x;
+ this.currentY = y;
+
+ this.repaint();
+};
+
+/**
+ * Function: repaint
+ *
+ * Computes the bounding box and updates the style of the <div>.
+ */
+mxRubberband.prototype.repaint = function()
+{
+ if (this.div != null)
+ {
+ var x = this.currentX - this.graph.panDx;
+ var y = this.currentY - this.graph.panDy;
+
+ this.x = Math.min(this.first.x, x);
+ this.y = Math.min(this.first.y, y);
+ this.width = Math.max(this.first.x, x) - this.x;
+ this.height = Math.max(this.first.y, y) - this.y;
+
+ var dx = (mxClient.IS_VML) ? this.graph.panDx : 0;
+ var dy = (mxClient.IS_VML) ? this.graph.panDy : 0;
+
+ this.div.style.left = (this.x + dx) + 'px';
+ this.div.style.top = (this.y + dy) + 'px';
+ this.div.style.width = Math.max(1, this.width) + 'px';
+ this.div.style.height = Math.max(1, this.height) + 'px';
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes. This does
+ * normally not need to be called, it is called automatically when the
+ * window unloads.
+ */
+mxRubberband.prototype.destroy = function()
+{
+ if (!this.destroyed)
+ {
+ this.destroyed = true;
+ this.graph.removeMouseListener(this);
+ this.graph.removeListener(this.panHandler);
+ this.reset();
+
+ if (this.sharedDiv != null)
+ {
+ this.sharedDiv = null;
+ }
+ }
+};
diff --git a/src/js/handler/mxSelectionCellsHandler.js b/src/js/handler/mxSelectionCellsHandler.js
new file mode 100644
index 0000000..800d718
--- /dev/null
+++ b/src/js/handler/mxSelectionCellsHandler.js
@@ -0,0 +1,260 @@
+/**
+ * $Id: mxSelectionCellsHandler.js,v 1.5 2012-08-10 11:35:06 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxSelectionCellsHandler
+ *
+ * An event handler that manages cell handlers and invokes their mouse event
+ * processing functions.
+ *
+ * Group: Events
+ *
+ * Event: mxEvent.ADD
+ *
+ * Fires if a cell has been added to the selection. The <code>state</code>
+ * property contains the <mxCellState> that has been added.
+ *
+ * Event: mxEvent.REMOVE
+ *
+ * Fires if a cell has been remove from the selection. The <code>state</code>
+ * property contains the <mxCellState> that has been removed.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxSelectionCellsHandler(graph)
+{
+ this.graph = graph;
+ this.handlers = new mxDictionary();
+ this.graph.addMouseListener(this);
+
+ this.refreshHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.refresh();
+ }
+ });
+
+ this.graph.getSelectionModel().addListener(mxEvent.CHANGE, this.refreshHandler);
+ this.graph.getModel().addListener(mxEvent.CHANGE, this.refreshHandler);
+ this.graph.getView().addListener(mxEvent.SCALE, this.refreshHandler);
+ this.graph.getView().addListener(mxEvent.TRANSLATE, this.refreshHandler);
+ this.graph.getView().addListener(mxEvent.SCALE_AND_TRANSLATE, this.refreshHandler);
+ this.graph.getView().addListener(mxEvent.DOWN, this.refreshHandler);
+ this.graph.getView().addListener(mxEvent.UP, this.refreshHandler);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxSelectionCellsHandler.prototype = new mxEventSource();
+mxSelectionCellsHandler.prototype.constructor = mxSelectionCellsHandler;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxSelectionCellsHandler.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxSelectionCellsHandler.prototype.enabled = true;
+
+/**
+ * Variable: refreshHandler
+ *
+ * Keeps a reference to an event listener for later removal.
+ */
+mxSelectionCellsHandler.prototype.refreshHandler = null;
+
+/**
+ * Variable: maxHandlers
+ *
+ * Defines the maximum number of handlers to paint individually. Default is 100.
+ */
+mxSelectionCellsHandler.prototype.maxHandlers = 100;
+
+/**
+ * Variable: handlers
+ *
+ * <mxDictionary> that maps from cells to handlers.
+ */
+mxSelectionCellsHandler.prototype.handlers = null;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns <enabled>.
+ */
+mxSelectionCellsHandler.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Sets <enabled>.
+ */
+mxSelectionCellsHandler.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: getHandler
+ *
+ * Returns the handler for the given cell.
+ */
+mxSelectionCellsHandler.prototype.getHandler = function(cell)
+{
+ return this.handlers.get(cell);
+};
+
+/**
+ * Function: reset
+ *
+ * Resets all handlers.
+ */
+mxSelectionCellsHandler.prototype.reset = function()
+{
+ this.handlers.visit(function(key, handler)
+ {
+ handler.reset.apply(handler);
+ });
+};
+
+/**
+ * Function: refresh
+ *
+ * Reloads or updates all handlers.
+ */
+mxSelectionCellsHandler.prototype.refresh = function()
+{
+ // Removes all existing handlers
+ var oldHandlers = this.handlers;
+ this.handlers = new mxDictionary();
+
+ // Creates handles for all selection cells
+ var tmp = this.graph.getSelectionCells();
+
+ for (var i = 0; i < tmp.length; i++)
+ {
+ var state = this.graph.view.getState(tmp[i]);
+
+ if (state != null)
+ {
+ var handler = oldHandlers.remove(tmp[i]);
+
+ if (handler != null)
+ {
+ if (handler.state != state)
+ {
+ handler.destroy();
+ handler = null;
+ }
+ else
+ {
+ handler.redraw();
+ }
+ }
+
+ if (handler == null)
+ {
+ handler = this.graph.createHandler(state);
+ this.fireEvent(new mxEventObject(mxEvent.ADD, 'state', state));
+ }
+
+ if (handler != null)
+ {
+ this.handlers.put(tmp[i], handler);
+ }
+ }
+ }
+
+ // Destroys all unused handlers
+ oldHandlers.visit(mxUtils.bind(this, function(key, handler)
+ {
+ this.fireEvent(new mxEventObject(mxEvent.REMOVE, 'state', handler.state));
+ handler.destroy();
+ }));
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Redirects the given event to the handlers.
+ */
+mxSelectionCellsHandler.prototype.mouseDown = function(sender, me)
+{
+ if (this.graph.isEnabled() && this.isEnabled())
+ {
+ var args = [sender, me];
+
+ this.handlers.visit(function(key, handler)
+ {
+ handler.mouseDown.apply(handler, args);
+ });
+ }
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Redirects the given event to the handlers.
+ */
+mxSelectionCellsHandler.prototype.mouseMove = function(sender, me)
+{
+ if (this.graph.isEnabled() && this.isEnabled())
+ {
+ var args = [sender, me];
+
+ this.handlers.visit(function(key, handler)
+ {
+ handler.mouseMove.apply(handler, args);
+ });
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Redirects the given event to the handlers.
+ */
+mxSelectionCellsHandler.prototype.mouseUp = function(sender, me)
+{
+ if (this.graph.isEnabled() && this.isEnabled())
+ {
+ var args = [sender, me];
+
+ this.handlers.visit(function(key, handler)
+ {
+ handler.mouseUp.apply(handler, args);
+ });
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxSelectionCellsHandler.prototype.destroy = function()
+{
+ this.graph.removeMouseListener(this);
+
+ if (this.refreshHandler != null)
+ {
+ this.graph.getSelectionModel().removeListener(this.refreshHandler);
+ this.graph.getModel().removeListener(this.refreshHandler);
+ this.graph.getView().removeListener(this.refreshHandler);
+ this.refreshHandler = null;
+ }
+};
diff --git a/src/js/handler/mxTooltipHandler.js b/src/js/handler/mxTooltipHandler.js
new file mode 100644
index 0000000..4e34a13
--- /dev/null
+++ b/src/js/handler/mxTooltipHandler.js
@@ -0,0 +1,317 @@
+/**
+ * $Id: mxTooltipHandler.js,v 1.51 2011-03-31 10:11:17 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxTooltipHandler
+ *
+ * Graph event handler that displays tooltips. <mxGraph.getTooltip> is used to
+ * get the tooltip for a cell or handle. This handler is built-into
+ * <mxGraph.tooltipHandler> and enabled using <mxGraph.setTooltips>.
+ *
+ * Example:
+ *
+ * (code>
+ * new mxTooltipHandler(graph);
+ * (end)
+ *
+ * Constructor: mxTooltipHandler
+ *
+ * Constructs an event handler that displays tooltips with the specified
+ * delay (in milliseconds). If no delay is specified then a default delay
+ * of 500 ms (0.5 sec) is used.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * delay - Optional delay in milliseconds.
+ */
+function mxTooltipHandler(graph, delay)
+{
+ if (graph != null)
+ {
+ this.graph = graph;
+ this.delay = delay || 500;
+ this.graph.addMouseListener(this);
+ }
+};
+
+/**
+ * Variable: zIndex
+ *
+ * Specifies the zIndex for the tooltip and its shadow. Default is 10005.
+ */
+mxTooltipHandler.prototype.zIndex = 10005;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxTooltipHandler.prototype.graph = null;
+
+/**
+ * Variable: delay
+ *
+ * Delay to show the tooltip in milliseconds. Default is 500.
+ */
+mxTooltipHandler.prototype.delay = null;
+
+/**
+ * Variable: hideOnHover
+ *
+ * Specifies if the tooltip should be hidden if the mouse is moved over the
+ * current cell. Default is false.
+ */
+mxTooltipHandler.prototype.hideOnHover = false;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxTooltipHandler.prototype.enabled = true;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxTooltipHandler.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ */
+mxTooltipHandler.prototype.setEnabled = function(enabled)
+{
+ this.enabled = enabled;
+};
+
+/**
+ * Function: isHideOnHover
+ *
+ * Returns <hideOnHover>.
+ */
+mxTooltipHandler.prototype.isHideOnHover = function()
+{
+ return this.hideOnHover;
+};
+
+/**
+ * Function: setHideOnHover
+ *
+ * Sets <hideOnHover>.
+ */
+mxTooltipHandler.prototype.setHideOnHover = function(value)
+{
+ this.hideOnHover = value;
+};
+
+/**
+ * Function: init
+ *
+ * Initializes the DOM nodes required for this tooltip handler.
+ */
+mxTooltipHandler.prototype.init = function()
+{
+ if (document.body != null)
+ {
+ this.div = document.createElement('div');
+ this.div.className = 'mxTooltip';
+ this.div.style.visibility = 'hidden';
+ this.div.style.zIndex = this.zIndex;
+
+ document.body.appendChild(this.div);
+
+ mxEvent.addListener(this.div, 'mousedown',
+ mxUtils.bind(this, function(evt)
+ {
+ this.hideTooltip();
+ })
+ );
+ }
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event by initiating a rubberband selection. By consuming the
+ * event all subsequent events of the gesture are redirected to this
+ * handler.
+ */
+mxTooltipHandler.prototype.mouseDown = function(sender, me)
+{
+ this.reset(me, false);
+ this.hideTooltip();
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by updating the rubberband selection.
+ */
+mxTooltipHandler.prototype.mouseMove = function(sender, me)
+{
+ if (me.getX() != this.lastX || me.getY() != this.lastY)
+ {
+ this.reset(me, true);
+
+ if (this.isHideOnHover() || me.getState() != this.state || (me.getSource() != this.node &&
+ (!this.stateSource || (me.getState() != null && this.stateSource ==
+ (me.isSource(me.getState().shape) || !me.isSource(me.getState().text))))))
+ {
+ this.hideTooltip();
+ }
+ }
+
+ this.lastX = me.getX();
+ this.lastY = me.getY();
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by resetting the tooltip timer or hiding the existing
+ * tooltip.
+ */
+mxTooltipHandler.prototype.mouseUp = function(sender, me)
+{
+ this.reset(me, true);
+ this.hideTooltip();
+};
+
+
+/**
+ * Function: resetTimer
+ *
+ * Resets the timer.
+ */
+mxTooltipHandler.prototype.resetTimer = function()
+{
+ if (this.thread != null)
+ {
+ window.clearTimeout(this.thread);
+ this.thread = null;
+ }
+};
+
+/**
+ * Function: reset
+ *
+ * Resets and/or restarts the timer to trigger the display of the tooltip.
+ */
+mxTooltipHandler.prototype.reset = function(me, restart)
+{
+ this.resetTimer();
+
+ if (restart && this.isEnabled() && me.getState() != null && (this.div == null ||
+ this.div.style.visibility == 'hidden'))
+ {
+ var state = me.getState();
+ var node = me.getSource();
+ var x = me.getX();
+ var y = me.getY();
+ var stateSource = me.isSource(state.shape) || me.isSource(state.text);
+
+ this.thread = window.setTimeout(mxUtils.bind(this, function()
+ {
+ if (!this.graph.isEditing() && !this.graph.panningHandler.isMenuShowing())
+ {
+ // Uses information from inside event cause using the event at
+ // this (delayed) point in time is not possible in IE as it no
+ // longer contains the required information (member not found)
+ var tip = this.graph.getTooltip(state, node, x, y);
+ this.show(tip, x, y);
+ this.state = state;
+ this.node = node;
+ this.stateSource = stateSource;
+ }
+ }), this.delay);
+ }
+};
+
+/**
+ * Function: hide
+ *
+ * Hides the tooltip and resets the timer.
+ */
+mxTooltipHandler.prototype.hide = function()
+{
+ this.resetTimer();
+ this.hideTooltip();
+};
+
+/**
+ * Function: hideTooltip
+ *
+ * Hides the tooltip.
+ */
+mxTooltipHandler.prototype.hideTooltip = function()
+{
+ if (this.div != null)
+ {
+ this.div.style.visibility = 'hidden';
+ }
+};
+
+/**
+ * Function: show
+ *
+ * Shows the tooltip for the specified cell and optional index at the
+ * specified location (with a vertical offset of 10 pixels).
+ */
+mxTooltipHandler.prototype.show = function(tip, x, y)
+{
+ if (tip != null && tip.length > 0)
+ {
+ // Initializes the DOM nodes if required
+ if (this.div == null)
+ {
+ this.init();
+ }
+
+ var origin = mxUtils.getScrollOrigin();
+
+ this.div.style.left = (x + origin.x) + 'px';
+ this.div.style.top = (y + mxConstants.TOOLTIP_VERTICAL_OFFSET +
+ origin.y) + 'px';
+
+ if (!mxUtils.isNode(tip))
+ {
+ this.div.innerHTML = tip.replace(/\n/g, '<br>');
+ }
+ else
+ {
+ this.div.innerHTML = '';
+ this.div.appendChild(tip);
+ }
+
+ this.div.style.visibility = '';
+ mxUtils.fit(this.div);
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxTooltipHandler.prototype.destroy = function()
+{
+ this.graph.removeMouseListener(this);
+ mxEvent.release(this.div);
+
+ if (this.div != null && this.div.parentNode != null)
+ {
+ this.div.parentNode.removeChild(this.div);
+ }
+
+ this.div = null;
+};
diff --git a/src/js/handler/mxVertexHandler.js b/src/js/handler/mxVertexHandler.js
new file mode 100644
index 0000000..0b12e27
--- /dev/null
+++ b/src/js/handler/mxVertexHandler.js
@@ -0,0 +1,753 @@
+/**
+ * $Id: mxVertexHandler.js,v 1.107 2012-11-20 09:06:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxVertexHandler
+ *
+ * Event handler for resizing cells. This handler is automatically created in
+ * <mxGraph.createHandler>.
+ *
+ * Constructor: mxVertexHandler
+ *
+ * Constructs an event handler that allows to resize vertices
+ * and groups.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the cell to be resized.
+ */
+function mxVertexHandler(state)
+{
+ if (state != null)
+ {
+ this.state = state;
+ this.init();
+ }
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxVertexHandler.prototype.graph = null;
+
+/**
+ * Variable: state
+ *
+ * Reference to the <mxCellState> being modified.
+ */
+mxVertexHandler.prototype.state = null;
+
+/**
+ * Variable: singleSizer
+ *
+ * Specifies if only one sizer handle at the bottom, right corner should be
+ * used. Default is false.
+ */
+mxVertexHandler.prototype.singleSizer = false;
+
+/**
+ * Variable: index
+ *
+ * Holds the index of the current handle.
+ */
+mxVertexHandler.prototype.index = null;
+
+/**
+ * Variable: allowHandleBoundsCheck
+ *
+ * Specifies if the bounds of handles should be used for hit-detection in IE
+ * Default is true.
+ */
+mxVertexHandler.prototype.allowHandleBoundsCheck = true;
+
+/**
+ * Variable: crisp
+ *
+ * Specifies if the selection bounds and handles should be rendered in crisp
+ * mode. Default is true.
+ */
+mxVertexHandler.prototype.crisp = true;
+
+/**
+ * Variable: handleImage
+ *
+ * Optional <mxImage> to be used as handles. Default is null.
+ */
+mxVertexHandler.prototype.handleImage = null;
+
+/**
+ * Variable: tolerance
+ *
+ * Optional tolerance for hit-detection in <getHandleForEvent>. Default is 0.
+ */
+mxVertexHandler.prototype.tolerance = 0;
+
+/**
+ * Function: init
+ *
+ * Initializes the shapes required for this vertex handler.
+ */
+mxVertexHandler.prototype.init = function()
+{
+ this.graph = this.state.view.graph;
+ this.selectionBounds = this.getSelectionBounds(this.state);
+ this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y,
+ this.selectionBounds.width, this.selectionBounds.height);
+ this.selectionBorder = this.createSelectionShape(this.bounds);
+ this.selectionBorder.dialect =
+ (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ this.selectionBorder.init(this.graph.getView().getOverlayPane());
+
+ // Event-transparency
+ if (this.selectionBorder.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.selectionBorder.node.setAttribute('pointer-events', 'none');
+ }
+ else
+ {
+ this.selectionBorder.node.style.background = '';
+ }
+
+ if (this.graph.isCellMovable(this.state.cell))
+ {
+ this.selectionBorder.node.style.cursor = mxConstants.CURSOR_MOVABLE_VERTEX;
+ }
+
+ mxEvent.redirectMouseEvents(this.selectionBorder.node, this.graph, this.state);
+
+ // Adds the sizer handles
+ if (mxGraphHandler.prototype.maxCells <= 0 ||
+ this.graph.getSelectionCount() < mxGraphHandler.prototype.maxCells)
+ {
+ var resizable = this.graph.isCellResizable(this.state.cell);
+ this.sizers = [];
+
+ if (resizable || (this.graph.isLabelMovable(this.state.cell) &&
+ this.state.width >= 2 && this.state.height >= 2))
+ {
+ var i = 0;
+
+ if (resizable)
+ {
+ if (!this.singleSizer)
+ {
+ this.sizers.push(this.createSizer('nw-resize', i++));
+ this.sizers.push(this.createSizer('n-resize', i++));
+ this.sizers.push(this.createSizer('ne-resize', i++));
+ this.sizers.push(this.createSizer('w-resize', i++));
+ this.sizers.push(this.createSizer('e-resize', i++));
+ this.sizers.push(this.createSizer('sw-resize', i++));
+ this.sizers.push(this.createSizer('s-resize', i++));
+ }
+
+ this.sizers.push(this.createSizer('se-resize', i++));
+ }
+
+ var geo = this.graph.model.getGeometry(this.state.cell);
+
+ if (geo != null && !geo.relative && !this.graph.isSwimlane(this.state.cell) &&
+ this.graph.isLabelMovable(this.state.cell))
+ {
+ // Marks this as the label handle for getHandleForEvent
+ this.labelShape = this.createSizer(mxConstants.CURSOR_LABEL_HANDLE,
+ mxEvent.LABEL_HANDLE, mxConstants.LABEL_HANDLE_SIZE,
+ mxConstants.LABEL_HANDLE_FILLCOLOR);
+ this.sizers.push(this.labelShape);
+ }
+ }
+ else if (this.graph.isCellMovable(this.state.cell) && !this.graph.isCellResizable(this.state.cell) &&
+ this.state.width < 2 && this.state.height < 2)
+ {
+ this.labelShape = this.createSizer(mxConstants.CURSOR_MOVABLE_VERTEX,
+ null, null, mxConstants.LABEL_HANDLE_FILLCOLOR);
+ this.sizers.push(this.labelShape);
+ }
+ }
+
+ this.redraw();
+};
+
+/**
+ * Function: getSelectionBounds
+ *
+ * Returns the mxRectangle that defines the bounds of the selection
+ * border.
+ */
+mxVertexHandler.prototype.getSelectionBounds = function(state)
+{
+ return new mxRectangle(state.x, state.y, state.width, state.height);
+};
+
+/**
+ * Function: createSelectionShape
+ *
+ * Creates the shape used to draw the selection border.
+ */
+mxVertexHandler.prototype.createSelectionShape = function(bounds)
+{
+ var shape = new mxRectangleShape(bounds, null, this.getSelectionColor());
+ shape.strokewidth = this.getSelectionStrokeWidth();
+ shape.isDashed = this.isSelectionDashed();
+ shape.crisp = this.crisp;
+
+ return shape;
+};
+
+/**
+ * Function: getSelectionColor
+ *
+ * Returns <mxConstants.VERTEX_SELECTION_COLOR>.
+ */
+mxVertexHandler.prototype.getSelectionColor = function()
+{
+ return mxConstants.VERTEX_SELECTION_COLOR;
+};
+
+/**
+ * Function: getSelectionStrokeWidth
+ *
+ * Returns <mxConstants.VERTEX_SELECTION_STROKEWIDTH>.
+ */
+mxVertexHandler.prototype.getSelectionStrokeWidth = function()
+{
+ return mxConstants.VERTEX_SELECTION_STROKEWIDTH;
+};
+
+/**
+ * Function: isSelectionDashed
+ *
+ * Returns <mxConstants.VERTEX_SELECTION_DASHED>.
+ */
+mxVertexHandler.prototype.isSelectionDashed = function()
+{
+ return mxConstants.VERTEX_SELECTION_DASHED;
+};
+
+/**
+ * Function: createSizer
+ *
+ * Creates a sizer handle for the specified cursor and index and returns
+ * the new <mxRectangleShape> that represents the handle.
+ */
+mxVertexHandler.prototype.createSizer = function(cursor, index, size, fillColor)
+{
+ size = size || mxConstants.HANDLE_SIZE;
+
+ var bounds = new mxRectangle(0, 0, size, size);
+ var sizer = this.createSizerShape(bounds, index, fillColor);
+
+ if (this.state.text != null && this.state.text.node.parentNode == this.graph.container)
+ {
+ sizer.bounds.height -= 1;
+ sizer.bounds.width -= 1;
+ sizer.dialect = mxConstants.DIALECT_STRICTHTML;
+ sizer.init(this.graph.container);
+ }
+ else
+ {
+ sizer.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ sizer.init(this.graph.getView().getOverlayPane());
+ }
+
+ mxEvent.redirectMouseEvents(sizer.node, this.graph, this.state);
+
+ if (this.graph.isEnabled())
+ {
+ sizer.node.style.cursor = cursor;
+ }
+
+ if (!this.isSizerVisible(index))
+ {
+ sizer.node.style.visibility = 'hidden';
+ }
+
+ return sizer;
+};
+
+/**
+ * Function: isSizerVisible
+ *
+ * Returns true if the sizer for the given index is visible.
+ * This returns true for all given indices.
+ */
+mxVertexHandler.prototype.isSizerVisible = function(index)
+{
+ return true;
+};
+
+/**
+ * Function: createSizerShape
+ *
+ * Creates the shape used for the sizer handle for the specified bounds and
+ * index.
+ */
+mxVertexHandler.prototype.createSizerShape = function(bounds, index, fillColor)
+{
+ if (this.handleImage != null)
+ {
+ bounds.width = this.handleImage.width;
+ bounds.height = this.handleImage.height;
+
+ return new mxImageShape(bounds, this.handleImage.src);
+ }
+ else
+ {
+ var shape = new mxRectangleShape(bounds,
+ fillColor || mxConstants.HANDLE_FILLCOLOR,
+ mxConstants.HANDLE_STROKECOLOR);
+ shape.crisp = this.crisp;
+
+ return shape;
+ }
+};
+
+/**
+ * Function: createBounds
+ *
+ * Helper method to create an <mxRectangle> around the given centerpoint
+ * with a width and height of 2*s or 6, if no s is given.
+ */
+mxVertexHandler.prototype.moveSizerTo = function(shape, x, y)
+{
+ if (shape != null)
+ {
+ shape.bounds.x = x - shape.bounds.width / 2;
+ shape.bounds.y = y - shape.bounds.height / 2;
+ shape.redraw();
+ }
+};
+
+/**
+ * Function: getHandleForEvent
+ *
+ * Returns the index of the handle for the given event. This returns the index
+ * of the sizer from where the event originated or <mxEvent.LABEL_INDEX>.
+ */
+mxVertexHandler.prototype.getHandleForEvent = function(me)
+{
+ if (me.isSource(this.labelShape))
+ {
+ return mxEvent.LABEL_HANDLE;
+ }
+
+ if (this.sizers != null)
+ {
+ // Connection highlight may consume events before they reach sizer handle
+ var tol = this.tolerance;
+ var hit = (this.allowHandleBoundsCheck && (mxClient.IS_IE || tol > 0)) ?
+ new mxRectangle(me.getGraphX() - tol, me.getGraphY() - tol, 2 * tol, 2 * tol) : null;
+
+ for (var i = 0; i < this.sizers.length; i++)
+ {
+ if (me.isSource(this.sizers[i]) || (hit != null &&
+ this.sizers[i].node.style.visibility != 'hidden' &&
+ mxUtils.intersects(this.sizers[i].bounds, hit)))
+ {
+ return i;
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event if a handle has been clicked. By consuming the
+ * event all subsequent events of the gesture are redirected to this
+ * handler.
+ */
+mxVertexHandler.prototype.mouseDown = function(sender, me)
+{
+ if (!me.isConsumed() && this.graph.isEnabled() && !this.graph.isForceMarqueeEvent(me.getEvent()) &&
+ (this.tolerance > 0 || me.getState() == this.state))
+ {
+ var handle = this.getHandleForEvent(me);
+
+ if (handle != null)
+ {
+ this.start(me.getX(), me.getY(), handle);
+ me.consume();
+ }
+ }
+};
+
+/**
+ * Function: start
+ *
+ * Starts the handling of the mouse gesture.
+ */
+mxVertexHandler.prototype.start = function(x, y, index)
+{
+ var pt = mxUtils.convertPoint(this.graph.container, x, y);
+ this.startX = pt.x;
+ this.startY = pt.y;
+ this.index = index;
+
+ // Creates a preview that can be on top of any HTML label
+ this.selectionBorder.node.style.visibility = 'hidden';
+ this.preview = this.createSelectionShape(this.bounds);
+
+ if (this.state.text != null && this.state.text.node.parentNode == this.graph.container)
+ {
+ this.preview.dialect = mxConstants.DIALECT_STRICTHTML;
+ this.preview.init(this.graph.container);
+ }
+ else
+ {
+ this.preview.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ this.preview.init(this.graph.view.getOverlayPane());
+ }
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by updating the preview.
+ */
+mxVertexHandler.prototype.mouseMove = function(sender, me)
+{
+ if (!me.isConsumed() && this.index != null)
+ {
+ var point = new mxPoint(me.getGraphX(), me.getGraphY());
+ var gridEnabled = this.graph.isGridEnabledEvent(me.getEvent());
+ var scale = this.graph.getView().scale;
+
+ if (this.index == mxEvent.LABEL_HANDLE)
+ {
+ if (gridEnabled)
+ {
+ point.x = this.graph.snap(point.x / scale) * scale;
+ point.y = this.graph.snap(point.y / scale) * scale;
+ }
+
+ this.moveSizerTo(this.sizers[this.sizers.length - 1], point.x, point.y);
+ me.consume();
+ }
+ else if (this.index != null)
+ {
+ var dx = point.x - this.startX;
+ var dy = point.y - this.startY;
+ var tr = this.graph.view.translate;
+ this.bounds = this.union(this.selectionBounds, dx, dy, this.index, gridEnabled, scale, tr);
+ this.drawPreview();
+ me.consume();
+ }
+ }
+ // Workaround for disabling the connect highlight when over handle
+ else if (this.getHandleForEvent(me) != null)
+ {
+ me.consume(false);
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by applying the changes to the geometry.
+ */
+mxVertexHandler.prototype.mouseUp = function(sender, me)
+{
+ if (!me.isConsumed() && this.index != null && this.state != null)
+ {
+ var point = new mxPoint(me.getGraphX(), me.getGraphY());
+ var scale = this.graph.getView().scale;
+
+ var gridEnabled = this.graph.isGridEnabledEvent(me.getEvent());
+ var dx = (point.x - this.startX) / scale;
+ var dy = (point.y - this.startY) / scale;
+
+ this.resizeCell(this.state.cell, dx, dy, this.index, gridEnabled);
+ this.reset();
+ me.consume();
+ }
+};
+
+/**
+ * Function: reset
+ *
+ * Resets the state of this handler.
+ */
+mxVertexHandler.prototype.reset = function()
+{
+ this.index = null;
+
+ if (this.preview != null)
+ {
+ this.preview.destroy();
+ this.preview = null;
+ }
+
+ // Checks if handler has been destroyed
+ if (this.selectionBorder != null)
+ {
+ this.selectionBounds = this.getSelectionBounds(this.state);
+ this.selectionBorder.node.style.visibility = 'visible';
+ this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y,
+ this.selectionBounds.width, this.selectionBounds.height);
+ this.drawPreview();
+ }
+};
+
+/**
+ * Function: resizeCell
+ *
+ * Uses the given vector to change the bounds of the given cell
+ * in the graph using <mxGraph.resizeCell>.
+ */
+mxVertexHandler.prototype.resizeCell = function(cell, dx, dy, index, gridEnabled)
+{
+ var geo = this.graph.model.getGeometry(cell);
+
+ if (index == mxEvent.LABEL_HANDLE)
+ {
+ var scale = this.graph.view.scale;
+ dx = (this.labelShape.bounds.getCenterX() - this.startX) / scale;
+ dy = (this.labelShape.bounds.getCenterY() - this.startY) / scale;
+
+ geo = geo.clone();
+
+ if (geo.offset == null)
+ {
+ geo.offset = new mxPoint(dx, dy);
+ }
+ else
+ {
+ geo.offset.x += dx;
+ geo.offset.y += dy;
+ }
+
+ this.graph.model.setGeometry(cell, geo);
+ }
+ else
+ {
+ var bounds = this.union(geo, dx, dy, index, gridEnabled, 1, new mxPoint(0, 0));
+ this.graph.resizeCell(cell, bounds);
+ }
+};
+
+/**
+ * Function: union
+ *
+ * Returns the union of the given bounds and location for the specified
+ * handle index.
+ *
+ * To override this to limit the size of vertex via a minWidth/-Height style,
+ * the following code can be used.
+ *
+ * (code)
+ * var vertexHandlerUnion = mxVertexHandler.prototype.union;
+ * mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr)
+ * {
+ * var result = vertexHandlerUnion.apply(this, arguments);
+ *
+ * result.width = Math.max(result.width, mxUtils.getNumber(this.state.style, 'minWidth', 0));
+ * result.height = Math.max(result.height, mxUtils.getNumber(this.state.style, 'minHeight', 0));
+ *
+ * return result;
+ * };
+ * (end)
+ *
+ * The minWidth/-Height style can then be used as follows:
+ *
+ * (code)
+ * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'minWidth=100;minHeight=100;');
+ * (end)
+ */
+mxVertexHandler.prototype.union = function(bounds, dx, dy, index, gridEnabled, scale, tr)
+{
+ if (this.singleSizer)
+ {
+ var x = bounds.x + bounds.width + dx;
+ var y = bounds.y + bounds.height + dy;
+
+ if (gridEnabled)
+ {
+ x = this.graph.snap(x / scale) * scale;
+ y = this.graph.snap(y / scale) * scale;
+ }
+
+ var rect = new mxRectangle(bounds.x, bounds.y, 0, 0);
+ rect.add(new mxRectangle(x, y, 0, 0));
+
+ return rect;
+ }
+ else
+ {
+ var left = bounds.x - tr.x * scale;
+ var right = left + bounds.width;
+ var top = bounds.y - tr.y * scale;
+ var bottom = top + bounds.height;
+
+ if (index > 4 /* Bottom Row */)
+ {
+ bottom = bottom + dy;
+
+ if (gridEnabled)
+ {
+ bottom = this.graph.snap(bottom / scale) * scale;
+ }
+ }
+ else if (index < 3 /* Top Row */)
+ {
+ top = top + dy;
+
+ if (gridEnabled)
+ {
+ top = this.graph.snap(top / scale) * scale;
+ }
+ }
+
+ if (index == 0 || index == 3 || index == 5 /* Left */)
+ {
+ left += dx;
+
+ if (gridEnabled)
+ {
+ left = this.graph.snap(left / scale) * scale;
+ }
+ }
+ else if (index == 2 || index == 4 || index == 7 /* Right */)
+ {
+ right += dx;
+
+ if (gridEnabled)
+ {
+ right = this.graph.snap(right / scale) * scale;
+ }
+ }
+
+ var width = right - left;
+ var height = bottom - top;
+
+ // Flips over left side
+ if (width < 0)
+ {
+ left += width;
+ width = Math.abs(width);
+ }
+
+ // Flips over top side
+ if (height < 0)
+ {
+ top += height;
+ height = Math.abs(height);
+ }
+
+ return new mxRectangle(left + tr.x * scale, top + tr.y * scale, width, height);
+ }
+};
+
+/**
+ * Function: redraw
+ *
+ * Redraws the handles and the preview.
+ */
+mxVertexHandler.prototype.redraw = function()
+{
+ this.selectionBounds = this.getSelectionBounds(this.state);
+ this.bounds = new mxRectangle(this.selectionBounds.x, this.selectionBounds.y,
+ this.selectionBounds.width, this.selectionBounds.height);
+
+ if (this.sizers != null)
+ {
+ var s = this.state;
+ var r = s.x + s.width;
+ var b = s.y + s.height;
+
+ if (this.singleSizer)
+ {
+ this.moveSizerTo(this.sizers[0], r, b);
+ }
+ else
+ {
+ var cx = s.x + s.width / 2;
+ var cy = s.y + s.height / 2;
+
+ if (this.sizers.length > 1)
+ {
+ this.moveSizerTo(this.sizers[0], s.x, s.y);
+ this.moveSizerTo(this.sizers[1], cx, s.y);
+ this.moveSizerTo(this.sizers[2], r, s.y);
+ this.moveSizerTo(this.sizers[3], s.x, cy);
+ this.moveSizerTo(this.sizers[4], r, cy);
+ this.moveSizerTo(this.sizers[5], s.x, b);
+ this.moveSizerTo(this.sizers[6], cx, b);
+ this.moveSizerTo(this.sizers[7], r, b);
+ this.moveSizerTo(this.sizers[8],
+ cx + s.absoluteOffset.x,
+ cy + s.absoluteOffset.y);
+ }
+ else if (this.state.width >= 2 && this.state.height >= 2)
+ {
+ this.moveSizerTo(this.sizers[0],
+ cx + s.absoluteOffset.x,
+ cy + s.absoluteOffset.y);
+ }
+ else
+ {
+ this.moveSizerTo(this.sizers[0], s.x, s.y);
+ }
+ }
+ }
+
+ this.drawPreview();
+};
+
+/**
+ * Function: drawPreview
+ *
+ * Redraws the preview.
+ */
+mxVertexHandler.prototype.drawPreview = function()
+{
+ if (this.preview != null)
+ {
+ this.preview.bounds = this.bounds;
+
+ if (this.preview.node.parentNode == this.graph.container)
+ {
+ this.preview.bounds.width = Math.max(0, this.preview.bounds.width - 1);
+ this.preview.bounds.height = Math.max(0, this.preview.bounds.height - 1);
+ }
+
+ this.preview.redraw();
+ }
+
+ this.selectionBorder.bounds = this.bounds;
+ this.selectionBorder.redraw();
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxVertexHandler.prototype.destroy = function()
+{
+ if (this.preview != null)
+ {
+ this.preview.destroy();
+ this.preview = null;
+ }
+
+ this.selectionBorder.destroy();
+ this.selectionBorder = null;
+ this.labelShape = null;
+
+ if (this.sizers != null)
+ {
+ for (var i = 0; i < this.sizers.length; i++)
+ {
+ this.sizers[i].destroy();
+ this.sizers[i] = null;
+ }
+ }
+};
diff --git a/src/js/index.txt b/src/js/index.txt
new file mode 100644
index 0000000..f3631d6
--- /dev/null
+++ b/src/js/index.txt
@@ -0,0 +1,316 @@
+Document: API Specification
+
+Overview:
+
+ This JavaScript library is divided into 8 packages. The top-level <mxClient>
+ class includes (or dynamically imports) everything else. The current version
+ is stored in <mxClient.VERSION>.
+
+ The *editor* package provides the classes required to implement a diagram
+ editor. The main class in this package is <mxEditor>.
+
+ The *view* and *model* packages implement the graph component, represented
+ by <mxGraph>. It refers to a <mxGraphModel> which contains <mxCell>s and
+ caches the state of the cells in a <mxGraphView>. The cells are painted
+ using a <mxCellRenderer> based on the appearance defined in <mxStylesheet>.
+ Undo history is implemented in <mxUndoManager>. To display an icon on the
+ graph, <mxCellOverlay> may be used. Validation rules are defined with
+ <mxMultiplicity>.
+
+ The *handler*, *layout* and *shape* packages contain event listeners,
+ layout algorithms and shapes, respectively. The graph event listeners
+ include <mxRubberband> for rubberband selection, <mxTooltipHandler>
+ for tooltips and <mxGraphHandler> for basic cell modifications.
+ <mxCompactTreeLayout> implements a tree layout algorithm, and the
+ shape package provides various shapes, which are subclasses of
+ <mxShape>.
+
+ The *util* package provides utility classes including <mxClipboard> for
+ copy-paste, <mxDatatransfer> for drag-and-drop, <mxConstants> for keys and
+ values of stylesheets, <mxEvent> and <mxUtils> for cross-browser
+ event-handling and general purpose functions, <mxResources> for
+ internationalization and <mxLog> for console output.
+
+ The *io* package implements a generic <mxObjectCodec> for turning
+ JavaScript objects into XML. The main class is <mxCodec>.
+ <mxCodecRegistry> is the global registry for custom codecs.
+
+Events:
+
+ There are three different types of events, namely native DOM events,
+ <mxEventObjects> which are fired in an <mxEventSource>, and <mxMouseEvents>
+ which are fired in <mxGraph>.
+
+ Some helper methods for handling native events are provided in <mxEvent>. It
+ also takes care of resolving cycles between DOM nodes and JavaScript event
+ handlers, which can lead to memory leaks in IE6.
+
+ Most custom events in mxGraph are implemented using <mxEventSource>. Its
+ listeners are functions that take a sender and <mxEventObject>. Additionally,
+ the <mxGraph> class fires special <mxMouseEvents> which are handled using
+ mouse listeners, which are objects that provide a mousedown, mousemove and
+ mouseup method.
+
+ Events in <mxEventSource> are fired using <mxEventSource.fireEvent>.
+ Listeners are added and removed using <mxEventSource.addListener> and
+ <mxEventSource.removeListener>. <mxMouseEvents> in <mxGraph> are fired using
+ <mxGraph.fireMouseEvent>. Listeners are added and removed using
+ <mxGraph.addMouseListener> and <mxGraph.removeMouseListener>, respectively.
+
+Key bindings:
+
+ The following key bindings are defined for mouse events in the client across
+ all browsers and platforms:
+
+ - Control-Drag: Duplicates (clones) selected cells
+ - Shift-Rightlick: Shows the context menu
+ - Alt-Click: Forces rubberband (aka. marquee)
+ - Control-Select: Toggles the selection state
+ - Shift-Drag: Constrains the offset to one direction
+ - Shift-Control-Drag: Panning (also Shift-Rightdrag)
+
+Configuration:
+
+ The following global variables may be defined before the client is loaded to
+ specify its language or base path, respectively.
+
+ - mxBasePath: Specifies the path in <mxClient.basePath>.
+ - mxImageBasePath: Specifies the path in <mxClient.imageBasePath>.
+ - mxLanguage: Specifies the language for resources in <mxClient.language>.
+ - mxDefaultLanguage: Specifies the default language in <mxClient.defaultLanguage>.
+ - mxLoadResources: Specifies if any resources should be loaded. Default is true.
+ - mxLoadStylesheets: Specifies if any stylesheets should be loaded. Default is true.
+
+Reserved Words:
+
+ The mx prefix is used for all classes and objects in mxGraph. The mx prefix
+ can be seen as the global namespace for all JavaScript code in mxGraph. The
+ following fieldnames should not be used in objects.
+
+ - *mxObjectId*: If the object is used with mxObjectIdentity
+ - *as*: If the object is a field of another object
+ - *id*: If the object is an idref in a codec
+ - *mxListenerList*: Added to DOM nodes when used with <mxEvent>
+ - *window._mxDynamicCode*: Temporarily used to load code in Safari and Chrome
+ (see <mxClient.include>).
+ - *_mxJavaScriptExpression*: Global variable that is temporarily used to
+ evaluate code in Safari, Opera, Firefox 3 and IE (see <mxUtils.eval>).
+
+Files:
+
+ The library contains these relative filenames. All filenames are relative
+ to <mxClient.basePath>.
+
+Built-in Images:
+
+ All images are loaded from the <mxClient.imageBasePath>,
+ which you can change to reflect your environment. The image variables can
+ also be changed individually.
+
+ - mxGraph.prototype.collapsedImage
+ - mxGraph.prototype.expandedImage
+ - mxGraph.prototype.warningImage
+ - mxWindow.prototype.closeImage
+ - mxWindow.prototype.minimizeImage
+ - mxWindow.prototype.normalizeImage
+ - mxWindow.prototype.maximizeImage
+ - mxWindow.prototype.resizeImage
+ - mxPopupMenu.prototype.submenuImage
+ - mxUtils.errorImage
+ - mxConstraintHandler.prototype.pointImage
+
+ The basename of the warning image (images/warning without extension) used in
+ <mxGraph.setCellWarning> is defined in <mxGraph.warningImage>.
+
+Resources:
+
+ The <mxEditor> and <mxGraph> classes add the following resources to
+ <mxResources> at class loading time:
+
+ - resources/editor*.properties
+ - resources/graph*.properties
+
+ By default, the library ships with English and German resource files.
+
+Images:
+
+ Recommendations for using images. Use GIF images (256 color palette) in HTML
+ elements (such as the toolbar and context menu), and PNG images (24 bit) for
+ all images which appear inside the graph component.
+
+ - For PNG images inside HTML elements, Internet Explorer will ignore any
+ transparency information.
+ - For GIF images inside the graph, Firefox on the Mac will display strange
+ colors. Furthermore, only the first image for animated GIFs is displayed
+ on the Mac.
+
+ For faster image rendering during application runtime, images can be
+ prefetched using the following code:
+
+ (code)
+ var image = new Image();
+ image.src = url_to_image;
+ (end)
+
+Deployment:
+
+ The client is added to the page using the following script tag inside the
+ head of a document:
+
+ (code)
+ <script type="text/javascript" src="js/mxClient.js"></script>
+ (end)
+
+ The deployment version of the mxClient.js file contains all required code
+ in a single file. For deployment, the complete javascript/src directory is
+ required.
+
+Source Code:
+
+ If you are a source code customer and you wish to develop using the
+ full source code, the commented source code is shipped in the
+ javascript/devel/source.zip file. It contains one file for each class
+ in mxGraph. To use the source code the source.zip file must be
+ uncompressed and the mxClient.js URL in the HTML page must be changed
+ to reference the uncompressed mxClient.js from the source.zip file.
+
+Compression:
+
+ When using Apache2 with mod_deflate, you can use the following directive
+ in src/js/.htaccess to speedup the loading of the JavaScript sources:
+
+ (code)
+ SetOutputFilter DEFLATE
+ (end)
+
+Classes:
+
+ There are two types of "classes" in mxGraph: classes and singletons (where
+ only one instance exists). Singletons are mapped to global objects where the
+ variable name equals the classname. For example mxConstants is an object with
+ all the constants defined as object fields. Normal classes are mapped to a
+ constructor function and a prototype which defines the instance fields and
+ methods. For example, <mxEditor> is a function and mxEditor.prototype is the
+ prototype for the object that the mxEditor function creates. The mx prefix is
+ a convention that is used for all classes in the mxGraph package to avoid
+ conflicts with other objects in the global namespace.
+
+Subclassing:
+
+ For subclassing, the superclass must provide a constructor that is either
+ parameterless or handles an invocation with no arguments. Furthermore, the
+ special constructor field must be redefined after extending the prototype.
+ For example, the superclass of mxEditor is <mxEventSource>. This is
+ represented in JavaScript by first "inheriting" all fields and methods from
+ the superclass by assigning the prototype to an instance of the superclass,
+ eg. mxEditor.prototype = new mxEventSource() and redefining the constructor
+ field using mxEditor.prototype.constructor = mxEditor. The latter rule is
+ applied so that the type of an object can be retrieved via the name of it’s
+ constructor using mxUtils.getFunctionName(obj.constructor).
+
+Constructor:
+
+ For subclassing in mxGraph, the same scheme should be applied. For example,
+ for subclassing the <mxGraph> class, first a constructor must be defined for
+ the new class. The constructor calls the super constructor with any arguments
+ that it may have using the call function on the mxGraph function object,
+ passing along explitely each argument:
+
+ (code)
+ function MyGraph(container)
+ {
+ mxGraph.call(this, container);
+ }
+ (end)
+
+ The prototype of MyGraph inherits from mxGraph as follows. As usual, the
+ constructor is redefined after extending the superclass:
+
+ (code)
+ MyGraph.prototype = new mxGraph();
+ MyGraph.prototype.constructor = MyGraph;
+ (end)
+
+ You may want to define the codec associated for the class after the above
+ code. This code will be executed at class loading time and makes sure the
+ same codec is used to encode instances of mxGraph and MyGraph.
+
+ (code)
+ var codec = mxCodecRegistry.getCodec(mxGraph);
+ codec.template = new MyGraph();
+ mxCodecRegistry.register(codec);
+ (end)
+
+Functions:
+
+ In the prototype for MyGraph, functions of mxGraph can then be extended as
+ follows.
+
+ (code)
+ MyGraph.prototype.isCellSelectable = function(cell)
+ {
+ var selectable = mxGraph.prototype.isSelectable.apply(this, arguments);
+
+ var geo = this.model.getGeometry(cell);
+ return selectable && (geo == null || !geo.relative);
+ }
+ (end)
+
+ The supercall in the first line is optional. It is done using the apply
+ function on the isSelectable function object of the mxGraph prototype, using
+ the special this and arguments variables as parameters. Calls to the
+ superclass function are only possible if the function is not replaced in the
+ superclass as follows, which is another way of “subclassing” in JavaScript.
+
+ (code)
+ mxGraph.prototype.isCellSelectable = function(cell)
+ {
+ var geo = this.model.getGeometry(cell);
+ return selectable &&
+ (geo == null ||
+ !geo.relative);
+ }
+ (end)
+
+ The above scheme is useful if a function definition needs to be replaced
+ completely.
+
+ In order to add new functions and fields to the subclass, the following code
+ is used. The example below adds a new function to return the XML
+ representation of the graph model:
+
+ (code)
+ MyGraph.prototype.getXml = function()
+ {
+ var enc = new mxCodec();
+ return enc.encode(this.getModel());
+ }
+ (end)
+
+Variables:
+
+ Likewise, a new field is declared and defined as follows.
+
+ (code)
+ MyGraph.prototype.myField = 'Hello, World!';
+ (end)
+
+ Note that the value assigned to myField is created only once, that is, all
+ instances of MyGraph share the same value. If you require instance-specific
+ values, then the field must be defined in the constructor instead.
+
+ (code)
+ function MyGraph(container)
+ {
+ mxGraph.call(this, container);
+
+ this.myField = new Array();
+ }
+ (end)
+
+ Finally, a new instance of MyGraph is created using the following code, where
+ container is a DOM node that acts as a container for the graph view:
+
+ (code)
+ var graph = new MyGraph(container);
+ (end)
diff --git a/src/js/io/mxCellCodec.js b/src/js/io/mxCellCodec.js
new file mode 100644
index 0000000..cbcd651
--- /dev/null
+++ b/src/js/io/mxCellCodec.js
@@ -0,0 +1,170 @@
+/**
+ * $Id: mxCellCodec.js,v 1.22 2010-10-21 07:12:31 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxCellCodec
+ *
+ * Codec for <mxCell>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec>
+ * and the <mxCodecRegistry>.
+ *
+ * Transient Fields:
+ *
+ * - children
+ * - edges
+ * - overlays
+ * - mxTransient
+ *
+ * Reference Fields:
+ *
+ * - parent
+ * - source
+ * - target
+ *
+ * Transient fields can be added using the following code:
+ *
+ * mxCodecRegistry.getCodec(mxCell).exclude.push('name_of_field');
+ */
+ var codec = new mxObjectCodec(new mxCell(),
+ ['children', 'edges', 'overlays', 'mxTransient'],
+ ['parent', 'source', 'target']);
+
+ /**
+ * Function: isCellCodec
+ *
+ * Returns true since this is a cell codec.
+ */
+ codec.isCellCodec = function()
+ {
+ return true;
+ };
+
+ /**
+ * Function: isExcluded
+ *
+ * Excludes user objects that are XML nodes.
+ */
+ codec.isExcluded = function(obj, attr, value, isWrite)
+ {
+ return mxObjectCodec.prototype.isExcluded.apply(this, arguments) ||
+ (isWrite && attr == 'value' &&
+ value.nodeType == mxConstants.NODETYPE_ELEMENT);
+ };
+
+ /**
+ * Function: afterEncode
+ *
+ * Encodes an <mxCell> and wraps the XML up inside the
+ * XML of the user object (inversion).
+ */
+ codec.afterEncode = function(enc, obj, node)
+ {
+ if (obj.value != null &&
+ obj.value.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ // Wraps the graphical annotation up in the user object (inversion)
+ // by putting the result of the default encoding into a clone of the
+ // user object (node type 1) and returning this cloned user object.
+ var tmp = node;
+ node = (mxClient.IS_IE) ?
+ obj.value.cloneNode(true) :
+ enc.document.importNode(obj.value, true);
+ node.appendChild(tmp);
+
+ // Moves the id attribute to the outermost XML node, namely the
+ // node which denotes the object boundaries in the file.
+ var id = tmp.getAttribute('id');
+ node.setAttribute('id', id);
+ tmp.removeAttribute('id');
+ }
+
+ return node;
+ };
+
+ /**
+ * Function: beforeDecode
+ *
+ * Decodes an <mxCell> and uses the enclosing XML node as
+ * the user object for the cell (inversion).
+ */
+ codec.beforeDecode = function(dec, node, obj)
+ {
+ var inner = node;
+ var classname = this.getName();
+
+ if (node.nodeName != classname)
+ {
+ // Passes the inner graphical annotation node to the
+ // object codec for further processing of the cell.
+ var tmp = node.getElementsByTagName(classname)[0];
+
+ if (tmp != null &&
+ tmp.parentNode == node)
+ {
+ mxUtils.removeWhitespace(tmp, true);
+ mxUtils.removeWhitespace(tmp, false);
+ tmp.parentNode.removeChild(tmp);
+ inner = tmp;
+ }
+ else
+ {
+ inner = null;
+ }
+
+ // Creates the user object out of the XML node
+ obj.value = node.cloneNode(true);
+ var id = obj.value.getAttribute('id');
+
+ if (id != null)
+ {
+ obj.setId(id);
+ obj.value.removeAttribute('id');
+ }
+ }
+ else
+ {
+ // Uses ID from XML file as ID for cell in model
+ obj.setId(node.getAttribute('id'));
+ }
+
+ // Preprocesses and removes all Id-references in order to use the
+ // correct encoder (this) for the known references to cells (all).
+ if (inner != null)
+ {
+ for (var i = 0; i < this.idrefs.length; i++)
+ {
+ var attr = this.idrefs[i];
+ var ref = inner.getAttribute(attr);
+
+ if (ref != null)
+ {
+ inner.removeAttribute(attr);
+ var object = dec.objects[ref] || dec.lookup(ref);
+
+ if (object == null)
+ {
+ // Needs to decode forward reference
+ var element = dec.getElementById(ref);
+
+ if (element != null)
+ {
+ var decoder = mxCodecRegistry.codecs[element.nodeName] || this;
+ object = decoder.decode(dec, element);
+ }
+ }
+
+ obj[attr] = object;
+ }
+ }
+ }
+
+ return inner;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxChildChangeCodec.js b/src/js/io/mxChildChangeCodec.js
new file mode 100644
index 0000000..deeb57b
--- /dev/null
+++ b/src/js/io/mxChildChangeCodec.js
@@ -0,0 +1,149 @@
+/**
+ * $Id: mxChildChangeCodec.js,v 1.12 2010-09-15 14:38:52 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxChildChangeCodec
+ *
+ * Codec for <mxChildChange>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec> and
+ * the <mxCodecRegistry>.
+ *
+ * Transient Fields:
+ *
+ * - model
+ * - previous
+ * - previousIndex
+ * - child
+ *
+ * Reference Fields:
+ *
+ * - parent
+ */
+ var codec = new mxObjectCodec(new mxChildChange(),
+ ['model', 'child', 'previousIndex'],
+ ['parent', 'previous']);
+
+ /**
+ * Function: isReference
+ *
+ * Returns true for the child attribute if the child
+ * cell had a previous parent or if we're reading the
+ * child as an attribute rather than a child node, in
+ * which case it's always a reference.
+ */
+ codec.isReference = function(obj, attr, value, isWrite)
+ {
+ if (attr == 'child' &&
+ (obj.previous != null ||
+ !isWrite))
+ {
+ return true;
+ }
+
+ return mxUtils.indexOf(this.idrefs, attr) >= 0;
+ };
+
+ /**
+ * Function: afterEncode
+ *
+ * Encodes the child recusively and adds the result
+ * to the given node.
+ */
+ codec.afterEncode = function(enc, obj, node)
+ {
+ if (this.isReference(obj, 'child', obj.child, true))
+ {
+ // Encodes as reference (id)
+ node.setAttribute('child', enc.getId(obj.child));
+ }
+ else
+ {
+ // At this point, the encoder is no longer able to know which cells
+ // are new, so we have to encode the complete cell hierarchy and
+ // ignore the ones that are already there at decoding time. Note:
+ // This can only be resolved by moving the notify event into the
+ // execute of the edit.
+ enc.encodeCell(obj.child, node);
+ }
+
+ return node;
+ };
+
+ /**
+ * Function: beforeDecode
+ *
+ * Decodes the any child nodes as using the respective
+ * codec from the registry.
+ */
+ codec.beforeDecode = function(dec, node, obj)
+ {
+ if (node.firstChild != null &&
+ node.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ // Makes sure the original node isn't modified
+ node = node.cloneNode(true);
+
+ var tmp = node.firstChild;
+ obj.child = dec.decodeCell(tmp, false);
+
+ var tmp2 = tmp.nextSibling;
+ tmp.parentNode.removeChild(tmp);
+ tmp = tmp2;
+
+ while (tmp != null)
+ {
+ tmp2 = tmp.nextSibling;
+
+ if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ // Ignores all existing cells because those do not need to
+ // be re-inserted into the model. Since the encoded version
+ // of these cells contains the new parent, this would leave
+ // to an inconsistent state on the model (ie. a parent
+ // change without a call to parentForCellChanged).
+ var id = tmp.getAttribute('id');
+
+ if (dec.lookup(id) == null)
+ {
+ dec.decodeCell(tmp);
+ }
+ }
+
+ tmp.parentNode.removeChild(tmp);
+ tmp = tmp2;
+ }
+ }
+ else
+ {
+ var childRef = node.getAttribute('child');
+ obj.child = dec.getObject(childRef);
+ }
+
+ return node;
+ };
+
+ /**
+ * Function: afterDecode
+ *
+ * Restores object state in the child change.
+ */
+ codec.afterDecode = function(dec, node, obj)
+ {
+ // Cells are encoded here after a complete transaction so the previous
+ // parent must be restored on the cell for the case where the cell was
+ // added. This is needed for the local model to identify the cell as a
+ // new cell and register the ID.
+ obj.child.parent = obj.previous;
+ obj.previous = obj.parent;
+ obj.previousIndex = obj.index;
+
+ return obj;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxCodec.js b/src/js/io/mxCodec.js
new file mode 100644
index 0000000..b8bfc6a
--- /dev/null
+++ b/src/js/io/mxCodec.js
@@ -0,0 +1,531 @@
+/**
+ * $Id: mxCodec.js,v 1.48 2012-01-04 10:01:16 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCodec
+ *
+ * XML codec for JavaScript object graphs. See <mxObjectCodec> for a
+ * description of the general encoding/decoding scheme. This class uses the
+ * codecs registered in <mxCodecRegistry> for encoding/decoding each object.
+ *
+ * References:
+ *
+ * In order to resolve references, especially forward references, the mxCodec
+ * constructor must be given the document that contains the referenced
+ * elements.
+ *
+ * Examples:
+ *
+ * The following code is used to encode a graph model.
+ *
+ * (code)
+ * var encoder = new mxCodec();
+ * var result = encoder.encode(graph.getModel());
+ * var xml = mxUtils.getXml(result);
+ * (end)
+ *
+ * Example:
+ *
+ * Using the following code, the selection cells of a graph are encoded and the
+ * output is displayed in a dialog box.
+ *
+ * (code)
+ * var enc = new mxCodec();
+ * var cells = graph.getSelectionCells();
+ * mxUtils.alert(mxUtils.getPrettyXml(enc.encode(cells)));
+ * (end)
+ *
+ * Newlines in the XML can be coverted to <br>, in which case a '<br>' argument
+ * must be passed to <mxUtils.getXml> as the second argument.
+ *
+ * Example:
+ *
+ * Using the code below, an XML document is decodec into an existing model. The
+ * document may be obtained using one of the functions in mxUtils for loading
+ * an XML file, eg. <mxUtils.get>, or using <mxUtils.parseXml> for parsing an
+ * XML string.
+ *
+ * (code)
+ * var decoder = new mxCodec(doc)
+ * decoder.decode(doc.documentElement, graph.getModel());
+ * (end)
+ *
+ * Debugging:
+ *
+ * For debugging i/o you can use the following code to get the sequence of
+ * encoded objects:
+ *
+ * (code)
+ * var oldEncode = mxCodec.prototype.encode;
+ * mxCodec.prototype.encode = function(obj)
+ * {
+ * mxLog.show();
+ * mxLog.debug('mxCodec.encode: obj='+mxUtils.getFunctionName(obj.constructor));
+ *
+ * return oldEncode.apply(this, arguments);
+ * };
+ * (end)
+ *
+ * Constructor: mxCodec
+ *
+ * Constructs an XML encoder/decoder for the specified
+ * owner document.
+ *
+ * Parameters:
+ *
+ * document - Optional XML document that contains the data.
+ * If no document is specified then a new document is created
+ * using <mxUtils.createXmlDocument>.
+ */
+function mxCodec(document)
+{
+ this.document = document || mxUtils.createXmlDocument();
+ this.objects = [];
+};
+
+/**
+ * Variable: document
+ *
+ * The owner document of the codec.
+ */
+mxCodec.prototype.document = null;
+
+/**
+ * Variable: objects
+ *
+ * Maps from IDs to objects.
+ */
+mxCodec.prototype.objects = null;
+
+/**
+ * Variable: encodeDefaults
+ *
+ * Specifies if default values should be encoded. Default is false.
+ */
+mxCodec.prototype.encodeDefaults = false;
+
+
+/**
+ * Function: putObject
+ *
+ * Assoiates the given object with the given ID and returns the given object.
+ *
+ * Parameters
+ *
+ * id - ID for the object to be associated with.
+ * obj - Object to be associated with the ID.
+ */
+mxCodec.prototype.putObject = function(id, obj)
+{
+ this.objects[id] = obj;
+
+ return obj;
+};
+
+/**
+ * Function: getObject
+ *
+ * Returns the decoded object for the element with the specified ID in
+ * <document>. If the object is not known then <lookup> is used to find an
+ * object. If no object is found, then the element with the respective ID
+ * from the document is parsed using <decode>.
+ */
+mxCodec.prototype.getObject = function(id)
+{
+ var obj = null;
+
+ if (id != null)
+ {
+ obj = this.objects[id];
+
+ if (obj == null)
+ {
+ obj = this.lookup(id);
+
+ if (obj == null)
+ {
+ var node = this.getElementById(id);
+
+ if (node != null)
+ {
+ obj = this.decode(node);
+ }
+ }
+ }
+ }
+
+ return obj;
+};
+
+/**
+ * Function: lookup
+ *
+ * Hook for subclassers to implement a custom lookup mechanism for cell IDs.
+ * This implementation always returns null.
+ *
+ * Example:
+ *
+ * (code)
+ * var codec = new mxCodec();
+ * codec.lookup = function(id)
+ * {
+ * return model.getCell(id);
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * id - ID of the object to be returned.
+ */
+mxCodec.prototype.lookup = function(id)
+{
+ return null;
+};
+
+/**
+ * Function: getElementById
+ *
+ * Returns the element with the given ID from <document>. The optional attr
+ * argument specifies the name of the ID attribute. Default is "id". The
+ * XPath expression used to find the element is //*[@attr='arg'] where attr is
+ * the name of the ID attribute and arg is the given id.
+ *
+ * Parameters:
+ *
+ * id - String that contains the ID.
+ * attr - Optional string for the attributename. Default is "id".
+ */
+mxCodec.prototype.getElementById = function(id, attr)
+{
+ attr = (attr != null) ? attr : 'id';
+
+ return mxUtils.findNodeByAttribute(this.document.documentElement, attr, id);
+};
+
+/**
+ * Function: getId
+ *
+ * Returns the ID of the specified object. This implementation
+ * calls <reference> first and if that returns null handles
+ * the object as an <mxCell> by returning their IDs using
+ * <mxCell.getId>. If no ID exists for the given cell, then
+ * an on-the-fly ID is generated using <mxCellPath.create>.
+ *
+ * Parameters:
+ *
+ * obj - Object to return the ID for.
+ */
+mxCodec.prototype.getId = function(obj)
+{
+ var id = null;
+
+ if (obj != null)
+ {
+ id = this.reference(obj);
+
+ if (id == null && obj instanceof mxCell)
+ {
+ id = obj.getId();
+
+ if (id == null)
+ {
+ // Uses an on-the-fly Id
+ id = mxCellPath.create(obj);
+
+ if (id.length == 0)
+ {
+ id = 'root';
+ }
+ }
+ }
+ }
+
+ return id;
+};
+
+/**
+ * Function: reference
+ *
+ * Hook for subclassers to implement a custom method
+ * for retrieving IDs from objects. This implementation
+ * always returns null.
+ *
+ * Example:
+ *
+ * (code)
+ * var codec = new mxCodec();
+ * codec.reference = function(obj)
+ * {
+ * return obj.getCustomId();
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * obj - Object whose ID should be returned.
+ */
+mxCodec.prototype.reference = function(obj)
+{
+ return null;
+};
+
+/**
+ * Function: encode
+ *
+ * Encodes the specified object and returns the resulting
+ * XML node.
+ *
+ * Parameters:
+ *
+ * obj - Object to be encoded.
+ */
+mxCodec.prototype.encode = function(obj)
+{
+ var node = null;
+
+ if (obj != null && obj.constructor != null)
+ {
+ var enc = mxCodecRegistry.getCodec(obj.constructor);
+
+ if (enc != null)
+ {
+ node = enc.encode(this, obj);
+ }
+ else
+ {
+ if (mxUtils.isNode(obj))
+ {
+ node = (mxClient.IS_IE) ? obj.cloneNode(true) :
+ this.document.importNode(obj, true);
+ }
+ else
+ {
+ mxLog.warn('mxCodec.encode: No codec for '+
+ mxUtils.getFunctionName(obj.constructor));
+ }
+ }
+ }
+
+ return node;
+};
+
+/**
+ * Function: decode
+ *
+ * Decodes the given XML node. The optional "into"
+ * argument specifies an existing object to be
+ * used. If no object is given, then a new instance
+ * is created using the constructor from the codec.
+ *
+ * The function returns the passed in object or
+ * the new instance if no object was given.
+ *
+ * Parameters:
+ *
+ * node - XML node to be decoded.
+ * into - Optional object to be decodec into.
+ */
+mxCodec.prototype.decode = function(node, into)
+{
+ var obj = null;
+
+ if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ var ctor = null;
+
+ try
+ {
+ ctor = eval(node.nodeName);
+ }
+ catch (err)
+ {
+ // ignore
+ }
+
+ try
+ {
+ var dec = mxCodecRegistry.getCodec(ctor);
+
+ if (dec != null)
+ {
+ obj = dec.decode(this, node, into);
+ }
+ else
+ {
+ obj = node.cloneNode(true);
+ obj.removeAttribute('as');
+ }
+ }
+ catch (err)
+ {
+ mxLog.debug('Cannot decode '+node.nodeName+': '+err.message);
+ }
+ }
+
+ return obj;
+};
+
+/**
+ * Function: encodeCell
+ *
+ * Encoding of cell hierarchies is built-into the core, but
+ * is a higher-level function that needs to be explicitely
+ * used by the respective object encoders (eg. <mxModelCodec>,
+ * <mxChildChangeCodec> and <mxRootChangeCodec>). This
+ * implementation writes the given cell and its children as a
+ * (flat) sequence into the given node. The children are not
+ * encoded if the optional includeChildren is false. The
+ * function is in charge of adding the result into the
+ * given node and has no return value.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be encoded.
+ * node - Parent XML node to add the encoded cell into.
+ * includeChildren - Optional boolean indicating if the
+ * function should include all descendents. Default is true.
+ */
+mxCodec.prototype.encodeCell = function(cell, node, includeChildren)
+{
+ node.appendChild(this.encode(cell));
+
+ if (includeChildren == null || includeChildren)
+ {
+ var childCount = cell.getChildCount();
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.encodeCell(cell.getChildAt(i), node);
+ }
+ }
+};
+
+/**
+ * Function: isCellCodec
+ *
+ * Returns true if the given codec is a cell codec. This uses
+ * <mxCellCodec.isCellCodec> to check if the codec is of the
+ * given type.
+ */
+mxCodec.prototype.isCellCodec = function(codec)
+{
+ if (codec != null && typeof(codec.isCellCodec) == 'function')
+ {
+ return codec.isCellCodec();
+ }
+
+ return false;
+};
+
+/**
+ * Function: decodeCell
+ *
+ * Decodes cells that have been encoded using inversion, ie.
+ * where the user object is the enclosing node in the XML,
+ * and restores the group and graph structure in the cells.
+ * Returns a new <mxCell> instance that represents the
+ * given node.
+ *
+ * Parameters:
+ *
+ * node - XML node that contains the cell data.
+ * restoreStructures - Optional boolean indicating whether
+ * the graph structure should be restored by calling insert
+ * and insertEdge on the parent and terminals, respectively.
+ * Default is true.
+ */
+mxCodec.prototype.decodeCell = function(node, restoreStructures)
+{
+ restoreStructures = (restoreStructures != null) ? restoreStructures : true;
+ var cell = null;
+
+ if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ // Tries to find a codec for the given node name. If that does
+ // not return a codec then the node is the user object (an XML node
+ // that contains the mxCell, aka inversion).
+ var decoder = mxCodecRegistry.getCodec(node.nodeName);
+
+ // Tries to find the codec for the cell inside the user object.
+ // This assumes all node names inside the user object are either
+ // not registered or they correspond to a class for cells.
+ if (!this.isCellCodec(decoder))
+ {
+ var child = node.firstChild;
+
+ while (child != null && !this.isCellCodec(decoder))
+ {
+ decoder = mxCodecRegistry.getCodec(child.nodeName);
+ child = child.nextSibling;
+ }
+ }
+
+ if (!this.isCellCodec(decoder))
+ {
+ decoder = mxCodecRegistry.getCodec(mxCell);
+ }
+
+ cell = decoder.decode(this, node);
+
+ if (restoreStructures)
+ {
+ this.insertIntoGraph(cell);
+ }
+ }
+
+ return cell;
+};
+
+/**
+ * Function: insertIntoGraph
+ *
+ * Inserts the given cell into its parent and terminal cells.
+ */
+mxCodec.prototype.insertIntoGraph = function(cell)
+{
+ var parent = cell.parent;
+ var source = cell.getTerminal(true);
+ var target = cell.getTerminal(false);
+
+ // Fixes possible inconsistencies during insert into graph
+ cell.setTerminal(null, false);
+ cell.setTerminal(null, true);
+ cell.parent = null;
+
+ if (parent != null)
+ {
+ parent.insert(cell);
+ }
+
+ if (source != null)
+ {
+ source.insertEdge(cell, true);
+ }
+
+ if (target != null)
+ {
+ target.insertEdge(cell, false);
+ }
+};
+
+/**
+ * Function: setAttribute
+ *
+ * Sets the attribute on the specified node to value. This is a
+ * helper method that makes sure the attribute and value arguments
+ * are not null.
+ *
+ * Parameters:
+ *
+ * node - XML node to set the attribute for.
+ * attributes - Attributename to be set.
+ * value - New value of the attribute.
+ */
+mxCodec.prototype.setAttribute = function(node, attribute, value)
+{
+ if (attribute != null && value != null)
+ {
+ node.setAttribute(attribute, value);
+ }
+};
diff --git a/src/js/io/mxCodecRegistry.js b/src/js/io/mxCodecRegistry.js
new file mode 100644
index 0000000..a0a0f20
--- /dev/null
+++ b/src/js/io/mxCodecRegistry.js
@@ -0,0 +1,137 @@
+/**
+ * $Id: mxCodecRegistry.js,v 1.12 2010-04-30 13:18:21 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxCodecRegistry =
+{
+ /**
+ * Class: mxCodecRegistry
+ *
+ * Singleton class that acts as a global registry for codecs.
+ *
+ * Adding an <mxCodec>:
+ *
+ * 1. Define a default codec with a new instance of the
+ * object to be handled.
+ *
+ * (code)
+ * var codec = new mxObjectCodec(new mxGraphModel());
+ * (end)
+ *
+ * 2. Define the functions required for encoding and decoding
+ * objects.
+ *
+ * (code)
+ * codec.encode = function(enc, obj) { ... }
+ * codec.decode = function(dec, node, into) { ... }
+ * (end)
+ *
+ * 3. Register the codec in the <mxCodecRegistry>.
+ *
+ * (code)
+ * mxCodecRegistry.register(codec);
+ * (end)
+ *
+ * <mxObjectCodec.decode> may be used to either create a new
+ * instance of an object or to configure an existing instance,
+ * in which case the into argument points to the existing
+ * object. In this case, we say the codec "configures" the
+ * object.
+ *
+ * Variable: codecs
+ *
+ * Maps from constructor names to codecs.
+ */
+ codecs: [],
+
+ /**
+ * Variable: aliases
+ *
+ * Maps from classnames to codecnames.
+ */
+ aliases: [],
+
+ /**
+ * Function: register
+ *
+ * Registers a new codec and associates the name of the template
+ * constructor in the codec with the codec object.
+ *
+ * Parameters:
+ *
+ * codec - <mxObjectCodec> to be registered.
+ */
+ register: function(codec)
+ {
+ if (codec != null)
+ {
+ var name = codec.getName();
+ mxCodecRegistry.codecs[name] = codec;
+
+ var classname = mxUtils.getFunctionName(codec.template.constructor);
+
+ if (classname != name)
+ {
+ mxCodecRegistry.addAlias(classname, name);
+ }
+ }
+
+ return codec;
+ },
+
+ /**
+ * Function: addAlias
+ *
+ * Adds an alias for mapping a classname to a codecname.
+ */
+ addAlias: function(classname, codecname)
+ {
+ mxCodecRegistry.aliases[classname] = codecname;
+ },
+
+ /**
+ * Function: getCodec
+ *
+ * Returns a codec that handles objects that are constructed
+ * using the given constructor.
+ *
+ * Parameters:
+ *
+ * ctor - JavaScript constructor function.
+ */
+ getCodec: function(ctor)
+ {
+ var codec = null;
+
+ if (ctor != null)
+ {
+ var name = mxUtils.getFunctionName(ctor);
+ var tmp = mxCodecRegistry.aliases[name];
+
+ if (tmp != null)
+ {
+ name = tmp;
+ }
+
+ codec = mxCodecRegistry.codecs[name];
+
+ // Registers a new default codec for the given constructor
+ // if no codec has been previously defined.
+ if (codec == null)
+ {
+ try
+ {
+ codec = new mxObjectCodec(new ctor());
+ mxCodecRegistry.register(codec);
+ }
+ catch (e)
+ {
+ // ignore
+ }
+ }
+ }
+
+ return codec;
+ }
+
+};
diff --git a/src/js/io/mxDefaultKeyHandlerCodec.js b/src/js/io/mxDefaultKeyHandlerCodec.js
new file mode 100644
index 0000000..628524a
--- /dev/null
+++ b/src/js/io/mxDefaultKeyHandlerCodec.js
@@ -0,0 +1,88 @@
+/**
+ * $Id: mxDefaultKeyHandlerCodec.js,v 1.5 2010-01-02 09:45:15 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxDefaultKeyHandlerCodec
+ *
+ * Custom codec for configuring <mxDefaultKeyHandler>s. This class is created
+ * and registered dynamically at load time and used implicitely via
+ * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
+ * data for existing key handlers, it does not encode or create key handlers.
+ */
+ var codec = new mxObjectCodec(new mxDefaultKeyHandler());
+
+ /**
+ * Function: encode
+ *
+ * Returns null.
+ */
+ codec.encode = function(enc, obj)
+ {
+ return null;
+ };
+
+ /**
+ * Function: decode
+ *
+ * Reads a sequence of the following child nodes
+ * and attributes:
+ *
+ * Child Nodes:
+ *
+ * add - Binds a keystroke to an actionname.
+ *
+ * Attributes:
+ *
+ * as - Keycode.
+ * action - Actionname to execute in editor.
+ * control - Optional boolean indicating if
+ * the control key must be pressed.
+ *
+ * Example:
+ *
+ * (code)
+ * <mxDefaultKeyHandler as="keyHandler">
+ * <add as="88" control="true" action="cut"/>
+ * <add as="67" control="true" action="copy"/>
+ * <add as="86" control="true" action="paste"/>
+ * </mxDefaultKeyHandler>
+ * (end)
+ *
+ * The keycodes are for the x, c and v keys.
+ *
+ * See also: <mxDefaultKeyHandler.bindAction>,
+ * http://www.js-examples.com/page/tutorials__key_codes.html
+ */
+ codec.decode = function(dec, node, into)
+ {
+ if (into != null)
+ {
+ var editor = into.editor;
+ node = node.firstChild;
+
+ while (node != null)
+ {
+ if (!this.processInclude(dec, node, into) &&
+ node.nodeName == 'add')
+ {
+ var as = node.getAttribute('as');
+ var action = node.getAttribute('action');
+ var control = node.getAttribute('control');
+
+ into.bindAction(as, action, control);
+ }
+
+ node = node.nextSibling;
+ }
+ }
+
+ return into;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxDefaultPopupMenuCodec.js b/src/js/io/mxDefaultPopupMenuCodec.js
new file mode 100644
index 0000000..8d949ea
--- /dev/null
+++ b/src/js/io/mxDefaultPopupMenuCodec.js
@@ -0,0 +1,54 @@
+/**
+ * $Id: mxDefaultPopupMenuCodec.js,v 1.6 2010-01-02 09:45:15 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxDefaultPopupMenuCodec
+ *
+ * Custom codec for configuring <mxDefaultPopupMenu>s. This class is created
+ * and registered dynamically at load time and used implicitely via
+ * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
+ * data for existing popup menus, it does not encode or create menus. Note
+ * that this codec only passes the configuration node to the popup menu,
+ * which uses the config to dynamically create menus. See
+ * <mxDefaultPopupMenu.createMenu>.
+ */
+ var codec = new mxObjectCodec(new mxDefaultPopupMenu());
+
+ /**
+ * Function: encode
+ *
+ * Returns null.
+ */
+ codec.encode = function(enc, obj)
+ {
+ return null;
+ };
+
+ /**
+ * Function: decode
+ *
+ * Uses the given node as the config for <mxDefaultPopupMenu>.
+ */
+ codec.decode = function(dec, node, into)
+ {
+ var inc = node.getElementsByTagName('include')[0];
+
+ if (inc != null)
+ {
+ this.processInclude(dec, inc, into);
+ }
+ else if (into != null)
+ {
+ into.config = node;
+ }
+
+ return into;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxDefaultToolbarCodec.js b/src/js/io/mxDefaultToolbarCodec.js
new file mode 100644
index 0000000..6698b9b
--- /dev/null
+++ b/src/js/io/mxDefaultToolbarCodec.js
@@ -0,0 +1,301 @@
+/**
+ * $Id: mxDefaultToolbarCodec.js,v 1.22 2012-04-11 07:00:52 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxDefaultToolbarCodec
+ *
+ * Custom codec for configuring <mxDefaultToolbar>s. This class is created
+ * and registered dynamically at load time and used implicitely via
+ * <mxCodec> and the <mxCodecRegistry>. This codec only reads configuration
+ * data for existing toolbars handlers, it does not encode or create toolbars.
+ */
+ var codec = new mxObjectCodec(new mxDefaultToolbar());
+
+ /**
+ * Function: encode
+ *
+ * Returns null.
+ */
+ codec.encode = function(enc, obj)
+ {
+ return null;
+ };
+
+ /**
+ * Function: decode
+ *
+ * Reads a sequence of the following child nodes
+ * and attributes:
+ *
+ * Child Nodes:
+ *
+ * add - Adds a new item to the toolbar. See below for attributes.
+ * separator - Adds a vertical separator. No attributes.
+ * hr - Adds a horizontal separator. No attributes.
+ * br - Adds a linefeed. No attributes.
+ *
+ * Attributes:
+ *
+ * as - Resource key for the label.
+ * action - Name of the action to execute in enclosing editor.
+ * mode - Modename (see below).
+ * template - Template name for cell insertion.
+ * style - Optional style to override the template style.
+ * icon - Icon (relative/absolute URL).
+ * pressedIcon - Optional icon for pressed state (relative/absolute URL).
+ * id - Optional ID to be used for the created DOM element.
+ * toggle - Optional 0 or 1 to disable toggling of the element. Default is
+ * 1 (true).
+ *
+ * The action, mode and template attributes are mutually exclusive. The
+ * style can only be used with the template attribute. The add node may
+ * contain another sequence of add nodes with as and action attributes
+ * to create a combo box in the toolbar. If the icon is specified then
+ * a list of the child node is expected to have its template attribute
+ * set and the action is ignored instead.
+ *
+ * Nodes with a specified template may define a function to be used for
+ * inserting the cloned template into the graph. Here is an example of such
+ * a node:
+ *
+ * (code)
+ * <add as="Swimlane" template="swimlane" icon="images/swimlane.gif"><![CDATA[
+ * function (editor, cell, evt, targetCell)
+ * {
+ * var pt = mxUtils.convertPoint(
+ * editor.graph.container, mxEvent.getClientX(evt),
+ * mxEvent.getClientY(evt));
+ * return editor.addVertex(targetCell, cell, pt.x, pt.y);
+ * }
+ * ]]></add>
+ * (end)
+ *
+ * In the above function, editor is the enclosing <mxEditor> instance, cell
+ * is the clone of the template, evt is the mouse event that represents the
+ * drop and targetCell is the cell under the mousepointer where the drop
+ * occurred. The targetCell is retrieved using <mxGraph.getCellAt>.
+ *
+ * Futhermore, nodes with the mode attribute may define a function to
+ * be executed upon selection of the respective toolbar icon. In the
+ * example below, the default edge style is set when this specific
+ * connect-mode is activated:
+ *
+ * (code)
+ * <add as="connect" mode="connect"><![CDATA[
+ * function (editor)
+ * {
+ * if (editor.defaultEdge != null)
+ * {
+ * editor.defaultEdge.style = 'straightEdge';
+ * }
+ * }
+ * ]]></add>
+ * (end)
+ *
+ * Modes:
+ *
+ * select - Left mouse button used for rubberband- & cell-selection.
+ * connect - Allows connecting vertices by inserting new edges.
+ * pan - Disables selection and switches to panning on the left button.
+ *
+ * Example:
+ *
+ * To add items to the toolbar:
+ *
+ * (code)
+ * <mxDefaultToolbar as="toolbar">
+ * <add as="save" action="save" icon="images/save.gif"/>
+ * <br/><hr/>
+ * <add as="select" mode="select" icon="images/select.gif"/>
+ * <add as="connect" mode="connect" icon="images/connect.gif"/>
+ * </mxDefaultToolbar>
+ * (end)
+ */
+ codec.decode = function(dec, node, into)
+ {
+ if (into != null)
+ {
+ var editor = into.editor;
+ node = node.firstChild;
+
+ while (node != null)
+ {
+ if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ if (!this.processInclude(dec, node, into))
+ {
+ if (node.nodeName == 'separator')
+ {
+ into.addSeparator();
+ }
+ else if (node.nodeName == 'br')
+ {
+ into.toolbar.addBreak();
+ }
+ else if (node.nodeName == 'hr')
+ {
+ into.toolbar.addLine();
+ }
+ else if (node.nodeName == 'add')
+ {
+ var as = node.getAttribute('as');
+ as = mxResources.get(as) || as;
+ var icon = node.getAttribute('icon');
+ var pressedIcon = node.getAttribute('pressedIcon');
+ var action = node.getAttribute('action');
+ var mode = node.getAttribute('mode');
+ var template = node.getAttribute('template');
+ var toggle = node.getAttribute('toggle') != '0';
+ var text = mxUtils.getTextContent(node);
+ var elt = null;
+
+ if (action != null)
+ {
+ elt = into.addItem(as, icon, action, pressedIcon);
+ }
+ else if (mode != null)
+ {
+ var funct = mxUtils.eval(text);
+ elt = into.addMode(as, icon, mode, pressedIcon, funct);
+ }
+ else if (template != null || (text != null && text.length > 0))
+ {
+ var cell = editor.templates[template];
+ var style = node.getAttribute('style');
+
+ if (cell != null && style != null)
+ {
+ cell = cell.clone();
+ cell.setStyle(style);
+ }
+
+ var insertFunction = null;
+
+ if (text != null && text.length > 0)
+ {
+ insertFunction = mxUtils.eval(text);
+ }
+
+ elt = into.addPrototype(as, icon, cell, pressedIcon, insertFunction, toggle);
+ }
+ else
+ {
+ var children = mxUtils.getChildNodes(node);
+
+ if (children.length > 0)
+ {
+ if (icon == null)
+ {
+ var combo = into.addActionCombo(as);
+
+ for (var i=0; i<children.length; i++)
+ {
+ var child = children[i];
+
+ if (child.nodeName == 'separator')
+ {
+ into.addOption(combo, '---');
+ }
+ else if (child.nodeName == 'add')
+ {
+ var lab = child.getAttribute('as');
+ var act = child.getAttribute('action');
+ into.addActionOption(combo, lab, act);
+ }
+ }
+ }
+ else
+ {
+ var select = null;
+ var create = function()
+ {
+ var template = editor.templates[select.value];
+
+ if (template != null)
+ {
+ var clone = template.clone();
+ var style = select.options[select.selectedIndex].cellStyle;
+
+ if (style != null)
+ {
+ clone.setStyle(style);
+ }
+
+ return clone;
+ }
+ else
+ {
+ mxLog.warn('Template '+template+' not found');
+ }
+
+ return null;
+ };
+
+ var img = into.addPrototype(as, icon, create, null, null, toggle);
+ select = into.addCombo();
+
+ // Selects the toolbar icon if a selection change
+ // is made in the corresponding combobox.
+ mxEvent.addListener(select, 'change', function()
+ {
+ into.toolbar.selectMode(img, function(evt)
+ {
+ var pt = mxUtils.convertPoint(editor.graph.container,
+ mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+
+ return editor.addVertex(null, funct(), pt.x, pt.y);
+ });
+
+ into.toolbar.noReset = false;
+ });
+
+ // Adds the entries to the combobox
+ for (var i=0; i<children.length; i++)
+ {
+ var child = children[i];
+
+ if (child.nodeName == 'separator')
+ {
+ into.addOption(select, '---');
+ }
+ else if (child.nodeName == 'add')
+ {
+ var lab = child.getAttribute('as');
+ var tmp = child.getAttribute('template');
+ var option = into.addOption(select, lab, tmp || template);
+ option.cellStyle = child.getAttribute('style');
+ }
+ }
+
+ }
+ }
+ }
+
+ // Assigns an ID to the created element to access it later.
+ if (elt != null)
+ {
+ var id = node.getAttribute('id');
+
+ if (id != null && id.length > 0)
+ {
+ elt.setAttribute('id', id);
+ }
+ }
+ }
+ }
+ }
+
+ node = node.nextSibling;
+ }
+ }
+
+ return into;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxEditorCodec.js b/src/js/io/mxEditorCodec.js
new file mode 100644
index 0000000..f61bd95
--- /dev/null
+++ b/src/js/io/mxEditorCodec.js
@@ -0,0 +1,246 @@
+/**
+ * $Id: mxEditorCodec.js,v 1.11 2010-01-04 11:18:26 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxEditorCodec
+ *
+ * Codec for <mxEditor>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec>
+ * and the <mxCodecRegistry>.
+ *
+ * Transient Fields:
+ *
+ * - modified
+ * - lastSnapshot
+ * - ignoredChanges
+ * - undoManager
+ * - graphContainer
+ * - toolbarContainer
+ */
+ var codec = new mxObjectCodec(new mxEditor(),
+ ['modified', 'lastSnapshot', 'ignoredChanges',
+ 'undoManager', 'graphContainer', 'toolbarContainer']);
+
+ /**
+ * Function: beforeDecode
+ *
+ * Decodes the ui-part of the configuration node by reading
+ * a sequence of the following child nodes and attributes
+ * and passes the control to the default decoding mechanism:
+ *
+ * Child Nodes:
+ *
+ * stylesheet - Adds a CSS stylesheet to the document.
+ * resource - Adds the basename of a resource bundle.
+ * add - Creates or configures a known UI element.
+ *
+ * These elements may appear in any order given that the
+ * graph UI element is added before the toolbar element
+ * (see Known Keys).
+ *
+ * Attributes:
+ *
+ * as - Key for the UI element (see below).
+ * element - ID for the element in the document.
+ * style - CSS style to be used for the element or window.
+ * x - X coordinate for the new window.
+ * y - Y coordinate for the new window.
+ * width - Width for the new window.
+ * height - Optional height for the new window.
+ * name - Name of the stylesheet (absolute/relative URL).
+ * basename - Basename of the resource bundle (see <mxResources>).
+ *
+ * The x, y, width and height attributes are used to create a new
+ * <mxWindow> if the element attribute is not specified in an add
+ * node. The name and basename are only used in the stylesheet and
+ * resource nodes, respectively.
+ *
+ * Known Keys:
+ *
+ * graph - Main graph element (see <mxEditor.setGraphContainer>).
+ * title - Title element (see <mxEditor.setTitleContainer>).
+ * toolbar - Toolbar element (see <mxEditor.setToolbarContainer>).
+ * status - Status bar element (see <mxEditor.setStatusContainer>).
+ *
+ * Example:
+ *
+ * (code)
+ * <ui>
+ * <stylesheet name="css/process.css"/>
+ * <resource basename="resources/mxApplication"/>
+ * <add as="graph" element="graph"
+ * style="left:70px;right:20px;top:20px;bottom:40px"/>
+ * <add as="status" element="status"/>
+ * <add as="toolbar" x="10" y="20" width="54"/>
+ * </ui>
+ * (end)
+ */
+ codec.afterDecode = function(dec, node, obj)
+ {
+ // Assigns the specified templates for edges
+ var defaultEdge = node.getAttribute('defaultEdge');
+
+ if (defaultEdge != null)
+ {
+ node.removeAttribute('defaultEdge');
+ obj.defaultEdge = obj.templates[defaultEdge];
+ }
+
+ // Assigns the specified templates for groups
+ var defaultGroup = node.getAttribute('defaultGroup');
+
+ if (defaultGroup != null)
+ {
+ node.removeAttribute('defaultGroup');
+ obj.defaultGroup = obj.templates[defaultGroup];
+ }
+
+ return obj;
+ };
+
+ /**
+ * Function: decodeChild
+ *
+ * Overrides decode child to handle special child nodes.
+ */
+ codec.decodeChild = function(dec, child, obj)
+ {
+ if (child.nodeName == 'Array')
+ {
+ var role = child.getAttribute('as');
+
+ if (role == 'templates')
+ {
+ this.decodeTemplates(dec, child, obj);
+ return;
+ }
+ }
+ else if (child.nodeName == 'ui')
+ {
+ this.decodeUi(dec, child, obj);
+ return;
+ }
+
+ mxObjectCodec.prototype.decodeChild.apply(this, arguments);
+ };
+
+ /**
+ * Function: decodeTemplates
+ *
+ * Decodes the cells from the given node as templates.
+ */
+ codec.decodeUi = function(dec, node, editor)
+ {
+ var tmp = node.firstChild;
+ while (tmp != null)
+ {
+ if (tmp.nodeName == 'add')
+ {
+ var as = tmp.getAttribute('as');
+ var elt = tmp.getAttribute('element');
+ var style = tmp.getAttribute('style');
+ var element = null;
+
+ if (elt != null)
+ {
+ element = document.getElementById(elt);
+
+ if (element != null &&
+ style != null)
+ {
+ element.style.cssText += ';'+style;
+ }
+ }
+ else
+ {
+ var x = parseInt(tmp.getAttribute('x'));
+ var y = parseInt(tmp.getAttribute('y'));
+ var width = tmp.getAttribute('width');
+ var height = tmp.getAttribute('height');
+
+ // Creates a new window around the element
+ element = document.createElement('div');
+ element.style.cssText = style;
+
+ var wnd = new mxWindow(mxResources.get(as) || as,
+ element, x, y, width, height, false, true);
+ wnd.setVisible(true);
+ }
+
+ // TODO: Make more generic
+ if (as == 'graph')
+ {
+ editor.setGraphContainer(element);
+ }
+ else if (as == 'toolbar')
+ {
+ editor.setToolbarContainer(element);
+ }
+ else if (as == 'title')
+ {
+ editor.setTitleContainer(element);
+ }
+ else if (as == 'status')
+ {
+ editor.setStatusContainer(element);
+ }
+ else if (as == 'map')
+ {
+ editor.setMapContainer(element);
+ }
+ }
+ else if (tmp.nodeName == 'resource')
+ {
+ mxResources.add(tmp.getAttribute('basename'));
+ }
+ else if (tmp.nodeName == 'stylesheet')
+ {
+ mxClient.link('stylesheet', tmp.getAttribute('name'));
+ }
+
+ tmp = tmp.nextSibling;
+ }
+ };
+
+ /**
+ * Function: decodeTemplates
+ *
+ * Decodes the cells from the given node as templates.
+ */
+ codec.decodeTemplates = function(dec, node, editor)
+ {
+ if (editor.templates == null)
+ {
+ editor.templates = [];
+ }
+
+ var children = mxUtils.getChildNodes(node);
+ for (var j=0; j<children.length; j++)
+ {
+ var name = children[j].getAttribute('as');
+ var child = children[j].firstChild;
+
+ while (child != null && child.nodeType != 1)
+ {
+ child = child.nextSibling;
+ }
+
+ if (child != null)
+ {
+ // LATER: Only single cells means you need
+ // to group multiple cells within another
+ // cell. This should be changed to support
+ // arrays of cells, or the wrapper must
+ // be automatically handled in this class.
+ editor.templates[name] = dec.decodeCell(child);
+ }
+ }
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxGenericChangeCodec.js b/src/js/io/mxGenericChangeCodec.js
new file mode 100644
index 0000000..8da7789
--- /dev/null
+++ b/src/js/io/mxGenericChangeCodec.js
@@ -0,0 +1,64 @@
+/**
+ * $Id: mxGenericChangeCodec.js,v 1.11 2010-09-13 15:50:36 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGenericChangeCodec
+ *
+ * Codec for <mxValueChange>s, <mxStyleChange>s, <mxGeometryChange>s,
+ * <mxCollapseChange>s and <mxVisibleChange>s. This class is created
+ * and registered dynamically at load time and used implicitely
+ * via <mxCodec> and the <mxCodecRegistry>.
+ *
+ * Transient Fields:
+ *
+ * - model
+ * - previous
+ *
+ * Reference Fields:
+ *
+ * - cell
+ *
+ * Constructor: mxGenericChangeCodec
+ *
+ * Factory function that creates a <mxObjectCodec> for
+ * the specified change and fieldname.
+ *
+ * Parameters:
+ *
+ * obj - An instance of the change object.
+ * variable - The fieldname for the change data.
+ */
+var mxGenericChangeCodec = function(obj, variable)
+{
+ var codec = new mxObjectCodec(obj, ['model', 'previous'], ['cell']);
+
+ /**
+ * Function: afterDecode
+ *
+ * Restores the state by assigning the previous value.
+ */
+ codec.afterDecode = function(dec, node, obj)
+ {
+ // Allows forward references in sessions. This is a workaround
+ // for the sequence of edits in mxGraph.moveCells and cellsAdded.
+ if (mxUtils.isNode(obj.cell))
+ {
+ obj.cell = dec.decodeCell(obj.cell, false);
+ }
+
+ obj.previous = obj[variable];
+
+ return obj;
+ };
+
+ return codec;
+};
+
+// Registers the codecs
+mxCodecRegistry.register(mxGenericChangeCodec(new mxValueChange(), 'value'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxStyleChange(), 'style'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxGeometryChange(), 'geometry'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxCollapseChange(), 'collapsed'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxVisibleChange(), 'visible'));
+mxCodecRegistry.register(mxGenericChangeCodec(new mxCellAttributeChange(), 'value'));
diff --git a/src/js/io/mxGraphCodec.js b/src/js/io/mxGraphCodec.js
new file mode 100644
index 0000000..f052e13
--- /dev/null
+++ b/src/js/io/mxGraphCodec.js
@@ -0,0 +1,28 @@
+/**
+ * $Id: mxGraphCodec.js,v 1.8 2010-06-10 06:54:18 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxGraphCodec
+ *
+ * Codec for <mxGraph>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec>
+ * and the <mxCodecRegistry>.
+ *
+ * Transient Fields:
+ *
+ * - graphListeners
+ * - eventListeners
+ * - view
+ * - container
+ * - cellRenderer
+ * - editor
+ * - selection
+ */
+ return new mxObjectCodec(new mxGraph(),
+ ['graphListeners', 'eventListeners', 'view', 'container',
+ 'cellRenderer', 'editor', 'selection']);
+
+}());
diff --git a/src/js/io/mxGraphViewCodec.js b/src/js/io/mxGraphViewCodec.js
new file mode 100644
index 0000000..110b212
--- /dev/null
+++ b/src/js/io/mxGraphViewCodec.js
@@ -0,0 +1,197 @@
+/**
+ * $Id: mxGraphViewCodec.js,v 1.18 2010-12-03 11:05:52 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxGraphViewCodec
+ *
+ * Custom encoder for <mxGraphView>s. This class is created
+ * and registered dynamically at load time and used implicitely via
+ * <mxCodec> and the <mxCodecRegistry>. This codec only writes views
+ * into a XML format that can be used to create an image for
+ * the graph, that is, it contains absolute coordinates with
+ * computed perimeters, edge styles and cell styles.
+ */
+ var codec = new mxObjectCodec(new mxGraphView());
+
+ /**
+ * Function: encode
+ *
+ * Encodes the given <mxGraphView> using <encodeCell>
+ * starting at the model's root. This returns the
+ * top-level graph node of the recursive encoding.
+ */
+ codec.encode = function(enc, view)
+ {
+ return this.encodeCell(enc, view,
+ view.graph.getModel().getRoot());
+ };
+
+ /**
+ * Function: encodeCell
+ *
+ * Recursively encodes the specifed cell. Uses layer
+ * as the default nodename. If the cell's parent is
+ * null, then graph is used for the nodename. If
+ * <mxGraphModel.isEdge> returns true for the cell,
+ * then edge is used for the nodename, else if
+ * <mxGraphModel.isVertex> returns true for the cell,
+ * then vertex is used for the nodename.
+ *
+ * <mxGraph.getLabel> is used to create the label
+ * attribute for the cell. For graph nodes and vertices
+ * the bounds are encoded into x, y, width and height.
+ * For edges the points are encoded into a points
+ * attribute as a space-separated list of comma-separated
+ * coordinate pairs (eg. x0,y0 x1,y1 ... xn,yn). All
+ * values from the cell style are added as attribute
+ * values to the node.
+ */
+ codec.encodeCell = function(enc, view, cell)
+ {
+ var model = view.graph.getModel();
+ var state = view.getState(cell);
+ var parent = model.getParent(cell);
+
+ if (parent == null || state != null)
+ {
+ var childCount = model.getChildCount(cell);
+ var geo = view.graph.getCellGeometry(cell);
+ var name = null;
+
+ if (parent == model.getRoot())
+ {
+ name = 'layer';
+ }
+ else if (parent == null)
+ {
+ name = 'graph';
+ }
+ else if (model.isEdge(cell))
+ {
+ name = 'edge';
+ }
+ else if (childCount > 0 && geo != null)
+ {
+ name = 'group';
+ }
+ else if (model.isVertex(cell))
+ {
+ name = 'vertex';
+ }
+
+ if (name != null)
+ {
+ var node = enc.document.createElement(name);
+ var lab = view.graph.getLabel(cell);
+
+ if (lab != null)
+ {
+ node.setAttribute('label', view.graph.getLabel(cell));
+
+ if (view.graph.isHtmlLabel(cell))
+ {
+ node.setAttribute('html', true);
+ }
+ }
+
+ if (parent == null)
+ {
+ var bounds = view.getGraphBounds();
+
+ if (bounds != null)
+ {
+ node.setAttribute('x', Math.round(bounds.x));
+ node.setAttribute('y', Math.round(bounds.y));
+ node.setAttribute('width', Math.round(bounds.width));
+ node.setAttribute('height', Math.round(bounds.height));
+ }
+
+ node.setAttribute('scale', view.scale);
+ }
+ else if (state != null && geo != null)
+ {
+ // Writes each key, value in the style pair to an attribute
+ for (var i in state.style)
+ {
+ var value = state.style[i];
+
+ // Tries to turn objects and functions into strings
+ if (typeof(value) == 'function' &&
+ typeof(value) == 'object')
+ {
+ value = mxStyleRegistry.getName(value);
+ }
+
+ if (value != null &&
+ typeof(value) != 'function' &&
+ typeof(value) != 'object')
+ {
+ node.setAttribute(i, value);
+ }
+ }
+
+ var abs = state.absolutePoints;
+
+ // Writes the list of points into one attribute
+ if (abs != null && abs.length > 0)
+ {
+ var pts = Math.round(abs[0].x) + ',' + Math.round(abs[0].y);
+
+ for (var i=1; i<abs.length; i++)
+ {
+ pts += ' ' + Math.round(abs[i].x) + ',' +
+ Math.round(abs[i].y);
+ }
+
+ node.setAttribute('points', pts);
+ }
+
+ // Writes the bounds into 4 attributes
+ else
+ {
+ node.setAttribute('x', Math.round(state.x));
+ node.setAttribute('y', Math.round(state.y));
+ node.setAttribute('width', Math.round(state.width));
+ node.setAttribute('height', Math.round(state.height));
+ }
+
+ var offset = state.absoluteOffset;
+
+ // Writes the offset into 2 attributes
+ if (offset != null)
+ {
+ if (offset.x != 0)
+ {
+ node.setAttribute('dx', Math.round(offset.x));
+ }
+
+ if (offset.y != 0)
+ {
+ node.setAttribute('dy', Math.round(offset.y));
+ }
+ }
+ }
+
+ for (var i=0; i<childCount; i++)
+ {
+ var childNode = this.encodeCell(enc,
+ view, model.getChildAt(cell, i));
+
+ if (childNode != null)
+ {
+ node.appendChild(childNode);
+ }
+ }
+ }
+ }
+
+ return node;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxModelCodec.js b/src/js/io/mxModelCodec.js
new file mode 100644
index 0000000..760a2b1
--- /dev/null
+++ b/src/js/io/mxModelCodec.js
@@ -0,0 +1,80 @@
+/**
+ * $Id: mxModelCodec.js,v 1.11 2010-11-23 08:46:41 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxModelCodec
+ *
+ * Codec for <mxGraphModel>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec>
+ * and the <mxCodecRegistry>.
+ */
+ var codec = new mxObjectCodec(new mxGraphModel());
+
+ /**
+ * Function: encodeObject
+ *
+ * Encodes the given <mxGraphModel> by writing a (flat) XML sequence of
+ * cell nodes as produced by the <mxCellCodec>. The sequence is
+ * wrapped-up in a node with the name root.
+ */
+ codec.encodeObject = function(enc, obj, node)
+ {
+ var rootNode = enc.document.createElement('root');
+ enc.encodeCell(obj.getRoot(), rootNode);
+ node.appendChild(rootNode);
+ };
+
+ /**
+ * Function: decodeChild
+ *
+ * Overrides decode child to handle special child nodes.
+ */
+ codec.decodeChild = function(dec, child, obj)
+ {
+ if (child.nodeName == 'root')
+ {
+ this.decodeRoot(dec, child, obj);
+ }
+ else
+ {
+ mxObjectCodec.prototype.decodeChild.apply(this, arguments);
+ }
+ };
+
+ /**
+ * Function: decodeRoot
+ *
+ * Reads the cells into the graph model. All cells
+ * are children of the root element in the node.
+ */
+ codec.decodeRoot = function(dec, root, model)
+ {
+ var rootCell = null;
+ var tmp = root.firstChild;
+
+ while (tmp != null)
+ {
+ var cell = dec.decodeCell(tmp);
+
+ if (cell != null && cell.getParent() == null)
+ {
+ rootCell = cell;
+ }
+
+ tmp = tmp.nextSibling;
+ }
+
+ // Sets the root on the model if one has been decoded
+ if (rootCell != null)
+ {
+ model.setRoot(rootCell);
+ }
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxObjectCodec.js b/src/js/io/mxObjectCodec.js
new file mode 100644
index 0000000..9d2473a
--- /dev/null
+++ b/src/js/io/mxObjectCodec.js
@@ -0,0 +1,983 @@
+/**
+ * $Id: mxObjectCodec.js,v 1.49 2010-12-01 09:19:58 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxObjectCodec
+ *
+ * Generic codec for JavaScript objects that implements a mapping between
+ * JavaScript objects and XML nodes that maps each field or element to an
+ * attribute or child node, and vice versa.
+ *
+ * Atomic Values:
+ *
+ * Consider the following example.
+ *
+ * (code)
+ * var obj = new Object();
+ * obj.foo = "Foo";
+ * obj.bar = "Bar";
+ * (end)
+ *
+ * This object is encoded into an XML node using the following.
+ *
+ * (code)
+ * var enc = new mxCodec();
+ * var node = enc.encode(obj);
+ * (end)
+ *
+ * The output of the encoding may be viewed using <mxLog> as follows.
+ *
+ * (code)
+ * mxLog.show();
+ * mxLog.debug(mxUtils.getPrettyXml(node));
+ * (end)
+ *
+ * Finally, the result of the encoding looks as follows.
+ *
+ * (code)
+ * <Object foo="Foo" bar="Bar"/>
+ * (end)
+ *
+ * In the above output, the foo and bar fields have been mapped to attributes
+ * with the same names, and the name of the constructor was used for the
+ * nodename.
+ *
+ * Booleans:
+ *
+ * Since booleans are numbers in JavaScript, all boolean values are encoded
+ * into 1 for true and 0 for false. The decoder also accepts the string true
+ * and false for boolean values.
+ *
+ * Objects:
+ *
+ * The above scheme is applied to all atomic fields, that is, to all non-object
+ * fields of an object. For object fields, a child node is created with a
+ * special attribute that contains the fieldname. This special attribute is
+ * called "as" and hence, as is a reserved word that should not be used for a
+ * fieldname.
+ *
+ * Consider the following example where foo is an object and bar is an atomic
+ * property of foo.
+ *
+ * (code)
+ * var obj = {foo: {bar: "Bar"}};
+ * (end)
+ *
+ * This will be mapped to the following XML structure by mxObjectCodec.
+ *
+ * (code)
+ * <Object>
+ * <Object bar="Bar" as="foo"/>
+ * </Object>
+ * (end)
+ *
+ * In the above output, the inner Object node contains the as-attribute that
+ * specifies the fieldname in the enclosing object. That is, the field foo was
+ * mapped to a child node with an as-attribute that has the value foo.
+ *
+ * Arrays:
+ *
+ * Arrays are special objects that are either associative, in which case each
+ * key, value pair is treated like a field where the key is the fieldname, or
+ * they are a sequence of atomic values and objects, which is mapped to a
+ * sequence of child nodes. For object elements, the above scheme is applied
+ * without the use of the special as-attribute for creating each child. For
+ * atomic elements, a special add-node is created with the value stored in the
+ * value-attribute.
+ *
+ * For example, the following array contains one atomic value and one object
+ * with a field called bar. Furthermore it contains two associative entries
+ * called bar with an atomic value, and foo with an object value.
+ *
+ * (code)
+ * var obj = ["Bar", {bar: "Bar"}];
+ * obj["bar"] = "Bar";
+ * obj["foo"] = {bar: "Bar"};
+ * (end)
+ *
+ * This array is represented by the following XML nodes.
+ *
+ * (code)
+ * <Array bar="Bar">
+ * <add value="Bar"/>
+ * <Object bar="Bar"/>
+ * <Object bar="Bar" as="foo"/>
+ * </Array>
+ * (end)
+ *
+ * The Array node name is the name of the constructor. The additional
+ * as-attribute in the last child contains the key of the associative entry,
+ * whereas the second last child is part of the array sequence and does not
+ * have an as-attribute.
+ *
+ * References:
+ *
+ * Objects may be represented as child nodes or attributes with ID values,
+ * which are used to lookup the object in a table within <mxCodec>. The
+ * <isReference> function is in charge of deciding if a specific field should
+ * be encoded as a reference or not. Its default implementation returns true if
+ * the fieldname is in <idrefs>, an array of strings that is used to configure
+ * the <mxObjectCodec>.
+ *
+ * Using this approach, the mapping does not guarantee that the referenced
+ * object itself exists in the document. The fields that are encoded as
+ * references must be carefully chosen to make sure all referenced objects
+ * exist in the document, or may be resolved by some other means if necessary.
+ *
+ * For example, in the case of the graph model all cells are stored in a tree
+ * whose root is referenced by the model's root field. A tree is a structure
+ * that is well suited for an XML representation, however, the additional edges
+ * in the graph model have a reference to a source and target cell, which are
+ * also contained in the tree. To handle this case, the source and target cell
+ * of an edge are treated as references, whereas the children are treated as
+ * objects. Since all cells are contained in the tree and no edge references a
+ * source or target outside the tree, this setup makes sure all referenced
+ * objects are contained in the document.
+ *
+ * In the case of a tree structure we must further avoid infinite recursion by
+ * ignoring the parent reference of each child. This is done by returning true
+ * in <isExcluded>, whose default implementation uses the array of excluded
+ * fieldnames passed to the mxObjectCodec constructor.
+ *
+ * References are only used for cells in mxGraph. For defining other
+ * referencable object types, the codec must be able to work out the ID of an
+ * object. This is done by implementing <mxCodec.reference>. For decoding a
+ * reference, the XML node with the respective id-attribute is fetched from the
+ * document, decoded, and stored in a lookup table for later reference. For
+ * looking up external objects, <mxCodec.lookup> may be implemented.
+ *
+ * Expressions:
+ *
+ * For decoding JavaScript expressions, the add-node may be used with a text
+ * content that contains the JavaScript expression. For example, the following
+ * creates a field called foo in the enclosing object and assigns it the value
+ * of <mxConstants.ALIGN_LEFT>.
+ *
+ * (code)
+ * <Object>
+ * <add as="foo">mxConstants.ALIGN_LEFT</add>
+ * </Object>
+ * (end)
+ *
+ * The resulting object has a field called foo with the value "left". Its XML
+ * representation looks as follows.
+ *
+ * (code)
+ * <Object foo="left"/>
+ * (end)
+ *
+ * This means the expression is evaluated at decoding time and the result of
+ * the evaluation is stored in the respective field. Valid expressions are all
+ * JavaScript expressions, including function definitions, which are mapped to
+ * functions on the resulting object.
+ *
+ * Constructor: mxObjectCodec
+ *
+ * Constructs a new codec for the specified template object.
+ * The variables in the optional exclude array are ignored by
+ * the codec. Variables in the optional idrefs array are
+ * turned into references in the XML. The optional mapping
+ * may be used to map from variable names to XML attributes.
+ * The argument is created as follows:
+ *
+ * (code)
+ * var mapping = new Object();
+ * mapping['variableName'] = 'attribute-name';
+ * (end)
+ *
+ * Parameters:
+ *
+ * template - Prototypical instance of the object to be
+ * encoded/decoded.
+ * exclude - Optional array of fieldnames to be ignored.
+ * idrefs - Optional array of fieldnames to be converted to/from
+ * references.
+ * mapping - Optional mapping from field- to attributenames.
+ */
+function mxObjectCodec(template, exclude, idrefs, mapping)
+{
+ this.template = template;
+
+ this.exclude = (exclude != null) ? exclude : [];
+ this.idrefs = (idrefs != null) ? idrefs : [];
+ this.mapping = (mapping != null) ? mapping : [];
+
+ this.reverse = new Object();
+
+ for (var i in this.mapping)
+ {
+ this.reverse[this.mapping[i]] = i;
+ }
+};
+
+/**
+ * Variable: template
+ *
+ * Holds the template object associated with this codec.
+ */
+mxObjectCodec.prototype.template = null;
+
+/**
+ * Variable: exclude
+ *
+ * Array containing the variable names that should be
+ * ignored by the codec.
+ */
+mxObjectCodec.prototype.exclude = null;
+
+/**
+ * Variable: idrefs
+ *
+ * Array containing the variable names that should be
+ * turned into or converted from references. See
+ * <mxCodec.getId> and <mxCodec.getObject>.
+ */
+mxObjectCodec.prototype.idrefs = null;
+
+/**
+ * Variable: mapping
+ *
+ * Maps from from fieldnames to XML attribute names.
+ */
+mxObjectCodec.prototype.mapping = null;
+
+/**
+ * Variable: reverse
+ *
+ * Maps from from XML attribute names to fieldnames.
+ */
+mxObjectCodec.prototype.reverse = null;
+
+/**
+ * Function: getName
+ *
+ * Returns the name used for the nodenames and lookup of the codec when
+ * classes are encoded and nodes are decoded. For classes to work with
+ * this the codec registry automatically adds an alias for the classname
+ * if that is different than what this returns. The default implementation
+ * returns the classname of the template class.
+ */
+mxObjectCodec.prototype.getName = function()
+{
+ return mxUtils.getFunctionName(this.template.constructor);
+};
+
+/**
+ * Function: cloneTemplate
+ *
+ * Returns a new instance of the template for this codec.
+ */
+mxObjectCodec.prototype.cloneTemplate = function()
+{
+ return new this.template.constructor();
+};
+
+/**
+ * Function: getFieldName
+ *
+ * Returns the fieldname for the given attributename.
+ * Looks up the value in the <reverse> mapping or returns
+ * the input if there is no reverse mapping for the
+ * given name.
+ */
+mxObjectCodec.prototype.getFieldName = function(attributename)
+{
+ if (attributename != null)
+ {
+ var mapped = this.reverse[attributename];
+
+ if (mapped != null)
+ {
+ attributename = mapped;
+ }
+ }
+
+ return attributename;
+};
+
+/**
+ * Function: getAttributeName
+ *
+ * Returns the attributename for the given fieldname.
+ * Looks up the value in the <mapping> or returns
+ * the input if there is no mapping for the
+ * given name.
+ */
+mxObjectCodec.prototype.getAttributeName = function(fieldname)
+{
+ if (fieldname != null)
+ {
+ var mapped = this.mapping[fieldname];
+
+ if (mapped != null)
+ {
+ fieldname = mapped;
+ }
+ }
+
+ return fieldname;
+};
+
+/**
+ * Function: isExcluded
+ *
+ * Returns true if the given attribute is to be ignored by the codec. This
+ * implementation returns true if the given fieldname is in <exclude> or
+ * if the fieldname equals <mxObjectIdentity.FIELD_NAME>.
+ *
+ * Parameters:
+ *
+ * obj - Object instance that contains the field.
+ * attr - Fieldname of the field.
+ * value - Value of the field.
+ * write - Boolean indicating if the field is being encoded or decoded.
+ * Write is true if the field is being encoded, else it is being decoded.
+ */
+mxObjectCodec.prototype.isExcluded = function(obj, attr, value, write)
+{
+ return attr == mxObjectIdentity.FIELD_NAME ||
+ mxUtils.indexOf(this.exclude, attr) >= 0;
+};
+
+/**
+ * Function: isReference
+ *
+ * Returns true if the given fieldname is to be treated
+ * as a textual reference (ID). This implementation returns
+ * true if the given fieldname is in <idrefs>.
+ *
+ * Parameters:
+ *
+ * obj - Object instance that contains the field.
+ * attr - Fieldname of the field.
+ * value - Value of the field.
+ * write - Boolean indicating if the field is being encoded or decoded.
+ * Write is true if the field is being encoded, else it is being decoded.
+ */
+mxObjectCodec.prototype.isReference = function(obj, attr, value, write)
+{
+ return mxUtils.indexOf(this.idrefs, attr) >= 0;
+};
+
+/**
+ * Function: encode
+ *
+ * Encodes the specified object and returns a node
+ * representing then given object. Calls <beforeEncode>
+ * after creating the node and <afterEncode> with the
+ * resulting node after processing.
+ *
+ * Enc is a reference to the calling encoder. It is used
+ * to encode complex objects and create references.
+ *
+ * This implementation encodes all variables of an
+ * object according to the following rules:
+ *
+ * - If the variable name is in <exclude> then it is ignored.
+ * - If the variable name is in <idrefs> then <mxCodec.getId>
+ * is used to replace the object with its ID.
+ * - The variable name is mapped using <mapping>.
+ * - If obj is an array and the variable name is numeric
+ * (ie. an index) then it is not encoded.
+ * - If the value is an object, then the codec is used to
+ * create a child node with the variable name encoded into
+ * the "as" attribute.
+ * - Else, if <encodeDefaults> is true or the value differs
+ * from the template value, then ...
+ * - ... if obj is not an array, then the value is mapped to
+ * an attribute.
+ * - ... else if obj is an array, the value is mapped to an
+ * add child with a value attribute or a text child node,
+ * if the value is a function.
+ *
+ * If no ID exists for a variable in <idrefs> or if an object
+ * cannot be encoded, a warning is issued using <mxLog.warn>.
+ *
+ * Returns the resulting XML node that represents the given
+ * object.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ */
+mxObjectCodec.prototype.encode = function(enc, obj)
+{
+ var node = enc.document.createElement(this.getName());
+
+ obj = this.beforeEncode(enc, obj, node);
+ this.encodeObject(enc, obj, node);
+
+ return this.afterEncode(enc, obj, node);
+};
+
+/**
+ * Function: encodeObject
+ *
+ * Encodes the value of each member in then given obj into the given node using
+ * <encodeValue>.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ * node - XML node that contains the encoded object.
+ */
+mxObjectCodec.prototype.encodeObject = function(enc, obj, node)
+{
+ enc.setAttribute(node, 'id', enc.getId(obj));
+
+ for (var i in obj)
+ {
+ var name = i;
+ var value = obj[name];
+
+ if (value != null && !this.isExcluded(obj, name, value, true))
+ {
+ if (mxUtils.isNumeric(name))
+ {
+ name = null;
+ }
+
+ this.encodeValue(enc, obj, name, value, node);
+ }
+ }
+};
+
+/**
+ * Function: encodeValue
+ *
+ * Converts the given value according to the mappings
+ * and id-refs in this codec and uses <writeAttribute>
+ * to write the attribute into the given node.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object whose property is going to be encoded.
+ * name - XML node that contains the encoded object.
+ * value - Value of the property to be encoded.
+ * node - XML node that contains the encoded object.
+ */
+mxObjectCodec.prototype.encodeValue = function(enc, obj,
+ name, value, node)
+{
+ if (value != null)
+ {
+ if (this.isReference(obj, name, value, true))
+ {
+ var tmp = enc.getId(value);
+
+ if (tmp == null)
+ {
+ mxLog.warn('mxObjectCodec.encode: No ID for ' +
+ this.getName() + '.' + name + '=' + value);
+ return; // exit
+ }
+
+ value = tmp;
+ }
+
+ var defaultValue = this.template[name];
+
+ // Checks if the value is a default value and
+ // the name is correct
+ if (name == null || enc.encodeDefaults ||
+ defaultValue != value)
+ {
+ name = this.getAttributeName(name);
+ this.writeAttribute(enc, obj, name, value, node);
+ }
+ }
+};
+
+/**
+ * Function: writeAttribute
+ *
+ * Writes the given value into node using <writePrimitiveAttribute>
+ * or <writeComplexAttribute> depending on the type of the value.
+ */
+mxObjectCodec.prototype.writeAttribute = function(enc, obj,
+ attr, value, node)
+{
+ if (typeof(value) != 'object' /* primitive type */)
+ {
+ this.writePrimitiveAttribute(enc, obj, attr, value, node);
+ }
+ else /* complex type */
+ {
+ this.writeComplexAttribute(enc, obj, attr, value, node);
+ }
+};
+
+/**
+ * Function: writePrimitiveAttribute
+ *
+ * Writes the given value as an attribute of the given node.
+ */
+mxObjectCodec.prototype.writePrimitiveAttribute = function(enc, obj,
+ attr, value, node)
+{
+ value = this.convertValueToXml(value);
+
+ if (attr == null)
+ {
+ var child = enc.document.createElement('add');
+
+ if (typeof(value) == 'function')
+ {
+ child.appendChild(
+ enc.document.createTextNode(value));
+ }
+ else
+ {
+ enc.setAttribute(child, 'value', value);
+ }
+
+ node.appendChild(child);
+ }
+ else if (typeof(value) != 'function')
+ {
+ enc.setAttribute(node, attr, value);
+ }
+};
+
+/**
+ * Function: writeComplexAttribute
+ *
+ * Writes the given value as a child node of the given node.
+ */
+mxObjectCodec.prototype.writeComplexAttribute = function(enc, obj,
+ attr, value, node)
+{
+ var child = enc.encode(value);
+
+ if (child != null)
+ {
+ if (attr != null)
+ {
+ child.setAttribute('as', attr);
+ }
+
+ node.appendChild(child);
+ }
+ else
+ {
+ mxLog.warn('mxObjectCodec.encode: No node for ' +
+ this.getName() + '.' + attr + ': ' + value);
+ }
+};
+
+/**
+ * Function: convertValueToXml
+ *
+ * Converts true to "1" and false to "0". All other values are ignored.
+ */
+mxObjectCodec.prototype.convertValueToXml = function(value)
+{
+ // Makes sure to encode boolean values as numeric values
+ if (typeof(value.length) == 'undefined' &&
+ (value == true ||
+ value == false))
+ {
+ // Checks if the value is true (do not use the
+ // value as is, because this would check if the
+ // value is not null, so 0 would be true!
+ value = (value == true) ? '1' : '0';
+ }
+ return value;
+};
+
+/**
+ * Function: convertValueFromXml
+ *
+ * Converts booleans and numeric values to the respective types.
+ */
+mxObjectCodec.prototype.convertValueFromXml = function(value)
+{
+ if (mxUtils.isNumeric(value))
+ {
+ value = parseFloat(value);
+ }
+
+ return value;
+};
+
+/**
+ * Function: beforeEncode
+ *
+ * Hook for subclassers to pre-process the object before
+ * encoding. This returns the input object. The return
+ * value of this function is used in <encode> to perform
+ * the default encoding into the given node.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ * node - XML node to encode the object into.
+ */
+mxObjectCodec.prototype.beforeEncode = function(enc, obj, node)
+{
+ return obj;
+};
+
+/**
+ * Function: afterEncode
+ *
+ * Hook for subclassers to post-process the node
+ * for the given object after encoding and return the
+ * post-processed node. This implementation returns
+ * the input node. The return value of this method
+ * is returned to the encoder from <encode>.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * obj - Object to be encoded.
+ * node - XML node that represents the default encoding.
+ */
+mxObjectCodec.prototype.afterEncode = function(enc, obj, node)
+{
+ return node;
+};
+
+/**
+ * Function: decode
+ *
+ * Parses the given node into the object or returns a new object
+ * representing the given node.
+ *
+ * Dec is a reference to the calling decoder. It is used to decode
+ * complex objects and resolve references.
+ *
+ * If a node has an id attribute then the object cache is checked for the
+ * object. If the object is not yet in the cache then it is constructed
+ * using the constructor of <template> and cached in <mxCodec.objects>.
+ *
+ * This implementation decodes all attributes and childs of a node
+ * according to the following rules:
+ *
+ * - If the variable name is in <exclude> or if the attribute name is "id"
+ * or "as" then it is ignored.
+ * - If the variable name is in <idrefs> then <mxCodec.getObject> is used
+ * to replace the reference with an object.
+ * - The variable name is mapped using a reverse <mapping>.
+ * - If the value has a child node, then the codec is used to create a
+ * child object with the variable name taken from the "as" attribute.
+ * - If the object is an array and the variable name is empty then the
+ * value or child object is appended to the array.
+ * - If an add child has no value or the object is not an array then
+ * the child text content is evaluated using <mxUtils.eval>.
+ *
+ * For add nodes where the object is not an array and the variable name
+ * is defined, the default mechanism is used, allowing to override/add
+ * methods as follows:
+ *
+ * (code)
+ * <Object>
+ * <add as="hello"><![CDATA[
+ * function(arg1) {
+ * mxUtils.alert('Hello '+arg1);
+ * }
+ * ]]></add>
+ * </Object>
+ * (end)
+ *
+ * If no object exists for an ID in <idrefs> a warning is issued
+ * using <mxLog.warn>.
+ *
+ * Returns the resulting object that represents the given XML node
+ * or the object given to the method as the into parameter.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * into - Optional objec to encode the node into.
+ */
+mxObjectCodec.prototype.decode = function(dec, node, into)
+{
+ var id = node.getAttribute('id');
+ var obj = dec.objects[id];
+
+ if (obj == null)
+ {
+ obj = into || this.cloneTemplate();
+
+ if (id != null)
+ {
+ dec.putObject(id, obj);
+ }
+ }
+
+ node = this.beforeDecode(dec, node, obj);
+ this.decodeNode(dec, node, obj);
+
+ return this.afterDecode(dec, node, obj);
+};
+
+/**
+ * Function: decodeNode
+ *
+ * Calls <decodeAttributes> and <decodeChildren> for the given node.
+ */
+mxObjectCodec.prototype.decodeNode = function(dec, node, obj)
+{
+ if (node != null)
+ {
+ this.decodeAttributes(dec, node, obj);
+ this.decodeChildren(dec, node, obj);
+ }
+};
+
+/**
+ * Function: decodeAttributes
+ *
+ * Decodes all attributes of the given node using <decodeAttribute>.
+ */
+mxObjectCodec.prototype.decodeAttributes = function(dec, node, obj)
+{
+ var attrs = node.attributes;
+
+ if (attrs != null)
+ {
+ for (var i = 0; i < attrs.length; i++)
+ {
+ this.decodeAttribute(dec, attrs[i], obj);
+ }
+ }
+};
+
+/**
+ * Function: decodeAttribute
+ *
+ * Reads the given attribute into the specified object.
+ */
+mxObjectCodec.prototype.decodeAttribute = function(dec, attr, obj)
+{
+ var name = attr.nodeName;
+
+ if (name != 'as' && name != 'id')
+ {
+ // Converts the string true and false to their boolean values.
+ // This may require an additional check on the obj to see if
+ // the existing field is a boolean value or uninitialized, in
+ // which case we may want to convert true and false to a string.
+ var value = this.convertValueFromXml(attr.nodeValue);
+ var fieldname = this.getFieldName(name);
+
+ if (this.isReference(obj, fieldname, value, false))
+ {
+ var tmp = dec.getObject(value);
+
+ if (tmp == null)
+ {
+ mxLog.warn('mxObjectCodec.decode: No object for ' +
+ this.getName() + '.' + name + '=' + value);
+ return; // exit
+ }
+
+ value = tmp;
+ }
+
+ if (!this.isExcluded(obj, name, value, false))
+ {
+ //mxLog.debug(mxUtils.getFunctionName(obj.constructor)+'.'+name+'='+value);
+ obj[name] = value;
+ }
+ }
+};
+
+/**
+ * Function: decodeChildren
+ *
+ * Decodec all children of the given node using <decodeChild>.
+ */
+mxObjectCodec.prototype.decodeChildren = function(dec, node, obj)
+{
+ var child = node.firstChild;
+
+ while (child != null)
+ {
+ var tmp = child.nextSibling;
+
+ if (child.nodeType == mxConstants.NODETYPE_ELEMENT &&
+ !this.processInclude(dec, child, obj))
+ {
+ this.decodeChild(dec, child, obj);
+ }
+
+ child = tmp;
+ }
+};
+
+/**
+ * Function: decodeChild
+ *
+ * Reads the specified child into the given object.
+ */
+mxObjectCodec.prototype.decodeChild = function(dec, child, obj)
+{
+ var fieldname = this.getFieldName(child.getAttribute('as'));
+
+ if (fieldname == null ||
+ !this.isExcluded(obj, fieldname, child, false))
+ {
+ var template = this.getFieldTemplate(obj, fieldname, child);
+ var value = null;
+
+ if (child.nodeName == 'add')
+ {
+ value = child.getAttribute('value');
+
+ if (value == null)
+ {
+ value = mxUtils.eval(mxUtils.getTextContent(child));
+ //mxLog.debug('Decoded '+fieldname+' '+mxUtils.getTextContent(child));
+ }
+ }
+ else
+ {
+ value = dec.decode(child, template);
+ // mxLog.debug('Decoded '+node.nodeName+'.'+fieldname+'='+
+ // ((tmp != null) ? tmp.constructor.name : 'null'));
+ }
+
+ this.addObjectValue(obj, fieldname, value, template);
+ }
+};
+
+/**
+ * Function: getFieldTemplate
+ *
+ * Returns the template instance for the given field. This returns the
+ * value of the field, null if the value is an array or an empty collection
+ * if the value is a collection. The value is then used to populate the
+ * field for a new instance. For strongly typed languages it may be
+ * required to override this to return the correct collection instance
+ * based on the encoded child.
+ */
+mxObjectCodec.prototype.getFieldTemplate = function(obj, fieldname, child)
+{
+ var template = obj[fieldname];
+
+ // Non-empty arrays are replaced completely
+ if (template instanceof Array && template.length > 0)
+ {
+ template = null;
+ }
+
+ return template;
+};
+
+/**
+ * Function: addObjectValue
+ *
+ * Sets the decoded child node as a value of the given object. If the
+ * object is a map, then the value is added with the given fieldname as a
+ * key. If the fieldname is not empty, then setFieldValue is called or
+ * else, if the object is a collection, the value is added to the
+ * collection. For strongly typed languages it may be required to
+ * override this with the correct code to add an entry to an object.
+ */
+mxObjectCodec.prototype.addObjectValue = function(obj, fieldname, value, template)
+{
+ if (value != null && value != template)
+ {
+ if (fieldname != null && fieldname.length > 0)
+ {
+ obj[fieldname] = value;
+ }
+ else
+ {
+ obj.push(value);
+ }
+ //mxLog.debug('Decoded '+mxUtils.getFunctionName(obj.constructor)+'.'+fieldname+': '+value);
+ }
+};
+
+/**
+ * Function: processInclude
+ *
+ * Returns true if the given node is an include directive and
+ * executes the include by decoding the XML document. Returns
+ * false if the given node is not an include directive.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the encoding/decoding process.
+ * node - XML node to be checked.
+ * into - Optional object to pass-thru to the codec.
+ */
+mxObjectCodec.prototype.processInclude = function(dec, node, into)
+{
+ if (node.nodeName == 'include')
+ {
+ var name = node.getAttribute('name');
+
+ if (name != null)
+ {
+ try
+ {
+ var xml = mxUtils.load(name).getDocumentElement();
+
+ if (xml != null)
+ {
+ dec.decode(xml, into);
+ }
+ }
+ catch (e)
+ {
+ // ignore
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * Function: beforeDecode
+ *
+ * Hook for subclassers to pre-process the node for
+ * the specified object and return the node to be
+ * used for further processing by <decode>.
+ * The object is created based on the template in the
+ * calling method and is never null. This implementation
+ * returns the input node. The return value of this
+ * function is used in <decode> to perform
+ * the default decoding into the given object.
+ *
+ * Parameters:
+ *
+ * dec - <mxCodec> that controls the decoding process.
+ * node - XML node to be decoded.
+ * obj - Object to encode the node into.
+ */
+mxObjectCodec.prototype.beforeDecode = function(dec, node, obj)
+{
+ return node;
+};
+
+/**
+ * Function: afterDecode
+ *
+ * Hook for subclassers to post-process the object after
+ * decoding. This implementation returns the given object
+ * without any changes. The return value of this method
+ * is returned to the decoder from <decode>.
+ *
+ * Parameters:
+ *
+ * enc - <mxCodec> that controls the encoding process.
+ * node - XML node to be decoded.
+ * obj - Object that represents the default decoding.
+ */
+mxObjectCodec.prototype.afterDecode = function(dec, node, obj)
+{
+ return obj;
+};
diff --git a/src/js/io/mxRootChangeCodec.js b/src/js/io/mxRootChangeCodec.js
new file mode 100644
index 0000000..fda613a
--- /dev/null
+++ b/src/js/io/mxRootChangeCodec.js
@@ -0,0 +1,83 @@
+/**
+ * $Id: mxRootChangeCodec.js,v 1.6 2010-09-15 14:38:51 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxRootChangeCodec
+ *
+ * Codec for <mxRootChange>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec> and
+ * the <mxCodecRegistry>.
+ *
+ * Transient Fields:
+ *
+ * - model
+ * - previous
+ * - root
+ */
+ var codec = new mxObjectCodec(new mxRootChange(),
+ ['model', 'previous', 'root']);
+
+ /**
+ * Function: onEncode
+ *
+ * Encodes the child recursively.
+ */
+ codec.afterEncode = function(enc, obj, node)
+ {
+ enc.encodeCell(obj.root, node);
+
+ return node;
+ };
+
+ /**
+ * Function: beforeDecode
+ *
+ * Decodes the optional children as cells
+ * using the respective decoder.
+ */
+ codec.beforeDecode = function(dec, node, obj)
+ {
+ if (node.firstChild != null &&
+ node.firstChild.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ // Makes sure the original node isn't modified
+ node = node.cloneNode(true);
+
+ var tmp = node.firstChild;
+ obj.root = dec.decodeCell(tmp, false);
+
+ var tmp2 = tmp.nextSibling;
+ tmp.parentNode.removeChild(tmp);
+ tmp = tmp2;
+
+ while (tmp != null)
+ {
+ tmp2 = tmp.nextSibling;
+ dec.decodeCell(tmp);
+ tmp.parentNode.removeChild(tmp);
+ tmp = tmp2;
+ }
+ }
+
+ return node;
+ };
+
+ /**
+ * Function: afterDecode
+ *
+ * Restores the state by assigning the previous value.
+ */
+ codec.afterDecode = function(dec, node, obj)
+ {
+ obj.previous = obj.root;
+
+ return obj;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxStylesheetCodec.js b/src/js/io/mxStylesheetCodec.js
new file mode 100644
index 0000000..7636eb1
--- /dev/null
+++ b/src/js/io/mxStylesheetCodec.js
@@ -0,0 +1,210 @@
+/**
+ * $Id: mxStylesheetCodec.js,v 1.19 2011-06-13 08:18:42 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxStylesheetCodec
+ *
+ * Codec for <mxStylesheet>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec>
+ * and the <mxCodecRegistry>.
+ */
+ var codec = new mxObjectCodec(new mxStylesheet());
+
+ /**
+ * Function: encode
+ *
+ * Encodes a stylesheet. See <decode> for a description of the
+ * format.
+ */
+ codec.encode = function(enc, obj)
+ {
+ var node = enc.document.createElement(this.getName());
+
+ for (var i in obj.styles)
+ {
+ var style = obj.styles[i];
+ var styleNode = enc.document.createElement('add');
+
+ if (i != null)
+ {
+ styleNode.setAttribute('as', i);
+
+ for (var j in style)
+ {
+ var value = this.getStringValue(j, style[j]);
+
+ if (value != null)
+ {
+ var entry = enc.document.createElement('add');
+ entry.setAttribute('value', value);
+ entry.setAttribute('as', j);
+ styleNode.appendChild(entry);
+ }
+ }
+
+ if (styleNode.childNodes.length > 0)
+ {
+ node.appendChild(styleNode);
+ }
+ }
+ }
+
+ return node;
+ };
+
+ /**
+ * Function: getStringValue
+ *
+ * Returns the string for encoding the given value.
+ */
+ codec.getStringValue = function(key, value)
+ {
+ var type = typeof(value);
+
+ if (type == 'function')
+ {
+ value = mxStyleRegistry.getName(style[j]);
+ }
+ else if (type == 'object')
+ {
+ value = null;
+ }
+
+ return value;
+ };
+
+ /**
+ * Function: decode
+ *
+ * Reads a sequence of the following child nodes
+ * and attributes:
+ *
+ * Child Nodes:
+ *
+ * add - Adds a new style.
+ *
+ * Attributes:
+ *
+ * as - Name of the style.
+ * extend - Name of the style to inherit from.
+ *
+ * Each node contains another sequence of add and remove nodes with the following
+ * attributes:
+ *
+ * as - Name of the style (see <mxConstants>).
+ * value - Value for the style.
+ *
+ * Instead of the value-attribute, one can put Javascript expressions into
+ * the node as follows:
+ * <add as="perimeter">mxPerimeter.RectanglePerimeter</add>
+ *
+ * A remove node will remove the entry with the name given in the as-attribute
+ * from the style.
+ *
+ * Example:
+ *
+ * (code)
+ * <mxStylesheet as="stylesheet">
+ * <add as="text">
+ * <add as="fontSize" value="12"/>
+ * </add>
+ * <add as="defaultVertex" extend="text">
+ * <add as="shape" value="rectangle"/>
+ * </add>
+ * </mxStylesheet>
+ * (end)
+ */
+ codec.decode = function(dec, node, into)
+ {
+ var obj = into || new this.template.constructor();
+ var id = node.getAttribute('id');
+
+ if (id != null)
+ {
+ dec.objects[id] = obj;
+ }
+
+ node = node.firstChild;
+
+ while (node != null)
+ {
+ if (!this.processInclude(dec, node, obj) &&
+ node.nodeName == 'add')
+ {
+ var as = node.getAttribute('as');
+
+ if (as != null)
+ {
+ var extend = node.getAttribute('extend');
+ var style = (extend != null) ? mxUtils.clone(obj.styles[extend]) : null;
+
+ if (style == null)
+ {
+ if (extend != null)
+ {
+ mxLog.warn('mxStylesheetCodec.decode: stylesheet ' +
+ extend + ' not found to extend');
+ }
+
+ style = new Object();
+ }
+
+ var entry = node.firstChild;
+
+ while (entry != null)
+ {
+ if (entry.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ var key = entry.getAttribute('as');
+
+ if (entry.nodeName == 'add')
+ {
+ var text = mxUtils.getTextContent(entry);
+ var value = null;
+
+ if (text != null &&
+ text.length > 0)
+ {
+ value = mxUtils.eval(text);
+ }
+ else
+ {
+ value = entry.getAttribute('value');
+
+ if (mxUtils.isNumeric(value))
+ {
+ value = parseFloat(value);
+ }
+ }
+
+ if (value != null)
+ {
+ style[key] = value;
+ }
+ }
+ else if (entry.nodeName == 'remove')
+ {
+ delete style[key];
+ }
+ }
+
+ entry = entry.nextSibling;
+ }
+
+ obj.putCellStyle(as, style);
+ }
+ }
+
+ node = node.nextSibling;
+ }
+
+ return obj;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/io/mxTerminalChangeCodec.js b/src/js/io/mxTerminalChangeCodec.js
new file mode 100644
index 0000000..a51d871
--- /dev/null
+++ b/src/js/io/mxTerminalChangeCodec.js
@@ -0,0 +1,42 @@
+/**
+ * $Id: mxTerminalChangeCodec.js,v 1.7 2010-09-13 15:58:36 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+mxCodecRegistry.register(function()
+{
+ /**
+ * Class: mxTerminalChangeCodec
+ *
+ * Codec for <mxTerminalChange>s. This class is created and registered
+ * dynamically at load time and used implicitely via <mxCodec> and
+ * the <mxCodecRegistry>.
+ *
+ * Transient Fields:
+ *
+ * - model
+ * - previous
+ *
+ * Reference Fields:
+ *
+ * - cell
+ * - terminal
+ */
+ var codec = new mxObjectCodec(new mxTerminalChange(),
+ ['model', 'previous'], ['cell', 'terminal']);
+
+ /**
+ * Function: afterDecode
+ *
+ * Restores the state by assigning the previous value.
+ */
+ codec.afterDecode = function(dec, node, obj)
+ {
+ obj.previous = obj.terminal;
+
+ return obj;
+ };
+
+ // Returns the codec into the registry
+ return codec;
+
+}());
diff --git a/src/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js b/src/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js
new file mode 100644
index 0000000..e2fe6a6
--- /dev/null
+++ b/src/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js
@@ -0,0 +1,206 @@
+/**
+ * $Id: mxGraphAbstractHierarchyCell.js,v 1.12 2010-01-04 11:18:26 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphAbstractHierarchyCell
+ *
+ * An abstraction of an internal hierarchy node or edge
+ *
+ * Constructor: mxGraphAbstractHierarchyCell
+ *
+ * Constructs a new hierarchical layout algorithm.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * deterministic - Optional boolean that specifies if this layout should be
+ * deterministic. Default is true.
+ */
+function mxGraphAbstractHierarchyCell()
+{
+ this.x = [];
+ this.y = [];
+ this.temp = [];
+};
+
+/**
+ * Variable: maxRank
+ *
+ * The maximum rank this cell occupies. Default is -1.
+ */
+mxGraphAbstractHierarchyCell.prototype.maxRank = -1;
+
+/**
+ * Variable: minRank
+ *
+ * The minimum rank this cell occupies. Default is -1.
+ */
+mxGraphAbstractHierarchyCell.prototype.minRank = -1;
+
+/**
+ * Variable: x
+ *
+ * The x position of this cell for each layer it occupies
+ */
+mxGraphAbstractHierarchyCell.prototype.x = null;
+
+/**
+ * Variable: y
+ *
+ * The y position of this cell for each layer it occupies
+ */
+mxGraphAbstractHierarchyCell.prototype.y = null;
+
+/**
+ * Variable: width
+ *
+ * The width of this cell
+ */
+mxGraphAbstractHierarchyCell.prototype.width = 0;
+
+/**
+ * Variable: height
+ *
+ * The height of this cell
+ */
+mxGraphAbstractHierarchyCell.prototype.height = 0;
+
+/**
+ * Variable: nextLayerConnectedCells
+ *
+ * A cached version of the cells this cell connects to on the next layer up
+ */
+mxGraphAbstractHierarchyCell.prototype.nextLayerConnectedCells = null;
+
+/**
+ * Variable: previousLayerConnectedCells
+ *
+ * A cached version of the cells this cell connects to on the next layer down
+ */
+mxGraphAbstractHierarchyCell.prototype.previousLayerConnectedCells = null;
+
+/**
+ * Variable: temp
+ *
+ * Temporary variable for general use. Generally, try to avoid
+ * carrying information between stages. Currently, the longest
+ * path layering sets temp to the rank position in fixRanks()
+ * and the crossing reduction uses this. This meant temp couldn't
+ * be used for hashing the nodes in the model dfs and so hashCode
+ * was created
+ */
+mxGraphAbstractHierarchyCell.prototype.temp = null;
+
+/**
+ * Function: getNextLayerConnectedCells
+ *
+ * Returns the cells this cell connects to on the next layer up
+ */
+mxGraphAbstractHierarchyCell.prototype.getNextLayerConnectedCells = function(layer)
+{
+ return null;
+};
+
+/**
+ * Function: getPreviousLayerConnectedCells
+ *
+ * Returns the cells this cell connects to on the next layer down
+ */
+mxGraphAbstractHierarchyCell.prototype.getPreviousLayerConnectedCells = function(layer)
+{
+ return null;
+};
+
+/**
+ * Function: isEdge
+ *
+ * Returns whether or not this cell is an edge
+ */
+mxGraphAbstractHierarchyCell.prototype.isEdge = function()
+{
+ return false;
+};
+
+/**
+ * Function: isVertex
+ *
+ * Returns whether or not this cell is a node
+ */
+mxGraphAbstractHierarchyCell.prototype.isVertex = function()
+{
+ return false;
+};
+
+/**
+ * Function: getGeneralPurposeVariable
+ *
+ * Gets the value of temp for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.getGeneralPurposeVariable = function(layer)
+{
+ return null;
+};
+
+/**
+ * Function: setGeneralPurposeVariable
+ *
+ * Set the value of temp for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.setGeneralPurposeVariable = function(layer, value)
+{
+ return null;
+};
+
+/**
+ * Function: setX
+ *
+ * Set the value of x for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.setX = function(layer, value)
+{
+ if (this.isVertex())
+ {
+ this.x[0] = value;
+ }
+ else if (this.isEdge())
+ {
+ this.x[layer - this.minRank - 1] = value;
+ }
+};
+
+/**
+ * Function: getX
+ *
+ * Gets the value of x on the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.getX = function(layer)
+{
+ if (this.isVertex())
+ {
+ return this.x[0];
+ }
+ else if (this.isEdge())
+ {
+ return this.x[layer - this.minRank - 1];
+ }
+
+ return 0.0;
+};
+
+/**
+ * Function: setY
+ *
+ * Set the value of y for the specified layer
+ */
+mxGraphAbstractHierarchyCell.prototype.setY = function(layer, value)
+{
+ if (this.isVertex())
+ {
+ this.y[0] = value;
+ }
+ else if (this.isEdge())
+ {
+ this.y[layer -this. minRank - 1] = value;
+ }
+};
diff --git a/src/js/layout/hierarchical/model/mxGraphHierarchyEdge.js b/src/js/layout/hierarchical/model/mxGraphHierarchyEdge.js
new file mode 100644
index 0000000..8ba16dd
--- /dev/null
+++ b/src/js/layout/hierarchical/model/mxGraphHierarchyEdge.js
@@ -0,0 +1,174 @@
+/**
+ * $Id: mxGraphHierarchyEdge.js,v 1.15 2012-06-12 20:23:14 david Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphHierarchyEdge
+ *
+ * An abstraction of a hierarchical edge for the hierarchy layout
+ *
+ * Constructor: mxGraphHierarchyEdge
+ *
+ * Constructs a hierarchy edge
+ *
+ * Arguments:
+ *
+ * edges - a list of real graph edges this abstraction represents
+ */
+function mxGraphHierarchyEdge(edges)
+{
+ mxGraphAbstractHierarchyCell.apply(this, arguments);
+ this.edges = edges;
+};
+
+/**
+ * Extends mxGraphAbstractHierarchyCell.
+ */
+mxGraphHierarchyEdge.prototype = new mxGraphAbstractHierarchyCell();
+mxGraphHierarchyEdge.prototype.constructor = mxGraphHierarchyEdge;
+
+/**
+ * Variable: edges
+ *
+ * The graph edge(s) this object represents. Parallel edges are all grouped
+ * together within one hierarchy edge.
+ */
+mxGraphHierarchyEdge.prototype.edges = null;
+
+/**
+ * Variable: source
+ *
+ * The node this edge is sourced at
+ */
+mxGraphHierarchyEdge.prototype.source = null;
+
+/**
+ * Variable: target
+ *
+ * The node this edge targets
+ */
+mxGraphHierarchyEdge.prototype.target = null;
+
+/**
+ * Variable: isReversed
+ *
+ * Whether or not the direction of this edge has been reversed
+ * internally to create a DAG for the hierarchical layout
+ */
+mxGraphHierarchyEdge.prototype.isReversed = false;
+
+/**
+ * Function: invert
+ *
+ * Inverts the direction of this internal edge(s)
+ */
+mxGraphHierarchyEdge.prototype.invert = function(layer)
+{
+ var temp = this.source;
+ this.source = this.target;
+ this.target = temp;
+ this.isReversed = !this.isReversed;
+};
+
+/**
+ * Function: getNextLayerConnectedCells
+ *
+ * Returns the cells this cell connects to on the next layer up
+ */
+mxGraphHierarchyEdge.prototype.getNextLayerConnectedCells = function(layer)
+{
+ if (this.nextLayerConnectedCells == null)
+ {
+ this.nextLayerConnectedCells = [];
+
+ for (var i = 0; i < this.temp.length; i++)
+ {
+ this.nextLayerConnectedCells[i] = [];
+
+ if (i == this.temp.length - 1)
+ {
+ this.nextLayerConnectedCells[i].push(this.source);
+ }
+ else
+ {
+ this.nextLayerConnectedCells[i].push(this);
+ }
+ }
+ }
+
+ return this.nextLayerConnectedCells[layer - this.minRank - 1];
+};
+
+/**
+ * Function: getPreviousLayerConnectedCells
+ *
+ * Returns the cells this cell connects to on the next layer down
+ */
+mxGraphHierarchyEdge.prototype.getPreviousLayerConnectedCells = function(layer)
+{
+ if (this.previousLayerConnectedCells == null)
+ {
+ this.previousLayerConnectedCells = [];
+
+ for (var i = 0; i < this.temp.length; i++)
+ {
+ this.previousLayerConnectedCells[i] = [];
+
+ if (i == 0)
+ {
+ this.previousLayerConnectedCells[i].push(this.target);
+ }
+ else
+ {
+ this.previousLayerConnectedCells[i].push(this);
+ }
+ }
+ }
+
+ return this.previousLayerConnectedCells[layer - this.minRank - 1];
+};
+
+/**
+ * Function: isEdge
+ *
+ * Returns true.
+ */
+mxGraphHierarchyEdge.prototype.isEdge = function()
+{
+ return true;
+};
+
+/**
+ * Function: getGeneralPurposeVariable
+ *
+ * Gets the value of temp for the specified layer
+ */
+mxGraphHierarchyEdge.prototype.getGeneralPurposeVariable = function(layer)
+{
+ return this.temp[layer - this.minRank - 1];
+};
+
+/**
+ * Function: setGeneralPurposeVariable
+ *
+ * Set the value of temp for the specified layer
+ */
+mxGraphHierarchyEdge.prototype.setGeneralPurposeVariable = function(layer, value)
+{
+ this.temp[layer - this.minRank - 1] = value;
+};
+
+/**
+ * Function: getCoreCell
+ *
+ * Gets the first core edge associated with this wrapper
+ */
+mxGraphHierarchyEdge.prototype.getCoreCell = function()
+{
+ if (this.edges != null && this.edges.length > 0)
+ {
+ return this.edges[0];
+ }
+
+ return null;
+}; \ No newline at end of file
diff --git a/src/js/layout/hierarchical/model/mxGraphHierarchyModel.js b/src/js/layout/hierarchical/model/mxGraphHierarchyModel.js
new file mode 100644
index 0000000..ca2ba30
--- /dev/null
+++ b/src/js/layout/hierarchical/model/mxGraphHierarchyModel.js
@@ -0,0 +1,685 @@
+/**
+ * $Id: mxGraphHierarchyModel.js,v 1.33 2012-12-18 13:16:43 david Exp $
+ * Copyright (c) 2006-2012, JGraph Ltd
+ */
+/**
+ * Class: mxGraphHierarchyModel
+ *
+ * Internal model of a hierarchical graph. This model stores nodes and edges
+ * equivalent to the real graph nodes and edges, but also stores the rank of the
+ * cells, the order within the ranks and the new candidate locations of cells.
+ * The internal model also reverses edge direction were appropriate , ignores
+ * self-loop and groups parallels together under one edge object.
+ *
+ * Constructor: mxGraphHierarchyModel
+ *
+ * Creates an internal ordered graph model using the vertices passed in. If
+ * there are any, leftward edge need to be inverted in the internal model
+ *
+ * Arguments:
+ *
+ * graph - the facade describing the graph to be operated on
+ * vertices - the vertices for this hierarchy
+ * ordered - whether or not the vertices are already ordered
+ * deterministic - whether or not this layout should be deterministic on each
+ * tightenToSource - whether or not to tighten vertices towards the sources
+ * scanRanksFromSinks - Whether rank assignment is from the sinks or sources.
+ * usage
+ */
+function mxGraphHierarchyModel(layout, vertices, roots, parent, tightenToSource)
+{
+ var graph = layout.getGraph();
+ this.tightenToSource = tightenToSource;
+ this.roots = roots;
+ this.parent = parent;
+
+ // map of cells to internal cell needed for second run through
+ // to setup the sink of edges correctly
+ this.vertexMapper = new Object();
+ this.edgeMapper = new Object();
+ this.maxRank = 0;
+ var internalVertices = [];
+
+ if (vertices == null)
+ {
+ vertices = this.graph.getChildVertices(parent);
+ }
+
+ this.maxRank = this.SOURCESCANSTARTRANK;
+ // map of cells to internal cell needed for second run through
+ // to setup the sink of edges correctly. Guess size by number
+ // of edges is roughly same as number of vertices.
+ this.createInternalCells(layout, vertices, internalVertices);
+
+ // Go through edges set their sink values. Also check the
+ // ordering if and invert edges if necessary
+ for (var i = 0; i < vertices.length; i++)
+ {
+ var edges = internalVertices[i].connectsAsSource;
+
+ for (var j = 0; j < edges.length; j++)
+ {
+ var internalEdge = edges[j];
+ var realEdges = internalEdge.edges;
+
+ // Only need to process the first real edge, since
+ // all the edges connect to the same other vertex
+ if (realEdges != null && realEdges.length > 0)
+ {
+ var realEdge = realEdges[0];
+ var targetCell = graph.getView().getVisibleTerminal(
+ realEdge, false);
+ var targetCellId = mxCellPath.create(targetCell);
+ var internalTargetCell = this.vertexMapper[targetCellId];
+
+ if (internalVertices[i] == internalTargetCell)
+ {
+ // The real edge is reversed relative to the internal edge
+ targetCell = graph.getView().getVisibleTerminal(
+ realEdge, true);
+ targetCellId = mxCellPath.create(targetCell);
+ internalTargetCell = this.vertexMapper[targetCellId];
+ }
+
+ if (internalTargetCell != null
+ && internalVertices[i] != internalTargetCell)
+ {
+ internalEdge.target = internalTargetCell;
+
+ if (internalTargetCell.connectsAsTarget.length == 0)
+ {
+ internalTargetCell.connectsAsTarget = [];
+ }
+
+ if (mxUtils.indexOf(internalTargetCell.connectsAsTarget, internalEdge) < 0)
+ {
+ internalTargetCell.connectsAsTarget.push(internalEdge);
+ }
+ }
+ }
+ }
+
+ // Use the temp variable in the internal nodes to mark this
+ // internal vertex as having been visited.
+ internalVertices[i].temp[0] = 1;
+ }
+};
+
+/**
+ * Variable: maxRank
+ *
+ * Stores the largest rank number allocated
+ */
+mxGraphHierarchyModel.prototype.maxRank = null;
+
+/**
+ * Variable: vertexMapper
+ *
+ * Map from graph vertices to internal model nodes.
+ */
+mxGraphHierarchyModel.prototype.vertexMapper = null;
+
+/**
+ * Variable: edgeMapper
+ *
+ * Map from graph edges to internal model edges
+ */
+mxGraphHierarchyModel.prototype.edgeMapper = null;
+
+/**
+ * Variable: ranks
+ *
+ * Mapping from rank number to actual rank
+ */
+mxGraphHierarchyModel.prototype.ranks = null;
+
+/**
+ * Variable: roots
+ *
+ * Store of roots of this hierarchy model, these are real graph cells, not
+ * internal cells
+ */
+mxGraphHierarchyModel.prototype.roots = null;
+
+/**
+ * Variable: parent
+ *
+ * The parent cell whose children are being laid out
+ */
+mxGraphHierarchyModel.prototype.parent = null;
+
+/**
+ * Variable: dfsCount
+ *
+ * Count of the number of times the ancestor dfs has been used.
+ */
+mxGraphHierarchyModel.prototype.dfsCount = 0;
+
+/**
+ * Variable: SOURCESCANSTARTRANK
+ *
+ * High value to start source layering scan rank value from.
+ */
+mxGraphHierarchyModel.prototype.SOURCESCANSTARTRANK = 100000000;
+
+/**
+ * Variable: tightenToSource
+ *
+ * Whether or not to tighten the assigned ranks of vertices up towards
+ * the source cells.
+ */
+mxGraphHierarchyModel.prototype.tightenToSource = false;
+
+/**
+ * Function: createInternalCells
+ *
+ * Creates all edges in the internal model
+ *
+ * Parameters:
+ *
+ * layout - Reference to the <mxHierarchicalLayout> algorithm.
+ * vertices - Array of <mxCells> that represent the vertices whom are to
+ * have an internal representation created.
+ * internalVertices - The array of <mxGraphHierarchyNodes> to have their
+ * information filled in using the real vertices.
+ */
+mxGraphHierarchyModel.prototype.createInternalCells = function(layout, vertices, internalVertices)
+{
+ var graph = layout.getGraph();
+
+ // Create internal edges
+ for (var i = 0; i < vertices.length; i++)
+ {
+ internalVertices[i] = new mxGraphHierarchyNode(vertices[i]);
+ var vertexId = mxCellPath.create(vertices[i]);
+ this.vertexMapper[vertexId] = internalVertices[i];
+
+ // If the layout is deterministic, order the cells
+ //List outgoingCells = graph.getNeighbours(vertices[i], deterministic);
+ var conns = layout.getEdges(vertices[i]);
+ var outgoingCells = graph.getOpposites(conns, vertices[i]);
+ internalVertices[i].connectsAsSource = [];
+
+ // Create internal edges, but don't do any rank assignment yet
+ // First use the information from the greedy cycle remover to
+ // invert the leftward edges internally
+ for (var j = 0; j < outgoingCells.length; j++)
+ {
+ var cell = outgoingCells[j];
+
+ if (cell != vertices[i] && layout.graph.model.isVertex(cell) &&
+ !layout.isVertexIgnored(cell))
+ {
+ // We process all edge between this source and its targets
+ // If there are edges going both ways, we need to collect
+ // them all into one internal edges to avoid looping problems
+ // later. We assume this direction (source -> target) is the
+ // natural direction if at least half the edges are going in
+ // that direction.
+
+ // The check below for edges[0] being in the vertex mapper is
+ // in case we've processed this the other way around
+ // (target -> source) and the number of edges in each direction
+ // are the same. All the graph edges will have been assigned to
+ // an internal edge going the other way, so we don't want to
+ // process them again
+ var undirectedEdges = graph.getEdgesBetween(vertices[i],
+ cell, false);
+ var directedEdges = graph.getEdgesBetween(vertices[i],
+ cell, true);
+ var edgeId = mxCellPath.create(undirectedEdges[0]);
+
+ if (undirectedEdges != null &&
+ undirectedEdges.length > 0 &&
+ this.edgeMapper[edgeId] == null &&
+ directedEdges.length * 2 >= undirectedEdges.length)
+ {
+ var internalEdge = new mxGraphHierarchyEdge(undirectedEdges);
+
+ for (var k = 0; k < undirectedEdges.length; k++)
+ {
+ var edge = undirectedEdges[k];
+ edgeId = mxCellPath.create(edge);
+ this.edgeMapper[edgeId] = internalEdge;
+
+ // Resets all point on the edge and disables the edge style
+ // without deleting it from the cell style
+ graph.resetEdge(edge);
+
+ if (layout.disableEdgeStyle)
+ {
+ layout.setEdgeStyleEnabled(edge, false);
+ layout.setOrthogonalEdge(edge,true);
+ }
+ }
+
+ internalEdge.source = internalVertices[i];
+
+ if (mxUtils.indexOf(internalVertices[i].connectsAsSource, internalEdge) < 0)
+ {
+ internalVertices[i].connectsAsSource.push(internalEdge);
+ }
+ }
+ }
+ }
+
+ // Ensure temp variable is cleared from any previous use
+ internalVertices[i].temp[0] = 0;
+ }
+};
+
+/**
+ * Function: initialRank
+ *
+ * Basic determination of minimum layer ranking by working from from sources
+ * or sinks and working through each node in the relevant edge direction.
+ * Starting at the sinks is basically a longest path layering algorithm.
+*/
+mxGraphHierarchyModel.prototype.initialRank = function()
+{
+ var startNodes = [];
+
+ if (this.roots != null)
+ {
+ for (var i = 0; i < this.roots.length; i++)
+ {
+ var vertexId = mxCellPath.create(this.roots[i]);
+ var internalNode = this.vertexMapper[vertexId];
+
+ if (internalNode != null)
+ {
+ startNodes.push(internalNode);
+ }
+ }
+ }
+
+ for (var key in this.vertexMapper)
+ {
+ var internalNode = this.vertexMapper[key];
+
+ // Mark the node as not having had a layer assigned
+ internalNode.temp[0] = -1;
+ }
+
+ var startNodesCopy = startNodes.slice();
+
+ while (startNodes.length > 0)
+ {
+ var internalNode = startNodes[0];
+ var layerDeterminingEdges;
+ var edgesToBeMarked;
+
+ layerDeterminingEdges = internalNode.connectsAsTarget;
+ edgesToBeMarked = internalNode.connectsAsSource;
+
+ // flag to keep track of whether or not all layer determining
+ // edges have been scanned
+ var allEdgesScanned = true;
+
+ // Work out the layer of this node from the layer determining
+ // edges. The minimum layer number of any node connected by one of
+ // the layer determining edges variable
+ var minimumLayer = this.SOURCESCANSTARTRANK;
+
+ for (var i = 0; i < layerDeterminingEdges.length; i++)
+ {
+ var internalEdge = layerDeterminingEdges[i];
+
+ if (internalEdge.temp[0] == 5270620)
+ {
+ // This edge has been scanned, get the layer of the
+ // node on the other end
+ var otherNode = internalEdge.source;
+ minimumLayer = Math.min(minimumLayer, otherNode.temp[0] - 1);
+ }
+ else
+ {
+ allEdgesScanned = false;
+
+ break;
+ }
+ }
+
+ // If all edge have been scanned, assign the layer, mark all
+ // edges in the other direction and remove from the nodes list
+ if (allEdgesScanned)
+ {
+ internalNode.temp[0] = minimumLayer;
+ this.maxRank = Math.min(this.maxRank, minimumLayer);
+
+ if (edgesToBeMarked != null)
+ {
+ for (var i = 0; i < edgesToBeMarked.length; i++)
+ {
+ var internalEdge = edgesToBeMarked[i];
+
+ // Assign unique stamp ( y/m/d/h )
+ internalEdge.temp[0] = 5270620;
+
+ // Add node on other end of edge to LinkedList of
+ // nodes to be analysed
+ var otherNode = internalEdge.target;
+
+ // Only add node if it hasn't been assigned a layer
+ if (otherNode.temp[0] == -1)
+ {
+ startNodes.push(otherNode);
+
+ // Mark this other node as neither being
+ // unassigned nor assigned so it isn't
+ // added to this list again, but it's
+ // layer isn't used in any calculation.
+ otherNode.temp[0] = -2;
+ }
+ }
+ }
+
+ startNodes.shift();
+ }
+ else
+ {
+ // Not all the edges have been scanned, get to the back of
+ // the class and put the dunces cap on
+ var removedCell = startNodes.shift();
+ startNodes.push(internalNode);
+
+ if (removedCell == internalNode && startNodes.length == 1)
+ {
+ // This is an error condition, we can't get out of
+ // this loop. It could happen for more than one node
+ // but that's a lot harder to detect. Log the error
+ // TODO make log comment
+ break;
+ }
+ }
+ }
+
+ // Normalize the ranks down from their large starting value to place
+ // at least 1 sink on layer 0
+ for (var key in this.vertexMapper)
+ {
+ var internalNode = this.vertexMapper[key];
+ // Mark the node as not having had a layer assigned
+ internalNode.temp[0] -= this.maxRank;
+ }
+
+ // Tighten the rank 0 nodes as far as possible
+ for ( var i = 0; i < startNodesCopy.length; i++)
+ {
+ var internalNode = startNodesCopy[i];
+ var currentMaxLayer = 0;
+ var layerDeterminingEdges = internalNode.connectsAsSource;
+
+ for ( var j = 0; j < layerDeterminingEdges.length; j++)
+ {
+ var internalEdge = layerDeterminingEdges[j];
+ var otherNode = internalEdge.target;
+ internalNode.temp[0] = Math.max(currentMaxLayer,
+ otherNode.temp[0] + 1);
+ currentMaxLayer = internalNode.temp[0];
+ }
+ }
+
+ // Reset the maxRank to that which would be expected for a from-sink
+ // scan
+ this.maxRank = this.SOURCESCANSTARTRANK - this.maxRank;
+};
+
+/**
+ * Function: fixRanks
+ *
+ * Fixes the layer assignments to the values stored in the nodes. Also needs
+ * to create dummy nodes for edges that cross layers.
+ */
+mxGraphHierarchyModel.prototype.fixRanks = function()
+{
+ var rankList = [];
+ this.ranks = [];
+
+ for (var i = 0; i < this.maxRank + 1; i++)
+ {
+ rankList[i] = [];
+ this.ranks[i] = rankList[i];
+ }
+
+ // Perform a DFS to obtain an initial ordering for each rank.
+ // Without doing this you would end up having to process
+ // crossings for a standard tree.
+ var rootsArray = null;
+
+ if (this.roots != null)
+ {
+ var oldRootsArray = this.roots;
+ rootsArray = [];
+
+ for (var i = 0; i < oldRootsArray.length; i++)
+ {
+ var cell = oldRootsArray[i];
+ var cellId = mxCellPath.create(cell);
+ var internalNode = this.vertexMapper[cellId];
+ rootsArray[i] = internalNode;
+ }
+ }
+
+ this.visit(function(parent, node, edge, layer, seen)
+ {
+ if (seen == 0 && node.maxRank < 0 && node.minRank < 0)
+ {
+ rankList[node.temp[0]].push(node);
+ node.maxRank = node.temp[0];
+ node.minRank = node.temp[0];
+
+ // Set temp[0] to the nodes position in the rank
+ node.temp[0] = rankList[node.maxRank].length - 1;
+ }
+
+ if (parent != null && edge != null)
+ {
+ var parentToCellRankDifference = parent.maxRank - node.maxRank;
+
+ if (parentToCellRankDifference > 1)
+ {
+ // There are ranks in between the parent and current cell
+ edge.maxRank = parent.maxRank;
+ edge.minRank = node.maxRank;
+ edge.temp = [];
+ edge.x = [];
+ edge.y = [];
+
+ for (var i = edge.minRank + 1; i < edge.maxRank; i++)
+ {
+ // The connecting edge must be added to the
+ // appropriate ranks
+ rankList[i].push(edge);
+ edge.setGeneralPurposeVariable(i, rankList[i]
+ .length - 1);
+ }
+ }
+ }
+ }, rootsArray, false, null);
+};
+
+/**
+ * Function: visit
+ *
+ * A depth first search through the internal heirarchy model.
+ *
+ * Parameters:
+ *
+ * visitor - The visitor function pattern to be called for each node.
+ * trackAncestors - Whether or not the search is to keep track all nodes
+ * directly above this one in the search path.
+ */
+mxGraphHierarchyModel.prototype.visit = function(visitor, dfsRoots, trackAncestors, seenNodes)
+{
+ // Run dfs through on all roots
+ if (dfsRoots != null)
+ {
+ for (var i = 0; i < dfsRoots.length; i++)
+ {
+ var internalNode = dfsRoots[i];
+
+ if (internalNode != null)
+ {
+ if (seenNodes == null)
+ {
+ seenNodes = new Object();
+ }
+
+ if (trackAncestors)
+ {
+ // Set up hash code for root
+ internalNode.hashCode = [];
+ internalNode.hashCode[0] = this.dfsCount;
+ internalNode.hashCode[1] = i;
+ this.extendedDfs(null, internalNode, null, visitor, seenNodes,
+ internalNode.hashCode, i, 0);
+ }
+ else
+ {
+ this.dfs(null, internalNode, null, visitor, seenNodes, 0);
+ }
+ }
+ }
+
+ this.dfsCount++;
+ }
+};
+
+/**
+ * Function: dfs
+ *
+ * Performs a depth first search on the internal hierarchy model
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * visitor - the visitor pattern to be called for each node
+ * seen - a set of all nodes seen by this dfs a set of all of the
+ * ancestor node of the current node
+ * layer - the layer on the dfs tree ( not the same as the model ranks )
+ */
+mxGraphHierarchyModel.prototype.dfs = function(parent, root, connectingEdge, visitor, seen, layer)
+{
+ if (root != null)
+ {
+ var rootId = mxCellPath.create(root.cell);
+
+ if (seen[rootId] == null)
+ {
+ seen[rootId] = root;
+ visitor(parent, root, connectingEdge, layer, 0);
+
+ // Copy the connects as source list so that visitors
+ // can change the original for edge direction inversions
+ var outgoingEdges = root.connectsAsSource.slice();
+
+ for (var i = 0; i< outgoingEdges.length; i++)
+ {
+ var internalEdge = outgoingEdges[i];
+ var targetNode = internalEdge.target;
+
+ // Root check is O(|roots|)
+ this.dfs(root, targetNode, internalEdge, visitor, seen,
+ layer + 1);
+ }
+ }
+ else
+ {
+ // Use the int field to indicate this node has been seen
+ visitor(parent, root, connectingEdge, layer, 1);
+ }
+ }
+};
+
+/**
+ * Function: extendedDfs
+ *
+ * Performs a depth first search on the internal hierarchy model. This dfs
+ * extends the default version by keeping track of cells ancestors, but it
+ * should be only used when necessary because of it can be computationally
+ * intensive for deep searches.
+ *
+ * Parameters:
+ *
+ * parent - the parent internal node of the current internal node
+ * root - the current internal node
+ * connectingEdge - the internal edge connecting the internal node and the parent
+ * internal node, if any
+ * visitor - the visitor pattern to be called for each node
+ * seen - a set of all nodes seen by this dfs
+ * ancestors - the parent hash code
+ * childHash - the new hash code for this node
+ * layer - the layer on the dfs tree ( not the same as the model ranks )
+ */
+mxGraphHierarchyModel.prototype.extendedDfs = function(parent, root, connectingEdge, visitor, seen, ancestors, childHash, layer)
+{
+ // Explanation of custom hash set. Previously, the ancestors variable
+ // was passed through the dfs as a HashSet. The ancestors were copied
+ // into a new HashSet and when the new child was processed it was also
+ // added to the set. If the current node was in its ancestor list it
+ // meant there is a cycle in the graph and this information is passed
+ // to the visitor.visit() in the seen parameter. The HashSet clone was
+ // very expensive on CPU so a custom hash was developed using primitive
+ // types. temp[] couldn't be used so hashCode[] was added to each node.
+ // Each new child adds another int to the array, copying the prefix
+ // from its parent. Child of the same parent add different ints (the
+ // limit is therefore 2^32 children per parent...). If a node has a
+ // child with the hashCode already set then the child code is compared
+ // to the same portion of the current nodes array. If they match there
+ // is a loop.
+ // Note that the basic mechanism would only allow for 1 use of this
+ // functionality, so the root nodes have two ints. The second int is
+ // incremented through each node root and the first is incremented
+ // through each run of the dfs algorithm (therefore the dfs is not
+ // thread safe). The hash code of each node is set if not already set,
+ // or if the first int does not match that of the current run.
+ if (root != null)
+ {
+ if (parent != null)
+ {
+ // Form this nodes hash code if necessary, that is, if the
+ // hashCode variable has not been initialized or if the
+ // start of the parent hash code does not equal the start of
+ // this nodes hash code, indicating the code was set on a
+ // previous run of this dfs.
+ if (root.hashCode == null ||
+ root.hashCode[0] != parent.hashCode[0])
+ {
+ var hashCodeLength = parent.hashCode.length + 1;
+ root.hashCode = parent.hashCode.slice();
+ root.hashCode[hashCodeLength - 1] = childHash;
+ }
+ }
+
+ var rootId = mxCellPath.create(root.cell);
+
+ if (seen[rootId] == null)
+ {
+ seen[rootId] = root;
+ visitor(parent, root, connectingEdge, layer, 0);
+
+ // Copy the connects as source list so that visitors
+ // can change the original for edge direction inversions
+ var outgoingEdges = root.connectsAsSource.slice();
+
+ for (var i = 0; i < outgoingEdges.length; i++)
+ {
+ var internalEdge = outgoingEdges[i];
+ var targetNode = internalEdge.target;
+
+ // Root check is O(|roots|)
+ this.extendedDfs(root, targetNode, internalEdge, visitor, seen,
+ root.hashCode, i, layer + 1);
+ }
+ }
+ else
+ {
+ // Use the int field to indicate this node has been seen
+ visitor(parent, root, connectingEdge, layer, 1);
+ }
+ }
+};
diff --git a/src/js/layout/hierarchical/model/mxGraphHierarchyNode.js b/src/js/layout/hierarchical/model/mxGraphHierarchyNode.js
new file mode 100644
index 0000000..d901d57
--- /dev/null
+++ b/src/js/layout/hierarchical/model/mxGraphHierarchyNode.js
@@ -0,0 +1,210 @@
+/**
+ * $Id: mxGraphHierarchyNode.js,v 1.13 2012-06-12 20:24:58 david Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphHierarchyNode
+ *
+ * An abstraction of a hierarchical edge for the hierarchy layout
+ *
+ * Constructor: mxGraphHierarchyNode
+ *
+ * Constructs an internal node to represent the specified real graph cell
+ *
+ * Arguments:
+ *
+ * cell - the real graph cell this node represents
+ */
+function mxGraphHierarchyNode(cell)
+{
+ mxGraphAbstractHierarchyCell.apply(this, arguments);
+ this.cell = cell;
+};
+
+/**
+ * Extends mxGraphAbstractHierarchyCell.
+ */
+mxGraphHierarchyNode.prototype = new mxGraphAbstractHierarchyCell();
+mxGraphHierarchyNode.prototype.constructor = mxGraphHierarchyNode;
+
+/**
+ * Variable: cell
+ *
+ * The graph cell this object represents.
+ */
+mxGraphHierarchyNode.prototype.cell = null;
+
+/**
+ * Variable: connectsAsTarget
+ *
+ * Collection of hierarchy edges that have this node as a target
+ */
+mxGraphHierarchyNode.prototype.connectsAsTarget = [];
+
+/**
+ * Variable: connectsAsSource
+ *
+ * Collection of hierarchy edges that have this node as a source
+ */
+mxGraphHierarchyNode.prototype.connectsAsSource = [];
+
+/**
+ * Variable: hashCode
+ *
+ * Assigns a unique hashcode for each node. Used by the model dfs instead
+ * of copying HashSets
+ */
+mxGraphHierarchyNode.prototype.hashCode = false;
+
+/**
+ * Function: getRankValue
+ *
+ * Returns the integer value of the layer that this node resides in
+ */
+mxGraphHierarchyNode.prototype.getRankValue = function(layer)
+{
+ return this.maxRank;
+};
+
+/**
+ * Function: getNextLayerConnectedCells
+ *
+ * Returns the cells this cell connects to on the next layer up
+ */
+mxGraphHierarchyNode.prototype.getNextLayerConnectedCells = function(layer)
+{
+ if (this.nextLayerConnectedCells == null)
+ {
+ this.nextLayerConnectedCells = [];
+ this.nextLayerConnectedCells[0] = [];
+
+ for (var i = 0; i < this.connectsAsTarget.length; i++)
+ {
+ var edge = this.connectsAsTarget[i];
+
+ if (edge.maxRank == -1 || edge.maxRank == layer + 1)
+ {
+ // Either edge is not in any rank or
+ // no dummy nodes in edge, add node of other side of edge
+ this.nextLayerConnectedCells[0].push(edge.source);
+ }
+ else
+ {
+ // Edge spans at least two layers, add edge
+ this.nextLayerConnectedCells[0].push(edge);
+ }
+ }
+ }
+
+ return this.nextLayerConnectedCells[0];
+};
+
+/**
+ * Function: getPreviousLayerConnectedCells
+ *
+ * Returns the cells this cell connects to on the next layer down
+ */
+mxGraphHierarchyNode.prototype.getPreviousLayerConnectedCells = function(layer)
+{
+ if (this.previousLayerConnectedCells == null)
+ {
+ this.previousLayerConnectedCells = [];
+ this.previousLayerConnectedCells[0] = [];
+
+ for (var i = 0; i < this.connectsAsSource.length; i++)
+ {
+ var edge = this.connectsAsSource[i];
+
+ if (edge.minRank == -1 || edge.minRank == layer - 1)
+ {
+ // No dummy nodes in edge, add node of other side of edge
+ this.previousLayerConnectedCells[0].push(edge.target);
+ }
+ else
+ {
+ // Edge spans at least two layers, add edge
+ this.previousLayerConnectedCells[0].push(edge);
+ }
+ }
+ }
+
+ return this.previousLayerConnectedCells[0];
+};
+
+/**
+ * Function: isVertex
+ *
+ * Returns true.
+ */
+mxGraphHierarchyNode.prototype.isVertex = function()
+{
+ return true;
+};
+
+/**
+ * Function: getGeneralPurposeVariable
+ *
+ * Gets the value of temp for the specified layer
+ */
+mxGraphHierarchyNode.prototype.getGeneralPurposeVariable = function(layer)
+{
+ return this.temp[0];
+};
+
+/**
+ * Function: setGeneralPurposeVariable
+ *
+ * Set the value of temp for the specified layer
+ */
+mxGraphHierarchyNode.prototype.setGeneralPurposeVariable = function(layer, value)
+{
+ this.temp[0] = value;
+};
+
+/**
+ * Function: isAncestor
+ */
+mxGraphHierarchyNode.prototype.isAncestor = function(otherNode)
+{
+ // Firstly, the hash code of this node needs to be shorter than the
+ // other node
+ if (otherNode != null && this.hashCode != null && otherNode.hashCode != null
+ && this.hashCode.length < otherNode.hashCode.length)
+ {
+ if (this.hashCode == otherNode.hashCode)
+ {
+ return true;
+ }
+
+ if (this.hashCode == null || this.hashCode == null)
+ {
+ return false;
+ }
+
+ // Secondly, this hash code must match the start of the other
+ // node's hash code. Arrays.equals cannot be used here since
+ // the arrays are different length, and we do not want to
+ // perform another array copy.
+ for (var i = 0; i < this.hashCode.length; i++)
+ {
+ if (this.hashCode[i] != otherNode.hashCode[i])
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * Function: getCoreCell
+ *
+ * Gets the core vertex associated with this wrapper
+ */
+mxGraphHierarchyNode.prototype.getCoreCell = function()
+{
+ return this.cell;
+}; \ No newline at end of file
diff --git a/src/js/layout/hierarchical/mxHierarchicalLayout.js b/src/js/layout/hierarchical/mxHierarchicalLayout.js
new file mode 100644
index 0000000..6ce0e05
--- /dev/null
+++ b/src/js/layout/hierarchical/mxHierarchicalLayout.js
@@ -0,0 +1,623 @@
+/**
+ * $Id: mxHierarchicalLayout.js,v 1.30 2012-12-18 12:41:06 david Exp $
+ * Copyright (c) 2005-2012, JGraph Ltd
+ */
+/**
+ * Class: mxHierarchicalLayout
+ *
+ * A hierarchical layout algorithm.
+ *
+ * Constructor: mxHierarchicalLayout
+ *
+ * Constructs a new hierarchical layout algorithm.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * orientation - Optional constant that defines the orientation of this
+ * layout.
+ * deterministic - Optional boolean that specifies if this layout should be
+ * deterministic. Default is true.
+ */
+function mxHierarchicalLayout(graph, orientation, deterministic)
+{
+ mxGraphLayout.call(this, graph);
+ this.orientation = (orientation != null) ? orientation : mxConstants.DIRECTION_NORTH;
+ this.deterministic = (deterministic != null) ? deterministic : true;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxHierarchicalLayout.prototype = new mxGraphLayout();
+mxHierarchicalLayout.prototype.constructor = mxHierarchicalLayout;
+
+/**
+ * Variable: roots
+ *
+ * Holds the array of <mxGraphLayouts> that this layout contains.
+ */
+mxHierarchicalLayout.prototype.roots = null;
+
+/**
+ * Variable: resizeParent
+ *
+ * Specifies if the parent should be resized after the layout so that it
+ * contains all the child cells. Default is false. See also <parentBorder>.
+ */
+mxHierarchicalLayout.prototype.resizeParent = false;
+
+/**
+ * Variable: moveParent
+ *
+ * Specifies if the parent should be moved if <resizeParent> is enabled.
+ * Default is false.
+ */
+mxHierarchicalLayout.prototype.moveParent = false;
+
+/**
+ * Variable: parentBorder
+ *
+ * The border to be added around the children if the parent is to be
+ * resized using <resizeParent>. Default is 0.
+ */
+mxHierarchicalLayout.prototype.parentBorder = 0;
+
+/**
+ * Variable: intraCellSpacing
+ *
+ * The spacing buffer added between cells on the same layer. Default is 30.
+ */
+mxHierarchicalLayout.prototype.intraCellSpacing = 30;
+
+/**
+ * Variable: interRankCellSpacing
+ *
+ * The spacing buffer added between cell on adjacent layers. Default is 50.
+ */
+mxHierarchicalLayout.prototype.interRankCellSpacing = 50;
+
+/**
+ * Variable: interHierarchySpacing
+ *
+ * The spacing buffer between unconnected hierarchies. Default is 60.
+ */
+mxHierarchicalLayout.prototype.interHierarchySpacing = 60;
+
+/**
+ * Variable: parallelEdgeSpacing
+ *
+ * The distance between each parallel edge on each ranks for long edges
+ */
+mxHierarchicalLayout.prototype.parallelEdgeSpacing = 10;
+
+/**
+ * Variable: orientation
+ *
+ * The position of the root node(s) relative to the laid out graph in.
+ * Default is <mxConstants.DIRECTION_NORTH>.
+ */
+mxHierarchicalLayout.prototype.orientation = mxConstants.DIRECTION_NORTH;
+
+/**
+ * Variable: fineTuning
+ *
+ * Whether or not to perform local optimisations and iterate multiple times
+ * through the algorithm. Default is true.
+ */
+mxHierarchicalLayout.prototype.fineTuning = true;
+
+/**
+ *
+ * Variable: tightenToSource
+ *
+ * Whether or not to tighten the assigned ranks of vertices up towards
+ * the source cells.
+ */
+mxHierarchicalLayout.prototype.tightenToSource = true;
+
+/**
+ * Variable: disableEdgeStyle
+ *
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxHierarchicalLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Variable: promoteEdges
+ *
+ * Whether or not to promote edges that terminate on vertices with
+ * different but common ancestry to appear connected to the highest
+ * siblings in the ancestry chains
+ */
+mxHierarchicalLayout.prototype.promoteEdges = true;
+
+/**
+ * Variable: traverseAncestors
+ *
+ * Whether or not to navigate edges whose terminal vertices
+ * have different parents but are in the same ancestry chain
+ */
+mxHierarchicalLayout.prototype.traverseAncestors = true;
+
+/**
+ * Variable: model
+ *
+ * The internal <mxGraphHierarchyModel> formed of the layout.
+ */
+mxHierarchicalLayout.prototype.model = null;
+
+/**
+ * Function: getModel
+ *
+ * Returns the internal <mxGraphHierarchyModel> for this layout algorithm.
+ */
+mxHierarchicalLayout.prototype.getModel = function()
+{
+ return this.model;
+};
+
+/**
+ * Function: execute
+ *
+ * Executes the layout for the children of the specified parent.
+ *
+ * Parameters:
+ *
+ * parent - Parent <mxCell> that contains the children to be laid out.
+ * roots - Optional starting roots of the layout.
+ */
+mxHierarchicalLayout.prototype.execute = function(parent, roots)
+{
+ this.parent = parent;
+ var model = this.graph.model;
+
+ // If the roots are set and the parent is set, only
+ // use the roots that are some dependent of the that
+ // parent.
+ // If just the root are set, use them as-is
+ // If just the parent is set use it's immediate
+ // children as the initial set
+
+ if (roots == null && parent == null)
+ {
+ // TODO indicate the problem
+ return;
+ }
+
+ if (roots != null && parent != null)
+ {
+ var rootsCopy = [];
+
+ for (var i = 0; i < roots.length; i++)
+ {
+
+ if (model.isAncestor(parent, roots[i]))
+ {
+ rootsCopy.push(roots[i]);
+ }
+ }
+
+ this.roots = rootsCopy;
+ }
+ else
+ {
+ this.roots = roots;
+ }
+
+ model.beginUpdate();
+ try
+ {
+ this.run(parent);
+
+ if (this.resizeParent &&
+ !this.graph.isCellCollapsed(parent))
+ {
+ this.graph.updateGroupBounds([parent],
+ this.parentBorder, this.moveParent);
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+};
+
+/**
+ * Function: findRoots
+ *
+ * Returns all visible children in the given parent which do not have
+ * incoming edges. If the result is empty then the children with the
+ * maximum difference between incoming and outgoing edges are returned.
+ * This takes into account edges that are being promoted to the given
+ * root due to invisible children or collapsed cells.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose children should be checked.
+ * vertices - array of vertices to limit search to
+ */
+mxHierarchicalLayout.prototype.findRoots = function(parent, vertices)
+{
+ var roots = [];
+
+ if (parent != null && vertices != null)
+ {
+ var model = this.graph.model;
+ var best = null;
+ var maxDiff = -100000;
+
+ for (var i in vertices)
+ {
+ var cell = vertices[i];
+
+ if (model.isVertex(cell) && this.graph.isCellVisible(cell))
+ {
+ var conns = this.getEdges(cell);
+ var fanOut = 0;
+ var fanIn = 0;
+
+ for (var k = 0; k < conns.length; k++)
+ {
+ var src = this.graph.view.getVisibleTerminal(conns[k], true);
+
+ if (src == cell)
+ {
+ fanOut++;
+ }
+ else
+ {
+ fanIn++;
+ }
+ }
+
+ if (fanIn == 0 && fanOut > 0)
+ {
+ roots.push(cell);
+ }
+
+ var diff = fanOut - fanIn;
+
+ if (diff > maxDiff)
+ {
+ maxDiff = diff;
+ best = cell;
+ }
+ }
+ }
+
+ if (roots.length == 0 && best != null)
+ {
+ roots.push(best);
+ }
+ }
+
+ return roots;
+};
+
+/**
+ * Function: getEdges
+ *
+ * Returns the connected edges for the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose edges should be returned.
+ */
+mxHierarchicalLayout.prototype.getEdges = function(cell)
+{
+ var model = this.graph.model;
+ var edges = [];
+ var isCollapsed = this.graph.isCellCollapsed(cell);
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(cell, i);
+
+ if (isCollapsed || !this.graph.isCellVisible(child))
+ {
+ edges = edges.concat(model.getEdges(child, true, true));
+ }
+ }
+
+ edges = edges.concat(model.getEdges(cell, true, true));
+ var result = [];
+
+ for (var i = 0; i < edges.length; i++)
+ {
+ var state = this.graph.view.getState(edges[i]);
+
+ var source = (state != null) ? state.getVisibleTerminal(true) : this.graph.view.getVisibleTerminal(edges[i], true);
+ var target = (state != null) ? state.getVisibleTerminal(false) : this.graph.view.getVisibleTerminal(edges[i], false);
+
+ if ((source == target) || ((source != target) && ((target == cell && (this.parent == null || this.graph.isValidAncestor(source, this.parent, this.traverseAncestors))) ||
+ (source == cell && (this.parent == null ||
+ this.graph.isValidAncestor(target, this.parent, this.traverseAncestors))))))
+ {
+ result.push(edges[i]);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: run
+ *
+ * The API method used to exercise the layout upon the graph description
+ * and produce a separate description of the vertex position and edge
+ * routing changes made. It runs each stage of the layout that has been
+ * created.
+ */
+mxHierarchicalLayout.prototype.run = function(parent)
+{
+ // Separate out unconnected hierarchies
+ var hierarchyVertices = [];
+ var allVertexSet = [];
+
+ if (this.roots == null && parent != null)
+ {
+ var filledVertexSet = this.filterDescendants(parent);
+
+ this.roots = [];
+ var filledVertexSetEmpty = true;
+
+ // Poor man's isSetEmpty
+ for (var key in filledVertexSet)
+ {
+ if (filledVertexSet[key] != null)
+ {
+ filledVertexSetEmpty = false;
+ break;
+ }
+ }
+
+ while (!filledVertexSetEmpty)
+ {
+ var candidateRoots = this.findRoots(parent, filledVertexSet);
+
+ for (var i = 0; i < candidateRoots.length; i++)
+ {
+ var vertexSet = [];
+ hierarchyVertices.push(vertexSet);
+
+ this.traverse(candidateRoots[i], true, null, allVertexSet, vertexSet,
+ hierarchyVertices, filledVertexSet);
+ }
+
+ for (var i = 0; i < candidateRoots.length; i++)
+ {
+ this.roots.push(candidateRoots[i]);
+ }
+
+ filledVertexSetEmpty = true;
+
+ // Poor man's isSetEmpty
+ for (var key in filledVertexSet)
+ {
+ if (filledVertexSet[key] != null)
+ {
+ filledVertexSetEmpty = false;
+ break;
+ }
+ }
+ }
+ }
+ else
+ {
+ // Find vertex set as directed traversal from roots
+
+ for (var i = 0; i < roots.length; i++)
+ {
+ var vertexSet = [];
+ hierarchyVertices.push(vertexSet);
+
+ traverse(roots.get(i), true, null, allVertexSet, vertexSet,
+ hierarchyVertices, null);
+ }
+ }
+
+ // Iterate through the result removing parents who have children in this layout
+
+ // Perform a layout for each seperate hierarchy
+ // Track initial coordinate x-positioning
+ var initialX = 0;
+
+ for (var i = 0; i < hierarchyVertices.length; i++)
+ {
+ var vertexSet = hierarchyVertices[i];
+ var tmp = [];
+
+ for (var key in vertexSet)
+ {
+ tmp.push(vertexSet[key]);
+ }
+
+ this.model = new mxGraphHierarchyModel(this, tmp, this.roots,
+ parent, this.tightenToSource);
+
+ this.cycleStage(parent);
+ this.layeringStage();
+
+ this.crossingStage(parent);
+ initialX = this.placementStage(initialX, parent);
+ }
+};
+
+/**
+ * Function: filterDescendants
+ *
+ * Creates an array of descendant cells
+ */
+mxHierarchicalLayout.prototype.filterDescendants = function(cell)
+{
+ var model = this.graph.model;
+ var result = [];
+
+ if (model.isVertex(cell) && cell != this.parent && this.graph.isCellVisible(cell))
+ {
+ result.push(cell);
+ }
+
+ if (this.traverseAncestors || cell == this.parent
+ && this.graph.isCellVisible(cell))
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(cell, i);
+ var children = this.filterDescendants(child);
+
+ for (var j = 0; j < children.length; j++)
+ {
+ result[mxCellPath.create(children[j])] = children[j];
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ *
+ * Parameters:
+ *
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * allVertices - Array of cell paths for the visited cells.
+ */
+mxHierarchicalLayout.prototype.traverse = function(vertex, directed, edge, allVertices, currentComp,
+ hierarchyVertices, filledVertexSet)
+{
+ var view = this.graph.view;
+ var model = this.graph.model;
+
+ if (vertex != null && allVertices != null)
+ {
+ // Has this vertex been seen before in any traversal
+ // And if the filled vertex set is populated, only
+ // process vertices in that it contains
+ var vertexID = mxCellPath.create(vertex);
+
+ if ((allVertices[vertexID] == null)
+ && (filledVertexSet == null ? true : filledVertexSet[vertexID] != null))
+ {
+ if (currentComp[vertexID] == null)
+ {
+ currentComp[vertexID] = vertex;
+ }
+ if (allVertices[vertexID] == null)
+ {
+ allVertices[vertexID] = vertex;
+ }
+
+ delete filledVertexSet[vertexID];
+
+ var edgeCount = model.getEdgeCount(vertex);
+
+ if (edgeCount > 0)
+ {
+ for (var i = 0; i < edgeCount; i++)
+ {
+ var e = model.getEdgeAt(vertex, i);
+ var isSource = view.getVisibleTerminal(e, true) == vertex;
+
+ if (!directed || isSource)
+ {
+ var next = view.getVisibleTerminal(e, !isSource);
+ currentComp = this.traverse(next, directed, e, allVertices,
+ currentComp, hierarchyVertices,
+ filledVertexSet);
+ }
+ }
+ }
+ }
+ else
+ {
+ if (currentComp[vertexID] == null)
+ {
+ // We've seen this vertex before, but not in the current component
+ // This component and the one it's in need to be merged
+
+ for (var i = 0; i < hierarchyVertices.length; i++)
+ {
+ var comp = hierarchyVertices[i];
+
+ if (comp[vertexID] != null)
+ {
+ for (var key in currentComp)
+ {
+ comp[key] = currentComp[key];
+ }
+
+ // Remove the current component from the hierarchy set
+ hierarchyVertices.pop();
+ return comp;
+ }
+ }
+ }
+ }
+ }
+
+ return currentComp;
+};
+
+/**
+ * Function: cycleStage
+ *
+ * Executes the cycle stage using mxMinimumCycleRemover.
+ */
+mxHierarchicalLayout.prototype.cycleStage = function(parent)
+{
+ var cycleStage = new mxMinimumCycleRemover(this);
+ cycleStage.execute(parent);
+};
+
+/**
+ * Function: layeringStage
+ *
+ * Implements first stage of a Sugiyama layout.
+ */
+mxHierarchicalLayout.prototype.layeringStage = function()
+{
+ this.model.initialRank();
+ this.model.fixRanks();
+};
+
+/**
+ * Function: crossingStage
+ *
+ * Executes the crossing stage using mxMedianHybridCrossingReduction.
+ */
+mxHierarchicalLayout.prototype.crossingStage = function(parent)
+{
+ var crossingStage = new mxMedianHybridCrossingReduction(this);
+ crossingStage.execute(parent);
+};
+
+/**
+ * Function: placementStage
+ *
+ * Executes the placement stage using mxCoordinateAssignment.
+ */
+mxHierarchicalLayout.prototype.placementStage = function(initialX, parent)
+{
+ var placementStage = new mxCoordinateAssignment(this, this.intraCellSpacing,
+ this.interRankCellSpacing, this.orientation, initialX,
+ this.parallelEdgeSpacing);
+ placementStage.fineTuning = this.fineTuning;
+ placementStage.execute(parent);
+
+ return placementStage.limitX + this.interHierarchySpacing;
+};
diff --git a/src/js/layout/hierarchical/stage/mxCoordinateAssignment.js b/src/js/layout/hierarchical/stage/mxCoordinateAssignment.js
new file mode 100644
index 0000000..8b73ccf
--- /dev/null
+++ b/src/js/layout/hierarchical/stage/mxCoordinateAssignment.js
@@ -0,0 +1,1836 @@
+/**
+ * $Id: mxCoordinateAssignment.js,v 1.29 2012-06-21 14:28:09 david Exp $
+ * Copyright (c) 2005-2012, JGraph Ltd
+ */
+/**
+ * Class: mxCoordinateAssignment
+ *
+ * Sets the horizontal locations of node and edge dummy nodes on each layer.
+ * Uses median down and up weighings as well as heuristics to straighten edges as
+ * far as possible.
+ *
+ * Constructor: mxCoordinateAssignment
+ *
+ * Creates a coordinate assignment.
+ *
+ * Arguments:
+ *
+ * intraCellSpacing - the minimum buffer between cells on the same rank
+ * interRankCellSpacing - the minimum distance between cells on adjacent ranks
+ * orientation - the position of the root node(s) relative to the graph
+ * initialX - the leftmost coordinate node placement starts at
+ */
+function mxCoordinateAssignment(layout, intraCellSpacing, interRankCellSpacing,
+ orientation, initialX, parallelEdgeSpacing)
+{
+ this.layout = layout;
+ this.intraCellSpacing = intraCellSpacing;
+ this.interRankCellSpacing = interRankCellSpacing;
+ this.orientation = orientation;
+ this.initialX = initialX;
+ this.parallelEdgeSpacing = parallelEdgeSpacing;
+};
+
+var mxHierarchicalEdgeStyle =
+{
+ ORTHOGONAL: 1,
+ POLYLINE: 2,
+ STRAIGHT: 3
+};
+
+/**
+ * Extends mxHierarchicalLayoutStage.
+ */
+mxCoordinateAssignment.prototype = new mxHierarchicalLayoutStage();
+mxCoordinateAssignment.prototype.constructor = mxCoordinateAssignment;
+
+/**
+ * Variable: layout
+ *
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxCoordinateAssignment.prototype.layout = null;
+
+/**
+ * Variable: intraCellSpacing
+ *
+ * The minimum buffer between cells on the same rank. Default is 30.
+ */
+mxCoordinateAssignment.prototype.intraCellSpacing = 30;
+
+/**
+ * Variable: interRankCellSpacing
+ *
+ * The minimum distance between cells on adjacent ranks. Default is 10.
+ */
+mxCoordinateAssignment.prototype.interRankCellSpacing = 10;
+
+/**
+ * Variable: parallelEdgeSpacing
+ *
+ * The distance between each parallel edge on each ranks for long edges.
+ * Default is 10.
+ */
+mxCoordinateAssignment.prototype.parallelEdgeSpacing = 10;
+
+/**
+ * Variable: maxIterations
+ *
+ * The number of heuristic iterations to run. Default is 8.
+ */
+mxCoordinateAssignment.prototype.maxIterations = 8;
+
+/**
+ * Variable: prefHozEdgeSep
+ *
+ * The preferred horizontal distance between edges exiting a vertex
+ */
+mxCoordinateAssignment.prototype.prefHozEdgeSep = 5;
+
+/**
+ * Variable: prefVertEdgeOff
+ *
+ * The preferred vertical offset between edges exiting a vertex
+ */
+mxCoordinateAssignment.prototype.prefVertEdgeOff = 2;
+
+/**
+ * Variable: minEdgeJetty
+ *
+ * The minimum distance for an edge jetty from a vertex
+ */
+mxCoordinateAssignment.prototype.minEdgeJetty = 12;
+
+/**
+ * Variable: channelBuffer
+ *
+ * The size of the vertical buffer in the center of inter-rank channels
+ * where edge control points should not be placed
+ */
+mxCoordinateAssignment.prototype.channelBuffer = 4;
+
+/**
+ * Variable: jettyPositions
+ *
+ * Map of internal edges and (x,y) pair of positions of the start and end jetty
+ * for that edge where it connects to the source and target vertices.
+ * Note this should technically be a WeakHashMap, but since JS does not
+ * have an equivalent, housekeeping must be performed before using.
+ * i.e. check all edges are still in the model and clear the values.
+ * Note that the y co-ord is the offset of the jetty, not the
+ * absolute point
+ */
+mxCoordinateAssignment.prototype.jettyPositions = null;
+
+/**
+ * Variable: orientation
+ *
+ * The position of the root ( start ) node(s) relative to the rest of the
+ * laid out graph. Default is <mxConstants.DIRECTION_NORTH>.
+ */
+mxCoordinateAssignment.prototype.orientation = mxConstants.DIRECTION_NORTH;
+
+/**
+ * Variable: initialX
+ *
+ * The minimum x position node placement starts at
+ */
+mxCoordinateAssignment.prototype.initialX = null;
+
+/**
+ * Variable: limitX
+ *
+ * The maximum x value this positioning lays up to
+ */
+mxCoordinateAssignment.prototype.limitX = null;
+
+/**
+ * Variable: currentXDelta
+ *
+ * The sum of x-displacements for the current iteration
+ */
+mxCoordinateAssignment.prototype.currentXDelta = null;
+
+/**
+ * Variable: widestRank
+ *
+ * The rank that has the widest x position
+ */
+mxCoordinateAssignment.prototype.widestRank = null;
+
+/**
+ * Variable: rankTopY
+ *
+ * Internal cache of top-most values of Y for each rank
+ */
+mxCoordinateAssignment.prototype.rankTopY = null;
+
+/**
+ * Variable: rankBottomY
+ *
+ * Internal cache of bottom-most value of Y for each rank
+ */
+mxCoordinateAssignment.prototype.rankBottomY = null;
+
+/**
+ * Variable: widestRankValue
+ *
+ * The X-coordinate of the edge of the widest rank
+ */
+mxCoordinateAssignment.prototype.widestRankValue = null;
+
+/**
+ * Variable: rankWidths
+ *
+ * The width of all the ranks
+ */
+mxCoordinateAssignment.prototype.rankWidths = null;
+
+/**
+ * Variable: rankY
+ *
+ * The Y-coordinate of all the ranks
+ */
+mxCoordinateAssignment.prototype.rankY = null;
+
+/**
+ * Variable: fineTuning
+ *
+ * Whether or not to perform local optimisations and iterate multiple times
+ * through the algorithm. Default is true.
+ */
+mxCoordinateAssignment.prototype.fineTuning = true;
+
+/**
+ * Variable: edgeStyle
+ *
+ * The style to apply between cell layers to edge segments
+ */
+mxCoordinateAssignment.prototype.edgeStyle = mxHierarchicalEdgeStyle.POLYLINE;
+
+/**
+ * Variable: nextLayerConnectedCache
+ *
+ * A store of connections to the layer above for speed
+ */
+mxCoordinateAssignment.prototype.nextLayerConnectedCache = null;
+
+/**
+ * Variable: previousLayerConnectedCache
+ *
+ * A store of connections to the layer below for speed
+ */
+mxCoordinateAssignment.prototype.previousLayerConnectedCache = null;
+
+/**
+ * Variable: groupPadding
+ *
+ * Padding added to resized parents
+ */
+mxCoordinateAssignment.prototype.groupPadding = 10;
+
+/**
+ * Utility method to display current positions
+ */
+mxCoordinateAssignment.prototype.printStatus = function()
+{
+ var model = this.layout.getModel();
+ mxLog.show();
+
+ mxLog.writeln('======Coord assignment debug=======');
+
+ for (var j = 0; j < model.ranks.length; j++)
+ {
+ mxLog.write('Rank ', j, ' : ' );
+ var rank = model.ranks[j];
+
+ for (var k = 0; k < rank.length; k++)
+ {
+ var cell = rank[k];
+
+ mxLog.write(cell.getGeneralPurposeVariable(j), ' ');
+ }
+ mxLog.writeln();
+ }
+
+ mxLog.writeln('====================================');
+};
+
+/**
+ * Function: execute
+ *
+ * A basic horizontal coordinate assignment algorithm
+ */
+mxCoordinateAssignment.prototype.execute = function(parent)
+{
+ this.jettyPositions = [];
+ var model = this.layout.getModel();
+ this.currentXDelta = 0.0;
+
+ this.initialCoords(this.layout.getGraph(), model);
+
+// this.printStatus();
+
+ if (this.fineTuning)
+ {
+ this.minNode(model);
+ }
+
+ var bestXDelta = 100000000.0;
+
+ if (this.fineTuning)
+ {
+ for (var i = 0; i < this.maxIterations; i++)
+ {
+// this.printStatus();
+
+ // Median Heuristic
+ if (i != 0)
+ {
+ this.medianPos(i, model);
+ this.minNode(model);
+ }
+
+ // if the total offset is less for the current positioning,
+ // there are less heavily angled edges and so the current
+ // positioning is used
+ if (this.currentXDelta < bestXDelta)
+ {
+ for (var j = 0; j < model.ranks.length; j++)
+ {
+ var rank = model.ranks[j];
+
+ for (var k = 0; k < rank.length; k++)
+ {
+ var cell = rank[k];
+ cell.setX(j, cell.getGeneralPurposeVariable(j));
+ }
+ }
+
+ bestXDelta = this.currentXDelta;
+ }
+ else
+ {
+ // Restore the best positions
+ for (var j = 0; j < model.ranks.length; j++)
+ {
+ var rank = model.ranks[j];
+
+ for (var k = 0; k < rank.length; k++)
+ {
+ var cell = rank[k];
+ cell.setGeneralPurposeVariable(j, cell.getX(j));
+ }
+ }
+ }
+
+ this.minPath(this.layout.getGraph(), model);
+
+ this.currentXDelta = 0;
+ }
+ }
+
+ this.setCellLocations(this.layout.getGraph(), model);
+};
+
+/**
+ * Function: minNode
+ *
+ * Performs one median positioning sweep in both directions
+ */
+mxCoordinateAssignment.prototype.minNode = function(model)
+{
+ // Queue all nodes
+ var nodeList = [];
+
+ // Need to be able to map from cell to cellWrapper
+ var map = [];
+ var rank = [];
+
+ for (var i = 0; i <= model.maxRank; i++)
+ {
+ rank[i] = model.ranks[i];
+
+ for (var j = 0; j < rank[i].length; j++)
+ {
+ // Use the weight to store the rank and visited to store whether
+ // or not the cell is in the list
+ var node = rank[i][j];
+ var nodeWrapper = new WeightedCellSorter(node, i);
+ nodeWrapper.rankIndex = j;
+ nodeWrapper.visited = true;
+ nodeList.push(nodeWrapper);
+
+ var cellId = mxCellPath.create(node.getCoreCell());
+ map[cellId] = nodeWrapper;
+ }
+ }
+
+ // Set a limit of the maximum number of times we will access the queue
+ // in case a loop appears
+ var maxTries = nodeList.length * 10;
+ var count = 0;
+
+ // Don't move cell within this value of their median
+ var tolerance = 1;
+
+ while (nodeList.length > 0 && count <= maxTries)
+ {
+ var cellWrapper = nodeList.shift();
+ var cell = cellWrapper.cell;
+
+ var rankValue = cellWrapper.weightedValue;
+ var rankIndex = parseInt(cellWrapper.rankIndex);
+
+ var nextLayerConnectedCells = cell.getNextLayerConnectedCells(rankValue);
+ var previousLayerConnectedCells = cell.getPreviousLayerConnectedCells(rankValue);
+
+ var numNextLayerConnected = nextLayerConnectedCells.length;
+ var numPreviousLayerConnected = previousLayerConnectedCells.length;
+
+ var medianNextLevel = this.medianXValue(nextLayerConnectedCells,
+ rankValue + 1);
+ var medianPreviousLevel = this.medianXValue(previousLayerConnectedCells,
+ rankValue - 1);
+
+ var numConnectedNeighbours = numNextLayerConnected
+ + numPreviousLayerConnected;
+ var currentPosition = cell.getGeneralPurposeVariable(rankValue);
+ var cellMedian = currentPosition;
+
+ if (numConnectedNeighbours > 0)
+ {
+ cellMedian = (medianNextLevel * numNextLayerConnected + medianPreviousLevel
+ * numPreviousLayerConnected)
+ / numConnectedNeighbours;
+ }
+
+ // Flag storing whether or not position has changed
+ var positionChanged = false;
+
+ if (cellMedian < currentPosition - tolerance)
+ {
+ if (rankIndex == 0)
+ {
+ cell.setGeneralPurposeVariable(rankValue, cellMedian);
+ positionChanged = true;
+ }
+ else
+ {
+ var leftCell = rank[rankValue][rankIndex - 1];
+ var leftLimit = leftCell
+ .getGeneralPurposeVariable(rankValue);
+ leftLimit = leftLimit + leftCell.width / 2
+ + this.intraCellSpacing + cell.width / 2;
+
+ if (leftLimit < cellMedian)
+ {
+ cell.setGeneralPurposeVariable(rankValue, cellMedian);
+ positionChanged = true;
+ }
+ else if (leftLimit < cell
+ .getGeneralPurposeVariable(rankValue)
+ - tolerance)
+ {
+ cell.setGeneralPurposeVariable(rankValue, leftLimit);
+ positionChanged = true;
+ }
+ }
+ }
+ else if (cellMedian > currentPosition + tolerance)
+ {
+ var rankSize = rank[rankValue].length;
+
+ if (rankIndex == rankSize - 1)
+ {
+ cell.setGeneralPurposeVariable(rankValue, cellMedian);
+ positionChanged = true;
+ }
+ else
+ {
+ var rightCell = rank[rankValue][rankIndex + 1];
+ var rightLimit = rightCell
+ .getGeneralPurposeVariable(rankValue);
+ rightLimit = rightLimit - rightCell.width / 2
+ - this.intraCellSpacing - cell.width / 2;
+
+ if (rightLimit > cellMedian)
+ {
+ cell.setGeneralPurposeVariable(rankValue, cellMedian);
+ positionChanged = true;
+ }
+ else if (rightLimit > cell
+ .getGeneralPurposeVariable(rankValue)
+ + tolerance)
+ {
+ cell.setGeneralPurposeVariable(rankValue, rightLimit);
+ positionChanged = true;
+ }
+ }
+ }
+
+ if (positionChanged)
+ {
+ // Add connected nodes to map and list
+ for (var i = 0; i < nextLayerConnectedCells.length; i++)
+ {
+ var connectedCell = nextLayerConnectedCells[i];
+ var connectedCellId = mxCellPath.create(connectedCell.getCoreCell());
+ var connectedCellWrapper = map[connectedCellId];
+
+ if (connectedCellWrapper != null)
+ {
+ if (connectedCellWrapper.visited == false)
+ {
+ connectedCellWrapper.visited = true;
+ nodeList.push(connectedCellWrapper);
+ }
+ }
+ }
+
+ // Add connected nodes to map and list
+ for (var i = 0; i < previousLayerConnectedCells.length; i++)
+ {
+ var connectedCell = previousLayerConnectedCells[i];
+ var connectedCellId = mxCellPath.create(connectedCell.getCoreCell());
+ var connectedCellWrapper = map[connectedCellId];
+
+ if (connectedCellWrapper != null)
+ {
+ if (connectedCellWrapper.visited == false)
+ {
+ connectedCellWrapper.visited = true;
+ nodeList.push(connectedCellWrapper);
+ }
+ }
+ }
+ }
+
+ cellWrapper.visited = false;
+ count++;
+ }
+};
+
+/**
+ * Function: medianPos
+ *
+ * Performs one median positioning sweep in one direction
+ *
+ * Parameters:
+ *
+ * i - the iteration of the whole process
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.medianPos = function(i, model)
+{
+ // Reverse sweep direction each time through this method
+ var downwardSweep = (i % 2 == 0);
+
+ if (downwardSweep)
+ {
+ for (var j = model.maxRank; j > 0; j--)
+ {
+ this.rankMedianPosition(j - 1, model, j);
+ }
+ }
+ else
+ {
+ for (var j = 0; j < model.maxRank - 1; j++)
+ {
+ this.rankMedianPosition(j + 1, model, j);
+ }
+ }
+};
+
+/**
+ * Function: rankMedianPosition
+ *
+ * Performs median minimisation over one rank.
+ *
+ * Parameters:
+ *
+ * rankValue - the layer number of this rank
+ * model - an internal model of the hierarchical layout
+ * nextRankValue - the layer number whose connected cels are to be laid out
+ * relative to
+ */
+mxCoordinateAssignment.prototype.rankMedianPosition = function(rankValue, model, nextRankValue)
+{
+ var rank = model.ranks[rankValue];
+
+ // Form an array of the order in which the cell are to be processed
+ // , the order is given by the weighted sum of the in or out edges,
+ // depending on whether we're travelling up or down the hierarchy.
+ var weightedValues = [];
+ var cellMap = [];
+
+ for (var i = 0; i < rank.length; i++)
+ {
+ var currentCell = rank[i];
+ weightedValues[i] = new WeightedCellSorter();
+ weightedValues[i].cell = currentCell;
+ weightedValues[i].rankIndex = i;
+ var currentCellId = mxCellPath.create(currentCell.getCoreCell());
+ cellMap[currentCellId] = weightedValues[i];
+ var nextLayerConnectedCells = null;
+
+ if (nextRankValue < rankValue)
+ {
+ nextLayerConnectedCells = currentCell
+ .getPreviousLayerConnectedCells(rankValue);
+ }
+ else
+ {
+ nextLayerConnectedCells = currentCell
+ .getNextLayerConnectedCells(rankValue);
+ }
+
+ // Calculate the weighing based on this node type and those this
+ // node is connected to on the next layer
+ weightedValues[i].weightedValue = this.calculatedWeightedValue(
+ currentCell, nextLayerConnectedCells);
+ }
+
+ weightedValues.sort(WeightedCellSorter.prototype.compare);
+
+ // Set the new position of each node within the rank using
+ // its temp variable
+
+ for (var i = 0; i < weightedValues.length; i++)
+ {
+ var numConnectionsNextLevel = 0;
+ var cell = weightedValues[i].cell;
+ var nextLayerConnectedCells = null;
+ var medianNextLevel = 0;
+
+ if (nextRankValue < rankValue)
+ {
+ nextLayerConnectedCells = cell.getPreviousLayerConnectedCells(
+ rankValue).slice();
+ }
+ else
+ {
+ nextLayerConnectedCells = cell.getNextLayerConnectedCells(
+ rankValue).slice();
+ }
+
+ if (nextLayerConnectedCells != null)
+ {
+ numConnectionsNextLevel = nextLayerConnectedCells.length;
+
+ if (numConnectionsNextLevel > 0)
+ {
+ medianNextLevel = this.medianXValue(nextLayerConnectedCells,
+ nextRankValue);
+ }
+ else
+ {
+ // For case of no connections on the next level set the
+ // median to be the current position and try to be
+ // positioned there
+ medianNextLevel = cell.getGeneralPurposeVariable(rankValue);
+ }
+ }
+
+ var leftBuffer = 0.0;
+ var leftLimit = -100000000.0;
+
+ for (var j = weightedValues[i].rankIndex - 1; j >= 0;)
+ {
+ var rankId = mxCellPath.create(rank[j].getCoreCell());
+ var weightedValue = cellMap[rankId];
+
+ if (weightedValue != null)
+ {
+ var leftCell = weightedValue.cell;
+
+ if (weightedValue.visited)
+ {
+ // The left limit is the right hand limit of that
+ // cell plus any allowance for unallocated cells
+ // in-between
+ leftLimit = leftCell
+ .getGeneralPurposeVariable(rankValue)
+ + leftCell.width
+ / 2.0
+ + this.intraCellSpacing
+ + leftBuffer + cell.width / 2.0;
+ j = -1;
+ }
+ else
+ {
+ leftBuffer += leftCell.width + this.intraCellSpacing;
+ j--;
+ }
+ }
+ }
+
+ var rightBuffer = 0.0;
+ var rightLimit = 100000000.0;
+
+ for (var j = weightedValues[i].rankIndex + 1; j < weightedValues.length;)
+ {
+ var rankId = mxCellPath.create(rank[j].getCoreCell());
+ var weightedValue = cellMap[rankId];
+
+ if (weightedValue != null)
+ {
+ var rightCell = weightedValue.cell;
+
+ if (weightedValue.visited)
+ {
+ // The left limit is the right hand limit of that
+ // cell plus any allowance for unallocated cells
+ // in-between
+ rightLimit = rightCell
+ .getGeneralPurposeVariable(rankValue)
+ - rightCell.width
+ / 2.0
+ - this.intraCellSpacing
+ - rightBuffer - cell.width / 2.0;
+ j = weightedValues.length;
+ }
+ else
+ {
+ rightBuffer += rightCell.width + this.intraCellSpacing;
+ j++;
+ }
+ }
+ }
+
+ if (medianNextLevel >= leftLimit && medianNextLevel <= rightLimit)
+ {
+ cell.setGeneralPurposeVariable(rankValue, medianNextLevel);
+ }
+ else if (medianNextLevel < leftLimit)
+ {
+ // Couldn't place at median value, place as close to that
+ // value as possible
+ cell.setGeneralPurposeVariable(rankValue, leftLimit);
+ this.currentXDelta += leftLimit - medianNextLevel;
+ }
+ else if (medianNextLevel > rightLimit)
+ {
+ // Couldn't place at median value, place as close to that
+ // value as possible
+ cell.setGeneralPurposeVariable(rankValue, rightLimit);
+ this.currentXDelta += medianNextLevel - rightLimit;
+ }
+
+ weightedValues[i].visited = true;
+ }
+};
+
+/**
+ * Function: calculatedWeightedValue
+ *
+ * Calculates the priority the specified cell has based on the type of its
+ * cell and the cells it is connected to on the next layer
+ *
+ * Parameters:
+ *
+ * currentCell - the cell whose weight is to be calculated
+ * collection - the cells the specified cell is connected to
+ */
+mxCoordinateAssignment.prototype.calculatedWeightedValue = function(currentCell, collection)
+{
+ var totalWeight = 0;
+
+ for (var i = 0; i < collection.length; i++)
+ {
+ var cell = collection[i];
+
+ if (currentCell.isVertex() && cell.isVertex())
+ {
+ totalWeight++;
+ }
+ else if (currentCell.isEdge() && cell.isEdge())
+ {
+ totalWeight += 8;
+ }
+ else
+ {
+ totalWeight += 2;
+ }
+ }
+
+ return totalWeight;
+};
+
+/**
+ * Function: medianXValue
+ *
+ * Calculates the median position of the connected cell on the specified
+ * rank
+ *
+ * Parameters:
+ *
+ * connectedCells - the cells the candidate connects to on this level
+ * rankValue - the layer number of this rank
+ */
+mxCoordinateAssignment.prototype.medianXValue = function(connectedCells, rankValue)
+{
+ if (connectedCells.length == 0)
+ {
+ return 0;
+ }
+
+ var medianValues = [];
+
+ for (var i = 0; i < connectedCells.length; i++)
+ {
+ medianValues[i] = connectedCells[i].getGeneralPurposeVariable(rankValue);
+ }
+
+ medianValues.sort(function(a,b){return a - b;});
+
+ if (connectedCells.length % 2 == 1)
+ {
+ // For odd numbers of adjacent vertices return the median
+ return medianValues[Math.floor(connectedCells.length / 2)];
+ }
+ else
+ {
+ var medianPoint = connectedCells.length / 2;
+ var leftMedian = medianValues[medianPoint - 1];
+ var rightMedian = medianValues[medianPoint];
+
+ return ((leftMedian + rightMedian) / 2);
+ }
+};
+
+/**
+ * Function: initialCoords
+ *
+ * Sets up the layout in an initial positioning. The ranks are all centered
+ * as much as possible along the middle vertex in each rank. The other cells
+ * are then placed as close as possible on either side.
+ *
+ * Parameters:
+ *
+ * facade - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.initialCoords = function(facade, model)
+{
+ this.calculateWidestRank(facade, model);
+
+ // Sweep up and down from the widest rank
+ for (var i = this.widestRank; i >= 0; i--)
+ {
+ if (i < model.maxRank)
+ {
+ this.rankCoordinates(i, facade, model);
+ }
+ }
+
+ for (var i = this.widestRank+1; i <= model.maxRank; i++)
+ {
+ if (i > 0)
+ {
+ this.rankCoordinates(i, facade, model);
+ }
+ }
+};
+
+/**
+ * Function: rankCoordinates
+ *
+ * Sets up the layout in an initial positioning. All the first cells in each
+ * rank are moved to the left and the rest of the rank inserted as close
+ * together as their size and buffering permits. This method works on just
+ * the specified rank.
+ *
+ * Parameters:
+ *
+ * rankValue - the current rank being processed
+ * graph - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.rankCoordinates = function(rankValue, graph, model)
+{
+ var rank = model.ranks[rankValue];
+ var maxY = 0.0;
+ var localX = this.initialX + (this.widestRankValue - this.rankWidths[rankValue])
+ / 2;
+
+ // Store whether or not any of the cells' bounds were unavailable so
+ // to only issue the warning once for all cells
+ var boundsWarning = false;
+
+ for (var i = 0; i < rank.length; i++)
+ {
+ var node = rank[i];
+
+ if (node.isVertex())
+ {
+ var bounds = this.layout.getVertexBounds(node.cell);
+
+ if (bounds != null)
+ {
+ if (this.orientation == mxConstants.DIRECTION_NORTH ||
+ this.orientation == mxConstants.DIRECTION_SOUTH)
+ {
+ node.width = bounds.width;
+ node.height = bounds.height;
+ }
+ else
+ {
+ node.width = bounds.height;
+ node.height = bounds.width;
+ }
+ }
+ else
+ {
+ boundsWarning = true;
+ }
+
+ maxY = Math.max(maxY, node.height);
+ }
+ else if (node.isEdge())
+ {
+ // The width is the number of additional parallel edges
+ // time the parallel edge spacing
+ var numEdges = 1;
+
+ if (node.edges != null)
+ {
+ numEdges = node.edges.length;
+ }
+ else
+ {
+ mxLog.warn('edge.edges is null');
+ }
+
+ node.width = (numEdges - 1) * this.parallelEdgeSpacing;
+ }
+
+ // Set the initial x-value as being the best result so far
+ localX += node.width / 2.0;
+ node.setX(rankValue, localX);
+ node.setGeneralPurposeVariable(rankValue, localX);
+ localX += node.width / 2.0;
+ localX += this.intraCellSpacing;
+ }
+
+ if (boundsWarning == true)
+ {
+ mxLog.warn('At least one cell has no bounds');
+ }
+};
+
+/**
+ * Function: calculateWidestRank
+ *
+ * Calculates the width rank in the hierarchy. Also set the y value of each
+ * rank whilst performing the calculation
+ *
+ * Parameters:
+ *
+ * graph - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.calculateWidestRank = function(graph, model)
+{
+ // Starting y co-ordinate
+ var y = -this.interRankCellSpacing;
+
+ // Track the widest cell on the last rank since the y
+ // difference depends on it
+ var lastRankMaxCellHeight = 0.0;
+ this.rankWidths = [];
+ this.rankY = [];
+
+ for (var rankValue = model.maxRank; rankValue >= 0; rankValue--)
+ {
+ // Keep track of the widest cell on this rank
+ var maxCellHeight = 0.0;
+ var rank = model.ranks[rankValue];
+ var localX = this.initialX;
+
+ // Store whether or not any of the cells' bounds were unavailable so
+ // to only issue the warning once for all cells
+ var boundsWarning = false;
+
+ for (var i = 0; i < rank.length; i++)
+ {
+ var node = rank[i];
+
+ if (node.isVertex())
+ {
+ var bounds = this.layout.getVertexBounds(node.cell);
+
+ if (bounds != null)
+ {
+ if (this.orientation == mxConstants.DIRECTION_NORTH ||
+ this.orientation == mxConstants.DIRECTION_SOUTH)
+ {
+ node.width = bounds.width;
+ node.height = bounds.height;
+ }
+ else
+ {
+ node.width = bounds.height;
+ node.height = bounds.width;
+ }
+ }
+ else
+ {
+ boundsWarning = true;
+ }
+
+ maxCellHeight = Math.max(maxCellHeight, node.height);
+ }
+ else if (node.isEdge())
+ {
+ // The width is the number of additional parallel edges
+ // time the parallel edge spacing
+ var numEdges = 1;
+
+ if (node.edges != null)
+ {
+ numEdges = node.edges.length;
+ }
+ else
+ {
+ mxLog.warn('edge.edges is null');
+ }
+
+ node.width = (numEdges - 1) * this.parallelEdgeSpacing;
+ }
+
+ // Set the initial x-value as being the best result so far
+ localX += node.width / 2.0;
+ node.setX(rankValue, localX);
+ node.setGeneralPurposeVariable(rankValue, localX);
+ localX += node.width / 2.0;
+ localX += this.intraCellSpacing;
+
+ if (localX > this.widestRankValue)
+ {
+ this.widestRankValue = localX;
+ this.widestRank = rankValue;
+ }
+
+ this.rankWidths[rankValue] = localX;
+ }
+
+ if (boundsWarning == true)
+ {
+ mxLog.warn('At least one cell has no bounds');
+ }
+
+ this.rankY[rankValue] = y;
+ var distanceToNextRank = maxCellHeight / 2.0
+ + lastRankMaxCellHeight / 2.0 + this.interRankCellSpacing;
+ lastRankMaxCellHeight = maxCellHeight;
+
+ if (this.orientation == mxConstants.DIRECTION_NORTH ||
+ this.orientation == mxConstants.DIRECTION_WEST)
+ {
+ y += distanceToNextRank;
+ }
+ else
+ {
+ y -= distanceToNextRank;
+ }
+
+ for (var i = 0; i < rank.length; i++)
+ {
+ var cell = rank[i];
+ cell.setY(rankValue, y);
+ }
+ }
+};
+
+/**
+ * Function: minPath
+ *
+ * Straightens out chains of virtual nodes where possibleacade to those stored after this layout
+ * processing step has completed.
+ *
+ * Parameters:
+ *
+ * graph - the facade describing the input graph
+ * model - an internal model of the hierarchical layout
+ */
+mxCoordinateAssignment.prototype.minPath = function(graph, model)
+{
+ // Work down and up each edge with at least 2 control points
+ // trying to straighten each one out. If the same number of
+ // straight segments are formed in both directions, the
+ // preferred direction used is the one where the final
+ // control points have the least offset from the connectable
+ // region of the terminating vertices
+ var edges = model.edgeMapper;
+
+ for (var key in edges)
+ {
+ var cell = edges[key];
+
+ if (cell.maxRank - cell.minRank - 1 < 1)
+ {
+ continue;
+ }
+
+ // At least two virtual nodes in the edge
+ // Check first whether the edge is already straight
+ var referenceX = cell
+ .getGeneralPurposeVariable(cell.minRank + 1);
+ var edgeStraight = true;
+ var refSegCount = 0;
+
+ for (var i = cell.minRank + 2; i < cell.maxRank; i++)
+ {
+ var x = cell.getGeneralPurposeVariable(i);
+
+ if (referenceX != x)
+ {
+ edgeStraight = false;
+ referenceX = x;
+ }
+ else
+ {
+ refSegCount++;
+ }
+ }
+
+ if (!edgeStraight)
+ {
+ var upSegCount = 0;
+ var downSegCount = 0;
+ var upXPositions = [];
+ var downXPositions = [];
+
+ var currentX = cell.getGeneralPurposeVariable(cell.minRank + 1);
+
+ for (var i = cell.minRank + 1; i < cell.maxRank - 1; i++)
+ {
+ // Attempt to straight out the control point on the
+ // next segment up with the current control point.
+ var nextX = cell.getX(i + 1);
+
+ if (currentX == nextX)
+ {
+ upXPositions[i - cell.minRank - 1] = currentX;
+ upSegCount++;
+ }
+ else if (this.repositionValid(model, cell, i + 1, currentX))
+ {
+ upXPositions[i - cell.minRank - 1] = currentX;
+ upSegCount++;
+ // Leave currentX at same value
+ }
+ else
+ {
+ upXPositions[i - cell.minRank - 1] = nextX;
+ currentX = nextX;
+ }
+ }
+
+ currentX = cell.getX(i);
+
+ for (var i = cell.maxRank - 1; i > cell.minRank + 1; i--)
+ {
+ // Attempt to straight out the control point on the
+ // next segment down with the current control point.
+ var nextX = cell.getX(i - 1);
+
+ if (currentX == nextX)
+ {
+ downXPositions[i - cell.minRank - 2] = currentX;
+ downSegCount++;
+ }
+ else if (this.repositionValid(model, cell, i - 1, currentX))
+ {
+ downXPositions[i - cell.minRank - 2] = currentX;
+ downSegCount++;
+ // Leave currentX at same value
+ }
+ else
+ {
+ downXPositions[i - cell.minRank - 2] = cell.getX(i-1);
+ currentX = nextX;
+ }
+ }
+
+ if (downSegCount > refSegCount || upSegCount > refSegCount)
+ {
+ if (downSegCount >= upSegCount)
+ {
+ // Apply down calculation values
+ for (var i = cell.maxRank - 2; i > cell.minRank; i--)
+ {
+ cell.setX(i, downXPositions[i - cell.minRank - 1]);
+ }
+ }
+ else if (upSegCount > downSegCount)
+ {
+ // Apply up calculation values
+ for (var i = cell.minRank + 2; i < cell.maxRank; i++)
+ {
+ cell.setX(i, upXPositions[i - cell.minRank - 2]);
+ }
+ }
+ else
+ {
+ // Neither direction provided a favourable result
+ // But both calculations are better than the
+ // existing solution, so apply the one with minimal
+ // offset to attached vertices at either end.
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Function: repositionValid
+ *
+ * Determines whether or not a node may be moved to the specified x
+ * position on the specified rank
+ *
+ * Parameters:
+ *
+ * model - the layout model
+ * cell - the cell being analysed
+ * rank - the layer of the cell
+ * position - the x position being sought
+ */
+mxCoordinateAssignment.prototype.repositionValid = function(model, cell, rank, position)
+{
+ var rankArray = model.ranks[rank];
+ var rankIndex = -1;
+
+ for (var i = 0; i < rankArray.length; i++)
+ {
+ if (cell == rankArray[i])
+ {
+ rankIndex = i;
+ break;
+ }
+ }
+
+ if (rankIndex < 0)
+ {
+ return false;
+ }
+
+ var currentX = cell.getGeneralPurposeVariable(rank);
+
+ if (position < currentX)
+ {
+ // Trying to move node to the left.
+ if (rankIndex == 0)
+ {
+ // Left-most node, can move anywhere
+ return true;
+ }
+
+ var leftCell = rankArray[rankIndex - 1];
+ var leftLimit = leftCell.getGeneralPurposeVariable(rank);
+ leftLimit = leftLimit + leftCell.width / 2
+ + this.intraCellSpacing + cell.width / 2;
+
+ if (leftLimit <= position)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+ else if (position > currentX)
+ {
+ // Trying to move node to the right.
+ if (rankIndex == rankArray.length - 1)
+ {
+ // Right-most node, can move anywhere
+ return true;
+ }
+
+ var rightCell = rankArray[rankIndex + 1];
+ var rightLimit = rightCell.getGeneralPurposeVariable(rank);
+ rightLimit = rightLimit - rightCell.width / 2
+ - this.intraCellSpacing - cell.width / 2;
+
+ if (rightLimit >= position)
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ return true;
+};
+
+/**
+ * Function: setCellLocations
+ *
+ * Sets the cell locations in the facade to those stored after this layout
+ * processing step has completed.
+ *
+ * Parameters:
+ *
+ * graph - the input graph
+ * model - the layout model
+ */
+mxCoordinateAssignment.prototype.setCellLocations = function(graph, model)
+{
+ this.rankTopY = [];
+ this.rankBottomY = [];
+
+ for (var i = 0; i < model.ranks.length; i++)
+ {
+ this.rankTopY[i] = Number.MAX_VALUE;
+ this.rankBottomY[i] = 0.0;
+ }
+
+ var parentsChanged = null;
+
+ if (this.layout.resizeParent)
+ {
+ parentsChanged = new Object();
+ }
+
+ var edges = model.edgeMapper;
+ var vertices = model.vertexMapper;
+
+ // Process vertices all first, since they define the lower and
+ // limits of each rank. Between these limits lie the channels
+ // where the edges can be routed across the graph
+
+ for (var key in vertices)
+ {
+ var vertex = vertices[key];
+ this.setVertexLocation(vertex);
+
+ if (this.layout.resizeParent)
+ {
+ var parent = graph.model.getParent(vertex.cell);
+ var id = mxCellPath.create(parent);
+
+ // Implements set semantic
+ if (parentsChanged[id] == null)
+ {
+ parentsChanged[id] = parent;
+ }
+ }
+ }
+
+ if (this.layout.resizeParent && parentsChanged != null)
+ {
+ this.adjustParents(parentsChanged);
+ }
+
+ // Post process edge styles. Needs the vertex locations set for initial
+ // values of the top and bottoms of each rank
+ if (this.edgeStyle == mxHierarchicalEdgeStyle.ORTHOGONAL
+ || this.edgeStyle == mxHierarchicalEdgeStyle.POLYLINE)
+ {
+ this.localEdgeProcessing(model);
+ }
+
+ for (var key in edges)
+ {
+ this.setEdgePosition(edges[key]);
+ }
+};
+
+/**
+ * Function: adjustParents
+ *
+ * Adjust parent cells whose child geometries have changed. The default
+ * implementation adjusts the group to just fit around the children with
+ * a padding.
+ */
+mxCoordinateAssignment.prototype.adjustParents = function(parentsChanged)
+{
+ var tmp = [];
+
+ for (var id in parentsChanged)
+ {
+ tmp.push(parentsChanged[id]);
+ }
+
+ this.layout.arrangeGroups(mxUtils.sortCells(tmp, true), this.groupPadding);
+};
+
+/**
+ * Function: localEdgeProcessing
+ *
+ * Separates the x position of edges as they connect to vertices
+ *
+ * Parameters:
+ *
+ * model - the layout model
+ */
+mxCoordinateAssignment.prototype.localEdgeProcessing = function(model)
+{
+ var edgeMapping = model.edgeMapper;
+
+ // Iterate through each vertex, look at the edges connected in
+ // both directions.
+ for (var rankIndex = 0; rankIndex < model.ranks.length; rankIndex++)
+ {
+ var rank = model.ranks[rankIndex];
+
+ for (var cellIndex = 0; cellIndex < rank.length; cellIndex++)
+ {
+ var cell = rank[cellIndex];
+
+ if (cell.isVertex())
+ {
+ var currentCells = cell.getPreviousLayerConnectedCells(rankIndex);
+
+ var currentRank = rankIndex - 1;
+
+ // Two loops, last connected cells, and next
+ for (var k = 0; k < 2; k++)
+ {
+ if (currentRank > -1
+ && currentRank < model.ranks.length
+ && currentCells != null
+ && currentCells.length > 0)
+ {
+ var sortedCells = [];
+
+ for (var j = 0; j < currentCells.length; j++)
+ {
+ var sorter = new WeightedCellSorter(
+ currentCells[j], currentCells[j].getX(currentRank));
+ sortedCells.push(sorter);
+ }
+
+ sortedCells.sort(WeightedCellSorter.prototype.compare);
+
+ var leftLimit = cell.x[0] - cell.width / 2;
+ var rightLimit = leftLimit + cell.width;
+
+ // Connected edge count starts at 1 to allow for buffer
+ // with edge of vertex
+ var connectedEdgeCount = 0;
+ var connectedEdgeGroupCount = 0;
+ var connectedEdges = [];
+ // Calculate width requirements for all connected edges
+ for (var j = 0; j < sortedCells.length; j++)
+ {
+ var innerCell = sortedCells[j].cell;
+ var connections;
+
+ if (innerCell.isVertex())
+ {
+ // Get the connecting edge
+ if (k == 0)
+ {
+ connections = cell.connectsAsSource;
+
+ }
+ else
+ {
+ connections = cell.connectsAsTarget;
+ }
+
+ for (var connIndex = 0; connIndex < connections.length; connIndex++)
+ {
+ if (connections[connIndex].source == innerCell
+ || connections[connIndex].target == innerCell)
+ {
+ connectedEdgeCount += connections[connIndex].edges
+ .length;
+ connectedEdgeGroupCount++;
+
+ connectedEdges.push(connections[connIndex]);
+ }
+ }
+ }
+ else
+ {
+ connectedEdgeCount += innerCell.edges.length;
+ connectedEdgeGroupCount++;
+ connectedEdges.push(innerCell);
+ }
+ }
+
+ var requiredWidth = (connectedEdgeCount + 1)
+ * this.prefHozEdgeSep;
+
+ // Add a buffer on the edges of the vertex if the edge count allows
+ if (cell.width > requiredWidth
+ + (2 * this.prefHozEdgeSep))
+ {
+ leftLimit += this.prefHozEdgeSep;
+ rightLimit -= this.prefHozEdgeSep;
+ }
+
+ var availableWidth = rightLimit - leftLimit;
+ var edgeSpacing = availableWidth / connectedEdgeCount;
+
+ var currentX = leftLimit + edgeSpacing / 2.0;
+ var currentYOffset = this.minEdgeJetty - this.prefVertEdgeOff;
+ var maxYOffset = 0;
+
+ for (var j = 0; j < connectedEdges.length; j++)
+ {
+ var numActualEdges = connectedEdges[j].edges
+ .length;
+ var edgeId = mxCellPath.create(connectedEdges[j].edges[0]);
+ var pos = this.jettyPositions[edgeId];
+
+ if (pos == null)
+ {
+ pos = [];
+ this.jettyPositions[edgeId] = pos;
+ }
+
+ if (j < connectedEdgeCount / 2)
+ {
+ currentYOffset += this.prefVertEdgeOff;
+ }
+ else if (j > connectedEdgeCount / 2)
+ {
+ currentYOffset -= this.prefVertEdgeOff;
+ }
+ // Ignore the case if equals, this means the second of 2
+ // jettys with the same y (even number of edges)
+
+ for (var m = 0; m < numActualEdges; m++)
+ {
+ pos[m * 4 + k * 2] = currentX;
+ currentX += edgeSpacing;
+ pos[m * 4 + k * 2 + 1] = currentYOffset;
+ }
+
+ maxYOffset = Math.max(maxYOffset,
+ currentYOffset);
+ }
+ }
+
+ currentCells = cell.getNextLayerConnectedCells(rankIndex);
+
+ currentRank = rankIndex + 1;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Function: setEdgePosition
+ *
+ * Fixes the control points
+ */
+mxCoordinateAssignment.prototype.setEdgePosition = function(cell)
+{
+ // For parallel edges we need to seperate out the points a
+ // little
+ var offsetX = 0;
+ // Only set the edge control points once
+
+ if (cell.temp[0] != 101207)
+ {
+ var maxRank = cell.maxRank;
+ var minRank = cell.minRank;
+
+ if (maxRank == minRank)
+ {
+ maxRank = cell.source.maxRank;
+ minRank = cell.target.minRank;
+ }
+
+ var parallelEdgeCount = 0;
+ var edgeId = mxCellPath.create(cell.edges[0]);
+ var jettys = this.jettyPositions[edgeId];
+
+ var source = cell.isReversed ? cell.target.cell : cell.source.cell;
+
+ for (var i = 0; i < cell.edges.length; i++)
+ {
+ var realEdge = cell.edges[i];
+ var realSource = this.layout.graph.view.getVisibleTerminal(realEdge, true);
+
+ //List oldPoints = graph.getPoints(realEdge);
+ var newPoints = [];
+
+ // Single length reversed edges end up with the jettys in the wrong
+ // places. Since single length edges only have jettys, not segment
+ // control points, we just say the edge isn't reversed in this section
+ var reversed = cell.isReversed;
+
+ if (realSource != source)
+ {
+ // The real edges include all core model edges and these can go
+ // in both directions. If the source of the hierarchical model edge
+ // isn't the source of the specific real edge in this iteration
+ // treat if as reversed
+ reversed = !reversed;
+ }
+
+ // First jetty of edge
+ if (jettys != null)
+ {
+ var arrayOffset = reversed ? 2 : 0;
+ var y = reversed ? this.rankTopY[minRank] : this.rankBottomY[maxRank];
+ var jetty = jettys[parallelEdgeCount * 4 + 1 + arrayOffset];
+
+ if (reversed)
+ {
+ jetty = -jetty;
+ }
+
+ y += jetty;
+ var x = jettys[parallelEdgeCount * 4 + arrayOffset];
+
+ if (this.orientation == mxConstants.DIRECTION_NORTH
+ || this.orientation == mxConstants.DIRECTION_SOUTH)
+ {
+ newPoints.push(new mxPoint(x, y));
+ }
+ else
+ {
+ newPoints.push(new mxPoint(y, x));
+ }
+ }
+
+ // Declare variables to define loop through edge points and
+ // change direction if edge is reversed
+
+ var loopStart = cell.x.length - 1;
+ var loopLimit = -1;
+ var loopDelta = -1;
+ var currentRank = cell.maxRank - 1;
+
+ if (reversed)
+ {
+ loopStart = 0;
+ loopLimit = cell.x.length;
+ loopDelta = 1;
+ currentRank = cell.minRank + 1;
+ }
+ // Reversed edges need the points inserted in
+ // reverse order
+ for (var j = loopStart; (cell.maxRank != cell.minRank) && j != loopLimit; j += loopDelta)
+ {
+ // The horizontal position in a vertical layout
+ var positionX = cell.x[j] + offsetX;
+
+ // Work out the vertical positions in a vertical layout
+ // in the edge buffer channels above and below this rank
+ var topChannelY = (this.rankTopY[currentRank] + this.rankBottomY[currentRank + 1]) / 2.0;
+ var bottomChannelY = (this.rankTopY[currentRank - 1] + this.rankBottomY[currentRank]) / 2.0;
+
+ if (reversed)
+ {
+ var tmp = topChannelY;
+ topChannelY = bottomChannelY;
+ bottomChannelY = tmp;
+ }
+
+ if (this.orientation == mxConstants.DIRECTION_NORTH ||
+ this.orientation == mxConstants.DIRECTION_SOUTH)
+ {
+ newPoints.push(new mxPoint(positionX, topChannelY));
+ newPoints.push(new mxPoint(positionX, bottomChannelY));
+ }
+ else
+ {
+ newPoints.push(new mxPoint(topChannelY, positionX));
+ newPoints.push(new mxPoint(bottomChannelY, positionX));
+ }
+
+ this.limitX = Math.max(this.limitX, positionX);
+ currentRank += loopDelta;
+ }
+
+ // Second jetty of edge
+ if (jettys != null)
+ {
+ var arrayOffset = reversed ? 2 : 0;
+ var rankY = reversed ? this.rankBottomY[maxRank] : this.rankTopY[minRank];
+ var jetty = jettys[parallelEdgeCount * 4 + 3 - arrayOffset];
+
+ if (reversed)
+ {
+ jetty = -jetty;
+ }
+ var y = rankY - jetty;
+ var x = jettys[parallelEdgeCount * 4 + 2 - arrayOffset];
+
+ if (this.orientation == mxConstants.DIRECTION_NORTH ||
+ this.orientation == mxConstants.DIRECTION_SOUTH)
+ {
+ newPoints.push(new mxPoint(x, y));
+ }
+ else
+ {
+ newPoints.push(new mxPoint(y, x));
+ }
+ }
+
+ if (cell.isReversed)
+ {
+ this.processReversedEdge(cell, realEdge);
+ }
+
+ this.layout.setEdgePoints(realEdge, newPoints);
+
+ // Increase offset so next edge is drawn next to
+ // this one
+ if (offsetX == 0.0)
+ {
+ offsetX = this.parallelEdgeSpacing;
+ }
+ else if (offsetX > 0)
+ {
+ offsetX = -offsetX;
+ }
+ else
+ {
+ offsetX = -offsetX + this.parallelEdgeSpacing;
+ }
+
+ parallelEdgeCount++;
+ }
+
+ cell.temp[0] = 101207;
+ }
+};
+
+
+/**
+ * Function: setVertexLocation
+ *
+ * Fixes the position of the specified vertex.
+ *
+ * Parameters:
+ *
+ * cell - the vertex to position
+ */
+mxCoordinateAssignment.prototype.setVertexLocation = function(cell)
+{
+ var realCell = cell.cell;
+ var positionX = cell.x[0] - cell.width / 2;
+ var positionY = cell.y[0] - cell.height / 2;
+
+ this.rankTopY[cell.minRank] = Math.min(this.rankTopY[cell.minRank], positionY);
+ this.rankBottomY[cell.minRank] = Math.max(this.rankBottomY[cell.minRank],
+ positionY + cell.height);
+
+ if (this.orientation == mxConstants.DIRECTION_NORTH ||
+ this.orientation == mxConstants.DIRECTION_SOUTH)
+ {
+ this.layout.setVertexLocation(realCell, positionX, positionY);
+ }
+ else
+ {
+ this.layout.setVertexLocation(realCell, positionY, positionX);
+ }
+
+ this.limitX = Math.max(this.limitX, positionX + cell.width);
+};
+
+/**
+ * Function: processReversedEdge
+ *
+ * Hook to add additional processing
+ *
+ * Parameters:
+ *
+ * edge - the hierarchical model edge
+ * realEdge - the real edge in the graph
+ */
+mxCoordinateAssignment.prototype.processReversedEdge = function(graph, model)
+{
+ // hook for subclassers
+};
+
+/**
+ * Class: WeightedCellSorter
+ *
+ * A utility class used to track cells whilst sorting occurs on the weighted
+ * sum of their connected edges. Does not violate (x.compareTo(y)==0) ==
+ * (x.equals(y))
+ *
+ * Constructor: WeightedCellSorter
+ *
+ * Constructs a new weighted cell sorted for the given cell and weight.
+ */
+function WeightedCellSorter(cell, weightedValue)
+{
+ this.cell = cell;
+ this.weightedValue = weightedValue;
+};
+
+/**
+ * Variable: weightedValue
+ *
+ * The weighted value of the cell stored.
+ */
+WeightedCellSorter.prototype.weightedValue = 0;
+
+/**
+ * Variable: nudge
+ *
+ * Whether or not to flip equal weight values.
+ */
+WeightedCellSorter.prototype.nudge = false;
+
+/**
+ * Variable: visited
+ *
+ * Whether or not this cell has been visited in the current assignment.
+ */
+WeightedCellSorter.prototype.visited = false;
+
+/**
+ * Variable: rankIndex
+ *
+ * The index this cell is in the model rank.
+ */
+WeightedCellSorter.prototype.rankIndex = null;
+
+/**
+ * Variable: cell
+ *
+ * The cell whose median value is being calculated.
+ */
+WeightedCellSorter.prototype.cell = null;
+
+/**
+ * Function: compare
+ *
+ * Compares two WeightedCellSorters.
+ */
+WeightedCellSorter.prototype.compare = function(a, b)
+{
+ if (a != null && b != null)
+ {
+ if (b.weightedValue > a.weightedValue)
+ {
+ return -1;
+ }
+ else if (b.weightedValue < a.weightedValue)
+ {
+ return 1;
+ }
+ else
+ {
+ if (b.nudge)
+ {
+ return -1;
+ }
+ else
+ {
+ return 1;
+ }
+ }
+ }
+ else
+ {
+ return 0;
+ }
+};
diff --git a/src/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js b/src/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js
new file mode 100644
index 0000000..2e635fc
--- /dev/null
+++ b/src/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js
@@ -0,0 +1,25 @@
+/**
+ * $Id: mxHierarchicalLayoutStage.js,v 1.8 2010-01-02 09:45:15 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxHierarchicalLayoutStage
+ *
+ * The specific layout interface for hierarchical layouts. It adds a
+ * <code>run</code> method with a parameter for the hierarchical layout model
+ * that is shared between the layout stages.
+ *
+ * Constructor: mxHierarchicalLayoutStage
+ *
+ * Constructs a new hierarchical layout stage.
+ */
+function mxHierarchicalLayoutStage() { };
+
+/**
+ * Function: execute
+ *
+ * Takes the graph detail and configuration information within the facade
+ * and creates the resulting laid out graph within that facade for further
+ * use.
+ */
+mxHierarchicalLayoutStage.prototype.execute = function(parent) { };
diff --git a/src/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js b/src/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js
new file mode 100644
index 0000000..997890e
--- /dev/null
+++ b/src/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js
@@ -0,0 +1,674 @@
+/**
+ * $Id: mxMedianHybridCrossingReduction.js,v 1.25 2012-06-07 11:16:41 david Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxMedianHybridCrossingReduction
+ *
+ * Sets the horizontal locations of node and edge dummy nodes on each layer.
+ * Uses median down and up weighings as well heuristic to straighten edges as
+ * far as possible.
+ *
+ * Constructor: mxMedianHybridCrossingReduction
+ *
+ * Creates a coordinate assignment.
+ *
+ * Arguments:
+ *
+ * intraCellSpacing - the minimum buffer between cells on the same rank
+ * interRankCellSpacing - the minimum distance between cells on adjacent ranks
+ * orientation - the position of the root node(s) relative to the graph
+ * initialX - the leftmost coordinate node placement starts at
+ */
+function mxMedianHybridCrossingReduction(layout)
+{
+ this.layout = layout;
+};
+
+/**
+ * Extends mxMedianHybridCrossingReduction.
+ */
+mxMedianHybridCrossingReduction.prototype = new mxHierarchicalLayoutStage();
+mxMedianHybridCrossingReduction.prototype.constructor = mxMedianHybridCrossingReduction;
+
+/**
+ * Variable: layout
+ *
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxMedianHybridCrossingReduction.prototype.layout = null;
+
+/**
+ * Variable: maxIterations
+ *
+ * The maximum number of iterations to perform whilst reducing edge
+ * crossings. Default is 24.
+ */
+mxMedianHybridCrossingReduction.prototype.maxIterations = 24;
+
+/**
+ * Variable: nestedBestRanks
+ *
+ * Stores each rank as a collection of cells in the best order found for
+ * each layer so far
+ */
+mxMedianHybridCrossingReduction.prototype.nestedBestRanks = null;
+
+/**
+ * Variable: currentBestCrossings
+ *
+ * The total number of crossings found in the best configuration so far
+ */
+mxMedianHybridCrossingReduction.prototype.currentBestCrossings = 0;
+
+/**
+ * Variable: iterationsWithoutImprovement
+ *
+ * The total number of crossings found in the best configuration so far
+ */
+mxMedianHybridCrossingReduction.prototype.iterationsWithoutImprovement = 0;
+
+/**
+ * Variable: maxNoImprovementIterations
+ *
+ * The total number of crossings found in the best configuration so far
+ */
+mxMedianHybridCrossingReduction.prototype.maxNoImprovementIterations = 2;
+
+/**
+ * Function: execute
+ *
+ * Performs a vertex ordering within ranks as described by Gansner et al
+ * 1993
+ */
+mxMedianHybridCrossingReduction.prototype.execute = function(parent)
+{
+ var model = this.layout.getModel();
+
+ // Stores initial ordering as being the best one found so far
+ this.nestedBestRanks = [];
+
+ for (var i = 0; i < model.ranks.length; i++)
+ {
+ this.nestedBestRanks[i] = model.ranks[i].slice();
+ }
+
+ var iterationsWithoutImprovement = 0;
+ var currentBestCrossings = this.calculateCrossings(model);
+
+ for (var i = 0; i < this.maxIterations &&
+ iterationsWithoutImprovement < this.maxNoImprovementIterations; i++)
+ {
+ this.weightedMedian(i, model);
+ this.transpose(i, model);
+ var candidateCrossings = this.calculateCrossings(model);
+
+ if (candidateCrossings < currentBestCrossings)
+ {
+ currentBestCrossings = candidateCrossings;
+ iterationsWithoutImprovement = 0;
+
+ // Store the current rankings as the best ones
+ for (var j = 0; j < this.nestedBestRanks.length; j++)
+ {
+ var rank = model.ranks[j];
+
+ for (var k = 0; k < rank.length; k++)
+ {
+ var cell = rank[k];
+ this.nestedBestRanks[j][cell.getGeneralPurposeVariable(j)] = cell;
+ }
+ }
+ }
+ else
+ {
+ // Increase count of iterations where we haven't improved the
+ // layout
+ iterationsWithoutImprovement++;
+
+ // Restore the best values to the cells
+ for (var j = 0; j < this.nestedBestRanks.length; j++)
+ {
+ var rank = model.ranks[j];
+
+ for (var k = 0; k < rank.length; k++)
+ {
+ var cell = rank[k];
+ cell.setGeneralPurposeVariable(j, k);
+ }
+ }
+ }
+
+ if (currentBestCrossings == 0)
+ {
+ // Do nothing further
+ break;
+ }
+ }
+
+ // Store the best rankings but in the model
+ var ranks = [];
+ var rankList = [];
+
+ for (var i = 0; i < model.maxRank + 1; i++)
+ {
+ rankList[i] = [];
+ ranks[i] = rankList[i];
+ }
+
+ for (var i = 0; i < this.nestedBestRanks.length; i++)
+ {
+ for (var j = 0; j < this.nestedBestRanks[i].length; j++)
+ {
+ rankList[i].push(this.nestedBestRanks[i][j]);
+ }
+ }
+
+ model.ranks = ranks;
+};
+
+
+/**
+ * Function: calculateCrossings
+ *
+ * Calculates the total number of edge crossing in the current graph.
+ * Returns the current number of edge crossings in the hierarchy graph
+ * model in the current candidate layout
+ *
+ * Parameters:
+ *
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.calculateCrossings = function(model)
+{
+ var numRanks = model.ranks.length;
+ var totalCrossings = 0;
+
+ for (var i = 1; i < numRanks; i++)
+ {
+ totalCrossings += this.calculateRankCrossing(i, model);
+ }
+
+ return totalCrossings;
+};
+
+/**
+ * Function: calculateRankCrossing
+ *
+ * Calculates the number of edges crossings between the specified rank and
+ * the rank below it. Returns the number of edges crossings with the rank
+ * beneath
+ *
+ * Parameters:
+ *
+ * i - the topmost rank of the pair ( higher rank value )
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.calculateRankCrossing = function(i, model)
+{
+ var totalCrossings = 0;
+ var rank = model.ranks[i];
+ var previousRank = model.ranks[i - 1];
+
+ // Create an array of connections between these two levels
+ var currentRankSize = rank.length;
+ var previousRankSize = previousRank.length;
+ var connections = [];
+
+ for (var j = 0; j < currentRankSize; j++)
+ {
+ connections[j] = [];
+ }
+
+ // Iterate over the top rank and fill in the connection information
+ for (var j = 0; j < rank.length; j++)
+ {
+ var node = rank[j];
+ var rankPosition = node.getGeneralPurposeVariable(i);
+ var connectedCells = node.getPreviousLayerConnectedCells(i);
+
+ for (var k = 0; k < connectedCells.length; k++)
+ {
+ var connectedNode = connectedCells[k];
+ var otherCellRankPosition = connectedNode.getGeneralPurposeVariable(i - 1);
+ connections[rankPosition][otherCellRankPosition] = 201207;
+ }
+ }
+
+ // Iterate through the connection matrix, crossing edges are
+ // indicated by other connected edges with a greater rank position
+ // on one rank and lower position on the other
+ for (var j = 0; j < currentRankSize; j++)
+ {
+ for (var k = 0; k < previousRankSize; k++)
+ {
+ if (connections[j][k] == 201207)
+ {
+ // Draw a grid of connections, crossings are top right
+ // and lower left from this crossing pair
+ for (var j2 = j + 1; j2 < currentRankSize; j2++)
+ {
+ for (var k2 = 0; k2 < k; k2++)
+ {
+ if (connections[j2][k2] == 201207)
+ {
+ totalCrossings++;
+ }
+ }
+ }
+
+ for (var j2 = 0; j2 < j; j2++)
+ {
+ for (var k2 = k + 1; k2 < previousRankSize; k2++)
+ {
+ if (connections[j2][k2] == 201207)
+ {
+ totalCrossings++;
+ }
+ }
+ }
+
+ }
+ }
+ }
+
+ return totalCrossings / 2;
+};
+
+/**
+ * Function: transpose
+ *
+ * Takes each possible adjacent cell pair on each rank and checks if
+ * swapping them around reduces the number of crossing
+ *
+ * Parameters:
+ *
+ * mainLoopIteration - the iteration number of the main loop
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.transpose = function(mainLoopIteration, model)
+{
+ var improved = true;
+
+ // Track the number of iterations in case of looping
+ var count = 0;
+ var maxCount = 10;
+ while (improved && count++ < maxCount)
+ {
+ // On certain iterations allow allow swapping of cell pairs with
+ // equal edge crossings switched or not switched. This help to
+ // nudge a stuck layout into a lower crossing total.
+ var nudge = mainLoopIteration % 2 == 1 && count % 2 == 1;
+ improved = false;
+
+ for (var i = 0; i < model.ranks.length; i++)
+ {
+ var rank = model.ranks[i];
+ var orderedCells = [];
+
+ for (var j = 0; j < rank.length; j++)
+ {
+ var cell = rank[j];
+ var tempRank = cell.getGeneralPurposeVariable(i);
+
+ // FIXME: Workaround to avoid negative tempRanks
+ if (tempRank < 0)
+ {
+ tempRank = j;
+ }
+ orderedCells[tempRank] = cell;
+ }
+
+ var leftCellAboveConnections = null;
+ var leftCellBelowConnections = null;
+ var rightCellAboveConnections = null;
+ var rightCellBelowConnections = null;
+
+ var leftAbovePositions = null;
+ var leftBelowPositions = null;
+ var rightAbovePositions = null;
+ var rightBelowPositions = null;
+
+ var leftCell = null;
+ var rightCell = null;
+
+ for (var j = 0; j < (rank.length - 1); j++)
+ {
+ // For each intra-rank adjacent pair of cells
+ // see if swapping them around would reduce the
+ // number of edges crossing they cause in total
+ // On every cell pair except the first on each rank, we
+ // can save processing using the previous values for the
+ // right cell on the new left cell
+ if (j == 0)
+ {
+ leftCell = orderedCells[j];
+ leftCellAboveConnections = leftCell
+ .getNextLayerConnectedCells(i);
+ leftCellBelowConnections = leftCell
+ .getPreviousLayerConnectedCells(i);
+ leftAbovePositions = [];
+ leftBelowPositions = [];
+
+ for (var k = 0; k < leftCellAboveConnections.length; k++)
+ {
+ leftAbovePositions[k] = leftCellAboveConnections[k].getGeneralPurposeVariable(i + 1);
+ }
+
+ for (var k = 0; k < leftCellBelowConnections.length; k++)
+ {
+ leftBelowPositions[k] = leftCellBelowConnections[k].getGeneralPurposeVariable(i - 1);
+ }
+ }
+ else
+ {
+ leftCellAboveConnections = rightCellAboveConnections;
+ leftCellBelowConnections = rightCellBelowConnections;
+ leftAbovePositions = rightAbovePositions;
+ leftBelowPositions = rightBelowPositions;
+ leftCell = rightCell;
+ }
+
+ rightCell = orderedCells[j + 1];
+ rightCellAboveConnections = rightCell
+ .getNextLayerConnectedCells(i);
+ rightCellBelowConnections = rightCell
+ .getPreviousLayerConnectedCells(i);
+
+ rightAbovePositions = [];
+ rightBelowPositions = [];
+
+ for (var k = 0; k < rightCellAboveConnections.length; k++)
+ {
+ rightAbovePositions[k] = rightCellAboveConnections[k].getGeneralPurposeVariable(i + 1);
+ }
+
+ for (var k = 0; k < rightCellBelowConnections.length; k++)
+ {
+ rightBelowPositions[k] = rightCellBelowConnections[k].getGeneralPurposeVariable(i - 1);
+ }
+
+ var totalCurrentCrossings = 0;
+ var totalSwitchedCrossings = 0;
+
+ for (var k = 0; k < leftAbovePositions.length; k++)
+ {
+ for (var ik = 0; ik < rightAbovePositions.length; ik++)
+ {
+ if (leftAbovePositions[k] > rightAbovePositions[ik])
+ {
+ totalCurrentCrossings++;
+ }
+
+ if (leftAbovePositions[k] < rightAbovePositions[ik])
+ {
+ totalSwitchedCrossings++;
+ }
+ }
+ }
+
+ for (var k = 0; k < leftBelowPositions.length; k++)
+ {
+ for (var ik = 0; ik < rightBelowPositions.length; ik++)
+ {
+ if (leftBelowPositions[k] > rightBelowPositions[ik])
+ {
+ totalCurrentCrossings++;
+ }
+
+ if (leftBelowPositions[k] < rightBelowPositions[ik])
+ {
+ totalSwitchedCrossings++;
+ }
+ }
+ }
+
+ if ((totalSwitchedCrossings < totalCurrentCrossings) ||
+ (totalSwitchedCrossings == totalCurrentCrossings &&
+ nudge))
+ {
+ var temp = leftCell.getGeneralPurposeVariable(i);
+ leftCell.setGeneralPurposeVariable(i, rightCell
+ .getGeneralPurposeVariable(i));
+ rightCell.setGeneralPurposeVariable(i, temp);
+
+ // With this pair exchanged we have to switch all of
+ // values for the left cell to the right cell so the
+ // next iteration for this rank uses it as the left
+ // cell again
+ rightCellAboveConnections = leftCellAboveConnections;
+ rightCellBelowConnections = leftCellBelowConnections;
+ rightAbovePositions = leftAbovePositions;
+ rightBelowPositions = leftBelowPositions;
+ rightCell = leftCell;
+
+ if (!nudge)
+ {
+ // Don't count nudges as improvement or we'll end
+ // up stuck in two combinations and not finishing
+ // as early as we should
+ improved = true;
+ }
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Function: weightedMedian
+ *
+ * Sweeps up or down the layout attempting to minimise the median placement
+ * of connected cells on adjacent ranks
+ *
+ * Parameters:
+ *
+ * iteration - the iteration number of the main loop
+ * model - the internal model describing the hierarchy
+ */
+mxMedianHybridCrossingReduction.prototype.weightedMedian = function(iteration, model)
+{
+ // Reverse sweep direction each time through this method
+ var downwardSweep = (iteration % 2 == 0);
+ if (downwardSweep)
+ {
+ for (var j = model.maxRank - 1; j >= 0; j--)
+ {
+ this.medianRank(j, downwardSweep);
+ }
+ }
+ else
+ {
+ for (var j = 1; j < model.maxRank; j++)
+ {
+ this.medianRank(j, downwardSweep);
+ }
+ }
+};
+
+/**
+ * Function: medianRank
+ *
+ * Attempts to minimise the median placement of connected cells on this rank
+ * and one of the adjacent ranks
+ *
+ * Parameters:
+ *
+ * rankValue - the layer number of this rank
+ * downwardSweep - whether or not this is a downward sweep through the graph
+ */
+mxMedianHybridCrossingReduction.prototype.medianRank = function(rankValue, downwardSweep)
+{
+ var numCellsForRank = this.nestedBestRanks[rankValue].length;
+ var medianValues = [];
+ var reservedPositions = [];
+
+ for (var i = 0; i < numCellsForRank; i++)
+ {
+ var cell = this.nestedBestRanks[rankValue][i];
+ var sorterEntry = new MedianCellSorter();
+ sorterEntry.cell = cell;
+
+ // Flip whether or not equal medians are flipped on up and down
+ // sweeps
+ // TODO re-implement some kind of nudge
+ // medianValues[i].nudge = !downwardSweep;
+ var nextLevelConnectedCells;
+
+ if (downwardSweep)
+ {
+ nextLevelConnectedCells = cell
+ .getNextLayerConnectedCells(rankValue);
+ }
+ else
+ {
+ nextLevelConnectedCells = cell
+ .getPreviousLayerConnectedCells(rankValue);
+ }
+
+ var nextRankValue;
+
+ if (downwardSweep)
+ {
+ nextRankValue = rankValue + 1;
+ }
+ else
+ {
+ nextRankValue = rankValue - 1;
+ }
+
+ if (nextLevelConnectedCells != null
+ && nextLevelConnectedCells.length != 0)
+ {
+ sorterEntry.medianValue = this.medianValue(
+ nextLevelConnectedCells, nextRankValue);
+ medianValues.push(sorterEntry);
+ }
+ else
+ {
+ // Nodes with no adjacent vertices are flagged in the reserved array
+ // to indicate they should be left in their current position.
+ reservedPositions[cell.getGeneralPurposeVariable(rankValue)] = true;
+ }
+ }
+
+ medianValues.sort(MedianCellSorter.prototype.compare);
+
+ // Set the new position of each node within the rank using
+ // its temp variable
+ for (var i = 0; i < numCellsForRank; i++)
+ {
+ if (reservedPositions[i] == null)
+ {
+ var cell = medianValues.shift().cell;
+ cell.setGeneralPurposeVariable(rankValue, i);
+ }
+ }
+};
+
+/**
+ * Function: medianValue
+ *
+ * Calculates the median rank order positioning for the specified cell using
+ * the connected cells on the specified rank. Returns the median rank
+ * ordering value of the connected cells
+ *
+ * Parameters:
+ *
+ * connectedCells - the cells on the specified rank connected to the
+ * specified cell
+ * rankValue - the rank that the connected cell lie upon
+ */
+mxMedianHybridCrossingReduction.prototype.medianValue = function(connectedCells, rankValue)
+{
+ var medianValues = [];
+ var arrayCount = 0;
+
+ for (var i = 0; i < connectedCells.length; i++)
+ {
+ var cell = connectedCells[i];
+ medianValues[arrayCount++] = cell.getGeneralPurposeVariable(rankValue);
+ }
+
+ // Sort() sorts lexicographically by default (i.e. 11 before 9) so force
+ // numerical order sort
+ medianValues.sort(function(a,b){return a - b;});
+
+ if (arrayCount % 2 == 1)
+ {
+ // For odd numbers of adjacent vertices return the median
+ return medianValues[Math.floor(arrayCount / 2)];
+ }
+ else if (arrayCount == 2)
+ {
+ return ((medianValues[0] + medianValues[1]) / 2.0);
+ }
+ else
+ {
+ var medianPoint = arrayCount / 2;
+ var leftMedian = medianValues[medianPoint - 1] - medianValues[0];
+ var rightMedian = medianValues[arrayCount - 1]
+ - medianValues[medianPoint];
+
+ return (medianValues[medianPoint - 1] * rightMedian + medianValues[medianPoint]
+ * leftMedian)
+ / (leftMedian + rightMedian);
+ }
+};
+
+/**
+ * Class: MedianCellSorter
+ *
+ * A utility class used to track cells whilst sorting occurs on the median
+ * values. Does not violate (x.compareTo(y)==0) == (x.equals(y))
+ *
+ * Constructor: MedianCellSorter
+ *
+ * Constructs a new median cell sorter.
+ */
+function MedianCellSorter()
+{
+ // empty
+};
+
+/**
+ * Variable: medianValue
+ *
+ * The weighted value of the cell stored.
+ */
+MedianCellSorter.prototype.medianValue = 0;
+
+/**
+ * Variable: cell
+ *
+ * The cell whose median value is being calculated
+ */
+MedianCellSorter.prototype.cell = false;
+
+/**
+ * Function: compare
+ *
+ * Compares two MedianCellSorters.
+ */
+MedianCellSorter.prototype.compare = function(a, b)
+{
+ if (a != null && b != null)
+ {
+ if (b.medianValue > a.medianValue)
+ {
+ return -1;
+ }
+ else if (b.medianValue < a.medianValue)
+ {
+ return 1;
+ }
+ else
+ {
+ return 0;
+ }
+ }
+ else
+ {
+ return 0;
+ }
+};
diff --git a/src/js/layout/hierarchical/stage/mxMinimumCycleRemover.js b/src/js/layout/hierarchical/stage/mxMinimumCycleRemover.js
new file mode 100644
index 0000000..4f18f62
--- /dev/null
+++ b/src/js/layout/hierarchical/stage/mxMinimumCycleRemover.js
@@ -0,0 +1,131 @@
+/**
+ * $Id: mxMinimumCycleRemover.js,v 1.14 2010-01-04 11:18:26 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxMinimumCycleRemover
+ *
+ * An implementation of the first stage of the Sugiyama layout. Straightforward
+ * longest path calculation of layer assignment
+ *
+ * Constructor: mxMinimumCycleRemover
+ *
+ * Creates a cycle remover for the given internal model.
+ */
+function mxMinimumCycleRemover(layout)
+{
+ this.layout = layout;
+};
+
+/**
+ * Extends mxHierarchicalLayoutStage.
+ */
+mxMinimumCycleRemover.prototype = new mxHierarchicalLayoutStage();
+mxMinimumCycleRemover.prototype.constructor = mxMinimumCycleRemover;
+
+/**
+ * Variable: layout
+ *
+ * Reference to the enclosing <mxHierarchicalLayout>.
+ */
+mxMinimumCycleRemover.prototype.layout = null;
+
+/**
+ * Function: execute
+ *
+ * Takes the graph detail and configuration information within the facade
+ * and creates the resulting laid out graph within that facade for further
+ * use.
+ */
+mxMinimumCycleRemover.prototype.execute = function(parent)
+{
+ var model = this.layout.getModel();
+ var seenNodes = new Object();
+ var unseenNodes = mxUtils.clone(model.vertexMapper, null, true);
+
+ // Perform a dfs through the internal model. If a cycle is found,
+ // reverse it.
+ var rootsArray = null;
+
+ if (model.roots != null)
+ {
+ var modelRoots = model.roots;
+ rootsArray = [];
+
+ for (var i = 0; i < modelRoots.length; i++)
+ {
+ var nodeId = mxCellPath.create(modelRoots[i]);
+ rootsArray[i] = model.vertexMapper[nodeId];
+ }
+ }
+
+ model.visit(function(parent, node, connectingEdge, layer, seen)
+ {
+ // Check if the cell is in it's own ancestor list, if so
+ // invert the connecting edge and reverse the target/source
+ // relationship to that edge in the parent and the cell
+ if (node.isAncestor(parent))
+ {
+ connectingEdge.invert();
+ mxUtils.remove(connectingEdge, parent.connectsAsSource);
+ parent.connectsAsTarget.push(connectingEdge);
+ mxUtils.remove(connectingEdge, node.connectsAsTarget);
+ node.connectsAsSource.push(connectingEdge);
+ }
+
+ var cellId = mxCellPath.create(node.cell);
+ seenNodes[cellId] = node;
+ delete unseenNodes[cellId];
+ }, rootsArray, true, null);
+
+ var possibleNewRoots = null;
+
+ if (unseenNodes.lenth > 0)
+ {
+ possibleNewRoots = mxUtils.clone(unseenNodes, null, true);
+ }
+
+ // If there are any nodes that should be nodes that the dfs can miss
+ // these need to be processed with the dfs and the roots assigned
+ // correctly to form a correct internal model
+ var seenNodesCopy = mxUtils.clone(seenNodes, null, true);
+
+ // Pick a random cell and dfs from it
+ model.visit(function(parent, node, connectingEdge, layer, seen)
+ {
+ // Check if the cell is in it's own ancestor list, if so
+ // invert the connecting edge and reverse the target/source
+ // relationship to that edge in the parent and the cell
+ if (node.isAncestor(parent))
+ {
+ connectingEdge.invert();
+ mxUtils.remove(connectingEdge, parent.connectsAsSource);
+ node.connectsAsSource.push(connectingEdge);
+ parent.connectsAsTarget.push(connectingEdge);
+ mxUtils.remove(connectingEdge, node.connectsAsTarget);
+ }
+
+ var cellId = mxCellPath.create(node.cell);
+ seenNodes[cellId] = node;
+ delete unseenNodes[cellId];
+ }, unseenNodes, true, seenNodesCopy);
+
+ var graph = this.layout.getGraph();
+
+ if (possibleNewRoots != null && possibleNewRoots.length > 0)
+ {
+ var roots = model.roots;
+
+ for (var i = 0; i < possibleNewRoots.length; i++)
+ {
+ var node = possibleNewRoots[i];
+ var realNode = node.cell;
+ var numIncomingEdges = graph.getIncomingEdges(realNode).length;
+
+ if (numIncomingEdges == 0)
+ {
+ roots.push(realNode);
+ }
+ }
+ }
+};
diff --git a/src/js/layout/mxCircleLayout.js b/src/js/layout/mxCircleLayout.js
new file mode 100644
index 0000000..e3e6ec1
--- /dev/null
+++ b/src/js/layout/mxCircleLayout.js
@@ -0,0 +1,203 @@
+/**
+ * $Id: mxCircleLayout.js,v 1.25 2012-08-22 17:26:12 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCircleLayout
+ *
+ * Extends <mxGraphLayout> to implement a circluar layout for a given radius.
+ * The vertices do not need to be connected for this layout to work and all
+ * connections between vertices are not taken into account.
+ *
+ * Example:
+ *
+ * (code)
+ * var layout = new mxCircleLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ *
+ * Constructor: mxCircleLayout
+ *
+ * Constructs a new circular layout for the specified radius.
+ *
+ * Arguments:
+ *
+ * graph - <mxGraph> that contains the cells.
+ * radius - Optional radius as an int. Default is 100.
+ */
+function mxCircleLayout(graph, radius)
+{
+ mxGraphLayout.call(this, graph);
+ this.radius = (radius != null) ? radius : 100;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxCircleLayout.prototype = new mxGraphLayout();
+mxCircleLayout.prototype.constructor = mxCircleLayout;
+
+/**
+ * Variable: radius
+ *
+ * Integer specifying the size of the radius. Default is 100.
+ */
+mxCircleLayout.prototype.radius = null;
+
+/**
+ * Variable: moveCircle
+ *
+ * Boolean specifying if the circle should be moved to the top,
+ * left corner specified by <x0> and <y0>. Default is false.
+ */
+mxCircleLayout.prototype.moveCircle = false;
+
+/**
+ * Variable: x0
+ *
+ * Integer specifying the left coordinate of the circle.
+ * Default is 0.
+ */
+mxCircleLayout.prototype.x0 = 0;
+
+/**
+ * Variable: y0
+ *
+ * Integer specifying the top coordinate of the circle.
+ * Default is 0.
+ */
+mxCircleLayout.prototype.y0 = 0;
+
+/**
+ * Variable: resetEdges
+ *
+ * Specifies if all edge points of traversed edges should be removed.
+ * Default is true.
+ */
+mxCircleLayout.prototype.resetEdges = true;
+
+/**
+ * Variable: disableEdgeStyle
+ *
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxCircleLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Function: execute
+ *
+ * Implements <mxGraphLayout.execute>.
+ */
+mxCircleLayout.prototype.execute = function(parent)
+{
+ var model = this.graph.getModel();
+
+ // Moves the vertices to build a circle. Makes sure the
+ // radius is large enough for the vertices to not
+ // overlap
+ model.beginUpdate();
+ try
+ {
+ // Gets all vertices inside the parent and finds
+ // the maximum dimension of the largest vertex
+ var max = 0;
+ var top = null;
+ var left = null;
+ var vertices = [];
+ var childCount = model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var cell = model.getChildAt(parent, i);
+
+ if (!this.isVertexIgnored(cell))
+ {
+ vertices.push(cell);
+ var bounds = this.getVertexBounds(cell);
+
+ if (top == null)
+ {
+ top = bounds.y;
+ }
+ else
+ {
+ top = Math.min(top, bounds.y);
+ }
+
+ if (left == null)
+ {
+ left = bounds.x;
+ }
+ else
+ {
+ left = Math.min(left, bounds.x);
+ }
+
+ max = Math.max(max, Math.max(bounds.width, bounds.height));
+ }
+ else if (!this.isEdgeIgnored(cell))
+ {
+ // Resets the points on the traversed edge
+ if (this.resetEdges)
+ {
+ this.graph.resetEdge(cell);
+ }
+
+ if (this.disableEdgeStyle)
+ {
+ this.setEdgeStyleEnabled(cell, false);
+ }
+ }
+ }
+
+ var r = this.getRadius(vertices.length, max);
+
+ // Moves the circle to the specified origin
+ if (this.moveCircle)
+ {
+ left = this.x0;
+ top = this.y0;
+ }
+
+ this.circle(vertices, r, left, top);
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+};
+
+/**
+ * Function: getRadius
+ *
+ * Returns the radius to be used for the given vertex count. Max is the maximum
+ * width or height of all vertices in the layout.
+ */
+mxCircleLayout.prototype.getRadius = function(count, max)
+{
+ return Math.max(count * max / Math.PI, this.radius);
+};
+
+/**
+ * Function: circle
+ *
+ * Executes the circular layout for the specified array
+ * of vertices and the given radius. This is called from
+ * <execute>.
+ */
+mxCircleLayout.prototype.circle = function(vertices, r, left, top)
+{
+ var vertexCount = vertices.length;
+ var phi = 2 * Math.PI / vertexCount;
+
+ for (var i = 0; i < vertexCount; i++)
+ {
+ if (this.isVertexMovable(vertices[i]))
+ {
+ this.setVertexLocation(vertices[i],
+ left + r + r * Math.sin(i*phi),
+ top + r + r * Math.cos(i*phi));
+ }
+ }
+};
diff --git a/src/js/layout/mxCompactTreeLayout.js b/src/js/layout/mxCompactTreeLayout.js
new file mode 100644
index 0000000..db6324c
--- /dev/null
+++ b/src/js/layout/mxCompactTreeLayout.js
@@ -0,0 +1,995 @@
+/**
+ * $Id: mxCompactTreeLayout.js,v 1.57 2012-05-24 13:09:34 david Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCompactTreeLayout
+ *
+ * Extends <mxGraphLayout> to implement a compact tree (Moen) algorithm. This
+ * layout is suitable for graphs that have no cycles (trees). Vertices that are
+ * not connected to the tree will be ignored by this layout.
+ *
+ * Example:
+ *
+ * (code)
+ * var layout = new mxCompactTreeLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ *
+ * Constructor: mxCompactTreeLayout
+ *
+ * Constructs a new compact tree layout for the specified graph
+ * and orientation.
+ */
+function mxCompactTreeLayout(graph, horizontal, invert)
+{
+ mxGraphLayout.call(this, graph);
+ this.horizontal = (horizontal != null) ? horizontal : true;
+ this.invert = (invert != null) ? invert : false;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxCompactTreeLayout.prototype = new mxGraphLayout();
+mxCompactTreeLayout.prototype.constructor = mxCompactTreeLayout;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies the orientation of the layout. Default is true.
+ */
+mxCompactTreeLayout.prototype.horizontal = null;
+
+/**
+ * Variable: invert
+ *
+ * Specifies if edge directions should be inverted. Default is false.
+ */
+mxCompactTreeLayout.prototype.invert = null;
+
+/**
+ * Variable: resizeParent
+ *
+ * If the parents should be resized to match the width/height of the
+ * children. Default is true.
+ */
+mxCompactTreeLayout.prototype.resizeParent = true;
+
+/**
+ * Variable: groupPadding
+ *
+ * Padding added to resized parents
+ */
+mxCompactTreeLayout.prototype.groupPadding = 10;
+
+/**
+ * Variable: parentsChanged
+ *
+ * A set of the parents that need updating based on children
+ * process as part of the layout
+ */
+mxCompactTreeLayout.prototype.parentsChanged = null;
+
+/**
+ * Variable: moveTree
+ *
+ * Specifies if the tree should be moved to the top, left corner
+ * if it is inside a top-level layer. Default is false.
+ */
+mxCompactTreeLayout.prototype.moveTree = false;
+
+/**
+ * Variable: levelDistance
+ *
+ * Holds the levelDistance. Default is 10.
+ */
+mxCompactTreeLayout.prototype.levelDistance = 10;
+
+/**
+ * Variable: nodeDistance
+ *
+ * Holds the nodeDistance. Default is 20.
+ */
+mxCompactTreeLayout.prototype.nodeDistance = 20;
+
+/**
+ * Variable: resetEdges
+ *
+ * Specifies if all edge points of traversed edges should be removed.
+ * Default is true.
+ */
+mxCompactTreeLayout.prototype.resetEdges = true;
+
+/**
+ * Variable: prefHozEdgeSep
+ *
+ * The preferred horizontal distance between edges exiting a vertex
+ */
+mxCompactTreeLayout.prototype.prefHozEdgeSep = 5;
+
+/**
+ * Variable: prefVertEdgeOff
+ *
+ * The preferred vertical offset between edges exiting a vertex
+ */
+mxCompactTreeLayout.prototype.prefVertEdgeOff = 4;
+
+/**
+ * Variable: minEdgeJetty
+ *
+ * The minimum distance for an edge jetty from a vertex
+ */
+mxCompactTreeLayout.prototype.minEdgeJetty = 8;
+
+/**
+ * Variable: channelBuffer
+ *
+ * The size of the vertical buffer in the center of inter-rank channels
+ * where edge control points should not be placed
+ */
+mxCompactTreeLayout.prototype.channelBuffer = 4;
+
+/**
+ * Variable: edgeRouting
+ *
+ * Whether or not to apply the internal tree edge routing
+ */
+mxCompactTreeLayout.prototype.edgeRouting = true;
+
+/**
+ * Function: isVertexIgnored
+ *
+ * Returns a boolean indicating if the given <mxCell> should be ignored as a
+ * vertex. This returns true if the cell has no connections.
+ *
+ * Parameters:
+ *
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxCompactTreeLayout.prototype.isVertexIgnored = function(vertex)
+{
+ return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
+ this.graph.getConnections(vertex).length == 0;
+};
+
+/**
+ * Function: isHorizontal
+ *
+ * Returns <horizontal>.
+ */
+mxCompactTreeLayout.prototype.isHorizontal = function()
+{
+ return this.horizontal;
+};
+
+/**
+ * Function: execute
+ *
+ * Implements <mxGraphLayout.execute>.
+ *
+ * If the parent has any connected edges, then it is used as the root of
+ * the tree. Else, <mxGraph.findTreeRoots> will be used to find a suitable
+ * root node within the set of children of the given parent.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose children should be laid out.
+ * root - Optional <mxCell> that will be used as the root of the tree.
+ */
+mxCompactTreeLayout.prototype.execute = function(parent, root)
+{
+ this.parent = parent;
+ var model = this.graph.getModel();
+
+ if (root == null)
+ {
+ // Takes the parent as the root if it has outgoing edges
+ if (this.graph.getEdges(parent, model.getParent(parent),
+ this.invert, !this.invert, false).length > 0)
+ {
+ root = parent;
+ }
+
+ // Tries to find a suitable root in the parent's
+ // children
+ else
+ {
+ var roots = this.graph.findTreeRoots(parent, true, this.invert);
+
+ if (roots.length > 0)
+ {
+ for (var i = 0; i < roots.length; i++)
+ {
+ if (!this.isVertexIgnored(roots[i]) &&
+ this.graph.getEdges(roots[i], null,
+ this.invert, !this.invert, false).length > 0)
+ {
+ root = roots[i];
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ if (root != null)
+ {
+ if (this.resizeParent)
+ {
+ this.parentsChanged = new Object();
+ }
+ else
+ {
+ this.parentsChanged = null;
+ }
+
+ model.beginUpdate();
+
+ try
+ {
+ var node = this.dfs(root, parent);
+
+ if (node != null)
+ {
+ this.layout(node);
+ var x0 = this.graph.gridSize;
+ var y0 = x0;
+
+ if (!this.moveTree)
+ {
+ var g = this.getVertexBounds(root);
+
+ if (g != null)
+ {
+ x0 = g.x;
+ y0 = g.y;
+ }
+ }
+
+ var bounds = null;
+
+ if (this.isHorizontal())
+ {
+ bounds = this.horizontalLayout(node, x0, y0);
+ }
+ else
+ {
+ bounds = this.verticalLayout(node, null, x0, y0);
+ }
+
+ if (bounds != null)
+ {
+ var dx = 0;
+ var dy = 0;
+
+ if (bounds.x < 0)
+ {
+ dx = Math.abs(x0 - bounds.x);
+ }
+
+ if (bounds.y < 0)
+ {
+ dy = Math.abs(y0 - bounds.y);
+ }
+
+ if (dx != 0 || dy != 0)
+ {
+ this.moveNode(node, dx, dy);
+ }
+
+ if (this.resizeParent)
+ {
+ this.adjustParents();
+ }
+
+ if (this.edgeRouting)
+ {
+ // Iterate through all edges setting their positions
+ this.localEdgeProcessing(node);
+ }
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: moveNode
+ *
+ * Moves the specified node and all of its children by the given amount.
+ */
+mxCompactTreeLayout.prototype.moveNode = function(node, dx, dy)
+{
+ node.x += dx;
+ node.y += dy;
+ this.apply(node);
+
+ var child = node.child;
+
+ while (child != null)
+ {
+ this.moveNode(child, dx, dy);
+ child = child.next;
+ }
+};
+
+/**
+ * Function: dfs
+ *
+ * Does a depth first search starting at the specified cell.
+ * Makes sure the specified parent is never left by the
+ * algorithm.
+ */
+mxCompactTreeLayout.prototype.dfs = function(cell, parent, visited)
+{
+ visited = (visited != null) ? visited : [];
+
+ var id = mxCellPath.create(cell);
+ var node = null;
+
+ if (cell != null && visited[id] == null && !this.isVertexIgnored(cell))
+ {
+ visited[id] = cell;
+ node = this.createNode(cell);
+
+ var model = this.graph.getModel();
+ var prev = null;
+ var out = this.graph.getEdges(cell, parent, this.invert, !this.invert, false, true);
+ var view = this.graph.getView();
+
+ for (var i = 0; i < out.length; i++)
+ {
+ var edge = out[i];
+
+ if (!this.isEdgeIgnored(edge))
+ {
+ // Resets the points on the traversed edge
+ if (this.resetEdges)
+ {
+ this.setEdgePoints(edge, null);
+ }
+
+ if (this.edgeRouting)
+ {
+ this.setEdgeStyleEnabled(edge, false);
+ this.setEdgePoints(edge, null);
+ }
+
+ // Checks if terminal in same swimlane
+ var state = view.getState(edge);
+ var target = (state != null) ? state.getVisibleTerminal(this.invert) : view.getVisibleTerminal(edge, this.invert);
+ var tmp = this.dfs(target, parent, visited);
+
+ if (tmp != null && model.getGeometry(target) != null)
+ {
+ if (prev == null)
+ {
+ node.child = tmp;
+ }
+ else
+ {
+ prev.next = tmp;
+ }
+
+ prev = tmp;
+ }
+ }
+ }
+ }
+
+ return node;
+};
+
+/**
+ * Function: layout
+ *
+ * Starts the actual compact tree layout algorithm
+ * at the given node.
+ */
+mxCompactTreeLayout.prototype.layout = function(node)
+{
+ if (node != null)
+ {
+ var child = node.child;
+
+ while (child != null)
+ {
+ this.layout(child);
+ child = child.next;
+ }
+
+ if (node.child != null)
+ {
+ this.attachParent(node, this.join(node));
+ }
+ else
+ {
+ this.layoutLeaf(node);
+ }
+ }
+};
+
+/**
+ * Function: horizontalLayout
+ */
+mxCompactTreeLayout.prototype.horizontalLayout = function(node, x0, y0, bounds)
+{
+ node.x += x0 + node.offsetX;
+ node.y += y0 + node.offsetY;
+ bounds = this.apply(node, bounds);
+ var child = node.child;
+
+ if (child != null)
+ {
+ bounds = this.horizontalLayout(child, node.x, node.y, bounds);
+ var siblingOffset = node.y + child.offsetY;
+ var s = child.next;
+
+ while (s != null)
+ {
+ bounds = this.horizontalLayout(s, node.x + child.offsetX, siblingOffset, bounds);
+ siblingOffset += s.offsetY;
+ s = s.next;
+ }
+ }
+
+ return bounds;
+};
+
+/**
+ * Function: verticalLayout
+ */
+mxCompactTreeLayout.prototype.verticalLayout = function(node, parent, x0, y0, bounds)
+{
+ node.x += x0 + node.offsetY;
+ node.y += y0 + node.offsetX;
+ bounds = this.apply(node, bounds);
+ var child = node.child;
+
+ if (child != null)
+ {
+ bounds = this.verticalLayout(child, node, node.x, node.y, bounds);
+ var siblingOffset = node.x + child.offsetY;
+ var s = child.next;
+
+ while (s != null)
+ {
+ bounds = this.verticalLayout(s, node, siblingOffset, node.y + child.offsetX, bounds);
+ siblingOffset += s.offsetY;
+ s = s.next;
+ }
+ }
+
+ return bounds;
+};
+
+/**
+ * Function: attachParent
+ */
+mxCompactTreeLayout.prototype.attachParent = function(node, height)
+{
+ var x = this.nodeDistance + this.levelDistance;
+ var y2 = (height - node.width) / 2 - this.nodeDistance;
+ var y1 = y2 + node.width + 2 * this.nodeDistance - height;
+
+ node.child.offsetX = x + node.height;
+ node.child.offsetY = y1;
+
+ node.contour.upperHead = this.createLine(node.height, 0,
+ this.createLine(x, y1, node.contour.upperHead));
+ node.contour.lowerHead = this.createLine(node.height, 0,
+ this.createLine(x, y2, node.contour.lowerHead));
+};
+
+/**
+ * Function: layoutLeaf
+ */
+mxCompactTreeLayout.prototype.layoutLeaf = function(node)
+{
+ var dist = 2 * this.nodeDistance;
+
+ node.contour.upperTail = this.createLine(
+ node.height + dist, 0);
+ node.contour.upperHead = node.contour.upperTail;
+ node.contour.lowerTail = this.createLine(
+ 0, -node.width - dist);
+ node.contour.lowerHead = this.createLine(
+ node.height + dist, 0, node.contour.lowerTail);
+};
+
+/**
+ * Function: join
+ */
+mxCompactTreeLayout.prototype.join = function(node)
+{
+ var dist = 2 * this.nodeDistance;
+
+ var child = node.child;
+ node.contour = child.contour;
+ var h = child.width + dist;
+ var sum = h;
+ child = child.next;
+
+ while (child != null)
+ {
+ var d = this.merge(node.contour, child.contour);
+ child.offsetY = d + h;
+ child.offsetX = 0;
+ h = child.width + dist;
+ sum += d + h;
+ child = child.next;
+ }
+
+ return sum;
+};
+
+/**
+ * Function: merge
+ */
+mxCompactTreeLayout.prototype.merge = function(p1, p2)
+{
+ var x = 0;
+ var y = 0;
+ var total = 0;
+
+ var upper = p1.lowerHead;
+ var lower = p2.upperHead;
+
+ while (lower != null && upper != null)
+ {
+ var d = this.offset(x, y, lower.dx, lower.dy,
+ upper.dx, upper.dy);
+ y += d;
+ total += d;
+
+ if (x + lower.dx <= upper.dx)
+ {
+ x += lower.dx;
+ y += lower.dy;
+ lower = lower.next;
+ }
+ else
+ {
+ x -= upper.dx;
+ y -= upper.dy;
+ upper = upper.next;
+ }
+ }
+
+ if (lower != null)
+ {
+ var b = this.bridge(p1.upperTail, 0, 0, lower, x, y);
+ p1.upperTail = (b.next != null) ? p2.upperTail : b;
+ p1.lowerTail = p2.lowerTail;
+ }
+ else
+ {
+ var b = this.bridge(p2.lowerTail, x, y, upper, 0, 0);
+
+ if (b.next == null)
+ {
+ p1.lowerTail = b;
+ }
+ }
+
+ p1.lowerHead = p2.lowerHead;
+
+ return total;
+};
+
+/**
+ * Function: offset
+ */
+mxCompactTreeLayout.prototype.offset = function(p1, p2, a1, a2, b1, b2)
+{
+ var d = 0;
+
+ if (b1 <= p1 || p1 + a1 <= 0)
+ {
+ return 0;
+ }
+
+ var t = b1 * a2 - a1 * b2;
+
+ if (t > 0)
+ {
+ if (p1 < 0)
+ {
+ var s = p1 * a2;
+ d = s / a1 - p2;
+ }
+ else if (p1 > 0)
+ {
+ var s = p1 * b2;
+ d = s / b1 - p2;
+ }
+ else
+ {
+ d = -p2;
+ }
+ }
+ else if (b1 < p1 + a1)
+ {
+ var s = (b1 - p1) * a2;
+ d = b2 - (p2 + s / a1);
+ }
+ else if (b1 > p1 + a1)
+ {
+ var s = (a1 + p1) * b2;
+ d = s / b1 - (p2 + a2);
+ }
+ else
+ {
+ d = b2 - (p2 + a2);
+ }
+
+ if (d > 0)
+ {
+ return d;
+ }
+ else
+ {
+ return 0;
+ }
+};
+
+/**
+ * Function: bridge
+ */
+mxCompactTreeLayout.prototype.bridge = function(line1, x1, y1, line2, x2, y2)
+{
+ var dx = x2 + line2.dx - x1;
+ var dy = 0;
+ var s = 0;
+
+ if (line2.dx == 0)
+ {
+ dy = line2.dy;
+ }
+ else
+ {
+ s = dx * line2.dy;
+ dy = s / line2.dx;
+ }
+
+ var r = this.createLine(dx, dy, line2.next);
+ line1.next = this.createLine(0, y2 + line2.dy - dy - y1, r);
+
+ return r;
+};
+
+/**
+ * Function: createNode
+ */
+mxCompactTreeLayout.prototype.createNode = function(cell)
+{
+ var node = new Object();
+ node.cell = cell;
+ node.x = 0;
+ node.y = 0;
+ node.width = 0;
+ node.height = 0;
+
+ var geo = this.getVertexBounds(cell);
+
+ if (geo != null)
+ {
+ if (this.isHorizontal())
+ {
+ node.width = geo.height;
+ node.height = geo.width;
+ }
+ else
+ {
+ node.width = geo.width;
+ node.height = geo.height;
+ }
+ }
+
+ node.offsetX = 0;
+ node.offsetY = 0;
+ node.contour = new Object();
+
+ return node;
+};
+
+/**
+ * Function: apply
+ */
+mxCompactTreeLayout.prototype.apply = function(node, bounds)
+{
+ var model = this.graph.getModel();
+ var cell = node.cell;
+ var g = model.getGeometry(cell);
+
+ if (cell != null && g != null)
+ {
+ if (this.isVertexMovable(cell))
+ {
+ g = this.setVertexLocation(cell, node.x, node.y);
+
+ if (this.resizeParent)
+ {
+ var parent = model.getParent(cell);
+ var id = mxCellPath.create(parent);
+
+ // Implements set semantic
+ if (this.parentsChanged[id] == null)
+ {
+ this.parentsChanged[id] = parent;
+ }
+ }
+ }
+
+ if (bounds == null)
+ {
+ bounds = new mxRectangle(g.x, g.y, g.width, g.height);
+ }
+ else
+ {
+ bounds = new mxRectangle(Math.min(bounds.x, g.x),
+ Math.min(bounds.y, g.y),
+ Math.max(bounds.x + bounds.width, g.x + g.width),
+ Math.max(bounds.y + bounds.height, g.y + g.height));
+ }
+ }
+
+ return bounds;
+};
+
+/**
+ * Function: createLine
+ */
+mxCompactTreeLayout.prototype.createLine = function(dx, dy, next)
+{
+ var line = new Object();
+ line.dx = dx;
+ line.dy = dy;
+ line.next = next;
+
+ return line;
+};
+
+/**
+ * Function: adjustParents
+ *
+ * Adjust parent cells whose child geometries have changed. The default
+ * implementation adjusts the group to just fit around the children with
+ * a padding.
+ */
+mxCompactTreeLayout.prototype.adjustParents = function()
+{
+ var tmp = [];
+
+ for (var id in this.parentsChanged)
+ {
+ tmp.push(this.parentsChanged[id]);
+ }
+
+ this.arrangeGroups(mxUtils.sortCells(tmp, true), this.groupPadding);
+};
+
+/**
+ * Function: localEdgeProcessing
+ *
+ * Moves the specified node and all of its children by the given amount.
+ */
+mxCompactTreeLayout.prototype.localEdgeProcessing = function(node)
+{
+ this.processNodeOutgoing(node);
+ var child = node.child;
+
+ while (child != null)
+ {
+ this.localEdgeProcessing(child);
+ child = child.next;
+ }
+};
+
+/**
+ * Function: localEdgeProcessing
+ *
+ * Separates the x position of edges as they connect to vertices
+ */
+mxCompactTreeLayout.prototype.processNodeOutgoing = function(node)
+{
+ var child = node.child;
+ var parentCell = node.cell;
+
+ var childCount = 0;
+ var sortedCells = [];
+
+ while (child != null)
+ {
+ childCount++;
+
+ var sortingCriterion = child.x;
+
+ if (this.horizontal)
+ {
+ sortingCriterion = child.y;
+ }
+
+ sortedCells.push(new WeightedCellSorter(child, sortingCriterion));
+ child = child.next;
+ }
+
+ sortedCells.sort(WeightedCellSorter.prototype.compare);
+
+ var availableWidth = node.width;
+
+ var requiredWidth = (childCount + 1) * this.prefHozEdgeSep;
+
+ // Add a buffer on the edges of the vertex if the edge count allows
+ if (availableWidth > requiredWidth + (2 * this.prefHozEdgeSep))
+ {
+ availableWidth -= 2 * this.prefHozEdgeSep;
+ }
+
+ var edgeSpacing = availableWidth / childCount;
+
+ var currentXOffset = edgeSpacing / 2.0;
+
+ if (availableWidth > requiredWidth + (2 * this.prefHozEdgeSep))
+ {
+ currentXOffset += this.prefHozEdgeSep;
+ }
+
+ var currentYOffset = this.minEdgeJetty - this.prefVertEdgeOff;
+ var maxYOffset = 0;
+
+ var parentBounds = this.getVertexBounds(parentCell);
+ child = node.child;
+
+ for (var j = 0; j < sortedCells.length; j++)
+ {
+ var childCell = sortedCells[j].cell.cell;
+ var childBounds = this.getVertexBounds(childCell);
+
+ var edges = this.graph.getEdgesBetween(parentCell,
+ childCell, false);
+
+ var newPoints = [];
+ var x = 0;
+ var y = 0;
+
+ for (var i = 0; i < edges.length; i++)
+ {
+ if (this.horizontal)
+ {
+ // Use opposite co-ords, calculation was done for
+ //
+ x = parentBounds.x + parentBounds.width;
+ y = parentBounds.y + currentXOffset;
+ newPoints.push(new mxPoint(x, y));
+ x = parentBounds.x + parentBounds.width
+ + currentYOffset;
+ newPoints.push(new mxPoint(x, y));
+ y = childBounds.y + childBounds.height / 2.0;
+ newPoints.push(new mxPoint(x, y));
+ this.setEdgePoints(edges[i], newPoints);
+ }
+ else
+ {
+ x = parentBounds.x + currentXOffset;
+ y = parentBounds.y + parentBounds.height;
+ newPoints.push(new mxPoint(x, y));
+ y = parentBounds.y + parentBounds.height
+ + currentYOffset;
+ newPoints.push(new mxPoint(x, y));
+ x = childBounds.x + childBounds.width / 2.0;
+ newPoints.push(new mxPoint(x, y));
+ this.setEdgePoints(edges[i], newPoints);
+ }
+ }
+
+ if (j < childCount / 2)
+ {
+ currentYOffset += this.prefVertEdgeOff;
+ }
+ else if (j > childCount / 2)
+ {
+ currentYOffset -= this.prefVertEdgeOff;
+ }
+ // Ignore the case if equals, this means the second of 2
+ // jettys with the same y (even number of edges)
+
+ // pos[k * 2] = currentX;
+ currentXOffset += edgeSpacing;
+ // pos[k * 2 + 1] = currentYOffset;
+
+ maxYOffset = Math.max(maxYOffset, currentYOffset);
+ }
+};
+
+/**
+ * Class: WeightedCellSorter
+ *
+ * A utility class used to track cells whilst sorting occurs on the weighted
+ * sum of their connected edges. Does not violate (x.compareTo(y)==0) ==
+ * (x.equals(y))
+ *
+ * Constructor: WeightedCellSorter
+ *
+ * Constructs a new weighted cell sorted for the given cell and weight.
+ */
+function WeightedCellSorter(cell, weightedValue)
+{
+ this.cell = cell;
+ this.weightedValue = weightedValue;
+};
+
+/**
+ * Variable: weightedValue
+ *
+ * The weighted value of the cell stored.
+ */
+WeightedCellSorter.prototype.weightedValue = 0;
+
+/**
+ * Variable: nudge
+ *
+ * Whether or not to flip equal weight values.
+ */
+WeightedCellSorter.prototype.nudge = false;
+
+/**
+ * Variable: visited
+ *
+ * Whether or not this cell has been visited in the current assignment.
+ */
+WeightedCellSorter.prototype.visited = false;
+
+/**
+ * Variable: rankIndex
+ *
+ * The index this cell is in the model rank.
+ */
+WeightedCellSorter.prototype.rankIndex = null;
+
+/**
+ * Variable: cell
+ *
+ * The cell whose median value is being calculated.
+ */
+WeightedCellSorter.prototype.cell = null;
+
+/**
+ * Function: compare
+ *
+ * Compares two WeightedCellSorters.
+ */
+WeightedCellSorter.prototype.compare = function(a, b)
+{
+ if (a != null && b != null)
+ {
+ if (b.weightedValue > a.weightedValue)
+ {
+ return 1;
+ }
+ else if (b.weightedValue < a.weightedValue)
+ {
+ return -1;
+ }
+ else
+ {
+ if (b.nudge)
+ {
+ return 1;
+ }
+ else
+ {
+ return -1;
+ }
+ }
+ }
+ else
+ {
+ return 0;
+ }
+}; \ No newline at end of file
diff --git a/src/js/layout/mxCompositeLayout.js b/src/js/layout/mxCompositeLayout.js
new file mode 100644
index 0000000..2ceb5f5
--- /dev/null
+++ b/src/js/layout/mxCompositeLayout.js
@@ -0,0 +1,101 @@
+/**
+ * $Id: mxCompositeLayout.js,v 1.11 2010-01-02 09:45:15 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCompositeLayout
+ *
+ * Allows to compose multiple layouts into a single layout. The master layout
+ * is the layout that handles move operations if another layout than the first
+ * element in <layouts> should be used. The <master> layout is not executed as
+ * the code assumes that it is part of <layouts>.
+ *
+ * Example:
+ * (code)
+ * var first = new mxFastOrganicLayout(graph);
+ * var second = new mxParallelEdgeLayout(graph);
+ * var layout = new mxCompositeLayout(graph, [first, second], first);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ *
+ * Constructor: mxCompositeLayout
+ *
+ * Constructs a new layout using the given layouts. The graph instance is
+ * required for creating the transaction that contains all layouts.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * layouts - Array of <mxGraphLayouts>.
+ * master - Optional layout that handles moves. If no layout is given then
+ * the first layout of the above array is used to handle moves.
+ */
+function mxCompositeLayout(graph, layouts, master)
+{
+ mxGraphLayout.call(this, graph);
+ this.layouts = layouts;
+ this.master = master;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxCompositeLayout.prototype = new mxGraphLayout();
+mxCompositeLayout.prototype.constructor = mxCompositeLayout;
+
+/**
+ * Variable: layouts
+ *
+ * Holds the array of <mxGraphLayouts> that this layout contains.
+ */
+mxCompositeLayout.prototype.layouts = null;
+
+/**
+ * Variable: layouts
+ *
+ * Reference to the <mxGraphLayouts> that handles moves. If this is null
+ * then the first layout in <layouts> is used.
+ */
+mxCompositeLayout.prototype.master = null;
+
+/**
+ * Function: moveCell
+ *
+ * Implements <mxGraphLayout.moveCell> by calling move on <master> or the first
+ * layout in <layouts>.
+ */
+mxCompositeLayout.prototype.moveCell = function(cell, x, y)
+{
+ if (this.master != null)
+ {
+ this.master.move.apply(this.master, arguments);
+ }
+ else
+ {
+ this.layouts[0].move.apply(this.layouts[0], arguments);
+ }
+};
+
+/**
+ * Function: execute
+ *
+ * Implements <mxGraphLayout.execute> by executing all <layouts> in a
+ * single transaction.
+ */
+mxCompositeLayout.prototype.execute = function(parent)
+{
+ var model = this.graph.getModel();
+
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < this.layouts.length; i++)
+ {
+ this.layouts[i].execute.apply(this.layouts[i], arguments);
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+};
diff --git a/src/js/layout/mxEdgeLabelLayout.js b/src/js/layout/mxEdgeLabelLayout.js
new file mode 100644
index 0000000..2bfb3c2
--- /dev/null
+++ b/src/js/layout/mxEdgeLabelLayout.js
@@ -0,0 +1,165 @@
+/**
+ * $Id: mxEdgeLabelLayout.js,v 1.8 2010-01-04 11:18:25 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxEdgeLabelLayout
+ *
+ * Extends <mxGraphLayout> to implement an edge label layout. This layout
+ * makes use of cell states, which means the graph must be validated in
+ * a graph view (so that the label bounds are available) before this layout
+ * can be executed.
+ *
+ * Example:
+ *
+ * (code)
+ * var layout = new mxEdgeLabelLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ *
+ * Constructor: mxEdgeLabelLayout
+ *
+ * Constructs a new edge label layout.
+ *
+ * Arguments:
+ *
+ * graph - <mxGraph> that contains the cells.
+ */
+function mxEdgeLabelLayout(graph, radius)
+{
+ mxGraphLayout.call(this, graph);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxEdgeLabelLayout.prototype = new mxGraphLayout();
+mxEdgeLabelLayout.prototype.constructor = mxEdgeLabelLayout;
+
+/**
+ * Function: execute
+ *
+ * Implements <mxGraphLayout.execute>.
+ */
+mxEdgeLabelLayout.prototype.execute = function(parent)
+{
+ var view = this.graph.view;
+ var model = this.graph.getModel();
+
+ // Gets all vertices and edges inside the parent
+ var edges = [];
+ var vertices = [];
+ var childCount = model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var cell = model.getChildAt(parent, i);
+ var state = view.getState(cell);
+
+ if (state != null)
+ {
+ if (!this.isVertexIgnored(cell))
+ {
+ vertices.push(state);
+ }
+ else if (!this.isEdgeIgnored(cell))
+ {
+ edges.push(state);
+ }
+ }
+ }
+
+ this.placeLabels(vertices, edges);
+};
+
+/**
+ * Function: placeLabels
+ *
+ * Places the labels of the given edges.
+ */
+mxEdgeLabelLayout.prototype.placeLabels = function(v, e)
+{
+ var model = this.graph.getModel();
+
+ // Moves the vertices to build a circle. Makes sure the
+ // radius is large enough for the vertices to not
+ // overlap
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < e.length; i++)
+ {
+ var edge = e[i];
+
+ if (edge != null && edge.text != null &&
+ edge.text.boundingBox != null)
+ {
+ for (var j = 0; j < v.length; j++)
+ {
+ var vertex = v[j];
+
+ if (vertex != null)
+ {
+ this.avoid(edge, vertex);
+ }
+ }
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+};
+
+/**
+ * Function: avoid
+ *
+ * Places the labels of the given edges.
+ */
+mxEdgeLabelLayout.prototype.avoid = function(edge, vertex)
+{
+ var model = this.graph.getModel();
+ var labRect = edge.text.boundingBox;
+
+ if (mxUtils.intersects(labRect, vertex))
+ {
+ var dy1 = -labRect.y - labRect.height + vertex.y;
+ var dy2 = -labRect.y + vertex.y + vertex.height;
+
+ var dy = (Math.abs(dy1) < Math.abs(dy2)) ? dy1 : dy2;
+
+ var dx1 = -labRect.x - labRect.width + vertex.x;
+ var dx2 = -labRect.x + vertex.x + vertex.width;
+
+ var dx = (Math.abs(dx1) < Math.abs(dx2)) ? dx1 : dx2;
+
+ if (Math.abs(dx) < Math.abs(dy))
+ {
+ dy = 0;
+ }
+ else
+ {
+ dx = 0;
+ }
+
+ var g = model.getGeometry(edge.cell);
+
+ if (g != null)
+ {
+ g = g.clone();
+
+ if (g.offset != null)
+ {
+ g.offset.x += dx;
+ g.offset.y += dy;
+ }
+ else
+ {
+ g.offset = new mxPoint(dx, dy);
+ }
+
+ model.setGeometry(edge.cell, g);
+ }
+ }
+};
diff --git a/src/js/layout/mxFastOrganicLayout.js b/src/js/layout/mxFastOrganicLayout.js
new file mode 100644
index 0000000..d7d6b5d
--- /dev/null
+++ b/src/js/layout/mxFastOrganicLayout.js
@@ -0,0 +1,591 @@
+/**
+ * $Id: mxFastOrganicLayout.js,v 1.37 2011-04-28 13:14:55 david Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxFastOrganicLayout
+ *
+ * Extends <mxGraphLayout> to implement a fast organic layout algorithm.
+ * The vertices need to be connected for this layout to work, vertices
+ * with no connections are ignored.
+ *
+ * Example:
+ *
+ * (code)
+ * var layout = new mxFastOrganicLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ *
+ * Constructor: mxCompactTreeLayout
+ *
+ * Constructs a new fast organic layout for the specified graph.
+ */
+function mxFastOrganicLayout(graph)
+{
+ mxGraphLayout.call(this, graph);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxFastOrganicLayout.prototype = new mxGraphLayout();
+mxFastOrganicLayout.prototype.constructor = mxFastOrganicLayout;
+
+/**
+ * Variable: useInputOrigin
+ *
+ * Specifies if the top left corner of the input cells should be the origin
+ * of the layout result. Default is true.
+ */
+mxFastOrganicLayout.prototype.useInputOrigin = true;
+
+/**
+ * Variable: resetEdges
+ *
+ * Specifies if all edge points of traversed edges should be removed.
+ * Default is true.
+ */
+mxFastOrganicLayout.prototype.resetEdges = true;
+
+/**
+ * Variable: disableEdgeStyle
+ *
+ * Specifies if the STYLE_NOEDGESTYLE flag should be set on edges that are
+ * modified by the result. Default is true.
+ */
+mxFastOrganicLayout.prototype.disableEdgeStyle = true;
+
+/**
+ * Variable: forceConstant
+ *
+ * The force constant by which the attractive forces are divided and the
+ * replusive forces are multiple by the square of. The value equates to the
+ * average radius there is of free space around each node. Default is 50.
+ */
+mxFastOrganicLayout.prototype.forceConstant = 50;
+
+/**
+ * Variable: forceConstantSquared
+ *
+ * Cache of <forceConstant>^2 for performance.
+ */
+mxFastOrganicLayout.prototype.forceConstantSquared = 0;
+
+/**
+ * Variable: minDistanceLimit
+ *
+ * Minimal distance limit. Default is 2. Prevents of
+ * dividing by zero.
+ */
+mxFastOrganicLayout.prototype.minDistanceLimit = 2;
+
+/**
+ * Variable: minDistanceLimit
+ *
+ * Minimal distance limit. Default is 2. Prevents of
+ * dividing by zero.
+ */
+mxFastOrganicLayout.prototype.maxDistanceLimit = 500;
+
+/**
+ * Variable: minDistanceLimitSquared
+ *
+ * Cached version of <minDistanceLimit> squared.
+ */
+mxFastOrganicLayout.prototype.minDistanceLimitSquared = 4;
+
+/**
+ * Variable: initialTemp
+ *
+ * Start value of temperature. Default is 200.
+ */
+mxFastOrganicLayout.prototype.initialTemp = 200;
+
+/**
+ * Variable: temperature
+ *
+ * Temperature to limit displacement at later stages of layout.
+ */
+mxFastOrganicLayout.prototype.temperature = 0;
+
+/**
+ * Variable: maxIterations
+ *
+ * Total number of iterations to run the layout though.
+ */
+mxFastOrganicLayout.prototype.maxIterations = 0;
+
+/**
+ * Variable: iteration
+ *
+ * Current iteration count.
+ */
+mxFastOrganicLayout.prototype.iteration = 0;
+
+/**
+ * Variable: vertexArray
+ *
+ * An array of all vertices to be laid out.
+ */
+mxFastOrganicLayout.prototype.vertexArray;
+
+/**
+ * Variable: dispX
+ *
+ * An array of locally stored X co-ordinate displacements for the vertices.
+ */
+mxFastOrganicLayout.prototype.dispX;
+
+/**
+ * Variable: dispY
+ *
+ * An array of locally stored Y co-ordinate displacements for the vertices.
+ */
+mxFastOrganicLayout.prototype.dispY;
+
+/**
+ * Variable: cellLocation
+ *
+ * An array of locally stored co-ordinate positions for the vertices.
+ */
+mxFastOrganicLayout.prototype.cellLocation;
+
+/**
+ * Variable: radius
+ *
+ * The approximate radius of each cell, nodes only.
+ */
+mxFastOrganicLayout.prototype.radius;
+
+/**
+ * Variable: radiusSquared
+ *
+ * The approximate radius squared of each cell, nodes only.
+ */
+mxFastOrganicLayout.prototype.radiusSquared;
+
+/**
+ * Variable: isMoveable
+ *
+ * Array of booleans representing the movable states of the vertices.
+ */
+mxFastOrganicLayout.prototype.isMoveable;
+
+/**
+ * Variable: neighbours
+ *
+ * Local copy of cell neighbours.
+ */
+mxFastOrganicLayout.prototype.neighbours;
+
+/**
+ * Variable: indices
+ *
+ * Hashtable from cells to local indices.
+ */
+mxFastOrganicLayout.prototype.indices;
+
+/**
+ * Variable: allowedToRun
+ *
+ * Boolean flag that specifies if the layout is allowed to run. If this is
+ * set to false, then the layout exits in the following iteration.
+ */
+mxFastOrganicLayout.prototype.allowedToRun = true;
+
+/**
+ * Function: isVertexIgnored
+ *
+ * Returns a boolean indicating if the given <mxCell> should be ignored as a
+ * vertex. This returns true if the cell has no connections.
+ *
+ * Parameters:
+ *
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxFastOrganicLayout.prototype.isVertexIgnored = function(vertex)
+{
+ return mxGraphLayout.prototype.isVertexIgnored.apply(this, arguments) ||
+ this.graph.getConnections(vertex).length == 0;
+};
+
+/**
+ * Function: execute
+ *
+ * Implements <mxGraphLayout.execute>. This operates on all children of the
+ * given parent where <isVertexIgnored> returns false.
+ */
+mxFastOrganicLayout.prototype.execute = function(parent)
+{
+ var model = this.graph.getModel();
+ this.vertexArray = [];
+ var cells = this.graph.getChildVertices(parent);
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!this.isVertexIgnored(cells[i]))
+ {
+ this.vertexArray.push(cells[i]);
+ }
+ }
+
+ var initialBounds = (this.useInputOrigin) ?
+ this.graph.view.getBounds(this.vertexArray) :
+ null;
+ var n = this.vertexArray.length;
+
+ this.indices = [];
+ this.dispX = [];
+ this.dispY = [];
+ this.cellLocation = [];
+ this.isMoveable = [];
+ this.neighbours = [];
+ this.radius = [];
+ this.radiusSquared = [];
+
+ if (this.forceConstant < 0.001)
+ {
+ this.forceConstant = 0.001;
+ }
+
+ this.forceConstantSquared = this.forceConstant * this.forceConstant;
+
+ // Create a map of vertices first. This is required for the array of
+ // arrays called neighbours which holds, for each vertex, a list of
+ // ints which represents the neighbours cells to that vertex as
+ // the indices into vertexArray
+ for (var i = 0; i < this.vertexArray.length; i++)
+ {
+ var vertex = this.vertexArray[i];
+ this.cellLocation[i] = [];
+
+ // Set up the mapping from array indices to cells
+ var id = mxCellPath.create(vertex);
+ this.indices[id] = i;
+ var bounds = this.getVertexBounds(vertex);
+
+ // Set the X,Y value of the internal version of the cell to
+ // the center point of the vertex for better positioning
+ var width = bounds.width;
+ var height = bounds.height;
+
+ // Randomize (0, 0) locations
+ var x = bounds.x;
+ var y = bounds.y;
+
+ this.cellLocation[i][0] = x + width / 2.0;
+ this.cellLocation[i][1] = y + height / 2.0;
+ this.radius[i] = Math.min(width, height);
+ this.radiusSquared[i] = this.radius[i] * this.radius[i];
+ }
+
+ // Moves cell location back to top-left from center locations used in
+ // algorithm, resetting the edge points is part of the transaction
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < n; i++)
+ {
+ this.dispX[i] = 0;
+ this.dispY[i] = 0;
+ this.isMoveable[i] = this.isVertexMovable(this.vertexArray[i]);
+
+ // Get lists of neighbours to all vertices, translate the cells
+ // obtained in indices into vertexArray and store as an array
+ // against the orginial cell index
+ var edges = this.graph.getConnections(this.vertexArray[i], parent);
+ var cells = this.graph.getOpposites(edges, this.vertexArray[i]);
+ this.neighbours[i] = [];
+
+ for (var j = 0; j < cells.length; j++)
+ {
+ // Resets the points on the traversed edge
+ if (this.resetEdges)
+ {
+ this.graph.resetEdge(edges[j]);
+ }
+
+ if (this.disableEdgeStyle)
+ {
+ this.setEdgeStyleEnabled(edges[j], false);
+ }
+
+ // Looks the cell up in the indices dictionary
+ var id = mxCellPath.create(cells[j]);
+ var index = this.indices[id];
+
+ // Check the connected cell in part of the vertex list to be
+ // acted on by this layout
+ if (index != null)
+ {
+ this.neighbours[i][j] = index;
+ }
+
+ // Else if index of the other cell doesn't correspond to
+ // any cell listed to be acted upon in this layout. Set
+ // the index to the value of this vertex (a dummy self-loop)
+ // so the attraction force of the edge is not calculated
+ else
+ {
+ this.neighbours[i][j] = i;
+ }
+ }
+ }
+ this.temperature = this.initialTemp;
+
+ // If max number of iterations has not been set, guess it
+ if (this.maxIterations == 0)
+ {
+ this.maxIterations = 20 * Math.sqrt(n);
+ }
+
+ // Main iteration loop
+ for (this.iteration = 0; this.iteration < this.maxIterations; this.iteration++)
+ {
+ if (!this.allowedToRun)
+ {
+ return;
+ }
+
+ // Calculate repulsive forces on all vertices
+ this.calcRepulsion();
+
+ // Calculate attractive forces through edges
+ this.calcAttraction();
+
+ this.calcPositions();
+ this.reduceTemperature();
+ }
+
+ var minx = null;
+ var miny = null;
+
+ for (var i = 0; i < this.vertexArray.length; i++)
+ {
+ var vertex = this.vertexArray[i];
+
+ if (this.isVertexMovable(vertex))
+ {
+ var bounds = this.getVertexBounds(vertex);
+
+ if (bounds != null)
+ {
+ this.cellLocation[i][0] -= bounds.width / 2.0;
+ this.cellLocation[i][1] -= bounds.height / 2.0;
+
+ var x = this.graph.snap(this.cellLocation[i][0]);
+ var y = this.graph.snap(this.cellLocation[i][1]);
+
+ this.setVertexLocation(vertex, x, y);
+
+ if (minx == null)
+ {
+ minx = x;
+ }
+ else
+ {
+ minx = Math.min(minx, x);
+ }
+
+ if (miny == null)
+ {
+ miny = y;
+ }
+ else
+ {
+ miny = Math.min(miny, y);
+ }
+ }
+ }
+ }
+
+ // Modifies the cloned geometries in-place. Not needed
+ // to clone the geometries again as we're in the same
+ // undoable change.
+ var dx = -(minx || 0) + 1;
+ var dy = -(miny || 0) + 1;
+
+ if (initialBounds != null)
+ {
+ dx += initialBounds.x;
+ dy += initialBounds.y;
+ }
+
+ this.graph.moveCells(this.vertexArray, dx, dy);
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+};
+
+/**
+ * Function: calcPositions
+ *
+ * Takes the displacements calculated for each cell and applies them to the
+ * local cache of cell positions. Limits the displacement to the current
+ * temperature.
+ */
+mxFastOrganicLayout.prototype.calcPositions = function()
+{
+ for (var index = 0; index < this.vertexArray.length; index++)
+ {
+ if (this.isMoveable[index])
+ {
+ // Get the distance of displacement for this node for this
+ // iteration
+ var deltaLength = Math.sqrt(this.dispX[index] * this.dispX[index] +
+ this.dispY[index] * this.dispY[index]);
+
+ if (deltaLength < 0.001)
+ {
+ deltaLength = 0.001;
+ }
+
+ // Scale down by the current temperature if less than the
+ // displacement distance
+ var newXDisp = this.dispX[index] / deltaLength
+ * Math.min(deltaLength, this.temperature);
+
+ var newYDisp = this.dispY[index] / deltaLength
+ * Math.min(deltaLength, this.temperature);
+
+ // reset displacements
+ this.dispX[index] = 0;
+ this.dispY[index] = 0;
+
+ // Update the cached cell locations
+ this.cellLocation[index][0] += newXDisp;
+ this.cellLocation[index][1] += newYDisp;
+ }
+ }
+};
+
+/**
+ * Function: calcAttraction
+ *
+ * Calculates the attractive forces between all laid out nodes linked by
+ * edges
+ */
+mxFastOrganicLayout.prototype.calcAttraction = function()
+{
+ // Check the neighbours of each vertex and calculate the attractive
+ // force of the edge connecting them
+ for (var i = 0; i < this.vertexArray.length; i++)
+ {
+ for (var k = 0; k < this.neighbours[i].length; k++)
+ {
+ // Get the index of the othe cell in the vertex array
+ var j = this.neighbours[i][k];
+
+ // Do not proceed self-loops
+ if (i != j &&
+ this.isMoveable[i] &&
+ this.isMoveable[j])
+ {
+ var xDelta = this.cellLocation[i][0] - this.cellLocation[j][0];
+ var yDelta = this.cellLocation[i][1] - this.cellLocation[j][1];
+
+ // The distance between the nodes
+ var deltaLengthSquared = xDelta * xDelta + yDelta
+ * yDelta - this.radiusSquared[i] - this.radiusSquared[j];
+
+ if (deltaLengthSquared < this.minDistanceLimitSquared)
+ {
+ deltaLengthSquared = this.minDistanceLimitSquared;
+ }
+
+ var deltaLength = Math.sqrt(deltaLengthSquared);
+ var force = (deltaLengthSquared) / this.forceConstant;
+
+ var displacementX = (xDelta / deltaLength) * force;
+ var displacementY = (yDelta / deltaLength) * force;
+
+ this.dispX[i] -= displacementX;
+ this.dispY[i] -= displacementY;
+
+ this.dispX[j] += displacementX;
+ this.dispY[j] += displacementY;
+ }
+ }
+ }
+};
+
+/**
+ * Function: calcRepulsion
+ *
+ * Calculates the repulsive forces between all laid out nodes
+ */
+mxFastOrganicLayout.prototype.calcRepulsion = function()
+{
+ var vertexCount = this.vertexArray.length;
+
+ for (var i = 0; i < vertexCount; i++)
+ {
+ for (var j = i; j < vertexCount; j++)
+ {
+ // Exits if the layout is no longer allowed to run
+ if (!this.allowedToRun)
+ {
+ return;
+ }
+
+ if (j != i &&
+ this.isMoveable[i] &&
+ this.isMoveable[j])
+ {
+ var xDelta = this.cellLocation[i][0] - this.cellLocation[j][0];
+ var yDelta = this.cellLocation[i][1] - this.cellLocation[j][1];
+
+ if (xDelta == 0)
+ {
+ xDelta = 0.01 + Math.random();
+ }
+
+ if (yDelta == 0)
+ {
+ yDelta = 0.01 + Math.random();
+ }
+
+ // Distance between nodes
+ var deltaLength = Math.sqrt((xDelta * xDelta)
+ + (yDelta * yDelta));
+ var deltaLengthWithRadius = deltaLength - this.radius[i]
+ - this.radius[j];
+
+ if (deltaLengthWithRadius > this.maxDistanceLimit)
+ {
+ // Ignore vertices too far apart
+ continue;
+ }
+
+ if (deltaLengthWithRadius < this.minDistanceLimit)
+ {
+ deltaLengthWithRadius = this.minDistanceLimit;
+ }
+
+ var force = this.forceConstantSquared / deltaLengthWithRadius;
+
+ var displacementX = (xDelta / deltaLength) * force;
+ var displacementY = (yDelta / deltaLength) * force;
+
+ this.dispX[i] += displacementX;
+ this.dispY[i] += displacementY;
+
+ this.dispX[j] -= displacementX;
+ this.dispY[j] -= displacementY;
+ }
+ }
+ }
+};
+
+/**
+ * Function: reduceTemperature
+ *
+ * Reduces the temperature of the layout from an initial setting in a linear
+ * fashion to zero.
+ */
+mxFastOrganicLayout.prototype.reduceTemperature = function()
+{
+ this.temperature = this.initialTemp * (1.0 - this.iteration / this.maxIterations);
+};
diff --git a/src/js/layout/mxGraphLayout.js b/src/js/layout/mxGraphLayout.js
new file mode 100644
index 0000000..c9f5f32
--- /dev/null
+++ b/src/js/layout/mxGraphLayout.js
@@ -0,0 +1,503 @@
+/**
+ * $Id: mxGraphLayout.js,v 1.48 2012-08-21 17:22:21 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphLayout
+ *
+ * Base class for all layout algorithms in mxGraph. Main public functions are
+ * <move> for handling a moved cell within a layouted parent, and <execute> for
+ * running the layout on a given parent cell.
+ *
+ * Known Subclasses:
+ *
+ * <mxCircleLayout>, <mxCompactTreeLayout>, <mxCompositeLayout>,
+ * <mxFastOrganicLayout>, <mxParallelEdgeLayout>, <mxPartitionLayout>,
+ * <mxStackLayout>
+ *
+ * Constructor: mxGraphLayout
+ *
+ * Constructs a new layout using the given layouts.
+ *
+ * Arguments:
+ *
+ * graph - Enclosing
+ */
+function mxGraphLayout(graph)
+{
+ this.graph = graph;
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphLayout.prototype.graph = null;
+
+/**
+ * Variable: useBoundingBox
+ *
+ * Boolean indicating if the bounding box of the label should be used if
+ * its available. Default is true.
+ */
+mxGraphLayout.prototype.useBoundingBox = true;
+
+/**
+ * Variable: parent
+ *
+ * The parent cell of the layout, if any
+ */
+mxGraphLayout.prototype.parent = null;
+
+/**
+ * Function: moveCell
+ *
+ * Notified when a cell is being moved in a parent that has automatic
+ * layout to update the cell state (eg. index) so that the outcome of the
+ * layout will position the vertex as close to the point (x, y) as
+ * possible.
+ *
+ * Empty implementation.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> which has been moved.
+ * x - X-coordinate of the new cell location.
+ * y - Y-coordinate of the new cell location.
+ */
+mxGraphLayout.prototype.moveCell = function(cell, x, y) { };
+
+/**
+ * Function: execute
+ *
+ * Executes the layout algorithm for the children of the given parent.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose children should be layed out.
+ */
+mxGraphLayout.prototype.execute = function(parent) { };
+
+/**
+ * Function: getGraph
+ *
+ * Returns the graph that this layout operates on.
+ */
+mxGraphLayout.prototype.getGraph = function()
+{
+ return this.graph;
+};
+
+/**
+ * Function: getConstraint
+ *
+ * Returns the constraint for the given key and cell. The optional edge and
+ * source arguments are used to return inbound and outgoing routing-
+ * constraints for the given edge and vertex. This implementation always
+ * returns the value for the given key in the style of the given cell.
+ *
+ * Parameters:
+ *
+ * key - Key of the constraint to be returned.
+ * cell - <mxCell> whose constraint should be returned.
+ * edge - Optional <mxCell> that represents the connection whose constraint
+ * should be returned. Default is null.
+ * source - Optional boolean that specifies if the connection is incoming
+ * or outgoing. Default is null.
+ */
+mxGraphLayout.prototype.getConstraint = function(key, cell, edge, source)
+{
+ var state = this.graph.view.getState(cell);
+ var style = (state != null) ? state.style : this.graph.getCellStyle(cell);
+
+ return (style != null) ? style[key] : null;
+};
+
+/**
+ * Function: traverse
+ *
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ *
+ * Example:
+ *
+ * (code)
+ * mxLog.show();
+ * var cell = graph.getSelectionCell();
+ * graph.traverse(cell, false, function(vertex, edge)
+ * {
+ * mxLog.debug(graph.getLabel(vertex));
+ * });
+ * (end)
+ *
+ * Parameters:
+ *
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - Optional boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * func - Visitor function that takes the current vertex and the incoming
+ * edge as arguments. The traversal stops if the function returns false.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * visited - Optional array of cell paths for the visited cells.
+ */
+mxGraphLayout.traverse = function(vertex, directed, func, edge, visited)
+{
+ if (func != null && vertex != null)
+ {
+ directed = (directed != null) ? directed : true;
+ visited = visited || [];
+ var id = mxCellPath.create(vertex);
+
+ if (visited[id] == null)
+ {
+ visited[id] = vertex;
+ var result = func(vertex, edge);
+
+ if (result == null || result)
+ {
+ var edgeCount = this.graph.model.getEdgeCount(vertex);
+
+ if (edgeCount > 0)
+ {
+ for (var i = 0; i < edgeCount; i++)
+ {
+ var e = this.graph.model.getEdgeAt(vertex, i);
+ var isSource = this.graph.model.getTerminal(e, true) == vertex;
+
+ if (!directed || isSource)
+ {
+ var next = this.graph.view.getVisibleTerminal(e, !isSource);
+ this.traverse(next, directed, func, e, visited);
+ }
+ }
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Function: isVertexMovable
+ *
+ * Returns a boolean indicating if the given <mxCell> is movable or
+ * bendable by the algorithm. This implementation returns true if the given
+ * cell is movable in the graph.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose movable state should be returned.
+ */
+mxGraphLayout.prototype.isVertexMovable = function(cell)
+{
+ return this.graph.isCellMovable(cell);
+};
+
+/**
+ * Function: isVertexIgnored
+ *
+ * Returns a boolean indicating if the given <mxCell> should be ignored by
+ * the algorithm. This implementation returns false for all vertices.
+ *
+ * Parameters:
+ *
+ * vertex - <mxCell> whose ignored state should be returned.
+ */
+mxGraphLayout.prototype.isVertexIgnored = function(vertex)
+{
+ return !this.graph.getModel().isVertex(vertex) ||
+ !this.graph.isCellVisible(vertex);
+};
+
+/**
+ * Function: isEdgeIgnored
+ *
+ * Returns a boolean indicating if the given <mxCell> should be ignored by
+ * the algorithm. This implementation returns false for all vertices.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose ignored state should be returned.
+ */
+mxGraphLayout.prototype.isEdgeIgnored = function(edge)
+{
+ var model = this.graph.getModel();
+
+ return !model.isEdge(edge) ||
+ !this.graph.isCellVisible(edge) ||
+ model.getTerminal(edge, true) == null ||
+ model.getTerminal(edge, false) == null;
+};
+
+/**
+ * Function: setEdgeStyleEnabled
+ *
+ * Disables or enables the edge style of the given edge.
+ */
+mxGraphLayout.prototype.setEdgeStyleEnabled = function(edge, value)
+{
+ this.graph.setCellStyles(mxConstants.STYLE_NOEDGESTYLE,
+ (value) ? '0' : '1', [edge]);
+};
+
+/**
+ * Function: setOrthogonalEdge
+ *
+ * Disables or enables orthogonal end segments of the given edge.
+ */
+mxGraphLayout.prototype.setOrthogonalEdge = function(edge, value)
+{
+ this.graph.setCellStyles(mxConstants.STYLE_ORTHOGONAL,
+ (value) ? '1' : '0', [edge]);
+};
+
+/**
+ * Function: getParentOffset
+ *
+ * Determines the offset of the given parent to the parent
+ * of the layout
+ */
+mxGraphLayout.prototype.getParentOffset = function(parent)
+{
+ var result = new mxPoint();
+
+ if (parent != null && parent != this.parent)
+ {
+ var model = this.graph.getModel();
+
+ if (model.isAncestor(this.parent, parent))
+ {
+ var parentGeo = model.getGeometry(parent);
+
+ while (parent != this.parent)
+ {
+ result.x = result.x + parentGeo.x;
+ result.y = result.y + parentGeo.y;
+
+ parent = model.getParent(parent);;
+ parentGeo = model.getGeometry(parent);
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: setEdgePoints
+ *
+ * Replaces the array of mxPoints in the geometry of the given edge
+ * with the given array of mxPoints.
+ */
+mxGraphLayout.prototype.setEdgePoints = function(edge, points)
+{
+ if (edge != null)
+ {
+ var model = this.graph.model;
+ var geometry = model.getGeometry(edge);
+
+ if (geometry == null)
+ {
+ geometry = new mxGeometry();
+ geometry.setRelative(true);
+ }
+ else
+ {
+ geometry = geometry.clone();
+ }
+
+ if (this.parent != null && points != null)
+ {
+ var parent = model.getParent(edge);
+
+ var parentOffset = this.getParentOffset(parent);
+
+ for (var i = 0; i < points.length; i++)
+ {
+ points[i].x = points[i].x - parentOffset.x;
+ points[i].y = points[i].y - parentOffset.y;
+ }
+ }
+
+ geometry.points = points;
+ model.setGeometry(edge, geometry);
+ }
+};
+
+/**
+ * Function: setVertexLocation
+ *
+ * Sets the new position of the given cell taking into account the size of
+ * the bounding box if <useBoundingBox> is true. The change is only carried
+ * out if the new location is not equal to the existing location, otherwise
+ * the geometry is not replaced with an updated instance. The new or old
+ * bounds are returned (including overlapping labels).
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose geometry is to be set.
+ * x - Integer that defines the x-coordinate of the new location.
+ * y - Integer that defines the y-coordinate of the new location.
+ */
+mxGraphLayout.prototype.setVertexLocation = function(cell, x, y)
+{
+ var model = this.graph.getModel();
+ var geometry = model.getGeometry(cell);
+ var result = null;
+
+ if (geometry != null)
+ {
+ result = new mxRectangle(x, y, geometry.width, geometry.height);
+
+ // Checks for oversize labels and shifts the result
+ // TODO: Use mxUtils.getStringSize for label bounds
+ if (this.useBoundingBox)
+ {
+ var state = this.graph.getView().getState(cell);
+
+ if (state != null && state.text != null && state.text.boundingBox != null)
+ {
+ var scale = this.graph.getView().scale;
+ var box = state.text.boundingBox;
+
+ if (state.text.boundingBox.x < state.x)
+ {
+ x += (state.x - box.x) / scale;
+ result.width = box.width;
+ }
+
+ if (state.text.boundingBox.y < state.y)
+ {
+ y += (state.y - box.y) / scale;
+ result.height = box.height;
+ }
+ }
+ }
+
+ if (this.parent != null)
+ {
+ var parent = model.getParent(cell);
+
+ if (parent != null && parent != this.parent)
+ {
+ var parentOffset = this.getParentOffset(parent);
+
+ x = x - parentOffset.x;
+ y = y - parentOffset.y;
+ }
+ }
+
+ if (geometry.x != x || geometry.y != y)
+ {
+ geometry = geometry.clone();
+ geometry.x = x;
+ geometry.y = y;
+
+ model.setGeometry(cell, geometry);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getVertexBounds
+ *
+ * Returns an <mxRectangle> that defines the bounds of the given cell or
+ * the bounding box if <useBoundingBox> is true.
+ */
+mxGraphLayout.prototype.getVertexBounds = function(cell)
+{
+ var geo = this.graph.getModel().getGeometry(cell);
+
+ // Checks for oversize label bounding box and corrects
+ // the return value accordingly
+ // TODO: Use mxUtils.getStringSize for label bounds
+ if (this.useBoundingBox)
+ {
+ var state = this.graph.getView().getState(cell);
+
+ if (state != null && state.text != null && state.text.boundingBox != null)
+ {
+ var scale = this.graph.getView().scale;
+ var tmp = state.text.boundingBox;
+
+ var dx0 = Math.max(state.x - tmp.x, 0) / scale;
+ var dy0 = Math.max(state.y - tmp.y, 0) / scale;
+ var dx1 = Math.max((tmp.x + tmp.width) - (state.x + state.width), 0) / scale;
+ var dy1 = Math.max((tmp.y + tmp.height) - (state.y + state.height), 0) / scale;
+
+ geo = new mxRectangle(geo.x - dx0, geo.y - dy0,
+ geo.width + dx0 + dx1, geo.height + dy0 + dy1);
+ }
+ }
+
+ if (this.parent != null)
+ {
+ var parent = this.graph.getModel().getParent(cell);
+ geo = geo.clone();
+
+ if (parent != null && parent != this.parent)
+ {
+ var parentOffset = this.getParentOffset(parent);
+ geo.x = geo.x + parentOffset.x;
+ geo.y = geo.y + parentOffset.y;
+ }
+ }
+
+ return new mxRectangle(geo.x, geo.y, geo.width, geo.height);
+};
+
+/**
+ * Function: arrangeGroups
+ *
+ * Updates the bounds of the given groups to include all children. Call
+ * this with the groups in parent to child order, top-most group first, eg.
+ *
+ * arrangeGroups(graph, mxUtils.sortCells(Arrays.asList(
+ * new Object[] { v1, v3 }), true).toArray(), 10);
+ */
+mxGraphLayout.prototype.arrangeGroups = function(groups, border)
+{
+ this.graph.getModel().beginUpdate();
+ try
+ {
+ for (var i = groups.length - 1; i >= 0; i--)
+ {
+ var group = groups[i];
+ var children = this.graph.getChildVertices(group);
+ var bounds = this.graph.getBoundingBoxFromGeometry(children);
+ var geometry = this.graph.getCellGeometry(group);
+ var left = 0;
+ var top = 0;
+
+ // Adds the size of the title area for swimlanes
+ if (this.graph.isSwimlane(group))
+ {
+ var size = this.graph.getStartSize(group);
+ left = size.width;
+ top = size.height;
+ }
+
+ if (bounds != null && geometry != null)
+ {
+ geometry = geometry.clone();
+ geometry.x = geometry.x + bounds.x - border - left;
+ geometry.y = geometry.y + bounds.y - border - top;
+ geometry.width = bounds.width + 2 * border + left;
+ geometry.height = bounds.height + 2 * border + top;
+ this.graph.getModel().setGeometry(group, geometry);
+ this.graph.moveCells(children, border + left - bounds.x,
+ border + top - bounds.y);
+ }
+ }
+ }
+ finally
+ {
+ this.graph.getModel().endUpdate();
+ }
+};
diff --git a/src/js/layout/mxParallelEdgeLayout.js b/src/js/layout/mxParallelEdgeLayout.js
new file mode 100644
index 0000000..e1ad57c
--- /dev/null
+++ b/src/js/layout/mxParallelEdgeLayout.js
@@ -0,0 +1,198 @@
+/**
+ * $Id: mxParallelEdgeLayout.js,v 1.24 2012-03-27 15:03:34 david Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxParallelEdgeLayout
+ *
+ * Extends <mxGraphLayout> for arranging parallel edges. This layout works
+ * on edges for all pairs of vertices where there is more than one edge
+ * connecting the latter.
+ *
+ * Example:
+ *
+ * (code)
+ * var layout = new mxParallelEdgeLayout(graph);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ *
+ * Constructor: mxCompactTreeLayout
+ *
+ * Constructs a new fast organic layout for the specified graph.
+ */
+function mxParallelEdgeLayout(graph)
+{
+ mxGraphLayout.call(this, graph);
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxParallelEdgeLayout.prototype = new mxGraphLayout();
+mxParallelEdgeLayout.prototype.constructor = mxParallelEdgeLayout;
+
+/**
+ * Variable: spacing
+ *
+ * Defines the spacing between the parallels. Default is 20.
+ */
+mxParallelEdgeLayout.prototype.spacing = 20;
+
+/**
+ * Function: execute
+ *
+ * Implements <mxGraphLayout.execute>.
+ */
+mxParallelEdgeLayout.prototype.execute = function(parent)
+{
+ var lookup = this.findParallels(parent);
+
+ this.graph.model.beginUpdate();
+ try
+ {
+ for (var i in lookup)
+ {
+ var parallels = lookup[i];
+
+ if (parallels.length > 1)
+ {
+ this.layout(parallels);
+ }
+ }
+ }
+ finally
+ {
+ this.graph.model.endUpdate();
+ }
+};
+
+/**
+ * Function: findParallels
+ *
+ * Finds the parallel edges in the given parent.
+ */
+mxParallelEdgeLayout.prototype.findParallels = function(parent)
+{
+ var model = this.graph.getModel();
+ var lookup = [];
+ var childCount = model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(parent, i);
+
+ if (!this.isEdgeIgnored(child))
+ {
+ var id = this.getEdgeId(child);
+
+ if (id != null)
+ {
+ if (lookup[id] == null)
+ {
+ lookup[id] = [];
+ }
+
+ lookup[id].push(child);
+ }
+ }
+ }
+
+ return lookup;
+};
+
+/**
+ * Function: getEdgeId
+ *
+ * Returns a unique ID for the given edge. The id is independent of the
+ * edge direction and is built using the visible terminal of the given
+ * edge.
+ */
+mxParallelEdgeLayout.prototype.getEdgeId = function(edge)
+{
+ var view = this.graph.getView();
+
+ var state = view.getState(edge);
+
+ var src = (state != null) ? state.getVisibleTerminal(true) : view.getVisibleTerminal(edge, true);
+ var trg = (state != null) ? state.getVisibleTerminal(false) : view.getVisibleTerminal(edge, false);
+
+ if (src != null && trg != null)
+ {
+ src = mxCellPath.create(src);
+ trg = mxCellPath.create(trg);
+
+ return (src > trg) ? trg+'-'+src : src+'-'+trg;
+ }
+
+ return null;
+};
+
+/**
+ * Function: layout
+ *
+ * Lays out the parallel edges in the given array.
+ */
+mxParallelEdgeLayout.prototype.layout = function(parallels)
+{
+ var edge = parallels[0];
+ var model = this.graph.getModel();
+
+ var src = model.getGeometry(model.getTerminal(edge, true));
+ var trg = model.getGeometry(model.getTerminal(edge, false));
+
+ // Routes multiple loops
+ if (src == trg)
+ {
+ var x0 = src.x + src.width + this.spacing;
+ var y0 = src.y + src.height / 2;
+
+ for (var i = 0; i < parallels.length; i++)
+ {
+ this.route(parallels[i], x0, y0);
+ x0 += this.spacing;
+ }
+ }
+ else if (src != null && trg != null)
+ {
+ // Routes parallel edges
+ var scx = src.x + src.width / 2;
+ var scy = src.y + src.height / 2;
+
+ var tcx = trg.x + trg.width / 2;
+ var tcy = trg.y + trg.height / 2;
+
+ var dx = tcx - scx;
+ var dy = tcy - scy;
+
+ var len = Math.sqrt(dx*dx+dy*dy);
+
+ var x0 = scx + dx / 2;
+ var y0 = scy + dy / 2;
+
+ var nx = dy * this.spacing / len;
+ var ny = dx * this.spacing / len;
+
+ x0 += nx * (parallels.length - 1) / 2;
+ y0 -= ny * (parallels.length - 1) / 2;
+
+ for (var i = 0; i < parallels.length; i++)
+ {
+ this.route(parallels[i], x0, y0);
+ x0 -= nx;
+ y0 += ny;
+ }
+ }
+};
+
+/**
+ * Function: route
+ *
+ * Routes the given edge via the given point.
+ */
+mxParallelEdgeLayout.prototype.route = function(edge, x, y)
+{
+ if (this.graph.isCellMovable(edge))
+ {
+ this.setEdgePoints(edge, [new mxPoint(x, y)]);
+ }
+};
diff --git a/src/js/layout/mxPartitionLayout.js b/src/js/layout/mxPartitionLayout.js
new file mode 100644
index 0000000..d3592f8
--- /dev/null
+++ b/src/js/layout/mxPartitionLayout.js
@@ -0,0 +1,240 @@
+/**
+ * $Id: mxPartitionLayout.js,v 1.25 2010-01-04 11:18:25 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxPartitionLayout
+ *
+ * Extends <mxGraphLayout> for partitioning the parent cell vertically or
+ * horizontally by filling the complete area with the child cells. A horizontal
+ * layout partitions the height of the given parent whereas a a non-horizontal
+ * layout partitions the width. If the parent is a layer (that is, a child of
+ * the root node), then the current graph size is partitioned. The children do
+ * not need to be connected for this layout to work.
+ *
+ * Example:
+ *
+ * (code)
+ * var layout = new mxPartitionLayout(graph, true, 10, 20);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ *
+ * Constructor: mxPartitionLayout
+ *
+ * Constructs a new stack layout layout for the specified graph,
+ * spacing, orientation and offset.
+ */
+function mxPartitionLayout(graph, horizontal, spacing, border)
+{
+ mxGraphLayout.call(this, graph);
+ this.horizontal = (horizontal != null) ? horizontal : true;
+ this.spacing = spacing || 0;
+ this.border = border || 0;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxPartitionLayout.prototype = new mxGraphLayout();
+mxPartitionLayout.prototype.constructor = mxPartitionLayout;
+
+/**
+ * Variable: horizontal
+ *
+ * Boolean indicating the direction in which the space is partitioned.
+ * Default is true.
+ */
+mxPartitionLayout.prototype.horizontal = null;
+
+/**
+ * Variable: spacing
+ *
+ * Integer that specifies the absolute spacing in pixels between the
+ * children. Default is 0.
+ */
+mxPartitionLayout.prototype.spacing = null;
+
+/**
+ * Variable: border
+ *
+ * Integer that specifies the absolute inset in pixels for the parent that
+ * contains the children. Default is 0.
+ */
+mxPartitionLayout.prototype.border = null;
+
+/**
+ * Variable: resizeVertices
+ *
+ * Boolean that specifies if vertices should be resized. Default is true.
+ */
+mxPartitionLayout.prototype.resizeVertices = true;
+
+/**
+ * Function: isHorizontal
+ *
+ * Returns <horizontal>.
+ */
+mxPartitionLayout.prototype.isHorizontal = function()
+{
+ return this.horizontal;
+};
+
+/**
+ * Function: moveCell
+ *
+ * Implements <mxGraphLayout.moveCell>.
+ */
+mxPartitionLayout.prototype.moveCell = function(cell, x, y)
+{
+ var model = this.graph.getModel();
+ var parent = model.getParent(cell);
+
+ if (cell != null &&
+ parent != null)
+ {
+ var i = 0;
+ var last = 0;
+ var childCount = model.getChildCount(parent);
+
+ // Finds index of the closest swimlane
+ // TODO: Take into account the orientation
+ for (i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(parent, i);
+ var bounds = this.getVertexBounds(child);
+
+ if (bounds != null)
+ {
+ var tmp = bounds.x + bounds.width / 2;
+
+ if (last < x && tmp > x)
+ {
+ break;
+ }
+
+ last = tmp;
+ }
+ }
+
+ // Changes child order in parent
+ var idx = parent.getIndex(cell);
+ idx = Math.max(0, i - ((i > idx) ? 1 : 0));
+
+ model.add(parent, cell, idx);
+ }
+};
+
+/**
+ * Function: execute
+ *
+ * Implements <mxGraphLayout.execute>. All children where <isVertexIgnored>
+ * returns false and <isVertexMovable> returns true are modified.
+ */
+mxPartitionLayout.prototype.execute = function(parent)
+{
+ var horizontal = this.isHorizontal();
+ var model = this.graph.getModel();
+ var pgeo = model.getGeometry(parent);
+
+ // Handles special case where the parent is either a layer with no
+ // geometry or the current root of the view in which case the size
+ // of the graph's container will be used.
+ if (this.graph.container != null &&
+ ((pgeo == null &&
+ model.isLayer(parent)) ||
+ parent == this.graph.getView().currentRoot))
+ {
+ var width = this.graph.container.offsetWidth - 1;
+ var height = this.graph.container.offsetHeight - 1;
+ pgeo = new mxRectangle(0, 0, width, height);
+ }
+
+ if (pgeo != null)
+ {
+ var children = [];
+ var childCount = model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(parent, i);
+
+ if (!this.isVertexIgnored(child) &&
+ this.isVertexMovable(child))
+ {
+ children.push(child);
+ }
+ }
+
+ var n = children.length;
+
+ if (n > 0)
+ {
+ var x0 = this.border;
+ var y0 = this.border;
+ var other = (horizontal) ? pgeo.height : pgeo.width;
+ other -= 2 * this.border;
+
+ var size = (this.graph.isSwimlane(parent)) ?
+ this.graph.getStartSize(parent) :
+ new mxRectangle();
+
+ other -= (horizontal) ? size.height : size.width;
+ x0 = x0 + size.width;
+ y0 = y0 + size.height;
+
+ var tmp = this.border + (n - 1) * this.spacing;
+ var value = (horizontal) ?
+ ((pgeo.width - x0 - tmp) / n) :
+ ((pgeo.height - y0 - tmp) / n);
+
+ // Avoids negative values, that is values where the sum of the
+ // spacing plus the border is larger then the available space
+ if (value > 0)
+ {
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < n; i++)
+ {
+ var child = children[i];
+ var geo = model.getGeometry(child);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+ geo.x = x0;
+ geo.y = y0;
+
+ if (horizontal)
+ {
+ if (this.resizeVertices)
+ {
+ geo.width = value;
+ geo.height = other;
+ }
+
+ x0 += value + this.spacing;
+ }
+ else
+ {
+ if (this.resizeVertices)
+ {
+ geo.height = value;
+ geo.width = other;
+ }
+
+ y0 += value + this.spacing;
+ }
+
+ model.setGeometry(child, geo);
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+ }
+ }
+};
diff --git a/src/js/layout/mxStackLayout.js b/src/js/layout/mxStackLayout.js
new file mode 100644
index 0000000..7f5cd47
--- /dev/null
+++ b/src/js/layout/mxStackLayout.js
@@ -0,0 +1,381 @@
+/**
+ * $Id: mxStackLayout.js,v 1.47 2012-12-14 08:54:34 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxStackLayout
+ *
+ * Extends <mxGraphLayout> to create a horizontal or vertical stack of the
+ * child vertices. The children do not need to be connected for this layout
+ * to work.
+ *
+ * Example:
+ *
+ * (code)
+ * var layout = new mxStackLayout(graph, true);
+ * layout.execute(graph.getDefaultParent());
+ * (end)
+ *
+ * Constructor: mxStackLayout
+ *
+ * Constructs a new stack layout layout for the specified graph,
+ * spacing, orientation and offset.
+ */
+function mxStackLayout(graph, horizontal, spacing, x0, y0, border)
+{
+ mxGraphLayout.call(this, graph);
+ this.horizontal = (horizontal != null) ? horizontal : true;
+ this.spacing = (spacing != null) ? spacing : 0;
+ this.x0 = (x0 != null) ? x0 : 0;
+ this.y0 = (y0 != null) ? y0 : 0;
+ this.border = (border != null) ? border : 0;
+};
+
+/**
+ * Extends mxGraphLayout.
+ */
+mxStackLayout.prototype = new mxGraphLayout();
+mxStackLayout.prototype.constructor = mxStackLayout;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies the orientation of the layout. Default is true.
+ */
+mxStackLayout.prototype.horizontal = null;
+
+/**
+ * Variable: spacing
+ *
+ * Specifies the spacing between the cells. Default is 0.
+ */
+mxStackLayout.prototype.spacing = null;
+
+/**
+ * Variable: x0
+ *
+ * Specifies the horizontal origin of the layout. Default is 0.
+ */
+mxStackLayout.prototype.x0 = null;
+
+/**
+ * Variable: y0
+ *
+ * Specifies the vertical origin of the layout. Default is 0.
+ */
+mxStackLayout.prototype.y0 = null;
+
+/**
+ * Variable: border
+ *
+ * Border to be added if fill is true. Default is 0.
+ */
+mxStackLayout.prototype.border = 0;
+
+/**
+ * Variable: keepFirstLocation
+ *
+ * Boolean indicating if the location of the first cell should be
+ * kept, that is, it will not be moved to x0 or y0.
+ */
+mxStackLayout.prototype.keepFirstLocation = false;
+
+/**
+ * Variable: fill
+ *
+ * Boolean indicating if dimension should be changed to fill out the parent
+ * cell. Default is false.
+ */
+mxStackLayout.prototype.fill = false;
+
+/**
+ * Variable: resizeParent
+ *
+ * If the parent should be resized to match the width/height of the
+ * stack. Default is false.
+ */
+mxStackLayout.prototype.resizeParent = false;
+
+/**
+ * Variable: resizeLast
+ *
+ * If the last element should be resized to fill out the parent. Default is
+ * false. If <resizeParent> is true then this is ignored.
+ */
+mxStackLayout.prototype.resizeLast = false;
+
+/**
+ * Variable: wrap
+ *
+ * Value at which a new column or row should be created. Default is null.
+ */
+mxStackLayout.prototype.wrap = null;
+
+/**
+ * Function: isHorizontal
+ *
+ * Returns <horizontal>.
+ */
+mxStackLayout.prototype.isHorizontal = function()
+{
+ return this.horizontal;
+};
+
+/**
+ * Function: moveCell
+ *
+ * Implements <mxGraphLayout.moveCell>.
+ */
+mxStackLayout.prototype.moveCell = function(cell, x, y)
+{
+ var model = this.graph.getModel();
+ var parent = model.getParent(cell);
+ var horizontal = this.isHorizontal();
+
+ if (cell != null && parent != null)
+ {
+ var i = 0;
+ var last = 0;
+ var childCount = model.getChildCount(parent);
+ var value = (horizontal) ? x : y;
+ var pstate = this.graph.getView().getState(parent);
+
+ if (pstate != null)
+ {
+ value -= (horizontal) ? pstate.x : pstate.y;
+ }
+
+ for (i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(parent, i);
+
+ if (child != cell)
+ {
+ var bounds = model.getGeometry(child);
+
+ if (bounds != null)
+ {
+ var tmp = (horizontal) ?
+ bounds.x + bounds.width / 2 :
+ bounds.y + bounds.height / 2;
+
+ if (last < value && tmp > value)
+ {
+ break;
+ }
+
+ last = tmp;
+ }
+ }
+ }
+
+ // Changes child order in parent
+ var idx = parent.getIndex(cell);
+ idx = Math.max(0, i - ((i > idx) ? 1 : 0));
+
+ model.add(parent, cell, idx);
+ }
+};
+
+/**
+ * Function: getParentSize
+ *
+ * Returns the size for the parent container or the size of the graph
+ * container if the parent is a layer or the root of the model.
+ */
+mxStackLayout.prototype.getParentSize = function(parent)
+{
+ var model = this.graph.getModel();
+ var pgeo = model.getGeometry(parent);
+
+ // Handles special case where the parent is either a layer with no
+ // geometry or the current root of the view in which case the size
+ // of the graph's container will be used.
+ if (this.graph.container != null && ((pgeo == null &&
+ model.isLayer(parent)) || parent == this.graph.getView().currentRoot))
+ {
+ var width = this.graph.container.offsetWidth - 1;
+ var height = this.graph.container.offsetHeight - 1;
+ pgeo = new mxRectangle(0, 0, width, height);
+ }
+
+ return pgeo;
+};
+
+/**
+ * Function: execute
+ *
+ * Implements <mxGraphLayout.execute>.
+ *
+ * Only children where <isVertexIgnored> returns false are taken into
+ * account.
+ */
+mxStackLayout.prototype.execute = function(parent)
+{
+ if (parent != null)
+ {
+ var horizontal = this.isHorizontal();
+ var model = this.graph.getModel();
+ var pgeo = this.getParentSize(parent);
+
+ var fillValue = 0;
+
+ if (pgeo != null)
+ {
+ fillValue = (horizontal) ? pgeo.height : pgeo.width;
+ }
+
+ fillValue -= 2 * this.spacing + 2 * this.border;
+ var x0 = this.x0 + this.border;
+ var y0 = this.y0 + this.border;
+
+ // Handles swimlane start size
+ if (this.graph.isSwimlane(parent))
+ {
+ // Uses computed style to get latest
+ var style = this.graph.getCellStyle(parent);
+ var start = mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE);
+ var horz = mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true);
+
+ if (horizontal == horz)
+ {
+ fillValue -= start;
+ }
+
+ if (horizontal)
+ {
+ y0 += start;
+ }
+ else
+ {
+ x0 += start;
+ }
+ }
+
+ model.beginUpdate();
+ try
+ {
+ var tmp = 0;
+ var last = null;
+ var childCount = model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(parent, i);
+
+ if (!this.isVertexIgnored(child) && this.isVertexMovable(child))
+ {
+ var geo = model.getGeometry(child);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+
+ if (this.wrap != null && last != null)
+ {
+ if ((horizontal && last.x + last.width +
+ geo.width + 2 * this.spacing > this.wrap) ||
+ (!horizontal && last.y + last.height +
+ geo.height + 2 * this.spacing > this.wrap))
+ {
+ last = null;
+
+ if (horizontal)
+ {
+ y0 += tmp + this.spacing;
+ }
+ else
+ {
+ x0 += tmp + this.spacing;
+ }
+
+ tmp = 0;
+ }
+ }
+
+ tmp = Math.max(tmp, (horizontal) ? geo.height : geo.width);
+
+ if (last != null)
+ {
+ if (horizontal)
+ {
+ geo.x = last.x + last.width + this.spacing;
+ }
+ else
+ {
+ geo.y = last.y + last.height + this.spacing;
+ }
+ }
+ else if (!this.keepFirstLocation)
+ {
+ if (horizontal)
+ {
+ geo.x = x0;
+ }
+ else
+ {
+ geo.y = y0;
+ }
+ }
+
+ if (horizontal)
+ {
+ geo.y = y0;
+ }
+ else
+ {
+ geo.x = x0;
+ }
+
+ if (this.fill && fillValue > 0)
+ {
+ if (horizontal)
+ {
+ geo.height = fillValue;
+ }
+ else
+ {
+ geo.width = fillValue;
+ }
+ }
+
+ model.setGeometry(child, geo);
+ last = geo;
+ }
+ }
+ }
+
+ if (this.resizeParent && pgeo != null && last != null &&
+ !this.graph.isCellCollapsed(parent))
+ {
+ pgeo = pgeo.clone();
+
+ if (horizontal)
+ {
+ pgeo.width = last.x + last.width + this.spacing;
+ }
+ else
+ {
+ pgeo.height = last.y + last.height + this.spacing;
+ }
+
+ model.setGeometry(parent, pgeo);
+ }
+ else if (this.resizeLast && pgeo != null && last != null)
+ {
+ if (horizontal)
+ {
+ last.width = pgeo.width - last.x - this.spacing;
+ }
+ else
+ {
+ last.height = pgeo.height - last.y - this.spacing;
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
diff --git a/src/js/model/mxCell.js b/src/js/model/mxCell.js
new file mode 100644
index 0000000..cb5eb9f
--- /dev/null
+++ b/src/js/model/mxCell.js
@@ -0,0 +1,806 @@
+/**
+ * $Id: mxCell.js,v 1.36 2011-06-17 13:45:08 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCell
+ *
+ * Cells are the elements of the graph model. They represent the state
+ * of the groups, vertices and edges in a graph.
+ *
+ * Custom attributes:
+ *
+ * For custom attributes we recommend using an XML node as the value of a cell.
+ * The following code can be used to create a cell with an XML node as the
+ * value:
+ *
+ * (code)
+ * var doc = mxUtils.createXmlDocument();
+ * var node = doc.createElement('MyNode')
+ * node.setAttribute('label', 'MyLabel');
+ * node.setAttribute('attribute1', 'value1');
+ * graph.insertVertex(graph.getDefaultParent(), null, node, 40, 40, 80, 30);
+ * (end)
+ *
+ * For the label to work, <mxGraph.convertValueToString> and
+ * <mxGraph.cellLabelChanged> should be overridden as follows:
+ *
+ * (code)
+ * graph.convertValueToString = function(cell)
+ * {
+ * if (mxUtils.isNode(cell.value))
+ * {
+ * return cell.getAttribute('label', '')
+ * }
+ * };
+ *
+ * var cellLabelChanged = graph.cellLabelChanged;
+ * graph.cellLabelChanged = function(cell, newValue, autoSize)
+ * {
+ * if (mxUtils.isNode(cell.value))
+ * {
+ * // Clones the value for correct undo/redo
+ * var elt = cell.value.cloneNode(true);
+ * elt.setAttribute('label', newValue);
+ * newValue = elt;
+ * }
+ *
+ * cellLabelChanged.apply(this, arguments);
+ * };
+ * (end)
+ *
+ * Callback: onInit
+ *
+ * Called from within the constructor.
+ *
+ * Constructor: mxCell
+ *
+ * Constructs a new cell to be used in a graph model.
+ * This method invokes <onInit> upon completion.
+ *
+ * Parameters:
+ *
+ * value - Optional object that represents the cell value.
+ * geometry - Optional <mxGeometry> that specifies the geometry.
+ * style - Optional formatted string that defines the style.
+ */
+function mxCell(value, geometry, style)
+{
+ this.value = value;
+ this.setGeometry(geometry);
+ this.setStyle(style);
+
+ if (this.onInit != null)
+ {
+ this.onInit();
+ }
+};
+
+/**
+ * Variable: id
+ *
+ * Holds the Id. Default is null.
+ */
+mxCell.prototype.id = null;
+
+/**
+ * Variable: value
+ *
+ * Holds the user object. Default is null.
+ */
+mxCell.prototype.value = null;
+
+/**
+ * Variable: geometry
+ *
+ * Holds the <mxGeometry>. Default is null.
+ */
+mxCell.prototype.geometry = null;
+
+/**
+ * Variable: style
+ *
+ * Holds the style as a string of the form [(stylename|key=value);]. Default is
+ * null.
+ */
+mxCell.prototype.style = null;
+
+/**
+ * Variable: vertex
+ *
+ * Specifies whether the cell is a vertex. Default is false.
+ */
+mxCell.prototype.vertex = false;
+
+/**
+ * Variable: edge
+ *
+ * Specifies whether the cell is an edge. Default is false.
+ */
+mxCell.prototype.edge = false;
+
+/**
+ * Variable: connectable
+ *
+ * Specifies whether the cell is connectable. Default is true.
+ */
+mxCell.prototype.connectable = true;
+
+/**
+ * Variable: visible
+ *
+ * Specifies whether the cell is visible. Default is true.
+ */
+mxCell.prototype.visible = true;
+
+/**
+ * Variable: collapsed
+ *
+ * Specifies whether the cell is collapsed. Default is false.
+ */
+mxCell.prototype.collapsed = false;
+
+/**
+ * Variable: parent
+ *
+ * Reference to the parent cell.
+ */
+mxCell.prototype.parent = null;
+
+/**
+ * Variable: source
+ *
+ * Reference to the source terminal.
+ */
+mxCell.prototype.source = null;
+
+/**
+ * Variable: target
+ *
+ * Reference to the target terminal.
+ */
+mxCell.prototype.target = null;
+
+/**
+ * Variable: children
+ *
+ * Holds the child cells.
+ */
+mxCell.prototype.children = null;
+
+/**
+ * Variable: edges
+ *
+ * Holds the edges.
+ */
+mxCell.prototype.edges = null;
+
+/**
+ * Variable: mxTransient
+ *
+ * List of members that should not be cloned inside <clone>. This field is
+ * passed to <mxUtils.clone> and is not made persistent in <mxCellCodec>.
+ * This is not a convention for all classes, it is only used in this class
+ * to mark transient fields since transient modifiers are not supported by
+ * the language.
+ */
+mxCell.prototype.mxTransient = ['id', 'value', 'parent', 'source',
+ 'target', 'children', 'edges'];
+
+/**
+ * Function: getId
+ *
+ * Returns the Id of the cell as a string.
+ */
+mxCell.prototype.getId = function()
+{
+ return this.id;
+};
+
+/**
+ * Function: setId
+ *
+ * Sets the Id of the cell to the given string.
+ */
+mxCell.prototype.setId = function(id)
+{
+ this.id = id;
+};
+
+/**
+ * Function: getValue
+ *
+ * Returns the user object of the cell. The user
+ * object is stored in <value>.
+ */
+mxCell.prototype.getValue = function()
+{
+ return this.value;
+};
+
+/**
+ * Function: setValue
+ *
+ * Sets the user object of the cell. The user object
+ * is stored in <value>.
+ */
+mxCell.prototype.setValue = function(value)
+{
+ this.value = value;
+};
+
+/**
+ * Function: valueChanged
+ *
+ * Changes the user object after an in-place edit
+ * and returns the previous value. This implementation
+ * replaces the user object with the given value and
+ * returns the old user object.
+ */
+mxCell.prototype.valueChanged = function(newValue)
+{
+ var previous = this.getValue();
+ this.setValue(newValue);
+
+ return previous;
+};
+
+/**
+ * Function: getGeometry
+ *
+ * Returns the <mxGeometry> that describes the <geometry>.
+ */
+mxCell.prototype.getGeometry = function()
+{
+ return this.geometry;
+};
+
+/**
+ * Function: setGeometry
+ *
+ * Sets the <mxGeometry> to be used as the <geometry>.
+ */
+mxCell.prototype.setGeometry = function(geometry)
+{
+ this.geometry = geometry;
+};
+
+/**
+ * Function: getStyle
+ *
+ * Returns a string that describes the <style>.
+ */
+mxCell.prototype.getStyle = function()
+{
+ return this.style;
+};
+
+/**
+ * Function: setStyle
+ *
+ * Sets the string to be used as the <style>.
+ */
+mxCell.prototype.setStyle = function(style)
+{
+ this.style = style;
+};
+
+/**
+ * Function: isVertex
+ *
+ * Returns true if the cell is a vertex.
+ */
+mxCell.prototype.isVertex = function()
+{
+ return this.vertex;
+};
+
+/**
+ * Function: setVertex
+ *
+ * Specifies if the cell is a vertex. This should only be assigned at
+ * construction of the cell and not be changed during its lifecycle.
+ *
+ * Parameters:
+ *
+ * vertex - Boolean that specifies if the cell is a vertex.
+ */
+mxCell.prototype.setVertex = function(vertex)
+{
+ this.vertex = vertex;
+};
+
+/**
+ * Function: isEdge
+ *
+ * Returns true if the cell is an edge.
+ */
+mxCell.prototype.isEdge = function()
+{
+ return this.edge;
+};
+
+/**
+ * Function: setEdge
+ *
+ * Specifies if the cell is an edge. This should only be assigned at
+ * construction of the cell and not be changed during its lifecycle.
+ *
+ * Parameters:
+ *
+ * edge - Boolean that specifies if the cell is an edge.
+ */
+mxCell.prototype.setEdge = function(edge)
+{
+ this.edge = edge;
+};
+
+/**
+ * Function: isConnectable
+ *
+ * Returns true if the cell is connectable.
+ */
+mxCell.prototype.isConnectable = function()
+{
+ return this.connectable;
+};
+
+/**
+ * Function: setConnectable
+ *
+ * Sets the connectable state.
+ *
+ * Parameters:
+ *
+ * connectable - Boolean that specifies the new connectable state.
+ */
+mxCell.prototype.setConnectable = function(connectable)
+{
+ this.connectable = connectable;
+};
+
+/**
+ * Function: isVisible
+ *
+ * Returns true if the cell is visibile.
+ */
+mxCell.prototype.isVisible = function()
+{
+ return this.visible;
+};
+
+/**
+ * Function: setVisible
+ *
+ * Specifies if the cell is visible.
+ *
+ * Parameters:
+ *
+ * visible - Boolean that specifies the new visible state.
+ */
+mxCell.prototype.setVisible = function(visible)
+{
+ this.visible = visible;
+};
+
+/**
+ * Function: isCollapsed
+ *
+ * Returns true if the cell is collapsed.
+ */
+mxCell.prototype.isCollapsed = function()
+{
+ return this.collapsed;
+};
+
+/**
+ * Function: setCollapsed
+ *
+ * Sets the collapsed state.
+ *
+ * Parameters:
+ *
+ * collapsed - Boolean that specifies the new collapsed state.
+ */
+mxCell.prototype.setCollapsed = function(collapsed)
+{
+ this.collapsed = collapsed;
+};
+
+/**
+ * Function: getParent
+ *
+ * Returns the cell's parent.
+ */
+mxCell.prototype.getParent = function()
+{
+ return this.parent;
+};
+
+/**
+ * Function: setParent
+ *
+ * Sets the parent cell.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> that represents the new parent.
+ */
+mxCell.prototype.setParent = function(parent)
+{
+ this.parent = parent;
+};
+
+/**
+ * Function: getTerminal
+ *
+ * Returns the source or target terminal.
+ *
+ * Parameters:
+ *
+ * source - Boolean that specifies if the source terminal should be
+ * returned.
+ */
+mxCell.prototype.getTerminal = function(source)
+{
+ return (source) ? this.source : this.target;
+};
+
+/**
+ * Function: setTerminal
+ *
+ * Sets the source or target terminal and returns the new terminal.
+ *
+ * Parameters:
+ *
+ * terminal - <mxCell> that represents the new source or target terminal.
+ * isSource - Boolean that specifies if the source or target terminal
+ * should be set.
+ */
+mxCell.prototype.setTerminal = function(terminal, isSource)
+{
+ if (isSource)
+ {
+ this.source = terminal;
+ }
+ else
+ {
+ this.target = terminal;
+ }
+
+ return terminal;
+};
+
+/**
+ * Function: getChildCount
+ *
+ * Returns the number of child cells.
+ */
+mxCell.prototype.getChildCount = function()
+{
+ return (this.children == null) ? 0 : this.children.length;
+};
+
+/**
+ * Function: getIndex
+ *
+ * Returns the index of the specified child in the child array.
+ *
+ * Parameters:
+ *
+ * child - Child whose index should be returned.
+ */
+mxCell.prototype.getIndex = function(child)
+{
+ return mxUtils.indexOf(this.children, child);
+};
+
+/**
+ * Function: getChildAt
+ *
+ * Returns the child at the specified index.
+ *
+ * Parameters:
+ *
+ * index - Integer that specifies the child to be returned.
+ */
+mxCell.prototype.getChildAt = function(index)
+{
+ return (this.children == null) ? null : this.children[index];
+};
+
+/**
+ * Function: insert
+ *
+ * Inserts the specified child into the child array at the specified index
+ * and updates the parent reference of the child. If not childIndex is
+ * specified then the child is appended to the child array. Returns the
+ * inserted child.
+ *
+ * Parameters:
+ *
+ * child - <mxCell> to be inserted or appended to the child array.
+ * index - Optional integer that specifies the index at which the child
+ * should be inserted into the child array.
+ */
+mxCell.prototype.insert = function(child, index)
+{
+ if (child != null)
+ {
+ if (index == null)
+ {
+ index = this.getChildCount();
+
+ if (child.getParent() == this)
+ {
+ index--;
+ }
+ }
+
+ child.removeFromParent();
+ child.setParent(this);
+
+ if (this.children == null)
+ {
+ this.children = [];
+ this.children.push(child);
+ }
+ else
+ {
+ this.children.splice(index, 0, child);
+ }
+ }
+
+ return child;
+};
+
+/**
+ * Function: remove
+ *
+ * Removes the child at the specified index from the child array and
+ * returns the child that was removed. Will remove the parent reference of
+ * the child.
+ *
+ * Parameters:
+ *
+ * index - Integer that specifies the index of the child to be
+ * removed.
+ */
+mxCell.prototype.remove = function(index)
+{
+ var child = null;
+
+ if (this.children != null && index >= 0)
+ {
+ child = this.getChildAt(index);
+
+ if (child != null)
+ {
+ this.children.splice(index, 1);
+ child.setParent(null);
+ }
+ }
+
+ return child;
+};
+
+/**
+ * Function: removeFromParent
+ *
+ * Removes the cell from its parent.
+ */
+mxCell.prototype.removeFromParent = function()
+{
+ if (this.parent != null)
+ {
+ var index = this.parent.getIndex(this);
+ this.parent.remove(index);
+ }
+};
+
+/**
+ * Function: getEdgeCount
+ *
+ * Returns the number of edges in the edge array.
+ */
+mxCell.prototype.getEdgeCount = function()
+{
+ return (this.edges == null) ? 0 : this.edges.length;
+};
+
+/**
+ * Function: getEdgeIndex
+ *
+ * Returns the index of the specified edge in <edges>.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> whose index in <edges> should be returned.
+ */
+mxCell.prototype.getEdgeIndex = function(edge)
+{
+ return mxUtils.indexOf(this.edges, edge);
+};
+
+/**
+ * Function: getEdgeAt
+ *
+ * Returns the edge at the specified index in <edges>.
+ *
+ * Parameters:
+ *
+ * index - Integer that specifies the index of the edge to be returned.
+ */
+mxCell.prototype.getEdgeAt = function(index)
+{
+ return (this.edges == null) ? null : this.edges[index];
+};
+
+/**
+ * Function: insertEdge
+ *
+ * Inserts the specified edge into the edge array and returns the edge.
+ * Will update the respective terminal reference of the edge.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> to be inserted into the edge array.
+ * isOutgoing - Boolean that specifies if the edge is outgoing.
+ */
+mxCell.prototype.insertEdge = function(edge, isOutgoing)
+{
+ if (edge != null)
+ {
+ edge.removeFromTerminal(isOutgoing);
+ edge.setTerminal(this, isOutgoing);
+
+ if (this.edges == null ||
+ edge.getTerminal(!isOutgoing) != this ||
+ mxUtils.indexOf(this.edges, edge) < 0)
+ {
+ if (this.edges == null)
+ {
+ this.edges = [];
+ }
+
+ this.edges.push(edge);
+ }
+ }
+
+ return edge;
+};
+
+/**
+ * Function: removeEdge
+ *
+ * Removes the specified edge from the edge array and returns the edge.
+ * Will remove the respective terminal reference from the edge.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> to be removed from the edge array.
+ * isOutgoing - Boolean that specifies if the edge is outgoing.
+ */
+mxCell.prototype.removeEdge = function(edge, isOutgoing)
+{
+ if (edge != null)
+ {
+ if (edge.getTerminal(!isOutgoing) != this &&
+ this.edges != null)
+ {
+ var index = this.getEdgeIndex(edge);
+
+ if (index >= 0)
+ {
+ this.edges.splice(index, 1);
+ }
+ }
+
+ edge.setTerminal(null, isOutgoing);
+ }
+
+ return edge;
+};
+
+/**
+ * Function: removeFromTerminal
+ *
+ * Removes the edge from its source or target terminal.
+ *
+ * Parameters:
+ *
+ * isSource - Boolean that specifies if the edge should be removed from its
+ * source or target terminal.
+ */
+mxCell.prototype.removeFromTerminal = function(isSource)
+{
+ var terminal = this.getTerminal(isSource);
+
+ if (terminal != null)
+ {
+ terminal.removeEdge(this, isSource);
+ }
+};
+
+/**
+ * Function: getAttribute
+ *
+ * Returns the specified attribute from the user object if it is an XML
+ * node.
+ *
+ * Parameters:
+ *
+ * name - Name of the attribute whose value should be returned.
+ * defaultValue - Optional default value to use if the attribute has no
+ * value.
+ */
+mxCell.prototype.getAttribute = function(name, defaultValue)
+{
+ var userObject = this.getValue();
+
+ var val = (userObject != null &&
+ userObject.nodeType == mxConstants.NODETYPE_ELEMENT) ?
+ userObject.getAttribute(name) : null;
+
+ return val || defaultValue;
+};
+
+/**
+ * Function: setAttribute
+ *
+ * Sets the specified attribute on the user object if it is an XML node.
+ *
+ * Parameters:
+ *
+ * name - Name of the attribute whose value should be set.
+ * value - New value of the attribute.
+ */
+mxCell.prototype.setAttribute = function(name, value)
+{
+ var userObject = this.getValue();
+
+ if (userObject != null &&
+ userObject.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ userObject.setAttribute(name, value);
+ }
+};
+
+/**
+ * Function: clone
+ *
+ * Returns a clone of the cell. Uses <cloneValue> to clone
+ * the user object. All fields in <mxTransient> are ignored
+ * during the cloning.
+ */
+mxCell.prototype.clone = function()
+{
+ var clone = mxUtils.clone(this, this.mxTransient);
+ clone.setValue(this.cloneValue());
+
+ return clone;
+};
+
+/**
+ * Function: cloneValue
+ *
+ * Returns a clone of the cell's user object.
+ */
+mxCell.prototype.cloneValue = function()
+{
+ var value = this.getValue();
+
+ if (value != null)
+ {
+ if (typeof(value.clone) == 'function')
+ {
+ value = value.clone();
+ }
+ else if (!isNaN(value.nodeType))
+ {
+ value = value.cloneNode(true);
+ }
+ }
+
+ return value;
+};
diff --git a/src/js/model/mxCellPath.js b/src/js/model/mxCellPath.js
new file mode 100644
index 0000000..71a379e
--- /dev/null
+++ b/src/js/model/mxCellPath.js
@@ -0,0 +1,163 @@
+/**
+ * $Id: mxCellPath.js,v 1.12 2010-01-02 09:45:15 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxCellPath =
+{
+
+ /**
+ * Class: mxCellPath
+ *
+ * Implements a mechanism for temporary cell Ids.
+ *
+ * Variable: PATH_SEPARATOR
+ *
+ * Defines the separator between the path components. Default is ".".
+ */
+ PATH_SEPARATOR: '.',
+
+ /**
+ * Function: create
+ *
+ * Creates the cell path for the given cell. The cell path is a
+ * concatenation of the indices of all ancestors on the (finite) path to
+ * the root, eg. "0.0.0.1".
+ *
+ * Parameters:
+ *
+ * cell - Cell whose path should be returned.
+ */
+ create: function(cell)
+ {
+ var result = '';
+
+ if (cell != null)
+ {
+ var parent = cell.getParent();
+
+ while (parent != null)
+ {
+ var index = parent.getIndex(cell);
+ result = index + mxCellPath.PATH_SEPARATOR + result;
+
+ cell = parent;
+ parent = cell.getParent();
+ }
+ }
+
+ // Removes trailing separator
+ var n = result.length;
+
+ if (n > 1)
+ {
+ result = result.substring(0, n - 1);
+ }
+
+ return result;
+ },
+
+ /**
+ * Function: getParentPath
+ *
+ * Returns the path for the parent of the cell represented by the given
+ * path. Returns null if the given path has no parent.
+ *
+ * Parameters:
+ *
+ * path - Path whose parent path should be returned.
+ */
+ getParentPath: function(path)
+ {
+ if (path != null)
+ {
+ var index = path.lastIndexOf(mxCellPath.PATH_SEPARATOR);
+
+ if (index >= 0)
+ {
+ return path.substring(0, index);
+ }
+ else if (path.length > 0)
+ {
+ return '';
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * Function: resolve
+ *
+ * Returns the cell for the specified cell path using the given root as the
+ * root of the path.
+ *
+ * Parameters:
+ *
+ * root - Root cell of the path to be resolved.
+ * path - String that defines the path.
+ */
+ resolve: function(root, path)
+ {
+ var parent = root;
+
+ if (path != null)
+ {
+ var tokens = path.split(mxCellPath.PATH_SEPARATOR);
+
+ for (var i=0; i<tokens.length; i++)
+ {
+ parent = parent.getChildAt(parseInt(tokens[i]));
+ }
+ }
+
+ return parent;
+ },
+
+ /**
+ * Function: compare
+ *
+ * Compares the given cell paths and returns -1 if p1 is smaller, 0 if
+ * p1 is equal and 1 if p1 is greater than p2.
+ */
+ compare: function(p1, p2)
+ {
+ var min = Math.min(p1.length, p2.length);
+ var comp = 0;
+
+ for (var i = 0; i < min; i++)
+ {
+ if (p1[i] != p2[i])
+ {
+ if (p1[i].length == 0 ||
+ p2[i].length == 0)
+ {
+ comp = (p1[i] == p2[i]) ? 0 : ((p1[i] > p2[i]) ? 1 : -1);
+ }
+ else
+ {
+ var t1 = parseInt(p1[i]);
+ var t2 = parseInt(p2[i]);
+
+ comp = (t1 == t2) ? 0 : ((t1 > t2) ? 1 : -1);
+ }
+
+ break;
+ }
+ }
+
+ // Compares path length if both paths are equal to this point
+ if (comp == 0)
+ {
+ var t1 = p1.length;
+ var t2 = p2.length;
+
+ if (t1 != t2)
+ {
+ comp = (t1 > t2) ? 1 : -1;
+ }
+ }
+
+ return comp;
+ }
+
+};
diff --git a/src/js/model/mxGeometry.js b/src/js/model/mxGeometry.js
new file mode 100644
index 0000000..51a7d3b
--- /dev/null
+++ b/src/js/model/mxGeometry.js
@@ -0,0 +1,277 @@
+/**
+ * $Id: mxGeometry.js,v 1.26 2010-01-02 09:45:15 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGeometry
+ *
+ * Extends <mxRectangle> to represent the geometry of a cell.
+ *
+ * For vertices, the geometry consists of the x- and y-location, and the width
+ * and height. For edges, the geometry consists of the optional terminal- and
+ * control points. The terminal points are only required if an edge is
+ * unconnected, and are stored in the sourcePoint> and <targetPoint>
+ * variables, respectively.
+ *
+ * Example:
+ *
+ * If an edge is unconnected, that is, it has no source or target terminal,
+ * then a geometry with terminal points for a new edge can be defined as
+ * follows.
+ *
+ * (code)
+ * geometry.setTerminalPoint(new mxPoint(x1, y1), true);
+ * geometry.points = [new mxPoint(x2, y2)];
+ * geometry.setTerminalPoint(new mxPoint(x3, y3), false);
+ * (end)
+ *
+ * Control points are used regardless of the connected state of an edge and may
+ * be ignored or interpreted differently depending on the edge's <mxEdgeStyle>.
+ *
+ * To disable automatic reset of control points after a cell has been moved or
+ * resized, the the <mxGraph.resizeEdgesOnMove> and
+ * <mxGraph.resetEdgesOnResize> may be used.
+ *
+ * Edge Labels:
+ *
+ * Using the x- and y-coordinates of a cell's geometry, it is possible to
+ * position the label on edges on a specific location on the actual edge shape
+ * as it appears on the screen. The x-coordinate of an edge's geometry is used
+ * to describe the distance from the center of the edge from -1 to 1 with 0
+ * being the center of the edge and the default value. The y-coordinate of an
+ * edge's geometry is used to describe the absolute, orthogonal distance in
+ * pixels from that point. In addition, the <mxGeometry.offset> is used as an
+ * absolute offset vector from the resulting point.
+ *
+ * This coordinate system is applied if <relative> is true, otherwise the
+ * offset defines the absolute vector from the edge's center point to the
+ * label.
+ *
+ * Ports:
+ *
+ * The term "port" refers to a relatively positioned, connectable child cell,
+ * which is used to specify the connection between the parent and another cell
+ * in the graph. Ports are typically modeled as vertices with relative
+ * geometries.
+ *
+ * Offsets:
+ *
+ * The <offset> field is interpreted in 3 different ways, depending on the cell
+ * and the geometry. For edges, the offset defines the absolute offset for the
+ * edge label. For relative geometries, the offset defines the absolute offset
+ * for the origin (top, left corner) of the vertex, otherwise the offset
+ * defines the absolute offset for the label inside the vertex or group.
+ *
+ * Constructor: mxGeometry
+ *
+ * Constructs a new object to describe the size and location of a vertex or
+ * the control points of an edge.
+ */
+function mxGeometry(x, y, width, height)
+{
+ mxRectangle.call(this, x, y, width, height);
+};
+
+/**
+ * Extends mxRectangle.
+ */
+mxGeometry.prototype = new mxRectangle();
+mxGeometry.prototype.constructor = mxGeometry;
+
+/**
+ * Variable: TRANSLATE_CONTROL_POINTS
+ *
+ * Global switch to translate the points in translate. Default is true.
+ */
+mxGeometry.prototype.TRANSLATE_CONTROL_POINTS = true;
+
+/**
+ * Variable: alternateBounds
+ *
+ * Stores alternate values for x, y, width and height in a rectangle. See
+ * <swap> to exchange the values. Default is null.
+ */
+mxGeometry.prototype.alternateBounds = null;
+
+/**
+ * Variable: sourcePoint
+ *
+ * Defines the source <mxPoint> of the edge. This is used if the
+ * corresponding edge does not have a source vertex. Otherwise it is
+ * ignored. Default is null.
+ */
+mxGeometry.prototype.sourcePoint = null;
+
+/**
+ * Variable: targetPoint
+ *
+ * Defines the target <mxPoint> of the edge. This is used if the
+ * corresponding edge does not have a target vertex. Otherwise it is
+ * ignored. Default is null.
+ */
+mxGeometry.prototype.targetPoint = null;
+
+/**
+ * Variable: points
+ *
+ * Array of <mxPoints> which specifies the control points along the edge.
+ * These points are the intermediate points on the edge, for the endpoints
+ * use <targetPoint> and <sourcePoint> or set the terminals of the edge to
+ * a non-null value. Default is null.
+ */
+mxGeometry.prototype.points = null;
+
+/**
+ * Variable: offset
+ *
+ * For edges, this holds the offset (in pixels) from the position defined
+ * by <x> and <y> on the edge. For relative geometries (for vertices), this
+ * defines the absolute offset from the point defined by the relative
+ * coordinates. For absolute geometries (for vertices), this defines the
+ * offset for the label. Default is null.
+ */
+mxGeometry.prototype.offset = null;
+
+/**
+ * Variable: relative
+ *
+ * Specifies if the coordinates in the geometry are to be interpreted as
+ * relative coordinates. For edges, this is used to define the location of
+ * the edge label relative to the edge as rendered on the display. For
+ * vertices, this specifies the relative location inside the bounds of the
+ * parent cell.
+ *
+ * If this is false, then the coordinates are relative to the origin of the
+ * parent cell or, for edges, the edge label position is relative to the
+ * center of the edge as rendered on screen.
+ *
+ * Default is false.
+ */
+mxGeometry.prototype.relative = false;
+
+/**
+ * Function: swap
+ *
+ * Swaps the x, y, width and height with the values stored in
+ * <alternateBounds> and puts the previous values into <alternateBounds> as
+ * a rectangle. This operation is carried-out in-place, that is, using the
+ * existing geometry instance. If this operation is called during a graph
+ * model transactional change, then the geometry should be cloned before
+ * calling this method and setting the geometry of the cell using
+ * <mxGraphModel.setGeometry>.
+ */
+mxGeometry.prototype.swap = function()
+{
+ if (this.alternateBounds != null)
+ {
+ var old = new mxRectangle(
+ this.x, this.y, this.width, this.height);
+
+ this.x = this.alternateBounds.x;
+ this.y = this.alternateBounds.y;
+ this.width = this.alternateBounds.width;
+ this.height = this.alternateBounds.height;
+
+ this.alternateBounds = old;
+ }
+};
+
+/**
+ * Function: getTerminalPoint
+ *
+ * Returns the <mxPoint> representing the source or target point of this
+ * edge. This is only used if the edge has no source or target vertex.
+ *
+ * Parameters:
+ *
+ * isSource - Boolean that specifies if the source or target point
+ * should be returned.
+ */
+mxGeometry.prototype.getTerminalPoint = function(isSource)
+{
+ return (isSource) ? this.sourcePoint : this.targetPoint;
+};
+
+/**
+ * Function: setTerminalPoint
+ *
+ * Sets the <sourcePoint> or <targetPoint> to the given <mxPoint> and
+ * returns the new point.
+ *
+ * Parameters:
+ *
+ * point - Point to be used as the new source or target point.
+ * isSource - Boolean that specifies if the source or target point
+ * should be set.
+ */
+mxGeometry.prototype.setTerminalPoint = function(point, isSource)
+{
+ if (isSource)
+ {
+ this.sourcePoint = point;
+ }
+ else
+ {
+ this.targetPoint = point;
+ }
+
+ return point;
+};
+
+/**
+ * Function: translate
+ *
+ * Translates the geometry by the specified amount. That is, <x> and <y>
+ * of the geometry, the <sourcePoint>, <targetPoint> and all elements of
+ * <points> are translated by the given amount. <x> and <y> are only
+ * translated if <relative> is false. If <TRANSLATE_CONTROL_POINTS> is
+ * false, then <points> are not modified by this function.
+ *
+ * Parameters:
+ *
+ * dx - Integer that specifies the x-coordinate of the translation.
+ * dy - Integer that specifies the y-coordinate of the translation.
+ */
+mxGeometry.prototype.translate = function(dx, dy)
+{
+ var clone = this.clone();
+
+ // Translates the geometry
+ if (!this.relative)
+ {
+ this.x += dx;
+ this.y += dy;
+ }
+
+ // Translates the source point
+ if (this.sourcePoint != null)
+ {
+ this.sourcePoint.x += dx;
+ this.sourcePoint.y += dy;
+ }
+
+ // Translates the target point
+ if (this.targetPoint != null)
+ {
+ this.targetPoint.x += dx;
+ this.targetPoint.y += dy;
+ }
+
+ // Translate the control points
+ if (this.TRANSLATE_CONTROL_POINTS &&
+ this.points != null)
+ {
+ var count = this.points.length;
+
+ for (var i = 0; i < count; i++)
+ {
+ var pt = this.points[i];
+
+ if (pt != null)
+ {
+ pt.x += dx;
+ pt.y += dy;
+ }
+ }
+ }
+};
diff --git a/src/js/model/mxGraphModel.js b/src/js/model/mxGraphModel.js
new file mode 100644
index 0000000..c65c0e1
--- /dev/null
+++ b/src/js/model/mxGraphModel.js
@@ -0,0 +1,2622 @@
+/**
+ * $Id: mxGraphModel.js,v 1.125 2012-04-16 10:48:43 david Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphModel
+ *
+ * Extends <mxEventSource> to implement a graph model. The graph model acts as
+ * a wrapper around the cells which are in charge of storing the actual graph
+ * datastructure. The model acts as a transactional wrapper with event
+ * notification for all changes, whereas the cells contain the atomic
+ * operations for updating the actual datastructure.
+ *
+ * Layers:
+ *
+ * The cell hierarchy in the model must have a top-level root cell which
+ * contains the layers (typically one default layer), which in turn contain the
+ * top-level cells of the layers. This means each cell is contained in a layer.
+ * If no layers are required, then all new cells should be added to the default
+ * layer.
+ *
+ * Layers are useful for hiding and showing groups of cells, or for placing
+ * groups of cells on top of other cells in the display. To identify a layer,
+ * the <isLayer> function is used. It returns true if the parent of the given
+ * cell is the root of the model.
+ *
+ * Encoding the model:
+ *
+ * To encode a graph model, use the following code:
+ *
+ * (code)
+ * var enc = new mxCodec();
+ * var node = enc.encode(graph.getModel());
+ * (end)
+ *
+ * This will create an XML node that contains all the model information.
+ *
+ * Encoding and decoding changes:
+ *
+ * For the encoding of changes, a graph model listener is required that encodes
+ * each change from the given array of changes.
+ *
+ * (code)
+ * model.addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ * var changes = evt.getProperty('edit').changes;
+ * var nodes = [];
+ * var codec = new mxCodec();
+ *
+ * for (var i = 0; i < changes.length; i++)
+ * {
+ * nodes.push(codec.encode(changes[i]));
+ * }
+ * // do something with the nodes
+ * });
+ * (end)
+ *
+ * For the decoding and execution of changes, the codec needs a lookup function
+ * that allows it to resolve cell IDs as follows:
+ *
+ * (code)
+ * var codec = new mxCodec();
+ * codec.lookup = function(id)
+ * {
+ * return model.getCell(id);
+ * }
+ * (end)
+ *
+ * For each encoded change (represented by a node), the following code can be
+ * used to carry out the decoding and create a change object.
+ *
+ * (code)
+ * var changes = [];
+ * var change = codec.decode(node);
+ * change.model = model;
+ * change.execute();
+ * changes.push(change);
+ * (end)
+ *
+ * The changes can then be dispatched using the model as follows.
+ *
+ * (code)
+ * var edit = new mxUndoableEdit(model, false);
+ * edit.changes = changes;
+ *
+ * edit.notify = function()
+ * {
+ * edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ * 'edit', edit, 'changes', edit.changes));
+ * edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,
+ * 'edit', edit, 'changes', edit.changes));
+ * }
+ *
+ * model.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+ * model.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ * 'edit', edit, 'changes', changes));
+ * (end)
+ *
+ * Event: mxEvent.CHANGE
+ *
+ * Fires when an undoable edit is dispatched. The <code>edit</code> property
+ * contains the <mxUndoableEdit>. The <code>changes</code> property contains
+ * the array of atomic changes inside the undoable edit. The changes property
+ * is <strong>deprecated</strong>, please use edit.changes instead.
+ *
+ * Example:
+ *
+ * For finding newly inserted cells, the following code can be used:
+ *
+ * (code)
+ * graph.model.addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ * var changes = evt.getProperty('edit').changes;
+ *
+ * for (var i = 0; i < changes.length; i++)
+ * {
+ * var change = changes[i];
+ *
+ * if (change instanceof mxChildChange &&
+ * change.change.previous == null)
+ * {
+ * graph.startEditingAtCell(change.child);
+ * break;
+ * }
+ * }
+ * });
+ * (end)
+ *
+ *
+ * Event: mxEvent.NOTIFY
+ *
+ * Same as <mxEvent.CHANGE>, this event can be used for classes that need to
+ * implement a sync mechanism between this model and, say, a remote model. In
+ * such a setup, only local changes should trigger a notify event and all
+ * changes should trigger a change event.
+ *
+ * Event: mxEvent.EXECUTE
+ *
+ * Fires between begin- and endUpdate and after an atomic change was executed
+ * in the model. The <code>change</code> property contains the atomic change
+ * that was executed.
+ *
+ * Event: mxEvent.BEGIN_UPDATE
+ *
+ * Fires after the <updateLevel> was incremented in <beginUpdate>. This event
+ * contains no properties.
+ *
+ * Event: mxEvent.END_UPDATE
+ *
+ * Fires after the <updateLevel> was decreased in <endUpdate> but before any
+ * notification or change dispatching. The <code>edit</code> property contains
+ * the <currentEdit>.
+ *
+ * Event: mxEvent.BEFORE_UNDO
+ *
+ * Fires before the change is dispatched after the update level has reached 0
+ * in <endUpdate>. The <code>edit</code> property contains the <curreneEdit>.
+ *
+ * Event: mxEvent.UNDO
+ *
+ * Fires after the change was dispatched in <endUpdate>. The <code>edit</code>
+ * property contains the <currentEdit>.
+ *
+ * Constructor: mxGraphModel
+ *
+ * Constructs a new graph model. If no root is specified then a new root
+ * <mxCell> with a default layer is created.
+ *
+ * Parameters:
+ *
+ * root - <mxCell> that represents the root cell.
+ */
+function mxGraphModel(root)
+{
+ this.currentEdit = this.createUndoableEdit();
+
+ if (root != null)
+ {
+ this.setRoot(root);
+ }
+ else
+ {
+ this.clear();
+ }
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraphModel.prototype = new mxEventSource();
+mxGraphModel.prototype.constructor = mxGraphModel;
+
+/**
+ * Variable: root
+ *
+ * Holds the root cell, which in turn contains the cells that represent the
+ * layers of the diagram as child cells. That is, the actual elements of the
+ * diagram are supposed to live in the third generation of cells and below.
+ */
+mxGraphModel.prototype.root = null;
+
+/**
+ * Variable: cells
+ *
+ * Maps from Ids to cells.
+ */
+mxGraphModel.prototype.cells = null;
+
+/**
+ * Variable: maintainEdgeParent
+ *
+ * Specifies if edges should automatically be moved into the nearest common
+ * ancestor of their terminals. Default is true.
+ */
+mxGraphModel.prototype.maintainEdgeParent = true;
+
+/**
+ * Variable: createIds
+ *
+ * Specifies if the model should automatically create Ids for new cells.
+ * Default is true.
+ */
+mxGraphModel.prototype.createIds = true;
+
+/**
+ * Variable: prefix
+ *
+ * Defines the prefix of new Ids. Default is an empty string.
+ */
+mxGraphModel.prototype.prefix = '';
+
+/**
+ * Variable: postfix
+ *
+ * Defines the postfix of new Ids. Default is an empty string.
+ */
+mxGraphModel.prototype.postfix = '';
+
+/**
+ * Variable: nextId
+ *
+ * Specifies the next Id to be created. Initial value is 0.
+ */
+mxGraphModel.prototype.nextId = 0;
+
+/**
+ * Variable: currentEdit
+ *
+ * Holds the changes for the current transaction. If the transaction is
+ * closed then a new object is created for this variable using
+ * <createUndoableEdit>.
+ */
+mxGraphModel.prototype.currentEdit = null;
+
+/**
+ * Variable: updateLevel
+ *
+ * Counter for the depth of nested transactions. Each call to <beginUpdate>
+ * will increment this number and each call to <endUpdate> will decrement
+ * it. When the counter reaches 0, the transaction is closed and the
+ * respective events are fired. Initial value is 0.
+ */
+mxGraphModel.prototype.updateLevel = 0;
+
+/**
+ * Variable: endingUpdate
+ *
+ * True if the program flow is currently inside endUpdate.
+ */
+mxGraphModel.prototype.endingUpdate = false;
+
+/**
+ * Function: clear
+ *
+ * Sets a new root using <createRoot>.
+ */
+mxGraphModel.prototype.clear = function()
+{
+ this.setRoot(this.createRoot());
+};
+
+/**
+ * Function: isCreateIds
+ *
+ * Returns <createIds>.
+ */
+mxGraphModel.prototype.isCreateIds = function()
+{
+ return this.createIds;
+};
+
+/**
+ * Function: setCreateIds
+ *
+ * Sets <createIds>.
+ */
+mxGraphModel.prototype.setCreateIds = function(value)
+{
+ this.createIds = value;
+};
+
+/**
+ * Function: createRoot
+ *
+ * Creates a new root cell with a default layer (child 0).
+ */
+mxGraphModel.prototype.createRoot = function()
+{
+ var cell = new mxCell();
+ cell.insert(new mxCell());
+
+ return cell;
+};
+
+/**
+ * Function: getCell
+ *
+ * Returns the <mxCell> for the specified Id or null if no cell can be
+ * found for the given Id.
+ *
+ * Parameters:
+ *
+ * id - A string representing the Id of the cell.
+ */
+mxGraphModel.prototype.getCell = function(id)
+{
+ return (this.cells != null) ? this.cells[id] : null;
+};
+
+/**
+ * Function: filterCells
+ *
+ * Returns the cells from the given array where the fiven filter function
+ * returns true.
+ */
+mxGraphModel.prototype.filterCells = function(cells, filter)
+{
+ var result = null;
+
+ if (cells != null)
+ {
+ result = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (filter(cells[i]))
+ {
+ result.push(cells[i]);
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getDescendants
+ *
+ * Returns all descendants of the given cell and the cell itself in an array.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose descendants should be returned.
+ */
+mxGraphModel.prototype.getDescendants = function(parent)
+{
+ return this.filterDescendants(null, parent);
+};
+
+/**
+ * Function: filterDescendants
+ *
+ * Visits all cells recursively and applies the specified filter function
+ * to each cell. If the function returns true then the cell is added
+ * to the resulting array. The parent and result paramters are optional.
+ * If parent is not specified then the recursion starts at <root>.
+ *
+ * Example:
+ * The following example extracts all vertices from a given model:
+ * (code)
+ * var filter = function(cell)
+ * {
+ * return model.isVertex(cell);
+ * }
+ * var vertices = model.filterDescendants(filter);
+ * (code)
+ *
+ * Parameters:
+ *
+ * filter - JavaScript function that takes an <mxCell> as an argument
+ * and returns a boolean.
+ * parent - Optional <mxCell> that is used as the root of the recursion.
+ */
+mxGraphModel.prototype.filterDescendants = function(filter, parent)
+{
+ // Creates a new array for storing the result
+ var result = [];
+
+ // Recursion starts at the root of the model
+ parent = parent || this.getRoot();
+
+ // Checks if the filter returns true for the cell
+ // and adds it to the result array
+ if (filter == null || filter(parent))
+ {
+ result.push(parent);
+ }
+
+ // Visits the children of the cell
+ var childCount = this.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.getChildAt(parent, i);
+ result = result.concat(this.filterDescendants(filter, child));
+ }
+
+ return result;
+};
+
+/**
+ * Function: getRoot
+ *
+ * Returns the root of the model or the topmost parent of the given cell.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> that specifies the child.
+ */
+mxGraphModel.prototype.getRoot = function(cell)
+{
+ var root = cell || this.root;
+
+ if (cell != null)
+ {
+ while (cell != null)
+ {
+ root = cell;
+ cell = this.getParent(cell);
+ }
+ }
+
+ return root;
+};
+
+/**
+ * Function: setRoot
+ *
+ * Sets the <root> of the model using <mxRootChange> and adds the change to
+ * the current transaction. This resets all datastructures in the model and
+ * is the preferred way of clearing an existing model. Returns the new
+ * root.
+ *
+ * Example:
+ *
+ * (code)
+ * var root = new mxCell();
+ * root.insert(new mxCell());
+ * model.setRoot(root);
+ * (end)
+ *
+ * Parameters:
+ *
+ * root - <mxCell> that specifies the new root.
+ */
+mxGraphModel.prototype.setRoot = function(root)
+{
+ this.execute(new mxRootChange(this, root));
+
+ return root;
+};
+
+/**
+ * Function: rootChanged
+ *
+ * Inner callback to change the root of the model and update the internal
+ * datastructures, such as <cells> and <nextId>. Returns the previous root.
+ *
+ * Parameters:
+ *
+ * root - <mxCell> that specifies the new root.
+ */
+mxGraphModel.prototype.rootChanged = function(root)
+{
+ var oldRoot = this.root;
+ this.root = root;
+
+ // Resets counters and datastructures
+ this.nextId = 0;
+ this.cells = null;
+ this.cellAdded(root);
+
+ return oldRoot;
+};
+
+/**
+ * Function: isRoot
+ *
+ * Returns true if the given cell is the root of the model and a non-null
+ * value.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the possible root.
+ */
+mxGraphModel.prototype.isRoot = function(cell)
+{
+ return cell != null && this.root == cell;
+};
+
+/**
+ * Function: isLayer
+ *
+ * Returns true if <isRoot> returns true for the parent of the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the possible layer.
+ */
+mxGraphModel.prototype.isLayer = function(cell)
+{
+ return this.isRoot(this.getParent(cell));
+};
+
+/**
+ * Function: isAncestor
+ *
+ * Returns true if the given parent is an ancestor of the given child.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> that specifies the parent.
+ * child - <mxCell> that specifies the child.
+ */
+mxGraphModel.prototype.isAncestor = function(parent, child)
+{
+ while (child != null && child != parent)
+ {
+ child = this.getParent(child);
+ }
+
+ return child == parent;
+};
+
+/**
+ * Function: contains
+ *
+ * Returns true if the model contains the given <mxCell>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that specifies the cell.
+ */
+mxGraphModel.prototype.contains = function(cell)
+{
+ return this.isAncestor(this.root, cell);
+};
+
+/**
+ * Function: getParent
+ *
+ * Returns the parent of the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose parent should be returned.
+ */
+mxGraphModel.prototype.getParent = function(cell)
+{
+ return (cell != null) ? cell.getParent() : null;
+};
+
+/**
+ * Function: add
+ *
+ * Adds the specified child to the parent at the given index using
+ * <mxChildChange> and adds the change to the current transaction. If no
+ * index is specified then the child is appended to the parent's array of
+ * children. Returns the inserted child.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> that specifies the parent to contain the child.
+ * child - <mxCell> that specifies the child to be inserted.
+ * index - Optional integer that specifies the index of the child.
+ */
+mxGraphModel.prototype.add = function(parent, child, index)
+{
+ if (child != parent && parent != null && child != null)
+ {
+ // Appends the child if no index was specified
+ if (index == null)
+ {
+ index = this.getChildCount(parent);
+ }
+
+ var parentChanged = parent != this.getParent(child);
+ this.execute(new mxChildChange(this, parent, child, index));
+
+ // Maintains the edges parents by moving the edges
+ // into the nearest common ancestor of its
+ // terminals
+ if (this.maintainEdgeParent && parentChanged)
+ {
+ this.updateEdgeParents(child);
+ }
+ }
+
+ return child;
+};
+
+/**
+ * Function: cellAdded
+ *
+ * Inner callback to update <cells> when a cell has been added. This
+ * implementation resolves collisions by creating new Ids. To change the
+ * ID of a cell after it was inserted into the model, use the following
+ * code:
+ *
+ * (code
+ * delete model.cells[cell.getId()];
+ * cell.setId(newId);
+ * model.cells[cell.getId()] = cell;
+ * (end)
+ *
+ * If the change of the ID should be part of the command history, then the
+ * cell should be removed from the model and a clone with the new ID should
+ * be reinserted into the model instead.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that specifies the cell that has been added.
+ */
+mxGraphModel.prototype.cellAdded = function(cell)
+{
+ if (cell != null)
+ {
+ // Creates an Id for the cell if not Id exists
+ if (cell.getId() == null && this.createIds)
+ {
+ cell.setId(this.createId(cell));
+ }
+
+ if (cell.getId() != null)
+ {
+ var collision = this.getCell(cell.getId());
+
+ if (collision != cell)
+ {
+ // Creates new Id for the cell
+ // as long as there is a collision
+ while (collision != null)
+ {
+ cell.setId(this.createId(cell));
+ collision = this.getCell(cell.getId());
+ }
+
+ // Lazily creates the cells dictionary
+ if (this.cells == null)
+ {
+ this.cells = new Object();
+ }
+
+ this.cells[cell.getId()] = cell;
+ }
+ }
+
+ // Makes sure IDs of deleted cells are not reused
+ if (mxUtils.isNumeric(cell.getId()))
+ {
+ this.nextId = Math.max(this.nextId, cell.getId());
+ }
+
+ // Recursively processes child cells
+ var childCount = this.getChildCount(cell);
+
+ for (var i=0; i<childCount; i++)
+ {
+ this.cellAdded(this.getChildAt(cell, i));
+ }
+ }
+};
+
+/**
+ * Function: createId
+ *
+ * Hook method to create an Id for the specified cell. This implementation
+ * concatenates <prefix>, id and <postfix> to create the Id and increments
+ * <nextId>. The cell is ignored by this implementation, but can be used in
+ * overridden methods to prefix the Ids with eg. the cell type.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to create the Id for.
+ */
+mxGraphModel.prototype.createId = function(cell)
+{
+ var id = this.nextId;
+ this.nextId++;
+
+ return this.prefix + id + this.postfix;
+};
+
+/**
+ * Function: updateEdgeParents
+ *
+ * Updates the parent for all edges that are connected to cell or one of
+ * its descendants using <updateEdgeParent>.
+ */
+mxGraphModel.prototype.updateEdgeParents = function(cell, root)
+{
+ // Gets the topmost node of the hierarchy
+ root = root || this.getRoot(cell);
+
+ // Updates edges on children first
+ var childCount = this.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.getChildAt(cell, i);
+ this.updateEdgeParents(child, root);
+ }
+
+ // Updates the parents of all connected edges
+ var edgeCount = this.getEdgeCount(cell);
+ var edges = [];
+
+ for (var i = 0; i < edgeCount; i++)
+ {
+ edges.push(this.getEdgeAt(cell, i));
+ }
+
+ for (var i = 0; i < edges.length; i++)
+ {
+ var edge = edges[i];
+
+ // Updates edge parent if edge and child have
+ // a common root node (does not need to be the
+ // model root node)
+ if (this.isAncestor(root, edge))
+ {
+ this.updateEdgeParent(edge, root);
+ }
+ }
+};
+
+/**
+ * Function: updateEdgeParent
+ *
+ * Inner callback to update the parent of the specified <mxCell> to the
+ * nearest-common-ancestor of its two terminals.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that specifies the edge.
+ * root - <mxCell> that represents the current root of the model.
+ */
+mxGraphModel.prototype.updateEdgeParent = function(edge, root)
+{
+ var source = this.getTerminal(edge, true);
+ var target = this.getTerminal(edge, false);
+ var cell = null;
+
+ // Uses the first non-relative descendants of the source terminal
+ while (source != null && !this.isEdge(source) &&
+ source.geometry != null && source.geometry.relative)
+ {
+ source = this.getParent(source);
+ }
+
+ // Uses the first non-relative descendants of the target terminal
+ while (target != null && !this.isEdge(target) &&
+ target.geometry != null && target.geometry.relative)
+ {
+ target = this.getParent(target);
+ }
+
+ if (this.isAncestor(root, source) && this.isAncestor(root, target))
+ {
+ if (source == target)
+ {
+ cell = this.getParent(source);
+ }
+ else
+ {
+ cell = this.getNearestCommonAncestor(source, target);
+ }
+
+ if (cell != null && (this.getParent(cell) != this.root ||
+ this.isAncestor(cell, edge)) && this.getParent(edge) != cell)
+ {
+ var geo = this.getGeometry(edge);
+
+ if (geo != null)
+ {
+ var origin1 = this.getOrigin(this.getParent(edge));
+ var origin2 = this.getOrigin(cell);
+
+ var dx = origin2.x - origin1.x;
+ var dy = origin2.y - origin1.y;
+
+ geo = geo.clone();
+ geo.translate(-dx, -dy);
+ this.setGeometry(edge, geo);
+ }
+
+ this.add(cell, edge, this.getChildCount(cell));
+ }
+ }
+};
+
+/**
+ * Function: getOrigin
+ *
+ * Returns the absolute, accumulated origin for the children inside the
+ * given parent as an <mxPoint>.
+ */
+mxGraphModel.prototype.getOrigin = function(cell)
+{
+ var result = null;
+
+ if (cell != null)
+ {
+ result = this.getOrigin(this.getParent(cell));
+
+ if (!this.isEdge(cell))
+ {
+ var geo = this.getGeometry(cell);
+
+ if (geo != null)
+ {
+ result.x += geo.x;
+ result.y += geo.y;
+ }
+ }
+ }
+ else
+ {
+ result = new mxPoint();
+ }
+
+ return result;
+};
+
+/**
+ * Function: getNearestCommonAncestor
+ *
+ * Returns the nearest common ancestor for the specified cells.
+ *
+ * Parameters:
+ *
+ * cell1 - <mxCell> that specifies the first cell in the tree.
+ * cell2 - <mxCell> that specifies the second cell in the tree.
+ */
+mxGraphModel.prototype.getNearestCommonAncestor = function(cell1, cell2)
+{
+ if (cell1 != null && cell2 != null)
+ {
+ // Creates the cell path for the second cell
+ var path = mxCellPath.create(cell2);
+
+ if (path != null && path.length > 0)
+ {
+ // Bubbles through the ancestors of the first
+ // cell to find the nearest common ancestor.
+ var cell = cell1;
+ var current = mxCellPath.create(cell);
+
+ // Inverts arguments
+ if (path.length < current.length)
+ {
+ cell = cell2;
+ var tmp = current;
+ current = path;
+ path = tmp;
+ }
+
+ while (cell != null)
+ {
+ var parent = this.getParent(cell);
+
+ // Checks if the cell path is equal to the beginning of the given cell path
+ if (path.indexOf(current + mxCellPath.PATH_SEPARATOR) == 0 && parent != null)
+ {
+ return cell;
+ }
+
+ current = mxCellPath.getParentPath(current);
+ cell = parent;
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: remove
+ *
+ * Removes the specified cell from the model using <mxChildChange> and adds
+ * the change to the current transaction. This operation will remove the
+ * cell and all of its children from the model. Returns the removed cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that should be removed.
+ */
+mxGraphModel.prototype.remove = function(cell)
+{
+ if (cell == this.root)
+ {
+ this.setRoot(null);
+ }
+ else if (this.getParent(cell) != null)
+ {
+ this.execute(new mxChildChange(this, null, cell));
+ }
+
+ return cell;
+};
+
+/**
+ * Function: cellRemoved
+ *
+ * Inner callback to update <cells> when a cell has been removed.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that specifies the cell that has been removed.
+ */
+mxGraphModel.prototype.cellRemoved = function(cell)
+{
+ if (cell != null && this.cells != null)
+ {
+ // Recursively processes child cells
+ var childCount = this.getChildCount(cell);
+
+ for (var i = childCount - 1; i >= 0; i--)
+ {
+ this.cellRemoved(this.getChildAt(cell, i));
+ }
+
+ // Removes the dictionary entry for the cell
+ if (this.cells != null && cell.getId() != null)
+ {
+ delete this.cells[cell.getId()];
+ }
+ }
+};
+
+/**
+ * Function: parentForCellChanged
+ *
+ * Inner callback to update the parent of a cell using <mxCell.insert>
+ * on the parent and return the previous parent.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to update the parent for.
+ * parent - <mxCell> that specifies the new parent of the cell.
+ * index - Optional integer that defines the index of the child
+ * in the parent's child array.
+ */
+mxGraphModel.prototype.parentForCellChanged = function(cell, parent, index)
+{
+ var previous = this.getParent(cell);
+
+ if (parent != null)
+ {
+ if (parent != previous || previous.getIndex(cell) != index)
+ {
+ parent.insert(cell, index);
+ }
+ }
+ else if (previous != null)
+ {
+ var oldIndex = previous.getIndex(cell);
+ previous.remove(oldIndex);
+ }
+
+ // Checks if the previous parent was already in the
+ // model and avoids calling cellAdded if it was.
+ if (!this.contains(previous) && parent != null)
+ {
+ this.cellAdded(cell);
+ }
+ else if (parent == null)
+ {
+ this.cellRemoved(cell);
+ }
+
+ return previous;
+};
+
+/**
+ * Function: getChildCount
+ *
+ * Returns the number of children in the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose number of children should be returned.
+ */
+mxGraphModel.prototype.getChildCount = function(cell)
+{
+ return (cell != null) ? cell.getChildCount() : 0;
+};
+
+/**
+ * Function: getChildAt
+ *
+ * Returns the child of the given <mxCell> at the given index.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the parent.
+ * index - Integer that specifies the index of the child to be returned.
+ */
+mxGraphModel.prototype.getChildAt = function(cell, index)
+{
+ return (cell != null) ? cell.getChildAt(index) : null;
+};
+
+/**
+ * Function: getChildren
+ *
+ * Returns all children of the given <mxCell> as an array of <mxCells>. The
+ * return value should be only be read.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> the represents the parent.
+ */
+mxGraphModel.prototype.getChildren = function(cell)
+{
+ return (cell != null) ? cell.children : null;
+};
+
+/**
+ * Function: getChildVertices
+ *
+ * Returns the child vertices of the given parent.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose child vertices should be returned.
+ */
+mxGraphModel.prototype.getChildVertices = function(parent)
+{
+ return this.getChildCells(parent, true, false);
+};
+
+/**
+ * Function: getChildEdges
+ *
+ * Returns the child edges of the given parent.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose child edges should be returned.
+ */
+mxGraphModel.prototype.getChildEdges = function(parent)
+{
+ return this.getChildCells(parent, false, true);
+};
+
+/**
+ * Function: getChildCells
+ *
+ * Returns the children of the given cell that are vertices and/or edges
+ * depending on the arguments.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> the represents the parent.
+ * vertices - Boolean indicating if child vertices should be returned.
+ * Default is false.
+ * edges - Boolean indicating if child edges should be returned.
+ * Default is false.
+ */
+mxGraphModel.prototype.getChildCells = function(parent, vertices, edges)
+{
+ vertices = (vertices != null) ? vertices : false;
+ edges = (edges != null) ? edges : false;
+
+ var childCount = this.getChildCount(parent);
+ var result = [];
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.getChildAt(parent, i);
+
+ if ((!edges && !vertices) || (edges && this.isEdge(child)) ||
+ (vertices && this.isVertex(child)))
+ {
+ result.push(child);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getTerminal
+ *
+ * Returns the source or target <mxCell> of the given edge depending on the
+ * value of the boolean parameter.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that specifies the edge.
+ * isSource - Boolean indicating which end of the edge should be returned.
+ */
+mxGraphModel.prototype.getTerminal = function(edge, isSource)
+{
+ return (edge != null) ? edge.getTerminal(isSource) : null;
+};
+
+/**
+ * Function: setTerminal
+ *
+ * Sets the source or target terminal of the given <mxCell> using
+ * <mxTerminalChange> and adds the change to the current transaction.
+ * This implementation updates the parent of the edge using <updateEdgeParent>
+ * if required.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that specifies the edge.
+ * terminal - <mxCell> that specifies the new terminal.
+ * isSource - Boolean indicating if the terminal is the new source or
+ * target terminal of the edge.
+ */
+mxGraphModel.prototype.setTerminal = function(edge, terminal, isSource)
+{
+ var terminalChanged = terminal != this.getTerminal(edge, isSource);
+ this.execute(new mxTerminalChange(this, edge, terminal, isSource));
+
+ if (this.maintainEdgeParent && terminalChanged)
+ {
+ this.updateEdgeParent(edge, this.getRoot());
+ }
+
+ return terminal;
+};
+
+/**
+ * Function: setTerminals
+ *
+ * Sets the source and target <mxCell> of the given <mxCell> in a single
+ * transaction using <setTerminal> for each end of the edge.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that specifies the edge.
+ * source - <mxCell> that specifies the new source terminal.
+ * target - <mxCell> that specifies the new target terminal.
+ */
+mxGraphModel.prototype.setTerminals = function(edge, source, target)
+{
+ this.beginUpdate();
+ try
+ {
+ this.setTerminal(edge, source, true);
+ this.setTerminal(edge, target, false);
+ }
+ finally
+ {
+ this.endUpdate();
+ }
+};
+
+/**
+ * Function: terminalForCellChanged
+ *
+ * Inner helper function to update the terminal of the edge using
+ * <mxCell.insertEdge> and return the previous terminal.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that specifies the edge to be updated.
+ * terminal - <mxCell> that specifies the new terminal.
+ * isSource - Boolean indicating if the terminal is the new source or
+ * target terminal of the edge.
+ */
+mxGraphModel.prototype.terminalForCellChanged = function(edge, terminal, isSource)
+{
+ var previous = this.getTerminal(edge, isSource);
+
+ if (terminal != null)
+ {
+ terminal.insertEdge(edge, isSource);
+ }
+ else if (previous != null)
+ {
+ previous.removeEdge(edge, isSource);
+ }
+
+ return previous;
+};
+
+/**
+ * Function: getEdgeCount
+ *
+ * Returns the number of distinct edges connected to the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the vertex.
+ */
+mxGraphModel.prototype.getEdgeCount = function(cell)
+{
+ return (cell != null) ? cell.getEdgeCount() : 0;
+};
+
+/**
+ * Function: getEdgeAt
+ *
+ * Returns the edge of cell at the given index.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that specifies the vertex.
+ * index - Integer that specifies the index of the edge
+ * to return.
+ */
+mxGraphModel.prototype.getEdgeAt = function(cell, index)
+{
+ return (cell != null) ? cell.getEdgeAt(index) : null;
+};
+
+/**
+ * Function: getDirectedEdgeCount
+ *
+ * Returns the number of incoming or outgoing edges, ignoring the given
+ * edge.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose edge count should be returned.
+ * outgoing - Boolean that specifies if the number of outgoing or
+ * incoming edges should be returned.
+ * ignoredEdge - <mxCell> that represents an edge to be ignored.
+ */
+mxGraphModel.prototype.getDirectedEdgeCount = function(cell, outgoing, ignoredEdge)
+{
+ var count = 0;
+ var edgeCount = this.getEdgeCount(cell);
+
+ for (var i = 0; i < edgeCount; i++)
+ {
+ var edge = this.getEdgeAt(cell, i);
+
+ if (edge != ignoredEdge && this.getTerminal(edge, outgoing) == cell)
+ {
+ count++;
+ }
+ }
+
+ return count;
+};
+
+/**
+ * Function: getConnections
+ *
+ * Returns all edges of the given cell without loops.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose edges should be returned.
+ *
+ */
+mxGraphModel.prototype.getConnections = function(cell)
+{
+ return this.getEdges(cell, true, true, false);
+};
+
+/**
+ * Function: getIncomingEdges
+ *
+ * Returns the incoming edges of the given cell without loops.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose incoming edges should be returned.
+ *
+ */
+mxGraphModel.prototype.getIncomingEdges = function(cell)
+{
+ return this.getEdges(cell, true, false, false);
+};
+
+/**
+ * Function: getOutgoingEdges
+ *
+ * Returns the outgoing edges of the given cell without loops.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose outgoing edges should be returned.
+ *
+ */
+mxGraphModel.prototype.getOutgoingEdges = function(cell)
+{
+ return this.getEdges(cell, false, true, false);
+};
+
+/**
+ * Function: getEdges
+ *
+ * Returns all distinct edges connected to this cell as a new array of
+ * <mxCells>. If at least one of incoming or outgoing is true, then loops
+ * are ignored, otherwise if both are false, then all edges connected to
+ * the given cell are returned including loops.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that specifies the cell.
+ * incoming - Optional boolean that specifies if incoming edges should be
+ * returned. Default is true.
+ * outgoing - Optional boolean that specifies if outgoing edges should be
+ * returned. Default is true.
+ * includeLoops - Optional boolean that specifies if loops should be returned.
+ * Default is true.
+ */
+mxGraphModel.prototype.getEdges = function(cell, incoming, outgoing, includeLoops)
+{
+ incoming = (incoming != null) ? incoming : true;
+ outgoing = (outgoing != null) ? outgoing : true;
+ includeLoops = (includeLoops != null) ? includeLoops : true;
+
+ var edgeCount = this.getEdgeCount(cell);
+ var result = [];
+
+ for (var i = 0; i < edgeCount; i++)
+ {
+ var edge = this.getEdgeAt(cell, i);
+ var source = this.getTerminal(edge, true);
+ var target = this.getTerminal(edge, false);
+
+ if ((includeLoops && source == target) || ((source != target) && ((incoming && target == cell) ||
+ (outgoing && source == cell))))
+ {
+ result.push(edge);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getEdgesBetween
+ *
+ * Returns all edges between the given source and target pair. If directed
+ * is true, then only edges from the source to the target are returned,
+ * otherwise, all edges between the two cells are returned.
+ *
+ * Parameters:
+ *
+ * source - <mxCell> that defines the source terminal of the edge to be
+ * returned.
+ * target - <mxCell> that defines the target terminal of the edge to be
+ * returned.
+ * directed - Optional boolean that specifies if the direction of the
+ * edge should be taken into account. Default is false.
+ */
+mxGraphModel.prototype.getEdgesBetween = function(source, target, directed)
+{
+ directed = (directed != null) ? directed : false;
+
+ var tmp1 = this.getEdgeCount(source);
+ var tmp2 = this.getEdgeCount(target);
+
+ // Assumes the source has less connected edges
+ var terminal = source;
+ var edgeCount = tmp1;
+
+ // Uses the smaller array of connected edges
+ // for searching the edge
+ if (tmp2 < tmp1)
+ {
+ edgeCount = tmp2;
+ terminal = target;
+ }
+
+ var result = [];
+
+ // Checks if the edge is connected to the correct
+ // cell and returns the first match
+ for (var i = 0; i < edgeCount; i++)
+ {
+ var edge = this.getEdgeAt(terminal, i);
+ var src = this.getTerminal(edge, true);
+ var trg = this.getTerminal(edge, false);
+ var directedMatch = (src == source) && (trg == target);
+ var oppositeMatch = (trg == source) && (src == target);
+
+ if (directedMatch || (!directed && oppositeMatch))
+ {
+ result.push(edge);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getOpposites
+ *
+ * Returns all opposite vertices wrt terminal for the given edges, only
+ * returning sources and/or targets as specified. The result is returned
+ * as an array of <mxCells>.
+ *
+ * Parameters:
+ *
+ * edges - Array of <mxCells> that contain the edges to be examined.
+ * terminal - <mxCell> that specifies the known end of the edges.
+ * sources - Boolean that specifies if source terminals should be contained
+ * in the result. Default is true.
+ * targets - Boolean that specifies if target terminals should be contained
+ * in the result. Default is true.
+ */
+mxGraphModel.prototype.getOpposites = function(edges, terminal, sources, targets)
+{
+ sources = (sources != null) ? sources : true;
+ targets = (targets != null) ? targets : true;
+
+ var terminals = [];
+
+ if (edges != null)
+ {
+ for (var i = 0; i < edges.length; i++)
+ {
+ var source = this.getTerminal(edges[i], true);
+ var target = this.getTerminal(edges[i], false);
+
+ // Checks if the terminal is the source of
+ // the edge and if the target should be
+ // stored in the result
+ if (source == terminal && target != null && target != terminal && targets)
+ {
+ terminals.push(target);
+ }
+
+ // Checks if the terminal is the taget of
+ // the edge and if the source should be
+ // stored in the result
+ else if (target == terminal && source != null && source != terminal && sources)
+ {
+ terminals.push(source);
+ }
+ }
+ }
+
+ return terminals;
+};
+
+/**
+ * Function: getTopmostCells
+ *
+ * Returns the topmost cells of the hierarchy in an array that contains no
+ * descendants for each <mxCell> that it contains. Duplicates should be
+ * removed in the cells array to improve performance.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose topmost ancestors should be returned.
+ */
+mxGraphModel.prototype.getTopmostCells = function(cells)
+{
+ var tmp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var cell = cells[i];
+ var topmost = true;
+ var parent = this.getParent(cell);
+
+ while (parent != null)
+ {
+ if (mxUtils.indexOf(cells, parent) >= 0)
+ {
+ topmost = false;
+ break;
+ }
+
+ parent = this.getParent(parent);
+ }
+
+ if (topmost)
+ {
+ tmp.push(cell);
+ }
+ }
+
+ return tmp;
+};
+
+/**
+ * Function: isVertex
+ *
+ * Returns true if the given cell is a vertex.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the possible vertex.
+ */
+mxGraphModel.prototype.isVertex = function(cell)
+{
+ return (cell != null) ? cell.isVertex() : false;
+};
+
+/**
+ * Function: isEdge
+ *
+ * Returns true if the given cell is an edge.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the possible edge.
+ */
+mxGraphModel.prototype.isEdge = function(cell)
+{
+ return (cell != null) ? cell.isEdge() : false;
+};
+
+/**
+ * Function: isConnectable
+ *
+ * Returns true if the given <mxCell> is connectable. If <edgesConnectable>
+ * is false, then this function returns false for all edges else it returns
+ * the return value of <mxCell.isConnectable>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose connectable state should be returned.
+ */
+mxGraphModel.prototype.isConnectable = function(cell)
+{
+ return (cell != null) ? cell.isConnectable() : false;
+};
+
+/**
+ * Function: getValue
+ *
+ * Returns the user object of the given <mxCell> using <mxCell.getValue>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose user object should be returned.
+ */
+mxGraphModel.prototype.getValue = function(cell)
+{
+ return (cell != null) ? cell.getValue() : null;
+};
+
+/**
+ * Function: setValue
+ *
+ * Sets the user object of then given <mxCell> using <mxValueChange>
+ * and adds the change to the current transaction.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose user object should be changed.
+ * value - Object that defines the new user object.
+ */
+mxGraphModel.prototype.setValue = function(cell, value)
+{
+ this.execute(new mxValueChange(this, cell, value));
+
+ return value;
+};
+
+/**
+ * Function: valueForCellChanged
+ *
+ * Inner callback to update the user object of the given <mxCell>
+ * using <mxCell.valueChanged> and return the previous value,
+ * that is, the return value of <mxCell.valueChanged>.
+ *
+ * To change a specific attribute in an XML node, the following code can be
+ * used.
+ *
+ * (code)
+ * graph.getModel().valueForCellChanged = function(cell, value)
+ * {
+ * var previous = cell.value.getAttribute('label');
+ * cell.value.setAttribute('label', value);
+ *
+ * return previous;
+ * };
+ * (end)
+ */
+mxGraphModel.prototype.valueForCellChanged = function(cell, value)
+{
+ return cell.valueChanged(value);
+};
+
+/**
+ * Function: getGeometry
+ *
+ * Returns the <mxGeometry> of the given <mxCell>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose geometry should be returned.
+ */
+mxGraphModel.prototype.getGeometry = function(cell, geometry)
+{
+ return (cell != null) ? cell.getGeometry() : null;
+};
+
+/**
+ * Function: setGeometry
+ *
+ * Sets the <mxGeometry> of the given <mxCell>. The actual update
+ * of the cell is carried out in <geometryForCellChanged>. The
+ * <mxGeometryChange> action is used to encapsulate the change.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose geometry should be changed.
+ * geometry - <mxGeometry> that defines the new geometry.
+ */
+mxGraphModel.prototype.setGeometry = function(cell, geometry)
+{
+ if (geometry != this.getGeometry(cell))
+ {
+ this.execute(new mxGeometryChange(this, cell, geometry));
+ }
+
+ return geometry;
+};
+
+/**
+ * Function: geometryForCellChanged
+ *
+ * Inner callback to update the <mxGeometry> of the given <mxCell> using
+ * <mxCell.setGeometry> and return the previous <mxGeometry>.
+ */
+mxGraphModel.prototype.geometryForCellChanged = function(cell, geometry)
+{
+ var previous = this.getGeometry(cell);
+ cell.setGeometry(geometry);
+
+ return previous;
+};
+
+/**
+ * Function: getStyle
+ *
+ * Returns the style of the given <mxCell>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose style should be returned.
+ */
+mxGraphModel.prototype.getStyle = function(cell)
+{
+ return (cell != null) ? cell.getStyle() : null;
+};
+
+/**
+ * Function: setStyle
+ *
+ * Sets the style of the given <mxCell> using <mxStyleChange> and
+ * adds the change to the current transaction.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose style should be changed.
+ * style - String of the form [stylename;|key=value;] to specify
+ * the new cell style.
+ */
+mxGraphModel.prototype.setStyle = function(cell, style)
+{
+ if (style != this.getStyle(cell))
+ {
+ this.execute(new mxStyleChange(this, cell, style));
+ }
+
+ return style;
+};
+
+/**
+ * Function: styleForCellChanged
+ *
+ * Inner callback to update the style of the given <mxCell>
+ * using <mxCell.setStyle> and return the previous style.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that specifies the cell to be updated.
+ * style - String of the form [stylename;|key=value;] to specify
+ * the new cell style.
+ */
+mxGraphModel.prototype.styleForCellChanged = function(cell, style)
+{
+ var previous = this.getStyle(cell);
+ cell.setStyle(style);
+
+ return previous;
+};
+
+/**
+ * Function: isCollapsed
+ *
+ * Returns true if the given <mxCell> is collapsed.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose collapsed state should be returned.
+ */
+mxGraphModel.prototype.isCollapsed = function(cell)
+{
+ return (cell != null) ? cell.isCollapsed() : false;
+};
+
+/**
+ * Function: setCollapsed
+ *
+ * Sets the collapsed state of the given <mxCell> using <mxCollapseChange>
+ * and adds the change to the current transaction.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose collapsed state should be changed.
+ * collapsed - Boolean that specifies the new collpased state.
+ */
+mxGraphModel.prototype.setCollapsed = function(cell, collapsed)
+{
+ if (collapsed != this.isCollapsed(cell))
+ {
+ this.execute(new mxCollapseChange(this, cell, collapsed));
+ }
+
+ return collapsed;
+};
+
+/**
+ * Function: collapsedStateForCellChanged
+ *
+ * Inner callback to update the collapsed state of the
+ * given <mxCell> using <mxCell.setCollapsed> and return
+ * the previous collapsed state.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that specifies the cell to be updated.
+ * collapsed - Boolean that specifies the new collpased state.
+ */
+mxGraphModel.prototype.collapsedStateForCellChanged = function(cell, collapsed)
+{
+ var previous = this.isCollapsed(cell);
+ cell.setCollapsed(collapsed);
+
+ return previous;
+};
+
+/**
+ * Function: isVisible
+ *
+ * Returns true if the given <mxCell> is visible.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose visible state should be returned.
+ */
+mxGraphModel.prototype.isVisible = function(cell)
+{
+ return (cell != null) ? cell.isVisible() : false;
+};
+
+/**
+ * Function: setVisible
+ *
+ * Sets the visible state of the given <mxCell> using <mxVisibleChange> and
+ * adds the change to the current transaction.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose visible state should be changed.
+ * visible - Boolean that specifies the new visible state.
+ */
+mxGraphModel.prototype.setVisible = function(cell, visible)
+{
+ if (visible != this.isVisible(cell))
+ {
+ this.execute(new mxVisibleChange(this, cell, visible));
+ }
+
+ return visible;
+};
+
+/**
+ * Function: visibleStateForCellChanged
+ *
+ * Inner callback to update the visible state of the
+ * given <mxCell> using <mxCell.setCollapsed> and return
+ * the previous visible state.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that specifies the cell to be updated.
+ * visible - Boolean that specifies the new visible state.
+ */
+mxGraphModel.prototype.visibleStateForCellChanged = function(cell, visible)
+{
+ var previous = this.isVisible(cell);
+ cell.setVisible(visible);
+
+ return previous;
+};
+
+/**
+ * Function: execute
+ *
+ * Executes the given edit and fires events if required. The edit object
+ * requires an execute function which is invoked. The edit is added to the
+ * <currentEdit> between <beginUpdate> and <endUpdate> calls, so that
+ * events will be fired if this execute is an individual transaction, that
+ * is, if no previous <beginUpdate> calls have been made without calling
+ * <endUpdate>. This implementation fires an <execute> event before
+ * executing the given change.
+ *
+ * Parameters:
+ *
+ * change - Object that described the change.
+ */
+mxGraphModel.prototype.execute = function(change)
+{
+ change.execute();
+ this.beginUpdate();
+ this.currentEdit.add(change);
+ this.fireEvent(new mxEventObject(mxEvent.EXECUTE, 'change', change));
+ this.endUpdate();
+};
+
+/**
+ * Function: beginUpdate
+ *
+ * Increments the <updateLevel> by one. The event notification
+ * is queued until <updateLevel> reaches 0 by use of
+ * <endUpdate>.
+ *
+ * All changes on <mxGraphModel> are transactional,
+ * that is, they are executed in a single undoable change
+ * on the model (without transaction isolation).
+ * Therefore, if you want to combine any
+ * number of changes into a single undoable change,
+ * you should group any two or more API calls that
+ * modify the graph model between <beginUpdate>
+ * and <endUpdate> calls as shown here:
+ *
+ * (code)
+ * var model = graph.getModel();
+ * var parent = graph.getDefaultParent();
+ * var index = model.getChildCount(parent);
+ * model.beginUpdate();
+ * try
+ * {
+ * model.add(parent, v1, index);
+ * model.add(parent, v2, index+1);
+ * }
+ * finally
+ * {
+ * model.endUpdate();
+ * }
+ * (end)
+ *
+ * Of course there is a shortcut for appending a
+ * sequence of cells into the default parent:
+ *
+ * (code)
+ * graph.addCells([v1, v2]).
+ * (end)
+ */
+mxGraphModel.prototype.beginUpdate = function()
+{
+ this.updateLevel++;
+ this.fireEvent(new mxEventObject(mxEvent.BEGIN_UPDATE));
+};
+
+/**
+ * Function: endUpdate
+ *
+ * Decrements the <updateLevel> by one and fires an <undo>
+ * event if the <updateLevel> reaches 0. This function
+ * indirectly fires a <change> event by invoking the notify
+ * function on the <currentEdit> und then creates a new
+ * <currentEdit> using <createUndoableEdit>.
+ *
+ * The <undo> event is fired only once per edit, whereas
+ * the <change> event is fired whenever the notify
+ * function is invoked, that is, on undo and redo of
+ * the edit.
+ */
+mxGraphModel.prototype.endUpdate = function()
+{
+ this.updateLevel--;
+
+ if (!this.endingUpdate)
+ {
+ this.endingUpdate = this.updateLevel == 0;
+ this.fireEvent(new mxEventObject(mxEvent.END_UPDATE, 'edit', this.currentEdit));
+
+ try
+ {
+ if (this.endingUpdate && !this.currentEdit.isEmpty())
+ {
+ this.fireEvent(new mxEventObject(mxEvent.BEFORE_UNDO, 'edit', this.currentEdit));
+ var tmp = this.currentEdit;
+ this.currentEdit = this.createUndoableEdit();
+ tmp.notify();
+ this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', tmp));
+ }
+ }
+ finally
+ {
+ this.endingUpdate = false;
+ }
+ }
+};
+
+/**
+ * Function: createUndoableEdit
+ *
+ * Creates a new <mxUndoableEdit> that implements the
+ * notify function to fire a <change> and <notify> event
+ * through the <mxUndoableEdit>'s source.
+ */
+mxGraphModel.prototype.createUndoableEdit = function()
+{
+ var edit = new mxUndoableEdit(this, true);
+
+ edit.notify = function()
+ {
+ // LATER: Remove changes property (deprecated)
+ edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ 'edit', edit, 'changes', edit.changes));
+ edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,
+ 'edit', edit, 'changes', edit.changes));
+ };
+
+ return edit;
+};
+
+/**
+ * Function: mergeChildren
+ *
+ * Merges the children of the given cell into the given target cell inside
+ * this model. All cells are cloned unless there is a corresponding cell in
+ * the model with the same id, in which case the source cell is ignored and
+ * all edges are connected to the corresponding cell in this model. Edges
+ * are considered to have no identity and are always cloned unless the
+ * cloneAllEdges flag is set to false, in which case edges with the same
+ * id in the target model are reconnected to reflect the terminals of the
+ * source edges.
+ */
+mxGraphModel.prototype.mergeChildren = function(from, to, cloneAllEdges)
+{
+ cloneAllEdges = (cloneAllEdges != null) ? cloneAllEdges : true;
+
+ this.beginUpdate();
+ try
+ {
+ var mapping = new Object();
+ this.mergeChildrenImpl(from, to, cloneAllEdges, mapping);
+
+ // Post-processes all edges in the mapping and
+ // reconnects the terminals to the corresponding
+ // cells in the target model
+ for (var key in mapping)
+ {
+ var cell = mapping[key];
+ var terminal = this.getTerminal(cell, true);
+
+ if (terminal != null)
+ {
+ terminal = mapping[mxCellPath.create(terminal)];
+ this.setTerminal(cell, terminal, true);
+ }
+
+ terminal = this.getTerminal(cell, false);
+
+ if (terminal != null)
+ {
+ terminal = mapping[mxCellPath.create(terminal)];
+ this.setTerminal(cell, terminal, false);
+ }
+ }
+ }
+ finally
+ {
+ this.endUpdate();
+ }
+};
+
+/**
+ * Function: mergeChildren
+ *
+ * Clones the children of the source cell into the given target cell in
+ * this model and adds an entry to the mapping that maps from the source
+ * cell to the target cell with the same id or the clone of the source cell
+ * that was inserted into this model.
+ */
+mxGraphModel.prototype.mergeChildrenImpl = function(from, to, cloneAllEdges, mapping)
+{
+ this.beginUpdate();
+ try
+ {
+ var childCount = from.getChildCount();
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var cell = from.getChildAt(i);
+
+ if (typeof(cell.getId) == 'function')
+ {
+ var id = cell.getId();
+ var target = (id != null && (!this.isEdge(cell) || !cloneAllEdges)) ?
+ this.getCell(id) : null;
+
+ // Clones and adds the child if no cell exists for the id
+ if (target == null)
+ {
+ var clone = cell.clone();
+ clone.setId(id);
+
+ // Sets the terminals from the original cell to the clone
+ // because the lookup uses strings not cells in JS
+ clone.setTerminal(cell.getTerminal(true), true);
+ clone.setTerminal(cell.getTerminal(false), false);
+
+ // Do *NOT* use model.add as this will move the edge away
+ // from the parent in updateEdgeParent if maintainEdgeParent
+ // is enabled in the target model
+ target = to.insert(clone);
+ this.cellAdded(target);
+ }
+
+ // Stores the mapping for later reconnecting edges
+ mapping[mxCellPath.create(cell)] = target;
+
+ // Recurses
+ this.mergeChildrenImpl(cell, target, cloneAllEdges, mapping);
+ }
+ }
+ }
+ finally
+ {
+ this.endUpdate();
+ }
+};
+
+/**
+ * Function: getParents
+ *
+ * Returns an array that represents the set (no duplicates) of all parents
+ * for the given array of cells.
+ *
+ * Parameters:
+ *
+ * cells - Array of cells whose parents should be returned.
+ */
+mxGraphModel.prototype.getParents = function(cells)
+{
+ var parents = [];
+
+ if (cells != null)
+ {
+ var hash = new Object();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var parent = this.getParent(cells[i]);
+
+ if (parent != null)
+ {
+ var id = mxCellPath.create(parent);
+
+ if (hash[id] == null)
+ {
+ hash[id] = parent;
+ parents.push(parent);
+ }
+ }
+ }
+ }
+
+ return parents;
+};
+
+//
+// Cell Cloning
+//
+
+/**
+ * Function: cloneCell
+ *
+ * Returns a deep clone of the given <mxCell> (including
+ * the children) which is created using <cloneCells>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be cloned.
+ */
+mxGraphModel.prototype.cloneCell = function(cell)
+{
+ if (cell != null)
+ {
+ return this.cloneCells([cell], true)[0];
+ }
+
+ return null;
+};
+
+/**
+ * Function: cloneCells
+ *
+ * Returns an array of clones for the given array of <mxCells>.
+ * Depending on the value of includeChildren, a deep clone is created for
+ * each cell. Connections are restored based if the corresponding
+ * cell is contained in the passed in array.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCell> to be cloned.
+ * includeChildren - Boolean indicating if the cells should be cloned
+ * with all descendants.
+ */
+mxGraphModel.prototype.cloneCells = function(cells, includeChildren)
+{
+ var mapping = new Object();
+ var clones = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (cells[i] != null)
+ {
+ clones.push(this.cloneCellImpl(cells[i], mapping, includeChildren));
+ }
+ else
+ {
+ clones.push(null);
+ }
+ }
+
+ for (var i = 0; i < clones.length; i++)
+ {
+ if (clones[i] != null)
+ {
+ this.restoreClone(clones[i], cells[i], mapping);
+ }
+ }
+
+ return clones;
+};
+
+/**
+ * Function: cloneCellImpl
+ *
+ * Inner helper method for cloning cells recursively.
+ */
+mxGraphModel.prototype.cloneCellImpl = function(cell, mapping, includeChildren)
+{
+ var clone = this.cellCloned(cell);
+
+ // Stores the clone in the lookup under the
+ // cell path for the original cell
+ mapping[mxObjectIdentity.get(cell)] = clone;
+
+ if (includeChildren)
+ {
+ var childCount = this.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var cloneChild = this.cloneCellImpl(
+ this.getChildAt(cell, i), mapping, true);
+ clone.insert(cloneChild);
+ }
+ }
+
+ return clone;
+};
+
+/**
+ * Function: cellCloned
+ *
+ * Hook for cloning the cell. This returns cell.clone() or
+ * any possible exceptions.
+ */
+mxGraphModel.prototype.cellCloned = function(cell)
+{
+ return cell.clone();
+};
+
+/**
+ * Function: restoreClone
+ *
+ * Inner helper method for restoring the connections in
+ * a network of cloned cells.
+ */
+mxGraphModel.prototype.restoreClone = function(clone, cell, mapping)
+{
+ var source = this.getTerminal(cell, true);
+
+ if (source != null)
+ {
+ var tmp = mapping[mxObjectIdentity.get(source)];
+
+ if (tmp != null)
+ {
+ tmp.insertEdge(clone, true);
+ }
+ }
+
+ var target = this.getTerminal(cell, false);
+
+ if (target != null)
+ {
+ var tmp = mapping[mxObjectIdentity.get(target)];
+
+ if (tmp != null)
+ {
+ tmp.insertEdge(clone, false);
+ }
+ }
+
+ var childCount = this.getChildCount(clone);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.restoreClone(this.getChildAt(clone, i),
+ this.getChildAt(cell, i), mapping);
+ }
+};
+
+//
+// Atomic changes
+//
+
+/**
+ * Class: mxRootChange
+ *
+ * Action to change the root in a model.
+ *
+ * Constructor: mxRootChange
+ *
+ * Constructs a change of the root in the
+ * specified model.
+ */
+function mxRootChange(model, root)
+{
+ this.model = model;
+ this.root = root;
+ this.previous = root;
+};
+
+/**
+ * Function: execute
+ *
+ * Carries out a change of the root using
+ * <mxGraphModel.rootChanged>.
+ */
+mxRootChange.prototype.execute = function()
+{
+ this.root = this.previous;
+ this.previous = this.model.rootChanged(this.previous);
+};
+
+/**
+ * Class: mxChildChange
+ *
+ * Action to add or remove a child in a model.
+ *
+ * Constructor: mxChildChange
+ *
+ * Constructs a change of a child in the
+ * specified model.
+ */
+function mxChildChange(model, parent, child, index)
+{
+ this.model = model;
+ this.parent = parent;
+ this.previous = parent;
+ this.child = child;
+ this.index = index;
+ this.previousIndex = index;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the parent of <child> using
+ * <mxGraphModel.parentForCellChanged> and
+ * removes or restores the cell's
+ * connections.
+ */
+mxChildChange.prototype.execute = function()
+{
+ var tmp = this.model.getParent(this.child);
+ var tmp2 = (tmp != null) ? tmp.getIndex(this.child) : 0;
+
+ if (this.previous == null)
+ {
+ this.connect(this.child, false);
+ }
+
+ tmp = this.model.parentForCellChanged(
+ this.child, this.previous, this.previousIndex);
+
+ if (this.previous != null)
+ {
+ this.connect(this.child, true);
+ }
+
+ this.parent = this.previous;
+ this.previous = tmp;
+ this.index = this.previousIndex;
+ this.previousIndex = tmp2;
+};
+
+/**
+ * Function: disconnect
+ *
+ * Disconnects the given cell recursively from its
+ * terminals and stores the previous terminal in the
+ * cell's terminals.
+ */
+mxChildChange.prototype.connect = function(cell, isConnect)
+{
+ isConnect = (isConnect != null) ? isConnect : true;
+
+ var source = cell.getTerminal(true);
+ var target = cell.getTerminal(false);
+
+ if (source != null)
+ {
+ if (isConnect)
+ {
+ this.model.terminalForCellChanged(cell, source, true);
+ }
+ else
+ {
+ this.model.terminalForCellChanged(cell, null, true);
+ }
+ }
+
+ if (target != null)
+ {
+ if (isConnect)
+ {
+ this.model.terminalForCellChanged(cell, target, false);
+ }
+ else
+ {
+ this.model.terminalForCellChanged(cell, null, false);
+ }
+ }
+
+ cell.setTerminal(source, true);
+ cell.setTerminal(target, false);
+
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i=0; i<childCount; i++)
+ {
+ this.connect(this.model.getChildAt(cell, i), isConnect);
+ }
+};
+
+/**
+ * Class: mxTerminalChange
+ *
+ * Action to change a terminal in a model.
+ *
+ * Constructor: mxTerminalChange
+ *
+ * Constructs a change of a terminal in the
+ * specified model.
+ */
+function mxTerminalChange(model, cell, terminal, source)
+{
+ this.model = model;
+ this.cell = cell;
+ this.terminal = terminal;
+ this.previous = terminal;
+ this.source = source;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the terminal of <cell> to <previous> using
+ * <mxGraphModel.terminalForCellChanged>.
+ */
+mxTerminalChange.prototype.execute = function()
+{
+ this.terminal = this.previous;
+ this.previous = this.model.terminalForCellChanged(
+ this.cell, this.previous, this.source);
+};
+
+/**
+ * Class: mxValueChange
+ *
+ * Action to change a user object in a model.
+ *
+ * Constructor: mxValueChange
+ *
+ * Constructs a change of a user object in the
+ * specified model.
+ */
+function mxValueChange(model, cell, value)
+{
+ this.model = model;
+ this.cell = cell;
+ this.value = value;
+ this.previous = value;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the value of <cell> to <previous> using
+ * <mxGraphModel.valueForCellChanged>.
+ */
+mxValueChange.prototype.execute = function()
+{
+ this.value = this.previous;
+ this.previous = this.model.valueForCellChanged(
+ this.cell, this.previous);
+};
+
+/**
+ * Class: mxStyleChange
+ *
+ * Action to change a cell's style in a model.
+ *
+ * Constructor: mxStyleChange
+ *
+ * Constructs a change of a style in the
+ * specified model.
+ */
+function mxStyleChange(model, cell, style)
+{
+ this.model = model;
+ this.cell = cell;
+ this.style = style;
+ this.previous = style;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the style of <cell> to <previous> using
+ * <mxGraphModel.styleForCellChanged>.
+ */
+mxStyleChange.prototype.execute = function()
+{
+ this.style = this.previous;
+ this.previous = this.model.styleForCellChanged(
+ this.cell, this.previous);
+};
+
+/**
+ * Class: mxGeometryChange
+ *
+ * Action to change a cell's geometry in a model.
+ *
+ * Constructor: mxGeometryChange
+ *
+ * Constructs a change of a geometry in the
+ * specified model.
+ */
+function mxGeometryChange(model, cell, geometry)
+{
+ this.model = model;
+ this.cell = cell;
+ this.geometry = geometry;
+ this.previous = geometry;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the geometry of <cell> ro <previous> using
+ * <mxGraphModel.geometryForCellChanged>.
+ */
+mxGeometryChange.prototype.execute = function()
+{
+ this.geometry = this.previous;
+ this.previous = this.model.geometryForCellChanged(
+ this.cell, this.previous);
+};
+
+/**
+ * Class: mxCollapseChange
+ *
+ * Action to change a cell's collapsed state in a model.
+ *
+ * Constructor: mxCollapseChange
+ *
+ * Constructs a change of a collapsed state in the
+ * specified model.
+ */
+function mxCollapseChange(model, cell, collapsed)
+{
+ this.model = model;
+ this.cell = cell;
+ this.collapsed = collapsed;
+ this.previous = collapsed;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the collapsed state of <cell> to <previous> using
+ * <mxGraphModel.collapsedStateForCellChanged>.
+ */
+mxCollapseChange.prototype.execute = function()
+{
+ this.collapsed = this.previous;
+ this.previous = this.model.collapsedStateForCellChanged(
+ this.cell, this.previous);
+};
+
+/**
+ * Class: mxVisibleChange
+ *
+ * Action to change a cell's visible state in a model.
+ *
+ * Constructor: mxVisibleChange
+ *
+ * Constructs a change of a visible state in the
+ * specified model.
+ */
+function mxVisibleChange(model, cell, visible)
+{
+ this.model = model;
+ this.cell = cell;
+ this.visible = visible;
+ this.previous = visible;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the visible state of <cell> to <previous> using
+ * <mxGraphModel.visibleStateForCellChanged>.
+ */
+mxVisibleChange.prototype.execute = function()
+{
+ this.visible = this.previous;
+ this.previous = this.model.visibleStateForCellChanged(
+ this.cell, this.previous);
+};
+
+/**
+ * Class: mxCellAttributeChange
+ *
+ * Action to change the attribute of a cell's user object.
+ * There is no method on the graph model that uses this
+ * action. To use the action, you can use the code shown
+ * in the example below.
+ *
+ * Example:
+ *
+ * To change the attributeName in the cell's user object
+ * to attributeValue, use the following code:
+ *
+ * (code)
+ * model.beginUpdate();
+ * try
+ * {
+ * var edit = new mxCellAttributeChange(
+ * cell, attributeName, attributeValue);
+ * model.execute(edit);
+ * }
+ * finally
+ * {
+ * model.endUpdate();
+ * }
+ * (end)
+ *
+ * Constructor: mxCellAttributeChange
+ *
+ * Constructs a change of a attribute of the DOM node
+ * stored as the value of the given <mxCell>.
+ */
+function mxCellAttributeChange(cell, attribute, value)
+{
+ this.cell = cell;
+ this.attribute = attribute;
+ this.value = value;
+ this.previous = value;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the attribute of the cell's user object by
+ * using <mxCell.setAttribute>.
+ */
+mxCellAttributeChange.prototype.execute = function()
+{
+ var tmp = this.cell.getAttribute(this.attribute);
+
+ if (this.previous == null)
+ {
+ this.cell.value.removeAttribute(this.attribute);
+ }
+ else
+ {
+ this.cell.setAttribute(this.attribute, this.previous);
+ }
+
+ this.previous = tmp;
+};
diff --git a/src/js/mxClient.js b/src/js/mxClient.js
new file mode 100644
index 0000000..a23b5fc
--- /dev/null
+++ b/src/js/mxClient.js
@@ -0,0 +1,643 @@
+/**
+ * $Id: mxClient.js,v 1.203 2012-07-19 15:19:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxClient =
+{
+
+ /**
+ * Class: mxClient
+ *
+ * Bootstrapping mechanism for the mxGraph thin client. The production version
+ * of this file contains all code required to run the mxGraph thin client, as
+ * well as global constants to identify the browser and operating system in
+ * use. You may have to load chrome://global/content/contentAreaUtils.js in
+ * your page to disable certain security restrictions in Mozilla.
+ *
+ * Variable: VERSION
+ *
+ * Contains the current version of the mxGraph library. The strings that
+ * communicate versions of mxGraph use the following format.
+ *
+ * versionMajor.versionMinor.buildNumber.revisionNumber
+ *
+ * Current version is 1.10.4.1.
+ */
+ VERSION: '1.10.4.1',
+
+ /**
+ * Variable: IS_IE
+ *
+ * True if the current browser is Internet Explorer.
+ */
+ IS_IE: navigator.userAgent.indexOf('MSIE') >= 0,
+
+ /**
+ * Variable: IS_IE6
+ *
+ * True if the current browser is Internet Explorer 6.x.
+ */
+ IS_IE6: navigator.userAgent.indexOf('MSIE 6') >= 0,
+
+ /**
+ * Variable: IS_QUIRKS
+ *
+ * True if the current browser is Internet Explorer and it is in quirks mode.
+ */
+ IS_QUIRKS: navigator.userAgent.indexOf('MSIE') >= 0 && (document.documentMode == null || document.documentMode == 5),
+
+ /**
+ * Variable: IS_NS
+ *
+ * True if the current browser is Netscape (including Firefox).
+ */
+ IS_NS: navigator.userAgent.indexOf('Mozilla/') >= 0 &&
+ navigator.userAgent.indexOf('MSIE') < 0,
+
+ /**
+ * Variable: IS_OP
+ *
+ * True if the current browser is Opera.
+ */
+ IS_OP: navigator.userAgent.indexOf('Opera/') >= 0,
+
+ /**
+ * Variable: IS_OT
+ *
+ * True if -o-transform is available as a CSS style. This is the case
+ * for Opera browsers that use Presto/2.5 and later.
+ */
+ IS_OT: navigator.userAgent.indexOf('Presto/2.4.') < 0 &&
+ navigator.userAgent.indexOf('Presto/2.3.') < 0 &&
+ navigator.userAgent.indexOf('Presto/2.2.') < 0 &&
+ navigator.userAgent.indexOf('Presto/2.1.') < 0 &&
+ navigator.userAgent.indexOf('Presto/2.0.') < 0 &&
+ navigator.userAgent.indexOf('Presto/1.') < 0,
+
+ /**
+ * Variable: IS_SF
+ *
+ * True if the current browser is Safari.
+ */
+ IS_SF: navigator.userAgent.indexOf('AppleWebKit/') >= 0 &&
+ navigator.userAgent.indexOf('Chrome/') < 0,
+
+ /**
+ * Variable: IS_GC
+ *
+ * True if the current browser is Google Chrome.
+ */
+ IS_GC: navigator.userAgent.indexOf('Chrome/') >= 0,
+
+ /**
+ * Variable: IS_MT
+ *
+ * True if -moz-transform is available as a CSS style. This is the case
+ * for all Firefox-based browsers newer than or equal 3, such as Camino,
+ * Iceweasel, Seamonkey and Iceape.
+ */
+ IS_MT: (navigator.userAgent.indexOf('Firefox/') >= 0 &&
+ navigator.userAgent.indexOf('Firefox/1.') < 0 &&
+ navigator.userAgent.indexOf('Firefox/2.') < 0) ||
+ (navigator.userAgent.indexOf('Iceweasel/') >= 0 &&
+ navigator.userAgent.indexOf('Iceweasel/1.') < 0 &&
+ navigator.userAgent.indexOf('Iceweasel/2.') < 0) ||
+ (navigator.userAgent.indexOf('SeaMonkey/') >= 0 &&
+ navigator.userAgent.indexOf('SeaMonkey/1.') < 0) ||
+ (navigator.userAgent.indexOf('Iceape/') >= 0 &&
+ navigator.userAgent.indexOf('Iceape/1.') < 0),
+
+ /**
+ * Variable: IS_SVG
+ *
+ * True if the browser supports SVG.
+ */
+ IS_SVG: navigator.userAgent.indexOf('Firefox/') >= 0 || // FF and Camino
+ navigator.userAgent.indexOf('Iceweasel/') >= 0 || // Firefox on Debian
+ navigator.userAgent.indexOf('Seamonkey/') >= 0 || // Firefox-based
+ navigator.userAgent.indexOf('Iceape/') >= 0 || // Seamonkey on Debian
+ navigator.userAgent.indexOf('Galeon/') >= 0 || // Gnome Browser (old)
+ navigator.userAgent.indexOf('Epiphany/') >= 0 || // Gnome Browser (new)
+ navigator.userAgent.indexOf('AppleWebKit/') >= 0 || // Safari/Google Chrome
+ navigator.userAgent.indexOf('Gecko/') >= 0 || // Netscape/Gecko
+ navigator.userAgent.indexOf('Opera/') >= 0,
+
+
+ /**
+ * Variable: NO_FO
+ *
+ * True if foreignObject support is not available. This is the case for
+ * Opera and older SVG-based browsers. IE does not require this type
+ * of tag.
+ */
+ NO_FO: navigator.userAgent.indexOf('Firefox/1.') >= 0 ||
+ navigator.userAgent.indexOf('Iceweasel/1.') >= 0 ||
+ navigator.userAgent.indexOf('Firefox/2.') >= 0 ||
+ navigator.userAgent.indexOf('Iceweasel/2.') >= 0 ||
+ navigator.userAgent.indexOf('SeaMonkey/1.') >= 0 ||
+ navigator.userAgent.indexOf('Iceape/1.') >= 0 ||
+ navigator.userAgent.indexOf('Camino/1.') >= 0 ||
+ navigator.userAgent.indexOf('Epiphany/2.') >= 0 ||
+ navigator.userAgent.indexOf('Opera/') >= 0 ||
+ navigator.userAgent.indexOf('MSIE') >= 0 ||
+ navigator.userAgent.indexOf('Mozilla/2.') >= 0, // Safari/Google Chrome
+
+ /**
+ * Variable: IS_VML
+ *
+ * True if the browser supports VML.
+ */
+ IS_VML: navigator.appName.toUpperCase() == 'MICROSOFT INTERNET EXPLORER',
+
+ /**
+ * Variable: IS_MAC
+ *
+ * True if the client is a Mac.
+ */
+ IS_MAC: navigator.userAgent.toUpperCase().indexOf('MACINTOSH') > 0,
+
+ /**
+ * Variable: IS_TOUCH
+ *
+ * True if this client uses a touch interface (no mouse). Currently this
+ * detects IPads, IPods, IPhones and Android devices.
+ */
+ IS_TOUCH: navigator.userAgent.toUpperCase().indexOf('IPAD') > 0 ||
+ navigator.userAgent.toUpperCase().indexOf('IPOD') > 0 ||
+ navigator.userAgent.toUpperCase().indexOf('IPHONE') > 0 ||
+ navigator.userAgent.toUpperCase().indexOf('ANDROID') > 0,
+
+ /**
+ * Variable: IS_LOCAL
+ *
+ * True if the documents location does not start with http:// or https://.
+ */
+ IS_LOCAL: document.location.href.indexOf('http://') < 0 &&
+ document.location.href.indexOf('https://') < 0,
+
+ /**
+ * Function: isBrowserSupported
+ *
+ * Returns true if the current browser is supported, that is, if
+ * <mxClient.IS_VML> or <mxClient.IS_SVG> is true.
+ *
+ * Example:
+ *
+ * (code)
+ * if (!mxClient.isBrowserSupported())
+ * {
+ * mxUtils.error('Browser is not supported!', 200, false);
+ * }
+ * (end)
+ */
+ isBrowserSupported: function()
+ {
+ return mxClient.IS_VML || mxClient.IS_SVG;
+ },
+
+ /**
+ * Function: link
+ *
+ * Adds a link node to the head of the document. Use this
+ * to add a stylesheet to the page as follows:
+ *
+ * (code)
+ * mxClient.link('stylesheet', filename);
+ * (end)
+ *
+ * where filename is the (relative) URL of the stylesheet. The charset
+ * is hardcoded to ISO-8859-1 and the type is text/css.
+ *
+ * Parameters:
+ *
+ * rel - String that represents the rel attribute of the link node.
+ * href - String that represents the href attribute of the link node.
+ * doc - Optional parent document of the link node.
+ */
+ link: function(rel, href, doc)
+ {
+ doc = doc || document;
+
+ // Workaround for Operation Aborted in IE6 if base tag is used in head
+ if (mxClient.IS_IE6)
+ {
+ doc.write('<link rel="'+rel+'" href="'+href+'" charset="ISO-8859-1" type="text/css"/>');
+ }
+ else
+ {
+ var link = doc.createElement('link');
+
+ link.setAttribute('rel', rel);
+ link.setAttribute('href', href);
+ link.setAttribute('charset', 'ISO-8859-1');
+ link.setAttribute('type', 'text/css');
+
+ var head = doc.getElementsByTagName('head')[0];
+ head.appendChild(link);
+ }
+ },
+
+ /**
+ * Function: include
+ *
+ * Dynamically adds a script node to the document header.
+ *
+ * In production environments, the includes are resolved in the mxClient.js
+ * file to reduce the number of requests required for client startup. This
+ * function should only be used in development environments, but not in
+ * production systems.
+ */
+ include: function(src)
+ {
+ document.write('<script src="'+src+'"></script>');
+ },
+
+ /**
+ * Function: dispose
+ *
+ * Frees up memory in IE by resolving cyclic dependencies between the DOM
+ * and the JavaScript objects. This is always invoked in IE when the page
+ * unloads.
+ */
+ dispose: function()
+ {
+ // Cleans all objects where listeners have been added
+ for (var i = 0; i < mxEvent.objects.length; i++)
+ {
+ if (mxEvent.objects[i].mxListenerList != null)
+ {
+ mxEvent.removeAllListeners(mxEvent.objects[i]);
+ }
+ }
+ }
+
+};
+
+/**
+ * Variable: mxLoadResources
+ *
+ * Optional global config variable to toggle loading of the two resource files
+ * in <mxGraph> and <mxEditor>. Default is true. NOTE: This is a global variable,
+ * not a variable of mxClient.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * var mxLoadResources = false;
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxLoadResources) == 'undefined')
+{
+ mxLoadResources = true;
+}
+
+/**
+ * Variable: mxLoadStylesheets
+ *
+ * Optional global config variable to toggle loading of the CSS files when
+ * the library is initialized. Default is true. NOTE: This is a global variable,
+ * not a variable of mxClient.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * var mxLoadStylesheets = false;
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxLoadStylesheets) == 'undefined')
+{
+ mxLoadStylesheets = true;
+}
+
+/**
+ * Variable: basePath
+ *
+ * Basepath for all URLs in the core without trailing slash. Default is '.'.
+ * Set mxBasePath prior to loading the mxClient library as follows to override
+ * this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * mxBasePath = '/path/to/core/directory';
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ *
+ * When using a relative path, the path is relative to the URL of the page that
+ * contains the assignment. Trailing slashes are automatically removed.
+ */
+if (typeof(mxBasePath) != 'undefined' && mxBasePath.length > 0)
+{
+ // Adds a trailing slash if required
+ if (mxBasePath.substring(mxBasePath.length - 1) == '/')
+ {
+ mxBasePath = mxBasePath.substring(0, mxBasePath.length - 1);
+ }
+
+ mxClient.basePath = mxBasePath;
+}
+else
+{
+ mxClient.basePath = '.';
+}
+
+/**
+ * Variable: imageBasePath
+ *
+ * Basepath for all images URLs in the core without trailing slash. Default is
+ * <mxClient.basePath> + '/images'. Set mxImageBasePath prior to loading the
+ * mxClient library as follows to override this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * mxImageBasePath = '/path/to/image/directory';
+ * </script>
+ * <script type="text/javascript" src="/path/to/core/directory/js/mxClient.js"></script>
+ * (end)
+ *
+ * When using a relative path, the path is relative to the URL of the page that
+ * contains the assignment. Trailing slashes are automatically removed.
+ */
+if (typeof(mxImageBasePath) != 'undefined' && mxImageBasePath.length > 0)
+{
+ // Adds a trailing slash if required
+ if (mxImageBasePath.substring(mxImageBasePath.length - 1) == '/')
+ {
+ mxImageBasePath = mxImageBasePath.substring(0, mxImageBasePath.length - 1);
+ }
+
+ mxClient.imageBasePath = mxImageBasePath;
+}
+else
+{
+ mxClient.imageBasePath = mxClient.basePath + '/images';
+}
+
+/**
+ * Variable: language
+ *
+ * Defines the language of the client, eg. en for english, de for german etc.
+ * The special value 'none' will disable all built-in internationalization and
+ * resource loading. See <mxResources.getSpecialBundle> for handling identifiers
+ * with and without a dash.
+ *
+ * Set mxLanguage prior to loading the mxClient library as follows to override
+ * this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * mxLanguage = 'en';
+ * </script>
+ * <script type="text/javascript" src="js/mxClient.js"></script>
+ * (end)
+ *
+ * If internationalization is disabled, then the following variables should be
+ * overridden to reflect the current language of the system. These variables are
+ * cleared when i18n is disabled.
+ * <mxEditor.askZoomResource>, <mxEditor.lastSavedResource>,
+ * <mxEditor.currentFileResource>, <mxEditor.propertiesResource>,
+ * <mxEditor.tasksResource>, <mxEditor.helpResource>, <mxEditor.outlineResource>,
+ * <mxElbowEdgeHandler.doubleClickOrientationResource>, <mxUtils.errorResource>,
+ * <mxUtils.closeResource>, <mxGraphSelectionModel.doneResource>,
+ * <mxGraphSelectionModel.updatingSelectionResource>, <mxGraphView.doneResource>,
+ * <mxGraphView.updatingDocumentResource>, <mxCellRenderer.collapseExpandResource>,
+ * <mxGraph.containsValidationErrorsResource> and
+ * <mxGraph.alreadyConnectedResource>.
+ */
+if (typeof(mxLanguage) != 'undefined')
+{
+ mxClient.language = mxLanguage;
+}
+else
+{
+ mxClient.language = (mxClient.IS_IE) ? navigator.userLanguage : navigator.language;
+}
+
+/**
+ * Variable: defaultLanguage
+ *
+ * Defines the default language which is used in the common resource files. Any
+ * resources for this language will only load the common resource file, but not
+ * the language-specific resource file. Default is 'en'.
+ *
+ * Set mxDefaultLanguage prior to loading the mxClient library as follows to override
+ * this setting:
+ *
+ * (code)
+ * <script type="text/javascript">
+ * mxDefaultLanguage = 'de';
+ * </script>
+ * <script type="text/javascript" src="js/mxClient.js"></script>
+ * (end)
+ */
+if (typeof(mxDefaultLanguage) != 'undefined')
+{
+ mxClient.defaultLanguage = mxDefaultLanguage;
+}
+else
+{
+ mxClient.defaultLanguage = 'en';
+}
+
+// Adds all required stylesheets and namespaces
+if (mxLoadStylesheets)
+{
+ mxClient.link('stylesheet', mxClient.basePath + '/css/common.css');
+}
+
+/**
+ * Variable: languages
+ *
+ * Defines the optional array of all supported language extensions. The default
+ * language does not have to be part of this list. See
+ * <mxResources.isLanguageSupported>.
+ *
+ * (code)
+ * <script type="text/javascript">
+ * mxLanguages = ['de', 'it', 'fr'];
+ * </script>
+ * <script type="text/javascript" src="js/mxClient.js"></script>
+ * (end)
+ *
+ * This is used to avoid unnecessary requests to language files, ie. if a 404
+ * will be returned.
+ */
+if (typeof(mxLanguages) != 'undefined')
+{
+ mxClient.languages = mxLanguages;
+}
+
+if (mxClient.IS_IE)
+{
+ // IE9/10 standards mode uses SVG (VML is broken)
+ if (document.documentMode >= 9)
+ {
+ mxClient.IS_VML = false;
+ mxClient.IS_SVG = true;
+ }
+ else
+ {
+ // Enables support for IE8 standards mode. Note that this requires all attributes for VML
+ // elements to be set using direct notation, ie. node.attr = value. The use of setAttribute
+ // is not possible. See mxShape.init for more code to handle this specific document mode.
+ if (document.documentMode == 8)
+ {
+ document.namespaces.add('v', 'urn:schemas-microsoft-com:vml', '#default#VML');
+ document.namespaces.add('o', 'urn:schemas-microsoft-com:office:office', '#default#VML');
+ }
+ else
+ {
+ document.namespaces.add('v', 'urn:schemas-microsoft-com:vml');
+ document.namespaces.add('o', 'urn:schemas-microsoft-com:office:office');
+ }
+
+ var ss = document.createStyleSheet();
+ ss.cssText = 'v\\:*{behavior:url(#default#VML)}o\\:*{behavior:url(#default#VML)}';
+
+ if (mxLoadStylesheets)
+ {
+ mxClient.link('stylesheet', mxClient.basePath + '/css/explorer.css');
+ }
+ }
+
+ // Cleans up resources when the application terminates
+ window.attachEvent('onunload', mxClient.dispose);
+}
+
+mxClient.include(mxClient.basePath+'/js/util/mxLog.js');
+mxClient.include(mxClient.basePath+'/js/util/mxObjectIdentity.js');
+mxClient.include(mxClient.basePath+'/js/util/mxDictionary.js');
+mxClient.include(mxClient.basePath+'/js/util/mxResources.js');
+mxClient.include(mxClient.basePath+'/js/util/mxPoint.js');
+mxClient.include(mxClient.basePath+'/js/util/mxRectangle.js');
+mxClient.include(mxClient.basePath+'/js/util/mxEffects.js');
+mxClient.include(mxClient.basePath+'/js/util/mxUtils.js');
+mxClient.include(mxClient.basePath+'/js/util/mxConstants.js');
+mxClient.include(mxClient.basePath+'/js/util/mxEventObject.js');
+mxClient.include(mxClient.basePath+'/js/util/mxMouseEvent.js');
+mxClient.include(mxClient.basePath+'/js/util/mxEventSource.js');
+mxClient.include(mxClient.basePath+'/js/util/mxEvent.js');
+mxClient.include(mxClient.basePath+'/js/util/mxXmlRequest.js');
+mxClient.include(mxClient.basePath+'/js/util/mxClipboard.js');
+mxClient.include(mxClient.basePath+'/js/util/mxWindow.js');
+mxClient.include(mxClient.basePath+'/js/util/mxForm.js');
+mxClient.include(mxClient.basePath+'/js/util/mxImage.js');
+mxClient.include(mxClient.basePath+'/js/util/mxDivResizer.js');
+mxClient.include(mxClient.basePath+'/js/util/mxDragSource.js');
+mxClient.include(mxClient.basePath+'/js/util/mxToolbar.js');
+mxClient.include(mxClient.basePath+'/js/util/mxSession.js');
+mxClient.include(mxClient.basePath+'/js/util/mxUndoableEdit.js');
+mxClient.include(mxClient.basePath+'/js/util/mxUndoManager.js');
+mxClient.include(mxClient.basePath+'/js/util/mxUrlConverter.js');
+mxClient.include(mxClient.basePath+'/js/util/mxPanningManager.js');
+mxClient.include(mxClient.basePath+'/js/util/mxPath.js');
+mxClient.include(mxClient.basePath+'/js/util/mxPopupMenu.js');
+mxClient.include(mxClient.basePath+'/js/util/mxAutoSaveManager.js');
+mxClient.include(mxClient.basePath+'/js/util/mxAnimation.js');
+mxClient.include(mxClient.basePath+'/js/util/mxMorphing.js');
+mxClient.include(mxClient.basePath+'/js/util/mxImageBundle.js');
+mxClient.include(mxClient.basePath+'/js/util/mxImageExport.js');
+mxClient.include(mxClient.basePath+'/js/util/mxXmlCanvas2D.js');
+mxClient.include(mxClient.basePath+'/js/util/mxSvgCanvas2D.js');
+mxClient.include(mxClient.basePath+'/js/util/mxGuide.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxShape.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxStencil.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxStencilRegistry.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxStencilShape.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxMarker.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxActor.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxCloud.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxRectangleShape.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxEllipse.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxDoubleEllipse.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxRhombus.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxPolyline.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxArrow.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxText.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxTriangle.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxHexagon.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxLine.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxImageShape.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxLabel.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxCylinder.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxConnector.js');
+mxClient.include(mxClient.basePath+'/js/shape/mxSwimlane.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxGraphLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxStackLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxPartitionLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxCompactTreeLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxFastOrganicLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxCircleLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxParallelEdgeLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxCompositeLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/mxEdgeLabelLayout.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxGraphAbstractHierarchyCell.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxGraphHierarchyNode.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxGraphHierarchyEdge.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/model/mxGraphHierarchyModel.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxHierarchicalLayoutStage.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxMedianHybridCrossingReduction.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxMinimumCycleRemover.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/stage/mxCoordinateAssignment.js');
+mxClient.include(mxClient.basePath+'/js/layout/hierarchical/mxHierarchicalLayout.js');
+mxClient.include(mxClient.basePath+'/js/model/mxGraphModel.js');
+mxClient.include(mxClient.basePath+'/js/model/mxCell.js');
+mxClient.include(mxClient.basePath+'/js/model/mxGeometry.js');
+mxClient.include(mxClient.basePath+'/js/model/mxCellPath.js');
+mxClient.include(mxClient.basePath+'/js/view/mxPerimeter.js');
+mxClient.include(mxClient.basePath+'/js/view/mxPrintPreview.js');
+mxClient.include(mxClient.basePath+'/js/view/mxStylesheet.js');
+mxClient.include(mxClient.basePath+'/js/view/mxCellState.js');
+mxClient.include(mxClient.basePath+'/js/view/mxGraphSelectionModel.js');
+mxClient.include(mxClient.basePath+'/js/view/mxCellEditor.js');
+mxClient.include(mxClient.basePath+'/js/view/mxCellRenderer.js');
+mxClient.include(mxClient.basePath+'/js/view/mxEdgeStyle.js');
+mxClient.include(mxClient.basePath+'/js/view/mxStyleRegistry.js');
+mxClient.include(mxClient.basePath+'/js/view/mxGraphView.js');
+mxClient.include(mxClient.basePath+'/js/view/mxGraph.js');
+mxClient.include(mxClient.basePath+'/js/view/mxCellOverlay.js');
+mxClient.include(mxClient.basePath+'/js/view/mxOutline.js');
+mxClient.include(mxClient.basePath+'/js/view/mxMultiplicity.js');
+mxClient.include(mxClient.basePath+'/js/view/mxLayoutManager.js');
+mxClient.include(mxClient.basePath+'/js/view/mxSpaceManager.js');
+mxClient.include(mxClient.basePath+'/js/view/mxSwimlaneManager.js');
+mxClient.include(mxClient.basePath+'/js/view/mxTemporaryCellStates.js');
+mxClient.include(mxClient.basePath+'/js/view/mxCellStatePreview.js');
+mxClient.include(mxClient.basePath+'/js/view/mxConnectionConstraint.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxGraphHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxPanningHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxCellMarker.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxSelectionCellsHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxConnectionHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxConstraintHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxRubberband.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxVertexHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxEdgeHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxElbowEdgeHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxEdgeSegmentHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxKeyHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxTooltipHandler.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxCellTracker.js');
+mxClient.include(mxClient.basePath+'/js/handler/mxCellHighlight.js');
+mxClient.include(mxClient.basePath+'/js/editor/mxDefaultKeyHandler.js');
+mxClient.include(mxClient.basePath+'/js/editor/mxDefaultPopupMenu.js');
+mxClient.include(mxClient.basePath+'/js/editor/mxDefaultToolbar.js');
+mxClient.include(mxClient.basePath+'/js/editor/mxEditor.js');
+mxClient.include(mxClient.basePath+'/js/io/mxCodecRegistry.js');
+mxClient.include(mxClient.basePath+'/js/io/mxCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxObjectCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxCellCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxModelCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxRootChangeCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxChildChangeCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxTerminalChangeCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxGenericChangeCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxGraphCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxGraphViewCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxStylesheetCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxDefaultKeyHandlerCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxDefaultToolbarCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxDefaultPopupMenuCodec.js');
+mxClient.include(mxClient.basePath+'/js/io/mxEditorCodec.js');
diff --git a/src/js/shape/mxActor.js b/src/js/shape/mxActor.js
new file mode 100644
index 0000000..e6a0765
--- /dev/null
+++ b/src/js/shape/mxActor.js
@@ -0,0 +1,183 @@
+/**
+ * $Id: mxActor.js,v 1.35 2012-07-31 11:46:53 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxActor
+ *
+ * Extends <mxShape> to implement an actor shape. If a custom shape with one
+ * filled area is needed, then this shape's <redrawPath> should be overridden.
+ *
+ * Example:
+ *
+ * (code)
+ * function SampleShape() { }
+ *
+ * SampleShape.prototype = new mxActor();
+ * SampleShape.prototype.constructor = vsAseShape;
+ *
+ * mxCellRenderer.prototype.defaultShapes['sample'] = SampleShape;
+ * SampleShape.prototype.redrawPath = function(path, x, y, w, h)
+ * {
+ * path.moveTo(0, 0);
+ * path.lineTo(w, h);
+ * // ...
+ * path.close();
+ * }
+ * (end)
+ *
+ * This shape is registered under <mxConstants.SHAPE_ACTOR> in
+ * <mxCellRenderer>.
+ *
+ * Constructor: mxActor
+ *
+ * Constructs a new actor shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxActor(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxActor.prototype = new mxShape();
+mxActor.prototype.constructor = mxActor;
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw in VML in mixed Html mode.
+ */
+mxActor.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw as VML in prefer Html mode.
+ */
+mxActor.prototype.preferModeHtml = false;
+
+/**
+ * Variable: vmlScale
+ *
+ * Renders VML with a scale of 2.
+ */
+mxActor.prototype.vmlScale = 2;
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node(s) to represent this shape.
+ */
+mxActor.prototype.createVml = function()
+{
+ var node = document.createElement('v:shape');
+ node.style.position = 'absolute';
+ this.configureVmlShape(node);
+
+ return node;
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Updates the VML node(s) to reflect the latest bounds and scale.
+ */
+mxActor.prototype.redrawVml = function()
+{
+ this.updateVmlShape(this.node);
+ this.node.path = this.createPath();
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxActor.prototype.createSvg = function()
+{
+ return this.createSvgGroup('path');
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxActor.prototype.redrawSvg = function()
+{
+ var strokeWidth = Math.round(Math.max(1, this.strokewidth * this.scale));
+ this.innerNode.setAttribute('stroke-width', strokeWidth);
+ this.innerNode.setAttribute('stroke-linejoin', 'round');
+
+ if (this.crisp && (this.rotation == null || this.rotation == 0))
+ {
+ this.innerNode.setAttribute('shape-rendering', 'crispEdges');
+ }
+ else
+ {
+ this.innerNode.removeAttribute('shape-rendering');
+ }
+
+ var d = this.createPath();
+
+ if (d.length > 0)
+ {
+ this.innerNode.setAttribute('d', d);
+
+ if (this.shadowNode != null)
+ {
+ this.shadowNode.setAttribute('transform', this.getSvgShadowTransform() +
+ (this.innerNode.getAttribute('transform') || ''));
+ this.shadowNode.setAttribute('stroke-width', strokeWidth);
+ this.shadowNode.setAttribute('d', d);
+ }
+ }
+ else
+ {
+ this.innerNode.removeAttribute('d');
+
+ if (this.shadowNode != null)
+ {
+ this.shadowNode.removeAttribute('d');
+ }
+ }
+
+ if (this.isDashed)
+ {
+ var phase = Math.max(1, Math.round(3 * this.scale * this.strokewidth));
+ this.innerNode.setAttribute('stroke-dasharray', phase + ' ' + phase);
+ }
+};
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape. This method uses the <mxPath>
+ * abstraction to paint the shape for VML and SVG.
+ */
+mxActor.prototype.redrawPath = function(path, x, y, w, h)
+{
+ var width = w/3;
+ path.moveTo(0, h);
+ path.curveTo(0, 3 * h / 5, 0, 2 * h / 5, w / 2, 2 * h / 5);
+ path.curveTo(w / 2 - width, 2 * h / 5, w / 2 - width, 0, w / 2, 0);
+ path.curveTo(w / 2 + width, 0, w / 2 + width, 2 * h / 5, w / 2, 2 * h / 5);
+ path.curveTo(w, 2 * h / 5, w, 3 * h / 5, w, h);
+ path.close();
+};
diff --git a/src/js/shape/mxArrow.js b/src/js/shape/mxArrow.js
new file mode 100644
index 0000000..93777d8
--- /dev/null
+++ b/src/js/shape/mxArrow.js
@@ -0,0 +1,226 @@
+/**
+ * $Id: mxArrow.js,v 1.31 2012-05-23 19:09:22 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxArrow
+ *
+ * Extends <mxShape> to implement an arrow shape. (The shape
+ * is used to represent edges, not vertices.)
+ * This shape is registered under <mxConstants.SHAPE_ARROW>
+ * in <mxCellRenderer>.
+ *
+ * Constructor: mxArrow
+ *
+ * Constructs a new arrow shape.
+ *
+ * Parameters:
+ *
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ * arrowWidth - Optional integer that defines the arrow width. Default is
+ * <mxConstants.ARROW_WIDTH>. This is stored in <arrowWidth>.
+ * spacing - Optional integer that defines the spacing between the arrow shape
+ * and its endpoints. Default is <mxConstants.ARROW_SPACING>. This is stored in
+ * <spacing>.
+ * endSize - Optional integer that defines the size of the arrowhead. Default
+ * is <mxConstants.ARROW_SIZE>. This is stored in <endSize>.
+ */
+function mxArrow(points, fill, stroke, strokewidth, arrowWidth, spacing, endSize)
+{
+ this.points = points;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+ this.arrowWidth = (arrowWidth != null) ? arrowWidth : mxConstants.ARROW_WIDTH;
+ this.spacing = (spacing != null) ? spacing : mxConstants.ARROW_SPACING;
+ this.endSize = (endSize != null) ? endSize : mxConstants.ARROW_SIZE;
+};
+
+/**
+ * Extends <mxActor>.
+ */
+mxArrow.prototype = new mxActor();
+mxArrow.prototype.constructor = mxArrow;
+
+/**
+ * Variable: addPipe
+ *
+ * Specifies if a SVG path should be created around any path to increase the
+ * tolerance for mouse events. Default is false since this shape is filled.
+ */
+mxArrow.prototype.addPipe = false;
+
+/**
+ * Variable: enableFill
+ *
+ * Specifies if fill colors should be ignored. This must be set to true for
+ * shapes that are stroked only. Default is true since this shape is filled.
+ */
+mxArrow.prototype.enableFill = true;
+
+/**
+ * Function: configureTransparentBackground
+ *
+ * Overidden to remove transparent background.
+ */
+mxArrow.prototype.configureTransparentBackground = function(node)
+{
+ // do nothing
+};
+
+/**
+ * Function: updateBoundingBox
+ *
+ * Updates the <boundingBox> for this shape.
+ */
+mxArrow.prototype.augmentBoundingBox = function(bbox)
+{
+ // FIXME: Fix precision, share math and cache results with painting code
+ bbox.grow(Math.max(this.arrowWidth / 2, this.endSize / 2) * this.scale);
+
+ mxShape.prototype.augmentBoundingBox.apply(this, arguments);
+};
+
+/**
+ * Function: createVml
+ *
+ * Extends <mxShape.createVml> to ignore fill if <enableFill> is false.
+ */
+mxArrow.prototype.createVml = function()
+{
+ if (!this.enableFill)
+ {
+ this.fill = null;
+ }
+
+ return mxActor.prototype.createVml.apply(this, arguments);
+};
+
+/**
+ * Function: createSvg
+ *
+ * Extends <mxActor.createSvg> to ignore fill if <enableFill> is false and
+ * create an event handling shape if <this.addPipe> is true.
+ */
+mxArrow.prototype.createSvg = function()
+{
+ if (!this.enableFill)
+ {
+ this.fill = null;
+ }
+
+ var g = mxActor.prototype.createSvg.apply(this, arguments);
+
+ // Creates an invisible shape around the path for easier
+ // selection with the mouse. Note: Firefox does not ignore
+ // the value of the stroke attribute for pointer-events: stroke,
+ // it does, however, ignore the visibility attribute.
+ if (this.addPipe)
+ {
+ this.pipe = this.createSvgPipe();
+ g.appendChild(this.pipe);
+ }
+
+ return g;
+};
+
+/**
+ * Function: reconfigure
+ *
+ * Extends <mxActor.reconfigure> to ignore fill if <enableFill> is false.
+ */
+mxArrow.prototype.reconfigure = function()
+{
+ if (!this.enableFill)
+ {
+ this.fill = null;
+ }
+
+ mxActor.prototype.reconfigure.apply(this, arguments);
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Extends <mxActor.redrawSvg> to update the event handling shape if one
+ * exists.
+ */
+mxArrow.prototype.redrawSvg = function()
+{
+ mxActor.prototype.redrawSvg.apply(this, arguments);
+
+ if (this.pipe != null)
+ {
+ var d = this.innerNode.getAttribute('d');
+
+ if (d != null)
+ {
+ this.pipe.setAttribute('d', this.innerNode.getAttribute('d'));
+ var strokeWidth = Math.round(this.strokewidth * this.scale);
+ this.pipe.setAttribute('stroke-width', strokeWidth + mxShape.prototype.SVG_STROKE_TOLERANCE);
+ }
+ }
+};
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape. This method uses the <mxPath>
+ * abstraction to paint the shape for VML and SVG.
+ */
+mxArrow.prototype.redrawPath = function(path, x, y, w, h)
+{
+ // All points are offset
+ path.translate.x -= x;
+ path.translate.y -= y;
+
+ // Geometry of arrow
+ var spacing = this.spacing * this.scale;
+ var width = this.arrowWidth * this.scale;
+ var arrow = this.endSize * this.scale;
+
+ // Base vector (between end points)
+ var p0 = this.points[0];
+ var pe = this.points[this.points.length - 1];
+
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ var length = dist - 2 * spacing - arrow;
+
+ // Computes the norm and the inverse norm
+ var nx = dx / dist;
+ var ny = dy / dist;
+ var basex = length * nx;
+ var basey = length * ny;
+ var floorx = width * ny/3;
+ var floory = -width * nx/3;
+
+ // Computes points
+ var p0x = p0.x - floorx / 2 + spacing * nx;
+ var p0y = p0.y - floory / 2 + spacing * ny;
+ var p1x = p0x + floorx;
+ var p1y = p0y + floory;
+ var p2x = p1x + basex;
+ var p2y = p1y + basey;
+ var p3x = p2x + floorx;
+ var p3y = p2y + floory;
+ // p4 not necessary
+ var p5x = p3x - 3 * floorx;
+ var p5y = p3y - 3 * floory;
+
+ path.moveTo(p0x, p0y);
+ path.lineTo(p1x, p1y);
+ path.lineTo(p2x, p2y);
+ path.lineTo(p3x, p3y);
+ path.lineTo(pe.x - spacing * nx, pe.y - spacing * ny);
+ path.lineTo(p5x, p5y);
+ path.lineTo(p5x + floorx, p5y + floory);
+ path.lineTo(p0x, p0y);
+ path.close();
+};
diff --git a/src/js/shape/mxCloud.js b/src/js/shape/mxCloud.js
new file mode 100644
index 0000000..3893a1b
--- /dev/null
+++ b/src/js/shape/mxCloud.js
@@ -0,0 +1,56 @@
+/**
+ * $Id: mxCloud.js,v 1.12 2011-06-24 11:27:30 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCloud
+ *
+ * Extends <mxActor> to implement a cloud shape.
+ *
+ * This shape is registered under <mxConstants.SHAPE_CLOUD> in
+ * <mxCellRenderer>.
+ *
+ * Constructor: mxCloud
+ *
+ * Constructs a new cloud shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxCloud(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxActor.
+ */
+mxCloud.prototype = new mxActor();
+mxCloud.prototype.constructor = mxActor;
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape. This method uses the <mxPath>
+ * abstraction to paint the shape for VML and SVG.
+ */
+mxCloud.prototype.redrawPath = function(path, x, y, w, h)
+{
+ path.moveTo(0.25 * w, 0.25 * h);
+ path.curveTo(0.05 * w, 0.25 * h, 0, 0.5 * h, 0.16 * w, 0.55 * h);
+ path.curveTo(0, 0.66 * h, 0.18 * w, 0.9 * h, 0.31 * w, 0.8 * h);
+ path.curveTo(0.4 * w, h, 0.7 * w, h, 0.8 * w, 0.8 * h);
+ path.curveTo(w, 0.8 * h, w, 0.6 * h, 0.875 * w, 0.5 * h);
+ path.curveTo(w, 0.3 * h, 0.8 * w, 0.1 * h, 0.625 * w, 0.2 * h);
+ path.curveTo(0.5 * w, 0.05 * h, 0.3 * w, 0.05 * h, 0.25 * w, 0.25 * h);
+ path.close();
+};
diff --git a/src/js/shape/mxConnector.js b/src/js/shape/mxConnector.js
new file mode 100644
index 0000000..092bf79
--- /dev/null
+++ b/src/js/shape/mxConnector.js
@@ -0,0 +1,446 @@
+/**
+ * $Id: mxConnector.js,v 1.80 2012-05-24 12:00:45 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxConnector
+ *
+ * Extends <mxShape> to implement a connector shape. The connector
+ * shape allows for arrow heads on either side.
+ *
+ * This shape is registered under <mxConstants.SHAPE_CONNECTOR> in
+ * <mxCellRenderer>.
+ *
+ * Constructor: mxConnector
+ *
+ * Constructs a new connector shape.
+ *
+ * Parameters:
+ *
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * Default is 'black'.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxConnector(points, stroke, strokewidth)
+{
+ this.points = points;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxConnector.prototype = new mxShape();
+mxConnector.prototype.constructor = mxConnector;
+
+/**
+ * Variable: vmlNodes
+ *
+ * Adds local references to <mxShape.vmlNodes>.
+ */
+mxConnector.prototype.vmlNodes = mxConnector.prototype.vmlNodes.concat([
+ 'shapeNode', 'start', 'end', 'startStroke', 'endStroke', 'startFill', 'endFill']);
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw in VML in mixed Html mode.
+ */
+mxConnector.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw as VML in prefer Html mode.
+ */
+mxConnector.prototype.preferModeHtml = false;
+
+/**
+ * Variable: allowCrispMarkers
+ *
+ * Specifies if <mxShape.crisp> should be allowed for markers. Default is false.
+ */
+mxConnector.prototype.allowCrispMarkers = false;
+
+/**
+ * Variable: addPipe
+ *
+ * Specifies if a SVG path should be created around any path to increase the
+ * tolerance for mouse events. Default is false since this shape is filled.
+ */
+mxConnector.prototype.addPipe = true;
+
+/**
+ * Function: configureHtmlShape
+ *
+ * Overrides <mxShape.configureHtmlShape> to clear the border and background.
+ */
+mxConnector.prototype.configureHtmlShape = function(node)
+{
+ mxShape.prototype.configureHtmlShape.apply(this, arguments);
+ node.style.borderStyle = '';
+ node.style.background = '';
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxConnector.prototype.createVml = function()
+{
+ var node = document.createElement('v:group');
+ node.style.position = 'absolute';
+ this.shapeNode = document.createElement('v:shape');
+ this.updateVmlStrokeColor(this.shapeNode);
+ this.updateVmlStrokeNode(this.shapeNode);
+ node.appendChild(this.shapeNode);
+ this.shapeNode.filled = 'false';
+
+ if (this.isShadow)
+ {
+ this.createVmlShadow(this.shapeNode);
+ }
+
+ // Creates the start arrow as an additional child path
+ if (this.startArrow != null)
+ {
+ this.start = document.createElement('v:shape');
+ this.start.style.position = 'absolute';
+
+ // Only required for opacity and joinstyle
+ this.startStroke = document.createElement('v:stroke');
+ this.startStroke.joinstyle = 'miter';
+ this.start.appendChild(this.startStroke);
+
+ this.startFill = document.createElement('v:fill');
+ this.start.appendChild(this.startFill);
+
+ node.appendChild(this.start);
+ }
+
+ // Creates the end arrows as an additional child path
+ if (this.endArrow != null)
+ {
+ this.end = document.createElement('v:shape');
+ this.end.style.position = 'absolute';
+
+ // Only required for opacity and joinstyle
+ this.endStroke = document.createElement('v:stroke');
+ this.endStroke.joinstyle = 'miter';
+ this.end.appendChild(this.endStroke);
+
+ this.endFill = document.createElement('v:fill');
+ this.end.appendChild(this.endFill);
+
+ node.appendChild(this.end);
+ }
+
+ this.updateVmlMarkerOpacity();
+
+ return node;
+};
+
+/**
+ * Function: updateVmlMarkerOpacity
+ *
+ * Updates the opacity for the markers in VML.
+ */
+mxConnector.prototype.updateVmlMarkerOpacity = function()
+{
+ var op = (this.opacity != null) ? (this.opacity + '%') : '100%';
+
+ if (this.start != null)
+ {
+ this.startFill.opacity = op;
+ this.startStroke.opacity = op;
+ }
+
+ if (this.end != null)
+ {
+ this.endFill.opacity = op;
+ this.endStroke.opacity = op;
+ }
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Redraws this VML shape by invoking <updateVmlShape> on this.node.
+ */
+mxConnector.prototype.reconfigure = function()
+{
+ // Never fill a connector
+ this.fill = null;
+
+ if (mxUtils.isVml(this.node))
+ {
+ // Updates the style of the given shape
+ // LATER: Check if this can be replaced with redrawVml and
+ // updating the color, dash pattern and shadow.
+ this.node.style.visibility = 'hidden';
+ this.configureVmlShape(this.shapeNode);
+ this.updateVmlMarkerOpacity();
+ this.node.style.visibility = 'visible';
+ }
+ else
+ {
+ mxShape.prototype.reconfigure.apply(this, arguments);
+ }
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Redraws this VML shape by invoking <updateVmlShape> on this.node.
+ */
+mxConnector.prototype.redrawVml = function()
+{
+ if (this.node != null && this.points != null && this.bounds != null &&
+ !isNaN(this.bounds.x) && !isNaN(this.bounds.y) &&
+ !isNaN(this.bounds.width) && !isNaN(this.bounds.height))
+ {
+ var w = Math.max(0, Math.round(this.bounds.width));
+ var h = Math.max(0, Math.round(this.bounds.height));
+ var cs = w + ',' + h;
+ w += 'px';
+ h += 'px';
+
+ // Computes the marker paths before the main path is updated so
+ // that offsets can be taken into account
+ if (this.start != null)
+ {
+ this.start.style.width = w;
+ this.start.style.height = h;
+ this.start.coordsize = cs;
+
+ var p0 = this.points[1];
+ var pe = this.points[0];
+
+ var size = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_MARKERSIZE);
+ this.startOffset = this.redrawMarker(this.start, this.startArrow, p0, pe, this.stroke, size);
+ }
+
+ if (this.end != null)
+ {
+ this.end.style.width = w;
+ this.end.style.height = h;
+ this.end.coordsize = cs;
+
+ var n = this.points.length;
+ var p0 = this.points[n - 2];
+ var pe = this.points[n - 1];
+
+ var size = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE, mxConstants.DEFAULT_MARKERSIZE);
+ this.endOffset = this.redrawMarker(this.end, this.endArrow, p0, pe, this.stroke, size);
+ }
+
+ this.updateVmlShape(this.node);
+ this.updateVmlShape(this.shapeNode);
+ this.shapeNode.filled = 'false';
+
+ // Adds custom dash pattern
+ if (this.isDashed)
+ {
+ var pat = mxUtils.getValue(this.style, 'dashStyle', null);
+
+ if (pat != null)
+ {
+ this.strokeNode.dashstyle = pat;
+ }
+
+ if (this.shadowStrokeNode != null)
+ {
+ this.shadowStrokeNode.dashstyle = this.strokeNode.dashstyle;
+ }
+ }
+ }
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node to represent this shape.
+ */
+mxConnector.prototype.createSvg = function()
+{
+ this.fill = null;
+ var g = this.createSvgGroup('path');
+
+ // Creates the start arrow as an additional child path
+ if (this.startArrow != null)
+ {
+ this.start = document.createElementNS(mxConstants.NS_SVG, 'path');
+ g.appendChild(this.start);
+ }
+
+ // Creates the end arrows as an additional child path
+ if (this.endArrow != null)
+ {
+ this.end = document.createElementNS(mxConstants.NS_SVG, 'path');
+ g.appendChild(this.end);
+ }
+
+ // Creates an invisible shape around the path for easier
+ // selection with the mouse. Note: Firefox does not ignore
+ // the value of the stroke attribute for pointer-events: stroke,
+ // it does, however, ignore the visibility attribute.
+ if (this.addPipe)
+ {
+ this.pipe = this.createSvgPipe();
+ g.appendChild(this.pipe);
+ }
+
+ return g;
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxConnector.prototype.redrawSvg = function()
+{
+ // Computes the markers first which modifies the coordinates of the
+ // endpoints to not overlap with the painted marker then updates the actual
+ // shape for the edge to take the modified endpoints into account.
+ if (this.points != null && this.points[0] != null)
+ {
+ var color = this.innerNode.getAttribute('stroke');
+
+ // Draws the start marker
+ if (this.start != null)
+ {
+ var p0 = this.points[1];
+ var pe = this.points[0];
+
+ var size = mxUtils.getNumber(this.style, mxConstants.STYLE_STARTSIZE,
+ mxConstants.DEFAULT_MARKERSIZE);
+ this.startOffset = this.redrawMarker(this.start,
+ this.startArrow, p0, pe, color, size);
+
+ if (this.allowCrispMarkers && this.crisp)
+ {
+ this.start.setAttribute('shape-rendering', 'crispEdges');
+ }
+ else
+ {
+ this.start.removeAttribute('shape-rendering');
+ }
+ }
+
+ // Draws the end marker
+ if (this.end != null)
+ {
+ var n = this.points.length;
+
+ var p0 = this.points[n - 2];
+ var pe = this.points[n - 1];
+
+ var size = mxUtils.getNumber(this.style, mxConstants.STYLE_ENDSIZE,
+ mxConstants.DEFAULT_MARKERSIZE);
+ this.endOffset = this.redrawMarker(this.end,
+ this.endArrow, p0, pe, color, size);
+
+ if (this.allowCrispMarkers && this.crisp)
+ {
+ this.end.setAttribute('shape-rendering', 'crispEdges');
+ }
+ else
+ {
+ this.end.removeAttribute('shape-rendering');
+ }
+ }
+ }
+
+ this.updateSvgShape(this.innerNode);
+ var d = this.innerNode.getAttribute('d');
+
+ if (d != null)
+ {
+ var strokeWidth = Math.round(this.strokewidth * this.scale);
+
+ // Updates the tolerance of the invisible shape for event handling
+ if (this.pipe != null)
+ {
+ this.pipe.setAttribute('d', this.innerNode.getAttribute('d'));
+ this.pipe.setAttribute('stroke-width', strokeWidth + mxShape.prototype.SVG_STROKE_TOLERANCE);
+ }
+
+ // Updates the shadow
+ if (this.shadowNode != null)
+ {
+ this.shadowNode.setAttribute('transform', this.getSvgShadowTransform());
+ this.shadowNode.setAttribute('d', d);
+ this.shadowNode.setAttribute('stroke-width', strokeWidth);
+ }
+ }
+
+ // Adds custom dash pattern
+ if (this.isDashed)
+ {
+ var pat = this.createDashPattern(this.scale * this.strokewidth);
+
+ if (pat != null)
+ {
+ this.innerNode.setAttribute('stroke-dasharray', pat);
+ }
+ }
+
+ // Updates the shadow
+ if (this.shadowNode != null)
+ {
+ var pat = this.innerNode.getAttribute('stroke-dasharray');
+
+ if (pat != null)
+ {
+ this.shadowNode.setAttribute('stroke-dasharray', pat);
+ }
+ }
+};
+
+/**
+ * Function: createDashPattern
+ *
+ * Creates a dash pattern for the given factor.
+ */
+mxConnector.prototype.createDashPattern = function(factor)
+{
+ var value = mxUtils.getValue(this.style, 'dashPattern', null);
+
+ if (value != null)
+ {
+ var tmp = value.split(' ');
+ var pat = [];
+
+ for (var i = 0; i < tmp.length; i++)
+ {
+ if (tmp[i].length > 0)
+ {
+ pat.push(Math.round(Number(tmp[i]) * factor));
+ }
+ }
+
+ return pat.join(' ');
+ }
+
+ return null;
+};
+
+/**
+ * Function: redrawMarker
+ *
+ * Updates the given SVG or VML marker.
+ */
+mxConnector.prototype.redrawMarker = function(node, type, p0, pe, color, size)
+{
+ return mxMarker.paintMarker(node, type, p0, pe, color, this.strokewidth,
+ size, this.scale, this.bounds.x, this.bounds.y, this.start == node,
+ this.style);
+};
diff --git a/src/js/shape/mxCylinder.js b/src/js/shape/mxCylinder.js
new file mode 100644
index 0000000..9a45760
--- /dev/null
+++ b/src/js/shape/mxCylinder.js
@@ -0,0 +1,319 @@
+/**
+ * $Id: mxCylinder.js,v 1.38 2012-07-31 11:46:53 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCylinder
+ *
+ * Extends <mxShape> to implement an cylinder shape. If a
+ * custom shape with one filled area and an overlay path is
+ * needed, then this shape's <redrawPath> should be overridden.
+ * This shape is registered under <mxConstants.SHAPE_CYLINDER>
+ * in <mxCellRenderer>.
+ *
+ * Constructor: mxCylinder
+ *
+ * Constructs a new cylinder shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxCylinder(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxCylinder.prototype = new mxShape();
+mxCylinder.prototype.constructor = mxCylinder;
+
+/**
+ * Variable: vmlNodes
+ *
+ * Adds local references to <mxShape.vmlNodes>.
+ */
+mxCylinder.prototype.vmlNodes = mxCylinder.prototype.vmlNodes.concat(['background', 'foreground']);
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw in VML in mixed Html mode.
+ */
+mxCylinder.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw as VML in prefer Html mode.
+ */
+mxCylinder.prototype.preferModeHtml = false;
+
+/**
+ * Variable: addPipe
+ *
+ * Specifies if a SVG path should be created around the background for better
+ * hit detection. Default is false.
+ */
+mxCylinder.prototype.addPipe = false;
+
+/**
+ * Variable: strokedBackground
+ *
+ * Specifies if the background should be stroked. Default is true.
+ */
+mxCylinder.prototype.strokedBackground = true;
+
+/**
+ * Variable: maxHeight
+ *
+ * Defines the maximum height of the top and bottom part
+ * of the cylinder shape.
+ */
+mxCylinder.prototype.maxHeight = 40;
+
+/**
+ * Variable: vmlScale
+ *
+ * Renders VML with a scale of 2.
+ */
+mxCylinder.prototype.vmlScale = 2;
+
+/**
+ * Function: create
+ *
+ * Overrides the method to make sure the <stroke> is never
+ * null. If it is null is will be assigned the <fill> color.
+ */
+mxCylinder.prototype.create = function(container)
+{
+ if (this.stroke == null)
+ {
+ this.stroke = this.fill;
+ }
+
+ // Calls superclass implementation of create
+ return mxShape.prototype.create.apply(this, arguments);
+};
+
+/**
+ * Function: reconfigure
+ *
+ * Overrides the method to make sure the <stroke> is applied to the foreground.
+ */
+mxCylinder.prototype.reconfigure = function()
+{
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.configureSvgShape(this.foreground);
+ this.foreground.setAttribute('fill', 'none');
+ }
+ else if (mxUtils.isVml(this.node))
+ {
+ this.configureVmlShape(this.background);
+ this.configureVmlShape(this.foreground);
+ }
+
+ mxShape.prototype.reconfigure.apply(this);
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxCylinder.prototype.createVml = function()
+{
+ var node = document.createElement('v:group');
+
+ // Draws the background
+ this.background = document.createElement('v:shape');
+ this.label = this.background;
+ this.configureVmlShape(this.background);
+ node.appendChild(this.background);
+
+ // Ignores values that only apply to the background
+ this.fill = null;
+ this.isShadow = false;
+ this.configureVmlShape(node);
+
+ // Draws the foreground
+ this.foreground = document.createElement('v:shape');
+ this.configureVmlShape(this.foreground);
+
+ // To match SVG defaults jointsyle miter, miterlimit 4
+ this.fgStrokeNode = document.createElement('v:stroke');
+ this.fgStrokeNode.joinstyle = 'miter';
+ this.fgStrokeNode.miterlimit = 4;
+ this.foreground.appendChild(this.fgStrokeNode);
+
+ node.appendChild(this.foreground);
+
+ return node;
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Updates the VML node(s) to reflect the latest bounds and scale.
+ */
+mxCylinder.prototype.redrawVml = function()
+{
+ this.updateVmlShape(this.node);
+ this.updateVmlShape(this.background);
+ this.updateVmlShape(this.foreground);
+ this.background.path = this.createPath(false);
+ this.foreground.path = this.createPath(true);
+
+ this.fgStrokeNode.dashstyle = this.strokeNode.dashstyle;
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxCylinder.prototype.createSvg = function()
+{
+ var g = this.createSvgGroup('path');
+ this.foreground = document.createElementNS(mxConstants.NS_SVG, 'path');
+
+ if (this.stroke != null && this.stroke != mxConstants.NONE)
+ {
+ this.foreground.setAttribute('stroke', this.stroke);
+ }
+ else
+ {
+ this.foreground.setAttribute('stroke', 'none');
+ }
+
+ this.foreground.setAttribute('fill', 'none');
+ g.appendChild(this.foreground);
+
+ if (this.addPipe)
+ {
+ this.pipe = this.createSvgPipe();
+ g.appendChild(this.pipe);
+ }
+
+ return g;
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxCylinder.prototype.redrawSvg = function()
+{
+ var strokeWidth = Math.round(Math.max(1, this.strokewidth * this.scale));
+ this.innerNode.setAttribute('stroke-width', strokeWidth);
+
+ if (this.crisp && (this.rotation == null || this.rotation == 0))
+ {
+ this.innerNode.setAttribute('shape-rendering', 'crispEdges');
+ this.foreground.setAttribute('shape-rendering', 'crispEdges');
+ }
+ else
+ {
+ this.innerNode.removeAttribute('shape-rendering');
+ this.foreground.removeAttribute('shape-rendering');
+ }
+
+ // Paints background
+ var d = this.createPath(false);
+
+ if (d.length > 0)
+ {
+ this.innerNode.setAttribute('d', d);
+
+ // Updates event handling element
+ if (this.pipe != null)
+ {
+ this.pipe.setAttribute('d', d);
+ this.pipe.setAttribute('stroke-width', strokeWidth + mxShape.prototype.SVG_STROKE_TOLERANCE);
+ this.pipe.setAttribute('transform', (this.innerNode.getAttribute('transform') || ''));
+ }
+ }
+ else
+ {
+ this.innerNode.removeAttribute('d');
+
+ // Updates event handling element
+ if (this.pipe != null)
+ {
+ this.pipe.removeAttribute('d');
+ }
+ }
+
+ // Stroked background
+ if (!this.strokedBackground)
+ {
+ this.innerNode.setAttribute('stroke', 'none');
+ }
+
+ // Paints shadow
+ if (this.shadowNode != null)
+ {
+ this.shadowNode.setAttribute('stroke-width', strokeWidth);
+ this.shadowNode.setAttribute('d', d);
+ this.shadowNode.setAttribute('transform', this.getSvgShadowTransform());
+ }
+
+ // Paints foreground
+ d = this.createPath(true);
+
+ if (d.length > 0)
+ {
+ this.foreground.setAttribute('stroke-width', strokeWidth);
+ this.foreground.setAttribute('d', d);
+ }
+ else
+ {
+ this.foreground.removeAttribute('d');
+ }
+
+ if (this.isDashed)
+ {
+ var phase = Math.max(1, Math.round(3 * this.scale * this.strokewidth));
+ this.innerNode.setAttribute('stroke-dasharray', phase + ' ' + phase);
+ this.foreground.setAttribute('stroke-dasharray', phase + ' ' + phase);
+ }
+};
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape. This method uses the <mxPath>
+ * abstraction to paint the shape for VML and SVG.
+ */
+mxCylinder.prototype.redrawPath = function(path, x, y, w, h, isForeground)
+{
+ var dy = Math.min(this.maxHeight, Math.round(h / 5));
+
+ if (isForeground)
+ {
+ path.moveTo(0, dy);
+ path.curveTo(0, 2 * dy, w, 2 * dy, w, dy);
+ }
+ else
+ {
+ path.moveTo(0, dy);
+ path.curveTo(0, -dy / 3, w, -dy / 3, w, dy);
+ path.lineTo(w, h - dy);
+ path.curveTo(w, h + dy / 3, 0, h + dy / 3, 0, h - dy);
+ path.close();
+ }
+};
diff --git a/src/js/shape/mxDoubleEllipse.js b/src/js/shape/mxDoubleEllipse.js
new file mode 100644
index 0000000..7854851
--- /dev/null
+++ b/src/js/shape/mxDoubleEllipse.js
@@ -0,0 +1,203 @@
+/**
+ * $Id: mxDoubleEllipse.js,v 1.19 2012-05-21 18:27:17 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxDoubleEllipse
+ *
+ * Extends <mxShape> to implement a double ellipse shape.
+ * This shape is registered under <mxConstants.SHAPE_DOUBLE_ELLIPSE>
+ * in <mxCellRenderer>.
+ *
+ * Constructor: mxDoubleEllipse
+ *
+ * Constructs a new ellipse shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxDoubleEllipse(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxDoubleEllipse.prototype = new mxShape();
+mxDoubleEllipse.prototype.constructor = mxDoubleEllipse;
+
+/**
+ * Variable: vmlNodes
+ *
+ * Adds local references to <mxShape.vmlNodes>.
+ */
+mxDoubleEllipse.prototype.vmlNodes = mxDoubleEllipse.prototype.vmlNodes.concat(['background', 'foreground']);
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw in VML in mixed Html mode.
+ */
+mxDoubleEllipse.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw as VML in prefer Html mode.
+ */
+mxDoubleEllipse.prototype.preferModeHtml = false;
+
+/**
+ * Variable: vmlScale
+ *
+ * Renders VML with a scale of 2.
+ */
+mxDoubleEllipse.prototype.vmlScale = 2;
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxDoubleEllipse.prototype.createVml = function()
+{
+ var node = document.createElement('v:group');
+
+ // Draws the background
+ this.background = document.createElement('v:arc');
+ this.background.startangle = '0';
+ this.background.endangle = '360';
+ this.configureVmlShape(this.background);
+
+ node.appendChild(this.background);
+
+ // Ignores values that only apply to the background
+ this.label = this.background;
+ this.isShadow = false;
+ this.fill = null;
+
+ // Draws the foreground
+ this.foreground = document.createElement('v:oval');
+ this.configureVmlShape(this.foreground);
+
+ node.appendChild(this.foreground);
+
+ this.stroke = null;
+ this.configureVmlShape(node);
+
+ return node;
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Updates the VML node(s) to reflect the latest bounds and scale.
+ */
+mxDoubleEllipse.prototype.redrawVml = function()
+{
+ this.updateVmlShape(this.node);
+ this.updateVmlShape(this.background);
+ this.updateVmlShape(this.foreground);
+
+ var inset = Math.round((this.strokewidth + 3) * this.scale) * this.vmlScale;
+ var w = Math.round(this.bounds.width * this.vmlScale);
+ var h = Math.round(this.bounds.height * this.vmlScale);
+
+ this.foreground.style.top = inset + 'px'; // relative
+ this.foreground.style.left = inset + 'px'; // relative
+ this.foreground.style.width = Math.max(0, w - 2 * inset) + 'px';
+ this.foreground.style.height = Math.max(0, h - 2 * inset) + 'px';
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxDoubleEllipse.prototype.createSvg = function()
+{
+ var g = this.createSvgGroup('ellipse');
+ this.foreground = document.createElementNS(mxConstants.NS_SVG, 'ellipse');
+
+ if (this.stroke != null)
+ {
+ this.foreground.setAttribute('stroke', this.stroke);
+ }
+ else
+ {
+ this.foreground.setAttribute('stroke', 'none');
+ }
+
+ this.foreground.setAttribute('fill', 'none');
+ g.appendChild(this.foreground);
+
+ return g;
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxDoubleEllipse.prototype.redrawSvg = function()
+{
+ if (this.crisp)
+ {
+ this.innerNode.setAttribute('shape-rendering', 'crispEdges');
+ this.foreground.setAttribute('shape-rendering', 'crispEdges');
+ }
+ else
+ {
+ this.innerNode.removeAttribute('shape-rendering');
+ this.foreground.removeAttribute('shape-rendering');
+ }
+
+ this.updateSvgNode(this.innerNode);
+ this.updateSvgNode(this.shadowNode);
+ this.updateSvgNode(this.foreground, (this.strokewidth + 3) * this.scale);
+
+ if (this.isDashed)
+ {
+ var phase = Math.max(1, Math.round(3 * this.scale * this.strokewidth));
+ this.innerNode.setAttribute('stroke-dasharray', phase + ' ' + phase);
+ }
+};
+
+/**
+ * Function: updateSvgNode
+ *
+ * Updates the given node to reflect the new <bounds> and <scale>.
+ */
+mxDoubleEllipse.prototype.updateSvgNode = function(node, inset)
+{
+ inset = (inset != null) ? inset : 0;
+
+ if (node != null)
+ {
+ var strokeWidth = Math.round(Math.max(1, this.strokewidth * this.scale));
+ node.setAttribute('stroke-width', strokeWidth);
+
+ node.setAttribute('cx', this.bounds.x + this.bounds.width / 2);
+ node.setAttribute('cy', this.bounds.y + this.bounds.height / 2);
+ node.setAttribute('rx', Math.max(0, this.bounds.width / 2 - inset));
+ node.setAttribute('ry', Math.max(0, this.bounds.height / 2 - inset));
+
+ // Updates the transform of the shadow
+ if (this.shadowNode != null)
+ {
+ this.shadowNode.setAttribute('transform', this.getSvgShadowTransform());
+ }
+ }
+};
diff --git a/src/js/shape/mxEllipse.js b/src/js/shape/mxEllipse.js
new file mode 100644
index 0000000..f3882cf
--- /dev/null
+++ b/src/js/shape/mxEllipse.js
@@ -0,0 +1,132 @@
+/**
+ * $Id: mxEllipse.js,v 1.20 2012-04-04 07:34:50 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxEllipse
+ *
+ * Extends <mxShape> to implement an ellipse shape.
+ * This shape is registered under <mxConstants.SHAPE_ELLIPSE>
+ * in <mxCellRenderer>.
+ *
+ * Constructor: mxEllipse
+ *
+ * Constructs a new ellipse shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxEllipse(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxEllipse.prototype = new mxShape();
+mxEllipse.prototype.constructor = mxEllipse;
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw in VML in mixed Html mode.
+ */
+mxEllipse.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw as VML in prefer Html mode.
+ */
+mxEllipse.prototype.preferModeHtml = false;
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxEllipse.prototype.createVml = function()
+{
+ // Uses an arc not an oval to make sure the
+ // textbox fills out the outer bounds of the
+ // circle, not just the inner rectangle
+ var node = document.createElement('v:arc');
+ node.startangle = '0';
+ node.endangle = '360';
+ this.configureVmlShape(node);
+
+ return node;
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxEllipse.prototype.createSvg = function()
+{
+ return this.createSvgGroup('ellipse');
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxEllipse.prototype.redrawSvg = function()
+{
+ if (this.crisp)
+ {
+ this.innerNode.setAttribute('shape-rendering', 'crispEdges');
+ }
+ else
+ {
+ this.innerNode.removeAttribute('shape-rendering');
+ }
+
+ this.updateSvgNode(this.innerNode);
+ this.updateSvgNode(this.shadowNode);
+};
+
+/**
+ * Function: updateSvgNode
+ *
+ * Updates the given node to reflect the new <bounds> and <scale>.
+ */
+mxEllipse.prototype.updateSvgNode = function(node)
+{
+ if (node != null)
+ {
+ var strokeWidth = Math.round(Math.max(1, this.strokewidth * this.scale));
+ node.setAttribute('stroke-width', strokeWidth);
+
+ node.setAttribute('cx', this.bounds.x + this.bounds.width / 2);
+ node.setAttribute('cy', this.bounds.y + this.bounds.height / 2);
+ node.setAttribute('rx', this.bounds.width / 2);
+ node.setAttribute('ry', this.bounds.height / 2);
+
+ // Updates the shadow offset
+ if (this.shadowNode != null)
+ {
+ this.shadowNode.setAttribute('transform', this.getSvgShadowTransform());
+ }
+
+ if (this.isDashed)
+ {
+ var phase = Math.max(1, Math.round(3 * this.scale * this.strokewidth));
+ node.setAttribute('stroke-dasharray', phase + ' ' + phase);
+ }
+ }
+};
diff --git a/src/js/shape/mxHexagon.js b/src/js/shape/mxHexagon.js
new file mode 100644
index 0000000..7fa45a3
--- /dev/null
+++ b/src/js/shape/mxHexagon.js
@@ -0,0 +1,37 @@
+/**
+ * $Id: mxHexagon.js,v 1.8 2011-09-02 10:01:00 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxHexagon
+ *
+ * Implementation of the hexagon shape.
+ *
+ * Constructor: mxHexagon
+ *
+ * Constructs a new hexagon shape.
+ */
+function mxHexagon() { };
+
+/**
+ * Extends <mxActor>.
+ */
+mxHexagon.prototype = new mxActor();
+mxHexagon.prototype.constructor = mxHexagon;
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape. This method uses the <mxPath>
+ * abstraction to paint the shape for VML and SVG.
+ */
+mxHexagon.prototype.redrawPath = function(path, x, y, w, h)
+{
+ path.moveTo(0.25 * w, 0);
+ path.lineTo(0.75 * w, 0);
+ path.lineTo(w, 0.5 * h);
+ path.lineTo(0.75 * w, h);
+ path.lineTo(0.25 * w, h);
+ path.lineTo(0, 0.5 * h);
+ path.close();
+};
diff --git a/src/js/shape/mxImageShape.js b/src/js/shape/mxImageShape.js
new file mode 100644
index 0000000..2f1eab0
--- /dev/null
+++ b/src/js/shape/mxImageShape.js
@@ -0,0 +1,405 @@
+/**
+ * $Id: mxImageShape.js,v 1.67 2012-04-22 10:16:23 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxImageShape
+ *
+ * Extends <mxShape> to implement an image shape. This shape is registered
+ * under <mxConstants.SHAPE_IMAGE> in <mxCellRenderer>.
+ *
+ * Constructor: mxImageShape
+ *
+ * Constructs a new image shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * image - String that specifies the URL of the image. This is stored in
+ * <image>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 0. This is stored in <strokewidth>.
+ */
+function mxImageShape(bounds, image, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.image = (image != null) ? image : '';
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+ this.isShadow = false;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxImageShape.prototype = new mxShape();
+mxImageShape.prototype.constructor = mxImageShape;
+
+/**
+ * Variable: crisp
+ *
+ * Disables crisp rendering via attributes. Image quality defines the rendering
+ * quality. Default is false.
+ */
+mxImageShape.prototype.crisp = false;
+
+/**
+ * Variable: preserveImageAspect
+ *
+ * Switch to preserve image aspect. Default is true.
+ */
+mxImageShape.prototype.preserveImageAspect = true;
+
+/**
+ * Function: apply
+ *
+ * Overrides <mxShape.apply> to replace the fill and stroke colors with the
+ * respective values from <mxConstants.STYLE_IMAGE_BACKGROUND> and
+ * <mxConstants.STYLE_IMAGE_BORDER>.
+ *
+ * Applies the style of the given <mxCellState> to the shape. This
+ * implementation assigns the following styles to local fields:
+ *
+ * - <mxConstants.STYLE_IMAGE_BACKGROUND> => fill
+ * - <mxConstants.STYLE_IMAGE_BORDER> => stroke
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the corresponding cell.
+ */
+mxImageShape.prototype.apply = function(state)
+{
+ mxShape.prototype.apply.apply(this, arguments);
+
+ this.fill = null;
+ this.stroke = null;
+
+ if (this.style != null)
+ {
+ this.fill = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BACKGROUND);
+ this.stroke = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_BORDER);
+ this.preserveImageAspect = mxUtils.getNumber(this.style, mxConstants.STYLE_IMAGE_ASPECT, 1) == 1;
+ this.gradient = null;
+ }
+};
+
+/**
+ * Function: create
+ *
+ * Override to create HTML regardless of gradient and
+ * rounded property.
+ */
+mxImageShape.prototype.create = function()
+{
+ var node = null;
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ // Workaround: To avoid control-click on images in Firefox to
+ // open the image in a new window, this image needs to be placed
+ // inside a group with a rectangle in the foreground which has a
+ // fill property but no visibility and absorbs all events.
+ // The image in turn must have all pointer-events disabled.
+ node = this.createSvgGroup('rect');
+ this.innerNode.setAttribute('visibility', 'hidden');
+ this.innerNode.setAttribute('pointer-events', 'fill');
+
+ this.imageNode = document.createElementNS(mxConstants.NS_SVG, 'image');
+ this.imageNode.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', this.image);
+ this.imageNode.setAttribute('style', 'pointer-events:none');
+ this.configureSvgShape(this.imageNode);
+
+ // Removes invalid attributes on the image node
+ this.imageNode.removeAttribute('stroke');
+ this.imageNode.removeAttribute('fill');
+ node.insertBefore(this.imageNode, this.innerNode);
+
+ // Inserts node for background and border color rendering
+ if ((this.fill != null && this.fill != mxConstants.NONE) ||
+ (this.stroke != null && this.stroke != mxConstants.NONE))
+ {
+ this.bg = document.createElementNS(mxConstants.NS_SVG, 'rect');
+ node.insertBefore(this.bg, node.firstChild);
+ }
+
+ // Preserves image aspect as default
+ if (!this.preserveImageAspect)
+ {
+ this.imageNode.setAttribute('preserveAspectRatio', 'none');
+ }
+ }
+ else
+ {
+ // Uses VML image for all non-embedded images in IE to support better
+ // image flipping quality and avoid workarounds for event redirection
+ var flipH = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_FLIPH, 0) == 1;
+ var flipV = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_FLIPV, 0) == 1;
+ var img = this.image.toUpperCase();
+
+ // Handles non-flipped embedded images in IE6
+ if (mxClient.IS_IE && !flipH && !flipV && img.substring(0, 6) == 'MHTML:')
+ {
+ // LATER: Check if outer DIV is required or if aspect can be implemented
+ // by adding an offset to the image loading or the background via CSS.
+ this.imageNode = document.createElement('DIV');
+ this.imageNode.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader ' +
+ '(src=\'' + this.image + '\', sizingMethod=\'scale\')';
+
+ node = document.createElement('DIV');
+ this.configureHtmlShape(node);
+ node.appendChild(this.imageNode);
+ }
+ // Handles all data URL images and HTML images for IE9 with no VML support (in SVG mode)
+ else if (!mxClient.IS_IE || img.substring(0, 5) == 'DATA:' || document.documentMode >= 9)
+ {
+ this.imageNode = document.createElement('img');
+ this.imageNode.setAttribute('src', this.image);
+ this.imageNode.setAttribute('border', '0');
+ this.imageNode.style.position = 'absolute';
+ this.imageNode.style.width = '100%';
+ this.imageNode.style.height = '100%';
+
+ node = document.createElement('DIV');
+ this.configureHtmlShape(node);
+ node.appendChild(this.imageNode);
+ }
+ else
+ {
+ this.imageNode = document.createElement('v:image');
+ this.imageNode.style.position = 'absolute';
+ this.imageNode.src = this.image;
+
+ // Needed to draw the background and border but known
+ // to cause problems in print preview with https
+ node = document.createElement('DIV');
+ this.configureHtmlShape(node);
+
+ // Workaround for cropped images in IE7/8
+ node.style.overflow = 'visible';
+ node.appendChild(this.imageNode);
+ }
+ }
+
+ return node;
+};
+
+/**
+ * Function: updateAspect
+ *
+ * Updates the aspect of the image for the given image width and height.
+ */
+mxImageShape.prototype.updateAspect = function(w, h)
+{
+ var s = Math.min(this.bounds.width / w, this.bounds.height / h);
+ w = Math.max(0, Math.round(w * s));
+ h = Math.max(0, Math.round(h * s));
+ var x0 = Math.max(0, Math.round((this.bounds.width - w) / 2));
+ var y0 = Math.max(0, Math.round((this.bounds.height - h) / 2));
+ var st = this.imageNode.style;
+
+ // Positions the child node relative to the parent node
+ if (this.imageNode.parentNode == this.node)
+ {
+ // Workaround for duplicate offset in VML in IE8 is
+ // to use parent padding instead of left and top
+ this.node.style.paddingLeft = x0 + 'px';
+ this.node.style.paddingTop = y0 + 'px';
+ }
+ else
+ {
+ st.left = (Math.round(this.bounds.x) + x0) + 'px';
+ st.top = (Math.round(this.bounds.y) + y0) + 'px';
+ }
+
+ st.width = w + 'px';
+ st.height = h + 'px';
+};
+
+/**
+ * Function: scheduleUpdateAspect
+ *
+ * Schedules an asynchronous <updateAspect> using the current <image>.
+ */
+mxImageShape.prototype.scheduleUpdateAspect = function()
+{
+ var img = new Image();
+
+ img.onload = mxUtils.bind(this, function()
+ {
+ mxImageShape.prototype.updateAspect.call(this, img.width, img.height);
+ });
+
+ img.src = this.image;
+};
+
+/**
+ * Function: redraw
+ *
+ * Overrides <mxShape.redraw> to preserve the aspect ratio of images.
+ */
+mxImageShape.prototype.redraw = function()
+{
+ mxShape.prototype.redraw.apply(this, arguments);
+
+ if (this.imageNode != null && this.bounds != null)
+ {
+ // Horizontal and vertical flipping
+ var flipH = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_FLIPH, 0) == 1;
+ var flipV = mxUtils.getValue(this.style, mxConstants.STYLE_IMAGE_FLIPV, 0) == 1;
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ var sx = 1;
+ var sy = 1;
+ var dx = 0;
+ var dy = 0;
+
+ if (flipH)
+ {
+ sx = -1;
+ dx = -this.bounds.width - 2 * this.bounds.x;
+ }
+
+ if (flipV)
+ {
+ sy = -1;
+ dy = -this.bounds.height - 2 * this.bounds.y;
+ }
+
+ // Adds image tansformation to existing transforms
+ var transform = (this.imageNode.getAttribute('transform') || '') +
+ ' scale('+sx+' '+sy+')'+ ' translate('+dx+' '+dy+')';
+ this.imageNode.setAttribute('transform', transform);
+ }
+ else
+ {
+ // Sets default size (no aspect)
+ if (this.imageNode.nodeName != 'DIV')
+ {
+ this.imageNode.style.width = Math.max(0, Math.round(this.bounds.width)) + 'px';
+ this.imageNode.style.height = Math.max(0, Math.round(this.bounds.height)) + 'px';
+ }
+
+ // Preserves image aspect
+ if (this.preserveImageAspect)
+ {
+ this.scheduleUpdateAspect();
+ }
+
+ if (flipH || flipV)
+ {
+ if (mxUtils.isVml(this.imageNode))
+ {
+ if (flipH && flipV)
+ {
+ this.imageNode.style.rotation = '180';
+ }
+ else if (flipH)
+ {
+ this.imageNode.style.flip = 'x';
+ }
+ else
+ {
+ this.imageNode.style.flip = 'y';
+ }
+ }
+ else
+ {
+ var filter = (this.imageNode.nodeName == 'DIV') ? 'progid:DXImageTransform.Microsoft.AlphaImageLoader ' +
+ '(src=\'' + this.image + '\', sizingMethod=\'scale\')' : '';
+
+ if (flipH && flipV)
+ {
+ filter += 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2)';
+ }
+ else if (flipH)
+ {
+ filter += 'progid:DXImageTransform.Microsoft.BasicImage(mirror=1)';
+ }
+ else
+ {
+ filter += 'progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)';
+ }
+
+ if (this.imageNode.style.filter != filter)
+ {
+ this.imageNode.style.filter = filter;
+ }
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Function: configureTransparentBackground
+ *
+ * Workaround for security warning in IE if this is used in the overlay pane
+ * of a diagram.
+ */
+mxImageShape.prototype.configureTransparentBackground = function(node)
+{
+ // do nothing
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxImageShape.prototype.redrawSvg = function()
+{
+ this.updateSvgShape(this.innerNode);
+ this.updateSvgShape(this.imageNode);
+
+ if (this.bg != null)
+ {
+ this.updateSvgShape(this.bg);
+
+ if (this.fill != null)
+ {
+ this.bg.setAttribute('fill', this.fill);
+ }
+ else
+ {
+ this.bg.setAttribute('fill', 'none');
+ }
+
+ if (this.stroke != null)
+ {
+ this.bg.setAttribute('stroke', this.stroke);
+ }
+ else
+ {
+ this.bg.setAttribute('stroke', 'none');
+ }
+
+ this.bg.setAttribute('shape-rendering', 'crispEdges');
+ }
+};
+
+/**
+ * Function: configureSvgShape
+ *
+ * Extends method to set opacity on images.
+ */
+mxImageShape.prototype.configureSvgShape = function(node)
+{
+ mxShape.prototype.configureSvgShape.apply(this, arguments);
+
+ if (this.imageNode != null)
+ {
+ if (this.opacity != null)
+ {
+ this.imageNode.setAttribute('opacity', this.opacity / 100);
+ }
+ else
+ {
+ this.imageNode.removeAttribute('opacity');
+ }
+ }
+};
diff --git a/src/js/shape/mxLabel.js b/src/js/shape/mxLabel.js
new file mode 100644
index 0000000..c31f3bf
--- /dev/null
+++ b/src/js/shape/mxLabel.js
@@ -0,0 +1,427 @@
+/**
+ * $Id: mxLabel.js,v 1.40 2012-05-22 16:10:12 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxLabel
+ *
+ * Extends <mxShape> to implement an image shape with a label.
+ * This shape is registered under <mxConstants.SHAPE_LABEL> in
+ * <mxCellRenderer>.
+ *
+ * Constructor: mxLabel
+ *
+ * Constructs a new label shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxLabel(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxLabel.prototype = new mxShape();
+mxLabel.prototype.constructor = mxLabel;
+
+/**
+ * Variable: vmlNodes
+ *
+ * Adds local references to <mxShape.vmlNodes>.
+ */
+mxLabel.prototype.vmlNodes = mxLabel.prototype.vmlNodes.concat(['label', 'imageNode', 'indicatorImageNode', 'rectNode']);
+
+/**
+ * Variable: imageSize
+ *
+ * Default width and height for the image. Default is
+ * <mxConstants.DEFAULT_IMAGESIZE>.
+ */
+mxLabel.prototype.imageSize = mxConstants.DEFAULT_IMAGESIZE;
+
+/**
+ * Variable: spacing
+ *
+ * Default value for spacing. Default is 2.
+ */
+mxLabel.prototype.spacing = 2;
+
+/**
+ * Variable: indicatorSize
+ *
+ * Default width and height for the indicicator. Default is
+ * 10.
+ */
+mxLabel.prototype.indicatorSize = 10;
+
+/**
+ * Variable: indicatorSpacing
+ *
+ * Default spacing between image and indicator. Default is 2.
+ */
+mxLabel.prototype.indicatorSpacing = 2;
+
+/**
+ * Variable: opaqueVmlImages
+ *
+ * Specifies if all VML images should be rendered without transparency, that
+ * is, if the current opacity should be ignored for images. Default is false.
+ */
+mxLabel.prototype.opaqueVmlImages = false;
+
+/**
+ * Function: init
+ *
+ * Initializes the shape and adds it to the container. This function is
+ * overridden so that the node is already in the DOM when the indicator
+ * is added. This is required to access the ownerSVGelement of the
+ * container in the init function of the indicator.
+ */
+mxLabel.prototype.init = function(container)
+{
+ mxShape.prototype.init.apply(this, arguments);
+
+ // Creates the indicator shape after the node was added to the DOM
+ if (this.indicatorColor != null && this.indicatorShape != null)
+ {
+ this.indicator = new this.indicatorShape();
+ this.indicator.dialect = this.dialect;
+ this.indicator.bounds = this.bounds;
+ this.indicator.fill = this.indicatorColor;
+ this.indicator.stroke = this.indicatorColor;
+ this.indicator.gradient = this.indicatorGradientColor;
+ this.indicator.direction = this.indicatorDirection;
+ this.indicator.init(this.node);
+ this.indicatorShape = null;
+ }
+};
+
+/**
+ * Function: reconfigure
+ *
+ * Reconfigures this shape. This will update the colors of the indicator
+ * and reconfigure it if required.
+ */
+mxLabel.prototype.reconfigure = function()
+{
+ mxShape.prototype.reconfigure.apply(this);
+
+ if (this.indicator != null)
+ {
+ this.indicator.fill = this.indicatorColor;
+ this.indicator.stroke = this.indicatorColor;
+ this.indicator.gradient = this.indicatorGradientColor;
+ this.indicator.direction = this.indicatorDirection;
+ this.indicator.reconfigure();
+ }
+};
+
+/**
+ * Function: createHtml
+ *
+ * Creates and returns the HTML node to represent this shape.
+ */
+mxLabel.prototype.createHtml = function()
+{
+ var name = 'DIV';
+ var node = document.createElement(name);
+ this.configureHtmlShape(node);
+
+ // Adds a small subshape inside this shape
+ if (this.indicatorImage != null)
+ {
+ this.indicatorImageNode = mxUtils.createImage(this.indicatorImage);
+ this.indicatorImageNode.style.position = 'absolute';
+ node.appendChild(this.indicatorImageNode);
+ }
+
+ // Adds an image node to the div
+ if (this.image != null)
+ {
+ this.imageNode = mxUtils.createImage(this.image);
+ this.stroke = null;
+ this.configureHtmlShape(this.imageNode);
+ mxUtils.setOpacity(this.imageNode, '100');
+ node.appendChild(this.imageNode);
+ }
+
+ return node;
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxLabel.prototype.createVml = function()
+{
+ var node = document.createElement('v:group');
+
+ // Background
+ var name = (this.isRounded) ? 'v:roundrect' : 'v:rect';
+ this.rectNode = document.createElement(name);
+ this.configureVmlShape(this.rectNode);
+
+ // Disables the shadow and configures the enclosing group
+ this.isShadow = false;
+ this.configureVmlShape(node);
+ node.coordorigin = '0,0';
+ node.appendChild(this.rectNode);
+
+ // Adds a small subshape inside this shape
+ if (this.indicatorImage != null)
+ {
+ this.indicatorImageNode = this.createVmlImage(this.indicatorImage, (this.opaqueVmlImages) ? null : this.opacity);
+ node.appendChild(this.indicatorImageNode);
+ }
+
+ // Adds an image node to the div
+ if (this.image != null)
+ {
+ this.imageNode = this.createVmlImage(this.image, (this.opaqueVmlImages) ? null : this.opacity);
+ node.appendChild(this.imageNode);
+ }
+
+ // Container for the label on top of everything
+ this.label = document.createElement('v:rect');
+ this.label.style.top = '0px'; // relative
+ this.label.style.left = '0px'; // relative
+ this.label.filled = 'false';
+ this.label.stroked = 'false';
+ node.appendChild(this.label);
+
+ return node;
+};
+
+/**
+ * Function: createVmlImage
+ *
+ * Creates an image node for the given image src and opacity to be used in VML.
+ */
+mxLabel.prototype.createVmlImage = function(src, opacity)
+{
+ var result = null;
+
+ // Workaround for data URIs not supported in VML and for added
+ // border around images if opacity is used (not needed in IE9,
+ // but IMG node is probably better and faster anyway).
+ if (src.substring(0, 5) == 'data:' || opacity != null)
+ {
+ result = document.createElement('img');
+ mxUtils.setOpacity(result, opacity);
+ result.setAttribute('border', '0');
+ result.style.position = 'absolute';
+ result.setAttribute('src', src);
+ }
+ else
+ {
+ result = document.createElement('v:image');
+ result.src = src;
+ }
+
+ return result;
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node to represent this shape.
+ */
+mxLabel.prototype.createSvg = function()
+{
+ var g = this.createSvgGroup('rect');
+
+ // Adds a small subshape to the svg group
+ if (this.indicatorImage != null)
+ {
+ this.indicatorImageNode = document.createElementNS(mxConstants.NS_SVG, 'image');
+ this.indicatorImageNode.setAttributeNS(mxConstants.NS_XLINK, 'href', this.indicatorImage);
+ g.appendChild(this.indicatorImageNode);
+
+ if (this.opacity != null)
+ {
+ this.indicatorImageNode.setAttribute('opacity', this.opacity / 100);
+ }
+ }
+
+ // Adds an image to the svg group
+ if (this.image != null)
+ {
+ this.imageNode = document.createElementNS(mxConstants.NS_SVG, 'image');
+ this.imageNode.setAttributeNS(mxConstants.NS_XLINK, 'href', this.image);
+
+ if (this.opacity != null)
+ {
+ this.imageNode.setAttribute('opacity', this.opacity / 100);
+ }
+
+ // Disables control-click and alt-click in Firefox
+ this.imageNode.setAttribute('style', 'pointer-events:none');
+ this.configureSvgShape(this.imageNode);
+ g.appendChild(this.imageNode);
+ }
+
+ return g;
+};
+
+/**
+ * Function: redraw
+ *
+ * Overrides redraw to define a unified implementation for redrawing
+ * all supported dialects.
+ */
+mxLabel.prototype.redraw = function()
+{
+ this.updateBoundingBox();
+ var isSvg = (this.dialect == mxConstants.DIALECT_SVG);
+ var isVml = mxUtils.isVml(this.node);
+
+ // Updates the bounds of the outermost shape
+ if (isSvg)
+ {
+ this.updateSvgShape(this.innerNode);
+
+ if (this.shadowNode != null)
+ {
+ this.updateSvgShape(this.shadowNode);
+ }
+
+ this.updateSvgGlassPane();
+ }
+ else if (isVml)
+ {
+ this.updateVmlShape(this.node);
+ this.updateVmlShape(this.rectNode);
+ this.label.style.width = this.node.style.width;
+ this.label.style.height = this.node.style.height;
+
+ this.updateVmlGlassPane();
+ }
+ else
+ {
+ this.updateHtmlShape(this.node);
+ }
+
+ // Updates the imagewidth and imageheight
+ var imageWidth = 0;
+ var imageHeight = 0;
+
+ if (this.imageNode != null)
+ {
+ imageWidth = (this.style[mxConstants.STYLE_IMAGE_WIDTH] ||
+ this.imageSize) * this.scale;
+ imageHeight = (this.style[mxConstants.STYLE_IMAGE_HEIGHT] ||
+ this.imageSize) * this.scale;
+ }
+
+ // Updates the subshape size and location
+ var indicatorSpacing = 0;
+ var indicatorWidth = 0;
+ var indicatorHeight = 0;
+
+ if (this.indicator != null || this.indicatorImageNode != null)
+ {
+ indicatorSpacing = (this.style[mxConstants.STYLE_INDICATOR_SPACING] ||
+ this.indicatorSpacing) * this.scale;
+ indicatorWidth = (this.style[mxConstants.STYLE_INDICATOR_WIDTH] ||
+ this.indicatorSize) * this.scale;
+ indicatorHeight = (this.style[mxConstants.STYLE_INDICATOR_HEIGHT] ||
+ this.indicatorSize) * this.scale;
+ }
+
+ var align = this.style[mxConstants.STYLE_IMAGE_ALIGN];
+ var valign = this.style[mxConstants.STYLE_IMAGE_VERTICAL_ALIGN];
+
+ var inset = this.spacing * this.scale + 5;
+ var width = Math.max(imageWidth, indicatorWidth);
+ var height = imageHeight + indicatorSpacing + indicatorHeight;
+
+ var x = (isSvg) ? this.bounds.x : 0;
+
+ if (align == mxConstants.ALIGN_RIGHT)
+ {
+ x += this.bounds.width - width - inset;
+ }
+ else if (align == mxConstants.ALIGN_CENTER)
+ {
+ x += (this.bounds.width - width) / 2;
+ }
+ else // default is left
+ {
+ x += inset;
+ }
+
+ var y = (isSvg) ? this.bounds.y : 0;
+
+ if (valign == mxConstants.ALIGN_BOTTOM)
+ {
+ y += this.bounds.height - height - inset;
+ }
+ else if (valign == mxConstants.ALIGN_TOP)
+ {
+ y += inset;
+ }
+ else // default is middle
+ {
+ y += (this.bounds.height - height) / 2;
+ }
+
+ // Updates the imagenode
+ if (this.imageNode != null)
+ {
+ if (isSvg)
+ {
+ this.imageNode.setAttribute('x', (x + (width - imageWidth) / 2) + 'px');
+ this.imageNode.setAttribute('y', y + 'px');
+ this.imageNode.setAttribute('width', imageWidth + 'px');
+ this.imageNode.setAttribute('height', imageHeight + 'px');
+ }
+ else
+ {
+ this.imageNode.style.left = (x + width - imageWidth) + 'px';
+ this.imageNode.style.top = y + 'px';
+ this.imageNode.style.width = imageWidth + 'px';
+ this.imageNode.style.height = imageHeight + 'px';
+ this.imageNode.stroked = 'false';
+ }
+ }
+
+ // Updates the subshapenode (aka. indicator)
+ if (this.indicator != null)
+ {
+ this.indicator.bounds = new mxRectangle(
+ x + (width - indicatorWidth) / 2,
+ y + imageHeight + indicatorSpacing,
+ indicatorWidth, indicatorHeight);
+ this.indicator.redraw();
+ }
+ else if (this.indicatorImageNode != null)
+ {
+ if (isSvg)
+ {
+ this.indicatorImageNode.setAttribute('x', (x + (width - indicatorWidth) / 2) + 'px');
+ this.indicatorImageNode.setAttribute('y', (y + imageHeight + indicatorSpacing) + 'px');
+ this.indicatorImageNode.setAttribute('width', indicatorWidth + 'px');
+ this.indicatorImageNode.setAttribute('height', indicatorHeight + 'px');
+ }
+ else
+ {
+ this.indicatorImageNode.style.left = (x + (width - indicatorWidth) / 2) + 'px';
+ this.indicatorImageNode.style.top = (y + imageHeight + indicatorSpacing) + 'px';
+ this.indicatorImageNode.style.width = indicatorWidth + 'px';
+ this.indicatorImageNode.style.height = indicatorHeight + 'px';
+ }
+ }
+};
diff --git a/src/js/shape/mxLine.js b/src/js/shape/mxLine.js
new file mode 100644
index 0000000..5ef3eb0
--- /dev/null
+++ b/src/js/shape/mxLine.js
@@ -0,0 +1,217 @@
+/**
+ * $Id: mxLine.js,v 1.36 2012-03-30 04:44:59 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxLine
+ *
+ * Extends <mxShape> to implement a horizontal line shape.
+ * This shape is registered under <mxConstants.SHAPE_LINE> in
+ * <mxCellRenderer>.
+ *
+ * Constructor: mxLine
+ *
+ * Constructs a new line shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * stroke - String that defines the stroke color. Default is 'black'. This is
+ * stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxLine(bounds, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxLine.prototype = new mxShape();
+mxLine.prototype.constructor = mxLine;
+
+/**
+ * Variable: vmlNodes
+ *
+ * Adds local references to <mxShape.vmlNodes>.
+ */
+mxLine.prototype.vmlNodes = mxLine.prototype.vmlNodes.concat(['label', 'innerNode']);
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw in VML in mixed Html mode.
+ */
+mxLine.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw as VML in prefer Html mode.
+ */
+mxLine.prototype.preferModeHtml = false;
+
+/**
+ * Function: clone
+ *
+ * Overrides the clone method to add special fields.
+ */
+mxLine.prototype.clone = function()
+{
+ var clone = new mxLine(this.bounds,
+ this.stroke, this.strokewidth);
+ clone.isDashed = this.isDashed;
+
+ return clone;
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxLine.prototype.createVml = function()
+{
+ var node = document.createElement('v:group');
+ node.style.position = 'absolute';
+
+ // Represents the text label container
+ this.label = document.createElement('v:rect');
+ this.label.style.position = 'absolute';
+ this.label.stroked = 'false';
+ this.label.filled = 'false';
+ node.appendChild(this.label);
+
+ // Represents the straight line shape
+ this.innerNode = document.createElement('v:shape');
+ this.configureVmlShape(this.innerNode);
+ node.appendChild(this.innerNode);
+
+ return node;
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Redraws this VML shape by invoking <updateVmlShape> on this.node.
+ */
+mxLine.prototype.reconfigure = function()
+{
+ if (mxUtils.isVml(this.node))
+ {
+ this.configureVmlShape(this.innerNode);
+ }
+ else
+ {
+ mxShape.prototype.reconfigure.apply(this, arguments);
+ }
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Updates the VML node(s) to reflect the latest bounds and scale.
+ */
+mxLine.prototype.redrawVml = function()
+{
+ this.updateVmlShape(this.node);
+ this.updateVmlShape(this.label);
+
+ this.innerNode.coordsize = this.node.coordsize;
+ this.innerNode.strokeweight = (this.strokewidth * this.scale) + 'px';
+ this.innerNode.style.width = this.node.style.width;
+ this.innerNode.style.height = this.node.style.height;
+
+ var w = this.bounds.width;
+ var h =this.bounds.height;
+
+ if (this.direction == mxConstants.DIRECTION_NORTH ||
+ this.direction == mxConstants.DIRECTION_SOUTH)
+ {
+ this.innerNode.path = 'm ' + Math.round(w / 2) + ' 0' +
+ ' l ' + Math.round(w / 2) + ' ' + Math.round(h) + ' e';
+ }
+ else
+ {
+ this.innerNode.path = 'm 0 ' + Math.round(h / 2) +
+ ' l ' + Math.round(w) + ' ' + Math.round(h / 2) + ' e';
+ }
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxLine.prototype.createSvg = function()
+{
+ var g = this.createSvgGroup('path');
+
+ // Creates an invisible shape around the path for easier
+ // selection with the mouse. Note: Firefox does not ignore
+ // the value of the stroke attribute for pointer-events: stroke.
+ // It does, however, ignore the visibility attribute.
+ this.pipe = this.createSvgPipe();
+ g.appendChild(this.pipe);
+
+ return g;
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxLine.prototype.redrawSvg = function()
+{
+ var strokeWidth = Math.round(Math.max(1, this.strokewidth * this.scale));
+ this.innerNode.setAttribute('stroke-width', strokeWidth);
+
+ if (this.bounds != null)
+ {
+ var x = this.bounds.x;
+ var y = this.bounds.y;
+ var w = this.bounds.width;
+ var h = this.bounds.height;
+
+ var d = null;
+
+ if (this.direction == mxConstants.DIRECTION_NORTH || this.direction == mxConstants.DIRECTION_SOUTH)
+ {
+ d = 'M ' + Math.round(x + w / 2) + ' ' + Math.round(y) + ' L ' + Math.round(x + w / 2) + ' ' + Math.round(y + h);
+ }
+ else
+ {
+ d = 'M ' + Math.round(x) + ' ' + Math.round(y + h / 2) + ' L ' + Math.round(x + w) + ' ' + Math.round(y + h / 2);
+ }
+
+ this.innerNode.setAttribute('d', d);
+ this.pipe.setAttribute('d', d);
+ this.pipe.setAttribute('stroke-width', this.strokewidth + mxShape.prototype.SVG_STROKE_TOLERANCE);
+
+ this.updateSvgTransform(this.innerNode, false);
+ this.updateSvgTransform(this.pipe, false);
+
+ if (this.crisp)
+ {
+ this.innerNode.setAttribute('shape-rendering', 'crispEdges');
+ }
+ else
+ {
+ this.innerNode.removeAttribute('shape-rendering');
+ }
+
+ if (this.isDashed)
+ {
+ var phase = Math.max(1, Math.round(3 * this.scale * this.strokewidth));
+ this.innerNode.setAttribute('stroke-dasharray', phase + ' ' + phase);
+ }
+ }
+};
diff --git a/src/js/shape/mxMarker.js b/src/js/shape/mxMarker.js
new file mode 100644
index 0000000..cfd6f66
--- /dev/null
+++ b/src/js/shape/mxMarker.js
@@ -0,0 +1,267 @@
+/**
+ * $Id: mxMarker.js,v 1.19 2012-03-30 12:51:58 david Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxMarker =
+{
+ /**
+ * Class: mxMarker
+ *
+ * A static class that implements all markers for VML and SVG using a
+ * registry. NOTE: The signatures in this class will change.
+ *
+ * Variable: markers
+ *
+ * Maps from markers names to functions to paint the markers.
+ */
+ markers: [],
+
+ /**
+ * Function: paintMarker
+ *
+ * Paints the given marker.
+ */
+ paintMarker: function(node, type, p0, pe, color, strokewidth, size, scale, x0, y0, source, style)
+ {
+ var marker = mxMarker.markers[type];
+ var result = null;
+
+ if (marker != null)
+ {
+ var isVml = mxUtils.isVml(node);
+
+ // Computes the norm and the inverse norm
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+
+ if (isNaN(dx) || isNaN(dy))
+ {
+ return;
+ }
+
+ var dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
+ var nx = dx * scale / dist;
+ var ny = dy * scale / dist;
+
+ pe = pe.clone();
+
+ if (isVml)
+ {
+ pe.x -= x0;
+ pe.y -= y0;
+ }
+
+ // Handles start-/endFill style
+ var filled = true;
+ var key = (source) ? mxConstants.STYLE_STARTFILL : mxConstants.STYLE_ENDFILL;
+
+ if (style[key] == 0)
+ {
+ filled = false;
+ }
+
+ if (isVml)
+ {
+ // Opacity is updated in reconfigure, use nf in path for no fill
+ node.strokecolor = color;
+
+ if (filled)
+ {
+ node.fillcolor = color;
+ }
+ else
+ {
+ node.filled = 'false';
+ }
+ }
+ else
+ {
+ node.setAttribute('stroke', color);
+
+ var op = (style.opacity != null) ? style.opacity / 100 : 1;
+ node.setAttribute('stroke-opacity', op);
+
+ if (filled)
+ {
+ node.setAttribute('fill', color);
+ node.setAttribute('fill-opacity', op);
+ }
+ else
+ {
+ node.setAttribute('fill', 'none');
+ }
+ }
+
+ result = marker.call(this, node, type, pe, nx, ny, strokewidth, size, scale, isVml);
+ }
+
+ return result;
+ }
+
+};
+
+(function()
+{
+ /**
+ * Drawing of the classic and block arrows.
+ */
+ var tmp = function(node, type, pe, nx, ny, strokewidth, size, scale, isVml)
+ {
+ // The angle of the forward facing arrow sides against the x axis is
+ // 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
+ // only half the strokewidth is processed ).
+ var endOffsetX = nx * strokewidth * 1.118;
+ var endOffsetY = ny * strokewidth * 1.118;
+ pe.x -= endOffsetX;
+ pe.y -= endOffsetY;
+
+ nx = nx * (size + strokewidth);
+ ny = ny * (size + strokewidth);
+
+ if (isVml)
+ {
+ node.path = 'm' + Math.round(pe.x) + ',' + Math.round(pe.y) +
+ ' l' + Math.round(pe.x - nx - ny / 2) + ' ' + Math.round(pe.y - ny + nx / 2) +
+ ((type != mxConstants.ARROW_CLASSIC) ? '' :
+ ' ' + Math.round(pe.x - nx * 3 / 4) + ' ' + Math.round(pe.y - ny * 3 / 4)) +
+ ' ' + Math.round(pe.x + ny / 2 - nx) + ' ' + Math.round(pe.y - ny - nx / 2) +
+ ' x e';
+ node.setAttribute('strokeweight', (strokewidth * scale) + 'px');
+ }
+ else
+ {
+ node.setAttribute('d', 'M ' + pe.x + ' ' + pe.y +
+ ' L ' + (pe.x - nx - ny / 2) + ' ' + (pe.y - ny + nx / 2) +
+ ((type != mxConstants.ARROW_CLASSIC) ? '' :
+ ' L ' + (pe.x - nx * 3 / 4) + ' ' + (pe.y - ny * 3 / 4)) +
+ ' L ' + (pe.x + ny / 2 - nx) + ' ' + (pe.y - ny - nx / 2) +
+ ' z');
+ node.setAttribute('stroke-width', strokewidth * scale);
+ }
+
+ var f = (type != mxConstants.ARROW_CLASSIC) ? 1 : 3 / 4;
+ return new mxPoint(-nx * f - endOffsetX, -ny * f - endOffsetY);
+ };
+
+ mxMarker.markers[mxConstants.ARROW_CLASSIC] = tmp;
+ mxMarker.markers[mxConstants.ARROW_BLOCK] = tmp;
+}());
+
+mxMarker.markers[mxConstants.ARROW_OPEN] = function(node, type, pe, nx, ny, strokewidth, size, scale, isVml)
+{
+ // The angle of the forward facing arrow sides against the x axis is
+ // 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
+ // only half the strokewidth is processed ).
+ var endOffsetX = nx * strokewidth * 1.118;
+ var endOffsetY = ny * strokewidth * 1.118;
+ pe.x -= endOffsetX;
+ pe.y -= endOffsetY;
+
+ nx = nx * (size + strokewidth);
+ ny = ny * (size + strokewidth);
+
+ if (isVml)
+ {
+ node.path = 'm' + Math.round(pe.x - nx - ny / 2) + ' ' + Math.round(pe.y - ny + nx / 2) +
+ ' l' + Math.round(pe.x) + ' ' + Math.round(pe.y) +
+ ' ' + Math.round(pe.x + ny / 2 - nx) + ' ' + Math.round(pe.y - ny - nx / 2) +
+ ' e nf';
+ node.setAttribute('strokeweight', (strokewidth * scale) + 'px');
+ }
+ else
+ {
+ node.setAttribute('d', 'M ' + (pe.x - nx - ny / 2) + ' ' + (pe.y - ny + nx / 2) +
+ ' L ' + (pe.x) + ' ' + (pe.y) +
+ ' L ' + (pe.x + ny / 2 - nx) + ' ' + (pe.y - ny - nx / 2));
+ node.setAttribute('stroke-width', strokewidth * scale);
+ node.setAttribute('fill', 'none');
+ }
+
+ return new mxPoint(-endOffsetX * 2, -endOffsetY * 2);
+};
+
+mxMarker.markers[mxConstants.ARROW_OVAL] = function(node, type, pe, nx, ny, strokewidth, size, scale, isVml)
+{
+ nx *= size;
+ ny *= size;
+
+ nx *= 0.5 + strokewidth / 2;
+ ny *= 0.5 + strokewidth / 2;
+
+ var absSize = size * scale;
+ var radius = absSize / 2;
+
+ if (isVml)
+ {
+ node.path = 'm' + Math.round(pe.x + radius) + ' ' + Math.round(pe.y) +
+ ' at ' + Math.round(pe.x - radius) + ' ' + Math.round(pe.y - radius) +
+ ' ' + Math.round(pe.x + radius) + ' ' + Math.round(pe.y + radius) +
+ ' ' + Math.round(pe.x + radius) + ' ' + Math.round(pe.y) +
+ ' ' + Math.round(pe.x + radius) + ' ' + Math.round(pe.y) +
+ ' x e';
+
+ node.setAttribute('strokeweight', (strokewidth * scale) + 'px');
+ }
+ else
+ {
+ node.setAttribute('d', 'M ' + (pe.x - radius) + ' ' + (pe.y) +
+ ' a ' + (radius) + ' ' + (radius) +
+ ' 0 1,1 ' + (absSize) + ' 0' +
+ ' a ' + (radius) + ' ' + (radius) +
+ ' 0 1,1 ' + (-absSize) + ' 0 z');
+ node.setAttribute('stroke-width', strokewidth * scale);
+ }
+
+ return new mxPoint(-nx / (2 + strokewidth), -ny / (2 + strokewidth));
+};
+
+(function()
+ {
+ /**
+ * Drawing of the diamond and thin diamond markers
+ */
+ var tmp_diamond = function(node, type, pe, nx, ny, strokewidth, size, scale, isVml)
+ {
+ // The angle of the forward facing arrow sides against the x axis is
+ // 45 degrees, 1/sin(45) = 1.4142 / 2 = 0.7071 ( / 2 allows for
+ // only half the strokewidth is processed ). Or 0.9862 for thin diamond.
+ // Note these values and the tk variable below are dependent, update
+ // both together (saves trig hard coding it).
+ var swFactor = (type == mxConstants.ARROW_DIAMOND) ? 0.7071 : 0.9862;
+ var endOffsetX = nx * strokewidth * swFactor;
+ var endOffsetY = ny * strokewidth * swFactor;
+
+ nx = nx * (size + strokewidth);
+ ny = ny * (size + strokewidth);
+
+ pe.x -= endOffsetX + nx / 2;
+ pe.y -= endOffsetY + ny / 2;
+
+ // thickness factor for diamond
+ var tk = ((type == mxConstants.ARROW_DIAMOND) ? 2 : 3.4);
+
+ if (isVml)
+ {
+ node.path = 'm' + Math.round(pe.x + nx / 2) + ' ' + Math.round(pe.y + ny / 2) +
+ ' l' + Math.round(pe.x - ny / tk) + ' ' + Math.round(pe.y + nx / tk) +
+ ' ' + Math.round(pe.x - nx / 2) + ' ' + Math.round(pe.y - ny / 2) +
+ ' ' + Math.round(pe.x + ny / tk) + ' ' + Math.round(pe.y - nx / tk) +
+ ' x e';
+ node.setAttribute('strokeweight', (strokewidth * scale) + 'px');
+ }
+ else
+ {
+ node.setAttribute('d', 'M ' + (pe.x + nx / 2) + ' ' + (pe.y + ny / 2) +
+ ' L ' + (pe.x - ny / tk) + ' ' + (pe.y + nx / tk) +
+ ' L ' + (pe.x - nx / 2) + ' ' + (pe.y - ny / 2) +
+ ' L ' + (pe.x + ny / tk) + ' ' + (pe.y - nx / tk) +
+ ' z');
+ node.setAttribute('stroke-width', strokewidth * scale);
+ }
+
+ return new mxPoint(-endOffsetX - nx, -endOffsetY - ny);
+ };
+
+ mxMarker.markers[mxConstants.ARROW_DIAMOND] = tmp_diamond;
+ mxMarker.markers[mxConstants.ARROW_DIAMOND_THIN] = tmp_diamond;
+ }());
diff --git a/src/js/shape/mxPolyline.js b/src/js/shape/mxPolyline.js
new file mode 100644
index 0000000..2d64323
--- /dev/null
+++ b/src/js/shape/mxPolyline.js
@@ -0,0 +1,146 @@
+/**
+ * $Id: mxPolyline.js,v 1.31 2012-05-24 12:00:45 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxPolyline
+ *
+ * Extends <mxShape> to implement a polyline (a line with multiple points).
+ * This shape is registered under <mxConstants.SHAPE_POLYLINE> in
+ * <mxCellRenderer>.
+ *
+ * Constructor: mxPolyline
+ *
+ * Constructs a new polyline shape.
+ *
+ * Parameters:
+ *
+ * points - Array of <mxPoints> that define the points. This is stored in
+ * <mxShape.points>.
+ * stroke - String that defines the stroke color. Default is 'black'. This is
+ * stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxPolyline(points, stroke, strokewidth)
+{
+ this.points = points;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxPolyline.prototype = new mxShape();
+mxPolyline.prototype.constructor = mxPolyline;
+
+/**
+ * Variable: addPipe
+ *
+ * Specifies if a SVG path should be created around any path to increase the
+ * tolerance for mouse events. Default is false since this shape is filled.
+ */
+mxPolyline.prototype.addPipe = true;
+
+/**
+ * Function: create
+ *
+ * Override to create HTML regardless of gradient and
+ * rounded property.
+ */
+mxPolyline.prototype.create = function()
+{
+ var node = null;
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ node = this.createSvg();
+ }
+ else if (this.dialect == mxConstants.DIALECT_STRICTHTML ||
+ (this.dialect == mxConstants.DIALECT_PREFERHTML &&
+ this.points != null && this.points.length > 0))
+ {
+ node = document.createElement('DIV');
+ this.configureHtmlShape(node);
+ node.style.borderStyle = '';
+ node.style.background = '';
+ }
+ else
+ {
+ node = document.createElement('v:shape');
+ this.configureVmlShape(node);
+ var strokeNode = document.createElement('v:stroke');
+
+ if (this.opacity != null)
+ {
+ strokeNode.opacity = this.opacity + '%';
+ }
+
+ node.appendChild(strokeNode);
+ }
+
+ return node;
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Overrides the method to update the bounds if they have not been
+ * assigned.
+ */
+mxPolyline.prototype.redrawVml = function()
+{
+ // Updates the bounds based on the points
+ if (this.points != null && this.points.length > 0 && this.points[0] != null)
+ {
+ this.bounds = new mxRectangle(this.points[0].x,this.points[0].y, 0, 0);
+
+ for (var i = 1; i < this.points.length; i++)
+ {
+ this.bounds.add(new mxRectangle(this.points[i].x,this.points[i].y, 0, 0));
+ }
+ }
+
+ mxShape.prototype.redrawVml.apply(this, arguments);
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxPolyline.prototype.createSvg = function()
+{
+ var g = this.createSvgGroup('path');
+
+ // Creates an invisible shape around the path for easier
+ // selection with the mouse. Note: Firefox does not ignore
+ // the value of the stroke attribute for pointer-events: stroke,
+ // it does, however, ignore the visibility attribute.
+ if (this.addPipe)
+ {
+ this.pipe = this.createSvgPipe();
+ g.appendChild(this.pipe);
+ }
+
+ return g;
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxPolyline.prototype.redrawSvg = function()
+{
+ this.updateSvgShape(this.innerNode);
+ var d = this.innerNode.getAttribute('d');
+
+ if (d != null && this.pipe != null)
+ {
+ this.pipe.setAttribute('d', d);
+ var strokeWidth = Math.round(Math.max(1, this.strokewidth * this.scale));
+ this.pipe.setAttribute('stroke-width', strokeWidth + mxShape.prototype.SVG_STROKE_TOLERANCE);
+ }
+};
diff --git a/src/js/shape/mxRectangleShape.js b/src/js/shape/mxRectangleShape.js
new file mode 100644
index 0000000..26688c3
--- /dev/null
+++ b/src/js/shape/mxRectangleShape.js
@@ -0,0 +1,61 @@
+/**
+ * $Id: mxRectangleShape.js,v 1.17 2012-09-26 07:51:29 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxRectangleShape
+ *
+ * Extends <mxShape> to implement a rectangle shape.
+ * This shape is registered under <mxConstants.SHAPE_RECTANGLE>
+ * in <mxCellRenderer>.
+ *
+ * Constructor: mxRectangleShape
+ *
+ * Constructs a new rectangle shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxRectangleShape(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxRectangleShape.prototype = new mxShape();
+mxRectangleShape.prototype.constructor = mxRectangleShape;
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxRectangleShape.prototype.createVml = function()
+{
+ var name = (this.isRounded) ? 'v:roundrect' : 'v:rect';
+ var node = document.createElement(name);
+ this.configureVmlShape(node);
+
+ return node;
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node to represent this shape.
+ */
+mxRectangleShape.prototype.createSvg = function()
+{
+ return this.createSvgGroup('rect');
+};
diff --git a/src/js/shape/mxRhombus.js b/src/js/shape/mxRhombus.js
new file mode 100644
index 0000000..37e35ec
--- /dev/null
+++ b/src/js/shape/mxRhombus.js
@@ -0,0 +1,172 @@
+/**
+ * $Id: mxRhombus.js,v 1.25 2012-04-04 07:34:50 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxRhombus
+ *
+ * Extends <mxShape> to implement a rhombus (aka diamond) shape.
+ * This shape is registered under <mxConstants.SHAPE_RHOMBUS>
+ * in <mxCellRenderer>.
+ *
+ * Constructor: mxRhombus
+ *
+ * Constructs a new rhombus shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxRhombus(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxRhombus.prototype = new mxShape();
+mxRhombus.prototype.constructor = mxRhombus;
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw in VML in mixed Html mode.
+ */
+mxRhombus.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw as VML in prefer Html mode.
+ */
+mxRhombus.prototype.preferModeHtml = false;
+
+/**
+ * Function: createHtml
+ *
+ * Creates and returns the HTML node to represent this shape.
+ */
+mxRhombus.prototype.createHtml = function()
+{
+ var node = document.createElement('DIV');
+ this.configureHtmlShape(node);
+
+ return node;
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node(s) to represent this shape.
+ */
+mxRhombus.prototype.createVml = function()
+{
+ var node = document.createElement('v:shape');
+ this.configureVmlShape(node);
+
+ return node;
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxRhombus.prototype.createSvg = function()
+{
+ return this.createSvgGroup('path');
+};
+
+// TODO: When used as an indicator, this.node.points is null
+// so we use a path object for building general diamonds.
+//mxRhombus.prototype.redraw = function() {
+// this.node.setAttribute('strokeweight', (this.strokewidth * this.scale) + 'px');
+// var x = this.bounds.x;
+// var y = this.bounds.y;
+// var w = this.bounds.width;
+// var h = this.bounds.height;
+// this.node.points.value = (x+w/2)+','+y+' '+(x+w)+','+(y+h/2)+
+// ' '+(x+w/2)+','+(y+h)+' '+x+','+(y+h/2)+' '+
+// (x+w/2)+','+y;
+//}
+
+/**
+ * Function: redrawVml
+ *
+ * Updates the VML node(s) to reflect the latest bounds and scale.
+ */
+mxRhombus.prototype.redrawVml = function()
+{
+ this.updateVmlShape(this.node);
+ var x = 0;
+ var y = 0;
+ var w = Math.round(this.bounds.width);
+ var h = Math.round(this.bounds.height);
+
+ this.node.path = 'm ' + Math.round(x + w / 2) + ' ' + y +
+ ' l ' + (x + w) + ' ' + Math.round(y + h / 2) +
+ ' l ' + Math.round(x + w / 2) + ' ' + (y + h) +
+ ' l ' + x + ' ' + Math.round(y + h / 2) + ' x e';
+};
+
+/**
+ * Function: redrawHtml
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxRhombus.prototype.redrawHtml = function()
+{
+ this.updateHtmlShape(this.node);
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxRhombus.prototype.redrawSvg = function()
+{
+ this.updateSvgNode(this.innerNode);
+
+ if (this.shadowNode != null)
+ {
+ this.updateSvgNode(this.shadowNode);
+ }
+};
+
+/**
+ * Function: createSvgSpan
+ *
+ * Updates the path for the given SVG node.
+ */
+mxRhombus.prototype.updateSvgNode = function(node)
+{
+ var strokeWidth = Math.round(Math.max(1, this.strokewidth * this.scale));
+ node.setAttribute('stroke-width', strokeWidth);
+ var x = this.bounds.x;
+ var y = this.bounds.y;
+ var w = this.bounds.width;
+ var h = this.bounds.height;
+ var d = 'M ' + Math.round(x + w / 2) + ' ' + Math.round(y) + ' L ' + Math.round(x + w) + ' ' + Math.round(y + h / 2) +
+ ' L ' + Math.round(x + w / 2) + ' ' + Math.round(y + h) + ' L ' + Math.round(x) + ' ' + Math.round(y + h / 2) +
+ ' Z ';
+ node.setAttribute('d', d);
+ this.updateSvgTransform(node, node == this.shadowNode);
+
+ if (this.isDashed)
+ {
+ var phase = Math.max(1, Math.round(3 * this.scale * this.strokewidth));
+ node.setAttribute('stroke-dasharray', phase + ' ' + phase);
+ }
+};
diff --git a/src/js/shape/mxShape.js b/src/js/shape/mxShape.js
new file mode 100644
index 0000000..44ba3e7
--- /dev/null
+++ b/src/js/shape/mxShape.js
@@ -0,0 +1,2045 @@
+/**
+ * $Id: mxShape.js,v 1.173 2012-07-31 11:46:53 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxShape
+ *
+ * Base class for all shapes. A shape in mxGraph is a
+ * separate implementation for SVG, VML and HTML. Which
+ * implementation to use is controlled by the <dialect>
+ * property which is assigned from within the <mxCellRenderer>
+ * when the shape is created. The dialect must be assigned
+ * for a shape, and it does normally depend on the browser and
+ * the confiuration of the graph (see <mxGraph> rendering hint).
+ *
+ * For each supported shape in SVG and VML, a corresponding
+ * shape exists in mxGraph, namely for text, image, rectangle,
+ * rhombus, ellipse and polyline. The other shapes are a
+ * combination of these shapes (eg. label and swimlane)
+ * or they consist of one or more (filled) path objects
+ * (eg. actor and cylinder). The HTML implementation is
+ * optional but may be required for a HTML-only view of
+ * the graph.
+ *
+ * Custom Shapes:
+ *
+ * To extend from this class, the basic code looks as follows.
+ * In the special case where the custom shape consists only of
+ * one filled region or one filled region and an additional stroke
+ * the <mxActor> and <mxCylinder> should be subclassed,
+ * respectively. These implement <redrawPath> in order to create
+ * the path expression for VML and SVG via a unified API (see
+ * <mxPath>). <mxCylinder.redrawPath> has an additional boolean
+ * argument to draw the foreground and background separately.
+ *
+ * (code)
+ * function CustomShape() { }
+ *
+ * CustomShape.prototype = new mxShape();
+ * CustomShape.prototype.constructor = CustomShape;
+ * (end)
+ *
+ * To register a custom shape in an existing graph instance,
+ * one must register the shape under a new name in the graph's
+ * cell renderer as follows:
+ *
+ * (code)
+ * graph.cellRenderer.registerShape('customShape', CustomShape);
+ * (end)
+ *
+ * The second argument is the name of the constructor.
+ *
+ * In order to use the shape you can refer to the given name above
+ * in a stylesheet. For example, to change the shape for the default
+ * vertex style, the following code is used:
+ *
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_SHAPE] = 'customShape';
+ * (end)
+ *
+ * Constructor: mxShape
+ *
+ * Constructs a new shape.
+ */
+function mxShape() { };
+
+/**
+ * Variable: SVG_STROKE_TOLERANCE
+ *
+ * Event-tolerance for SVG strokes (in px). Default is 8.
+ */
+mxShape.prototype.SVG_STROKE_TOLERANCE = 8;
+
+/**
+ * Variable: scale
+ *
+ * Holds the scale in which the shape is being painted.
+ */
+mxShape.prototype.scale = 1;
+
+/**
+ * Variable: dialect
+ *
+ * Holds the dialect in which the shape is to be painted.
+ * This can be one of the DIALECT constants in <mxConstants>.
+ */
+mxShape.prototype.dialect = null;
+
+/**
+ * Variable: crisp
+ *
+ * Special attribute for SVG rendering to set the shape-rendering attribute to
+ * crispEdges in the output. This is ignored in IE. Default is false. To
+ * disable antialias in IE, the explorer.css file can be changed as follows:
+ *
+ * [code]
+ * v\:* {
+ * behavior: url(#default#VML);
+ * antialias: false;
+ * }
+ * [/code]
+ */
+mxShape.prototype.crisp = false;
+
+/**
+ * Variable: roundedCrispSvg
+ *
+ * Specifies if crisp rendering should be enabled for rounded shapes.
+ * Default is true.
+ */
+mxShape.prototype.roundedCrispSvg = true;
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Specifies if <createHtml> should be used in mixed Html mode.
+ * Default is true.
+ */
+mxShape.prototype.mixedModeHtml = true;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Specifies if <createHtml> should be used in prefer Html mode.
+ * Default is true.
+ */
+mxShape.prototype.preferModeHtml = true;
+
+/**
+ * Variable: bounds
+ *
+ * Holds the <mxRectangle> that specifies the bounds of this shape.
+ */
+mxShape.prototype.bounds = null;
+
+/**
+ * Variable: points
+ *
+ * Holds the array of <mxPoints> that specify the points of this shape.
+ */
+mxShape.prototype.points = null;
+
+/**
+ * Variable: node
+ *
+ * Holds the outermost DOM node that represents this shape.
+ */
+mxShape.prototype.node = null;
+
+/**
+ * Variable: label
+ *
+ * Reference to the DOM node that should contain the label. This is null
+ * if the label should be placed inside <node> or <innerNode>.
+ */
+mxShape.prototype.label = null;
+
+/**
+ * Variable: innerNode
+ *
+ * Holds the DOM node that graphically represents this shape. This may be
+ * null if the outermost DOM <node> represents this shape.
+ */
+mxShape.prototype.innerNode = null;
+
+/**
+ * Variable: style
+ *
+ * Holds the style of the cell state that corresponds to this shape. This may
+ * be null if the shape is used directly, without a cell state.
+ */
+mxShape.prototype.style = null;
+
+/**
+ * Variable: startOffset
+ *
+ * Specifies the offset in pixels from the first point in <points> and
+ * the actual start of the shape.
+ */
+mxShape.prototype.startOffset = null;
+
+/**
+ * Variable: endOffset
+ *
+ * Specifies the offset in pixels from the last point in <points> and
+ * the actual start of the shape.
+ */
+mxShape.prototype.endOffset = null;
+
+/**
+ * Variable: boundingBox
+ *
+ * Contains the bounding box of the shape, that is, the smallest rectangle
+ * that includes all pixels of the shape.
+ */
+mxShape.prototype.boundingBox = null;
+
+/**
+ * Variable: vmlNodes
+ *
+ * Array if VML node names to fix in IE8 standards mode.
+ */
+mxShape.prototype.vmlNodes = ['node', 'strokeNode', 'fillNode', 'shadowNode'];
+
+/**
+ * Variable: vmlScale
+ *
+ * Internal scaling for VML using coordsize for better precision.
+ */
+mxShape.prototype.vmlScale = 1;
+
+/**
+ * Variable: strokewidth
+ *
+ * Holds the current strokewidth. Default is 1.
+ */
+mxShape.prototype.strokewidth = 1;
+
+/**
+ * Function: setCursor
+ *
+ * Sets the cursor on the given shape.
+ *
+ * Parameters:
+ *
+ * cursor - The cursor to be used.
+ */
+mxShape.prototype.setCursor = function(cursor)
+{
+ if (cursor == null)
+ {
+ cursor = '';
+ }
+
+ this.cursor = cursor;
+
+ if (this.innerNode != null)
+ {
+ this.innerNode.style.cursor = cursor;
+ }
+
+ if (this.node != null)
+ {
+ this.node.style.cursor = cursor;
+ }
+
+ if (this.pipe != null)
+ {
+ this.pipe.style.cursor = cursor;
+ }
+};
+
+/**
+ * Function: getCursor
+ *
+ * Returns the current cursor.
+ */
+mxShape.prototype.getCursor = function()
+{
+ return this.cursor;
+};
+
+/**
+ * Function: init
+ *
+ * Initializes the shape by creaing the DOM node using <create>
+ * and adding it into the given container.
+ *
+ * Parameters:
+ *
+ * container - DOM node that will contain the shape.
+ */
+mxShape.prototype.init = function(container)
+{
+ if (this.node == null)
+ {
+ this.node = this.create(container);
+
+ if (container != null)
+ {
+ container.appendChild(this.node);
+
+ // Workaround for broken VML in IE8 standards mode. This gives an ID to
+ // each element that is referenced from this instance. After adding the
+ // DOM to the document, the outerHTML is overwritten to fix the VML
+ // rendering and the references are restored.
+ if (document.documentMode == 8 && mxUtils.isVml(this.node))
+ {
+ this.reparseVml();
+ }
+ }
+ }
+
+ // Gradients are inserted late when the owner SVG element is known
+ if (this.insertGradientNode != null)
+ {
+ this.insertGradient(this.insertGradientNode);
+ this.insertGradientNode = null;
+ }
+};
+
+/**
+ * Function: reparseVml
+ *
+ * Forces a parsing of the outerHTML of this node and restores all references specified in <vmlNodes>.
+ * This is a workaround for the VML rendering bug in IE8 standards mode.
+ */
+mxShape.prototype.reparseVml = function()
+{
+ // Assigns temporary IDs to VML nodes so that references can be restored when
+ // inserted into the DOM as a string
+ for (var i = 0; i < this.vmlNodes.length; i++)
+ {
+ if (this[this.vmlNodes[i]] != null)
+ {
+ this[this.vmlNodes[i]].setAttribute('id', 'mxTemporaryReference-' + this.vmlNodes[i]);
+ }
+ }
+
+ this.node.outerHTML = this.node.outerHTML;
+
+ // Restores references to the actual DOM nodes
+ for (var i = 0; i < this.vmlNodes.length; i++)
+ {
+ if (this[this.vmlNodes[i]] != null)
+ {
+ this[this.vmlNodes[i]] = this.node.ownerDocument.getElementById('mxTemporaryReference-' + this.vmlNodes[i]);
+ this[this.vmlNodes[i]].removeAttribute('id');
+ }
+ }
+};
+
+/**
+ * Function: insertGradient
+ *
+ * Inserts the given gradient node.
+ */
+mxShape.prototype.insertGradient = function(node)
+{
+ // Gradients are inserted late when the owner SVG element is known
+ if (node != null)
+ {
+ // Checks if the given gradient already exists inside the SVG element
+ // that also contains the node that represents this shape. If the gradient
+ // with the same ID exists in another SVG element, then this will add
+ // a copy of the gradient with a different ID to the SVG element and update
+ // the reference accordingly. This is required in Firefox because if the
+ // referenced fill element is removed from the DOM the shape appears black.
+ var count = 0;
+ var id = node.getAttribute('id');
+ var gradient = document.getElementById(id);
+
+ while (gradient != null && gradient.ownerSVGElement != this.node.ownerSVGElement)
+ {
+ count++;
+ id = node.getAttribute('id') + '-' + count;
+ gradient = document.getElementById(id);
+ }
+
+ // According to specification, gradients should be put in a defs
+ // section in the first child of the owner SVG element. However,
+ // it turns out that gradients only work when added as follows.
+ if (gradient == null)
+ {
+ node.setAttribute('id', id);
+ this.node.ownerSVGElement.appendChild(node);
+ gradient = node;
+ }
+
+ if (gradient != null)
+ {
+ var ref = 'url(#' + id + ')';
+ var tmp = (this.innerNode != null) ? this.innerNode : this.node;
+
+ if (tmp != null && tmp.getAttribute('fill') != ref)
+ {
+ tmp.setAttribute('fill', ref);
+ }
+ }
+ }
+};
+
+/**
+ * Function: isMixedModeHtml
+ *
+ * Used to determine if a shape can be rendered using <createHtml> in mixed
+ * mode Html without compromising the display accuracy. The default
+ * implementation will check if the shape is not rounded or rotated and has
+ * no gradient, and will use a DIV if that is the case. It will also check
+ * if <mxShape.mixedModeHtml> is true, which is the default settings.
+ * Subclassers can either override <mixedModeHtml> or this function if the
+ * result depends on dynamic values. The graph's dialect is available via
+ * <dialect>.
+ */
+mxShape.prototype.isMixedModeHtml = function()
+{
+ return this.mixedModeHtml && !this.isRounded && !this.isShadow && this.gradient == null &&
+ mxUtils.getValue(this.style, mxConstants.STYLE_GLASS, 0) == 0 &&
+ mxUtils.getValue(this.style, mxConstants.STYLE_ROTATION, 0) == 0;
+};
+
+/**
+ * Function: create
+ *
+ * Creates and returns the DOM node(s) for the shape in
+ * the given container. This implementation invokes
+ * <createSvg>, <createHtml> or <createVml> depending
+ * on the <dialect> and style settings.
+ *
+ * Parameters:
+ *
+ * container - DOM node that will contain the shape.
+ */
+mxShape.prototype.create = function(container)
+{
+ var node = null;
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ node = this.createSvg();
+ }
+ else if (this.dialect == mxConstants.DIALECT_STRICTHTML ||
+ (this.preferModeHtml && this.dialect == mxConstants.DIALECT_PREFERHTML) ||
+ (this.isMixedModeHtml() && this.dialect == mxConstants.DIALECT_MIXEDHTML))
+ {
+ node = this.createHtml();
+ }
+ else
+ {
+ node = this.createVml();
+ }
+
+ return node;
+};
+
+/**
+ * Function: createHtml
+ *
+ * Creates and returns the HTML DOM node(s) to represent
+ * this shape. This implementation falls back to <createVml>
+ * so that the HTML creation is optional.
+ */
+mxShape.prototype.createHtml = function()
+{
+ var node = document.createElement('DIV');
+ this.configureHtmlShape(node);
+
+ return node;
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the shape by removing it from the DOM and releasing the DOM
+ * node associated with the shape using <mxEvent.release>.
+ */
+mxShape.prototype.destroy = function()
+{
+ if (this.node != null)
+ {
+ mxEvent.release(this.node);
+
+ if (this.node.parentNode != null)
+ {
+ this.node.parentNode.removeChild(this.node);
+ }
+
+ if (this.node.glassOverlay)
+ {
+ this.node.glassOverlay.parentNode.removeChild(this.node.glassOverlay);
+ this.node.glassOverlay = null;
+ }
+
+ this.node = null;
+ }
+};
+
+/**
+ * Function: apply
+ *
+ * Applies the style of the given <mxCellState> to the shape. This
+ * implementation assigns the following styles to local fields:
+ *
+ * - <mxConstants.STYLE_FILLCOLOR> => fill
+ * - <mxConstants.STYLE_GRADIENTCOLOR> => gradient
+ * - <mxConstants.STYLE_GRADIENT_DIRECTION> => gradientDirection
+ * - <mxConstants.STYLE_OPACITY> => opacity
+ * - <mxConstants.STYLE_STROKECOLOR> => stroke
+ * - <mxConstants.STYLE_STROKEWIDTH> => strokewidth
+ * - <mxConstants.STYLE_SHADOW> => isShadow
+ * - <mxConstants.STYLE_DASHED> => isDashed
+ * - <mxConstants.STYLE_SPACING> => spacing
+ * - <mxConstants.STYLE_STARTSIZE> => startSize
+ * - <mxConstants.STYLE_ENDSIZE> => endSize
+ * - <mxConstants.STYLE_ROUNDED> => isRounded
+ * - <mxConstants.STYLE_STARTARROW> => startArrow
+ * - <mxConstants.STYLE_ENDARROW> => endArrow
+ * - <mxConstants.STYLE_ROTATION> => rotation
+ * - <mxConstants.STYLE_DIRECTION> => direction
+ *
+ * This keeps a reference to the <style>. If you need to keep a reference to
+ * the cell, you can override this method and store a local reference to
+ * state.cell or the <mxCellState> itself.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the corresponding cell.
+ */
+mxShape.prototype.apply = function(state)
+{
+ var style = state.style;
+ this.style = style;
+
+ if (style != null)
+ {
+ this.fill = mxUtils.getValue(style, mxConstants.STYLE_FILLCOLOR, this.fill);
+ this.gradient = mxUtils.getValue(style, mxConstants.STYLE_GRADIENTCOLOR, this.gradient);
+ this.gradientDirection = mxUtils.getValue(style, mxConstants.STYLE_GRADIENT_DIRECTION, this.gradientDirection);
+ this.opacity = mxUtils.getValue(style, mxConstants.STYLE_OPACITY, this.opacity);
+ this.stroke = mxUtils.getValue(style, mxConstants.STYLE_STROKECOLOR, this.stroke);
+ this.strokewidth = mxUtils.getNumber(style, mxConstants.STYLE_STROKEWIDTH, this.strokewidth);
+ this.isShadow = mxUtils.getValue(style, mxConstants.STYLE_SHADOW, this.isShadow);
+ this.isDashed = mxUtils.getValue(style, mxConstants.STYLE_DASHED, this.isDashed);
+ this.spacing = mxUtils.getValue(style, mxConstants.STYLE_SPACING, this.spacing);
+ this.startSize = mxUtils.getNumber(style, mxConstants.STYLE_STARTSIZE, this.startSize);
+ this.endSize = mxUtils.getNumber(style, mxConstants.STYLE_ENDSIZE, this.endSize);
+ this.isRounded = mxUtils.getValue(style, mxConstants.STYLE_ROUNDED, this.isRounded);
+ this.startArrow = mxUtils.getValue(style, mxConstants.STYLE_STARTARROW, this.startArrow);
+ this.endArrow = mxUtils.getValue(style, mxConstants.STYLE_ENDARROW, this.endArrow);
+ this.rotation = mxUtils.getValue(style, mxConstants.STYLE_ROTATION, this.rotation);
+ this.direction = mxUtils.getValue(style, mxConstants.STYLE_DIRECTION, this.direction);
+
+ if (this.fill == 'none')
+ {
+ this.fill = null;
+ }
+
+ if (this.gradient == 'none')
+ {
+ this.gradient = null;
+ }
+
+ if (this.stroke == 'none')
+ {
+ this.stroke = null;
+ }
+ }
+};
+
+/**
+ * Function: createSvgGroup
+ *
+ * Creates a SVG group element and adds the given shape as a child of the
+ * element. The child is stored in <innerNode> for later access.
+ */
+mxShape.prototype.createSvgGroup = function(shape)
+{
+ var g = document.createElementNS(mxConstants.NS_SVG, 'g');
+
+ // Creates the shape inside an svg group
+ this.innerNode = document.createElementNS(mxConstants.NS_SVG, shape);
+ this.configureSvgShape(this.innerNode);
+
+ // Avoids anti-aliasing for non-rounded rectangles with a
+ // strokewidth of 1 or more pixels
+ if (shape == 'rect' && this.strokewidth * this.scale >= 1 && !this.isRounded)
+ {
+ this.innerNode.setAttribute('shape-rendering', 'optimizeSpeed');
+ }
+
+ // Creates the shadow
+ this.shadowNode = this.createSvgShadow(this.innerNode);
+
+ if (this.shadowNode != null)
+ {
+ g.appendChild(this.shadowNode);
+ }
+
+ // Appends the main shape after the shadow
+ g.appendChild(this.innerNode);
+
+ return g;
+};
+
+/**
+ * Function: createSvgShadow
+ *
+ * Creates a clone of the given node and configures the node's color
+ * to use <mxConstants.SHADOWCOLOR>.
+ */
+mxShape.prototype.createSvgShadow = function(node)
+{
+ if (this.isShadow)
+ {
+ var shadow = node.cloneNode(true);
+ shadow.setAttribute('opacity', mxConstants.SHADOW_OPACITY);
+
+ if (this.fill != null && this.fill != mxConstants.NONE)
+ {
+ shadow.setAttribute('fill', mxConstants.SHADOWCOLOR);
+ }
+
+ if (this.stroke != null && this.stroke != mxConstants.NONE)
+ {
+ shadow.setAttribute('stroke', mxConstants.SHADOWCOLOR);
+ }
+
+ return shadow;
+ }
+
+ return null;
+};
+
+/**
+ * Function: configureHtmlShape
+ *
+ * Configures the specified HTML node by applying the current color,
+ * bounds, shadow, opacity etc.
+ */
+mxShape.prototype.configureHtmlShape = function(node)
+{
+ if (mxUtils.isVml(node))
+ {
+ this.configureVmlShape(node);
+ }
+ else
+ {
+ node.style.position = 'absolute';
+ node.style.overflow = 'hidden';
+ var color = this.stroke;
+
+ if (color != null && color != mxConstants.NONE)
+ {
+ node.style.borderColor = color;
+
+ if (this.isDashed)
+ {
+ node.style.borderStyle = 'dashed';
+ }
+ else if (this.strokewidth > 0)
+ {
+ node.style.borderStyle = 'solid';
+ }
+
+ node.style.borderWidth = Math.ceil(this.strokewidth * this.scale) + 'px';
+ }
+ else
+ {
+ node.style.borderWidth = '0px';
+ }
+
+ color = this.fill;
+ node.style.background = '';
+
+ if (color != null && color != mxConstants.NONE)
+ {
+ node.style.backgroundColor = color;
+ }
+ else if (this.points == null)
+ {
+ this.configureTransparentBackground(node);
+ }
+
+ if (this.opacity != null)
+ {
+ mxUtils.setOpacity(node, this.opacity);
+ }
+ }
+};
+
+/**
+ * Function: updateVmlFill
+ *
+ * Updates the given VML fill node.
+ */
+mxShape.prototype.updateVmlFill = function(node, c1, c2, dir, alpha)
+{
+ node.color = c1;
+
+ if (alpha != null && alpha != 100)
+ {
+ node.opacity = alpha + '%';
+
+ if (c2 != null)
+ {
+ // LATER: Set namespaced attribute without using setAttribute
+ // which is required for updating the value in IE8 standards.
+ node.setAttribute('o:opacity2', alpha + '%');
+ }
+ }
+
+ if (c2 != null)
+ {
+ node.type = 'gradient';
+ node.color2 = c2;
+ var angle = '180';
+
+ if (this.gradientDirection == mxConstants.DIRECTION_EAST)
+ {
+ angle = '270';
+ }
+ else if (this.gradientDirection == mxConstants.DIRECTION_WEST)
+ {
+ angle = '90';
+ }
+ else if (this.gradientDirection == mxConstants.DIRECTION_NORTH)
+ {
+ angle = '0';
+ }
+
+ node.angle = angle;
+ }
+};
+
+/**
+ * Function: updateVmlStrokeNode
+ *
+ * Creates the stroke node for VML.
+ */
+mxShape.prototype.updateVmlStrokeNode = function(parent)
+{
+ // Stroke node is always needed to specify defaults that match SVG output
+ if (this.strokeNode == null)
+ {
+ this.strokeNode = document.createElement('v:stroke');
+
+ // To math SVG defaults jointsyle miter and miterlimit 4
+ this.strokeNode.joinstyle = 'miter';
+ this.strokeNode.miterlimit = 4;
+
+ parent.appendChild(this.strokeNode);
+ }
+
+ if (this.opacity != null)
+ {
+ this.strokeNode.opacity = this.opacity + '%';
+ }
+
+ this.updateVmlDashStyle();
+};
+
+/**
+ * Function: updateVmlStrokeColor
+ *
+ * Updates the VML stroke color for the given node.
+ */
+mxShape.prototype.updateVmlStrokeColor = function(node)
+{
+ var color = this.stroke;
+
+ if (color != null && color != mxConstants.NONE)
+ {
+ node.stroked = 'true';
+ node.strokecolor = color;
+ }
+ else
+ {
+ node.stroked = 'false';
+ }
+};
+
+/**
+ * Function: configureVmlShape
+ *
+ * Configures the specified VML node by applying the current color,
+ * bounds, shadow, opacity etc.
+ */
+mxShape.prototype.configureVmlShape = function(node)
+{
+ node.style.position = 'absolute';
+ this.updateVmlStrokeColor(node);
+ node.style.background = '';
+ var color = this.fill;
+
+ if (color != null && color != mxConstants.NONE)
+ {
+ if (this.fillNode == null)
+ {
+ this.fillNode = document.createElement('v:fill');
+ node.appendChild(this.fillNode);
+ }
+
+ this.updateVmlFill(this.fillNode, color, this.gradient, this.gradientDirection, this.opacity);
+ }
+ else
+ {
+ node.filled = 'false';
+
+ if (this.points == null)
+ {
+ this.configureTransparentBackground(node);
+ }
+ }
+
+ this.updateVmlStrokeNode(node);
+
+ if (this.isShadow)
+ {
+ this.createVmlShadow(node);
+ }
+
+ // Fixes possible hang in IE when arcsize is set on non-rects
+ if (node.nodeName == 'roundrect')
+ {
+ // Workaround for occasional "member not found" error
+ try
+ {
+ var f = mxConstants.RECTANGLE_ROUNDING_FACTOR * 100;
+
+ if (this.style != null)
+ {
+ f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, f);
+ }
+
+ node.setAttribute('arcsize', String(f) + '%');
+ }
+ catch (e)
+ {
+ // ignore
+ }
+ }
+};
+
+/**
+ * Function: createVmlShadow
+ *
+ * Creates the VML shadow node.
+ */
+mxShape.prototype.createVmlShadow = function(node)
+{
+ // Adds a shadow only once per shape
+ if (this.shadowNode == null)
+ {
+ this.shadowNode = document.createElement('v:shadow');
+ this.shadowNode.on = 'true';
+ this.shadowNode.color = mxConstants.SHADOWCOLOR;
+ this.shadowNode.opacity = (mxConstants.SHADOW_OPACITY * 100) + '%';
+
+ this.shadowStrokeNode = document.createElement('v:stroke');
+ this.shadowNode.appendChild(this.shadowStrokeNode);
+
+ node.appendChild(this.shadowNode);
+ }
+};
+
+/**
+ * Function: configureTransparentBackground
+ *
+ * Hook to make the background of a shape transparent. This hook was added as
+ * a workaround for the "display non secure items" warning dialog in IE which
+ * appears if the background:url(transparent.gif) is used in the overlay pane
+ * of a diagram. Since only mxImageShapes currently exist in the overlay pane
+ * this function is only overridden in mxImageShape.
+ */
+mxShape.prototype.configureTransparentBackground = function(node)
+{
+ node.style.background = 'url(\'' + mxClient.imageBasePath + '/transparent.gif\')';
+};
+
+/**
+ * Function: configureSvgShape
+ *
+ * Configures the specified SVG node by applying the current color,
+ * bounds, shadow, opacity etc.
+ */
+mxShape.prototype.configureSvgShape = function(node)
+{
+ var color = this.stroke;
+
+ if (color != null && color != mxConstants.NONE)
+ {
+ node.setAttribute('stroke', color);
+ }
+ else
+ {
+ node.setAttribute('stroke', 'none');
+ }
+
+ color = this.fill;
+
+ if (color != null && color != mxConstants.NONE)
+ {
+ // Fetches a reference to a shared gradient
+ if (this.gradient != null)
+ {
+ var id = this.getGradientId(color, this.gradient);
+
+ if (this.gradientNode != null && this.gradientNode.getAttribute('id') != id)
+ {
+ this.gradientNode = null;
+ node.setAttribute('fill', '');
+ }
+
+ if (this.gradientNode == null)
+ {
+ this.gradientNode = this.createSvgGradient(id,
+ color, this.gradient, node);
+ node.setAttribute('fill', 'url(#'+id+')');
+ }
+ }
+ else
+ {
+ // TODO: Remove gradient from document if no longer shared
+ this.gradientNode = null;
+ node.setAttribute('fill', color);
+ }
+ }
+ else
+ {
+ node.setAttribute('fill', 'none');
+ }
+
+ if (this.opacity != null)
+ {
+ // Improves opacity performance in Firefox
+ node.setAttribute('fill-opacity', this.opacity / 100);
+ node.setAttribute('stroke-opacity', this.opacity / 100);
+ }
+};
+
+/**
+ * Function: getGradientId
+ *
+ * Creates a unique ID for the gradient of this shape.
+ */
+mxShape.prototype.getGradientId = function(start, end)
+{
+ // Removes illegal characters from gradient ID
+ if (start.charAt(0) == '#')
+ {
+ start = start.substring(1);
+ }
+
+ if (end.charAt(0) == '#')
+ {
+ end = end.substring(1);
+ }
+
+ // Workaround for gradient IDs not working in Safari 5 / Chrome 6
+ // if they contain uppercase characters
+ start = start.toLowerCase();
+ end = end.toLowerCase();
+
+ var dir = null;
+
+ if (this.gradientDirection == null ||
+ this.gradientDirection == mxConstants.DIRECTION_SOUTH)
+ {
+ dir = 'south';
+ }
+ else if (this.gradientDirection == mxConstants.DIRECTION_EAST)
+ {
+ dir = 'east';
+ }
+ else
+ {
+ var tmp = start;
+ start = end;
+ end = tmp;
+
+ if (this.gradientDirection == mxConstants.DIRECTION_NORTH)
+ {
+ dir = 'south';
+ }
+ else if (this.gradientDirection == mxConstants.DIRECTION_WEST)
+ {
+ dir = 'east';
+ }
+ }
+
+ return 'mx-gradient-'+start+'-'+end+'-'+dir;
+};
+
+/**
+ * Function: createSvgPipe
+ *
+ * Creates an invisible path which is used to increase the hit detection for
+ * edges in SVG.
+ */
+mxShape.prototype.createSvgPipe = function(id, start, end, node)
+{
+ var pipe = document.createElementNS(mxConstants.NS_SVG, 'path');
+ pipe.setAttribute('pointer-events', 'stroke');
+ pipe.setAttribute('fill', 'none');
+ pipe.setAttribute('visibility', 'hidden');
+ // Workaround for Opera ignoring the visiblity attribute above while
+ // other browsers need a stroke color to perform the hit-detection but
+ // do not ignore the visibility attribute. Side-effect is that Opera's
+ // hit detection for horizontal/vertical edges seems to ignore the pipe.
+ pipe.setAttribute('stroke', (mxClient.IS_OP) ? 'none' : 'white');
+
+ return pipe;
+};
+
+/**
+ * Function: createSvgGradient
+ *
+ * Creates a gradient object for SVG using the specified startcolor,
+ * endcolor and opacity.
+ */
+mxShape.prototype.createSvgGradient = function(id, start, end, node)
+{
+ var gradient = this.insertGradientNode;
+
+ if (gradient == null)
+ {
+ gradient = document.createElementNS(mxConstants.NS_SVG, 'linearGradient');
+ gradient.setAttribute('id', id);
+ gradient.setAttribute('x1', '0%');
+ gradient.setAttribute('y1', '0%');
+ gradient.setAttribute('x2', '0%');
+ gradient.setAttribute('y2', '0%');
+
+ if (this.gradientDirection == null ||
+ this.gradientDirection == mxConstants.DIRECTION_SOUTH)
+ {
+ gradient.setAttribute('y2', '100%');
+ }
+ else if (this.gradientDirection == mxConstants.DIRECTION_EAST)
+ {
+ gradient.setAttribute('x2', '100%');
+ }
+ else if (this.gradientDirection == mxConstants.DIRECTION_NORTH)
+ {
+ gradient.setAttribute('y1', '100%');
+ }
+ else if (this.gradientDirection == mxConstants.DIRECTION_WEST)
+ {
+ gradient.setAttribute('x1', '100%');
+ }
+
+ var stop = document.createElementNS(mxConstants.NS_SVG, 'stop');
+ stop.setAttribute('offset', '0%');
+ stop.setAttribute('style', 'stop-color:'+start);
+ gradient.appendChild(stop);
+
+ stop = document.createElementNS(mxConstants.NS_SVG, 'stop');
+ stop.setAttribute('offset', '100%');
+ stop.setAttribute('style', 'stop-color:'+end);
+ gradient.appendChild(stop);
+ }
+
+ // Inserted later when the owner SVG element is known
+ this.insertGradientNode = gradient;
+
+ return gradient;
+};
+
+/**
+ * Function: createPoints
+ *
+ * Creates a path expression using the specified commands for this.points.
+ * If <isRounded> is true, then the path contains curves for the corners.
+ */
+mxShape.prototype.createPoints = function(moveCmd, lineCmd, curveCmd, isRelative)
+{
+ var offsetX = (isRelative) ? this.bounds.x : 0;
+ var offsetY = (isRelative) ? this.bounds.y : 0;
+
+ // Workaround for crisp shape-rendering in IE9
+ var crisp = (this.crisp && this.dialect == mxConstants.DIALECT_SVG && mxClient.IS_IE) ? 0.5 : 0;
+
+ if (isNaN(this.points[0].x) || isNaN(this.points[0].y))
+ {
+ return null;
+ }
+
+ var size = mxConstants.LINE_ARCSIZE * this.scale;
+ var p0 = this.points[0];
+
+ if (this.startOffset != null)
+ {
+ p0 = p0.clone();
+ p0.x += this.startOffset.x;
+ p0.y += this.startOffset.y;
+ }
+
+ var points = moveCmd + ' ' + (Math.round(p0.x - offsetX) + crisp) + ' ' +
+ (Math.round(p0.y - offsetY) + crisp) + ' ';
+
+ for (var i = 1; i < this.points.length; i++)
+ {
+ p0 = this.points[i - 1];
+ var pt = this.points[i];
+
+ if (isNaN(pt.x) || isNaN(pt.y))
+ {
+ return null;
+ }
+
+ if (i == this.points.length - 1 && this.endOffset != null)
+ {
+ pt = pt.clone();
+ pt.x += this.endOffset.x;
+ pt.y += this.endOffset.y;
+ }
+
+ var dx = p0.x - pt.x;
+ var dy = p0.y - pt.y;
+
+ if ((this.isRounded && i < this.points.length - 1) &&
+ (dx != 0 || dy != 0) && this.scale > 0.3)
+ {
+ // Draws a line from the last point to the current point with a spacing
+ // of size off the current point into direction of the last point
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ var nx1 = dx * Math.min(size, dist / 2) / dist;
+ var ny1 = dy * Math.min(size, dist / 2) / dist;
+ points += lineCmd + ' ' + (Math.round(pt.x + nx1 - offsetX) + crisp) + ' ' +
+ (Math.round(pt.y + ny1 - offsetY) + crisp) + ' ';
+
+ // Draws a curve from the last point to the current point with a spacing
+ // of size off the current point into direction of the next point
+ var pe = this.points[i+1];
+ dx = pe.x - pt.x;
+ dy = pe.y - pt.y;
+
+ dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
+
+ if (dist != 0)
+ {
+ var nx2 = dx * Math.min(size, dist / 2) / dist;
+ var ny2 = dy * Math.min(size, dist / 2) / dist;
+
+ points += curveCmd + ' ' + Math.round(pt.x - offsetX) + ' '+
+ Math.round(pt.y - offsetY) + ' ' + Math.round(pt.x - offsetX) + ',' +
+ Math.round(pt.y - offsetY) + ' ' + (Math.round(pt.x + nx2 - offsetX) + crisp) + ' ' +
+ (Math.round(pt.y + ny2 - offsetY) + crisp) + ' ';
+ }
+ }
+ else
+ {
+ points += lineCmd + ' ' + (Math.round(pt.x - offsetX) + crisp) + ' ' + (Math.round(pt.y - offsetY) + crisp) + ' ';
+ }
+ }
+
+ return points;
+};
+
+/**
+ * Function: updateHtmlShape
+ *
+ * Updates the bounds or points of the specified HTML node and
+ * updates the inner children to reflect the changes.
+ */
+mxShape.prototype.updateHtmlShape = function(node)
+{
+ if (node != null)
+ {
+ if (mxUtils.isVml(node))
+ {
+ this.updateVmlShape(node);
+ }
+ else
+ {
+ var sw = Math.ceil(this.strokewidth * this.scale);
+ node.style.borderWidth = Math.max(1, sw) + 'px';
+
+ if (this.bounds != null && !isNaN(this.bounds.x) && !isNaN(this.bounds.y) &&
+ !isNaN(this.bounds.width) && !isNaN(this.bounds.height))
+ {
+ node.style.left = Math.round(this.bounds.x - sw / 2) + 'px';
+ node.style.top = Math.round(this.bounds.y - sw / 2) + 'px';
+
+ if (document.compatMode == 'CSS1Compat')
+ {
+ sw = -sw;
+ }
+
+ node.style.width = Math.round(Math.max(0, this.bounds.width + sw)) + 'px';
+ node.style.height = Math.round(Math.max(0, this.bounds.height + sw)) + 'px';
+
+ if (this.bounds.width == 0 || this.bounds.height == 0)
+ {
+ node.style.visibility = 'hidden';
+ }
+ else
+ {
+ node.style.visibility = 'visible';
+ }
+ }
+ }
+
+ if (this.points != null && this.bounds != null && !mxUtils.isVml(node))
+ {
+ if (this.divContainer == null)
+ {
+ this.divContainer = node;
+ }
+
+ while (this.divContainer.firstChild != null)
+ {
+ mxEvent.release(this.divContainer.firstChild);
+ this.divContainer.removeChild(this.divContainer.firstChild);
+ }
+
+ node.style.borderStyle = '';
+ node.style.background = '';
+
+ if (this.points.length == 2)
+ {
+ var p0 = this.points[0];
+ var pe = this.points[1];
+
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+
+ if (dx == 0 || dy == 0)
+ {
+ node.style.borderStyle = 'solid';
+ }
+ else
+ {
+ node.style.width = Math.round(this.bounds.width + 1) + 'px';
+ node.style.height = Math.round(this.bounds.height + 1) + 'px';
+
+ var length = Math.sqrt(dx * dx + dy * dy);
+ var dotCount = 1 + (length / (8 * this.scale));
+
+ var nx = dx / dotCount;
+ var ny = dy / dotCount;
+ var x = p0.x - this.bounds.x;
+ var y = p0.y - this.bounds.y;
+
+ for (var i = 0; i < dotCount; i++)
+ {
+ var tmp = document.createElement('DIV');
+
+ tmp.style.position = 'absolute';
+ tmp.style.overflow = 'hidden';
+
+ tmp.style.left = Math.round(x) + 'px';
+ tmp.style.top = Math.round(y) + 'px';
+ tmp.style.width = Math.max(1, 2 * this.scale) + 'px';
+ tmp.style.height = Math.max(1, 2 * this.scale) + 'px';
+
+ tmp.style.backgroundColor = this.stroke;
+ this.divContainer.appendChild(tmp);
+
+ x += nx;
+ y += ny;
+ }
+ }
+ }
+ else if (this.points.length == 3)
+ {
+ var mid = this.points[1];
+
+ var n = '0';
+ var s = '1';
+ var w = '0';
+ var e = '1';
+
+ if (mid.x == this.bounds.x)
+ {
+ e = '0';
+ w = '1';
+ }
+
+ if (mid.y == this.bounds.y)
+ {
+ n = '1';
+ s = '0';
+ }
+
+ node.style.borderStyle = 'solid';
+ node.style.borderWidth = n + ' ' + e + ' ' + s + ' ' + w + 'px';
+ }
+ else
+ {
+ node.style.width = Math.round(this.bounds.width + 1) + 'px';
+ node.style.height = Math.round(this.bounds.height + 1) + 'px';
+ var last = this.points[0];
+
+ for (var i = 1; i < this.points.length; i++)
+ {
+ var next = this.points[i];
+
+ // TODO: Use one div for multiple lines
+ var tmp = document.createElement('DIV');
+
+ tmp.style.position = 'absolute';
+ tmp.style.overflow = 'hidden';
+
+ tmp.style.borderColor = this.stroke;
+ tmp.style.borderStyle = 'solid';
+ tmp.style.borderWidth = '1 0 0 1px';
+
+ var x = Math.min(next.x, last.x) - this.bounds.x;
+ var y = Math.min(next.y, last.y) - this.bounds.y;
+ var w = Math.max(1, Math.abs(next.x - last.x));
+ var h = Math.max(1, Math.abs(next.y - last.y));
+
+ tmp.style.left = x + 'px';
+ tmp.style.top = y + 'px';
+ tmp.style.width = w + 'px';
+ tmp.style.height = h + 'px';
+
+ this.divContainer.appendChild(tmp);
+ last = next;
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Function: updateVmlDashStyle
+ *
+ * Updates the dashstyle in the stroke node.
+ */
+mxShape.prototype.updateVmlDashStyle = function()
+{
+ if (this.isDashed)
+ {
+ if (this.strokeNode.dashstyle != 'dash')
+ {
+ this.strokeNode.dashstyle = 'dash';
+ }
+ }
+ else if (this.strokeNode.dashstyle != 'solid')
+ {
+ this.strokeNode.dashstyle = 'solid';
+ }
+};
+
+/**
+ * Function: updateVmlShape
+ *
+ * Updates the bounds or points of the specified VML node and
+ * updates the inner children to reflect the changes.
+ */
+mxShape.prototype.updateVmlShape = function(node)
+{
+ node.strokeweight = (this.strokewidth * this.scale) + 'px';
+
+ // Dash pattern needs updating as it depends on strokeweight in VML
+ if (this.strokeNode != null)
+ {
+ this.updateVmlDashStyle();
+ }
+
+ // Updates the offset of the shadow
+ if (this.shadowNode != null)
+ {
+ var dx = Math.round(mxConstants.SHADOW_OFFSET_X * this.scale);
+ var dy = Math.round(mxConstants.SHADOW_OFFSET_Y * this.scale);
+ this.shadowNode.offset = dx + 'px,' + dy + 'px';
+ }
+
+ if (this.bounds != null && !isNaN(this.bounds.x) && !isNaN(this.bounds.y) &&
+ !isNaN(this.bounds.width) && !isNaN(this.bounds.height))
+ {
+ var f = 1;
+
+ var w = Math.max(0, Math.round(this.bounds.width));
+ var h = Math.max(0, Math.round(this.bounds.height));
+
+ // Groups and shapes need a coordsize
+ if (this.points != null || node.nodeName == 'shape' || node.nodeName == 'group')
+ {
+ var tmp = (node.parentNode.nodeName == 'group') ? 1 : this.vmlScale;
+ node.coordsize = (w * tmp) + ',' + (h * tmp);
+ }
+ else if (node.parentNode.nodeName == 'group')
+ {
+ f = this.vmlScale;
+ }
+
+ // Only top-level nodes are non-relative and rotated
+ if (node.parentNode != this.node)
+ {
+ node.style.left = Math.round(this.bounds.x * f) + 'px';
+ node.style.top = Math.round(this.bounds.y * f) + 'px';
+
+ if (this.points == null)
+ {
+ if (this.rotation != null && this.rotation != 0)
+ {
+ node.style.rotation = this.rotation;
+ }
+ else if (node.style.rotation != null)
+ {
+ node.style.rotation = '';
+ }
+ }
+ }
+
+ node.style.width = (w * f) + 'px';
+ node.style.height = (h * f) + 'px';
+ }
+
+ if (this.points != null && node.nodeName != 'group')
+ {
+ if (node.nodeName == 'polyline' && node.points != null)
+ {
+ var points = '';
+
+ for (var i = 0; i < this.points.length; i++)
+ {
+ points += this.points[i].x + ',' + this.points[i].y + ' ';
+ }
+
+ node.points.value = points;
+
+ node.style.left = null;
+ node.style.top = null;
+ node.style.width = null;
+ node.style.height = null;
+ }
+ else if (this.bounds != null)
+ {
+ var points = this.createPoints('m', 'l', 'c', true);
+
+ // Smooth style for VML (experimental)
+ if (this.style != null && this.style[mxConstants.STYLE_SMOOTH])
+ {
+ var pts = this.points;
+ var n = pts.length;
+
+ if (n > 3)
+ {
+ var x0 = this.bounds.x;
+ var y0 = this.bounds.y;
+ points = 'm ' + Math.round(pts[0].x - x0) + ' ' + Math.round(pts[0].y - y0) + ' qb';
+
+ for (var i = 1; i < n - 1; i++)
+ {
+ points += ' ' + Math.round(pts[i].x - x0) + ' ' + Math.round(pts[i].y - y0);
+ }
+
+ points += ' nf l ' + Math.round(pts[n - 1].x - x0) + ' ' + Math.round(pts[n - 1].y - y0);
+ }
+ }
+
+ node.path = points + ' e';
+ }
+ }
+};
+
+/**
+ * Function: updateSvgBounds
+ *
+ * Updates the bounds of the given node using <bounds>.
+ */
+mxShape.prototype.updateSvgBounds = function(node)
+{
+ var w = this.bounds.width;
+ var h = this.bounds.height;
+
+ if (this.isRounded && !(this.crisp && mxClient.IS_IE))
+ {
+ node.setAttribute('x', this.bounds.x);
+ node.setAttribute('y', this.bounds.y);
+ node.setAttribute('width', w);
+ node.setAttribute('height', h);
+ }
+ else
+ {
+ // Workaround for crisp shape-rendering in IE9
+ var dd = (this.crisp && mxClient.IS_IE) ? 0.5 : 0;
+ node.setAttribute('x', Math.round(this.bounds.x) + dd);
+ node.setAttribute('y', Math.round(this.bounds.y) + dd);
+
+ w = Math.round(w);
+ h = Math.round(h);
+
+ node.setAttribute('width', w);
+ node.setAttribute('height', h);
+ }
+
+ if (this.isRounded)
+ {
+ var f = mxConstants.RECTANGLE_ROUNDING_FACTOR * 100;
+
+ if (this.style != null)
+ {
+ f = mxUtils.getValue(this.style, mxConstants.STYLE_ARCSIZE, f) / 100;
+ }
+
+ var r = Math.min(w * f, h * f);
+ node.setAttribute('rx', r);
+ node.setAttribute('ry', r);
+ }
+
+ this.updateSvgTransform(node, node == this.shadowNode);
+};
+
+/**
+ * Function: updateSvgPath
+ *
+ * Updates the path of the given node using <points>.
+ */
+mxShape.prototype.updateSvgPath = function(node)
+{
+ var d = this.createPoints('M', 'L', 'C', false);
+
+ if (d != null)
+ {
+ node.setAttribute('d', d);
+
+ // Smooth style for SVG (experimental)
+ if (this.style != null && this.style[mxConstants.STYLE_SMOOTH])
+ {
+ var pts = this.points;
+ var n = pts.length;
+
+ if (n > 3)
+ {
+ var points = 'M '+pts[0].x+' '+pts[0].y+' ';
+ points += ' Q '+pts[1].x + ' ' + pts[1].y + ' ' +
+ ' '+pts[2].x + ' ' + pts[2].y;
+
+ for (var i = 3; i < n; i++)
+ {
+ points += ' T ' + pts[i].x + ' ' + pts[i].y;
+ }
+
+ node.setAttribute('d', points);
+ }
+ }
+
+ node.removeAttribute('x');
+ node.removeAttribute('y');
+ node.removeAttribute('width');
+ node.removeAttribute('height');
+ }
+};
+
+/**
+ * Function: updateSvgScale
+ *
+ * Updates the properties of the given node that depend on the scale and checks
+ * the crisp rendering attribute.
+ */
+mxShape.prototype.updateSvgScale = function(node)
+{
+ node.setAttribute('stroke-width', Math.round(Math.max(1, this.strokewidth * this.scale)));
+
+ if (this.isDashed)
+ {
+ var phase = Math.max(1, Math.round(3 * this.scale * this.strokewidth));
+ node.setAttribute('stroke-dasharray', phase + ' ' + phase);
+ }
+
+ if (this.crisp && (this.roundedCrispSvg || this.isRounded != true) &&
+ (this.rotation == null || this.rotation == 0))
+ {
+ node.setAttribute('shape-rendering', 'crispEdges');
+ }
+ else
+ {
+ node.removeAttribute('shape-rendering');
+ }
+};
+
+/**
+ * Function: updateSvgShape
+ *
+ * Updates the bounds or points of the specified SVG node and
+ * updates the inner children to reflect the changes.
+ */
+mxShape.prototype.updateSvgShape = function(node)
+{
+ if (this.points != null && this.points[0] != null)
+ {
+ this.updateSvgPath(node);
+ }
+ else if (this.bounds != null)
+ {
+ this.updateSvgBounds(node);
+ }
+
+ this.updateSvgScale(node);
+};
+
+/**
+ * Function: getSvgShadowTransform
+ *
+ * Returns the current transformation for SVG shadows.
+ */
+mxShape.prototype.getSvgShadowTransform = function(node, shadow)
+{
+ var dx = mxConstants.SHADOW_OFFSET_X * this.scale;
+ var dy = mxConstants.SHADOW_OFFSET_Y * this.scale;
+
+ return 'translate(' + dx + ' ' + dy + ')';
+};
+
+/**
+ * Function: updateSvgTransform
+ *
+ * Updates the tranform of the given node.
+ */
+mxShape.prototype.updateSvgTransform = function(node, shadow)
+{
+ var st = (shadow) ? this.getSvgShadowTransform() : '';
+
+ if (this.rotation != null && this.rotation != 0)
+ {
+ var cx = this.bounds.x + this.bounds.width / 2;
+ var cy = this.bounds.y + this.bounds.height / 2;
+ node.setAttribute('transform', 'rotate(' + this.rotation + ',' + cx + ',' + cy + ') ' + st);
+ }
+ else
+ {
+ if (shadow)
+ {
+ node.setAttribute('transform', st);
+ }
+ else
+ {
+ node.removeAttribute('transform');
+ }
+ }
+};
+
+/**
+ * Function: reconfigure
+ *
+ * Reconfigures this shape. This will update the colors etc in
+ * addition to the bounds or points.
+ */
+mxShape.prototype.reconfigure = function()
+{
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ if (this.innerNode != null)
+ {
+ this.configureSvgShape(this.innerNode);
+ }
+ else
+ {
+ this.configureSvgShape(this.node);
+ }
+
+ if (this.insertGradientNode != null)
+ {
+ this.insertGradient(this.insertGradientNode);
+ this.insertGradientNode = null;
+ }
+ }
+ else if (mxUtils.isVml(this.node))
+ {
+ this.node.style.visibility = 'hidden';
+ this.configureVmlShape(this.node);
+ this.node.style.visibility = 'visible';
+ }
+ else
+ {
+ this.node.style.visibility = 'hidden';
+ this.configureHtmlShape(this.node);
+ this.node.style.visibility = 'visible';
+ }
+};
+
+/**
+ * Function: redraw
+ *
+ * Invokes <redrawSvg>, <redrawVml> or <redrawHtml> depending on the
+ * dialect of the shape.
+ */
+mxShape.prototype.redraw = function()
+{
+ this.updateBoundingBox();
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.redrawSvg();
+ }
+ else if (mxUtils.isVml(this.node))
+ {
+ this.node.style.visibility = 'hidden';
+ this.redrawVml();
+ this.node.style.visibility = 'visible';
+ }
+ else
+ {
+ this.redrawHtml();
+ }
+};
+
+/**
+ * Function: updateBoundingBox
+ *
+ * Updates the <boundingBox> for this shape using <createBoundingBox> and
+ * <augmentBoundingBox> and stores the result in <boundingBox>.
+ */
+mxShape.prototype.updateBoundingBox = function()
+{
+ if (this.bounds != null)
+ {
+ var bbox = this.createBoundingBox();
+ this.augmentBoundingBox(bbox);
+
+ var rot = Number(mxUtils.getValue(this.style, mxConstants.STYLE_ROTATION, 0));
+
+ if (rot != 0)
+ {
+ bbox = mxUtils.getBoundingBox(bbox, rot);
+ }
+
+ bbox.x = Math.floor(bbox.x);
+ bbox.y = Math.floor(bbox.y);
+ // TODO: Fix rounding errors
+ bbox.width = Math.ceil(bbox.width);
+ bbox.height = Math.ceil(bbox.height);
+
+ this.boundingBox = bbox;
+ }
+};
+
+/**
+ * Function: createBoundingBox
+ *
+ * Returns a new rectangle that represents the bounding box of the bare shape
+ * with no shadows or strokewidths.
+ */
+mxShape.prototype.createBoundingBox = function()
+{
+ return this.bounds.clone();
+};
+
+/**
+ * Function: augmentBoundingBox
+ *
+ * Augments the bounding box with the strokewidth and shadow offsets.
+ */
+mxShape.prototype.augmentBoundingBox = function(bbox)
+{
+ if (this.isShadow)
+ {
+ bbox.width += Math.ceil(mxConstants.SHADOW_OFFSET_X * this.scale);
+ bbox.height += Math.ceil(mxConstants.SHADOW_OFFSET_Y * this.scale);
+ }
+
+ // Adds strokeWidth
+ var sw = Math.ceil(this.strokewidth * this.scale);
+ bbox.grow(Math.ceil(sw / 2));
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Redraws this SVG shape by invoking <updateSvgShape> on this.node,
+ * this.innerNode and this.shadowNode.
+ */
+mxShape.prototype.redrawSvg = function()
+{
+ if (this.innerNode != null)
+ {
+ this.updateSvgShape(this.innerNode);
+
+ if (this.shadowNode != null)
+ {
+ this.updateSvgShape(this.shadowNode);
+ }
+ }
+ else
+ {
+ this.updateSvgShape(this.node);
+
+ // Updates the transform of the shadow
+ if (this.shadowNode != null)
+ {
+ this.shadowNode.setAttribute('transform', this.getSvgShadowTransform());
+ }
+ }
+
+ this.updateSvgGlassPane();
+};
+
+/**
+ * Function: updateVmlGlassPane
+ *
+ * Draws the glass overlay if mxConstants.STYLE_GLASS is 1.
+ */
+mxShape.prototype.updateVmlGlassPane = function()
+{
+ // Currently only used in mxLabel. Most shapes would have to be changed to use
+ // a group node in VML which might affect performance for glass-less cells.
+ if (this.bounds != null && this.node.nodeName == 'group' && this.style != null &&
+ mxUtils.getValue(this.style, mxConstants.STYLE_GLASS, 0) == 1)
+ {
+ // Glass overlay
+ if (this.node.glassOverlay == null)
+ {
+ // Creates glass overlay
+ this.node.glassOverlay = document.createElement('v:shape');
+ this.node.glassOverlay.setAttribute('filled', 'true');
+ this.node.glassOverlay.setAttribute('fillcolor', 'white');
+ this.node.glassOverlay.setAttribute('stroked', 'false');
+
+ var fillNode = document.createElement('v:fill');
+ fillNode.setAttribute('type', 'gradient');
+ fillNode.setAttribute('color', 'white');
+ fillNode.setAttribute('color2', 'white');
+ fillNode.setAttribute('opacity', '90%');
+ fillNode.setAttribute('o:opacity2', '15%');
+ fillNode.setAttribute('angle', '180');
+
+ this.node.glassOverlay.appendChild(fillNode);
+ this.node.appendChild(this.node.glassOverlay);
+ }
+
+ var size = 0.4;
+
+ // TODO: Mask with rectangle or rounded rectangle of label
+ var b = this.bounds;
+ var sw = Math.ceil(this.strokewidth * this.scale / 2 + 1);
+ var d = 'm ' + (-sw) + ' ' + (-sw) + ' l ' + (-sw) + ' ' + Math.round(b.height * size) +
+ ' c ' + Math.round(b.width * 0.3) + ' ' + Math.round(b.height * 0.6) +
+ ' ' + Math.round(b.width * 0.7) + ' ' + Math.round(b.height * 0.6) +
+ ' ' + Math.round(b.width + sw) + ' ' + Math.round(b.height * size) +
+ ' l '+Math.round(b.width + sw)+' ' + (-sw) + ' x e';
+ this.node.glassOverlay.style.position = 'absolute';
+ this.node.glassOverlay.style.width = b.width + 'px';
+ this.node.glassOverlay.style.height = b.height + 'px';
+ this.node.glassOverlay.setAttribute('coordsize',
+ Math.round(this.bounds.width) + ',' +
+ Math.round(this.bounds.height));
+ this.node.glassOverlay.setAttribute('path', d);
+ }
+ else if (this.node.glassOverlay != null)
+ {
+ this.node.glassOverlay.parentNode.removeChild(this.node.glassOverlay);
+ this.node.glassOverlay = null;
+ }
+};
+
+/**
+ * Function: updateSvgGlassPane
+ *
+ * Draws the glass overlay if mxConstants.STYLE_GLASS is 1.
+ */
+mxShape.prototype.updateSvgGlassPane = function()
+{
+ if (this.node.nodeName == 'g' && this.style != null &&
+ mxUtils.getValue(this.style, mxConstants.STYLE_GLASS, 0) == 1)
+ {
+ // Glass overlay
+ if (this.node.glassOverlay == null)
+ {
+ // Glass overlay gradient
+ if (this.node.ownerSVGElement.glassGradient == null)
+ {
+ // Creates glass overlay gradient
+ var glassGradient = document.createElementNS(mxConstants.NS_SVG, 'linearGradient');
+ glassGradient.setAttribute('x1', '0%');
+ glassGradient.setAttribute('y1', '0%');
+ glassGradient.setAttribute('x2', '0%');
+ glassGradient.setAttribute('y2', '100%');
+
+ var stop1 = document.createElementNS(mxConstants.NS_SVG, 'stop');
+ stop1.setAttribute('offset', '0%');
+ stop1.setAttribute('style', 'stop-color:#ffffff;stop-opacity:0.9');
+ glassGradient.appendChild(stop1);
+
+ var stop2 = document.createElementNS(mxConstants.NS_SVG, 'stop');
+ stop2.setAttribute('offset', '100%');
+ stop2.setAttribute('style', 'stop-color:#ffffff;stop-opacity:0.1');
+ glassGradient.appendChild(stop2);
+
+ // Finds a unique ID for the gradient
+ var prefix = 'mx-glass-gradient-';
+ var counter = 0;
+
+ while (document.getElementById(prefix+counter) != null)
+ {
+ counter++;
+ }
+
+ glassGradient.setAttribute('id', prefix+counter);
+ this.node.ownerSVGElement.appendChild(glassGradient);
+ this.node.ownerSVGElement.glassGradient = glassGradient;
+ }
+
+ // Creates glass overlay
+ this.node.glassOverlay = document.createElementNS(mxConstants.NS_SVG, 'path');
+ // LATER: Not sure what the behaviour is for mutiple SVG elements in page.
+ // Probably its possible that this points to an element in another SVG
+ // node which when removed will result in an undefined background.
+ var id = this.node.ownerSVGElement.glassGradient.getAttribute('id');
+ this.node.glassOverlay.setAttribute('style', 'fill:url(#'+id+');');
+ this.node.appendChild(this.node.glassOverlay);
+ }
+
+ var size = 0.4;
+
+ // TODO: Mask with rectangle or rounded rectangle of label
+ var b = this.bounds;
+ var sw = Math.ceil(this.strokewidth * this.scale / 2);
+ var d = 'm ' + (b.x - sw) + ',' + (b.y - sw) +
+ ' L ' + (b.x - sw) + ',' + (b.y + b.height * size) +
+ ' Q '+ (b.x + b.width * 0.5) + ',' + (b.y + b.height * 0.7) + ' '+
+ (b.x + b.width + sw) + ',' + (b.y + b.height * size) +
+ ' L ' + (b.x + b.width + sw) + ',' + (b.y - sw) + ' z';
+ this.node.glassOverlay.setAttribute('d', d);
+ }
+ else if (this.node.glassOverlay != null)
+ {
+ this.node.glassOverlay.parentNode.removeChild(this.node.glassOverlay);
+ this.node.glassOverlay = null;
+ }
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Redraws this VML shape by invoking <updateVmlShape> on this.node.
+ */
+mxShape.prototype.redrawVml = function()
+{
+ this.node.style.visibility = 'hidden';
+ this.updateVmlShape(this.node);
+ this.updateVmlGlassPane();
+ this.node.style.visibility = 'visible';
+};
+
+/**
+ * Function: redrawHtml
+ *
+ * Redraws this HTML shape by invoking <updateHtmlShape> on this.node.
+ */
+mxShape.prototype.redrawHtml = function()
+{
+ this.updateHtmlShape(this.node);
+};
+
+/**
+ * Function: getRotation
+ *
+ * Returns the current rotation including direction.
+ */
+mxShape.prototype.getRotation = function()
+{
+ var rot = this.rotation || 0;
+
+ // Default direction is east (ignored if rotation exists)
+ if (this.direction != null)
+ {
+ if (this.direction == 'north')
+ {
+ rot += 270;
+ }
+ else if (this.direction == 'west')
+ {
+ rot += 180;
+ }
+ else if (this.direction == 'south')
+ {
+ rot += 90;
+ }
+ }
+
+ return rot;
+};
+
+/**
+ * Function: createPath
+ *
+ * Creates an <mxPath> for the specified format and origin. The path object is
+ * then passed to <redrawPath> and <mxPath.getPath> is returned.
+ */
+mxShape.prototype.createPath = function(arg)
+{
+ var x = this.bounds.x;
+ var y = this.bounds.y;
+ var w = this.bounds.width;
+ var h = this.bounds.height;
+ var dx = 0;
+ var dy = 0;
+
+ // Inverts bounds for stencils which are rotated 90 or 270 degrees
+ if (this.direction == 'north' || this.direction == 'south')
+ {
+ dx = (w - h) / 2;
+ dy = (h - w) / 2;
+ x += dx;
+ y += dy;
+ var tmp = w;
+ w = h;
+ h = tmp;
+ }
+
+ var rotation = this.getRotation();
+ var path = null;
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ path = new mxPath('svg');
+ path.setTranslate(x, y);
+
+ // Adds rotation as a separate transform
+ if (rotation != 0)
+ {
+ var cx = this.bounds.getCenterX();
+ var cy = this.bounds.getCenterY();
+ var transform = 'rotate(' + rotation + ' ' + cx + ' ' + cy + ')';
+
+ if (this.innerNode != null)
+ {
+ this.innerNode.setAttribute('transform', transform);
+ }
+
+ if (this.foreground != null)
+ {
+ this.foreground.setAttribute('transform', transform);
+ }
+
+ // Shadow needs different transform so that it ends up on the correct side
+ if (this.shadowNode != null)
+ {
+ this.shadowNode.setAttribute('transform', this.getSvgShadowTransform() + ' ' + transform);
+ }
+ }
+ }
+ else
+ {
+ path = new mxPath('vml');
+ path.setTranslate(dx, -dx);
+ path.scale = this.vmlScale;
+
+ if (rotation != 0)
+ {
+ this.node.style.rotation = rotation;
+ }
+ }
+
+ this.redrawPath(path, x, y, w, h, arg);
+
+ return path.getPath();
+};
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape. This implementation is empty. See
+ * <mxActor> and <mxCylinder> for implementations.
+ */
+mxShape.prototype.redrawPath = function(path, x, y, w, h)
+{
+ // do nothing
+};
diff --git a/src/js/shape/mxStencil.js b/src/js/shape/mxStencil.js
new file mode 100644
index 0000000..d0e1a63
--- /dev/null
+++ b/src/js/shape/mxStencil.js
@@ -0,0 +1,1585 @@
+/**
+ * $Id: mxStencil.js,v 1.91 2012-07-16 10:22:44 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxStencil
+ *
+ * Implements a generic shape which is based on a XML node as a description.
+ * The node contains a background and a foreground node, which contain the
+ * definition to render the respective part of the shape. Note that the
+ * fill, stroke or fillstroke of the background is be the first statement
+ * of the foreground. This is because the content of the background node
+ * maybe used to not only render the shape itself, but also its shadow and
+ * other elements which do not require a fill, stroke or fillstroke.
+ *
+ * The shape uses a coordinate system with a width of 100 and a height of
+ * 100 by default. This can be changed by setting the w and h attribute of
+ * the shape element. The aspect attribute can be set to "variable" (default)
+ * or "fixed". If fixed is used, then the aspect which is defined via the w
+ * and h attribute is kept constant while the shape is scaled.
+ *
+ * The possible contents of the background and foreground elements are rect,
+ * ellipse, roundrect, text, image, include-shape or paths. A path element
+ * contains move, line, curve, quad, arc and close elements. The rect, ellipse
+ * and roundrect elements may be thought of as special path elements. All these
+ * path elements must be followed by either fill, stroke or fillstroke (note
+ * that text, image and include-shape or not path elements).
+ *
+ * The background element can be empty or contain at most one path element. It
+ * should not contain a text, image or include-shape element. If the background
+ * element is empty, then no shadow or glass effect will be rendered. If the
+ * background element is non-empty, then the corresponding fill, stroke or
+ * fillstroke should be the first element in the subsequent foreground element.
+ *
+ * The format of the XML is "a simplified HTML 5 Canvas". Each command changes
+ * the "current" state, so eg. a linecap, linejoin will be used for all
+ * subsequent line drawing, unless a save/restore appears, which saves/restores
+ * a state in a stack.
+ *
+ * The connections section contains the fixed connection points for a stencil.
+ * The perimeter attribute of the constraint element should have a value of 0
+ * or 1 (default), where 1 (true) specifies that the given point should be
+ * projected into the perimeter of the given shape.
+ *
+ * The x- and y-coordinates are typically between 0 and 1 and define the
+ * location of the connection point relative to the width and height of the
+ * shape.
+ *
+ * The dashpattern directive sets the current dashpattern. The format for the
+ * pattern attribute is a space-separated sequence of numbers, eg. 5 5 5 5,
+ * that specifies the lengths of alternating dashes and spaces in dashed lines.
+ * The dashpattern should be used together with the dashed directive to
+ * enabled/disable the dashpattern. The default dashpattern is 3 3.
+ *
+ * The strokewidth attribute defines a fixed strokewidth for the shape. It
+ * can contain a numeric value or the keyword "inherit", which means that the
+ * strokeWidth from the cell's style will be used and muliplied with the shape's
+ * scale. If numeric values are used, those are multiplied with the minimum
+ * scale used to render the stencil inside the shape's bounds.
+ *
+ * Constructor: mxStencilShape
+ *
+ * Constructs a new generic shape by setting <desc> to the given XML node and
+ * invoking <parseDescription> and <parseConstraints>.
+ *
+ * Parameters:
+ *
+ * desc - XML node that contains the stencil description.
+ */
+function mxStencil(desc)
+{
+ this.desc = desc;
+ this.parseDescription();
+ this.parseConstraints();
+};
+
+/**
+ * Variable: desc
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.desc = null;
+
+/**
+ * Variable: constraints
+ *
+ * Holds an array of <mxConnectionConstraints> as defined in the shape.
+ */
+mxStencil.prototype.constraints = null;
+
+/**
+ * Variable: aspect
+ *
+ * Holds the aspect of the shape. Default is 'auto'.
+ */
+mxStencil.prototype.aspect = null;
+
+/**
+ * Variable: w0
+ *
+ * Holds the width of the shape. Default is 100.
+ */
+mxStencil.prototype.w0 = null;
+
+/**
+ * Variable: h0
+ *
+ * Holds the height of the shape. Default is 100.
+ */
+mxStencil.prototype.h0 = null;
+
+/**
+ * Variable: bgNodes
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.bgNode = null;
+
+/**
+ * Variable: fgNodes
+ *
+ * Holds the XML node with the stencil description.
+ */
+mxStencil.prototype.fgNode = null;
+
+/**
+ * Variable: strokewidth
+ *
+ * Holds the strokewidth direction from the description.
+ */
+mxStencil.prototype.strokewidth = null;
+
+/**
+ * Function: parseDescription
+ *
+ * Reads <w0>, <h0>, <aspect>, <bgNodes> and <fgNodes> from <desc>.
+ */
+mxStencil.prototype.parseDescription = function()
+{
+ // LATER: Preprocess nodes for faster painting
+ this.fgNode = this.desc.getElementsByTagName('foreground')[0];
+ this.bgNode = this.desc.getElementsByTagName('background')[0];
+ this.w0 = Number(this.desc.getAttribute('w') || 100);
+ this.h0 = Number(this.desc.getAttribute('h') || 100);
+
+ // Possible values for aspect are: variable and fixed where
+ // variable means fill the available space and fixed means
+ // use w0 and h0 to compute the aspect.
+ var aspect = this.desc.getAttribute('aspect');
+ this.aspect = (aspect != null) ? aspect : 'variable';
+
+ // Possible values for strokewidth are all numbers and "inherit"
+ // where the inherit means take the value from the style (ie. the
+ // user-defined stroke-width). Note that the strokewidth is scaled
+ // by the minimum scaling that is used to draw the shape (sx, sy).
+ var sw = this.desc.getAttribute('strokewidth');
+ this.strokewidth = (sw != null) ? sw : '1';
+};
+
+/**
+ * Function: parseConstraints
+ *
+ * Reads the constraints from <desc> into <constraints> using
+ * <parseConstraint>.
+ */
+mxStencil.prototype.parseConstraints = function()
+{
+ var conns = this.desc.getElementsByTagName('connections')[0];
+
+ if (conns != null)
+ {
+ var tmp = mxUtils.getChildNodes(conns);
+
+ if (tmp != null && tmp.length > 0)
+ {
+ this.constraints = [];
+
+ for (var i = 0; i < tmp.length; i++)
+ {
+ this.constraints.push(this.parseConstraint(tmp[i]));
+ }
+ }
+ }
+};
+
+/**
+ * Function: parseConstraint
+ *
+ * Parses the given XML node and returns its <mxConnectionConstraint>.
+ */
+mxStencil.prototype.parseConstraint = function(node)
+{
+ var x = Number(node.getAttribute('x'));
+ var y = Number(node.getAttribute('y'));
+ var perimeter = node.getAttribute('perimeter') == '1';
+
+ return new mxConnectionConstraint(new mxPoint(x, y), perimeter);
+};
+
+/**
+ * Function: evaluateAttribute
+ *
+ * Gets the attribute for the given name from the given node. If the attribute
+ * does not exist then the text content of the node is evaluated and if it is
+ * a function it is invoked with <state> as the only argument and the return
+ * value is used as the attribute value to be returned.
+ */
+mxStencil.prototype.evaluateAttribute = function(node, attribute, state)
+{
+ var result = node.getAttribute(attribute);
+
+ if (result == null)
+ {
+ var text = mxUtils.getTextContent(node);
+
+ if (text != null)
+ {
+ var funct = mxUtils.eval(text);
+
+ if (typeof(funct) == 'function')
+ {
+ result = funct(state);
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: renderDom
+ *
+ * Updates the SVG or VML shape.
+ */
+mxStencil.prototype.renderDom = function(shape, bounds, parentNode, state)
+{
+ var vml = shape.dialect != mxConstants.DIALECT_SVG;
+ var vmlScale = (document.documentMode == 8) ? 1 : shape.vmlScale;
+ var rotation = shape.rotation || 0;
+ var inverse = false;
+
+ // New styles for shape flipping the stencil
+ var flipH = shape.style[mxConstants.STYLE_STENCIL_FLIPH];
+ var flipV = shape.style[mxConstants.STYLE_STENCIL_FLIPV];
+
+ if (flipH ? !flipV : flipV)
+ {
+ rotation *= -1;
+ }
+
+ // Default direction is east (ignored if rotation exists)
+ if (shape.direction != null)
+ {
+ if (shape.direction == 'north')
+ {
+ rotation += 270;
+ }
+ else if (shape.direction == 'west')
+ {
+ rotation += 180;
+ }
+ else if (shape.direction == 'south')
+ {
+ rotation += 90;
+ }
+
+ inverse = (shape.direction == 'north' || shape.direction == 'south');
+ }
+
+ if (flipH && flipV)
+ {
+ rotation += 180;
+ flipH = false;
+ flipV = false;
+ }
+
+ // SVG transform should be applied on all child shapes
+ var svgTransform = '';
+
+ // Implements direction style and vertical/horizontal flip
+ // via container transformation.
+ if (vml)
+ {
+ if (flipH)
+ {
+ parentNode.style.flip = 'x';
+ }
+ else if (flipV)
+ {
+ parentNode.style.flip = 'y';
+ }
+
+ if (rotation != 0)
+ {
+ parentNode.style.rotation = rotation;
+ }
+ }
+ else
+ {
+ if (flipH || flipV)
+ {
+ var sx = 1;
+ var sy = 1;
+ var dx = 0;
+ var dy = 0;
+
+ if (flipH)
+ {
+ sx = -1;
+ dx = -bounds.width - 2 * bounds.x;
+ }
+
+ if (flipV)
+ {
+ sy = -1;
+ dy = -bounds.height - 2 * bounds.y;
+ }
+
+ svgTransform = 'scale(' + sx + ' ' + sy + ') translate(' + dx + ' ' + dy + ')';
+ }
+
+ // Adds rotation as a separate transform
+ if (rotation != 0)
+ {
+ var cx = bounds.getCenterX();
+ var cy = bounds.getCenterY();
+ svgTransform += ' rotate(' + rotation + ' ' + cx + ' ' + cy + ')';
+ }
+ }
+
+ var background = (state == null);
+
+ if (this.bgNode != null || this.fgNode != null)
+ {
+ var x0 = (vml && state == null) ? 0 : bounds.x;
+ var y0 = (vml && state == null) ? 0 : bounds.y;
+ var sx = bounds.width / this.w0;
+ var sy = bounds.height / this.h0;
+
+ // Stores current location inside path
+ this.lastMoveX = 0;
+ this.lastMoveY = 0;
+
+ if (inverse)
+ {
+ sy = bounds.width / this.h0;
+ sx = bounds.height / this.w0;
+
+ var delta = (bounds.width - bounds.height) / 2;
+
+ x0 += delta;
+ y0 -= delta;
+ }
+
+ if (this.aspect == 'fixed')
+ {
+ sy = Math.min(sx, sy);
+ sx = sy;
+
+ // Centers the shape inside the available space
+ if (inverse)
+ {
+ x0 += (bounds.height - this.w0 * sx) / 2;
+ y0 += (bounds.width - this.h0 * sy) / 2;
+ }
+ else
+ {
+ x0 += (bounds.width - this.w0 * sx) / 2;
+ y0 += (bounds.height - this.h0 * sy) / 2;
+ }
+ }
+
+ // Workaround to improve VML rendering precision.
+ if (vml)
+ {
+ sx *= vmlScale;
+ sy *= vmlScale;
+ x0 *= vmlScale;
+ y0 *= vmlScale;
+ }
+
+ var minScale = Math.min(sx, sy);
+
+ // Stack of states for save/restore ops
+ var stack = [];
+
+ var currentState = (state != null) ? state :
+ {
+ fillColorAssigned: false,
+ fill: shape.fill,
+ stroke: shape.stroke,
+ strokeWidth: (this.strokewidth == 'inherit') ?
+ Number(shape.strokewidth) * shape.scale :
+ Number(this.strokewidth) * minScale / ((vml) ? vmlScale : 1),
+ dashed: shape.isDashed,
+ dashpattern: [3, 3],
+ alpha: shape.opacity,
+ linejoin: 'miter',
+ fontColor: '#000000',
+ fontSize: mxConstants.DEFAULT_FONTSIZE,
+ fontFamily: mxConstants.DEFAULT_FONTFAMILY,
+ fontStyle: 0
+ };
+
+ var currentPath = null;
+ var currentPoints = null;
+
+ var configurePath = function(path, state)
+ {
+ var sw = Math.max(1, state.strokeWidth);
+
+ if (vml)
+ {
+ path.strokeweight = Math.round(sw) + 'px';
+
+ if (state.fill != null)
+ {
+ // Gradient in foregrounds not supported because special gradients
+ // with bounds must be created for each element in graphics-canvases
+ var gradient = (!state.fillColorAssigned) ? shape.gradient : null;
+ var fill = document.createElement('v:fill');
+ shape.updateVmlFill(fill, state.fill, gradient, shape.gradientDirection, state.alpha);
+ path.appendChild(fill);
+ }
+ else
+ {
+ path.filled = 'false';
+ }
+
+ if (state.stroke != null)
+ {
+ path.stroked = 'true';
+ path.strokecolor = state.stroke;
+ }
+ else
+ {
+ path.stroked = 'false';
+ }
+
+ path.style.position = 'absolute';
+ }
+ else
+ {
+ path.setAttribute('stroke-width', sw);
+
+ if (state.fill != null && state.fillColorAssigned)
+ {
+ path.setAttribute('fill', state.fill);
+ }
+
+ if (state.stroke != null)
+ {
+ path.setAttribute('stroke', state.stroke);
+ }
+ }
+ };
+
+ var addToPath = function(s)
+ {
+ if (currentPath != null && currentPoints != null)
+ {
+ currentPoints.push(s);
+ }
+ };
+
+ var round = function(value)
+ {
+ return (vml) ? Math.round(value) : value;
+ };
+
+ // Will be moved to a hook later for example to set text values
+ var renderNode = function(node)
+ {
+ var name = node.nodeName;
+
+ var fillOp = name == 'fill';
+ var strokeOp = name == 'stroke';
+ var fillStrokeOp = name == 'fillstroke';
+
+ if (name == 'save')
+ {
+ stack.push(currentState);
+ currentState = mxUtils.clone(currentState);
+ }
+ else if (name == 'restore')
+ {
+ currentState = stack.pop();
+ }
+ else if (name == 'path')
+ {
+ currentPoints = [];
+
+ if (vml)
+ {
+ currentPath = document.createElement('v:shape');
+ configurePath.call(this, currentPath, currentState);
+ var w = Math.round(bounds.width) * vmlScale;
+ var h = Math.round(bounds.height) * vmlScale;
+ currentPath.style.width = w + 'px';
+ currentPath.style.height = h + 'px';
+ currentPath.coordsize = w + ',' + h;
+ }
+ else
+ {
+ currentPath = document.createElementNS(mxConstants.NS_SVG, 'path');
+ configurePath.call(this, currentPath, currentState);
+
+ if (svgTransform.length > 0)
+ {
+ currentPath.setAttribute('transform', svgTransform);
+ }
+
+ if (node.getAttribute('crisp') == '1')
+ {
+ currentPath.setAttribute('shape-rendering', 'crispEdges');
+ }
+ }
+
+ // Renders the elements inside the given path
+ var childNode = node.firstChild;
+
+ while (childNode != null)
+ {
+ if (childNode.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ renderNode.call(this, childNode);
+ }
+
+ childNode = childNode.nextSibling;
+ }
+
+ // Ends the current path
+ if (vml)
+ {
+ addToPath('e');
+ currentPath.path = currentPoints.join('');
+ }
+ else
+ {
+ currentPath.setAttribute('d', currentPoints.join(''));
+ }
+ }
+ else if (name == 'move')
+ {
+ var op = (vml) ? 'm' : 'M';
+ this.lastMoveX = round(x0 + Number(node.getAttribute('x')) * sx);
+ this.lastMoveY = round(y0 + Number(node.getAttribute('y')) * sy);
+ addToPath(op + ' ' + this.lastMoveX + ' ' + this.lastMoveY);
+ }
+ else if (name == 'line')
+ {
+ var op = (vml) ? 'l' : 'L';
+ this.lastMoveX = round(x0 + Number(node.getAttribute('x')) * sx);
+ this.lastMoveY = round(y0 + Number(node.getAttribute('y')) * sy);
+ addToPath(op + ' ' + this.lastMoveX + ' ' + this.lastMoveY);
+ }
+ else if (name == 'quad')
+ {
+ if (vml)
+ {
+ var cpx0 = this.lastMoveX;
+ var cpy0 = this.lastMoveY;
+ var qpx1 = x0 + Number(node.getAttribute('x1')) * sx;
+ var qpy1 = y0 + Number(node.getAttribute('y1')) * sy;
+ var cpx3 = x0 + Number(node.getAttribute('x2')) * sx;
+ var cpy3 = y0 + Number(node.getAttribute('y2')) * sy;
+
+ var cpx1 = cpx0 + 2/3 * (qpx1 - cpx0);
+ var cpy1 = cpy0 + 2/3 * (qpy1 - cpy0);
+
+ var cpx2 = cpx3 + 2/3 * (qpx1 - cpx3);
+ var cpy2 = cpy3 + 2/3 * (qpy1 - cpy3);
+
+ addToPath('c ' + Math.round(cpx1) + ' ' + Math.round(cpy1) + ' ' +
+ Math.round(cpx2) + ' ' + Math.round(cpy2) + ' ' +
+ Math.round(cpx3) + ' ' + Math.round(cpy3));
+
+ this.lastMoveX = cpx3;
+ this.lastMoveY = cpy3;
+ }
+ else
+ {
+ this.lastMoveX = x0 + Number(node.getAttribute('x2')) * sx;
+ this.lastMoveY = y0 + Number(node.getAttribute('y2')) * sy;
+
+ addToPath('Q ' + (x0 + Number(node.getAttribute('x1')) * sx) + ' ' +
+ (y0 + Number(node.getAttribute('y1')) * sy) + ' ' +
+ this.lastMoveX + ' ' + this.lastMoveY);
+ }
+ }
+ else if (name == 'curve')
+ {
+ var op = (vml) ? 'c' : 'C';
+ this.lastMoveX = round(x0 + Number(node.getAttribute('x3')) * sx);
+ this.lastMoveY = round(y0 + Number(node.getAttribute('y3')) * sy);
+
+ addToPath(op + ' ' + round(x0 + Number(node.getAttribute('x1')) * sx) + ' ' +
+ round(y0 + Number(node.getAttribute('y1')) * sy) + ' ' +
+ round(x0 + Number(node.getAttribute('x2')) * sx) + ' ' +
+ round(y0 + Number(node.getAttribute('y2')) * sy) + ' ' +
+ this.lastMoveX + ' ' + this.lastMoveY);
+ }
+ else if (name == 'close')
+ {
+ addToPath((vml) ? 'x' : 'Z');
+ }
+ else if (name == 'rect' || name == 'roundrect')
+ {
+ var rounded = name == 'roundrect';
+ var x = round(x0 + Number(node.getAttribute('x')) * sx);
+ var y = round(y0 + Number(node.getAttribute('y')) * sy);
+ var w = round(Number(node.getAttribute('w')) * sx);
+ var h = round(Number(node.getAttribute('h')) * sy);
+
+ var arcsize = node.getAttribute('arcsize');
+
+ if (arcsize == 0)
+ {
+ arcsize = mxConstants.RECTANGLE_ROUNDING_FACTOR * 100;
+ }
+
+ if (vml)
+ {
+ // LATER: Use HTML for non-rounded, gradientless rectangles
+ currentPath = document.createElement((rounded) ? 'v:roundrect' : 'v:rect');
+ currentPath.style.left = x + 'px';
+ currentPath.style.top = y + 'px';
+ currentPath.style.width = w + 'px';
+ currentPath.style.height = h + 'px';
+
+ if (rounded)
+ {
+ currentPath.setAttribute('arcsize', String(arcsize) + '%');
+ }
+ }
+ else
+ {
+ currentPath = document.createElementNS(mxConstants.NS_SVG, 'rect');
+ currentPath.setAttribute('x', x);
+ currentPath.setAttribute('y', y);
+ currentPath.setAttribute('width', w);
+ currentPath.setAttribute('height', h);
+
+ if (rounded)
+ {
+ var factor = Number(arcsize) / 100;
+ var r = Math.min(w * factor, h * factor);
+ currentPath.setAttribute('rx', r);
+ currentPath.setAttribute('ry', r);
+ }
+
+ if (svgTransform.length > 0)
+ {
+ currentPath.setAttribute('transform', svgTransform);
+ }
+
+ if (node.getAttribute('crisp') == '1')
+ {
+ currentPath.setAttribute('shape-rendering', 'crispEdges');
+ }
+ }
+
+ configurePath.call(this, currentPath, currentState);
+ }
+ else if (name == 'ellipse')
+ {
+ var x = round(x0 + Number(node.getAttribute('x')) * sx);
+ var y = round(y0 + Number(node.getAttribute('y')) * sy);
+ var w = round(Number(node.getAttribute('w')) * sx);
+ var h = round(Number(node.getAttribute('h')) * sy);
+
+ if (vml)
+ {
+ currentPath = document.createElement('v:arc');
+ currentPath.startangle = '0';
+ currentPath.endangle = '360';
+ currentPath.style.left = x + 'px';
+ currentPath.style.top = y + 'px';
+ currentPath.style.width = w + 'px';
+ currentPath.style.height = h + 'px';
+ }
+ else
+ {
+ currentPath = document.createElementNS(mxConstants.NS_SVG, 'ellipse');
+ currentPath.setAttribute('cx', x + w / 2);
+ currentPath.setAttribute('cy', y + h / 2);
+ currentPath.setAttribute('rx', w / 2);
+ currentPath.setAttribute('ry', h / 2);
+
+ if (svgTransform.length > 0)
+ {
+ currentPath.setAttribute('transform', svgTransform);
+ }
+ }
+
+ configurePath.call(this, currentPath, currentState);
+ }
+ else if (name == 'arc')
+ {
+ var r1 = Number(node.getAttribute('rx')) * sx;
+ var r2 = Number(node.getAttribute('ry')) * sy;
+ var angle = Number(node.getAttribute('x-axis-rotation'));
+ var largeArcFlag = Number(node.getAttribute('large-arc-flag'));
+ var sweepFlag = Number(node.getAttribute('sweep-flag'));
+ var x = x0 + Number(node.getAttribute('x')) * sx;
+ var y = y0 + Number(node.getAttribute('y')) * sy;
+
+ if (vml)
+ {
+ var curves = mxUtils.arcToCurves(this.lastMoveX, this.lastMoveY, r1, r2, angle, largeArcFlag, sweepFlag, x, y);
+
+ for (var i = 0; i < curves.length; i += 6)
+ {
+ addToPath('c' + ' ' + Math.round(curves[i]) + ' ' + Math.round(curves[i + 1]) + ' ' +
+ Math.round(curves[i + 2]) + ' ' + Math.round(curves[i + 3]) + ' ' +
+ Math.round(curves[i + 4]) + ' ' + Math.round(curves[i + 5]));
+
+ this.lastMoveX = curves[i + 4];
+ this.lastMoveY = curves[i + 5];
+ }
+ }
+ else
+ {
+ addToPath('A ' + r1 + ',' + r2 + ' ' + angle + ' ' + largeArcFlag + ',' + sweepFlag + ' ' + x + ',' + y);
+ this.lastMoveX = x0 + x;
+ this.lastMoveY = y0 + y;
+ }
+ }
+ else if (name == 'image')
+ {
+ var src = this.evaluateAttribute(node, 'src', shape.state);
+
+ if (src != null)
+ {
+ var x = round(x0 + Number(node.getAttribute('x')) * sx);
+ var y = round(y0 + Number(node.getAttribute('y')) * sy);
+ var w = round(Number(node.getAttribute('w')) * sx);
+ var h = round(Number(node.getAttribute('h')) * sy);
+
+ // TODO: _Not_ providing an aspect in the shapes format has the advantage
+ // of not needing a callback to adjust the image in VML. Since the shape
+ // developer can specify the aspect via width and height this should OK.
+ //var aspect = node.getAttribute('aspect') != '0';
+ var aspect = false;
+ var flipH = node.getAttribute('flipH') == '1';
+ var flipV = node.getAttribute('flipV') == '1';
+
+ if (vml)
+ {
+ currentPath = document.createElement('v:image');
+ currentPath.style.filter = 'alpha(opacity=' + currentState.alpha + ')';
+ currentPath.style.left = x + 'px';
+ currentPath.style.top = y + 'px';
+ currentPath.style.width = w + 'px';
+ currentPath.style.height = h + 'px';
+ currentPath.src = src;
+
+ if (flipH && flipV)
+ {
+ currentPath.style.rotation = '180';
+ }
+ else if (flipH)
+ {
+ currentPath.style.flip = 'x';
+ }
+ else if (flipV)
+ {
+ currentPath.style.flip = 'y';
+ }
+ }
+ else
+ {
+ currentPath = document.createElementNS(mxConstants.NS_SVG, 'image');
+ currentPath.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src);
+ currentPath.setAttribute('opacity', currentState.alpha / 100);
+ currentPath.setAttribute('x', x);
+ currentPath.setAttribute('y', y);
+ currentPath.setAttribute('width', w);
+ currentPath.setAttribute('height', h);
+
+ if (!aspect)
+ {
+ currentPath.setAttribute('preserveAspectRatio', 'none');
+ }
+
+ if (flipH || flipV)
+ {
+ var scx = 1;
+ var scy = 1;
+ var dx = 0;
+ var dy = 0;
+
+ if (flipH)
+ {
+ scx = -1;
+ dx = -w - 2 * x;
+ }
+
+ if (flipV)
+ {
+ scy = -1;
+ dy = -h - 2 * y;
+ }
+
+ currentPath.setAttribute('transform', svgTransform + 'scale(' + scx + ' ' + scy + ')' +
+ ' translate('+dx+' '+dy+') ');
+ }
+ else
+ {
+ currentPath.setAttribute('transform', svgTransform);
+ }
+ }
+
+ parentNode.appendChild(currentPath);
+ }
+ }
+ else if (name == 'include-shape')
+ {
+ var stencil = mxStencilRegistry.getStencil(node.getAttribute('name'));
+
+ if (stencil != null)
+ {
+ var x = x0 + Number(node.getAttribute('x')) * sx;
+ var y = y0 + Number(node.getAttribute('y')) * sy;
+ var w = Number(node.getAttribute('w')) * sx;
+ var h = Number(node.getAttribute('h')) * sy;
+
+ stencil.renderDom(shape, new mxRectangle(x, y, w, h), parentNode, currentState);
+ }
+ }
+ // Additional labels are currently disabled. Needs fixing of VML
+ // text positon, SVG text rotation and ignored baseline in FF
+ else if (name == 'text')
+ {
+ var str = this.evaluateAttribute(node, 'str', shape.state);
+
+ if (str != null)
+ {
+ var x = round(x0 + Number(node.getAttribute('x')) * sx);
+ var y = round(y0 + Number(node.getAttribute('y')) * sy);
+ var align = node.getAttribute('align') || 'left';
+ var valign = node.getAttribute('valign') || 'top';
+
+ if (vml)
+ {
+ // Renders a single line of text with full rotation support
+ currentPath = document.createElement('v:shape');
+ currentPath.style.position = 'absolute';
+ currentPath.style.width = '1px';
+ currentPath.style.height = '1px';
+ currentPath.style.left = x + 'px';
+ currentPath.style.top = y + 'px';
+
+ var fill = document.createElement('v:fill');
+ fill.color = currentState.fontColor;
+ fill.on = 'true';
+ currentPath.appendChild(fill);
+
+ var stroke = document.createElement('v:stroke');
+ stroke.on = 'false';
+ currentPath.appendChild(stroke);
+
+ var path = document.createElement('v:path');
+ path.textpathok = 'true';
+ path.v = 'm ' + x + ' ' + y + ' l ' + (x + 1) + ' ' + y;
+
+ currentPath.appendChild(path);
+
+ var tp = document.createElement('v:textpath');
+ tp.style.cssText = 'v-text-align:' + align;
+ tp.style.fontSize = Math.round(currentState.fontSize / vmlScale) + 'px';
+
+ // FIXME: Font-family seems to be ignored for textpath
+ tp.style.fontFamily = currentState.fontFamily;
+ tp.string = str;
+ tp.on = 'true';
+
+ // Bold
+ if ((currentState.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+ {
+ tp.style.fontWeight = 'bold';
+ }
+
+ // Italic
+ if ((currentState.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+ {
+ tp.style.fontStyle = 'italic';
+ }
+
+ // FIXME: Text decoration not supported in textpath
+ if ((currentState.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+ {
+ tp.style.textDecoration = 'underline';
+ }
+
+ // LATER: Find vertical center for div via CSS if possible
+ if (valign == 'top')
+ {
+ currentPath.style.top = (y + currentState.fontSize / 2) + 'px';
+ }
+ else if (valign == 'bottom')
+ {
+ currentPath.style.top = (y - currentState.fontSize / 3) + 'px';
+ }
+
+ currentPath.appendChild(tp);
+ }
+ else
+ {
+ currentPath = document.createElementNS(mxConstants.NS_SVG, 'text');
+ currentPath.setAttribute('fill', currentState.fontColor);
+ currentPath.setAttribute('font-family', currentState.fontFamily);
+ currentPath.setAttribute('font-size', currentState.fontSize);
+ currentPath.setAttribute('stroke', 'none');
+ currentPath.setAttribute('x', x);
+ currentPath.appendChild(document.createTextNode(str));
+
+ // Bold
+ if ((currentState.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+ {
+ currentPath.setAttribute('font-weight', 'bold');
+ }
+
+ // Italic
+ if ((currentState.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+ {
+ currentPath.setAttribute('font-style', 'italic');
+ }
+
+ // Underline
+ if ((currentState.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+ {
+ currentPath.setAttribute('text-decoration', uline);
+ }
+
+ // Horizontal alignment
+ if (align == 'left')
+ {
+ align = 'start';
+ }
+ else if (align == 'center')
+ {
+ align = 'middle';
+ }
+ else if (align == 'right')
+ {
+ align = 'end';
+ }
+
+ currentPath.setAttribute('text-anchor', align);
+
+ // Vertical alignment
+ // Uses dy because FF ignores alignment-baseline
+ if (valign == 'top')
+ {
+ currentPath.setAttribute('y', y + currentState.fontSize / 5);
+ currentPath.setAttribute('dy', '1ex');
+ }
+ else if (valign == 'middle')
+ {
+ currentPath.setAttribute('y', y + currentState.fontSize / 8);
+ currentPath.setAttribute('dy', '0.5ex');
+ }
+ else
+ {
+ currentPath.setAttribute('y', y);
+ }
+
+ if (svgTransform.length > 0)
+ {
+ currentPath.setAttribute('transform', svgTransform);
+ }
+ }
+
+ parentNode.appendChild(currentPath);
+ }
+ }
+ else if (fillOp || strokeOp || fillStrokeOp)
+ {
+ if (currentPath != null)
+ {
+ var pattern = null;
+
+ if (currentState.dashed)
+ {
+ var f = (vml) ? minScale : Number(currentPath.getAttribute('stroke-width'));
+ var pat = [];
+
+ for (var i = 0; i < currentState.dashpattern.length; i++)
+ {
+ pat.push(Math.max(1, Math.round(Number(currentState.dashpattern[i]) * f)));
+ }
+
+ pattern = pat.join(' ');
+ }
+
+ if (strokeOp || fillStrokeOp)
+ {
+ if (vml)
+ {
+ var stroke = document.createElement('v:stroke');
+ stroke.endcap = currentState.linecap || 'flat';
+ stroke.joinstyle = currentState.linejoin || 'miter';
+ stroke.miterlimit = currentState.miterlimit || '10';
+ currentPath.appendChild(stroke);
+
+ // TODO: Dashpattern support in VML is limited, we should
+ // map this to VML or allow for a separate VML dashstyle.
+ if (pattern != null)
+ {
+ stroke.dashstyle = pattern;
+ }
+ }
+ else
+ {
+ if (currentState.linejoin != null)
+ {
+ currentPath.setAttribute('stroke-linejoin', currentState.linejoin);
+ }
+
+ if (currentState.linecap != null)
+ {
+ // flat is called butt in SVG
+ var value = currentState.linecap;
+
+ if (value == 'flat')
+ {
+ value = 'butt';
+ }
+
+ currentPath.setAttribute('stroke-linecap', value);
+ }
+
+ if (currentState.miterlimit != null)
+ {
+ currentPath.setAttribute('stroke-miterlimit', currentState.miterlimit);
+ }
+
+ // Handles dash pattern
+ if (pattern != null)
+ {
+ currentPath.setAttribute('stroke-dasharray', pattern);
+ }
+ }
+ }
+
+ // Adds the shadow
+ if (background && shape.isShadow)
+ {
+ var dx = mxConstants.SHADOW_OFFSET_X * shape.scale;
+ var dy = mxConstants.SHADOW_OFFSET_Y * shape.scale;
+
+ // Adds the shadow
+ if (vml)
+ {
+ var shadow = document.createElement('v:shadow');
+ shadow.setAttribute('on', 'true');
+ shadow.setAttribute('color', mxConstants.SHADOWCOLOR);
+ shadow.setAttribute('offset', Math.round(dx) + 'px,' + Math.round(dy) + 'px');
+ shadow.setAttribute('opacity', (mxConstants.SHADOW_OPACITY * 100) + '%');
+
+ var stroke = document.createElement('v:stroke');
+ stroke.endcap = currentState.linecap || 'flat';
+ stroke.joinstyle = currentState.linejoin || 'miter';
+ stroke.miterlimit = currentState.miterlimit || '10';
+
+ if (pattern != null)
+ {
+ stroke.dashstyle = pattern;
+ }
+
+ shadow.appendChild(stroke);
+ currentPath.appendChild(shadow);
+ }
+ else
+ {
+ var shadow = currentPath.cloneNode(true);
+ shadow.setAttribute('stroke', mxConstants.SHADOWCOLOR);
+
+ if (currentState.fill != null && (fillOp || fillStrokeOp))
+ {
+ shadow.setAttribute('fill', mxConstants.SHADOWCOLOR);
+ }
+ else
+ {
+ shadow.setAttribute('fill', 'none');
+ }
+
+ shadow.setAttribute('transform', 'translate(' + dx + ' ' + dy + ') ' +
+ (shadow.getAttribute('transform') || ''));
+ shadow.setAttribute('opacity', mxConstants.SHADOW_OPACITY);
+ parentNode.appendChild(shadow);
+ }
+ }
+
+ if (fillOp)
+ {
+ if (vml)
+ {
+ currentPath.stroked = 'false';
+ }
+ else
+ {
+ currentPath.setAttribute('stroke', 'none');
+ }
+ }
+ else if (strokeOp)
+ {
+ if (vml)
+ {
+ currentPath.filled = 'false';
+ }
+ else
+ {
+ currentPath.setAttribute('fill', 'none');
+ }
+ }
+
+ parentNode.appendChild(currentPath);
+ }
+
+ // Background was painted
+ if (background)
+ {
+ background = false;
+ }
+ }
+ else if (name == 'linecap')
+ {
+ currentState.linecap = node.getAttribute('cap');
+ }
+ else if (name == 'linejoin')
+ {
+ currentState.linejoin = node.getAttribute('join');
+ }
+ else if (name == 'miterlimit')
+ {
+ currentState.miterlimit = node.getAttribute('limit');
+ }
+ else if (name == 'dashed')
+ {
+ currentState.dashed = node.getAttribute('dashed') == '1';
+ }
+ else if (name == 'dashpattern')
+ {
+ var value = node.getAttribute('pattern');
+
+ if (value != null && value.length > 0)
+ {
+ currentState.dashpattern = value.split(' ');
+ }
+ }
+ else if (name == 'strokewidth')
+ {
+ currentState.strokeWidth = node.getAttribute('width') * minScale;
+
+ if (vml)
+ {
+ currentState.strokeWidth /= vmlScale;
+ }
+ }
+ else if (name == 'strokecolor')
+ {
+ currentState.stroke = node.getAttribute('color');
+ }
+ else if (name == 'fillcolor')
+ {
+ currentState.fill = node.getAttribute('color');
+ currentState.fillColorAssigned = true;
+ }
+ else if (name == 'alpha')
+ {
+ currentState.alpha = Number(node.getAttribute('alpha'));
+ }
+ else if (name == 'fontcolor')
+ {
+ currentState.fontColor = node.getAttribute('color');
+ }
+ else if (name == 'fontsize')
+ {
+ currentState.fontSize = Number(node.getAttribute('size')) * minScale;
+ }
+ else if (name == 'fontfamily')
+ {
+ currentState.fontFamily = node.getAttribute('family');
+ }
+ else if (name == 'fontstyle')
+ {
+ currentState.fontStyle = Number(node.getAttribute('style'));
+ }
+ };
+
+ // Adds a transparent rectangle in the background for hit-detection in SVG
+ if (!vml)
+ {
+ var rect = document.createElementNS(mxConstants.NS_SVG, 'rect');
+ rect.setAttribute('x', bounds.x);
+ rect.setAttribute('y', bounds.y);
+ rect.setAttribute('width', bounds.width);
+ rect.setAttribute('height', bounds.height);
+ rect.setAttribute('fill', 'none');
+ rect.setAttribute('stroke', 'none');
+ parentNode.appendChild(rect);
+ }
+
+ // Background switches to false after fill/stroke of the background
+ if (this.bgNode != null)
+ {
+ var tmp = this.bgNode.firstChild;
+
+ while (tmp != null)
+ {
+ if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ renderNode.call(this, tmp);
+ }
+
+ tmp = tmp.nextSibling;
+ }
+ }
+ else
+ {
+ background = false;
+ }
+
+ if (this.fgNode != null)
+ {
+ var tmp = this.fgNode.firstChild;
+
+ while (tmp != null)
+ {
+ if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ renderNode.call(this, tmp);
+ }
+
+ tmp = tmp.nextSibling;
+ }
+ }
+ }
+};
+
+/**
+ * Function: drawShape
+ *
+ * Draws this stencil inside the given bounds.
+ */
+mxStencil.prototype.drawShape = function(canvas, state, bounds, background)
+{
+ // TODO: Unify with renderDom, check performance of pluggable shape,
+ // internal structure (array of special structs?), relative and absolute
+ // coordinates (eg. note shape, process vs star, actor etc.), text rendering
+ // and non-proportional scaling, how to implement pluggable edge shapes
+ // (start, segment, end blocks), pluggable markers, how to implement
+ // swimlanes (title area) with this API, add icon, horizontal/vertical
+ // label, indicator for all shapes, rotation
+ var node = (background) ? this.bgNode : this.fgNode;
+
+ if (node != null)
+ {
+ var direction = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION, null);
+ var aspect = this.computeAspect(state, bounds, direction);
+ var minScale = Math.min(aspect.width, aspect.height);
+ var sw = (this.strokewidth == 'inherit') ?
+ Number(mxUtils.getNumber(state.style, mxConstants.STYLE_STROKEWIDTH, 1)) * state.view.scale :
+ Number(this.strokewidth) * minScale;
+ this.lastMoveX = 0;
+ this.lastMoveY = 0;
+ canvas.setStrokeWidth(sw);
+
+ var tmp = node.firstChild;
+
+ while (tmp != null)
+ {
+ if (tmp.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ this.drawNode(canvas, state, tmp, aspect);
+ }
+
+ tmp = tmp.nextSibling;
+ }
+
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * Function: computeAspect
+ *
+ * Returns a rectangle that contains the offset in x and y and the horizontal
+ * and vertical scale in width and height used to draw this shape inside the
+ * given <mxRectangle>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be drawn.
+ * bounds - <mxRectangle> that should contain the stencil.
+ * direction - Optional direction of the shape to be darwn.
+ */
+mxStencil.prototype.computeAspect = function(state, bounds, direction)
+{
+ var x0 = bounds.x;
+ var y0 = bounds.y;
+ var sx = bounds.width / this.w0;
+ var sy = bounds.height / this.h0;
+
+ var inverse = (direction == 'north' || direction == 'south');
+
+ if (inverse)
+ {
+ sy = bounds.width / this.h0;
+ sx = bounds.height / this.w0;
+
+ var delta = (bounds.width - bounds.height) / 2;
+
+ x0 += delta;
+ y0 -= delta;
+ }
+
+ if (this.aspect == 'fixed')
+ {
+ sy = Math.min(sx, sy);
+ sx = sy;
+
+ // Centers the shape inside the available space
+ if (inverse)
+ {
+ x0 += (bounds.height - this.w0 * sx) / 2;
+ y0 += (bounds.width - this.h0 * sy) / 2;
+ }
+ else
+ {
+ x0 += (bounds.width - this.w0 * sx) / 2;
+ y0 += (bounds.height - this.h0 * sy) / 2;
+ }
+ }
+
+ return new mxRectangle(x0, y0, sx, sy);
+};
+
+/**
+ * Function: drawNode
+ *
+ * Draws this stencil inside the given bounds.
+ */
+mxStencil.prototype.drawNode = function(canvas, state, node, aspect)
+{
+ var name = node.nodeName;
+ var x0 = aspect.x;
+ var y0 = aspect.y;
+ var sx = aspect.width;
+ var sy = aspect.height;
+ var minScale = Math.min(sx, sy);
+
+ // LATER: Move to lookup table
+ if (name == 'save')
+ {
+ canvas.save();
+ }
+ else if (name == 'restore')
+ {
+ canvas.restore();
+ }
+ else if (name == 'path')
+ {
+ canvas.begin();
+
+ // Renders the elements inside the given path
+ var childNode = node.firstChild;
+
+ while (childNode != null)
+ {
+ if (childNode.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ this.drawNode(canvas, state, childNode, aspect);
+ }
+
+ childNode = childNode.nextSibling;
+ }
+ }
+ else if (name == 'close')
+ {
+ canvas.close();
+ }
+ else if (name == 'move')
+ {
+ this.lastMoveX = x0 + Number(node.getAttribute('x')) * sx;
+ this.lastMoveY = y0 + Number(node.getAttribute('y')) * sy;
+ canvas.moveTo(this.lastMoveX, this.lastMoveY);
+ }
+ else if (name == 'line')
+ {
+ this.lastMoveX = x0 + Number(node.getAttribute('x')) * sx;
+ this.lastMoveY = y0 + Number(node.getAttribute('y')) * sy;
+ canvas.lineTo(this.lastMoveX, this.lastMoveY);
+ }
+ else if (name == 'quad')
+ {
+ this.lastMoveX = x0 + Number(node.getAttribute('x2')) * sx;
+ this.lastMoveY = y0 + Number(node.getAttribute('y2')) * sy;
+ canvas.quadTo(x0 + Number(node.getAttribute('x1')) * sx,
+ y0 + Number(node.getAttribute('y1')) * sy,
+ this.lastMoveX, this.lastMoveY);
+ }
+ else if (name == 'curve')
+ {
+ this.lastMoveX = x0 + Number(node.getAttribute('x3')) * sx;
+ this.lastMoveY = y0 + Number(node.getAttribute('y3')) * sy;
+ canvas.curveTo(x0 + Number(node.getAttribute('x1')) * sx,
+ y0 + Number(node.getAttribute('y1')) * sy,
+ x0 + Number(node.getAttribute('x2')) * sx,
+ y0 + Number(node.getAttribute('y2')) * sy,
+ this.lastMoveX, this.lastMoveY);
+ }
+ else if (name == 'arc')
+ {
+ // Arc from stencil is turned into curves in image output
+ var r1 = Number(node.getAttribute('rx')) * sx;
+ var r2 = Number(node.getAttribute('ry')) * sy;
+ var angle = Number(node.getAttribute('x-axis-rotation'));
+ var largeArcFlag = Number(node.getAttribute('large-arc-flag'));
+ var sweepFlag = Number(node.getAttribute('sweep-flag'));
+ var x = x0 + Number(node.getAttribute('x')) * sx;
+ var y = y0 + Number(node.getAttribute('y')) * sy;
+
+ var curves = mxUtils.arcToCurves(this.lastMoveX, this.lastMoveY, r1, r2, angle, largeArcFlag, sweepFlag, x, y);
+
+ for (var i = 0; i < curves.length; i += 6)
+ {
+ canvas.curveTo(curves[i], curves[i + 1], curves[i + 2],
+ curves[i + 3], curves[i + 4], curves[i + 5]);
+
+ this.lastMoveX = curves[i + 4];
+ this.lastMoveY = curves[i + 5];
+ }
+ }
+ else if (name == 'rect')
+ {
+ canvas.rect(x0 + Number(node.getAttribute('x')) * sx,
+ y0 + Number(node.getAttribute('y')) * sy,
+ Number(node.getAttribute('w')) * sx,
+ Number(node.getAttribute('h')) * sy);
+ }
+ else if (name == 'roundrect')
+ {
+ var arcsize = node.getAttribute('arcsize');
+
+ if (arcsize == 0)
+ {
+ arcsize = mxConstants.RECTANGLE_ROUNDING_FACTOR * 100;
+ }
+
+ var w = Number(node.getAttribute('w')) * sx;
+ var h = Number(node.getAttribute('h')) * sy;
+ var factor = Number(arcsize) / 100;
+ var r = Math.min(w * factor, h * factor);
+
+ canvas.roundrect(x0 + Number(node.getAttribute('x')) * sx,
+ y0 + Number(node.getAttribute('y')) * sy,
+ w, h, r, r);
+ }
+ else if (name == 'ellipse')
+ {
+ canvas.ellipse(x0 + Number(node.getAttribute('x')) * sx,
+ y0 + Number(node.getAttribute('y')) * sy,
+ Number(node.getAttribute('w')) * sx,
+ Number(node.getAttribute('h')) * sy);
+ }
+ else if (name == 'image')
+ {
+ var src = this.evaluateAttribute(node, 'src', state);
+
+ canvas.image(x0 + Number(node.getAttribute('x')) * sx,
+ y0 + Number(node.getAttribute('y')) * sy,
+ Number(node.getAttribute('w')) * sx,
+ Number(node.getAttribute('h')) * sy,
+ src, false, node.getAttribute('flipH') == '1',
+ node.getAttribute('flipV') == '1');
+ }
+ else if (name == 'text')
+ {
+ var str = this.evaluateAttribute(node, 'str', state);
+
+ canvas.text(x0 + Number(node.getAttribute('x')) * sx,
+ y0 + Number(node.getAttribute('y')) * sy,
+ 0, 0, str, node.getAttribute('align'),
+ node.getAttribute('valign'),
+ node.getAttribute('vertical'));
+ }
+ else if (name == 'include-shape')
+ {
+ var stencil = mxStencilRegistry.getStencil(node.getAttribute('name'));
+
+ if (stencil != null)
+ {
+ var x = x0 + Number(node.getAttribute('x')) * sx;
+ var y = y0 + Number(node.getAttribute('y')) * sy;
+ var w = Number(node.getAttribute('w')) * sx;
+ var h = Number(node.getAttribute('h')) * sy;
+
+ var tmp = new mxRectangle(x, y, w, h);
+ stencil.drawShape(canvas, state, tmp, true);
+ stencil.drawShape(canvas, state, tmp, false);
+ }
+ }
+ else if (name == 'fillstroke')
+ {
+ canvas.fillAndStroke();
+ }
+ else if (name == 'fill')
+ {
+ canvas.fill();
+ }
+ else if (name == 'stroke')
+ {
+ canvas.stroke();
+ }
+ else if (name == 'strokewidth')
+ {
+ canvas.setStrokeWidth(Number(node.getAttribute('width')) * minScale);
+ }
+ else if (name == 'dashed')
+ {
+ canvas.setDashed(node.getAttribute('dashed') == '1');
+ }
+ else if (name == 'dashpattern')
+ {
+ var value = node.getAttribute('pattern');
+
+ if (value != null)
+ {
+ var tmp = value.split(' ');
+ var pat = [];
+
+ for (var i = 0; i < tmp.length; i++)
+ {
+ if (tmp[i].length > 0)
+ {
+ pat.push(Number(tmp[i]) * minScale);
+ }
+ }
+
+ value = pat.join(' ');
+ canvas.setDashPattern(value);
+ }
+ }
+ else if (name == 'strokecolor')
+ {
+ canvas.setStrokeColor(node.getAttribute('color'));
+ }
+ else if (name == 'linecap')
+ {
+ canvas.setLineCap(node.getAttribute('cap'));
+ }
+ else if (name == 'linejoin')
+ {
+ canvas.setLineJoin(node.getAttribute('join'));
+ }
+ else if (name == 'miterlimit')
+ {
+ canvas.setMiterLimit(Number(node.getAttribute('limit')));
+ }
+ else if (name == 'fillcolor')
+ {
+ canvas.setFillColor(node.getAttribute('color'));
+ }
+ else if (name == 'fontcolor')
+ {
+ canvas.setFontColor(node.getAttribute('color'));
+ }
+ else if (name == 'fontstyle')
+ {
+ canvas.setFontStyle(node.getAttribute('style'));
+ }
+ else if (name == 'fontfamily')
+ {
+ canvas.setFontFamily(node.getAttribute('family'));
+ }
+ else if (name == 'fontsize')
+ {
+ canvas.setFontSize(Number(node.getAttribute('size')) * minScale);
+ }
+};
diff --git a/src/js/shape/mxStencilRegistry.js b/src/js/shape/mxStencilRegistry.js
new file mode 100644
index 0000000..7621573
--- /dev/null
+++ b/src/js/shape/mxStencilRegistry.js
@@ -0,0 +1,53 @@
+/**
+ * $Id: mxStencilRegistry.js,v 1.2 2011-07-15 12:57:50 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ *
+ * Code to add stencils.
+ *
+ * (code)
+ * var req = mxUtils.load('test/stencils.xml');
+ * var root = req.getDocumentElement();
+ * var shape = root.firstChild;
+ *
+ * while (shape != null)
+ * {
+ * if (shape.nodeType == mxConstants.NODETYPE_ELEMENT)
+ * {
+ * mxStencilRegistry.addStencil(shape.getAttribute('name'), new mxStencil(shape));
+ * }
+ *
+ * shape = shape.nextSibling;
+ * }
+ * (end)
+ */
+var mxStencilRegistry =
+{
+ /**
+ * Class: mxStencilRegistry
+ *
+ * A singleton class that provides a registry for stencils and the methods
+ * for painting those stencils onto a canvas or into a DOM.
+ */
+ stencils: [],
+
+ /**
+ * Function: addStencil
+ *
+ * Adds the given <mxStencil>.
+ */
+ addStencil: function(name, stencil)
+ {
+ mxStencilRegistry.stencils[name] = stencil;
+ },
+
+ /**
+ * Function: getStencil
+ *
+ * Returns the <mxStencil> for the given name.
+ */
+ getStencil: function(name)
+ {
+ return mxStencilRegistry.stencils[name];
+ }
+
+};
diff --git a/src/js/shape/mxStencilShape.js b/src/js/shape/mxStencilShape.js
new file mode 100644
index 0000000..9c95f8b
--- /dev/null
+++ b/src/js/shape/mxStencilShape.js
@@ -0,0 +1,209 @@
+/**
+ * $Id: mxStencilShape.js,v 1.10 2012-07-16 10:22:44 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxStencilShape
+ *
+ * Implements a shape based on a <mxStencil>.
+ *
+ * Constructor: mxStencilShape
+ *
+ * Constructs a new generic shape.
+ */
+function mxStencilShape(stencil)
+{
+ this.stencil = stencil;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxStencilShape.prototype = new mxShape();
+mxStencilShape.prototype.constructor = mxStencilShape;
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Always prefers VML in mixed mode for stencil shapes. Default is false.
+ */
+mxStencilShape.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Always prefers VML in prefer HTML mode for stencil shapes. Default is false.
+ */
+mxStencilShape.prototype.preferModeHtml = false;
+
+/**
+ * Variable: stencil
+ *
+ * Holds the <mxStencil> that defines the shape.
+ */
+mxStencilShape.prototype.stencil = null;
+
+/**
+ * Variable: state
+ *
+ * Holds the <mxCellState> associated with this shape.
+ */
+mxStencilShape.prototype.state = null;
+
+/**
+ * Variable: vmlScale
+ *
+ * Renders VML with a scale of 4.
+ */
+mxStencilShape.prototype.vmlScale = 4;
+
+/**
+ * Function: apply
+ *
+ * Extends <mxShape> apply to keep a reference to the <mxCellState>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> of the corresponding cell.
+ */
+mxStencilShape.prototype.apply = function(state)
+{
+ this.state = state;
+ mxShape.prototype.apply.apply(this, arguments);
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxStencilShape.prototype.createSvg = function()
+{
+ var node = document.createElementNS(mxConstants.NS_SVG, 'g');
+ this.configureSvgShape(node);
+
+ return node;
+};
+
+/**
+ * Function: configureHtmlShape
+ *
+ * Overrides method to set the overflow style to visible.
+ */
+mxStencilShape.prototype.configureHtmlShape = function(node)
+{
+ mxShape.prototype.configureHtmlShape.apply(this, arguments);
+
+ if (!mxUtils.isVml(node))
+ {
+ node.style.overflow = 'visible';
+ }
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node to represent this shape.
+ */
+mxStencilShape.prototype.createVml = function()
+{
+ var name = (document.documentMode == 8) ? 'div' : 'v:group';
+ var node = document.createElement(name);
+ this.configureTransparentBackground(node);
+ node.style.position = 'absolute';
+
+ return node;
+};
+
+/**
+ * Function: configureVmlShape
+ *
+ * Configures the specified VML node by applying the current color,
+ * bounds, shadow, opacity etc.
+ */
+mxStencilShape.prototype.configureVmlShape = function(node)
+{
+ // do nothing
+};
+
+/**
+ * Function: redraw
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxStencilShape.prototype.redraw = function()
+{
+ this.updateBoundingBox();
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.redrawShape();
+ }
+ else
+ {
+ this.node.style.visibility = 'hidden';
+ this.redrawShape();
+ this.node.style.visibility = 'visible';
+ }
+};
+
+/**
+ * Function: redrawShape
+ *
+ * Updates the SVG or VML shape.
+ */
+mxStencilShape.prototype.redrawShape = function()
+{
+ // LATER: Update existing DOM nodes to improve repaint performance
+ if (this.dialect != mxConstants.DIALECT_SVG)
+ {
+ this.node.style.left = Math.round(this.bounds.x) + 'px';
+ this.node.style.top = Math.round(this.bounds.y) + 'px';
+ var w = Math.round(this.bounds.width);
+ var h = Math.round(this.bounds.height);
+ this.node.style.width = w + 'px';
+ this.node.style.height = h + 'px';
+
+ var node = this.node;
+
+ // Workaround for VML rendering bug in IE8 standards mode where all VML must be
+ // parsed via assigning the innerHTML of the parent HTML node to keep all event
+ // handlers referencing node and support rotation via v:group parent element.
+ if (this.node.nodeName == 'DIV')
+ {
+ node = document.createElement('v:group');
+ node.style.position = 'absolute';
+ node.style.left = '0px';
+ node.style.top = '0px';
+ node.style.width = w + 'px';
+ node.style.height = h + 'px';
+ }
+ else
+ {
+ node.innerHTML = '';
+ }
+
+ if (mxUtils.isVml(node))
+ {
+ var s = (document.documentMode != 8) ? this.vmlScale : 1;
+ node.coordsize = (w * s) + ',' + (h * s);
+ }
+
+ this.stencil.renderDom(this, this.bounds, node);
+
+ if(this.node != node)
+ {
+ // Forces parsing in IE8 standards mode
+ this.node.innerHTML = node.outerHTML;
+ }
+ }
+ else
+ {
+ while (this.node.firstChild != null)
+ {
+ this.node.removeChild(this.node.firstChild);
+ }
+
+ this.stencil.renderDom(this, this.bounds, this.node);
+ }
+};
diff --git a/src/js/shape/mxSwimlane.js b/src/js/shape/mxSwimlane.js
new file mode 100644
index 0000000..22720fd
--- /dev/null
+++ b/src/js/shape/mxSwimlane.js
@@ -0,0 +1,553 @@
+/**
+ * $Id: mxSwimlane.js,v 1.43 2011-11-04 13:54:50 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxSwimlane
+ *
+ * Extends <mxShape> to implement a swimlane shape.
+ * This shape is registered under <mxConstants.SHAPE_SWIMLANE>
+ * in <mxCellRenderer>.
+ *
+ * Constructor: mxSwimlane
+ *
+ * Constructs a new swimlane shape.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * fill - String that defines the fill color. This is stored in <fill>.
+ * stroke - String that defines the stroke color. This is stored in <stroke>.
+ * strokewidth - Optional integer that defines the stroke width. Default is
+ * 1. This is stored in <strokewidth>.
+ */
+function mxSwimlane(bounds, fill, stroke, strokewidth)
+{
+ this.bounds = bounds;
+ this.fill = fill;
+ this.stroke = stroke;
+ this.strokewidth = (strokewidth != null) ? strokewidth : 1;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxSwimlane.prototype = new mxShape();
+mxSwimlane.prototype.constructor = mxSwimlane;
+
+/**
+ * Variable: vmlNodes
+ *
+ * Adds local references to <mxShape.vmlNodes>.
+ */
+mxSwimlane.prototype.vmlNodes = mxSwimlane.prototype.vmlNodes.concat(['label', 'content', 'imageNode', 'separator']);
+
+/**
+ * Variable: imageSize
+ *
+ * Default imagewidth and imageheight if an image but no imagewidth
+ * and imageheight are defined in the style. Value is 16.
+ */
+mxSwimlane.prototype.imageSize = 16;
+
+/**
+ * Variable: mixedModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw in VML in mixed Html mode. This is for better
+ * handling of event-transparency of the content area.
+ */
+mxSwimlane.prototype.mixedModeHtml = false;
+
+/**
+ * Variable: preferModeHtml
+ *
+ * Overrides the parent value with false, meaning it will
+ * draw as VML in prefer Html mode. This is for better
+ * handling of event-transparency of the content area.
+ */
+mxRhombus.prototype.preferModeHtml = false;
+
+/**
+ * Function: createHtml
+ *
+ * Creates and returns the HTML node to represent this shape.
+ */
+mxSwimlane.prototype.createHtml = function()
+{
+ var node = document.createElement('DIV');
+ this.configureHtmlShape(node);
+ node.style.background = '';
+ node.style.backgroundColor = '';
+ node.style.borderStyle = 'none';
+
+ // Adds a node that will contain the text label
+ this.label = document.createElement('DIV');
+ this.configureHtmlShape(this.label);
+ node.appendChild(this.label);
+
+ // Adds a node for the content area of the swimlane
+ this.content = document.createElement('DIV');
+ this.configureHtmlShape(this.content);
+ this.content.style.backgroundColor = '';
+
+ // Sets border styles depending on orientation
+ if (mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ this.content.style.borderTopStyle = 'none';
+ }
+ else
+ {
+ this.content.style.borderLeftStyle = 'none';
+ }
+
+ this.content.style.cursor = 'default';
+ node.appendChild(this.content);
+
+ // Adds a node for the separator
+ var color = this.style[mxConstants.STYLE_SEPARATORCOLOR];
+
+ if (color != null)
+ {
+ this.separator = document.createElement('DIV');
+ this.separator.style.borderColor = color;
+ this.separator.style.borderLeftStyle = 'dashed';
+ node.appendChild(this.separator);
+ }
+
+ // Adds a node for the image
+ if (this.image != null)
+ {
+ this.imageNode = mxUtils.createImage(this.image);
+ this.configureHtmlShape(this.imageNode);
+ this.imageNode.style.borderStyle = 'none';
+ node.appendChild(this.imageNode);
+ }
+
+ return node;
+};
+
+/**
+ * Function: reconfigure
+ *
+ * Overrides to avoid filled content area in HTML and updates the shadow
+ * in SVG.
+ */
+mxSwimlane.prototype.reconfigure = function(node)
+{
+ mxShape.prototype.reconfigure.apply(this, arguments);
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ if (this.shadowNode != null)
+ {
+ this.updateSvgShape(this.shadowNode);
+
+ if (mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ this.shadowNode.setAttribute('height', this.startSize*this.scale);
+ }
+ else
+ {
+ this.shadowNode.setAttribute('width', this.startSize*this.scale);
+ }
+ }
+ }
+ else if (!mxUtils.isVml(this.node))
+ {
+ this.node.style.background = '';
+ this.node.style.backgroundColor = '';
+ }
+};
+
+/**
+ * Function: redrawHtml
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxSwimlane.prototype.redrawHtml = function()
+{
+ this.updateHtmlShape(this.node);
+ this.node.style.background = '';
+ this.node.style.backgroundColor = '';
+ this.startSize = parseInt(mxUtils.getValue(this.style,
+ mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
+ this.updateHtmlShape(this.label);
+ this.label.style.top = '0px';
+ this.label.style.left = '0px';
+
+ if (mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ this.startSize = Math.min(this.startSize, this.bounds.height);
+ this.label.style.height = (this.startSize * this.scale)+'px'; // relative
+ this.updateHtmlShape(this.content);
+ this.content.style.background = '';
+ this.content.style.backgroundColor = '';
+
+ var h = this.startSize*this.scale;
+
+ this.content.style.top = h+'px';
+ this.content.style.left = '0px';
+ this.content.style.height = Math.max(1, this.bounds.height - h)+'px';
+
+ if (this.separator != null)
+ {
+ this.separator.style.left = Math.round(this.bounds.width)+'px';
+ this.separator.style.top = Math.round(this.startSize*this.scale)+'px';
+ this.separator.style.width = '1px';
+ this.separator.style.height = Math.round(this.bounds.height)+'px';
+ this.separator.style.borderWidth = Math.round(this.scale)+'px';
+ }
+
+ if (this.imageNode != null)
+ {
+ this.imageNode.style.left = (this.bounds.width-this.imageSize-4)+'px';
+ this.imageNode.style.top = '0px';
+ // TODO: Use imageWidth and height from style if available
+ this.imageNode.style.width = Math.round(this.imageSize*this.scale)+'px';
+ this.imageNode.style.height = Math.round(this.imageSize*this.scale)+'px';
+ }
+ }
+ else
+ {
+ this.startSize = Math.min(this.startSize, this.bounds.width);
+ this.label.style.width = (this.startSize * this.scale)+'px'; // relative
+ this.updateHtmlShape(this.content);
+ this.content.style.background = '';
+ this.content.style.backgroundColor = '';
+
+ var w = this.startSize*this.scale;
+
+ this.content.style.top = '0px';
+ this.content.style.left = w+'px';
+ this.content.style.width = Math.max(0, this.bounds.width - w)+'px';
+
+ if (this.separator != null)
+ {
+ this.separator.style.left = Math.round(this.startSize*this.scale)+'px';
+ this.separator.style.top = Math.round(this.bounds.height)+'px';
+ this.separator.style.width = Math.round(this.bounds.width)+'px';
+ this.separator.style.height = '1px';
+ }
+
+ if (this.imageNode != null)
+ {
+ this.imageNode.style.left = (this.bounds.width-this.imageSize-4)+'px';
+ this.imageNode.style.top = '0px';
+ this.imageNode.style.width = this.imageSize*this.scale+'px';
+ this.imageNode.style.height = this.imageSize*this.scale+'px';
+ }
+ }
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node(s) to represent this shape.
+ */
+mxSwimlane.prototype.createVml = function()
+{
+ var node = document.createElement('v:group');
+ var name = (this.isRounded) ? 'v:roundrect' : 'v:rect';
+ this.label = document.createElement(name);
+
+ // First configure the label with all settings
+ this.configureVmlShape(this.label);
+
+ if (this.isRounded)
+ {
+ this.label.setAttribute('arcsize', '20%');
+ }
+
+ // Disables stuff and configures the rest
+ this.isShadow = false;
+ this.configureVmlShape(node);
+ node.coordorigin = '0,0';
+ node.appendChild(this.label);
+
+ this.content = document.createElement(name);
+
+ var tmp = this.fill;
+ this.fill = null;
+
+ this.configureVmlShape(this.content);
+ node.style.background = '';
+
+ if (this.isRounded)
+ {
+ this.content.setAttribute('arcsize', '4%');
+ }
+
+ this.fill = tmp;
+ this.content.style.borderBottom = '0px';
+
+ node.appendChild(this.content);
+
+ var color = this.style[mxConstants.STYLE_SEPARATORCOLOR];
+
+ if (color != null)
+ {
+ this.separator = document.createElement('v:shape');
+ this.separator.style.position = 'absolute';
+ this.separator.strokecolor = color;
+
+ var strokeNode = document.createElement('v:stroke');
+ strokeNode.dashstyle = '2 2';
+ this.separator.appendChild(strokeNode);
+
+ node.appendChild(this.separator);
+ }
+
+ if (this.image != null)
+ {
+ this.imageNode = document.createElement('v:image');
+ this.imageNode.src = this.image;
+ this.configureVmlShape(this.imageNode);
+ this.imageNode.stroked = 'false';
+
+ node.appendChild(this.imageNode);
+ }
+
+ return node;
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Updates the VML node(s) to reflect the latest bounds and scale.
+ */
+mxSwimlane.prototype.redrawVml = function()
+{
+ var x = Math.round(this.bounds.x);
+ var y = Math.round(this.bounds.y);
+ var w = Math.round(this.bounds.width);
+ var h = Math.round(this.bounds.height);
+
+ this.updateVmlShape(this.node);
+ this.node.coordsize = w + ',' + h;
+
+ this.updateVmlShape(this.label);
+ this.label.style.top = '0px';
+ this.label.style.left = '0px';
+ this.label.style.rotation = null;
+
+ this.startSize = parseInt(mxUtils.getValue(this.style,
+ mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
+ var start = Math.round(this.startSize * this.scale);
+
+ if (this.separator != null)
+ {
+ this.separator.coordsize = w + ',' + h;
+ this.separator.style.left = x + 'px';
+ this.separator.style.top = y + 'px';
+ this.separator.style.width = w + 'px';
+ this.separator.style.height = h + 'px';
+ }
+
+ if (mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ start = Math.min(start, this.bounds.height);
+ this.label.style.height = start + 'px'; // relative
+ this.updateVmlShape(this.content);
+ this.content.style.background = '';
+ this.content.style.top = start + 'px';
+ this.content.style.left = '0px';
+ this.content.style.height = Math.max(0, h - start)+'px';
+
+ if (this.separator != null)
+ {
+ var d = 'm ' + (w - x) + ' ' + (start - y) +
+ ' l ' + (w - x) + ' ' + (h - y) + ' e';
+ this.separator.path = d;
+ }
+
+ if (this.imageNode != null)
+ {
+ var img = Math.round(this.imageSize*this.scale);
+
+ this.imageNode.style.left = (w-img-4)+'px';
+ this.imageNode.style.top = '0px';
+ this.imageNode.style.width = img + 'px';
+ this.imageNode.style.height = img + 'px';
+ }
+ }
+ else
+ {
+ start = Math.min(start, this.bounds.width);
+ this.label.style.width = start + 'px'; // relative
+ this.updateVmlShape(this.content);
+ this.content.style.background = '';
+ this.content.style.top = '0px';
+ this.content.style.left = start + 'px';
+ this.content.style.width = Math.max(0, w - start) + 'px';
+
+ if (this.separator != null)
+ {
+ var d = 'm ' + (start - x) + ' ' + (h - y) +
+ ' l ' + (w - x) + ' ' + (h - y) + ' e';
+ this.separator.path = d;
+ }
+
+ if (this.imageNode != null)
+ {
+ var img = Math.round(this.imageSize * this.scale);
+
+ this.imageNode.style.left = (w - img - 4)+'px';
+ this.imageNode.style.top = '0px';
+ this.imageNode.style.width = img + 'px';
+ this.imageNode.style.height = img + 'px';
+ }
+ }
+
+ this.content.style.rotation = null;
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxSwimlane.prototype.createSvg = function()
+{
+ var node = this.createSvgGroup('rect');
+
+ if (this.isRounded)
+ {
+ this.innerNode.setAttribute('rx', 10);
+ this.innerNode.setAttribute('ry', 10);
+ }
+
+ this.content = document.createElementNS(mxConstants.NS_SVG, 'path');
+ this.configureSvgShape(this.content);
+ this.content.setAttribute('fill', 'none');
+
+ if (this.isRounded)
+ {
+ this.content.setAttribute('rx', 10);
+ this.content.setAttribute('ry', 10);
+ }
+
+ node.appendChild(this.content);
+ var color = this.style[mxConstants.STYLE_SEPARATORCOLOR];
+
+ if (color != null)
+ {
+ this.separator = document.createElementNS(mxConstants.NS_SVG, 'line');
+
+ this.separator.setAttribute('stroke', color);
+ this.separator.setAttribute('fill', 'none');
+ this.separator.setAttribute('stroke-dasharray', '2, 2');
+
+ node.appendChild(this.separator);
+ }
+
+ if (this.image != null)
+ {
+ this.imageNode = document.createElementNS(mxConstants.NS_SVG, 'image');
+
+ this.imageNode.setAttributeNS(mxConstants.NS_XLINK, 'href', this.image);
+ this.configureSvgShape(this.imageNode);
+
+ node.appendChild(this.imageNode);
+ }
+
+ return node;
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxSwimlane.prototype.redrawSvg = function()
+{
+ var tmp = this.isRounded;
+ this.isRounded = false;
+
+ this.updateSvgShape(this.innerNode);
+ this.updateSvgShape(this.content);
+ var horizontal = mxUtils.getValue(this.style, mxConstants.STYLE_HORIZONTAL, true);
+ this.startSize = parseInt(mxUtils.getValue(this.style,
+ mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
+ var ss = this.startSize * this.scale;
+
+ // Updates the size of the shadow node
+ if (this.shadowNode != null)
+ {
+ this.updateSvgShape(this.shadowNode);
+
+ if (horizontal)
+ {
+ this.shadowNode.setAttribute('height', ss);
+ }
+ else
+ {
+ this.shadowNode.setAttribute('width', ss);
+ }
+ }
+
+ this.isRounded = tmp;
+
+ this.content.removeAttribute('x');
+ this.content.removeAttribute('y');
+ this.content.removeAttribute('width');
+ this.content.removeAttribute('height');
+
+ var crisp = (this.crisp && mxClient.IS_IE) ? 0.5 : 0;
+ var x = Math.round(this.bounds.x) + crisp;
+ var y = Math.round(this.bounds.y) + crisp;
+ var w = Math.round(this.bounds.width);
+ var h = Math.round(this.bounds.height);
+
+ if (horizontal)
+ {
+ ss = Math.min(ss, h);
+ this.innerNode.setAttribute('height', ss);
+ var points = 'M ' + x + ' ' + (y + ss) +
+ ' l 0 ' + (h - ss) + ' l ' + w + ' 0' +
+ ' l 0 ' + (ss - h);
+ this.content.setAttribute('d', points);
+
+ if (this.separator != null)
+ {
+ this.separator.setAttribute('x1', x + w);
+ this.separator.setAttribute('y1', y + ss);
+ this.separator.setAttribute('x2', x + w);
+ this.separator.setAttribute('y2', y + h);
+ }
+
+ if (this.imageNode != null)
+ {
+ this.imageNode.setAttribute('x', x + w - this.imageSize - 4);
+ this.imageNode.setAttribute('y', y);
+ this.imageNode.setAttribute('width', this.imageSize * this.scale + 'px');
+ this.imageNode.setAttribute('height', this.imageSize * this.scale + 'px');
+ }
+ }
+ else
+ {
+ ss = Math.min(ss, w);
+ this.innerNode.setAttribute('width', ss);
+ var points = 'M ' + (x + ss) + ' ' + y +
+ ' l ' + (w - ss) + ' 0' + ' l 0 ' + h +
+ ' l ' + (ss - w) + ' 0';
+ this.content.setAttribute('d', points);
+
+ if (this.separator != null)
+ {
+ this.separator.setAttribute('x1', x + ss);
+ this.separator.setAttribute('y1', y + h);
+ this.separator.setAttribute('x2', x + w);
+ this.separator.setAttribute('y2', y + h);
+ }
+
+ if (this.imageNode != null)
+ {
+ this.imageNode.setAttribute('x', x + w - this.imageSize - 4);
+ this.imageNode.setAttribute('y', y);
+ this.imageNode.setAttribute('width', this.imageSize * this.scale + 'px');
+ this.imageNode.setAttribute('height', this.imageSize * this.scale + 'px');
+ }
+ }
+};
diff --git a/src/js/shape/mxText.js b/src/js/shape/mxText.js
new file mode 100644
index 0000000..de44b59
--- /dev/null
+++ b/src/js/shape/mxText.js
@@ -0,0 +1,1811 @@
+/**
+ * $Id: mxText.js,v 1.174 2012-09-27 10:20:30 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxText
+ *
+ * Extends <mxShape> to implement a text shape. To change vertical text from
+ * bottom to top to top to bottom, the following code can be used:
+ *
+ * (code)
+ * mxText.prototype.ieVerticalFilter = 'progid:DXImageTransform.Microsoft.BasicImage(rotation=1)';
+ * mxText.prototype.verticalTextDegree = 90;
+ *
+ * mxText.prototype.getVerticalOffset = function(offset)
+ * {
+ * return new mxPoint(-offset.y, offset.x);
+ * };
+ * (end)
+ *
+ * Constructor: mxText
+ *
+ * Constructs a new text shape.
+ *
+ * Parameters:
+ *
+ * value - String that represents the text to be displayed. This is stored in
+ * <value>.
+ * bounds - <mxRectangle> that defines the bounds. This is stored in
+ * <mxShape.bounds>.
+ * align - Specifies the horizontal alignment. Default is ''. This is stored in
+ * <align>.
+ * valign - Specifies the vertical alignment. Default is ''. This is stored in
+ * <valign>.
+ * color - String that specifies the text color. Default is 'black'. This is
+ * stored in <color>.
+ * family - String that specifies the font family. Default is
+ * <mxConstants.DEFAULT_FONTFAMILY>. This is stored in <family>.
+ * size - Integer that specifies the font size. Default is
+ * <mxConstants.DEFAULT_FONTSIZE>. This is stored in <size>.
+ * fontStyle - Specifies the font style. Default is 0. This is stored in
+ * <fontStyle>.
+ * spacing - Integer that specifies the global spacing. Default is 2. This is
+ * stored in <spacing>.
+ * spacingTop - Integer that specifies the top spacing. Default is 0. The
+ * sum of the spacing and this is stored in <spacingTop>.
+ * spacingRight - Integer that specifies the right spacing. Default is 0. The
+ * sum of the spacing and this is stored in <spacingRight>.
+ * spacingBottom - Integer that specifies the bottom spacing. Default is 0.The
+ * sum of the spacing and this is stored in <spacingBottom>.
+ * spacingLeft - Integer that specifies the left spacing. Default is 0. The
+ * sum of the spacing and this is stored in <spacingLeft>.
+ * horizontal - Boolean that specifies if the label is horizontal. Default is
+ * true. This is stored in <horizontal>.
+ * background - String that specifies the background color. Default is null.
+ * This is stored in <background>.
+ * border - String that specifies the label border color. Default is null.
+ * This is stored in <border>.
+ * wrap - Specifies if word-wrapping should be enabled. Default is false.
+ * This is stored in <wrap>.
+ * clipped - Specifies if the label should be clipped. Default is false.
+ * This is stored in <clipped>.
+ * overflow - Value of the overflow style. Default is 'visible'.
+ */
+function mxText(value, bounds, align, valign, color,
+ family, size, fontStyle, spacing, spacingTop, spacingRight,
+ spacingBottom, spacingLeft, horizontal, background, border,
+ wrap, clipped, overflow, labelPadding)
+{
+ this.value = value;
+ this.bounds = bounds;
+ this.color = (color != null) ? color : 'black';
+ this.align = (align != null) ? align : '';
+ this.valign = (valign != null) ? valign : '';
+ this.family = (family != null) ? family : mxConstants.DEFAULT_FONTFAMILY;
+ this.size = (size != null) ? size : mxConstants.DEFAULT_FONTSIZE;
+ this.fontStyle = (fontStyle != null) ? fontStyle : 0;
+ this.spacing = parseInt(spacing || 2);
+ this.spacingTop = this.spacing + parseInt(spacingTop || 0);
+ this.spacingRight = this.spacing + parseInt(spacingRight || 0);
+ this.spacingBottom = this.spacing + parseInt(spacingBottom || 0);
+ this.spacingLeft = this.spacing + parseInt(spacingLeft || 0);
+ this.horizontal = (horizontal != null) ? horizontal : true;
+ this.background = background;
+ this.border = border;
+ this.wrap = (wrap != null) ? wrap : false;
+ this.clipped = (clipped != null) ? clipped : false;
+ this.overflow = (overflow != null) ? overflow : 'visible';
+ this.labelPadding = (labelPadding != null) ? labelPadding : 0;
+};
+
+/**
+ * Extends mxShape.
+ */
+mxText.prototype = new mxShape();
+mxText.prototype.constructor = mxText;
+
+/**
+ * Variable: replaceLinefeeds
+ *
+ * Specifies if linefeeds in HTML labels should be replaced with BR tags.
+ * Default is true. This is also used in <mxImageExport> to export the label.
+ */
+mxText.prototype.replaceLinefeeds = true;
+
+/**
+ * Variable: ieVerticalFilter
+ *
+ * Holds the filter definition for vertical text in IE. Default is
+ * progid:DXImageTransform.Microsoft.BasicImage(rotation=3).
+ */
+mxText.prototype.ieVerticalFilter = 'progid:DXImageTransform.Microsoft.BasicImage(rotation=3)';
+
+/**
+ * Variable: verticalTextDegree
+ *
+ * Specifies the degree to be used for vertical text. Default is -90.
+ */
+mxText.prototype.verticalTextDegree = -90;
+
+/**
+ * Variable: forceIgnoreStringSize
+ *
+ * Specifies if the string size should always be ignored. Default is false.
+ * This can be used to improve rendering speed in slow browsers. This can be
+ * used if all labels are smaller than the vertex width. String sizes are
+ * ignored by default for labels which are left aligned with no background and
+ * border or if the overflow is set to fill.
+ */
+mxText.prototype.forceIgnoreStringSize = false;
+
+/**
+ * Function: isStyleSet
+ *
+ * Returns true if the given font style (bold, italic etc)
+ * is true in this shape's fontStyle.
+ *
+ * Parameters:
+ *
+ * style - Fontstyle constant from <mxConstants>.
+ */
+mxText.prototype.isStyleSet = function(style)
+{
+ return (this.fontStyle & style) == style;
+};
+
+/**
+ * Function: create
+ *
+ * Override to create HTML regardless of gradient and
+ * rounded property.
+ */
+mxText.prototype.create = function(container)
+{
+ var node = null;
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ node = this.createSvg();
+ }
+ else if (this.dialect == mxConstants.DIALECT_STRICTHTML ||
+ this.dialect == mxConstants.DIALECT_PREFERHTML ||
+ !mxUtils.isVml(container))
+ {
+ if (mxClient.IS_SVG && !mxClient.NO_FO)
+ {
+ node = this.createForeignObject();
+ }
+ else
+ {
+ node = this.createHtml();
+ }
+ }
+ else
+ {
+ node = this.createVml();
+ }
+
+ return node;
+};
+
+/**
+ * Function: updateBoundingBox
+ *
+ * Overrides method to do nothing.
+ */
+mxText.prototype.updateBoundingBox = function()
+{
+ // do nothing
+};
+
+/**
+ * Function: createForeignObject
+ *
+ * Creates and returns the foreignObject node to represent this shape.
+ */
+mxText.prototype.createForeignObject = function()
+{
+ var node = document.createElementNS(mxConstants.NS_SVG, 'g');
+
+ var fo = document.createElementNS(mxConstants.NS_SVG, 'foreignObject');
+ fo.setAttribute('pointer-events', 'fill');
+
+ // Ignored in FF
+ if (this.overflow == 'hidden')
+ {
+ fo.style.overflow = 'hidden';
+ }
+ else
+ {
+ // Fill and default are visible
+ fo.style.overflow = 'visible';
+ }
+
+ var body = document.createElement('div');
+ body.style.margin = '0px';
+ body.style.height = '100%';
+
+ fo.appendChild(body);
+ node.appendChild(fo);
+
+ return node;
+};
+
+/**
+ * Function: createHtml
+ *
+ * Creates and returns the HTML node to represent this shape.
+ */
+mxText.prototype.createHtml = function()
+{
+ var table = this.createHtmlTable();
+ table.style.position = 'absolute';
+
+ return table;
+};
+
+/**
+ * Function: createVml
+ *
+ * Creates and returns the VML node(s) to represent this shape.
+ */
+mxText.prototype.createVml = function()
+{
+ return document.createElement('v:textbox');
+};
+
+/**
+ * Function: redrawHtml
+ *
+ * Updates the HTML node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.redrawHtml = function()
+{
+ this.redrawVml();
+};
+
+/**
+ * Function: getOffset
+ *
+ * Returns the description of the space between the <bounds> size and the label
+ * size as an <mxPoint>.
+ */
+mxText.prototype.getOffset = function(outerWidth, outerHeight, actualWidth, actualHeight, horizontal)
+{
+ horizontal = (horizontal != null) ? horizontal : this.horizontal;
+
+ var tmpalign = (horizontal) ? this.align : this.valign;
+ var tmpvalign = (horizontal) ? this.valign : this.align;
+ var dx = actualWidth - outerWidth;
+ var dy = actualHeight - outerHeight;
+
+ if (tmpalign == mxConstants.ALIGN_CENTER || tmpalign == mxConstants.ALIGN_MIDDLE)
+ {
+ dx = Math.round(dx / 2);
+ }
+ else if (tmpalign == mxConstants.ALIGN_LEFT || tmpalign === mxConstants.ALIGN_TOP)
+ {
+ dx = (horizontal) ? 0 : (actualWidth - actualHeight) / 2;
+ }
+ else if (!horizontal) // BOTTOM
+ {
+ dx = (actualWidth + actualHeight) / 2 - outerWidth;
+ }
+
+ if (tmpvalign == mxConstants.ALIGN_MIDDLE || tmpvalign == mxConstants.ALIGN_CENTER)
+ {
+ dy = Math.round(dy / 2);
+ }
+ else if (tmpvalign == mxConstants.ALIGN_TOP || tmpvalign == mxConstants.ALIGN_LEFT)
+ {
+ dy = (horizontal) ? 0 : (actualHeight + actualWidth) / 2 - outerHeight;
+ }
+ else if (!horizontal) // RIGHT
+ {
+ dy = (actualHeight - actualWidth) / 2;
+ }
+
+ return new mxPoint(dx, dy);
+};
+
+/**
+ * Function: getSpacing
+ *
+ * Returns the spacing as an <mxPoint>.
+ */
+mxText.prototype.getSpacing = function(horizontal)
+{
+ horizontal = (horizontal != null) ? horizontal : this.horizontal;
+
+ var dx = 0;
+ var dy = 0;
+
+ if (this.align == mxConstants.ALIGN_CENTER)
+ {
+ dx = (this.spacingLeft - this.spacingRight) / 2;
+ }
+ else if (this.align == mxConstants.ALIGN_RIGHT)
+ {
+ dx = -this.spacingRight;
+ }
+ else
+ {
+ dx = this.spacingLeft;
+ }
+
+ if (this.valign == mxConstants.ALIGN_MIDDLE)
+ {
+ dy = (this.spacingTop - this.spacingBottom) / 2;
+ }
+ else if (this.valign == mxConstants.ALIGN_BOTTOM)
+ {
+ dy = -this.spacingBottom;
+ }
+ else
+ {
+ dy = this.spacingTop;
+ }
+
+ return (horizontal) ? new mxPoint(dx, dy) : new mxPoint(dy, dx);
+};
+
+/**
+ * Function: createHtmlTable
+ *
+ * Creates and returns a HTML table with a table body and a single row with a
+ * single cell.
+ */
+mxText.prototype.createHtmlTable = function()
+{
+ var table = document.createElement('table');
+ table.style.borderCollapse = 'collapse';
+ var tbody = document.createElement('tbody');
+ var tr = document.createElement('tr');
+ var td = document.createElement('td');
+
+ // Workaround for ignored table height in IE9 standards mode
+ if (document.documentMode >= 9)
+ {
+ // FIXME: Ignored in print preview for IE9 standards mode
+ td.style.height = '100%';
+ }
+
+ tr.appendChild(td);
+ tbody.appendChild(tr);
+ table.appendChild(tbody);
+
+ return table;
+};
+
+/**
+ * Function: updateTableStyle
+ *
+ * Updates the style of the given HTML table and the value
+ * within the table.
+ */
+mxText.prototype.updateHtmlTable = function(table, scale)
+{
+ scale = (scale != null) ? scale : 1;
+ var td = table.firstChild.firstChild.firstChild;
+
+ // Reset of width required to measure actual width after word wrap
+ if (this.wrap)
+ {
+ table.style.width = '';
+ }
+
+ // Updates the value
+ if (mxUtils.isNode(this.value))
+ {
+ if (td.firstChild != this.value)
+ {
+ if (td.firstChild != null)
+ {
+ td.removeChild(td.firstChild);
+ }
+
+ td.appendChild(this.value);
+ }
+ }
+ else
+ {
+ if (this.lastValue != this.value)
+ {
+ td.innerHTML = (this.replaceLinefeeds) ? this.value.replace(/\n/g, '<br/>') : this.value;
+ this.lastValue = this.value;
+ }
+ }
+
+ // Font style
+ var fontSize = Math.round(this.size * scale);
+
+ if (fontSize <= 0)
+ {
+ table.style.visibility = 'hidden';
+ }
+ else
+ {
+ // Do not use visible here as it will clone
+ // all labels while panning in IE
+ table.style.visibility = '';
+ }
+
+ table.style.fontSize = fontSize + 'px';
+ table.style.color = this.color;
+ table.style.fontFamily = this.family;
+
+ // Bold
+ if (this.isStyleSet(mxConstants.FONT_BOLD))
+ {
+ table.style.fontWeight = 'bold';
+ }
+ else
+ {
+ table.style.fontWeight = 'normal';
+ }
+
+ // Italic
+ if (this.isStyleSet(mxConstants.FONT_ITALIC))
+ {
+ table.style.fontStyle = 'italic';
+ }
+ else
+ {
+ table.style.fontStyle = '';
+ }
+
+ // Underline
+ if (this.isStyleSet(mxConstants.FONT_UNDERLINE))
+ {
+ table.style.textDecoration = 'underline';
+ }
+ else
+ {
+ table.style.textDecoration = '';
+ }
+
+ // Font shadow (only available in IE)
+ if (mxClient.IS_IE)
+ {
+ if (this.isStyleSet(mxConstants.FONT_SHADOW))
+ {
+ td.style.filter = 'Shadow(Color=#666666,'+'Direction=135,Strength=%)';
+ }
+ else
+ {
+ td.style.removeAttribute('filter');
+ }
+ }
+
+ // Horizontal and vertical alignment
+ td.style.textAlign =
+ (this.align == mxConstants.ALIGN_RIGHT) ? 'right' :
+ ((this.align == mxConstants.ALIGN_CENTER) ? 'center' :
+ 'left');
+ td.style.verticalAlign =
+ (this.valign == mxConstants.ALIGN_BOTTOM) ? 'bottom' :
+ ((this.valign == mxConstants.ALIGN_MIDDLE) ? 'middle' :
+ 'top');
+
+ // Background style (Must use TD not TABLE for Firefox when rotated)
+ if (this.value.length > 0 && this.background != null)
+ {
+ td.style.background = this.background;
+ }
+ else
+ {
+ td.style.background = '';
+ }
+
+ td.style.padding = this.labelPadding + 'px';
+
+ if (this.value.length > 0 && this.border != null)
+ {
+ table.style.borderColor = this.border;
+ table.style.borderWidth = '1px';
+ table.style.borderStyle = 'solid';
+ }
+ else
+ {
+ table.style.borderStyle = 'none';
+ }
+};
+
+/**
+ * Function: getTableSize
+ *
+ * Returns the actual size of the table.
+ */
+mxText.prototype.getTableSize = function(table)
+{
+ return new mxRectangle(0, 0, table.offsetWidth, table.offsetHeight);
+};
+
+/**
+ * Function: updateTableWidth
+ *
+ * Updates the width of the given HTML table.
+ */
+mxText.prototype.updateTableWidth = function(table)
+{
+ var td = table.firstChild.firstChild.firstChild;
+
+ // Word-wrap for vertices (not edges) and only if not
+ // just getting the bounding box in SVG
+ if (this.wrap && this.bounds.width > 0 && this.dialect != mxConstants.DIALECT_SVG)
+ {
+ // Makes sure the label is not wrapped when measuring full length
+ td.style.whiteSpace = 'nowrap';
+ var size = this.getTableSize(table);
+ var space = Math.min(size.width, ((this.horizontal || mxUtils.isVml(this.node)) ?
+ this.bounds.width : this.bounds.height) / this.scale);
+
+ // Opera needs the new width to be scaled
+ if (mxClient.IS_OP)
+ {
+ space *= this.scale;
+ }
+
+ table.style.width = Math.round(space) + 'px';
+ td.style.whiteSpace = 'normal';
+ }
+ else
+ {
+ table.style.width = '';
+ }
+
+ if (!this.wrap)
+ {
+ td.style.whiteSpace = 'nowrap';
+ }
+ else
+ {
+ td.style.whiteSpace = 'normal';
+ }
+};
+
+/**
+ * Function: redrawVml
+ *
+ * Updates the VML node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.redrawVml = function()
+{
+ if (this.node.nodeName == 'g')
+ {
+ this.redrawForeignObject();
+ }
+ else if (mxUtils.isVml(this.node))
+ {
+ this.redrawTextbox();
+ }
+ else
+ {
+ this.redrawHtmlTable();
+ }
+};
+
+/**
+ * Function: redrawTextbox
+ *
+ * Redraws the textbox for this text. This is only used in IE in exact
+ * rendering mode.
+ */
+mxText.prototype.redrawTextbox = function()
+{
+ // Gets VML textbox
+ var textbox = this.node;
+
+ // Creates HTML container on the fly
+ if (textbox.firstChild == null)
+ {
+ textbox.appendChild(this.createHtmlTable());
+ }
+
+ // Updates the table style and value
+ var table = textbox.firstChild;
+ this.updateHtmlTable(table);
+ this.updateTableWidth(table);
+
+ // Opacity
+ if (this.opacity != null)
+ {
+ mxUtils.setOpacity(table, this.opacity);
+ }
+
+ table.style.filter = '';
+ textbox.inset = '0px,0px,0px,0px';
+
+ if (this.overflow != 'fill')
+ {
+ // Only tables can be used to work out the actual size of the markup
+ var size = this.getTableSize(table);
+ var w = size.width * this.scale;
+ var h = size.height * this.scale;
+ var offset = this.getOffset(this.bounds.width, this.bounds.height, w, h);
+
+ // Rotates the label (IE only)
+ if (!this.horizontal)
+ {
+ table.style.filter = this.ieVerticalFilter;
+ }
+
+ // Adds horizontal/vertical spacing
+ var spacing = this.getSpacing();
+ var x = this.bounds.x - offset.x + spacing.x * this.scale;
+ var y = this.bounds.y - offset.y + spacing.y * this.scale;
+
+ // Textboxes are always relative to their parent shape's top, left corner so
+ // we use the inset for absolute positioning as they allow negative values
+ // except for edges where the bounds are used to find the shape center
+ var x0 = this.bounds.x;
+ var y0 = this.bounds.y;
+ var ow = this.bounds.width;
+ var oh = this.bounds.height;
+
+ // Insets are given as left, top, right, bottom
+ if (this.horizontal)
+ {
+ var tx = Math.round(x - x0);
+ var ty = Math.round(y - y0);
+
+ var r = Math.min(0, Math.round(x0 + ow - x - w - 1));
+ var b = Math.min(0, Math.round(y0 + oh - y - h - 1));
+ textbox.inset = tx + 'px,' + ty + 'px,' + r + 'px,' + b + 'px';
+ }
+ else
+ {
+ var t = 0;
+ var l = 0;
+ var r = 0;
+ var b = 0;
+
+ if (this.align == mxConstants.ALIGN_CENTER)
+ {
+ t = (oh - w) / 2;
+ b = t;
+ }
+ else if (this.align == mxConstants.ALIGN_LEFT)
+ {
+ t = oh - w;
+ }
+ else
+ {
+ b = oh - w;
+ }
+
+ if (this.valign == mxConstants.ALIGN_MIDDLE)
+ {
+ l = (ow - h) / 2;
+ r = l;
+ }
+ else if (this.valign == mxConstants.ALIGN_BOTTOM)
+ {
+ l = ow - h;
+ }
+ else
+ {
+ r = ow - h;
+ }
+
+ textbox.inset = l + 'px,' + t + 'px,' + r + 'px,' + b + 'px';
+ }
+
+ textbox.style.zoom = this.scale;
+
+ // Clipping
+ if (this.clipped && this.bounds.width > 0 && this.bounds.height > 0)
+ {
+ this.boundingBox = this.bounds.clone();
+ var dx = Math.round(x0 - x);
+ var dy = Math.round(y0 - y);
+
+ textbox.style.clip = 'rect(' + (dy / this.scale) + ' ' +
+ ((dx + this.bounds.width) / this.scale) + ' ' +
+ ((dy + this.bounds.height) / this.scale) + ' ' +
+ (dx / this.scale) + ')';
+ }
+ else
+ {
+ this.boundingBox = new mxRectangle(x, y, w, h);
+ }
+ }
+ else
+ {
+ this.boundingBox = this.bounds.clone();
+ }
+};
+
+/**
+ * Function: redrawHtmlTable
+ *
+ * Redraws the HTML table. This is used for HTML labels in all modes except
+ * exact in IE and if NO_FO is false for the browser.
+ */
+mxText.prototype.redrawHtmlTable = function()
+{
+ if (isNaN(this.bounds.x) || isNaN(this.bounds.y) ||
+ isNaN(this.bounds.width) || isNaN(this.bounds.height))
+ {
+ return;
+ }
+
+ // Gets table
+ var table = this.node;
+ var td = table.firstChild.firstChild.firstChild;
+
+ // Un-rotates for computing the actual size
+ // TODO: Check if the result can be tweaked instead in getActualSize
+ // and only do this if actual rotation did change
+ var oldBrowser = false;
+ var fallbackScale = 1;
+
+ if (mxClient.IS_IE)
+ {
+ table.style.removeAttribute('filter');
+ }
+ else if (mxClient.IS_SF || mxClient.IS_GC)
+ {
+ table.style.WebkitTransform = '';
+ }
+ else if (mxClient.IS_MT)
+ {
+ table.style.MozTransform = '';
+ td.style.MozTransform = '';
+ }
+ else
+ {
+ if (mxClient.IS_OT)
+ {
+ table.style.OTransform = '';
+ }
+
+ fallbackScale = this.scale;
+ oldBrowser = true;
+ }
+
+ // Resets the current zoom for text measuring
+ td.style.zoom = '';
+
+ // Updates the table style and value
+ this.updateHtmlTable(table, fallbackScale);
+ this.updateTableWidth(table);
+
+ // Opacity
+ if (this.opacity != null)
+ {
+ mxUtils.setOpacity(table, this.opacity);
+ }
+
+ // Resets the bounds for computing the actual size
+ table.style.left = '';
+ table.style.top = '';
+ table.style.height = '';
+
+ // Workaround for multiple zoom even if CSS style is reset here
+ var currentZoom = parseFloat(td.style.zoom) || 1;
+
+ // Only tables can be used to work out the actual size of the markup
+ // NOTE: offsetWidth and offsetHeight are very slow in quirks and IE 8 standards mode
+ var w = this.bounds.width;
+ var h = this.bounds.height;
+
+ var ignoreStringSize = this.forceIgnoreStringSize || this.overflow == 'fill' ||
+ (this.align == mxConstants.ALIGN_LEFT && this.background == null && this.border == null);
+
+ if (!ignoreStringSize)
+ {
+ var size = this.getTableSize(table);
+ w = size.width / currentZoom;
+ h = size.height / currentZoom;
+ }
+
+ var offset = this.getOffset(this.bounds.width / this.scale,
+ this.bounds.height / this.scale, w, h,
+ oldBrowser || this.horizontal);
+
+ // Adds horizontal/vertical spacing
+ var spacing = this.getSpacing(oldBrowser || this.horizontal);
+ var x = this.bounds.x / this.scale - offset.x + spacing.x;
+ var y = this.bounds.y / this.scale - offset.y + spacing.y;
+
+ // Updates the table bounds and stores the scale to be used for
+ // defining the table width and height, as well as an offset
+ var s = this.scale;
+ var s2 = 1;
+ var shiftX = 0;
+ var shiftY = 0;
+
+ // Rotates the label and adds offset
+ if (!this.horizontal)
+ {
+ if (mxClient.IS_IE && mxClient.IS_SVG)
+ {
+ table.style.msTransform = 'rotate(' + this.verticalTextDegree + 'deg)';
+ }
+ else if (mxClient.IS_IE)
+ {
+ table.style.filter = this.ieVerticalFilter;
+ shiftX = (w - h) / 2;
+ shiftY = -shiftX;
+ }
+ else if (mxClient.IS_SF || mxClient.IS_GC)
+ {
+ table.style.WebkitTransform = 'rotate(' + this.verticalTextDegree + 'deg)';
+ }
+ else if (mxClient.IS_OT)
+ {
+ table.style.OTransform = 'rotate(' + this.verticalTextDegree + 'deg)';
+ }
+ else if (mxClient.IS_MT)
+ {
+ // Firefox paints background and border only if background is on TD
+ // and border is on TABLE and both are rotated, just the TD with a
+ // rotation of zero (don't remove the 0-rotate CSS style)
+ table.style.MozTransform = 'rotate(' + this.verticalTextDegree + 'deg)';
+ td.style.MozTransform = 'rotate(0deg)';
+
+ s2 = 1 / this.scale;
+ s = 1;
+ }
+ }
+
+ // Sets the zoom
+ var correction = true;
+
+ if (mxClient.IS_MT || oldBrowser)
+ {
+ if (mxClient.IS_MT)
+ {
+ table.style.MozTransform += ' scale(' + this.scale + ')';
+ s2 = 1 / this.scale;
+ }
+ else if (mxClient.IS_OT)
+ {
+ td.style.OTransform = 'scale(' + this.scale + ')';
+ table.style.borderWidth = Math.round(this.scale * parseInt(table.style.borderWidth)) + 'px';
+ }
+ }
+ else if (!oldBrowser)
+ {
+ // Workaround for unsupported zoom CSS in IE9 standards mode
+ if (document.documentMode >= 9)
+ {
+ td.style.msTransform = 'scale(' + this.scale + ')';
+ }
+ // Uses transform in Webkit for better HTML scaling
+ else if (mxClient.IS_SF || mxClient.IS_GC)
+ {
+ td.style.WebkitTransform = 'scale(' + this.scale + ')';
+ }
+ else
+ {
+ td.style.zoom = this.scale;
+
+ // Fixes scaling of border width
+ if (table.style.borderWidth != '' && document.documentMode != 8)
+ {
+ table.style.borderWidth = Math.round(this.scale * parseInt(table.style.borderWidth)) + 'px';
+ }
+
+ // Workaround for wrong scale in IE8 standards mode
+ if (document.documentMode == 8 || !mxClient.IS_IE)
+ {
+ s = 1;
+ }
+
+ correction = false;
+ }
+ }
+
+ if (correction)
+ {
+ // Workaround for scaled TD position
+ shiftX = (this.scale - 1) * w / (2 * this.scale);
+ shiftY = (this.scale - 1) * h / (2 * this.scale);
+ s = 1;
+ }
+
+ if (this.overflow != 'fill')
+ {
+ var rect = new mxRectangle(Math.round((x + shiftX) * this.scale),
+ Math.round((y + shiftY) * this.scale), Math.round(w * s), Math.round(h * s));
+ table.style.left = rect.x + 'px';
+ table.style.top = rect.y + 'px';
+ table.style.width = rect.width + 'px';
+ table.style.height = rect.height + 'px';
+
+ // Workaround for wrong scale in border and background rendering for table and td in IE8/9 standards mode
+ if ((this.background != null || this.border != null) && document.documentMode >= 8)
+ {
+ var html = (this.replaceLinefeeds) ? this.value.replace(/\n/g, '<br/>') : this.value;
+ td.innerHTML = '<div style="padding:' + this.labelPadding + 'px;background:' + td.style.background + ';border:' + table.style.border + '">' + html + '</div>';
+ td.style.padding = '0px';
+ td.style.background = '';
+ table.style.border = '';
+ }
+
+ // Clipping
+ if (this.clipped && this.bounds.width > 0 && this.bounds.height > 0)
+ {
+ this.boundingBox = this.bounds.clone();
+
+ // Clipping without rotation or for older browsers
+ if (this.horizontal || (oldBrowser && !mxClient.IS_OT))
+ {
+ var dx = Math.max(0, offset.x * s);
+ var dy = Math.max(0, offset.y * s);
+
+ // TODO: Fix clipping for Opera
+ table.style.clip = 'rect(' + (dy) + 'px ' + (dx + this.bounds.width * s2) +
+ 'px ' + (dy + this.bounds.height * s2) + 'px ' + (dx) + 'px)';
+ }
+ else
+ {
+ // Workaround for IE clip using top, right, bottom, left (un-rotated)
+ if (mxClient.IS_IE)
+ {
+ var uw = this.bounds.width;
+ var uh = this.bounds.height;
+ var dx = 0;
+ var dy = 0;
+
+ if (this.align == mxConstants.ALIGN_LEFT)
+ {
+ dx = Math.max(0, w - uh / this.scale) * this.scale;
+ }
+ else if (this.align == mxConstants.ALIGN_CENTER)
+ {
+ dx = Math.max(0, w - uh / this.scale) * this.scale / 2;
+ }
+
+ if (this.valign == mxConstants.ALIGN_BOTTOM)
+ {
+ dy = Math.max(0, h - uw / this.scale) * this.scale;
+ }
+ else if (this.valign == mxConstants.ALIGN_MIDDLE)
+ {
+ dy = Math.max(0, h - uw / this.scale) * this.scale / 2;
+ }
+
+ table.style.clip = 'rect(' + (dx) + 'px ' + (dy + uw - 1) +
+ 'px ' + (dx + uh - 1) + 'px ' + (dy) + 'px)';
+ }
+ else
+ {
+ var uw = this.bounds.width / this.scale;
+ var uh = this.bounds.height / this.scale;
+
+ if (mxClient.IS_OT)
+ {
+ uw = this.bounds.width;
+ uh = this.bounds.height;
+ }
+
+ var dx = 0;
+ var dy = 0;
+
+ if (this.align == mxConstants.ALIGN_RIGHT)
+ {
+ dx = Math.max(0, w - uh);
+ }
+ else if (this.align == mxConstants.ALIGN_CENTER)
+ {
+ dx = Math.max(0, w - uh) / 2;
+ }
+
+ if (this.valign == mxConstants.ALIGN_BOTTOM)
+ {
+ dy = Math.max(0, h - uw);
+ }
+ else if (this.valign == mxConstants.ALIGN_MIDDLE)
+ {
+ dy = Math.max(0, h - uw) / 2;
+ }
+
+ if (mxClient.IS_GC || mxClient.IS_SF)
+ {
+ dx *= this.scale;
+ dy *= this.scale;
+ uw *= this.scale;
+ uh *= this.scale;
+ }
+
+ table.style.clip = 'rect(' + (dy) + ' ' + (dx + uh) +
+ ' ' + (dy + uw) + ' ' + (dx) + ')';
+ }
+ }
+ }
+ else
+ {
+ this.boundingBox = rect;
+ }
+ }
+ else
+ {
+ this.boundingBox = this.bounds.clone();
+
+ if (document.documentMode >= 9 || mxClient.IS_SVG)
+ {
+ table.style.left = Math.round(this.bounds.x + this.scale / 2 + shiftX) + 'px';
+ table.style.top = Math.round(this.bounds.y + this.scale / 2 + shiftY) + 'px';
+ table.style.width = Math.round((this.bounds.width - this.scale) / this.scale) + 'px';
+ table.style.height = Math.round((this.bounds.height - this.scale) / this.scale) + 'px';
+ }
+ else
+ {
+ s = (document.documentMode == 8) ? this.scale : 1;
+ table.style.left = Math.round(this.bounds.x + this.scale / 2) + 'px';
+ table.style.top = Math.round(this.bounds.y + this.scale / 2) + 'px';
+ table.style.width = Math.round((this.bounds.width - this.scale) / s) + 'px';
+ table.style.height = Math.round((this.bounds.height - this.scale) / s) + 'px';
+ }
+ }
+};
+
+/**
+ * Function: getVerticalOffset
+ *
+ * Returns the factors for the offset to be added to the text vertical
+ * text rotation. This implementation returns (offset.y, -offset.x).
+ */
+mxText.prototype.getVerticalOffset = function(offset)
+{
+ return new mxPoint(offset.y, -offset.x);
+};
+
+/**
+ * Function: redrawForeignObject
+ *
+ * Redraws the foreign object for this text.
+ */
+mxText.prototype.redrawForeignObject = function()
+{
+ // Gets SVG group with foreignObject
+ var group = this.node;
+ var fo = group.firstChild;
+
+ // Searches the table which appears behind the background
+ while (fo == this.backgroundNode)
+ {
+ fo = fo.nextSibling;
+ }
+
+ var body = fo.firstChild;
+
+ // Creates HTML container on the fly
+ if (body.firstChild == null)
+ {
+ body.appendChild(this.createHtmlTable());
+ }
+
+ // Updates the table style and value
+ var table = body.firstChild;
+ this.updateHtmlTable(table);
+
+ // Workaround for bug in Google Chrome where the text is moved to origin if opacity
+ // is set on the table, so we set the opacity on the foreignObject instead.
+ if (this.opacity != null)
+ {
+ fo.setAttribute('opacity', this.opacity / 100);
+ }
+
+ // Workaround for table background not appearing above the shape that is
+ // behind the label in Safari. To solve this, we add a background rect that
+ // paints the background instead.
+ if (mxClient.IS_SF)
+ {
+ table.style.borderStyle = 'none';
+ table.firstChild.firstChild.firstChild.style.background = '';
+
+ if (this.backgroundNode == null && (this.background != null || this.border != null))
+ {
+ this.backgroundNode = document.createElementNS(mxConstants.NS_SVG, 'rect');
+ group.insertBefore(this.backgroundNode, group.firstChild);
+ }
+ else if (this.backgroundNode != null && this.background == null && this.border == null)
+ {
+ this.backgroundNode.parentNode.removeChild(this.backgroundNode);
+ this.backgroundNode = null;
+ }
+
+ if (this.backgroundNode != null)
+ {
+ if (this.background != null)
+ {
+ this.backgroundNode.setAttribute('fill', this.background);
+ }
+ else
+ {
+ this.backgroundNode.setAttribute('fill', 'none');
+ }
+
+ if (this.border != null)
+ {
+ this.backgroundNode.setAttribute('stroke', this.border);
+ }
+ else
+ {
+ this.backgroundNode.setAttribute('stroke', 'none');
+ }
+ }
+ }
+
+ var tr = '';
+
+ if (this.overflow != 'fill')
+ {
+ // Resets the bounds for computing the actual size
+ fo.removeAttribute('width');
+ fo.removeAttribute('height');
+ fo.style.width = '';
+ fo.style.height = '';
+ fo.style.clip = '';
+
+ // Workaround for size of table not updated if inside foreignObject
+ if (this.wrap || (!mxClient.IS_GC && !mxClient.IS_SF))
+ {
+ document.body.appendChild(table);
+ }
+
+ this.updateTableWidth(table);
+
+ // Only tables can be used to work out the actual size of the markup
+ var size = this.getTableSize(table);
+ var w = size.width;
+ var h = size.height;
+
+ if (table.parentNode != body)
+ {
+ body.appendChild(table);
+ }
+
+ // Adds horizontal/vertical spacing
+ var spacing = this.getSpacing();
+
+ var x = this.bounds.x / this.scale + spacing.x;
+ var y = this.bounds.y / this.scale + spacing.y;
+ var uw = this.bounds.width / this.scale;
+ var uh = this.bounds.height / this.scale;
+ var offset = this.getOffset(uw, uh, w, h);
+
+ // Rotates the label and adds offset
+ if (this.horizontal)
+ {
+ x -= offset.x;
+ y -= offset.y;
+
+ tr = 'scale(' + this.scale + ')';
+ }
+ else
+ {
+ var x0 = x + w / 2;
+ var y0 = y + h / 2;
+
+ tr = 'scale(' + this.scale + ') rotate(' + this.verticalTextDegree + ' ' + x0 + ' ' + y0 + ')';
+
+ var tmp = this.getVerticalOffset(offset);
+ x += tmp.x;
+ y += tmp.y;
+ }
+
+ // Must use translate instead of x- and y-attribute on FO for iOS
+ tr += ' translate(' + x + ' ' + y + ')';
+
+ // Updates the bounds of the background node in Webkit
+ if (this.backgroundNode != null)
+ {
+ this.backgroundNode.setAttribute('width', w);
+ this.backgroundNode.setAttribute('height', h);
+ }
+
+ // Updates the foreignObject size
+ fo.setAttribute('width', w);
+ fo.setAttribute('height', h);
+
+ // Clipping
+ // TODO: Fix/check clipping for foreignObjects in Chrome 5.0 - if clipPath
+ // is used in the group then things can no longer be moved around
+ if (this.clipped && this.bounds.width > 0 && this.bounds.height > 0)
+ {
+ this.boundingBox = this.bounds.clone();
+ var dx = Math.max(0, offset.x);
+ var dy = Math.max(0, offset.y);
+
+ if (this.horizontal)
+ {
+ fo.style.clip = 'rect(' + dy + 'px,' + (dx + uw) +
+ 'px,' + (dy + uh) + 'px,' + (dx) + 'px)';
+ }
+ else
+ {
+ var dx = 0;
+ var dy = 0;
+
+ if (this.align == mxConstants.ALIGN_RIGHT)
+ {
+ dx = Math.max(0, w - uh);
+ }
+ else if (this.align == mxConstants.ALIGN_CENTER)
+ {
+ dx = Math.max(0, w - uh) / 2;
+ }
+
+ if (this.valign == mxConstants.ALIGN_BOTTOM)
+ {
+ dy = Math.max(0, h - uw);
+ }
+ else if (this.valign == mxConstants.ALIGN_MIDDLE)
+ {
+ dy = Math.max(0, h - uw) / 2;
+ }
+
+ fo.style.clip = 'rect(' + (dy) + 'px,' + (dx + uh) +
+ 'px,' + (dy + uw) + 'px,' + (dx) + 'px)';
+ }
+
+ // Clipping for the background node in Chrome
+ if (this.backgroundNode != null)
+ {
+ x = this.bounds.x / this.scale;
+ y = this.bounds.y / this.scale;
+
+ if (!this.horizontal)
+ {
+ x += (h + w) / 2 - uh;
+ y += (h - w) / 2;
+
+ var tmp = uw;
+ uw = uh;
+ uh = tmp;
+ }
+
+ // No clipping in Chome available due to bug
+ if (!mxClient.IS_GC)
+ {
+ var clip = this.getSvgClip(this.node.ownerSVGElement, x, y, uw, uh);
+
+ if (clip != this.clip)
+ {
+ this.releaseSvgClip();
+ this.clip = clip;
+ clip.refCount++;
+ }
+
+ this.backgroundNode.setAttribute('clip-path', 'url(#' + clip.getAttribute('id') + ')');
+ }
+ }
+ }
+ else
+ {
+ // Removes clipping from background and cleans up the clip
+ this.releaseSvgClip();
+
+ if (this.backgroundNode != null)
+ {
+ this.backgroundNode.removeAttribute('clip-path');
+ }
+
+ if (this.horizontal)
+ {
+ this.boundingBox = new mxRectangle(x * this.scale, y * this.scale, w * this.scale, h * this.scale);
+ }
+ else
+ {
+ this.boundingBox = new mxRectangle(x * this.scale, y * this.scale, h * this.scale, w * this.scale);
+ }
+ }
+ }
+ else
+ {
+ this.boundingBox = this.bounds.clone();
+
+ var s = this.scale;
+ var w = this.bounds.width / s;
+ var h = this.bounds.height / s;
+
+ // Updates the foreignObject and table bounds
+ fo.setAttribute('width', w);
+ fo.setAttribute('height', h);
+ table.style.width = w + 'px';
+ table.style.height = h + 'px';
+
+ // Updates the bounds of the background node in Webkit
+ if (this.backgroundNode != null)
+ {
+ this.backgroundNode.setAttribute('width', table.clientWidth);
+ this.backgroundNode.setAttribute('height', table.offsetHeight);
+ }
+
+ // Must use translate instead of x- and y-attribute on FO for iOS
+ tr = 'scale(' + s + ') translate(' + (this.bounds.x / s) +
+ ' ' + (this.bounds.y / s) + ')';
+
+ if (!this.wrap)
+ {
+ var td = table.firstChild.firstChild.firstChild;
+ td.style.whiteSpace = 'nowrap';
+ }
+ }
+
+ group.setAttribute('transform', tr);
+};
+
+/**
+ * Function: createSvg
+ *
+ * Creates and returns the SVG node(s) to represent this shape.
+ */
+mxText.prototype.createSvg = function()
+{
+ // Creates a group so that shapes inside are rendered properly, if this is
+ // a text node then the background rectangle is not rendered in Webkit.
+ var node = document.createElementNS(mxConstants.NS_SVG, 'g');
+
+ var uline = this.isStyleSet(mxConstants.FONT_UNDERLINE) ? 'underline' : 'none';
+ var weight = this.isStyleSet(mxConstants.FONT_BOLD) ? 'bold' : 'normal';
+ var s = this.isStyleSet(mxConstants.FONT_ITALIC) ? 'italic' : null;
+
+ // Underline is not implemented in FF, see
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=317196
+ node.setAttribute('text-decoration', uline);
+ node.setAttribute('font-family', this.family);
+ node.setAttribute('font-weight', weight);
+ node.setAttribute('font-size', Math.round(this.size * this.scale) + 'px');
+ node.setAttribute('fill', this.color);
+ var align = (this.align == mxConstants.ALIGN_RIGHT) ? 'end' :
+ (this.align == mxConstants.ALIGN_CENTER) ? 'middle' :
+ 'start';
+ node.setAttribute('text-anchor', align);
+
+ if (s != null)
+ {
+ node.setAttribute('font-style', s);
+ }
+
+ // Adds a rectangle for the background color
+ if (this.background != null || this.border != null)
+ {
+ this.backgroundNode = document.createElementNS(mxConstants.NS_SVG, 'rect');
+ this.backgroundNode.setAttribute('shape-rendering', 'crispEdges');
+
+ if (this.background != null)
+ {
+ this.backgroundNode.setAttribute('fill', this.background);
+ }
+ else
+ {
+ this.backgroundNode.setAttribute('fill', 'none');
+ }
+
+ if (this.border != null)
+ {
+ this.backgroundNode.setAttribute('stroke', this.border);
+ }
+ else
+ {
+ this.backgroundNode.setAttribute('stroke', 'none');
+ }
+ }
+
+ this.updateSvgValue(node);
+
+ return node;
+};
+
+/**
+ * Updates the text represented by the SVG DOM nodes.
+ */
+mxText.prototype.updateSvgValue = function(node)
+{
+ if (this.currentValue != this.value)
+ {
+ // Removes all existing children
+ while (node.firstChild != null)
+ {
+ node.removeChild(node.firstChild);
+ }
+
+ if (this.value != null)
+ {
+ // Adds tspan elements for the lines
+ var uline = this.isStyleSet(mxConstants.FONT_UNDERLINE) ? 'underline' : 'none';
+ var lines = this.value.split('\n');
+
+ // Workaround for empty lines breaking the return value of getBBox
+ // for the enclosing g element so we avoid adding empty lines
+ // but still count them as a linefeed
+ this.textNodes = new Array(lines.length);
+
+ for (var i = 0; i < lines.length; i++)
+ {
+ if (!this.isEmptyString(lines[i]))
+ {
+ var tspan = this.createSvgSpan(lines[i]);
+ node.appendChild(tspan);
+ this.textNodes[i] = tspan;
+
+ // Requires either 'inherit' in Webkit or explicit setting
+ // to work in Webkit and IE9 standards mode. Both, inherit
+ // and underline do not work in FF. This is a known bug in
+ // FF (see above).
+ tspan.setAttribute('text-decoration', uline);
+ }
+ else
+ {
+ this.textNodes[i] = null;
+ }
+ }
+ }
+
+ this.currentValue = this.value;
+ }
+};
+
+/**
+ * Function: redrawSvg
+ *
+ * Updates the SVG node(s) to reflect the latest bounds and scale.
+ */
+mxText.prototype.redrawSvg = function()
+{
+ if (this.node.nodeName == 'foreignObject')
+ {
+ this.redrawHtml();
+
+ return;
+ }
+
+ var fontSize = Math.round(this.size * this.scale);
+
+ if (fontSize <= 0)
+ {
+ this.node.setAttribute('visibility', 'hidden');
+ }
+ else
+ {
+ this.node.removeAttribute('visibility');
+ }
+
+ this.updateSvgValue(this.node);
+ this.node.setAttribute('font-size', fontSize + 'px');
+
+ if (this.opacity != null)
+ {
+ // Improves opacity performance in Firefox
+ this.node.setAttribute('fill-opacity', this.opacity/100);
+ this.node.setAttribute('stroke-opacity', this.opacity/100);
+ }
+
+ // Workaround to avoid the use of getBBox to find the size
+ // of the label. A temporary HTML table is created instead.
+ var previous = this.value;
+ var table = this.createHtmlTable();
+
+ // Makes sure the table is updated and replaces all HTML entities
+ this.lastValue = null;
+ this.value = mxUtils.htmlEntities(this.value, false);
+ this.updateHtmlTable(table);
+
+ // Adds the table to the DOM to find the actual size
+ document.body.appendChild(table);
+ var w = table.offsetWidth * this.scale;
+ var h = table.offsetHeight * this.scale;
+
+ // Cleans up the DOM and restores the original value
+ table.parentNode.removeChild(table);
+ this.value = previous;
+
+ // Sets the bounding box for the unclipped case so that
+ // the full background can be painted using it, the initial
+ // value for dx and the +4 in the width below are for
+ // error correction of the HTML and SVG text width
+ var dx = 2 * this.scale;
+
+ if (this.align == mxConstants.ALIGN_CENTER)
+ {
+ dx += w / 2;
+ }
+ else if (this.align == mxConstants.ALIGN_RIGHT)
+ {
+ dx += w;
+ }
+
+ var dy = Math.round(fontSize * 1.3);
+ var childCount = this.node.childNodes.length;
+ var lineCount = (this.textNodes != null) ? this.textNodes.length : 0;
+
+ if (this.backgroundNode != null)
+ {
+ childCount--;
+ }
+
+ var x = this.bounds.x;
+ var y = this.bounds.y;
+
+ x += (this.align == mxConstants.ALIGN_RIGHT) ?
+ ((this.horizontal) ? this.bounds.width : this.bounds.height)-
+ this.spacingRight * this.scale :
+ (this.align == mxConstants.ALIGN_CENTER) ?
+ this.spacingLeft * this.scale +
+ (((this.horizontal) ? this.bounds.width : this.bounds.height) -
+ this.spacingLeft * this.scale - this.spacingRight * this.scale) / 2 :
+ this.spacingLeft * this.scale + 1;
+
+ // Makes sure the alignment is like in VML and HTML
+ y += (this.valign == mxConstants.ALIGN_BOTTOM) ?
+ ((this.horizontal) ? this.bounds.height : this.bounds.width) -
+ (lineCount - 1) * dy - this.spacingBottom * this.scale - 4 :
+ (this.valign == mxConstants.ALIGN_MIDDLE) ?
+ (this.spacingTop * this.scale +
+ ((this.horizontal) ? this.bounds.height : this.bounds.width) -
+ this.spacingBottom * this.scale -
+ (lineCount - 1.5) * dy) / 2 :
+ this.spacingTop * this.scale + dy;
+
+ if (this.overflow == 'fill')
+ {
+ if (this.align == mxConstants.ALIGN_CENTER)
+ {
+ x = Math.max(this.bounds.x + w / 2, x);
+ }
+
+ y = Math.max(this.bounds.y + fontSize, y);
+
+ this.boundingBox = new mxRectangle(x - dx, y - dy,
+ w + 4 * this.scale, h + 1 * this.scale);
+ this.boundingBox.x = Math.min(this.bounds.x, this.boundingBox.x);
+ this.boundingBox.y = Math.min(this.bounds.y, this.boundingBox.y);
+ this.boundingBox.width = Math.max(this.bounds.width, this.boundingBox.width);
+ this.boundingBox.height = Math.max(this.bounds.height, this.boundingBox.height);
+ }
+ else
+ {
+ this.boundingBox = new mxRectangle(x - dx, y - dy,
+ w + 4 * this.scale, h + 1 * this.scale);
+ }
+
+ if (!this.horizontal)
+ {
+ var cx = this.bounds.x + this.bounds.width / 2;
+ var cy = this.bounds.y + this.bounds.height / 2;
+
+ var offsetX = (this.bounds.width - this.bounds.height) / 2;
+ var offsetY = (this.bounds.height - this.bounds.width) / 2;
+
+ this.node.setAttribute('transform',
+ 'rotate(' + this.verticalTextDegree + ' ' + cx + ' ' + cy + ') ' +
+ 'translate(' + (-offsetY) + ' ' + (-offsetX) + ')');
+ }
+
+ // TODO: Font-shadow
+ this.redrawSvgTextNodes(x, y, dy);
+
+ /*
+ * FIXME: Bounding box is not rotated. This seems to be a problem for
+ * all vertical text boxes. Workaround is in mxImageExport.
+ if (!this.horizontal)
+ {
+ var b = this.bounds.y + this.bounds.height;
+ var cx = this.boundingBox.getCenterX() - this.bounds.x;
+ var cy = this.boundingBox.getCenterY() - this.bounds.y;
+
+ var y = b - cx - this.bounds.height / 2;
+ this.boundingBox.x = this.bounds.x + cy - this.boundingBox.width / 2;
+ this.boundingBox.y = y;
+ }
+ */
+
+ // Updates the bounds of the background node if one exists
+ if (this.value.length > 0 && this.backgroundNode != null && this.node.firstChild != null)
+ {
+ if (this.node.firstChild != this.backgroundNode)
+ {
+ this.node.insertBefore(this.backgroundNode, this.node.firstChild);
+ }
+
+ // FIXME: For larger font sizes the linespacing between HTML and SVG
+ // seems to be different and hence the bounding box isn't accurate.
+ // Also in Firefox the background box is slighly offset.
+ this.backgroundNode.setAttribute('x', this.boundingBox.x + this.scale / 2 + 1 * this.scale);
+ this.backgroundNode.setAttribute('y', this.boundingBox.y + this.scale / 2 + 2 * this.scale - this.labelPadding);
+ this.backgroundNode.setAttribute('width', this.boundingBox.width - this.scale - 2 * this.scale);
+ this.backgroundNode.setAttribute('height', this.boundingBox.height - this.scale);
+
+ var strokeWidth = Math.round(Math.max(1, this.scale));
+ this.backgroundNode.setAttribute('stroke-width', strokeWidth);
+ }
+
+ // Adds clipping and updates the bounding box
+ if (this.clipped && this.bounds.width > 0 && this.bounds.height > 0)
+ {
+ this.boundingBox = this.bounds.clone();
+
+ if (!this.horizontal)
+ {
+ this.boundingBox.width = this.bounds.height;
+ this.boundingBox.height = this.bounds.width;
+ }
+
+ x = this.bounds.x;
+ y = this.bounds.y;
+
+ if (this.horizontal)
+ {
+ w = this.bounds.width;
+ h = this.bounds.height;
+ }
+ else
+ {
+ w = this.bounds.height;
+ h = this.bounds.width;
+ }
+
+ var clip = this.getSvgClip(this.node.ownerSVGElement, x, y, w, h);
+
+ if (clip != this.clip)
+ {
+ this.releaseSvgClip();
+ this.clip = clip;
+ clip.refCount++;
+ }
+
+ this.node.setAttribute('clip-path', 'url(#' + clip.getAttribute('id') + ')');
+ }
+ else
+ {
+ this.releaseSvgClip();
+ this.node.removeAttribute('clip-path');
+ }
+};
+
+/**
+ * Function: redrawSvgTextNodes
+ *
+ * Hook to update the position of the SVG text nodes.
+ */
+mxText.prototype.redrawSvgTextNodes = function(x, y, dy)
+{
+ if (this.textNodes != null)
+ {
+ var currentY = y;
+
+ for (var i = 0; i < this.textNodes.length; i++)
+ {
+ var node = this.textNodes[i];
+
+ if (node != null)
+ {
+ node.setAttribute('x', x);
+ node.setAttribute('y', currentY);
+
+ // Triggers an update in Firefox 1.5.0.x (don't add a semicolon!)
+ node.setAttribute('style', 'pointer-events: all');
+ }
+
+ currentY += dy;
+ }
+ }
+};
+
+/**
+ * Function: releaseSvgClip
+ *
+ * Releases the given SVG clip removing it from the DOM if required.
+ */
+mxText.prototype.releaseSvgClip = function()
+{
+ if (this.clip != null)
+ {
+ this.clip.refCount--;
+
+ if (this.clip.refCount == 0)
+ {
+ this.clip.parentNode.removeChild(this.clip);
+ }
+
+ this.clip = null;
+ }
+};
+
+/**
+ * Function: getSvgClip
+ *
+ * Returns a new or existing SVG clip path which is a descendant of the given
+ * SVG node with a unique ID.
+ */
+mxText.prototype.getSvgClip = function(svg, x, y, w, h)
+{
+ x = Math.round(x);
+ y = Math.round(y);
+ w = Math.round(w);
+ h = Math.round(h);
+
+ var id = 'mx-clip-' + x + '-' + y + '-' + w + '-' + h;
+
+ // Quick access
+ if (this.clip != null && this.clip.ident == id)
+ {
+ return this.clip;
+ }
+
+ var counter = 0;
+ var tmp = id + '-' + counter;
+ var clip = document.getElementById(tmp);
+
+ // Tries to find an existing clip in the given SVG
+ while (clip != null)
+ {
+ if (clip.ownerSVGElement == svg)
+ {
+ return clip;
+ }
+
+ counter++;
+ tmp = id + '-' + counter;
+ clip = document.getElementById(tmp);
+ }
+
+ // Creates a new clip node and adds it to the DOM
+ if (clip != null)
+ {
+ clip = clip.cloneNode(true);
+ counter++;
+ }
+ else
+ {
+ clip = document.createElementNS(mxConstants.NS_SVG, 'clipPath');
+
+ var rect = document.createElementNS(mxConstants.NS_SVG, 'rect');
+ rect.setAttribute('x', x);
+ rect.setAttribute('y', y);
+ rect.setAttribute('width', w);
+ rect.setAttribute('height', h);
+
+ clip.appendChild(rect);
+ }
+
+ clip.setAttribute('id', id + '-' + counter);
+ clip.ident = id; // For quick access above
+ svg.appendChild(clip);
+ clip.refCount = 0;
+
+ return clip;
+};
+
+/**
+ * Function: isEmptyString
+ *
+ * Returns true if the given string is empty or
+ * contains only whitespace.
+ */
+mxText.prototype.isEmptyString = function(text)
+{
+ return text.replace(/ /g, '').length == 0;
+};
+
+/**
+ * Function: createSvgSpan
+ *
+ * Creats an SVG tspan node for the given text.
+ */
+mxText.prototype.createSvgSpan = function(text)
+{
+ // Creates a text node since there is no enclosing text element but
+ // rather a group, which is required to render the background rectangle
+ // in Webkit. This can be changed to tspan if the enclosing node is
+ // a text but this leads to an hidden background in Webkit.
+ var node = document.createElementNS(mxConstants.NS_SVG, 'text');
+ // Needed to preserve multiple white spaces, but ignored in IE9 plus white-space:pre
+ // is ignored in HTML output for VML, so better to not use this for SVG labels
+ // node.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve')
+ // Alternative idea is to replace all spaces with &nbsp; to fix HTML in IE, but
+ // IE9/10 with SVG will still ignore the xml:space preserve tag as discussed here:
+ // http://stackoverflow.com/questions/8086292/significant-whitespace-in-svg-embedded-in-html
+ // Could replace spaces with &nbsp; in text but HTML tags must be scaped first.
+ mxUtils.write(node, text);
+
+ return node;
+};
+
+/**
+ * Function: destroy
+ *
+ * Extends destroy to remove any allocated SVG clips.
+ */
+mxText.prototype.destroy = function()
+{
+ this.releaseSvgClip();
+ mxShape.prototype.destroy.apply(this, arguments);
+};
diff --git a/src/js/shape/mxTriangle.js b/src/js/shape/mxTriangle.js
new file mode 100644
index 0000000..3a48db2
--- /dev/null
+++ b/src/js/shape/mxTriangle.js
@@ -0,0 +1,34 @@
+/**
+ * $Id: mxTriangle.js,v 1.10 2011-09-02 10:01:00 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxTriangle
+ *
+ * Implementation of the triangle shape.
+ *
+ * Constructor: mxTriangle
+ *
+ * Constructs a new triangle shape.
+ */
+function mxTriangle() { };
+
+/**
+ * Extends <mxActor>.
+ */
+mxTriangle.prototype = new mxActor();
+mxTriangle.prototype.constructor = mxTriangle;
+
+/**
+ * Function: redrawPath
+ *
+ * Draws the path for this shape. This method uses the <mxPath>
+ * abstraction to paint the shape for VML and SVG.
+ */
+mxTriangle.prototype.redrawPath = function(path, x, y, w, h)
+{
+ path.moveTo(0, 0);
+ path.lineTo(w, 0.5 * h);
+ path.lineTo(0, h);
+ path.close();
+};
diff --git a/src/js/util/mxAnimation.js b/src/js/util/mxAnimation.js
new file mode 100644
index 0000000..80901ef
--- /dev/null
+++ b/src/js/util/mxAnimation.js
@@ -0,0 +1,82 @@
+/**
+ * $Id: mxAnimation.js,v 1.2 2010-03-19 12:53:29 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ *
+ * Class: mxAnimation
+ *
+ * Implements a basic animation in JavaScript.
+ *
+ * Constructor: mxAnimation
+ *
+ * Constructs an animation.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxAnimation(delay)
+{
+ this.delay = (delay != null) ? delay : 20;
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxAnimation.prototype = new mxEventSource();
+mxAnimation.prototype.constructor = mxAnimation;
+
+/**
+ * Variable: delay
+ *
+ * Specifies the delay between the animation steps. Defaul is 30ms.
+ */
+mxAnimation.prototype.delay = null;
+
+/**
+ * Variable: thread
+ *
+ * Reference to the thread while the animation is running.
+ */
+mxAnimation.prototype.thread = null;
+
+/**
+ * Function: startAnimation
+ *
+ * Starts the animation by repeatedly invoking updateAnimation.
+ */
+mxAnimation.prototype.startAnimation = function()
+{
+ if (this.thread == null)
+ {
+ this.thread = window.setInterval(mxUtils.bind(this, this.updateAnimation), this.delay);
+ }
+};
+
+/**
+ * Function: updateAnimation
+ *
+ * Hook for subclassers to implement the animation. Invoke stopAnimation
+ * when finished, startAnimation to resume. This is called whenever the
+ * timer fires and fires an mxEvent.EXECUTE event with no properties.
+ */
+mxAnimation.prototype.updateAnimation = function()
+{
+ this.fireEvent(new mxEventObject(mxEvent.EXECUTE));
+};
+
+/**
+ * Function: stopAnimation
+ *
+ * Stops the animation by deleting the timer and fires an <mxEvent.DONE>.
+ */
+mxAnimation.prototype.stopAnimation = function()
+{
+ if (this.thread != null)
+ {
+ window.clearInterval(this.thread);
+ this.thread = null;
+ this.fireEvent(new mxEventObject(mxEvent.DONE));
+ }
+};
diff --git a/src/js/util/mxAutoSaveManager.js b/src/js/util/mxAutoSaveManager.js
new file mode 100644
index 0000000..85c23dc
--- /dev/null
+++ b/src/js/util/mxAutoSaveManager.js
@@ -0,0 +1,213 @@
+/**
+ * $Id: mxAutoSaveManager.js,v 1.9 2010-09-16 09:10:21 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxAutoSaveManager
+ *
+ * Manager for automatically saving diagrams. The <save> hook must be
+ * implemented.
+ *
+ * Example:
+ *
+ * (code)
+ * var mgr = new mxAutoSaveManager(editor.graph);
+ * mgr.save = function()
+ * {
+ * mxLog.show();
+ * mxLog.debug('save');
+ * };
+ * (end)
+ *
+ * Constructor: mxAutoSaveManager
+ *
+ * Constructs a new automatic layout for the given graph.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing graph.
+ */
+function mxAutoSaveManager(graph)
+{
+ // Notifies the manager of a change
+ this.changeHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.graphModelChanged(evt.getProperty('edit').changes);
+ }
+ });
+
+ this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxAutoSaveManager.prototype = new mxEventSource();
+mxAutoSaveManager.prototype.constructor = mxAutoSaveManager;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxAutoSaveManager.prototype.graph = null;
+
+/**
+ * Variable: autoSaveDelay
+ *
+ * Minimum amount of seconds between two consecutive autosaves. Eg. a
+ * value of 1 (s) means the graph is not stored more than once per second.
+ * Default is 10.
+ */
+mxAutoSaveManager.prototype.autoSaveDelay = 10;
+
+/**
+ * Variable: autoSaveThrottle
+ *
+ * Minimum amount of seconds between two consecutive autosaves triggered by
+ * more than <autoSaveThreshhold> changes within a timespan of less than
+ * <autoSaveDelay> seconds. Eg. a value of 1 (s) means the graph is not
+ * stored more than once per second even if there are more than
+ * <autoSaveThreshold> changes within that timespan. Default is 2.
+ */
+mxAutoSaveManager.prototype.autoSaveThrottle = 2;
+
+/**
+ * Variable: autoSaveThreshold
+ *
+ * Minimum amount of ignored changes before an autosave. Eg. a value of 2
+ * means after 2 change of the graph model the autosave will trigger if the
+ * condition below is true. Default is 5.
+ */
+mxAutoSaveManager.prototype.autoSaveThreshold = 5;
+
+/**
+ * Variable: ignoredChanges
+ *
+ * Counter for ignored changes in autosave.
+ */
+mxAutoSaveManager.prototype.ignoredChanges = 0;
+
+/**
+ * Variable: lastSnapshot
+ *
+ * Used for autosaving. See <autosave>.
+ */
+mxAutoSaveManager.prototype.lastSnapshot = 0;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxAutoSaveManager.prototype.enabled = true;
+
+/**
+ * Variable: changeHandler
+ *
+ * Holds the function that handles graph model changes.
+ */
+mxAutoSaveManager.prototype.changeHandler = null;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxAutoSaveManager.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxAutoSaveManager.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: setGraph
+ *
+ * Sets the graph that the layouts operate on.
+ */
+mxAutoSaveManager.prototype.setGraph = function(graph)
+{
+ if (this.graph != null)
+ {
+ this.graph.getModel().removeListener(this.changeHandler);
+ }
+
+ this.graph = graph;
+
+ if (this.graph != null)
+ {
+ this.graph.getModel().addListener(mxEvent.CHANGE, this.changeHandler);
+ }
+};
+
+/**
+ * Function: save
+ *
+ * Empty hook that is called if the graph should be saved.
+ */
+mxAutoSaveManager.prototype.save = function()
+{
+ // empty
+};
+
+/**
+ * Function: graphModelChanged
+ *
+ * Invoked when the graph model has changed.
+ */
+mxAutoSaveManager.prototype.graphModelChanged = function(changes)
+{
+ var now = new Date().getTime();
+ var dt = (now - this.lastSnapshot) / 1000;
+
+ if (dt > this.autoSaveDelay ||
+ (this.ignoredChanges >= this.autoSaveThreshold &&
+ dt > this.autoSaveThrottle))
+ {
+ this.save();
+ this.reset();
+ }
+ else
+ {
+ // Increments the number of ignored changes
+ this.ignoredChanges++;
+ }
+};
+
+/**
+ * Function: reset
+ *
+ * Resets all counters.
+ */
+mxAutoSaveManager.prototype.reset = function()
+{
+ this.lastSnapshot = new Date().getTime();
+ this.ignoredChanges = 0;
+};
+
+/**
+ * Function: destroy
+ *
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxAutoSaveManager.prototype.destroy = function()
+{
+ this.setGraph(null);
+};
diff --git a/src/js/util/mxClipboard.js b/src/js/util/mxClipboard.js
new file mode 100644
index 0000000..e9fec6b
--- /dev/null
+++ b/src/js/util/mxClipboard.js
@@ -0,0 +1,144 @@
+/**
+ * $Id: mxClipboard.js,v 1.29 2010-01-02 09:45:14 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxClipboard =
+{
+ /**
+ * Class: mxClipboard
+ *
+ * Singleton that implements a clipboard for graph cells.
+ *
+ * Example:
+ *
+ * (code)
+ * mxClipboard.copy(graph);
+ * mxClipboard.paste(graph2);
+ * (end)
+ *
+ * This copies the selection cells from the graph to the
+ * clipboard and pastes them into graph2.
+ *
+ * For fine-grained control of the clipboard data the <mxGraph.canExportCell>
+ * and <mxGraph.canImportCell> functions can be overridden.
+ *
+ * Variable: STEPSIZE
+ *
+ * Defines the step size to offset the cells
+ * after each paste operation. Default is 10.
+ */
+ STEPSIZE: 10,
+
+ /**
+ * Variable: insertCount
+ *
+ * Counts the number of times the clipboard data has been inserted.
+ */
+ insertCount: 1,
+
+ /**
+ * Variable: cells
+ *
+ * Holds the array of <mxCells> currently in the clipboard.
+ */
+ cells: null,
+
+ /**
+ * Function: isEmpty
+ *
+ * Returns true if the clipboard currently has not data stored.
+ */
+ isEmpty: function()
+ {
+ return mxClipboard.cells == null;
+ },
+
+ /**
+ * Function: cut
+ *
+ * Cuts the given array of <mxCells> from the specified graph.
+ * If cells is null then the selection cells of the graph will
+ * be used. Returns the cells that have been cut from the graph.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> that contains the cells to be cut.
+ * cells - Optional array of <mxCells> to be cut.
+ */
+ cut: function(graph, cells)
+ {
+ cells = mxClipboard.copy(graph, cells);
+ mxClipboard.insertCount = 0;
+ mxClipboard.removeCells(graph, cells);
+
+ return cells;
+ },
+
+ /**
+ * Function: removeCells
+ *
+ * Hook to remove the given cells from the given graph after
+ * a cut operation.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> that contains the cells to be cut.
+ * cells - Array of <mxCells> to be cut.
+ */
+ removeCells: function(graph, cells)
+ {
+ graph.removeCells(cells);
+ },
+
+ /**
+ * Function: copy
+ *
+ * Copies the given array of <mxCells> from the specified
+ * graph to <cells>.Returns the original array of cells that has
+ * been cloned.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> that contains the cells to be copied.
+ * cells - Optional array of <mxCells> to be copied.
+ */
+ copy: function(graph, cells)
+ {
+ cells = cells || graph.getSelectionCells();
+ var result = graph.getExportableCells(cells);
+ mxClipboard.insertCount = 1;
+ mxClipboard.cells = graph.cloneCells(result);
+
+ return result;
+ },
+
+ /**
+ * Function: paste
+ *
+ * Pastes the <cells> into the specified graph restoring
+ * the relation to <parents>, if possible. If the parents
+ * are no longer in the graph or invisible then the
+ * cells are added to the graph's default or into the
+ * swimlane under the cell's new location if one exists.
+ * The cells are added to the graph using <mxGraph.importCells>.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> to paste the <cells> into.
+ */
+ paste: function(graph)
+ {
+ if (mxClipboard.cells != null)
+ {
+ var cells = graph.getImportableCells(mxClipboard.cells);
+ var delta = mxClipboard.insertCount * mxClipboard.STEPSIZE;
+ var parent = graph.getDefaultParent();
+ cells = graph.importCells(cells, delta, delta, parent);
+
+ // Increments the counter and selects the inserted cells
+ mxClipboard.insertCount++;
+ graph.setSelectionCells(cells);
+ }
+ }
+
+};
diff --git a/src/js/util/mxConstants.js b/src/js/util/mxConstants.js
new file mode 100644
index 0000000..8d11dc1
--- /dev/null
+++ b/src/js/util/mxConstants.js
@@ -0,0 +1,1911 @@
+/**
+ * $Id: mxConstants.js,v 1.127 2012-11-20 09:06:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+ var mxConstants =
+ {
+ /**
+ * Class: mxConstants
+ *
+ * Defines various global constants.
+ *
+ * Variable: DEFAULT_HOTSPOT
+ *
+ * Defines the portion of the cell which is to be used as a connectable
+ * region. Default is 0.3. Possible values are 0 < x <= 1.
+ */
+ DEFAULT_HOTSPOT: 0.3,
+
+ /**
+ * Variable: MIN_HOTSPOT_SIZE
+ *
+ * Defines the minimum size in pixels of the portion of the cell which is
+ * to be used as a connectable region. Default is 8.
+ */
+ MIN_HOTSPOT_SIZE: 8,
+
+ /**
+ * Variable: MAX_HOTSPOT_SIZE
+ *
+ * Defines the maximum size in pixels of the portion of the cell which is
+ * to be used as a connectable region. Use 0 for no maximum. Default is 0.
+ */
+ MAX_HOTSPOT_SIZE: 0,
+
+ /**
+ * Variable: RENDERING_HINT_EXACT
+ *
+ * Defines the exact rendering hint.
+ */
+ RENDERING_HINT_EXACT: 'exact',
+
+ /**
+ * Variable: RENDERING_HINT_FASTER
+ *
+ * Defines the faster rendering hint.
+ */
+ RENDERING_HINT_FASTER: 'faster',
+
+ /**
+ * Variable: RENDERING_HINT_FASTEST
+ *
+ * Defines the fastest rendering hint.
+ */
+ RENDERING_HINT_FASTEST: 'fastest',
+
+ /**
+ * Variable: DIALECT_SVG
+ *
+ * Defines the SVG display dialect name.
+ */
+ DIALECT_SVG: 'svg',
+
+ /**
+ * Variable: DIALECT_VML
+ *
+ * Defines the VML display dialect name.
+ */
+ DIALECT_VML: 'vml',
+
+ /**
+ * Variable: DIALECT_MIXEDHTML
+ *
+ * Defines the mixed HTML display dialect name.
+ */
+ DIALECT_MIXEDHTML: 'mixedHtml',
+
+ /**
+ * Variable: DIALECT_PREFERHTML
+ *
+ * Defines the preferred HTML display dialect name.
+ */
+ DIALECT_PREFERHTML: 'preferHtml',
+
+ /**
+ * Variable: DIALECT_STRICTHTML
+ *
+ * Defines the strict HTML display dialect.
+ */
+ DIALECT_STRICTHTML: 'strictHtml',
+
+ /**
+ * Variable: NS_SVG
+ *
+ * Defines the SVG namespace.
+ */
+ NS_SVG: 'http://www.w3.org/2000/svg',
+
+ /**
+ * Variable: NS_XHTML
+ *
+ * Defines the XHTML namespace.
+ */
+ NS_XHTML: 'http://www.w3.org/1999/xhtml',
+
+ /**
+ * Variable: NS_XLINK
+ *
+ * Defines the XLink namespace.
+ */
+ NS_XLINK: 'http://www.w3.org/1999/xlink',
+
+ /**
+ * Variable: SHADOWCOLOR
+ *
+ * Defines the color to be used to draw shadows in shapes and windows.
+ * Default is gray.
+ */
+ SHADOWCOLOR: 'gray',
+
+ /**
+ * Variable: SHADOW_OFFSET_X
+ *
+ * Specifies the x-offset of the shadow. Default is 2.
+ */
+ SHADOW_OFFSET_X: 2,
+
+ /**
+ * Variable: SHADOW_OFFSET_Y
+ *
+ * Specifies the y-offset of the shadow. Default is 3.
+ */
+ SHADOW_OFFSET_Y: 3,
+
+ /**
+ * Variable: SHADOW_OPACITY
+ *
+ * Defines the opacity for shadows. Default is 1.
+ */
+ SHADOW_OPACITY: 1,
+
+ /**
+ * Variable: NODETYPE_ELEMENT
+ *
+ * DOM node of type ELEMENT.
+ */
+ NODETYPE_ELEMENT: 1,
+
+ /**
+ * Variable: NODETYPE_ATTRIBUTE
+ *
+ * DOM node of type ATTRIBUTE.
+ */
+ NODETYPE_ATTRIBUTE: 2,
+
+ /**
+ * Variable: NODETYPE_TEXT
+ *
+ * DOM node of type TEXT.
+ */
+ NODETYPE_TEXT: 3,
+
+ /**
+ * Variable: NODETYPE_CDATA
+ *
+ * DOM node of type CDATA.
+ */
+ NODETYPE_CDATA: 4,
+
+ /**
+ * Variable: NODETYPE_ENTITY_REFERENCE
+ *
+ * DOM node of type ENTITY_REFERENCE.
+ */
+ NODETYPE_ENTITY_REFERENCE: 5,
+
+ /**
+ * Variable: NODETYPE_ENTITY
+ *
+ * DOM node of type ENTITY.
+ */
+ NODETYPE_ENTITY: 6,
+
+ /**
+ * Variable: NODETYPE_PROCESSING_INSTRUCTION
+ *
+ * DOM node of type PROCESSING_INSTRUCTION.
+ */
+ NODETYPE_PROCESSING_INSTRUCTION: 7,
+
+ /**
+ * Variable: NODETYPE_COMMENT
+ *
+ * DOM node of type COMMENT.
+ */
+ NODETYPE_COMMENT: 8,
+
+ /**
+ * Variable: NODETYPE_DOCUMENT
+ *
+ * DOM node of type DOCUMENT.
+ */
+ NODETYPE_DOCUMENT: 9,
+
+ /**
+ * Variable: NODETYPE_DOCUMENTTYPE
+ *
+ * DOM node of type DOCUMENTTYPE.
+ */
+ NODETYPE_DOCUMENTTYPE: 10,
+
+ /**
+ * Variable: NODETYPE_DOCUMENT_FRAGMENT
+ *
+ * DOM node of type DOCUMENT_FRAGMENT.
+ */
+ NODETYPE_DOCUMENT_FRAGMENT: 11,
+
+ /**
+ * Variable: NODETYPE_NOTATION
+ *
+ * DOM node of type NOTATION.
+ */
+ NODETYPE_NOTATION: 12,
+
+ /**
+ * Variable: TOOLTIP_VERTICAL_OFFSET
+ *
+ * Defines the vertical offset for the tooltip.
+ * Default is 16.
+ */
+ TOOLTIP_VERTICAL_OFFSET: 16,
+
+ /**
+ * Variable: DEFAULT_VALID_COLOR
+ *
+ * Specifies the default valid colorr. Default is #0000FF.
+ */
+ DEFAULT_VALID_COLOR: '#00FF00',
+
+ /**
+ * Variable: DEFAULT_INVALID_COLOR
+ *
+ * Specifies the default invalid color. Default is #FF0000.
+ */
+ DEFAULT_INVALID_COLOR: '#FF0000',
+
+ /**
+ * Variable: HIGHLIGHT_STROKEWIDTH
+ *
+ * Defines the strokewidth to be used for the highlights.
+ * Default is 3.
+ */
+ HIGHLIGHT_STROKEWIDTH: 3,
+
+ /**
+ * Variable: CURSOR_MOVABLE_VERTEX
+ *
+ * Defines the cursor for a movable vertex. Default is 'move'.
+ */
+ CURSOR_MOVABLE_VERTEX: 'move',
+
+ /**
+ * Variable: CURSOR_MOVABLE_EDGE
+ *
+ * Defines the cursor for a movable edge. Default is 'move'.
+ */
+ CURSOR_MOVABLE_EDGE: 'move',
+
+ /**
+ * Variable: CURSOR_LABEL_HANDLE
+ *
+ * Defines the cursor for a movable label. Default is 'default'.
+ */
+ CURSOR_LABEL_HANDLE: 'default',
+
+ /**
+ * Variable: CURSOR_BEND_HANDLE
+ *
+ * Defines the cursor for a movable bend. Default is 'pointer'.
+ */
+ CURSOR_BEND_HANDLE: 'pointer',
+
+ /**
+ * Variable: CURSOR_CONNECT
+ *
+ * Defines the cursor for a connectable state. Default is 'pointer'.
+ */
+ CURSOR_CONNECT: 'pointer',
+
+ /**
+ * Variable: HIGHLIGHT_COLOR
+ *
+ * Defines the color to be used for the cell highlighting.
+ * Use 'none' for no color. Default is #00FF00.
+ */
+ HIGHLIGHT_COLOR: '#00FF00',
+
+ /**
+ * Variable: TARGET_HIGHLIGHT_COLOR
+ *
+ * Defines the color to be used for highlighting a target cell for a new
+ * or changed connection. Note that this may be either a source or
+ * target terminal in the graph. Use 'none' for no color.
+ * Default is #0000FF.
+ */
+ CONNECT_TARGET_COLOR: '#0000FF',
+
+ /**
+ * Variable: INVALID_CONNECT_TARGET_COLOR
+ *
+ * Defines the color to be used for highlighting a invalid target cells
+ * for a new or changed connections. Note that this may be either a source
+ * or target terminal in the graph. Use 'none' for no color. Default is
+ * #FF0000.
+ */
+ INVALID_CONNECT_TARGET_COLOR: '#FF0000',
+
+ /**
+ * Variable: DROP_TARGET_COLOR
+ *
+ * Defines the color to be used for the highlighting target parent cells
+ * (for drag and drop). Use 'none' for no color. Default is #0000FF.
+ */
+ DROP_TARGET_COLOR: '#0000FF',
+
+ /**
+ * Variable: VALID_COLOR
+ *
+ * Defines the color to be used for the coloring valid connection
+ * previews. Use 'none' for no color. Default is #FF0000.
+ */
+ VALID_COLOR: '#00FF00',
+
+ /**
+ * Variable: INVALID_COLOR
+ *
+ * Defines the color to be used for the coloring invalid connection
+ * previews. Use 'none' for no color. Default is #FF0000.
+ */
+ INVALID_COLOR: '#FF0000',
+
+ /**
+ * Variable: EDGE_SELECTION_COLOR
+ *
+ * Defines the color to be used for the selection border of edges. Use
+ * 'none' for no color. Default is #00FF00.
+ */
+ EDGE_SELECTION_COLOR: '#00FF00',
+
+ /**
+ * Variable: VERTEX_SELECTION_COLOR
+ *
+ * Defines the color to be used for the selection border of vertices. Use
+ * 'none' for no color. Default is #00FF00.
+ */
+ VERTEX_SELECTION_COLOR: '#00FF00',
+
+ /**
+ * Variable: VERTEX_SELECTION_STROKEWIDTH
+ *
+ * Defines the strokewidth to be used for vertex selections.
+ * Default is 1.
+ */
+ VERTEX_SELECTION_STROKEWIDTH: 1,
+
+ /**
+ * Variable: EDGE_SELECTION_STROKEWIDTH
+ *
+ * Defines the strokewidth to be used for edge selections.
+ * Default is 1.
+ */
+ EDGE_SELECTION_STROKEWIDTH: 1,
+
+ /**
+ * Variable: SELECTION_DASHED
+ *
+ * Defines the dashed state to be used for the vertex selection
+ * border. Default is true.
+ */
+ VERTEX_SELECTION_DASHED: true,
+
+ /**
+ * Variable: SELECTION_DASHED
+ *
+ * Defines the dashed state to be used for the edge selection
+ * border. Default is true.
+ */
+ EDGE_SELECTION_DASHED: true,
+
+ /**
+ * Variable: GUIDE_COLOR
+ *
+ * Defines the color to be used for the guidelines in mxGraphHandler.
+ * Default is #FF0000.
+ */
+ GUIDE_COLOR: '#FF0000',
+
+ /**
+ * Variable: GUIDE_STROKEWIDTH
+ *
+ * Defines the strokewidth to be used for the guidelines in mxGraphHandler.
+ * Default is 1.
+ */
+ GUIDE_STROKEWIDTH: 1,
+
+ /**
+ * Variable: OUTLINE_COLOR
+ *
+ * Defines the color to be used for the outline rectangle
+ * border. Use 'none' for no color. Default is #0099FF.
+ */
+ OUTLINE_COLOR: '#0099FF',
+
+ /**
+ * Variable: OUTLINE_STROKEWIDTH
+ *
+ * Defines the strokewidth to be used for the outline rectangle
+ * stroke width. Default is 3.
+ */
+ OUTLINE_STROKEWIDTH: (mxClient.IS_IE) ? 2 : 3,
+
+ /**
+ * Variable: HANDLE_SIZE
+ *
+ * Defines the default size for handles. Default is 7.
+ */
+ HANDLE_SIZE: 7,
+
+ /**
+ * Variable: LABEL_HANDLE_SIZE
+ *
+ * Defines the default size for label handles. Default is 4.
+ */
+ LABEL_HANDLE_SIZE: 4,
+
+ /**
+ * Variable: HANDLE_FILLCOLOR
+ *
+ * Defines the color to be used for the handle fill color. Use 'none' for
+ * no color. Default is #00FF00 (green).
+ */
+ HANDLE_FILLCOLOR: '#00FF00',
+
+ /**
+ * Variable: HANDLE_STROKECOLOR
+ *
+ * Defines the color to be used for the handle stroke color. Use 'none' for
+ * no color. Default is black.
+ */
+ HANDLE_STROKECOLOR: 'black',
+
+ /**
+ * Variable: LABEL_HANDLE_FILLCOLOR
+ *
+ * Defines the color to be used for the label handle fill color. Use 'none'
+ * for no color. Default is yellow.
+ */
+ LABEL_HANDLE_FILLCOLOR: 'yellow',
+
+ /**
+ * Variable: CONNECT_HANDLE_FILLCOLOR
+ *
+ * Defines the color to be used for the connect handle fill color. Use
+ * 'none' for no color. Default is #0000FF (blue).
+ */
+ CONNECT_HANDLE_FILLCOLOR: '#0000FF',
+
+ /**
+ * Variable: LOCKED_HANDLE_FILLCOLOR
+ *
+ * Defines the color to be used for the locked handle fill color. Use
+ * 'none' for no color. Default is #FF0000 (red).
+ */
+ LOCKED_HANDLE_FILLCOLOR: '#FF0000',
+
+ /**
+ * Variable: OUTLINE_HANDLE_FILLCOLOR
+ *
+ * Defines the color to be used for the outline sizer fill color. Use
+ * 'none' for no color. Default is #00FFFF.
+ */
+ OUTLINE_HANDLE_FILLCOLOR: '#00FFFF',
+
+ /**
+ * Variable: OUTLINE_HANDLE_STROKECOLOR
+ *
+ * Defines the color to be used for the outline sizer stroke color. Use
+ * 'none' for no color. Default is #0033FF.
+ */
+ OUTLINE_HANDLE_STROKECOLOR: '#0033FF',
+
+ /**
+ * Variable: DEFAULT_FONTFAMILY
+ *
+ * Defines the default family for all fonts in points. Default is
+ * Arial,Helvetica.
+ */
+ DEFAULT_FONTFAMILY: 'Arial,Helvetica',
+
+ /**
+ * Variable: DEFAULT_FONTSIZE
+ *
+ * Defines the default size for all fonts in points. Default is 11.
+ */
+ DEFAULT_FONTSIZE: 11,
+
+ /**
+ * Variable: DEFAULT_STARTSIZE
+ *
+ * Defines the default start size for swimlanes. Default is 40.
+ */
+ DEFAULT_STARTSIZE: 40,
+
+ /**
+ * Variable: DEFAULT_MARKERSIZE
+ *
+ * Defines the default size for all markers. Default is 6.
+ */
+ DEFAULT_MARKERSIZE: 6,
+
+ /**
+ * Variable: DEFAULT_IMAGESIZE
+ *
+ * Defines the default width and height for images used in the
+ * label shape. Default is 24.
+ */
+ DEFAULT_IMAGESIZE: 24,
+
+ /**
+ * Variable: ENTITY_SEGMENT
+ *
+ * Defines the length of the horizontal segment of an Entity Relation.
+ * This can be overridden using <mxConstants.STYLE_SEGMENT> style.
+ * Default is 30.
+ */
+ ENTITY_SEGMENT: 30,
+
+ /**
+ * Variable: RECTANGLE_ROUNDING_FACTOR
+ *
+ * Defines the rounding factor for rounded rectangles in percent between
+ * 0 and 1. Values should be smaller than 0.5. Default is 0.15.
+ */
+ RECTANGLE_ROUNDING_FACTOR: 0.15,
+
+ /**
+ * Variable: LINE_ARCSIZE
+ *
+ * Defines the size of the arcs for rounded edges. Default is 20.
+ */
+ LINE_ARCSIZE: 20,
+
+ /**
+ * Variable: ARROW_SPACING
+ *
+ * Defines the spacing between the arrow shape and its terminals. Default
+ * is 10.
+ */
+ ARROW_SPACING: 10,
+
+ /**
+ * Variable: ARROW_WIDTH
+ *
+ * Defines the width of the arrow shape. Default is 30.
+ */
+ ARROW_WIDTH: 30,
+
+ /**
+ * Variable: ARROW_SIZE
+ *
+ * Defines the size of the arrowhead in the arrow shape. Default is 30.
+ */
+ ARROW_SIZE: 30,
+
+ /**
+ * Variable: PAGE_FORMAT_A4_PORTRAIT
+ *
+ * Defines the rectangle for the A4 portrait page format. The dimensions
+ * of this page format are 826x1169 pixels.
+ */
+ PAGE_FORMAT_A4_PORTRAIT: new mxRectangle(0, 0, 826, 1169),
+
+ /**
+ * Variable: PAGE_FORMAT_A4_PORTRAIT
+ *
+ * Defines the rectangle for the A4 portrait page format. The dimensions
+ * of this page format are 826x1169 pixels.
+ */
+ PAGE_FORMAT_A4_LANDSCAPE: new mxRectangle(0, 0, 1169, 826),
+
+ /**
+ * Variable: PAGE_FORMAT_LETTER_PORTRAIT
+ *
+ * Defines the rectangle for the Letter portrait page format. The
+ * dimensions of this page format are 850x1100 pixels.
+ */
+ PAGE_FORMAT_LETTER_PORTRAIT: new mxRectangle(0, 0, 850, 1100),
+
+ /**
+ * Variable: PAGE_FORMAT_LETTER_PORTRAIT
+ *
+ * Defines the rectangle for the Letter portrait page format. The dimensions
+ * of this page format are 850x1100 pixels.
+ */
+ PAGE_FORMAT_LETTER_LANDSCAPE: new mxRectangle(0, 0, 1100, 850),
+
+ /**
+ * Variable: NONE
+ *
+ * Defines the value for none. Default is "none".
+ */
+ NONE: 'none',
+
+ /**
+ * Variable: STYLE_PERIMETER
+ *
+ * Defines the key for the perimeter style. This is a function that defines
+ * the perimeter around a particular shape. Possible values are the
+ * functions defined in <mxPerimeter>. Alternatively, the constants in this
+ * class that start with <code>PERIMETER_</code> may be used to access
+ * perimeter styles in <mxStyleRegistry>.
+ */
+ STYLE_PERIMETER: 'perimeter',
+
+ /**
+ * Variable: STYLE_SOURCE_PORT
+ *
+ * Defines the ID of the cell that should be used for computing the
+ * perimeter point of the source for an edge. This allows for graphically
+ * connecting to a cell while keeping the actual terminal of the edge.
+ */
+ STYLE_SOURCE_PORT: 'sourcePort',
+
+ /**
+ * Variable: STYLE_TARGET_PORT
+ *
+ * Defines the ID of the cell that should be used for computing the
+ * perimeter point of the target for an edge. This allows for graphically
+ * connecting to a cell while keeping the actual terminal of the edge.
+ */
+ STYLE_TARGET_PORT: 'targetPort',
+
+ /**
+ * Variable: STYLE_PORT_CONSTRAINT
+ *
+ * Defines the direction(s) that edges are allowed to connect to cells in.
+ * Possible values are <code>DIRECTION_NORTH, DIRECTION_SOUTH,
+ * DIRECTION_EAST</code> and <code>DIRECTION_WEST</code>.
+ */
+ STYLE_PORT_CONSTRAINT: 'portConstraint',
+
+ /**
+ * Variable: STYLE_OPACITY
+ *
+ * Defines the key for the opacity style. The type of the value is
+ * numeric and the possible range is 0-100.
+ */
+ STYLE_OPACITY: 'opacity',
+
+ /**
+ * Variable: STYLE_TEXT_OPACITY
+ *
+ * Defines the key for the text opacity style. The type of the value is
+ * numeric and the possible range is 0-100.
+ */
+ STYLE_TEXT_OPACITY: 'textOpacity',
+
+ /**
+ * Variable: STYLE_OVERFLOW
+ *
+ * Defines the key for the overflow style. Possible values are 'visible',
+ * 'hidden' and 'fill'. The default value is 'visible'. This value
+ * specifies how overlapping vertex labels are handled. A value of
+ * 'visible' will show the complete label. A value of 'hidden' will clip
+ * the label so that it does not overlap the vertex bounds. A value of
+ * 'fill' will use the vertex bounds for the label. See
+ * <mxGraph.isLabelClipped>.
+ */
+ STYLE_OVERFLOW: 'overflow',
+
+ /**
+ * Variable: STYLE_ORTHOGONAL
+ *
+ * Defines if the connection points on either end of the edge should be
+ * computed so that the edge is vertical or horizontal if possible and
+ * if the point is not at a fixed location. Default is false. This is
+ * used in <mxGraph.isOrthogonal>, which also returns true if the edgeStyle
+ * of the edge is an elbow or entity.
+ */
+ STYLE_ORTHOGONAL: 'orthogonal',
+
+ /**
+ * Variable: STYLE_EXIT_X
+ *
+ * Defines the key for the horizontal relative coordinate connection point
+ * of an edge with its source terminal.
+ */
+ STYLE_EXIT_X: 'exitX',
+
+ /**
+ * Variable: STYLE_EXIT_Y
+ *
+ * Defines the key for the vertical relative coordinate connection point
+ * of an edge with its source terminal.
+ */
+ STYLE_EXIT_Y: 'exitY',
+
+ /**
+ * Variable: STYLE_EXIT_PERIMETER
+ *
+ * Defines if the perimeter should be used to find the exact entry point
+ * along the perimeter of the source. Possible values are 0 (false) and
+ * 1 (true). Default is 1 (true).
+ */
+ STYLE_EXIT_PERIMETER: 'exitPerimeter',
+
+ /**
+ * Variable: STYLE_ENTRY_X
+ *
+ * Defines the key for the horizontal relative coordinate connection point
+ * of an edge with its target terminal.
+ */
+ STYLE_ENTRY_X: 'entryX',
+
+ /**
+ * Variable: STYLE_ENTRY_Y
+ *
+ * Defines the key for the vertical relative coordinate connection point
+ * of an edge with its target terminal.
+ */
+ STYLE_ENTRY_Y: 'entryY',
+
+ /**
+ * Variable: STYLE_ENTRY_PERIMETER
+ *
+ * Defines if the perimeter should be used to find the exact entry point
+ * along the perimeter of the target. Possible values are 0 (false) and
+ * 1 (true). Default is 1 (true).
+ */
+ STYLE_ENTRY_PERIMETER: 'entryPerimeter',
+
+ /**
+ * Variable: STYLE_WHITE_SPACE
+ *
+ * Defines the key for the white-space style. Possible values are 'nowrap'
+ * and 'wrap'. The default value is 'nowrap'. This value specifies how
+ * white-space inside a HTML vertex label should be handled. A value of
+ * 'nowrap' means the text will never wrap to the next line until a
+ * linefeed is encountered. A value of 'wrap' means text will wrap when
+ * necessary. This style is only used for HTML labels.
+ * See <mxGraph.isWrapping>.
+ */
+ STYLE_WHITE_SPACE: 'whiteSpace',
+
+ /**
+ * Variable: STYLE_ROTATION
+ *
+ * Defines the key for the rotation style. The type of the value is
+ * numeric and the possible range is 0-360.
+ */
+ STYLE_ROTATION: 'rotation',
+
+ /**
+ * Variable: STYLE_FILLCOLOR
+ *
+ * Defines the key for the fill color. Possible values are all HTML color
+ * names or HEX codes, as well as special keywords such as 'swimlane,
+ * 'inherit' or 'indicated' to use the color code of a related cell or the
+ * indicator shape.
+ */
+ STYLE_FILLCOLOR: 'fillColor',
+
+ /**
+ * Variable: STYLE_GRADIENTCOLOR
+ *
+ * Defines the key for the gradient color. Possible values are all HTML color
+ * names or HEX codes, as well as special keywords such as 'swimlane,
+ * 'inherit' or 'indicated' to use the color code of a related cell or the
+ * indicator shape. This is ignored if no fill color is defined.
+ */
+ STYLE_GRADIENTCOLOR: 'gradientColor',
+
+ /**
+ * Variable: STYLE_GRADIENT_DIRECTION
+ *
+ * Defines the key for the gradient direction. Possible values are
+ * <DIRECTION_EAST>, <DIRECTION_WEST>, <DIRECTION_NORTH> and
+ * <DIRECTION_SOUTH>. Default is <DIRECTION_SOUTH>. Generally, and by
+ * default in mxGraph, gradient painting is done from the value of
+ * <STYLE_FILLCOLOR> to the value of <STYLE_GRADIENTCOLOR>. Taking the
+ * example of <DIRECTION_NORTH>, this means <STYLE_FILLCOLOR> color at the
+ * bottom of paint pattern and <STYLE_GRADIENTCOLOR> at top, with a
+ * gradient in-between.
+ */
+ STYLE_GRADIENT_DIRECTION: 'gradientDirection',
+
+ /**
+ * Variable: STYLE_STROKECOLOR
+ *
+ * Defines the key for the strokeColor style. Possible values are all HTML
+ * color names or HEX codes, as well as special keywords such as 'swimlane,
+ * 'inherit', 'indicated' to use the color code of a related cell or the
+ * indicator shape or 'none' for no color.
+ */
+ STYLE_STROKECOLOR: 'strokeColor',
+
+ /**
+ * Variable: STYLE_SEPARATORCOLOR
+ *
+ * Defines the key for the separatorColor style. Possible values are all
+ * HTML color names or HEX codes. This style is only used for
+ * <SHAPE_SWIMLANE> shapes.
+ */
+ STYLE_SEPARATORCOLOR: 'separatorColor',
+
+ /**
+ * Variable: STYLE_STROKEWIDTH
+ *
+ * Defines the key for the strokeWidth style. The type of the value is
+ * numeric and the possible range is any non-negative value larger or equal
+ * to 1. The value defines the stroke width in pixels. Note: To hide a
+ * stroke use strokeColor none.
+ */
+ STYLE_STROKEWIDTH: 'strokeWidth',
+
+ /**
+ * Variable: STYLE_ALIGN
+ *
+ * Defines the key for the align style. Possible values are <ALIGN_LEFT>,
+ * <ALIGN_CENTER> and <ALIGN_RIGHT>. This value defines how the lines of
+ * the label are horizontally aligned. <ALIGN_LEFT> mean label text lines
+ * are aligned to left of the label bounds, <ALIGN_RIGHT> to the right of
+ * the label bounds and <ALIGN_CENTER> means the center of the text lines
+ * are aligned in the center of the label bounds. Note this value doesn't
+ * affect the positioning of the overall label bounds relative to the
+ * vertex, to move the label bounds horizontally, use
+ * <STYLE_LABEL_POSITION>.
+ */
+ STYLE_ALIGN: 'align',
+
+ /**
+ * Variable: STYLE_VERTICAL_ALIGN
+ *
+ * Defines the key for the verticalAlign style. Possible values are
+ * <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>. This value defines how
+ * the lines of the label are vertically aligned. <ALIGN_TOP> means the
+ * topmost label text line is aligned against the top of the label bounds,
+ * <ALIGN_BOTTOM> means the bottom-most label text line is aligned against
+ * the bottom of the label bounds and <ALIGN_MIDDLE> means there is equal
+ * spacing between the topmost text label line and the top of the label
+ * bounds and the bottom-most text label line and the bottom of the label
+ * bounds. Note this value doesn't affect the positioning of the overall
+ * label bounds relative to the vertex, to move the label bounds
+ * vertically, use <STYLE_VERTICAL_LABEL_POSITION>.
+ */
+ STYLE_VERTICAL_ALIGN: 'verticalAlign',
+
+ /**
+ * Variable: STYLE_LABEL_POSITION
+ *
+ * Defines the key for the horizontal label position of vertices. Possible
+ * values are <ALIGN_LEFT>, <ALIGN_CENTER> and <ALIGN_RIGHT>. Default is
+ * <ALIGN_CENTER>. The label align defines the position of the label
+ * relative to the cell. <ALIGN_LEFT> means the entire label bounds is
+ * placed completely just to the left of the vertex, <ALIGN_RIGHT> means
+ * adjust to the right and <ALIGN_CENTER> means the label bounds are
+ * vertically aligned with the bounds of the vertex. Note this value
+ * doesn't affect the positioning of label within the label bounds, to move
+ * the label horizontally within the label bounds, use <STYLE_ALIGN>.
+ */
+ STYLE_LABEL_POSITION: 'labelPosition',
+
+ /**
+ * Variable: STYLE_VERTICAL_LABEL_POSITION
+ *
+ * Defines the key for the vertical label position of vertices. Possible
+ * values are <ALIGN_TOP>, <ALIGN_BOTTOM> and <ALIGN_MIDDLE>. Default is
+ * <ALIGN_MIDDLE>. The label align defines the position of the label
+ * relative to the cell. <ALIGN_TOP> means the entire label bounds is
+ * placed completely just on the top of the vertex, <ALIGN_BOTTOM> means
+ * adjust on the bottom and <ALIGN_MIDDLE> means the label bounds are
+ * horizontally aligned with the bounds of the vertex. Note this value
+ * doesn't affect the positioning of label within the label bounds, to move
+ * the label vertically within the label bounds, use
+ * <STYLE_VERTICAL_ALIGN>.
+ */
+ STYLE_VERTICAL_LABEL_POSITION: 'verticalLabelPosition',
+
+ /**
+ * Variable: STYLE_IMAGE_ASPECT
+ *
+ * Defines the key for the image aspect style. Possible values are 0 (do
+ * not preserve aspect) or 1 (keep aspect). This is only used in
+ * <mxImageShape>. Default is 1.
+ */
+ STYLE_IMAGE_ASPECT: 'imageAspect',
+
+ /**
+ * Variable: STYLE_IMAGE_ALIGN
+ *
+ * Defines the key for the align style. Possible values are <ALIGN_LEFT>,
+ * <ALIGN_CENTER> and <ALIGN_RIGHT>. The value defines how any image in the
+ * vertex label is aligned horizontally within the label bounds of a
+ * <SHAPE_LABEL> shape.
+ */
+ STYLE_IMAGE_ALIGN: 'imageAlign',
+
+ /**
+ * Variable: STYLE_IMAGE_VERTICAL_ALIGN
+ *
+ * Defines the key for the verticalAlign style. Possible values are
+ * <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>. The value defines how
+ * any image in the vertex label is aligned vertically within the label
+ * bounds of a <SHAPE_LABEL> shape.
+ */
+ STYLE_IMAGE_VERTICAL_ALIGN: 'imageVerticalAlign',
+
+ /**
+ * Variable: STYLE_GLASS
+ *
+ * Defines the key for the glass style. Possible values are 0 (disabled) and
+ * 1(enabled). The default value is 0. This is used in <mxLabel>.
+ */
+ STYLE_GLASS: 'glass',
+
+ /**
+ * Variable: STYLE_IMAGE
+ *
+ * Defines the key for the image style. Possible values are any image URL,
+ * the type of the value is String. This is the path to the image to image
+ * that is to be displayed within the label of a vertex. Data URLs should
+ * use the following format: data:image/png,xyz where xyz is the base64
+ * encoded data (without the "base64"-prefix). Note that Data URLs are only
+ * supported in modern browsers.
+ */
+ STYLE_IMAGE: 'image',
+
+ /**
+ * Variable: STYLE_IMAGE_WIDTH
+ *
+ * Defines the key for the imageWidth style. The type of this value is
+ * int, the value is the image width in pixels and must be greater than 0.
+ */
+ STYLE_IMAGE_WIDTH: 'imageWidth',
+
+ /**
+ * Variable: STYLE_IMAGE_HEIGHT
+ *
+ * Defines the key for the imageHeight style. The type of this value is
+ * int, the value is the image height in pixels and must be greater than 0.
+ */
+ STYLE_IMAGE_HEIGHT: 'imageHeight',
+
+ /**
+ * Variable: STYLE_IMAGE_BACKGROUND
+ *
+ * Defines the key for the image background color. This style is only used
+ * in <mxImageShape>. Possible values are all HTML color names or HEX
+ * codes.
+ */
+ STYLE_IMAGE_BACKGROUND: 'imageBackground',
+
+ /**
+ * Variable: STYLE_IMAGE_BORDER
+ *
+ * Defines the key for the image border color. This style is only used in
+ * <mxImageShape>. Possible values are all HTML color names or HEX codes.
+ */
+ STYLE_IMAGE_BORDER: 'imageBorder',
+
+ /**
+ * Variable: STYLE_IMAGE_FLIPH
+ *
+ * Defines the key for the horizontal image flip. This style is only used
+ * in <mxImageShape>. Possible values are 0 and 1. Default is 0.
+ */
+ STYLE_IMAGE_FLIPH: 'imageFlipH',
+
+ /**
+ * Variable: STYLE_IMAGE_FLIPV
+ *
+ * Defines the key for the vertical image flip. This style is only used
+ * in <mxImageShape>. Possible values are 0 and 1. Default is 0.
+ */
+ STYLE_IMAGE_FLIPV: 'imageFlipV',
+
+ /**
+ * Variable: STYLE_STENCIL_FLIPH
+ *
+ * Defines the key for the horizontal stencil flip. This style is only used
+ * for <mxStencilShape>. Possible values are 0 and 1. Default is 0.
+ */
+ STYLE_STENCIL_FLIPH: 'stencilFlipH',
+
+ /**
+ * Variable: STYLE_STENCIL_FLIPV
+ *
+ * Defines the key for the vertical stencil flip. This style is only used
+ * for <mxStencilShape>. Possible values are 0 and 1. Default is 0.
+ */
+ STYLE_STENCIL_FLIPV: 'stencilFlipV',
+
+ /**
+ * Variable: STYLE_NOLABEL
+ *
+ * Defines the key for the noLabel style. If this is
+ * true then no label is visible for a given cell.
+ * Possible values are true or false (1 or 0).
+ * Default is false.
+ */
+ STYLE_NOLABEL: 'noLabel',
+
+ /**
+ * Variable: STYLE_NOEDGESTYLE
+ *
+ * Defines the key for the noEdgeStyle style. If this is
+ * true then no edge style is applied for a given edge.
+ * Possible values are true or false (1 or 0).
+ * Default is false.
+ */
+ STYLE_NOEDGESTYLE: 'noEdgeStyle',
+
+ /**
+ * Variable: STYLE_LABEL_BACKGROUNDCOLOR
+ *
+ * Defines the key for the label background color. Possible values are all
+ * HTML color names or HEX codes.
+ */
+ STYLE_LABEL_BACKGROUNDCOLOR: 'labelBackgroundColor',
+
+ /**
+ * Variable: STYLE_LABEL_BORDERCOLOR
+ *
+ * Defines the key for the label border color. Possible values are all
+ * HTML color names or HEX codes.
+ */
+ STYLE_LABEL_BORDERCOLOR: 'labelBorderColor',
+
+ /**
+ * Variable: STYLE_LABEL_PADDING
+ *
+ * Defines the key for the label padding, ie. the space between the label
+ * border and the label.
+ */
+ STYLE_LABEL_PADDING: 'labelPadding',
+
+ /**
+ * Variable: STYLE_INDICATOR_SHAPE
+ *
+ * Defines the key for the indicator shape used within an <mxLabel>.
+ * Possible values are all SHAPE_* constants or the names of any new
+ * shapes. The indicatorShape has precedence over the indicatorImage.
+ */
+ STYLE_INDICATOR_SHAPE: 'indicatorShape',
+
+ /**
+ * Variable: STYLE_INDICATOR_IMAGE
+ *
+ * Defines the key for the indicator image used within an <mxLabel>.
+ * Possible values are all image URLs. The indicatorShape has
+ * precedence over the indicatorImage.
+ */
+ STYLE_INDICATOR_IMAGE: 'indicatorImage',
+
+ /**
+ * Variable: STYLE_INDICATOR_COLOR
+ *
+ * Defines the key for the indicatorColor style. Possible values are all
+ * HTML color names or HEX codes, as well as the special 'swimlane' keyword
+ * to refer to the color of the parent swimlane if one exists.
+ */
+ STYLE_INDICATOR_COLOR: 'indicatorColor',
+
+ /**
+ * Variable: STYLE_INDICATOR_STROKECOLOR
+ *
+ * Defines the key for the indicator stroke color in <mxLabel>.
+ * Possible values are all color codes.
+ */
+ STYLE_INDICATOR_STROKECOLOR: 'indicatorStrokeColor',
+
+ /**
+ * Variable: STYLE_INDICATOR_GRADIENTCOLOR
+ *
+ * Defines the key for the indicatorGradientColor style. Possible values
+ * are all HTML color names or HEX codes. This style is only supported in
+ * <SHAPE_LABEL> shapes.
+ */
+ STYLE_INDICATOR_GRADIENTCOLOR: 'indicatorGradientColor',
+
+ /**
+ * Variable: STYLE_INDICATOR_SPACING
+ *
+ * The defines the key for the spacing between the label and the
+ * indicator in <mxLabel>. Possible values are in pixels.
+ */
+ STYLE_INDICATOR_SPACING: 'indicatorSpacing',
+
+ /**
+ * Variable: STYLE_INDICATOR_WIDTH
+ *
+ * Defines the key for the indicator width.
+ * Possible values start at 0 (in pixels).
+ */
+ STYLE_INDICATOR_WIDTH: 'indicatorWidth',
+
+ /**
+ * Variable: STYLE_INDICATOR_HEIGHT
+ *
+ * Defines the key for the indicator height.
+ * Possible values start at 0 (in pixels).
+ */
+ STYLE_INDICATOR_HEIGHT: 'indicatorHeight',
+
+ /**
+ * Variable: STYLE_INDICATOR_DIRECTION
+ *
+ * Defines the key for the indicatorDirection style. The direction style is
+ * used to specify the direction of certain shapes (eg. <mxTriangle>).
+ * Possible values are <DIRECTION_EAST> (default), <DIRECTION_WEST>,
+ * <DIRECTION_NORTH> and <DIRECTION_SOUTH>.
+ */
+ STYLE_INDICATOR_DIRECTION: 'indicatorDirection',
+
+ /**
+ * Variable: STYLE_SHADOW
+ *
+ * Defines the key for the shadow style. The type of the value is Boolean.
+ */
+ STYLE_SHADOW: 'shadow',
+
+ /**
+ * Variable: STYLE_SEGMENT
+ *
+ * Defines the key for the segment style. The type of this value is
+ * float and the value represents the size of the horizontal
+ * segment of the entity relation style. Default is ENTITY_SEGMENT.
+ */
+ STYLE_SEGMENT: 'segment',
+
+ /**
+ * Variable: STYLE_ENDARROW
+ *
+ * Defines the key for the end arrow marker.
+ * Possible values are all constants with an ARROW-prefix.
+ * This is only used in <mxConnector>.
+ *
+ * Example:
+ * (code)
+ * style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;
+ * (end)
+ */
+ STYLE_ENDARROW: 'endArrow',
+
+ /**
+ * Variable: STYLE_STARTARROW
+ *
+ * Defines the key for the start arrow marker.
+ * Possible values are all constants with an ARROW-prefix.
+ * This is only used in <mxConnector>.
+ * See <STYLE_ENDARROW>.
+ */
+ STYLE_STARTARROW: 'startArrow',
+
+ /**
+ * Variable: STYLE_ENDSIZE
+ *
+ * Defines the key for the endSize style. The type of this value is numeric
+ * and the value represents the size of the end marker in pixels.
+ */
+ STYLE_ENDSIZE: 'endSize',
+
+ /**
+ * Variable: STYLE_STARTSIZE
+ *
+ * Defines the key for the startSize style. The type of this value is
+ * numeric and the value represents the size of the start marker or the
+ * size of the swimlane title region depending on the shape it is used for.
+ */
+ STYLE_STARTSIZE: 'startSize',
+
+ /**
+ * Variable: STYLE_ENDFILL
+ *
+ * Defines the key for the endFill style. Use 0 for no fill or 1
+ * (default) for fill. (This style is only exported via <mxImageExport>.)
+ */
+ STYLE_ENDFILL: 'endFill',
+
+ /**
+ * Variable: STYLE_STARTFILL
+ *
+ * Defines the key for the startFill style. Use 0 for no fill or 1
+ * (default) for fill. (This style is only exported via <mxImageExport>.)
+ */
+ STYLE_STARTFILL: 'startFill',
+
+ /**
+ * Variable: STYLE_DASHED
+ *
+ * Defines the key for the dashed style. Use 0 (default) for non-dashed or 1
+ * for dashed.
+ */
+ STYLE_DASHED: 'dashed',
+
+ /**
+ * Defines the key for the dashed pattern style in SVG and image exports.
+ * The type of this value is a space separated list of numbers that specify
+ * a custom-defined dash pattern. Dash styles are defined in terms of the
+ * length of the dash (the drawn part of the stroke) and the length of the
+ * space between the dashes. The lengths are relative to the line width: a
+ * length of "1" is equal to the line width. VML ignores this style and
+ * uses dashStyle instead as defined in the VML specification. This style
+ * is only used in the <mxConnector> shape.
+ */
+ STYLE_DASH_PATTERN: 'dashPattern',
+
+ /**
+ * Variable: STYLE_ROUNDED
+ *
+ * Defines the key for the rounded style. The type of this value is
+ * Boolean. For edges this determines whether or not joins between edges
+ * segments are smoothed to a rounded finish. For vertices that have the
+ * rectangle shape, this determines whether or not the rectangle is
+ * rounded.
+ */
+ STYLE_ROUNDED: 'rounded',
+
+ /**
+ * Variable: STYLE_ARCSIZE
+ *
+ * Defines the rounding factor for a rounded rectangle in percent (without
+ * the percent sign). Possible values are between 0 and 100. If this value
+ * is not specified then RECTANGLE_ROUNDING_FACTOR * 100 is used.
+ * (This style is only exported via <mxImageExport>.)
+ */
+ STYLE_ARCSIZE: 'arcSize',
+
+ /**
+ * Variable: STYLE_SMOOTH
+ *
+ * An experimental style for edges. This style is currently not available
+ * in the backends and is implemented differently for VML and SVG. The use
+ * of this style is currently only recommended for VML.
+ */
+ STYLE_SMOOTH: 'smooth',
+
+ /**
+ * Variable: STYLE_SOURCE_PERIMETER_SPACING
+ *
+ * Defines the key for the source perimeter spacing. The type of this value
+ * is numeric. This is the distance between the source connection point of
+ * an edge and the perimeter of the source vertex in pixels. This style
+ * only applies to edges.
+ */
+ STYLE_SOURCE_PERIMETER_SPACING: 'sourcePerimeterSpacing',
+
+ /**
+ * Variable: STYLE_TARGET_PERIMETER_SPACING
+ *
+ * Defines the key for the target perimeter spacing. The type of this value
+ * is numeric. This is the distance between the target connection point of
+ * an edge and the perimeter of the target vertex in pixels. This style
+ * only applies to edges.
+ */
+ STYLE_TARGET_PERIMETER_SPACING: 'targetPerimeterSpacing',
+
+ /**
+ * Variable: STYLE_PERIMETER_SPACING
+ *
+ * Defines the key for the perimeter spacing. This is the distance between
+ * the connection point and the perimeter in pixels. When used in a vertex
+ * style, this applies to all incoming edges to floating ports (edges that
+ * terminate on the perimeter of the vertex). When used in an edge style,
+ * this spacing applies to the source and target separately, if they
+ * terminate in floating ports (on the perimeter of the vertex).
+ */
+ STYLE_PERIMETER_SPACING: 'perimeterSpacing',
+
+ /**
+ * Variable: STYLE_SPACING
+ *
+ * Defines the key for the spacing. The value represents the spacing, in
+ * pixels, added to each side of a label in a vertex (style applies to
+ * vertices only).
+ */
+ STYLE_SPACING: 'spacing',
+
+ /**
+ * Variable: STYLE_SPACING_TOP
+ *
+ * Defines the key for the spacingTop style. The value represents the
+ * spacing, in pixels, added to the top side of a label in a vertex (style
+ * applies to vertices only).
+ */
+ STYLE_SPACING_TOP: 'spacingTop',
+
+ /**
+ * Variable: STYLE_SPACING_LEFT
+ *
+ * Defines the key for the spacingLeft style. The value represents the
+ * spacing, in pixels, added to the left side of a label in a vertex (style
+ * applies to vertices only).
+ */
+ STYLE_SPACING_LEFT: 'spacingLeft',
+
+ /**
+ * Variable: STYLE_SPACING_BOTTOM
+ *
+ * Defines the key for the spacingBottom style The value represents the
+ * spacing, in pixels, added to the bottom side of a label in a vertex
+ * (style applies to vertices only).
+ */
+ STYLE_SPACING_BOTTOM: 'spacingBottom',
+
+ /**
+ * Variable: STYLE_SPACING_RIGHT
+ *
+ * Defines the key for the spacingRight style The value represents the
+ * spacing, in pixels, added to the right side of a label in a vertex (style
+ * applies to vertices only).
+ */
+ STYLE_SPACING_RIGHT: 'spacingRight',
+
+ /**
+ * Variable: STYLE_HORIZONTAL
+ *
+ * Defines the key for the horizontal style. Possible values are
+ * true or false. This value only applies to vertices. If the <STYLE_SHAPE>
+ * is <code>SHAPE_SWIMLANE</code> a value of false indicates that the
+ * swimlane should be drawn vertically, true indicates to draw it
+ * horizontally. If the shape style does not indicate that this vertex is a
+ * swimlane, this value affects only whether the label is drawn
+ * horizontally or vertically.
+ */
+ STYLE_HORIZONTAL: 'horizontal',
+
+ /**
+ * Variable: STYLE_DIRECTION
+ *
+ * Defines the key for the direction style. The direction style is used
+ * to specify the direction of certain shapes (eg. <mxTriangle>).
+ * Possible values are <DIRECTION_EAST> (default), <DIRECTION_WEST>,
+ * <DIRECTION_NORTH> and <DIRECTION_SOUTH>.
+ */
+ STYLE_DIRECTION: 'direction',
+
+ /**
+ * Variable: STYLE_ELBOW
+ *
+ * Defines the key for the elbow style. Possible values are
+ * <ELBOW_HORIZONTAL> and <ELBOW_VERTICAL>. Default is <ELBOW_HORIZONTAL>.
+ * This defines how the three segment orthogonal edge style leaves its
+ * terminal vertices. The vertical style leaves the terminal vertices at
+ * the top and bottom sides.
+ */
+ STYLE_ELBOW: 'elbow',
+
+ /**
+ * Variable: STYLE_FONTCOLOR
+ *
+ * Defines the key for the fontColor style. Possible values are all HTML
+ * color names or HEX codes.
+ */
+ STYLE_FONTCOLOR: 'fontColor',
+
+ /**
+ * Variable: STYLE_FONTFAMILY
+ *
+ * Defines the key for the fontFamily style. Possible values are names such
+ * as Arial; Dialog; Verdana; Times New Roman. The value is of type String.
+ */
+ STYLE_FONTFAMILY: 'fontFamily',
+
+ /**
+ * Variable: STYLE_FONTSIZE
+ *
+ * Defines the key for the fontSize style (in points). The type of the value
+ * is int.
+ */
+ STYLE_FONTSIZE: 'fontSize',
+
+ /**
+ * Variable: STYLE_FONTSTYLE
+ *
+ * Defines the key for the fontStyle style. Values may be any logical AND
+ * (sum) of <FONT_BOLD>, <FONT_ITALIC>, <FONT_UNDERLINE> and <FONT_SHADOW>.
+ * The type of the value is int.
+ */
+ STYLE_FONTSTYLE: 'fontStyle',
+
+ /**
+ * Variable: STYLE_AUTOSIZE
+ *
+ * Defines the key for the autosize style. This specifies if a cell should be
+ * resized automatically if the value has changed. Possible values are 0 or 1.
+ * Default is 0. See <mxGraph.isAutoSizeCell>. This is normally combined with
+ * <STYLE_RESIZABLE> to disable manual sizing.
+ */
+ STYLE_AUTOSIZE: 'autosize',
+
+ /**
+ * Variable: STYLE_FOLDABLE
+ *
+ * Defines the key for the foldable style. This specifies if a cell is foldable
+ * using a folding icon. Possible values are 0 or 1. Default is 1. See
+ * <mxGraph.isCellFoldable>.
+ */
+ STYLE_FOLDABLE: 'foldable',
+
+ /**
+ * Variable: STYLE_EDITABLE
+ *
+ * Defines the key for the editable style. This specifies if the value of
+ * a cell can be edited using the in-place editor. Possible values are 0 or
+ * 1. Default is 1. See <mxGraph.isCellEditable>.
+ */
+ STYLE_EDITABLE: 'editable',
+
+ /**
+ * Variable: STYLE_BENDABLE
+ *
+ * Defines the key for the bendable style. This specifies if the control
+ * points of an edge can be moved. Possible values are 0 or 1. Default is
+ * 1. See <mxGraph.isCellBendable>.
+ */
+ STYLE_BENDABLE: 'bendable',
+
+ /**
+ * Variable: STYLE_MOVABLE
+ *
+ * Defines the key for the movable style. This specifies if a cell can
+ * be moved. Possible values are 0 or 1. Default is 1. See
+ * <mxGraph.isCellMovable>.
+ */
+ STYLE_MOVABLE: 'movable',
+
+ /**
+ * Variable: STYLE_RESIZABLE
+ *
+ * Defines the key for the resizable style. This specifies if a cell can
+ * be resized. Possible values are 0 or 1. Default is 1. See
+ * <mxGraph.isCellResizable>.
+ */
+ STYLE_RESIZABLE: 'resizable',
+
+ /**
+ * Variable: STYLE_CLONEABLE
+ *
+ * Defines the key for the cloneable style. This specifies if a cell can
+ * be cloned. Possible values are 0 or 1. Default is 1. See
+ * <mxGraph.isCellCloneable>.
+ */
+ STYLE_CLONEABLE: 'cloneable',
+
+ /**
+ * Variable: STYLE_DELETABLE
+ *
+ * Defines the key for the deletable style. This specifies if a cell can be
+ * deleted. Possible values are 0 or 1. Default is 1. See
+ * <mxGraph.isCellDeletable>.
+ */
+ STYLE_DELETABLE: 'deletable',
+
+ /**
+ * Variable: STYLE_SHAPE
+ *
+ * Defines the key for the shape. Possible values are all constants
+ * with a SHAPE-prefix or any newly defined shape names.
+ */
+ STYLE_SHAPE: 'shape',
+
+ /**
+ * Variable: STYLE_EDGE
+ *
+ * Defines the key for the edge style. Possible values are the functions
+ * defined in <mxEdgeStyle>.
+ */
+ STYLE_EDGE: 'edgeStyle',
+
+ /**
+ * Variable: STYLE_LOOP
+ *
+ * Defines the key for the loop style. Possible values are the functions
+ * defined in <mxEdgeStyle>.
+ */
+ STYLE_LOOP: 'loopStyle',
+
+ /**
+ * Variable: STYLE_ROUTING_CENTER_X
+ *
+ * Defines the key for the horizontal routing center. Possible values are
+ * between -0.5 and 0.5. This is the relative offset from the center used
+ * for connecting edges. The type of this value is numeric.
+ */
+ STYLE_ROUTING_CENTER_X: 'routingCenterX',
+
+ /**
+ * Variable: STYLE_ROUTING_CENTER_Y
+ *
+ * Defines the key for the vertical routing center. Possible values are
+ * between -0.5 and 0.5. This is the relative offset from the center used
+ * for connecting edges. The type of this value is numeric.
+ */
+ STYLE_ROUTING_CENTER_Y: 'routingCenterY',
+
+ /**
+ * Variable: FONT_BOLD
+ *
+ * Constant for bold fonts. Default is 1.
+ */
+ FONT_BOLD: 1,
+
+ /**
+ * Variable: FONT_ITALIC
+ *
+ * Constant for italic fonts. Default is 2.
+ */
+ FONT_ITALIC: 2,
+
+ /**
+ * Variable: FONT_UNDERLINE
+ *
+ * Constant for underlined fonts. Default is 4.
+ */
+ FONT_UNDERLINE: 4,
+
+ /**
+ * Variable: FONT_SHADOW
+ *
+ * Constant for fonts with a shadow. Default is 8.
+ */
+ FONT_SHADOW: 8,
+
+ /**
+ * Variable: SHAPE_RECTANGLE
+ *
+ * Name under which <mxRectangleShape> is registered
+ * in <mxCellRenderer>. Default is rectangle.
+ */
+ SHAPE_RECTANGLE: 'rectangle',
+
+ /**
+ * Variable: SHAPE_ELLIPSE
+ *
+ * Name under which <mxEllipse> is registered
+ * in <mxCellRenderer>. Default is ellipse.
+ */
+ SHAPE_ELLIPSE: 'ellipse',
+
+ /**
+ * Variable: SHAPE_DOUBLE_ELLIPSE
+ *
+ * Name under which <mxDoubleEllipse> is registered
+ * in <mxCellRenderer>. Default is doubleEllipse.
+ */
+ SHAPE_DOUBLE_ELLIPSE: 'doubleEllipse',
+
+ /**
+ * Variable: SHAPE_RHOMBUS
+ *
+ * Name under which <mxRhombus> is registered
+ * in <mxCellRenderer>. Default is rhombus.
+ */
+ SHAPE_RHOMBUS: 'rhombus',
+
+ /**
+ * Variable: SHAPE_LINE
+ *
+ * Name under which <mxLine> is registered
+ * in <mxCellRenderer>. Default is line.
+ */
+ SHAPE_LINE: 'line',
+
+ /**
+ * Variable: SHAPE_IMAGE
+ *
+ * Name under which <mxImageShape> is registered
+ * in <mxCellRenderer>. Default is image.
+ */
+ SHAPE_IMAGE: 'image',
+
+ /**
+ * Variable: SHAPE_ARROW
+ *
+ * Name under which <mxArrow> is registered
+ * in <mxCellRenderer>. Default is arrow.
+ */
+ SHAPE_ARROW: 'arrow',
+
+ /**
+ * Variable: SHAPE_LABEL
+ *
+ * Name under which <mxLabel> is registered
+ * in <mxCellRenderer>. Default is label.
+ */
+ SHAPE_LABEL: 'label',
+
+ /**
+ * Variable: SHAPE_CYLINDER
+ *
+ * Name under which <mxCylinder> is registered
+ * in <mxCellRenderer>. Default is cylinder.
+ */
+ SHAPE_CYLINDER: 'cylinder',
+
+ /**
+ * Variable: SHAPE_SWIMLANE
+ *
+ * Name under which <mxSwimlane> is registered
+ * in <mxCellRenderer>. Default is swimlane.
+ */
+ SHAPE_SWIMLANE: 'swimlane',
+
+ /**
+ * Variable: SHAPE_CONNECTOR
+ *
+ * Name under which <mxConnector> is registered
+ * in <mxCellRenderer>. Default is connector.
+ */
+ SHAPE_CONNECTOR: 'connector',
+
+ /**
+ * Variable: SHAPE_ACTOR
+ *
+ * Name under which <mxActor> is registered
+ * in <mxCellRenderer>. Default is actor.
+ */
+ SHAPE_ACTOR: 'actor',
+
+ /**
+ * Variable: SHAPE_CLOUD
+ *
+ * Name under which <mxCloud> is registered
+ * in <mxCellRenderer>. Default is cloud.
+ */
+ SHAPE_CLOUD: 'cloud',
+
+ /**
+ * Variable: SHAPE_TRIANGLE
+ *
+ * Name under which <mxTriangle> is registered
+ * in <mxCellRenderer>. Default is triangle.
+ */
+ SHAPE_TRIANGLE: 'triangle',
+
+ /**
+ * Variable: SHAPE_HEXAGON
+ *
+ * Name under which <mxHexagon> is registered
+ * in <mxCellRenderer>. Default is hexagon.
+ */
+ SHAPE_HEXAGON: 'hexagon',
+
+ /**
+ * Variable: ARROW_CLASSIC
+ *
+ * Constant for classic arrow markers.
+ */
+ ARROW_CLASSIC: 'classic',
+
+ /**
+ * Variable: ARROW_BLOCK
+ *
+ * Constant for block arrow markers.
+ */
+ ARROW_BLOCK: 'block',
+
+ /**
+ * Variable: ARROW_OPEN
+ *
+ * Constant for open arrow markers.
+ */
+ ARROW_OPEN: 'open',
+
+ /**
+ * Variable: ARROW_OVAL
+ *
+ * Constant for oval arrow markers.
+ */
+ ARROW_OVAL: 'oval',
+
+ /**
+ * Variable: ARROW_DIAMOND
+ *
+ * Constant for diamond arrow markers.
+ */
+ ARROW_DIAMOND: 'diamond',
+
+ /**
+ * Variable: ARROW_DIAMOND
+ *
+ * Constant for diamond arrow markers.
+ */
+ ARROW_DIAMOND_THIN: 'diamondThin',
+
+ /**
+ * Variable: ALIGN_LEFT
+ *
+ * Constant for left horizontal alignment. Default is left.
+ */
+ ALIGN_LEFT: 'left',
+
+ /**
+ * Variable: ALIGN_CENTER
+ *
+ * Constant for center horizontal alignment. Default is center.
+ */
+ ALIGN_CENTER: 'center',
+
+ /**
+ * Variable: ALIGN_RIGHT
+ *
+ * Constant for right horizontal alignment. Default is right.
+ */
+ ALIGN_RIGHT: 'right',
+
+ /**
+ * Variable: ALIGN_TOP
+ *
+ * Constant for top vertical alignment. Default is top.
+ */
+ ALIGN_TOP: 'top',
+
+ /**
+ * Variable: ALIGN_MIDDLE
+ *
+ * Constant for middle vertical alignment. Default is middle.
+ */
+ ALIGN_MIDDLE: 'middle',
+
+ /**
+ * Variable: ALIGN_BOTTOM
+ *
+ * Constant for bottom vertical alignment. Default is bottom.
+ */
+ ALIGN_BOTTOM: 'bottom',
+
+ /**
+ * Variable: DIRECTION_NORTH
+ *
+ * Constant for direction north. Default is north.
+ */
+ DIRECTION_NORTH: 'north',
+
+ /**
+ * Variable: DIRECTION_SOUTH
+ *
+ * Constant for direction south. Default is south.
+ */
+ DIRECTION_SOUTH: 'south',
+
+ /**
+ * Variable: DIRECTION_EAST
+ *
+ * Constant for direction east. Default is east.
+ */
+ DIRECTION_EAST: 'east',
+
+ /**
+ * Variable: DIRECTION_WEST
+ *
+ * Constant for direction west. Default is west.
+ */
+ DIRECTION_WEST: 'west',
+
+ /**
+ * Variable: DIRECTION_MASK_NONE
+ *
+ * Constant for no direction.
+ */
+ DIRECTION_MASK_NONE: 0,
+
+ /**
+ * Variable: DIRECTION_MASK_WEST
+ *
+ * Bitwise mask for west direction.
+ */
+ DIRECTION_MASK_WEST: 1,
+
+ /**
+ * Variable: DIRECTION_MASK_NORTH
+ *
+ * Bitwise mask for north direction.
+ */
+ DIRECTION_MASK_NORTH: 2,
+
+ /**
+ * Variable: DIRECTION_MASK_SOUTH
+ *
+ * Bitwise mask for south direction.
+ */
+ DIRECTION_MASK_SOUTH: 4,
+
+ /**
+ * Variable: DIRECTION_MASK_EAST
+ *
+ * Bitwise mask for east direction.
+ */
+ DIRECTION_MASK_EAST: 8,
+
+ /**
+ * Variable: DIRECTION_MASK_ALL
+ *
+ * Bitwise mask for all directions.
+ */
+ DIRECTION_MASK_ALL: 15,
+
+ /**
+ * Variable: ELBOW_VERTICAL
+ *
+ * Constant for elbow vertical. Default is horizontal.
+ */
+ ELBOW_VERTICAL: 'vertical',
+
+ /**
+ * Variable: ELBOW_HORIZONTAL
+ *
+ * Constant for elbow horizontal. Default is horizontal.
+ */
+ ELBOW_HORIZONTAL: 'horizontal',
+
+ /**
+ * Variable: EDGESTYLE_ELBOW
+ *
+ * Name of the elbow edge style. Can be used as a string value
+ * for the STYLE_EDGE style.
+ */
+ EDGESTYLE_ELBOW: 'elbowEdgeStyle',
+
+ /**
+ * Variable: EDGESTYLE_ENTITY_RELATION
+ *
+ * Name of the entity relation edge style. Can be used as a string value
+ * for the STYLE_EDGE style.
+ */
+ EDGESTYLE_ENTITY_RELATION: 'entityRelationEdgeStyle',
+
+ /**
+ * Variable: EDGESTYLE_LOOP
+ *
+ * Name of the loop edge style. Can be used as a string value
+ * for the STYLE_EDGE style.
+ */
+ EDGESTYLE_LOOP: 'loopEdgeStyle',
+
+ /**
+ * Variable: EDGESTYLE_SIDETOSIDE
+ *
+ * Name of the side to side edge style. Can be used as a string value
+ * for the STYLE_EDGE style.
+ */
+ EDGESTYLE_SIDETOSIDE: 'sideToSideEdgeStyle',
+
+ /**
+ * Variable: EDGESTYLE_TOPTOBOTTOM
+ *
+ * Name of the top to bottom edge style. Can be used as a string value
+ * for the STYLE_EDGE style.
+ */
+ EDGESTYLE_TOPTOBOTTOM: 'topToBottomEdgeStyle',
+
+ /**
+ * Variable: EDGESTYLE_ORTHOGONAL
+ *
+ * Name of the generic orthogonal edge style. Can be used as a string value
+ * for the STYLE_EDGE style.
+ */
+ EDGESTYLE_ORTHOGONAL: 'orthogonalEdgeStyle',
+
+ /**
+ * Variable: EDGESTYLE_SEGMENT
+ *
+ * Name of the generic segment edge style. Can be used as a string value
+ * for the STYLE_EDGE style.
+ */
+ EDGESTYLE_SEGMENT: 'segmentEdgeStyle',
+
+ /**
+ * Variable: PERIMETER_ELLIPSE
+ *
+ * Name of the ellipse perimeter. Can be used as a string value
+ * for the STYLE_PERIMETER style.
+ */
+ PERIMETER_ELLIPSE: 'ellipsePerimeter',
+
+ /**
+ * Variable: PERIMETER_RECTANGLE
+ *
+ * Name of the rectangle perimeter. Can be used as a string value
+ * for the STYLE_PERIMETER style.
+ */
+ PERIMETER_RECTANGLE: 'rectanglePerimeter',
+
+ /**
+ * Variable: PERIMETER_RHOMBUS
+ *
+ * Name of the rhombus perimeter. Can be used as a string value
+ * for the STYLE_PERIMETER style.
+ */
+ PERIMETER_RHOMBUS: 'rhombusPerimeter',
+
+ /**
+ * Variable: PERIMETER_TRIANGLE
+ *
+ * Name of the triangle perimeter. Can be used as a string value
+ * for the STYLE_PERIMETER style.
+ */
+ PERIMETER_TRIANGLE: 'trianglePerimeter'
+
+};
diff --git a/src/js/util/mxDictionary.js b/src/js/util/mxDictionary.js
new file mode 100644
index 0000000..a2e503a
--- /dev/null
+++ b/src/js/util/mxDictionary.js
@@ -0,0 +1,130 @@
+/**
+ * $Id: mxDictionary.js,v 1.12 2012-04-26 08:08:54 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxDictionary
+ *
+ * A wrapper class for an associative array with object keys. Note: This
+ * implementation uses <mxObjectIdentitiy> to turn object keys into strings.
+ *
+ * Constructor: mxEventSource
+ *
+ * Constructs a new dictionary which allows object to be used as keys.
+ */
+function mxDictionary()
+{
+ this.clear();
+};
+
+/**
+ * Function: map
+ *
+ * Stores the (key, value) pairs in this dictionary.
+ */
+mxDictionary.prototype.map = null;
+
+/**
+ * Function: clear
+ *
+ * Clears the dictionary.
+ */
+mxDictionary.prototype.clear = function()
+{
+ this.map = {};
+};
+
+/**
+ * Function: get
+ *
+ * Returns the value for the given key.
+ */
+mxDictionary.prototype.get = function(key)
+{
+ var id = mxObjectIdentity.get(key);
+
+ return this.map[id];
+};
+
+/**
+ * Function: put
+ *
+ * Stores the value under the given key and returns the previous
+ * value for that key.
+ */
+mxDictionary.prototype.put = function(key, value)
+{
+ var id = mxObjectIdentity.get(key);
+ var previous = this.map[id];
+ this.map[id] = value;
+
+ return previous;
+};
+
+/**
+ * Function: remove
+ *
+ * Removes the value for the given key and returns the value that
+ * has been removed.
+ */
+mxDictionary.prototype.remove = function(key)
+{
+ var id = mxObjectIdentity.get(key);
+ var previous = this.map[id];
+ delete this.map[id];
+
+ return previous;
+};
+
+/**
+ * Function: getKeys
+ *
+ * Returns all keys as an array.
+ */
+mxDictionary.prototype.getKeys = function()
+{
+ var result = [];
+
+ for (var key in this.map)
+ {
+ result.push(key);
+ }
+
+ return result;
+};
+
+/**
+ * Function: getValues
+ *
+ * Returns all values as an array.
+ */
+mxDictionary.prototype.getValues = function()
+{
+ var result = [];
+
+ for (var key in this.map)
+ {
+ result.push(this.map[key]);
+ }
+
+ return result;
+};
+
+/**
+ * Function: visit
+ *
+ * Visits all entries in the dictionary using the given function with the
+ * following signature: function(key, value) where key is a string and
+ * value is an object.
+ *
+ * Parameters:
+ *
+ * visitor - A function that takes the key and value as arguments.
+ */
+mxDictionary.prototype.visit = function(visitor)
+{
+ for (var key in this.map)
+ {
+ visitor(key, this.map[key]);
+ }
+};
diff --git a/src/js/util/mxDivResizer.js b/src/js/util/mxDivResizer.js
new file mode 100644
index 0000000..2a2e4eb
--- /dev/null
+++ b/src/js/util/mxDivResizer.js
@@ -0,0 +1,151 @@
+/**
+ * $Id: mxDivResizer.js,v 1.22 2010-01-02 09:45:14 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxDivResizer
+ *
+ * Maintains the size of a div element in Internet Explorer. This is a
+ * workaround for the right and bottom style being ignored in IE.
+ *
+ * If you need a div to cover the scrollwidth and -height of a document,
+ * then you can use this class as follows:
+ *
+ * (code)
+ * var resizer = new mxDivResizer(background);
+ * resizer.getDocumentHeight = function()
+ * {
+ * return document.body.scrollHeight;
+ * }
+ * resizer.getDocumentWidth = function()
+ * {
+ * return document.body.scrollWidth;
+ * }
+ * resizer.resize();
+ * (end)
+ *
+ * Constructor: mxDivResizer
+ *
+ * Constructs an object that maintains the size of a div
+ * element when the window is being resized. This is only
+ * required for Internet Explorer as it ignores the respective
+ * stylesheet information for DIV elements.
+ *
+ * Parameters:
+ *
+ * div - Reference to the DOM node whose size should be maintained.
+ * container - Optional Container that contains the div. Default is the
+ * window.
+ */
+function mxDivResizer(div, container)
+{
+ if (div.nodeName.toLowerCase() == 'div')
+ {
+ if (container == null)
+ {
+ container = window;
+ }
+
+ this.div = div;
+ var style = mxUtils.getCurrentStyle(div);
+
+ if (style != null)
+ {
+ this.resizeWidth = style.width == 'auto';
+ this.resizeHeight = style.height == 'auto';
+ }
+
+ mxEvent.addListener(container, 'resize',
+ mxUtils.bind(this, function(evt)
+ {
+ if (!this.handlingResize)
+ {
+ this.handlingResize = true;
+ this.resize();
+ this.handlingResize = false;
+ }
+ })
+ );
+
+ this.resize();
+ }
+};
+
+/**
+ * Function: resizeWidth
+ *
+ * Boolean specifying if the width should be updated.
+ */
+mxDivResizer.prototype.resizeWidth = true;
+
+/**
+ * Function: resizeHeight
+ *
+ * Boolean specifying if the height should be updated.
+ */
+mxDivResizer.prototype.resizeHeight = true;
+
+/**
+ * Function: handlingResize
+ *
+ * Boolean specifying if the width should be updated.
+ */
+mxDivResizer.prototype.handlingResize = false;
+
+/**
+ * Function: resize
+ *
+ * Updates the style of the DIV after the window has been resized.
+ */
+mxDivResizer.prototype.resize = function()
+{
+ var w = this.getDocumentWidth();
+ var h = this.getDocumentHeight();
+
+ var l = parseInt(this.div.style.left);
+ var r = parseInt(this.div.style.right);
+ var t = parseInt(this.div.style.top);
+ var b = parseInt(this.div.style.bottom);
+
+ if (this.resizeWidth &&
+ !isNaN(l) &&
+ !isNaN(r) &&
+ l >= 0 &&
+ r >= 0 &&
+ w - r - l > 0)
+ {
+ this.div.style.width = (w - r - l)+'px';
+ }
+
+ if (this.resizeHeight &&
+ !isNaN(t) &&
+ !isNaN(b) &&
+ t >= 0 &&
+ b >= 0 &&
+ h - t - b > 0)
+ {
+ this.div.style.height = (h - t - b)+'px';
+ }
+};
+
+/**
+ * Function: getDocumentWidth
+ *
+ * Hook for subclassers to return the width of the document (without
+ * scrollbars).
+ */
+mxDivResizer.prototype.getDocumentWidth = function()
+{
+ return document.body.clientWidth;
+};
+
+/**
+ * Function: getDocumentHeight
+ *
+ * Hook for subclassers to return the height of the document (without
+ * scrollbars).
+ */
+mxDivResizer.prototype.getDocumentHeight = function()
+{
+ return document.body.clientHeight;
+};
diff --git a/src/js/util/mxDragSource.js b/src/js/util/mxDragSource.js
new file mode 100644
index 0000000..d0cafdf
--- /dev/null
+++ b/src/js/util/mxDragSource.js
@@ -0,0 +1,594 @@
+/**
+ * $Id: mxDragSource.js,v 1.14 2012-12-05 21:43:16 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxDragSource
+ *
+ * Wrapper to create a drag source from a DOM element so that the element can
+ * be dragged over a graph and dropped into the graph as a new cell.
+ *
+ * TODO: Problem is that in the dropHandler the current preview location is
+ * not available, so the preview and the dropHandler must match.
+ *
+ * Constructor: mxDragSource
+ *
+ * Constructs a new drag source for the given element.
+ */
+function mxDragSource(element, dropHandler)
+{
+ this.element = element;
+ this.dropHandler = dropHandler;
+
+ // Handles a drag gesture on the element
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ mxEvent.addListener(element, md, mxUtils.bind(this, this.mouseDown));
+};
+
+/**
+ * Variable: element
+ *
+ * Reference to the DOM node which was made draggable.
+ */
+mxDragSource.prototype.element = null;
+
+/**
+ * Variable: dropHandler
+ *
+ * Holds the DOM node that is used to represent the drag preview. If this is
+ * null then the source element will be cloned and used for the drag preview.
+ */
+mxDragSource.prototype.dropHandler = null;
+
+/**
+ * Variable: dragOffset
+ *
+ * <mxPoint> that specifies the offset of the <dragElement>. Default is null.
+ */
+mxDragSource.prototype.dragOffset = null;
+
+/**
+ * Variable: dragElement
+ *
+ * Holds the DOM node that is used to represent the drag preview. If this is
+ * null then the source element will be cloned and used for the drag preview.
+ */
+mxDragSource.prototype.dragElement = null;
+
+/**
+ * Variable: previewElement
+ *
+ * Optional <mxRectangle> that specifies the unscaled size of the preview.
+ */
+mxDragSource.prototype.previewElement = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if this drag source is enabled. Default is true.
+ */
+mxDragSource.prototype.enabled = true;
+
+/**
+ * Variable: currentGraph
+ *
+ * Reference to the <mxGraph> that is the current drop target.
+ */
+mxDragSource.prototype.currentGraph = null;
+
+/**
+ * Variable: currentDropTarget
+ *
+ * Holds the current drop target under the mouse.
+ */
+mxDragSource.prototype.currentDropTarget = null;
+
+/**
+ * Variable: currentPoint
+ *
+ * Holds the current drop location.
+ */
+mxDragSource.prototype.currentPoint = null;
+
+/**
+ * Variable: currentGuide
+ *
+ * Holds an <mxGuide> for the <currentGraph> if <dragPreview> is not null.
+ */
+mxDragSource.prototype.currentGuide = null;
+
+/**
+ * Variable: currentGuide
+ *
+ * Holds an <mxGuide> for the <currentGraph> if <dragPreview> is not null.
+ */
+mxDragSource.prototype.currentHighlight = null;
+
+/**
+ * Variable: autoscroll
+ *
+ * Specifies if the graph should scroll automatically. Default is true.
+ */
+mxDragSource.prototype.autoscroll = true;
+
+/**
+ * Variable: guidesEnabled
+ *
+ * Specifies if <mxGuide> should be enabled. Default is true.
+ */
+mxDragSource.prototype.guidesEnabled = true;
+
+/**
+ * Variable: gridEnabled
+ *
+ * Specifies if the grid should be allowed. Default is true.
+ */
+mxDragSource.prototype.gridEnabled = true;
+
+/**
+ * Variable: highlightDropTargets
+ *
+ * Specifies if drop targets should be highlighted. Default is true.
+ */
+mxDragSource.prototype.highlightDropTargets = true;
+
+/**
+ * Variable: dragElementZIndex
+ *
+ * ZIndex for the drag element. Default is 100.
+ */
+mxDragSource.prototype.dragElementZIndex = 100;
+
+/**
+ * Variable: dragElementOpacity
+ *
+ * Opacity of the drag element in %. Default is 70.
+ */
+mxDragSource.prototype.dragElementOpacity = 70;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns <enabled>.
+ */
+mxDragSource.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Sets <enabled>.
+ */
+mxDragSource.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: isGuidesEnabled
+ *
+ * Returns <guidesEnabled>.
+ */
+mxDragSource.prototype.isGuidesEnabled = function()
+{
+ return this.guidesEnabled;
+};
+
+/**
+ * Function: setGuidesEnabled
+ *
+ * Sets <guidesEnabled>.
+ */
+mxDragSource.prototype.setGuidesEnabled = function(value)
+{
+ this.guidesEnabled = value;
+};
+
+/**
+ * Function: isGridEnabled
+ *
+ * Returns <gridEnabled>.
+ */
+mxDragSource.prototype.isGridEnabled = function()
+{
+ return this.gridEnabled;
+};
+
+/**
+ * Function: setGridEnabled
+ *
+ * Sets <gridEnabled>.
+ */
+mxDragSource.prototype.setGridEnabled = function(value)
+{
+ this.gridEnabled = value;
+};
+
+/**
+ * Function: getGraphForEvent
+ *
+ * Returns the graph for the given mouse event. This implementation returns
+ * null.
+ */
+mxDragSource.prototype.getGraphForEvent = function(evt)
+{
+ return null;
+};
+
+/**
+ * Function: getDropTarget
+ *
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses <mxGraph.getCellAt>.
+ */
+mxDragSource.prototype.getDropTarget = function(graph, x, y)
+{
+ return graph.getCellAt(x, y);
+};
+
+/**
+ * Function: createDragElement
+ *
+ * Creates and returns a clone of the <dragElementPrototype> or the <element>
+ * if the former is not defined.
+ */
+mxDragSource.prototype.createDragElement = function(evt)
+{
+ return this.element.cloneNode(true);
+};
+
+/**
+ * Function: createPreviewElement
+ *
+ * Creates and returns an element which can be used as a preview in the given
+ * graph.
+ */
+mxDragSource.prototype.createPreviewElement = function(graph)
+{
+ return null;
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses <mxGraph.getCellAt>.
+ */
+mxDragSource.prototype.mouseDown = function(evt)
+{
+ if (this.enabled && !mxEvent.isConsumed(evt) && this.mouseMoveHandler == null)
+ {
+ this.startDrag(evt);
+
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ this.mouseMoveHandler = mxUtils.bind(this, this.mouseMove);
+ mxEvent.addListener(document, mm, this.mouseMoveHandler);
+ this.mouseUpHandler = mxUtils.bind(this, this.mouseUp);
+ mxEvent.addListener(document, mu, this.mouseUpHandler);
+
+ // Prevents default action (native DnD for images in FF 10)
+ // but does not stop event propagation
+ mxEvent.consume(evt, true, false);
+ }
+};
+
+/**
+ * Function: startDrag
+ *
+ * Creates the <dragElement> using <createDragElement>.
+ */
+mxDragSource.prototype.startDrag = function(evt)
+{
+ this.dragElement = this.createDragElement(evt);
+ this.dragElement.style.position = 'absolute';
+ this.dragElement.style.zIndex = this.dragElementZIndex;
+ mxUtils.setOpacity(this.dragElement, this.dragElementOpacity);
+};
+
+
+/**
+ * Function: stopDrag
+ *
+ * Removes and destroys the <dragElement>.
+ */
+mxDragSource.prototype.stopDrag = function(evt)
+{
+ if (this.dragElement != null)
+ {
+ if (this.dragElement.parentNode != null)
+ {
+ this.dragElement.parentNode.removeChild(this.dragElement);
+ }
+
+ this.dragElement = null;
+ }
+};
+
+/**
+ * Function: graphContainsEvent
+ *
+ * Returns true if the given graph contains the given event.
+ */
+mxDragSource.prototype.graphContainsEvent = function(graph, evt)
+{
+ var x = mxEvent.getClientX(evt);
+ var y = mxEvent.getClientY(evt);
+ var offset = mxUtils.getOffset(graph.container);
+ var origin = mxUtils.getScrollOrigin();
+
+ // Checks if event is inside the bounds of the graph container
+ return x >= offset.x - origin.x && y >= offset.y - origin.y &&
+ x <= offset.x - origin.x + graph.container.offsetWidth &&
+ y <= offset.y - origin.y + graph.container.offsetHeight;
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Gets the graph for the given event using <getGraphForEvent>, updates the
+ * <currentGraph>, calling <dragEnter> and <dragExit> on the new and old graph,
+ * respectively, and invokes <dragOver> if <currentGraph> is not null.
+ */
+mxDragSource.prototype.mouseMove = function(evt)
+{
+ var graph = this.getGraphForEvent(evt);
+
+ // Checks if event is inside the bounds of the graph container
+ if (graph != null && !this.graphContainsEvent(graph, evt))
+ {
+ graph = null;
+ }
+
+ if (graph != this.currentGraph)
+ {
+ if (this.currentGraph != null)
+ {
+ this.dragExit(this.currentGraph);
+ }
+
+ this.currentGraph = graph;
+
+ if (this.currentGraph != null)
+ {
+ this.dragEnter(this.currentGraph);
+ }
+ }
+
+ if (this.currentGraph != null)
+ {
+ this.dragOver(this.currentGraph, evt);
+ }
+
+ if (this.dragElement != null && (this.previewElement == null || this.previewElement.style.visibility != 'visible'))
+ {
+ var x = mxEvent.getClientX(evt);
+ var y = mxEvent.getClientY(evt);
+
+ if (this.dragElement.parentNode == null)
+ {
+ document.body.appendChild(this.dragElement);
+ }
+
+ this.dragElement.style.visibility = 'visible';
+
+ if (this.dragOffset != null)
+ {
+ x += this.dragOffset.x;
+ y += this.dragOffset.y;
+ }
+
+ x += document.body.scrollLeft || document.documentElement.scrollLeft;
+ y += document.body.scrollTop || document.documentElement.scrollTop;
+ this.dragElement.style.left = x + 'px';
+ this.dragElement.style.top = y + 'px';
+ }
+ else if (this.dragElement != null)
+ {
+ this.dragElement.style.visibility = 'hidden';
+ }
+
+ mxEvent.consume(evt);
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Processes the mouse up event and invokes <drop>, <dragExit> and <stopDrag>
+ * as required.
+ */
+mxDragSource.prototype.mouseUp = function(evt)
+{
+ if (this.currentGraph != null)
+ {
+ if (this.currentPoint != null && (this.previewElement == null ||
+ this.previewElement.style.visibility != 'hidden'))
+ {
+ var scale = this.currentGraph.view.scale;
+ var tr = this.currentGraph.view.translate;
+ var x = this.currentPoint.x / scale - tr.x;
+ var y = this.currentPoint.y / scale - tr.y;
+
+ this.drop(this.currentGraph, evt, this.currentDropTarget, x, y);
+ }
+
+ this.dragExit(this.currentGraph);
+ }
+
+ this.stopDrag(evt);
+
+ this.currentGraph = null;
+
+ if (this.mouseMoveHandler != null)
+ {
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ mxEvent.removeListener(document, mm, this.mouseMoveHandler);
+ this.mouseMoveHandler = null;
+ }
+
+ if (this.mouseUpHandler != null)
+ {
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+ mxEvent.removeListener(document, mu, this.mouseUpHandler);
+ this.mouseUpHandler = null;
+ }
+
+ mxEvent.consume(evt);
+};
+
+/**
+ * Function: dragEnter
+ *
+ * Actives the given graph as a drop target.
+ */
+mxDragSource.prototype.dragEnter = function(graph)
+{
+ graph.isMouseDown = true;
+ this.previewElement = this.createPreviewElement(graph);
+
+ // Guide is only needed if preview element is used
+ if (this.isGuidesEnabled() && this.previewElement != null)
+ {
+ this.currentGuide = new mxGuide(graph, graph.graphHandler.getGuideStates());
+ }
+
+ if (this.highlightDropTargets)
+ {
+ this.currentHighlight = new mxCellHighlight(graph, mxConstants.DROP_TARGET_COLOR);
+ }
+};
+
+/**
+ * Function: dragExit
+ *
+ * Deactivates the given graph as a drop target.
+ */
+mxDragSource.prototype.dragExit = function(graph)
+{
+ this.currentDropTarget = null;
+ this.currentPoint = null;
+ graph.isMouseDown = false;
+
+ if (this.previewElement != null)
+ {
+ if (this.previewElement.parentNode != null)
+ {
+ this.previewElement.parentNode.removeChild(this.previewElement);
+ }
+
+ this.previewElement = null;
+ }
+
+ if (this.currentGuide != null)
+ {
+ this.currentGuide.destroy();
+ this.currentGuide = null;
+ }
+
+ if (this.currentHighlight != null)
+ {
+ this.currentHighlight.destroy();
+ this.currentHighlight = null;
+ }
+};
+
+/**
+ * Function: dragOver
+ *
+ * Implements autoscroll, updates the <currentPoint>, highlights any drop
+ * targets and updates the preview.
+ */
+mxDragSource.prototype.dragOver = function(graph, evt)
+{
+ var offset = mxUtils.getOffset(graph.container);
+ var origin = mxUtils.getScrollOrigin(graph.container);
+ var x = mxEvent.getClientX(evt) - offset.x + origin.x;
+ var y = mxEvent.getClientY(evt) - offset.y + origin.y;
+
+ if (graph.autoScroll && (this.autoscroll == null || this.autoscroll))
+ {
+ graph.scrollPointToVisible(x, y, graph.autoExtend);
+ }
+
+ // Highlights the drop target under the mouse
+ if (this.currentHighlight != null && graph.isDropEnabled())
+ {
+ this.currentDropTarget = this.getDropTarget(graph, x, y);
+ var state = graph.getView().getState(this.currentDropTarget);
+ this.currentHighlight.highlight(state);
+ }
+
+ // Updates the location of the preview
+ if (this.previewElement != null)
+ {
+ if (this.previewElement.parentNode == null)
+ {
+ graph.container.appendChild(this.previewElement);
+
+ this.previewElement.style.zIndex = '3';
+ this.previewElement.style.position = 'absolute';
+ }
+
+ var gridEnabled = this.isGridEnabled() && graph.isGridEnabledEvent(evt);
+ var hideGuide = true;
+
+ // Grid and guides
+ if (this.currentGuide != null && this.currentGuide.isEnabledForEvent(evt))
+ {
+ // LATER: HTML preview appears smaller than SVG preview
+ var w = parseInt(this.previewElement.style.width);
+ var h = parseInt(this.previewElement.style.height);
+ var bounds = new mxRectangle(0, 0, w, h);
+ var delta = new mxPoint(x, y);
+ delta = this.currentGuide.move(bounds, delta, gridEnabled);
+ hideGuide = false;
+ x = delta.x;
+ y = delta.y;
+ }
+ else if (gridEnabled)
+ {
+ var scale = graph.view.scale;
+ var tr = graph.view.translate;
+ var off = graph.gridSize / 2;
+ x = (graph.snap(x / scale - tr.x - off) + tr.x) * scale;
+ y = (graph.snap(y / scale - tr.y - off) + tr.y) * scale;
+ }
+
+ if (this.currentGuide != null && hideGuide)
+ {
+ this.currentGuide.hide();
+ }
+
+ if (this.previewOffset != null)
+ {
+ x += this.previewOffset.x;
+ y += this.previewOffset.y;
+ }
+
+ this.previewElement.style.left = Math.round(x) + 'px';
+ this.previewElement.style.top = Math.round(y) + 'px';
+ this.previewElement.style.visibility = 'visible';
+ }
+
+ this.currentPoint = new mxPoint(x, y);
+};
+
+/**
+ * Function: drop
+ *
+ * Returns the drop target for the given graph and coordinates. This
+ * implementation uses <mxGraph.getCellAt>.
+ */
+mxDragSource.prototype.drop = function(graph, evt, dropTarget, x, y)
+{
+ this.dropHandler(graph, evt, dropTarget, x, y);
+
+ // Had to move this to after the insert because it will
+ // affect the scrollbars of the window in IE to try and
+ // make the complete container visible.
+ // LATER: Should be made optional.
+ graph.container.focus();
+};
diff --git a/src/js/util/mxEffects.js b/src/js/util/mxEffects.js
new file mode 100644
index 0000000..89d6a71
--- /dev/null
+++ b/src/js/util/mxEffects.js
@@ -0,0 +1,214 @@
+/**
+ * $Id: mxEffects.js,v 1.6 2012-01-04 10:01:16 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxEffects =
+{
+
+ /**
+ * Class: mxEffects
+ *
+ * Provides animation effects.
+ */
+
+ /**
+ * Function: animateChanges
+ *
+ * Asynchronous animated move operation. See also: <mxMorphing>.
+ *
+ * Example:
+ *
+ * (code)
+ * graph.model.addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ * var changes = evt.getProperty('edit').changes;
+ *
+ * if (changes.length < 10)
+ * {
+ * mxEffects.animateChanges(graph, changes);
+ * }
+ * });
+ * (end)
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> that received the changes.
+ * changes - Array of changes to be animated.
+ * done - Optional function argument that is invoked after the
+ * last step of the animation.
+ */
+ animateChanges: function(graph, changes, done)
+ {
+ var maxStep = 10;
+ var step = 0;
+
+ var animate = function()
+ {
+ var isRequired = false;
+
+ for (var i = 0; i < changes.length; i++)
+ {
+ var change = changes[i];
+
+ if (change instanceof mxGeometryChange ||
+ change instanceof mxTerminalChange ||
+ change instanceof mxValueChange ||
+ change instanceof mxChildChange ||
+ change instanceof mxStyleChange)
+ {
+ var state = graph.getView().getState(change.cell || change.child, false);
+
+ if (state != null)
+ {
+ isRequired = true;
+
+ if (change.constructor != mxGeometryChange || graph.model.isEdge(change.cell))
+ {
+ mxUtils.setOpacity(state.shape.node, 100 * step / maxStep);
+ }
+ else
+ {
+ var scale = graph.getView().scale;
+
+ var dx = (change.geometry.x - change.previous.x) * scale;
+ var dy = (change.geometry.y - change.previous.y) * scale;
+
+ var sx = (change.geometry.width - change.previous.width) * scale;
+ var sy = (change.geometry.height - change.previous.height) * scale;
+
+ if (step == 0)
+ {
+ state.x -= dx;
+ state.y -= dy;
+ state.width -= sx;
+ state.height -= sy;
+ }
+ else
+ {
+ state.x += dx / maxStep;
+ state.y += dy / maxStep;
+ state.width += sx / maxStep;
+ state.height += sy / maxStep;
+ }
+
+ graph.cellRenderer.redraw(state);
+
+ // Fades all connected edges and children
+ mxEffects.cascadeOpacity(graph, change.cell, 100 * step / maxStep);
+ }
+ }
+ }
+ }
+
+ // Workaround to force a repaint in AppleWebKit
+ mxUtils.repaintGraph(graph, new mxPoint(1, 1));
+
+ if (step < maxStep && isRequired)
+ {
+ step++;
+ window.setTimeout(animate, delay);
+ }
+ else if (done != null)
+ {
+ done();
+ }
+ };
+
+ var delay = 30;
+ animate();
+ },
+
+ /**
+ * Function: cascadeOpacity
+ *
+ * Sets the opacity on the given cell and its descendants.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> that contains the cells.
+ * cell - <mxCell> to set the opacity for.
+ * opacity - New value for the opacity in %.
+ */
+ cascadeOpacity: function(graph, cell, opacity)
+ {
+ // Fades all children
+ var childCount = graph.model.getChildCount(cell);
+
+ for (var i=0; i<childCount; i++)
+ {
+ var child = graph.model.getChildAt(cell, i);
+ var childState = graph.getView().getState(child);
+
+ if (childState != null)
+ {
+ mxUtils.setOpacity(childState.shape.node, opacity);
+ mxEffects.cascadeOpacity(graph, child, opacity);
+ }
+ }
+
+ // Fades all connected edges
+ var edges = graph.model.getEdges(cell);
+
+ if (edges != null)
+ {
+ for (var i=0; i<edges.length; i++)
+ {
+ var edgeState = graph.getView().getState(edges[i]);
+
+ if (edgeState != null)
+ {
+ mxUtils.setOpacity(edgeState.shape.node, opacity);
+ }
+ }
+ }
+ },
+
+ /**
+ * Function: fadeOut
+ *
+ * Asynchronous fade-out operation.
+ */
+ fadeOut: function(node, from, remove, step, delay, isEnabled)
+ {
+ step = step || 40;
+ delay = delay || 30;
+
+ var opacity = from || 100;
+
+ mxUtils.setOpacity(node, opacity);
+
+ if (isEnabled || isEnabled == null)
+ {
+ var f = function()
+ {
+ opacity = Math.max(opacity-step, 0);
+ mxUtils.setOpacity(node, opacity);
+
+ if (opacity > 0)
+ {
+ window.setTimeout(f, delay);
+ }
+ else
+ {
+ node.style.visibility = 'hidden';
+
+ if (remove && node.parentNode)
+ {
+ node.parentNode.removeChild(node);
+ }
+ }
+ };
+ window.setTimeout(f, delay);
+ }
+ else
+ {
+ node.style.visibility = 'hidden';
+
+ if (remove && node.parentNode)
+ {
+ node.parentNode.removeChild(node);
+ }
+ }
+ }
+
+};
diff --git a/src/js/util/mxEvent.js b/src/js/util/mxEvent.js
new file mode 100644
index 0000000..f831631
--- /dev/null
+++ b/src/js/util/mxEvent.js
@@ -0,0 +1,1175 @@
+/**
+ * $Id: mxEvent.js,v 1.76 2012-12-07 07:39:03 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxEvent =
+{
+
+ /**
+ * Class: mxEvent
+ *
+ * Cross-browser DOM event support. For internal event handling,
+ * <mxEventSource> and the graph event dispatch loop in <mxGraph> are used.
+ *
+ * Memory Leaks:
+ *
+ * Use this class for adding and removing listeners to/from DOM nodes. The
+ * <removeAllListeners> function is provided to remove all listeners that
+ * have been added using <addListener>. The function should be invoked when
+ * the last reference is removed in the JavaScript code, typically when the
+ * referenced DOM node is removed from the DOM, and helps to reduce memory
+ * leaks in IE6.
+ *
+ * Variable: objects
+ *
+ * Contains all objects where any listener was added using <addListener>.
+ * This is used to reduce memory leaks in IE, see <mxClient.dispose>.
+ */
+ objects: [],
+
+ /**
+ * Function: addListener
+ *
+ * Binds the function to the specified event on the given element. Use
+ * <mxUtils.bind> in order to bind the "this" keyword inside the function
+ * to a given execution scope.
+ */
+ addListener: function()
+ {
+ var updateListenerList = function(element, eventName, funct)
+ {
+ if (element.mxListenerList == null)
+ {
+ element.mxListenerList = [];
+ mxEvent.objects.push(element);
+ }
+
+ var entry = {name: eventName, f: funct};
+ element.mxListenerList.push(entry);
+ };
+
+ if (window.addEventListener)
+ {
+ return function(element, eventName, funct)
+ {
+ element.addEventListener(eventName, funct, false);
+ updateListenerList(element, eventName, funct);
+ };
+ }
+ else
+ {
+ return function(element, eventName, funct)
+ {
+ element.attachEvent('on' + eventName, funct);
+ updateListenerList(element, eventName, funct);
+ };
+ }
+ }(),
+
+ /**
+ * Function: removeListener
+ *
+ * Removes the specified listener from the given element.
+ */
+ removeListener: function()
+ {
+ var updateListener = function(element, eventName, funct)
+ {
+ if (element.mxListenerList != null)
+ {
+ var listenerCount = element.mxListenerList.length;
+
+ for (var i=0; i<listenerCount; i++)
+ {
+ var entry = element.mxListenerList[i];
+
+ if (entry.f == funct)
+ {
+ element.mxListenerList.splice(i, 1);
+ break;
+ }
+ }
+
+ if (element.mxListenerList.length == 0)
+ {
+ element.mxListenerList = null;
+ }
+ }
+ };
+
+ if (window.removeEventListener)
+ {
+ return function(element, eventName, funct)
+ {
+ element.removeEventListener(eventName, funct, false);
+ updateListener(element, eventName, funct);
+ };
+ }
+ else
+ {
+ return function(element, eventName, funct)
+ {
+ element.detachEvent('on' + eventName, funct);
+ updateListener(element, eventName, funct);
+ };
+ }
+ }(),
+
+ /**
+ * Function: removeAllListeners
+ *
+ * Removes all listeners from the given element.
+ */
+ removeAllListeners: function(element)
+ {
+ var list = element.mxListenerList;
+
+ if (list != null)
+ {
+ while (list.length > 0)
+ {
+ var entry = list[0];
+ mxEvent.removeListener(element, entry.name, entry.f);
+ }
+ }
+ },
+
+ /**
+ * Function: redirectMouseEvents
+ *
+ * Redirects the mouse events from the given DOM node to the graph dispatch
+ * loop using the event and given state as event arguments. State can
+ * either be an instance of <mxCellState> or a function that returns an
+ * <mxCellState>. The down, move, up and dblClick arguments are optional
+ * functions that take the trigger event as arguments and replace the
+ * default behaviour.
+ */
+ redirectMouseEvents: function(node, graph, state, down, move, up, dblClick)
+ {
+ var getState = function(evt)
+ {
+ return (typeof(state) == 'function') ? state(evt) : state;
+ };
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ mxEvent.addListener(node, md, function (evt)
+ {
+ if (down != null)
+ {
+ down(evt);
+ }
+ else if (!mxEvent.isConsumed(evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+ new mxMouseEvent(evt, getState(evt)));
+ }
+ });
+
+ mxEvent.addListener(node, mm, function (evt)
+ {
+ if (move != null)
+ {
+ move(evt);
+ }
+ else if (!mxEvent.isConsumed(evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, getState(evt)));
+ }
+ });
+
+ mxEvent.addListener(node, mu, function (evt)
+ {
+ if (up != null)
+ {
+ up(evt);
+ }
+ else if (!mxEvent.isConsumed(evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt, getState(evt)));
+ }
+ });
+
+ mxEvent.addListener(node, 'dblclick', function (evt)
+ {
+ if (dblClick != null)
+ {
+ dblClick(evt);
+ }
+ else if (!mxEvent.isConsumed(evt))
+ {
+ var tmp = getState(evt);
+ graph.dblClick(evt, (tmp != null) ? tmp.cell : null);
+ }
+ });
+ },
+
+ /**
+ * Function: release
+ *
+ * Removes the known listeners from the given DOM node and its descendants.
+ *
+ * Parameters:
+ *
+ * element - DOM node to remove the listeners from.
+ */
+ release: function(element)
+ {
+ if (element != null)
+ {
+ mxEvent.removeAllListeners(element);
+
+ var children = element.childNodes;
+
+ if (children != null)
+ {
+ var childCount = children.length;
+
+ for (var i = 0; i < childCount; i += 1)
+ {
+ mxEvent.release(children[i]);
+ }
+ }
+ }
+ },
+
+ /**
+ * Function: addMouseWheelListener
+ *
+ * Installs the given function as a handler for mouse wheel events. The
+ * function has two arguments: the mouse event and a boolean that specifies
+ * if the wheel was moved up or down.
+ *
+ * This has been tested with IE 6 and 7, Firefox (all versions), Opera and
+ * Safari. It does currently not work on Safari for Mac.
+ *
+ * Example:
+ *
+ * (code)
+ * mxEvent.addMouseWheelListener(function (evt, up)
+ * {
+ * mxLog.show();
+ * mxLog.debug('mouseWheel: up='+up);
+ * });
+ *(end)
+ *
+ * Parameters:
+ *
+ * funct - Handler function that takes the event argument and a boolean up
+ * argument for the mousewheel direction.
+ */
+ addMouseWheelListener: function(funct)
+ {
+ if (funct != null)
+ {
+ var wheelHandler = function(evt)
+ {
+ // IE does not give an event object but the
+ // global event object is the mousewheel event
+ // at this point in time.
+ if (evt == null)
+ {
+ evt = window.event;
+ }
+
+ var delta = 0;
+
+ if (mxClient.IS_NS && !mxClient.IS_SF && !mxClient.IS_GC)
+ {
+ delta = -evt.detail/2;
+ }
+ else
+ {
+ delta = evt.wheelDelta/120;
+ }
+
+ // Handles the event using the given function
+ if (delta != 0)
+ {
+ funct(evt, delta > 0);
+ }
+ };
+
+ // Webkit has NS event API, but IE event name and details
+ if (mxClient.IS_NS)
+ {
+ var eventName = (mxClient.IS_SF || mxClient.IS_GC) ?
+ 'mousewheel' : 'DOMMouseScroll';
+ mxEvent.addListener(window, eventName, wheelHandler);
+ }
+ else
+ {
+ // TODO: Does not work with Safari and Chrome but it should be
+ // working as tested in etc/markup/wheel.html
+ mxEvent.addListener(document, 'mousewheel', wheelHandler);
+ }
+ }
+ },
+
+ /**
+ * Function: disableContextMenu
+ *
+ * Disables the context menu for the given element.
+ */
+ disableContextMenu: function()
+ {
+ if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9))
+ {
+ return function(element)
+ {
+ mxEvent.addListener(element, 'contextmenu', function()
+ {
+ return false;
+ });
+ };
+ }
+ else
+ {
+ return function(element)
+ {
+ element.setAttribute('oncontextmenu', 'return false;');
+ };
+ }
+ }(),
+
+ /**
+ * Function: getSource
+ *
+ * Returns the event's target or srcElement depending on the browser.
+ */
+ getSource: function(evt)
+ {
+ return (evt.srcElement != null) ? evt.srcElement : evt.target;
+ },
+
+ /**
+ * Function: isConsumed
+ *
+ * Returns true if the event has been consumed using <consume>.
+ */
+ isConsumed: function(evt)
+ {
+ return evt.isConsumed != null && evt.isConsumed;
+ },
+
+ /**
+ * Function: isLeftMouseButton
+ *
+ * Returns true if the left mouse button is pressed for the given event.
+ * To check if a button is pressed during a mouseMove you should use the
+ * <mxGraph.isMouseDown> property.
+ */
+ isLeftMouseButton: function(evt)
+ {
+ return evt.button == ((mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9)) ? 1 : 0);
+ },
+
+ /**
+ * Function: isRightMouseButton
+ *
+ * Returns true if the right mouse button was pressed. Note that this
+ * button might not be available on some systems. For handling a popup
+ * trigger <isPopupTrigger> should be used.
+ */
+ isRightMouseButton: function(evt)
+ {
+ return evt.button == 2;
+ },
+
+ /**
+ * Function: isPopupTrigger
+ *
+ * Returns true if the event is a popup trigger. This implementation
+ * returns true if the right mouse button or shift was pressed.
+ */
+ isPopupTrigger: function(evt)
+ {
+ return mxEvent.isRightMouseButton(evt) ||
+ (mxEvent.isShiftDown(evt) &&
+ !mxEvent.isControlDown(evt));
+ },
+
+ /**
+ * Function: isShiftDown
+ *
+ * Returns true if the shift key is pressed for the given event.
+ */
+ isShiftDown: function(evt)
+ {
+ return (evt != null) ? evt.shiftKey : false;
+ },
+
+ /**
+ * Function: isAltDown
+ *
+ * Returns true if the alt key is pressed for the given event.
+ */
+ isAltDown: function(evt)
+ {
+ return (evt != null) ? evt.altKey : false;
+ },
+
+ /**
+ * Function: isControlDown
+ *
+ * Returns true if the control key is pressed for the given event.
+ */
+ isControlDown: function(evt)
+ {
+ return (evt != null) ? evt.ctrlKey : false;
+ },
+
+ /**
+ * Function: isMetaDown
+ *
+ * Returns true if the meta key is pressed for the given event.
+ */
+ isMetaDown: function(evt)
+ {
+ return (evt != null) ? evt.metaKey : false;
+ },
+
+ /**
+ * Function: getMainEvent
+ *
+ * Returns the touch or mouse event that contains the mouse coordinates.
+ */
+ getMainEvent: function(e)
+ {
+ if ((e.type == 'touchstart' || e.type == 'touchmove') &&
+ e.touches != null && e.touches[0] != null)
+ {
+ e = e.touches[0];
+ }
+ else if (e.type == 'touchend' && e.changedTouches != null &&
+ e.changedTouches[0] != null)
+ {
+ e = e.changedTouches[0];
+ }
+
+ return e;
+ },
+
+ /**
+ * Function: getClientX
+ *
+ * Returns true if the meta key is pressed for the given event.
+ */
+ getClientX: function(e)
+ {
+ return mxEvent.getMainEvent(e).clientX;
+ },
+
+ /**
+ * Function: getClientY
+ *
+ * Returns true if the meta key is pressed for the given event.
+ */
+ getClientY: function(e)
+ {
+ return mxEvent.getMainEvent(e).clientY;
+ },
+
+ /**
+ * Function: consume
+ *
+ * Consumes the given event.
+ *
+ * Parameters:
+ *
+ * evt - Native event to be consumed.
+ * preventDefault - Optional boolean to prevent the default for the event.
+ * Default is true.
+ * stopPropagation - Option boolean to stop event propagation. Default is
+ * true.
+ */
+ consume: function(evt, preventDefault, stopPropagation)
+ {
+ preventDefault = (preventDefault != null) ? preventDefault : true;
+ stopPropagation = (stopPropagation != null) ? stopPropagation : true;
+
+ if (preventDefault)
+ {
+ if (evt.preventDefault)
+ {
+ if (stopPropagation)
+ {
+ evt.stopPropagation();
+ }
+
+ evt.preventDefault();
+ }
+ else if (stopPropagation)
+ {
+ evt.cancelBubble = true;
+ }
+ }
+
+ // Opera
+ evt.isConsumed = true;
+
+ // Other browsers
+ evt.returnValue = false;
+ },
+
+ //
+ // Special handles in mouse events
+ //
+
+ /**
+ * Variable: LABEL_HANDLE
+ *
+ * Index for the label handle in an mxMouseEvent. This should be a negative
+ * value that does not interfere with any possible handle indices. Default
+ * is -1.
+ */
+ LABEL_HANDLE: -1,
+
+ /**
+ * Variable: ROTATION_HANDLE
+ *
+ * Index for the rotation handle in an mxMouseEvent. This should be a
+ * negative value that does not interfere with any possible handle indices.
+ * Default is -2.
+ */
+ ROTATION_HANDLE: -2,
+
+ //
+ // Event names
+ //
+
+ /**
+ * Variable: MOUSE_DOWN
+ *
+ * Specifies the event name for mouseDown.
+ */
+ MOUSE_DOWN: 'mouseDown',
+
+ /**
+ * Variable: MOUSE_MOVE
+ *
+ * Specifies the event name for mouseMove.
+ */
+ MOUSE_MOVE: 'mouseMove',
+
+ /**
+ * Variable: MOUSE_UP
+ *
+ * Specifies the event name for mouseUp.
+ */
+ MOUSE_UP: 'mouseUp',
+
+ /**
+ * Variable: ACTIVATE
+ *
+ * Specifies the event name for activate.
+ */
+ ACTIVATE: 'activate',
+
+ /**
+ * Variable: RESIZE_START
+ *
+ * Specifies the event name for resizeStart.
+ */
+ RESIZE_START: 'resizeStart',
+
+ /**
+ * Variable: RESIZE
+ *
+ * Specifies the event name for resize.
+ */
+ RESIZE: 'resize',
+
+ /**
+ * Variable: RESIZE_END
+ *
+ * Specifies the event name for resizeEnd.
+ */
+ RESIZE_END: 'resizeEnd',
+
+ /**
+ * Variable: MOVE_START
+ *
+ * Specifies the event name for moveStart.
+ */
+ MOVE_START: 'moveStart',
+
+ /**
+ * Variable: MOVE
+ *
+ * Specifies the event name for move.
+ */
+ MOVE: 'move',
+
+ /**
+ * Variable: MOVE_END
+ *
+ * Specifies the event name for moveEnd.
+ */
+ MOVE_END: 'moveEnd',
+
+ /**
+ * Variable: PAN_START
+ *
+ * Specifies the event name for panStart.
+ */
+ PAN_START: 'panStart',
+
+ /**
+ * Variable: PAN
+ *
+ * Specifies the event name for pan.
+ */
+ PAN: 'pan',
+
+ /**
+ * Variable: PAN_END
+ *
+ * Specifies the event name for panEnd.
+ */
+ PAN_END: 'panEnd',
+
+ /**
+ * Variable: MINIMIZE
+ *
+ * Specifies the event name for minimize.
+ */
+ MINIMIZE: 'minimize',
+
+ /**
+ * Variable: NORMALIZE
+ *
+ * Specifies the event name for normalize.
+ */
+ NORMALIZE: 'normalize',
+
+ /**
+ * Variable: MAXIMIZE
+ *
+ * Specifies the event name for maximize.
+ */
+ MAXIMIZE: 'maximize',
+
+ /**
+ * Variable: HIDE
+ *
+ * Specifies the event name for hide.
+ */
+ HIDE: 'hide',
+
+ /**
+ * Variable: SHOW
+ *
+ * Specifies the event name for show.
+ */
+ SHOW: 'show',
+
+ /**
+ * Variable: CLOSE
+ *
+ * Specifies the event name for close.
+ */
+ CLOSE: 'close',
+
+ /**
+ * Variable: DESTROY
+ *
+ * Specifies the event name for destroy.
+ */
+ DESTROY: 'destroy',
+
+ /**
+ * Variable: REFRESH
+ *
+ * Specifies the event name for refresh.
+ */
+ REFRESH: 'refresh',
+
+ /**
+ * Variable: SIZE
+ *
+ * Specifies the event name for size.
+ */
+ SIZE: 'size',
+
+ /**
+ * Variable: SELECT
+ *
+ * Specifies the event name for select.
+ */
+ SELECT: 'select',
+
+ /**
+ * Variable: FIRED
+ *
+ * Specifies the event name for fired.
+ */
+ FIRED: 'fired',
+
+ /**
+ * Variable: GET
+ *
+ * Specifies the event name for get.
+ */
+ GET: 'get',
+
+ /**
+ * Variable: RECEIVE
+ *
+ * Specifies the event name for receive.
+ */
+ RECEIVE: 'receive',
+
+ /**
+ * Variable: CONNECT
+ *
+ * Specifies the event name for connect.
+ */
+ CONNECT: 'connect',
+
+ /**
+ * Variable: DISCONNECT
+ *
+ * Specifies the event name for disconnect.
+ */
+ DISCONNECT: 'disconnect',
+
+ /**
+ * Variable: SUSPEND
+ *
+ * Specifies the event name for suspend.
+ */
+ SUSPEND: 'suspend',
+
+ /**
+ * Variable: RESUME
+ *
+ * Specifies the event name for suspend.
+ */
+ RESUME: 'resume',
+
+ /**
+ * Variable: MARK
+ *
+ * Specifies the event name for mark.
+ */
+ MARK: 'mark',
+
+ /**
+ * Variable: SESSION
+ *
+ * Specifies the event name for session.
+ */
+ SESSION: 'session',
+
+ /**
+ * Variable: ROOT
+ *
+ * Specifies the event name for root.
+ */
+ ROOT: 'root',
+
+ /**
+ * Variable: POST
+ *
+ * Specifies the event name for post.
+ */
+ POST: 'post',
+
+ /**
+ * Variable: OPEN
+ *
+ * Specifies the event name for open.
+ */
+ OPEN: 'open',
+
+ /**
+ * Variable: SAVE
+ *
+ * Specifies the event name for open.
+ */
+ SAVE: 'save',
+
+ /**
+ * Variable: BEFORE_ADD_VERTEX
+ *
+ * Specifies the event name for beforeAddVertex.
+ */
+ BEFORE_ADD_VERTEX: 'beforeAddVertex',
+
+ /**
+ * Variable: ADD_VERTEX
+ *
+ * Specifies the event name for addVertex.
+ */
+ ADD_VERTEX: 'addVertex',
+
+ /**
+ * Variable: AFTER_ADD_VERTEX
+ *
+ * Specifies the event name for afterAddVertex.
+ */
+ AFTER_ADD_VERTEX: 'afterAddVertex',
+
+ /**
+ * Variable: DONE
+ *
+ * Specifies the event name for done.
+ */
+ DONE: 'done',
+
+ /**
+ * Variable: EXECUTE
+ *
+ * Specifies the event name for execute.
+ */
+ EXECUTE: 'execute',
+
+ /**
+ * Variable: BEGIN_UPDATE
+ *
+ * Specifies the event name for beginUpdate.
+ */
+ BEGIN_UPDATE: 'beginUpdate',
+
+ /**
+ * Variable: END_UPDATE
+ *
+ * Specifies the event name for endUpdate.
+ */
+ END_UPDATE: 'endUpdate',
+
+ /**
+ * Variable: BEFORE_UNDO
+ *
+ * Specifies the event name for beforeUndo.
+ */
+ BEFORE_UNDO: 'beforeUndo',
+
+ /**
+ * Variable: UNDO
+ *
+ * Specifies the event name for undo.
+ */
+ UNDO: 'undo',
+
+ /**
+ * Variable: REDO
+ *
+ * Specifies the event name for redo.
+ */
+ REDO: 'redo',
+
+ /**
+ * Variable: CHANGE
+ *
+ * Specifies the event name for change.
+ */
+ CHANGE: 'change',
+
+ /**
+ * Variable: NOTIFY
+ *
+ * Specifies the event name for notify.
+ */
+ NOTIFY: 'notify',
+
+ /**
+ * Variable: LAYOUT_CELLS
+ *
+ * Specifies the event name for layoutCells.
+ */
+ LAYOUT_CELLS: 'layoutCells',
+
+ /**
+ * Variable: CLICK
+ *
+ * Specifies the event name for click.
+ */
+ CLICK: 'click',
+
+ /**
+ * Variable: SCALE
+ *
+ * Specifies the event name for scale.
+ */
+ SCALE: 'scale',
+
+ /**
+ * Variable: TRANSLATE
+ *
+ * Specifies the event name for translate.
+ */
+ TRANSLATE: 'translate',
+
+ /**
+ * Variable: SCALE_AND_TRANSLATE
+ *
+ * Specifies the event name for scaleAndTranslate.
+ */
+ SCALE_AND_TRANSLATE: 'scaleAndTranslate',
+
+ /**
+ * Variable: UP
+ *
+ * Specifies the event name for up.
+ */
+ UP: 'up',
+
+ /**
+ * Variable: DOWN
+ *
+ * Specifies the event name for down.
+ */
+ DOWN: 'down',
+
+ /**
+ * Variable: ADD
+ *
+ * Specifies the event name for add.
+ */
+ ADD: 'add',
+
+ /**
+ * Variable: REMOVE
+ *
+ * Specifies the event name for remove.
+ */
+ REMOVE: 'remove',
+
+ /**
+ * Variable: CLEAR
+ *
+ * Specifies the event name for clear.
+ */
+ CLEAR: 'clear',
+
+ /**
+ * Variable: ADD_CELLS
+ *
+ * Specifies the event name for addCells.
+ */
+ ADD_CELLS: 'addCells',
+
+ /**
+ * Variable: CELLS_ADDED
+ *
+ * Specifies the event name for cellsAdded.
+ */
+ CELLS_ADDED: 'cellsAdded',
+
+ /**
+ * Variable: MOVE_CELLS
+ *
+ * Specifies the event name for moveCells.
+ */
+ MOVE_CELLS: 'moveCells',
+
+ /**
+ * Variable: CELLS_MOVED
+ *
+ * Specifies the event name for cellsMoved.
+ */
+ CELLS_MOVED: 'cellsMoved',
+
+ /**
+ * Variable: RESIZE_CELLS
+ *
+ * Specifies the event name for resizeCells.
+ */
+ RESIZE_CELLS: 'resizeCells',
+
+ /**
+ * Variable: CELLS_RESIZED
+ *
+ * Specifies the event name for cellsResized.
+ */
+ CELLS_RESIZED: 'cellsResized',
+
+ /**
+ * Variable: TOGGLE_CELLS
+ *
+ * Specifies the event name for toggleCells.
+ */
+ TOGGLE_CELLS: 'toggleCells',
+
+ /**
+ * Variable: CELLS_TOGGLED
+ *
+ * Specifies the event name for cellsToggled.
+ */
+ CELLS_TOGGLED: 'cellsToggled',
+
+ /**
+ * Variable: ORDER_CELLS
+ *
+ * Specifies the event name for orderCells.
+ */
+ ORDER_CELLS: 'orderCells',
+
+ /**
+ * Variable: CELLS_ORDERED
+ *
+ * Specifies the event name for cellsOrdered.
+ */
+ CELLS_ORDERED: 'cellsOrdered',
+
+ /**
+ * Variable: REMOVE_CELLS
+ *
+ * Specifies the event name for removeCells.
+ */
+ REMOVE_CELLS: 'removeCells',
+
+ /**
+ * Variable: CELLS_REMOVED
+ *
+ * Specifies the event name for cellsRemoved.
+ */
+ CELLS_REMOVED: 'cellsRemoved',
+
+ /**
+ * Variable: GROUP_CELLS
+ *
+ * Specifies the event name for groupCells.
+ */
+ GROUP_CELLS: 'groupCells',
+
+ /**
+ * Variable: UNGROUP_CELLS
+ *
+ * Specifies the event name for ungroupCells.
+ */
+ UNGROUP_CELLS: 'ungroupCells',
+
+ /**
+ * Variable: REMOVE_CELLS_FROM_PARENT
+ *
+ * Specifies the event name for removeCellsFromParent.
+ */
+ REMOVE_CELLS_FROM_PARENT: 'removeCellsFromParent',
+
+ /**
+ * Variable: FOLD_CELLS
+ *
+ * Specifies the event name for foldCells.
+ */
+ FOLD_CELLS: 'foldCells',
+
+ /**
+ * Variable: CELLS_FOLDED
+ *
+ * Specifies the event name for cellsFolded.
+ */
+ CELLS_FOLDED: 'cellsFolded',
+
+ /**
+ * Variable: ALIGN_CELLS
+ *
+ * Specifies the event name for alignCells.
+ */
+ ALIGN_CELLS: 'alignCells',
+
+ /**
+ * Variable: LABEL_CHANGED
+ *
+ * Specifies the event name for labelChanged.
+ */
+ LABEL_CHANGED: 'labelChanged',
+
+ /**
+ * Variable: CONNECT_CELL
+ *
+ * Specifies the event name for connectCell.
+ */
+ CONNECT_CELL: 'connectCell',
+
+ /**
+ * Variable: CELL_CONNECTED
+ *
+ * Specifies the event name for cellConnected.
+ */
+ CELL_CONNECTED: 'cellConnected',
+
+ /**
+ * Variable: SPLIT_EDGE
+ *
+ * Specifies the event name for splitEdge.
+ */
+ SPLIT_EDGE: 'splitEdge',
+
+ /**
+ * Variable: FLIP_EDGE
+ *
+ * Specifies the event name for flipEdge.
+ */
+ FLIP_EDGE: 'flipEdge',
+
+ /**
+ * Variable: START_EDITING
+ *
+ * Specifies the event name for startEditing.
+ */
+ START_EDITING: 'startEditing',
+
+ /**
+ * Variable: ADD_OVERLAY
+ *
+ * Specifies the event name for addOverlay.
+ */
+ ADD_OVERLAY: 'addOverlay',
+
+ /**
+ * Variable: REMOVE_OVERLAY
+ *
+ * Specifies the event name for removeOverlay.
+ */
+ REMOVE_OVERLAY: 'removeOverlay',
+
+ /**
+ * Variable: UPDATE_CELL_SIZE
+ *
+ * Specifies the event name for updateCellSize.
+ */
+ UPDATE_CELL_SIZE: 'updateCellSize',
+
+ /**
+ * Variable: ESCAPE
+ *
+ * Specifies the event name for escape.
+ */
+ ESCAPE: 'escape',
+
+ /**
+ * Variable: CLICK
+ *
+ * Specifies the event name for click.
+ */
+ CLICK: 'click',
+
+ /**
+ * Variable: DOUBLE_CLICK
+ *
+ * Specifies the event name for doubleClick.
+ */
+ DOUBLE_CLICK: 'doubleClick',
+
+ /**
+ * Variable: START
+ *
+ * Specifies the event name for start.
+ */
+ START: 'start',
+
+ /**
+ * Variable: RESET
+ *
+ * Specifies the event name for reset.
+ */
+ RESET: 'reset'
+
+};
diff --git a/src/js/util/mxEventObject.js b/src/js/util/mxEventObject.js
new file mode 100644
index 0000000..cb08a55
--- /dev/null
+++ b/src/js/util/mxEventObject.js
@@ -0,0 +1,111 @@
+/**
+ * $Id: mxEventObject.js,v 1.11 2011-09-09 10:29:05 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxEventObject
+ *
+ * The mxEventObject is a wrapper for all properties of a single event.
+ * Additionally, it also offers functions to consume the event and check if it
+ * was consumed as follows:
+ *
+ * (code)
+ * evt.consume();
+ * INV: evt.isConsumed() == true
+ * (end)
+ *
+ * Constructor: mxEventObject
+ *
+ * Constructs a new event object with the specified name. An optional
+ * sequence of key, value pairs can be appended to define properties.
+ *
+ * Example:
+ *
+ * (code)
+ * new mxEventObject("eventName", key1, val1, .., keyN, valN)
+ * (end)
+ */
+function mxEventObject(name)
+{
+ this.name = name;
+ this.properties = [];
+
+ for (var i = 1; i < arguments.length; i += 2)
+ {
+ if (arguments[i + 1] != null)
+ {
+ this.properties[arguments[i]] = arguments[i + 1];
+ }
+ }
+};
+
+/**
+ * Variable: name
+ *
+ * Holds the name.
+ */
+mxEventObject.prototype.name = null;
+
+/**
+ * Variable: properties
+ *
+ * Holds the properties as an associative array.
+ */
+mxEventObject.prototype.properties = null;
+
+/**
+ * Variable: consumed
+ *
+ * Holds the consumed state. Default is false.
+ */
+mxEventObject.prototype.consumed = false;
+
+/**
+ * Function: getName
+ *
+ * Returns <name>.
+ */
+mxEventObject.prototype.getName = function()
+{
+ return this.name;
+};
+
+/**
+ * Function: getProperties
+ *
+ * Returns <properties>.
+ */
+mxEventObject.prototype.getProperties = function()
+{
+ return this.properties;
+};
+
+/**
+ * Function: getProperty
+ *
+ * Returns the property for the given key.
+ */
+mxEventObject.prototype.getProperty = function(key)
+{
+ return this.properties[key];
+};
+
+/**
+ * Function: isConsumed
+ *
+ * Returns true if the event has been consumed.
+ */
+mxEventObject.prototype.isConsumed = function()
+{
+ return this.consumed;
+};
+
+/**
+ * Function: consume
+ *
+ * Consumes the event.
+ */
+mxEventObject.prototype.consume = function()
+{
+ this.consumed = true;
+};
diff --git a/src/js/util/mxEventSource.js b/src/js/util/mxEventSource.js
new file mode 100644
index 0000000..595f560
--- /dev/null
+++ b/src/js/util/mxEventSource.js
@@ -0,0 +1,191 @@
+/**
+ * $Id: mxEventSource.js,v 1.25 2012-04-16 10:54:20 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxEventSource
+ *
+ * Base class for objects that dispatch named events. To create a subclass that
+ * inherits from mxEventSource, the following code is used.
+ *
+ * (code)
+ * function MyClass() { };
+ *
+ * MyClass.prototype = new mxEventSource();
+ * MyClass.prototype.constructor = MyClass;
+ * (end)
+ *
+ * Known Subclasses:
+ *
+ * <mxGraphModel>, <mxGraph>, <mxGraphView>, <mxEditor>, <mxCellOverlay>,
+ * <mxToolbar>, <mxWindow>
+ *
+ * Constructor: mxEventSource
+ *
+ * Constructs a new event source.
+ */
+function mxEventSource(eventSource)
+{
+ this.setEventSource(eventSource);
+};
+
+/**
+ * Variable: eventListeners
+ *
+ * Holds the event names and associated listeners in an array. The array
+ * contains the event name followed by the respective listener for each
+ * registered listener.
+ */
+mxEventSource.prototype.eventListeners = null;
+
+/**
+ * Variable: eventsEnabled
+ *
+ * Specifies if events can be fired. Default is true.
+ */
+mxEventSource.prototype.eventsEnabled = true;
+
+/**
+ * Variable: eventSource
+ *
+ * Optional source for events. Default is null.
+ */
+mxEventSource.prototype.eventSource = null;
+
+/**
+ * Function: isEventsEnabled
+ *
+ * Returns <eventsEnabled>.
+ */
+mxEventSource.prototype.isEventsEnabled = function()
+{
+ return this.eventsEnabled;
+};
+
+/**
+ * Function: setEventsEnabled
+ *
+ * Sets <eventsEnabled>.
+ */
+mxEventSource.prototype.setEventsEnabled = function(value)
+{
+ this.eventsEnabled = value;
+};
+
+/**
+ * Function: getEventSource
+ *
+ * Returns <eventSource>.
+ */
+mxEventSource.prototype.getEventSource = function()
+{
+ return this.eventSource;
+};
+
+/**
+ * Function: setEventSource
+ *
+ * Sets <eventSource>.
+ */
+mxEventSource.prototype.setEventSource = function(value)
+{
+ this.eventSource = value;
+};
+
+/**
+ * Function: addListener
+ *
+ * Binds the specified function to the given event name. If no event name
+ * is given, then the listener is registered for all events.
+ *
+ * The parameters of the listener are the sender and an <mxEventObject>.
+ */
+mxEventSource.prototype.addListener = function(name, funct)
+{
+ if (this.eventListeners == null)
+ {
+ this.eventListeners = [];
+ }
+
+ this.eventListeners.push(name);
+ this.eventListeners.push(funct);
+};
+
+/**
+ * Function: removeListener
+ *
+ * Removes all occurrences of the given listener from <eventListeners>.
+ */
+mxEventSource.prototype.removeListener = function(funct)
+{
+ if (this.eventListeners != null)
+ {
+ var i = 0;
+
+ while (i < this.eventListeners.length)
+ {
+ if (this.eventListeners[i+1] == funct)
+ {
+ this.eventListeners.splice(i, 2);
+ }
+ else
+ {
+ i += 2;
+ }
+ }
+ }
+};
+
+/**
+ * Function: fireEvent
+ *
+ * Dispatches the given event to the listeners which are registered for
+ * the event. The sender argument is optional. The current execution scope
+ * ("this") is used for the listener invocation (see <mxUtils.bind>).
+ *
+ * Example:
+ *
+ * (code)
+ * fireEvent(new mxEventObject("eventName", key1, val1, .., keyN, valN))
+ * (end)
+ *
+ * Parameters:
+ *
+ * evt - <mxEventObject> that represents the event.
+ * sender - Optional sender to be passed to the listener. Default value is
+ * the return value of <getEventSource>.
+ */
+mxEventSource.prototype.fireEvent = function(evt, sender)
+{
+ if (this.eventListeners != null &&
+ this.isEventsEnabled())
+ {
+ if (evt == null)
+ {
+ evt = new mxEventObject();
+ }
+
+ if (sender == null)
+ {
+ sender = this.getEventSource();
+ }
+
+ if (sender == null)
+ {
+ sender = this;
+ }
+
+ var args = [sender, evt];
+
+ for (var i = 0; i < this.eventListeners.length; i += 2)
+ {
+ var listen = this.eventListeners[i];
+
+ if (listen == null ||
+ listen == evt.getName())
+ {
+ this.eventListeners[i+1].apply(this, args);
+ }
+ }
+ }
+};
diff --git a/src/js/util/mxForm.js b/src/js/util/mxForm.js
new file mode 100644
index 0000000..bcee299
--- /dev/null
+++ b/src/js/util/mxForm.js
@@ -0,0 +1,202 @@
+/**
+ * $Id: mxForm.js,v 1.16 2010-10-08 04:21:45 david Exp $
+ * Copyright (c) 2006-2010, Gaudenz Alder, David Benson
+ */
+/**
+ * Class: mxForm
+ *
+ * A simple class for creating HTML forms.
+ *
+ * Constructor: mxForm
+ *
+ * Creates a HTML table using the specified classname.
+ */
+function mxForm(className)
+{
+ this.table = document.createElement('table');
+ this.table.className = className;
+ this.body = document.createElement('tbody');
+
+ this.table.appendChild(this.body);
+};
+
+/**
+ * Variable: table
+ *
+ * Holds the DOM node that represents the table.
+ */
+mxForm.prototype.table = null;
+
+/**
+ * Variable: body
+ *
+ * Holds the DOM node that represents the tbody (table body). New rows
+ * can be added to this object using DOM API.
+ */
+mxForm.prototype.body = false;
+
+/**
+ * Function: getTable
+ *
+ * Returns the table that contains this form.
+ */
+mxForm.prototype.getTable = function()
+{
+ return this.table;
+};
+
+/**
+ * Function: addButtons
+ *
+ * Helper method to add an OK and Cancel button using the respective
+ * functions.
+ */
+mxForm.prototype.addButtons = function(okFunct, cancelFunct)
+{
+ var tr = document.createElement('tr');
+ var td = document.createElement('td');
+ tr.appendChild(td);
+ td = document.createElement('td');
+
+ // Adds the ok button
+ var button = document.createElement('button');
+ mxUtils.write(button, mxResources.get('ok') || 'OK');
+ td.appendChild(button);
+
+ mxEvent.addListener(button, 'click', function()
+ {
+ okFunct();
+ });
+
+ // Adds the cancel button
+ button = document.createElement('button');
+ mxUtils.write(button, mxResources.get('cancel') || 'Cancel');
+ td.appendChild(button);
+
+ mxEvent.addListener(button, 'click', function()
+ {
+ cancelFunct();
+ });
+
+ tr.appendChild(td);
+ this.body.appendChild(tr);
+};
+
+/**
+ * Function: addText
+ *
+ * Adds a textfield for the given name and value and returns the textfield.
+ */
+mxForm.prototype.addText = function(name, value)
+{
+ var input = document.createElement('input');
+
+ input.setAttribute('type', 'text');
+ input.value = value;
+
+ return this.addField(name, input);
+};
+
+/**
+ * Function: addCheckbox
+ *
+ * Adds a checkbox for the given name and value and returns the textfield.
+ */
+mxForm.prototype.addCheckbox = function(name, value)
+{
+ var input = document.createElement('input');
+
+ input.setAttribute('type', 'checkbox');
+ this.addField(name, input);
+
+ // IE can only change the checked value if the input is inside the DOM
+ if (value)
+ {
+ input.checked = true;
+ }
+
+ return input;
+};
+
+/**
+ * Function: addTextarea
+ *
+ * Adds a textarea for the given name and value and returns the textarea.
+ */
+mxForm.prototype.addTextarea = function(name, value, rows)
+{
+ var input = document.createElement('textarea');
+
+ if (mxClient.IS_NS)
+ {
+ rows--;
+ }
+
+ input.setAttribute('rows', rows || 2);
+ input.value = value;
+
+ return this.addField(name, input);
+};
+
+/**
+ * Function: addCombo
+ *
+ * Adds a combo for the given name and returns the combo.
+ */
+mxForm.prototype.addCombo = function(name, isMultiSelect, size)
+{
+ var select = document.createElement('select');
+
+ if (size != null)
+ {
+ select.setAttribute('size', size);
+ }
+
+ if (isMultiSelect)
+ {
+ select.setAttribute('multiple', 'true');
+ }
+
+ return this.addField(name, select);
+};
+
+/**
+ * Function: addOption
+ *
+ * Adds an option for the given label to the specified combo.
+ */
+mxForm.prototype.addOption = function(combo, label, value, isSelected)
+{
+ var option = document.createElement('option');
+
+ mxUtils.writeln(option, label);
+ option.setAttribute('value', value);
+
+ if (isSelected)
+ {
+ option.setAttribute('selected', isSelected);
+ }
+
+ combo.appendChild(option);
+};
+
+/**
+ * Function: addField
+ *
+ * Adds a new row with the name and the input field in two columns and
+ * returns the given input.
+ */
+mxForm.prototype.addField = function(name, input)
+{
+ var tr = document.createElement('tr');
+ var td = document.createElement('td');
+ mxUtils.write(td, name);
+ tr.appendChild(td);
+
+ td = document.createElement('td');
+ td.appendChild(input);
+ tr.appendChild(td);
+ this.body.appendChild(tr);
+
+ return input;
+};
diff --git a/src/js/util/mxGuide.js b/src/js/util/mxGuide.js
new file mode 100644
index 0000000..ab5c26d
--- /dev/null
+++ b/src/js/util/mxGuide.js
@@ -0,0 +1,364 @@
+/**
+ * $Id: mxGuide.js,v 1.7 2012-04-13 12:53:30 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGuide
+ *
+ * Implements the alignment of selection cells to other cells in the graph.
+ *
+ * Constructor: mxGuide
+ *
+ * Constructs a new guide object.
+ */
+function mxGuide(graph, states)
+{
+ this.graph = graph;
+ this.setStates(states);
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph> instance.
+ */
+mxGuide.prototype.graph = null;
+
+/**
+ * Variable: states
+ *
+ * Contains the <mxCellStates> that are used for alignment.
+ */
+mxGuide.prototype.states = null;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies if horizontal guides are enabled. Default is true.
+ */
+mxGuide.prototype.horizontal = true;
+
+/**
+ * Variable: vertical
+ *
+ * Specifies if vertical guides are enabled. Default is true.
+ */
+mxGuide.prototype.vertical = true;
+
+/**
+ * Variable: vertical
+ *
+ * Holds the <mxShape> for the horizontal guide.
+ */
+mxGuide.prototype.guideX = null;
+
+/**
+ * Variable: vertical
+ *
+ * Holds the <mxShape> for the vertical guide.
+ */
+mxGuide.prototype.guideY = null;
+
+/**
+ * Variable: crisp
+ *
+ * Specifies if theguide should be rendered in crisp mode if applicable.
+ * Default is true.
+ */
+mxGuide.prototype.crisp = true;
+
+/**
+ * Function: setStates
+ *
+ * Sets the <mxCellStates> that should be used for alignment.
+ */
+mxGuide.prototype.setStates = function(states)
+{
+ this.states = states;
+};
+
+/**
+ * Function: isEnabledForEvent
+ *
+ * Returns true if the guide should be enabled for the given native event. This
+ * implementation always returns true.
+ */
+mxGuide.prototype.isEnabledForEvent = function(evt)
+{
+ return true;
+};
+
+/**
+ * Function: getGuideTolerance
+ *
+ * Returns the tolerance for the guides. Default value is
+ * gridSize * scale / 2.
+ */
+mxGuide.prototype.getGuideTolerance = function()
+{
+ return this.graph.gridSize * this.graph.view.scale / 2;
+};
+
+/**
+ * Function: createGuideShape
+ *
+ * Returns the mxShape to be used for painting the respective guide. This
+ * implementation returns a new, dashed and crisp <mxPolyline> using
+ * <mxConstants.GUIDE_COLOR> and <mxConstants.GUIDE_STROKEWIDTH> as the format.
+ *
+ * Parameters:
+ *
+ * horizontal - Boolean that specifies which guide should be created.
+ */
+mxGuide.prototype.createGuideShape = function(horizontal)
+{
+ var guide = new mxPolyline([], mxConstants.GUIDE_COLOR, mxConstants.GUIDE_STROKEWIDTH);
+ guide.crisp = this.crisp;
+ guide.isDashed = true;
+
+ return guide;
+};
+
+/**
+ * Function: move
+ *
+ * Moves the <bounds> by the given <mxPoint> and returnt the snapped point.
+ */
+mxGuide.prototype.move = function(bounds, delta, gridEnabled)
+{
+ if (this.states != null && (this.horizontal || this.vertical) && bounds != null && delta != null)
+ {
+ var trx = this.graph.getView().translate;
+ var scale = this.graph.getView().scale;
+ var dx = delta.x;
+ var dy = delta.y;
+
+ var overrideX = false;
+ var overrideY = false;
+
+ var tt = this.getGuideTolerance();
+ var ttX = tt;
+ var ttY = tt;
+
+ var b = bounds.clone();
+ b.x += delta.x;
+ b.y += delta.y;
+
+ var left = b.x;
+ var right = b.x + b.width;
+ var center = b.getCenterX();
+ var top = b.y;
+ var bottom = b.y + b.height;
+ var middle = b.getCenterY();
+
+ // Snaps the left, center and right to the given x-coordinate
+ function snapX(x)
+ {
+ x += this.graph.panDx;
+ var override = false;
+
+ if (Math.abs(x - center) < ttX)
+ {
+ dx = x - bounds.getCenterX();
+ ttX = Math.abs(x - center);
+ override = true;
+ }
+ else if (Math.abs(x - left) < ttX)
+ {
+ dx = x - bounds.x;
+ ttX = Math.abs(x - left);
+ override = true;
+ }
+ else if (Math.abs(x - right) < ttX)
+ {
+ dx = x - bounds.x - bounds.width;
+ ttX = Math.abs(x - right);
+ override = true;
+ }
+
+ if (override)
+ {
+ if (this.guideX == null)
+ {
+ this.guideX = this.createGuideShape(true);
+
+ // Makes sure to use either VML or SVG shapes in order to implement
+ // event-transparency on the background area of the rectangle since
+ // HTML shapes do not let mouseevents through even when transparent
+ this.guideX.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ this.guideX.init(this.graph.getView().getOverlayPane());
+
+ if (this.graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.guideX.node.setAttribute('pointer-events', 'none');
+ this.guideX.pipe.setAttribute('pointer-events', 'none');
+ }
+ }
+
+ var c = this.graph.container;
+ x -= this.graph.panDx;
+ this.guideX.points = [new mxPoint(x, -this.graph.panDy), new mxPoint(x, c.scrollHeight - 3 - this.graph.panDy)];
+ }
+
+ overrideX = overrideX || override;
+ };
+
+ // Snaps the top, middle or bottom to the given y-coordinate
+ function snapY(y)
+ {
+ y += this.graph.panDy;
+ var override = false;
+
+ if (Math.abs(y - middle) < ttY)
+ {
+ dy = y - bounds.getCenterY();
+ ttY = Math.abs(y - middle);
+ override = true;
+ }
+ else if (Math.abs(y - top) < ttY)
+ {
+ dy = y - bounds.y;
+ ttY = Math.abs(y - top);
+ override = true;
+ }
+ else if (Math.abs(y - bottom) < ttY)
+ {
+ dy = y - bounds.y - bounds.height;
+ ttY = Math.abs(y - bottom);
+ override = true;
+ }
+
+ if (override)
+ {
+ if (this.guideY == null)
+ {
+ this.guideY = this.createGuideShape(false);
+
+ // Makes sure to use either VML or SVG shapes in order to implement
+ // event-transparency on the background area of the rectangle since
+ // HTML shapes do not let mouseevents through even when transparent
+ this.guideY.dialect = (this.graph.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ this.guideY.init(this.graph.getView().getOverlayPane());
+
+ if (this.graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.guideY.node.setAttribute('pointer-events', 'none');
+ this.guideY.pipe.setAttribute('pointer-events', 'none');
+ }
+ }
+
+ var c = this.graph.container;
+ y -= this.graph.panDy;
+ this.guideY.points = [new mxPoint(-this.graph.panDx, y), new mxPoint(c.scrollWidth - 3 - this.graph.panDx, y)];
+ }
+
+ overrideY = overrideY || override;
+ };
+
+ for (var i = 0; i < this.states.length; i++)
+ {
+ var state = this.states[i];
+
+ if (state != null)
+ {
+ // Align x
+ if (this.horizontal)
+ {
+ snapX.call(this, state.getCenterX());
+ snapX.call(this, state.x);
+ snapX.call(this, state.x + state.width);
+ }
+
+ // Align y
+ if (this.vertical)
+ {
+ snapY.call(this, state.getCenterY());
+ snapY.call(this, state.y);
+ snapY.call(this, state.y + state.height);
+ }
+ }
+ }
+
+ if (!overrideX && this.guideX != null)
+ {
+ this.guideX.node.style.visibility = 'hidden';
+ }
+ else if (this.guideX != null)
+ {
+ this.guideX.node.style.visibility = 'visible';
+ this.guideX.redraw();
+ }
+
+ if (!overrideY && this.guideY != null)
+ {
+ this.guideY.node.style.visibility = 'hidden';
+ }
+ else if (this.guideY != null)
+ {
+ this.guideY.node.style.visibility = 'visible';
+ this.guideY.redraw();
+ }
+
+ // Moves cells that are off-grid back to the grid on move
+ if (gridEnabled)
+ {
+ if (!overrideX)
+ {
+ var tx = bounds.x - (this.graph.snap(bounds.x /
+ scale - trx.x) + trx.x) * scale;
+ dx = this.graph.snap(dx / scale) * scale - tx;
+ }
+
+ if (!overrideY)
+ {
+ var ty = bounds.y - (this.graph.snap(bounds.y /
+ scale - trx.y) + trx.y) * scale;
+ dy = this.graph.snap(dy / scale) * scale - ty;
+ }
+ }
+
+ delta = new mxPoint(dx, dy);
+ }
+
+ return delta;
+};
+
+/**
+ * Function: hide
+ *
+ * Hides all current guides.
+ */
+mxGuide.prototype.hide = function()
+{
+ if (this.guideX != null)
+ {
+ this.guideX.node.style.visibility = 'hidden';
+ }
+
+ if (this.guideY != null)
+ {
+ this.guideY.node.style.visibility = 'hidden';
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys all resources that this object uses.
+ */
+mxGuide.prototype.destroy = function()
+{
+ if (this.guideX != null)
+ {
+ this.guideX.destroy();
+ this.guideX = null;
+ }
+
+ if (this.guideY != null)
+ {
+ this.guideY.destroy();
+ this.guideY = null;
+ }
+};
diff --git a/src/js/util/mxImage.js b/src/js/util/mxImage.js
new file mode 100644
index 0000000..39d1a09
--- /dev/null
+++ b/src/js/util/mxImage.js
@@ -0,0 +1,40 @@
+/**
+ * $Id: mxImage.js,v 1.7 2010-01-02 09:45:14 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxImage
+ *
+ * Encapsulates the URL, width and height of an image.
+ *
+ * Constructor: mxImage
+ *
+ * Constructs a new image.
+ */
+function mxImage(src, width, height)
+{
+ this.src = src;
+ this.width = width;
+ this.height = height;
+};
+
+/**
+ * Variable: src
+ *
+ * String that specifies the URL of the image.
+ */
+mxImage.prototype.src = null;
+
+/**
+ * Variable: width
+ *
+ * Integer that specifies the width of the image.
+ */
+mxImage.prototype.width = null;
+
+/**
+ * Variable: height
+ *
+ * Integer that specifies the height of the image.
+ */
+mxImage.prototype.height = null;
diff --git a/src/js/util/mxImageBundle.js b/src/js/util/mxImageBundle.js
new file mode 100644
index 0000000..dc4c2cf
--- /dev/null
+++ b/src/js/util/mxImageBundle.js
@@ -0,0 +1,98 @@
+/**
+ * $Id: mxImageBundle.js,v 1.3 2011-01-20 19:08:11 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxImageBundle
+ *
+ * Maps from keys to base64 encoded images or file locations. All values must
+ * be URLs or use the format data:image/format followed by a comma and the base64
+ * encoded image data, eg. "data:image/gif,XYZ", where XYZ is the base64 encoded
+ * image data.
+ *
+ * To add a new image bundle to an existing graph, the following code is used:
+ *
+ * (code)
+ * var bundle = new mxImageBundle(alt);
+ * bundle.putImage('myImage', 'data:image/gif,R0lGODlhEAAQAMIGAAAAAICAAICAgP' +
+ * '//AOzp2O3r2////////yH+FUNyZWF0ZWQgd2l0aCBUaGUgR0lNUAAh+QQBCgAHACwAAAAA' +
+ * 'EAAQAAADTXi63AowynnAMDfjPUDlnAAJhmeBFxAEloliKltWmiYCQvfVr6lBPB1ggxN1hi' +
+ * 'laSSASFQpIV5HJBDyHpqK2ejVRm2AAgZCdmCGO9CIBADs=', fallback);
+ * graph.addImageBundle(bundle);
+ * (end);
+ *
+ * Alt is an optional boolean (default is false) that specifies if the value
+ * or the fallback should be returned in <getImage>.
+ *
+ * The image can then be referenced in any cell style using image=myImage.
+ * If you are using mxOutline, you should use the same image bundles in the
+ * graph that renders the outline.
+ *
+ * The keys for images are resolved in <mxGraph.postProcessCellStyle> and
+ * turned into a data URI if the returned value has a short data URI format
+ * as specified above.
+ *
+ * A typical value for the fallback is a MTHML link as defined in RFC 2557.
+ * Note that this format requires a file to be dynamically created on the
+ * server-side, or the page that contains the graph to be modified to contain
+ * the resources, this can be done by adding a comment that contains the
+ * resource in the HEAD section of the page after the title tag.
+ *
+ * This type of fallback mechanism should be used in IE6 and IE7. IE8 does
+ * support data URIs, but the maximum size is limited to 32 KB, which means
+ * all data URIs should be limited to 32 KB.
+ */
+function mxImageBundle(alt)
+{
+ this.images = [];
+ this.alt = (alt != null) ? alt : false;
+};
+
+/**
+ * Variable: images
+ *
+ * Maps from keys to images.
+ */
+mxImageBundle.prototype.images = null;
+
+/**
+ * Variable: alt
+ *
+ * Specifies if the fallback representation should be returned.
+ */
+mxImageBundle.prototype.images = null;
+
+/**
+ * Function: putImage
+ *
+ * Adds the specified entry to the map. The entry is an object with a value and
+ * fallback property as specified in the arguments.
+ */
+mxImageBundle.prototype.putImage = function(key, value, fallback)
+{
+ this.images[key] = {value: value, fallback: fallback};
+};
+
+/**
+ * Function: getImage
+ *
+ * Returns the value for the given key. This returns the value
+ * or fallback, depending on <alt>. The fallback is returned if
+ * <alt> is true, the value is returned otherwise.
+ */
+mxImageBundle.prototype.getImage = function(key)
+{
+ var result = null;
+
+ if (key != null)
+ {
+ var img = this.images[key];
+
+ if (img != null)
+ {
+ result = (this.alt) ? img.fallback : img.value;
+ }
+ }
+
+ return result;
+};
diff --git a/src/js/util/mxImageExport.js b/src/js/util/mxImageExport.js
new file mode 100644
index 0000000..dcbcf9a
--- /dev/null
+++ b/src/js/util/mxImageExport.js
@@ -0,0 +1,1412 @@
+/**
+ * $Id: mxImageExport.js,v 1.47 2012-09-24 14:54:32 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxImageExport
+ *
+ * Creates a new image export instance to be used with an export canvas. Here
+ * is an example that uses this class to create an image via a backend using
+ * <mxXmlExportCanvas>.
+ *
+ * (code)
+ * var xmlDoc = mxUtils.createXmlDocument();
+ * var root = xmlDoc.createElement('output');
+ * xmlDoc.appendChild(root);
+ *
+ * var xmlCanvas = new mxXmlCanvas2D(root);
+ * var imgExport = new mxImageExport();
+ * imgExport.drawState(graph.getView().getState(graph.model.root), xmlCanvas);
+ *
+ * var bounds = graph.getGraphBounds();
+ * var w = Math.ceil(bounds.x + bounds.width);
+ * var h = Math.ceil(bounds.y + bounds.height);
+ *
+ * var xml = mxUtils.getXml(root);
+ * new mxXmlRequest('export', 'format=png&w=' + w +
+ * '&h=' + h + '&bg=#F9F7ED&xml=' + encodeURIComponent(xml))
+ * .simulate(document, '_blank');
+ * (end)
+ *
+ * In order to export images for a graph whose container is not visible or not
+ * part of the DOM, the following workaround can be used to compute the size of
+ * the labels.
+ *
+ * (code)
+ * mxText.prototype.getTableSize = function(table)
+ * {
+ * var oldParent = table.parentNode;
+ *
+ * document.body.appendChild(table);
+ * var size = new mxRectangle(0, 0, table.offsetWidth, table.offsetHeight);
+ * oldParent.appendChild(table);
+ *
+ * return size;
+ * };
+ * (end)
+ *
+ * Constructor: mxImageExport
+ *
+ * Constructs a new image export.
+ */
+function mxImageExport()
+{
+ this.initShapes();
+ this.initMarkers();
+};
+
+/**
+ * Variable: includeOverlays
+ *
+ * Specifies if overlays should be included in the export. Default is false.
+ */
+mxImageExport.prototype.includeOverlays = false;
+
+/**
+ * Variable: glassSize
+ *
+ * Reference to the thread while the animation is running.
+ */
+mxImageExport.prototype.glassSize = 0.4;
+
+/**
+ * Variable: shapes
+ *
+ * Holds implementations for the built-in shapes.
+ */
+mxImageExport.prototype.shapes = null;
+
+/**
+ * Variable: markers
+ *
+ * Holds implementations for the built-in markers.
+ */
+mxImageExport.prototype.markers = null;
+
+/**
+ * Function: drawState
+ *
+ * Draws the given state and all its descendants to the given canvas.
+ */
+mxImageExport.prototype.drawState = function(state, canvas)
+{
+ if (state != null)
+ {
+ if (state.shape != null)
+ {
+ var shape = (state.shape.stencil != null) ?
+ state.shape.stencil :
+ this.shapes[state.style[mxConstants.STYLE_SHAPE]];
+
+ if (shape == null)
+ {
+ // Checks if there is a custom shape
+ if (typeof(state.shape.redrawPath) == 'function')
+ {
+ shape = this.createShape(state, canvas);
+ }
+ // Uses a rectangle for all vertices where no shape can be found
+ else if (state.view.graph.getModel().isVertex(state.cell))
+ {
+ shape = this.shapes['rectangle'];
+ }
+ }
+
+ if (shape != null)
+ {
+ this.drawShape(state, canvas, shape);
+
+ if (this.includeOverlays)
+ {
+ this.drawOverlays(state, canvas);
+ }
+ }
+ }
+
+ var graph = state.view.graph;
+ var childCount = graph.model.getChildCount(state.cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var childState = graph.view.getState(graph.model.getChildAt(state.cell, i));
+ this.drawState(childState, canvas);
+ }
+ }
+};
+
+/**
+ * Function: createShape
+ *
+ * Creates a shape wrapper for the custom shape in the given cell state and
+ * links its output to the given canvas.
+ */
+mxImageExport.prototype.createShape = function(state, canvas)
+{
+ return {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ var path =
+ {
+ translate: new mxPoint(bounds.x, bounds.y),
+ moveTo: function(x, y)
+ {
+ canvas.moveTo(this.translate.x + x, this.translate.y + y);
+ },
+ lineTo: function(x, y)
+ {
+ canvas.lineTo(this.translate.x + x, this.translate.y + y);
+ },
+ quadTo: function(x1, y1, x, y)
+ {
+ canvas.quadTo(this.translate.x + x1, this.translate.y + y1, this.translate.x + x, this.translate.y + y);
+ },
+ curveTo: function(x1, y1, x2, y2, x, y)
+ {
+ canvas.curveTo(this.translate.x + x1, this.translate.y + y1, this.translate.x + x2, this.translate.y + y2, this.translate.x + x, this.translate.y + y);
+ },
+ end: function()
+ {
+ // do nothing
+ },
+ close: function()
+ {
+ canvas.close();
+ }
+ };
+
+ if (!background)
+ {
+ canvas.fillAndStroke();
+ }
+
+ // LATER: Remove empty path if shape does not implement foreground, add shadow/clipping
+ canvas.begin();
+ state.shape.redrawPath.call(state.shape, path, bounds.x, bounds.y, bounds.width, bounds.height, !background);
+
+ if (!background)
+ {
+ canvas.fillAndStroke();
+ }
+
+ return true;
+ }
+ };
+};
+
+/**
+ * Function: drawOverlays
+ *
+ * Draws the overlays for the given state. This is called if <includeOverlays>
+ * is true.
+ */
+mxImageExport.prototype.drawOverlays = function(state, canvas)
+{
+ if (state.overlays != null)
+ {
+ state.overlays.visit(function(id, shape)
+ {
+ var bounds = shape.bounds;
+
+ if (bounds != null)
+ {
+ canvas.image(bounds.x, bounds.y, bounds.width, bounds.height, shape.image);
+ }
+ });
+ }
+};
+
+/**
+ * Function: drawShape
+ *
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.drawShape = function(state, canvas, shape)
+{
+ var rotation = mxUtils.getNumber(state.style, mxConstants.STYLE_ROTATION, 0);
+ var direction = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION, null);
+
+ // New styles for shape flipping the stencil
+ var flipH = state.style[mxConstants.STYLE_STENCIL_FLIPH];
+ var flipV = state.style[mxConstants.STYLE_STENCIL_FLIPV];
+
+ if (flipH ? !flipV : flipV)
+ {
+ rotation *= -1;
+ }
+
+ // Default direction is east (ignored if rotation exists)
+ if (direction != null)
+ {
+ if (direction == 'north')
+ {
+ rotation += 270;
+ }
+ else if (direction == 'west')
+ {
+ rotation += 180;
+ }
+ else if (direction == 'south')
+ {
+ rotation += 90;
+ }
+ }
+
+ if (flipH && flipV)
+ {
+ rotation += 180;
+ flipH = false;
+ flipV = false;
+ }
+
+ // Saves the global state for each cell
+ canvas.save();
+
+ // Adds rotation and horizontal/vertical flipping
+ // FIXME: Rotation and stencil flip only supported for stencil shapes
+ rotation = rotation % 360;
+
+ if (rotation != 0 || flipH || flipV)
+ {
+ canvas.rotate(rotation, flipH, flipV, state.getCenterX(), state.getCenterY());
+ }
+
+ // Note: Overwritten in mxStencil.paintShape (can depend on aspect)
+ var scale = state.view.scale;
+ var sw = mxUtils.getNumber(state.style, mxConstants.STYLE_STROKEWIDTH, 1) * scale;
+ canvas.setStrokeWidth(sw);
+
+ var sw2 = sw / 2;
+ var bg = this.getBackgroundBounds(state);
+
+ // Stencils will rotate the bounds as required
+ if (state.shape.stencil == null && (direction == 'south' || direction == 'north'))
+ {
+ var dx = (bg.width - bg.height) / 2;
+ bg.x += dx;
+ bg.y += -dx;
+ var tmp = bg.width;
+ bg.width = bg.height;
+ bg.height = tmp;
+ }
+
+ var bb = new mxRectangle(bg.x - sw2, bg.y - sw2, bg.width + sw, bg.height + sw);
+ var alpha = mxUtils.getValue(state.style, mxConstants.STYLE_OPACITY, 100) / 100;
+
+ var shp = state.style[mxConstants.STYLE_SHAPE];
+ var imageShape = shp == mxConstants.SHAPE_IMAGE;
+ var gradientColor = (imageShape) ? null : mxUtils.getValue(state.style, mxConstants.STYLE_GRADIENTCOLOR);
+
+ // Converts colors with special keyword none to null
+ if (gradientColor == mxConstants.NONE)
+ {
+ gradientColor = null;
+ }
+
+ var fcKey = (imageShape) ? mxConstants.STYLE_IMAGE_BACKGROUND : mxConstants.STYLE_FILLCOLOR;
+ var fillColor = mxUtils.getValue(state.style, fcKey, null);
+
+ if (fillColor == mxConstants.NONE)
+ {
+ fillColor = null;
+ }
+
+ var scKey = (imageShape) ? mxConstants.STYLE_IMAGE_BORDER : mxConstants.STYLE_STROKECOLOR;
+ var strokeColor = mxUtils.getValue(state.style, scKey, null);
+
+ if (strokeColor == mxConstants.NONE)
+ {
+ strokeColor = null;
+ }
+
+ var glass = (fillColor != null && (shp == mxConstants.SHAPE_LABEL || shp == mxConstants.SHAPE_RECTANGLE));
+
+ // Draws the shadow if the fillColor is not transparent
+ if (mxUtils.getValue(state.style, mxConstants.STYLE_SHADOW, false))
+ {
+ this.drawShadow(canvas, state, shape, rotation, flipH, flipV, bg, alpha, fillColor != null);
+ }
+
+ canvas.setAlpha(alpha);
+
+ // Sets the dashed state
+ if (mxUtils.getValue(state.style, mxConstants.STYLE_DASHED, '0') == '1')
+ {
+ canvas.setDashed(true);
+
+ // Supports custom dash patterns
+ var dash = state.style['dashPattern'];
+
+ if (dash != null)
+ {
+ canvas.setDashPattern(dash);
+ }
+ }
+
+ // Draws background and foreground
+ if (strokeColor != null || fillColor != null)
+ {
+ if (strokeColor != null)
+ {
+ canvas.setStrokeColor(strokeColor);
+ }
+
+ if (fillColor != null)
+ {
+ if (gradientColor != null && gradientColor != 'transparent')
+ {
+ canvas.setGradient(fillColor, gradientColor, bg.x, bg.y, bg.width, bg.height, direction);
+ }
+ else
+ {
+ canvas.setFillColor(fillColor);
+ }
+ }
+
+ // Draws background and foreground of shape
+ glass = shape.drawShape(canvas, state, bg, true, false) && glass;
+ shape.drawShape(canvas, state, bg, false, false);
+ }
+
+ // Draws the glass effect
+ // Requires background in generic shape for clipping
+ if (glass && mxUtils.getValue(state.style, mxConstants.STYLE_GLASS, 0) == 1)
+ {
+ this.drawGlass(state, canvas, bb, shape, this.glassSize);
+ }
+
+ // Draws the image (currently disabled for everything but image and label shapes)
+ if (imageShape || shp == mxConstants.SHAPE_LABEL)
+ {
+ var src = state.view.graph.getImage(state);
+
+ if (src != null)
+ {
+ var imgBounds = this.getImageBounds(state);
+
+ if (imgBounds != null)
+ {
+ this.drawImage(state, canvas, imgBounds, src);
+ }
+ }
+ }
+
+ // Restores canvas state
+ canvas.restore();
+
+ // Draws the label (label has separate rotation)
+ var txt = state.text;
+
+ // Does not use mxCellRenderer.getLabelValue to avoid conversion of HTML entities for VML
+ var label = state.view.graph.getLabel(state.cell);
+
+ if (txt != null && label != null && label.length > 0)
+ {
+ canvas.save();
+ canvas.setAlpha(mxUtils.getValue(state.style, mxConstants.STYLE_TEXT_OPACITY, 100) / 100);
+ var bounds = new mxRectangle(txt.boundingBox.x, txt.boundingBox.y, txt.boundingBox.width, txt.boundingBox.height);
+ var vert = mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, 1) == 0;
+
+ // Vertical error offset
+ bounds.y += 2;
+
+ if (vert)
+ {
+ if (txt.dialect != mxConstants.DIALECT_SVG)
+ {
+ var cx = bounds.x + bounds.width / 2;
+ var cy = bounds.y + bounds.height / 2;
+ var tmp = bounds.width;
+ bounds.width = bounds.height;
+ bounds.height = tmp;
+ bounds.x = cx - bounds.width / 2;
+ bounds.y = cy - bounds.height / 2;
+ }
+ else if (txt.dialect == mxConstants.DIALECT_SVG)
+ {
+ // Workarounds for different label bounding boxes (mostly ignoring rotation).
+ // LATER: Fix in mxText so that the bounding box is consistent and rotated.
+ // TODO: Check non-center/middle-aligned vertical labels in VML for IE8.
+ var b = state.y + state.height;
+ var cx = bounds.getCenterX() - state.x;
+ var cy = bounds.getCenterY() - state.y;
+
+ var y = b - cx - bounds.height / 2;
+ bounds.x = state.x + cy - bounds.width / 2;
+ bounds.y = y;
+ //bounds.x -= state.height / 2 - state.width / 2;
+ //bounds.y -= state.width / 2 - state.height / 2;
+ }
+ }
+
+ this.drawLabelBackground(state, canvas, bounds, vert);
+ this.drawLabel(state, canvas, bounds, vert, label);
+ canvas.restore();
+ }
+};
+
+/**
+ * Function: drawGlass
+ *
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.drawShadow = function(canvas, state, shape, rotation, flipH, flipV, bounds, alpha, filled)
+{
+ // Requires background in generic shape for shadow, looks like only one
+ // fillAndStroke is allowed per current path, try working around that
+ // Computes rotated shadow offset
+ var rad = rotation * Math.PI / 180;
+ var cos = Math.cos(-rad);
+ var sin = Math.sin(-rad);
+ var offset = mxUtils.getRotatedPoint(new mxPoint(mxConstants.SHADOW_OFFSET_X, mxConstants.SHADOW_OFFSET_Y), cos, sin);
+
+ if (flipH)
+ {
+ offset.x *= -1;
+ }
+
+ if (flipV)
+ {
+ offset.y *= -1;
+ }
+
+ // TODO: Use save/restore instead of negative offset to restore (requires fix for HTML canvas)
+ canvas.translate(offset.x, offset.y);
+
+ // Returns true if a shadow has been painted (path has been created)
+ if (shape.drawShape(canvas, state, bounds, true, true))
+ {
+ canvas.setAlpha(mxConstants.SHADOW_OPACITY * alpha);
+ canvas.shadow(mxConstants.SHADOWCOLOR, filled);
+ }
+
+ canvas.translate(-offset.x, -offset.y);
+};
+
+/**
+ * Function: drawGlass
+ *
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.drawGlass = function(state, canvas, bounds, shape, size)
+{
+ // LATER: Clipping region should include stroke
+ if (shape.drawShape(canvas, state, bounds, true, false))
+ {
+ canvas.save();
+ canvas.clip();
+ canvas.setGlassGradient(bounds.x, bounds.y, bounds.width, bounds.height);
+
+ canvas.begin();
+ canvas.moveTo(bounds.x, bounds.y);
+ canvas.lineTo(bounds.x, (bounds.y + bounds.height * size));
+ canvas.quadTo((bounds.x + bounds.width * 0.5),
+ (bounds.y + bounds.height * 0.7), bounds.x + bounds.width,
+ (bounds.y + bounds.height * size));
+ canvas.lineTo(bounds.x + bounds.width, bounds.y);
+ canvas.close();
+
+ canvas.fill();
+ canvas.restore();
+ }
+};
+
+/**
+ * Function: drawImage
+ *
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.drawImage = function(state, canvas, bounds, image)
+{
+ var aspect = mxUtils.getValue(state.style, mxConstants.STYLE_IMAGE_ASPECT, 1) == 1;
+ var flipH = mxUtils.getValue(state.style, mxConstants.STYLE_IMAGE_FLIPH, 0) == 1;
+ var flipV = mxUtils.getValue(state.style, mxConstants.STYLE_IMAGE_FLIPV, 0) == 1;
+
+ canvas.image(bounds.x, bounds.y, bounds.width, bounds.height, image, aspect, flipH, flipV);
+};
+
+/**
+ * Function: drawLabelBackground
+ *
+ * Draws background for the label of the given state to the given canvas.
+ */
+mxImageExport.prototype.drawLabelBackground = function(state, canvas, bounds, vert)
+{
+ var stroke = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_BORDERCOLOR);
+ var fill = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_BACKGROUNDCOLOR);
+
+ if (stroke == mxConstants.NONE)
+ {
+ stroke = null;
+ }
+
+ if (fill == mxConstants.NONE)
+ {
+ fill = null;
+ }
+
+ if (stroke != null || fill != null)
+ {
+ var x = bounds.x;
+ var y = bounds.y - mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_PADDING, 0);
+ var w = bounds.width;
+ var h = bounds.height;
+
+ if (vert)
+ {
+ x += (w - h) / 2;
+ y += (h - w) / 2;
+ var tmp = w;
+ w = h;
+ h = tmp;
+ }
+
+ if (fill != null)
+ {
+ canvas.setFillColor(fill);
+ }
+
+ if (stroke != null)
+ {
+ canvas.setStrokeColor(stroke);
+ canvas.setStrokeWidth(1);
+ canvas.setDashed(false);
+ }
+
+ canvas.rect(x, y, w, h);
+
+ if (fill != null && stroke != null)
+ {
+ canvas.fillAndStroke();
+ }
+ else if (fill != null)
+ {
+ canvas.fill();
+ }
+ else if (stroke != null)
+ {
+ canvas.stroke();
+ }
+ }
+};
+
+/**
+ * Function: drawLabel
+ *
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.drawLabel = function(state, canvas, bounds, vert, str)
+{
+ var scale = state.view.scale;
+
+ // Applies color
+ canvas.setFontColor(mxUtils.getValue(state.style, mxConstants.STYLE_FONTCOLOR, '#000000'));
+
+ // Applies font settings
+ canvas.setFontFamily(mxUtils.getValue(state.style, mxConstants.STYLE_FONTFAMILY,
+ mxConstants.DEFAULT_FONTFAMILY));
+ canvas.setFontStyle(mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0));
+ canvas.setFontSize(mxUtils.getValue(state.style, mxConstants.STYLE_FONTSIZE,
+ mxConstants.DEFAULT_FONTSIZE) * scale);
+
+ var align = mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT);
+
+ // Uses null alignment for default values (valign default is 'top' which is fine)
+ if (align == 'left')
+ {
+ align = null;
+ }
+
+ var y = bounds.y - mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_PADDING, 0);
+ var wrap = state.view.graph.isWrapping(state.cell);
+ var html = state.view.graph.isHtmlLabel(state.cell);
+
+ // Replaces linefeeds in HTML markup to match the display output
+ if (html && mxText.prototype.replaceLinefeeds)
+ {
+ str = str.replace(/\n/g, '<br/>');
+ }
+
+ canvas.text(bounds.x, y, bounds.width, bounds.height, str, align, null, vert, wrap, (html) ? 'html' : '');
+};
+
+/**
+ * Function: getBackgroundBounds
+ *
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.getBackgroundBounds = function(state)
+{
+ if (state.style[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_SWIMLANE)
+ {
+ var scale = state.view.scale;
+ var start = mxUtils.getValue(state.style, mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE) * scale;
+ var w = state.width;
+ var h = state.height;
+
+ if (mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ h = start;
+ }
+ else
+ {
+ w = start;
+ }
+
+ return new mxRectangle(state.x, state.y, Math.min(state.width, w), Math.min(state.height, h));
+ }
+ else
+ {
+ return new mxRectangle(state.x, state.y, state.width, state.height);
+ }
+};
+
+/**
+ * Function: getImageBounds
+ *
+ * Draws the given state to the given canvas.
+ */
+mxImageExport.prototype.getImageBounds = function(state)
+{
+ var bounds = new mxRectangle(state.x, state.y, state.width, state.height);
+ var style = state.style;
+
+ if (mxUtils.getValue(style, mxConstants.STYLE_SHAPE) != mxConstants.SHAPE_IMAGE)
+ {
+ var imgAlign = mxUtils.getValue(style, mxConstants.STYLE_IMAGE_ALIGN, mxConstants.ALIGN_LEFT);
+ var imgValign = mxUtils.getValue(style, mxConstants.STYLE_IMAGE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE);
+ var imgWidth = mxUtils.getValue(style, mxConstants.STYLE_IMAGE_WIDTH, mxConstants.DEFAULT_IMAGESIZE);
+ var imgHeight = mxUtils.getValue(style, mxConstants.STYLE_IMAGE_HEIGHT, mxConstants.DEFAULT_IMAGESIZE);
+ var spacing = mxUtils.getValue(style, mxConstants.STYLE_SPACING, 2);
+
+ if (imgAlign == mxConstants.ALIGN_CENTER)
+ {
+ bounds.x += (bounds.width - imgWidth) / 2;
+ }
+ else if (imgAlign == mxConstants.ALIGN_RIGHT)
+ {
+ bounds.x += bounds.width - imgWidth - spacing - 2;
+ }
+ else
+ // LEFT
+ {
+ bounds.x += spacing + 4;
+ }
+
+ if (imgValign == mxConstants.ALIGN_TOP)
+ {
+ bounds.y += spacing;
+ }
+ else if (imgValign == mxConstants.ALIGN_BOTTOM)
+ {
+ bounds.y += bounds.height - imgHeight - spacing;
+ }
+ else
+ // MIDDLE
+ {
+ bounds.y += (bounds.height - imgHeight) / 2;
+ }
+
+ bounds.width = imgWidth;
+ bounds.height = imgHeight;
+ }
+
+ return bounds;
+};
+
+/**
+ * Function: drawMarker
+ *
+ * Initializes the built-in shapes.
+ */
+mxImageExport.prototype.drawMarker = function(canvas, state, source)
+{
+ var offset = null;
+
+ // Computes the norm and the inverse norm
+ var pts = state.absolutePoints;
+ var n = pts.length;
+
+ var p0 = (source) ? pts[1] : pts[n - 2];
+ var pe = (source) ? pts[0] : pts[n - 1];
+
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+
+ var dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
+
+ var unitX = dx / dist;
+ var unitY = dy / dist;
+
+ var size = mxUtils.getValue(state.style, (source) ?
+ mxConstants.STYLE_STARTSIZE :
+ mxConstants.STYLE_ENDSIZE,
+ mxConstants.DEFAULT_MARKERSIZE);
+
+ // Allow for stroke width in the end point used and the
+ // orthogonal vectors describing the direction of the marker
+ // TODO: Should get strokewidth from canvas (same for strokecolor)
+ var sw = mxUtils.getValue(state.style, mxConstants.STYLE_STROKEWIDTH, 1);
+
+ pe = pe.clone();
+
+ var type = mxUtils.getValue(state.style, (source) ?
+ mxConstants.STYLE_STARTARROW :
+ mxConstants.STYLE_ENDARROW);
+ var f = this.markers[type];
+
+ if (f != null)
+ {
+ offset = f(canvas, state, type, pe, unitX, unitY, size, source, sw);
+ }
+
+ return offset;
+};
+
+/**
+ * Function: initShapes
+ *
+ * Initializes the built-in shapes.
+ */
+mxImageExport.prototype.initShapes = function()
+{
+ this.shapes = [];
+
+ // Implements the rectangle and rounded rectangle shape
+ this.shapes['rectangle'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ // Paints the shape
+ if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false))
+ {
+ var f = mxUtils.getValue(state.style, mxConstants.STYLE_ARCSIZE, mxConstants.RECTANGLE_ROUNDING_FACTOR * 100) / 100;
+ var r = Math.min(bounds.width * f, bounds.height * f);
+ canvas.roundrect(bounds.x, bounds.y, bounds.width, bounds.height, r, r);
+ }
+ else
+ {
+ canvas.rect(bounds.x, bounds.y, bounds.width, bounds.height);
+ }
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+ }
+ };
+
+ // Implements the swimlane shape
+ this.shapes['swimlane'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ if (mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false))
+ {
+ var r = Math.min(bounds.width * mxConstants.RECTANGLE_ROUNDING_FACTOR,
+ bounds.height * mxConstants.RECTANGLE_ROUNDING_FACTOR);
+ canvas.roundrect(bounds.x, bounds.y, bounds.width, bounds.height, r, r);
+ }
+ else
+ {
+ canvas.rect(bounds.x, bounds.y, bounds.width, bounds.height);
+ }
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ canvas.begin();
+
+ var x = state.x;
+ var y = state.y;
+ var w = state.width;
+ var h = state.height;
+
+ if (mxUtils.getValue(state.style, mxConstants.STYLE_HORIZONTAL, 1) == 0)
+ {
+ x += bounds.width;
+ w -= bounds.width;
+
+ canvas.moveTo(x, y);
+ canvas.lineTo(x + w, y);
+ canvas.lineTo(x + w, y + h);
+ canvas.lineTo(x, y + h);
+ }
+ else
+ {
+ y += bounds.height;
+ h -= bounds.height;
+
+ canvas.moveTo(x, y);
+ canvas.lineTo(x, y + h);
+ canvas.lineTo(x + w, y + h);
+ canvas.lineTo(x + w, y);
+ }
+
+ canvas.stroke();
+ }
+ }
+ };
+
+ this.shapes['image'] = this.shapes['rectangle'];
+ this.shapes['label'] = this.shapes['rectangle'];
+
+ var imageExport = this;
+
+ this.shapes['connector'] =
+ {
+ translatePoint: function(points, index, offset)
+ {
+ if (offset != null)
+ {
+ var pt = points[index].clone();
+ pt.x += offset.x;
+ pt.y += offset.y;
+ points[index] = pt;
+ }
+ },
+
+ drawShape: function(canvas, state, bounds, background, shadow)
+ {
+ if (background)
+ {
+ var rounded = mxUtils.getValue(state.style, mxConstants.STYLE_ROUNDED, false);
+ var arcSize = mxConstants.LINE_ARCSIZE / 2;
+
+ // Does not draw the markers in the shadow to match the display
+ canvas.setFillColor((shadow) ? mxConstants.NONE : mxUtils.getValue(state.style, mxConstants.STYLE_STROKECOLOR, "#000000"));
+ canvas.setDashed(false);
+ var pts = state.absolutePoints.slice();
+ this.translatePoint(pts, 0, imageExport.drawMarker(canvas, state, true));
+ this.translatePoint(pts, pts.length - 1, imageExport.drawMarker(canvas, state, false));
+ canvas.setDashed(mxUtils.getValue(state.style, mxConstants.STYLE_DASHED, '0') == '1');
+
+ var pt = pts[0];
+ var pe = pts[pts.length - 1];
+ canvas.begin();
+ canvas.moveTo(pt.x, pt.y);
+
+ // Draws the line segments
+ for (var i = 1; i < pts.length - 1; i++)
+ {
+ var tmp = pts[i];
+ var dx = pt.x - tmp.x;
+ var dy = pt.y - tmp.y;
+
+ if ((rounded && i < pts.length - 1) && (dx != 0 || dy != 0))
+ {
+ // Draws a line from the last point to the current
+ // point with a spacing of size off the current point
+ // into direction of the last point
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ var nx1 = dx * Math.min(arcSize, dist / 2) / dist;
+ var ny1 = dy * Math.min(arcSize, dist / 2) / dist;
+
+ var x1 = tmp.x + nx1;
+ var y1 = tmp.y + ny1;
+ canvas.lineTo(x1, y1);
+
+ // Draws a curve from the last point to the current
+ // point with a spacing of size off the current point
+ // into direction of the next point
+ var next = pts[i + 1];
+ dx = next.x - tmp.x;
+ dy = next.y - tmp.y;
+
+ dist = Math.max(1, Math.sqrt(dx * dx + dy * dy));
+ var nx2 = dx * Math.min(arcSize, dist / 2) / dist;
+ var ny2 = dy * Math.min(arcSize, dist / 2) / dist;
+
+ var x2 = tmp.x + nx2;
+ var y2 = tmp.y + ny2;
+
+ canvas.curveTo(tmp.x, tmp.y, tmp.x, tmp.y, x2, y2);
+ tmp = new mxPoint(x2, y2);
+ }
+ else
+ {
+ canvas.lineTo(tmp.x, tmp.y);
+ }
+
+ pt = tmp;
+ }
+
+ canvas.lineTo(pe.x, pe.y);
+ canvas.stroke();
+
+ return true;
+ }
+ else
+ {
+ // no foreground
+ }
+ }
+ };
+
+ this.shapes['arrow'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ // Geometry of arrow
+ var spacing = mxConstants.ARROW_SPACING;
+ var width = mxConstants.ARROW_WIDTH;
+ var arrow = mxConstants.ARROW_SIZE;
+
+ // Base vector (between end points)
+ var pts = state.absolutePoints;
+ var p0 = pts[0];
+ var pe = pts[pts.length - 1];
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+ var dist = Math.sqrt(dx * dx + dy * dy);
+ var length = dist - 2 * spacing - arrow;
+
+ // Computes the norm and the inverse norm
+ var nx = dx / dist;
+ var ny = dy / dist;
+ var basex = length * nx;
+ var basey = length * ny;
+ var floorx = width * ny/3;
+ var floory = -width * nx/3;
+
+ // Computes points
+ var p0x = p0.x - floorx / 2 + spacing * nx;
+ var p0y = p0.y - floory / 2 + spacing * ny;
+ var p1x = p0x + floorx;
+ var p1y = p0y + floory;
+ var p2x = p1x + basex;
+ var p2y = p1y + basey;
+ var p3x = p2x + floorx;
+ var p3y = p2y + floory;
+ // p4 not necessary
+ var p5x = p3x - 3 * floorx;
+ var p5y = p3y - 3 * floory;
+
+ canvas.begin();
+ canvas.moveTo(p0x, p0y);
+ canvas.lineTo(p1x, p1y);
+ canvas.lineTo(p2x, p2y);
+ canvas.lineTo(p3x, p3y);
+ canvas.lineTo(pe.x - spacing * nx, pe.y - spacing * ny);
+ canvas.lineTo(p5x, p5y);
+ canvas.lineTo(p5x + floorx, p5y + floory);
+ canvas.close();
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+ }
+ };
+
+ this.shapes['cylinder'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ return false;
+ }
+ else
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+ var dy = Math.min(mxCylinder.prototype.maxHeight, Math.floor(h / 5));
+
+ canvas.begin();
+ canvas.moveTo(x, y + dy);
+ canvas.curveTo(x, y - dy / 3, x + w, y - dy / 3, x + w, y + dy);
+ canvas.lineTo(x + w, y + h - dy);
+ canvas.curveTo(x + w, y + h + dy / 3, x, y + h + dy / 3, x, y + h - dy);
+ canvas.close();
+ canvas.fillAndStroke();
+
+ canvas.begin();
+ canvas.moveTo(x, y + dy);
+ canvas.curveTo(x, y + 2 * dy, x + w, y + 2 * dy, x + w, y + dy);
+ canvas.stroke();
+ }
+ }
+ };
+
+ this.shapes['line'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ return false;
+ }
+ else
+ {
+ canvas.begin();
+
+ var mid = state.getCenterY();
+ canvas.moveTo(bounds.x, mid);
+ canvas.lineTo(bounds.x + bounds.width, mid);
+
+ canvas.stroke();
+ }
+ }
+ };
+
+ this.shapes['ellipse'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ canvas.ellipse(bounds.x, bounds.y, bounds.width, bounds.height);
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+ }
+ };
+
+ this.shapes['doubleEllipse'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+
+ if (background)
+ {
+ canvas.ellipse(x, y, w, h);
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+
+ var inset = Math.min(4, Math.min(w / 5, h / 5));
+ x += inset;
+ y += inset;
+ w -= 2 * inset;
+ h -= 2 * inset;
+
+ if (w > 0 && h > 0)
+ {
+ canvas.ellipse(x, y, w, h);
+ }
+
+ canvas.stroke();
+ }
+ }
+ };
+
+ this.shapes['triangle'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+ canvas.begin();
+ canvas.moveTo(x, y);
+ canvas.lineTo(x + w, y + h / 2);
+ canvas.lineTo(x, y + h);
+ canvas.close();
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+ }
+ };
+
+ this.shapes['rhombus'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+ var hw = w / 2;
+ var hh = h / 2;
+
+ canvas.begin();
+ canvas.moveTo(x + hw, y);
+ canvas.lineTo(x + w, y + hh);
+ canvas.lineTo(x + hw, y + h);
+ canvas.lineTo(x, y + hh);
+ canvas.close();
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+ }
+
+ };
+
+ this.shapes['hexagon'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+
+ canvas.begin();
+ canvas.moveTo(x + 0.25 * w, y);
+ canvas.lineTo(x + 0.75 * w, y);
+ canvas.lineTo(x + w, y + 0.5 * h);
+ canvas.lineTo(x + 0.75 * w, y + h);
+ canvas.lineTo(x + 0.25 * w, y + h);
+ canvas.lineTo(x, y + 0.5 * h);
+ canvas.close();
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+ }
+ };
+
+ this.shapes['actor'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+ var width = w * 2 / 6;
+
+ canvas.begin();
+ canvas.moveTo(x, y + h);
+ canvas.curveTo(x, y + 3 * h / 5, x, y + 2 * h / 5, x + w / 2, y + 2 * h
+ / 5);
+ canvas.curveTo(x + w / 2 - width, y + 2 * h / 5, x + w / 2 - width, y, x
+ + w / 2, y);
+ canvas.curveTo(x + w / 2 + width, y, x + w / 2 + width, y + 2 * h / 5, x
+ + w / 2, y + 2 * h / 5);
+ canvas.curveTo(x + w, y + 2 * h / 5, x + w, y + 3 * h / 5, x + w, y + h);
+ canvas.close();
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+ }
+ };
+
+ this.shapes['cloud'] =
+ {
+ drawShape: function(canvas, state, bounds, background)
+ {
+ if (background)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+
+ canvas.begin();
+ canvas.moveTo(x + 0.25 * w, y + 0.25 * h);
+ canvas.curveTo(x + 0.05 * w, y + 0.25 * h, x,
+ y + 0.5 * h, x + 0.16 * w, y + 0.55 * h);
+ canvas.curveTo(x, y + 0.66 * h, x + 0.18 * w,
+ y + 0.9 * h, x + 0.31 * w, y + 0.8 * h);
+ canvas.curveTo(x + 0.4 * w, y + h, x + 0.7 * w,
+ y + h, x + 0.8 * w, y + 0.8 * h);
+ canvas.curveTo(x + w, y + 0.8 * h, x + w,
+ y + 0.6 * h, x + 0.875 * w, y + 0.5 * h);
+ canvas.curveTo(x + w, y + 0.3 * h, x + 0.8 * w,
+ y + 0.1 * h, x + 0.625 * w, y + 0.2 * h);
+ canvas.curveTo(x + 0.5 * w, y + 0.05 * h,
+ x + 0.3 * w, y + 0.05 * h,
+ x + 0.25 * w, y + 0.25 * h);
+ canvas.close();
+
+ return true;
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+ }
+ };
+
+};
+
+/**
+ * Function: initMarkers
+ *
+ * Initializes the built-in markers.
+ */
+mxImageExport.prototype.initMarkers = function()
+{
+ this.markers = [];
+
+ var tmp = function(canvas, state, type, pe, unitX, unitY, size, source, sw)
+ {
+ // The angle of the forward facing arrow sides against the x axis is
+ // 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
+ // only half the strokewidth is processed ).
+ var endOffsetX = unitX * sw * 1.118;
+ var endOffsetY = unitY * sw * 1.118;
+
+ pe.x -= endOffsetX;
+ pe.y -= endOffsetY;
+
+ unitX = unitX * (size + sw);
+ unitY = unitY * (size + sw);
+
+ canvas.begin();
+ canvas.moveTo(pe.x, pe.y);
+ canvas.lineTo(pe.x - unitX - unitY / 2, pe.y - unitY + unitX / 2);
+
+ if (type == mxConstants.ARROW_CLASSIC)
+ {
+ canvas.lineTo(pe.x - unitX * 3 / 4, pe.y - unitY * 3 / 4);
+ }
+
+ canvas.lineTo(pe.x + unitY / 2 - unitX, pe.y - unitY - unitX / 2);
+ canvas.close();
+
+ var key = (source) ? mxConstants.STYLE_STARTFILL : mxConstants.STYLE_ENDFILL;
+
+ if (state.style[key] == 0)
+ {
+ canvas.stroke();
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+
+ var f = (type != mxConstants.ARROW_CLASSIC) ? 1 : 3 / 4;
+ return new mxPoint(-unitX * f - endOffsetX, -unitY * f - endOffsetY);
+ };
+
+ this.markers['classic'] = tmp;
+ this.markers['block'] = tmp;
+
+ this.markers['open'] = function(canvas, state, type, pe, unitX, unitY, size, source, sw)
+ {
+ // The angle of the forward facing arrow sides against the x axis is
+ // 26.565 degrees, 1/sin(26.565) = 2.236 / 2 = 1.118 ( / 2 allows for
+ // only half the strokewidth is processed ).
+ var endOffsetX = unitX * sw * 1.118;
+ var endOffsetY = unitY * sw * 1.118;
+
+ pe.x -= endOffsetX;
+ pe.y -= endOffsetY;
+
+ unitX = unitX * (size + sw);
+ unitY = unitY * (size + sw);
+
+ canvas.begin();
+ canvas.moveTo(pe.x - unitX - unitY / 2, pe.y - unitY + unitX / 2);
+ canvas.lineTo(pe.x, pe.y);
+ canvas.lineTo(pe.x + unitY / 2 - unitX, pe.y - unitY - unitX / 2);
+ canvas.stroke();
+
+ return new mxPoint(-endOffsetX * 2, -endOffsetY * 2);
+ };
+
+ this.markers['oval'] = function(canvas, state, type, pe, unitX, unitY, size, source, sw)
+ {
+ var a = size / 2;
+
+ canvas.ellipse(pe.x - a, pe.y - a, size, size);
+
+ var key = (source) ? mxConstants.STYLE_STARTFILL : mxConstants.STYLE_ENDFILL;
+
+ if (state.style[key] == 0)
+ {
+ canvas.stroke();
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+
+ return new mxPoint(-unitX / 2, -unitY / 2);
+ };
+
+ var tmp_diamond = function(canvas, state, type, pe, unitX, unitY, size, source, sw)
+ {
+ // The angle of the forward facing arrow sides against the x axis is
+ // 45 degrees, 1/sin(45) = 1.4142 / 2 = 0.7071 ( / 2 allows for
+ // only half the strokewidth is processed ). Or 0.9862 for thin diamond.
+ // Note these values and the tk variable below are dependent, update
+ // both together (saves trig hard coding it).
+ var swFactor = (type == mxConstants.ARROW_DIAMOND) ? 0.7071 : 0.9862;
+ var endOffsetX = unitX * sw * swFactor;
+ var endOffsetY = unitY * sw * swFactor;
+
+ unitX = unitX * (size + sw);
+ unitY = unitY * (size + sw);
+
+ pe.x -= endOffsetX;
+ pe.y -= endOffsetY;
+
+ // thickness factor for diamond
+ var tk = ((type == mxConstants.ARROW_DIAMOND) ? 2 : 3.4);
+
+ canvas.begin();
+ canvas.moveTo(pe.x, pe.y);
+ canvas.lineTo(pe.x - unitX / 2 - unitY / tk, pe.y + unitX / tk - unitY / 2);
+ canvas.lineTo(pe.x - unitX, pe.y - unitY);
+ canvas.lineTo(pe.x - unitX / 2 + unitY / tk, pe.y - unitY / 2 - unitX / tk);
+ canvas.close();
+
+ var key = (source) ? mxConstants.STYLE_STARTFILL : mxConstants.STYLE_ENDFILL;
+
+ if (state.style[key] == 0)
+ {
+ canvas.stroke();
+ }
+ else
+ {
+ canvas.fillAndStroke();
+ }
+
+ return new mxPoint(-endOffsetX - unitX, -endOffsetY - unitY);
+ };
+
+ this.markers['diamond'] = tmp_diamond;
+ this.markers['diamondThin'] = tmp_diamond;
+};
diff --git a/src/js/util/mxLog.js b/src/js/util/mxLog.js
new file mode 100644
index 0000000..c556e22
--- /dev/null
+++ b/src/js/util/mxLog.js
@@ -0,0 +1,410 @@
+/**
+ * $Id: mxLog.js,v 1.32 2012-11-12 09:40:59 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxLog =
+{
+ /**
+ * Class: mxLog
+ *
+ * A singleton class that implements a simple console.
+ *
+ * Variable: consoleName
+ *
+ * Specifies the name of the console window. Default is 'Console'.
+ */
+ consoleName: 'Console',
+
+ /**
+ * Variable: TRACE
+ *
+ * Specified if the output for <enter> and <leave> should be visible in the
+ * console. Default is false.
+ */
+ TRACE: false,
+
+ /**
+ * Variable: DEBUG
+ *
+ * Specifies if the output for <debug> should be visible in the console.
+ * Default is true.
+ */
+ DEBUG: true,
+
+ /**
+ * Variable: WARN
+ *
+ * Specifies if the output for <warn> should be visible in the console.
+ * Default is true.
+ */
+ WARN: true,
+
+ /**
+ * Variable: buffer
+ *
+ * Buffer for pre-initialized content.
+ */
+ buffer: '',
+
+ /**
+ * Function: init
+ *
+ * Initializes the DOM node for the console. This requires document.body to
+ * point to a non-null value. This is called from within <setVisible> if the
+ * log has not yet been initialized.
+ */
+ init: function()
+ {
+ if (mxLog.window == null && document.body != null)
+ {
+ var title = mxLog.consoleName + ' - mxGraph ' + mxClient.VERSION;
+
+ // Creates a table that maintains the layout
+ var table = document.createElement('table');
+ table.setAttribute('width', '100%');
+ table.setAttribute('height', '100%');
+
+ var tbody = document.createElement('tbody');
+ var tr = document.createElement('tr');
+ var td = document.createElement('td');
+ td.style.verticalAlign = 'top';
+
+ // Adds the actual console as a textarea
+ mxLog.textarea = document.createElement('textarea');
+ mxLog.textarea.setAttribute('readOnly', 'true');
+ mxLog.textarea.style.height = '100%';
+ mxLog.textarea.style.resize = 'none';
+ mxLog.textarea.value = mxLog.buffer;
+
+ // Workaround for wrong width in standards mode
+ if (mxClient.IS_NS && document.compatMode != 'BackCompat')
+ {
+ mxLog.textarea.style.width = '99%';
+ }
+ else
+ {
+ mxLog.textarea.style.width = '100%';
+ }
+
+ td.appendChild(mxLog.textarea);
+ tr.appendChild(td);
+ tbody.appendChild(tr);
+
+ // Creates the container div
+ tr = document.createElement('tr');
+ mxLog.td = document.createElement('td');
+ mxLog.td.style.verticalAlign = 'top';
+ mxLog.td.setAttribute('height', '30px');
+
+ tr.appendChild(mxLog.td);
+ tbody.appendChild(tr);
+ table.appendChild(tbody);
+
+ // Adds various debugging buttons
+ mxLog.addButton('Info', function (evt)
+ {
+ mxLog.info();
+ });
+
+ mxLog.addButton('DOM', function (evt)
+ {
+ var content = mxUtils.getInnerHtml(document.body);
+ mxLog.debug(content);
+ });
+
+ mxLog.addButton('Trace', function (evt)
+ {
+ mxLog.TRACE = !mxLog.TRACE;
+
+ if (mxLog.TRACE)
+ {
+ mxLog.debug('Tracing enabled');
+ }
+ else
+ {
+ mxLog.debug('Tracing disabled');
+ }
+ });
+
+ mxLog.addButton('Copy', function (evt)
+ {
+ try
+ {
+ mxUtils.copy(mxLog.textarea.value);
+ }
+ catch (err)
+ {
+ mxUtils.alert(err);
+ }
+ });
+
+ mxLog.addButton('Show', function (evt)
+ {
+ try
+ {
+ mxUtils.popup(mxLog.textarea.value);
+ }
+ catch (err)
+ {
+ mxUtils.alert(err);
+ }
+ });
+
+ mxLog.addButton('Clear', function (evt)
+ {
+ mxLog.textarea.value = '';
+ });
+
+ // Cross-browser code to get window size
+ var h = 0;
+ var w = 0;
+
+ if (typeof(window.innerWidth) === 'number')
+ {
+ h = window.innerHeight;
+ w = window.innerWidth;
+ }
+ else
+ {
+ h = (document.documentElement.clientHeight || document.body.clientHeight);
+ w = document.body.clientWidth;
+ }
+
+ mxLog.window = new mxWindow(title, table, Math.max(0, w-320), Math.max(0, h-210), 300, 160);
+ mxLog.window.setMaximizable(true);
+ mxLog.window.setScrollable(false);
+ mxLog.window.setResizable(true);
+ mxLog.window.setClosable(true);
+ mxLog.window.destroyOnClose = false;
+
+ // Workaround for ignored textarea height in various setups
+ if ((mxClient.IS_NS || mxClient.IS_IE) && !mxClient.IS_GC &&
+ !mxClient.IS_SF && document.compatMode != 'BackCompat')
+ {
+ var elt = mxLog.window.getElement();
+
+ var resizeHandler = function(sender, evt)
+ {
+ mxLog.textarea.style.height = Math.max(0, elt.offsetHeight - 70)+'px';
+ };
+
+ mxLog.window.addListener(mxEvent.RESIZE_END, resizeHandler);
+ mxLog.window.addListener(mxEvent.MAXIMIZE, resizeHandler);
+ mxLog.window.addListener(mxEvent.NORMALIZE, resizeHandler);
+
+ mxLog.textarea.style.height = '92px';
+ }
+ }
+ },
+
+ /**
+ * Function: info
+ *
+ * Writes the current navigator information to the console.
+ */
+ info: function()
+ {
+ mxLog.writeln(mxUtils.toString(navigator));
+ },
+
+ /**
+ * Function: addButton
+ *
+ * Adds a button to the console using the given label and function.
+ */
+ addButton: function(lab, funct)
+ {
+ var button = document.createElement('button');
+ mxUtils.write(button, lab);
+ mxEvent.addListener(button, 'click', funct);
+ mxLog.td.appendChild(button);
+ },
+
+ /**
+ * Function: isVisible
+ *
+ * Returns true if the console is visible.
+ */
+ isVisible: function()
+ {
+ if (mxLog.window != null)
+ {
+ return mxLog.window.isVisible();
+ }
+ return false;
+ },
+
+
+ /**
+ * Function: show
+ *
+ * Shows the console.
+ */
+ show: function()
+ {
+ mxLog.setVisible(true);
+ },
+
+ /**
+ * Function: setVisible
+ *
+ * Shows or hides the console.
+ */
+ setVisible: function(visible)
+ {
+ if (mxLog.window == null)
+ {
+ mxLog.init();
+ }
+
+ if (mxLog.window != null)
+ {
+ mxLog.window.setVisible(visible);
+ }
+ },
+
+ /**
+ * Function: enter
+ *
+ * Writes the specified string to the console
+ * if <TRACE> is true and returns the current
+ * time in milliseconds.
+ *
+ * Example:
+ *
+ * (code)
+ * mxLog.show();
+ * var t0 = mxLog.enter('Hello');
+ * // Do something
+ * mxLog.leave('World!', t0);
+ * (end)
+ */
+ enter: function(string)
+ {
+ if (mxLog.TRACE)
+ {
+ mxLog.writeln('Entering '+string);
+
+ return new Date().getTime();
+ }
+ },
+
+ /**
+ * Function: leave
+ *
+ * Writes the specified string to the console
+ * if <TRACE> is true and computes the difference
+ * between the current time and t0 in milliseconds.
+ * See <enter> for an example.
+ */
+ leave: function(string, t0)
+ {
+ if (mxLog.TRACE)
+ {
+ var dt = (t0 != 0) ? ' ('+(new Date().getTime() - t0)+' ms)' : '';
+ mxLog.writeln('Leaving '+string+dt);
+ }
+ },
+
+ /**
+ * Function: debug
+ *
+ * Adds all arguments to the console if <DEBUG> is enabled.
+ *
+ * Example:
+ *
+ * (code)
+ * mxLog.show();
+ * mxLog.debug('Hello, World!');
+ * (end)
+ */
+ debug: function()
+ {
+ if (mxLog.DEBUG)
+ {
+ mxLog.writeln.apply(this, arguments);
+ }
+ },
+
+ /**
+ * Function: warn
+ *
+ * Adds all arguments to the console if <WARN> is enabled.
+ *
+ * Example:
+ *
+ * (code)
+ * mxLog.show();
+ * mxLog.warn('Hello, World!');
+ * (end)
+ */
+ warn: function()
+ {
+ if (mxLog.WARN)
+ {
+ mxLog.writeln.apply(this, arguments);
+ }
+ },
+
+ /**
+ * Function: write
+ *
+ * Adds the specified strings to the console.
+ */
+ write: function()
+ {
+ var string = '';
+
+ for (var i = 0; i < arguments.length; i++)
+ {
+ string += arguments[i];
+
+ if (i < arguments.length - 1)
+ {
+ string += ' ';
+ }
+ }
+
+ if (mxLog.textarea != null)
+ {
+ mxLog.textarea.value = mxLog.textarea.value + string;
+
+ // Workaround for no update in Presto 2.5.22 (Opera 10.5)
+ if (navigator.userAgent.indexOf('Presto/2.5') >= 0)
+ {
+ mxLog.textarea.style.visibility = 'hidden';
+ mxLog.textarea.style.visibility = 'visible';
+ }
+
+ mxLog.textarea.scrollTop = mxLog.textarea.scrollHeight;
+ }
+ else
+ {
+ mxLog.buffer += string;
+ }
+ },
+
+ /**
+ * Function: writeln
+ *
+ * Adds the specified strings to the console, appending a linefeed at the
+ * end of each string.
+ */
+ writeln: function()
+ {
+ var string = '';
+
+ for (var i = 0; i < arguments.length; i++)
+ {
+ string += arguments[i];
+
+ if (i < arguments.length - 1)
+ {
+ string += ' ';
+ }
+ }
+
+ mxLog.write(string + '\n');
+ }
+
+};
diff --git a/src/js/util/mxMorphing.js b/src/js/util/mxMorphing.js
new file mode 100644
index 0000000..442143d
--- /dev/null
+++ b/src/js/util/mxMorphing.js
@@ -0,0 +1,239 @@
+/**
+ * $Id: mxMorphing.js,v 1.4 2010-06-03 13:37:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ *
+ * Class: mxMorphing
+ *
+ * Implements animation for morphing cells. Here is an example of
+ * using this class for animating the result of a layout algorithm:
+ *
+ * (code)
+ * graph.getModel().beginUpdate();
+ * try
+ * {
+ * var circleLayout = new mxCircleLayout(graph);
+ * circleLayout.execute(graph.getDefaultParent());
+ * }
+ * finally
+ * {
+ * var morph = new mxMorphing(graph);
+ * morph.addListener(mxEvent.DONE, function()
+ * {
+ * graph.getModel().endUpdate();
+ * });
+ *
+ * morph.startAnimation();
+ * }
+ * (end)
+ *
+ * Constructor: mxMorphing
+ *
+ * Constructs an animation.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ * steps - Optional number of steps in the morphing animation. Default is 6.
+ * ease - Optional easing constant for the animation. Default is 1.5.
+ * delay - Optional delay between the animation steps. Passed to <mxAnimation>.
+ */
+function mxMorphing(graph, steps, ease, delay)
+{
+ mxAnimation.call(this, delay);
+ this.graph = graph;
+ this.steps = (steps != null) ? steps : 6;
+ this.ease = (ease != null) ? ease : 1.5;
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxMorphing.prototype = new mxAnimation();
+mxMorphing.prototype.constructor = mxMorphing;
+
+/**
+ * Variable: graph
+ *
+ * Specifies the delay between the animation steps. Defaul is 30ms.
+ */
+mxMorphing.prototype.graph = null;
+
+/**
+ * Variable: steps
+ *
+ * Specifies the maximum number of steps for the morphing.
+ */
+mxMorphing.prototype.steps = null;
+
+/**
+ * Variable: step
+ *
+ * Contains the current step.
+ */
+mxMorphing.prototype.step = 0;
+
+/**
+ * Variable: ease
+ *
+ * Ease-off for movement towards the given vector. Larger values are
+ * slower and smoother. Default is 4.
+ */
+mxMorphing.prototype.ease = null;
+
+/**
+ * Variable: cells
+ *
+ * Optional array of cells to be animated. If this is not specified
+ * then all cells are checked and animated if they have been moved
+ * in the current transaction.
+ */
+mxMorphing.prototype.cells = null;
+
+/**
+ * Function: updateAnimation
+ *
+ * Animation step.
+ */
+mxMorphing.prototype.updateAnimation = function()
+{
+ var move = new mxCellStatePreview(this.graph);
+
+ if (this.cells != null)
+ {
+ // Animates the given cells individually without recursion
+ for (var i = 0; i < this.cells.length; i++)
+ {
+ this.animateCell(cells[i], move, false);
+ }
+ }
+ else
+ {
+ // Animates all changed cells by using recursion to find
+ // the changed cells but not for the animation itself
+ this.animateCell(this.graph.getModel().getRoot(), move, true);
+ }
+
+ this.show(move);
+
+ if (move.isEmpty() ||
+ this.step++ >= this.steps)
+ {
+ this.stopAnimation();
+ }
+};
+
+/**
+ * Function: show
+ *
+ * Shows the changes in the given <mxCellStatePreview>.
+ */
+mxMorphing.prototype.show = function(move)
+{
+ move.show();
+};
+
+/**
+ * Function: animateCell
+ *
+ * Animates the given cell state using <mxCellStatePreview.moveState>.
+ */
+mxMorphing.prototype.animateCell = function(cell, move, recurse)
+{
+ var state = this.graph.getView().getState(cell);
+ var delta = null;
+
+ if (state != null)
+ {
+ // Moves the animated state from where it will be after the model
+ // change by subtracting the given delta vector from that location
+ delta = this.getDelta(state);
+
+ if (this.graph.getModel().isVertex(cell) &&
+ (delta.x != 0 || delta.y != 0))
+ {
+ var translate = this.graph.view.getTranslate();
+ var scale = this.graph.view.getScale();
+
+ delta.x += translate.x * scale;
+ delta.y += translate.y * scale;
+
+ move.moveState(state, -delta.x / this.ease, -delta.y / this.ease);
+ }
+ }
+
+ if (recurse && !this.stopRecursion(state, delta))
+ {
+ var childCount = this.graph.getModel().getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.animateCell(this.graph.getModel().getChildAt(cell, i), move, recurse);
+ }
+ }
+};
+
+/**
+ * Function: stopRecursion
+ *
+ * Returns true if the animation should not recursively find more
+ * deltas for children if the given parent state has been animated.
+ */
+mxMorphing.prototype.stopRecursion = function(state, delta)
+{
+ return delta != null && (delta.x != 0 || delta.y != 0);
+};
+
+/**
+ * Function: getDelta
+ *
+ * Returns the vector between the current rendered state and the future
+ * location of the state after the display will be updated.
+ */
+mxMorphing.prototype.getDelta = function(state)
+{
+ var origin = this.getOriginForCell(state.cell);
+ var translate = this.graph.getView().getTranslate();
+ var scale = this.graph.getView().getScale();
+ var current = new mxPoint(
+ state.x / scale - translate.x,
+ state.y / scale - translate.y);
+
+ return new mxPoint(
+ (origin.x - current.x) * scale,
+ (origin.y - current.y) * scale);
+};
+
+/**
+ * Function: getOriginForCell
+ *
+ * Returns the top, left corner of the given cell. TODO: Improve performance
+ * by using caching inside this method as the result per cell never changes
+ * during the lifecycle of this object.
+ */
+mxMorphing.prototype.getOriginForCell = function(cell)
+{
+ var result = null;
+
+ if (cell != null)
+ {
+ result = this.getOriginForCell(this.graph.getModel().getParent(cell));
+ var geo = this.graph.getCellGeometry(cell);
+
+ // TODO: Handle offset, relative geometries etc
+ if (geo != null)
+ {
+ result.x += geo.x;
+ result.y += geo.y;
+ }
+ }
+
+ if (result == null)
+ {
+ var t = this.graph.view.getTranslate();
+ result = new mxPoint(-t.x, -t.y);
+ }
+
+ return result;
+};
diff --git a/src/js/util/mxMouseEvent.js b/src/js/util/mxMouseEvent.js
new file mode 100644
index 0000000..e161d3a
--- /dev/null
+++ b/src/js/util/mxMouseEvent.js
@@ -0,0 +1,241 @@
+/**
+ * $Id: mxMouseEvent.js,v 1.20 2011-03-02 17:24:39 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxMouseEvent
+ *
+ * Base class for all mouse events in mxGraph. A listener for this event should
+ * implement the following methods:
+ *
+ * (code)
+ * graph.addMouseListener(
+ * {
+ * mouseDown: function(sender, evt)
+ * {
+ * mxLog.debug('mouseDown');
+ * },
+ * mouseMove: function(sender, evt)
+ * {
+ * mxLog.debug('mouseMove');
+ * },
+ * mouseUp: function(sender, evt)
+ * {
+ * mxLog.debug('mouseUp');
+ * }
+ * });
+ * (end)
+ *
+ * Constructor: mxMouseEvent
+ *
+ * Constructs a new event object for the given arguments.
+ *
+ * Parameters:
+ *
+ * evt - Native mouse event.
+ * state - Optional <mxCellState> under the mouse.
+ *
+ */
+function mxMouseEvent(evt, state)
+{
+ this.evt = evt;
+ this.state = state;
+};
+
+/**
+ * Variable: consumed
+ *
+ * Holds the consumed state of this event.
+ */
+mxMouseEvent.prototype.consumed = false;
+
+/**
+ * Variable: evt
+ *
+ * Holds the inner event object.
+ */
+mxMouseEvent.prototype.evt = null;
+
+/**
+ * Variable: graphX
+ *
+ * Holds the x-coordinate of the event in the graph. This value is set in
+ * <mxGraph.fireMouseEvent>.
+ */
+mxMouseEvent.prototype.graphX = null;
+
+/**
+ * Variable: graphY
+ *
+ * Holds the y-coordinate of the event in the graph. This value is set in
+ * <mxGraph.fireMouseEvent>.
+ */
+mxMouseEvent.prototype.graphY = null;
+
+/**
+ * Variable: state
+ *
+ * Holds the optional <mxCellState> associated with this event.
+ */
+mxMouseEvent.prototype.state = null;
+
+/**
+ * Function: getEvent
+ *
+ * Returns <evt>.
+ */
+mxMouseEvent.prototype.getEvent = function()
+{
+ return this.evt;
+};
+
+/**
+ * Function: getSource
+ *
+ * Returns the target DOM element using <mxEvent.getSource> for <evt>.
+ */
+mxMouseEvent.prototype.getSource = function()
+{
+ return mxEvent.getSource(this.evt);
+};
+
+/**
+ * Function: isSource
+ *
+ * Returns true if the given <mxShape> is the source of <evt>.
+ */
+mxMouseEvent.prototype.isSource = function(shape)
+{
+ if (shape != null)
+ {
+ var source = this.getSource();
+
+ while (source != null)
+ {
+ if (source == shape.node)
+ {
+ return true;
+ }
+
+ source = source.parentNode;
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Function: getX
+ *
+ * Returns <evt.clientX>.
+ */
+mxMouseEvent.prototype.getX = function()
+{
+ return mxEvent.getClientX(this.getEvent());
+};
+
+/**
+ * Function: getY
+ *
+ * Returns <evt.clientY>.
+ */
+mxMouseEvent.prototype.getY = function()
+{
+ return mxEvent.getClientY(this.getEvent());
+};
+
+/**
+ * Function: getGraphX
+ *
+ * Returns <graphX>.
+ */
+mxMouseEvent.prototype.getGraphX = function()
+{
+ return this.graphX;
+};
+
+/**
+ * Function: getGraphY
+ *
+ * Returns <graphY>.
+ */
+mxMouseEvent.prototype.getGraphY = function()
+{
+ return this.graphY;
+};
+
+/**
+ * Function: getState
+ *
+ * Returns <state>.
+ */
+mxMouseEvent.prototype.getState = function()
+{
+ return this.state;
+};
+
+/**
+ * Function: getCell
+ *
+ * Returns the <mxCell> in <state> is not null.
+ */
+mxMouseEvent.prototype.getCell = function()
+{
+ var state = this.getState();
+
+ if (state != null)
+ {
+ return state.cell;
+ }
+
+ return null;
+};
+
+/**
+ * Function: isPopupTrigger
+ *
+ * Returns true if the event is a popup trigger.
+ */
+mxMouseEvent.prototype.isPopupTrigger = function()
+{
+ return mxEvent.isPopupTrigger(this.getEvent());
+};
+
+/**
+ * Function: isConsumed
+ *
+ * Returns <consumed>.
+ */
+mxMouseEvent.prototype.isConsumed = function()
+{
+ return this.consumed;
+};
+
+/**
+ * Function: consume
+ *
+ * Sets <consumed> to true and invokes preventDefault on the native event
+ * if such a method is defined. This is used mainly to avoid the cursor from
+ * being changed to a text cursor in Webkit. You can use the preventDefault
+ * flag to disable this functionality.
+ *
+ * Parameters:
+ *
+ * preventDefault - Specifies if the native event should be canceled. Default
+ * is true.
+ */
+mxMouseEvent.prototype.consume = function(preventDefault)
+{
+ preventDefault = (preventDefault != null) ? preventDefault : true;
+
+ if (preventDefault && this.evt.preventDefault)
+ {
+ this.evt.preventDefault();
+ }
+
+ // Workaround for images being dragged in IE
+ this.evt.returnValue = false;
+
+ // Sets local consumed state
+ this.consumed = true;
+};
diff --git a/src/js/util/mxObjectIdentity.js b/src/js/util/mxObjectIdentity.js
new file mode 100644
index 0000000..778a4ea
--- /dev/null
+++ b/src/js/util/mxObjectIdentity.js
@@ -0,0 +1,59 @@
+/**
+ * $Id: mxObjectIdentity.js,v 1.8 2010-01-02 09:45:14 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxObjectIdentity =
+{
+ /**
+ * Class: mxObjectIdentity
+ *
+ * Identity for JavaScript objects. This is implemented using a simple
+ * incremeting counter which is stored in each object under <ID_NAME>.
+ *
+ * The identity for an object does not change during its lifecycle.
+ *
+ * Variable: FIELD_NAME
+ *
+ * Name of the field to be used to store the object ID. Default is
+ * '_mxObjectId'.
+ */
+ FIELD_NAME: 'mxObjectId',
+
+ /**
+ * Variable: counter
+ *
+ * Current counter for objects.
+ */
+ counter: 0,
+
+ /**
+ * Function: get
+ *
+ * Returns the object id for the given object.
+ */
+ get: function(obj)
+ {
+ if (typeof(obj) == 'object' &&
+ obj[mxObjectIdentity.FIELD_NAME] == null)
+ {
+ var ctor = mxUtils.getFunctionName(obj.constructor);
+ obj[mxObjectIdentity.FIELD_NAME] = ctor+'#'+mxObjectIdentity.counter++;
+ }
+
+ return obj[mxObjectIdentity.FIELD_NAME];
+ },
+
+ /**
+ * Function: clear
+ *
+ * Removes the object id from the given object.
+ */
+ clear: function(obj)
+ {
+ if (typeof(obj) == 'object')
+ {
+ delete obj[mxObjectIdentity.FIELD_NAME];
+ }
+ }
+
+};
diff --git a/src/js/util/mxPanningManager.js b/src/js/util/mxPanningManager.js
new file mode 100644
index 0000000..9f9f349
--- /dev/null
+++ b/src/js/util/mxPanningManager.js
@@ -0,0 +1,262 @@
+/**
+ * $Id: mxPanningManager.js,v 1.7 2012-06-13 06:46:37 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxPanningManager
+ *
+ * Implements a handler for panning.
+ */
+function mxPanningManager(graph)
+{
+ this.thread = null;
+ this.active = false;
+ this.tdx = 0;
+ this.tdy = 0;
+ this.t0x = 0;
+ this.t0y = 0;
+ this.dx = 0;
+ this.dy = 0;
+ this.scrollbars = false;
+ this.scrollLeft = 0;
+ this.scrollTop = 0;
+
+ this.mouseListener =
+ {
+ mouseDown: function(sender, me) { },
+ mouseMove: function(sender, me) { },
+ mouseUp: mxUtils.bind(this, function(sender, me)
+ {
+ if (this.active)
+ {
+ this.stop();
+ }
+ })
+ };
+
+ graph.addMouseListener(this.mouseListener);
+
+ // Stops scrolling on every mouseup anywhere in the document
+ mxEvent.addListener(document, 'mouseup', mxUtils.bind(this, function()
+ {
+ if (this.active)
+ {
+ this.stop();
+ }
+ }));
+
+ var createThread = mxUtils.bind(this, function()
+ {
+ this.scrollbars = mxUtils.hasScrollbars(graph.container);
+ this.scrollLeft = graph.container.scrollLeft;
+ this.scrollTop = graph.container.scrollTop;
+
+ return window.setInterval(mxUtils.bind(this, function()
+ {
+ this.tdx -= this.dx;
+ this.tdy -= this.dy;
+
+ if (this.scrollbars)
+ {
+ var left = -graph.container.scrollLeft - Math.ceil(this.dx);
+ var top = -graph.container.scrollTop - Math.ceil(this.dy);
+ graph.panGraph(left, top);
+ graph.panDx = this.scrollLeft - graph.container.scrollLeft;
+ graph.panDy = this.scrollTop - graph.container.scrollTop;
+ graph.fireEvent(new mxEventObject(mxEvent.PAN));
+ // TODO: Implement graph.autoExtend
+ }
+ else
+ {
+ graph.panGraph(this.getDx(), this.getDy());
+ }
+ }), this.delay);
+ });
+
+ this.isActive = function()
+ {
+ return active;
+ };
+
+ this.getDx = function()
+ {
+ return Math.round(this.tdx);
+ };
+
+ this.getDy = function()
+ {
+ return Math.round(this.tdy);
+ };
+
+ this.start = function()
+ {
+ this.t0x = graph.view.translate.x;
+ this.t0y = graph.view.translate.y;
+ this.active = true;
+ };
+
+ this.panTo = function(x, y, w, h)
+ {
+ if (!this.active)
+ {
+ this.start();
+ }
+
+ this.scrollLeft = graph.container.scrollLeft;
+ this.scrollTop = graph.container.scrollTop;
+
+ w = (w != null) ? w : 0;
+ h = (h != null) ? h : 0;
+
+ var c = graph.container;
+ this.dx = x + w - c.scrollLeft - c.clientWidth;
+
+ if (this.dx < 0 && Math.abs(this.dx) < this.border)
+ {
+ this.dx = this.border + this.dx;
+ }
+ else if (this.handleMouseOut)
+ {
+ this.dx = Math.max(this.dx, 0);
+ }
+ else
+ {
+ this.dx = 0;
+ }
+
+ if (this.dx == 0)
+ {
+ this.dx = x - c.scrollLeft;
+
+ if (this.dx > 0 && this.dx < this.border)
+ {
+ this.dx = this.dx - this.border;
+ }
+ else if (this.handleMouseOut)
+ {
+ this.dx = Math.min(0, this.dx);
+ }
+ else
+ {
+ this.dx = 0;
+ }
+ }
+
+ this.dy = y + h - c.scrollTop - c.clientHeight;
+
+ if (this.dy < 0 && Math.abs(this.dy) < this.border)
+ {
+ this.dy = this.border + this.dy;
+ }
+ else if (this.handleMouseOut)
+ {
+ this.dy = Math.max(this.dy, 0);
+ }
+ else
+ {
+ this.dy = 0;
+ }
+
+ if (this.dy == 0)
+ {
+ this.dy = y - c.scrollTop;
+
+ if (this.dy > 0 && this.dy < this.border)
+ {
+ this.dy = this.dy - this.border;
+ }
+ else if (this.handleMouseOut)
+ {
+ this.dy = Math.min(0, this.dy);
+ }
+ else
+ {
+ this.dy = 0;
+ }
+ }
+
+ if (this.dx != 0 || this.dy != 0)
+ {
+ this.dx *= this.damper;
+ this.dy *= this.damper;
+
+ if (this.thread == null)
+ {
+ this.thread = createThread();
+ }
+ }
+ else if (this.thread != null)
+ {
+ window.clearInterval(this.thread);
+ this.thread = null;
+ }
+ };
+
+ this.stop = function()
+ {
+ if (this.active)
+ {
+ this.active = false;
+
+ if (this.thread != null)
+ {
+ window.clearInterval(this.thread);
+ this.thread = null;
+ }
+
+ this.tdx = 0;
+ this.tdy = 0;
+
+ if (!this.scrollbars)
+ {
+ var px = graph.panDx;
+ var py = graph.panDy;
+
+ if (px != 0 || py != 0)
+ {
+ graph.panGraph(0, 0);
+ graph.view.setTranslate(this.t0x + px / graph.view.scale, this.t0y + py / graph.view.scale);
+ }
+ }
+ else
+ {
+ graph.panDx = 0;
+ graph.panDy = 0;
+ graph.fireEvent(new mxEventObject(mxEvent.PAN));
+ }
+ }
+ };
+
+ this.destroy = function()
+ {
+ graph.removeMouseListener(this.mouseListener);
+ };
+};
+
+/**
+ * Variable: damper
+ *
+ * Damper value for the panning. Default is 1/6.
+ */
+mxPanningManager.prototype.damper = 1/6;
+
+/**
+ * Variable: delay
+ *
+ * Delay in milliseconds for the panning. Default is 10.
+ */
+mxPanningManager.prototype.delay = 10;
+
+/**
+ * Variable: handleMouseOut
+ *
+ * Specifies if mouse events outside of the component should be handled. Default is true.
+ */
+mxPanningManager.prototype.handleMouseOut = true;
+
+/**
+ * Variable: border
+ *
+ * Border to handle automatic panning inside the component. Default is 0 (disabled).
+ */
+mxPanningManager.prototype.border = 0;
diff --git a/src/js/util/mxPath.js b/src/js/util/mxPath.js
new file mode 100644
index 0000000..57efe74
--- /dev/null
+++ b/src/js/util/mxPath.js
@@ -0,0 +1,314 @@
+/**
+ * $Id: mxPath.js,v 1.24 2012-06-13 17:31:32 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxPath
+ *
+ * An abstraction for creating VML and SVG paths. See <mxActor> for using this
+ * object inside an <mxShape> for painting cells.
+ *
+ * Constructor: mxPath
+ *
+ * Constructs a path for the given format, which is one of svg or vml.
+ *
+ * Parameters:
+ *
+ * format - String specifying the <format>. May be one of vml or svg
+ * (default).
+ */
+function mxPath(format)
+{
+ this.format = format;
+ this.path = [];
+ this.translate = new mxPoint(0, 0);
+};
+
+/**
+ * Variable: format
+ *
+ * Defines the format for the output of this path. Possible values are
+ * svg and vml.
+ */
+mxPath.prototype.format = null;
+
+/**
+ * Variable: translate
+ *
+ * <mxPoint> that specifies the translation of the complete path.
+ */
+mxPath.prototype.translate = null;
+
+/**
+ * Variable: scale
+ *
+ * Number that specifies the translation of the path.
+ */
+mxPath.prototype.scale = 1;
+
+/**
+ * Variable: path
+ *
+ * Contains the textual representation of the path as an array.
+ */
+mxPath.prototype.path = null;
+
+/**
+ * Function: isVml
+ *
+ * Returns true if <format> is vml.
+ */
+mxPath.prototype.isVml = function()
+{
+ return this.format == 'vml';
+};
+
+/**
+ * Function: getPath
+ *
+ * Returns string that represents the path in <format>.
+ */
+mxPath.prototype.getPath = function()
+{
+ return this.path.join('');
+};
+
+/**
+ * Function: setTranslate
+ *
+ * Set the global translation of this path, that is, the origin of the
+ * coordinate system.
+ *
+ * Parameters:
+ *
+ * x - X-coordinate of the new origin.
+ * y - Y-coordinate of the new origin.
+ */
+mxPath.prototype.setTranslate = function(x, y)
+{
+ this.translate = new mxPoint(x, y);
+};
+
+/**
+ * Function: moveTo
+ *
+ * Moves the cursor to (x, y).
+ *
+ * Parameters:
+ *
+ * x - X-coordinate of the new cursor location.
+ * y - Y-coordinate of the new cursor location.
+ */
+mxPath.prototype.moveTo = function(x, y)
+{
+ x += this.translate.x;
+ y += this.translate.y;
+
+ x *= this.scale;
+ y *= this.scale;
+
+ if (this.isVml())
+ {
+ this.path.push('m ', Math.round(x), ' ', Math.round(y), ' ');
+ }
+ else
+ {
+ this.path.push('M ', x, ' ', y, ' ');
+ }
+};
+
+/**
+ * Function: lineTo
+ *
+ * Draws a straight line from the current poin to (x, y).
+ *
+ * Parameters:
+ *
+ * x - X-coordinate of the endpoint.
+ * y - Y-coordinate of the endpoint.
+ */
+mxPath.prototype.lineTo = function(x, y)
+{
+ x += this.translate.x;
+ y += this.translate.y;
+
+ x *= this.scale;
+ y *= this.scale;
+
+ if (this.isVml())
+ {
+ this.path.push('l ', Math.round(x), ' ', Math.round(y), ' ');
+ }
+ else
+ {
+ this.path.push('L ', x, ' ', y, ' ');
+ }
+};
+
+/**
+ * Function: quadTo
+ *
+ * Draws a quadratic Bézier curve from the current point to (x, y) using
+ * (x1, y1) as the control point.
+ *
+ * Parameters:
+ *
+ * x1 - X-coordinate of the control point.
+ * y1 - Y-coordinate of the control point.
+ * x - X-coordinate of the endpoint.
+ * y - Y-coordinate of the endpoint.
+ */
+mxPath.prototype.quadTo = function(x1, y1, x, y)
+{
+ x1 += this.translate.x;
+ y1 += this.translate.y;
+
+ x1 *= this.scale;
+ y1 *= this.scale;
+
+ x += this.translate.x;
+ y += this.translate.y;
+
+ x *= this.scale;
+ y *= this.scale;
+
+ if (this.isVml())
+ {
+ this.path.push('c ', Math.round(x1), ' ', Math.round(y1), ' ', Math.round(x), ' ',
+ Math.round(y), ' ', Math.round(x), ' ', Math.round(y), ' ');
+ }
+ else
+ {
+ this.path.push('Q ', x1, ' ', y1, ' ', x, ' ', y, ' ');
+ }
+};
+
+/**
+ * Function: curveTo
+ *
+ * Draws a cubic Bézier curve from the current point to (x, y) using
+ * (x1, y1) as the control point at the beginning of the curve and (x2, y2)
+ * as the control point at the end of the curve.
+ *
+ * Parameters:
+ *
+ * x1 - X-coordinate of the first control point.
+ * y1 - Y-coordinate of the first control point.
+ * x2 - X-coordinate of the second control point.
+ * y2 - Y-coordinate of the second control point.
+ * x - X-coordinate of the endpoint.
+ * y - Y-coordinate of the endpoint.
+ */
+mxPath.prototype.curveTo = function(x1, y1, x2, y2, x, y)
+{
+ x1 += this.translate.x;
+ y1 += this.translate.y;
+
+ x1 *= this.scale;
+ y1 *= this.scale;
+
+ x2 += this.translate.x;
+ y2 += this.translate.y;
+
+ x2 *= this.scale;
+ y2 *= this.scale;
+
+ x += this.translate.x;
+ y += this.translate.y;
+
+ x *= this.scale;
+ y *= this.scale;
+
+ if (this.isVml())
+ {
+ this.path.push('c ', Math.round(x1), ' ', Math.round(y1), ' ', Math.round(x2),
+ ' ', Math.round(y2), ' ', Math.round(x), ' ', Math.round(y), ' ');
+ }
+ else
+ {
+ this.path.push('C ', x1, ' ', y1, ' ', x2,
+ ' ', y2, ' ', x, ' ', y, ' ');
+ }
+};
+
+/**
+ * Function: ellipse
+ *
+ * Adds the given ellipse. Some implementations may require the path to be
+ * closed after this operation.
+ */
+mxPath.prototype.ellipse = function(x, y, w, h)
+{
+ x += this.translate.x;
+ y += this.translate.y;
+ x *= this.scale;
+ y *= this.scale;
+
+ if (this.isVml())
+ {
+ this.path.push('at ', Math.round(x), ' ', Math.round(y), ' ', Math.round(x + w), ' ', Math.round(y + h), ' ',
+ Math.round(x), ' ', Math.round(y + h / 2), ' ', Math.round(x), ' ', Math.round(y + h / 2));
+ }
+ else
+ {
+ var startX = x;
+ var startY = y + h/2;
+ var endX = x + w;
+ var endY = y + h/2;
+ var r1 = w/2;
+ var r2 = h/2;
+ this.path.push('M ', startX, ' ', startY, ' ');
+ this.path.push('A ', r1, ' ', r2, ' 0 1 0 ', endX, ' ', endY, ' ');
+ this.path.push('A ', r1, ' ', r2, ' 0 1 0 ', startX, ' ', startY);
+ }
+};
+
+/**
+ * Function: addPath
+ *
+ * Adds the given path.
+ */
+mxPath.prototype.addPath = function(path)
+{
+ this.path = this.path.concat(path.path);
+};
+
+/**
+ * Function: write
+ *
+ * Writes directly into the path. This bypasses all conversions.
+ */
+mxPath.prototype.write = function(string)
+{
+ this.path.push(string, ' ');
+};
+
+/**
+ * Function: end
+ *
+ * Ends the path.
+ */
+mxPath.prototype.end = function()
+{
+ if (this.format == 'vml')
+ {
+ this.path.push('e');
+ }
+};
+
+/**
+ * Function: close
+ *
+ * Closes the path.
+ */
+mxPath.prototype.close = function()
+{
+ if (this.format == 'vml')
+ {
+ this.path.push('x e');
+ }
+ else
+ {
+ this.path.push('Z');
+ }
+};
diff --git a/src/js/util/mxPoint.js b/src/js/util/mxPoint.js
new file mode 100644
index 0000000..e029a29
--- /dev/null
+++ b/src/js/util/mxPoint.js
@@ -0,0 +1,55 @@
+/**
+ * $Id: mxPoint.js,v 1.12 2010-01-02 09:45:14 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxPoint
+ *
+ * Implements a 2-dimensional vector with double precision coordinates.
+ *
+ * Constructor: mxPoint
+ *
+ * Constructs a new point for the optional x and y coordinates. If no
+ * coordinates are given, then the default values for <x> and <y> are used.
+ */
+function mxPoint(x, y)
+{
+ this.x = (x != null) ? x : 0;
+ this.y = (y != null) ? y : 0;
+};
+
+/**
+ * Variable: x
+ *
+ * Holds the x-coordinate of the point. Default is 0.
+ */
+mxPoint.prototype.x = null;
+
+/**
+ * Variable: y
+ *
+ * Holds the y-coordinate of the point. Default is 0.
+ */
+mxPoint.prototype.y = null;
+
+/**
+ * Function: equals
+ *
+ * Returns true if the given object equals this rectangle.
+ */
+mxPoint.prototype.equals = function(obj)
+{
+ return obj.x == this.x &&
+ obj.y == this.y;
+};
+
+/**
+ * Function: clone
+ *
+ * Returns a clone of this <mxPoint>.
+ */
+mxPoint.prototype.clone = function()
+{
+ // Handles subclasses as well
+ return mxUtils.clone(this);
+};
diff --git a/src/js/util/mxPopupMenu.js b/src/js/util/mxPopupMenu.js
new file mode 100644
index 0000000..b188cb6
--- /dev/null
+++ b/src/js/util/mxPopupMenu.js
@@ -0,0 +1,574 @@
+/**
+ * $Id: mxPopupMenu.js,v 1.37 2012-04-22 10:16:23 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxPopupMenu
+ *
+ * Event handler that pans and creates popupmenus. To use the left
+ * mousebutton for panning without interfering with cell moving and
+ * resizing, use <isUseLeftButton> and <isIgnoreCell>. For grid size
+ * steps while panning, use <useGrid>. This handler is built-into
+ * <mxGraph.panningHandler> and enabled using <mxGraph.setPanning>.
+ *
+ * Constructor: mxPopupMenu
+ *
+ * Constructs an event handler that creates a popupmenu. The
+ * event handler is not installed anywhere in this ctor.
+ *
+ * Event: mxEvent.SHOW
+ *
+ * Fires after the menu has been shown in <popup>.
+ */
+function mxPopupMenu(factoryMethod)
+{
+ this.factoryMethod = factoryMethod;
+
+ if (factoryMethod != null)
+ {
+ this.init();
+ }
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxPopupMenu.prototype = new mxEventSource();
+mxPopupMenu.prototype.constructor = mxPopupMenu;
+
+/**
+ * Variable: submenuImage
+ *
+ * URL of the image to be used for the submenu icon.
+ */
+mxPopupMenu.prototype.submenuImage = mxClient.imageBasePath + '/submenu.gif';
+
+/**
+ * Variable: zIndex
+ *
+ * Specifies the zIndex for the popupmenu and its shadow. Default is 1006.
+ */
+mxPopupMenu.prototype.zIndex = 10006;
+
+/**
+ * Variable: factoryMethod
+ *
+ * Function that is used to create the popup menu. The function takes the
+ * current panning handler, the <mxCell> under the mouse and the mouse
+ * event that triggered the call as arguments.
+ */
+mxPopupMenu.prototype.factoryMethod = null;
+
+/**
+ * Variable: useLeftButtonForPopup
+ *
+ * Specifies if popupmenus should be activated by clicking the left mouse
+ * button. Default is false.
+ */
+mxPopupMenu.prototype.useLeftButtonForPopup = false;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxPopupMenu.prototype.enabled = true;
+
+/**
+ * Variable: itemCount
+ *
+ * Contains the number of times <addItem> has been called for a new menu.
+ */
+mxPopupMenu.prototype.itemCount = 0;
+
+/**
+ * Variable: autoExpand
+ *
+ * Specifies if submenus should be expanded on mouseover. Default is false.
+ */
+mxPopupMenu.prototype.autoExpand = false;
+
+/**
+ * Variable: smartSeparators
+ *
+ * Specifies if separators should only be added if a menu item follows them.
+ * Default is false.
+ */
+mxPopupMenu.prototype.smartSeparators = false;
+
+/**
+ * Variable: labels
+ *
+ * Specifies if any labels should be visible. Default is true.
+ */
+mxPopupMenu.prototype.labels = true;
+
+/**
+ * Function: init
+ *
+ * Initializes the shapes required for this vertex handler.
+ */
+mxPopupMenu.prototype.init = function()
+{
+ // Adds the inner table
+ this.table = document.createElement('table');
+ this.table.className = 'mxPopupMenu';
+
+ this.tbody = document.createElement('tbody');
+ this.table.appendChild(this.tbody);
+
+ // Adds the outer div
+ this.div = document.createElement('div');
+ this.div.className = 'mxPopupMenu';
+ this.div.style.display = 'inline';
+ this.div.style.zIndex = this.zIndex;
+ this.div.appendChild(this.table);
+
+ // Disables the context menu on the outer div
+ mxEvent.disableContextMenu(this.div);
+};
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxPopupMenu.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ */
+mxPopupMenu.prototype.setEnabled = function(enabled)
+{
+ this.enabled = enabled;
+};
+
+/**
+ * Function: isPopupTrigger
+ *
+ * Returns true if the given event is a popupmenu trigger for the optional
+ * given cell.
+ *
+ * Parameters:
+ *
+ * me - <mxMouseEvent> that represents the mouse event.
+ */
+mxPopupMenu.prototype.isPopupTrigger = function(me)
+{
+ return me.isPopupTrigger() || (this.useLeftButtonForPopup &&
+ mxEvent.isLeftMouseButton(me.getEvent()));
+};
+
+/**
+ * Function: addItem
+ *
+ * Adds the given item to the given parent item. If no parent item is specified
+ * then the item is added to the top-level menu. The return value may be used
+ * as the parent argument, ie. as a submenu item. The return value is the table
+ * row that represents the item.
+ *
+ * Paramters:
+ *
+ * title - String that represents the title of the menu item.
+ * image - Optional URL for the image icon.
+ * funct - Function associated that takes a mouseup or touchend event.
+ * parent - Optional item returned by <addItem>.
+ * iconCls - Optional string that represents the CSS class for the image icon.
+ * IconsCls is ignored if image is given.
+ * enabled - Optional boolean indicating if the item is enabled. Default is true.
+ */
+mxPopupMenu.prototype.addItem = function(title, image, funct, parent, iconCls, enabled)
+{
+ parent = parent || this;
+ this.itemCount++;
+
+ // Smart separators only added if element contains items
+ if (parent.willAddSeparator)
+ {
+ if (parent.containsItems)
+ {
+ this.addSeparator(parent, true);
+ }
+
+ parent.willAddSeparator = false;
+ }
+
+ parent.containsItems = true;
+ var tr = document.createElement('tr');
+ tr.className = 'mxPopupMenuItem';
+ var col1 = document.createElement('td');
+ col1.className = 'mxPopupMenuIcon';
+
+ // Adds the given image into the first column
+ if (image != null)
+ {
+ var img = document.createElement('img');
+ img.src = image;
+ col1.appendChild(img);
+ }
+ else if (iconCls != null)
+ {
+ var div = document.createElement('div');
+ div.className = iconCls;
+ col1.appendChild(div);
+ }
+
+ tr.appendChild(col1);
+
+ if (this.labels)
+ {
+ var col2 = document.createElement('td');
+ col2.className = 'mxPopupMenuItem' +
+ ((enabled != null && !enabled) ? ' disabled' : '');
+ mxUtils.write(col2, title);
+ col2.align = 'left';
+ tr.appendChild(col2);
+
+ var col3 = document.createElement('td');
+ col3.className = 'mxPopupMenuItem' +
+ ((enabled != null && !enabled) ? ' disabled' : '');
+ col3.style.paddingRight = '6px';
+ col3.style.textAlign = 'right';
+
+ tr.appendChild(col3);
+
+ if (parent.div == null)
+ {
+ this.createSubmenu(parent);
+ }
+ }
+
+ parent.tbody.appendChild(tr);
+
+ if (enabled == null || enabled)
+ {
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ // Consumes the event on mouse down
+ mxEvent.addListener(tr, md, mxUtils.bind(this, function(evt)
+ {
+ this.eventReceiver = tr;
+
+ if (parent.activeRow != tr && parent.activeRow != parent)
+ {
+ if (parent.activeRow != null &&
+ parent.activeRow.div.parentNode != null)
+ {
+ this.hideSubmenu(parent);
+ }
+
+ if (tr.div != null)
+ {
+ this.showSubmenu(parent, tr);
+ parent.activeRow = tr;
+ }
+ }
+
+ mxEvent.consume(evt);
+ }));
+
+ mxEvent.addListener(tr, mm, mxUtils.bind(this, function(evt)
+ {
+ if (parent.activeRow != tr && parent.activeRow != parent)
+ {
+ if (parent.activeRow != null &&
+ parent.activeRow.div.parentNode != null)
+ {
+ this.hideSubmenu(parent);
+ }
+
+ if (this.autoExpand && tr.div != null)
+ {
+ this.showSubmenu(parent, tr);
+ parent.activeRow = tr;
+ }
+ }
+
+ // Sets hover style because TR in IE doesn't have hover
+ tr.className = 'mxPopupMenuItemHover';
+ }));
+
+ mxEvent.addListener(tr, mu, mxUtils.bind(this, function(evt)
+ {
+ // EventReceiver avoids clicks on a submenu item
+ // which has just been shown in the mousedown
+ if (this.eventReceiver == tr)
+ {
+ if (parent.activeRow != tr)
+ {
+ this.hideMenu();
+ }
+
+ if (funct != null)
+ {
+ funct(evt);
+ }
+ }
+
+ this.eventReceiver = null;
+ mxEvent.consume(evt);
+ }));
+
+ // Resets hover style because TR in IE doesn't have hover
+ mxEvent.addListener(tr, 'mouseout',
+ mxUtils.bind(this, function(evt)
+ {
+ tr.className = 'mxPopupMenuItem';
+ })
+ );
+ }
+
+ return tr;
+};
+
+/**
+ * Function: createSubmenu
+ *
+ * Creates the nodes required to add submenu items inside the given parent
+ * item. This is called in <addItem> if a parent item is used for the first
+ * time. This adds various DOM nodes and a <submenuImage> to the parent.
+ *
+ * Parameters:
+ *
+ * parent - An item returned by <addItem>.
+ */
+mxPopupMenu.prototype.createSubmenu = function(parent)
+{
+ parent.table = document.createElement('table');
+ parent.table.className = 'mxPopupMenu';
+
+ parent.tbody = document.createElement('tbody');
+ parent.table.appendChild(parent.tbody);
+
+ parent.div = document.createElement('div');
+ parent.div.className = 'mxPopupMenu';
+
+ parent.div.style.position = 'absolute';
+ parent.div.style.display = 'inline';
+ parent.div.style.zIndex = this.zIndex;
+
+ parent.div.appendChild(parent.table);
+
+ var img = document.createElement('img');
+ img.setAttribute('src', this.submenuImage);
+
+ // Last column of the submenu item in the parent menu
+ td = parent.firstChild.nextSibling.nextSibling;
+ td.appendChild(img);
+};
+
+/**
+ * Function: showSubmenu
+ *
+ * Shows the submenu inside the given parent row.
+ */
+mxPopupMenu.prototype.showSubmenu = function(parent, row)
+{
+ if (row.div != null)
+ {
+ row.div.style.left = (parent.div.offsetLeft +
+ row.offsetLeft+row.offsetWidth - 1) + 'px';
+ row.div.style.top = (parent.div.offsetTop+row.offsetTop) + 'px';
+ document.body.appendChild(row.div);
+
+ // Moves the submenu to the left side if there is no space
+ var left = parseInt(row.div.offsetLeft);
+ var width = parseInt(row.div.offsetWidth);
+
+ var b = document.body;
+ var d = document.documentElement;
+
+ var right = (b.scrollLeft || d.scrollLeft) + (b.clientWidth || d.clientWidth);
+
+ if (left + width > right)
+ {
+ row.div.style.left = (parent.div.offsetLeft - width +
+ ((mxClient.IS_IE) ? 6 : -6)) + 'px';
+ }
+
+ mxUtils.fit(row.div);
+ }
+};
+
+/**
+ * Function: addSeparator
+ *
+ * Adds a horizontal separator in the given parent item or the top-level menu
+ * if no parent is specified.
+ *
+ * Parameters:
+ *
+ * parent - Optional item returned by <addItem>.
+ * force - Optional boolean to ignore <smartSeparators>. Default is false.
+ */
+mxPopupMenu.prototype.addSeparator = function(parent, force)
+{
+ parent = parent || this;
+
+ if (this.smartSeparators && !force)
+ {
+ parent.willAddSeparator = true;
+ }
+ else if (parent.tbody != null)
+ {
+ parent.willAddSeparator = false;
+ var tr = document.createElement('tr');
+
+ var col1 = document.createElement('td');
+ col1.className = 'mxPopupMenuIcon';
+ col1.style.padding = '0 0 0 0px';
+
+ tr.appendChild(col1);
+
+ var col2 = document.createElement('td');
+ col2.style.padding = '0 0 0 0px';
+ col2.setAttribute('colSpan', '2');
+
+ var hr = document.createElement('hr');
+ hr.setAttribute('size', '1');
+ col2.appendChild(hr);
+
+ tr.appendChild(col2);
+
+ parent.tbody.appendChild(tr);
+ }
+};
+
+/**
+ * Function: popup
+ *
+ * Shows the popup menu for the given event and cell.
+ *
+ * Example:
+ *
+ * (code)
+ * graph.panningHandler.popup = function(x, y, cell, evt)
+ * {
+ * mxUtils.alert('Hello, World!');
+ * }
+ * (end)
+ */
+mxPopupMenu.prototype.popup = function(x, y, cell, evt)
+{
+ if (this.div != null && this.tbody != null && this.factoryMethod != null)
+ {
+ this.div.style.left = x + 'px';
+ this.div.style.top = y + 'px';
+
+ // Removes all child nodes from the existing menu
+ while (this.tbody.firstChild != null)
+ {
+ mxEvent.release(this.tbody.firstChild);
+ this.tbody.removeChild(this.tbody.firstChild);
+ }
+
+ this.itemCount = 0;
+ this.factoryMethod(this, cell, evt);
+
+ if (this.itemCount > 0)
+ {
+ this.showMenu();
+ this.fireEvent(new mxEventObject(mxEvent.SHOW));
+ }
+ }
+};
+
+/**
+ * Function: isMenuShowing
+ *
+ * Returns true if the menu is showing.
+ */
+mxPopupMenu.prototype.isMenuShowing = function()
+{
+ return this.div != null && this.div.parentNode == document.body;
+};
+
+/**
+ * Function: showMenu
+ *
+ * Shows the menu.
+ */
+mxPopupMenu.prototype.showMenu = function()
+{
+ // Disables filter-based shadow in IE9 standards mode
+ if (document.documentMode >= 9)
+ {
+ this.div.style.filter = 'none';
+ }
+
+ // Fits the div inside the viewport
+ document.body.appendChild(this.div);
+ mxUtils.fit(this.div);
+};
+
+/**
+ * Function: hideMenu
+ *
+ * Removes the menu and all submenus.
+ */
+mxPopupMenu.prototype.hideMenu = function()
+{
+ if (this.div != null)
+ {
+ if (this.div.parentNode != null)
+ {
+ this.div.parentNode.removeChild(this.div);
+ }
+
+ this.hideSubmenu(this);
+ this.containsItems = false;
+ }
+};
+
+/**
+ * Function: hideSubmenu
+ *
+ * Removes all submenus inside the given parent.
+ *
+ * Parameters:
+ *
+ * parent - An item returned by <addItem>.
+ */
+mxPopupMenu.prototype.hideSubmenu = function(parent)
+{
+ if (parent.activeRow != null)
+ {
+ this.hideSubmenu(parent.activeRow);
+
+ if (parent.activeRow.div.parentNode != null)
+ {
+ parent.activeRow.div.parentNode.removeChild(parent.activeRow.div);
+ }
+
+ parent.activeRow = null;
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the handler and all its resources and DOM nodes.
+ */
+mxPopupMenu.prototype.destroy = function()
+{
+ if (this.div != null)
+ {
+ mxEvent.release(this.div);
+
+ if (this.div.parentNode != null)
+ {
+ this.div.parentNode.removeChild(this.div);
+ }
+
+ this.div = null;
+ }
+};
diff --git a/src/js/util/mxRectangle.js b/src/js/util/mxRectangle.js
new file mode 100644
index 0000000..035abf5
--- /dev/null
+++ b/src/js/util/mxRectangle.js
@@ -0,0 +1,134 @@
+/**
+ * $Id: mxRectangle.js,v 1.17 2010-12-08 12:46:03 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxRectangle
+ *
+ * Extends <mxPoint> to implement a 2-dimensional rectangle with double
+ * precision coordinates.
+ *
+ * Constructor: mxRectangle
+ *
+ * Constructs a new rectangle for the optional parameters. If no parameters
+ * are given then the respective default values are used.
+ */
+function mxRectangle(x, y, width, height)
+{
+ mxPoint.call(this, x, y);
+
+ this.width = (width != null) ? width : 0;
+ this.height = (height != null) ? height : 0;
+};
+
+/**
+ * Extends mxPoint.
+ */
+mxRectangle.prototype = new mxPoint();
+mxRectangle.prototype.constructor = mxRectangle;
+
+/**
+ * Variable: width
+ *
+ * Holds the width of the rectangle. Default is 0.
+ */
+mxRectangle.prototype.width = null;
+
+/**
+ * Variable: height
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxRectangle.prototype.height = null;
+
+/**
+ * Function: setRect
+ *
+ * Sets this rectangle to the specified values
+ */
+mxRectangle.prototype.setRect = function(x, y, w, h)
+{
+ this.x = x;
+ this.y = y;
+ this.width = w;
+ this.height = h;
+};
+
+/**
+ * Function: getCenterX
+ *
+ * Returns the x-coordinate of the center point.
+ */
+mxRectangle.prototype.getCenterX = function ()
+{
+ return this.x + this.width/2;
+};
+
+/**
+ * Function: getCenterY
+ *
+ * Returns the y-coordinate of the center point.
+ */
+mxRectangle.prototype.getCenterY = function ()
+{
+ return this.y + this.height/2;
+};
+
+/**
+ * Function: add
+ *
+ * Adds the given rectangle to this rectangle.
+ */
+mxRectangle.prototype.add = function(rect)
+{
+ if (rect != null)
+ {
+ var minX = Math.min(this.x, rect.x);
+ var minY = Math.min(this.y, rect.y);
+ var maxX = Math.max(this.x + this.width, rect.x + rect.width);
+ var maxY = Math.max(this.y + this.height, rect.y + rect.height);
+
+ this.x = minX;
+ this.y = minY;
+ this.width = maxX - minX;
+ this.height = maxY - minY;
+ }
+};
+
+/**
+ * Function: grow
+ *
+ * Grows the rectangle by the given amount, that is, this method subtracts
+ * the given amount from the x- and y-coordinates and adds twice the amount
+ * to the width and height.
+ */
+mxRectangle.prototype.grow = function(amount)
+{
+ this.x -= amount;
+ this.y -= amount;
+ this.width += 2 * amount;
+ this.height += 2 * amount;
+};
+
+/**
+ * Function: getPoint
+ *
+ * Returns the top, left corner as a new <mxPoint>.
+ */
+mxRectangle.prototype.getPoint = function()
+{
+ return new mxPoint(this.x, this.y);
+};
+
+/**
+ * Function: equals
+ *
+ * Returns true if the given object equals this rectangle.
+ */
+mxRectangle.prototype.equals = function(obj)
+{
+ return obj.x == this.x &&
+ obj.y == this.y &&
+ obj.width == this.width &&
+ obj.height == this.height;
+};
diff --git a/src/js/util/mxResources.js b/src/js/util/mxResources.js
new file mode 100644
index 0000000..0969ebe
--- /dev/null
+++ b/src/js/util/mxResources.js
@@ -0,0 +1,366 @@
+/**
+ * $Id: mxResources.js,v 1.32 2012-10-26 13:36:50 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxResources =
+{
+ /**
+ * Class: mxResources
+ *
+ * Implements internationalization. You can provide any number of
+ * resource files on the server using the following format for the
+ * filename: name[-en].properties. The en stands for any lowercase
+ * 2-character language shortcut (eg. de for german, fr for french).
+ *
+ * If the optional language extension is omitted, then the file is used as a
+ * default resource which is loaded in all cases. If a properties file for a
+ * specific language exists, then it is used to override the settings in the
+ * default resource. All entries in the file are of the form key=value. The
+ * values may then be accessed in code via <get>. Lines without
+ * equal signs in the properties files are ignored.
+ *
+ * Resource files may either be added programmatically using
+ * <add> or via a resource tag in the UI section of the
+ * editor configuration file, eg:
+ *
+ * (code)
+ * <mxEditor>
+ * <ui>
+ * <resource basename="examples/resources/mxWorkflow"/>
+ * (end)
+ *
+ * The above element will load examples/resources/mxWorkflow.properties as well
+ * as the language specific file for the current language, if it exists.
+ *
+ * Values may contain placeholders of the form {1}...{n} where each placeholder
+ * is replaced with the value of the corresponding array element in the params
+ * argument passed to <mxResources.get>. The placeholder {1} maps to the first
+ * element in the array (at index 0).
+ *
+ * See <mxClient.language> for more information on specifying the default
+ * language or disabling all loading of resources.
+ *
+ * Lines that start with a # sign will be ignored.
+ *
+ * Special characters
+ *
+ * To use unicode characters, use the standard notation (eg. \u8fd1) or %u as a
+ * prefix (eg. %u20AC will display a Euro sign). For normal hex encoded strings,
+ * use % as a prefix, eg. %F6 will display a � (&ouml;).
+ *
+ * See <resourcesEncoded> to disable this. If you disable this, make sure that
+ * your files are UTF-8 encoded.
+ *
+ * Variable: resources
+ *
+ * Associative array that maps from keys to values.
+ */
+ resources: [],
+
+ /**
+ * Variable: extension
+ *
+ * Specifies the extension used for language files. Default is '.properties'.
+ */
+ extension: '.properties',
+
+ /**
+ * Variable: resourcesEncoded
+ *
+ * Specifies whether or not values in resource files are encoded with \u or
+ * percentage. Default is true.
+ */
+ resourcesEncoded: true,
+
+ /**
+ * Variable: loadDefaultBundle
+ *
+ * Specifies if the default file for a given basename should be loaded.
+ * Default is true.
+ */
+ loadDefaultBundle: true,
+
+ /**
+ * Variable: loadDefaultBundle
+ *
+ * Specifies if the specific language file file for a given basename should
+ * be loaded. Default is true.
+ */
+ loadSpecialBundle: true,
+
+ /**
+ * Function: isBundleSupported
+ *
+ * Hook for subclassers to disable support for a given language. This
+ * implementation always returns true.
+ *
+ * Parameters:
+ *
+ * basename - The basename for which the file should be loaded.
+ * lan - The current language.
+ */
+ isLanguageSupported: function(lan)
+ {
+ if (mxClient.languages != null)
+ {
+ return mxUtils.indexOf(mxClient.languages, lan) >= 0;
+ }
+
+ return true;
+ },
+
+ /**
+ * Function: getDefaultBundle
+ *
+ * Hook for subclassers to return the URL for the special bundle. This
+ * implementation returns basename + <extension> or null if
+ * <loadDefaultBundle> is false.
+ *
+ * Parameters:
+ *
+ * basename - The basename for which the file should be loaded.
+ * lan - The current language.
+ */
+ getDefaultBundle: function(basename, lan)
+ {
+ if (mxResources.loadDefaultBundle || !mxResources.isLanguageSupported(lan))
+ {
+ return basename + mxResources.extension;
+ }
+ else
+ {
+ return null;
+ }
+ },
+
+ /**
+ * Function: getSpecialBundle
+ *
+ * Hook for subclassers to return the URL for the special bundle. This
+ * implementation returns basename + '_' + lan + <extension> or null if
+ * <loadSpecialBundle> is false or lan equals <mxClient.defaultLanguage>.
+ *
+ * If <mxResources.languages> is not null and <mxClient.language> contains
+ * a dash, then this method checks if <isLanguageSupported> returns true
+ * for the full language (including the dash). If that returns false the
+ * first part of the language (up to the dash) will be tried as an extension.
+ *
+ * If <mxResources.language> is null then the first part of the language is
+ * used to maintain backwards compatibility.
+ *
+ * Parameters:
+ *
+ * basename - The basename for which the file should be loaded.
+ * lan - The language for which the file should be loaded.
+ */
+ getSpecialBundle: function(basename, lan)
+ {
+ if (mxClient.languages == null || !this.isLanguageSupported(lan))
+ {
+ var dash = lan.indexOf('-');
+
+ if (dash > 0)
+ {
+ lan = lan.substring(0, dash);
+ }
+ }
+
+ if (mxResources.loadSpecialBundle && mxResources.isLanguageSupported(lan) && lan != mxClient.defaultLanguage)
+ {
+ return basename + '_' + lan + mxResources.extension;
+ }
+ else
+ {
+ return null;
+ }
+ },
+
+ /**
+ * Function: add
+ *
+ * Adds the default and current language properties
+ * file for the specified basename. Existing keys
+ * are overridden as new files are added.
+ *
+ * Example:
+ *
+ * At application startup, additional resources may be
+ * added using the following code:
+ *
+ * (code)
+ * mxResources.add('resources/editor');
+ * (end)
+ */
+ add: function(basename, lan)
+ {
+ lan = (lan != null) ? lan : mxClient.language.toLowerCase();
+
+ if (lan != mxConstants.NONE)
+ {
+ // Loads the common language file (no extension)
+ var defaultBundle = mxResources.getDefaultBundle(basename, lan);
+
+ if (defaultBundle != null)
+ {
+ try
+ {
+ var req = mxUtils.load(defaultBundle);
+
+ if (req.isReady())
+ {
+ mxResources.parse(req.getText());
+ }
+ }
+ catch (e)
+ {
+ // ignore
+ }
+ }
+
+ // Overlays the language specific file (_lan-extension)
+ var specialBundle = mxResources.getSpecialBundle(basename, lan);
+
+ if (specialBundle != null)
+ {
+ try
+ {
+ var req = mxUtils.load(specialBundle);
+
+ if (req.isReady())
+ {
+ mxResources.parse(req.getText());
+ }
+ }
+ catch (e)
+ {
+ // ignore
+ }
+ }
+ }
+ },
+
+ /**
+ * Function: parse
+ *
+ * Parses the key, value pairs in the specified
+ * text and stores them as local resources.
+ */
+ parse: function(text)
+ {
+ if (text != null)
+ {
+ var lines = text.split('\n');
+
+ for (var i = 0; i < lines.length; i++)
+ {
+ if (lines[i].charAt(0) != '#')
+ {
+ var index = lines[i].indexOf('=');
+
+ if (index > 0)
+ {
+ var key = lines[i].substring(0, index);
+ var idx = lines[i].length;
+
+ if (lines[i].charCodeAt(idx - 1) == 13)
+ {
+ idx--;
+ }
+
+ var value = lines[i].substring(index + 1, idx);
+
+ if (this.resourcesEncoded)
+ {
+ value = value.replace(/\\(?=u[a-fA-F\d]{4})/g,"%");
+ mxResources.resources[key] = unescape(value);
+ }
+ else
+ {
+ mxResources.resources[key] = value;
+ }
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Function: get
+ *
+ * Returns the value for the specified resource key.
+ *
+ * Example:
+ * To read the value for 'welomeMessage', use the following:
+ * (code)
+ * var result = mxResources.get('welcomeMessage') || '';
+ * (end)
+ *
+ * This would require an entry of the following form in
+ * one of the English language resource files:
+ * (code)
+ * welcomeMessage=Welcome to mxGraph!
+ * (end)
+ *
+ * The part behind the || is the string value to be used if the given
+ * resource is not available.
+ *
+ * Parameters:
+ *
+ * key - String that represents the key of the resource to be returned.
+ * params - Array of the values for the placeholders of the form {1}...{n}
+ * to be replaced with in the resulting string.
+ * defaultValue - Optional string that specifies the default return value.
+ */
+ get: function(key, params, defaultValue)
+ {
+ var value = mxResources.resources[key];
+
+ // Applies the default value if no resource was found
+ if (value == null)
+ {
+ value = defaultValue;
+ }
+
+ // Replaces the placeholders with the values in the array
+ if (value != null &&
+ params != null)
+ {
+ var result = [];
+ var index = null;
+
+ for (var i = 0; i < value.length; i++)
+ {
+ var c = value.charAt(i);
+
+ if (c == '{')
+ {
+ index = '';
+ }
+ else if (index != null && c == '}')
+ {
+ index = parseInt(index)-1;
+
+ if (index >= 0 && index < params.length)
+ {
+ result.push(params[index]);
+ }
+
+ index = null;
+ }
+ else if (index != null)
+ {
+ index += c;
+ }
+ else
+ {
+ result.push(c);
+ }
+ }
+
+ value = result.join('');
+ }
+
+ return value;
+ }
+
+};
diff --git a/src/js/util/mxSession.js b/src/js/util/mxSession.js
new file mode 100644
index 0000000..4c2a70c
--- /dev/null
+++ b/src/js/util/mxSession.js
@@ -0,0 +1,674 @@
+/**
+ * $Id: mxSession.js,v 1.46 2012-08-22 15:30:49 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxSession
+ *
+ * Session for sharing an <mxGraphModel> with other parties
+ * via a backend that acts as a multicaster for all changes.
+ *
+ * Diagram Sharing:
+ *
+ * The diagram sharing is a mechanism where each atomic change of the model is
+ * encoded into XML using <mxCodec> and then transmitted to the server by the
+ * <mxSession> object. On the server, the XML data is dispatched to each
+ * listener on the same diagram (except the sender), and the XML is decoded
+ * back into atomic changes on the client side, which are then executed on the
+ * model and stored in the command history.
+ *
+ * The <mxSession.significantRemoteChanges> specifies how these changes are
+ * treated with respect to undo: The default value (true) will undo the last
+ * change regardless of whether it was a remote or a local change. If the
+ * switch is false, then an undo will go back until the last local change,
+ * silently undoing all remote changes up to that point. Note that these
+ * changes will be added as new remote changes to the history of the other
+ * clients.
+ *
+ * Event: mxEvent.CONNECT
+ *
+ * Fires after the session has been started, that is, after the response to the
+ * initial request was received and the session goes into polling mode. This
+ * event has no properties.
+ *
+ * Event: mxEvent.SUSPEND
+ *
+ * Fires after <suspend> was called an the session was not already in suspended
+ * state. This event has no properties.
+ *
+ * Event: mxEvent.RESUME
+ *
+ * Fires after the session was resumed in <resume>. This event has no
+ * properties.
+ *
+ * Event: mxEvent.DISCONNECT
+ *
+ * Fires after the session was stopped in <stop>. The <code>reason</code>
+ * property contains the optional exception that was passed to the stop method.
+ *
+ * Event: mxEvent.NOTIFY
+ *
+ * Fires after a notification was sent in <notify>. The <code>url</code>
+ * property contains the URL and the <code>xml</code> property contains the XML
+ * data of the request.
+ *
+ * Event: mxEvent.GET
+ *
+ * Fires after a response was received in <get>. The <code>url</code> property
+ * contains the URL and the <code>request</code> is the <mxXmlRequest> that
+ * contains the response.
+ *
+ * Event: mxEvent.FIRED
+ *
+ * Fires after an array of edits has been executed on the model. The
+ * <code>changes</code> property contains the array of changes.
+ *
+ * Event: mxEvent.RECEIVE
+ *
+ * Fires after an XML node was received in <receive>. The <code>node</code>
+ * property contains the node that was received.
+ *
+ * Constructor: mxSession
+ *
+ * Constructs a new session using the given <mxGraphModel> and URLs to
+ * communicate with the backend.
+ *
+ * Parameters:
+ *
+ * model - <mxGraphModel> that contains the data.
+ * urlInit - URL to be used for initializing the session.
+ * urlPoll - URL to be used for polling the backend.
+ * urlNotify - URL to be used for sending changes to the backend.
+ */
+function mxSession(model, urlInit, urlPoll, urlNotify)
+{
+ this.model = model;
+ this.urlInit = urlInit;
+ this.urlPoll = urlPoll;
+ this.urlNotify = urlNotify;
+
+ // Resolves cells by id using the model
+ if (model != null)
+ {
+ this.codec = new mxCodec();
+
+ this.codec.lookup = function(id)
+ {
+ return model.getCell(id);
+ };
+ }
+
+ // Adds the listener for notifying the backend of any
+ // changes in the model
+ model.addListener(mxEvent.NOTIFY, mxUtils.bind(this, function(sender, evt)
+ {
+ var edit = evt.getProperty('edit');
+
+ if (edit != null && this.debug || (this.connected && !this.suspended))
+ {
+ this.notify('<edit>'+this.encodeChanges(edit.changes, edit.undone)+'</edit>');
+ }
+ }));
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxSession.prototype = new mxEventSource();
+mxSession.prototype.constructor = mxSession;
+
+/**
+ * Variable: model
+ *
+ * Reference to the enclosing <mxGraphModel>.
+ */
+mxSession.prototype.model = null;
+
+/**
+ * Variable: urlInit
+ *
+ * URL to initialize the session.
+ */
+mxSession.prototype.urlInit = null;
+
+/**
+ * Variable: urlPoll
+ *
+ * URL for polling the backend.
+ */
+mxSession.prototype.urlPoll = null;
+
+/**
+ * Variable: urlNotify
+ *
+ * URL to send changes to the backend.
+ */
+mxSession.prototype.urlNotify = null;
+
+/**
+ * Variable: codec
+ *
+ * Reference to the <mxCodec> used to encoding and decoding changes.
+ */
+mxSession.prototype.codec = null;
+
+/**
+ * Variable: linefeed
+ *
+ * Used for encoding linefeeds. Default is '&#xa;'.
+ */
+mxSession.prototype.linefeed = '&#xa;';
+
+/**
+ * Variable: escapePostData
+ *
+ * Specifies if the data in the post request sent in <notify>
+ * should be converted using encodeURIComponent. Default is true.
+ */
+mxSession.prototype.escapePostData = true;
+
+/**
+ * Variable: significantRemoteChanges
+ *
+ * Whether remote changes should be significant in the
+ * local command history. Default is true.
+ */
+mxSession.prototype.significantRemoteChanges = true;
+
+/**
+ * Variable: sent
+ *
+ * Total number of sent bytes.
+ */
+mxSession.prototype.sent = 0;
+
+/**
+ * Variable: received
+ *
+ * Total number of received bytes.
+ */
+mxSession.prototype.received = 0;
+
+/**
+ * Variable: debug
+ *
+ * Specifies if the session should run in debug mode. In this mode, no
+ * connection is established. The data is written to the console instead.
+ * Default is false.
+ */
+mxSession.prototype.debug = false;
+
+/**
+ * Variable: connected
+ */
+mxSession.prototype.connected = false;
+
+/**
+ * Variable: send
+ */
+mxSession.prototype.suspended = false;
+
+/**
+ * Variable: polling
+ */
+mxSession.prototype.polling = false;
+
+/**
+ * Function: start
+ */
+mxSession.prototype.start = function()
+{
+ if (this.debug)
+ {
+ this.connected = true;
+ this.fireEvent(new mxEventObject(mxEvent.CONNECT));
+ }
+ else if (!this.connected)
+ {
+ this.get(this.urlInit, mxUtils.bind(this, function(req)
+ {
+ this.connected = true;
+ this.fireEvent(new mxEventObject(mxEvent.CONNECT));
+ this.poll();
+ }));
+ }
+};
+
+/**
+ * Function: suspend
+ *
+ * Suspends the polling. Use <resume> to reactive the session. Fires a
+ * suspend event.
+ */
+mxSession.prototype.suspend = function()
+{
+ if (this.connected && !this.suspended)
+ {
+ this.suspended = true;
+ this.fireEvent(new mxEventObject(mxEvent.SUSPEND));
+ }
+};
+
+/**
+ * Function: resume
+ *
+ * Resumes the session if it has been suspended. Fires a resume-event
+ * before starting the polling.
+ */
+mxSession.prototype.resume = function(type, attr, value)
+{
+ if (this.connected &&
+ this.suspended)
+ {
+ this.suspended = false;
+ this.fireEvent(new mxEventObject(mxEvent.RESUME));
+
+ if (!this.polling)
+ {
+ this.poll();
+ }
+ }
+};
+
+/**
+ * Function: stop
+ *
+ * Stops the session and fires a disconnect event. The given reason is
+ * passed to the disconnect event listener as the second argument.
+ */
+mxSession.prototype.stop = function(reason)
+{
+ if (this.connected)
+ {
+ this.connected = false;
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.DISCONNECT,
+ 'reason', reason));
+};
+
+/**
+ * Function: poll
+ *
+ * Sends an asynchronous GET request to <urlPoll>.
+ */
+mxSession.prototype.poll = function()
+{
+ if (this.connected &&
+ !this.suspended &&
+ this.urlPoll != null)
+ {
+ this.polling = true;
+
+ this.get(this.urlPoll, mxUtils.bind(this, function()
+ {
+ this.poll();
+ }));
+ }
+ else
+ {
+ this.polling = false;
+ }
+};
+
+/**
+ * Function: notify
+ *
+ * Sends out the specified XML to <urlNotify> and fires a <notify> event.
+ */
+mxSession.prototype.notify = function(xml, onLoad, onError)
+{
+ if (xml != null &&
+ xml.length > 0)
+ {
+ if (this.urlNotify != null)
+ {
+ if (this.debug)
+ {
+ mxLog.show();
+ mxLog.debug('mxSession.notify: '+this.urlNotify+' xml='+xml);
+ }
+ else
+ {
+ xml = '<message><delta>'+xml+'</delta></message>';
+
+ if (this.escapePostData)
+ {
+ xml = encodeURIComponent(xml);
+ }
+
+ mxUtils.post(this.urlNotify, 'xml='+xml, onLoad, onError);
+ }
+ }
+
+ this.sent += xml.length;
+ this.fireEvent(new mxEventObject(mxEvent.NOTIFY,
+ 'url', this.urlNotify, 'xml', xml));
+ }
+};
+
+/**
+ * Function: get
+ *
+ * Sends an asynchronous get request to the given URL, fires a <get> event
+ * and invokes the given onLoad function when a response is received.
+ */
+mxSession.prototype.get = function(url, onLoad, onError)
+{
+ // Response after browser refresh has no global scope
+ // defined. This response is ignored and the session
+ // stops implicitely.
+ if (typeof(mxUtils) != 'undefined')
+ {
+ var onErrorWrapper = mxUtils.bind(this, function(ex)
+ {
+ if (onError != null)
+ {
+ onError(ex);
+ }
+ else
+ {
+ this.stop(ex);
+ }
+ });
+
+ // Handles a successful response for
+ // the above request.
+ mxUtils.get(url, mxUtils.bind(this, function(req)
+ {
+ if (typeof(mxUtils) != 'undefined')
+ {
+ if (req.isReady() && req.getStatus() != 404)
+ {
+ this.received += req.getText().length;
+ this.fireEvent(new mxEventObject(mxEvent.GET, 'url', url, 'request', req));
+
+ if (this.isValidResponse(req))
+ {
+ if (req.getText().length > 0)
+ {
+ var node = req.getDocumentElement();
+
+ if (node == null)
+ {
+ onErrorWrapper('Invalid response: '+req.getText());
+ }
+ else
+ {
+ this.receive(node);
+ }
+ }
+
+ if (onLoad != null)
+ {
+ onLoad(req);
+ }
+ }
+ }
+ else
+ {
+ onErrorWrapper('Response not ready');
+ }
+ }
+ }),
+ // Handles a transmission error for the
+ // above request
+ function(req)
+ {
+ onErrorWrapper('Transmission error');
+ });
+ }
+};
+
+/**
+ * Function: isValidResponse
+ *
+ * Returns true if the response data in the given <mxXmlRequest> is valid.
+ */
+mxSession.prototype.isValidResponse = function(req)
+{
+ // TODO: Find condition to check if response
+ // contains valid XML (not eg. the PHP code).
+ return req.getText().indexOf('<?php') < 0;
+};
+
+/**
+ * Function: encodeChanges
+ *
+ * Returns the XML representation for the given array of changes.
+ */
+mxSession.prototype.encodeChanges = function(changes, invert)
+{
+ // TODO: Use array for string concatenation
+ var xml = '';
+ var step = (invert) ? -1 : 1;
+ var i0 = (invert) ? changes.length - 1 : 0;
+
+ for (var i = i0; i >= 0 && i < changes.length; i += step)
+ {
+ // Newlines must be kept, they will be converted
+ // to &#xa; when the server sends data to the
+ // client
+ var node = this.codec.encode(changes[i]);
+ xml += mxUtils.getXml(node, this.linefeed);
+ }
+
+ return xml;
+};
+
+/**
+ * Function: receive
+ *
+ * Processes the given node by applying the changes to the model. If the nodename
+ * is state, then the namespace is used as a prefix for creating Ids in the model,
+ * and the child nodes are visited recursively. If the nodename is delta, then the
+ * changes encoded in the child nodes are applied to the model. Each call to the
+ * receive function fires a <receive> event with the given node as the second argument
+ * after processing. If changes are processed, then the function additionally fires
+ * a <mxEvent.FIRED> event before the <mxEvent.RECEIVE> event.
+ */
+mxSession.prototype.receive = function(node)
+{
+ if (node != null && node.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ // Uses the namespace in the model
+ var ns = node.getAttribute('namespace');
+
+ if (ns != null)
+ {
+ this.model.prefix = ns + '-';
+ }
+
+ var child = node.firstChild;
+
+ while (child != null)
+ {
+ var name = child.nodeName.toLowerCase();
+
+ if (name == 'state')
+ {
+ this.processState(child);
+ }
+ else if (name == 'delta')
+ {
+ this.processDelta(child);
+ }
+
+ child = child.nextSibling;
+ }
+
+ // Fires receive event
+ this.fireEvent(new mxEventObject(mxEvent.RECEIVE, 'node', node));
+ }
+};
+
+/**
+ * Function: processState
+ *
+ * Processes the given state node which contains the current state of the
+ * remote model.
+ */
+mxSession.prototype.processState = function(node)
+{
+ var dec = new mxCodec(node.ownerDocument);
+ dec.decode(node.firstChild, this.model);
+};
+
+/**
+ * Function: processDelta
+ *
+ * Processes the given delta node which contains a sequence of edits which in
+ * turn map to one transaction on the remote model each.
+ */
+mxSession.prototype.processDelta = function(node)
+{
+ var edit = node.firstChild;
+
+ while (edit != null)
+ {
+ if (edit.nodeName == 'edit')
+ {
+ this.processEdit(edit);
+ }
+
+ edit = edit.nextSibling;
+ }
+};
+
+/**
+ * Function: processEdit
+ *
+ * Processes the given edit by executing its changes and firing the required
+ * events via the model.
+ */
+mxSession.prototype.processEdit = function(node)
+{
+ var changes = this.decodeChanges(node);
+
+ if (changes.length > 0)
+ {
+ var edit = this.createUndoableEdit(changes);
+
+ // No notify event here to avoid the edit from being encoded and transmitted
+ // LATER: Remove changes property (deprecated)
+ this.model.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ 'edit', edit, 'changes', changes));
+ this.model.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+ this.fireEvent(new mxEventObject(mxEvent.FIRED, 'edit', edit));
+ }
+};
+
+/**
+ * Function: createUndoableEdit
+ *
+ * Creates a new <mxUndoableEdit> that implements the notify function to fire a
+ * <change> and <notify> event via the model.
+ */
+mxSession.prototype.createUndoableEdit = function(changes)
+{
+ var edit = new mxUndoableEdit(this.model, this.significantRemoteChanges);
+ edit.changes = changes;
+
+ edit.notify = function()
+ {
+ // LATER: Remove changes property (deprecated)
+ edit.source.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ 'edit', edit, 'changes', edit.changes));
+ edit.source.fireEvent(new mxEventObject(mxEvent.NOTIFY,
+ 'edit', edit, 'changes', edit.changes));
+ };
+
+ return edit;
+};
+
+/**
+ * Function: decodeChanges
+ *
+ * Decodes and executes the changes represented by the children in the
+ * given node. Returns an array that contains all changes.
+ */
+mxSession.prototype.decodeChanges = function(node)
+{
+ // Updates the document in the existing codec
+ this.codec.document = node.ownerDocument;
+
+ // Parses and executes the changes on the model
+ var changes = [];
+ node = node.firstChild;
+
+ while (node != null)
+ {
+ var change = this.decodeChange(node);
+
+ if (change != null)
+ {
+ changes.push(change);
+ }
+
+ node = node.nextSibling;
+ }
+
+ return changes;
+};
+
+/**
+ * Function: decodeChange
+ *
+ * Decodes, executes and returns the change object represented by the given
+ * XML node.
+ */
+mxSession.prototype.decodeChange = function(node)
+{
+ var change = null;
+
+ if (node.nodeType == mxConstants.NODETYPE_ELEMENT)
+ {
+ if (node.nodeName == 'mxRootChange')
+ {
+ // Handles the special case were no ids should be
+ // resolved in the existing model. This change will
+ // replace all registered ids and cells from the
+ // model and insert a new cell hierarchy instead.
+ var tmp = new mxCodec(node.ownerDocument);
+ change = tmp.decode(node);
+ }
+ else
+ {
+ change = this.codec.decode(node);
+ }
+
+ if (change != null)
+ {
+ change.model = this.model;
+ change.execute();
+
+ // Workaround for references not being resolved if cells have
+ // been removed from the model prior to being referenced. This
+ // adds removed cells in the codec object lookup table.
+ if (node.nodeName == 'mxChildChange' && change.parent == null)
+ {
+ this.cellRemoved(change.child);
+ }
+ }
+ }
+
+ return change;
+};
+
+/**
+ * Function: cellRemoved
+ *
+ * Adds removed cells to the codec object lookup for references to the removed
+ * cells after this point in time.
+ */
+mxSession.prototype.cellRemoved = function(cell, codec)
+{
+ this.codec.putObject(cell.getId(), cell);
+
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.cellRemoved(this.model.getChildAt(cell, i));
+ }
+};
diff --git a/src/js/util/mxSvgCanvas2D.js b/src/js/util/mxSvgCanvas2D.js
new file mode 100644
index 0000000..4af0642
--- /dev/null
+++ b/src/js/util/mxSvgCanvas2D.js
@@ -0,0 +1,1234 @@
+/**
+ * $Id: mxSvgCanvas2D.js,v 1.18 2012-11-23 15:13:19 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ *
+ * Class: mxSvgCanvas2D
+ *
+ * Implements a canvas to be used with <mxImageExport>. This canvas writes all
+ * calls as SVG output to the given SVG root node.
+ *
+ * (code)
+ * var svgDoc = mxUtils.createXmlDocument();
+ * var root = (svgDoc.createElementNS != null) ?
+ * svgDoc.createElementNS(mxConstants.NS_SVG, 'svg') : svgDoc.createElement('svg');
+ *
+ * if (svgDoc.createElementNS == null)
+ * {
+ * root.setAttribute('xmlns', mxConstants.NS_SVG);
+ * }
+ *
+ * var bounds = graph.getGraphBounds();
+ * root.setAttribute('width', (bounds.x + bounds.width + 4) + 'px');
+ * root.setAttribute('height', (bounds.y + bounds.height + 4) + 'px');
+ * root.setAttribute('version', '1.1');
+ *
+ * svgDoc.appendChild(root);
+ *
+ * var svgCanvas = new mxSvgCanvas2D(root);
+ * (end)
+ *
+ * Constructor: mxSvgCanvas2D
+ *
+ * Constructs an SVG canvas.
+ *
+ * Parameters:
+ *
+ * root - SVG container for the output.
+ * styleEnabled - Optional boolean that specifies if a style section should be
+ * added. The style section sets the default font-size, font-family and
+ * stroke-miterlimit globally. Default is false.
+ */
+var mxSvgCanvas2D = function(root, styleEnabled)
+{
+ styleEnabled = (styleEnabled != null) ? styleEnabled : false;
+
+ /**
+ * Variable: converter
+ *
+ * Holds the <mxUrlConverter> to convert image URLs.
+ */
+ var converter = new mxUrlConverter();
+
+ /**
+ * Variable: autoAntiAlias
+ *
+ * Specifies if anti aliasing should be disabled for rectangles
+ * and orthogonal paths. Default is true.
+ */
+ var autoAntiAlias = true;
+
+ /**
+ * Variable: textEnabled
+ *
+ * Specifies if text output should be enabled. Default is true.
+ */
+ var textEnabled = true;
+
+ /**
+ * Variable: foEnabled
+ *
+ * Specifies if use of foreignObject for HTML markup is allowed. Default is true.
+ */
+ var foEnabled = true;
+
+ // Private helper function to create SVG elements
+ var create = function(tagName, namespace)
+ {
+ var doc = root.ownerDocument || document;
+
+ if (doc.createElementNS != null)
+ {
+ return doc.createElementNS(namespace || mxConstants.NS_SVG, tagName);
+ }
+ else
+ {
+ var elt = doc.createElement(tagName);
+
+ if (namespace != null)
+ {
+ elt.setAttribute('xmlns', namespace);
+ }
+
+ return elt;
+ }
+ };
+
+ // Defs section contains optional style and gradients
+ var defs = create('defs');
+
+ // Creates defs section with optional global style
+ if (styleEnabled)
+ {
+ var style = create('style');
+ style.setAttribute('type', 'text/css');
+ mxUtils.write(style, 'svg{font-family:' + mxConstants.DEFAULT_FONTFAMILY +
+ ';font-size:' + mxConstants.DEFAULT_FONTSIZE +
+ ';fill:none;stroke-miterlimit:10}');
+
+ if (autoAntiAlias)
+ {
+ mxUtils.write(style, 'rect{shape-rendering:crispEdges}');
+ }
+
+ // Appends style to defs and defs to SVG container
+ defs.appendChild(style);
+ }
+
+ root.appendChild(defs);
+
+ // Defines the current state
+ var currentState =
+ {
+ dx: 0,
+ dy: 0,
+ scale: 1,
+ transform: '',
+ fill: null,
+ gradient: null,
+ stroke: null,
+ strokeWidth: 1,
+ dashed: false,
+ dashpattern: '3 3',
+ alpha: 1,
+ linecap: 'flat',
+ linejoin: 'miter',
+ miterlimit: 10,
+ fontColor: '#000000',
+ fontSize: mxConstants.DEFAULT_FONTSIZE,
+ fontFamily: mxConstants.DEFAULT_FONTFAMILY,
+ fontStyle: 0
+ };
+
+ // Local variables
+ var currentPathIsOrthogonal = true;
+ var glassGradient = null;
+ var currentNode = null;
+ var currentPath = null;
+ var lastPoint = null;
+ var gradients = [];
+ var refCount = 0;
+ var stack = [];
+
+ // Other private helper methods
+ var createGradientId = function(start, end, direction)
+ {
+ // Removes illegal characters from gradient ID
+ if (start.charAt(0) == '#')
+ {
+ start = start.substring(1);
+ }
+
+ if (end.charAt(0) == '#')
+ {
+ end = end.substring(1);
+ }
+
+ // Workaround for gradient IDs not working in Safari 5 / Chrome 6
+ // if they contain uppercase characters
+ start = start.toLowerCase();
+ end = end.toLowerCase();
+
+ // Wrong gradient directions possible?
+ var dir = null;
+
+ if (direction == null || direction == mxConstants.DIRECTION_SOUTH)
+ {
+ dir = 's';
+ }
+ else if (direction == mxConstants.DIRECTION_EAST)
+ {
+ dir = 'e';
+ }
+ else
+ {
+ var tmp = start;
+ start = end;
+ end = tmp;
+
+ if (direction == mxConstants.DIRECTION_NORTH)
+ {
+ dir = 's';
+ }
+ else if (direction == mxConstants.DIRECTION_WEST)
+ {
+ dir = 'e';
+ }
+ }
+
+ return start+'-'+end+'-'+dir;
+ };
+
+ var createHtmlBody = function(str, align, valign)
+ {
+ var style = 'margin:0px;font-size:' + Math.floor(currentState.fontSize) + 'px;' +
+ 'font-family:' + currentState.fontFamily + ';color:' + currentState.fontColor+ ';';
+
+ if ((currentState.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+ {
+ style += 'font-weight:bold;';
+ }
+
+ if ((currentState.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+ {
+ style += 'font-style:italic;';
+ }
+
+ if ((currentState.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+ {
+ style += 'font-decoration:underline;';
+ }
+
+ if (align == mxConstants.ALIGN_CENTER)
+ {
+ style += 'text-align:center;';
+ }
+ else if (align == mxConstants.ALIGN_RIGHT)
+ {
+ style += 'text-align:right;';
+ }
+
+ // Converts HTML entities to unicode
+ var t = document.createElement('div');
+ t.innerHTML = str;
+ str = t.innerHTML.replace(/&nbsp;/g, '&#160;');
+
+ // LATER: Add vertical align support via table, adds xmlns to workaround empty NS in IE9 standards
+ var node = mxUtils.parseXml('<div xmlns="http://www.w3.org/1999/xhtml" style="' +
+ style + '">' + str + '</div>').documentElement;
+
+ return node;
+ };
+
+ var getSvgGradient = function(start, end, direction)
+ {
+ var id = createGradientId(start, end, direction);
+ var gradient = gradients[id];
+
+ if (gradient == null)
+ {
+ gradient = create('linearGradient');
+ gradient.setAttribute('id', ++refCount);
+ gradient.setAttribute('x1', '0%');
+ gradient.setAttribute('y1', '0%');
+ gradient.setAttribute('x2', '0%');
+ gradient.setAttribute('y2', '0%');
+
+ if (direction == null || direction == mxConstants.DIRECTION_SOUTH)
+ {
+ gradient.setAttribute('y2', '100%');
+ }
+ else if (direction == mxConstants.DIRECTION_EAST)
+ {
+ gradient.setAttribute('x2', '100%');
+ }
+ else if (direction == mxConstants.DIRECTION_NORTH)
+ {
+ gradient.setAttribute('y1', '100%');
+ }
+ else if (direction == mxConstants.DIRECTION_WEST)
+ {
+ gradient.setAttribute('x1', '100%');
+ }
+
+ var stop = create('stop');
+ stop.setAttribute('offset', '0%');
+ stop.setAttribute('style', 'stop-color:'+start);
+ gradient.appendChild(stop);
+
+ stop = create('stop');
+ stop.setAttribute('offset', '100%');
+ stop.setAttribute('style', 'stop-color:'+end);
+ gradient.appendChild(stop);
+
+ defs.appendChild(gradient);
+ gradients[id] = gradient;
+ }
+
+ return gradient.getAttribute('id');
+ };
+
+ var appendNode = function(node, state, filled, stroked)
+ {
+ if (node != null)
+ {
+ if (state.clip != null)
+ {
+ node.setAttribute('clip-path', 'url(#' + state.clip + ')');
+ state.clip = null;
+ }
+
+ if (currentPath != null)
+ {
+ node.setAttribute('d', currentPath.join(' '));
+ currentPath = null;
+
+ if (autoAntiAlias && currentPathIsOrthogonal)
+ {
+ node.setAttribute('shape-rendering', 'crispEdges');
+ state.strokeWidth = Math.max(1, state.strokeWidth);
+ }
+ }
+
+ if (state.alpha < 1)
+ {
+ // LATER: Check if using fill/stroke-opacity here is faster
+ node.setAttribute('opacity', state.alpha);
+ //node.setAttribute('fill-opacity', state.alpha);
+ //node.setAttribute('stroke-opacity', state.alpha);
+ }
+
+ if (filled && (state.fill != null || state.gradient != null))
+ {
+ if (state.gradient != null)
+ {
+ node.setAttribute('fill', 'url(#' + state.gradient + ')');
+ }
+ else
+ {
+ node.setAttribute('fill', state.fill.toLowerCase());
+ }
+ }
+ else if (!styleEnabled)
+ {
+ node.setAttribute('fill', 'none');
+ }
+
+ if (stroked && state.stroke != null)
+ {
+ node.setAttribute('stroke', state.stroke.toLowerCase());
+
+ // Sets the stroke properties (1 is default is SVG)
+ if (state.strokeWidth != 1)
+ {
+ if (node.nodeName == 'rect' && autoAntiAlias)
+ {
+ state.strokeWidth = Math.max(1, state.strokeWidth);
+ }
+
+ node.setAttribute('stroke-width', state.strokeWidth);
+ }
+
+ if (node.nodeName == 'path')
+ {
+ // Linejoin miter is default in SVG
+ if (state.linejoin != null && state.linejoin != 'miter')
+ {
+ node.setAttribute('stroke-linejoin', state.linejoin);
+ }
+
+ if (state.linecap != null)
+ {
+ // flat is called butt in SVG
+ var value = state.linecap;
+
+ if (value == 'flat')
+ {
+ value = 'butt';
+ }
+
+ // Linecap butt is default in SVG
+ if (value != 'butt')
+ {
+ node.setAttribute('stroke-linecap', value);
+ }
+ }
+
+ // Miterlimit 10 is default in our document
+ if (state.miterlimit != null && (!styleEnabled || state.miterlimit != 10))
+ {
+ node.setAttribute('stroke-miterlimit', state.miterlimit);
+ }
+ }
+
+ if (state.dashed)
+ {
+ var dash = state.dashpattern.split(' ');
+
+ if (dash.length > 0)
+ {
+ var pat = [];
+
+ for (var i = 0; i < dash.length; i++)
+ {
+ pat[i] = Number(dash[i]) * currentState.strokeWidth;
+ }
+
+
+ node.setAttribute('stroke-dasharray', pat.join(' '));
+ }
+ }
+ }
+
+ if (state.transform.length > 0)
+ {
+ node.setAttribute('transform', state.transform);
+ }
+
+ root.appendChild(node);
+ }
+ };
+
+ // Private helper function to format a number
+ var f2 = function(x)
+ {
+ return Math.round(parseFloat(x) * 100) / 100;
+ };
+
+ // Returns public interface
+ return {
+
+ /**
+ * Function: getConverter
+ *
+ * Returns <converter>.
+ */
+ getConverter: function()
+ {
+ return converter;
+ },
+
+ /**
+ * Function: isAutoAntiAlias
+ *
+ * Returns <autoAntiAlias>.
+ */
+ isAutoAntiAlias: function()
+ {
+ return autoAntiAlias;
+ },
+
+ /**
+ * Function: setAutoAntiAlias
+ *
+ * Sets <autoAntiAlias>.
+ */
+ setAutoAntiAlias: function(value)
+ {
+ autoAntiAlias = value;
+ },
+
+ /**
+ * Function: isTextEnabled
+ *
+ * Returns <textEnabled>.
+ */
+ isTextEnabled: function()
+ {
+ return textEnabled;
+ },
+
+ /**
+ * Function: setTextEnabled
+ *
+ * Sets <textEnabled>.
+ */
+ setTextEnabled: function(value)
+ {
+ textEnabled = value;
+ },
+
+ /**
+ * Function: isFoEnabled
+ *
+ * Returns <foEnabled>.
+ */
+ isFoEnabled: function()
+ {
+ return foEnabled;
+ },
+
+ /**
+ * Function: setFoEnabled
+ *
+ * Sets <foEnabled>.
+ */
+ setFoEnabled: function(value)
+ {
+ foEnabled = value;
+ },
+
+ /**
+ * Function: save
+ *
+ * Saves the state of the graphics object.
+ */
+ save: function()
+ {
+ stack.push(currentState);
+ currentState = mxUtils.clone(currentState);
+ },
+
+ /**
+ * Function: restore
+ *
+ * Restores the state of the graphics object.
+ */
+ restore: function()
+ {
+ currentState = stack.pop();
+ },
+
+ /**
+ * Function: scale
+ *
+ * Scales the current graphics object.
+ */
+ scale: function(value)
+ {
+ currentState.scale *= value;
+ currentState.strokeWidth *= value;
+ },
+
+ /**
+ * Function: translate
+ *
+ * Translates the current graphics object.
+ */
+ translate: function(dx, dy)
+ {
+ currentState.dx += dx;
+ currentState.dy += dy;
+ },
+
+ /**
+ * Function: rotate
+ *
+ * Rotates and/or flips the current graphics object.
+ */
+ rotate: function(theta, flipH, flipV, cx, cy)
+ {
+ cx += currentState.dx;
+ cy += currentState.dy;
+
+ cx *= currentState.scale;
+ cy *= currentState.scale;
+
+ // This implementation uses custom scale/translate and built-in rotation
+ // Rotation state is part of the AffineTransform in state.transform
+ if (flipH ^ flipV)
+ {
+ var tx = (flipH) ? cx : 0;
+ var sx = (flipH) ? -1 : 1;
+
+ var ty = (flipV) ? cy : 0;
+ var sy = (flipV) ? -1 : 1;
+
+ currentState.transform += 'translate(' + f2(tx) + ',' + f2(ty) + ')';
+ currentState.transform += 'scale(' + f2(sx) + ',' + f2(sy) + ')';
+ currentState.transform += 'translate(' + f2(-tx) + ' ' + f2(-ty) + ')';
+ }
+
+ currentState.transform += 'rotate(' + f2(theta) + ',' + f2(cx) + ',' + f2(cy) + ')';
+ },
+
+ /**
+ * Function: setStrokeWidth
+ *
+ * Sets the stroke width.
+ */
+ setStrokeWidth: function(value)
+ {
+ currentState.strokeWidth = value * currentState.scale;
+ },
+
+ /**
+ * Function: setStrokeColor
+ *
+ * Sets the stroke color.
+ */
+ setStrokeColor: function(value)
+ {
+ currentState.stroke = value;
+ },
+
+ /**
+ * Function: setDashed
+ *
+ * Sets the dashed state to true or false.
+ */
+ setDashed: function(value)
+ {
+ currentState.dashed = value;
+ },
+
+ /**
+ * Function: setDashPattern
+ *
+ * Sets the dashed pattern to the given space separated list of numbers.
+ */
+ setDashPattern: function(value)
+ {
+ currentState.dashpattern = value;
+ },
+
+ /**
+ * Function: setLineCap
+ *
+ * Sets the linecap.
+ */
+ setLineCap: function(value)
+ {
+ currentState.linecap = value;
+ },
+
+ /**
+ * Function: setLineJoin
+ *
+ * Sets the linejoin.
+ */
+ setLineJoin: function(value)
+ {
+ currentState.linejoin = value;
+ },
+
+ /**
+ * Function: setMiterLimit
+ *
+ * Sets the miterlimit.
+ */
+ setMiterLimit: function(value)
+ {
+ currentState.miterlimit = value;
+ },
+
+ /**
+ * Function: setFontSize
+ *
+ * Sets the fontsize.
+ */
+ setFontSize: function(value)
+ {
+ currentState.fontSize = value;
+ },
+
+ /**
+ * Function: setFontColor
+ *
+ * Sets the fontcolor.
+ */
+ setFontColor: function(value)
+ {
+ currentState.fontColor = value;
+ },
+
+ /**
+ * Function: setFontFamily
+ *
+ * Sets the fontfamily.
+ */
+ setFontFamily: function(value)
+ {
+ currentState.fontFamily = value;
+ },
+
+ /**
+ * Function: setFontStyle
+ *
+ * Sets the fontstyle.
+ */
+ setFontStyle: function(value)
+ {
+ currentState.fontStyle = value;
+ },
+
+ /**
+ * Function: setAlpha
+ *
+ * Sets the current alpha.
+ */
+ setAlpha: function(alpha)
+ {
+ currentState.alpha = alpha;
+ },
+
+ /**
+ * Function: setFillColor
+ *
+ * Sets the fillcolor.
+ */
+ setFillColor: function(value)
+ {
+ currentState.fill = value;
+ currentState.gradient = null;
+ },
+
+ /**
+ * Function: setGradient
+ *
+ * Sets the gradient color.
+ */
+ setGradient: function(color1, color2, x, y, w, h, direction)
+ {
+ if (color1 != null && color2 != null)
+ {
+ currentState.gradient = getSvgGradient(color1, color2, direction);
+ currentState.fill = color1;
+ }
+ },
+
+ /**
+ * Function: setGlassGradient
+ *
+ * Sets the glass gradient.
+ */
+ setGlassGradient: function(x, y, w, h)
+ {
+ // Creates glass overlay gradient
+ if (glassGradient == null)
+ {
+ glassGradient = create('linearGradient');
+ glassGradient.setAttribute('id', '0');
+ glassGradient.setAttribute('x1', '0%');
+ glassGradient.setAttribute('y1', '0%');
+ glassGradient.setAttribute('x2', '0%');
+ glassGradient.setAttribute('y2', '100%');
+
+ var stop1 = create('stop');
+ stop1.setAttribute('offset', '0%');
+ stop1.setAttribute('style', 'stop-color:#ffffff;stop-opacity:0.9');
+ glassGradient.appendChild(stop1);
+
+ var stop2 = create('stop');
+ stop2.setAttribute('offset', '100%');
+ stop2.setAttribute('style', 'stop-color:#ffffff;stop-opacity:0.1');
+ glassGradient.appendChild(stop2);
+
+ // Makes it the first entry of all gradients in defs
+ if (defs.firstChild.nextSibling != null)
+ {
+ defs.insertBefore(glassGradient, defs.firstChild.nextSibling);
+ }
+ else
+ {
+ defs.appendChild(glassGradient);
+ }
+ }
+
+ // Glass gradient has hardcoded ID (see above)
+ currentState.gradient = '0';
+ },
+
+ /**
+ * Function: rect
+ *
+ * Sets the current path to a rectangle.
+ */
+ rect: function(x, y, w, h)
+ {
+ x += currentState.dx;
+ y += currentState.dy;
+
+ currentNode = create('rect');
+ currentNode.setAttribute('x', f2(x * currentState.scale));
+ currentNode.setAttribute('y', f2(y * currentState.scale));
+ currentNode.setAttribute('width', f2(w * currentState.scale));
+ currentNode.setAttribute('height', f2(h * currentState.scale));
+
+ if (!styleEnabled && autoAntiAlias)
+ {
+ currentNode.setAttribute('shape-rendering', 'crispEdges');
+ }
+ },
+
+ /**
+ * Function: roundrect
+ *
+ * Sets the current path to a rounded rectangle.
+ */
+ roundrect: function(x, y, w, h, dx, dy)
+ {
+ x += currentState.dx;
+ y += currentState.dy;
+
+ currentNode = create('rect');
+ currentNode.setAttribute('x', f2(x * currentState.scale));
+ currentNode.setAttribute('y', f2(y * currentState.scale));
+ currentNode.setAttribute('width', f2(w * currentState.scale));
+ currentNode.setAttribute('height', f2(h * currentState.scale));
+
+ if (dx > 0)
+ {
+ currentNode.setAttribute('rx', f2(dx * currentState.scale));
+ }
+
+ if (dy > 0)
+ {
+ currentNode.setAttribute('ry', f2(dy * currentState.scale));
+ }
+
+ if (!styleEnabled && autoAntiAlias)
+ {
+ currentNode.setAttribute('shape-rendering', 'crispEdges');
+ }
+ },
+
+ /**
+ * Function: ellipse
+ *
+ * Sets the current path to an ellipse.
+ */
+ ellipse: function(x, y, w, h)
+ {
+ x += currentState.dx;
+ y += currentState.dy;
+
+ currentNode = create('ellipse');
+ currentNode.setAttribute('cx', f2((x + w / 2) * currentState.scale));
+ currentNode.setAttribute('cy', f2((y + h / 2) * currentState.scale));
+ currentNode.setAttribute('rx', f2(w / 2 * currentState.scale));
+ currentNode.setAttribute('ry', f2(h / 2 * currentState.scale));
+ },
+
+ /**
+ * Function: image
+ *
+ * Paints an image.
+ */
+ image: function(x, y, w, h, src, aspect, flipH, flipV)
+ {
+ src = converter.convert(src);
+
+ // TODO: Add option for embedded images as base64. Current
+ // known issues are binary loading of cross-domain images.
+ aspect = (aspect != null) ? aspect : true;
+ flipH = (flipH != null) ? flipH : false;
+ flipV = (flipV != null) ? flipV : false;
+ x += currentState.dx;
+ y += currentState.dy;
+
+ var node = create('image');
+ node.setAttribute('x', f2(x * currentState.scale));
+ node.setAttribute('y', f2(y * currentState.scale));
+ node.setAttribute('width', f2(w * currentState.scale));
+ node.setAttribute('height', f2(h * currentState.scale));
+
+ if (mxClient.IS_VML)
+ {
+ node.setAttribute('xlink:href', src);
+ }
+ else
+ {
+ node.setAttributeNS(mxConstants.NS_XLINK, 'xlink:href', src);
+ }
+
+ if (!aspect)
+ {
+ node.setAttribute('preserveAspectRatio', 'none');
+ }
+
+ if (currentState.alpha < 1)
+ {
+ node.setAttribute('opacity', currentState.alpha);
+ }
+
+
+ var tr = currentState.transform;
+
+ if (flipH || flipV)
+ {
+ var sx = 1;
+ var sy = 1;
+ var dx = 0;
+ var dy = 0;
+
+ if (flipH)
+ {
+ sx = -1;
+ dx = -w - 2 * x;
+ }
+
+ if (flipV)
+ {
+ sy = -1;
+ dy = -h - 2 * y;
+ }
+
+ // Adds image tansformation to existing transforms
+ tr += 'scale(' + sx + ',' + sy + ')translate(' + dx + ',' + dy + ')';
+ }
+
+ if (tr.length > 0)
+ {
+ node.setAttribute('transform', tr);
+ }
+
+ root.appendChild(node);
+ },
+
+ /**
+ * Function: text
+ *
+ * Paints the given text. Possible values for format are empty string for
+ * plain text and html for HTML markup.
+ */
+ text: function(x, y, w, h, str, align, valign, vertical, wrap, format)
+ {
+ if (textEnabled)
+ {
+ x += currentState.dx;
+ y += currentState.dy;
+
+ if (foEnabled && format == 'html')
+ {
+ var node = create('g');
+ node.setAttribute('transform', currentState.transform + 'scale(' + currentState.scale + ',' + currentState.scale + ')');
+
+ if (currentState.alpha < 1)
+ {
+ node.setAttribute('opacity', currentState.alpha);
+ }
+
+ var fo = create('foreignObject');
+ fo.setAttribute('x', Math.round(x));
+ fo.setAttribute('y', Math.round(y));
+ fo.setAttribute('width', Math.round(w));
+ fo.setAttribute('height', Math.round(h));
+ fo.appendChild(createHtmlBody(str, align, valign));
+ node.appendChild(fo);
+ root.appendChild(node);
+ }
+ else
+ {
+ var size = Math.floor(currentState.fontSize);
+ var node = create('g');
+ var tr = currentState.transform;
+
+ if (vertical)
+ {
+ var cx = x + w / 2;
+ var cy = y + h / 2;
+ tr += 'rotate(-90,' + f2(cx * currentState.scale) + ',' + f2(cy * currentState.scale) + ')';
+ }
+
+ if (tr.length > 0)
+ {
+ node.setAttribute('transform', tr);
+ }
+
+ if (currentState.alpha < 1)
+ {
+ node.setAttribute('opacity', currentState.alpha);
+ }
+
+ // Default is left
+ var anchor = (align == mxConstants.ALIGN_RIGHT) ? 'end' :
+ (align == mxConstants.ALIGN_CENTER) ? 'middle' :
+ 'start';
+
+ if (anchor == 'end')
+ {
+ x += Math.max(0, w - 2);
+ }
+ else if (anchor == 'middle')
+ {
+ x += w / 2;
+ }
+ else
+ {
+ x += (w > 0) ? 2 : 0;
+ }
+
+ if ((currentState.fontStyle & mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD)
+ {
+ node.setAttribute('font-weight', 'bold');
+ }
+
+ if ((currentState.fontStyle & mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC)
+ {
+ node.setAttribute('font-style', 'italic');
+ }
+
+ if ((currentState.fontStyle & mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE)
+ {
+ node.setAttribute('text-decoration', 'underline');
+ }
+
+ // Text-anchor start is default in SVG
+ if (anchor != 'start')
+ {
+ node.setAttribute('text-anchor', anchor);
+ }
+
+ if (!styleEnabled || size != mxConstants.DEFAULT_FONTSIZE)
+ {
+ node.setAttribute('font-size', Math.floor(size * currentState.scale) + 'px');
+ }
+
+ if (!styleEnabled || currentState.fontFamily != mxConstants.DEFAULT_FONTFAMILY)
+ {
+ node.setAttribute('font-family', currentState.fontFamily);
+ }
+
+ node.setAttribute('fill', currentState.fontColor);
+
+ var lines = str.split('\n');
+
+ var lineHeight = size * 1.25;
+ var textHeight = (h > 0) ? size + (lines.length - 1) * lineHeight : lines.length * lineHeight - 1;
+ var dy = h - textHeight;
+
+ // Top is default
+ if (valign == null || valign == mxConstants.ALIGN_TOP)
+ {
+ y = Math.max(y - 3 * currentState.scale, y + dy / 2 + ((h > 0) ? lineHeight / 2 - 8 : 0));
+ }
+ else if (valign == mxConstants.ALIGN_MIDDLE)
+ {
+ y = y + dy / 2;
+ }
+ else if (valign == mxConstants.ALIGN_BOTTOM)
+ {
+ y = Math.min(y, y + dy + 2 * currentState.scale);
+ }
+
+ y += size;
+
+ for (var i = 0; i < lines.length; i++)
+ {
+ var text = create('text');
+ text.setAttribute('x', f2(x * currentState.scale));
+ text.setAttribute('y', f2(y * currentState.scale));
+
+ mxUtils.write(text, lines[i]);
+ node.appendChild(text);
+ y += size * 1.3;
+ }
+
+ root.appendChild(node);
+ }
+ }
+ },
+
+ /**
+ * Function: begin
+ *
+ * Starts a new path.
+ */
+ begin: function()
+ {
+ currentNode = create('path');
+ currentPath = [];
+ lastPoint = null;
+ currentPathIsOrthogonal = true;
+ },
+
+ /**
+ * Function: moveTo
+ *
+ * Moves the current path the given coordinates.
+ */
+ moveTo: function(x, y)
+ {
+ if (currentPath != null)
+ {
+ x += currentState.dx;
+ y += currentState.dy;
+ currentPath.push('M ' + f2(x * currentState.scale) + ' ' + f2(y * currentState.scale));
+
+ if (autoAntiAlias)
+ {
+ lastPoint = new mxPoint(x, y);
+ }
+ }
+ },
+
+ /**
+ * Function: lineTo
+ *
+ * Adds a line to the current path.
+ */
+ lineTo: function(x, y)
+ {
+ if (currentPath != null)
+ {
+ x += currentState.dx;
+ y += currentState.dy;
+ currentPath.push('L ' + f2(x * currentState.scale) + ' ' + f2(y * currentState.scale));
+
+ if (autoAntiAlias)
+ {
+ if (lastPoint != null && currentPathIsOrthogonal && x != lastPoint.x && y != lastPoint.y)
+ {
+ currentPathIsOrthogonal = false;
+ }
+
+ lastPoint = new mxPoint(x, y);
+ }
+ }
+ },
+
+ /**
+ * Function: quadTo
+ *
+ * Adds a quadratic curve to the current path.
+ */
+ quadTo: function(x1, y1, x2, y2)
+ {
+ if (currentPath != null)
+ {
+ x1 += currentState.dx;
+ y1 += currentState.dy;
+ x2 += currentState.dx;
+ y2 += currentState.dy;
+ currentPath.push('Q ' + f2(x1 * currentState.scale) + ' ' + f2(y1 * currentState.scale) +
+ ' ' + f2(x2 * currentState.scale) + ' ' + f2(y2 * currentState.scale));
+ currentPathIsOrthogonal = false;
+ }
+ },
+
+ /**
+ * Function: curveTo
+ *
+ * Adds a bezier curve to the current path.
+ */
+ curveTo: function(x1, y1, x2, y2, x3, y3)
+ {
+ if (currentPath != null)
+ {
+ x1 += currentState.dx;
+ y1 += currentState.dy;
+ x2 += currentState.dx;
+ y2 += currentState.dy;
+ x3 += currentState.dx;
+ y3 += currentState.dy;
+ currentPath.push('C ' + f2(x1 * currentState.scale) + ' ' + f2(y1 * currentState.scale) +
+ ' ' + f2(x2 * currentState.scale) + ' ' + f2(y2 * currentState.scale) +' ' +
+ f2(x3 * currentState.scale) + ' ' + f2(y3 * currentState.scale));
+ currentPathIsOrthogonal = false;
+ }
+ },
+
+ /**
+ * Function: close
+ *
+ * Closes the current path.
+ */
+ close: function()
+ {
+ if (currentPath != null)
+ {
+ currentPath.push('Z');
+ }
+ },
+
+ /**
+ * Function: stroke
+ *
+ * Paints the outline of the current path.
+ */
+ stroke: function()
+ {
+ appendNode(currentNode, currentState, false, true);
+ },
+
+ /**
+ * Function: fill
+ *
+ * Fills the current path.
+ */
+ fill: function()
+ {
+ appendNode(currentNode, currentState, true, false);
+ },
+
+ /**
+ * Function: fillstroke
+ *
+ * Fills and paints the outline of the current path.
+ */
+ fillAndStroke: function()
+ {
+ appendNode(currentNode, currentState, true, true);
+ },
+
+ /**
+ * Function: shadow
+ *
+ * Paints the current path as a shadow of the given color.
+ */
+ shadow: function(value, filled)
+ {
+ this.save();
+ this.setStrokeColor(value);
+
+ if (filled)
+ {
+ this.setFillColor(value);
+ this.fillAndStroke();
+ }
+ else
+ {
+ this.stroke();
+ }
+
+ this.restore();
+ },
+
+ /**
+ * Function: clip
+ *
+ * Uses the current path for clipping.
+ */
+ clip: function()
+ {
+ if (currentNode != null)
+ {
+ if (currentPath != null)
+ {
+ currentNode.setAttribute('d', currentPath.join(' '));
+ currentPath = null;
+ }
+
+ var id = ++refCount;
+ var clip = create('clipPath');
+ clip.setAttribute('id', id);
+ clip.appendChild(currentNode);
+ defs.appendChild(clip);
+ currentState.clip = id;
+ }
+ }
+ };
+
+}; \ No newline at end of file
diff --git a/src/js/util/mxToolbar.js b/src/js/util/mxToolbar.js
new file mode 100644
index 0000000..754e6b3
--- /dev/null
+++ b/src/js/util/mxToolbar.js
@@ -0,0 +1,528 @@
+/**
+ * $Id: mxToolbar.js,v 1.36 2012-06-22 11:17:13 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxToolbar
+ *
+ * Creates a toolbar inside a given DOM node. The toolbar may contain icons,
+ * buttons and combo boxes.
+ *
+ * Event: mxEvent.SELECT
+ *
+ * Fires when an item was selected in the toolbar. The <code>function</code>
+ * property contains the function that was selected in <selectMode>.
+ *
+ * Constructor: mxToolbar
+ *
+ * Constructs a toolbar in the specified container.
+ *
+ * Parameters:
+ *
+ * container - DOM node that contains the toolbar.
+ */
+function mxToolbar(container)
+{
+ this.container = container;
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxToolbar.prototype = new mxEventSource();
+mxToolbar.prototype.constructor = mxToolbar;
+
+/**
+ * Variable: container
+ *
+ * Reference to the DOM nodes that contains the toolbar.
+ */
+mxToolbar.prototype.container = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxToolbar.prototype.enabled = true;
+
+/**
+ * Variable: noReset
+ *
+ * Specifies if <resetMode> requires a forced flag of true for resetting
+ * the current mode in the toolbar. Default is false. This is set to true
+ * if the toolbar item is double clicked to avoid a reset after a single
+ * use of the item.
+ */
+mxToolbar.prototype.noReset = false;
+
+/**
+ * Variable: updateDefaultMode
+ *
+ * Boolean indicating if the default mode should be the last selected
+ * switch mode or the first inserted switch mode. Default is true, that
+ * is the last selected switch mode is the default mode. The default mode
+ * is the mode to be selected after a reset of the toolbar. If this is
+ * false, then the default mode is the first inserted mode item regardless
+ * of what was last selected. Otherwise, the selected item after a reset is
+ * the previously selected item.
+ */
+mxToolbar.prototype.updateDefaultMode = true;
+
+/**
+ * Function: addItem
+ *
+ * Adds the given function as an image with the specified title and icon
+ * and returns the new image node.
+ *
+ * Parameters:
+ *
+ * title - Optional string that is used as the tooltip.
+ * icon - Optional URL of the image to be used. If no URL is given, then a
+ * button is created.
+ * funct - Function to execute on a mouse click.
+ * pressedIcon - Optional URL of the pressed image. Default is a gray
+ * background.
+ * style - Optional style classname. Default is mxToolbarItem.
+ * factoryMethod - Optional factory method for popup menu, eg.
+ * function(menu, evt, cell) { menu.addItem('Hello, World!'); }
+ */
+mxToolbar.prototype.addItem = function(title, icon, funct, pressedIcon, style, factoryMethod)
+{
+ var img = document.createElement((icon != null) ? 'img' : 'button');
+ var initialClassName = style || ((factoryMethod != null) ?
+ 'mxToolbarMode' : 'mxToolbarItem');
+ img.className = initialClassName;
+ img.setAttribute('src', icon);
+
+ if (title != null)
+ {
+ if (icon != null)
+ {
+ img.setAttribute('title', title);
+ }
+ else
+ {
+ mxUtils.write(img, title);
+ }
+ }
+
+ this.container.appendChild(img);
+
+ // Invokes the function on a click on the toolbar item
+ if (funct != null)
+ {
+ mxEvent.addListener(img, (mxClient.IS_TOUCH) ? 'touchend' : 'click', funct);
+ }
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ // Highlights the toolbar item with a gray background
+ // while it is being clicked with the mouse
+ mxEvent.addListener(img, md, mxUtils.bind(this, function(evt)
+ {
+ if (pressedIcon != null)
+ {
+ img.setAttribute('src', pressedIcon);
+ }
+ else
+ {
+ img.style.backgroundColor = 'gray';
+ }
+
+ // Popup Menu
+ if (factoryMethod != null)
+ {
+ if (this.menu == null)
+ {
+ this.menu = new mxPopupMenu();
+ this.menu.init();
+ }
+
+ var last = this.currentImg;
+
+ if (this.menu.isMenuShowing())
+ {
+ this.menu.hideMenu();
+ }
+
+ if (last != img)
+ {
+ // Redirects factory method to local factory method
+ this.currentImg = img;
+ this.menu.factoryMethod = factoryMethod;
+
+ var point = new mxPoint(
+ img.offsetLeft,
+ img.offsetTop + img.offsetHeight);
+ this.menu.popup(point.x, point.y, null, evt);
+
+ // Sets and overrides to restore classname
+ if (this.menu.isMenuShowing())
+ {
+ img.className = initialClassName + 'Selected';
+
+ this.menu.hideMenu = function()
+ {
+ mxPopupMenu.prototype.hideMenu.apply(this);
+ img.className = initialClassName;
+ this.currentImg = null;
+ };
+ }
+ }
+ }
+ }));
+
+ var mouseHandler = mxUtils.bind(this, function(evt)
+ {
+ if (pressedIcon != null)
+ {
+ img.setAttribute('src', icon);
+ }
+ else
+ {
+ img.style.backgroundColor = '';
+ }
+ });
+
+ mxEvent.addListener(img, mu, mouseHandler);
+ mxEvent.addListener(img, 'mouseout', mouseHandler);
+
+ return img;
+};
+
+/**
+ * Function: addCombo
+ *
+ * Adds and returns a new SELECT element using the given style. The element
+ * is placed inside a DIV with the mxToolbarComboContainer style classname.
+ *
+ * Parameters:
+ *
+ * style - Optional style classname. Default is mxToolbarCombo.
+ */
+mxToolbar.prototype.addCombo = function(style)
+{
+ var div = document.createElement('div');
+ div.style.display = 'inline';
+ div.className = 'mxToolbarComboContainer';
+
+ var select = document.createElement('select');
+ select.className = style || 'mxToolbarCombo';
+ div.appendChild(select);
+
+ this.container.appendChild(div);
+
+ return select;
+};
+
+/**
+ * Function: addCombo
+ *
+ * Adds and returns a new SELECT element using the given title as the
+ * default element. The selection is reset to this element after each
+ * change.
+ *
+ * Parameters:
+ *
+ * title - String that specifies the title of the default element.
+ * style - Optional style classname. Default is mxToolbarCombo.
+ */
+mxToolbar.prototype.addActionCombo = function(title, style)
+{
+ var select = document.createElement('select');
+ select.className = style || 'mxToolbarCombo';
+
+ this.addOption(select, title, null);
+
+ mxEvent.addListener(select, 'change', function(evt)
+ {
+ var value = select.options[select.selectedIndex];
+ select.selectedIndex = 0;
+ if (value.funct != null)
+ {
+ value.funct(evt);
+ }
+ });
+
+ this.container.appendChild(select);
+
+ return select;
+};
+
+/**
+ * Function: addOption
+ *
+ * Adds and returns a new OPTION element inside the given SELECT element.
+ * If the given value is a function then it is stored in the option's funct
+ * field.
+ *
+ * Parameters:
+ *
+ * combo - SELECT element that will contain the new entry.
+ * title - String that specifies the title of the option.
+ * value - Specifies the value associated with this option.
+ */
+mxToolbar.prototype.addOption = function(combo, title, value)
+{
+ var option = document.createElement('option');
+ mxUtils.writeln(option, title);
+
+ if (typeof(value) == 'function')
+ {
+ option.funct = value;
+ }
+ else
+ {
+ option.setAttribute('value', value);
+ }
+
+ combo.appendChild(option);
+
+ return option;
+};
+
+/**
+ * Function: addSwitchMode
+ *
+ * Adds a new selectable item to the toolbar. Only one switch mode item may
+ * be selected at a time. The currently selected item is the default item
+ * after a reset of the toolbar.
+ */
+mxToolbar.prototype.addSwitchMode = function(title, icon, funct, pressedIcon, style)
+{
+ var img = document.createElement('img');
+ img.initialClassName = style || 'mxToolbarMode';
+ img.className = img.initialClassName;
+ img.setAttribute('src', icon);
+ img.altIcon = pressedIcon;
+
+ if (title != null)
+ {
+ img.setAttribute('title', title);
+ }
+
+ mxEvent.addListener(img, 'click', mxUtils.bind(this, function(evt)
+ {
+ var tmp = this.selectedMode.altIcon;
+
+ if (tmp != null)
+ {
+ this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
+ this.selectedMode.setAttribute('src', tmp);
+ }
+ else
+ {
+ this.selectedMode.className = this.selectedMode.initialClassName;
+ }
+
+ if (this.updateDefaultMode)
+ {
+ this.defaultMode = img;
+ }
+
+ this.selectedMode = img;
+
+ var tmp = img.altIcon;
+
+ if (tmp != null)
+ {
+ img.altIcon = img.getAttribute('src');
+ img.setAttribute('src', tmp);
+ }
+ else
+ {
+ img.className = img.initialClassName+'Selected';
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.SELECT));
+ funct();
+ }));
+
+ this.container.appendChild(img);
+
+ if (this.defaultMode == null)
+ {
+ this.defaultMode = img;
+
+ // Function should fire only once so
+ // do not pass it with the select event
+ this.selectMode(img);
+ funct();
+ }
+
+ return img;
+};
+
+/**
+ * Function: addMode
+ *
+ * Adds a new item to the toolbar. The selection is typically reset after
+ * the item has been consumed, for example by adding a new vertex to the
+ * graph. The reset is not carried out if the item is double clicked.
+ *
+ * The function argument uses the following signature: funct(evt, cell) where
+ * evt is the native mouse event and cell is the cell under the mouse.
+ */
+mxToolbar.prototype.addMode = function(title, icon, funct, pressedIcon, style, toggle)
+{
+ toggle = (toggle != null) ? toggle : true;
+ var img = document.createElement((icon != null) ? 'img' : 'button');
+
+ img.initialClassName = style || 'mxToolbarMode';
+ img.className = img.initialClassName;
+ img.setAttribute('src', icon);
+ img.altIcon = pressedIcon;
+
+ if (title != null)
+ {
+ img.setAttribute('title', title);
+ }
+
+ if (this.enabled && toggle)
+ {
+ mxEvent.addListener(img, 'click', mxUtils.bind(this, function(evt)
+ {
+ this.selectMode(img, funct);
+ this.noReset = false;
+ }));
+ mxEvent.addListener(img, 'dblclick',
+ mxUtils.bind(this, function(evt)
+ {
+ this.selectMode(img, funct);
+ this.noReset = true;
+ })
+ );
+
+ if (this.defaultMode == null)
+ {
+ this.defaultMode = img;
+ this.defaultFunction = funct;
+ this.selectMode(img, funct);
+ }
+ }
+
+ this.container.appendChild(img);
+
+ return img;
+};
+
+/**
+ * Function: selectMode
+ *
+ * Resets the state of the previously selected mode and displays the given
+ * DOM node as selected. This function fires a select event with the given
+ * function as a parameter.
+ */
+mxToolbar.prototype.selectMode = function(domNode, funct)
+{
+ if (this.selectedMode != domNode)
+ {
+ if (this.selectedMode != null)
+ {
+ var tmp = this.selectedMode.altIcon;
+
+ if (tmp != null)
+ {
+ this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
+ this.selectedMode.setAttribute('src', tmp);
+ }
+ else
+ {
+ this.selectedMode.className = this.selectedMode.initialClassName;
+ }
+ }
+
+ this.selectedMode = domNode;
+ var tmp = this.selectedMode.altIcon;
+
+ if (tmp != null)
+ {
+ this.selectedMode.altIcon = this.selectedMode.getAttribute('src');
+ this.selectedMode.setAttribute('src', tmp);
+ }
+ else
+ {
+ this.selectedMode.className = this.selectedMode.initialClassName+'Selected';
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.SELECT, "function", funct));
+ }
+};
+
+/**
+ * Function: resetMode
+ *
+ * Selects the default mode and resets the state of the previously selected
+ * mode.
+ */
+mxToolbar.prototype.resetMode = function(forced)
+{
+ if ((forced || !this.noReset) &&
+ this.selectedMode != this.defaultMode)
+ {
+ // The last selected switch mode will be activated
+ // so the function was already executed and is
+ // no longer required here
+ this.selectMode(this.defaultMode, this.defaultFunction);
+ }
+};
+
+/**
+ * Function: addSeparator
+ *
+ * Adds the specifies image as a separator.
+ *
+ * Parameters:
+ *
+ * icon - URL of the separator icon.
+ */
+mxToolbar.prototype.addSeparator = function(icon)
+{
+ return this.addItem(null, icon, null);
+};
+
+/**
+ * Function: addBreak
+ *
+ * Adds a break to the container.
+ */
+mxToolbar.prototype.addBreak = function()
+{
+ mxUtils.br(this.container);
+};
+
+/**
+ * Function: addLine
+ *
+ * Adds a horizontal line to the container.
+ */
+mxToolbar.prototype.addLine = function()
+{
+ var hr = document.createElement('hr');
+
+ hr.style.marginRight = '6px';
+ hr.setAttribute('size', '1');
+
+ this.container.appendChild(hr);
+};
+
+/**
+ * Function: destroy
+ *
+ * Removes the toolbar and all its associated resources.
+ */
+mxToolbar.prototype.destroy = function ()
+{
+ mxEvent.release(this.container);
+ this.container = null;
+ this.defaultMode = null;
+ this.defaultFunction = null;
+ this.selectedMode = null;
+
+ if (this.menu != null)
+ {
+ this.menu.destroy();
+ }
+};
diff --git a/src/js/util/mxUndoManager.js b/src/js/util/mxUndoManager.js
new file mode 100644
index 0000000..2cb93cb
--- /dev/null
+++ b/src/js/util/mxUndoManager.js
@@ -0,0 +1,229 @@
+/**
+ * $Id: mxUndoManager.js,v 1.30 2011-10-05 06:39:19 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxUndoManager
+ *
+ * Implements a command history. When changing the graph model, an
+ * <mxUndoableChange> object is created at the start of the transaction (when
+ * model.beginUpdate is called). All atomic changes are then added to this
+ * object until the last model.endUpdate call, at which point the
+ * <mxUndoableEdit> is dispatched in an event, and added to the history inside
+ * <mxUndoManager>. This is done by an event listener in
+ * <mxEditor.installUndoHandler>.
+ *
+ * Each atomic change of the model is represented by an object (eg.
+ * <mxRootChange>, <mxChildChange>, <mxTerminalChange> etc) which contains the
+ * complete undo information. The <mxUndoManager> also listens to the
+ * <mxGraphView> and stores it's changes to the current root as insignificant
+ * undoable changes, so that drilling (step into, step up) is undone.
+ *
+ * This means when you execute an atomic change on the model, then change the
+ * current root on the view and click undo, the change of the root will be
+ * undone together with the change of the model so that the display represents
+ * the state at which the model was changed. However, these changes are not
+ * transmitted for sharing as they do not represent a state change.
+ *
+ * Example:
+ *
+ * When adding an undo manager to a graph, make sure to add it
+ * to the model and the view as well to maintain a consistent
+ * display across multiple undo/redo steps.
+ *
+ * (code)
+ * var undoManager = new mxUndoManager();
+ * var listener = function(sender, evt)
+ * {
+ * undoManager.undoableEditHappened(evt.getProperty('edit'));
+ * };
+ * graph.getModel().addListener(mxEvent.UNDO, listener);
+ * graph.getView().addListener(mxEvent.UNDO, listener);
+ * (end)
+ *
+ * The code creates a function that informs the undoManager
+ * of an undoable edit and binds it to the undo event of
+ * <mxGraphModel> and <mxGraphView> using
+ * <mxEventSource.addListener>.
+ *
+ * Event: mxEvent.CLEAR
+ *
+ * Fires after <clear> was invoked. This event has no properties.
+ *
+ * Event: mxEvent.UNDO
+ *
+ * Fires afer a significant edit was undone in <undo>. The <code>edit</code>
+ * property contains the <mxUndoableEdit> that was undone.
+ *
+ * Event: mxEvent.REDO
+ *
+ * Fires afer a significant edit was redone in <redo>. The <code>edit</code>
+ * property contains the <mxUndoableEdit> that was redone.
+ *
+ * Event: mxEvent.ADD
+ *
+ * Fires after an undoable edit was added to the history. The <code>edit</code>
+ * property contains the <mxUndoableEdit> that was added.
+ *
+ * Constructor: mxUndoManager
+ *
+ * Constructs a new undo manager with the given history size. If no history
+ * size is given, then a default size of 100 steps is used.
+ */
+function mxUndoManager(size)
+{
+ this.size = (size != null) ? size : 100;
+ this.clear();
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxUndoManager.prototype = new mxEventSource();
+mxUndoManager.prototype.constructor = mxUndoManager;
+
+/**
+ * Variable: size
+ *
+ * Maximum command history size. 0 means unlimited history. Default is
+ * 100.
+ */
+mxUndoManager.prototype.size = null;
+
+/**
+ * Variable: history
+ *
+ * Array that contains the steps of the command history.
+ */
+mxUndoManager.prototype.history = null;
+
+/**
+ * Variable: indexOfNextAdd
+ *
+ * Index of the element to be added next.
+ */
+mxUndoManager.prototype.indexOfNextAdd = 0;
+
+/**
+ * Function: isEmpty
+ *
+ * Returns true if the history is empty.
+ */
+mxUndoManager.prototype.isEmpty = function()
+{
+ return this.history.length == 0;
+};
+
+/**
+ * Function: clear
+ *
+ * Clears the command history.
+ */
+mxUndoManager.prototype.clear = function()
+{
+ this.history = [];
+ this.indexOfNextAdd = 0;
+ this.fireEvent(new mxEventObject(mxEvent.CLEAR));
+};
+
+/**
+ * Function: canUndo
+ *
+ * Returns true if an undo is possible.
+ */
+mxUndoManager.prototype.canUndo = function()
+{
+ return this.indexOfNextAdd > 0;
+};
+
+/**
+ * Function: undo
+ *
+ * Undoes the last change.
+ */
+mxUndoManager.prototype.undo = function()
+{
+ while (this.indexOfNextAdd > 0)
+ {
+ var edit = this.history[--this.indexOfNextAdd];
+ edit.undo();
+
+ if (edit.isSignificant())
+ {
+ this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+ break;
+ }
+ }
+};
+
+/**
+ * Function: canRedo
+ *
+ * Returns true if a redo is possible.
+ */
+mxUndoManager.prototype.canRedo = function()
+{
+ return this.indexOfNextAdd < this.history.length;
+};
+
+/**
+ * Function: redo
+ *
+ * Redoes the last change.
+ */
+mxUndoManager.prototype.redo = function()
+{
+ var n = this.history.length;
+
+ while (this.indexOfNextAdd < n)
+ {
+ var edit = this.history[this.indexOfNextAdd++];
+ edit.redo();
+
+ if (edit.isSignificant())
+ {
+ this.fireEvent(new mxEventObject(mxEvent.REDO, 'edit', edit));
+ break;
+ }
+ }
+};
+
+/**
+ * Function: undoableEditHappened
+ *
+ * Method to be called to add new undoable edits to the <history>.
+ */
+mxUndoManager.prototype.undoableEditHappened = function(undoableEdit)
+{
+ this.trim();
+
+ if (this.size > 0 &&
+ this.size == this.history.length)
+ {
+ this.history.shift();
+ }
+
+ this.history.push(undoableEdit);
+ this.indexOfNextAdd = this.history.length;
+ this.fireEvent(new mxEventObject(mxEvent.ADD, 'edit', undoableEdit));
+};
+
+/**
+ * Function: trim
+ *
+ * Removes all pending steps after <indexOfNextAdd> from the history,
+ * invoking die on each edit. This is called from <undoableEditHappened>.
+ */
+mxUndoManager.prototype.trim = function()
+{
+ if (this.history.length > this.indexOfNextAdd)
+ {
+ var edits = this.history.splice(this.indexOfNextAdd,
+ this.history.length - this.indexOfNextAdd);
+
+ for (var i = 0; i < edits.length; i++)
+ {
+ edits[i].die();
+ }
+ }
+};
diff --git a/src/js/util/mxUndoableEdit.js b/src/js/util/mxUndoableEdit.js
new file mode 100644
index 0000000..886c262
--- /dev/null
+++ b/src/js/util/mxUndoableEdit.js
@@ -0,0 +1,168 @@
+/**
+ * $Id: mxUndoableEdit.js,v 1.14 2010-09-15 16:58:51 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxUndoableEdit
+ *
+ * Implements a composite undoable edit.
+ *
+ * Constructor: mxUndoableEdit
+ *
+ * Constructs a new undoable edit for the given source.
+ */
+function mxUndoableEdit(source, significant)
+{
+ this.source = source;
+ this.changes = [];
+ this.significant = (significant != null) ? significant : true;
+};
+
+/**
+ * Variable: source
+ *
+ * Specifies the source of the edit.
+ */
+mxUndoableEdit.prototype.source = null;
+
+/**
+ * Variable: changes
+ *
+ * Array that contains the changes that make up this edit. The changes are
+ * expected to either have an undo and redo function, or an execute
+ * function. Default is an empty array.
+ */
+mxUndoableEdit.prototype.changes = null;
+
+/**
+ * Variable: significant
+ *
+ * Specifies if the undoable change is significant.
+ * Default is true.
+ */
+mxUndoableEdit.prototype.significant = null;
+
+/**
+ * Variable: undone
+ *
+ * Specifies if this edit has been undone. Default is false.
+ */
+mxUndoableEdit.prototype.undone = false;
+
+/**
+ * Variable: redone
+ *
+ * Specifies if this edit has been redone. Default is false.
+ */
+mxUndoableEdit.prototype.redone = false;
+
+/**
+ * Function: isEmpty
+ *
+ * Returns true if the this edit contains no changes.
+ */
+mxUndoableEdit.prototype.isEmpty = function()
+{
+ return this.changes.length == 0;
+};
+
+/**
+ * Function: isSignificant
+ *
+ * Returns <significant>.
+ */
+mxUndoableEdit.prototype.isSignificant = function()
+{
+ return this.significant;
+};
+
+/**
+ * Function: add
+ *
+ * Adds the specified change to this edit. The change is an object that is
+ * expected to either have an undo and redo, or an execute function.
+ */
+mxUndoableEdit.prototype.add = function(change)
+{
+ this.changes.push(change);
+};
+
+/**
+ * Function: notify
+ *
+ * Hook to notify any listeners of the changes after an <undo> or <redo>
+ * has been carried out. This implementation is empty.
+ */
+mxUndoableEdit.prototype.notify = function() { };
+
+/**
+ * Function: die
+ *
+ * Hook to free resources after the edit has been removed from the command
+ * history. This implementation is empty.
+ */
+mxUndoableEdit.prototype.die = function() { };
+
+/**
+ * Function: undo
+ *
+ * Undoes all changes in this edit.
+ */
+mxUndoableEdit.prototype.undo = function()
+{
+ if (!this.undone)
+ {
+ var count = this.changes.length;
+
+ for (var i = count - 1; i >= 0; i--)
+ {
+ var change = this.changes[i];
+
+ if (change.execute != null)
+ {
+ change.execute();
+ }
+ else if (change.undo != null)
+ {
+ change.undo();
+ }
+ }
+
+ this.undone = true;
+ this.redone = false;
+ }
+
+ this.notify();
+};
+
+/**
+ * Function: redo
+ *
+ * Redoes all changes in this edit.
+ */
+mxUndoableEdit.prototype.redo = function()
+{
+ if (!this.redone)
+ {
+ var count = this.changes.length;
+
+ for (var i = 0; i < count; i++)
+ {
+ var change = this.changes[i];
+
+ if (change.execute != null)
+ {
+ change.execute();
+ }
+ else if (change.redo != null)
+ {
+ change.redo();
+ }
+ }
+
+ this.undone = false;
+ this.redone = true;
+ }
+
+ this.notify();
+};
diff --git a/src/js/util/mxUrlConverter.js b/src/js/util/mxUrlConverter.js
new file mode 100644
index 0000000..764767f
--- /dev/null
+++ b/src/js/util/mxUrlConverter.js
@@ -0,0 +1,141 @@
+/**
+ * $Id: mxUrlConverter.js,v 1.3 2012-08-24 17:10:41 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ *
+ * Class: mxUrlConverter
+ *
+ * Converts relative and absolute URLs to absolute URLs with protocol and domain.
+ */
+var mxUrlConverter = function(root)
+{
+ /**
+ * Variable: enabled
+ *
+ * Specifies if the converter is enabled. Default is true.
+ */
+ var enabled = true;
+
+ /**
+ * Variable: baseUrl
+ *
+ * Specifies the base URL to be used as a prefix for relative URLs.
+ */
+ var baseUrl = null;
+
+ /**
+ * Variable: baseDomain
+ *
+ * Specifies the base domain to be used as a prefix for absolute URLs.
+ */
+ var baseDomain = null;
+
+ // Private helper function to update the base URL
+ var updateBaseUrl = function()
+ {
+ baseDomain = location.protocol + '//' + location.host;
+ baseUrl = baseDomain + location.pathname;
+ var tmp = baseUrl.lastIndexOf('/');
+
+ // Strips filename etc
+ if (tmp > 0)
+ {
+ baseUrl = baseUrl.substring(0, tmp + 1);
+ }
+ };
+
+ // Returns public interface
+ return {
+
+ /**
+ * Function: isEnabled
+ *
+ * Returns <enabled>.
+ */
+ isEnabled: function()
+ {
+ return enabled;
+ },
+
+ /**
+ * Function: setEnabled
+ *
+ * Sets <enabled>.
+ */
+ setEnabled: function(value)
+ {
+ enabled = value;
+ },
+
+ /**
+ * Function: getBaseUrl
+ *
+ * Returns <baseUrl>.
+ */
+ getBaseUrl: function()
+ {
+ return baseUrl;
+ },
+
+ /**
+ * Function: setBaseUrl
+ *
+ * Sets <baseUrl>.
+ */
+ setBaseUrl: function(value)
+ {
+ baseUrl = value;
+ },
+
+ /**
+ * Function: getBaseDomain
+ *
+ * Returns <baseDomain>.
+ */
+ getBaseDomain: function()
+ {
+ return baseUrl;
+ },
+
+ /**
+ * Function: setBaseDomain
+ *
+ * Sets <baseDomain>.
+ */
+ setBaseDomain: function(value)
+ {
+ baseUrl = value;
+ },
+
+ /**
+ * Function: convert
+ *
+ * Converts the given URL to an absolute URL with protol and domain.
+ * Relative URLs are first converted to absolute URLs.
+ */
+ convert: function(url)
+ {
+ if (enabled && url.indexOf('http://') != 0 && url.indexOf('https://') != 0 && url.indexOf('data:image') != 0)
+ {
+ if (baseUrl == null)
+ {
+ updateBaseUrl();
+ }
+
+ if (url.charAt(0) == '/')
+ {
+ url = baseDomain + url;
+ }
+ else
+ {
+ url = baseUrl + url;
+ }
+ }
+
+ return url;
+ }
+
+ };
+
+}; \ No newline at end of file
diff --git a/src/js/util/mxUtils.js b/src/js/util/mxUtils.js
new file mode 100644
index 0000000..34c0318
--- /dev/null
+++ b/src/js/util/mxUtils.js
@@ -0,0 +1,3920 @@
+/**
+ * $Id: mxUtils.js,v 1.297 2012-12-07 19:47:29 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxUtils =
+{
+ /**
+ * Class: mxUtils
+ *
+ * A singleton class that provides cross-browser helper methods.
+ * This is a global functionality. To access the functions in this
+ * class, use the global classname appended by the functionname.
+ * You may have to load chrome://global/content/contentAreaUtils.js
+ * to disable certain security restrictions in Mozilla for the <open>,
+ * <save>, <saveAs> and <copy> function.
+ *
+ * For example, the following code displays an error message:
+ *
+ * (code)
+ * mxUtils.error('Browser is not supported!', 200, false);
+ * (end)
+ *
+ * Variable: errorResource
+ *
+ * Specifies the resource key for the title of the error window. If the
+ * resource for this key does not exist then the value is used as
+ * the title. Default is 'error'.
+ */
+ errorResource: (mxClient.language != 'none') ? 'error' : '',
+
+ /**
+ * Variable: closeResource
+ *
+ * Specifies the resource key for the label of the close button. If the
+ * resource for this key does not exist then the value is used as
+ * the label. Default is 'close'.
+ */
+ closeResource: (mxClient.language != 'none') ? 'close' : '',
+
+ /**
+ * Variable: errorImage
+ *
+ * Defines the image used for error dialogs.
+ */
+ errorImage: mxClient.imageBasePath + '/error.gif',
+
+ /**
+ * Function: removeCursors
+ *
+ * Removes the cursors from the style of the given DOM node and its
+ * descendants.
+ *
+ * Parameters:
+ *
+ * element - DOM node to remove the cursor style from.
+ */
+ removeCursors: function(element)
+ {
+ if (element.style != null)
+ {
+ element.style.cursor = '';
+ }
+
+ var children = element.childNodes;
+
+ if (children != null)
+ {
+ var childCount = children.length;
+
+ for (var i = 0; i < childCount; i += 1)
+ {
+ mxUtils.removeCursors(children[i]);
+ }
+ }
+ },
+
+ /**
+ * Function: repaintGraph
+ *
+ * Normally not required, this contains the code to workaround a repaint
+ * issue and force a repaint of the graph container in AppleWebKit.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> to be repainted.
+ * pt - <mxPoint> where the dummy element should be placed.
+ */
+ repaintGraph: function(graph, pt)
+ {
+ if (mxClient.IS_GC || mxClient.IS_SF || mxClient.IS_OP)
+ {
+ var c = graph.container;
+
+ if (c != null && pt != null && (c.scrollLeft > 0 || c.scrollTop > 0))
+ {
+ var dummy = document.createElement('div');
+ dummy.style.position = 'absolute';
+ dummy.style.left = pt.x + 'px';
+ dummy.style.top = pt.y + 'px';
+ dummy.style.width = '1px';
+ dummy.style.height = '1px';
+
+ c.appendChild(dummy);
+ c.removeChild(dummy);
+ }
+ }
+ },
+
+ /**
+ * Function: getCurrentStyle
+ *
+ * Returns the current style of the specified element.
+ *
+ * Parameters:
+ *
+ * element - DOM node whose current style should be returned.
+ */
+ getCurrentStyle: function()
+ {
+ if (mxClient.IS_IE)
+ {
+ return function(element)
+ {
+ return (element != null) ? element.currentStyle : null;
+ };
+ }
+ else
+ {
+ return function(element)
+ {
+ return (element != null) ?
+ window.getComputedStyle(element, '') :
+ null;
+ };
+ }
+ }(),
+
+ /**
+ * Function: hasScrollbars
+ *
+ * Returns true if the overflow CSS property of the given node is either
+ * scroll or auto.
+ *
+ * Parameters:
+ *
+ * node - DOM node whose style should be checked for scrollbars.
+ */
+ hasScrollbars: function(node)
+ {
+ var style = mxUtils.getCurrentStyle(node);
+
+ return style != null && (style.overflow == 'scroll' || style.overflow == 'auto');
+ },
+
+ /**
+ * Function: bind
+ *
+ * Returns a wrapper function that locks the execution scope of the given
+ * function to the specified scope. Inside funct, the "this" keyword
+ * becomes a reference to that scope.
+ */
+ bind: function(scope, funct)
+ {
+ return function()
+ {
+ return funct.apply(scope, arguments);
+ };
+ },
+
+ /**
+ * Function: eval
+ *
+ * Evaluates the given expression using eval and returns the JavaScript
+ * object that represents the expression result. Supports evaluation of
+ * expressions that define functions and returns the function object for
+ * these expressions.
+ *
+ * Parameters:
+ *
+ * expr - A string that represents a JavaScript expression.
+ */
+ eval: function(expr)
+ {
+ var result = null;
+
+ if (expr.indexOf('function') >= 0)
+ {
+ try
+ {
+ eval('var _mxJavaScriptExpression='+expr);
+ result = _mxJavaScriptExpression;
+ // TODO: Use delete here?
+ _mxJavaScriptExpression = null;
+ }
+ catch (e)
+ {
+ mxLog.warn(e.message + ' while evaluating ' + expr);
+ }
+ }
+ else
+ {
+ try
+ {
+ result = eval(expr);
+ }
+ catch (e)
+ {
+ mxLog.warn(e.message + ' while evaluating ' + expr);
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * Function: findNode
+ *
+ * Returns the first node where attr equals value.
+ * This implementation does not use XPath.
+ */
+ findNode: function(node, attr, value)
+ {
+ var tmp = node.getAttribute(attr);
+
+ if (tmp != null && tmp == value)
+ {
+ return node;
+ }
+
+ node = node.firstChild;
+
+ while (node != null)
+ {
+ var result = mxUtils.findNode(node, attr, value);
+
+ if (result != null)
+ {
+ return result;
+ }
+
+ node = node.nextSibling;
+ }
+
+ return null;
+ },
+
+ /**
+ * Function: findNodeByAttribute
+ *
+ * Returns the first node where the given attribute matches the given value.
+ *
+ * Parameters:
+ *
+ * node - Root node where the search should start.
+ * attr - Name of the attribute to be checked.
+ * value - Value of the attribute to match.
+ */
+ findNodeByAttribute: function()
+ {
+ // Workaround for missing XPath support in IE9
+ if (document.documentMode >= 9)
+ {
+ return function(node, attr, value)
+ {
+ var result = null;
+
+ if (node != null)
+ {
+ if (node.nodeType == mxConstants.NODETYPE_ELEMENT && node.getAttribute(attr) == value)
+ {
+ result = node;
+ }
+ else
+ {
+ var child = node.firstChild;
+
+ while (child != null && result == null)
+ {
+ result = mxUtils.findNodeByAttribute(child, attr, value);
+ child = child.nextSibling;
+ }
+ }
+ }
+
+ return result;
+ };
+ }
+ else if (mxClient.IS_IE)
+ {
+ return function(node, attr, value)
+ {
+ if (node == null)
+ {
+ return null;
+ }
+ else
+ {
+ var expr = '//*[@' + attr + '=\'' + value + '\']';
+
+ return node.ownerDocument.selectSingleNode(expr);
+ }
+ };
+ }
+ else
+ {
+ return function(node, attr, value)
+ {
+ if (node == null)
+ {
+ return null;
+ }
+ else
+ {
+ var result = node.ownerDocument.evaluate(
+ '//*[@' + attr + '=\'' + value + '\']',
+ node.ownerDocument, null,
+ XPathResult.ANY_TYPE, null);
+
+ return result.iterateNext();
+ }
+ };
+ }
+ }(),
+
+ /**
+ * Function: getFunctionName
+ *
+ * Returns the name for the given function.
+ *
+ * Parameters:
+ *
+ * f - JavaScript object that represents a function.
+ */
+ getFunctionName: function(f)
+ {
+ var str = null;
+
+ if (f != null)
+ {
+ if (f.name != null)
+ {
+ str = f.name;
+ }
+ else
+ {
+ var tmp = f.toString();
+ var idx1 = 9;
+
+ while (tmp.charAt(idx1) == ' ')
+ {
+ idx1++;
+ }
+
+ var idx2 = tmp.indexOf('(', idx1);
+ str = tmp.substring(idx1, idx2);
+ }
+ }
+
+ return str;
+ },
+
+ /**
+ * Function: indexOf
+ *
+ * Returns the index of obj in array or -1 if the array does not contains
+ * the given object.
+ *
+ * Parameters:
+ *
+ * array - Array to check for the given obj.
+ * obj - Object to find in the given array.
+ */
+ indexOf: function(array, obj)
+ {
+ if (array != null && obj != null)
+ {
+ for (var i = 0; i < array.length; i++)
+ {
+ if (array[i] == obj)
+ {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ },
+
+ /**
+ * Function: remove
+ *
+ * Removes all occurrences of the given object in the given array or
+ * object. If there are multiple occurrences of the object, be they
+ * associative or as an array entry, all occurrences are removed from
+ * the array or deleted from the object. By removing the object from
+ * the array, all elements following the removed element are shifted
+ * by one step towards the beginning of the array.
+ *
+ * The length of arrays is not modified inside this function.
+ *
+ * Parameters:
+ *
+ * obj - Object to find in the given array.
+ * array - Array to check for the given obj.
+ */
+ remove: function(obj, array)
+ {
+ var result = null;
+
+ if (typeof(array) == 'object')
+ {
+ var index = mxUtils.indexOf(array, obj);
+
+ while (index >= 0)
+ {
+ array.splice(index, 1);
+ result = obj;
+ index = mxUtils.indexOf(array, obj);
+ }
+ }
+
+ for (var key in array)
+ {
+ if (array[key] == obj)
+ {
+ delete array[key];
+ result = obj;
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * Function: isNode
+ *
+ * Returns true if the given value is an XML node with the node name
+ * and if the optional attribute has the specified value.
+ *
+ * This implementation assumes that the given value is a DOM node if the
+ * nodeType property is numeric, that is, if isNaN returns false for
+ * value.nodeType.
+ *
+ * Parameters:
+ *
+ * value - Object that should be examined as a node.
+ * nodeName - String that specifies the node name.
+ * attributeName - Optional attribute name to check.
+ * attributeValue - Optional attribute value to check.
+ */
+ isNode: function(value, nodeName, attributeName, attributeValue)
+ {
+ if (value != null && !isNaN(value.nodeType) && (nodeName == null ||
+ value.nodeName.toLowerCase() == nodeName.toLowerCase()))
+ {
+ return attributeName == null ||
+ value.getAttribute(attributeName) == attributeValue;
+ }
+
+ return false;
+ },
+
+ /**
+ * Function: getChildNodes
+ *
+ * Returns an array of child nodes that are of the given node type.
+ *
+ * Parameters:
+ *
+ * node - Parent DOM node to return the children from.
+ * nodeType - Optional node type to return. Default is
+ * <mxConstants.NODETYPE_ELEMENT>.
+ */
+ getChildNodes: function(node, nodeType)
+ {
+ nodeType = nodeType || mxConstants.NODETYPE_ELEMENT;
+
+ var children = [];
+ var tmp = node.firstChild;
+
+ while (tmp != null)
+ {
+ if (tmp.nodeType == nodeType)
+ {
+ children.push(tmp);
+ }
+
+ tmp = tmp.nextSibling;
+ }
+
+ return children;
+ },
+
+ /**
+ * Function: createXmlDocument
+ *
+ * Returns a new, empty XML document.
+ */
+ createXmlDocument: function()
+ {
+ var doc = null;
+
+ if (document.implementation && document.implementation.createDocument)
+ {
+ doc = document.implementation.createDocument('', '', null);
+ }
+ else if (window.ActiveXObject)
+ {
+ doc = new ActiveXObject('Microsoft.XMLDOM');
+ }
+
+ return doc;
+ },
+
+ /**
+ * Function: parseXml
+ *
+ * Parses the specified XML string into a new XML document and returns the
+ * new document.
+ *
+ * Example:
+ *
+ * (code)
+ * var doc = mxUtils.parseXml(
+ * '<mxGraphModel><root><MyDiagram id="0"><mxCell/></MyDiagram>'+
+ * '<MyLayer id="1"><mxCell parent="0" /></MyLayer><MyObject id="2">'+
+ * '<mxCell style="strokeColor=blue;fillColor=red" parent="1" vertex="1">'+
+ * '<mxGeometry x="10" y="10" width="80" height="30" as="geometry"/>'+
+ * '</mxCell></MyObject></root></mxGraphModel>');
+ * (end)
+ *
+ * Parameters:
+ *
+ * xml - String that contains the XML data.
+ */
+ parseXml: function()
+ {
+ if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9))
+ {
+ return function(xml)
+ {
+ var result = mxUtils.createXmlDocument();
+
+ result.async = 'false';
+ result.loadXML(xml);
+
+ return result;
+ };
+ }
+ else
+ {
+ return function(xml)
+ {
+ var parser = new DOMParser();
+
+ return parser.parseFromString(xml, 'text/xml');
+ };
+ }
+ }(),
+
+ /**
+ * Function: clearSelection
+ *
+ * Clears the current selection in the page.
+ */
+ clearSelection: function()
+ {
+ if (document.selection)
+ {
+ return function()
+ {
+ document.selection.empty();
+ };
+ }
+ else if (window.getSelection)
+ {
+ return function()
+ {
+ window.getSelection().removeAllRanges();
+ };
+ }
+ }(),
+
+ /**
+ * Function: getPrettyXML
+ *
+ * Returns a pretty printed string that represents the XML tree for the
+ * given node. This method should only be used to print XML for reading,
+ * use <getXml> instead to obtain a string for processing.
+ *
+ * Parameters:
+ *
+ * node - DOM node to return the XML for.
+ * tab - Optional string that specifies the indentation for one level.
+ * Default is two spaces.
+ * indent - Optional string that represents the current indentation.
+ * Default is an empty string.
+ */
+ getPrettyXml: function(node, tab, indent)
+ {
+ var result = [];
+
+ if (node != null)
+ {
+ tab = tab || ' ';
+ indent = indent || '';
+
+ if (node.nodeType == mxConstants.NODETYPE_TEXT)
+ {
+ result.push(node.nodeValue);
+ }
+ else
+ {
+ result.push(indent + '<'+node.nodeName);
+
+ // Creates the string with the node attributes
+ // and converts all HTML entities in the values
+ var attrs = node.attributes;
+
+ if (attrs != null)
+ {
+ for (var i = 0; i < attrs.length; i++)
+ {
+ var val = mxUtils.htmlEntities(attrs[i].nodeValue);
+ result.push(' ' + attrs[i].nodeName +
+ '="' + val + '"');
+ }
+ }
+
+ // Recursively creates the XML string for each
+ // child nodes and appends it here with an
+ // indentation
+ var tmp = node.firstChild;
+
+ if (tmp != null)
+ {
+ result.push('>\n');
+
+ while (tmp != null)
+ {
+ result.push(mxUtils.getPrettyXml(
+ tmp, tab, indent + tab));
+ tmp = tmp.nextSibling;
+ }
+
+ result.push(indent + '</'+node.nodeName+'>\n');
+ }
+ else
+ {
+ result.push('/>\n');
+ }
+ }
+ }
+
+ return result.join('');
+ },
+
+ /**
+ * Function: removeWhitespace
+ *
+ * Removes the sibling text nodes for the given node that only consists
+ * of tabs, newlines and spaces.
+ *
+ * Parameters:
+ *
+ * node - DOM node whose siblings should be removed.
+ * before - Optional boolean that specifies the direction of the traversal.
+ */
+ removeWhitespace: function(node, before)
+ {
+ var tmp = (before) ? node.previousSibling : node.nextSibling;
+
+ while (tmp != null && tmp.nodeType == mxConstants.NODETYPE_TEXT)
+ {
+ var next = (before) ? tmp.previousSibling : tmp.nextSibling;
+ var text = mxUtils.getTextContent(tmp);
+
+ if (mxUtils.trim(text).length == 0)
+ {
+ tmp.parentNode.removeChild(tmp);
+ }
+
+ tmp = next;
+ }
+ },
+
+ /**
+ * Function: htmlEntities
+ *
+ * Replaces characters (less than, greater than, newlines and quotes) with
+ * their HTML entities in the given string and returns the result.
+ *
+ * Parameters:
+ *
+ * s - String that contains the characters to be converted.
+ * newline - If newlines should be replaced. Default is true.
+ */
+ htmlEntities: function(s, newline)
+ {
+ s = s || '';
+
+ s = s.replace(/&/g,'&amp;'); // 38 26
+ s = s.replace(/"/g,'&quot;'); // 34 22
+ s = s.replace(/\'/g,'&#39;'); // 39 27
+ s = s.replace(/</g,'&lt;'); // 60 3C
+ s = s.replace(/>/g,'&gt;'); // 62 3E
+
+ if (newline == null || newline)
+ {
+ s = s.replace(/\n/g, '&#xa;');
+ }
+
+ return s;
+ },
+
+ /**
+ * Function: isVml
+ *
+ * Returns true if the given node is in the VML namespace.
+ *
+ * Parameters:
+ *
+ * node - DOM node whose tag urn should be checked.
+ */
+ isVml: function(node)
+ {
+ return node != null && node.tagUrn == 'urn:schemas-microsoft-com:vml';
+ },
+
+ /**
+ * Function: getXml
+ *
+ * Returns the XML content of the specified node. For Internet Explorer,
+ * all \r\n\t[\t]* are removed from the XML string and the remaining \r\n
+ * are replaced by \n. All \n are then replaced with linefeed, or &#xa; if
+ * no linefeed is defined.
+ *
+ * Parameters:
+ *
+ * node - DOM node to return the XML for.
+ * linefeed - Optional string that linefeeds are converted into. Default is
+ * &#xa;
+ */
+ getXml: function(node, linefeed)
+ {
+ var xml = '';
+
+ if (node != null)
+ {
+ xml = node.xml;
+
+ if (xml == null)
+ {
+ if (node.innerHTML)
+ {
+ xml = node.innerHTML;
+ }
+ else
+ {
+ var xmlSerializer = new XMLSerializer();
+ xml = xmlSerializer.serializeToString(node);
+ }
+ }
+ else
+ {
+ xml = xml.replace(/\r\n\t[\t]*/g, '').
+ replace(/>\r\n/g, '>').
+ replace(/\r\n/g, '\n');
+ }
+ }
+
+ // Replaces linefeeds with HTML Entities.
+ linefeed = linefeed || '&#xa;';
+ xml = xml.replace(/\n/g, linefeed);
+
+ return xml;
+ },
+
+ /**
+ * Function: getTextContent
+ *
+ * Returns the text content of the specified node.
+ *
+ * Parameters:
+ *
+ * node - DOM node to return the text content for.
+ */
+ getTextContent: function(node)
+ {
+ var result = '';
+
+ if (node != null)
+ {
+ if (node.firstChild != null)
+ {
+ node = node.firstChild;
+ }
+
+ result = node.nodeValue || '';
+ }
+
+ return result;
+ },
+
+ /**
+ * Function: getInnerHtml
+ *
+ * Returns the inner HTML for the given node as a string or an empty string
+ * if no node was specified. The inner HTML is the text representing all
+ * children of the node, but not the node itself.
+ *
+ * Parameters:
+ *
+ * node - DOM node to return the inner HTML for.
+ */
+ getInnerHtml: function()
+ {
+ if (mxClient.IS_IE)
+ {
+ return function(node)
+ {
+ if (node != null)
+ {
+ return node.innerHTML;
+ }
+
+ return '';
+ };
+ }
+ else
+ {
+ return function(node)
+ {
+ if (node != null)
+ {
+ var serializer = new XMLSerializer();
+ return serializer.serializeToString(node);
+ }
+
+ return '';
+ };
+ }
+ }(),
+
+ /**
+ * Function: getOuterHtml
+ *
+ * Returns the outer HTML for the given node as a string or an empty
+ * string if no node was specified. The outer HTML is the text representing
+ * all children of the node including the node itself.
+ *
+ * Parameters:
+ *
+ * node - DOM node to return the outer HTML for.
+ */
+ getOuterHtml: function()
+ {
+ if (mxClient.IS_IE)
+ {
+ return function(node)
+ {
+ if (node != null)
+ {
+ if (node.outerHTML != null)
+ {
+ return node.outerHTML;
+ }
+ else
+ {
+ var tmp = [];
+ tmp.push('<'+node.nodeName);
+
+ var attrs = node.attributes;
+
+ if (attrs != null)
+ {
+ for (var i = 0; i < attrs.length; i++)
+ {
+ var value = attrs[i].nodeValue;
+
+ if (value != null && value.length > 0)
+ {
+ tmp.push(' ');
+ tmp.push(attrs[i].nodeName);
+ tmp.push('="');
+ tmp.push(value);
+ tmp.push('"');
+ }
+ }
+ }
+
+ if (node.innerHTML.length == 0)
+ {
+ tmp.push('/>');
+ }
+ else
+ {
+ tmp.push('>');
+ tmp.push(node.innerHTML);
+ tmp.push('</'+node.nodeName+'>');
+ }
+
+ return tmp.join('');
+ }
+ }
+
+ return '';
+ };
+ }
+ else
+ {
+ return function(node)
+ {
+ if (node != null)
+ {
+ var serializer = new XMLSerializer();
+ return serializer.serializeToString(node);
+ }
+
+ return '';
+ };
+ }
+ }(),
+
+ /**
+ * Function: write
+ *
+ * Creates a text node for the given string and appends it to the given
+ * parent. Returns the text node.
+ *
+ * Parameters:
+ *
+ * parent - DOM node to append the text node to.
+ * text - String representing the text to be added.
+ */
+ write: function(parent, text)
+ {
+ var doc = parent.ownerDocument;
+ var node = doc.createTextNode(text);
+
+ if (parent != null)
+ {
+ parent.appendChild(node);
+ }
+
+ return node;
+ },
+
+ /**
+ * Function: writeln
+ *
+ * Creates a text node for the given string and appends it to the given
+ * parent with an additional linefeed. Returns the text node.
+ *
+ * Parameters:
+ *
+ * parent - DOM node to append the text node to.
+ * text - String representing the text to be added.
+ */
+ writeln: function(parent, text)
+ {
+ var doc = parent.ownerDocument;
+ var node = doc.createTextNode(text);
+
+ if (parent != null)
+ {
+ parent.appendChild(node);
+ parent.appendChild(document.createElement('br'));
+ }
+
+ return node;
+ },
+
+ /**
+ * Function: br
+ *
+ * Appends a linebreak to the given parent and returns the linebreak.
+ *
+ * Parameters:
+ *
+ * parent - DOM node to append the linebreak to.
+ */
+ br: function(parent, count)
+ {
+ count = count || 1;
+ var br = null;
+
+ for (var i = 0; i < count; i++)
+ {
+ if (parent != null)
+ {
+ br = parent.ownerDocument.createElement('br');
+ parent.appendChild(br);
+ }
+ }
+
+ return br;
+ },
+
+ /**
+ * Function: button
+ *
+ * Returns a new button with the given level and function as an onclick
+ * event handler.
+ *
+ * (code)
+ * document.body.appendChild(mxUtils.button('Test', function(evt)
+ * {
+ * alert('Hello, World!');
+ * }));
+ * (end)
+ *
+ * Parameters:
+ *
+ * label - String that represents the label of the button.
+ * funct - Function to be called if the button is pressed.
+ * doc - Optional document to be used for creating the button. Default is the
+ * current document.
+ */
+ button: function(label, funct, doc)
+ {
+ doc = (doc != null) ? doc : document;
+
+ var button = doc.createElement('button');
+ mxUtils.write(button, label);
+
+ mxEvent.addListener(button, 'click', function(evt)
+ {
+ funct(evt);
+ });
+
+ return button;
+ },
+
+ /**
+ * Function: para
+ *
+ * Appends a new paragraph with the given text to the specified parent and
+ * returns the paragraph.
+ *
+ * Parameters:
+ *
+ * parent - DOM node to append the text node to.
+ * text - String representing the text for the new paragraph.
+ */
+ para: function(parent, text)
+ {
+ var p = document.createElement('p');
+ mxUtils.write(p, text);
+
+ if (parent != null)
+ {
+ parent.appendChild(p);
+ }
+
+ return p;
+ },
+
+ /**
+ * Function: linkAction
+ *
+ * Adds a hyperlink to the specified parent that invokes action on the
+ * specified editor.
+ *
+ * Parameters:
+ *
+ * parent - DOM node to contain the new link.
+ * text - String that is used as the link label.
+ * editor - <mxEditor> that will execute the action.
+ * action - String that defines the name of the action to be executed.
+ * pad - Optional left-padding for the link. Default is 0.
+ */
+ linkAction: function(parent, text, editor, action, pad)
+ {
+ return mxUtils.link(parent, text, function()
+ {
+ editor.execute(action);
+ }, pad);
+ },
+
+ /**
+ * Function: linkInvoke
+ *
+ * Adds a hyperlink to the specified parent that invokes the specified
+ * function on the editor passing along the specified argument. The
+ * function name is the name of a function of the editor instance,
+ * not an action name.
+ *
+ * Parameters:
+ *
+ * parent - DOM node to contain the new link.
+ * text - String that is used as the link label.
+ * editor - <mxEditor> instance to execute the function on.
+ * functName - String that represents the name of the function.
+ * arg - Object that represents the argument to the function.
+ * pad - Optional left-padding for the link. Default is 0.
+ */
+ linkInvoke: function(parent, text, editor, functName, arg, pad)
+ {
+ return mxUtils.link(parent, text, function()
+ {
+ editor[functName](arg);
+ }, pad);
+ },
+
+ /**
+ * Function: link
+ *
+ * Adds a hyperlink to the specified parent and invokes the given function
+ * when the link is clicked.
+ *
+ * Parameters:
+ *
+ * parent - DOM node to contain the new link.
+ * text - String that is used as the link label.
+ * funct - Function to execute when the link is clicked.
+ * pad - Optional left-padding for the link. Default is 0.
+ */
+ link: function(parent, text, funct, pad)
+ {
+ var a = document.createElement('span');
+
+ a.style.color = 'blue';
+ a.style.textDecoration = 'underline';
+ a.style.cursor = 'pointer';
+
+ if (pad != null)
+ {
+ a.style.paddingLeft = pad+'px';
+ }
+
+ mxEvent.addListener(a, 'click', funct);
+ mxUtils.write(a, text);
+
+ if (parent != null)
+ {
+ parent.appendChild(a);
+ }
+
+ return a;
+ },
+
+ /**
+ * Function: fit
+ *
+ * Makes sure the given node is inside the visible area of the window. This
+ * is done by setting the left and top in the style.
+ */
+ fit: function(node)
+ {
+ var left = parseInt(node.offsetLeft);
+ var width = parseInt(node.offsetWidth);
+
+ var b = document.body;
+ var d = document.documentElement;
+
+ var right = (b.scrollLeft || d.scrollLeft) +
+ (b.clientWidth || d.clientWidth);
+
+ if (left + width > right)
+ {
+ node.style.left = Math.max((b.scrollLeft || d.scrollLeft),
+ right - width)+'px';
+ }
+
+ var top = parseInt(node.offsetTop);
+ var height = parseInt(node.offsetHeight);
+
+ var bottom = (b.scrollTop || d.scrollTop) +
+ Math.max(b.clientHeight || 0, d.clientHeight);
+
+ if (top + height > bottom)
+ {
+ node.style.top = Math.max((b.scrollTop || d.scrollTop),
+ bottom - height)+'px';
+ }
+ },
+
+ /**
+ * Function: open
+ *
+ * Opens the specified file from the local filesystem and returns the
+ * contents of the file as a string. This implementation requires an
+ * ActiveX object in IE and special privileges in Firefox. Relative
+ * filenames are only supported in IE and will go onto the users'
+ * Desktop. You may have to load
+ * chrome://global/content/contentAreaUtils.js to disable certain
+ * security restrictions in Mozilla for this to work.
+ *
+ * See known-issues before using this function.
+ *
+ * Example:
+ * (code)
+ * var data = mxUtils.open('C:\\temp\\test.txt');
+ * mxUtils.alert('Data: '+data);
+ * (end)
+ *
+ * Parameters:
+ *
+ * filename - String representing the local file name.
+ */
+ open: function(filename)
+ {
+ // Requests required privileges in Firefox
+ if (mxClient.IS_NS)
+ {
+ try
+ {
+ netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+ }
+ catch (e)
+ {
+ mxUtils.alert('Permission to read file denied.');
+
+ return '';
+ }
+
+ var file = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
+ file.initWithPath(filename);
+
+ if (!file.exists())
+ {
+ mxUtils.alert('File not found.');
+ return '';
+ }
+
+ var is = Components.classes['@mozilla.org/network/file-input-stream;1'].createInstance(Components.interfaces.nsIFileInputStream);
+ is.init(file,0x01, 00004, null);
+
+ var sis = Components.classes['@mozilla.org/scriptableinputstream;1'].createInstance(Components.interfaces.nsIScriptableInputStream);
+ sis.init(is);
+
+ var output = sis.read(sis.available());
+
+ return output;
+ }
+ else
+ {
+ var activeXObject = new ActiveXObject('Scripting.FileSystemObject');
+
+ var newStream = activeXObject.OpenTextFile(filename, 1);
+ var text = newStream.readAll();
+ newStream.close();
+
+ return text;
+ }
+ },
+
+ /**
+ * Function: save
+ *
+ * Saves the specified content in the given file on the local file system.
+ * This implementation requires an ActiveX object in IE and special
+ * privileges in Firefox. Relative filenames are only supported in IE and
+ * will be loaded from the users' Desktop. You may have to load
+ * chrome://global/content/contentAreaUtils.js to disable certain
+ * security restrictions in Mozilla for this to work.
+ *
+ * See known-issues before using this function.
+ *
+ * Example:
+ *
+ * (code)
+ * var data = 'Hello, World!';
+ * mxUtils.save('C:\\test.txt', data);
+ * (end)
+ *
+ * Parameters:
+ *
+ * filename - String representing the local file name.
+ */
+ save: function(filename, content)
+ {
+ if (mxClient.IS_NS)
+ {
+ try
+ {
+ netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+ }
+ catch (e)
+ {
+ mxUtils.alert('Permission to write file denied.');
+ return;
+ }
+
+ var file = Components.classes['@mozilla.org/file/local;1'].createInstance(Components.interfaces.nsILocalFile);
+ file.initWithPath(filename);
+
+ if (!file.exists())
+ {
+ file.create(0x00, 0644);
+ }
+
+ var outputStream = Components.classes['@mozilla.org/network/file-output-stream;1'].createInstance(Components.interfaces.nsIFileOutputStream);
+
+ outputStream.init(file, 0x20 | 0x02,00004, null);
+ outputStream.write(content, content.length);
+ outputStream.flush();
+ outputStream.close();
+ }
+ else
+ {
+ var fso = new ActiveXObject('Scripting.FileSystemObject');
+
+ var file = fso.CreateTextFile(filename, true);
+ file.Write(content);
+ file.Close();
+ }
+ },
+
+ /**
+ * Function: saveAs
+ *
+ * Saves the specified content by displaying a dialog to save the content
+ * as a file on the local filesystem. This implementation does not use an
+ * ActiveX object in IE, however, it does require special privileges in
+ * Firefox. You may have to load
+ * chrome://global/content/contentAreaUtils.js to disable certain
+ * security restrictions in Mozilla for this to work.
+ *
+ * See known-issues before using this function. It is not recommended using
+ * this function in production environment as access to the filesystem
+ * cannot be guaranteed in Firefox. The following code is used in
+ * Firefox to try and enable saving to the filesystem.
+ *
+ * (code)
+ * netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+ * (end)
+ *
+ * Example:
+ *
+ * (code)
+ * mxUtils.saveAs('Hello, World!');
+ * (end)
+ *
+ * Parameters:
+ *
+ * content - String representing the file's content.
+ */
+ saveAs: function(content)
+ {
+ var iframe = document.createElement('iframe');
+ iframe.setAttribute('src', '');
+ iframe.style.visibility = 'hidden';
+ document.body.appendChild(iframe);
+
+ try
+ {
+ if (mxClient.IS_NS)
+ {
+ var doc = iframe.contentDocument;
+
+ doc.open();
+ doc.write(content);
+ doc.close();
+
+ try
+ {
+ netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+ // LATER: Remove existing HTML markup in file
+ iframe.focus();
+ saveDocument(doc);
+ }
+ catch (e)
+ {
+ mxUtils.alert('Permission to save document denied.');
+ }
+ }
+ else
+ {
+ var doc = iframe.contentWindow.document;
+ doc.write(content);
+ doc.execCommand('SaveAs', false, document.location);
+ }
+ }
+ finally
+ {
+ document.body.removeChild(iframe);
+ }
+ },
+
+ /**
+ * Function: copy
+ *
+ * Copies the specified content to the local clipboard. This implementation
+ * requires special privileges in Firefox. You may have to load
+ * chrome://global/content/contentAreaUtils.js to disable certain
+ * security restrictions in Mozilla for this to work.
+ *
+ * Parameters:
+ *
+ * content - String to be copied to the clipboard.
+ */
+ copy: function(content)
+ {
+ if (window.clipboardData)
+ {
+ window.clipboardData.setData('Text', content);
+ }
+ else
+ {
+ netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+
+ var clip = Components.classes['@mozilla.org/widget/clipboard;1']
+ .createInstance(Components.interfaces.nsIClipboard);
+
+ if (!clip)
+ {
+ return;
+ }
+
+ var trans = Components.classes['@mozilla.org/widget/transferable;1']
+ .createInstance(Components.interfaces.nsITransferable);
+
+ if (!trans)
+ {
+ return;
+ }
+
+ trans.addDataFlavor('text/unicode');
+ var str = Components.classes['@mozilla.org/supports-string;1']
+ .createInstance(Components.interfaces.nsISupportsString);
+
+ var copytext=content;
+ str.data=copytext;
+ trans.setTransferData('text/unicode', str, copytext.length*2);
+ var clipid=Components.interfaces.nsIClipboard;
+
+ clip.setData(trans,null,clipid.kGlobalClipboard);
+ }
+ },
+
+ /**
+ * Function: load
+ *
+ * Loads the specified URL *synchronously* and returns the <mxXmlRequest>.
+ * Throws an exception if the file cannot be loaded. See <mxUtils.get> for
+ * an asynchronous implementation.
+ *
+ * Example:
+ *
+ * (code)
+ * try
+ * {
+ * var req = mxUtils.load(filename);
+ * var root = req.getDocumentElement();
+ * // Process XML DOM...
+ * }
+ * catch (ex)
+ * {
+ * mxUtils.alert('Cannot load '+filename+': '+ex);
+ * }
+ * (end)
+ *
+ * Parameters:
+ *
+ * url - URL to get the data from.
+ */
+ load: function(url)
+ {
+ var req = new mxXmlRequest(url, null, 'GET', false);
+ req.send();
+
+ return req;
+ },
+
+ /**
+ * Function: get
+ *
+ * Loads the specified URL *asynchronously* and invokes the given functions
+ * depending on the request status. Returns the <mxXmlRequest> in use. Both
+ * functions take the <mxXmlRequest> as the only parameter. See
+ * <mxUtils.load> for a synchronous implementation.
+ *
+ * Example:
+ *
+ * (code)
+ * mxUtils.get(url, function(req)
+ * {
+ * var node = req.getDocumentElement();
+ * // Process XML DOM...
+ * });
+ * (end)
+ *
+ * So for example, to load a diagram into an existing graph model, the
+ * following code is used.
+ *
+ * (code)
+ * mxUtils.get(url, function(req)
+ * {
+ * var node = req.getDocumentElement();
+ * var dec = new mxCodec(node.ownerDocument);
+ * dec.decode(node, graph.getModel());
+ * });
+ * (end)
+ *
+ * Parameters:
+ *
+ * url - URL to get the data from.
+ * onload - Optional function to execute for a successful response.
+ * onerror - Optional function to execute on error.
+ */
+ get: function(url, onload, onerror)
+ {
+ return new mxXmlRequest(url, null, 'GET').send(onload, onerror);
+ },
+
+ /**
+ * Function: post
+ *
+ * Posts the specified params to the given URL *asynchronously* and invokes
+ * the given functions depending on the request status. Returns the
+ * <mxXmlRequest> in use. Both functions take the <mxXmlRequest> as the
+ * only parameter. Make sure to use encodeURIComponent for the parameter
+ * values.
+ *
+ * Example:
+ *
+ * (code)
+ * mxUtils.post(url, 'key=value', function(req)
+ * {
+ * mxUtils.alert('Ready: '+req.isReady()+' Status: '+req.getStatus());
+ * // Process req.getDocumentElement() using DOM API if OK...
+ * });
+ * (end)
+ *
+ * Parameters:
+ *
+ * url - URL to get the data from.
+ * params - Parameters for the post request.
+ * onload - Optional function to execute for a successful response.
+ * onerror - Optional function to execute on error.
+ */
+ post: function(url, params, onload, onerror)
+ {
+ return new mxXmlRequest(url, params).send(onload, onerror);
+ },
+
+ /**
+ * Function: submit
+ *
+ * Submits the given parameters to the specified URL using
+ * <mxXmlRequest.simulate> and returns the <mxXmlRequest>.
+ * Make sure to use encodeURIComponent for the parameter
+ * values.
+ *
+ * Parameters:
+ *
+ * url - URL to get the data from.
+ * params - Parameters for the form.
+ * doc - Document to create the form in.
+ * target - Target to send the form result to.
+ */
+ submit: function(url, params, doc, target)
+ {
+ return new mxXmlRequest(url, params).simulate(doc, target);
+ },
+
+ /**
+ * Function: loadInto
+ *
+ * Loads the specified URL *asynchronously* into the specified document,
+ * invoking onload after the document has been loaded. This implementation
+ * does not use <mxXmlRequest>, but the document.load method.
+ *
+ * Parameters:
+ *
+ * url - URL to get the data from.
+ * doc - The document to load the URL into.
+ * onload - Function to execute when the URL has been loaded.
+ */
+ loadInto: function(url, doc, onload)
+ {
+ if (mxClient.IS_IE)
+ {
+ doc.onreadystatechange = function ()
+ {
+ if (doc.readyState == 4)
+ {
+ onload();
+ }
+ };
+ }
+ else
+ {
+ doc.addEventListener('load', onload, false);
+ }
+
+ doc.load(url);
+ },
+
+ /**
+ * Function: getValue
+ *
+ * Returns the value for the given key in the given associative array or
+ * the given default value if the value is null.
+ *
+ * Parameters:
+ *
+ * array - Associative array that contains the value for the key.
+ * key - Key whose value should be returned.
+ * defaultValue - Value to be returned if the value for the given
+ * key is null.
+ */
+ getValue: function(array, key, defaultValue)
+ {
+ var value = (array != null) ? array[key] : null;
+
+ if (value == null)
+ {
+ value = defaultValue;
+ }
+
+ return value;
+ },
+
+ /**
+ * Function: getNumber
+ *
+ * Returns the numeric value for the given key in the given associative
+ * array or the given default value (or 0) if the value is null. The value
+ * is converted to a numeric value using the Number function.
+ *
+ * Parameters:
+ *
+ * array - Associative array that contains the value for the key.
+ * key - Key whose value should be returned.
+ * defaultValue - Value to be returned if the value for the given
+ * key is null. Default is 0.
+ */
+ getNumber: function(array, key, defaultValue)
+ {
+ var value = (array != null) ? array[key] : null;
+
+ if (value == null)
+ {
+ value = defaultValue || 0;
+ }
+
+ return Number(value);
+ },
+
+ /**
+ * Function: getColor
+ *
+ * Returns the color value for the given key in the given associative
+ * array or the given default value if the value is null. If the value
+ * is <mxConstants.NONE> then null is returned.
+ *
+ * Parameters:
+ *
+ * array - Associative array that contains the value for the key.
+ * key - Key whose value should be returned.
+ * defaultValue - Value to be returned if the value for the given
+ * key is null. Default is null.
+ */
+ getColor: function(array, key, defaultValue)
+ {
+ var value = (array != null) ? array[key] : null;
+
+ if (value == null)
+ {
+ value = defaultValue;
+ }
+ else if (value == mxConstants.NONE)
+ {
+ value = null;
+ }
+
+ return value;
+ },
+
+ /**
+ * Function: clone
+ *
+ * Recursively clones the specified object ignoring all fieldnames in the
+ * given array of transient fields. <mxObjectIdentity.FIELD_NAME> is always
+ * ignored by this function.
+ *
+ * Parameters:
+ *
+ * obj - Object to be cloned.
+ * transients - Optional array of strings representing the fieldname to be
+ * ignored.
+ * shallow - Optional boolean argument to specify if a shallow clone should
+ * be created, that is, one where all object references are not cloned or,
+ * in other words, one where only atomic (strings, numbers) values are
+ * cloned. Default is false.
+ */
+ clone: function(obj, transients, shallow)
+ {
+ shallow = (shallow != null) ? shallow : false;
+ var clone = null;
+
+ if (obj != null && typeof(obj.constructor) == 'function')
+ {
+ clone = new obj.constructor();
+
+ for (var i in obj)
+ {
+ if (i != mxObjectIdentity.FIELD_NAME && (transients == null ||
+ mxUtils.indexOf(transients, i) < 0))
+ {
+ if (!shallow && typeof(obj[i]) == 'object')
+ {
+ clone[i] = mxUtils.clone(obj[i]);
+ }
+ else
+ {
+ clone[i] = obj[i];
+ }
+ }
+ }
+ }
+
+ return clone;
+ },
+
+ /**
+ * Function: equalPoints
+ *
+ * Compares all mxPoints in the given lists.
+ *
+ * Parameters:
+ *
+ * a - Array of <mxPoints> to be compared.
+ * b - Array of <mxPoints> to be compared.
+ */
+ equalPoints: function(a, b)
+ {
+ if ((a == null && b != null) || (a != null && b == null) ||
+ (a != null && b != null && a.length != b.length))
+ {
+ return false;
+ }
+ else if (a != null && b != null)
+ {
+ for (var i = 0; i < a.length; i++)
+ {
+ if (a[i] == b[i] || (a[i] != null && !a[i].equals(b[i])))
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Function: equalEntries
+ *
+ * Compares all entries in the given dictionaries.
+ *
+ * Parameters:
+ *
+ * a - <mxRectangle> to be compared.
+ * b - <mxRectangle> to be compared.
+ */
+ equalEntries: function(a, b)
+ {
+ if ((a == null && b != null) || (a != null && b == null) ||
+ (a != null && b != null && a.length != b.length))
+ {
+ return false;
+ }
+ else if (a != null && b != null)
+ {
+ for (var key in a)
+ {
+ if (a[key] != b[key])
+ {
+ return false;
+ }
+ }
+ }
+
+ return true;
+ },
+
+ /**
+ * Function: extend
+ *
+ * Assigns a copy of the superclass prototype to the subclass prototype.
+ * Note that this does not call the constructor of the superclass at this
+ * point, the superclass constructor should be called explicitely in the
+ * subclass constructor. Below is an example.
+ *
+ * (code)
+ * MyGraph = function(container, model, renderHint, stylesheet)
+ * {
+ * mxGraph.call(this, container, model, renderHint, stylesheet);
+ * }
+ *
+ * mxUtils.extend(MyGraph, mxGraph);
+ * (end)
+ *
+ * Parameters:
+ *
+ * ctor - Constructor of the subclass.
+ * superCtor - Constructor of the superclass.
+ */
+ extend: function(ctor, superCtor)
+ {
+ var f = function() {};
+ f.prototype = superCtor.prototype;
+
+ ctor.prototype = new f();
+ ctor.prototype.constructor = ctor;
+ },
+
+ /**
+ * Function: toString
+ *
+ * Returns a textual representation of the specified object.
+ *
+ * Parameters:
+ *
+ * obj - Object to return the string representation for.
+ */
+ toString: function(obj)
+ {
+ var output = '';
+
+ for (var i in obj)
+ {
+ try
+ {
+ if (obj[i] == null)
+ {
+ output += i + ' = [null]\n';
+ }
+ else if (typeof(obj[i]) == 'function')
+ {
+ output += i + ' => [Function]\n';
+ }
+ else if (typeof(obj[i]) == 'object')
+ {
+ var ctor = mxUtils.getFunctionName(obj[i].constructor);
+ output += i + ' => [' + ctor + ']\n';
+ }
+ else
+ {
+ output += i + ' = ' + obj[i] + '\n';
+ }
+ }
+ catch (e)
+ {
+ output += i + '=' + e.message;
+ }
+ }
+
+ return output;
+ },
+
+ /**
+ * Function: toRadians
+ *
+ * Converts the given degree to radians.
+ */
+ toRadians: function(deg)
+ {
+ return Math.PI * deg / 180;
+ },
+
+ /**
+ * Function: arcToCurves
+ *
+ * Converts the given arc to a series of curves.
+ */
+ arcToCurves: function(x0, y0, r1, r2, angle, largeArcFlag, sweepFlag, x, y)
+ {
+ x -= x0;
+ y -= y0;
+
+ if (r1 === 0 || r2 === 0)
+ {
+ return result;
+ }
+
+ var fS = sweepFlag;
+ var psai = angle;
+ r1 = Math.abs(r1);
+ r2 = Math.abs(r2);
+ var ctx = -x / 2;
+ var cty = -y / 2;
+ var cpsi = Math.cos(psai * Math.PI / 180);
+ var spsi = Math.sin(psai * Math.PI / 180);
+ var rxd = cpsi * ctx + spsi * cty;
+ var ryd = -1 * spsi * ctx + cpsi * cty;
+ var rxdd = rxd * rxd;
+ var rydd = ryd * ryd;
+ var r1x = r1 * r1;
+ var r2y = r2 * r2;
+ var lamda = rxdd / r1x + rydd / r2y;
+ var sds;
+
+ if (lamda > 1)
+ {
+ r1 = Math.sqrt(lamda) * r1;
+ r2 = Math.sqrt(lamda) * r2;
+ sds = 0;
+ }
+ else
+ {
+ var seif = 1;
+
+ if (largeArcFlag === fS)
+ {
+ seif = -1;
+ }
+
+ sds = seif * Math.sqrt((r1x * r2y - r1x * rydd - r2y * rxdd) / (r1x * rydd + r2y * rxdd));
+ }
+
+ var txd = sds * r1 * ryd / r2;
+ var tyd = -1 * sds * r2 * rxd / r1;
+ var tx = cpsi * txd - spsi * tyd + x / 2;
+ var ty = spsi * txd + cpsi * tyd + y / 2;
+ var rad = Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1) - Math.atan2(0, 1);
+ var s1 = (rad >= 0) ? rad : 2 * Math.PI + rad;
+ rad = Math.atan2((-ryd - tyd) / r2, (-rxd - txd) / r1) - Math.atan2((ryd - tyd) / r2, (rxd - txd) / r1);
+ var dr = (rad >= 0) ? rad : 2 * Math.PI + rad;
+
+ if (fS == 0 && dr > 0)
+ {
+ dr -= 2 * Math.PI;
+ }
+ else if (fS != 0 && dr < 0)
+ {
+ dr += 2 * Math.PI;
+ }
+
+ var sse = dr * 2 / Math.PI;
+ var seg = Math.ceil(sse < 0 ? -1 * sse : sse);
+ var segr = dr / seg;
+ var t = 8/3 * Math.sin(segr / 4) * Math.sin(segr / 4) / Math.sin(segr / 2);
+ var cpsir1 = cpsi * r1;
+ var cpsir2 = cpsi * r2;
+ var spsir1 = spsi * r1;
+ var spsir2 = spsi * r2;
+ var mc = Math.cos(s1);
+ var ms = Math.sin(s1);
+ var x2 = -t * (cpsir1 * ms + spsir2 * mc);
+ var y2 = -t * (spsir1 * ms - cpsir2 * mc);
+ var x3 = 0;
+ var y3 = 0;
+
+ var result = [];
+
+ for (var n = 0; n < seg; ++n)
+ {
+ s1 += segr;
+ mc = Math.cos(s1);
+ ms = Math.sin(s1);
+
+ x3 = cpsir1 * mc - spsir2 * ms + tx;
+ y3 = spsir1 * mc + cpsir2 * ms + ty;
+ var dx = -t * (cpsir1 * ms + spsir2 * mc);
+ var dy = -t * (spsir1 * ms - cpsir2 * mc);
+
+ // CurveTo updates x0, y0 so need to restore it
+ var index = n * 6;
+ result[index] = Number(x2 + x0);
+ result[index + 1] = Number(y2 + y0);
+ result[index + 2] = Number(x3 - dx + x0);
+ result[index + 3] = Number(y3 - dy + y0);
+ result[index + 4] = Number(x3 + x0);
+ result[index + 5] = Number(y3 + y0);
+
+ x2 = x3 + dx;
+ y2 = y3 + dy;
+ }
+
+ return result;
+ },
+
+ /**
+ * Function: getBoundingBox
+ *
+ * Returns the bounding box for the rotated rectangle.
+ */
+ getBoundingBox: function(rect, rotation)
+ {
+ var result = null;
+
+ if (rect != null && rotation != null && rotation != 0)
+ {
+ var rad = mxUtils.toRadians(rotation);
+ var cos = Math.cos(rad);
+ var sin = Math.sin(rad);
+
+ var cx = new mxPoint(
+ rect.x + rect.width / 2,
+ rect.y + rect.height / 2);
+
+ var p1 = new mxPoint(rect.x, rect.y);
+ var p2 = new mxPoint(rect.x + rect.width, rect.y);
+ var p3 = new mxPoint(p2.x, rect.y + rect.height);
+ var p4 = new mxPoint(rect.x, p3.y);
+
+ p1 = mxUtils.getRotatedPoint(p1, cos, sin, cx);
+ p2 = mxUtils.getRotatedPoint(p2, cos, sin, cx);
+ p3 = mxUtils.getRotatedPoint(p3, cos, sin, cx);
+ p4 = mxUtils.getRotatedPoint(p4, cos, sin, cx);
+
+ result = new mxRectangle(p1.x, p1.y, 0, 0);
+ result.add(new mxRectangle(p2.x, p2.y, 0, 0));
+ result.add(new mxRectangle(p3.x, p3.y, 0, 0));
+ result.add(new mxRectangle(p4.x, p4.y, 0, 0));
+ }
+
+ return result;
+ },
+
+ /**
+ * Function: getRotatedPoint
+ *
+ * Rotates the given point by the given cos and sin.
+ */
+ getRotatedPoint: function(pt, cos, sin, c)
+ {
+ c = (c != null) ? c : new mxPoint();
+ var x = pt.x - c.x;
+ var y = pt.y - c.y;
+
+ var x1 = x * cos - y * sin;
+ var y1 = y * cos + x * sin;
+
+ return new mxPoint(x1 + c.x, y1 + c.y);
+ },
+
+ /**
+ * Returns an integer mask of the port constraints of the given map
+ * @param dict the style map to determine the port constraints for
+ * @param defaultValue Default value to return if the key is undefined.
+ * @return the mask of port constraint directions
+ *
+ * Parameters:
+ *
+ * terminal - <mxCelState> that represents the terminal.
+ * edge - <mxCellState> that represents the edge.
+ * source - Boolean that specifies if the terminal is the source terminal.
+ * defaultValue - Default value to be returned.
+ */
+ getPortConstraints: function(terminal, edge, source, defaultValue)
+ {
+ var value = mxUtils.getValue(terminal.style, mxConstants.STYLE_PORT_CONSTRAINT, null);
+
+ if (value == null)
+ {
+ return defaultValue;
+ }
+ else
+ {
+ var directions = value.toString();
+ var returnValue = mxConstants.DIRECTION_MASK_NONE;
+
+ if (directions.indexOf(mxConstants.DIRECTION_NORTH) >= 0)
+ {
+ returnValue |= mxConstants.DIRECTION_MASK_NORTH;
+ }
+ if (directions.indexOf(mxConstants.DIRECTION_WEST) >= 0)
+ {
+ returnValue |= mxConstants.DIRECTION_MASK_WEST;
+ }
+ if (directions.indexOf(mxConstants.DIRECTION_SOUTH) >= 0)
+ {
+ returnValue |= mxConstants.DIRECTION_MASK_SOUTH;
+ }
+ if (directions.indexOf(mxConstants.DIRECTION_EAST) >= 0)
+ {
+ returnValue |= mxConstants.DIRECTION_MASK_EAST;
+ }
+
+ return returnValue;
+ }
+ },
+
+ /**
+ * Function: reversePortConstraints
+ *
+ * Reverse the port constraint bitmask. For example, north | east
+ * becomes south | west
+ */
+ reversePortConstraints: function(constraint)
+ {
+ var result = 0;
+
+ result = (constraint & mxConstants.DIRECTION_MASK_WEST) << 3;
+ result |= (constraint & mxConstants.DIRECTION_MASK_NORTH) << 1;
+ result |= (constraint & mxConstants.DIRECTION_MASK_SOUTH) >> 1;
+ result |= (constraint & mxConstants.DIRECTION_MASK_EAST) >> 3;
+
+ return result;
+ },
+
+ /**
+ * Function: findNearestSegment
+ *
+ * Finds the index of the nearest segment on the given cell state for
+ * the specified coordinate pair.
+ */
+ findNearestSegment: function(state, x, y)
+ {
+ var index = -1;
+
+ if (state.absolutePoints.length > 0)
+ {
+ var last = state.absolutePoints[0];
+ var min = null;
+
+ for (var i = 1; i < state.absolutePoints.length; i++)
+ {
+ var current = state.absolutePoints[i];
+ var dist = mxUtils.ptSegDistSq(last.x, last.y,
+ current.x, current.y, x, y);
+
+ if (min == null || dist < min)
+ {
+ min = dist;
+ index = i - 1;
+ }
+
+ last = current;
+ }
+ }
+
+ return index;
+ },
+
+ /**
+ * Function: rectangleIntersectsSegment
+ *
+ * Returns true if the given rectangle intersects the given segment.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that represents the rectangle.
+ * p1 - <mxPoint> that represents the first point of the segment.
+ * p2 - <mxPoint> that represents the second point of the segment.
+ */
+ rectangleIntersectsSegment: function(bounds, p1, p2)
+ {
+ var top = bounds.y;
+ var left = bounds.x;
+ var bottom = top + bounds.height;
+ var right = left + bounds.width;
+
+ // Find min and max X for the segment
+ var minX = p1.x;
+ var maxX = p2.x;
+
+ if (p1.x > p2.x)
+ {
+ minX = p2.x;
+ maxX = p1.x;
+ }
+
+ // Find the intersection of the segment's and rectangle's x-projections
+ if (maxX > right)
+ {
+ maxX = right;
+ }
+
+ if (minX < left)
+ {
+ minX = left;
+ }
+
+ if (minX > maxX) // If their projections do not intersect return false
+ {
+ return false;
+ }
+
+ // Find corresponding min and max Y for min and max X we found before
+ var minY = p1.y;
+ var maxY = p2.y;
+ var dx = p2.x - p1.x;
+
+ if (Math.abs(dx) > 0.0000001)
+ {
+ var a = (p2.y - p1.y) / dx;
+ var b = p1.y - a * p1.x;
+ minY = a * minX + b;
+ maxY = a * maxX + b;
+ }
+
+ if (minY > maxY)
+ {
+ var tmp = maxY;
+ maxY = minY;
+ minY = tmp;
+ }
+
+ // Find the intersection of the segment's and rectangle's y-projections
+ if (maxY > bottom)
+ {
+ maxY = bottom;
+ }
+
+ if (minY < top)
+ {
+ minY = top;
+ }
+
+ if (minY > maxY) // If Y-projections do not intersect return false
+ {
+ return false;
+ }
+
+ return true;
+ },
+
+ /**
+ * Function: contains
+ *
+ * Returns true if the specified point (x, y) is contained in the given rectangle.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that represents the area.
+ * x - X-coordinate of the point.
+ * y - Y-coordinate of the point.
+ */
+ contains: function(bounds, x, y)
+ {
+ return (bounds.x <= x && bounds.x + bounds.width >= x &&
+ bounds.y <= y && bounds.y + bounds.height >= y);
+ },
+
+ /**
+ * Function: intersects
+ *
+ * Returns true if the two rectangles intersect.
+ *
+ * Parameters:
+ *
+ * a - <mxRectangle> to be checked for intersection.
+ * b - <mxRectangle> to be checked for intersection.
+ */
+ intersects: function(a, b)
+ {
+ var tw = a.width;
+ var th = a.height;
+ var rw = b.width;
+ var rh = b.height;
+
+ if (rw <= 0 || rh <= 0 || tw <= 0 || th <= 0)
+ {
+ return false;
+ }
+
+ var tx = a.x;
+ var ty = a.y;
+ var rx = b.x;
+ var ry = b.y;
+
+ rw += rx;
+ rh += ry;
+ tw += tx;
+ th += ty;
+
+ return ((rw < rx || rw > tx) &&
+ (rh < ry || rh > ty) &&
+ (tw < tx || tw > rx) &&
+ (th < ty || th > ry));
+ },
+
+ /**
+ * Function: intersects
+ *
+ * Returns true if the two rectangles intersect.
+ *
+ * Parameters:
+ *
+ * a - <mxRectangle> to be checked for intersection.
+ * b - <mxRectangle> to be checked for intersection.
+ */
+ intersectsHotspot: function(state, x, y, hotspot, min, max)
+ {
+ hotspot = (hotspot != null) ? hotspot : 1;
+ min = (min != null) ? min : 0;
+ max = (max != null) ? max : 0;
+
+ if (hotspot > 0)
+ {
+ var cx = state.getCenterX();
+ var cy = state.getCenterY();
+ var w = state.width;
+ var h = state.height;
+
+ var start = mxUtils.getValue(state.style, mxConstants.STYLE_STARTSIZE) * state.view.scale;
+
+ if (start > 0)
+ {
+ if (mxUtils.getValue(state.style,
+ mxConstants.STYLE_HORIZONTAL, true))
+ {
+ cy = state.y + start / 2;
+ h = start;
+ }
+ else
+ {
+ cx = state.x + start / 2;
+ w = start;
+ }
+ }
+
+ w = Math.max(min, w * hotspot);
+ h = Math.max(min, h * hotspot);
+
+ if (max > 0)
+ {
+ w = Math.min(w, max);
+ h = Math.min(h, max);
+ }
+
+ var rect = new mxRectangle(cx - w / 2, cy - h / 2, w, h);
+
+ return mxUtils.contains(rect, x, y);
+ }
+
+ return true;
+ },
+
+ /**
+ * Function: getOffset
+ *
+ * Returns the offset for the specified container as an <mxPoint>. The
+ * offset is the distance from the top left corner of the container to the
+ * top left corner of the document.
+ *
+ * Parameters:
+ *
+ * container - DOM node to return the offset for.
+ * scollOffset - Optional boolean to add the scroll offset of the document.
+ * Default is false.
+ */
+ getOffset: function(container, scrollOffset)
+ {
+ var offsetLeft = 0;
+ var offsetTop = 0;
+
+ if (scrollOffset != null && scrollOffset)
+ {
+ var b = document.body;
+ var d = document.documentElement;
+ offsetLeft += (b.scrollLeft || d.scrollLeft);
+ offsetTop += (b.scrollTop || d.scrollTop);
+ }
+
+ while (container.offsetParent)
+ {
+ offsetLeft += container.offsetLeft;
+ offsetTop += container.offsetTop;
+
+ container = container.offsetParent;
+ }
+
+ return new mxPoint(offsetLeft, offsetTop);
+ },
+
+ /**
+ * Function: getScrollOrigin
+ *
+ * Returns the top, left corner of the viewrect as an <mxPoint>.
+ */
+ getScrollOrigin: function(node)
+ {
+ var b = document.body;
+ var d = document.documentElement;
+ var sl = (b.scrollLeft || d.scrollLeft);
+ var st = (b.scrollTop || d.scrollTop);
+
+ var result = new mxPoint(sl, st);
+
+ while (node != null && node != b && node != d)
+ {
+ if (!isNaN(node.scrollLeft) && !isNaN(node.scrollTop))
+ {
+ result.x += node.scrollLeft;
+ result.y += node.scrollTop;
+ }
+
+ node = node.parentNode;
+ }
+
+ return result;
+ },
+
+ /**
+ * Function: convertPoint
+ *
+ * Converts the specified point (x, y) using the offset of the specified
+ * container and returns a new <mxPoint> with the result.
+ *
+ * Parameters:
+ *
+ * container - DOM node to use for the offset.
+ * x - X-coordinate of the point to be converted.
+ * y - Y-coordinate of the point to be converted.
+ */
+ convertPoint: function(container, x, y)
+ {
+ var origin = mxUtils.getScrollOrigin(container);
+ var offset = mxUtils.getOffset(container);
+
+ offset.x -= origin.x;
+ offset.y -= origin.y;
+
+ return new mxPoint(x - offset.x, y - offset.y);
+ },
+
+ /**
+ * Function: ltrim
+ *
+ * Strips all whitespaces from the beginning of the string.
+ * Without the second parameter, Javascript function will trim these
+ * characters:
+ *
+ * - " " (ASCII 32 (0x20)), an ordinary space
+ * - "\t" (ASCII 9 (0x09)), a tab
+ * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
+ * - "\r" (ASCII 13 (0x0D)), a carriage return
+ * - "\0" (ASCII 0 (0x00)), the NUL-byte
+ * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
+ */
+ ltrim: function(str, chars)
+ {
+ chars = chars || "\\s";
+
+ return str.replace(new RegExp("^[" + chars + "]+", "g"), "");
+ },
+
+ /**
+ * Function: rtrim
+ *
+ * Strips all whitespaces from the end of the string.
+ * Without the second parameter, Javascript function will trim these
+ * characters:
+ *
+ * - " " (ASCII 32 (0x20)), an ordinary space
+ * - "\t" (ASCII 9 (0x09)), a tab
+ * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
+ * - "\r" (ASCII 13 (0x0D)), a carriage return
+ * - "\0" (ASCII 0 (0x00)), the NUL-byte
+ * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
+ */
+ rtrim: function(str, chars)
+ {
+ chars = chars || "\\s";
+
+ return str.replace(new RegExp("[" + chars + "]+$", "g"), "");
+ },
+
+ /**
+ * Function: trim
+ *
+ * Strips all whitespaces from both end of the string.
+ * Without the second parameter, Javascript function will trim these
+ * characters:
+ *
+ * - " " (ASCII 32 (0x20)), an ordinary space
+ * - "\t" (ASCII 9 (0x09)), a tab
+ * - "\n" (ASCII 10 (0x0A)), a new line (line feed)
+ * - "\r" (ASCII 13 (0x0D)), a carriage return
+ * - "\0" (ASCII 0 (0x00)), the NUL-byte
+ * - "\x0B" (ASCII 11 (0x0B)), a vertical tab
+ */
+ trim: function(str, chars)
+ {
+ return mxUtils.ltrim(mxUtils.rtrim(str, chars), chars);
+ },
+
+ /**
+ * Function: isNumeric
+ *
+ * Returns true if the specified value is numeric, that is, if it is not
+ * null, not an empty string, not a HEX number and isNaN returns false.
+ *
+ * Parameters:
+ *
+ * str - String representing the possibly numeric value.
+ */
+ isNumeric: function(str)
+ {
+ return str != null && (str.length == null || (str.length > 0 &&
+ str.indexOf('0x') < 0) && str.indexOf('0X') < 0) && !isNaN(str);
+ },
+
+ /**
+ * Function: mod
+ *
+ * Returns the remainder of division of n by m. You should use this instead
+ * of the built-in operation as the built-in operation does not properly
+ * handle negative numbers.
+ */
+ mod: function(n, m)
+ {
+ return ((n % m) + m) % m;
+ },
+
+ /**
+ * Function: intersection
+ *
+ * Returns the intersection of two lines as an <mxPoint>.
+ *
+ * Parameters:
+ *
+ * x0 - X-coordinate of the first line's startpoint.
+ * y0 - X-coordinate of the first line's startpoint.
+ * x1 - X-coordinate of the first line's endpoint.
+ * y1 - Y-coordinate of the first line's endpoint.
+ * x2 - X-coordinate of the second line's startpoint.
+ * y2 - Y-coordinate of the second line's startpoint.
+ * x3 - X-coordinate of the second line's endpoint.
+ * y3 - Y-coordinate of the second line's endpoint.
+ */
+ intersection: function (x0, y0, x1, y1, x2, y2, x3, y3)
+ {
+ var denom = ((y3 - y2)*(x1 - x0)) - ((x3 - x2)*(y1 - y0));
+ var nume_a = ((x3 - x2)*(y0 - y2)) - ((y3 - y2)*(x0 - x2));
+ var nume_b = ((x1 - x0)*(y0 - y2)) - ((y1 - y0)*(x0 - x2));
+
+ var ua = nume_a / denom;
+ var ub = nume_b / denom;
+
+ if(ua >= 0.0 && ua <= 1.0 && ub >= 0.0 && ub <= 1.0)
+ {
+ // Get the intersection point
+ var intersectionX = x0 + ua*(x1 - x0);
+ var intersectionY = y0 + ua*(y1 - y0);
+
+ return new mxPoint(intersectionX, intersectionY);
+ }
+
+ // No intersection
+ return null;
+ },
+
+ /**
+ * Function: ptSeqDistSq
+ *
+ * Returns the square distance between a segment and a point.
+ *
+ * Parameters:
+ *
+ * x1 - X-coordinate of the startpoint of the segment.
+ * y1 - Y-coordinate of the startpoint of the segment.
+ * x2 - X-coordinate of the endpoint of the segment.
+ * y2 - Y-coordinate of the endpoint of the segment.
+ * px - X-coordinate of the point.
+ * py - Y-coordinate of the point.
+ */
+ ptSegDistSq: function(x1, y1, x2, y2, px, py)
+ {
+ x2 -= x1;
+ y2 -= y1;
+
+ px -= x1;
+ py -= y1;
+
+ var dotprod = px * x2 + py * y2;
+ var projlenSq;
+
+ if (dotprod <= 0.0)
+ {
+ projlenSq = 0.0;
+ }
+ else
+ {
+ px = x2 - px;
+ py = y2 - py;
+ dotprod = px * x2 + py * y2;
+
+ if (dotprod <= 0.0)
+ {
+ projlenSq = 0.0;
+ }
+ else
+ {
+ projlenSq = dotprod * dotprod / (x2 * x2 + y2 * y2);
+ }
+ }
+
+ var lenSq = px * px + py * py - projlenSq;
+
+ if (lenSq < 0)
+ {
+ lenSq = 0;
+ }
+
+ return lenSq;
+ },
+
+ /**
+ * Function: relativeCcw
+ *
+ * Returns 1 if the given point on the right side of the segment, 0 if its
+ * on the segment, and -1 if the point is on the left side of the segment.
+ *
+ * Parameters:
+ *
+ * x1 - X-coordinate of the startpoint of the segment.
+ * y1 - Y-coordinate of the startpoint of the segment.
+ * x2 - X-coordinate of the endpoint of the segment.
+ * y2 - Y-coordinate of the endpoint of the segment.
+ * px - X-coordinate of the point.
+ * py - Y-coordinate of the point.
+ */
+ relativeCcw: function(x1, y1, x2, y2, px, py)
+ {
+ x2 -= x1;
+ y2 -= y1;
+ px -= x1;
+ py -= y1;
+ var ccw = px * y2 - py * x2;
+
+ if (ccw == 0.0)
+ {
+ ccw = px * x2 + py * y2;
+
+ if (ccw > 0.0)
+ {
+ px -= x2;
+ py -= y2;
+ ccw = px * x2 + py * y2;
+
+ if (ccw < 0.0)
+ {
+ ccw = 0.0;
+ }
+ }
+ }
+
+ return (ccw < 0.0) ? -1 : ((ccw > 0.0) ? 1 : 0);
+ },
+
+ /**
+ * Function: animateChanges
+ *
+ * See <mxEffects.animateChanges>. This is for backwards compatibility and
+ * will be removed later.
+ */
+ animateChanges: function(graph, changes)
+ {
+ // LATER: Deprecated, remove this function
+ mxEffects.animateChanges.apply(this, arguments);
+ },
+
+ /**
+ * Function: cascadeOpacity
+ *
+ * See <mxEffects.cascadeOpacity>. This is for backwards compatibility and
+ * will be removed later.
+ */
+ cascadeOpacity: function(graph, cell, opacity)
+ {
+ mxEffects.cascadeOpacity.apply(this, arguments);
+ },
+
+ /**
+ * Function: fadeOut
+ *
+ * See <mxEffects.fadeOut>. This is for backwards compatibility and
+ * will be removed later.
+ */
+ fadeOut: function(node, from, remove, step, delay, isEnabled)
+ {
+ mxEffects.fadeOut.apply(this, arguments);
+ },
+
+ /**
+ * Function: setOpacity
+ *
+ * Sets the opacity of the specified DOM node to the given value in %.
+ *
+ * Parameters:
+ *
+ * node - DOM node to set the opacity for.
+ * value - Opacity in %. Possible values are between 0 and 100.
+ */
+ setOpacity: function(node, value)
+ {
+ if (mxUtils.isVml(node))
+ {
+ if (value >= 100)
+ {
+ node.style.filter = null;
+ }
+ else
+ {
+ // TODO: Why is the division by 5 needed in VML?
+ node.style.filter = 'alpha(opacity=' + (value/5) + ')';
+ }
+ }
+ else if (mxClient.IS_IE && (typeof(document.documentMode) === 'undefined' || document.documentMode < 9))
+ {
+ if (value >= 100)
+ {
+ node.style.filter = null;
+ }
+ else
+ {
+ node.style.filter = 'alpha(opacity=' + value + ')';
+ }
+ }
+ else
+ {
+ node.style.opacity = (value / 100);
+ }
+ },
+
+ /**
+ * Function: createImage
+ *
+ * Creates and returns an image (IMG node) or VML image (v:image) in IE6 in
+ * quirs mode.
+ *
+ * Parameters:
+ *
+ * src - URL that points to the image to be displayed.
+ */
+ createImage: function(src)
+ {
+ var imageNode = null;
+
+ if (mxClient.IS_IE6 && document.compatMode != 'CSS1Compat')
+ {
+ imageNode = document.createElement('v:image');
+ imageNode.setAttribute('src', src);
+ imageNode.style.borderStyle = 'none';
+ }
+ else
+ {
+ imageNode = document.createElement('img');
+ imageNode.setAttribute('src', src);
+ imageNode.setAttribute('border', '0');
+ }
+
+ return imageNode;
+ },
+
+ /**
+ * Function: sortCells
+ *
+ * Sorts the given cells according to the order in the cell hierarchy.
+ * Ascending is optional and defaults to true.
+ */
+ sortCells: function(cells, ascending)
+ {
+ ascending = (ascending != null) ? ascending : true;
+ var lookup = new mxDictionary();
+ cells.sort(function(o1, o2)
+ {
+ var p1 = lookup.get(o1);
+
+ if (p1 == null)
+ {
+ p1 = mxCellPath.create(o1).split(mxCellPath.PATH_SEPARATOR);
+ lookup.put(o1, p1);
+ }
+
+ var p2 = lookup.get(o2);
+
+ if (p2 == null)
+ {
+ p2 = mxCellPath.create(o2).split(mxCellPath.PATH_SEPARATOR);
+ lookup.put(o2, p2);
+ }
+
+ var comp = mxCellPath.compare(p1, p2);
+
+ return (comp == 0) ? 0 : (((comp > 0) == ascending) ? 1 : -1);
+ });
+
+ return cells;
+ },
+
+ /**
+ * Function: getStylename
+ *
+ * Returns the stylename in a style of the form [(stylename|key=value);] or
+ * an empty string if the given style does not contain a stylename.
+ *
+ * Parameters:
+ *
+ * style - String of the form [(stylename|key=value);].
+ */
+ getStylename: function(style)
+ {
+ if (style != null)
+ {
+ var pairs = style.split(';');
+ var stylename = pairs[0];
+
+ if (stylename.indexOf('=') < 0)
+ {
+ return stylename;
+ }
+ }
+
+ return '';
+ },
+
+ /**
+ * Function: getStylenames
+ *
+ * Returns the stylenames in a style of the form [(stylename|key=value);]
+ * or an empty array if the given style does not contain any stylenames.
+ *
+ * Parameters:
+ *
+ * style - String of the form [(stylename|key=value);].
+ */
+ getStylenames: function(style)
+ {
+ var result = [];
+
+ if (style != null)
+ {
+ var pairs = style.split(';');
+
+ for (var i = 0; i < pairs.length; i++)
+ {
+ if (pairs[i].indexOf('=') < 0)
+ {
+ result.push(pairs[i]);
+ }
+ }
+ }
+
+ return result;
+ },
+
+ /**
+ * Function: indexOfStylename
+ *
+ * Returns the index of the given stylename in the given style. This
+ * returns -1 if the given stylename does not occur (as a stylename) in the
+ * given style, otherwise it returns the index of the first character.
+ */
+ indexOfStylename: function(style, stylename)
+ {
+ if (style != null && stylename != null)
+ {
+ var tokens = style.split(';');
+ var pos = 0;
+
+ for (var i = 0; i < tokens.length; i++)
+ {
+ if (tokens[i] == stylename)
+ {
+ return pos;
+ }
+
+ pos += tokens[i].length + 1;
+ }
+ }
+
+ return -1;
+ },
+
+ /**
+ * Function: addStylename
+ *
+ * Adds the specified stylename to the given style if it does not already
+ * contain the stylename.
+ */
+ addStylename: function(style, stylename)
+ {
+ if (mxUtils.indexOfStylename(style, stylename) < 0)
+ {
+ if (style == null)
+ {
+ style = '';
+ }
+ else if (style.length > 0 && style.charAt(style.length - 1) != ';')
+ {
+ style += ';';
+ }
+
+ style += stylename;
+ }
+
+ return style;
+ },
+
+ /**
+ * Function: removeStylename
+ *
+ * Removes all occurrences of the specified stylename in the given style
+ * and returns the updated style. Trailing semicolons are not preserved.
+ */
+ removeStylename: function(style, stylename)
+ {
+ var result = [];
+
+ if (style != null)
+ {
+ var tokens = style.split(';');
+
+ for (var i = 0; i < tokens.length; i++)
+ {
+ if (tokens[i] != stylename)
+ {
+ result.push(tokens[i]);
+ }
+ }
+ }
+
+ return result.join(';');
+ },
+
+ /**
+ * Function: removeAllStylenames
+ *
+ * Removes all stylenames from the given style and returns the updated
+ * style.
+ */
+ removeAllStylenames: function(style)
+ {
+ var result = [];
+
+ if (style != null)
+ {
+ var tokens = style.split(';');
+
+ for (var i = 0; i < tokens.length; i++)
+ {
+ // Keeps the key, value assignments
+ if (tokens[i].indexOf('=') >= 0)
+ {
+ result.push(tokens[i]);
+ }
+ }
+ }
+
+ return result.join(';');
+ },
+
+ /**
+ * Function: setCellStyles
+ *
+ * Assigns the value for the given key in the styles of the given cells, or
+ * removes the key from the styles if the value is null.
+ *
+ * Parameters:
+ *
+ * model - <mxGraphModel> to execute the transaction in.
+ * cells - Array of <mxCells> to be updated.
+ * key - Key of the style to be changed.
+ * value - New value for the given key.
+ */
+ setCellStyles: function(model, cells, key, value)
+ {
+ if (cells != null && cells.length > 0)
+ {
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (cells[i] != null)
+ {
+ var style = mxUtils.setStyle(
+ model.getStyle(cells[i]),
+ key, value);
+ model.setStyle(cells[i], style);
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+ },
+
+ /**
+ * Function: setStyle
+ *
+ * Adds or removes the given key, value pair to the style and returns the
+ * new style. If value is null or zero length then the key is removed from
+ * the style. This is for cell styles, not for CSS styles.
+ *
+ * Parameters:
+ *
+ * style - String of the form [(stylename|key=value);].
+ * key - Key of the style to be changed.
+ * value - New value for the given key.
+ */
+ setStyle: function(style, key, value)
+ {
+ var isValue = value != null && (typeof(value.length) == 'undefined' || value.length > 0);
+
+ if (style == null || style.length == 0)
+ {
+ if (isValue)
+ {
+ style = key+'='+value;
+ }
+ }
+ else
+ {
+ var index = style.indexOf(key+'=');
+
+ if (index < 0)
+ {
+ if (isValue)
+ {
+ var sep = (style.charAt(style.length-1) == ';') ? '' : ';';
+ style = style + sep + key+'='+value;
+ }
+ }
+ else
+ {
+ var tmp = (isValue) ? (key + '=' + value) : '';
+ var cont = style.indexOf(';', index);
+
+ if (!isValue)
+ {
+ cont++;
+ }
+
+ style = style.substring(0, index) + tmp +
+ ((cont > index) ? style.substring(cont) : '');
+ }
+ }
+
+ return style;
+ },
+
+ /**
+ * Function: setCellStyleFlags
+ *
+ * Sets or toggles the flag bit for the given key in the cell's styles.
+ * If value is null then the flag is toggled.
+ *
+ * Example:
+ *
+ * (code)
+ * var cells = graph.getSelectionCells();
+ * mxUtils.setCellStyleFlags(graph.model,
+ * cells,
+ * mxConstants.STYLE_FONTSTYLE,
+ * mxConstants.FONT_BOLD);
+ * (end)
+ *
+ * Toggles the bold font style.
+ *
+ * Parameters:
+ *
+ * model - <mxGraphModel> that contains the cells.
+ * cells - Array of <mxCells> to change the style for.
+ * key - Key of the style to be changed.
+ * flag - Integer for the bit to be changed.
+ * value - Optional boolean value for the flag.
+ */
+ setCellStyleFlags: function(model, cells, key, flag, value)
+ {
+ if (cells != null && cells.length > 0)
+ {
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (cells[i] != null)
+ {
+ var style = mxUtils.setStyleFlag(
+ model.getStyle(cells[i]),
+ key, flag, value);
+ model.setStyle(cells[i], style);
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+ },
+
+ /**
+ * Function: setStyleFlag
+ *
+ * Sets or removes the given key from the specified style and returns the
+ * new style. If value is null then the flag is toggled.
+ *
+ * Parameters:
+ *
+ * style - String of the form [(stylename|key=value);].
+ * key - Key of the style to be changed.
+ * flag - Integer for the bit to be changed.
+ * value - Optional boolean value for the given flag.
+ */
+ setStyleFlag: function(style, key, flag, value)
+ {
+ if (style == null || style.length == 0)
+ {
+ if (value || value == null)
+ {
+ style = key+'='+flag;
+ }
+ else
+ {
+ style = key+'=0';
+ }
+ }
+ else
+ {
+ var index = style.indexOf(key+'=');
+
+ if (index < 0)
+ {
+ var sep = (style.charAt(style.length-1) == ';') ? '' : ';';
+
+ if (value || value == null)
+ {
+ style = style + sep + key + '=' + flag;
+ }
+ else
+ {
+ style = style + sep + key + '=0';
+ }
+ }
+ else
+ {
+ var cont = style.indexOf(';', index);
+ var tmp = '';
+
+ if (cont < 0)
+ {
+ tmp = style.substring(index+key.length+1);
+ }
+ else
+ {
+ tmp = style.substring(index+key.length+1, cont);
+ }
+
+ if (value == null)
+ {
+ tmp = parseInt(tmp) ^ flag;
+ }
+ else if (value)
+ {
+ tmp = parseInt(tmp) | flag;
+ }
+ else
+ {
+ tmp = parseInt(tmp) & ~flag;
+ }
+
+ style = style.substring(0, index) + key + '=' + tmp +
+ ((cont >= 0) ? style.substring(cont) : '');
+ }
+ }
+
+ return style;
+ },
+
+ /**
+ * Function: getSizeForString
+ *
+ * Returns an <mxRectangle> with the size (width and height in pixels) of
+ * the given string. The string may contain HTML markup. Newlines should be
+ * converted to <br> before calling this method.
+ *
+ * Example:
+ *
+ * (code)
+ * var label = graph.getLabel(cell).replace(/\n/g, "<br>");
+ * var size = graph.getSizeForString(label);
+ * (end)
+ *
+ * Parameters:
+ *
+ * text - String whose size should be returned.
+ * fontSize - Integer that specifies the font size in pixels. Default is
+ * <mxConstants.DEFAULT_FONTSIZE>.
+ * fontFamily - String that specifies the name of the font family. Default
+ * is <mxConstants.DEFAULT_FONTFAMILY>.
+ */
+ getSizeForString: function(text, fontSize, fontFamily)
+ {
+ var div = document.createElement('div');
+
+ // Sets the font size and family if non-default
+ div.style.fontSize = (fontSize || mxConstants.DEFAULT_FONTSIZE) + 'px';
+ div.style.fontFamily = fontFamily || mxConstants.DEFAULT_FONTFAMILY;
+
+ // Disables block layout and outside wrapping and hides the div
+ div.style.position = 'absolute';
+ div.style.display = 'inline';
+ div.style.visibility = 'hidden';
+
+ // Adds the text and inserts into DOM for updating of size
+ div.innerHTML = text;
+ document.body.appendChild(div);
+
+ // Gets the size and removes from DOM
+ var size = new mxRectangle(0, 0, div.offsetWidth, div.offsetHeight);
+ document.body.removeChild(div);
+
+ return size;
+ },
+
+ /**
+ * Function: getViewXml
+ */
+ getViewXml: function(graph, scale, cells, x0, y0)
+ {
+ x0 = (x0 != null) ? x0 : 0;
+ y0 = (y0 != null) ? y0 : 0;
+ scale = (scale != null) ? scale : 1;
+
+ if (cells == null)
+ {
+ var model = graph.getModel();
+ cells = [model.getRoot()];
+ }
+
+ var view = graph.getView();
+ var result = null;
+
+ // Disables events on the view
+ var eventsEnabled = view.isEventsEnabled();
+ view.setEventsEnabled(false);
+
+ // Workaround for label bounds not taken into account for image export.
+ // Creates a temporary draw pane which is used for rendering the text.
+ // Text rendering is required for finding the bounds of the labels.
+ var drawPane = view.drawPane;
+ var overlayPane = view.overlayPane;
+
+ if (graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ view.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+ view.canvas.appendChild(view.drawPane);
+
+ // Redirects cell overlays into temporary container
+ view.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+ view.canvas.appendChild(view.overlayPane);
+ }
+ else
+ {
+ view.drawPane = view.drawPane.cloneNode(false);
+ view.canvas.appendChild(view.drawPane);
+
+ // Redirects cell overlays into temporary container
+ view.overlayPane = view.overlayPane.cloneNode(false);
+ view.canvas.appendChild(view.overlayPane);
+ }
+
+ // Resets the translation
+ var translate = view.getTranslate();
+ view.translate = new mxPoint(x0, y0);
+
+ // Creates the temporary cell states in the view
+ var temp = new mxTemporaryCellStates(graph.getView(), scale, cells);
+
+ try
+ {
+ var enc = new mxCodec();
+ result = enc.encode(graph.getView());
+ }
+ finally
+ {
+ temp.destroy();
+ view.translate = translate;
+ view.canvas.removeChild(view.drawPane);
+ view.canvas.removeChild(view.overlayPane);
+ view.drawPane = drawPane;
+ view.overlayPane = overlayPane;
+ view.setEventsEnabled(eventsEnabled);
+ }
+
+ return result;
+ },
+
+ /**
+ * Function: getScaleForPageCount
+ *
+ * Returns the scale to be used for printing the graph with the given
+ * bounds across the specifies number of pages with the given format. The
+ * scale is always computed such that it given the given amount or fewer
+ * pages in the print output. See <mxPrintPreview> for an example.
+ *
+ * Parameters:
+ *
+ * pageCount - Specifies the number of pages in the print output.
+ * graph - <mxGraph> that should be printed.
+ * pageFormat - Optional <mxRectangle> that specifies the page format.
+ * Default is <mxConstants.PAGE_FORMAT_A4_PORTRAIT>.
+ * border - The border along each side of every page.
+ */
+ getScaleForPageCount: function(pageCount, graph, pageFormat, border)
+ {
+ if (pageCount < 1)
+ {
+ // We can't work with less than 1 page, return no scale
+ // change
+ return 1;
+ }
+
+ pageFormat = (pageFormat != null) ? pageFormat : mxConstants.PAGE_FORMAT_A4_PORTRAIT;
+ border = (border != null) ? border : 0;
+
+ var availablePageWidth = pageFormat.width - (border * 2);
+ var availablePageHeight = pageFormat.height - (border * 2);
+
+ // Work out the number of pages required if the
+ // graph is not scaled.
+ var graphBounds = graph.getGraphBounds().clone();
+ var sc = graph.getView().getScale();
+ graphBounds.width /= sc;
+ graphBounds.height /= sc;
+ var graphWidth = graphBounds.width;
+ var graphHeight = graphBounds.height;
+
+ var scale = 1;
+
+ // The ratio of the width/height for each printer page
+ var pageFormatAspectRatio = availablePageWidth / availablePageHeight;
+ // The ratio of the width/height for the graph to be printer
+ var graphAspectRatio = graphWidth / graphHeight;
+
+ // The ratio of horizontal pages / vertical pages for this
+ // graph to maintain its aspect ratio on this page format
+ var pagesAspectRatio = graphAspectRatio / pageFormatAspectRatio;
+
+ // Factor the square root of the page count up and down
+ // by the pages aspect ratio to obtain a horizontal and
+ // vertical page count that adds up to the page count
+ // and has the correct aspect ratio
+ var pageRoot = Math.sqrt(pageCount);
+ var pagesAspectRatioSqrt = Math.sqrt(pagesAspectRatio);
+ var numRowPages = pageRoot * pagesAspectRatioSqrt;
+ var numColumnPages = pageRoot / pagesAspectRatioSqrt;
+
+ // These value are rarely more than 2 rounding downs away from
+ // a total that meets the page count. In cases of one being less
+ // than 1 page, the other value can be too high and take more iterations
+ // In this case, just change that value to be the page count, since
+ // we know the other value is 1
+ if (numRowPages < 1 && numColumnPages > pageCount)
+ {
+ var scaleChange = numColumnPages / pageCount;
+ numColumnPages = pageCount;
+ numRowPages /= scaleChange;
+ }
+
+ if (numColumnPages < 1 && numRowPages > pageCount)
+ {
+ var scaleChange = numRowPages / pageCount;
+ numRowPages = pageCount;
+ numColumnPages /= scaleChange;
+ }
+
+ var currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages);
+
+ var numLoops = 0;
+
+ // Iterate through while the rounded up number of pages comes to
+ // a total greater than the required number
+ while (currentTotalPages > pageCount)
+ {
+ // Round down the page count (rows or columns) that is
+ // closest to its next integer down in percentage terms.
+ // i.e. Reduce the page total by reducing the total
+ // page area by the least possible amount
+
+ var roundRowDownProportion = Math.floor(numRowPages) / numRowPages;
+ var roundColumnDownProportion = Math.floor(numColumnPages) / numColumnPages;
+
+ // If the round down proportion is, work out the proportion to
+ // round down to 1 page less
+ if (roundRowDownProportion == 1)
+ {
+ roundRowDownProportion = Math.floor(numRowPages-1) / numRowPages;
+ }
+ if (roundColumnDownProportion == 1)
+ {
+ roundColumnDownProportion = Math.floor(numColumnPages-1) / numColumnPages;
+ }
+
+ // Check which rounding down is smaller, but in the case of very small roundings
+ // try the other dimension instead
+ var scaleChange = 1;
+
+ // Use the higher of the two values
+ if (roundRowDownProportion > roundColumnDownProportion)
+ {
+ scaleChange = roundRowDownProportion;
+ }
+ else
+ {
+ scaleChange = roundColumnDownProportion;
+ }
+
+ numRowPages = numRowPages * scaleChange;
+ numColumnPages = numColumnPages * scaleChange;
+ currentTotalPages = Math.ceil(numRowPages) * Math.ceil(numColumnPages);
+
+ numLoops++;
+
+ if (numLoops > 10)
+ {
+ break;
+ }
+ }
+
+ // Work out the scale from the number of row pages required
+ // The column pages will give the same value
+ var posterWidth = availablePageWidth * numRowPages;
+ scale = posterWidth / graphWidth;
+
+ // Allow for rounding errors
+ return scale * 0.99999;
+ },
+
+ /**
+ * Function: show
+ *
+ * Copies the styles and the markup from the graph's container into the
+ * given document and removes all cursor styles. The document is returned.
+ *
+ * This function should be called from within the document with the graph.
+ * If you experience problems with missing stylesheets in IE then try adding
+ * the domain to the trusted sites.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> to be copied.
+ * doc - Document where the new graph is created.
+ * x0 - X-coordinate of the graph view origin. Default is 0.
+ * y0 - Y-coordinate of the graph view origin. Default is 0.
+ */
+ show: function(graph, doc, x0, y0)
+ {
+ x0 = (x0 != null) ? x0 : 0;
+ y0 = (y0 != null) ? y0 : 0;
+
+ if (doc == null)
+ {
+ var wnd = window.open();
+ doc = wnd.document;
+ }
+ else
+ {
+ doc.open();
+ }
+
+ var bounds = graph.getGraphBounds();
+ var dx = -bounds.x + x0;
+ var dy = -bounds.y + y0;
+
+ // Needs a special way of creating the page so that no click is required
+ // to refresh the contents after the external CSS styles have been loaded.
+ // To avoid a click or programmatic refresh, the styleSheets[].cssText
+ // property is copied over from the original document.
+ if (mxClient.IS_IE)
+ {
+ var html = '<html>';
+ html += '<head>';
+
+ var base = document.getElementsByTagName('base');
+
+ for (var i = 0; i < base.length; i++)
+ {
+ html += base[i].outerHTML;
+ }
+
+ html += '<style>';
+
+ // Copies the stylesheets without having to load them again
+ for (var i = 0; i < document.styleSheets.length; i++)
+ {
+ try
+ {
+ html += document.styleSheets(i).cssText;
+ }
+ catch (e)
+ {
+ // ignore security exception
+ }
+ }
+
+ html += '</style>';
+
+ html += '</head>';
+ html += '<body>';
+
+ // Copies the contents of the graph container
+ html += graph.container.innerHTML;
+
+ html += '</body>';
+ html += '<html>';
+
+ doc.writeln(html);
+ doc.close();
+
+ // Makes sure the inner container is on the top, left
+ var node = doc.body.getElementsByTagName('DIV')[0];
+
+ if (node != null)
+ {
+ node.style.position = 'absolute';
+ node.style.left = dx + 'px';
+ node.style.top = dy + 'px';
+ }
+ }
+ else
+ {
+ doc.writeln('<html');
+ doc.writeln('<head>');
+
+ var base = document.getElementsByTagName('base');
+
+ for (var i=0; i<base.length; i++)
+ {
+ doc.writeln(mxUtils.getOuterHtml(base[i]));
+ }
+
+ var links = document.getElementsByTagName('link');
+
+ for (var i=0; i<links.length; i++)
+ {
+ doc.writeln(mxUtils.getOuterHtml(links[i]));
+ }
+
+ var styles = document.getElementsByTagName('style');
+
+ for (var i=0; i<styles.length; i++)
+ {
+ doc.writeln(mxUtils.getOuterHtml(styles[i]));
+ }
+
+ doc.writeln('</head>');
+ doc.writeln('</html>');
+ doc.close();
+
+ // Workaround for FF2 which has no body element in a document where
+ // the body has been added using document.write.
+ if (doc.body == null)
+ {
+ doc.documentElement.appendChild(doc.createElement('body'));
+ }
+
+ // Workaround for missing scrollbars in FF
+ doc.body.style.overflow = 'auto';
+
+ var node = graph.container.firstChild;
+
+ while (node != null)
+ {
+ var clone = node.cloneNode(true);
+ doc.body.appendChild(clone);
+ node = node.nextSibling;
+ }
+
+ // Shifts negative coordinates into visible space
+ var node = doc.getElementsByTagName('g')[0];
+
+ if (node != null)
+ {
+ node.setAttribute('transform', 'translate(' + dx + ',' + dy + ')');
+
+ // Updates the size of the SVG container
+ var root = node.ownerSVGElement;
+ root.setAttribute('width', bounds.width + Math.max(bounds.x, 0) + 3);
+ root.setAttribute('height', bounds.height + Math.max(bounds.y, 0) + 3);
+ }
+ }
+
+ mxUtils.removeCursors(doc.body);
+
+ return doc;
+ },
+
+ /**
+ * Function: printScreen
+ *
+ * Prints the specified graph using a new window and the built-in print
+ * dialog.
+ *
+ * This function should be called from within the document with the graph.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> to be printed.
+ */
+ printScreen: function(graph)
+ {
+ var wnd = window.open();
+ mxUtils.show(graph, wnd.document);
+
+ var print = function()
+ {
+ wnd.focus();
+ wnd.print();
+ wnd.close();
+ };
+
+ // Workaround for Google Chrome which needs a bit of a
+ // delay in order to render the SVG contents
+ if (mxClient.IS_GC)
+ {
+ wnd.setTimeout(print, 500);
+ }
+ else
+ {
+ print();
+ }
+ },
+
+ /**
+ * Function: popup
+ *
+ * Shows the specified text content in a new <mxWindow> or a new browser
+ * window if isInternalWindow is false.
+ *
+ * Parameters:
+ *
+ * content - String that specifies the text to be displayed.
+ * isInternalWindow - Optional boolean indicating if an mxWindow should be
+ * used instead of a new browser window. Default is false.
+ */
+ popup: function(content, isInternalWindow)
+ {
+ if (isInternalWindow)
+ {
+ var div = document.createElement('div');
+
+ div.style.overflow = 'scroll';
+ div.style.width = '636px';
+ div.style.height = '460px';
+
+ var pre = document.createElement('pre');
+ pre.innerHTML = mxUtils.htmlEntities(content, false).
+ replace(/\n/g,'<br>').replace(/ /g, '&nbsp;');
+
+ div.appendChild(pre);
+
+ var w = document.body.clientWidth;
+ var h = (document.body.clientHeight || document.documentElement.clientHeight);
+ var wnd = new mxWindow('Popup Window', div,
+ w/2-320, h/2-240, 640, 480, false, true);
+
+ wnd.setClosable(true);
+ wnd.setVisible(true);
+ }
+ else
+ {
+ // Wraps up the XML content in a textarea
+ if (mxClient.IS_NS)
+ {
+ var wnd = window.open();
+ wnd.document.writeln('<pre>'+mxUtils.htmlEntities(content)+'</pre');
+ wnd.document.close();
+ }
+ else
+ {
+ var wnd = window.open();
+ var pre = wnd.document.createElement('pre');
+ pre.innerHTML = mxUtils.htmlEntities(content, false).
+ replace(/\n/g,'<br>').replace(/ /g, '&nbsp;');
+ wnd.document.body.appendChild(pre);
+ }
+ }
+ },
+
+ /**
+ * Function: alert
+ *
+ * Displayss the given alert in a new dialog. This implementation uses the
+ * built-in alert function. This is used to display validation errors when
+ * connections cannot be changed or created.
+ *
+ * Parameters:
+ *
+ * message - String specifying the message to be displayed.
+ */
+ alert: function(message)
+ {
+ alert(message);
+ },
+
+ /**
+ * Function: prompt
+ *
+ * Displays the given message in a prompt dialog. This implementation uses
+ * the built-in prompt function.
+ *
+ * Parameters:
+ *
+ * message - String specifying the message to be displayed.
+ * defaultValue - Optional string specifying the default value.
+ */
+ prompt: function(message, defaultValue)
+ {
+ return prompt(message, defaultValue);
+ },
+
+ /**
+ * Function: confirm
+ *
+ * Displays the given message in a confirm dialog. This implementation uses
+ * the built-in confirm function.
+ *
+ * Parameters:
+ *
+ * message - String specifying the message to be displayed.
+ */
+ confirm: function(message)
+ {
+ return confirm(message);
+ },
+
+ /**
+ * Function: error
+ *
+ * Displays the given error message in a new <mxWindow> of the given width.
+ * If close is true then an additional close button is added to the window.
+ * The optional icon specifies the icon to be used for the window. Default
+ * is <mxUtils.errorImage>.
+ *
+ * Parameters:
+ *
+ * message - String specifying the message to be displayed.
+ * width - Integer specifying the width of the window.
+ * close - Optional boolean indicating whether to add a close button.
+ * icon - Optional icon for the window decoration.
+ */
+ error: function(message, width, close, icon)
+ {
+ var div = document.createElement('div');
+ div.style.padding = '20px';
+
+ var img = document.createElement('img');
+ img.setAttribute('src', icon || mxUtils.errorImage);
+ img.setAttribute('valign', 'bottom');
+ img.style.verticalAlign = 'middle';
+ div.appendChild(img);
+
+ div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
+ div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
+ div.appendChild(document.createTextNode('\u00a0')); // &nbsp;
+ mxUtils.write(div, message);
+
+ var w = document.body.clientWidth;
+ var h = (document.body.clientHeight || document.documentElement.clientHeight);
+ var warn = new mxWindow(mxResources.get(mxUtils.errorResource) ||
+ mxUtils.errorResource, div, (w-width)/2, h/4, width, null,
+ false, true);
+
+ if (close)
+ {
+ mxUtils.br(div);
+
+ var tmp = document.createElement('p');
+ var button = document.createElement('button');
+
+ if (mxClient.IS_IE)
+ {
+ button.style.cssText = 'float:right';
+ }
+ else
+ {
+ button.setAttribute('style', 'float:right');
+ }
+
+ mxEvent.addListener(button, 'click', function(evt)
+ {
+ warn.destroy();
+ });
+
+ mxUtils.write(button, mxResources.get(mxUtils.closeResource) ||
+ mxUtils.closeResource);
+
+ tmp.appendChild(button);
+ div.appendChild(tmp);
+
+ mxUtils.br(div);
+
+ warn.setClosable(true);
+ }
+
+ warn.setVisible(true);
+
+ return warn;
+ },
+
+ /**
+ * Function: makeDraggable
+ *
+ * Configures the given DOM element to act as a drag source for the
+ * specified graph. Returns a a new <mxDragSource>. If
+ * <mxDragSource.guideEnabled> is enabled then the x and y arguments must
+ * be used in funct to match the preview location.
+ *
+ * Example:
+ *
+ * (code)
+ * var funct = function(graph, evt, cell, x, y)
+ * {
+ * if (graph.canImportCell(cell))
+ * {
+ * var parent = graph.getDefaultParent();
+ * var vertex = null;
+ *
+ * graph.getModel().beginUpdate();
+ * try
+ * {
+ * vertex = graph.insertVertex(parent, null, 'Hello', x, y, 80, 30);
+ * }
+ * finally
+ * {
+ * graph.getModel().endUpdate();
+ * }
+ *
+ * graph.setSelectionCell(vertex);
+ * }
+ * }
+ *
+ * var img = document.createElement('img');
+ * img.setAttribute('src', 'editors/images/rectangle.gif');
+ * img.style.position = 'absolute';
+ * img.style.left = '0px';
+ * img.style.top = '0px';
+ * img.style.width = '16px';
+ * img.style.height = '16px';
+ *
+ * var dragImage = img.cloneNode(true);
+ * dragImage.style.width = '32px';
+ * dragImage.style.height = '32px';
+ * mxUtils.makeDraggable(img, graph, funct, dragImage);
+ * document.body.appendChild(img);
+ * (end)
+ *
+ * Parameters:
+ *
+ * element - DOM element to make draggable.
+ * graphF - <mxGraph> that acts as the drop target or a function that takes a
+ * mouse event and returns the current <mxGraph>.
+ * funct - Function to execute on a successful drop.
+ * dragElement - Optional DOM node to be used for the drag preview.
+ * dx - Optional horizontal offset between the cursor and the drag
+ * preview.
+ * dy - Optional vertical offset between the cursor and the drag
+ * preview.
+ * autoscroll - Optional boolean that specifies if autoscroll should be
+ * used. Default is mxGraph.autoscroll.
+ * scalePreview - Optional boolean that specifies if the preview element
+ * should be scaled according to the graph scale. If this is true, then
+ * the offsets will also be scaled. Default is false.
+ * highlightDropTargets - Optional boolean that specifies if dropTargets
+ * should be highlighted. Default is true.
+ * getDropTarget - Optional function to return the drop target for a given
+ * location (x, y). Default is mxGraph.getCellAt.
+ */
+ makeDraggable: function(element, graphF, funct, dragElement, dx, dy, autoscroll,
+ scalePreview, highlightDropTargets, getDropTarget)
+ {
+ var dragSource = new mxDragSource(element, funct);
+ dragSource.dragOffset = new mxPoint((dx != null) ? dx : 0,
+ (dy != null) ? dy : mxConstants.TOOLTIP_VERTICAL_OFFSET);
+ dragSource.autoscroll = autoscroll;
+
+ // Cannot enable this by default. This needs to be enabled in the caller
+ // if the funct argument uses the new x- and y-arguments.
+ dragSource.setGuidesEnabled(false);
+
+ if (highlightDropTargets != null)
+ {
+ dragSource.highlightDropTargets = highlightDropTargets;
+ }
+
+ // Overrides function to find drop target cell
+ if (getDropTarget != null)
+ {
+ dragSource.getDropTarget = getDropTarget;
+ }
+
+ // Overrides function to get current graph
+ dragSource.getGraphForEvent = function(evt)
+ {
+ return (typeof(graphF) == 'function') ? graphF(evt) : graphF;
+ };
+
+ // Translates switches into dragSource customizations
+ if (dragElement != null)
+ {
+ dragSource.createDragElement = function()
+ {
+ return dragElement.cloneNode(true);
+ };
+
+ if (scalePreview)
+ {
+ dragSource.createPreviewElement = function(graph)
+ {
+ var elt = dragElement.cloneNode(true);
+
+ var w = parseInt(elt.style.width);
+ var h = parseInt(elt.style.height);
+ elt.style.width = Math.round(w * graph.view.scale) + 'px';
+ elt.style.height = Math.round(h * graph.view.scale) + 'px';
+
+ return elt;
+ };
+ }
+ }
+
+ return dragSource;
+ }
+
+};
diff --git a/src/js/util/mxWindow.js b/src/js/util/mxWindow.js
new file mode 100644
index 0000000..e4cbcfc
--- /dev/null
+++ b/src/js/util/mxWindow.js
@@ -0,0 +1,1065 @@
+/**
+ * $Id: mxWindow.js,v 1.67 2012-10-11 17:18:51 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxWindow
+ *
+ * Basic window inside a document.
+ *
+ * Examples:
+ *
+ * Creating a simple window.
+ *
+ * (code)
+ * var tb = document.createElement('div');
+ * var wnd = new mxWindow('Title', tb, 100, 100, 200, 200, true, true);
+ * wnd.setVisible(true);
+ * (end)
+ *
+ * Creating a window that contains an iframe.
+ *
+ * (code)
+ * var frame = document.createElement('iframe');
+ * frame.setAttribute('width', '192px');
+ * frame.setAttribute('height', '172px');
+ * frame.setAttribute('src', 'http://www.example.com/');
+ * frame.style.backgroundColor = 'white';
+ *
+ * var w = document.body.clientWidth;
+ * var h = (document.body.clientHeight || document.documentElement.clientHeight);
+ * var wnd = new mxWindow('Title', frame, (w-200)/2, (h-200)/3, 200, 200);
+ * wnd.setVisible(true);
+ * (end)
+ *
+ * To limit the movement of a window, eg. to keep it from being moved beyond
+ * the top, left corner the following method can be overridden (recommended):
+ *
+ * (code)
+ * wnd.setLocation = function(x, y)
+ * {
+ * x = Math.max(0, x);
+ * y = Math.max(0, y);
+ * mxWindow.prototype.setLocation.apply(this, arguments);
+ * };
+ * (end)
+ *
+ * Or the following event handler can be used:
+ *
+ * (code)
+ * wnd.addListener(mxEvent.MOVE, function(e)
+ * {
+ * wnd.setLocation(Math.max(0, wnd.getX()), Math.max(0, wnd.getY()));
+ * });
+ * (end)
+ *
+ * Event: mxEvent.MOVE_START
+ *
+ * Fires before the window is moved. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.MOVE
+ *
+ * Fires while the window is being moved. The <code>event</code> property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.MOVE_END
+ *
+ * Fires after the window is moved. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE_START
+ *
+ * Fires before the window is resized. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE
+ *
+ * Fires while the window is being resized. The <code>event</code> property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.RESIZE_END
+ *
+ * Fires after the window is resized. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.MAXIMIZE
+ *
+ * Fires after the window is maximized. The <code>event</code> property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.MINIMIZE
+ *
+ * Fires after the window is minimized. The <code>event</code> property
+ * contains the corresponding mouse event.
+ *
+ * Event: mxEvent.NORMALIZE
+ *
+ * Fires after the window is normalized, that is, it returned from
+ * maximized or minimized state. The <code>event</code> property contains the
+ * corresponding mouse event.
+ *
+ * Event: mxEvent.ACTIVATE
+ *
+ * Fires after a window is activated. The <code>previousWindow</code> property
+ * contains the previous window. The event sender is the active window.
+ *
+ * Event: mxEvent.SHOW
+ *
+ * Fires after the window is shown. This event has no properties.
+ *
+ * Event: mxEvent.HIDE
+ *
+ * Fires after the window is hidden. This event has no properties.
+ *
+ * Event: mxEvent.CLOSE
+ *
+ * Fires before the window is closed. The <code>event</code> property contains
+ * the corresponding mouse event.
+ *
+ * Event: mxEvent.DESTROY
+ *
+ * Fires before the window is destroyed. This event has no properties.
+ *
+ * Constructor: mxWindow
+ *
+ * Constructs a new window with the given dimension and title to display
+ * the specified content. The window elements use the given style as a
+ * prefix for the classnames of the respective window elements, namely,
+ * the window title and window pane. The respective postfixes are appended
+ * to the given stylename as follows:
+ *
+ * style - Base style for the window.
+ * style+Title - Style for the window title.
+ * style+Pane - Style for the window pane.
+ *
+ * The default value for style is mxWindow, resulting in the following
+ * classnames for the window elements: mxWindow, mxWindowTitle and
+ * mxWindowPane.
+ *
+ * If replaceNode is given then the window replaces the given DOM node in
+ * the document.
+ *
+ * Parameters:
+ *
+ * title - String that represents the title of the new window.
+ * content - DOM node that is used as the window content.
+ * x - X-coordinate of the window location.
+ * y - Y-coordinate of the window location.
+ * width - Width of the window.
+ * height - Optional height of the window. Default is to match the height
+ * of the content at the specified width.
+ * minimizable - Optional boolean indicating if the window is minimizable.
+ * Default is true.
+ * movable - Optional boolean indicating if the window is movable. Default
+ * is true.
+ * replaceNode - Optional DOM node that the window should replace.
+ * style - Optional base classname for the window elements. Default is
+ * mxWindow.
+ */
+function mxWindow(title, content, x, y, width, height, minimizable, movable, replaceNode, style)
+{
+ if (content != null)
+ {
+ minimizable = (minimizable != null) ? minimizable : true;
+ this.content = content;
+ this.init(x, y, width, height, style);
+
+ this.installMaximizeHandler();
+ this.installMinimizeHandler();
+ this.installCloseHandler();
+ this.setMinimizable(minimizable);
+ this.setTitle(title);
+
+ if (movable == null || movable)
+ {
+ this.installMoveHandler();
+ }
+
+ if (replaceNode != null && replaceNode.parentNode != null)
+ {
+ replaceNode.parentNode.replaceChild(this.div, replaceNode);
+ }
+ else
+ {
+ document.body.appendChild(this.div);
+ }
+ }
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxWindow.prototype = new mxEventSource();
+mxWindow.prototype.constructor = mxWindow;
+
+/**
+ * Variable: closeImage
+ *
+ * URL of the image to be used for the close icon in the titlebar.
+ */
+mxWindow.prototype.closeImage = mxClient.imageBasePath + '/close.gif';
+
+/**
+ * Variable: minimizeImage
+ *
+ * URL of the image to be used for the minimize icon in the titlebar.
+ */
+mxWindow.prototype.minimizeImage = mxClient.imageBasePath + '/minimize.gif';
+
+/**
+ * Variable: normalizeImage
+ *
+ * URL of the image to be used for the normalize icon in the titlebar.
+ */
+mxWindow.prototype.normalizeImage = mxClient.imageBasePath + '/normalize.gif';
+
+/**
+ * Variable: maximizeImage
+ *
+ * URL of the image to be used for the maximize icon in the titlebar.
+ */
+mxWindow.prototype.maximizeImage = mxClient.imageBasePath + '/maximize.gif';
+
+/**
+ * Variable: normalizeImage
+ *
+ * URL of the image to be used for the resize icon.
+ */
+mxWindow.prototype.resizeImage = mxClient.imageBasePath + '/resize.gif';
+
+/**
+ * Variable: visible
+ *
+ * Boolean flag that represents the visible state of the window.
+ */
+mxWindow.prototype.visible = false;
+
+/**
+ * Variable: content
+ *
+ * Reference to the DOM node that represents the window content.
+ */
+mxWindow.prototype.content = false;
+
+/**
+ * Variable: minimumSize
+ *
+ * <mxRectangle> that specifies the minimum width and height of the window.
+ * Default is (50, 40).
+ */
+mxWindow.prototype.minimumSize = new mxRectangle(0, 0, 50, 40);
+
+/**
+ * Variable: title
+ *
+ * Reference to the DOM node (TD) that contains the title.
+ */
+mxWindow.prototype.title = false;
+
+/**
+ * Variable: content
+ *
+ * Reference to the DOM node that represents the window content.
+ */
+mxWindow.prototype.content = false;
+
+/**
+ * Variable: destroyOnClose
+ *
+ * Specifies if the window should be destroyed when it is closed. If this
+ * is false then the window is hidden using <setVisible>. Default is true.
+ */
+mxWindow.prototype.destroyOnClose = true;
+
+/**
+ * Function: init
+ *
+ * Initializes the DOM tree that represents the window.
+ */
+mxWindow.prototype.init = function(x, y, width, height, style)
+{
+ style = (style != null) ? style : 'mxWindow';
+
+ this.div = document.createElement('div');
+ this.div.className = style;
+ this.div.style.left = x+'px';
+ this.div.style.top = y+'px';
+ this.table = document.createElement('table');
+ this.table.className = style;
+
+ // Workaround for table size problems in FF
+ if (width != null)
+ {
+ if (!mxClient.IS_IE)
+ {
+ this.div.style.width = width+'px';
+ }
+
+ this.table.style.width = width+'px';
+ }
+
+ if (height != null)
+ {
+ if (!mxClient.IS_IE)
+ {
+ this.div.style.height = height+'px';
+ }
+
+ this.table.style.height = height+'px';
+ }
+
+ // Creates title row
+ var tbody = document.createElement('tbody');
+ var tr = document.createElement('tr');
+
+ this.title = document.createElement('td');
+ this.title.className = style+'Title';
+ tr.appendChild(this.title);
+ tbody.appendChild(tr);
+
+ // Creates content row and table cell
+ tr = document.createElement('tr');
+ this.td = document.createElement('td');
+ this.td.className = style+'Pane';
+
+ this.contentWrapper = document.createElement('div');
+ this.contentWrapper.className = style+'Pane';
+ this.contentWrapper.style.width = '100%';
+ this.contentWrapper.appendChild(this.content);
+
+ // Workaround for div around div restricts height
+ // of inner div if outerdiv has hidden overflow
+ if (mxClient.IS_IE || this.content.nodeName.toUpperCase() != 'DIV')
+ {
+ this.contentWrapper.style.height = '100%';
+ }
+
+ // Puts all content into the DOM
+ this.td.appendChild(this.contentWrapper);
+ tr.appendChild(this.td);
+ tbody.appendChild(tr);
+ this.table.appendChild(tbody);
+ this.div.appendChild(this.table);
+
+ // Puts the window on top of other windows when clicked
+ var activator = mxUtils.bind(this, function(evt)
+ {
+ this.activate();
+ });
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ mxEvent.addListener(this.title, md, activator);
+ mxEvent.addListener(this.table, md, activator);
+
+ this.hide();
+};
+
+/**
+ * Function: setTitle
+ *
+ * Sets the window title to the given string. HTML markup inside the title
+ * will be escaped.
+ */
+mxWindow.prototype.setTitle = function(title)
+{
+ // Removes all text content nodes (normally just one)
+ var child = this.title.firstChild;
+
+ while (child != null)
+ {
+ var next = child.nextSibling;
+
+ if (child.nodeType == mxConstants.NODETYPE_TEXT)
+ {
+ child.parentNode.removeChild(child);
+ }
+
+ child = next;
+ }
+
+ mxUtils.write(this.title, title || '');
+};
+
+/**
+ * Function: setScrollable
+ *
+ * Sets if the window contents should be scrollable.
+ */
+mxWindow.prototype.setScrollable = function(scrollable)
+{
+ // Workaround for hang in Presto 2.5.22 (Opera 10.5)
+ if (navigator.userAgent.indexOf('Presto/2.5') < 0)
+ {
+ if (scrollable)
+ {
+ this.contentWrapper.style.overflow = 'auto';
+ }
+ else
+ {
+ this.contentWrapper.style.overflow = 'hidden';
+ }
+ }
+};
+
+/**
+ * Function: activate
+ *
+ * Puts the window on top of all other windows.
+ */
+mxWindow.prototype.activate = function()
+{
+ if (mxWindow.activeWindow != this)
+ {
+ var style = mxUtils.getCurrentStyle(this.getElement());
+ var index = (style != null) ? style.zIndex : 3;
+
+ if (mxWindow.activeWindow)
+ {
+ var elt = mxWindow.activeWindow.getElement();
+
+ if (elt != null && elt.style != null)
+ {
+ elt.style.zIndex = index;
+ }
+ }
+
+ var previousWindow = mxWindow.activeWindow;
+ this.getElement().style.zIndex = parseInt(index) + 1;
+ mxWindow.activeWindow = this;
+
+ this.fireEvent(new mxEventObject(mxEvent.ACTIVATE, 'previousWindow', previousWindow));
+ }
+};
+
+/**
+ * Function: getElement
+ *
+ * Returuns the outermost DOM node that makes up the window.
+ */
+mxWindow.prototype.getElement = function()
+{
+ return this.div;
+};
+
+/**
+ * Function: fit
+ *
+ * Makes sure the window is inside the client area of the window.
+ */
+mxWindow.prototype.fit = function()
+{
+ mxUtils.fit(this.div);
+};
+
+/**
+ * Function: isResizable
+ *
+ * Returns true if the window is resizable.
+ */
+mxWindow.prototype.isResizable = function()
+{
+ if (this.resize != null)
+ {
+ return this.resize.style.display != 'none';
+ }
+
+ return false;
+};
+
+/**
+ * Function: setResizable
+ *
+ * Sets if the window should be resizable.
+ */
+mxWindow.prototype.setResizable = function(resizable)
+{
+ if (resizable)
+ {
+ if (this.resize == null)
+ {
+ this.resize = document.createElement('img');
+ this.resize.style.position = 'absolute';
+ this.resize.style.bottom = '2px';
+ this.resize.style.right = '2px';
+
+ this.resize.setAttribute('src', mxClient.imageBasePath + '/resize.gif');
+ this.resize.style.cursor = 'nw-resize';
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ mxEvent.addListener(this.resize, md, mxUtils.bind(this, function(evt)
+ {
+ this.activate();
+ var startX = mxEvent.getClientX(evt);
+ var startY = mxEvent.getClientY(evt);
+ var width = this.div.offsetWidth;
+ var height = this.div.offsetHeight;
+
+ // Adds a temporary pair of listeners to intercept
+ // the gesture event in the document
+ var dragHandler = mxUtils.bind(this, function(evt)
+ {
+ var dx = mxEvent.getClientX(evt) - startX;
+ var dy = mxEvent.getClientY(evt) - startY;
+
+ this.setSize(width + dx, height + dy);
+
+ this.fireEvent(new mxEventObject(mxEvent.RESIZE, 'event', evt));
+ mxEvent.consume(evt);
+ });
+
+ var dropHandler = mxUtils.bind(this, function(evt)
+ {
+ mxEvent.removeListener(document, mm, dragHandler);
+ mxEvent.removeListener(document, mu, dropHandler);
+
+ this.fireEvent(new mxEventObject(mxEvent.RESIZE_END, 'event', evt));
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(document, mm, dragHandler);
+ mxEvent.addListener(document, mu, dropHandler);
+
+ this.fireEvent(new mxEventObject(mxEvent.RESIZE_START, 'event', evt));
+ mxEvent.consume(evt);
+ }));
+
+ this.div.appendChild(this.resize);
+ }
+ else
+ {
+ this.resize.style.display = 'inline';
+ }
+ }
+ else if (this.resize != null)
+ {
+ this.resize.style.display = 'none';
+ }
+};
+
+/**
+ * Function: setSize
+ *
+ * Sets the size of the window.
+ */
+mxWindow.prototype.setSize = function(width, height)
+{
+ width = Math.max(this.minimumSize.width, width);
+ height = Math.max(this.minimumSize.height, height);
+
+ // Workaround for table size problems in FF
+ if (!mxClient.IS_IE)
+ {
+ this.div.style.width = width + 'px';
+ this.div.style.height = height + 'px';
+ }
+
+ this.table.style.width = width + 'px';
+ this.table.style.height = height + 'px';
+
+ if (!mxClient.IS_IE)
+ {
+ this.contentWrapper.style.height =
+ (this.div.offsetHeight - this.title.offsetHeight - 2)+'px';
+ }
+};
+
+/**
+ * Function: setMinimizable
+ *
+ * Sets if the window is minimizable.
+ */
+mxWindow.prototype.setMinimizable = function(minimizable)
+{
+ this.minimize.style.display = (minimizable) ? '' : 'none';
+};
+
+/**
+ * Function: getMinimumSize
+ *
+ * Returns an <mxRectangle> that specifies the size for the minimized window.
+ * A width or height of 0 means keep the existing width or height. This
+ * implementation returns the height of the window title and keeps the width.
+ */
+mxWindow.prototype.getMinimumSize = function()
+{
+ return new mxRectangle(0, 0, 0, this.title.offsetHeight);
+};
+
+/**
+ * Function: installMinimizeHandler
+ *
+ * Installs the event listeners required for minimizing the window.
+ */
+mxWindow.prototype.installMinimizeHandler = function()
+{
+ this.minimize = document.createElement('img');
+
+ this.minimize.setAttribute('src', this.minimizeImage);
+ this.minimize.setAttribute('align', 'right');
+ this.minimize.setAttribute('title', 'Minimize');
+ this.minimize.style.cursor = 'pointer';
+ this.minimize.style.marginRight = '1px';
+ this.minimize.style.display = 'none';
+
+ this.title.appendChild(this.minimize);
+
+ var minimized = false;
+ var maxDisplay = null;
+ var height = null;
+
+ var funct = mxUtils.bind(this, function(evt)
+ {
+ this.activate();
+
+ if (!minimized)
+ {
+ minimized = true;
+
+ this.minimize.setAttribute('src', this.normalizeImage);
+ this.minimize.setAttribute('title', 'Normalize');
+ this.contentWrapper.style.display = 'none';
+ maxDisplay = this.maximize.style.display;
+
+ this.maximize.style.display = 'none';
+ height = this.table.style.height;
+
+ var minSize = this.getMinimumSize();
+
+ if (minSize.height > 0)
+ {
+ if (!mxClient.IS_IE)
+ {
+ this.div.style.height = minSize.height + 'px';
+ }
+
+ this.table.style.height = minSize.height + 'px';
+ }
+
+ if (minSize.width > 0)
+ {
+ if (!mxClient.IS_IE)
+ {
+ this.div.style.width = minSize.width + 'px';
+ }
+
+ this.table.style.width = minSize.width + 'px';
+ }
+
+ if (this.resize != null)
+ {
+ this.resize.style.visibility = 'hidden';
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.MINIMIZE, 'event', evt));
+ }
+ else
+ {
+ minimized = false;
+
+ this.minimize.setAttribute('src', this.minimizeImage);
+ this.minimize.setAttribute('title', 'Minimize');
+ this.contentWrapper.style.display = ''; // default
+ this.maximize.style.display = maxDisplay;
+
+ if (!mxClient.IS_IE)
+ {
+ this.div.style.height = height;
+ }
+
+ this.table.style.height = height;
+
+ if (this.resize != null)
+ {
+ this.resize.style.visibility = '';
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));
+ }
+
+ mxEvent.consume(evt);
+ });
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ mxEvent.addListener(this.minimize, md, funct);
+};
+
+/**
+ * Function: setMaximizable
+ *
+ * Sets if the window is maximizable.
+ */
+mxWindow.prototype.setMaximizable = function(maximizable)
+{
+ this.maximize.style.display = (maximizable) ? '' : 'none';
+};
+
+/**
+ * Function: installMaximizeHandler
+ *
+ * Installs the event listeners required for maximizing the window.
+ */
+mxWindow.prototype.installMaximizeHandler = function()
+{
+ this.maximize = document.createElement('img');
+
+ this.maximize.setAttribute('src', this.maximizeImage);
+ this.maximize.setAttribute('align', 'right');
+ this.maximize.setAttribute('title', 'Maximize');
+ this.maximize.style.cursor = 'default';
+ this.maximize.style.marginLeft = '1px';
+ this.maximize.style.cursor = 'pointer';
+ this.maximize.style.display = 'none';
+
+ this.title.appendChild(this.maximize);
+
+ var maximized = false;
+ var x = null;
+ var y = null;
+ var height = null;
+ var width = null;
+
+ var funct = mxUtils.bind(this, function(evt)
+ {
+ this.activate();
+
+ if (this.maximize.style.display != 'none')
+ {
+ if (!maximized)
+ {
+ maximized = true;
+
+ this.maximize.setAttribute('src', this.normalizeImage);
+ this.maximize.setAttribute('title', 'Normalize');
+ this.contentWrapper.style.display = '';
+ this.minimize.style.visibility = 'hidden';
+
+ // Saves window state
+ x = parseInt(this.div.style.left);
+ y = parseInt(this.div.style.top);
+ height = this.table.style.height;
+ width = this.table.style.width;
+
+ this.div.style.left = '0px';
+ this.div.style.top = '0px';
+
+ if (!mxClient.IS_IE)
+ {
+ this.div.style.height = (document.body.clientHeight-2)+'px';
+ this.div.style.width = (document.body.clientWidth-2)+'px';
+ }
+
+ this.table.style.width = (document.body.clientWidth-2)+'px';
+ this.table.style.height = (document.body.clientHeight-2)+'px';
+
+ if (this.resize != null)
+ {
+ this.resize.style.visibility = 'hidden';
+ }
+
+ if (!mxClient.IS_IE)
+ {
+ var style = mxUtils.getCurrentStyle(this.contentWrapper);
+
+ if (style.overflow == 'auto' || this.resize != null)
+ {
+ this.contentWrapper.style.height =
+ (this.div.offsetHeight - this.title.offsetHeight - 2)+'px';
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.MAXIMIZE, 'event', evt));
+ }
+ else
+ {
+ maximized = false;
+
+ this.maximize.setAttribute('src', this.maximizeImage);
+ this.maximize.setAttribute('title', 'Maximize');
+ this.contentWrapper.style.display = '';
+ this.minimize.style.visibility = '';
+
+ // Restores window state
+ this.div.style.left = x+'px';
+ this.div.style.top = y+'px';
+
+ if (!mxClient.IS_IE)
+ {
+ this.div.style.height = height;
+ this.div.style.width = width;
+
+ var style = mxUtils.getCurrentStyle(this.contentWrapper);
+
+ if (style.overflow == 'auto' || this.resize != null)
+ {
+ this.contentWrapper.style.height =
+ (this.div.offsetHeight - this.title.offsetHeight - 2)+'px';
+ }
+ }
+
+ this.table.style.height = height;
+ this.table.style.width = width;
+
+ if (this.resize != null)
+ {
+ this.resize.style.visibility = '';
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.NORMALIZE, 'event', evt));
+ }
+
+ mxEvent.consume(evt);
+ }
+ });
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ mxEvent.addListener(this.maximize, md, funct);
+ mxEvent.addListener(this.title, 'dblclick', funct);
+};
+
+/**
+ * Function: installMoveHandler
+ *
+ * Installs the event listeners required for moving the window.
+ */
+mxWindow.prototype.installMoveHandler = function()
+{
+ this.title.style.cursor = 'move';
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ mxEvent.addListener(this.title, md, mxUtils.bind(this, function(evt)
+ {
+ var startX = mxEvent.getClientX(evt);
+ var startY = mxEvent.getClientY(evt);
+ var x = this.getX();
+ var y = this.getY();
+
+ // Adds a temporary pair of listeners to intercept
+ // the gesture event in the document
+ var dragHandler = mxUtils.bind(this, function(evt)
+ {
+ var dx = mxEvent.getClientX(evt) - startX;
+ var dy = mxEvent.getClientY(evt) - startY;
+ this.setLocation(x + dx, y + dy);
+ this.fireEvent(new mxEventObject(mxEvent.MOVE, 'event', evt));
+ mxEvent.consume(evt);
+ });
+
+ var dropHandler = mxUtils.bind(this, function(evt)
+ {
+ mxEvent.removeListener(document, mm, dragHandler);
+ mxEvent.removeListener(document, mu, dropHandler);
+
+ this.fireEvent(new mxEventObject(mxEvent.MOVE_END, 'event', evt));
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(document, mm, dragHandler);
+ mxEvent.addListener(document, mu, dropHandler);
+
+ this.fireEvent(new mxEventObject(mxEvent.MOVE_START, 'event', evt));
+ mxEvent.consume(evt);
+ }));
+};
+
+/**
+ * Function: setLocation
+ *
+ * Sets the upper, left corner of the window.
+ */
+ mxWindow.prototype.setLocation = function(x, y)
+ {
+ this.div.style.left = x + 'px';
+ this.div.style.top = y + 'px';
+ };
+
+/**
+ * Function: getX
+ *
+ * Returns the current position on the x-axis.
+ */
+mxWindow.prototype.getX = function()
+{
+ return parseInt(this.div.style.left);
+};
+
+/**
+ * Function: getY
+ *
+ * Returns the current position on the y-axis.
+ */
+mxWindow.prototype.getY = function()
+{
+ return parseInt(this.div.style.top);
+};
+
+/**
+ * Function: installCloseHandler
+ *
+ * Adds the <closeImage> as a new image node in <closeImg> and installs the
+ * <close> event.
+ */
+mxWindow.prototype.installCloseHandler = function()
+{
+ this.closeImg = document.createElement('img');
+
+ this.closeImg.setAttribute('src', this.closeImage);
+ this.closeImg.setAttribute('align', 'right');
+ this.closeImg.setAttribute('title', 'Close');
+ this.closeImg.style.marginLeft = '2px';
+ this.closeImg.style.cursor = 'pointer';
+ this.closeImg.style.display = 'none';
+
+ this.title.insertBefore(this.closeImg, this.title.firstChild);
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ mxEvent.addListener(this.closeImg, md, mxUtils.bind(this, function(evt)
+ {
+ this.fireEvent(new mxEventObject(mxEvent.CLOSE, 'event', evt));
+
+ if (this.destroyOnClose)
+ {
+ this.destroy();
+ }
+ else
+ {
+ this.setVisible(false);
+ }
+
+ mxEvent.consume(evt);
+ }));
+};
+
+/**
+ * Function: setImage
+ *
+ * Sets the image associated with the window.
+ *
+ * Parameters:
+ *
+ * image - URL of the image to be used.
+ */
+mxWindow.prototype.setImage = function(image)
+{
+ this.image = document.createElement('img');
+ this.image.setAttribute('src', image);
+ this.image.setAttribute('align', 'left');
+ this.image.style.marginRight = '4px';
+ this.image.style.marginLeft = '0px';
+ this.image.style.marginTop = '-2px';
+
+ this.title.insertBefore(this.image, this.title.firstChild);
+};
+
+/**
+ * Function: setClosable
+ *
+ * Sets the image associated with the window.
+ *
+ * Parameters:
+ *
+ * closable - Boolean specifying if the window should be closable.
+ */
+mxWindow.prototype.setClosable = function(closable)
+{
+ this.closeImg.style.display = (closable) ? '' : 'none';
+};
+
+/**
+ * Function: isVisible
+ *
+ * Returns true if the window is visible.
+ */
+mxWindow.prototype.isVisible = function()
+{
+ if (this.div != null)
+ {
+ return this.div.style.visibility != 'hidden';
+ }
+
+ return false;
+};
+
+/**
+ * Function: setVisible
+ *
+ * Shows or hides the window depending on the given flag.
+ *
+ * Parameters:
+ *
+ * visible - Boolean indicating if the window should be made visible.
+ */
+mxWindow.prototype.setVisible = function(visible)
+{
+ if (this.div != null && this.isVisible() != visible)
+ {
+ if (visible)
+ {
+ this.show();
+ }
+ else
+ {
+ this.hide();
+ }
+ }
+};
+
+/**
+ * Function: show
+ *
+ * Shows the window.
+ */
+mxWindow.prototype.show = function()
+{
+ this.div.style.visibility = '';
+ this.activate();
+
+ var style = mxUtils.getCurrentStyle(this.contentWrapper);
+
+ if (!mxClient.IS_IE && (style.overflow == 'auto' || this.resize != null))
+ {
+ this.contentWrapper.style.height =
+ (this.div.offsetHeight - this.title.offsetHeight - 2)+'px';
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.SHOW));
+};
+
+/**
+ * Function: hide
+ *
+ * Hides the window.
+ */
+mxWindow.prototype.hide = function()
+{
+ this.div.style.visibility = 'hidden';
+ this.fireEvent(new mxEventObject(mxEvent.HIDE));
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the window and removes all associated resources. Fires a
+ * <destroy> event prior to destroying the window.
+ */
+mxWindow.prototype.destroy = function()
+{
+ this.fireEvent(new mxEventObject(mxEvent.DESTROY));
+
+ if (this.div != null)
+ {
+ mxEvent.release(this.div);
+ this.div.parentNode.removeChild(this.div);
+ this.div = null;
+ }
+
+ this.title = null;
+ this.content = null;
+ this.contentWrapper = null;
+};
diff --git a/src/js/util/mxXmlCanvas2D.js b/src/js/util/mxXmlCanvas2D.js
new file mode 100644
index 0000000..499c71a
--- /dev/null
+++ b/src/js/util/mxXmlCanvas2D.js
@@ -0,0 +1,715 @@
+/**
+ * $Id: mxXmlCanvas2D.js,v 1.9 2012-04-24 13:56:56 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ *
+ * Class: mxXmlCanvas2D
+ *
+ * Implements a canvas to be used with <mxImageExport>. This canvas writes all
+ * calls as child nodes to the given root XML node.
+ *
+ * (code)
+ * var xmlDoc = mxUtils.createXmlDocument();
+ * var root = xmlDoc.createElement('output');
+ * xmlDoc.appendChild(root);
+ * var xmlCanvas = new mxXmlCanvas2D(root);
+ * (end)
+ *
+ * Constructor: mxXmlCanvas2D
+ *
+ * Constructs a XML canvas.
+ *
+ * Parameters:
+ *
+ * root - XML node for adding child nodes.
+ */
+var mxXmlCanvas2D = function(root)
+{
+ /**
+ * Variable: converter
+ *
+ * Holds the <mxUrlConverter> to convert image URLs.
+ */
+ var converter = new mxUrlConverter();
+
+ /**
+ * Variable: compressed
+ *
+ * Specifies if the output should be compressed by removing redundant calls.
+ * Default is true.
+ */
+ var compressed = true;
+
+ /**
+ * Variable: textEnabled
+ *
+ * Specifies if text output should be enabled. Default is true.
+ */
+ var textEnabled = true;
+
+ // Private reference to the owner document
+ var doc = root.ownerDocument;
+
+ // Implements stack for save/restore
+ var stack = [];
+
+ // Implements state for redundancy checks
+ var state =
+ {
+ alpha: 1,
+ dashed: false,
+ strokewidth: 1,
+ fontsize: mxConstants.DEFAULT_FONTSIZE,
+ fontfamily: mxConstants.DEFAULT_FONTFAMILY,
+ fontcolor: '#000000'
+ };
+
+ // Private helper function set set precision to 2
+ var f2 = function(x)
+ {
+ return Math.round(parseFloat(x) * 100) / 100;
+ };
+
+ // Returns public interface
+ return {
+
+ /**
+ * Function: getConverter
+ *
+ * Returns <converter>.
+ */
+ getConverter: function()
+ {
+ return converter;
+ },
+
+ /**
+ * Function: isCompressed
+ *
+ * Returns <compressed>.
+ */
+ isCompressed: function()
+ {
+ return compressed;
+ },
+
+ /**
+ * Function: setCompressed
+ *
+ * Sets <compressed>.
+ */
+ setCompressed: function(value)
+ {
+ compressed = value;
+ },
+
+ /**
+ * Function: isTextEnabled
+ *
+ * Returns <textEnabled>.
+ */
+ isTextEnabled: function()
+ {
+ return textEnabled;
+ },
+
+ /**
+ * Function: setTextEnabled
+ *
+ * Sets <textEnabled>.
+ */
+ setTextEnabled: function(value)
+ {
+ textEnabled = value;
+ },
+
+ /**
+ * Function: getDocument
+ *
+ * Returns the owner document of the root element.
+ */
+ getDocument: function()
+ {
+ return doc;
+ },
+
+ /**
+ * Function: save
+ *
+ * Saves the state of the graphics object.
+ */
+ save: function()
+ {
+ if (compressed)
+ {
+ stack.push(state);
+ state = mxUtils.clone(state);
+ }
+
+ root.appendChild(doc.createElement('save'));
+ },
+
+ /**
+ * Function: restore
+ *
+ * Restores the state of the graphics object.
+ */
+ restore: function()
+ {
+ if (compressed)
+ {
+ state = stack.pop();
+ }
+
+ root.appendChild(doc.createElement('restore'));
+ },
+
+ /**
+ * Function: scale
+ *
+ * Scales the current graphics object.
+ */
+ scale: function(value)
+ {
+ var elem = doc.createElement('scale');
+ elem.setAttribute('scale', value);
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: translate
+ *
+ * Translates the current graphics object.
+ */
+ translate: function(dx, dy)
+ {
+ var elem = doc.createElement('translate');
+ elem.setAttribute('dx', f2(dx));
+ elem.setAttribute('dy', f2(dy));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: rotate
+ *
+ * Rotates and/or flips the current graphics object.
+ */
+ rotate: function(theta, flipH, flipV, cx, cy)
+ {
+ var elem = doc.createElement('rotate');
+ elem.setAttribute('theta', f2(theta));
+ elem.setAttribute('flipH', (flipH) ? '1' : '0');
+ elem.setAttribute('flipV', (flipV) ? '1' : '0');
+ elem.setAttribute('cx', f2(cx));
+ elem.setAttribute('cy', f2(cy));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setStrokeWidth
+ *
+ * Sets the stroke width.
+ */
+ setStrokeWidth: function(value)
+ {
+ if (compressed)
+ {
+ if (state.strokewidth == value)
+ {
+ return;
+ }
+
+ state.strokewidth = value;
+ }
+
+ var elem = doc.createElement('strokewidth');
+ elem.setAttribute('width', f2(value));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setStrokeColor
+ *
+ * Sets the stroke color.
+ */
+ setStrokeColor: function(value)
+ {
+ var elem = doc.createElement('strokecolor');
+ elem.setAttribute('color', value);
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setDashed
+ *
+ * Sets the dashed state to true or false.
+ */
+ setDashed: function(value)
+ {
+ if (compressed)
+ {
+ if (state.dashed == value)
+ {
+ return;
+ }
+
+ state.dashed = value;
+ }
+
+ var elem = doc.createElement('dashed');
+ elem.setAttribute('dashed', (value) ? '1' : '0');
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setDashPattern
+ *
+ * Sets the dashed pattern to the given space separated list of numbers.
+ */
+ setDashPattern: function(value)
+ {
+ var elem = doc.createElement('dashpattern');
+ elem.setAttribute('pattern', value);
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setLineCap
+ *
+ * Sets the linecap.
+ */
+ setLineCap: function(value)
+ {
+ var elem = doc.createElement('linecap');
+ elem.setAttribute('cap', value);
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setLineJoin
+ *
+ * Sets the linejoin.
+ */
+ setLineJoin: function(value)
+ {
+ var elem = doc.createElement('linejoin');
+ elem.setAttribute('join', value);
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setMiterLimit
+ *
+ * Sets the miterlimit.
+ */
+ setMiterLimit: function(value)
+ {
+ var elem = doc.createElement('miterlimit');
+ elem.setAttribute('limit', value);
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setFontSize
+ *
+ * Sets the fontsize.
+ */
+ setFontSize: function(value)
+ {
+ if (textEnabled)
+ {
+ if (compressed)
+ {
+ if (state.fontsize == value)
+ {
+ return;
+ }
+
+ state.fontsize = value;
+ }
+
+ var elem = doc.createElement('fontsize');
+ elem.setAttribute('size', value);
+ root.appendChild(elem);
+ }
+ },
+
+ /**
+ * Function: setFontColor
+ *
+ * Sets the fontcolor.
+ */
+ setFontColor: function(value)
+ {
+ if (textEnabled)
+ {
+ if (compressed)
+ {
+ if (state.fontcolor == value)
+ {
+ return;
+ }
+
+ state.fontcolor = value;
+ }
+
+ var elem = doc.createElement('fontcolor');
+ elem.setAttribute('color', value);
+ root.appendChild(elem);
+ }
+ },
+
+ /**
+ * Function: setFontFamily
+ *
+ * Sets the fontfamily.
+ */
+ setFontFamily: function(value)
+ {
+ if (textEnabled)
+ {
+ if (compressed)
+ {
+ if (state.fontfamily == value)
+ {
+ return;
+ }
+
+ state.fontfamily = value;
+ }
+
+ var elem = doc.createElement('fontfamily');
+ elem.setAttribute('family', value);
+ root.appendChild(elem);
+ }
+ },
+
+ /**
+ * Function: setFontStyle
+ *
+ * Sets the fontstyle.
+ */
+ setFontStyle: function(value)
+ {
+ if (textEnabled)
+ {
+ var elem = doc.createElement('fontstyle');
+ elem.setAttribute('style', value);
+ root.appendChild(elem);
+ }
+ },
+
+ /**
+ * Function: setAlpha
+ *
+ * Sets the current alpha.
+ */
+ setAlpha: function(alpha)
+ {
+ if (compressed)
+ {
+ if (state.alpha == alpha)
+ {
+ return;
+ }
+
+ state.alpha = alpha;
+ }
+
+ var elem = doc.createElement('alpha');
+ elem.setAttribute('alpha', f2(alpha));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setFillColor
+ *
+ * Sets the fillcolor.
+ */
+ setFillColor: function(value)
+ {
+ var elem = doc.createElement('fillcolor');
+ elem.setAttribute('color', value);
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setGradient
+ *
+ * Sets the gradient color.
+ */
+ setGradient: function(color1, color2, x, y, w, h, direction)
+ {
+ var elem = doc.createElement('gradient');
+ elem.setAttribute('c1', color1);
+ elem.setAttribute('c2', color2);
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ elem.setAttribute('w', f2(w));
+ elem.setAttribute('h', f2(h));
+
+ // Default direction is south
+ if (direction != null)
+ {
+ elem.setAttribute('direction', direction);
+ }
+
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: setGlassGradient
+ *
+ * Sets the glass gradient.
+ */
+ setGlassGradient: function(x, y, w, h)
+ {
+ var elem = doc.createElement('glass');
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ elem.setAttribute('w', f2(w));
+ elem.setAttribute('h', f2(h));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: rect
+ *
+ * Sets the current path to a rectangle.
+ */
+ rect: function(x, y, w, h)
+ {
+ var elem = doc.createElement('rect');
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ elem.setAttribute('w', f2(w));
+ elem.setAttribute('h', f2(h));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: roundrect
+ *
+ * Sets the current path to a rounded rectangle.
+ */
+ roundrect: function(x, y, w, h, dx, dy)
+ {
+ var elem = doc.createElement('roundrect');
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ elem.setAttribute('w', f2(w));
+ elem.setAttribute('h', f2(h));
+ elem.setAttribute('dx', f2(dx));
+ elem.setAttribute('dy', f2(dy));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: ellipse
+ *
+ * Sets the current path to an ellipse.
+ */
+ ellipse: function(x, y, w, h)
+ {
+ var elem = doc.createElement('ellipse');
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ elem.setAttribute('w', f2(w));
+ elem.setAttribute('h', f2(h));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: image
+ *
+ * Paints an image.
+ */
+ image: function(x, y, w, h, src, aspect, flipH, flipV)
+ {
+ src = converter.convert(src);
+
+ // TODO: Add option for embedding images as base64
+ var elem = doc.createElement('image');
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ elem.setAttribute('w', f2(w));
+ elem.setAttribute('h', f2(h));
+ elem.setAttribute('src', src);
+ elem.setAttribute('aspect', (aspect) ? '1' : '0');
+ elem.setAttribute('flipH', (flipH) ? '1' : '0');
+ elem.setAttribute('flipV', (flipV) ? '1' : '0');
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: text
+ *
+ * Paints the given text.
+ */
+ text: function(x, y, w, h, str, align, valign, vertical, wrap, format)
+ {
+ if (textEnabled)
+ {
+ var elem = doc.createElement('text');
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ elem.setAttribute('w', f2(w));
+ elem.setAttribute('h', f2(h));
+ elem.setAttribute('str', str);
+
+ if (align != null)
+ {
+ elem.setAttribute('align', align);
+ }
+
+ if (valign != null)
+ {
+ elem.setAttribute('valign', valign);
+ }
+
+ elem.setAttribute('vertical', (vertical) ? '1' : '0');
+ elem.setAttribute('wrap', (wrap) ? '1' : '0');
+ elem.setAttribute('format', format);
+ root.appendChild(elem);
+ }
+ },
+
+ /**
+ * Function: begin
+ *
+ * Starts a new path.
+ */
+ begin: function()
+ {
+ root.appendChild(doc.createElement('begin'));
+ },
+
+ /**
+ * Function: moveTo
+ *
+ * Moves the current path the given coordinates.
+ */
+ moveTo: function(x, y)
+ {
+ var elem = doc.createElement('move');
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: lineTo
+ *
+ * Adds a line to the current path.
+ */
+ lineTo: function(x, y)
+ {
+ var elem = doc.createElement('line');
+ elem.setAttribute('x', f2(x));
+ elem.setAttribute('y', f2(y));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: quadTo
+ *
+ * Adds a quadratic curve to the current path.
+ */
+ quadTo: function(x1, y1, x2, y2)
+ {
+ var elem = doc.createElement('quad');
+ elem.setAttribute('x1', f2(x1));
+ elem.setAttribute('y1', f2(y1));
+ elem.setAttribute('x2', f2(x2));
+ elem.setAttribute('y2', f2(y2));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: curveTo
+ *
+ * Adds a bezier curve to the current path.
+ */
+ curveTo: function(x1, y1, x2, y2, x3, y3)
+ {
+ var elem = doc.createElement('curve');
+ elem.setAttribute('x1', f2(x1));
+ elem.setAttribute('y1', f2(y1));
+ elem.setAttribute('x2', f2(x2));
+ elem.setAttribute('y2', f2(y2));
+ elem.setAttribute('x3', f2(x3));
+ elem.setAttribute('y3', f2(y3));
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: close
+ *
+ * Closes the current path.
+ */
+ close: function()
+ {
+ root.appendChild(doc.createElement('close'));
+ },
+
+ /**
+ * Function: stroke
+ *
+ * Paints the outline of the current path.
+ */
+ stroke: function()
+ {
+ root.appendChild(doc.createElement('stroke'));
+ },
+
+ /**
+ * Function: fill
+ *
+ * Fills the current path.
+ */
+ fill: function()
+ {
+ root.appendChild(doc.createElement('fill'));
+ },
+
+ /**
+ * Function: fillstroke
+ *
+ * Fills and paints the outline of the current path.
+ */
+ fillAndStroke: function()
+ {
+ root.appendChild(doc.createElement('fillstroke'));
+ },
+
+ /**
+ * Function: shadow
+ *
+ * Paints the current path as a shadow of the given color.
+ */
+ shadow: function(value, filled)
+ {
+ var elem = doc.createElement('shadow');
+ elem.setAttribute('value', value);
+
+ if (filled != null)
+ {
+ elem.setAttribute('filled', (filled) ? '1' : '0');
+ }
+
+ root.appendChild(elem);
+ },
+
+ /**
+ * Function: clip
+ *
+ * Uses the current path for clipping.
+ */
+ clip: function()
+ {
+ root.appendChild(doc.createElement('clip'));
+ }
+ };
+
+}; \ No newline at end of file
diff --git a/src/js/util/mxXmlRequest.js b/src/js/util/mxXmlRequest.js
new file mode 100644
index 0000000..0ac55ed
--- /dev/null
+++ b/src/js/util/mxXmlRequest.js
@@ -0,0 +1,425 @@
+/**
+ * $Id: mxXmlRequest.js,v 1.38 2012-04-22 10:16:23 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxXmlRequest
+ *
+ * XML HTTP request wrapper. See also: <mxUtils.get>, <mxUtils.post> and
+ * <mxUtils.load>. This class provides a cross-browser abstraction for Ajax
+ * requests.
+ *
+ * Encoding:
+ *
+ * For encoding parameter values, the built-in encodeURIComponent JavaScript
+ * method must be used. For automatic encoding of post data in <mxEditor> the
+ * <mxEditor.escapePostData> switch can be set to true (default). The encoding
+ * will be carried out using the conte type of the page. That is, the page
+ * containting the editor should contain a meta tag in the header, eg.
+ * <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+ *
+ * Example:
+ *
+ * (code)
+ * var onload = function(req)
+ * {
+ * mxUtils.alert(req.getDocumentElement());
+ * }
+ *
+ * var onerror = function(req)
+ * {
+ * mxUtils.alert(req.getStatus());
+ * }
+ * new mxXmlRequest(url, 'key=value').send(onload, onerror);
+ * (end)
+ *
+ * Sends an asynchronous POST request to the specified URL.
+ *
+ * Example:
+ *
+ * (code)
+ * var req = new mxXmlRequest(url, 'key=value', 'POST', false);
+ * req.send();
+ * mxUtils.alert(req.getDocumentElement());
+ * (end)
+ *
+ * Sends a synchronous POST request to the specified URL.
+ *
+ * Example:
+ *
+ * (code)
+ * var encoder = new mxCodec();
+ * var result = encoder.encode(graph.getModel());
+ * var xml = encodeURIComponent(mxUtils.getXml(result));
+ * new mxXmlRequest(url, 'xml='+xml).send();
+ * (end)
+ *
+ * Sends an encoded graph model to the specified URL using xml as the
+ * parameter name. The parameter can then be retrieved in C# as follows:
+ *
+ * (code)
+ * string xml = HttpUtility.UrlDecode(context.Request.Params["xml"]);
+ * (end)
+ *
+ * Or in Java as follows:
+ *
+ * (code)
+ * String xml = URLDecoder.decode(request.getParameter("xml"), "UTF-8").replace("\n", "&#xa;");
+ * (end)
+ *
+ * Note that the linefeeds should only be replaced if the XML is
+ * processed in Java, for example when creating an image.
+ *
+ * Constructor: mxXmlRequest
+ *
+ * Constructs an XML HTTP request.
+ *
+ * Parameters:
+ *
+ * url - Target URL of the request.
+ * params - Form encoded parameters to send with a POST request.
+ * method - String that specifies the request method. Possible values are
+ * POST and GET. Default is POST.
+ * async - Boolean specifying if an asynchronous request should be used.
+ * Default is true.
+ * username - String specifying the username to be used for the request.
+ * password - String specifying the password to be used for the request.
+ */
+function mxXmlRequest(url, params, method, async, username, password)
+{
+ this.url = url;
+ this.params = params;
+ this.method = method || 'POST';
+ this.async = (async != null) ? async : true;
+ this.username = username;
+ this.password = password;
+};
+
+/**
+ * Variable: url
+ *
+ * Holds the target URL of the request.
+ */
+mxXmlRequest.prototype.url = null;
+
+/**
+ * Variable: params
+ *
+ * Holds the form encoded data for the POST request.
+ */
+mxXmlRequest.prototype.params = null;
+
+/**
+ * Variable: method
+ *
+ * Specifies the request method. Possible values are POST and GET. Default
+ * is POST.
+ */
+mxXmlRequest.prototype.method = null;
+
+/**
+ * Variable: async
+ *
+ * Boolean indicating if the request is asynchronous.
+ */
+mxXmlRequest.prototype.async = null;
+
+/**
+ * Variable: binary
+ *
+ * Boolean indicating if the request is binary. This option is ignored in IE.
+ * In all other browsers the requested mime type is set to
+ * text/plain; charset=x-user-defined. Default is false.
+ */
+mxXmlRequest.prototype.binary = false;
+
+/**
+ * Variable: username
+ *
+ * Specifies the username to be used for authentication.
+ */
+mxXmlRequest.prototype.username = null;
+
+/**
+ * Variable: password
+ *
+ * Specifies the password to be used for authentication.
+ */
+mxXmlRequest.prototype.password = null;
+
+/**
+ * Variable: request
+ *
+ * Holds the inner, browser-specific request object.
+ */
+mxXmlRequest.prototype.request = null;
+
+/**
+ * Function: isBinary
+ *
+ * Returns <binary>.
+ */
+mxXmlRequest.prototype.isBinary = function()
+{
+ return this.binary;
+};
+
+/**
+ * Function: setBinary
+ *
+ * Sets <binary>.
+ */
+mxXmlRequest.prototype.setBinary = function(value)
+{
+ this.binary = value;
+};
+
+/**
+ * Function: getText
+ *
+ * Returns the response as a string.
+ */
+mxXmlRequest.prototype.getText = function()
+{
+ return this.request.responseText;
+};
+
+/**
+ * Function: isReady
+ *
+ * Returns true if the response is ready.
+ */
+mxXmlRequest.prototype.isReady = function()
+{
+ return this.request.readyState == 4;
+};
+
+/**
+ * Function: getDocumentElement
+ *
+ * Returns the document element of the response XML document.
+ */
+mxXmlRequest.prototype.getDocumentElement = function()
+{
+ var doc = this.getXml();
+
+ if (doc != null)
+ {
+ return doc.documentElement;
+ }
+
+ return null;
+};
+
+/**
+ * Function: getXml
+ *
+ * Returns the response as an XML document. Use <getDocumentElement> to get
+ * the document element of the XML document.
+ */
+mxXmlRequest.prototype.getXml = function()
+{
+ var xml = this.request.responseXML;
+
+ // Handles missing response headers in IE, the first condition handles
+ // the case where responseXML is there, but using its nodes leads to
+ // type errors in the mxCellCodec when putting the nodes into a new
+ // document. This happens in IE9 standards mode and with XML user
+ // objects only, as they are used directly as values in cells.
+ if (document.documentMode >= 9 || xml == null || xml.documentElement == null)
+ {
+ xml = mxUtils.parseXml(this.request.responseText);
+ }
+
+ return xml;
+};
+
+/**
+ * Function: getText
+ *
+ * Returns the response as a string.
+ */
+mxXmlRequest.prototype.getText = function()
+{
+ return this.request.responseText;
+};
+
+/**
+ * Function: getStatus
+ *
+ * Returns the status as a number, eg. 404 for "Not found" or 200 for "OK".
+ * Note: The NS_ERROR_NOT_AVAILABLE for invalid responses cannot be cought.
+ */
+mxXmlRequest.prototype.getStatus = function()
+{
+ return this.request.status;
+};
+
+/**
+ * Function: create
+ *
+ * Creates and returns the inner <request> object.
+ */
+mxXmlRequest.prototype.create = function()
+{
+ if (window.XMLHttpRequest)
+ {
+ return function()
+ {
+ var req = new XMLHttpRequest();
+
+ // TODO: Check for overrideMimeType required here?
+ if (this.isBinary() && req.overrideMimeType)
+ {
+ req.overrideMimeType('text/plain; charset=x-user-defined');
+ }
+
+ return req;
+ };
+ }
+ else if (typeof(ActiveXObject) != "undefined")
+ {
+ return function()
+ {
+ // TODO: Implement binary option
+ return new ActiveXObject("Microsoft.XMLHTTP");
+ };
+ }
+}();
+
+/**
+ * Function: send
+ *
+ * Send the <request> to the target URL using the specified functions to
+ * process the response asychronously.
+ *
+ * Parameters:
+ *
+ * onload - Function to be invoked if a successful response was received.
+ * onerror - Function to be called on any error.
+ */
+mxXmlRequest.prototype.send = function(onload, onerror)
+{
+ this.request = this.create();
+
+ if (this.request != null)
+ {
+ if (onload != null)
+ {
+ this.request.onreadystatechange = mxUtils.bind(this, function()
+ {
+ if (this.isReady())
+ {
+ onload(this);
+ this.onreadystatechaange = null;
+ }
+ });
+ }
+
+ this.request.open(this.method, this.url, this.async,
+ this.username, this.password);
+ this.setRequestHeaders(this.request, this.params);
+ this.request.send(this.params);
+ }
+};
+
+/**
+ * Function: setRequestHeaders
+ *
+ * Sets the headers for the given request and parameters. This sets the
+ * content-type to application/x-www-form-urlencoded if any params exist.
+ *
+ * Example:
+ *
+ * (code)
+ * request.setRequestHeaders = function(request, params)
+ * {
+ * if (params != null)
+ * {
+ * request.setRequestHeader('Content-Type',
+ * 'multipart/form-data');
+ * request.setRequestHeader('Content-Length',
+ * params.length);
+ * }
+ * };
+ * (end)
+ *
+ * Use the code above before calling <send> if you require a
+ * multipart/form-data request.
+ */
+mxXmlRequest.prototype.setRequestHeaders = function(request, params)
+{
+ if (params != null)
+ {
+ request.setRequestHeader('Content-Type',
+ 'application/x-www-form-urlencoded');
+ }
+};
+
+/**
+ * Function: simulate
+ *
+ * Creates and posts a request to the given target URL using a dynamically
+ * created form inside the given document.
+ *
+ * Parameters:
+ *
+ * docs - Document that contains the form element.
+ * target - Target to send the form result to.
+ */
+mxXmlRequest.prototype.simulate = function(doc, target)
+{
+ doc = doc || document;
+ var old = null;
+
+ if (doc == document)
+ {
+ old = window.onbeforeunload;
+ window.onbeforeunload = null;
+ }
+
+ var form = doc.createElement('form');
+ form.setAttribute('method', this.method);
+ form.setAttribute('action', this.url);
+
+ if (target != null)
+ {
+ form.setAttribute('target', target);
+ }
+
+ form.style.display = 'none';
+ form.style.visibility = 'hidden';
+
+ var pars = (this.params.indexOf('&') > 0) ?
+ this.params.split('&') :
+ this.params.split();
+
+ // Adds the parameters as textareas to the form
+ for (var i=0; i<pars.length; i++)
+ {
+ var pos = pars[i].indexOf('=');
+
+ if (pos > 0)
+ {
+ var name = pars[i].substring(0, pos);
+ var value = pars[i].substring(pos+1);
+
+ var textarea = doc.createElement('textarea');
+ textarea.setAttribute('name', name);
+ value = value.replace(/\n/g, '&#xa;');
+
+ var content = doc.createTextNode(value);
+ textarea.appendChild(content);
+ form.appendChild(textarea);
+ }
+ }
+
+ doc.body.appendChild(form);
+ form.submit();
+ doc.body.removeChild(form);
+
+ if (old != null)
+ {
+ window.onbeforeunload = old;
+ }
+};
diff --git a/src/js/view/mxCellEditor.js b/src/js/view/mxCellEditor.js
new file mode 100644
index 0000000..2086cca
--- /dev/null
+++ b/src/js/view/mxCellEditor.js
@@ -0,0 +1,522 @@
+/**
+ * $Id: mxCellEditor.js,v 1.62 2012-12-11 16:59:31 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellEditor
+ *
+ * In-place editor for the graph. To control this editor, use
+ * <mxGraph.invokesStopCellEditing>, <mxGraph.enterStopsCellEditing> and
+ * <mxGraph.escapeEnabled>. If <mxGraph.enterStopsCellEditing> is true then
+ * ctrl-enter or shift-enter can be used to create a linefeed. The F2 and
+ * escape keys can always be used to stop editing. To customize the location
+ * of the textbox in the graph, override <getEditorBounds> as follows:
+ *
+ * (code)
+ * graph.cellEditor.getEditorBounds = function(state)
+ * {
+ * var result = mxCellEditor.prototype.getEditorBounds.apply(this, arguments);
+ *
+ * if (this.graph.getModel().isEdge(state.cell))
+ * {
+ * result.x = state.getCenterX() - result.width / 2;
+ * result.y = state.getCenterY() - result.height / 2;
+ * }
+ *
+ * return result;
+ * };
+ * (end)
+ *
+ * The textarea uses the mxCellEditor CSS class. You can modify this class in
+ * your custom CSS. Note: You should modify the CSS after loading the client
+ * in the page.
+ *
+ * Example:
+ *
+ * To only allow numeric input in the in-place editor, use the following code.
+ *
+ * (code)
+ * var text = graph.cellEditor.textarea;
+ *
+ * mxEvent.addListener(text, 'keydown', function (evt)
+ * {
+ * if (!(evt.keyCode >= 48 && evt.keyCode <= 57) &&
+ * !(evt.keyCode >= 96 && evt.keyCode <= 105))
+ * {
+ * mxEvent.consume(evt);
+ * }
+ * });
+ * (end)
+ *
+ * Initial values:
+ *
+ * To implement an initial value for cells without a label, use the
+ * <emptyLabelText> variable.
+ *
+ * Resize in Chrome:
+ *
+ * Resize of the textarea is disabled by default. If you want to enable
+ * this feature extend <init> and set this.textarea.style.resize = ''.
+ *
+ * Constructor: mxCellEditor
+ *
+ * Constructs a new in-place editor for the specified graph.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxCellEditor(graph)
+{
+ this.graph = graph;
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellEditor.prototype.graph = null;
+
+/**
+ * Variable: textarea
+ *
+ * Holds the input textarea. Note that this may be null before the first
+ * edit. Instantiated in <init>.
+ */
+mxCellEditor.prototype.textarea = null;
+
+/**
+ * Variable: editingCell
+ *
+ * Reference to the <mxCell> that is currently being edited.
+ */
+mxCellEditor.prototype.editingCell = null;
+
+/**
+ * Variable: trigger
+ *
+ * Reference to the event that was used to start editing.
+ */
+mxCellEditor.prototype.trigger = null;
+
+/**
+ * Variable: modified
+ *
+ * Specifies if the label has been modified.
+ */
+mxCellEditor.prototype.modified = false;
+
+/**
+ * Variable: emptyLabelText
+ *
+ * Text to be displayed for empty labels. Default is ''. This can be set
+ * to eg. "[Type Here]" to easier visualize editing of empty labels. The
+ * value is only displayed before the first keystroke and is never used
+ * as the actual editin value.
+ */
+mxCellEditor.prototype.emptyLabelText = '';
+
+/**
+ * Variable: textNode
+ *
+ * Reference to the label DOM node that has been hidden.
+ */
+mxCellEditor.prototype.textNode = '';
+
+/**
+ * Function: init
+ *
+ * Creates the <textarea> and installs the event listeners. The key handler
+ * updates the <modified> state.
+ */
+mxCellEditor.prototype.init = function ()
+{
+ this.textarea = document.createElement('textarea');
+
+ this.textarea.className = 'mxCellEditor';
+ this.textarea.style.position = 'absolute';
+ this.textarea.style.overflow = 'visible';
+
+ this.textarea.setAttribute('cols', '20');
+ this.textarea.setAttribute('rows', '4');
+
+ if (mxClient.IS_GC)
+ {
+ this.textarea.style.resize = 'none';
+ }
+
+ mxEvent.addListener(this.textarea, 'blur', mxUtils.bind(this, function(evt)
+ {
+ this.focusLost();
+ }));
+
+ mxEvent.addListener(this.textarea, 'keydown', mxUtils.bind(this, function(evt)
+ {
+ if (!mxEvent.isConsumed(evt))
+ {
+ if (evt.keyCode == 113 /* F2 */ || (this.graph.isEnterStopsCellEditing() &&
+ evt.keyCode == 13 /* Enter */ && !mxEvent.isControlDown(evt) &&
+ !mxEvent.isShiftDown(evt)))
+ {
+ this.graph.stopEditing(false);
+ mxEvent.consume(evt);
+ }
+ else if (evt.keyCode == 27 /* Escape */)
+ {
+ this.graph.stopEditing(true);
+ mxEvent.consume(evt);
+ }
+ else
+ {
+ // Clears the initial empty label on the first keystroke
+ if (this.clearOnChange)
+ {
+ this.clearOnChange = false;
+ this.textarea.value = '';
+ }
+
+ // Updates the modified flag for storing the value
+ this.setModified(true);
+ }
+ }
+ }));
+};
+
+/**
+ * Function: isModified
+ *
+ * Returns <modified>.
+ */
+mxCellEditor.prototype.isModified = function()
+{
+ return this.modified;
+};
+
+/**
+ * Function: setModified
+ *
+ * Sets <modified> to the specified boolean value.
+ */
+mxCellEditor.prototype.setModified = function(value)
+{
+ this.modified = value;
+};
+
+/**
+ * Function: focusLost
+ *
+ * Called if the textarea has lost focus.
+ */
+mxCellEditor.prototype.focusLost = function()
+{
+ this.stopEditing(!this.graph.isInvokesStopCellEditing());
+};
+
+/**
+ * Function: startEditing
+ *
+ * Starts the editor for the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to start editing.
+ * trigger - Optional mouse event that triggered the editor.
+ */
+mxCellEditor.prototype.startEditing = function(cell, trigger)
+{
+ // Lazy instantiates textarea to save memory in IE
+ if (this.textarea == null)
+ {
+ this.init();
+ }
+
+ this.stopEditing(true);
+ var state = this.graph.getView().getState(cell);
+
+ if (state != null)
+ {
+ this.editingCell = cell;
+ this.trigger = trigger;
+ this.textNode = null;
+
+ if (state.text != null && this.isHideLabel(state))
+ {
+ this.textNode = state.text.node;
+ this.textNode.style.visibility = 'hidden';
+ }
+
+ // Configures the style of the in-place editor
+ var scale = this.graph.getView().scale;
+ var size = mxUtils.getValue(state.style, mxConstants.STYLE_FONTSIZE, mxConstants.DEFAULT_FONTSIZE) * scale;
+ var family = mxUtils.getValue(state.style, mxConstants.STYLE_FONTFAMILY, mxConstants.DEFAULT_FONTFAMILY);
+ var color = mxUtils.getValue(state.style, mxConstants.STYLE_FONTCOLOR, 'black');
+ var align = (this.graph.model.isEdge(state.cell)) ? mxConstants.ALIGN_LEFT :
+ mxUtils.getValue(state.style, mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT);
+ var bold = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+ mxConstants.FONT_BOLD) == mxConstants.FONT_BOLD;
+ var italic = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+ mxConstants.FONT_ITALIC) == mxConstants.FONT_ITALIC;
+ var uline = (mxUtils.getValue(state.style, mxConstants.STYLE_FONTSTYLE, 0) &
+ mxConstants.FONT_UNDERLINE) == mxConstants.FONT_UNDERLINE;
+
+ this.textarea.style.fontSize = size + 'px';
+ this.textarea.style.fontFamily = family;
+ this.textarea.style.textAlign = align;
+ this.textarea.style.color = color;
+ this.textarea.style.fontWeight = (bold) ? 'bold' : 'normal';
+ this.textarea.style.fontStyle = (italic) ? 'italic' : '';
+ this.textarea.style.textDecoration = (uline) ? 'underline' : '';
+
+ // Specifies the bounds of the editor box
+ var bounds = this.getEditorBounds(state);
+
+ this.textarea.style.left = bounds.x + 'px';
+ this.textarea.style.top = bounds.y + 'px';
+ this.textarea.style.width = bounds.width + 'px';
+ this.textarea.style.height = bounds.height + 'px';
+ this.textarea.style.zIndex = 5;
+
+ var value = this.getInitialValue(state, trigger);
+
+ // Uses an optional text value for empty labels which is cleared
+ // when the first keystroke appears. This makes it easier to see
+ // that a label is being edited even if the label is empty.
+ if (value == null || value.length == 0)
+ {
+ value = this.getEmptyLabelText();
+ this.clearOnChange = true;
+ }
+ else
+ {
+ this.clearOnChange = false;
+ }
+
+ this.setModified(false);
+ this.textarea.value = value;
+ this.graph.container.appendChild(this.textarea);
+
+ if (this.textarea.style.display != 'none')
+ {
+ // FIXME: Doesn't bring up the virtual keyboard on iPad
+ this.textarea.focus();
+ this.textarea.select();
+ }
+ }
+};
+
+/**
+ * Function: stopEditing
+ *
+ * Stops the editor and applies the value if cancel is false.
+ */
+mxCellEditor.prototype.stopEditing = function(cancel)
+{
+ cancel = cancel || false;
+
+ if (this.editingCell != null)
+ {
+ if (this.textNode != null)
+ {
+ this.textNode.style.visibility = 'visible';
+ this.textNode = null;
+ }
+
+ if (!cancel && this.isModified())
+ {
+ this.graph.labelChanged(this.editingCell, this.getCurrentValue(), this.trigger);
+ }
+
+ this.editingCell = null;
+ this.trigger = null;
+ this.textarea.blur();
+ this.textarea.parentNode.removeChild(this.textarea);
+ }
+};
+
+/**
+ * Function: getInitialValue
+ *
+ * Gets the initial editing value for the given cell.
+ */
+mxCellEditor.prototype.getInitialValue = function(state, trigger)
+{
+ return this.graph.getEditingValue(state.cell, trigger);
+};
+
+/**
+ * Function: getCurrentValue
+ *
+ * Returns the current editing value.
+ */
+mxCellEditor.prototype.getCurrentValue = function()
+{
+ return this.textarea.value.replace(/\r/g, '');
+};
+
+/**
+ * Function: isHideLabel
+ *
+ * Returns true if the label should be hidden while the cell is being
+ * edited.
+ */
+mxCellEditor.prototype.isHideLabel = function(state)
+{
+ return true;
+};
+
+/**
+ * Function: getMinimumSize
+ *
+ * Returns the minimum width and height for editing the given state.
+ */
+mxCellEditor.prototype.getMinimumSize = function(state)
+{
+ var scale = this.graph.getView().scale;
+
+ return new mxRectangle(0, 0, (state.text == null) ? 30 : state.text.size * scale + 20,
+ (this.textarea.style.textAlign == 'left') ? 120 : 40);
+};
+
+/**
+ * Function: getEditorBounds
+ *
+ * Returns the <mxRectangle> that defines the bounds of the editor.
+ */
+mxCellEditor.prototype.getEditorBounds = function(state)
+{
+ var isEdge = this.graph.getModel().isEdge(state.cell);
+ var scale = this.graph.getView().scale;
+ var minSize = this.getMinimumSize(state);
+ var minWidth = minSize.width;
+ var minHeight = minSize.height;
+
+ var spacing = parseInt(state.style[mxConstants.STYLE_SPACING] || 2) * scale;
+ var spacingTop = (parseInt(state.style[mxConstants.STYLE_SPACING_TOP] || 0)) * scale + spacing;
+ var spacingRight = (parseInt(state.style[mxConstants.STYLE_SPACING_RIGHT] || 0)) * scale + spacing;
+ var spacingBottom = (parseInt(state.style[mxConstants.STYLE_SPACING_BOTTOM] || 0)) * scale + spacing;
+ var spacingLeft = (parseInt(state.style[mxConstants.STYLE_SPACING_LEFT] || 0)) * scale + spacing;
+
+ var result = new mxRectangle(state.x, state.y,
+ Math.max(minWidth, state.width - spacingLeft - spacingRight),
+ Math.max(minHeight, state.height - spacingTop - spacingBottom));
+
+ if (isEdge)
+ {
+ result.x = state.absoluteOffset.x;
+ result.y = state.absoluteOffset.y;
+
+ if (state.text != null && state.text.boundingBox != null)
+ {
+ // Workaround for label containing just spaces in which case
+ // the bounding box location contains negative numbers
+ if (state.text.boundingBox.x > 0)
+ {
+ result.x = state.text.boundingBox.x;
+ }
+
+ if (state.text.boundingBox.y > 0)
+ {
+ result.y = state.text.boundingBox.y;
+ }
+ }
+ }
+ else if (state.text != null && state.text.boundingBox != null)
+ {
+ result.x = Math.min(result.x, state.text.boundingBox.x);
+ result.y = Math.min(result.y, state.text.boundingBox.y);
+ }
+
+ result.x += spacingLeft;
+ result.y += spacingTop;
+
+ if (state.text != null && state.text.boundingBox != null)
+ {
+ if (!isEdge)
+ {
+ result.width = Math.max(result.width, state.text.boundingBox.width);
+ result.height = Math.max(result.height, state.text.boundingBox.height);
+ }
+ else
+ {
+ result.width = Math.max(minWidth, state.text.boundingBox.width);
+ result.height = Math.max(minHeight, state.text.boundingBox.height);
+ }
+ }
+
+ // Applies the horizontal and vertical label positions
+ if (this.graph.getModel().isVertex(state.cell))
+ {
+ var horizontal = mxUtils.getValue(state.style, mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_CENTER);
+
+ if (horizontal == mxConstants.ALIGN_LEFT)
+ {
+ result.x -= state.width;
+ }
+ else if (horizontal == mxConstants.ALIGN_RIGHT)
+ {
+ result.x += state.width;
+ }
+
+ var vertical = mxUtils.getValue(state.style, mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_MIDDLE);
+
+ if (vertical == mxConstants.ALIGN_TOP)
+ {
+ result.y -= state.height;
+ }
+ else if (vertical == mxConstants.ALIGN_BOTTOM)
+ {
+ result.y += state.height;
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getEmptyLabelText
+ *
+ * Returns the initial label value to be used of the label of the given
+ * cell is empty. This label is displayed and cleared on the first keystroke.
+ * This implementation returns <emptyLabelText>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which a text for an empty editing box should be
+ * returned.
+ */
+mxCellEditor.prototype.getEmptyLabelText = function (cell)
+{
+ return this.emptyLabelText;
+};
+
+/**
+ * Function: getEditingCell
+ *
+ * Returns the cell that is currently being edited or null if no cell is
+ * being edited.
+ */
+mxCellEditor.prototype.getEditingCell = function ()
+{
+ return this.editingCell;
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the editor and removes all associated resources.
+ */
+mxCellEditor.prototype.destroy = function ()
+{
+ if (this.textarea != null)
+ {
+ mxEvent.release(this.textarea);
+
+ if (this.textarea.parentNode != null)
+ {
+ this.textarea.parentNode.removeChild(this.textarea);
+ }
+
+ this.textarea = null;
+ }
+};
diff --git a/src/js/view/mxCellOverlay.js b/src/js/view/mxCellOverlay.js
new file mode 100644
index 0000000..316e2c4
--- /dev/null
+++ b/src/js/view/mxCellOverlay.js
@@ -0,0 +1,233 @@
+/**
+ * $Id: mxCellOverlay.js,v 1.18 2012-12-06 15:58:44 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellOverlay
+ *
+ * Extends <mxEventSource> to implement a graph overlay, represented by an icon
+ * and a tooltip. Overlays can handle and fire <click> events and are added to
+ * the graph using <mxGraph.addCellOverlay>, and removed using
+ * <mxGraph.removeCellOverlay>, or <mxGraph.removeCellOverlays> to remove all overlays.
+ * The <mxGraph.getCellOverlays> function returns the array of overlays for a given
+ * cell in a graph. If multiple overlays exist for the same cell, then
+ * <getBounds> should be overridden in at least one of the overlays.
+ *
+ * Overlays appear on top of all cells in a special layer. If this is not
+ * desirable, then the image must be rendered as part of the shape or label of
+ * the cell instead.
+ *
+ * Example:
+ *
+ * The following adds a new overlays for a given vertex and selects the cell
+ * if the overlay is clicked.
+ *
+ * (code)
+ * var overlay = new mxCellOverlay(img, html);
+ * graph.addCellOverlay(vertex, overlay);
+ * overlay.addListener(mxEvent.CLICK, function(sender, evt)
+ * {
+ * var cell = evt.getProperty('cell');
+ * graph.setSelectionCell(cell);
+ * });
+ * (end)
+ *
+ * For cell overlays to be printed use <mxPrintPreview.printOverlays>.
+ *
+ * Event: mxEvent.CLICK
+ *
+ * Fires when the user clicks on the overlay. The <code>event</code> property
+ * contains the corresponding mouse event and the <code>cell</code> property
+ * contains the cell. For touch devices this is fired if the element receives
+ * a touchend event.
+ *
+ * Constructor: mxCellOverlay
+ *
+ * Constructs a new overlay using the given image and tooltip.
+ *
+ * Parameters:
+ *
+ * image - <mxImage> that represents the icon to be displayed.
+ * tooltip - Optional string that specifies the tooltip.
+ * align - Optional horizontal alignment for the overlay. Possible
+ * values are <ALIGN_LEFT>, <ALIGN_CENTER> and <ALIGN_RIGHT>
+ * (default).
+ * verticalAlign - Vertical alignment for the overlay. Possible
+ * values are <ALIGN_TOP>, <ALIGN_MIDDLE> and <ALIGN_BOTTOM>
+ * (default).
+ */
+function mxCellOverlay(image, tooltip, align, verticalAlign, offset, cursor)
+{
+ this.image = image;
+ this.tooltip = tooltip;
+ this.align = (align != null) ? align : this.align;
+ this.verticalAlign = (verticalAlign != null) ? verticalAlign : this.verticalAlign;
+ this.offset = (offset != null) ? offset : new mxPoint();
+ this.cursor = (cursor != null) ? cursor : 'help';
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxCellOverlay.prototype = new mxEventSource();
+mxCellOverlay.prototype.constructor = mxCellOverlay;
+
+/**
+ * Variable: image
+ *
+ * Holds the <mxImage> to be used as the icon.
+ */
+mxCellOverlay.prototype.image = null;
+
+/**
+ * Variable: tooltip
+ *
+ * Holds the optional string to be used as the tooltip.
+ */
+mxCellOverlay.prototype.tooltip = null;
+
+/**
+ * Variable: align
+ *
+ * Holds the horizontal alignment for the overlay. Default is
+ * <mxConstants.ALIGN_RIGHT>. For edges, the overlay always appears in the
+ * center of the edge.
+ */
+mxCellOverlay.prototype.align = mxConstants.ALIGN_RIGHT;
+
+/**
+ * Variable: verticalAlign
+ *
+ * Holds the vertical alignment for the overlay. Default is
+ * <mxConstants.ALIGN_BOTTOM>. For edges, the overlay always appears in the
+ * center of the edge.
+ */
+mxCellOverlay.prototype.verticalAlign = mxConstants.ALIGN_BOTTOM;
+
+/**
+ * Variable: offset
+ *
+ * Holds the offset as an <mxPoint>. The offset will be scaled according to the
+ * current scale.
+ */
+mxCellOverlay.prototype.offset = null;
+
+/**
+ * Variable: cursor
+ *
+ * Holds the cursor for the overlay. Default is 'help'.
+ */
+mxCellOverlay.prototype.cursor = null;
+
+/**
+ * Variable: defaultOverlap
+ *
+ * Defines the overlapping for the overlay, that is, the proportional distance
+ * from the origin to the point defined by the alignment. Default is 0.5.
+ */
+mxCellOverlay.prototype.defaultOverlap = 0.5;
+
+/**
+ * Function: getBounds
+ *
+ * Returns the bounds of the overlay for the given <mxCellState> as an
+ * <mxRectangle>. This should be overridden when using multiple overlays
+ * per cell so that the overlays do not overlap.
+ *
+ * The following example will place the overlay along an edge (where
+ * x=[-1..1] from the start to the end of the edge and y is the
+ * orthogonal offset in px).
+ *
+ * (code)
+ * overlay.getBounds = function(state)
+ * {
+ * var bounds = mxCellOverlay.prototype.getBounds.apply(this, arguments);
+ *
+ * if (state.view.graph.getModel().isEdge(state.cell))
+ * {
+ * var pt = state.view.getPoint(state, {x: 0, y: 0, relative: true});
+ *
+ * bounds.x = pt.x - bounds.width / 2;
+ * bounds.y = pt.y - bounds.height / 2;
+ * }
+ *
+ * return bounds;
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the current state of the
+ * associated cell.
+ */
+mxCellOverlay.prototype.getBounds = function(state)
+{
+ var isEdge = state.view.graph.getModel().isEdge(state.cell);
+ var s = state.view.scale;
+ var pt = null;
+
+ var w = this.image.width;
+ var h = this.image.height;
+
+ if (isEdge)
+ {
+ var pts = state.absolutePoints;
+
+ if (pts.length % 2 == 1)
+ {
+ pt = pts[Math.floor(pts.length / 2)];
+ }
+ else
+ {
+ var idx = pts.length / 2;
+ var p0 = pts[idx-1];
+ var p1 = pts[idx];
+ pt = new mxPoint(p0.x + (p1.x - p0.x) / 2,
+ p0.y + (p1.y - p0.y) / 2);
+ }
+ }
+ else
+ {
+ pt = new mxPoint();
+
+ if (this.align == mxConstants.ALIGN_LEFT)
+ {
+ pt.x = state.x;
+ }
+ else if (this.align == mxConstants.ALIGN_CENTER)
+ {
+ pt.x = state.x + state.width / 2;
+ }
+ else
+ {
+ pt.x = state.x + state.width;
+ }
+
+ if (this.verticalAlign == mxConstants.ALIGN_TOP)
+ {
+ pt.y = state.y;
+ }
+ else if (this.verticalAlign == mxConstants.ALIGN_MIDDLE)
+ {
+ pt.y = state.y + state.height / 2;
+ }
+ else
+ {
+ pt.y = state.y + state.height;
+ }
+ }
+
+ return new mxRectangle(pt.x - (w * this.defaultOverlap - this.offset.x) * s,
+ pt.y - (h * this.defaultOverlap - this.offset.y) * s, w * s, h * s);
+};
+
+/**
+ * Function: toString
+ *
+ * Returns the textual representation of the overlay to be used as the
+ * tooltip. This implementation returns <tooltip>.
+ */
+mxCellOverlay.prototype.toString = function()
+{
+ return this.tooltip;
+};
diff --git a/src/js/view/mxCellRenderer.js b/src/js/view/mxCellRenderer.js
new file mode 100644
index 0000000..6b506ad
--- /dev/null
+++ b/src/js/view/mxCellRenderer.js
@@ -0,0 +1,1480 @@
+/**
+ * $Id: mxCellRenderer.js,v 1.189 2012-11-20 09:06:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellRenderer
+ *
+ * Renders cells into a document object model. The <defaultShapes> is a global
+ * map of shapename, constructor pairs that is used in all instances. You can
+ * get a list of all available shape names using the following code.
+ *
+ * In general the cell renderer is in charge of creating, redrawing and
+ * destroying the shape and label associated with a cell state, as well as
+ * some other graphical objects, namely controls and overlays. The shape
+ * hieararchy in the display (ie. the hierarchy in which the DOM nodes
+ * appear in the document) does not reflect the cell hierarchy. The shapes
+ * are a (flat) sequence of shapes and labels inside the draw pane of the
+ * graph view, with some exceptions, namely the HTML labels being placed
+ * directly inside the graph container for certain browsers.
+ *
+ * (code)
+ * mxLog.show();
+ * for (var i in mxCellRenderer.prototype.defaultShapes)
+ * {
+ * mxLog.debug(i);
+ * }
+ * (end)
+ *
+ * Constructor: mxCellRenderer
+ *
+ * Constructs a new cell renderer with the following built-in shapes:
+ * arrow, rectangle, ellipse, rhombus, image, line, label, cylinder,
+ * swimlane, connector, actor and cloud.
+ */
+function mxCellRenderer()
+{
+ this.shapes = mxUtils.clone(this.defaultShapes);
+};
+
+/**
+ * Variable: shapes
+ *
+ * Array that maps from shape names to shape constructors. All entries
+ * in <defaultShapes> are added to this array.
+ */
+mxCellRenderer.prototype.shapes = null;
+
+/**
+ * Variable: defaultEdgeShape
+ *
+ * Defines the default shape for edges. Default is <mxConnector>.
+ */
+mxCellRenderer.prototype.defaultEdgeShape = mxConnector;
+
+/**
+ * Variable: defaultVertexShape
+ *
+ * Defines the default shape for vertices. Default is <mxRectangleShape>.
+ */
+mxCellRenderer.prototype.defaultVertexShape = mxRectangleShape;
+
+/**
+ * Variable: defaultShapes
+ *
+ * Static array that contains the globally registered shapes which are
+ * known to all instances of this class. For adding instance-specific
+ * shapes you should use <registerShape> on the instance. For adding
+ * a shape to this array you can use the following code:
+ *
+ * (code)
+ * mxCellRenderer.prototype.defaultShapes['myshape'] = myShape;
+ * (end)
+ *
+ * Where 'myshape' is the key under which the shape is to be registered
+ * and myShape is the name of the constructor function.
+ */
+mxCellRenderer.prototype.defaultShapes = new Object();
+
+// Adds default shapes into the default shapes array
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_ARROW] = mxArrow;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_RECTANGLE] = mxRectangleShape;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_ELLIPSE] = mxEllipse;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_DOUBLE_ELLIPSE] = mxDoubleEllipse;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_RHOMBUS] = mxRhombus;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_IMAGE] = mxImageShape;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_LINE] = mxLine;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_LABEL] = mxLabel;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_CYLINDER] = mxCylinder;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_SWIMLANE] = mxSwimlane;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_CONNECTOR] = mxConnector;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_ACTOR] = mxActor;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_CLOUD] = mxCloud;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_TRIANGLE] = mxTriangle;
+mxCellRenderer.prototype.defaultShapes[mxConstants.SHAPE_HEXAGON] = mxHexagon;
+
+/**
+ * Function: registerShape
+ *
+ * Registers the given constructor under the specified key in this instance
+ * of the renderer.
+ *
+ * Example:
+ *
+ * (code)
+ * this.registerShape(mxConstants.SHAPE_RECTANGLE, mxRectangleShape);
+ * (end)
+ *
+ * Parameters:
+ *
+ * key - String representing the shape name.
+ * shape - Constructor of the <mxShape> subclass.
+ */
+mxCellRenderer.prototype.registerShape = function(key, shape)
+{
+ this.shapes[key] = shape;
+};
+
+/**
+ * Function: initialize
+ *
+ * Initializes the display for the given cell state. This is required once
+ * after the cell state has been created. This is invoked in
+ * mxGraphView.createState.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the display should be initialized.
+ * rendering - Optional boolean that specifies if the cell should actually
+ * be initialized for any given DOM node. If this is false then init
+ * will not be called on the shape.
+ */
+mxCellRenderer.prototype.initialize = function(state, rendering)
+{
+ var model = state.view.graph.getModel();
+
+ if (state.view.graph.container != null && state.shape == null &&
+ state.cell != state.view.currentRoot &&
+ (model.isVertex(state.cell) || model.isEdge(state.cell)))
+ {
+ this.createShape(state);
+
+ if (state.shape != null && (rendering == null || rendering))
+ {
+ this.initializeShape(state);
+
+ // Maintains the model order in the DOM
+ if (state.view.graph.ordered || model.isEdge(state.cell))
+ {
+ //state.orderChanged = true;
+ state.invalidOrder = true;
+ }
+ else if (state.view.graph.keepEdgesInForeground && this.firstEdge != null)
+ {
+ if (this.firstEdge.parentNode == state.shape.node.parentNode)
+ {
+ this.insertState(state, this.firstEdge);
+ }
+ else
+ {
+ this.firstEdge = null;
+ }
+ }
+
+ state.shape.scale = state.view.scale;
+
+ this.createCellOverlays(state);
+ this.installListeners(state);
+ }
+ }
+};
+
+/**
+ * Function: initializeShape
+ *
+ * Initializes the shape in the given state by calling its init method with
+ * the correct container.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be initialized.
+ */
+mxCellRenderer.prototype.initializeShape = function(state)
+{
+ state.shape.init(state.view.getDrawPane());
+};
+
+/**
+ * Returns the previous state that has a shape inside the given parent.
+ */
+mxCellRenderer.prototype.getPreviousStateInContainer = function(state, container)
+{
+ var result = null;
+ var graph = state.view.graph;
+ var model = graph.getModel();
+ var child = state.cell;
+ var p = model.getParent(child);
+
+ while (p != null && result == null)
+ {
+ result = this.findPreviousStateInContainer(graph, p, child, container);
+ child = p;
+ p = model.getParent(child);
+ }
+
+ return result;
+};
+
+/**
+ * Returns the previous state that has a shape inside the given parent.
+ */
+mxCellRenderer.prototype.findPreviousStateInContainer = function(graph, cell, stop, container)
+{
+ // Recurse first
+ var result = null;
+ var model = graph.getModel();
+
+ if (stop != null)
+ {
+ var start = cell.getIndex(stop);
+
+ for (var i = start - 1; i >= 0 && result == null; i--)
+ {
+ result = this.findPreviousStateInContainer(graph, model.getChildAt(cell, i), null, container);
+ }
+ }
+ else
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = childCount - 1; i >= 0 && result == null; i--)
+ {
+ result = this.findPreviousStateInContainer(graph, model.getChildAt(cell, i), null, container);
+ }
+ }
+
+ if (result == null)
+ {
+ result = graph.view.getState(cell);
+
+ if (result != null && (result.shape == null || result.shape.node == null ||
+ result.shape.node.parentNode != container))
+ {
+ result = null;
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: order
+ *
+ * Orders the DOM node of the shape for the given state according to the
+ * position of the corresponding cell in the graph model.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose shape's DOM node should be ordered.
+ */
+mxCellRenderer.prototype.order = function(state)
+{
+ var container = state.shape.node.parentNode;
+ var previous = this.getPreviousStateInContainer(state, container);
+ var nextNode = container.firstChild;
+
+ if (previous != null)
+ {
+ nextNode = previous.shape.node;
+
+ if (previous.text != null && previous.text.node != null &&
+ previous.text.node.parentNode == container)
+ {
+ nextNode = previous.text.node;
+ }
+
+ nextNode = nextNode.nextSibling;
+ }
+
+ this.insertState(state, nextNode);
+};
+
+/**
+ * Function: orderEdge
+ *
+ * Orders the DOM node of the shape for the given edge's state according to
+ * the <mxGraph.keepEdgesInBackground> and <mxGraph.keepEdgesInBackground>
+ * rules.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose shape's DOM node should be ordered.
+ */
+mxCellRenderer.prototype.orderEdge = function(state)
+{
+ var view = state.view;
+ var model = view.graph.getModel();
+
+ // Moves edges to the foreground/background
+ if (view.graph.keepEdgesInForeground)
+ {
+ if (this.firstEdge == null || this.firstEdge.parentNode == null ||
+ this.firstEdge.parentNode != state.shape.node.parentNode)
+ {
+ this.firstEdge = state.shape.node;
+ }
+ }
+ else if (view.graph.keepEdgesInBackground)
+ {
+ var node = state.shape.node;
+ var parent = node.parentNode;
+
+ // Keeps the DOM node in front of its parent
+ var pcell = model.getParent(state.cell);
+ var pstate = view.getState(pcell);
+
+ if (pstate != null && pstate.shape != null && pstate.shape.node != null)
+ {
+ var child = pstate.shape.node.nextSibling;
+
+ if (child != null && child != node)
+ {
+ this.insertState(state, child);
+ }
+ }
+ else
+ {
+ var child = parent.firstChild;
+
+ if (child != null && child != node)
+ {
+ this.insertState(state, child);
+ }
+ }
+ }
+};
+
+/**
+ * Function: insertState
+ *
+ * Inserts the given state before the given node into its parent.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be created.
+ */
+mxCellRenderer.prototype.insertState = function(state, nextNode)
+{
+ state.shape.node.parentNode.insertBefore(state.shape.node, nextNode);
+
+ if (state.text != null && state.text.node != null &&
+ state.text.node.parentNode == state.shape.node.parentNode)
+ {
+ state.shape.node.parentNode.insertBefore(state.text.node, state.shape.node.nextSibling);
+ }
+};
+
+/**
+ * Function: createShape
+ *
+ * Creates the shape for the given cell state. The shape is configured
+ * using <configureShape>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be created.
+ */
+mxCellRenderer.prototype.createShape = function(state)
+{
+ if (state.style != null)
+ {
+ // Checks if there is a stencil for the name and creates
+ // a shape instance for the stencil if one exists
+ var key = state.style[mxConstants.STYLE_SHAPE];
+ var stencil = mxStencilRegistry.getStencil(key);
+
+ if (stencil != null)
+ {
+ state.shape = new mxStencilShape(stencil);
+ }
+ else
+ {
+ var ctor = this.getShapeConstructor(state);
+ state.shape = new ctor();
+ }
+
+ // Sets the initial bounds and points (will be updated in redraw)
+ state.shape.points = state.absolutePoints;
+ state.shape.bounds = new mxRectangle(
+ state.x, state.y, state.width, state.height);
+ state.shape.dialect = state.view.graph.dialect;
+
+ this.configureShape(state);
+ }
+};
+
+/**
+ * Function: getShapeConstructor
+ *
+ * Returns the constructor to be used for creating the shape.
+ */
+mxCellRenderer.prototype.getShapeConstructor = function(state)
+{
+ var key = state.style[mxConstants.STYLE_SHAPE];
+ var ctor = (key != null) ? this.shapes[key] : null;
+
+ if (ctor == null)
+ {
+ ctor = (state.view.graph.getModel().isEdge(state.cell)) ?
+ this.defaultEdgeShape : this.defaultVertexShape;
+ }
+
+ return ctor;
+};
+
+/**
+ * Function: configureShape
+ *
+ * Configures the shape for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shape should be configured.
+ */
+mxCellRenderer.prototype.configureShape = function(state)
+{
+ state.shape.apply(state);
+ var image = state.view.graph.getImage(state);
+
+ if (image != null)
+ {
+ state.shape.image = image;
+ }
+
+ var indicator = state.view.graph.getIndicatorColor(state);
+ var key = state.view.graph.getIndicatorShape(state);
+ var ctor = (key != null) ? this.shapes[key] : null;
+
+ // Configures the indicator shape or image
+ if (indicator != null)
+ {
+ state.shape.indicatorShape = ctor;
+ state.shape.indicatorColor = indicator;
+ state.shape.indicatorGradientColor =
+ state.view.graph.getIndicatorGradientColor(state);
+ state.shape.indicatorDirection =
+ state.style[mxConstants.STYLE_INDICATOR_DIRECTION];
+ }
+ else
+ {
+ var indicator = state.view.graph.getIndicatorImage(state);
+
+ if (indicator != null)
+ {
+ state.shape.indicatorImage = indicator;
+ }
+ }
+
+ this.postConfigureShape(state);
+};
+
+/**
+ * Function: postConfigureShape
+ *
+ * Replaces any reserved words used for attributes, eg. inherit,
+ * indicated or swimlane for colors in the shape for the given state.
+ * This implementation resolves these keywords on the fill, stroke
+ * and gradient color keys.
+ */
+mxCellRenderer.prototype.postConfigureShape = function(state)
+{
+ if (state.shape != null)
+ {
+ this.resolveColor(state, 'indicatorColor', mxConstants.STYLE_FILLCOLOR);
+ this.resolveColor(state, 'indicatorGradientColor', mxConstants.STYLE_GRADIENTCOLOR);
+ this.resolveColor(state, 'fill', mxConstants.STYLE_FILLCOLOR);
+ this.resolveColor(state, 'stroke', mxConstants.STYLE_STROKECOLOR);
+ this.resolveColor(state, 'gradient', mxConstants.STYLE_GRADIENTCOLOR);
+ }
+};
+
+/**
+ * Function: resolveColor
+ *
+ * Resolves special keywords 'inherit', 'indicated' and 'swimlane' and sets
+ * the respective color on the shape.
+ */
+mxCellRenderer.prototype.resolveColor = function(state, field, key)
+{
+ var value = state.shape[field];
+ var graph = state.view.graph;
+ var referenced = null;
+
+ if (value == 'inherit')
+ {
+ referenced = graph.model.getParent(state.cell);
+ }
+ else if (value == 'swimlane')
+ {
+ if (graph.model.getTerminal(state.cell, false) != null)
+ {
+ referenced = graph.model.getTerminal(state.cell, false);
+ }
+ else
+ {
+ referenced = state.cell;
+ }
+
+ referenced = graph.getSwimlane(referenced);
+ key = graph.swimlaneIndicatorColorAttribute;
+ }
+ else if (value == 'indicated')
+ {
+ state.shape[field] = state.shape.indicatorColor;
+ }
+
+ if (referenced != null)
+ {
+ var rstate = graph.getView().getState(referenced);
+ state.shape[field] = null;
+
+ if (rstate != null)
+ {
+ if (rstate.shape != null && field != 'indicatorColor')
+ {
+ state.shape[field] = rstate.shape[field];
+ }
+ else
+ {
+ state.shape[field] = rstate.style[key];
+ }
+ }
+ }
+};
+
+/**
+ * Function: getLabelValue
+ *
+ * Returns the value to be used for the label.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the label should be created.
+ */
+mxCellRenderer.prototype.getLabelValue = function(state)
+{
+ var graph = state.view.graph;
+ var value = graph.getLabel(state.cell);
+
+ if (!graph.isHtmlLabel(state.cell) && !mxUtils.isNode(value) &&
+ graph.dialect != mxConstants.DIALECT_SVG && value != null)
+ {
+ value = mxUtils.htmlEntities(value, false);
+ }
+
+ return value;
+};
+
+/**
+ * Function: createLabel
+ *
+ * Creates the label for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the label should be created.
+ */
+mxCellRenderer.prototype.createLabel = function(state, value)
+{
+ var graph = state.view.graph;
+ var isEdge = graph.getModel().isEdge(state.cell);
+
+ if (state.style[mxConstants.STYLE_FONTSIZE] > 0 ||
+ state.style[mxConstants.STYLE_FONTSIZE] == null)
+ {
+ // Avoids using DOM node for empty labels
+ var isForceHtml = (graph.isHtmlLabel(state.cell) ||
+ (value != null && mxUtils.isNode(value))) &&
+ graph.dialect == mxConstants.DIALECT_SVG;
+
+ state.text = new mxText(value, new mxRectangle(),
+ (state.style[mxConstants.STYLE_ALIGN] ||
+ mxConstants.ALIGN_CENTER),
+ graph.getVerticalAlign(state),
+ state.style[mxConstants.STYLE_FONTCOLOR],
+ state.style[mxConstants.STYLE_FONTFAMILY],
+ state.style[mxConstants.STYLE_FONTSIZE],
+ state.style[mxConstants.STYLE_FONTSTYLE],
+ state.style[mxConstants.STYLE_SPACING],
+ state.style[mxConstants.STYLE_SPACING_TOP],
+ state.style[mxConstants.STYLE_SPACING_RIGHT],
+ state.style[mxConstants.STYLE_SPACING_BOTTOM],
+ state.style[mxConstants.STYLE_SPACING_LEFT],
+ state.style[mxConstants.STYLE_HORIZONTAL],
+ state.style[mxConstants.STYLE_LABEL_BACKGROUNDCOLOR],
+ state.style[mxConstants.STYLE_LABEL_BORDERCOLOR],
+ graph.isWrapping(state.cell) && graph.isHtmlLabel(state.cell),
+ graph.isLabelClipped(state.cell),
+ state.style[mxConstants.STYLE_OVERFLOW],
+ state.style[mxConstants.STYLE_LABEL_PADDING]);
+ state.text.opacity = state.style[mxConstants.STYLE_TEXT_OPACITY];
+
+ state.text.dialect = (isForceHtml) ?
+ mxConstants.DIALECT_STRICTHTML :
+ state.view.graph.dialect;
+ this.initializeLabel(state);
+
+ // Workaround for touch devices routing all events for a mouse
+ // gesture (down, move, up) via the initial DOM node. IE is even
+ // worse in that it redirects the event via the initial DOM node
+ // but the event source is the node under the mouse, so we need
+ // to check if this is the case and force getCellAt for the
+ // subsequent mouseMoves and the final mouseUp.
+ var forceGetCell = false;
+
+ var getState = function(evt)
+ {
+ var result = state;
+
+ if (mxClient.IS_TOUCH || forceGetCell)
+ {
+ var x = mxEvent.getClientX(evt);
+ var y = mxEvent.getClientY(evt);
+
+ // Dispatches the drop event to the graph which
+ // consumes and executes the source function
+ var pt = mxUtils.convertPoint(graph.container, x, y);
+ result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+ }
+
+ return result;
+ };
+
+ // TODO: Add handling for gestures
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ mxEvent.addListener(state.text.node, md,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isLabelEvent(state, evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+ new mxMouseEvent(evt, state));
+ forceGetCell = graph.dialect != mxConstants.DIALECT_SVG && mxEvent.getSource(evt).nodeName == 'IMG';
+ }
+ })
+ );
+
+ mxEvent.addListener(state.text.node, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isLabelEvent(state, evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, getState(evt)));
+ }
+ })
+ );
+
+ mxEvent.addListener(state.text.node, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isLabelEvent(state, evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt, getState(evt)));
+ forceGetCell = false;
+ }
+ })
+ );
+
+ mxEvent.addListener(state.text.node, 'dblclick',
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isLabelEvent(state, evt))
+ {
+ graph.dblClick(evt, state.cell);
+ mxEvent.consume(evt);
+ }
+ })
+ );
+ }
+};
+
+/**
+ * Function: initializeLabel
+ *
+ * Initiailzes the label with a suitable container.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label should be initialized.
+ */
+mxCellRenderer.prototype.initializeLabel = function(state)
+{
+ var graph = state.view.graph;
+
+ if (state.text.dialect != mxConstants.DIALECT_SVG)
+ {
+ // Adds the text to the container if the dialect is not SVG and we
+ // have an SVG-based browser which doesn't support foreignObjects
+ if (mxClient.IS_SVG && mxClient.NO_FO)
+ {
+ state.text.init(graph.container);
+ }
+ else if (mxUtils.isVml(state.view.getDrawPane()))
+ {
+ if (state.shape.label != null)
+ {
+ state.text.init(state.shape.label);
+ }
+ else
+ {
+ state.text.init(state.shape.node);
+ }
+ }
+ }
+
+ if (state.text.node == null)
+ {
+ state.text.init(state.view.getDrawPane());
+
+ if (state.shape != null && state.text != null)
+ {
+ state.shape.node.parentNode.insertBefore(
+ state.text.node, state.shape.node.nextSibling);
+ }
+ }
+};
+
+/**
+ * Function: createCellOverlays
+ *
+ * Creates the actual shape for showing the overlay for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the overlay should be created.
+ */
+mxCellRenderer.prototype.createCellOverlays = function(state)
+{
+ var graph = state.view.graph;
+ var overlays = graph.getCellOverlays(state.cell);
+ var dict = null;
+
+ if (overlays != null)
+ {
+ dict = new mxDictionary();
+
+ for (var i = 0; i < overlays.length; i++)
+ {
+ var shape = (state.overlays != null) ? state.overlays.remove(overlays[i]) : null;
+
+ if (shape == null)
+ {
+ var tmp = new mxImageShape(new mxRectangle(),
+ overlays[i].image.src);
+ tmp.dialect = state.view.graph.dialect;
+ tmp.preserveImageAspect = false;
+ tmp.overlay = overlays[i];
+ this.initializeOverlay(state, tmp);
+ this.installCellOverlayListeners(state, overlays[i], tmp);
+
+ if (overlays[i].cursor != null)
+ {
+ tmp.node.style.cursor = overlays[i].cursor;
+ }
+
+ dict.put(overlays[i], tmp);
+ }
+ else
+ {
+ dict.put(overlays[i], shape);
+ }
+ }
+ }
+
+ // Removes unused
+ if (state.overlays != null)
+ {
+ state.overlays.visit(function(id, shape)
+ {
+ shape.destroy();
+ });
+ }
+
+ state.overlays = dict;
+};
+
+/**
+ * Function: initializeOverlay
+ *
+ * Initializes the given overlay.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the overlay should be created.
+ * overlay - <mxImageShape> that represents the overlay.
+ */
+mxCellRenderer.prototype.initializeOverlay = function(state, overlay)
+{
+ overlay.init(state.view.getOverlayPane());
+};
+
+/**
+ * Function: installOverlayListeners
+ *
+ * Installs the listeners for the given <mxCellState>, <mxCellOverlay> and
+ * <mxShape> that represents the overlay.
+ */
+mxCellRenderer.prototype.installCellOverlayListeners = function(state, overlay, shape)
+{
+ var graph = state.view.graph;
+
+ mxEvent.addListener(shape.node, 'click', function (evt)
+ {
+ if (graph.isEditing())
+ {
+ graph.stopEditing(!graph.isInvokesStopCellEditing());
+ }
+
+ overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
+ 'event', evt, 'cell', state.cell));
+ });
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+
+ mxEvent.addListener(shape.node, md, function (evt)
+ {
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(shape.node, mm, function (evt)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, state));
+ });
+
+ if (mxClient.IS_TOUCH)
+ {
+ mxEvent.addListener(shape.node, 'touchend', function (evt)
+ {
+ overlay.fireEvent(new mxEventObject(mxEvent.CLICK,
+ 'event', evt, 'cell', state.cell));
+ });
+ }
+};
+
+/**
+ * Function: createControl
+ *
+ * Creates the control for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the control should be created.
+ */
+mxCellRenderer.prototype.createControl = function(state)
+{
+ var graph = state.view.graph;
+ var image = graph.getFoldingImage(state);
+
+ if (graph.foldingEnabled && image != null)
+ {
+ if (state.control == null)
+ {
+ var b = new mxRectangle(0, 0, image.width, image.height);
+ state.control = new mxImageShape(b, image.src);
+ state.control.dialect = graph.dialect;
+ state.control.preserveImageAspect = false;
+
+ this.initControl(state, state.control, true, function (evt)
+ {
+ if (graph.isEnabled())
+ {
+ var collapse = !graph.isCellCollapsed(state.cell);
+ graph.foldCells(collapse, false, [state.cell]);
+ mxEvent.consume(evt);
+ }
+ });
+ }
+ }
+ else if (state.control != null)
+ {
+ state.control.destroy();
+ state.control = null;
+ }
+};
+
+/**
+ * Function: initControl
+ *
+ * Initializes the given control and returns the corresponding DOM node.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the control should be initialized.
+ * control - <mxShape> to be initialized.
+ * handleEvents - Boolean indicating if mousedown and mousemove should fire events via the graph.
+ * clickHandler - Optional function to implement clicks on the control.
+ */
+mxCellRenderer.prototype.initControl = function(state, control, handleEvents, clickHandler)
+{
+ var graph = state.view.graph;
+
+ // In the special case where the label is in HTML and the display is SVG the image
+ // should go into the graph container directly in order to be clickable. Otherwise
+ // it is obscured by the HTML label that overlaps the cell.
+ var isForceHtml = graph.isHtmlLabel(state.cell) &&
+ mxClient.NO_FO &&
+ graph.dialect == mxConstants.DIALECT_SVG;
+
+ if (isForceHtml)
+ {
+ control.dialect = mxConstants.DIALECT_PREFERHTML;
+ control.init(graph.container);
+ control.node.style.zIndex = 1;
+ }
+ else
+ {
+ control.init(state.view.getOverlayPane());
+ }
+
+ var node = control.innerNode || control.node;
+
+ if (clickHandler)
+ {
+ if (graph.isEnabled())
+ {
+ node.style.cursor = 'pointer';
+ }
+
+ mxEvent.addListener(node, 'click', clickHandler);
+ }
+
+ if (handleEvents)
+ {
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+
+ mxEvent.addListener(node, md, function (evt)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt, state));
+ mxEvent.consume(evt);
+ });
+
+ mxEvent.addListener(node, mm, function (evt)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE, new mxMouseEvent(evt, state));
+ });
+ }
+
+ return node;
+};
+
+/**
+ * Function: isShapeEvent
+ *
+ * Returns true if the event is for the shape of the given state. This
+ * implementation always returns true.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose shape fired the event.
+ * evt - Mouse event which was fired.
+ */
+mxCellRenderer.prototype.isShapeEvent = function(state, evt)
+{
+ return true;
+};
+
+/**
+ * Function: isLabelEvent
+ *
+ * Returns true if the event is for the label of the given state. This
+ * implementation always returns true.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label fired the event.
+ * evt - Mouse event which was fired.
+ */
+mxCellRenderer.prototype.isLabelEvent = function(state, evt)
+{
+ return true;
+};
+
+/**
+ * Function: installListeners
+ *
+ * Installs the event listeners for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the event listeners should be isntalled.
+ */
+mxCellRenderer.prototype.installListeners = function(state)
+{
+ var graph = state.view.graph;
+
+ // Receives events from transparent backgrounds
+ if (graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ var events = 'all';
+
+ // Disabled fill-events on non-filled edges
+ if (graph.getModel().isEdge(state.cell) && state.shape.stroke != null &&
+ (state.shape.fill == null || state.shape.fill == mxConstants.NONE))
+ {
+ events = 'visibleStroke';
+ }
+
+ // Specifies the event-processing on the shape
+ if (state.shape.innerNode != null)
+ {
+ state.shape.innerNode.setAttribute('pointer-events', events);
+ }
+ else
+ {
+ state.shape.node.setAttribute('pointer-events', events);
+ }
+ }
+
+ // Workaround for touch devices routing all events for a mouse
+ // gesture (down, move, up) via the initial DOM node. Same for
+ // HTML images in all IE versions (VML images are working).
+ var getState = function(evt)
+ {
+ var result = state;
+
+ if ((graph.dialect != mxConstants.DIALECT_SVG && mxEvent.getSource(evt).nodeName == 'IMG') || mxClient.IS_TOUCH)
+ {
+ var x = mxEvent.getClientX(evt);
+ var y = mxEvent.getClientY(evt);
+
+ // Dispatches the drop event to the graph which
+ // consumes and executes the source function
+ var pt = mxUtils.convertPoint(graph.container, x, y);
+ result = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+ }
+
+ return result;
+ };
+
+ // Experimental support for two-finger pinch to resize cells
+ var gestureInProgress = false;
+
+ mxEvent.addListener(state.shape.node, 'gesturestart',
+ mxUtils.bind(this, function(evt)
+ {
+ // FIXME: Breaks encapsulation to reset the double
+ // tap event handling when gestures take place
+ graph.lastTouchTime = 0;
+
+ gestureInProgress = true;
+ mxEvent.consume(evt);
+ })
+ );
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ mxEvent.addListener(state.shape.node, md,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isShapeEvent(state, evt) && !gestureInProgress)
+ {
+ // Redirects events from the "event-transparent" region of
+ // a swimlane to the graph. This is only required in HTML,
+ // SVG and VML do not fire mouse events on transparent
+ // backgrounds.
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+ new mxMouseEvent(evt, (state.shape != null &&
+ mxEvent.getSource(evt) == state.shape.content) ?
+ null : state));
+ }
+ else if (gestureInProgress)
+ {
+ mxEvent.consume(evt);
+ }
+ })
+ );
+
+ mxEvent.addListener(state.shape.node, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isShapeEvent(state, evt) && !gestureInProgress)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, (state.shape != null &&
+ mxEvent.getSource(evt) == state.shape.content) ?
+ null : getState(evt)));
+ }
+ else if (gestureInProgress)
+ {
+ mxEvent.consume(evt);
+ }
+ })
+ );
+
+ mxEvent.addListener(state.shape.node, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isShapeEvent(state, evt) && !gestureInProgress)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt, (state.shape != null &&
+ mxEvent.getSource(evt) == state.shape.content) ?
+ null : getState(evt)));
+ }
+ else if (gestureInProgress)
+ {
+ mxEvent.consume(evt);
+ }
+ })
+ );
+
+ // Experimental handling for gestures. Double-tap handling is implemented
+ // in mxGraph.fireMouseEvent.
+ var dc = (mxClient.IS_TOUCH) ? 'gestureend' : 'dblclick';
+
+ mxEvent.addListener(state.shape.node, dc,
+ mxUtils.bind(this, function(evt)
+ {
+ gestureInProgress = false;
+
+ if (dc == 'gestureend')
+ {
+ // FIXME: Breaks encapsulation to reset the double
+ // tap event handling when gestures take place
+ graph.lastTouchTime = 0;
+
+ if (graph.gestureEnabled)
+ {
+ graph.handleGesture(state, evt);
+ mxEvent.consume(evt);
+ }
+ }
+ else if (this.isShapeEvent(state, evt))
+ {
+ graph.dblClick(evt, (state.shape != null &&
+ mxEvent.getSource(evt) == state.shape.content) ?
+ null : state.cell);
+ mxEvent.consume(evt);
+ }
+ })
+ );
+};
+
+/**
+ * Function: redrawLabel
+ *
+ * Redraws the label for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label should be redrawn.
+ */
+mxCellRenderer.prototype.redrawLabel = function(state)
+{
+ var value = this.getLabelValue(state);
+
+ // FIXME: Add label always if HTML label and NO_FO
+ if (state.text == null && value != null && (mxUtils.isNode(value) || value.length > 0))
+ {
+ this.createLabel(state, value);
+ }
+ else if (state.text != null && (value == null || value.length == 0))
+ {
+ state.text.destroy();
+ state.text = null;
+ }
+
+ if (state.text != null)
+ {
+ var graph = state.view.graph;
+ var wrapping = graph.isWrapping(state.cell);
+ var clipping = graph.isLabelClipped(state.cell);
+ var bounds = this.getLabelBounds(state);
+
+ if (state.text.value != value || state.text.isWrapping != wrapping ||
+ state.text.isClipping != clipping || state.text.scale != state.view.scale ||
+ !state.text.bounds.equals(bounds))
+ {
+ state.text.value = value;
+ state.text.bounds = bounds;
+ state.text.scale = this.getTextScale(state);
+ state.text.isWrapping = wrapping;
+ state.text.isClipping = clipping;
+
+ state.text.redraw();
+ }
+ }
+};
+
+/**
+ * Function: getTextScale
+ *
+ * Returns the scaling used for the label of the given state
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label scale should be returned.
+ */
+mxCellRenderer.prototype.getTextScale = function(state)
+{
+ return state.view.scale;
+};
+
+/**
+ * Function: getLabelBounds
+ *
+ * Returns the bounds to be used to draw the label of the given state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose label bounds should be returned.
+ */
+mxCellRenderer.prototype.getLabelBounds = function(state)
+{
+ var graph = state.view.graph;
+ var isEdge = graph.getModel().isEdge(state.cell);
+ var bounds = new mxRectangle(state.absoluteOffset.x, state.absoluteOffset.y);
+
+ if (!isEdge)
+ {
+ bounds.x += state.x;
+ bounds.y += state.y;
+
+ // Minimum of 1 fixes alignment bug in HTML labels
+ bounds.width = Math.max(1, state.width);
+ bounds.height = Math.max(1, state.height);
+
+ if (graph.isSwimlane(state.cell))
+ {
+ var scale = graph.view.scale;
+ var size = graph.getStartSize(state.cell);
+
+ if (size.width > 0)
+ {
+ bounds.width = size.width * scale;
+ }
+ else if (size.height > 0)
+ {
+ bounds.height = size.height * scale;
+ }
+ }
+ }
+
+ return bounds;
+};
+
+/**
+ * Function: redrawCellOverlays
+ *
+ * Redraws the overlays for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose overlays should be redrawn.
+ */
+mxCellRenderer.prototype.redrawCellOverlays = function(state)
+{
+ this.createCellOverlays(state);
+
+ if (state.overlays != null)
+ {
+ state.overlays.visit(function(id, shape)
+ {
+ var bounds = shape.overlay.getBounds(state);
+
+ if (shape.bounds == null || shape.scale != state.view.scale ||
+ !shape.bounds.equals(bounds))
+ {
+ shape.bounds = bounds;
+ shape.scale = state.view.scale;
+ shape.redraw();
+ }
+ });
+ }
+};
+
+/**
+ * Function: redrawControl
+ *
+ * Redraws the control for the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose control should be redrawn.
+ */
+mxCellRenderer.prototype.redrawControl = function(state)
+{
+ if (state.control != null)
+ {
+ var bounds = this.getControlBounds(state);
+ var s = state.view.scale;
+
+ if (state.control.scale != s || !state.control.bounds.equals(bounds))
+ {
+ state.control.bounds = bounds;
+ state.control.scale = s;
+ state.control.redraw();
+ }
+ }
+};
+
+/**
+ * Function: getControlBounds
+ *
+ * Returns the bounds to be used to draw the control (folding icon) of the
+ * given state.
+ */
+mxCellRenderer.prototype.getControlBounds = function(state)
+{
+ if (state.control != null)
+ {
+ var oldScale = state.control.scale;
+ var w = state.control.bounds.width / oldScale;
+ var h = state.control.bounds.height / oldScale;
+ var s = state.view.scale;
+
+ return (state.view.graph.getModel().isEdge(state.cell)) ?
+ new mxRectangle(state.x + state.width / 2 - w / 2 * s,
+ state.y + state.height / 2 - h / 2 * s, w * s, h * s)
+ : new mxRectangle(state.x + w / 2 * s,
+ state.y + h / 2 * s, w * s, h * s);
+ }
+
+ return null;
+};
+
+/**
+ * Function: redraw
+ *
+ * Updates the bounds or points and scale of the shapes for the given cell
+ * state. This is called in mxGraphView.validatePoints as the last step of
+ * updating all cells.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shapes should be updated.
+ * force - Optional boolean that specifies if the cell should be reconfiured
+ * and redrawn without any additional checks.
+ * rendering - Optional boolean that specifies if the cell should actually
+ * be drawn into the DOM. If this is false then redraw and/or reconfigure
+ * will not be called on the shape.
+ */
+mxCellRenderer.prototype.redraw = function(state, force, rendering)
+{
+ if (state.shape != null)
+ {
+ var model = state.view.graph.getModel();
+ var isEdge = model.isEdge(state.cell);
+ reconfigure = (force != null) ? force : false;
+
+ // Handles changes of the collapse icon
+ this.createControl(state);
+
+ // Handles changes to the order in the DOM
+ if (state.orderChanged || state.invalidOrder)
+ {
+ if (state.view.graph.ordered)
+ {
+ this.order(state);
+ }
+ else
+ {
+ // Assert state.cell is edge
+ this.orderEdge(state);
+ }
+
+ // Required to update inherited styles
+ reconfigure = state.orderChanged;
+ }
+
+ delete state.invalidOrder;
+ delete state.orderChanged;
+
+ // Checks if the style in the state is different from the style
+ // in the shape and re-applies the style if required
+ if (!reconfigure && !mxUtils.equalEntries(state.shape.style, state.style))
+ {
+ reconfigure = true;
+ }
+
+ // Reconfiures the shape after an order or style change
+ if (reconfigure)
+ {
+ this.configureShape(state);
+ state.shape.reconfigure();
+ }
+
+ // Redraws the cell if required
+ if (force || state.shape.bounds == null || state.shape.scale != state.view.scale ||
+ !state.shape.bounds.equals(state) ||
+ !mxUtils.equalPoints(state.shape.points, state.absolutePoints))
+ {
+ // FIXME: Move indicator color update into shape.redraw
+// var indicator = state.view.graph.getIndicatorColor(state);
+// if (indicator != null)
+// {
+// state.shape.indicatorColor = indicator;
+// }
+
+ if (state.absolutePoints != null)
+ {
+ state.shape.points = state.absolutePoints.slice();
+ }
+ else
+ {
+ state.shape.points = null;
+ }
+
+ state.shape.bounds = new mxRectangle(
+ state.x, state.y, state.width, state.height);
+ state.shape.scale = state.view.scale;
+
+ if (rendering == null || rendering)
+ {
+ state.shape.redraw();
+ }
+ else
+ {
+ state.shape.updateBoundingBox();
+ }
+ }
+
+ // Updates the text label, overlays and control
+ if (rendering == null || rendering)
+ {
+ this.redrawLabel(state);
+ this.redrawCellOverlays(state);
+ this.redrawControl(state);
+ }
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the shapes associated with the given cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> for which the shapes should be destroyed.
+ */
+mxCellRenderer.prototype.destroy = function(state)
+{
+ if (state.shape != null)
+ {
+ if (state.text != null)
+ {
+ state.text.destroy();
+ state.text = null;
+ }
+
+ if (state.overlays != null)
+ {
+ state.overlays.visit(function(id, shape)
+ {
+ shape.destroy();
+ });
+
+ state.overlays = null;
+ }
+
+ if (state.control != null)
+ {
+ state.control.destroy();
+ state.control = null;
+ }
+
+ state.shape.destroy();
+ state.shape = null;
+ }
+};
diff --git a/src/js/view/mxCellState.js b/src/js/view/mxCellState.js
new file mode 100644
index 0000000..7e7a3b0
--- /dev/null
+++ b/src/js/view/mxCellState.js
@@ -0,0 +1,375 @@
+/**
+ * $Id: mxCellState.js,v 1.42 2012-03-19 10:47:08 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxCellState
+ *
+ * Represents the current state of a cell in a given <mxGraphView>.
+ *
+ * For edges, the edge label position is stored in <absoluteOffset>.
+ *
+ * The size for oversize labels can be retrieved using the boundingBox property
+ * of the <text> field as shown below.
+ *
+ * (code)
+ * var bbox = (state.text != null) ? state.text.boundingBox : null;
+ * (end)
+ *
+ * Constructor: mxCellState
+ *
+ * Constructs a new object that represents the current state of the given
+ * cell in the specified view.
+ *
+ * Parameters:
+ *
+ * view - <mxGraphView> that contains the state.
+ * cell - <mxCell> that this state represents.
+ * style - Array of key, value pairs that constitute the style.
+ */
+function mxCellState(view, cell, style)
+{
+ this.view = view;
+ this.cell = cell;
+ this.style = style;
+
+ this.origin = new mxPoint();
+ this.absoluteOffset = new mxPoint();
+};
+
+/**
+ * Extends mxRectangle.
+ */
+mxCellState.prototype = new mxRectangle();
+mxCellState.prototype.constructor = mxCellState;
+
+/**
+ * Variable: view
+ *
+ * Reference to the enclosing <mxGraphView>.
+ */
+mxCellState.prototype.view = null;
+
+/**
+ * Variable: cell
+ *
+ * Reference to the <mxCell> that is represented by this state.
+ */
+mxCellState.prototype.cell = null;
+
+/**
+ * Variable: style
+ *
+ * Contains an array of key, value pairs that represent the style of the
+ * cell.
+ */
+mxCellState.prototype.style = null;
+
+/**
+ * Variable: invalid
+ *
+ * Specifies if the state is invalid. Default is true.
+ */
+mxCellState.prototype.invalid = true;
+
+/**
+ * Variable: invalidOrder
+ *
+ * Specifies if the cell has an invalid order. For internal use. Default is
+ * false.
+ */
+mxCellState.prototype.invalidOrder = false;
+
+/**
+ * Variable: orderChanged
+ *
+ * Specifies if the cell has changed order and the display needs to be
+ * updated.
+ */
+mxCellState.prototype.orderChanged = false;
+
+/**
+ * Variable: origin
+ *
+ * <mxPoint> that holds the origin for all child cells. Default is a new
+ * empty <mxPoint>.
+ */
+mxCellState.prototype.origin = null;
+
+/**
+ * Variable: absolutePoints
+ *
+ * Holds an array of <mxPoints> that represent the absolute points of an
+ * edge.
+ */
+mxCellState.prototype.absolutePoints = null;
+
+/**
+ * Variable: absoluteOffset
+ *
+ * <mxPoint> that holds the absolute offset. For edges, this is the
+ * absolute coordinates of the label position. For vertices, this is the
+ * offset of the label relative to the top, left corner of the vertex.
+ */
+mxCellState.prototype.absoluteOffset = null;
+
+/**
+ * Variable: visibleSourceState
+ *
+ * Caches the visible source terminal state.
+ */
+mxCellState.prototype.visibleSourceState = null;
+
+/**
+ * Variable: visibleTargetState
+ *
+ * Caches the visible target terminal state.
+ */
+mxCellState.prototype.visibleTargetState = null;
+
+/**
+ * Variable: terminalDistance
+ *
+ * Caches the distance between the end points for an edge.
+ */
+mxCellState.prototype.terminalDistance = 0;
+
+/**
+ * Variable: length
+ *
+ * Caches the length of an edge.
+ */
+mxCellState.prototype.length = 0;
+
+/**
+ * Variable: segments
+ *
+ * Array of numbers that represent the cached length of each segment of the
+ * edge.
+ */
+mxCellState.prototype.segments = null;
+
+/**
+ * Variable: shape
+ *
+ * Holds the <mxShape> that represents the cell graphically.
+ */
+mxCellState.prototype.shape = null;
+
+/**
+ * Variable: text
+ *
+ * Holds the <mxText> that represents the label of the cell. Thi smay be
+ * null if the cell has no label.
+ */
+mxCellState.prototype.text = null;
+
+/**
+ * Function: getPerimeterBounds
+ *
+ * Returns the <mxRectangle> that should be used as the perimeter of the
+ * cell.
+ *
+ * Parameters:
+ *
+ * border - Optional border to be added around the perimeter bounds.
+ * bounds - Optional <mxRectangle> to be used as the initial bounds.
+ */
+mxCellState.prototype.getPerimeterBounds = function (border, bounds)
+{
+ border = border || 0;
+ bounds = (bounds != null) ? bounds : new mxRectangle(this.x, this.y, this.width, this.height);
+
+ if (this.shape != null && this.shape.stencil != null)
+ {
+ var aspect = this.shape.stencil.computeAspect(this, bounds, null);
+
+ bounds.x = aspect.x;
+ bounds.y = aspect.y;
+ bounds.width = this.shape.stencil.w0 * aspect.width;
+ bounds.height = this.shape.stencil.h0 * aspect.height;
+ }
+
+ if (border != 0)
+ {
+ bounds.grow(border);
+ }
+
+ return bounds;
+};
+
+/**
+ * Function: setAbsoluteTerminalPoint
+ *
+ * Sets the first or last point in <absolutePoints> depending on isSource.
+ *
+ * Parameters:
+ *
+ * point - <mxPoint> that represents the terminal point.
+ * isSource - Boolean that specifies if the first or last point should
+ * be assigned.
+ */
+mxCellState.prototype.setAbsoluteTerminalPoint = function (point, isSource)
+{
+ if (isSource)
+ {
+ if (this.absolutePoints == null)
+ {
+ this.absolutePoints = [];
+ }
+
+ if (this.absolutePoints.length == 0)
+ {
+ this.absolutePoints.push(point);
+ }
+ else
+ {
+ this.absolutePoints[0] = point;
+ }
+ }
+ else
+ {
+ if (this.absolutePoints == null)
+ {
+ this.absolutePoints = [];
+ this.absolutePoints.push(null);
+ this.absolutePoints.push(point);
+ }
+ else if (this.absolutePoints.length == 1)
+ {
+ this.absolutePoints.push(point);
+ }
+ else
+ {
+ this.absolutePoints[this.absolutePoints.length - 1] = point;
+ }
+ }
+};
+
+/**
+ * Function: setCursor
+ *
+ * Sets the given cursor on the shape and text shape.
+ */
+mxCellState.prototype.setCursor = function (cursor)
+{
+ if (this.shape != null)
+ {
+ this.shape.setCursor(cursor);
+ }
+
+ if (this.text != null)
+ {
+ this.text.setCursor(cursor);
+ }
+};
+
+/**
+ * Function: getVisibleTerminal
+ *
+ * Returns the visible source or target terminal cell.
+ *
+ * Parameters:
+ *
+ * source - Boolean that specifies if the source or target cell should be
+ * returned.
+ */
+mxCellState.prototype.getVisibleTerminal = function (source)
+{
+ var tmp = this.getVisibleTerminalState(source);
+
+ return (tmp != null) ? tmp.cell : null;
+};
+
+/**
+ * Function: getVisibleTerminalState
+ *
+ * Returns the visible source or target terminal state.
+ *
+ * Parameters:
+ *
+ * source - Boolean that specifies if the source or target state should be
+ * returned.
+ */
+mxCellState.prototype.getVisibleTerminalState = function (source)
+{
+ return (source) ? this.visibleSourceState : this.visibleTargetState;
+};
+
+/**
+ * Function: setVisibleTerminalState
+ *
+ * Sets the visible source or target terminal state.
+ *
+ * Parameters:
+ *
+ * terminalState - <mxCellState> that represents the terminal.
+ * source - Boolean that specifies if the source or target state should be set.
+ */
+mxCellState.prototype.setVisibleTerminalState = function (terminalState, source)
+{
+ if (source)
+ {
+ this.visibleSourceState = terminalState;
+ }
+ else
+ {
+ this.visibleTargetState = terminalState;
+ }
+};
+
+/**
+ * Destructor: destroy
+ *
+ * Destroys the state and all associated resources.
+ */
+mxCellState.prototype.destroy = function ()
+{
+ this.view.graph.cellRenderer.destroy(this);
+};
+
+/**
+ * Function: clone
+ *
+ * Returns a clone of this <mxPoint>.
+ */
+mxCellState.prototype.clone = function()
+{
+ var clone = new mxCellState(this.view, this.cell, this.style);
+
+ // Clones the absolute points
+ if (this.absolutePoints != null)
+ {
+ clone.absolutePoints = [];
+
+ for (var i = 0; i < this.absolutePoints.length; i++)
+ {
+ clone.absolutePoints[i] = this.absolutePoints[i].clone();
+ }
+ }
+
+ if (this.origin != null)
+ {
+ clone.origin = this.origin.clone();
+ }
+
+ if (this.absoluteOffset != null)
+ {
+ clone.absoluteOffset = this.absoluteOffset.clone();
+ }
+
+ if (this.boundingBox != null)
+ {
+ clone.boundingBox = this.boundingBox.clone();
+ }
+
+ clone.terminalDistance = this.terminalDistance;
+ clone.segments = this.segments;
+ clone.length = this.length;
+ clone.x = this.x;
+ clone.y = this.y;
+ clone.width = this.width;
+ clone.height = this.height;
+
+ return clone;
+};
diff --git a/src/js/view/mxCellStatePreview.js b/src/js/view/mxCellStatePreview.js
new file mode 100644
index 0000000..b853748
--- /dev/null
+++ b/src/js/view/mxCellStatePreview.js
@@ -0,0 +1,223 @@
+/**
+ * $Id: mxCellStatePreview.js,v 1.6 2012-10-26 07:19:11 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ *
+ * Class: mxCellStatePreview
+ *
+ * Implements a live preview for moving cells.
+ *
+ * Constructor: mxCellStatePreview
+ *
+ * Constructs a move preview for the given graph.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxCellStatePreview(graph)
+{
+ this.graph = graph;
+ this.deltas = new Object();
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellStatePreview.prototype.graph = null;
+
+/**
+ * Variable: deltas
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxCellStatePreview.prototype.deltas = null;
+
+/**
+ * Variable: count
+ *
+ * Contains the number of entries in the map.
+ */
+mxCellStatePreview.prototype.count = 0;
+
+/**
+ * Function: isEmpty
+ *
+ * Returns true if this contains no entries.
+ */
+mxCellStatePreview.prototype.isEmpty = function()
+{
+ return this.count == 0;
+};
+
+/**
+ * Function: moveState
+ */
+mxCellStatePreview.prototype.moveState = function(state, dx, dy, add, includeEdges)
+{
+ add = (add != null) ? add : true;
+ includeEdges = (includeEdges != null) ? includeEdges : true;
+ var id = mxCellPath.create(state.cell);
+ var delta = this.deltas[id];
+
+ if (delta == null)
+ {
+ delta = new mxPoint(dx, dy);
+ this.deltas[id] = delta;
+ this.count++;
+ }
+ else
+ {
+ if (add)
+ {
+ delta.X += dx;
+ delta.Y += dy;
+ }
+ else
+ {
+ delta.X = dx;
+ delta.Y = dy;
+ }
+ }
+
+ if (includeEdges)
+ {
+ this.addEdges(state);
+ }
+
+ return delta;
+};
+
+/**
+ * Function: show
+ */
+mxCellStatePreview.prototype.show = function(visitor)
+{
+ var model = this.graph.getModel();
+ var root = model.getRoot();
+
+ // Translates the states in step
+ for (var id in this.deltas)
+ {
+ var cell = mxCellPath.resolve(root, id);
+ var state = this.graph.view.getState(cell);
+ var delta = this.deltas[id];
+ var parentState = this.graph.view.getState(
+ model.getParent(cell));
+ this.translateState(parentState, state, delta.x, delta.y);
+ }
+
+ // Revalidates the states in step
+ for (var id in this.deltas)
+ {
+ var cell = mxCellPath.resolve(root, id);
+ var state = this.graph.view.getState(cell);
+ var delta = this.deltas[id];
+ var parentState = this.graph.view.getState(
+ model.getParent(cell));
+ this.revalidateState(parentState, state, delta.x, delta.y, visitor);
+ }
+};
+
+/**
+ * Function: translateState
+ */
+mxCellStatePreview.prototype.translateState = function(parentState, state, dx, dy)
+{
+ if (state != null)
+ {
+ var model = this.graph.getModel();
+
+ if (model.isVertex(state.cell))
+ {
+ // LATER: Use hashtable to store initial state bounds
+ state.invalid = true;
+ this.graph.view.validateBounds(parentState, state.cell);
+ var geo = model.getGeometry(state.cell);
+ var id = mxCellPath.create(state.cell);
+
+ // Moves selection cells and non-relative vertices in
+ // the first phase so that edge terminal points will
+ // be updated in the second phase
+ if ((dx != 0 || dy != 0) && geo != null &&
+ (!geo.relative || this.deltas[id] != null))
+ {
+ state.x += dx;
+ state.y += dy;
+ }
+ }
+
+ var childCount = model.getChildCount(state.cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.translateState(state, this.graph.view.getState(
+ model.getChildAt(state.cell, i)), dx, dy);
+ }
+ }
+};
+
+/**
+ * Function: revalidateState
+ */
+mxCellStatePreview.prototype.revalidateState = function(parentState, state, dx, dy, visitor)
+{
+ if (state != null)
+ {
+ // Updates the edge terminal points and restores the
+ // (relative) positions of any (relative) children
+ state.invalid = true;
+ this.graph.view.validatePoints(parentState, state.cell);
+
+ // Moves selection vertices which are relative
+ var id = mxCellPath.create(state.cell);
+ var model = this.graph.getModel();
+ var geo = this.graph.getCellGeometry(state.cell);
+
+ if ((dx != 0 || dy != 0) && geo != null && geo.relative &&
+ model.isVertex(state.cell) && (parentState == null ||
+ model.isVertex(parentState.cell) || this.deltas[id] != null))
+ {
+ state.x += dx;
+ state.y += dy;
+
+ this.graph.cellRenderer.redraw(state);
+ }
+
+ // Invokes the visitor on the given state
+ if (visitor != null)
+ {
+ visitor(state);
+ }
+
+ var childCount = model.getChildCount(state.cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.revalidateState(state, this.graph.view.getState(model.getChildAt(
+ state.cell, i)), dx, dy, visitor);
+ }
+ }
+};
+
+/**
+ * Function: addEdges
+ */
+mxCellStatePreview.prototype.addEdges = function(state)
+{
+ var model = this.graph.getModel();
+ var edgeCount = model.getEdgeCount(state.cell);
+
+ for (var i = 0; i < edgeCount; i++)
+ {
+ var s = this.graph.view.getState(model.getEdgeAt(state.cell, i));
+
+ if (s != null)
+ {
+ this.moveState(s, 0, 0);
+ }
+ }
+};
diff --git a/src/js/view/mxConnectionConstraint.js b/src/js/view/mxConnectionConstraint.js
new file mode 100644
index 0000000..70f457f
--- /dev/null
+++ b/src/js/view/mxConnectionConstraint.js
@@ -0,0 +1,42 @@
+/**
+ * $Id: mxConnectionConstraint.js,v 1.2 2010-04-29 09:33:52 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxConnectionConstraint
+ *
+ * Defines an object that contains the constraints about how to connect one
+ * side of an edge to its terminal.
+ *
+ * Constructor: mxConnectionConstraint
+ *
+ * Constructs a new connection constraint for the given point and boolean
+ * arguments.
+ *
+ * Parameters:
+ *
+ * point - Optional <mxPoint> that specifies the fixed location of the point
+ * in relative coordinates. Default is null.
+ * perimeter - Optional boolean that specifies if the fixed point should be
+ * projected onto the perimeter of the terminal. Default is true.
+ */
+function mxConnectionConstraint(point, perimeter)
+{
+ this.point = point;
+ this.perimeter = (perimeter != null) ? perimeter : true;
+};
+
+/**
+ * Variable: point
+ *
+ * <mxPoint> that specifies the fixed location of the connection point.
+ */
+mxConnectionConstraint.prototype.point = null;
+
+/**
+ * Variable: perimeter
+ *
+ * Boolean that specifies if the point should be projected onto the perimeter
+ * of the terminal.
+ */
+mxConnectionConstraint.prototype.perimeter = null;
diff --git a/src/js/view/mxEdgeStyle.js b/src/js/view/mxEdgeStyle.js
new file mode 100644
index 0000000..41493d6
--- /dev/null
+++ b/src/js/view/mxEdgeStyle.js
@@ -0,0 +1,1302 @@
+/**
+ * $Id: mxEdgeStyle.js,v 1.68 2012-11-20 09:06:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxEdgeStyle =
+{
+ /**
+ * Class: mxEdgeStyle
+ *
+ * Provides various edge styles to be used as the values for
+ * <mxConstants.STYLE_EDGE> in a cell style.
+ *
+ * Example:
+ *
+ * (code)
+ * var style = stylesheet.getDefaultEdgeStyle();
+ * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.ElbowConnector;
+ * (end)
+ *
+ * Sets the default edge style to <ElbowConnector>.
+ *
+ * Custom edge style:
+ *
+ * To write a custom edge style, a function must be added to the mxEdgeStyle
+ * object as follows:
+ *
+ * (code)
+ * mxEdgeStyle.MyStyle = function(state, source, target, points, result)
+ * {
+ * if (source != null && target != null)
+ * {
+ * var pt = new mxPoint(target.getCenterX(), source.getCenterY());
+ *
+ * if (mxUtils.contains(source, pt.x, pt.y))
+ * {
+ * pt.y = source.y + source.height;
+ * }
+ *
+ * result.push(pt);
+ * }
+ * };
+ * (end)
+ *
+ * In the above example, a right angle is created using a point on the
+ * horizontal center of the target vertex and the vertical center of the source
+ * vertex. The code checks if that point intersects the source vertex and makes
+ * the edge straight if it does. The point is then added into the result array,
+ * which acts as the return value of the function.
+ *
+ * The new edge style should then be registered in the <mxStyleRegistry> as follows:
+ * (code)
+ * mxStyleRegistry.putValue('myEdgeStyle', mxEdgeStyle.MyStyle);
+ * (end)
+ *
+ * The custom edge style above can now be used in a specific edge as follows:
+ *
+ * (code)
+ * model.setStyle(edge, 'edgeStyle=myEdgeStyle');
+ * (end)
+ *
+ * Note that the key of the <mxStyleRegistry> entry for the function should
+ * be used in string values, unless <mxGraphView.allowEval> is true, in
+ * which case you can also use mxEdgeStyle.MyStyle for the value in the
+ * cell style above.
+ *
+ * Or it can be used for all edges in the graph as follows:
+ *
+ * (code)
+ * var style = graph.getStylesheet().getDefaultEdgeStyle();
+ * style[mxConstants.STYLE_EDGE] = mxEdgeStyle.MyStyle;
+ * (end)
+ *
+ * Note that the object can be used directly when programmatically setting
+ * the value, but the key in the <mxStyleRegistry> should be used when
+ * setting the value via a key, value pair in a cell style.
+ *
+ * Function: EntityRelation
+ *
+ * Implements an entity relation style for edges (as used in database
+ * schema diagrams). At the time the function is called, the result
+ * array contains a placeholder (null) for the first absolute point,
+ * that is, the point where the edge and source terminal are connected.
+ * The implementation of the style then adds all intermediate waypoints
+ * except for the last point, that is, the connection point between the
+ * edge and the target terminal. The first ant the last point in the
+ * result array are then replaced with mxPoints that take into account
+ * the terminal's perimeter and next point on the edge.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the edge to be updated.
+ * source - <mxCellState> that represents the source terminal.
+ * target - <mxCellState> that represents the target terminal.
+ * points - List of relative control points.
+ * result - Array of <mxPoints> that represent the actual points of the
+ * edge.
+ */
+ EntityRelation: function (state, source, target, points, result)
+ {
+ var view = state.view;
+ var graph = view.graph;
+ var segment = mxUtils.getValue(state.style,
+ mxConstants.STYLE_SEGMENT,
+ mxConstants.ENTITY_SEGMENT) * view.scale;
+
+ var pts = state.absolutePoints;
+ var p0 = pts[0];
+ var pe = pts[pts.length-1];
+
+ var isSourceLeft = false;
+
+ if (p0 != null)
+ {
+ source = new mxCellState();
+ source.x = p0.x;
+ source.y = p0.y;
+ }
+ else if (source != null)
+ {
+ var constraint = mxUtils.getPortConstraints(source, state, true, mxConstants.DIRECTION_MASK_NONE);
+
+ if (constraint != mxConstants.DIRECTION_MASK_NONE)
+ {
+ isSourceLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
+ }
+ else
+ {
+ var sourceGeometry = graph.getCellGeometry(source.cell);
+
+ if (sourceGeometry.relative)
+ {
+ isSourceLeft = sourceGeometry.x <= 0.5;
+ }
+ else if (target != null)
+ {
+ isSourceLeft = target.x + target.width < source.x;
+ }
+ }
+ }
+ else
+ {
+ return;
+ }
+
+ var isTargetLeft = true;
+
+ if (pe != null)
+ {
+ target = new mxCellState();
+ target.x = pe.x;
+ target.y = pe.y;
+ }
+ else if (target != null)
+ {
+ var constraint = mxUtils.getPortConstraints(target, state, false, mxConstants.DIRECTION_MASK_NONE);
+
+ if (constraint != mxConstants.DIRECTION_MASK_NONE)
+ {
+ isTargetLeft = constraint == mxConstants.DIRECTION_MASK_WEST;
+ }
+ else
+ {
+ var targetGeometry = graph.getCellGeometry(target.cell);
+
+ if (targetGeometry.relative)
+ {
+ isTargetLeft = targetGeometry.x <= 0.5;
+ }
+ else if (source != null)
+ {
+ isTargetLeft = source.x + source.width < target.x;
+ }
+ }
+ }
+
+ if (source != null && target != null)
+ {
+ var x0 = (isSourceLeft) ? source.x : source.x + source.width;
+ var y0 = view.getRoutingCenterY(source);
+
+ var xe = (isTargetLeft) ? target.x : target.x + target.width;
+ var ye = view.getRoutingCenterY(target);
+
+ var seg = segment;
+
+ var dx = (isSourceLeft) ? -seg : seg;
+ var dep = new mxPoint(x0 + dx, y0);
+
+ dx = (isTargetLeft) ? -seg : seg;
+ var arr = new mxPoint(xe + dx, ye);
+
+ // Adds intermediate points if both go out on same side
+ if (isSourceLeft == isTargetLeft)
+ {
+ var x = (isSourceLeft) ?
+ Math.min(x0, xe)-segment :
+ Math.max(x0, xe)+segment;
+
+ result.push(new mxPoint(x, y0));
+ result.push(new mxPoint(x, ye));
+ }
+ else if ((dep.x < arr.x) == isSourceLeft)
+ {
+ var midY = y0 + (ye - y0) / 2;
+
+ result.push(dep);
+ result.push(new mxPoint(dep.x, midY));
+ result.push(new mxPoint(arr.x, midY));
+ result.push(arr);
+ }
+ else
+ {
+ result.push(dep);
+ result.push(arr);
+ }
+ }
+ },
+
+ /**
+ * Function: Loop
+ *
+ * Implements a self-reference, aka. loop.
+ */
+ Loop: function (state, source, target, points, result)
+ {
+ if (source != null)
+ {
+ var view = state.view;
+ var graph = view.graph;
+ var pt = (points != null && points.length > 0) ? points[0] : null;
+
+ if (pt != null)
+ {
+ pt = view.transformControlPoint(state, pt);
+
+ if (mxUtils.contains(source, pt.x, pt.y))
+ {
+ pt = null;
+ }
+ }
+
+ var x = 0;
+ var dx = 0;
+ var y = 0;
+ var dy = 0;
+
+ var seg = mxUtils.getValue(state.style, mxConstants.STYLE_SEGMENT,
+ graph.gridSize) * view.scale;
+ var dir = mxUtils.getValue(state.style, mxConstants.STYLE_DIRECTION,
+ mxConstants.DIRECTION_WEST);
+
+ if (dir == mxConstants.DIRECTION_NORTH ||
+ dir == mxConstants.DIRECTION_SOUTH)
+ {
+ x = view.getRoutingCenterX(source);
+ dx = seg;
+ }
+ else
+ {
+ y = view.getRoutingCenterY(source);
+ dy = seg;
+ }
+
+ if (pt == null ||
+ pt.x < source.x ||
+ pt.x > source.x + source.width)
+ {
+ if (pt != null)
+ {
+ x = pt.x;
+ dy = Math.max(Math.abs(y - pt.y), dy);
+ }
+ else
+ {
+ if (dir == mxConstants.DIRECTION_NORTH)
+ {
+ y = source.y - 2 * dx;
+ }
+ else if (dir == mxConstants.DIRECTION_SOUTH)
+ {
+ y = source.y + source.height + 2 * dx;
+ }
+ else if (dir == mxConstants.DIRECTION_EAST)
+ {
+ x = source.x - 2 * dy;
+ }
+ else
+ {
+ x = source.x + source.width + 2 * dy;
+ }
+ }
+ }
+ else if (pt != null)
+ {
+ x = view.getRoutingCenterX(source);
+ dx = Math.max(Math.abs(x - pt.x), dy);
+ y = pt.y;
+ dy = 0;
+ }
+
+ result.push(new mxPoint(x - dx, y - dy));
+ result.push(new mxPoint(x + dx, y + dy));
+ }
+ },
+
+ /**
+ * Function: ElbowConnector
+ *
+ * Uses either <SideToSide> or <TopToBottom> depending on the horizontal
+ * flag in the cell style. <SideToSide> is used if horizontal is true or
+ * unspecified. See <EntityRelation> for a description of the
+ * parameters.
+ */
+ ElbowConnector: function (state, source, target, points, result)
+ {
+ var pt = (points != null && points.length > 0) ? points[0] : null;
+
+ var vertical = false;
+ var horizontal = false;
+
+ if (source != null && target != null)
+ {
+ if (pt != null)
+ {
+ var left = Math.min(source.x, target.x);
+ var right = Math.max(source.x + source.width,
+ target.x + target.width);
+
+ var top = Math.min(source.y, target.y);
+ var bottom = Math.max(source.y + source.height,
+ target.y + target.height);
+
+ pt = state.view.transformControlPoint(state, pt);
+
+ vertical = pt.y < top || pt.y > bottom;
+ horizontal = pt.x < left || pt.x > right;
+ }
+ else
+ {
+ var left = Math.max(source.x, target.x);
+ var right = Math.min(source.x + source.width,
+ target.x + target.width);
+
+ vertical = left == right;
+
+ if (!vertical)
+ {
+ var top = Math.max(source.y, target.y);
+ var bottom = Math.min(source.y + source.height,
+ target.y + target.height);
+
+ horizontal = top == bottom;
+ }
+ }
+ }
+
+ if (!horizontal && (vertical ||
+ state.style[mxConstants.STYLE_ELBOW] == mxConstants.ELBOW_VERTICAL))
+ {
+ mxEdgeStyle.TopToBottom(state, source, target, points, result);
+ }
+ else
+ {
+ mxEdgeStyle.SideToSide(state, source, target, points, result);
+ }
+ },
+
+ /**
+ * Function: SideToSide
+ *
+ * Implements a vertical elbow edge. See <EntityRelation> for a description
+ * of the parameters.
+ */
+ SideToSide: function (state, source, target, points, result)
+ {
+ var view = state.view;
+ var pt = (points != null && points.length > 0) ? points[0] : null;
+ var pts = state.absolutePoints;
+ var p0 = pts[0];
+ var pe = pts[pts.length-1];
+
+ if (pt != null)
+ {
+ pt = view.transformControlPoint(state, pt);
+ }
+
+ if (p0 != null)
+ {
+ source = new mxCellState();
+ source.x = p0.x;
+ source.y = p0.y;
+ }
+
+ if (pe != null)
+ {
+ target = new mxCellState();
+ target.x = pe.x;
+ target.y = pe.y;
+ }
+
+ if (source != null && target != null)
+ {
+ var l = Math.max(source.x, target.x);
+ var r = Math.min(source.x + source.width,
+ target.x + target.width);
+
+ var x = (pt != null) ? pt.x : r + (l - r) / 2;
+
+ var y1 = view.getRoutingCenterY(source);
+ var y2 = view.getRoutingCenterY(target);
+
+ if (pt != null)
+ {
+ if (pt.y >= source.y && pt.y <= source.y + source.height)
+ {
+ y1 = pt.y;
+ }
+
+ if (pt.y >= target.y && pt.y <= target.y + target.height)
+ {
+ y2 = pt.y;
+ }
+ }
+
+ if (!mxUtils.contains(target, x, y1) &&
+ !mxUtils.contains(source, x, y1))
+ {
+ result.push(new mxPoint(x, y1));
+ }
+
+ if (!mxUtils.contains(target, x, y2) &&
+ !mxUtils.contains(source, x, y2))
+ {
+ result.push(new mxPoint(x, y2));
+ }
+
+ if (result.length == 1)
+ {
+ if (pt != null)
+ {
+ if (!mxUtils.contains(target, x, pt.y) &&
+ !mxUtils.contains(source, x, pt.y))
+ {
+ result.push(new mxPoint(x, pt.y));
+ }
+ }
+ else
+ {
+ var t = Math.max(source.y, target.y);
+ var b = Math.min(source.y + source.height,
+ target.y + target.height);
+
+ result.push(new mxPoint(x, t + (b - t) / 2));
+ }
+ }
+ }
+ },
+
+ /**
+ * Function: TopToBottom
+ *
+ * Implements a horizontal elbow edge. See <EntityRelation> for a
+ * description of the parameters.
+ */
+ TopToBottom: function(state, source, target, points, result)
+ {
+ var view = state.view;
+ var pt = (points != null && points.length > 0) ? points[0] : null;
+ var pts = state.absolutePoints;
+ var p0 = pts[0];
+ var pe = pts[pts.length-1];
+
+ if (pt != null)
+ {
+ pt = view.transformControlPoint(state, pt);
+ }
+
+ if (p0 != null)
+ {
+ source = new mxCellState();
+ source.x = p0.x;
+ source.y = p0.y;
+ }
+
+ if (pe != null)
+ {
+ target = new mxCellState();
+ target.x = pe.x;
+ target.y = pe.y;
+ }
+
+ if (source != null && target != null)
+ {
+ var t = Math.max(source.y, target.y);
+ var b = Math.min(source.y + source.height,
+ target.y + target.height);
+
+ var x = view.getRoutingCenterX(source);
+
+ if (pt != null &&
+ pt.x >= source.x &&
+ pt.x <= source.x + source.width)
+ {
+ x = pt.x;
+ }
+
+ var y = (pt != null) ? pt.y : b + (t - b) / 2;
+
+ if (!mxUtils.contains(target, x, y) &&
+ !mxUtils.contains(source, x, y))
+ {
+ result.push(new mxPoint(x, y));
+ }
+
+ if (pt != null &&
+ pt.x >= target.x &&
+ pt.x <= target.x + target.width)
+ {
+ x = pt.x;
+ }
+ else
+ {
+ x = view.getRoutingCenterX(target);
+ }
+
+ if (!mxUtils.contains(target, x, y) &&
+ !mxUtils.contains(source, x, y))
+ {
+ result.push(new mxPoint(x, y));
+ }
+
+ if (result.length == 1)
+ {
+ if (pt != null && result.length == 1)
+ {
+ if (!mxUtils.contains(target, pt.x, y) &&
+ !mxUtils.contains(source, pt.x, y))
+ {
+ result.push(new mxPoint(pt.x, y));
+ }
+ }
+ else
+ {
+ var l = Math.max(source.x, target.x);
+ var r = Math.min(source.x + source.width,
+ target.x + target.width);
+
+ result.push(new mxPoint(l + (r - l) / 2, y));
+ }
+ }
+ }
+ },
+
+ /**
+ * Function: SegmentConnector
+ *
+ * Implements an orthogonal edge style. Use <mxEdgeSegmentHandler>
+ * as an interactive handler for this style.
+ */
+ SegmentConnector: function(state, source, target, hints, result)
+ {
+ // Creates array of all way- and terminalpoints
+ var pts = state.absolutePoints;
+ var horizontal = true;
+ var hint = null;
+
+ // Adds the first point
+ var pt = pts[0];
+
+ if (pt == null && source != null)
+ {
+ pt = new mxPoint(state.view.getRoutingCenterX(source), state.view.getRoutingCenterY(source));
+ }
+ else if (pt != null)
+ {
+ pt = pt.clone();
+ }
+
+ var lastInx = pts.length - 1;
+
+ // Adds the waypoints
+ if (hints != null && hints.length > 0)
+ {
+ hint = state.view.transformControlPoint(state, hints[0]);
+
+ var currentTerm = source;
+ var currentPt = pts[0];
+ var hozChan = false;
+ var vertChan = false;
+ var currentHint = hint;
+ var hintsLen = hints.length;
+
+ for (var i = 0; i < 2; i++)
+ {
+ var fixedVertAlign = currentPt != null && currentPt.x == currentHint.x;
+ var fixedHozAlign = currentPt != null && currentPt.y == currentHint.y;
+ var inHozChan = currentTerm != null && (currentHint.y >= currentTerm.y &&
+ currentHint.y <= currentTerm.y + currentTerm.height);
+ var inVertChan = currentTerm != null && (currentHint.x >= currentTerm.x &&
+ currentHint.x <= currentTerm.x + currentTerm.width);
+
+ hozChan = fixedHozAlign || (currentPt == null && inHozChan);
+ vertChan = fixedVertAlign || (currentPt == null && inVertChan);
+
+ if (currentPt != null && (!fixedHozAlign && !fixedVertAlign) && (inHozChan || inVertChan))
+ {
+ horizontal = inHozChan ? false : true;
+ break;
+ }
+
+ if (vertChan || hozChan)
+ {
+ horizontal = hozChan;
+
+ if (i == 1)
+ {
+ // Work back from target end
+ horizontal = hints.length % 2 == 0 ? hozChan : vertChan;
+ }
+
+ break;
+ }
+
+ currentTerm = target;
+ currentPt = pts[lastInx];
+ currentHint = state.view.transformControlPoint(state, hints[hintsLen - 1]);
+ }
+
+ if (horizontal && ((pts[0] != null && pts[0].y != hint.y) ||
+ (pts[0] == null && source != null &&
+ (hint.y < source.y || hint.y > source.y + source.height))))
+ {
+ result.push(new mxPoint(pt.x, hint.y));
+ }
+ else if (!horizontal && ((pts[0] != null && pts[0].x != hint.x) ||
+ (pts[0] == null && source != null &&
+ (hint.x < source.x || hint.x > source.x + source.width))))
+ {
+ result.push(new mxPoint(hint.x, pt.y));
+ }
+
+ if (horizontal)
+ {
+ pt.y = hint.y;
+ }
+ else
+ {
+ pt.x = hint.x;
+ }
+
+ for (var i = 0; i < hints.length; i++)
+ {
+ horizontal = !horizontal;
+ hint = state.view.transformControlPoint(state, hints[i]);
+
+// mxLog.show();
+// mxLog.debug('hint', i, hint.x, hint.y);
+
+ if (horizontal)
+ {
+ pt.y = hint.y;
+ }
+ else
+ {
+ pt.x = hint.x;
+ }
+
+ result.push(pt.clone());
+ }
+ }
+ else
+ {
+ hint = pt;
+ // FIXME: First click in connect preview toggles orientation
+ horizontal = true;
+ }
+
+ // Adds the last point
+ pt = pts[lastInx];
+
+ if (pt == null && target != null)
+ {
+ pt = new mxPoint(state.view.getRoutingCenterX(target), state.view.getRoutingCenterY(target));
+ }
+
+ if (horizontal && ((pts[lastInx] != null && pts[lastInx].y != hint.y) ||
+ (pts[lastInx] == null && target != null &&
+ (hint.y < target.y || hint.y > target.y + target.height))))
+ {
+ result.push(new mxPoint(pt.x, hint.y));
+ }
+ else if (!horizontal && ((pts[lastInx] != null && pts[lastInx].x != hint.x) ||
+ (pts[lastInx] == null && target != null &&
+ (hint.x < target.x || hint.x > target.x + target.width))))
+ {
+ result.push(new mxPoint(hint.x, pt.y));
+ }
+
+ // Removes bends inside the source terminal for floating ports
+ if (pts[0] == null && source != null)
+ {
+ while (result.length > 1 && mxUtils.contains(source, result[1].x, result[1].y))
+ {
+ result = result.splice(1, 1);
+ }
+ }
+
+ // Removes bends inside the target terminal
+ if (pts[lastInx] == null && target != null)
+ {
+ while (result.length > 1 && mxUtils.contains(target, result[result.length - 1].x, result[result.length - 1].y))
+ {
+ result = result.splice(result.length - 1, 1);
+ }
+ }
+
+ },
+
+ orthBuffer: 10,
+
+ dirVectors: [ [ -1, 0 ],
+ [ 0, -1 ], [ 1, 0 ], [ 0, 1 ], [ -1, 0 ], [ 0, -1 ], [ 1, 0 ] ],
+
+ wayPoints1: [ [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0],
+ [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0] ],
+
+ routePatterns: [
+ [ [ 513, 2308, 2081, 2562 ], [ 513, 1090, 514, 2184, 2114, 2561 ],
+ [ 513, 1090, 514, 2564, 2184, 2562 ],
+ [ 513, 2308, 2561, 1090, 514, 2568, 2308 ] ],
+ [ [ 514, 1057, 513, 2308, 2081, 2562 ], [ 514, 2184, 2114, 2561 ],
+ [ 514, 2184, 2562, 1057, 513, 2564, 2184 ],
+ [ 514, 1057, 513, 2568, 2308, 2561 ] ],
+ [ [ 1090, 514, 1057, 513, 2308, 2081, 2562 ], [ 2114, 2561 ],
+ [ 1090, 2562, 1057, 513, 2564, 2184 ],
+ [ 1090, 514, 1057, 513, 2308, 2561, 2568 ] ],
+ [ [ 2081, 2562 ], [ 1057, 513, 1090, 514, 2184, 2114, 2561 ],
+ [ 1057, 513, 1090, 514, 2184, 2562, 2564 ],
+ [ 1057, 2561, 1090, 514, 2568, 2308 ] ] ],
+
+ inlineRoutePatterns: [
+ [ null, [ 2114, 2568 ], null, null ],
+ [ null, [ 514, 2081, 2114, 2568 ] , null, null ],
+ [ null, [ 2114, 2561 ], null, null ],
+ [ [ 2081, 2562 ], [ 1057, 2114, 2568 ],
+ [ 2184, 2562 ],
+ null ] ],
+ vertexSeperations: [],
+
+ limits: [
+ [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
+ [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ] ],
+
+ LEFT_MASK: 32,
+
+ TOP_MASK: 64,
+
+ RIGHT_MASK: 128,
+
+ BOTTOM_MASK: 256,
+
+ LEFT: 1,
+
+ TOP: 2,
+
+ RIGHT: 4,
+
+ BOTTOM: 8,
+
+ // TODO remove magic numbers
+ SIDE_MASK: 480,
+ //mxEdgeStyle.LEFT_MASK | mxEdgeStyle.TOP_MASK | mxEdgeStyle.RIGHT_MASK
+ //| mxEdgeStyle.BOTTOM_MASK,
+
+ CENTER_MASK: 512,
+
+ SOURCE_MASK: 1024,
+
+ TARGET_MASK: 2048,
+
+ VERTEX_MASK: 3072,
+ // mxEdgeStyle.SOURCE_MASK | mxEdgeStyle.TARGET_MASK,
+
+ /**
+ * Function: OrthConnector
+ *
+ * Implements a local orthogonal router between the given
+ * cells.
+ */
+ OrthConnector: function(state, source, target, points, result)
+ {
+ var graph = state.view.graph;
+ var sourceEdge = source == null ? false : graph.getModel().isEdge(source.cell);
+ var targetEdge = target == null ? false : graph.getModel().isEdge(target.cell);
+
+ if ((points != null && points.length > 0) || (sourceEdge) || (targetEdge))
+ {
+ mxEdgeStyle.SegmentConnector(state, source, target, points, result);
+ return;
+ }
+
+ var pts = state.absolutePoints;
+ var p0 = pts[0];
+ var pe = pts[pts.length-1];
+
+ var sourceX = source != null ? source.x : p0.x;
+ var sourceY = source != null ? source.y : p0.y;
+ var sourceWidth = source != null ? source.width : 1;
+ var sourceHeight = source != null ? source.height : 1;
+
+ var targetX = target != null ? target.x : pe.x;
+ var targetY = target != null ? target.y : pe.y;
+ var targetWidth = target != null ? target.width : 1;
+ var targetHeight = target != null ? target.height : 1;
+
+ var scaledOrthBuffer = state.view.scale * mxEdgeStyle.orthBuffer;
+ // Determine the side(s) of the source and target vertices
+ // that the edge may connect to
+ // portConstraint [source, target]
+ var portConstraint = [mxConstants.DIRECTION_MASK_ALL, mxConstants.DIRECTION_MASK_ALL];
+
+ if (source != null)
+ {
+ portConstraint[0] = mxUtils.getPortConstraints(source, state, true,
+ mxConstants.DIRECTION_MASK_ALL);
+ }
+
+ if (target != null)
+ {
+ portConstraint[1] = mxUtils.getPortConstraints(target, state, false,
+ mxConstants.DIRECTION_MASK_ALL);
+ }
+
+ var dir = [0, 0] ;
+
+ // Work out which faces of the vertices present against each other
+ // in a way that would allow a 3-segment connection if port constraints
+ // permitted.
+ // geo -> [source, target] [x, y, width, height]
+ var geo = [ [sourceX, sourceY, sourceWidth, sourceHeight] ,
+ [targetX, targetY, targetWidth, targetHeight] ];
+
+ for (var i = 0; i < 2; i++)
+ {
+ mxEdgeStyle.limits[i][1] = geo[i][0] - scaledOrthBuffer;
+ mxEdgeStyle.limits[i][2] = geo[i][1] - scaledOrthBuffer;
+ mxEdgeStyle.limits[i][4] = geo[i][0] + geo[i][2] + scaledOrthBuffer;
+ mxEdgeStyle.limits[i][8] = geo[i][1] + geo[i][3] + scaledOrthBuffer;
+ }
+
+ // Work out which quad the target is in
+ var sourceCenX = geo[0][0] + geo[0][2] / 2.0;
+ var sourceCenY = geo[0][1] + geo[0][3] / 2.0;
+ var targetCenX = geo[1][0] + geo[1][2] / 2.0;
+ var targetCenY = geo[1][1] + geo[1][3] / 2.0;
+
+ var dx = sourceCenX - targetCenX;
+ var dy = sourceCenY - targetCenY;
+
+ var quad = 0;
+
+ if (dx < 0)
+ {
+ if (dy < 0)
+ {
+ quad = 2;
+ }
+ else
+ {
+ quad = 1;
+ }
+ }
+ else
+ {
+ if (dy <= 0)
+ {
+ quad = 3;
+
+ // Special case on x = 0 and negative y
+ if (dx == 0)
+ {
+ quad = 2;
+ }
+ }
+ }
+
+ // Check for connection constraints
+ var currentTerm = null;
+
+ if (source != null)
+ {
+ currentTerm = p0;
+ }
+
+ var constraint = [ [0.5, 0.5] , [0.5, 0.5] ];
+
+ for (var i = 0; i < 2; i++)
+ {
+ if (currentTerm != null)
+ {
+ constraint[i][0] = (currentTerm.x - geo[i][0]) / geo[i][2];
+
+ if (constraint[i][0] < 0.01)
+ {
+ dir[i] = mxConstants.DIRECTION_MASK_WEST;
+ }
+ else if (constraint[i][0] > 0.99)
+ {
+ dir[i] = mxConstants.DIRECTION_MASK_EAST;
+ }
+
+ constraint[i][1] = (currentTerm.y - geo[i][1]) / geo[i][3];
+
+ if (constraint[i][1] < 0.01)
+ {
+ dir[i] = mxConstants.DIRECTION_MASK_NORTH;
+ }
+ else if (constraint[i][1] > 0.99)
+ {
+ dir[i] = mxConstants.DIRECTION_MASK_SOUTH;
+ }
+ }
+
+ currentTerm = null;
+
+ if (target != null)
+ {
+ currentTerm = pe;
+ }
+ }
+
+ var sourceTopDist = geo[0][1] - (geo[1][1] + geo[1][3]);
+ var sourceLeftDist = geo[0][0] - (geo[1][0] + geo[1][2]);
+ var sourceBottomDist = geo[1][1] - (geo[0][1] + geo[0][3]);
+ var sourceRightDist = geo[1][0] - (geo[0][0] + geo[0][2]);
+
+ mxEdgeStyle.vertexSeperations[1] = Math.max(
+ sourceLeftDist - 2 * scaledOrthBuffer, 0);
+ mxEdgeStyle.vertexSeperations[2] = Math.max(sourceTopDist - 2 * scaledOrthBuffer,
+ 0);
+ mxEdgeStyle.vertexSeperations[4] = Math.max(sourceBottomDist - 2
+ * scaledOrthBuffer, 0);
+ mxEdgeStyle.vertexSeperations[3] = Math.max(sourceRightDist - 2
+ * scaledOrthBuffer, 0);
+
+ //==============================================================
+ // Start of source and target direction determination
+
+ // Work through the preferred orientations by relative positioning
+ // of the vertices and list them in preferred and available order
+
+ var dirPref = [];
+ var horPref = [];
+ var vertPref = [];
+
+ horPref[0] = (sourceLeftDist >= sourceRightDist) ? mxConstants.DIRECTION_MASK_WEST
+ : mxConstants.DIRECTION_MASK_EAST;
+ vertPref[0] = (sourceTopDist >= sourceBottomDist) ? mxConstants.DIRECTION_MASK_NORTH
+ : mxConstants.DIRECTION_MASK_SOUTH;
+
+ horPref[1] = mxUtils.reversePortConstraints(horPref[0]);
+ vertPref[1] = mxUtils.reversePortConstraints(vertPref[0]);
+
+ var preferredHorizDist = sourceLeftDist >= sourceRightDist ? sourceLeftDist
+ : sourceRightDist;
+ var preferredVertDist = sourceTopDist >= sourceBottomDist ? sourceTopDist
+ : sourceBottomDist;
+
+ var prefOrdering = [ [0, 0] , [0, 0] ];
+ var preferredOrderSet = false;
+
+ // If the preferred port isn't available, switch it
+ for (var i = 0; i < 2; i++)
+ {
+ if (dir[i] != 0x0)
+ {
+ continue;
+ }
+
+ if ((horPref[i] & portConstraint[i]) == 0)
+ {
+ horPref[i] = mxUtils.reversePortConstraints(horPref[i]);
+ }
+
+ if ((vertPref[i] & portConstraint[i]) == 0)
+ {
+ vertPref[i] = mxUtils
+ .reversePortConstraints(vertPref[i]);
+ }
+
+ prefOrdering[i][0] = vertPref[i];
+ prefOrdering[i][1] = horPref[i];
+ }
+
+ if (preferredVertDist > scaledOrthBuffer * 2
+ && preferredHorizDist > scaledOrthBuffer * 2)
+ {
+ // Possibility of two segment edge connection
+ if (((horPref[0] & portConstraint[0]) > 0)
+ && ((vertPref[1] & portConstraint[1]) > 0))
+ {
+ prefOrdering[0][0] = horPref[0];
+ prefOrdering[0][1] = vertPref[0];
+ prefOrdering[1][0] = vertPref[1];
+ prefOrdering[1][1] = horPref[1];
+ preferredOrderSet = true;
+ }
+ else if (((vertPref[0] & portConstraint[0]) > 0)
+ && ((horPref[1] & portConstraint[1]) > 0))
+ {
+ prefOrdering[0][0] = vertPref[0];
+ prefOrdering[0][1] = horPref[0];
+ prefOrdering[1][0] = horPref[1];
+ prefOrdering[1][1] = vertPref[1];
+ preferredOrderSet = true;
+ }
+ }
+ if (preferredVertDist > scaledOrthBuffer * 2 && !preferredOrderSet)
+ {
+ prefOrdering[0][0] = vertPref[0];
+ prefOrdering[0][1] = horPref[0];
+ prefOrdering[1][0] = vertPref[1];
+ prefOrdering[1][1] = horPref[1];
+ preferredOrderSet = true;
+
+ }
+ if (preferredHorizDist > scaledOrthBuffer * 2 && !preferredOrderSet)
+ {
+ prefOrdering[0][0] = horPref[0];
+ prefOrdering[0][1] = vertPref[0];
+ prefOrdering[1][0] = horPref[1];
+ prefOrdering[1][1] = vertPref[1];
+ preferredOrderSet = true;
+ }
+
+ // The source and target prefs are now an ordered list of
+ // the preferred port selections
+ // It the list can contain gaps, compact it
+
+ for (var i = 0; i < 2; i++)
+ {
+ if (dir[i] != 0x0)
+ {
+ continue;
+ }
+
+ if ((prefOrdering[i][0] & portConstraint[i]) == 0)
+ {
+ prefOrdering[i][0] = prefOrdering[i][1];
+ }
+
+ dirPref[i] = prefOrdering[i][0] & portConstraint[i];
+ dirPref[i] |= (prefOrdering[i][1] & portConstraint[i]) << 8;
+ dirPref[i] |= (prefOrdering[1 - i][i] & portConstraint[i]) << 16;
+ dirPref[i] |= (prefOrdering[1 - i][1 - i] & portConstraint[i]) << 24;
+
+ if ((dirPref[i] & 0xF) == 0)
+ {
+ dirPref[i] = dirPref[i] << 8;
+ }
+ if ((dirPref[i] & 0xF00) == 0)
+ {
+ dirPref[i] = (dirPref[i] & 0xF) | dirPref[i] >> 8;
+ }
+ if ((dirPref[i] & 0xF0000) == 0)
+ {
+ dirPref[i] = (dirPref[i] & 0xFFFF)
+ | ((dirPref[i] & 0xF000000) >> 8);
+ }
+
+ dir[i] = dirPref[i] & 0xF;
+
+ if (portConstraint[i] == mxConstants.DIRECTION_MASK_WEST
+ || portConstraint[i] == mxConstants.DIRECTION_MASK_NORTH
+ || portConstraint[i] == mxConstants.DIRECTION_MASK_EAST
+ || portConstraint[i] == mxConstants.DIRECTION_MASK_SOUTH)
+ {
+ dir[i] = portConstraint[i];
+ }
+ }
+
+ //==============================================================
+ // End of source and target direction determination
+
+ var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
+ : dir[0];
+ var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
+ : dir[1];
+
+ sourceIndex -= quad;
+ targetIndex -= quad;
+
+ if (sourceIndex < 1)
+ {
+ sourceIndex += 4;
+ }
+ if (targetIndex < 1)
+ {
+ targetIndex += 4;
+ }
+
+ var routePattern = mxEdgeStyle.routePatterns[sourceIndex - 1][targetIndex - 1];
+
+ mxEdgeStyle.wayPoints1[0][0] = geo[0][0];
+ mxEdgeStyle.wayPoints1[0][1] = geo[0][1];
+
+ switch (dir[0])
+ {
+ case mxConstants.DIRECTION_MASK_WEST:
+ mxEdgeStyle.wayPoints1[0][0] -= scaledOrthBuffer;
+ mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
+ break;
+ case mxConstants.DIRECTION_MASK_SOUTH:
+ mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
+ mxEdgeStyle.wayPoints1[0][1] += geo[0][3] + scaledOrthBuffer;
+ break;
+ case mxConstants.DIRECTION_MASK_EAST:
+ mxEdgeStyle.wayPoints1[0][0] += geo[0][2] + scaledOrthBuffer;
+ mxEdgeStyle.wayPoints1[0][1] += constraint[0][1] * geo[0][3];
+ break;
+ case mxConstants.DIRECTION_MASK_NORTH:
+ mxEdgeStyle.wayPoints1[0][0] += constraint[0][0] * geo[0][2];
+ mxEdgeStyle.wayPoints1[0][1] -= scaledOrthBuffer;
+ break;
+ }
+
+ var currentIndex = 0;
+
+ // Orientation, 0 horizontal, 1 vertical
+ var lastOrientation = (dir[0] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
+ : 1;
+ var initialOrientation = lastOrientation;
+ var currentOrientation = 0;
+
+ for (var i = 0; i < routePattern.length; i++)
+ {
+ var nextDirection = routePattern[i] & 0xF;
+
+ // Rotate the index of this direction by the quad
+ // to get the real direction
+ var directionIndex = nextDirection == mxConstants.DIRECTION_MASK_EAST ? 3
+ : nextDirection;
+
+ directionIndex += quad;
+
+ if (directionIndex > 4)
+ {
+ directionIndex -= 4;
+ }
+
+ var direction = mxEdgeStyle.dirVectors[directionIndex - 1];
+
+ currentOrientation = (directionIndex % 2 > 0) ? 0 : 1;
+ // Only update the current index if the point moved
+ // in the direction of the current segment move,
+ // otherwise the same point is moved until there is
+ // a segment direction change
+ if (currentOrientation != lastOrientation)
+ {
+ currentIndex++;
+ // Copy the previous way point into the new one
+ // We can't base the new position on index - 1
+ // because sometime elbows turn out not to exist,
+ // then we'd have to rewind.
+ mxEdgeStyle.wayPoints1[currentIndex][0] = mxEdgeStyle.wayPoints1[currentIndex - 1][0];
+ mxEdgeStyle.wayPoints1[currentIndex][1] = mxEdgeStyle.wayPoints1[currentIndex - 1][1];
+ }
+
+ var tar = (routePattern[i] & mxEdgeStyle.TARGET_MASK) > 0;
+ var sou = (routePattern[i] & mxEdgeStyle.SOURCE_MASK) > 0;
+ var side = (routePattern[i] & mxEdgeStyle.SIDE_MASK) >> 5;
+ side = side << quad;
+
+ if (side > 0xF)
+ {
+ side = side >> 4;
+ }
+
+ var center = (routePattern[i] & mxEdgeStyle.CENTER_MASK) > 0;
+
+ if ((sou || tar) && side < 9)
+ {
+ var limit = 0;
+ var souTar = sou ? 0 : 1;
+
+ if (center && currentOrientation == 0)
+ {
+ limit = geo[souTar][0] + constraint[souTar][0] * geo[souTar][2];
+ }
+ else if (center)
+ {
+ limit = geo[souTar][1] + constraint[souTar][1] * geo[souTar][3];
+ }
+ else
+ {
+ limit = mxEdgeStyle.limits[souTar][side];
+ }
+
+ if (currentOrientation == 0)
+ {
+ var lastX = mxEdgeStyle.wayPoints1[currentIndex][0];
+ var deltaX = (limit - lastX) * direction[0];
+
+ if (deltaX > 0)
+ {
+ mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]
+ * deltaX;
+ }
+ }
+ else
+ {
+ var lastY = mxEdgeStyle.wayPoints1[currentIndex][1];
+ var deltaY = (limit - lastY) * direction[1];
+
+ if (deltaY > 0)
+ {
+ mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]
+ * deltaY;
+ }
+ }
+ }
+
+ else if (center)
+ {
+ // Which center we're travelling to depend on the current direction
+ mxEdgeStyle.wayPoints1[currentIndex][0] += direction[0]
+ * Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
+ mxEdgeStyle.wayPoints1[currentIndex][1] += direction[1]
+ * Math.abs(mxEdgeStyle.vertexSeperations[directionIndex] / 2);
+ }
+
+ if (currentIndex > 0
+ && mxEdgeStyle.wayPoints1[currentIndex][currentOrientation] == mxEdgeStyle.wayPoints1[currentIndex - 1][currentOrientation])
+ {
+ currentIndex--;
+ }
+ else
+ {
+ lastOrientation = currentOrientation;
+ }
+ }
+
+ for (var i = 0; i <= currentIndex; i++)
+ {
+ if (i == currentIndex)
+ {
+ // Last point can cause last segment to be in
+ // same direction as jetty/approach. If so,
+ // check the number of points is consistent
+ // with the relative orientation of source and target
+ // jettys. Same orientation requires an even
+ // number of turns (points), different requires
+ // odd.
+ var targetOrientation = (dir[1] & (mxConstants.DIRECTION_MASK_EAST | mxConstants.DIRECTION_MASK_WEST)) > 0 ? 0
+ : 1;
+ var sameOrient = targetOrientation == initialOrientation ? 0 : 1;
+
+ // (currentIndex + 1) % 2 is 0 for even number of points,
+ // 1 for odd
+ if (sameOrient != (currentIndex + 1) % 2)
+ {
+ // The last point isn't required
+ break;
+ }
+ }
+
+ result.push(new mxPoint(mxEdgeStyle.wayPoints1[i][0], mxEdgeStyle.wayPoints1[i][1]));
+ }
+ },
+
+ getRoutePattern: function(dir, quad, dx, dy)
+ {
+ var sourceIndex = dir[0] == mxConstants.DIRECTION_MASK_EAST ? 3
+ : dir[0];
+ var targetIndex = dir[1] == mxConstants.DIRECTION_MASK_EAST ? 3
+ : dir[1];
+
+ sourceIndex -= quad;
+ targetIndex -= quad;
+
+ if (sourceIndex < 1)
+ {
+ sourceIndex += 4;
+ }
+ if (targetIndex < 1)
+ {
+ targetIndex += 4;
+ }
+
+ var result = routePatterns[sourceIndex - 1][targetIndex - 1];
+
+ if (dx == 0 || dy == 0)
+ {
+ if (inlineRoutePatterns[sourceIndex - 1][targetIndex - 1] != null)
+ {
+ result = inlineRoutePatterns[sourceIndex - 1][targetIndex - 1];
+ }
+ }
+
+ return result;
+ }
+}; \ No newline at end of file
diff --git a/src/js/view/mxGraph.js b/src/js/view/mxGraph.js
new file mode 100644
index 0000000..7c90f9b
--- /dev/null
+++ b/src/js/view/mxGraph.js
@@ -0,0 +1,11176 @@
+/**
+ * $Id: mxGraph.js,v 1.702 2012-12-13 15:07:34 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraph
+ *
+ * Extends <mxEventSource> to implement a graph component for
+ * the browser. This is the main class of the package. To activate
+ * panning and connections use <setPanning> and <setConnectable>.
+ * For rubberband selection you must create a new instance of
+ * <mxRubberband>. The following listeners are added to
+ * <mouseListeners> by default:
+ *
+ * - <tooltipHandler>: <mxTooltipHandler> that displays tooltips
+ * - <panningHandler>: <mxPanningHandler> for panning and popup menus
+ * - <connectionHandler>: <mxConnectionHandler> for creating connections
+ * - <graphHandler>: <mxGraphHandler> for moving and cloning cells
+ *
+ * These listeners will be called in the above order if they are enabled.
+ *
+ * Background Images:
+ *
+ * To display a background image, set the image, image width and
+ * image height using <setBackgroundImage>. If one of the
+ * above values has changed then the <view>'s <mxGraphView.validate>
+ * should be invoked.
+ *
+ * Cell Images:
+ *
+ * To use images in cells, a shape must be specified in the default
+ * vertex style (or any named style). Possible shapes are
+ * <mxConstants.SHAPE_IMAGE> and <mxConstants.SHAPE_LABEL>.
+ * The code to change the shape used in the default vertex style,
+ * the following code is used:
+ *
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_IMAGE;
+ * (end)
+ *
+ * For the default vertex style, the image to be displayed can be
+ * specified in a cell's style using the <mxConstants.STYLE_IMAGE>
+ * key and the image URL as a value, for example:
+ *
+ * (code)
+ * image=http://www.example.com/image.gif
+ * (end)
+ *
+ * For a named style, the the stylename must be the first element
+ * of the cell style:
+ *
+ * (code)
+ * stylename;image=http://www.example.com/image.gif
+ * (end)
+ *
+ * A cell style can have any number of key=value pairs added, divided
+ * by a semicolon as follows:
+ *
+ * (code)
+ * [stylename;|key=value;]
+ * (end)
+ *
+ * Labels:
+ *
+ * The cell labels are defined by <getLabel> which uses <convertValueToString>
+ * if <labelsVisible> is true. If a label must be rendered as HTML markup, then
+ * <isHtmlLabel> should return true for the respective cell. If all labels
+ * contain HTML markup, <htmlLabels> can be set to true. NOTE: Enabling HTML
+ * labels carries a possible security risk (see the section on security in
+ * the manual).
+ *
+ * If wrapping is needed for a label, then <isHtmlLabel> and <isWrapping> must
+ * return true for the cell whose label should be wrapped. See <isWrapping> for
+ * an example.
+ *
+ * If clipping is needed to keep the rendering of a HTML label inside the
+ * bounds of its vertex, then <isClipping> should return true for the
+ * respective cell.
+ *
+ * By default, edge labels are movable and vertex labels are fixed. This can be
+ * changed by setting <edgeLabelsMovable> and <vertexLabelsMovable>, or by
+ * overriding <isLabelMovable>.
+ *
+ * In-place Editing:
+ *
+ * In-place editing is started with a doubleclick or by typing F2.
+ * Programmatically, <edit> is used to check if the cell is editable
+ * (<isCellEditable>) and call <startEditingAtCell>, which invokes
+ * <mxCellEditor.startEditing>. The editor uses the value returned
+ * by <getEditingValue> as the editing value.
+ *
+ * After in-place editing, <labelChanged> is called, which invokes
+ * <mxGraphModel.setValue>, which in turn calls
+ * <mxGraphModel.valueForCellChanged> via <mxValueChange>.
+ *
+ * The event that triggers in-place editing is passed through to the
+ * <cellEditor>, which may take special actions depending on the type of the
+ * event or mouse location, and is also passed to <getEditingValue>. The event
+ * is then passed back to the event processing functions which can perform
+ * specific actions based on the trigger event.
+ *
+ * Tooltips:
+ *
+ * Tooltips are implemented by <getTooltip>, which calls <getTooltipForCell>
+ * if a cell is under the mousepointer. The default implementation checks if
+ * the cell has a getTooltip function and calls it if it exists. Hence, in order
+ * to provide custom tooltips, the cell must provide a getTooltip function, or
+ * one of the two above functions must be overridden.
+ *
+ * Typically, for custom cell tooltips, the latter function is overridden as
+ * follows:
+ *
+ * (code)
+ * graph.getTooltipForCell = function(cell)
+ * {
+ * var label = this.convertValueToString(cell);
+ * return 'Tooltip for '+label;
+ * }
+ * (end)
+ *
+ * When using a config file, the function is overridden in the mxGraph section
+ * using the following entry:
+ *
+ * (code)
+ * <add as="getTooltipForCell"><![CDATA[
+ * function(cell)
+ * {
+ * var label = this.convertValueToString(cell);
+ * return 'Tooltip for '+label;
+ * }
+ * ]]></add>
+ * (end)
+ *
+ * "this" refers to the graph in the implementation, so for example to check if
+ * a cell is an edge, you use this.getModel().isEdge(cell)
+ *
+ * For replacing the default implementation of <getTooltipForCell> (rather than
+ * replacing the function on a specific instance), the following code should be
+ * used after loading the JavaScript files, but before creating a new mxGraph
+ * instance using <mxGraph>:
+ *
+ * (code)
+ * mxGraph.prototype.getTooltipForCell = function(cell)
+ * {
+ * var label = this.convertValueToString(cell);
+ * return 'Tooltip for '+label;
+ * }
+ * (end)
+ *
+ * Shapes & Styles:
+ *
+ * The implementation of new shapes is demonstrated in the examples. We'll assume
+ * that we have implemented a custom shape with the name BoxShape which we want
+ * to use for drawing vertices. To use this shape, it must first be registered in
+ * the cell renderer as follows:
+ *
+ * (code)
+ * graph.cellRenderer.registerShape('box', BoxShape);
+ * (end)
+ *
+ * The code registers the BoxShape constructor under the name box in the cell
+ * renderer of the graph. The shape can now be referenced using the shape-key in
+ * a style definition. (The cell renderer contains a set of additional shapes,
+ * namely one for each constant with a SHAPE-prefix in <mxConstants>.)
+ *
+ * Styles are a collection of key, value pairs and a stylesheet is a collection
+ * of named styles. The names are referenced by the cellstyle, which is stored
+ * in <mxCell.style> with the following format: [stylename;|key=value;]. The
+ * string is resolved to a collection of key, value pairs, where the keys are
+ * overridden with the values in the string.
+ *
+ * When introducing a new shape, the name under which the shape is registered
+ * must be used in the stylesheet. There are three ways of doing this:
+ *
+ * - By changing the default style, so that all vertices will use the new
+ * shape
+ * - By defining a new style, so that only vertices with the respective
+ * cellstyle will use the new shape
+ * - By using shape=box in the cellstyle's optional list of key, value pairs
+ * to be overridden
+ *
+ * In the first case, the code to fetch and modify the default style for
+ * vertices is as follows:
+ *
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_SHAPE] = 'box';
+ * (end)
+ *
+ * The code takes the default vertex style, which is used for all vertices that
+ * do not have a specific cellstyle, and modifies the value for the shape-key
+ * in-place to use the new BoxShape for drawing vertices. This is done by
+ * assigning the box value in the second line, which refers to the name of the
+ * BoxShape in the cell renderer.
+ *
+ * In the second case, a collection of key, value pairs is created and then
+ * added to the stylesheet under a new name. In order to distinguish the
+ * shapename and the stylename we'll use boxstyle for the stylename:
+ *
+ * (code)
+ * var style = new Object();
+ * style[mxConstants.STYLE_SHAPE] = 'box';
+ * style[mxConstants.STYLE_STROKECOLOR] = '#000000';
+ * style[mxConstants.STYLE_FONTCOLOR] = '#000000';
+ * graph.getStylesheet().putCellStyle('boxstyle', style);
+ * (end)
+ *
+ * The code adds a new style with the name boxstyle to the stylesheet. To use
+ * this style with a cell, it must be referenced from the cellstyle as follows:
+ *
+ * (code)
+ * var vertex = graph.insertVertex(parent, null, 'Hello, World!', 20, 20, 80, 20,
+ * 'boxstyle');
+ * (end)
+ *
+ * To summarize, each new shape must be registered in the <mxCellRenderer> with
+ * a unique name. That name is then used as the value of the shape-key in a
+ * default or custom style. If there are multiple custom shapes, then there
+ * should be a separate style for each shape.
+ *
+ * Inheriting Styles:
+ *
+ * For fill-, stroke-, gradient- and indicatorColors special keywords can be
+ * used. The inherit keyword for one of these colors will inherit the color
+ * for the same key from the parent cell. The swimlane keyword does the same,
+ * but inherits from the nearest swimlane in the ancestor hierarchy. Finally,
+ * the indicated keyword will use the color of the indicator as the color for
+ * the given key.
+ *
+ * Scrollbars:
+ *
+ * The <containers> overflow CSS property defines if scrollbars are used to
+ * display the graph. For values of 'auto' or 'scroll', the scrollbars will
+ * be shown. Note that the <resizeContainer> flag is normally not used
+ * together with scrollbars, as it will resize the container to match the
+ * size of the graph after each change.
+ *
+ * Multiplicities and Validation:
+ *
+ * To control the possible connections in mxGraph, <getEdgeValidationError> is
+ * used. The default implementation of the function uses <multiplicities>,
+ * which is an array of <mxMultiplicity>. Using this class allows to establish
+ * simple multiplicities, which are enforced by the graph.
+ *
+ * The <mxMultiplicity> uses <mxCell.is> to determine for which terminals it
+ * applies. The default implementation of <mxCell.is> works with DOM nodes (XML
+ * nodes) and checks if the given type parameter matches the nodeName of the
+ * node (case insensitive). Optionally, an attributename and value can be
+ * specified which are also checked.
+ *
+ * <getEdgeValidationError> is called whenever the connectivity of an edge
+ * changes. It returns an empty string or an error message if the edge is
+ * invalid or null if the edge is valid. If the returned string is not empty
+ * then it is displayed as an error message.
+ *
+ * <mxMultiplicity> allows to specify the multiplicity between a terminal and
+ * its possible neighbors. For example, if any rectangle may only be connected
+ * to, say, a maximum of two circles you can add the following rule to
+ * <multiplicities>:
+ *
+ * (code)
+ * graph.multiplicities.push(new mxMultiplicity(
+ * true, 'rectangle', null, null, 0, 2, ['circle'],
+ * 'Only 2 targets allowed',
+ * 'Only shape targets allowed'));
+ * (end)
+ *
+ * This will display the first error message whenever a rectangle is connected
+ * to more than two circles and the second error message if a rectangle is
+ * connected to anything but a circle.
+ *
+ * For certain multiplicities, such as a minimum of 1 connection, which cannot
+ * be enforced at cell creation time (unless the cell is created together with
+ * the connection), mxGraph offers <validate> which checks all multiplicities
+ * for all cells and displays the respective error messages in an overlay icon
+ * on the cells.
+ *
+ * If a cell is collapsed and contains validation errors, a respective warning
+ * icon is attached to the collapsed cell.
+ *
+ * Auto-Layout:
+ *
+ * For automatic layout, the <getLayout> hook is provided in <mxLayoutManager>.
+ * It can be overridden to return a layout algorithm for the children of a
+ * given cell.
+ *
+ * Unconnected edges:
+ *
+ * The default values for all switches are designed to meet the requirements of
+ * general diagram drawing applications. A very typical set of settings to
+ * avoid edges that are not connected is the following:
+ *
+ * (code)
+ * graph.setAllowDanglingEdges(false);
+ * graph.setDisconnectOnMove(false);
+ * (end)
+ *
+ * Setting the <cloneInvalidEdges> switch to true is optional. This switch
+ * controls if edges are inserted after a copy, paste or clone-drag if they are
+ * invalid. For example, edges are invalid if copied or control-dragged without
+ * having selected the corresponding terminals and allowDanglingEdges is
+ * false, in which case the edges will not be cloned if the switch is false.
+ *
+ * Output:
+ *
+ * To produce an XML representation for a diagram, the following code can be
+ * used.
+ *
+ * (code)
+ * var enc = new mxCodec(mxUtils.createXmlDocument());
+ * var node = enc.encode(graph.getModel());
+ * (end)
+ *
+ * This will produce an XML node than can be handled using the DOM API or
+ * turned into a string representation using the following code:
+ *
+ * (code)
+ * var xml = mxUtils.getXml(node);
+ * (end)
+ *
+ * To obtain a formatted string, mxUtils.getPrettyXml can be used instead.
+ *
+ * This string can now be stored in a local persistent storage (for example
+ * using Google Gears) or it can be passed to a backend using mxUtils.post as
+ * follows. The url variable is the URL of the Java servlet, PHP page or HTTP
+ * handler, depending on the server.
+ *
+ * (code)
+ * var xmlString = encodeURIComponent(mxUtils.getXml(node));
+ * mxUtils.post(url, 'xml='+xmlString, function(req)
+ * {
+ * // Process server response using req of type mxXmlRequest
+ * });
+ * (end)
+ *
+ * Input:
+ *
+ * To load an XML representation of a diagram into an existing graph object
+ * mxUtils.load can be used as follows. The url variable is the URL of the Java
+ * servlet, PHP page or HTTP handler that produces the XML string.
+ *
+ * (code)
+ * var xmlDoc = mxUtils.load(url).getXml();
+ * var node = xmlDoc.documentElement;
+ * var dec = new mxCodec(node.ownerDocument);
+ * dec.decode(node, graph.getModel());
+ * (end)
+ *
+ * For creating a page that loads the client and a diagram using a single
+ * request please refer to the deployment examples in the backends.
+ *
+ * Functional dependencies:
+ *
+ * (see images/callgraph.png)
+ *
+ * Resources:
+ *
+ * resources/graph - Language resources for mxGraph
+ *
+ * Group: Events
+ *
+ * Event: mxEvent.ROOT
+ *
+ * Fires if the root in the model has changed. This event has no properties.
+ *
+ * Event: mxEvent.ALIGN_CELLS
+ *
+ * Fires between begin- and endUpdate in <alignCells>. The <code>cells</code>
+ * and <code>align</code> properties contain the respective arguments that were
+ * passed to <alignCells>.
+ *
+ * Event: mxEvent.FLIP_EDGE
+ *
+ * Fires between begin- and endUpdate in <flipEdge>. The <code>edge</code>
+ * property contains the edge passed to <flipEdge>.
+ *
+ * Event: mxEvent.ORDER_CELLS
+ *
+ * Fires between begin- and endUpdate in <orderCells>. The <code>cells</code>
+ * and <code>back</code> properties contain the respective arguments that were
+ * passed to <orderCells>.
+ *
+ * Event: mxEvent.CELLS_ORDERED
+ *
+ * Fires between begin- and endUpdate in <cellsOrdered>. The <code>cells</code>
+ * and <code>back</code> arguments contain the respective arguments that were
+ * passed to <cellsOrdered>.
+ *
+ * Event: mxEvent.GROUP_CELLS
+ *
+ * Fires between begin- and endUpdate in <groupCells>. The <code>group</code>,
+ * <code>cells</code> and <code>border</code> arguments contain the respective
+ * arguments that were passed to <groupCells>.
+ *
+ * Event: mxEvent.UNGROUP_CELLS
+ *
+ * Fires between begin- and endUpdate in <ungroupCells>. The <code>cells</code>
+ * property contains the array of cells that was passed to <ungroupCells>.
+ *
+ * Event: mxEvent.REMOVE_CELLS_FROM_PARENT
+ *
+ * Fires between begin- and endUpdate in <removeCellsFromParent>. The
+ * <code>cells</code> property contains the array of cells that was passed to
+ * <removeCellsFromParent>.
+ *
+ * Event: mxEvent.ADD_CELLS
+ *
+ * Fires between begin- and endUpdate in <addCells>. The <code>cells</code>,
+ * <code>parent</code>, <code>index</code>, <code>source</code> and
+ * <code>target</code> properties contain the respective arguments that were
+ * passed to <addCells>.
+ *
+ * Event: mxEvent.CELLS_ADDED
+ *
+ * Fires between begin- and endUpdate in <cellsAdded>. The <code>cells</code>,
+ * <code>parent</code>, <code>index</code>, <code>source</code>,
+ * <code>target</code> and <code>absolute</code> properties contain the
+ * respective arguments that were passed to <cellsAdded>.
+ *
+ * Event: mxEvent.REMOVE_CELLS
+ *
+ * Fires between begin- and endUpdate in <removeCells>. The <code>cells</code>
+ * and <code>includeEdges</code> arguments contain the respective arguments
+ * that were passed to <removeCells>.
+ *
+ * Event: mxEvent.CELLS_REMOVED
+ *
+ * Fires between begin- and endUpdate in <cellsRemoved>. The <code>cells</code>
+ * argument contains the array of cells that was removed.
+ *
+ * Event: mxEvent.SPLIT_EDGE
+ *
+ * Fires between begin- and endUpdate in <splitEdge>. The <code>edge</code>
+ * property contains the edge to be splitted, the <code>cells</code>,
+ * <code>newEdge</code>, <code>dx</code> and <code>dy</code> properties contain
+ * the respective arguments that were passed to <splitEdge>.
+ *
+ * Event: mxEvent.TOGGLE_CELLS
+ *
+ * Fires between begin- and endUpdate in <toggleCells>. The <code>show</code>,
+ * <code>cells</code> and <code>includeEdges</code> properties contain the
+ * respective arguments that were passed to <toggleCells>.
+ *
+ * Event: mxEvent.FOLD_CELLS
+ *
+ * Fires between begin- and endUpdate in <foldCells>. The
+ * <code>collapse</code>, <code>cells</code> and <code>recurse</code>
+ * properties contain the respective arguments that were passed to <foldCells>.
+ *
+ * Event: mxEvent.CELLS_FOLDED
+ *
+ * Fires between begin- and endUpdate in cellsFolded. The
+ * <code>collapse</code>, <code>cells</code> and <code>recurse</code>
+ * properties contain the respective arguments that were passed to
+ * <cellsFolded>.
+ *
+ * Event: mxEvent.UPDATE_CELL_SIZE
+ *
+ * Fires between begin- and endUpdate in <updateCellSize>. The
+ * <code>cell</code> and <code>ignoreChildren</code> properties contain the
+ * respective arguments that were passed to <updateCellSize>.
+ *
+ * Event: mxEvent.RESIZE_CELLS
+ *
+ * Fires between begin- and endUpdate in <resizeCells>. The <code>cells</code>
+ * and <code>bounds</code> properties contain the respective arguments that
+ * were passed to <resizeCells>.
+ *
+ * Event: mxEvent.CELLS_RESIZED
+ *
+ * Fires between begin- and endUpdate in <cellsResized>. The <code>cells</code>
+ * and <code>bounds</code> properties contain the respective arguments that
+ * were passed to <cellsResized>.
+ *
+ * Event: mxEvent.MOVE_CELLS
+ *
+ * Fires between begin- and endUpdate in <moveCells>. The <code>cells</code>,
+ * <code>dx</code>, <code>dy</code>, <code>clone</code>, <code>target</code>
+ * and <code>event</code> properties contain the respective arguments that
+ * were passed to <moveCells>.
+ *
+ * Event: mxEvent.CELLS_MOVED
+ *
+ * Fires between begin- and endUpdate in <cellsMoved>. The <code>cells</code>,
+ * <code>dx</code>, <code>dy</code> and <code>disconnect</code> properties
+ * contain the respective arguments that were passed to <cellsMoved>.
+ *
+ * Event: mxEvent.CONNECT_CELL
+ *
+ * Fires between begin- and endUpdate in <connectCell>. The <code>edge</code>,
+ * <code>terminal</code> and <code>source</code> properties contain the
+ * respective arguments that were passed to <connectCell>.
+ *
+ * Event: mxEvent.CELL_CONNECTED
+ *
+ * Fires between begin- and endUpdate in <cellConnected>. The
+ * <code>edge</code>, <code>terminal</code> and <code>source</code> properties
+ * contain the respective arguments that were passed to <cellConnected>.
+ *
+ * Event: mxEvent.REFRESH
+ *
+ * Fires after <refresh> was executed. This event has no properties.
+ *
+ * Event: mxEvent.CLICK
+ *
+ * Fires in <click> after a click event. The <code>event</code> property
+ * contains the original mouse event and <code>cell</code> property contains
+ * the cell under the mouse or null if the background was clicked.
+ *
+ * To handle a click event, use the following code:
+ *
+ * (code)
+ * graph.addListener(mxEvent.CLICK, function(sender, evt)
+ * {
+ * var e = evt.getProperty('event'); // mouse event
+ * var cell = evt.getProperty('cell'); // cell may be null
+ *
+ * if (!evt.isConsumed())
+ * {
+ * if (cell != null)
+ * {
+ * // Do something useful with cell and consume the event
+ * evt.consume();
+ * }
+ * }
+ * });
+ * (end)
+ *
+ * Event: mxEvent.DOUBLE_CLICK
+ *
+ * Fires in <dblClick> after a double click. The <code>event</code> property
+ * contains the original mouse event and the <code>cell</code> property
+ * contains the cell under the mouse or null if the background was clicked.
+ *
+ * Event: mxEvent.SIZE
+ *
+ * Fires after <sizeDidChange> was executed. The <code>bounds</code> property
+ * contains the new graph bounds.
+ *
+ * Event: mxEvent.START_EDITING
+ *
+ * Fires before the in-place editor starts in <startEditingAtCell>. The
+ * <code>cell</code> property contains the cell that is being edited and the
+ * <code>event</code> property contains the optional event argument that was
+ * passed to <startEditingAtCell>.
+ *
+ * Event: mxEvent.LABEL_CHANGED
+ *
+ * Fires between begin- and endUpdate in <cellLabelChanged>. The
+ * <code>cell</code> property contains the cell, the <code>value</code>
+ * property contains the new value for the cell and the optional
+ * <code>event</code> property contains the mouse event that started the edit.
+ *
+ * Event: mxEvent.ADD_OVERLAY
+ *
+ * Fires after an overlay is added in <addCellOverlay>. The <code>cell</code>
+ * property contains the cell and the <code>overlay</code> property contains
+ * the <mxCellOverlay> that was added.
+ *
+ * Event: mxEvent.REMOVE_OVERLAY
+ *
+ * Fires after an overlay is removed in <removeCellOverlay> and
+ * <removeCellOverlays>. The <code>cell</code> property contains the cell and
+ * the <code>overlay</code> property contains the <mxCellOverlay> that was
+ * removed.
+ *
+ * Constructor: mxGraph
+ *
+ * Constructs a new mxGraph in the specified container. Model is an optional
+ * mxGraphModel. If no model is provided, a new mxGraphModel instance is
+ * used as the model. The container must have a valid owner document prior
+ * to calling this function in Internet Explorer. RenderHint is a string to
+ * affect the display performance and rendering in IE, but not in SVG-based
+ * browsers. The parameter is mapped to <dialect>, which may
+ * be one of <mxConstants.DIALECT_SVG> for SVG-based browsers,
+ * <mxConstants.DIALECT_STRICTHTML> for fastest display mode,
+ * <mxConstants.DIALECT_PREFERHTML> for faster display mode,
+ * <mxConstants.DIALECT_MIXEDHTML> for fast and <mxConstants.DIALECT_VML>
+ * for exact display mode (slowest). The dialects are defined in mxConstants.
+ * The default values are DIALECT_SVG for SVG-based browsers and
+ * DIALECT_MIXED for IE.
+ *
+ * The possible values for the renderingHint parameter are explained below:
+ *
+ * fast - The parameter is based on the fact that the display performance is
+ * highly improved in IE if the VML is not contained within a VML group
+ * element. The lack of a group element only slightly affects the display while
+ * panning, but improves the performance by almost a factor of 2, while keeping
+ * the display sufficiently accurate. This also allows to render certain shapes as HTML
+ * if the display accuracy is not affected, which is implemented by
+ * <mxShape.isMixedModeHtml>. This is the default setting and is mapped to
+ * DIALECT_MIXEDHTML.
+ * faster - Same as fast, but more expensive shapes are avoided. This is
+ * controlled by <mxShape.preferModeHtml>. The default implementation will
+ * avoid gradients and rounded rectangles, but more significant shapes, such
+ * as rhombus, ellipse, actor and cylinder will be rendered accurately. This
+ * setting is mapped to DIALECT_PREFERHTML.
+ * fastest - Almost anything will be rendered in Html. This allows for
+ * rectangles, labels and images. This setting is mapped to
+ * DIALECT_STRICTHTML.
+ * exact - If accurate panning is required and if the diagram is small (up
+ * to 100 cells), then this value should be used. In this mode, a group is
+ * created that contains the VML. This allows for accurate panning and is
+ * mapped to DIALECT_VML.
+ *
+ * Example:
+ *
+ * To create a graph inside a DOM node with an id of graph:
+ * (code)
+ * var container = document.getElementById('graph');
+ * var graph = new mxGraph(container);
+ * (end)
+ *
+ * Parameters:
+ *
+ * container - Optional DOM node that acts as a container for the graph.
+ * If this is null then the container can be initialized later using
+ * <init>.
+ * model - Optional <mxGraphModel> that constitutes the graph data.
+ * renderHint - Optional string that specifies the display accuracy and
+ * performance. Default is mxConstants.DIALECT_MIXEDHTML (for IE).
+ * stylesheet - Optional <mxStylesheet> to be used in the graph.
+ */
+function mxGraph(container, model, renderHint, stylesheet)
+{
+ // Initializes the variable in case the prototype has been
+ // modified to hold some listeners (which is possible because
+ // the createHandlers call is executed regardless of the
+ // arguments passed into the ctor).
+ this.mouseListeners = null;
+
+ // Converts the renderHint into a dialect
+ this.renderHint = renderHint;
+
+ if (mxClient.IS_SVG)
+ {
+ this.dialect = mxConstants.DIALECT_SVG;
+ }
+ else if (renderHint == mxConstants.RENDERING_HINT_EXACT && mxClient.IS_VML)
+ {
+ this.dialect = mxConstants.DIALECT_VML;
+ }
+ else if (renderHint == mxConstants.RENDERING_HINT_FASTEST)
+ {
+ this.dialect = mxConstants.DIALECT_STRICTHTML;
+ }
+ else if (renderHint == mxConstants.RENDERING_HINT_FASTER)
+ {
+ this.dialect = mxConstants.DIALECT_PREFERHTML;
+ }
+ else // default for VML
+ {
+ this.dialect = mxConstants.DIALECT_MIXEDHTML;
+ }
+
+ // Initializes the main members that do not require a container
+ this.model = (model != null) ? model : new mxGraphModel();
+ this.multiplicities = [];
+ this.imageBundles = [];
+ this.cellRenderer = this.createCellRenderer();
+ this.setSelectionModel(this.createSelectionModel());
+ this.setStylesheet((stylesheet != null) ? stylesheet : this.createStylesheet());
+ this.view = this.createGraphView();
+
+ // Adds a graph model listener to update the view
+ this.graphModelChangeListener = mxUtils.bind(this, function(sender, evt)
+ {
+ this.graphModelChanged(evt.getProperty('edit').changes);
+ });
+
+ this.model.addListener(mxEvent.CHANGE, this.graphModelChangeListener);
+
+ // Installs basic event handlers with disabled default settings.
+ this.createHandlers();
+
+ // Initializes the display if a container was specified
+ if (container != null)
+ {
+ this.init(container);
+ }
+
+ this.view.revalidate();
+};
+
+/**
+ * Installs the required language resources at class
+ * loading time.
+ */
+if (mxLoadResources)
+{
+ mxResources.add(mxClient.basePath+'/resources/graph');
+}
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraph.prototype = new mxEventSource();
+mxGraph.prototype.constructor = mxGraph;
+
+/**
+ * Variable: EMPTY_ARRAY
+ *
+ * Immutable empty array instance.
+ */
+mxGraph.prototype.EMPTY_ARRAY = [];
+
+/**
+ * Group: Variables
+ */
+
+/**
+ * Variable: mouseListeners
+ *
+ * Holds the mouse event listeners. See <fireMouseEvent>.
+ */
+mxGraph.prototype.mouseListeners = null;
+
+/**
+ * Variable: isMouseDown
+ *
+ * Holds the state of the mouse button.
+ */
+mxGraph.prototype.isMouseDown = false;
+
+/**
+ * Variable: model
+ *
+ * Holds the <mxGraphModel> that contains the cells to be displayed.
+ */
+mxGraph.prototype.model = null;
+
+/**
+ * Variable: view
+ *
+ * Holds the <mxGraphView> that caches the <mxCellStates> for the cells.
+ */
+mxGraph.prototype.view = null;
+
+/**
+ * Variable: stylesheet
+ *
+ * Holds the <mxStylesheet> that defines the appearance of the cells.
+ *
+ *
+ * Example:
+ *
+ * Use the following code to read a stylesheet into an existing graph.
+ *
+ * (code)
+ * var req = mxUtils.load('stylesheet.xml');
+ * var root = req.getDocumentElement();
+ * var dec = new mxCodec(root.ownerDocument);
+ * dec.decode(root, graph.stylesheet);
+ * (end)
+ */
+mxGraph.prototype.stylesheet = null;
+
+/**
+ * Variable: selectionModel
+ *
+ * Holds the <mxGraphSelectionModel> that models the current selection.
+ */
+mxGraph.prototype.selectionModel = null;
+
+/**
+ * Variable: cellEditor
+ *
+ * Holds the <mxCellEditor> that is used as the in-place editing.
+ */
+mxGraph.prototype.cellEditor = null;
+
+/**
+ * Variable: cellRenderer
+ *
+ * Holds the <mxCellRenderer> for rendering the cells in the graph.
+ */
+mxGraph.prototype.cellRenderer = null;
+
+/**
+ * Variable: multiplicities
+ *
+ * An array of <mxMultiplicities> describing the allowed
+ * connections in a graph.
+ */
+mxGraph.prototype.multiplicities = null;
+
+/**
+ * Variable: renderHint
+ *
+ * RenderHint as it was passed to the constructor.
+ */
+mxGraph.prototype.renderHint = null;
+
+/**
+ * Variable: dialect
+ *
+ * Dialect to be used for drawing the graph. Possible values are all
+ * constants in <mxConstants> with a DIALECT-prefix.
+ */
+mxGraph.prototype.dialect = null;
+
+/**
+ * Variable: gridSize
+ *
+ * Specifies the grid size. Default is 10.
+ */
+mxGraph.prototype.gridSize = 10;
+
+/**
+ * Variable: gridEnabled
+ *
+ * Specifies if the grid is enabled. This is used in <snap>. Default is
+ * true.
+ */
+mxGraph.prototype.gridEnabled = true;
+
+/**
+ * Variable: portsEnabled
+ *
+ * Specifies if ports are enabled. This is used in <cellConnected> to update
+ * the respective style. Default is true.
+ */
+mxGraph.prototype.portsEnabled = true;
+
+/**
+ * Variable: doubleTapEnabled
+ *
+ * Specifies if double taps on touch-based devices should be handled. Default
+ * is true.
+ */
+mxGraph.prototype.doubleTapEnabled = true;
+
+/**
+ * Variable: doubleTapTimeout
+ *
+ * Specifies the timeout for double taps. Default is 700 ms.
+ */
+mxGraph.prototype.doubleTapTimeout = 700;
+
+/**
+ * Variable: doubleTapTolerance
+ *
+ * Specifies the tolerance for double taps. Default is 25 pixels.
+ */
+mxGraph.prototype.doubleTapTolerance = 25;
+
+/**
+ * Variable: lastTouchX
+ *
+ * Holds the x-coordinate of the last touch event for double tap detection.
+ */
+mxGraph.prototype.lastTouchY = 0;
+
+/**
+ * Variable: lastTouchX
+ *
+ * Holds the y-coordinate of the last touch event for double tap detection.
+ */
+mxGraph.prototype.lastTouchY = 0;
+
+/**
+ * Variable: lastTouchTime
+ *
+ * Holds the time of the last touch event for double click detection.
+ */
+mxGraph.prototype.lastTouchTime = 0;
+
+/**
+ * Variable: gestureEnabled
+ *
+ * Specifies if the handleGesture method should be invoked. Default is true. This
+ * is an experimental feature for touch-based devices.
+ */
+mxGraph.prototype.gestureEnabled = true;
+
+/**
+ * Variable: tolerance
+ *
+ * Tolerance for a move to be handled as a single click.
+ * Default is 4 pixels.
+ */
+mxGraph.prototype.tolerance = 4;
+
+/**
+ * Variable: defaultOverlap
+ *
+ * Value returned by <getOverlap> if <isAllowOverlapParent> returns
+ * true for the given cell. <getOverlap> is used in <constrainChild> if
+ * <isConstrainChild> returns true. The value specifies the
+ * portion of the child which is allowed to overlap the parent.
+ */
+mxGraph.prototype.defaultOverlap = 0.5;
+
+/**
+ * Variable: defaultParent
+ *
+ * Specifies the default parent to be used to insert new cells.
+ * This is used in <getDefaultParent>. Default is null.
+ */
+mxGraph.prototype.defaultParent = null;
+
+/**
+ * Variable: alternateEdgeStyle
+ *
+ * Specifies the alternate edge style to be used if the main control point
+ * on an edge is being doubleclicked. Default is null.
+ */
+mxGraph.prototype.alternateEdgeStyle = null;
+
+/**
+ * Variable: backgroundImage
+ *
+ * Specifies the <mxImage> to be returned by <getBackgroundImage>. Default
+ * is null.
+ *
+ * Example:
+ *
+ * (code)
+ * var img = new mxImage('http://www.example.com/maps/examplemap.jpg', 1024, 768);
+ * graph.setBackgroundImage(img);
+ * graph.view.validate();
+ * (end)
+ */
+mxGraph.prototype.backgroundImage = null;
+
+/**
+ * Variable: pageVisible
+ *
+ * Specifies if the background page should be visible. Default is false.
+ * Not yet implemented.
+ */
+mxGraph.prototype.pageVisible = false;
+
+/**
+ * Variable: pageBreaksVisible
+ *
+ * Specifies if a dashed line should be drawn between multiple pages. Default
+ * is false. If you change this value while a graph is being displayed then you
+ * should call <sizeDidChange> to force an update of the display.
+ */
+mxGraph.prototype.pageBreaksVisible = false;
+
+/**
+ * Variable: pageBreakColor
+ *
+ * Specifies the color for page breaks. Default is 'gray'.
+ */
+mxGraph.prototype.pageBreakColor = 'gray';
+
+/**
+ * Variable: pageBreakDashed
+ *
+ * Specifies the page breaks should be dashed. Default is true.
+ */
+mxGraph.prototype.pageBreakDashed = true;
+
+/**
+ * Variable: minPageBreakDist
+ *
+ * Specifies the minimum distance for page breaks to be visible. Default is
+ * 20 (in pixels).
+ */
+mxGraph.prototype.minPageBreakDist = 20;
+
+/**
+ * Variable: preferPageSize
+ *
+ * Specifies if the graph size should be rounded to the next page number in
+ * <sizeDidChange>. This is only used if the graph container has scrollbars.
+ * Default is false.
+ */
+mxGraph.prototype.preferPageSize = false;
+
+/**
+ * Variable: pageFormat
+ *
+ * Specifies the page format for the background page. Default is
+ * <mxConstants.PAGE_FORMAT_A4_PORTRAIT>. This is used as the default in
+ * <mxPrintPreview> and for painting the background page if <pageVisible> is
+ * true and the pagebreaks if <pageBreaksVisible> is true.
+ */
+mxGraph.prototype.pageFormat = mxConstants.PAGE_FORMAT_A4_PORTRAIT;
+
+/**
+ * Variable: pageScale
+ *
+ * Specifies the scale of the background page. Default is 1.5.
+ * Not yet implemented.
+ */
+mxGraph.prototype.pageScale = 1.5;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies the return value for <isEnabled>. Default is true.
+ */
+mxGraph.prototype.enabled = true;
+
+/**
+ * Variable: escapeEnabled
+ *
+ * Specifies if <mxKeyHandler> should invoke <escape> when the escape key
+ * is pressed. Default is true.
+ */
+mxGraph.prototype.escapeEnabled = true;
+
+/**
+ * Variable: invokesStopCellEditing
+ *
+ * If true, when editing is to be stopped by way of selection changing,
+ * data in diagram changing or other means stopCellEditing is invoked, and
+ * changes are saved. This is implemented in a focus handler in
+ * <mxCellEditor>. Default is true.
+ */
+mxGraph.prototype.invokesStopCellEditing = true;
+
+/**
+ * Variable: enterStopsCellEditing
+ *
+ * If true, pressing the enter key without pressing control or shift will stop
+ * editing and accept the new value. This is used in <mxCellEditor> to stop
+ * cell editing. Note: You can always use F2 and escape to stop editing.
+ * Default is false.
+ */
+mxGraph.prototype.enterStopsCellEditing = false;
+
+/**
+ * Variable: useScrollbarsForPanning
+ *
+ * Specifies if scrollbars should be used for panning in <panGraph> if
+ * any scrollbars are available. If scrollbars are enabled in CSS, but no
+ * scrollbars appear because the graph is smaller than the container size,
+ * then no panning occurs if this is true. Default is true.
+ */
+mxGraph.prototype.useScrollbarsForPanning = true;
+
+/**
+ * Variable: exportEnabled
+ *
+ * Specifies the return value for <canExportCell>. Default is true.
+ */
+mxGraph.prototype.exportEnabled = true;
+
+/**
+ * Variable: importEnabled
+ *
+ * Specifies the return value for <canImportCell>. Default is true.
+ */
+mxGraph.prototype.importEnabled = true;
+
+/**
+ * Variable: cellsLocked
+ *
+ * Specifies the return value for <isCellLocked>. Default is false.
+ */
+mxGraph.prototype.cellsLocked = false;
+
+/**
+ * Variable: cellsCloneable
+ *
+ * Specifies the return value for <isCellCloneable>. Default is true.
+ */
+mxGraph.prototype.cellsCloneable = true;
+
+/**
+ * Variable: foldingEnabled
+ *
+ * Specifies if folding (collapse and expand via an image icon in the graph
+ * should be enabled). Default is true.
+ */
+mxGraph.prototype.foldingEnabled = true;
+
+/**
+ * Variable: cellsEditable
+ *
+ * Specifies the return value for <isCellEditable>. Default is true.
+ */
+mxGraph.prototype.cellsEditable = true;
+
+/**
+ * Variable: cellsDeletable
+ *
+ * Specifies the return value for <isCellDeletable>. Default is true.
+ */
+mxGraph.prototype.cellsDeletable = true;
+
+/**
+ * Variable: cellsMovable
+ *
+ * Specifies the return value for <isCellMovable>. Default is true.
+ */
+mxGraph.prototype.cellsMovable = true;
+
+/**
+ * Variable: edgeLabelsMovable
+ *
+ * Specifies the return value for edges in <isLabelMovable>. Default is true.
+ */
+mxGraph.prototype.edgeLabelsMovable = true;
+
+/**
+ * Variable: vertexLabelsMovable
+ *
+ * Specifies the return value for vertices in <isLabelMovable>. Default is false.
+ */
+mxGraph.prototype.vertexLabelsMovable = false;
+
+/**
+ * Variable: dropEnabled
+ *
+ * Specifies the return value for <isDropEnabled>. Default is false.
+ */
+mxGraph.prototype.dropEnabled = false;
+
+/**
+ * Variable: splitEnabled
+ *
+ * Specifies if dropping onto edges should be enabled. Default is true.
+ */
+mxGraph.prototype.splitEnabled = true;
+
+/**
+ * Variable: cellsResizable
+ *
+ * Specifies the return value for <isCellResizable>. Default is true.
+ */
+mxGraph.prototype.cellsResizable = true;
+
+/**
+ * Variable: cellsBendable
+ *
+ * Specifies the return value for <isCellsBendable>. Default is true.
+ */
+mxGraph.prototype.cellsBendable = true;
+
+/**
+ * Variable: cellsSelectable
+ *
+ * Specifies the return value for <isCellSelectable>. Default is true.
+ */
+mxGraph.prototype.cellsSelectable = true;
+
+/**
+ * Variable: cellsDisconnectable
+ *
+ * Specifies the return value for <isCellDisconntable>. Default is true.
+ */
+mxGraph.prototype.cellsDisconnectable = true;
+
+/**
+ * Variable: autoSizeCells
+ *
+ * Specifies if the graph should automatically update the cell size after an
+ * edit. This is used in <isAutoSizeCell>. Default is false.
+ */
+mxGraph.prototype.autoSizeCells = false;
+
+/**
+ * Variable: autoScroll
+ *
+ * Specifies if the graph should automatically scroll if the mouse goes near
+ * the container edge while dragging. This is only taken into account if the
+ * container has scrollbars. Default is true.
+ *
+ * If you need this to work without scrollbars then set <ignoreScrollbars> to
+ * true.
+ */
+mxGraph.prototype.autoScroll = true;
+
+/**
+ * Variable: timerAutoScroll
+ *
+ * Specifies if timer-based autoscrolling should be used via mxPanningManager.
+ * Note that this disables the code in <scrollPointToVisible> and uses code in
+ * mxPanningManager instead. Note that <autoExtend> is disabled if this is
+ * true and that this should only be used with a scroll buffer or when
+ * scollbars are visible and scrollable in all directions. Default is false.
+ */
+mxGraph.prototype.timerAutoScroll = false;
+
+/**
+ * Variable: allowAutoPanning
+ *
+ * Specifies if panning via <panGraph> should be allowed to implement autoscroll
+ * if no scrollbars are available in <scrollPointToVisible>. Default is false.
+ */
+mxGraph.prototype.allowAutoPanning = false;
+
+/**
+ * Variable: ignoreScrollbars
+ *
+ * Specifies if the graph should automatically scroll regardless of the
+ * scrollbars.
+ */
+mxGraph.prototype.ignoreScrollbars = false;
+
+/**
+ * Variable: autoExtend
+ *
+ * Specifies if the size of the graph should be automatically extended if the
+ * mouse goes near the container edge while dragging. This is only taken into
+ * account if the container has scrollbars. Default is true. See <autoScroll>.
+ */
+mxGraph.prototype.autoExtend = true;
+
+/**
+ * Variable: maximumGraphBounds
+ *
+ * <mxRectangle> that specifies the area in which all cells in the diagram
+ * should be placed. Uses in <getMaximumGraphBounds>. Use a width or height of
+ * 0 if you only want to give a upper, left corner.
+ */
+mxGraph.prototype.maximumGraphBounds = null;
+
+/**
+ * Variable: minimumGraphSize
+ *
+ * <mxRectangle> that specifies the minimum size of the graph. This is ignored
+ * if the graph container has no scrollbars. Default is null.
+ */
+mxGraph.prototype.minimumGraphSize = null;
+
+/**
+ * Variable: minimumContainerSize
+ *
+ * <mxRectangle> that specifies the minimum size of the <container> if
+ * <resizeContainer> is true.
+ */
+mxGraph.prototype.minimumContainerSize = null;
+
+/**
+ * Variable: maximumContainerSize
+ *
+ * <mxRectangle> that specifies the maximum size of the container if
+ * <resizeContainer> is true.
+ */
+mxGraph.prototype.maximumContainerSize = null;
+
+/**
+ * Variable: resizeContainer
+ *
+ * Specifies if the container should be resized to the graph size when
+ * the graph size has changed. Default is false.
+ */
+mxGraph.prototype.resizeContainer = false;
+
+/**
+ * Variable: border
+ *
+ * Border to be added to the bottom and right side when the container is
+ * being resized after the graph has been changed. Default is 0.
+ */
+mxGraph.prototype.border = 0;
+
+/**
+ * Variable: ordered
+ *
+ * Specifies if the display should reflect the order of the cells in
+ * the model. Default is true. This has precendence over
+ * <keepEdgesInBackground> and <keepEdgesInForeground>.
+ */
+mxGraph.prototype.ordered = true;
+
+/**
+ * Variable: keepEdgesInForeground
+ *
+ * Specifies if edges should appear in the foreground regardless of their
+ * order in the model. This has precendence over <keepEdgeInBackground>,
+ * but not over <ordered>. Default is false.
+ */
+mxGraph.prototype.keepEdgesInForeground = false;
+
+/**
+ * Variable: keepEdgesInBackground
+ *
+ * Specifies if edges should appear in the background regardless of their
+ * order in the model. <ordered> and <keepEdgesInForeground> have
+ * precedence over this setting. Default is true.
+ */
+mxGraph.prototype.keepEdgesInBackground = true;
+
+/**
+ * Variable: allowNegativeCoordinates
+ *
+ * Specifies if negative coordinates for vertices are allowed. Default is true.
+ */
+mxGraph.prototype.allowNegativeCoordinates = true;
+
+/**
+ * Variable: constrainChildren
+ *
+ * Specifies the return value for <isConstrainChildren>. Default is
+ * true.
+ */
+mxGraph.prototype.constrainChildren = true;
+
+/**
+ * Variable: extendParents
+ *
+ * Specifies if a parent should contain the child bounds after a resize of
+ * the child. Default is true.
+ */
+mxGraph.prototype.extendParents = true;
+
+/**
+ * Variable: extendParentsOnAdd
+ *
+ * Specifies if parents should be extended according to the <extendParents>
+ * switch if cells are added. Default is true.
+ */
+mxGraph.prototype.extendParentsOnAdd = true;
+
+/**
+ * Variable: collapseToPreferredSize
+ *
+ * Specifies if the cell size should be changed to the preferred size when
+ * a cell is first collapsed. Default is true.
+ */
+mxGraph.prototype.collapseToPreferredSize = true;
+
+/**
+ * Variable: zoomFactor
+ *
+ * Specifies the factor used for <zoomIn> and <zoomOut>. Default is 1.2
+ * (120%).
+ */
+mxGraph.prototype.zoomFactor = 1.2;
+
+/**
+ * Variable: keepSelectionVisibleOnZoom
+ *
+ * Specifies if the viewport should automatically contain the selection cells
+ * after a zoom operation. Default is false.
+ */
+mxGraph.prototype.keepSelectionVisibleOnZoom = false;
+
+/**
+ * Variable: centerZoom
+ *
+ * Specifies if the zoom operations should go into the center of the actual
+ * diagram rather than going from top, left. Default is true.
+ */
+mxGraph.prototype.centerZoom = true;
+
+/**
+ * Variable: resetViewOnRootChange
+ *
+ * Specifies if the scale and translate should be reset if the root changes in
+ * the model. Default is true.
+ */
+mxGraph.prototype.resetViewOnRootChange = true;
+
+/**
+ * Variable: resetEdgesOnResize
+ *
+ * Specifies if edge control points should be reset after the resize of a
+ * connected cell. Default is false.
+ */
+mxGraph.prototype.resetEdgesOnResize = false;
+
+/**
+ * Variable: resetEdgesOnMove
+ *
+ * Specifies if edge control points should be reset after the move of a
+ * connected cell. Default is false.
+ */
+mxGraph.prototype.resetEdgesOnMove = false;
+
+/**
+ * Variable: resetEdgesOnConnect
+ *
+ * Specifies if edge control points should be reset after the the edge has been
+ * reconnected. Default is true.
+ */
+mxGraph.prototype.resetEdgesOnConnect = true;
+
+/**
+ * Variable: allowLoops
+ *
+ * Specifies if loops (aka self-references) are allowed. Default is false.
+ */
+mxGraph.prototype.allowLoops = false;
+
+/**
+ * Variable: defaultLoopStyle
+ *
+ * <mxEdgeStyle> to be used for loops. This is a fallback for loops if the
+ * <mxConstants.STYLE_LOOP> is undefined. Default is <mxEdgeStyle.Loop>.
+ */
+mxGraph.prototype.defaultLoopStyle = mxEdgeStyle.Loop;
+
+/**
+ * Variable: multigraph
+ *
+ * Specifies if multiple edges in the same direction between the same pair of
+ * vertices are allowed. Default is true.
+ */
+mxGraph.prototype.multigraph = true;
+
+/**
+ * Variable: connectableEdges
+ *
+ * Specifies if edges are connectable. Default is false. This overrides the
+ * connectable field in edges.
+ */
+mxGraph.prototype.connectableEdges = false;
+
+/**
+ * Variable: allowDanglingEdges
+ *
+ * Specifies if edges with disconnected terminals are allowed in the graph.
+ * Default is true.
+ */
+mxGraph.prototype.allowDanglingEdges = true;
+
+/**
+ * Variable: cloneInvalidEdges
+ *
+ * Specifies if edges that are cloned should be validated and only inserted
+ * if they are valid. Default is true.
+ */
+mxGraph.prototype.cloneInvalidEdges = false;
+
+/**
+ * Variable: disconnectOnMove
+ *
+ * Specifies if edges should be disconnected from their terminals when they
+ * are moved. Default is true.
+ */
+mxGraph.prototype.disconnectOnMove = true;
+
+/**
+ * Variable: labelsVisible
+ *
+ * Specifies if labels should be visible. This is used in <getLabel>. Default
+ * is true.
+ */
+mxGraph.prototype.labelsVisible = true;
+
+/**
+ * Variable: htmlLabels
+ *
+ * Specifies the return value for <isHtmlLabel>. Default is false.
+ */
+mxGraph.prototype.htmlLabels = false;
+
+/**
+ * Variable: swimlaneSelectionEnabled
+ *
+ * Specifies if swimlanes should be selectable via the content if the
+ * mouse is released. Default is true.
+ */
+mxGraph.prototype.swimlaneSelectionEnabled = true;
+
+/**
+ * Variable: swimlaneNesting
+ *
+ * Specifies if nesting of swimlanes is allowed. Default is true.
+ */
+mxGraph.prototype.swimlaneNesting = true;
+
+/**
+ * Variable: swimlaneIndicatorColorAttribute
+ *
+ * The attribute used to find the color for the indicator if the indicator
+ * color is set to 'swimlane'. Default is <mxConstants.STYLE_FILLCOLOR>.
+ */
+mxGraph.prototype.swimlaneIndicatorColorAttribute = mxConstants.STYLE_FILLCOLOR;
+
+/**
+ * Variable: imageBundles
+ *
+ * Holds the list of image bundles.
+ */
+mxGraph.prototype.imageBundles = null;
+
+/**
+ * Variable: minFitScale
+ *
+ * Specifies the minimum scale to be applied in <fit>. Default is 0.1. Set this
+ * to null to allow any value.
+ */
+mxGraph.prototype.minFitScale = 0.1;
+
+/**
+ * Variable: maxFitScale
+ *
+ * Specifies the maximum scale to be applied in <fit>. Default is 8. Set this
+ * to null to allow any value.
+ */
+mxGraph.prototype.maxFitScale = 8;
+
+/**
+ * Variable: panDx
+ *
+ * Current horizontal panning value. Default is 0.
+ */
+mxGraph.prototype.panDx = 0;
+
+/**
+ * Variable: panDy
+ *
+ * Current vertical panning value. Default is 0.
+ */
+mxGraph.prototype.panDy = 0;
+
+/**
+ * Variable: collapsedImage
+ *
+ * Specifies the <mxImage> to indicate a collapsed state.
+ * Default value is mxClient.imageBasePath + '/collapsed.gif'
+ */
+mxGraph.prototype.collapsedImage = new mxImage(mxClient.imageBasePath + '/collapsed.gif', 9, 9);
+
+/**
+ * Variable: expandedImage
+ *
+ * Specifies the <mxImage> to indicate a expanded state.
+ * Default value is mxClient.imageBasePath + '/expanded.gif'
+ */
+mxGraph.prototype.expandedImage = new mxImage(mxClient.imageBasePath + '/expanded.gif', 9, 9);
+
+/**
+ * Variable: warningImage
+ *
+ * Specifies the <mxImage> for the image to be used to display a warning
+ * overlay. See <setCellWarning>. Default value is mxClient.imageBasePath +
+ * '/warning'. The extension for the image depends on the platform. It is
+ * '.png' on the Mac and '.gif' on all other platforms.
+ */
+mxGraph.prototype.warningImage = new mxImage(mxClient.imageBasePath + '/warning'+
+ ((mxClient.IS_MAC) ? '.png' : '.gif'), 16, 16);
+
+/**
+ * Variable: alreadyConnectedResource
+ *
+ * Specifies the resource key for the error message to be displayed in
+ * non-multigraphs when two vertices are already connected. If the resource
+ * for this key does not exist then the value is used as the error message.
+ * Default is 'alreadyConnected'.
+ */
+mxGraph.prototype.alreadyConnectedResource = (mxClient.language != 'none') ? 'alreadyConnected' : '';
+
+/**
+ * Variable: containsValidationErrorsResource
+ *
+ * Specifies the resource key for the warning message to be displayed when
+ * a collapsed cell contains validation errors. If the resource for this
+ * key does not exist then the value is used as the warning message.
+ * Default is 'containsValidationErrors'.
+ */
+mxGraph.prototype.containsValidationErrorsResource = (mxClient.language != 'none') ? 'containsValidationErrors' : '';
+
+/**
+ * Variable: collapseExpandResource
+ *
+ * Specifies the resource key for the tooltip on the collapse/expand icon.
+ * If the resource for this key does not exist then the value is used as
+ * the tooltip. Default is 'collapse-expand'.
+ */
+mxGraph.prototype.collapseExpandResource = (mxClient.language != 'none') ? 'collapse-expand' : '';
+
+/**
+ * Function: init
+ *
+ * Initializes the <container> and creates the respective datastructures.
+ *
+ * Parameters:
+ *
+ * container - DOM node that will contain the graph display.
+ */
+ mxGraph.prototype.init = function(container)
+ {
+ this.container = container;
+
+ // Initializes the in-place editor
+ this.cellEditor = this.createCellEditor();
+
+ // Initializes the container using the view
+ this.view.init();
+
+ // Updates the size of the container for the current graph
+ this.sizeDidChange();
+
+ // Automatic deallocation of memory
+ if (mxClient.IS_IE)
+ {
+ mxEvent.addListener(window, 'unload', mxUtils.bind(this, function()
+ {
+ this.destroy();
+ }));
+
+ // Disable shift-click for text
+ mxEvent.addListener(container, 'selectstart',
+ mxUtils.bind(this, function()
+ {
+ return this.isEditing();
+ })
+ );
+ }
+};
+
+/**
+ * Function: createHandlers
+ *
+ * Creates the tooltip-, panning-, connection- and graph-handler (in this
+ * order). This is called in the constructor before <init> is called.
+ */
+mxGraph.prototype.createHandlers = function(container)
+{
+ this.tooltipHandler = new mxTooltipHandler(this);
+ this.tooltipHandler.setEnabled(false);
+ this.panningHandler = new mxPanningHandler(this);
+ this.panningHandler.panningEnabled = false;
+ this.selectionCellsHandler = new mxSelectionCellsHandler(this);
+ this.connectionHandler = new mxConnectionHandler(this);
+ this.connectionHandler.setEnabled(false);
+ this.graphHandler = new mxGraphHandler(this);
+};
+
+/**
+ * Function: createSelectionModel
+ *
+ * Creates a new <mxGraphSelectionModel> to be used in this graph.
+ */
+mxGraph.prototype.createSelectionModel = function()
+{
+ return new mxGraphSelectionModel(this);
+};
+
+/**
+ * Function: createStylesheet
+ *
+ * Creates a new <mxGraphSelectionModel> to be used in this graph.
+ */
+mxGraph.prototype.createStylesheet = function()
+{
+ return new mxStylesheet();
+};
+
+/**
+ * Function: createGraphView
+ *
+ * Creates a new <mxGraphView> to be used in this graph.
+ */
+mxGraph.prototype.createGraphView = function()
+{
+ return new mxGraphView(this);
+};
+
+/**
+ * Function: createCellRenderer
+ *
+ * Creates a new <mxCellRenderer> to be used in this graph.
+ */
+mxGraph.prototype.createCellRenderer = function()
+{
+ return new mxCellRenderer();
+};
+
+/**
+ * Function: createCellEditor
+ *
+ * Creates a new <mxCellEditor> to be used in this graph.
+ */
+mxGraph.prototype.createCellEditor = function()
+{
+ return new mxCellEditor(this);
+};
+
+/**
+ * Function: getModel
+ *
+ * Returns the <mxGraphModel> that contains the cells.
+ */
+mxGraph.prototype.getModel = function()
+{
+ return this.model;
+};
+
+/**
+ * Function: getView
+ *
+ * Returns the <mxGraphView> that contains the <mxCellStates>.
+ */
+mxGraph.prototype.getView = function()
+{
+ return this.view;
+};
+
+/**
+ * Function: getStylesheet
+ *
+ * Returns the <mxStylesheet> that defines the style.
+ */
+mxGraph.prototype.getStylesheet = function()
+{
+ return this.stylesheet;
+};
+
+/**
+ * Function: setStylesheet
+ *
+ * Sets the <mxStylesheet> that defines the style.
+ */
+mxGraph.prototype.setStylesheet = function(stylesheet)
+{
+ this.stylesheet = stylesheet;
+};
+
+/**
+ * Function: getSelectionModel
+ *
+ * Returns the <mxGraphSelectionModel> that contains the selection.
+ */
+mxGraph.prototype.getSelectionModel = function()
+{
+ return this.selectionModel;
+};
+
+/**
+ * Function: setSelectionModel
+ *
+ * Sets the <mxSelectionModel> that contains the selection.
+ */
+mxGraph.prototype.setSelectionModel = function(selectionModel)
+{
+ this.selectionModel = selectionModel;
+};
+
+/**
+ * Function: getSelectionCellsForChanges
+ *
+ * Returns the cells to be selected for the given array of changes.
+ */
+mxGraph.prototype.getSelectionCellsForChanges = function(changes)
+{
+ var cells = [];
+
+ for (var i = 0; i < changes.length; i++)
+ {
+ var change = changes[i];
+
+ if (change.constructor != mxRootChange)
+ {
+ var cell = null;
+
+ if (change instanceof mxChildChange && change.previous == null)
+ {
+ cell = change.child;
+ }
+ else if (change.cell != null && change.cell instanceof mxCell)
+ {
+ cell = change.cell;
+ }
+
+ if (cell != null && mxUtils.indexOf(cells, cell) < 0)
+ {
+ cells.push(cell);
+ }
+ }
+ }
+
+ return this.getModel().getTopmostCells(cells);
+};
+
+/**
+ * Function: graphModelChanged
+ *
+ * Called when the graph model changes. Invokes <processChange> on each
+ * item of the given array to update the view accordingly.
+ *
+ * Parameters:
+ *
+ * changes - Array that contains the individual changes.
+ */
+mxGraph.prototype.graphModelChanged = function(changes)
+{
+ for (var i = 0; i < changes.length; i++)
+ {
+ this.processChange(changes[i]);
+ }
+
+ this.removeSelectionCells(this.getRemovedCellsForChanges(changes));
+
+ this.view.validate();
+ this.sizeDidChange();
+};
+
+/**
+ * Function: getRemovedCellsForChanges
+ *
+ * Returns the cells that have been removed from the model.
+ */
+mxGraph.prototype.getRemovedCellsForChanges = function(changes)
+{
+ var result = [];
+
+ for (var i = 0; i < changes.length; i++)
+ {
+ var change = changes[i];
+
+ // Resets the view settings, removes all cells and clears
+ // the selection if the root changes.
+ if (change instanceof mxRootChange)
+ {
+ break;
+ }
+ else if (change instanceof mxChildChange)
+ {
+ if (change.previous != null && change.parent == null)
+ {
+ result = result.concat(this.model.getDescendants(change.child));
+ }
+ }
+ else if (change instanceof mxVisibleChange)
+ {
+ result = result.concat(this.model.getDescendants(change.cell));
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: processChange
+ *
+ * Processes the given change and invalidates the respective cached data
+ * in <view>. This fires a <root> event if the root has changed in the
+ * model.
+ *
+ * Parameters:
+ *
+ * change - Object that represents the change on the model.
+ */
+mxGraph.prototype.processChange = function(change)
+{
+ // Resets the view settings, removes all cells and clears
+ // the selection if the root changes.
+ if (change instanceof mxRootChange)
+ {
+ this.clearSelection();
+ this.removeStateForCell(change.previous);
+
+ if (this.resetViewOnRootChange)
+ {
+ this.view.scale = 1;
+ this.view.translate.x = 0;
+ this.view.translate.y = 0;
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.ROOT));
+ }
+
+ // Adds or removes a child to the view by online invaliding
+ // the minimal required portions of the cache, namely, the
+ // old and new parent and the child.
+ else if (change instanceof mxChildChange)
+ {
+ var newParent = this.model.getParent(change.child);
+
+ if (newParent != null)
+ {
+ // Flags the cell for updating the order in the renderer
+ this.view.invalidate(change.child, true, false, change.previous != null);
+ }
+ else
+ {
+ this.removeStateForCell(change.child);
+
+ // Handles special case of current root of view being removed
+ if (this.view.currentRoot == change.child)
+ {
+ this.home();
+ }
+ }
+
+ if (newParent != change.previous)
+ {
+ // Refreshes the collapse/expand icons on the parents
+ if (newParent != null)
+ {
+ this.view.invalidate(newParent, false, false);
+ }
+
+ if (change.previous != null)
+ {
+ this.view.invalidate(change.previous, false, false);
+ }
+ }
+ }
+
+ // Handles two special cases where the shape does not need to be
+ // recreated from scratch, it only need to be invalidated.
+ else if (change instanceof mxTerminalChange ||
+ change instanceof mxGeometryChange)
+ {
+ this.view.invalidate(change.cell);
+ }
+
+ // Handles two special cases where only the shape, but no
+ // descendants need to be recreated
+ else if (change instanceof mxValueChange)
+ {
+ this.view.invalidate(change.cell, false, false);
+ }
+
+ // Requires a new mxShape in JavaScript
+ else if (change instanceof mxStyleChange)
+ {
+ this.view.invalidate(change.cell, true, true, false);
+ this.view.removeState(change.cell);
+ }
+
+ // Removes the state from the cache by default
+ else if (change.cell != null &&
+ change.cell instanceof mxCell)
+ {
+ this.removeStateForCell(change.cell);
+ }
+};
+
+/**
+ * Function: removeStateForCell
+ *
+ * Removes all cached information for the given cell and its descendants.
+ * This is called when a cell was removed from the model.
+ *
+ * Paramters:
+ *
+ * cell - <mxCell> that was removed from the model.
+ */
+mxGraph.prototype.removeStateForCell = function(cell)
+{
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.removeStateForCell(this.model.getChildAt(cell, i));
+ }
+
+ this.view.removeState(cell);
+};
+
+/**
+ * Group: Overlays
+ */
+
+/**
+ * Function: addCellOverlay
+ *
+ * Adds an <mxCellOverlay> for the specified cell. This method fires an
+ * <addoverlay> event and returns the new <mxCellOverlay>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to add the overlay for.
+ * overlay - <mxCellOverlay> to be added for the cell.
+ */
+mxGraph.prototype.addCellOverlay = function(cell, overlay)
+{
+ if (cell.overlays == null)
+ {
+ cell.overlays = [];
+ }
+
+ cell.overlays.push(overlay);
+
+ var state = this.view.getState(cell);
+
+ // Immediately updates the cell display if the state exists
+ if (state != null)
+ {
+ this.cellRenderer.redraw(state);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.ADD_OVERLAY,
+ 'cell', cell, 'overlay', overlay));
+
+ return overlay;
+};
+
+/**
+ * Function: getCellOverlays
+ *
+ * Returns the array of <mxCellOverlays> for the given cell or null, if
+ * no overlays are defined.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose overlays should be returned.
+ */
+mxGraph.prototype.getCellOverlays = function(cell)
+{
+ return cell.overlays;
+};
+
+/**
+ * Function: removeCellOverlay
+ *
+ * Removes and returns the given <mxCellOverlay> from the given cell. This
+ * method fires a <removeoverlay> event. If no overlay is given, then all
+ * overlays are removed using <removeOverlays>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose overlay should be removed.
+ * overlay - Optional <mxCellOverlay> to be removed.
+ */
+mxGraph.prototype.removeCellOverlay = function(cell, overlay)
+{
+ if (overlay == null)
+ {
+ this.removeCellOverlays(cell);
+ }
+ else
+ {
+ var index = mxUtils.indexOf(cell.overlays, overlay);
+
+ if (index >= 0)
+ {
+ cell.overlays.splice(index, 1);
+
+ if (cell.overlays.length == 0)
+ {
+ cell.overlays = null;
+ }
+
+ // Immediately updates the cell display if the state exists
+ var state = this.view.getState(cell);
+
+ if (state != null)
+ {
+ this.cellRenderer.redraw(state);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,
+ 'cell', cell, 'overlay', overlay));
+ }
+ else
+ {
+ overlay = null;
+ }
+ }
+
+ return overlay;
+};
+
+/**
+ * Function: removeCellOverlays
+ *
+ * Removes all <mxCellOverlays> from the given cell. This method
+ * fires a <removeoverlay> event for each <mxCellOverlay> and returns
+ * the array of <mxCellOverlays> that was removed from the cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose overlays should be removed
+ */
+mxGraph.prototype.removeCellOverlays = function(cell)
+{
+ var overlays = cell.overlays;
+
+ if (overlays != null)
+ {
+ cell.overlays = null;
+
+ // Immediately updates the cell display if the state exists
+ var state = this.view.getState(cell);
+
+ if (state != null)
+ {
+ this.cellRenderer.redraw(state);
+ }
+
+ for (var i = 0; i < overlays.length; i++)
+ {
+ this.fireEvent(new mxEventObject(mxEvent.REMOVE_OVERLAY,
+ 'cell', cell, 'overlay', overlays[i]));
+ }
+ }
+
+ return overlays;
+};
+
+/**
+ * Function: clearCellOverlays
+ *
+ * Removes all <mxCellOverlays> in the graph for the given cell and all its
+ * descendants. If no cell is specified then all overlays are removed from
+ * the graph. This implementation uses <removeCellOverlays> to remove the
+ * overlays from the individual cells.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> that represents the root of the subtree to
+ * remove the overlays from. Default is the root in the model.
+ */
+mxGraph.prototype.clearCellOverlays = function(cell)
+{
+ cell = (cell != null) ? cell : this.model.getRoot();
+ this.removeCellOverlays(cell);
+
+ // Recursively removes all overlays from the children
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.model.getChildAt(cell, i);
+ this.clearCellOverlays(child); // recurse
+ }
+};
+
+/**
+ * Function: setCellWarning
+ *
+ * Creates an overlay for the given cell using the warning and image or
+ * <warningImage> and returns the new <mxCellOverlay>. The warning is
+ * displayed as a tooltip in a red font and may contain HTML markup. If
+ * the warning is null or a zero length string, then all overlays are
+ * removed from the cell.
+ *
+ * Example:
+ *
+ * (code)
+ * graph.setCellWarning(cell, '<b>Warning:</b>: Hello, World!');
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose warning should be set.
+ * warning - String that represents the warning to be displayed.
+ * img - Optional <mxImage> to be used for the overlay. Default is
+ * <warningImage>.
+ * isSelect - Optional boolean indicating if a click on the overlay
+ * should select the corresponding cell. Default is false.
+ */
+mxGraph.prototype.setCellWarning = function(cell, warning, img, isSelect)
+{
+ if (warning != null && warning.length > 0)
+ {
+ img = (img != null) ? img : this.warningImage;
+
+ // Creates the overlay with the image and warning
+ var overlay = new mxCellOverlay(img,
+ '<font color=red>'+warning+'</font>');
+
+ // Adds a handler for single mouseclicks to select the cell
+ if (isSelect)
+ {
+ overlay.addListener(mxEvent.CLICK,
+ mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.setSelectionCell(cell);
+ }
+ })
+ );
+ }
+
+ // Sets and returns the overlay in the graph
+ return this.addCellOverlay(cell, overlay);
+ }
+ else
+ {
+ this.removeCellOverlays(cell);
+ }
+
+ return null;
+};
+
+/**
+ * Group: In-place editing
+ */
+
+/**
+ * Function: startEditing
+ *
+ * Calls <startEditingAtCell> using the given cell or the first selection
+ * cell.
+ *
+ * Parameters:
+ *
+ * evt - Optional mouse event that triggered the editing.
+ */
+mxGraph.prototype.startEditing = function(evt)
+{
+ this.startEditingAtCell(null, evt);
+};
+
+/**
+ * Function: startEditingAtCell
+ *
+ * Fires a <startEditing> event and invokes <mxCellEditor.startEditing>
+ * on <editor>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to start the in-place editor for.
+ * evt - Optional mouse event that triggered the editing.
+ */
+mxGraph.prototype.startEditingAtCell = function(cell, evt)
+{
+ if (cell == null)
+ {
+ cell = this.getSelectionCell();
+
+ if (cell != null && !this.isCellEditable(cell))
+ {
+ cell = null;
+ }
+ }
+
+ if (cell != null)
+ {
+ this.fireEvent(new mxEventObject(mxEvent.START_EDITING,
+ 'cell', cell, 'event', evt));
+ this.cellEditor.startEditing(cell, evt);
+ }
+};
+
+/**
+ * Function: getEditingValue
+ *
+ * Returns the initial value for in-place editing. This implementation
+ * returns <convertValueToString> for the given cell. If this function is
+ * overridden, then <mxGraphModel.valueForCellChanged> should take care
+ * of correctly storing the actual new value inside the user object.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the initial editing value should be returned.
+ * evt - Optional mouse event that triggered the editor.
+ */
+mxGraph.prototype.getEditingValue = function(cell, evt)
+{
+ return this.convertValueToString(cell);
+};
+
+/**
+ * Function: stopEditing
+ *
+ * Stops the current editing.
+ *
+ * Parameters:
+ *
+ * cancel - Boolean that specifies if the current editing value
+ * should be stored.
+ */
+mxGraph.prototype.stopEditing = function(cancel)
+{
+ this.cellEditor.stopEditing(cancel);
+};
+
+/**
+ * Function: labelChanged
+ *
+ * Sets the label of the specified cell to the given value using
+ * <cellLabelChanged> and fires <mxEvent.LABEL_CHANGED> while the
+ * transaction is in progress. Returns the cell whose label was changed.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose label should be changed.
+ * value - New label to be assigned.
+ * evt - Optional event that triggered the change.
+ */
+mxGraph.prototype.labelChanged = function(cell, value, evt)
+{
+ this.model.beginUpdate();
+ try
+ {
+ this.cellLabelChanged(cell, value, this.isAutoSizeCell(cell));
+ this.fireEvent(new mxEventObject(mxEvent.LABEL_CHANGED,
+ 'cell', cell, 'value', value, 'event', evt));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cell;
+};
+
+/**
+ * Function: cellLabelChanged
+ *
+ * Sets the new label for a cell. If autoSize is true then
+ * <cellSizeUpdated> will be called.
+ *
+ * In the following example, the function is extended to map changes to
+ * attributes in an XML node, as shown in <convertValueToString>.
+ * Alternatively, the handling of this can be implemented as shown in
+ * <mxGraphModel.valueForCellChanged> without the need to clone the
+ * user object.
+ *
+ * (code)
+ * var graphCellLabelChanged = graph.cellLabelChanged;
+ * graph.cellLabelChanged = function(cell, newValue, autoSize)
+ * {
+ * // Cloned for correct undo/redo
+ * var elt = cell.value.cloneNode(true);
+ * elt.setAttribute('label', newValue);
+ *
+ * newValue = elt;
+ * graphCellLabelChanged.apply(this, arguments);
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose label should be changed.
+ * value - New label to be assigned.
+ * autoSize - Boolean that specifies if <cellSizeUpdated> should be called.
+ */
+mxGraph.prototype.cellLabelChanged = function(cell, value, autoSize)
+{
+ this.model.beginUpdate();
+ try
+ {
+ this.model.setValue(cell, value);
+
+ if (autoSize)
+ {
+ this.cellSizeUpdated(cell, false);
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+};
+
+/**
+ * Group: Event processing
+ */
+
+/**
+ * Function: escape
+ *
+ * Processes an escape keystroke.
+ *
+ * Parameters:
+ *
+ * evt - Mouseevent that represents the keystroke.
+ */
+mxGraph.prototype.escape = function(evt)
+{
+ this.stopEditing(true);
+ this.connectionHandler.reset();
+ this.graphHandler.reset();
+
+ // Cancels all cell-based editing
+ var cells = this.getSelectionCells();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var state = this.view.getState(cells[i]);
+
+ if (state != null && state.handler != null)
+ {
+ state.handler.reset();
+ }
+ }
+};
+
+/**
+ * Function: click
+ *
+ * Processes a singleclick on an optional cell and fires a <click> event.
+ * The click event is fired initially. If the graph is enabled and the
+ * event has not been consumed, then the cell is selected using
+ * <selectCellForEvent> or the selection is cleared using
+ * <clearSelection>. The events consumed state is set to true if the
+ * corresponding <mxMouseEvent> has been consumed.
+ *
+ * Parameters:
+ *
+ * me - <mxMouseEvent> that represents the single click.
+ */
+mxGraph.prototype.click = function(me)
+{
+ var evt = me.getEvent();
+ var cell = me.getCell();
+ var mxe = new mxEventObject(mxEvent.CLICK, 'event', evt, 'cell', cell);
+
+ if (me.isConsumed())
+ {
+ mxe.consume();
+ }
+
+ this.fireEvent(mxe);
+
+ // Handles the event if it has not been consumed
+ if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed())
+ {
+ if (cell != null)
+ {
+ this.selectCellForEvent(cell, evt);
+ }
+ else
+ {
+ var swimlane = null;
+
+ if (this.isSwimlaneSelectionEnabled())
+ {
+ // Gets the swimlane at the location (includes
+ // content area of swimlanes)
+ swimlane = this.getSwimlaneAt(me.getGraphX(), me.getGraphY());
+ }
+
+ // Selects the swimlane and consumes the event
+ if (swimlane != null)
+ {
+ this.selectCellForEvent(swimlane, evt);
+ }
+
+ // Ignores the event if the control key is pressed
+ else if (!this.isToggleEvent(evt))
+ {
+ this.clearSelection();
+ }
+ }
+ }
+};
+
+/**
+ * Function: dblClick
+ *
+ * Processes a doubleclick on an optional cell and fires a <dblclick>
+ * event. The event is fired initially. If the graph is enabled and the
+ * event has not been consumed, then <edit> is called with the given
+ * cell. The event is ignored if no cell was specified.
+ *
+ * Example for overriding this method.
+ *
+ * (code)
+ * graph.dblClick = function(evt, cell)
+ * {
+ * var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);
+ * this.fireEvent(mxe);
+ *
+ * if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed())
+ * {
+ * mxUtils.alert('Hello, World!');
+ * mxe.consume();
+ * }
+ * }
+ * (end)
+ *
+ * Example listener for this event.
+ *
+ * (code)
+ * graph.addListener(mxEvent.DOUBLE_CLICK, function(sender, evt)
+ * {
+ * var cell = evt.getProperty('cell');
+ * // do something with the cell...
+ * });
+ * (end)
+ *
+ * Parameters:
+ *
+ * evt - Mouseevent that represents the doubleclick.
+ * cell - Optional <mxCell> under the mousepointer.
+ */
+mxGraph.prototype.dblClick = function(evt, cell)
+{
+ var mxe = new mxEventObject(mxEvent.DOUBLE_CLICK, 'event', evt, 'cell', cell);
+ this.fireEvent(mxe);
+
+ // Handles the event if it has not been consumed
+ if (this.isEnabled() && !mxEvent.isConsumed(evt) && !mxe.isConsumed() &&
+ cell != null && this.isCellEditable(cell))
+ {
+ this.startEditingAtCell(cell, evt);
+ }
+};
+
+/**
+ * Function: scrollPointToVisible
+ *
+ * Scrolls the graph to the given point, extending the graph container if
+ * specified.
+ */
+mxGraph.prototype.scrollPointToVisible = function(x, y, extend, border)
+{
+ if (!this.timerAutoScroll && (this.ignoreScrollbars || mxUtils.hasScrollbars(this.container)))
+ {
+ var c = this.container;
+ border = (border != null) ? border : 20;
+
+ if (x >= c.scrollLeft && y >= c.scrollTop && x <= c.scrollLeft + c.clientWidth &&
+ y <= c.scrollTop + c.clientHeight)
+ {
+ var dx = c.scrollLeft + c.clientWidth - x;
+
+ if (dx < border)
+ {
+ var old = c.scrollLeft;
+ c.scrollLeft += border - dx;
+
+ // Automatically extends the canvas size to the bottom, right
+ // if the event is outside of the canvas and the edge of the
+ // canvas has been reached. Notes: Needs fix for IE.
+ if (extend && old == c.scrollLeft)
+ {
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ var root = this.view.getDrawPane().ownerSVGElement;
+ var width = this.container.scrollWidth + border - dx;
+
+ // Updates the clipping region. This is an expensive
+ // operation that should not be executed too often.
+ root.setAttribute('width', width);
+ }
+ else
+ {
+ var width = Math.max(c.clientWidth, c.scrollWidth) + border - dx;
+ var canvas = this.view.getCanvas();
+ canvas.style.width = width + 'px';
+ }
+
+ c.scrollLeft += border - dx;
+ }
+ }
+ else
+ {
+ dx = x - c.scrollLeft;
+
+ if (dx < border)
+ {
+ c.scrollLeft -= border - dx;
+ }
+ }
+
+ var dy = c.scrollTop + c.clientHeight - y;
+
+ if (dy < border)
+ {
+ var old = c.scrollTop;
+ c.scrollTop += border - dy;
+
+ if (old == c.scrollTop && extend)
+ {
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ var root = this.view.getDrawPane().ownerSVGElement;
+ var height = this.container.scrollHeight + border - dy;
+
+ // Updates the clipping region. This is an expensive
+ // operation that should not be executed too often.
+ root.setAttribute('height', height);
+ }
+ else
+ {
+ var height = Math.max(c.clientHeight, c.scrollHeight) + border - dy;
+ var canvas = this.view.getCanvas();
+ canvas.style.height = height + 'px';
+ }
+
+ c.scrollTop += border - dy;
+ }
+ }
+ else
+ {
+ dy = y - c.scrollTop;
+
+ if (dy < border)
+ {
+ c.scrollTop -= border - dy;
+ }
+ }
+ }
+ }
+ else if (this.allowAutoPanning && !this.panningHandler.active)
+ {
+ if (this.panningManager == null)
+ {
+ this.panningManager = this.createPanningManager();
+ }
+
+ this.panningManager.panTo(x + this.panDx, y + this.panDy);
+ }
+};
+
+
+/**
+ * Function: createPanningManager
+ *
+ * Creates and returns an <mxPanningManager>.
+ */
+mxGraph.prototype.createPanningManager = function()
+{
+ return new mxPanningManager(this);
+};
+
+/**
+ * Function: getBorderSizes
+ *
+ * Returns the size of the border and padding on all four sides of the
+ * container. The left, top, right and bottom borders are stored in the x, y,
+ * width and height of the returned <mxRectangle>, respectively.
+ */
+mxGraph.prototype.getBorderSizes = function()
+{
+ // Helper function to handle string values for border widths (approx)
+ function parseBorder(value)
+ {
+ var result = 0;
+
+ if (value == 'thin')
+ {
+ result = 2;
+ }
+ else if (value == 'medium')
+ {
+ result = 4;
+ }
+ else if (value == 'thick')
+ {
+ result = 6;
+ }
+ else
+ {
+ result = parseInt(value);
+ }
+
+ if (isNaN(result))
+ {
+ result = 0;
+ }
+
+ return result;
+ }
+
+ var style = mxUtils.getCurrentStyle(this.container);
+ var result = new mxRectangle();
+ result.x = parseBorder(style.borderLeftWidth) + parseInt(style.paddingLeft || 0);
+ result.y = parseBorder(style.borderTopWidth) + parseInt(style.paddingTop || 0);
+ result.width = parseBorder(style.borderRightWidth) + parseInt(style.paddingRight || 0);
+ result.height = parseBorder(style.borderBottomWidth) + parseInt(style.paddingBottom || 0);
+
+ return result;
+};
+
+
+/**
+ * Function: getPreferredPageSize
+ *
+ * Returns the preferred size of the background page if <preferPageSize> is true.
+ */
+mxGraph.prototype.getPreferredPageSize = function(bounds, width, height)
+{
+ var scale = this.view.scale;
+ var tr = this.view.translate;
+ var fmt = this.pageFormat;
+ var ps = scale * this.pageScale;
+ var page = new mxRectangle(0, 0, fmt.width * ps, fmt.height * ps);
+
+ var hCount = (this.pageBreaksVisible) ? Math.ceil(width / page.width) : 1;
+ var vCount = (this.pageBreaksVisible) ? Math.ceil(height / page.height) : 1;
+
+ return new mxRectangle(0, 0, hCount * page.width + 2 + tr.x / scale, vCount * page.height + 2 + tr.y / scale);
+};
+
+/**
+ * Function: sizeDidChange
+ *
+ * Called when the size of the graph has changed. This implementation fires
+ * a <size> event after updating the clipping region of the SVG element in
+ * SVG-bases browsers.
+ */
+mxGraph.prototype.sizeDidChange = function()
+{
+ var bounds = this.getGraphBounds();
+
+ if (this.container != null)
+ {
+ var border = this.getBorder();
+
+ var width = Math.max(0, bounds.x + bounds.width + 1 + border);
+ var height = Math.max(0, bounds.y + bounds.height + 1 + border);
+
+ if (this.minimumContainerSize != null)
+ {
+ width = Math.max(width, this.minimumContainerSize.width);
+ height = Math.max(height, this.minimumContainerSize.height);
+ }
+
+ if (this.resizeContainer)
+ {
+ this.doResizeContainer(width, height);
+ }
+
+ if (this.preferPageSize || (!mxClient.IS_IE && this.pageVisible))
+ {
+ var size = this.getPreferredPageSize(bounds, width, height);
+
+ if (size != null)
+ {
+ width = size.width;
+ height = size.height;
+ }
+ }
+
+ if (this.minimumGraphSize != null)
+ {
+ width = Math.max(width, this.minimumGraphSize.width * this.view.scale);
+ height = Math.max(height, this.minimumGraphSize.height * this.view.scale);
+ }
+
+ width = Math.ceil(width - 1);
+ height = Math.ceil(height - 1);
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ var root = this.view.getDrawPane().ownerSVGElement;
+
+ root.style.minWidth = Math.max(1, width) + 'px';
+ root.style.minHeight = Math.max(1, height) + 'px';
+ }
+ else
+ {
+ if (mxClient.IS_QUIRKS)
+ {
+ // Quirks mode has no minWidth/minHeight support
+ this.view.updateHtmlCanvasSize(Math.max(1, width), Math.max(1, height));
+ }
+ else
+ {
+ this.view.canvas.style.minWidth = Math.max(1, width) + 'px';
+ this.view.canvas.style.minHeight = Math.max(1, height) + 'px';
+ }
+ }
+
+ this.updatePageBreaks(this.pageBreaksVisible, width - 1, height - 1);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.SIZE, 'bounds', bounds));
+};
+
+/**
+ * Function: doResizeContainer
+ *
+ * Resizes the container for the given graph width and height.
+ */
+mxGraph.prototype.doResizeContainer = function(width, height)
+{
+ // Fixes container size for different box models
+ if (mxClient.IS_IE)
+ {
+ if (mxClient.IS_QUIRKS)
+ {
+ var borders = this.getBorderSizes();
+
+ // max(2, ...) required for native IE8 in quirks mode
+ width += Math.max(2, borders.x + borders.width + 1);
+ height += Math.max(2, borders.y + borders.height + 1);
+ }
+ else if (document.documentMode >= 9)
+ {
+ width += 3;
+ height += 5;
+ }
+ else
+ {
+ width += 1;
+ height += 1;
+ }
+ }
+ else
+ {
+ height += 1;
+ }
+
+ if (this.maximumContainerSize != null)
+ {
+ width = Math.min(this.maximumContainerSize.width, width);
+ height = Math.min(this.maximumContainerSize.height, height);
+ }
+
+ this.container.style.width = Math.ceil(width) + 'px';
+ this.container.style.height = Math.ceil(height) + 'px';
+};
+
+/**
+ * Function: redrawPageBreaks
+ *
+ * Invokes from <sizeDidChange> to redraw the page breaks.
+ *
+ * Parameters:
+ *
+ * visible - Boolean that specifies if page breaks should be shown.
+ * width - Specifies the width of the container in pixels.
+ * height - Specifies the height of the container in pixels.
+ */
+mxGraph.prototype.updatePageBreaks = function(visible, width, height)
+{
+ var scale = this.view.scale;
+ var tr = this.view.translate;
+ var fmt = this.pageFormat;
+ var ps = scale * this.pageScale;
+ var bounds = new mxRectangle(scale * tr.x, scale * tr.y,
+ fmt.width * ps, fmt.height * ps);
+
+ // Does not show page breaks if the scale is too small
+ visible = visible && Math.min(bounds.width, bounds.height) > this.minPageBreakDist;
+
+ // Draws page breaks independent of translate. To ignore
+ // the translate set bounds.x/y = 0. Note that modulo
+ // in JavaScript has a bug, so use mxUtils instead.
+ bounds.x = mxUtils.mod(bounds.x, bounds.width);
+ bounds.y = mxUtils.mod(bounds.y, bounds.height);
+
+ var horizontalCount = (visible) ? Math.ceil((width - bounds.x) / bounds.width) : 0;
+ var verticalCount = (visible) ? Math.ceil((height - bounds.y) / bounds.height) : 0;
+ var right = width;
+ var bottom = height;
+
+ if (this.horizontalPageBreaks == null && horizontalCount > 0)
+ {
+ this.horizontalPageBreaks = [];
+ }
+
+ if (this.horizontalPageBreaks != null)
+ {
+ for (var i = 0; i <= horizontalCount; i++)
+ {
+ var pts = [new mxPoint(bounds.x + i * bounds.width, 1),
+ new mxPoint(bounds.x + i * bounds.width, bottom)];
+
+ if (this.horizontalPageBreaks[i] != null)
+ {
+ this.horizontalPageBreaks[i].scale = 1;
+ this.horizontalPageBreaks[i].points = pts;
+ this.horizontalPageBreaks[i].redraw();
+ }
+ else
+ {
+ var pageBreak = new mxPolyline(pts, this.pageBreakColor, this.scale);
+ pageBreak.dialect = this.dialect;
+ pageBreak.isDashed = this.pageBreakDashed;
+ pageBreak.scale = scale;
+ pageBreak.crisp = true;
+ pageBreak.init(this.view.backgroundPane);
+ pageBreak.redraw();
+
+ this.horizontalPageBreaks[i] = pageBreak;
+ }
+ }
+
+ for (var i = horizontalCount; i < this.horizontalPageBreaks.length; i++)
+ {
+ this.horizontalPageBreaks[i].destroy();
+ }
+
+ this.horizontalPageBreaks.splice(horizontalCount, this.horizontalPageBreaks.length - horizontalCount);
+ }
+
+ if (this.verticalPageBreaks == null && verticalCount > 0)
+ {
+ this.verticalPageBreaks = [];
+ }
+
+ if (this.verticalPageBreaks != null)
+ {
+ for (var i = 0; i <= verticalCount; i++)
+ {
+ var pts = [new mxPoint(1, bounds.y + i * bounds.height),
+ new mxPoint(right, bounds.y + i * bounds.height)];
+
+ if (this.verticalPageBreaks[i] != null)
+ {
+ this.verticalPageBreaks[i].scale = 1;
+ this.verticalPageBreaks[i].points = pts;
+ this.verticalPageBreaks[i].redraw();
+ }
+ else
+ {
+ var pageBreak = new mxPolyline(pts, this.pageBreakColor, scale);
+ pageBreak.dialect = this.dialect;
+ pageBreak.isDashed = this.pageBreakDashed;
+ pageBreak.scale = scale;
+ pageBreak.crisp = true;
+ pageBreak.init(this.view.backgroundPane);
+ pageBreak.redraw();
+
+ this.verticalPageBreaks[i] = pageBreak;
+ }
+ }
+
+ for (var i = verticalCount; i < this.verticalPageBreaks.length; i++)
+ {
+ this.verticalPageBreaks[i].destroy();
+ }
+
+ this.verticalPageBreaks.splice(verticalCount, this.verticalPageBreaks.length - verticalCount);
+ }
+};
+
+/**
+ * Group: Cell styles
+ */
+
+/**
+ * Function: getCellStyle
+ *
+ * Returns an array of key, value pairs representing the cell style for the
+ * given cell. If no string is defined in the model that specifies the
+ * style, then the default style for the cell is returned or <EMPTY_ARRAY>,
+ * if not style can be found. Note: You should try and get the cell state
+ * for the given cell and use the cached style in the state before using
+ * this method.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose style should be returned as an array.
+ */
+mxGraph.prototype.getCellStyle = function(cell)
+{
+ var stylename = this.model.getStyle(cell);
+ var style = null;
+
+ // Gets the default style for the cell
+ if (this.model.isEdge(cell))
+ {
+ style = this.stylesheet.getDefaultEdgeStyle();
+ }
+ else
+ {
+ style = this.stylesheet.getDefaultVertexStyle();
+ }
+
+ // Resolves the stylename using the above as the default
+ if (stylename != null)
+ {
+ style = this.postProcessCellStyle(this.stylesheet.getCellStyle(stylename, style));
+ }
+
+ // Returns a non-null value if no style can be found
+ if (style == null)
+ {
+ style = mxGraph.prototype.EMPTY_ARRAY;
+ }
+
+ return style;
+};
+
+/**
+ * Function: postProcessCellStyle
+ *
+ * Tries to resolve the value for the image style in the image bundles and
+ * turns short data URIs as defined in mxImageBundle to data URIs as
+ * defined in RFC 2397 of the IETF.
+ */
+mxGraph.prototype.postProcessCellStyle = function(style)
+{
+ if (style != null)
+ {
+ var key = style[mxConstants.STYLE_IMAGE];
+ var image = this.getImageFromBundles(key);
+
+ if (image != null)
+ {
+ style[mxConstants.STYLE_IMAGE] = image;
+ }
+ else
+ {
+ image = key;
+ }
+
+ // Converts short data uris to normal data uris
+ if (image != null && image.substring(0, 11) == "data:image/")
+ {
+ var comma = image.indexOf(',');
+
+ if (comma > 0)
+ {
+ image = image.substring(0, comma) + ";base64,"
+ + image.substring(comma + 1);
+ }
+
+ style[mxConstants.STYLE_IMAGE] = image;
+ }
+ }
+
+ return style;
+};
+
+/**
+ * Function: setCellStyle
+ *
+ * Sets the style of the specified cells. If no cells are given, then the
+ * selection cells are changed.
+ *
+ * Parameters:
+ *
+ * style - String representing the new style of the cells.
+ * cells - Optional array of <mxCells> to set the style for. Default is the
+ * selection cells.
+ */
+mxGraph.prototype.setCellStyle = function(style, cells)
+{
+ cells = cells || this.getSelectionCells();
+
+ if (cells != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ this.model.setStyle(cells[i], style);
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: toggleCellStyle
+ *
+ * Toggles the boolean value for the given key in the style of the
+ * given cell. If no cell is specified then the selection cell is
+ * used.
+ *
+ * Parameter:
+ *
+ * key - String representing the key for the boolean value to be toggled.
+ * defaultValue - Optional boolean default value if no value is defined.
+ * Default is false.
+ * cell - Optional <mxCell> whose style should be modified. Default is
+ * the selection cell.
+ */
+mxGraph.prototype.toggleCellStyle = function(key, defaultValue, cell)
+{
+ cell = cell || this.getSelectionCell();
+
+ this.toggleCellStyles(key, defaultValue, [cell]);
+};
+
+/**
+ * Function: toggleCellStyles
+ *
+ * Toggles the boolean value for the given key in the style of the given
+ * cells. If no cells are specified, then the selection cells are used. For
+ * example, this can be used to toggle <mxConstants.STYLE_ROUNDED> or any
+ * other style with a boolean value.
+ *
+ * Parameter:
+ *
+ * key - String representing the key for the boolean value to be toggled.
+ * defaultValue - Optional boolean default value if no value is defined.
+ * Default is false.
+ * cells - Optional array of <mxCells> whose styles should be modified.
+ * Default is the selection cells.
+ */
+mxGraph.prototype.toggleCellStyles = function(key, defaultValue, cells)
+{
+ defaultValue = (defaultValue != null) ? defaultValue : false;
+ cells = cells || this.getSelectionCells();
+
+ if (cells != null && cells.length > 0)
+ {
+ var state = this.view.getState(cells[0]);
+ var style = (state != null) ? state.style : this.getCellStyle(cells[0]);
+
+ if (style != null)
+ {
+ var val = (mxUtils.getValue(style, key, defaultValue)) ? 0 : 1;
+ this.setCellStyles(key, val, cells);
+ }
+ }
+};
+
+/**
+ * Function: setCellStyles
+ *
+ * Sets the key to value in the styles of the given cells. This will modify
+ * the existing cell styles in-place and override any existing assignment
+ * for the given key. If no cells are specified, then the selection cells
+ * are changed. If no value is specified, then the respective key is
+ * removed from the styles.
+ *
+ * Parameters:
+ *
+ * key - String representing the key to be assigned.
+ * value - String representing the new value for the key.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.setCellStyles = function(key, value, cells)
+{
+ cells = cells || this.getSelectionCells();
+ mxUtils.setCellStyles(this.model, cells, key, value);
+};
+
+/**
+ * Function: toggleCellStyleFlags
+ *
+ * Toggles the given bit for the given key in the styles of the specified
+ * cells.
+ *
+ * Parameters:
+ *
+ * key - String representing the key to toggle the flag in.
+ * flag - Integer that represents the bit to be toggled.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.toggleCellStyleFlags = function(key, flag, cells)
+{
+ this.setCellStyleFlags(key, flag, null, cells);
+};
+
+/**
+ * Function: setCellStyleFlags
+ *
+ * Sets or toggles the given bit for the given key in the styles of the
+ * specified cells.
+ *
+ * Parameters:
+ *
+ * key - String representing the key to toggle the flag in.
+ * flag - Integer that represents the bit to be toggled.
+ * value - Boolean value to be used or null if the value should be toggled.
+ * cells - Optional array of <mxCells> to change the style for. Default is
+ * the selection cells.
+ */
+mxGraph.prototype.setCellStyleFlags = function(key, flag, value, cells)
+{
+ cells = cells || this.getSelectionCells();
+
+ if (cells != null && cells.length > 0)
+ {
+ if (value == null)
+ {
+ var state = this.view.getState(cells[0]);
+ var style = (state != null) ? state.style : this.getCellStyle(cells[0]);
+
+ if (style != null)
+ {
+ var current = parseInt(style[key] || 0);
+ value = !((current & flag) == flag);
+ }
+ }
+
+ mxUtils.setCellStyleFlags(this.model, cells, key, flag, value);
+ }
+};
+
+/**
+ * Group: Cell alignment and orientation
+ */
+
+/**
+ * Function: alignCells
+ *
+ * Aligns the given cells vertically or horizontally according to the given
+ * alignment using the optional parameter as the coordinate.
+ *
+ * Parameters:
+ *
+ * align - Specifies the alignment. Possible values are all constants in
+ * mxConstants with an ALIGN prefix.
+ * cells - Array of <mxCells> to be aligned.
+ * param - Optional coordinate for the alignment.
+ */
+mxGraph.prototype.alignCells = function(align, cells, param)
+{
+ if (cells == null)
+ {
+ cells = this.getSelectionCells();
+ }
+
+ if (cells != null && cells.length > 1)
+ {
+ // Finds the required coordinate for the alignment
+ if (param == null)
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var geo = this.getCellGeometry(cells[i]);
+
+ if (geo != null && !this.model.isEdge(cells[i]))
+ {
+ if (param == null)
+ {
+ if (align == mxConstants.ALIGN_CENTER)
+ {
+ param = geo.x + geo.width / 2;
+ break;
+ }
+ else if (align == mxConstants.ALIGN_RIGHT)
+ {
+ param = geo.x + geo.width;
+ }
+ else if (align == mxConstants.ALIGN_TOP)
+ {
+ param = geo.y;
+ }
+ else if (align == mxConstants.ALIGN_MIDDLE)
+ {
+ param = geo.y + geo.height / 2;
+ break;
+ }
+ else if (align == mxConstants.ALIGN_BOTTOM)
+ {
+ param = geo.y + geo.height;
+ }
+ else
+ {
+ param = geo.x;
+ }
+ }
+ else
+ {
+ if (align == mxConstants.ALIGN_RIGHT)
+ {
+ param = Math.max(param, geo.x + geo.width);
+ }
+ else if (align == mxConstants.ALIGN_TOP)
+ {
+ param = Math.min(param, geo.y);
+ }
+ else if (align == mxConstants.ALIGN_BOTTOM)
+ {
+ param = Math.max(param, geo.y + geo.height);
+ }
+ else
+ {
+ param = Math.min(param, geo.x);
+ }
+ }
+ }
+ }
+ }
+
+ // Aligns the cells to the coordinate
+ if (param != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var geo = this.getCellGeometry(cells[i]);
+
+ if (geo != null && !this.model.isEdge(cells[i]))
+ {
+ geo = geo.clone();
+
+ if (align == mxConstants.ALIGN_CENTER)
+ {
+ geo.x = param - geo.width / 2;
+ }
+ else if (align == mxConstants.ALIGN_RIGHT)
+ {
+ geo.x = param - geo.width;
+ }
+ else if (align == mxConstants.ALIGN_TOP)
+ {
+ geo.y = param;
+ }
+ else if (align == mxConstants.ALIGN_MIDDLE)
+ {
+ geo.y = param - geo.height / 2;
+ }
+ else if (align == mxConstants.ALIGN_BOTTOM)
+ {
+ geo.y = param - geo.height;
+ }
+ else
+ {
+ geo.x = param;
+ }
+
+ this.model.setGeometry(cells[i], geo);
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.ALIGN_CELLS,
+ 'align', align, 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+ }
+
+ return cells;
+};
+
+/**
+ * Function: flipEdge
+ *
+ * Toggles the style of the given edge between null (or empty) and
+ * <alternateEdgeStyle>. This method fires <mxEvent.FLIP_EDGE> while the
+ * transaction is in progress. Returns the edge that was flipped.
+ *
+ * Here is an example that overrides this implementation to invert the
+ * value of <mxConstants.STYLE_ELBOW> without removing any existing styles.
+ *
+ * (code)
+ * graph.flipEdge = function(edge)
+ * {
+ * if (edge != null)
+ * {
+ * var state = this.view.getState(edge);
+ * var style = (state != null) ? state.style : this.getCellStyle(edge);
+ *
+ * if (style != null)
+ * {
+ * var elbow = mxUtils.getValue(style, mxConstants.STYLE_ELBOW,
+ * mxConstants.ELBOW_HORIZONTAL);
+ * var value = (elbow == mxConstants.ELBOW_HORIZONTAL) ?
+ * mxConstants.ELBOW_VERTICAL : mxConstants.ELBOW_HORIZONTAL;
+ * this.setCellStyles(mxConstants.STYLE_ELBOW, value, [edge]);
+ * }
+ * }
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> whose style should be changed.
+ */
+mxGraph.prototype.flipEdge = function(edge)
+{
+ if (edge != null &&
+ this.alternateEdgeStyle != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ var style = this.model.getStyle(edge);
+
+ if (style == null || style.length == 0)
+ {
+ this.model.setStyle(edge, this.alternateEdgeStyle);
+ }
+ else
+ {
+ this.model.setStyle(edge, null);
+ }
+
+ // Removes all existing control points
+ this.resetEdge(edge);
+ this.fireEvent(new mxEventObject(mxEvent.FLIP_EDGE, 'edge', edge));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+
+ return edge;
+};
+
+/**
+ * Function: addImageBundle
+ *
+ * Adds the specified <mxImageBundle>.
+ */
+mxGraph.prototype.addImageBundle = function(bundle)
+{
+ this.imageBundles.push(bundle);
+};
+
+/**
+ * Function: removeImageBundle
+ *
+ * Removes the specified <mxImageBundle>.
+ */
+mxGraph.prototype.removeImageBundle = function(bundle)
+{
+ var tmp = [];
+
+ for (var i = 0; i < this.imageBundles.length; i++)
+ {
+ if (this.imageBundles[i] != bundle)
+ {
+ tmp.push(this.imageBundles[i]);
+ }
+ }
+
+ this.imageBundles = tmp;
+};
+
+/**
+ * Function: getImageFromBundles
+ *
+ * Searches all <imageBundles> for the specified key and returns the value
+ * for the first match or null if the key is not found.
+ */
+mxGraph.prototype.getImageFromBundles = function(key)
+{
+ if (key != null)
+ {
+ for (var i = 0; i < this.imageBundles.length; i++)
+ {
+ var image = this.imageBundles[i].getImage(key);
+
+ if (image != null)
+ {
+ return image;
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Group: Order
+ */
+
+/**
+ * Function: orderCells
+ *
+ * Moves the given cells to the front or back. The change is carried out
+ * using <cellsOrdered>. This method fires <mxEvent.ORDER_CELLS> while the
+ * transaction is in progress.
+ *
+ * Parameters:
+ *
+ * back - Boolean that specifies if the cells should be moved to back.
+ * cells - Array of <mxCells> to move to the background. If null is
+ * specified then the selection cells are used.
+ */
+ mxGraph.prototype.orderCells = function(back, cells)
+ {
+ if (cells == null)
+ {
+ cells = mxUtils.sortCells(this.getSelectionCells(), true);
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsOrdered(cells, back);
+ this.fireEvent(new mxEventObject(mxEvent.ORDER_CELLS,
+ 'back', back, 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+ };
+
+/**
+ * Function: cellsOrdered
+ *
+ * Moves the given cells to the front or back. This method fires
+ * <mxEvent.CELLS_ORDERED> while the transaction is in progress.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose order should be changed.
+ * back - Boolean that specifies if the cells should be moved to back.
+ */
+ mxGraph.prototype.cellsOrdered = function(cells, back)
+ {
+ if (cells != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var parent = this.model.getParent(cells[i]);
+
+ if (back)
+ {
+ this.model.add(parent, cells[i], i);
+ }
+ else
+ {
+ this.model.add(parent, cells[i],
+ this.model.getChildCount(parent) - 1);
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_ORDERED,
+ 'back', back, 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Group: Grouping
+ */
+
+/**
+ * Function: groupCells
+ *
+ * Adds the cells into the given group. The change is carried out using
+ * <cellsAdded>, <cellsMoved> and <cellsResized>. This method fires
+ * <mxEvent.GROUP_CELLS> while the transaction is in progress. Returns the
+ * new group. A group is only created if there is at least one entry in the
+ * given array of cells.
+ *
+ * Parameters:
+ *
+ * group - <mxCell> that represents the target group. If null is specified
+ * then a new group is created using <createGroupCell>.
+ * border - Optional integer that specifies the border between the child
+ * area and the group bounds. Default is 0.
+ * cells - Optional array of <mxCells> to be grouped. If null is specified
+ * then the selection cells are used.
+ */
+mxGraph.prototype.groupCells = function(group, border, cells)
+{
+ if (cells == null)
+ {
+ cells = mxUtils.sortCells(this.getSelectionCells(), true);
+ }
+
+ cells = this.getCellsForGroup(cells);
+
+ if (group == null)
+ {
+ group = this.createGroupCell(cells);
+ }
+
+ var bounds = this.getBoundsForGroup(group, cells, border);
+
+ if (cells.length > 0 && bounds != null)
+ {
+ // Uses parent of group or previous parent of first child
+ var parent = this.model.getParent(group);
+
+ if (parent == null)
+ {
+ parent = this.model.getParent(cells[0]);
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ // Checks if the group has a geometry and
+ // creates one if one does not exist
+ if (this.getCellGeometry(group) == null)
+ {
+ this.model.setGeometry(group, new mxGeometry());
+ }
+
+ // Adds the children into the group and moves
+ var index = this.model.getChildCount(group);
+ this.cellsAdded(cells, group, index, null, null, false, false);
+ this.cellsMoved(cells, -bounds.x, -bounds.y, false, true);
+
+ // Adds the group into the parent and resizes
+ index = this.model.getChildCount(parent);
+ this.cellsAdded([group], parent, index, null, null, false);
+ this.cellsResized([group], [bounds]);
+
+ this.fireEvent(new mxEventObject(mxEvent.GROUP_CELLS,
+ 'group', group, 'border', border, 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+
+ return group;
+};
+
+/**
+ * Function: getCellsForGroup
+ *
+ * Returns the cells with the same parent as the first cell
+ * in the given array.
+ */
+mxGraph.prototype.getCellsForGroup = function(cells)
+{
+ var result = [];
+
+ if (cells != null && cells.length > 0)
+ {
+ var parent = this.model.getParent(cells[0]);
+ result.push(cells[0]);
+
+ // Filters selection cells with the same parent
+ for (var i = 1; i < cells.length; i++)
+ {
+ if (this.model.getParent(cells[i]) == parent)
+ {
+ result.push(cells[i]);
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getBoundsForGroup
+ *
+ * Returns the bounds to be used for the given group and children.
+ */
+mxGraph.prototype.getBoundsForGroup = function(group, children, border)
+{
+ var result = this.getBoundingBoxFromGeometry(children);
+
+ if (result != null)
+ {
+ if (this.isSwimlane(group))
+ {
+ var size = this.getStartSize(group);
+
+ result.x -= size.width;
+ result.y -= size.height;
+ result.width += size.width;
+ result.height += size.height;
+ }
+
+ // Adds the border
+ result.x -= border;
+ result.y -= border;
+ result.width += 2 * border;
+ result.height += 2 * border;
+ }
+
+ return result;
+};
+
+/**
+ * Function: createGroupCell
+ *
+ * Hook for creating the group cell to hold the given array of <mxCells> if
+ * no group cell was given to the <group> function.
+ *
+ * The following code can be used to set the style of new group cells.
+ *
+ * (code)
+ * var graphCreateGroupCell = graph.createGroupCell;
+ * graph.createGroupCell = function(cells)
+ * {
+ * var group = graphCreateGroupCell.apply(this, arguments);
+ * group.setStyle('group');
+ *
+ * return group;
+ * };
+ */
+mxGraph.prototype.createGroupCell = function(cells)
+{
+ var group = new mxCell('');
+ group.setVertex(true);
+ group.setConnectable(false);
+
+ return group;
+};
+
+/**
+ * Function: ungroupCells
+ *
+ * Ungroups the given cells by moving the children the children to their
+ * parents parent and removing the empty groups. Returns the children that
+ * have been removed from the groups.
+ *
+ * Parameters:
+ *
+ * cells - Array of cells to be ungrouped. If null is specified then the
+ * selection cells are used.
+ */
+mxGraph.prototype.ungroupCells = function(cells)
+{
+ var result = [];
+
+ if (cells == null)
+ {
+ cells = this.getSelectionCells();
+
+ // Finds the cells with children
+ var tmp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.model.getChildCount(cells[i]) > 0)
+ {
+ tmp.push(cells[i]);
+ }
+ }
+
+ cells = tmp;
+ }
+
+ if (cells != null && cells.length > 0)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var children = this.model.getChildren(cells[i]);
+
+ if (children != null && children.length > 0)
+ {
+ children = children.slice();
+ var parent = this.model.getParent(cells[i]);
+ var index = this.model.getChildCount(parent);
+
+ this.cellsAdded(children, parent, index, null, null, true);
+ result = result.concat(children);
+ }
+ }
+
+ this.cellsRemoved(this.addAllEdges(cells));
+ this.fireEvent(new mxEventObject(mxEvent.UNGROUP_CELLS,
+ 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: removeCellsFromParent
+ *
+ * Removes the specified cells from their parents and adds them to the
+ * default parent. Returns the cells that were removed from their parents.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be removed from their parents.
+ */
+mxGraph.prototype.removeCellsFromParent = function(cells)
+{
+ if (cells == null)
+ {
+ cells = this.getSelectionCells();
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ var parent = this.getDefaultParent();
+ var index = this.model.getChildCount(parent);
+
+ this.cellsAdded(cells, parent, index, null, null, true);
+ this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS_FROM_PARENT,
+ 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: updateGroupBounds
+ *
+ * Updates the bounds of the given array of groups so that it includes
+ * all child vertices.
+ *
+ * Parameters:
+ *
+ * cells - The groups whose bounds should be updated.
+ * border - Optional border to be added in the group. Default is 0.
+ * moveGroup - Optional boolean that allows the group to be moved. Default
+ * is false.
+ */
+mxGraph.prototype.updateGroupBounds = function(cells, border, moveGroup)
+{
+ if (cells == null)
+ {
+ cells = this.getSelectionCells();
+ }
+
+ border = (border != null) ? border : 0;
+ moveGroup = (moveGroup != null) ? moveGroup : false;
+
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var geo = this.getCellGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ var children = this.getChildCells(cells[i]);
+
+ if (children != null && children.length > 0)
+ {
+ var childBounds = this.getBoundingBoxFromGeometry(children);
+
+ if (childBounds.width > 0 && childBounds.height > 0)
+ {
+ var size = (this.isSwimlane(cells[i])) ?
+ this.getStartSize(cells[i]) : new mxRectangle();
+
+ geo = geo.clone();
+
+ if (moveGroup)
+ {
+ geo.x += childBounds.x - size.width - border;
+ geo.y += childBounds.y - size.height - border;
+ }
+
+ geo.width = childBounds.width + size.width + 2 * border;
+ geo.height = childBounds.height + size.height + 2 * border;
+
+ this.model.setGeometry(cells[i], geo);
+ this.moveCells(children, -childBounds.x + size.width + border,
+ -childBounds.y + size.height + border);
+ }
+ }
+ }
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Group: Cell cloning, insertion and removal
+ */
+
+/**
+ * Function: cloneCells
+ *
+ * Returns the clones for the given cells. If the terminal of an edge is
+ * not in the given array, then the respective end is assigned a terminal
+ * point and the terminal is removed.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be cloned.
+ * allowInvalidEdges - Optional boolean that specifies if invalid edges
+ * should be cloned. Default is true.
+ */
+mxGraph.prototype.cloneCells = function(cells, allowInvalidEdges)
+{
+ allowInvalidEdges = (allowInvalidEdges != null) ? allowInvalidEdges : true;
+ var clones = null;
+
+ if (cells != null)
+ {
+ // Creates a hashtable for cell lookups
+ var hash = new Object();
+ var tmp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var id = mxCellPath.create(cells[i]);
+ hash[id] = cells[i];
+ tmp.push(cells[i]);
+ }
+
+ if (tmp.length > 0)
+ {
+ var scale = this.view.scale;
+ var trans = this.view.translate;
+ clones = this.model.cloneCells(cells, true);
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!allowInvalidEdges && this.model.isEdge(clones[i]) &&
+ this.getEdgeValidationError(clones[i],
+ this.model.getTerminal(clones[i], true),
+ this.model.getTerminal(clones[i], false)) != null)
+ {
+ clones[i] = null;
+ }
+ else
+ {
+ var g = this.model.getGeometry(clones[i]);
+
+ if (g != null)
+ {
+ var state = this.view.getState(cells[i]);
+ var pstate = this.view.getState(
+ this.model.getParent(cells[i]));
+
+ if (state != null && pstate != null)
+ {
+ var dx = pstate.origin.x;
+ var dy = pstate.origin.y;
+
+ if (this.model.isEdge(clones[i]))
+ {
+ var pts = state.absolutePoints;
+
+ // Checks if the source is cloned or sets the terminal point
+ var src = this.model.getTerminal(cells[i], true);
+ var srcId = mxCellPath.create(src);
+
+ while (src != null && hash[srcId] == null)
+ {
+ src = this.model.getParent(src);
+ srcId = mxCellPath.create(src);
+ }
+
+ if (src == null)
+ {
+ g.setTerminalPoint(
+ new mxPoint(pts[0].x / scale - trans.x,
+ pts[0].y / scale - trans.y), true);
+ }
+
+ // Checks if the target is cloned or sets the terminal point
+ var trg = this.model.getTerminal(cells[i], false);
+ var trgId = mxCellPath.create(trg);
+
+ while (trg != null && hash[trgId] == null)
+ {
+ trg = this.model.getParent(trg);
+ trgId = mxCellPath.create(trg);
+ }
+
+ if (trg == null)
+ {
+ var n = pts.length - 1;
+ g.setTerminalPoint(
+ new mxPoint(pts[n].x / scale - trans.x,
+ pts[n].y / scale - trans.y), false);
+ }
+
+ // Translates the control points
+ var points = g.points;
+
+ if (points != null)
+ {
+ for (var j = 0; j < points.length; j++)
+ {
+ points[j].x += dx;
+ points[j].y += dy;
+ }
+ }
+ }
+ else
+ {
+ g.x += dx;
+ g.y += dy;
+ }
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ clones = [];
+ }
+ }
+
+ return clones;
+};
+
+/**
+ * Function: insertVertex
+ *
+ * Adds a new vertex into the given parent <mxCell> using value as the user
+ * object and the given coordinates as the <mxGeometry> of the new vertex.
+ * The id and style are used for the respective properties of the new
+ * <mxCell>, which is returned.
+ *
+ * When adding new vertices from a mouse event, one should take into
+ * account the offset of the graph container and the scale and translation
+ * of the view in order to find the correct unscaled, untranslated
+ * coordinates using <mxGraph.getPointForEvent> as follows:
+ *
+ * (code)
+ * var pt = graph.getPointForEvent(evt);
+ * var parent = graph.getDefaultParent();
+ * graph.insertVertex(parent, null,
+ * 'Hello, World!', x, y, 220, 30);
+ * (end)
+ *
+ * For adding image cells, the style parameter can be assigned as
+ *
+ * (code)
+ * stylename;image=imageUrl
+ * (end)
+ *
+ * See <mxGraph> for more information on using images.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> that specifies the parent of the new vertex.
+ * id - Optional string that defines the Id of the new vertex.
+ * value - Object to be used as the user object.
+ * x - Integer that defines the x coordinate of the vertex.
+ * y - Integer that defines the y coordinate of the vertex.
+ * width - Integer that defines the width of the vertex.
+ * height - Integer that defines the height of the vertex.
+ * style - Optional string that defines the cell style.
+ * relative - Optional boolean that specifies if the geometry is relative.
+ * Default is false.
+ */
+mxGraph.prototype.insertVertex = function(parent, id, value,
+ x, y, width, height, style, relative)
+{
+ var vertex = this.createVertex(parent, id, value, x, y, width, height, style, relative);
+
+ return this.addCell(vertex, parent);
+};
+
+/**
+ * Function: createVertex
+ *
+ * Hook method that creates the new vertex for <insertVertex>.
+ */
+mxGraph.prototype.createVertex = function(parent, id, value,
+ x, y, width, height, style, relative)
+{
+ // Creates the geometry for the vertex
+ var geometry = new mxGeometry(x, y, width, height);
+ geometry.relative = (relative != null) ? relative : false;
+
+ // Creates the vertex
+ var vertex = new mxCell(value, geometry, style);
+ vertex.setId(id);
+ vertex.setVertex(true);
+ vertex.setConnectable(true);
+
+ return vertex;
+};
+
+/**
+ * Function: insertEdge
+ *
+ * Adds a new edge into the given parent <mxCell> using value as the user
+ * object and the given source and target as the terminals of the new edge.
+ * The id and style are used for the respective properties of the new
+ * <mxCell>, which is returned.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> that specifies the parent of the new edge.
+ * id - Optional string that defines the Id of the new edge.
+ * value - JavaScript object to be used as the user object.
+ * source - <mxCell> that defines the source of the edge.
+ * target - <mxCell> that defines the target of the edge.
+ * style - Optional string that defines the cell style.
+ */
+mxGraph.prototype.insertEdge = function(parent, id, value, source, target, style)
+{
+ var edge = this.createEdge(parent, id, value, source, target, style);
+
+ return this.addEdge(edge, parent, source, target);
+};
+
+/**
+ * Function: createEdge
+ *
+ * Hook method that creates the new edge for <insertEdge>. This
+ * implementation does not set the source and target of the edge, these
+ * are set when the edge is added to the model.
+ *
+ */
+mxGraph.prototype.createEdge = function(parent, id, value, source, target, style)
+{
+ // Creates the edge
+ var edge = new mxCell(value, new mxGeometry(), style);
+ edge.setId(id);
+ edge.setEdge(true);
+ edge.geometry.relative = true;
+
+ return edge;
+};
+
+/**
+ * Function: addEdge
+ *
+ * Adds the edge to the parent and connects it to the given source and
+ * target terminals. This is a shortcut method. Returns the edge that was
+ * added.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> to be inserted into the given parent.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * source - Optional <mxCell> that represents the source terminal.
+ * target - Optional <mxCell> that represents the target terminal.
+ * index - Optional index to insert the cells at. Default is to append.
+ */
+mxGraph.prototype.addEdge = function(edge, parent, source, target, index)
+{
+ return this.addCell(edge, parent, index, source, target);
+};
+
+/**
+ * Function: addCell
+ *
+ * Adds the cell to the parent and connects it to the given source and
+ * target terminals. This is a shortcut method. Returns the cell that was
+ * added.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be inserted into the given parent.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * index - Optional index to insert the cells at. Default is to append.
+ * source - Optional <mxCell> that represents the source terminal.
+ * target - Optional <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.addCell = function(cell, parent, index, source, target)
+{
+ return this.addCells([cell], parent, index, source, target)[0];
+};
+
+/**
+ * Function: addCells
+ *
+ * Adds the cells to the parent at the given index, connecting each cell to
+ * the optional source and target terminal. The change is carried out using
+ * <cellsAdded>. This method fires <mxEvent.ADD_CELLS> while the
+ * transaction is in progress. Returns the cells that were added.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be inserted.
+ * parent - <mxCell> that represents the new parent. If no parent is
+ * given then the default parent is used.
+ * index - Optional index to insert the cells at. Default is to append.
+ * source - Optional source <mxCell> for all inserted cells.
+ * target - Optional target <mxCell> for all inserted cells.
+ */
+mxGraph.prototype.addCells = function(cells, parent, index, source, target)
+{
+ if (parent == null)
+ {
+ parent = this.getDefaultParent();
+ }
+
+ if (index == null)
+ {
+ index = this.model.getChildCount(parent);
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsAdded(cells, parent, index, source, target, false, true);
+ this.fireEvent(new mxEventObject(mxEvent.ADD_CELLS, 'cells', cells,
+ 'parent', parent, 'index', index, 'source', source, 'target', target));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsAdded
+ *
+ * Adds the specified cells to the given parent. This method fires
+ * <mxEvent.CELLS_ADDED> while the transaction is in progress.
+ */
+mxGraph.prototype.cellsAdded = function(cells, parent, index, source, target, absolute, constrain)
+{
+ if (cells != null && parent != null && index != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ var parentState = (absolute) ? this.view.getState(parent) : null;
+ var o1 = (parentState != null) ? parentState.origin : null;
+ var zero = new mxPoint(0, 0);
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (cells[i] == null)
+ {
+ index--;
+ }
+ else
+ {
+ var previous = this.model.getParent(cells[i]);
+
+ // Keeps the cell at its absolute location
+ if (o1 != null && cells[i] != parent && parent != previous)
+ {
+ var oldState = this.view.getState(previous);
+ var o2 = (oldState != null) ? oldState.origin : zero;
+ var geo = this.model.getGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ var dx = o2.x - o1.x;
+ var dy = o2.y - o1.y;
+
+ // FIXME: Cells should always be inserted first before any other edit
+ // to avoid forward references in sessions.
+ geo = geo.clone();
+ geo.translate(dx, dy);
+
+ if (!geo.relative && this.model.isVertex(cells[i]) &&
+ !this.isAllowNegativeCoordinates())
+ {
+ geo.x = Math.max(0, geo.x);
+ geo.y = Math.max(0, geo.y);
+ }
+
+ this.model.setGeometry(cells[i], geo);
+ }
+ }
+
+ // Decrements all following indices
+ // if cell is already in parent
+ if (parent == previous)
+ {
+ index--;
+ }
+
+ this.model.add(parent, cells[i], index + i);
+
+ // Extends the parent
+ if (this.isExtendParentsOnAdd() && this.isExtendParent(cells[i]))
+ {
+ this.extendParent(cells[i]);
+ }
+
+ // Constrains the child
+ if (constrain == null || constrain)
+ {
+ this.constrainChild(cells[i]);
+ }
+
+ // Sets the source terminal
+ if (source != null)
+ {
+ this.cellConnected(cells[i], source, true);
+ }
+
+ // Sets the target terminal
+ if (target != null)
+ {
+ this.cellConnected(cells[i], target, false);
+ }
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_ADDED, 'cells', cells,
+ 'parent', parent, 'index', index, 'source', source, 'target', target,
+ 'absolute', absolute));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: removeCells
+ *
+ * Removes the given cells from the graph including all connected edges if
+ * includeEdges is true. The change is carried out using <cellsRemoved>.
+ * This method fires <mxEvent.REMOVE_CELLS> while the transaction is in
+ * progress. The removed cells are returned as an array.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to remove. If null is specified then the
+ * selection cells which are deletable are used.
+ * includeEdges - Optional boolean which specifies if all connected edges
+ * should be removed as well. Default is true.
+ */
+mxGraph.prototype.removeCells = function(cells, includeEdges)
+{
+ includeEdges = (includeEdges != null) ? includeEdges : true;
+
+ if (cells == null)
+ {
+ cells = this.getDeletableCells(this.getSelectionCells());
+ }
+
+ // Adds all edges to the cells
+ if (includeEdges)
+ {
+ cells = this.getDeletableCells(this.addAllEdges(cells));
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsRemoved(cells);
+ this.fireEvent(new mxEventObject(mxEvent.REMOVE_CELLS,
+ 'cells', cells, 'includeEdges', includeEdges));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsRemoved
+ *
+ * Removes the given cells from the model. This method fires
+ * <mxEvent.CELLS_REMOVED> while the transaction is in progress.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to remove.
+ */
+mxGraph.prototype.cellsRemoved = function(cells)
+{
+ if (cells != null && cells.length > 0)
+ {
+ var scale = this.view.scale;
+ var tr = this.view.translate;
+
+ this.model.beginUpdate();
+ try
+ {
+ // Creates hashtable for faster lookup
+ var hash = new Object();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var id = mxCellPath.create(cells[i]);
+ hash[id] = cells[i];
+ }
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ // Disconnects edges which are not in cells
+ var edges = this.getConnections(cells[i]);
+
+ for (var j = 0; j < edges.length; j++)
+ {
+ var id = mxCellPath.create(edges[j]);
+
+ if (hash[id] == null)
+ {
+ var geo = this.model.getGeometry(edges[j]);
+
+ if (geo != null)
+ {
+ var state = this.view.getState(edges[j]);
+
+ if (state != null)
+ {
+ geo = geo.clone();
+ var source = state.getVisibleTerminal(true) == cells[i];
+ var pts = state.absolutePoints;
+ var n = (source) ? 0 : pts.length - 1;
+
+ geo.setTerminalPoint(
+ new mxPoint(pts[n].x / scale - tr.x,
+ pts[n].y / scale - tr.y), source);
+ this.model.setTerminal(edges[j], null, source);
+ this.model.setGeometry(edges[j], geo);
+ }
+ }
+ }
+ }
+
+ this.model.remove(cells[i]);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_REMOVED,
+ 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: splitEdge
+ *
+ * Splits the given edge by adding the newEdge between the previous source
+ * and the given cell and reconnecting the source of the given edge to the
+ * given cell. This method fires <mxEvent.SPLIT_EDGE> while the transaction
+ * is in progress. Returns the new edge that was inserted.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge to be splitted.
+ * cells - <mxCells> that represents the cells to insert into the edge.
+ * newEdge - <mxCell> that represents the edge to be inserted.
+ * dx - Optional integer that specifies the vector to move the cells.
+ * dy - Optional integer that specifies the vector to move the cells.
+ */
+mxGraph.prototype.splitEdge = function(edge, cells, newEdge, dx, dy)
+{
+ dx = dx || 0;
+ dy = dy || 0;
+
+ if (newEdge == null)
+ {
+ newEdge = this.cloneCells([edge])[0];
+ }
+
+ var parent = this.model.getParent(edge);
+ var source = this.model.getTerminal(edge, true);
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsMoved(cells, dx, dy, false, false);
+ this.cellsAdded(cells, parent, this.model.getChildCount(parent), null, null,
+ true);
+ this.cellsAdded([newEdge], parent, this.model.getChildCount(parent),
+ source, cells[0], false);
+ this.cellConnected(edge, cells[0], true);
+ this.fireEvent(new mxEventObject(mxEvent.SPLIT_EDGE, 'edge', edge,
+ 'cells', cells, 'newEdge', newEdge, 'dx', dx, 'dy', dy));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return newEdge;
+};
+
+/**
+ * Group: Cell visibility
+ */
+
+/**
+ * Function: toggleCells
+ *
+ * Sets the visible state of the specified cells and all connected edges
+ * if includeEdges is true. The change is carried out using <cellsToggled>.
+ * This method fires <mxEvent.TOGGLE_CELLS> while the transaction is in
+ * progress. Returns the cells whose visible state was changed.
+ *
+ * Parameters:
+ *
+ * show - Boolean that specifies the visible state to be assigned.
+ * cells - Array of <mxCells> whose visible state should be changed. If
+ * null is specified then the selection cells are used.
+ * includeEdges - Optional boolean indicating if the visible state of all
+ * connected edges should be changed as well. Default is true.
+ */
+mxGraph.prototype.toggleCells = function(show, cells, includeEdges)
+{
+ if (cells == null)
+ {
+ cells = this.getSelectionCells();
+ }
+
+ // Adds all connected edges recursively
+ if (includeEdges)
+ {
+ cells = this.addAllEdges(cells);
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsToggled(cells, show);
+ this.fireEvent(new mxEventObject(mxEvent.TOGGLE_CELLS,
+ 'show', show, 'cells', cells, 'includeEdges', includeEdges));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsToggled
+ *
+ * Sets the visible state of the specified cells.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose visible state should be changed.
+ * show - Boolean that specifies the visible state to be assigned.
+ */
+mxGraph.prototype.cellsToggled = function(cells, show)
+{
+ if (cells != null && cells.length > 0)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ this.model.setVisible(cells[i], show);
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Group: Folding
+ */
+
+/**
+ * Function: foldCells
+ *
+ * Sets the collapsed state of the specified cells and all descendants
+ * if recurse is true. The change is carried out using <cellsFolded>.
+ * This method fires <mxEvent.FOLD_CELLS> while the transaction is in
+ * progress. Returns the cells whose collapsed state was changed.
+ *
+ * Parameters:
+ *
+ * collapsed - Boolean indicating the collapsed state to be assigned.
+ * recurse - Optional boolean indicating if the collapsed state of all
+ * descendants should be set. Default is false.
+ * cells - Array of <mxCells> whose collapsed state should be set. If
+ * null is specified then the foldable selection cells are used.
+ * checkFoldable - Optional boolean indicating of isCellFoldable should be
+ * checked. Default is false.
+ */
+mxGraph.prototype.foldCells = function(collapse, recurse, cells, checkFoldable)
+{
+ recurse = (recurse != null) ? recurse : false;
+
+ if (cells == null)
+ {
+ cells = this.getFoldableCells(this.getSelectionCells(), collapse);
+ }
+
+ this.stopEditing(false);
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsFolded(cells, collapse, recurse, checkFoldable);
+ this.fireEvent(new mxEventObject(mxEvent.FOLD_CELLS,
+ 'collapse', collapse, 'recurse', recurse, 'cells', cells));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsFolded
+ *
+ * Sets the collapsed state of the specified cells. This method fires
+ * <mxEvent.CELLS_FOLDED> while the transaction is in progress. Returns the
+ * cells whose collapsed state was changed.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose collapsed state should be set.
+ * collapsed - Boolean indicating the collapsed state to be assigned.
+ * recurse - Boolean indicating if the collapsed state of all descendants
+ * should be set.
+ * checkFoldable - Optional boolean indicating of isCellFoldable should be
+ * checked. Default is false.
+ */
+mxGraph.prototype.cellsFolded = function(cells, collapse, recurse, checkFoldable)
+{
+ if (cells != null && cells.length > 0)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if ((!checkFoldable || this.isCellFoldable(cells[i], collapse)) &&
+ collapse != this.isCellCollapsed(cells[i]))
+ {
+ this.model.setCollapsed(cells[i], collapse);
+ this.swapBounds(cells[i], collapse);
+
+ if (this.isExtendParent(cells[i]))
+ {
+ this.extendParent(cells[i]);
+ }
+
+ if (recurse)
+ {
+ var children = this.model.getChildren(cells[i]);
+ this.foldCells(children, collapse, recurse);
+ }
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_FOLDED,
+ 'cells', cells, 'collapse', collapse, 'recurse', recurse));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: swapBounds
+ *
+ * Swaps the alternate and the actual bounds in the geometry of the given
+ * cell invoking <updateAlternateBounds> before carrying out the swap.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the bounds should be swapped.
+ * willCollapse - Boolean indicating if the cell is going to be collapsed.
+ */
+mxGraph.prototype.swapBounds = function(cell, willCollapse)
+{
+ if (cell != null)
+ {
+ var geo = this.model.getGeometry(cell);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+
+ this.updateAlternateBounds(cell, geo, willCollapse);
+ geo.swap();
+
+ this.model.setGeometry(cell, geo);
+ }
+ }
+};
+
+/**
+ * Function: updateAlternateBounds
+ *
+ * Updates or sets the alternate bounds in the given geometry for the given
+ * cell depending on whether the cell is going to be collapsed. If no
+ * alternate bounds are defined in the geometry and
+ * <collapseToPreferredSize> is true, then the preferred size is used for
+ * the alternate bounds. The top, left corner is always kept at the same
+ * location.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the geometry is being udpated.
+ * g - <mxGeometry> for which the alternate bounds should be updated.
+ * willCollapse - Boolean indicating if the cell is going to be collapsed.
+ */
+mxGraph.prototype.updateAlternateBounds = function(cell, geo, willCollapse)
+{
+ if (cell != null && geo != null)
+ {
+ if (geo.alternateBounds == null)
+ {
+ var bounds = geo;
+
+ if (this.collapseToPreferredSize)
+ {
+ var tmp = this.getPreferredSizeForCell(cell);
+
+ if (tmp != null)
+ {
+ bounds = tmp;
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ var startSize = mxUtils.getValue(style, mxConstants.STYLE_STARTSIZE);
+
+ if (startSize > 0)
+ {
+ bounds.height = Math.max(bounds.height, startSize);
+ }
+ }
+ }
+
+ geo.alternateBounds = new mxRectangle(
+ geo.x, geo.y, bounds.width, bounds.height);
+ }
+ else
+ {
+ geo.alternateBounds.x = geo.x;
+ geo.alternateBounds.y = geo.y;
+ }
+ }
+};
+
+/**
+ * Function: addAllEdges
+ *
+ * Returns an array with the given cells and all edges that are connected
+ * to a cell or one of its descendants.
+ */
+mxGraph.prototype.addAllEdges = function(cells)
+{
+ var allCells = cells.slice(); // FIXME: Required?
+ allCells = allCells.concat(this.getAllEdges(cells));
+
+ return allCells;
+};
+
+/**
+ * Function: getAllEdges
+ *
+ * Returns all edges connected to the given cells or its descendants.
+ */
+mxGraph.prototype.getAllEdges = function(cells)
+{
+ var edges = [];
+
+ if (cells != null)
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var edgeCount = this.model.getEdgeCount(cells[i]);
+
+ for (var j = 0; j < edgeCount; j++)
+ {
+ edges.push(this.model.getEdgeAt(cells[i], j));
+ }
+
+ // Recurses
+ var children = this.model.getChildren(cells[i]);
+ edges = edges.concat(this.getAllEdges(children));
+ }
+ }
+
+ return edges;
+};
+
+/**
+ * Group: Cell sizing
+ */
+
+/**
+ * Function: updateCellSize
+ *
+ * Updates the size of the given cell in the model using <cellSizeUpdated>.
+ * This method fires <mxEvent.UPDATE_CELL_SIZE> while the transaction is in
+ * progress. Returns the cell whose size was updated.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose size should be updated.
+ */
+mxGraph.prototype.updateCellSize = function(cell, ignoreChildren)
+{
+ ignoreChildren = (ignoreChildren != null) ? ignoreChildren : false;
+
+ this.model.beginUpdate();
+ try
+ {
+ this.cellSizeUpdated(cell, ignoreChildren);
+ this.fireEvent(new mxEventObject(mxEvent.UPDATE_CELL_SIZE,
+ 'cell', cell, 'ignoreChildren', ignoreChildren));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cell;
+};
+
+/**
+ * Function: cellSizeUpdated
+ *
+ * Updates the size of the given cell in the model using
+ * <getPreferredSizeForCell> to get the new size.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the size should be changed.
+ */
+mxGraph.prototype.cellSizeUpdated = function(cell, ignoreChildren)
+{
+ if (cell != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ var size = this.getPreferredSizeForCell(cell);
+ var geo = this.model.getGeometry(cell);
+
+ if (size != null && geo != null)
+ {
+ var collapsed = this.isCellCollapsed(cell);
+ geo = geo.clone();
+
+ if (this.isSwimlane(cell))
+ {
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+ var cellStyle = this.model.getStyle(cell);
+
+ if (cellStyle == null)
+ {
+ cellStyle = '';
+ }
+
+ if (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ cellStyle = mxUtils.setStyle(cellStyle,
+ mxConstants.STYLE_STARTSIZE, size.height + 8);
+
+ if (collapsed)
+ {
+ geo.height = size.height + 8;
+ }
+
+ geo.width = size.width;
+ }
+ else
+ {
+ cellStyle = mxUtils.setStyle(cellStyle,
+ mxConstants.STYLE_STARTSIZE, size.width + 8);
+
+ if (collapsed)
+ {
+ geo.width = size.width + 8;
+ }
+
+ geo.height = size.height;
+ }
+
+ this.model.setStyle(cell, cellStyle);
+ }
+ else
+ {
+ geo.width = size.width;
+ geo.height = size.height;
+ }
+
+ if (!ignoreChildren && !collapsed)
+ {
+ var bounds = this.view.getBounds(this.model.getChildren(cell));
+
+ if (bounds != null)
+ {
+ var tr = this.view.translate;
+ var scale = this.view.scale;
+
+ var width = (bounds.x + bounds.width) / scale - geo.x - tr.x;
+ var height = (bounds.y + bounds.height) / scale - geo.y - tr.y;
+
+ geo.width = Math.max(geo.width, width);
+ geo.height = Math.max(geo.height, height);
+ }
+ }
+
+ this.cellsResized([cell], [geo]);
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: getPreferredSizeForCell
+ *
+ * Returns the preferred width and height of the given <mxCell> as an
+ * <mxRectangle>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the preferred size should be returned.
+ */
+mxGraph.prototype.getPreferredSizeForCell = function(cell)
+{
+ var result = null;
+
+ if (cell != null)
+ {
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ if (style != null && !this.model.isEdge(cell))
+ {
+ var fontSize = style[mxConstants.STYLE_FONTSIZE] || mxConstants.DEFAULT_FONTSIZE;
+ var dx = 0;
+ var dy = 0;
+
+ // Adds dimension of image if shape is a label
+ if (this.getImage(state) != null || style[mxConstants.STYLE_IMAGE] != null)
+ {
+ if (style[mxConstants.STYLE_SHAPE] == mxConstants.SHAPE_LABEL)
+ {
+ if (style[mxConstants.STYLE_VERTICAL_ALIGN] == mxConstants.ALIGN_MIDDLE)
+ {
+ dx += parseFloat(style[mxConstants.STYLE_IMAGE_WIDTH]) || mxLabel.prototype.imageSize;
+ }
+
+ if (style[mxConstants.STYLE_ALIGN] != mxConstants.ALIGN_CENTER)
+ {
+ dy += parseFloat(style[mxConstants.STYLE_IMAGE_HEIGHT]) || mxLabel.prototype.imageSize;
+ }
+ }
+ }
+
+ // Adds spacings
+ dx += 2 * (style[mxConstants.STYLE_SPACING] || 0);
+ dx += style[mxConstants.STYLE_SPACING_LEFT] || 0;
+ dx += style[mxConstants.STYLE_SPACING_RIGHT] || 0;
+
+ dy += 2 * (style[mxConstants.STYLE_SPACING] || 0);
+ dy += style[mxConstants.STYLE_SPACING_TOP] || 0;
+ dy += style[mxConstants.STYLE_SPACING_BOTTOM] || 0;
+
+ // Add spacing for collapse/expand icon
+ // LATER: Check alignment and use constants
+ // for image spacing
+ var image = this.getFoldingImage(state);
+
+ if (image != null)
+ {
+ dx += image.width + 8;
+ }
+
+ // Adds space for label
+ var value = this.getLabel(cell);
+
+ if (value != null && value.length > 0)
+ {
+ if (!this.isHtmlLabel(cell))
+ {
+ value = value.replace(/\n/g, '<br>');
+ }
+
+ var size = mxUtils.getSizeForString(value,
+ fontSize, style[mxConstants.STYLE_FONTFAMILY]);
+ var width = size.width + dx;
+ var height = size.height + dy;
+
+ if (!mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ var tmp = height;
+
+ height = width;
+ width = tmp;
+ }
+
+ if (this.gridEnabled)
+ {
+ width = this.snap(width + this.gridSize / 2);
+ height = this.snap(height + this.gridSize / 2);
+ }
+
+ result = new mxRectangle(0, 0, width, height);
+ }
+ else
+ {
+ var gs2 = 4 * this.gridSize;
+ result = new mxRectangle(0, 0, gs2, gs2);
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: handleGesture
+ *
+ * Invokes if a gesture event has been detected on a cell state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> which was pinched.
+ * evt - Object that represents the gesture event.
+ */
+mxGraph.prototype.handleGesture = function(state, evt)
+{
+ if (Math.abs(1 - evt.scale) > 0.2)
+ {
+ var scale = this.view.scale;
+ var tr = this.view.translate;
+
+ var w = state.width * evt.scale;
+ var h = state.height * evt.scale;
+ var x = state.x - (w - state.width) / 2;
+ var y = state.y - (h - state.height) / 2;
+
+ var bounds = new mxRectangle(this.snap(x / scale) - tr.x,
+ this.snap(y / scale) - tr.y,
+ this.snap(w / scale), this.snap(h / scale));
+ this.resizeCell(state.cell, bounds);
+ }
+};
+
+/**
+ * Function: resizeCell
+ *
+ * Sets the bounds of the given cell using <resizeCells>. Returns the
+ * cell which was passed to the function.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose bounds should be changed.
+ * bounds - <mxRectangle> that represents the new bounds.
+ */
+mxGraph.prototype.resizeCell = function(cell, bounds)
+{
+ return this.resizeCells([cell], [bounds])[0];
+};
+
+/**
+ * Function: resizeCells
+ *
+ * Sets the bounds of the given cells and fires a <mxEvent.RESIZE_CELLS>
+ * event while the transaction is in progress. Returns the cells which
+ * have been passed to the function.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounds should be changed.
+ * bounds - Array of <mxRectangles> that represent the new bounds.
+ */
+mxGraph.prototype.resizeCells = function(cells, bounds)
+{
+ this.model.beginUpdate();
+ try
+ {
+ this.cellsResized(cells, bounds);
+ this.fireEvent(new mxEventObject(mxEvent.RESIZE_CELLS,
+ 'cells', cells, 'bounds', bounds));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsResized
+ *
+ * Sets the bounds of the given cells and fires a <mxEvent.CELLS_RESIZED>
+ * event. If <extendParents> is true, then the parent is extended if a
+ * child size is changed so that it overlaps with the parent.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounds should be changed.
+ * bounds - Array of <mxRectangles> that represent the new bounds.
+ */
+mxGraph.prototype.cellsResized = function(cells, bounds)
+{
+ if (cells != null && bounds != null && cells.length == bounds.length)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var tmp = bounds[i];
+ var geo = this.model.getGeometry(cells[i]);
+
+ if (geo != null && (geo.x != tmp.x || geo.y != tmp.y ||
+ geo.width != tmp.width || geo.height != tmp.height))
+ {
+ geo = geo.clone();
+
+ if (geo.relative)
+ {
+ var offset = geo.offset;
+
+ if (offset != null)
+ {
+ offset.x += tmp.x - geo.x;
+ offset.y += tmp.y - geo.y;
+ }
+ }
+ else
+ {
+ geo.x = tmp.x;
+ geo.y = tmp.y;
+ }
+
+ geo.width = tmp.width;
+ geo.height = tmp.height;
+
+ if (!geo.relative && this.model.isVertex(cells[i]) &&
+ !this.isAllowNegativeCoordinates())
+ {
+ geo.x = Math.max(0, geo.x);
+ geo.y = Math.max(0, geo.y);
+ }
+
+ this.model.setGeometry(cells[i], geo);
+
+ if (this.isExtendParent(cells[i]))
+ {
+ this.extendParent(cells[i]);
+ }
+ }
+ }
+
+ if (this.resetEdgesOnResize)
+ {
+ this.resetEdges(cells);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_RESIZED,
+ 'cells', cells, 'bounds', bounds));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: extendParent
+ *
+ * Resizes the parents recursively so that they contain the complete area
+ * of the resized child cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that has been resized.
+ */
+mxGraph.prototype.extendParent = function(cell)
+{
+ if (cell != null)
+ {
+ var parent = this.model.getParent(cell);
+ var p = this.model.getGeometry(parent);
+
+ if (parent != null && p != null && !this.isCellCollapsed(parent))
+ {
+ var geo = this.model.getGeometry(cell);
+
+ if (geo != null && (p.width < geo.x + geo.width ||
+ p.height < geo.y + geo.height))
+ {
+ p = p.clone();
+
+ p.width = Math.max(p.width, geo.x + geo.width);
+ p.height = Math.max(p.height, geo.y + geo.height);
+
+ this.cellsResized([parent], [p]);
+ }
+ }
+ }
+};
+
+/**
+ * Group: Cell moving
+ */
+
+/**
+ * Function: importCells
+ *
+ * Clones and inserts the given cells into the graph using the move
+ * method and returns the inserted cells. This shortcut is used if
+ * cells are inserted via datatransfer.
+ */
+mxGraph.prototype.importCells = function(cells, dx, dy, target, evt)
+{
+ return this.moveCells(cells, dx, dy, true, target, evt);
+};
+
+/**
+ * Function: moveCells
+ *
+ * Moves or clones the specified cells and moves the cells or clones by the
+ * given amount, adding them to the optional target cell. The evt is the
+ * mouse event as the mouse was released. The change is carried out using
+ * <cellsMoved>. This method fires <mxEvent.MOVE_CELLS> while the
+ * transaction is in progress. Returns the cells that were moved.
+ *
+ * Use the following code to move all cells in the graph.
+ *
+ * (code)
+ * graph.moveCells(graph.getChildCells(null, true, true), 10, 10);
+ * (end)
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be moved, cloned or added to the target.
+ * dx - Integer that specifies the x-coordinate of the vector. Default is 0.
+ * dy - Integer that specifies the y-coordinate of the vector. Default is 0.
+ * clone - Boolean indicating if the cells should be cloned. Default is false.
+ * target - <mxCell> that represents the new parent of the cells.
+ * evt - Mouseevent that triggered the invocation.
+ */
+mxGraph.prototype.moveCells = function(cells, dx, dy, clone, target, evt)
+{
+ dx = (dx != null) ? dx : 0;
+ dy = (dy != null) ? dy : 0;
+ clone = (clone != null) ? clone : false;
+
+ if (cells != null && (dx != 0 || dy != 0 || clone || target != null))
+ {
+ this.model.beginUpdate();
+ try
+ {
+ if (clone)
+ {
+ cells = this.cloneCells(cells, this.isCloneInvalidEdges());
+
+ if (target == null)
+ {
+ target = this.getDefaultParent();
+ }
+ }
+
+ // FIXME: Cells should always be inserted first before any other edit
+ // to avoid forward references in sessions.
+ // Need to disable allowNegativeCoordinates if target not null to
+ // allow for temporary negative numbers until cellsAdded is called.
+ var previous = this.isAllowNegativeCoordinates();
+
+ if (target != null)
+ {
+ this.setAllowNegativeCoordinates(true);
+ }
+
+ this.cellsMoved(cells, dx, dy, !clone && this.isDisconnectOnMove()
+ && this.isAllowDanglingEdges(), target == null);
+
+ this.setAllowNegativeCoordinates(previous);
+
+ if (target != null)
+ {
+ var index = this.model.getChildCount(target);
+ this.cellsAdded(cells, target, index, null, null, true);
+ }
+
+ // Dispatches a move event
+ this.fireEvent(new mxEventObject(mxEvent.MOVE_CELLS, 'cells', cells,
+ 'dx', dx, 'dy', dy, 'clone', clone, 'target', target, 'event', evt));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+
+ return cells;
+};
+
+/**
+ * Function: cellsMoved
+ *
+ * Moves the specified cells by the given vector, disconnecting the cells
+ * using disconnectGraph is disconnect is true. This method fires
+ * <mxEvent.CELLS_MOVED> while the transaction is in progress.
+ */
+mxGraph.prototype.cellsMoved = function(cells, dx, dy, disconnect, constrain)
+{
+ if (cells != null && (dx != 0 || dy != 0))
+ {
+ this.model.beginUpdate();
+ try
+ {
+ if (disconnect)
+ {
+ this.disconnectGraph(cells);
+ }
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ this.translateCell(cells[i], dx, dy);
+
+ if (constrain)
+ {
+ this.constrainChild(cells[i]);
+ }
+ }
+
+ if (this.resetEdgesOnMove)
+ {
+ this.resetEdges(cells);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELLS_MOVED,
+ 'cells', cells, 'dx', dy, 'dy', dy, 'disconnect', disconnect));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: translateCell
+ *
+ * Translates the geometry of the given cell and stores the new,
+ * translated geometry in the model as an atomic change.
+ */
+mxGraph.prototype.translateCell = function(cell, dx, dy)
+{
+ var geo = this.model.getGeometry(cell);
+
+ if (geo != null)
+ {
+ geo = geo.clone();
+ geo.translate(dx, dy);
+
+ if (!geo.relative && this.model.isVertex(cell) && !this.isAllowNegativeCoordinates())
+ {
+ geo.x = Math.max(0, geo.x);
+ geo.y = Math.max(0, geo.y);
+ }
+
+ if (geo.relative && !this.model.isEdge(cell))
+ {
+ if (geo.offset == null)
+ {
+ geo.offset = new mxPoint(dx, dy);
+ }
+ else
+ {
+ geo.offset.x += dx;
+ geo.offset.y += dy;
+ }
+ }
+
+ this.model.setGeometry(cell, geo);
+ }
+};
+
+/**
+ * Function: getCellContainmentArea
+ *
+ * Returns the <mxRectangle> inside which a cell is to be kept.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the area should be returned.
+ */
+mxGraph.prototype.getCellContainmentArea = function(cell)
+{
+ if (cell != null && !this.model.isEdge(cell))
+ {
+ var parent = this.model.getParent(cell);
+
+ if (parent == this.getDefaultParent() || parent == this.getCurrentRoot())
+ {
+ return this.getMaximumGraphBounds();
+ }
+ else if (parent != null && parent != this.getDefaultParent())
+ {
+ var g = this.model.getGeometry(parent);
+
+ if (g != null)
+ {
+ var x = 0;
+ var y = 0;
+ var w = g.width;
+ var h = g.height;
+
+ if (this.isSwimlane(parent))
+ {
+ var size = this.getStartSize(parent);
+
+ x = size.width;
+ w -= size.width;
+ y = size.height;
+ h -= size.height;
+ }
+
+ return new mxRectangle(x, y, w, h);
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: getMaximumGraphBounds
+ *
+ * Returns the bounds inside which the diagram should be kept as an
+ * <mxRectangle>.
+ */
+mxGraph.prototype.getMaximumGraphBounds = function()
+{
+ return this.maximumGraphBounds;
+};
+
+/**
+ * Function: constrainChild
+ *
+ * Keeps the given cell inside the bounds returned by
+ * <getCellContainmentArea> for its parent, according to the rules defined by
+ * <getOverlap> and <isConstrainChild>. This modifies the cell's geometry
+ * in-place and does not clone it.
+ *
+ * Parameters:
+ *
+ * cells - <mxCell> which should be constrained.
+ */
+mxGraph.prototype.constrainChild = function(cell)
+{
+ if (cell != null)
+ {
+ var geo = this.model.getGeometry(cell);
+ var area = (this.isConstrainChild(cell)) ?
+ this.getCellContainmentArea(cell) :
+ this.getMaximumGraphBounds();
+
+ if (geo != null && area != null)
+ {
+ // Keeps child within the content area of the parent
+ if (!geo.relative && (geo.x < area.x || geo.y < area.y ||
+ area.width < geo.x + geo.width || area.height < geo.y + geo.height))
+ {
+ var overlap = this.getOverlap(cell);
+
+ if (area.width > 0)
+ {
+ geo.x = Math.min(geo.x, area.x + area.width -
+ (1 - overlap) * geo.width);
+ }
+
+ if (area.height > 0)
+ {
+ geo.y = Math.min(geo.y, area.y + area.height -
+ (1 - overlap) * geo.height);
+ }
+
+ geo.x = Math.max(geo.x, area.x - geo.width * overlap);
+ geo.y = Math.max(geo.y, area.y - geo.height * overlap);
+ }
+ }
+ }
+};
+
+/**
+ * Function: resetEdges
+ *
+ * Resets the control points of the edges that are connected to the given
+ * cells if not both ends of the edge are in the given cells array.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> for which the connected edges should be
+ * reset.
+ */
+mxGraph.prototype.resetEdges = function(cells)
+{
+ if (cells != null)
+ {
+ // Prepares a hashtable for faster cell lookups
+ var hash = new Object();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var id = mxCellPath.create(cells[i]);
+ hash[id] = cells[i];
+ }
+
+ this.model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ var edges = this.model.getEdges(cells[i]);
+
+ if (edges != null)
+ {
+ for (var j = 0; j < edges.length; j++)
+ {
+ var state = this.view.getState(edges[j]);
+
+ var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[j], true);
+ var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[j], false);
+
+ var sourceId = mxCellPath.create(source);
+ var targetId = mxCellPath.create(target);
+
+ // Checks if one of the terminals is not in the given array
+ if (hash[sourceId] == null || hash[targetId] == null)
+ {
+ this.resetEdge(edges[j]);
+ }
+ }
+ }
+
+ this.resetEdges(this.model.getChildren(cells[i]));
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: resetEdge
+ *
+ * Resets the control points of the given edge.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> whose points should be reset.
+ */
+mxGraph.prototype.resetEdge = function(edge)
+{
+ var geo = this.model.getGeometry(edge);
+
+ // Resets the control points
+ if (geo != null && geo.points != null && geo.points.length > 0)
+ {
+ geo = geo.clone();
+ geo.points = [];
+ this.model.setGeometry(edge, geo);
+ }
+
+ return edge;
+};
+
+/**
+ * Group: Cell connecting and connection constraints
+ */
+
+/**
+ * Function: getAllConnectionConstraints
+ *
+ * Returns an array of all <mxConnectionConstraints> for the given terminal. If
+ * the shape of the given terminal is a <mxStencilShape> then the constraints
+ * of the corresponding <mxStencil> are returned.
+ *
+ * Parameters:
+ *
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean that specifies if the terminal is the source or target.
+ */
+mxGraph.prototype.getAllConnectionConstraints = function(terminal, source)
+{
+ if (terminal != null && terminal.shape != null &&
+ terminal.shape instanceof mxStencilShape)
+ {
+ if (terminal.shape.stencil != null)
+ {
+ return terminal.shape.stencil.constraints;
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: getConnectionConstraint
+ *
+ * Returns an <mxConnectionConstraint> that describes the given connection
+ * point. This result can then be passed to <getConnectionPoint>.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> that represents the edge.
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean indicating if the terminal is the source or target.
+ */
+mxGraph.prototype.getConnectionConstraint = function(edge, terminal, source)
+{
+ var point = null;
+ var x = edge.style[(source) ?
+ mxConstants.STYLE_EXIT_X :
+ mxConstants.STYLE_ENTRY_X];
+
+ if (x != null)
+ {
+ var y = edge.style[(source) ?
+ mxConstants.STYLE_EXIT_Y :
+ mxConstants.STYLE_ENTRY_Y];
+
+ if (y != null)
+ {
+ point = new mxPoint(parseFloat(x), parseFloat(y));
+ }
+ }
+
+ var perimeter = false;
+
+ if (point != null)
+ {
+ perimeter = mxUtils.getValue(edge.style, (source) ? mxConstants.STYLE_EXIT_PERIMETER :
+ mxConstants.STYLE_ENTRY_PERIMETER, true);
+ }
+
+ return new mxConnectionConstraint(point, perimeter);
+};
+
+/**
+ * Function: setConnectionConstraint
+ *
+ * Sets the <mxConnectionConstraint> that describes the given connection point.
+ * If no constraint is given then nothing is changed. To remove an existing
+ * constraint from the given edge, use an empty constraint instead.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge.
+ * terminal - <mxCell> that represents the terminal.
+ * source - Boolean indicating if the terminal is the source or target.
+ * constraint - Optional <mxConnectionConstraint> to be used for this
+ * connection.
+ */
+mxGraph.prototype.setConnectionConstraint = function(edge, terminal, source, constraint)
+{
+ if (constraint != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ if (constraint == null || constraint.point == null)
+ {
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_X :
+ mxConstants.STYLE_ENTRY_X, null, [edge]);
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_Y :
+ mxConstants.STYLE_ENTRY_Y, null, [edge]);
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+ mxConstants.STYLE_ENTRY_PERIMETER, null, [edge]);
+ }
+ else if (constraint.point != null)
+ {
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_X :
+ mxConstants.STYLE_ENTRY_X, constraint.point.x, [edge]);
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_Y :
+ mxConstants.STYLE_ENTRY_Y, constraint.point.y, [edge]);
+
+ // Only writes 0 since 1 is default
+ if (!constraint.perimeter)
+ {
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+ mxConstants.STYLE_ENTRY_PERIMETER, '0', [edge]);
+ }
+ else
+ {
+ this.setCellStyles((source) ? mxConstants.STYLE_EXIT_PERIMETER :
+ mxConstants.STYLE_ENTRY_PERIMETER, null, [edge]);
+ }
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: getConnectionPoint
+ *
+ * Returns the nearest point in the list of absolute points or the center
+ * of the opposite terminal.
+ *
+ * Parameters:
+ *
+ * vertex - <mxCellState> that represents the vertex.
+ * constraint - <mxConnectionConstraint> that represents the connection point
+ * constraint as returned by <getConnectionConstraint>.
+ */
+mxGraph.prototype.getConnectionPoint = function(vertex, constraint)
+{
+ var point = null;
+
+ if (vertex != null)
+ {
+ var bounds = this.view.getPerimeterBounds(vertex);
+ var cx = new mxPoint(bounds.getCenterX(), bounds.getCenterY());
+
+ var direction = vertex.style[mxConstants.STYLE_DIRECTION];
+ var r1 = 0;
+
+ // Bounds need to be rotated by 90 degrees for further computation
+ if (direction != null)
+ {
+ if (direction == 'north')
+ {
+ r1 += 270;
+ }
+ else if (direction == 'west')
+ {
+ r1 += 180;
+ }
+ else if (direction == 'south')
+ {
+ r1 += 90;
+ }
+
+ // Bounds need to be rotated by 90 degrees for further computation
+ if (direction == 'north' || direction == 'south')
+ {
+ bounds.x += bounds.width / 2 - bounds.height / 2;
+ bounds.y += bounds.height / 2 - bounds.width / 2;
+ var tmp = bounds.width;
+ bounds.width = bounds.height;
+ bounds.height = tmp;
+ }
+ }
+
+ if (constraint.point != null)
+ {
+ var sx = 1;
+ var sy = 1;
+ var dx = 0;
+ var dy = 0;
+
+ // LATER: Add flipping support for image shapes
+ if (vertex.shape instanceof mxStencilShape)
+ {
+ var flipH = vertex.style[mxConstants.STYLE_STENCIL_FLIPH];
+ var flipV = vertex.style[mxConstants.STYLE_STENCIL_FLIPV];
+
+ if (direction == 'north' || direction == 'south')
+ {
+ var tmp = flipH;
+ flipH = flipV;
+ flipV = tmp;
+ }
+
+ if (flipH)
+ {
+ sx = -1;
+ dx = -bounds.width;
+ }
+
+ if (flipV)
+ {
+ sy = -1;
+ dy = -bounds.height ;
+ }
+ }
+
+ point = new mxPoint(bounds.x + constraint.point.x * bounds.width * sx - dx,
+ bounds.y + constraint.point.y * bounds.height * sy - dy);
+ }
+
+ // Rotation for direction before projection on perimeter
+ var r2 = vertex.style[mxConstants.STYLE_ROTATION] || 0;
+
+ if (constraint.perimeter)
+ {
+ if (r1 != 0 && point != null)
+ {
+ // Only 90 degrees steps possible here so no trig needed
+ var cos = 0;
+ var sin = 0;
+
+ if (r1 == 90)
+ {
+ sin = 1;
+ }
+ else if (r1 == 180)
+ {
+ cos = -1;
+ }
+ else if (r2 == 270)
+ {
+ sin = -1;
+ }
+
+ point = mxUtils.getRotatedPoint(point, cos, sin, cx);
+ }
+
+ if (point != null && constraint.perimeter)
+ {
+ point = this.view.getPerimeterPoint(vertex, point, false);
+ }
+ }
+ else
+ {
+ r2 += r1;
+ }
+
+ // Generic rotation after projection on perimeter
+ if (r2 != 0 && point != null)
+ {
+ var rad = mxUtils.toRadians(r2);
+ var cos = Math.cos(rad);
+ var sin = Math.sin(rad);
+
+ point = mxUtils.getRotatedPoint(point, cos, sin, cx);
+ }
+ }
+
+ return point;
+};
+
+/**
+ * Function: connectCell
+ *
+ * Connects the specified end of the given edge to the given terminal
+ * using <cellConnected> and fires <mxEvent.CONNECT_CELL> while the
+ * transaction is in progress. Returns the updated edge.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> whose terminal should be updated.
+ * terminal - <mxCell> that represents the new terminal to be used.
+ * source - Boolean indicating if the new terminal is the source or target.
+ * constraint - Optional <mxConnectionConstraint> to be used for this
+ * connection.
+ */
+mxGraph.prototype.connectCell = function(edge, terminal, source, constraint)
+{
+ this.model.beginUpdate();
+ try
+ {
+ var previous = this.model.getTerminal(edge, source);
+ this.cellConnected(edge, terminal, source, constraint);
+ this.fireEvent(new mxEventObject(mxEvent.CONNECT_CELL,
+ 'edge', edge, 'terminal', terminal, 'source', source,
+ 'previous', previous));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+
+ return edge;
+};
+
+/**
+ * Function: cellConnected
+ *
+ * Sets the new terminal for the given edge and resets the edge points if
+ * <resetEdgesOnConnect> is true. This method fires
+ * <mxEvent.CELL_CONNECTED> while the transaction is in progress.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> whose terminal should be updated.
+ * terminal - <mxCell> that represents the new terminal to be used.
+ * source - Boolean indicating if the new terminal is the source or target.
+ * constraint - <mxConnectionConstraint> to be used for this connection.
+ */
+mxGraph.prototype.cellConnected = function(edge, terminal, source, constraint)
+{
+ if (edge != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ var previous = this.model.getTerminal(edge, source);
+
+ // Updates the constraint
+ this.setConnectionConstraint(edge, terminal, source, constraint);
+
+ // Checks if the new terminal is a port, uses the ID of the port in the
+ // style and the parent of the port as the actual terminal of the edge.
+ if (this.isPortsEnabled())
+ {
+ var id = null;
+
+ if (this.isPort(terminal))
+ {
+ id = terminal.getId();
+ terminal = this.getTerminalForPort(terminal, source);
+ }
+
+ // Sets or resets all previous information for connecting to a child port
+ var key = (source) ? mxConstants.STYLE_SOURCE_PORT :
+ mxConstants.STYLE_TARGET_PORT;
+ this.setCellStyles(key, id, [edge]);
+ }
+
+ this.model.setTerminal(edge, terminal, source);
+
+ if (this.resetEdgesOnConnect)
+ {
+ this.resetEdge(edge);
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.CELL_CONNECTED,
+ 'edge', edge, 'terminal', terminal, 'source', source,
+ 'previous', previous));
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: disconnectGraph
+ *
+ * Disconnects the given edges from the terminals which are not in the
+ * given array.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be disconnected.
+ */
+mxGraph.prototype.disconnectGraph = function(cells)
+{
+ if (cells != null)
+ {
+ this.model.beginUpdate();
+ try
+ {
+ var scale = this.view.scale;
+ var tr = this.view.translate;
+
+ // Prepares a hashtable for faster cell lookups
+ var hash = new Object();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var id = mxCellPath.create(cells[i]);
+ hash[id] = cells[i];
+ }
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.model.isEdge(cells[i]))
+ {
+ var geo = this.model.getGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ var state = this.view.getState(cells[i]);
+ var pstate = this.view.getState(
+ this.model.getParent(cells[i]));
+
+ if (state != null &&
+ pstate != null)
+ {
+ geo = geo.clone();
+
+ var dx = -pstate.origin.x;
+ var dy = -pstate.origin.y;
+ var pts = state.absolutePoints;
+
+ var src = this.model.getTerminal(cells[i], true);
+
+ if (src != null && this.isCellDisconnectable(cells[i], src, true))
+ {
+ var srcId = mxCellPath.create(src);
+
+ while (src != null && hash[srcId] == null)
+ {
+ src = this.model.getParent(src);
+ srcId = mxCellPath.create(src);
+ }
+
+ if (src == null)
+ {
+ geo.setTerminalPoint(
+ new mxPoint(pts[0].x / scale - tr.x + dx,
+ pts[0].y / scale - tr.y + dy), true);
+ this.model.setTerminal(cells[i], null, true);
+ }
+ }
+
+ var trg = this.model.getTerminal(cells[i], false);
+
+ if (trg != null && this.isCellDisconnectable(cells[i], trg, false))
+ {
+ var trgId = mxCellPath.create(trg);
+
+ while (trg != null && hash[trgId] == null)
+ {
+ trg = this.model.getParent(trg);
+ trgId = mxCellPath.create(trg);
+ }
+
+ if (trg == null)
+ {
+ var n = pts.length - 1;
+ geo.setTerminalPoint(
+ new mxPoint(pts[n].x / scale - tr.x + dx,
+ pts[n].y / scale - tr.y + dy), false);
+ this.model.setTerminal(cells[i], null, false);
+ }
+ }
+
+ this.model.setGeometry(cells[i], geo);
+ }
+ }
+ }
+ }
+ }
+ finally
+ {
+ this.model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Group: Drilldown
+ */
+
+/**
+ * Function: getCurrentRoot
+ *
+ * Returns the current root of the displayed cell hierarchy. This is a
+ * shortcut to <mxGraphView.currentRoot> in <view>.
+ */
+ mxGraph.prototype.getCurrentRoot = function()
+ {
+ return this.view.currentRoot;
+ };
+
+ /**
+ * Function: getTranslateForRoot
+ *
+ * Returns the translation to be used if the given cell is the root cell as
+ * an <mxPoint>. This implementation returns null.
+ *
+ * Example:
+ *
+ * To keep the children at their absolute position while stepping into groups,
+ * this function can be overridden as follows.
+ *
+ * (code)
+ * var offset = new mxPoint(0, 0);
+ *
+ * while (cell != null)
+ * {
+ * var geo = this.model.getGeometry(cell);
+ *
+ * if (geo != null)
+ * {
+ * offset.x -= geo.x;
+ * offset.y -= geo.y;
+ * }
+ *
+ * cell = this.model.getParent(cell);
+ * }
+ *
+ * return offset;
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the root.
+ */
+mxGraph.prototype.getTranslateForRoot = function(cell)
+{
+ return null;
+};
+
+/**
+ * Function: isPort
+ *
+ * Returns true if the given cell is a "port", that is, when connecting to
+ * it, the cell returned by getTerminalForPort should be used as the
+ * terminal and the port should be referenced by the ID in either the
+ * mxConstants.STYLE_SOURCE_PORT or the or the
+ * mxConstants.STYLE_TARGET_PORT. Note that a port should not be movable.
+ * This implementation always returns false.
+ *
+ * A typical implementation is the following:
+ *
+ * (code)
+ * graph.isPort = function(cell)
+ * {
+ * var geo = this.getCellGeometry(cell);
+ *
+ * return (geo != null) ? geo.relative : false;
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the port.
+ */
+mxGraph.prototype.isPort = function(cell)
+{
+ return false;
+};
+
+/**
+ * Function: getTerminalForPort
+ *
+ * Returns the terminal to be used for a given port. This implementation
+ * always returns the parent cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the port.
+ * source - If the cell is the source or target port.
+ */
+mxGraph.prototype.getTerminalForPort = function(cell, source)
+{
+ return this.model.getParent(cell);
+};
+
+/**
+ * Function: getChildOffsetForCell
+ *
+ * Returns the offset to be used for the cells inside the given cell. The
+ * root and layer cells may be identified using <mxGraphModel.isRoot> and
+ * <mxGraphModel.isLayer>. For all other current roots, the
+ * <mxGraphView.currentRoot> field points to the respective cell, so that
+ * the following holds: cell == this.view.currentRoot. This implementation
+ * returns null.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose offset should be returned.
+ */
+mxGraph.prototype.getChildOffsetForCell = function(cell)
+{
+ return null;
+};
+
+/**
+ * Function: enterGroup
+ *
+ * Uses the given cell as the root of the displayed cell hierarchy. If no
+ * cell is specified then the selection cell is used. The cell is only used
+ * if <isValidRoot> returns true.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> to be used as the new root. Default is the
+ * selection cell.
+ */
+mxGraph.prototype.enterGroup = function(cell)
+{
+ cell = cell || this.getSelectionCell();
+
+ if (cell != null && this.isValidRoot(cell))
+ {
+ this.view.setCurrentRoot(cell);
+ this.clearSelection();
+ }
+};
+
+/**
+ * Function: exitGroup
+ *
+ * Changes the current root to the next valid root in the displayed cell
+ * hierarchy.
+ */
+mxGraph.prototype.exitGroup = function()
+{
+ var root = this.model.getRoot();
+ var current = this.getCurrentRoot();
+
+ if (current != null)
+ {
+ var next = this.model.getParent(current);
+
+ // Finds the next valid root in the hierarchy
+ while (next != root && !this.isValidRoot(next) &&
+ this.model.getParent(next) != root)
+ {
+ next = this.model.getParent(next);
+ }
+
+ // Clears the current root if the new root is
+ // the model's root or one of the layers.
+ if (next == root || this.model.getParent(next) == root)
+ {
+ this.view.setCurrentRoot(null);
+ }
+ else
+ {
+ this.view.setCurrentRoot(next);
+ }
+
+ var state = this.view.getState(current);
+
+ // Selects the previous root in the graph
+ if (state != null)
+ {
+ this.setSelectionCell(current);
+ }
+ }
+};
+
+/**
+ * Function: home
+ *
+ * Uses the root of the model as the root of the displayed cell hierarchy
+ * and selects the previous root.
+ */
+mxGraph.prototype.home = function()
+{
+ var current = this.getCurrentRoot();
+
+ if (current != null)
+ {
+ this.view.setCurrentRoot(null);
+ var state = this.view.getState(current);
+
+ if (state != null)
+ {
+ this.setSelectionCell(current);
+ }
+ }
+};
+
+/**
+ * Function: isValidRoot
+ *
+ * Returns true if the given cell is a valid root for the cell display
+ * hierarchy. This implementation returns true for all non-null values.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> which should be checked as a possible root.
+ */
+mxGraph.prototype.isValidRoot = function(cell)
+{
+ return (cell != null);
+};
+
+/**
+ * Group: Graph display
+ */
+
+/**
+ * Function: getGraphBounds
+ *
+ * Returns the bounds of the visible graph. Shortcut to
+ * <mxGraphView.getGraphBounds>. See also: <getBoundingBoxFromGeometry>.
+ */
+ mxGraph.prototype.getGraphBounds = function()
+ {
+ return this.view.getGraphBounds();
+ };
+
+/**
+ * Function: getCellBounds
+ *
+ * Returns the scaled, translated bounds for the given cell. See
+ * <mxGraphView.getBounds> for arrays.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose bounds should be returned.
+ * includeEdge - Optional boolean that specifies if the bounds of
+ * the connected edges should be included. Default is false.
+ * includeDescendants - Optional boolean that specifies if the bounds
+ * of all descendants should be included. Default is false.
+ */
+mxGraph.prototype.getCellBounds = function(cell, includeEdges, includeDescendants)
+{
+ var cells = [cell];
+
+ // Includes all connected edges
+ if (includeEdges)
+ {
+ cells = cells.concat(this.model.getEdges(cell));
+ }
+
+ var result = this.view.getBounds(cells);
+
+ // Recursively includes the bounds of the children
+ if (includeDescendants)
+ {
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var tmp = this.getCellBounds(this.model.getChildAt(cell, i),
+ includeEdges, true);
+
+ if (result != null)
+ {
+ result.add(tmp);
+ }
+ else
+ {
+ result = tmp;
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getBoundingBoxFromGeometry
+ *
+ * Returns the bounding box for the geometries of the vertices in the
+ * given array of cells. This can be used to find the graph bounds during
+ * a layout operation (ie. before the last endUpdate) as follows:
+ *
+ * (code)
+ * var cells = graph.getChildCells(graph.getDefaultParent(), true, true);
+ * var bounds = graph.getBoundingBoxFromGeometry(cells, true);
+ * (end)
+ *
+ * This can then be used to move cells to the origin:
+ *
+ * (code)
+ * if (bounds.x < 0 || bounds.y < 0)
+ * {
+ * graph.moveCells(cells, -Math.min(bounds.x, 0), -Math.min(bounds.y, 0))
+ * }
+ * (end)
+ *
+ * Or to translate the graph view:
+ *
+ * (code)
+ * if (bounds.x < 0 || bounds.y < 0)
+ * {
+ * graph.view.setTranslate(-Math.min(bounds.x, 0), -Math.min(bounds.y, 0));
+ * }
+ * (end)
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose bounds should be returned.
+ * includeEdges - Specifies if edge bounds should be included by computing
+ * the bounding box for all points its geometry. Default is false.
+ */
+mxGraph.prototype.getBoundingBoxFromGeometry = function(cells, includeEdges)
+{
+ includeEdges = (includeEdges != null) ? includeEdges : false;
+ var result = null;
+
+ if (cells != null)
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (includeEdges || this.model.isVertex(cells[i]))
+ {
+ // Computes the bounding box for the points in the geometry
+ var geo = this.getCellGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ var pts = geo.points;
+
+ if (pts != null && pts.length > 0)
+ {
+ var tmp = new mxRectangle(pts[0].x, pts[0].y, 0, 0);
+ var addPoint = function(pt)
+ {
+ if (pt != null)
+ {
+ tmp.add(new mxRectangle(pt.x, pt.y, 0, 0));
+ }
+ };
+
+ for (var j = 1; j < pts.length; j++)
+ {
+ addPoint(pts[j]);
+ }
+
+ addPoint(geo.getTerminalPoint(true));
+ addPoint(geo.getTerminalPoint(false));
+ }
+
+ if (result == null)
+ {
+ result = new mxRectangle(geo.x, geo.y, geo.width, geo.height);
+ }
+ else
+ {
+ result.add(geo);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: refresh
+ *
+ * Clears all cell states or the states for the hierarchy starting at the
+ * given cell and validates the graph. This fires a refresh event as the
+ * last step.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> for which the cell states should be cleared.
+ */
+mxGraph.prototype.refresh = function(cell)
+{
+ this.view.clear(cell, cell == null);
+ this.view.validate();
+ this.sizeDidChange();
+ this.fireEvent(new mxEventObject(mxEvent.REFRESH));
+};
+
+/**
+ * Function: snap
+ *
+ * Snaps the given numeric value to the grid if <gridEnabled> is true.
+ *
+ * Parameters:
+ *
+ * value - Numeric value to be snapped to the grid.
+ */
+mxGraph.prototype.snap = function(value)
+{
+ if (this.gridEnabled)
+ {
+ value = Math.round(value / this.gridSize ) * this.gridSize;
+ }
+
+ return value;
+};
+
+/**
+ * Function: panGraph
+ *
+ * Shifts the graph display by the given amount. This is used to preview
+ * panning operations, use <mxGraphView.setTranslate> to set a persistent
+ * translation of the view. Fires <mxEvent.PAN>.
+ *
+ * Parameters:
+ *
+ * dx - Amount to shift the graph along the x-axis.
+ * dy - Amount to shift the graph along the y-axis.
+ */
+mxGraph.prototype.panGraph = function(dx, dy)
+{
+ if (this.useScrollbarsForPanning && mxUtils.hasScrollbars(this.container))
+ {
+ this.container.scrollLeft = -dx;
+ this.container.scrollTop = -dy;
+ }
+ else
+ {
+ var canvas = this.view.getCanvas();
+
+ if (this.dialect == mxConstants.DIALECT_SVG)
+ {
+ // Puts everything inside the container in a DIV so that it
+ // can be moved without changing the state of the container
+ if (dx == 0 && dy == 0)
+ {
+ // Workaround for ignored removeAttribute on SVG element in IE9 standards
+ if (mxClient.IS_IE)
+ {
+ canvas.setAttribute('transform', 'translate('+ dx + ',' + dy + ')');
+ }
+ else
+ {
+ canvas.removeAttribute('transform');
+ }
+
+ if (this.shiftPreview1 != null)
+ {
+ var child = this.shiftPreview1.firstChild;
+
+ while (child != null)
+ {
+ var next = child.nextSibling;
+ this.container.appendChild(child);
+ child = next;
+ }
+
+ this.shiftPreview1.parentNode.removeChild(this.shiftPreview1);
+ this.shiftPreview1 = null;
+
+ this.container.appendChild(canvas.parentNode);
+
+ child = this.shiftPreview2.firstChild;
+
+ while (child != null)
+ {
+ var next = child.nextSibling;
+ this.container.appendChild(child);
+ child = next;
+ }
+
+ this.shiftPreview2.parentNode.removeChild(this.shiftPreview2);
+ this.shiftPreview2 = null;
+ }
+ }
+ else
+ {
+ canvas.setAttribute('transform', 'translate('+ dx + ',' + dy + ')');
+
+ if (this.shiftPreview1 == null)
+ {
+ // Needs two divs for stuff before and after the SVG element
+ this.shiftPreview1 = document.createElement('div');
+ this.shiftPreview1.style.position = 'absolute';
+ this.shiftPreview1.style.overflow = 'visible';
+
+ this.shiftPreview2 = document.createElement('div');
+ this.shiftPreview2.style.position = 'absolute';
+ this.shiftPreview2.style.overflow = 'visible';
+
+ var current = this.shiftPreview1;
+ var child = this.container.firstChild;
+
+ while (child != null)
+ {
+ var next = child.nextSibling;
+
+ // SVG element is moved via transform attribute
+ if (child != canvas.parentNode)
+ {
+ current.appendChild(child);
+ }
+ else
+ {
+ current = this.shiftPreview2;
+ }
+
+ child = next;
+ }
+
+ this.container.insertBefore(this.shiftPreview1, canvas.parentNode);
+ this.container.appendChild(this.shiftPreview2);
+ }
+
+ this.shiftPreview1.style.left = dx + 'px';
+ this.shiftPreview1.style.top = dy + 'px';
+ this.shiftPreview2.style.left = dx + 'px';
+ this.shiftPreview2.style.top = dy + 'px';
+ }
+ }
+ else
+ {
+ canvas.style.left = dx + 'px';
+ canvas.style.top = dy + 'px';
+ }
+
+ this.panDx = dx;
+ this.panDy = dy;
+
+ this.fireEvent(new mxEventObject(mxEvent.PAN));
+ }
+};
+
+/**
+ * Function: zoomIn
+ *
+ * Zooms into the graph by <zoomFactor>.
+ */
+mxGraph.prototype.zoomIn = function()
+{
+ this.zoom(this.zoomFactor);
+};
+
+/**
+ * Function: zoomOut
+ *
+ * Zooms out of the graph by <zoomFactor>.
+ */
+mxGraph.prototype.zoomOut = function()
+{
+ this.zoom(1 / this.zoomFactor);
+};
+
+/**
+ * Function: zoomActual
+ *
+ * Resets the zoom and panning in the view.
+ */
+mxGraph.prototype.zoomActual = function()
+{
+ if (this.view.scale == 1)
+ {
+ this.view.setTranslate(0, 0);
+ }
+ else
+ {
+ this.view.translate.x = 0;
+ this.view.translate.y = 0;
+
+ this.view.setScale(1);
+ }
+};
+
+/**
+ * Function: zoomTo
+ *
+ * Zooms the graph to the given scale with an optional boolean center
+ * argument, which is passd to <zoom>.
+ */
+mxGraph.prototype.zoomTo = function(scale, center)
+{
+ this.zoom(scale / this.view.scale, center);
+};
+
+/**
+ * Function: zoom
+ *
+ * Zooms the graph using the given factor. Center is an optional boolean
+ * argument that keeps the graph scrolled to the center. If the center argument
+ * is omitted, then <centerZoom> will be used as its value.
+ */
+mxGraph.prototype.zoom = function(factor, center)
+{
+ center = (center != null) ? center : this.centerZoom;
+ var scale = this.view.scale * factor;
+ var state = this.view.getState(this.getSelectionCell());
+
+ if (this.keepSelectionVisibleOnZoom && state != null)
+ {
+ var rect = new mxRectangle(
+ state.x * factor,
+ state.y * factor,
+ state.width * factor,
+ state.height * factor);
+
+ // Refreshes the display only once if a
+ // scroll is carried out
+ this.view.scale = scale;
+
+ if (!this.scrollRectToVisible(rect))
+ {
+ this.view.revalidate();
+
+ // Forces an event to be fired but does not revalidate again
+ this.view.setScale(scale);
+ }
+ }
+ else if (center && !mxUtils.hasScrollbars(this.container))
+ {
+ var dx = this.container.offsetWidth;
+ var dy = this.container.offsetHeight;
+
+ if (factor > 1)
+ {
+ var f = (factor -1) / (scale * 2);
+ dx *= -f;
+ dy *= -f;
+ }
+ else
+ {
+ var f = (1/factor -1) / (this.view.scale * 2);
+ dx *= f;
+ dy *= f;
+ }
+
+ this.view.scaleAndTranslate(scale,
+ this.view.translate.x + dx,
+ this.view.translate.y + dy);
+ }
+ else
+ {
+ this.view.setScale(scale);
+
+ if (mxUtils.hasScrollbars(this.container))
+ {
+ var dx = 0;
+ var dy = 0;
+
+ if (center)
+ {
+ dx = this.container.offsetWidth * (factor - 1) / 2;
+ dy = this.container.offsetHeight * (factor - 1) / 2;
+ }
+
+ this.container.scrollLeft = Math.round(this.container.scrollLeft * factor + dx);
+ this.container.scrollTop = Math.round(this.container.scrollTop * factor + dy);
+ }
+ }
+};
+
+/**
+ * Function: zoomToRect
+ *
+ * Zooms the graph to the specified rectangle. If the rectangle does not have same aspect
+ * ratio as the display container, it is increased in the smaller relative dimension only
+ * until the aspect match. The original rectangle is centralised within this expanded one.
+ *
+ * Note that the input rectangular must be un-scaled and un-translated.
+ *
+ * Parameters:
+ *
+ * rect - The un-scaled and un-translated rectangluar region that should be just visible
+ * after the operation
+ */
+mxGraph.prototype.zoomToRect = function(rect)
+{
+ var scaleX = this.container.clientWidth / rect.width;
+ var scaleY = this.container.clientHeight / rect.height;
+ var aspectFactor = scaleX / scaleY;
+
+ // Remove any overlap of the rect outside the client area
+ rect.x = Math.max(0, rect.x);
+ rect.y = Math.max(0, rect.y);
+ var rectRight = Math.min(this.container.scrollWidth, rect.x + rect.width);
+ var rectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height);
+ rect.width = rectRight - rect.x;
+ rect.height = rectBottom - rect.y;
+
+ // The selection area has to be increased to the same aspect
+ // ratio as the container, centred around the centre point of the
+ // original rect passed in.
+ if (aspectFactor < 1.0)
+ {
+ // Height needs increasing
+ var newHeight = rect.height / aspectFactor;
+ var deltaHeightBuffer = (newHeight - rect.height) / 2.0;
+ rect.height = newHeight;
+
+ // Assign up to half the buffer to the upper part of the rect, not crossing 0
+ // put the rest on the bottom
+ var upperBuffer = Math.min(rect.y , deltaHeightBuffer);
+ rect.y = rect.y - upperBuffer;
+
+ // Check if the bottom has extended too far
+ rectBottom = Math.min(this.container.scrollHeight, rect.y + rect.height);
+ rect.height = rectBottom - rect.y;
+ }
+ else
+ {
+ // Width needs increasing
+ var newWidth = rect.width * aspectFactor;
+ var deltaWidthBuffer = (newWidth - rect.width) / 2.0;
+ rect.width = newWidth;
+
+ // Assign up to half the buffer to the upper part of the rect, not crossing 0
+ // put the rest on the bottom
+ var leftBuffer = Math.min(rect.x , deltaWidthBuffer);
+ rect.x = rect.x - leftBuffer;
+
+ // Check if the right hand side has extended too far
+ rectRight = Math.min(this.container.scrollWidth, rect.x + rect.width);
+ rect.width = rectRight - rect.x;
+ }
+
+ var scale = this.container.clientWidth / rect.width;
+
+ if (!mxUtils.hasScrollbars(this.container))
+ {
+ this.view.scaleAndTranslate(scale, -rect.x, -rect.y);
+ }
+ else
+ {
+ this.view.setScale(scale);
+ this.container.scrollLeft = Math.round(rect.x * scale);
+ this.container.scrollTop = Math.round(rect.y * scale);
+ }
+};
+
+/**
+ * Function: fit
+ *
+ * Scales the graph such that the complete diagram fits into <container> and
+ * returns the current scale in the view. To fit an initial graph prior to
+ * rendering, set <mxGraphView.rendering> to false prior to changing the model
+ * and execute the following after changing the model.
+ *
+ * (code)
+ * graph.fit();
+ * graph.view.rendering = true;
+ * graph.refresh();
+ * (end)
+ *
+ * Parameters:
+ *
+ * border - Optional number that specifies the border. Default is 0.
+ * keepOrigin - Optional boolean that specifies if the translate should be
+ * changed. Default is false.
+ */
+mxGraph.prototype.fit = function(border, keepOrigin)
+{
+ if (this.container != null)
+ {
+ border = (border != null) ? border : 0;
+ keepOrigin = (keepOrigin != null) ? keepOrigin : false;
+
+ var w1 = this.container.clientWidth;
+ var h1 = this.container.clientHeight;
+
+ var bounds = this.view.getGraphBounds();
+
+ if (keepOrigin && bounds.x != null && bounds.y != null)
+ {
+ bounds.width += bounds.x;
+ bounds.height += bounds.y;
+ bounds.x = 0;
+ bounds.y = 0;
+ }
+
+ var s = this.view.scale;
+ var w2 = bounds.width / s;
+ var h2 = bounds.height / s;
+
+ // Fits to the size of the background image if required
+ if (this.backgroundImage != null)
+ {
+ w2 = Math.max(w2, this.backgroundImage.width - bounds.x / s);
+ h2 = Math.max(h2, this.backgroundImage.height - bounds.y / s);
+ }
+
+ var b = (keepOrigin) ? border : 2 * border;
+ var s2 = Math.floor(Math.min(w1 / (w2 + b), h1 / (h2 + b)) * 100) / 100;
+
+ if (this.minFitScale != null)
+ {
+ s2 = Math.max(s2, this.minFitScale);
+ }
+
+ if (this.maxFitScale != null)
+ {
+ s2 = Math.min(s2, this.maxFitScale);
+ }
+
+ if (!keepOrigin)
+ {
+ if (!mxUtils.hasScrollbars(this.container))
+ {
+ var x0 = (bounds.x != null) ? Math.floor(this.view.translate.x - bounds.x / s + border + 1) : border;
+ var y0 = (bounds.y != null) ? Math.floor(this.view.translate.y - bounds.y / s + border + 1) : border;
+
+ this.view.scaleAndTranslate(s2, x0, y0);
+ }
+ else
+ {
+ this.view.setScale(s2);
+
+ if (bounds.x != null)
+ {
+ this.container.scrollLeft = Math.round(bounds.x / s) * s2 - border -
+ Math.max(0, (this.container.clientWidth - w2 * s2) / 2);
+ }
+
+ if (bounds.y != null)
+ {
+ this.container.scrollTop = Math.round(bounds.y / s) * s2 - border -
+ Math.max(0, (this.container.clientHeight - h2 * s2) / 2);
+ }
+ }
+ }
+ else if (this.view.scale != s2)
+ {
+ this.view.setScale(s2);
+ }
+ }
+
+ return this.view.scale;
+};
+
+/**
+ * Function: scrollCellToVisible
+ *
+ * Pans the graph so that it shows the given cell. Optionally the cell may
+ * be centered in the container.
+ *
+ * To center a given graph if the <container> has no scrollbars, use the following code.
+ *
+ * [code]
+ * var bounds = graph.getGraphBounds();
+ * graph.view.setTranslate(-bounds.x - (bounds.width - container.clientWidth) / 2,
+ * -bounds.y - (bounds.height - container.clientHeight) / 2);
+ * [/code]
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be made visible.
+ * center - Optional boolean flag. Default is false.
+ */
+mxGraph.prototype.scrollCellToVisible = function(cell, center)
+{
+ var x = -this.view.translate.x;
+ var y = -this.view.translate.y;
+
+ var state = this.view.getState(cell);
+
+ if (state != null)
+ {
+ var bounds = new mxRectangle(x + state.x, y + state.y, state.width,
+ state.height);
+
+ if (center && this.container != null)
+ {
+ var w = this.container.clientWidth;
+ var h = this.container.clientHeight;
+
+ bounds.x = bounds.getCenterX() - w / 2;
+ bounds.width = w;
+ bounds.y = bounds.getCenterY() - h / 2;
+ bounds.height = h;
+ }
+
+ if (this.scrollRectToVisible(bounds))
+ {
+ // Triggers an update via the view's event source
+ this.view.setTranslate(this.view.translate.x, this.view.translate.y);
+ }
+ }
+};
+
+/**
+ * Function: scrollRectToVisible
+ *
+ * Pans the graph so that it shows the given rectangle.
+ *
+ * Parameters:
+ *
+ * rect - <mxRectangle> to be made visible.
+ */
+mxGraph.prototype.scrollRectToVisible = function(rect)
+{
+ var isChanged = false;
+
+ if (rect != null)
+ {
+ var w = this.container.offsetWidth;
+ var h = this.container.offsetHeight;
+
+ var widthLimit = Math.min(w, rect.width);
+ var heightLimit = Math.min(h, rect.height);
+
+ if (mxUtils.hasScrollbars(this.container))
+ {
+ var c = this.container;
+ rect.x += this.view.translate.x;
+ rect.y += this.view.translate.y;
+ var dx = c.scrollLeft - rect.x;
+ var ddx = Math.max(dx - c.scrollLeft, 0);
+
+ if (dx > 0)
+ {
+ c.scrollLeft -= dx + 2;
+ }
+ else
+ {
+ dx = rect.x + widthLimit - c.scrollLeft - c.clientWidth;
+
+ if (dx > 0)
+ {
+ c.scrollLeft += dx + 2;
+ }
+ }
+
+ var dy = c.scrollTop - rect.y;
+ var ddy = Math.max(0, dy - c.scrollTop);
+
+ if (dy > 0)
+ {
+ c.scrollTop -= dy + 2;
+ }
+ else
+ {
+ dy = rect.y + heightLimit - c.scrollTop - c.clientHeight;
+
+ if (dy > 0)
+ {
+ c.scrollTop += dy + 2;
+ }
+ }
+
+ if (!this.useScrollbarsForPanning && (ddx != 0 || ddy != 0))
+ {
+ this.view.setTranslate(ddx, ddy);
+ }
+ }
+ else
+ {
+ var x = -this.view.translate.x;
+ var y = -this.view.translate.y;
+
+ var s = this.view.scale;
+
+ if (rect.x + widthLimit > x + w)
+ {
+ this.view.translate.x -= (rect.x + widthLimit - w - x) / s;
+ isChanged = true;
+ }
+
+ if (rect.y + heightLimit > y + h)
+ {
+ this.view.translate.y -= (rect.y + heightLimit - h - y) / s;
+ isChanged = true;
+ }
+
+ if (rect.x < x)
+ {
+ this.view.translate.x += (x - rect.x) / s;
+ isChanged = true;
+ }
+
+ if (rect.y < y)
+ {
+ this.view.translate.y += (y - rect.y) / s;
+ isChanged = true;
+ }
+
+ if (isChanged)
+ {
+ this.view.refresh();
+
+ // Repaints selection marker (ticket 18)
+ if (this.selectionCellsHandler != null)
+ {
+ this.selectionCellsHandler.refresh();
+ }
+ }
+ }
+ }
+
+ return isChanged;
+};
+
+/**
+ * Function: getCellGeometry
+ *
+ * Returns the <mxGeometry> for the given cell. This implementation uses
+ * <mxGraphModel.getGeometry>. Subclasses can override this to implement
+ * specific geometries for cells in only one graph, that is, it can return
+ * geometries that depend on the current state of the view.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose geometry should be returned.
+ */
+mxGraph.prototype.getCellGeometry = function(cell)
+{
+ return this.model.getGeometry(cell);
+};
+
+/**
+ * Function: isCellVisible
+ *
+ * Returns true if the given cell is visible in this graph. This
+ * implementation uses <mxGraphModel.isVisible>. Subclassers can override
+ * this to implement specific visibility for cells in only one graph, that
+ * is, without affecting the visible state of the cell.
+ *
+ * When using dynamic filter expressions for cell visibility, then the
+ * graph should be revalidated after the filter expression has changed.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose visible state should be returned.
+ */
+mxGraph.prototype.isCellVisible = function(cell)
+{
+ return this.model.isVisible(cell);
+};
+
+/**
+ * Function: isCellCollapsed
+ *
+ * Returns true if the given cell is collapsed in this graph. This
+ * implementation uses <mxGraphModel.isCollapsed>. Subclassers can override
+ * this to implement specific collapsed states for cells in only one graph,
+ * that is, without affecting the collapsed state of the cell.
+ *
+ * When using dynamic filter expressions for the collapsed state, then the
+ * graph should be revalidated after the filter expression has changed.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose collapsed state should be returned.
+ */
+mxGraph.prototype.isCellCollapsed = function(cell)
+{
+ return this.model.isCollapsed(cell);
+};
+
+/**
+ * Function: isCellConnectable
+ *
+ * Returns true if the given cell is connectable in this graph. This
+ * implementation uses <mxGraphModel.isConnectable>. Subclassers can override
+ * this to implement specific connectable states for cells in only one graph,
+ * that is, without affecting the connectable state of the cell in the model.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose connectable state should be returned.
+ */
+mxGraph.prototype.isCellConnectable = function(cell)
+{
+ return this.model.isConnectable(cell);
+};
+
+/**
+ * Function: isOrthogonal
+ *
+ * Returns true if perimeter points should be computed such that the
+ * resulting edge has only horizontal or vertical segments.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> that represents the edge.
+ */
+mxGraph.prototype.isOrthogonal = function(edge)
+{
+ var orthogonal = edge.style[mxConstants.STYLE_ORTHOGONAL];
+
+ if (orthogonal != null)
+ {
+ return orthogonal;
+ }
+
+ var tmp = this.view.getEdgeStyle(edge);
+
+ return tmp == mxEdgeStyle.SegmentConnector ||
+ tmp == mxEdgeStyle.ElbowConnector ||
+ tmp == mxEdgeStyle.SideToSide ||
+ tmp == mxEdgeStyle.TopToBottom ||
+ tmp == mxEdgeStyle.EntityRelation ||
+ tmp == mxEdgeStyle.OrthConnector;
+};
+
+/**
+ * Function: isLoop
+ *
+ * Returns true if the given cell state is a loop.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents a potential loop.
+ */
+mxGraph.prototype.isLoop = function(state)
+{
+ var src = state.getVisibleTerminalState(true);
+ var trg = state.getVisibleTerminalState(false);
+
+ return (src != null && src == trg);
+};
+
+/**
+ * Function: isCloneEvent
+ *
+ * Returns true if the given event is a clone event. This implementation
+ * returns true if control is pressed.
+ */
+mxGraph.prototype.isCloneEvent = function(evt)
+{
+ return mxEvent.isControlDown(evt);
+};
+
+/**
+ * Function: isToggleEvent
+ *
+ * Returns true if the given event is a toggle event. This implementation
+ * returns true if the meta key (Cmd) is pressed on Macs or if control is
+ * pressed on any other platform.
+ */
+mxGraph.prototype.isToggleEvent = function(evt)
+{
+ return (mxClient.IS_MAC) ? mxEvent.isMetaDown(evt) : mxEvent.isControlDown(evt);
+};
+
+/**
+ * Function: isGridEnabledEvent
+ *
+ * Returns true if the given mouse event should be aligned to the grid.
+ */
+mxGraph.prototype.isGridEnabledEvent = function(evt)
+{
+ return evt != null && !mxEvent.isAltDown(evt);
+};
+
+/**
+ * Function: isConstrainedEvent
+ *
+ * Returns true if the given mouse event should be aligned to the grid.
+ */
+mxGraph.prototype.isConstrainedEvent = function(evt)
+{
+ return mxEvent.isShiftDown(evt);
+};
+
+/**
+ * Function: isForceMarqueeEvent
+ *
+ * Returns true if the given event forces marquee selection. This implementation
+ * returns true if alt is pressed.
+ */
+mxGraph.prototype.isForceMarqueeEvent = function(evt)
+{
+ return mxEvent.isAltDown(evt);
+};
+
+/**
+ * Group: Validation
+ */
+
+/**
+ * Function: validationAlert
+ *
+ * Displays the given validation error in a dialog. This implementation uses
+ * mxUtils.alert.
+ */
+mxGraph.prototype.validationAlert = function(message)
+{
+ mxUtils.alert(message);
+};
+
+/**
+ * Function: isEdgeValid
+ *
+ * Checks if the return value of <getEdgeValidationError> for the given
+ * arguments is null.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.isEdgeValid = function(edge, source, target)
+{
+ return this.getEdgeValidationError(edge, source, target) == null;
+};
+
+/**
+ * Function: getEdgeValidationError
+ *
+ * Returns the validation error message to be displayed when inserting or
+ * changing an edges' connectivity. A return value of null means the edge
+ * is valid, a return value of '' means it's not valid, but do not display
+ * an error message. Any other (non-empty) string returned from this method
+ * is displayed as an error message when trying to connect an edge to a
+ * source and target. This implementation uses the <multiplicities>, and
+ * checks <multigraph>, <allowDanglingEdges> and <allowLoops> to generate
+ * validation errors.
+ *
+ * For extending this method with specific checks for source/target cells,
+ * the method can be extended as follows. Returning an empty string means
+ * the edge is invalid with no error message, a non-null string specifies
+ * the error message, and null means the edge is valid.
+ *
+ * (code)
+ * graph.getEdgeValidationError = function(edge, source, target)
+ * {
+ * if (source != null && target != null &&
+ * this.model.getValue(source) != null &&
+ * this.model.getValue(target) != null)
+ * {
+ * if (target is not valid for source)
+ * {
+ * return 'Invalid Target';
+ * }
+ * }
+ *
+ * // "Supercall"
+ * return mxGraph.prototype.getEdgeValidationError.apply(this, arguments);
+ * }
+ * (end)
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.getEdgeValidationError = function(edge, source, target)
+{
+ if (edge != null && !this.isAllowDanglingEdges() && (source == null || target == null))
+ {
+ return '';
+ }
+
+ if (edge != null && this.model.getTerminal(edge, true) == null &&
+ this.model.getTerminal(edge, false) == null)
+ {
+ return null;
+ }
+
+ // Checks if we're dealing with a loop
+ if (!this.allowLoops && source == target && source != null)
+ {
+ return '';
+ }
+
+ // Checks if the connection is generally allowed
+ if (!this.isValidConnection(source, target))
+ {
+ return '';
+ }
+
+ if (source != null && target != null)
+ {
+ var error = '';
+
+ // Checks if the cells are already connected
+ // and adds an error message if required
+ if (!this.multigraph)
+ {
+ var tmp = this.model.getEdgesBetween(source, target, true);
+
+ // Checks if the source and target are not connected by another edge
+ if (tmp.length > 1 || (tmp.length == 1 && tmp[0] != edge))
+ {
+ error += (mxResources.get(this.alreadyConnectedResource) ||
+ this.alreadyConnectedResource)+'\n';
+ }
+ }
+
+ // Gets the number of outgoing edges from the source
+ // and the number of incoming edges from the target
+ // without counting the edge being currently changed.
+ var sourceOut = this.model.getDirectedEdgeCount(source, true, edge);
+ var targetIn = this.model.getDirectedEdgeCount(target, false, edge);
+
+ // Checks the change against each multiplicity rule
+ if (this.multiplicities != null)
+ {
+ for (var i = 0; i < this.multiplicities.length; i++)
+ {
+ var err = this.multiplicities[i].check(this, edge, source,
+ target, sourceOut, targetIn);
+
+ if (err != null)
+ {
+ error += err;
+ }
+ }
+ }
+
+ // Validates the source and target terminals independently
+ var err = this.validateEdge(edge, source, target);
+
+ if (err != null)
+ {
+ error += err;
+ }
+
+ return (error.length > 0) ? error : null;
+ }
+
+ return (this.allowDanglingEdges) ? null : '';
+};
+
+/**
+ * Function: validateEdge
+ *
+ * Hook method for subclassers to return an error message for the given
+ * edge and terminals. This implementation returns null.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ */
+mxGraph.prototype.validateEdge = function(edge, source, target)
+{
+ return null;
+};
+
+/**
+ * Function: validateGraph
+ *
+ * Validates the graph by validating each descendant of the given cell or
+ * the root of the model. Context is an object that contains the validation
+ * state for the complete validation run. The validation errors are
+ * attached to their cells using <setCellWarning>. This function returns true
+ * if no validation errors exist in the graph.
+ *
+ * Paramters:
+ *
+ * cell - Optional <mxCell> to start the validation recursion. Default is
+ * the graph root.
+ * context - Object that represents the global validation state.
+ */
+mxGraph.prototype.validateGraph = function(cell, context)
+{
+ cell = (cell != null) ? cell : this.model.getRoot();
+ context = (context != null) ? context : new Object();
+
+ var isValid = true;
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var tmp = this.model.getChildAt(cell, i);
+ var ctx = context;
+
+ if (this.isValidRoot(tmp))
+ {
+ ctx = new Object();
+ }
+
+ var warn = this.validateGraph(tmp, ctx);
+
+ if (warn != null)
+ {
+ this.setCellWarning(tmp, warn.replace(/\n/g, '<br>'));
+ }
+ else
+ {
+ this.setCellWarning(tmp, null);
+ }
+
+ isValid = isValid && warn == null;
+ }
+
+ var warning = '';
+
+ // Adds error for invalid children if collapsed (children invisible)
+ if (this.isCellCollapsed(cell) && !isValid)
+ {
+ warning += (mxResources.get(this.containsValidationErrorsResource) ||
+ this.containsValidationErrorsResource)+'\n';
+ }
+
+ // Checks edges and cells using the defined multiplicities
+ if (this.model.isEdge(cell))
+ {
+ warning += this.getEdgeValidationError(cell,
+ this.model.getTerminal(cell, true),
+ this.model.getTerminal(cell, false)) || '';
+ }
+ else
+ {
+ warning += this.getCellValidationError(cell) || '';
+ }
+
+ // Checks custom validation rules
+ var err = this.validateCell(cell, context);
+
+ if (err != null)
+ {
+ warning += err;
+ }
+
+ // Updates the display with the warning icons
+ // before any potential alerts are displayed.
+ // LATER: Move this into addCellOverlay. Redraw
+ // should check if overlay was added or removed.
+ if (this.model.getParent(cell) == null)
+ {
+ this.view.validate();
+ }
+
+ return (warning.length > 0 || !isValid) ? warning : null;
+};
+
+/**
+ * Function: getCellValidationError
+ *
+ * Checks all <multiplicities> that cannot be enforced while the graph is
+ * being modified, namely, all multiplicities that require a minimum of
+ * 1 edge.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the multiplicities should be checked.
+ */
+mxGraph.prototype.getCellValidationError = function(cell)
+{
+ var outCount = this.model.getDirectedEdgeCount(cell, true);
+ var inCount = this.model.getDirectedEdgeCount(cell, false);
+ var value = this.model.getValue(cell);
+ var error = '';
+
+ if (this.multiplicities != null)
+ {
+ for (var i = 0; i < this.multiplicities.length; i++)
+ {
+ var rule = this.multiplicities[i];
+
+ if (rule.source && mxUtils.isNode(value, rule.type,
+ rule.attr, rule.value) && ((rule.max == 0 && outCount > 0) ||
+ (rule.min == 1 && outCount == 0) || (rule.max == 1 && outCount > 1)))
+ {
+ error += rule.countError + '\n';
+ }
+ else if (!rule.source && mxUtils.isNode(value, rule.type,
+ rule.attr, rule.value) && ((rule.max == 0 && inCount > 0) ||
+ (rule.min == 1 && inCount == 0) || (rule.max == 1 && inCount > 1)))
+ {
+ error += rule.countError + '\n';
+ }
+ }
+ }
+
+ return (error.length > 0) ? error : null;
+};
+
+/**
+ * Function: validateCell
+ *
+ * Hook method for subclassers to return an error message for the given
+ * cell and validation context. This implementation returns null. Any HTML
+ * breaks will be converted to linefeeds in the calling method.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the cell to validate.
+ * context - Object that represents the global validation state.
+ */
+mxGraph.prototype.validateCell = function(cell, context)
+{
+ return null;
+};
+
+/**
+ * Group: Graph appearance
+ */
+
+/**
+ * Function: getBackgroundImage
+ *
+ * Returns the <backgroundImage> as an <mxImage>.
+ */
+mxGraph.prototype.getBackgroundImage = function()
+{
+ return this.backgroundImage;
+};
+
+/**
+ * Function: setBackgroundImage
+ *
+ * Sets the new <backgroundImage>.
+ *
+ * Parameters:
+ *
+ * image - New <mxImage> to be used for the background.
+ */
+mxGraph.prototype.setBackgroundImage = function(image)
+{
+ this.backgroundImage = image;
+};
+
+/**
+ * Function: getFoldingImage
+ *
+ * Returns the <mxImage> used to display the collapsed state of
+ * the specified cell state. This returns null for all edges.
+ */
+mxGraph.prototype.getFoldingImage = function(state)
+{
+ if (state != null && this.foldingEnabled && !this.getModel().isEdge(state.cell))
+ {
+ var tmp = this.isCellCollapsed(state.cell);
+
+ if (this.isCellFoldable(state.cell, !tmp))
+ {
+ return (tmp) ? this.collapsedImage : this.expandedImage;
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: convertValueToString
+ *
+ * Returns the textual representation for the given cell. This
+ * implementation returns the nodename or string-representation of the user
+ * object.
+ *
+ * Example:
+ *
+ * The following returns the label attribute from the cells user
+ * object if it is an XML node.
+ *
+ * (code)
+ * graph.convertValueToString = function(cell)
+ * {
+ * return cell.getAttribute('label');
+ * }
+ * (end)
+ *
+ * See also: <cellLabelChanged>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose textual representation should be returned.
+ */
+mxGraph.prototype.convertValueToString = function(cell)
+{
+ var value = this.model.getValue(cell);
+
+ if (value != null)
+ {
+ if (mxUtils.isNode(value))
+ {
+ return value.nodeName;
+ }
+ else if (typeof(value.toString) == 'function')
+ {
+ return value.toString();
+ }
+ }
+
+ return '';
+};
+
+/**
+ * Function: getLabel
+ *
+ * Returns a string or DOM node that represents the label for the given
+ * cell. This implementation uses <convertValueToString> if <labelsVisible>
+ * is true. Otherwise it returns an empty string.
+ *
+ * To truncate label to match the size of the cell, the following code
+ * can be used.
+ *
+ * (code)
+ * graph.getLabel = function(cell)
+ * {
+ * var label = mxGraph.prototype.getLabel.apply(this, arguments);
+ *
+ * if (label != null && this.model.isVertex(cell))
+ * {
+ * var geo = this.getCellGeometry(cell);
+ *
+ * if (geo != null)
+ * {
+ * var max = parseInt(geo.width / 8);
+ *
+ * if (label.length > max)
+ * {
+ * label = label.substring(0, max)+'...';
+ * }
+ * }
+ * }
+ * return mxUtils.htmlEntities(label);
+ * }
+ * (end)
+ *
+ * A resize listener is needed in the graph to force a repaint of the label
+ * after a resize.
+ *
+ * (code)
+ * graph.addListener(mxEvent.RESIZE_CELLS, function(sender, evt)
+ * {
+ * var cells = evt.getProperty('cells');
+ *
+ * for (var i = 0; i < cells.length; i++)
+ * {
+ * this.view.removeState(cells[i]);
+ * }
+ * });
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose label should be returned.
+ */
+mxGraph.prototype.getLabel = function(cell)
+{
+ var result = '';
+
+ if (this.labelsVisible && cell != null)
+ {
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ if (!mxUtils.getValue(style, mxConstants.STYLE_NOLABEL, false))
+ {
+ result = this.convertValueToString(cell);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: isHtmlLabel
+ *
+ * Returns true if the label must be rendered as HTML markup. The default
+ * implementation returns <htmlLabels>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose label should be displayed as HTML markup.
+ */
+mxGraph.prototype.isHtmlLabel = function(cell)
+{
+ return this.isHtmlLabels();
+};
+
+/**
+ * Function: isHtmlLabels
+ *
+ * Returns <htmlLabels>.
+ */
+mxGraph.prototype.isHtmlLabels = function()
+{
+ return this.htmlLabels;
+};
+
+/**
+ * Function: setHtmlLabels
+ *
+ * Sets <htmlLabels>.
+ */
+mxGraph.prototype.setHtmlLabels = function(value)
+{
+ this.htmlLabels = value;
+};
+
+/**
+ * Function: isWrapping
+ *
+ * This enables wrapping for HTML labels.
+ *
+ * Returns true if no white-space CSS style directive should be used for
+ * displaying the given cells label. This implementation returns true if
+ * <mxConstants.STYLE_WHITE_SPACE> in the style of the given cell is 'wrap'.
+ *
+ * This is used as a workaround for IE ignoring the white-space directive
+ * of child elements if the directive appears in a parent element. It
+ * should be overridden to return true if a white-space directive is used
+ * in the HTML markup that represents the given cells label. In order for
+ * HTML markup to work in labels, <isHtmlLabel> must also return true
+ * for the given cell.
+ *
+ * Example:
+ *
+ * (code)
+ * graph.getLabel = function(cell)
+ * {
+ * var tmp = mxGraph.prototype.getLabel.apply(this, arguments); // "supercall"
+ *
+ * if (this.model.isEdge(cell))
+ * {
+ * tmp = '<div style="width: 150px; white-space:normal;">'+tmp+'</div>';
+ * }
+ *
+ * return tmp;
+ * }
+ *
+ * graph.isWrapping = function(state)
+ * {
+ * return this.model.isEdge(state.cell);
+ * }
+ * (end)
+ *
+ * Makes sure no edge label is wider than 150 pixels, otherwise the content
+ * is wrapped. Note: No width must be specified for wrapped vertex labels as
+ * the vertex defines the width in its geometry.
+ *
+ * Parameters:
+ *
+ * state - <mxCell> whose label should be wrapped.
+ */
+mxGraph.prototype.isWrapping = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return (style != null) ? style[mxConstants.STYLE_WHITE_SPACE] == 'wrap' : false;
+};
+
+/**
+ * Function: isLabelClipped
+ *
+ * Returns true if the overflow portion of labels should be hidden. If this
+ * returns true then vertex labels will be clipped to the size of the vertices.
+ * This implementation returns true if <mxConstants.STYLE_OVERFLOW> in the
+ * style of the given cell is 'hidden'.
+ *
+ * Parameters:
+ *
+ * state - <mxCell> whose label should be clipped.
+ */
+mxGraph.prototype.isLabelClipped = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return (style != null) ? style[mxConstants.STYLE_OVERFLOW] == 'hidden' : false;
+};
+
+/**
+ * Function: getTooltip
+ *
+ * Returns the string or DOM node that represents the tooltip for the given
+ * state, node and coordinate pair. This implementation checks if the given
+ * node is a folding icon or overlay and returns the respective tooltip. If
+ * this does not result in a tooltip, the handler for the cell is retrieved
+ * from <selectionCellsHandler> and the optional getTooltipForNode method is
+ * called. If no special tooltip exists here then <getTooltipForCell> is used
+ * with the cell in the given state as the argument to return a tooltip for the
+ * given state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose tooltip should be returned.
+ * node - DOM node that is currently under the mouse.
+ * x - X-coordinate of the mouse.
+ * y - Y-coordinate of the mouse.
+ */
+mxGraph.prototype.getTooltip = function(state, node, x, y)
+{
+ var tip = null;
+
+ if (state != null)
+ {
+ // Checks if the mouse is over the folding icon
+ if (state.control != null && (node == state.control.node ||
+ node.parentNode == state.control.node))
+ {
+ tip = this.collapseExpandResource;
+ tip = mxResources.get(tip) || tip;
+ }
+
+ if (tip == null && state.overlays != null)
+ {
+ state.overlays.visit(function(id, shape)
+ {
+ // LATER: Exit loop if tip is not null
+ if (tip == null && (node == shape.node || node.parentNode == shape.node))
+ {
+ tip = shape.overlay.toString();
+ }
+ });
+ }
+
+ if (tip == null)
+ {
+ var handler = this.selectionCellsHandler.getHandler(state.cell);
+
+ if (handler != null && typeof(handler.getTooltipForNode) == 'function')
+ {
+ tip = handler.getTooltipForNode(node);
+ }
+ }
+
+ if (tip == null)
+ {
+ tip = this.getTooltipForCell(state.cell);
+ }
+ }
+
+ return tip;
+};
+
+/**
+ * Function: getTooltipForCell
+ *
+ * Returns the string or DOM node to be used as the tooltip for the given
+ * cell. This implementation uses the cells getTooltip function if it
+ * exists, or else it returns <convertValueToString> for the cell.
+ *
+ * Example:
+ *
+ * (code)
+ * graph.getTooltipForCell = function(cell)
+ * {
+ * return 'Hello, World!';
+ * }
+ * (end)
+ *
+ * Replaces all tooltips with the string Hello, World!
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose tooltip should be returned.
+ */
+mxGraph.prototype.getTooltipForCell = function(cell)
+{
+ var tip = null;
+
+ if (cell != null && cell.getTooltip != null)
+ {
+ tip = cell.getTooltip();
+ }
+ else
+ {
+ tip = this.convertValueToString(cell);
+ }
+
+ return tip;
+};
+
+/**
+ * Function: getCursorForCell
+ *
+ * Returns the cursor value to be used for the CSS of the shape for the
+ * given cell. This implementation returns null.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose cursor should be returned.
+ */
+mxGraph.prototype.getCursorForCell = function(cell)
+{
+ return null;
+};
+
+/**
+ * Function: getStartSize
+ *
+ * Returns the start size of the given swimlane, that is, the width or
+ * height of the part that contains the title, depending on the
+ * horizontal style. The return value is an <mxRectangle> with either
+ * width or height set as appropriate.
+ *
+ * Parameters:
+ *
+ * swimlane - <mxCell> whose start size should be returned.
+ */
+mxGraph.prototype.getStartSize = function(swimlane)
+{
+ var result = new mxRectangle();
+ var state = this.view.getState(swimlane);
+ var style = (state != null) ? state.style : this.getCellStyle(swimlane);
+
+ if (style != null)
+ {
+ var size = parseInt(mxUtils.getValue(style,
+ mxConstants.STYLE_STARTSIZE, mxConstants.DEFAULT_STARTSIZE));
+
+ if (mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, true))
+ {
+ result.height = size;
+ }
+ else
+ {
+ result.width = size;
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getImage
+ *
+ * Returns the image URL for the given cell state. This implementation
+ * returns the value stored under <mxConstants.STYLE_IMAGE> in the cell
+ * style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose image URL should be returned.
+ */
+mxGraph.prototype.getImage = function(state)
+{
+ return (state != null && state.style != null) ?
+ state.style[mxConstants.STYLE_IMAGE] : null;
+};
+
+/**
+ * Function: getVerticalAlign
+ *
+ * Returns the vertical alignment for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_VERTICAL_ALIGN> in the cell style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose vertical alignment should be
+ * returned.
+ */
+mxGraph.prototype.getVerticalAlign = function(state)
+{
+ return (state != null && state.style != null) ?
+ (state.style[mxConstants.STYLE_VERTICAL_ALIGN] ||
+ mxConstants.ALIGN_MIDDLE ):
+ null;
+};
+
+/**
+ * Function: getIndicatorColor
+ *
+ * Returns the indicator color for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_COLOR> in the cell style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose indicator color should be
+ * returned.
+ */
+mxGraph.prototype.getIndicatorColor = function(state)
+{
+ return (state != null && state.style != null) ?
+ state.style[mxConstants.STYLE_INDICATOR_COLOR] : null;
+};
+
+/**
+ * Function: getIndicatorGradientColor
+ *
+ * Returns the indicator gradient color for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_GRADIENTCOLOR> in the cell style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose indicator gradient color should be
+ * returned.
+ */
+mxGraph.prototype.getIndicatorGradientColor = function(state)
+{
+ return (state != null && state.style != null) ?
+ state.style[mxConstants.STYLE_INDICATOR_GRADIENTCOLOR] : null;
+};
+
+/**
+ * Function: getIndicatorShape
+ *
+ * Returns the indicator shape for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_SHAPE> in the cell style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose indicator shape should be returned.
+ */
+mxGraph.prototype.getIndicatorShape = function(state)
+{
+ return (state != null && state.style != null) ?
+ state.style[mxConstants.STYLE_INDICATOR_SHAPE] : null;
+};
+
+/**
+ * Function: getIndicatorImage
+ *
+ * Returns the indicator image for the given cell state. This
+ * implementation returns the value stored under
+ * <mxConstants.STYLE_INDICATOR_IMAGE> in the cell style.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose indicator image should be returned.
+ */
+mxGraph.prototype.getIndicatorImage = function(state)
+{
+ return (state != null && state.style != null) ?
+ state.style[mxConstants.STYLE_INDICATOR_IMAGE] : null;
+};
+
+/**
+ * Function: getBorder
+ *
+ * Returns the value of <border>.
+ */
+mxGraph.prototype.getBorder = function()
+{
+ return this.border;
+};
+
+/**
+ * Function: setBorder
+ *
+ * Sets the value of <border>.
+ *
+ * Parameters:
+ *
+ * value - Positive integer that represents the border to be used.
+ */
+mxGraph.prototype.setBorder = function(value)
+{
+ this.border = value;
+};
+
+/**
+ * Function: isSwimlane
+ *
+ * Returns true if the given cell is a swimlane in the graph. A swimlane is
+ * a container cell with some specific behaviour. This implementation
+ * checks if the shape associated with the given cell is a <mxSwimlane>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be checked.
+ */
+mxGraph.prototype.isSwimlane = function (cell)
+{
+ if (cell != null)
+ {
+ if (this.model.getParent(cell) != this.model.getRoot())
+ {
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ if (style != null && !this.model.isEdge(cell))
+ {
+ return style[mxConstants.STYLE_SHAPE] ==
+ mxConstants.SHAPE_SWIMLANE;
+ }
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Group: Graph behaviour
+ */
+
+/**
+ * Function: isResizeContainer
+ *
+ * Returns <resizeContainer>.
+ */
+mxGraph.prototype.isResizeContainer = function()
+{
+ return this.resizeContainer;
+};
+
+/**
+ * Function: setResizeContainer
+ *
+ * Sets <resizeContainer>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the container should be resized.
+ */
+mxGraph.prototype.setResizeContainer = function(value)
+{
+ this.resizeContainer = value;
+};
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if the graph is <enabled>.
+ */
+mxGraph.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Specifies if the graph should allow any interactions. This
+ * implementation updates <enabled>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should be enabled.
+ */
+mxGraph.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: isEscapeEnabled
+ *
+ * Returns <escapeEnabled>.
+ */
+mxGraph.prototype.isEscapeEnabled = function()
+{
+ return this.escapeEnabled;
+};
+
+/**
+ * Function: setEscapeEnabled
+ *
+ * Sets <escapeEnabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean indicating if escape should be enabled.
+ */
+mxGraph.prototype.setEscapeEnabled = function(value)
+{
+ this.escapeEnabled = value;
+};
+
+/**
+ * Function: isInvokesStopCellEditing
+ *
+ * Returns <invokesStopCellEditing>.
+ */
+mxGraph.prototype.isInvokesStopCellEditing = function()
+{
+ return this.invokesStopCellEditing;
+};
+
+/**
+ * Function: setInvokesStopCellEditing
+ *
+ * Sets <invokesStopCellEditing>.
+ */
+mxGraph.prototype.setInvokesStopCellEditing = function(value)
+{
+ this.invokesStopCellEditing = value;
+};
+
+/**
+ * Function: isEnterStopsCellEditing
+ *
+ * Returns <enterStopsCellEditing>.
+ */
+mxGraph.prototype.isEnterStopsCellEditing = function()
+{
+ return this.enterStopsCellEditing;
+};
+
+/**
+ * Function: setEnterStopsCellEditing
+ *
+ * Sets <enterStopsCellEditing>.
+ */
+mxGraph.prototype.setEnterStopsCellEditing = function(value)
+{
+ this.enterStopsCellEditing = value;
+};
+
+/**
+ * Function: isCellLocked
+ *
+ * Returns true if the given cell may not be moved, sized, bended,
+ * disconnected, edited or selected. This implementation returns true for
+ * all vertices with a relative geometry if <locked> is false.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose locked state should be returned.
+ */
+mxGraph.prototype.isCellLocked = function(cell)
+{
+ var geometry = this.model.getGeometry(cell);
+
+ return this.isCellsLocked() || (geometry != null &&
+ this.model.isVertex(cell) && geometry.relative);
+};
+
+/**
+ * Function: isCellsLocked
+ *
+ * Returns true if the given cell may not be moved, sized, bended,
+ * disconnected, edited or selected. This implementation returns true for
+ * all vertices with a relative geometry if <locked> is false.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose locked state should be returned.
+ */
+mxGraph.prototype.isCellsLocked = function()
+{
+ return this.cellsLocked;
+};
+
+/**
+ * Function: setLocked
+ *
+ * Sets if any cell may be moved, sized, bended, disconnected, edited or
+ * selected.
+ *
+ * Parameters:
+ *
+ * value - Boolean that defines the new value for <cellsLocked>.
+ */
+mxGraph.prototype.setCellsLocked = function(value)
+{
+ this.cellsLocked = value;
+};
+
+/**
+ * Function: getCloneableCells
+ *
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getCloneableCells = function(cells)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.isCellCloneable(cell);
+ }));
+};
+
+/**
+ * Function: isCellCloneable
+ *
+ * Returns true if the given cell is cloneable. This implementation returns
+ * <isCellsCloneable> for all cells unless a cell style specifies
+ * <mxConstants.STYLE_CLONEABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> whose cloneable state should be returned.
+ */
+mxGraph.prototype.isCellCloneable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsCloneable() && style[mxConstants.STYLE_CLONEABLE] != 0;
+};
+
+/**
+ * Function: isCellsCloneable
+ *
+ * Returns <cellsCloneable>, that is, if the graph allows cloning of cells
+ * by using control-drag.
+ */
+mxGraph.prototype.isCellsCloneable = function()
+{
+ return this.cellsCloneable;
+};
+
+/**
+ * Function: setCellsCloneable
+ *
+ * Specifies if the graph should allow cloning of cells by holding down the
+ * control key while cells are being moved. This implementation updates
+ * <cellsCloneable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should be cloneable.
+ */
+mxGraph.prototype.setCellsCloneable = function(value)
+{
+ this.cellsCloneable = value;
+};
+
+/**
+ * Function: getExportableCells
+ *
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getExportableCells = function(cells)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.canExportCell(cell);
+ }));
+};
+
+/**
+ * Function: canExportCell
+ *
+ * Returns true if the given cell may be exported to the clipboard. This
+ * implementation returns <exportEnabled> for all cells.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the cell to be exported.
+ */
+mxGraph.prototype.canExportCell = function(cell)
+{
+ return this.exportEnabled;
+};
+
+/**
+ * Function: getImportableCells
+ *
+ * Returns the cells which may be imported in the given array of cells.
+ */
+mxGraph.prototype.getImportableCells = function(cells)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.canImportCell(cell);
+ }));
+};
+
+/**
+ * Function: canImportCell
+ *
+ * Returns true if the given cell may be imported from the clipboard.
+ * This implementation returns <importEnabled> for all cells.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the cell to be imported.
+ */
+mxGraph.prototype.canImportCell = function(cell)
+{
+ return this.importEnabled;
+};
+
+/**
+ * Function: isCellSelectable
+ *
+ * Returns true if the given cell is selectable. This implementation
+ * returns <cellsSelectable>.
+ *
+ * To add a new style for making cells (un)selectable, use the following code.
+ *
+ * (code)
+ * mxGraph.prototype.isCellSelectable = function(cell)
+ * {
+ * var state = this.view.getState(cell);
+ * var style = (state != null) ? state.style : this.getCellStyle(cell);
+ *
+ * return this.isCellsSelectable() && !this.isCellLocked(cell) && style['selectable'] != 0;
+ * };
+ * (end)
+ *
+ * You can then use the new style as shown in this example.
+ *
+ * (code)
+ * graph.insertVertex(parent, null, 'Hello,', 20, 20, 80, 30, 'selectable=0');
+ * (end)
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose selectable state should be returned.
+ */
+mxGraph.prototype.isCellSelectable = function(cell)
+{
+ return this.isCellsSelectable();
+};
+
+/**
+ * Function: isCellsSelectable
+ *
+ * Returns <cellsSelectable>.
+ */
+mxGraph.prototype.isCellsSelectable = function()
+{
+ return this.cellsSelectable;
+};
+
+/**
+ * Function: setCellsSelectable
+ *
+ * Sets <cellsSelectable>.
+ */
+mxGraph.prototype.setCellsSelectable = function(value)
+{
+ this.cellsSelectable = value;
+};
+
+/**
+ * Function: getDeletableCells
+ *
+ * Returns the cells which may be exported in the given array of cells.
+ */
+mxGraph.prototype.getDeletableCells = function(cells)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.isCellDeletable(cell);
+ }));
+};
+
+/**
+ * Function: isCellDeletable
+ *
+ * Returns true if the given cell is moveable. This returns
+ * <cellsDeletable> for all given cells if a cells style does not specify
+ * <mxConstants.STYLE_DELETABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose deletable state should be returned.
+ */
+mxGraph.prototype.isCellDeletable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsDeletable() && style[mxConstants.STYLE_DELETABLE] != 0;
+};
+
+/**
+ * Function: isCellsDeletable
+ *
+ * Returns <cellsDeletable>.
+ */
+mxGraph.prototype.isCellsDeletable = function()
+{
+ return this.cellsDeletable;
+};
+
+/**
+ * Function: setCellsDeletable
+ *
+ * Sets <cellsDeletable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should allow deletion of cells.
+ */
+mxGraph.prototype.setCellsDeletable = function(value)
+{
+ this.cellsDeletable = value;
+};
+
+/**
+ * Function: isLabelMovable
+ *
+ * Returns true if the given edges's label is moveable. This returns
+ * <movable> for all given cells if <isLocked> does not return true
+ * for the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose label should be moved.
+ */
+mxGraph.prototype.isLabelMovable = function(cell)
+{
+ return !this.isCellLocked(cell) &&
+ ((this.model.isEdge(cell) && this.edgeLabelsMovable) ||
+ (this.model.isVertex(cell) && this.vertexLabelsMovable));
+};
+
+/**
+ * Function: getMovableCells
+ *
+ * Returns the cells which are movable in the given array of cells.
+ */
+mxGraph.prototype.getMovableCells = function(cells)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.isCellMovable(cell);
+ }));
+};
+
+/**
+ * Function: isCellMovable
+ *
+ * Returns true if the given cell is moveable. This returns <cellsMovable>
+ * for all given cells if <isCellLocked> does not return true for the given
+ * cell and its style does not specify <mxConstants.STYLE_MOVABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose movable state should be returned.
+ */
+mxGraph.prototype.isCellMovable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsMovable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_MOVABLE] != 0;
+};
+
+/**
+ * Function: isCellsMovable
+ *
+ * Returns <cellsMovable>.
+ */
+mxGraph.prototype.isCellsMovable = function()
+{
+ return this.cellsMovable;
+};
+
+/**
+ * Function: setCellsMovable
+ *
+ * Specifies if the graph should allow moving of cells. This implementation
+ * updates <cellsMsovable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should allow moving of cells.
+ */
+mxGraph.prototype.setCellsMovable = function(value)
+{
+ this.cellsMovable = value;
+};
+
+/**
+ * Function: isGridEnabled
+ *
+ * Returns <gridEnabled> as a boolean.
+ */
+mxGraph.prototype.isGridEnabled = function()
+{
+ return this.gridEnabled;
+};
+
+/**
+ * Function: setGridEnabled
+ *
+ * Specifies if the grid should be enabled.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the grid should be enabled.
+ */
+mxGraph.prototype.setGridEnabled = function(value)
+{
+ this.gridEnabled = value;
+};
+
+/**
+ * Function: isPortsEnabled
+ *
+ * Returns <portsEnabled> as a boolean.
+ */
+mxGraph.prototype.isPortsEnabled = function()
+{
+ return this.portsEnabled;
+};
+
+/**
+ * Function: setPortsEnabled
+ *
+ * Specifies if the ports should be enabled.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the ports should be enabled.
+ */
+mxGraph.prototype.setPortsEnabled = function(value)
+{
+ this.portsEnabled = value;
+};
+
+/**
+ * Function: getGridSize
+ *
+ * Returns <gridSize>.
+ */
+mxGraph.prototype.getGridSize = function()
+{
+ return this.gridSize;
+};
+
+/**
+ * Function: setGridSize
+ *
+ * Sets <gridSize>.
+ */
+mxGraph.prototype.setGridSize = function(value)
+{
+ this.gridSize = value;
+};
+
+/**
+ * Function: getTolerance
+ *
+ * Returns <tolerance>.
+ */
+mxGraph.prototype.getTolerance = function()
+{
+ return this.tolerance;
+};
+
+/**
+ * Function: setTolerance
+ *
+ * Sets <tolerance>.
+ */
+mxGraph.prototype.setTolerance = function(value)
+{
+ this.tolerance = value;
+};
+
+/**
+ * Function: isVertexLabelsMovable
+ *
+ * Returns <vertexLabelsMovable>.
+ */
+mxGraph.prototype.isVertexLabelsMovable = function()
+{
+ return this.vertexLabelsMovable;
+};
+
+/**
+ * Function: setVertexLabelsMovable
+ *
+ * Sets <vertexLabelsMovable>.
+ */
+mxGraph.prototype.setVertexLabelsMovable = function(value)
+{
+ this.vertexLabelsMovable = value;
+};
+
+/**
+ * Function: isEdgeLabelsMovable
+ *
+ * Returns <edgeLabelsMovable>.
+ */
+mxGraph.prototype.isEdgeLabelsMovable = function()
+{
+ return this.edgeLabelsMovable;
+};
+
+/**
+ * Function: isEdgeLabelsMovable
+ *
+ * Sets <edgeLabelsMovable>.
+ */
+mxGraph.prototype.setEdgeLabelsMovable = function(value)
+{
+ this.edgeLabelsMovable = value;
+};
+
+/**
+ * Function: isSwimlaneNesting
+ *
+ * Returns <swimlaneNesting> as a boolean.
+ */
+mxGraph.prototype.isSwimlaneNesting = function()
+{
+ return this.swimlaneNesting;
+};
+
+/**
+ * Function: setSwimlaneNesting
+ *
+ * Specifies if swimlanes can be nested by drag and drop. This is only
+ * taken into account if dropEnabled is true.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if swimlanes can be nested.
+ */
+mxGraph.prototype.setSwimlaneNesting = function(value)
+{
+ this.swimlaneNesting = value;
+};
+
+/**
+ * Function: isSwimlaneSelectionEnabled
+ *
+ * Returns <swimlaneSelectionEnabled> as a boolean.
+ */
+mxGraph.prototype.isSwimlaneSelectionEnabled = function()
+{
+ return this.swimlaneSelectionEnabled;
+};
+
+/**
+ * Function: setSwimlaneSelectionEnabled
+ *
+ * Specifies if swimlanes should be selected if the mouse is released
+ * over their content area.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if swimlanes content areas
+ * should be selected when the mouse is released over them.
+ */
+mxGraph.prototype.setSwimlaneSelectionEnabled = function(value)
+{
+ this.swimlaneSelectionEnabled = value;
+};
+
+/**
+ * Function: isMultigraph
+ *
+ * Returns <multigraph> as a boolean.
+ */
+mxGraph.prototype.isMultigraph = function()
+{
+ return this.multigraph;
+};
+
+/**
+ * Function: setMultigraph
+ *
+ * Specifies if the graph should allow multiple connections between the
+ * same pair of vertices.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph allows multiple connections
+ * between the same pair of vertices.
+ */
+mxGraph.prototype.setMultigraph = function(value)
+{
+ this.multigraph = value;
+};
+
+/**
+ * Function: isAllowLoops
+ *
+ * Returns <allowLoops> as a boolean.
+ */
+mxGraph.prototype.isAllowLoops = function()
+{
+ return this.allowLoops;
+};
+
+/**
+ * Function: setAllowDanglingEdges
+ *
+ * Specifies if dangling edges are allowed, that is, if edges are allowed
+ * that do not have a source and/or target terminal defined.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if dangling edges are allowed.
+ */
+mxGraph.prototype.setAllowDanglingEdges = function(value)
+{
+ this.allowDanglingEdges = value;
+};
+
+/**
+ * Function: isAllowDanglingEdges
+ *
+ * Returns <allowDanglingEdges> as a boolean.
+ */
+mxGraph.prototype.isAllowDanglingEdges = function()
+{
+ return this.allowDanglingEdges;
+};
+
+/**
+ * Function: setConnectableEdges
+ *
+ * Specifies if edges should be connectable.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if edges should be connectable.
+ */
+mxGraph.prototype.setConnectableEdges = function(value)
+{
+ this.connectableEdges = value;
+};
+
+/**
+ * Function: isConnectableEdges
+ *
+ * Returns <connectableEdges> as a boolean.
+ */
+mxGraph.prototype.isConnectableEdges = function()
+{
+ return this.connectableEdges;
+};
+
+/**
+ * Function: setCloneInvalidEdges
+ *
+ * Specifies if edges should be inserted when cloned but not valid wrt.
+ * <getEdgeValidationError>. If false such edges will be silently ignored.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if cloned invalid edges should be
+ * inserted into the graph or ignored.
+ */
+mxGraph.prototype.setCloneInvalidEdges = function(value)
+{
+ this.cloneInvalidEdges = value;
+};
+
+/**
+ * Function: isCloneInvalidEdges
+ *
+ * Returns <cloneInvalidEdges> as a boolean.
+ */
+mxGraph.prototype.isCloneInvalidEdges = function()
+{
+ return this.cloneInvalidEdges;
+};
+
+/**
+ * Function: setAllowLoops
+ *
+ * Specifies if loops are allowed.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if loops are allowed.
+ */
+mxGraph.prototype.setAllowLoops = function(value)
+{
+ this.allowLoops = value;
+};
+
+/**
+ * Function: isDisconnectOnMove
+ *
+ * Returns <disconnectOnMove> as a boolean.
+ */
+mxGraph.prototype.isDisconnectOnMove = function()
+{
+ return this.disconnectOnMove;
+};
+
+/**
+ * Function: setDisconnectOnMove
+ *
+ * Specifies if edges should be disconnected when moved. (Note: Cloned
+ * edges are always disconnected.)
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if edges should be disconnected
+ * when moved.
+ */
+mxGraph.prototype.setDisconnectOnMove = function(value)
+{
+ this.disconnectOnMove = value;
+};
+
+/**
+ * Function: isDropEnabled
+ *
+ * Returns <dropEnabled> as a boolean.
+ */
+mxGraph.prototype.isDropEnabled = function()
+{
+ return this.dropEnabled;
+};
+
+/**
+ * Function: setDropEnabled
+ *
+ * Specifies if the graph should allow dropping of cells onto or into other
+ * cells.
+ *
+ * Parameters:
+ *
+ * dropEnabled - Boolean indicating if the graph should allow dropping
+ * of cells into other cells.
+ */
+mxGraph.prototype.setDropEnabled = function(value)
+{
+ this.dropEnabled = value;
+};
+
+/**
+ * Function: isSplitEnabled
+ *
+ * Returns <splitEnabled> as a boolean.
+ */
+mxGraph.prototype.isSplitEnabled = function()
+{
+ return this.splitEnabled;
+};
+
+/**
+ * Function: setSplitEnabled
+ *
+ * Specifies if the graph should allow dropping of cells onto or into other
+ * cells.
+ *
+ * Parameters:
+ *
+ * dropEnabled - Boolean indicating if the graph should allow dropping
+ * of cells into other cells.
+ */
+mxGraph.prototype.setSplitEnabled = function(value)
+{
+ this.splitEnabled = value;
+};
+
+/**
+ * Function: isCellResizable
+ *
+ * Returns true if the given cell is resizable. This returns
+ * <cellsResizable> for all given cells if <isCellLocked> does not return
+ * true for the given cell and its style does not specify
+ * <mxConstants.STYLE_RESIZABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose resizable state should be returned.
+ */
+mxGraph.prototype.isCellResizable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsResizable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_RESIZABLE] != 0;
+};
+
+/**
+ * Function: isCellsResizable
+ *
+ * Returns <cellsResizable>.
+ */
+mxGraph.prototype.isCellsResizable = function()
+{
+ return this.cellsResizable;
+};
+
+/**
+ * Function: setCellsResizable
+ *
+ * Specifies if the graph should allow resizing of cells. This
+ * implementation updates <cellsResizable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should allow resizing of
+ * cells.
+ */
+mxGraph.prototype.setCellsResizable = function(value)
+{
+ this.cellsResizable = value;
+};
+
+/**
+ * Function: isTerminalPointMovable
+ *
+ * Returns true if the given terminal point is movable. This is independent
+ * from <isCellConnectable> and <isCellDisconnectable> and controls if terminal
+ * points can be moved in the graph if the edge is not connected. Note that it
+ * is required for this to return true to connect unconnected edges. This
+ * implementation returns true.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose terminal point should be moved.
+ * source - Boolean indicating if the source or target terminal should be moved.
+ */
+mxGraph.prototype.isTerminalPointMovable = function(cell, source)
+{
+ return true;
+};
+
+/**
+ * Function: isCellBendable
+ *
+ * Returns true if the given cell is bendable. This returns <cellsBendable>
+ * for all given cells if <isLocked> does not return true for the given
+ * cell and its style does not specify <mxConstants.STYLE_BENDABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose bendable state should be returned.
+ */
+mxGraph.prototype.isCellBendable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsBendable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_BENDABLE] != 0;
+};
+
+/**
+ * Function: isCellsBendable
+ *
+ * Returns <cellsBenadable>.
+ */
+mxGraph.prototype.isCellsBendable = function()
+{
+ return this.cellsBendable;
+};
+
+/**
+ * Function: setCellsBendable
+ *
+ * Specifies if the graph should allow bending of edges. This
+ * implementation updates <bendable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should allow bending of
+ * edges.
+ */
+mxGraph.prototype.setCellsBendable = function(value)
+{
+ this.cellsBendable = value;
+};
+
+/**
+ * Function: isCellEditable
+ *
+ * Returns true if the given cell is editable. This returns <cellsEditable> for
+ * all given cells if <isCellLocked> does not return true for the given cell
+ * and its style does not specify <mxConstants.STYLE_EDITABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose editable state should be returned.
+ */
+mxGraph.prototype.isCellEditable = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isCellsEditable() && !this.isCellLocked(cell) && style[mxConstants.STYLE_EDITABLE] != 0;
+};
+
+/**
+ * Function: isCellsEditable
+ *
+ * Returns <cellsEditable>.
+ */
+mxGraph.prototype.isCellsEditable = function()
+{
+ return this.cellsEditable;
+};
+
+/**
+ * Function: setCellsEditable
+ *
+ * Specifies if the graph should allow in-place editing for cell labels.
+ * This implementation updates <cellsEditable>.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if the graph should allow in-place
+ * editing.
+ */
+mxGraph.prototype.setCellsEditable = function(value)
+{
+ this.cellsEditable = value;
+};
+
+/**
+ * Function: isCellDisconnectable
+ *
+ * Returns true if the given cell is disconnectable from the source or
+ * target terminal. This returns <isCellsDisconnectable> for all given
+ * cells if <isCellLocked> does not return true for the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose disconnectable state should be returned.
+ * terminal - <mxCell> that represents the source or target terminal.
+ * source - Boolean indicating if the source or target terminal is to be
+ * disconnected.
+ */
+mxGraph.prototype.isCellDisconnectable = function(cell, terminal, source)
+{
+ return this.isCellsDisconnectable() && !this.isCellLocked(cell);
+};
+
+/**
+ * Function: isCellsDisconnectable
+ *
+ * Returns <cellsDisconnectable>.
+ */
+mxGraph.prototype.isCellsDisconnectable = function()
+{
+ return this.cellsDisconnectable;
+};
+
+/**
+ * Function: setCellsDisconnectable
+ *
+ * Sets <cellsDisconnectable>.
+ */
+mxGraph.prototype.setCellsDisconnectable = function(value)
+{
+ this.cellsDisconnectable = value;
+};
+
+/**
+ * Function: isValidSource
+ *
+ * Returns true if the given cell is a valid source for new connections.
+ * This implementation returns true for all non-null values and is
+ * called by is called by <isValidConnection>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents a possible source or null.
+ */
+mxGraph.prototype.isValidSource = function(cell)
+{
+ return (cell == null && this.allowDanglingEdges) ||
+ (cell != null && (!this.model.isEdge(cell) ||
+ this.connectableEdges) && this.isCellConnectable(cell));
+};
+
+/**
+ * Function: isValidTarget
+ *
+ * Returns <isValidSource> for the given cell. This is called by
+ * <isValidConnection>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents a possible target or null.
+ */
+mxGraph.prototype.isValidTarget = function(cell)
+{
+ return this.isValidSource(cell);
+};
+
+/**
+ * Function: isValidConnection
+ *
+ * Returns true if the given target cell is a valid target for source.
+ * This is a boolean implementation for not allowing connections between
+ * certain pairs of vertices and is called by <getEdgeValidationError>.
+ * This implementation returns true if <isValidSource> returns true for
+ * the source and <isValidTarget> returns true for the target.
+ *
+ * Parameters:
+ *
+ * source - <mxCell> that represents the source cell.
+ * target - <mxCell> that represents the target cell.
+ */
+mxGraph.prototype.isValidConnection = function(source, target)
+{
+ return this.isValidSource(source) && this.isValidTarget(target);
+};
+
+/**
+ * Function: setConnectable
+ *
+ * Specifies if the graph should allow new connections. This implementation
+ * updates <mxConnectionHandler.enabled> in <connectionHandler>.
+ *
+ * Parameters:
+ *
+ * connectable - Boolean indicating if new connections should be allowed.
+ */
+mxGraph.prototype.setConnectable = function(connectable)
+{
+ this.connectionHandler.setEnabled(connectable);
+};
+
+/**
+ * Function: isConnectable
+ *
+ * Returns true if the <connectionHandler> is enabled.
+ */
+mxGraph.prototype.isConnectable = function(connectable)
+{
+ return this.connectionHandler.isEnabled();
+};
+
+/**
+ * Function: setTooltips
+ *
+ * Specifies if tooltips should be enabled. This implementation updates
+ * <mxTooltipHandler.enabled> in <tooltipHandler>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean indicating if tooltips should be enabled.
+ */
+mxGraph.prototype.setTooltips = function (enabled)
+{
+ this.tooltipHandler.setEnabled(enabled);
+};
+
+/**
+ * Function: setPanning
+ *
+ * Specifies if panning should be enabled. This implementation updates
+ * <mxPanningHandler.panningEnabled> in <panningHandler>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean indicating if panning should be enabled.
+ */
+mxGraph.prototype.setPanning = function(enabled)
+{
+ this.panningHandler.panningEnabled = enabled;
+};
+
+/**
+ * Function: isEditing
+ *
+ * Returns true if the given cell is currently being edited.
+ * If no cell is specified then this returns true if any
+ * cell is currently being edited.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that should be checked.
+ */
+mxGraph.prototype.isEditing = function(cell)
+{
+ if (this.cellEditor != null)
+ {
+ var editingCell = this.cellEditor.getEditingCell();
+
+ return (cell == null) ?
+ editingCell != null :
+ cell == editingCell;
+ }
+
+ return false;
+};
+
+/**
+ * Function: isAutoSizeCell
+ *
+ * Returns true if the size of the given cell should automatically be
+ * updated after a change of the label. This implementation returns
+ * <autoSizeCells> or checks if the cell style does specify
+ * <mxConstants.STYLE_AUTOSIZE> to be 1.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that should be resized.
+ */
+mxGraph.prototype.isAutoSizeCell = function(cell)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.isAutoSizeCells() || style[mxConstants.STYLE_AUTOSIZE] == 1;
+};
+
+/**
+ * Function: isAutoSizeCells
+ *
+ * Returns <autoSizeCells>.
+ */
+mxGraph.prototype.isAutoSizeCells = function()
+{
+ return this.autoSizeCells;
+};
+
+/**
+ * Function: setAutoSizeCells
+ *
+ * Specifies if cell sizes should be automatically updated after a label
+ * change. This implementation sets <autoSizeCells> to the given parameter.
+ *
+ * Parameters:
+ *
+ * value - Boolean indicating if cells should be resized
+ * automatically.
+ */
+mxGraph.prototype.setAutoSizeCells = function(value)
+{
+ this.autoSizeCells = value;
+};
+
+/**
+ * Function: isExtendParent
+ *
+ * Returns true if the parent of the given cell should be extended if the
+ * child has been resized so that it overlaps the parent. This
+ * implementation returns <isExtendParents> if the cell is not an edge.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that has been resized.
+ */
+mxGraph.prototype.isExtendParent = function(cell)
+{
+ return !this.getModel().isEdge(cell) && this.isExtendParents();
+};
+
+/**
+ * Function: isExtendParents
+ *
+ * Returns <extendParents>.
+ */
+mxGraph.prototype.isExtendParents = function()
+{
+ return this.extendParents;
+};
+
+/**
+ * Function: setExtendParents
+ *
+ * Sets <extendParents>.
+ *
+ * Parameters:
+ *
+ * value - New boolean value for <extendParents>.
+ */
+mxGraph.prototype.setExtendParents = function(value)
+{
+ this.extendParents = value;
+};
+
+/**
+ * Function: isExtendParentsOnAdd
+ *
+ * Returns <extendParentsOnAdd>.
+ */
+mxGraph.prototype.isExtendParentsOnAdd = function()
+{
+ return this.extendParentsOnAdd;
+};
+
+/**
+ * Function: setExtendParentsOnAdd
+ *
+ * Sets <extendParentsOnAdd>.
+ *
+ * Parameters:
+ *
+ * value - New boolean value for <extendParentsOnAdd>.
+ */
+mxGraph.prototype.setExtendParentsOnAdd = function(value)
+{
+ this.extendParentsOnAdd = value;
+};
+
+/**
+ * Function: isConstrainChild
+ *
+ * Returns true if the given cell should be kept inside the bounds of its
+ * parent according to the rules defined by <getOverlap> and
+ * <isAllowOverlapParent>. This implementation returns false for all children
+ * of edges and <isConstrainChildren> otherwise.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that should be constrained.
+ */
+mxGraph.prototype.isConstrainChild = function(cell)
+{
+ return this.isConstrainChildren() && !this.getModel().isEdge(this.getModel().getParent(cell));
+
+};
+
+/**
+ * Function: isConstrainChildren
+ *
+ * Returns <constrainChildren>.
+ */
+mxGraph.prototype.isConstrainChildren = function()
+{
+ return this.constrainChildren;
+};
+
+/**
+ * Function: setConstrainChildren
+ *
+ * Sets <constrainChildren>.
+ */
+mxGraph.prototype.setConstrainChildren = function(value)
+{
+ this.constrainChildren = value;
+};
+
+/**
+ * Function: isConstrainChildren
+ *
+ * Returns <allowNegativeCoordinates>.
+ */
+mxGraph.prototype.isAllowNegativeCoordinates = function()
+{
+ return this.allowNegativeCoordinates;
+};
+
+/**
+ * Function: setConstrainChildren
+ *
+ * Sets <allowNegativeCoordinates>.
+ */
+mxGraph.prototype.setAllowNegativeCoordinates = function(value)
+{
+ this.allowNegativeCoordinates = value;
+};
+
+/**
+ * Function: getOverlap
+ *
+ * Returns a decimal number representing the amount of the width and height
+ * of the given cell that is allowed to overlap its parent. A value of 0
+ * means all children must stay inside the parent, 1 means the child is
+ * allowed to be placed outside of the parent such that it touches one of
+ * the parents sides. If <isAllowOverlapParent> returns false for the given
+ * cell, then this method returns 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the overlap ratio should be returned.
+ */
+mxGraph.prototype.getOverlap = function(cell)
+{
+ return (this.isAllowOverlapParent(cell)) ? this.defaultOverlap : 0;
+};
+
+/**
+ * Function: isAllowOverlapParent
+ *
+ * Returns true if the given cell is allowed to be placed outside of the
+ * parents area.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the child to be checked.
+ */
+mxGraph.prototype.isAllowOverlapParent = function(cell)
+{
+ return false;
+};
+
+/**
+ * Function: getFoldableCells
+ *
+ * Returns the cells which are movable in the given array of cells.
+ */
+mxGraph.prototype.getFoldableCells = function(cells, collapse)
+{
+ return this.model.filterCells(cells, mxUtils.bind(this, function(cell)
+ {
+ return this.isCellFoldable(cell, collapse);
+ }));
+};
+
+/**
+ * Function: isCellFoldable
+ *
+ * Returns true if the given cell is foldable. This implementation
+ * returns true if the cell has at least one child and its style
+ * does not specify <mxConstants.STYLE_FOLDABLE> to be 0.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose foldable state should be returned.
+ */
+mxGraph.prototype.isCellFoldable = function(cell, collapse)
+{
+ var state = this.view.getState(cell);
+ var style = (state != null) ? state.style : this.getCellStyle(cell);
+
+ return this.model.getChildCount(cell) > 0 && style[mxConstants.STYLE_FOLDABLE] != 0;
+};
+
+/**
+ * Function: isValidDropTarget
+ *
+ * Returns true if the given cell is a valid drop target for the specified
+ * cells. If the given cell is an edge, then <isSplitDropTarget> is used,
+ * else <isParentDropTarget> is used to compute the return value.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that represents the possible drop target.
+ * cells - <mxCells> that should be dropped into the target.
+ * evt - Mouseevent that triggered the invocation.
+ */
+mxGraph.prototype.isValidDropTarget = function(cell, cells, evt)
+{
+ return cell != null && ((this.isSplitEnabled() &&
+ this.isSplitTarget(cell, cells, evt)) || (!this.model.isEdge(cell) &&
+ (this.isSwimlane(cell) || (this.model.getChildCount(cell) > 0 &&
+ !this.isCellCollapsed(cell)))));
+};
+
+/**
+ * Function: isSplitTarget
+ *
+ * Returns true if the given edge may be splitted into two edges with the
+ * given cell as a new terminal between the two.
+ *
+ * Parameters:
+ *
+ * target - <mxCell> that represents the edge to be splitted.
+ * cells - <mxCells> that should split the edge.
+ * evt - Mouseevent that triggered the invocation.
+ */
+mxGraph.prototype.isSplitTarget = function(target, cells, evt)
+{
+ if (this.model.isEdge(target) && cells != null && cells.length == 1 &&
+ this.isCellConnectable(cells[0]) && this.getEdgeValidationError(target,
+ this.model.getTerminal(target, true), cells[0]) == null)
+ {
+ var src = this.model.getTerminal(target, true);
+ var trg = this.model.getTerminal(target, false);
+
+ return (!this.model.isAncestor(cells[0], src) &&
+ !this.model.isAncestor(cells[0], trg));
+ }
+
+ return false;
+};
+
+/**
+ * Function: getDropTarget
+ *
+ * Returns the given cell if it is a drop target for the given cells or the
+ * nearest ancestor that may be used as a drop target for the given cells.
+ * If the given array contains a swimlane and <swimlaneNesting> is false
+ * then this always returns null. If no cell is given, then the bottommost
+ * swimlane at the location of the given event is returned.
+ *
+ * This function should only be used if <isDropEnabled> returns true.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> which are to be dropped onto the target.
+ * evt - Mouseevent for the drag and drop.
+ * cell - <mxCell> that is under the mousepointer.
+ */
+mxGraph.prototype.getDropTarget = function(cells, evt, cell)
+{
+ if (!this.isSwimlaneNesting())
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.isSwimlane(cells[i]))
+ {
+ return null;
+ }
+ }
+ }
+
+ var pt = mxUtils.convertPoint(this.container,
+ mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+ pt.x -= this.panDx;
+ pt.y -= this.panDy;
+ var swimlane = this.getSwimlaneAt(pt.x, pt.y);
+
+ if (cell == null)
+ {
+ cell = swimlane;
+ }
+ else if (swimlane != null)
+ {
+ // Checks if the cell is an ancestor of the swimlane
+ // under the mouse and uses the swimlane in that case
+ var tmp = this.model.getParent(swimlane);
+
+ while (tmp != null && this.isSwimlane(tmp) && tmp != cell)
+ {
+ tmp = this.model.getParent(tmp);
+ }
+
+ if (tmp == cell)
+ {
+ cell = swimlane;
+ }
+ }
+
+ while (cell != null && !this.isValidDropTarget(cell, cells, evt) &&
+ !this.model.isLayer(cell))
+ {
+ cell = this.model.getParent(cell);
+ }
+
+ return (!this.model.isLayer(cell) && mxUtils.indexOf(cells, cell) < 0) ? cell : null;
+};
+
+/**
+ * Group: Cell retrieval
+ */
+
+/**
+ * Function: getDefaultParent
+ *
+ * Returns <defaultParent> or <mxGraphView.currentRoot> or the first child
+ * child of <mxGraphModel.root> if both are null. The value returned by
+ * this function should be used as the parent for new cells (aka default
+ * layer).
+ */
+mxGraph.prototype.getDefaultParent = function()
+{
+ var parent = this.defaultParent;
+
+ if (parent == null)
+ {
+ parent = this.getCurrentRoot();
+
+ if (parent == null)
+ {
+ var root = this.model.getRoot();
+ parent = this.model.getChildAt(root, 0);
+ }
+ }
+
+ return parent;
+};
+
+/**
+ * Function: setDefaultParent
+ *
+ * Sets the <defaultParent> to the given cell. Set this to null to return
+ * the first child of the root in getDefaultParent.
+ */
+mxGraph.prototype.setDefaultParent = function(cell)
+{
+ this.defaultParent = cell;
+};
+
+/**
+ * Function: getSwimlane
+ *
+ * Returns the nearest ancestor of the given cell which is a swimlane, or
+ * the given cell, if it is itself a swimlane.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the ancestor swimlane should be returned.
+ */
+mxGraph.prototype.getSwimlane = function(cell)
+{
+ while (cell != null && !this.isSwimlane(cell))
+ {
+ cell = this.model.getParent(cell);
+ }
+
+ return cell;
+};
+
+/**
+ * Function: getSwimlaneAt
+ *
+ * Returns the bottom-most swimlane that intersects the given point (x, y)
+ * in the cell hierarchy that starts at the given parent.
+ *
+ * Parameters:
+ *
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ * parent - <mxCell> that should be used as the root of the recursion.
+ * Default is <defaultParent>.
+ */
+mxGraph.prototype.getSwimlaneAt = function (x, y, parent)
+{
+ parent = parent || this.getDefaultParent();
+
+ if (parent != null)
+ {
+ var childCount = this.model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.model.getChildAt(parent, i);
+ var result = this.getSwimlaneAt(x, y, child);
+
+ if (result != null)
+ {
+ return result;
+ }
+ else if (this.isSwimlane(child))
+ {
+ var state = this.view.getState(child);
+
+ if (this.intersects(state, x, y))
+ {
+ return child;
+ }
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: getCellAt
+ *
+ * Returns the bottom-most cell that intersects the given point (x, y) in
+ * the cell hierarchy starting at the given parent. This will also return
+ * swimlanes if the given location intersects the content area of the
+ * swimlane. If this is not desired, then the <hitsSwimlaneContent> may be
+ * used if the returned cell is a swimlane to determine if the location
+ * is inside the content area or on the actual title of the swimlane.
+ *
+ * Parameters:
+ *
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ * parent - <mxCell> that should be used as the root of the recursion.
+ * Default is <defaultParent>.
+ * vertices - Optional boolean indicating if vertices should be returned.
+ * Default is true.
+ * edges - Optional boolean indicating if edges should be returned. Default
+ * is true.
+ */
+mxGraph.prototype.getCellAt = function(x, y, parent, vertices, edges)
+{
+ vertices = (vertices != null) ? vertices : true;
+ edges = (edges != null) ? edges : true;
+ parent = (parent != null) ? parent : this.getDefaultParent();
+
+ if (parent != null)
+ {
+ var childCount = this.model.getChildCount(parent);
+
+ for (var i = childCount - 1; i >= 0; i--)
+ {
+ var cell = this.model.getChildAt(parent, i);
+ var result = this.getCellAt(x, y, cell, vertices, edges);
+
+ if (result != null)
+ {
+ return result;
+ }
+ else if (this.isCellVisible(cell) && (edges && this.model.isEdge(cell) ||
+ vertices && this.model.isVertex(cell)))
+ {
+ var state = this.view.getState(cell);
+
+ if (this.intersects(state, x, y))
+ {
+ return cell;
+ }
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: intersects
+ *
+ * Returns the bottom-most cell that intersects the given point (x, y) in
+ * the cell hierarchy that starts at the given parent.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the cell state.
+ * x - X-coordinate of the location to be checked.
+ * y - Y-coordinate of the location to be checked.
+ */
+mxGraph.prototype.intersects = function(state, x, y)
+{
+ if (state != null)
+ {
+ var pts = state.absolutePoints;
+
+ if (pts != null)
+ {
+ var t2 = this.tolerance * this.tolerance;
+
+ var pt = pts[0];
+
+ for (var i = 1; i<pts.length; i++)
+ {
+ var next = pts[i];
+ var dist = mxUtils.ptSegDistSq(
+ pt.x, pt.y, next.x, next.y, x, y);
+
+ if (dist <= t2)
+ {
+ return true;
+ }
+
+ pt = next;
+ }
+ }
+ else if (mxUtils.contains(state, x, y))
+ {
+ return true;
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Function: hitsSwimlaneContent
+ *
+ * Returns true if the given coordinate pair is inside the content
+ * are of the given swimlane.
+ *
+ * Parameters:
+ *
+ * swimlane - <mxCell> that specifies the swimlane.
+ * x - X-coordinate of the mouse event.
+ * y - Y-coordinate of the mouse event.
+ */
+mxGraph.prototype.hitsSwimlaneContent = function(swimlane, x, y)
+{
+ var state = this.getView().getState(swimlane);
+ var size = this.getStartSize(swimlane);
+
+ if (state != null)
+ {
+ var scale = this.getView().getScale();
+ x -= state.x;
+ y -= state.y;
+
+ if (size.width > 0 && x > 0 && x > size.width * scale)
+ {
+ return true;
+ }
+ else if (size.height > 0 && y > 0 && y > size.height * scale)
+ {
+ return true;
+ }
+ }
+
+ return false;
+};
+
+/**
+ * Function: getChildVertices
+ *
+ * Returns the visible child vertices of the given parent.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose children should be returned.
+ */
+mxGraph.prototype.getChildVertices = function(parent)
+{
+ return this.getChildCells(parent, true, false);
+};
+
+/**
+ * Function: getChildEdges
+ *
+ * Returns the visible child edges of the given parent.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose child vertices should be returned.
+ */
+mxGraph.prototype.getChildEdges = function(parent)
+{
+ return this.getChildCells(parent, false, true);
+};
+
+/**
+ * Function: getChildCells
+ *
+ * Returns the visible child vertices or edges in the given parent. If
+ * vertices and edges is false, then all children are returned.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose children should be returned.
+ * vertices - Optional boolean that specifies if child vertices should
+ * be returned. Default is false.
+ * edges - Optional boolean that specifies if child edges should
+ * be returned. Default is false.
+ */
+mxGraph.prototype.getChildCells = function(parent, vertices, edges)
+{
+ parent = (parent != null) ? parent : this.getDefaultParent();
+ vertices = (vertices != null) ? vertices : false;
+ edges = (edges != null) ? edges : false;
+
+ var cells = this.model.getChildCells(parent, vertices, edges);
+ var result = [];
+
+ // Filters out the non-visible child cells
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.isCellVisible(cells[i]))
+ {
+ result.push(cells[i]);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getConnections
+ *
+ * Returns all visible edges connected to the given cell without loops.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose connections should be returned.
+ * parent - Optional parent of the opposite end for a connection to be
+ * returned.
+ */
+mxGraph.prototype.getConnections = function(cell, parent)
+{
+ return this.getEdges(cell, parent, true, true, false);
+};
+
+/**
+ * Function: getIncomingEdges
+ *
+ * Returns the visible incoming edges for the given cell. If the optional
+ * parent argument is specified, then only child edges of the given parent
+ * are returned.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose incoming edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ */
+mxGraph.prototype.getIncomingEdges = function(cell, parent)
+{
+ return this.getEdges(cell, parent, true, false, false);
+};
+
+/**
+ * Function: getOutgoingEdges
+ *
+ * Returns the visible outgoing edges for the given cell. If the optional
+ * parent argument is specified, then only child edges of the given parent
+ * are returned.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose outgoing edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ */
+mxGraph.prototype.getOutgoingEdges = function(cell, parent)
+{
+ return this.getEdges(cell, parent, false, true, false);
+};
+
+/**
+ * Function: getEdges
+ *
+ * Returns the incoming and/or outgoing edges for the given cell.
+ * If the optional parent argument is specified, then only edges are returned
+ * where the opposite is in the given parent cell. If at least one of incoming
+ * or outgoing is true, then loops are ignored, if both are false, then all
+ * edges connected to the given cell are returned including loops.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> whose edges should be returned.
+ * parent - Optional parent of the opposite end for an edge to be
+ * returned.
+ * incoming - Optional boolean that specifies if incoming edges should
+ * be included in the result. Default is true.
+ * outgoing - Optional boolean that specifies if outgoing edges should
+ * be included in the result. Default is true.
+ * includeLoops - Optional boolean that specifies if loops should be
+ * included in the result. Default is true.
+ * recurse - Optional boolean the specifies if the parent specified only
+ * need be an ancestral parent, true, or the direct parent, false.
+ * Default is false
+ */
+mxGraph.prototype.getEdges = function(cell, parent, incoming, outgoing, includeLoops, recurse)
+{
+ incoming = (incoming != null) ? incoming : true;
+ outgoing = (outgoing != null) ? outgoing : true;
+ includeLoops = (includeLoops != null) ? includeLoops : true;
+ recurse = (recurse != null) ? recurse : false;
+
+ var edges = [];
+ var isCollapsed = this.isCellCollapsed(cell);
+ var childCount = this.model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.model.getChildAt(cell, i);
+
+ if (isCollapsed || !this.isCellVisible(child))
+ {
+ edges = edges.concat(this.model.getEdges(child, incoming, outgoing));
+ }
+ }
+
+ edges = edges.concat(this.model.getEdges(cell, incoming, outgoing));
+ var result = [];
+
+ for (var i = 0; i < edges.length; i++)
+ {
+ var state = this.view.getState(edges[i]);
+
+ var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+ var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+
+ if ((includeLoops && source == target) || ((source != target) && ((incoming &&
+ target == cell && (parent == null || this.isValidAncestor(source, parent, recurse))) ||
+ (outgoing && source == cell && (parent == null ||
+ this.isValidAncestor(target, parent, recurse))))))
+ {
+ result.push(edges[i]);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: isValidAncestor
+ *
+ * Returns whether or not the specified parent is a valid
+ * ancestor of the specified cell, either direct or indirectly
+ * based on whether ancestor recursion is enabled.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> the possible child cell
+ * parent - <mxCell> the possible parent cell
+ * recurse - boolean whether or not to recurse the child ancestors
+ */
+mxGraph.prototype.isValidAncestor = function(cell, parent, recurse)
+{
+ return (recurse ? this.model.isAncestor(parent, cell) : this.model
+ .getParent(cell) == parent);
+};
+
+/**
+ * Function: getOpposites
+ *
+ * Returns all distinct visible opposite cells for the specified terminal
+ * on the given edges.
+ *
+ * Parameters:
+ *
+ * edges - Array of <mxCells> that contains the edges whose opposite
+ * terminals should be returned.
+ * terminal - Terminal that specifies the end whose opposite should be
+ * returned.
+ * source - Optional boolean that specifies if source terminals should be
+ * included in the result. Default is true.
+ * targets - Optional boolean that specifies if targer terminals should be
+ * included in the result. Default is true.
+ */
+mxGraph.prototype.getOpposites = function(edges, terminal, sources, targets)
+{
+ sources = (sources != null) ? sources : true;
+ targets = (targets != null) ? targets : true;
+
+ var terminals = [];
+
+ // Implements set semantic on the terminals array using a string
+ // representation of each cell in an associative array lookup
+ var hash = new Object();
+
+ if (edges != null)
+ {
+ for (var i = 0; i < edges.length; i++)
+ {
+ var state = this.view.getState(edges[i]);
+
+ var source = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+ var target = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+
+ // Checks if the terminal is the source of the edge and if the
+ // target should be stored in the result
+ if (source == terminal && target != null &&
+ target != terminal && targets)
+ {
+ var id = mxCellPath.create(target);
+
+ if (hash[id] == null)
+ {
+ hash[id] = target;
+ terminals.push(target);
+ }
+ }
+
+ // Checks if the terminal is the taget of the edge and if the
+ // source should be stored in the result
+ else if (target == terminal && source != null &&
+ source != terminal && sources)
+ {
+ var id = mxCellPath.create(source);
+
+ if (hash[id] == null)
+ {
+ hash[id] = source;
+ terminals.push(source);
+ }
+ }
+ }
+ }
+
+ return terminals;
+};
+
+/**
+ * Function: getEdgesBetween
+ *
+ * Returns the edges between the given source and target. This takes into
+ * account collapsed and invisible cells and returns the connected edges
+ * as displayed on the screen.
+ *
+ * Parameters:
+ *
+ * source -
+ * target -
+ * directed -
+ */
+mxGraph.prototype.getEdgesBetween = function(source, target, directed)
+{
+ directed = (directed != null) ? directed : false;
+ var edges = this.getEdges(source);
+ var result = [];
+
+ // Checks if the edge is connected to the correct
+ // cell and returns the first match
+ for (var i = 0; i < edges.length; i++)
+ {
+ var state = this.view.getState(edges[i]);
+
+ var src = (state != null) ? state.getVisibleTerminal(true) : this.view.getVisibleTerminal(edges[i], true);
+ var trg = (state != null) ? state.getVisibleTerminal(false) : this.view.getVisibleTerminal(edges[i], false);
+
+ if ((src == source && trg == target) || (!directed && src == target && trg == source))
+ {
+ result.push(edges[i]);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getPointForEvent
+ *
+ * Returns an <mxPoint> representing the given event in the unscaled,
+ * non-translated coordinate space of <container> and applies the grid.
+ *
+ * Parameters:
+ *
+ * evt - Mousevent that contains the mouse pointer location.
+ * addOffset - Optional boolean that specifies if the position should be
+ * offset by half of the <gridSize>. Default is true.
+ */
+ mxGraph.prototype.getPointForEvent = function(evt, addOffset)
+ {
+ var p = mxUtils.convertPoint(this.container,
+ mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+
+ var s = this.view.scale;
+ var tr = this.view.translate;
+ var off = (addOffset != false) ? this.gridSize / 2 : 0;
+
+ p.x = this.snap(p.x / s - tr.x - off);
+ p.y = this.snap(p.y / s - tr.y - off);
+
+ return p;
+ };
+
+/**
+ * Function: getCells
+ *
+ * Returns the children of the given parent that are contained in the given
+ * rectangle (x, y, width, height). The result is added to the optional
+ * result array, which is returned from the function. If no result array
+ * is specified then a new array is created and returned.
+ *
+ * Parameters:
+ *
+ * x - X-coordinate of the rectangle.
+ * y - Y-coordinate of the rectangle.
+ * width - Width of the rectangle.
+ * height - Height of the rectangle.
+ * parent - <mxCell> whose children should be checked. Default is
+ * <defaultParent>.
+ * result - Optional array to store the result in.
+ */
+mxGraph.prototype.getCells = function(x, y, width, height, parent, result)
+{
+ result = (result != null) ? result : [];
+
+ if (width > 0 || height > 0)
+ {
+ var right = x + width;
+ var bottom = y + height;
+
+ parent = parent || this.getDefaultParent();
+
+ if (parent != null)
+ {
+ var childCount = this.model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var cell = this.model.getChildAt(parent, i);
+ var state = this.view.getState(cell);
+
+ if (this.isCellVisible(cell) && state != null)
+ {
+ if (state.x >= x && state.y >= y &&
+ state.x + state.width <= right &&
+ state.y + state.height <= bottom)
+ {
+ result.push(cell);
+ }
+ else
+ {
+ this.getCells(x, y, width, height, cell, result);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getCellsBeyond
+ *
+ * Returns the children of the given parent that are contained in the
+ * halfpane from the given point (x0, y0) rightwards and/or downwards
+ * depending on rightHalfpane and bottomHalfpane.
+ *
+ * Parameters:
+ *
+ * x0 - X-coordinate of the origin.
+ * y0 - Y-coordinate of the origin.
+ * parent - Optional <mxCell> whose children should be checked. Default is
+ * <defaultParent>.
+ * rightHalfpane - Boolean indicating if the cells in the right halfpane
+ * from the origin should be returned.
+ * bottomHalfpane - Boolean indicating if the cells in the bottom halfpane
+ * from the origin should be returned.
+ */
+mxGraph.prototype.getCellsBeyond = function(x0, y0, parent, rightHalfpane, bottomHalfpane)
+{
+ var result = [];
+
+ if (rightHalfpane || bottomHalfpane)
+ {
+ if (parent == null)
+ {
+ parent = this.getDefaultParent();
+ }
+
+ if (parent != null)
+ {
+ var childCount = this.model.getChildCount(parent);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = this.model.getChildAt(parent, i);
+ var state = this.view.getState(child);
+
+ if (this.isCellVisible(child) && state != null)
+ {
+ if ((!rightHalfpane ||
+ state.x >= x0) &&
+ (!bottomHalfpane ||
+ state.y >= y0))
+ {
+ result.push(child);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: findTreeRoots
+ *
+ * Returns all children in the given parent which do not have incoming
+ * edges. If the result is empty then the with the greatest difference
+ * between incoming and outgoing edges is returned.
+ *
+ * Parameters:
+ *
+ * parent - <mxCell> whose children should be checked.
+ * isolate - Optional boolean that specifies if edges should be ignored if
+ * the opposite end is not a child of the given parent cell. Default is
+ * false.
+ * invert - Optional boolean that specifies if outgoing or incoming edges
+ * should be counted for a tree root. If false then outgoing edges will be
+ * counted. Default is false.
+ */
+mxGraph.prototype.findTreeRoots = function(parent, isolate, invert)
+{
+ isolate = (isolate != null) ? isolate : false;
+ invert = (invert != null) ? invert : false;
+ var roots = [];
+
+ if (parent != null)
+ {
+ var model = this.getModel();
+ var childCount = model.getChildCount(parent);
+ var best = null;
+ var maxDiff = 0;
+
+ for (var i=0; i<childCount; i++)
+ {
+ var cell = model.getChildAt(parent, i);
+
+ if (this.model.isVertex(cell) && this.isCellVisible(cell))
+ {
+ var conns = this.getConnections(cell, (isolate) ? parent : null);
+ var fanOut = 0;
+ var fanIn = 0;
+
+ for (var j = 0; j < conns.length; j++)
+ {
+ var src = this.view.getVisibleTerminal(conns[j], true);
+
+ if (src == cell)
+ {
+ fanOut++;
+ }
+ else
+ {
+ fanIn++;
+ }
+ }
+
+ if ((invert && fanOut == 0 && fanIn > 0) ||
+ (!invert && fanIn == 0 && fanOut > 0))
+ {
+ roots.push(cell);
+ }
+
+ var diff = (invert) ? fanIn - fanOut : fanOut - fanIn;
+
+ if (diff > maxDiff)
+ {
+ maxDiff = diff;
+ best = cell;
+ }
+ }
+ }
+
+ if (roots.length == 0 && best != null)
+ {
+ roots.push(best);
+ }
+ }
+
+ return roots;
+};
+
+/**
+ * Function: traverse
+ *
+ * Traverses the (directed) graph invoking the given function for each
+ * visited vertex and edge. The function is invoked with the current vertex
+ * and the incoming edge as a parameter. This implementation makes sure
+ * each vertex is only visited once. The function may return false if the
+ * traversal should stop at the given vertex.
+ *
+ * Example:
+ *
+ * (code)
+ * mxLog.show();
+ * var cell = graph.getSelectionCell();
+ * graph.traverse(cell, false, function(vertex, edge)
+ * {
+ * mxLog.debug(graph.getLabel(vertex));
+ * });
+ * (end)
+ *
+ * Parameters:
+ *
+ * vertex - <mxCell> that represents the vertex where the traversal starts.
+ * directed - Optional boolean indicating if edges should only be traversed
+ * from source to target. Default is true.
+ * func - Visitor function that takes the current vertex and the incoming
+ * edge as arguments. The traversal stops if the function returns false.
+ * edge - Optional <mxCell> that represents the incoming edge. This is
+ * null for the first step of the traversal.
+ * visited - Optional array of cell paths for the visited cells.
+ */
+mxGraph.prototype.traverse = function(vertex, directed, func, edge, visited)
+{
+ if (func != null && vertex != null)
+ {
+ directed = (directed != null) ? directed : true;
+ visited = visited || [];
+ var id = mxCellPath.create(vertex);
+
+ if (visited[id] == null)
+ {
+ visited[id] = vertex;
+ var result = func(vertex, edge);
+
+ if (result == null || result)
+ {
+ var edgeCount = this.model.getEdgeCount(vertex);
+
+ if (edgeCount > 0)
+ {
+ for (var i = 0; i < edgeCount; i++)
+ {
+ var e = this.model.getEdgeAt(vertex, i);
+ var isSource = this.model.getTerminal(e, true) == vertex;
+
+ if (!directed || isSource)
+ {
+ var next = this.model.getTerminal(e, !isSource);
+ this.traverse(next, directed, func, e, visited);
+ }
+ }
+ }
+ }
+ }
+ }
+};
+
+/**
+ * Group: Selection
+ */
+
+/**
+ * Function: isCellSelected
+ *
+ * Returns true if the given cell is selected.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the selection state should be returned.
+ */
+mxGraph.prototype.isCellSelected = function(cell)
+{
+ return this.getSelectionModel().isSelected(cell);
+};
+
+/**
+ * Function: isSelectionEmpty
+ *
+ * Returns true if the selection is empty.
+ */
+mxGraph.prototype.isSelectionEmpty = function()
+{
+ return this.getSelectionModel().isEmpty();
+};
+
+/**
+ * Function: clearSelection
+ *
+ * Clears the selection using <mxGraphSelectionModel.clear>.
+ */
+mxGraph.prototype.clearSelection = function()
+{
+ return this.getSelectionModel().clear();
+};
+
+/**
+ * Function: getSelectionCount
+ *
+ * Returns the number of selected cells.
+ */
+mxGraph.prototype.getSelectionCount = function()
+{
+ return this.getSelectionModel().cells.length;
+};
+
+/**
+ * Function: getSelectionCell
+ *
+ * Returns the first cell from the array of selected <mxCells>.
+ */
+mxGraph.prototype.getSelectionCell = function()
+{
+ return this.getSelectionModel().cells[0];
+};
+
+/**
+ * Function: getSelectionCells
+ *
+ * Returns the array of selected <mxCells>.
+ */
+mxGraph.prototype.getSelectionCells = function()
+{
+ return this.getSelectionModel().cells.slice();
+};
+
+/**
+ * Function: setSelectionCell
+ *
+ * Sets the selection cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be selected.
+ */
+mxGraph.prototype.setSelectionCell = function(cell)
+{
+ this.getSelectionModel().setCell(cell);
+};
+
+/**
+ * Function: setSelectionCells
+ *
+ * Sets the selection cell.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be selected.
+ */
+mxGraph.prototype.setSelectionCells = function(cells)
+{
+ this.getSelectionModel().setCells(cells);
+};
+
+/**
+ * Function: addSelectionCell
+ *
+ * Adds the given cell to the selection.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be add to the selection.
+ */
+mxGraph.prototype.addSelectionCell = function(cell)
+{
+ this.getSelectionModel().addCell(cell);
+};
+
+/**
+ * Function: addSelectionCells
+ *
+ * Adds the given cells to the selection.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be added to the selection.
+ */
+mxGraph.prototype.addSelectionCells = function(cells)
+{
+ this.getSelectionModel().addCells(cells);
+};
+
+/**
+ * Function: removeSelectionCell
+ *
+ * Removes the given cell from the selection.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be removed from the selection.
+ */
+mxGraph.prototype.removeSelectionCell = function(cell)
+{
+ this.getSelectionModel().removeCell(cell);
+};
+
+/**
+ * Function: removeSelectionCells
+ *
+ * Removes the given cells from the selection.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be removed from the selection.
+ */
+mxGraph.prototype.removeSelectionCells = function(cells)
+{
+ this.getSelectionModel().removeCells(cells);
+};
+
+/**
+ * Function: selectRegion
+ *
+ * Selects and returns the cells inside the given rectangle for the
+ * specified event.
+ *
+ * Parameters:
+ *
+ * rect - <mxRectangle> that represents the region to be selected.
+ * evt - Mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectRegion = function(rect, evt)
+{
+ var cells = this.getCells(rect.x, rect.y, rect.width, rect.height);
+ this.selectCellsForEvent(cells, evt);
+
+ return cells;
+};
+
+/**
+ * Function: selectNextCell
+ *
+ * Selects the next cell.
+ */
+mxGraph.prototype.selectNextCell = function()
+{
+ this.selectCell(true);
+};
+
+/**
+ * Function: selectPreviousCell
+ *
+ * Selects the previous cell.
+ */
+mxGraph.prototype.selectPreviousCell = function()
+{
+ this.selectCell();
+};
+
+/**
+ * Function: selectParentCell
+ *
+ * Selects the parent cell.
+ */
+mxGraph.prototype.selectParentCell = function()
+{
+ this.selectCell(false, true);
+};
+
+/**
+ * Function: selectChildCell
+ *
+ * Selects the first child cell.
+ */
+mxGraph.prototype.selectChildCell = function()
+{
+ this.selectCell(false, false, true);
+};
+
+/**
+ * Function: selectCell
+ *
+ * Selects the next, parent, first child or previous cell, if all arguments
+ * are false.
+ *
+ * Parameters:
+ *
+ * isNext - Boolean indicating if the next cell should be selected.
+ * isParent - Boolean indicating if the parent cell should be selected.
+ * isChild - Boolean indicating if the first child cell should be selected.
+ */
+mxGraph.prototype.selectCell = function(isNext, isParent, isChild)
+{
+ var sel = this.selectionModel;
+ var cell = (sel.cells.length > 0) ? sel.cells[0] : null;
+
+ if (sel.cells.length > 1)
+ {
+ sel.clear();
+ }
+
+ var parent = (cell != null) ?
+ this.model.getParent(cell) :
+ this.getDefaultParent();
+
+ var childCount = this.model.getChildCount(parent);
+
+ if (cell == null && childCount > 0)
+ {
+ var child = this.model.getChildAt(parent, 0);
+ this.setSelectionCell(child);
+ }
+ else if ((cell == null || isParent) &&
+ this.view.getState(parent) != null &&
+ this.model.getGeometry(parent) != null)
+ {
+ if (this.getCurrentRoot() != parent)
+ {
+ this.setSelectionCell(parent);
+ }
+ }
+ else if (cell != null && isChild)
+ {
+ var tmp = this.model.getChildCount(cell);
+
+ if (tmp > 0)
+ {
+ var child = this.model.getChildAt(cell, 0);
+ this.setSelectionCell(child);
+ }
+ }
+ else if (childCount > 0)
+ {
+ var i = parent.getIndex(cell);
+
+ if (isNext)
+ {
+ i++;
+ var child = this.model.getChildAt(parent, i % childCount);
+ this.setSelectionCell(child);
+ }
+ else
+ {
+ i--;
+ var index = (i < 0) ? childCount - 1 : i;
+ var child = this.model.getChildAt(parent, index);
+ this.setSelectionCell(child);
+ }
+ }
+};
+
+/**
+ * Function: selectAll
+ *
+ * Selects all children of the given parent cell or the children of the
+ * default parent if no parent is specified. To select leaf vertices and/or
+ * edges use <selectCells>.
+ *
+ * Parameters:
+ *
+ * parent - Optional <mxCell> whose children should be selected.
+ * Default is <defaultParent>.
+ */
+mxGraph.prototype.selectAll = function(parent)
+{
+ parent = parent || this.getDefaultParent();
+
+ var children = this.model.getChildren(parent);
+
+ if (children != null)
+ {
+ this.setSelectionCells(children);
+ }
+};
+
+/**
+ * Function: selectVertices
+ *
+ * Select all vertices inside the given parent or the default parent.
+ */
+mxGraph.prototype.selectVertices = function(parent)
+{
+ this.selectCells(true, false, parent);
+};
+
+/**
+ * Function: selectVertices
+ *
+ * Select all vertices inside the given parent or the default parent.
+ */
+mxGraph.prototype.selectEdges = function(parent)
+{
+ this.selectCells(false, true, parent);
+};
+
+/**
+ * Function: selectCells
+ *
+ * Selects all vertices and/or edges depending on the given boolean
+ * arguments recursively, starting at the given parent or the default
+ * parent if no parent is specified. Use <selectAll> to select all cells.
+ *
+ * Parameters:
+ *
+ * vertices - Boolean indicating if vertices should be selected.
+ * edges - Boolean indicating if edges should be selected.
+ * parent - Optional <mxCell> that acts as the root of the recursion.
+ * Default is <defaultParent>.
+ */
+mxGraph.prototype.selectCells = function(vertices, edges, parent)
+{
+ parent = parent || this.getDefaultParent();
+
+ var filter = mxUtils.bind(this, function(cell)
+ {
+ return this.view.getState(cell) != null &&
+ this.model.getChildCount(cell) == 0 &&
+ ((this.model.isVertex(cell) && vertices) ||
+ (this.model.isEdge(cell) && edges));
+ });
+
+ var cells = this.model.filterDescendants(filter, parent);
+ this.setSelectionCells(cells);
+};
+
+/**
+ * Function: selectCellForEvent
+ *
+ * Selects the given cell by either adding it to the selection or
+ * replacing the selection depending on whether the given mouse event is a
+ * toggle event.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be selected.
+ * evt - Optional mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectCellForEvent = function(cell, evt)
+{
+ var isSelected = this.isCellSelected(cell);
+
+ if (this.isToggleEvent(evt))
+ {
+ if (isSelected)
+ {
+ this.removeSelectionCell(cell);
+ }
+ else
+ {
+ this.addSelectionCell(cell);
+ }
+ }
+ else if (!isSelected || this.getSelectionCount() != 1)
+ {
+ this.setSelectionCell(cell);
+ }
+};
+
+/**
+ * Function: selectCellsForEvent
+ *
+ * Selects the given cells by either adding them to the selection or
+ * replacing the selection depending on whether the given mouse event is a
+ * toggle event.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be selected.
+ * evt - Optional mouseevent that triggered the selection.
+ */
+mxGraph.prototype.selectCellsForEvent = function(cells, evt)
+{
+ if (this.isToggleEvent(evt))
+ {
+ this.addSelectionCells(cells);
+ }
+ else
+ {
+ this.setSelectionCells(cells);
+ }
+};
+
+/**
+ * Group: Selection state
+ */
+
+/**
+ * Function: createHandler
+ *
+ * Creates a new handler for the given cell state. This implementation
+ * returns a new <mxEdgeHandler> of the corresponding cell is an edge,
+ * otherwise it returns an <mxVertexHandler>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose handler should be created.
+ */
+mxGraph.prototype.createHandler = function(state)
+{
+ var result = null;
+
+ if (state != null)
+ {
+ if (this.model.isEdge(state.cell))
+ {
+ var style = this.view.getEdgeStyle(state);
+
+ if (this.isLoop(state) ||
+ style == mxEdgeStyle.ElbowConnector ||
+ style == mxEdgeStyle.SideToSide ||
+ style == mxEdgeStyle.TopToBottom)
+ {
+ result = new mxElbowEdgeHandler(state);
+ }
+ else if (style == mxEdgeStyle.SegmentConnector ||
+ style == mxEdgeStyle.OrthConnector)
+ {
+ result = new mxEdgeSegmentHandler(state);
+ }
+ else
+ {
+ result = new mxEdgeHandler(state);
+ }
+ }
+ else
+ {
+ result = new mxVertexHandler(state);
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Group: Graph events
+ */
+
+/**
+ * Function: addMouseListener
+ *
+ * Adds a listener to the graph event dispatch loop. The listener
+ * must implement the mouseDown, mouseMove and mouseUp methods
+ * as shown in the <mxMouseEvent> class.
+ *
+ * Parameters:
+ *
+ * listener - Listener to be added to the graph event listeners.
+ */
+mxGraph.prototype.addMouseListener = function(listener)
+{
+ if (this.mouseListeners == null)
+ {
+ this.mouseListeners = [];
+ }
+
+ this.mouseListeners.push(listener);
+};
+
+/**
+ * Function: removeMouseListener
+ *
+ * Removes the specified graph listener.
+ *
+ * Parameters:
+ *
+ * listener - Listener to be removed from the graph event listeners.
+ */
+mxGraph.prototype.removeMouseListener = function(listener)
+{
+ if (this.mouseListeners != null)
+ {
+ for (var i = 0; i < this.mouseListeners.length; i++)
+ {
+ if (this.mouseListeners[i] == listener)
+ {
+ this.mouseListeners.splice(i, 1);
+ break;
+ }
+ }
+ }
+};
+
+/**
+ * Function: updateMouseEvent
+ *
+ * Sets the graphX and graphY properties if the given <mxMouseEvent> if
+ * required.
+ */
+mxGraph.prototype.updateMouseEvent = function(me)
+{
+ if (me.graphX == null || me.graphY == null)
+ {
+ var pt = mxUtils.convertPoint(this.container, me.getX(), me.getY());
+
+ me.graphX = pt.x - this.panDx;
+ me.graphY = pt.y - this.panDy;
+ }
+};
+
+/**
+ * Function: fireMouseEvent
+ *
+ * Dispatches the given event in the graph event dispatch loop. Possible
+ * event names are <mxEvent.MOUSE_DOWN>, <mxEvent.MOUSE_MOVE> and
+ * <mxEvent.MOUSE_UP>. All listeners are invoked for all events regardless
+ * of the consumed state of the event.
+ *
+ * Parameters:
+ *
+ * evtName - String that specifies the type of event to be dispatched.
+ * me - <mxMouseEvent> to be fired.
+ * sender - Optional sender argument. Default is this.
+ */
+mxGraph.prototype.fireMouseEvent = function(evtName, me, sender)
+{
+ if (sender == null)
+ {
+ sender = this;
+ }
+
+ // Updates the graph coordinates in the event
+ this.updateMouseEvent(me);
+
+ // Makes sure we have a uniform event-sequence across all
+ // browsers for a double click. Since evt.detail == 2 is only
+ // available on Firefox we use the fact that each mousedown
+ // must be followed by a mouseup, all out-of-sync downs
+ // will be dropped silently.
+ if (evtName == mxEvent.MOUSE_DOWN)
+ {
+ this.isMouseDown = true;
+ }
+
+ // Detects and processes double taps for touch-based devices
+ // which do not have native double click events
+ if (mxClient.IS_TOUCH && this.doubleTapEnabled && evtName == mxEvent.MOUSE_DOWN)
+ {
+ var currentTime = new Date().getTime();
+
+ if (currentTime - this.lastTouchTime < this.doubleTapTimeout &&
+ Math.abs(this.lastTouchX - me.getX()) < this.doubleTapTolerance &&
+ Math.abs(this.lastTouchY - me.getY()) < this.doubleTapTolerance)
+ {
+ // FIXME: The actual editing should start on MOUSE_UP event but
+ // the detection of the double click should use the mouse_down event
+ // to make it consistent with behaviour in browser with mouse.
+ this.lastTouchTime = 0;
+ this.dblClick(me.getEvent(), me.getCell());
+
+ // Stop bubbling but do not consume to make sure the device
+ // can bring up the virtual keyboard for editing
+ me.getEvent().cancelBubble = true;
+ }
+ else
+ {
+ this.lastTouchX = me.getX();
+ this.lastTouchY = me.getY();
+ this.lastTouchTime = currentTime;
+ }
+ }
+
+ // Workaround for IE9 standards mode ignoring tolerance for double clicks
+ var noDoubleClick = me.getEvent().detail/*clickCount*/ != 2;
+
+ if (mxClient.IS_IE && document.compatMode == 'CSS1Compat')
+ {
+ if ((this.lastMouseX != null && Math.abs(this.lastMouseX - me.getX()) > this.doubleTapTolerance) ||
+ (this.lastMouseY != null && Math.abs(this.lastMouseY - me.getY()) > this.doubleTapTolerance))
+ {
+ noDoubleClick = true;
+ }
+
+ if (evtName == mxEvent.MOUSE_UP)
+ {
+ this.lastMouseX = me.getX();
+ this.lastMouseY = me.getY();
+ }
+ }
+
+ // Filters too many mouse ups when the mouse is down
+ if ((evtName != mxEvent.MOUSE_UP || this.isMouseDown) && noDoubleClick)
+ {
+ if (evtName == mxEvent.MOUSE_UP)
+ {
+ this.isMouseDown = false;
+ }
+
+ if (!this.isEditing() && (mxClient.IS_OP || mxClient.IS_SF || mxClient.IS_GC ||
+ (mxClient.IS_IE && mxClient.IS_SVG) || me.getEvent().target != this.container))
+ {
+ if (evtName == mxEvent.MOUSE_MOVE && this.isMouseDown && this.autoScroll)
+ {
+ this.scrollPointToVisible(me.getGraphX(), me.getGraphY(), this.autoExtend);
+ }
+
+ if (this.mouseListeners != null)
+ {
+ var args = [sender, me];
+
+ // Does not change returnValue in Opera
+ me.getEvent().returnValue = true;
+
+ for (var i = 0; i < this.mouseListeners.length; i++)
+ {
+ var l = this.mouseListeners[i];
+
+ if (evtName == mxEvent.MOUSE_DOWN)
+ {
+ l.mouseDown.apply(l, args);
+ }
+ else if (evtName == mxEvent.MOUSE_MOVE)
+ {
+ l.mouseMove.apply(l, args);
+ }
+ else if (evtName == mxEvent.MOUSE_UP)
+ {
+ l.mouseUp.apply(l, args);
+ }
+ }
+ }
+
+ // Invokes the click handler
+ if (evtName == mxEvent.MOUSE_UP)
+ {
+ this.click(me);
+ }
+ }
+ }
+ else if (evtName == mxEvent.MOUSE_UP)
+ {
+ this.isMouseDown = false;
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the graph and all its resources.
+ */
+mxGraph.prototype.destroy = function()
+{
+ if (!this.destroyed)
+ {
+ this.destroyed = true;
+
+ if (this.tooltipHandler != null)
+ {
+ this.tooltipHandler.destroy();
+ }
+
+ if (this.selectionCellsHandler != null)
+ {
+ this.selectionCellsHandler.destroy();
+ }
+
+ if (this.panningHandler != null)
+ {
+ this.panningHandler.destroy();
+ }
+
+ if (this.connectionHandler != null)
+ {
+ this.connectionHandler.destroy();
+ }
+
+ if (this.graphHandler != null)
+ {
+ this.graphHandler.destroy();
+ }
+
+ if (this.cellEditor != null)
+ {
+ this.cellEditor.destroy();
+ }
+
+ if (this.view != null)
+ {
+ this.view.destroy();
+ }
+
+ if (this.model != null && this.graphModelChangeListener != null)
+ {
+ this.model.removeListener(this.graphModelChangeListener);
+ this.graphModelChangeListener = null;
+ }
+
+ this.container = null;
+ }
+};
diff --git a/src/js/view/mxGraphSelectionModel.js b/src/js/view/mxGraphSelectionModel.js
new file mode 100644
index 0000000..5cd16a8
--- /dev/null
+++ b/src/js/view/mxGraphSelectionModel.js
@@ -0,0 +1,435 @@
+/**
+ * $Id: mxGraphSelectionModel.js,v 1.14 2011-11-25 10:16:08 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphSelectionModel
+ *
+ * Implements the selection model for a graph. Here is a listener that handles
+ * all removed selection cells.
+ *
+ * (code)
+ * graph.getSelectionModel().addListener(mxEvent.CHANGE, function(sender, evt)
+ * {
+ * var cells = evt.getProperty('added');
+ *
+ * for (var i = 0; i < cells.length; i++)
+ * {
+ * // Handle cells[i]...
+ * }
+ * });
+ * (end)
+ *
+ * Event: mxEvent.UNDO
+ *
+ * Fires after the selection was changed in <changeSelection>. The
+ * <code>edit</code> property contains the <mxUndoableEdit> which contains the
+ * <mxSelectionChange>.
+ *
+ * Event: mxEvent.CHANGE
+ *
+ * Fires after the selection changes by executing an <mxSelectionChange>. The
+ * <code>added</code> and <code>removed</code> properties contain arrays of
+ * cells that have been added to or removed from the selection, respectively.
+ *
+ * Constructor: mxGraphSelectionModel
+ *
+ * Constructs a new graph selection model for the given <mxGraph>.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxGraphSelectionModel(graph)
+{
+ this.graph = graph;
+ this.cells = [];
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraphSelectionModel.prototype = new mxEventSource();
+mxGraphSelectionModel.prototype.constructor = mxGraphSelectionModel;
+
+/**
+ * Variable: doneResource
+ *
+ * Specifies the resource key for the status message after a long operation.
+ * If the resource for this key does not exist then the value is used as
+ * the status message. Default is 'done'.
+ */
+mxGraphSelectionModel.prototype.doneResource = (mxClient.language != 'none') ? 'done' : '';
+
+/**
+ * Variable: updatingSelectionResource
+ *
+ * Specifies the resource key for the status message while the selection is
+ * being updated. If the resource for this key does not exist then the
+ * value is used as the status message. Default is 'updatingSelection'.
+ */
+mxGraphSelectionModel.prototype.updatingSelectionResource = (mxClient.language != 'none') ? 'updatingSelection' : '';
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphSelectionModel.prototype.graph = null;
+
+/**
+ * Variable: singleSelection
+ *
+ * Specifies if only one selected item at a time is allowed.
+ * Default is false.
+ */
+mxGraphSelectionModel.prototype.singleSelection = false;
+
+/**
+ * Function: isSingleSelection
+ *
+ * Returns <singleSelection> as a boolean.
+ */
+mxGraphSelectionModel.prototype.isSingleSelection = function()
+{
+ return this.singleSelection;
+};
+
+/**
+ * Function: setSingleSelection
+ *
+ * Sets the <singleSelection> flag.
+ *
+ * Parameters:
+ *
+ * singleSelection - Boolean that specifies the new value for
+ * <singleSelection>.
+ */
+mxGraphSelectionModel.prototype.setSingleSelection = function(singleSelection)
+{
+ this.singleSelection = singleSelection;
+};
+
+/**
+ * Function: isSelected
+ *
+ * Returns true if the given <mxCell> is selected.
+ */
+mxGraphSelectionModel.prototype.isSelected = function(cell)
+{
+ if (cell != null)
+ {
+ return mxUtils.indexOf(this.cells, cell) >= 0;
+ }
+
+ return false;
+};
+
+/**
+ * Function: isEmpty
+ *
+ * Returns true if no cells are currently selected.
+ */
+mxGraphSelectionModel.prototype.isEmpty = function()
+{
+ return this.cells.length == 0;
+};
+
+/**
+ * Function: clear
+ *
+ * Clears the selection and fires a <change> event if the selection was not
+ * empty.
+ */
+mxGraphSelectionModel.prototype.clear = function()
+{
+ this.changeSelection(null, this.cells);
+};
+
+/**
+ * Function: setCell
+ *
+ * Selects the specified <mxCell> using <setCells>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to be selected.
+ */
+mxGraphSelectionModel.prototype.setCell = function(cell)
+{
+ if (cell != null)
+ {
+ this.setCells([cell]);
+ }
+};
+
+/**
+ * Function: setCells
+ *
+ * Selects the given array of <mxCells> and fires a <change> event.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to be selected.
+ */
+mxGraphSelectionModel.prototype.setCells = function(cells)
+{
+ if (cells != null)
+ {
+ if (this.singleSelection)
+ {
+ cells = [this.getFirstSelectableCell(cells)];
+ }
+
+ var tmp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.graph.isCellSelectable(cells[i]))
+ {
+ tmp.push(cells[i]);
+ }
+ }
+
+ this.changeSelection(tmp, this.cells);
+ }
+};
+
+/**
+ * Function: getFirstSelectableCell
+ *
+ * Returns the first selectable cell in the given array of cells.
+ */
+mxGraphSelectionModel.prototype.getFirstSelectableCell = function(cells)
+{
+ if (cells != null)
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.graph.isCellSelectable(cells[i]))
+ {
+ return cells[i];
+ }
+ }
+ }
+
+ return null;
+};
+
+/**
+ * Function: addCell
+ *
+ * Adds the given <mxCell> to the selection and fires a <select> event.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.addCell = function(cell)
+{
+ if (cell != null)
+ {
+ this.addCells([cell]);
+ }
+};
+
+/**
+ * Function: addCells
+ *
+ * Adds the given array of <mxCells> to the selection and fires a <select>
+ * event.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.addCells = function(cells)
+{
+ if (cells != null)
+ {
+ var remove = null;
+
+ if (this.singleSelection)
+ {
+ remove = this.cells;
+ cells = [this.getFirstSelectableCell(cells)];
+ }
+
+ var tmp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!this.isSelected(cells[i]) &&
+ this.graph.isCellSelectable(cells[i]))
+ {
+ tmp.push(cells[i]);
+ }
+ }
+
+ this.changeSelection(tmp, remove);
+ }
+};
+
+/**
+ * Function: removeCell
+ *
+ * Removes the specified <mxCell> from the selection and fires a <select>
+ * event for the remaining cells.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to remove from the selection.
+ */
+mxGraphSelectionModel.prototype.removeCell = function(cell)
+{
+ if (cell != null)
+ {
+ this.removeCells([cell]);
+ }
+};
+
+/**
+ * Function: removeCells
+ */
+mxGraphSelectionModel.prototype.removeCells = function(cells)
+{
+ if (cells != null)
+ {
+ var tmp = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (this.isSelected(cells[i]))
+ {
+ tmp.push(cells[i]);
+ }
+ }
+
+ this.changeSelection(null, tmp);
+ }
+};
+
+/**
+ * Function: changeSelection
+ *
+ * Inner callback to add the specified <mxCell> to the selection. No event
+ * is fired in this implementation.
+ *
+ * Paramters:
+ *
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.changeSelection = function(added, removed)
+{
+ if ((added != null &&
+ added.length > 0 &&
+ added[0] != null) ||
+ (removed != null &&
+ removed.length > 0 &&
+ removed[0] != null))
+ {
+ var change = new mxSelectionChange(this, added, removed);
+ change.execute();
+ var edit = new mxUndoableEdit(this, false);
+ edit.add(change);
+ this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+ }
+};
+
+/**
+ * Function: cellAdded
+ *
+ * Inner callback to add the specified <mxCell> to the selection. No event
+ * is fired in this implementation.
+ *
+ * Paramters:
+ *
+ * cell - <mxCell> to add to the selection.
+ */
+mxGraphSelectionModel.prototype.cellAdded = function(cell)
+{
+ if (cell != null &&
+ !this.isSelected(cell))
+ {
+ this.cells.push(cell);
+ }
+};
+
+/**
+ * Function: cellRemoved
+ *
+ * Inner callback to remove the specified <mxCell> from the selection. No
+ * event is fired in this implementation.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> to remove from the selection.
+ */
+mxGraphSelectionModel.prototype.cellRemoved = function(cell)
+{
+ if (cell != null)
+ {
+ var index = mxUtils.indexOf(this.cells, cell);
+
+ if (index >= 0)
+ {
+ this.cells.splice(index, 1);
+ }
+ }
+};
+
+/**
+ * Class: mxSelectionChange
+ *
+ * Action to change the current root in a view.
+ *
+ * Constructor: mxCurrentRootChange
+ *
+ * Constructs a change of the current root in the given view.
+ */
+function mxSelectionChange(selectionModel, added, removed)
+{
+ this.selectionModel = selectionModel;
+ this.added = (added != null) ? added.slice() : null;
+ this.removed = (removed != null) ? removed.slice() : null;
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the current root of the view.
+ */
+mxSelectionChange.prototype.execute = function()
+{
+ var t0 = mxLog.enter('mxSelectionChange.execute');
+ window.status = mxResources.get(
+ this.selectionModel.updatingSelectionResource) ||
+ this.selectionModel.updatingSelectionResource;
+
+ if (this.removed != null)
+ {
+ for (var i = 0; i < this.removed.length; i++)
+ {
+ this.selectionModel.cellRemoved(this.removed[i]);
+ }
+ }
+
+ if (this.added != null)
+ {
+ for (var i = 0; i < this.added.length; i++)
+ {
+ this.selectionModel.cellAdded(this.added[i]);
+ }
+ }
+
+ var tmp = this.added;
+ this.added = this.removed;
+ this.removed = tmp;
+
+ window.status = mxResources.get(this.selectionModel.doneResource) ||
+ this.selectionModel.doneResource;
+ mxLog.leave('mxSelectionChange.execute', t0);
+
+ this.selectionModel.fireEvent(new mxEventObject(mxEvent.CHANGE,
+ 'added', this.added, 'removed', this.removed));
+};
diff --git a/src/js/view/mxGraphView.js b/src/js/view/mxGraphView.js
new file mode 100644
index 0000000..0ef2dc8
--- /dev/null
+++ b/src/js/view/mxGraphView.js
@@ -0,0 +1,2545 @@
+/**
+ * $Id: mxGraphView.js,v 1.195 2012-11-20 09:06:07 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxGraphView
+ *
+ * Extends <mxEventSource> to implement a view for a graph. This class is in
+ * charge of computing the absolute coordinates for the relative child
+ * geometries, the points for perimeters and edge styles and keeping them
+ * cached in <mxCellStates> for faster retrieval. The states are updated
+ * whenever the model or the view state (translate, scale) changes. The scale
+ * and translate are honoured in the bounds.
+ *
+ * Event: mxEvent.UNDO
+ *
+ * Fires after the root was changed in <setCurrentRoot>. The <code>edit</code>
+ * property contains the <mxUndoableEdit> which contains the
+ * <mxCurrentRootChange>.
+ *
+ * Event: mxEvent.SCALE_AND_TRANSLATE
+ *
+ * Fires after the scale and translate have been changed in <scaleAndTranslate>.
+ * The <code>scale</code>, <code>previousScale</code>, <code>translate</code>
+ * and <code>previousTranslate</code> properties contain the new and previous
+ * scale and translate, respectively.
+ *
+ * Event: mxEvent.SCALE
+ *
+ * Fires after the scale was changed in <setScale>. The <code>scale</code> and
+ * <code>previousScale</code> properties contain the new and previous scale.
+ *
+ * Event: mxEvent.TRANSLATE
+ *
+ * Fires after the translate was changed in <setTranslate>. The
+ * <code>translate</code> and <code>previousTranslate</code> properties contain
+ * the new and previous value for translate.
+ *
+ * Event: mxEvent.DOWN and mxEvent.UP
+ *
+ * Fire if the current root is changed by executing an <mxCurrentRootChange>.
+ * The event name depends on the location of the root in the cell hierarchy
+ * with respect to the current root. The <code>root</code> and
+ * <code>previous</code> properties contain the new and previous root,
+ * respectively.
+ *
+ * Constructor: mxGraphView
+ *
+ * Constructs a new view for the given <mxGraph>.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph>.
+ */
+function mxGraphView(graph)
+{
+ this.graph = graph;
+ this.translate = new mxPoint();
+ this.graphBounds = new mxRectangle();
+ this.states = new mxDictionary();
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxGraphView.prototype = new mxEventSource();
+mxGraphView.prototype.constructor = mxGraphView;
+
+/**
+ *
+ */
+mxGraphView.prototype.EMPTY_POINT = new mxPoint();
+
+/**
+ * Variable: doneResource
+ *
+ * Specifies the resource key for the status message after a long operation.
+ * If the resource for this key does not exist then the value is used as
+ * the status message. Default is 'done'.
+ */
+mxGraphView.prototype.doneResource = (mxClient.language != 'none') ? 'done' : '';
+
+/**
+ * Function: updatingDocumentResource
+ *
+ * Specifies the resource key for the status message while the document is
+ * being updated. If the resource for this key does not exist then the
+ * value is used as the status message. Default is 'updatingDocument'.
+ */
+mxGraphView.prototype.updatingDocumentResource = (mxClient.language != 'none') ? 'updatingDocument' : '';
+
+/**
+ * Variable: allowEval
+ *
+ * Specifies if string values in cell styles should be evaluated using
+ * <mxUtils.eval>. This will only be used if the string values can't be mapped
+ * to objects using <mxStyleRegistry>. Default is false. NOTE: Enabling this
+ * switch carries a possible security risk (see the section on security in
+ * the manual).
+ */
+mxGraphView.prototype.allowEval = false;
+
+/**
+ * Variable: captureDocumentGesture
+ *
+ * Specifies if a gesture should be captured when it goes outside of the
+ * graph container. Default is true.
+ */
+mxGraphView.prototype.captureDocumentGesture = true;
+
+/**
+ * Variable: rendering
+ *
+ * Specifies if shapes should be created, updated and destroyed using the
+ * methods of <mxCellRenderer> in <graph>. Default is true.
+ */
+mxGraphView.prototype.rendering = true;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxGraphView.prototype.graph = null;
+
+/**
+ * Variable: currentRoot
+ *
+ * <mxCell> that acts as the root of the displayed cell hierarchy.
+ */
+mxGraphView.prototype.currentRoot = null;
+
+/**
+ * Variable: graphBounds
+ *
+ * <mxRectangle> that caches the scales, translated bounds of the current view.
+ */
+mxGraphView.prototype.graphBounds = null;
+
+/**
+ * Variable: scale
+ *
+ * Specifies the scale. Default is 1 (100%).
+ */
+mxGraphView.prototype.scale = 1;
+
+/**
+ * Variable: translate
+ *
+ * <mxPoint> that specifies the current translation. Default is a new
+ * empty <mxPoint>.
+ */
+mxGraphView.prototype.translate = null;
+
+/**
+ * Variable: updateStyle
+ *
+ * Specifies if the style should be updated in each validation step. If this
+ * is false then the style is only updated if the state is created or if the
+ * style of the cell was changed. Default is false.
+ */
+mxGraphView.prototype.updateStyle = false;
+
+/**
+ * Function: getGraphBounds
+ *
+ * Returns <graphBounds>.
+ */
+mxGraphView.prototype.getGraphBounds = function()
+{
+ return this.graphBounds;
+};
+
+/**
+ * Function: setGraphBounds
+ *
+ * Sets <graphBounds>.
+ */
+mxGraphView.prototype.setGraphBounds = function(value)
+{
+ this.graphBounds = value;
+};
+
+/**
+ * Function: getBounds
+ *
+ * Returns the bounds (on the screen) for the given array of <mxCells>.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> to return the bounds for.
+ */
+mxGraphView.prototype.getBounds = function(cells)
+{
+ var result = null;
+
+ if (cells != null && cells.length > 0)
+ {
+ var model = this.graph.getModel();
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (model.isVertex(cells[i]) || model.isEdge(cells[i]))
+ {
+ var state = this.getState(cells[i]);
+
+ if (state != null)
+ {
+ if (result == null)
+ {
+ result = new mxRectangle(state.x, state.y,
+ state.width, state.height);
+ }
+ else
+ {
+ result.add(state);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: setCurrentRoot
+ *
+ * Sets and returns the current root and fires an <undo> event before
+ * calling <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * root - <mxCell> that specifies the root of the displayed cell hierarchy.
+ */
+mxGraphView.prototype.setCurrentRoot = function(root)
+{
+ if (this.currentRoot != root)
+ {
+ var change = new mxCurrentRootChange(this, root);
+ change.execute();
+ var edit = new mxUndoableEdit(this, false);
+ edit.add(change);
+ this.fireEvent(new mxEventObject(mxEvent.UNDO, 'edit', edit));
+ this.graph.sizeDidChange();
+ }
+
+ return root;
+};
+
+/**
+ * Function: scaleAndTranslate
+ *
+ * Sets the scale and translation and fires a <scale> and <translate> event
+ * before calling <revalidate> followed by <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * scale - Decimal value that specifies the new scale (1 is 100%).
+ * dx - X-coordinate of the translation.
+ * dy - Y-coordinate of the translation.
+ */
+mxGraphView.prototype.scaleAndTranslate = function(scale, dx, dy)
+{
+ var previousScale = this.scale;
+ var previousTranslate = new mxPoint(this.translate.x, this.translate.y);
+
+ if (this.scale != scale || this.translate.x != dx || this.translate.y != dy)
+ {
+ this.scale = scale;
+
+ this.translate.x = dx;
+ this.translate.y = dy;
+
+ if (this.isEventsEnabled())
+ {
+ this.revalidate();
+ this.graph.sizeDidChange();
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.SCALE_AND_TRANSLATE,
+ 'scale', scale, 'previousScale', previousScale,
+ 'translate', this.translate, 'previousTranslate', previousTranslate));
+};
+
+/**
+ * Function: getScale
+ *
+ * Returns the <scale>.
+ */
+mxGraphView.prototype.getScale = function()
+{
+ return this.scale;
+};
+
+/**
+ * Function: setScale
+ *
+ * Sets the scale and fires a <scale> event before calling <revalidate> followed
+ * by <mxGraph.sizeDidChange>.
+ *
+ * Parameters:
+ *
+ * value - Decimal value that specifies the new scale (1 is 100%).
+ */
+mxGraphView.prototype.setScale = function(value)
+{
+ var previousScale = this.scale;
+
+ if (this.scale != value)
+ {
+ this.scale = value;
+
+ if (this.isEventsEnabled())
+ {
+ this.revalidate();
+ this.graph.sizeDidChange();
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.SCALE,
+ 'scale', value, 'previousScale', previousScale));
+};
+
+/**
+ * Function: getTranslate
+ *
+ * Returns the <translate>.
+ */
+mxGraphView.prototype.getTranslate = function()
+{
+ return this.translate;
+};
+
+/**
+ * Function: setTranslate
+ *
+ * Sets the translation and fires a <translate> event before calling
+ * <revalidate> followed by <mxGraph.sizeDidChange>. The translation is the
+ * negative of the origin.
+ *
+ * Parameters:
+ *
+ * dx - X-coordinate of the translation.
+ * dy - Y-coordinate of the translation.
+ */
+mxGraphView.prototype.setTranslate = function(dx, dy)
+{
+ var previousTranslate = new mxPoint(this.translate.x, this.translate.y);
+
+ if (this.translate.x != dx || this.translate.y != dy)
+ {
+ this.translate.x = dx;
+ this.translate.y = dy;
+
+ if (this.isEventsEnabled())
+ {
+ this.revalidate();
+ this.graph.sizeDidChange();
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.TRANSLATE,
+ 'translate', this.translate, 'previousTranslate', previousTranslate));
+};
+
+/**
+ * Function: refresh
+ *
+ * Clears the view if <currentRoot> is not null and revalidates.
+ */
+mxGraphView.prototype.refresh = function()
+{
+ if (this.currentRoot != null)
+ {
+ this.clear();
+ }
+
+ this.revalidate();
+};
+
+/**
+ * Function: revalidate
+ *
+ * Revalidates the complete view with all cell states.
+ */
+mxGraphView.prototype.revalidate = function()
+{
+ this.invalidate();
+ this.validate();
+};
+
+/**
+ * Function: clear
+ *
+ * Removes the state of the given cell and all descendants if the given
+ * cell is not the current root.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> for which the state should be removed. Default
+ * is the root of the model.
+ * force - Boolean indicating if the current root should be ignored for
+ * recursion.
+ */
+mxGraphView.prototype.clear = function(cell, force, recurse)
+{
+ var model = this.graph.getModel();
+ cell = cell || model.getRoot();
+ force = (force != null) ? force : false;
+ recurse = (recurse != null) ? recurse : true;
+
+ this.removeState(cell);
+
+ if (recurse && (force || cell != this.currentRoot))
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.clear(model.getChildAt(cell, i), force);
+ }
+ }
+ else
+ {
+ this.invalidate(cell);
+ }
+};
+
+/**
+ * Function: invalidate
+ *
+ * Invalidates the state of the given cell, all its descendants and
+ * connected edges.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> to be invalidated. Default is the root of the
+ * model.
+ */
+mxGraphView.prototype.invalidate = function(cell, recurse, includeEdges, orderChanged)
+{
+ var model = this.graph.getModel();
+ cell = cell || model.getRoot();
+ recurse = (recurse != null) ? recurse : true;
+ includeEdges = (includeEdges != null) ? includeEdges : true;
+ orderChanged = (orderChanged != null) ? orderChanged : false;
+
+ var state = this.getState(cell);
+
+ if (state != null)
+ {
+ state.invalid = true;
+
+ if (orderChanged)
+ {
+ state.orderChanged = true;
+ }
+ }
+
+ // Recursively invalidates all descendants
+ if (recurse)
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(cell, i);
+ this.invalidate(child, recurse, includeEdges, orderChanged);
+ }
+ }
+
+ // Propagates invalidation to all connected edges
+ if (includeEdges)
+ {
+ var edgeCount = model.getEdgeCount(cell);
+
+ for (var i = 0; i < edgeCount; i++)
+ {
+ this.invalidate(model.getEdgeAt(cell, i), recurse, includeEdges);
+ }
+ }
+};
+
+/**
+ * Function: validate
+ *
+ * First validates all bounds and then validates all points recursively on
+ * all visible cells starting at the given cell. Finally the background
+ * is validated using <validateBackground>.
+ *
+ * Parameters:
+ *
+ * cell - Optional <mxCell> to be used as the root of the validation.
+ * Default is <currentRoot> or the root of the model.
+ */
+mxGraphView.prototype.validate = function(cell)
+{
+ var t0 = mxLog.enter('mxGraphView.validate');
+ window.status = mxResources.get(this.updatingDocumentResource) ||
+ this.updatingDocumentResource;
+
+ cell = cell || ((this.currentRoot != null) ?
+ this.currentRoot :
+ this.graph.getModel().getRoot());
+ this.validateBounds(null, cell);
+ var graphBounds = this.validatePoints(null, cell);
+
+ if (graphBounds == null)
+ {
+ graphBounds = new mxRectangle();
+ }
+
+ this.setGraphBounds(graphBounds);
+ this.validateBackground();
+
+ window.status = mxResources.get(this.doneResource) ||
+ this.doneResource;
+ mxLog.leave('mxGraphView.validate', t0);
+};
+
+/**
+ * Function: createBackgroundPageShape
+ *
+ * Creates and returns the shape used as the background page.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that represents the bounds of the shape.
+ */
+mxGraphView.prototype.createBackgroundPageShape = function(bounds)
+{
+ return new mxRectangleShape(bounds, 'white', 'black');
+};
+
+/**
+ * Function: validateBackground
+ *
+ * Validates the background image.
+ */
+mxGraphView.prototype.validateBackground = function()
+{
+ var bg = this.graph.getBackgroundImage();
+
+ if (bg != null)
+ {
+ if (this.backgroundImage == null || this.backgroundImage.image != bg.src)
+ {
+ if (this.backgroundImage != null)
+ {
+ this.backgroundImage.destroy();
+ }
+
+ var bounds = new mxRectangle(0, 0, 1, 1);
+
+ this.backgroundImage = new mxImageShape(bounds, bg.src);
+ this.backgroundImage.dialect = this.graph.dialect;
+ this.backgroundImage.init(this.backgroundPane);
+ this.backgroundImage.redraw();
+ }
+
+ this.redrawBackgroundImage(this.backgroundImage, bg);
+ }
+ else if (this.backgroundImage != null)
+ {
+ this.backgroundImage.destroy();
+ this.backgroundImage = null;
+ }
+
+ if (this.graph.pageVisible)
+ {
+ var bounds = this.getBackgroundPageBounds();
+
+ if (this.backgroundPageShape == null)
+ {
+ this.backgroundPageShape = this.createBackgroundPageShape(bounds);
+ this.backgroundPageShape.scale = this.scale;
+ this.backgroundPageShape.isShadow = true;
+ this.backgroundPageShape.dialect = this.graph.dialect;
+ this.backgroundPageShape.init(this.backgroundPane);
+ this.backgroundPageShape.redraw();
+
+ // Adds listener for double click handling on background
+ mxEvent.addListener(this.backgroundPageShape.node, 'dblclick',
+ mxUtils.bind(this, function(evt)
+ {
+ this.graph.dblClick(evt);
+ })
+ );
+
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ // Adds basic listeners for graph event dispatching outside of the
+ // container and finishing the handling of a single gesture
+ mxEvent.addListener(this.backgroundPageShape.node, md,
+ mxUtils.bind(this, function(evt)
+ {
+ this.graph.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+ })
+ );
+ mxEvent.addListener(this.backgroundPageShape.node, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ // Hides the tooltip if mouse is outside container
+ if (this.graph.tooltipHandler != null &&
+ this.graph.tooltipHandler.isHideOnHover())
+ {
+ this.graph.tooltipHandler.hide();
+ }
+
+ if (this.graph.isMouseDown &&
+ !mxEvent.isConsumed(evt))
+ {
+ this.graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt));
+ }
+ })
+ );
+ mxEvent.addListener(this.backgroundPageShape.node, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ this.graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt));
+ })
+ );
+ }
+ else
+ {
+ this.backgroundPageShape.scale = this.scale;
+ this.backgroundPageShape.bounds = bounds;
+ this.backgroundPageShape.redraw();
+ }
+ }
+ else if (this.backgroundPageShape != null)
+ {
+ this.backgroundPageShape.destroy();
+ this.backgroundPageShape = null;
+ }
+};
+
+/**
+ * Function: getBackgroundPageBounds
+ *
+ * Returns the bounds for the background page.
+ */
+mxGraphView.prototype.getBackgroundPageBounds = function()
+{
+ var fmt = this.graph.pageFormat;
+ var ps = this.scale * this.graph.pageScale;
+ var bounds = new mxRectangle(this.scale * this.translate.x, this.scale * this.translate.y,
+ fmt.width * ps, fmt.height * ps);
+
+ return bounds;
+};
+
+/**
+ * Function: redrawBackgroundImage
+ *
+ * Updates the bounds and redraws the background image.
+ *
+ * Example:
+ *
+ * If the background image should not be scaled, this can be replaced with
+ * the following.
+ *
+ * (code)
+ * mxGraphView.prototype.redrawBackground = function(backgroundImage, bg)
+ * {
+ * backgroundImage.bounds.x = this.translate.x;
+ * backgroundImage.bounds.y = this.translate.y;
+ * backgroundImage.bounds.width = bg.width;
+ * backgroundImage.bounds.height = bg.height;
+ *
+ * backgroundImage.redraw();
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * backgroundImage - <mxImageShape> that represents the background image.
+ * bg - <mxImage> that specifies the image and its dimensions.
+ */
+mxGraphView.prototype.redrawBackgroundImage = function(backgroundImage, bg)
+{
+ backgroundImage.scale = this.scale;
+ backgroundImage.bounds.x = this.scale * this.translate.x;
+ backgroundImage.bounds.y = this.scale * this.translate.y;
+ backgroundImage.bounds.width = this.scale * bg.width;
+ backgroundImage.bounds.height = this.scale * bg.height;
+
+ backgroundImage.redraw();
+};
+
+/**
+ * Function: validateBounds
+ *
+ * Validates the bounds of the given parent's child using the given parent
+ * state as the origin for the child. The validation is carried out
+ * recursively for all non-collapsed descendants.
+ *
+ * Parameters:
+ *
+ * parentState - <mxCellState> for the given parent.
+ * cell - <mxCell> for which the bounds in the state should be updated.
+ */
+mxGraphView.prototype.validateBounds = function(parentState, cell)
+{
+ var model = this.graph.getModel();
+ var state = this.getState(cell, true);
+
+ if (state != null && state.invalid)
+ {
+ if (!this.graph.isCellVisible(cell))
+ {
+ this.removeState(cell);
+ }
+
+ // Updates the cell state's origin
+ else if (cell != this.currentRoot && parentState != null)
+ {
+ state.absoluteOffset.x = 0;
+ state.absoluteOffset.y = 0;
+ state.origin.x = parentState.origin.x;
+ state.origin.y = parentState.origin.y;
+ var geo = this.graph.getCellGeometry(cell);
+
+ if (geo != null)
+ {
+ if (!model.isEdge(cell))
+ {
+ var offset = geo.offset || this.EMPTY_POINT;
+
+ if (geo.relative)
+ {
+ state.origin.x += geo.x * parentState.width /
+ this.scale + offset.x;
+ state.origin.y += geo.y * parentState.height /
+ this.scale + offset.y;
+ }
+ else
+ {
+ state.absoluteOffset.x = this.scale * offset.x;
+ state.absoluteOffset.y = this.scale * offset.y;
+ state.origin.x += geo.x;
+ state.origin.y += geo.y;
+ }
+ }
+
+ // Updates cell state's bounds
+ state.x = this.scale * (this.translate.x + state.origin.x);
+ state.y = this.scale * (this.translate.y + state.origin.y);
+ state.width = this.scale * geo.width;
+ state.height = this.scale * geo.height;
+
+ if (model.isVertex(cell))
+ {
+ this.updateVertexLabelOffset(state);
+ }
+ }
+ }
+
+ // Applies child offset to origin
+ var offset = this.graph.getChildOffsetForCell(cell);
+
+ if (offset != null)
+ {
+ state.origin.x += offset.x;
+ state.origin.y += offset.y;
+ }
+ }
+
+ // Recursively validates the child bounds
+ if (state != null && (!this.graph.isCellCollapsed(cell) ||
+ cell == this.currentRoot))
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(cell, i);
+ this.validateBounds(state, child);
+ }
+ }
+};
+
+/**
+ * Function: updateVertexLabelOffset
+ *
+ * Updates the absoluteOffset of the given vertex cell state. This takes
+ * into account the label position styles.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose absolute offset should be updated.
+ */
+mxGraphView.prototype.updateVertexLabelOffset = function(state)
+{
+ var horizontal = mxUtils.getValue(state.style,
+ mxConstants.STYLE_LABEL_POSITION,
+ mxConstants.ALIGN_CENTER);
+
+ if (horizontal == mxConstants.ALIGN_LEFT)
+ {
+ state.absoluteOffset.x -= state.width;
+ }
+ else if (horizontal == mxConstants.ALIGN_RIGHT)
+ {
+ state.absoluteOffset.x += state.width;
+ }
+
+ var vertical = mxUtils.getValue(state.style,
+ mxConstants.STYLE_VERTICAL_LABEL_POSITION,
+ mxConstants.ALIGN_MIDDLE);
+
+ if (vertical == mxConstants.ALIGN_TOP)
+ {
+ state.absoluteOffset.y -= state.height;
+ }
+ else if (vertical == mxConstants.ALIGN_BOTTOM)
+ {
+ state.absoluteOffset.y += state.height;
+ }
+};
+
+/**
+ * Function: validatePoints
+ *
+ * Validates the points for the state of the given cell recursively if the
+ * cell is not collapsed and returns the bounding box of all visited states
+ * as an <mxRectangle>.
+ *
+ * Parameters:
+ *
+ * parentState - <mxCellState> for the parent cell.
+ * cell - <mxCell> whose points in the state should be updated.
+ */
+mxGraphView.prototype.validatePoints = function(parentState, cell)
+{
+ var model = this.graph.getModel();
+ var state = this.getState(cell);
+ var bbox = null;
+
+ if (state != null)
+ {
+ if (state.invalid)
+ {
+ var geo = this.graph.getCellGeometry(cell);
+
+ if (geo != null && model.isEdge(cell))
+ {
+ // Updates the points on the source terminal if its an edge
+ var source = this.getState(this.getVisibleTerminal(cell, true));
+ state.setVisibleTerminalState(source, true);
+
+ if (source != null && model.isEdge(source.cell) &&
+ !model.isAncestor(source.cell, cell))
+ {
+ var tmp = this.getState(model.getParent(source.cell));
+ this.validatePoints(tmp, source.cell);
+ }
+
+ // Updates the points on the target terminal if its an edge
+ var target = this.getState(this.getVisibleTerminal(cell, false));
+ state.setVisibleTerminalState(target, false);
+
+ if (target != null && model.isEdge(target.cell) &&
+ !model.isAncestor(target.cell, cell))
+ {
+ var tmp = this.getState(model.getParent(target.cell));
+ this.validatePoints(tmp, target.cell);
+ }
+
+ this.updateFixedTerminalPoints(state, source, target);
+ this.updatePoints(state, geo.points, source, target);
+ this.updateFloatingTerminalPoints(state, source, target);
+ this.updateEdgeBounds(state);
+ this.updateEdgeLabelOffset(state);
+ }
+ else if (geo != null && geo.relative && parentState != null &&
+ model.isEdge(parentState.cell))
+ {
+ var origin = this.getPoint(parentState, geo);
+
+ if (origin != null)
+ {
+ state.x = origin.x;
+ state.y = origin.y;
+
+ origin.x = (origin.x / this.scale) - this.translate.x;
+ origin.y = (origin.y / this.scale) - this.translate.y;
+ state.origin = origin;
+
+ this.childMoved(parentState, state);
+ }
+ }
+
+ state.invalid = false;
+
+ if (cell != this.currentRoot)
+ {
+ // NOTE: Label bounds currently ignored if rendering is false
+ this.graph.cellRenderer.redraw(state, false, this.isRendering());
+ }
+ }
+
+ if (model.isEdge(cell) || model.isVertex(cell))
+ {
+ if (state.shape != null && state.shape.boundingBox != null)
+ {
+ bbox = state.shape.boundingBox.clone();
+ }
+
+ if (state.text != null && !this.graph.isLabelClipped(state.cell))
+ {
+ // Adds label bounding box to graph bounds
+ if (state.text.boundingBox != null)
+ {
+ if (bbox != null)
+ {
+ bbox.add(state.text.boundingBox);
+ }
+ else
+ {
+ bbox = state.text.boundingBox.clone();
+ }
+ }
+ }
+ }
+ }
+
+ if (state != null && (!this.graph.isCellCollapsed(cell) ||
+ cell == this.currentRoot))
+ {
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(cell, i);
+ var bounds = this.validatePoints(state, child);
+
+ if (bounds != null)
+ {
+ if (bbox == null)
+ {
+ bbox = bounds;
+ }
+ else
+ {
+ bbox.add(bounds);
+ }
+ }
+ }
+ }
+
+ return bbox;
+};
+
+/**
+ * Function: childMoved
+ *
+ * Invoked when a child state was moved as a result of late evaluation
+ * of its position. This is invoked for relative edge children whose
+ * position can only be determined after the points of the parent edge
+ * are updated in validatePoints, and validates the bounds of all
+ * descendants of the child using validateBounds.
+ *
+ * Parameters:
+ *
+ * parent - <mxCellState> that represents the parent state.
+ * child - <mxCellState> that represents the child state.
+ */
+mxGraphView.prototype.childMoved = function(parent, child)
+{
+ var cell = child.cell;
+
+ // Children of relative edge children need to validate
+ // their bounds after their parent state was updated
+ if (!this.graph.isCellCollapsed(cell) || cell == this.currentRoot)
+ {
+ var model = this.graph.getModel();
+ var childCount = model.getChildCount(cell);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ this.validateBounds(child, model.getChildAt(cell, i));
+ }
+ }
+};
+
+/**
+ * Function: updateFixedTerminalPoints
+ *
+ * Sets the initial absolute terminal points in the given state before the edge
+ * style is computed.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> whose initial terminal points should be updated.
+ * source - <mxCellState> which represents the source terminal.
+ * target - <mxCellState> which represents the target terminal.
+ */
+mxGraphView.prototype.updateFixedTerminalPoints = function(edge, source, target)
+{
+ this.updateFixedTerminalPoint(edge, source, true,
+ this.graph.getConnectionConstraint(edge, source, true));
+ this.updateFixedTerminalPoint(edge, target, false,
+ this.graph.getConnectionConstraint(edge, target, false));
+};
+
+/**
+ * Function: updateFixedTerminalPoint
+ *
+ * Sets the fixed source or target terminal point on the given edge.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> whose terminal point should be updated.
+ * terminal - <mxCellState> which represents the actual terminal.
+ * source - Boolean that specifies if the terminal is the source.
+ * constraint - <mxConnectionConstraint> that specifies the connection.
+ */
+mxGraphView.prototype.updateFixedTerminalPoint = function(edge, terminal, source, constraint)
+{
+ var pt = null;
+
+ if (constraint != null)
+ {
+ pt = this.graph.getConnectionPoint(terminal, constraint);
+ }
+
+ if (pt == null && terminal == null)
+ {
+ var s = this.scale;
+ var tr = this.translate;
+ var orig = edge.origin;
+ var geo = this.graph.getCellGeometry(edge.cell);
+ pt = geo.getTerminalPoint(source);
+
+ if (pt != null)
+ {
+ pt = new mxPoint(s * (tr.x + pt.x + orig.x),
+ s * (tr.y + pt.y + orig.y));
+ }
+ }
+
+ edge.setAbsoluteTerminalPoint(pt, source);
+};
+
+/**
+ * Function: updatePoints
+ *
+ * Updates the absolute points in the given state using the specified array
+ * of <mxPoints> as the relative points.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> whose absolute points should be updated.
+ * points - Array of <mxPoints> that constitute the relative points.
+ * source - <mxCellState> that represents the source terminal.
+ * target - <mxCellState> that represents the target terminal.
+ */
+mxGraphView.prototype.updatePoints = function(edge, points, source, target)
+{
+ if (edge != null)
+ {
+ var pts = [];
+ pts.push(edge.absolutePoints[0]);
+ var edgeStyle = this.getEdgeStyle(edge, points, source, target);
+
+ if (edgeStyle != null)
+ {
+ var src = this.getTerminalPort(edge, source, true);
+ var trg = this.getTerminalPort(edge, target, false);
+
+ edgeStyle(edge, src, trg, points, pts);
+ }
+ else if (points != null)
+ {
+ for (var i = 0; i < points.length; i++)
+ {
+ if (points[i] != null)
+ {
+ var pt = mxUtils.clone(points[i]);
+ pts.push(this.transformControlPoint(edge, pt));
+ }
+ }
+ }
+
+ var tmp = edge.absolutePoints;
+ pts.push(tmp[tmp.length-1]);
+
+ edge.absolutePoints = pts;
+ }
+};
+
+/**
+ * Function: transformControlPoint
+ *
+ * Transforms the given control point to an absolute point.
+ */
+mxGraphView.prototype.transformControlPoint = function(state, pt)
+{
+ var orig = state.origin;
+
+ return new mxPoint(this.scale * (pt.x + this.translate.x + orig.x),
+ this.scale * (pt.y + this.translate.y + orig.y));
+};
+
+/**
+ * Function: getEdgeStyle
+ *
+ * Returns the edge style function to be used to render the given edge
+ * state.
+ */
+mxGraphView.prototype.getEdgeStyle = function(edge, points, source, target)
+{
+ var edgeStyle = (source != null && source == target) ?
+ mxUtils.getValue(edge.style, mxConstants.STYLE_LOOP,
+ this.graph.defaultLoopStyle) :
+ (!mxUtils.getValue(edge.style,
+ mxConstants.STYLE_NOEDGESTYLE, false) ?
+ edge.style[mxConstants.STYLE_EDGE] :
+ null);
+
+ // Converts string values to objects
+ if (typeof(edgeStyle) == "string")
+ {
+ var tmp = mxStyleRegistry.getValue(edgeStyle);
+
+ if (tmp == null && this.isAllowEval())
+ {
+ tmp = mxUtils.eval(edgeStyle);
+ }
+
+ edgeStyle = tmp;
+ }
+
+ if (typeof(edgeStyle) == "function")
+ {
+ return edgeStyle;
+ }
+
+ return null;
+};
+
+/**
+ * Function: updateFloatingTerminalPoints
+ *
+ * Updates the terminal points in the given state after the edge style was
+ * computed for the edge.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose terminal points should be updated.
+ * source - <mxCellState> that represents the source terminal.
+ * target - <mxCellState> that represents the target terminal.
+ */
+mxGraphView.prototype.updateFloatingTerminalPoints = function(state, source, target)
+{
+ var pts = state.absolutePoints;
+ var p0 = pts[0];
+ var pe = pts[pts.length - 1];
+
+ if (pe == null && target != null)
+ {
+ this.updateFloatingTerminalPoint(state, target, source, false);
+ }
+
+ if (p0 == null && source != null)
+ {
+ this.updateFloatingTerminalPoint(state, source, target, true);
+ }
+};
+
+/**
+ * Function: updateFloatingTerminalPoint
+ *
+ * Updates the absolute terminal point in the given state for the given
+ * start and end state, where start is the source if source is true.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> whose terminal point should be updated.
+ * start - <mxCellState> for the terminal on "this" side of the edge.
+ * end - <mxCellState> for the terminal on the other side of the edge.
+ * source - Boolean indicating if start is the source terminal state.
+ */
+mxGraphView.prototype.updateFloatingTerminalPoint = function(edge, start, end, source)
+{
+ start = this.getTerminalPort(edge, start, source);
+ var next = this.getNextPoint(edge, end, source);
+
+ var alpha = mxUtils.toRadians(Number(start.style[mxConstants.STYLE_ROTATION] || '0'));
+ var center = new mxPoint(start.getCenterX(), start.getCenterY());
+
+ if (alpha != 0)
+ {
+ var cos = Math.cos(-alpha);
+ var sin = Math.sin(-alpha);
+ next = mxUtils.getRotatedPoint(next, cos, sin, center);
+ }
+
+ var border = parseFloat(edge.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
+ border += parseFloat(edge.style[(source) ?
+ mxConstants.STYLE_SOURCE_PERIMETER_SPACING :
+ mxConstants.STYLE_TARGET_PERIMETER_SPACING] || 0);
+ var pt = this.getPerimeterPoint(start, next, this.graph.isOrthogonal(edge), border);
+
+ if (alpha != 0)
+ {
+ var cos = Math.cos(alpha);
+ var sin = Math.sin(alpha);
+ pt = mxUtils.getRotatedPoint(pt, cos, sin, center);
+ }
+
+ edge.setAbsoluteTerminalPoint(pt, source);
+};
+
+/**
+ * Function: getTerminalPort
+ *
+ * Returns an <mxCellState> that represents the source or target terminal or
+ * port for the given edge.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the state of the edge.
+ * terminal - <mxCellState> that represents the terminal.
+ * source - Boolean indicating if the given terminal is the source terminal.
+ */
+mxGraphView.prototype.getTerminalPort = function(state, terminal, source)
+{
+ var key = (source) ? mxConstants.STYLE_SOURCE_PORT :
+ mxConstants.STYLE_TARGET_PORT;
+ var id = mxUtils.getValue(state.style, key);
+
+ if (id != null)
+ {
+ var tmp = this.getState(this.graph.getModel().getCell(id));
+
+ // Only uses ports where a cell state exists
+ if (tmp != null)
+ {
+ terminal = tmp;
+ }
+ }
+
+ return terminal;
+};
+
+/**
+ * Function: getPerimeterPoint
+ *
+ * Returns an <mxPoint> that defines the location of the intersection point between
+ * the perimeter and the line between the center of the shape and the given point.
+ *
+ * Parameters:
+ *
+ * terminal - <mxCellState> for the source or target terminal.
+ * next - <mxPoint> that lies outside of the given terminal.
+ * orthogonal - Boolean that specifies if the orthogonal projection onto
+ * the perimeter should be returned. If this is false then the intersection
+ * of the perimeter and the line between the next and the center point is
+ * returned.
+ * border - Optional border between the perimeter and the shape.
+ */
+mxGraphView.prototype.getPerimeterPoint = function(terminal, next, orthogonal, border)
+{
+ var point = null;
+
+ if (terminal != null)
+ {
+ var perimeter = this.getPerimeterFunction(terminal);
+
+ if (perimeter != null && next != null)
+ {
+ var bounds = this.getPerimeterBounds(terminal, border);
+
+ if (bounds.width > 0 || bounds.height > 0)
+ {
+ point = perimeter(bounds, terminal, next, orthogonal);
+ }
+ }
+
+ if (point == null)
+ {
+ point = this.getPoint(terminal);
+ }
+ }
+
+ return point;
+};
+
+/**
+ * Function: getRoutingCenterX
+ *
+ * Returns the x-coordinate of the center point for automatic routing.
+ */
+mxGraphView.prototype.getRoutingCenterX = function (state)
+{
+ var f = (state.style != null) ? parseFloat(state.style
+ [mxConstants.STYLE_ROUTING_CENTER_X]) || 0 : 0;
+
+ return state.getCenterX() + f * state.width;
+};
+
+/**
+ * Function: getRoutingCenterY
+ *
+ * Returns the y-coordinate of the center point for automatic routing.
+ */
+mxGraphView.prototype.getRoutingCenterY = function (state)
+{
+ var f = (state.style != null) ? parseFloat(state.style
+ [mxConstants.STYLE_ROUTING_CENTER_Y]) || 0 : 0;
+
+ return state.getCenterY() + f * state.height;
+};
+
+/**
+ * Function: getPerimeterBounds
+ *
+ * Returns the perimeter bounds for the given terminal, edge pair as an
+ * <mxRectangle>.
+ *
+ * If you have a model where each terminal has a relative child that should
+ * act as the graphical endpoint for a connection from/to the terminal, then
+ * this method can be replaced as follows:
+ *
+ * (code)
+ * var oldGetPerimeterBounds = mxGraphView.prototype.getPerimeterBounds;
+ * mxGraphView.prototype.getPerimeterBounds = function(terminal, edge, isSource)
+ * {
+ * var model = this.graph.getModel();
+ * var childCount = model.getChildCount(terminal.cell);
+ *
+ * if (childCount > 0)
+ * {
+ * var child = model.getChildAt(terminal.cell, 0);
+ * var geo = model.getGeometry(child);
+ *
+ * if (geo != null &&
+ * geo.relative)
+ * {
+ * var state = this.getState(child);
+ *
+ * if (state != null)
+ * {
+ * terminal = state;
+ * }
+ * }
+ * }
+ *
+ * return oldGetPerimeterBounds.apply(this, arguments);
+ * };
+ * (end)
+ *
+ * Parameters:
+ *
+ * terminal - <mxCellState> that represents the terminal.
+ * border - Number that adds a border between the shape and the perimeter.
+ */
+mxGraphView.prototype.getPerimeterBounds = function(terminal, border)
+{
+ border = (border != null) ? border : 0;
+
+ if (terminal != null)
+ {
+ border += parseFloat(terminal.style[mxConstants.STYLE_PERIMETER_SPACING] || 0);
+ }
+
+ return terminal.getPerimeterBounds(border * this.scale);
+};
+
+/**
+ * Function: getPerimeterFunction
+ *
+ * Returns the perimeter function for the given state.
+ */
+mxGraphView.prototype.getPerimeterFunction = function(state)
+{
+ var perimeter = state.style[mxConstants.STYLE_PERIMETER];
+
+ // Converts string values to objects
+ if (typeof(perimeter) == "string")
+ {
+ var tmp = mxStyleRegistry.getValue(perimeter);
+
+ if (tmp == null && this.isAllowEval())
+ {
+ tmp = mxUtils.eval(perimeter);
+ }
+
+ perimeter = tmp;
+ }
+
+ if (typeof(perimeter) == "function")
+ {
+ return perimeter;
+ }
+
+ return null;
+};
+
+/**
+ * Function: getNextPoint
+ *
+ * Returns the nearest point in the list of absolute points or the center
+ * of the opposite terminal.
+ *
+ * Parameters:
+ *
+ * edge - <mxCellState> that represents the edge.
+ * opposite - <mxCellState> that represents the opposite terminal.
+ * source - Boolean indicating if the next point for the source or target
+ * should be returned.
+ */
+mxGraphView.prototype.getNextPoint = function(edge, opposite, source)
+{
+ var pts = edge.absolutePoints;
+ var point = null;
+
+ if (pts != null && (source || pts.length > 2 || opposite == null))
+ {
+ var count = pts.length;
+ point = pts[(source) ? Math.min(1, count - 1) : Math.max(0, count - 2)];
+ }
+
+ if (point == null && opposite != null)
+ {
+ point = new mxPoint(opposite.getCenterX(), opposite.getCenterY());
+ }
+
+ return point;
+};
+
+/**
+ * Function: getVisibleTerminal
+ *
+ * Returns the nearest ancestor terminal that is visible. The edge appears
+ * to be connected to this terminal on the display. The result of this method
+ * is cached in <mxCellState.getVisibleTerminalState>.
+ *
+ * Parameters:
+ *
+ * edge - <mxCell> whose visible terminal should be returned.
+ * source - Boolean that specifies if the source or target terminal
+ * should be returned.
+ */
+mxGraphView.prototype.getVisibleTerminal = function(edge, source)
+{
+ var model = this.graph.getModel();
+ var result = model.getTerminal(edge, source);
+ var best = result;
+
+ while (result != null && result != this.currentRoot)
+ {
+ if (!this.graph.isCellVisible(best) || this.graph.isCellCollapsed(result))
+ {
+ best = result;
+ }
+
+ result = model.getParent(result);
+ }
+
+ // Checks if the result is not a layer
+ if (model.getParent(best) == model.getRoot())
+ {
+ best = null;
+ }
+
+ return best;
+};
+
+/**
+ * Function: updateEdgeBounds
+ *
+ * Updates the given state using the bounding box of the absolute points.
+ * Also updates <mxCellState.terminalDistance>, <mxCellState.length> and
+ * <mxCellState.segments>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose bounds should be updated.
+ */
+mxGraphView.prototype.updateEdgeBounds = function(state)
+{
+ var points = state.absolutePoints;
+ state.length = 0;
+
+ if (points != null && points.length > 0)
+ {
+ var p0 = points[0];
+ var pe = points[points.length - 1];
+
+ if (p0 == null || pe == null)
+ {
+ // Drops the edge state if the edge is not the root
+ if (state.cell != this.currentRoot)
+ {
+ // Note: This condition normally occurs if a connected edge has a
+ // null-terminal, ie. edge.source == null or edge.target == null,
+ // and no corresponding terminal point defined, which happens for
+ // example if the terminal-id was not resolved at cell decoding time.
+ this.clear(state.cell, true);
+ }
+ }
+ else
+ {
+ if (p0.x != pe.x || p0.y != pe.y)
+ {
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+ state.terminalDistance = Math.sqrt(dx * dx + dy * dy);
+ }
+ else
+ {
+ state.terminalDistance = 0;
+ }
+
+ var length = 0;
+ var segments = [];
+ var pt = p0;
+
+ if (pt != null)
+ {
+ var minX = pt.x;
+ var minY = pt.y;
+ var maxX = minX;
+ var maxY = minY;
+
+ for (var i = 1; i < points.length; i++)
+ {
+ var tmp = points[i];
+
+ if (tmp != null)
+ {
+ var dx = pt.x - tmp.x;
+ var dy = pt.y - tmp.y;
+
+ var segment = Math.sqrt(dx * dx + dy * dy);
+ segments.push(segment);
+ length += segment;
+
+ pt = tmp;
+
+ minX = Math.min(pt.x, minX);
+ minY = Math.min(pt.y, minY);
+ maxX = Math.max(pt.x, maxX);
+ maxY = Math.max(pt.y, maxY);
+ }
+ }
+
+ state.length = length;
+ state.segments = segments;
+
+ var markerSize = 1; // TODO: include marker size
+
+ state.x = minX;
+ state.y = minY;
+ state.width = Math.max(markerSize, maxX - minX);
+ state.height = Math.max(markerSize, maxY - minY);
+ }
+ }
+ }
+};
+
+/**
+ * Function: getPoint
+ *
+ * Returns the absolute point on the edge for the given relative
+ * <mxGeometry> as an <mxPoint>. The edge is represented by the given
+ * <mxCellState>.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the state of the parent edge.
+ * geometry - <mxGeometry> that represents the relative location.
+ */
+mxGraphView.prototype.getPoint = function(state, geometry)
+{
+ var x = state.getCenterX();
+ var y = state.getCenterY();
+
+ if (state.segments != null && (geometry == null || geometry.relative))
+ {
+ var gx = (geometry != null) ? geometry.x / 2 : 0;
+ var pointCount = state.absolutePoints.length;
+ var dist = (gx + 0.5) * state.length;
+ var segment = state.segments[0];
+ var length = 0;
+ var index = 1;
+
+ while (dist > length + segment && index < pointCount-1)
+ {
+ length += segment;
+ segment = state.segments[index++];
+ }
+
+ var factor = (segment == 0) ? 0 : (dist - length) / segment;
+ var p0 = state.absolutePoints[index-1];
+ var pe = state.absolutePoints[index];
+
+ if (p0 != null && pe != null)
+ {
+ var gy = 0;
+ var offsetX = 0;
+ var offsetY = 0;
+
+ if (geometry != null)
+ {
+ gy = geometry.y;
+ var offset = geometry.offset;
+
+ if (offset != null)
+ {
+ offsetX = offset.x;
+ offsetY = offset.y;
+ }
+ }
+
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+ var nx = (segment == 0) ? 0 : dy / segment;
+ var ny = (segment == 0) ? 0 : dx / segment;
+
+ x = p0.x + dx * factor + (nx * gy + offsetX) * this.scale;
+ y = p0.y + dy * factor - (ny * gy - offsetY) * this.scale;
+ }
+ }
+ else if (geometry != null)
+ {
+ var offset = geometry.offset;
+
+ if (offset != null)
+ {
+ x += offset.x;
+ y += offset.y;
+ }
+ }
+
+ return new mxPoint(x, y);
+};
+
+/**
+ * Function: getRelativePoint
+ *
+ * Gets the relative point that describes the given, absolute label
+ * position for the given edge state.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> that represents the state of the parent edge.
+ * x - Specifies the x-coordinate of the absolute label location.
+ * y - Specifies the y-coordinate of the absolute label location.
+ */
+mxGraphView.prototype.getRelativePoint = function(edgeState, x, y)
+{
+ var model = this.graph.getModel();
+ var geometry = model.getGeometry(edgeState.cell);
+
+ if (geometry != null)
+ {
+ var pointCount = edgeState.absolutePoints.length;
+
+ if (geometry.relative && pointCount > 1)
+ {
+ var totalLength = edgeState.length;
+ var segments = edgeState.segments;
+
+ // Works which line segment the point of the label is closest to
+ var p0 = edgeState.absolutePoints[0];
+ var pe = edgeState.absolutePoints[1];
+ var minDist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);
+
+ var index = 0;
+ var tmp = 0;
+ var length = 0;
+
+ for (var i = 2; i < pointCount; i++)
+ {
+ tmp += segments[i - 2];
+ pe = edgeState.absolutePoints[i];
+ var dist = mxUtils.ptSegDistSq(p0.x, p0.y, pe.x, pe.y, x, y);
+
+ if (dist <= minDist)
+ {
+ minDist = dist;
+ index = i - 1;
+ length = tmp;
+ }
+
+ p0 = pe;
+ }
+
+ var seg = segments[index];
+ p0 = edgeState.absolutePoints[index];
+ pe = edgeState.absolutePoints[index + 1];
+
+ var x2 = p0.x;
+ var y2 = p0.y;
+
+ var x1 = pe.x;
+ var y1 = pe.y;
+
+ var px = x;
+ var py = y;
+
+ var xSegment = x2 - x1;
+ var ySegment = y2 - y1;
+
+ px -= x1;
+ py -= y1;
+ var projlenSq = 0;
+
+ px = xSegment - px;
+ py = ySegment - py;
+ var dotprod = px * xSegment + py * ySegment;
+
+ if (dotprod <= 0.0)
+ {
+ projlenSq = 0;
+ }
+ else
+ {
+ projlenSq = dotprod * dotprod
+ / (xSegment * xSegment + ySegment * ySegment);
+ }
+
+ var projlen = Math.sqrt(projlenSq);
+
+ if (projlen > seg)
+ {
+ projlen = seg;
+ }
+
+ var yDistance = Math.sqrt(mxUtils.ptSegDistSq(p0.x, p0.y, pe
+ .x, pe.y, x, y));
+ var direction = mxUtils.relativeCcw(p0.x, p0.y, pe.x, pe.y, x, y);
+
+ if (direction == -1)
+ {
+ yDistance = -yDistance;
+ }
+
+ // Constructs the relative point for the label
+ return new mxPoint(((totalLength / 2 - length - projlen) / totalLength) * -2,
+ yDistance / this.scale);
+ }
+ }
+
+ return new mxPoint();
+};
+
+/**
+ * Function: updateEdgeLabelOffset
+ *
+ * Updates <mxCellState.absoluteOffset> for the given state. The absolute
+ * offset is normally used for the position of the edge label. Is is
+ * calculated from the geometry as an absolute offset from the center
+ * between the two endpoints if the geometry is absolute, or as the
+ * relative distance between the center along the line and the absolute
+ * orthogonal distance if the geometry is relative.
+ *
+ * Parameters:
+ *
+ * state - <mxCellState> whose absolute offset should be updated.
+ */
+mxGraphView.prototype.updateEdgeLabelOffset = function(state)
+{
+ var points = state.absolutePoints;
+
+ state.absoluteOffset.x = state.getCenterX();
+ state.absoluteOffset.y = state.getCenterY();
+
+ if (points != null && points.length > 0 && state.segments != null)
+ {
+ var geometry = this.graph.getCellGeometry(state.cell);
+
+ if (geometry.relative)
+ {
+ var offset = this.getPoint(state, geometry);
+
+ if (offset != null)
+ {
+ state.absoluteOffset = offset;
+ }
+ }
+ else
+ {
+ var p0 = points[0];
+ var pe = points[points.length - 1];
+
+ if (p0 != null && pe != null)
+ {
+ var dx = pe.x - p0.x;
+ var dy = pe.y - p0.y;
+ var x0 = 0;
+ var y0 = 0;
+
+ var off = geometry.offset;
+
+ if (off != null)
+ {
+ x0 = off.x;
+ y0 = off.y;
+ }
+
+ var x = p0.x + dx / 2 + x0 * this.scale;
+ var y = p0.y + dy / 2 + y0 * this.scale;
+
+ state.absoluteOffset.x = x;
+ state.absoluteOffset.y = y;
+ }
+ }
+ }
+};
+
+/**
+ * Function: getState
+ *
+ * Returns the <mxCellState> for the given cell. If create is true, then
+ * the state is created if it does not yet exist.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the <mxCellState> should be returned.
+ * create - Optional boolean indicating if a new state should be created
+ * if it does not yet exist. Default is false.
+ */
+mxGraphView.prototype.getState = function(cell, create)
+{
+ create = create || false;
+ var state = null;
+
+ if (cell != null)
+ {
+ state = this.states.get(cell);
+
+ if (this.graph.isCellVisible(cell))
+ {
+ if (state == null && create && this.graph.isCellVisible(cell))
+ {
+ state = this.createState(cell);
+ this.states.put(cell, state);
+ }
+ else if (create && state != null && this.updateStyle)
+ {
+ state.style = this.graph.getCellStyle(cell);
+ }
+ }
+ }
+
+ return state;
+};
+
+/**
+ * Function: isRendering
+ *
+ * Returns <rendering>.
+ */
+mxGraphView.prototype.isRendering = function()
+{
+ return this.rendering;
+};
+
+/**
+ * Function: setRendering
+ *
+ * Sets <rendering>.
+ */
+mxGraphView.prototype.setRendering = function(value)
+{
+ this.rendering = value;
+};
+
+/**
+ * Function: isAllowEval
+ *
+ * Returns <allowEval>.
+ */
+mxGraphView.prototype.isAllowEval = function()
+{
+ return this.allowEval;
+};
+
+/**
+ * Function: setAllowEval
+ *
+ * Sets <allowEval>.
+ */
+mxGraphView.prototype.setAllowEval = function(value)
+{
+ this.allowEval = value;
+};
+
+/**
+ * Function: getStates
+ *
+ * Returns <states>.
+ */
+mxGraphView.prototype.getStates = function()
+{
+ return this.states;
+};
+
+/**
+ * Function: setStates
+ *
+ * Sets <states>.
+ */
+mxGraphView.prototype.setStates = function(value)
+{
+ this.states = value;
+};
+
+/**
+ * Function: getCellStates
+ *
+ * Returns the <mxCellStates> for the given array of <mxCells>. The array
+ * contains all states that are not null, that is, the returned array may
+ * have less elements than the given array. If no argument is given, then
+ * this returns <states>.
+ */
+mxGraphView.prototype.getCellStates = function(cells)
+{
+ if (cells == null)
+ {
+ return this.states;
+ }
+ else
+ {
+ var result = [];
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var state = this.getState(cells[i]);
+
+ if (state != null)
+ {
+ result.push(state);
+ }
+ }
+
+ return result;
+ }
+};
+
+/**
+ * Function: removeState
+ *
+ * Removes and returns the <mxCellState> for the given cell.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which the <mxCellState> should be removed.
+ */
+mxGraphView.prototype.removeState = function(cell)
+{
+ var state = null;
+
+ if (cell != null)
+ {
+ state = this.states.remove(cell);
+
+ if (state != null)
+ {
+ this.graph.cellRenderer.destroy(state);
+ state.destroy();
+ }
+ }
+
+ return state;
+};
+
+/**
+ * Function: createState
+ *
+ * Creates and returns an <mxCellState> for the given cell and initializes
+ * it using <mxCellRenderer.initialize>.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> for which a new <mxCellState> should be created.
+ */
+mxGraphView.prototype.createState = function(cell)
+{
+ var style = this.graph.getCellStyle(cell);
+ var state = new mxCellState(this, cell, style);
+ this.graph.cellRenderer.initialize(state, this.isRendering());
+
+ return state;
+};
+
+/**
+ * Function: getCanvas
+ *
+ * Returns the DOM node that contains the background-, draw- and
+ * overlaypane.
+ */
+mxGraphView.prototype.getCanvas = function()
+{
+ return this.canvas;
+};
+
+/**
+ * Function: getBackgroundPane
+ *
+ * Returns the DOM node that represents the background layer.
+ */
+mxGraphView.prototype.getBackgroundPane = function()
+{
+ return this.backgroundPane;
+};
+
+/**
+ * Function: getDrawPane
+ *
+ * Returns the DOM node that represents the main drawing layer.
+ */
+mxGraphView.prototype.getDrawPane = function()
+{
+ return this.drawPane;
+};
+
+/**
+ * Function: getOverlayPane
+ *
+ * Returns the DOM node that represents the topmost drawing layer.
+ */
+mxGraphView.prototype.getOverlayPane = function()
+{
+ return this.overlayPane;
+};
+
+/**
+ * Function: isContainerEvent
+ *
+ * Returns true if the event origin is one of the drawing panes or
+ * containers of the view.
+ */
+mxGraphView.prototype.isContainerEvent = function(evt)
+{
+ var source = mxEvent.getSource(evt);
+
+ return (source == this.graph.container ||
+ source.parentNode == this.backgroundPane ||
+ (source.parentNode != null &&
+ source.parentNode.parentNode == this.backgroundPane) ||
+ source == this.canvas.parentNode ||
+ source == this.canvas ||
+ source == this.backgroundPane ||
+ source == this.drawPane ||
+ source == this.overlayPane);
+};
+
+/**
+ * Function: isScrollEvent
+ *
+ * Returns true if the event origin is one of the scrollbars of the
+ * container in IE. Such events are ignored.
+ */
+ mxGraphView.prototype.isScrollEvent = function(evt)
+{
+ var offset = mxUtils.getOffset(this.graph.container);
+ var pt = new mxPoint(evt.clientX - offset.x, evt.clientY - offset.y);
+
+ var outWidth = this.graph.container.offsetWidth;
+ var inWidth = this.graph.container.clientWidth;
+
+ if (outWidth > inWidth && pt.x > inWidth + 2 && pt.x <= outWidth)
+ {
+ return true;
+ }
+
+ var outHeight = this.graph.container.offsetHeight;
+ var inHeight = this.graph.container.clientHeight;
+
+ if (outHeight > inHeight && pt.y > inHeight + 2 && pt.y <= outHeight)
+ {
+ return true;
+ }
+
+ return false;
+};
+
+/**
+ * Function: init
+ *
+ * Initializes the graph event dispatch loop for the specified container
+ * and invokes <create> to create the required DOM nodes for the display.
+ */
+mxGraphView.prototype.init = function()
+{
+ this.installListeners();
+
+ // Creates the DOM nodes for the respective display dialect
+ var graph = this.graph;
+
+ if (graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ this.createSvg();
+ }
+ else if (graph.dialect == mxConstants.DIALECT_VML)
+ {
+ this.createVml();
+ }
+ else
+ {
+ this.createHtml();
+ }
+};
+
+/**
+ * Function: installListeners
+ *
+ * Installs the required listeners in the container.
+ */
+mxGraphView.prototype.installListeners = function()
+{
+ var graph = this.graph;
+ var container = graph.container;
+
+ if (container != null)
+ {
+ var md = (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown';
+ var mm = (mxClient.IS_TOUCH) ? 'touchmove' : 'mousemove';
+ var mu = (mxClient.IS_TOUCH) ? 'touchend' : 'mouseup';
+
+ // Adds basic listeners for graph event dispatching
+ mxEvent.addListener(container, md,
+ mxUtils.bind(this, function(evt)
+ {
+ // Workaround for touch-based device not transferring
+ // the focus while editing with virtual keyboard
+ if (mxClient.IS_TOUCH && graph.isEditing())
+ {
+ graph.stopEditing(!graph.isInvokesStopCellEditing());
+ }
+
+ // Condition to avoid scrollbar events starting a rubberband
+ // selection
+ if (this.isContainerEvent(evt) && ((!mxClient.IS_IE &&
+ !mxClient.IS_GC && !mxClient.IS_OP && !mxClient.IS_SF) ||
+ !this.isScrollEvent(evt)))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_DOWN,
+ new mxMouseEvent(evt));
+ }
+ })
+ );
+ mxEvent.addListener(container, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isContainerEvent(evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt));
+ }
+ })
+ );
+ mxEvent.addListener(container, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.isContainerEvent(evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt));
+ }
+ })
+ );
+
+ // Adds listener for double click handling on background
+ mxEvent.addListener(container, 'dblclick',
+ mxUtils.bind(this, function(evt)
+ {
+ graph.dblClick(evt);
+ })
+ );
+
+ // Workaround for touch events which started on some DOM node
+ // on top of the container, in which case the cells under the
+ // mouse for the move and up events are not detected.
+ var getState = function(evt)
+ {
+ var state = null;
+
+ // Workaround for touch events which started on some DOM node
+ // on top of the container, in which case the cells under the
+ // mouse for the move and up events are not detected.
+ if (mxClient.IS_TOUCH)
+ {
+ var x = mxEvent.getClientX(evt);
+ var y = mxEvent.getClientY(evt);
+
+ // Dispatches the drop event to the graph which
+ // consumes and executes the source function
+ var pt = mxUtils.convertPoint(container, x, y);
+ state = graph.view.getState(graph.getCellAt(pt.x, pt.y));
+ }
+
+ return state;
+ };
+
+ // Adds basic listeners for graph event dispatching outside of the
+ // container and finishing the handling of a single gesture
+ // Implemented via graph event dispatch loop to avoid duplicate events
+ // in Firefox and Chrome
+ graph.addMouseListener(
+ {
+ mouseDown: function(sender, me)
+ {
+ graph.panningHandler.hideMenu();
+ },
+ mouseMove: function() { },
+ mouseUp: function() { }
+ });
+ mxEvent.addListener(document, mm,
+ mxUtils.bind(this, function(evt)
+ {
+ // Hides the tooltip if mouse is outside container
+ if (graph.tooltipHandler != null &&
+ graph.tooltipHandler.isHideOnHover())
+ {
+ graph.tooltipHandler.hide();
+ }
+
+ if (this.captureDocumentGesture && graph.isMouseDown &&
+ !mxEvent.isConsumed(evt))
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_MOVE,
+ new mxMouseEvent(evt, getState(evt)));
+ }
+ })
+ );
+ mxEvent.addListener(document, mu,
+ mxUtils.bind(this, function(evt)
+ {
+ if (this.captureDocumentGesture)
+ {
+ graph.fireMouseEvent(mxEvent.MOUSE_UP,
+ new mxMouseEvent(evt));
+ }
+ })
+ );
+ }
+};
+
+/**
+ * Function: create
+ *
+ * Creates the DOM nodes for the HTML display.
+ */
+mxGraphView.prototype.createHtml = function()
+{
+ var container = this.graph.container;
+
+ if (container != null)
+ {
+ this.canvas = this.createHtmlPane('100%', '100%');
+
+ // Uses minimal size for inner DIVs on Canvas. This is required
+ // for correct event processing in IE. If we have an overlapping
+ // DIV then the events on the cells are only fired for labels.
+ this.backgroundPane = this.createHtmlPane('1px', '1px');
+ this.drawPane = this.createHtmlPane('1px', '1px');
+ this.overlayPane = this.createHtmlPane('1px', '1px');
+
+ this.canvas.appendChild(this.backgroundPane);
+ this.canvas.appendChild(this.drawPane);
+ this.canvas.appendChild(this.overlayPane);
+
+ container.appendChild(this.canvas);
+
+ // Implements minWidth/minHeight in quirks mode
+ if (mxClient.IS_QUIRKS)
+ {
+ var onResize = mxUtils.bind(this, function(evt)
+ {
+ var bounds = this.getGraphBounds();
+ var width = bounds.x + bounds.width + this.graph.border;
+ var height = bounds.y + bounds.height + this.graph.border;
+
+ this.updateHtmlCanvasSize(width, height);
+ });
+
+ mxEvent.addListener(window, 'resize', onResize);
+ }
+ }
+};
+
+/**
+ * Function: updateHtmlCanvasSize
+ *
+ * Updates the size of the HTML canvas.
+ */
+mxGraphView.prototype.updateHtmlCanvasSize = function(width, height)
+{
+ if (this.graph.container != null)
+ {
+ var ow = this.graph.container.offsetWidth;
+ var oh = this.graph.container.offsetHeight;
+
+ if (ow < width)
+ {
+ this.canvas.style.width = width + 'px';
+ }
+ else
+ {
+ this.canvas.style.width = '100%';
+ }
+
+ if (oh < height)
+ {
+ this.canvas.style.height = height + 'px';
+ }
+ else
+ {
+ this.canvas.style.height = '100%';
+ }
+ }
+};
+
+/**
+ * Function: createHtmlPane
+ *
+ * Creates and returns a drawing pane in HTML (DIV).
+ */
+mxGraphView.prototype.createHtmlPane = function(width, height)
+{
+ var pane = document.createElement('DIV');
+
+ if (width != null && height != null)
+ {
+ pane.style.position = 'absolute';
+ pane.style.left = '0px';
+ pane.style.top = '0px';
+
+ pane.style.width = width;
+ pane.style.height = height;
+ }
+ else
+ {
+ pane.style.position = 'relative';
+ }
+
+ return pane;
+};
+
+/**
+ * Function: create
+ *
+ * Creates the DOM nodes for the VML display.
+ */
+mxGraphView.prototype.createVml = function()
+{
+ var container = this.graph.container;
+
+ if (container != null)
+ {
+ var width = container.offsetWidth;
+ var height = container.offsetHeight;
+ this.canvas = this.createVmlPane(width, height);
+
+ this.backgroundPane = this.createVmlPane(width, height);
+ this.drawPane = this.createVmlPane(width, height);
+ this.overlayPane = this.createVmlPane(width, height);
+
+ this.canvas.appendChild(this.backgroundPane);
+ this.canvas.appendChild(this.drawPane);
+ this.canvas.appendChild(this.overlayPane);
+
+ container.appendChild(this.canvas);
+ }
+};
+
+/**
+ * Function: createVmlPane
+ *
+ * Creates a drawing pane in VML (group).
+ */
+mxGraphView.prototype.createVmlPane = function(width, height)
+{
+ var pane = document.createElement('v:group');
+
+ // At this point the width and height are potentially
+ // uninitialized. That's OK.
+ pane.style.position = 'absolute';
+ pane.style.left = '0px';
+ pane.style.top = '0px';
+
+ pane.style.width = width+'px';
+ pane.style.height = height+'px';
+
+ pane.setAttribute('coordsize', width+','+height);
+ pane.setAttribute('coordorigin', '0,0');
+
+ return pane;
+};
+
+/**
+ * Function: create
+ *
+ * Creates and returns the DOM nodes for the SVG display.
+ */
+mxGraphView.prototype.createSvg = function()
+{
+ var container = this.graph.container;
+ this.canvas = document.createElementNS(mxConstants.NS_SVG, 'g');
+
+ // For background image
+ this.backgroundPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+ this.canvas.appendChild(this.backgroundPane);
+
+ // Adds two layers (background is early feature)
+ this.drawPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+ this.canvas.appendChild(this.drawPane);
+
+ this.overlayPane = document.createElementNS(mxConstants.NS_SVG, 'g');
+ this.canvas.appendChild(this.overlayPane);
+
+ var root = document.createElementNS(mxConstants.NS_SVG, 'svg');
+ root.style.width = '100%';
+ root.style.height = '100%';
+
+ if (mxClient.IS_IE)
+ {
+ root.style.marginBottom = '-4px';
+ }
+
+ root.appendChild(this.canvas);
+
+ if (container != null)
+ {
+ container.appendChild(root);
+
+ // Workaround for offset of container
+ var style = mxUtils.getCurrentStyle(container);
+
+ if (style.position == 'static')
+ {
+ container.style.position = 'relative';
+ }
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroys the view and all its resources.
+ */
+mxGraphView.prototype.destroy = function()
+{
+ var root = (this.canvas != null) ? this.canvas.ownerSVGElement : null;
+
+ if (root == null)
+ {
+ root = this.canvas;
+ }
+
+ if (root != null && root.parentNode != null)
+ {
+ this.clear(this.currentRoot, true);
+ mxEvent.removeAllListeners(document);
+ mxEvent.release(this.graph.container);
+ root.parentNode.removeChild(root);
+
+ this.canvas = null;
+ this.backgroundPane = null;
+ this.drawPane = null;
+ this.overlayPane = null;
+ }
+};
+
+/**
+ * Class: mxCurrentRootChange
+ *
+ * Action to change the current root in a view.
+ *
+ * Constructor: mxCurrentRootChange
+ *
+ * Constructs a change of the current root in the given view.
+ */
+function mxCurrentRootChange(view, root)
+{
+ this.view = view;
+ this.root = root;
+ this.previous = root;
+ this.isUp = root == null;
+
+ if (!this.isUp)
+ {
+ var tmp = this.view.currentRoot;
+ var model = this.view.graph.getModel();
+
+ while (tmp != null)
+ {
+ if (tmp == root)
+ {
+ this.isUp = true;
+ break;
+ }
+
+ tmp = model.getParent(tmp);
+ }
+ }
+};
+
+/**
+ * Function: execute
+ *
+ * Changes the current root of the view.
+ */
+mxCurrentRootChange.prototype.execute = function()
+{
+ var tmp = this.view.currentRoot;
+ this.view.currentRoot = this.previous;
+ this.previous = tmp;
+
+ var translate = this.view.graph.getTranslateForRoot(this.view.currentRoot);
+
+ if (translate != null)
+ {
+ this.view.translate = new mxPoint(-translate.x, -translate.y);
+ }
+
+ var name = (this.isUp) ? mxEvent.UP : mxEvent.DOWN;
+ this.view.fireEvent(new mxEventObject(name,
+ 'root', this.view.currentRoot, 'previous', this.previous));
+
+ if (this.isUp)
+ {
+ this.view.clear(this.view.currentRoot, true);
+ this.view.validate();
+ }
+ else
+ {
+ this.view.refresh();
+ }
+
+ this.isUp = !this.isUp;
+};
diff --git a/src/js/view/mxLayoutManager.js b/src/js/view/mxLayoutManager.js
new file mode 100644
index 0000000..ee8ec65
--- /dev/null
+++ b/src/js/view/mxLayoutManager.js
@@ -0,0 +1,375 @@
+/**
+ * $Id: mxLayoutManager.js,v 1.21 2012-01-04 10:01:16 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxLayoutManager
+ *
+ * Implements a layout manager that updates the layout for a given transaction.
+ *
+ * Example:
+ *
+ * (code)
+ * var layoutMgr = new mxLayoutManager(graph);
+ * layoutMgr.getLayout = function(cell)
+ * {
+ * return layout;
+ * };
+ * (end)
+ *
+ * Event: mxEvent.LAYOUT_CELLS
+ *
+ * Fires between begin- and endUpdate after all cells have been layouted in
+ * <layoutCells>. The <code>cells</code> property contains all cells that have
+ * been passed to <layoutCells>.
+ *
+ * Constructor: mxLayoutManager
+ *
+ * Constructs a new automatic layout for the given graph.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing graph.
+ */
+function mxLayoutManager(graph)
+{
+ // Executes the layout before the changes are dispatched
+ this.undoHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.beforeUndo(evt.getProperty('edit'));
+ }
+ });
+
+ // Notifies the layout of a move operation inside a parent
+ this.moveHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.cellsMoved(evt.getProperty('cells'), evt.getProperty('event'));
+ }
+ });
+
+ this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxLayoutManager.prototype = new mxEventSource();
+mxLayoutManager.prototype.constructor = mxLayoutManager;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxLayoutManager.prototype.graph = null;
+
+/**
+ * Variable: bubbling
+ *
+ * Specifies if the layout should bubble along
+ * the cell hierarchy. Default is true.
+ */
+mxLayoutManager.prototype.bubbling = true;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxLayoutManager.prototype.enabled = true;
+
+/**
+ * Variable: updateHandler
+ *
+ * Holds the function that handles the endUpdate event.
+ */
+mxLayoutManager.prototype.updateHandler = null;
+
+/**
+ * Variable: moveHandler
+ *
+ * Holds the function that handles the move event.
+ */
+mxLayoutManager.prototype.moveHandler = null;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxLayoutManager.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxLayoutManager.prototype.setEnabled = function(enabled)
+{
+ this.enabled = enabled;
+};
+
+/**
+ * Function: isBubbling
+ *
+ * Returns true if a layout should bubble, that is, if the parent layout
+ * should be executed whenever a cell layout (layout of the children of
+ * a cell) has been executed. This implementation returns <bubbling>.
+ */
+mxLayoutManager.prototype.isBubbling = function()
+{
+ return this.bubbling;
+};
+
+/**
+ * Function: setBubbling
+ *
+ * Sets <bubbling>.
+ */
+mxLayoutManager.prototype.setBubbling = function(value)
+{
+ this.bubbling = value;
+};
+
+/**
+ * Function: getGraph
+ *
+ * Returns the graph that this layout operates on.
+ */
+mxLayoutManager.prototype.getGraph = function()
+{
+ return this.graph;
+};
+
+/**
+ * Function: setGraph
+ *
+ * Sets the graph that the layouts operate on.
+ */
+mxLayoutManager.prototype.setGraph = function(graph)
+{
+ if (this.graph != null)
+ {
+ var model = this.graph.getModel();
+ model.removeListener(this.undoHandler);
+ this.graph.removeListener(this.moveHandler);
+ }
+
+ this.graph = graph;
+
+ if (this.graph != null)
+ {
+ var model = this.graph.getModel();
+ model.addListener(mxEvent.BEFORE_UNDO, this.undoHandler);
+ this.graph.addListener(mxEvent.MOVE_CELLS, this.moveHandler);
+ }
+};
+
+/**
+ * Function: getLayout
+ *
+ * Returns the layout to be executed for the given graph and parent.
+ */
+mxLayoutManager.prototype.getLayout = function(parent)
+{
+ return null;
+};
+
+/**
+ * Function: beforeUndo
+ *
+ * Called from the undoHandler.
+ *
+ * Parameters:
+ *
+ * cell - Array of <mxCells> that have been moved.
+ * evt - Mouse event that represents the mousedown.
+ */
+mxLayoutManager.prototype.beforeUndo = function(undoableEdit)
+{
+ var cells = this.getCellsForChanges(undoableEdit.changes);
+ var model = this.getGraph().getModel();
+
+ // Adds all parent ancestors
+ if (this.isBubbling())
+ {
+ var tmp = model.getParents(cells);
+
+ while (tmp.length > 0)
+ {
+ cells = cells.concat(tmp);
+ tmp = model.getParents(tmp);
+ }
+ }
+
+ this.layoutCells(mxUtils.sortCells(cells, false));
+};
+
+/**
+ * Function: cellsMoved
+ *
+ * Called from the moveHandler.
+ *
+ * Parameters:
+ *
+ * cell - Array of <mxCells> that have been moved.
+ * evt - Mouse event that represents the mousedown.
+ */
+mxLayoutManager.prototype.cellsMoved = function(cells, evt)
+{
+ if (cells != null &&
+ evt != null)
+ {
+ var point = mxUtils.convertPoint(this.getGraph().container,
+ mxEvent.getClientX(evt), mxEvent.getClientY(evt));
+ var model = this.getGraph().getModel();
+
+ // Checks if a layout exists to take care of the moving
+ for (var i = 0; i < cells.length; i++)
+ {
+ var layout = this.getLayout(model.getParent(cells[i]));
+
+ if (layout != null)
+ {
+ layout.moveCell(cells[i], point.x, point.y);
+ }
+ }
+ }
+};
+
+/**
+ * Function: getCellsForEdit
+ *
+ * Returns the cells to be layouted for the given sequence of changes.
+ */
+mxLayoutManager.prototype.getCellsForChanges = function(changes)
+{
+ var result = [];
+ var hash = new Object();
+
+ for (var i = 0; i < changes.length; i++)
+ {
+ var change = changes[i];
+
+ if (change instanceof mxRootChange)
+ {
+ return [];
+ }
+ else
+ {
+ var cells = this.getCellsForChange(change);
+
+ for (var j = 0; j < cells.length; j++)
+ {
+ if (cells[j] != null)
+ {
+ var id = mxCellPath.create(cells[j]);
+
+ if (hash[id] == null)
+ {
+ hash[id] = cells[j];
+ result.push(cells[j]);
+ }
+ }
+ }
+ }
+ }
+
+ return result;
+};
+
+/**
+ * Function: getCellsForChange
+ *
+ * Executes all layouts which have been scheduled during the
+ * changes.
+ */
+mxLayoutManager.prototype.getCellsForChange = function(change)
+{
+ var model = this.getGraph().getModel();
+
+ if (change instanceof mxChildChange)
+ {
+ return [change.child, change.previous, model.getParent(change.child)];
+ }
+ else if (change instanceof mxTerminalChange ||
+ change instanceof mxGeometryChange)
+ {
+ return [change.cell, model.getParent(change.cell)];
+ }
+
+ return [];
+};
+
+/**
+ * Function: layoutCells
+ *
+ * Executes all layouts which have been scheduled during the
+ * changes.
+ */
+mxLayoutManager.prototype.layoutCells = function(cells)
+{
+ if (cells.length > 0)
+ {
+ // Invokes the layouts while removing duplicates
+ var model = this.getGraph().getModel();
+
+ model.beginUpdate();
+ try
+ {
+ var last = null;
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (cells[i] != model.getRoot() &&
+ cells[i] != last)
+ {
+ last = cells[i];
+ this.executeLayout(this.getLayout(last), last);
+ }
+ }
+
+ this.fireEvent(new mxEventObject(mxEvent.LAYOUT_CELLS, 'cells', cells));
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: executeLayout
+ *
+ * Executes the given layout on the given parent.
+ */
+mxLayoutManager.prototype.executeLayout = function(layout, parent)
+{
+ if (layout != null && parent != null)
+ {
+ layout.execute(parent);
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxLayoutManager.prototype.destroy = function()
+{
+ this.setGraph(null);
+};
diff --git a/src/js/view/mxMultiplicity.js b/src/js/view/mxMultiplicity.js
new file mode 100644
index 0000000..c927d3f
--- /dev/null
+++ b/src/js/view/mxMultiplicity.js
@@ -0,0 +1,257 @@
+/**
+ * $Id: mxMultiplicity.js,v 1.24 2010-11-03 14:52:40 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxMultiplicity
+ *
+ * Defines invalid connections along with the error messages that they produce.
+ * To add or remove rules on a graph, you must add/remove instances of this
+ * class to <mxGraph.multiplicities>.
+ *
+ * Example:
+ *
+ * (code)
+ * graph.multiplicities.push(new mxMultiplicity(
+ * true, 'rectangle', null, null, 0, 2, ['circle'],
+ * 'Only 2 targets allowed',
+ * 'Only circle targets allowed'));
+ * (end)
+ *
+ * Defines a rule where each rectangle must be connected to no more than 2
+ * circles and no other types of targets are allowed.
+ *
+ * Constructor: mxMultiplicity
+ *
+ * Instantiate class mxMultiplicity in order to describe allowed
+ * connections in a graph. Not all constraints can be enforced while
+ * editing, some must be checked at validation time. The <countError> and
+ * <typeError> are treated as resource keys in <mxResources>.
+ *
+ * Parameters:
+ *
+ * source - Boolean indicating if this rule applies to the source or target
+ * terminal.
+ * type - Type of the source or target terminal that this rule applies to.
+ * See <type> for more information.
+ * attr - Optional attribute name to match the source or target terminal.
+ * value - Optional attribute value to match the source or target terminal.
+ * min - Minimum number of edges for this rule. Default is 1.
+ * max - Maximum number of edges for this rule. n means infinite. Default
+ * is n.
+ * validNeighbors - Array of types of the opposite terminal for which this
+ * rule applies.
+ * countError - Error to be displayed for invalid number of edges.
+ * typeError - Error to be displayed for invalid opposite terminals.
+ * validNeighborsAllowed - Optional boolean indicating if the array of
+ * opposite types should be valid or invalid.
+ */
+function mxMultiplicity(source, type, attr, value, min, max,
+ validNeighbors, countError, typeError, validNeighborsAllowed)
+{
+ this.source = source;
+ this.type = type;
+ this.attr = attr;
+ this.value = value;
+ this.min = (min != null) ? min : 0;
+ this.max = (max != null) ? max : 'n';
+ this.validNeighbors = validNeighbors;
+ this.countError = mxResources.get(countError) || countError;
+ this.typeError = mxResources.get(typeError) || typeError;
+ this.validNeighborsAllowed = (validNeighborsAllowed != null) ?
+ validNeighborsAllowed : true;
+};
+
+/**
+ * Variable: type
+ *
+ * Defines the type of the source or target terminal. The type is a string
+ * passed to <mxUtils.isNode> together with the source or target vertex
+ * value as the first argument.
+ */
+mxMultiplicity.prototype.type = null;
+
+/**
+ * Variable: attr
+ *
+ * Optional string that specifies the attributename to be passed to
+ * <mxUtils.isNode> to check if the rule applies to a cell.
+ */
+mxMultiplicity.prototype.attr = null;
+
+/**
+ * Variable: value
+ *
+ * Optional string that specifies the value of the attribute to be passed
+ * to <mxUtils.isNode> to check if the rule applies to a cell.
+ */
+mxMultiplicity.prototype.value = null;
+
+/**
+ * Variable: source
+ *
+ * Boolean that specifies if the rule is applied to the source or target
+ * terminal of an edge.
+ */
+mxMultiplicity.prototype.source = null;
+
+/**
+ * Variable: min
+ *
+ * Defines the minimum number of connections for which this rule applies.
+ * Default is 0.
+ */
+mxMultiplicity.prototype.min = null;
+
+/**
+ * Variable: max
+ *
+ * Defines the maximum number of connections for which this rule applies.
+ * A value of 'n' means unlimited times. Default is 'n'.
+ */
+mxMultiplicity.prototype.max = null;
+
+/**
+ * Variable: validNeighbors
+ *
+ * Holds an array of strings that specify the type of neighbor for which
+ * this rule applies. The strings are used in <mxCell.is> on the opposite
+ * terminal to check if the rule applies to the connection.
+ */
+mxMultiplicity.prototype.validNeighbors = null;
+
+/**
+ * Variable: validNeighborsAllowed
+ *
+ * Boolean indicating if the list of validNeighbors are those that are allowed
+ * for this rule or those that are not allowed for this rule.
+ */
+mxMultiplicity.prototype.validNeighborsAllowed = true;
+
+/**
+ * Variable: countError
+ *
+ * Holds the localized error message to be displayed if the number of
+ * connections for which the rule applies is smaller than <min> or greater
+ * than <max>.
+ */
+mxMultiplicity.prototype.countError = null;
+
+/**
+ * Variable: typeError
+ *
+ * Holds the localized error message to be displayed if the type of the
+ * neighbor for a connection does not match the rule.
+ */
+mxMultiplicity.prototype.typeError = null;
+
+/**
+ * Function: check
+ *
+ * Checks the multiplicity for the given arguments and returns the error
+ * for the given connection or null if the multiplicity does not apply.
+ *
+ * Parameters:
+ *
+ * graph - Reference to the enclosing <mxGraph> instance.
+ * edge - <mxCell> that represents the edge to validate.
+ * source - <mxCell> that represents the source terminal.
+ * target - <mxCell> that represents the target terminal.
+ * sourceOut - Number of outgoing edges from the source terminal.
+ * targetIn - Number of incoming edges for the target terminal.
+ */
+mxMultiplicity.prototype.check = function(graph, edge, source, target, sourceOut, targetIn)
+{
+ var error = '';
+
+ if ((this.source && this.checkTerminal(graph, source, edge)) ||
+ (!this.source && this.checkTerminal(graph, target, edge)))
+ {
+ if (this.countError != null &&
+ ((this.source && (this.max == 0 || (sourceOut >= this.max))) ||
+ (!this.source && (this.max == 0 || (targetIn >= this.max)))))
+ {
+ error += this.countError + '\n';
+ }
+
+ if (this.validNeighbors != null && this.typeError != null && this.validNeighbors.length > 0)
+ {
+ var isValid = this.checkNeighbors(graph, edge, source, target);
+
+ if (!isValid)
+ {
+ error += this.typeError + '\n';
+ }
+ }
+ }
+
+ return (error.length > 0) ? error : null;
+};
+
+/**
+ * Function: checkNeighbors
+ *
+ * Checks if there are any valid neighbours in <validNeighbors>. This is only
+ * called if <validNeighbors> is a non-empty array.
+ */
+mxMultiplicity.prototype.checkNeighbors = function(graph, edge, source, target)
+{
+ var sourceValue = graph.model.getValue(source);
+ var targetValue = graph.model.getValue(target);
+ var isValid = !this.validNeighborsAllowed;
+ var valid = this.validNeighbors;
+
+ for (var j = 0; j < valid.length; j++)
+ {
+ if (this.source &&
+ this.checkType(graph, targetValue, valid[j]))
+ {
+ isValid = this.validNeighborsAllowed;
+ break;
+ }
+ else if (!this.source &&
+ this.checkType(graph, sourceValue, valid[j]))
+ {
+ isValid = this.validNeighborsAllowed;
+ break;
+ }
+ }
+
+ return isValid;
+};
+
+/**
+ * Function: checkTerminal
+ *
+ * Checks the given terminal cell and returns true if this rule applies. The
+ * given cell is the source or target of the given edge, depending on
+ * <source>. This implementation uses <checkType> on the terminal's value.
+ */
+mxMultiplicity.prototype.checkTerminal = function(graph, terminal, edge)
+{
+ var value = graph.model.getValue(terminal);
+
+ return this.checkType(graph, value, this.type, this.attr, this.value);
+};
+
+/**
+ * Function: checkType
+ *
+ * Checks the type of the given value.
+ */
+mxMultiplicity.prototype.checkType = function(graph, value, type, attr, attrValue)
+{
+ if (value != null)
+ {
+ if (!isNaN(value.nodeType)) // Checks if value is a DOM node
+ {
+ return mxUtils.isNode(value, type, attr, attrValue);
+ }
+ else
+ {
+ return value == type;
+ }
+ }
+
+ return false;
+};
diff --git a/src/js/view/mxOutline.js b/src/js/view/mxOutline.js
new file mode 100644
index 0000000..a0d6fd3
--- /dev/null
+++ b/src/js/view/mxOutline.js
@@ -0,0 +1,649 @@
+/**
+ * $Id: mxOutline.js,v 1.81 2012-06-20 14:13:37 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxOutline
+ *
+ * Implements an outline (aka overview) for a graph. Set <updateOnPan> to true
+ * to enable updates while the source graph is panning.
+ *
+ * Example:
+ *
+ * (code)
+ * var outline = new mxOutline(graph, div);
+ * (end)
+ *
+ * If the selection border in the outline appears behind the contents of the
+ * graph, then you can use the following code. (This may happen when using a
+ * transparent container for the outline in IE.)
+ *
+ * (code)
+ * mxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_EXACT;
+ * (end)
+ *
+ * To move the graph to the top, left corner the following code can be used.
+ *
+ * (code)
+ * var scale = graph.view.scale;
+ * var bounds = graph.getGraphBounds();
+ * graph.view.setTranslate(-bounds.x / scale, -bounds.y / scale);
+ * (end)
+ *
+ * To toggle the suspended mode, the following can be used.
+ *
+ * (code)
+ * outline.suspended = !outln.suspended;
+ * if (!outline.suspended)
+ * {
+ * outline.update(true);
+ * }
+ * (end)
+ *
+ * Constructor: mxOutline
+ *
+ * Constructs a new outline for the specified graph inside the given
+ * container.
+ *
+ * Parameters:
+ *
+ * source - <mxGraph> to create the outline for.
+ * container - DOM node that will contain the outline.
+ */
+function mxOutline(source, container)
+{
+ this.source = source;
+
+ if (container != null)
+ {
+ this.init(container);
+ }
+};
+
+/**
+ * Function: source
+ *
+ * Reference to the source <mxGraph>.
+ */
+mxOutline.prototype.source = null;
+
+/**
+ * Function: outline
+ *
+ * Reference to the outline <mxGraph>.
+ */
+mxOutline.prototype.outline = null;
+
+/**
+ * Function: graphRenderHint
+ *
+ * Renderhint to be used for the outline graph. Default is faster.
+ */
+mxOutline.prototype.graphRenderHint = mxConstants.RENDERING_HINT_FASTER;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if events are handled. Default is true.
+ */
+mxOutline.prototype.enabled = true;
+
+/**
+ * Variable: showViewport
+ *
+ * Specifies a viewport rectangle should be shown. Default is true.
+ */
+mxOutline.prototype.showViewport = true;
+
+/**
+ * Variable: border
+ *
+ * Border to be added at the bottom and right. Default is 10.
+ */
+mxOutline.prototype.border = 10;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies the size of the sizer handler. Default is 8.
+ */
+mxOutline.prototype.sizerSize = 8;
+
+/**
+ * Variable: updateOnPan
+ *
+ * Specifies if <update> should be called for <mxEvent.PAN> in the source
+ * graph. Default is false.
+ */
+mxOutline.prototype.updateOnPan = false;
+
+/**
+ * Variable: sizerImage
+ *
+ * Optional <mxImage> to be used for the sizer. Default is null.
+ */
+mxOutline.prototype.sizerImage = null;
+
+/**
+ * Variable: suspended
+ *
+ * Optional boolean flag to suspend updates. Default is false. This flag will
+ * also suspend repaints of the outline. To toggle this switch, use the
+ * following code.
+ *
+ * (code)
+ * nav.suspended = !nav.suspended;
+ *
+ * if (!nav.suspended)
+ * {
+ * nav.update(true);
+ * }
+ * (end)
+ */
+mxOutline.prototype.suspended = false;
+
+/**
+ * Function: init
+ *
+ * Initializes the outline inside the given container.
+ */
+mxOutline.prototype.init = function(container)
+{
+ this.outline = new mxGraph(container, this.source.getModel(), this.graphRenderHint, this.source.getStylesheet());
+ this.outline.foldingEnabled = false;
+ this.outline.autoScroll = false;
+
+ // Do not repaint when suspended
+ var outlineGraphModelChanged = this.outline.graphModelChanged;
+ this.outline.graphModelChanged = mxUtils.bind(this, function(changes)
+ {
+ if (!this.suspended && this.outline != null)
+ {
+ outlineGraphModelChanged.apply(this.outline, arguments);
+ }
+ });
+
+ // Enables faster painting in SVG
+ if (mxClient.IS_SVG)
+ {
+ var node = this.outline.getView().getCanvas().parentNode;
+ node.setAttribute('shape-rendering', 'optimizeSpeed');
+ node.setAttribute('image-rendering', 'optimizeSpeed');
+ }
+
+ // Hides cursors and labels
+ this.outline.labelsVisible = false;
+ this.outline.setEnabled(false);
+
+ this.updateHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (!this.suspended && !this.active)
+ {
+ this.update();
+ }
+ });
+
+ // Updates the scale of the outline after a change of the main graph
+ this.source.getModel().addListener(mxEvent.CHANGE, this.updateHandler);
+ this.outline.addMouseListener(this);
+
+ // Adds listeners to keep the outline in sync with the source graph
+ var view = this.source.getView();
+ view.addListener(mxEvent.SCALE, this.updateHandler);
+ view.addListener(mxEvent.TRANSLATE, this.updateHandler);
+ view.addListener(mxEvent.SCALE_AND_TRANSLATE, this.updateHandler);
+ view.addListener(mxEvent.DOWN, this.updateHandler);
+ view.addListener(mxEvent.UP, this.updateHandler);
+
+ // Updates blue rectangle on scroll
+ mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);
+
+ this.panHandler = mxUtils.bind(this, function(sender)
+ {
+ if (this.updateOnPan)
+ {
+ this.updateHandler.apply(this, arguments);
+ }
+ });
+ this.source.addListener(mxEvent.PAN, this.panHandler);
+
+ // Refreshes the graph in the outline after a refresh of the main graph
+ this.refreshHandler = mxUtils.bind(this, function(sender)
+ {
+ this.outline.setStylesheet(this.source.getStylesheet());
+ this.outline.refresh();
+ });
+ this.source.addListener(mxEvent.REFRESH, this.refreshHandler);
+
+ // Creates the blue rectangle for the viewport
+ this.bounds = new mxRectangle(0, 0, 0, 0);
+ this.selectionBorder = new mxRectangleShape(this.bounds, null,
+ mxConstants.OUTLINE_COLOR, mxConstants.OUTLINE_STROKEWIDTH);
+ this.selectionBorder.dialect =
+ (this.outline.dialect != mxConstants.DIALECT_SVG) ?
+ mxConstants.DIALECT_VML : mxConstants.DIALECT_SVG;
+ this.selectionBorder.crisp = true;
+ this.selectionBorder.init(this.outline.getView().getOverlayPane());
+ mxEvent.redirectMouseEvents(this.selectionBorder.node, this.outline);
+ this.selectionBorder.node.style.background = '';
+
+ // Creates a small blue rectangle for sizing (sizer handle)
+ this.sizer = this.createSizer();
+ this.sizer.init(this.outline.getView().getOverlayPane());
+
+ if (this.enabled)
+ {
+ this.sizer.node.style.cursor = 'pointer';
+ }
+
+ // Redirects all events from the sizerhandle to the outline
+ mxEvent.addListener(this.sizer.node, (mxClient.IS_TOUCH) ? 'touchstart' : 'mousedown',
+ mxUtils.bind(this, function(evt)
+ {
+ this.outline.fireMouseEvent(mxEvent.MOUSE_DOWN, new mxMouseEvent(evt));
+ })
+ );
+
+ this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
+ this.sizer.node.style.display = this.selectionBorder.node.style.display;
+ this.selectionBorder.node.style.cursor = 'move';
+
+ this.update(false);
+};
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxOutline.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * value - Boolean that specifies the new enabled state.
+ */
+mxOutline.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: setZoomEnabled
+ *
+ * Enables or disables the zoom handling by showing or hiding the respective
+ * handle.
+ *
+ * Parameters:
+ *
+ * value - Boolean that specifies the new enabled state.
+ */
+mxOutline.prototype.setZoomEnabled = function(value)
+{
+ this.sizer.node.style.visibility = (value) ? 'visible' : 'hidden';
+};
+
+/**
+ * Function: refresh
+ *
+ * Invokes <update> and revalidate the outline. This method is deprecated.
+ */
+mxOutline.prototype.refresh = function()
+{
+ this.update(true);
+};
+
+/**
+ * Function: createSizer
+ *
+ * Creates the shape used as the sizer.
+ */
+mxOutline.prototype.createSizer = function()
+{
+ if (this.sizerImage != null)
+ {
+ var sizer = new mxImageShape(new mxRectangle(0, 0, this.sizerImage.width, this.sizerImage.height), this.sizerImage.src);
+ sizer.dialect = this.outline.dialect;
+
+ return sizer;
+ }
+ else
+ {
+ var sizer = new mxRectangleShape(new mxRectangle(0, 0, this.sizerSize, this.sizerSize),
+ mxConstants.OUTLINE_HANDLE_FILLCOLOR, mxConstants.OUTLINE_HANDLE_STROKECOLOR);
+ sizer.dialect = this.outline.dialect;
+ sizer.crisp = true;
+
+ return sizer;
+ }
+};
+
+/**
+ * Function: getSourceContainerSize
+ *
+ * Returns the size of the source container.
+ */
+mxOutline.prototype.getSourceContainerSize = function()
+{
+ return new mxRectangle(0, 0, this.source.container.scrollWidth, this.source.container.scrollHeight);
+};
+
+/**
+ * Function: getOutlineOffset
+ *
+ * Returns the offset for drawing the outline graph.
+ */
+mxOutline.prototype.getOutlineOffset = function(scale)
+{
+ return null;
+};
+
+/**
+ * Function: update
+ *
+ * Updates the outline.
+ */
+mxOutline.prototype.update = function(revalidate)
+{
+ if (this.source != null)
+ {
+ var sourceScale = this.source.view.scale;
+ var scaledGraphBounds = this.source.getGraphBounds();
+ var unscaledGraphBounds = new mxRectangle(scaledGraphBounds.x / sourceScale + this.source.panDx,
+ scaledGraphBounds.y / sourceScale + this.source.panDy, scaledGraphBounds.width / sourceScale,
+ scaledGraphBounds.height / sourceScale);
+
+ var unscaledFinderBounds = new mxRectangle(0, 0,
+ this.source.container.clientWidth / sourceScale,
+ this.source.container.clientHeight / sourceScale);
+
+ var union = unscaledGraphBounds.clone();
+ union.add(unscaledFinderBounds);
+
+ // Zooms to the scrollable area if that is bigger than the graph
+ var size = this.getSourceContainerSize();
+ var completeWidth = Math.max(size.width / sourceScale, union.width);
+ var completeHeight = Math.max(size.height / sourceScale, union.height);
+
+ var availableWidth = Math.max(0, this.outline.container.clientWidth - this.border);
+ var availableHeight = Math.max(0, this.outline.container.clientHeight - this.border);
+
+ var outlineScale = Math.min(availableWidth / completeWidth, availableHeight / completeHeight);
+ var scale = outlineScale;
+
+ if (scale > 0)
+ {
+ if (this.outline.getView().scale != scale)
+ {
+ this.outline.getView().scale = scale;
+ revalidate = true;
+ }
+
+ var navView = this.outline.getView();
+
+ if (navView.currentRoot != this.source.getView().currentRoot)
+ {
+ navView.setCurrentRoot(this.source.getView().currentRoot);
+ }
+
+ var t = this.source.view.translate;
+ var tx = t.x + this.source.panDx;
+ var ty = t.y + this.source.panDy;
+
+ var off = this.getOutlineOffset(scale);
+
+ if (off != null)
+ {
+ tx += off.x;
+ ty += off.y;
+ }
+
+ if (unscaledGraphBounds.x < 0)
+ {
+ tx = tx - unscaledGraphBounds.x;
+ }
+ if (unscaledGraphBounds.y < 0)
+ {
+ ty = ty - unscaledGraphBounds.y;
+ }
+
+ if (navView.translate.x != tx || navView.translate.y != ty)
+ {
+ navView.translate.x = tx;
+ navView.translate.y = ty;
+ revalidate = true;
+ }
+
+ // Prepares local variables for computations
+ var t2 = navView.translate;
+ scale = this.source.getView().scale;
+ var scale2 = scale / navView.scale;
+ var scale3 = 1.0 / navView.scale;
+ var container = this.source.container;
+
+ // Updates the bounds of the viewrect in the navigation
+ this.bounds = new mxRectangle(
+ (t2.x - t.x - this.source.panDx) / scale3,
+ (t2.y - t.y - this.source.panDy) / scale3,
+ (container.clientWidth / scale2),
+ (container.clientHeight / scale2));
+
+ // Adds the scrollbar offset to the finder
+ this.bounds.x += this.source.container.scrollLeft * navView.scale / scale;
+ this.bounds.y += this.source.container.scrollTop * navView.scale / scale;
+
+ var b = this.selectionBorder.bounds;
+
+ if (b.x != this.bounds.x || b.y != this.bounds.y || b.width != this.bounds.width || b.height != this.bounds.height)
+ {
+ this.selectionBorder.bounds = this.bounds;
+ this.selectionBorder.redraw();
+ }
+
+ // Updates the bounds of the zoom handle at the bottom right
+ var b = this.sizer.bounds;
+ var b2 = new mxRectangle(this.bounds.x + this.bounds.width - b.width / 2,
+ this.bounds.y + this.bounds.height - b.height / 2, b.width, b.height);
+
+ if (b.x != b2.x || b.y != b2.y || b.width != b2.width || b.height != b2.height)
+ {
+ this.sizer.bounds = b2;
+
+ // Avoids update of visibility in redraw for VML
+ if (this.sizer.node.style.visibility != 'hidden')
+ {
+ this.sizer.redraw();
+ }
+ }
+
+ if (revalidate)
+ {
+ this.outline.view.revalidate();
+ }
+ }
+ }
+};
+
+/**
+ * Function: mouseDown
+ *
+ * Handles the event by starting a translation or zoom.
+ */
+mxOutline.prototype.mouseDown = function(sender, me)
+{
+ if (this.enabled && this.showViewport)
+ {
+ this.zoom = me.isSource(this.sizer);
+ this.startX = me.getX();
+ this.startY = me.getY();
+ this.active = true;
+
+ if (this.source.useScrollbarsForPanning &&
+ mxUtils.hasScrollbars(this.source.container))
+ {
+ this.dx0 = this.source.container.scrollLeft;
+ this.dy0 = this.source.container.scrollTop;
+ }
+ else
+ {
+ this.dx0 = 0;
+ this.dy0 = 0;
+ }
+ }
+
+ me.consume();
+};
+
+/**
+ * Function: mouseMove
+ *
+ * Handles the event by previewing the viewrect in <graph> and updating the
+ * rectangle that represents the viewrect in the outline.
+ */
+mxOutline.prototype.mouseMove = function(sender, me)
+{
+ if (this.active)
+ {
+ this.selectionBorder.node.style.display = (this.showViewport) ? '' : 'none';
+ this.sizer.node.style.display = this.selectionBorder.node.style.display;
+
+ var dx = me.getX() - this.startX;
+ var dy = me.getY() - this.startY;
+ var bounds = null;
+
+ if (!this.zoom)
+ {
+ // Previews the panning on the source graph
+ var scale = this.outline.getView().scale;
+ bounds = new mxRectangle(this.bounds.x + dx,
+ this.bounds.y + dy, this.bounds.width, this.bounds.height);
+ this.selectionBorder.bounds = bounds;
+ this.selectionBorder.redraw();
+ dx /= scale;
+ dx *= this.source.getView().scale;
+ dy /= scale;
+ dy *= this.source.getView().scale;
+ this.source.panGraph(-dx - this.dx0, -dy - this.dy0);
+ }
+ else
+ {
+ // Does *not* preview zooming on the source graph
+ var container = this.source.container;
+ var viewRatio = container.clientWidth / container.clientHeight;
+ dy = dx / viewRatio;
+ bounds = new mxRectangle(this.bounds.x,
+ this.bounds.y,
+ Math.max(1, this.bounds.width + dx),
+ Math.max(1, this.bounds.height + dy));
+ this.selectionBorder.bounds = bounds;
+ this.selectionBorder.redraw();
+ }
+
+ // Updates the zoom handle
+ var b = this.sizer.bounds;
+ this.sizer.bounds = new mxRectangle(
+ bounds.x + bounds.width - b.width / 2,
+ bounds.y + bounds.height - b.height / 2,
+ b.width, b.height);
+
+ // Avoids update of visibility in redraw for VML
+ if (this.sizer.node.style.visibility != 'hidden')
+ {
+ this.sizer.redraw();
+ }
+
+ me.consume();
+ }
+};
+
+/**
+ * Function: mouseUp
+ *
+ * Handles the event by applying the translation or zoom to <graph>.
+ */
+mxOutline.prototype.mouseUp = function(sender, me)
+{
+ if (this.active)
+ {
+ var dx = me.getX() - this.startX;
+ var dy = me.getY() - this.startY;
+
+ if (Math.abs(dx) > 0 || Math.abs(dy) > 0)
+ {
+ if (!this.zoom)
+ {
+ // Applies the new translation if the source
+ // has no scrollbars
+ if (!this.source.useScrollbarsForPanning ||
+ !mxUtils.hasScrollbars(this.source.container))
+ {
+ this.source.panGraph(0, 0);
+ dx /= this.outline.getView().scale;
+ dy /= this.outline.getView().scale;
+ var t = this.source.getView().translate;
+ this.source.getView().setTranslate(t.x - dx, t.y - dy);
+ }
+ }
+ else
+ {
+ // Applies the new zoom
+ var w = this.selectionBorder.bounds.width;
+ var scale = this.source.getView().scale;
+ this.source.zoomTo(scale - (dx * scale) / w, false);
+ }
+
+ this.update();
+ me.consume();
+ }
+
+ // Resets the state of the handler
+ this.index = null;
+ this.active = false;
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Destroy this outline and removes all listeners from <source>.
+ */
+mxOutline.prototype.destroy = function()
+{
+ if (this.source != null)
+ {
+ this.source.removeListener(this.panHandler);
+ this.source.removeListener(this.refreshHandler);
+ this.source.getModel().removeListener(this.updateHandler);
+ this.source.getView().removeListener(this.updateHandler);
+ mxEvent.addListener(this.source.container, 'scroll', this.updateHandler);
+ this.source = null;
+ }
+
+ if (this.outline != null)
+ {
+ this.outline.removeMouseListener(this);
+ this.outline.destroy();
+ this.outline = null;
+ }
+
+ if (this.selectionBorder != null)
+ {
+ this.selectionBorder.destroy();
+ this.selectionBorder = null;
+ }
+
+ if (this.sizer != null)
+ {
+ this.sizer.destroy();
+ this.sizer = null;
+ }
+};
diff --git a/src/js/view/mxPerimeter.js b/src/js/view/mxPerimeter.js
new file mode 100644
index 0000000..7aaa187
--- /dev/null
+++ b/src/js/view/mxPerimeter.js
@@ -0,0 +1,484 @@
+/**
+ * $Id: mxPerimeter.js,v 1.28 2012-01-11 09:06:56 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxPerimeter =
+{
+ /**
+ * Class: mxPerimeter
+ *
+ * Provides various perimeter functions to be used in a style
+ * as the value of <mxConstants.STYLE_PERIMETER>. Perimeters for
+ * rectangle, circle, rhombus and triangle are available.
+ *
+ * Example:
+ *
+ * (code)
+ * <add as="perimeter">mxPerimeter.RightAngleRectanglePerimeter</add>
+ * (end)
+ *
+ * Or programmatically:
+ *
+ * (code)
+ * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+ * (end)
+ *
+ * When adding new perimeter functions, it is recommended to use the
+ * mxPerimeter-namespace as follows:
+ *
+ * (code)
+ * mxPerimeter.CustomPerimeter = function (bounds, vertex, next, orthogonal)
+ * {
+ * var x = 0; // Calculate x-coordinate
+ * var y = 0; // Calculate y-coordainte
+ *
+ * return new mxPoint(x, y);
+ * }
+ * (end)
+ *
+ * The new perimeter should then be registered in the <mxStyleRegistry> as follows:
+ * (code)
+ * mxStyleRegistry.putValue('customPerimeter', mxPerimeter.CustomPerimeter);
+ * (end)
+ *
+ * The custom perimeter above can now be used in a specific vertex as follows:
+ *
+ * (code)
+ * model.setStyle(vertex, 'perimeter=customPerimeter');
+ * (end)
+ *
+ * Note that the key of the <mxStyleRegistry> entry for the function should
+ * be used in string values, unless <mxGraphView.allowEval> is true, in
+ * which case you can also use mxPerimeter.CustomPerimeter for the value in
+ * the cell style above.
+ *
+ * Or it can be used for all vertices in the graph as follows:
+ *
+ * (code)
+ * var style = graph.getStylesheet().getDefaultVertexStyle();
+ * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.CustomPerimeter;
+ * (end)
+ *
+ * Note that the object can be used directly when programmatically setting
+ * the value, but the key in the <mxStyleRegistry> should be used when
+ * setting the value via a key, value pair in a cell style.
+ *
+ * The parameters are explained in <RectanglePerimeter>.
+ *
+ * Function: RectanglePerimeter
+ *
+ * Describes a rectangular perimeter for the given bounds.
+ *
+ * Parameters:
+ *
+ * bounds - <mxRectangle> that represents the absolute bounds of the
+ * vertex.
+ * vertex - <mxCellState> that represents the vertex.
+ * next - <mxPoint> that represents the nearest neighbour point on the
+ * given edge.
+ * orthogonal - Boolean that specifies if the orthogonal projection onto
+ * the perimeter should be returned. If this is false then the intersection
+ * of the perimeter and the line between the next and the center point is
+ * returned.
+ */
+ RectanglePerimeter: function (bounds, vertex, next, orthogonal)
+ {
+ var cx = bounds.getCenterX();
+ var cy = bounds.getCenterY();
+ var dx = next.x - cx;
+ var dy = next.y - cy;
+ var alpha = Math.atan2(dy, dx);
+ var p = new mxPoint(0, 0);
+ var pi = Math.PI;
+ var pi2 = Math.PI/2;
+ var beta = pi2 - alpha;
+ var t = Math.atan2(bounds.height, bounds.width);
+
+ if (alpha < -pi + t || alpha > pi - t)
+ {
+ // Left edge
+ p.x = bounds.x;
+ p.y = cy - bounds.width * Math.tan(alpha) / 2;
+ }
+ else if (alpha < -t)
+ {
+ // Top Edge
+ p.y = bounds.y;
+ p.x = cx - bounds.height * Math.tan(beta) / 2;
+ }
+ else if (alpha < t)
+ {
+ // Right Edge
+ p.x = bounds.x + bounds.width;
+ p.y = cy + bounds.width * Math.tan(alpha) / 2;
+ }
+ else
+ {
+ // Bottom Edge
+ p.y = bounds.y + bounds.height;
+ p.x = cx + bounds.height * Math.tan(beta) / 2;
+ }
+
+ if (orthogonal)
+ {
+ if (next.x >= bounds.x &&
+ next.x <= bounds.x + bounds.width)
+ {
+ p.x = next.x;
+ }
+ else if (next.y >= bounds.y &&
+ next.y <= bounds.y + bounds.height)
+ {
+ p.y = next.y;
+ }
+ if (next.x < bounds.x)
+ {
+ p.x = bounds.x;
+ }
+ else if (next.x > bounds.x + bounds.width)
+ {
+ p.x = bounds.x + bounds.width;
+ }
+ if (next.y < bounds.y)
+ {
+ p.y = bounds.y;
+ }
+ else if (next.y > bounds.y + bounds.height)
+ {
+ p.y = bounds.y + bounds.height;
+ }
+ }
+
+ return p;
+ },
+
+ /**
+ * Function: EllipsePerimeter
+ *
+ * Describes an elliptic perimeter. See <RectanglePerimeter>
+ * for a description of the parameters.
+ */
+ EllipsePerimeter: function (bounds, vertex, next, orthogonal)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var a = bounds.width / 2;
+ var b = bounds.height / 2;
+ var cx = x + a;
+ var cy = y + b;
+ var px = next.x;
+ var py = next.y;
+
+ // Calculates straight line equation through
+ // point and ellipse center y = d * x + h
+ var dx = parseInt(px - cx);
+ var dy = parseInt(py - cy);
+
+ if (dx == 0 && dy != 0)
+ {
+ return new mxPoint(cx, cy + b * dy / Math.abs(dy));
+ }
+ else if (dx == 0 && dy == 0)
+ {
+ return new mxPoint(px, py);
+ }
+
+ if (orthogonal)
+ {
+ if (py >= y && py <= y + bounds.height)
+ {
+ var ty = py - cy;
+ var tx = Math.sqrt(a*a*(1-(ty*ty)/(b*b))) || 0;
+
+ if (px <= x)
+ {
+ tx = -tx;
+ }
+
+ return new mxPoint(cx+tx, py);
+ }
+
+ if (px >= x && px <= x + bounds.width)
+ {
+ var tx = px - cx;
+ var ty = Math.sqrt(b*b*(1-(tx*tx)/(a*a))) || 0;
+
+ if (py <= y)
+ {
+ ty = -ty;
+ }
+
+ return new mxPoint(px, cy+ty);
+ }
+ }
+
+ // Calculates intersection
+ var d = dy / dx;
+ var h = cy - d * cx;
+ var e = a * a * d * d + b * b;
+ var f = -2 * cx * e;
+ var g = a * a * d * d * cx * cx +
+ b * b * cx * cx -
+ a * a * b * b;
+ var det = Math.sqrt(f * f - 4 * e * g);
+
+ // Two solutions (perimeter points)
+ var xout1 = (-f + det) / (2 * e);
+ var xout2 = (-f - det) / (2 * e);
+ var yout1 = d * xout1 + h;
+ var yout2 = d * xout2 + h;
+ var dist1 = Math.sqrt(Math.pow((xout1 - px), 2)
+ + Math.pow((yout1 - py), 2));
+ var dist2 = Math.sqrt(Math.pow((xout2 - px), 2)
+ + Math.pow((yout2 - py), 2));
+
+ // Correct solution
+ var xout = 0;
+ var yout = 0;
+
+ if (dist1 < dist2)
+ {
+ xout = xout1;
+ yout = yout1;
+ }
+ else
+ {
+ xout = xout2;
+ yout = yout2;
+ }
+
+ return new mxPoint(xout, yout);
+ },
+
+ /**
+ * Function: RhombusPerimeter
+ *
+ * Describes a rhombus (aka diamond) perimeter. See <RectanglePerimeter>
+ * for a description of the parameters.
+ */
+ RhombusPerimeter: function (bounds, vertex, next, orthogonal)
+ {
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+
+ var cx = x + w / 2;
+ var cy = y + h / 2;
+
+ var px = next.x;
+ var py = next.y;
+
+ // Special case for intersecting the diamond's corners
+ if (cx == px)
+ {
+ if (cy > py)
+ {
+ return new mxPoint(cx, y); // top
+ }
+ else
+ {
+ return new mxPoint(cx, y + h); // bottom
+ }
+ }
+ else if (cy == py)
+ {
+ if (cx > px)
+ {
+ return new mxPoint(x, cy); // left
+ }
+ else
+ {
+ return new mxPoint(x + w, cy); // right
+ }
+ }
+
+ var tx = cx;
+ var ty = cy;
+
+ if (orthogonal)
+ {
+ if (px >= x && px <= x + w)
+ {
+ tx = px;
+ }
+ else if (py >= y && py <= y + h)
+ {
+ ty = py;
+ }
+ }
+
+ // In which quadrant will the intersection be?
+ // set the slope and offset of the border line accordingly
+ if (px < cx)
+ {
+ if (py < cy)
+ {
+ return mxUtils.intersection(px, py, tx, ty, cx, y, x, cy);
+ }
+ else
+ {
+ return mxUtils.intersection(px, py, tx, ty, cx, y + h, x, cy);
+ }
+ }
+ else if (py < cy)
+ {
+ return mxUtils.intersection(px, py, tx, ty, cx, y, x + w, cy);
+ }
+ else
+ {
+ return mxUtils.intersection(px, py, tx, ty, cx, y + h, x + w, cy);
+ }
+ },
+
+ /**
+ * Function: TrianglePerimeter
+ *
+ * Describes a triangle perimeter. See <RectanglePerimeter>
+ * for a description of the parameters.
+ */
+ TrianglePerimeter: function (bounds, vertex, next, orthogonal)
+ {
+ var direction = (vertex != null) ?
+ vertex.style[mxConstants.STYLE_DIRECTION] : null;
+ var vertical = direction == mxConstants.DIRECTION_NORTH ||
+ direction == mxConstants.DIRECTION_SOUTH;
+
+ var x = bounds.x;
+ var y = bounds.y;
+ var w = bounds.width;
+ var h = bounds.height;
+
+ var cx = x + w / 2;
+ var cy = y + h / 2;
+
+ var start = new mxPoint(x, y);
+ var corner = new mxPoint(x + w, cy);
+ var end = new mxPoint(x, y + h);
+
+ if (direction == mxConstants.DIRECTION_NORTH)
+ {
+ start = end;
+ corner = new mxPoint(cx, y);
+ end = new mxPoint(x + w, y + h);
+ }
+ else if (direction == mxConstants.DIRECTION_SOUTH)
+ {
+ corner = new mxPoint(cx, y + h);
+ end = new mxPoint(x + w, y);
+ }
+ else if (direction == mxConstants.DIRECTION_WEST)
+ {
+ start = new mxPoint(x + w, y);
+ corner = new mxPoint(x, cy);
+ end = new mxPoint(x + w, y + h);
+ }
+
+ var dx = next.x - cx;
+ var dy = next.y - cy;
+
+ var alpha = (vertical) ? Math.atan2(dx, dy) : Math.atan2(dy, dx);
+ var t = (vertical) ? Math.atan2(w, h) : Math.atan2(h, w);
+
+ var base = false;
+
+ if (direction == mxConstants.DIRECTION_NORTH ||
+ direction == mxConstants.DIRECTION_WEST)
+ {
+ base = alpha > -t && alpha < t;
+ }
+ else
+ {
+ base = alpha < -Math.PI + t || alpha > Math.PI - t;
+ }
+
+ var result = null;
+
+ if (base)
+ {
+ if (orthogonal && ((vertical && next.x >= start.x && next.x <= end.x) ||
+ (!vertical && next.y >= start.y && next.y <= end.y)))
+ {
+ if (vertical)
+ {
+ result = new mxPoint(next.x, start.y);
+ }
+ else
+ {
+ result = new mxPoint(start.x, next.y);
+ }
+ }
+ else
+ {
+ if (direction == mxConstants.DIRECTION_NORTH)
+ {
+ result = new mxPoint(x + w / 2 + h * Math.tan(alpha) / 2,
+ y + h);
+ }
+ else if (direction == mxConstants.DIRECTION_SOUTH)
+ {
+ result = new mxPoint(x + w / 2 - h * Math.tan(alpha) / 2,
+ y);
+ }
+ else if (direction == mxConstants.DIRECTION_WEST)
+ {
+ result = new mxPoint(x + w, y + h / 2 +
+ w * Math.tan(alpha) / 2);
+ }
+ else
+ {
+ result = new mxPoint(x, y + h / 2 -
+ w * Math.tan(alpha) / 2);
+ }
+ }
+ }
+ else
+ {
+ if (orthogonal)
+ {
+ var pt = new mxPoint(cx, cy);
+
+ if (next.y >= y && next.y <= y + h)
+ {
+ pt.x = (vertical) ? cx : (
+ (direction == mxConstants.DIRECTION_WEST) ?
+ x + w : x);
+ pt.y = next.y;
+ }
+ else if (next.x >= x && next.x <= x + w)
+ {
+ pt.x = next.x;
+ pt.y = (!vertical) ? cy : (
+ (direction == mxConstants.DIRECTION_NORTH) ?
+ y + h : y);
+ }
+
+ // Compute angle
+ dx = next.x - pt.x;
+ dy = next.y - pt.y;
+
+ cx = pt.x;
+ cy = pt.y;
+ }
+
+ if ((vertical && next.x <= x + w / 2) ||
+ (!vertical && next.y <= y + h / 2))
+ {
+ result = mxUtils.intersection(next.x, next.y, cx, cy,
+ start.x, start.y, corner.x, corner.y);
+ }
+ else
+ {
+ result = mxUtils.intersection(next.x, next.y, cx, cy,
+ corner.x, corner.y, end.x, end.y);
+ }
+ }
+
+ if (result == null)
+ {
+ result = new mxPoint(cx, cy);
+ }
+
+ return result;
+ }
+};
diff --git a/src/js/view/mxPrintPreview.js b/src/js/view/mxPrintPreview.js
new file mode 100644
index 0000000..24a65e6
--- /dev/null
+++ b/src/js/view/mxPrintPreview.js
@@ -0,0 +1,801 @@
+/**
+ * $Id: mxPrintPreview.js,v 1.61 2012-05-15 14:12:40 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxPrintPreview
+ *
+ * Implements printing of a diagram across multiple pages. The following opens
+ * a print preview for an existing graph:
+ *
+ * (code)
+ * var preview = new mxPrintPreview(graph);
+ * preview.open();
+ * (end)
+ *
+ * Use <mxUtils.getScaleForPageCount> as follows in order to print the graph
+ * across a given number of pages:
+ *
+ * (code)
+ * var pageCount = mxUtils.prompt('Enter page count', '1');
+ *
+ * if (pageCount != null)
+ * {
+ * var scale = mxUtils.getScaleForPageCount(pageCount, graph);
+ * var preview = new mxPrintPreview(graph, scale);
+ * preview.open();
+ * }
+ * (end)
+ *
+ * Headers:
+ *
+ * Apart from setting the title argument in the mxPrintPreview constructor you
+ * can override <renderPage> as follows to add a header to any page:
+ *
+ * (code)
+ * var oldRenderPage = mxPrintPreview.prototype.renderPage;
+ * mxPrintPreview.prototype.renderPage = function(w, h, dx, dy, scale, pageNumber)
+ * {
+ * var div = oldRenderPage.apply(this, arguments);
+ *
+ * var header = document.createElement('div');
+ * header.style.position = 'absolute';
+ * header.style.top = '0px';
+ * header.style.width = '100%';
+ * header.style.textAlign = 'right';
+ * mxUtils.write(header, 'Your header here - Page ' + pageNumber + ' / ' + this.pageCount);
+ * div.firstChild.appendChild(header);
+ *
+ * return div;
+ * };
+ * (end)
+ *
+ * Page Format:
+ *
+ * For landscape printing, use <mxConstants.PAGE_FORMAT_A4_LANDSCAPE> as
+ * the pageFormat in <mxUtils.getScaleForPageCount> and <mxPrintPreview>.
+ * Keep in mind that one can not set the defaults for the print dialog
+ * of the operating system from JavaScript so the user must manually choose
+ * a page format that matches this setting.
+ *
+ * You can try passing the following CSS directive to <open> to set the
+ * page format in the print dialog to landscape. However, this CSS
+ * directive seems to be ignored in most major browsers, including IE.
+ *
+ * (code)
+ * @page {
+ * size: landscape;
+ * }
+ * (end)
+ *
+ * Note that the print preview behaves differently in IE when used from the
+ * filesystem or via HTTP so printing should always be tested via HTTP.
+ *
+ * If you are using a DOCTYPE in the source page you can override <getDoctype>
+ * and provide the same DOCTYPE for the print preview if required. Here is
+ * an example for IE8 standards mode.
+ *
+ * (code)
+ * var preview = new mxPrintPreview(graph);
+ * preview.getDoctype = function()
+ * {
+ * return '<!--[if IE]><meta http-equiv="X-UA-Compatible" content="IE=5,IE=8" ><![endif]-->';
+ * };
+ * preview.open();
+ * (end)
+ *
+ * Constructor: mxPrintPreview
+ *
+ * Constructs a new print preview for the given parameters.
+ *
+ * Parameters:
+ *
+ * graph - <mxGraph> to be previewed.
+ * scale - Optional scale of the output. Default is 1 / <mxGraph.pageScale>.
+ * border - Border in pixels along each side of every page. Note that the
+ * actual print function in the browser will add another border for
+ * printing.
+ * pageFormat - <mxRectangle> that specifies the page format (in pixels).
+ * This should match the page format of the printer. Default uses the
+ * <mxGraph.pageFormat> of the given graph.
+ * x0 - Optional left offset of the output. Default is 0.
+ * y0 - Optional top offset of the output. Default is 0.
+ * borderColor - Optional color of the page border. Default is no border.
+ * Note that a border is sometimes useful to highlight the printed page
+ * border in the print preview of the browser.
+ * title - Optional string that is used for the window title. Default
+ * is 'Printer-friendly version'.
+ * pageSelector - Optional boolean that specifies if the page selector
+ * should appear in the window with the print preview. Default is true.
+ */
+function mxPrintPreview(graph, scale, pageFormat, border, x0, y0, borderColor, title, pageSelector)
+{
+ this.graph = graph;
+ this.scale = (scale != null) ? scale : 1 / graph.pageScale;
+ this.border = (border != null) ? border : 0;
+ this.pageFormat = (pageFormat != null) ? pageFormat : graph.pageFormat;
+ this.title = (title != null) ? title : 'Printer-friendly version';
+ this.x0 = (x0 != null) ? x0 : 0;
+ this.y0 = (y0 != null) ? y0 : 0;
+ this.borderColor = borderColor;
+ this.pageSelector = (pageSelector != null) ? pageSelector : true;
+};
+
+/**
+ * Variable: graph
+ *
+ * Reference to the <mxGraph> that should be previewed.
+ */
+mxPrintPreview.prototype.graph = null;
+
+/**
+ * Variable: pageFormat
+ *
+ * Holds the <mxRectangle> that defines the page format.
+ */
+mxPrintPreview.prototype.pageFormat = null;
+
+/**
+ * Variable: scale
+ *
+ * Holds the scale of the print preview.
+ */
+mxPrintPreview.prototype.scale = null;
+
+/**
+ * Variable: border
+ *
+ * The border inset around each side of every page in the preview. This is set
+ * to 0 if autoOrigin is false.
+ */
+mxPrintPreview.prototype.border = 0;
+
+/**
+/**
+ * Variable: x0
+ *
+ * Holds the horizontal offset of the output.
+ */
+mxPrintPreview.prototype.x0 = 0;
+
+/**
+ * Variable: y0
+ *
+ * Holds the vertical offset of the output.
+ */
+mxPrintPreview.prototype.y0 = 0;
+
+/**
+ * Variable: autoOrigin
+ *
+ * Specifies if the origin should be automatically computed based on the top,
+ * left corner of the actual diagram contents. If this is set to false then the
+ * values for <x0> and <y0> will be overridden in <open>. Default is true.
+ */
+mxPrintPreview.prototype.autoOrigin = true;
+
+/**
+ * Variable: printOverlays
+ *
+ * Specifies if overlays should be printed. Default is false.
+ */
+mxPrintPreview.prototype.printOverlays = false;
+
+/**
+ * Variable: borderColor
+ *
+ * Holds the color value for the page border.
+ */
+mxPrintPreview.prototype.borderColor = null;
+
+/**
+ * Variable: title
+ *
+ * Holds the title of the preview window.
+ */
+mxPrintPreview.prototype.title = null;
+
+/**
+ * Variable: pageSelector
+ *
+ * Boolean that specifies if the page selector should be
+ * displayed. Default is true.
+ */
+mxPrintPreview.prototype.pageSelector = null;
+
+/**
+ * Variable: wnd
+ *
+ * Reference to the preview window.
+ */
+mxPrintPreview.prototype.wnd = null;
+
+/**
+ * Variable: pageCount
+ *
+ * Holds the actual number of pages in the preview.
+ */
+mxPrintPreview.prototype.pageCount = 0;
+
+/**
+ * Function: getWindow
+ *
+ * Returns <wnd>.
+ */
+mxPrintPreview.prototype.getWindow = function()
+{
+ return this.wnd;
+};
+
+/**
+ * Function: getDocType
+ *
+ * Returns the string that should go before the HTML tag in the print preview
+ * page. This implementation returns an empty string.
+ */
+mxPrintPreview.prototype.getDoctype = function()
+{
+ return '';
+};
+
+/**
+ * Function: open
+ *
+ * Shows the print preview window. The window is created here if it does
+ * not exist.
+ *
+ * Parameters:
+ *
+ * css - Optional CSS string to be used in the new page's head section.
+ */
+mxPrintPreview.prototype.open = function(css)
+{
+ // Closing the window while the page is being rendered may cause an
+ // exception in IE. This and any other exceptions are simply ignored.
+ var previousInitializeOverlay = this.graph.cellRenderer.initializeOverlay;
+ var div = null;
+
+ try
+ {
+ // Temporarily overrides the method to redirect rendering of overlays
+ // to the draw pane so that they are visible in the printout
+ if (this.printOverlays)
+ {
+ this.graph.cellRenderer.initializeOverlay = function(state, overlay)
+ {
+ overlay.init(state.view.getDrawPane());
+ };
+ }
+
+ if (this.wnd == null)
+ {
+ this.wnd = window.open();
+ var doc = this.wnd.document;
+ var dt = this.getDoctype();
+
+ if (dt != null && dt.length > 0)
+ {
+ doc.writeln(dt);
+ }
+
+ doc.writeln('<html>');
+ doc.writeln('<head>');
+ this.writeHead(doc, css);
+ doc.writeln('</head>');
+ doc.writeln('<body class="mxPage">');
+
+ // Adds all required stylesheets and namespaces
+ mxClient.link('stylesheet', mxClient.basePath + '/css/common.css', doc);
+
+ if (mxClient.IS_IE && document.documentMode != 9)
+ {
+ doc.namespaces.add('v', 'urn:schemas-microsoft-com:vml');
+ doc.namespaces.add('o', 'urn:schemas-microsoft-com:office:office');
+ var ss = doc.createStyleSheet();
+ ss.cssText = 'v\\:*{behavior:url(#default#VML)}o\\:*{behavior:url(#default#VML)}';
+ mxClient.link('stylesheet', mxClient.basePath + '/css/explorer.css', doc);
+ }
+
+ // Computes the horizontal and vertical page count
+ var bounds = this.graph.getGraphBounds().clone();
+ var currentScale = this.graph.getView().getScale();
+ var sc = currentScale / this.scale;
+ var tr = this.graph.getView().getTranslate();
+
+ // Uses the absolute origin with no offset for all printing
+ if (!this.autoOrigin)
+ {
+ this.x0 = -tr.x * this.scale;
+ this.y0 = -tr.y * this.scale;
+ bounds.width += bounds.x;
+ bounds.height += bounds.y;
+ bounds.x = 0;
+ bounds.y = 0;
+ this.border = 0;
+ }
+
+ // Compute the unscaled, untranslated bounds to find
+ // the number of vertical and horizontal pages
+ bounds.width /= sc;
+ bounds.height /= sc;
+
+ // Store the available page area
+ var availableWidth = this.pageFormat.width - (this.border * 2);
+ var availableHeight = this.pageFormat.height - (this.border * 2);
+
+ var hpages = Math.max(1, Math.ceil((bounds.width + this.x0) / availableWidth));
+ var vpages = Math.max(1, Math.ceil((bounds.height + this.y0) / availableHeight));
+ this.pageCount = hpages * vpages;
+
+ var writePageSelector = mxUtils.bind(this, function()
+ {
+ if (this.pageSelector && (vpages > 1 || hpages > 1))
+ {
+ var table = this.createPageSelector(vpages, hpages);
+ doc.body.appendChild(table);
+
+ // Workaround for position: fixed which isn't working in IE
+ if (mxClient.IS_IE)
+ {
+ table.style.position = 'absolute';
+
+ var update = function()
+ {
+ table.style.top = (doc.body.scrollTop + 10) + 'px';
+ };
+
+ mxEvent.addListener(this.wnd, 'scroll', function(evt)
+ {
+ update();
+ });
+
+ mxEvent.addListener(this.wnd, 'resize', function(evt)
+ {
+ update();
+ });
+ }
+ }
+ });
+
+ // Stores pages for later retrieval
+ var pages = null;
+
+ // Workaround for aspect of image shapes updated asynchronously
+ // in VML so we need to fetch the markup of the DIV containing
+ // the image after the udpate of the style of the DOM node.
+ // LATER: Allow document for display markup to be customized.
+ if (mxClient.IS_IE && document.documentMode != 9)
+ {
+ pages = [];
+
+ // Overrides asynchronous loading of images for fetching HTML markup
+ var waitCounter = 0;
+ var isDone = false;
+
+ var mxImageShapeScheduleUpdateAspect = mxImageShape.prototype.scheduleUpdateAspect;
+ var mxImageShapeUpdateAspect = mxImageShape.prototype.updateAspect;
+
+ var writePages = function()
+ {
+ if (isDone && waitCounter == 0)
+ {
+ // Restores previous implementations
+ mxImageShape.prototype.scheduleUpdateAspect = mxImageShapeScheduleUpdateAspect;
+ mxImageShape.prototype.updateAspect = mxImageShapeUpdateAspect;
+
+ var markup = '';
+
+ for (var i = 0; i < pages.length; i++)
+ {
+ markup += pages[i].outerHTML;
+ pages[i].parentNode.removeChild(pages[i]);
+
+ if (i < pages.length - 1)
+ {
+ markup += '<hr/>';
+ }
+ }
+
+ doc.body.innerHTML = markup;
+ writePageSelector();
+ }
+ };
+
+ // Overrides functions to implement wait counter
+ mxImageShape.prototype.scheduleUpdateAspect = function()
+ {
+ waitCounter++;
+ mxImageShapeScheduleUpdateAspect.apply(this, arguments);
+ };
+
+ // Overrides functions to implement wait counter
+ mxImageShape.prototype.updateAspect = function()
+ {
+ mxImageShapeUpdateAspect.apply(this, arguments);
+ waitCounter--;
+ writePages();
+ };
+ }
+
+ // Appends each page to the page output for printing, making
+ // sure there will be a page break after each page (ie. div)
+ for (var i = 0; i < vpages; i++)
+ {
+ var dy = i * availableHeight / this.scale - this.y0 / this.scale +
+ (bounds.y - tr.y * currentScale) / currentScale;
+
+ for (var j = 0; j < hpages; j++)
+ {
+ if (this.wnd == null)
+ {
+ return null;
+ }
+
+ var dx = j * availableWidth / this.scale - this.x0 / this.scale +
+ (bounds.x - tr.x * currentScale) / currentScale;
+ var pageNum = i * hpages + j + 1;
+
+ div = this.renderPage(this.pageFormat.width, this.pageFormat.height,
+ -dx, -dy, this.scale, pageNum);
+
+ // Gives the page a unique ID for later accessing the page
+ div.setAttribute('id', 'mxPage-'+pageNum);
+
+ // Border of the DIV (aka page) inside the document
+ if (this.borderColor != null)
+ {
+ div.style.borderColor = this.borderColor;
+ div.style.borderStyle = 'solid';
+ div.style.borderWidth = '1px';
+ }
+
+ // Needs to be assigned directly because IE doesn't support
+ // child selectors, eg. body > div { background: white; }
+ div.style.background = 'white';
+
+ if (i < vpages - 1 || j < hpages - 1)
+ {
+ div.style.pageBreakAfter = 'always';
+ }
+
+ // NOTE: We are dealing with cross-window DOM here, which
+ // is a problem in IE, so we copy the HTML markup instead.
+ // The underlying problem is that the graph display markup
+ // creation (in mxShape, mxGraphView) is hardwired to using
+ // document.createElement and hence we must use document
+ // to create the complete page and then copy it over to the
+ // new window.document. This can be fixed later by using the
+ // ownerDocument of the container in mxShape and mxGraphView.
+ if (mxClient.IS_IE)
+ {
+ // For some obscure reason, removing the DIV from the
+ // parent before fetching its outerHTML has missing
+ // fillcolor properties and fill children, so the div
+ // must be removed afterwards to keep the fillcolors.
+ // For delayed output we remote the DIV from the
+ // original document when we write out all pages.
+ doc.writeln(div.outerHTML);
+
+ if (pages != null)
+ {
+ pages.push(div);
+ }
+ else
+ {
+ div.parentNode.removeChild(div);
+ }
+ }
+ else
+ {
+ div.parentNode.removeChild(div);
+ doc.body.appendChild(div);
+ }
+
+ if (i < vpages - 1 || j < hpages - 1)
+ {
+ var hr = doc.createElement('hr');
+ hr.className = 'mxPageBreak';
+ doc.body.appendChild(hr);
+ }
+ }
+ }
+
+ doc.writeln('</body>');
+ doc.writeln('</html>');
+ doc.close();
+
+ // Marks the printing complete for async handling
+ if (pages != null)
+ {
+ isDone = true;
+ writePages();
+ }
+ else
+ {
+ writePageSelector();
+ }
+
+ // Removes all event handlers in the print output
+ mxEvent.release(doc.body);
+ }
+
+ this.wnd.focus();
+ }
+ catch (e)
+ {
+ // Removes the DIV from the document in case of an error
+ if (div != null && div.parentNode != null)
+ {
+ div.parentNode.removeChild(div);
+ }
+ }
+ finally
+ {
+ this.graph.cellRenderer.initializeOverlay = previousInitializeOverlay;
+ }
+
+ return this.wnd;
+};
+
+/**
+ * Function: writeHead
+ *
+ * Writes the HEAD section into the given document, without the opening
+ * and closing HEAD tags.
+ */
+mxPrintPreview.prototype.writeHead = function(doc, css)
+{
+ if (this.title != null)
+ {
+ doc.writeln('<title>' + this.title + '</title>');
+ }
+
+ // Makes sure no horizontal rulers are printed
+ doc.writeln('<style type="text/css">');
+ doc.writeln('@media print {');
+ doc.writeln(' table.mxPageSelector { display: none; }');
+ doc.writeln(' hr.mxPageBreak { display: none; }');
+ doc.writeln('}');
+ doc.writeln('@media screen {');
+
+ // NOTE: position: fixed is not supported in IE, so the page selector
+ // position (absolute) needs to be updated in IE (see below)
+ doc.writeln(' table.mxPageSelector { position: fixed; right: 10px; top: 10px;' +
+ 'font-family: Arial; font-size:10pt; border: solid 1px darkgray;' +
+ 'background: white; border-collapse:collapse; }');
+ doc.writeln(' table.mxPageSelector td { border: solid 1px gray; padding:4px; }');
+ doc.writeln(' body.mxPage { background: gray; }');
+ doc.writeln('}');
+
+ if (css != null)
+ {
+ doc.writeln(css);
+ }
+
+ doc.writeln('</style>');
+};
+
+/**
+ * Function: createPageSelector
+ *
+ * Creates the page selector table.
+ */
+mxPrintPreview.prototype.createPageSelector = function(vpages, hpages)
+{
+ var doc = this.wnd.document;
+ var table = doc.createElement('table');
+ table.className = 'mxPageSelector';
+ table.setAttribute('border', '0');
+
+ var tbody = doc.createElement('tbody');
+
+ for (var i = 0; i < vpages; i++)
+ {
+ var row = doc.createElement('tr');
+
+ for (var j = 0; j < hpages; j++)
+ {
+ var pageNum = i * hpages + j + 1;
+ var cell = doc.createElement('td');
+
+ // Needs anchor for all browers to work without JavaScript
+ // LATER: Does not work in Firefox because the generated document
+ // has the URL of the opening document, the anchor is appended
+ // to that URL and the full URL is loaded on click.
+ if (!mxClient.IS_NS || mxClient.IS_SF || mxClient.IS_GC)
+ {
+ var a = doc.createElement('a');
+ a.setAttribute('href', '#mxPage-' + pageNum);
+ mxUtils.write(a, pageNum, doc);
+ cell.appendChild(a);
+ }
+ else
+ {
+ mxUtils.write(cell, pageNum, doc);
+ }
+
+ row.appendChild(cell);
+ }
+
+ tbody.appendChild(row);
+ }
+
+ table.appendChild(tbody);
+
+ return table;
+};
+
+/**
+ * Function: renderPage
+ *
+ * Creates a DIV that prints a single page of the given
+ * graph using the given scale and returns the DIV that
+ * represents the page.
+ *
+ * Parameters:
+ *
+ * w - Width of the page in pixels.
+ * h - Height of the page in pixels.
+ * dx - Horizontal translation for the diagram.
+ * dy - Vertical translation for the diagram.
+ * scale - Scale for the diagram.
+ * pageNumber - Number of the page to be rendered.
+ */
+mxPrintPreview.prototype.renderPage = function(w, h, dx, dy, scale, pageNumber)
+{
+ var div = document.createElement('div');
+
+ try
+ {
+ div.style.width = w + 'px';
+ div.style.height = h + 'px';
+ div.style.overflow = 'hidden';
+ div.style.pageBreakInside = 'avoid';
+
+ var innerDiv = document.createElement('div');
+ innerDiv.style.top = this.border + 'px';
+ innerDiv.style.left = this.border + 'px';
+ innerDiv.style.width = (w - 2 * this.border) + 'px';
+ innerDiv.style.height = (h - 2 * this.border) + 'px';
+ innerDiv.style.overflow = 'hidden';
+
+ if (this.graph.dialect == mxConstants.DIALECT_VML)
+ {
+ innerDiv.style.position = 'absolute';
+ }
+
+ div.appendChild(innerDiv);
+ document.body.appendChild(div);
+ var view = this.graph.getView();
+
+ var previousContainer = this.graph.container;
+ this.graph.container = innerDiv;
+
+ var canvas = view.getCanvas();
+ var backgroundPane = view.getBackgroundPane();
+ var drawPane = view.getDrawPane();
+ var overlayPane = view.getOverlayPane();
+
+ if (this.graph.dialect == mxConstants.DIALECT_SVG)
+ {
+ view.createSvg();
+ }
+ else if (this.graph.dialect == mxConstants.DIALECT_VML)
+ {
+ view.createVml();
+ }
+ else
+ {
+ view.createHtml();
+ }
+
+ // Disables events on the view
+ var eventsEnabled = view.isEventsEnabled();
+ view.setEventsEnabled(false);
+
+ // Disables the graph to avoid cursors
+ var graphEnabled = this.graph.isEnabled();
+ this.graph.setEnabled(false);
+
+ // Resets the translation
+ var translate = view.getTranslate();
+ view.translate = new mxPoint(dx, dy);
+
+ var temp = null;
+
+ try
+ {
+ // Creates the temporary cell states in the view and
+ // draws them onto the temporary DOM nodes in the view
+ var model = this.graph.getModel();
+ var cells = [model.getRoot()];
+ temp = new mxTemporaryCellStates(view, scale, cells);
+ }
+ finally
+ {
+ // Removes overlay pane with selection handles
+ // controls and icons from the print output
+ if (mxClient.IS_IE)
+ {
+ view.overlayPane.innerHTML = '';
+ }
+ else
+ {
+ // Removes everything but the SVG node
+ var tmp = innerDiv.firstChild;
+
+ while (tmp != null)
+ {
+ var next = tmp.nextSibling;
+ var name = tmp.nodeName.toLowerCase();
+
+ // Note: Width and heigh are required in FF 11
+ if (name == 'svg')
+ {
+ tmp.setAttribute('width', parseInt(innerDiv.style.width));
+ tmp.setAttribute('height', parseInt(innerDiv.style.height));
+ }
+ // Tries to fetch all text labels and only text labels
+ else if (tmp.style.cursor != 'default' && name != 'table')
+ {
+ tmp.parentNode.removeChild(tmp);
+ }
+
+ tmp = next;
+ }
+ }
+
+ // Completely removes the overlay pane to remove more handles
+ view.overlayPane.parentNode.removeChild(view.overlayPane);
+
+ // Restores the state of the view
+ this.graph.setEnabled(graphEnabled);
+ this.graph.container = previousContainer;
+ view.canvas = canvas;
+ view.backgroundPane = backgroundPane;
+ view.drawPane = drawPane;
+ view.overlayPane = overlayPane;
+ view.translate = translate;
+ temp.destroy();
+ view.setEventsEnabled(eventsEnabled);
+ }
+ }
+ catch (e)
+ {
+ div.parentNode.removeChild(div);
+ div = null;
+
+ throw e;
+ }
+
+ return div;
+};
+
+/**
+ * Function: print
+ *
+ * Opens the print preview and shows the print dialog.
+ */
+mxPrintPreview.prototype.print = function()
+{
+ var wnd = this.open();
+
+ if (wnd != null)
+ {
+ wnd.print();
+ }
+};
+
+/**
+ * Function: close
+ *
+ * Closes the print preview window.
+ */
+mxPrintPreview.prototype.close = function()
+{
+ if (this.wnd != null)
+ {
+ this.wnd.close();
+ this.wnd = null;
+ }
+};
diff --git a/src/js/view/mxSpaceManager.js b/src/js/view/mxSpaceManager.js
new file mode 100644
index 0000000..2a2dd11
--- /dev/null
+++ b/src/js/view/mxSpaceManager.js
@@ -0,0 +1,460 @@
+/**
+ * $Id: mxSpaceManager.js,v 1.9 2010-01-02 09:45:15 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxSpaceManager
+ *
+ * In charge of moving cells after a resize.
+ *
+ * Constructor: mxSpaceManager
+ *
+ * Constructs a new automatic layout for the given graph.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing graph.
+ */
+function mxSpaceManager(graph, shiftRightwards, shiftDownwards, extendParents)
+{
+ this.resizeHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.cellsResized(evt.getProperty('cells'));
+ }
+ });
+
+ this.foldHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled())
+ {
+ this.cellsResized(evt.getProperty('cells'));
+ }
+ });
+
+ this.shiftRightwards = (shiftRightwards != null) ? shiftRightwards : true;
+ this.shiftDownwards = (shiftDownwards != null) ? shiftDownwards : true;
+ this.extendParents = (extendParents != null) ? extendParents : true;
+ this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxSpaceManager.prototype = new mxEventSource();
+mxSpaceManager.prototype.constructor = mxSpaceManager;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxSpaceManager.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSpaceManager.prototype.enabled = true;
+
+/**
+ * Variable: shiftRightwards
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSpaceManager.prototype.shiftRightwards = true;
+
+/**
+ * Variable: shiftDownwards
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSpaceManager.prototype.shiftDownwards = true;
+
+/**
+ * Variable: extendParents
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSpaceManager.prototype.extendParents = true;
+
+/**
+ * Variable: resizeHandler
+ *
+ * Holds the function that handles the move event.
+ */
+mxSpaceManager.prototype.resizeHandler = null;
+
+/**
+ * Variable: foldHandler
+ *
+ * Holds the function that handles the fold event.
+ */
+mxSpaceManager.prototype.foldHandler = null;
+
+/**
+ * Function: isCellIgnored
+ *
+ * Sets the graph that the layouts operate on.
+ */
+mxSpaceManager.prototype.isCellIgnored = function(cell)
+{
+ return !this.getGraph().getModel().isVertex(cell);
+};
+
+/**
+ * Function: isCellShiftable
+ *
+ * Sets the graph that the layouts operate on.
+ */
+mxSpaceManager.prototype.isCellShiftable = function(cell)
+{
+ return this.getGraph().getModel().isVertex(cell) &&
+ this.getGraph().isCellMovable(cell);
+};
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSpaceManager.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSpaceManager.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: isShiftRightwards
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSpaceManager.prototype.isShiftRightwards = function()
+{
+ return this.shiftRightwards;
+};
+
+/**
+ * Function: setShiftRightwards
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSpaceManager.prototype.setShiftRightwards = function(value)
+{
+ this.shiftRightwards = value;
+};
+
+/**
+ * Function: isShiftDownwards
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSpaceManager.prototype.isShiftDownwards = function()
+{
+ return this.shiftDownwards;
+};
+
+/**
+ * Function: setShiftDownwards
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSpaceManager.prototype.setShiftDownwards = function(value)
+{
+ this.shiftDownwards = value;
+};
+
+/**
+ * Function: isExtendParents
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSpaceManager.prototype.isExtendParents = function()
+{
+ return this.extendParents;
+};
+
+/**
+ * Function: setShiftDownwards
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSpaceManager.prototype.setExtendParents = function(value)
+{
+ this.extendParents = value;
+};
+
+/**
+ * Function: getGraph
+ *
+ * Returns the graph that this layout operates on.
+ */
+mxSpaceManager.prototype.getGraph = function()
+{
+ return this.graph;
+};
+
+/**
+ * Function: setGraph
+ *
+ * Sets the graph that the layouts operate on.
+ */
+mxSpaceManager.prototype.setGraph = function(graph)
+{
+ if (this.graph != null)
+ {
+ this.graph.removeListener(this.resizeHandler);
+ this.graph.removeListener(this.foldHandler);
+ }
+
+ this.graph = graph;
+
+ if (this.graph != null)
+ {
+ this.graph.addListener(mxEvent.RESIZE_CELLS, this.resizeHandler);
+ this.graph.addListener(mxEvent.FOLD_CELLS, this.foldHandler);
+ }
+};
+
+/**
+ * Function: cellsResized
+ *
+ * Called from <moveCellsIntoParent> to invoke the <move> hook in the
+ * automatic layout of each modified cell's parent. The event is used to
+ * define the x- and y-coordinates passed to the move function.
+ *
+ * Parameters:
+ *
+ * cell - Array of <mxCells> that have been resized.
+ */
+mxSpaceManager.prototype.cellsResized = function(cells)
+{
+ if (cells != null)
+ {
+ var model = this.graph.getModel();
+
+ // Raising the update level should not be required
+ // since only one call is made below
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!this.isCellIgnored(cells[i]))
+ {
+ this.cellResized(cells[i]);
+ break;
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: cellResized
+ *
+ * Called from <moveCellsIntoParent> to invoke the <move> hook in the
+ * automatic layout of each modified cell's parent. The event is used to
+ * define the x- and y-coordinates passed to the move function.
+ *
+ * Parameters:
+ *
+ * cell - <mxCell> that has been resized.
+ */
+mxSpaceManager.prototype.cellResized = function(cell)
+{
+ var graph = this.getGraph();
+ var view = graph.getView();
+ var model = graph.getModel();
+
+ var state = view.getState(cell);
+ var pstate = view.getState(model.getParent(cell));
+
+ if (state != null &&
+ pstate != null)
+ {
+ var cells = this.getCellsToShift(state);
+ var geo = model.getGeometry(cell);
+
+ if (cells != null &&
+ geo != null)
+ {
+ var tr = view.translate;
+ var scale = view.scale;
+
+ var x0 = state.x - pstate.origin.x - tr.x * scale;
+ var y0 = state.y - pstate.origin.y - tr.y * scale;
+ var right = state.x + state.width;
+ var bottom = state.y + state.height;
+
+ var dx = state.width - geo.width * scale + x0 - geo.x * scale;
+ var dy = state.height - geo.height * scale + y0 - geo.y * scale;
+
+ var fx = 1 - geo.width * scale / state.width;
+ var fy = 1 - geo.height * scale / state.height;
+
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (cells[i] != cell &&
+ this.isCellShiftable(cells[i]))
+ {
+ this.shiftCell(cells[i], dx, dy, x0, y0, right, bottom, fx, fy,
+ this.isExtendParents() &&
+ graph.isExtendParent(cells[i]));
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+ }
+};
+
+/**
+ * Function: shiftCell
+ *
+ * Called from <moveCellsIntoParent> to invoke the <move> hook in the
+ * automatic layout of each modified cell's parent. The event is used to
+ * define the x- and y-coordinates passed to the move function.
+ *
+ * Parameters:
+ *
+ * cell - Array of <mxCells> that have been moved.
+ * evt - Mouse event that represents the mousedown.
+ */
+mxSpaceManager.prototype.shiftCell = function(cell, dx, dy, Ox0, y0, right,
+ bottom, fx, fy, extendParent)
+{
+ var graph = this.getGraph();
+ var state = graph.getView().getState(cell);
+
+ if (state != null)
+ {
+ var model = graph.getModel();
+ var geo = model.getGeometry(cell);
+
+ if (geo != null)
+ {
+ model.beginUpdate();
+ try
+ {
+ if (this.isShiftRightwards())
+ {
+ if (state.x >= right)
+ {
+ geo = geo.clone();
+ geo.translate(-dx, 0);
+ }
+ else
+ {
+ var tmpDx = Math.max(0, state.x - x0);
+ geo = geo.clone();
+ geo.translate(-fx * tmpDx, 0);
+ }
+ }
+
+ if (this.isShiftDownwards())
+ {
+ if (state.y >= bottom)
+ {
+ geo = geo.clone();
+ geo.translate(0, -dy);
+ }
+ else
+ {
+ var tmpDy = Math.max(0, state.y - y0);
+ geo = geo.clone();
+ geo.translate(0, -fy * tmpDy);
+ }
+ }
+
+ if (geo != model.getGeometry(cell))
+ {
+ model.setGeometry(cell, geo);
+
+ // Parent size might need to be updated if this
+ // is seen as part of the resize
+ if (extendParent)
+ {
+ graph.extendParent(cell);
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+ }
+};
+
+/**
+ * Function: getCellsToShift
+ *
+ * Returns the cells to shift after a resize of the
+ * specified <mxCellState>.
+ */
+mxSpaceManager.prototype.getCellsToShift = function(state)
+{
+ var graph = this.getGraph();
+ var parent = graph.getModel().getParent(state.cell);
+ var down = this.isShiftDownwards();
+ var right = this.isShiftRightwards();
+
+ return graph.getCellsBeyond(state.x + ((down) ? 0 : state.width),
+ state.y + ((down && right) ? 0 : state.height), parent, right, down);
+};
+
+/**
+ * Function: destroy
+ *
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxSpaceManager.prototype.destroy = function()
+{
+ this.setGraph(null);
+};
diff --git a/src/js/view/mxStyleRegistry.js b/src/js/view/mxStyleRegistry.js
new file mode 100644
index 0000000..6ad878d
--- /dev/null
+++ b/src/js/view/mxStyleRegistry.js
@@ -0,0 +1,70 @@
+/**
+ * $Id: mxStyleRegistry.js,v 1.10 2011-04-27 10:15:39 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+var mxStyleRegistry =
+{
+ /**
+ * Class: mxStyleRegistry
+ *
+ * Singleton class that acts as a global converter from string to object values
+ * in a style. This is currently only used to perimeters and edge styles.
+ *
+ * Variable: values
+ *
+ * Maps from strings to objects.
+ */
+ values: [],
+
+ /**
+ * Function: putValue
+ *
+ * Puts the given object into the registry under the given name.
+ */
+ putValue: function(name, obj)
+ {
+ mxStyleRegistry.values[name] = obj;
+ },
+
+ /**
+ * Function: getValue
+ *
+ * Returns the value associated with the given name.
+ */
+ getValue: function(name)
+ {
+ return mxStyleRegistry.values[name];
+ },
+
+ /**
+ * Function: getName
+ *
+ * Returns the name for the given value.
+ */
+ getName: function(value)
+ {
+ for (var key in mxStyleRegistry.values)
+ {
+ if (mxStyleRegistry.values[key] == value)
+ {
+ return key;
+ }
+ }
+
+ return null;
+ }
+
+};
+
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ELBOW, mxEdgeStyle.ElbowConnector);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ENTITY_RELATION, mxEdgeStyle.EntityRelation);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_LOOP, mxEdgeStyle.Loop);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SIDETOSIDE, mxEdgeStyle.SideToSide);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_TOPTOBOTTOM, mxEdgeStyle.TopToBottom);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_ORTHOGONAL, mxEdgeStyle.OrthConnector);
+mxStyleRegistry.putValue(mxConstants.EDGESTYLE_SEGMENT, mxEdgeStyle.SegmentConnector);
+
+mxStyleRegistry.putValue(mxConstants.PERIMETER_ELLIPSE, mxPerimeter.EllipsePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_RECTANGLE, mxPerimeter.RectanglePerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_RHOMBUS, mxPerimeter.RhombusPerimeter);
+mxStyleRegistry.putValue(mxConstants.PERIMETER_TRIANGLE, mxPerimeter.TrianglePerimeter);
diff --git a/src/js/view/mxStylesheet.js b/src/js/view/mxStylesheet.js
new file mode 100644
index 0000000..82a520e
--- /dev/null
+++ b/src/js/view/mxStylesheet.js
@@ -0,0 +1,266 @@
+/**
+ * $Id: mxStylesheet.js,v 1.35 2010-03-26 10:24:58 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxStylesheet
+ *
+ * Defines the appearance of the cells in a graph. See <putCellStyle> for an
+ * example of creating a new cell style. It is recommended to use objects, not
+ * arrays for holding cell styles. Existing styles can be cloned using
+ * <mxUtils.clone> and turned into a string for debugging using
+ * <mxUtils.toString>.
+ *
+ * Default Styles:
+ *
+ * The stylesheet contains two built-in styles, which are used if no style is
+ * defined for a cell:
+ *
+ * defaultVertex - Default style for vertices
+ * defaultEdge - Default style for edges
+ *
+ * Example:
+ *
+ * (code)
+ * var vertexStyle = stylesheet.getDefaultVertexStyle();
+ * vertexStyle[mxConstants.ROUNDED] = true;
+ * var edgeStyle = stylesheet.getDefaultEdgeStyle();
+ * edgeStyle[mxConstants.STYLE_EDGE] = mxEdgeStyle.EntityRelation;
+ * (end)
+ *
+ * Modifies the built-in default styles.
+ *
+ * To avoid the default style for a cell, add a leading semicolon
+ * to the style definition, eg.
+ *
+ * (code)
+ * ;shadow=1
+ * (end)
+ *
+ * Removing keys:
+ *
+ * For removing a key in a cell style of the form [stylename;|key=value;] the
+ * special value none can be used, eg. highlight;fillColor=none
+ *
+ * See also the helper methods in mxUtils to modify strings of this format,
+ * namely <mxUtils.setStyle>, <mxUtils.indexOfStylename>,
+ * <mxUtils.addStylename>, <mxUtils.removeStylename>,
+ * <mxUtils.removeAllStylenames> and <mxUtils.setStyleFlag>.
+ *
+ * Constructor: mxStylesheet
+ *
+ * Constructs a new stylesheet and assigns default styles.
+ */
+function mxStylesheet()
+{
+ this.styles = new Object();
+
+ this.putDefaultVertexStyle(this.createDefaultVertexStyle());
+ this.putDefaultEdgeStyle(this.createDefaultEdgeStyle());
+};
+
+/**
+ * Function: styles
+ *
+ * Maps from names to cell styles. Each cell style is a map of key,
+ * value pairs.
+ */
+mxStylesheet.prototype.styles;
+
+/**
+ * Function: createDefaultVertexStyle
+ *
+ * Creates and returns the default vertex style.
+ */
+mxStylesheet.prototype.createDefaultVertexStyle = function()
+{
+ var style = new Object();
+
+ style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
+ style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+ style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
+ style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
+ style[mxConstants.STYLE_FILLCOLOR] = '#C3D9FF';
+ style[mxConstants.STYLE_STROKECOLOR] = '#6482B9';
+ style[mxConstants.STYLE_FONTCOLOR] = '#774400';
+
+ return style;
+};
+
+/**
+ * Function: createDefaultEdgeStyle
+ *
+ * Creates and returns the default edge style.
+ */
+mxStylesheet.prototype.createDefaultEdgeStyle = function()
+{
+ var style = new Object();
+
+ style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_CONNECTOR;
+ style[mxConstants.STYLE_ENDARROW] = mxConstants.ARROW_CLASSIC;
+ style[mxConstants.STYLE_VERTICAL_ALIGN] = mxConstants.ALIGN_MIDDLE;
+ style[mxConstants.STYLE_ALIGN] = mxConstants.ALIGN_CENTER;
+ style[mxConstants.STYLE_STROKECOLOR] = '#6482B9';
+ style[mxConstants.STYLE_FONTCOLOR] = '#446299';
+
+ return style;
+};
+
+/**
+ * Function: putDefaultVertexStyle
+ *
+ * Sets the default style for vertices using defaultVertex as the
+ * stylename.
+ *
+ * Parameters:
+ * style - Key, value pairs that define the style.
+ */
+mxStylesheet.prototype.putDefaultVertexStyle = function(style)
+{
+ this.putCellStyle('defaultVertex', style);
+};
+
+/**
+ * Function: putDefaultEdgeStyle
+ *
+ * Sets the default style for edges using defaultEdge as the stylename.
+ */
+mxStylesheet.prototype.putDefaultEdgeStyle = function(style)
+{
+ this.putCellStyle('defaultEdge', style);
+};
+
+/**
+ * Function: getDefaultVertexStyle
+ *
+ * Returns the default style for vertices.
+ */
+mxStylesheet.prototype.getDefaultVertexStyle = function()
+{
+ return this.styles['defaultVertex'];
+};
+
+/**
+ * Function: getDefaultEdgeStyle
+ *
+ * Sets the default style for edges.
+ */
+mxStylesheet.prototype.getDefaultEdgeStyle = function()
+{
+ return this.styles['defaultEdge'];
+};
+
+/**
+ * Function: putCellStyle
+ *
+ * Stores the given map of key, value pairs under the given name in
+ * <styles>.
+ *
+ * Example:
+ *
+ * The following example adds a new style called 'rounded' into an
+ * existing stylesheet:
+ *
+ * (code)
+ * var style = new Object();
+ * style[mxConstants.STYLE_SHAPE] = mxConstants.SHAPE_RECTANGLE;
+ * style[mxConstants.STYLE_PERIMETER] = mxPerimeter.RectanglePerimeter;
+ * style[mxConstants.STYLE_ROUNDED] = true;
+ * graph.getStylesheet().putCellStyle('rounded', style);
+ * (end)
+ *
+ * In the above example, the new style is an object. The possible keys of
+ * the object are all the constants in <mxConstants> that start with STYLE
+ * and the values are either JavaScript objects, such as
+ * <mxPerimeter.RightAngleRectanglePerimeter> (which is in fact a function)
+ * or expressions, such as true. Note that not all keys will be
+ * interpreted by all shapes (eg. the line shape ignores the fill color).
+ * The final call to this method associates the style with a name in the
+ * stylesheet. The style is used in a cell with the following code:
+ *
+ * (code)
+ * model.setStyle(cell, 'rounded');
+ * (end)
+ *
+ * Parameters:
+ *
+ * name - Name for the style to be stored.
+ * style - Key, value pairs that define the style.
+ */
+mxStylesheet.prototype.putCellStyle = function(name, style)
+{
+ this.styles[name] = style;
+};
+
+/**
+ * Function: getCellStyle
+ *
+ * Returns the cell style for the specified stylename or the given
+ * defaultStyle if no style can be found for the given stylename.
+ *
+ * Parameters:
+ *
+ * name - String of the form [(stylename|key=value);] that represents the
+ * style.
+ * defaultStyle - Default style to be returned if no style can be found.
+ */
+mxStylesheet.prototype.getCellStyle = function(name, defaultStyle)
+{
+ var style = defaultStyle;
+
+ if (name != null && name.length > 0)
+ {
+ var pairs = name.split(';');
+
+ if (style != null &&
+ name.charAt(0) != ';')
+ {
+ style = mxUtils.clone(style);
+ }
+ else
+ {
+ style = new Object();
+ }
+
+ // Parses each key, value pair into the existing style
+ for (var i = 0; i < pairs.length; i++)
+ {
+ var tmp = pairs[i];
+ var pos = tmp.indexOf('=');
+
+ if (pos >= 0)
+ {
+ var key = tmp.substring(0, pos);
+ var value = tmp.substring(pos + 1);
+
+ if (value == mxConstants.NONE)
+ {
+ delete style[key];
+ }
+ else if (mxUtils.isNumeric(value))
+ {
+ style[key] = parseFloat(value);
+ }
+ else
+ {
+ style[key] = value;
+ }
+ }
+ else
+ {
+ // Merges the entries from a named style
+ var tmpStyle = this.styles[tmp];
+
+ if (tmpStyle != null)
+ {
+ for (var key in tmpStyle)
+ {
+ style[key] = tmpStyle[key];
+ }
+ }
+ }
+ }
+ }
+
+ return style;
+};
diff --git a/src/js/view/mxSwimlaneManager.js b/src/js/view/mxSwimlaneManager.js
new file mode 100644
index 0000000..fe40613
--- /dev/null
+++ b/src/js/view/mxSwimlaneManager.js
@@ -0,0 +1,449 @@
+/**
+ * $Id: mxSwimlaneManager.js,v 1.17 2011-01-14 15:21:10 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxSwimlaneManager
+ *
+ * Manager for swimlanes and nested swimlanes that sets the size of newly added
+ * swimlanes to that of their siblings, and propagates changes to the size of a
+ * swimlane to its siblings, if <siblings> is true, and its ancestors, if
+ * <bubbling> is true.
+ *
+ * Constructor: mxSwimlaneManager
+ *
+ * Constructs a new swimlane manager for the given graph.
+ *
+ * Arguments:
+ *
+ * graph - Reference to the enclosing graph.
+ */
+function mxSwimlaneManager(graph, horizontal, addEnabled, resizeEnabled)
+{
+ this.horizontal = (horizontal != null) ? horizontal : true;
+ this.addEnabled = (addEnabled != null) ? addEnabled : true;
+ this.resizeEnabled = (resizeEnabled != null) ? resizeEnabled : true;
+
+ this.addHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled() && this.isAddEnabled())
+ {
+ this.cellsAdded(evt.getProperty('cells'));
+ }
+ });
+
+ this.resizeHandler = mxUtils.bind(this, function(sender, evt)
+ {
+ if (this.isEnabled() && this.isResizeEnabled())
+ {
+ this.cellsResized(evt.getProperty('cells'));
+ }
+ });
+
+ this.setGraph(graph);
+};
+
+/**
+ * Extends mxEventSource.
+ */
+mxSwimlaneManager.prototype = new mxEventSource();
+mxSwimlaneManager.prototype.constructor = mxSwimlaneManager;
+
+/**
+ * Variable: graph
+ *
+ * Reference to the enclosing <mxGraph>.
+ */
+mxSwimlaneManager.prototype.graph = null;
+
+/**
+ * Variable: enabled
+ *
+ * Specifies if event handling is enabled. Default is true.
+ */
+mxSwimlaneManager.prototype.enabled = true;
+
+/**
+ * Variable: horizontal
+ *
+ * Specifies the orientation of the swimlanes. Default is true.
+ */
+mxSwimlaneManager.prototype.horizontal = true;
+
+/**
+ * Variable: addEnabled
+ *
+ * Specifies if newly added cells should be resized to match the size of their
+ * existing siblings. Default is true.
+ */
+mxSwimlaneManager.prototype.addEnabled = true;
+
+/**
+ * Variable: resizeEnabled
+ *
+ * Specifies if resizing of swimlanes should be handled. Default is true.
+ */
+mxSwimlaneManager.prototype.resizeEnabled = true;
+
+/**
+ * Variable: moveHandler
+ *
+ * Holds the function that handles the move event.
+ */
+mxSwimlaneManager.prototype.addHandler = null;
+
+/**
+ * Variable: moveHandler
+ *
+ * Holds the function that handles the move event.
+ */
+mxSwimlaneManager.prototype.resizeHandler = null;
+
+/**
+ * Function: isEnabled
+ *
+ * Returns true if events are handled. This implementation
+ * returns <enabled>.
+ */
+mxSwimlaneManager.prototype.isEnabled = function()
+{
+ return this.enabled;
+};
+
+/**
+ * Function: setEnabled
+ *
+ * Enables or disables event handling. This implementation
+ * updates <enabled>.
+ *
+ * Parameters:
+ *
+ * enabled - Boolean that specifies the new enabled state.
+ */
+mxSwimlaneManager.prototype.setEnabled = function(value)
+{
+ this.enabled = value;
+};
+
+/**
+ * Function: isHorizontal
+ *
+ * Returns <horizontal>.
+ */
+mxSwimlaneManager.prototype.isHorizontal = function()
+{
+ return this.horizontal;
+};
+
+/**
+ * Function: setHorizontal
+ *
+ * Sets <horizontal>.
+ */
+mxSwimlaneManager.prototype.setHorizontal = function(value)
+{
+ this.horizontal = value;
+};
+
+/**
+ * Function: isAddEnabled
+ *
+ * Returns <addEnabled>.
+ */
+mxSwimlaneManager.prototype.isAddEnabled = function()
+{
+ return this.addEnabled;
+};
+
+/**
+ * Function: setAddEnabled
+ *
+ * Sets <addEnabled>.
+ */
+mxSwimlaneManager.prototype.setAddEnabled = function(value)
+{
+ this.addEnabled = value;
+};
+
+/**
+ * Function: isResizeEnabled
+ *
+ * Returns <resizeEnabled>.
+ */
+mxSwimlaneManager.prototype.isResizeEnabled = function()
+{
+ return this.resizeEnabled;
+};
+
+/**
+ * Function: setResizeEnabled
+ *
+ * Sets <resizeEnabled>.
+ */
+mxSwimlaneManager.prototype.setResizeEnabled = function(value)
+{
+ this.resizeEnabled = value;
+};
+
+/**
+ * Function: getGraph
+ *
+ * Returns the graph that this manager operates on.
+ */
+mxSwimlaneManager.prototype.getGraph = function()
+{
+ return this.graph;
+};
+
+/**
+ * Function: setGraph
+ *
+ * Sets the graph that the manager operates on.
+ */
+mxSwimlaneManager.prototype.setGraph = function(graph)
+{
+ if (this.graph != null)
+ {
+ this.graph.removeListener(this.addHandler);
+ this.graph.removeListener(this.resizeHandler);
+ }
+
+ this.graph = graph;
+
+ if (this.graph != null)
+ {
+ this.graph.addListener(mxEvent.ADD_CELLS, this.addHandler);
+ this.graph.addListener(mxEvent.CELLS_RESIZED, this.resizeHandler);
+ }
+};
+
+/**
+ * Function: isSwimlaneIgnored
+ *
+ * Returns true if the given swimlane should be ignored.
+ */
+mxSwimlaneManager.prototype.isSwimlaneIgnored = function(swimlane)
+{
+ return !this.getGraph().isSwimlane(swimlane);
+};
+
+/**
+ * Function: isCellHorizontal
+ *
+ * Returns true if the given cell is horizontal. If the given cell is not a
+ * swimlane, then the global orientation is returned.
+ */
+mxSwimlaneManager.prototype.isCellHorizontal = function(cell)
+{
+ if (this.graph.isSwimlane(cell))
+ {
+ var state = this.graph.view.getState(cell);
+ var style = (state != null) ? state.style : this.graph.getCellStyle(cell);
+
+ return mxUtils.getValue(style, mxConstants.STYLE_HORIZONTAL, 1) == 1;
+ }
+
+ return !this.isHorizontal();
+};
+
+/**
+ * Function: cellsAdded
+ *
+ * Called if any cells have been added.
+ *
+ * Parameters:
+ *
+ * cell - Array of <mxCells> that have been added.
+ */
+mxSwimlaneManager.prototype.cellsAdded = function(cells)
+{
+ if (cells != null)
+ {
+ var model = this.getGraph().getModel();
+
+ model.beginUpdate();
+ try
+ {
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!this.isSwimlaneIgnored(cells[i]))
+ {
+ this.swimlaneAdded(cells[i]);
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: swimlaneAdded
+ *
+ * Updates the size of the given swimlane to match that of any existing
+ * siblings swimlanes.
+ *
+ * Parameters:
+ *
+ * swimlane - <mxCell> that represents the new swimlane.
+ */
+mxSwimlaneManager.prototype.swimlaneAdded = function(swimlane)
+{
+ var model = this.getGraph().getModel();
+ var parent = model.getParent(swimlane);
+ var childCount = model.getChildCount(parent);
+ var geo = null;
+
+ // Finds the first valid sibling swimlane as reference
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(parent, i);
+
+ if (child != swimlane && !this.isSwimlaneIgnored(child))
+ {
+ geo = model.getGeometry(child);
+
+ if (geo != null)
+ {
+ break;
+ }
+ }
+ }
+
+ // Applies the size of the refernece to the newly added swimlane
+ if (geo != null)
+ {
+ this.resizeSwimlane(swimlane, geo.width, geo.height);
+ }
+};
+
+/**
+ * Function: cellsResized
+ *
+ * Called if any cells have been resizes. Calls <swimlaneResized> for all
+ * swimlanes where <isSwimlaneIgnored> returns false.
+ *
+ * Parameters:
+ *
+ * cells - Array of <mxCells> whose size was changed.
+ */
+mxSwimlaneManager.prototype.cellsResized = function(cells)
+{
+ if (cells != null)
+ {
+ var model = this.getGraph().getModel();
+
+ model.beginUpdate();
+ try
+ {
+ // Finds the top-level swimlanes and adds offsets
+ for (var i = 0; i < cells.length; i++)
+ {
+ if (!this.isSwimlaneIgnored(cells[i]))
+ {
+ var geo = model.getGeometry(cells[i]);
+
+ if (geo != null)
+ {
+ var size = new mxRectangle(0, 0, geo.width, geo.height);
+ var top = cells[i];
+ var current = top;
+
+ while (current != null)
+ {
+ top = current;
+ current = model.getParent(current);
+ var tmp = (this.graph.isSwimlane(current)) ?
+ this.graph.getStartSize(current) :
+ new mxRectangle();
+ size.width += tmp.width;
+ size.height += tmp.height;
+ }
+
+ this.resizeSwimlane(top, size.width, size.height);
+ }
+ }
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+ }
+};
+
+/**
+ * Function: resizeSwimlane
+ *
+ * Called from <cellsResized> for all swimlanes that are not ignored to update
+ * the size of the siblings and the size of the parent swimlanes, recursively,
+ * if <bubbling> is true.
+ *
+ * Parameters:
+ *
+ * swimlane - <mxCell> whose size has changed.
+ */
+mxSwimlaneManager.prototype.resizeSwimlane = function(swimlane, w, h)
+{
+ var model = this.getGraph().getModel();
+
+ model.beginUpdate();
+ try
+ {
+ if (!this.isSwimlaneIgnored(swimlane))
+ {
+ var geo = model.getGeometry(swimlane);
+
+ if (geo != null)
+ {
+ var horizontal = this.isCellHorizontal(swimlane);
+
+ if ((horizontal && geo.height != h) || (!horizontal && geo.width != w))
+ {
+ geo = geo.clone();
+
+ if (horizontal)
+ {
+ geo.height = h;
+ }
+ else
+ {
+ geo.width = w;
+ }
+
+ model.setGeometry(swimlane, geo);
+ }
+ }
+ }
+
+ var tmp = (this.graph.isSwimlane(swimlane)) ?
+ this.graph.getStartSize(swimlane) :
+ new mxRectangle();
+ w -= tmp.width;
+ h -= tmp.height;
+
+ var childCount = model.getChildCount(swimlane);
+
+ for (var i = 0; i < childCount; i++)
+ {
+ var child = model.getChildAt(swimlane, i);
+ this.resizeSwimlane(child, w, h);
+ }
+ }
+ finally
+ {
+ model.endUpdate();
+ }
+};
+
+/**
+ * Function: destroy
+ *
+ * Removes all handlers from the <graph> and deletes the reference to it.
+ */
+mxSwimlaneManager.prototype.destroy = function()
+{
+ this.setGraph(null);
+};
diff --git a/src/js/view/mxTemporaryCellStates.js b/src/js/view/mxTemporaryCellStates.js
new file mode 100644
index 0000000..ce8232c
--- /dev/null
+++ b/src/js/view/mxTemporaryCellStates.js
@@ -0,0 +1,105 @@
+/**
+ * $Id: mxTemporaryCellStates.js,v 1.10 2010-04-20 14:43:12 gaudenz Exp $
+ * Copyright (c) 2006-2010, JGraph Ltd
+ */
+/**
+ * Class: mxTemporaryCellStates
+ *
+ * Extends <mxPoint> to implement a 2-dimensional rectangle with double
+ * precision coordinates.
+ *
+ * Constructor: mxRectangle
+ *
+ * Constructs a new rectangle for the optional parameters. If no parameters
+ * are given then the respective default values are used.
+ */
+function mxTemporaryCellStates(view, scale, cells)
+{
+ this.view = view;
+ scale = (scale != null) ? scale : 1;
+
+ // Stores the previous state
+ this.oldBounds = view.getGraphBounds();
+ this.oldStates = view.getStates();
+ this.oldScale = view.getScale();
+
+ // Creates space for new states
+ view.setStates(new mxDictionary());
+ view.setScale(scale);
+
+ if (cells != null)
+ {
+ // Creates virtual parent state for validation
+ var state = view.createState(new mxCell());
+
+ // Validates the vertices and edges without adding them to
+ // the model so that the original cells are not modified
+ for (var i = 0; i < cells.length; i++)
+ {
+ view.validateBounds(state, cells[i]);
+ }
+
+ var bbox = null;
+
+ for (var i = 0; i < cells.length; i++)
+ {
+ var bounds = view.validatePoints(state, cells[i]);
+
+ if (bbox == null)
+ {
+ bbox = bounds;
+ }
+ else
+ {
+ bbox.add(bounds);
+ }
+ }
+
+ if (bbox == null)
+ {
+ bbox = new mxRectangle();
+ }
+
+ view.setGraphBounds(bbox);
+ }
+};
+
+/**
+ * Variable: view
+ *
+ * Holds the width of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.view = null;
+
+/**
+ * Variable: oldStates
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldStates = null;
+
+/**
+ * Variable: oldBounds
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldBounds = null;
+
+/**
+ * Variable: oldScale
+ *
+ * Holds the height of the rectangle. Default is 0.
+ */
+mxTemporaryCellStates.prototype.oldScale = null;
+
+/**
+ * Function: destroy
+ *
+ * Returns the top, left corner as a new <mxPoint>.
+ */
+mxTemporaryCellStates.prototype.destroy = function()
+{
+ this.view.setScale(this.oldScale);
+ this.view.setStates(this.oldStates);
+ this.view.setGraphBounds(this.oldBounds);
+};
diff --git a/src/resources/editor.properties b/src/resources/editor.properties
new file mode 100644
index 0000000..23432a8
--- /dev/null
+++ b/src/resources/editor.properties
@@ -0,0 +1,5 @@
+askZoom=Enter zoom (%25)
+properties=Properties
+outline=Outline
+tasks=Tasks
+help=Help
diff --git a/src/resources/graph.properties b/src/resources/graph.properties
new file mode 100644
index 0000000..baf61f8
--- /dev/null
+++ b/src/resources/graph.properties
@@ -0,0 +1,11 @@
+alreadyConnected=Nodes already connected
+containsValidationErrors=Contains validation errors
+updatingDocument=Updating Document. Please wait...
+updatingSelection=Updating Selection. Please wait...
+collapse-expand=Collapse/Expand
+doubleClickOrientation=Doubleclick to change orientation
+close=Close
+error=Error
+done=Done
+cancel=Cancel
+ok=OK
diff --git a/styles/Xcos-style.xml b/styles/Xcos-style.xml
new file mode 100644
index 0000000..49d984e
--- /dev/null
+++ b/styles/Xcos-style.xml
@@ -0,0 +1,932 @@
+<?xml version="1.0"?>
+<mxStylesheet>
+ <!-- *** OVERLOADING DEFINITION *** -->
+ <add as="defaultVertex">
+ <add as="shape" value="label"/>
+ <add as="perimeter" value="rectanglePerimeter"/>
+ <add as="strokeColor" value="black"/>
+ <add as="strokeWidth" value="0.5"/>
+ <add as="fillColor" value="white"/>
+ <add as="fontColor" value="black"/>
+ <add as="noLabel" value="0"/>
+ </add>
+ <!-- *** GENERIC BLOCKS *** -->
+ <add as="block" extend="defaultVertex">
+ <add as="fillColor" value="#cdcdcd"/>
+ <add as="gradientColor" value="white"/>
+ <add as="rounded" value="1"/>
+ </add>
+ <add as="blockWithLabel" extend="defaultVertex">
+ <add as="noLabel" value="0"/>
+ <add as="spacing" value="13"/>
+ <add as="fillColor" value="#cdcdcd"/>
+ <add as="gradientColor" value="white"/>
+ <add as="rounded" value="1"/>
+ </add>
+ <!-- *** BLOCK DEFINITION *** -->
+ <!-- Affiche -->
+ <add as="Affiche" extend="blockWithLabel"/>
+ <add as="Split" extend="block">
+ <add as="shape" value="rectangle"/>
+ <add as="strokeColor" value="black"/>
+ <add as="fillColor" value="orange"/>
+ <remove as="gradientColor"/>
+ <remove as="rounded"/>
+ </add>
+ <!-- Label -->
+ <add as="Label" extend="defaultVertex">
+ <add as="shape" value="rectangle"/>
+ <add as="perimeter" value="rectanglePerimeter"/>
+ <add as="fillColor" value="white"/>
+ <add as="fontColor" value="black"/>
+ <add as="noLabel" value="0"/>
+ <add as="strokeColor" value="white"/>
+ </add>
+ <!-- Print (Label as block) -->
+ <add as="Print" extend="blockWithLabel">
+ <add as="displayedLabel" value="%s"/>
+ </add>
+ <!-- Gain -->
+ <add as="Gain" extend="blockWithLabel">
+ <add as="shape" value="triangle"/>
+ <add as="perimeter" value="trianglePerimeter"/>
+ <add as="direction" value="east"/>
+ <add as="displayedLabel" value="%s"/>
+ </add>
+ <!-- Icon -->
+ <add as="Icon" extend="block">
+ <add as="imageBorder" value="black"/>
+ </add>
+ <!-- *** PORT DEFINITION *** -->
+ <add as="Port" extend="defaultVertex">
+ <add as="shape" value="triangle"/>
+ <add as="perimeter" value="trianglePerimeter"/>
+ <add as="noLabel" value="0"/>
+ </add>
+ <!-- Command Port -->
+ <add as="CommandPort" extend="Port">
+ <add as="strokeColor" value="red"/>
+ <add as="fillColor" value="red"/>
+ <add as="rotation" value="90"/>
+ <add as="type" value="Command"/>
+ </add>
+ <!-- Control Port -->
+ <add as="ControlPort" extend="Port">
+ <add as="strokeColor" value="red"/>
+ <add as="fillColor" value="red"/>
+ <add as="rotation" value="90"/>
+ <add as="type" value="Control"/>
+ </add>
+ <!-- Explicit Input -->
+ <add as="ExplicitInputPort" extend="Port">
+ <add as="strokeColor" value="black"/>
+ <add as="fillColor" value="black"/>
+ <add as="rotation" value="0"/>
+ </add>
+ <!-- Explicit Output -->
+ <add as="ExplicitOutputPort" extend="Port">
+ <add as="strokeColor" value="black"/>
+ <add as="fillColor" value="black"/>
+ <add as="rotation" value="0"/>
+ </add>
+ <!-- Implicit Input -->
+ <add as="ImplicitInputPort" extend="Port">
+ <add as="shape" value="rectangle"/>
+ <add as="strokeColor" value="black"/>
+ <add as="fillColor" value="black"/>
+ </add>
+ <!-- Implicit Output -->
+ <add as="ImplicitOutputPort" extend="Port">
+ <add as="shape" value="rectangle"/>
+ <add as="strokeColor" value="black"/>
+ <add as="fillColor" value="white"/>
+ </add>
+ <!-- *** LINK definition *** -->
+ <add as="defaultEdge">
+ <!--<add as="edgeStyle" value="entityRelationEdgeStyle"/>-->
+ <add as="labelBackgroundColor" value="white"/>
+ <!-- To have rounded links -->
+ <!-- <add as="rounded" value="1"/> -->
+ <!-- <add as="edgeStyle" value="elbowEdgeStyle"/> -->
+ <add as="elbow" value="horizontal"/>
+ <add as="shape" value="connector"/>
+ <add as="labelBackgroundColor" value="white"/>
+ <add as="endArrow" value="classicnone"/>
+ <add as="fontSize" value="10"/>
+ <add as="align" value="center"/>
+ <add as="verticalAlign" value="middle"/>
+ <add as="strokeColor" value="black"/>
+ <add as="perimeter" value="null"/>
+ </add>
+ <add as="CommandControlLink" extend="defaultEdge">
+ <add as="strokeColor" value="red"/>
+ <add as="elbow" value="vertical"/>
+ </add>
+ <add as="ExplicitLink" extend="defaultEdge">
+ <add as="strokeColor" value="blue"/>
+ </add>
+ <add as="ImplicitLink" extend="defaultEdge">
+ <add as="strokeColor" value="blue"/>
+ </add>
+ <!-- SOURCES -->
+ <add as="CONST_m" extend="Print"/>
+ <add as="CONST_f" extend="Print"/>
+ <add as="CONST" extend="Print"/>
+ <add as="GENSQR_f" extend="Icon">
+ <add as="image" value="blocks/SQUARE_WAVE_f.svg"/>
+ </add>
+ <add as="RAMP" extend="Icon">
+ <add as="image" value="blocks/RAMP.svg"/>
+ </add>
+ <add as="RAND_m" extend="blockWithLabel">
+ <add as="displayedLabel" value="Random&lt;BR&gt; generator"/>
+ </add>
+ <add as="RFILE_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Read from&lt;BR&gt; input file"/>
+ </add>
+ <add as="CLKINV_f" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="rounded" value="1"/>
+ <add as="fillColor" value="white"/>
+ <add as="strokeColor" value="red"/>
+ </add>
+ <add as="CURV_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Curve"/>
+ </add>
+ <add as="INIMPL_f" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="rounded" value="1"/>
+ <add as="fillColor" value="white"/>
+ </add>
+ <add as="READAU_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Read from .au&lt;BR&gt; sound file"/>
+ </add>
+ <add as="SAWTOOTH_f" extend="Icon">
+ <add as="image" value="blocks/sawtooth.svg"/>
+ </add>
+ <add as="STEP_FUNCTION" extend="Icon">
+ <add as="image" value="blocks/STEP_FUNCTION.svg"/>
+ </add>
+ <add as="STEP" extend="Icon">
+ <add as="image" value="blocks/STEP_FUNCTION.svg"/>
+ </add>
+ <add as="PULSE_SC" extend="Icon">
+ <add as="image" value="blocks/PULSE_SC.svg"/>
+ </add>
+ <add as="CLOCK_c" extend="Icon">
+ </add>
+ <add as="GENSIN_f" extend="Icon">
+ <add as="image" value="blocks/SINUS_f.svg"/>
+ </add>
+ <add as="IN_f" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="rounded" value="1"/>
+ <add as="fillColor" value="white"/>
+ </add>
+ <add as="READC_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Read from&lt;BR&gt; C binary file"/>
+ </add>
+ <add as="TIME_f" extend="Icon">
+ <add as="image" value="blocks/CLOCK_f.svg"/>
+ </add>
+ <add as="Modulo_Count" extend="blockWithLabel">
+ <add as="displayedLabel" value="Counter&lt;BR&gt;Modulo %2$s"/>
+ </add>
+ <add as="Sigbuilder" extend="blockWithLabel">
+ <add as="displayedLabel" value="Signal&lt;BR&gt;Builder"/>
+ </add>
+ <add as="SampleCLK" extend="Icon">
+ </add>
+ <add as="TKSCALE" extend="blockWithLabel">
+ <add as="displayedLabel" value="TK Scale"/>
+ </add>
+ <add as="FROMWSB" extend="blockWithLabel">
+ <!-- FIXME: update label from parameters (not as exprs) -->
+ <add as="displayedLabel" value="From workspace"/>
+ </add>
+ <!-- CONTINUOUS -->
+ <add as="DERIV" extend="blockWithLabel">
+ <add as="displayedLabel" value="du / dt"/>
+ </add>
+ <add as="INTEGRAL_m" extend="Icon">
+ </add>
+ <add as="INTEGRAL" extend="INTEGRAL_m"/>
+ <add as="CLSS" extend="blockWithLabel">
+ <add as="displayedLabel" value="&lt;TABLE&gt; &lt;TR&gt; &lt;TD ALIGN=&quot;RIGHT&quot;&gt;xd&lt;/TD&gt; &lt;TD&gt;=&lt;/TD&gt; &lt;TD&gt;Ax+Bu&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD ALIGN=&quot;RIGHT&quot;&gt;y&lt;/TD&gt; &lt;TD&gt;=&lt;/TD&gt; &lt;TD&gt;Cx+Du&lt;/TD&gt; &lt;/TR&gt; &lt;/TABLE&gt;"/>
+ </add>
+ <add as="CLSS_f" extend="CLSS"/>
+ <add as="CLR" extend="blockWithLabel">
+ <add as="displayedLabel" value="$\frac{%s}{%s}$"/>
+ </add>
+ <add as="CLR_f" extend="CLR"/>
+ <add as="TIME_DELAY" extend="blockWithLabel">
+ <add as="displayedLabel" value="Continuous&lt;BR&gt; fix delay"/>
+ </add>
+ <add as="TCLSS" extend="blockWithLabel">
+ <add as="displayedLabel" value="Jump&lt;BR&gt; (A,B,C,D)"/>
+ </add>
+ <add as="TCLSS_f" extend="TCLSS"/>
+ <add as="VARIABLE_DELAY" extend="blockWithLabel">
+ <add as="displayedLabel" value="Variable&lt;BR&gt; delay"/>
+ </add>
+ <add as="PID" extend="blockWithLabel">
+ <add as="displayedLabel" value="PID"/>
+ </add>
+ <add as="INTEGRAL_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="1/s"/>
+ </add>
+ <!-- DISCONTINUOUS -->
+ <add as="SATURATION" extend="Icon">
+ </add>
+ <add as="DEADBAND" extend="Icon">
+ </add>
+ <add as="HYSTHERESIS" extend="Icon">
+ </add>
+ <add as="BACKLASH" extend="blockWithLabel">
+ <add as="displayedLabel" value="Backlash"/>
+ </add>
+ <add as="RATELIMITER" extend="blockWithLabel">
+ <add as="displayedLabel" value="Rate limiter"/>
+ </add>
+ <add as="REGISTER" extend="blockWithLabel">
+ <add as="displayedLabel" value="Shift&lt;BR&gt; register"/>
+ </add>
+ <add as="DELAYV_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Variable&lt;BR&gt; delay"/>
+ </add>
+ <!-- LOOKUP TABLES -->
+ <add as="LOOKUP_f" extend="Icon">
+ </add>
+ <add as="INTRP2BLK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Interp 2"/>
+ </add>
+ <add as="INTRPLBLK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Interp"/>
+ </add>
+ <!-- SIGNAL PROCESSING -->
+ <add as="MCLOCK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="2freq clock&lt;BR&gt; f/n f"/>
+ </add>
+ <add as="QUANT_f" extend="Icon">
+ </add>
+ <add as="MFCLCK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="M. freq&lt;BR&gt; clock"/>
+ </add>
+ <add as="SAMPHOLD_m" extend="blockWithLabel">
+ <add as="displayedLabel" value="S / H"/>
+ </add>
+ <add as="SAMPLEHOLD_f" extend="SAMPHOLD_m"/>
+ <!-- THRESHOLD -->
+ <add as="NEGTOPOS_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="- to +"/>
+ </add>
+ <add as="POSTONEG_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="+ to -"/>
+ </add>
+ <add as="ZCROSS_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Zcross"/>
+ </add>
+ <add as="GENERAL_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="GENERAL"/>
+ </add>
+ <add as="CLINDUMMY_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="DUMMY&lt;BR&gt; CLSS"/>
+ </add>
+ <!-- MATH OPERATIONS -->
+ <add as="MAX_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="MAX"/>
+ </add>
+ <add as="MIN_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="MIN"/>
+ </add>
+ <add as="BIGSOM_f" extend="Icon">
+ </add>
+ <add as="POWBLK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="u^a"/>
+ </add>
+ <add as="INVBLK" extend="blockWithLabel">
+ <add as="displayedLabel" value="1/u"/>
+ </add>
+ <add as="INVBLK_f" extend="INVBLK"/>
+ <add as="SINBLK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="SIN"/>
+ </add>
+ <add as="COSBLK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="COS"/>
+ </add>
+ <add as="TANBLK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="TAN"/>
+ </add>
+ <add as="MATDIV" extend="blockWithLabel">
+ <add as="displayedLabel" value="A / B"/>
+ </add>
+ <add as="EXPBLK_m" extend="blockWithLabel">
+ <add as="displayedLabel" value="a^u"/>
+ </add>
+ <add as="PROD_f" extend="blockWithLabel">
+ <add as="shape" value="ellipse"/>
+ <remove as="image"/>
+ <add as="fontSize" value="20"/>
+ </add>
+ <add as="MATZREIM" extend="blockWithLabel">
+ <add as="displayedLabel" value="Re &amp; Im"/>
+ </add>
+ <add as="MATMAGPHI" extend="blockWithLabel">
+ <add as="displayedLabel" value="Mag &amp; Phi"/>
+ </add>
+ <add as="SQRT" extend="blockWithLabel"/>
+ <add as="GAINBLK_f" extend="Gain"/>
+ <add as="LOGBLK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="LOG"/>
+ </add>
+ <add as="SUMMATION" extend="Icon">
+ </add>
+ <add as="TrigFun" extend="blockWithLabel">
+ <add as="displayedLabel" value="Trig function"/>
+ </add>
+ <add as="PRODUCT" extend="Icon">
+ <add as="noLabel" value="0"/>
+ <!--
+ <add as="displayedLabel" value="&lt;TABLE&gt; &lt;TR&gt;
+ &lt;TD&gt;*&lt;/TD&gt; &lt;TD ROWSPAN=&quot;2&quot;&gt; &lt;FONT
+ SIZE=&quot;6&quot;&gt;&#8719;&lt;/FONT&gt; &lt;TD&gt; &lt;/TR&gt;
+ &lt;TR&gt; &lt;TD&gt;/&lt;/TD&gt; &lt;TD/&gt; &lt;/TR&gt;
+ &lt;/TABLE&gt;"/>
+ -->
+ </add>
+ <add as="MAXMIN" extend="blockWithLabel">
+ <add as="displayedLabel" value="MIN / MAX"/>
+ </add>
+ <add as="ABS_VALUE" extend="blockWithLabel">
+ <add as="displayedLabel" value="ABS"/>
+ </add>
+ <add as="SIGNUM" extend="blockWithLabel">
+ <add as="displayedLabel" value="SIGN"/>
+ </add>
+ <add as="SUM_f" extend="blockWithLabel">
+ <add as="shape" value="ellipse"/>
+ <remove as="image"/>
+ <add as="fontSize" value="20"/>
+ </add>
+ <add as="SOM_f" extend="blockWithLabel">
+ <add as="shape" value="ellipse"/>
+ <remove as="image"/>
+ <add as="fontSize" value="20"/>
+ <add as="displayedLabel" value="+"/>
+ </add>
+ <add as="CONSTRAINT_c" extend="blockWithLabel">
+ <add as="displayedLabel" value="x == %s"/>
+ </add>
+ <add as="CONSTRAINT2_c" extend="blockWithLabel">
+ <add as="displayedLabel" value="x == %s&lt;br/&gt;x' == %s"/>
+ </add>
+ <!-- MODELICA -->
+ <add as="MBLOCK" extend="blockWithLabel">
+ <add as="displayedLabel" value="Modelica&lt;BR&gt; generic"/>
+ </add>
+ <add as="FROMMO" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="fillColor" value="white"/>
+ <add as="displayedLabel" value="%s"/>
+ </add>
+ <add as="GOTOMO" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="fillColor" value="white"/>
+ <add as="displayedLabel" value="%s"/>
+ </add>
+ <add as="GotoTagVisibilityMO" extend="blockWithLabel">
+ <add as="displayedLabel" value="&lt;FONT SIZE=&quot;6&quot;&gt;{%s}&lt;/FONT&gt;"/>
+ <add as="shape" value="ellipse"/>
+ <add as="strokeColor" value="green"/>
+ </add>
+ <add as="OUTIMPL_f" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="fillColor" value="white"/>
+ </add>
+ <!-- INTEGER -->
+ <add as="BITCLEAR" extend="blockWithLabel">
+ <add as="displayedLabel" value="Clear bit %2$s"/>
+ </add>
+ <add as="BITSET" extend="blockWithLabel">
+ <add as="displayedLabel" value="Set bit %2$s"/>
+ </add>
+ <add as="CONVERT" extend="blockWithLabel">
+ <!-- FIXME: update label from parameters -->
+ <add as="displayedLabel" value="Convert to"/>
+ </add>
+ <add as="EXTRACTBITS" extend="blockWithLabel">
+ <add as="displayedLabel" value="Extract Bits %2$s"/>
+ </add>
+ <add as="INTMUL" extend="blockWithLabel">
+ <add as="displayedLabel" value="INTMUL"/>
+ </add>
+ <add as="SHIFT" extend="blockWithLabel">
+ <add as="displayedLabel" value="Arithmetic&lt;BR&gt; shift %2$s"/>
+ </add>
+ <add as="LOGIC" extend="blockWithLabel">
+ <add as="displayedLabel" value="LOGIC"/>
+ </add>
+ <add as="DLATCH" extend="blockWithLabel">
+ <add as="displayedLabel" value="&lt;TABLE&gt; &lt;TR&gt; &lt;TD&gt;D&lt;/TD&gt; &lt;TD&gt;Q&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD&gt;C&lt;/TD&gt; &lt;TD&gt;!Q&lt;/TD&gt; &lt;/TR&gt; &lt;/TABLE&gt;"/>
+ </add>
+ <add as="DFLIPFLOP" extend="blockWithLabel">
+ <add as="displayedLabel" value="&lt;TABLE&gt; &lt;TR&gt; &lt;TD&gt;D&lt;/TD&gt; &lt;TD/&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD/&gt; &lt;TD&gt;Q&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD&gt;clk&lt;/TD&gt; &lt;TD/&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD/&gt; &lt;TD&gt;!Q&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD&gt;en&lt;/TD&gt; &lt;TD/&gt; &lt;/TR&gt; &lt;/TABLE&gt;"/>
+ </add>
+ <add as="JKFLIPFLOP" extend="blockWithLabel">
+ <add as="displayedLabel" value="&lt;TABLE&gt; &lt;TR&gt; &lt;TD&gt;J&lt;/TD&gt; &lt;TD/&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD/&gt; &lt;TD&gt;Q&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD&gt;clk&lt;/TD&gt; &lt;TD/&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD/&gt; &lt;TD&gt;!Q&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD&gt;K&lt;/TD&gt; &lt;TD/&gt; &lt;/TR&gt; &lt;/TABLE&gt;"/>
+ </add>
+ <add as="SRFLIPFLOP" extend="blockWithLabel">
+ <add as="displayedLabel" value="&lt;TABLE&gt; &lt;TR&gt; &lt;TD&gt;S&lt;/TD&gt; &lt;TD&gt;Q&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD&gt;R&lt;/TD&gt; &lt;TD&gt;!Q&lt;/TD&gt; &lt;/TR&gt; &lt;/TABLE&gt;"/>
+ </add>
+ <!-- MATRIX -->
+ <add as="CUMSUM" extend="blockWithLabel">
+ <add as="displayedLabel" value="CUMSUM"/>
+ </add>
+ <add as="SUBMAT" extend="blockWithLabel"/>
+ <add as="MATBKSL" extend="blockWithLabel">
+ <add as="displayedLabel" value="A \ B"/>
+ </add>
+ <add as="MATINV" extend="blockWithLabel">
+ <add as="displayedLabel" value="INV"/>
+ </add>
+ <add as="MATCATV" extend="blockWithLabel">
+ <add as="displayedLabel" value="Vert&lt;BR&gt; Cat"/>
+ </add>
+ <add as="MATSUM" extend="blockWithLabel"/>
+ <add as="RICC" extend="blockWithLabel"/>
+ <add as="ROOTCOEF" extend="blockWithLabel">
+ <add as="displayedLabel" value="ROOT&lt;BR&gt;COEF"/>
+ </add>
+ <add as="MATCATH" extend="blockWithLabel">
+ <add as="displayedLabel" value="Horiz&lt;BR&gt; Cat"/>
+ </add>
+ <add as="MATLU" extend="blockWithLabel">
+ <add as="displayedLabel" value="LU"/>
+ </add>
+ <add as="MATZCONJ" extend="blockWithLabel">
+ <add as="displayedLabel" value="CONJ"/>
+ </add>
+ <add as="EXTRACT" extend="blockWithLabel"/>
+ <add as="MATEXPM" extend="blockWithLabel">
+ <add as="displayedLabel" value="EXPM"/>
+ </add>
+ <add as="MATDET" extend="blockWithLabel">
+ <add as="displayedLabel" value="DET"/>
+ </add>
+ <add as="MATPINV" extend="blockWithLabel">
+ <add as="displayedLabel" value="PINV"/>
+ </add>
+ <add as="EXTTRI" extend="blockWithLabel">
+ <add as="displayedLabel" value="Tri/Diag&lt;BR&gt; Extraction"/>
+ </add>
+ <add as="MATMUL" extend="blockWithLabel"/>
+ <add as="MATTRAN" extend="blockWithLabel"/>
+ <add as="MATSING" extend="blockWithLabel">
+ <add as="displayedLabel" value="SVD"/>
+ </add>
+ <add as="MATRESH" extend="blockWithLabel">
+ <add as="displayedLabel" value="RESHAPE"/>
+ </add>
+ <add as="MATDIAG" extend="blockWithLabel">
+ <add as="displayedLabel" value="DIAG"/>
+ </add>
+ <add as="MATEIG" extend="blockWithLabel">
+ <add as="displayedLabel" value="EIG"/>
+ </add>
+ <!-- SINKS -->
+ <add as="CFSCOPE" extend="Icon">
+ <add as="image" value="blocks/ASCOPE.svg"/>
+ </add>
+ <add as="BARXY" extend="Icon">
+ <add as="image" value="blocks/BARXY.svg"/>
+ </add>
+ <add as="CANIMXY" extend="Icon">
+ <add as="image" value="blocks/3DSCOPE.svg"/>
+ </add>
+ <add as="CSCOPE" extend="Icon">
+ <add as="image" value="blocks/ASCOPE.svg"/>
+ </add>
+ <add as="CSCOPXY" extend="Icon">
+ </add>
+ <add as="TOWS_c" extend="blockWithLabel">
+ <add as="displayedLabel" value="To workspace&lt;BR&gt;%2$s [%1$s]"/>
+ </add>
+ <add as="CMAT3D" extend="blockWithLabel">
+ <add as="image" value="blocks/ASCOPE.svg"/>
+ <add as="verticalLabelPosition" value="bottom"/>
+ <add as="verticalAlign" value="top"/>
+ <add as="spacing" value="2"/>
+ <add as="displayedLabel" value="Mat. 3D"/>
+ </add>
+ <add as="CSCOPXY3D" extend="Icon">
+ <add as="image" value="blocks/CSCOPXY3D.svg"/>
+ </add>
+ <add as="CANIMXY3D" extend="Icon">
+ <add as="image" value="blocks/3DSCOPE.svg"/>
+ </add>
+ <add as="CMATVIEW" extend="blockWithLabel">
+ <add as="image" value="blocks/ASCOPE.svg"/>
+ <add as="verticalLabelPosition" value="bottom"/>
+ <add as="verticalAlign" value="top"/>
+ <add as="spacing" value="2"/>
+ <add as="displayedLabel" value="Mat. View"/>
+ </add>
+ <add as="CMSCOPE" extend="Icon">
+ </add>
+ <add as="AFFICH_m" extend="Affiche"/>
+ <add as="AFFICH_f" extend="Affiche"/>
+ <add as="TRASH_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Trash"/>
+ </add>
+ <!-- PORT ACTION -->
+ <add as="Extract_Activation" extend="blockWithLabel">
+ <add as="displayedLabel" value="Extract&lt;BR&gt; activation"/>
+ </add>
+ <add as="IFTHEL_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="if in&gt;0&lt;BR&gt; then else"/>
+ </add>
+ <add as="ESELECT_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Event select"/>
+ </add>
+ <add as="EDGE_TRIGGER" extend="blockWithLabel">
+ <add as="displayedLabel" value="Edge&lt;BR&gt; trigger"/>
+ </add>
+ <!-- DISCRETE -->
+ <add as="DLRADAPT_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="N(z,p)&lt;BR&gt;&lt;HR&gt;D(z,p)"/>
+ </add>
+ <add as="DLR" extend="blockWithLabel">
+ <add as="displayedLabel" value="$\frac{%s}{%s}$"/>
+ </add>
+ <add as="DLR_f" extend="DLR"/>
+ <add as="DLSS" extend="blockWithLabel">
+ <add as="displayedLabel" value="&lt;TABLE&gt; &lt;TR&gt; &lt;TD ALIGN=&quot;RIGHT&quot;&gt;x&lt;/TD&gt; &lt;TD ALIGN=&quot;CENTER&quot;&gt;+=&lt;/TD&gt; &lt;TD&gt;Ax+Bu&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD ALIGN=&quot;RIGHT&quot;&gt;y&lt;/TD&gt; &lt;TD ALIGN=&quot;CENTER&quot;&gt;=&lt;/TD&gt; &lt;TD&gt;Cx+Du&lt;/TD&gt; &lt;/TR&gt; &lt;/TABLE&gt;"/>
+ </add>
+ <add as="DELAY_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Delay"/>
+ </add>
+ <add as="DOLLAR_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="1/z"/>
+ </add>
+ <add as="DOLLAR" extend="DOLLAR_f"/>
+ <!-- EVENTS -->
+ <add as="CLKFROM" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="fillColor" value="white"/>
+ <add as="strokeColor" value="red"/>
+ <add as="displayedLabel" value="%s"/>
+ </add>
+ <add as="CLKGOTO" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="fillColor" value="white"/>
+ <add as="strokeColor" value="red"/>
+ <add as="displayedLabel" value="%s"/>
+ </add>
+ <add as="CLKGotoTagVisibility" extend="blockWithLabel">
+ <add as="fontSize" value="20"/>
+ <add as="displayedLabel" value="{%s}"/>
+ <add as="shape" value="ellipse"/>
+ <add as="strokeColor" value="red"/>
+ </add>
+ <add as="CLKOUTV_f" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="fillColor" value="white"/>
+ <add as="strokeColor" value="red"/>
+ </add>
+ <add as="CLKOUT_f" extend="CLKOUTV_f"/>
+ <add as="CLKSOMV_f" extend="blockWithLabel">
+ <add as="shape" value="ellipse"/>
+ <add as="displayedLabel" value="+"/>
+ <add as="fontColor" value="red"/>
+ <add as="strokeColor" value="red"/>
+ <add as="fontSize" value="20"/>
+ <add as="spacing" value="5"/>
+ <add as="spacingLeft" value="6"/>
+ <add as="spacingRight" value="6"/>
+ </add>
+ <add as="EVTGEN_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Event at&lt;BR&gt; time %s"/>
+ </add>
+ <add as="EVTVARDLY" extend="blockWithLabel">
+ <add as="displayedLabel" value="Event&lt;BR&gt; delay"/>
+ </add>
+ <add as="M_freq" extend="blockWithLabel">
+ <add as="displayedLabel" value="Multiple&lt;BR&gt; frequency"/>
+
+ </add>
+ <add as="ANDBLK" extend="Icon">
+ </add>
+ <add as="HALT_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="HALT"/>
+ </add>
+ <add as="freq_div" extend="blockWithLabel">
+ <add as="displayedLabel" value="Frequency&lt;BR&gt; division"/>
+ </add>
+ <add as="ANDLOG_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="LOGICAL&lt;BR&gt; AND"/>
+ </add>
+ <add as="EVTDLY_c" extend="blockWithLabel">
+ <add as="displayedLabel" value="Delay: %s"/>
+ </add>
+ <add as="CEVENTSCOPE" extend="Icon">
+ <add as="image" value="blocks/DSCOPE.svg"/>
+ </add>
+ <!-- SIGNAL ROUTING -->
+ <add as="SELF_SWITCH_ON" extend="Icon">
+ <add as="image" value="blocks/Self_Switch_on.svg"/>
+ </add>
+ <add as="SELF_SWITCH_OFF" extend="Icon">
+ <add as="image" value="blocks/Self_Switch_off.svg"/>
+ </add>
+ <add as="ISELECT_m" extend="blockWithLabel">
+ <add as="displayedLabel" value="Selector"/>
+ </add>
+ <add as="RELAY_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Relay"/>
+ </add>
+ <add as="WRITEAU_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Write AU to&lt;BR&gt; /dev/audio"/>
+ </add>
+ <add as="SELECT_m" extend="blockWithLabel">
+ <add as="displayedLabel" value="Selector"/>
+ </add>
+ <add as="SELECT_f" extend="SELECT_m"/>
+ <add as="EXTRACTOR" extend="blockWithLabel">
+ <add as="displayedLabel" value="Extractor"/>
+ </add>
+ <add as="M_SWITCH" extend="Icon">
+ <add as="verticalLabelPosition" value="bottom"/>
+ <add as="verticalAlign" value="top"/>
+ <add as="spacing" value="2"/>
+ <add as="noLabel" value="0"/>
+ <add as="displayedLabel" value="Dynamic index"/>
+ <add as="image" value="blocks/SWITCH.svg"/>
+ </add>
+ <add as="SWITCH_f" extend="Icon">
+ <add as="verticalLabelPosition" value="bottom"/>
+ <add as="verticalAlign" value="top"/>
+ <add as="spacing" value="2"/>
+ <add as="noLabel" value="0"/>
+ <add as="displayedLabel" value="Static: %2$s"/>
+ <add as="image" value="blocks/SWITCH.svg"/>
+ </add>
+ <add as="SWITCH2_m" extend="Icon">
+ </add>
+ <add as="NRMSOM_f" extend="blockWithLabel">
+ <add as="verticalLabelPosition" value="bottom"/>
+ <add as="verticalAlign" value="top"/>
+ <add as="spacing" value="2"/>
+ <add as="displayedLabel" value="Bus creator"/>
+ </add>
+ <add as="WRITEC_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Write to&lt;BR&gt;C binary file"/>
+ </add>
+ <add as="GOTO" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="fillColor" value="white"/>
+ <add as="displayedLabel" value="%s"/>
+ </add>
+ <add as="GotoTagVisibility" extend="blockWithLabel">
+ <add as="displayedLabel" value="&lt;FONT SIZE=&quot;6&quot;&gt;{%s}&lt;/FONT&gt;"/>
+ </add>
+ <add as="FROM" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="fillColor" value="white"/>
+ <add as="displayedLabel" value="%s"/>
+ </add>
+ <add as="WFILE_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Write to&lt;BR&gt; output file"/>
+ </add>
+ <add as="MUX" extend="blockWithLabel">
+ <add as="verticalLabelPosition" value="bottom"/>
+ <add as="verticalAlign" value="top"/>
+ <add as="displayedLabel" value="MUX"/>
+ <add as="spacing" value="2"/>
+ </add>
+ <add as="DEMUX" extend="blockWithLabel">
+ <add as="verticalLabelPosition" value="bottom"/>
+ <add as="verticalAlign" value="top"/>
+ <add as="displayedLabel" value="DEMUX"/>
+ <add as="spacing" value="2"/>
+ </add>
+ <add as="SCALAR2VECTOR" extend="blockWithLabel">
+ <add as="displayedLabel" value="SCALAR&lt;BR&gt;to VECTOR"/>
+ </add>
+ <!-- COMMONLY USED BLOCKS -->
+ <add as="OUT_f" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="rounded" value="1"/>
+ <add as="fillColor" value="white"/>
+ </add>
+ <add as="RELATIONALOP" extend="blockWithLabel">
+ <add as="displayedLabel" value="Relational&lt;BR&gt; op : &amp;&lt;"/>
+ <!-- new value for the label defined in the interface function of block -->
+ </add>
+ <add as="TEXT_f" extend="Label">
+ <!-- <add as="displayedLabel" value="Text"/> -->
+ <add as="strokeColor" value="none"/>
+ <add as="fillColor" value="none"/>
+ </add>
+ <!-- USER-DEFINED FUNCTIONS -->
+ <add as="PDE" extend="blockWithLabel"/>
+ <add as="fortran_block" extend="blockWithLabel">
+ <add as="displayedLabel" value="Fortran block:&lt;BR&gt;%4$s"/>
+ </add>
+ <add as="DEBUG" extend="blockWithLabel">
+ <add as="displayedLabel" value="Debug:&lt;BR&gt;%2$s"/>
+ </add>
+ <add as="EXPRESSION" extend="blockWithLabel">
+ <add as="displayedLabel" value="Expression:&lt;BR&gt;%2$s"/>
+ </add>
+ <add as="scifunc_block_m" extend="blockWithLabel">
+ <add as="displayedLabel" value="Function:&lt;BR&gt;%10$s"/>
+ </add>
+ <add as="scifunc_block" extend="scifunc_block_m"/>
+ <add as="CBLOCK" extend="blockWithLabel">
+ <add as="displayedLabel" value="C block 2:&lt;BR&gt;%1$s"/>
+ </add>
+ <add as="CBLOCK4" extend="blockWithLabel">
+ <add as="displayedLabel" value="C block 4:&lt;BR&gt;%1$s"/>
+ </add>
+ <add as="generic_block3" extend="blockWithLabel">
+ <add as="displayedLabel" value="native block:&lt;BR&gt;%1$s"/>
+ </add>
+ <add as="c_block" extend="blockWithLabel">
+ <add as="displayedLabel" value="C block:&lt;BR&gt;%4$s"/>
+ </add>
+ <add as="SUPER_f" extend="Icon">
+ <add as="image" value="blocks/SUPER.svg"/>
+ </add>
+ <add as="DSUPER" extend="SUPER_f"/>
+ <!-- ELECTRICAL -->
+ <add as="Capacitor" extend="Icon">
+ <add as="image" value="blocks/Capacitor.svg"/>
+ </add>
+ <add as="Ground" extend="Icon">
+ <add as="image" value="blocks/Ground.svg"/>
+ </add>
+ <add as="VVsourceAC" extend="blockWithLabel">
+ <add as="shape" value="ellipse"/>
+ <add as="perimeter" value="ellipsePerimeter"/>
+ <add as="displayedLabel" value="1 V&lt;br/&gt;~ %s"/>
+ </add>
+ <add as="ConstantVoltage" extend="Icon">
+ <add as="image" value="blocks/ConstantVoltage.svg"/>
+ </add>
+ <add as="Inductor" extend="Icon">
+ <add as="image" value="blocks/Inductor.svg"/>
+ </add>
+ <add as="PotentialSensor" extend="Icon">
+ <add as="image" value="blocks/PotentialSensor.svg"/>
+ </add>
+ <add as="VariableResistor" extend="Icon">
+ <add as="image" value="blocks/VariableResistor.svg"/>
+ </add>
+ <add as="CurrentSensor" extend="Icon">
+ <add as="image" value="blocks/CurrentSensor.svg"/>
+ </add>
+ <add as="Resistor" extend="Icon">
+ <add as="image" value="blocks/Resistor.svg"/>
+ </add>
+ <add as="VoltageSensor" extend="Icon">
+ <add as="image" value="blocks/VoltageSensor.svg"/>
+ </add>
+ <add as="Diode" extend="Icon">
+ <add as="image" value="blocks/Diode.svg"/>
+ </add>
+ <add as="VsourceAC" extend="blockWithLabel">
+ <add as="shape" value="ellipse"/>
+ <add as="perimeter" value="ellipsePerimeter"/>
+ <add as="textAlign" value="center"/>
+ <add as="displayedLabel" value="%s V&lt;br/&gt;~ %s"/>
+ </add>
+ <add as="NPN" extend="Icon">
+ <add as="image" value="blocks/NPN.svg"/>
+ </add>
+ <add as="PNP" extend="Icon">
+ <add as="image" value="blocks/PNP.svg"/>
+ </add>
+ <add as="SineVoltage" extend="blockWithLabel">
+ <add as="shape" value="ellipse"/>
+ <add as="perimeter" value="ellipsePerimeter"/>
+ <add as="displayedLabel" value="%s V&lt;br/&gt;~"/>
+ </add>
+ <add as="Switch" extend="Icon">
+ <add as="image" value="blocks/SWITCH.svg"/>
+ </add>
+ <add as="OpAmp" extend="blockWithLabel">
+ <add as="shape" value="triangle"/>
+ <add as="perimeter" value="trianglePerimeter"/>
+ <add as="direction" value="east"/>
+ <add as="displayedLabel" value="&lt;TABLE&gt; &lt;TR&gt; &lt;TD&gt;+&lt;/TD&gt; &lt;TD&gt;&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD&gt;&lt;/TD&gt; &lt;TD&gt;OP&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD&gt;-&lt;/TD&gt; &lt;TD&gt;&lt;/TD&gt; &lt;/TR&gt; &lt;/TABLE&gt;"/>
+ <add as="spacing" value="0"/>
+ <add as="spacingRight" value="5"/>
+ <add as="spacingTop" value="7"/>
+ <add as="spacingBottom" value="7"/>
+ </add>
+ <add as="PMOS" extend="Icon">
+ <add as="image" value="blocks/PMOS.svg"/>
+ </add>
+ <add as="NMOS" extend="Icon">
+ <add as="image" value="blocks/NMOS.svg"/>
+ </add>
+ <add as="CCS" extend="Icon">
+ <add as="image" value="blocks/CCS.svg"/>
+ </add>
+ <add as="CVS" extend="Icon">
+ <add as="image" value="blocks/CVS.svg"/>
+ </add>
+ <add as="IdealTransformer" extend="Icon">
+ <add as="image" value="blocks/IdealTransformer.svg"/>
+ </add>
+ <add as="Gyrator" extend="Icon">
+ <add as="image" value="blocks/Gyrator.svg"/>
+ </add>
+ <!-- THERMO-HYDRAULICS -->
+ <add as="Bache" extend="Icon">
+ <add as="image" value="blocks/BACHE.svg"/>
+ </add>
+ <add as="VanneReglante" extend="Icon">
+ <add as="image" value="blocks/VanneReglante.svg"/>
+ </add>
+ <add as="PerteDP" extend="Icon">
+ <add as="image" value="blocks/PerteDP.svg"/>
+ </add>
+ <add as="PuitsP" extend="Icon">
+ <add as="image" value="blocks/PuitP.svg"/>
+ </add>
+ <add as="SourceP" extend="Icon">
+ <add as="image" value="blocks/SourceP.svg"/>
+ </add>
+ <add as="Flowmeter" extend="Icon">
+ <add as="image" value="blocks/Flowmeter.svg"/>
+ </add>
+ <!-- DEMONSTRATION BLOCKS -->
+ <add as="BOUNCE" extend="blockWithLabel">
+ <add as="displayedLabel" value="Bouncing&lt;BR&gt; balls"/>
+ </add>
+ <add as="BOUNCEXY" extend="Icon">
+ <add as="image" value="blocks/3DSCOPE.svg"/>
+ </add>
+ <add as="BPLATFORM" extend="Icon">
+ <add as="image" value="blocks/BPLATFORM.svg"/>
+ </add>
+ <add as="AUTOMAT" extend="blockWithLabel">
+ <!-- FIXME : Show parameters over block -->
+ <add as="displayedLabel" value="Automaton&lt;BR&gt; nM=2, nX=1"/>
+ </add>
+ <!-- GENERATED BLOCKS -->
+ <add as="SPLIT_f" extend="Split"/>
+ <!--
+ <add as="SCALAR2VECTOR" extend="Icon">
+ <add as="image" value="blocks/SCALAR2VECTOR.gif" />
+ </add>
+ -->
+ <add as="SAT_f" extend="SATURATION"/>
+ <!-- RAND_f and RAND_m looks exactly the same -->
+ <add as="RAND_f" extend="RAND_m"/>
+ <add as="MUX_f" extend="MUX"/>
+ <add as="MEMORY_f" extend="blockWithLabel"/>
+ <add as="LOGICAL_OP" extend="blockWithLabel">
+ <add as="displayedLabel" value="AND"/>
+ </add>
+ <add as="generic_block" extend="blockWithLabel">
+ <add as="displayedLabel" value="GENERIC"/>
+ </add>
+ <add as="GAINBLK" extend="Gain"/>
+ <add as="GAIN_f" extend="Gain"/>
+ <add as="EVTDLY_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Delay"/>
+ </add>
+ <add as="END_c" extend="blockWithLabel">
+ <add as="displayedLabel" value="END"/>
+ </add>
+ <add as="ENDBLK" extend="blockWithLabel">
+ <add as="displayedLabel" value="END"/>
+ </add>
+ <add as="EDGETRIGGER" extend="blockWithLabel">
+ <add as="displayedLabel" value="Edge &lt;BR&gt;trigger"/>
+ </add>
+ <add as="DOLLAR_m" extend="DOLLAR_f"/>
+ <add as="DIFF_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="S"/>
+ </add>
+ <add as="DEMUX_f" extend="DEMUX"/>
+ <add as="DEADBAND" extend="Icon">
+ </add>
+ <add as="Counter" extend="blockWithLabel">
+ <add as="displayedLabel" value="Counter&lt;BR&gt;%s &amp;#8594; %s"/>
+ </add>
+ <add as="CLOCK_f" extend="Icon">
+ </add>
+ <add as="VirtualCLK0" extend="CLOCK_f"/>
+ <add as="CLKSPLIT_f" extend="Split"/>
+ <add as="IMPSPLIT_f" extend="Split"/>
+ <add as="CLKSOM_f" extend="CLKSOMV_f"/>
+ <add as="CLKOUT_f" extend="Icon">
+ <add as="image" value="blocks/CLKOUT_f.gif"/>
+ </add>
+ <add as="ABSBLK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="y = |u|"/>
+ </add>
+</mxStylesheet>
diff --git a/styles/new.xml b/styles/new.xml
new file mode 100644
index 0000000..f2b7211
--- /dev/null
+++ b/styles/new.xml
@@ -0,0 +1,974 @@
+<?xml version="1.0"?>
+<mxStylesheet>
+ <!-- *** OVERLOADING DEFINITION *** -->
+ <add as="defaultVertex">
+ <add as="shape" value="label"/>
+ <add as="perimeter" value="rectanglePerimeter"/>
+ <add as="strokeColor" value="black"/>
+ <add as="strokeWidth" value="0.5"/>
+ <add as="fillColor" value="white"/>
+ <add as="fontColor" value="black"/>
+ <add as="noLabel" value="1"/>
+ </add>
+ <!-- *** GENERIC BLOCKS *** -->
+ <add as="block" extend="defaultVertex">
+ <add as="fillColor" value="#cdcdcd"/>
+ <add as="gradientColor" value="white"/>
+ <add as="rounded" value="1"/>
+ </add>
+ <add as="blockWithLabel" extend="defaultVertex">
+ <add as="noLabel" value="0"/>
+ <add as="spacing" value="13"/>
+ <add as="fillColor" value="#cdcdcd"/>
+ <add as="gradientColor" value="white"/>
+ <add as="rounded" value="1"/>
+ </add>
+ <!-- *** BLOCK DEFINITION *** -->
+ <!-- Affiche -->
+ <add as="Affiche" extend="blockWithLabel"/>
+ <add as="Split" extend="block">
+ <add as="shape" value="rectangle"/>
+ <add as="strokeColor" value="black"/>
+ <add as="fillColor" value="orange"/>
+ <remove as="gradientColor"/>
+ <remove as="rounded"/>
+ </add>
+ <!-- Label -->
+ <add as="Label" extend="defaultVertex">
+ <add as="shape" value="rectangle"/>
+ <add as="perimeter" value="rectanglePerimeter"/>
+ <add as="fillColor" value="white"/>
+ <add as="fontColor" value="black"/>
+ <add as="noLabel" value="0"/>
+ <add as="strokeColor" value="white"/>
+ </add>
+ <!-- Print (Label as block) -->
+ <add as="Print" extend="blockWithLabel">
+ <add as="displayedLabel" value="%s"/>
+ </add>
+ <!-- Gain -->
+ <add as="Gain" extend="blockWithLabel">
+ <add as="shape" value="triangle"/>
+ <add as="perimeter" value="trianglePerimeter"/>
+ <add as="direction" value="east"/>
+ <add as="displayedLabel" value="%s"/>
+ </add>
+ <!-- Icon -->
+ <add as="Icon" extend="block">
+ <add as="imageBorder" value="black"/>
+ </add>
+ <!-- *** PORT DEFINITION *** -->
+ <add as="Port" extend="defaultVertex">
+ <add as="shape" value="triangle"/>
+ <add as="perimeter" value="trianglePerimeter"/>
+ <add as="noLabel" value="0"/>
+ </add>
+ <!-- Command Port -->
+ <add as="CommandPort" extend="Port">
+ <add as="strokeColor" value="red"/>
+ <add as="fillColor" value="red"/>
+ <add as="rotation" value="90"/>
+ <add as="type" value="Command"/>
+ </add>
+ <!-- Control Port -->
+ <add as="ControlPort" extend="Port">
+ <add as="strokeColor" value="red"/>
+ <add as="fillColor" value="red"/>
+ <add as="rotation" value="90"/>
+ <add as="type" value="Control"/>
+ </add>
+ <!-- Explicit Input -->
+ <add as="ExplicitInputPort" extend="Port">
+ <add as="strokeColor" value="black"/>
+ <add as="fillColor" value="black"/>
+ <add as="rotation" value="0"/>
+ </add>
+ <!-- Explicit Output -->
+ <add as="ExplicitOutputPort" extend="Port">
+ <add as="strokeColor" value="black"/>
+ <add as="fillColor" value="black"/>
+ <add as="rotation" value="0"/>
+ </add>
+ <!-- Implicit Input -->
+ <add as="ImplicitInputPort" extend="Port">
+ <add as="shape" value="rectangle"/>
+ <add as="strokeColor" value="black"/>
+ <add as="fillColor" value="black"/>
+ </add>
+ <!-- Implicit Output -->
+ <add as="ImplicitOutputPort" extend="Port">
+ <add as="shape" value="rectangle"/>
+ <add as="strokeColor" value="black"/>
+ <add as="fillColor" value="white"/>
+ </add>
+ <!-- *** LINK definition *** -->
+ <add as="defaultEdge">
+ <!--<add as="edgeStyle" value="entityRelationEdgeStyle"/>-->
+ <add as="labelBackgroundColor" value="white"/>
+ <!-- To have rounded links -->
+ <!-- <add as="rounded" value="1"/> -->
+ <!-- <add as="edgeStyle" value="elbowEdgeStyle"/> -->
+ <add as="elbow" value="horizontal"/>
+ <add as="shape" value="connector"/>
+ <add as="labelBackgroundColor" value="white"/>
+ <add as="endArrow" value="classicnone"/>
+ <add as="fontSize" value="10"/>
+ <add as="align" value="center"/>
+ <add as="verticalAlign" value="middle"/>
+ <add as="strokeColor" value="black"/>
+ <add as="perimeter" value="null"/>
+ </add>
+ <add as="CommandControlLink" extend="defaultEdge">
+ <add as="strokeColor" value="red"/>
+ <add as="elbow" value="vertical"/>
+ </add>
+ <add as="ExplicitLink" extend="defaultEdge">
+ <add as="strokeColor" value="blue"/>
+ </add>
+ <add as="ImplicitLink" extend="defaultEdge">
+ <add as="strokeColor" value="blue"/>
+ </add>
+ <!-- SOURCES -->
+ <add as="CONST_m" extend="Print"/>
+ <add as="CONST_f" extend="Print"/>
+ <add as="CONST" extend="Print"/>
+ <add as="GENSQR_f" extend="Icon">
+ <add as="image" value="blocks/SQUARE_WAVE_f.svg"/>
+ </add>
+ <add as="RAMP" extend="Icon">
+ <add as="image" value="blocks/RAMP.svg"/>
+ </add>
+ <add as="RAND_m" extend="blockWithLabel">
+ <add as="displayedLabel" value="Random&lt;BR&gt; generator"/>
+ </add>
+ <add as="RFILE_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Read from&lt;BR&gt; input file"/>
+ </add>
+ <add as="CLKINV_f" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="rounded" value="1"/>
+ <add as="fillColor" value="white"/>
+ <add as="strokeColor" value="red"/>
+ </add>
+ <add as="CURV_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Curve"/>
+ </add>
+ <add as="INIMPL_f" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="rounded" value="1"/>
+ <add as="fillColor" value="white"/>
+ </add>
+ <add as="READAU_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Read from .au&lt;BR&gt; sound file"/>
+ </add>
+ <add as="SAWTOOTH_f" extend="Icon">
+ <add as="image" value="blocks/sawtooth.svg"/>
+ </add>
+ <add as="STEP_FUNCTION" extend="Icon">
+ <add as="image" value="blocks/STEP_FUNCTION.svg"/>
+ </add>
+ <add as="STEP" extend="Icon">
+ <add as="image" value="blocks/STEP_FUNCTION.svg"/>
+ </add>
+ <add as="PULSE_SC" extend="Icon">
+ <add as="image" value="blocks/PULSE_SC.svg"/>
+ </add>
+ <add as="CLOCK_c" extend="Icon">
+ <add as="image" value="blocks/CLOCK_c.svg"/>
+ </add>
+ <add as="GENSIN_f" extend="Icon">
+ <add as="image" value="blocks/SINUS_f.svg"/>
+ </add>
+ <add as="IN_f" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="rounded" value="1"/>
+ <add as="fillColor" value="white"/>
+ </add>
+ <add as="READC_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Read from&lt;BR&gt; C binary file"/>
+ </add>
+ <add as="TIME_f" extend="Icon">
+ <add as="image" value="blocks/CLOCK_f.svg"/>
+ </add>
+ <add as="Modulo_Count" extend="blockWithLabel">
+ <add as="displayedLabel" value="Counter&lt;BR&gt;Modulo %2$s"/>
+ </add>
+ <add as="Sigbuilder" extend="blockWithLabel">
+ <add as="displayedLabel" value="Signal&lt;BR&gt;Builder"/>
+ </add>
+ <add as="SampleCLK" extend="Icon">
+ <add as="image" value="blocks/SampleCLK.svg"/>
+ </add>
+ <add as="TKSCALE" extend="blockWithLabel">
+ <add as="displayedLabel" value="TK Scale"/>
+ </add>
+ <add as="FROMWSB" extend="blockWithLabel">
+ <!-- FIXME: update label from parameters (not as exprs) -->
+ <add as="displayedLabel" value="From workspace"/>
+ </add>
+ <!-- CONTINUOUS -->
+ <add as="DERIV" extend="blockWithLabel">
+ <add as="displayedLabel" value="du / dt"/>
+ </add>
+ <add as="INTEGRAL_m" extend="Icon">
+ <add as="image" value="blocks/INTEGRAL.svg"/>
+ <!--
+ <add as="displayedLabel" value="&lt;FONT SIZE=&quot;6&quot;&gt;
+ &#8747; &lt;/FONT&gt;"/>
+ -->
+ </add>
+ <add as="INTEGRAL" extend="INTEGRAL_m"/>
+ <add as="CLSS" extend="blockWithLabel">
+ <add as="displayedLabel" value="&lt;TABLE&gt; &lt;TR&gt; &lt;TD ALIGN=&quot;RIGHT&quot;&gt;xd&lt;/TD&gt; &lt;TD&gt;=&lt;/TD&gt; &lt;TD&gt;Ax+Bu&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD ALIGN=&quot;RIGHT&quot;&gt;y&lt;/TD&gt; &lt;TD&gt;=&lt;/TD&gt; &lt;TD&gt;Cx+Du&lt;/TD&gt; &lt;/TR&gt; &lt;/TABLE&gt;"/>
+ </add>
+ <add as="CLSS_f" extend="CLSS"/>
+ <add as="CLR" extend="blockWithLabel">
+ <add as="displayedLabel" value="$\frac{%s}{%s}$"/>
+ </add>
+ <add as="CLR_f" extend="CLR"/>
+ <add as="TIME_DELAY" extend="blockWithLabel">
+ <add as="displayedLabel" value="Continuous&lt;BR&gt; fix delay"/>
+ </add>
+ <add as="TCLSS" extend="blockWithLabel">
+ <add as="displayedLabel" value="Jump&lt;BR&gt; (A,B,C,D)"/>
+ </add>
+ <add as="TCLSS_f" extend="TCLSS"/>
+ <add as="VARIABLE_DELAY" extend="blockWithLabel">
+ <add as="displayedLabel" value="Variable&lt;BR&gt; delay"/>
+ </add>
+ <add as="PID" extend="blockWithLabel">
+ <add as="displayedLabel" value="PID"/>
+ </add>
+ <add as="INTEGRAL_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="1/s"/>
+ </add>
+ <!-- DISCONTINUOUS -->
+ <add as="SATURATION" extend="Icon">
+ <add as="image" value="blocks/SATURATION.svg"/>
+ </add>
+ <add as="DEADBAND" extend="Icon">
+ <add as="image" value="blocks/DEADBAND.svg"/>
+ </add>
+ <add as="HYSTHERESIS" extend="Icon">
+ <add as="image" value="blocks/HYSTHERESIS.svg"/>
+ </add>
+ <add as="BACKLASH" extend="blockWithLabel">
+ <add as="displayedLabel" value="Backlash"/>
+ </add>
+ <add as="RATELIMITER" extend="blockWithLabel">
+ <add as="displayedLabel" value="Rate limiter"/>
+ </add>
+ <add as="REGISTER" extend="blockWithLabel">
+ <add as="displayedLabel" value="Shift&lt;BR&gt; register"/>
+ </add>
+ <add as="DELAYV_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Variable&lt;BR&gt; delay"/>
+ </add>
+ <!-- LOOKUP TABLES -->
+ <add as="LOOKUP_f" extend="Icon">
+ <add as="image" value="blocks/DSCOPE.svg"/>
+ </add>
+ <add as="INTRP2BLK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Interp 2"/>
+ </add>
+ <add as="INTRPLBLK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Interp"/>
+ </add>
+ <!-- SIGNAL PROCESSING -->
+ <add as="MCLOCK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="2freq clock&lt;BR&gt; f/n f"/>
+ </add>
+ <add as="QUANT_f" extend="Icon">
+ <add as="image" value="blocks/QUANT_f.svg"/>
+ </add>
+ <add as="MFCLCK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="M. freq&lt;BR&gt; clock"/>
+ </add>
+ <add as="SAMPHOLD_m" extend="blockWithLabel">
+ <add as="displayedLabel" value="S / H"/>
+ </add>
+ <add as="SAMPLEHOLD_f" extend="SAMPHOLD_m"/>
+ <!-- THRESHOLD -->
+ <add as="NEGTOPOS_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="- to +"/>
+ </add>
+ <add as="POSTONEG_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="+ to -"/>
+ </add>
+ <add as="ZCROSS_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Zcross"/>
+ </add>
+ <add as="GENERAL_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="GENERAL"/>
+ </add>
+ <add as="CLINDUMMY_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="DUMMY&lt;BR&gt; CLSS"/>
+ </add>
+ <!-- MATH OPERATIONS -->
+ <add as="MAX_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="MAX"/>
+ </add>
+ <add as="MIN_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="MIN"/>
+ </add>
+ <add as="BIGSOM_f" extend="Icon">
+ <add as="image" value="blocks/SUM.svg"/>
+ <add as="noLabel" value="1"/>
+ <!--
+ <add as="displayedLabel" value="&lt;TABLE&gt;&lt;TR&gt;
+ &lt;TD&gt;+&lt;/TD&gt; &lt;TD ROWSPAN=&quot;2&quot;&gt; &lt;FONT
+ SIZE=&quot;6&quot;&gt;&#8721;&lt;/FONT&gt; &lt;/TD&gt; &lt;/TR&gt;
+ &lt;TR&gt; &lt;TD&gt;+&lt;/TD&gt; &lt;TD/&gt; &lt;/TR&gt;
+ &lt;/TABLE&gt;"/>
+ -->
+ </add>
+ <add as="POWBLK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="u^a"/>
+ </add>
+ <add as="INVBLK" extend="blockWithLabel">
+ <add as="displayedLabel" value="1/u"/>
+ </add>
+ <add as="INVBLK_f" extend="INVBLK"/>
+ <add as="SINBLK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="SIN"/>
+ </add>
+ <add as="COSBLK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="COS"/>
+ </add>
+ <add as="TANBLK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="TAN"/>
+ </add>
+ <add as="MATDIV" extend="blockWithLabel">
+ <add as="displayedLabel" value="A / B"/>
+ </add>
+ <add as="EXPBLK_m" extend="blockWithLabel">
+ <add as="displayedLabel" value="a^u"/>
+ </add>
+ <add as="PROD_f" extend="blockWithLabel">
+ <add as="shape" value="ellipse"/>
+ <remove as="image"/>
+ <add as="fontSize" value="20"/>
+ </add>
+ <add as="MATZREIM" extend="blockWithLabel">
+ <add as="displayedLabel" value="Re &amp; Im"/>
+ </add>
+ <add as="MATMAGPHI" extend="blockWithLabel">
+ <add as="displayedLabel" value="Mag &amp; Phi"/>
+ </add>
+ <add as="SQRT" extend="blockWithLabel"/>
+ <add as="GAINBLK_f" extend="Gain"/>
+ <add as="LOGBLK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="LOG"/>
+ </add>
+ <add as="SUMMATION" extend="Icon">
+ <add as="image" value="blocks/SUM.svg"/>
+ <add as="noLabel" value="1"/>
+ <!--
+ <add as="displayedLabel" value="&lt;TABLE&gt; &lt;TR&gt;
+ &lt;TD&gt;+&lt;/TD&gt; &lt;TD ROWSPAN=&quot;2&quot;&gt; &lt;FONT
+ SIZE=&quot;6&quot;&gt;&#8721;&lt;/FONT&gt; &lt;TD&gt; &lt;/TR&gt;
+ &lt;TR&gt; &lt;TD&gt;-&lt;/TD&gt; &lt;TD/&gt; &lt;/TR&gt;
+ &lt;/TABLE&gt;"/>
+ -->
+ </add>
+ <add as="TrigFun" extend="blockWithLabel">
+ <add as="displayedLabel" value="Trig function"/>
+ </add>
+ <add as="PRODUCT" extend="Icon">
+ <add as="image" value="blocks/PRODUCT.svg"/>
+ <add as="noLabel" value="1"/>
+ <!--
+ <add as="displayedLabel" value="&lt;TABLE&gt; &lt;TR&gt;
+ &lt;TD&gt;*&lt;/TD&gt; &lt;TD ROWSPAN=&quot;2&quot;&gt; &lt;FONT
+ SIZE=&quot;6&quot;&gt;&#8719;&lt;/FONT&gt; &lt;TD&gt; &lt;/TR&gt;
+ &lt;TR&gt; &lt;TD&gt;/&lt;/TD&gt; &lt;TD/&gt; &lt;/TR&gt;
+ &lt;/TABLE&gt;"/>
+ -->
+ </add>
+ <add as="MAXMIN" extend="blockWithLabel">
+ <add as="displayedLabel" value="MIN / MAX"/>
+ </add>
+ <add as="ABS_VALUE" extend="blockWithLabel">
+ <add as="displayedLabel" value="ABS"/>
+ </add>
+ <add as="SIGNUM" extend="blockWithLabel">
+ <add as="displayedLabel" value="SIGN"/>
+ </add>
+ <add as="SUM_f" extend="blockWithLabel">
+ <add as="shape" value="ellipse"/>
+ <remove as="image"/>
+ <add as="fontSize" value="20"/>
+ </add>
+ <add as="SOM_f" extend="blockWithLabel">
+ <add as="shape" value="ellipse"/>
+ <remove as="image"/>
+ <add as="fontSize" value="20"/>
+ <add as="displayedLabel" value="+"/>
+ </add>
+ <add as="CONSTRAINT_c" extend="blockWithLabel">
+ <add as="displayedLabel" value="x == %s"/>
+ </add>
+ <add as="CONSTRAINT2_c" extend="blockWithLabel">
+ <add as="displayedLabel" value="x == %s&lt;br/&gt;x' == %s"/>
+ </add>
+ <!-- MODELICA -->
+ <add as="MBLOCK" extend="blockWithLabel">
+ <add as="displayedLabel" value="Modelica&lt;BR&gt; generic"/>
+ </add>
+ <add as="FROMMO" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="fillColor" value="white"/>
+ <add as="displayedLabel" value="%s"/>
+ </add>
+ <add as="GOTOMO" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="fillColor" value="white"/>
+ <add as="displayedLabel" value="%s"/>
+ </add>
+ <add as="GotoTagVisibilityMO" extend="blockWithLabel">
+ <add as="displayedLabel" value="&lt;FONT SIZE=&quot;6&quot;&gt;{%s}&lt;/FONT&gt;"/>
+ <add as="shape" value="ellipse"/>
+ <add as="strokeColor" value="green"/>
+ </add>
+ <add as="OUTIMPL_f" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="fillColor" value="white"/>
+ </add>
+ <!-- INTEGER -->
+ <add as="BITCLEAR" extend="blockWithLabel">
+ <add as="displayedLabel" value="Clear bit %2$s"/>
+ </add>
+ <add as="BITSET" extend="blockWithLabel">
+ <add as="displayedLabel" value="Set bit %2$s"/>
+ </add>
+ <add as="CONVERT" extend="blockWithLabel">
+ <!-- FIXME: update label from parameters -->
+ <add as="displayedLabel" value="Convert to"/>
+ </add>
+ <add as="EXTRACTBITS" extend="blockWithLabel">
+ <add as="displayedLabel" value="Extract Bits %2$s"/>
+ </add>
+ <add as="INTMUL" extend="blockWithLabel">
+ <add as="displayedLabel" value="INTMUL"/>
+ </add>
+ <add as="SHIFT" extend="blockWithLabel">
+ <add as="displayedLabel" value="Arithmetic&lt;BR&gt; shift %2$s"/>
+ </add>
+ <add as="LOGIC" extend="blockWithLabel">
+ <add as="displayedLabel" value="LOGIC"/>
+ </add>
+ <add as="DLATCH" extend="blockWithLabel">
+ <add as="displayedLabel" value="&lt;TABLE&gt; &lt;TR&gt; &lt;TD&gt;D&lt;/TD&gt; &lt;TD&gt;Q&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD&gt;C&lt;/TD&gt; &lt;TD&gt;!Q&lt;/TD&gt; &lt;/TR&gt; &lt;/TABLE&gt;"/>
+ </add>
+ <add as="DFLIPFLOP" extend="blockWithLabel">
+ <add as="displayedLabel" value="&lt;TABLE&gt; &lt;TR&gt; &lt;TD&gt;D&lt;/TD&gt; &lt;TD/&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD/&gt; &lt;TD&gt;Q&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD&gt;clk&lt;/TD&gt; &lt;TD/&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD/&gt; &lt;TD&gt;!Q&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD&gt;en&lt;/TD&gt; &lt;TD/&gt; &lt;/TR&gt; &lt;/TABLE&gt;"/>
+ </add>
+ <add as="JKFLIPFLOP" extend="blockWithLabel">
+ <add as="displayedLabel" value="&lt;TABLE&gt; &lt;TR&gt; &lt;TD&gt;J&lt;/TD&gt; &lt;TD/&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD/&gt; &lt;TD&gt;Q&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD&gt;clk&lt;/TD&gt; &lt;TD/&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD/&gt; &lt;TD&gt;!Q&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD&gt;K&lt;/TD&gt; &lt;TD/&gt; &lt;/TR&gt; &lt;/TABLE&gt;"/>
+ </add>
+ <add as="SRFLIPFLOP" extend="blockWithLabel">
+ <add as="displayedLabel" value="&lt;TABLE&gt; &lt;TR&gt; &lt;TD&gt;S&lt;/TD&gt; &lt;TD&gt;Q&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD&gt;R&lt;/TD&gt; &lt;TD&gt;!Q&lt;/TD&gt; &lt;/TR&gt; &lt;/TABLE&gt;"/>
+ </add>
+ <!-- MATRIX -->
+ <add as="CUMSUM" extend="blockWithLabel">
+ <add as="displayedLabel" value="CUMSUM"/>
+ </add>
+ <add as="SUBMAT" extend="blockWithLabel"/>
+ <add as="MATBKSL" extend="blockWithLabel">
+ <add as="displayedLabel" value="A \ B"/>
+ </add>
+ <add as="MATINV" extend="blockWithLabel">
+ <add as="displayedLabel" value="INV"/>
+ </add>
+ <add as="MATCATV" extend="blockWithLabel">
+ <add as="displayedLabel" value="Vert&lt;BR&gt; Cat"/>
+ </add>
+ <add as="MATSUM" extend="blockWithLabel"/>
+ <add as="RICC" extend="blockWithLabel"/>
+ <add as="ROOTCOEF" extend="blockWithLabel">
+ <add as="displayedLabel" value="ROOT&lt;BR&gt;COEF"/>
+ </add>
+ <add as="MATCATH" extend="blockWithLabel">
+ <add as="displayedLabel" value="Horiz&lt;BR&gt; Cat"/>
+ </add>
+ <add as="MATLU" extend="blockWithLabel">
+ <add as="displayedLabel" value="LU"/>
+ </add>
+ <add as="MATZCONJ" extend="blockWithLabel">
+ <add as="displayedLabel" value="CONJ"/>
+ </add>
+ <add as="EXTRACT" extend="blockWithLabel"/>
+ <add as="MATEXPM" extend="blockWithLabel">
+ <add as="displayedLabel" value="EXPM"/>
+ </add>
+ <add as="MATDET" extend="blockWithLabel">
+ <add as="displayedLabel" value="DET"/>
+ </add>
+ <add as="MATPINV" extend="blockWithLabel">
+ <add as="displayedLabel" value="PINV"/>
+ </add>
+ <add as="EXTTRI" extend="blockWithLabel">
+ <add as="displayedLabel" value="Tri/Diag&lt;BR&gt; Extraction"/>
+ </add>
+ <add as="MATMUL" extend="blockWithLabel"/>
+ <add as="MATTRAN" extend="blockWithLabel"/>
+ <add as="MATSING" extend="blockWithLabel">
+ <add as="displayedLabel" value="SVD"/>
+ </add>
+ <add as="MATRESH" extend="blockWithLabel">
+ <add as="displayedLabel" value="RESHAPE"/>
+ </add>
+ <add as="MATDIAG" extend="blockWithLabel">
+ <add as="displayedLabel" value="DIAG"/>
+ </add>
+ <add as="MATEIG" extend="blockWithLabel">
+ <add as="displayedLabel" value="EIG"/>
+ </add>
+ <!-- SINKS -->
+ <add as="CFSCOPE" extend="Icon">
+ <add as="image" value="blocks/ASCOPE.svg"/>
+ </add>
+ <add as="BARXY" extend="Icon">
+ <add as="image" value="blocks/BARXY.svg"/>
+ </add>
+ <add as="CANIMXY" extend="Icon">
+ <add as="image" value="blocks/3DSCOPE.svg"/>
+ </add>
+ <add as="CSCOPE" extend="Icon">
+ <add as="image" value="blocks/ASCOPE.svg"/>
+ </add>
+ <add as="CSCOPXY" extend="Icon">
+ <add as="image" value="blocks/CSCOPXY.svg"/>
+ </add>
+ <add as="TOWS_c" extend="blockWithLabel">
+ <add as="displayedLabel" value="To workspace&lt;BR&gt;%2$s [%1$s]"/>
+ </add>
+ <add as="CMAT3D" extend="blockWithLabel">
+ <add as="image" value="blocks/ASCOPE.svg"/>
+ <add as="verticalLabelPosition" value="bottom"/>
+ <add as="verticalAlign" value="top"/>
+ <add as="spacing" value="2"/>
+ <add as="displayedLabel" value="Mat. 3D"/>
+ </add>
+ <add as="CSCOPXY3D" extend="Icon">
+ <add as="image" value="blocks/CSCOPXY3D.svg"/>
+ </add>
+ <add as="CANIMXY3D" extend="Icon">
+ <add as="image" value="blocks/3DSCOPE.svg"/>
+ </add>
+ <add as="CMATVIEW" extend="blockWithLabel">
+ <add as="image" value="blocks/ASCOPE.svg"/>
+ <add as="verticalLabelPosition" value="bottom"/>
+ <add as="verticalAlign" value="top"/>
+ <add as="spacing" value="2"/>
+ <add as="displayedLabel" value="Mat. View"/>
+ </add>
+ <add as="CMSCOPE" extend="Icon">
+ <add as="image" value="blocks/ASCOPE.svg"/>
+ </add>
+ <add as="AFFICH_m" extend="Affiche"/>
+ <add as="AFFICH_f" extend="Affiche"/>
+ <add as="TRASH_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Trash"/>
+ </add>
+ <!-- PORT ACTION -->
+ <add as="Extract_Activation" extend="blockWithLabel">
+ <add as="displayedLabel" value="Extract&lt;BR&gt; activation"/>
+ </add>
+ <add as="IFTHEL_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="if in&gt;0&lt;BR&gt; then else"/>
+ </add>
+ <add as="ESELECT_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Event select"/>
+ </add>
+ <add as="EDGE_TRIGGER" extend="blockWithLabel">
+ <add as="displayedLabel" value="Edge&lt;BR&gt; trigger"/>
+ </add>
+ <!-- DISCRETE -->
+ <add as="DLRADAPT_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="N(z,p)&lt;BR&gt;&lt;HR&gt;D(z,p)"/>
+ </add>
+ <add as="DLR" extend="blockWithLabel">
+ <add as="displayedLabel" value="$\frac{%s}{%s}$"/>
+ </add>
+ <add as="DLR_f" extend="DLR"/>
+ <add as="DLSS" extend="blockWithLabel">
+ <add as="displayedLabel" value="&lt;TABLE&gt; &lt;TR&gt; &lt;TD ALIGN=&quot;RIGHT&quot;&gt;x&lt;/TD&gt; &lt;TD ALIGN=&quot;CENTER&quot;&gt;+=&lt;/TD&gt; &lt;TD&gt;Ax+Bu&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD ALIGN=&quot;RIGHT&quot;&gt;y&lt;/TD&gt; &lt;TD ALIGN=&quot;CENTER&quot;&gt;=&lt;/TD&gt; &lt;TD&gt;Cx+Du&lt;/TD&gt; &lt;/TR&gt; &lt;/TABLE&gt;"/>
+ </add>
+ <add as="DELAY_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Delay"/>
+ </add>
+ <add as="DOLLAR_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="1/z"/>
+ </add>
+ <add as="DOLLAR" extend="DOLLAR_f"/>
+ <!-- EVENTS -->
+ <add as="CLKFROM" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="fillColor" value="white"/>
+ <add as="strokeColor" value="red"/>
+ <add as="displayedLabel" value="%s"/>
+ </add>
+ <add as="CLKGOTO" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="fillColor" value="white"/>
+ <add as="strokeColor" value="red"/>
+ <add as="displayedLabel" value="%s"/>
+ </add>
+ <add as="CLKGotoTagVisibility" extend="blockWithLabel">
+ <add as="fontSize" value="20"/>
+ <add as="displayedLabel" value="{%s}"/>
+ <add as="shape" value="ellipse"/>
+ <add as="strokeColor" value="red"/>
+ </add>
+ <add as="CLKOUTV_f" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="fillColor" value="white"/>
+ <add as="strokeColor" value="red"/>
+ </add>
+ <add as="CLKOUT_f" extend="CLKOUTV_f"/>
+ <add as="CLKSOMV_f" extend="blockWithLabel">
+ <add as="shape" value="ellipse"/>
+ <add as="displayedLabel" value="+"/>
+ <add as="fontColor" value="red"/>
+ <add as="strokeColor" value="red"/>
+ <add as="fontSize" value="20"/>
+ <add as="spacing" value="5"/>
+ <add as="spacingLeft" value="6"/>
+ <add as="spacingRight" value="6"/>
+ </add>
+ <add as="EVTGEN_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Event at&lt;BR&gt; time %s"/>
+ </add>
+ <add as="EVTVARDLY" extend="blockWithLabel">
+ <add as="displayedLabel" value="Event&lt;BR&gt; delay"/>
+ </add>
+ <add as="M_freq" extend="blockWithLabel">
+ <add as="displayedLabel" value="Multiple&lt;BR&gt; frequency"/>
+
+ </add>
+ <add as="ANDBLK" extend="Icon">
+ <add as="image" value="blocks/ANDBLK.svg"/>
+ </add>
+ <add as="HALT_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="HALT"/>
+ </add>
+ <add as="freq_div" extend="blockWithLabel">
+ <add as="displayedLabel" value="Frequency&lt;BR&gt; division"/>
+ </add>
+ <add as="ANDLOG_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="LOGICAL&lt;BR&gt; AND"/>
+ </add>
+ <add as="EVTDLY_c" extend="blockWithLabel">
+ <add as="displayedLabel" value="Delay: %s"/>
+ </add>
+ <add as="CEVENTSCOPE" extend="Icon">
+ <add as="image" value="blocks/DSCOPE.svg"/>
+ </add>
+ <!-- SIGNAL ROUTING -->
+ <add as="SELF_SWITCH_ON" extend="Icon">
+ <add as="image" value="blocks/Self_Switch_on.svg"/>
+ </add>
+ <add as="SELF_SWITCH_OFF" extend="Icon">
+ <add as="image" value="blocks/Self_Switch_off.svg"/>
+ </add>
+ <add as="ISELECT_m" extend="blockWithLabel">
+ <add as="displayedLabel" value="Selector"/>
+ </add>
+ <add as="RELAY_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Relay"/>
+ </add>
+ <add as="WRITEAU_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Write AU to&lt;BR&gt; /dev/audio"/>
+ </add>
+ <add as="SELECT_m" extend="blockWithLabel">
+ <add as="displayedLabel" value="Selector"/>
+ </add>
+ <add as="SELECT_f" extend="SELECT_m"/>
+ <add as="EXTRACTOR" extend="blockWithLabel">
+ <add as="displayedLabel" value="Extractor"/>
+ </add>
+ <add as="M_SWITCH" extend="Icon">
+ <add as="verticalLabelPosition" value="bottom"/>
+ <add as="verticalAlign" value="top"/>
+ <add as="spacing" value="2"/>
+ <add as="noLabel" value="0"/>
+ <add as="displayedLabel" value="Dynamic index"/>
+ <add as="image" value="blocks/SWITCH.svg"/>
+ </add>
+ <add as="SWITCH_f" extend="Icon">
+ <add as="verticalLabelPosition" value="bottom"/>
+ <add as="verticalAlign" value="top"/>
+ <add as="spacing" value="2"/>
+ <add as="noLabel" value="0"/>
+ <add as="displayedLabel" value="Static: %2$s"/>
+ <add as="image" value="blocks/SWITCH.svg"/>
+ </add>
+ <add as="SWITCH2_m" extend="Icon">
+ <add as="verticalLabelPosition" value="bottom"/>
+ <add as="verticalAlign" value="top"/>
+ <add as="spacing" value="2"/>
+ <add as="noLabel" value="0"/>
+ <add as="displayedLabel" value="Dynamic"/>
+ <add as="image" value="blocks/SWITCH.svg"/>
+ </add>
+ <add as="NRMSOM_f" extend="blockWithLabel">
+ <add as="verticalLabelPosition" value="bottom"/>
+ <add as="verticalAlign" value="top"/>
+ <add as="spacing" value="2"/>
+ <add as="displayedLabel" value="Bus creator"/>
+ </add>
+ <add as="WRITEC_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Write to&lt;BR&gt;C binary file"/>
+ </add>
+ <add as="GOTO" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="fillColor" value="white"/>
+ <add as="displayedLabel" value="%s"/>
+ </add>
+ <add as="GotoTagVisibility" extend="blockWithLabel">
+ <add as="displayedLabel" value="&lt;FONT SIZE=&quot;6&quot;&gt;{%s}&lt;/FONT&gt;"/>
+ </add>
+ <add as="FROM" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="fillColor" value="white"/>
+ <add as="displayedLabel" value="%s"/>
+ </add>
+ <add as="WFILE_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Write to&lt;BR&gt; output file"/>
+ </add>
+ <add as="MUX" extend="blockWithLabel">
+ <add as="verticalLabelPosition" value="bottom"/>
+ <add as="verticalAlign" value="top"/>
+ <add as="displayedLabel" value="MUX"/>
+ <add as="spacing" value="2"/>
+ </add>
+ <add as="DEMUX" extend="blockWithLabel">
+ <add as="verticalLabelPosition" value="bottom"/>
+ <add as="verticalAlign" value="top"/>
+ <add as="displayedLabel" value="DEMUX"/>
+ <add as="spacing" value="2"/>
+ </add>
+ <add as="SCALAR2VECTOR" extend="blockWithLabel">
+ <add as="displayedLabel" value="SCALAR&lt;BR&gt;to VECTOR"/>
+ </add>
+ <!-- COMMONLY USED BLOCKS -->
+ <add as="OUT_f" extend="blockWithLabel">
+ <add as="shape" value="hexagon"/>
+ <add as="rounded" value="1"/>
+ <add as="fillColor" value="white"/>
+ </add>
+ <add as="RELATIONALOP" extend="blockWithLabel">
+ <add as="displayedLabel" value="Relational&lt;BR&gt; op : &amp;&lt;"/>
+ <!-- new value for the label defined in the interface function of block -->
+ </add>
+ <add as="TEXT_f" extend="Label">
+ <!-- <add as="displayedLabel" value="Text"/> -->
+ <add as="strokeColor" value="none"/>
+ <add as="fillColor" value="none"/>
+ </add>
+ <!-- USER-DEFINED FUNCTIONS -->
+ <add as="PDE" extend="blockWithLabel"/>
+ <add as="fortran_block" extend="blockWithLabel">
+ <add as="displayedLabel" value="Fortran block:&lt;BR&gt;%4$s"/>
+ </add>
+ <add as="DEBUG" extend="blockWithLabel">
+ <add as="displayedLabel" value="Debug:&lt;BR&gt;%2$s"/>
+ </add>
+ <add as="EXPRESSION" extend="blockWithLabel">
+ <add as="displayedLabel" value="Expression:&lt;BR&gt;%2$s"/>
+ </add>
+ <add as="scifunc_block_m" extend="blockWithLabel">
+ <add as="displayedLabel" value="Function:&lt;BR&gt;%10$s"/>
+ </add>
+ <add as="scifunc_block" extend="scifunc_block_m"/>
+ <add as="CBLOCK" extend="blockWithLabel">
+ <add as="displayedLabel" value="C block 2:&lt;BR&gt;%1$s"/>
+ </add>
+ <add as="CBLOCK4" extend="blockWithLabel">
+ <add as="displayedLabel" value="C block 4:&lt;BR&gt;%1$s"/>
+ </add>
+ <add as="generic_block3" extend="blockWithLabel">
+ <add as="displayedLabel" value="native block:&lt;BR&gt;%1$s"/>
+ </add>
+ <add as="c_block" extend="blockWithLabel">
+ <add as="displayedLabel" value="C block:&lt;BR&gt;%4$s"/>
+ </add>
+ <add as="SUPER_f" extend="Icon">
+ <add as="image" value="blocks/SUPER.svg"/>
+ </add>
+ <add as="DSUPER" extend="SUPER_f"/>
+ <!-- ELECTRICAL -->
+ <add as="Capacitor" extend="Icon">
+ <add as="image" value="blocks/Capacitor.svg"/>
+ </add>
+ <add as="Ground" extend="Icon">
+ <add as="image" value="blocks/Ground.svg"/>
+ </add>
+ <add as="VVsourceAC" extend="blockWithLabel">
+ <add as="shape" value="ellipse"/>
+ <add as="perimeter" value="ellipsePerimeter"/>
+ <add as="displayedLabel" value="1 V&lt;br/&gt;~ %s"/>
+ </add>
+ <add as="ConstantVoltage" extend="Icon">
+ <add as="image" value="blocks/ConstantVoltage.svg"/>
+ </add>
+ <add as="Inductor" extend="Icon">
+ <add as="image" value="blocks/Inductor.svg"/>
+ </add>
+ <add as="PotentialSensor" extend="Icon">
+ <add as="image" value="blocks/PotentialSensor.svg"/>
+ </add>
+ <add as="VariableResistor" extend="Icon">
+ <add as="image" value="blocks/VariableResistor.svg"/>
+ </add>
+ <add as="CurrentSensor" extend="Icon">
+ <add as="image" value="blocks/CurrentSensor.svg"/>
+ </add>
+ <add as="Resistor" extend="Icon">
+ <add as="image" value="blocks/Resistor.svg"/>
+ </add>
+ <add as="VoltageSensor" extend="Icon">
+ <add as="image" value="blocks/VoltageSensor.svg"/>
+ </add>
+ <add as="Diode" extend="Icon">
+ <add as="image" value="blocks/Diode.svg"/>
+ </add>
+ <add as="VsourceAC" extend="blockWithLabel">
+ <add as="shape" value="ellipse"/>
+ <add as="perimeter" value="ellipsePerimeter"/>
+ <add as="textAlign" value="center"/>
+ <add as="displayedLabel" value="%s V&lt;br/&gt;~ %s"/>
+ </add>
+ <add as="NPN" extend="Icon">
+ <add as="image" value="blocks/NPN.svg"/>
+ </add>
+ <add as="PNP" extend="Icon">
+ <add as="image" value="blocks/PNP.svg"/>
+ </add>
+ <add as="SineVoltage" extend="blockWithLabel">
+ <add as="shape" value="ellipse"/>
+ <add as="perimeter" value="ellipsePerimeter"/>
+ <add as="displayedLabel" value="%s V&lt;br/&gt;~"/>
+ </add>
+ <add as="Switch" extend="Icon">
+ <add as="image" value="blocks/SWITCH.svg"/>
+ </add>
+ <add as="OpAmp" extend="blockWithLabel">
+ <add as="shape" value="triangle"/>
+ <add as="perimeter" value="trianglePerimeter"/>
+ <add as="direction" value="east"/>
+ <add as="displayedLabel" value="&lt;TABLE&gt; &lt;TR&gt; &lt;TD&gt;+&lt;/TD&gt; &lt;TD&gt;&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD&gt;&lt;/TD&gt; &lt;TD&gt;OP&lt;/TD&gt; &lt;/TR&gt; &lt;TR&gt; &lt;TD&gt;-&lt;/TD&gt; &lt;TD&gt;&lt;/TD&gt; &lt;/TR&gt; &lt;/TABLE&gt;"/>
+ <add as="spacing" value="0"/>
+ <add as="spacingRight" value="5"/>
+ <add as="spacingTop" value="7"/>
+ <add as="spacingBottom" value="7"/>
+ </add>
+ <add as="PMOS" extend="Icon">
+ <add as="image" value="blocks/PMOS.svg"/>
+ </add>
+ <add as="NMOS" extend="Icon">
+ <add as="image" value="blocks/NMOS.svg"/>
+ </add>
+ <add as="CCS" extend="Icon">
+ <add as="image" value="blocks/CCS.svg"/>
+ </add>
+ <add as="CVS" extend="Icon">
+ <add as="image" value="blocks/CVS.svg"/>
+ </add>
+ <add as="IdealTransformer" extend="Icon">
+ <add as="image" value="blocks/IdealTransformer.svg"/>
+ </add>
+ <add as="Gyrator" extend="Icon">
+ <add as="image" value="blocks/Gyrator.svg"/>
+ </add>
+ <!-- THERMO-HYDRAULICS -->
+ <add as="Bache" extend="Icon">
+ <add as="image" value="blocks/BACHE.svg"/>
+ </add>
+ <add as="VanneReglante" extend="Icon">
+ <add as="image" value="blocks/VanneReglante.svg"/>
+ </add>
+ <add as="PerteDP" extend="Icon">
+ <add as="image" value="blocks/PerteDP.svg"/>
+ </add>
+ <add as="PuitsP" extend="Icon">
+ <add as="image" value="blocks/PuitP.svg"/>
+ </add>
+ <add as="SourceP" extend="Icon">
+ <add as="image" value="blocks/SourceP.svg"/>
+ </add>
+ <add as="Flowmeter" extend="Icon">
+ <add as="image" value="blocks/Flowmeter.svg"/>
+ </add>
+ <!-- DEMONSTRATION BLOCKS -->
+ <add as="BOUNCE" extend="blockWithLabel">
+ <add as="displayedLabel" value="Bouncing&lt;BR&gt; balls"/>
+ </add>
+ <add as="BOUNCEXY" extend="Icon">
+ <add as="image" value="blocks/3DSCOPE.svg"/>
+ </add>
+ <add as="BPLATFORM" extend="Icon">
+ <add as="image" value="blocks/BPLATFORM.svg"/>
+ </add>
+ <add as="AUTOMAT" extend="blockWithLabel">
+ <!-- FIXME : Show parameters over block -->
+ <add as="displayedLabel" value="Automaton&lt;BR&gt; nM=2, nX=1"/>
+ </add>
+ <!-- GENERATED BLOCKS -->
+ <add as="SPLIT_f" extend="Split"/>
+ <!--
+ <add as="SCALAR2VECTOR" extend="Icon">
+ <add as="image" value="blocks/SCALAR2VECTOR.gif" />
+ </add>
+ -->
+ <add as="SAT_f" extend="SATURATION"/>
+ <!-- RAND_f and RAND_m looks exactly the same -->
+ <add as="RAND_f" extend="RAND_m"/>
+ <add as="MUX_f" extend="MUX"/>
+ <add as="MEMORY_f" extend="blockWithLabel"/>
+ <add as="LOGICAL_OP" extend="blockWithLabel">
+ <add as="displayedLabel" value="AND"/>
+ </add>
+ <add as="generic_block" extend="blockWithLabel">
+ <add as="displayedLabel" value="GENERIC"/>
+ </add>
+ <add as="GAINBLK" extend="Gain"/>
+ <add as="GAIN_f" extend="Gain"/>
+ <add as="EVTDLY_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="Delay"/>
+ </add>
+ <add as="END_c" extend="blockWithLabel">
+ <add as="displayedLabel" value="END"/>
+ </add>
+ <add as="ENDBLK" extend="blockWithLabel">
+ <add as="displayedLabel" value="END"/>
+ </add>
+ <add as="EDGETRIGGER" extend="blockWithLabel">
+ <add as="displayedLabel" value="Edge &lt;BR&gt;trigger"/>
+ </add>
+ <add as="DOLLAR_m" extend="DOLLAR_f"/>
+ <add as="DIFF_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="S"/>
+ </add>
+ <add as="DEMUX_f" extend="DEMUX"/>
+ <add as="DEADBAND" extend="Icon">
+ <add as="image" value="blocks/DEADBAND.svg"/>
+ </add>
+ <add as="Counter" extend="blockWithLabel">
+ <add as="displayedLabel" value="Counter&lt;BR&gt;%s &amp;#8594; %s"/>
+ </add>
+ <add as="CLOCK_f" extend="Icon">
+ <add as="image" value="blocks/CLOCK_c.svg"/>
+ </add>
+ <add as="VirtualCLK0" extend="CLOCK_f"/>
+ <add as="CLKSPLIT_f" extend="Split"/>
+ <add as="IMPSPLIT_f" extend="Split"/>
+ <add as="CLKSOM_f" extend="CLKSOMV_f"/>
+ <add as="CLKOUT_f" extend="Icon">
+ <add as="image" value="blocks/CLKOUT_f.gif"/>
+ </add>
+ <add as="ABSBLK_f" extend="blockWithLabel">
+ <add as="displayedLabel" value="y = |u|"/>
+ </add>
+</mxStylesheet>