summaryrefslogtreecommitdiffstats
path: root/deluge/ui
diff options
context:
space:
mode:
Diffstat (limited to 'deluge/ui')
-rw-r--r--deluge/ui/__init__.py0
-rw-r--r--deluge/ui/client.py838
-rw-r--r--deluge/ui/common.py564
-rw-r--r--deluge/ui/console/__init__.py19
-rw-r--r--deluge/ui/console/cmdline/__init__.py0
-rw-r--r--deluge/ui/console/cmdline/command.py215
-rw-r--r--deluge/ui/console/cmdline/commands/__init__.py6
-rw-r--r--deluge/ui/console/cmdline/commands/add.py126
-rw-r--r--deluge/ui/console/cmdline/commands/cache.py31
-rw-r--r--deluge/ui/console/cmdline/commands/config.py168
-rw-r--r--deluge/ui/console/cmdline/commands/connect.py86
-rw-r--r--deluge/ui/console/cmdline/commands/debug.py40
-rw-r--r--deluge/ui/console/cmdline/commands/gui.py30
-rw-r--r--deluge/ui/console/cmdline/commands/halt.py35
-rw-r--r--deluge/ui/console/cmdline/commands/help.py74
-rw-r--r--deluge/ui/console/cmdline/commands/info.py484
-rw-r--r--deluge/ui/console/cmdline/commands/manage.py119
-rw-r--r--deluge/ui/console/cmdline/commands/move.py97
-rw-r--r--deluge/ui/console/cmdline/commands/pause.py48
-rw-r--r--deluge/ui/console/cmdline/commands/plugin.py143
-rw-r--r--deluge/ui/console/cmdline/commands/quit.py25
-rw-r--r--deluge/ui/console/cmdline/commands/recheck.py47
-rw-r--r--deluge/ui/console/cmdline/commands/resume.py48
-rw-r--r--deluge/ui/console/cmdline/commands/rm.py83
-rw-r--r--deluge/ui/console/cmdline/commands/status.py114
-rw-r--r--deluge/ui/console/cmdline/commands/update_tracker.py47
-rw-r--r--deluge/ui/console/console.py169
-rw-r--r--deluge/ui/console/main.py765
-rw-r--r--deluge/ui/console/modes/__init__.py0
-rw-r--r--deluge/ui/console/modes/add_util.py97
-rw-r--r--deluge/ui/console/modes/addtorrents.py545
-rw-r--r--deluge/ui/console/modes/basemode.py357
-rw-r--r--deluge/ui/console/modes/cmdline.py850
-rw-r--r--deluge/ui/console/modes/connectionmanager.py206
-rw-r--r--deluge/ui/console/modes/eventview.py115
-rw-r--r--deluge/ui/console/modes/preferences/__init__.py5
-rw-r--r--deluge/ui/console/modes/preferences/preference_panes.py764
-rw-r--r--deluge/ui/console/modes/preferences/preferences.py379
-rw-r--r--deluge/ui/console/modes/torrentdetail.py1026
-rw-r--r--deluge/ui/console/modes/torrentlist/__init__.py20
-rw-r--r--deluge/ui/console/modes/torrentlist/add_torrents_popup.py113
-rw-r--r--deluge/ui/console/modes/torrentlist/filtersidebar.py134
-rw-r--r--deluge/ui/console/modes/torrentlist/queue_mode.py157
-rw-r--r--deluge/ui/console/modes/torrentlist/search_mode.py210
-rw-r--r--deluge/ui/console/modes/torrentlist/torrentactions.py276
-rw-r--r--deluge/ui/console/modes/torrentlist/torrentlist.py348
-rw-r--r--deluge/ui/console/modes/torrentlist/torrentview.py517
-rw-r--r--deluge/ui/console/modes/torrentlist/torrentviewcolumns.py162
-rw-r--r--deluge/ui/console/parser.py143
-rw-r--r--deluge/ui/console/utils/__init__.py0
-rw-r--r--deluge/ui/console/utils/colors.py326
-rw-r--r--deluge/ui/console/utils/column.py77
-rw-r--r--deluge/ui/console/utils/common.py23
-rw-r--r--deluge/ui/console/utils/curses_util.py65
-rw-r--r--deluge/ui/console/utils/format_utils.py353
-rw-r--r--deluge/ui/console/widgets/__init__.py7
-rw-r--r--deluge/ui/console/widgets/fields.py1210
-rw-r--r--deluge/ui/console/widgets/inputpane.py397
-rw-r--r--deluge/ui/console/widgets/popup.py402
-rw-r--r--deluge/ui/console/widgets/sidebar.py82
-rw-r--r--deluge/ui/console/widgets/statusbars.py122
-rw-r--r--deluge/ui/console/widgets/window.py185
-rw-r--r--deluge/ui/coreconfig.py54
-rw-r--r--deluge/ui/countries.py256
-rw-r--r--deluge/ui/data/__pycache__/__init__.cpython-37.pycbin0 -> 141 bytes
-rw-r--r--deluge/ui/data/icons/hicolor/128x128/apps/deluge.pngbin0 -> 4365 bytes
-rw-r--r--deluge/ui/data/icons/hicolor/16x16/apps/deluge-panel.pngbin0 -> 551 bytes
-rw-r--r--deluge/ui/data/icons/hicolor/16x16/apps/deluge.pngbin0 -> 551 bytes
-rw-r--r--deluge/ui/data/icons/hicolor/192x192/apps/deluge.pngbin0 -> 6606 bytes
-rw-r--r--deluge/ui/data/icons/hicolor/22x22/apps/deluge-panel.pngbin0 -> 778 bytes
-rw-r--r--deluge/ui/data/icons/hicolor/22x22/apps/deluge.pngbin0 -> 778 bytes
-rw-r--r--deluge/ui/data/icons/hicolor/24x24/apps/deluge-panel.pngbin0 -> 877 bytes
-rw-r--r--deluge/ui/data/icons/hicolor/24x24/apps/deluge.pngbin0 -> 877 bytes
-rw-r--r--deluge/ui/data/icons/hicolor/256x256/apps/deluge.pngbin0 -> 8907 bytes
-rw-r--r--deluge/ui/data/icons/hicolor/32x32/apps/deluge.pngbin0 -> 1126 bytes
-rw-r--r--deluge/ui/data/icons/hicolor/36x36/apps/deluge.pngbin0 -> 1270 bytes
-rw-r--r--deluge/ui/data/icons/hicolor/48x48/apps/deluge.pngbin0 -> 1798 bytes
-rw-r--r--deluge/ui/data/icons/hicolor/512x512/apps/deluge.pngbin0 -> 20135 bytes
-rw-r--r--deluge/ui/data/icons/hicolor/64x64/apps/deluge.pngbin0 -> 2324 bytes
-rw-r--r--deluge/ui/data/icons/hicolor/72x72/apps/deluge.pngbin0 -> 2535 bytes
-rw-r--r--deluge/ui/data/icons/hicolor/96x96/apps/deluge.pngbin0 -> 3323 bytes
-rw-r--r--deluge/ui/data/icons/hicolor/scalable/apps/deluge.svg610
-rw-r--r--deluge/ui/data/pixmaps/__pycache__/__init__.cpython-37.pycbin0 -> 149 bytes
-rw-r--r--deluge/ui/data/pixmaps/active.svg612
-rw-r--r--deluge/ui/data/pixmaps/active16.pngbin0 -> 505 bytes
-rw-r--r--deluge/ui/data/pixmaps/alert.svg512
-rw-r--r--deluge/ui/data/pixmaps/alert16.pngbin0 -> 462 bytes
-rw-r--r--deluge/ui/data/pixmaps/all.svg826
-rw-r--r--deluge/ui/data/pixmaps/all16.pngbin0 -> 533 bytes
-rw-r--r--deluge/ui/data/pixmaps/checking.svg504
-rw-r--r--deluge/ui/data/pixmaps/checking16.pngbin0 -> 490 bytes
-rw-r--r--deluge/ui/data/pixmaps/deluge-about.pngbin0 -> 6117 bytes
-rw-r--r--deluge/ui/data/pixmaps/deluge.icobin0 -> 112928 bytes
-rw-r--r--deluge/ui/data/pixmaps/deluge.pngbin0 -> 1798 bytes
-rw-r--r--deluge/ui/data/pixmaps/deluge.svg610
-rw-r--r--deluge/ui/data/pixmaps/deluge16.pngbin0 -> 552 bytes
-rw-r--r--deluge/ui/data/pixmaps/dht.svg163
-rw-r--r--deluge/ui/data/pixmaps/dht16.pngbin0 -> 582 bytes
-rw-r--r--deluge/ui/data/pixmaps/downloading.svg515
-rw-r--r--deluge/ui/data/pixmaps/downloading16.pngbin0 -> 465 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ad.pngbin0 -> 444 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ae.pngbin0 -> 275 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/af.pngbin0 -> 415 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ag.pngbin0 -> 431 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ai.pngbin0 -> 492 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/al.pngbin0 -> 427 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/am.pngbin0 -> 322 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/an.pngbin0 -> 359 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ao.pngbin0 -> 381 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/aq.pngbin0 -> 582 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ar.pngbin0 -> 354 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/as.pngbin0 -> 514 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/at.pngbin0 -> 286 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/au.pngbin0 -> 554 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/aw.pngbin0 -> 376 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ax.pngbin0 -> 470 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/az.pngbin0 -> 411 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ba.pngbin0 -> 449 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/bb.pngbin0 -> 392 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/bd.pngbin0 -> 355 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/be.pngbin0 -> 286 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/bf.pngbin0 -> 333 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/bg.pngbin0 -> 306 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/bh.pngbin0 -> 331 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/bi.pngbin0 -> 548 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/bj.pngbin0 -> 307 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/bm.pngbin0 -> 475 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/bn.pngbin0 -> 486 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/bo.pngbin0 -> 335 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/br.pngbin0 -> 458 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/bs.pngbin0 -> 386 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/bt.pngbin0 -> 460 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/bv.pngbin0 -> 380 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/bw.pngbin0 -> 313 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/by.pngbin0 -> 365 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/bz.pngbin0 -> 457 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ca.pngbin0 -> 459 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/cc.pngbin0 -> 480 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/cd.pngbin0 -> 448 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/cf.pngbin0 -> 443 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/cg.pngbin0 -> 366 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ch.pngbin0 -> 231 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ci.pngbin0 -> 300 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ck.pngbin0 -> 474 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/cl.pngbin0 -> 316 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/cm.pngbin0 -> 335 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/cn.pngbin0 -> 339 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/co.pngbin0 -> 322 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/cr.pngbin0 -> 339 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/cs.pngbin0 -> 305 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/cu.pngbin0 -> 416 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/cv.pngbin0 -> 403 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/cx.pngbin0 -> 459 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/cy.pngbin0 -> 318 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/cz.pngbin0 -> 349 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/de.pngbin0 -> 353 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/dj.pngbin0 -> 411 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/dk.pngbin0 -> 342 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/dm.pngbin0 -> 488 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/do.pngbin0 -> 361 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/dz.pngbin0 -> 442 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ec.pngbin0 -> 345 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ee.pngbin0 -> 285 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/eg.pngbin0 -> 344 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/eh.pngbin0 -> 377 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/er.pngbin0 -> 480 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/es.pngbin0 -> 336 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/et.pngbin0 -> 424 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/fi.pngbin0 -> 355 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/fj.pngbin0 -> 485 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/fk.pngbin0 -> 498 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/fm.pngbin0 -> 394 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/fo.pngbin0 -> 367 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/fr.pngbin0 -> 363 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/fx.pngbin0 -> 460 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ga.pngbin0 -> 331 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/gb.pngbin0 -> 526 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/gd.pngbin0 -> 446 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ge.pngbin0 -> 468 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/gf.pngbin0 -> 363 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/gg.pngbin0 -> 445 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/gh.pngbin0 -> 320 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/gi.pngbin0 -> 354 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/gl.pngbin0 -> 337 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/gm.pngbin0 -> 349 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/gn.pngbin0 -> 309 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/gp.pngbin0 -> 343 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/gq.pngbin0 -> 391 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/gr.pngbin0 -> 377 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/gs.pngbin0 -> 499 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/gt.pngbin0 -> 327 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/gu.pngbin0 -> 366 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/gw.pngbin0 -> 339 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/gy.pngbin0 -> 500 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/hk.pngbin0 -> 380 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/hm.pngbin0 -> 554 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/hn.pngbin0 -> 394 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/hr.pngbin0 -> 368 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ht.pngbin0 -> 319 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/hu.pngbin0 -> 284 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/id.pngbin0 -> 291 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ie.pngbin0 -> 325 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/il.pngbin0 -> 310 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/in.pngbin0 -> 360 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/io.pngbin0 -> 556 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/iq.pngbin0 -> 391 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ir.pngbin0 -> 385 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/is.pngbin0 -> 395 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/it.pngbin0 -> 277 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/je.pngbin0 -> 630 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/jm.pngbin0 -> 478 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/jo.pngbin0 -> 344 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/jp.pngbin0 -> 299 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ke.pngbin0 -> 423 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/kg.pngbin0 -> 341 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/kh.pngbin0 -> 409 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ki.pngbin0 -> 520 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/km.pngbin0 -> 432 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/kn.pngbin0 -> 455 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/kp.pngbin0 -> 409 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/kr.pngbin0 -> 484 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/kw.pngbin0 -> 341 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ky.pngbin0 -> 505 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/kz.pngbin0 -> 441 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/la.pngbin0 -> 395 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/lb.pngbin0 -> 374 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/lc.pngbin0 -> 454 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/li.pngbin0 -> 382 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/lk.pngbin0 -> 454 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/lr.pngbin0 -> 356 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ls.pngbin0 -> 480 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/lt.pngbin0 -> 336 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/lu.pngbin0 -> 320 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/lv.pngbin0 -> 329 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ly.pngbin0 -> 269 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ma.pngbin0 -> 286 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/mc.pngbin0 -> 251 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/md.pngbin0 -> 393 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/me.pngbin0 -> 371 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/mg.pngbin0 -> 304 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/mh.pngbin0 -> 500 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/mk.pngbin0 -> 433 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ml.pngbin0 -> 314 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/mm.pngbin0 -> 334 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/mn.pngbin0 -> 328 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/mo.pngbin0 -> 440 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/mp.pngbin0 -> 460 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/mq.pngbin0 -> 524 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/mr.pngbin0 -> 398 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ms.pngbin0 -> 472 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/mt.pngbin0 -> 290 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/mu.pngbin0 -> 350 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/mv.pngbin0 -> 385 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/mw.pngbin0 -> 356 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/mx.pngbin0 -> 409 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/my.pngbin0 -> 453 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/mz.pngbin0 -> 418 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/na.pngbin0 -> 531 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/nc.pngbin0 -> 450 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ne.pngbin0 -> 370 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/nf.pngbin0 -> 457 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ng.pngbin0 -> 334 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ni.pngbin0 -> 358 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/nl.pngbin0 -> 300 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/no.pngbin0 -> 380 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/np.pngbin0 -> 330 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/nr.pngbin0 -> 376 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/nu.pngbin0 -> 449 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/nz.pngbin0 -> 500 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/om.pngbin0 -> 334 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/pa.pngbin0 -> 379 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/pe.pngbin0 -> 251 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/pf.pngbin0 -> 373 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/pg.pngbin0 -> 414 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ph.pngbin0 -> 406 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/pk.pngbin0 -> 439 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/pl.pngbin0 -> 236 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/pm.pngbin0 -> 552 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/pn.pngbin0 -> 523 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/pr.pngbin0 -> 420 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ps.pngbin0 -> 334 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/pt.pngbin0 -> 391 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/pw.pngbin0 -> 403 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/py.pngbin0 -> 334 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/qa.pngbin0 -> 325 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/re.pngbin0 -> 363 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ro.pngbin0 -> 322 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/rs.pngbin0 -> 362 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ru.pngbin0 -> 281 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/rw.pngbin0 -> 365 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/sa.pngbin0 -> 418 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/sb.pngbin0 -> 489 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/sc.pngbin0 -> 464 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/sd.pngbin0 -> 349 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/se.pngbin0 -> 374 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/sg.pngbin0 -> 338 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/sh.pngbin0 -> 498 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/si.pngbin0 -> 370 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/sj.pngbin0 -> 380 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/sk.pngbin0 -> 419 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/sl.pngbin0 -> 311 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/sm.pngbin0 -> 367 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/sn.pngbin0 -> 351 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/so.pngbin0 -> 366 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/sr.pngbin0 -> 360 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/st.pngbin0 -> 415 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/sv.pngbin0 -> 357 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/sy.pngbin0 -> 312 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/sz.pngbin0 -> 486 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/tc.pngbin0 -> 485 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/td.pngbin0 -> 370 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/tf.pngbin0 -> 386 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/tg.pngbin0 -> 397 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/th.pngbin0 -> 324 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/tj.pngbin0 -> 353 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/tk.pngbin0 -> 499 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/tl.pngbin0 -> 377 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/tm.pngbin0 -> 438 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/tn.pngbin0 -> 358 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/to.pngbin0 -> 292 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/tp.pngbin0 -> 445 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/tr.pngbin0 -> 358 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/tt.pngbin0 -> 460 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/tv.pngbin0 -> 416 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/tw.pngbin0 -> 320 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/tz.pngbin0 -> 488 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ua.pngbin0 -> 297 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ug.pngbin0 -> 374 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/um.pngbin0 -> 443 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/us.pngbin0 -> 455 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/uy.pngbin0 -> 396 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/uz.pngbin0 -> 390 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/va.pngbin0 -> 401 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/vc.pngbin0 -> 398 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ve.pngbin0 -> 384 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/vg.pngbin0 -> 485 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/vi.pngbin0 -> 503 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/vn.pngbin0 -> 310 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/vu.pngbin0 -> 423 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/wf.pngbin0 -> 429 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ws.pngbin0 -> 346 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/ye.pngbin0 -> 289 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/yt.pngbin0 -> 446 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/yu.pngbin0 -> 385 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/za.pngbin0 -> 497 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/zm.pngbin0 -> 349 bytes
-rw-r--r--deluge/ui/data/pixmaps/flags/zw.pngbin0 -> 440 bytes
-rw-r--r--deluge/ui/data/pixmaps/inactive.svg492
-rw-r--r--deluge/ui/data/pixmaps/inactive16.pngbin0 -> 432 bytes
-rw-r--r--deluge/ui/data/pixmaps/loading.gifbin0 -> 723 bytes
-rw-r--r--deluge/ui/data/pixmaps/magnet.pngbin0 -> 906 bytes
-rw-r--r--deluge/ui/data/pixmaps/queued.svg532
-rw-r--r--deluge/ui/data/pixmaps/queued16.pngbin0 -> 410 bytes
-rw-r--r--deluge/ui/data/pixmaps/seeding.svg509
-rw-r--r--deluge/ui/data/pixmaps/seeding16.pngbin0 -> 505 bytes
-rw-r--r--deluge/ui/data/pixmaps/tracker_all16.pngbin0 -> 1097 bytes
-rw-r--r--deluge/ui/data/pixmaps/tracker_warning16.pngbin0 -> 683 bytes
-rw-r--r--deluge/ui/data/pixmaps/traffic.svg94
-rw-r--r--deluge/ui/data/pixmaps/traffic16.pngbin0 -> 394 bytes
-rw-r--r--deluge/ui/data/share/appdata/deluge.appdata.xml.in30
-rw-r--r--deluge/ui/data/share/applications/deluge.desktop.in16
-rw-r--r--deluge/ui/gtk3/__init__.py63
-rw-r--r--deluge/ui/gtk3/aboutdialog.py855
-rw-r--r--deluge/ui/gtk3/addtorrentdialog.py1101
-rw-r--r--deluge/ui/gtk3/common.py395
-rw-r--r--deluge/ui/gtk3/connectionmanager.py568
-rw-r--r--deluge/ui/gtk3/createtorrentdialog.py520
-rw-r--r--deluge/ui/gtk3/details_tab.py74
-rw-r--r--deluge/ui/gtk3/dialogs.py455
-rw-r--r--deluge/ui/gtk3/edittrackersdialog.py300
-rw-r--r--deluge/ui/gtk3/files_tab.py860
-rw-r--r--deluge/ui/gtk3/filtertreeview.py378
-rw-r--r--deluge/ui/gtk3/glade/add_torrent_dialog.infohash.ui219
-rw-r--r--deluge/ui/gtk3/glade/add_torrent_dialog.ui1039
-rw-r--r--deluge/ui/gtk3/glade/add_torrent_dialog.url.ui176
-rw-r--r--deluge/ui/gtk3/glade/connect_peer_dialog.ui154
-rw-r--r--deluge/ui/gtk3/glade/connection_manager.addhost.ui237
-rw-r--r--deluge/ui/gtk3/glade/connection_manager.ui394
-rw-r--r--deluge/ui/gtk3/glade/create_torrent_dialog.progress.ui57
-rw-r--r--deluge/ui/gtk3/glade/create_torrent_dialog.remote_path.ui176
-rw-r--r--deluge/ui/gtk3/glade/create_torrent_dialog.remote_save.ui176
-rw-r--r--deluge/ui/gtk3/glade/create_torrent_dialog.ui847
-rw-r--r--deluge/ui/gtk3/glade/edit_trackers.add.ui183
-rw-r--r--deluge/ui/gtk3/glade/edit_trackers.edit.ui177
-rw-r--r--deluge/ui/gtk3/glade/edit_trackers.ui247
-rw-r--r--deluge/ui/gtk3/glade/filtertree_menu.ui60
-rw-r--r--deluge/ui/gtk3/glade/main_window.new_release.ui249
-rw-r--r--deluge/ui/gtk3/glade/main_window.tabs.menu_file.ui138
-rw-r--r--deluge/ui/gtk3/glade/main_window.tabs.menu_peer.ui27
-rw-r--r--deluge/ui/gtk3/glade/main_window.tabs.ui1679
-rw-r--r--deluge/ui/gtk3/glade/main_window.ui796
-rw-r--r--deluge/ui/gtk3/glade/move_storage_dialog.ui163
-rw-r--r--deluge/ui/gtk3/glade/other_dialog.ui190
-rw-r--r--deluge/ui/gtk3/glade/path_combo_chooser.ui1002
-rw-r--r--deluge/ui/gtk3/glade/preferences_dialog.ui5020
-rw-r--r--deluge/ui/gtk3/glade/queuedtorrents.ui211
-rw-r--r--deluge/ui/gtk3/glade/remove_torrent_dialog.ui187
-rw-r--r--deluge/ui/gtk3/glade/torrent_menu.options.ui104
-rw-r--r--deluge/ui/gtk3/glade/torrent_menu.queue.ui69
-rw-r--r--deluge/ui/gtk3/glade/torrent_menu.ui225
-rw-r--r--deluge/ui/gtk3/glade/tray_menu.ui167
-rw-r--r--deluge/ui/gtk3/gtkui.py391
-rw-r--r--deluge/ui/gtk3/ipcinterface.py230
-rw-r--r--deluge/ui/gtk3/listview.py838
-rw-r--r--deluge/ui/gtk3/mainwindow.py381
-rw-r--r--deluge/ui/gtk3/menubar.py614
-rw-r--r--deluge/ui/gtk3/menubar_osx.py87
-rw-r--r--deluge/ui/gtk3/new_release_dialog.py73
-rw-r--r--deluge/ui/gtk3/options_tab.py222
-rw-r--r--deluge/ui/gtk3/path_chooser.py201
-rwxr-xr-xdeluge/ui/gtk3/path_combo_chooser.py1742
-rw-r--r--deluge/ui/gtk3/peers_tab.py394
-rw-r--r--deluge/ui/gtk3/piecesbar.py235
-rw-r--r--deluge/ui/gtk3/pluginmanager.py137
-rw-r--r--deluge/ui/gtk3/preferences.py1527
-rw-r--r--deluge/ui/gtk3/queuedtorrents.py168
-rw-r--r--deluge/ui/gtk3/removetorrentdialog.py93
-rw-r--r--deluge/ui/gtk3/sidebar.py73
-rw-r--r--deluge/ui/gtk3/status_tab.py162
-rw-r--r--deluge/ui/gtk3/statusbar.py578
-rw-r--r--deluge/ui/gtk3/systemtray.py445
-rw-r--r--deluge/ui/gtk3/tab_data_funcs.py96
-rw-r--r--deluge/ui/gtk3/toolbar.py134
-rw-r--r--deluge/ui/gtk3/torrentdetails.py458
-rw-r--r--deluge/ui/gtk3/torrentview.py934
-rw-r--r--deluge/ui/gtk3/torrentview_data_funcs.py285
-rw-r--r--deluge/ui/gtk3/trackers_tab.py74
-rw-r--r--deluge/ui/hostlist.py292
-rw-r--r--deluge/ui/sessionproxy.py278
-rw-r--r--deluge/ui/tracker_icons.py662
-rw-r--r--deluge/ui/ui.py72
-rw-r--r--deluge/ui/ui_entry.py143
-rw-r--r--deluge/ui/web/__init__.py8
-rw-r--r--deluge/ui/web/auth.py257
-rw-r--r--deluge/ui/web/common.py48
-rw-r--r--deluge/ui/web/css/deluge.css568
-rw-r--r--deluge/ui/web/css/ext-all-notheme.css5349
-rw-r--r--deluge/ui/web/css/ext-extensions-debug.css261
-rw-r--r--deluge/ui/web/css/ext-extensions.css1
-rw-r--r--deluge/ui/web/icons/active.pngbin0 -> 503 bytes
-rw-r--r--deluge/ui/web/icons/add.pngbin0 -> 358 bytes
-rw-r--r--deluge/ui/web/icons/add_file.pngbin0 -> 520 bytes
-rw-r--r--deluge/ui/web/icons/add_magnet.pngbin0 -> 589 bytes
-rw-r--r--deluge/ui/web/icons/add_url.pngbin0 -> 815 bytes
-rw-r--r--deluge/ui/web/icons/alert.pngbin0 -> 462 bytes
-rw-r--r--deluge/ui/web/icons/all.pngbin0 -> 533 bytes
-rw-r--r--deluge/ui/web/icons/back.pngbin0 -> 443 bytes
-rw-r--r--deluge/ui/web/icons/bottom.pngbin0 -> 346 bytes
-rw-r--r--deluge/ui/web/icons/checking.pngbin0 -> 489 bytes
-rw-r--r--deluge/ui/web/icons/connection_manager.pngbin0 -> 464 bytes
-rw-r--r--deluge/ui/web/icons/connections.pngbin0 -> 560 bytes
-rw-r--r--deluge/ui/web/icons/create.pngbin0 -> 541 bytes
-rw-r--r--deluge/ui/web/icons/deluge-192.pngbin0 -> 6604 bytes
-rw-r--r--deluge/ui/web/icons/deluge-32.pngbin0 -> 1126 bytes
-rw-r--r--deluge/ui/web/icons/deluge-512.pngbin0 -> 20126 bytes
-rw-r--r--deluge/ui/web/icons/deluge-apple-180.pngbin0 -> 5367 bytes
-rw-r--r--deluge/ui/web/icons/deluge.pngbin0 -> 551 bytes
-rw-r--r--deluge/ui/web/icons/dht.pngbin0 -> 582 bytes
-rw-r--r--deluge/ui/web/icons/document.pngbin0 -> 406 bytes
-rw-r--r--deluge/ui/web/icons/down.pngbin0 -> 427 bytes
-rw-r--r--deluge/ui/web/icons/downloading.pngbin0 -> 462 bytes
-rw-r--r--deluge/ui/web/icons/drive.pngbin0 -> 381 bytes
-rw-r--r--deluge/ui/web/icons/edit_trackers.pngbin0 -> 589 bytes
-rw-r--r--deluge/ui/web/icons/error.pngbin0 -> 730 bytes
-rw-r--r--deluge/ui/web/icons/expand_all.pngbin0 -> 612 bytes
-rw-r--r--deluge/ui/web/icons/favicon.icobin0 -> 15086 bytes
-rw-r--r--deluge/ui/web/icons/find_more.pngbin0 -> 557 bytes
-rw-r--r--deluge/ui/web/icons/forward.pngbin0 -> 436 bytes
-rw-r--r--deluge/ui/web/icons/help.pngbin0 -> 772 bytes
-rw-r--r--deluge/ui/web/icons/high.pngbin0 -> 505 bytes
-rw-r--r--deluge/ui/web/icons/home.pngbin0 -> 550 bytes
-rw-r--r--deluge/ui/web/icons/inactive.pngbin0 -> 431 bytes
-rw-r--r--deluge/ui/web/icons/install_plugin.pngbin0 -> 674 bytes
-rw-r--r--deluge/ui/web/icons/login.pngbin0 -> 469 bytes
-rw-r--r--deluge/ui/web/icons/logout.pngbin0 -> 599 bytes
-rw-r--r--deluge/ui/web/icons/low.pngbin0 -> 358 bytes
-rw-r--r--deluge/ui/web/icons/move.pngbin0 -> 524 bytes
-rw-r--r--deluge/ui/web/icons/no_download.pngbin0 -> 581 bytes
-rw-r--r--deluge/ui/web/icons/normal.pngbin0 -> 436 bytes
-rw-r--r--deluge/ui/web/icons/ok.pngbin0 -> 766 bytes
-rw-r--r--deluge/ui/web/icons/pause.pngbin0 -> 331 bytes
-rw-r--r--deluge/ui/web/icons/preferences.pngbin0 -> 694 bytes
-rw-r--r--deluge/ui/web/icons/queue.pngbin0 -> 432 bytes
-rw-r--r--deluge/ui/web/icons/queued.pngbin0 -> 410 bytes
-rw-r--r--deluge/ui/web/icons/recheck.pngbin0 -> 557 bytes
-rw-r--r--deluge/ui/web/icons/remove.pngbin0 -> 194 bytes
-rw-r--r--deluge/ui/web/icons/seeding.pngbin0 -> 505 bytes
-rw-r--r--deluge/ui/web/icons/start.pngbin0 -> 358 bytes
-rw-r--r--deluge/ui/web/icons/top.pngbin0 -> 325 bytes
-rw-r--r--deluge/ui/web/icons/traffic.pngbin0 -> 394 bytes
-rw-r--r--deluge/ui/web/icons/up.pngbin0 -> 420 bytes
-rw-r--r--deluge/ui/web/icons/update.pngbin0 -> 675 bytes
-rw-r--r--deluge/ui/web/icons/upload_slots.pngbin0 -> 422 bytes
-rw-r--r--deluge/ui/web/icons/warning.pngbin0 -> 521 bytes
-rw-r--r--deluge/ui/web/images/s.gifbin0 -> 43 bytes
-rw-r--r--deluge/ui/web/images/spinner-split.gifbin0 -> 49 bytes
-rw-r--r--deluge/ui/web/images/spinner.gifbin0 -> 3186 bytes
-rw-r--r--deluge/ui/web/index.html48
-rw-r--r--deluge/ui/web/js/deluge-all-debug.js9868
-rw-r--r--deluge/ui/web/js/deluge-all/.order2
-rw-r--r--deluge/ui/web/js/deluge-all/AboutWindow.js129
-rw-r--r--deluge/ui/web/js/deluge-all/AddConnectionWindow.js117
-rw-r--r--deluge/ui/web/js/deluge-all/AddTrackerWindow.js94
-rw-r--r--deluge/ui/web/js/deluge-all/Client.js199
-rw-r--r--deluge/ui/web/js/deluge-all/ConnectionManager.js429
-rw-r--r--deluge/ui/web/js/deluge-all/Deluge.js186
-rw-r--r--deluge/ui/web/js/deluge-all/EditConnectionWindow.js134
-rw-r--r--deluge/ui/web/js/deluge-all/EditTrackerWindow.js83
-rw-r--r--deluge/ui/web/js/deluge-all/EditTrackersWindow.js239
-rw-r--r--deluge/ui/web/js/deluge-all/EventsManager.js118
-rw-r--r--deluge/ui/web/js/deluge-all/FileBrowser.js43
-rw-r--r--deluge/ui/web/js/deluge-all/FilterPanel.js175
-rw-r--r--deluge/ui/web/js/deluge-all/Formatters.js181
-rw-r--r--deluge/ui/web/js/deluge-all/Keys.js138
-rw-r--r--deluge/ui/web/js/deluge-all/LoginWindow.js134
-rw-r--r--deluge/ui/web/js/deluge-all/Menus.js388
-rw-r--r--deluge/ui/web/js/deluge-all/MoveStorage.js85
-rw-r--r--deluge/ui/web/js/deluge-all/MultiOptionsManager.js218
-rw-r--r--deluge/ui/web/js/deluge-all/OptionsManager.js279
-rw-r--r--deluge/ui/web/js/deluge-all/OtherLimitWindow.js82
-rw-r--r--deluge/ui/web/js/deluge-all/Plugin.js106
-rw-r--r--deluge/ui/web/js/deluge-all/RemoveWindow.js77
-rw-r--r--deluge/ui/web/js/deluge-all/Sidebar.js144
-rw-r--r--deluge/ui/web/js/deluge-all/Statusbar.js362
-rw-r--r--deluge/ui/web/js/deluge-all/StatusbarMenu.js79
-rw-r--r--deluge/ui/web/js/deluge-all/Toolbar.js206
-rw-r--r--deluge/ui/web/js/deluge-all/TorrentGrid.js510
-rw-r--r--deluge/ui/web/js/deluge-all/UI.js292
-rw-r--r--deluge/ui/web/js/deluge-all/add/.order1
-rw-r--r--deluge/ui/web/js/deluge-all/add/AddWindow.js321
-rw-r--r--deluge/ui/web/js/deluge-all/add/FilesTab.js99
-rw-r--r--deluge/ui/web/js/deluge-all/add/Infohash.js10
-rw-r--r--deluge/ui/web/js/deluge-all/add/OptionsPanel.js146
-rw-r--r--deluge/ui/web/js/deluge-all/add/OptionsTab.js217
-rw-r--r--deluge/ui/web/js/deluge-all/add/UrlWindow.js98
-rw-r--r--deluge/ui/web/js/deluge-all/add/Window.js29
-rw-r--r--deluge/ui/web/js/deluge-all/data/.order1
-rw-r--r--deluge/ui/web/js/deluge-all/data/PeerRecord.js53
-rw-r--r--deluge/ui/web/js/deluge-all/data/SortTypes.js37
-rw-r--r--deluge/ui/web/js/deluge-all/data/TorrentRecord.js121
-rw-r--r--deluge/ui/web/js/deluge-all/details/DetailsPanel.js81
-rw-r--r--deluge/ui/web/js/deluge-all/details/DetailsTab.js98
-rw-r--r--deluge/ui/web/js/deluge-all/details/FilesTab.js233
-rw-r--r--deluge/ui/web/js/deluge-all/details/OptionsTab.js417
-rw-r--r--deluge/ui/web/js/deluge-all/details/PeersTab.js166
-rw-r--r--deluge/ui/web/js/deluge-all/details/StatusTab.js155
-rw-r--r--deluge/ui/web/js/deluge-all/preferences/BandwidthPage.js203
-rw-r--r--deluge/ui/web/js/deluge-all/preferences/CachePage.js61
-rw-r--r--deluge/ui/web/js/deluge-all/preferences/DaemonPage.js85
-rw-r--r--deluge/ui/web/js/deluge-all/preferences/DownloadsPage.js124
-rw-r--r--deluge/ui/web/js/deluge-all/preferences/EncryptionPage.js99
-rw-r--r--deluge/ui/web/js/deluge-all/preferences/InstallPluginWindow.js83
-rw-r--r--deluge/ui/web/js/deluge-all/preferences/InterfacePage.js304
-rw-r--r--deluge/ui/web/js/deluge-all/preferences/NetworkPage.js257
-rw-r--r--deluge/ui/web/js/deluge-all/preferences/OtherPage.js100
-rw-r--r--deluge/ui/web/js/deluge-all/preferences/PluginsPage.js277
-rw-r--r--deluge/ui/web/js/deluge-all/preferences/PreferencesWindow.js246
-rw-r--r--deluge/ui/web/js/deluge-all/preferences/ProxyField.js225
-rw-r--r--deluge/ui/web/js/deluge-all/preferences/ProxyPage.js62
-rw-r--r--deluge/ui/web/js/deluge-all/preferences/QueuePage.js234
-rw-r--r--deluge/ui/web/js/extjs/ext-all-debug.js52270
-rw-r--r--deluge/ui/web/js/extjs/ext-all.js21
-rw-r--r--deluge/ui/web/js/extjs/ext-base-debug.js3352
-rw-r--r--deluge/ui/web/js/extjs/ext-base.js21
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions-debug.js2931
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/JSLoader.js40
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/Spinner.js474
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/StatusBar.js422
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/form/FileUploadField.js208
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/form/RadioGroupFix.js50
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/form/SpinnerField.js68
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/form/SpinnerFieldFix.js13
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/form/SpinnerGroup.js206
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/form/ToggleField.js75
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/grid/BufferView.js270
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/layout/FormLayoutFix.js39
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/tree/MultiSelectionModelFix.js68
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/tree/TreeGrid.js468
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridColumnResizer.js123
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridColumns.js40
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridLoader.js18
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridNodeUI.js149
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridNodeUIFix.js33
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridRenderColumn.js9
-rw-r--r--deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridSorter.js158
-rw-r--r--deluge/ui/web/js/gettext.js322
-rw-r--r--deluge/ui/web/json_api.py1019
-rw-r--r--deluge/ui/web/pluginmanager.py161
-rw-r--r--deluge/ui/web/render/404.html10
-rw-r--r--deluge/ui/web/render/tab_status.html29
-rw-r--r--deluge/ui/web/server.py806
-rw-r--r--deluge/ui/web/themes/css/xtheme-access.css1933
-rw-r--r--deluge/ui/web/themes/css/xtheme-blue.css1793
-rw-r--r--deluge/ui/web/themes/css/xtheme-gray.css1791
-rw-r--r--deluge/ui/web/themes/images/access/box/corners-blue.gifbin0 -> 1010 bytes
-rw-r--r--deluge/ui/web/themes/images/access/box/corners.gifbin0 -> 1005 bytes
-rw-r--r--deluge/ui/web/themes/images/access/box/l-blue.gifbin0 -> 810 bytes
-rw-r--r--deluge/ui/web/themes/images/access/box/l.gifbin0 -> 810 bytes
-rw-r--r--deluge/ui/web/themes/images/access/box/r-blue.gifbin0 -> 810 bytes
-rw-r--r--deluge/ui/web/themes/images/access/box/r.gifbin0 -> 810 bytes
-rw-r--r--deluge/ui/web/themes/images/access/box/tb-blue.gifbin0 -> 843 bytes
-rw-r--r--deluge/ui/web/themes/images/access/box/tb.gifbin0 -> 839 bytes
-rw-r--r--deluge/ui/web/themes/images/access/button/arrow.gifbin0 -> 833 bytes
-rw-r--r--deluge/ui/web/themes/images/access/button/btn.gifbin0 -> 2871 bytes
-rw-r--r--deluge/ui/web/themes/images/access/button/group-cs.gifbin0 -> 2459 bytes
-rw-r--r--deluge/ui/web/themes/images/access/button/group-lr.gifbin0 -> 861 bytes
-rw-r--r--deluge/ui/web/themes/images/access/button/group-tb.gifbin0 -> 70 bytes
-rw-r--r--deluge/ui/web/themes/images/access/button/s-arrow-b-noline.gifbin0 -> 904 bytes
-rw-r--r--deluge/ui/web/themes/images/access/button/s-arrow-b.gifbin0 -> 943 bytes
-rw-r--r--deluge/ui/web/themes/images/access/button/s-arrow-bo.gifbin0 -> 961 bytes
-rw-r--r--deluge/ui/web/themes/images/access/button/s-arrow-noline.gifbin0 -> 875 bytes
-rw-r--r--deluge/ui/web/themes/images/access/button/s-arrow-o.gifbin0 -> 155 bytes
-rw-r--r--deluge/ui/web/themes/images/access/button/s-arrow.gifbin0 -> 956 bytes
-rw-r--r--deluge/ui/web/themes/images/access/editor/tb-sprite.gifbin0 -> 1994 bytes
-rw-r--r--deluge/ui/web/themes/images/access/form/checkbox.gifbin0 -> 2061 bytes
-rw-r--r--deluge/ui/web/themes/images/access/form/clear-trigger.gifbin0 -> 2027 bytes
-rw-r--r--deluge/ui/web/themes/images/access/form/clear-trigger.psdbin0 -> 41047 bytes
-rw-r--r--deluge/ui/web/themes/images/access/form/date-trigger.gifbin0 -> 1620 bytes
-rw-r--r--deluge/ui/web/themes/images/access/form/date-trigger.psdbin0 -> 46095 bytes
-rw-r--r--deluge/ui/web/themes/images/access/form/error-tip-corners.gifbin0 -> 4183 bytes
-rw-r--r--deluge/ui/web/themes/images/access/form/exclamation.gifbin0 -> 614 bytes
-rw-r--r--deluge/ui/web/themes/images/access/form/radio.gifbin0 -> 1746 bytes
-rw-r--r--deluge/ui/web/themes/images/access/form/search-trigger.gifbin0 -> 1534 bytes
-rw-r--r--deluge/ui/web/themes/images/access/form/search-trigger.psdbin0 -> 49761 bytes
-rw-r--r--deluge/ui/web/themes/images/access/form/text-bg.gifbin0 -> 66 bytes
-rw-r--r--deluge/ui/web/themes/images/access/form/trigger-tpl.gifbin0 -> 908 bytes
-rw-r--r--deluge/ui/web/themes/images/access/form/trigger.gifbin0 -> 1451 bytes
-rw-r--r--deluge/ui/web/themes/images/access/form/trigger.psdbin0 -> 44793 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/arrow-left-white.gifbin0 -> 825 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/arrow-right-white.gifbin0 -> 825 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/col-move-bottom.gifbin0 -> 868 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/col-move-top.gifbin0 -> 869 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/columns.gifbin0 -> 962 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/dirty.gifbin0 -> 68 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/done.gifbin0 -> 133 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/drop-no.gifbin0 -> 947 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/drop-yes.gifbin0 -> 860 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/footer-bg.gifbin0 -> 834 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/grid-blue-hd.gifbin0 -> 829 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/grid-blue-split.gifbin0 -> 47 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/grid-hrow.gifbin0 -> 855 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/grid-loading.gifbin0 -> 701 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/grid-split.gifbin0 -> 817 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/grid-vista-hd.gifbin0 -> 829 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/grid3-hd-btn.gifbin0 -> 419 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/grid3-hrow-over.gifbin0 -> 268 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/grid3-hrow.gifbin0 -> 164 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/grid3-special-col-bg.gifbin0 -> 162 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/grid3-special-col-sel-bg.gifbin0 -> 162 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/group-by.gifbin0 -> 917 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/group-collapse.gifbin0 -> 77 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/group-expand-sprite.gifbin0 -> 131 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/group-expand.gifbin0 -> 82 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/hd-pop.gifbin0 -> 839 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/hmenu-asc.gifbin0 -> 931 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/hmenu-desc.gifbin0 -> 930 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/hmenu-lock.gifbin0 -> 955 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/hmenu-lock.pngbin0 -> 484 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/hmenu-unlock.gifbin0 -> 971 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/hmenu-unlock.pngbin0 -> 548 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/invalid_line.gifbin0 -> 46 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/loading.gifbin0 -> 771 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/mso-hd.gifbin0 -> 875 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/nowait.gifbin0 -> 884 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/page-first-disabled.gifbin0 -> 340 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/page-first.gifbin0 -> 96 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/page-last-disabled.gifbin0 -> 340 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/page-last.gifbin0 -> 96 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/page-next-disabled.gifbin0 -> 195 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/page-next.gifbin0 -> 82 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/page-prev-disabled.gifbin0 -> 197 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/page-prev.gifbin0 -> 82 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/pick-button.gifbin0 -> 1036 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/refresh.gifbin0 -> 91 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/row-check-sprite.gifbin0 -> 1083 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/row-expand-sprite.gifbin0 -> 955 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/row-over.gifbin0 -> 823 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/row-sel.gifbin0 -> 823 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/sort-hd.gifbin0 -> 2075 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/sort_asc.gifbin0 -> 74 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/sort_desc.gifbin0 -> 73 bytes
-rw-r--r--deluge/ui/web/themes/images/access/grid/wait.gifbin0 -> 1100 bytes
-rw-r--r--deluge/ui/web/themes/images/access/menu/checked.gifbin0 -> 959 bytes
-rw-r--r--deluge/ui/web/themes/images/access/menu/group-checked.gifbin0 -> 856 bytes
-rw-r--r--deluge/ui/web/themes/images/access/menu/item-over.gifbin0 -> 820 bytes
-rw-r--r--deluge/ui/web/themes/images/access/menu/menu-parent.gifbin0 -> 73 bytes
-rw-r--r--deluge/ui/web/themes/images/access/menu/menu.gifbin0 -> 826 bytes
-rw-r--r--deluge/ui/web/themes/images/access/menu/unchecked.gifbin0 -> 941 bytes
-rw-r--r--deluge/ui/web/themes/images/access/panel/corners-sprite.gifbin0 -> 577 bytes
-rw-r--r--deluge/ui/web/themes/images/access/panel/left-right.gifbin0 -> 52 bytes
-rw-r--r--deluge/ui/web/themes/images/access/panel/light-hd.gifbin0 -> 161 bytes
-rw-r--r--deluge/ui/web/themes/images/access/panel/tool-sprite-tpl.gifbin0 -> 971 bytes
-rw-r--r--deluge/ui/web/themes/images/access/panel/tool-sprites.gifbin0 -> 1981 bytes
-rw-r--r--deluge/ui/web/themes/images/access/panel/tools-sprites-trans.gifbin0 -> 2843 bytes
-rw-r--r--deluge/ui/web/themes/images/access/panel/top-bottom.gifbin0 -> 116 bytes
-rw-r--r--deluge/ui/web/themes/images/access/panel/white-corners-sprite.gifbin0 -> 1366 bytes
-rw-r--r--deluge/ui/web/themes/images/access/panel/white-left-right.gifbin0 -> 52 bytes
-rw-r--r--deluge/ui/web/themes/images/access/panel/white-top-bottom.gifbin0 -> 115 bytes
-rw-r--r--deluge/ui/web/themes/images/access/progress/progress-bg.gifbin0 -> 151 bytes
-rw-r--r--deluge/ui/web/themes/images/access/qtip/close.gifbin0 -> 972 bytes
-rw-r--r--deluge/ui/web/themes/images/access/qtip/tip-anchor-sprite.gifbin0 -> 951 bytes
-rw-r--r--deluge/ui/web/themes/images/access/qtip/tip-sprite.gifbin0 -> 3376 bytes
-rw-r--r--deluge/ui/web/themes/images/access/shared/glass-bg.gifbin0 -> 103 bytes
-rw-r--r--deluge/ui/web/themes/images/access/shared/hd-sprite.gifbin0 -> 673 bytes
-rw-r--r--deluge/ui/web/themes/images/access/shared/left-btn.gifbin0 -> 77 bytes
-rw-r--r--deluge/ui/web/themes/images/access/shared/right-btn.gifbin0 -> 79 bytes
-rw-r--r--deluge/ui/web/themes/images/access/sizer/e-handle-dark.gifbin0 -> 248 bytes
-rw-r--r--deluge/ui/web/themes/images/access/sizer/e-handle.gifbin0 -> 753 bytes
-rw-r--r--deluge/ui/web/themes/images/access/sizer/ne-handle-dark.gifbin0 -> 66 bytes
-rw-r--r--deluge/ui/web/themes/images/access/sizer/ne-handle.gifbin0 -> 115 bytes
-rw-r--r--deluge/ui/web/themes/images/access/sizer/nw-handle-dark.gifbin0 -> 66 bytes
-rw-r--r--deluge/ui/web/themes/images/access/sizer/nw-handle.gifbin0 -> 114 bytes
-rw-r--r--deluge/ui/web/themes/images/access/sizer/s-handle-dark.gifbin0 -> 246 bytes
-rw-r--r--deluge/ui/web/themes/images/access/sizer/s-handle.gifbin0 -> 494 bytes
-rw-r--r--deluge/ui/web/themes/images/access/sizer/se-handle-dark.gifbin0 -> 65 bytes
-rw-r--r--deluge/ui/web/themes/images/access/sizer/se-handle.gifbin0 -> 114 bytes
-rw-r--r--deluge/ui/web/themes/images/access/sizer/square.gifbin0 -> 123 bytes
-rw-r--r--deluge/ui/web/themes/images/access/sizer/sw-handle-dark.gifbin0 -> 66 bytes
-rw-r--r--deluge/ui/web/themes/images/access/sizer/sw-handle.gifbin0 -> 116 bytes
-rw-r--r--deluge/ui/web/themes/images/access/slider/slider-bg.pngbin0 -> 185 bytes
-rw-r--r--deluge/ui/web/themes/images/access/slider/slider-thumb.pngbin0 -> 512 bytes
-rw-r--r--deluge/ui/web/themes/images/access/slider/slider-v-bg.pngbin0 -> 154 bytes
-rw-r--r--deluge/ui/web/themes/images/access/slider/slider-v-thumb.pngbin0 -> 481 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tabs/scroll-left.gifbin0 -> 996 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tabs/scroll-right.gifbin0 -> 999 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tabs/tab-btm-inactive-left-bg.gifbin0 -> 130 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tabs/tab-btm-inactive-right-bg.gifbin0 -> 513 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tabs/tab-btm-left-bg.gifbin0 -> 512 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tabs/tab-btm-right-bg.gifbin0 -> 117 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tabs/tab-close.gifbin0 -> 76 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tabs/tab-strip-bg.gifbin0 -> 827 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tabs/tab-strip-btm-bg.gifbin0 -> 70 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tabs/tabs-sprite.gifbin0 -> 1221 bytes
-rw-r--r--deluge/ui/web/themes/images/access/toolbar/bg.gifbin0 -> 82 bytes
-rw-r--r--deluge/ui/web/themes/images/access/toolbar/btn-arrow-light.gifbin0 -> 916 bytes
-rw-r--r--deluge/ui/web/themes/images/access/toolbar/btn-arrow.gifbin0 -> 919 bytes
-rw-r--r--deluge/ui/web/themes/images/access/toolbar/btn-over-bg.gifbin0 -> 837 bytes
-rw-r--r--deluge/ui/web/themes/images/access/toolbar/gray-bg.gifbin0 -> 832 bytes
-rw-r--r--deluge/ui/web/themes/images/access/toolbar/more.gifbin0 -> 67 bytes
-rw-r--r--deluge/ui/web/themes/images/access/toolbar/s-arrow-bo.gifbin0 -> 186 bytes
-rw-r--r--deluge/ui/web/themes/images/access/toolbar/tb-btn-sprite.gifbin0 -> 1127 bytes
-rw-r--r--deluge/ui/web/themes/images/access/toolbar/tb-xl-btn-sprite.gifbin0 -> 1663 bytes
-rw-r--r--deluge/ui/web/themes/images/access/toolbar/tb-xl-sep.gifbin0 -> 810 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/arrows.gifbin0 -> 183 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/drop-add.gifbin0 -> 1001 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/drop-between.gifbin0 -> 907 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/drop-no.gifbin0 -> 949 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/drop-over.gifbin0 -> 911 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/drop-under.gifbin0 -> 911 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/drop-yes.gifbin0 -> 1016 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/elbow-end-minus-nl.gifbin0 -> 86 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/elbow-end-minus.gifbin0 -> 104 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/elbow-end-plus-nl.gifbin0 -> 89 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/elbow-end-plus.gifbin0 -> 108 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/elbow-end.gifbin0 -> 844 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/elbow-line.gifbin0 -> 846 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/elbow-minus-nl.gifbin0 -> 86 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/elbow-minus.gifbin0 -> 106 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/elbow-plus-nl.gifbin0 -> 89 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/elbow-plus.gifbin0 -> 111 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/elbow.gifbin0 -> 850 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/folder-open.gifbin0 -> 342 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/folder.gifbin0 -> 340 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/leaf.gifbin0 -> 945 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/loading.gifbin0 -> 771 bytes
-rw-r--r--deluge/ui/web/themes/images/access/tree/s.gifbin0 -> 43 bytes
-rw-r--r--deluge/ui/web/themes/images/access/window/icon-error.gifbin0 -> 256 bytes
-rw-r--r--deluge/ui/web/themes/images/access/window/icon-info.gifbin0 -> 172 bytes
-rw-r--r--deluge/ui/web/themes/images/access/window/icon-question.gifbin0 -> 217 bytes
-rw-r--r--deluge/ui/web/themes/images/access/window/icon-warning.gifbin0 -> 173 bytes
-rw-r--r--deluge/ui/web/themes/images/access/window/left-corners.pngbin0 -> 137 bytes
-rw-r--r--deluge/ui/web/themes/images/access/window/left-right.pngbin0 -> 83 bytes
-rw-r--r--deluge/ui/web/themes/images/access/window/right-corners.pngbin0 -> 138 bytes
-rw-r--r--deluge/ui/web/themes/images/access/window/top-bottom.pngbin0 -> 105 bytes
-rw-r--r--deluge/ui/web/themes/images/default/box/corners-blue.gifbin0 -> 1010 bytes
-rw-r--r--deluge/ui/web/themes/images/default/box/corners.gifbin0 -> 1005 bytes
-rw-r--r--deluge/ui/web/themes/images/default/box/l-blue.gifbin0 -> 810 bytes
-rw-r--r--deluge/ui/web/themes/images/default/box/l.gifbin0 -> 810 bytes
-rw-r--r--deluge/ui/web/themes/images/default/box/r-blue.gifbin0 -> 810 bytes
-rw-r--r--deluge/ui/web/themes/images/default/box/r.gifbin0 -> 810 bytes
-rw-r--r--deluge/ui/web/themes/images/default/box/tb-blue.gifbin0 -> 851 bytes
-rw-r--r--deluge/ui/web/themes/images/default/box/tb.gifbin0 -> 839 bytes
-rw-r--r--deluge/ui/web/themes/images/default/button/arrow.gifbin0 -> 828 bytes
-rw-r--r--deluge/ui/web/themes/images/default/button/btn.gifbin0 -> 4298 bytes
-rw-r--r--deluge/ui/web/themes/images/default/button/group-cs.gifbin0 -> 2459 bytes
-rw-r--r--deluge/ui/web/themes/images/default/button/group-lr.gifbin0 -> 861 bytes
-rw-r--r--deluge/ui/web/themes/images/default/button/group-tb.gifbin0 -> 846 bytes
-rw-r--r--deluge/ui/web/themes/images/default/button/s-arrow-b-noline.gifbin0 -> 898 bytes
-rw-r--r--deluge/ui/web/themes/images/default/button/s-arrow-b.gifbin0 -> 937 bytes
-rw-r--r--deluge/ui/web/themes/images/default/button/s-arrow-bo.gifbin0 -> 139 bytes
-rw-r--r--deluge/ui/web/themes/images/default/button/s-arrow-noline.gifbin0 -> 863 bytes
-rw-r--r--deluge/ui/web/themes/images/default/button/s-arrow-o.gifbin0 -> 937 bytes
-rw-r--r--deluge/ui/web/themes/images/default/button/s-arrow.gifbin0 -> 937 bytes
-rw-r--r--deluge/ui/web/themes/images/default/dd/drop-add.gifbin0 -> 1001 bytes
-rw-r--r--deluge/ui/web/themes/images/default/dd/drop-no.gifbin0 -> 949 bytes
-rw-r--r--deluge/ui/web/themes/images/default/dd/drop-yes.gifbin0 -> 1016 bytes
-rw-r--r--deluge/ui/web/themes/images/default/editor/tb-sprite.gifbin0 -> 2072 bytes
-rw-r--r--deluge/ui/web/themes/images/default/form/checkbox.gifbin0 -> 2061 bytes
-rw-r--r--deluge/ui/web/themes/images/default/form/clear-trigger.gifbin0 -> 1988 bytes
-rw-r--r--deluge/ui/web/themes/images/default/form/clear-trigger.psdbin0 -> 11804 bytes
-rw-r--r--deluge/ui/web/themes/images/default/form/date-trigger.gifbin0 -> 1603 bytes
-rw-r--r--deluge/ui/web/themes/images/default/form/date-trigger.psdbin0 -> 12377 bytes
-rw-r--r--deluge/ui/web/themes/images/default/form/error-tip-corners.gifbin0 -> 4183 bytes
-rw-r--r--deluge/ui/web/themes/images/default/form/exclamation.gifbin0 -> 996 bytes
-rw-r--r--deluge/ui/web/themes/images/default/form/radio.gifbin0 -> 1746 bytes
-rw-r--r--deluge/ui/web/themes/images/default/form/search-trigger.gifbin0 -> 2182 bytes
-rw-r--r--deluge/ui/web/themes/images/default/form/search-trigger.psdbin0 -> 15601 bytes
-rw-r--r--deluge/ui/web/themes/images/default/form/text-bg.gifbin0 -> 819 bytes
-rw-r--r--deluge/ui/web/themes/images/default/form/trigger-square.gifbin0 -> 1810 bytes
-rw-r--r--deluge/ui/web/themes/images/default/form/trigger-square.psdbin0 -> 36542 bytes
-rw-r--r--deluge/ui/web/themes/images/default/form/trigger-tpl.gifbin0 -> 1487 bytes
-rw-r--r--deluge/ui/web/themes/images/default/form/trigger.gifbin0 -> 1816 bytes
-rw-r--r--deluge/ui/web/themes/images/default/form/trigger.psdbin0 -> 37599 bytes
-rw-r--r--deluge/ui/web/themes/images/default/gradient-bg.gifbin0 -> 1472 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/arrow-left-white.gifbin0 -> 825 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/arrow-right-white.gifbin0 -> 825 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/col-move-bottom.gifbin0 -> 868 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/col-move-top.gifbin0 -> 869 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/columns.gifbin0 -> 962 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/dirty.gifbin0 -> 832 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/done.gifbin0 -> 133 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/drop-no.gifbin0 -> 947 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/drop-yes.gifbin0 -> 860 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/footer-bg.gifbin0 -> 834 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/grid-blue-hd.gifbin0 -> 829 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/grid-blue-split.gifbin0 -> 817 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/grid-hrow.gifbin0 -> 855 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/grid-loading.gifbin0 -> 701 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/grid-split.gifbin0 -> 817 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/grid-vista-hd.gifbin0 -> 829 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/grid3-hd-btn.gifbin0 -> 1229 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/grid3-hrow-over.gifbin0 -> 823 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/grid3-hrow.gifbin0 -> 836 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/grid3-rowheader.gifbin0 -> 43 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/grid3-special-col-bg.gifbin0 -> 837 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/grid3-special-col-sel-bg.gifbin0 -> 843 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/group-by.gifbin0 -> 917 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/group-collapse.gifbin0 -> 881 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/group-expand-sprite.gifbin0 -> 955 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/group-expand.gifbin0 -> 884 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/hd-pop.gifbin0 -> 839 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/hmenu-asc.gifbin0 -> 931 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/hmenu-desc.gifbin0 -> 930 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/hmenu-lock.gifbin0 -> 955 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/hmenu-lock.pngbin0 -> 484 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/hmenu-unlock.gifbin0 -> 971 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/hmenu-unlock.pngbin0 -> 548 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/invalid_line.gifbin0 -> 815 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/loading.gifbin0 -> 771 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/mso-hd.gifbin0 -> 875 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/nowait.gifbin0 -> 884 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/page-first-disabled.gifbin0 -> 925 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/page-first.gifbin0 -> 925 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/page-last-disabled.gifbin0 -> 923 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/page-last.gifbin0 -> 923 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/page-next-disabled.gifbin0 -> 875 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/page-next.gifbin0 -> 875 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/page-prev-disabled.gifbin0 -> 879 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/page-prev.gifbin0 -> 879 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/pick-button.gifbin0 -> 1036 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/refresh-disabled.gifbin0 -> 577 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/refresh.gifbin0 -> 977 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/row-check-sprite.gifbin0 -> 1083 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/row-expand-sprite.gifbin0 -> 955 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/row-over.gifbin0 -> 823 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/row-sel.gifbin0 -> 823 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/sort-hd.gifbin0 -> 1473 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/sort_asc.gifbin0 -> 830 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/sort_desc.gifbin0 -> 833 bytes
-rw-r--r--deluge/ui/web/themes/images/default/grid/wait.gifbin0 -> 1100 bytes
-rw-r--r--deluge/ui/web/themes/images/default/layout/collapse.gifbin0 -> 842 bytes
-rw-r--r--deluge/ui/web/themes/images/default/layout/expand.gifbin0 -> 842 bytes
-rw-r--r--deluge/ui/web/themes/images/default/layout/gradient-bg.gifbin0 -> 1472 bytes
-rw-r--r--deluge/ui/web/themes/images/default/layout/mini-bottom.gifbin0 -> 856 bytes
-rw-r--r--deluge/ui/web/themes/images/default/layout/mini-left.gifbin0 -> 871 bytes
-rw-r--r--deluge/ui/web/themes/images/default/layout/mini-right.gifbin0 -> 872 bytes
-rw-r--r--deluge/ui/web/themes/images/default/layout/mini-top.gifbin0 -> 856 bytes
-rw-r--r--deluge/ui/web/themes/images/default/layout/ns-collapse.gifbin0 -> 842 bytes
-rw-r--r--deluge/ui/web/themes/images/default/layout/ns-expand.gifbin0 -> 843 bytes
-rw-r--r--deluge/ui/web/themes/images/default/layout/panel-close.gifbin0 -> 829 bytes
-rw-r--r--deluge/ui/web/themes/images/default/layout/panel-title-bg.gifbin0 -> 838 bytes
-rw-r--r--deluge/ui/web/themes/images/default/layout/panel-title-light-bg.gifbin0 -> 835 bytes
-rw-r--r--deluge/ui/web/themes/images/default/layout/stick.gifbin0 -> 874 bytes
-rw-r--r--deluge/ui/web/themes/images/default/layout/stuck.gifbin0 -> 92 bytes
-rw-r--r--deluge/ui/web/themes/images/default/layout/tab-close-on.gifbin0 -> 880 bytes
-rw-r--r--deluge/ui/web/themes/images/default/layout/tab-close.gifbin0 -> 859 bytes
-rw-r--r--deluge/ui/web/themes/images/default/menu/checked.gifbin0 -> 959 bytes
-rw-r--r--deluge/ui/web/themes/images/default/menu/group-checked.gifbin0 -> 891 bytes
-rw-r--r--deluge/ui/web/themes/images/default/menu/item-over.gifbin0 -> 820 bytes
-rw-r--r--deluge/ui/web/themes/images/default/menu/menu-parent.gifbin0 -> 854 bytes
-rw-r--r--deluge/ui/web/themes/images/default/menu/menu.gifbin0 -> 834 bytes
-rw-r--r--deluge/ui/web/themes/images/default/menu/unchecked.gifbin0 -> 941 bytes
-rw-r--r--deluge/ui/web/themes/images/default/panel/corners-sprite.gifbin0 -> 1418 bytes
-rw-r--r--deluge/ui/web/themes/images/default/panel/left-right.gifbin0 -> 815 bytes
-rw-r--r--deluge/ui/web/themes/images/default/panel/light-hd.gifbin0 -> 827 bytes
-rw-r--r--deluge/ui/web/themes/images/default/panel/tool-sprite-tpl.gifbin0 -> 971 bytes
-rw-r--r--deluge/ui/web/themes/images/default/panel/tool-sprites.gifbin0 -> 5421 bytes
-rw-r--r--deluge/ui/web/themes/images/default/panel/tools-sprites-trans.gifbin0 -> 2843 bytes
-rw-r--r--deluge/ui/web/themes/images/default/panel/top-bottom.gifbin0 -> 875 bytes
-rw-r--r--deluge/ui/web/themes/images/default/panel/top-bottom.pngbin0 -> 160 bytes
-rw-r--r--deluge/ui/web/themes/images/default/panel/white-corners-sprite.gifbin0 -> 1366 bytes
-rw-r--r--deluge/ui/web/themes/images/default/panel/white-left-right.gifbin0 -> 815 bytes
-rw-r--r--deluge/ui/web/themes/images/default/panel/white-top-bottom.gifbin0 -> 872 bytes
-rw-r--r--deluge/ui/web/themes/images/default/progress/progress-bg.gifbin0 -> 834 bytes
-rw-r--r--deluge/ui/web/themes/images/default/qtip/bg.gifbin0 -> 1091 bytes
-rw-r--r--deluge/ui/web/themes/images/default/qtip/close.gifbin0 -> 972 bytes
-rw-r--r--deluge/ui/web/themes/images/default/qtip/tip-anchor-sprite.gifbin0 -> 951 bytes
-rw-r--r--deluge/ui/web/themes/images/default/qtip/tip-sprite.gifbin0 -> 4271 bytes
-rw-r--r--deluge/ui/web/themes/images/default/s.gifbin0 -> 43 bytes
-rw-r--r--deluge/ui/web/themes/images/default/shadow-c.pngbin0 -> 71 bytes
-rw-r--r--deluge/ui/web/themes/images/default/shadow-lr.pngbin0 -> 88 bytes
-rw-r--r--deluge/ui/web/themes/images/default/shadow.pngbin0 -> 223 bytes
-rw-r--r--deluge/ui/web/themes/images/default/shared/blue-loading.gifbin0 -> 3236 bytes
-rw-r--r--deluge/ui/web/themes/images/default/shared/calendar.gifbin0 -> 979 bytes
-rw-r--r--deluge/ui/web/themes/images/default/shared/glass-bg.gifbin0 -> 873 bytes
-rw-r--r--deluge/ui/web/themes/images/default/shared/hd-sprite.gifbin0 -> 1099 bytes
-rw-r--r--deluge/ui/web/themes/images/default/shared/large-loading.gifbin0 -> 3236 bytes
-rw-r--r--deluge/ui/web/themes/images/default/shared/left-btn.gifbin0 -> 870 bytes
-rw-r--r--deluge/ui/web/themes/images/default/shared/loading-balls.gifbin0 -> 2118 bytes
-rw-r--r--deluge/ui/web/themes/images/default/shared/right-btn.gifbin0 -> 871 bytes
-rw-r--r--deluge/ui/web/themes/images/default/shared/warning.gifbin0 -> 960 bytes
-rw-r--r--deluge/ui/web/themes/images/default/sizer/e-handle-dark.gifbin0 -> 1062 bytes
-rw-r--r--deluge/ui/web/themes/images/default/sizer/e-handle.gifbin0 -> 1586 bytes
-rw-r--r--deluge/ui/web/themes/images/default/sizer/ne-handle-dark.gifbin0 -> 839 bytes
-rw-r--r--deluge/ui/web/themes/images/default/sizer/ne-handle.gifbin0 -> 854 bytes
-rw-r--r--deluge/ui/web/themes/images/default/sizer/nw-handle-dark.gifbin0 -> 839 bytes
-rw-r--r--deluge/ui/web/themes/images/default/sizer/nw-handle.gifbin0 -> 853 bytes
-rw-r--r--deluge/ui/web/themes/images/default/sizer/s-handle-dark.gifbin0 -> 1060 bytes
-rw-r--r--deluge/ui/web/themes/images/default/sizer/s-handle.gifbin0 -> 1318 bytes
-rw-r--r--deluge/ui/web/themes/images/default/sizer/se-handle-dark.gifbin0 -> 838 bytes
-rw-r--r--deluge/ui/web/themes/images/default/sizer/se-handle.gifbin0 -> 853 bytes
-rw-r--r--deluge/ui/web/themes/images/default/sizer/square.gifbin0 -> 864 bytes
-rw-r--r--deluge/ui/web/themes/images/default/sizer/sw-handle-dark.gifbin0 -> 839 bytes
-rw-r--r--deluge/ui/web/themes/images/default/sizer/sw-handle.gifbin0 -> 855 bytes
-rw-r--r--deluge/ui/web/themes/images/default/slider/slider-bg.pngbin0 -> 191 bytes
-rw-r--r--deluge/ui/web/themes/images/default/slider/slider-thumb.pngbin0 -> 626 bytes
-rw-r--r--deluge/ui/web/themes/images/default/slider/slider-v-bg.pngbin0 -> 161 bytes
-rw-r--r--deluge/ui/web/themes/images/default/slider/slider-v-thumb.pngbin0 -> 601 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tabs/scroll-left.gifbin0 -> 1295 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tabs/scroll-right.gifbin0 -> 1300 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tabs/scroller-bg.gifbin0 -> 1100 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tabs/tab-btm-inactive-left-bg.gifbin0 -> 886 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tabs/tab-btm-inactive-right-bg.gifbin0 -> 1386 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tabs/tab-btm-left-bg.gifbin0 -> 1402 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tabs/tab-btm-over-left-bg.gifbin0 -> 191 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tabs/tab-btm-over-right-bg.gifbin0 -> 638 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tabs/tab-btm-right-bg.gifbin0 -> 863 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tabs/tab-close.gifbin0 -> 896 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tabs/tab-strip-bg.gifbin0 -> 835 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tabs/tab-strip-bg.pngbin0 -> 95 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tabs/tab-strip-btm-bg.gifbin0 -> 826 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tabs/tabs-sprite.gifbin0 -> 2120 bytes
-rw-r--r--deluge/ui/web/themes/images/default/toolbar/bg.gifbin0 -> 904 bytes
-rw-r--r--deluge/ui/web/themes/images/default/toolbar/btn-arrow-light.gifbin0 -> 916 bytes
-rw-r--r--deluge/ui/web/themes/images/default/toolbar/btn-arrow.gifbin0 -> 919 bytes
-rw-r--r--deluge/ui/web/themes/images/default/toolbar/btn-over-bg.gifbin0 -> 837 bytes
-rw-r--r--deluge/ui/web/themes/images/default/toolbar/gray-bg.gifbin0 -> 832 bytes
-rw-r--r--deluge/ui/web/themes/images/default/toolbar/more.gifbin0 -> 845 bytes
-rw-r--r--deluge/ui/web/themes/images/default/toolbar/tb-bg.gifbin0 -> 862 bytes
-rw-r--r--deluge/ui/web/themes/images/default/toolbar/tb-btn-sprite.gifbin0 -> 1127 bytes
-rw-r--r--deluge/ui/web/themes/images/default/toolbar/tb-xl-btn-sprite.gifbin0 -> 1663 bytes
-rw-r--r--deluge/ui/web/themes/images/default/toolbar/tb-xl-sep.gifbin0 -> 810 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/arrows.gifbin0 -> 617 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/drop-add.gifbin0 -> 1001 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/drop-between.gifbin0 -> 907 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/drop-no.gifbin0 -> 949 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/drop-over.gifbin0 -> 911 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/drop-under.gifbin0 -> 911 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/drop-yes.gifbin0 -> 1016 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/elbow-end-minus-nl.gifbin0 -> 898 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/elbow-end-minus.gifbin0 -> 905 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/elbow-end-plus-nl.gifbin0 -> 900 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/elbow-end-plus.gifbin0 -> 907 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/elbow-end.gifbin0 -> 844 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/elbow-line.gifbin0 -> 846 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/elbow-minus-nl.gifbin0 -> 898 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/elbow-minus.gifbin0 -> 908 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/elbow-plus-nl.gifbin0 -> 900 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/elbow-plus.gifbin0 -> 910 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/elbow.gifbin0 -> 850 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/folder-open.gifbin0 -> 956 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/folder.gifbin0 -> 952 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/leaf.gifbin0 -> 945 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/loading.gifbin0 -> 771 bytes
-rw-r--r--deluge/ui/web/themes/images/default/tree/s.gifbin0 -> 43 bytes
-rw-r--r--deluge/ui/web/themes/images/default/window/icon-error.gifbin0 -> 1669 bytes
-rw-r--r--deluge/ui/web/themes/images/default/window/icon-info.gifbin0 -> 1586 bytes
-rw-r--r--deluge/ui/web/themes/images/default/window/icon-question.gifbin0 -> 1607 bytes
-rw-r--r--deluge/ui/web/themes/images/default/window/icon-warning.gifbin0 -> 1483 bytes
-rw-r--r--deluge/ui/web/themes/images/default/window/left-corners.pngbin0 -> 137 bytes
-rw-r--r--deluge/ui/web/themes/images/default/window/left-corners.psdbin0 -> 15576 bytes
-rw-r--r--deluge/ui/web/themes/images/default/window/left-right.pngbin0 -> 84 bytes
-rw-r--r--deluge/ui/web/themes/images/default/window/left-right.psdbin0 -> 24046 bytes
-rw-r--r--deluge/ui/web/themes/images/default/window/right-corners.pngbin0 -> 138 bytes
-rw-r--r--deluge/ui/web/themes/images/default/window/right-corners.psdbin0 -> 15530 bytes
-rw-r--r--deluge/ui/web/themes/images/default/window/top-bottom.pngbin0 -> 119 bytes
-rw-r--r--deluge/ui/web/themes/images/default/window/top-bottom.psdbin0 -> 32128 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/button/btn-arrow.gifbin0 -> 870 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/button/btn-sprite.gifbin0 -> 1222 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/button/btn.gifbin0 -> 3319 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/button/group-cs.gifbin0 -> 2459 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/button/group-lr.gifbin0 -> 861 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/button/group-tb.gifbin0 -> 846 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/button/s-arrow-bo.gifbin0 -> 123 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/button/s-arrow-o.gifbin0 -> 139 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/form/clear-trigger.gifbin0 -> 1425 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/form/date-trigger.gifbin0 -> 929 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/form/search-trigger.gifbin0 -> 2220 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/form/trigger-square.gifbin0 -> 1071 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/form/trigger.gifbin0 -> 1080 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/gradient-bg.gifbin0 -> 1472 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/col-move-bottom.gifbin0 -> 177 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/col-move-top.gifbin0 -> 178 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/grid3-hd-btn.gifbin0 -> 482 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/grid3-hrow-over.gifbin0 -> 56 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/grid3-hrow-over2.gifbin0 -> 107 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/grid3-hrow.gifbin0 -> 836 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/grid3-hrow2.gifbin0 -> 107 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/grid3-special-col-bg.gifbin0 -> 158 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/grid3-special-col-bg2.gifbin0 -> 158 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/grid3-special-col-sel-bg.gifbin0 -> 158 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/group-collapse.gifbin0 -> 136 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/group-expand-sprite.gifbin0 -> 196 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/group-expand.gifbin0 -> 138 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/page-first.gifbin0 -> 327 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/page-last.gifbin0 -> 325 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/page-next.gifbin0 -> 183 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/page-prev.gifbin0 -> 186 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/refresh.gifbin0 -> 570 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/row-expand-sprite.gifbin0 -> 196 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/sort-hd.gifbin0 -> 2731 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/sort_asc.gifbin0 -> 59 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/grid/sort_desc.gifbin0 -> 59 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/menu/group-checked.gifbin0 -> 295 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/menu/item-over-disabled.gifbin0 -> 49 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/menu/item-over.gifbin0 -> 850 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/menu/menu-parent.gifbin0 -> 165 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/panel/corners-sprite.gifbin0 -> 1402 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/panel/left-right.gifbin0 -> 815 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/panel/light-hd.gifbin0 -> 827 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/panel/tool-sprite-tpl.gifbin0 -> 971 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/panel/tool-sprites.gifbin0 -> 5835 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/panel/tools-sprites-trans.gifbin0 -> 1981 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/panel/top-bottom.gifbin0 -> 871 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/panel/top-bottom.pngbin0 -> 160 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/panel/white-corners-sprite.gifbin0 -> 1365 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/panel/white-left-right.gifbin0 -> 815 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/panel/white-top-bottom.gifbin0 -> 860 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/progress/progress-bg.gifbin0 -> 107 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/qtip/bg.gifbin0 -> 1024 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/qtip/close.gifbin0 -> 972 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/qtip/tip-anchor-sprite.gifbin0 -> 164 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/qtip/tip-sprite.gifbin0 -> 3241 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/s.gifbin0 -> 43 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/shared/hd-sprite.gifbin0 -> 305 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/shared/left-btn.gifbin0 -> 106 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/shared/right-btn.gifbin0 -> 107 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/sizer/e-handle.gifbin0 -> 753 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/sizer/ne-handle.gifbin0 -> 128 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/sizer/nw-handle.gifbin0 -> 114 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/sizer/s-handle.gifbin0 -> 494 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/sizer/se-handle.gifbin0 -> 114 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/sizer/square.gifbin0 -> 123 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/sizer/sw-handle.gifbin0 -> 116 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/slider/slider-thumb.pngbin0 -> 376 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/slider/slider-v-thumb.pngbin0 -> 333 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tabs/scroll-left.gifbin0 -> 1260 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tabs/scroll-right.gifbin0 -> 1269 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tabs/scroller-bg.gifbin0 -> 1090 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tabs/tab-btm-inactive-left-bg.gifbin0 -> 881 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tabs/tab-btm-inactive-right-bg.gifbin0 -> 1383 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tabs/tab-btm-left-bg.gifbin0 -> 1402 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tabs/tab-btm-over-left-bg.gifbin0 -> 189 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tabs/tab-btm-over-right-bg.gifbin0 -> 635 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tabs/tab-btm-right-bg.gifbin0 -> 863 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tabs/tab-close.gifbin0 -> 896 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tabs/tab-strip-bg.gifbin0 -> 835 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tabs/tab-strip-bg.pngbin0 -> 95 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tabs/tab-strip-btm-bg.gifbin0 -> 826 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tabs/tabs-sprite.gifbin0 -> 2109 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/toolbar/bg.gifbin0 -> 854 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/toolbar/btn-arrow-light.gifbin0 -> 916 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/toolbar/btn-arrow.gifbin0 -> 919 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/toolbar/btn-over-bg.gifbin0 -> 837 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/toolbar/gray-bg.gifbin0 -> 815 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/toolbar/more.gifbin0 -> 67 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/toolbar/tb-bg.gifbin0 -> 862 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/toolbar/tb-btn-sprite.gifbin0 -> 1021 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tree/arrows.gifbin0 -> 407 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tree/elbow-end-minus-nl.gifbin0 -> 149 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tree/elbow-end-minus.gifbin0 -> 154 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tree/elbow-end-plus-nl.gifbin0 -> 151 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/tree/elbow-end-plus.gifbin0 -> 156 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/window/icon-error.gifbin0 -> 1669 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/window/icon-info.gifbin0 -> 1586 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/window/icon-question.gifbin0 -> 1607 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/window/icon-warning.gifbin0 -> 1483 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/window/left-corners.pngbin0 -> 205 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/window/left-right.pngbin0 -> 75 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/window/right-corners.pngbin0 -> 204 bytes
-rw-r--r--deluge/ui/web/themes/images/gray/window/top-bottom.pngbin0 -> 108 bytes
-rw-r--r--deluge/ui/web/web.py91
1102 files changed, 148147 insertions, 0 deletions
diff --git a/deluge/ui/__init__.py b/deluge/ui/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/deluge/ui/__init__.py
diff --git a/deluge/ui/client.py b/deluge/ui/client.py
new file mode 100644
index 0000000..686f962
--- /dev/null
+++ b/deluge/ui/client.py
@@ -0,0 +1,838 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import subprocess
+import sys
+
+from twisted.internet import defer, reactor, ssl
+from twisted.internet.protocol import ClientFactory
+
+from deluge import error
+from deluge.common import get_localhost_auth, get_version
+from deluge.decorators import deprecated
+from deluge.transfer import DelugeTransferProtocol
+
+RPC_RESPONSE = 1
+RPC_ERROR = 2
+RPC_EVENT = 3
+
+log = logging.getLogger(__name__)
+
+
+def format_kwargs(kwargs):
+ return ', '.join([key + '=' + str(value) for key, value in kwargs.items()])
+
+
+class DelugeRPCRequest(object):
+ """
+ This object is created whenever there is a RPCRequest to be sent to the
+ daemon. It is generally only used by the DaemonProxy's call method.
+ """
+
+ request_id = None
+ method = None
+ args = None
+ kwargs = None
+
+ def __repr__(self):
+ """
+ Returns a string of the RPCRequest in the following form:
+ method(arg, kwarg=foo, ...)
+ """
+ s = self.method + '('
+ if self.args:
+ s += ', '.join([str(x) for x in self.args])
+ if self.kwargs:
+ if self.args:
+ s += ', '
+ s += format_kwargs(self.kwargs)
+ s += ')'
+
+ return s
+
+ def format_message(self):
+ """
+ Returns a properly formatted RPCRequest based on the properties. Will
+ raise a TypeError if the properties haven't been set yet.
+
+ :returns: a properly formated RPCRequest
+ """
+ if (
+ self.request_id is None
+ or self.method is None
+ or self.args is None
+ or self.kwargs is None
+ ):
+ raise TypeError(
+ 'You must set the properties of this object before calling format_message!'
+ )
+
+ return (self.request_id, self.method, self.args, self.kwargs)
+
+
+class DelugeRPCProtocol(DelugeTransferProtocol):
+ def connectionMade(self): # NOQA: N802
+ self.__rpc_requests = {}
+ # Set the protocol in the daemon so it can send data
+ self.factory.daemon.protocol = self
+ # Get the address of the daemon that we've connected to
+ peer = self.transport.getPeer()
+ self.factory.daemon.host = peer.host
+ self.factory.daemon.port = peer.port
+ self.factory.daemon.connected = True
+ log.debug('Connected to daemon at %s:%s..', peer.host, peer.port)
+ self.factory.daemon.connect_deferred.callback((peer.host, peer.port))
+
+ def message_received(self, request):
+ """
+ This method is called whenever we receive a message from the daemon.
+
+ :param request: a tuple that should be either a RPCResponse, RCPError or RPCSignal
+
+ """
+ if not isinstance(request, tuple):
+ log.debug('Received invalid message: type is not tuple')
+ return
+ if len(request) < 3:
+ log.debug(
+ 'Received invalid message: number of items in ' 'response is %s',
+ len(request),
+ )
+ return
+
+ message_type = request[0]
+
+ if message_type == RPC_EVENT:
+ event = request[1]
+ # log.debug('Received RPCEvent: %s', event)
+ # A RPCEvent was received from the daemon so run any handlers
+ # associated with it.
+ if event in self.factory.event_handlers:
+ for handler in self.factory.event_handlers[event]:
+ reactor.callLater(0, handler, *request[2])
+ return
+
+ request_id = request[1]
+
+ # We get the Deferred object for this request_id to either run the
+ # callbacks or the errbacks dependent on the response from the daemon.
+ d = self.factory.daemon.pop_deferred(request_id)
+
+ if message_type == RPC_RESPONSE:
+ # Run the callbacks registered with this Deferred object
+ d.callback(request[2])
+ elif message_type == RPC_ERROR:
+ # Recreate exception and errback'it
+ try:
+ # The exception class is located in deluge.error
+ try:
+ exception_cls = getattr(error, request[2])
+ exception = exception_cls(*request[3], **request[4])
+ except TypeError:
+ log.warning(
+ 'Received invalid RPC_ERROR (Old daemon?): %s', request[2]
+ )
+ return
+
+ # Ideally we would chain the deferreds instead of instance
+ # checking just to log them. But, that would mean that any
+ # errback on the fist deferred should returns it's failure
+ # so it could pass back to the 2nd deferred on the chain. But,
+ # that does not always happen.
+ # So, just do some instance checking and just log rpc error at
+ # diferent levels.
+ r = self.__rpc_requests[request_id]
+ msg = 'RPCError Message Received!'
+ msg += '\n' + '-' * 80
+ msg += '\n' + 'RPCRequest: ' + r.__repr__()
+ msg += '\n' + '-' * 80
+ if isinstance(exception, error.WrappedException):
+ msg += '\n' + exception.type + '\n' + exception.message + ': '
+ msg += exception.traceback
+ else:
+ msg += '\n' + request[5] + '\n' + request[2] + ': '
+ msg += str(exception)
+ msg += '\n' + '-' * 80
+
+ if not isinstance(exception, error._ClientSideRecreateError):
+ # Let's log these as errors
+ log.error(msg)
+ else:
+ # The rest just get's logged in debug level, just to log
+ # what's happening
+ log.debug(msg)
+ except Exception:
+ import traceback
+
+ log.error(
+ 'Failed to handle RPC_ERROR (Old daemon?): %s\nLocal error: %s',
+ request[2],
+ traceback.format_exc(),
+ )
+ d.errback(exception)
+ del self.__rpc_requests[request_id]
+
+ def send_request(self, request):
+ """
+ Sends a RPCRequest to the server.
+
+ :param request: RPCRequest
+
+ """
+ try:
+ # Store the DelugeRPCRequest object just in case a RPCError is sent in
+ # response to this request. We use the extra information when printing
+ # out the error for debugging purposes.
+ self.__rpc_requests[request.request_id] = request
+ # log.debug('Sending RPCRequest %s: %s', request.request_id, request)
+ # Send the request in a tuple because multiple requests can be sent at once
+ self.transfer_message((request.format_message(),))
+ except Exception as ex:
+ log.warning('Error occurred when sending message: %s', ex)
+
+
+class DelugeRPCClientFactory(ClientFactory):
+ protocol = DelugeRPCProtocol
+
+ def __init__(self, daemon, event_handlers):
+ self.daemon = daemon
+ self.event_handlers = event_handlers
+
+ def startedConnecting(self, connector): # NOQA: N802
+ log.debug('Connecting to daemon at "%s:%s"...', connector.host, connector.port)
+
+ def clientConnectionFailed(self, connector, reason): # NOQA: N802
+ log.debug(
+ 'Connection to daemon at "%s:%s" failed: %s',
+ connector.host,
+ connector.port,
+ reason.value,
+ )
+ self.daemon.connect_deferred.errback(reason)
+
+ def clientConnectionLost(self, connector, reason): # NOQA: N802
+ log.debug(
+ 'Connection lost to daemon at "%s:%s" reason: %s',
+ connector.host,
+ connector.port,
+ reason.value,
+ )
+ self.daemon.host = None
+ self.daemon.port = None
+ self.daemon.username = None
+ self.daemon.connected = False
+
+ if (
+ self.daemon.disconnect_deferred
+ and not self.daemon.disconnect_deferred.called
+ ):
+ self.daemon.disconnect_deferred.callback(reason.value)
+ self.daemon.disconnect_deferred = None
+
+ if self.daemon.disconnect_callback:
+ self.daemon.disconnect_callback()
+
+
+class DaemonProxy(object):
+ pass
+
+
+class DaemonSSLProxy(DaemonProxy):
+ def __init__(self, event_handlers=None):
+ if event_handlers is None:
+ event_handlers = {}
+ self.__factory = DelugeRPCClientFactory(self, event_handlers)
+ self.__factory.noisy = False
+ self.__request_counter = 0
+ self.__deferred = {}
+
+ # This is set when a connection is made to the daemon
+ self.protocol = None
+
+ # This is set when a connection is made
+ self.host = None
+ self.port = None
+ self.username = None
+ self.authentication_level = 0
+
+ self.connected = False
+
+ self.disconnect_deferred = None
+ self.disconnect_callback = None
+
+ self.auth_levels_mapping = None
+ self.auth_levels_mapping_reverse = None
+
+ def connect(self, host, port):
+ """
+ Connects to a daemon at host:port
+
+ :param host: str, the host to connect to
+ :param port: int, the listening port on the daemon
+
+ :returns: twisted.Deferred
+
+ """
+ log.debug('sslproxy.connect()')
+ self.host = host
+ self.port = port
+ self.__connector = reactor.connectSSL(
+ self.host, self.port, self.__factory, ssl.ClientContextFactory()
+ )
+ self.connect_deferred = defer.Deferred()
+ self.daemon_info_deferred = defer.Deferred()
+
+ # Upon connect we do a 'daemon.login' RPC
+ self.connect_deferred.addCallback(self.__on_connect)
+ self.connect_deferred.addErrback(self.__on_connect_fail)
+
+ return self.daemon_info_deferred
+
+ def disconnect(self):
+ log.debug('sslproxy.disconnect()')
+ self.disconnect_deferred = defer.Deferred()
+ self.__connector.disconnect()
+ return self.disconnect_deferred
+
+ def call(self, method, *args, **kwargs):
+ """
+ Makes a RPCRequest to the daemon. All methods should be in the form of
+ 'component.method'.
+
+ :params method: str, the method to call in the form of 'component.method'
+ :params args: the arguments to call the remote method with
+ :params kwargs: the keyword arguments to call the remote method with
+
+ :return: a twisted.Deferred object that will be activated when a RPCResponse
+ or RPCError is received from the daemon
+
+ """
+ # Create the DelugeRPCRequest to pass to protocol.send_request()
+ request = DelugeRPCRequest()
+ request.request_id = self.__request_counter
+ request.method = method
+ request.args = args
+ request.kwargs = kwargs
+ # Send the request to the server
+ self.protocol.send_request(request)
+ # Create a Deferred object to return and add a default errback to print
+ # the error.
+ d = defer.Deferred()
+
+ # Store the Deferred until we receive a response from the daemon
+ self.__deferred[self.__request_counter] = d
+
+ # Increment the request counter since we don't want to use the same one
+ # before a response is received.
+ self.__request_counter += 1
+
+ return d
+
+ def pop_deferred(self, request_id):
+ """
+ Pops a Deferred object. This is generally called once we receive the
+ reply we were waiting on from the server.
+
+ :param request_id: the request_id of the Deferred to pop
+ :type request_id: int
+
+ """
+ return self.__deferred.pop(request_id)
+
+ def register_event_handler(self, event, handler):
+ """
+ Registers a handler function to be called when `:param:event` is received
+ from the daemon.
+
+ :param event: the name of the event to handle
+ :type event: str
+ :param handler: the function to be called when `:param:event`
+ is emitted from the daemon
+ :type handler: function
+
+ """
+ if event not in self.__factory.event_handlers:
+ # This is a new event to handle, so we need to tell the daemon
+ # that we're interested in receiving this type of event
+ self.__factory.event_handlers[event] = []
+ if self.connected:
+ self.call('daemon.set_event_interest', [event])
+
+ # Only add the handler if it's not already registered
+ if handler not in self.__factory.event_handlers[event]:
+ self.__factory.event_handlers[event].append(handler)
+
+ def deregister_event_handler(self, event, handler):
+ """
+ Deregisters a event handler.
+
+ :param event: the name of the event
+ :type event: str
+ :param handler: the function registered
+ :type handler: function
+
+ """
+ if (
+ event in self.__factory.event_handlers
+ and handler in self.__factory.event_handlers[event]
+ ):
+ self.__factory.event_handlers[event].remove(handler)
+
+ def __on_connect(self, result):
+ log.debug('__on_connect called')
+
+ def on_info(daemon_info):
+ self.daemon_info = daemon_info
+ log.debug('Got info from daemon: %s', daemon_info)
+ self.daemon_info_deferred.callback(daemon_info)
+
+ def on_info_fail(reason):
+ log.debug('Failed to get info from daemon')
+ log.exception(reason)
+ self.daemon_info_deferred.errback(reason)
+
+ self.call('daemon.info').addCallback(on_info).addErrback(on_info_fail)
+ return self.daemon_info_deferred
+
+ def __on_connect_fail(self, reason):
+ self.daemon_info_deferred.errback(reason)
+
+ def authenticate(self, username, password):
+ log.debug('%s.authenticate: %s', self.__class__.__name__, username)
+ login_deferred = defer.Deferred()
+ d = self.call('daemon.login', username, password, client_version=get_version())
+ d.addCallbacks(
+ self.__on_login,
+ self.__on_login_fail,
+ callbackArgs=[username, login_deferred],
+ errbackArgs=[login_deferred],
+ )
+ return login_deferred
+
+ def __on_login(self, result, username, login_deferred):
+ log.debug('__on_login called: %s %s', username, result)
+ self.username = username
+ self.authentication_level = result
+ # We need to tell the daemon what events we're interested in receiving
+ if self.__factory.event_handlers:
+ self.call('daemon.set_event_interest', list(self.__factory.event_handlers))
+ self.call('core.get_auth_levels_mappings').addCallback(
+ self.__on_auth_levels_mappings
+ )
+
+ login_deferred.callback(result)
+
+ def __on_login_fail(self, result, login_deferred):
+ login_deferred.errback(result)
+
+ def __on_auth_levels_mappings(self, result):
+ auth_levels_mapping, auth_levels_mapping_reverse = result
+ self.auth_levels_mapping = auth_levels_mapping
+ self.auth_levels_mapping_reverse = auth_levels_mapping_reverse
+
+ def set_disconnect_callback(self, cb):
+ """
+ Set a function to be called when the connection to the daemon is lost
+ for any reason.
+ """
+ self.disconnect_callback = cb
+
+ def get_bytes_recv(self):
+ return self.protocol.get_bytes_recv()
+
+ def get_bytes_sent(self):
+ return self.protocol.get_bytes_sent()
+
+
+class DaemonStandaloneProxy(DaemonProxy):
+ def __init__(self, event_handlers=None):
+ if event_handlers is None:
+ event_handlers = {}
+ from deluge.core import daemon
+
+ self.__daemon = daemon.Daemon(standalone=True)
+ self.__daemon.start()
+ log.debug('daemon created!')
+ self.connected = True
+ self.host = 'localhost'
+ self.port = 58846
+ # Running in standalone mode, it's safe to import auth level
+ from deluge.core.authmanager import (
+ AUTH_LEVEL_ADMIN,
+ AUTH_LEVELS_MAPPING,
+ AUTH_LEVELS_MAPPING_REVERSE,
+ )
+
+ self.username = 'localclient'
+ self.authentication_level = AUTH_LEVEL_ADMIN
+ self.auth_levels_mapping = AUTH_LEVELS_MAPPING
+ self.auth_levels_mapping_reverse = AUTH_LEVELS_MAPPING_REVERSE
+ # Register the event handlers
+ for event in event_handlers:
+ for handler in event_handlers[event]:
+ self.__daemon.core.eventmanager.register_event_handler(event, handler)
+
+ def disconnect(self):
+ self.connected = False
+ self.__daemon = None
+
+ def call(self, method, *args, **kwargs):
+ # log.debug('call: %s %s %s', method, args, kwargs)
+
+ import copy
+
+ try:
+ m = self.__daemon.rpcserver.get_object_method(method)
+ except Exception as ex:
+ log.exception(ex)
+ return defer.fail(ex)
+ else:
+ return defer.maybeDeferred(m, *copy.deepcopy(args), **copy.deepcopy(kwargs))
+
+ def register_event_handler(self, event, handler):
+ """
+ Registers a handler function to be called when `:param:event` is
+ received from the daemon.
+
+ :param event: the name of the event to handle
+ :type event: str
+ :param handler: the function to be called when `:param:event`
+ is emitted from the daemon
+ :type handler: function
+
+ """
+ self.__daemon.core.eventmanager.register_event_handler(event, handler)
+
+ def deregister_event_handler(self, event, handler):
+ """
+ Deregisters a event handler.
+
+ :param event: the name of the event
+ :type event: str
+ :param handler: the function registered
+ :type handler: function
+
+ """
+ self.__daemon.core.eventmanager.deregister_event_handler(event, handler)
+
+
+class DottedObject(object):
+ """
+ This is used for dotted name calls to client
+ """
+
+ def __init__(self, daemon, method):
+ self.daemon = daemon
+ self.base = method
+
+ def __call__(self, *args, **kwargs):
+ raise Exception('You must make calls in the form of "component.method"')
+
+ def __getattr__(self, name):
+ return RemoteMethod(self.daemon, self.base + '.' + name)
+
+
+class RemoteMethod(DottedObject):
+ """
+ This is used when something like 'client.core.get_something()' is attempted.
+ """
+
+ def __call__(self, *args, **kwargs):
+ return self.daemon.call(self.base, *args, **kwargs)
+
+
+class Client(object):
+ """
+ This class is used to connect to a daemon process and issue RPC requests.
+ """
+
+ __event_handlers = {}
+
+ def __init__(self):
+ self._daemon_proxy = None
+ self.disconnect_callback = None
+ self.__started_standalone = False
+
+ def connect(
+ self,
+ host='127.0.0.1',
+ port=58846,
+ username='',
+ password='',
+ skip_authentication=False,
+ ):
+ """
+ Connects to a daemon process.
+
+ :param host: str, the hostname of the daemon
+ :param port: int, the port of the daemon
+ :param username: str, the username to login with
+ :param password: str, the password to login with
+
+ :returns: a Deferred object that will be called once the connection
+ has been established or fails
+ """
+
+ self._daemon_proxy = DaemonSSLProxy(dict(self.__event_handlers))
+ self._daemon_proxy.set_disconnect_callback(self.__on_disconnect)
+
+ d = self._daemon_proxy.connect(host, port)
+
+ def on_connected(daemon_version):
+ log.debug('on_connected. Daemon version: %s', daemon_version)
+ return daemon_version
+
+ def on_connect_fail(reason):
+ log.debug('on_connect_fail: %s', reason)
+ self.disconnect()
+ return reason
+
+ def on_authenticate(result, daemon_info):
+ log.debug('Authentication successful: %s', result)
+ return result
+
+ def on_authenticate_fail(reason):
+ log.debug('Failed to authenticate: %s', reason.value)
+ return reason
+
+ def authenticate(daemon_version, username, password):
+ if not username and host in ('127.0.0.1', 'localhost'):
+ # No username provided and it's localhost, so attempt to get credentials from auth file.
+ username, password = get_localhost_auth()
+
+ d = self._daemon_proxy.authenticate(username, password)
+ d.addCallback(on_authenticate, daemon_version)
+ d.addErrback(on_authenticate_fail)
+ return d
+
+ d.addCallback(on_connected)
+ d.addErrback(on_connect_fail)
+ if not skip_authentication:
+ d.addCallback(authenticate, username, password)
+ return d
+
+ def disconnect(self):
+ """
+ Disconnects from the daemon.
+ """
+ if self.is_standalone():
+ self._daemon_proxy.disconnect()
+ self.stop_standalone()
+ return defer.succeed(True)
+
+ if self._daemon_proxy:
+ return self._daemon_proxy.disconnect()
+
+ def start_standalone(self):
+ """
+ Starts a daemon in the same process as the client.
+ """
+ self._daemon_proxy = DaemonStandaloneProxy(self.__event_handlers)
+ self.__started_standalone = True
+
+ def stop_standalone(self):
+ """
+ Stops the daemon process in the client.
+ """
+ self._daemon_proxy = None
+ self.__started_standalone = False
+
+ @deprecated
+ def start_classic_mode(self):
+ """Deprecated: Use start_standalone"""
+ self.start_standalone()
+
+ @deprecated
+ def stop_classic_mode(self):
+ """Deprecated: Use stop_standalone"""
+ self.stop_standalone()
+
+ def start_daemon(self, port, config):
+ """Starts a daemon process.
+
+ Args:
+ port (int): Port for the daemon to listen on.
+ config (str): Config path to pass to daemon.
+
+ Returns:
+ bool: True is successfully started the daemon, False otherwise.
+
+ """
+ # subprocess.popen does not work with unicode args (with non-ascii characters) on windows
+ config = config.encode(sys.getfilesystemencoding())
+ try:
+ subprocess.Popen(['deluged', '--port=%s' % port, b'--config=%s' % config])
+ except OSError as ex:
+ from errno import ENOENT
+
+ if ex.errno == ENOENT:
+ log.error(
+ _(
+ 'Deluge cannot find the `deluged` executable, check that '
+ 'the deluged package is installed, or added to your PATH.'
+ )
+ )
+ else:
+ log.exception(ex)
+ except Exception as ex:
+ log.error('Unable to start daemon!')
+ log.exception(ex)
+ else:
+ return True
+ return False
+
+ def is_localhost(self):
+ """
+ Checks if the current connected host is a localhost or not.
+
+ :returns: bool, True if connected to a localhost
+
+ """
+ if (
+ self._daemon_proxy
+ and self._daemon_proxy.host in ('127.0.0.1', 'localhost')
+ or isinstance(self._daemon_proxy, DaemonStandaloneProxy)
+ ):
+ return True
+ return False
+
+ def is_standalone(self):
+ """
+ Checks to see if the client has been started in standalone mode.
+
+ :returns: bool, True if in standalone mode
+
+ """
+ return self.__started_standalone
+
+ @deprecated
+ def is_classicmode(self):
+ """Deprecated: Use is_standalone"""
+ self.is_standalone()
+
+ def connected(self):
+ """
+ Check to see if connected to a daemon.
+
+ :returns: bool, True if connected
+
+ """
+ return self._daemon_proxy.connected if self._daemon_proxy else False
+
+ def connection_info(self):
+ """
+ Get some info about the connection or return None if not connected.
+
+ :returns: a tuple of (host, port, username) or None if not connected
+ """
+ if self.connected():
+ return (
+ self._daemon_proxy.host,
+ self._daemon_proxy.port,
+ self._daemon_proxy.username,
+ )
+
+ return None
+
+ def register_event_handler(self, event, handler):
+ """
+ Registers a handler that will be called when an event is received from the daemon.
+
+ :params event: str, the event to handle
+ :params handler: func, the handler function, f(args)
+ """
+ if event not in self.__event_handlers:
+ self.__event_handlers[event] = []
+ self.__event_handlers[event].append(handler)
+ # We need to replicate this in the daemon proxy
+ if self._daemon_proxy:
+ self._daemon_proxy.register_event_handler(event, handler)
+
+ def deregister_event_handler(self, event, handler):
+ """
+ Deregisters a event handler.
+
+ :param event: str, the name of the event
+ :param handler: function, the function registered
+
+ """
+ if event in self.__event_handlers and handler in self.__event_handlers[event]:
+ self.__event_handlers[event].remove(handler)
+ if self._daemon_proxy:
+ self._daemon_proxy.deregister_event_handler(event, handler)
+
+ def force_call(self, block=False):
+ # no-op for now.. we'll see if we need this in the future
+ pass
+
+ def __getattr__(self, method):
+ return DottedObject(self._daemon_proxy, method)
+
+ def set_disconnect_callback(self, cb):
+ """
+ Set a function to be called whenever the client is disconnected from
+ the daemon for any reason.
+ """
+ self.disconnect_callback = cb
+
+ def __on_disconnect(self):
+ if self.disconnect_callback:
+ self.disconnect_callback()
+
+ def get_bytes_recv(self):
+ """
+ Returns the number of bytes received from the daemon.
+
+ :returns: the number of bytes received
+ :rtype: int
+ """
+ return self._daemon_proxy.get_bytes_recv()
+
+ def get_bytes_sent(self):
+ """
+ Returns the number of bytes sent to the daemon.
+
+ :returns: the number of bytes sent
+ :rtype: int
+ """
+ return self._daemon_proxy.get_bytes_sent()
+
+ def get_auth_user(self):
+ """
+ Returns the current authenticated username.
+
+ :returns: the authenticated username
+ :rtype: str
+ """
+ return self._daemon_proxy.username
+
+ def get_auth_level(self):
+ """
+ Returns the authentication level the daemon returned upon authentication.
+
+ :returns: the authentication level
+ :rtype: int
+ """
+ return self._daemon_proxy.authentication_level
+
+ @property
+ def auth_levels_mapping(self):
+ return self._daemon_proxy.auth_levels_mapping
+
+ @property
+ def auth_levels_mapping_reverse(self):
+ return self._daemon_proxy.auth_levels_mapping_reverse
+
+
+# This is the object clients will use
+client = Client()
diff --git a/deluge/ui/common.py b/deluge/ui/common.py
new file mode 100644
index 0000000..21bcafd
--- /dev/null
+++ b/deluge/ui/common.py
@@ -0,0 +1,564 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) Damien Churchill 2008-2009 <damoxc@gmail.com>
+# Copyright (C) Andrew Resch 2009 <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+"""
+The ui common module contains methods and classes that are deemed useful for all the interfaces.
+"""
+from __future__ import unicode_literals
+
+import logging
+import os
+from binascii import hexlify
+from hashlib import sha1 as sha
+
+from deluge import bencode
+from deluge.common import decode_bytes
+
+log = logging.getLogger(__name__)
+
+
+# Dummy translation dicts so the text is available for Translators.
+#
+# All entries in deluge.common.TORRENT_STATE should be added here.
+#
+# No need to import these, just simply use the `_()` function around a status variable.
+def _(message):
+ return message
+
+
+STATE_TRANSLATION = {
+ 'All': _('All'),
+ 'Active': _('Active'),
+ 'Allocating': _('Allocating'),
+ 'Checking': _('Checking'),
+ 'Downloading': _('Downloading'),
+ 'Seeding': _('Seeding'),
+ 'Paused': _('Paused'),
+ 'Queued': _('Queued'),
+ 'Error': _('Error'),
+}
+
+TORRENT_DATA_FIELD = {
+ 'queue': {'name': '#', 'status': ['queue']},
+ 'name': {'name': _('Name'), 'status': ['state', 'name']},
+ 'progress_state': {'name': _('Progress'), 'status': ['progress', 'state']},
+ 'state': {'name': _('State'), 'status': ['state']},
+ 'progress': {'name': _('Progress'), 'status': ['progress']},
+ 'size': {'name': _('Size'), 'status': ['total_wanted']},
+ 'downloaded': {'name': _('Downloaded'), 'status': ['all_time_download']},
+ 'uploaded': {'name': _('Uploaded'), 'status': ['total_uploaded']},
+ 'remaining': {'name': _('Remaining'), 'status': ['total_remaining']},
+ 'ratio': {'name': _('Ratio'), 'status': ['ratio']},
+ 'download_speed': {'name': _('Down Speed'), 'status': ['download_payload_rate']},
+ 'upload_speed': {'name': _('Up Speed'), 'status': ['upload_payload_rate']},
+ 'max_download_speed': {'name': _('Down Limit'), 'status': ['max_download_speed']},
+ 'max_upload_speed': {'name': _('Up Limit'), 'status': ['max_upload_speed']},
+ 'max_connections': {'name': _('Max Connections'), 'status': ['max_connections']},
+ 'max_upload_slots': {'name': _('Max Upload Slots'), 'status': ['max_upload_slots']},
+ 'peers': {'name': _('Peers'), 'status': ['num_peers', 'total_peers']},
+ 'seeds': {'name': _('Seeds'), 'status': ['num_seeds', 'total_seeds']},
+ 'avail': {'name': _('Avail'), 'status': ['distributed_copies']},
+ 'seeds_peers_ratio': {'name': _('Seeds:Peers'), 'status': ['seeds_peers_ratio']},
+ 'time_added': {'name': _('Added'), 'status': ['time_added']},
+ 'tracker': {'name': _('Tracker'), 'status': ['tracker_host']},
+ 'download_location': {
+ 'name': _('Download Folder'),
+ 'status': ['download_location'],
+ },
+ 'seeding_time': {'name': _('Seeding Time'), 'status': ['seeding_time']},
+ 'active_time': {'name': _('Active Time'), 'status': ['active_time']},
+ 'time_since_transfer': {
+ 'name': _('Last Activity'),
+ 'status': ['time_since_transfer'],
+ },
+ 'finished_time': {'name': _('Finished Time'), 'status': ['finished_time']},
+ 'last_seen_complete': {
+ 'name': _('Complete Seen'),
+ 'status': ['last_seen_complete'],
+ },
+ 'completed_time': {'name': _('Completed'), 'status': ['completed_time']},
+ 'eta': {'name': _('ETA'), 'status': ['eta']},
+ 'shared': {'name': _('Shared'), 'status': ['shared']},
+ 'prioritize_first_last': {
+ 'name': _('Prioritize First/Last'),
+ 'status': ['prioritize_first_last'],
+ },
+ 'sequential_download': {
+ 'name': _('Sequential Download'),
+ 'status': ['sequential_download'],
+ },
+ 'is_auto_managed': {'name': _('Auto Managed'), 'status': ['is_auto_managed']},
+ 'auto_managed': {'name': _('Auto Managed'), 'status': ['auto_managed']},
+ 'stop_at_ratio': {'name': _('Stop At Ratio'), 'status': ['stop_at_ratio']},
+ 'stop_ratio': {'name': _('Stop Ratio'), 'status': ['stop_ratio']},
+ 'remove_at_ratio': {'name': _('Remove At Ratio'), 'status': ['remove_at_ratio']},
+ 'move_completed': {'name': _('Move On Completed'), 'status': ['move_completed']},
+ 'move_completed_path': {
+ 'name': _('Move Completed Path'),
+ 'status': ['move_completed_path'],
+ },
+ 'move_on_completed': {
+ 'name': _('Move On Completed'),
+ 'status': ['move_on_completed'],
+ },
+ 'move_on_completed_path': {
+ 'name': _('Move On Completed Path'),
+ 'status': ['move_on_completed_path'],
+ },
+ 'owner': {'name': _('Owner'), 'status': ['owner']},
+ 'pieces': {'name': _('Pieces'), 'status': ['num_pieces', 'piece_length']},
+ 'seed_rank': {'name': _('Seed Rank'), 'status': ['seed_rank']},
+ 'super_seeding': {'name': _('Super Seeding'), 'status': ['super_seeding']},
+}
+
+TRACKER_STATUS_TRANSLATION = [
+ _('Error'),
+ _('Warning'),
+ _('Announce OK'),
+ _('Announce Sent'),
+]
+
+PREFS_CATOG_TRANS = {
+ 'interface': _('Interface'),
+ 'downloads': _('Downloads'),
+ 'bandwidth': _('Bandwidth'),
+ 'queue': _('Queue'),
+ 'network': _('Network'),
+ 'proxy': _('Proxy'),
+ 'cache': _('Cache'),
+ 'other': _('Other'),
+ 'daemon': _('Daemon'),
+ 'plugins': _('Plugins'),
+}
+
+FILE_PRIORITY = {
+ 0: 'Skip',
+ 1: 'Low',
+ 2: 'Low',
+ 3: 'Low',
+ 4: 'Normal',
+ 5: 'High',
+ 6: 'High',
+ 7: 'High',
+ _('Skip'): 0,
+ _('Low'): 1,
+ _('Normal'): 4,
+ _('High'): 7,
+}
+
+del _
+
+# The keys from session statistics for cache status.
+DISK_CACHE_KEYS = [
+ 'disk.num_blocks_read',
+ 'disk.num_blocks_written',
+ 'disk.num_read_ops',
+ 'disk.num_write_ops',
+ 'disk.num_blocks_cache_hits',
+ 'read_hit_ratio',
+ 'write_hit_ratio',
+ 'disk.disk_blocks_in_use',
+ 'disk.read_cache_blocks',
+]
+
+
+class TorrentInfo(object):
+ """Collects information about a torrent file.
+
+ Args:
+ filename (str, optional): The path to the .torrent file.
+ filetree (int, optional): The version of filetree to create (defaults to 1).
+ torrent_file (dict, optional): A bdecoded .torrent file contents.
+
+ """
+
+ def __init__(self, filename='', filetree=1, torrent_file=None):
+ self._filedata = None
+ if torrent_file:
+ self._metainfo = torrent_file
+ elif filename:
+ log.debug('Attempting to open %s.', filename)
+ try:
+ with open(filename, 'rb') as _file:
+ self._filedata = _file.read()
+ except IOError as ex:
+ log.warning('Unable to open %s: %s', filename, ex)
+ return
+
+ try:
+ self._metainfo = bencode.bdecode(self._filedata)
+ except bencode.BTFailure as ex:
+ log.warning('Failed to decode %s: %s', filename, ex)
+ return
+ else:
+ log.warning('Requires valid arguments.')
+ return
+
+ # info_dict with keys decoded to unicode.
+ info_dict = {k.decode(): v for k, v in self._metainfo[b'info'].items()}
+ self._info_hash = sha(bencode.bencode(info_dict)).hexdigest()
+
+ # Get encoding from torrent file if available
+ encoding = info_dict.get('encoding', None)
+ codepage = info_dict.get('codepage', None)
+ if not encoding:
+ encoding = codepage if codepage else b'UTF-8'
+ if encoding:
+ encoding = encoding.decode()
+
+ # Decode 'name' with encoding unless 'name.utf-8' found.
+ if 'name.utf-8' in info_dict:
+ self._name = decode_bytes(info_dict['name.utf-8'])
+ else:
+ self._name = decode_bytes(info_dict['name'], encoding)
+
+ # Get list of files from torrent info
+ self._files = []
+ if 'files' in info_dict:
+ paths = {}
+ dirs = {}
+ prefix = self._name if len(info_dict['files']) > 1 else ''
+
+ for index, f in enumerate(info_dict['files']):
+ f = {k.decode(): v for k, v in f.items()}
+
+ if 'path.utf-8' in f:
+ path = decode_bytes(os.path.join(*f['path.utf-8']))
+ del f['path.utf-8']
+ else:
+ path = decode_bytes(os.path.join(*f['path']), encoding)
+
+ if prefix:
+ path = os.path.join(prefix, path)
+
+ self._files.append(
+ {'path': path, 'size': f['length'], 'download': True}
+ )
+
+ f['path'] = path
+ f['index'] = index
+ if 'sha1' in f and len(f['sha1']) == 20:
+ f['sha1'] = hexlify(f['sha1']).decode()
+ if 'ed2k' in f and len(f['ed2k']) == 16:
+ f['ed2k'] = hexlify(f['ed2k']).decode()
+ if 'filehash' in f and len(f['filehash']) == 20:
+ f['filehash'] = hexlify(f['filehash']).decode()
+
+ paths[path] = f
+ dirname = os.path.dirname(path)
+ while dirname:
+ dirinfo = dirs.setdefault(dirname, {})
+ dirinfo['length'] = dirinfo.get('length', 0) + f['length']
+ dirname = os.path.dirname(dirname)
+
+ if filetree == 2:
+
+ def walk(path, item):
+ if item['type'] == 'dir':
+ item.update(dirs[path])
+ else:
+ item.update(paths[path])
+ item['download'] = True
+
+ file_tree = FileTree2(list(paths))
+ file_tree.walk(walk)
+ else:
+
+ def walk(path, item):
+ if isinstance(item, dict):
+ return item
+ return [paths[path]['index'], paths[path]['length'], True]
+
+ file_tree = FileTree(paths)
+ file_tree.walk(walk)
+ self._files_tree = file_tree.get_tree()
+ else:
+ self._files.append(
+ {'path': self._name, 'size': info_dict['length'], 'download': True}
+ )
+ if filetree == 2:
+ self._files_tree = {
+ 'contents': {
+ self._name: {
+ 'type': 'file',
+ 'index': 0,
+ 'length': info_dict['length'],
+ 'download': True,
+ }
+ }
+ }
+ else:
+ self._files_tree = {self._name: (0, info_dict['length'], True)}
+
+ @classmethod
+ def from_metadata(cls, metadata, trackers=None):
+ """Create a TorrentInfo from metadata and trackers
+
+ Args:
+ metadata (dict): A bdecoded info section of torrent file.
+ trackers (list of lists, optional): The trackers to include.
+
+ """
+ if not isinstance(metadata, dict):
+ return
+
+ metainfo = {b'info': metadata}
+ if trackers:
+ metainfo[b'announce'] = trackers[0][0].encode('utf-8')
+ trackers_utf8 = [[t.encode('utf-8') for t in tier] for tier in trackers]
+ metainfo[b'announce-list'] = trackers_utf8
+ return cls(torrent_file=metainfo)
+
+ def as_dict(self, *keys):
+ """The torrent info as a dictionary, filtered by keys.
+
+ Args:
+ keys (str): A space-separated string of keys.
+
+ Returns:
+ dict: The torrent info dict with specified keys.
+ """
+ return {key: getattr(self, key) for key in keys}
+
+ @property
+ def name(self):
+ """The name of the torrent.
+
+ Returns:
+ str: The torrent name.
+
+ """
+ return self._name
+
+ @property
+ def info_hash(self):
+ """The calculated torrent info_hash.
+
+ Returns:
+ str: The torrent info_hash.
+ """
+ return self._info_hash
+
+ @property
+ def files(self):
+ """The files that the torrent contains.
+
+ Returns:
+ list: The list of torrent files.
+
+ """
+ return self._files
+
+ @property
+ def files_tree(self):
+ """A tree of the files the torrent contains.
+
+ ::
+
+ {
+ "some_directory": {
+ "some_file": (index, size, download)
+ }
+ }
+
+ Returns:
+ dict: The tree of files.
+
+ """
+ return self._files_tree
+
+ @property
+ def metainfo(self):
+ """Returns the torrent metainfo dictionary.
+
+ This is the bdecoded torrent file contents.
+
+ Returns:
+ dict: The metainfo dictionary.
+
+ """
+ return self._metainfo
+
+ @property
+ def filedata(self):
+ """The contents of the .torrent file.
+
+ Returns:
+ bytes: The bencoded metainfo.
+
+ """
+ if not self._filedata:
+ self._filedata = bencode.bencode(self._metainfo)
+ return self._filedata
+
+
+class FileTree2(object):
+ """
+ Converts a list of paths in to a file tree.
+
+ :param paths: The paths to be converted
+ :type paths: list
+ """
+
+ def __init__(self, paths):
+ self.tree = {'contents': {}, 'type': 'dir'}
+
+ def get_parent(path):
+ parent = self.tree
+ while '/' in path:
+ directory, path = path.split('/', 1)
+ child = parent['contents'].get(directory)
+ if child is None:
+ parent['contents'][directory] = {'type': 'dir', 'contents': {}}
+ parent = parent['contents'][directory]
+ return parent, path
+
+ for path in paths:
+ if path[-1] == '/':
+ path = path[:-1]
+ parent, path = get_parent(path)
+ parent['contents'][path] = {'type': 'dir', 'contents': {}}
+ else:
+ parent, path = get_parent(path)
+ parent['contents'][path] = {'type': 'file'}
+
+ def get_tree(self):
+ """
+ Return the tree.
+
+ :returns: the file tree.
+ :rtype: dictionary
+ """
+ return self.tree
+
+ def walk(self, callback):
+ """
+ Walk through the file tree calling the callback function on each item
+ contained.
+
+ :param callback: The function to be used as a callback, it should have
+ the signature func(item, path) where item is a `tuple` for a file
+ and `dict` for a directory.
+ :type callback: function
+ """
+
+ def walk(directory, parent_path):
+ for path in list(directory['contents']):
+ full_path = os.path.join(parent_path, path).replace('\\', '/')
+ if directory['contents'][path]['type'] == 'dir':
+ directory['contents'][path] = (
+ callback(full_path, directory['contents'][path])
+ or directory['contents'][path]
+ )
+ walk(directory['contents'][path], full_path)
+ else:
+ directory['contents'][path] = (
+ callback(full_path, directory['contents'][path])
+ or directory['contents'][path]
+ )
+
+ walk(self.tree, '')
+
+ def __str__(self):
+ lines = []
+
+ def write(path, item):
+ depth = path.count('/')
+ path = os.path.basename(path)
+ path = path + '/' if item['type'] == 'dir' else path
+ lines.append(' ' * depth + path)
+
+ self.walk(write)
+ return '\n'.join(lines)
+
+
+class FileTree(object):
+ """
+ Convert a list of paths in a file tree.
+
+ :param paths: The paths to be converted.
+ :type paths: list
+ """
+
+ def __init__(self, paths):
+ self.tree = {}
+
+ def get_parent(path):
+ parent = self.tree
+ while '/' in path:
+ directory, path = path.split('/', 1)
+ child = parent.get(directory)
+ if child is None:
+ parent[directory] = {}
+ parent = parent[directory]
+ return parent, path
+
+ for path in paths:
+ if path[-1] == '/':
+ path = path[:-1]
+ parent, path = get_parent(path)
+ parent[path] = {}
+ else:
+ parent, path = get_parent(path)
+ parent[path] = []
+
+ def get_tree(self):
+ """
+ Return the tree, after first converting all file lists to a tuple.
+
+ :returns: the file tree.
+ :rtype: dictionary
+ """
+
+ def to_tuple(path, item):
+ if isinstance(item, dict):
+ return item
+ return tuple(item)
+
+ self.walk(to_tuple)
+ return self.tree
+
+ def walk(self, callback):
+ """
+ Walk through the file tree calling the callback function on each item
+ contained.
+
+ :param callback: The function to be used as a callback, it should have
+ the signature func(item, path) where item is a `tuple` for a file
+ and `dict` for a directory.
+ :type callback: function
+ """
+
+ def walk(directory, parent_path):
+ for path in list(directory):
+ full_path = os.path.join(parent_path, path)
+ if isinstance(directory[path], dict):
+ directory[path] = (
+ callback(full_path, directory[path]) or directory[path]
+ )
+ walk(directory[path], full_path)
+ else:
+ directory[path] = (
+ callback(full_path, directory[path]) or directory[path]
+ )
+
+ walk(self.tree, '')
+
+ def __str__(self):
+ lines = []
+
+ def write(path, item):
+ depth = path.count('/')
+ path = os.path.basename(path)
+ path = isinstance(item, dict) and path + '/' or path
+ lines.append(' ' * depth + path)
+
+ self.walk(write)
+ return '\n'.join(lines)
diff --git a/deluge/ui/console/__init__.py b/deluge/ui/console/__init__.py
new file mode 100644
index 0000000..56e8d62
--- /dev/null
+++ b/deluge/ui/console/__init__.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+from deluge.ui.console.console import Console
+
+UI_PATH = __path__[0]
+
+
+def start():
+
+ Console().start()
diff --git a/deluge/ui/console/cmdline/__init__.py b/deluge/ui/console/cmdline/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/deluge/ui/console/cmdline/__init__.py
diff --git a/deluge/ui/console/cmdline/command.py b/deluge/ui/console/cmdline/command.py
new file mode 100644
index 0000000..2ff32df
--- /dev/null
+++ b/deluge/ui/console/cmdline/command.py
@@ -0,0 +1,215 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import print_function, unicode_literals
+
+import logging
+import shlex
+
+from twisted.internet import defer
+
+from deluge.ui.client import client
+from deluge.ui.console.parser import OptionParser, OptionParserError
+from deluge.ui.console.utils.colors import strip_colors
+
+log = logging.getLogger(__name__)
+
+
+class Commander(object):
+ def __init__(self, cmds, interactive=False):
+ self._commands = cmds
+ self.interactive = interactive
+
+ def write(self, line):
+ print(strip_colors(line))
+
+ def do_command(self, cmd_line):
+ """Run a console command.
+
+ Args:
+ cmd_line (str): Console command.
+
+ Returns:
+ Deferred: A deferred that fires when the command has been executed.
+
+ """
+ options = self.parse_command(cmd_line)
+ if options:
+ return self.exec_command(options)
+ return defer.succeed(None)
+
+ def exit(self, status=0, msg=None):
+ self._exit = True
+ if msg:
+ print(msg)
+
+ def parse_command(self, cmd_line):
+ """Parse a console command and process with argparse.
+
+ Args:
+ cmd_line (str): Console command.
+
+ Returns:
+ argparse.Namespace: The parsed command.
+
+ """
+ if not cmd_line:
+ return
+ cmd, _, line = cmd_line.partition(' ')
+ try:
+ parser = self._commands[cmd].create_parser()
+ except KeyError:
+ self.write('{!error!}Unknown command: %s' % cmd)
+ return
+
+ try:
+ args = [cmd] + self._commands[cmd].split(line)
+ except ValueError as ex:
+ self.write('{!error!}Error parsing command: %s' % ex)
+ return
+
+ # Do a little hack here to print 'command --help' properly
+ parser._print_help = parser.print_help
+
+ def print_help(f=None):
+ if self.interactive:
+ self.write(parser.format_help())
+ else:
+ parser._print_help(f)
+
+ parser.print_help = print_help
+
+ # Only these commands can be run when not connected to a daemon
+ not_connected_cmds = ['help', 'connect', 'quit']
+ aliases = []
+ for c in not_connected_cmds:
+ aliases.extend(self._commands[c].aliases)
+ not_connected_cmds.extend(aliases)
+
+ if not client.connected() and cmd not in not_connected_cmds:
+ self.write(
+ '{!error!}Not connected to a daemon, please use the connect command first.'
+ )
+ return
+
+ try:
+ options = parser.parse_args(args=args)
+ options.command = cmd
+ except TypeError as ex:
+ self.write('{!error!}Error parsing options: %s' % ex)
+ import traceback
+
+ self.write('%s' % traceback.format_exc())
+ return
+ except OptionParserError as ex:
+ import traceback
+
+ log.warning('Error parsing command "%s": %s', args, ex)
+ self.write('{!error!} %s' % ex)
+ parser.print_help()
+ return
+
+ if getattr(parser, '_exit', False):
+ return
+ return options
+
+ def exec_command(self, options, *args):
+ """Execute a console command.
+
+ Args:
+ options (argparse.Namespace): The command to execute.
+
+ Returns:
+ Deferred: A deferred that fires when command has been executed.
+
+ """
+ try:
+ ret = self._commands[options.command].handle(options)
+ except Exception as ex: # pylint: disable=broad-except
+ self.write('{!error!} %s' % ex)
+ log.exception(ex)
+ import traceback
+
+ self.write('%s' % traceback.format_exc())
+ return defer.succeed(True)
+ else:
+ return ret
+
+
+class BaseCommand(object):
+
+ usage = None
+ interactive_only = False
+ aliases = []
+ _name = 'base'
+ epilog = ''
+
+ def complete(self, text, *args):
+ return []
+
+ def handle(self, options):
+ pass
+
+ @property
+ def name(self):
+ return self._name
+
+ @property
+ def name_with_alias(self):
+ return '/'.join([self._name] + self.aliases)
+
+ @property
+ def description(self):
+ return self.__doc__
+
+ def split(self, text):
+ text = text.replace('\\', '\\\\')
+ result = shlex.split(text)
+ for i, s in enumerate(result):
+ result[i] = s.replace(r'\ ', ' ')
+ result = [s for s in result if s != '']
+ return result
+
+ def create_parser(self):
+ opts = {
+ 'prog': self.name_with_alias,
+ 'description': self.__doc__,
+ 'epilog': self.epilog,
+ }
+ if self.usage:
+ opts['usage'] = self.usage
+ parser = OptionParser(**opts)
+ parser.add_argument(self.name, metavar='')
+ parser.base_parser = parser
+ self.add_arguments(parser)
+ return parser
+
+ def add_subparser(self, subparsers):
+ opts = {
+ 'prog': self.name_with_alias,
+ 'help': self.__doc__,
+ 'description': self.__doc__,
+ }
+ if self.usage:
+ opts['usage'] = self.usage
+
+ # A workaround for aliases showing as duplicate command names in help output.
+ for cmd_name in sorted([self.name] + self.aliases):
+ if cmd_name not in subparsers._name_parser_map:
+ if cmd_name in self.aliases:
+ opts['help'] = _('`%s` alias' % self.name)
+ parser = subparsers.add_parser(cmd_name, **opts)
+ break
+
+ self.add_arguments(parser)
+
+ def add_arguments(self, parser):
+ pass
diff --git a/deluge/ui/console/cmdline/commands/__init__.py b/deluge/ui/console/cmdline/commands/__init__.py
new file mode 100644
index 0000000..628fae5
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/__init__.py
@@ -0,0 +1,6 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from deluge.ui.console.cmdline.command import BaseCommand
+
+__all__ = ['BaseCommand']
diff --git a/deluge/ui/console/cmdline/commands/add.py b/deluge/ui/console/cmdline/commands/add.py
new file mode 100644
index 0000000..34881d8
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/add.py
@@ -0,0 +1,126 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import os
+from base64 import b64encode
+
+from twisted.internet import defer
+
+import deluge.common
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+try:
+ from urllib.parse import urlparse
+ from urllib.request import url2pathname
+except ImportError:
+ # PY2 fallback
+ from urlparse import urlparse # pylint: disable=ungrouped-imports
+ from urllib import url2pathname # pylint: disable=ungrouped-imports
+
+
+class Command(BaseCommand):
+ """Add torrents"""
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '-p', '--path', dest='path', help=_('Download folder for torrent')
+ )
+ parser.add_argument(
+ '-m',
+ '--move-path',
+ dest='move_completed_path',
+ help=_('Move the completed torrent to this folder'),
+ )
+ parser.add_argument(
+ 'torrents',
+ metavar='<torrent>',
+ nargs='+',
+ help=_('One or more torrent files, URLs or magnet URIs'),
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ t_options = {}
+ if options.path:
+ t_options['download_location'] = os.path.abspath(
+ os.path.expanduser(options.path)
+ )
+
+ if options.move_completed_path:
+ t_options['move_completed'] = True
+ t_options['move_completed_path'] = os.path.abspath(
+ os.path.expanduser(options.move_completed_path)
+ )
+
+ def on_success(result):
+ if not result:
+ self.console.write('{!error!}Torrent was not added: Already in session')
+ else:
+ self.console.write('{!success!}Torrent added!')
+
+ def on_fail(result):
+ self.console.write('{!error!}Torrent was not added: %s' % result)
+
+ # Keep a list of deferreds to make a DeferredList
+ deferreds = []
+ for torrent in options.torrents:
+ if not torrent.strip():
+ continue
+ if deluge.common.is_url(torrent):
+ self.console.write(
+ '{!info!}Attempting to add torrent from url: %s' % torrent
+ )
+ deferreds.append(
+ client.core.add_torrent_url(torrent, t_options)
+ .addCallback(on_success)
+ .addErrback(on_fail)
+ )
+ elif deluge.common.is_magnet(torrent):
+ self.console.write(
+ '{!info!}Attempting to add torrent from magnet uri: %s' % torrent
+ )
+ deferreds.append(
+ client.core.add_torrent_magnet(torrent, t_options)
+ .addCallback(on_success)
+ .addErrback(on_fail)
+ )
+ else:
+ # Just a file
+ if urlparse(torrent).scheme == 'file':
+ torrent = url2pathname(urlparse(torrent).path)
+ path = os.path.abspath(os.path.expanduser(torrent))
+ if not os.path.exists(path):
+ self.console.write('{!error!}%s does not exist!' % path)
+ continue
+ if not os.path.isfile(path):
+ self.console.write('{!error!}This is a directory!')
+ continue
+ self.console.write('{!info!}Attempting to add torrent: %s' % path)
+ filename = os.path.split(path)[-1]
+ with open(path, 'rb') as _file:
+ filedump = b64encode(_file.read())
+ deferreds.append(
+ client.core.add_torrent_file_async(filename, filedump, t_options)
+ .addCallback(on_success)
+ .addErrback(on_fail)
+ )
+
+ return defer.DeferredList(deferreds)
+
+ def complete(self, line):
+ return component.get('ConsoleUI').tab_complete_path(
+ line, ext='.torrent', sort='date'
+ )
diff --git a/deluge/ui/console/cmdline/commands/cache.py b/deluge/ui/console/cmdline/commands/cache.py
new file mode 100644
index 0000000..e427f08
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/cache.py
@@ -0,0 +1,31 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import deluge.component as component
+from deluge.ui.client import client
+from deluge.ui.common import DISK_CACHE_KEYS
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Show information about the disk cache"""
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ def on_cache_status(status):
+ for key, value in sorted(status.items()):
+ self.console.write('{!info!}%s: {!input!}%s' % (key, value))
+
+ return client.core.get_session_status(DISK_CACHE_KEYS).addCallback(
+ on_cache_status
+ )
diff --git a/deluge/ui/console/cmdline/commands/config.py b/deluge/ui/console/cmdline/commands/config.py
new file mode 100644
index 0000000..bd0a1e1
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/config.py
@@ -0,0 +1,168 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import tokenize
+from io import StringIO
+
+import deluge.component as component
+import deluge.ui.console.utils.colors as colors
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+log = logging.getLogger(__name__)
+
+
+def atom(src, token):
+ """taken with slight modifications from http://effbot.org/zone/simple-iterator-parser.htm"""
+ if token[1] == '(':
+ out = []
+ token = next(src)
+ while token[1] != ')':
+ out.append(atom(src, token))
+ token = next(src)
+ if token[1] == ',':
+ token = next(src)
+ return tuple(out)
+ elif token[0] is tokenize.NUMBER or token[1] == '-':
+ try:
+ if token[1] == '-':
+ return int(token[-1], 0)
+ else:
+ if token[1].startswith('0x'):
+ # Hex number so return unconverted as string.
+ return token[1].decode('string-escape')
+ else:
+ return int(token[1], 0)
+ except ValueError:
+ try:
+ return float(token[-1])
+ except ValueError:
+ return str(token[-1])
+ elif token[1].lower() == 'true':
+ return True
+ elif token[1].lower() == 'false':
+ return False
+ elif token[0] is tokenize.STRING or token[1] == '/':
+ return token[-1].decode('string-escape')
+ elif token[1].isalpha():
+ # Parse Windows paths e.g. 'C:\\xyz' or 'C:/xyz'.
+ if next()[1] == ':' and next()[1] in '\\/':
+ return token[-1].decode('string-escape')
+
+ raise SyntaxError('malformed expression (%s)' % token[1])
+
+
+def simple_eval(source):
+ """ evaluates the 'source' string into a combination of primitive python objects
+ taken from http://effbot.org/zone/simple-iterator-parser.htm"""
+ src = StringIO(source).readline
+ src = tokenize.generate_tokens(src)
+ src = (token for token in src if token[0] is not tokenize.NL)
+ res = atom(src, next(src))
+ return res
+
+
+class Command(BaseCommand):
+ """Show and set configuration values"""
+
+ usage = _('Usage: config [--set <key> <value>] [<key> [<key>...] ]')
+
+ def add_arguments(self, parser):
+ set_group = parser.add_argument_group('setting a value')
+ set_group.add_argument(
+ '-s',
+ '--set',
+ action='store',
+ metavar='<key>',
+ help=_('set value for this key'),
+ )
+ set_group.add_argument(
+ 'values', metavar='<value>', nargs='+', help=_('Value to set')
+ )
+ get_group = parser.add_argument_group('getting values')
+ get_group.add_argument(
+ 'keys',
+ metavar='<keys>',
+ nargs='*',
+ help=_('one or more keys separated by space'),
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+ if options.set:
+ return self._set_config(options)
+ else:
+ return self._get_config(options)
+
+ def _get_config(self, options):
+ def _on_get_config(config):
+ string = ''
+ for key in sorted(config):
+ if key not in options.values:
+ continue
+
+ color = '{!white,black,bold!}'
+ value = config[key]
+ try:
+ color = colors.type_color[type(value)]
+ except KeyError:
+ pass
+
+ # We need to format dicts for printing
+ if isinstance(value, dict):
+ import pprint
+
+ value = pprint.pformat(value, 2, 80)
+ new_value = []
+ for line in value.splitlines():
+ new_value.append('%s%s' % (color, line))
+ value = '\n'.join(new_value)
+
+ string += '%s: %s%s\n' % (key, color, value)
+ self.console.write(string.strip())
+
+ return client.core.get_config().addCallback(_on_get_config)
+
+ def _set_config(self, options):
+ config = component.get('CoreConfig')
+ key = options.set
+ val = ' '.join(options.values)
+
+ try:
+ val = simple_eval(val)
+ except SyntaxError as ex:
+ self.console.write('{!error!}%s' % ex)
+ return
+
+ if key not in config:
+ self.console.write('{!error!}Invalid key: %s' % key)
+ return
+
+ if not isinstance(config[key], type(val)):
+ try:
+ val = type(config[key])(val)
+ except TypeError:
+ self.config.write(
+ '{!error!}Configuration value provided has incorrect type.'
+ )
+ return
+
+ def on_set_config(result):
+ self.console.write('{!success!}Configuration value successfully updated.')
+
+ self.console.write('Setting "%s" to: %s' % (key, val))
+ return client.core.set_config({key: val}).addCallback(on_set_config)
+
+ def complete(self, text):
+ return [k for k in component.get('CoreConfig') if k.startswith(text)]
diff --git a/deluge/ui/console/cmdline/commands/connect.py b/deluge/ui/console/cmdline/commands/connect.py
new file mode 100644
index 0000000..6588f7a
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/connect.py
@@ -0,0 +1,86 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+log = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ """Connect to a new deluge server"""
+
+ usage = _('Usage: connect <host[:port]> [<username>] [<password>]')
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'host', help=_('Daemon host and port'), metavar='<host[:port]>'
+ )
+ parser.add_argument(
+ 'username', help=_('Username'), metavar='<username>', nargs='?', default=''
+ )
+ parser.add_argument(
+ 'password', help=_('Password'), metavar='<password>', nargs='?', default=''
+ )
+
+ def add_parser(self, subparsers):
+ parser = subparsers.add_parser(
+ self.name, help=self.__doc__, description=self.__doc__, prog='connect'
+ )
+ self.add_arguments(parser)
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ host = options.host
+ try:
+ host, port = host.split(':')
+ port = int(port)
+ except ValueError:
+ port = 58846
+
+ def do_connect():
+ d = client.connect(host, port, options.username, options.password)
+
+ def on_connect(result):
+ if self.console.interactive:
+ self.console.write('{!success!}Connected to %s:%s!' % (host, port))
+ return component.start()
+
+ def on_connect_fail(result):
+ try:
+ msg = result.value.exception_msg
+ except AttributeError:
+ msg = result.value.message
+ self.console.write(
+ '{!error!}Failed to connect to %s:%s with reason: %s'
+ % (host, port, msg)
+ )
+ return result
+
+ d.addCallbacks(on_connect, on_connect_fail)
+ return d
+
+ if client.connected():
+
+ def on_disconnect(result):
+ if self.console.statusbars:
+ self.console.statusbars.update_statusbars()
+ return do_connect()
+
+ return client.disconnect().addCallback(on_disconnect)
+ else:
+ return do_connect()
diff --git a/deluge/ui/console/cmdline/commands/debug.py b/deluge/ui/console/cmdline/commands/debug.py
new file mode 100644
index 0000000..3ca06ed
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/debug.py
@@ -0,0 +1,40 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+from twisted.internet import defer
+
+import deluge.component as component
+import deluge.log
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Enable and disable debugging"""
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'state', metavar='<on|off>', choices=['on', 'off'], help=_('The new state')
+ )
+
+ def handle(self, options):
+ if options.state == 'on':
+ deluge.log.set_logger_level('debug')
+ elif options.state == 'off':
+ deluge.log.set_logger_level('error')
+ else:
+ component.get('ConsoleUI').write('{!error!}%s' % self.usage)
+
+ return defer.succeed(True)
+
+ def complete(self, text):
+ return [x for x in ['on', 'off'] if x.startswith(text)]
diff --git a/deluge/ui/console/cmdline/commands/gui.py b/deluge/ui/console/cmdline/commands/gui.py
new file mode 100644
index 0000000..10e4c49
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/gui.py
@@ -0,0 +1,30 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+import deluge.component as component
+
+from . import BaseCommand
+
+log = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ """Enable interactive mode"""
+
+ interactive_only = True
+
+ def handle(self, options):
+ console = component.get('ConsoleUI')
+ at = console.set_mode('TorrentList')
+ at.go_top = True
+ at.resume()
diff --git a/deluge/ui/console/cmdline/commands/halt.py b/deluge/ui/console/cmdline/commands/halt.py
new file mode 100644
index 0000000..6355958
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/halt.py
@@ -0,0 +1,35 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Shutdown the deluge server."""
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ def on_shutdown(result):
+ self.console.write('{!success!}Daemon was shutdown')
+
+ def on_shutdown_fail(reason):
+ self.console.write('{!error!}Unable to shutdown daemon: %s' % reason)
+
+ return (
+ client.daemon.shutdown()
+ .addCallback(on_shutdown)
+ .addErrback(on_shutdown_fail)
+ )
diff --git a/deluge/ui/console/cmdline/commands/help.py b/deluge/ui/console/cmdline/commands/help.py
new file mode 100644
index 0000000..2711eea
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/help.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+from twisted.internet import defer
+
+import deluge.component as component
+
+from . import BaseCommand
+
+log = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ """Displays help on other commands"""
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'commands', metavar='<command>', nargs='*', help=_('One or more commands')
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+ self._commands = self.console._commands
+ deferred = defer.succeed(True)
+ if options.commands:
+ for arg in options.commands:
+ try:
+ cmd = self._commands[arg]
+ except KeyError:
+ self.console.write('{!error!}Unknown command %s' % arg)
+ continue
+ try:
+ parser = cmd.create_parser()
+ self.console.write(parser.format_help())
+ except AttributeError:
+ self.console.write(cmd.__doc__ or 'No help for this command')
+ self.console.write(' ')
+ else:
+ self.console.set_batch_write(True)
+ cmds_doc = ''
+ for cmd in sorted(self._commands):
+ if cmd in self._commands[cmd].aliases:
+ continue
+ parser = self._commands[cmd].create_parser()
+ cmd_doc = (
+ '{!info!}'
+ + '%-9s' % self._commands[cmd].name_with_alias
+ + '{!input!} - '
+ + self._commands[cmd].__doc__
+ + '\n '
+ + parser.format_usage()
+ or ''
+ )
+ cmds_doc += parser.formatter.format_colors(cmd_doc)
+ self.console.write(cmds_doc)
+ self.console.write(' ')
+ self.console.write('For help on a specific command, use `<command> --help`')
+ self.console.set_batch_write(False)
+
+ return deferred
+
+ def complete(self, line):
+ return [x for x in component.get('ConsoleUI')._commands if x.startswith(line)]
diff --git a/deluge/ui/console/cmdline/commands/info.py b/deluge/ui/console/cmdline/commands/info.py
new file mode 100644
index 0000000..0d22f76
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/info.py
@@ -0,0 +1,484 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import division, unicode_literals
+
+from os.path import sep as dirsep
+
+import deluge.component as component
+import deluge.ui.console.utils.colors as colors
+from deluge.common import TORRENT_STATE, fsize, fspeed
+from deluge.ui.client import client
+from deluge.ui.common import FILE_PRIORITY
+from deluge.ui.console.utils.format_utils import (
+ f_progressbar,
+ f_seedrank_dash,
+ format_date_never,
+ format_progress,
+ format_time,
+ ftotal_sized,
+ pad_string,
+ remove_formatting,
+ shorten_hash,
+ strwidth,
+ trim_string,
+)
+
+from . import BaseCommand
+
+STATUS_KEYS = [
+ 'state',
+ 'download_location',
+ 'tracker_host',
+ 'tracker_status',
+ 'next_announce',
+ 'name',
+ 'total_size',
+ 'progress',
+ 'num_seeds',
+ 'total_seeds',
+ 'num_peers',
+ 'total_peers',
+ 'eta',
+ 'download_payload_rate',
+ 'upload_payload_rate',
+ 'ratio',
+ 'distributed_copies',
+ 'num_pieces',
+ 'piece_length',
+ 'total_done',
+ 'files',
+ 'file_priorities',
+ 'file_progress',
+ 'peers',
+ 'is_seed',
+ 'is_finished',
+ 'active_time',
+ 'seeding_time',
+ 'time_since_transfer',
+ 'last_seen_complete',
+ 'seed_rank',
+ 'all_time_download',
+ 'total_uploaded',
+ 'total_payload_download',
+ 'total_payload_upload',
+ 'time_added',
+]
+
+# Add filter specific state to torrent states
+STATES = ['Active'] + TORRENT_STATE
+
+
+class Command(BaseCommand):
+ """Show information about the torrents"""
+
+ sort_help = 'sort items. Possible keys: ' + ', '.join(STATUS_KEYS)
+
+ epilog = """
+ You can give the first few characters of a torrent-id to identify the torrent.
+
+ Tab Completion in interactive mode (info *pattern*<tab>):\n
+ | First press of <tab> will output up to 15 matches;
+ | hitting <tab> a second time, will print 15 more matches;
+ | and a third press will print all remaining matches.
+ | (To modify behaviour of third <tab>, set `third_tab_lists_all` to False)
+"""
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '-v',
+ '--verbose',
+ action='store_true',
+ default=False,
+ dest='verbose',
+ help=_('Show more information per torrent.'),
+ )
+ parser.add_argument(
+ '-d',
+ '--detailed',
+ action='store_true',
+ default=False,
+ dest='detailed',
+ help=_('Show more detailed information including files and peers.'),
+ )
+ parser.add_argument(
+ '-s',
+ '--state',
+ action='store',
+ dest='state',
+ help=_('Show torrents with state STATE: %s.' % (', '.join(STATES))),
+ )
+ parser.add_argument(
+ '--sort',
+ action='store',
+ type=str,
+ default='',
+ dest='sort',
+ help=self.sort_help,
+ )
+ parser.add_argument(
+ '--sort-reverse',
+ action='store',
+ type=str,
+ default='',
+ dest='sort_rev',
+ help=_('Same as --sort but items are in reverse order.'),
+ )
+ parser.add_argument(
+ 'torrent_ids',
+ metavar='<torrent-id>',
+ nargs='*',
+ help=_('One or more torrent ids. If none is given, list all'),
+ )
+
+ def add_subparser(self, subparsers):
+ parser = subparsers.add_parser(
+ self.name,
+ prog=self.name,
+ help=self.__doc__,
+ description=self.__doc__,
+ epilog=self.epilog,
+ )
+ self.add_arguments(parser)
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+ # Compile a list of torrent_ids to request the status of
+ torrent_ids = []
+
+ if options.torrent_ids:
+ for t_id in options.torrent_ids:
+ torrent_ids.extend(self.console.match_torrent(t_id))
+ else:
+ torrent_ids.extend(self.console.match_torrent(''))
+
+ def on_torrents_status(status):
+ # Print out the information for each torrent
+ sort_key = options.sort
+ sort_reverse = False
+ if not sort_key:
+ sort_key = options.sort_rev
+ sort_reverse = True
+ if not sort_key:
+ sort_key = 'name'
+ sort_reverse = False
+ if sort_key not in STATUS_KEYS:
+ self.console.write('')
+ self.console.write(
+ '{!error!}Unknown sort key: ' + sort_key + ', will sort on name'
+ )
+ sort_key = 'name'
+ sort_reverse = False
+ for key, value in sorted(
+ list(status.items()),
+ key=lambda x: x[1].get(sort_key),
+ reverse=sort_reverse,
+ ):
+ self.show_info(key, status[key], options.verbose, options.detailed)
+
+ def on_torrents_status_fail(reason):
+ self.console.write('{!error!}Error getting torrent info: %s' % reason)
+
+ status_dict = {'id': torrent_ids}
+
+ if options.state:
+ options.state = options.state.capitalize()
+ if options.state in STATES:
+ status_dict.state = options.state
+ else:
+ self.console.write('Invalid state: %s' % options.state)
+ self.console.write('Possible values are: %s.' % (', '.join(STATES)))
+ return
+
+ d = client.core.get_torrents_status(status_dict, STATUS_KEYS)
+ d.addCallback(on_torrents_status)
+ d.addErrback(on_torrents_status_fail)
+ return d
+
+ def show_file_info(self, torrent_id, status):
+ spaces_per_level = 2
+
+ if hasattr(self.console, 'screen'):
+ cols = self.console.screen.cols
+ else:
+ cols = 80
+
+ prevpath = []
+ for index, torrent_file in enumerate(status['files']):
+ filename = torrent_file['path'].split(dirsep)[-1]
+ filepath = torrent_file['path'].split(dirsep)[:-1]
+
+ for depth, subdir in enumerate(filepath):
+ indent = ' ' * depth * spaces_per_level
+ if depth >= len(prevpath):
+ self.console.write('%s{!cyan!}%s' % (indent, subdir))
+ elif subdir != prevpath[depth]:
+ self.console.write('%s{!cyan!}%s' % (indent, subdir))
+
+ depth = len(filepath)
+
+ indent = ' ' * depth * spaces_per_level
+
+ col_filename = indent + filename
+ col_size = ' ({!cyan!}%s{!input!})' % fsize(torrent_file['size'])
+ col_progress = ' {!input!}%.2f%%' % (status['file_progress'][index] * 100)
+
+ col_priority = ' {!info!}Priority: '
+
+ file_priority = FILE_PRIORITY[status['file_priorities'][index]]
+
+ if status['file_progress'][index] != 1.0:
+ if file_priority == 'Skip':
+ col_priority += '{!error!}'
+ else:
+ col_priority += '{!success!}'
+ else:
+ col_priority += '{!input!}'
+ col_priority += file_priority
+
+ def tlen(string):
+ return strwidth(remove_formatting(string))
+
+ col_all_info = col_size + col_progress + col_priority
+ # Check how much space we've got left after writing all the info
+ space_left = cols - tlen(col_all_info)
+ # And how much we will potentially have with the longest possible column
+ maxlen_space_left = cols - tlen(' (1000.0 MiB) 100.00% Priority: Normal')
+ if maxlen_space_left > tlen(col_filename) + 1:
+ # If there is enough space, pad it all nicely
+ col_all_info = ''
+ col_all_info += ' ('
+ spaces_to_add = tlen(' (1000.0 MiB)') - tlen(col_size)
+ col_all_info += ' ' * spaces_to_add
+ col_all_info += col_size[2:]
+ spaces_to_add = tlen(' 100.00%') - tlen(col_progress)
+ col_all_info += ' ' * spaces_to_add
+ col_all_info += col_progress
+ spaces_to_add = tlen(' Priority: Normal') - tlen(col_priority)
+ col_all_info += col_priority
+ col_all_info += ' ' * spaces_to_add
+ # And remember to put it to the left!
+ col_filename = pad_string(
+ col_filename, maxlen_space_left - 2, side='right'
+ )
+ elif space_left > tlen(col_filename) + 1:
+ # If there is enough space, put the info to the right
+ col_filename = pad_string(col_filename, space_left - 2, side='right')
+ else:
+ # And if there is not, shorten the name
+ col_filename = trim_string(col_filename, space_left, True)
+ self.console.write(col_filename + col_all_info)
+
+ prevpath = filepath
+
+ def show_peer_info(self, torrent_id, status):
+ if len(status['peers']) == 0:
+ self.console.write(' None')
+ else:
+ s = ''
+ for peer in status['peers']:
+ if peer['seed']:
+ s += '%sSeed\t{!input!}' % colors.state_color['Seeding']
+ else:
+ s += '%sPeer\t{!input!}' % colors.state_color['Downloading']
+
+ s += peer['country'] + '\t'
+
+ if peer['ip'].count(':') == 1:
+ # IPv4
+ s += peer['ip']
+ else:
+ # IPv6
+ s += '[%s]:%s' % (
+ ':'.join(peer['ip'].split(':')[:-1]),
+ peer['ip'].split(':')[-1],
+ )
+
+ c = peer['client']
+ s += '\t' + c
+
+ if len(c) < 16:
+ s += '\t\t'
+ else:
+ s += '\t'
+ s += '%s%s\t%s%s' % (
+ colors.state_color['Seeding'],
+ fspeed(peer['up_speed']),
+ colors.state_color['Downloading'],
+ fspeed(peer['down_speed']),
+ )
+ s += '\n'
+
+ self.console.write(s[:-1])
+
+ def show_info(self, torrent_id, status, verbose=False, detailed=False):
+ """
+ Writes out the torrents information to the screen.
+
+ Format depends on switches given.
+ """
+ self.console.set_batch_write(True)
+
+ if hasattr(self.console, 'screen'):
+ cols = self.console.screen.cols
+ else:
+ cols = 80
+
+ sep = ' '
+
+ if verbose or detailed:
+ self.console.write('{!info!}Name: {!input!}%s' % (status['name']))
+ self.console.write('{!info!}ID: {!input!}%s' % (torrent_id))
+ s = '{!info!}State: %s%s' % (
+ colors.state_color[status['state']],
+ status['state'],
+ )
+ # Only show speed if active
+ if status['state'] in ('Seeding', 'Downloading'):
+ if status['state'] != 'Seeding':
+ s += sep
+ s += '{!info!}Down Speed: {!input!}%s' % fspeed(
+ status['download_payload_rate'], shortform=True
+ )
+ s += sep
+ s += '{!info!}Up Speed: {!input!}%s' % fspeed(
+ status['upload_payload_rate'], shortform=True
+ )
+ self.console.write(s)
+
+ if status['state'] in ('Seeding', 'Downloading', 'Queued'):
+ s = '{!info!}Seeds: {!input!}%s (%s)' % (
+ status['num_seeds'],
+ status['total_seeds'],
+ )
+ s += sep
+ s += '{!info!}Peers: {!input!}%s (%s)' % (
+ status['num_peers'],
+ status['total_peers'],
+ )
+ s += sep
+ s += (
+ '{!info!}Availability: {!input!}%.2f' % status['distributed_copies']
+ )
+ s += sep
+ s += '{!info!}Seed Rank: {!input!}%s' % f_seedrank_dash(
+ status['seed_rank'], status['seeding_time']
+ )
+ self.console.write(s)
+
+ total_done = fsize(status['total_done'], shortform=True)
+ total_size = fsize(status['total_size'], shortform=True)
+ if total_done == total_size:
+ s = '{!info!}Size: {!input!}%s' % (total_size)
+ else:
+ s = '{!info!}Size: {!input!}%s/%s' % (total_done, total_size)
+ s += sep
+ s += '{!info!}Downloaded: {!input!}%s' % fsize(
+ status['all_time_download'], shortform=True
+ )
+ s += sep
+ s += '{!info!}Uploaded: {!input!}%s' % fsize(
+ status['total_uploaded'], shortform=True
+ )
+ s += sep
+ s += '{!info!}Share Ratio: {!input!}%.2f' % status['ratio']
+ self.console.write(s)
+
+ s = '{!info!}ETA: {!input!}%s' % format_time(status['eta'])
+ s += sep
+ s += '{!info!}Seeding: {!input!}%s' % format_time(status['seeding_time'])
+ s += sep
+ s += '{!info!}Active: {!input!}%s' % format_time(status['active_time'])
+ self.console.write(s)
+
+ s = '{!info!}Last Transfer: {!input!}%s' % format_time(
+ status['time_since_transfer']
+ )
+ s += sep
+ s += '{!info!}Complete Seen: {!input!}%s' % format_date_never(
+ status['last_seen_complete']
+ )
+ self.console.write(s)
+
+ s = '{!info!}Tracker: {!input!}%s' % status['tracker_host']
+ self.console.write(s)
+
+ self.console.write(
+ '{!info!}Tracker status: {!input!}%s' % status['tracker_status']
+ )
+
+ if not status['is_finished']:
+ pbar = f_progressbar(
+ status['progress'], cols - (13 + len('%.2f%%' % status['progress']))
+ )
+ s = '{!info!}Progress: {!input!}%.2f%% %s' % (status['progress'], pbar)
+ self.console.write(s)
+
+ s = '{!info!}Download Folder: {!input!}%s' % status['download_location']
+ self.console.write(s + '\n')
+
+ if detailed:
+ self.console.write('{!info!}Files in torrent')
+ self.show_file_info(torrent_id, status)
+ self.console.write('{!info!}Connected peers')
+ self.show_peer_info(torrent_id, status)
+ else:
+ up_color = colors.state_color['Seeding']
+ down_color = colors.state_color['Downloading']
+
+ s = '%s%s' % (
+ colors.state_color[status['state']],
+ '[' + status['state'][0] + ']',
+ )
+
+ s += ' {!info!}' + format_progress(status['progress']).rjust(6, ' ')
+ s += ' {!input!}%s' % (status['name'])
+
+ # Shorten the ID if it's necessary. Pretty hacky
+ # XXX: should make a nice function for it that can partition and shorten stuff
+ space_left = cols - strwidth('[S] 99.99% ' + status['name'])
+
+ if self.console.interactive and space_left >= len(sep + torrent_id):
+ # Not enough line space so shorten the hash (for interactive mode).
+ torrent_id = shorten_hash(torrent_id, space_left)
+ s += sep
+ s += '{!cyan!}%s' % torrent_id
+ self.console.write(s)
+
+ dl_info = '{!info!}DL: {!input!}'
+ dl_info += '%s' % ftotal_sized(
+ status['all_time_download'], status['total_payload_download']
+ )
+
+ if status['download_payload_rate'] > 0:
+ dl_info += ' @ %s%s' % (
+ down_color,
+ fspeed(status['download_payload_rate'], shortform=True),
+ )
+
+ ul_info = ' {!info!}UL: {!input!}'
+ ul_info += '%s' % ftotal_sized(
+ status['total_uploaded'], status['total_payload_upload']
+ )
+ if status['upload_payload_rate'] > 0:
+ ul_info += ' @ %s%s' % (
+ up_color,
+ fspeed(status['upload_payload_rate'], shortform=True),
+ )
+
+ eta = ' {!info!}ETA: {!magenta!}%s' % format_time(status['eta'])
+
+ self.console.write(' ' + dl_info + ul_info + eta + '\n')
+
+ self.console.set_batch_write(False)
+
+ def complete(self, line):
+ # We use the ConsoleUI torrent tab complete method
+ return component.get('ConsoleUI').tab_complete_torrent(line)
diff --git a/deluge/ui/console/cmdline/commands/manage.py b/deluge/ui/console/cmdline/commands/manage.py
new file mode 100644
index 0000000..6375a74
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/manage.py
@@ -0,0 +1,119 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+from twisted.internet import defer
+
+import deluge.component as component
+from deluge.ui.client import client
+from deluge.ui.console.utils.common import TORRENT_OPTIONS
+
+from . import BaseCommand
+
+log = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ """Show and manage per-torrent options"""
+
+ usage = _('Usage: manage <torrent-id> [--set <key> <value>] [<key> [<key>...] ]')
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'torrent',
+ metavar='<torrent>',
+ help=_('an expression matched against torrent ids and torrent names'),
+ )
+ set_group = parser.add_argument_group('setting a value')
+ set_group.add_argument(
+ '-s',
+ '--set',
+ action='store',
+ metavar='<key>',
+ help=_('set value for this key'),
+ )
+ set_group.add_argument(
+ 'values', metavar='<value>', nargs='+', help=_('Value to set')
+ )
+ get_group = parser.add_argument_group('getting values')
+ get_group.add_argument(
+ 'keys',
+ metavar='<keys>',
+ nargs='*',
+ help=_('one or more keys separated by space'),
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+ if options.set:
+ return self._set_option(options)
+ else:
+ return self._get_option(options)
+
+ def _get_option(self, options):
+ def on_torrents_status(status):
+ for torrentid, data in status.items():
+ self.console.write('')
+ if 'name' in data:
+ self.console.write('{!info!}Name: {!input!}%s' % data.get('name'))
+ self.console.write('{!info!}ID: {!input!}%s' % torrentid)
+ for k, v in data.items():
+ if k != 'name':
+ self.console.write('{!info!}%s: {!input!}%s' % (k, v))
+
+ def on_torrents_status_fail(reason):
+ self.console.write('{!error!}Failed to get torrent data.')
+
+ torrent_ids = self.console.match_torrent(options.torrent)
+
+ request_options = []
+ for opt in options.values:
+ if opt not in TORRENT_OPTIONS:
+ self.console.write('{!error!}Unknown torrent option: %s' % opt)
+ return
+ request_options.append(opt)
+ if not request_options:
+ request_options = list(TORRENT_OPTIONS)
+ request_options.append('name')
+
+ d = client.core.get_torrents_status({'id': torrent_ids}, request_options)
+ d.addCallbacks(on_torrents_status, on_torrents_status_fail)
+ return d
+
+ def _set_option(self, options):
+ deferred = defer.Deferred()
+ key = options.set
+ val = ' '.join(options.values)
+ torrent_ids = self.console.match_torrent(options.torrent)
+
+ if key not in TORRENT_OPTIONS:
+ self.console.write('{!error!}Invalid key: %s' % key)
+ return
+
+ val = TORRENT_OPTIONS[key](val)
+
+ def on_set_config(result):
+ self.console.write('{!success!}Torrent option successfully updated.')
+ deferred.callback(True)
+
+ self.console.write(
+ 'Setting %s to %s for torrents %s..' % (key, val, torrent_ids)
+ )
+ client.core.set_torrent_options(torrent_ids, {key: val}).addCallback(
+ on_set_config
+ )
+ return deferred
+
+ def complete(self, line):
+ # We use the ConsoleUI torrent tab complete method
+ return component.get('ConsoleUI').tab_complete_torrent(line)
diff --git a/deluge/ui/console/cmdline/commands/move.py b/deluge/ui/console/cmdline/commands/move.py
new file mode 100644
index 0000000..13e475e
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/move.py
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os.path
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+log = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ """Move torrents' storage location"""
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'torrent_ids',
+ metavar='<torrent-id>',
+ nargs='+',
+ help=_('One or more torrent ids'),
+ )
+ parser.add_argument(
+ 'path', metavar='<path>', help=_('The path to move the torrents to')
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ if os.path.exists(options.path) and not os.path.isdir(options.path):
+ self.console.write(
+ '{!error!}Cannot Move Download Folder: %s exists and is not a directory'
+ % options.path
+ )
+ return
+
+ ids = []
+ names = []
+ for t_id in options.torrent_ids:
+ tid = self.console.match_torrent(t_id)
+ ids.extend(tid)
+ names.append(self.console.get_torrent_name(tid))
+
+ def on_move(res):
+ msg = 'Moved "%s" to %s' % (', '.join(names), options.path)
+ self.console.write(msg)
+ log.info(msg)
+
+ d = client.core.move_storage(ids, options.path)
+ d.addCallback(on_move)
+ return d
+
+ def complete(self, line):
+ line = os.path.abspath(os.path.expanduser(line))
+ ret = []
+ if os.path.exists(line):
+ # This is a correct path, check to see if it's a directory
+ if os.path.isdir(line):
+ # Directory, so we need to show contents of directory
+ # ret.extend(os.listdir(line))
+ for f in os.listdir(line):
+ # Skip hidden
+ if f.startswith('.'):
+ continue
+ f = os.path.join(line, f)
+ if os.path.isdir(f):
+ f += '/'
+ ret.append(f)
+ else:
+ # This is a file, but we could be looking for another file that
+ # shares a common prefix.
+ for f in os.listdir(os.path.dirname(line)):
+ if f.startswith(os.path.split(line)[1]):
+ ret.append(os.path.join(os.path.dirname(line), f))
+ else:
+ # This path does not exist, so lets do a listdir on it's parent
+ # and find any matches.
+ ret = []
+ if os.path.isdir(os.path.dirname(line)):
+ for f in os.listdir(os.path.dirname(line)):
+ if f.startswith(os.path.split(line)[1]):
+ p = os.path.join(os.path.dirname(line), f)
+
+ if os.path.isdir(p):
+ p += '/'
+ ret.append(p)
+ return ret
diff --git a/deluge/ui/console/cmdline/commands/pause.py b/deluge/ui/console/cmdline/commands/pause.py
new file mode 100644
index 0000000..1f7ef31
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/pause.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Pause torrents"""
+
+ usage = 'pause [ * | <torrent-id> [<torrent-id> ...] ]'
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'torrent_ids',
+ metavar='<torrent-id>',
+ nargs='+',
+ help=_('One or more torrent ids. Use "*" to pause all torrents'),
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ if options.torrent_ids[0] == '*':
+ client.core.pause_session()
+ return
+
+ torrent_ids = []
+ for arg in options.torrent_ids:
+ torrent_ids.extend(self.console.match_torrent(arg))
+
+ if torrent_ids:
+ return client.core.pause_torrent(torrent_ids)
+
+ def complete(self, line):
+ # We use the ConsoleUI torrent tab complete method
+ return component.get('ConsoleUI').tab_complete_torrent(line)
diff --git a/deluge/ui/console/cmdline/commands/plugin.py b/deluge/ui/console/cmdline/commands/plugin.py
new file mode 100644
index 0000000..fafc77a
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/plugin.py
@@ -0,0 +1,143 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import deluge.component as component
+import deluge.configmanager
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Manage plugins"""
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '-l',
+ '--list',
+ action='store_true',
+ default=False,
+ dest='list',
+ help=_('Lists available plugins'),
+ )
+ parser.add_argument(
+ '-s',
+ '--show',
+ action='store_true',
+ default=False,
+ dest='show',
+ help=_('Shows enabled plugins'),
+ )
+ parser.add_argument(
+ '-e', '--enable', dest='enable', nargs='+', help=_('Enables a plugin')
+ )
+ parser.add_argument(
+ '-d', '--disable', dest='disable', nargs='+', help=_('Disables a plugin')
+ )
+ parser.add_argument(
+ '-r',
+ '--reload',
+ action='store_true',
+ default=False,
+ dest='reload',
+ help=_('Reload list of available plugins'),
+ )
+ parser.add_argument(
+ '-i', '--install', help=_('Install a plugin from an .egg file')
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ if options.reload:
+ client.core.pluginmanager.rescan_plugins()
+ self.console.write('{!green!}Plugin list successfully reloaded')
+ return
+
+ elif options.list:
+
+ def on_available_plugins(result):
+ self.console.write('{!info!}Available Plugins:')
+ for p in result:
+ self.console.write('{!input!} ' + p)
+
+ return client.core.get_available_plugins().addCallback(on_available_plugins)
+
+ elif options.show:
+
+ def on_enabled_plugins(result):
+ self.console.write('{!info!}Enabled Plugins:')
+ for p in result:
+ self.console.write('{!input!} ' + p)
+
+ return client.core.get_enabled_plugins().addCallback(on_enabled_plugins)
+
+ elif options.enable:
+
+ def on_available_plugins(result):
+ plugins = {}
+ for p in result:
+ plugins[p.lower()] = p
+ for arg in options.enable:
+ if arg.lower() in plugins:
+ client.core.enable_plugin(plugins[arg.lower()])
+
+ return client.core.get_available_plugins().addCallback(on_available_plugins)
+
+ elif options.disable:
+
+ def on_enabled_plugins(result):
+ plugins = {}
+ for p in result:
+ plugins[p.lower()] = p
+ for arg in options.disable:
+ if arg.lower() in plugins:
+ client.core.disable_plugin(plugins[arg.lower()])
+
+ return client.core.get_enabled_plugins().addCallback(on_enabled_plugins)
+
+ elif options.install:
+ import os.path
+ from base64 import b64encode
+ import shutil
+
+ filepath = options.install
+
+ if not os.path.exists(filepath):
+ self.console.write('{!error!}Invalid path: %s' % filepath)
+ return
+
+ config_dir = deluge.configmanager.get_config_dir()
+ filename = os.path.split(filepath)[1]
+ shutil.copyfile(filepath, os.path.join(config_dir, 'plugins', filename))
+
+ client.core.rescan_plugins()
+
+ if not client.is_localhost():
+ # We need to send this plugin to the daemon
+ with open(filepath, 'rb') as _file:
+ filedump = b64encode(_file.read())
+ try:
+ client.core.upload_plugin(filename, filedump)
+ client.core.rescan_plugins()
+ except Exception:
+ self.console.write(
+ '{!error!}An error occurred, plugin was not installed'
+ )
+
+ self.console.write(
+ '{!green!}Plugin was successfully installed: %s' % filename
+ )
+
+ def complete(self, line):
+ return component.get('ConsoleUI').tab_complete_path(
+ line, ext='.egg', sort='name', dirs_first=-1
+ )
diff --git a/deluge/ui/console/cmdline/commands/quit.py b/deluge/ui/console/cmdline/commands/quit.py
new file mode 100644
index 0000000..261a01a
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/quit.py
@@ -0,0 +1,25 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import deluge.component as component
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Exit the client"""
+
+ aliases = ['exit']
+ interactive_only = True
+
+ def handle(self, options):
+ component.get('ConsoleUI').quit()
diff --git a/deluge/ui/console/cmdline/commands/recheck.py b/deluge/ui/console/cmdline/commands/recheck.py
new file mode 100644
index 0000000..c9b6360
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/recheck.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Forces a recheck of the torrent data"""
+
+ usage = 'recheck [ * | <torrent-id> [<torrent-id> ...] ]'
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'torrent_ids',
+ metavar='<torrent-id>',
+ nargs='+',
+ help=_('One or more torrent ids'),
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ if options.torrent_ids[0] == '*':
+ client.core.force_recheck(self.console.match_torrent(''))
+ return
+
+ torrent_ids = []
+ for arg in options.torrent_ids:
+ torrent_ids.extend(self.console.match_torrent(arg))
+
+ if torrent_ids:
+ return client.core.force_recheck(torrent_ids)
+
+ def complete(self, line):
+ # We use the ConsoleUI torrent tab complete method
+ return component.get('ConsoleUI').tab_complete_torrent(line)
diff --git a/deluge/ui/console/cmdline/commands/resume.py b/deluge/ui/console/cmdline/commands/resume.py
new file mode 100644
index 0000000..1f62c5f
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/resume.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Resume torrents"""
+
+ usage = _('Usage: resume [ * | <torrent-id> [<torrent-id> ...] ]')
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'torrent_ids',
+ metavar='<torrent-id>',
+ nargs='+',
+ help=_('One or more torrent ids. Use "*" to resume all torrents'),
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+
+ if options.torrent_ids[0] == '*':
+ client.core.resume_session()
+ return
+
+ torrent_ids = []
+ for t_id in options.torrent_ids:
+ torrent_ids.extend(self.console.match_torrent(t_id))
+
+ if torrent_ids:
+ return client.core.resume_torrent(torrent_ids)
+
+ def complete(self, line):
+ # We use the ConsoleUI torrent tab complete method
+ return component.get('ConsoleUI').tab_complete_torrent(line)
diff --git a/deluge/ui/console/cmdline/commands/rm.py b/deluge/ui/console/cmdline/commands/rm.py
new file mode 100644
index 0000000..ff3125d
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/rm.py
@@ -0,0 +1,83 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+log = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ """Remove a torrent"""
+
+ aliases = ['del']
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '--remove_data',
+ action='store_true',
+ default=False,
+ help=_('Also removes the torrent data'),
+ )
+ parser.add_argument(
+ '-c',
+ '--confirm',
+ action='store_true',
+ default=False,
+ help=_('List the matching torrents without removing.'),
+ )
+ parser.add_argument(
+ 'torrent_ids',
+ metavar='<torrent-id>',
+ nargs='+',
+ help=_('One or more torrent ids'),
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+ torrent_ids = self.console.match_torrents(options.torrent_ids)
+
+ if not options.confirm:
+ self.console.write(
+ '{!info!}%d %s %s{!info!}'
+ % (
+ len(torrent_ids),
+ _n('torrent', 'torrents', len(torrent_ids)),
+ _n('match', 'matches', len(torrent_ids)),
+ )
+ )
+ for t_id in torrent_ids:
+ name = self.console.get_torrent_name(t_id)
+ self.console.write('* %-50s (%s)' % (name, t_id))
+ self.console.write(
+ _('Confirm with -c to remove the listed torrents (Count: %d)')
+ % len(torrent_ids)
+ )
+ return
+
+ def on_removed_finished(errors):
+ if errors:
+ self.console.write('Error(s) occured when trying to delete torrent(s).')
+ for t_id, e_msg in errors:
+ self.console.write('Error removing torrent %s : %s' % (t_id, e_msg))
+
+ log.info('Removing %d torrents', len(torrent_ids))
+ d = client.core.remove_torrents(torrent_ids, options.remove_data)
+ d.addCallback(on_removed_finished)
+
+ def complete(self, line):
+ # We use the ConsoleUI torrent tab complete method
+ return component.get('ConsoleUI').tab_complete_torrent(line)
diff --git a/deluge/ui/console/cmdline/commands/status.py b/deluge/ui/console/cmdline/commands/status.py
new file mode 100644
index 0000000..948ad6b
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/status.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+from twisted.internet import defer
+
+import deluge.component as component
+from deluge.common import TORRENT_STATE, fspeed
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+log = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+ """Shows various status information from the daemon"""
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ '-r',
+ '--raw',
+ action='store_true',
+ default=False,
+ dest='raw',
+ help=_(
+ 'Raw values for upload/download rates (without KiB/s suffix)'
+ '(useful for scripts that want to do their own parsing)'
+ ),
+ )
+ parser.add_argument(
+ '-n',
+ '--no-torrents',
+ action='store_false',
+ default=True,
+ dest='show_torrents',
+ help=_('Do not show torrent status (Improves command speed)'),
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+ self.status = None
+ self.torrents = 1 if options.show_torrents else 0
+ self.raw = options.raw
+
+ def on_session_status(status):
+ self.status = status
+
+ def on_torrents_status(status):
+ self.torrents = status
+
+ def on_torrents_status_fail(reason):
+ log.warning('Failed to retrieve session status: %s', reason)
+ self.torrents = -2
+
+ deferreds = []
+
+ ds = client.core.get_session_status(
+ ['num_peers', 'payload_upload_rate', 'payload_download_rate', 'dht_nodes']
+ )
+ ds.addCallback(on_session_status)
+ deferreds.append(ds)
+
+ if options.show_torrents:
+ dt = client.core.get_torrents_status({}, ['state'])
+ dt.addCallback(on_torrents_status)
+ dt.addErrback(on_torrents_status_fail)
+ deferreds.append(dt)
+
+ return defer.DeferredList(deferreds).addCallback(self.print_status)
+
+ def print_status(self, *args):
+ self.console.set_batch_write(True)
+ if self.raw:
+ self.console.write(
+ '{!info!}Total upload: %f' % self.status['payload_upload_rate']
+ )
+ self.console.write(
+ '{!info!}Total download: %f' % self.status['payload_download_rate']
+ )
+ else:
+ self.console.write(
+ '{!info!}Total upload: %s' % fspeed(self.status['payload_upload_rate'])
+ )
+ self.console.write(
+ '{!info!}Total download: %s'
+ % fspeed(self.status['payload_download_rate'])
+ )
+ self.console.write('{!info!}DHT Nodes: %i' % self.status['dht_nodes'])
+
+ if isinstance(self.torrents, int):
+ if self.torrents == -2:
+ self.console.write('{!error!}Error getting torrent info')
+ else:
+ self.console.write('{!info!}Total torrents: %i' % len(self.torrents))
+ state_counts = {}
+ for state in TORRENT_STATE:
+ state_counts[state] = 0
+ for t in self.torrents:
+ s = self.torrents[t]
+ state_counts[s['state']] += 1
+ for state in TORRENT_STATE:
+ self.console.write('{!info!} %s: %i' % (state, state_counts[state]))
+
+ self.console.set_batch_write(False)
diff --git a/deluge/ui/console/cmdline/commands/update_tracker.py b/deluge/ui/console/cmdline/commands/update_tracker.py
new file mode 100644
index 0000000..591b951
--- /dev/null
+++ b/deluge/ui/console/cmdline/commands/update_tracker.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from . import BaseCommand
+
+
+class Command(BaseCommand):
+ """Update tracker for torrent(s)"""
+
+ usage = 'update_tracker [ * | <torrent-id> [<torrent-id> ...] ]'
+ aliases = ['reannounce']
+
+ def add_arguments(self, parser):
+ parser.add_argument(
+ 'torrent_ids',
+ metavar='<torrent-id>',
+ nargs='+',
+ help='One or more torrent ids. "*" updates all torrents',
+ )
+
+ def handle(self, options):
+ self.console = component.get('ConsoleUI')
+ args = options.torrent_ids
+ if options.torrent_ids[0] == '*':
+ args = ['']
+
+ torrent_ids = []
+ for arg in args:
+ torrent_ids.extend(self.console.match_torrent(arg))
+
+ client.core.force_reannounce(torrent_ids)
+
+ def complete(self, line):
+ # We use the ConsoleUI torrent tab complete method
+ return component.get('ConsoleUI').tab_complete_torrent(line)
diff --git a/deluge/ui/console/console.py b/deluge/ui/console/console.py
new file mode 100644
index 0000000..58d31d5
--- /dev/null
+++ b/deluge/ui/console/console.py
@@ -0,0 +1,169 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+from __future__ import print_function, unicode_literals
+
+import fnmatch
+import logging
+import os
+import sys
+
+import deluge.common
+from deluge.argparserbase import ArgParserBase, DelugeTextHelpFormatter
+from deluge.ui.ui import UI
+
+log = logging.getLogger(__name__)
+
+#
+# Note: Cannot import from console.main here because it imports the twisted reactor.
+# Console is imported from console/__init__.py loaded by the script entry points
+# defined in setup.py
+#
+
+
+def load_commands(command_dir):
+ def get_command(name):
+ command = getattr(
+ __import__(
+ 'deluge.ui.console.cmdline.commands.%s' % name, {}, {}, ['Command']
+ ),
+ 'Command',
+ )()
+ command._name = name
+ return command
+
+ try:
+ dir_list = fnmatch.filter(os.listdir(command_dir), '*.py')
+ except OSError:
+ return {}
+
+ commands = []
+ for filename in dir_list:
+ if filename.startswith('_'):
+ continue
+ cmd = get_command(os.path.splitext(filename)[0])
+ for cmd_name in [cmd._name] + cmd.aliases:
+ commands.append((cmd_name, cmd))
+ return dict(commands)
+
+
+class LogStream(object):
+ out = sys.stdout
+
+ def write(self, data):
+ self.out.write(data)
+
+ def flush(self):
+ self.out.flush()
+
+
+class Console(UI):
+
+ cmd_description = """Console or command-line user interface"""
+
+ def __init__(self, *args, **kwargs):
+ super(Console, self).__init__(
+ 'console', *args, log_stream=LogStream(), **kwargs
+ )
+
+ group = self.parser.add_argument_group(
+ _('Console Options'),
+ _(
+ 'These daemon connect options will be '
+ 'used for commands, or if console ui autoconnect is enabled.'
+ ),
+ )
+ group.add_argument(
+ '-d',
+ '--daemon',
+ metavar='<ip_addr>',
+ dest='daemon_addr',
+ help=_('Deluge daemon IP address to connect to (default 127.0.0.1)'),
+ default='127.0.0.1',
+ )
+ group.add_argument(
+ '-p',
+ '--port',
+ metavar='<port>',
+ dest='daemon_port',
+ type=int,
+ help=_('Deluge daemon port to connect to (default 58846)'),
+ default='58846',
+ )
+ group.add_argument(
+ '-U',
+ '--username',
+ metavar='<user>',
+ dest='daemon_user',
+ help=_('Deluge daemon username to use when connecting'),
+ )
+ group.add_argument(
+ '-P',
+ '--password',
+ metavar='<pass>',
+ dest='daemon_pass',
+ help=_('Deluge daemon password to use when connecting'),
+ )
+ # To properly print help message for the console commands ( e.g. deluge-console info -h),
+ # we add a subparser for each command which will trigger the help/usage when given
+ from deluge.ui.console.parser import (
+ ConsoleCommandParser,
+ ) # import here because (see top)
+
+ self.console_parser = ConsoleCommandParser(
+ parents=[self.parser],
+ add_help=False,
+ prog=self.parser.prog,
+ description='Starts the Deluge console interface',
+ formatter_class=lambda prog: DelugeTextHelpFormatter(
+ prog, max_help_position=33, width=90
+ ),
+ )
+ self.parser.subparser = self.console_parser
+ self.console_parser.base_parser = self.parser
+ subparsers = self.console_parser.add_subparsers(
+ title=_('Console Commands'),
+ help=_('Description'),
+ description=_('The following console commands are available:'),
+ metavar=_('Command'),
+ dest='command',
+ )
+ from deluge.ui.console import UI_PATH # Must import here
+
+ self.console_cmds = load_commands(os.path.join(UI_PATH, 'cmdline', 'commands'))
+ for cmd in sorted(self.console_cmds):
+ self.console_cmds[cmd].add_subparser(subparsers)
+
+ def start(self):
+ if self.ui_args is None:
+ # Started directly by deluge-console script so must find the UI args manually
+ options, remaining = ArgParserBase(common_help=False).parse_known_args()
+ self.ui_args = remaining
+
+ i = self.console_parser.find_subcommand(args=self.ui_args)
+ self.console_parser.subcommand = False
+ self.parser.subcommand = False if i == -1 else True
+
+ super(Console, self).start(self.console_parser)
+ from deluge.ui.console.main import ConsoleUI # import here because (see top)
+
+ def run(options):
+ try:
+ c = ConsoleUI(self.options, self.console_cmds, self.parser.log_stream)
+ return c.start_ui()
+ except Exception as ex:
+ log.exception(ex)
+ raise
+
+ return deluge.common.run_profiled(
+ run,
+ self.options,
+ output_file=self.options.profile,
+ do_profile=self.options.profile,
+ )
diff --git a/deluge/ui/console/main.py b/deluge/ui/console/main.py
new file mode 100644
index 0000000..23965bb
--- /dev/null
+++ b/deluge/ui/console/main.py
@@ -0,0 +1,765 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import print_function, unicode_literals
+
+import locale
+import logging
+import os
+import sys
+import time
+
+from twisted.internet import defer, error, reactor
+
+import deluge.common
+import deluge.component as component
+from deluge.configmanager import ConfigManager
+from deluge.decorators import overrides
+from deluge.error import DelugeError
+from deluge.ui.client import client
+from deluge.ui.console.modes.addtorrents import AddTorrents
+from deluge.ui.console.modes.basemode import TermResizeHandler
+from deluge.ui.console.modes.cmdline import CmdLine
+from deluge.ui.console.modes.eventview import EventView
+from deluge.ui.console.modes.preferences import Preferences
+from deluge.ui.console.modes.torrentdetail import TorrentDetail
+from deluge.ui.console.modes.torrentlist.torrentlist import TorrentList
+from deluge.ui.console.utils import colors
+from deluge.ui.console.widgets import StatusBars
+from deluge.ui.coreconfig import CoreConfig
+from deluge.ui.sessionproxy import SessionProxy
+
+log = logging.getLogger(__name__)
+
+DEFAULT_CONSOLE_PREFS = {
+ 'ring_bell': False,
+ 'first_run': True,
+ 'language': '',
+ 'torrentview': {
+ 'sort_primary': 'queue',
+ 'sort_secondary': 'name',
+ 'show_sidebar': True,
+ 'sidebar_width': 25,
+ 'separate_complete': True,
+ 'move_selection': True,
+ 'columns': {},
+ },
+ 'addtorrents': {
+ 'show_misc_files': False, # TODO: Showing/hiding this
+ 'show_hidden_folders': False, # TODO: Showing/hiding this
+ 'sort_column': 'date',
+ 'reverse_sort': True,
+ 'last_path': '~',
+ },
+ 'cmdline': {
+ 'ignore_duplicate_lines': False,
+ 'third_tab_lists_all': False,
+ 'torrents_per_tab_press': 15,
+ 'save_command_history': True,
+ },
+}
+
+
+class ConsoleUI(component.Component, TermResizeHandler):
+ def __init__(self, options, cmds, log_stream):
+ component.Component.__init__(self, 'ConsoleUI')
+ TermResizeHandler.__init__(self)
+ self.options = options
+ self.log_stream = log_stream
+
+ # keep track of events for the log view
+ self.events = []
+ self.torrents = []
+ self.statusbars = None
+ self.modes = {}
+ self.active_mode = None
+ self.initialized = False
+
+ try:
+ locale.setlocale(locale.LC_ALL, '')
+ self.encoding = locale.getpreferredencoding()
+ except locale.Error:
+ self.encoding = sys.getdefaultencoding()
+
+ log.debug('Using encoding: %s', self.encoding)
+
+ # start up the session proxy
+ self.sessionproxy = SessionProxy()
+
+ client.set_disconnect_callback(self.on_client_disconnect)
+
+ # Set the interactive flag to indicate where we should print the output
+ self.interactive = True
+ self._commands = cmds
+ self.coreconfig = CoreConfig()
+
+ def start_ui(self):
+ """Start the console UI.
+
+ Note: When running console UI reactor.run() will be called which
+ effectively blocks this function making the return value
+ insignificant. However, when running unit tests, the reacor is
+ replaced by a mock object, leaving the return deferred object
+ necessary for the tests to run properly.
+
+ Returns:
+ Deferred: If valid commands are provided, a deferred that fires when
+ all commands are executed. Else None is returned.
+ """
+ if self.options.parsed_cmds:
+ self.interactive = False
+ if not self._commands:
+ print('No valid console commands found')
+ return
+
+ deferred = self.exec_args(self.options)
+ reactor.run()
+ return deferred
+ else:
+ # Interactive
+ if deluge.common.windows_check():
+ print(
+ """\nDeluge-console does not run in interactive mode on Windows. \n
+Please use commands from the command line, e.g.:\n
+ deluge-console.exe help
+ deluge-console.exe info
+ deluge-console.exe "add --help"
+ deluge-console.exe "add -p c:\\mytorrents c:\\new.torrent"
+"""
+ )
+ else:
+
+ class ConsoleLog(object):
+ def write(self, data):
+ pass
+
+ def flush(self):
+ pass
+
+ # We don't ever want log output to terminal when running in
+ # interactive mode, so insert a dummy here
+ self.log_stream.out = ConsoleLog()
+
+ # Set Esc key delay to 0 to avoid a very annoying delay
+ # due to curses waiting in case of other key are pressed
+ # after ESC is pressed
+ os.environ.setdefault('ESCDELAY', '0')
+
+ # We use the curses.wrapper function to prevent the console from getting
+ # messed up if an uncaught exception is experienced.
+ from curses import wrapper
+
+ wrapper(self.run)
+
+ def quit(self):
+ if client.connected():
+
+ def on_disconnect(result):
+ reactor.stop()
+
+ return client.disconnect().addCallback(on_disconnect)
+ else:
+ try:
+ reactor.stop()
+ except error.ReactorNotRunning:
+ pass
+
+ def exec_args(self, options):
+ """Execute console commands from command line."""
+ from deluge.ui.console.cmdline.command import Commander
+
+ commander = Commander(self._commands)
+
+ def on_connect(result):
+ def on_components_started(result):
+ def on_started(result):
+ def do_command(result, cmd):
+ return commander.do_command(cmd)
+
+ def exec_command(result, cmd):
+ return commander.exec_command(cmd)
+
+ d = defer.succeed(None)
+ for command in options.parsed_cmds:
+ if command.command in ('quit', 'exit'):
+ break
+ d.addCallback(exec_command, command)
+ d.addCallback(do_command, 'quit')
+ return d
+
+ # We need to wait for the rpcs in start() to finish before processing
+ # any of the commands.
+ self.started_deferred.addCallback(on_started)
+ return self.started_deferred
+
+ d = self.start_console()
+ d.addCallback(on_components_started)
+ return d
+
+ def on_connect_fail(reason):
+ if reason.check(DelugeError):
+ rm = reason.getErrorMessage()
+ else:
+ rm = reason.value.message
+ print(
+ 'Could not connect to daemon: %s:%s\n %s'
+ % (options.daemon_addr, options.daemon_port, rm)
+ )
+ commander.do_command('quit')
+
+ d = None
+ if not self.interactive and options.parsed_cmds[0].command == 'connect':
+ d = commander.exec_command(options.parsed_cmds.pop(0))
+ else:
+ log.info(
+ 'connect: host=%s, port=%s, username=%s, password=%s',
+ options.daemon_addr,
+ options.daemon_port,
+ options.daemon_user,
+ options.daemon_pass,
+ )
+ d = client.connect(
+ options.daemon_addr,
+ options.daemon_port,
+ options.daemon_user,
+ options.daemon_pass,
+ )
+ d.addCallback(on_connect)
+ d.addErrback(on_connect_fail)
+ return d
+
+ def run(self, stdscr):
+ """This method is called by the curses.wrapper to start the mainloop and screen.
+
+ Args:
+ stdscr (_curses.curses window): curses screen passed in from curses.wrapper.
+
+ """
+ # We want to do an interactive session, so start up the curses screen and
+ # pass it the function that handles commands
+ colors.init_colors()
+ self.stdscr = stdscr
+ self.config = ConfigManager(
+ 'console.conf', defaults=DEFAULT_CONSOLE_PREFS, file_version=2
+ )
+ self.config.run_converter((0, 1), 2, self._migrate_config_1_to_2)
+
+ self.statusbars = StatusBars()
+ from deluge.ui.console.modes.connectionmanager import ConnectionManager
+
+ self.register_mode(ConnectionManager(stdscr, self.encoding), set_mode=True)
+
+ torrentlist = self.register_mode(TorrentList(self.stdscr, self.encoding))
+ self.register_mode(CmdLine(self.stdscr, self.encoding))
+ self.register_mode(EventView(torrentlist, self.stdscr, self.encoding))
+ self.register_mode(
+ TorrentDetail(torrentlist, self.stdscr, self.config, self.encoding)
+ )
+ self.register_mode(
+ Preferences(torrentlist, self.stdscr, self.config, self.encoding)
+ )
+ self.register_mode(
+ AddTorrents(torrentlist, self.stdscr, self.config, self.encoding)
+ )
+
+ self.eventlog = EventLog()
+
+ self.active_mode.topbar = (
+ '{!status!}Deluge ' + deluge.common.get_version() + ' Console'
+ )
+ self.active_mode.bottombar = '{!status!}'
+ self.active_mode.refresh()
+ # Start the twisted mainloop
+ reactor.run()
+
+ @overrides(TermResizeHandler)
+ def on_terminal_size(self, *args):
+ rows, cols = super(ConsoleUI, self).on_terminal_size(args)
+ for mode in self.modes:
+ self.modes[mode].on_resize(rows, cols)
+
+ def register_mode(self, mode, set_mode=False):
+ self.modes[mode.mode_name] = mode
+ if set_mode:
+ self.set_mode(mode.mode_name)
+ return mode
+
+ def set_mode(self, mode_name, refresh=False):
+ log.debug('Setting console mode: %s', mode_name)
+ mode = self.modes.get(mode_name, None)
+ if mode is None:
+ log.error('Non-existent mode requested: %s', mode_name)
+ return
+ self.stdscr.erase()
+
+ if self.active_mode:
+ self.active_mode.pause()
+ d = component.pause([self.active_mode.mode_name])
+
+ def on_mode_paused(result, mode, *args):
+ from deluge.ui.console.widgets.popup import PopupsHandler
+
+ if isinstance(mode, PopupsHandler):
+ if mode.popup is not None:
+ # If popups are not removed, they are still referenced in the memory
+ # which can cause issues as the popup's screen will not be destroyed.
+ # This can lead to the popup border being visible for short periods
+ # while the current modes' screen is repainted.
+ log.error(
+ 'Mode "%s" still has popups available after being paused.'
+ ' Ensure all popups are removed on pause!',
+ mode.popup.title,
+ )
+
+ d.addCallback(on_mode_paused, self.active_mode)
+ reactor.removeReader(self.active_mode)
+
+ self.active_mode = mode
+ self.statusbars.screen = self.active_mode
+
+ # The Screen object is designed to run as a twisted reader so that it
+ # can use twisted's select poll for non-blocking user input.
+ reactor.addReader(self.active_mode)
+ self.stdscr.clear()
+
+ if self.active_mode._component_state == 'Stopped':
+ component.start([self.active_mode.mode_name])
+ else:
+ component.resume([self.active_mode.mode_name])
+
+ mode.resume()
+ if refresh:
+ mode.refresh()
+ return mode
+
+ def switch_mode(self, func, error_smg):
+ def on_stop(arg):
+ if arg and True in arg[0]:
+ func()
+ else:
+ self.messages.append(('Error', error_smg))
+
+ component.stop(['TorrentList']).addCallback(on_stop)
+
+ def is_active_mode(self, mode):
+ return mode == self.active_mode
+
+ def start_components(self):
+ def on_started(result):
+ component.pause(
+ [
+ 'TorrentList',
+ 'EventView',
+ 'AddTorrents',
+ 'TorrentDetail',
+ 'Preferences',
+ ]
+ )
+
+ if self.interactive:
+ d = component.start().addCallback(on_started)
+ else:
+ d = component.start(['SessionProxy', 'ConsoleUI', 'CoreConfig'])
+ return d
+
+ def start_console(self):
+ # Maintain a list of (torrent_id, name) for use in tab completion
+ self.started_deferred = defer.Deferred()
+
+ if not self.initialized:
+ self.initialized = True
+ d = self.start_components()
+ else:
+
+ def on_stopped(result):
+ return component.start(['SessionProxy'])
+
+ d = component.stop(['SessionProxy']).addCallback(on_stopped)
+ return d
+
+ def start(self):
+ def on_session_state(result):
+ self.torrents = []
+ self.events = []
+
+ def on_torrents_status(torrents):
+ for torrent_id, status in torrents.items():
+ self.torrents.append((torrent_id, status['name']))
+ self.started_deferred.callback(True)
+
+ client.core.get_torrents_status({'id': result}, ['name']).addCallback(
+ on_torrents_status
+ )
+
+ d = client.core.get_session_state().addCallback(on_session_state)
+
+ # Register event handlers to keep the torrent list up-to-date
+ client.register_event_handler('TorrentAddedEvent', self.on_torrent_added_event)
+ client.register_event_handler(
+ 'TorrentRemovedEvent', self.on_torrent_removed_event
+ )
+ return d
+
+ def on_torrent_added_event(self, event, from_state=False):
+ def on_torrent_status(status):
+ self.torrents.append((event, status['name']))
+
+ client.core.get_torrent_status(event, ['name']).addCallback(on_torrent_status)
+
+ def on_torrent_removed_event(self, event):
+ for index, (tid, name) in enumerate(self.torrents):
+ if event == tid:
+ del self.torrents[index]
+
+ def match_torrents(self, strings):
+ torrent_ids = []
+ for s in strings:
+ torrent_ids.extend(self.match_torrent(s))
+ return list(set(torrent_ids))
+
+ def match_torrent(self, string):
+ """
+ Returns a list of torrent_id matches for the string. It will search both
+ torrent_ids and torrent names, but will only return torrent_ids.
+
+ :param string: str, the string to match on
+
+ :returns: list of matching torrent_ids. Will return an empty list if
+ no matches are found.
+
+ """
+ deluge.common.decode_bytes(string, self.encoding)
+
+ if string == '*' or string == '':
+ return [tid for tid, name in self.torrents]
+
+ match_func = '__eq__'
+ if string.startswith('*'):
+ string = string[1:]
+ match_func = 'endswith'
+ if string.endswith('*'):
+ match_func = '__contains__' if match_func == 'endswith' else 'startswith'
+ string = string[:-1]
+
+ matches = []
+ for tid, name in self.torrents:
+ deluge.common.decode_bytes(name, self.encoding)
+ if getattr(tid, match_func, None)(string) or getattr(
+ name, match_func, None
+ )(string):
+ matches.append(tid)
+ return matches
+
+ def get_torrent_name(self, torrent_id):
+ for tid, name in self.torrents:
+ if torrent_id == tid:
+ return name
+ return None
+
+ def set_batch_write(self, batch):
+ if self.interactive and isinstance(
+ self.active_mode, deluge.ui.console.modes.cmdline.CmdLine
+ ):
+ return self.active_mode.set_batch_write(batch)
+
+ def tab_complete_torrent(self, line):
+ if self.interactive and isinstance(
+ self.active_mode, deluge.ui.console.modes.cmdline.CmdLine
+ ):
+ return self.active_mode.tab_complete_torrent(line)
+
+ def tab_complete_path(
+ self, line, path_type='file', ext='', sort='name', dirs_first=True
+ ):
+ if self.interactive and isinstance(
+ self.active_mode, deluge.ui.console.modes.cmdline.CmdLine
+ ):
+ return self.active_mode.tab_complete_path(
+ line, path_type=path_type, ext=ext, sort=sort, dirs_first=dirs_first
+ )
+
+ def on_client_disconnect(self):
+ component.stop()
+
+ def write(self, s):
+ if self.interactive:
+ if isinstance(self.active_mode, deluge.ui.console.modes.cmdline.CmdLine):
+ self.active_mode.write(s)
+ else:
+ component.get('CmdLine').add_line(s, False)
+ self.events.append(s)
+ else:
+ print(colors.strip_colors(s))
+
+ def write_event(self, s):
+ if self.interactive:
+ if isinstance(self.active_mode, deluge.ui.console.modes.cmdline.CmdLine):
+ self.events.append(s)
+ self.active_mode.write(s)
+ else:
+ component.get('CmdLine').add_line(s, False)
+ self.events.append(s)
+ else:
+ print(colors.strip_colors(s))
+
+ def _migrate_config_1_to_2(self, config):
+ """Create better structure by moving most settings out of dict root
+ and into sub categories. Some keys are also renamed to be consistent
+ with other UIs.
+ """
+
+ def move_key(source, dest, source_key, dest_key=None):
+ if dest_key is None:
+ dest_key = source_key
+ dest[dest_key] = source[source_key]
+ del source[source_key]
+
+ # These are moved to 'torrentview' sub dict
+ for k in [
+ 'sort_primary',
+ 'sort_secondary',
+ 'move_selection',
+ 'separate_complete',
+ ]:
+ move_key(config, config['torrentview'], k)
+
+ # These are moved to 'addtorrents' sub dict
+ for k in [
+ 'show_misc_files',
+ 'show_hidden_folders',
+ 'sort_column',
+ 'reverse_sort',
+ 'last_path',
+ ]:
+ move_key(config, config['addtorrents'], 'addtorrents_%s' % k, dest_key=k)
+
+ # These are moved to 'cmdline' sub dict
+ for k in [
+ 'ignore_duplicate_lines',
+ 'torrents_per_tab_press',
+ 'third_tab_lists_all',
+ ]:
+ move_key(config, config['cmdline'], k)
+
+ move_key(
+ config,
+ config['cmdline'],
+ 'save_legacy_history',
+ dest_key='save_command_history',
+ )
+
+ # Add key for localization
+ config['language'] = DEFAULT_CONSOLE_PREFS['language']
+
+ # Migrate column settings
+ columns = [
+ 'queue',
+ 'size',
+ 'state',
+ 'progress',
+ 'seeds',
+ 'peers',
+ 'downspeed',
+ 'upspeed',
+ 'eta',
+ 'ratio',
+ 'avail',
+ 'added',
+ 'tracker',
+ 'savepath',
+ 'downloaded',
+ 'uploaded',
+ 'remaining',
+ 'owner',
+ 'downloading_time',
+ 'seeding_time',
+ 'completed',
+ 'seeds_peers_ratio',
+ 'complete_seen',
+ 'down_limit',
+ 'up_limit',
+ 'shared',
+ 'name',
+ ]
+ column_name_mapping = {
+ 'downspeed': 'download_speed',
+ 'upspeed': 'upload_speed',
+ 'added': 'time_added',
+ 'savepath': 'download_location',
+ 'completed': 'completed_time',
+ 'complete_seen': 'last_seen_complete',
+ 'down_limit': 'max_download_speed',
+ 'up_limit': 'max_upload_speed',
+ 'downloading_time': 'active_time',
+ }
+
+ from deluge.ui.console.modes.torrentlist.torrentview import default_columns
+
+ # These are moved to 'torrentview.columns' sub dict
+ for k in columns:
+ column_name = column_name_mapping.get(k, k)
+ config['torrentview']['columns'][column_name] = {}
+ if k == 'name':
+ config['torrentview']['columns'][column_name]['visible'] = True
+ else:
+ move_key(
+ config,
+ config['torrentview']['columns'][column_name],
+ 'show_%s' % k,
+ dest_key='visible',
+ )
+ move_key(
+ config,
+ config['torrentview']['columns'][column_name],
+ '%s_width' % k,
+ dest_key='width',
+ )
+ config['torrentview']['columns'][column_name]['order'] = default_columns[
+ column_name
+ ]['order']
+
+ return config
+
+
+class EventLog(component.Component):
+ """
+ Prints out certain events as they are received from the core.
+ """
+
+ def __init__(self):
+ component.Component.__init__(self, 'EventLog')
+ self.console = component.get('ConsoleUI')
+ self.prefix = '{!event!}* [%H:%M:%S] '
+ self.date_change_format = 'On {!yellow!}%a, %d %b %Y{!input!} %Z:'
+
+ client.register_event_handler('TorrentAddedEvent', self.on_torrent_added_event)
+ client.register_event_handler(
+ 'PreTorrentRemovedEvent', self.on_torrent_removed_event
+ )
+ client.register_event_handler(
+ 'TorrentStateChangedEvent', self.on_torrent_state_changed_event
+ )
+ client.register_event_handler(
+ 'TorrentFinishedEvent', self.on_torrent_finished_event
+ )
+ client.register_event_handler(
+ 'NewVersionAvailableEvent', self.on_new_version_available_event
+ )
+ client.register_event_handler(
+ 'SessionPausedEvent', self.on_session_paused_event
+ )
+ client.register_event_handler(
+ 'SessionResumedEvent', self.on_session_resumed_event
+ )
+ client.register_event_handler(
+ 'ConfigValueChangedEvent', self.on_config_value_changed_event
+ )
+ client.register_event_handler(
+ 'PluginEnabledEvent', self.on_plugin_enabled_event
+ )
+ client.register_event_handler(
+ 'PluginDisabledEvent', self.on_plugin_disabled_event
+ )
+
+ self.previous_time = time.localtime(0)
+
+ def on_torrent_added_event(self, torrent_id, from_state):
+ if from_state:
+ return
+
+ def on_torrent_status(status):
+ self.write(
+ '{!green!}Torrent Added: {!info!}%s ({!cyan!}%s{!info!})'
+ % (status['name'], torrent_id)
+ )
+ # Write out what state the added torrent took
+ self.on_torrent_state_changed_event(torrent_id, status['state'])
+
+ client.core.get_torrent_status(torrent_id, ['name', 'state']).addCallback(
+ on_torrent_status
+ )
+
+ def on_torrent_removed_event(self, torrent_id):
+ self.write(
+ '{!red!}Torrent Removed: {!info!}%s ({!cyan!}%s{!info!})'
+ % (self.console.get_torrent_name(torrent_id), torrent_id)
+ )
+
+ def on_torrent_state_changed_event(self, torrent_id, state):
+ # It's probably a new torrent, ignore it
+ if not state:
+ return
+ # Modify the state string color
+ if state in colors.state_color:
+ state = colors.state_color[state] + state
+
+ t_name = self.console.get_torrent_name(torrent_id)
+
+ # Again, it's most likely a new torrent
+ if not t_name:
+ return
+
+ self.write('%s: {!info!}%s ({!cyan!}%s{!info!})' % (state, t_name, torrent_id))
+
+ def on_torrent_finished_event(self, torrent_id):
+ if component.get('TorrentList').config['ring_bell']:
+ import curses.beep
+
+ curses.beep()
+ self.write(
+ '{!info!}Torrent Finished: %s ({!cyan!}%s{!info!})'
+ % (self.console.get_torrent_name(torrent_id), torrent_id)
+ )
+
+ def on_new_version_available_event(self, version):
+ self.write('{!input!}New Deluge version available: {!info!}%s' % (version))
+
+ def on_session_paused_event(self):
+ self.write('{!input!}Session Paused')
+
+ def on_session_resumed_event(self):
+ self.write('{!green!}Session Resumed')
+
+ def on_config_value_changed_event(self, key, value):
+ color = '{!white,black,bold!}'
+ try:
+ color = colors.type_color[type(value)]
+ except KeyError:
+ pass
+
+ self.write('ConfigValueChanged: {!input!}%s: %s%s' % (key, color, value))
+
+ def write(self, s):
+ current_time = time.localtime()
+
+ date_different = False
+ for field in ['tm_mday', 'tm_mon', 'tm_year']:
+ c = getattr(current_time, field)
+ p = getattr(self.previous_time, field)
+ if c != p:
+ date_different = True
+
+ if date_different:
+ string = time.strftime(self.date_change_format)
+ if deluge.common.PY2:
+ string = string.decode()
+ self.console.write_event(' ')
+ self.console.write_event(string)
+
+ p = time.strftime(self.prefix)
+
+ self.console.write_event(p + s)
+ self.previous_time = current_time
+
+ def on_plugin_enabled_event(self, name):
+ self.write('PluginEnabled: {!info!}%s' % name)
+
+ def on_plugin_disabled_event(self, name):
+ self.write('PluginDisabled: {!info!}%s' % name)
diff --git a/deluge/ui/console/modes/__init__.py b/deluge/ui/console/modes/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/deluge/ui/console/modes/__init__.py
diff --git a/deluge/ui/console/modes/add_util.py b/deluge/ui/console/modes/add_util.py
new file mode 100644
index 0000000..88a24d0
--- /dev/null
+++ b/deluge/ui/console/modes/add_util.py
@@ -0,0 +1,97 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import glob
+import logging
+import os
+from base64 import b64encode
+
+from six import unichr as chr
+
+import deluge.common
+from deluge.ui.client import client
+from deluge.ui.common import TorrentInfo
+
+log = logging.getLogger(__name__)
+
+
+def _bracket_fixup(path):
+ if path.find('[') == -1 and path.find(']') == -1:
+ return path
+ sentinal = 256
+ while path.find(chr(sentinal)) != -1:
+ sentinal += 1
+ if sentinal > 65535:
+ log.error(
+ 'Cannot fix brackets in path, path contains all possible sentinal characters'
+ )
+ return path
+ newpath = path.replace(']', chr(sentinal))
+ newpath = newpath.replace('[', '[[]')
+ newpath = newpath.replace(chr(sentinal), '[]]')
+ return newpath
+
+
+def add_torrent(t_file, options, success_cb, fail_cb, ress):
+ t_options = {}
+ if options['path']:
+ t_options['download_location'] = os.path.expanduser(options['path'])
+ t_options['add_paused'] = options['add_paused']
+
+ is_url = (options['path_type'] != 1) and (
+ deluge.common.is_url(t_file) or options['path_type'] == 2
+ )
+ is_magnet = (
+ not (is_url) and (options['path_type'] != 1) and deluge.common.is_magnet(t_file)
+ )
+
+ if is_url or is_magnet:
+ files = [t_file]
+ else:
+ files = glob.glob(_bracket_fixup(t_file))
+ num_files = len(files)
+ ress['total'] = num_files
+
+ if num_files <= 0:
+ fail_cb('Does not exist', t_file, ress)
+
+ for f in files:
+ if is_url:
+ client.core.add_torrent_url(f, t_options).addCallback(
+ success_cb, f, ress
+ ).addErrback(fail_cb, f, ress)
+ elif is_magnet:
+ client.core.add_torrent_magnet(f, t_options).addCallback(
+ success_cb, f, ress
+ ).addErrback(fail_cb, f, ress)
+ else:
+ if not os.path.exists(f):
+ fail_cb('Does not exist', f, ress)
+ continue
+ if not os.path.isfile(f):
+ fail_cb('Is a directory', f, ress)
+ continue
+
+ try:
+ TorrentInfo(f)
+ except Exception as ex:
+ fail_cb(ex.message, f, ress)
+ continue
+
+ filename = os.path.split(f)[-1]
+ with open(f, 'rb') as _file:
+ filedump = b64encode(_file.read())
+
+ client.core.add_torrent_file_async(
+ filename, filedump, t_options
+ ).addCallback(success_cb, f, ress).addErrback(fail_cb, f, ress)
diff --git a/deluge/ui/console/modes/addtorrents.py b/deluge/ui/console/modes/addtorrents.py
new file mode 100644
index 0000000..6b2c105
--- /dev/null
+++ b/deluge/ui/console/modes/addtorrents.py
@@ -0,0 +1,545 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2012 Arek Stefański <asmageddon@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os
+from base64 import b64encode
+
+import deluge.common
+import deluge.component as component
+from deluge.decorators import overrides
+from deluge.ui.client import client
+from deluge.ui.console.modes.basemode import BaseMode
+from deluge.ui.console.modes.torrentlist.add_torrents_popup import report_add_status
+from deluge.ui.console.utils import curses_util as util
+from deluge.ui.console.utils import format_utils
+from deluge.ui.console.widgets.popup import InputPopup, MessagePopup
+
+try:
+ from future_builtins import zip
+except ImportError:
+ # Ignore on Py3.
+ pass
+
+try:
+ import curses
+except ImportError:
+ pass
+
+log = logging.getLogger(__name__)
+
+# Big help string that gets displayed when the user hits 'h'
+HELP_STR = """\
+This screen allows you to browse and add torrent files located on your \
+hard disk. Currently selected file is highlighted with a white background.
+You can change the selected file using the up/down arrows or the \
+PgUp/PgDown keys. Home and End keys go to the first and last file \
+in current directory respectively.
+
+Select files with the 'm' key. Use 'M' for multi-selection. Press \
+enter key to add them to session.
+
+{!info!}'h'{!normal!} - Show this help
+
+{!info!}'<'{!normal!} and {!info!}'>'{!normal!} - Change sort column and/or order
+
+{!info!}'m'{!normal!} - Mark or unmark currently highlighted file
+{!info!}'M'{!normal!} - Mark all files between current file and last selection.
+{!info!}'c'{!normal!} - Clear selection.
+
+{!info!}Left Arrow{!normal!} - Go up in directory hierarchy.
+{!info!}Right Arrow{!normal!} - Enter currently highlighted folder.
+
+{!info!}Enter{!normal!} - Enter currently highlighted folder or add torrents \
+if a file is highlighted
+
+{!info!}'q'{!normal!} - Go back to torrent overview
+"""
+
+
+class AddTorrents(BaseMode):
+ def __init__(self, parent_mode, stdscr, console_config, encoding=None):
+ self.console_config = console_config
+ self.parent_mode = parent_mode
+ self.popup = None
+ self.view_offset = 0
+ self.cursel = 0
+ self.marked = set()
+ self.last_mark = -1
+
+ path = os.path.expanduser(self.console_config['addtorrents']['last_path'])
+
+ self.path_stack = ['/'] + path.strip('/').split('/')
+ self.path_stack_pos = len(self.path_stack)
+ self.listing_files = []
+ self.listing_dirs = []
+
+ self.raw_rows = []
+ self.raw_rows_files = []
+ self.raw_rows_dirs = []
+ self.formatted_rows = []
+
+ self.sort_column = self.console_config['addtorrents']['sort_column']
+ self.reverse_sort = self.console_config['addtorrents']['reverse_sort']
+
+ BaseMode.__init__(self, stdscr, encoding)
+
+ self._listing_space = self.rows - 5
+ self.__refresh_listing()
+
+ util.safe_curs_set(util.Curser.INVISIBLE)
+ self.stdscr.notimeout(0)
+
+ @overrides(component.Component)
+ def start(self):
+ pass
+
+ @overrides(component.Component)
+ def update(self):
+ pass
+
+ def __refresh_listing(self):
+ path = os.path.join(*self.path_stack[: self.path_stack_pos])
+
+ listing = os.listdir(path)
+
+ self.listing_files = []
+ self.listing_dirs = []
+
+ self.raw_rows = []
+ self.raw_rows_files = []
+ self.raw_rows_dirs = []
+ self.formatted_rows = []
+
+ for f in listing:
+ if os.path.isdir(os.path.join(path, f)):
+ if self.console_config['addtorrents']['show_hidden_folders']:
+ self.listing_dirs.append(f)
+ elif f[0] != '.':
+ self.listing_dirs.append(f)
+ elif os.path.isfile(os.path.join(path, f)):
+ if self.console_config['addtorrents']['show_misc_files']:
+ self.listing_files.append(f)
+ elif f.endswith('.torrent'):
+ self.listing_files.append(f)
+
+ for dirname in self.listing_dirs:
+ row = []
+ full_path = os.path.join(path, dirname)
+ try:
+ size = len(os.listdir(full_path))
+ except OSError:
+ size = -1
+ time = os.stat(full_path).st_mtime
+
+ row = [dirname, size, time, full_path, 1]
+
+ self.raw_rows.append(row)
+ self.raw_rows_dirs.append(row)
+
+ # Highlight the directory we came from
+ if self.path_stack_pos < len(self.path_stack):
+ selected = self.path_stack[self.path_stack_pos]
+ ld = sorted(self.listing_dirs, key=lambda n: n.lower())
+ c = ld.index(selected)
+ self.cursel = c
+
+ if (self.view_offset + self._listing_space) <= self.cursel:
+ self.view_offset = self.cursel - self._listing_space
+
+ for filename in self.listing_files:
+ row = []
+ full_path = os.path.join(path, filename)
+ size = os.stat(full_path).st_size
+ time = os.stat(full_path).st_mtime
+
+ row = [filename, size, time, full_path, 0]
+
+ self.raw_rows.append(row)
+ self.raw_rows_files.append(row)
+
+ self.__sort_rows()
+
+ def __sort_rows(self):
+ self.console_config['addtorrents']['sort_column'] = self.sort_column
+ self.console_config['addtorrents']['reverse_sort'] = self.reverse_sort
+ self.console_config.save()
+
+ self.raw_rows_dirs.sort(key=lambda r: r[0].lower())
+
+ if self.sort_column == 'name':
+ self.raw_rows_files.sort(
+ key=lambda r: r[0].lower(), reverse=self.reverse_sort
+ )
+ elif self.sort_column == 'date':
+ self.raw_rows_files.sort(key=lambda r: r[2], reverse=self.reverse_sort)
+ self.raw_rows = self.raw_rows_dirs + self.raw_rows_files
+ self.__refresh_rows()
+
+ def __refresh_rows(self):
+ self.formatted_rows = []
+
+ for row in self.raw_rows:
+ filename = deluge.common.decode_bytes(row[0])
+ size = row[1]
+ time = row[2]
+
+ if row[4]:
+ if size != -1:
+ size_str = '%i items' % size
+ else:
+ size_str = ' unknown'
+
+ cols = [filename, size_str, deluge.common.fdate(time)]
+ widths = [self.cols - 35, 12, 23]
+ self.formatted_rows.append(format_utils.format_row(cols, widths))
+ else:
+ # Size of .torrent file itself couldn't matter less so we'll leave it out
+ cols = [filename, deluge.common.fdate(time)]
+ widths = [self.cols - 23, 23]
+ self.formatted_rows.append(format_utils.format_row(cols, widths))
+
+ def scroll_list_up(self, distance):
+ self.cursel -= distance
+ if self.cursel < 0:
+ self.cursel = 0
+
+ if self.cursel < self.view_offset + 1:
+ self.view_offset = max(self.cursel - 1, 0)
+
+ def scroll_list_down(self, distance):
+ self.cursel += distance
+ if self.cursel >= len(self.formatted_rows):
+ self.cursel = len(self.formatted_rows) - 1
+
+ if (self.view_offset + self._listing_space) <= self.cursel + 1:
+ self.view_offset = self.cursel - self._listing_space + 1
+
+ def set_popup(self, pu):
+ self.popup = pu
+ self.refresh()
+
+ @overrides(BaseMode)
+ def on_resize(self, rows, cols):
+ BaseMode.on_resize(self, rows, cols)
+ if self.popup:
+ self.popup.handle_resize()
+ self._listing_space = self.rows - 5
+ self.refresh()
+
+ def refresh(self, lines=None):
+ if self.mode_paused():
+ return
+
+ # Update the status bars
+ self.stdscr.erase()
+ self.draw_statusbars()
+
+ off = 1
+
+ # Render breadcrumbs
+ s = 'Location: '
+ for i, e in enumerate(self.path_stack):
+ if e == '/':
+ if i == self.path_stack_pos - 1:
+ s += '{!black,red,bold!}root'
+ else:
+ s += '{!red,black,bold!}root'
+ else:
+ if i == self.path_stack_pos - 1:
+ s += '{!black,white,bold!}%s' % e
+ else:
+ s += '{!white,black,bold!}%s' % e
+
+ if e != len(self.path_stack) - 1:
+ s += '{!white,black!}/'
+
+ self.add_string(off, s)
+ off += 1
+
+ # Render header
+ cols = ['Name', 'Contents', 'Modification time']
+ widths = [self.cols - 35, 12, 23]
+ s = ''
+ for i, (c, w) in enumerate(zip(cols, widths)):
+ cn = ''
+ if i == 0:
+ cn = 'name'
+ elif i == 2:
+ cn = 'date'
+
+ if cn == self.sort_column:
+ s += '{!black,green,bold!}' + c.ljust(w - 2)
+ if self.reverse_sort:
+ s += '^ '
+ else:
+ s += 'v '
+ else:
+ s += '{!green,black,bold!}' + c.ljust(w)
+ self.add_string(off, s)
+ off += 1
+
+ # Render files and folders
+ for i, row in enumerate(self.formatted_rows[self.view_offset :]):
+ i += self.view_offset
+ # It's a folder
+ color_string = ''
+ if self.raw_rows[i][4]:
+ if self.raw_rows[i][1] == -1:
+ if i == self.cursel:
+ color_string = '{!black,red,bold!}'
+ else:
+ color_string = '{!red,black!}'
+ else:
+ if i == self.cursel:
+ color_string = '{!black,cyan,bold!}'
+ else:
+ color_string = '{!cyan,black!}'
+
+ elif i == self.cursel:
+ if self.raw_rows[i][0] in self.marked:
+ color_string = '{!blue,white,bold!}'
+ else:
+ color_string = '{!black,white,bold!}'
+ elif self.raw_rows[i][0] in self.marked:
+ color_string = '{!white,blue,bold!}'
+
+ self.add_string(off, color_string + row)
+ off += 1
+
+ if off > self.rows - 2:
+ break
+
+ if not component.get('ConsoleUI').is_active_mode(self):
+ return
+
+ self.stdscr.noutrefresh()
+
+ if self.popup:
+ self.popup.refresh()
+
+ curses.doupdate()
+
+ def back_to_overview(self):
+ self.parent_mode.go_top = False
+ component.get('ConsoleUI').set_mode(self.parent_mode.mode_name)
+
+ def _perform_action(self):
+ if self.cursel < len(self.listing_dirs):
+ self._enter_dir()
+ else:
+ s = self.raw_rows[self.cursel][0]
+ if s not in self.marked:
+ self.last_mark = self.cursel
+ self.marked.add(s)
+ self._show_add_dialog()
+
+ def _enter_dir(self):
+ # Enter currently selected directory
+ dirname = self.raw_rows[self.cursel][0]
+ new_dir = self.path_stack_pos >= len(self.path_stack)
+ new_dir = new_dir or (dirname != self.path_stack[self.path_stack_pos])
+ if new_dir:
+ self.path_stack = self.path_stack[: self.path_stack_pos]
+ self.path_stack.append(dirname)
+
+ path = os.path.join(*self.path_stack[: self.path_stack_pos + 1])
+
+ if not os.access(path, os.R_OK):
+ self.path_stack = self.path_stack[: self.path_stack_pos]
+ self.popup = MessagePopup(
+ self, 'Error', '{!error!}Access denied: %s' % path
+ )
+ self.__refresh_listing()
+ return
+
+ self.path_stack_pos += 1
+
+ self.view_offset = 0
+ self.cursel = 0
+ self.last_mark = -1
+ self.marked = set()
+
+ self.__refresh_listing()
+
+ def _show_add_dialog(self):
+ def _do_add(result, **kwargs):
+ ress = {'succ': 0, 'fail': 0, 'total': len(self.marked), 'fmsg': []}
+
+ def fail_cb(msg, t_file, ress):
+ log.debug('failed to add torrent: %s: %s', t_file, msg)
+ ress['fail'] += 1
+ ress['fmsg'].append('{!input!} * %s: {!error!}%s' % (t_file, msg))
+ if (ress['succ'] + ress['fail']) >= ress['total']:
+ report_add_status(
+ component.get('TorrentList'),
+ ress['succ'],
+ ress['fail'],
+ ress['fmsg'],
+ )
+
+ def success_cb(tid, t_file, ress):
+ if tid:
+ log.debug('added torrent: %s (%s)', t_file, tid)
+ ress['succ'] += 1
+ if (ress['succ'] + ress['fail']) >= ress['total']:
+ report_add_status(
+ component.get('TorrentList'),
+ ress['succ'],
+ ress['fail'],
+ ress['fmsg'],
+ )
+ else:
+ fail_cb('Already in session (probably)', t_file, ress)
+
+ for m in self.marked:
+ filename = m
+ directory = os.path.join(*self.path_stack[: self.path_stack_pos])
+ path = os.path.join(directory, filename)
+ with open(path, 'rb') as _file:
+ filedump = b64encode(_file.read())
+ t_options = {}
+ if result['location']['value']:
+ t_options['download_location'] = result['location']['value']
+ t_options['add_paused'] = result['add_paused']['value']
+
+ d = client.core.add_torrent_file_async(filename, filedump, t_options)
+ d.addCallback(success_cb, filename, ress)
+ d.addErrback(fail_cb, filename, ress)
+
+ self.console_config['addtorrents']['last_path'] = os.path.join(
+ *self.path_stack[: self.path_stack_pos]
+ )
+ self.console_config.save()
+
+ self.back_to_overview()
+
+ config = component.get('ConsoleUI').coreconfig
+ if config['add_paused']:
+ ap = 0
+ else:
+ ap = 1
+ self.popup = InputPopup(
+ self, 'Add Torrents (Esc to cancel)', close_cb=_do_add, height_req=17
+ )
+
+ msg = 'Adding torrent files:'
+ for i, m in enumerate(self.marked):
+ name = m
+ msg += '\n * {!input!}%s' % name
+ if i == 5:
+ if i < len(self.marked):
+ msg += '\n {!red!}And %i more' % (len(self.marked) - 5)
+ break
+ self.popup.add_text(msg)
+ self.popup.add_spaces(1)
+
+ self.popup.add_text_input(
+ 'location', 'Download Folder:', config['download_location'], complete=True
+ )
+ self.popup.add_select_input(
+ 'add_paused', 'Add Paused:', ['Yes', 'No'], [True, False], ap
+ )
+
+ def _go_up(self):
+ # Go up in directory hierarchy
+ if self.path_stack_pos > 1:
+ self.path_stack_pos -= 1
+
+ self.view_offset = 0
+ self.cursel = 0
+ self.last_mark = -1
+ self.marked = set()
+
+ self.__refresh_listing()
+
+ def read_input(self):
+ c = self.stdscr.getch()
+
+ if self.popup:
+ if self.popup.handle_read(c):
+ self.popup = None
+ self.refresh()
+ return
+
+ if util.is_printable_chr(c):
+ if chr(c) == 'Q':
+ component.get('ConsoleUI').quit()
+ elif chr(c) == 'q':
+ self.back_to_overview()
+ return
+
+ # Navigate the torrent list
+ if c == curses.KEY_UP:
+ self.scroll_list_up(1)
+ elif c == curses.KEY_PPAGE:
+ self.scroll_list_up(self.rows // 2)
+ elif c == curses.KEY_HOME:
+ self.scroll_list_up(len(self.formatted_rows))
+ elif c == curses.KEY_DOWN:
+ self.scroll_list_down(1)
+ elif c == curses.KEY_NPAGE:
+ self.scroll_list_down(self.rows // 2)
+ elif c == curses.KEY_END:
+ self.scroll_list_down(len(self.formatted_rows))
+ elif c == curses.KEY_RIGHT:
+ if self.cursel < len(self.listing_dirs):
+ self._enter_dir()
+ elif c == curses.KEY_LEFT:
+ self._go_up()
+ elif c in [curses.KEY_ENTER, util.KEY_ENTER2]:
+ self._perform_action()
+ elif c == util.KEY_ESC:
+ self.back_to_overview()
+ else:
+ if util.is_printable_chr(c):
+ if chr(c) == 'h':
+ self.popup = MessagePopup(self, 'Help', HELP_STR, width_req=0.75)
+ elif chr(c) == '>':
+ if self.sort_column == 'date':
+ self.reverse_sort = not self.reverse_sort
+ else:
+ self.sort_column = 'date'
+ self.reverse_sort = True
+ self.__sort_rows()
+ elif chr(c) == '<':
+ if self.sort_column == 'name':
+ self.reverse_sort = not self.reverse_sort
+ else:
+ self.sort_column = 'name'
+ self.reverse_sort = False
+ self.__sort_rows()
+ elif chr(c) == 'm':
+ s = self.raw_rows[self.cursel][0]
+ if s in self.marked:
+ self.marked.remove(s)
+ else:
+ self.marked.add(s)
+
+ self.last_mark = self.cursel
+ elif chr(c) == 'j':
+ self.scroll_list_up(1)
+ elif chr(c) == 'k':
+ self.scroll_list_down(1)
+ elif chr(c) == 'M':
+ if self.last_mark != -1:
+ if self.last_mark > self.cursel:
+ m = list(range(self.cursel, self.last_mark))
+ else:
+ m = list(range(self.last_mark, self.cursel + 1))
+
+ for i in m:
+ s = self.raw_rows[i][0]
+ self.marked.add(s)
+ elif chr(c) == 'c':
+ self.marked.clear()
+
+ self.refresh()
diff --git a/deluge/ui/console/modes/basemode.py b/deluge/ui/console/modes/basemode.py
new file mode 100644
index 0000000..dd3681f
--- /dev/null
+++ b/deluge/ui/console/modes/basemode.py
@@ -0,0 +1,357 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import sys
+
+import deluge.component as component
+import deluge.ui.console.utils.colors as colors
+from deluge.ui.console.utils import curses_util as util
+from deluge.ui.console.utils.format_utils import remove_formatting
+
+try:
+ import curses
+ import curses.panel
+except ImportError:
+ pass
+
+try:
+ import signal
+ from fcntl import ioctl
+ import termios
+ import struct
+except ImportError:
+ pass
+
+
+log = logging.getLogger(__name__)
+
+
+class InputKeyHandler(object):
+ def __init__(self):
+ self._input_result = None
+
+ def set_input_result(self, result):
+ self._input_result = result
+
+ def get_input_result(self):
+ result = self._input_result
+ self._input_result = None
+ return result
+
+ def handle_read(self, c):
+ """Handle a character read from curses screen
+
+ Returns:
+ int: One of the constants defined in util.curses_util.ReadState.
+ ReadState.IGNORED: The key was not handled. Further processing should continue.
+ ReadState.READ: The key was read and processed. Do no further processing
+ ReadState.CHANGED: The key was read and processed. Internal state was changed
+ leaving data to be read by the caller.
+
+ """
+ return util.ReadState.IGNORED
+
+
+class TermResizeHandler(object):
+ def __init__(self):
+ try:
+ signal.signal(signal.SIGWINCH, self.on_terminal_size)
+ except ValueError as ex:
+ log.debug('Unable to catch SIGWINCH signal: %s', ex)
+
+ def on_terminal_size(self, *args):
+ # Get the new rows and cols value
+ rows, cols = struct.unpack('hhhh', ioctl(0, termios.TIOCGWINSZ, b'\000' * 8))[
+ 0:2
+ ]
+ curses.resizeterm(rows, cols)
+ return rows, cols
+
+
+class CursesStdIO(object):
+ """
+ fake fd to be registered as a reader with the twisted reactor.
+ Curses classes needing input should extend this
+ """
+
+ def fileno(self):
+ """ We want to select on FD 0 """
+ return 0
+
+ def doRead(self): # NOQA: N802
+ """called when input is ready"""
+ pass
+
+ def logPrefix(self): # NOQA: N802
+ return 'CursesClient'
+
+
+class BaseMode(CursesStdIO, component.Component):
+ def __init__(
+ self, stdscr, encoding=None, do_refresh=True, mode_name=None, depend=None
+ ):
+ """
+ A mode that provides a curses screen designed to run as a reader in a twisted reactor.
+ This mode doesn't do much, just shows status bars and "Base Mode" on the screen
+
+ Modes should subclass this and provide overrides for:
+
+ do_read(self) - Handle user input
+ refresh(self) - draw the mode to the screen
+ add_string(self, row, string) - add a string of text to be displayed.
+ see method for detailed info
+
+ The init method of a subclass *must* call BaseMode.__init__
+
+ Useful fields after calling BaseMode.__init__:
+ self.stdscr - the curses screen
+ self.rows - # of rows on the curses screen
+ self.cols - # of cols on the curses screen
+ self.topbar - top statusbar
+ self.bottombar - bottom statusbar
+ """
+ self.mode_name = mode_name if mode_name else self.__class__.__name__
+ component.Component.__init__(self, self.mode_name, 1, depend=depend)
+ self.stdscr = stdscr
+ # Make the input calls non-blocking
+ self.stdscr.nodelay(1)
+
+ self.paused = False
+ # Strings for the 2 status bars
+ self.statusbars = component.get('StatusBars')
+ self.help_hstr = '{!status!} Press {!magenta,blue,bold!}[h]{!status!} for help'
+
+ # Keep track of the screen size
+ self.rows, self.cols = self.stdscr.getmaxyx()
+
+ if not encoding:
+ self.encoding = sys.getdefaultencoding()
+ else:
+ self.encoding = encoding
+
+ # Do a refresh right away to draw the screen
+ if do_refresh:
+ self.refresh()
+
+ def on_resize(self, rows, cols):
+ self.rows, self.cols = rows, cols
+
+ def connectionLost(self, reason): # NOQA: N802
+ self.close()
+
+ def add_string(self, row, string, scr=None, **kwargs):
+ if scr:
+ screen = scr
+ else:
+ screen = self.stdscr
+
+ return add_string(row, string, screen, self.encoding, **kwargs)
+
+ def draw_statusbars(
+ self,
+ top_row=0,
+ bottom_row=-1,
+ topbar=None,
+ bottombar=None,
+ bottombar_help=True,
+ scr=None,
+ ):
+ self.add_string(top_row, topbar if topbar else self.statusbars.topbar, scr=scr)
+ bottombar = bottombar if bottombar else self.statusbars.bottombar
+ if bottombar_help:
+ if bottombar_help is True:
+ bottombar_help = self.help_hstr
+ bottombar += (
+ ' '
+ * (
+ self.cols
+ - len(remove_formatting(bottombar))
+ - len(remove_formatting(bottombar_help))
+ )
+ + bottombar_help
+ )
+ self.add_string(self.rows + bottom_row, bottombar, scr=scr)
+
+ # This mode doesn't do anything with popups
+ def set_popup(self, popup):
+ pass
+
+ def pause(self):
+ self.paused = True
+
+ def mode_paused(self):
+ return self.paused
+
+ def resume(self):
+ self.paused = False
+ self.refresh()
+
+ def refresh(self):
+ """
+ Refreshes the screen.
+ Updates the lines based on the`:attr:lines` based on the `:attr:display_lines_offset`
+ attribute and the status bars.
+ """
+ self.stdscr.erase()
+ self.draw_statusbars()
+ # Update the status bars
+
+ self.add_string(1, '{!info!}Base Mode (or subclass has not overridden refresh)')
+
+ self.stdscr.redrawwin()
+ self.stdscr.refresh()
+
+ def doRead(self): # NOQA: N802
+ """
+ Called when there is data to be read, ie, input from the keyboard.
+ """
+ # We wrap this function to catch exceptions and shutdown the mainloop
+ try:
+ self.read_input()
+ except Exception as ex: # pylint: disable=broad-except
+ log.exception(ex)
+
+ def read_input(self):
+ # Read the character
+ self.stdscr.getch()
+ self.stdscr.refresh()
+
+ def close(self):
+ """
+ Clean up the curses stuff on exit.
+ """
+ curses.nocbreak()
+ self.stdscr.keypad(0)
+ curses.echo()
+ curses.endwin()
+
+
+def add_string(
+ row, fstring, screen, encoding, col=0, pad=True, pad_char=' ', trim='..', leaveok=0
+):
+ """
+ Adds a string to the desired `:param:row`.
+
+ Args:
+ row(int): the row number to write the string
+ row(int): the row number to write the string
+ fstring(str): the (formatted) string of text to add
+ scr(curses.window): optional window to add string to instead of self.stdscr
+ col(int): optional starting column offset
+ pad(bool): optional bool if the string should be padded out to the width of the screen
+ trim(bool): optional bool if the string should be trimmed if it is too wide for the screen
+
+ The text can be formatted with color using the following format:
+
+ "{!fg, bg, attributes, ...!}"
+
+ See: http://docs.python.org/library/curses.html#constants for attributes.
+
+ Alternatively, it can use some built-in scheme for coloring.
+ See colors.py for built-in schemes.
+
+ "{!scheme!}"
+
+ Examples:
+
+ "{!blue, black, bold!}My Text is {!white, black!}cool"
+ "{!info!}I am some info text!"
+ "{!error!}Uh oh!"
+
+ Returns:
+ int: the next row
+
+ """
+ try:
+ parsed = colors.parse_color_string(fstring)
+ except colors.BadColorString as ex:
+ log.error('Cannot add bad color string %s: %s', fstring, ex)
+ return
+
+ if leaveok:
+ screen.leaveok(leaveok)
+
+ max_y, max_x = screen.getmaxyx()
+ for index, (color, string) in enumerate(parsed):
+ # Skip printing chars beyond max_x
+ if col >= max_x:
+ break
+
+ if index + 1 == len(parsed) and pad:
+ # This is the last string so lets append some padding to it
+ string += pad_char * (max_x - (col + len(string)))
+
+ if col + len(string) > max_x:
+ remaining_chrs = max(0, max_x - col)
+ if trim:
+ string = string[0 : max(0, remaining_chrs - len(trim))] + trim
+ else:
+ string = string[0:remaining_chrs]
+
+ try:
+ screen.addstr(row, col, string.encode(encoding), color)
+ except curses.error:
+ # Ignore exception for writing offscreen.
+ pass
+
+ col += len(string)
+
+ if leaveok:
+ screen.leaveok(0)
+
+ return row + 1
+
+
+def mkpanel(color, rows, cols, tly, tlx):
+ win = curses.newwin(rows, cols, tly, tlx)
+ pan = curses.panel.new_panel(win)
+ if curses.has_colors():
+ win.bkgdset(ord(' '), curses.color_pair(color))
+ else:
+ win.bkgdset(ord(' '), curses.A_BOLD)
+ return pan
+
+
+def mkwin(color, rows, cols, tly, tlx):
+ win = curses.newwin(rows, cols, tly, tlx)
+ if curses.has_colors():
+ win.bkgdset(ord(' '), curses.color_pair(color))
+ else:
+ win.bkgdset(ord(' '), curses.A_BOLD)
+ return win
+
+
+def mkpad(color, rows, cols):
+ win = curses.newpad(rows, cols)
+ if curses.has_colors():
+ win.bkgdset(ord(' '), curses.color_pair(color))
+ else:
+ win.bkgdset(ord(' '), curses.A_BOLD)
+ return win
+
+
+def move_cursor(screen, row, col):
+ try:
+ screen.move(row, col)
+ except curses.error as ex:
+ import traceback
+
+ log.warning(
+ 'Error on screen.move(%s, %s): (curses.LINES: %s, curses.COLS: %s) Error: %s\nStack: %s',
+ row,
+ col,
+ curses.LINES,
+ curses.COLS,
+ ex,
+ ''.join(traceback.format_stack()),
+ )
diff --git a/deluge/ui/console/modes/cmdline.py b/deluge/ui/console/modes/cmdline.py
new file mode 100644
index 0000000..2735168
--- /dev/null
+++ b/deluge/ui/console/modes/cmdline.py
@@ -0,0 +1,850 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os
+import re
+from io import open
+
+import deluge.component as component
+import deluge.configmanager
+from deluge.common import PY2
+from deluge.decorators import overrides
+from deluge.ui.console.cmdline.command import Commander
+from deluge.ui.console.modes.basemode import BaseMode, move_cursor
+from deluge.ui.console.utils import colors
+from deluge.ui.console.utils import curses_util as util
+from deluge.ui.console.utils.format_utils import (
+ delete_alt_backspace,
+ remove_formatting,
+ strwidth,
+)
+
+try:
+ import curses
+except ImportError:
+ pass
+
+log = logging.getLogger(__name__)
+LINES_BUFFER_SIZE = 5000
+INPUT_HISTORY_SIZE = 500
+MAX_HISTFILE_SIZE = 2000
+
+
+def complete_line(line, possible_matches):
+ """Find the common prefix of possible matches.
+
+ Proritizing matching-case elements.
+ """
+
+ if not possible_matches:
+ return line
+
+ line = line.replace(r'\ ', ' ')
+
+ matches1 = []
+ matches2 = []
+
+ for match in possible_matches:
+ match = remove_formatting(match)
+ match = match.replace(r'\ ', ' ')
+ m1, m2 = '', ''
+ for i, c in enumerate(line):
+ if m1 and m2:
+ break
+ if not m1 and c != line[i]:
+ m1 = line[:i]
+ if not m2 and c.lower() != line[i].lower():
+ m2 = line[:i]
+ if not m1:
+ matches1.append(match)
+ elif not m2:
+ matches2.append(match)
+
+ possible_matches = matches1 + matches2
+
+ maxlen = 9001
+
+ for match in possible_matches[1:]:
+ for i, c in enumerate(match):
+ try:
+ if c.lower() != possible_matches[0][i].lower():
+ maxlen = min(maxlen, i)
+ break
+ except IndexError:
+ maxlen = min(maxlen, i)
+ break
+
+ return possible_matches[0][:maxlen].replace(' ', r'\ ')
+
+
+def commonprefix(m):
+ """Returns the longest common leading component from list of pathnames."""
+ if not m:
+ return ''
+ s1 = min(m)
+ s2 = max(m)
+ for i, c in enumerate(s1):
+ if c != s2[i]:
+ return s1[:i]
+ return s2
+
+
+class CmdLine(BaseMode, Commander):
+ def __init__(self, stdscr, encoding=None):
+ # Get a handle to the main console
+ self.console = component.get('ConsoleUI')
+ Commander.__init__(self, self.console._commands, interactive=True)
+
+ self.batch_write = False
+
+ # A list of strings to be displayed based on the offset (scroll)
+ self.lines = []
+ # The offset to display lines
+ self.display_lines_offset = 0
+
+ # Holds the user input and is cleared on 'enter'
+ self.input = ''
+ self.input_incomplete = ''
+
+ # Keep track of where the cursor is
+ self.input_cursor = 0
+ # Keep a history of inputs
+ self.input_history = []
+ self.input_history_index = 0
+
+ # Keep track of double- and multi-tabs
+ self.tab_count = 0
+
+ self.console_config = component.get('TorrentList').config
+
+ # To avoid having to truncate the file every time we're writing
+ # or doing it on exit(and therefore relying on an error-less
+ # or in other words clean exit, we're going to have two files
+ # that we swap around based on length
+ config_dir = deluge.configmanager.get_config_dir()
+ self.history_file = [
+ os.path.join(config_dir, 'cmd_line.hist1'),
+ os.path.join(config_dir, 'cmd_line.hist2'),
+ ]
+ self._hf_lines = [0, 0]
+ if self.console_config['cmdline']['save_command_history']:
+ try:
+ with open(self.history_file[0], 'r', encoding='utf8') as _file:
+ lines1 = _file.read().splitlines()
+ self._hf_lines[0] = len(lines1)
+ except IOError:
+ lines1 = []
+ self._hf_lines[0] = 0
+
+ try:
+ with open(self.history_file[1], 'r', encoding='utf8') as _file:
+ lines2 = _file.read().splitlines()
+ self._hf_lines[1] = len(lines2)
+ except IOError:
+ lines2 = []
+ self._hf_lines[1] = 0
+
+ # The non-full file is the active one
+ if self._hf_lines[0] > self._hf_lines[1]:
+ self.lines = lines1 + lines2
+ else:
+ self.lines = lines2 + lines1
+
+ if len(self.lines) > MAX_HISTFILE_SIZE:
+ self.lines = self.lines[-MAX_HISTFILE_SIZE:]
+
+ # Instead of having additional input history file, we can
+ # simply scan for lines beginning with ">>> "
+ for i, line in enumerate(self.lines):
+ line = remove_formatting(line)
+ if line.startswith('>>> '):
+ console_input = line[4:]
+ if self.console_config['cmdline']['ignore_duplicate_lines']:
+ if len(self.input_history) > 0:
+ if self.input_history[-1] != console_input:
+ self.input_history.append(console_input)
+ else:
+ self.input_history.append(console_input)
+
+ self.input_history_index = len(self.input_history)
+
+ # show the cursor
+ util.safe_curs_set(util.Curser.VERY_VISIBLE)
+ BaseMode.__init__(self, stdscr, encoding, depend=['SessionProxy'])
+
+ @overrides(component.Component)
+ def update(self):
+ if not component.get('ConsoleUI').is_active_mode(self):
+ return
+ # Update just the status bars
+ self.draw_statusbars(bottom_row=-2, bottombar_help=False)
+ move_cursor(self.stdscr, self.rows - 1, min(self.input_cursor, curses.COLS - 1))
+ self.stdscr.refresh()
+
+ @overrides(BaseMode)
+ def pause(self):
+ self.stdscr.leaveok(0)
+
+ @overrides(BaseMode)
+ def resume(self):
+ util.safe_curs_set(util.Curser.VERY_VISIBLE)
+
+ @overrides(BaseMode)
+ def read_input(self):
+ # Read the character
+ c = self.stdscr.getch()
+
+ # Either ESC or ALT+<some key>
+ if c == util.KEY_ESC:
+ n = self.stdscr.getch()
+ if n == -1:
+ # Escape key
+ return
+ c = [c, n]
+
+ # We remove the tab count if the key wasn't a tab
+ if c != util.KEY_TAB:
+ self.tab_count = 0
+
+ # We clear the input string and send it to the command parser on ENTER
+ if c in [curses.KEY_ENTER, util.KEY_ENTER2]:
+ if self.input:
+ if self.input.endswith('\\'):
+ self.input = self.input[:-1]
+ self.input_cursor -= 1
+ self.add_line('{!yellow,black,bold!}>>>{!input!} %s' % self.input)
+ self.do_command(self.input)
+ if len(self.input_history) == INPUT_HISTORY_SIZE:
+ # Remove the oldest input history if the max history size
+ # is reached.
+ del self.input_history[0]
+ if self.console_config['cmdline']['ignore_duplicate_lines']:
+ if len(self.input_history) > 0:
+ if self.input_history[-1] != self.input:
+ self.input_history.append(self.input)
+ else:
+ self.input_history.append(self.input)
+ else:
+ self.input_history.append(self.input)
+ self.input_history_index = len(self.input_history)
+ self.input = ''
+ self.input_incomplete = ''
+ self.input_cursor = 0
+ self.stdscr.refresh()
+
+ # Run the tab completer function
+ elif c == util.KEY_TAB:
+ # Keep track of tab hit count to know when it's double-hit
+ self.tab_count += 1
+
+ if self.tab_completer:
+ # We only call the tab completer function if we're at the end of
+ # the input string on the cursor is on a space
+ if (
+ self.input_cursor == len(self.input)
+ or self.input[self.input_cursor] == ' '
+ ):
+ self.input, self.input_cursor = self.tab_completer(
+ self.input, self.input_cursor, self.tab_count
+ )
+
+ # We use the UP and DOWN keys to cycle through input history
+ elif c == curses.KEY_UP:
+ if self.input_history_index - 1 >= 0:
+ if self.input_history_index == len(self.input_history):
+ # We're moving from non-complete input so save it just incase
+ # we move back down to it.
+ self.input_incomplete = self.input
+ # Going back in the history
+ self.input_history_index -= 1
+ self.input = self.input_history[self.input_history_index]
+ self.input_cursor = len(self.input)
+ elif c == curses.KEY_DOWN:
+ if self.input_history_index + 1 < len(self.input_history):
+ # Going forward in the history
+ self.input_history_index += 1
+ self.input = self.input_history[self.input_history_index]
+ self.input_cursor = len(self.input)
+ elif self.input_history_index + 1 == len(self.input_history):
+ # We're moving back down to an incomplete input
+ self.input_history_index += 1
+ self.input = self.input_incomplete
+ self.input_cursor = len(self.input)
+
+ # Cursor movement
+ elif c == curses.KEY_LEFT:
+ if self.input_cursor:
+ self.input_cursor -= 1
+ elif c == curses.KEY_RIGHT:
+ if self.input_cursor < len(self.input):
+ self.input_cursor += 1
+ elif c == curses.KEY_HOME:
+ self.input_cursor = 0
+ elif c == curses.KEY_END:
+ self.input_cursor = len(self.input)
+
+ # Scrolling through buffer
+ elif c == curses.KEY_PPAGE:
+ self.display_lines_offset += self.rows - 3
+ # We substract 3 for the unavailable lines and 1 extra due to len(self.lines)
+ if self.display_lines_offset > (len(self.lines) - 4 - self.rows):
+ self.display_lines_offset = len(self.lines) - 4 - self.rows
+
+ self.refresh()
+ elif c == curses.KEY_NPAGE:
+ self.display_lines_offset -= self.rows - 3
+ if self.display_lines_offset < 0:
+ self.display_lines_offset = 0
+ self.refresh()
+
+ # Delete a character in the input string based on cursor position
+ elif c in [curses.KEY_BACKSPACE, util.KEY_BACKSPACE2]:
+ if self.input and self.input_cursor > 0:
+ self.input = (
+ self.input[: self.input_cursor - 1]
+ + self.input[self.input_cursor :]
+ )
+ self.input_cursor -= 1
+ # Delete a word when alt+backspace is pressed
+ elif c == [util.KEY_ESC, util.KEY_BACKSPACE2] or c == [
+ util.KEY_ESC,
+ curses.KEY_BACKSPACE,
+ ]:
+ self.input, self.input_cursor = delete_alt_backspace(
+ self.input, self.input_cursor
+ )
+ elif c == curses.KEY_DC:
+ if self.input and self.input_cursor < len(self.input):
+ self.input = (
+ self.input[: self.input_cursor]
+ + self.input[self.input_cursor + 1 :]
+ )
+
+ # A key to add to the input string
+ else:
+ if c > 31 and c < 256:
+ # Emulate getwch
+ stroke = chr(c)
+ uchar = '' if PY2 else stroke
+ while not uchar:
+ try:
+ uchar = stroke.decode(self.encoding)
+ except UnicodeDecodeError:
+ c = self.stdscr.getch()
+ stroke += chr(c)
+
+ if uchar:
+ if self.input_cursor == len(self.input):
+ self.input += uchar
+ else:
+ # Insert into string
+ self.input = (
+ self.input[: self.input_cursor]
+ + uchar
+ + self.input[self.input_cursor :]
+ )
+
+ # Move the cursor forward
+ self.input_cursor += 1
+
+ self.refresh()
+
+ @overrides(BaseMode)
+ def on_resize(self, rows, cols):
+ BaseMode.on_resize(self, rows, cols)
+ self.stdscr.erase()
+ self.refresh()
+
+ @overrides(BaseMode)
+ def refresh(self):
+ """
+ Refreshes the screen.
+ Updates the lines based on the`:attr:lines` based on the `:attr:display_lines_offset`
+ attribute and the status bars.
+ """
+ if not component.get('ConsoleUI').is_active_mode(self):
+ return
+ self.stdscr.erase()
+
+ # Update the status bars
+ self.add_string(0, self.statusbars.topbar)
+ self.add_string(self.rows - 2, self.statusbars.bottombar)
+
+ # The number of rows minus the status bars and the input line
+ available_lines = self.rows - 3
+ # If the amount of lines exceeds the number of rows, we need to figure out
+ # which ones to display based on the offset
+ if len(self.lines) > available_lines:
+ # Get the lines to display based on the offset
+ offset = len(self.lines) - self.display_lines_offset
+ lines = self.lines[-(available_lines - offset) : offset]
+ elif len(self.lines) == available_lines:
+ lines = self.lines
+ else:
+ lines = [''] * (available_lines - len(self.lines))
+ lines.extend(self.lines)
+
+ # Add the lines to the screen
+ for index, line in enumerate(lines):
+ self.add_string(index + 1, line)
+
+ # Add the input string
+ self.add_string(self.rows - 1, self.input, pad=False, trim=False)
+
+ move_cursor(self.stdscr, self.rows - 1, min(self.input_cursor, curses.COLS - 1))
+ self.stdscr.redrawwin()
+ self.stdscr.refresh()
+
+ def add_line(self, text, refresh=True):
+ """
+ Add a line to the screen. This will be showed between the two bars.
+ The text can be formatted with color using the following format:
+
+ "{!fg, bg, attributes, ...!}"
+
+ See: http://docs.python.org/library/curses.html#constants for attributes.
+
+ Alternatively, it can use some built-in scheme for coloring.
+ See colors.py for built-in schemes.
+
+ "{!scheme!}"
+
+ Examples:
+
+ "{!blue, black, bold!}My Text is {!white, black!}cool"
+ "{!info!}I am some info text!"
+ "{!error!}Uh oh!"
+
+ :param text: the text to show
+ :type text: string
+ :param refresh: if True, the screen will refresh after the line is added
+ :type refresh: bool
+
+ """
+
+ if self.console_config['cmdline']['save_command_history']:
+ # Determine which file is the active one
+ # If both are under maximum, it's first, otherwise it's the one not full
+ if (
+ self._hf_lines[0] < MAX_HISTFILE_SIZE
+ and self._hf_lines[1] < MAX_HISTFILE_SIZE
+ ):
+ active_file = 0
+ elif self._hf_lines[0] == MAX_HISTFILE_SIZE:
+ active_file = 1
+ else:
+ active_file = 0
+
+ # Write the line
+ with open(self.history_file[active_file], 'a', encoding='utf8') as _file:
+ _file.write(text + '\n')
+
+ # And increment line counter
+ self._hf_lines[active_file] += 1
+
+ # If the active file reaches max size, we truncate it
+ # therefore swapping the currently active file
+ if self._hf_lines[active_file] == MAX_HISTFILE_SIZE:
+ self._hf_lines[1 - active_file] = 0
+ with open(
+ self.history_file[1 - active_file], 'w', encoding='utf8'
+ ) as _file:
+ _file.truncate(0)
+
+ def get_line_chunks(line):
+ """
+ Returns a list of 2-tuples (color string, text)
+
+ """
+ if not line or line.count('{!') != line.count('!}'):
+ return []
+
+ chunks = []
+ if not line.startswith('{!'):
+ begin = line.find('{!')
+ if begin == -1:
+ begin = len(line)
+ chunks.append(('', line[:begin]))
+ line = line[begin:]
+
+ while line:
+ # We know the line starts with "{!" here
+ end_color = line.find('!}')
+ next_color = line.find('{!', end_color)
+ if next_color == -1:
+ next_color = len(line)
+ chunks.append((line[: end_color + 2], line[end_color + 2 : next_color]))
+ line = line[next_color:]
+ return chunks
+
+ for line in text.splitlines():
+ # We need to check for line lengths here and split as necessary
+ try:
+ line_length = colors.get_line_width(line)
+ except colors.BadColorString:
+ log.error('Passed a bad colored line: %s', line)
+ continue
+
+ if line_length >= (self.cols - 1):
+ s = ''
+ # The length of the text without the color tags
+ s_len = 0
+ # We need to split this over multiple lines
+ for chunk in get_line_chunks(line):
+ if (strwidth(chunk[1]) + s_len) < (self.cols - 1):
+ # This chunk plus the current string in 's' isn't over
+ # the maximum width, so just append the color tag and text
+ s += chunk[0] + chunk[1]
+ s_len += strwidth(chunk[1])
+ else:
+ # The chunk plus the current string in 's' is too long.
+ # We need to take as much of the chunk and put it into 's'
+ # with the color tag.
+ remain = (self.cols - 1) - s_len
+ s += chunk[0] + chunk[1][:remain]
+ # We append the line since it's full
+ self.lines.append(s)
+ # Start a new 's' with the remainder chunk
+ s = chunk[0] + chunk[1][remain:]
+ s_len = strwidth(chunk[1][remain:])
+ # Append the final string which may or may not be the full width
+ if s:
+ self.lines.append(s)
+ else:
+ self.lines.append(line)
+
+ while len(self.lines) > LINES_BUFFER_SIZE:
+ # Remove the oldest line if the max buffer size has been reached
+ del self.lines[0]
+
+ if refresh:
+ self.refresh()
+
+ def _add_string(self, row, string):
+ """
+ Adds a string to the desired `:param:row`.
+
+ :param row: int, the row number to write the string
+
+ """
+ col = 0
+ try:
+ parsed = colors.parse_color_string(string)
+ except colors.BadColorString as ex:
+ log.error('Cannot add bad color string %s: %s', string, ex)
+ return
+
+ for index, (color, p_str) in enumerate(parsed):
+ if index + 1 == len(parsed):
+ # This is the last string so lets append some " " to it
+ p_str += ' ' * (self.cols - (col + strwidth(p_str)) - 1)
+ try:
+ self.stdscr.addstr(row, col, p_str.encode(self.encoding), color)
+ except curses.error:
+ pass
+
+ col += strwidth(p_str)
+
+ def set_batch_write(self, batch):
+ """
+ When this is set the screen is not refreshed after a `:meth:write` until
+ this is set to False.
+
+ :param batch: set True to prevent screen refreshes after a `:meth:write`
+ :type batch: bool
+
+ """
+ self.batch_write = batch
+ if not batch:
+ self.refresh()
+
+ def write(self, line):
+ """
+ Writes a line out
+
+ :param line: str, the line to print
+
+ """
+
+ self.add_line(line, not self.batch_write)
+
+ def tab_completer(self, line, cursor, hits):
+ """
+ Called when the user hits 'tab' and will autocomplete or show options.
+ If a command is already supplied in the line, this function will call the
+ complete method of the command.
+
+ :param line: str, the current input string
+ :param cursor: int, the cursor position in the line
+ :param second_hit: bool, if this is the second time in a row the tab key
+ has been pressed
+
+ :returns: 2-tuple (string, cursor position)
+
+ """
+ # First check to see if there is no space, this will mean that it's a
+ # command that needs to be completed.
+
+ # We don't want to split by escaped spaces
+ def split(string):
+ return re.split(r'(?<!\\) ', string)
+
+ if ' ' not in line:
+ possible_matches = []
+ # Iterate through the commands looking for ones that startwith the
+ # line.
+ for cmd in self.console._commands:
+ if cmd.startswith(line):
+ possible_matches.append(cmd)
+
+ line_prefix = ''
+ else:
+ cmd = split(line)[0]
+ if cmd in self.console._commands:
+ # Call the command's complete method to get 'er done
+ possible_matches = self.console._commands[cmd].complete(split(line)[-1])
+ line_prefix = ' '.join(split(line)[:-1]) + ' '
+ else:
+ # This is a bogus command
+ return (line, cursor)
+
+ # No matches, so just return what we got passed
+ if len(possible_matches) == 0:
+ return (line, cursor)
+ # If we only have 1 possible match, then just modify the line and
+ # return it, else we need to print out the matches without modifying
+ # the line.
+ elif len(possible_matches) == 1:
+ # Do not append space after directory names
+ new_line = line_prefix + possible_matches[0]
+ if not new_line.endswith('/') and not new_line.endswith(r'\\'):
+ new_line += ' '
+ # We only want to print eventual colors or other control characters, not return them
+ new_line = remove_formatting(new_line)
+ return (new_line, len(new_line))
+ else:
+ if hits == 1:
+ p = ' '.join(split(line)[:-1])
+
+ try:
+ l_arg = split(line)[-1]
+ except IndexError:
+ l_arg = ''
+
+ new_line = ' '.join(
+ [p, complete_line(l_arg, possible_matches)]
+ ).lstrip()
+
+ if len(remove_formatting(new_line)) > len(line):
+ line = new_line
+ cursor = len(line)
+ elif hits >= 2:
+ max_list = self.console_config['cmdline']['torrents_per_tab_press']
+ match_count = len(possible_matches)
+ listed = (hits - 2) * max_list
+ pages = (match_count - 1) // max_list + 1
+ left = match_count - listed
+ if hits == 2:
+ self.write(' ')
+
+ if match_count >= 4:
+ self.write('{!green!}Autocompletion matches:')
+ # Only list some of the matching torrents as there can be hundreds of them
+ if self.console_config['cmdline']['third_tab_lists_all']:
+ if hits == 2 and left > max_list:
+ for i in range(listed, listed + max_list):
+ match = possible_matches[i]
+ self.write(match.replace(r'\ ', ' '))
+ self.write(
+ '{!error!}And %i more. Press <tab> to list them'
+ % (left - max_list)
+ )
+ else:
+ self.tab_count = 0
+ for match in possible_matches[listed:]:
+ self.write(match.replace(r'\ ', ' '))
+ else:
+ if left > max_list:
+ for i in range(listed, listed + max_list):
+ match = possible_matches[i]
+ self.write(match.replace(r'\ ', ' '))
+ self.write(
+ '{!error!}And %i more (%i/%i). Press <tab> to view more'
+ % (left - max_list, hits - 1, pages)
+ )
+ else:
+ self.tab_count = 0
+ for match in possible_matches[listed:]:
+ self.write(match.replace(r'\ ', ' '))
+ if hits > 2:
+ self.write(
+ '{!green!}Finished listing %i torrents (%i/%i)'
+ % (match_count, hits - 1, pages)
+ )
+
+ # We only want to print eventual colors or other control characters, not return them
+ line = remove_formatting(line)
+ cursor = len(line)
+ return (line, cursor)
+
+ def tab_complete_path(
+ self, line, path_type='file', ext='', sort='name', dirs_first=1
+ ):
+ self.console = component.get('ConsoleUI')
+
+ line = line.replace('\\ ', ' ')
+ line = os.path.abspath(os.path.expanduser(line))
+ ret = []
+ if os.path.exists(line):
+ # This is a correct path, check to see if it's a directory
+ if os.path.isdir(line):
+ # Directory, so we need to show contents of directory
+ # ret.extend(os.listdir(line))
+ try:
+ for f in os.listdir(line):
+ # Skip hidden
+ if f.startswith('.'):
+ continue
+ f = os.path.join(line, f)
+ if os.path.isdir(f):
+ if os.sep == '\\': # Windows path support
+ f += '\\'
+ else: # Unix
+ f += '/'
+ elif not f.endswith(ext):
+ continue
+ ret.append(f)
+ except OSError:
+ self.console.write('{!error!}Permission denied: {!info!}%s' % line)
+ else:
+ try:
+ # This is a file, but we could be looking for another file that
+ # shares a common prefix.
+ for f in os.listdir(os.path.dirname(line)):
+ if f.startswith(os.path.split(line)[1]):
+ ret.append(os.path.join(os.path.dirname(line), f))
+ except OSError:
+ self.console.write('{!error!}Permission denied: {!info!}%s' % line)
+ else:
+ # This path does not exist, so lets do a listdir on it's parent
+ # and find any matches.
+ try:
+ ret = []
+ if os.path.isdir(os.path.dirname(line)):
+ for f in os.listdir(os.path.dirname(line)):
+ if f.startswith(os.path.split(line)[1]):
+ p = os.path.join(os.path.dirname(line), f)
+
+ if os.path.isdir(p):
+ if os.sep == '\\': # Windows path support
+ p += '\\'
+ else: # Unix
+ p += '/'
+ ret.append(p)
+ except OSError:
+ self.console.write('{!error!}Permission denied: {!info!}%s' % line)
+
+ if sort == 'date':
+ ret = sorted(ret, key=os.path.getmtime, reverse=True)
+
+ if dirs_first == 1:
+ ret = sorted(ret, key=os.path.isdir, reverse=True)
+ elif dirs_first == -1:
+ ret = sorted(ret, key=os.path.isdir, reverse=False)
+
+ # Highlight directory names
+ for i, filename in enumerate(ret):
+ if os.path.isdir(filename):
+ ret[i] = '{!cyan!}%s' % filename
+
+ for i in range(0, len(ret)):
+ ret[i] = ret[i].replace(' ', r'\ ')
+ return ret
+
+ def tab_complete_torrent(self, line):
+ """
+ Completes torrent_ids or names.
+
+ :param line: str, the string to complete
+
+ :returns: list of matches
+
+ """
+
+ empty = len(line) == 0
+
+ # Remove dangling backslashes to avoid breaking shlex
+ if line.endswith('\\'):
+ line = line[:-1]
+
+ raw_line = line
+ line = line.replace('\\ ', ' ')
+
+ possible_matches = []
+ possible_matches2 = []
+
+ match_count = 0
+ match_count2 = 0
+ for torrent_id, torrent_name in self.console.torrents:
+ if torrent_id.startswith(line):
+ match_count += 1
+ if torrent_name.startswith(line):
+ match_count += 1
+ elif torrent_name.lower().startswith(line.lower()):
+ match_count2 += 1
+
+ # Find all possible matches
+ for torrent_id, torrent_name in self.console.torrents:
+ # Escape spaces to avoid, for example, expanding "Doc" into "Doctor Who" and removing
+ # everything containing one of these words
+ escaped_name = torrent_name.replace(' ', '\\ ')
+ # If we only matched one torrent, don't add the full name or it'll also get autocompleted
+ if match_count == 1:
+ if torrent_id.startswith(line):
+ possible_matches.append(torrent_id)
+ break
+ if torrent_name.startswith(line):
+ possible_matches.append(escaped_name)
+ break
+ elif match_count == 0 and match_count2 == 1:
+ if torrent_name.lower().startswith(line.lower()):
+ possible_matches.append(escaped_name)
+ break
+ else:
+ line_len = len(raw_line)
+
+ # Let's avoid listing all torrents twice if there's no pattern
+ if not empty and torrent_id.startswith(line):
+ # Highlight the matching part
+ text = '{!info!}%s{!input!}%s - "%s"' % (
+ torrent_id[:line_len],
+ torrent_id[line_len:],
+ torrent_name,
+ )
+ possible_matches.append(text)
+ if torrent_name.startswith(line):
+ text = '{!info!}%s{!input!}%s ({!cyan!}%s{!input!})' % (
+ escaped_name[:line_len],
+ escaped_name[line_len:],
+ torrent_id,
+ )
+ possible_matches.append(text)
+ elif torrent_name.lower().startswith(line.lower()):
+ text = '{!info!}%s{!input!}%s ({!cyan!}%s{!input!})' % (
+ escaped_name[:line_len],
+ escaped_name[line_len:],
+ torrent_id,
+ )
+ possible_matches2.append(text)
+
+ return possible_matches + possible_matches2
diff --git a/deluge/ui/console/modes/connectionmanager.py b/deluge/ui/console/modes/connectionmanager.py
new file mode 100644
index 0000000..84a3fbc
--- /dev/null
+++ b/deluge/ui/console/modes/connectionmanager.py
@@ -0,0 +1,206 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+import deluge.component as component
+from deluge.decorators import overrides
+from deluge.ui.console.modes.basemode import BaseMode
+from deluge.ui.console.utils.curses_util import is_printable_chr
+from deluge.ui.console.widgets.popup import InputPopup, PopupsHandler, SelectablePopup
+from deluge.ui.hostlist import HostList
+
+try:
+ import curses
+except ImportError:
+ pass
+
+log = logging.getLogger(__name__)
+
+
+class ConnectionManager(BaseMode, PopupsHandler):
+ def __init__(self, stdscr, encoding=None):
+ PopupsHandler.__init__(self)
+ self.statuses = {}
+ self.all_torrents = None
+ self.hostlist = HostList()
+ self.update_hosts_status()
+ BaseMode.__init__(self, stdscr, encoding=encoding)
+ self.update_select_host_popup()
+
+ def update_select_host_popup(self):
+ selected_index = self.popup.current_selection() if self.popup else None
+
+ popup = SelectablePopup(
+ self,
+ _('Select Host'),
+ self._host_selected,
+ border_off_west=1,
+ active_wrap=True,
+ )
+ popup.add_header(
+ "{!white,black,bold!}'Q'=%s, 'a'=%s, 'D'=%s"
+ % (_('Quit'), _('Add Host'), _('Delete Host')),
+ space_below=True,
+ )
+ self.push_popup(popup, clear=True)
+
+ for host_entry in self.hostlist.get_hosts_info():
+ host_id, hostname, port, user = host_entry
+ args = {'data': host_id, 'foreground': 'red'}
+ state = 'Offline'
+ if host_id in self.statuses:
+ state = 'Online'
+ args.update({'data': self.statuses[host_id], 'foreground': 'green'})
+ host_str = '%s:%d [%s]' % (hostname, port, state)
+ self.popup.add_line(
+ host_id, host_str, selectable=True, use_underline=True, **args
+ )
+
+ if selected_index:
+ self.popup.set_selection(selected_index)
+ self.inlist = True
+ self.refresh()
+
+ def update_hosts_status(self):
+ for host_entry in self.hostlist.get_hosts_info():
+
+ def on_host_status(status_info):
+ self.statuses[status_info[0]] = status_info
+ self.update_select_host_popup()
+
+ self.hostlist.get_host_status(host_entry[0]).addCallback(on_host_status)
+
+ def _on_connected(self, result):
+ def on_console_start(result):
+ component.get('ConsoleUI').set_mode('TorrentList')
+
+ d = component.get('ConsoleUI').start_console()
+ d.addCallback(on_console_start)
+
+ def _on_connect_fail(self, result):
+ self.report_message('Failed to connect!', result)
+ self.refresh()
+ if hasattr(result, 'getTraceback'):
+ log.exception(result)
+
+ def _host_selected(self, selected_host, *args, **kwargs):
+ if selected_host in self.statuses:
+ d = self.hostlist.connect_host(selected_host)
+ d.addCallback(self._on_connected)
+ d.addErrback(self._on_connect_fail)
+
+ def _do_add(self, result, **kwargs):
+ if not result or kwargs.get('close', False):
+ self.pop_popup()
+ else:
+ self.add_host(
+ result['hostname']['value'],
+ result['port']['value'],
+ result['username']['value'],
+ result['password']['value'],
+ )
+
+ def add_popup(self):
+ self.inlist = False
+ popup = InputPopup(
+ self,
+ _('Add Host (Up & Down arrows to navigate, Esc to cancel)'),
+ border_off_north=1,
+ border_off_east=1,
+ close_cb=self._do_add,
+ )
+ popup.add_text_input('hostname', _('Hostname:'))
+ popup.add_text_input('port', _('Port:'))
+ popup.add_text_input('username', _('Username:'))
+ popup.add_text_input('password', _('Password:'))
+ self.push_popup(popup, clear=True)
+ self.refresh()
+
+ def add_host(self, hostname, port, username, password):
+ log.info('Adding host: %s', hostname)
+ try:
+ self.hostlist.add_host(hostname, port, username, password)
+ except ValueError as ex:
+ self.report_message(_('Error adding host'), '%s: %s' % (hostname, ex))
+ else:
+ self.update_select_host_popup()
+
+ def delete_host(self, host_id):
+ log.info('Deleting host: %s', host_id)
+ self.hostlist.remove_host(host_id)
+ self.update_select_host_popup()
+
+ @overrides(component.Component)
+ def start(self):
+ self.refresh()
+
+ @overrides(component.Component)
+ def update(self):
+ self.update_hosts_status()
+
+ @overrides(BaseMode)
+ def pause(self):
+ self.pop_popup()
+ BaseMode.pause(self)
+
+ @overrides(BaseMode)
+ def resume(self):
+ BaseMode.resume(self)
+ self.refresh()
+
+ @overrides(BaseMode)
+ def refresh(self):
+ if self.mode_paused():
+ return
+
+ self.stdscr.erase()
+ self.draw_statusbars()
+ self.stdscr.noutrefresh()
+
+ if not self.popup:
+ self.update_select_host_popup()
+
+ self.popup.refresh()
+ curses.doupdate()
+
+ @overrides(BaseMode)
+ def on_resize(self, rows, cols):
+ BaseMode.on_resize(self, rows, cols)
+
+ if self.popup:
+ self.popup.handle_resize()
+
+ self.stdscr.erase()
+ self.refresh()
+
+ @overrides(BaseMode)
+ def read_input(self):
+ c = self.stdscr.getch()
+
+ if is_printable_chr(c):
+ if chr(c) == 'Q':
+ component.get('ConsoleUI').quit()
+ elif self.inlist:
+ if chr(c) == 'q':
+ return
+ elif chr(c) == 'D':
+ host_id = self.popup.current_selection()[1]
+ self.delete_host(host_id)
+ return
+ elif chr(c) == 'a':
+ self.add_popup()
+ return
+
+ if self.popup:
+ if self.popup.handle_read(c) and self.popup.closed():
+ self.pop_popup()
+ self.refresh()
diff --git a/deluge/ui/console/modes/eventview.py b/deluge/ui/console/modes/eventview.py
new file mode 100644
index 0000000..cd3308c
--- /dev/null
+++ b/deluge/ui/console/modes/eventview.py
@@ -0,0 +1,115 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+import deluge.component as component
+from deluge.decorators import overrides
+from deluge.ui.console.modes.basemode import BaseMode
+from deluge.ui.console.utils import curses_util as util
+
+try:
+ import curses
+except ImportError:
+ pass
+
+log = logging.getLogger(__name__)
+
+
+class EventView(BaseMode):
+ def __init__(self, parent_mode, stdscr, encoding=None):
+ BaseMode.__init__(self, stdscr, encoding)
+ self.parent_mode = parent_mode
+ self.offset = 0
+
+ def back_to_overview(self):
+ component.get('ConsoleUI').set_mode(self.parent_mode.mode_name)
+
+ @overrides(component.Component)
+ def update(self):
+ self.refresh()
+
+ @overrides(BaseMode)
+ def refresh(self):
+ """
+ This method just shows each line of the event log
+ """
+ events = component.get('ConsoleUI').events
+
+ self.stdscr.erase()
+ self.draw_statusbars()
+
+ if events:
+ for i, event in enumerate(events):
+ if i - self.offset >= self.rows - 2:
+ more = len(events) - self.offset - self.rows + 2
+ if more > 0:
+ self.add_string(i - self.offset, ' (And %i more)' % more)
+ break
+
+ elif i - self.offset < 0:
+ continue
+ try:
+ self.add_string(i + 1 - self.offset, event)
+ except curses.error:
+ pass # This'll just cut the line. Note: This seriously should be fixed in a better way
+ else:
+ self.add_string(1, '{!white,black,bold!}No events to show yet')
+
+ if not component.get('ConsoleUI').is_active_mode(self):
+ return
+
+ self.stdscr.noutrefresh()
+ curses.doupdate()
+
+ @overrides(BaseMode)
+ def on_resize(self, rows, cols):
+ BaseMode.on_resize(self, rows, cols)
+ self.refresh()
+
+ @overrides(BaseMode)
+ def read_input(self):
+ c = self.stdscr.getch()
+
+ if c in [ord('q'), util.KEY_ESC]:
+ self.back_to_overview()
+ return
+
+ # TODO: Scroll event list
+ jumplen = self.rows - 3
+ num_events = len(component.get('ConsoleUI').events)
+
+ if c == curses.KEY_UP:
+ self.offset -= 1
+ elif c == curses.KEY_PPAGE:
+ self.offset -= jumplen
+ elif c == curses.KEY_HOME:
+ self.offset = 0
+ elif c == curses.KEY_DOWN:
+ self.offset += 1
+ elif c == curses.KEY_NPAGE:
+ self.offset += jumplen
+ elif c == curses.KEY_END:
+ self.offset += num_events
+ elif c == ord('j'):
+ self.offset -= 1
+ elif c == ord('k'):
+ self.offset += 1
+
+ if self.offset <= 0:
+ self.offset = 0
+ elif num_events > self.rows - 3:
+ if self.offset > num_events - self.rows + 3:
+ self.offset = num_events - self.rows + 3
+ else:
+ self.offset = 0
+
+ self.refresh()
diff --git a/deluge/ui/console/modes/preferences/__init__.py b/deluge/ui/console/modes/preferences/__init__.py
new file mode 100644
index 0000000..15d77c4
--- /dev/null
+++ b/deluge/ui/console/modes/preferences/__init__.py
@@ -0,0 +1,5 @@
+from __future__ import unicode_literals
+
+from deluge.ui.console.modes.preferences.preferences import Preferences
+
+__all__ = ['Preferences']
diff --git a/deluge/ui/console/modes/preferences/preference_panes.py b/deluge/ui/console/modes/preferences/preference_panes.py
new file mode 100644
index 0000000..62029a6
--- /dev/null
+++ b/deluge/ui/console/modes/preferences/preference_panes.py
@@ -0,0 +1,764 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+from deluge.common import is_ip
+from deluge.decorators import overrides
+from deluge.i18n import get_languages
+from deluge.ui.client import client
+from deluge.ui.common import DISK_CACHE_KEYS
+from deluge.ui.console.widgets import BaseInputPane, BaseWindow
+from deluge.ui.console.widgets.fields import FloatSpinInput, TextInput
+from deluge.ui.console.widgets.popup import PopupsHandler
+
+log = logging.getLogger(__name__)
+
+
+class BasePreferencePane(BaseInputPane, BaseWindow, PopupsHandler):
+ def __init__(self, name, preferences):
+ PopupsHandler.__init__(self)
+ self.preferences = preferences
+ BaseWindow.__init__(
+ self,
+ '%s' % name,
+ self.pane_width,
+ preferences.height,
+ posy=1,
+ posx=self.pane_x_pos,
+ )
+ BaseInputPane.__init__(self, preferences, border_off_east=1)
+ self.name = name
+
+ # have we scrolled down in the list
+ self.input_offset = 0
+
+ @overrides(BaseInputPane)
+ def handle_read(self, c):
+ if self.popup:
+ ret = self.popup.handle_read(c)
+ if self.popup and self.popup.closed():
+ self.pop_popup()
+ self.refresh()
+ return ret
+ return BaseInputPane.handle_read(self, c)
+
+ @property
+ def visible_content_pane_height(self):
+ y, x = self.visible_content_pane_size
+ return y
+
+ @property
+ def pane_x_pos(self):
+ return self.preferences.sidebar_width
+
+ @property
+ def pane_width(self):
+ return self.preferences.width
+
+ @property
+ def cols(self):
+ return self.pane_width
+
+ @property
+ def rows(self):
+ return self.preferences.height
+
+ def is_active_pane(self):
+ return self.preferences.is_active_pane(self)
+
+ def create_pane(self, core_conf, console_config):
+ pass
+
+ def add_config_values(self, conf_dict):
+ for ipt in self.inputs:
+ if ipt.has_input():
+ # Need special cases for in/out ports or proxy since they are tuples or dicts.
+ if ipt.name == 'listen_ports_to' or ipt.name == 'listen_ports_from':
+ conf_dict['listen_ports'] = (
+ self.infrom.get_value(),
+ self.into.get_value(),
+ )
+ elif ipt.name == 'out_ports_to' or ipt.name == 'out_ports_from':
+ conf_dict['outgoing_ports'] = (
+ self.outfrom.get_value(),
+ self.outto.get_value(),
+ )
+ elif ipt.name == 'listen_interface':
+ listen_interface = ipt.get_value().strip()
+ if is_ip(listen_interface) or not listen_interface:
+ conf_dict['listen_interface'] = listen_interface
+ elif ipt.name == 'outgoing_interface':
+ outgoing_interface = ipt.get_value().strip()
+ conf_dict['outgoing_interface'] = outgoing_interface
+ elif ipt.name.startswith('proxy_'):
+ if ipt.name == 'proxy_type':
+ conf_dict.setdefault('proxy', {})['type'] = ipt.get_value()
+ elif ipt.name == 'proxy_username':
+ conf_dict.setdefault('proxy', {})['username'] = ipt.get_value()
+ elif ipt.name == 'proxy_password':
+ conf_dict.setdefault('proxy', {})['password'] = ipt.get_value()
+ elif ipt.name == 'proxy_hostname':
+ conf_dict.setdefault('proxy', {})['hostname'] = ipt.get_value()
+ elif ipt.name == 'proxy_port':
+ conf_dict.setdefault('proxy', {})['port'] = ipt.get_value()
+ elif ipt.name == 'proxy_hostnames':
+ conf_dict.setdefault('proxy', {})[
+ 'proxy_hostnames'
+ ] = ipt.get_value()
+ elif ipt.name == 'proxy_peer_connections':
+ conf_dict.setdefault('proxy', {})[
+ 'proxy_peer_connections'
+ ] = ipt.get_value()
+ elif ipt.name == 'proxy_tracker_connections':
+ conf_dict.setdefault('proxy', {})[
+ 'proxy_tracker_connections'
+ ] = ipt.get_value()
+ elif ipt.name == 'force_proxy':
+ conf_dict.setdefault('proxy', {})['force_proxy'] = ipt.get_value()
+ elif ipt.name == 'anonymous_mode':
+ conf_dict.setdefault('proxy', {})[
+ 'anonymous_mode'
+ ] = ipt.get_value()
+ else:
+ conf_dict[ipt.name] = ipt.get_value()
+
+ if hasattr(ipt, 'get_child'):
+ c = ipt.get_child()
+ conf_dict[c.name] = c.get_value()
+
+ def update_values(self, conf_dict):
+ for ipt in self.inputs:
+ if ipt.has_input():
+ try:
+ ipt.set_value(conf_dict[ipt.name])
+ except KeyError: # just ignore if it's not in dict
+ pass
+ if hasattr(ipt, 'get_child'):
+ try:
+ c = ipt.get_child()
+ c.set_value(conf_dict[c.name])
+ except KeyError: # just ignore if it's not in dict
+ pass
+
+ def render(self, mode, screen, width, focused):
+ height = self.get_content_height()
+ self.ensure_content_pane_height(height)
+ self.screen.erase()
+
+ if focused and self.active_input == -1:
+ self.move_active_down(1)
+
+ self.render_inputs(focused=focused)
+
+ @overrides(BaseWindow)
+ def refresh(self):
+ BaseWindow.refresh(self)
+ if self.popup:
+ self.popup.refresh()
+
+ def update(self, active):
+ pass
+
+
+class InterfacePane(BasePreferencePane):
+ def __init__(self, preferences):
+ BasePreferencePane.__init__(self, ' %s ' % _('Interface'), preferences)
+
+ @overrides(BasePreferencePane)
+ def create_pane(self, core_conf, console_config):
+ self.add_header(_('General options'))
+
+ self.add_checked_input(
+ 'ring_bell',
+ _('Ring system bell when a download finishes'),
+ console_config['ring_bell'],
+ )
+ self.add_header('Console UI', space_above=True)
+ self.add_checked_input(
+ 'separate_complete',
+ _('List complete torrents after incomplete regardless of sorting order'),
+ console_config['torrentview']['separate_complete'],
+ )
+ self.add_checked_input(
+ 'move_selection',
+ _('Move selection when moving torrents in the queue'),
+ console_config['torrentview']['move_selection'],
+ )
+
+ langs = get_languages()
+ langs.insert(0, ('', 'System Default'))
+ self.add_combo_input(
+ 'language', _('Language'), langs, default=console_config['language']
+ )
+ self.add_header(_('Command Line Mode'), space_above=True)
+ self.add_checked_input(
+ 'ignore_duplicate_lines',
+ _('Do not store duplicate input in history'),
+ console_config['cmdline']['ignore_duplicate_lines'],
+ )
+ self.add_checked_input(
+ 'save_command_history',
+ _('Store and load command line history in command line mode'),
+ console_config['cmdline']['save_command_history'],
+ )
+ self.add_header('')
+ self.add_checked_input(
+ 'third_tab_lists_all',
+ _('Third tab lists all remaining torrents in command line mode'),
+ console_config['cmdline']['third_tab_lists_all'],
+ )
+ self.add_int_spin_input(
+ 'torrents_per_tab_press',
+ _('Torrents per tab press'),
+ console_config['cmdline']['torrents_per_tab_press'],
+ min_val=5,
+ max_val=10000,
+ )
+
+
+class DownloadsPane(BasePreferencePane):
+ def __init__(self, preferences):
+ BasePreferencePane.__init__(self, ' %s ' % _('Downloads'), preferences)
+
+ @overrides(BasePreferencePane)
+ def create_pane(self, core_conf, console_config):
+ self.add_header(_('Folders'))
+ self.add_text_input(
+ 'download_location',
+ '%s:' % _('Download To'),
+ core_conf['download_location'],
+ complete=True,
+ activate_input=True,
+ col='+1',
+ )
+ cmptxt = TextInput(
+ self.preferences,
+ 'move_completed_path',
+ None,
+ self.move,
+ self.pane_width,
+ core_conf['move_completed_path'],
+ False,
+ )
+ self.add_checkedplus_input(
+ 'move_completed',
+ '%s:' % _('Move completed to'),
+ cmptxt,
+ core_conf['move_completed'],
+ )
+ copytxt = TextInput(
+ self.preferences,
+ 'torrentfiles_location',
+ None,
+ self.move,
+ self.pane_width,
+ core_conf['torrentfiles_location'],
+ False,
+ )
+ self.add_checkedplus_input(
+ 'copy_torrent_file',
+ '%s:' % _('Copy of .torrent files to'),
+ copytxt,
+ core_conf['copy_torrent_file'],
+ )
+ self.add_checked_input(
+ 'del_copy_torrent_file',
+ _('Delete copy of torrent file on remove'),
+ core_conf['del_copy_torrent_file'],
+ )
+
+ self.add_header(_('Options'), space_above=True)
+ self.add_checked_input(
+ 'prioritize_first_last_pieces',
+ ('Prioritize first and last pieces of torrent'),
+ core_conf['prioritize_first_last_pieces'],
+ )
+ self.add_checked_input(
+ 'sequential_download',
+ _('Sequential download'),
+ core_conf['sequential_download'],
+ )
+ self.add_checked_input('add_paused', _('Add Paused'), core_conf['add_paused'])
+ self.add_checked_input(
+ 'pre_allocate_storage',
+ _('Pre-Allocate disk space'),
+ core_conf['pre_allocate_storage'],
+ )
+
+
+class NetworkPane(BasePreferencePane):
+ def __init__(self, preferences):
+ BasePreferencePane.__init__(self, ' %s ' % _('Network'), preferences)
+
+ @overrides(BasePreferencePane)
+ def create_pane(self, core_conf, console_config):
+ self.add_header(_('Incomming Ports'))
+ inrand = self.add_checked_input(
+ 'random_port',
+ 'Use Random Ports Active Port: %d' % self.preferences.active_port,
+ core_conf['random_port'],
+ )
+ listen_ports = core_conf['listen_ports']
+ self.infrom = self.add_int_spin_input(
+ 'listen_ports_from',
+ ' %s:' % _('From'),
+ value=listen_ports[0],
+ min_val=0,
+ max_val=65535,
+ )
+ self.infrom.set_depend(inrand, inverse=True)
+ self.into = self.add_int_spin_input(
+ 'listen_ports_to',
+ ' %s:' % _('To'),
+ value=listen_ports[1],
+ min_val=0,
+ max_val=65535,
+ )
+ self.into.set_depend(inrand, inverse=True)
+
+ self.add_header(_('Outgoing Ports'), space_above=True)
+ outrand = self.add_checked_input(
+ 'random_outgoing_ports',
+ _('Use Random Ports'),
+ core_conf['random_outgoing_ports'],
+ )
+ out_ports = core_conf['outgoing_ports']
+ self.outfrom = self.add_int_spin_input(
+ 'out_ports_from',
+ ' %s:' % _('From'),
+ value=out_ports[0],
+ min_val=0,
+ max_val=65535,
+ )
+ self.outfrom.set_depend(outrand, inverse=True)
+ self.outto = self.add_int_spin_input(
+ 'out_ports_to',
+ ' %s:' % _('To'),
+ value=out_ports[1],
+ min_val=0,
+ max_val=65535,
+ )
+ self.outto.set_depend(outrand, inverse=True)
+
+ self.add_header(_('Incoming Interface'), space_above=True)
+ self.add_text_input(
+ 'listen_interface',
+ _('IP address of the interface to listen on (leave empty for default):'),
+ core_conf['listen_interface'],
+ )
+
+ self.add_header(_('Outgoing Interface'), space_above=True)
+ self.add_text_input(
+ 'outgoing_interface',
+ _(
+ 'The network interface name or IP address for outgoing '
+ 'BitTorrent connections. (Leave empty for default.):'
+ ),
+ core_conf['outgoing_interface'],
+ )
+
+ self.add_header('TOS', space_above=True)
+ self.add_text_input('peer_tos', 'Peer TOS Byte:', core_conf['peer_tos'])
+
+ self.add_header(_('Network Extras'), space_above=True)
+ self.add_checked_input('upnp', 'UPnP', core_conf['upnp'])
+ self.add_checked_input('natpmp', 'NAT-PMP', core_conf['natpmp'])
+ self.add_checked_input('utpex', 'Peer Exchange', core_conf['utpex'])
+ self.add_checked_input('lsd', 'LSD', core_conf['lsd'])
+ self.add_checked_input('dht', 'DHT', core_conf['dht'])
+
+ self.add_header(_('Encryption'), space_above=True)
+ self.add_select_input(
+ 'enc_in_policy',
+ '%s:' % _('Inbound'),
+ [_('Forced'), _('Enabled'), _('Disabled')],
+ [0, 1, 2],
+ core_conf['enc_in_policy'],
+ active_default=True,
+ col='+1',
+ )
+ self.add_select_input(
+ 'enc_out_policy',
+ '%s:' % _('Outbound'),
+ [_('Forced'), _('Enabled'), _('Disabled')],
+ [0, 1, 2],
+ core_conf['enc_out_policy'],
+ active_default=True,
+ )
+ self.add_select_input(
+ 'enc_level',
+ '%s:' % _('Level'),
+ [_('Handshake'), _('Full Stream'), _('Either')],
+ [0, 1, 2],
+ core_conf['enc_level'],
+ active_default=True,
+ )
+
+
+class BandwidthPane(BasePreferencePane):
+ def __init__(self, preferences):
+ BasePreferencePane.__init__(self, ' %s ' % _('Bandwidth'), preferences)
+
+ @overrides(BasePreferencePane)
+ def create_pane(self, core_conf, console_config):
+ self.add_header(_('Global Bandwidth Usage'))
+ self.add_int_spin_input(
+ 'max_connections_global',
+ '%s:' % _('Maximum Connections'),
+ core_conf['max_connections_global'],
+ min_val=-1,
+ max_val=9000,
+ )
+ self.add_int_spin_input(
+ 'max_upload_slots_global',
+ '%s:' % _('Maximum Upload Slots'),
+ core_conf['max_upload_slots_global'],
+ min_val=-1,
+ max_val=9000,
+ )
+ self.add_float_spin_input(
+ 'max_download_speed',
+ '%s:' % _('Maximum Download Speed (KiB/s)'),
+ core_conf['max_download_speed'],
+ min_val=-1.0,
+ max_val=60000.0,
+ )
+ self.add_float_spin_input(
+ 'max_upload_speed',
+ '%s:' % _('Maximum Upload Speed (KiB/s)'),
+ core_conf['max_upload_speed'],
+ min_val=-1.0,
+ max_val=60000.0,
+ )
+ self.add_int_spin_input(
+ 'max_half_open_connections',
+ '%s:' % _('Maximum Half-Open Connections'),
+ core_conf['max_half_open_connections'],
+ min_val=-1,
+ max_val=9999,
+ )
+ self.add_int_spin_input(
+ 'max_connections_per_second',
+ '%s:' % _('Maximum Connection Attempts per Second'),
+ core_conf['max_connections_per_second'],
+ min_val=-1,
+ max_val=9999,
+ )
+ self.add_checked_input(
+ 'ignore_limits_on_local_network',
+ _('Ignore limits on local network'),
+ core_conf['ignore_limits_on_local_network'],
+ )
+ self.add_checked_input(
+ 'rate_limit_ip_overhead',
+ _('Rate Limit IP Overhead'),
+ core_conf['rate_limit_ip_overhead'],
+ )
+ self.add_header(_('Per Torrent Bandwidth Usage'), space_above=True)
+ self.add_int_spin_input(
+ 'max_connections_per_torrent',
+ '%s:' % _('Maximum Connections'),
+ core_conf['max_connections_per_torrent'],
+ min_val=-1,
+ max_val=9000,
+ )
+ self.add_int_spin_input(
+ 'max_upload_slots_per_torrent',
+ '%s:' % _('Maximum Upload Slots'),
+ core_conf['max_upload_slots_per_torrent'],
+ min_val=-1,
+ max_val=9000,
+ )
+ self.add_float_spin_input(
+ 'max_download_speed_per_torrent',
+ '%s:' % _('Maximum Download Speed (KiB/s)'),
+ core_conf['max_download_speed_per_torrent'],
+ min_val=-1.0,
+ max_val=60000.0,
+ )
+ self.add_float_spin_input(
+ 'max_upload_speed_per_torrent',
+ '%s:' % _('Maximum Upload Speed (KiB/s)'),
+ core_conf['max_upload_speed_per_torrent'],
+ min_val=-1.0,
+ max_val=60000.0,
+ )
+
+
+class OtherPane(BasePreferencePane):
+ def __init__(self, preferences):
+ BasePreferencePane.__init__(self, ' %s ' % _('Other'), preferences)
+
+ @overrides(BasePreferencePane)
+ def create_pane(self, core_conf, console_config):
+ self.add_header(_('System Information'))
+ self.add_info_field('info1', ' Help us improve Deluge by sending us your', '')
+ self.add_info_field(
+ 'info2', ' Python version, PyGTK version, OS and processor', ''
+ )
+ self.add_info_field(
+ 'info3', ' types. Absolutely no other information is sent.', ''
+ )
+ self.add_checked_input(
+ 'send_info',
+ _('Yes, please send anonymous statistics.'),
+ core_conf['send_info'],
+ )
+ self.add_header(_('GeoIP Database'), space_above=True)
+ self.add_text_input(
+ 'geoip_db_location', 'Location:', core_conf['geoip_db_location']
+ )
+
+
+class DaemonPane(BasePreferencePane):
+ def __init__(self, preferences):
+ BasePreferencePane.__init__(self, ' %s ' % _('Daemon'), preferences)
+
+ @overrides(BasePreferencePane)
+ def create_pane(self, core_conf, console_config):
+ self.add_header('Port')
+ self.add_int_spin_input(
+ 'daemon_port',
+ '%s:' % _('Daemon Port'),
+ core_conf['daemon_port'],
+ min_val=0,
+ max_val=65535,
+ )
+ self.add_header('Connections', space_above=True)
+ self.add_checked_input(
+ 'allow_remote', _('Allow remote connections'), core_conf['allow_remote']
+ )
+ self.add_header('Other', space_above=True)
+ self.add_checked_input(
+ 'new_release_check',
+ _('Periodically check the website for new releases'),
+ core_conf['new_release_check'],
+ )
+
+
+class QueuePane(BasePreferencePane):
+ def __init__(self, preferences):
+ BasePreferencePane.__init__(self, ' %s ' % _('Queue'), preferences)
+
+ @overrides(BasePreferencePane)
+ def create_pane(self, core_conf, console_config):
+ self.add_header(_('New Torrents'))
+ self.add_checked_input(
+ 'queue_new_to_top', _('Queue to top'), core_conf['queue_new_to_top']
+ )
+ self.add_header(_('Active Torrents'), True)
+ self.add_int_spin_input(
+ 'max_active_limit',
+ '%s:' % _('Total'),
+ core_conf['max_active_limit'],
+ min_val=-1,
+ max_val=9999,
+ )
+ self.add_int_spin_input(
+ 'max_active_downloading',
+ '%s:' % _('Downloading'),
+ core_conf['max_active_downloading'],
+ min_val=-1,
+ max_val=9999,
+ )
+ self.add_int_spin_input(
+ 'max_active_seeding',
+ '%s:' % _('Seeding'),
+ core_conf['max_active_seeding'],
+ min_val=-1,
+ max_val=9999,
+ )
+ self.add_checked_input(
+ 'dont_count_slow_torrents',
+ 'Ignore slow torrents',
+ core_conf['dont_count_slow_torrents'],
+ )
+ self.add_checked_input(
+ 'auto_manage_prefer_seeds',
+ 'Prefer seeding torrents',
+ core_conf['auto_manage_prefer_seeds'],
+ )
+ self.add_header(_('Seeding Rotation'), space_above=True)
+ self.add_float_spin_input(
+ 'share_ratio_limit',
+ '%s:' % _('Share Ratio'),
+ core_conf['share_ratio_limit'],
+ precision=2,
+ min_val=-1.0,
+ max_val=100.0,
+ )
+ self.add_float_spin_input(
+ 'seed_time_ratio_limit',
+ '%s:' % _('Time Ratio'),
+ core_conf['seed_time_ratio_limit'],
+ precision=2,
+ min_val=-1.0,
+ max_val=100.0,
+ )
+ self.add_int_spin_input(
+ 'seed_time_limit',
+ '%s:' % _('Time (m)'),
+ core_conf['seed_time_limit'],
+ min_val=1,
+ max_val=10000,
+ )
+ seedratio = FloatSpinInput(
+ self.mode,
+ 'stop_seed_ratio',
+ '',
+ self.move,
+ core_conf['stop_seed_ratio'],
+ precision=2,
+ inc_amt=0.1,
+ min_val=0.5,
+ max_val=100.0,
+ )
+ self.add_checkedplus_input(
+ 'stop_seed_at_ratio',
+ '%s:' % _('Share Ratio Reached'),
+ seedratio,
+ core_conf['stop_seed_at_ratio'],
+ )
+ self.add_checked_input(
+ 'remove_seed_at_ratio',
+ _('Remove torrent (Unchecked pauses torrent)'),
+ core_conf['remove_seed_at_ratio'],
+ )
+
+
+class ProxyPane(BasePreferencePane):
+ def __init__(self, preferences):
+ BasePreferencePane.__init__(self, ' %s ' % _('Proxy'), preferences)
+
+ @overrides(BasePreferencePane)
+ def create_pane(self, core_conf, console_config):
+ proxy = core_conf['proxy']
+
+ self.add_header(_('Proxy Settings'))
+ self.add_header(_('Proxy'), space_above=True)
+ self.add_int_spin_input(
+ 'proxy_type', '%s:' % _('Type'), proxy['type'], min_val=0, max_val=5
+ )
+ self.add_text_input('proxy_username', '%s:' % _('Username'), proxy['username'])
+ self.add_text_input('proxy_password', '%s:' % _('Password'), proxy['password'])
+ self.add_text_input('proxy_hostname', '%s:' % _('Hostname'), proxy['hostname'])
+ self.add_int_spin_input(
+ 'proxy_port', '%s:' % _('Port'), proxy['port'], min_val=0, max_val=65535
+ )
+ self.add_checked_input(
+ 'proxy_hostnames', _('Proxy Hostnames'), proxy['proxy_hostnames']
+ )
+ self.add_checked_input(
+ 'proxy_peer_connections', _('Proxy Peers'), proxy['proxy_peer_connections']
+ )
+ self.add_checked_input(
+ 'proxy_tracker_connections',
+ _('Proxy Trackers'),
+ proxy['proxy_tracker_connections'],
+ )
+ self.add_header('%s' % _('Force Proxy'), space_above=True)
+ self.add_checked_input('force_proxy', _('Force Proxy'), proxy['force_proxy'])
+ self.add_checked_input(
+ 'anonymous_mode', _('Hide Client Identity'), proxy['anonymous_mode']
+ )
+ self.add_header('%s' % _('Proxy Type Help'), space_above=True)
+ self.add_text_area(
+ 'proxy_text_area',
+ ' 0: None 1: Socks4\n'
+ ' 2: Socks5 3: Socks5 Auth\n'
+ ' 4: HTTP 5: HTTP Auth\n'
+ ' 6: I2P',
+ )
+
+
+class CachePane(BasePreferencePane):
+ def __init__(self, preferences):
+ BasePreferencePane.__init__(self, ' %s ' % _('Cache'), preferences)
+ self.created = False
+
+ @overrides(BasePreferencePane)
+ def create_pane(self, core_conf, console_config):
+ self.core_conf = core_conf
+
+ def build_pane(self, core_conf, status):
+ self.created = True
+ self.add_header(_('Settings'), space_below=True)
+ self.add_int_spin_input(
+ 'cache_size',
+ '%s:' % _('Cache Size (16 KiB blocks)'),
+ core_conf['cache_size'],
+ min_val=0,
+ max_val=99999,
+ )
+ self.add_int_spin_input(
+ 'cache_expiry',
+ '%s:' % _('Cache Expiry (seconds)'),
+ core_conf['cache_expiry'],
+ min_val=1,
+ max_val=32000,
+ )
+ self.add_header(' %s' % _('Write'), space_above=True)
+ self.add_info_field(
+ 'blocks_written',
+ ' %s:' % _('Blocks Written'),
+ status['disk.num_blocks_written'],
+ )
+ self.add_info_field(
+ 'writes', ' %s:' % _('Writes'), status['disk.num_write_ops']
+ )
+ self.add_info_field(
+ 'write_hit_ratio',
+ ' %s:' % _('Write Cache Hit Ratio'),
+ '%.2f' % status['write_hit_ratio'],
+ )
+ self.add_header(' %s' % _('Read'))
+ self.add_info_field(
+ 'blocks_read', ' %s:' % _('Blocks Read'), status['disk.num_blocks_read']
+ )
+ self.add_info_field(
+ 'blocks_read_hit',
+ ' %s:' % _('Blocks Read hit'),
+ status['disk.num_blocks_cache_hits'],
+ )
+ self.add_info_field('reads', ' %s:' % _('Reads'), status['disk.num_read_ops'])
+ self.add_info_field(
+ 'read_hit_ratio',
+ ' %s:' % _('Read Cache Hit Ratio'),
+ '%.2f' % status['read_hit_ratio'],
+ )
+ self.add_header(' %s' % _('Size'))
+ self.add_info_field(
+ 'cache_size_info',
+ ' %s:' % _('Cache Size'),
+ status['disk.disk_blocks_in_use'],
+ )
+ self.add_info_field(
+ 'read_cache_size',
+ ' %s:' % _('Read Cache Size'),
+ status['disk.read_cache_blocks'],
+ )
+
+ @overrides(BasePreferencePane)
+ def update(self, active):
+ if active:
+ client.core.get_session_status(DISK_CACHE_KEYS).addCallback(
+ self.update_cache_status_fields
+ )
+
+ def update_cache_status_fields(self, status):
+ if not self.created:
+ self.build_pane(self.core_conf, status)
+ else:
+ for ipt in self.inputs:
+ if not ipt.has_input() and ipt.name in status:
+ ipt.set_value(status[ipt.name])
+ self.preferences.refresh()
diff --git a/deluge/ui/console/modes/preferences/preferences.py b/deluge/ui/console/modes/preferences/preferences.py
new file mode 100644
index 0000000..45a39a6
--- /dev/null
+++ b/deluge/ui/console/modes/preferences/preferences.py
@@ -0,0 +1,379 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+from collections import deque
+
+import deluge.component as component
+from deluge.decorators import overrides
+from deluge.ui.client import client
+from deluge.ui.console.modes.basemode import BaseMode
+from deluge.ui.console.modes.preferences.preference_panes import (
+ BandwidthPane,
+ CachePane,
+ DaemonPane,
+ DownloadsPane,
+ InterfacePane,
+ NetworkPane,
+ OtherPane,
+ ProxyPane,
+ QueuePane,
+)
+from deluge.ui.console.utils import curses_util as util
+from deluge.ui.console.widgets.fields import SelectInput
+from deluge.ui.console.widgets.popup import MessagePopup, PopupsHandler
+from deluge.ui.console.widgets.sidebar import Sidebar
+
+try:
+ import curses
+except ImportError:
+ pass
+
+
+log = logging.getLogger(__name__)
+
+
+# Big help string that gets displayed when the user hits 'h'
+HELP_STR = """This screen lets you view and configure various options in deluge.
+
+There are three main sections to this screen. Only one section is active at a time. \
+You can switch the active section by hitting TAB (or Shift-TAB to go back one)
+
+The section on the left displays the various categories that the settings fall in. \
+You can navigate the list using the up/down arrows
+
+The section on the right shows the settings for the selected category. When this \
+section is active you can navigate the various settings with the up/down arrows. \
+Special keys for each input type are described below.
+
+The final section is at the bottom right, the: [Cancel] [Apply] [OK] buttons.
+When this section is active, simply select the option you want using the arrow
+keys and press Enter to confim.
+
+
+Special keys for various input types are as follows:
+- For text inputs you can simply type in the value.
+
+{|indent: |}- For numeric inputs (indicated by the value being in []s), you can type a value, \
+or use PageUp and PageDown to increment/decrement the value.
+
+- For checkbox inputs use the spacebar to toggle
+
+{|indent: |}- For checkbox plus something else inputs (the something else being only visible \
+when you check the box) you can toggle the check with space, use the right \
+arrow to edit the other value, and escape to get back to the check box.
+
+"""
+
+
+class ZONE(object):
+ length = 3
+ CATEGORIES, PREFRENCES, ACTIONS = list(range(length))
+
+
+class PreferenceSidebar(Sidebar):
+ def __init__(self, torrentview, width):
+ height = curses.LINES - 2
+ Sidebar.__init__(
+ self, torrentview, width, height, title=None, border_off_north=1
+ )
+ self.categories = [
+ _('Interface'),
+ _('Downloads'),
+ _('Network'),
+ _('Bandwidth'),
+ _('Other'),
+ _('Daemon'),
+ _('Queue'),
+ _('Proxy'),
+ _('Cache'),
+ ]
+ for name in self.categories:
+ self.add_text_field(
+ name,
+ name,
+ selectable=True,
+ font_unfocused_active='bold',
+ color_unfocused_active='white,black',
+ )
+
+ def on_resize(self):
+ self.resize_window(curses.LINES - 2, self.width)
+
+
+class Preferences(BaseMode, PopupsHandler):
+ def __init__(self, parent_mode, stdscr, console_config, encoding=None):
+ BaseMode.__init__(self, stdscr, encoding=encoding, do_refresh=False)
+ PopupsHandler.__init__(self)
+ self.parent_mode = parent_mode
+ self.cur_cat = 0
+ self.messages = deque()
+ self.action_input = None
+ self.config_loaded = False
+ self.console_config = console_config
+ self.active_port = -1
+ self.active_zone = ZONE.CATEGORIES
+ self.sidebar_width = 15 # Width of the categories pane
+
+ self.sidebar = PreferenceSidebar(parent_mode, self.sidebar_width)
+ self.sidebar.set_focused(True)
+ self.sidebar.active_input = 0
+
+ self._calc_sizes(resize=False)
+
+ self.panes = [
+ InterfacePane(self),
+ DownloadsPane(self),
+ NetworkPane(self),
+ BandwidthPane(self),
+ OtherPane(self),
+ DaemonPane(self),
+ QueuePane(self),
+ ProxyPane(self),
+ CachePane(self),
+ ]
+
+ self.action_input = SelectInput(
+ self, None, None, [_('Cancel'), _('Apply'), _('OK')], [0, 1, 2], 0
+ )
+
+ def load_config(self):
+ if self.config_loaded:
+ return
+
+ def on_get_config(core_config):
+ self.core_config = core_config
+ self.config_loaded = True
+ for p in self.panes:
+ p.create_pane(core_config, self.console_config)
+ self.refresh()
+
+ client.core.get_config().addCallback(on_get_config)
+
+ def on_get_listen_port(port):
+ self.active_port = port
+
+ client.core.get_listen_port().addCallback(on_get_listen_port)
+
+ @property
+ def height(self):
+ # top/bottom bars: 2, Action buttons (Cancel/Apply/OK): 1
+ return self.rows - 3
+
+ @property
+ def width(self):
+ return self.prefs_width
+
+ def _calc_sizes(self, resize=True):
+ self.prefs_width = self.cols - self.sidebar_width
+
+ if not resize:
+ return
+
+ for p in self.panes:
+ p.resize_window(self.height, p.pane_width)
+
+ def _draw_preferences(self):
+ self.cur_cat = self.sidebar.active_input
+ self.panes[self.cur_cat].render(
+ self, self.stdscr, self.prefs_width, self.active_zone == ZONE.PREFRENCES
+ )
+ self.panes[self.cur_cat].refresh()
+
+ def _draw_actions(self):
+ selected = self.active_zone == ZONE.ACTIONS
+ self.stdscr.hline(self.rows - 3, self.sidebar_width, b'_', self.cols)
+ self.action_input.render(
+ self.stdscr,
+ self.rows - 2,
+ width=self.cols,
+ active=selected,
+ focus=True,
+ col=self.cols - 22,
+ )
+
+ @overrides(BaseMode)
+ def on_resize(self, rows, cols):
+ BaseMode.on_resize(self, rows, cols)
+ self._calc_sizes()
+
+ if self.popup:
+ self.popup.handle_resize()
+
+ self.sidebar.on_resize()
+ self.refresh()
+
+ @overrides(component.Component)
+ def update(self):
+ for i, p in enumerate(self.panes):
+ self.panes[i].update(i == self.cur_cat)
+
+ @overrides(BaseMode)
+ def resume(self):
+ BaseMode.resume(self)
+ self.sidebar.show()
+
+ @overrides(BaseMode)
+ def refresh(self):
+ if (
+ not component.get('ConsoleUI').is_active_mode(self)
+ or not self.config_loaded
+ ):
+ return
+
+ if self.popup is None and self.messages:
+ title, msg = self.messages.popleft()
+ self.push_popup(MessagePopup(self, title, msg))
+
+ self.stdscr.erase()
+ self.draw_statusbars()
+ self._draw_actions()
+ # Necessary to force updating the stdscr
+ self.stdscr.noutrefresh()
+
+ self.sidebar.refresh()
+
+ # do this last since it moves the cursor
+ self._draw_preferences()
+
+ if self.popup:
+ self.popup.refresh()
+
+ curses.doupdate()
+
+ def _apply_prefs(self):
+ if self.core_config is None:
+ return
+
+ def update_conf_value(key, source_dict, dest_dict, updated):
+ if dest_dict[key] != source_dict[key]:
+ dest_dict[key] = source_dict[key]
+ updated = True
+ return updated
+
+ new_core_config = {}
+ for pane in self.panes:
+ if not isinstance(pane, InterfacePane):
+ pane.add_config_values(new_core_config)
+ # Apply Core Prefs
+ if client.connected():
+ # Only do this if we're connected to a daemon
+ config_to_set = {}
+ for key in new_core_config:
+ # The values do not match so this needs to be updated
+ if self.core_config[key] != new_core_config[key]:
+ config_to_set[key] = new_core_config[key]
+
+ if config_to_set:
+ # Set each changed config value in the core
+ client.core.set_config(config_to_set)
+ client.force_call(True)
+ # Update the configuration
+ self.core_config.update(config_to_set)
+
+ # Update Interface Prefs
+ new_console_config = {}
+ didupdate = False
+ for pane in self.panes:
+ # could just access panes by index, but that would break if panes
+ # are ever reordered, so do it the slightly slower but safer way
+ if isinstance(pane, InterfacePane):
+ pane.add_config_values(new_console_config)
+ for k in ['ring_bell', 'language']:
+ didupdate = update_conf_value(
+ k, new_console_config, self.console_config, didupdate
+ )
+ for k in ['separate_complete', 'move_selection']:
+ didupdate = update_conf_value(
+ k,
+ new_console_config,
+ self.console_config['torrentview'],
+ didupdate,
+ )
+ for k in [
+ 'ignore_duplicate_lines',
+ 'save_command_history',
+ 'third_tab_lists_all',
+ 'torrents_per_tab_press',
+ ]:
+ didupdate = update_conf_value(
+ k, new_console_config, self.console_config['cmdline'], didupdate
+ )
+
+ if didupdate:
+ self.parent_mode.on_config_changed()
+
+ def _update_preferences(self, core_config):
+ self.core_config = core_config
+ for pane in self.panes:
+ pane.update_values(core_config)
+
+ def _actions_read(self, c):
+ self.action_input.handle_read(c)
+ if c in [curses.KEY_ENTER, util.KEY_ENTER2]:
+ # take action
+ if self.action_input.selected_index == 0: # Cancel
+ self.back_to_parent()
+ elif self.action_input.selected_index == 1: # Apply
+ self._apply_prefs()
+ client.core.get_config().addCallback(self._update_preferences)
+ elif self.action_input.selected_index == 2: # OK
+ self._apply_prefs()
+ self.back_to_parent()
+
+ def back_to_parent(self):
+ component.get('ConsoleUI').set_mode(self.parent_mode.mode_name)
+
+ @overrides(BaseMode)
+ def read_input(self):
+ c = self.stdscr.getch()
+
+ if self.popup:
+ if self.popup.handle_read(c):
+ self.pop_popup()
+ self.refresh()
+ return
+
+ if util.is_printable_chr(c):
+ char = chr(c)
+ if char == 'Q':
+ component.get('ConsoleUI').quit()
+ elif char == 'h':
+ self.push_popup(MessagePopup(self, 'Preferences Help', HELP_STR))
+
+ if self.sidebar.has_focus() and c == util.KEY_ESC:
+ self.back_to_parent()
+ return
+
+ def update_active_zone(val):
+ self.active_zone += val
+ if self.active_zone == -1:
+ self.active_zone = ZONE.length - 1
+ else:
+ self.active_zone %= ZONE.length
+ self.sidebar.set_focused(self.active_zone == ZONE.CATEGORIES)
+
+ if c == util.KEY_TAB:
+ update_active_zone(1)
+ elif c == curses.KEY_BTAB:
+ update_active_zone(-1)
+ else:
+ if self.active_zone == ZONE.CATEGORIES:
+ self.sidebar.handle_read(c)
+ elif self.active_zone == ZONE.PREFRENCES:
+ self.panes[self.cur_cat].handle_read(c)
+ elif self.active_zone == ZONE.ACTIONS:
+ self._actions_read(c)
+
+ self.refresh()
+
+ def is_active_pane(self, pane):
+ return pane == self.panes[self.cur_cat]
diff --git a/deluge/ui/console/modes/torrentdetail.py b/deluge/ui/console/modes/torrentdetail.py
new file mode 100644
index 0000000..d02a0d3
--- /dev/null
+++ b/deluge/ui/console/modes/torrentdetail.py
@@ -0,0 +1,1026 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import division, unicode_literals
+
+import logging
+
+import deluge.component as component
+from deluge.common import fsize
+from deluge.decorators import overrides
+from deluge.ui.client import client
+from deluge.ui.common import FILE_PRIORITY
+from deluge.ui.console.modes.basemode import BaseMode
+from deluge.ui.console.modes.torrentlist.torrentactions import (
+ ACTION,
+ torrent_actions_popup,
+)
+from deluge.ui.console.utils import colors
+from deluge.ui.console.utils import curses_util as util
+from deluge.ui.console.utils.column import get_column_value, torrent_data_fields
+from deluge.ui.console.utils.format_utils import (
+ format_priority,
+ format_progress,
+ format_row,
+)
+from deluge.ui.console.widgets.popup import (
+ InputPopup,
+ MessagePopup,
+ PopupsHandler,
+ SelectablePopup,
+)
+
+try:
+ import curses
+except ImportError:
+ pass
+
+log = logging.getLogger(__name__)
+
+# Big help string that gets displayed when the user hits 'h'
+HELP_STR = """\
+This screen shows detailed information about a torrent, and also the \
+information about the individual files in the torrent.
+
+You can navigate the file list with the Up/Down arrows and use space to \
+collapse/expand the file tree.
+
+All popup windows can be closed/canceled by hitting the Esc key \
+(you might need to wait a second for an Esc to register)
+
+The actions you can perform and the keys to perform them are as follows:
+
+{!info!}'h'{!normal!} - Show this help
+
+{!info!}'a'{!normal!} - Show torrent actions popup. Here you can do things like \
+pause/resume, recheck, set torrent options and so on.
+
+{!info!}'r'{!normal!} - Rename currently highlighted folder or a file. You can't \
+rename multiple files at once so you need to first clear your selection \
+with {!info!}'c'{!normal!}
+
+{!info!}'m'{!normal!} - Mark or unmark a file or a folder
+{!info!}'c'{!normal!} - Un-mark all files
+
+{!info!}Space{!normal!} - Expand/Collapse currently selected folder
+
+{!info!}Enter{!normal!} - Show priority popup in which you can set the \
+download priority of selected files and folders.
+
+{!info!}Left Arrow{!normal!} - Go back to torrent overview.
+"""
+
+
+class TorrentDetail(BaseMode, PopupsHandler):
+ def __init__(self, parent_mode, stdscr, console_config, encoding=None):
+ PopupsHandler.__init__(self)
+ self.console_config = console_config
+ self.parent_mode = parent_mode
+ self.torrentid = None
+ self.torrent_state = None
+ self._status_keys = [
+ 'files',
+ 'name',
+ 'state',
+ 'download_payload_rate',
+ 'upload_payload_rate',
+ 'progress',
+ 'eta',
+ 'all_time_download',
+ 'total_uploaded',
+ 'ratio',
+ 'num_seeds',
+ 'total_seeds',
+ 'num_peers',
+ 'total_peers',
+ 'active_time',
+ 'seeding_time',
+ 'time_added',
+ 'distributed_copies',
+ 'num_pieces',
+ 'piece_length',
+ 'download_location',
+ 'file_progress',
+ 'file_priorities',
+ 'message',
+ 'total_wanted',
+ 'tracker_host',
+ 'owner',
+ 'seed_rank',
+ 'last_seen_complete',
+ 'completed_time',
+ 'time_since_transfer',
+ 'super_seeding',
+ ]
+ self.file_list = None
+ self.current_file = None
+ self.current_file_idx = 0
+ self.file_off = 0
+ self.more_to_draw = False
+ self.full_names = None
+ self.column_string = ''
+ self.files_sep = None
+ self.marked = {}
+
+ BaseMode.__init__(self, stdscr, encoding)
+ self.column_names = ['Filename', 'Size', 'Progress', 'Priority']
+ self.__update_columns()
+
+ self._listing_start = self.rows // 2
+ self._listing_space = self._listing_start - self._listing_start
+
+ client.register_event_handler(
+ 'TorrentFileRenamedEvent', self._on_torrentfilerenamed_event
+ )
+ client.register_event_handler(
+ 'TorrentFolderRenamedEvent', self._on_torrentfolderrenamed_event
+ )
+ client.register_event_handler(
+ 'TorrentRemovedEvent', self._on_torrentremoved_event
+ )
+
+ util.safe_curs_set(util.Curser.INVISIBLE)
+ self.stdscr.notimeout(0)
+
+ def set_torrent_id(self, torrentid):
+ self.torrentid = torrentid
+ self.file_list = None
+
+ def back_to_overview(self):
+ component.get('ConsoleUI').set_mode(self.parent_mode.mode_name)
+
+ @overrides(component.Component)
+ def start(self):
+ self.update()
+
+ @overrides(component.Component)
+ def update(self, torrentid=None):
+ if torrentid:
+ self.set_torrent_id(torrentid)
+
+ if self.torrentid:
+ component.get('SessionProxy').get_torrent_status(
+ self.torrentid, self._status_keys
+ ).addCallback(self.set_state)
+
+ @overrides(BaseMode)
+ def pause(self):
+ self.set_torrent_id(None)
+
+ @overrides(BaseMode)
+ def on_resize(self, rows, cols):
+ BaseMode.on_resize(self, rows, cols)
+ self.__update_columns()
+ if self.popup:
+ self.popup.handle_resize()
+
+ self._listing_start = self.rows // 2
+ self.refresh()
+
+ def set_state(self, state):
+
+ if state.get('files'):
+ self.full_names = {x['index']: x['path'] for x in state['files']}
+
+ need_prio_update = False
+ if not self.file_list:
+ # don't keep getting the files once we've got them once
+ if state.get('files'):
+ self.files_sep = '{!green,black,bold,underline!}%s' % (
+ ('Files (torrent has %d files)' % len(state['files'])).center(
+ self.cols
+ )
+ )
+ self.file_list, self.file_dict = self.build_file_list(
+ state['files'], state['file_progress'], state['file_priorities']
+ )
+ else:
+ self.files_sep = '{!green,black,bold,underline!}%s' % (
+ ('Files (File list unknown)').center(self.cols)
+ )
+ need_prio_update = True
+
+ self.__fill_progress(self.file_list, state['file_progress'])
+
+ for i, prio in enumerate(state['file_priorities']):
+ if self.file_dict[i][6] != prio:
+ need_prio_update = True
+ self.file_dict[i][6] = prio
+ if need_prio_update and self.file_list:
+ self.__fill_prio(self.file_list)
+ del state['file_progress']
+ del state['file_priorities']
+ self.torrent_state = state
+ self.refresh()
+
+ def build_file_list(self, torrent_files, progress, priority):
+ """ Split file list from torrent state into a directory tree.
+
+ Returns:
+
+ Tuple:
+ A list of lists in the form:
+ [file/dir_name, index, size, children, expanded, progress, priority]
+
+ Dictionary:
+ Map of file index for fast updating of progress and priorities.
+ """
+
+ file_list = []
+ file_dict = {}
+ # directory index starts from total file count.
+ dir_idx = len(torrent_files)
+ for torrent_file in torrent_files:
+ cur = file_list
+ paths = torrent_file['path'].split('/')
+ for path in paths:
+ if not cur or path != cur[-1][0]:
+ child_list = []
+ if path == paths[-1]:
+ file_progress = format_progress(
+ progress[torrent_file['index']] * 100
+ )
+ entry = [
+ path,
+ torrent_file['index'],
+ torrent_file['size'],
+ child_list,
+ False,
+ file_progress,
+ priority[torrent_file['index']],
+ ]
+ file_dict[torrent_file['index']] = entry
+ else:
+ entry = [path, dir_idx, -1, child_list, False, 0, -1]
+ file_dict[dir_idx] = entry
+ dir_idx += 1
+ cur.append(entry)
+ cur = child_list
+ else:
+ cur = cur[-1][3]
+ self.__build_sizes(file_list)
+ self.__fill_progress(file_list, progress)
+
+ return file_list, file_dict
+
+ # fill in the sizes of the directory entries based on their children
+ def __build_sizes(self, fs):
+ ret = 0
+ for f in fs:
+ if f[2] == -1:
+ val = self.__build_sizes(f[3])
+ ret += val
+ f[2] = val
+ else:
+ ret += f[2]
+ return ret
+
+ # fills in progress fields in all entries based on progs
+ # returns the # of bytes complete in all the children of fs
+ def __fill_progress(self, fs, progs):
+ if not progs:
+ return 0
+ tb = 0
+ for f in fs:
+ if f[3]: # dir, has some children
+ bd = self.__fill_progress(f[3], progs)
+ f[5] = format_progress(bd // f[2] * 100)
+ else: # file, update own prog and add to total
+ bd = f[2] * progs[f[1]]
+ f[5] = format_progress(progs[f[1]] * 100)
+ tb += bd
+ return tb
+
+ def __fill_prio(self, fs):
+ for f in fs:
+ if f[3]: # dir, so fill in children and compute our prio
+ self.__fill_prio(f[3])
+ child_prios = [e[6] for e in f[3]]
+ if len(child_prios) > 1:
+ f[6] = -2 # mixed
+ else:
+ f[6] = child_prios.pop(0)
+
+ def __update_columns(self):
+ self.column_widths = [-1, 15, 15, 20]
+ req = sum(col_width for col_width in self.column_widths if col_width >= 0)
+ if req > self.cols: # can't satisfy requests, just spread out evenly
+ cw = self.cols // len(self.column_names)
+ for i in range(0, len(self.column_widths)):
+ self.column_widths[i] = cw
+ else:
+ rem = self.cols - req
+ var_cols = len(
+ [col_width for col_width in self.column_widths if col_width < 0]
+ )
+ vw = rem // var_cols
+ for i in range(0, len(self.column_widths)):
+ if self.column_widths[i] < 0:
+ self.column_widths[i] = vw
+
+ self.column_string = '{!green,black,bold!}%s' % (
+ ''.join(
+ [
+ '%s%s'
+ % (
+ self.column_names[i],
+ ' ' * (self.column_widths[i] - len(self.column_names[i])),
+ )
+ for i in range(0, len(self.column_names))
+ ]
+ )
+ )
+
+ def _on_torrentremoved_event(self, torrent_id):
+ if torrent_id == self.torrentid:
+ self.back_to_overview()
+
+ def _on_torrentfilerenamed_event(self, torrent_id, index, new_name):
+ if torrent_id == self.torrentid:
+ self.file_dict[index][0] = new_name.split('/')[-1]
+ component.get('SessionProxy').get_torrent_status(
+ self.torrentid, self._status_keys
+ ).addCallback(self.set_state)
+
+ def _on_torrentfolderrenamed_event(self, torrent_id, old_folder, new_folder):
+ if torrent_id == self.torrentid:
+ fe = None
+ fl = None
+ for i in old_folder.strip('/').split('/'):
+ if not fl:
+ fe = fl = self.file_list
+ s = [files for files in fl if files[0].strip('/') == i][0]
+ fe = s
+ fl = s[3]
+ fe[0] = new_folder.strip('/').rpartition('/')[-1]
+
+ # self.__get_file_by_name(old_folder, self.file_list)[0] = new_folder.strip('/')
+ component.get('SessionProxy').get_torrent_status(
+ self.torrentid, self._status_keys
+ ).addCallback(self.set_state)
+
+ def draw_files(self, files, depth, off, idx):
+
+ color_selected = 'blue'
+ color_partially_selected = 'magenta'
+ color_highlighted = 'white'
+ for fl in files:
+ # from sys import stderr
+ # print >> stderr, fl[6]
+ # kick out if we're going to draw too low on the screen
+ if off >= self.rows - 1:
+ self.more_to_draw = True
+ return -1, -1
+
+ # default color values
+ fg = 'white'
+ bg = 'black'
+ attr = ''
+
+ priority_fg_color = {
+ -2: 'white', # Mixed
+ 0: 'red', # Skip
+ 1: 'yellow', # Low
+ 2: 'yellow',
+ 3: 'yellow',
+ 4: 'white', # Normal
+ 5: 'green',
+ 6: 'green',
+ 7: 'green', # High
+ }
+
+ fg = priority_fg_color[fl[6]]
+
+ if idx >= self.file_off:
+ # set fg/bg colors based on whether the file is selected/marked or not
+
+ if fl[1] in self.marked:
+ bg = color_selected
+ if fl[3]:
+ if self.marked[fl[1]] < self.__get_contained_files_count(
+ file_list=fl[3]
+ ):
+ bg = color_partially_selected
+ attr = 'bold'
+
+ if idx == self.current_file_idx:
+ self.current_file = fl
+ bg = color_highlighted
+ if fl[1] in self.marked:
+ fg = color_selected
+ if fl[3]:
+ if self.marked[fl[1]] < self.__get_contained_files_count(
+ file_list=fl[3]
+ ):
+ fg = color_partially_selected
+ else:
+ if fg == 'white':
+ fg = 'black'
+ attr = 'bold'
+
+ if attr:
+ color_string = '{!%s,%s,%s!}' % (fg, bg, attr)
+ else:
+ color_string = '{!%s,%s!}' % (fg, bg)
+
+ # actually draw the dir/file string
+ if fl[3] and fl[4]: # this is an expanded directory
+ xchar = 'v'
+ elif fl[3]: # collapsed directory
+ xchar = '>'
+ else: # file
+ xchar = '-'
+
+ r = format_row(
+ [
+ '%s%s %s' % (' ' * depth, xchar, fl[0]),
+ fsize(fl[2]),
+ fl[5],
+ format_priority(fl[6]),
+ ],
+ self.column_widths,
+ )
+
+ self.add_string(off, '%s%s' % (color_string, r), trim=False)
+ off += 1
+
+ if fl[3] and fl[4]:
+ # recurse if we have children and are expanded
+ off, idx = self.draw_files(fl[3], depth + 1, off, idx + 1)
+ if off < 0:
+ return (off, idx)
+ else:
+ idx += 1
+
+ return (off, idx)
+
+ def __get_file_list_length(self, file_list=None):
+ """
+ Counts length of the displayed file list.
+ """
+ if file_list is None:
+ file_list = self.file_list
+ length = 0
+ if file_list:
+ for element in file_list:
+ length += 1
+ if element[3] and element[4]:
+ length += self.__get_file_list_length(element[3])
+ return length
+
+ def __get_contained_files_count(self, file_list=None, idx=None):
+ length = 0
+ if file_list is None:
+ file_list = self.file_list
+ if idx is not None:
+ for element in file_list:
+ if element[1] == idx:
+ return self.__get_contained_files_count(file_list=element[3])
+ elif element[3]:
+ c = self.__get_contained_files_count(file_list=element[3], idx=idx)
+ if c > 0:
+ return c
+ else:
+ for element in file_list:
+ length += 1
+ if element[3]:
+ length -= 1
+ length += self.__get_contained_files_count(element[3])
+ return length
+
+ def render_header(self, row):
+ status = self.torrent_state
+
+ download_color = '{!info!}'
+ if status['download_payload_rate'] > 0:
+ download_color = colors.state_color['Downloading']
+
+ def add_field(name, row, pre_color='{!info!}', post_color='{!input!}'):
+ s = '%s%s: %s%s' % (
+ pre_color,
+ torrent_data_fields[name]['name'],
+ post_color,
+ get_column_value(name, status),
+ )
+ if row:
+ row = self.add_string(row, s)
+ return row
+ return s
+
+ # Name
+ row = add_field('name', row)
+ # State
+ row = add_field('state', row)
+
+ # Print DL info and ETA
+ s = add_field('downloaded', 0, download_color)
+ if status['progress'] != 100.0:
+ s += '/%s' % fsize(status['total_wanted'])
+ if status['download_payload_rate'] > 0:
+ s += ' {!yellow!}@ %s%s' % (
+ download_color,
+ fsize(status['download_payload_rate']),
+ )
+ s += add_field('eta', 0)
+ if s:
+ row = self.add_string(row, s)
+
+ # Print UL info and ratio
+ s = add_field('uploaded', 0, download_color)
+ if status['upload_payload_rate'] > 0:
+ s += ' {!yellow!}@ %s%s' % (
+ colors.state_color['Seeding'],
+ fsize(status['upload_payload_rate']),
+ )
+ s += ' ' + add_field('ratio', 0)
+ row = self.add_string(row, s)
+
+ # Seed/peer info
+ s = '{!info!}%s:{!green!} %s {!input!}(%s)' % (
+ torrent_data_fields['seeds']['name'],
+ status['num_seeds'],
+ status['total_seeds'],
+ )
+ row = self.add_string(row, s)
+ s = '{!info!}%s:{!red!} %s {!input!}(%s)' % (
+ torrent_data_fields['peers']['name'],
+ status['num_peers'],
+ status['total_peers'],
+ )
+ row = self.add_string(row, s)
+
+ # Tracker
+ tracker_color = '{!green!}' if status['message'] == 'OK' else '{!red!}'
+ s = '{!info!}%s: {!magenta!}%s{!input!} says "%s%s{!input!}"' % (
+ torrent_data_fields['tracker']['name'],
+ status['tracker_host'],
+ tracker_color,
+ status['message'],
+ )
+ row = self.add_string(row, s)
+
+ # Pieces and availability
+ s = '{!info!}%s: {!yellow!}%s {!input!}x {!yellow!}%s' % (
+ torrent_data_fields['pieces']['name'],
+ status['num_pieces'],
+ fsize(status['piece_length']),
+ )
+ if status['distributed_copies']:
+ s += '{!info!}%s: {!input!}%s' % (
+ torrent_data_fields['seed_rank']['name'],
+ status['seed_rank'],
+ )
+ row = self.add_string(row, s)
+
+ # Time added
+ row = add_field('time_added', row)
+ # Time active
+ row = add_field('active_time', row)
+ if status['seeding_time']:
+ row = add_field('seeding_time', row)
+ # Download Folder
+ row = add_field('download_location', row)
+ # Seed Rank
+ row = add_field('seed_rank', row)
+ # Super Seeding
+ row = add_field('super_seeding', row)
+ # Last seen complete
+ row = add_field('last_seen_complete', row)
+ # Last activity
+ row = add_field('time_since_transfer', row)
+ # Owner
+ if status['owner']:
+ row = add_field('owner', row)
+ return row
+ # Last act
+
+ @overrides(BaseMode)
+ def refresh(self, lines=None):
+ # Update the status bars
+ self.stdscr.erase()
+ self.draw_statusbars()
+
+ row = 1
+ if self.torrent_state:
+ row = self.render_header(row)
+ else:
+ self.add_string(1, 'Waiting for torrent state')
+
+ row += 1
+
+ if self.files_sep:
+ self.add_string(row, self.files_sep)
+ row += 1
+
+ self._listing_start = row
+ self._listing_space = self.rows - self._listing_start
+
+ self.add_string(row, self.column_string)
+ if self.file_list:
+ row += 1
+ self.more_to_draw = False
+ self.draw_files(self.file_list, 0, row, 0)
+
+ if not component.get('ConsoleUI').is_active_mode(self):
+ return
+
+ self.stdscr.noutrefresh()
+
+ if self.popup:
+ self.popup.refresh()
+
+ curses.doupdate()
+
+ def expcol_cur_file(self):
+ """
+ Expand or collapse current file
+ """
+ self.current_file[4] = not self.current_file[4]
+ self.refresh()
+
+ def file_list_down(self, rows=1):
+ maxlen = self.__get_file_list_length() - 1
+
+ self.current_file_idx += rows
+
+ if self.current_file_idx > maxlen:
+ self.current_file_idx = maxlen
+
+ if self.current_file_idx > self.file_off + (self._listing_space - 3):
+ self.file_off = self.current_file_idx - (self._listing_space - 3)
+
+ self.refresh()
+
+ def file_list_up(self, rows=1):
+ self.current_file_idx = max(0, self.current_file_idx - rows)
+ self.file_off = min(self.file_off, self.current_file_idx)
+ self.refresh()
+
+ # build list of priorities for all files in the torrent
+ # based on what is currently selected and a selected priority.
+ def build_prio_list(self, files, ret_list, parent_prio, selected_prio):
+ # has a priority been set on my parent (if so, I inherit it)
+ for f in files:
+ # Do not set priorities for the whole dir, just selected contents
+ if f[3]:
+ self.build_prio_list(f[3], ret_list, parent_prio, selected_prio)
+ else: # file, need to add to list
+ if f[1] in self.marked or parent_prio >= 0:
+ # selected (or parent selected), use requested priority
+ ret_list.append((f[1], selected_prio))
+ else:
+ # not selected, just keep old priority
+ ret_list.append((f[1], f[6]))
+
+ def do_priority(self, priority, was_empty):
+ plist = []
+ self.build_prio_list(self.file_list, plist, -1, priority)
+ plist.sort()
+ priorities = [p[1] for p in plist]
+ client.core.set_torrent_options(
+ [self.torrentid], {'file_priorities': priorities}
+ )
+
+ if was_empty:
+ self.marked = {}
+ return True
+
+ # show popup for priority selections
+ def show_priority_popup(self, was_empty):
+ def popup_func(name, data, was_empty, **kwargs):
+ if not name:
+ return
+ return self.do_priority(data[name], was_empty)
+
+ if self.marked:
+ popup = SelectablePopup(
+ self,
+ 'Set File Priority',
+ popup_func,
+ border_off_north=1,
+ cb_args={'was_empty': was_empty},
+ )
+ popup.add_line(
+ 'skip_priority',
+ '_Skip',
+ foreground='red',
+ cb_arg=FILE_PRIORITY['Low'],
+ was_empty=was_empty,
+ )
+ popup.add_line(
+ 'low_priority', '_Low', cb_arg=FILE_PRIORITY['Low'], foreground='yellow'
+ )
+ popup.add_line('normal_priority', '_Normal', cb_arg=FILE_PRIORITY['Normal'])
+ popup.add_line(
+ 'high_priority',
+ '_High',
+ cb_arg=FILE_PRIORITY['High'],
+ foreground='green',
+ )
+ popup._selected = 1
+ self.push_popup(popup)
+
+ def __mark_unmark(self, idx):
+ """
+ Selects or unselects file or a catalog(along with contained files)
+ """
+ fc = self.__get_contained_files_count(idx=idx)
+ if idx not in self.marked:
+ # Not selected, select it
+ self.__mark_tree(self.file_list, idx)
+ elif self.marked[idx] < fc:
+ # Partially selected, unselect all contents
+ self.__unmark_tree(self.file_list, idx)
+ else:
+ # Selected, unselect it
+ self.__unmark_tree(self.file_list, idx)
+
+ def __mark_tree(self, file_list, idx, mark_all=False):
+ """
+ Given file_list of TorrentDetail and index of file or folder,
+ recursively selects all files contained
+ as well as marks folders higher in hierarchy as partially selected
+ """
+ total_marked = 0
+ for element in file_list:
+ marked = 0
+ # Select the file if it's the one we want or
+ # if it's inside a directory that got selected
+ if (element[1] == idx) or mark_all:
+ # If it's a folder then select everything inside
+ if element[3]:
+ marked = self.__mark_tree(element[3], idx, True)
+ self.marked[element[1]] = marked
+ else:
+ marked = 1
+ self.marked[element[1]] = 1
+ else:
+ # Does not match but the item to be selected might be inside, recurse
+ if element[3]:
+ marked = self.__mark_tree(element[3], idx, False)
+ # Partially select the folder if it contains files that were selected
+ if marked > 0:
+ self.marked[element[1]] = marked
+ else:
+ if element[1] in self.marked:
+ # It's not the element we want but it's marked so count it
+ marked = 1
+ # Count and then return total amount of files selected in all subdirectories
+ total_marked += marked
+
+ return total_marked
+
+ def __get_file_by_num(self, num, file_list, idx=0):
+ for element in file_list:
+ if idx == num:
+ return element
+ if element[3] and element[4]:
+ i = self.__get_file_by_num(num, element[3], idx + 1)
+ if not isinstance(i, int):
+ return i
+ idx = i
+ else:
+ idx += 1
+ return idx
+
+ def __get_file_by_name(self, name, file_list, idx=0):
+ for element in file_list:
+ if element[0].strip('/') == name.strip('/'):
+ return element
+ if element[3] and element[4]:
+ i = self.__get_file_by_name(name, element[3], idx + 1)
+ if not isinstance(i, int):
+ return i
+ else:
+ idx = i
+ else:
+ idx += 1
+ return idx
+
+ def __unmark_tree(self, file_list, idx, unmark_all=False):
+ """
+ Given file_list of TorrentDetail and index of file or folder,
+ recursively deselects all files contained
+ as well as marks folders higher in hierarchy as unselected or partially selected
+ """
+ total_marked = 0
+ for element in file_list:
+ marked = 0
+ # It's either the item we want to select or
+ # a contained item, deselect it
+ if (element[1] == idx) or unmark_all:
+ if element[1] in self.marked:
+ del self.marked[element[1]]
+ # Deselect all contents if it's a catalog
+ if element[3]:
+ self.__unmark_tree(element[3], idx, True)
+ else:
+ # Not file we wanted but it might be inside this folder, recurse inside
+ if element[3]:
+ marked = self.__unmark_tree(element[3], idx, False)
+ # If none of the contents remain selected, unselect this folder as well
+ if marked == 0:
+ if element[1] in self.marked:
+ del self.marked[element[1]]
+ # Otherwise update selection count
+ else:
+ self.marked[element[1]] = marked
+ else:
+ if element[1] in self.marked:
+ marked = 1
+
+ # Count and then return selection count so we can update
+ # directories higher up in the hierarchy
+ total_marked += marked
+ return total_marked
+
+ def _selection_to_file_idx(self, file_list=None, idx=0, true_idx=0, closed=False):
+ if not file_list:
+ file_list = self.file_list
+
+ for element in file_list:
+ if idx == self.current_file_idx:
+ return true_idx
+
+ # It's a folder
+ if element[3]:
+ i = self._selection_to_file_idx(
+ element[3], idx + 1, true_idx, closed or not element[4]
+ )
+ if isinstance(i, tuple):
+ idx, true_idx = i
+ if element[4]:
+ idx, true_idx = i
+ else:
+ idx += 1
+ tmp, true_idx = i
+ else:
+ return i
+ else:
+ if not closed:
+ idx += 1
+ true_idx += 1
+
+ return (idx, true_idx)
+
+ def _get_full_folder_path(self, num, file_list=None, path='', idx=0):
+ if not file_list:
+ file_list = self.file_list
+
+ for element in file_list:
+ if not element[3]:
+ idx += 1
+ continue
+ if num == idx:
+ return '%s%s/' % (path, element[0])
+ if element[4]:
+ i = self._get_full_folder_path(
+ num, element[3], path + element[0] + '/', idx + 1
+ )
+ if not isinstance(i, int):
+ return i
+ idx = i
+ else:
+ idx += 1
+ return idx
+
+ def _do_rename_folder(self, torrent_id, folder, new_folder):
+ client.core.rename_folder(torrent_id, folder, new_folder)
+
+ def _do_rename_file(self, torrent_id, file_idx, new_filename):
+ if not new_filename:
+ return
+ client.core.rename_files(torrent_id, [(file_idx, new_filename)])
+
+ def _show_rename_popup(self):
+ # Perhaps in the future: Renaming multiple files
+ if self.marked:
+ self.report_message(
+ 'Error (Enter to close)',
+ 'Sorry, you cannot rename multiple files, please clear '
+ 'selection with {!info!}"c"{!normal!} key',
+ )
+ else:
+ _file = self.__get_file_by_num(self.current_file_idx, self.file_list)
+ old_filename = _file[0]
+ idx = self._selection_to_file_idx()
+ tid = self.torrentid
+
+ if _file[3]:
+
+ def do_rename(result, **kwargs):
+ if (
+ not result
+ or not result['new_foldername']['value']
+ or kwargs.get('close', False)
+ ):
+ self.popup.close(None, call_cb=False)
+ return
+ old_fname = self._get_full_folder_path(self.current_file_idx)
+ new_fname = '%s/%s/' % (
+ old_fname.strip('/').rpartition('/')[0],
+ result['new_foldername']['value'],
+ )
+ self._do_rename_folder(tid, old_fname, new_fname)
+
+ popup = InputPopup(
+ self, 'Rename folder (Esc to cancel)', close_cb=do_rename
+ )
+ popup.add_text_input(
+ 'new_foldername',
+ 'Enter new folder name:',
+ old_filename.strip('/'),
+ complete=True,
+ )
+ self.push_popup(popup)
+ else:
+
+ def do_rename(result, **kwargs):
+ if (
+ not result
+ or not result['new_filename']['value']
+ or kwargs.get('close', False)
+ ):
+ self.popup.close(None, call_cb=False)
+ return
+ fname = '%s/%s' % (
+ self.full_names[idx].rpartition('/')[0],
+ result['new_filename']['value'],
+ )
+ self._do_rename_file(tid, idx, fname)
+
+ popup = InputPopup(self, ' Rename file ', close_cb=do_rename)
+ popup.add_text_input(
+ 'new_filename', 'Enter new filename:', old_filename, complete=True
+ )
+ self.push_popup(popup)
+
+ @overrides(BaseMode)
+ def read_input(self):
+ c = self.stdscr.getch()
+
+ if self.popup:
+ ret = self.popup.handle_read(c)
+ if ret != util.ReadState.IGNORED and self.popup.closed():
+ self.pop_popup()
+ self.refresh()
+ return
+
+ if c in [util.KEY_ESC, curses.KEY_LEFT, ord('q')]:
+ self.back_to_overview()
+ return util.ReadState.READ
+
+ if not self.torrent_state:
+ # actions below only make sense if there is a torrent state
+ return
+
+ # Navigate the torrent list
+ if c == curses.KEY_UP:
+ self.file_list_up()
+ elif c == curses.KEY_PPAGE:
+ self.file_list_up(self._listing_space - 2)
+ elif c == curses.KEY_HOME:
+ self.file_off = 0
+ self.current_file_idx = 0
+ elif c == curses.KEY_DOWN:
+ self.file_list_down()
+ elif c == curses.KEY_NPAGE:
+ self.file_list_down(self._listing_space - 2)
+ elif c == curses.KEY_END:
+ self.current_file_idx = self.__get_file_list_length() - 1
+ self.file_off = self.current_file_idx - (self._listing_space - 3)
+ elif c == curses.KEY_DC:
+ torrent_actions_popup(self, [self.torrentid], action=ACTION.REMOVE)
+ elif c in [curses.KEY_ENTER, util.KEY_ENTER2]:
+ was_empty = self.marked == {}
+ self.__mark_tree(self.file_list, self.current_file[1])
+ self.show_priority_popup(was_empty)
+ elif c == util.KEY_SPACE:
+ self.expcol_cur_file()
+ elif c == ord('m'):
+ if self.current_file:
+ self.__mark_unmark(self.current_file[1])
+ elif c == ord('r'):
+ self._show_rename_popup()
+ elif c == ord('c'):
+ self.marked = {}
+ elif c == ord('a'):
+ torrent_actions_popup(self, [self.torrentid], details=False)
+ return
+ elif c == ord('o'):
+ torrent_actions_popup(self, [self.torrentid], action=ACTION.TORRENT_OPTIONS)
+ return
+ elif c == ord('h'):
+ self.push_popup(MessagePopup(self, 'Help', HELP_STR, width_req=0.75))
+ elif c == ord('j'):
+ self.file_list_up()
+ elif c == ord('k'):
+ self.file_list_down()
+
+ self.refresh()
diff --git a/deluge/ui/console/modes/torrentlist/__init__.py b/deluge/ui/console/modes/torrentlist/__init__.py
new file mode 100644
index 0000000..18c4db3
--- /dev/null
+++ b/deluge/ui/console/modes/torrentlist/__init__.py
@@ -0,0 +1,20 @@
+from __future__ import unicode_literals
+
+
+class ACTION(object):
+ PAUSE = 'pause'
+ RESUME = 'resume'
+ REANNOUNCE = 'update_tracker'
+ EDIT_TRACKERS = 3
+ RECHECK = 'force_recheck'
+ REMOVE = 'remove_torrent'
+ REMOVE_DATA = 6
+ REMOVE_NODATA = 7
+ DETAILS = 'torrent_details'
+ MOVE_STORAGE = 'move_download_folder'
+ QUEUE = 'queue'
+ QUEUE_TOP = 'queue_top'
+ QUEUE_UP = 'queue_up'
+ QUEUE_DOWN = 'queue_down'
+ QUEUE_BOTTOM = 'queue_bottom'
+ TORRENT_OPTIONS = 'torrent_options'
diff --git a/deluge/ui/console/modes/torrentlist/add_torrents_popup.py b/deluge/ui/console/modes/torrentlist/add_torrents_popup.py
new file mode 100644
index 0000000..b0ac483
--- /dev/null
+++ b/deluge/ui/console/modes/torrentlist/add_torrents_popup.py
@@ -0,0 +1,113 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+import deluge.common
+from deluge.ui.client import client
+from deluge.ui.console.widgets.popup import InputPopup, SelectablePopup
+
+log = logging.getLogger(__name__)
+
+
+def report_add_status(torrentlist, succ_cnt, fail_cnt, fail_msgs):
+ if fail_cnt == 0:
+ torrentlist.report_message(
+ 'Torrents Added', '{!success!}Successfully added %d torrent(s)' % succ_cnt
+ )
+ else:
+ msg = (
+ '{!error!}Failed to add the following %d torrent(s):\n {!input!}' % fail_cnt
+ ) + '\n '.join(fail_msgs)
+ if succ_cnt != 0:
+ msg += '\n \n{!success!}Successfully added %d torrent(s)' % succ_cnt
+ torrentlist.report_message('Torrent Add Report', msg)
+
+
+def show_torrent_add_popup(torrentlist):
+ def do_add_from_url(data=None, **kwargs):
+ torrentlist.pop_popup()
+ if not data or kwargs.get('close', False):
+ return
+
+ def fail_cb(msg, url):
+ log.debug('failed to add torrent: %s: %s', url, msg)
+ error_msg = '{!input!} * %s: {!error!}%s' % (url, msg)
+ report_add_status(torrentlist, 0, 1, [error_msg])
+
+ def success_cb(tid, url):
+ if tid:
+ log.debug('added torrent: %s (%s)', url, tid)
+ report_add_status(torrentlist, 1, 0, [])
+ else:
+ fail_cb('Already in session (probably)', url)
+
+ url = data['url']['value']
+ if not url:
+ return
+
+ t_options = {
+ 'download_location': data['path']['value'],
+ 'add_paused': data['add_paused']['value'],
+ }
+
+ if deluge.common.is_magnet(url):
+ client.core.add_torrent_magnet(url, t_options).addCallback(
+ success_cb, url
+ ).addErrback(fail_cb, url)
+ elif deluge.common.is_url(url):
+ client.core.add_torrent_url(url, t_options).addCallback(
+ success_cb, url
+ ).addErrback(fail_cb, url)
+ else:
+ torrentlist.report_message(
+ 'Error', '{!error!}Invalid URL or magnet link: %s' % url
+ )
+ return
+
+ log.debug(
+ 'Adding Torrent(s): %s (dl path: %s) (paused: %d)',
+ url,
+ data['path']['value'],
+ data['add_paused']['value'],
+ )
+
+ def show_add_url_popup():
+ add_paused = 1 if 'add_paused' in torrentlist.coreconfig else 0
+ popup = InputPopup(
+ torrentlist, 'Add Torrent (Esc to cancel)', close_cb=do_add_from_url
+ )
+ popup.add_text_input('url', 'Enter torrent URL or Magnet link:')
+ popup.add_text_input(
+ 'path',
+ 'Enter save path:',
+ torrentlist.coreconfig.get('download_location', ''),
+ complete=True,
+ )
+ popup.add_select_input(
+ 'add_paused', 'Add Paused:', ['Yes', 'No'], [True, False], add_paused
+ )
+ torrentlist.push_popup(popup)
+
+ def option_chosen(selected, *args, **kwargs):
+ if not selected or selected == 'cancel':
+ torrentlist.pop_popup()
+ return
+ if selected == 'file':
+ torrentlist.consoleui.set_mode('AddTorrents')
+ elif selected == 'url':
+ show_add_url_popup()
+
+ popup = SelectablePopup(torrentlist, 'Add torrent', option_chosen)
+ popup.add_line('file', '- From _File(s)', use_underline=True)
+ popup.add_line('url', '- From _URL or Magnet', use_underline=True)
+ popup.add_line('cancel', '- _Cancel', use_underline=True)
+ torrentlist.push_popup(popup, clear=True)
diff --git a/deluge/ui/console/modes/torrentlist/filtersidebar.py b/deluge/ui/console/modes/torrentlist/filtersidebar.py
new file mode 100644
index 0000000..0f39b5c
--- /dev/null
+++ b/deluge/ui/console/modes/torrentlist/filtersidebar.py
@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import curses
+import logging
+
+from deluge.component import Component
+from deluge.decorators import overrides
+from deluge.ui.client import client
+from deluge.ui.console.utils import curses_util as util
+from deluge.ui.console.widgets import BaseInputPane
+from deluge.ui.console.widgets.sidebar import Sidebar
+
+log = logging.getLogger(__name__)
+
+
+class FilterSidebar(Sidebar, Component):
+ """The sidebar in the main torrentview
+
+ Shows the different states of the torrents and allows to filter the
+ torrents based on state.
+
+ """
+
+ def __init__(self, torrentlist, config):
+ self.config = config
+ height = curses.LINES - 2
+ width = self.config['torrentview']['sidebar_width']
+ Sidebar.__init__(
+ self,
+ torrentlist,
+ width,
+ height,
+ title=' Filter ',
+ border_off_north=1,
+ allow_resize=True,
+ )
+ Component.__init__(self, 'FilterSidebar')
+ self.checked_index = 0
+ kwargs = {
+ 'checked_char': '*',
+ 'unchecked_char': '-',
+ 'checkbox_format': ' %s ',
+ 'col': 0,
+ }
+ self.add_checked_input('All', 'All', checked=True, **kwargs)
+ self.add_checked_input('Active', 'Active', **kwargs)
+ self.add_checked_input(
+ 'Downloading', 'Downloading', color='green,black', **kwargs
+ )
+ self.add_checked_input('Seeding', 'Seeding', color='cyan,black', **kwargs)
+ self.add_checked_input('Paused', 'Paused', **kwargs)
+ self.add_checked_input('Error', 'Error', color='red,black', **kwargs)
+ self.add_checked_input('Checking', 'Checking', color='blue,black', **kwargs)
+ self.add_checked_input('Queued', 'Queued', **kwargs)
+ self.add_checked_input(
+ 'Allocating', 'Allocating', color='yellow,black', **kwargs
+ )
+ self.add_checked_input('Moving', 'Moving', color='green,black', **kwargs)
+
+ @overrides(Component)
+ def update(self):
+ if not self.hidden() and client.connected():
+ d = client.core.get_filter_tree(True, []).addCallback(
+ self._cb_update_filter_tree
+ )
+
+ def on_filter_tree_updated(changed):
+ if changed:
+ self.refresh()
+
+ d.addCallback(on_filter_tree_updated)
+
+ def _cb_update_filter_tree(self, filter_items):
+ """Callback function on client.core.get_filter_tree"""
+ states = filter_items['state']
+ largest_count = 0
+ largest_state_width = 0
+ for state in states:
+ largest_state_width = max(len(state[0]), largest_state_width)
+ largest_count = max(int(state[1]), largest_count)
+
+ border_and_spacing = 6 # Account for border + whitespace
+ filter_state_width = largest_state_width
+ filter_count_width = self.width - filter_state_width - border_and_spacing
+
+ changed = False
+ for state in states:
+ field = self.get_input(state[0])
+ if field:
+ txt = (
+ '%%-%ds%%%ds'
+ % (filter_state_width, filter_count_width)
+ % (state[0], state[1])
+ )
+ if field.set_message(txt):
+ changed = True
+ return changed
+
+ @overrides(BaseInputPane)
+ def immediate_action_cb(self, state_changed=True):
+ if state_changed:
+ self.parent.torrentview.set_torrent_filter(
+ self.inputs[self.active_input].name
+ )
+
+ @overrides(Sidebar)
+ def handle_read(self, c):
+ if c == util.KEY_SPACE:
+ if self.checked_index != self.active_input:
+ self.inputs[self.checked_index].set_value(False)
+ Sidebar.handle_read(self, c)
+ self.checked_index = self.active_input
+ return util.ReadState.READ
+ else:
+ return Sidebar.handle_read(self, c)
+
+ @overrides(Sidebar)
+ def on_resize(self, width):
+ sidebar_width = self.config['torrentview']['sidebar_width']
+ if sidebar_width != width:
+ self.config['torrentview']['sidebar_width'] = width
+ self.config.save()
+ self.resize_window(self.height, width)
+ self.parent.toggle_sidebar()
+ self.refresh()
diff --git a/deluge/ui/console/modes/torrentlist/queue_mode.py b/deluge/ui/console/modes/torrentlist/queue_mode.py
new file mode 100644
index 0000000..0c44aaf
--- /dev/null
+++ b/deluge/ui/console/modes/torrentlist/queue_mode.py
@@ -0,0 +1,157 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+from deluge.ui.client import client
+from deluge.ui.console.utils import curses_util as util
+from deluge.ui.console.widgets.popup import MessagePopup, SelectablePopup
+
+from . import ACTION
+
+try:
+ import curses
+except ImportError:
+ pass
+
+key_to_action = {
+ curses.KEY_HOME: ACTION.QUEUE_TOP,
+ curses.KEY_UP: ACTION.QUEUE_UP,
+ curses.KEY_DOWN: ACTION.QUEUE_DOWN,
+ curses.KEY_END: ACTION.QUEUE_BOTTOM,
+}
+QUEUE_MODE_HELP_STR = """
+Change queue position of selected torrents
+
+{!info!}'+'{!normal!} - {|indent_pos:|}Move up
+{!info!}'-'{!normal!} - {|indent_pos:|}Move down
+
+{!info!}'Home'{!normal!} - {|indent_pos:|}Move to top
+{!info!}'End'{!normal!} - {|indent_pos:|}Move to bottom
+
+"""
+
+
+class QueueMode(object):
+ def __init__(self, torrentslist, torrent_ids):
+ self.torrentslist = torrentslist
+ self.torrentview = torrentslist.torrentview
+ self.torrent_ids = torrent_ids
+
+ def set_statusbar_args(self, statusbar_args):
+ statusbar_args[
+ 'bottombar'
+ ] = '{!black,white!}Queue mode: change queue position of selected torrents.'
+ statusbar_args['bottombar_help'] = ' Press [h] for help'
+
+ def update_cursor(self):
+ pass
+
+ def update_colors(self, tidx, colors):
+ pass
+
+ def handle_read(self, c):
+ if c in [util.KEY_ESC, util.KEY_BELL]: # If Escape key or CTRL-g, we abort
+ self.torrentslist.set_minor_mode(None)
+ elif c == ord('h'):
+ popup = MessagePopup(
+ self.torrentslist,
+ 'Help',
+ QUEUE_MODE_HELP_STR,
+ width_req=0.65,
+ border_off_west=1,
+ )
+ self.torrentslist.push_popup(popup, clear=True)
+ elif c in [
+ curses.KEY_UP,
+ curses.KEY_DOWN,
+ curses.KEY_HOME,
+ curses.KEY_END,
+ curses.KEY_NPAGE,
+ curses.KEY_PPAGE,
+ ]:
+ action = key_to_action[c]
+ self.do_queue(action)
+
+ def move_selection(self, cb_arg, qact):
+ if self.torrentslist.config['torrentview']['move_selection'] is False:
+ return
+ queue_length = 0
+ selected_num = 0
+ for tid in self.torrentview.curstate:
+ tq = self.torrentview.curstate[tid]['queue']
+ if tq != -1:
+ queue_length += 1
+ if tq in self.torrentview.marked:
+ selected_num += 1
+ if qact == ACTION.QUEUE_TOP:
+ if self.torrentview.marked:
+ self.torrentview.cursel = 1 + sorted(self.torrentview.marked).index(
+ self.torrentview.cursel
+ )
+ else:
+ self.torrentview.cursel = 1
+ self.torrentview.marked = list(range(1, selected_num + 1))
+ elif qact == ACTION.QUEUE_UP:
+ self.torrentview.cursel = max(1, self.torrentview.cursel - 1)
+ self.torrentview.marked = [marked - 1 for marked in self.torrentview.marked]
+ self.torrentview.marked = [
+ marked for marked in self.torrentview.marked if marked > 0
+ ]
+ elif qact == ACTION.QUEUE_DOWN:
+ self.torrentview.cursel = min(queue_length, self.torrentview.cursel + 1)
+ self.torrentview.marked = [marked + 1 for marked in self.torrentview.marked]
+ self.torrentview.marked = [
+ marked for marked in self.torrentview.marked if marked <= queue_length
+ ]
+ elif qact == ACTION.QUEUE_BOTTOM:
+ if self.torrentview.marked:
+ self.torrentview.cursel = (
+ queue_length
+ - selected_num
+ + 1
+ + sorted(self.torrentview.marked).index(self.torrentview.cursel)
+ )
+ else:
+ self.torrentview.cursel = queue_length
+ self.torrentview.marked = list(
+ range(queue_length - selected_num + 1, queue_length + 1)
+ )
+
+ def do_queue(self, qact, *args, **kwargs):
+ if qact == ACTION.QUEUE_TOP:
+ client.core.queue_top(self.torrent_ids).addCallback(
+ self.move_selection, qact
+ )
+ elif qact == ACTION.QUEUE_BOTTOM:
+ client.core.queue_bottom(self.torrent_ids).addCallback(
+ self.move_selection, qact
+ )
+ elif qact == ACTION.QUEUE_UP:
+ client.core.queue_up(self.torrent_ids).addCallback(
+ self.move_selection, qact
+ )
+ elif qact == ACTION.QUEUE_DOWN:
+ client.core.queue_down(self.torrent_ids).addCallback(
+ self.move_selection, qact
+ )
+
+ def popup(self, **kwargs):
+ popup = SelectablePopup(
+ self.torrentslist,
+ 'Queue Action',
+ self.do_queue,
+ cb_args=kwargs,
+ border_off_west=1,
+ )
+ popup.add_line(ACTION.QUEUE_TOP, '_Top')
+ popup.add_line(ACTION.QUEUE_UP, '_Up')
+ popup.add_line(ACTION.QUEUE_DOWN, '_Down')
+ popup.add_line(ACTION.QUEUE_BOTTOM, '_Bottom')
+ self.torrentslist.push_popup(popup)
diff --git a/deluge/ui/console/modes/torrentlist/search_mode.py b/deluge/ui/console/modes/torrentlist/search_mode.py
new file mode 100644
index 0000000..57a8e5f
--- /dev/null
+++ b/deluge/ui/console/modes/torrentlist/search_mode.py
@@ -0,0 +1,210 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+from deluge.common import PY2
+from deluge.decorators import overrides
+from deluge.ui.console.modes.basemode import InputKeyHandler, move_cursor
+from deluge.ui.console.modes.torrentlist.torrentactions import torrent_actions_popup
+from deluge.ui.console.utils import curses_util as util
+
+try:
+ import curses
+except ImportError:
+ pass
+
+log = logging.getLogger(__name__)
+QUEUE_MODE_HELP_STR = """
+Change queue position of selected torrents
+
+{!info!}'+'{!normal!} - {|indent_pos:|}Move up
+{!info!}'-'{!normal!} - {|indent_pos:|}Move down
+
+{!info!}'Home'{!normal!} - {|indent_pos:|}Move to top
+{!info!}'End'{!normal!} - {|indent_pos:|}Move to bottom
+
+"""
+SEARCH_EMPTY = 0
+SEARCH_FAILING = 1
+SEARCH_SUCCESS = 2
+SEARCH_START_REACHED = 3
+SEARCH_END_REACHED = 4
+SEARCH_FORMAT = {
+ SEARCH_EMPTY: '{!black,white!}Search torrents: %s{!black,white!}',
+ SEARCH_SUCCESS: '{!black,white!}Search torrents: {!black,green!}%s{!black,white!}',
+ SEARCH_FAILING: '{!black,white!}Search torrents: {!black,red!}%s{!black,white!}',
+ SEARCH_START_REACHED: '{!black,white!}Search torrents: {!black,yellow!}%s{!black,white!} (start reached)',
+ SEARCH_END_REACHED: '{!black,white!}Search torrents: {!black,yellow!}%s{!black,white!} (end reached)',
+}
+
+
+class SearchMode(InputKeyHandler):
+ def __init__(self, torrentlist):
+ super(SearchMode, self).__init__()
+ self.torrentlist = torrentlist
+ self.torrentview = torrentlist.torrentview
+ self.search_state = SEARCH_EMPTY
+ self.search_string = ''
+
+ def update_cursor(self):
+ util.safe_curs_set(util.Curser.VERY_VISIBLE)
+ move_cursor(
+ self.torrentlist.stdscr,
+ self.torrentlist.rows - 1,
+ len(self.search_string) + 17,
+ )
+
+ def set_statusbar_args(self, statusbar_args):
+ statusbar_args['bottombar'] = (
+ SEARCH_FORMAT[self.search_state] % self.search_string
+ )
+ statusbar_args['bottombar_help'] = False
+
+ def update_colors(self, tidx, colors):
+ if len(self.search_string) > 1:
+ lcase_name = self.torrentview.torrent_names[tidx].lower()
+ sstring_lower = self.search_string.lower()
+ if lcase_name.find(sstring_lower) != -1:
+ if tidx == self.torrentview.cursel:
+ pass
+ elif tidx in self.torrentview.marked:
+ colors['bg'] = 'magenta'
+ else:
+ colors['bg'] = 'green'
+ if colors['fg'] == 'green':
+ colors['fg'] = 'black'
+ colors['attr'] = 'bold'
+
+ def do_search(self, direction='first'):
+ """
+ Performs a search on visible torrent and sets cursor to the match
+
+ Args:
+ direction (str): The direction to search. Must be one of 'first', 'last', 'next' or 'previous'
+
+ """
+ search_space = list(enumerate(self.torrentview.torrent_names))
+
+ if direction == 'last':
+ search_space = reversed(search_space)
+ elif direction == 'next':
+ search_space = search_space[self.torrentview.cursel + 1 :]
+ elif direction == 'previous':
+ search_space = reversed(search_space[: self.torrentview.cursel])
+
+ search_string = self.search_string.lower()
+ for i, n in search_space:
+ n = n.lower()
+ if n.find(search_string) != -1:
+ self.torrentview.cursel = i
+ if (
+ self.torrentview.curoff
+ + self.torrentview.torrent_rows
+ - self.torrentview.torrentlist_offset
+ ) < self.torrentview.cursel:
+ self.torrentview.curoff = (
+ self.torrentview.cursel - self.torrentview.torrent_rows + 1
+ )
+ elif (self.torrentview.curoff + 1) > self.torrentview.cursel:
+ self.torrentview.curoff = max(0, self.torrentview.cursel)
+ self.search_state = SEARCH_SUCCESS
+ return
+ if direction in ['first', 'last']:
+ self.search_state = SEARCH_FAILING
+ elif direction == 'next':
+ self.search_state = SEARCH_END_REACHED
+ elif direction == 'previous':
+ self.search_state = SEARCH_START_REACHED
+
+ @overrides(InputKeyHandler)
+ def handle_read(self, c):
+ cname = self.torrentview.torrent_names[self.torrentview.cursel]
+ refresh = True
+
+ if c in [
+ util.KEY_ESC,
+ util.KEY_BELL,
+ ]: # If Escape key or CTRL-g, we abort search
+ self.torrentlist.set_minor_mode(None)
+ self.search_state = SEARCH_EMPTY
+ elif c in [curses.KEY_BACKSPACE, util.KEY_BACKSPACE2]:
+ if self.search_string:
+ self.search_string = self.search_string[:-1]
+ if cname.lower().find(self.search_string.lower()) != -1:
+ self.search_state = SEARCH_SUCCESS
+ else:
+ self.torrentlist.set_minor_mode(None)
+ self.search_state = SEARCH_EMPTY
+ elif c == curses.KEY_DC:
+ self.search_string = ''
+ self.search_state = SEARCH_SUCCESS
+ elif c == curses.KEY_UP:
+ self.do_search('previous')
+ elif c == curses.KEY_DOWN:
+ self.do_search('next')
+ elif c == curses.KEY_LEFT:
+ self.torrentlist.set_minor_mode(None)
+ self.search_state = SEARCH_EMPTY
+ elif c == ord('/'):
+ self.torrentlist.set_minor_mode(None)
+ self.search_state = SEARCH_EMPTY
+ elif c == curses.KEY_RIGHT:
+ tid = self.torrentview.current_torrent_id()
+ self.torrentlist.show_torrent_details(tid)
+ refresh = False
+ elif c == curses.KEY_HOME:
+ self.do_search('first')
+ elif c == curses.KEY_END:
+ self.do_search('last')
+ elif c in [10, curses.KEY_ENTER]:
+ self.last_mark = -1
+ tid = self.torrentview.current_torrent_id()
+ torrent_actions_popup(self.torrentlist, [tid], details=True)
+ refresh = False
+ elif c == util.KEY_ESC:
+ self.search_string = ''
+ self.search_state = SEARCH_EMPTY
+ elif c > 31 and c < 256:
+ old_search_string = self.search_string
+ stroke = chr(c)
+ uchar = '' if PY2 else stroke
+ while not uchar:
+ try:
+ uchar = stroke.decode(self.torrentlist.encoding)
+ except UnicodeDecodeError:
+ c = self.torrentlist.stdscr.getch()
+ stroke += chr(c)
+
+ if uchar:
+ self.search_string += uchar
+
+ still_matching = (
+ cname.lower().find(self.search_string.lower())
+ == cname.lower().find(old_search_string.lower())
+ and cname.lower().find(self.search_string.lower()) != -1
+ )
+
+ if self.search_string and not still_matching:
+ self.do_search()
+ elif self.search_string:
+ self.search_state = SEARCH_SUCCESS
+ else:
+ refresh = False
+
+ if not self.search_string:
+ self.search_state = SEARCH_EMPTY
+ refresh = True
+
+ if refresh:
+ self.torrentlist.refresh([])
+
+ return util.ReadState.READ
diff --git a/deluge/ui/console/modes/torrentlist/torrentactions.py b/deluge/ui/console/modes/torrentlist/torrentactions.py
new file mode 100644
index 0000000..f3cd395
--- /dev/null
+++ b/deluge/ui/console/modes/torrentlist/torrentactions.py
@@ -0,0 +1,276 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os
+
+from twisted.internet import defer
+
+import deluge.component as component
+from deluge.ui.client import client
+from deluge.ui.common import TORRENT_DATA_FIELD
+from deluge.ui.console.modes.torrentlist.queue_mode import QueueMode
+from deluge.ui.console.utils import colors
+from deluge.ui.console.utils.common import TORRENT_OPTIONS
+from deluge.ui.console.widgets.popup import InputPopup, MessagePopup, SelectablePopup
+
+from . import ACTION
+
+log = logging.getLogger(__name__)
+
+
+def action_error(error, mode):
+ mode.report_message('Error Occurred', error.getErrorMessage())
+ mode.refresh()
+
+
+def action_remove(mode=None, torrent_ids=None, **kwargs):
+ def do_remove(*args, **kwargs):
+ data = args[0] if args else None
+ if data is None or kwargs.get('close', False):
+ mode.pop_popup()
+ return True
+
+ mode.torrentview.clear_marked()
+ remove_data = data['remove_files']['value']
+
+ def on_removed_finished(errors):
+ if errors:
+ error_msgs = ''
+ for t_id, e_msg in errors:
+ error_msgs += 'Error removing torrent %s : %s\n' % (t_id, e_msg)
+ mode.report_message(
+ 'Error(s) occured when trying to delete torrent(s).', error_msgs
+ )
+ mode.refresh()
+
+ d = client.core.remove_torrents(torrent_ids, remove_data)
+ d.addCallback(on_removed_finished)
+ mode.pop_popup()
+
+ def got_status(status):
+ return (status['name'], status['state'])
+
+ callbacks = []
+ for tid in torrent_ids:
+ d = client.core.get_torrent_status(tid, ['name', 'state'])
+ callbacks.append(d.addCallback(got_status))
+
+ def remove_dialog(status):
+ status = [t_status[1] for t_status in status]
+
+ if len(torrent_ids) == 1:
+ rem_msg = '{!info!}Remove the following torrent?{!input!}'
+ else:
+ rem_msg = '{!info!}Remove the following %d torrents?{!input!}' % len(
+ torrent_ids
+ )
+
+ show_max = 6
+ for i, (name, state) in enumerate(status):
+ color = colors.state_color[state]
+ rem_msg += '\n %s* {!input!}%s' % (color, name)
+ if i == show_max - 1:
+ if i < len(status) - 1:
+ rem_msg += '\n {!red!}And %i more' % (len(status) - show_max)
+ break
+
+ popup = InputPopup(
+ mode,
+ '(Esc to cancel, Enter to remove)',
+ close_cb=do_remove,
+ border_off_west=1,
+ border_off_north=1,
+ )
+ popup.add_text(rem_msg)
+ popup.add_spaces(1)
+ popup.add_select_input(
+ 'remove_files',
+ '{!info!}Torrent files:',
+ ['Keep', 'Remove'],
+ [False, True],
+ False,
+ )
+ mode.push_popup(popup)
+
+ defer.DeferredList(callbacks).addCallback(remove_dialog)
+
+
+def action_torrent_info(mode=None, torrent_ids=None, **kwargs):
+ popup = MessagePopup(mode, 'Torrent options', 'Querying core, please wait...')
+ mode.push_popup(popup)
+ torrents = torrent_ids
+ options = {}
+
+ def _do_set_torrent_options(torrent_ids, result):
+ options = {}
+ for opt, val in result.items():
+ if val['value'] not in ['multiple', None]:
+ options[opt] = val['value']
+ client.core.set_torrent_options(torrent_ids, options)
+
+ def on_torrent_status(status):
+ for key in status:
+ if key not in options:
+ options[key] = status[key]
+ elif options[key] != status[key]:
+ options[key] = 'multiple'
+
+ def create_popup(status):
+ mode.pop_popup()
+
+ def cb(result, **kwargs):
+ if result is None:
+ return
+ _do_set_torrent_options(torrent_ids, result)
+ if kwargs.get('close', False):
+ mode.pop_popup()
+ return True
+
+ option_popup = InputPopup(
+ mode,
+ ' Set Torrent Options ',
+ close_cb=cb,
+ border_off_west=1,
+ border_off_north=1,
+ base_popup=kwargs.get('base_popup', None),
+ )
+ for field in TORRENT_OPTIONS:
+ caption = '{!info!}' + TORRENT_DATA_FIELD[field]['name']
+ value = options[field]
+ if isinstance(value, ''.__class__):
+ option_popup.add_text_input(field, caption, value)
+ elif isinstance(value, bool):
+ choices = (['Yes', 'No'], [True, False], [True, False].index(value))
+ option_popup.add_select_input(
+ field, caption, choices[0], choices[1], choices[2]
+ )
+ elif isinstance(value, float):
+ option_popup.add_float_spin_input(
+ field, caption, value=value, min_val=-1
+ )
+ elif isinstance(value, int):
+ option_popup.add_int_spin_input(field, caption, value=value, min_val=-1)
+
+ mode.push_popup(option_popup)
+
+ callbacks = []
+ for tid in torrents:
+ deferred = component.get('SessionProxy').get_torrent_status(
+ tid, list(TORRENT_OPTIONS)
+ )
+ callbacks.append(deferred.addCallback(on_torrent_status))
+
+ callbacks = defer.DeferredList(callbacks)
+ callbacks.addCallback(create_popup)
+
+
+def torrent_action(action, *args, **kwargs):
+ retval = False
+ torrent_ids = kwargs.get('torrent_ids', None)
+ mode = kwargs.get('mode', None)
+
+ if torrent_ids is None:
+ return
+
+ if action == ACTION.PAUSE:
+ log.debug('Pausing torrents: %s', torrent_ids)
+ client.core.pause_torrents(torrent_ids).addErrback(action_error, mode)
+ retval = True
+ elif action == ACTION.RESUME:
+ log.debug('Resuming torrents: %s', torrent_ids)
+ client.core.resume_torrents(torrent_ids).addErrback(action_error, mode)
+ retval = True
+ elif action == ACTION.QUEUE:
+ queue_mode = QueueMode(mode, torrent_ids)
+ queue_mode.popup(**kwargs)
+ elif action == ACTION.REMOVE:
+ action_remove(**kwargs)
+ retval = True
+ elif action == ACTION.MOVE_STORAGE:
+
+ def do_move(res, **kwargs):
+ if res is None or kwargs.get('close', False):
+ mode.pop_popup()
+ return True
+
+ if os.path.exists(res['path']['value']) and not os.path.isdir(
+ res['path']['value']
+ ):
+ mode.report_message(
+ 'Cannot Move Download Folder',
+ '{!error!}%s exists and is not a directory' % res['path']['value'],
+ )
+ else:
+ log.debug('Moving %s to: %s', torrent_ids, res['path']['value'])
+ client.core.move_storage(torrent_ids, res['path']['value']).addErrback(
+ action_error, mode
+ )
+
+ popup = InputPopup(
+ mode, 'Move Download Folder', close_cb=do_move, border_off_east=1
+ )
+ popup.add_text_input('path', 'Enter path to move to:', complete=True)
+ mode.push_popup(popup)
+ elif action == ACTION.RECHECK:
+ log.debug('Rechecking torrents: %s', torrent_ids)
+ client.core.force_recheck(torrent_ids).addErrback(action_error, mode)
+ retval = True
+ elif action == ACTION.REANNOUNCE:
+ log.debug('Reannouncing torrents: %s', torrent_ids)
+ client.core.force_reannounce(torrent_ids).addErrback(action_error, mode)
+ retval = True
+ elif action == ACTION.DETAILS:
+ log.debug('Torrent details')
+ tid = mode.torrentview.current_torrent_id()
+ if tid:
+ mode.show_torrent_details(tid)
+ else:
+ log.error('No current torrent in _torrentaction, this is a bug')
+ elif action == ACTION.TORRENT_OPTIONS:
+ action_torrent_info(**kwargs)
+
+ return retval
+
+
+# Creates the popup. mode is the calling mode, tids is a list of torrents to take action upon
+def torrent_actions_popup(mode, torrent_ids, details=False, action=None, close_cb=None):
+
+ if action is not None:
+ torrent_action(action, mode=mode, torrent_ids=torrent_ids)
+ return
+
+ popup = SelectablePopup(
+ mode,
+ 'Torrent Actions',
+ torrent_action,
+ cb_args={'mode': mode, 'torrent_ids': torrent_ids},
+ close_cb=close_cb,
+ border_off_north=1,
+ border_off_west=1,
+ border_off_east=1,
+ )
+ popup.add_line(ACTION.PAUSE, '_Pause')
+ popup.add_line(ACTION.RESUME, '_Resume')
+ if details:
+ popup.add_divider()
+ popup.add_line(ACTION.QUEUE, 'Queue')
+ popup.add_divider()
+ popup.add_line(ACTION.REANNOUNCE, '_Update Tracker')
+ popup.add_divider()
+ popup.add_line(ACTION.REMOVE, 'Remo_ve Torrent')
+ popup.add_line(ACTION.RECHECK, '_Force Recheck')
+ popup.add_line(ACTION.MOVE_STORAGE, '_Move Download Folder')
+ popup.add_divider()
+ if details:
+ popup.add_line(ACTION.DETAILS, 'Torrent _Details')
+ popup.add_line(ACTION.TORRENT_OPTIONS, 'Torrent _Options')
+ mode.push_popup(popup)
diff --git a/deluge/ui/console/modes/torrentlist/torrentlist.py b/deluge/ui/console/modes/torrentlist/torrentlist.py
new file mode 100644
index 0000000..a427d65
--- /dev/null
+++ b/deluge/ui/console/modes/torrentlist/torrentlist.py
@@ -0,0 +1,348 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+from collections import deque
+
+import deluge.component as component
+from deluge.component import Component
+from deluge.decorators import overrides
+from deluge.ui.client import client
+from deluge.ui.console.modes.basemode import BaseMode, mkwin
+from deluge.ui.console.modes.torrentlist import torrentview, torrentviewcolumns
+from deluge.ui.console.modes.torrentlist.add_torrents_popup import (
+ show_torrent_add_popup,
+)
+from deluge.ui.console.modes.torrentlist.filtersidebar import FilterSidebar
+from deluge.ui.console.modes.torrentlist.queue_mode import QueueMode
+from deluge.ui.console.modes.torrentlist.search_mode import SearchMode
+from deluge.ui.console.utils import curses_util as util
+from deluge.ui.console.widgets.popup import MessagePopup, PopupsHandler
+
+try:
+ import curses
+except ImportError:
+ pass
+
+log = logging.getLogger(__name__)
+
+
+# Big help string that gets displayed when the user hits 'h'
+HELP_STR = """
+This screen shows an overview of the current torrents Deluge is managing. \
+The currently selected torrent is indicated with a white background. \
+You can change the selected torrent using the up/down arrows or the \
+PgUp/PgDown keys. Home and End keys go to the first and last torrent \
+respectively.
+
+Operations can be performed on multiple torrents by marking them and \
+then hitting Enter. See below for the keys used to mark torrents.
+
+You can scroll a popup window that doesn't fit its content (like \
+this one) using the up/down arrows, PgUp/PgDown and Home/End keys.
+
+All popup windows can be closed/canceled by hitting the Esc key \
+or the 'q' key (does not work for dialogs like the add torrent dialog)
+
+The actions you can perform and the keys to perform them are as follows:
+
+{!info!}'h'{!normal!} - {|indent_pos:|}Show this help
+{!info!}'p'{!normal!} - {|indent_pos:|}Open preferences
+{!info!}'l'{!normal!} - {|indent_pos:|}Enter Command Line mode
+{!info!}'e'{!normal!} - {|indent_pos:|}Show the event log view ({!info!}'q'{!normal!} to go back to overview)
+
+{!info!}'a'{!normal!} - {|indent_pos:|}Add a torrent
+{!info!}Delete{!normal!} - {|indent_pos:|}Delete a torrent
+
+{!info!}'/'{!normal!} - {|indent_pos:|}Search torrent names. \
+Searching starts immediately - matching torrents are highlighted in \
+green, you can cycle through them with Up/Down arrows and Home/End keys \
+You can view torrent details with right arrow, open action popup with \
+Enter key and exit search mode with '/' key, left arrow or \
+backspace with empty search field
+
+{!info!}'f'{!normal!} - {|indent_pos:|}Show only torrents in a certain state
+ (Will open a popup where you can select the state you want to see)
+{!info!}'q'{!normal!} - {|indent_pos:|}Enter queue mode
+
+{!info!}'S'{!normal!} - {|indent_pos:|}Show or hide the sidebar
+
+{!info!}Enter{!normal!} - {|indent_pos:|}Show torrent actions popup. Here you can do things like \
+pause/resume, remove, recheck and so on. These actions \
+apply to all currently marked torrents. The currently \
+selected torrent is automatically marked when you press enter.
+
+{!info!}'o'{!normal!} - {|indent_pos:|}Show and set torrent options - this will either apply \
+to all selected torrents(but not the highlighted one) or currently \
+selected torrent if nothing is selected
+
+{!info!}'Q'{!normal!} - {|indent_pos:|}quit deluge-console
+{!info!}'C'{!normal!} - {|indent_pos:|}show connection manager
+
+{!info!}'m'{!normal!} - {|indent_pos:|}Mark a torrent
+{!info!}'M'{!normal!} - {|indent_pos:|}Mark all torrents between currently selected torrent and last marked torrent
+{!info!}'c'{!normal!} - {|indent_pos:|}Clear selection
+
+{!info!}'v'{!normal!} - {|indent_pos:|}Show a dialog which allows you to choose columns to display
+{!info!}'<' / '>'{!normal!} - {|indent_pos:|}Change column by which to sort torrents
+
+{!info!}Right Arrow{!normal!} - {|indent_pos:|}Torrent Detail Mode. This includes more detailed information \
+about the currently selected torrent, as well as a view of the \
+files in the torrent and the ability to set file priorities.
+
+{!info!}'q'/Esc{!normal!} - {|indent_pos:|}Close a popup (Note that 'q' does not work for dialogs \
+where you input something
+"""
+
+
+class TorrentList(BaseMode, PopupsHandler):
+ def __init__(self, stdscr, encoding=None):
+ BaseMode.__init__(
+ self, stdscr, encoding=encoding, do_refresh=False, depend=['SessionProxy']
+ )
+ PopupsHandler.__init__(self)
+ self.messages = deque()
+ self.last_mark = -1
+ self.go_top = False
+ self.minor_mode = None
+
+ self.consoleui = component.get('ConsoleUI')
+ self.coreconfig = self.consoleui.coreconfig
+ self.config = self.consoleui.config
+ self.sidebar = FilterSidebar(self, self.config)
+ self.torrentview_panel = mkwin(
+ curses.COLOR_GREEN,
+ curses.LINES - 1,
+ curses.COLS - self.sidebar.width,
+ 0,
+ self.sidebar.width,
+ )
+ self.torrentview = torrentview.TorrentView(self, self.config)
+
+ util.safe_curs_set(util.Curser.INVISIBLE)
+ self.stdscr.notimeout(0)
+
+ def torrentview_columns(self):
+ return self.torrentview_panel.getmaxyx()[1]
+
+ def on_config_changed(self):
+ self.config.save()
+ self.torrentview.on_config_changed()
+
+ def toggle_sidebar(self):
+ if self.config['torrentview']['show_sidebar']:
+ self.sidebar.show()
+ self.sidebar.resize_window(curses.LINES - 2, self.sidebar.width)
+ self.torrentview_panel.resize(
+ curses.LINES - 1, curses.COLS - self.sidebar.width
+ )
+ self.torrentview_panel.mvwin(0, self.sidebar.width)
+ else:
+ self.sidebar.hide()
+ self.torrentview_panel.resize(curses.LINES - 1, curses.COLS)
+ self.torrentview_panel.mvwin(0, 0)
+ self.torrentview.update_columns()
+ # After updating the columns widths, clear row cache to recreate them
+ self.torrentview.cached_rows.clear()
+ self.refresh()
+
+ @overrides(Component)
+ def start(self):
+ self.torrentview.on_config_changed()
+ self.toggle_sidebar()
+
+ if self.config['first_run']:
+ self.push_popup(
+ MessagePopup(self, 'Welcome to Deluge', HELP_STR, width_req=0.65)
+ )
+ self.config['first_run'] = False
+ self.config.save()
+
+ if client.connected():
+ self.torrentview.update(refresh=False)
+
+ @overrides(Component)
+ def update(self):
+ if self.mode_paused():
+ return
+
+ if client.connected():
+ self.torrentview.update(refresh=True)
+
+ @overrides(BaseMode)
+ def resume(self):
+ super(TorrentList, self).resume()
+
+ @overrides(BaseMode)
+ def on_resize(self, rows, cols):
+ BaseMode.on_resize(self, rows, cols)
+
+ if self.popup:
+ self.popup.handle_resize()
+
+ if not self.consoleui.is_active_mode(self):
+ return
+
+ self.toggle_sidebar()
+
+ def show_torrent_details(self, tid):
+ mode = self.consoleui.set_mode('TorrentDetail')
+ mode.update(tid)
+
+ def set_minor_mode(self, mode):
+ self.minor_mode = mode
+ self.refresh()
+
+ def _show_visible_columns_popup(self):
+ self.push_popup(torrentviewcolumns.TorrentViewColumns(self))
+
+ @overrides(BaseMode)
+ def refresh(self, lines=None):
+ # Something has requested we scroll to the top of the list
+ if self.go_top:
+ self.torrentview.cursel = 0
+ self.torrentview.curoff = 0
+ self.go_top = False
+
+ if not lines:
+ if not self.consoleui.is_active_mode(self):
+ return
+ self.stdscr.erase()
+
+ self.add_string(1, self.torrentview.column_string, scr=self.torrentview_panel)
+
+ # Update the status bars
+ statusbar_args = {'scr': self.stdscr, 'bottombar_help': True}
+ if self.torrentview.curr_filter is not None:
+ statusbar_args['topbar'] = '%s {!filterstatus!}Current filter: %s' % (
+ self.statusbars.topbar,
+ self.torrentview.curr_filter,
+ )
+
+ if self.minor_mode:
+ self.minor_mode.set_statusbar_args(statusbar_args)
+
+ self.draw_statusbars(**statusbar_args)
+
+ self.torrentview.update_torrents(lines)
+
+ if self.minor_mode:
+ self.minor_mode.update_cursor()
+ else:
+ util.safe_curs_set(util.Curser.INVISIBLE)
+
+ if not self.consoleui.is_active_mode(self):
+ return
+
+ self.stdscr.noutrefresh()
+ self.torrentview_panel.noutrefresh()
+
+ if not self.sidebar.hidden():
+ self.sidebar.refresh()
+
+ if self.popup:
+ self.popup.refresh()
+
+ curses.doupdate()
+
+ @overrides(BaseMode)
+ def read_input(self):
+ # Read the character
+ affected_lines = None
+ c = self.stdscr.getch()
+
+ # Either ESC or ALT+<some key>
+ if c == util.KEY_ESC:
+ n = self.stdscr.getch()
+ if n == -1: # Means it was the escape key
+ pass
+ else: # ALT+<some key>
+ c = [c, n]
+
+ if self.popup:
+ ret = self.popup.handle_read(c)
+ if self.popup and self.popup.closed():
+ self.pop_popup()
+ self.refresh()
+ return ret
+ if util.is_printable_chr(c):
+ if chr(c) == 'Q':
+ component.get('ConsoleUI').quit()
+ elif chr(c) == 'C':
+ self.consoleui.set_mode('ConnectionManager')
+ return
+ elif chr(c) == 'q':
+ self.torrentview.update_marked(self.torrentview.cursel)
+ self.set_minor_mode(
+ QueueMode(self, self.torrentview._selected_torrent_ids())
+ )
+ return
+ elif chr(c) == '/':
+ self.set_minor_mode(SearchMode(self))
+ return
+
+ if self.sidebar.has_focus() and c not in [curses.KEY_RIGHT]:
+ self.sidebar.handle_read(c)
+ self.refresh()
+ return
+
+ if self.torrentview.numtorrents < 0:
+ return
+ elif self.minor_mode:
+ self.minor_mode.handle_read(c)
+ return
+
+ affected_lines = None
+ # Hand off to torrentview
+ if self.torrentview.handle_read(c) == util.ReadState.CHANGED:
+ affected_lines = self.torrentview.get_input_result()
+
+ if c == curses.KEY_LEFT:
+ if not self.sidebar.has_focus():
+ self.sidebar.set_focused(True)
+ self.refresh()
+ return
+ elif c == curses.KEY_RIGHT:
+ if self.sidebar.has_focus():
+ self.sidebar.set_focused(False)
+ self.refresh()
+ return
+ # We enter a new mode for the selected torrent here
+ tid = self.torrentview.current_torrent_id()
+ if tid:
+ self.show_torrent_details(tid)
+ return
+
+ elif util.is_printable_chr(c):
+ if chr(c) == 'a':
+ show_torrent_add_popup(self)
+ elif chr(c) == 'v':
+ self._show_visible_columns_popup()
+ elif chr(c) == 'h':
+ self.push_popup(MessagePopup(self, 'Help', HELP_STR, width_req=0.65))
+ elif chr(c) == 'p':
+ mode = self.consoleui.set_mode('Preferences')
+ mode.load_config()
+ return
+ elif chr(c) == 'e':
+ self.consoleui.set_mode('EventView')
+ return
+ elif chr(c) == 'S':
+ self.config['torrentview']['show_sidebar'] = (
+ self.config['torrentview']['show_sidebar'] is False
+ )
+ self.config.save()
+ self.toggle_sidebar()
+ elif chr(c) == 'l':
+ self.consoleui.set_mode('CmdLine', refresh=True)
+ return
+
+ self.refresh(affected_lines)
diff --git a/deluge/ui/console/modes/torrentlist/torrentview.py b/deluge/ui/console/modes/torrentlist/torrentview.py
new file mode 100644
index 0000000..67de3e7
--- /dev/null
+++ b/deluge/ui/console/modes/torrentlist/torrentview.py
@@ -0,0 +1,517 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+import deluge.component as component
+from deluge.decorators import overrides
+from deluge.ui.console.modes.basemode import InputKeyHandler
+from deluge.ui.console.modes.torrentlist import torrentviewcolumns
+from deluge.ui.console.modes.torrentlist.torrentactions import torrent_actions_popup
+from deluge.ui.console.utils import curses_util as util
+from deluge.ui.console.utils import format_utils
+from deluge.ui.console.utils.column import (
+ get_column_value,
+ get_required_fields,
+ torrent_data_fields,
+)
+
+from . import ACTION
+
+try:
+ import curses
+except ImportError:
+ pass
+
+log = logging.getLogger(__name__)
+
+
+state_fg_colors = {
+ 'Downloading': 'green',
+ 'Seeding': 'cyan',
+ 'Error': 'red',
+ 'Queued': 'yellow',
+ 'Checking': 'blue',
+ 'Moving': 'green',
+}
+
+
+reverse_sort_fields = [
+ 'size',
+ 'download_speed',
+ 'upload_speed',
+ 'num_seeds',
+ 'num_peers',
+ 'distributed_copies',
+ 'time_added',
+ 'total_uploaded',
+ 'all_time_download',
+ 'total_remaining',
+ 'progress',
+ 'ratio',
+ 'seeding_time',
+ 'active_time',
+]
+
+
+default_column_values = {
+ 'queue': {'width': 4, 'visible': True},
+ 'name': {'width': -1, 'visible': True},
+ 'size': {'width': 8, 'visible': True},
+ 'progress': {'width': 7, 'visible': True},
+ 'download_speed': {'width': 7, 'visible': True},
+ 'upload_speed': {'width': 7, 'visible': True},
+ 'state': {'width': 13},
+ 'eta': {'width': 8, 'visible': True},
+ 'time_added': {'width': 15},
+ 'tracker': {'width': 15},
+ 'download_location': {'width': 15},
+ 'downloaded': {'width': 13},
+ 'uploaded': {'width': 7},
+ 'remaining': {'width': 13},
+ 'completed_time': {'width': 15},
+ 'last_seen_complete': {'width': 15},
+ 'max_upload_speed': {'width': 7},
+}
+
+
+default_columns = {}
+for col_i, col_name in enumerate(torrentviewcolumns.column_pref_names):
+ default_columns[col_name] = {'width': 10, 'order': col_i, 'visible': False}
+ if col_name in default_column_values:
+ default_columns[col_name].update(default_column_values[col_name])
+
+
+class TorrentView(InputKeyHandler):
+ def __init__(self, torrentlist, config):
+ super(TorrentView, self).__init__()
+ self.torrentlist = torrentlist
+ self.config = config
+ self.filter_dict = {}
+ self.curr_filter = None
+ self.cached_rows = {}
+ self.sorted_ids = None
+ self.torrent_names = None
+ self.numtorrents = -1
+ self.column_string = ''
+ self.curoff = 0
+ self.marked = []
+ self.cursel = 0
+
+ @property
+ def rows(self):
+ return self.torrentlist.rows
+
+ @property
+ def torrent_rows(self):
+ return self.torrentlist.rows - 3 # Account for header lines + columns line
+
+ @property
+ def torrentlist_offset(self):
+ return 2
+
+ def update_state(self, state, refresh=False):
+ self.curstate = state # cache in case we change sort order
+ self.cached_rows.clear()
+ self.numtorrents = len(state)
+ self.sorted_ids = self._sort_torrents(state)
+ self.torrent_names = []
+ for torrent_id in self.sorted_ids:
+ ts = self.curstate[torrent_id]
+ self.torrent_names.append(ts['name'])
+
+ if refresh:
+ self.torrentlist.refresh()
+
+ def set_torrent_filter(self, state):
+ self.curr_filter = state
+ filter_dict = {'state': [state]}
+ if state == 'All':
+ self.curr_filter = None
+ filter_dict = {}
+ self.filter_dict = filter_dict
+ self.torrentlist.go_top = True
+ self.torrentlist.update()
+ return True
+
+ def _scroll_up(self, by):
+ cursel = self.cursel
+ prevoff = self.curoff
+ self.cursel = max(self.cursel - by, 0)
+ if self.cursel < self.curoff:
+ self.curoff = self.cursel
+ affected = []
+ if prevoff == self.curoff:
+ affected.append(cursel)
+ if cursel != self.cursel:
+ affected.insert(0, self.cursel)
+ return affected
+
+ def _scroll_down(self, by):
+ cursel = self.cursel
+ prevoff = self.curoff
+ self.cursel = min(self.cursel + by, self.numtorrents - 1)
+ if (self.curoff + self.torrent_rows) <= self.cursel:
+ self.curoff = self.cursel - self.torrent_rows + 1
+ affected = []
+ if prevoff == self.curoff:
+ affected.append(cursel)
+ if cursel != self.cursel:
+ affected.append(self.cursel)
+ return affected
+
+ def current_torrent_id(self):
+ if not self.sorted_ids:
+ return None
+ return self.sorted_ids[self.cursel]
+
+ def _selected_torrent_ids(self):
+ if not self.sorted_ids:
+ return None
+ ret = []
+ for i in self.marked:
+ ret.append(self.sorted_ids[i])
+ return ret
+
+ def clear_marked(self):
+ self.marked = []
+ self.last_mark = -1
+
+ def mark_unmark(self, idx):
+ if idx in self.marked:
+ self.marked.remove(idx)
+ self.last_mark = -1
+ else:
+ self.marked.append(idx)
+ self.last_mark = idx
+
+ def add_marked(self, indices, last_marked):
+ for i in indices:
+ if i not in self.marked:
+ self.marked.append(i)
+ self.last_mark = last_marked
+
+ def update_marked(self, index, last_mark=True, clear=False):
+ if index not in self.marked:
+ if clear:
+ self.marked = []
+ self.marked.append(index)
+ if last_mark:
+ self.last_mark = index
+ return True
+ return False
+
+ def _sort_torrents(self, state):
+ """Sorts by primary and secondary sort fields."""
+
+ if not state:
+ return {}
+
+ s_primary = self.config['torrentview']['sort_primary']
+ s_secondary = self.config['torrentview']['sort_secondary']
+
+ result = state
+
+ # Sort first by secondary sort field and then primary sort field
+ # so it all works out
+
+ def sort_by_field(state, to_sort, field):
+ field = torrent_data_fields[field]['status'][0]
+ reverse = field in reverse_sort_fields
+
+ # Get first element so we can check if it has given field
+ # and if it's a string
+ first_element = state[list(state)[0]]
+ if field in first_element:
+
+ def sort_key(s):
+ try:
+ # Sort case-insensitively but preserve A>a order.
+ return state.get(s)[field].lower()
+ except AttributeError:
+ # Not a string.
+ return state.get(s)[field]
+
+ to_sort = sorted(to_sort, key=sort_key, reverse=reverse)
+
+ if field == 'eta':
+ to_sort = sorted(to_sort, key=lambda s: state.get(s)['eta'] == 0)
+
+ return to_sort
+
+ # Just in case primary and secondary fields are empty and/or
+ # both are too ambiguous, also sort by queue position first
+ if 'queue' not in [s_secondary, s_primary]:
+ result = sort_by_field(state, result, 'queue')
+ if s_secondary != s_primary:
+ result = sort_by_field(state, result, s_secondary)
+ result = sort_by_field(state, result, s_primary)
+
+ if self.config['torrentview']['separate_complete']:
+ result = sorted(
+ result, key=lambda s: state.get(s).get('progress', 0) == 100.0
+ )
+
+ return result
+
+ def _get_colors(self, row, tidx):
+ # default style
+ colors = {'fg': 'white', 'bg': 'black', 'attr': None}
+
+ if tidx in self.marked:
+ colors.update({'bg': 'blue', 'attr': 'bold'})
+
+ if tidx == self.cursel:
+ col_selected = {'bg': 'white', 'fg': 'black', 'attr': 'bold'}
+ if tidx in self.marked:
+ col_selected['fg'] = 'blue'
+ colors.update(col_selected)
+
+ colors['fg'] = state_fg_colors.get(row[1], colors['fg'])
+
+ if self.torrentlist.minor_mode:
+ self.torrentlist.minor_mode.update_colors(tidx, colors)
+ return colors
+
+ def update_torrents(self, lines):
+ # add all the torrents
+ if self.numtorrents == 0:
+ cols = self.torrentlist.torrentview_columns()
+ msg = 'No torrents match filter'.center(cols)
+ self.torrentlist.add_string(
+ 3, '{!info!}%s' % msg, scr=self.torrentlist.torrentview_panel
+ )
+ elif self.numtorrents == 0:
+ self.torrentlist.add_string(1, 'Waiting for torrents from core...')
+ return
+
+ def draw_row(index):
+ if index not in self.cached_rows:
+ ts = self.curstate[self.sorted_ids[index]]
+ self.cached_rows[index] = (
+ format_utils.format_row(
+ [get_column_value(name, ts) for name in self.cols_to_show],
+ self.column_widths,
+ ),
+ ts['state'],
+ )
+ return self.cached_rows[index]
+
+ tidx = self.curoff
+ currow = 0
+ todraw = []
+ # Affected lines are given when changing selected torrent
+ if lines:
+ for line in lines:
+ if line < tidx:
+ continue
+ if line >= (tidx + self.torrent_rows) or line >= self.numtorrents:
+ break
+ todraw.append((line, line - self.curoff, draw_row(line)))
+ else:
+ for i in range(tidx, tidx + self.torrent_rows):
+ if i >= self.numtorrents:
+ break
+ todraw.append((i, i - self.curoff, draw_row(i)))
+
+ for tidx, currow, row in todraw:
+ if (currow + self.torrentlist_offset - 1) > self.torrent_rows:
+ continue
+ colors = self._get_colors(row, tidx)
+ if colors['attr']:
+ colorstr = '{!%(fg)s,%(bg)s,%(attr)s!}' % colors
+ else:
+ colorstr = '{!%(fg)s,%(bg)s!}' % colors
+
+ self.torrentlist.add_string(
+ currow + self.torrentlist_offset,
+ '%s%s' % (colorstr, row[0]),
+ trim=False,
+ scr=self.torrentlist.torrentview_panel,
+ )
+
+ def update(self, refresh=False):
+ d = component.get('SessionProxy').get_torrents_status(
+ self.filter_dict, self.status_fields
+ )
+ d.addCallback(self.update_state, refresh=refresh)
+
+ def on_config_changed(self):
+ s_primary = self.config['torrentview']['sort_primary']
+ s_secondary = self.config['torrentview']['sort_secondary']
+ changed = None
+ for col in default_columns:
+ if col not in self.config['torrentview']['columns']:
+ changed = self.config['torrentview']['columns'][col] = default_columns[
+ col
+ ]
+ if changed:
+ self.config.save()
+
+ self.cols_to_show = [
+ col
+ for col in sorted(
+ self.config['torrentview']['columns'],
+ key=lambda k: self.config['torrentview']['columns'][k]['order'],
+ )
+ if self.config['torrentview']['columns'][col]['visible']
+ ]
+ self.status_fields = get_required_fields(self.cols_to_show)
+
+ # we always need these, even if we're not displaying them
+ for rf in ['state', 'name', 'queue', 'progress']:
+ if rf not in self.status_fields:
+ self.status_fields.append(rf)
+
+ # same with sort keys
+ if s_primary and s_primary not in self.status_fields:
+ self.status_fields.append(s_primary)
+ if s_secondary and s_secondary not in self.status_fields:
+ self.status_fields.append(s_secondary)
+
+ self.update_columns()
+
+ def update_columns(self):
+ self.column_widths = [
+ self.config['torrentview']['columns'][col]['width']
+ for col in self.cols_to_show
+ ]
+ requested_width = sum(width for width in self.column_widths if width >= 0)
+
+ cols = self.torrentlist.torrentview_columns()
+ if requested_width > cols: # can't satisfy requests, just spread out evenly
+ cw = int(cols / len(self.cols_to_show))
+ for i in range(0, len(self.column_widths)):
+ self.column_widths[i] = cw
+ else:
+ rem = cols - requested_width
+ var_cols = len([width for width in self.column_widths if width < 0])
+ if var_cols > 0:
+ vw = int(rem / var_cols)
+ for i in range(0, len(self.column_widths)):
+ if self.column_widths[i] < 0:
+ self.column_widths[i] = vw
+
+ self.column_string = '{!header!}'
+
+ primary_sort_col_name = self.config['torrentview']['sort_primary']
+
+ for i, column in enumerate(self.cols_to_show):
+ ccol = torrent_data_fields[column]['name']
+ width = self.column_widths[i]
+
+ # Trim the column if it's too long to fit
+ if len(ccol) > width:
+ ccol = ccol[: width - 1]
+
+ # Padding
+ ccol += ' ' * (width - len(ccol))
+
+ # Highlight the primary sort column
+ if column == primary_sort_col_name:
+ if i != len(self.cols_to_show) - 1:
+ ccol = '{!black,green,bold!}%s{!header!}' % ccol
+ else:
+ ccol = ('{!black,green,bold!}%s' % ccol)[:-1]
+
+ self.column_string += ccol
+
+ @overrides(InputKeyHandler)
+ def handle_read(self, c):
+ affected_lines = None
+ if c == curses.KEY_UP:
+ if self.cursel != 0:
+ affected_lines = self._scroll_up(1)
+ elif c == curses.KEY_PPAGE:
+ affected_lines = self._scroll_up(int(self.torrent_rows / 2))
+ elif c == curses.KEY_DOWN:
+ if self.cursel < self.numtorrents:
+ affected_lines = self._scroll_down(1)
+ elif c == curses.KEY_NPAGE:
+ affected_lines = self._scroll_down(int(self.torrent_rows / 2))
+ elif c == curses.KEY_HOME:
+ affected_lines = self._scroll_up(self.cursel)
+ elif c == curses.KEY_END:
+ affected_lines = self._scroll_down(self.numtorrents - self.cursel)
+ elif c == curses.KEY_DC: # DEL
+ added = self.update_marked(self.cursel)
+
+ def on_close(**kwargs):
+ if added:
+ self.marked.pop()
+
+ torrent_actions_popup(
+ self.torrentlist,
+ self._selected_torrent_ids(),
+ action=ACTION.REMOVE,
+ close_cb=on_close,
+ )
+ elif c in [curses.KEY_ENTER, util.KEY_ENTER2] and self.numtorrents:
+ added = self.update_marked(self.cursel)
+
+ def on_close(data, **kwargs):
+ if added:
+ self.marked.remove(self.cursel)
+
+ torrent_actions_popup(
+ self.torrentlist,
+ self._selected_torrent_ids(),
+ details=True,
+ close_cb=on_close,
+ )
+ self.torrentlist.refresh()
+ elif c == ord('j'):
+ affected_lines = self._scroll_up(1)
+ elif c == ord('k'):
+ affected_lines = self._scroll_down(1)
+ elif c == ord('m'):
+ self.mark_unmark(self.cursel)
+ affected_lines = [self.cursel]
+ elif c == ord('M'):
+ if self.last_mark >= 0:
+ if self.cursel > self.last_mark:
+ mrange = list(range(self.last_mark, self.cursel + 1))
+ else:
+ mrange = list(range(self.cursel, self.last_mark))
+ self.add_marked(mrange, self.cursel)
+ affected_lines = mrange
+ else:
+ self.mark_unmark(self.cursel)
+ affected_lines = [self.cursel]
+ elif c == ord('c'):
+ self.clear_marked()
+ elif c == ord('o'):
+ if not self.marked:
+ added = self.update_marked(self.cursel, clear=True)
+ else:
+ self.last_mark = -1
+ torrent_actions_popup(
+ self.torrentlist,
+ self._selected_torrent_ids(),
+ action=ACTION.TORRENT_OPTIONS,
+ )
+ elif c in [ord('>'), ord('<')]:
+ try:
+ i = self.cols_to_show.index(self.config['torrentview']['sort_primary'])
+ except ValueError:
+ i = 0 if chr(c) == '<' else len(self.cols_to_show)
+ else:
+ i += 1 if chr(c) == '>' else -1
+
+ i = max(0, min(len(self.cols_to_show) - 1, i))
+ self.config['torrentview']['sort_primary'] = self.cols_to_show[i]
+ self.config.save()
+ self.on_config_changed()
+ self.update_columns()
+ self.torrentlist.refresh([])
+ else:
+ return util.ReadState.IGNORED
+
+ self.set_input_result(affected_lines)
+ return util.ReadState.CHANGED if affected_lines else util.ReadState.READ
diff --git a/deluge/ui/console/modes/torrentlist/torrentviewcolumns.py b/deluge/ui/console/modes/torrentlist/torrentviewcolumns.py
new file mode 100644
index 0000000..9dff843
--- /dev/null
+++ b/deluge/ui/console/modes/torrentlist/torrentviewcolumns.py
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+from deluge.decorators import overrides
+from deluge.ui.console.utils import curses_util as util
+from deluge.ui.console.utils.column import torrent_data_fields
+from deluge.ui.console.widgets.fields import CheckedPlusInput, IntSpinInput
+from deluge.ui.console.widgets.popup import InputPopup, MessagePopup
+
+COLUMN_VIEW_HELP_STR = """
+Control column visibilty with the following actions:
+
+{!info!}'+'{!normal!} - {|indent_pos:|}Increase column width
+{!info!}'-'{!normal!} - {|indent_pos:|}Decrease column width
+
+{!info!}'CTRL+up'{!normal!} - {|indent_pos:|} Move column left
+{!info!}'CTRL+down'{!normal!} - {|indent_pos:|} Move column right
+"""
+
+column_pref_names = [
+ 'queue',
+ 'name',
+ 'size',
+ 'downloaded',
+ 'uploaded',
+ 'remaining',
+ 'state',
+ 'progress',
+ 'seeds',
+ 'peers',
+ 'seeds_peers_ratio',
+ 'download_speed',
+ 'upload_speed',
+ 'max_download_speed',
+ 'max_upload_speed',
+ 'eta',
+ 'ratio',
+ 'avail',
+ 'time_added',
+ 'completed_time',
+ 'last_seen_complete',
+ 'tracker',
+ 'download_location',
+ 'active_time',
+ 'seeding_time',
+ 'finished_time',
+ 'time_since_transfer',
+ 'shared',
+ 'owner',
+]
+
+
+class ColumnAndWidth(CheckedPlusInput):
+ def __init__(self, parent, name, message, child, on_width_func, **kwargs):
+ CheckedPlusInput.__init__(self, parent, name, message, child, **kwargs)
+ self.on_width_func = on_width_func
+
+ @overrides(CheckedPlusInput)
+ def handle_read(self, c):
+ if c in [ord('+'), ord('-')]:
+ val = self.child.get_value()
+ change = 1 if chr(c) == '+' else -1
+ self.child.set_value(val + change, validate=True)
+ self.on_width_func(self.name, self.child.get_value())
+ return util.ReadState.CHANGED
+ return CheckedPlusInput.handle_read(self, c)
+
+
+class TorrentViewColumns(InputPopup):
+ def __init__(self, torrentlist):
+ self.torrentlist = torrentlist
+ self.torrentview = torrentlist.torrentview
+
+ title = 'Visible columns (Esc to exit)'
+ InputPopup.__init__(
+ self,
+ torrentlist,
+ title,
+ close_cb=self._do_set_column_visibility,
+ immediate_action=True,
+ height_req=len(column_pref_names) - 5,
+ width_req=max(len(col) for col in column_pref_names + [title]) + 14,
+ border_off_west=1,
+ allow_rearrange=True,
+ )
+
+ msg_fmt = '%-25s'
+ self.add_header((msg_fmt % _('Columns')) + ' ' + _('Width'), space_below=True)
+
+ for colpref_name in column_pref_names:
+ col = self.torrentview.config['torrentview']['columns'][colpref_name]
+ width_spin = IntSpinInput(
+ self,
+ colpref_name + '_ width',
+ '',
+ self.move,
+ col['width'],
+ min_val=-1,
+ max_val=99,
+ fmt='%2d',
+ )
+
+ def on_width_func(name, width):
+ self.torrentview.config['torrentview']['columns'][name]['width'] = width
+
+ self._add_input(
+ ColumnAndWidth(
+ self,
+ colpref_name,
+ torrent_data_fields[colpref_name]['name'],
+ width_spin,
+ on_width_func,
+ checked=col['visible'],
+ checked_char='*',
+ msg_fmt=msg_fmt,
+ show_usage_hints=False,
+ child_always_visible=True,
+ )
+ )
+
+ def _do_set_column_visibility(
+ self, data=None, state_changed=True, close=True, **kwargs
+ ):
+ if close:
+ self.torrentlist.pop_popup()
+ return
+ elif not state_changed:
+ return
+
+ for key, value in data.items():
+ self.torrentview.config['torrentview']['columns'][key]['visible'] = value[
+ 'value'
+ ]
+ self.torrentview.config['torrentview']['columns'][key]['order'] = value[
+ 'order'
+ ]
+
+ self.torrentview.config.save()
+ self.torrentview.on_config_changed()
+ self.torrentlist.refresh([])
+
+ @overrides(InputPopup)
+ def handle_read(self, c):
+ if c == ord('h'):
+ popup = MessagePopup(
+ self.torrentlist,
+ 'Help',
+ COLUMN_VIEW_HELP_STR,
+ width_req=70,
+ border_off_west=1,
+ )
+ self.torrentlist.push_popup(popup)
+ return util.ReadState.READ
+ return InputPopup.handle_read(self, c)
diff --git a/deluge/ui/console/parser.py b/deluge/ui/console/parser.py
new file mode 100644
index 0000000..27f2485
--- /dev/null
+++ b/deluge/ui/console/parser.py
@@ -0,0 +1,143 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import print_function, unicode_literals
+
+import argparse
+import shlex
+
+import deluge.component as component
+from deluge.ui.console.utils.colors import ConsoleColorFormatter
+
+
+class OptionParserError(Exception):
+ pass
+
+
+class ConsoleBaseParser(argparse.ArgumentParser):
+ def format_help(self):
+ """Differs from ArgumentParser.format_help by adding the raw epilog
+ as formatted in the string. Default bahavior mangles the formatting.
+
+ """
+ # Handle epilog manually to keep the text formatting
+ epilog = self.epilog
+ self.epilog = ''
+ help_str = super(ConsoleBaseParser, self).format_help()
+ if epilog is not None:
+ help_str += epilog
+ self.epilog = epilog
+ return help_str
+
+
+class ConsoleCommandParser(ConsoleBaseParser):
+ def _split_args(self, args):
+ command_options = []
+ for a in args:
+ if not a:
+ continue
+ if ';' in a:
+ cmd_lines = [arg.strip() for arg in a.split(';')]
+ elif ' ' in a:
+ cmd_lines = [a]
+ else:
+ continue
+
+ for cmd_line in cmd_lines:
+ cmds = shlex.split(cmd_line)
+ cmd_options = super(ConsoleCommandParser, self).parse_args(args=cmds)
+ cmd_options.command = cmds[0]
+ command_options.append(cmd_options)
+
+ return command_options
+
+ def parse_args(self, args=None):
+ """Parse known UI args and handle common and process group options.
+
+ Notes:
+ If started by deluge entry script this has already been done.
+
+ Args:
+ args (list, optional): The arguments to parse.
+
+ Returns:
+ argparse.Namespace: The parsed arguments.
+ """
+ from deluge.ui.ui_entry import AMBIGUOUS_CMD_ARGS
+
+ self.base_parser.parse_known_ui_args(args, withhold=AMBIGUOUS_CMD_ARGS)
+
+ multi_command = self._split_args(args)
+ # If multiple commands were passed to console
+ if multi_command:
+ # With multiple commands, normal parsing will fail, so only parse
+ # known arguments using the base parser, and then set
+ # options.parsed_cmds to the already parsed commands
+ options, remaining = self.base_parser.parse_known_args(args=args)
+ options.parsed_cmds = multi_command
+ else:
+ subcommand = False
+ if hasattr(self.base_parser, 'subcommand'):
+ subcommand = getattr(self.base_parser, 'subcommand')
+ if not subcommand:
+ # We must use parse_known_args to handle case when no subcommand
+ # is provided, because argparse does not support parsing without
+ # a subcommand
+ options, remaining = self.base_parser.parse_known_args(args=args)
+ # If any options remain it means they do not exist. Reparse with
+ # parse_args to trigger help message
+ if remaining:
+ options = self.base_parser.parse_args(args=args)
+ options.parsed_cmds = []
+ else:
+ options = super(ConsoleCommandParser, self).parse_args(args=args)
+ options.parsed_cmds = [options]
+
+ if not hasattr(options, 'remaining'):
+ options.remaining = []
+
+ return options
+
+
+class OptionParser(ConsoleBaseParser):
+ def __init__(self, **kwargs):
+ super(OptionParser, self).__init__(**kwargs)
+ self.formatter = ConsoleColorFormatter()
+
+ def exit(self, status=0, msg=None):
+ self._exit = True
+ if msg:
+ print(msg)
+
+ def error(self, msg):
+ """error(msg : string)
+
+ Print a usage message incorporating 'msg' to stderr and exit.
+ If you override this in a subclass, it should not return -- it
+ should either exit or raise an exception.
+ """
+ raise OptionParserError(msg)
+
+ def print_usage(self, _file=None):
+ console = component.get('ConsoleUI')
+ if self.usage:
+ for line in self.format_usage().splitlines():
+ console.write(line)
+
+ def print_help(self, _file=None):
+ console = component.get('ConsoleUI')
+ console.set_batch_write(True)
+ for line in self.format_help().splitlines():
+ console.write(line)
+ console.set_batch_write(False)
+
+ def format_help(self):
+ """Return help formatted with colors."""
+ help_str = super(OptionParser, self).format_help()
+ return self.formatter.format_colors(help_str)
diff --git a/deluge/ui/console/utils/__init__.py b/deluge/ui/console/utils/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/deluge/ui/console/utils/__init__.py
diff --git a/deluge/ui/console/utils/colors.py b/deluge/ui/console/utils/colors.py
new file mode 100644
index 0000000..587c1f3
--- /dev/null
+++ b/deluge/ui/console/utils/colors.py
@@ -0,0 +1,326 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import re
+
+from deluge.ui.console.utils import format_utils
+
+try:
+ import curses
+except ImportError:
+ pass
+
+log = logging.getLogger(__name__)
+
+colors = [
+ 'COLOR_BLACK',
+ 'COLOR_BLUE',
+ 'COLOR_CYAN',
+ 'COLOR_GREEN',
+ 'COLOR_MAGENTA',
+ 'COLOR_RED',
+ 'COLOR_WHITE',
+ 'COLOR_YELLOW',
+]
+
+# {(fg, bg): pair_number, ...}
+color_pairs = {('white', 'black'): 0} # Special case, can't be changed
+
+# Some default color schemes
+schemes = {
+ 'input': ('white', 'black'),
+ 'normal': ('white', 'black'),
+ 'status': ('yellow', 'blue', 'bold'),
+ 'info': ('white', 'black', 'bold'),
+ 'error': ('red', 'black', 'bold'),
+ 'success': ('green', 'black', 'bold'),
+ 'event': ('magenta', 'black', 'bold'),
+ 'selected': ('black', 'white', 'bold'),
+ 'marked': ('white', 'blue', 'bold'),
+ 'selectedmarked': ('blue', 'white', 'bold'),
+ 'header': ('green', 'black', 'bold'),
+ 'filterstatus': ('green', 'blue', 'bold'),
+}
+
+# Colors for various torrent states
+state_color = {
+ 'Seeding': '{!blue,black,bold!}',
+ 'Downloading': '{!green,black,bold!}',
+ 'Paused': '{!white,black!}',
+ 'Checking': '{!green,black!}',
+ 'Queued': '{!yellow,black!}',
+ 'Error': '{!red,black,bold!}',
+ 'Moving': '{!green,black,bold!}',
+}
+
+type_color = {
+ bool: '{!yellow,black,bold!}',
+ int: '{!green,black,bold!}',
+ float: '{!green,black,bold!}',
+ str: '{!cyan,black,bold!}',
+ list: '{!magenta,black,bold!}',
+ dict: '{!white,black,bold!}',
+}
+
+tab_char = '\t'
+color_tag_start = '{!'
+color_tag_end = '!}'
+
+
+def get_color_pair(fg, bg):
+ return color_pairs[(fg, bg)]
+
+
+def init_colors():
+ curses.start_color()
+
+ # We want to redefine white/black as it makes underlining work for some terminals
+ # but can also fail on others, so we try/except
+
+ def define_pair(counter, fg_name, bg_name, fg, bg):
+ try:
+ curses.init_pair(counter, fg, bg)
+ color_pairs[(fg_name, bg_name)] = counter
+ counter += 1
+ except curses.error as ex:
+ log.warning('Error: %s', ex)
+ return counter
+
+ # Create the color_pairs dict
+ counter = 1
+ for fg in colors:
+ for bg in colors:
+ counter = define_pair(
+ counter,
+ fg[6:].lower(),
+ bg[6:].lower(),
+ getattr(curses, fg),
+ getattr(curses, bg),
+ )
+
+ counter = define_pair(counter, 'white', 'grey', curses.COLOR_WHITE, 241)
+ counter = define_pair(counter, 'black', 'whitegrey', curses.COLOR_BLACK, 249)
+ counter = define_pair(counter, 'magentadark', 'white', 99, curses.COLOR_WHITE)
+
+
+class BadColorString(Exception):
+ pass
+
+
+def check_tag_count(string):
+ """Raise BadColorString if color tag open/close not equal."""
+ if string.count(color_tag_start) != string.count(color_tag_end):
+ raise BadColorString('Number of {! is not equal to number of !}')
+
+
+def replace_tabs(line):
+ """
+ Returns a string with tabs replaced with spaces.
+
+ """
+ for i in range(line.count(tab_char)):
+ tab_length = 8 - (len(line[: line.find(tab_char)]) % 8)
+ line = line.replace(tab_char, b' ' * tab_length, 1)
+ return line
+
+
+def strip_colors(line):
+ """
+ Returns a string with the color formatting removed.
+
+ """
+ check_tag_count(line)
+
+ # Remove all the color tags
+ while line.find(color_tag_start) != -1:
+ tag_start = line.find(color_tag_start)
+ tag_end = line.find(color_tag_end) + 2
+ line = line[:tag_start] + line[tag_end:]
+
+ return line
+
+
+def get_line_length(line):
+ """
+ Returns the string length without the color formatting.
+
+ """
+ # Remove all the color tags
+ line = strip_colors(line)
+
+ # Replace tabs with the appropriate amount of spaces
+ line = replace_tabs(line)
+ return len(line)
+
+
+def get_line_width(line):
+ """
+ Get width of string considering double width characters
+
+ """
+ # Remove all the color tags
+ line = strip_colors(line)
+
+ # Replace tabs with the appropriate amount of spaces
+ line = replace_tabs(line)
+ return format_utils.strwidth(line)
+
+
+def parse_color_string(string):
+ """Parses a string and returns a list of 2-tuples (color, string).
+
+ Args:
+ string (str): The string to parse.
+ """
+ check_tag_count(string)
+
+ ret = []
+ last_color_attr = None
+ # Keep track of where the strings
+ while string.find(color_tag_start) != -1:
+ begin = string.find(color_tag_start)
+ if begin > 0:
+ ret.append(
+ (
+ curses.color_pair(
+ color_pairs[(schemes['input'][0], schemes['input'][1])]
+ ),
+ string[:begin],
+ )
+ )
+
+ end = string.find(color_tag_end)
+ if end == -1:
+ raise BadColorString('Missing closing "!}"')
+
+ # Get a list of attributes in the bracketed section
+ attrs = string[begin + 2 : end].split(',')
+
+ if len(attrs) == 1 and not attrs[0].strip(' '):
+ raise BadColorString('No description in {! !}')
+
+ def apply_attrs(cp, attrs):
+ # This function applies any additional attributes as necessary
+ for attr in attrs:
+ if attr == 'ignore':
+ continue
+ mode = '+'
+ if attr[0] in ['+', '-']:
+ mode = attr[0]
+ attr = attr[1:]
+ if mode == '+':
+ cp |= getattr(curses, 'A_' + attr.upper())
+ else:
+ cp ^= getattr(curses, 'A_' + attr.upper())
+ return cp
+
+ # Check for a builtin type first
+ if attrs[0] in schemes:
+ pair = (schemes[attrs[0]][0], schemes[attrs[0]][1])
+ if pair not in color_pairs:
+ log.debug('Color pair does not exist: %s, attrs: %s', pair, attrs)
+ pair = ('white', 'black')
+ # Get the color pair number
+ color_pair = curses.color_pair(color_pairs[pair])
+ color_pair = apply_attrs(color_pair, schemes[attrs[0]][2:])
+ last_color_attr = color_pair
+ else:
+ attrlist = ['blink', 'bold', 'dim', 'reverse', 'standout', 'underline']
+
+ if attrs[0][0] in ['+', '-']:
+ # Color is not given, so use last color
+ if last_color_attr is None:
+ raise BadColorString(
+ 'No color value given when no previous color was used!: %s'
+ % (attrs[0])
+ )
+ color_pair = last_color_attr
+ for i, attr in enumerate(attrs):
+ if attr[1:] not in attrlist:
+ raise BadColorString('Bad attribute value!: %s' % (attr))
+ else:
+ # This is a custom color scheme
+ fg = attrs[0]
+ bg = 'black' # Default to 'black' if no bg is chosen
+ if len(attrs) > 1:
+ bg = attrs[1]
+ try:
+ pair = (fg, bg)
+ if pair not in color_pairs:
+ # Color pair missing, this could be because the
+ # terminal settings allows no colors. If background is white, we
+ # assume this means selection, and use "white", "black" + reverse
+ # To have white background and black foreground
+ log.debug('Color pair does not exist: %s', pair)
+ if pair[1] == 'white':
+ if attrs[2] == 'ignore':
+ attrs[2] = 'reverse'
+ else:
+ attrs.append('reverse')
+ pair = ('white', 'black')
+ color_pair = curses.color_pair(color_pairs[pair])
+ last_color_attr = color_pair
+ attrs = attrs[2:] # Remove colors
+ except KeyError:
+ raise BadColorString('Bad color value in tag: %s,%s' % (fg, bg))
+ # Check for additional attributes and OR them to the color_pair
+ color_pair = apply_attrs(color_pair, attrs)
+ last_color_attr = color_pair
+ # We need to find the text now, so lets try to find another {! and if
+ # there isn't one, then it's the rest of the string
+ next_begin = string.find(color_tag_start, end)
+
+ if next_begin == -1:
+ ret.append((color_pair, replace_tabs(string[end + 2 :])))
+ break
+ else:
+ ret.append((color_pair, replace_tabs(string[end + 2 : next_begin])))
+ string = string[next_begin:]
+
+ if not ret:
+ # There was no color scheme so we add it with a 0 for white on black
+ ret = [(0, string)]
+ return ret
+
+
+class ConsoleColorFormatter(object):
+ """
+ Format help in a way suited to deluge CmdLine mode - colors, format, indentation...
+ """
+
+ replace_dict = {
+ '<torrent-id>': '{!green!}%s{!input!}',
+ '<torrent>': '{!green!}%s{!input!}',
+ '<command>': '{!green!}%s{!input!}',
+ '<state>': '{!yellow!}%s{!input!}',
+ '\\.\\.\\.': '{!yellow!}%s{!input!}',
+ '\\s\\*\\s': '{!blue!}%s{!input!}',
+ '(?<![\\-a-z])(-[a-zA-Z0-9])': '{!red!}%s{!input!}',
+ # "(\-[a-zA-Z0-9])": "{!red!}%s{!input!}",
+ '--[_\\-a-zA-Z0-9]+': '{!green!}%s{!input!}',
+ '(\\[|\\])': '{!info!}%s{!input!}',
+ '<tab>': '{!white!}%s{!input!}',
+ '[_A-Z]{3,}': '{!cyan!}%s{!input!}',
+ '<key>': '{!cyan!}%s{!input!}',
+ '<value>': '{!cyan!}%s{!input!}',
+ 'usage:': '{!info!}%s{!input!}',
+ '<download-folder>': '{!yellow!}%s{!input!}',
+ '<torrent-file>': '{!green!}%s{!input!}',
+ }
+
+ def format_colors(self, string):
+ def r(repl):
+ return lambda s: repl % s.group()
+
+ for key, replacement in self.replace_dict.items():
+ string = re.sub(key, r(replacement), string)
+ return string
diff --git a/deluge/ui/console/utils/column.py b/deluge/ui/console/utils/column.py
new file mode 100644
index 0000000..d932159
--- /dev/null
+++ b/deluge/ui/console/utils/column.py
@@ -0,0 +1,77 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import copy
+import logging
+
+import deluge.common
+from deluge.i18n import setup_translation
+from deluge.ui.common import TORRENT_DATA_FIELD
+from deluge.ui.console.utils import format_utils
+
+setup_translation()
+
+log = logging.getLogger(__name__)
+
+torrent_data_fields = copy.deepcopy(TORRENT_DATA_FIELD)
+
+formatters = {
+ 'queue': format_utils.format_queue,
+ 'name': lambda a, b: b,
+ 'state': None,
+ 'tracker': None,
+ 'download_location': None,
+ 'owner': None,
+ 'progress_state': format_utils.format_progress,
+ 'progress': format_utils.format_progress,
+ 'size': format_utils.format_size,
+ 'downloaded': format_utils.format_size,
+ 'uploaded': format_utils.format_size,
+ 'remaining': format_utils.format_size,
+ 'ratio': format_utils.format_float,
+ 'avail': format_utils.format_float,
+ 'seeds_peers_ratio': format_utils.format_float,
+ 'download_speed': format_utils.format_speed,
+ 'upload_speed': format_utils.format_speed,
+ 'max_download_speed': format_utils.format_speed,
+ 'max_upload_speed': format_utils.format_speed,
+ 'peers': format_utils.format_seeds_peers,
+ 'seeds': format_utils.format_seeds_peers,
+ 'time_added': deluge.common.fdate,
+ 'seeding_time': format_utils.format_time,
+ 'active_time': format_utils.format_time,
+ 'time_since_transfer': format_utils.format_date_dash,
+ 'finished_time': deluge.common.ftime,
+ 'last_seen_complete': format_utils.format_date_never,
+ 'completed_time': format_utils.format_date_dash,
+ 'eta': format_utils.format_time,
+ 'pieces': format_utils.format_pieces,
+}
+
+for data_field in torrent_data_fields:
+ torrent_data_fields[data_field]['formatter'] = formatters.get(data_field, str)
+
+
+def get_column_value(name, state):
+ col = torrent_data_fields[name]
+
+ if col['formatter']:
+ args = [state[key] for key in col['status']]
+ return col['formatter'](*args)
+ else:
+ return state[col['status'][0]]
+
+
+def get_required_fields(cols):
+ fields = []
+ for col in cols:
+ fields.extend(torrent_data_fields[col]['status'])
+ return fields
diff --git a/deluge/ui/console/utils/common.py b/deluge/ui/console/utils/common.py
new file mode 100644
index 0000000..df1c079
--- /dev/null
+++ b/deluge/ui/console/utils/common.py
@@ -0,0 +1,23 @@
+# -*- coding: utf-8 -*-
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+TORRENT_OPTIONS = {
+ 'max_download_speed': float,
+ 'max_upload_speed': float,
+ 'max_connections': int,
+ 'max_upload_slots': int,
+ 'prioritize_first_last': bool,
+ 'is_auto_managed': bool,
+ 'stop_at_ratio': bool,
+ 'stop_ratio': float,
+ 'remove_at_ratio': bool,
+ 'move_completed': bool,
+ 'move_completed_path': str,
+ 'super_seeding': bool,
+}
diff --git a/deluge/ui/console/utils/curses_util.py b/deluge/ui/console/utils/curses_util.py
new file mode 100644
index 0000000..a0cd6dc
--- /dev/null
+++ b/deluge/ui/console/utils/curses_util.py
@@ -0,0 +1,65 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+try:
+ import curses
+except ImportError:
+ pass
+
+KEY_BELL = 7 # CTRL-/ ^G (curses.keyname(KEY_BELL) == "^G")
+KEY_TAB = 9
+KEY_ENTER2 = 10
+KEY_ESC = 27
+KEY_SPACE = 32
+KEY_BACKSPACE2 = 127
+
+KEY_ALT_AND_ARROW_UP = 564
+KEY_ALT_AND_ARROW_DOWN = 523
+
+KEY_ALT_AND_KEY_PPAGE = 553
+KEY_ALT_AND_KEY_NPAGE = 548
+
+KEY_CTRL_AND_ARROW_UP = 566
+KEY_CTRL_AND_ARROW_DOWN = 525
+
+
+def is_printable_chr(c):
+ return c >= 32 and c <= 126
+
+
+def is_int_chr(c):
+ return c > 47 and c < 58
+
+
+class Curser(object):
+ INVISIBLE = 0
+ NORMAL = 1
+ VERY_VISIBLE = 2
+
+
+def safe_curs_set(visibility):
+ """
+ Args:
+ visibility(int): 0, 1, or 2, for invisible, normal, or very visible
+
+ curses.curs_set fails on monochrome terminals so use this
+ to ignore errors
+ """
+ try:
+ curses.curs_set(visibility)
+ except curses.error:
+ pass
+
+
+class ReadState(object):
+ IGNORED = 0
+ READ = 1
+ CHANGED = 2
diff --git a/deluge/ui/console/utils/format_utils.py b/deluge/ui/console/utils/format_utils.py
new file mode 100644
index 0000000..029fb20
--- /dev/null
+++ b/deluge/ui/console/utils/format_utils.py
@@ -0,0 +1,353 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import re
+from collections import deque
+from unicodedata import east_asian_width, normalize
+
+import deluge.common
+from deluge.ui.common import FILE_PRIORITY
+
+
+def format_size(size):
+ return deluge.common.fsize(size, shortform=True)
+
+
+def format_speed(speed):
+ if speed > 0:
+ return deluge.common.fspeed(speed, shortform=True)
+ else:
+ return '-'
+
+
+def format_time(time):
+ if time > 0:
+ return deluge.common.ftime(time)
+ elif time == 0:
+ return '-'
+ else:
+ return '∞'
+
+
+def format_date_dash(time):
+ if time > 0:
+ return deluge.common.fdate(time, date_only=True)
+ else:
+ return '-'
+
+
+def format_date_never(time):
+ if time > 0:
+ return deluge.common.fdate(time, date_only=True)
+ else:
+ return 'Never'
+
+
+def format_float(x):
+ if x < 0:
+ return '-'
+ else:
+ return '%.3f' % x
+
+
+def format_seeds_peers(num, total):
+ return '%d (%d)' % (num, total)
+
+
+def format_progress(value):
+ return ('%.2f' % value).rstrip('0').rstrip('.') + '%'
+
+
+def f_progressbar(progress, width):
+ """
+ Returns a string of a progress bar.
+
+ :param progress: float, a value between 0-100
+
+ :returns: str, a progress bar based on width
+
+ """
+
+ w = width - 2 # we use a [] for the beginning and end
+ s = '['
+ p = int(round((progress / 100) * w))
+ s += '#' * p
+ s += '-' * (w - p)
+ s += ']'
+ return s
+
+
+def f_seedrank_dash(seed_rank, seeding_time):
+ """Display value if seeding otherwise dash"""
+
+ if seeding_time > 0:
+ if seed_rank >= 1000:
+ return '%ik' % (seed_rank // 1000)
+ else:
+ return str(seed_rank)
+ else:
+ return '-'
+
+
+def ftotal_sized(first, second):
+ return '%s (%s)' % (
+ deluge.common.fsize(first, shortform=True),
+ deluge.common.fsize(second, shortform=True),
+ )
+
+
+def format_pieces(num, size):
+ return '%d (%s)' % (num, deluge.common.fsize(size, shortform=True))
+
+
+def format_priority(prio):
+ if prio == -2:
+ return '[Mixed]'
+ elif prio < 0:
+ return '-'
+ return FILE_PRIORITY[prio]
+
+
+def format_queue(qnum):
+ if qnum < 0:
+ return ''
+ return '%d' % (qnum + 1)
+
+
+def trim_string(string, w, have_dbls):
+ if w <= 0:
+ return ''
+ elif w == 1:
+ return ' '
+ elif have_dbls:
+ # have to do this the slow way
+ chrs = []
+ width = 4
+ idx = 0
+ while width < w:
+ chrs.append(string[idx])
+ if east_asian_width(string[idx]) in 'WF':
+ width += 2
+ else:
+ width += 1
+ idx += 1
+ if width != w:
+ chrs.pop()
+ chrs.append('.')
+ return '%s ' % (''.join(chrs))
+ else:
+ return '%s ' % (string[0 : w - 1])
+
+
+def format_column(col, lim):
+ try:
+ # might have some double width chars
+ col = normalize('NFC', col)
+ dbls = sum(east_asian_width(c) in 'WF' for c in col)
+ except TypeError:
+ dbls = 0
+
+ size = len(col) + dbls
+ if size >= lim - 1:
+ return trim_string(col, lim, dbls > 0)
+ else:
+ return '%s%s' % (col, ' ' * (lim - size))
+
+
+def format_row(row, column_widths):
+ return ''.join(
+ [format_column(row[i], column_widths[i]) for i in range(0, len(row))]
+ )
+
+
+_strip_re = re.compile(r'\{!.*?!\}')
+_format_code = re.compile(r'\{\|(.*)\|\}')
+
+
+def remove_formatting(string):
+ return re.sub(_strip_re, '', string)
+
+
+def shorten_hash(tid, space_left, min_width=13, placeholder='...'):
+ """Shorten the supplied torrent infohash by removing chars from the middle.
+
+ Use a placeholder to indicate shortened.
+ If unable to shorten will justify so entire tid is on the next line.
+
+ """
+ tid = tid.strip()
+ if space_left >= min_width:
+ mid = len(tid) // 2
+ trim, remain = divmod(len(tid) + len(placeholder) - space_left, 2)
+ return tid[0 : mid - trim] + placeholder + tid[mid + trim + remain :]
+ else:
+ # Justity the tid so it is completely on the next line.
+ return tid.rjust(len(tid) + space_left)
+
+
+def wrap_string(string, width, min_lines=0, strip_colors=True):
+ """
+ Wrap a string to fit in a particular width. Returns a list of output lines.
+
+ :param string: str, the string to wrap
+ :param width: int, the maximum width of a line of text
+ :param min_lines: int, extra lines will be added so the output tuple contains at least min_lines lines
+ :param strip_colors: boolean, if True, text in {!!} blocks will not be considered as adding to the
+ width of the line. They will still be present in the output.
+ """
+ ret = []
+ s1 = string.split('\n')
+ indent = ''
+
+ def insert_clr(s, offset, mtchs, clrs):
+ end_pos = offset + len(s)
+ while mtchs and (mtchs[0] <= end_pos) and (mtchs[0] >= offset):
+ mtc = mtchs.popleft() - offset
+ clr = clrs.popleft()
+ end_pos += len(clr)
+ s = '%s%s%s' % (s[:mtc], clr, s[mtc:])
+ return s
+
+ for s in s1:
+ offset = 0
+ indent = ''
+ m = _format_code.search(remove_formatting(s))
+ if m:
+ if m.group(1).startswith('indent:'):
+ indent = m.group(1)[len('indent:') :]
+ elif m.group(1).startswith('indent_pos:'):
+ begin = m.start(0)
+ indent = ' ' * begin
+ s = _format_code.sub('', s)
+
+ if strip_colors:
+ mtchs = deque()
+ clrs = deque()
+ for m in _strip_re.finditer(s):
+ mtchs.append(m.start())
+ clrs.append(m.group())
+ cstr = _strip_re.sub('', s)
+ else:
+ cstr = s
+
+ def append_indent(l, string, offset):
+ """Prepends indent to string if specified"""
+ if indent and offset != 0:
+ string = indent + string
+ l.append(string)
+
+ while cstr:
+ # max with for a line. If indent is specified, we account for this
+ max_width = width - (len(indent) if offset != 0 else 0)
+ if len(cstr) < max_width:
+ break
+ sidx = cstr.rfind(' ', 0, max_width - 1)
+ sidx += 1
+ if sidx > 0:
+ if strip_colors:
+ to_app = cstr[0:sidx]
+ to_app = insert_clr(to_app, offset, mtchs, clrs)
+ append_indent(ret, to_app, offset)
+ offset += len(to_app)
+ else:
+ append_indent(ret, cstr[0:sidx], offset)
+ cstr = cstr[sidx:]
+ if not cstr:
+ cstr = None
+ break
+ else:
+ # can't find a reasonable split, just split at width
+ if strip_colors:
+ to_app = cstr[0:width]
+ to_app = insert_clr(to_app, offset, mtchs, clrs)
+ append_indent(ret, to_app, offset)
+ offset += len(to_app)
+ else:
+ append_indent(ret, cstr[0:width], offset)
+ cstr = cstr[width:]
+ if not cstr:
+ cstr = None
+ break
+ if cstr is not None:
+ to_append = cstr
+ if strip_colors:
+ to_append = insert_clr(cstr, offset, mtchs, clrs)
+ append_indent(ret, to_append, offset)
+
+ if min_lines > 0:
+ for i in range(len(ret), min_lines):
+ ret.append(' ')
+
+ # Carry colors over to the next line
+ last_color_string = ''
+ for i, line in enumerate(ret):
+ if i != 0:
+ ret[i] = '%s%s' % (last_color_string, ret[i])
+
+ colors = re.findall('\\{![^!]+!\\}', line)
+ if colors:
+ last_color_string = colors[-1]
+
+ return ret
+
+
+def strwidth(string):
+ """
+ Measure width of a string considering asian double width characters
+ """
+ return sum(1 + (east_asian_width(char) in ['W', 'F']) for char in string)
+
+
+def pad_string(string, length, character=' ', side='right'):
+ """
+ Pad string with specified character to desired length, considering double width characters.
+ """
+ w = strwidth(string)
+ diff = length - w
+ if side == 'left':
+ return '%s%s' % (character * diff, string)
+ elif side == 'right':
+ return '%s%s' % (string, character * diff)
+
+
+def delete_alt_backspace(input_text, input_cursor, sep_chars=' *?!._~-#$^;\'"/'):
+ """
+ Remove text from input_text on ALT+backspace
+ Stop removing when countering any of the sep chars
+ """
+ deleted = 0
+ seg_start = input_text[:input_cursor]
+ seg_end = input_text[input_cursor:]
+ none_space_deleted = False # Track if any none-space characters have been deleted
+
+ while seg_start and input_cursor > 0:
+ if (not seg_start) or (input_cursor == 0):
+ break
+ if deleted and seg_start[-1] in sep_chars:
+ if seg_start[-1] == ' ':
+ if seg_start[-2] == ' ' or none_space_deleted is False:
+ # Continue as long as:
+ # * next char is also a space
+ # * no none-space characters have been deleted
+ pass
+ else:
+ break
+ else:
+ break
+
+ if not none_space_deleted:
+ none_space_deleted = seg_start[-1] != ' '
+ seg_start = seg_start[:-1]
+ deleted += 1
+ input_cursor -= 1
+
+ input_text = seg_start + seg_end
+ return input_text, input_cursor
diff --git a/deluge/ui/console/widgets/__init__.py b/deluge/ui/console/widgets/__init__.py
new file mode 100644
index 0000000..a11e3f2
--- /dev/null
+++ b/deluge/ui/console/widgets/__init__.py
@@ -0,0 +1,7 @@
+from __future__ import unicode_literals
+
+from deluge.ui.console.widgets.inputpane import BaseInputPane
+from deluge.ui.console.widgets.statusbars import StatusBars
+from deluge.ui.console.widgets.window import BaseWindow
+
+__all__ = ['BaseInputPane', 'StatusBars', 'BaseWindow']
diff --git a/deluge/ui/console/widgets/fields.py b/deluge/ui/console/widgets/fields.py
new file mode 100644
index 0000000..1966c66
--- /dev/null
+++ b/deluge/ui/console/widgets/fields.py
@@ -0,0 +1,1210 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os
+
+from deluge.common import PY2
+from deluge.decorators import overrides
+from deluge.ui.console.modes.basemode import InputKeyHandler
+from deluge.ui.console.utils import colors
+from deluge.ui.console.utils import curses_util as util
+from deluge.ui.console.utils.format_utils import (
+ delete_alt_backspace,
+ remove_formatting,
+ wrap_string,
+)
+
+try:
+ import curses
+except ImportError:
+ pass
+
+log = logging.getLogger(__name__)
+
+
+class BaseField(InputKeyHandler):
+ def __init__(self, parent=None, name=None, selectable=True, **kwargs):
+ super(BaseField, self).__init__()
+ self.name = name
+ self.parent = parent
+ self.fmt_keys = {}
+ self.set_fmt_key('font', 'ignore', kwargs)
+ self.set_fmt_key('color', 'white,black', kwargs)
+ self.set_fmt_key('color_end', 'white,black', kwargs)
+ self.set_fmt_key('color_active', 'black,white', kwargs)
+ self.set_fmt_key('color_unfocused', 'color', kwargs)
+ self.set_fmt_key('color_unfocused_active', 'black,whitegrey', kwargs)
+ self.set_fmt_key('font_active', 'font', kwargs)
+ self.set_fmt_key('font_unfocused', 'font', kwargs)
+ self.set_fmt_key('font_unfocused_active', 'font_active', kwargs)
+ self.default_col = kwargs.get('col', -1)
+ self._selectable = selectable
+ self.value = None
+
+ def selectable(self):
+ return self.has_input() and not self.depend_skip() and self._selectable
+
+ def set_fmt_key(self, key, default, kwargsdict=None):
+ value = self.fmt_keys.get(default, default)
+ if kwargsdict:
+ value = kwargsdict.get(key, value)
+ self.fmt_keys[key] = value
+
+ def get_fmt_keys(self, focused, active, **kwargs):
+ color_key = kwargs.get('color_key', 'color')
+ font_key = 'font'
+ if not focused:
+ color_key += '_unfocused'
+ font_key += '_unfocused'
+ if active:
+ color_key += '_active'
+ font_key += '_active'
+ return color_key, font_key
+
+ def build_fmt_string(self, focused, active, value_key='msg', **kwargs):
+ color_key, font_key = self.get_fmt_keys(focused, active, **kwargs)
+ return '{!%%(%s)s,%%(%s)s!}%%(%s)s{!%%(%s)s!}' % (
+ color_key,
+ font_key,
+ value_key,
+ 'color_end',
+ )
+
+ def depend_skip(self):
+ return False
+
+ def has_input(self):
+ return True
+
+ @overrides(InputKeyHandler)
+ def handle_read(self, c):
+ return util.ReadState.IGNORED
+
+ def render(self, screen, row, **kwargs):
+ return 0
+
+ @property
+ def height(self):
+ return 1
+
+ def set_value(self, value):
+ self.value = value
+
+ def get_value(self):
+ return self.value
+
+
+class NoInputField(BaseField):
+ @overrides(BaseField)
+ def has_input(self):
+ return False
+
+
+class InputField(BaseField):
+ def __init__(self, parent, name, message, format_default=None, **kwargs):
+ BaseField.__init__(self, parent=parent, name=name, **kwargs)
+ self.format_default = format_default
+ self.message = None
+ self.set_message(message)
+
+ depend = None
+
+ @overrides(BaseField)
+ def handle_read(self, c):
+ if c in [curses.KEY_ENTER, util.KEY_ENTER2, util.KEY_BACKSPACE2, 113]:
+ return util.ReadState.READ
+ return util.ReadState.IGNORED
+
+ def set_message(self, msg):
+ changed = self.message != msg
+ self.message = msg
+ return changed
+
+ def set_depend(self, i, inverse=False):
+ if not isinstance(i, CheckedInput):
+ raise Exception('Can only depend on CheckedInputs')
+ self.depend = i
+ self.inverse = inverse
+
+ def depend_skip(self):
+ if not self.depend:
+ return False
+ if self.inverse:
+ return self.depend.checked
+ else:
+ return not self.depend.checked
+
+
+class Header(NoInputField):
+ def __init__(self, parent, header, space_above, space_below, **kwargs):
+ if 'name' not in kwargs:
+ kwargs['name'] = header
+ NoInputField.__init__(self, parent=parent, **kwargs)
+ self.header = '{!white,black,bold!}%s' % header
+ self.space_above = space_above
+ self.space_below = space_below
+
+ @overrides(BaseField)
+ def render(self, screen, row, col=0, **kwargs):
+ rows = 1
+ if self.space_above:
+ row += 1
+ rows += 1
+ self.parent.add_string(row, self.header, scr=screen, col=col, pad=False)
+ if self.space_below:
+ rows += 1
+ return rows
+
+ @property
+ def height(self):
+ return 1 + int(self.space_above) + int(self.space_below)
+
+
+class InfoField(NoInputField):
+ def __init__(self, parent, name, label, value, **kwargs):
+ NoInputField.__init__(self, parent=parent, name=name, **kwargs)
+ self.label = label
+ self.value = value
+ self.txt = '%s %s' % (label, value)
+
+ @overrides(BaseField)
+ def render(self, screen, row, col=0, **kwargs):
+ self.parent.add_string(row, self.txt, scr=screen, col=col, pad=False)
+ return 1
+
+ @overrides(BaseField)
+ def set_value(self, v):
+ self.value = v
+ if isinstance(v, float):
+ self.txt = '%s %.2f' % (self.label, self.value)
+ else:
+ self.txt = '%s %s' % (self.label, self.value)
+
+
+class CheckedInput(InputField):
+ def __init__(
+ self,
+ parent,
+ name,
+ message,
+ checked=False,
+ checked_char='X',
+ unchecked_char=' ',
+ checkbox_format='[%s] ',
+ **kwargs
+ ):
+ InputField.__init__(self, parent, name, message, **kwargs)
+ self.set_value(checked)
+ self.fmt_keys.update(
+ {
+ 'msg': message,
+ 'checkbox_format': checkbox_format,
+ 'unchecked_char': unchecked_char,
+ 'checked_char': checked_char,
+ }
+ )
+ self.set_fmt_key('font_checked', 'font', kwargs)
+ self.set_fmt_key('font_unfocused_checked', 'font_checked', kwargs)
+ self.set_fmt_key('font_active_checked', 'font_active', kwargs)
+ self.set_fmt_key('font_unfocused_active_checked', 'font_active_checked', kwargs)
+ self.set_fmt_key('color_checked', 'color', kwargs)
+ self.set_fmt_key('color_active_checked', 'color_active', kwargs)
+ self.set_fmt_key('color_unfocused_checked', 'color_checked', kwargs)
+ self.set_fmt_key(
+ 'color_unfocused_active_checked', 'color_unfocused_active', kwargs
+ )
+
+ @property
+ def checked(self):
+ return self.value
+
+ @overrides(BaseField)
+ def get_fmt_keys(self, focused, active, **kwargs):
+ color_key, font_key = super(CheckedInput, self).get_fmt_keys(
+ focused, active, **kwargs
+ )
+ if self.checked:
+ color_key += '_checked'
+ font_key += '_checked'
+ return color_key, font_key
+
+ def build_msg_string(self, focused, active):
+ fmt_str = self.build_fmt_string(focused, active)
+ char = self.fmt_keys['checked_char' if self.checked else 'unchecked_char']
+ chk_box = ''
+ try:
+ chk_box = self.fmt_keys['checkbox_format'] % char
+ except KeyError:
+ pass
+ msg = fmt_str % self.fmt_keys
+ return chk_box + msg
+
+ @overrides(InputField)
+ def render(self, screen, row, col=0, **kwargs):
+ string = self.build_msg_string(kwargs.get('focused'), kwargs.get('active'))
+
+ self.parent.add_string(row, string, scr=screen, col=col, pad=False)
+ return 1
+
+ @overrides(InputField)
+ def handle_read(self, c):
+ if c == util.KEY_SPACE:
+ self.set_value(not self.checked)
+ return util.ReadState.CHANGED
+ return util.ReadState.IGNORED
+
+ @overrides(InputField)
+ def set_message(self, msg):
+ changed = InputField.set_message(self, msg)
+ if 'msg' in self.fmt_keys and self.fmt_keys['msg'] != msg:
+ changed = True
+ self.fmt_keys.update({'msg': msg})
+
+ return changed
+
+
+class CheckedPlusInput(CheckedInput):
+ def __init__(
+ self,
+ parent,
+ name,
+ message,
+ child,
+ child_always_visible=False,
+ show_usage_hints=True,
+ msg_fmt='%s ',
+ **kwargs
+ ):
+ CheckedInput.__init__(self, parent, name, message, **kwargs)
+ self.child = child
+ self.child_active = False
+ self.show_usage_hints = show_usage_hints
+ self.msg_fmt = msg_fmt
+ self.child_always_visible = child_always_visible
+
+ @property
+ def height(self):
+ return max(2 if self.show_usage_hints else 1, self.child.height)
+
+ @overrides(CheckedInput)
+ def render(
+ self, screen, row, width=None, active=False, focused=False, col=0, **kwargs
+ ):
+ isact = active and not self.child_active
+ CheckedInput.render(
+ self, screen, row, width=width, active=isact, focused=focused, col=col
+ )
+ rows = 1
+ if self.show_usage_hints and (
+ self.child_always_visible or (active and self.checked)
+ ):
+ msg = '(esc to leave)' if self.child_active else '(right arrow to edit)'
+ self.parent.add_string(row + 1, msg, scr=screen, col=col, pad=False)
+ rows += 1
+
+ msglen = len(
+ self.msg_fmt % colors.strip_colors(self.build_msg_string(focused, active))
+ )
+ # show child
+ if self.checked or self.child_always_visible:
+ crows = self.child.render(
+ screen,
+ row,
+ width=width - msglen,
+ active=self.child_active and active,
+ col=col + msglen,
+ cursor_offset=msglen,
+ )
+ rows = max(rows, crows)
+ else:
+ self.parent.add_string(
+ row,
+ '(enable to view/edit value)',
+ scr=screen,
+ col=col + msglen,
+ pad=False,
+ )
+ return rows
+
+ @overrides(CheckedInput)
+ def handle_read(self, c):
+ if self.child_active:
+ if c == util.KEY_ESC: # leave child on esc
+ self.child_active = False
+ return util.ReadState.READ
+ # pass keys through to child
+ return self.child.handle_read(c)
+ else:
+ if c == util.KEY_SPACE:
+ self.set_value(not self.checked)
+ return util.ReadState.CHANGED
+ if (self.checked or self.child_always_visible) and c == curses.KEY_RIGHT:
+ self.child_active = True
+ return util.ReadState.READ
+ return util.ReadState.IGNORED
+
+ def get_child(self):
+ return self.child
+
+
+class IntSpinInput(InputField):
+ def __init__(
+ self,
+ parent,
+ name,
+ message,
+ move_func,
+ value,
+ min_val=None,
+ max_val=None,
+ inc_amt=1,
+ incr_large=10,
+ strict_validation=False,
+ fmt='%d',
+ **kwargs
+ ):
+ InputField.__init__(self, parent, name, message, **kwargs)
+ self.convert_func = int
+ self.fmt = fmt
+ self.valstr = str(value)
+ self.default_str = self.valstr
+ self.set_value(value)
+ self.default_value = self.value
+ self.last_valid_value = self.value
+ self.last_active = False
+ self.cursor = len(self.valstr)
+ self.cursoff = (
+ colors.get_line_width(self.message) + 3
+ ) # + 4 for the " [ " in the rendered string
+ self.move_func = move_func
+ self.strict_validation = strict_validation
+ self.min_val = min_val
+ self.max_val = max_val
+ self.inc_amt = inc_amt
+ self.incr_large = incr_large
+
+ def validate_value(self, value, on_invalid=None):
+ if (self.min_val is not None) and value < self.min_val:
+ value = on_invalid if on_invalid else self.min_val
+ if (self.max_val is not None) and value > self.max_val:
+ value = on_invalid if on_invalid else self.max_val
+ return value
+
+ @overrides(InputField)
+ def render(
+ self, screen, row, active=False, focused=True, col=0, cursor_offset=0, **kwargs
+ ):
+ if active:
+ self.last_active = True
+ elif self.last_active:
+ self.set_value(
+ self.valstr, validate=True, value_on_fail=self.last_valid_value
+ )
+ self.last_active = False
+
+ fmt_str = self.build_fmt_string(focused, active, value_key='value')
+ value_format = '%(msg)s {!input!}'
+ if not self.valstr:
+ value_format += '[ ]'
+ elif self.format_default and self.valstr == self.default_str:
+ value_format += '[ {!magenta,black!}%(value)s{!input!} ]'
+ else:
+ value_format += '[ ' + fmt_str + ' ]'
+
+ self.parent.add_string(
+ row,
+ value_format
+ % dict({'msg': self.message, 'value': '%s' % self.valstr}, **self.fmt_keys),
+ scr=screen,
+ col=col,
+ pad=False,
+ )
+ if active:
+ if focused:
+ util.safe_curs_set(util.Curser.NORMAL)
+ self.move_func(row, self.cursor + self.cursoff + cursor_offset)
+ else:
+ util.safe_curs_set(util.Curser.INVISIBLE)
+ return 1
+
+ @overrides(InputField)
+ def handle_read(self, c):
+ if c == util.KEY_SPACE:
+ return util.ReadState.READ
+ elif c == curses.KEY_PPAGE:
+ self.set_value(self.value + self.inc_amt, validate=True)
+ elif c == curses.KEY_NPAGE:
+ self.set_value(self.value - self.inc_amt, validate=True)
+ elif c == util.KEY_ALT_AND_KEY_PPAGE:
+ self.set_value(self.value + self.incr_large, validate=True)
+ elif c == util.KEY_ALT_AND_KEY_NPAGE:
+ self.set_value(self.value - self.incr_large, validate=True)
+ elif c == curses.KEY_LEFT:
+ self.cursor = max(0, self.cursor - 1)
+ elif c == curses.KEY_RIGHT:
+ self.cursor = min(len(self.valstr), self.cursor + 1)
+ elif c == curses.KEY_HOME:
+ self.cursor = 0
+ elif c == curses.KEY_END:
+ self.cursor = len(self.valstr)
+ elif c == curses.KEY_BACKSPACE or c == util.KEY_BACKSPACE2:
+ if self.valstr and self.cursor > 0:
+ new_val = self.valstr[: self.cursor - 1] + self.valstr[self.cursor :]
+ self.set_value(
+ new_val,
+ validate=False,
+ cursor=self.cursor - 1,
+ cursor_on_fail=True,
+ value_on_fail=self.valstr if self.strict_validation else None,
+ )
+ elif c == curses.KEY_DC: # Del
+ if self.valstr and self.cursor <= len(self.valstr):
+ if self.cursor == 0:
+ new_val = self.valstr[1:]
+ else:
+ new_val = (
+ self.valstr[: self.cursor] + self.valstr[self.cursor + 1 :]
+ )
+ self.set_value(
+ new_val,
+ validate=False,
+ cursor=False,
+ value_on_fail=self.valstr if self.strict_validation else None,
+ cursor_on_fail=True,
+ )
+ elif c == ord('-'): # minus
+ self.set_value(
+ self.value - 1,
+ validate=True,
+ cursor=True,
+ cursor_on_fail=True,
+ value_on_fail=self.value,
+ on_invalid=self.value,
+ )
+ elif c == ord('+'): # plus
+ self.set_value(
+ self.value + 1,
+ validate=True,
+ cursor=True,
+ cursor_on_fail=True,
+ value_on_fail=self.value,
+ on_invalid=self.value,
+ )
+ elif util.is_int_chr(c):
+ if self.strict_validation:
+ new_val = (
+ self.valstr[: self.cursor - 1]
+ + chr(c)
+ + self.valstr[self.cursor - 1 :]
+ )
+ self.set_value(
+ new_val,
+ validate=True,
+ cursor=self.cursor + 1,
+ value_on_fail=self.valstr,
+ on_invalid=self.value,
+ )
+ else:
+ minus_place = self.valstr.find('-')
+ if self.cursor > minus_place:
+ new_val = (
+ self.valstr[: self.cursor] + chr(c) + self.valstr[self.cursor :]
+ )
+ self.set_value(
+ new_val,
+ validate=True,
+ cursor=self.cursor + 1,
+ on_invalid=self.value,
+ )
+ else:
+ return util.ReadState.IGNORED
+ return util.ReadState.READ
+
+ @overrides(BaseField)
+ def set_value(
+ self,
+ val,
+ cursor=True,
+ validate=False,
+ cursor_on_fail=False,
+ value_on_fail=None,
+ on_invalid=None,
+ ):
+ value = None
+ try:
+ value = self.convert_func(val)
+ if validate:
+ validated = self.validate_value(value, on_invalid)
+ if validated != value:
+ # Value was not valid, so use validated value instead.
+ # Also set cursor according to validated value
+ cursor = True
+ value = validated
+
+ new_valstr = self.fmt % value
+ if new_valstr == self.valstr:
+ # If string has not change, keep cursor
+ cursor = False
+ self.valstr = new_valstr
+ self.last_valid_value = self.value = value
+ except ValueError:
+ if value_on_fail is not None:
+ self.set_value(
+ value_on_fail,
+ cursor=cursor,
+ cursor_on_fail=cursor_on_fail,
+ validate=validate,
+ on_invalid=on_invalid,
+ )
+ return
+ self.value = None
+ self.valstr = val
+ if cursor_on_fail:
+ self.cursor = cursor
+ except TypeError:
+ import traceback
+
+ log.warning('TypeError: %s', ''.join(traceback.format_exc()))
+ else:
+ if cursor is True:
+ self.cursor = len(self.valstr)
+ elif cursor is not False:
+ self.cursor = cursor
+
+
+class FloatSpinInput(IntSpinInput):
+ def __init__(self, parent, message, name, move_func, value, precision=1, **kwargs):
+ self.precision = precision
+ IntSpinInput.__init__(self, parent, message, name, move_func, value, **kwargs)
+ self.fmt = '%%.%df' % precision
+ self.convert_func = lambda valstr: round(float(valstr), self.precision)
+ self.set_value(value)
+ self.cursor = len(self.valstr)
+
+ @overrides(IntSpinInput)
+ def handle_read(self, c):
+ if c == ord('.'):
+ minus_place = self.valstr.find('-')
+ if self.cursor <= minus_place:
+ return util.ReadState.READ
+ point_place = self.valstr.find('.')
+ if point_place >= 0:
+ return util.ReadState.READ
+ new_val = self.valstr[: self.cursor] + chr(c) + self.valstr[self.cursor :]
+ self.set_value(new_val, validate=True, cursor=self.cursor + 1)
+ else:
+ return IntSpinInput.handle_read(self, c)
+
+
+class SelectInput(InputField):
+ def __init__(
+ self,
+ parent,
+ name,
+ message,
+ opts,
+ vals,
+ active_index,
+ active_default=False,
+ require_select_action=True,
+ **kwargs
+ ):
+ InputField.__init__(self, parent, name, message, **kwargs)
+ self.opts = opts
+ self.vals = vals
+ self.active_index = active_index
+ self.selected_index = active_index
+ self.default_option = active_index if active_default else None
+ self.require_select_action = require_select_action
+ self.fmt_keys.update({'font_active': 'bold'})
+ font_selected = kwargs.get('font_selected', 'bold,underline')
+
+ self.set_fmt_key('font_selected', font_selected, kwargs)
+ self.set_fmt_key('font_active_selected', 'font_selected', kwargs)
+ self.set_fmt_key('font_unfocused_selected', 'font_selected', kwargs)
+ self.set_fmt_key(
+ 'font_unfocused_active_selected', 'font_active_selected', kwargs
+ )
+
+ self.set_fmt_key('color_selected', 'color', kwargs)
+ self.set_fmt_key('color_active_selected', 'color_active', kwargs)
+ self.set_fmt_key('color_unfocused_selected', 'color_selected', kwargs)
+ self.set_fmt_key(
+ 'color_unfocused_active_selected', 'color_unfocused_active', kwargs
+ )
+ self.set_fmt_key('color_default_value', 'magenta,black', kwargs)
+
+ self.set_fmt_key('color_default_value', 'magenta,black')
+ self.set_fmt_key('color_default_value_active', 'magentadark,white')
+ self.set_fmt_key('color_default_value_selected', 'color_default_value', kwargs)
+ self.set_fmt_key('color_default_value_unfocused', 'color_default_value', kwargs)
+ self.set_fmt_key(
+ 'color_default_value_unfocused_selected',
+ 'color_default_value_selected',
+ kwargs,
+ )
+ self.set_fmt_key('color_default_value_active_selected', 'magentadark,white')
+ self.set_fmt_key(
+ 'color_default_value_unfocused_active_selected',
+ 'color_unfocused_active',
+ kwargs,
+ )
+
+ @property
+ def height(self):
+ return 1 + bool(self.message)
+
+ @overrides(BaseField)
+ def get_fmt_keys(self, focused, active, selected=False, **kwargs):
+ color_key, font_key = super(SelectInput, self).get_fmt_keys(
+ focused, active, **kwargs
+ )
+ if selected:
+ color_key += '_selected'
+ font_key += '_selected'
+ return color_key, font_key
+
+ @overrides(InputField)
+ def render(self, screen, row, active=False, focused=True, col=0, **kwargs):
+ if self.message:
+ self.parent.add_string(row, self.message, scr=screen, col=col, pad=False)
+ row += 1
+
+ off = col + 1
+ for i, opt in enumerate(self.opts):
+ self.fmt_keys['msg'] = opt
+ fmt_args = {'selected': i == self.selected_index}
+ if i == self.default_option:
+ fmt_args['color_key'] = 'color_default_value'
+ fmt = self.build_fmt_string(
+ focused, (i == self.active_index) and active, **fmt_args
+ )
+ string = '[%s]' % (fmt % self.fmt_keys)
+ self.parent.add_string(row, string, scr=screen, col=off, pad=False)
+ off += len(opt) + 3
+ if self.message:
+ return 2
+ else:
+ return 1
+
+ @overrides(InputField)
+ def handle_read(self, c):
+ if c == curses.KEY_LEFT:
+ self.active_index = max(0, self.active_index - 1)
+ if not self.require_select_action:
+ self.selected_index = self.active_index
+ elif c == curses.KEY_RIGHT:
+ self.active_index = min(len(self.opts) - 1, self.active_index + 1)
+ if not self.require_select_action:
+ self.selected_index = self.active_index
+ elif c == ord(' '):
+ if self.require_select_action:
+ self.selected_index = self.active_index
+ else:
+ return util.ReadState.IGNORED
+ return util.ReadState.READ
+
+ @overrides(BaseField)
+ def get_value(self):
+ return self.vals[self.selected_index]
+
+ @overrides(BaseField)
+ def set_value(self, value):
+ for i, val in enumerate(self.vals):
+ if value == val:
+ self.selected_index = i
+ return
+ raise Exception('Invalid value for SelectInput')
+
+
+class TextInput(InputField):
+ def __init__(
+ self,
+ parent,
+ name,
+ message,
+ move_func,
+ width,
+ value,
+ complete=False,
+ activate_input=False,
+ **kwargs
+ ):
+ InputField.__init__(self, parent, name, message, **kwargs)
+ self.move_func = move_func
+ self._width = width
+ self.value = value if value else ''
+ self.default_value = value
+ self.complete = complete
+ self.tab_count = 0
+ self.cursor = len(self.value)
+ self.opts = None
+ self.opt_off = 0
+ self.value_offset = 0
+ self.activate_input = activate_input # Wether input must be activated
+ self.input_active = not self.activate_input
+
+ @property
+ def width(self):
+ return self._width
+
+ @property
+ def height(self):
+ return 1 + bool(self.message)
+
+ def calculate_textfield_value(self, width, cursor_offset):
+ cursor_width = width
+
+ if self.cursor > (cursor_width - 1):
+ c_pos_abs = self.cursor - cursor_width
+ if cursor_width <= (self.cursor - self.value_offset):
+ new_cur = c_pos_abs + 1
+ self.value_offset = new_cur
+ else:
+ if self.cursor >= len(self.value):
+ c_pos_abs = len(self.value) - cursor_width
+ new_cur = c_pos_abs + 1
+ self.value_offset = new_cur
+ vstr = self.value[self.value_offset :]
+
+ if len(vstr) > cursor_width:
+ vstr = vstr[:cursor_width]
+ vstr = vstr.ljust(cursor_width)
+ else:
+ if len(self.value) <= cursor_width:
+ self.value_offset = 0
+ vstr = self.value.ljust(cursor_width)
+ else:
+ self.value_offset = min(self.value_offset, self.cursor)
+ vstr = self.value[self.value_offset :]
+ if len(vstr) > cursor_width:
+ vstr = vstr[:cursor_width]
+ vstr = vstr.ljust(cursor_width)
+
+ return vstr
+
+ def calculate_cursor_pos(self, width, col):
+ cursor_width = width
+ x_pos = self.cursor + col
+
+ if (self.cursor + col - self.value_offset) > cursor_width:
+ x_pos += self.value_offset
+ else:
+ x_pos -= self.value_offset
+
+ return min(width - 1 + col, x_pos)
+
+ @overrides(InputField)
+ def render(
+ self,
+ screen,
+ row,
+ width=None,
+ active=False,
+ focused=True,
+ col=0,
+ cursor_offset=0,
+ **kwargs
+ ):
+ if not self.value and not active and len(self.default_value) != 0:
+ self.value = self.default_value
+ self.cursor = len(self.value)
+
+ if self.message:
+ self.parent.add_string(row, self.message, scr=screen, col=col, pad=False)
+ row += 1
+
+ vstr = self.calculate_textfield_value(width, cursor_offset)
+
+ if active:
+ if self.opts:
+ self.parent.add_string(
+ row + 1, self.opts[self.opt_off :], scr=screen, col=col, pad=False
+ )
+
+ if focused and self.input_active:
+ util.safe_curs_set(
+ util.Curser.NORMAL
+ ) # Make cursor visible when text field is focused
+ x_pos = self.calculate_cursor_pos(width, col)
+ self.move_func(row, x_pos)
+
+ fmt = '{!black,white,bold!}%s'
+ if (
+ self.format_default
+ and len(self.value) != 0
+ and self.value == self.default_value
+ ):
+ fmt = '{!magenta,white!}%s'
+ if not active or not focused or self.input_active:
+ fmt = '{!white,grey,bold!}%s'
+
+ self.parent.add_string(
+ row, fmt % vstr, scr=screen, col=col, pad=False, trim=False
+ )
+ return self.height
+
+ @overrides(BaseField)
+ def set_value(self, val):
+ self.value = val
+ self.cursor = len(self.value)
+
+ @overrides(InputField)
+ def handle_read(self, c):
+ """
+ Return False when key was swallowed, i.e. we recognised
+ the key and no further action by other components should
+ be performed.
+ """
+ if self.activate_input:
+ if not self.input_active:
+ if c in [
+ curses.KEY_LEFT,
+ curses.KEY_RIGHT,
+ curses.KEY_HOME,
+ curses.KEY_END,
+ curses.KEY_ENTER,
+ util.KEY_ENTER2,
+ ]:
+ self.input_active = True
+ return util.ReadState.READ
+ else:
+ return util.ReadState.IGNORED
+ elif c == util.KEY_ESC:
+ self.input_active = False
+ return util.ReadState.READ
+
+ if c == util.KEY_TAB and self.complete:
+ # Keep track of tab hit count to know when it's double-hit
+ self.tab_count += 1
+ if self.tab_count > 1:
+ second_hit = True
+ self.tab_count = 0
+ else:
+ second_hit = False
+
+ # We only call the tab completer function if we're at the end of
+ # the input string on the cursor is on a space
+ if self.cursor == len(self.value) or self.value[self.cursor] == ' ':
+ if self.opts:
+ prev = self.opt_off
+ self.opt_off += self.width - 3
+ # now find previous double space, best guess at a split point
+ # in future could keep opts unjoined to get this really right
+ self.opt_off = self.opts.rfind(' ', 0, self.opt_off) + 2
+ if (
+ second_hit and self.opt_off == prev
+ ): # double tap and we're at the end
+ self.opt_off = 0
+ else:
+ opts = self.do_complete(self.value)
+ if len(opts) == 1: # only one option, just complete it
+ self.value = opts[0]
+ self.cursor = len(opts[0])
+ self.tab_count = 0
+ elif len(opts) > 1:
+ prefix = os.path.commonprefix(opts)
+ if prefix:
+ self.value = prefix
+ self.cursor = len(prefix)
+
+ if (
+ len(opts) > 1 and second_hit
+ ): # display multiple options on second tab hit
+ sp = self.value.rfind(os.sep) + 1
+ self.opts = ' '.join([o[sp:] for o in opts])
+
+ # Cursor movement
+ elif c == curses.KEY_LEFT:
+ self.cursor = max(0, self.cursor - 1)
+ elif c == curses.KEY_RIGHT:
+ self.cursor = min(len(self.value), self.cursor + 1)
+ elif c == curses.KEY_HOME:
+ self.cursor = 0
+ elif c == curses.KEY_END:
+ self.cursor = len(self.value)
+
+ # Delete a character in the input string based on cursor position
+ elif c == curses.KEY_BACKSPACE or c == util.KEY_BACKSPACE2:
+ if self.value and self.cursor > 0:
+ self.value = self.value[: self.cursor - 1] + self.value[self.cursor :]
+ self.cursor -= 1
+ elif c == [util.KEY_ESC, util.KEY_BACKSPACE2] or c == [
+ util.KEY_ESC,
+ curses.KEY_BACKSPACE,
+ ]:
+ self.value, self.cursor = delete_alt_backspace(self.value, self.cursor)
+ elif c == curses.KEY_DC:
+ if self.value and self.cursor < len(self.value):
+ self.value = self.value[: self.cursor] + self.value[self.cursor + 1 :]
+ elif c > 31 and c < 256:
+ # Emulate getwch
+ stroke = chr(c)
+ uchar = '' if PY2 else stroke
+ while not uchar:
+ try:
+ uchar = stroke.decode(self.parent.encoding)
+ except UnicodeDecodeError:
+ c = self.parent.parent.stdscr.getch()
+ stroke += chr(c)
+ if uchar:
+ if self.cursor == len(self.value):
+ self.value += uchar
+ else:
+ # Insert into string
+ self.value = (
+ self.value[: self.cursor] + uchar + self.value[self.cursor :]
+ )
+ # Move the cursor forward
+ self.cursor += 1
+
+ else:
+ self.opts = None
+ self.opt_off = 0
+ self.tab_count = 0
+ return util.ReadState.IGNORED
+ return util.ReadState.READ
+
+ def do_complete(self, line):
+ line = os.path.abspath(os.path.expanduser(line))
+ ret = []
+ if os.path.exists(line):
+ # This is a correct path, check to see if it's a directory
+ if os.path.isdir(line):
+ # Directory, so we need to show contents of directory
+ for f in os.listdir(line):
+ # Skip hidden
+ if f.startswith('.'):
+ continue
+ f = os.path.join(line, f)
+ if os.path.isdir(f):
+ f += os.sep
+ ret.append(f)
+ else:
+ # This is a file, but we could be looking for another file that
+ # shares a common prefix.
+ for f in os.listdir(os.path.dirname(line)):
+ if f.startswith(os.path.split(line)[1]):
+ ret.append(os.path.join(os.path.dirname(line), f))
+ else:
+ # This path does not exist, so lets do a listdir on it's parent
+ # and find any matches.
+ ret = []
+ if os.path.isdir(os.path.dirname(line)):
+ for f in os.listdir(os.path.dirname(line)):
+ if f.startswith(os.path.split(line)[1]):
+ p = os.path.join(os.path.dirname(line), f)
+
+ if os.path.isdir(p):
+ p += os.sep
+ ret.append(p)
+ return ret
+
+
+class ComboInput(InputField):
+ def __init__(
+ self, parent, name, message, choices, default=None, searchable=True, **kwargs
+ ):
+ InputField.__init__(self, parent, name, message, **kwargs)
+ self.choices = choices
+ self.default = default
+ self.set_value(default)
+ max_width = 0
+ for c in choices:
+ max_width = max(max_width, len(c[1]))
+ self.choices_width = max_width
+ self.searchable = searchable
+
+ @overrides(BaseField)
+ def render(self, screen, row, col=0, **kwargs):
+ fmt_str = self.build_fmt_string(kwargs.get('focused'), kwargs.get('active'))
+ string = '%s: [%10s]' % (self.message, fmt_str % self.fmt_keys)
+ self.parent.add_string(row, string, scr=screen, col=col, pad=False)
+ return 1
+
+ def _lang_selected(self, selected, *args, **kwargs):
+ if selected is not None:
+ self.set_value(selected)
+ self.parent.pop_popup()
+
+ @overrides(InputField)
+ def handle_read(self, c):
+ if c in [util.KEY_SPACE, curses.KEY_ENTER, util.KEY_ENTER2]:
+
+ def search_handler(key):
+ """Handle keyboard input to seach the list"""
+ if not util.is_printable_chr(key):
+ return
+ selected = select_popup.current_selection()
+
+ def select_in_range(begin, end):
+ for i in range(begin, end):
+ val = select_popup.inputs[i].get_value()
+ if val.lower().startswith(chr(key)):
+ select_popup.set_selection(i)
+ return True
+ return False
+
+ # First search downwards
+ if not select_in_range(selected + 1, len(select_popup.inputs)):
+ # No match, so start at beginning
+ select_in_range(0, selected)
+
+ from deluge.ui.console.widgets.popup import (
+ SelectablePopup,
+ ) # Must import here
+
+ select_popup = SelectablePopup(
+ self.parent,
+ ' %s ' % _('Select Language'),
+ self._lang_selected,
+ input_cb=search_handler if self.searchable else None,
+ border_off_west=1,
+ active_wrap=False,
+ width_req=self.choices_width + 12,
+ )
+ for choice in self.choices:
+ args = {'data': choice[0]}
+ select_popup.add_line(
+ choice[0],
+ choice[1],
+ selectable=True,
+ selected=choice[0] == self.get_value(),
+ **args
+ )
+ self.parent.push_popup(select_popup)
+ return util.ReadState.CHANGED
+ return util.ReadState.IGNORED
+
+ @overrides(BaseField)
+ def set_value(self, val):
+ self.value = val
+ msg = None
+ for c in self.choices:
+ if c[0] == val:
+ msg = c[1]
+ break
+ if msg is None:
+ log.warning(
+ 'Setting value "%s" found nothing in choices: %s', val, self.choices
+ )
+ self.fmt_keys.update({'msg': msg})
+
+
+class TextField(BaseField):
+ def __init__(self, parent, name, value, selectable=True, value_fmt='%s', **kwargs):
+ BaseField.__init__(
+ self, parent=parent, name=name, selectable=selectable, **kwargs
+ )
+ self.value = value
+ self.value_fmt = value_fmt
+ self.set_value(value)
+
+ @overrides(BaseField)
+ def set_value(self, value):
+ self.value = value
+ self.txt = self.value_fmt % (value)
+
+ @overrides(BaseField)
+ def has_input(self):
+ return True
+
+ @overrides(BaseField)
+ def render(self, screen, row, active=False, focused=False, col=0, **kwargs):
+ util.safe_curs_set(
+ util.Curser.INVISIBLE
+ ) # Make cursor invisible when text field is active
+ fmt = self.build_fmt_string(focused, active)
+ self.fmt_keys['msg'] = self.txt
+ string = fmt % self.fmt_keys
+ self.parent.add_string(row, string, scr=screen, col=col, pad=False, trim=False)
+ return 1
+
+
+class TextArea(TextField):
+ def __init__(self, parent, name, value, value_fmt='%s', **kwargs):
+ TextField.__init__(
+ self, parent, name, value, selectable=False, value_fmt=value_fmt, **kwargs
+ )
+
+ @overrides(TextField)
+ def render(self, screen, row, col=0, **kwargs):
+ util.safe_curs_set(
+ util.Curser.INVISIBLE
+ ) # Make cursor invisible when text field is active
+ color = '{!white,black!}'
+ lines = wrap_string(self.txt, self.parent.width - 3, 3, True)
+
+ for i, line in enumerate(lines):
+ self.parent.add_string(
+ row + i,
+ '%s%s' % (color, line),
+ scr=screen,
+ col=col,
+ pad=False,
+ trim=False,
+ )
+ return len(lines)
+
+ @property
+ def height(self):
+ lines = wrap_string(self.txt, self.parent.width - 3, 3, True)
+ return len(lines)
+
+ @overrides(TextField)
+ def has_input(self):
+ return False
+
+
+class DividerField(NoInputField):
+ def __init__(
+ self,
+ parent,
+ name,
+ value,
+ selectable=False,
+ fill_width=True,
+ value_fmt='%s',
+ **kwargs
+ ):
+ NoInputField.__init__(
+ self, parent=parent, name=name, selectable=selectable, **kwargs
+ )
+ self.value = value
+ self.value_fmt = value_fmt
+ self.set_value(value)
+ self.fill_width = fill_width
+
+ @overrides(BaseField)
+ def set_value(self, value):
+ self.value = value
+ self.txt = self.value_fmt % (value)
+
+ @overrides(BaseField)
+ def render(
+ self, screen, row, active=False, focused=False, col=0, width=None, **kwargs
+ ):
+ util.safe_curs_set(
+ util.Curser.INVISIBLE
+ ) # Make cursor invisible when text field is active
+ fmt = self.build_fmt_string(focused, active)
+ self.fmt_keys['msg'] = self.txt
+ if self.fill_width:
+ self.fmt_keys['msg'] = ''
+ string_len = len(remove_formatting(fmt % self.fmt_keys))
+ fill_len = width - string_len - (len(self.txt) - 1)
+ self.fmt_keys['msg'] = self.txt * fill_len
+ string = fmt % self.fmt_keys
+ self.parent.add_string(row, string, scr=screen, col=col, pad=False, trim=False)
+ return 1
diff --git a/deluge/ui/console/widgets/inputpane.py b/deluge/ui/console/widgets/inputpane.py
new file mode 100644
index 0000000..097a6cb
--- /dev/null
+++ b/deluge/ui/console/widgets/inputpane.py
@@ -0,0 +1,397 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+from deluge.decorators import overrides
+from deluge.ui.console.modes.basemode import InputKeyHandler, move_cursor
+from deluge.ui.console.utils import curses_util as util
+from deluge.ui.console.widgets.fields import (
+ CheckedInput,
+ CheckedPlusInput,
+ ComboInput,
+ DividerField,
+ FloatSpinInput,
+ Header,
+ InfoField,
+ IntSpinInput,
+ NoInputField,
+ SelectInput,
+ TextArea,
+ TextField,
+ TextInput,
+)
+
+try:
+ import curses
+except ImportError:
+ pass
+
+log = logging.getLogger(__name__)
+
+
+class BaseInputPane(InputKeyHandler):
+ def __init__(
+ self,
+ mode,
+ allow_rearrange=False,
+ immediate_action=False,
+ set_first_input_active=True,
+ border_off_west=0,
+ border_off_north=0,
+ border_off_east=0,
+ border_off_south=0,
+ active_wrap=False,
+ **kwargs
+ ):
+ InputKeyHandler.__init__(self)
+ self.inputs = []
+ self.mode = mode
+ self.active_input = 0
+ self.set_first_input_active = set_first_input_active
+ self.allow_rearrange = allow_rearrange
+ self.immediate_action = immediate_action
+ self.move_active_many = 4
+ self.active_wrap = active_wrap
+ self.lineoff = 0
+ self.border_off_west = border_off_west
+ self.border_off_north = border_off_north
+ self.border_off_east = border_off_east
+ self.border_off_south = border_off_south
+ self.last_lineoff_move = 0
+
+ if not hasattr(self, 'visible_content_pane_height'):
+ log.error(
+ 'The class "%s" does not have the attribute "%s" required by super class "%s"',
+ self.__class__.__name__,
+ 'visible_content_pane_height',
+ BaseInputPane.__name__,
+ )
+ raise AttributeError('visible_content_pane_height')
+
+ @property
+ def visible_content_pane_width(self):
+ return self.mode.width
+
+ def add_spaces(self, num):
+ string = ''
+ for i in range(num):
+ string += '\n'
+
+ self.add_text_area('space %d' % len(self.inputs), string)
+
+ def add_text(self, string):
+ self.add_text_area('', string)
+
+ def move(self, r, c):
+ self._cursor_row = r
+ self._cursor_col = c
+
+ def get_input(self, name):
+ for e in self.inputs:
+ if e.name == name:
+ return e
+
+ def _add_input(self, input_element):
+ for e in self.inputs:
+ if isinstance(e, NoInputField):
+ continue
+ if e.name == input_element.name:
+ import traceback
+
+ log.warning(
+ 'Input element with name "%s" already exists in input pane (%s):\n%s',
+ input_element.name,
+ e,
+ ''.join(traceback.format_stack(limit=5)),
+ )
+ return
+
+ self.inputs.append(input_element)
+ if self.set_first_input_active and input_element.selectable():
+ self.active_input = len(self.inputs) - 1
+ self.set_first_input_active = False
+ return input_element
+
+ def add_header(self, header, space_above=False, space_below=False, **kwargs):
+ return self._add_input(Header(self, header, space_above, space_below, **kwargs))
+
+ def add_info_field(self, name, label, value):
+ return self._add_input(InfoField(self, name, label, value))
+
+ def add_text_field(self, name, message, selectable=True, col='+1', **kwargs):
+ return self._add_input(
+ TextField(self, name, message, selectable=selectable, col=col, **kwargs)
+ )
+
+ def add_text_area(self, name, message, **kwargs):
+ return self._add_input(TextArea(self, name, message, **kwargs))
+
+ def add_divider_field(self, name, message, **kwargs):
+ return self._add_input(DividerField(self, name, message, **kwargs))
+
+ def add_text_input(self, name, message, value='', col='+1', **kwargs):
+ """
+ Add a text input field
+
+ :param message: string to display above the input field
+ :param name: name of the field, for the return callback
+ :param value: initial value of the field
+ :param complete: should completion be run when tab is hit and this field is active
+ """
+ return self._add_input(
+ TextInput(
+ self,
+ name,
+ message,
+ self.move,
+ self.visible_content_pane_width,
+ value,
+ col=col,
+ **kwargs
+ )
+ )
+
+ def add_select_input(self, name, message, opts, vals, default_index=0, **kwargs):
+ return self._add_input(
+ SelectInput(self, name, message, opts, vals, default_index, **kwargs)
+ )
+
+ def add_checked_input(self, name, message, checked=False, col='+1', **kwargs):
+ return self._add_input(
+ CheckedInput(self, name, message, checked=checked, col=col, **kwargs)
+ )
+
+ def add_checkedplus_input(
+ self, name, message, child, checked=False, col='+1', **kwargs
+ ):
+ return self._add_input(
+ CheckedPlusInput(
+ self, name, message, child, checked=checked, col=col, **kwargs
+ )
+ )
+
+ def add_float_spin_input(self, name, message, value=0.0, col='+1', **kwargs):
+ return self._add_input(
+ FloatSpinInput(self, name, message, self.move, value, col=col, **kwargs)
+ )
+
+ def add_int_spin_input(self, name, message, value=0, col='+1', **kwargs):
+ return self._add_input(
+ IntSpinInput(self, name, message, self.move, value, col=col, **kwargs)
+ )
+
+ def add_combo_input(self, name, message, choices, col='+1', **kwargs):
+ return self._add_input(
+ ComboInput(self, name, message, choices, col=col, **kwargs)
+ )
+
+ @overrides(InputKeyHandler)
+ def handle_read(self, c):
+ if not self.inputs: # no inputs added yet
+ return util.ReadState.IGNORED
+ ret = self.inputs[self.active_input].handle_read(c)
+ if ret != util.ReadState.IGNORED:
+ if self.immediate_action:
+ self.immediate_action_cb(
+ state_changed=False if ret == util.ReadState.READ else True
+ )
+ return ret
+
+ ret = util.ReadState.READ
+
+ if c == curses.KEY_UP:
+ self.move_active_up(1)
+ elif c == curses.KEY_DOWN:
+ self.move_active_down(1)
+ elif c == curses.KEY_HOME:
+ self.move_active_up(len(self.inputs))
+ elif c == curses.KEY_END:
+ self.move_active_down(len(self.inputs))
+ elif c == curses.KEY_PPAGE:
+ self.move_active_up(self.move_active_many)
+ elif c == curses.KEY_NPAGE:
+ self.move_active_down(self.move_active_many)
+ elif c == util.KEY_ALT_AND_ARROW_UP:
+ self.lineoff = max(self.lineoff - 1, 0)
+ elif c == util.KEY_ALT_AND_ARROW_DOWN:
+ tot_height = self.get_content_height()
+ self.lineoff = min(
+ self.lineoff + 1, tot_height - self.visible_content_pane_height
+ )
+ elif c == util.KEY_CTRL_AND_ARROW_UP:
+ if not self.allow_rearrange:
+ return ret
+ val = self.inputs.pop(self.active_input)
+ self.active_input -= 1
+ self.inputs.insert(self.active_input, val)
+ if self.immediate_action:
+ self.immediate_action_cb(state_changed=True)
+ elif c == util.KEY_CTRL_AND_ARROW_DOWN:
+ if not self.allow_rearrange:
+ return ret
+ val = self.inputs.pop(self.active_input)
+ self.active_input += 1
+ self.inputs.insert(self.active_input, val)
+ if self.immediate_action:
+ self.immediate_action_cb(state_changed=True)
+ else:
+ ret = util.ReadState.IGNORED
+ return ret
+
+ def get_values(self):
+ vals = {}
+ for i, ipt in enumerate(self.inputs):
+ if not ipt.has_input():
+ continue
+ vals[ipt.name] = {
+ 'value': ipt.get_value(),
+ 'order': i,
+ 'active': self.active_input == i,
+ }
+ return vals
+
+ def immediate_action_cb(self, state_changed=True):
+ pass
+
+ def move_active(self, direction, amount):
+ """
+ direction == -1: Up
+ direction == 1: Down
+
+ """
+ self.last_lineoff_move = direction * amount
+
+ if direction > 0:
+ if self.active_wrap:
+ limit = self.active_input - 1
+ if limit < 0:
+ limit = len(self.inputs) + limit
+ else:
+ limit = len(self.inputs) - 1
+ else:
+ limit = 0
+ if self.active_wrap:
+ limit = self.active_input + 1
+
+ def next_move(nc, direction, limit):
+ next_index = nc
+ while next_index != limit:
+ next_index += direction
+ if direction > 0:
+ next_index %= len(self.inputs)
+ elif next_index < 0:
+ next_index = len(self.inputs) + next_index
+
+ if self.inputs[next_index].selectable():
+ return next_index
+ if next_index == limit:
+ return nc
+ return nc
+
+ next_sel = self.active_input
+ for a in range(amount):
+ cur_sel = next_sel
+ next_sel = next_move(next_sel, direction, limit)
+ if cur_sel == next_sel:
+ tot_height = (
+ self.get_content_height()
+ + self.border_off_north
+ + self.border_off_south
+ )
+ if direction > 0:
+ self.lineoff = min(
+ self.lineoff + 1, tot_height - self.visible_content_pane_height
+ )
+ else:
+ self.lineoff = max(self.lineoff - 1, 0)
+
+ if next_sel is not None:
+ self.active_input = next_sel
+
+ def move_active_up(self, amount):
+ self.move_active(-1, amount)
+ if self.immediate_action:
+ self.immediate_action_cb(state_changed=False)
+
+ def move_active_down(self, amount):
+ self.move_active(1, amount)
+ if self.immediate_action:
+ self.immediate_action_cb(state_changed=False)
+
+ def get_content_height(self):
+ height = 0
+ for i, ipt in enumerate(self.inputs):
+ if ipt.depend_skip():
+ continue
+ height += ipt.height
+ return height
+
+ def ensure_active_visible(self):
+ start_row = 0
+ end_row = self.border_off_north
+ for i, ipt in enumerate(self.inputs):
+ if ipt.depend_skip():
+ continue
+ start_row = end_row
+ end_row += ipt.height
+ if i != self.active_input or not ipt.has_input():
+ continue
+ height = self.visible_content_pane_height
+ if end_row > height + self.lineoff:
+ self.lineoff += end_row - (
+ height + self.lineoff
+ ) # Correct result depends on paranthesis
+ elif start_row < self.lineoff:
+ self.lineoff -= self.lineoff - start_row
+ break
+
+ def render_inputs(self, focused=False):
+ self._cursor_row = -1
+ self._cursor_col = -1
+ util.safe_curs_set(util.Curser.INVISIBLE)
+
+ self.ensure_active_visible()
+
+ crow = self.border_off_north
+ for i, ipt in enumerate(self.inputs):
+ if ipt.depend_skip():
+ continue
+ col = self.border_off_west
+ field_width = self.width - self.border_off_east - self.border_off_west
+ cursor_offset = self.border_off_west
+
+ if ipt.default_col != -1:
+ default_col = int(ipt.default_col)
+ if isinstance(ipt.default_col, ''.__class__) and ipt.default_col[0] in [
+ '+',
+ '-',
+ ]:
+ col += default_col
+ cursor_offset += default_col
+ field_width -= default_col # Increase to col must be reflected here
+ else:
+ col = default_col
+ crow += ipt.render(
+ self.screen,
+ crow,
+ width=field_width,
+ active=i == self.active_input,
+ focused=focused,
+ col=col,
+ cursor_offset=cursor_offset,
+ )
+
+ if self._cursor_row >= 0:
+ util.safe_curs_set(util.Curser.VERY_VISIBLE)
+ move_cursor(self.screen, self._cursor_row, self._cursor_col)
diff --git a/deluge/ui/console/widgets/popup.py b/deluge/ui/console/widgets/popup.py
new file mode 100644
index 0000000..d588bbb
--- /dev/null
+++ b/deluge/ui/console/widgets/popup.py
@@ -0,0 +1,402 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+from deluge.decorators import overrides
+from deluge.ui.console.modes.basemode import InputKeyHandler
+from deluge.ui.console.utils import curses_util as util
+from deluge.ui.console.utils import format_utils
+from deluge.ui.console.widgets import BaseInputPane, BaseWindow
+
+try:
+ import curses
+except ImportError:
+ pass
+
+log = logging.getLogger(__name__)
+
+
+class ALIGN(object):
+ TOP_LEFT = 1
+ TOP_CENTER = 2
+ TOP_RIGHT = 3
+ MIDDLE_LEFT = 4
+ MIDDLE_CENTER = 5
+ MIDDLE_RIGHT = 6
+ BOTTOM_LEFT = 7
+ BOTTOM_CENTER = 8
+ BOTTOM_RIGHT = 9
+ DEFAULT = MIDDLE_CENTER
+
+
+class PopupsHandler(object):
+ def __init__(self):
+ self._popups = []
+
+ @property
+ def popup(self):
+ if self._popups:
+ return self._popups[-1]
+ return None
+
+ def push_popup(self, pu, clear=False):
+ if clear:
+ self._popups = []
+ self._popups.append(pu)
+
+ def pop_popup(self):
+ if self.popup:
+ return self._popups.pop()
+
+ def report_message(self, title, message):
+ self.push_popup(MessagePopup(self, title, message))
+
+
+class Popup(BaseWindow, InputKeyHandler):
+ def __init__(
+ self,
+ parent_mode,
+ title,
+ width_req=0,
+ height_req=0,
+ align=ALIGN.DEFAULT,
+ close_cb=None,
+ encoding=None,
+ base_popup=None,
+ **kwargs
+ ):
+ """
+ Init a new popup. The default constructor will handle sizing and borders and the like.
+
+ Args:
+ parent_mode (basemode subclass): The mode which the popup will be drawn over
+ title (str): the title of the popup window
+ width_req (int or float): An integer value will be used as the width of the popup in character.
+ A float value will indicate the requested ratio in relation to the
+ parents screen width.
+ height_req (int or float): An integer value will be used as the height of the popup in character.
+ A float value will indicate the requested ratio in relation to the
+ parents screen height.
+ align (ALIGN): The alignment controlling the position of the popup on the screen.
+ close_cb (func): Function to be called when the popup is closed
+ encoding (str): The terminal encoding
+ base_popup (Popup): A popup used to inherit width_req and height_req if not explicitly specified.
+
+ Note: The parent mode is responsible for calling refresh on any popups it wants to show.
+ This should be called as the last thing in the parents refresh method.
+
+ The parent *must* also call read_input on the popup instead of/in addition to
+ running its own read_input code if it wants to have the popup handle user input.
+
+ Popups have two methods that must be implemented:
+
+ refresh(self) - draw the popup window to screen. this default mode simply draws a bordered window
+ with the supplied title to the screen
+
+ read_input(self) - handle user input to the popup.
+
+ """
+ InputKeyHandler.__init__(self)
+ self.parent = parent_mode
+ self.close_cb = close_cb
+ self.height_req = height_req
+ self.width_req = width_req
+ self.align = align
+ if base_popup:
+ if not self.width_req:
+ self.width_req = base_popup.width_req
+ if not self.height_req:
+ self.height_req = base_popup.height_req
+
+ hr, wr, posy, posx = self.calculate_size()
+ BaseWindow.__init__(self, title, wr, hr, encoding=None)
+ self.move_window(posy, posx)
+ self._closed = False
+
+ @overrides(BaseWindow)
+ def refresh(self):
+ self.screen.erase()
+ height = self.get_content_height()
+ self.ensure_content_pane_height(
+ height + self.border_off_north + self.border_off_south
+ )
+ BaseInputPane.render_inputs(self, focused=True)
+ BaseWindow.refresh(self)
+
+ def calculate_size(self):
+
+ if isinstance(self.height_req, float) and 0.0 < self.height_req <= 1.0:
+ height = int((self.parent.rows - 2) * self.height_req)
+ else:
+ height = self.height_req
+
+ if isinstance(self.width_req, float) and 0.0 < self.width_req <= 1.0:
+ width = int((self.parent.cols - 2) * self.width_req)
+ else:
+ width = self.width_req
+
+ # Height
+ if height == 0:
+ height = int(self.parent.rows / 2)
+ elif height == -1:
+ height = self.parent.rows - 2
+ elif height > self.parent.rows - 2:
+ height = self.parent.rows - 2
+
+ # Width
+ if width == 0:
+ width = int(self.parent.cols / 2)
+ elif width == -1:
+ width = self.parent.cols
+ elif width >= self.parent.cols:
+ width = self.parent.cols
+
+ if self.align in [ALIGN.TOP_CENTER, ALIGN.TOP_LEFT, ALIGN.TOP_RIGHT]:
+ begin_y = 1
+ elif self.align in [ALIGN.MIDDLE_CENTER, ALIGN.MIDDLE_LEFT, ALIGN.MIDDLE_RIGHT]:
+ begin_y = (self.parent.rows / 2) - (height / 2)
+ elif self.align in [ALIGN.BOTTOM_CENTER, ALIGN.BOTTOM_LEFT, ALIGN.BOTTOM_RIGHT]:
+ begin_y = self.parent.rows - height - 1
+
+ if self.align in [ALIGN.TOP_LEFT, ALIGN.MIDDLE_LEFT, ALIGN.BOTTOM_LEFT]:
+ begin_x = 0
+ elif self.align in [ALIGN.TOP_CENTER, ALIGN.MIDDLE_CENTER, ALIGN.BOTTOM_CENTER]:
+ begin_x = (self.parent.cols / 2) - (width / 2)
+ elif self.align in [ALIGN.TOP_RIGHT, ALIGN.MIDDLE_RIGHT, ALIGN.BOTTOM_RIGHT]:
+ begin_x = self.parent.cols - width
+
+ return height, width, begin_y, begin_x
+
+ def handle_resize(self):
+ height, width, begin_y, begin_x = self.calculate_size()
+ self.resize_window(height, width)
+ self.move_window(begin_y, begin_x)
+
+ def closed(self):
+ return self._closed
+
+ def close(self, *args, **kwargs):
+ self._closed = True
+ if kwargs.get('call_cb', True):
+ self._call_close_cb(*args)
+ self.panel.hide()
+
+ def _call_close_cb(self, *args, **kwargs):
+ if self.close_cb:
+ self.close_cb(*args, base_popup=self, **kwargs)
+
+ @overrides(InputKeyHandler)
+ def handle_read(self, c):
+ if c == util.KEY_ESC: # close on esc, no action
+ self.close(None)
+ return util.ReadState.READ
+ return util.ReadState.IGNORED
+
+
+class SelectablePopup(BaseInputPane, Popup):
+ """
+ A popup which will let the user select from some of the lines that are added.
+ """
+
+ def __init__(
+ self,
+ parent_mode,
+ title,
+ selection_cb,
+ close_cb=None,
+ input_cb=None,
+ allow_rearrange=False,
+ immediate_action=False,
+ **kwargs
+ ):
+ """
+ Args:
+ parent_mode (basemode subclass): The mode which the popup will be drawn over
+ title (str): the title of the popup window
+ selection_cb (func): Function to be called on selection
+ close_cb (func, optional): Function to be called when the popup is closed
+ input_cb (func, optional): Function to be called on every keyboard input
+ allow_rearrange (bool): Allow rearranging the selectable value
+ immediate_action (bool): If immediate_action_cb should be called for every action
+ kwargs (dict): Arguments passed to Popup
+
+ """
+ Popup.__init__(self, parent_mode, title, close_cb=close_cb, **kwargs)
+ kwargs.update(
+ {'allow_rearrange': allow_rearrange, 'immediate_action': immediate_action}
+ )
+ BaseInputPane.__init__(self, self, **kwargs)
+ self.selection_cb = selection_cb
+ self.input_cb = input_cb
+ self.hotkeys = {}
+ self.cb_arg = {}
+ self.cb_args = kwargs.get('cb_args', {})
+ if 'base_popup' not in self.cb_args:
+ self.cb_args['base_popup'] = self
+
+ @property
+ @overrides(BaseWindow)
+ def visible_content_pane_height(self):
+ """We want to use the Popup property"""
+ return Popup.visible_content_pane_height.fget(self)
+
+ def current_selection(self):
+ """Returns a tuple of (selected index, selected data)."""
+ return self.active_input
+
+ def set_selection(self, index):
+ """Set a selected index"""
+ self.active_input = index
+
+ def add_line(
+ self,
+ name,
+ string,
+ use_underline=True,
+ cb_arg=None,
+ foreground=None,
+ selectable=True,
+ selected=False,
+ **kwargs
+ ):
+ hotkey = None
+ self.cb_arg[name] = cb_arg
+ if use_underline:
+ udx = string.find('_')
+ if udx >= 0:
+ hotkey = string[udx].lower()
+ string = (
+ string[:udx]
+ + '{!+underline!}'
+ + string[udx + 1]
+ + '{!-underline!}'
+ + string[udx + 2 :]
+ )
+
+ kwargs['selectable'] = selectable
+ if foreground:
+ kwargs['color_active'] = '%s,white' % foreground
+ kwargs['color'] = '%s,black' % foreground
+
+ field = self.add_text_field(name, string, **kwargs)
+ if hotkey:
+ self.hotkeys[hotkey] = field
+
+ if selected:
+ self.set_selection(len(self.inputs) - 1)
+
+ @overrides(Popup, BaseInputPane)
+ def handle_read(self, c):
+ if c in [curses.KEY_ENTER, util.KEY_ENTER2]:
+ for k, v in self.get_values().items():
+ if v['active']:
+ if self.selection_cb(k, **dict(self.cb_args, data=self.cb_arg)):
+ self.close(None)
+ return util.ReadState.READ
+ else:
+ ret = BaseInputPane.handle_read(self, c)
+ if ret != util.ReadState.IGNORED:
+ return ret
+ ret = Popup.handle_read(self, c)
+ if ret != util.ReadState.IGNORED:
+ if self.selection_cb(None):
+ self.close(None)
+ return ret
+
+ if self.input_cb:
+ self.input_cb(c)
+
+ self.refresh()
+ return util.ReadState.IGNORED
+
+ def add_divider(self, message=None, char='-', fill_width=True, color='white'):
+ if message is not None:
+ fill_width = False
+ else:
+ message = char
+ self.add_divider_field('', message, selectable=False, fill_width=fill_width)
+
+
+class MessagePopup(Popup, BaseInputPane):
+ """
+ Popup that just displays a message
+ """
+
+ def __init__(
+ self,
+ parent_mode,
+ title,
+ message,
+ align=ALIGN.DEFAULT,
+ height_req=0.75,
+ width_req=0.5,
+ **kwargs
+ ):
+ self.message = message
+ Popup.__init__(
+ self,
+ parent_mode,
+ title,
+ align=align,
+ height_req=height_req,
+ width_req=width_req,
+ )
+ BaseInputPane.__init__(self, self, immediate_action=True, **kwargs)
+ lns = format_utils.wrap_string(self.message, self.width - 3, 3, True)
+
+ if isinstance(self.height_req, float):
+ self.height_req = min(len(lns) + 2, int(parent_mode.rows * self.height_req))
+
+ self.handle_resize()
+ self.no_refresh = False
+ self.add_text_area('TextMessage', message)
+
+ @overrides(Popup, BaseInputPane)
+ def handle_read(self, c):
+ ret = BaseInputPane.handle_read(self, c)
+ if ret != util.ReadState.IGNORED:
+ return ret
+ return Popup.handle_read(self, c)
+
+
+class InputPopup(Popup, BaseInputPane):
+ def __init__(self, parent_mode, title, **kwargs):
+ Popup.__init__(self, parent_mode, title, **kwargs)
+ BaseInputPane.__init__(self, self, **kwargs)
+ # We need to replicate some things in order to wrap our inputs
+ self.encoding = parent_mode.encoding
+
+ def _handle_callback(self, state_changed=True, close=True):
+ self._call_close_cb(self.get_values(), state_changed=state_changed, close=close)
+
+ @overrides(BaseInputPane)
+ def immediate_action_cb(self, state_changed=True):
+ self._handle_callback(state_changed=state_changed, close=False)
+
+ @overrides(Popup, BaseInputPane)
+ def handle_read(self, c):
+ ret = BaseInputPane.handle_read(self, c)
+ if ret != util.ReadState.IGNORED:
+ return ret
+
+ if c in [curses.KEY_ENTER, util.KEY_ENTER2]:
+ if self.close_cb:
+ self._handle_callback(state_changed=False, close=False)
+ util.safe_curs_set(util.Curser.INVISIBLE)
+ return util.ReadState.READ
+ elif c == util.KEY_ESC: # close on esc, no action
+ self._handle_callback(state_changed=False, close=True)
+ self.close(None)
+ return util.ReadState.READ
+
+ self.refresh()
+ return util.ReadState.READ
diff --git a/deluge/ui/console/widgets/sidebar.py b/deluge/ui/console/widgets/sidebar.py
new file mode 100644
index 0000000..cc23717
--- /dev/null
+++ b/deluge/ui/console/widgets/sidebar.py
@@ -0,0 +1,82 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2016 bendikro <bro.devel+deluge@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import curses
+import logging
+
+from deluge.decorators import overrides
+from deluge.ui.console.modes.basemode import add_string
+from deluge.ui.console.utils import curses_util as util
+from deluge.ui.console.widgets import BaseInputPane, BaseWindow
+
+log = logging.getLogger(__name__)
+
+
+class Sidebar(BaseInputPane, BaseWindow):
+ """Base sidebar widget that handles choosing a selected widget
+ with Up/Down arrows.
+
+ Shows the different states of the torrents and allows to filter the
+ torrents based on state.
+
+ """
+
+ def __init__(
+ self, torrentlist, width, height, title=None, allow_resize=False, **kwargs
+ ):
+ BaseWindow.__init__(self, title, width, height, posy=1)
+ BaseInputPane.__init__(self, self, immediate_action=True, **kwargs)
+ self.parent = torrentlist
+ self.focused = False
+ self.allow_resize = allow_resize
+
+ def set_focused(self, focused):
+ self.focused = focused
+
+ def has_focus(self):
+ return self.focused and not self.hidden()
+
+ @overrides(BaseInputPane)
+ def handle_read(self, c):
+ if c == curses.KEY_UP:
+ self.move_active_up(1)
+ elif c == curses.KEY_DOWN:
+ self.move_active_down(1)
+ elif self.allow_resize and c in [ord('+'), ord('-')]:
+ width = self.visible_content_pane_width + (1 if c == ord('+') else -1)
+ self.on_resize(width)
+ else:
+ return BaseInputPane.handle_read(self, c)
+ return util.ReadState.READ
+
+ def on_resize(self, width):
+ self.resize_window(self.height, width)
+
+ @overrides(BaseWindow)
+ def refresh(self):
+ height = self.get_content_height()
+ self.ensure_content_pane_height(
+ height + self.border_off_north + self.border_off_south
+ )
+ BaseInputPane.render_inputs(self, focused=self.has_focus())
+ BaseWindow.refresh(self)
+
+ def _refresh(self):
+ self.screen.erase()
+ height = self.get_content_height()
+ self.ensure_content_pane_height(
+ height + self.border_off_north + self.border_off_south
+ )
+ BaseInputPane.render_inputs(self, focused=True)
+ BaseWindow.refresh(self)
+
+ def add_string(self, row, string, scr=None, **kwargs):
+ add_string(row, string, self.screen, self.parent.encoding, **kwargs)
diff --git a/deluge/ui/console/widgets/statusbars.py b/deluge/ui/console/widgets/statusbars.py
new file mode 100644
index 0000000..fcf4f2f
--- /dev/null
+++ b/deluge/ui/console/widgets/statusbars.py
@@ -0,0 +1,122 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import deluge.common
+import deluge.component as component
+from deluge.core.preferencesmanager import DEFAULT_PREFS
+from deluge.ui.client import client
+
+
+class StatusBars(component.Component):
+ def __init__(self):
+ component.Component.__init__(self, 'StatusBars', 2, depend=['CoreConfig'])
+ self.config = component.get('CoreConfig')
+
+ # Hold some values we get from the core
+ self.connections = 0
+ self.download = ''
+ self.upload = ''
+ self.dht = 0
+ self.external_ip = ''
+
+ # Default values
+ self.topbar = '{!status!}Deluge %s Console - ' % deluge.common.get_version()
+ self.bottombar = '{!status!}C: %s' % self.connections
+
+ def start(self):
+ self.update()
+
+ def update(self):
+ def on_get_session_status(status):
+ self.upload = deluge.common.fsize(status['payload_upload_rate'])
+ self.download = deluge.common.fsize(status['payload_download_rate'])
+ self.connections = status['num_peers']
+ if 'dht_nodes' in status:
+ self.dht = status['dht_nodes']
+
+ self.update_statusbars()
+
+ def on_get_external_ip(external_ip):
+ self.external_ip = external_ip
+
+ keys = ['num_peers', 'payload_upload_rate', 'payload_download_rate']
+
+ if self.config['dht']:
+ keys.append('dht_nodes')
+
+ client.core.get_session_status(keys).addCallback(on_get_session_status)
+ client.core.get_external_ip().addCallback(on_get_external_ip)
+
+ def update_statusbars(self):
+ # Update the topbar string
+ self.topbar = '{!status!}Deluge %s Console - ' % deluge.common.get_version()
+
+ if client.connected():
+ info = client.connection_info()
+ connection_info = ''
+
+ # Client name
+ if info[2] == 'localclient':
+ connection_info += '{!white,blue!}%s'
+ else:
+ connection_info += '{!green,blue,bold!}%s'
+
+ # Hostname
+ if info[0] == '127.0.0.1':
+ connection_info += '{!white,blue,bold!}@{!white,blue!}%s'
+ else:
+ connection_info += '{!white,blue,bold!}@{!red,blue,bold!}%s'
+
+ # Port
+ if info[1] == DEFAULT_PREFS['daemon_port']:
+ connection_info += '{!white,blue!}:%s'
+ else:
+ connection_info += '{!status!}:%s'
+
+ # Change color back to normal, just in case
+ connection_info += '{!status!}'
+
+ self.topbar += connection_info % (info[2], info[0], info[1])
+ else:
+ self.topbar += 'Not Connected'
+
+ # Update the bottombar string
+ self.bottombar = '{!status!}C: {!white,blue!}%s{!status!}' % self.connections
+
+ if self.config['max_connections_global'] > -1:
+ self.bottombar += ' (%s)' % self.config['max_connections_global']
+
+ if self.download != '0.0 KiB':
+ self.bottombar += ' D: {!magenta,blue,bold!}%s{!status!}' % self.download
+ else:
+ self.bottombar += ' D: {!white,blue!}%s{!status!}' % self.download
+
+ if self.config['max_download_speed'] > -1:
+ self.bottombar += (
+ ' (%s ' % self.config['max_download_speed'] + _('KiB/s') + ')'
+ )
+
+ if self.upload != '0.0 KiB':
+ self.bottombar += ' U: {!green,blue,bold!}%s{!status!}' % self.upload
+ else:
+ self.bottombar += ' U: {!white,blue!}%s{!status!}' % self.upload
+
+ if self.config['max_upload_speed'] > -1:
+ self.bottombar += (
+ ' (%s ' % self.config['max_upload_speed'] + _('KiB/s') + ')'
+ )
+
+ if self.config['dht']:
+ self.bottombar += ' ' + _('DHT') + ': {!white,blue!}%s{!status!}' % self.dht
+
+ self.bottombar += ' ' + _('IP {!white,blue!}%s{!status!}') % (
+ self.external_ip if self.external_ip else _('n/a')
+ )
diff --git a/deluge/ui/console/widgets/window.py b/deluge/ui/console/widgets/window.py
new file mode 100644
index 0000000..2ef3528
--- /dev/null
+++ b/deluge/ui/console/widgets/window.py
@@ -0,0 +1,185 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Nick Lanham <nick@afternight.org>
+# Copyright (C) 2008-2009 Ido Abramovich <ido.deluge@gmail.com>
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+from deluge.ui.console.modes.basemode import add_string, mkpad, mkpanel
+from deluge.ui.console.utils.colors import get_color_pair
+
+try:
+ import curses
+except ImportError:
+ pass
+
+log = logging.getLogger(__name__)
+
+
+class BaseWindow(object):
+ """
+ BaseWindow creates a curses screen to be used for showing panels and popup dialogs
+ """
+
+ def __init__(self, title, width, height, posy=0, posx=0, encoding=None):
+ """
+ Args:
+ title (str): The title of the panel
+ width (int): Width of the panel
+ height (int): Height of the panel
+ posy (int): Position of the panel's first row relative to the terminal screen
+ posx (int): Position of the panel's first column relative to the terminal screen
+ encoding (str): Terminal encoding
+ """
+ self.title = title
+ self.posy, self.posx = posy, posx
+ if encoding is None:
+ from deluge import component
+
+ encoding = component.get('ConsoleUI').encoding
+ self.encoding = encoding
+
+ self.panel = mkpanel(curses.COLOR_GREEN, height, width, posy, posx)
+ self.outer_screen = self.panel.window()
+ self.outer_screen.bkgdset(0, curses.COLOR_RED)
+ by, bx = self.outer_screen.getbegyx()
+ self.screen = mkpad(get_color_pair('white', 'black'), height - 1, width - 2)
+ self._height, self._width = self.outer_screen.getmaxyx()
+
+ @property
+ def height(self):
+ return self._height
+
+ @property
+ def width(self):
+ return self._width
+
+ def add_string(self, row, string, scr=None, **kwargs):
+ scr = scr if scr else self.screen
+ add_string(row, string, scr, self.encoding, **kwargs)
+
+ def hide(self):
+ self.panel.hide()
+
+ def show(self):
+ self.panel.show()
+
+ def hidden(self):
+ return self.panel.hidden()
+
+ def set_title(self, title):
+ self.title = title
+
+ @property
+ def visible_content_pane_size(self):
+ y, x = self.outer_screen.getmaxyx()
+ return (y - 2, x - 2)
+
+ @property
+ def visible_content_pane_height(self):
+ y, x = self.visible_content_pane_size
+ return y
+
+ @property
+ def visible_content_pane_width(self):
+ y, x = self.visible_content_pane_size
+ return x
+
+ def getmaxyx(self):
+ return self.screen.getmaxyx()
+
+ def resize_window(self, rows, cols):
+ self.outer_screen.resize(rows, cols)
+ self.screen.resize(rows - 2, cols - 2)
+ self._height, self._width = rows, cols
+
+ def move_window(self, posy, posx):
+ posy = int(posy)
+ posx = int(posx)
+ self.outer_screen.mvwin(posy, posx)
+ self.posy = posy
+ self.posx = posx
+ self._height, self._width = self.screen.getmaxyx()
+
+ def ensure_content_pane_height(self, height):
+ max_y, max_x = self.screen.getmaxyx()
+ if max_y < height:
+ self.screen.resize(height, max_x)
+
+ def draw_scroll_indicator(self, screen):
+ content_height = self.get_content_height()
+ if content_height <= self.visible_content_pane_height:
+ return
+
+ percent_scroll = float(self.lineoff) / (
+ content_height - self.visible_content_pane_height
+ )
+ indicator_row = int(self.visible_content_pane_height * percent_scroll) + 1
+
+ # Never greater than height
+ indicator_row = min(indicator_row, self.visible_content_pane_height)
+ indicator_col = self.width + 1
+
+ add_string(
+ indicator_row,
+ '{!red,black,bold!}#',
+ screen,
+ self.encoding,
+ col=indicator_col,
+ pad=False,
+ trim=False,
+ )
+
+ def refresh(self):
+ height, width = self.visible_content_pane_size
+ self.outer_screen.erase()
+ self.outer_screen.border(0, 0, 0, 0)
+
+ if self.title:
+ toff = max(1, (self.width // 2) - (len(self.title) // 2))
+ self.add_string(
+ 0,
+ '{!white,black,bold!}%s' % self.title,
+ scr=self.outer_screen,
+ col=toff,
+ pad=False,
+ )
+
+ self.draw_scroll_indicator(self.outer_screen)
+ self.outer_screen.noutrefresh()
+
+ try:
+ # pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol
+ # the p arguments refer to the upper left corner of the pad region to be displayed and
+ # the s arguments define a clipping box on the screen within which the pad region is to be displayed.
+ pminrow = self.lineoff
+ pmincol = 0
+ sminrow = self.posy + 1
+ smincol = self.posx + 1
+ smaxrow = height + self.posy
+ smaxcol = width + self.posx
+ self.screen.noutrefresh(
+ pminrow, pmincol, sminrow, smincol, smaxrow, smaxcol
+ )
+ except curses.error as ex:
+ import traceback
+
+ log.warning(
+ 'Error on screen.noutrefresh(%s, %s, %s, %s, %s, %s) Error: %s\nStack: %s',
+ pminrow,
+ pmincol,
+ sminrow,
+ smincol,
+ smaxrow,
+ smaxcol,
+ ex,
+ ''.join(traceback.format_stack()),
+ )
diff --git a/deluge/ui/coreconfig.py b/deluge/ui/coreconfig.py
new file mode 100644
index 0000000..ed6b614
--- /dev/null
+++ b/deluge/ui/coreconfig.py
@@ -0,0 +1,54 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+import deluge.component as component
+from deluge.ui.client import client
+
+log = logging.getLogger(__name__)
+
+
+class CoreConfig(component.Component):
+ def __init__(self):
+ log.debug('CoreConfig init..')
+ component.Component.__init__(self, 'CoreConfig')
+ self.config = {}
+
+ def on_configvaluechanged_event(key, value):
+ self.config[key] = value
+
+ client.register_event_handler(
+ 'ConfigValueChangedEvent', on_configvaluechanged_event
+ )
+
+ def start(self):
+ def on_get_config(config):
+ self.config = config
+ return config
+
+ return client.core.get_config().addCallback(on_get_config)
+
+ def stop(self):
+ self.config = {}
+
+ def __contains__(self, key):
+ return key in self.config
+
+ def __getitem__(self, key):
+ return self.config[key]
+
+ def __setitem__(self, key, value):
+ client.core.set_config({key: value})
+
+ def __getattr__(self, attr):
+ # We treat this directly interacting with the dictionary
+ return getattr(self.config, attr)
diff --git a/deluge/ui/countries.py b/deluge/ui/countries.py
new file mode 100644
index 0000000..fe17da1
--- /dev/null
+++ b/deluge/ui/countries.py
@@ -0,0 +1,256 @@
+# -*- coding: utf-8 -*-
+#
+# This file is public domain.
+#
+
+from __future__ import unicode_literals
+
+# ISO 3166-1 country names and codes
+COUNTRIES = {
+ 'AF': _('Afghanistan'),
+ 'AX': _('Aland Islands'),
+ 'AL': _('Albania'),
+ 'DZ': _('Algeria'),
+ 'AS': _('American Samoa'),
+ 'AD': _('Andorra'),
+ 'AO': _('Angola'),
+ 'AI': _('Anguilla'),
+ 'AQ': _('Antarctica'),
+ 'AG': _('Antigua and Barbuda'),
+ 'AR': _('Argentina'),
+ 'AM': _('Armenia'),
+ 'AW': _('Aruba'),
+ 'AU': _('Australia'),
+ 'AT': _('Austria'),
+ 'AZ': _('Azerbaijan'),
+ 'BS': _('Bahamas'),
+ 'BH': _('Bahrain'),
+ 'BD': _('Bangladesh'),
+ 'BB': _('Barbados'),
+ 'BY': _('Belarus'),
+ 'BE': _('Belgium'),
+ 'BZ': _('Belize'),
+ 'BJ': _('Benin'),
+ 'BM': _('Bermuda'),
+ 'BT': _('Bhutan'),
+ 'BO': _('Bolivia'),
+ 'BA': _('Bosnia and Herzegovina'),
+ 'BW': _('Botswana'),
+ 'BV': _('Bouvet Island'),
+ 'BR': _('Brazil'),
+ 'IO': _('British Indian Ocean Territory'),
+ 'BN': _('Brunei Darussalam'),
+ 'BG': _('Bulgaria'),
+ 'BF': _('Burkina Faso'),
+ 'BI': _('Burundi'),
+ 'KH': _('Cambodia'),
+ 'CM': _('Cameroon'),
+ 'CA': _('Canada'),
+ 'CV': _('Cape Verde'),
+ 'KY': _('Cayman Islands'),
+ 'CF': _('Central African Republic'),
+ 'TD': _('Chad'),
+ 'CL': _('Chile'),
+ 'CN': _('China'),
+ 'CX': _('Christmas Island'),
+ 'CC': _('Cocos (Keeling) Islands'),
+ 'CO': _('Colombia'),
+ 'KM': _('Comoros'),
+ 'CG': _('Congo'),
+ 'CD': _('Congo, The Democratic Republic of the'),
+ 'CK': _('Cook Islands'),
+ 'CR': _('Costa Rica'),
+ 'CI': _('Cote d\'Ivoire'),
+ 'HR': _('Croatia'),
+ 'CU': _('Cuba'),
+ 'CY': _('Cyprus'),
+ 'CZ': _('Czech Republic'),
+ 'DK': _('Denmark'),
+ 'DJ': _('Djibouti'),
+ 'DM': _('Dominica'),
+ 'DO': _('Dominican Republic'),
+ 'EC': _('Ecuador'),
+ 'EG': _('Egypt'),
+ 'SV': _('El Salvador'),
+ 'GQ': _('Equatorial Guinea'),
+ 'ER': _('Eritrea'),
+ 'EE': _('Estonia'),
+ 'ET': _('Ethiopia'),
+ 'FK': _('Falkland Islands (Malvinas)'),
+ 'FO': _('Faroe Islands'),
+ 'FJ': _('Fiji'),
+ 'FI': _('Finland'),
+ 'FR': _('France'),
+ 'GF': _('French Guiana'),
+ 'PF': _('French Polynesia'),
+ 'TF': _('French Southern Territories'),
+ 'GA': _('Gabon'),
+ 'GM': _('Gambia'),
+ 'GE': _('Georgia'),
+ 'DE': _('Germany'),
+ 'GH': _('Ghana'),
+ 'GI': _('Gibraltar'),
+ 'GR': _('Greece'),
+ 'GL': _('Greenland'),
+ 'GD': _('Grenada'),
+ 'GP': _('Guadeloupe'),
+ 'GU': _('Guam'),
+ 'GT': _('Guatemala'),
+ 'GG': _('Guernsey'),
+ 'GN': _('Guinea'),
+ 'GW': _('Guinea-Bissau'),
+ 'GY': _('Guyana'),
+ 'HT': _('Haiti'),
+ 'HM': _('Heard Island and McDonald Islands'),
+ 'VA': _('Holy See (Vatican City State)'),
+ 'HN': _('Honduras'),
+ 'HK': _('Hong Kong'),
+ 'HU': _('Hungary'),
+ 'IS': _('Iceland'),
+ 'IN': _('India'),
+ 'ID': _('Indonesia'),
+ 'IR': _('Iran, Islamic Republic of'),
+ 'IQ': _('Iraq'),
+ 'IE': _('Ireland'),
+ 'IM': _('Isle of Man'),
+ 'IL': _('Israel'),
+ 'IT': _('Italy'),
+ 'JM': _('Jamaica'),
+ 'JP': _('Japan'),
+ 'JE': _('Jersey'),
+ 'JO': _('Jordan'),
+ 'KZ': _('Kazakhstan'),
+ 'KE': _('Kenya'),
+ 'KI': _('Kiribati'),
+ 'KP': _('Korea, Democratic People\'s Republic of'),
+ 'KR': _('Korea, Republic of'),
+ 'KW': _('Kuwait'),
+ 'KG': _('Kyrgyzstan'),
+ 'LA': _('Lao People\'s Democratic Republic'),
+ 'LV': _('Latvia'),
+ 'LB': _('Lebanon'),
+ 'LS': _('Lesotho'),
+ 'LR': _('Liberia'),
+ 'LY': _('Libyan Arab Jamahiriya'),
+ 'LI': _('Liechtenstein'),
+ 'LT': _('Lithuania'),
+ 'LU': _('Luxembourg'),
+ 'MO': _('Macao'),
+ 'MK': _('Macedonia, The Former Yugoslav Republic of'),
+ 'MG': _('Madagascar'),
+ 'MW': _('Malawi'),
+ 'MY': _('Malaysia'),
+ 'MV': _('Maldives'),
+ 'ML': _('Mali'),
+ 'MT': _('Malta'),
+ 'MH': _('Marshall Islands'),
+ 'MQ': _('Martinique'),
+ 'MR': _('Mauritania'),
+ 'MU': _('Mauritius'),
+ 'YT': _('Mayotte'),
+ 'MX': _('Mexico'),
+ 'FM': _('Micronesia, Federated States of'),
+ 'MD': _('Moldova'),
+ 'MC': _('Monaco'),
+ 'MN': _('Mongolia'),
+ 'ME': _('Montenegro'),
+ 'MS': _('Montserrat'),
+ 'MA': _('Morocco'),
+ 'MZ': _('Mozambique'),
+ 'MM': _('Myanmar'),
+ 'NA': _('Namibia'),
+ 'NR': _('Nauru'),
+ 'NP': _('Nepal'),
+ 'NL': _('Netherlands'),
+ 'AN': _('Netherlands Antilles'),
+ 'NC': _('New Caledonia'),
+ 'NZ': _('New Zealand'),
+ 'NI': _('Nicaragua'),
+ 'NE': _('Niger'),
+ 'NG': _('Nigeria'),
+ 'NU': _('Niue'),
+ 'NF': _('Norfolk Island'),
+ 'MP': _('Northern Mariana Islands'),
+ 'NO': _('Norway'),
+ 'OM': _('Oman'),
+ 'PK': _('Pakistan'),
+ 'PW': _('Palau'),
+ 'PS': _('Palestinian Territory, Occupied'),
+ 'PA': _('Panama'),
+ 'PG': _('Papua New Guinea'),
+ 'PY': _('Paraguay'),
+ 'PE': _('Peru'),
+ 'PH': _('Philippines'),
+ 'PN': _('Pitcairn'),
+ 'PL': _('Poland'),
+ 'PT': _('Portugal'),
+ 'PR': _('Puerto Rico'),
+ 'QA': _('Qatar'),
+ 'RE': _('Reunion'),
+ 'RO': _('Romania'),
+ 'RU': _('Russian Federation'),
+ 'RW': _('Rwanda'),
+ 'BL': _('Saint Barthelemy'),
+ 'SH': _('Saint Helena'),
+ 'KN': _('Saint Kitts and Nevis'),
+ 'LC': _('Saint Lucia'),
+ 'MF': _('Saint Martin'),
+ 'PM': _('Saint Pierre and Miquelon'),
+ 'VC': _('Saint Vincent and the Grenadines'),
+ 'WS': _('Samoa'),
+ 'SM': _('San Marino'),
+ 'ST': _('Sao Tome and Principe'),
+ 'SA': _('Saudi Arabia'),
+ 'SN': _('Senegal'),
+ 'RS': _('Serbia'),
+ 'SC': _('Seychelles'),
+ 'SL': _('Sierra Leone'),
+ 'SG': _('Singapore'),
+ 'SK': _('Slovakia'),
+ 'SI': _('Slovenia'),
+ 'SB': _('Solomon Islands'),
+ 'SO': _('Somalia'),
+ 'ZA': _('South Africa'),
+ 'GS': _('South Georgia and the South Sandwich Islands'),
+ 'ES': _('Spain'),
+ 'LK': _('Sri Lanka'),
+ 'SD': _('Sudan'),
+ 'SR': _('Suriname'),
+ 'SJ': _('Svalbard and Jan Mayen'),
+ 'SZ': _('Swaziland'),
+ 'SE': _('Sweden'),
+ 'CH': _('Switzerland'),
+ 'SY': _('Syrian Arab Republic'),
+ 'TW': _('Taiwan'),
+ 'TJ': _('Tajikistan'),
+ 'TZ': _('Tanzania, United Republic of'),
+ 'TH': _('Thailand'),
+ 'TL': _('Timor-Leste'),
+ 'TG': _('Togo'),
+ 'TK': _('Tokelau'),
+ 'TO': _('Tonga'),
+ 'TT': _('Trinidad and Tobago'),
+ 'TN': _('Tunisia'),
+ 'TR': _('Turkey'),
+ 'TM': _('Turkmenistan'),
+ 'TC': _('Turks and Caicos Islands'),
+ 'TV': _('Tuvalu'),
+ 'UG': _('Uganda'),
+ 'UA': _('Ukraine'),
+ 'AE': _('United Arab Emirates'),
+ 'GB': _('United Kingdom'),
+ 'US': _('United States'),
+ 'UM': _('United States Minor Outlying Islands'),
+ 'UY': _('Uruguay'),
+ 'UZ': _('Uzbekistan'),
+ 'VU': _('Vanuatu'),
+ 'VE': _('Venezuela'),
+ 'VN': _('Viet Nam'),
+ 'VG': _('Virgin Islands, British'),
+ 'VI': _('Virgin Islands, U.S.'),
+ 'WF': _('Wallis and Futuna'),
+ 'EH': _('Western Sahara'),
+ 'YE': _('Yemen'),
+ 'ZM': _('Zambia'),
+ 'ZW': _('Zimbabwe'),
+}
diff --git a/deluge/ui/data/__pycache__/__init__.cpython-37.pyc b/deluge/ui/data/__pycache__/__init__.cpython-37.pyc
new file mode 100644
index 0000000..498f846
--- /dev/null
+++ b/deluge/ui/data/__pycache__/__init__.cpython-37.pyc
Binary files differ
diff --git a/deluge/ui/data/icons/hicolor/128x128/apps/deluge.png b/deluge/ui/data/icons/hicolor/128x128/apps/deluge.png
new file mode 100644
index 0000000..48fcc47
--- /dev/null
+++ b/deluge/ui/data/icons/hicolor/128x128/apps/deluge.png
Binary files differ
diff --git a/deluge/ui/data/icons/hicolor/16x16/apps/deluge-panel.png b/deluge/ui/data/icons/hicolor/16x16/apps/deluge-panel.png
new file mode 100644
index 0000000..2f4ae4c
--- /dev/null
+++ b/deluge/ui/data/icons/hicolor/16x16/apps/deluge-panel.png
Binary files differ
diff --git a/deluge/ui/data/icons/hicolor/16x16/apps/deluge.png b/deluge/ui/data/icons/hicolor/16x16/apps/deluge.png
new file mode 100644
index 0000000..2f4ae4c
--- /dev/null
+++ b/deluge/ui/data/icons/hicolor/16x16/apps/deluge.png
Binary files differ
diff --git a/deluge/ui/data/icons/hicolor/192x192/apps/deluge.png b/deluge/ui/data/icons/hicolor/192x192/apps/deluge.png
new file mode 100644
index 0000000..5d54ea4
--- /dev/null
+++ b/deluge/ui/data/icons/hicolor/192x192/apps/deluge.png
Binary files differ
diff --git a/deluge/ui/data/icons/hicolor/22x22/apps/deluge-panel.png b/deluge/ui/data/icons/hicolor/22x22/apps/deluge-panel.png
new file mode 100644
index 0000000..13fe852
--- /dev/null
+++ b/deluge/ui/data/icons/hicolor/22x22/apps/deluge-panel.png
Binary files differ
diff --git a/deluge/ui/data/icons/hicolor/22x22/apps/deluge.png b/deluge/ui/data/icons/hicolor/22x22/apps/deluge.png
new file mode 100644
index 0000000..13fe852
--- /dev/null
+++ b/deluge/ui/data/icons/hicolor/22x22/apps/deluge.png
Binary files differ
diff --git a/deluge/ui/data/icons/hicolor/24x24/apps/deluge-panel.png b/deluge/ui/data/icons/hicolor/24x24/apps/deluge-panel.png
new file mode 100644
index 0000000..3a345eb
--- /dev/null
+++ b/deluge/ui/data/icons/hicolor/24x24/apps/deluge-panel.png
Binary files differ
diff --git a/deluge/ui/data/icons/hicolor/24x24/apps/deluge.png b/deluge/ui/data/icons/hicolor/24x24/apps/deluge.png
new file mode 100644
index 0000000..3a345eb
--- /dev/null
+++ b/deluge/ui/data/icons/hicolor/24x24/apps/deluge.png
Binary files differ
diff --git a/deluge/ui/data/icons/hicolor/256x256/apps/deluge.png b/deluge/ui/data/icons/hicolor/256x256/apps/deluge.png
new file mode 100644
index 0000000..ee5d290
--- /dev/null
+++ b/deluge/ui/data/icons/hicolor/256x256/apps/deluge.png
Binary files differ
diff --git a/deluge/ui/data/icons/hicolor/32x32/apps/deluge.png b/deluge/ui/data/icons/hicolor/32x32/apps/deluge.png
new file mode 100644
index 0000000..6787fa3
--- /dev/null
+++ b/deluge/ui/data/icons/hicolor/32x32/apps/deluge.png
Binary files differ
diff --git a/deluge/ui/data/icons/hicolor/36x36/apps/deluge.png b/deluge/ui/data/icons/hicolor/36x36/apps/deluge.png
new file mode 100644
index 0000000..4050041
--- /dev/null
+++ b/deluge/ui/data/icons/hicolor/36x36/apps/deluge.png
Binary files differ
diff --git a/deluge/ui/data/icons/hicolor/48x48/apps/deluge.png b/deluge/ui/data/icons/hicolor/48x48/apps/deluge.png
new file mode 100644
index 0000000..7b067ac
--- /dev/null
+++ b/deluge/ui/data/icons/hicolor/48x48/apps/deluge.png
Binary files differ
diff --git a/deluge/ui/data/icons/hicolor/512x512/apps/deluge.png b/deluge/ui/data/icons/hicolor/512x512/apps/deluge.png
new file mode 100644
index 0000000..70cd91a
--- /dev/null
+++ b/deluge/ui/data/icons/hicolor/512x512/apps/deluge.png
Binary files differ
diff --git a/deluge/ui/data/icons/hicolor/64x64/apps/deluge.png b/deluge/ui/data/icons/hicolor/64x64/apps/deluge.png
new file mode 100644
index 0000000..4275563
--- /dev/null
+++ b/deluge/ui/data/icons/hicolor/64x64/apps/deluge.png
Binary files differ
diff --git a/deluge/ui/data/icons/hicolor/72x72/apps/deluge.png b/deluge/ui/data/icons/hicolor/72x72/apps/deluge.png
new file mode 100644
index 0000000..7ba0efb
--- /dev/null
+++ b/deluge/ui/data/icons/hicolor/72x72/apps/deluge.png
Binary files differ
diff --git a/deluge/ui/data/icons/hicolor/96x96/apps/deluge.png b/deluge/ui/data/icons/hicolor/96x96/apps/deluge.png
new file mode 100644
index 0000000..2c64ec8
--- /dev/null
+++ b/deluge/ui/data/icons/hicolor/96x96/apps/deluge.png
Binary files differ
diff --git a/deluge/ui/data/icons/hicolor/scalable/apps/deluge.svg b/deluge/ui/data/icons/hicolor/scalable/apps/deluge.svg
new file mode 100644
index 0000000..4f29f73
--- /dev/null
+++ b/deluge/ui/data/icons/hicolor/scalable/apps/deluge.svg
@@ -0,0 +1,610 @@
+<?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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg3440"
+ sodipodi:version="0.32"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ sodipodi:docname="deluge.svg"
+ inkscape:export-xdpi="32"
+ inkscape:export-ydpi="32"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="TRUE"
+ version="1.1"
+ inkscape:export-filename="/home/calum/Desktop/test1b.png">
+ <defs
+ id="defs3">
+ <color-profile
+ name="sRGB"
+ xlink:href="/usr/share/color/icc/sRGB.icc"
+ id="color-profile2" />
+ <linearGradient
+ id="linearGradient4345"
+ inkscape:collect="always">
+ <stop
+ id="stop4347"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1" />
+ <stop
+ id="stop4349"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2973">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop2975" />
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:0;"
+ offset="1"
+ id="stop2977" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4126">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop4128" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.16494845;"
+ offset="1.0000000"
+ id="stop4130" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4114">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4116" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop4118" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3962">
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop3964" />
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.15517241"
+ id="stop4134" />
+ <stop
+ style="stop-color:#4074ae;stop-opacity:1.0000000;"
+ offset="0.75000000"
+ id="stop4346" />
+ <stop
+ style="stop-color:#36486c;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop3966" />
+ </linearGradient>
+ <radialGradient
+ r="13.994944"
+ fy="33.506763"
+ fx="-10.089286"
+ cy="33.506763"
+ cx="-10.089286"
+ gradientTransform="matrix(1,0,0,0.791446,-14.01786,-11.28667)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4019"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ gradientTransform="matrix(1,0,0,0.792374,-19.58761,2.818569)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4004"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.792374,0,6.785475)"
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ id="radialGradient3999"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.341185,-0.153831,1.08001,2.395374,-15.42222,-25.62103)"
+ r="13.994946"
+ fy="24.241488"
+ fx="61.662098"
+ cy="24.241488"
+ cx="61.662098"
+ id="radialGradient3943"
+ xlink:href="#linearGradient1312"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient1312">
+ <stop
+ id="stop1314"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop1316"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3993">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3995" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0"
+ offset="1"
+ id="stop3997" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2973"
+ id="radialGradient3866"
+ cx="-22.375"
+ cy="18.499998"
+ fx="-22.375"
+ fy="18.499998"
+ r="14.33462"
+ gradientTransform="matrix(1,0,0,1.140022,40.17678,1.347091)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ r="12.289036"
+ fy="63.965388"
+ fx="15.115514"
+ cy="63.965388"
+ cx="15.115514"
+ gradientTransform="scale(1.643990,0.608276)"
+ id="radialGradient5000"
+ xlink:href="#linearGradient4114"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4989">
+ <stop
+ id="stop4991"
+ offset="0"
+ style="stop-color:#47abff;stop-opacity:1" />
+ <stop
+ id="stop4993"
+ offset="0.35955963"
+ style="stop-color:#53a6ff;stop-opacity:1" />
+ <stop
+ id="stop4995"
+ offset="0.79518169"
+ style="stop-color:#286cbb;stop-opacity:1" />
+ <stop
+ id="stop4997"
+ offset="1"
+ style="stop-color:#003d87;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4977">
+ <stop
+ id="stop4979"
+ offset="0.0000000"
+ style="stop-color:#ffffff;stop-opacity:1" />
+ <stop
+ id="stop4981"
+ offset="1.0000000"
+ style="stop-color:#ffffff;stop-opacity:0" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4345"
+ id="radialGradient5878"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.56833945,-0.14774444,0.12240592,0.69984003,9.8157812,6.7018745)"
+ cx="11.707551"
+ cy="36.527763"
+ fx="11.707551"
+ fy="36.527763"
+ r="14.33681" />
+ <filter
+ id="filter6406"
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ height="1.2"
+ width="1.2"
+ y="-0.15000000000000002"
+ x="-0.050000000000000017">
+ <feFlood
+ id="feFlood6408"
+ flood-opacity="0.50171821305841924"
+ flood-color="rgb(50,51,108)"
+ result="flood"
+ stdDeviation="0.01" />
+ <feComposite
+ id="feComposite6410"
+ in2="SourceGraphic"
+ in="flood"
+ operator="in"
+ result="composite1"
+ k4="-7.2400000000000002"
+ k3="5.5899999999999999"
+ k2="4.9299999999999997"
+ k1="8.1400000000000006" />
+ <feGaussianBlur
+ id="feGaussianBlur6412"
+ stdDeviation="0.20000000000000001"
+ result="blur"
+ in="composite" />
+ <feOffset
+ id="feOffset6414"
+ dx="0.29999999999999999"
+ dy="0.29999999999999999"
+ result="offset"
+ flood-opacity="0.5" />
+ <feComposite
+ id="feComposite6416"
+ in2="offset"
+ in="SourceGraphic"
+ result="composite2"
+ operator="" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ id="filter4258">
+ <feFlood
+ flood-opacity="0.80200501253132828"
+ flood-color="rgb(197,204,222)"
+ result="flood"
+ id="feFlood4260" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="in"
+ result="composite1"
+ id="feComposite4262"
+ flood-opacity="0.80000000000000004" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4264" />
+ <feOffset
+ dx="-0"
+ dy="-0.5"
+ result="offset"
+ id="feOffset4266" />
+ <feComposite
+ in="SourceGraphic"
+ in2="offset"
+ operator="over"
+ result="composite2"
+ id="feComposite4268" />
+ </filter>
+ <filter
+ id="filter4290"
+ inkscape:label="Drop Shadow"
+ style="color-interpolation-filters:sRGB;">
+ <feFlood
+ id="feFlood4292"
+ result="flood"
+ flood-color="rgb(255,255,255)"
+ flood-opacity="0.59999999999999998" />
+ <feComposite
+ id="feComposite4294"
+ result="composite1"
+ operator="in"
+ in2="SourceGraphic"
+ in="flood" />
+ <feGaussianBlur
+ id="feGaussianBlur4296"
+ result="blur"
+ stdDeviation="6.7999999999999998"
+ in="composite1" />
+ <feOffset
+ id="feOffset4298"
+ result="offset"
+ dy="1.9000000000000004"
+ dx="0.69999999999999996"
+ flood-opacity="0.68000000000000005" />
+ <feComposite
+ id="feComposite4300"
+ result="composite2"
+ in2="offset"
+ in="SourceGraphic"
+ operator="" />
+ </filter>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4977"
+ id="radialGradient4375"
+ cx="19.015932"
+ cy="19.611181"
+ fx="19.015932"
+ fy="19.611181"
+ r="15.627373"
+ gradientTransform="matrix(-0.21582873,1.1173231,-1.0803619,-0.20155681,42.177326,-6.1234)"
+ gradientUnits="userSpaceOnUse" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="0.65098039"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="13.92"
+ inkscape:cx="3.3184766"
+ inkscape:cy="26.850801"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1981"
+ inkscape:window-height="1097"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:showpageshadow="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-maximized="1"
+ width="512px"
+ units="px"
+ inkscape:snap-global="false"
+ inkscape:snap-to-guides="true"
+ inkscape:snap-others="true"
+ inkscape:pagecheckerboard="false"
+ showborder="true"
+ borderlayer="false"
+ scale-x="2">
+ <sodipodi:guide
+ orientation="0,1"
+ position="4.331,47"
+ id="guide3056"
+ inkscape:label=""
+ inkscape:color="rgb(99,99,125)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="24,41.875"
+ id="guide3058"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="40,25.375"
+ id="guide5866" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="34.626135,29.742679"
+ id="guide5868" />
+ <sodipodi:guide
+ position="3.8890873,1"
+ orientation="0,1"
+ id="guide4521"
+ inkscape:label=""
+ inkscape:color="rgb(106,106,146)" />
+ <sodipodi:guide
+ position="1,17.324"
+ orientation="1,0"
+ id="guide4523"
+ inkscape:label=""
+ inkscape:color="rgb(152,152,181)" />
+ <sodipodi:guide
+ position="47,33.852737"
+ orientation="1,0"
+ id="guide4525"
+ inkscape:label=""
+ inkscape:color="rgb(121,121,172)" />
+ <sodipodi:guide
+ position="13.433908,46"
+ orientation="0,1"
+ id="guide4527"
+ inkscape:label=""
+ inkscape:color="rgb(255,40,0)" />
+ <sodipodi:guide
+ position="8,36.135"
+ orientation="1,0"
+ id="guide4529"
+ inkscape:label=""
+ inkscape:color="rgb(255,11,0)" />
+ <sodipodi:guide
+ position="40,26.436782"
+ orientation="1,0"
+ id="guide4531"
+ inkscape:label=""
+ inkscape:color="rgb(255,6,0)" />
+ <sodipodi:guide
+ position="34.052,2"
+ orientation="0,1"
+ id="guide4533"
+ inkscape:label=""
+ inkscape:color="rgb(255,0,5)" />
+ <sodipodi:guide
+ position="36.879276,26"
+ orientation="0,1"
+ id="guide4542"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="43.660795,14.731391"
+ orientation="0,1"
+ id="guide4546" />
+ <inkscape:grid
+ type="xygrid"
+ id="grid4297" />
+ </sodipodi:namedview>
+ <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:title></dc:title>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Tuomas Kuosmanen</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
+ <dc:source>http://jimmac.musichall.cz</dc:source>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>internet</rdf:li>
+ <rdf:li>tools</rdf:li>
+ <rdf:li>applications</rdf:li>
+ <rdf:li>category</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Attribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ style="display:inline">
+ <path
+ style="fill:#4c90e8;fill-opacity:1;fill-rule:evenodd;stroke:#094491;stroke-width:3.19036889;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 24.00916,2.8848679 35.921225,21.107868 c 7.631218,11.674181 -0.393962,24.296949 -11.919555,24.296949 -11.525593,0 -19.5637412,-12.627213 -11.925606,-24.293557 z"
+ id="path2069"
+ sodipodi:nodetypes="cszsc"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="wave fill"
+ style="display:inline">
+ <path
+ style="display:inline;fill:#094491;fill-opacity:1;fill-rule:evenodd;stroke-width:0.43658349;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 28.249173,24.076764 c -6.513399,-2.656088 -9.930764,4.129513 -8.844266,7.299777 2.363892,6.897492 11.278562,8.624272 17.861607,-2.864877 0,0 0.07021,1.386537 0.139797,2.046235 0.884013,8.376987 -6.162376,13.70953 -13.302295,13.605305 -7.139921,-0.104225 -9.320813,-2.876749 -11.424685,-6.068739 -3.4133569,-5.178732 -2.561398,-13.615464 2.027677,-17.561337 5.237731,-4.328395 11.004287,-2.017675 13.542165,3.543636 z"
+ id="path2969"
+ sodipodi:nodetypes="cscszscc"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ style="display:none"
+ inkscape:label="wave fill light"
+ id="g4327"
+ inkscape:groupmode="layer">
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cscszscc"
+ id="path4329"
+ d="m 24.811452,24.996951 c -6.049549,-2.467385 -7.648816,3.683738 -6.639688,6.628769 2.195546,6.407462 11.948502,7.605174 18.062738,-3.067723 0,0 0.11598,1.28803 0.180639,1.900859 0.82106,7.781844 -5.621926,12.735535 -12.253379,12.638715 -6.631454,-0.09682 -8.657036,-2.672368 -10.611083,-5.637583 -3.170275,-4.810809 -2.429788,-11.327408 1.78168,-14.840553 4.864733,-3.959831 8.798281,-0.09585 9.479093,2.377516 z"
+ style="display:inline;fill:#99c3f9;fill-opacity:1;fill-rule:evenodd;stroke-width:0.40552941;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer6"
+ inkscape:label="wave stroke 1.3px and fill "
+ style="display:none">
+ <path
+ style="display:inline;fill:#99c3f9;fill-opacity:1;fill-rule:evenodd;stroke:#094491 icc-color(sRGB, 0.18046845, 0.27837034, 0.41960784);stroke-width:1.33399999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 23.761543,24.179576 c -6.481311,-1.72424 -6.932125,6.183803 -5.453851,8.969604 2.408248,4.538333 12.206251,5.377175 17.706883,-3.282742 0,0 0.07651,2.193563 0.02062,2.77892 -0.534334,5.596549 -6.848986,9.789262 -11.999526,9.621298 -5.243605,-0.170999 -8.282469,-2.549917 -10.154056,-5.379263 -3.036492,-4.590373 -2.048612,-10.811985 1.554101,-13.80496 4.248899,-3.646516 7.44994,-0.562347 8.325827,1.097143 z"
+ id="path2969-6"
+ sodipodi:nodetypes="cscssscc"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ style="display:none"
+ inkscape:label="wave stroke 2px and fill "
+ id="g4217"
+ inkscape:groupmode="layer">
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cscssscc"
+ id="path4219"
+ d="m 23.507553,23.925586 c -6.495438,-1.064032 -6.790118,6.421446 -5.149063,9.579179 2.304561,4.434459 10.903063,6.282759 17.3005,-2.825561 0,0 -0.02509,1.990372 -0.08098,2.575729 -0.534334,5.596549 -6.391805,8.925697 -11.542345,8.757733 -5.243605,-0.170999 -8.247979,-2.119778 -10.001662,-5.023677 -2.772156,-4.590373 -2.467304,-9.592836 0.487345,-12.585811 3.100966,-3.879425 7.831125,-2.714919 8.986201,-0.477592 z"
+ style="display:inline;fill:#99c3f9;fill-opacity:1;fill-rule:evenodd;stroke:#094491 icc-color(sRGB, 0.18046845, 0.27837034, 0.41960784);stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="gradient inner wave "
+ style="display:none">
+ <path
+ style="fill:url(#radialGradient5878);fill-opacity:1;fill-rule:evenodd;stroke:none"
+ d="m 23.55546,23.805666 c -6.490966,-1.345852 -7.700279,4.84717 -6.759909,8.059555 1.100923,3.760845 4.033145,7.507508 11.754513,5.50458 -3.268904,5.846892 -11.255492,4.570412 -13.940353,0.387138 -2.563055,-3.993493 -2.992402,-9.05746 -1.115519,-11.776277 4.049097,-6.655711 9.485026,-4.029293 10.061268,-2.174996 z"
+ id="path3868"
+ sodipodi:nodetypes="cscscc"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ style="display:inline"
+ inkscape:label="solid inner wavelet small"
+ id="g4476"
+ inkscape:groupmode="layer">
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cscszc"
+ id="path4478"
+ d="m 24.158349,21.568695 c -7.295351,0.177796 -8.680227,8.584045 -6.659444,12.185022 2.809336,5.006167 7.071815,5.968946 12.90485,4.121032 -3.330593,3.620896 -10.166138,4.579185 -14.809086,-0.592946 -3.194749,-3.558875 -3.473998,-8.525976 -1.256883,-12.674161 2.217115,-4.148189 6.638012,-5.301549 9.820563,-3.038947 z"
+ style="fill:#83b8f9;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.07657671" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="g4486"
+ inkscape:label="solid inner wavelet larger"
+ style="display:none">
+ <path
+ style="fill:#99c3f9;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ d="m 21.55277,22.974939 c -5.188765,1.005551 -6.173294,6.973638 -4.694095,10.155871 2.191227,4.714034 8.449957,8.07696 17.664862,0.691234 -5.828901,10.827975 -14.873199,7.043001 -17.313798,4.977307 -1.97159,-1.66873 -4.164846,-4.855384 -4.11832,-8.341257 0.09522,-7.134172 6.604293,-9.266569 8.461351,-7.483155 z"
+ id="path4488"
+ sodipodi:nodetypes="cscssc"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Finish"
+ style="display:none"
+ sodipodi:insensitive="true">
+ <path
+ style="display:inline;opacity:0.68800001;fill:url(#radialGradient4375);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.01508224;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 23.956644,1.939572 -11.98159,18.384686 c -3.9618093,5.166305 -3.6931007,11.439356 -2.4054307,15.8855 5.3079527,10.817522 14.5668877,9.659123 14.5668877,9.659123 0,0 6.868403,-0.005 11.475831,-5.120874 2.283123,-2.535051 5.325726,-9.801674 2.662706,-16.234991 -0.49634,-1.028711 -3.75284,-6.551522 -4.429455,-7.554788 z"
+ id="path4279"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccsccc" />
+ </g>
+</svg>
diff --git a/deluge/ui/data/pixmaps/__pycache__/__init__.cpython-37.pyc b/deluge/ui/data/pixmaps/__pycache__/__init__.cpython-37.pyc
new file mode 100644
index 0000000..e07f647
--- /dev/null
+++ b/deluge/ui/data/pixmaps/__pycache__/__init__.cpython-37.pyc
Binary files differ
diff --git a/deluge/ui/data/pixmaps/active.svg b/deluge/ui/data/pixmaps/active.svg
new file mode 100644
index 0000000..2e6116a
--- /dev/null
+++ b/deluge/ui/data/pixmaps/active.svg
@@ -0,0 +1,612 @@
+<?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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg3440"
+ sodipodi:version="0.32"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="active.svg"
+ inkscape:export-xdpi="30"
+ inkscape:export-ydpi="30"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="TRUE"
+ version="1.1"
+ inkscape:export-filename="/home/calum/projects/deluge-logo/deluge_download16svg.png">
+ <defs
+ id="defs3">
+ <color-profile
+ name="sRGB"
+ xlink:href="/usr/share/color/icc/sRGB.icc"
+ id="color-profile4484" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2973">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop2975" />
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:0;"
+ offset="1"
+ id="stop2977" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4126">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop4128" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.16494845;"
+ offset="1.0000000"
+ id="stop4130" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4114">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4116" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop4118" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3962">
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop3964" />
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.15517241"
+ id="stop4134" />
+ <stop
+ style="stop-color:#4074ae;stop-opacity:1.0000000;"
+ offset="0.75000000"
+ id="stop4346" />
+ <stop
+ style="stop-color:#36486c;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop3966" />
+ </linearGradient>
+ <radialGradient
+ r="13.994944"
+ fy="33.506763"
+ fx="-10.089286"
+ cy="33.506763"
+ cx="-10.089286"
+ gradientTransform="matrix(1,0,0,0.791446,-14.01786,-11.28667)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4019"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ gradientTransform="matrix(1,0,0,0.792374,-19.58761,2.818569)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4004"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.792374,0,6.785475)"
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ id="radialGradient3999"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.341185,-0.153831,1.08001,2.395374,-15.42222,-25.62103)"
+ r="13.994946"
+ fy="24.241488"
+ fx="61.662098"
+ cy="24.241488"
+ cx="61.662098"
+ id="radialGradient3943"
+ xlink:href="#linearGradient1312"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient1312">
+ <stop
+ id="stop1314"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop1316"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3993">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3995" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0"
+ offset="1"
+ id="stop3997" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2973"
+ id="radialGradient3866"
+ cx="-22.375"
+ cy="18.499998"
+ fx="-22.375"
+ fy="18.499998"
+ r="14.33462"
+ gradientTransform="matrix(1,0,0,1.140022,40.17678,1.347091)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ r="12.289036"
+ fy="63.965388"
+ fx="15.115514"
+ cy="63.965388"
+ cx="15.115514"
+ gradientTransform="scale(1.643990,0.608276)"
+ id="radialGradient5000"
+ xlink:href="#linearGradient4114"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4989">
+ <stop
+ id="stop4991"
+ offset="0"
+ style="stop-color:#47abff;stop-opacity:1" />
+ <stop
+ id="stop4993"
+ offset="0.35955963"
+ style="stop-color:#53a6ff;stop-opacity:1" />
+ <stop
+ id="stop4995"
+ offset="0.79518169"
+ style="stop-color:#286cbb;stop-opacity:1" />
+ <stop
+ id="stop4997"
+ offset="1"
+ style="stop-color:#003d87;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4977">
+ <stop
+ id="stop4979"
+ offset="0.0000000"
+ style="stop-color:#ffffff;stop-opacity:1" />
+ <stop
+ id="stop4981"
+ offset="1.0000000"
+ style="stop-color:#ffffff;stop-opacity:0" />
+ </linearGradient>
+ <filter
+ id="filter6406"
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ height="1.2"
+ width="1.2"
+ y="-0.15000000000000002"
+ x="-0.050000000000000017">
+ <feFlood
+ id="feFlood6408"
+ flood-opacity="0.50171821305841924"
+ flood-color="rgb(50,51,108)"
+ result="flood"
+ stdDeviation="0.01" />
+ <feComposite
+ id="feComposite6410"
+ in2="SourceGraphic"
+ in="flood"
+ operator="in"
+ result="composite1"
+ k1="8.1400000000000006"
+ k2="4.9299999999999997"
+ k3="5.5899999999999999"
+ k4="-7.2400000000000002" />
+ <feGaussianBlur
+ id="feGaussianBlur6412"
+ stdDeviation="0.20000000000000001"
+ result="blur"
+ in="composite" />
+ <feOffset
+ id="feOffset6414"
+ dx="0.29999999999999999"
+ dy="0.29999999999999999"
+ result="offset"
+ flood-opacity="0.5" />
+ <feComposite
+ id="feComposite6416"
+ in2="offset"
+ in="SourceGraphic"
+ result="composite2"
+ operator="" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ id="filter4258">
+ <feFlood
+ flood-opacity="0.80200501253132828"
+ flood-color="rgb(197,204,222)"
+ result="flood"
+ id="feFlood4260" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="in"
+ result="composite1"
+ id="feComposite4262"
+ flood-opacity="0.80000000000000004" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4264" />
+ <feOffset
+ dx="-0"
+ dy="-0.5"
+ result="offset"
+ id="feOffset4266" />
+ <feComposite
+ in="SourceGraphic"
+ in2="offset"
+ operator="over"
+ result="composite2"
+ id="feComposite4268" />
+ </filter>
+ <filter
+ id="filter4290"
+ inkscape:label="Drop Shadow"
+ style="color-interpolation-filters:sRGB;">
+ <feFlood
+ id="feFlood4292"
+ result="flood"
+ flood-color="rgb(255,255,255)"
+ flood-opacity="0.59999999999999998" />
+ <feComposite
+ id="feComposite4294"
+ result="composite1"
+ operator="in"
+ in2="SourceGraphic"
+ in="flood" />
+ <feGaussianBlur
+ id="feGaussianBlur4296"
+ result="blur"
+ stdDeviation="6.7999999999999998"
+ in="composite1" />
+ <feOffset
+ id="feOffset4298"
+ result="offset"
+ dy="1.9000000000000004"
+ dx="0.69999999999999996"
+ flood-opacity="0.68000000000000005" />
+ <feComposite
+ id="feComposite4300"
+ result="composite2"
+ in2="offset"
+ in="SourceGraphic"
+ operator="" />
+ </filter>
+ <filter
+ id="filter6406-6"
+ style="color-interpolation-filters:sRGB"
+ inkscape:label="Drop Shadow"
+ height="1.2"
+ width="1.2"
+ y="-0.15000001"
+ x="-0.050000001">
+ <feFlood
+ id="feFlood6408-0"
+ flood-opacity="0.50171821305841924"
+ flood-color="rgb(50,51,108)"
+ result="flood"
+ stdDeviation="0.01" />
+ <feComposite
+ id="feComposite6410-6"
+ in2="SourceGraphic"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6412-2"
+ stdDeviation="0.20000000000000001"
+ result="blur" />
+ <feOffset
+ id="feOffset6414-6"
+ dx="0.29999999999999999"
+ dy="0.29999999999999999"
+ result="offset"
+ flood-opacity="0.5" />
+ <feComposite
+ id="feComposite6416-1"
+ in2="offset"
+ in="SourceGraphic"
+ result="composite2" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="0.17254902"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.627417"
+ inkscape:cx="23.537703"
+ inkscape:cy="24.61952"
+ inkscape:current-layer="layer5"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1861"
+ inkscape:window-height="1176"
+ inkscape:window-x="59"
+ inkscape:window-y="24"
+ inkscape:showpageshadow="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-maximized="1"
+ width="512px"
+ units="px"
+ inkscape:snap-global="false"
+ inkscape:snap-to-guides="true"
+ inkscape:snap-others="true">
+ <sodipodi:guide
+ orientation="0,1"
+ position="4.331,47"
+ id="guide3056"
+ inkscape:label=""
+ inkscape:color="rgb(99,99,125)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="23.9375,24.5625"
+ id="guide3058"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="40,25.375"
+ id="guide5866" />
+ <sodipodi:guide
+ position="3.8890873,1"
+ orientation="0,1"
+ id="guide4521"
+ inkscape:label=""
+ inkscape:color="rgb(106,106,146)" />
+ <sodipodi:guide
+ position="1,17.324"
+ orientation="1,0"
+ id="guide4523"
+ inkscape:label=""
+ inkscape:color="rgb(152,152,181)" />
+ <sodipodi:guide
+ position="47,33.852737"
+ orientation="1,0"
+ id="guide4525"
+ inkscape:label=""
+ inkscape:color="rgb(121,121,172)" />
+ <sodipodi:guide
+ position="13.433908,46"
+ orientation="0,1"
+ id="guide4527"
+ inkscape:label=""
+ inkscape:color="rgb(255,40,0)" />
+ <sodipodi:guide
+ position="8,36.135"
+ orientation="1,0"
+ id="guide4529"
+ inkscape:label=""
+ inkscape:color="rgb(255,11,0)" />
+ <sodipodi:guide
+ position="40,26.436782"
+ orientation="1,0"
+ id="guide4531"
+ inkscape:label=""
+ inkscape:color="rgb(255,6,0)" />
+ <sodipodi:guide
+ position="34.052,2"
+ orientation="0,1"
+ id="guide4533"
+ inkscape:label=""
+ inkscape:color="rgb(255,0,5)" />
+ <sodipodi:guide
+ position="36.879276,26"
+ orientation="0,1"
+ id="guide4542"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="43.660795,14.731391"
+ orientation="0,1"
+ id="guide4546" />
+ <sodipodi:guide
+ position="21.25,21"
+ orientation="0,1"
+ id="guide4229" />
+ <sodipodi:guide
+ position="35.84375,19.53125"
+ orientation="0,1"
+ id="guide5116" />
+ <sodipodi:guide
+ position="30.21875,22.46875"
+ orientation="0,1"
+ id="guide5118" />
+ <inkscape:grid
+ type="xygrid"
+ id="grid4217" />
+ </sodipodi:namedview>
+ <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:title />
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Tuomas Kuosmanen</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
+ <dc:source>http://jimmac.musichall.cz</dc:source>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>internet</rdf:li>
+ <rdf:li>tools</rdf:li>
+ <rdf:li>applications</rdf:li>
+ <rdf:li>category</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Attribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="base droplet"
+ inkscape:groupmode="layer"
+ style="display:none">
+ <path
+ style="fill:none;fill-opacity:1;fill-rule:evenodd;stroke:#129b00;stroke-width:3.27999091;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter6406)"
+ d="M 23.295622,2.4822833 35.490217,21.297229 C 43.30243,33.350632 35.08691,46.383433 23.287956,46.383433 11.489002,46.383433 3.2602089,33.346045 11.079504,21.30073 Z"
+ id="path2069"
+ sodipodi:nodetypes="cszsc"
+ inkscape:connector-curvature="0"
+ transform="matrix(0.90726715,0,0,0.89972834,2.9383017,2.518482)" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="base droplet copy 1"
+ style="display:none" />
+ <g
+ style="display:inline"
+ inkscape:groupmode="layer"
+ inkscape:label="base droplet copy"
+ id="g4225">
+ <path
+ transform="matrix(0.90726715,0,0,0.89972834,2.9383017,2.518482)"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csscc"
+ id="path4227"
+ d="m 38.395387,28.809989 c 1.339184,9.392872 -5.759479,17.573444 -15.107431,17.573444 -5.899477,0 -10.906414,-3.259347 -13.4610694,-8.024358 C 8.3379994,35.581962 7.7795043,32.31798 8.1679076,28.840613 Z"
+ style="display:inline;fill:#16c816;fill-opacity:1;fill-rule:evenodd;stroke:#129b00;stroke-width:3.27999091;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <path
+ style="display:inline;fill:#6699ff;fill-opacity:1;fill-rule:evenodd;stroke:#3366cc;stroke-width:2.96343851;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 24.073654,4.7518625 35.13741,21.680202 c 0.830822,1.271218 1.561747,2.541942 1.952222,3.843334 l -26.064146,0.03312 c 0.398283,-1.282502 1.126319,-2.592255 1.964886,-3.8733 z"
+ id="path2069-8"
+ sodipodi:nodetypes="csccsc"
+ inkscape:connector-curvature="0" />
+ <path
+ style="display:inline;fill:#16c816;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.27999091;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 36.879847,27.238166 C 38.115698,38.19401 31.666974,44.823427 23.184624,44.750996 14.702274,44.678564 8.2127922,37.110525 9.6490039,27.219671 Z"
+ id="path4228"
+ sodipodi:nodetypes="czcc"
+ inkscape:connector-curvature="0"
+ transform="matrix(0.90726715,0,0,0.89972834,2.9383017,2.518482)" />
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="csccsc"
+ id="path4239"
+ d="M 24.011154,7.3143625 33.51241,21.805202 c 0.832698,1.26999 2.522684,3.932567 2.920972,5.249584 l -24.751646,-0.02935 c 0.398283,-1.282502 2.041931,-3.742425 2.871136,-5.02955 z"
+ style="display:inline;fill:#6699ff;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.96343851;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="seed/download icon"
+ style="display:inline">
+ <path
+ sodipodi:type="star"
+ style="opacity:1;fill:#005000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path5039"
+ sodipodi:sides="3"
+ sodipodi:cx="23.9375"
+ sodipodi:cy="21.1875"
+ sodipodi:r1="4.6958261"
+ sodipodi:r2="2.347913"
+ sodipodi:arg1="0.52359878"
+ sodipodi:arg2="1.5707963"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 28.004205,23.535413 -4.066705,0 -4.066705,0 2.033353,-3.521869 2.033352,-3.52187 2.033352,3.521869 z"
+ transform="matrix(-1.8442451,0,0,-0.99379036,68.146617,57.377998)"
+ inkscape:transform-center-y="-1.1666696" />
+ <rect
+ style="display:inline;opacity:1;fill:#0e2960;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4364-1-7-0"
+ width="8.3000002"
+ height="4.2000003"
+ x="19.75"
+ y="21.788212" />
+ <rect
+ style="display:inline;opacity:1;fill:#005000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4364-1-7"
+ width="8.3000002"
+ height="4.2000003"
+ x="-28.049999"
+ y="-32.268642"
+ transform="scale(-1,-1)" />
+ <path
+ sodipodi:type="star"
+ style="display:inline;opacity:1;fill:#0e2960;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path5039-2"
+ sodipodi:sides="3"
+ sodipodi:cx="23.9375"
+ sodipodi:cy="21.1875"
+ sodipodi:r1="4.6958261"
+ sodipodi:r2="2.347913"
+ sodipodi:arg1="0.52359878"
+ sodipodi:arg2="1.5707963"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 28.004205,23.535413 -4.066705,0 -4.066705,0 2.033353,-3.521869 2.033352,-3.52187 2.033352,3.521869 z"
+ inkscape:transform-center-y="-1.1666662"
+ transform="matrix(1.8442451,0,0,0.99379036,-20.146617,-3.2560397)" />
+ </g>
+</svg>
diff --git a/deluge/ui/data/pixmaps/active16.png b/deluge/ui/data/pixmaps/active16.png
new file mode 100644
index 0000000..c9af82a
--- /dev/null
+++ b/deluge/ui/data/pixmaps/active16.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/alert.svg b/deluge/ui/data/pixmaps/alert.svg
new file mode 100644
index 0000000..8f73872
--- /dev/null
+++ b/deluge/ui/data/pixmaps/alert.svg
@@ -0,0 +1,512 @@
+<?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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg3440"
+ sodipodi:version="0.32"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="alert.svg"
+ inkscape:export-xdpi="960"
+ inkscape:export-ydpi="960"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="TRUE"
+ version="1.1"
+ inkscape:export-filename="/home/calum/projects/deluge-logo/deluge.512.x.png">
+ <defs
+ id="defs3">
+ <color-profile
+ name="sRGB"
+ xlink:href="/usr/share/color/icc/sRGB.icc"
+ id="color-profile4484" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2973">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop2975" />
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:0;"
+ offset="1"
+ id="stop2977" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4126">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop4128" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.16494845;"
+ offset="1.0000000"
+ id="stop4130" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4114">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4116" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop4118" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3962">
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop3964" />
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.15517241"
+ id="stop4134" />
+ <stop
+ style="stop-color:#4074ae;stop-opacity:1.0000000;"
+ offset="0.75000000"
+ id="stop4346" />
+ <stop
+ style="stop-color:#36486c;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop3966" />
+ </linearGradient>
+ <radialGradient
+ r="13.994944"
+ fy="33.506763"
+ fx="-10.089286"
+ cy="33.506763"
+ cx="-10.089286"
+ gradientTransform="matrix(1,0,0,0.791446,-14.01786,-11.28667)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4019"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ gradientTransform="matrix(1,0,0,0.792374,-19.58761,2.818569)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4004"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.792374,0,6.785475)"
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ id="radialGradient3999"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.341185,-0.153831,1.08001,2.395374,-15.42222,-25.62103)"
+ r="13.994946"
+ fy="24.241488"
+ fx="61.662098"
+ cy="24.241488"
+ cx="61.662098"
+ id="radialGradient3943"
+ xlink:href="#linearGradient1312"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient1312">
+ <stop
+ id="stop1314"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop1316"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3993">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3995" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0"
+ offset="1"
+ id="stop3997" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2973"
+ id="radialGradient3866"
+ cx="-22.375"
+ cy="18.499998"
+ fx="-22.375"
+ fy="18.499998"
+ r="14.33462"
+ gradientTransform="matrix(1,0,0,1.140022,40.17678,1.347091)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ r="12.289036"
+ fy="63.965388"
+ fx="15.115514"
+ cy="63.965388"
+ cx="15.115514"
+ gradientTransform="scale(1.643990,0.608276)"
+ id="radialGradient5000"
+ xlink:href="#linearGradient4114"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4989">
+ <stop
+ id="stop4991"
+ offset="0"
+ style="stop-color:#47abff;stop-opacity:1" />
+ <stop
+ id="stop4993"
+ offset="0.35955963"
+ style="stop-color:#53a6ff;stop-opacity:1" />
+ <stop
+ id="stop4995"
+ offset="0.79518169"
+ style="stop-color:#286cbb;stop-opacity:1" />
+ <stop
+ id="stop4997"
+ offset="1"
+ style="stop-color:#003d87;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4977">
+ <stop
+ id="stop4979"
+ offset="0.0000000"
+ style="stop-color:#ffffff;stop-opacity:1" />
+ <stop
+ id="stop4981"
+ offset="1.0000000"
+ style="stop-color:#ffffff;stop-opacity:0" />
+ </linearGradient>
+ <filter
+ id="filter6406"
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ height="1.2"
+ width="1.2"
+ y="-0.15000000000000002"
+ x="-0.050000000000000017">
+ <feFlood
+ id="feFlood6408"
+ flood-opacity="0.50171821305841924"
+ flood-color="rgb(50,51,108)"
+ result="flood"
+ stdDeviation="0.01" />
+ <feComposite
+ id="feComposite6410"
+ in2="SourceGraphic"
+ in="flood"
+ operator="in"
+ result="composite1"
+ k1="8.1400000000000006"
+ k2="4.9299999999999997"
+ k3="5.5899999999999999"
+ k4="-7.2400000000000002" />
+ <feGaussianBlur
+ id="feGaussianBlur6412"
+ stdDeviation="0.20000000000000001"
+ result="blur"
+ in="composite" />
+ <feOffset
+ id="feOffset6414"
+ dx="0.29999999999999999"
+ dy="0.29999999999999999"
+ result="offset"
+ flood-opacity="0.5" />
+ <feComposite
+ id="feComposite6416"
+ in2="offset"
+ in="SourceGraphic"
+ result="composite2"
+ operator="" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ id="filter4258">
+ <feFlood
+ flood-opacity="0.80200501253132828"
+ flood-color="rgb(197,204,222)"
+ result="flood"
+ id="feFlood4260" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="in"
+ result="composite1"
+ id="feComposite4262"
+ flood-opacity="0.80000000000000004" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4264" />
+ <feOffset
+ dx="-0"
+ dy="-0.5"
+ result="offset"
+ id="feOffset4266" />
+ <feComposite
+ in="SourceGraphic"
+ in2="offset"
+ operator="over"
+ result="composite2"
+ id="feComposite4268" />
+ </filter>
+ <filter
+ id="filter4290"
+ inkscape:label="Drop Shadow"
+ style="color-interpolation-filters:sRGB;">
+ <feFlood
+ id="feFlood4292"
+ result="flood"
+ flood-color="rgb(255,255,255)"
+ flood-opacity="0.59999999999999998" />
+ <feComposite
+ id="feComposite4294"
+ result="composite1"
+ operator="in"
+ in2="SourceGraphic"
+ in="flood" />
+ <feGaussianBlur
+ id="feGaussianBlur4296"
+ result="blur"
+ stdDeviation="6.7999999999999998"
+ in="composite1" />
+ <feOffset
+ id="feOffset4298"
+ result="offset"
+ dy="1.9000000000000004"
+ dx="0.69999999999999996"
+ flood-opacity="0.68000000000000005" />
+ <feComposite
+ id="feComposite4300"
+ result="composite2"
+ in2="offset"
+ in="SourceGraphic"
+ operator="" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="0.17254902"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="16"
+ inkscape:cx="28.836938"
+ inkscape:cy="22.782595"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1861"
+ inkscape:window-height="1176"
+ inkscape:window-x="59"
+ inkscape:window-y="24"
+ inkscape:showpageshadow="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-maximized="1"
+ width="512px"
+ units="px"
+ inkscape:snap-global="false"
+ inkscape:snap-to-guides="true"
+ inkscape:snap-others="true">
+ <sodipodi:guide
+ orientation="0,1"
+ position="4.331,47"
+ id="guide3056"
+ inkscape:label=""
+ inkscape:color="rgb(99,99,125)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="23.9375,24.5625"
+ id="guide3058"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="40,25.375"
+ id="guide5866" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="38.448931,29.742679"
+ id="guide5868" />
+ <sodipodi:guide
+ position="3.8890873,1"
+ orientation="0,1"
+ id="guide4521"
+ inkscape:label=""
+ inkscape:color="rgb(106,106,146)" />
+ <sodipodi:guide
+ position="1,17.324"
+ orientation="1,0"
+ id="guide4523"
+ inkscape:label=""
+ inkscape:color="rgb(152,152,181)" />
+ <sodipodi:guide
+ position="47,33.852737"
+ orientation="1,0"
+ id="guide4525"
+ inkscape:label=""
+ inkscape:color="rgb(121,121,172)" />
+ <sodipodi:guide
+ position="13.433908,46"
+ orientation="0,1"
+ id="guide4527"
+ inkscape:label=""
+ inkscape:color="rgb(255,40,0)" />
+ <sodipodi:guide
+ position="8,36.135"
+ orientation="1,0"
+ id="guide4529"
+ inkscape:label=""
+ inkscape:color="rgb(255,11,0)" />
+ <sodipodi:guide
+ position="40,26.436782"
+ orientation="1,0"
+ id="guide4531"
+ inkscape:label=""
+ inkscape:color="rgb(255,6,0)" />
+ <sodipodi:guide
+ position="34.052,2"
+ orientation="0,1"
+ id="guide4533"
+ inkscape:label=""
+ inkscape:color="rgb(255,0,5)" />
+ <sodipodi:guide
+ position="36.879276,26"
+ orientation="0,1"
+ id="guide4542"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="43.660795,14.731391"
+ orientation="0,1"
+ id="guide4546" />
+ <inkscape:grid
+ type="xygrid"
+ id="grid4214" />
+ </sodipodi:namedview>
+ <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:title />
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Tuomas Kuosmanen</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
+ <dc:source>http://jimmac.musichall.cz</dc:source>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>internet</rdf:li>
+ <rdf:li>tools</rdf:li>
+ <rdf:li>applications</rdf:li>
+ <rdf:li>category</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Attribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="base droplet"
+ inkscape:groupmode="layer"
+ style="display:inline">
+ <path
+ style="fill:#e6381f;fill-opacity:1;fill-rule:evenodd;stroke:#a60e0e;stroke-width:3.27999091;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;"
+ d="M 23.295622,2.4822833 35.490217,21.297229 C 43.30243,33.350632 35.08691,46.383433 23.287956,46.383433 11.489002,46.383433 3.2602089,33.346045 11.079504,21.30073 Z"
+ id="path2069"
+ sodipodi:nodetypes="cszsc"
+ inkscape:connector-curvature="0"
+ transform="matrix(0.90726715,0,0,0.89972834,2.9383017,2.518482)" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="seed/download icon"
+ style="display:none">
+ <text
+ xml:space="preserve"
+ style="font-style:normal;font-weight:normal;font-size:17.90330505px;line-height:125%;font-family:Sans;letter-spacing:0px;word-spacing:0px;fill:#630000;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ x="12.545032"
+ y="40.473927"
+ id="text5111"
+ sodipodi:linespacing="125%"
+ transform="scale(1.0196557,0.98072319)"><tspan
+ sodipodi:role="line"
+ id="tspan5113"
+ x="12.545032"
+ y="40.473927"
+ style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:35.80661011px;font-family:'Courier 10 Pitch';-inkscape-font-specification:'Courier 10 Pitch Bold';fill:#630000;fill-opacity:1;stroke:none">!</tspan></text>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="seed/download icon 1"
+ style="display:inline">
+ <path
+ style="display:inline;opacity:1;fill:#630000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="m 20.966529,17.53531 6,0 -0.97907,14.309359 -3.824288,0 z"
+ id="rect4203"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccc" />
+ <path
+ style="display:inline;opacity:1;fill:#630000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="m 21.203402,33.393779 5.5,0 0,5.5 -5.5,0 z"
+ id="rect4206"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/deluge/ui/data/pixmaps/alert16.png b/deluge/ui/data/pixmaps/alert16.png
new file mode 100644
index 0000000..7036638
--- /dev/null
+++ b/deluge/ui/data/pixmaps/alert16.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/all.svg b/deluge/ui/data/pixmaps/all.svg
new file mode 100644
index 0000000..6dd99c9
--- /dev/null
+++ b/deluge/ui/data/pixmaps/all.svg
@@ -0,0 +1,826 @@
+<?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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg3440"
+ sodipodi:version="0.32"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="all.svg"
+ inkscape:export-xdpi="960"
+ inkscape:export-ydpi="960"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="TRUE"
+ version="1.1"
+ inkscape:export-filename="/home/calum/projects/deluge-logo/deluge.512.x.png">
+ <defs
+ id="defs3">
+ <color-profile
+ name="sRGB"
+ xlink:href="/usr/share/color/icc/sRGB.icc"
+ id="color-profile4484" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2973">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop2975" />
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:0;"
+ offset="1"
+ id="stop2977" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4126">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop4128" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.16494845;"
+ offset="1.0000000"
+ id="stop4130" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4114">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4116" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop4118" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3962">
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop3964" />
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.15517241"
+ id="stop4134" />
+ <stop
+ style="stop-color:#4074ae;stop-opacity:1.0000000;"
+ offset="0.75000000"
+ id="stop4346" />
+ <stop
+ style="stop-color:#36486c;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop3966" />
+ </linearGradient>
+ <radialGradient
+ r="13.994944"
+ fy="33.506763"
+ fx="-10.089286"
+ cy="33.506763"
+ cx="-10.089286"
+ gradientTransform="matrix(1,0,0,0.791446,-14.01786,-11.28667)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4019"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ gradientTransform="matrix(1,0,0,0.792374,-19.58761,2.818569)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4004"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.792374,0,6.785475)"
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ id="radialGradient3999"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.341185,-0.153831,1.08001,2.395374,-15.42222,-25.62103)"
+ r="13.994946"
+ fy="24.241488"
+ fx="61.662098"
+ cy="24.241488"
+ cx="61.662098"
+ id="radialGradient3943"
+ xlink:href="#linearGradient1312"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient1312">
+ <stop
+ id="stop1314"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop1316"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3993">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3995" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0"
+ offset="1"
+ id="stop3997" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2973"
+ id="radialGradient3866"
+ cx="-22.375"
+ cy="18.499998"
+ fx="-22.375"
+ fy="18.499998"
+ r="14.33462"
+ gradientTransform="matrix(1,0,0,1.140022,40.17678,1.347091)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ r="12.289036"
+ fy="63.965388"
+ fx="15.115514"
+ cy="63.965388"
+ cx="15.115514"
+ gradientTransform="scale(1.643990,0.608276)"
+ id="radialGradient5000"
+ xlink:href="#linearGradient4114"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4989">
+ <stop
+ id="stop4991"
+ offset="0"
+ style="stop-color:#47abff;stop-opacity:1" />
+ <stop
+ id="stop4993"
+ offset="0.35955963"
+ style="stop-color:#53a6ff;stop-opacity:1" />
+ <stop
+ id="stop4995"
+ offset="0.79518169"
+ style="stop-color:#286cbb;stop-opacity:1" />
+ <stop
+ id="stop4997"
+ offset="1"
+ style="stop-color:#003d87;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4977">
+ <stop
+ id="stop4979"
+ offset="0.0000000"
+ style="stop-color:#ffffff;stop-opacity:1" />
+ <stop
+ id="stop4981"
+ offset="1.0000000"
+ style="stop-color:#ffffff;stop-opacity:0" />
+ </linearGradient>
+ <filter
+ id="filter6406"
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ height="1.2"
+ width="1.2"
+ y="-0.15000000000000002"
+ x="-0.050000000000000017">
+ <feFlood
+ id="feFlood6408"
+ flood-opacity="0.50171821305841924"
+ flood-color="rgb(50,51,108)"
+ result="flood"
+ stdDeviation="0.01" />
+ <feComposite
+ id="feComposite6410"
+ in2="SourceGraphic"
+ in="flood"
+ operator="in"
+ result="composite1"
+ k1="8.1400000000000006"
+ k2="4.9299999999999997"
+ k3="5.5899999999999999"
+ k4="-7.2400000000000002" />
+ <feGaussianBlur
+ id="feGaussianBlur6412"
+ stdDeviation="0.20000000000000001"
+ result="blur"
+ in="composite" />
+ <feOffset
+ id="feOffset6414"
+ dx="0.29999999999999999"
+ dy="0.29999999999999999"
+ result="offset"
+ flood-opacity="0.5" />
+ <feComposite
+ id="feComposite6416"
+ in2="offset"
+ in="SourceGraphic"
+ result="composite2"
+ operator="" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ id="filter4258">
+ <feFlood
+ flood-opacity="0.80200501253132828"
+ flood-color="rgb(197,204,222)"
+ result="flood"
+ id="feFlood4260" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="in"
+ result="composite1"
+ id="feComposite4262"
+ flood-opacity="0.80000000000000004" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4264" />
+ <feOffset
+ dx="-0"
+ dy="-0.5"
+ result="offset"
+ id="feOffset4266" />
+ <feComposite
+ in="SourceGraphic"
+ in2="offset"
+ operator="over"
+ result="composite2"
+ id="feComposite4268" />
+ </filter>
+ <filter
+ id="filter4290"
+ inkscape:label="Drop Shadow"
+ style="color-interpolation-filters:sRGB;">
+ <feFlood
+ id="feFlood4292"
+ result="flood"
+ flood-color="rgb(255,255,255)"
+ flood-opacity="0.59999999999999998" />
+ <feComposite
+ id="feComposite4294"
+ result="composite1"
+ operator="in"
+ in2="SourceGraphic"
+ in="flood" />
+ <feGaussianBlur
+ id="feGaussianBlur4296"
+ result="blur"
+ stdDeviation="6.7999999999999998"
+ in="composite1" />
+ <feOffset
+ id="feOffset4298"
+ result="offset"
+ dy="1.9000000000000004"
+ dx="0.69999999999999996"
+ flood-opacity="0.68000000000000005" />
+ <feComposite
+ id="feComposite4300"
+ result="composite2"
+ in2="offset"
+ in="SourceGraphic"
+ operator="" />
+ </filter>
+ <filter
+ id="filter6406-6"
+ style="color-interpolation-filters:sRGB"
+ inkscape:label="Drop Shadow"
+ height="1.2"
+ width="1.2"
+ y="-0.15000001"
+ x="-0.050000001">
+ <feFlood
+ id="feFlood6408-1"
+ flood-opacity="0.50171821305841924"
+ flood-color="rgb(50,51,108)"
+ result="flood"
+ stdDeviation="0.01" />
+ <feComposite
+ id="feComposite6410-2"
+ in2="SourceGraphic"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6412-9"
+ stdDeviation="0.20000000000000001"
+ result="blur" />
+ <feOffset
+ id="feOffset6414-3"
+ dx="0.29999999999999999"
+ dy="0.29999999999999999"
+ result="offset"
+ flood-opacity="0.5" />
+ <feComposite
+ id="feComposite6416-1"
+ in2="offset"
+ in="SourceGraphic"
+ result="composite2" />
+ </filter>
+ <filter
+ id="filter6406-4"
+ style="color-interpolation-filters:sRGB"
+ inkscape:label="Drop Shadow"
+ height="1.2"
+ width="1.2"
+ y="-0.15000001"
+ x="-0.050000001">
+ <feFlood
+ id="feFlood6408-7"
+ flood-opacity="0.50171821305841924"
+ flood-color="rgb(50,51,108)"
+ result="flood"
+ stdDeviation="0.01" />
+ <feComposite
+ id="feComposite6410-8"
+ in2="SourceGraphic"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6412-4"
+ stdDeviation="0.20000000000000001"
+ result="blur" />
+ <feOffset
+ id="feOffset6414-5"
+ dx="0.29999999999999999"
+ dy="0.29999999999999999"
+ result="offset"
+ flood-opacity="0.5" />
+ <feComposite
+ id="feComposite6416-0"
+ in2="offset"
+ in="SourceGraphic"
+ result="composite2" />
+ </filter>
+ <filter
+ id="filter6406-1"
+ style="color-interpolation-filters:sRGB"
+ inkscape:label="Drop Shadow"
+ height="1.2"
+ width="1.2"
+ y="-0.15000001"
+ x="-0.050000001">
+ <feFlood
+ id="feFlood6408-0"
+ flood-opacity="0.50171821305841924"
+ flood-color="rgb(50,51,108)"
+ result="flood"
+ stdDeviation="0.01" />
+ <feComposite
+ id="feComposite6410-6"
+ in2="SourceGraphic"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6412-3"
+ stdDeviation="0.20000000000000001"
+ result="blur" />
+ <feOffset
+ id="feOffset6414-2"
+ dx="0.29999999999999999"
+ dy="0.29999999999999999"
+ result="offset"
+ flood-opacity="0.5" />
+ <feComposite
+ id="feComposite6416-06"
+ in2="offset"
+ in="SourceGraphic"
+ result="composite2" />
+ </filter>
+ <filter
+ id="filter6406-65"
+ style="color-interpolation-filters:sRGB"
+ inkscape:label="Drop Shadow"
+ height="1.2"
+ width="1.2"
+ y="-0.15000001"
+ x="-0.050000001">
+ <feFlood
+ id="feFlood6408-6"
+ flood-opacity="0.50171821305841924"
+ flood-color="rgb(50,51,108)"
+ result="flood"
+ stdDeviation="0.01" />
+ <feComposite
+ id="feComposite6410-9"
+ in2="SourceGraphic"
+ in="flood"
+ operator="in"
+ result="composite1" />
+ <feGaussianBlur
+ id="feGaussianBlur6412-37"
+ stdDeviation="0.20000000000000001"
+ result="blur" />
+ <feOffset
+ id="feOffset6414-4"
+ dx="0.29999999999999999"
+ dy="0.29999999999999999"
+ result="offset"
+ flood-opacity="0.5" />
+ <feComposite
+ id="feComposite6416-5"
+ in2="offset"
+ in="SourceGraphic"
+ result="composite2" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="0.17254902"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="22.627418"
+ inkscape:cx="24.522092"
+ inkscape:cy="23.789911"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1861"
+ inkscape:window-height="1176"
+ inkscape:window-x="59"
+ inkscape:window-y="24"
+ inkscape:showpageshadow="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-maximized="1"
+ width="512px"
+ units="px"
+ inkscape:snap-global="false"
+ inkscape:snap-to-guides="true"
+ inkscape:snap-others="true">
+ <sodipodi:guide
+ orientation="0,1"
+ position="4.331,47"
+ id="guide3056"
+ inkscape:label=""
+ inkscape:color="rgb(99,99,125)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="23.9375,24.5625"
+ id="guide3058"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="40,25.375"
+ id="guide5866" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="38.448931,29.742679"
+ id="guide5868" />
+ <sodipodi:guide
+ position="3.8890873,1"
+ orientation="0,1"
+ id="guide4521"
+ inkscape:label=""
+ inkscape:color="rgb(106,106,146)" />
+ <sodipodi:guide
+ position="1,17.324"
+ orientation="1,0"
+ id="guide4523"
+ inkscape:label=""
+ inkscape:color="rgb(152,152,181)" />
+ <sodipodi:guide
+ position="47,33.852737"
+ orientation="1,0"
+ id="guide4525"
+ inkscape:label=""
+ inkscape:color="rgb(121,121,172)" />
+ <sodipodi:guide
+ position="13.433908,46"
+ orientation="0,1"
+ id="guide4527"
+ inkscape:label=""
+ inkscape:color="rgb(255,40,0)" />
+ <sodipodi:guide
+ position="8,36.135"
+ orientation="1,0"
+ id="guide4529"
+ inkscape:label=""
+ inkscape:color="rgb(255,11,0)" />
+ <sodipodi:guide
+ position="40,26.436782"
+ orientation="1,0"
+ id="guide4531"
+ inkscape:label=""
+ inkscape:color="rgb(255,6,0)" />
+ <sodipodi:guide
+ position="34.052,2"
+ orientation="0,1"
+ id="guide4533"
+ inkscape:label=""
+ inkscape:color="rgb(255,0,5)" />
+ <sodipodi:guide
+ position="36.879276,26"
+ orientation="0,1"
+ id="guide4542"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="43.660795,14.731391"
+ orientation="0,1"
+ id="guide4546" />
+ </sodipodi:namedview>
+ <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:title />
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Tuomas Kuosmanen</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
+ <dc:source>http://jimmac.musichall.cz</dc:source>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>internet</rdf:li>
+ <rdf:li>tools</rdf:li>
+ <rdf:li>applications</rdf:li>
+ <rdf:li>category</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Attribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="seed/download icon"
+ style="display:none" />
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="paused"
+ style="display:none">
+ <g
+ id="g6424"
+ transform="matrix(0.5,0,0,0.5,2.9837572,-5.2773854)">
+ <path
+ transform="matrix(0.90726715,0,0,0.89972834,26.239516,15.92952)"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cszsc"
+ id="path2069-9"
+ d="M 23.295622,2.4822833 35.490217,21.297229 C 43.30243,33.350632 35.08691,46.383433 23.287956,46.383433 11.489002,46.383433 3.2602089,33.346045 11.079504,21.30073 Z"
+ style="display:inline;fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#999999;stroke-width:3.27999091;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter6406-6)" />
+ <rect
+ y="35.411037"
+ x="48.613716"
+ height="16"
+ width="5.5"
+ id="rect4364"
+ style="display:inline;opacity:1;fill:#666666;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ y="35.411037"
+ x="40.426212"
+ height="16"
+ width="5.5"
+ id="rect4364-1"
+ style="display:inline;opacity:1;fill:#666666;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+ </g>
+ <g
+ id="layer1"
+ inkscape:label="alert"
+ inkscape:groupmode="layer"
+ style="display:inline">
+ <path
+ style="display:inline;fill:#e6381f;fill-opacity:1;fill-rule:evenodd;stroke:#a60e0e;stroke-width:1.48663402;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 23.83453,3.7818049 5.550226,8.4922441 c 3.555636,5.44038 -0.183561,11.322817 -5.553715,11.322817 -5.370156,0 -9.115393,-5.884508 -5.556534,-11.321236 z"
+ id="path2069-8"
+ sodipodi:nodetypes="cszsc"
+ inkscape:connector-curvature="0" />
+ <path
+ style="display:inline;opacity:1;fill:#630000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="m 22.275814,10.194729 3.009951,0 -0.491159,7.178411 -1.918487,0 z"
+ id="rect4203"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccc" />
+ <path
+ style="display:inline;opacity:1;fill:#630000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ d="m 22.394643,18.150264 2.759122,0 0,2.759121 -2.759122,0 z"
+ id="rect4206"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer6"
+ inkscape:label="queue"
+ style="display:inline">
+ <g
+ id="g4381"
+ transform="matrix(0.50041261,0,0,0.50041261,10.301289,13.764121)">
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cszsc"
+ id="path2069-23"
+ d="M 43.762814,0.18101753 54.826569,17.109357 c 7.087764,10.844788 -0.365907,22.570769 -11.07071,22.570769 -10.704803,0 -18.170517,-11.730108 -11.076327,-22.567619 z"
+ style="display:inline;fill:#dcdc00;fill-opacity:1;fill-rule:evenodd;stroke:#b4b400;stroke-width:2.96343851;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ y="25.66815"
+ x="35.689159"
+ height="4"
+ width="16"
+ id="rect4221-3-6-7"
+ style="display:inline;opacity:1;fill:#6a6a00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.03118205;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <rect
+ y="19.302549"
+ x="35.689159"
+ height="4"
+ width="16"
+ id="rect4221-3-9-5"
+ style="display:inline;opacity:1;fill:#6a6a00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.03118205;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ inkscape:transform-center-x="2.0998475e-05"
+ inkscape:transform-center-y="-0.7497936"
+ transform="matrix(0,121.76383,-210.90115,0,3630.2786,-2895.3827)"
+ d="m 23.88441,17.006021 0.01848,-0.01067 0.01848,-0.01067 0,0.02134 0,0.02134 -0.01848,-0.01067 z"
+ inkscape:randomized="0"
+ inkscape:rounded="0"
+ inkscape:flatsided="false"
+ sodipodi:arg2="4.1887903"
+ sodipodi:arg1="3.1415927"
+ sodipodi:r2="0.012318929"
+ sodipodi:r1="0.024637857"
+ sodipodi:cy="17.006021"
+ sodipodi:cx="23.909048"
+ sodipodi:sides="3"
+ id="path4266-5-9"
+ style="display:inline;opacity:1;fill:#6a6a00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.97536206;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ sodipodi:type="star" />
+ <path
+ inkscape:transform-center-x="-0.00022730407"
+ inkscape:transform-center-y="0.74972138"
+ transform="matrix(0,-121.76383,210.90115,0,-3542.9004,2944.2596)"
+ d="m 23.88441,17.006021 0.01848,-0.01067 0.01848,-0.01067 0,0.02134 0,0.02134 -0.01848,-0.01067 z"
+ inkscape:randomized="0"
+ inkscape:rounded="0"
+ inkscape:flatsided="false"
+ sodipodi:arg2="4.1887903"
+ sodipodi:arg1="3.1415927"
+ sodipodi:r2="0.012318929"
+ sodipodi:r1="0.024637857"
+ sodipodi:cy="17.006021"
+ sodipodi:cx="23.909048"
+ sodipodi:sides="3"
+ id="path4266-5-3-2"
+ style="display:inline;opacity:1;fill:#6a6a00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.97536206;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ sodipodi:type="star" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer4"
+ inkscape:label="seed"
+ style="display:inline">
+ <g
+ id="g4316"
+ transform="matrix(0.50386528,0,0,0.50386528,18.968897,3.1952438)">
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cszsc"
+ id="path2069-18"
+ d="m -6.3533765,21.261638 11.063755,16.928339 c 7.0877645,10.844788 -0.365907,22.570769 -11.07071,22.570769 -10.7048035,0 -18.1705175,-11.730108 -11.0763275,-22.567619 z"
+ style="display:inline;fill:#6699ff;fill-opacity:1;fill-rule:evenodd;stroke:#3366cc;stroke-width:2.96343851;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ y="49.509773"
+ x="-10.666519"
+ height="4.5"
+ width="8.5"
+ id="rect4364-1-36"
+ style="display:inline;opacity:1;fill:#0f3171;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ inkscape:transform-center-y="1.3125004"
+ transform="matrix(1.9671947,0,0,1.1180142,-53.583459,15.251026)"
+ d="m 28.004205,23.535413 -4.066705,0 -4.066705,0 2.033353,-3.521869 2.033352,-3.52187 2.033352,3.521869 z"
+ inkscape:randomized="0"
+ inkscape:rounded="0"
+ inkscape:flatsided="false"
+ sodipodi:arg2="1.5707963"
+ sodipodi:arg1="0.52359878"
+ sodipodi:r2="2.347913"
+ sodipodi:r1="4.6958261"
+ sodipodi:cy="21.1875"
+ sodipodi:cx="23.9375"
+ sodipodi:sides="3"
+ id="path5039-7"
+ style="display:inline;opacity:1;fill:#0f3171;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ sodipodi:type="star" />
+ <rect
+ y="43.411861"
+ x="-10.68544"
+ height="4.5"
+ width="8.5"
+ id="rect4364-1-3"
+ style="display:inline;opacity:1;fill:#0f3171;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="down"
+ style="display:inline">
+ <g
+ transform="matrix(0.99398243,0,0,0.99398243,26.888997,23.507801)"
+ style="display:inline"
+ id="g4334">
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cszsc"
+ id="path2069-7"
+ d="M -2.9505196,0.78380503 2.6291501,9.3211008 C 6.2036493,14.790341 2.444616,20.703985 -2.9540272,20.703985 c -5.3986437,0 -9.1637498,-5.915725 -5.5860106,-11.3812956 z"
+ style="display:inline;fill:#16c816;fill-opacity:1;fill-rule:evenodd;stroke:#129b00;stroke-width:1.49452055;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ <rect
+ transform="scale(-1,-1)"
+ y="-10.455302"
+ x="0.89548177"
+ height="2.2694387"
+ width="4.2766037"
+ id="rect4364-1-9"
+ style="display:inline;opacity:1;fill:#005000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ <path
+ inkscape:transform-center-y="-0.65561611"
+ transform="matrix(-0.99209509,0,0,-0.55846672,20.75849,27.675627)"
+ d="m 28.004205,23.535413 -4.066705,0 -4.066705,0 2.033353,-3.521869 2.033352,-3.52187 2.033352,3.521869 z"
+ inkscape:randomized="0"
+ inkscape:rounded="0"
+ inkscape:flatsided="false"
+ sodipodi:arg2="1.5707963"
+ sodipodi:arg1="0.52359878"
+ sodipodi:r2="2.347913"
+ sodipodi:r1="4.6958261"
+ sodipodi:cy="21.1875"
+ sodipodi:cx="23.9375"
+ sodipodi:sides="3"
+ id="path5039-2"
+ style="display:inline;opacity:1;fill:#005000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ sodipodi:type="star" />
+ <rect
+ transform="scale(-1,-1)"
+ y="-13.625159"
+ x="0.88536817"
+ height="2.2694387"
+ width="4.2766037"
+ id="rect4364-1-3-0"
+ style="display:inline;opacity:1;fill:#005000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1" />
+ </g>
+ </g>
+</svg>
diff --git a/deluge/ui/data/pixmaps/all16.png b/deluge/ui/data/pixmaps/all16.png
new file mode 100644
index 0000000..c63f8df
--- /dev/null
+++ b/deluge/ui/data/pixmaps/all16.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/checking.svg b/deluge/ui/data/pixmaps/checking.svg
new file mode 100644
index 0000000..4d64b7e
--- /dev/null
+++ b/deluge/ui/data/pixmaps/checking.svg
@@ -0,0 +1,504 @@
+<?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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg3440"
+ sodipodi:version="0.32"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="deluge_checking.svg"
+ inkscape:export-xdpi="960"
+ inkscape:export-ydpi="960"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="TRUE"
+ version="1.1"
+ inkscape:export-filename="/home/calum/projects/deluge-logo/deluge.512.x.png">
+ <defs
+ id="defs3">
+ <color-profile
+ name="sRGB"
+ xlink:href="/usr/share/color/icc/sRGB.icc"
+ id="color-profile4484" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2973">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop2975" />
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:0;"
+ offset="1"
+ id="stop2977" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4126">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop4128" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.16494845;"
+ offset="1.0000000"
+ id="stop4130" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4114">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4116" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop4118" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3962">
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop3964" />
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.15517241"
+ id="stop4134" />
+ <stop
+ style="stop-color:#4074ae;stop-opacity:1.0000000;"
+ offset="0.75000000"
+ id="stop4346" />
+ <stop
+ style="stop-color:#36486c;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop3966" />
+ </linearGradient>
+ <radialGradient
+ r="13.994944"
+ fy="33.506763"
+ fx="-10.089286"
+ cy="33.506763"
+ cx="-10.089286"
+ gradientTransform="matrix(1,0,0,0.791446,-14.01786,-11.28667)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4019"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ gradientTransform="matrix(1,0,0,0.792374,-19.58761,2.818569)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4004"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.792374,0,6.785475)"
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ id="radialGradient3999"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.341185,-0.153831,1.08001,2.395374,-15.42222,-25.62103)"
+ r="13.994946"
+ fy="24.241488"
+ fx="61.662098"
+ cy="24.241488"
+ cx="61.662098"
+ id="radialGradient3943"
+ xlink:href="#linearGradient1312"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient1312">
+ <stop
+ id="stop1314"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop1316"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3993">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3995" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0"
+ offset="1"
+ id="stop3997" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2973"
+ id="radialGradient3866"
+ cx="-22.375"
+ cy="18.499998"
+ fx="-22.375"
+ fy="18.499998"
+ r="14.33462"
+ gradientTransform="matrix(1,0,0,1.140022,40.17678,1.347091)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ r="12.289036"
+ fy="63.965388"
+ fx="15.115514"
+ cy="63.965388"
+ cx="15.115514"
+ gradientTransform="scale(1.643990,0.608276)"
+ id="radialGradient5000"
+ xlink:href="#linearGradient4114"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4989">
+ <stop
+ id="stop4991"
+ offset="0"
+ style="stop-color:#47abff;stop-opacity:1" />
+ <stop
+ id="stop4993"
+ offset="0.35955963"
+ style="stop-color:#53a6ff;stop-opacity:1" />
+ <stop
+ id="stop4995"
+ offset="0.79518169"
+ style="stop-color:#286cbb;stop-opacity:1" />
+ <stop
+ id="stop4997"
+ offset="1"
+ style="stop-color:#003d87;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4977">
+ <stop
+ id="stop4979"
+ offset="0.0000000"
+ style="stop-color:#ffffff;stop-opacity:1" />
+ <stop
+ id="stop4981"
+ offset="1.0000000"
+ style="stop-color:#ffffff;stop-opacity:0" />
+ </linearGradient>
+ <filter
+ id="filter6406"
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ height="1.2"
+ width="1.2"
+ y="-0.15000000000000002"
+ x="-0.050000000000000017">
+ <feFlood
+ id="feFlood6408"
+ flood-opacity="0.50171821305841924"
+ flood-color="rgb(50,51,108)"
+ result="flood"
+ stdDeviation="0.01" />
+ <feComposite
+ id="feComposite6410"
+ in2="SourceGraphic"
+ in="flood"
+ operator="in"
+ result="composite1"
+ k1="8.1400000000000006"
+ k2="4.9299999999999997"
+ k3="5.5899999999999999"
+ k4="-7.2400000000000002" />
+ <feGaussianBlur
+ id="feGaussianBlur6412"
+ stdDeviation="0.20000000000000001"
+ result="blur"
+ in="composite" />
+ <feOffset
+ id="feOffset6414"
+ dx="0.29999999999999999"
+ dy="0.29999999999999999"
+ result="offset"
+ flood-opacity="0.5" />
+ <feComposite
+ id="feComposite6416"
+ in2="offset"
+ in="SourceGraphic"
+ result="composite2"
+ operator="" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ id="filter4258">
+ <feFlood
+ flood-opacity="0.80200501253132828"
+ flood-color="rgb(197,204,222)"
+ result="flood"
+ id="feFlood4260" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="in"
+ result="composite1"
+ id="feComposite4262"
+ flood-opacity="0.80000000000000004" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4264" />
+ <feOffset
+ dx="-0"
+ dy="-0.5"
+ result="offset"
+ id="feOffset4266" />
+ <feComposite
+ in="SourceGraphic"
+ in2="offset"
+ operator="over"
+ result="composite2"
+ id="feComposite4268" />
+ </filter>
+ <filter
+ id="filter4290"
+ inkscape:label="Drop Shadow"
+ style="color-interpolation-filters:sRGB;">
+ <feFlood
+ id="feFlood4292"
+ result="flood"
+ flood-color="rgb(255,255,255)"
+ flood-opacity="0.59999999999999998" />
+ <feComposite
+ id="feComposite4294"
+ result="composite1"
+ operator="in"
+ in2="SourceGraphic"
+ in="flood" />
+ <feGaussianBlur
+ id="feGaussianBlur4296"
+ result="blur"
+ stdDeviation="6.7999999999999998"
+ in="composite1" />
+ <feOffset
+ id="feOffset4298"
+ result="offset"
+ dy="1.9000000000000004"
+ dx="0.69999999999999996"
+ flood-opacity="0.68000000000000005" />
+ <feComposite
+ id="feComposite4300"
+ result="composite2"
+ in2="offset"
+ in="SourceGraphic"
+ operator="" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="0.17254902"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="11.313709"
+ inkscape:cx="37.39656"
+ inkscape:cy="17.410941"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1861"
+ inkscape:window-height="1176"
+ inkscape:window-x="59"
+ inkscape:window-y="24"
+ inkscape:showpageshadow="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-maximized="1"
+ width="512px"
+ units="px"
+ inkscape:snap-global="false"
+ inkscape:snap-to-guides="true"
+ inkscape:snap-others="true">
+ <sodipodi:guide
+ orientation="0,1"
+ position="4.331,47"
+ id="guide3056"
+ inkscape:label=""
+ inkscape:color="rgb(99,99,125)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="23.9375,24.5625"
+ id="guide3058"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="40,25.375"
+ id="guide5866" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="38.448931,29.742679"
+ id="guide5868" />
+ <sodipodi:guide
+ position="3.8890873,1"
+ orientation="0,1"
+ id="guide4521"
+ inkscape:label=""
+ inkscape:color="rgb(106,106,146)" />
+ <sodipodi:guide
+ position="1,17.324"
+ orientation="1,0"
+ id="guide4523"
+ inkscape:label=""
+ inkscape:color="rgb(152,152,181)" />
+ <sodipodi:guide
+ position="47,33.852737"
+ orientation="1,0"
+ id="guide4525"
+ inkscape:label=""
+ inkscape:color="rgb(121,121,172)" />
+ <sodipodi:guide
+ position="13.433908,46"
+ orientation="0,1"
+ id="guide4527"
+ inkscape:label=""
+ inkscape:color="rgb(255,40,0)" />
+ <sodipodi:guide
+ position="8,36.135"
+ orientation="1,0"
+ id="guide4529"
+ inkscape:label=""
+ inkscape:color="rgb(255,11,0)" />
+ <sodipodi:guide
+ position="40,26.436782"
+ orientation="1,0"
+ id="guide4531"
+ inkscape:label=""
+ inkscape:color="rgb(255,6,0)" />
+ <sodipodi:guide
+ position="34.052,2"
+ orientation="0,1"
+ id="guide4533"
+ inkscape:label=""
+ inkscape:color="rgb(255,0,5)" />
+ <sodipodi:guide
+ position="36.879276,26"
+ orientation="0,1"
+ id="guide4542"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="43.660795,14.731391"
+ orientation="0,1"
+ id="guide4546" />
+ </sodipodi:namedview>
+ <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:title></dc:title>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Tuomas Kuosmanen</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
+ <dc:source>http://jimmac.musichall.cz</dc:source>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>internet</rdf:li>
+ <rdf:li>tools</rdf:li>
+ <rdf:li>applications</rdf:li>
+ <rdf:li>category</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Attribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="base droplet"
+ inkscape:groupmode="layer"
+ style="display:inline">
+ <path
+ style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#999999;stroke-width:3.27999091;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter6406)"
+ d="M 23.295622,2.4822833 35.490217,21.297229 C 43.30243,33.350632 35.08691,46.383433 23.287956,46.383433 11.489002,46.383433 3.2602089,33.346045 11.079504,21.30073 Z"
+ id="path2069"
+ sodipodi:nodetypes="cszsc"
+ inkscape:connector-curvature="0"
+ transform="matrix(0.90726715,0,0,0.89972834,2.9383017,2.518482)" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="paused icon"
+ style="display:none">
+ <rect
+ style="display:inline;opacity:1;fill:#666666;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4364"
+ width="5.5"
+ height="16"
+ x="25.3125"
+ y="22" />
+ <rect
+ style="display:inline;opacity:1;fill:#666666;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4364-1"
+ width="5.5"
+ height="16"
+ x="17.125"
+ y="22" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="checking icon">
+ <path
+ inkscape:connector-curvature="0"
+ style="fill:#666666;fill-opacity:1;stroke:none;stroke-width:0.48776338;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 15.70911,22.157482 c 0.119914,0.199935 0.418813,8.847951 0.34197,8.725818 -0.05095,-0.115892 9.029726,-0.318613 9.052253,-0.33886 -0.04746,0.004 -2.879595,-2.667362 -2.855268,-2.629987 2.723225,-2.451281 6.566426,-1.740041 6.843087,3.098922 0.276661,4.838963 -7.571006,6.978129 -8.064694,0.917979 l -4.575574,-0.07664 c 0.742237,4.962428 4.540705,7.477492 7.550683,7.485704 3.009978,0.0082 8.821989,-1.590934 8.794824,-8.313673 -0.02717,-6.722739 -4.319772,-8.932187 -8.343129,-8.887584 -4.023357,0.0446 -5.694705,2.520898 -5.856847,2.535638 0,0 -2.784162,-2.424246 -2.887305,-2.517317 z"
+ id="path3485"
+ sodipodi:nodetypes="cccczcczzzcc" />
+ </g>
+</svg>
diff --git a/deluge/ui/data/pixmaps/checking16.png b/deluge/ui/data/pixmaps/checking16.png
new file mode 100644
index 0000000..0ac2c65
--- /dev/null
+++ b/deluge/ui/data/pixmaps/checking16.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/deluge-about.png b/deluge/ui/data/pixmaps/deluge-about.png
new file mode 100644
index 0000000..39322eb
--- /dev/null
+++ b/deluge/ui/data/pixmaps/deluge-about.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/deluge.ico b/deluge/ui/data/pixmaps/deluge.ico
new file mode 100644
index 0000000..d946d11
--- /dev/null
+++ b/deluge/ui/data/pixmaps/deluge.ico
Binary files differ
diff --git a/deluge/ui/data/pixmaps/deluge.png b/deluge/ui/data/pixmaps/deluge.png
new file mode 100644
index 0000000..7b067ac
--- /dev/null
+++ b/deluge/ui/data/pixmaps/deluge.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/deluge.svg b/deluge/ui/data/pixmaps/deluge.svg
new file mode 100644
index 0000000..4f29f73
--- /dev/null
+++ b/deluge/ui/data/pixmaps/deluge.svg
@@ -0,0 +1,610 @@
+<?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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg3440"
+ sodipodi:version="0.32"
+ inkscape:version="0.92.3 (2405546, 2018-03-11)"
+ sodipodi:docname="deluge.svg"
+ inkscape:export-xdpi="32"
+ inkscape:export-ydpi="32"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="TRUE"
+ version="1.1"
+ inkscape:export-filename="/home/calum/Desktop/test1b.png">
+ <defs
+ id="defs3">
+ <color-profile
+ name="sRGB"
+ xlink:href="/usr/share/color/icc/sRGB.icc"
+ id="color-profile2" />
+ <linearGradient
+ id="linearGradient4345"
+ inkscape:collect="always">
+ <stop
+ id="stop4347"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1" />
+ <stop
+ id="stop4349"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2973">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop2975" />
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:0;"
+ offset="1"
+ id="stop2977" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4126">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop4128" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.16494845;"
+ offset="1.0000000"
+ id="stop4130" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4114">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4116" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop4118" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3962">
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop3964" />
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.15517241"
+ id="stop4134" />
+ <stop
+ style="stop-color:#4074ae;stop-opacity:1.0000000;"
+ offset="0.75000000"
+ id="stop4346" />
+ <stop
+ style="stop-color:#36486c;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop3966" />
+ </linearGradient>
+ <radialGradient
+ r="13.994944"
+ fy="33.506763"
+ fx="-10.089286"
+ cy="33.506763"
+ cx="-10.089286"
+ gradientTransform="matrix(1,0,0,0.791446,-14.01786,-11.28667)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4019"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ gradientTransform="matrix(1,0,0,0.792374,-19.58761,2.818569)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4004"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.792374,0,6.785475)"
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ id="radialGradient3999"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.341185,-0.153831,1.08001,2.395374,-15.42222,-25.62103)"
+ r="13.994946"
+ fy="24.241488"
+ fx="61.662098"
+ cy="24.241488"
+ cx="61.662098"
+ id="radialGradient3943"
+ xlink:href="#linearGradient1312"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient1312">
+ <stop
+ id="stop1314"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop1316"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3993">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3995" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0"
+ offset="1"
+ id="stop3997" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2973"
+ id="radialGradient3866"
+ cx="-22.375"
+ cy="18.499998"
+ fx="-22.375"
+ fy="18.499998"
+ r="14.33462"
+ gradientTransform="matrix(1,0,0,1.140022,40.17678,1.347091)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ r="12.289036"
+ fy="63.965388"
+ fx="15.115514"
+ cy="63.965388"
+ cx="15.115514"
+ gradientTransform="scale(1.643990,0.608276)"
+ id="radialGradient5000"
+ xlink:href="#linearGradient4114"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4989">
+ <stop
+ id="stop4991"
+ offset="0"
+ style="stop-color:#47abff;stop-opacity:1" />
+ <stop
+ id="stop4993"
+ offset="0.35955963"
+ style="stop-color:#53a6ff;stop-opacity:1" />
+ <stop
+ id="stop4995"
+ offset="0.79518169"
+ style="stop-color:#286cbb;stop-opacity:1" />
+ <stop
+ id="stop4997"
+ offset="1"
+ style="stop-color:#003d87;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4977">
+ <stop
+ id="stop4979"
+ offset="0.0000000"
+ style="stop-color:#ffffff;stop-opacity:1" />
+ <stop
+ id="stop4981"
+ offset="1.0000000"
+ style="stop-color:#ffffff;stop-opacity:0" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4345"
+ id="radialGradient5878"
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.56833945,-0.14774444,0.12240592,0.69984003,9.8157812,6.7018745)"
+ cx="11.707551"
+ cy="36.527763"
+ fx="11.707551"
+ fy="36.527763"
+ r="14.33681" />
+ <filter
+ id="filter6406"
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ height="1.2"
+ width="1.2"
+ y="-0.15000000000000002"
+ x="-0.050000000000000017">
+ <feFlood
+ id="feFlood6408"
+ flood-opacity="0.50171821305841924"
+ flood-color="rgb(50,51,108)"
+ result="flood"
+ stdDeviation="0.01" />
+ <feComposite
+ id="feComposite6410"
+ in2="SourceGraphic"
+ in="flood"
+ operator="in"
+ result="composite1"
+ k4="-7.2400000000000002"
+ k3="5.5899999999999999"
+ k2="4.9299999999999997"
+ k1="8.1400000000000006" />
+ <feGaussianBlur
+ id="feGaussianBlur6412"
+ stdDeviation="0.20000000000000001"
+ result="blur"
+ in="composite" />
+ <feOffset
+ id="feOffset6414"
+ dx="0.29999999999999999"
+ dy="0.29999999999999999"
+ result="offset"
+ flood-opacity="0.5" />
+ <feComposite
+ id="feComposite6416"
+ in2="offset"
+ in="SourceGraphic"
+ result="composite2"
+ operator="" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ id="filter4258">
+ <feFlood
+ flood-opacity="0.80200501253132828"
+ flood-color="rgb(197,204,222)"
+ result="flood"
+ id="feFlood4260" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="in"
+ result="composite1"
+ id="feComposite4262"
+ flood-opacity="0.80000000000000004" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4264" />
+ <feOffset
+ dx="-0"
+ dy="-0.5"
+ result="offset"
+ id="feOffset4266" />
+ <feComposite
+ in="SourceGraphic"
+ in2="offset"
+ operator="over"
+ result="composite2"
+ id="feComposite4268" />
+ </filter>
+ <filter
+ id="filter4290"
+ inkscape:label="Drop Shadow"
+ style="color-interpolation-filters:sRGB;">
+ <feFlood
+ id="feFlood4292"
+ result="flood"
+ flood-color="rgb(255,255,255)"
+ flood-opacity="0.59999999999999998" />
+ <feComposite
+ id="feComposite4294"
+ result="composite1"
+ operator="in"
+ in2="SourceGraphic"
+ in="flood" />
+ <feGaussianBlur
+ id="feGaussianBlur4296"
+ result="blur"
+ stdDeviation="6.7999999999999998"
+ in="composite1" />
+ <feOffset
+ id="feOffset4298"
+ result="offset"
+ dy="1.9000000000000004"
+ dx="0.69999999999999996"
+ flood-opacity="0.68000000000000005" />
+ <feComposite
+ id="feComposite4300"
+ result="composite2"
+ in2="offset"
+ in="SourceGraphic"
+ operator="" />
+ </filter>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient4977"
+ id="radialGradient4375"
+ cx="19.015932"
+ cy="19.611181"
+ fx="19.015932"
+ fy="19.611181"
+ r="15.627373"
+ gradientTransform="matrix(-0.21582873,1.1173231,-1.0803619,-0.20155681,42.177326,-6.1234)"
+ gradientUnits="userSpaceOnUse" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="0.65098039"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="13.92"
+ inkscape:cx="3.3184766"
+ inkscape:cy="26.850801"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1981"
+ inkscape:window-height="1097"
+ inkscape:window-x="67"
+ inkscape:window-y="27"
+ inkscape:showpageshadow="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-maximized="1"
+ width="512px"
+ units="px"
+ inkscape:snap-global="false"
+ inkscape:snap-to-guides="true"
+ inkscape:snap-others="true"
+ inkscape:pagecheckerboard="false"
+ showborder="true"
+ borderlayer="false"
+ scale-x="2">
+ <sodipodi:guide
+ orientation="0,1"
+ position="4.331,47"
+ id="guide3056"
+ inkscape:label=""
+ inkscape:color="rgb(99,99,125)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="24,41.875"
+ id="guide3058"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="40,25.375"
+ id="guide5866" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="34.626135,29.742679"
+ id="guide5868" />
+ <sodipodi:guide
+ position="3.8890873,1"
+ orientation="0,1"
+ id="guide4521"
+ inkscape:label=""
+ inkscape:color="rgb(106,106,146)" />
+ <sodipodi:guide
+ position="1,17.324"
+ orientation="1,0"
+ id="guide4523"
+ inkscape:label=""
+ inkscape:color="rgb(152,152,181)" />
+ <sodipodi:guide
+ position="47,33.852737"
+ orientation="1,0"
+ id="guide4525"
+ inkscape:label=""
+ inkscape:color="rgb(121,121,172)" />
+ <sodipodi:guide
+ position="13.433908,46"
+ orientation="0,1"
+ id="guide4527"
+ inkscape:label=""
+ inkscape:color="rgb(255,40,0)" />
+ <sodipodi:guide
+ position="8,36.135"
+ orientation="1,0"
+ id="guide4529"
+ inkscape:label=""
+ inkscape:color="rgb(255,11,0)" />
+ <sodipodi:guide
+ position="40,26.436782"
+ orientation="1,0"
+ id="guide4531"
+ inkscape:label=""
+ inkscape:color="rgb(255,6,0)" />
+ <sodipodi:guide
+ position="34.052,2"
+ orientation="0,1"
+ id="guide4533"
+ inkscape:label=""
+ inkscape:color="rgb(255,0,5)" />
+ <sodipodi:guide
+ position="36.879276,26"
+ orientation="0,1"
+ id="guide4542"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="43.660795,14.731391"
+ orientation="0,1"
+ id="guide4546" />
+ <inkscape:grid
+ type="xygrid"
+ id="grid4297" />
+ </sodipodi:namedview>
+ <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:title></dc:title>
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Tuomas Kuosmanen</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
+ <dc:source>http://jimmac.musichall.cz</dc:source>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>internet</rdf:li>
+ <rdf:li>tools</rdf:li>
+ <rdf:li>applications</rdf:li>
+ <rdf:li>category</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Attribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ style="display:inline">
+ <path
+ style="fill:#4c90e8;fill-opacity:1;fill-rule:evenodd;stroke:#094491;stroke-width:3.19036889;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="M 24.00916,2.8848679 35.921225,21.107868 c 7.631218,11.674181 -0.393962,24.296949 -11.919555,24.296949 -11.525593,0 -19.5637412,-12.627213 -11.925606,-24.293557 z"
+ id="path2069"
+ sodipodi:nodetypes="cszsc"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="wave fill"
+ style="display:inline">
+ <path
+ style="display:inline;fill:#094491;fill-opacity:1;fill-rule:evenodd;stroke-width:0.43658349;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 28.249173,24.076764 c -6.513399,-2.656088 -9.930764,4.129513 -8.844266,7.299777 2.363892,6.897492 11.278562,8.624272 17.861607,-2.864877 0,0 0.07021,1.386537 0.139797,2.046235 0.884013,8.376987 -6.162376,13.70953 -13.302295,13.605305 -7.139921,-0.104225 -9.320813,-2.876749 -11.424685,-6.068739 -3.4133569,-5.178732 -2.561398,-13.615464 2.027677,-17.561337 5.237731,-4.328395 11.004287,-2.017675 13.542165,3.543636 z"
+ id="path2969"
+ sodipodi:nodetypes="cscszscc"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ style="display:none"
+ inkscape:label="wave fill light"
+ id="g4327"
+ inkscape:groupmode="layer">
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cscszscc"
+ id="path4329"
+ d="m 24.811452,24.996951 c -6.049549,-2.467385 -7.648816,3.683738 -6.639688,6.628769 2.195546,6.407462 11.948502,7.605174 18.062738,-3.067723 0,0 0.11598,1.28803 0.180639,1.900859 0.82106,7.781844 -5.621926,12.735535 -12.253379,12.638715 -6.631454,-0.09682 -8.657036,-2.672368 -10.611083,-5.637583 -3.170275,-4.810809 -2.429788,-11.327408 1.78168,-14.840553 4.864733,-3.959831 8.798281,-0.09585 9.479093,2.377516 z"
+ style="display:inline;fill:#99c3f9;fill-opacity:1;fill-rule:evenodd;stroke-width:0.40552941;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer6"
+ inkscape:label="wave stroke 1.3px and fill "
+ style="display:none">
+ <path
+ style="display:inline;fill:#99c3f9;fill-opacity:1;fill-rule:evenodd;stroke:#094491 icc-color(sRGB, 0.18046845, 0.27837034, 0.41960784);stroke-width:1.33399999;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 23.761543,24.179576 c -6.481311,-1.72424 -6.932125,6.183803 -5.453851,8.969604 2.408248,4.538333 12.206251,5.377175 17.706883,-3.282742 0,0 0.07651,2.193563 0.02062,2.77892 -0.534334,5.596549 -6.848986,9.789262 -11.999526,9.621298 -5.243605,-0.170999 -8.282469,-2.549917 -10.154056,-5.379263 -3.036492,-4.590373 -2.048612,-10.811985 1.554101,-13.80496 4.248899,-3.646516 7.44994,-0.562347 8.325827,1.097143 z"
+ id="path2969-6"
+ sodipodi:nodetypes="cscssscc"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ style="display:none"
+ inkscape:label="wave stroke 2px and fill "
+ id="g4217"
+ inkscape:groupmode="layer">
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cscssscc"
+ id="path4219"
+ d="m 23.507553,23.925586 c -6.495438,-1.064032 -6.790118,6.421446 -5.149063,9.579179 2.304561,4.434459 10.903063,6.282759 17.3005,-2.825561 0,0 -0.02509,1.990372 -0.08098,2.575729 -0.534334,5.596549 -6.391805,8.925697 -11.542345,8.757733 -5.243605,-0.170999 -8.247979,-2.119778 -10.001662,-5.023677 -2.772156,-4.590373 -2.467304,-9.592836 0.487345,-12.585811 3.100966,-3.879425 7.831125,-2.714919 8.986201,-0.477592 z"
+ style="display:inline;fill:#99c3f9;fill-opacity:1;fill-rule:evenodd;stroke:#094491 icc-color(sRGB, 0.18046845, 0.27837034, 0.41960784);stroke-width:2;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer3"
+ inkscape:label="gradient inner wave "
+ style="display:none">
+ <path
+ style="fill:url(#radialGradient5878);fill-opacity:1;fill-rule:evenodd;stroke:none"
+ d="m 23.55546,23.805666 c -6.490966,-1.345852 -7.700279,4.84717 -6.759909,8.059555 1.100923,3.760845 4.033145,7.507508 11.754513,5.50458 -3.268904,5.846892 -11.255492,4.570412 -13.940353,0.387138 -2.563055,-3.993493 -2.992402,-9.05746 -1.115519,-11.776277 4.049097,-6.655711 9.485026,-4.029293 10.061268,-2.174996 z"
+ id="path3868"
+ sodipodi:nodetypes="cscscc"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ style="display:inline"
+ inkscape:label="solid inner wavelet small"
+ id="g4476"
+ inkscape:groupmode="layer">
+ <path
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="cscszc"
+ id="path4478"
+ d="m 24.158349,21.568695 c -7.295351,0.177796 -8.680227,8.584045 -6.659444,12.185022 2.809336,5.006167 7.071815,5.968946 12.90485,4.121032 -3.330593,3.620896 -10.166138,4.579185 -14.809086,-0.592946 -3.194749,-3.558875 -3.473998,-8.525976 -1.256883,-12.674161 2.217115,-4.148189 6.638012,-5.301549 9.820563,-3.038947 z"
+ style="fill:#83b8f9;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.07657671" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="g4486"
+ inkscape:label="solid inner wavelet larger"
+ style="display:none">
+ <path
+ style="fill:#99c3f9;fill-opacity:1;fill-rule:evenodd;stroke:none"
+ d="m 21.55277,22.974939 c -5.188765,1.005551 -6.173294,6.973638 -4.694095,10.155871 2.191227,4.714034 8.449957,8.07696 17.664862,0.691234 -5.828901,10.827975 -14.873199,7.043001 -17.313798,4.977307 -1.97159,-1.66873 -4.164846,-4.855384 -4.11832,-8.341257 0.09522,-7.134172 6.604293,-9.266569 8.461351,-7.483155 z"
+ id="path4488"
+ sodipodi:nodetypes="cscssc"
+ inkscape:connector-curvature="0" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer2"
+ inkscape:label="Finish"
+ style="display:none"
+ sodipodi:insensitive="true">
+ <path
+ style="display:inline;opacity:0.68800001;fill:url(#radialGradient4375);fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.01508224;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 23.956644,1.939572 -11.98159,18.384686 c -3.9618093,5.166305 -3.6931007,11.439356 -2.4054307,15.8855 5.3079527,10.817522 14.5668877,9.659123 14.5668877,9.659123 0,0 6.868403,-0.005 11.475831,-5.120874 2.283123,-2.535051 5.325726,-9.801674 2.662706,-16.234991 -0.49634,-1.028711 -3.75284,-6.551522 -4.429455,-7.554788 z"
+ id="path4279"
+ inkscape:connector-curvature="0"
+ sodipodi:nodetypes="ccccsccc" />
+ </g>
+</svg>
diff --git a/deluge/ui/data/pixmaps/deluge16.png b/deluge/ui/data/pixmaps/deluge16.png
new file mode 100644
index 0000000..5afdbe4
--- /dev/null
+++ b/deluge/ui/data/pixmaps/deluge16.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/dht.svg b/deluge/ui/data/pixmaps/dht.svg
new file mode 100644
index 0000000..f145d0b
--- /dev/null
+++ b/deluge/ui/data/pixmaps/dht.svg
@@ -0,0 +1,163 @@
+<?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"
+ width="128"
+ height="128"
+ id="svg2"
+ sodipodi:version="0.32"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="dht.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/dht16.png"
+ inkscape:export-xdpi="1.9579109"
+ inkscape:export-ydpi="1.9579109"
+ version="1.1">
+ <defs
+ id="defs4">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ id="perspective10561" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10000"
+ guidetolerance="10"
+ objecttolerance="10"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.6568542"
+ inkscape:cx="51.409667"
+ inkscape:cy="66.916781"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ inkscape:window-width="1861"
+ inkscape:window-height="1176"
+ inkscape:window-x="59"
+ inkscape:window-y="24"
+ showgrid="false"
+ inkscape:showpageshadow="false"
+ inkscape:window-maximized="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" />
+ <dc:title />
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-924.36219)">
+ <path
+ style="opacity:1;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 51.371573,974.02754 4.703341,36.73196"
+ id="path3695"
+ inkscape:export-filename="/home/andrew/dht16-2.png"
+ inkscape:export-xdpi="3.8918922"
+ inkscape:export-ydpi="3.8918922"
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0" />
+ <path
+ style="opacity:1;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 63.256291,969.16777 23.581249,23.7999"
+ id="path3699"
+ inkscape:export-filename="/home/andrew/dht16-2.png"
+ inkscape:export-xdpi="3.8918922"
+ inkscape:export-ydpi="3.8918922"
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0" />
+ <path
+ style="opacity:1;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 31.776499,981.71786 4.647106,-9.48366"
+ id="path3697"
+ inkscape:export-filename="/home/andrew/dht16-2.png"
+ inkscape:export-xdpi="3.8918922"
+ inkscape:export-ydpi="3.8918922"
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0" />
+ <path
+ style="opacity:1;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 69.943212,952.70635 12.971145,2.50294"
+ id="path3691"
+ inkscape:export-filename="/home/andrew/dht16-2.png"
+ inkscape:export-xdpi="3.8918922"
+ inkscape:export-ydpi="3.8918922"
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0" />
+ <path
+ style="opacity:1;fill:none;fill-rule:evenodd;stroke:#000000;stroke-width:3.5;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 67.557838,1019.8019 14.681475,-5.7109"
+ id="path3693"
+ inkscape:export-filename="/home/andrew/dht16-2.png"
+ inkscape:export-xdpi="3.8918922"
+ inkscape:export-ydpi="3.8918922"
+ sodipodi:nodetypes="cc"
+ inkscape:connector-curvature="0" />
+ <circle
+ style="opacity:1;fill:#16c816;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path3437"
+ inkscape:export-filename="/home/andrew/dht16-2.png"
+ inkscape:export-xdpi="3.8918922"
+ inkscape:export-ydpi="3.8918922"
+ cx="47.440121"
+ cy="951.93445"
+ r="24.014807" />
+ <circle
+ style="opacity:1;fill:#4c90e8;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path3443"
+ inkscape:export-filename="/home/andrew/dht16-2.png"
+ inkscape:export-xdpi="3.8918922"
+ inkscape:export-ydpi="3.8918922"
+ cx="23.889254"
+ cy="997.40765"
+ r="18.128822" />
+ <circle
+ style="opacity:1;fill:#b3b3b3;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path3445"
+ inkscape:export-filename="/home/andrew/dht16-2.png"
+ inkscape:export-xdpi="3.8918922"
+ inkscape:export-ydpi="3.8918922"
+ cx="95.936546"
+ cy="960.33722"
+ r="14.597237" />
+ <circle
+ style="opacity:1;fill:#4c90e8;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path3439"
+ inkscape:export-filename="/home/andrew/dht16-2.png"
+ inkscape:export-xdpi="3.8918922"
+ inkscape:export-ydpi="3.8918922"
+ cx="99.223282"
+ cy="1006.9724"
+ r="21.189529" />
+ <circle
+ style="opacity:1;fill:#16c816;fill-opacity:1;stroke:#000000;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ id="path3441"
+ inkscape:export-filename="/home/andrew/dht16-2.png"
+ inkscape:export-xdpi="3.8918922"
+ inkscape:export-ydpi="3.8918922"
+ cx="54.840214"
+ cy="1026.1222"
+ r="18.364264" />
+ </g>
+</svg>
diff --git a/deluge/ui/data/pixmaps/dht16.png b/deluge/ui/data/pixmaps/dht16.png
new file mode 100644
index 0000000..363ee0c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/dht16.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/downloading.svg b/deluge/ui/data/pixmaps/downloading.svg
new file mode 100644
index 0000000..d48cb69
--- /dev/null
+++ b/deluge/ui/data/pixmaps/downloading.svg
@@ -0,0 +1,515 @@
+<?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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg3440"
+ sodipodi:version="0.32"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="downloading.svg"
+ inkscape:export-xdpi="30"
+ inkscape:export-ydpi="30"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="TRUE"
+ version="1.1"
+ inkscape:export-filename="/home/calum/projects/deluge-logo/deluge_download16.svg.png">
+ <defs
+ id="defs3">
+ <color-profile
+ name="sRGB"
+ xlink:href="/usr/share/color/icc/sRGB.icc"
+ id="color-profile4484" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2973">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop2975" />
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:0;"
+ offset="1"
+ id="stop2977" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4126">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop4128" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.16494845;"
+ offset="1.0000000"
+ id="stop4130" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4114">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4116" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop4118" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3962">
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop3964" />
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.15517241"
+ id="stop4134" />
+ <stop
+ style="stop-color:#4074ae;stop-opacity:1.0000000;"
+ offset="0.75000000"
+ id="stop4346" />
+ <stop
+ style="stop-color:#36486c;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop3966" />
+ </linearGradient>
+ <radialGradient
+ r="13.994944"
+ fy="33.506763"
+ fx="-10.089286"
+ cy="33.506763"
+ cx="-10.089286"
+ gradientTransform="matrix(1,0,0,0.791446,-14.01786,-11.28667)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4019"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ gradientTransform="matrix(1,0,0,0.792374,-19.58761,2.818569)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4004"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.792374,0,6.785475)"
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ id="radialGradient3999"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.341185,-0.153831,1.08001,2.395374,-15.42222,-25.62103)"
+ r="13.994946"
+ fy="24.241488"
+ fx="61.662098"
+ cy="24.241488"
+ cx="61.662098"
+ id="radialGradient3943"
+ xlink:href="#linearGradient1312"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient1312">
+ <stop
+ id="stop1314"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop1316"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3993">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3995" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0"
+ offset="1"
+ id="stop3997" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2973"
+ id="radialGradient3866"
+ cx="-22.375"
+ cy="18.499998"
+ fx="-22.375"
+ fy="18.499998"
+ r="14.33462"
+ gradientTransform="matrix(1,0,0,1.140022,40.17678,1.347091)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ r="12.289036"
+ fy="63.965388"
+ fx="15.115514"
+ cy="63.965388"
+ cx="15.115514"
+ gradientTransform="scale(1.643990,0.608276)"
+ id="radialGradient5000"
+ xlink:href="#linearGradient4114"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4989">
+ <stop
+ id="stop4991"
+ offset="0"
+ style="stop-color:#47abff;stop-opacity:1" />
+ <stop
+ id="stop4993"
+ offset="0.35955963"
+ style="stop-color:#53a6ff;stop-opacity:1" />
+ <stop
+ id="stop4995"
+ offset="0.79518169"
+ style="stop-color:#286cbb;stop-opacity:1" />
+ <stop
+ id="stop4997"
+ offset="1"
+ style="stop-color:#003d87;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4977">
+ <stop
+ id="stop4979"
+ offset="0.0000000"
+ style="stop-color:#ffffff;stop-opacity:1" />
+ <stop
+ id="stop4981"
+ offset="1.0000000"
+ style="stop-color:#ffffff;stop-opacity:0" />
+ </linearGradient>
+ <filter
+ id="filter6406"
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ height="1.2"
+ width="1.2"
+ y="-0.15000000000000002"
+ x="-0.050000000000000017">
+ <feFlood
+ id="feFlood6408"
+ flood-opacity="0.50171821305841924"
+ flood-color="rgb(50,51,108)"
+ result="flood"
+ stdDeviation="0.01" />
+ <feComposite
+ id="feComposite6410"
+ in2="SourceGraphic"
+ in="flood"
+ operator="in"
+ result="composite1"
+ k1="8.1400000000000006"
+ k2="4.9299999999999997"
+ k3="5.5899999999999999"
+ k4="-7.2400000000000002" />
+ <feGaussianBlur
+ id="feGaussianBlur6412"
+ stdDeviation="0.20000000000000001"
+ result="blur"
+ in="composite" />
+ <feOffset
+ id="feOffset6414"
+ dx="0.29999999999999999"
+ dy="0.29999999999999999"
+ result="offset"
+ flood-opacity="0.5" />
+ <feComposite
+ id="feComposite6416"
+ in2="offset"
+ in="SourceGraphic"
+ result="composite2"
+ operator="" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ id="filter4258">
+ <feFlood
+ flood-opacity="0.80200501253132828"
+ flood-color="rgb(197,204,222)"
+ result="flood"
+ id="feFlood4260" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="in"
+ result="composite1"
+ id="feComposite4262"
+ flood-opacity="0.80000000000000004" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4264" />
+ <feOffset
+ dx="-0"
+ dy="-0.5"
+ result="offset"
+ id="feOffset4266" />
+ <feComposite
+ in="SourceGraphic"
+ in2="offset"
+ operator="over"
+ result="composite2"
+ id="feComposite4268" />
+ </filter>
+ <filter
+ id="filter4290"
+ inkscape:label="Drop Shadow"
+ style="color-interpolation-filters:sRGB;">
+ <feFlood
+ id="feFlood4292"
+ result="flood"
+ flood-color="rgb(255,255,255)"
+ flood-opacity="0.59999999999999998" />
+ <feComposite
+ id="feComposite4294"
+ result="composite1"
+ operator="in"
+ in2="SourceGraphic"
+ in="flood" />
+ <feGaussianBlur
+ id="feGaussianBlur4296"
+ result="blur"
+ stdDeviation="6.7999999999999998"
+ in="composite1" />
+ <feOffset
+ id="feOffset4298"
+ result="offset"
+ dy="1.9000000000000004"
+ dx="0.69999999999999996"
+ flood-opacity="0.68000000000000005" />
+ <feComposite
+ id="feComposite4300"
+ result="composite2"
+ in2="offset"
+ in="SourceGraphic"
+ operator="" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="0.17254902"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="16"
+ inkscape:cx="22.077879"
+ inkscape:cy="23.209575"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1861"
+ inkscape:window-height="1176"
+ inkscape:window-x="59"
+ inkscape:window-y="24"
+ inkscape:showpageshadow="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-maximized="1"
+ width="512px"
+ units="px"
+ inkscape:snap-global="false"
+ inkscape:snap-to-guides="true"
+ inkscape:snap-others="true">
+ <sodipodi:guide
+ orientation="0,1"
+ position="4.331,47"
+ id="guide3056"
+ inkscape:label=""
+ inkscape:color="rgb(99,99,125)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="23.9375,24.5625"
+ id="guide3058"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="40,25.375"
+ id="guide5866" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="38.448931,29.742679"
+ id="guide5868" />
+ <sodipodi:guide
+ position="3.8890873,1"
+ orientation="0,1"
+ id="guide4521"
+ inkscape:label=""
+ inkscape:color="rgb(106,106,146)" />
+ <sodipodi:guide
+ position="1,17.324"
+ orientation="1,0"
+ id="guide4523"
+ inkscape:label=""
+ inkscape:color="rgb(152,152,181)" />
+ <sodipodi:guide
+ position="47,33.852737"
+ orientation="1,0"
+ id="guide4525"
+ inkscape:label=""
+ inkscape:color="rgb(121,121,172)" />
+ <sodipodi:guide
+ position="13.433908,46"
+ orientation="0,1"
+ id="guide4527"
+ inkscape:label=""
+ inkscape:color="rgb(255,40,0)" />
+ <sodipodi:guide
+ position="8,36.135"
+ orientation="1,0"
+ id="guide4529"
+ inkscape:label=""
+ inkscape:color="rgb(255,11,0)" />
+ <sodipodi:guide
+ position="40,26.436782"
+ orientation="1,0"
+ id="guide4531"
+ inkscape:label=""
+ inkscape:color="rgb(255,6,0)" />
+ <sodipodi:guide
+ position="34.052,2"
+ orientation="0,1"
+ id="guide4533"
+ inkscape:label=""
+ inkscape:color="rgb(255,0,5)" />
+ <sodipodi:guide
+ position="36.879276,26"
+ orientation="0,1"
+ id="guide4542"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="43.660795,14.731391"
+ orientation="0,1"
+ id="guide4546" />
+ <inkscape:grid
+ type="xygrid"
+ id="grid4225" />
+ </sodipodi:namedview>
+ <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:title />
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Tuomas Kuosmanen</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
+ <dc:source>http://jimmac.musichall.cz</dc:source>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>internet</rdf:li>
+ <rdf:li>tools</rdf:li>
+ <rdf:li>applications</rdf:li>
+ <rdf:li>category</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Attribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="base droplet"
+ inkscape:groupmode="layer"
+ style="display:inline">
+ <path
+ style="fill:#16c816;fill-opacity:1;fill-rule:evenodd;stroke:#129b00;stroke-width:3.27999091;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;"
+ d="M 23.295622,2.4822833 35.490217,21.297229 C 43.30243,33.350632 35.08691,46.383433 23.287956,46.383433 11.489002,46.383433 3.2602089,33.346045 11.079504,21.30073 Z"
+ id="path2069"
+ sodipodi:nodetypes="cszsc"
+ inkscape:connector-curvature="0"
+ transform="matrix(0.90726715,0,0,0.89972834,2.9383017,2.518482)" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="seed/download icon"
+ style="display:inline">
+ <rect
+ style="display:inline;opacity:1;fill:#005000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4364-1"
+ width="8.4799452"
+ height="4.5"
+ x="-28.148525"
+ y="-23.929176"
+ transform="scale(-1,-1)" />
+ <path
+ sodipodi:type="star"
+ style="opacity:1;fill:#005000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path5039"
+ sodipodi:sides="3"
+ sodipodi:cx="23.9375"
+ sodipodi:cy="21.1875"
+ sodipodi:r1="4.6958261"
+ sodipodi:r2="2.347913"
+ sodipodi:arg1="0.52359878"
+ sodipodi:arg2="1.5707963"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 28.004205,23.535413 -4.066705,0 -4.066705,0 2.033353,-3.521869 2.033352,-3.52187 2.033352,3.521869 z"
+ transform="matrix(-1.9671947,0,0,-1.1073664,71.085518,58.074825)"
+ inkscape:transform-center-y="-1.3000008" />
+ <rect
+ style="display:inline;opacity:1;fill:#005000;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4364-1-3"
+ width="8.4799452"
+ height="4.5"
+ x="-28.168579"
+ y="-30.214588"
+ transform="scale(-1,-1)" />
+ </g>
+</svg>
diff --git a/deluge/ui/data/pixmaps/downloading16.png b/deluge/ui/data/pixmaps/downloading16.png
new file mode 100644
index 0000000..24d6ffa
--- /dev/null
+++ b/deluge/ui/data/pixmaps/downloading16.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ad.png b/deluge/ui/data/pixmaps/flags/ad.png
new file mode 100644
index 0000000..93656aa
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ad.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ae.png b/deluge/ui/data/pixmaps/flags/ae.png
new file mode 100644
index 0000000..e3abee1
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ae.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/af.png b/deluge/ui/data/pixmaps/flags/af.png
new file mode 100644
index 0000000..8506736
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/af.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ag.png b/deluge/ui/data/pixmaps/flags/ag.png
new file mode 100644
index 0000000..ba1aff9
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ag.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ai.png b/deluge/ui/data/pixmaps/flags/ai.png
new file mode 100644
index 0000000..37c723c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ai.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/al.png b/deluge/ui/data/pixmaps/flags/al.png
new file mode 100644
index 0000000..9b56fcb
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/al.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/am.png b/deluge/ui/data/pixmaps/flags/am.png
new file mode 100644
index 0000000..83ac72e
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/am.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/an.png b/deluge/ui/data/pixmaps/flags/an.png
new file mode 100644
index 0000000..09cfdb1
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/an.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ao.png b/deluge/ui/data/pixmaps/flags/ao.png
new file mode 100644
index 0000000..c2004d6
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ao.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/aq.png b/deluge/ui/data/pixmaps/flags/aq.png
new file mode 100644
index 0000000..76fe736
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/aq.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ar.png b/deluge/ui/data/pixmaps/flags/ar.png
new file mode 100644
index 0000000..f16a290
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ar.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/as.png b/deluge/ui/data/pixmaps/flags/as.png
new file mode 100644
index 0000000..e38ce43
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/as.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/at.png b/deluge/ui/data/pixmaps/flags/at.png
new file mode 100644
index 0000000..25c7e42
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/at.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/au.png b/deluge/ui/data/pixmaps/flags/au.png
new file mode 100644
index 0000000..0de18f0
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/au.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/aw.png b/deluge/ui/data/pixmaps/flags/aw.png
new file mode 100644
index 0000000..788d738
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/aw.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ax.png b/deluge/ui/data/pixmaps/flags/ax.png
new file mode 100644
index 0000000..0060c35
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ax.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/az.png b/deluge/ui/data/pixmaps/flags/az.png
new file mode 100644
index 0000000..cb9c9a1
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/az.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ba.png b/deluge/ui/data/pixmaps/flags/ba.png
new file mode 100644
index 0000000..160c5e2
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ba.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/bb.png b/deluge/ui/data/pixmaps/flags/bb.png
new file mode 100644
index 0000000..c908dcd
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/bb.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/bd.png b/deluge/ui/data/pixmaps/flags/bd.png
new file mode 100644
index 0000000..10266cd
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/bd.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/be.png b/deluge/ui/data/pixmaps/flags/be.png
new file mode 100644
index 0000000..bc09e3c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/be.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/bf.png b/deluge/ui/data/pixmaps/flags/bf.png
new file mode 100644
index 0000000..452329a
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/bf.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/bg.png b/deluge/ui/data/pixmaps/flags/bg.png
new file mode 100644
index 0000000..b60e401
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/bg.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/bh.png b/deluge/ui/data/pixmaps/flags/bh.png
new file mode 100644
index 0000000..1b876e2
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/bh.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/bi.png b/deluge/ui/data/pixmaps/flags/bi.png
new file mode 100644
index 0000000..f4d9adf
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/bi.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/bj.png b/deluge/ui/data/pixmaps/flags/bj.png
new file mode 100644
index 0000000..5740ccc
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/bj.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/bm.png b/deluge/ui/data/pixmaps/flags/bm.png
new file mode 100644
index 0000000..85411bf
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/bm.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/bn.png b/deluge/ui/data/pixmaps/flags/bn.png
new file mode 100644
index 0000000..a0c223f
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/bn.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/bo.png b/deluge/ui/data/pixmaps/flags/bo.png
new file mode 100644
index 0000000..c2ef0f4
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/bo.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/br.png b/deluge/ui/data/pixmaps/flags/br.png
new file mode 100644
index 0000000..b1a884c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/br.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/bs.png b/deluge/ui/data/pixmaps/flags/bs.png
new file mode 100644
index 0000000..959252d
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/bs.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/bt.png b/deluge/ui/data/pixmaps/flags/bt.png
new file mode 100644
index 0000000..2e19696
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/bt.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/bv.png b/deluge/ui/data/pixmaps/flags/bv.png
new file mode 100644
index 0000000..ae9060c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/bv.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/bw.png b/deluge/ui/data/pixmaps/flags/bw.png
new file mode 100644
index 0000000..2dee7fb
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/bw.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/by.png b/deluge/ui/data/pixmaps/flags/by.png
new file mode 100644
index 0000000..29361d6
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/by.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/bz.png b/deluge/ui/data/pixmaps/flags/bz.png
new file mode 100644
index 0000000..88e4ea1
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/bz.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ca.png b/deluge/ui/data/pixmaps/flags/ca.png
new file mode 100644
index 0000000..155eea8
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ca.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/cc.png b/deluge/ui/data/pixmaps/flags/cc.png
new file mode 100644
index 0000000..93509ae
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/cc.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/cd.png b/deluge/ui/data/pixmaps/flags/cd.png
new file mode 100644
index 0000000..69886e6
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/cd.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/cf.png b/deluge/ui/data/pixmaps/flags/cf.png
new file mode 100644
index 0000000..3951b78
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/cf.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/cg.png b/deluge/ui/data/pixmaps/flags/cg.png
new file mode 100644
index 0000000..6f32484
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/cg.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ch.png b/deluge/ui/data/pixmaps/flags/ch.png
new file mode 100644
index 0000000..1553916
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ch.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ci.png b/deluge/ui/data/pixmaps/flags/ci.png
new file mode 100644
index 0000000..ee17406
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ci.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ck.png b/deluge/ui/data/pixmaps/flags/ck.png
new file mode 100644
index 0000000..043a12f
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ck.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/cl.png b/deluge/ui/data/pixmaps/flags/cl.png
new file mode 100644
index 0000000..6c05438
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/cl.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/cm.png b/deluge/ui/data/pixmaps/flags/cm.png
new file mode 100644
index 0000000..e2a7c0a
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/cm.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/cn.png b/deluge/ui/data/pixmaps/flags/cn.png
new file mode 100644
index 0000000..5a893ee
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/cn.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/co.png b/deluge/ui/data/pixmaps/flags/co.png
new file mode 100644
index 0000000..24eb981
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/co.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/cr.png b/deluge/ui/data/pixmaps/flags/cr.png
new file mode 100644
index 0000000..4efd967
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/cr.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/cs.png b/deluge/ui/data/pixmaps/flags/cs.png
new file mode 100644
index 0000000..cd58f89
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/cs.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/cu.png b/deluge/ui/data/pixmaps/flags/cu.png
new file mode 100644
index 0000000..addfb8e
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/cu.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/cv.png b/deluge/ui/data/pixmaps/flags/cv.png
new file mode 100644
index 0000000..6411a62
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/cv.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/cx.png b/deluge/ui/data/pixmaps/flags/cx.png
new file mode 100644
index 0000000..75c8bfa
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/cx.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/cy.png b/deluge/ui/data/pixmaps/flags/cy.png
new file mode 100644
index 0000000..bbacfbe
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/cy.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/cz.png b/deluge/ui/data/pixmaps/flags/cz.png
new file mode 100644
index 0000000..830f6a7
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/cz.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/de.png b/deluge/ui/data/pixmaps/flags/de.png
new file mode 100644
index 0000000..933014a
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/de.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/dj.png b/deluge/ui/data/pixmaps/flags/dj.png
new file mode 100644
index 0000000..883343a
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/dj.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/dk.png b/deluge/ui/data/pixmaps/flags/dk.png
new file mode 100644
index 0000000..408eea2
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/dk.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/dm.png b/deluge/ui/data/pixmaps/flags/dm.png
new file mode 100644
index 0000000..e7671bc
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/dm.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/do.png b/deluge/ui/data/pixmaps/flags/do.png
new file mode 100644
index 0000000..deefc77
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/do.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/dz.png b/deluge/ui/data/pixmaps/flags/dz.png
new file mode 100644
index 0000000..65028f9
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/dz.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ec.png b/deluge/ui/data/pixmaps/flags/ec.png
new file mode 100644
index 0000000..7f93b49
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ec.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ee.png b/deluge/ui/data/pixmaps/flags/ee.png
new file mode 100644
index 0000000..aa2a0b9
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ee.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/eg.png b/deluge/ui/data/pixmaps/flags/eg.png
new file mode 100644
index 0000000..7219431
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/eg.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/eh.png b/deluge/ui/data/pixmaps/flags/eh.png
new file mode 100644
index 0000000..ae7daca
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/eh.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/er.png b/deluge/ui/data/pixmaps/flags/er.png
new file mode 100644
index 0000000..b3644d4
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/er.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/es.png b/deluge/ui/data/pixmaps/flags/es.png
new file mode 100644
index 0000000..9cc55dd
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/es.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/et.png b/deluge/ui/data/pixmaps/flags/et.png
new file mode 100644
index 0000000..f4356fa
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/et.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/fi.png b/deluge/ui/data/pixmaps/flags/fi.png
new file mode 100644
index 0000000..73f8c91
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/fi.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/fj.png b/deluge/ui/data/pixmaps/flags/fj.png
new file mode 100644
index 0000000..0544e7a
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/fj.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/fk.png b/deluge/ui/data/pixmaps/flags/fk.png
new file mode 100644
index 0000000..184e890
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/fk.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/fm.png b/deluge/ui/data/pixmaps/flags/fm.png
new file mode 100644
index 0000000..601dba9
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/fm.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/fo.png b/deluge/ui/data/pixmaps/flags/fo.png
new file mode 100644
index 0000000..b922a4f
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/fo.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/fr.png b/deluge/ui/data/pixmaps/flags/fr.png
new file mode 100644
index 0000000..59346a9
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/fr.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/fx.png b/deluge/ui/data/pixmaps/flags/fx.png
new file mode 100644
index 0000000..e1bb1dd
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/fx.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ga.png b/deluge/ui/data/pixmaps/flags/ga.png
new file mode 100644
index 0000000..63cb013
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ga.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/gb.png b/deluge/ui/data/pixmaps/flags/gb.png
new file mode 100644
index 0000000..95007c7
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/gb.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/gd.png b/deluge/ui/data/pixmaps/flags/gd.png
new file mode 100644
index 0000000..1c7de15
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/gd.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ge.png b/deluge/ui/data/pixmaps/flags/ge.png
new file mode 100644
index 0000000..44685b6
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ge.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/gf.png b/deluge/ui/data/pixmaps/flags/gf.png
new file mode 100644
index 0000000..59346a9
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/gf.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/gg.png b/deluge/ui/data/pixmaps/flags/gg.png
new file mode 100644
index 0000000..f65fbe1
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/gg.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/gh.png b/deluge/ui/data/pixmaps/flags/gh.png
new file mode 100644
index 0000000..bc5741c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/gh.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/gi.png b/deluge/ui/data/pixmaps/flags/gi.png
new file mode 100644
index 0000000..8edbe37
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/gi.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/gl.png b/deluge/ui/data/pixmaps/flags/gl.png
new file mode 100644
index 0000000..d8c022f
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/gl.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/gm.png b/deluge/ui/data/pixmaps/flags/gm.png
new file mode 100644
index 0000000..05be830
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/gm.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/gn.png b/deluge/ui/data/pixmaps/flags/gn.png
new file mode 100644
index 0000000..b738333
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/gn.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/gp.png b/deluge/ui/data/pixmaps/flags/gp.png
new file mode 100644
index 0000000..81a21d3
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/gp.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/gq.png b/deluge/ui/data/pixmaps/flags/gq.png
new file mode 100644
index 0000000..5d22f18
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/gq.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/gr.png b/deluge/ui/data/pixmaps/flags/gr.png
new file mode 100644
index 0000000..9fcb345
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/gr.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/gs.png b/deluge/ui/data/pixmaps/flags/gs.png
new file mode 100644
index 0000000..ab0a7c7
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/gs.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/gt.png b/deluge/ui/data/pixmaps/flags/gt.png
new file mode 100644
index 0000000..a05b89c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/gt.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/gu.png b/deluge/ui/data/pixmaps/flags/gu.png
new file mode 100644
index 0000000..83beb89
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/gu.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/gw.png b/deluge/ui/data/pixmaps/flags/gw.png
new file mode 100644
index 0000000..fe406b2
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/gw.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/gy.png b/deluge/ui/data/pixmaps/flags/gy.png
new file mode 100644
index 0000000..fff34e3
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/gy.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/hk.png b/deluge/ui/data/pixmaps/flags/hk.png
new file mode 100644
index 0000000..6a5625a
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/hk.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/hm.png b/deluge/ui/data/pixmaps/flags/hm.png
new file mode 100644
index 0000000..0de18f0
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/hm.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/hn.png b/deluge/ui/data/pixmaps/flags/hn.png
new file mode 100644
index 0000000..77a009a
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/hn.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/hr.png b/deluge/ui/data/pixmaps/flags/hr.png
new file mode 100644
index 0000000..f56bc29
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/hr.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ht.png b/deluge/ui/data/pixmaps/flags/ht.png
new file mode 100644
index 0000000..16d00e8
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ht.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/hu.png b/deluge/ui/data/pixmaps/flags/hu.png
new file mode 100644
index 0000000..6f20d2b
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/hu.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/id.png b/deluge/ui/data/pixmaps/flags/id.png
new file mode 100644
index 0000000..201ad0f
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/id.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ie.png b/deluge/ui/data/pixmaps/flags/ie.png
new file mode 100644
index 0000000..238d3b3
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ie.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/il.png b/deluge/ui/data/pixmaps/flags/il.png
new file mode 100644
index 0000000..9c73906
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/il.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/in.png b/deluge/ui/data/pixmaps/flags/in.png
new file mode 100644
index 0000000..0b515d7
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/in.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/io.png b/deluge/ui/data/pixmaps/flags/io.png
new file mode 100644
index 0000000..5e4d175
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/io.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/iq.png b/deluge/ui/data/pixmaps/flags/iq.png
new file mode 100644
index 0000000..cb1ca16
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/iq.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ir.png b/deluge/ui/data/pixmaps/flags/ir.png
new file mode 100644
index 0000000..f6d1027
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ir.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/is.png b/deluge/ui/data/pixmaps/flags/is.png
new file mode 100644
index 0000000..f7383cb
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/is.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/it.png b/deluge/ui/data/pixmaps/flags/it.png
new file mode 100644
index 0000000..c3c3143
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/it.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/je.png b/deluge/ui/data/pixmaps/flags/je.png
new file mode 100644
index 0000000..0d5752d
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/je.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/jm.png b/deluge/ui/data/pixmaps/flags/jm.png
new file mode 100644
index 0000000..1f8b4be
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/jm.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/jo.png b/deluge/ui/data/pixmaps/flags/jo.png
new file mode 100644
index 0000000..8202d6c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/jo.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/jp.png b/deluge/ui/data/pixmaps/flags/jp.png
new file mode 100644
index 0000000..e470d75
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/jp.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ke.png b/deluge/ui/data/pixmaps/flags/ke.png
new file mode 100644
index 0000000..212cc2e
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ke.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/kg.png b/deluge/ui/data/pixmaps/flags/kg.png
new file mode 100644
index 0000000..848da86
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/kg.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/kh.png b/deluge/ui/data/pixmaps/flags/kh.png
new file mode 100644
index 0000000..bf961c5
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/kh.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ki.png b/deluge/ui/data/pixmaps/flags/ki.png
new file mode 100644
index 0000000..db57066
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ki.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/km.png b/deluge/ui/data/pixmaps/flags/km.png
new file mode 100644
index 0000000..7ead97e
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/km.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/kn.png b/deluge/ui/data/pixmaps/flags/kn.png
new file mode 100644
index 0000000..e408227
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/kn.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/kp.png b/deluge/ui/data/pixmaps/flags/kp.png
new file mode 100644
index 0000000..b517d1c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/kp.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/kr.png b/deluge/ui/data/pixmaps/flags/kr.png
new file mode 100644
index 0000000..ea3122e
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/kr.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/kw.png b/deluge/ui/data/pixmaps/flags/kw.png
new file mode 100644
index 0000000..a3a7fcb
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/kw.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ky.png b/deluge/ui/data/pixmaps/flags/ky.png
new file mode 100644
index 0000000..63b5546
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ky.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/kz.png b/deluge/ui/data/pixmaps/flags/kz.png
new file mode 100644
index 0000000..193cf38
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/kz.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/la.png b/deluge/ui/data/pixmaps/flags/la.png
new file mode 100644
index 0000000..85b6097
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/la.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/lb.png b/deluge/ui/data/pixmaps/flags/lb.png
new file mode 100644
index 0000000..b8e9c2f
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/lb.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/lc.png b/deluge/ui/data/pixmaps/flags/lc.png
new file mode 100644
index 0000000..d8a8656
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/lc.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/li.png b/deluge/ui/data/pixmaps/flags/li.png
new file mode 100644
index 0000000..6bb7b2b
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/li.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/lk.png b/deluge/ui/data/pixmaps/flags/lk.png
new file mode 100644
index 0000000..4f16cd7
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/lk.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/lr.png b/deluge/ui/data/pixmaps/flags/lr.png
new file mode 100644
index 0000000..d76e171
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/lr.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ls.png b/deluge/ui/data/pixmaps/flags/ls.png
new file mode 100644
index 0000000..02cdd66
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ls.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/lt.png b/deluge/ui/data/pixmaps/flags/lt.png
new file mode 100644
index 0000000..e0714c5
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/lt.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/lu.png b/deluge/ui/data/pixmaps/flags/lu.png
new file mode 100644
index 0000000..f750b0c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/lu.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/lv.png b/deluge/ui/data/pixmaps/flags/lv.png
new file mode 100644
index 0000000..f357095
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/lv.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ly.png b/deluge/ui/data/pixmaps/flags/ly.png
new file mode 100644
index 0000000..9e35e38
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ly.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ma.png b/deluge/ui/data/pixmaps/flags/ma.png
new file mode 100644
index 0000000..b8f8dec
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ma.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/mc.png b/deluge/ui/data/pixmaps/flags/mc.png
new file mode 100644
index 0000000..67099ea
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/mc.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/md.png b/deluge/ui/data/pixmaps/flags/md.png
new file mode 100644
index 0000000..6ff6cf5
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/md.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/me.png b/deluge/ui/data/pixmaps/flags/me.png
new file mode 100644
index 0000000..36cbdd5
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/me.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/mg.png b/deluge/ui/data/pixmaps/flags/mg.png
new file mode 100644
index 0000000..d9313e2
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/mg.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/mh.png b/deluge/ui/data/pixmaps/flags/mh.png
new file mode 100644
index 0000000..7618cc6
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/mh.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/mk.png b/deluge/ui/data/pixmaps/flags/mk.png
new file mode 100644
index 0000000..1c98d51
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/mk.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ml.png b/deluge/ui/data/pixmaps/flags/ml.png
new file mode 100644
index 0000000..d59cea8
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ml.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/mm.png b/deluge/ui/data/pixmaps/flags/mm.png
new file mode 100644
index 0000000..175fc57
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/mm.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/mn.png b/deluge/ui/data/pixmaps/flags/mn.png
new file mode 100644
index 0000000..0ad97b3
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/mn.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/mo.png b/deluge/ui/data/pixmaps/flags/mo.png
new file mode 100644
index 0000000..f49e677
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/mo.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/mp.png b/deluge/ui/data/pixmaps/flags/mp.png
new file mode 100644
index 0000000..e91b75b
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/mp.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/mq.png b/deluge/ui/data/pixmaps/flags/mq.png
new file mode 100644
index 0000000..5c5ae82
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/mq.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/mr.png b/deluge/ui/data/pixmaps/flags/mr.png
new file mode 100644
index 0000000..91d9a5a
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/mr.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ms.png b/deluge/ui/data/pixmaps/flags/ms.png
new file mode 100644
index 0000000..d45c16c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ms.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/mt.png b/deluge/ui/data/pixmaps/flags/mt.png
new file mode 100644
index 0000000..497c59f
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/mt.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/mu.png b/deluge/ui/data/pixmaps/flags/mu.png
new file mode 100644
index 0000000..ecdc186
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/mu.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/mv.png b/deluge/ui/data/pixmaps/flags/mv.png
new file mode 100644
index 0000000..b172944
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/mv.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/mw.png b/deluge/ui/data/pixmaps/flags/mw.png
new file mode 100644
index 0000000..cb9b61f
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/mw.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/mx.png b/deluge/ui/data/pixmaps/flags/mx.png
new file mode 100644
index 0000000..b8e70b8
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/mx.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/my.png b/deluge/ui/data/pixmaps/flags/my.png
new file mode 100644
index 0000000..879cf51
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/my.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/mz.png b/deluge/ui/data/pixmaps/flags/mz.png
new file mode 100644
index 0000000..3554592
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/mz.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/na.png b/deluge/ui/data/pixmaps/flags/na.png
new file mode 100644
index 0000000..426cde9
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/na.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/nc.png b/deluge/ui/data/pixmaps/flags/nc.png
new file mode 100644
index 0000000..88c35ac
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/nc.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ne.png b/deluge/ui/data/pixmaps/flags/ne.png
new file mode 100644
index 0000000..eff1c35
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ne.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/nf.png b/deluge/ui/data/pixmaps/flags/nf.png
new file mode 100644
index 0000000..5ddabca
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/nf.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ng.png b/deluge/ui/data/pixmaps/flags/ng.png
new file mode 100644
index 0000000..e2f2b61
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ng.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ni.png b/deluge/ui/data/pixmaps/flags/ni.png
new file mode 100644
index 0000000..9332b95
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ni.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/nl.png b/deluge/ui/data/pixmaps/flags/nl.png
new file mode 100644
index 0000000..267d1dc
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/nl.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/no.png b/deluge/ui/data/pixmaps/flags/no.png
new file mode 100644
index 0000000..ae9060c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/no.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/np.png b/deluge/ui/data/pixmaps/flags/np.png
new file mode 100644
index 0000000..6637ea5
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/np.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/nr.png b/deluge/ui/data/pixmaps/flags/nr.png
new file mode 100644
index 0000000..60f7346
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/nr.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/nu.png b/deluge/ui/data/pixmaps/flags/nu.png
new file mode 100644
index 0000000..21e52a7
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/nu.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/nz.png b/deluge/ui/data/pixmaps/flags/nz.png
new file mode 100644
index 0000000..d5a7ebc
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/nz.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/om.png b/deluge/ui/data/pixmaps/flags/om.png
new file mode 100644
index 0000000..6dc30d4
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/om.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/pa.png b/deluge/ui/data/pixmaps/flags/pa.png
new file mode 100644
index 0000000..8954436
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/pa.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/pe.png b/deluge/ui/data/pixmaps/flags/pe.png
new file mode 100644
index 0000000..9fc0740
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/pe.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/pf.png b/deluge/ui/data/pixmaps/flags/pf.png
new file mode 100644
index 0000000..ea4d046
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/pf.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/pg.png b/deluge/ui/data/pixmaps/flags/pg.png
new file mode 100644
index 0000000..c97ce6a
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/pg.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ph.png b/deluge/ui/data/pixmaps/flags/ph.png
new file mode 100644
index 0000000..67d2891
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ph.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/pk.png b/deluge/ui/data/pixmaps/flags/pk.png
new file mode 100644
index 0000000..a92d71c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/pk.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/pl.png b/deluge/ui/data/pixmaps/flags/pl.png
new file mode 100644
index 0000000..2c4f2ed
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/pl.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/pm.png b/deluge/ui/data/pixmaps/flags/pm.png
new file mode 100644
index 0000000..ce429f0
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/pm.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/pn.png b/deluge/ui/data/pixmaps/flags/pn.png
new file mode 100644
index 0000000..8577faf
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/pn.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/pr.png b/deluge/ui/data/pixmaps/flags/pr.png
new file mode 100644
index 0000000..60d3599
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/pr.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ps.png b/deluge/ui/data/pixmaps/flags/ps.png
new file mode 100644
index 0000000..ceda7d7
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ps.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/pt.png b/deluge/ui/data/pixmaps/flags/pt.png
new file mode 100644
index 0000000..c489acd
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/pt.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/pw.png b/deluge/ui/data/pixmaps/flags/pw.png
new file mode 100644
index 0000000..668c93a
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/pw.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/py.png b/deluge/ui/data/pixmaps/flags/py.png
new file mode 100644
index 0000000..d5e943b
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/py.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/qa.png b/deluge/ui/data/pixmaps/flags/qa.png
new file mode 100644
index 0000000..08a3793
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/qa.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/re.png b/deluge/ui/data/pixmaps/flags/re.png
new file mode 100644
index 0000000..59346a9
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/re.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ro.png b/deluge/ui/data/pixmaps/flags/ro.png
new file mode 100644
index 0000000..f80a44a
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ro.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/rs.png b/deluge/ui/data/pixmaps/flags/rs.png
new file mode 100644
index 0000000..9a35bcc
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/rs.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ru.png b/deluge/ui/data/pixmaps/flags/ru.png
new file mode 100644
index 0000000..723f732
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ru.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/rw.png b/deluge/ui/data/pixmaps/flags/rw.png
new file mode 100644
index 0000000..0d31437
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/rw.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/sa.png b/deluge/ui/data/pixmaps/flags/sa.png
new file mode 100644
index 0000000..2fc1704
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/sa.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/sb.png b/deluge/ui/data/pixmaps/flags/sb.png
new file mode 100644
index 0000000..65998c6
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/sb.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/sc.png b/deluge/ui/data/pixmaps/flags/sc.png
new file mode 100644
index 0000000..c25e113
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/sc.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/sd.png b/deluge/ui/data/pixmaps/flags/sd.png
new file mode 100644
index 0000000..d22408c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/sd.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/se.png b/deluge/ui/data/pixmaps/flags/se.png
new file mode 100644
index 0000000..482101a
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/se.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/sg.png b/deluge/ui/data/pixmaps/flags/sg.png
new file mode 100644
index 0000000..055e04a
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/sg.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/sh.png b/deluge/ui/data/pixmaps/flags/sh.png
new file mode 100644
index 0000000..838dad7
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/sh.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/si.png b/deluge/ui/data/pixmaps/flags/si.png
new file mode 100644
index 0000000..206721e
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/si.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/sj.png b/deluge/ui/data/pixmaps/flags/sj.png
new file mode 100644
index 0000000..ae9060c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/sj.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/sk.png b/deluge/ui/data/pixmaps/flags/sk.png
new file mode 100644
index 0000000..acdd189
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/sk.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/sl.png b/deluge/ui/data/pixmaps/flags/sl.png
new file mode 100644
index 0000000..d50e5d4
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/sl.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/sm.png b/deluge/ui/data/pixmaps/flags/sm.png
new file mode 100644
index 0000000..168dc78
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/sm.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/sn.png b/deluge/ui/data/pixmaps/flags/sn.png
new file mode 100644
index 0000000..4029d3a
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/sn.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/so.png b/deluge/ui/data/pixmaps/flags/so.png
new file mode 100644
index 0000000..b17fcf2
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/so.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/sr.png b/deluge/ui/data/pixmaps/flags/sr.png
new file mode 100644
index 0000000..e790291
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/sr.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/st.png b/deluge/ui/data/pixmaps/flags/st.png
new file mode 100644
index 0000000..63c3e12
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/st.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/sv.png b/deluge/ui/data/pixmaps/flags/sv.png
new file mode 100644
index 0000000..309eb39
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/sv.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/sy.png b/deluge/ui/data/pixmaps/flags/sy.png
new file mode 100644
index 0000000..8a369aa
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/sy.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/sz.png b/deluge/ui/data/pixmaps/flags/sz.png
new file mode 100644
index 0000000..2da7509
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/sz.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/tc.png b/deluge/ui/data/pixmaps/flags/tc.png
new file mode 100644
index 0000000..c7b7354
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/tc.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/td.png b/deluge/ui/data/pixmaps/flags/td.png
new file mode 100644
index 0000000..c82f450
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/td.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/tf.png b/deluge/ui/data/pixmaps/flags/tf.png
new file mode 100644
index 0000000..bf80773
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/tf.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/tg.png b/deluge/ui/data/pixmaps/flags/tg.png
new file mode 100644
index 0000000..fecc10c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/tg.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/th.png b/deluge/ui/data/pixmaps/flags/th.png
new file mode 100644
index 0000000..bf73994
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/th.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/tj.png b/deluge/ui/data/pixmaps/flags/tj.png
new file mode 100644
index 0000000..3efd165
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/tj.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/tk.png b/deluge/ui/data/pixmaps/flags/tk.png
new file mode 100644
index 0000000..218ca79
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/tk.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/tl.png b/deluge/ui/data/pixmaps/flags/tl.png
new file mode 100644
index 0000000..d7c41a9
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/tl.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/tm.png b/deluge/ui/data/pixmaps/flags/tm.png
new file mode 100644
index 0000000..a2aed22
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/tm.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/tn.png b/deluge/ui/data/pixmaps/flags/tn.png
new file mode 100644
index 0000000..61086fa
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/tn.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/to.png b/deluge/ui/data/pixmaps/flags/to.png
new file mode 100644
index 0000000..8e49d30
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/to.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/tp.png b/deluge/ui/data/pixmaps/flags/tp.png
new file mode 100644
index 0000000..aa298c0
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/tp.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/tr.png b/deluge/ui/data/pixmaps/flags/tr.png
new file mode 100644
index 0000000..09f82d0
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/tr.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/tt.png b/deluge/ui/data/pixmaps/flags/tt.png
new file mode 100644
index 0000000..18b09e1
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/tt.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/tv.png b/deluge/ui/data/pixmaps/flags/tv.png
new file mode 100644
index 0000000..ff201fd
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/tv.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/tw.png b/deluge/ui/data/pixmaps/flags/tw.png
new file mode 100644
index 0000000..9bf75fd
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/tw.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/tz.png b/deluge/ui/data/pixmaps/flags/tz.png
new file mode 100644
index 0000000..3543543
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/tz.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ua.png b/deluge/ui/data/pixmaps/flags/ua.png
new file mode 100644
index 0000000..e09f110
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ua.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ug.png b/deluge/ui/data/pixmaps/flags/ug.png
new file mode 100644
index 0000000..d1d9bc0
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ug.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/um.png b/deluge/ui/data/pixmaps/flags/um.png
new file mode 100644
index 0000000..2694ab9
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/um.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/us.png b/deluge/ui/data/pixmaps/flags/us.png
new file mode 100644
index 0000000..68707f4
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/us.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/uy.png b/deluge/ui/data/pixmaps/flags/uy.png
new file mode 100644
index 0000000..4c51567
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/uy.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/uz.png b/deluge/ui/data/pixmaps/flags/uz.png
new file mode 100644
index 0000000..e85d67d
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/uz.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/va.png b/deluge/ui/data/pixmaps/flags/va.png
new file mode 100644
index 0000000..582e2ba
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/va.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/vc.png b/deluge/ui/data/pixmaps/flags/vc.png
new file mode 100644
index 0000000..e572611
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/vc.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ve.png b/deluge/ui/data/pixmaps/flags/ve.png
new file mode 100644
index 0000000..0899d54
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ve.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/vg.png b/deluge/ui/data/pixmaps/flags/vg.png
new file mode 100644
index 0000000..d9f04d6
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/vg.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/vi.png b/deluge/ui/data/pixmaps/flags/vi.png
new file mode 100644
index 0000000..2b887b3
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/vi.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/vn.png b/deluge/ui/data/pixmaps/flags/vn.png
new file mode 100644
index 0000000..335e9fc
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/vn.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/vu.png b/deluge/ui/data/pixmaps/flags/vu.png
new file mode 100644
index 0000000..f3fa6e6
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/vu.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/wf.png b/deluge/ui/data/pixmaps/flags/wf.png
new file mode 100644
index 0000000..1d6460e
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/wf.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ws.png b/deluge/ui/data/pixmaps/flags/ws.png
new file mode 100644
index 0000000..628f387
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ws.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/ye.png b/deluge/ui/data/pixmaps/flags/ye.png
new file mode 100644
index 0000000..b3f760b
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/ye.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/yt.png b/deluge/ui/data/pixmaps/flags/yt.png
new file mode 100644
index 0000000..d0c5a2c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/yt.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/yu.png b/deluge/ui/data/pixmaps/flags/yu.png
new file mode 100644
index 0000000..4827aec
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/yu.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/za.png b/deluge/ui/data/pixmaps/flags/za.png
new file mode 100644
index 0000000..53ba7e1
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/za.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/zm.png b/deluge/ui/data/pixmaps/flags/zm.png
new file mode 100644
index 0000000..2911ca7
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/zm.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/flags/zw.png b/deluge/ui/data/pixmaps/flags/zw.png
new file mode 100644
index 0000000..9c06501
--- /dev/null
+++ b/deluge/ui/data/pixmaps/flags/zw.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/inactive.svg b/deluge/ui/data/pixmaps/inactive.svg
new file mode 100644
index 0000000..644f5f3
--- /dev/null
+++ b/deluge/ui/data/pixmaps/inactive.svg
@@ -0,0 +1,492 @@
+<?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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg3440"
+ sodipodi:version="0.32"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="deluge_paused.svg"
+ inkscape:export-xdpi="960"
+ inkscape:export-ydpi="960"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="TRUE"
+ version="1.1"
+ inkscape:export-filename="/home/calum/projects/deluge-logo/deluge.512.x.png">
+ <defs
+ id="defs3">
+ <color-profile
+ name="sRGB"
+ xlink:href="/usr/share/color/icc/sRGB.icc"
+ id="color-profile4484" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2973">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop2975" />
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:0;"
+ offset="1"
+ id="stop2977" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4126">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop4128" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.16494845;"
+ offset="1.0000000"
+ id="stop4130" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4114">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4116" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop4118" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3962">
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop3964" />
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.15517241"
+ id="stop4134" />
+ <stop
+ style="stop-color:#4074ae;stop-opacity:1.0000000;"
+ offset="0.75000000"
+ id="stop4346" />
+ <stop
+ style="stop-color:#36486c;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop3966" />
+ </linearGradient>
+ <radialGradient
+ r="13.994944"
+ fy="33.506763"
+ fx="-10.089286"
+ cy="33.506763"
+ cx="-10.089286"
+ gradientTransform="matrix(1,0,0,0.791446,-14.01786,-11.28667)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4019"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ gradientTransform="matrix(1,0,0,0.792374,-19.58761,2.818569)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4004"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.792374,0,6.785475)"
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ id="radialGradient3999"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.341185,-0.153831,1.08001,2.395374,-15.42222,-25.62103)"
+ r="13.994946"
+ fy="24.241488"
+ fx="61.662098"
+ cy="24.241488"
+ cx="61.662098"
+ id="radialGradient3943"
+ xlink:href="#linearGradient1312"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient1312">
+ <stop
+ id="stop1314"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop1316"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3993">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3995" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0"
+ offset="1"
+ id="stop3997" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2973"
+ id="radialGradient3866"
+ cx="-22.375"
+ cy="18.499998"
+ fx="-22.375"
+ fy="18.499998"
+ r="14.33462"
+ gradientTransform="matrix(1,0,0,1.140022,40.17678,1.347091)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ r="12.289036"
+ fy="63.965388"
+ fx="15.115514"
+ cy="63.965388"
+ cx="15.115514"
+ gradientTransform="scale(1.643990,0.608276)"
+ id="radialGradient5000"
+ xlink:href="#linearGradient4114"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4989">
+ <stop
+ id="stop4991"
+ offset="0"
+ style="stop-color:#47abff;stop-opacity:1" />
+ <stop
+ id="stop4993"
+ offset="0.35955963"
+ style="stop-color:#53a6ff;stop-opacity:1" />
+ <stop
+ id="stop4995"
+ offset="0.79518169"
+ style="stop-color:#286cbb;stop-opacity:1" />
+ <stop
+ id="stop4997"
+ offset="1"
+ style="stop-color:#003d87;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4977">
+ <stop
+ id="stop4979"
+ offset="0.0000000"
+ style="stop-color:#ffffff;stop-opacity:1" />
+ <stop
+ id="stop4981"
+ offset="1.0000000"
+ style="stop-color:#ffffff;stop-opacity:0" />
+ </linearGradient>
+ <filter
+ id="filter6406"
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ height="1.2"
+ width="1.2"
+ y="-0.15000000000000002"
+ x="-0.050000000000000017">
+ <feFlood
+ id="feFlood6408"
+ flood-opacity="0.50171821305841924"
+ flood-color="rgb(50,51,108)"
+ result="flood"
+ stdDeviation="0.01" />
+ <feComposite
+ id="feComposite6410"
+ in2="SourceGraphic"
+ in="flood"
+ operator="in"
+ result="composite1"
+ k1="8.1400000000000006"
+ k2="4.9299999999999997"
+ k3="5.5899999999999999"
+ k4="-7.2400000000000002" />
+ <feGaussianBlur
+ id="feGaussianBlur6412"
+ stdDeviation="0.20000000000000001"
+ result="blur"
+ in="composite" />
+ <feOffset
+ id="feOffset6414"
+ dx="0.29999999999999999"
+ dy="0.29999999999999999"
+ result="offset"
+ flood-opacity="0.5" />
+ <feComposite
+ id="feComposite6416"
+ in2="offset"
+ in="SourceGraphic"
+ result="composite2"
+ operator="" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ id="filter4258">
+ <feFlood
+ flood-opacity="0.80200501253132828"
+ flood-color="rgb(197,204,222)"
+ result="flood"
+ id="feFlood4260" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="in"
+ result="composite1"
+ id="feComposite4262"
+ flood-opacity="0.80000000000000004" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4264" />
+ <feOffset
+ dx="-0"
+ dy="-0.5"
+ result="offset"
+ id="feOffset4266" />
+ <feComposite
+ in="SourceGraphic"
+ in2="offset"
+ operator="over"
+ result="composite2"
+ id="feComposite4268" />
+ </filter>
+ <filter
+ id="filter4290"
+ inkscape:label="Drop Shadow"
+ style="color-interpolation-filters:sRGB;">
+ <feFlood
+ id="feFlood4292"
+ result="flood"
+ flood-color="rgb(255,255,255)"
+ flood-opacity="0.59999999999999998" />
+ <feComposite
+ id="feComposite4294"
+ result="composite1"
+ operator="in"
+ in2="SourceGraphic"
+ in="flood" />
+ <feGaussianBlur
+ id="feGaussianBlur4296"
+ result="blur"
+ stdDeviation="6.7999999999999998"
+ in="composite1" />
+ <feOffset
+ id="feOffset4298"
+ result="offset"
+ dy="1.9000000000000004"
+ dx="0.69999999999999996"
+ flood-opacity="0.68000000000000005" />
+ <feComposite
+ id="feComposite4300"
+ result="composite2"
+ in2="offset"
+ in="SourceGraphic"
+ operator="" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="0.17254902"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="16"
+ inkscape:cx="44.452879"
+ inkscape:cy="23.209575"
+ inkscape:current-layer="layer5"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1861"
+ inkscape:window-height="1176"
+ inkscape:window-x="59"
+ inkscape:window-y="24"
+ inkscape:showpageshadow="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-maximized="1"
+ width="512px"
+ units="px"
+ inkscape:snap-global="false"
+ inkscape:snap-to-guides="true"
+ inkscape:snap-others="true">
+ <sodipodi:guide
+ orientation="0,1"
+ position="4.331,47"
+ id="guide3056"
+ inkscape:label=""
+ inkscape:color="rgb(99,99,125)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="23.9375,24.5625"
+ id="guide3058"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="40,25.375"
+ id="guide5866" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="38.448931,29.742679"
+ id="guide5868" />
+ <sodipodi:guide
+ position="3.8890873,1"
+ orientation="0,1"
+ id="guide4521"
+ inkscape:label=""
+ inkscape:color="rgb(106,106,146)" />
+ <sodipodi:guide
+ position="1,17.324"
+ orientation="1,0"
+ id="guide4523"
+ inkscape:label=""
+ inkscape:color="rgb(152,152,181)" />
+ <sodipodi:guide
+ position="47,33.852737"
+ orientation="1,0"
+ id="guide4525"
+ inkscape:label=""
+ inkscape:color="rgb(121,121,172)" />
+ <sodipodi:guide
+ position="13.433908,46"
+ orientation="0,1"
+ id="guide4527"
+ inkscape:label=""
+ inkscape:color="rgb(255,40,0)" />
+ <sodipodi:guide
+ position="8,36.135"
+ orientation="1,0"
+ id="guide4529"
+ inkscape:label=""
+ inkscape:color="rgb(255,11,0)" />
+ <sodipodi:guide
+ position="40,26.436782"
+ orientation="1,0"
+ id="guide4531"
+ inkscape:label=""
+ inkscape:color="rgb(255,6,0)" />
+ <sodipodi:guide
+ position="34.052,2"
+ orientation="0,1"
+ id="guide4533"
+ inkscape:label=""
+ inkscape:color="rgb(255,0,5)" />
+ <sodipodi:guide
+ position="36.879276,26"
+ orientation="0,1"
+ id="guide4542"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="43.660795,14.731391"
+ orientation="0,1"
+ id="guide4546" />
+ </sodipodi:namedview>
+ <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:title />
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Tuomas Kuosmanen</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
+ <dc:source>http://jimmac.musichall.cz</dc:source>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>internet</rdf:li>
+ <rdf:li>tools</rdf:li>
+ <rdf:li>applications</rdf:li>
+ <rdf:li>category</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Attribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="base droplet"
+ inkscape:groupmode="layer"
+ style="display:inline">
+ <path
+ style="fill:#cccccc;fill-opacity:1;fill-rule:evenodd;stroke:#999999;stroke-width:3.27999091;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;filter:url(#filter6406)"
+ d="M 23.295622,2.4822833 35.490217,21.297229 C 43.30243,33.350632 35.08691,46.383433 23.287956,46.383433 11.489002,46.383433 3.2602089,33.346045 11.079504,21.30073 Z"
+ id="path2069"
+ sodipodi:nodetypes="cszsc"
+ inkscape:connector-curvature="0"
+ transform="matrix(0.90726715,0,0,0.89972834,2.9383017,2.518482)" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="paused icon">
+ <rect
+ style="display:inline;opacity:1;fill:#666666;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4364"
+ width="5.5"
+ height="16"
+ x="25.3125"
+ y="22" />
+ <rect
+ style="display:inline;opacity:1;fill:#666666;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4364-1"
+ width="5.5"
+ height="16"
+ x="17.125"
+ y="22" />
+ </g>
+</svg>
diff --git a/deluge/ui/data/pixmaps/inactive16.png b/deluge/ui/data/pixmaps/inactive16.png
new file mode 100644
index 0000000..9f38e77
--- /dev/null
+++ b/deluge/ui/data/pixmaps/inactive16.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/loading.gif b/deluge/ui/data/pixmaps/loading.gif
new file mode 100644
index 0000000..09d621e
--- /dev/null
+++ b/deluge/ui/data/pixmaps/loading.gif
Binary files differ
diff --git a/deluge/ui/data/pixmaps/magnet.png b/deluge/ui/data/pixmaps/magnet.png
new file mode 100644
index 0000000..a192cd8
--- /dev/null
+++ b/deluge/ui/data/pixmaps/magnet.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/queued.svg b/deluge/ui/data/pixmaps/queued.svg
new file mode 100644
index 0000000..a3c2d1c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/queued.svg
@@ -0,0 +1,532 @@
+<?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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg3440"
+ sodipodi:version="0.32"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="queued.svg"
+ inkscape:export-xdpi="960"
+ inkscape:export-ydpi="960"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="TRUE"
+ version="1.1"
+ inkscape:export-filename="/home/calum/projects/deluge-logo/deluge.512.x.png">
+ <defs
+ id="defs3">
+ <color-profile
+ name="sRGB"
+ xlink:href="/usr/share/color/icc/sRGB.icc"
+ id="color-profile4484" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2973">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop2975" />
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:0;"
+ offset="1"
+ id="stop2977" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4126">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop4128" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.16494845;"
+ offset="1.0000000"
+ id="stop4130" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4114">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4116" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop4118" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3962">
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop3964" />
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.15517241"
+ id="stop4134" />
+ <stop
+ style="stop-color:#4074ae;stop-opacity:1.0000000;"
+ offset="0.75000000"
+ id="stop4346" />
+ <stop
+ style="stop-color:#36486c;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop3966" />
+ </linearGradient>
+ <radialGradient
+ r="13.994944"
+ fy="33.506763"
+ fx="-10.089286"
+ cy="33.506763"
+ cx="-10.089286"
+ gradientTransform="matrix(1,0,0,0.791446,-14.01786,-11.28667)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4019"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ gradientTransform="matrix(1,0,0,0.792374,-19.58761,2.818569)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4004"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.792374,0,6.785475)"
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ id="radialGradient3999"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.341185,-0.153831,1.08001,2.395374,-15.42222,-25.62103)"
+ r="13.994946"
+ fy="24.241488"
+ fx="61.662098"
+ cy="24.241488"
+ cx="61.662098"
+ id="radialGradient3943"
+ xlink:href="#linearGradient1312"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient1312">
+ <stop
+ id="stop1314"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop1316"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3993">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3995" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0"
+ offset="1"
+ id="stop3997" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2973"
+ id="radialGradient3866"
+ cx="-22.375"
+ cy="18.499998"
+ fx="-22.375"
+ fy="18.499998"
+ r="14.33462"
+ gradientTransform="matrix(1,0,0,1.140022,40.17678,1.347091)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ r="12.289036"
+ fy="63.965388"
+ fx="15.115514"
+ cy="63.965388"
+ cx="15.115514"
+ gradientTransform="scale(1.643990,0.608276)"
+ id="radialGradient5000"
+ xlink:href="#linearGradient4114"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4989">
+ <stop
+ id="stop4991"
+ offset="0"
+ style="stop-color:#47abff;stop-opacity:1" />
+ <stop
+ id="stop4993"
+ offset="0.35955963"
+ style="stop-color:#53a6ff;stop-opacity:1" />
+ <stop
+ id="stop4995"
+ offset="0.79518169"
+ style="stop-color:#286cbb;stop-opacity:1" />
+ <stop
+ id="stop4997"
+ offset="1"
+ style="stop-color:#003d87;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4977">
+ <stop
+ id="stop4979"
+ offset="0.0000000"
+ style="stop-color:#ffffff;stop-opacity:1" />
+ <stop
+ id="stop4981"
+ offset="1.0000000"
+ style="stop-color:#ffffff;stop-opacity:0" />
+ </linearGradient>
+ <filter
+ id="filter6406"
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ height="1.2"
+ width="1.2"
+ y="-0.15000000000000002"
+ x="-0.050000000000000017">
+ <feFlood
+ id="feFlood6408"
+ flood-opacity="0.50171821305841924"
+ flood-color="rgb(50,51,108)"
+ result="flood"
+ stdDeviation="0.01" />
+ <feComposite
+ id="feComposite6410"
+ in2="SourceGraphic"
+ in="flood"
+ operator="in"
+ result="composite1"
+ k1="8.1400000000000006"
+ k2="4.9299999999999997"
+ k3="5.5899999999999999"
+ k4="-7.2400000000000002" />
+ <feGaussianBlur
+ id="feGaussianBlur6412"
+ stdDeviation="0.20000000000000001"
+ result="blur"
+ in="composite" />
+ <feOffset
+ id="feOffset6414"
+ dx="0.29999999999999999"
+ dy="0.29999999999999999"
+ result="offset"
+ flood-opacity="0.5" />
+ <feComposite
+ id="feComposite6416"
+ in2="offset"
+ in="SourceGraphic"
+ result="composite2"
+ operator="" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ id="filter4258">
+ <feFlood
+ flood-opacity="0.80200501253132828"
+ flood-color="rgb(197,204,222)"
+ result="flood"
+ id="feFlood4260" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="in"
+ result="composite1"
+ id="feComposite4262"
+ flood-opacity="0.80000000000000004" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4264" />
+ <feOffset
+ dx="-0"
+ dy="-0.5"
+ result="offset"
+ id="feOffset4266" />
+ <feComposite
+ in="SourceGraphic"
+ in2="offset"
+ operator="over"
+ result="composite2"
+ id="feComposite4268" />
+ </filter>
+ <filter
+ id="filter4290"
+ inkscape:label="Drop Shadow"
+ style="color-interpolation-filters:sRGB;">
+ <feFlood
+ id="feFlood4292"
+ result="flood"
+ flood-color="rgb(255,255,255)"
+ flood-opacity="0.59999999999999998" />
+ <feComposite
+ id="feComposite4294"
+ result="composite1"
+ operator="in"
+ in2="SourceGraphic"
+ in="flood" />
+ <feGaussianBlur
+ id="feGaussianBlur4296"
+ result="blur"
+ stdDeviation="6.7999999999999998"
+ in="composite1" />
+ <feOffset
+ id="feOffset4298"
+ result="offset"
+ dy="1.9000000000000004"
+ dx="0.69999999999999996"
+ flood-opacity="0.68000000000000005" />
+ <feComposite
+ id="feComposite4300"
+ result="composite2"
+ in2="offset"
+ in="SourceGraphic"
+ operator="" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="0.17254902"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="16"
+ inkscape:cx="26.569169"
+ inkscape:cy="27.737594"
+ inkscape:current-layer="layer1"
+ showgrid="false"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1861"
+ inkscape:window-height="1176"
+ inkscape:window-x="59"
+ inkscape:window-y="24"
+ inkscape:showpageshadow="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-maximized="1"
+ width="512px"
+ units="px"
+ inkscape:snap-global="false"
+ inkscape:snap-to-guides="true"
+ inkscape:snap-others="true">
+ <sodipodi:guide
+ orientation="0,1"
+ position="4.331,47"
+ id="guide3056"
+ inkscape:label=""
+ inkscape:color="rgb(99,99,125)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="23.9375,24.5625"
+ id="guide3058"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="40,25.375"
+ id="guide5866" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="38.448931,29.742679"
+ id="guide5868" />
+ <sodipodi:guide
+ position="3.8890873,1"
+ orientation="0,1"
+ id="guide4521"
+ inkscape:label=""
+ inkscape:color="rgb(106,106,146)" />
+ <sodipodi:guide
+ position="1,17.324"
+ orientation="1,0"
+ id="guide4523"
+ inkscape:label=""
+ inkscape:color="rgb(152,152,181)" />
+ <sodipodi:guide
+ position="47,33.852737"
+ orientation="1,0"
+ id="guide4525"
+ inkscape:label=""
+ inkscape:color="rgb(121,121,172)" />
+ <sodipodi:guide
+ position="13.433908,46"
+ orientation="0,1"
+ id="guide4527"
+ inkscape:label=""
+ inkscape:color="rgb(255,40,0)" />
+ <sodipodi:guide
+ position="8,36.135"
+ orientation="1,0"
+ id="guide4529"
+ inkscape:label=""
+ inkscape:color="rgb(255,11,0)" />
+ <sodipodi:guide
+ position="40,26.436782"
+ orientation="1,0"
+ id="guide4531"
+ inkscape:label=""
+ inkscape:color="rgb(255,6,0)" />
+ <sodipodi:guide
+ position="34.052,2"
+ orientation="0,1"
+ id="guide4533"
+ inkscape:label=""
+ inkscape:color="rgb(255,0,5)" />
+ <sodipodi:guide
+ position="36.879276,26"
+ orientation="0,1"
+ id="guide4542"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="43.660795,14.731391"
+ orientation="0,1"
+ id="guide4546" />
+ <inkscape:grid
+ type="xygrid"
+ id="grid4206" />
+ </sodipodi:namedview>
+ <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:title />
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Tuomas Kuosmanen</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
+ <dc:source>http://jimmac.musichall.cz</dc:source>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>internet</rdf:li>
+ <rdf:li>tools</rdf:li>
+ <rdf:li>applications</rdf:li>
+ <rdf:li>category</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Attribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="base droplet"
+ inkscape:groupmode="layer"
+ style="display:inline">
+ <path
+ style="fill:#dcdc00;fill-opacity:1;fill-rule:evenodd;stroke:#b4b400;stroke-width:3.27999091;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;"
+ d="M 23.295622,2.4822833 35.490217,21.297229 C 43.30243,33.350632 35.08691,46.383433 23.287956,46.383433 11.489002,46.383433 3.2602089,33.346045 11.079504,21.30073 Z"
+ id="path2069"
+ sodipodi:nodetypes="cszsc"
+ inkscape:connector-curvature="0"
+ transform="matrix(0.90726715,0,0,0.89972834,2.9383017,2.518482)" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer4"
+ inkscape:label="queue icon"
+ style="display:inline">
+ <rect
+ style="display:inline;opacity:1;fill:#6a6a00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.03118205;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4221-3-6"
+ width="16"
+ height="4"
+ x="16"
+ y="30.238995" />
+ <rect
+ style="display:inline;opacity:1;fill:#6a6a00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:2.03118205;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4221-3-9"
+ width="16"
+ height="4"
+ x="16"
+ y="23.873394" />
+ <path
+ sodipodi:type="star"
+ style="display:inline;opacity:1;fill:#6a6a00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.97536206;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path4266-5"
+ sodipodi:sides="3"
+ sodipodi:cx="23.909048"
+ sodipodi:cy="17.006021"
+ sodipodi:r1="0.024637857"
+ sodipodi:r2="0.012318929"
+ sodipodi:arg1="3.1415927"
+ sodipodi:arg2="4.1887903"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 23.88441,17.006021 0.01848,-0.01067 0.01848,-0.01067 0,0.02134 0,0.02134 -0.01848,-0.01067 z"
+ transform="matrix(0,121.76383,-210.90115,0,3610.5895,-2890.8119)"
+ inkscape:transform-center-y="-0.7497936"
+ inkscape:transform-center-x="2.0998475e-05" />
+ <path
+ sodipodi:type="star"
+ style="display:inline;opacity:1;fill:#6a6a00;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:1.97536206;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path4266-5-3"
+ sodipodi:sides="3"
+ sodipodi:cx="23.909048"
+ sodipodi:cy="17.006021"
+ sodipodi:r1="0.024637857"
+ sodipodi:r2="0.012318929"
+ sodipodi:arg1="3.1415927"
+ sodipodi:arg2="4.1887903"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 23.88441,17.006021 0.01848,-0.01067 0.01848,-0.01067 0,0.02134 0,0.02134 -0.01848,-0.01067 z"
+ transform="matrix(0,-121.76383,210.90115,0,-3562.5895,2948.8304)"
+ inkscape:transform-center-y="0.74972138"
+ inkscape:transform-center-x="-0.00022730407" />
+ </g>
+</svg>
diff --git a/deluge/ui/data/pixmaps/queued16.png b/deluge/ui/data/pixmaps/queued16.png
new file mode 100644
index 0000000..f9f7454
--- /dev/null
+++ b/deluge/ui/data/pixmaps/queued16.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/seeding.svg b/deluge/ui/data/pixmaps/seeding.svg
new file mode 100644
index 0000000..e98b4a0
--- /dev/null
+++ b/deluge/ui/data/pixmaps/seeding.svg
@@ -0,0 +1,509 @@
+<?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:xlink="http://www.w3.org/1999/xlink"
+ xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+ xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+ width="48px"
+ height="48px"
+ id="svg3440"
+ sodipodi:version="0.32"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="seeding.svg"
+ inkscape:export-xdpi="960"
+ inkscape:export-ydpi="960"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ sodipodi:modified="TRUE"
+ version="1.1"
+ inkscape:export-filename="/home/calum/projects/deluge-logo/deluge.512.x.png">
+ <defs
+ id="defs3">
+ <color-profile
+ name="sRGB"
+ xlink:href="/usr/share/color/icc/sRGB.icc"
+ id="color-profile4484" />
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient2973">
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:1;"
+ offset="0"
+ id="stop2975" />
+ <stop
+ style="stop-color:#eeeeec;stop-opacity:0;"
+ offset="1"
+ id="stop2977" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4126">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop4128" />
+ <stop
+ style="stop-color:#ffffff;stop-opacity:0.16494845;"
+ offset="1.0000000"
+ id="stop4130" />
+ </linearGradient>
+ <linearGradient
+ inkscape:collect="always"
+ id="linearGradient4114">
+ <stop
+ style="stop-color:#000000;stop-opacity:1;"
+ offset="0"
+ id="stop4116" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0;"
+ offset="1"
+ id="stop4118" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3962">
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.0000000"
+ id="stop3964" />
+ <stop
+ style="stop-color:#d3e9ff;stop-opacity:1.0000000;"
+ offset="0.15517241"
+ id="stop4134" />
+ <stop
+ style="stop-color:#4074ae;stop-opacity:1.0000000;"
+ offset="0.75000000"
+ id="stop4346" />
+ <stop
+ style="stop-color:#36486c;stop-opacity:1.0000000;"
+ offset="1.0000000"
+ id="stop3966" />
+ </linearGradient>
+ <radialGradient
+ r="13.994944"
+ fy="33.506763"
+ fx="-10.089286"
+ cy="33.506763"
+ cx="-10.089286"
+ gradientTransform="matrix(1,0,0,0.791446,-14.01786,-11.28667)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4019"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ gradientTransform="matrix(1,0,0,0.792374,-19.58761,2.818569)"
+ gradientUnits="userSpaceOnUse"
+ id="radialGradient4004"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(1,0,0,0.792374,0,6.785475)"
+ r="14.057444"
+ fy="31.329016"
+ fx="-10.323107"
+ cy="31.329016"
+ cx="-10.323107"
+ id="radialGradient3999"
+ xlink:href="#linearGradient3993"
+ inkscape:collect="always" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ gradientTransform="matrix(0.341185,-0.153831,1.08001,2.395374,-15.42222,-25.62103)"
+ r="13.994946"
+ fy="24.241488"
+ fx="61.662098"
+ cy="24.241488"
+ cx="61.662098"
+ id="radialGradient3943"
+ xlink:href="#linearGradient1312"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient1312">
+ <stop
+ id="stop1314"
+ offset="0"
+ style="stop-color:#ffffff;stop-opacity:1;" />
+ <stop
+ id="stop1316"
+ offset="1"
+ style="stop-color:#ffffff;stop-opacity:0;" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient3993">
+ <stop
+ style="stop-color:#ffffff;stop-opacity:1;"
+ offset="0"
+ id="stop3995" />
+ <stop
+ style="stop-color:#000000;stop-opacity:0"
+ offset="1"
+ id="stop3997" />
+ </linearGradient>
+ <radialGradient
+ inkscape:collect="always"
+ xlink:href="#linearGradient2973"
+ id="radialGradient3866"
+ cx="-22.375"
+ cy="18.499998"
+ fx="-22.375"
+ fy="18.499998"
+ r="14.33462"
+ gradientTransform="matrix(1,0,0,1.140022,40.17678,1.347091)"
+ gradientUnits="userSpaceOnUse" />
+ <radialGradient
+ gradientUnits="userSpaceOnUse"
+ r="12.289036"
+ fy="63.965388"
+ fx="15.115514"
+ cy="63.965388"
+ cx="15.115514"
+ gradientTransform="scale(1.643990,0.608276)"
+ id="radialGradient5000"
+ xlink:href="#linearGradient4114"
+ inkscape:collect="always" />
+ <linearGradient
+ id="linearGradient4989">
+ <stop
+ id="stop4991"
+ offset="0"
+ style="stop-color:#47abff;stop-opacity:1" />
+ <stop
+ id="stop4993"
+ offset="0.35955963"
+ style="stop-color:#53a6ff;stop-opacity:1" />
+ <stop
+ id="stop4995"
+ offset="0.79518169"
+ style="stop-color:#286cbb;stop-opacity:1" />
+ <stop
+ id="stop4997"
+ offset="1"
+ style="stop-color:#003d87;stop-opacity:1" />
+ </linearGradient>
+ <linearGradient
+ id="linearGradient4977">
+ <stop
+ id="stop4979"
+ offset="0.0000000"
+ style="stop-color:#ffffff;stop-opacity:1" />
+ <stop
+ id="stop4981"
+ offset="1.0000000"
+ style="stop-color:#ffffff;stop-opacity:0" />
+ </linearGradient>
+ <filter
+ id="filter6406"
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ height="1.2"
+ width="1.2"
+ y="-0.15000000000000002"
+ x="-0.050000000000000017">
+ <feFlood
+ id="feFlood6408"
+ flood-opacity="0.50171821305841924"
+ flood-color="rgb(50,51,108)"
+ result="flood"
+ stdDeviation="0.01" />
+ <feComposite
+ id="feComposite6410"
+ in2="SourceGraphic"
+ in="flood"
+ operator="in"
+ result="composite1"
+ k1="8.1400000000000006"
+ k2="4.9299999999999997"
+ k3="5.5899999999999999"
+ k4="-7.2400000000000002" />
+ <feGaussianBlur
+ id="feGaussianBlur6412"
+ stdDeviation="0.20000000000000001"
+ result="blur"
+ in="composite" />
+ <feOffset
+ id="feOffset6414"
+ dx="0.29999999999999999"
+ dy="0.29999999999999999"
+ result="offset"
+ flood-opacity="0.5" />
+ <feComposite
+ id="feComposite6416"
+ in2="offset"
+ in="SourceGraphic"
+ result="composite2"
+ operator="" />
+ </filter>
+ <filter
+ style="color-interpolation-filters:sRGB;"
+ inkscape:label="Drop Shadow"
+ id="filter4258">
+ <feFlood
+ flood-opacity="0.80200501253132828"
+ flood-color="rgb(197,204,222)"
+ result="flood"
+ id="feFlood4260" />
+ <feComposite
+ in="flood"
+ in2="SourceGraphic"
+ operator="in"
+ result="composite1"
+ id="feComposite4262"
+ flood-opacity="0.80000000000000004" />
+ <feGaussianBlur
+ in="composite1"
+ stdDeviation="0.5"
+ result="blur"
+ id="feGaussianBlur4264" />
+ <feOffset
+ dx="-0"
+ dy="-0.5"
+ result="offset"
+ id="feOffset4266" />
+ <feComposite
+ in="SourceGraphic"
+ in2="offset"
+ operator="over"
+ result="composite2"
+ id="feComposite4268" />
+ </filter>
+ <filter
+ id="filter4290"
+ inkscape:label="Drop Shadow"
+ style="color-interpolation-filters:sRGB;">
+ <feFlood
+ id="feFlood4292"
+ result="flood"
+ flood-color="rgb(255,255,255)"
+ flood-opacity="0.59999999999999998" />
+ <feComposite
+ id="feComposite4294"
+ result="composite1"
+ operator="in"
+ in2="SourceGraphic"
+ in="flood" />
+ <feGaussianBlur
+ id="feGaussianBlur4296"
+ result="blur"
+ stdDeviation="6.7999999999999998"
+ in="composite1" />
+ <feOffset
+ id="feOffset4298"
+ result="offset"
+ dy="1.9000000000000004"
+ dx="0.69999999999999996"
+ flood-opacity="0.68000000000000005" />
+ <feComposite
+ id="feComposite4300"
+ result="composite2"
+ in2="offset"
+ in="SourceGraphic"
+ operator="" />
+ </filter>
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="0.17254902"
+ inkscape:pageopacity="0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="16"
+ inkscape:cx="33.265379"
+ inkscape:cy="23.209575"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:grid-bbox="true"
+ inkscape:document-units="px"
+ inkscape:window-width="1861"
+ inkscape:window-height="1176"
+ inkscape:window-x="59"
+ inkscape:window-y="24"
+ inkscape:showpageshadow="false"
+ showguides="true"
+ inkscape:guide-bbox="true"
+ inkscape:window-maximized="1"
+ width="512px"
+ units="px"
+ inkscape:snap-global="false"
+ inkscape:snap-to-guides="true"
+ inkscape:snap-others="true">
+ <sodipodi:guide
+ orientation="0,1"
+ position="4.331,47"
+ id="guide3056"
+ inkscape:label=""
+ inkscape:color="rgb(99,99,125)" />
+ <sodipodi:guide
+ orientation="1,0"
+ position="40,25.375"
+ id="guide5866" />
+ <sodipodi:guide
+ orientation="0,1"
+ position="38.448931,29.742679"
+ id="guide5868" />
+ <sodipodi:guide
+ position="3.8890873,1"
+ orientation="0,1"
+ id="guide4521"
+ inkscape:label=""
+ inkscape:color="rgb(106,106,146)" />
+ <sodipodi:guide
+ position="1,17.324"
+ orientation="1,0"
+ id="guide4523"
+ inkscape:label=""
+ inkscape:color="rgb(152,152,181)" />
+ <sodipodi:guide
+ position="47,33.852737"
+ orientation="1,0"
+ id="guide4525"
+ inkscape:label=""
+ inkscape:color="rgb(121,121,172)" />
+ <sodipodi:guide
+ position="13.433908,46"
+ orientation="0,1"
+ id="guide4527"
+ inkscape:label=""
+ inkscape:color="rgb(255,40,0)" />
+ <sodipodi:guide
+ position="8,36.135"
+ orientation="1,0"
+ id="guide4529"
+ inkscape:label=""
+ inkscape:color="rgb(255,11,0)" />
+ <sodipodi:guide
+ position="40,26.436782"
+ orientation="1,0"
+ id="guide4531"
+ inkscape:label=""
+ inkscape:color="rgb(255,6,0)" />
+ <sodipodi:guide
+ position="34.052,2"
+ orientation="0,1"
+ id="guide4533"
+ inkscape:label=""
+ inkscape:color="rgb(255,0,5)" />
+ <sodipodi:guide
+ position="36.879276,26"
+ orientation="0,1"
+ id="guide4542"
+ inkscape:label=""
+ inkscape:color="rgb(0,0,255)" />
+ <sodipodi:guide
+ position="43.660795,14.731391"
+ orientation="0,1"
+ id="guide4546" />
+ <inkscape:grid
+ type="xygrid"
+ id="grid4215" />
+ <sodipodi:guide
+ position="24,45"
+ orientation="1,0"
+ id="guide4225" />
+ </sodipodi:namedview>
+ <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:title />
+ <dc:creator>
+ <cc:Agent>
+ <dc:title>Jakub Steiner</dc:title>
+ </cc:Agent>
+ </dc:creator>
+ <dc:contributor>
+ <cc:Agent>
+ <dc:title>Tuomas Kuosmanen</dc:title>
+ </cc:Agent>
+ </dc:contributor>
+ <cc:license
+ rdf:resource="http://creativecommons.org/licenses/by-sa/2.0/" />
+ <dc:source>http://jimmac.musichall.cz</dc:source>
+ <dc:subject>
+ <rdf:Bag>
+ <rdf:li>internet</rdf:li>
+ <rdf:li>tools</rdf:li>
+ <rdf:li>applications</rdf:li>
+ <rdf:li>category</rdf:li>
+ </rdf:Bag>
+ </dc:subject>
+ </cc:Work>
+ <cc:License
+ rdf:about="http://creativecommons.org/licenses/by-sa/2.0/">
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Reproduction" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/Distribution" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Notice" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/Attribution" />
+ <cc:permits
+ rdf:resource="http://web.resource.org/cc/DerivativeWorks" />
+ <cc:requires
+ rdf:resource="http://web.resource.org/cc/ShareAlike" />
+ </cc:License>
+ </rdf:RDF>
+ </metadata>
+ <g
+ id="layer1"
+ inkscape:label="base droplet"
+ inkscape:groupmode="layer"
+ style="display:inline">
+ <path
+ style="fill:#6699ff;fill-opacity:1;fill-rule:evenodd;stroke:#3366cc;stroke-width:3.27999091;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1;"
+ d="M 23.295622,2.4822833 35.490217,21.297229 C 43.30243,33.350632 35.08691,46.383433 23.287956,46.383433 11.489002,46.383433 3.2602089,33.346045 11.079504,21.30073 Z"
+ id="path2069"
+ sodipodi:nodetypes="cszsc"
+ inkscape:connector-curvature="0"
+ transform="matrix(0.90726715,0,0,0.89972834,2.9383017,2.518482)" />
+ <rect
+ style="display:inline;opacity:1;fill:#0f3171;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4364-1-36"
+ width="8.5"
+ height="4.5"
+ x="19.760511"
+ y="33" />
+ <path
+ sodipodi:type="star"
+ style="display:inline;opacity:1;fill:#0f3171;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="path5039-7"
+ sodipodi:sides="3"
+ sodipodi:cx="23.9375"
+ sodipodi:cy="21.1875"
+ sodipodi:r1="4.6958261"
+ sodipodi:r2="2.347913"
+ sodipodi:arg1="0.52359878"
+ sodipodi:arg2="1.5707963"
+ inkscape:flatsided="false"
+ inkscape:rounded="0"
+ inkscape:randomized="0"
+ d="m 28.004205,23.535413 -4.066705,0 -4.066705,0 2.033353,-3.521869 2.033352,-3.52187 2.033352,3.521869 z"
+ transform="matrix(1.9671947,0,0,1.1180142,-23.156428,-1.2587488)"
+ inkscape:transform-center-y="1.3125004" />
+ <rect
+ style="display:inline;opacity:1;fill:#0f3171;fill-opacity:1;fill-rule:evenodd;stroke:none;stroke-width:3.54330707;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1"
+ id="rect4364-1-3"
+ width="8.5"
+ height="4.5"
+ x="19.74159"
+ y="26.902088" />
+ </g>
+ <g
+ inkscape:groupmode="layer"
+ id="layer5"
+ inkscape:label="paused icon" />
+</svg>
diff --git a/deluge/ui/data/pixmaps/seeding16.png b/deluge/ui/data/pixmaps/seeding16.png
new file mode 100644
index 0000000..fce70a8
--- /dev/null
+++ b/deluge/ui/data/pixmaps/seeding16.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/tracker_all16.png b/deluge/ui/data/pixmaps/tracker_all16.png
new file mode 100644
index 0000000..36756bb
--- /dev/null
+++ b/deluge/ui/data/pixmaps/tracker_all16.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/tracker_warning16.png b/deluge/ui/data/pixmaps/tracker_warning16.png
new file mode 100644
index 0000000..219432c
--- /dev/null
+++ b/deluge/ui/data/pixmaps/tracker_warning16.png
Binary files differ
diff --git a/deluge/ui/data/pixmaps/traffic.svg b/deluge/ui/data/pixmaps/traffic.svg
new file mode 100644
index 0000000..bea6164
--- /dev/null
+++ b/deluge/ui/data/pixmaps/traffic.svg
@@ -0,0 +1,94 @@
+<?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"
+ width="128"
+ height="128"
+ id="svg2517"
+ sodipodi:version="0.32"
+ inkscape:version="0.91 r13725"
+ sodipodi:docname="traffic.svg"
+ inkscape:output_extension="org.inkscape.output.svg.inkscape"
+ inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/traffic16.png"
+ inkscape:export-xdpi="2.459017"
+ inkscape:export-ydpi="2.459017"
+ version="1.1">
+ <defs
+ id="defs2519">
+ <inkscape:perspective
+ sodipodi:type="inkscape:persp3d"
+ inkscape:vp_x="0 : 526.18109 : 1"
+ inkscape:vp_y="0 : 1000 : 0"
+ inkscape:vp_z="744.09448 : 526.18109 : 1"
+ inkscape:persp3d-origin="372.04724 : 350.78739 : 1"
+ id="perspective2525" />
+ </defs>
+ <sodipodi:namedview
+ id="base"
+ pagecolor="#ffffff"
+ bordercolor="#666666"
+ borderopacity="1.0"
+ gridtolerance="10000"
+ guidetolerance="10"
+ objecttolerance="10"
+ inkscape:pageopacity="0.0"
+ inkscape:pageshadow="2"
+ inkscape:zoom="5.6"
+ inkscape:cx="56.051291"
+ inkscape:cy="66.506813"
+ inkscape:document-units="px"
+ inkscape:current-layer="layer1"
+ showgrid="true"
+ inkscape:window-width="1861"
+ inkscape:window-height="1176"
+ inkscape:window-x="59"
+ inkscape:window-y="24"
+ inkscape:window-maximized="1">
+ <inkscape:grid
+ type="xygrid"
+ id="grid3336"
+ empspacing="1"
+ dotted="false" />
+ </sodipodi:namedview>
+ <metadata
+ id="metadata2522">
+ <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></dc:title>
+ </cc:Work>
+ </rdf:RDF>
+ </metadata>
+ <g
+ inkscape:label="Layer 1"
+ inkscape:groupmode="layer"
+ id="layer1"
+ transform="translate(0,-924.36218)">
+ <path
+ style="opacity:1;fill:#4c90e8;fill-opacity:1;stroke:#000000;stroke-width:4.088;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 79.742286,929.51964 c -14.206752,13.93715 -28.471266,27.83834 -42.678018,41.77545 7.643855,0.014 15.306814,-0.0139 22.950711,0 0,16.50389 0,33.00771 0,49.51151 13.581299,0 27.16264,0 40.743951,0 0,-16.4609 0,-32.92164 0,-49.38258 7.04267,0.0129 14.10294,-0.0129 21.14561,0 -14.04989,-13.98881 -28.112421,-27.91554 -42.162254,-41.90438 z"
+ id="rect2634"
+ inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/traffic32.png"
+ inkscape:export-xdpi="4.9180341"
+ inkscape:export-ydpi="4.9180341"
+ inkscape:connector-curvature="0" />
+ <path
+ style="opacity:1;fill:#16c816;fill-opacity:1;stroke:#000000;stroke-width:4.088;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-opacity:1"
+ d="m 48.980975,1047.5605 c -14.206752,-13.9372 -28.471263,-27.8383 -42.6780126,-41.7755 7.6438566,-0.014 15.3068146,0.014 22.9507156,0 0,-16.5037 0,-33.00759 0,-49.51147 13.581288,0 27.162629,0 40.74397,0 0,16.46089 0,32.92179 0,49.38257 7.042653,-0.014 14.102929,0.013 21.145603,0 -14.049885,13.9888 -28.112433,27.9156 -42.162276,41.9044 z"
+ id="path2528"
+ inkscape:export-filename="/home/andrew/Projects/deluge/trunk/deluge/data/pixmaps/traffic32.png"
+ inkscape:export-xdpi="4.9180341"
+ inkscape:export-ydpi="4.9180341"
+ inkscape:connector-curvature="0" />
+ </g>
+</svg>
diff --git a/deluge/ui/data/pixmaps/traffic16.png b/deluge/ui/data/pixmaps/traffic16.png
new file mode 100644
index 0000000..ecd8720
--- /dev/null
+++ b/deluge/ui/data/pixmaps/traffic16.png
Binary files differ
diff --git a/deluge/ui/data/share/appdata/deluge.appdata.xml.in b/deluge/ui/data/share/appdata/deluge.appdata.xml.in
new file mode 100644
index 0000000..dcbf063
--- /dev/null
+++ b/deluge/ui/data/share/appdata/deluge.appdata.xml.in
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<component type="desktop">
+ <id>deluge.desktop</id>
+ <metadata_license>CC0-1.0</metadata_license>
+ <project_license>GPL-3.0+</project_license>
+ <translation type="gettext">deluge</translation>
+ <_developer_name>Deluge Team</_developer_name>
+ <_summary>
+ Deluge is a lightweight, Free Software, cross-platform BitTorrent client.
+ </_summary>
+ <url type="homepage">https://www.deluge-torrent.org/</url>
+ <url type="bugtracker">https://dev.deluge-torrent.org</url>
+ <description>
+ <_p>Deluge contains the common features to BitTorrent clients such as
+ Protocol Encryption, DHT, Local Peer Discovery (LSD), Peer Exchange (PEX),
+ UPnP, NAT-PMP, Proxy support, Web seeds, global and per-torrent speed
+ limits. As Deluge heavily utilises the libtorrent library it has a
+ comprehensive list of the features provided.</_p>
+ <_p>Deluge has been designed to run as both a normal standalone desktop
+ application and as a client-server. In Thinclient mode a Deluge daemon
+ handles all the BitTorrent activity and is able to run on headless machines
+ with the user-interfaces connecting remotely from any other platform.</_p>
+ </description>
+ <screenshots>
+ <screenshot type="default">
+ <image>https://upload.wikimedia.org/wikipedia/commons/5/50/Deluge-torrent.png</image>
+ </screenshot>
+ </screenshots>
+ <launchable type="desktop-id">deluge.desktop</launchable>
+</component>
diff --git a/deluge/ui/data/share/applications/deluge.desktop.in b/deluge/ui/data/share/applications/deluge.desktop.in
new file mode 100644
index 0000000..c952d42
--- /dev/null
+++ b/deluge/ui/data/share/applications/deluge.desktop.in
@@ -0,0 +1,16 @@
+[Desktop Entry]
+Version=1.0
+_Name=Deluge
+_GenericName=BitTorrent Client
+_X-GNOME-FullName=Deluge BitTorrent Client
+_Comment=Download and share files over BitTorrent
+TryExec=deluge-gtk
+Exec=deluge-gtk %U
+Icon=deluge
+Terminal=false
+Type=Application
+Categories=Network;FileTransfer;P2P;GTK;
+StartupWMClass=deluge
+StartupNotify=true
+MimeType=application/x-bittorrent;x-scheme-handler/magnet;
+X-GNOME-UsesNotifications=true
diff --git a/deluge/ui/gtk3/__init__.py b/deluge/ui/gtk3/__init__.py
new file mode 100644
index 0000000..3b9d2b1
--- /dev/null
+++ b/deluge/ui/gtk3/__init__.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+from deluge.ui.ui import UI
+
+log = logging.getLogger(__name__)
+
+
+# Keep this class in __init__.py to avoid the console having to import everything in gtkui.py
+class Gtk(UI):
+
+ cmd_description = """GTK-based graphical user interface"""
+
+ def __init__(self, *args, **kwargs):
+ super(Gtk, self).__init__(
+ 'gtk', *args, description='Starts the Deluge GTK+ interface', **kwargs
+ )
+
+ group = self.parser.add_argument_group(_('GTK Options'))
+ group.add_argument(
+ 'torrents',
+ metavar='<torrent>',
+ nargs='*',
+ default=None,
+ help=_(
+ 'Add one or more torrent files, torrent URLs or magnet URIs'
+ ' to a currently running Deluge GTK instance'
+ ),
+ )
+
+ def start(self):
+ super(Gtk, self).start()
+ from .gtkui import GtkUI
+ import deluge.common
+
+ def run(options):
+ try:
+ gtkui = GtkUI(options)
+ gtkui.start()
+ except Exception as ex:
+ log.exception(ex)
+ raise
+
+ deluge.common.run_profiled(
+ run,
+ self.options,
+ output_file=self.options.profile,
+ do_profile=self.options.profile,
+ )
+
+
+def start():
+ Gtk().start()
diff --git a/deluge/ui/gtk3/aboutdialog.py b/deluge/ui/gtk3/aboutdialog.py
new file mode 100644
index 0000000..9974a13
--- /dev/null
+++ b/deluge/ui/gtk3/aboutdialog.py
@@ -0,0 +1,855 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Marcos Mobley ('markybob') <markybob@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+from gi.repository import Gtk
+
+import deluge.component as component
+from deluge.common import get_version, open_url_in_browser, windows_check
+from deluge.ui.client import client
+
+from .common import get_deluge_icon, get_pixbuf
+
+
+class AboutDialog(object):
+ def __init__(self):
+ self.about = Gtk.AboutDialog()
+ self.about.set_transient_for(component.get('MainWindow').window)
+ self.about.set_position(Gtk.WindowPosition.CENTER)
+ self.about.set_name(_('Deluge'))
+ self.about.set_program_name(_('Deluge'))
+ if windows_check():
+
+ def url_hook(dialog, url):
+ """Url hook for Windows OS which has no default browser."""
+ open_url_in_browser(url)
+ return True
+
+ self.about.connect('activate-link', url_hook)
+
+ version = get_version()
+
+ self.about.set_copyright(
+ _('Copyright %(year_start)s-%(year_end)s Deluge Team')
+ % {'year_start': 2007, 'year_end': 2019}
+ )
+ self.about.set_comments(
+ _('A peer-to-peer file sharing program\nutilizing the BitTorrent protocol.')
+ + '\n\n'
+ + _('Client:')
+ + ' %s\n' % version
+ )
+ self.about.set_version(version)
+ self.about.set_authors(
+ [
+ _('Current Developers:'),
+ 'Andrew Resch',
+ 'Damien Churchill',
+ 'John Garland',
+ 'Calum Lind',
+ '',
+ 'libtorrent (libtorrent.org):',
+ 'Arvid Norberg',
+ '',
+ _('Past Developers or Contributors:'),
+ 'Zach Tibbitts',
+ 'Alon Zakai',
+ 'Marcos Mobley',
+ 'Alex Dedul',
+ 'Sadrul Habib Chowdhury',
+ 'Ido Abramovich',
+ 'Martijn Voncken',
+ ]
+ )
+ self.about.set_artists(['Andrew Wedderburn', 'Andrew Resch'])
+ self.about.set_translator_credits(
+ '\n'.join(
+ [
+ 'Aaron Wang Shi',
+ 'abbigss',
+ 'ABCdatos',
+ 'Abcx',
+ 'Actam',
+ 'Adam',
+ 'adaminikisi',
+ 'adi_oporanu',
+ 'Adrian Goll',
+ 'afby',
+ 'Ahmades',
+ 'Ahmad Farghal',
+ 'Ahmad Gharbeia أحمد غربية',
+ 'akira',
+ 'Aki Sivula',
+ 'Alan Pepelko',
+ 'Alberto',
+ 'Alberto Ferrer',
+ 'alcatr4z',
+ 'AlckO',
+ 'Aleksej Korgenkov',
+ 'Alessio Treglia',
+ 'Alexander Ilyashov',
+ 'Alexander Matveev',
+ 'Alexander Saltykov',
+ 'Alexander Taubenkorb',
+ 'Alexander Telenga',
+ 'Alexander Yurtsev',
+ 'Alexandre Martani',
+ 'Alexandre Rosenfeld',
+ 'Alexandre Sapata Carbonell',
+ 'Alexey Osipov',
+ 'Alin Claudiu Radut',
+ 'allah',
+ 'AlSim',
+ 'Alvaro Carrillanca P.',
+ 'A.Matveev',
+ 'Andras Hipsag',
+ 'András Kárász',
+ 'Andrea Ratto',
+ 'Andreas Johansson',
+ 'Andreas Str',
+ 'André F. Oliveira',
+ 'AndreiF',
+ 'andrewh',
+ 'Angel Guzman Maeso',
+ 'Aníbal Deboni Neto',
+ 'animarval',
+ 'Antonio Cono',
+ 'antoniojreyes',
+ 'Anton Shestakov',
+ 'Anton Yakutovich',
+ 'antou',
+ 'Arkadiusz Kalinowski',
+ 'Artin',
+ 'artir',
+ 'Astur',
+ 'Athanasios Lefteris',
+ 'Athmane MOKRAOUI (ButterflyOfFire)',
+ 'Augusta Carla Klug',
+ 'Avoledo Marco',
+ 'axaard',
+ 'AxelRafn',
+ 'Axezium',
+ 'Ayont',
+ 'b3rx',
+ 'Bae Taegil',
+ 'Bajusz Tamás',
+ "Balaam's Miracle",
+ 'Ballestein',
+ 'Bent Ole Fosse',
+ 'berto89',
+ 'bigx',
+ 'Bjorn Inge Berg',
+ 'blackbird',
+ 'Blackeyed',
+ 'blackmx',
+ 'BlueSky',
+ 'Blutheo',
+ 'bmhm',
+ 'bob00work',
+ 'boenki',
+ 'Bogdan Bădic-Spătariu',
+ 'bonpu',
+ 'Boone',
+ 'boss01',
+ 'Branislav Jovanović',
+ 'bronze',
+ 'brownie',
+ 'Brus46',
+ 'bumper',
+ 'butely',
+ 'BXCracer',
+ 'c0nfidencal',
+ 'Can Kaya',
+ 'Carlos Alexandro Becker',
+ 'cassianoleal',
+ 'Cédric.h',
+ 'César Rubén',
+ 'chaoswizard',
+ 'Chen Tao',
+ 'chicha',
+ 'Chien Cheng Wei',
+ 'Christian Kopac',
+ 'Christian Widell',
+ 'Christoffer Brodd-Reijer',
+ 'christooss',
+ 'CityAceE',
+ 'Clopy',
+ 'Clusty',
+ 'cnu',
+ 'Commandant',
+ 'Constantinos Koniaris',
+ 'Coolmax',
+ 'cosmix',
+ 'Costin Chirvasuta',
+ 'CoVaLiDiTy',
+ 'cow_2001',
+ 'Crispin Kirchner',
+ 'crom',
+ 'Cruster',
+ 'Cybolic',
+ 'Dan Bishop',
+ 'Danek',
+ 'Dani',
+ 'Daniel Demarco',
+ 'Daniel Ferreira',
+ 'Daniel Frank',
+ 'Daniel Holm',
+ 'Daniel Høyer Iversen',
+ 'Daniel Marynicz',
+ 'Daniel Nylander',
+ 'Daniel Patriche',
+ 'Daniel Schildt',
+ 'Daniil Sorokin',
+ 'Dante Díaz',
+ 'Daria Michalska',
+ 'DarkenCZ',
+ 'Darren',
+ 'Daspah',
+ 'David Eurenius',
+ 'davidhjelm',
+ 'David Machakhelidze',
+ 'Dawid Dziurdzia',
+ 'Daya Adianto ',
+ 'dcruz',
+ 'Deady',
+ 'Dereck Wonnacott',
+ 'Devgru',
+ 'Devid Antonio Filoni' 'DevilDogTG',
+ 'di0rz`',
+ 'Dialecti Valsamou',
+ 'Diego Medeiros',
+ 'Dkzoffy',
+ 'Dmitrij D. Czarkoff',
+ 'Dmitriy Geels',
+ 'Dmitry Olyenyov',
+ 'Dominik Kozaczko',
+ 'Dominik Lübben',
+ 'doomster',
+ 'Dorota Król',
+ 'Doyen Philippe',
+ 'Dread Knight',
+ 'DreamSonic',
+ 'duan',
+ 'Duong Thanh An',
+ 'DvoglavaZver',
+ 'dwori',
+ 'dylansmrjones',
+ 'Ebuntor',
+ 'Edgar Alejandro Jarquin Flores',
+ 'Eetu',
+ 'ekerazha',
+ 'Elias Julkunen',
+ 'elparia',
+ 'Emberke',
+ 'Emiliano Goday Caneda',
+ 'EndelWar',
+ 'eng.essam',
+ 'enubuntu',
+ 'ercangun',
+ 'Erdal Ronahi',
+ 'ergin üresin',
+ 'Eric',
+ 'Éric Lassauge',
+ 'Erlend Finvåg',
+ 'Errdil',
+ 'ethan shalev',
+ 'Evgeni Spasov',
+ 'ezekielnin',
+ 'Fabian Ordelmans',
+ 'Fabio Mazanatti',
+ 'Fábio Nogueira',
+ 'FaCuZ',
+ 'Felipe Lerena',
+ 'Fernando Pereira',
+ 'fjetland',
+ 'Florian Schäfer',
+ 'FoBoS',
+ 'Folke',
+ 'Force',
+ 'fosk',
+ 'fragarray',
+ 'freddeg',
+ 'Frédéric Perrin',
+ 'Fredrik Kilegran',
+ 'FreeAtMind',
+ 'Fulvio Ciucci',
+ 'Gabor Kelemen',
+ 'Galatsanos Panagiotis',
+ 'Gaussian',
+ 'gdevitis',
+ 'Georg Brzyk',
+ 'George Dumitrescu',
+ 'Georgi Arabadjiev',
+ 'Georg Sieber',
+ 'Gerd Radecke',
+ 'Germán Heusdens',
+ 'Gianni Vialetto',
+ 'Gigih Aji Ibrahim',
+ 'Giorgio Wicklein',
+ 'Giovanni Rapagnani',
+ 'Giuseppe',
+ 'gl',
+ 'glen',
+ 'granjerox',
+ 'Green Fish',
+ 'greentea',
+ 'Greyhound',
+ 'G. U.',
+ 'Guillaume BENOIT',
+ 'Guillaume Pelletier',
+ 'Gustavo Henrique Klug',
+ 'gutocarvalho',
+ 'Guybrush88',
+ 'Hans Rødtang',
+ 'HardDisk',
+ 'Hargas Gábor',
+ 'Heitor Thury Barreiros Barbosa',
+ 'helios91940',
+ 'helix84',
+ 'Helton Rodrigues',
+ 'Hendrik Luup',
+ 'Henrique Ferreiro',
+ 'Henry Goury-Laffont',
+ 'Hezy Amiel',
+ 'hidro',
+ 'hoball',
+ 'hokten',
+ 'Holmsss',
+ 'hristo.num',
+ 'Hubert Życiński',
+ 'Hyo',
+ 'Iarwain',
+ 'ibe',
+ 'ibear',
+ 'Id2ndR',
+ 'Igor Zubarev',
+ 'IKON (Ion)',
+ 'imen',
+ 'Ionuț Jula',
+ 'Isabelle STEVANT',
+ 'István Nyitrai',
+ 'Ivan Petrovic',
+ 'Ivan Prignano',
+ 'IvaSerge',
+ 'jackmc',
+ 'Jacks0nxD',
+ 'Jack Shen',
+ 'Jacky Yeung',
+ 'Jacques Stadler',
+ 'Janek Thomaschewski',
+ 'Jan Kaláb',
+ 'Jan Niklas Hasse',
+ 'Jasper Groenewegen',
+ 'Javi Rodríguez',
+ 'Jayasimha (ಜಯಸಿಂಹ)',
+ 'jeannich',
+ 'Jeff Bailes',
+ 'Jesse Zilstorff',
+ 'Joan Duran',
+ 'João Santos',
+ 'Joar Bagge',
+ 'Joe Anderson',
+ 'Joel Calado',
+ 'Johan Linde',
+ 'John Garland',
+ 'Jojan',
+ 'jollyr0ger',
+ 'Jonas Bo Grimsgaard',
+ 'Jonas Granqvist',
+ 'Jonas Slivka',
+ 'Jonathan Zeppettini',
+ 'Jørgen',
+ 'Jørgen Tellnes',
+ 'josé',
+ 'José Geraldo Gouvêa',
+ 'José Iván León Islas',
+ 'José Lou C.',
+ 'Jose Sun',
+ 'Jr.',
+ 'Jukka Kauppinen',
+ 'Julián Alarcón',
+ 'julietgolf',
+ 'Jusic',
+ 'Justzupi',
+ 'Kaarel',
+ 'Kai Thomsen',
+ 'Kalman Tarnay',
+ 'Kamil Páral',
+ 'Kane_F',
+ 'kaotiks@gmail.com',
+ 'Kateikyoushii',
+ 'kaxhinaz',
+ 'Kazuhiro NISHIYAMA',
+ 'Kerberos',
+ 'Keresztes Ákos',
+ 'kevintyk',
+ 'kiersie',
+ 'Kimbo^',
+ 'Kim Lübbe',
+ 'kitzOgen',
+ 'Kjetil Rydland',
+ 'kluon',
+ 'kmikz',
+ 'Knedlyk',
+ 'koleoptero',
+ 'Kőrösi Krisztián',
+ 'Kouta',
+ 'Krakatos',
+ 'Krešo Kunjas',
+ 'kripken',
+ 'Kristaps',
+ 'Kristian Øllegaard',
+ 'Kristoffer Egil Bonarjee',
+ 'Krzysztof Janowski',
+ 'Krzysztof Zawada',
+ 'Larry Wei Liu',
+ 'laughterwym',
+ 'Laur Mõtus',
+ 'lazka',
+ 'leandrud',
+ 'lê bình',
+ 'Le Coz Florent',
+ 'Leo',
+ 'liorda',
+ 'LKRaider',
+ 'LoLo_SaG',
+ 'Long Tran',
+ 'Lorenz',
+ 'Low Kian Seong',
+ 'Luca Andrea Rossi',
+ 'Luca Ferretti',
+ 'Lucky LIX',
+ 'Luis Gomes',
+ 'Luis Reis',
+ 'Łukasz Wyszyński',
+ 'luojie-dune',
+ 'maaark',
+ 'Maciej Chojnacki',
+ 'Maciej Meller',
+ 'Mads Peter Rommedahl',
+ 'Major Kong',
+ 'Malaki',
+ 'malde',
+ 'Malte Lenz',
+ 'Mantas Kriaučiūnas',
+ 'Mara Sorella',
+ 'Marcin',
+ 'Marcin Falkiewicz',
+ 'marcobra',
+ 'Marco da Silva',
+ 'Marco de Moulin',
+ 'Marco Rodrigues',
+ 'Marcos',
+ 'Marcos Escalier',
+ 'Marcos Mobley',
+ 'Marcus Ekstrom',
+ 'Marek Dębowski',
+ 'Mário Buči',
+ 'Mario Munda',
+ 'Marius Andersen',
+ 'Marius Hudea',
+ 'Marius Mihai',
+ 'Mariusz Cielecki',
+ 'Mark Krapivner',
+ 'marko-markovic',
+ 'Markus Brummer',
+ 'Markus Sutter',
+ 'Martin',
+ 'Martin Dybdal',
+ 'Martin Iglesias',
+ 'Martin Lettner',
+ 'Martin Pihl',
+ 'Masoud Kalali',
+ 'mat02',
+ 'Matej Urbančič',
+ 'Mathias-K',
+ 'Mathieu Arès',
+ 'Mathieu D. (MatToufoutu)',
+ 'Mathijs',
+ 'Matrik',
+ 'Matteo Renzulli',
+ 'Matteo Settenvini',
+ 'Matthew Gadd',
+ 'Matthias Benkard',
+ 'Matthias Mailänder',
+ 'Mattias Ohlsson',
+ 'Mauro de Carvalho',
+ 'Max Molchanov',
+ 'Me',
+ 'MercuryCC',
+ 'Mert Bozkurt',
+ 'Mert Dirik',
+ 'MFX',
+ 'mhietar',
+ 'mibtha',
+ 'Michael Budde',
+ 'Michael Kaliszka',
+ 'Michalis Makaronides',
+ 'Michał Tokarczyk',
+ 'Miguel Pires da Rosa',
+ 'Mihai Capotă',
+ 'Miika Metsälä',
+ 'Mikael Fernblad',
+ 'Mike Sierra',
+ 'mikhalek',
+ 'Milan Prvulović',
+ 'Milo Casagrande',
+ 'Mindaugas',
+ 'Miroslav Matejaš',
+ 'misel',
+ 'mithras',
+ 'Mitja Pagon',
+ 'M.Kitchen',
+ 'Mohamed Magdy',
+ 'moonkey',
+ 'MrBlonde',
+ 'muczy',
+ 'Münir Ekinci',
+ 'Mustafa Temizel',
+ 'mvoncken',
+ 'Mytonn',
+ 'NagyMarton',
+ 'neaion',
+ 'Neil Lin',
+ 'Nemo',
+ 'Nerijus Arlauskas',
+ 'Nicklas Larsson',
+ 'Nicolaj Wyke',
+ 'Nicola Piovesan',
+ 'Nicolas Sabatier',
+ 'Nicolas Velin',
+ 'Nightfall',
+ 'NiKoB',
+ 'Nikolai M. Riabov',
+ 'Niko_Thien',
+ 'niska',
+ 'Nithir',
+ 'noisemonkey',
+ 'nomemohes',
+ 'nosense',
+ 'null',
+ 'Nuno Estêvão',
+ 'Nuno Santos',
+ 'nxxs',
+ 'nyo',
+ 'obo',
+ 'Ojan',
+ 'Olav Andreas Lindekleiv',
+ 'oldbeggar',
+ 'Olivier FAURAX',
+ 'orphe',
+ 'osantana',
+ 'Osman Tosun',
+ 'OssiR',
+ 'otypoks',
+ 'ounn',
+ 'Oz123',
+ 'Özgür BASKIN',
+ 'Pablo Carmona A.',
+ 'Pablo Ledesma',
+ 'Pablo Navarro Castillo',
+ 'Paco Molinero',
+ 'Pål-Eivind Johnsen',
+ 'pano',
+ 'Paolo Naldini',
+ 'Paracelsus',
+ 'Patryk13_03',
+ 'Patryk Skorupa',
+ 'PattogoTehen',
+ 'Paul Lange',
+ 'Pavcio',
+ 'Paweł Wysocki',
+ 'Pedro Brites Moita',
+ 'Pedro Clemente Pereira Neto',
+ 'Pekka "PEXI" Niemistö',
+ 'Penegal',
+ 'Penzo',
+ 'perdido',
+ 'Peter Kotrcka',
+ 'Peter Skov',
+ 'Peter Van den Bosch',
+ 'Petter Eklund',
+ 'Petter Viklund',
+ 'phatsphere',
+ 'Phenomen',
+ 'Philipi',
+ 'Philippides Homer',
+ 'phoenix',
+ 'pidi',
+ 'Pierre Quillery',
+ 'Pierre Rudloff',
+ 'Pierre Slamich',
+ 'Pietrao',
+ 'Piotr Strębski',
+ 'Piotr Wicijowski',
+ 'Pittmann Tamás',
+ 'Playmolas',
+ 'Prescott',
+ 'Prescott_SK',
+ 'pronull',
+ 'Przemysław Kulczycki',
+ 'Pumy',
+ 'pushpika',
+ 'PY',
+ 'qubicllj',
+ 'r21vo',
+ 'Rafał Barański',
+ 'rainofchaos',
+ 'Rajbir',
+ 'ras0ir',
+ 'Rat',
+ 'rd1381',
+ 'Renato',
+ 'Rene Hennig',
+ 'Rene Pärts',
+ 'Ricardo Duarte',
+ 'Richard',
+ 'Robert Hrovat',
+ 'Roberth Sjonøy',
+ 'Robert Lundmark',
+ 'Robin Jakobsson',
+ 'Robin Kåveland',
+ 'Rodrigo Donado',
+ 'Roel Groeneveld',
+ 'rohmaru',
+ 'Rolf Christensen',
+ 'Rolf Leggewie',
+ 'Roni Kantis',
+ 'Ronmi',
+ 'Rostislav Raykov',
+ 'royto',
+ 'RuiAmaro',
+ 'Rui Araújo',
+ 'Rui Moura',
+ 'Rune Svendsen',
+ 'Rusna',
+ 'Rytis',
+ 'Sabirov Mikhail',
+ 'salseeg',
+ 'Sami Koskinen',
+ 'Samir van de Sand',
+ 'Samuel Arroyo Acuña',
+ 'Samuel R. C. Vale',
+ 'Sanel',
+ 'Santi',
+ 'Santi Martínez Cantelli',
+ 'Sardan',
+ 'Sargate Kanogan',
+ 'Sarmad Jari',
+ 'Saša Bodiroža',
+ 'sat0shi',
+ 'Saulius Pranckevičius',
+ 'Savvas Radevic',
+ 'Sebastian Krauß',
+ 'Sebastián Porta',
+ 'Sedir',
+ 'Sefa Denizoğlu',
+ 'sekolands',
+ 'Selim Suerkan',
+ 'semsomi',
+ 'Sergii Golovatiuk',
+ 'setarcos',
+ 'Sheki',
+ 'Shironeko',
+ 'Shlomil',
+ 'silfiriel',
+ 'Simone Tolotti',
+ 'Simone Vendemia',
+ 'sirkubador',
+ 'Sławomir Więch',
+ 'slip',
+ 'slyon',
+ 'smoke',
+ 'Sonja',
+ 'spectral',
+ 'spin_555',
+ 'spitf1r3',
+ 'Spiziuz',
+ 'Spyros Theodoritsis',
+ 'SqUe',
+ 'Squigly',
+ 'srtck',
+ 'Stefan Horning',
+ 'Stefano Maggiolo',
+ 'Stefano Roberto Soleti',
+ 'steinberger',
+ 'Stéphane Travostino',
+ 'Stephan Klein',
+ 'Steven De Winter',
+ 'Stevie',
+ 'Stian24',
+ 'stylius',
+ 'Sukarn Maini',
+ 'Sunjae Park',
+ 'Susana Pereira',
+ 'szymon siglowy',
+ 'takercena',
+ 'TAS',
+ 'Taygeto',
+ 'temy4',
+ 'texxxxxx',
+ 'thamood',
+ 'Thanos Chatziathanassiou',
+ 'Tharawut Paripaiboon',
+ 'Theodoor',
+ 'Théophane Anestis',
+ 'Thor Marius K. Høgås',
+ 'Tiago Silva',
+ 'Tiago Sousa',
+ 'Tikkel',
+ 'tim__b',
+ 'Tim Bordemann',
+ 'Tim Fuchs',
+ 'Tim Kornhammar',
+ 'Timo',
+ 'Timo Jyrinki',
+ 'Timothy Babych',
+ 'TitkosRejtozo',
+ 'Tom',
+ 'Tomas Gustavsson',
+ 'Tomas Valentukevičius',
+ 'Tomasz Dominikowski',
+ 'Tomislav Plavčić',
+ 'Tom Mannerhagen',
+ 'Tommy Mikkelsen',
+ 'Tom Verdaat',
+ 'Tony Manco',
+ 'Tor Erling H. Opsahl',
+ 'Toudi',
+ 'tqm_z',
+ 'Trapanator',
+ 'Tribaal',
+ 'Triton',
+ 'TuniX12',
+ 'Tuomo Sipola',
+ 'turbojugend_gr',
+ 'Turtle.net',
+ 'twilight',
+ 'tymmej',
+ 'Ulrik',
+ 'Umarzuki Mochlis',
+ 'unikob',
+ 'Vadim Gusev',
+ 'Vagi',
+ 'Valentin Bora',
+ 'Valmantas Palikša',
+ 'VASKITTU',
+ 'Vassilis Skoullis',
+ 'vetal17',
+ 'vicedo',
+ 'viki',
+ 'villads hamann',
+ 'Vincent Garibal',
+ 'Vincent Ortalda',
+ 'vinchi007',
+ 'Vinícius de Figueiredo Silva',
+ 'Vinzenz Vietzke',
+ 'virtoo',
+ 'virtual_spirit',
+ 'Vitor Caike',
+ 'Vitor Lamas Gatti',
+ 'Vladimir Lazic',
+ 'Vladimir Sharshov',
+ 'Wanderlust',
+ 'Wander Nauta',
+ 'Ward De Ridder',
+ 'WebCrusader',
+ 'webdr',
+ 'Wentao Tang',
+ 'wilana',
+ 'Wilfredo Ernesto Guerrero Campos',
+ 'Wim Champagne',
+ 'World Sucks',
+ 'Xabi Ezpeleta',
+ 'Xavi de Moner',
+ 'XavierToo',
+ 'XChesser',
+ 'Xiaodong Xu',
+ 'xyb',
+ 'Yaron',
+ 'Yasen Pramatarov',
+ 'YesPoX',
+ 'Yuren Ju',
+ 'Yves MATHIEU',
+ 'zekopeko',
+ 'zhuqin',
+ 'Zissan',
+ 'Γιάννης Κατσαμπίρης',
+ 'Артём Попов',
+ 'Миша',
+ 'Шаймарданов Максим',
+ '蔡查理',
+ ]
+ )
+ )
+ self.about.set_wrap_license(True)
+ self.about.set_license(
+ _(
+ 'This program is free software; you can redistribute it and/or '
+ 'modify it under the terms of the GNU General Public License as '
+ 'published by the Free Software Foundation; either version 3 of '
+ 'the License, or (at your option) any later version. \n\n'
+ 'This program '
+ 'is distributed in the hope that it will be useful, but WITHOUT '
+ 'ANY WARRANTY; without even the implied warranty of '
+ 'MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU '
+ 'General Public License for more details. \n\n'
+ 'You should have received '
+ 'a copy of the GNU General Public License along with this program; '
+ 'if not, see <http://www.gnu.org/licenses>. \n\n'
+ 'In addition, as a '
+ 'special exception, the copyright holders give permission to link '
+ 'the code of portions of this program with the OpenSSL library. '
+ 'You must obey the GNU General Public License in all respects for '
+ 'all of the code used other than OpenSSL. \n\n'
+ 'If you modify file(s) '
+ 'with this exception, you may extend this exception to your '
+ 'version of the file(s), but you are not obligated to do so. If '
+ 'you do not wish to do so, delete this exception statement from '
+ 'your version. If you delete this exception statement from all '
+ 'source files in the program, then also delete it here.'
+ )
+ )
+ self.about.set_website('http://deluge-torrent.org')
+ self.about.set_website_label('deluge-torrent.org')
+
+ self.about.set_icon(get_deluge_icon())
+ self.about.set_logo(get_pixbuf('deluge-about.png'))
+
+ if client.connected():
+ if not client.is_standalone():
+ self.about.set_comments(
+ self.about.get_comments() + _('Server:') + ' %coreversion%\n'
+ )
+
+ self.about.set_comments(
+ self.about.get_comments() + '\n' + _('libtorrent:') + ' %ltversion%\n'
+ )
+
+ def on_lt_version(result):
+ c = self.about.get_comments()
+ c = c.replace('%ltversion%', result)
+ self.about.set_comments(c)
+
+ def on_info(result):
+ c = self.about.get_comments()
+ c = c.replace('%coreversion%', result)
+ self.about.set_comments(c)
+ client.core.get_libtorrent_version().addCallback(on_lt_version)
+
+ if not client.is_standalone():
+ client.daemon.info().addCallback(on_info)
+ else:
+ client.core.get_libtorrent_version().addCallback(on_lt_version)
+
+ def run(self):
+ self.about.show_all()
+ self.about.run()
+ self.about.destroy()
diff --git a/deluge/ui/gtk3/addtorrentdialog.py b/deluge/ui/gtk3/addtorrentdialog.py
new file mode 100644
index 0000000..9ede710
--- /dev/null
+++ b/deluge/ui/gtk3/addtorrentdialog.py
@@ -0,0 +1,1101 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import division, unicode_literals
+
+import logging
+import os
+from base64 import b64encode
+from xml.sax.saxutils import escape as xml_escape
+from xml.sax.saxutils import unescape as xml_unescape
+
+from gi.repository import Gtk
+from gi.repository.GObject import TYPE_INT64, TYPE_UINT64
+
+import deluge.component as component
+from deluge.common import (
+ create_magnet_uri,
+ decode_bytes,
+ fsize,
+ get_magnet_info,
+ is_infohash,
+ is_magnet,
+ is_url,
+ resource_filename,
+)
+from deluge.configmanager import ConfigManager
+from deluge.httpdownloader import download_file
+from deluge.ui.client import client
+from deluge.ui.common import TorrentInfo
+
+from .common import (
+ get_clipboard_text,
+ listview_replace_treestore,
+ reparent_iter,
+ windowing,
+)
+from .dialogs import ErrorDialog
+from .edittrackersdialog import trackers_tiers_from_text
+from .path_chooser import PathChooser
+from .torrentview_data_funcs import cell_data_size
+
+log = logging.getLogger(__name__)
+
+
+class AddTorrentDialog(component.Component):
+ def __init__(self):
+ component.Component.__init__(self, 'AddTorrentDialog')
+ self.builder = Gtk.Builder()
+ # The base dialog
+ self.builder.add_from_file(
+ resource_filename(
+ __package__, os.path.join('glade', 'add_torrent_dialog.ui')
+ )
+ )
+ # The infohash dialog
+ self.builder.add_from_file(
+ resource_filename(
+ __package__, os.path.join('glade', 'add_torrent_dialog.infohash.ui')
+ )
+ )
+ # The url dialog
+ self.builder.add_from_file(
+ resource_filename(
+ __package__, os.path.join('glade', 'add_torrent_dialog.url.ui')
+ )
+ )
+
+ self.dialog = self.builder.get_object('dialog_add_torrent')
+
+ self.dialog.connect('delete-event', self._on_delete_event)
+
+ self.builder.connect_signals(self)
+
+ # download?, path, filesize, sequence number, inconsistent?
+ self.files_treestore = Gtk.TreeStore(
+ bool, str, TYPE_UINT64, TYPE_INT64, bool, str
+ )
+ self.files_treestore.set_sort_column_id(1, Gtk.SortType.ASCENDING)
+
+ # Holds the files info
+ self.files = {}
+ self.infos = {}
+ self.core_config = {}
+ self.options = {}
+
+ self.previous_selected_torrent = None
+
+ self.listview_torrents = self.builder.get_object('listview_torrents')
+ self.listview_files = self.builder.get_object('listview_files')
+
+ self.prefetching_magnets = []
+
+ render = Gtk.CellRendererText()
+ render.connect('edited', self._on_torrent_name_edit)
+ render.set_property('editable', True)
+ column = Gtk.TreeViewColumn(_('Torrent'), render, text=1)
+ self.listview_torrents.append_column(column)
+
+ render = Gtk.CellRendererToggle()
+ render.connect('toggled', self._on_file_toggled)
+ column = Gtk.TreeViewColumn(None, render, active=0, inconsistent=4)
+ self.listview_files.append_column(column)
+
+ column = Gtk.TreeViewColumn(_('Filename'))
+ render = Gtk.CellRendererPixbuf()
+ column.pack_start(render, False)
+ column.add_attribute(render, 'icon-name', 5)
+ render = Gtk.CellRendererText()
+ render.set_property('editable', True)
+ render.connect('edited', self._on_filename_edited)
+ column.pack_start(render, True)
+ column.add_attribute(render, 'text', 1)
+ column.set_expand(True)
+ self.listview_files.append_column(column)
+
+ render = Gtk.CellRendererText()
+ column = Gtk.TreeViewColumn(_('Size'))
+ column.pack_start(render, True)
+ column.set_cell_data_func(render, cell_data_size, 2)
+ self.listview_files.append_column(column)
+
+ self.torrent_liststore = Gtk.ListStore(str, str, str)
+ self.listview_torrents.set_model(self.torrent_liststore)
+ self.listview_torrents.set_tooltip_column(2)
+ self.listview_files.set_model(self.files_treestore)
+
+ self.listview_files.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
+ self.listview_torrents.get_selection().connect(
+ 'changed', self._on_torrent_changed
+ )
+ self.torrent_liststore.connect('row-inserted', self.update_dialog_title_count)
+ self.torrent_liststore.connect('row-deleted', self.update_dialog_title_count)
+
+ self.setup_move_completed_path_chooser()
+ self.setup_download_location_path_chooser()
+
+ # Get default config values from the core
+ self.core_keys = [
+ 'pre_allocate_storage',
+ 'max_connections_per_torrent',
+ 'max_upload_slots_per_torrent',
+ 'max_upload_speed_per_torrent',
+ 'max_download_speed_per_torrent',
+ 'prioritize_first_last_pieces',
+ 'sequential_download',
+ 'add_paused',
+ 'download_location',
+ 'download_location_paths_list',
+ 'move_completed',
+ 'move_completed_path',
+ 'move_completed_paths_list',
+ 'super_seeding',
+ ]
+ # self.core_keys += self.move_completed_path_chooser.get_config_keys()
+ self.builder.get_object('notebook1').connect(
+ 'switch-page', self._on_switch_page
+ )
+
+ def start(self):
+ self.update_core_config()
+
+ def show(self, focus=False):
+ self.update_core_config(True, focus)
+
+ def _show(self, focus=False):
+ main_window = component.get('MainWindow')
+ if main_window.is_on_active_workspace():
+ self.dialog.set_transient_for(main_window.window)
+ else:
+ self.dialog.set_transient_for(None)
+ self.dialog.set_position(Gtk.WindowPosition.CENTER)
+
+ if focus:
+ timestamp = main_window.get_timestamp()
+ if windowing('X11'):
+ # Use present with X11 set_user_time since
+ # present_with_time is inconsistent.
+ self.dialog.present()
+ self.dialog.get_window().set_user_time(timestamp)
+ else:
+ self.dialog.present_with_time(timestamp)
+ else:
+ self.dialog.present()
+
+ def hide(self):
+ self.dialog.hide()
+ self.files = {}
+ self.infos = {}
+ self.options = {}
+ self.previous_selected_torrent = None
+ self.torrent_liststore.clear()
+ self.files_treestore.clear()
+ self.prefetching_magnets = []
+ self.dialog.set_transient_for(component.get('MainWindow').window)
+
+ def _on_config_values(self, config, show=False, focus=False):
+ self.core_config = config
+ if self.core_config:
+ self.set_default_options()
+ if show:
+ self._show(focus)
+
+ def update_core_config(self, show=False, focus=False):
+ # Send requests to the core for these config values
+ d = client.core.get_config_values(self.core_keys)
+ d.addCallback(self._on_config_values, show, focus)
+
+ def _add_torrent_liststore(self, info_hash, name, filename, files, filedata):
+ """Add a torrent to torrent_liststore."""
+ if info_hash in self.files:
+ return False
+
+ torrent_row = [info_hash, name, xml_escape(filename)]
+ row_iter = self.torrent_liststore.append(torrent_row)
+ self.files[info_hash] = files
+ self.infos[info_hash] = filedata
+ self.listview_torrents.get_selection().select_iter(row_iter)
+
+ self.set_default_options()
+ self.save_torrent_options(row_iter)
+
+ return row_iter
+
+ def update_dialog_title_count(self, *args):
+ """Update the AddTorrent dialog title with current torrent count."""
+ self.dialog.set_title(_('Add Torrents (%d)') % len(self.torrent_liststore))
+
+ def show_already_added_dialog(self, count):
+ """Show a message about trying to add duplicate torrents."""
+ log.debug('Tried to add %d duplicate torrents!', count)
+ ErrorDialog(
+ _('Duplicate torrent(s)'),
+ _(
+ 'You cannot add the same torrent twice.'
+ ' %d torrents were already added.' % count
+ ),
+ self.dialog,
+ ).run()
+
+ def add_from_files(self, filenames):
+ already_added = 0
+
+ for filename in filenames:
+ # Get the torrent data from the torrent file
+ try:
+ info = TorrentInfo(filename)
+ except Exception as ex:
+ log.debug('Unable to open torrent file: %s', ex)
+ ErrorDialog(_('Invalid File'), ex, self.dialog).run()
+ continue
+
+ if not self._add_torrent_liststore(
+ info.info_hash, info.name, filename, info.files, info.filedata
+ ):
+ already_added += 1
+
+ if already_added:
+ self.show_already_added_dialog(already_added)
+
+ def _on_uri_metadata(self, result, uri, trackers):
+ """Process prefetched metadata to allow file priority selection."""
+ info_hash, metadata = result
+ log.debug('magnet metadata for %s (%s)', uri, info_hash)
+ if info_hash not in self.prefetching_magnets:
+ return
+
+ if metadata:
+ info = TorrentInfo.from_metadata(metadata, [[t] for t in trackers])
+ self.files[info_hash] = info.files
+ self.infos[info_hash] = info.filedata
+ else:
+ log.info('Unable to fetch metadata for magnet: %s', uri)
+ self.prefetching_magnets.remove(info_hash)
+ self._on_torrent_changed(self.listview_torrents.get_selection())
+
+ def _on_uri_metadata_fail(self, result, info_hash):
+ self.prefetching_magnets.remove(info_hash)
+ self._on_torrent_changed(self.listview_torrents.get_selection())
+
+ def prefetch_waiting_message(self, torrent_id, files):
+ """Show magnet files fetching or failed message above files list."""
+ if torrent_id in self.prefetching_magnets:
+ self.builder.get_object('prefetch_label').set_text(
+ _('Please wait for files...')
+ )
+ self.builder.get_object('prefetch_spinner').show()
+ self.builder.get_object('prefetch_hbox').show()
+ elif not files:
+ self.builder.get_object('prefetch_label').set_text(
+ _('Unable to download files for this magnet')
+ )
+ self.builder.get_object('prefetch_spinner').hide()
+ self.builder.get_object('prefetch_hbox').show()
+ else:
+ self.builder.get_object('prefetch_hbox').hide()
+
+ def add_from_magnets(self, uris):
+ """Add a list of magnet uris to torrent_liststore."""
+ already_added = 0
+
+ for uri in uris:
+ magnet = get_magnet_info(uri)
+ if not magnet:
+ log.error('Invalid magnet: %s', uri)
+ continue
+
+ torrent_id = magnet['info_hash']
+ files = magnet['files_tree']
+ if not self._add_torrent_liststore(
+ torrent_id, magnet['name'], uri, files, None
+ ):
+ already_added += 1
+ continue
+
+ if files:
+ continue
+
+ self.prefetching_magnets.append(torrent_id)
+ self.prefetch_waiting_message(torrent_id, None)
+ d = client.core.prefetch_magnet_metadata(uri)
+ d.addCallback(self._on_uri_metadata, uri, magnet['trackers'])
+ d.addErrback(self._on_uri_metadata_fail, torrent_id)
+
+ if already_added:
+ self.show_already_added_dialog(already_added)
+
+ def _on_torrent_changed(self, treeselection):
+ (model, row) = treeselection.get_selected()
+ if row is None or not model.iter_is_valid(row):
+ self.files_treestore.clear()
+ self.previous_selected_torrent = None
+ return
+
+ if model[row][0] not in self.files:
+ self.files_treestore.clear()
+ self.previous_selected_torrent = None
+ return
+
+ # Save the previous torrents options
+ self.save_torrent_options()
+
+ torrent_id = model.get_value(row, 0)
+ # Update files list
+ files_list = self.files[torrent_id]
+ self.prepare_file_store(files_list)
+
+ if self.core_config == {}:
+ self.update_core_config()
+
+ # Update the options frame
+ self.update_torrent_options(torrent_id)
+ # Update magnet prefetch message
+ self.prefetch_waiting_message(torrent_id, files_list)
+
+ self.previous_selected_torrent = row
+
+ def _on_torrent_name_edit(self, w, row, new_name):
+ # TODO: Update torrent name
+ pass
+
+ def _on_switch_page(self, widget, page, page_num):
+ # Save the torrent options when switching notebook pages
+ self.save_torrent_options()
+
+ def prepare_file_store(self, files):
+ with listview_replace_treestore(self.listview_files):
+ split_files = {}
+ for idx, _file in enumerate(files):
+ self.prepare_file(
+ _file, _file['path'], idx, _file.get('download', True), split_files
+ )
+ self.add_files(None, split_files)
+ root = Gtk.TreePath.new_first()
+ self.listview_files.expand_row(root, False)
+
+ def prepare_file(self, _file, file_name, file_num, download, files_storage):
+ first_slash_index = file_name.find(os.path.sep)
+ if first_slash_index == -1:
+ files_storage[file_name] = (file_num, _file, download)
+ else:
+ file_name_chunk = file_name[: first_slash_index + 1]
+ if file_name_chunk not in files_storage:
+ files_storage[file_name_chunk] = {}
+ self.prepare_file(
+ _file,
+ file_name[first_slash_index + 1 :],
+ file_num,
+ download,
+ files_storage[file_name_chunk],
+ )
+
+ def add_files(self, parent_iter, split_files):
+ ret = 0
+ for key, value in split_files.items():
+ if key.endswith(os.path.sep):
+ chunk_iter = self.files_treestore.append(
+ parent_iter, [True, key, 0, -1, False, 'folder-symbolic']
+ )
+ chunk_size = self.add_files(chunk_iter, value)
+ self.files_treestore.set(chunk_iter, 2, chunk_size)
+ ret += chunk_size
+ else:
+ self.files_treestore.append(
+ parent_iter,
+ [
+ value[2],
+ key,
+ value[1]['size'],
+ value[0],
+ False,
+ 'text-x-generic-symbolic',
+ ],
+ )
+ ret += value[1]['size']
+ if parent_iter and self.files_treestore.iter_has_child(parent_iter):
+ # Iterate through the children and see what we should label the
+ # folder, download true, download false or inconsistent.
+ itr = self.files_treestore.iter_children(parent_iter)
+ download = []
+ download_value = False
+ inconsistent = False
+ while itr:
+ download.append(self.files_treestore.get_value(itr, 0))
+ itr = self.files_treestore.iter_next(itr)
+
+ if sum(download) == len(download):
+ download_value = True
+ elif sum(download) == 0:
+ download_value = False
+ else:
+ inconsistent = True
+
+ self.files_treestore.set_value(parent_iter, 0, download_value)
+ self.files_treestore.set_value(parent_iter, 4, inconsistent)
+ return ret
+
+ def load_path_choosers_data(self):
+ self.move_completed_path_chooser.set_text(
+ self.core_config['move_completed_path'], cursor_end=False, default_text=True
+ )
+ self.download_location_path_chooser.set_text(
+ self.core_config['download_location'], cursor_end=False, default_text=True
+ )
+ self.builder.get_object('chk_move_completed').set_active(
+ self.core_config['move_completed']
+ )
+ self.move_completed_path_chooser.set_sensitive(
+ self.core_config['move_completed']
+ )
+
+ def setup_move_completed_path_chooser(self):
+ self.move_completed_hbox = self.builder.get_object(
+ 'hbox_move_completed_chooser'
+ )
+ self.move_completed_path_chooser = PathChooser(
+ 'move_completed_paths_list', parent=self.dialog
+ )
+ self.move_completed_hbox.add(self.move_completed_path_chooser)
+ self.move_completed_hbox.show_all()
+
+ def setup_download_location_path_chooser(self):
+ self.download_location_hbox = self.builder.get_object(
+ 'hbox_download_location_chooser'
+ )
+ self.download_location_path_chooser = PathChooser(
+ 'download_location_paths_list', parent=self.dialog
+ )
+ self.download_location_hbox.add(self.download_location_path_chooser)
+ self.download_location_hbox.show_all()
+
+ def update_torrent_options(self, torrent_id):
+ if torrent_id not in self.options:
+ self.set_default_options()
+ return
+
+ options = self.options[torrent_id]
+
+ self.download_location_path_chooser.set_text(
+ options['download_location'], cursor_end=True
+ )
+ self.move_completed_path_chooser.set_text(
+ options['move_completed_path'], cursor_end=True
+ )
+
+ self.builder.get_object('spin_maxdown').set_value(options['max_download_speed'])
+ self.builder.get_object('spin_maxup').set_value(options['max_upload_speed'])
+ self.builder.get_object('spin_maxconnections').set_value(
+ options['max_connections']
+ )
+ self.builder.get_object('spin_maxupslots').set_value(
+ options['max_upload_slots']
+ )
+ self.builder.get_object('chk_paused').set_active(options['add_paused'])
+ self.builder.get_object('chk_pre_alloc').set_active(
+ options['pre_allocate_storage']
+ )
+ self.builder.get_object('chk_prioritize').set_active(
+ options['prioritize_first_last_pieces']
+ )
+ self.builder.get_object('chk_sequential_download').set_active(
+ options['sequential_download']
+ )
+ self.builder.get_object('chk_move_completed').set_active(
+ options['move_completed']
+ )
+ self.builder.get_object('chk_super_seeding').set_active(
+ options['super_seeding']
+ )
+
+ def save_torrent_options(self, row=None):
+ # Keeps the torrent options dictionary up-to-date with what the user has
+ # selected.
+ if row is None:
+ if self.previous_selected_torrent and self.torrent_liststore.iter_is_valid(
+ self.previous_selected_torrent
+ ):
+ row = self.previous_selected_torrent
+ else:
+ return
+
+ torrent_id = self.torrent_liststore.get_value(row, 0)
+
+ if torrent_id in self.options:
+ options = self.options[torrent_id]
+ else:
+ options = {}
+
+ options['download_location'] = decode_bytes(
+ self.download_location_path_chooser.get_text()
+ )
+ options['move_completed_path'] = decode_bytes(
+ self.move_completed_path_chooser.get_text()
+ )
+ options['pre_allocate_storage'] = self.builder.get_object(
+ 'chk_pre_alloc'
+ ).get_active()
+ options['move_completed'] = self.builder.get_object(
+ 'chk_move_completed'
+ ).get_active()
+ options['max_download_speed'] = self.builder.get_object(
+ 'spin_maxdown'
+ ).get_value()
+ options['max_upload_speed'] = self.builder.get_object('spin_maxup').get_value()
+ options['max_connections'] = self.builder.get_object(
+ 'spin_maxconnections'
+ ).get_value_as_int()
+ options['max_upload_slots'] = self.builder.get_object(
+ 'spin_maxupslots'
+ ).get_value_as_int()
+ options['add_paused'] = self.builder.get_object('chk_paused').get_active()
+ options['prioritize_first_last_pieces'] = self.builder.get_object(
+ 'chk_prioritize'
+ ).get_active()
+ options['sequential_download'] = (
+ self.builder.get_object('chk_sequential_download').get_active() or False
+ )
+ options['move_completed'] = self.builder.get_object(
+ 'chk_move_completed'
+ ).get_active()
+ options['seed_mode'] = self.builder.get_object('chk_seed_mode').get_active()
+ options['super_seeding'] = self.builder.get_object(
+ 'chk_super_seeding'
+ ).get_active()
+
+ self.options[torrent_id] = options
+
+ # Save the file priorities
+ files_priorities = self.build_priorities(
+ self.files_treestore.get_iter_first(), {}
+ )
+
+ if len(files_priorities) > 0:
+ for i, file_dict in enumerate(self.files[torrent_id]):
+ file_dict['download'] = files_priorities[i]
+
+ def build_priorities(self, _iter, priorities):
+ while _iter is not None:
+ if self.files_treestore.iter_has_child(_iter):
+ self.build_priorities(
+ self.files_treestore.iter_children(_iter), priorities
+ )
+ elif not self.files_treestore.get_value(_iter, 1).endswith(os.path.sep):
+ priorities[
+ self.files_treestore.get_value(_iter, 3)
+ ] = self.files_treestore.get_value(_iter, 0)
+ _iter = self.files_treestore.iter_next(_iter)
+ return priorities
+
+ def set_default_options(self):
+ if not self.core_config:
+ # update_core_config will call this method again.
+ self.update_core_config()
+ return
+
+ self.load_path_choosers_data()
+
+ self.builder.get_object('chk_pre_alloc').set_active(
+ self.core_config['pre_allocate_storage']
+ )
+ self.builder.get_object('spin_maxdown').set_value(
+ self.core_config['max_download_speed_per_torrent']
+ )
+ self.builder.get_object('spin_maxup').set_value(
+ self.core_config['max_upload_speed_per_torrent']
+ )
+ self.builder.get_object('spin_maxconnections').set_value(
+ self.core_config['max_connections_per_torrent']
+ )
+ self.builder.get_object('spin_maxupslots').set_value(
+ self.core_config['max_upload_slots_per_torrent']
+ )
+ self.builder.get_object('chk_paused').set_active(self.core_config['add_paused'])
+ self.builder.get_object('chk_prioritize').set_active(
+ self.core_config['prioritize_first_last_pieces']
+ )
+ self.builder.get_object('chk_sequential_download').set_active(
+ self.core_config['sequential_download']
+ )
+ self.builder.get_object('chk_move_completed').set_active(
+ self.core_config['move_completed']
+ )
+ self.builder.get_object('chk_seed_mode').set_active(False)
+ self.builder.get_object('chk_super_seeding').set_active(
+ self.core_config['super_seeding']
+ )
+
+ def get_file_priorities(self, torrent_id):
+ # A list of priorities
+ files_list = []
+
+ for file_dict in self.files[torrent_id]:
+ if not file_dict['download']:
+ files_list.append(0)
+ else:
+ # Default lt file priority is 4
+ files_list.append(4)
+
+ return files_list
+
+ def _on_file_toggled(self, render, path):
+ (model, paths) = self.listview_files.get_selection().get_selected_rows()
+ if len(paths) > 1:
+ for path in paths:
+ row = model.get_iter(path)
+ self.toggle_iter(row)
+ else:
+ row = model.get_iter(path)
+ self.toggle_iter(row)
+ self.update_treeview_toggles(self.files_treestore.get_iter_first())
+
+ def toggle_iter(self, _iter, toggle_to=None):
+ if toggle_to is None:
+ toggle_to = not self.files_treestore.get_value(_iter, 0)
+ self.files_treestore.set_value(_iter, 0, toggle_to)
+ if self.files_treestore.iter_has_child(_iter):
+ child = self.files_treestore.iter_children(_iter)
+ while child is not None:
+ self.toggle_iter(child, toggle_to)
+ child = self.files_treestore.iter_next(child)
+
+ def update_treeview_toggles(self, _iter):
+ toggle_inconsistent = -1
+ this_level_toggle = None
+ while _iter is not None:
+ if self.files_treestore.iter_has_child(_iter):
+ toggle = self.update_treeview_toggles(
+ self.files_treestore.iter_children(_iter)
+ )
+ if toggle == toggle_inconsistent:
+ self.files_treestore.set_value(_iter, 4, True)
+ else:
+ self.files_treestore.set_value(_iter, 0, toggle)
+ # set inconsistent to false
+ self.files_treestore.set_value(_iter, 4, False)
+ else:
+ toggle = self.files_treestore.get_value(_iter, 0)
+ if this_level_toggle is None:
+ this_level_toggle = toggle
+ elif this_level_toggle != toggle:
+ this_level_toggle = toggle_inconsistent
+ _iter = self.files_treestore.iter_next(_iter)
+ return this_level_toggle
+
+ def on_button_file_clicked(self, widget):
+ log.debug('on_button_file_clicked')
+ # Setup the filechooserdialog
+ chooser = Gtk.FileChooserDialog(
+ _('Choose a .torrent file'),
+ None,
+ Gtk.FileChooserAction.OPEN,
+ buttons=(
+ _('_Cancel'),
+ Gtk.ResponseType.CANCEL,
+ _('_Open'),
+ Gtk.ResponseType.OK,
+ ),
+ )
+
+ chooser.set_transient_for(self.dialog)
+ chooser.set_select_multiple(True)
+ chooser.set_property('skip-taskbar-hint', True)
+ chooser.set_local_only(False)
+
+ # Add .torrent and * file filters
+ file_filter = Gtk.FileFilter()
+ file_filter.set_name(_('Torrent files'))
+ file_filter.add_pattern('*.' + 'torrent')
+ chooser.add_filter(file_filter)
+ file_filter = Gtk.FileFilter()
+ file_filter.set_name(_('All files'))
+ file_filter.add_pattern('*')
+ chooser.add_filter(file_filter)
+
+ # Load the 'default_load_path' from the config
+ self.config = ConfigManager('gtk3ui.conf')
+ if (
+ 'default_load_path' in self.config
+ and self.config['default_load_path'] is not None
+ ):
+ chooser.set_current_folder(self.config['default_load_path'])
+
+ # Run the dialog
+ response = chooser.run()
+
+ if response == Gtk.ResponseType.OK:
+ result = [decode_bytes(f) for f in chooser.get_filenames()]
+ self.config['default_load_path'] = decode_bytes(
+ chooser.get_current_folder()
+ )
+ else:
+ chooser.destroy()
+ return
+
+ chooser.destroy()
+ self.add_from_files(result)
+
+ def on_button_url_clicked(self, widget):
+ log.debug('on_button_url_clicked')
+ dialog = self.builder.get_object('url_dialog')
+ entry = self.builder.get_object('entry_url')
+
+ dialog.set_default_response(Gtk.ResponseType.OK)
+ dialog.set_transient_for(self.dialog)
+ entry.grab_focus()
+
+ text = get_clipboard_text()
+ if text and is_url(text) or is_magnet(text):
+ entry.set_text(text)
+
+ dialog.show_all()
+ response = dialog.run()
+
+ if response == Gtk.ResponseType.OK:
+ url = decode_bytes(entry.get_text())
+ else:
+ url = None
+
+ entry.set_text('')
+ dialog.hide()
+
+ # This is where we need to fetch the .torrent file from the URL and
+ # add it to the list.
+ log.debug('url: %s', url)
+ if url:
+ if is_url(url):
+ self.add_from_url(url)
+ elif is_magnet(url):
+ self.add_from_magnets([url])
+ else:
+ ErrorDialog(
+ _('Invalid URL'),
+ '%s %s' % (url, _('is not a valid URL.')),
+ self.dialog,
+ ).run()
+
+ def add_from_url(self, url):
+ dialog = Gtk.Dialog(
+ _('Downloading...'),
+ flags=Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+ parent=self.dialog,
+ )
+ dialog.set_transient_for(self.dialog)
+
+ pb = Gtk.ProgressBar()
+ dialog.vbox.pack_start(pb, True, True, 0)
+ dialog.show_all()
+
+ # Create a tmp file path
+ import tempfile
+
+ tmp_fd, tmp_file = tempfile.mkstemp(prefix='deluge_url.', suffix='.torrent')
+
+ def on_part(data, current_length, total_length):
+ if total_length:
+ percent = current_length / total_length
+ pb.set_fraction(percent)
+ pb.set_text(
+ '%.2f%% (%s / %s)'
+ % (percent * 100, fsize(current_length), fsize(total_length))
+ )
+ else:
+ pb.pulse()
+ pb.set_text('%s' % fsize(current_length))
+
+ def on_download_success(result):
+ self.add_from_files([result])
+ dialog.destroy()
+
+ def on_download_fail(result):
+ log.debug('Download failed: %s', result)
+ dialog.destroy()
+ ErrorDialog(
+ _('Download Failed'),
+ '%s %s' % (_('Failed to download:'), url),
+ details=result.getErrorMessage(),
+ parent=self.dialog,
+ ).run()
+ return result
+
+ d = download_file(url, tmp_file, on_part)
+ os.close(tmp_fd)
+ d.addCallbacks(on_download_success, on_download_fail)
+
+ def on_button_hash_clicked(self, widget):
+ log.debug('on_button_hash_clicked')
+ dialog = self.builder.get_object('dialog_infohash')
+ entry = self.builder.get_object('entry_hash')
+ textview = self.builder.get_object('text_trackers')
+
+ dialog.set_default_response(Gtk.ResponseType.OK)
+ dialog.set_transient_for(self.dialog)
+ entry.grab_focus()
+
+ text = get_clipboard_text()
+ if is_infohash(text):
+ entry.set_text(text)
+
+ dialog.show_all()
+ response = dialog.run()
+ infohash = decode_bytes(entry.get_text()).strip()
+ if response == Gtk.ResponseType.OK and is_infohash(infohash):
+ # Create a list of trackers from the textview buffer
+ tview_buf = textview.get_buffer()
+ trackers_text = decode_bytes(
+ tview_buf.get_text(*tview_buf.get_bounds(), include_hidden_chars=False)
+ )
+ log.debug('Create torrent tracker lines: %s', trackers_text)
+ trackers = list(trackers_tiers_from_text(trackers_text).keys())
+
+ # Convert the information to a magnet uri, this is just easier to
+ # handle this way.
+ log.debug('trackers: %s', trackers)
+ magnet = create_magnet_uri(infohash, infohash, trackers)
+ log.debug('magnet uri: %s', magnet)
+ self.add_from_magnets([magnet])
+
+ entry.set_text('')
+ textview.get_buffer().set_text('')
+ dialog.hide()
+
+ def on_button_remove_clicked(self, widget):
+ log.debug('on_button_remove_clicked')
+ (model, row) = self.listview_torrents.get_selection().get_selected()
+ if row is None:
+ return
+
+ torrent_id = model.get_value(row, 0)
+
+ model.remove(row)
+ del self.files[torrent_id]
+ del self.infos[torrent_id]
+
+ def on_button_trackers_clicked(self, widget):
+ log.debug('on_button_trackers_clicked')
+
+ def on_button_cancel_clicked(self, widget):
+ log.debug('on_button_cancel_clicked')
+ self.hide()
+
+ def on_button_add_clicked(self, widget):
+ log.debug('on_button_add_clicked')
+ self.add_torrents()
+ self.hide()
+
+ def add_torrents(self):
+ (model, row) = self.listview_torrents.get_selection().get_selected()
+ if row is not None:
+ self.save_torrent_options(row)
+
+ torrents_to_add = []
+
+ row = self.torrent_liststore.get_iter_first()
+ while row is not None:
+ torrent_id = self.torrent_liststore.get_value(row, 0)
+ filename = xml_unescape(
+ decode_bytes(self.torrent_liststore.get_value(row, 2))
+ )
+ try:
+ options = self.options[torrent_id]
+ except KeyError:
+ options = None
+
+ file_priorities = self.get_file_priorities(torrent_id)
+ if options is not None:
+ options['file_priorities'] = file_priorities
+
+ if self.infos[torrent_id]:
+ torrents_to_add.append(
+ (
+ os.path.split(filename)[-1],
+ b64encode(self.infos[torrent_id]),
+ options,
+ )
+ )
+ elif is_magnet(filename):
+ client.core.add_torrent_magnet(filename, options).addErrback(log.debug)
+
+ row = self.torrent_liststore.iter_next(row)
+
+ def on_torrents_added(errors):
+ if errors:
+ log.info(
+ 'Failed to add %d out of %d torrents.',
+ len(errors),
+ len(torrents_to_add),
+ )
+ for e in errors:
+ log.info('Torrent add failed: %s', e)
+ else:
+ log.info('Successfully added %d torrents.', len(torrents_to_add))
+
+ if torrents_to_add:
+ client.core.add_torrent_files(torrents_to_add).addCallback(
+ on_torrents_added
+ )
+
+ def on_button_apply_clicked(self, widget):
+ log.debug('on_button_apply_clicked')
+ (model, row) = self.listview_torrents.get_selection().get_selected()
+ if row is None:
+ return
+
+ self.save_torrent_options(row)
+
+ # The options, except file renames, we want all the torrents to have
+ options = self.options[model.get_value(row, 0)].copy()
+ options.pop('mapped_files', None)
+
+ # Set all the torrent options
+ row = model.get_iter_first()
+ while row is not None:
+ torrent_id = model.get_value(row, 0)
+ self.options[torrent_id].update(options)
+ row = model.iter_next(row)
+
+ def on_button_revert_clicked(self, widget):
+ log.debug('on_button_revert_clicked')
+ (model, row) = self.listview_torrents.get_selection().get_selected()
+ if row is None:
+ return
+
+ del self.options[model.get_value(row, 0)]
+ self.set_default_options()
+
+ def on_chk_move_completed_toggled(self, widget):
+ value = widget.get_active()
+ self.move_completed_path_chooser.set_sensitive(value)
+
+ def _on_delete_event(self, widget, event):
+ self.hide()
+ return True
+
+ def get_file_path(self, row, path=''):
+ if not row:
+ return path
+
+ path = self.files_treestore[row][1] + path
+ return self.get_file_path(self.files_treestore.iter_parent(row), path)
+
+ def _on_filename_edited(self, renderer, path, new_text):
+ index = self.files_treestore[path][3]
+
+ new_text = new_text.strip(os.path.sep).strip()
+
+ # Return if the text hasn't changed
+ if new_text == self.files_treestore[path][1]:
+ return
+
+ # Get the tree iter
+ itr = self.files_treestore.get_iter(path)
+
+ # Get the torrent_id
+ (model, row) = self.listview_torrents.get_selection().get_selected()
+ torrent_id = model[row][0]
+
+ if 'mapped_files' not in self.options[torrent_id]:
+ self.options[torrent_id]['mapped_files'] = {}
+
+ if index > -1:
+ # We're renaming a file! Yay! That's easy!
+ if not new_text:
+ return
+ parent = self.files_treestore.iter_parent(itr)
+ file_path = os.path.join(self.get_file_path(parent), new_text)
+ # Don't rename if filename exists
+ if parent:
+ for row in self.files_treestore[parent].iterchildren():
+ if new_text == row[1]:
+ return
+ if os.path.sep in new_text:
+ # There are folders in this path, so we need to create them
+ # and then move the file iter to top
+ split_text = new_text.split(os.path.sep)
+ for s in split_text[:-1]:
+ parent = self.files_treestore.append(
+ parent, [True, s, 0, -1, False, 'folder-symbolic']
+ )
+
+ self.files_treestore[itr][1] = split_text[-1]
+ reparent_iter(self.files_treestore, itr, parent)
+ else:
+ # Update the row's text
+ self.files_treestore[itr][1] = new_text
+
+ # Update the mapped_files dict in the options with the index and new
+ # file path.
+ # We'll send this to the core when adding the torrent so it knows
+ # what to rename before adding.
+ self.options[torrent_id]['mapped_files'][index] = file_path
+ self.files[torrent_id][index]['path'] = file_path
+ else:
+ # Folder!
+ def walk_tree(row):
+ if not row:
+ return
+
+ # Get the file path base once, since it will be the same for
+ # all siblings
+ file_path_base = self.get_file_path(
+ self.files_treestore.iter_parent(row)
+ )
+
+ # Iterate through all the siblings at this level
+ while row:
+ # We recurse if there are children
+ if self.files_treestore.iter_has_child(row):
+ walk_tree(self.files_treestore.iter_children(row))
+
+ index = self.files_treestore[row][3]
+
+ if index > -1:
+ # Get the new full path for this file
+ file_path = file_path_base + self.files_treestore[row][1]
+
+ # Update the file path in the mapped_files dict
+ self.options[torrent_id]['mapped_files'][index] = file_path
+ self.files[torrent_id][index]['path'] = file_path
+
+ # Get the next siblings iter
+ row = self.files_treestore.iter_next(row)
+
+ # Update the treestore row first so that when walking the tree
+ # we can construct the new proper paths
+
+ # We need to check if this folder has been split
+ if os.path.sep in new_text:
+ # It's been split, so we need to add new folders and then re-parent
+ # itr.
+ parent = self.files_treestore.iter_parent(itr)
+ split_text = new_text.split(os.path.sep)
+ for s in split_text[:-1]:
+ # We don't iterate over the last item because we'll just use
+ # the existing itr and change the text
+ parent = self.files_treestore.append(
+ parent, [True, s + os.path.sep, 0, -1, False, 'folder-symbolic']
+ )
+
+ self.files_treestore[itr][1] = split_text[-1] + os.path.sep
+
+ # Now re-parent itr to parent
+ reparent_iter(self.files_treestore, itr, parent)
+ itr = parent
+
+ # We need to re-expand the view because it might contracted
+ # if we change the root iter
+ root = Gtk.TreePath.new_first()
+ self.listview_files.expand_row(root, False)
+ else:
+ # This was a simple folder rename without any splits, so just
+ # change the path for itr
+ self.files_treestore[itr][1] = new_text + os.path.sep
+
+ # Walk through the tree from 'itr' and add all the new file paths
+ # to the 'mapped_files' option
+ walk_tree(itr)
diff --git a/deluge/ui/gtk3/common.py b/deluge/ui/gtk3/common.py
new file mode 100644
index 0000000..8359327
--- /dev/null
+++ b/deluge/ui/gtk3/common.py
@@ -0,0 +1,395 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Marcos Mobley ('markybob') <markybob@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+"""Common functions for various parts of gtkui to use."""
+from __future__ import unicode_literals
+
+import contextlib
+import logging
+import os
+import shutil
+import sys
+
+import six.moves.cPickle as pickle # noqa: N813
+from gi.repository.Gdk import SELECTION_CLIPBOARD, Display
+from gi.repository.GdkPixbuf import Colorspace, Pixbuf
+from gi.repository.GLib import GError
+from gi.repository.Gtk import (
+ Clipboard,
+ IconTheme,
+ Menu,
+ MenuItem,
+ RadioMenuItem,
+ SeparatorMenuItem,
+ SortType,
+)
+
+from deluge.common import PY2, get_pixmap, osx_check, windows_check
+
+log = logging.getLogger(__name__)
+
+
+def cmp(x, y):
+ """Replacement for built-in function cmp that was removed in Python 3.
+
+ Compare the two objects x and y and return an integer according to
+ the outcome. The return value is negative if x < y, zero if x == y
+ and strictly positive if x > y.
+ """
+
+ return (x > y) - (x < y)
+
+
+def create_blank_pixbuf(size=16):
+ pix = Pixbuf.new(Colorspace.RGB, True, 8, size, size)
+ pix.fill(0x0)
+ return pix
+
+
+def get_pixbuf(filename):
+ try:
+ return Pixbuf.new_from_file(get_pixmap(filename))
+ except GError as ex:
+ log.warning(ex)
+ return create_blank_pixbuf()
+
+
+# Status icons.. Create them from file only once to avoid constantly re-creating them.
+icon_downloading = get_pixbuf('downloading16.png')
+icon_seeding = get_pixbuf('seeding16.png')
+icon_inactive = get_pixbuf('inactive16.png')
+icon_alert = get_pixbuf('alert16.png')
+icon_queued = get_pixbuf('queued16.png')
+icon_checking = get_pixbuf('checking16.png')
+
+
+def get_pixbuf_at_size(filename, size):
+ if not os.path.isabs(filename):
+ filename = get_pixmap(filename)
+ try:
+ return Pixbuf.new_from_file_at_size(filename, size, size)
+ except GError as ex:
+ # Failed to load the pixbuf (Bad image file), so return a blank pixbuf.
+ log.warning(ex)
+ return create_blank_pixbuf(size)
+
+
+def get_logo(size):
+ """A Deluge logo.
+
+ Params:
+ size (int): Size of logo in pixels
+
+ Returns:
+ Pixbuf: deluge logo
+ """
+ filename = 'deluge.svg'
+ if windows_check():
+ filename = 'deluge.png'
+ return get_pixbuf_at_size(filename, size)
+
+
+def build_menu_radio_list(
+ value_list,
+ callback,
+ pref_value=None,
+ suffix=None,
+ show_notset=False,
+ notset_label='∞',
+ notset_lessthan=0,
+ show_other=False,
+):
+ """Build a menu with radio menu items from a list and connect them to the callback.
+
+ Params:
+ value_list [list]: List of values to build into a menu.
+ callback (function): The function to call when menu item is clicked.
+ pref_value (int): A preferred value to insert into value_list
+ suffix (str): Append a suffix the the menu items in value_list.
+ show_notset (bool): Show the unlimited menu item.
+ notset_label (str): The text for the unlimited menu item.
+ notset_lessthan (int): Activates the unlimited menu item if pref_value is less than this.
+ show_other (bool): Show the `Other` menu item.
+
+ The pref_value is what you would like to test for the default active radio item.
+
+ Returns:
+ Menu: The menu radio
+ """
+ menu = Menu()
+ # Create menuitem to prevent unwanted toggled callback when creating menu.
+ menuitem = RadioMenuItem()
+ group = menuitem.get_group()
+
+ if pref_value > -1 and pref_value not in value_list:
+ value_list.pop()
+ value_list.append(pref_value)
+
+ for value in sorted(value_list):
+ item_text = str(value)
+ if suffix:
+ item_text += ' ' + suffix
+ menuitem = RadioMenuItem.new_with_label(group, item_text)
+ if pref_value and value == pref_value:
+ menuitem.set_active(True)
+ if callback:
+ menuitem.connect('toggled', callback)
+ menu.append(menuitem)
+
+ if show_notset:
+ menuitem = RadioMenuItem.new_with_label(group, notset_label)
+ menuitem.set_name('unlimited')
+ if pref_value and pref_value < notset_lessthan:
+ menuitem.set_active(True)
+ menuitem.connect('toggled', callback)
+ menu.append(menuitem)
+
+ if show_other:
+ menuitem = SeparatorMenuItem()
+ menu.append(menuitem)
+ menuitem = MenuItem.new_with_label(_('Other...'))
+ menuitem.set_name('other')
+ menuitem.connect('activate', callback)
+ menu.append(menuitem)
+
+ return menu
+
+
+def reparent_iter(treestore, itr, parent, move_siblings=False):
+ """
+ This effectively moves itr plus it's children to be a child of parent in treestore
+
+ Params:
+ treestore (gtkTreeStore): the treestore
+ itr (gtkTreeIter): the iter to move
+ parent (gtkTreeIter): the new parent for itr
+ move_siblings (bool): if True, it will move all itr's siblings to parent
+ """
+ src = itr
+
+ def move_children(i, dest):
+ while i:
+ n_cols = treestore.append(
+ dest, treestore.get(i, *range(treestore.get_n_columns()))
+ )
+ to_remove = i
+ if treestore.iter_children(i):
+ move_children(treestore.iter_children(i), n_cols)
+ if not move_siblings and i == src:
+ i = None
+ else:
+ i = treestore.iter_next(i)
+
+ treestore.remove(to_remove)
+
+ move_children(itr, parent)
+
+
+def get_deluge_icon():
+ """The deluge icon for use in dialogs.
+
+ It will first attempt to get the icon from the theme and will fallback to using an image
+ that is distributed with the package.
+
+ Returns:
+ Pixbuf: the deluge icon
+ """
+ if windows_check():
+ return get_logo(32)
+ else:
+ try:
+ icon_theme = IconTheme.get_default()
+ return icon_theme.load_icon('deluge', 64, 0)
+ except GError:
+ return get_logo(64)
+
+
+def associate_magnet_links(overwrite=False):
+ """
+ Associates magnet links to Deluge.
+
+ Params:
+ overwrite (bool): if this is True, the current setting will be overwritten
+
+ Returns:
+ bool: True if association was set
+ """
+
+ if windows_check():
+ try:
+ import winreg
+ except ImportError:
+ import _winreg as winreg # For Python 2.
+
+ try:
+ hkey = winreg.OpenKey(winreg.HKEY_CLASSES_ROOT, 'Magnet')
+ except WindowsError:
+ overwrite = True
+ else:
+ winreg.CloseKey(hkey)
+
+ if overwrite:
+ deluge_exe = os.path.join(os.path.dirname(sys.executable), 'deluge.exe')
+ try:
+ magnet_key = winreg.CreateKey(winreg.HKEY_CLASSES_ROOT, 'Magnet')
+ except WindowsError:
+ # Could not create for all users, falling back to current user
+ magnet_key = winreg.CreateKey(
+ winreg.HKEY_CURRENT_USER, 'Software\\Classes\\Magnet'
+ )
+
+ winreg.SetValue(magnet_key, '', winreg.REG_SZ, 'URL:Magnet Protocol')
+ winreg.SetValueEx(magnet_key, 'URL Protocol', 0, winreg.REG_SZ, '')
+ winreg.SetValueEx(magnet_key, 'BrowserFlags', 0, winreg.REG_DWORD, 0x8)
+ winreg.SetValue(
+ magnet_key, 'DefaultIcon', winreg.REG_SZ, '{},0'.format(deluge_exe)
+ )
+ winreg.SetValue(
+ magnet_key,
+ r'shell\open\command',
+ winreg.REG_SZ,
+ '"{}" "%1"'.format(deluge_exe),
+ )
+ winreg.CloseKey(magnet_key)
+
+ # Don't try associate magnet on OSX see: #2420
+ elif not osx_check():
+ # gconf method is only available in a GNOME environment
+ try:
+ import gi
+
+ gi.require_version('GConf', '2.0')
+ from gi.repository import GConf
+ except ValueError:
+ log.debug(
+ 'gconf not available, so will not attempt to register magnet uri handler'
+ )
+ return False
+ else:
+ key = '/desktop/gnome/url-handlers/magnet/command'
+ gconf_client = GConf.Client.get_default()
+ if (gconf_client.get(key) and overwrite) or not gconf_client.get(key):
+ # We are either going to overwrite the key, or do it if it hasn't been set yet
+ if gconf_client.set_string(key, 'deluge "%s"'):
+ gconf_client.set_bool(
+ '/desktop/gnome/url-handlers/magnet/needs_terminal', False
+ )
+ gconf_client.set_bool(
+ '/desktop/gnome/url-handlers/magnet/enabled', True
+ )
+ log.info('Deluge registered as default magnet uri handler!')
+ return True
+ else:
+ log.error(
+ 'Unable to register Deluge as default magnet uri handler.'
+ )
+ return False
+ return False
+
+
+def save_pickled_state_file(filename, state):
+ """Save a file in the config directory and creates a backup
+
+ Params:
+ filename (str): Filename to be saved to config
+ state (state): The data to be pickled and written to file
+ """
+ from deluge.configmanager import get_config_dir
+
+ filepath = os.path.join(get_config_dir(), 'gtk3ui_state', filename)
+ filepath_bak = filepath + '.bak'
+ filepath_tmp = filepath + '.tmp'
+
+ try:
+ if os.path.isfile(filepath):
+ log.debug('Creating backup of %s at: %s', filename, filepath_bak)
+ shutil.copy2(filepath, filepath_bak)
+ except IOError as ex:
+ log.error('Unable to backup %s to %s: %s', filepath, filepath_bak, ex)
+ else:
+ log.info('Saving the %s at: %s', filename, filepath)
+ try:
+ with open(filepath_tmp, 'wb') as _file:
+ # Pickle the state object
+ pickle.dump(state, _file, protocol=2)
+ _file.flush()
+ os.fsync(_file.fileno())
+ shutil.move(filepath_tmp, filepath)
+ except (IOError, EOFError, pickle.PicklingError) as ex:
+ log.error('Unable to save %s: %s', filename, ex)
+ if os.path.isfile(filepath_bak):
+ log.info('Restoring backup of %s from: %s', filename, filepath_bak)
+ shutil.move(filepath_bak, filepath)
+
+
+def load_pickled_state_file(filename):
+ """Loads a file from the config directory, attempting backup if original fails to load.
+
+ Params:
+ filename (str): Filename to be loaded from config
+
+ Returns:
+ state: the unpickled state
+ """
+ from deluge.configmanager import get_config_dir
+
+ filepath = os.path.join(get_config_dir(), 'gtk3ui_state', filename)
+ filepath_bak = filepath + '.bak'
+
+ for _filepath in (filepath, filepath_bak):
+ log.info('Opening %s for load: %s', filename, _filepath)
+ try:
+ with open(_filepath, 'rb') as _file:
+ if PY2:
+ state = pickle.load(_file)
+ else:
+ state = pickle.load(_file, encoding='utf8')
+ except (IOError, pickle.UnpicklingError) as ex:
+ log.warning('Unable to load %s: %s', _filepath, ex)
+ else:
+ log.info('Successfully loaded %s: %s', filename, _filepath)
+ return state
+
+
+@contextlib.contextmanager
+def listview_replace_treestore(listview):
+ """Prepare a listview's treestore to be entirely replaced.
+
+ Params:
+ listview: a listview backed by a treestore
+ """
+ # From http://faq.pygtk.org/index.py?req=show&file=faq13.043.htp
+ # "tips for improving performance when adding many rows to a Treeview"
+ listview.freeze_child_notify()
+ treestore = listview.get_model()
+ listview.set_model(None)
+ treestore.clear()
+ treestore.set_default_sort_func(lambda *args: 0)
+ original_sort = treestore.get_sort_column_id()
+ treestore.set_sort_column_id(-1, SortType.ASCENDING)
+
+ yield
+
+ if original_sort != (None, None):
+ treestore.set_sort_column_id(*original_sort)
+
+ listview.set_model(treestore)
+ listview.thaw_child_notify()
+
+
+def get_clipboard_text():
+ text = (
+ Clipboard.get(selection=SELECTION_CLIPBOARD).wait_for_text()
+ or Clipboard.get().wait_for_text()
+ )
+ if text:
+ return text.strip()
+
+
+def windowing(like):
+ return like.lower() in str(type(Display.get_default())).lower()
diff --git a/deluge/ui/gtk3/connectionmanager.py b/deluge/ui/gtk3/connectionmanager.py
new file mode 100644
index 0000000..d5883c4
--- /dev/null
+++ b/deluge/ui/gtk3/connectionmanager.py
@@ -0,0 +1,568 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os
+from socket import gaierror, gethostbyname
+
+from gi.repository import Gtk
+from twisted.internet import defer, reactor
+
+import deluge.component as component
+from deluge.common import resource_filename, windows_check
+from deluge.configmanager import ConfigManager, get_config_dir
+from deluge.error import AuthenticationRequired, BadLoginError, IncompatibleClient
+from deluge.ui.client import Client, client
+from deluge.ui.hostlist import DEFAULT_PORT, LOCALHOST, HostList
+
+from .common import get_clipboard_text
+from .dialogs import AuthenticationDialog, ErrorDialog
+
+try:
+ from urllib.parse import urlparse
+except ImportError:
+ # PY2 fallback
+ from urlparse import urlparse # pylint: disable=ungrouped-imports
+
+log = logging.getLogger(__name__)
+
+HOSTLIST_COL_ID = 0
+HOSTLIST_COL_HOST = 1
+HOSTLIST_COL_PORT = 2
+HOSTLIST_COL_USER = 3
+HOSTLIST_COL_PASS = 4
+HOSTLIST_COL_STATUS = 5
+HOSTLIST_COL_VERSION = 6
+HOSTLIST_COL_STATUS_I18N = 7
+
+HOSTLIST_ICONS = {
+ 'offline': 'action-unavailable-symbolic',
+ 'online': 'network-server-symbolic',
+ 'connected': 'network-transmit-receive-symbolic',
+}
+STATUS_I18N = {
+ 'offline': _('Offline'),
+ 'online': _('Online'),
+ 'connected': _('Connected'),
+}
+
+
+def cell_render_host(column, cell, model, row, data):
+ host, port, username = model.get(row, *data)
+ text = host + ':' + str(port)
+ if username:
+ text = username + '@' + text
+ cell.set_property('text', text)
+
+
+def cell_render_status_icon(column, cell, model, row, data):
+ status = model[row][data]
+ status = status if status else 'offline'
+ icon_name = HOSTLIST_ICONS.get(status, None)
+ cell.set_property('icon-name', icon_name)
+
+
+class ConnectionManager(component.Component):
+ def __init__(self):
+ component.Component.__init__(self, 'ConnectionManager')
+ self.gtkui_config = ConfigManager('gtk3ui.conf')
+ self.hostlist = HostList()
+ self.running = False
+
+ # Component overrides
+ def start(self):
+ pass
+
+ def stop(self):
+ # Close this dialog when we are shutting down
+ if self.running:
+ self.connection_manager.response(Gtk.ResponseType.CLOSE)
+
+ def shutdown(self):
+ pass
+
+ # Public methods
+ def show(self):
+ """Show the ConnectionManager dialog."""
+ self.builder = Gtk.Builder()
+ self.builder.add_from_file(
+ resource_filename(
+ __package__, os.path.join('glade', 'connection_manager.ui')
+ )
+ )
+ self.connection_manager = self.builder.get_object('connection_manager')
+ self.connection_manager.set_transient_for(component.get('MainWindow').window)
+
+ # Setup the hostlist liststore and treeview
+ self.treeview = self.builder.get_object('treeview_hostlist')
+ self.treeview.set_tooltip_column(HOSTLIST_COL_STATUS_I18N)
+ self.liststore = self.builder.get_object('liststore_hostlist')
+
+ render = Gtk.CellRendererPixbuf()
+ column = Gtk.TreeViewColumn(_('Status'), render)
+ column.set_cell_data_func(render, cell_render_status_icon, HOSTLIST_COL_STATUS)
+ self.treeview.append_column(column)
+
+ render = Gtk.CellRendererText()
+ column = Gtk.TreeViewColumn(_('Host'), render, text=HOSTLIST_COL_HOST)
+ host_data = (HOSTLIST_COL_HOST, HOSTLIST_COL_PORT, HOSTLIST_COL_USER)
+ column.set_cell_data_func(render, cell_render_host, host_data)
+ column.set_expand(True)
+ self.treeview.append_column(column)
+
+ column = Gtk.TreeViewColumn(
+ _('Version'), Gtk.CellRendererText(), text=HOSTLIST_COL_VERSION
+ )
+ self.treeview.append_column(column)
+
+ # Load any saved host entries
+ self._load_liststore()
+ # Set widgets to values from gtkui config.
+ self._load_widget_config()
+ self._update_widget_buttons()
+
+ # Connect the signals to the handlers
+ self.builder.connect_signals(self)
+ self.treeview.get_selection().connect(
+ 'changed', self.on_hostlist_selection_changed
+ )
+
+ # Set running True before update status call.
+ self.running = True
+
+ if windows_check():
+ # Call to simulate() required to workaround showing daemon status (see #2813)
+ reactor.simulate()
+ self._update_host_status()
+
+ # Trigger the on_selection_changed code and select the first host if possible
+ self.treeview.get_selection().unselect_all()
+ if len(self.liststore):
+ self.treeview.get_selection().select_path(0)
+
+ # Run the dialog
+ self.connection_manager.run()
+
+ # Dialog closed so cleanup.
+ self.running = False
+ self.connection_manager.destroy()
+ del self.builder
+ del self.connection_manager
+ del self.liststore
+ del self.treeview
+
+ def _load_liststore(self):
+ """Load saved host entries"""
+ for host_entry in self.hostlist.get_hosts_info():
+ host_id, host, port, username = host_entry
+ self.liststore.append([host_id, host, port, username, '', '', '', ''])
+
+ def _load_widget_config(self):
+ """Set the widgets to show the correct options from the config."""
+ self.builder.get_object('chk_autoconnect').set_active(
+ self.gtkui_config['autoconnect']
+ )
+ self.builder.get_object('chk_autostart').set_active(
+ self.gtkui_config['autostart_localhost']
+ )
+ self.builder.get_object('chk_donotshow').set_active(
+ not self.gtkui_config['show_connection_manager_on_start']
+ )
+
+ def _update_host_status(self):
+ """Updates the host status"""
+ if not self.running:
+ # Callback likely fired after the window closed.
+ return
+
+ def on_host_status(status_info, row):
+ if self.running and row:
+ status = status_info[1].lower()
+ row[HOSTLIST_COL_STATUS] = status
+ row[HOSTLIST_COL_STATUS_I18N] = STATUS_I18N[status]
+ row[HOSTLIST_COL_VERSION] = status_info[2]
+ self._update_widget_buttons()
+
+ deferreds = []
+ for row in self.liststore:
+ host_id = row[HOSTLIST_COL_ID]
+ d = self.hostlist.get_host_status(host_id)
+ try:
+ d.addCallback(on_host_status, row)
+ except AttributeError:
+ on_host_status(d, row)
+ else:
+ deferreds.append(d)
+ defer.DeferredList(deferreds)
+
+ def _update_widget_buttons(self):
+ """Updates the dialog button states."""
+ self.builder.get_object('button_refresh').set_sensitive(len(self.liststore))
+ self.builder.get_object('button_startdaemon').set_sensitive(False)
+ self.builder.get_object('button_connect').set_sensitive(False)
+ self.builder.get_object('button_connect').set_label(_('C_onnect'))
+ self.builder.get_object('button_edithost').set_sensitive(False)
+ self.builder.get_object('button_removehost').set_sensitive(False)
+ self.builder.get_object('button_startdaemon').set_sensitive(False)
+ self.builder.get_object('image_startdaemon').set_from_icon_name(
+ 'system-run-symbolic', Gtk.IconSize.BUTTON
+ )
+ self.builder.get_object('label_startdaemon').set_text_with_mnemonic(
+ _('_Start Daemon')
+ )
+
+ model, row = self.treeview.get_selection().get_selected()
+ if row:
+ self.builder.get_object('button_edithost').set_sensitive(True)
+ self.builder.get_object('button_removehost').set_sensitive(True)
+ else:
+ return
+
+ # Get selected host info.
+ __, host, port, __, __, status, __, __ = model[row]
+
+ try:
+ gethostbyname(host)
+ except gaierror as ex:
+ log.error(
+ 'Error resolving host %s to ip: %s', row[HOSTLIST_COL_HOST], ex.args[1]
+ )
+ self.builder.get_object('button_connect').set_sensitive(False)
+ return
+
+ log.debug('Host Status: %s, %s', host, status)
+
+ # Check to see if the host is online
+ if status == 'connected' or status == 'online':
+ self.builder.get_object('button_connect').set_sensitive(True)
+ self.builder.get_object('image_startdaemon').set_from_icon_name(
+ 'process-stop-symbolic', Gtk.IconSize.MENU
+ )
+ self.builder.get_object('label_startdaemon').set_text_with_mnemonic(
+ _('_Stop Daemon')
+ )
+ self.builder.get_object('button_startdaemon').set_sensitive(False)
+ if status == 'connected':
+ # Display a disconnect button if we're connected to this host
+ self.builder.get_object('button_connect').set_label(_('_Disconnect'))
+ self.builder.get_object('button_removehost').set_sensitive(False)
+ # Currently can only stop daemon when connected to it
+ self.builder.get_object('button_startdaemon').set_sensitive(True)
+ elif host in LOCALHOST:
+ # If localhost we can start the dameon.
+ self.builder.get_object('button_startdaemon').set_sensitive(True)
+
+ def start_daemon(self, port, config):
+ """Attempts to start local daemon process and will show an ErrorDialog if not.
+
+ Args:
+ port (int): Port for the daemon to listen on.
+ config (str): Config path to pass to daemon.
+
+ Returns:
+ bool: True is successfully started the daemon, False otherwise.
+
+ """
+ if client.start_daemon(port, config):
+ log.debug('Localhost daemon started')
+ reactor.callLater(1, self._update_host_status)
+ return True
+ else:
+ ErrorDialog(
+ _('Unable to start daemon!'),
+ _('Check deluged package is installed and logs for further details'),
+ ).run()
+ return False
+
+ # Signal handlers
+ def _connect(self, host_id, username=None, password=None, try_counter=0):
+ def do_connect(result, username=None, password=None, *args):
+ log.debug('Attempting to connect to daemon...')
+ for host_entry in self.hostlist.config['hosts']:
+ if host_entry[0] == host_id:
+ __, host, port, host_user, host_pass = host_entry
+
+ username = username if username else host_user
+ password = password if password else host_pass
+
+ d = client.connect(host, port, username, password)
+ d.addCallback(self._on_connect, host_id)
+ d.addErrback(self._on_connect_fail, host_id, try_counter)
+ return d
+
+ if client.connected():
+ return client.disconnect().addCallback(do_connect, username, password)
+ else:
+ return do_connect(None, username, password)
+
+ def _on_connect(self, daemon_info, host_id):
+ log.debug('Connected to daemon: %s', host_id)
+ if self.gtkui_config['autoconnect']:
+ self.gtkui_config['autoconnect_host_id'] = host_id
+ if self.running:
+ # When connected to a client, and then trying to connect to another,
+ # this component will be stopped(while the connect deferred is
+ # running), so, self.connection_manager will be deleted.
+ # If that's not the case, close the dialog.
+ self.connection_manager.response(Gtk.ResponseType.OK)
+ component.start()
+
+ def _on_connect_fail(self, reason, host_id, try_counter):
+ log.debug('Failed to connect: %s', reason.value)
+
+ if reason.check(AuthenticationRequired, BadLoginError):
+ log.debug('PasswordRequired exception')
+ dialog = AuthenticationDialog(reason.value.message, reason.value.username)
+
+ def dialog_finished(response_id):
+ if response_id == Gtk.ResponseType.OK:
+ self._connect(host_id, dialog.get_username(), dialog.get_password())
+
+ return dialog.run().addCallback(dialog_finished)
+
+ elif reason.check(IncompatibleClient):
+ return ErrorDialog(_('Incompatible Client'), reason.value.message).run()
+
+ if try_counter:
+ log.info('Retrying connection.. Retries left: %s', try_counter)
+ return reactor.callLater(
+ 0.5, self._connect, host_id, try_counter=try_counter - 1
+ )
+
+ msg = str(reason.value)
+ if not self.gtkui_config['autostart_localhost']:
+ msg += '\n' + _(
+ 'Auto-starting the daemon locally is not enabled. '
+ 'See "Options" on the "Connection Manager".'
+ )
+ ErrorDialog(_('Failed To Connect'), msg).run()
+
+ def on_button_connect_clicked(self, widget=None):
+ """Button handler for connect to or disconnect from daemon."""
+ model, row = self.treeview.get_selection().get_selected()
+ if not row:
+ return
+
+ host_id, host, port, __, __, status, __, __ = model[row]
+ # If status is connected then connect button disconnects instead.
+ if status == 'connected':
+
+ def on_disconnect(reason):
+ self._update_host_status()
+
+ return client.disconnect().addCallback(on_disconnect)
+
+ try_counter = 0
+ auto_start = self.builder.get_object('chk_autostart').get_active()
+ if auto_start and host in LOCALHOST and status == 'offline':
+ # Start the local daemon and then connect with retries set.
+ if self.start_daemon(port, get_config_dir()):
+ try_counter = 6
+ else:
+ # Don't attempt to connect to offline daemon.
+ return
+
+ self._connect(host_id, try_counter=try_counter)
+
+ def on_button_close_clicked(self, widget):
+ self.connection_manager.response(Gtk.ResponseType.CLOSE)
+
+ def _run_addhost_dialog(self, edit_host_info=None):
+ """Create and runs the add host dialog.
+
+ Supplying edit_host_info changes the dialog to an edit dialog.
+
+ Args:
+ edit_host_info (list): A list of (host, port, user, pass) to edit.
+
+ Returns:
+ list: The new host info values (host, port, user, pass).
+
+ """
+ self.builder.add_from_file(
+ resource_filename(
+ __package__, os.path.join('glade', 'connection_manager.addhost.ui')
+ )
+ )
+ dialog = self.builder.get_object('addhost_dialog')
+ dialog.set_transient_for(self.connection_manager)
+ hostname_entry = self.builder.get_object('entry_hostname')
+ port_spinbutton = self.builder.get_object('spinbutton_port')
+ username_entry = self.builder.get_object('entry_username')
+ password_entry = self.builder.get_object('entry_password')
+
+ if edit_host_info:
+ dialog.set_title(_('Edit Host'))
+ hostname_entry.set_text(edit_host_info[0])
+ port_spinbutton.set_value(edit_host_info[1])
+ username_entry.set_text(edit_host_info[2])
+ password_entry.set_text(edit_host_info[3])
+
+ response = dialog.run()
+ new_host_info = []
+ if response:
+ new_host_info.append(hostname_entry.get_text())
+ new_host_info.append(port_spinbutton.get_value_as_int())
+ new_host_info.append(username_entry.get_text())
+ new_host_info.append(password_entry.get_text())
+
+ dialog.destroy()
+ return new_host_info
+
+ def on_button_addhost_clicked(self, widget):
+ log.debug('on_button_addhost_clicked')
+ host_info = self._run_addhost_dialog()
+ if host_info:
+ hostname, port, username, password = host_info
+ try:
+ host_id = self.hostlist.add_host(hostname, port, username, password)
+ except ValueError as ex:
+ ErrorDialog(_('Error Adding Host'), ex).run()
+ else:
+ status = 'offline'
+ version = ''
+ self.liststore.append(
+ [
+ host_id,
+ hostname,
+ port,
+ username,
+ password,
+ status,
+ version,
+ STATUS_I18N[status],
+ ]
+ )
+ self._update_host_status()
+
+ def on_button_edithost_clicked(self, widget=None):
+ log.debug('on_button_edithost_clicked')
+ model, row = self.treeview.get_selection().get_selected()
+ status = model[row][HOSTLIST_COL_STATUS]
+ host_id = model[row][HOSTLIST_COL_ID]
+ host_info = [
+ self.liststore[row][HOSTLIST_COL_HOST],
+ self.liststore[row][HOSTLIST_COL_PORT],
+ self.liststore[row][HOSTLIST_COL_USER],
+ self.liststore[row][HOSTLIST_COL_PASS],
+ ]
+
+ new_host_info = self._run_addhost_dialog(edit_host_info=host_info)
+ if new_host_info:
+ hostname, port, username, password = new_host_info
+ try:
+ self.hostlist.update_host(host_id, hostname, port, username, password)
+ except ValueError as ex:
+ ErrorDialog(_('Error Updating Host'), ex).run()
+ else:
+ self.liststore[row] = (
+ host_id,
+ hostname,
+ port,
+ username,
+ password,
+ '',
+ '',
+ '',
+ )
+ self._update_host_status()
+
+ if status == 'connected':
+
+ def on_disconnect(reason):
+ self._update_host_status()
+
+ client.disconnect().addCallback(on_disconnect)
+
+ def on_button_removehost_clicked(self, widget):
+ log.debug('on_button_removehost_clicked')
+ # Get the selected rows
+ model, row = self.treeview.get_selection().get_selected()
+ self.hostlist.remove_host(model[row][HOSTLIST_COL_ID])
+ self.liststore.remove(row)
+ # Update the hostlist
+ self._update_host_status()
+
+ def on_button_startdaemon_clicked(self, widget):
+ log.debug('on_button_startdaemon_clicked')
+ if not self.liststore.iter_n_children(None):
+ # There is nothing in the list, so lets create a localhost entry
+ try:
+ self.hostlist.add_default_host()
+ except ValueError as ex:
+ log.error('Error adding default host: %s', ex)
+ else:
+ self.start_daemon(DEFAULT_PORT, get_config_dir())
+ finally:
+ return
+
+ paths = self.treeview.get_selection().get_selected_rows()[1]
+ if len(paths):
+ __, host, port, user, password, status, __, __ = self.liststore[paths[0]]
+ else:
+ return
+
+ if host not in LOCALHOST:
+ return
+
+ def on_daemon_status_change(result):
+ """Daemon start/stop callback"""
+ reactor.callLater(0.7, self._update_host_status)
+
+ if status in ('online', 'connected'):
+ # Button will stop the daemon if status is online or connected.
+ def on_connect(d, c):
+ """Client callback to call daemon shutdown"""
+ c.daemon.shutdown().addCallback(on_daemon_status_change)
+
+ if client.connected() and (host, port, user) == client.connection_info():
+ client.daemon.shutdown().addCallback(on_daemon_status_change)
+ elif user and password:
+ c = Client()
+ c.connect(host, port, user, password).addCallback(on_connect, c)
+ else:
+ # Otherwise button will start the daemon.
+ self.start_daemon(port, get_config_dir())
+
+ def on_button_refresh_clicked(self, widget):
+ self._update_host_status()
+
+ def on_hostlist_row_activated(self, tree, path, view_column):
+ self.on_button_connect_clicked()
+
+ def on_hostlist_selection_changed(self, treeselection):
+ self._update_widget_buttons()
+
+ def on_chk_toggled(self, widget):
+ self.gtkui_config['autoconnect'] = self.builder.get_object(
+ 'chk_autoconnect'
+ ).get_active()
+ self.gtkui_config['autostart_localhost'] = self.builder.get_object(
+ 'chk_autostart'
+ ).get_active()
+ self.gtkui_config[
+ 'show_connection_manager_on_start'
+ ] = not self.builder.get_object('chk_donotshow').get_active()
+
+ def on_entry_host_paste_clipboard(self, widget):
+ text = get_clipboard_text()
+ log.debug('on_entry_proxy_host_paste-clipboard: got paste: %s', text)
+ text = text if '//' in text else '//' + text
+ parsed = urlparse(text)
+ if parsed.hostname:
+ widget.set_text(parsed.hostname)
+ widget.emit_stop_by_name('paste-clipboard')
+ if parsed.port:
+ self.builder.get_object('spinbutton_port').set_value(parsed.port)
+ if parsed.username:
+ self.builder.get_object('entry_username').set_text(parsed.username)
+ if parsed.password:
+ self.builder.get_object('entry_password').set_text(parsed.password)
diff --git a/deluge/ui/gtk3/createtorrentdialog.py b/deluge/ui/gtk3/createtorrentdialog.py
new file mode 100644
index 0000000..1e5e73c
--- /dev/null
+++ b/deluge/ui/gtk3/createtorrentdialog.py
@@ -0,0 +1,520 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import division, unicode_literals
+
+import logging
+import os.path
+from base64 import b64encode
+
+from gi.repository import Gtk
+from gi.repository.GObject import TYPE_UINT64, idle_add
+from twisted.internet.threads import deferToThread
+
+import deluge.component as component
+from deluge.common import decode_bytes, get_path_size, is_url, resource_filename
+from deluge.configmanager import ConfigManager
+from deluge.ui.client import client
+
+from .edittrackersdialog import (
+ last_tier_trackers_from_liststore,
+ trackers_tiers_from_text,
+)
+from .torrentview_data_funcs import cell_data_size
+
+log = logging.getLogger(__name__)
+
+
+class CreateTorrentDialog(object):
+ def __init__(self):
+ pass
+
+ def show(self):
+ self.builder = Gtk.Builder()
+
+ ui_filenames = [
+ 'create_torrent_dialog.ui',
+ 'create_torrent_dialog.remote_path.ui',
+ 'create_torrent_dialog.remote_save.ui',
+ 'create_torrent_dialog.progress.ui',
+ ]
+ for filename in ui_filenames:
+ self.builder.add_from_file(
+ resource_filename(__package__, os.path.join('glade', filename))
+ )
+
+ self.config = ConfigManager('gtk3ui.conf')
+
+ self.dialog = self.builder.get_object('create_torrent_dialog')
+ self.dialog.set_transient_for(component.get('MainWindow').window)
+
+ self.builder.connect_signals(self)
+
+ # path, icon, size
+ self.files_treestore = Gtk.TreeStore(str, str, TYPE_UINT64)
+
+ column = Gtk.TreeViewColumn(_('Filename'))
+ render = Gtk.CellRendererPixbuf()
+ column.pack_start(render, False)
+ column.add_attribute(render, 'icon-name', 1)
+ render = Gtk.CellRendererText()
+ column.pack_start(render, True)
+ column.add_attribute(render, 'text', 0)
+ column.set_expand(True)
+ self.builder.get_object('treeview_files').append_column(column)
+
+ column = Gtk.TreeViewColumn(_('Size'))
+ render = Gtk.CellRendererText()
+ column.pack_start(render, True)
+ column.set_cell_data_func(render, cell_data_size, 2)
+ self.builder.get_object('treeview_files').append_column(column)
+
+ self.builder.get_object('treeview_files').set_model(self.files_treestore)
+ self.builder.get_object('treeview_files').set_show_expanders(False)
+
+ # tier, url
+ self.trackers_liststore = Gtk.ListStore(int, str)
+
+ self.builder.get_object('tracker_treeview').append_column(
+ Gtk.TreeViewColumn(_('Tier'), Gtk.CellRendererText(), text=0)
+ )
+ self.builder.get_object('tracker_treeview').append_column(
+ Gtk.TreeViewColumn(_('Tracker'), Gtk.CellRendererText(), text=1)
+ )
+
+ self.builder.get_object('tracker_treeview').set_model(self.trackers_liststore)
+ self.trackers_liststore.set_sort_column_id(0, Gtk.SortType.ASCENDING)
+
+ if not client.is_localhost() and client.connected():
+ self.builder.get_object('button_remote_path').show()
+ else:
+ self.builder.get_object('button_remote_path').hide()
+
+ self.dialog.show()
+
+ def parse_piece_size_text(self, value):
+ psize, metric = value.split()
+ psize = int(psize)
+ if psize < 32:
+ # This is a MiB value
+ psize = psize * 1024 * 1024
+ else:
+ # This is a KiB value
+ psize = psize * 1024
+
+ return psize
+
+ def adjust_piece_size(self):
+ """Adjusts the recommended piece based on the file/folder/path selected."""
+ size = self.files_treestore[0][2]
+ model = self.builder.get_object('combo_piece_size').get_model()
+ for index, value in enumerate(model):
+ psize = self.parse_piece_size_text(value[0])
+ pieces = size // psize
+ if pieces < 2048 or (index + 1) == len(model):
+ self.builder.get_object('combo_piece_size').set_active(index)
+ break
+
+ def on_button_file_clicked(self, widget):
+ log.debug('on_button_file_clicked')
+ # Setup the filechooserdialog
+ chooser = Gtk.FileChooserDialog(
+ _('Choose a file'),
+ self.dialog,
+ Gtk.FileChooserAction.OPEN,
+ buttons=(
+ _('_Cancel'),
+ Gtk.ResponseType.CANCEL,
+ _('_Open'),
+ Gtk.ResponseType.OK,
+ ),
+ )
+
+ chooser.set_transient_for(self.dialog)
+ chooser.set_select_multiple(False)
+ chooser.set_property('skip-taskbar-hint', True)
+
+ # Run the dialog
+ response = chooser.run()
+
+ if response == Gtk.ResponseType.OK:
+ result = chooser.get_filename()
+ else:
+ chooser.destroy()
+ return
+
+ path = decode_bytes(result)
+
+ self.files_treestore.clear()
+ self.files_treestore.append(
+ None, [result, 'text-x-generic-symbolic', get_path_size(path)]
+ )
+ self.adjust_piece_size()
+ chooser.destroy()
+
+ def on_button_folder_clicked(self, widget):
+ log.debug('on_button_folder_clicked')
+ # Setup the filechooserdialog
+ chooser = Gtk.FileChooserDialog(
+ _('Choose a folder'),
+ self.dialog,
+ Gtk.FileChooserAction.SELECT_FOLDER,
+ buttons=(
+ _('_Cancel'),
+ Gtk.ResponseType.CANCEL,
+ _('_Open'),
+ Gtk.ResponseType.OK,
+ ),
+ )
+
+ chooser.set_transient_for(self.dialog)
+ chooser.set_select_multiple(False)
+ chooser.set_property('skip-taskbar-hint', True)
+ # Run the dialog
+ response = chooser.run()
+
+ if response == Gtk.ResponseType.OK:
+ result = chooser.get_filename()
+ else:
+ chooser.destroy()
+ return
+
+ path = decode_bytes(result)
+
+ self.files_treestore.clear()
+ self.files_treestore.append(
+ None, [result, 'document-open-symbolic', get_path_size(path)]
+ )
+ self.adjust_piece_size()
+ chooser.destroy()
+
+ def on_button_remote_path_clicked(self, widget):
+ log.debug('on_button_remote_path_clicked')
+ dialog = self.builder.get_object('remote_path_dialog')
+ entry = self.builder.get_object('entry_path')
+ dialog.set_transient_for(self.dialog)
+ entry.set_text('/')
+ entry.grab_focus()
+ response = dialog.run()
+
+ if response == Gtk.ResponseType.OK:
+ result = entry.get_text()
+
+ def _on_get_path_size(size):
+ log.debug('size: %s', size)
+ if size > 0:
+ self.files_treestore.clear()
+ self.files_treestore.append(
+ None, [result, 'network-workgroup-symbolic', size]
+ )
+ self.adjust_piece_size()
+
+ client.core.get_path_size(result).addCallback(_on_get_path_size)
+ client.force_call(True)
+
+ dialog.hide()
+
+ def on_button_cancel_clicked(self, widget):
+ log.debug('on_button_cancel_clicked')
+ self.dialog.destroy()
+
+ def on_button_save_clicked(self, widget):
+ log.debug('on_button_save_clicked')
+ if len(self.files_treestore) == 0:
+ return
+
+ # Get the path
+ path = self.files_treestore[0][0].rstrip('\\/')
+ torrent_filename = '%s.torrent' % os.path.split(path)[-1]
+
+ is_remote = 'network' in self.files_treestore[0][1]
+
+ if is_remote:
+ # This is a remote path
+ dialog = self.builder.get_object('remote_save_dialog')
+ dialog.set_transient_for(self.dialog)
+ dialog_save_path = self.builder.get_object('entry_save_path')
+ dialog_save_path.set_text(path + '.torrent')
+ response = dialog.run()
+ if response == Gtk.ResponseType.OK:
+ result = dialog_save_path.get_text()
+ else:
+ dialog.hide()
+ return
+ dialog.hide()
+ else:
+ # Setup the filechooserdialog
+ chooser = Gtk.FileChooserDialog(
+ _('Save .torrent file'),
+ self.dialog,
+ Gtk.FileChooserAction.SAVE,
+ buttons=(
+ _('_Cancel'),
+ Gtk.ResponseType.CANCEL,
+ _('_Save'),
+ Gtk.ResponseType.OK,
+ ),
+ )
+
+ chooser.set_transient_for(self.dialog)
+ chooser.set_select_multiple(False)
+ chooser.set_property('skip-taskbar-hint', True)
+
+ # Add .torrent and * file filters
+ file_filter = Gtk.FileFilter()
+ file_filter.set_name(_('Torrent files'))
+ file_filter.add_pattern('*.' + 'torrent')
+ chooser.add_filter(file_filter)
+ file_filter = Gtk.FileFilter()
+ file_filter.set_name(_('All files'))
+ file_filter.add_pattern('*')
+ chooser.add_filter(file_filter)
+
+ chooser.set_current_name(torrent_filename)
+ # Run the dialog
+ response = chooser.run()
+
+ if response == Gtk.ResponseType.OK:
+ result = chooser.get_filename()
+ else:
+ chooser.destroy()
+ return
+ chooser.destroy()
+
+ # Fix up torrent filename
+ if len(result) < 9:
+ result += '.torrent'
+ elif result[-8:] != '.torrent':
+ result += '.torrent'
+
+ # Get a list of trackers
+ trackers = []
+ if not len(self.trackers_liststore):
+ tracker = None
+ else:
+ # Create a list of lists [[tier0, ...], [tier1, ...], ...]
+ tier_dict = {}
+ for tier, tracker in self.trackers_liststore:
+ tier_dict.setdefault(tier, []).append(tracker)
+
+ trackers = [tier_dict[tier] for tier in sorted(tier_dict)]
+ # Get the first tracker in the first tier
+ tracker = trackers[0][0]
+
+ # Get a list of webseeds
+ textview_buf = self.builder.get_object('textview_webseeds').get_buffer()
+ lines = (
+ textview_buf.get_text(
+ *textview_buf.get_bounds(), include_hidden_chars=False
+ )
+ .strip()
+ .split('\n')
+ )
+ webseeds = []
+ for line in lines:
+ line = line.replace('\\', '/') # Fix any mistyped urls.
+ if is_url(line):
+ webseeds.append(line)
+ # Get the piece length in bytes
+ combo = self.builder.get_object('combo_piece_size')
+ piece_length = self.parse_piece_size_text(
+ combo.get_model()[combo.get_active()][0]
+ )
+
+ author = self.builder.get_object('entry_author').get_text()
+ comment = self.builder.get_object('entry_comments').get_text()
+ private = self.builder.get_object('chk_private_flag').get_active()
+ add_to_session = self.builder.get_object('chk_add_to_session').get_active()
+
+ if is_remote:
+
+ def torrent_created():
+ self.builder.get_object('progress_dialog').hide()
+ client.deregister_event_handler(
+ 'CreateTorrentProgressEvent', on_create_torrent_progress_event
+ )
+
+ def on_create_torrent_progress_event(piece_count, num_pieces):
+ self._on_create_torrent_progress(piece_count, num_pieces)
+ if piece_count == num_pieces:
+ from twisted.internet import reactor
+
+ reactor.callLater(0.5, torrent_created)
+
+ client.register_event_handler(
+ 'CreateTorrentProgressEvent', on_create_torrent_progress_event
+ )
+
+ client.core.create_torrent(
+ decode_bytes(path),
+ tracker,
+ piece_length,
+ comment,
+ decode_bytes(result),
+ webseeds,
+ private,
+ author,
+ trackers,
+ add_to_session,
+ )
+
+ else:
+
+ def hide_progress(result):
+ self.builder.get_object('progress_dialog').hide()
+
+ deferToThread(
+ self.create_torrent,
+ decode_bytes(path),
+ tracker,
+ piece_length,
+ self._on_create_torrent_progress,
+ comment,
+ decode_bytes(result),
+ webseeds,
+ private,
+ author,
+ trackers,
+ add_to_session,
+ ).addCallback(hide_progress)
+
+ # Setup progress dialog
+ self.builder.get_object('progress_dialog').set_transient_for(
+ component.get('MainWindow').window
+ )
+ self.builder.get_object('progress_dialog').show_all()
+
+ self.dialog.destroy()
+
+ def create_torrent(
+ self,
+ path,
+ tracker,
+ piece_length,
+ progress,
+ comment,
+ target,
+ webseeds,
+ private,
+ created_by,
+ trackers,
+ add_to_session,
+ ):
+ import deluge.metafile
+
+ deluge.metafile.make_meta_file(
+ path,
+ tracker,
+ piece_length,
+ progress=progress,
+ comment=comment,
+ target=target,
+ webseeds=webseeds,
+ private=private,
+ created_by=created_by,
+ trackers=trackers,
+ )
+
+ if add_to_session:
+ with open(target, 'rb') as _file:
+ filedump = b64encode(_file.read())
+ client.core.add_torrent_file_async(
+ os.path.split(target)[-1],
+ filedump,
+ {'download_location': os.path.split(path)[0]},
+ )
+
+ def _on_create_torrent_progress(self, value, num_pieces):
+ percent = value / num_pieces
+
+ def update_pbar_with_gobject(percent):
+ pbar = self.builder.get_object('progressbar')
+ pbar.set_text('%.2f%%' % (percent * 100))
+ pbar.set_fraction(percent)
+ return False
+
+ if percent >= 0 and percent <= 1.0:
+ # Make sure there are no threads race conditions that can
+ # crash the UI while updating it.
+ idle_add(update_pbar_with_gobject, percent)
+
+ def on_button_up_clicked(self, widget):
+ log.debug('on_button_up_clicked')
+ row = (
+ self.builder.get_object('tracker_treeview')
+ .get_selection()
+ .get_selected()[1]
+ )
+ if row is None:
+ return
+ if self.trackers_liststore[row][0] == 0:
+ return
+ else:
+ self.trackers_liststore[row][0] -= 1
+
+ def on_button_down_clicked(self, widget):
+ log.debug('on_button_down_clicked')
+ row = (
+ self.builder.get_object('tracker_treeview')
+ .get_selection()
+ .get_selected()[1]
+ )
+ if row is None:
+ return
+ self.trackers_liststore[row][0] += 1
+
+ def on_button_add_clicked(self, widget):
+ log.debug('on_button_add_clicked')
+ builder = Gtk.Builder()
+ builder.add_from_file(
+ resource_filename(
+ __package__, os.path.join('glade', 'edit_trackers.add.ui')
+ )
+ )
+ dialog = builder.get_object('add_tracker_dialog')
+ dialog.set_transient_for(self.dialog)
+ textview = builder.get_object('textview_trackers')
+ if self.config['createtorrent.trackers']:
+ textview.get_buffer().set_text(
+ '\n'.join(self.config['createtorrent.trackers'])
+ )
+ else:
+ textview.get_buffer().set_text('')
+ textview.grab_focus()
+ response = dialog.run()
+
+ if response == Gtk.ResponseType.OK:
+ # Create a list of trackers from the textview buffer
+ textview_buf = textview.get_buffer()
+ trackers_text = textview_buf.get_text(
+ *textview_buf.get_bounds(), include_hidden_chars=False
+ )
+ log.debug('Create torrent tracker lines: %s', trackers_text)
+ self.config['createtorrent.trackers'] = trackers_text.split('/n')
+
+ # Append trackers liststore with unique trackers and tiers starting from last tier number.
+ last_tier, orig_trackers = last_tier_trackers_from_liststore(
+ self.trackers_liststore
+ )
+ for tracker, tier in trackers_tiers_from_text(trackers_text).items():
+ if tracker not in orig_trackers:
+ self.trackers_liststore.append([tier + last_tier, tracker])
+
+ dialog.destroy()
+
+ def on_button_remove_clicked(self, widget):
+ log.debug('on_button_remove_clicked')
+ row = (
+ self.builder.get_object('tracker_treeview')
+ .get_selection()
+ .get_selected()[1]
+ )
+ if row is None:
+ return
+ self.trackers_liststore.remove(row)
diff --git a/deluge/ui/gtk3/details_tab.py b/deluge/ui/gtk3/details_tab.py
new file mode 100644
index 0000000..2431e08
--- /dev/null
+++ b/deluge/ui/gtk3/details_tab.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+from xml.sax.saxutils import escape as xml_escape
+
+import deluge.component as component
+from deluge.common import decode_bytes, fdate, fsize, is_url
+
+from .tab_data_funcs import fdate_or_dash, fpieces_num_size
+from .torrentdetails import Tab
+
+log = logging.getLogger(__name__)
+
+
+class DetailsTab(Tab):
+ def __init__(self):
+ super(DetailsTab, self).__init__('Details', 'details_tab', 'details_tab_label')
+
+ self.add_tab_widget('summary_name', None, ('name',))
+ self.add_tab_widget('summary_total_size', fsize, ('total_size',))
+ self.add_tab_widget('summary_num_files', str, ('num_files',))
+ self.add_tab_widget('summary_completed', fdate_or_dash, ('completed_time',))
+ self.add_tab_widget('summary_date_added', fdate, ('time_added',))
+ self.add_tab_widget('summary_torrent_path', None, ('download_location',))
+ self.add_tab_widget('summary_hash', str, ('hash',))
+ self.add_tab_widget('summary_comments', str, ('comment',))
+ self.add_tab_widget('summary_creator', str, ('creator',))
+ self.add_tab_widget(
+ 'summary_pieces', fpieces_num_size, ('num_pieces', 'piece_length')
+ )
+
+ def update(self):
+ # Get the first selected torrent
+ selected = component.get('TorrentView').get_selected_torrents()
+
+ # Only use the first torrent in the list or return if None selected
+ if selected:
+ selected = selected[0]
+ else:
+ # No torrent is selected in the torrentview
+ self.clear()
+ return
+
+ session = component.get('SessionProxy')
+ session.get_torrent_status(selected, self.status_keys).addCallback(
+ self._on_get_torrent_status
+ )
+
+ def _on_get_torrent_status(self, status):
+ # Check to see if we got valid data from the core
+ if status is None:
+ return
+
+ # Update all the label widgets
+ for widget in self.tab_widgets.values():
+ txt = xml_escape(self.widget_status_as_fstr(widget, status))
+ if decode_bytes(widget.obj.get_text()) != txt:
+ if 'comment' in widget.status_keys and is_url(txt):
+ widget.obj.set_markup('<a href="%s">%s</a>' % (txt, txt))
+ else:
+ widget.obj.set_markup(txt)
+
+ def clear(self):
+ for widget in self.tab_widgets.values():
+ widget.obj.set_text('')
diff --git a/deluge/ui/gtk3/dialogs.py b/deluge/ui/gtk3/dialogs.py
new file mode 100644
index 0000000..5169ab4
--- /dev/null
+++ b/deluge/ui/gtk3/dialogs.py
@@ -0,0 +1,455 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+# pylint: disable=super-on-old-class
+
+from __future__ import unicode_literals
+
+from gi.repository import Gtk
+from twisted.internet import defer
+
+import deluge.component as component
+from deluge.common import windows_check
+
+from .common import get_deluge_icon, get_pixbuf_at_size
+
+
+class BaseDialog(Gtk.Dialog):
+ """
+ Base dialog class that should be used with all dialogs.
+ """
+
+ def __init__(self, header, text, icon, buttons, parent=None):
+ """
+ :param header: str, the header portion of the dialog
+ :param text: str, the text body of the dialog
+ :param icon: icon name from icon theme or icon filename.
+ :param buttons: tuple, of icon name and responses
+ :param parent: gtkWindow, the parent window, if None it will default to the
+ MainWindow
+ """
+ super(BaseDialog, self).__init__(
+ title=header,
+ parent=parent if parent else component.get('MainWindow').window,
+ flags=Gtk.DialogFlags.MODAL | Gtk.DialogFlags.DESTROY_WITH_PARENT,
+ buttons=buttons,
+ )
+
+ self.set_icon(get_deluge_icon())
+
+ self.connect('delete-event', self._on_delete_event)
+ self.connect('response', self._on_response)
+
+ # Setup all the formatting and such to make our dialog look pretty
+ self.set_border_width(5)
+ self.set_default_size(200, 100)
+ hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, spacing=5)
+ image = Gtk.Image()
+ if icon.endswith('.svg') or icon.endswith('.png'):
+ # Hack for Windows since it doesn't support svg
+ if icon.endswith('.svg') and windows_check():
+ icon = icon.rpartition('.svg')[0] + '16.png'
+ image.set_from_pixbuf(get_pixbuf_at_size(icon, 24))
+ else:
+ image.set_from_icon_name(icon, Gtk.IconSize.LARGE_TOOLBAR)
+ image.set_alignment(0.5, 0.0)
+ hbox.pack_start(image, False, False, 0)
+ vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, spacing=5)
+ tlabel = Gtk.Label(label=text)
+ tlabel.set_use_markup(True)
+ tlabel.set_line_wrap(True)
+ tlabel.set_alignment(0.0, 0.5)
+ vbox.pack_start(tlabel, False, False, 0)
+ hbox.pack_start(vbox, False, False, 0)
+ self.vbox.pack_start(hbox, False, False, 0)
+ self.vbox.set_spacing(5)
+ self.vbox.show_all()
+
+ def _on_delete_event(self, widget, event):
+ self.deferred.callback(Gtk.ResponseType.DELETE_EVENT)
+ self.destroy()
+
+ def _on_response(self, widget, response):
+ self.deferred.callback(response)
+ self.destroy()
+
+ def run(self):
+ """
+ Shows the dialog and returns a Deferred object. The deferred, when fired
+ will contain the response ID.
+ """
+ self.deferred = defer.Deferred()
+ self.show()
+ return self.deferred
+
+
+class YesNoDialog(BaseDialog):
+ """
+ Displays a dialog asking the user to select Yes or No to a question.
+
+ When run(), it will return either a Gtk.ResponseType.YES or a Gtk.ResponseType.NO.
+
+ """
+
+ def __init__(self, header, text, parent=None):
+ """
+ :param header: see `:class:BaseDialog`
+ :param text: see `:class:BaseDialog`
+ :param parent: see `:class:BaseDialog`
+ """
+ super(YesNoDialog, self).__init__(
+ header,
+ text,
+ 'dialog-question',
+ (_('_No'), Gtk.ResponseType.NO, _('_Yes'), Gtk.ResponseType.YES),
+ parent,
+ )
+
+
+class InformationDialog(BaseDialog):
+ """
+ Displays an information dialog.
+
+ When run(), it will return a Gtk.ResponseType.CLOSE.
+ """
+
+ def __init__(self, header, text, parent=None):
+ """
+ :param header: see `:class:BaseDialog`
+ :param text: see `:class:BaseDialog`
+ :param parent: see `:class:BaseDialog`
+ """
+ super(InformationDialog, self).__init__(
+ header,
+ text,
+ 'dialog-information',
+ (_('_Close'), Gtk.ResponseType.CLOSE),
+ parent,
+ )
+
+
+class ErrorDialog(BaseDialog):
+ """
+ Displays an error dialog with optional details text for more information.
+
+ When run(), it will return a Gtk.ResponseType.CLOSE.
+ """
+
+ def __init__(self, header, text, parent=None, details=None, traceback=False):
+ """
+ :param header: see `:class:BaseDialog`
+ :param text: see `:class:BaseDialog`
+ :param parent: see `:class:BaseDialog`
+ :param details: extra information that will be displayed in a
+ scrollable textview
+ :type details: string
+ :param traceback: show the traceback information in the details area
+ :type traceback: bool
+ """
+ super(ErrorDialog, self).__init__(
+ header, text, 'dialog-error', (_('_Close'), Gtk.ResponseType.CLOSE), parent
+ )
+
+ if traceback:
+ import traceback
+ import sys
+
+ tb = sys.exc_info()
+ tb = traceback.format_exc(tb[2])
+ if details:
+ details += '\n' + tb
+ else:
+ details = tb
+
+ if details:
+ self.set_default_size(600, 400)
+ textview = Gtk.TextView()
+ textview.set_editable(False)
+ textview.get_buffer().set_text(details)
+ sw = Gtk.ScrolledWindow()
+ sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
+ sw.set_shadow_type(Gtk.ShadowType.IN)
+ sw.add(textview)
+ label = Gtk.Label(label=_('Details:'))
+ label.set_alignment(0.0, 0.5)
+ self.vbox.pack_start(label, False, False, 0)
+ self.vbox.pack_start(sw, True, True, 0)
+ self.vbox.show_all()
+
+
+class AuthenticationDialog(BaseDialog):
+ """
+ Displays a dialog with entry fields asking for username and password.
+
+ When run(), it will return either a Gtk.ResponseType.CANCEL or a
+ Gtk.ResponseType.OK.
+ """
+
+ def __init__(self, err_msg='', username=None, parent=None):
+ """
+ :param err_msg: the error message we got back from the server
+ :type err_msg: string
+ """
+ super(AuthenticationDialog, self).__init__(
+ _('Authenticate'),
+ err_msg,
+ 'dialog-password',
+ (_('_Cancel'), Gtk.ResponseType.CANCEL, _('C_onnect'), Gtk.ResponseType.OK),
+ parent,
+ )
+
+ table = Gtk.Table(2, 2, False)
+ self.username_label = Gtk.Label()
+ self.username_label.set_markup('<b>' + _('Username:') + '</b>')
+ self.username_label.set_alignment(1.0, 0.5)
+ self.username_label.set_padding(5, 5)
+ self.username_entry = Gtk.Entry()
+ table.attach(self.username_label, 0, 1, 0, 1)
+ table.attach(self.username_entry, 1, 2, 0, 1)
+
+ self.password_label = Gtk.Label()
+ self.password_label.set_markup('<b>' + _('Password:') + '</b>')
+ self.password_label.set_alignment(1.0, 0.5)
+ self.password_label.set_padding(5, 5)
+ self.password_entry = Gtk.Entry()
+ self.password_entry.set_visibility(False)
+ self.password_entry.connect('activate', self.on_password_activate)
+ table.attach(self.password_label, 0, 1, 1, 2)
+ table.attach(self.password_entry, 1, 2, 1, 2)
+
+ self.vbox.pack_start(table, False, False, padding=5)
+ self.set_focus(self.password_entry)
+ if username:
+ self.username_entry.set_text(username)
+ self.username_entry.set_editable(False)
+ self.set_focus(self.password_entry)
+ else:
+ self.set_focus(self.username_entry)
+ self.show_all()
+
+ def get_username(self):
+ return self.username_entry.get_text()
+
+ def get_password(self):
+ return self.password_entry.get_text()
+
+ def on_password_activate(self, widget):
+ self.response(Gtk.ResponseType.OK)
+
+
+class AccountDialog(BaseDialog):
+ def __init__(
+ self,
+ username=None,
+ password=None,
+ authlevel=None,
+ levels_mapping=None,
+ parent=None,
+ ):
+ if username:
+ super(AccountDialog, self).__init__(
+ _('Edit Account'),
+ _('Edit existing account'),
+ 'dialog-information',
+ (
+ _('_Cancel'),
+ Gtk.ResponseType.CANCEL,
+ _('_Apply'),
+ Gtk.ResponseType.OK,
+ ),
+ parent,
+ )
+ else:
+ super(AccountDialog, self).__init__(
+ _('New Account'),
+ _('Create a new account'),
+ 'dialog-information',
+ (_('_Cancel'), Gtk.ResponseType.CANCEL, _('_Add'), Gtk.ResponseType.OK),
+ parent,
+ )
+
+ self.levels_mapping = levels_mapping
+
+ table = Gtk.Table(2, 3, False)
+ self.username_label = Gtk.Label()
+ self.username_label.set_markup('<b>' + _('Username:') + '</b>')
+ self.username_label.set_alignment(1.0, 0.5)
+ self.username_label.set_padding(5, 5)
+ self.username_entry = Gtk.Entry()
+ table.attach(self.username_label, 0, 1, 0, 1)
+ table.attach(self.username_entry, 1, 2, 0, 1)
+
+ self.authlevel_label = Gtk.Label()
+ self.authlevel_label.set_markup('<b>' + _('Authentication Level:') + '</b>')
+ self.authlevel_label.set_alignment(1.0, 0.5)
+ self.authlevel_label.set_padding(5, 5)
+
+ # combo_box_new_text is deprecated but no other pygtk alternative.
+ self.authlevel_combo = Gtk.ComboBoxText()
+ active_idx = None
+ for idx, level in enumerate(levels_mapping):
+ self.authlevel_combo.append_text(level)
+ if authlevel and authlevel == level:
+ active_idx = idx
+ elif not authlevel and level == 'DEFAULT':
+ active_idx = idx
+
+ if active_idx is not None:
+ self.authlevel_combo.set_active(active_idx)
+
+ table.attach(self.authlevel_label, 0, 1, 1, 2)
+ table.attach(self.authlevel_combo, 1, 2, 1, 2)
+
+ self.password_label = Gtk.Label()
+ self.password_label.set_markup('<b>' + _('Password:') + '</b>')
+ self.password_label.set_alignment(1.0, 0.5)
+ self.password_label.set_padding(5, 5)
+ self.password_entry = Gtk.Entry()
+ self.password_entry.set_visibility(False)
+ table.attach(self.password_label, 0, 1, 2, 3)
+ table.attach(self.password_entry, 1, 2, 2, 3)
+
+ self.vbox.pack_start(table, False, False, padding=5)
+ if username:
+ self.username_entry.set_text(username)
+ self.username_entry.set_editable(False)
+ else:
+ self.set_focus(self.username_entry)
+
+ if password:
+ self.password_entry.set_text(username)
+
+ self.show_all()
+
+ def get_username(self):
+ return self.username_entry.get_text()
+
+ def get_password(self):
+ return self.password_entry.get_text()
+
+ def get_authlevel(self):
+ combobox = self.authlevel_combo
+ level = combobox.get_model()[combobox.get_active()][0]
+ return level
+
+
+class OtherDialog(BaseDialog):
+ """
+ Displays a dialog with a spinner for setting a value.
+
+ Returns:
+ int or float:
+ """
+
+ def __init__(
+ self, header, text='', unit_text='', icon=None, default=0, parent=None
+ ):
+ self.value_type = type(default)
+ if self.value_type not in (int, float):
+ raise TypeError('default value needs to be an int or float')
+
+ if not icon:
+ icon = 'dialog-information'
+
+ super(OtherDialog, self).__init__(
+ header,
+ text,
+ icon,
+ (_('_Cancel'), Gtk.ResponseType.CANCEL, _('_Apply'), Gtk.ResponseType.OK),
+ parent,
+ )
+
+ hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, spacing=5)
+ alignment_spacer = Gtk.Alignment()
+ hbox.pack_start(alignment_spacer, True, True, 0)
+ alignment_spin = Gtk.Alignment(xalign=1, yalign=0.5, xscale=1, yscale=1)
+ adjustment_spin = Gtk.Adjustment(
+ value=-1, lower=-1, upper=2097151, step_increment=1, page_increment=10
+ )
+ self.spinbutton = Gtk.SpinButton(
+ adjustment=adjustment_spin, climb_rate=0, digits=0
+ )
+ self.spinbutton.set_value(default)
+ self.spinbutton.select_region(0, -1)
+ self.spinbutton.set_width_chars(6)
+ self.spinbutton.set_alignment(1)
+ self.spinbutton.set_max_length(6)
+ if self.value_type is float:
+ self.spinbutton.set_digits(1)
+ alignment_spin.add(self.spinbutton)
+ hbox.pack_start(alignment_spin, False, True, 0)
+ label_type = Gtk.Label()
+ label_type.set_text(unit_text)
+ label_type.set_alignment(0.0, 0.5)
+ hbox.pack_start(label_type, True, True, 0)
+
+ self.vbox.pack_start(hbox, False, False, padding=5)
+ self.vbox.show_all()
+
+ def _on_delete_event(self, widget, event):
+ self.deferred.callback(None)
+ self.destroy()
+
+ def _on_response(self, widget, response):
+ value = None
+ if response == Gtk.ResponseType.OK:
+ if self.value_type is int:
+ value = self.spinbutton.get_value_as_int()
+ else:
+ value = self.spinbutton.get_value()
+ self.deferred.callback(value)
+ self.destroy()
+
+
+class PasswordDialog(BaseDialog):
+ """
+ Displays a dialog with an entry field asking for a password.
+
+ When run(), it will return either a Gtk.ResponseType.CANCEL or a Gtk.ResponseType.OK.
+ """
+
+ def __init__(self, password_msg='', parent=None):
+ """
+ :param password_msg: the error message we got back from the server
+ :type password_msg: string
+ """
+ super(PasswordDialog, self).__init__(
+ header=_('Password Protected'),
+ text=password_msg,
+ icon='dialog-password',
+ buttons=(
+ _('_Cancel'),
+ Gtk.ResponseType.CANCEL,
+ _('_OK'),
+ Gtk.ResponseType.OK,
+ ),
+ parent=parent,
+ )
+
+ table = Gtk.Table(1, 2, False)
+ self.password_label = Gtk.Label()
+ self.password_label.set_markup('<b>' + _('Password:') + '</b>')
+ self.password_label.set_alignment(1.0, 0.5)
+ self.password_label.set_padding(5, 5)
+ self.password_entry = Gtk.Entry()
+ self.password_entry.set_visibility(False)
+ self.password_entry.connect('activate', self.on_password_activate)
+ table.attach(self.password_label, 0, 1, 1, 2)
+ table.attach(self.password_entry, 1, 2, 1, 2)
+
+ self.vbox.pack_start(table, False, False, padding=5)
+ self.set_focus(self.password_entry)
+
+ self.show_all()
+
+ def get_password(self):
+ return self.password_entry.get_text()
+
+ def on_password_activate(self, widget):
+ self.response(Gtk.ResponseType.OK)
diff --git a/deluge/ui/gtk3/edittrackersdialog.py b/deluge/ui/gtk3/edittrackersdialog.py
new file mode 100644
index 0000000..1dfdd2a
--- /dev/null
+++ b/deluge/ui/gtk3/edittrackersdialog.py
@@ -0,0 +1,300 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os.path
+
+from gi.repository import Gtk
+from twisted.internet import defer
+
+import deluge.component as component
+from deluge.common import is_url, resource_filename
+from deluge.configmanager import ConfigManager
+from deluge.ui.client import client
+
+from .common import get_deluge_icon
+
+log = logging.getLogger(__name__)
+
+
+def last_tier_trackers_from_liststore(trackers_liststore):
+ """Create a list of tracker from existing liststore and find last tier number.
+
+ Args:
+ tracker_liststore (Gtk.ListStore): A Gtk.ListStore with [tier (int), tracker (str)] rows.
+
+ Returns:
+ tuple(int, list): A tuple containing last tier number and list of trackers.
+
+ """
+
+ last_tier = 0
+ trackers_from_liststore = []
+ for tier, tracker in trackers_liststore:
+ trackers_from_liststore.append(tracker)
+ if tier >= last_tier:
+ last_tier = tier + 1
+
+ return last_tier, trackers_from_liststore
+
+
+def trackers_tiers_from_text(text_str=''):
+ """Create a list of trackers from text.
+
+ Any duplicate trackers are removed.
+
+ Args:
+ text_input (str): A block of text with tracker separated by newlines.
+
+ Returns:
+ list: The list of trackers.
+
+ Notes:
+ Trackers should be separated by newlines and empty line denotes start of new tier.
+
+ """
+
+ trackers = {}
+ tier = 0
+
+ lines = text_str.strip().split('\n')
+ for line in lines:
+ if not line:
+ tier += 1
+ continue
+ line = line.replace('\\', '/') # Fix any mistyped urls.
+ if is_url(line) and line not in trackers:
+ trackers[line] = tier
+
+ return trackers
+
+
+class EditTrackersDialog(object):
+ def __init__(self, torrent_id, parent=None):
+ self.torrent_id = torrent_id
+ self.builder = Gtk.Builder()
+ self.gtkui_config = ConfigManager('gtk3ui.conf')
+
+ # Main dialog
+ self.builder.add_from_file(
+ resource_filename(__package__, os.path.join('glade', 'edit_trackers.ui'))
+ )
+ # add tracker dialog
+ self.builder.add_from_file(
+ resource_filename(
+ __package__, os.path.join('glade', 'edit_trackers.add.ui')
+ )
+ )
+ # edit tracker dialog
+ self.builder.add_from_file(
+ resource_filename(
+ __package__, os.path.join('glade', 'edit_trackers.edit.ui')
+ )
+ )
+
+ self.dialog = self.builder.get_object('edit_trackers_dialog')
+ self.treeview = self.builder.get_object('tracker_treeview')
+ self.add_tracker_dialog = self.builder.get_object('add_tracker_dialog')
+ self.add_tracker_dialog.set_transient_for(self.dialog)
+ self.edit_tracker_entry = self.builder.get_object('edit_tracker_entry')
+ self.edit_tracker_entry.set_transient_for(self.dialog)
+ self.dialog.set_icon(get_deluge_icon())
+
+ self.load_edit_trackers_dialog_state()
+
+ if parent is not None:
+ self.dialog.set_transient_for(parent)
+
+ # Connect the signals
+ self.builder.connect_signals(self)
+
+ # Create a liststore for tier, url
+ self.liststore = Gtk.ListStore(int, str)
+
+ # Create the columns
+ self.treeview.append_column(
+ Gtk.TreeViewColumn(_('Tier'), Gtk.CellRendererText(), text=0)
+ )
+ self.treeview.append_column(
+ Gtk.TreeViewColumn(_('Tracker'), Gtk.CellRendererText(), text=1)
+ )
+
+ self.treeview.set_model(self.liststore)
+ self.liststore.set_sort_column_id(0, Gtk.SortType.ASCENDING)
+
+ self.dialog.connect('delete-event', self._on_delete_event)
+ self.dialog.connect('response', self._on_response)
+
+ def run(self):
+ # Make sure we have a torrent_id.. if not just return
+ if self.torrent_id is None:
+ return
+
+ # Get the trackers for this torrent
+ session = component.get('SessionProxy')
+ session.get_torrent_status(self.torrent_id, ['trackers']).addCallback(
+ self._on_get_torrent_status
+ )
+ client.force_call()
+
+ self.deferred = defer.Deferred()
+ return self.deferred
+
+ def __del__(self):
+ del self.gtkui_config
+
+ def load_edit_trackers_dialog_state(self):
+ w = self.gtkui_config['edit_trackers_dialog_width']
+ h = self.gtkui_config['edit_trackers_dialog_height']
+ if w is not None and h is not None:
+ self.dialog.resize(w, h)
+
+ def on_edit_trackers_dialog_configure_event(self, widget, event):
+ self.gtkui_config['edit_trackers_dialog_width'] = event.width
+ self.gtkui_config['edit_trackers_dialog_height'] = event.height
+
+ def _on_delete_event(self, widget, event):
+ self.deferred.callback(Gtk.ResponseType.DELETE_EVENT)
+ self.dialog.destroy()
+
+ def _on_response(self, widget, response):
+ if response == 1:
+ self.trackers = []
+
+ def each(model, path, _iter, data):
+ tracker = {}
+ tracker['tier'] = model.get_value(_iter, 0)
+ tracker['url'] = model.get_value(_iter, 1)
+ self.trackers.append(tracker)
+
+ self.liststore.foreach(each, None)
+ if self.old_trackers != self.trackers:
+ # Set the torrens trackers
+ client.core.set_torrent_trackers(self.torrent_id, self.trackers)
+ self.deferred.callback(Gtk.ResponseType.OK)
+ else:
+ self.deferred.callback(Gtk.ResponseType.CANCEL)
+ else:
+ self.deferred.callback(Gtk.ResponseType.CANCEL)
+ self.dialog.destroy()
+
+ def _on_get_torrent_status(self, status):
+ """Display trackers dialog"""
+ self.old_trackers = list(status['trackers'])
+ for tracker in self.old_trackers:
+ self.add_tracker(tracker['tier'], tracker['url'])
+ self.treeview.set_cursor((0))
+ self.dialog.show()
+
+ def add_tracker(self, tier, url):
+ """Adds a tracker to the list"""
+ self.liststore.append([tier, url])
+
+ def get_selected(self):
+ """Returns the selected tracker"""
+ return self.treeview.get_selection().get_selected()[1]
+
+ def on_button_add_clicked(self, widget):
+ log.debug('on_button_add_clicked')
+ # Show the add tracker dialog
+ self.add_tracker_dialog.show()
+ self.builder.get_object('textview_trackers').grab_focus()
+
+ def on_button_remove_clicked(self, widget):
+ log.debug('on_button_remove_clicked')
+ selected = self.get_selected()
+ if selected is not None:
+ self.liststore.remove(selected)
+
+ def on_button_edit_clicked(self, widget):
+ """edits an existing tracker"""
+ log.debug('on_button_edit_clicked')
+ selected = self.get_selected()
+ if selected:
+ tracker = self.liststore.get_value(selected, 1)
+ self.builder.get_object('entry_edit_tracker').set_text(tracker)
+ self.edit_tracker_entry.show()
+ self.edit_tracker_entry.grab_focus()
+ self.dialog.set_sensitive(False)
+
+ def on_button_edit_cancel_clicked(self, widget):
+ log.debug('on_button_edit_cancel_clicked')
+ self.dialog.set_sensitive(True)
+ self.edit_tracker_entry.hide()
+
+ def on_button_edit_ok_clicked(self, widget):
+ log.debug('on_button_edit_ok_clicked')
+ selected = self.get_selected()
+ tracker = self.builder.get_object('entry_edit_tracker').get_text()
+ self.liststore.set_value(selected, 1, tracker)
+ self.dialog.set_sensitive(True)
+ self.edit_tracker_entry.hide()
+
+ def on_button_up_clicked(self, widget):
+ log.debug('on_button_up_clicked')
+ selected = self.get_selected()
+ num_rows = self.liststore.iter_n_children(None)
+ if selected is not None and num_rows > 1:
+ tier = self.liststore.get_value(selected, 0)
+ if tier <= 0:
+ return
+ new_tier = tier - 1
+ # Now change the tier for this tracker
+ self.liststore.set_value(selected, 0, new_tier)
+
+ def on_button_down_clicked(self, widget):
+ log.debug('on_button_down_clicked')
+ selected = self.get_selected()
+ num_rows = self.liststore.iter_n_children(None)
+ if selected is not None and num_rows > 1:
+ tier = self.liststore.get_value(selected, 0)
+ new_tier = tier + 1
+ # Now change the tier for this tracker
+ self.liststore.set_value(selected, 0, new_tier)
+
+ def on_button_add_ok_clicked(self, widget):
+ log.debug('on_button_add_ok_clicked')
+
+ # Create a list of trackers from the textview widget
+ textview_buf = self.builder.get_object('textview_trackers').get_buffer()
+ trackers_text = textview_buf.get_text(
+ *textview_buf.get_bounds(), include_hidden_chars=False
+ )
+
+ for tracker in trackers_tiers_from_text(trackers_text):
+ # Figure out what tier number to use.. it's going to be the highest+1
+ # Also check for duplicates
+ # Check if there are any entries
+ duplicate = False
+ highest_tier = -1
+ for row in self.liststore:
+ tier = row[0]
+ if tier > highest_tier:
+ highest_tier = tier
+ if tracker == row[1]:
+ duplicate = True
+ break
+
+ # If not a duplicate, then add it to the list
+ if not duplicate:
+ # Add the tracker to the list
+ self.add_tracker(highest_tier + 1, tracker)
+
+ # Clear the entry widget and hide the dialog
+ textview_buf.set_text('')
+ self.add_tracker_dialog.hide()
+
+ def on_button_add_cancel_clicked(self, widget):
+ log.debug('on_button_add_cancel_clicked')
+ # Clear the entry widget and hide the dialog
+ b = Gtk.TextBuffer()
+ self.builder.get_object('textview_trackers').set_buffer(b)
+ self.add_tracker_dialog.hide()
diff --git a/deluge/ui/gtk3/files_tab.py b/deluge/ui/gtk3/files_tab.py
new file mode 100644
index 0000000..b3bd5b5
--- /dev/null
+++ b/deluge/ui/gtk3/files_tab.py
@@ -0,0 +1,860 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import division, unicode_literals
+
+import json
+import logging
+import os.path
+
+from gi.repository import Gio, Gtk
+from gi.repository.Gdk import DragAction, ModifierType, keyval_name
+from gi.repository.GObject import TYPE_UINT64
+
+import deluge.component as component
+from deluge.common import open_file, show_file
+from deluge.ui.client import client
+from deluge.ui.common import FILE_PRIORITY
+
+from .common import (
+ listview_replace_treestore,
+ load_pickled_state_file,
+ reparent_iter,
+ save_pickled_state_file,
+)
+from .torrentdetails import Tab
+from .torrentview_data_funcs import cell_data_size
+
+log = logging.getLogger(__name__)
+
+CELL_PRIORITY_ICONS = {
+ FILE_PRIORITY['Skip']: 'action-unavailable-symbolic',
+ FILE_PRIORITY['Low']: 'go-down-symbolic',
+ FILE_PRIORITY['Normal']: 'go-next-symbolic',
+ FILE_PRIORITY['High']: 'go-up-symbolic',
+}
+
+G_ICON_DIRECTORY = Gio.content_type_get_icon('inode/directory')
+
+
+def cell_priority(column, cell, model, row, data):
+ if model.get_value(row, 5) == -1:
+ # This is a folder, so lets just set it blank for now
+ cell.set_property('text', '')
+ return
+ priority = model.get_value(row, data)
+ cell.set_property('text', _(FILE_PRIORITY[priority]))
+
+
+def cell_priority_icon(column, cell, model, row, data):
+ if model.get_value(row, 5) == -1:
+ # This is a folder, so lets just set it blank for now
+ cell.set_property('icon-name', None)
+ return
+ priority = model.get_value(row, data)
+ cell.set_property('icon-name', CELL_PRIORITY_ICONS[priority])
+
+
+def cell_filename(column, cell, model, row, data):
+ """Only show the tail portion of the file path"""
+ filepath = model.get_value(row, data)
+ cell.set_property('text', os.path.split(filepath)[1])
+
+
+def cell_progress(column, cell, model, row, data):
+ text = model.get_value(row, data[0])
+ value = model.get_value(row, data[1])
+ cell.set_property('visible', True)
+ cell.set_property('text', text)
+ cell.set_property('value', value)
+
+
+class FilesTab(Tab):
+ def __init__(self):
+ super(FilesTab, self).__init__('Files', 'files_tab', 'files_tab_label')
+
+ self.listview = self.main_builder.get_object('files_listview')
+ # filename, size, progress string, progress value, priority, file index, icon id
+ self.treestore = Gtk.TreeStore(str, TYPE_UINT64, str, float, int, int, Gio.Icon)
+ self.treestore.set_sort_column_id(0, Gtk.SortType.ASCENDING)
+
+ # We need to store the row that's being edited to prevent updating it until
+ # it's been done editing
+ self._editing_index = None
+
+ # Filename column
+ self.filename_column_name = _('Filename')
+ column = Gtk.TreeViewColumn(self.filename_column_name)
+ render = Gtk.CellRendererPixbuf()
+ column.pack_start(render, False)
+ column.add_attribute(render, 'gicon', 6)
+ render = Gtk.CellRendererText()
+ render.set_property('editable', True)
+ render.connect('edited', self._on_filename_edited)
+ render.connect('editing-started', self._on_filename_editing_start)
+ render.connect('editing-canceled', self._on_filename_editing_canceled)
+ column.pack_start(render, True)
+ column.add_attribute(render, 'text', 0)
+ column.set_sort_column_id(0)
+ column.set_clickable(True)
+ column.set_resizable(True)
+ column.set_expand(False)
+ column.set_min_width(200)
+ column.set_reorderable(True)
+ self.listview.append_column(column)
+
+ # Size column
+ column = Gtk.TreeViewColumn(_('Size'))
+ render = Gtk.CellRendererText()
+ column.pack_start(render, False)
+ column.set_cell_data_func(render, cell_data_size, 1)
+ column.set_sort_column_id(1)
+ column.set_clickable(True)
+ column.set_resizable(True)
+ column.set_expand(False)
+ column.set_min_width(50)
+ column.set_reorderable(True)
+ self.listview.append_column(column)
+
+ # Progress column
+ column = Gtk.TreeViewColumn(_('Progress'))
+ render = Gtk.CellRendererProgress()
+ render.set_padding(0, 1)
+ column.pack_start(render, True)
+ column.set_cell_data_func(render, cell_progress, (2, 3))
+ column.set_sort_column_id(3)
+ column.set_clickable(True)
+ column.set_resizable(True)
+ column.set_expand(False)
+ column.set_min_width(100)
+ column.set_reorderable(True)
+ self.listview.append_column(column)
+
+ # Priority column
+ column = Gtk.TreeViewColumn(_('Priority'))
+ render = Gtk.CellRendererPixbuf()
+ column.pack_start(render, False)
+ column.set_cell_data_func(render, cell_priority_icon, 4)
+ render = Gtk.CellRendererText()
+ column.pack_start(render, False)
+ column.set_cell_data_func(render, cell_priority, 4)
+ column.set_sort_column_id(4)
+ column.set_clickable(True)
+ column.set_resizable(True)
+ column.set_expand(False)
+ column.set_min_width(100)
+ # Bugfix: Last column needs max_width set to stop scrollbar appearing
+ column.set_max_width(200)
+ column.set_reorderable(True)
+ self.listview.append_column(column)
+
+ self.listview.set_model(self.treestore)
+
+ self.listview.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
+
+ self.file_menu = self.main_builder.get_object('menu_file_tab')
+ self.file_menu_priority_items = [
+ self.main_builder.get_object('menuitem_skip'),
+ self.main_builder.get_object('menuitem_low'),
+ self.main_builder.get_object('menuitem_normal'),
+ self.main_builder.get_object('menuitem_high'),
+ self.main_builder.get_object('menuitem_priority_sep'),
+ ]
+
+ self.localhost_widgets = [
+ self.main_builder.get_object('menuitem_open_file'),
+ self.main_builder.get_object('menuitem_show_file'),
+ self.main_builder.get_object('menuitem3'),
+ ]
+
+ self.listview.connect('row-activated', self._on_row_activated)
+ self.listview.connect('key-press-event', self._on_key_press_event)
+ self.listview.connect('button-press-event', self._on_button_press_event)
+
+ self.listview.enable_model_drag_source(
+ ModifierType.BUTTON1_MASK,
+ [('text/plain', 0, 0)],
+ DragAction.DEFAULT | DragAction.MOVE,
+ )
+ self.listview.enable_model_drag_dest([('text/plain', 0, 0)], DragAction.DEFAULT)
+
+ self.listview.connect('drag_data_get', self._on_drag_data_get_data)
+ self.listview.connect('drag_data_received', self._on_drag_data_received_data)
+
+ component.get('MainWindow').connect_signals(self)
+
+ # Connect to various events from the daemon
+ client.register_event_handler(
+ 'TorrentFileRenamedEvent', self._on_torrentfilerenamed_event
+ )
+ client.register_event_handler(
+ 'TorrentFolderRenamedEvent', self._on_torrentfolderrenamed_event
+ )
+ client.register_event_handler(
+ 'TorrentRemovedEvent', self._on_torrentremoved_event
+ )
+
+ # Attempt to load state
+ self.load_state()
+
+ # torrent_id: (filepath, size)
+ self.files_list = {}
+
+ self.torrent_id = None
+
+ def start(self):
+ attr = 'hide' if not client.is_localhost() else 'show'
+ for widget in self.localhost_widgets:
+ getattr(widget, attr)()
+
+ def save_state(self):
+ # Get the current sort order of the view
+ column_id, sort_order = self.treestore.get_sort_column_id()
+
+ # Setup state dict
+ state = {
+ 'columns': {},
+ 'sort_id': int(column_id) if column_id >= 0 else None,
+ 'sort_order': int(sort_order) if sort_order >= 0 else None,
+ }
+
+ for index, column in enumerate(self.listview.get_columns()):
+ state['columns'][column.get_title()] = {
+ 'position': index,
+ 'width': column.get_width(),
+ }
+
+ save_pickled_state_file('files_tab.state', state)
+
+ def load_state(self):
+ state = load_pickled_state_file('files_tab.state')
+
+ if not state:
+ return
+
+ if state['sort_id'] is not None and state['sort_order'] is not None:
+ self.treestore.set_sort_column_id(state['sort_id'], state['sort_order'])
+
+ for (index, column) in enumerate(self.listview.get_columns()):
+ cname = column.get_title()
+ if cname in state['columns']:
+ cstate = state['columns'][cname]
+ column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
+ column.set_fixed_width(cstate['width'] if cstate['width'] > 0 else 10)
+ if state['sort_id'] == index and state['sort_order'] is not None:
+ column.set_sort_indicator(True)
+ column.set_sort_order(state['sort_order'])
+ if cstate['position'] != index:
+ # Column is in wrong position
+ if cstate['position'] == 0:
+ self.listview.move_column_after(column, None)
+ elif (
+ self.listview.get_columns()[cstate['position'] - 1].get_title()
+ != cname
+ ):
+ self.listview.move_column_after(
+ column, self.listview.get_columns()[cstate['position'] - 1]
+ )
+
+ def update(self):
+ # Get the first selected torrent
+ torrent_id = component.get('TorrentView').get_selected_torrents()
+
+ # Only use the first torrent in the list or return if None selected
+ if len(torrent_id) != 0:
+ torrent_id = torrent_id[0]
+ else:
+ # No torrent is selected in the torrentview
+ self.clear()
+ return
+
+ status_keys = ['file_progress', 'file_priorities']
+ if torrent_id != self.torrent_id:
+ # We only want to do this if the torrent_id has changed
+ self.treestore.clear()
+ self.torrent_id = torrent_id
+ status_keys += ['storage_mode', 'is_seed']
+
+ if self.torrent_id in self.files_list:
+ # We already have the files list stored, so just update the view
+ self.update_files()
+
+ if (
+ self.torrent_id not in self.files_list
+ or not self.files_list[self.torrent_id]
+ ):
+ # We need to get the files list
+ log.debug('Getting file list from core..')
+ status_keys += ['files']
+
+ component.get('SessionProxy').get_torrent_status(
+ self.torrent_id, status_keys
+ ).addCallback(self._on_get_torrent_status, self.torrent_id)
+
+ def clear(self):
+ self.treestore.clear()
+ self.torrent_id = None
+
+ def _on_row_activated(self, tree, path, view_column):
+ self.on_menuitem_open_file_activate(None)
+
+ def get_file_path(self, row, path=''):
+ if not row:
+ return path
+
+ path = self.treestore.get_value(row, 0) + path
+ return self.get_file_path(self.treestore.iter_parent(row), path)
+
+ def _on_open_file(self, status):
+ paths = self.listview.get_selection().get_selected_rows()[1]
+ selected = []
+ for path in paths:
+ selected.append(self.treestore.get_iter(path))
+
+ for select in selected:
+ path = self.get_file_path(select).split('/')
+ filepath = os.path.join(status['download_location'], *path)
+ log.debug('Open file: %s', filepath)
+ timestamp = component.get('MainWindow').get_timestamp()
+ open_file(filepath, timestamp=timestamp)
+
+ def _on_show_file(self, status):
+ paths = self.listview.get_selection().get_selected_rows()[1]
+ selected = []
+ for path in paths:
+ selected.append(self.treestore.get_iter(path))
+
+ for select in selected:
+ path = self.get_file_path(select).split('/')
+ filepath = os.path.join(status['download_location'], *path)
+ log.debug('Show file: %s', filepath)
+ timestamp = component.get('MainWindow').get_timestamp()
+ show_file(filepath, timestamp=timestamp)
+
+ # The following 3 methods create the folder/file view in the treeview
+ def prepare_file_store(self, torrent_files):
+ split_files = {}
+ for index, torrent_file in enumerate(torrent_files):
+ self.prepare_file(torrent_file, torrent_file['path'], index, split_files)
+ self.add_files(None, split_files)
+
+ def prepare_file(self, torrent_file, file_name, file_num, files_storage):
+ first_slash_index = file_name.find('/')
+ if first_slash_index == -1:
+ files_storage[file_name] = (file_num, torrent_file)
+ else:
+ file_name_chunk = file_name[: first_slash_index + 1]
+ if file_name_chunk not in files_storage:
+ files_storage[file_name_chunk] = {}
+ self.prepare_file(
+ torrent_file,
+ file_name[first_slash_index + 1 :],
+ file_num,
+ files_storage[file_name_chunk],
+ )
+
+ def add_files(self, parent_iter, split_files):
+ chunk_size_total = 0
+ for key, value in split_files.items():
+ if key.endswith('/'):
+ chunk_iter = self.treestore.append(
+ parent_iter, [key, 0, '', 0, 0, -1, G_ICON_DIRECTORY]
+ )
+ chunk_size = self.add_files(chunk_iter, value)
+ self.treestore.set(chunk_iter, 1, chunk_size)
+ chunk_size_total += chunk_size
+ else:
+ mime_type, uncertain = Gio.content_type_guess(key, None)
+ if not uncertain and mime_type:
+ mime_icon = Gio.content_type_get_symbolic_icon(mime_type)
+ else:
+ mime_icon = Gio.content_type_get_symbolic_icon('text/plain')
+ self.treestore.append(
+ parent_iter, [key, value[1]['size'], '', 0, 0, value[0], mime_icon]
+ )
+ chunk_size_total += value[1]['size']
+ return chunk_size_total
+
+ def update_files(self):
+ with listview_replace_treestore(self.listview):
+ self.prepare_file_store(self.files_list[self.torrent_id])
+ root = Gtk.TreePath.new_first()
+ self.listview.expand_row(root, False)
+
+ def get_selected_files(self):
+ """Returns a list of file indexes that are selected."""
+
+ def get_iter_children(itr, selected):
+ i = self.treestore.iter_children(itr)
+ while i:
+ selected.append(self.treestore[i][5])
+ if self.treestore.iter_has_child(i):
+ get_iter_children(i, selected)
+ i = self.treestore.iter_next(i)
+
+ selected = []
+ paths = self.listview.get_selection().get_selected_rows()[1]
+ for path in paths:
+ i = self.treestore.get_iter(path)
+ selected.append(self.treestore[i][5])
+ if self.treestore.iter_has_child(i):
+ get_iter_children(i, selected)
+
+ return selected
+
+ def get_files_from_tree(self, rows, files_list, indent):
+ if not rows:
+ return None
+
+ for row in rows:
+ if row[5] > -1:
+ files_list.append((row[5], row))
+ self.get_files_from_tree(row.iterchildren(), files_list, indent + 1)
+ return None
+
+ def update_folder_percentages(self):
+ """Go through the tree and update the folder complete percentages."""
+ root = self.treestore.get_iter_first()
+ if root is None or self.treestore[root][5] != -1:
+ return
+
+ def get_completed_bytes(row):
+ completed_bytes = 0
+ parent = self.treestore.iter_parent(row)
+ while row:
+ if self.treestore.iter_children(row):
+ completed_bytes += get_completed_bytes(
+ self.treestore.iter_children(row)
+ )
+ else:
+ completed_bytes += (
+ self.treestore[row][1] * self.treestore[row][3] / 100
+ )
+
+ row = self.treestore.iter_next(row)
+
+ try:
+ value = completed_bytes / self.treestore[parent][1] * 100
+ except ZeroDivisionError:
+ # Catch the unusal error found when moving folders around
+ value = 0
+ self.treestore[parent][3] = value
+ self.treestore[parent][2] = '%i%%' % value
+ return completed_bytes
+
+ get_completed_bytes(self.treestore.iter_children(root))
+
+ def _on_get_torrent_status(self, status, torrent_id):
+ # Check stored torrent id matches the callback id
+ if self.torrent_id != torrent_id:
+ return
+
+ if 'is_seed' in status:
+ self.__is_seed = status['is_seed']
+
+ if 'files' in status:
+ self.files_list[self.torrent_id] = status['files']
+ self.update_files()
+
+ # (index, iter)
+ files_list = []
+ self.get_files_from_tree(self.treestore, files_list, 0)
+ files_list.sort()
+ for index, row in files_list:
+ # Do not update a row that is being edited
+ if self._editing_index == row[5]:
+ continue
+
+ try:
+ progress_string = '%i%%' % (status['file_progress'][index] * 100)
+ except IndexError:
+ continue
+ if row[2] != progress_string:
+ row[2] = progress_string
+ progress_value = status['file_progress'][index] * 100
+ if row[3] != progress_value:
+ row[3] = progress_value
+ file_priority = status['file_priorities'][index]
+ if row[4] != file_priority:
+ row[4] = file_priority
+ if self._editing_index != -1:
+ # Only update if no folder is being edited
+ self.update_folder_percentages()
+
+ def _on_button_press_event(self, widget, event):
+ """This is a callback for showing the right-click context menu."""
+ log.debug('on_button_press_event')
+ # We only care about right-clicks
+ if event.button == 3:
+ x, y = event.get_coords()
+ cursor_path = self.listview.get_path_at_pos(int(x), int(y))
+ if not cursor_path:
+ return
+
+ paths = self.listview.get_selection().get_selected_rows()[1]
+ if cursor_path[0] not in paths:
+ row = self.treestore.get_iter(cursor_path[0])
+ self.listview.get_selection().unselect_all()
+ self.listview.get_selection().select_iter(row)
+
+ for widget in self.file_menu_priority_items:
+ widget.set_sensitive(not self.__is_seed)
+
+ self.file_menu.popup(None, None, None, None, event.button, event.time)
+ return True
+
+ def _on_key_press_event(self, widget, event):
+ keyname = keyval_name(event.keyval)
+ if keyname is not None:
+ func = getattr(self, 'keypress_' + keyname.lower(), None)
+ selected_rows = self.listview.get_selection().get_selected_rows()[1]
+ if func and selected_rows:
+ return func(event)
+
+ def keypress_menu(self, event):
+ self.file_menu.popup(None, None, None, None, 3, event.time)
+ return True
+
+ def keypress_f2(self, event):
+ path, col = self.listview.get_cursor()
+ for column in self.listview.get_columns():
+ if column.get_title() == self.filename_column_name:
+ self.listview.set_cursor(path, column, True)
+ return True
+
+ def on_menuitem_open_file_activate(self, menuitem):
+ if client.is_localhost:
+ component.get('SessionProxy').get_torrent_status(
+ self.torrent_id, ['download_location']
+ ).addCallback(self._on_open_file)
+
+ def on_menuitem_show_file_activate(self, menuitem):
+ if client.is_localhost:
+ component.get('SessionProxy').get_torrent_status(
+ self.torrent_id, ['download_location']
+ ).addCallback(self._on_show_file)
+
+ def _set_file_priorities_on_user_change(self, selected, priority):
+ """Sets the file priorities in the core. It will change the selected with the 'priority'"""
+ file_priorities = []
+
+ def set_file_priority(model, path, _iter, data):
+ index = model.get_value(_iter, 5)
+ if index in selected and index != -1:
+ file_priorities.append((index, priority))
+ elif index != -1:
+ file_priorities.append((index, model.get_value(_iter, 4)))
+
+ self.treestore.foreach(set_file_priority, None)
+ file_priorities.sort()
+ priorities = [p[1] for p in file_priorities]
+ log.debug('priorities: %s', priorities)
+ client.core.set_torrent_options(
+ [self.torrent_id], {'file_priorities': priorities}
+ )
+
+ def on_menuitem_skip_activate(self, menuitem):
+ self._set_file_priorities_on_user_change(
+ self.get_selected_files(), FILE_PRIORITY['Skip']
+ )
+
+ def on_menuitem_low_activate(self, menuitem):
+ self._set_file_priorities_on_user_change(
+ self.get_selected_files(), FILE_PRIORITY['Low']
+ )
+
+ def on_menuitem_normal_activate(self, menuitem):
+ self._set_file_priorities_on_user_change(
+ self.get_selected_files(), FILE_PRIORITY['Normal']
+ )
+
+ def on_menuitem_high_activate(self, menuitem):
+ self._set_file_priorities_on_user_change(
+ self.get_selected_files(), FILE_PRIORITY['High']
+ )
+
+ def on_menuitem_expand_all_activate(self, menuitem):
+ self.listview.expand_all()
+
+ def _on_filename_edited(self, renderer, path, new_text):
+ index = self.treestore[path][5]
+ log.debug('new_text: %s', new_text)
+
+ # Don't do anything if the text hasn't changed
+ if new_text == self.treestore[path][0]:
+ self._editing_index = None
+ return
+
+ if index > -1:
+ # We are renaming a file
+ itr = self.treestore.get_iter(path)
+ # Recurse through the treestore to get the actual path of the file
+
+ def get_filepath(i):
+ ip = self.treestore.iter_parent(i)
+ fp = ''
+ while ip:
+ fp = self.treestore[ip][0] + fp
+ ip = self.treestore.iter_parent(ip)
+ return fp
+
+ # Only recurse if file is in a folder..
+ if self.treestore.iter_parent(itr):
+ filepath = get_filepath(itr) + new_text
+ else:
+ filepath = new_text
+
+ log.debug('filepath: %s', filepath)
+
+ client.core.rename_files(self.torrent_id, [(index, filepath)])
+ else:
+ # We are renaming a folder
+ folder = self.treestore[path][0]
+
+ parent_path = ''
+ itr = self.treestore.iter_parent(self.treestore.get_iter(path))
+ while itr:
+ parent_path = self.treestore[itr][0] + parent_path
+ itr = self.treestore.iter_parent(itr)
+
+ client.core.rename_folder(
+ self.torrent_id, parent_path + folder, parent_path + new_text
+ )
+
+ self._editing_index = None
+
+ def _on_filename_editing_start(self, renderer, editable, path):
+ self._editing_index = self.treestore[path][5]
+
+ def _on_filename_editing_canceled(self, renderer):
+ self._editing_index = None
+
+ def _on_torrentfilerenamed_event(self, torrent_id, index, name):
+ log.debug('index: %s name: %s', index, name)
+
+ if torrent_id not in self.files_list:
+ return
+
+ old_name = self.files_list[torrent_id][index]['path']
+ self.files_list[torrent_id][index]['path'] = name
+
+ # We need to update the filename displayed if we're currently viewing
+ # this torrents files.
+ if torrent_id != self.torrent_id:
+ return
+
+ old_name_parent = old_name.split('/')[:-1]
+ parent_path = name.split('/')[:-1]
+
+ if old_name_parent != parent_path:
+ if parent_path:
+ for i, p in enumerate(parent_path):
+ p_itr = self.get_iter_at_path('/'.join(parent_path[: i + 1]) + '/')
+ if not p_itr:
+ p_itr = self.get_iter_at_path('/'.join(parent_path[:i]) + '/')
+ p_itr = self.treestore.append(
+ p_itr,
+ [parent_path[i] + '/', 0, '', 0, 0, -1, G_ICON_DIRECTORY],
+ )
+ p_itr = self.get_iter_at_path('/'.join(parent_path) + '/')
+ old_name_itr = self.get_iter_at_path(old_name)
+ self.treestore.append(
+ p_itr,
+ self.treestore.get(
+ old_name_itr, *range(self.treestore.get_n_columns())
+ ),
+ )
+ self.treestore.remove(old_name_itr)
+
+ # Remove old parent path
+ p_itr = self.get_iter_at_path('/'.join(old_name_parent) + '/')
+ self.remove_childless_folders(p_itr)
+ else:
+ new_folders = name.split('/')[:-1]
+ parent_iter = None
+ for f in new_folders:
+ parent_iter = self.treestore.append(
+ parent_iter, [f + '/', 0, '', 0, 0, -1, G_ICON_DIRECTORY]
+ )
+ child = self.get_iter_at_path(old_name)
+ self.treestore.append(
+ parent_iter,
+ self.treestore.get(child, *range(self.treestore.get_n_columns())),
+ )
+ self.treestore.remove(child)
+
+ else:
+ # This is just changing a filename without any folder changes
+ def set_file_name(model, path, itr, user_data):
+ if model[itr][5] == index:
+ model[itr][0] = os.path.split(name)[-1]
+ return True
+
+ self.treestore.foreach(set_file_name, None)
+
+ def get_iter_at_path(self, filepath):
+ """Returns the gtkTreeIter for filepath."""
+ log.debug('get_iter_at_path: %s', filepath)
+ is_dir = False
+ if filepath[-1] == '/':
+ is_dir = True
+
+ filepath = filepath.split('/')
+ if '' in filepath:
+ filepath.remove('')
+
+ path_iter = None
+ itr = self.treestore.iter_children(None)
+ level = 0
+ while itr:
+ ipath = self.treestore[itr][0]
+ if (level + 1) != len(filepath) and ipath == filepath[level] + '/':
+ # We're not at the last index, but we do have a match
+ itr = self.treestore.iter_children(itr)
+ level += 1
+ continue
+ elif (level + 1) == len(filepath) and ipath == (
+ filepath[level] + '/' if is_dir else filepath[level]
+ ):
+ # This is the iter we've been searching for
+ path_iter = itr
+ break
+ else:
+ itr = self.treestore.iter_next(itr)
+ continue
+ return path_iter
+
+ def remove_childless_folders(self, itr):
+ """Goes up the tree removing childless itrs starting at itr."""
+ while not self.treestore.iter_children(itr):
+ parent = self.treestore.iter_parent(itr)
+ self.treestore.remove(itr)
+ itr = parent
+
+ def _on_torrentfolderrenamed_event(self, torrent_id, old_folder, new_folder):
+ log.debug('on_torrent_folder_renamed_signal')
+ log.debug('old_folder: %s new_folder: %s', old_folder, new_folder)
+
+ if torrent_id not in self.files_list:
+ return
+
+ if old_folder[-1] != '/':
+ old_folder += '/'
+
+ if len(new_folder) > 0 and new_folder[-1] != '/':
+ new_folder += '/'
+
+ for fd in self.files_list[torrent_id]:
+ if fd['path'].startswith(old_folder):
+ fd['path'] = fd['path'].replace(old_folder, new_folder, 1)
+
+ if torrent_id == self.torrent_id:
+
+ old_split = old_folder.split('/')
+ try:
+ old_split.remove('')
+ except ValueError:
+ pass
+
+ new_split = new_folder.split('/')
+ try:
+ new_split.remove('')
+ except ValueError:
+ pass
+
+ old_folder_iter = self.get_iter_at_path(old_folder)
+ old_folder_iter_parent = self.treestore.iter_parent(old_folder_iter)
+
+ new_folder_iter = self.get_iter_at_path(new_folder) if new_folder else None
+
+ if len(new_split) == len(old_split):
+ # These are at the same tree depth, so it's a simple rename
+ self.treestore[old_folder_iter][0] = new_split[-1] + '/'
+ return
+ if new_folder_iter:
+ # This means that a folder by this name already exists
+ reparent_iter(
+ self.treestore,
+ self.treestore.iter_children(old_folder_iter),
+ new_folder_iter,
+ )
+ else:
+ parent = old_folder_iter_parent
+ if new_split:
+ for ns in new_split[:-1]:
+ parent = self.treestore.append(
+ parent, [ns + '/', 0, '', 0, 0, -1, G_ICON_DIRECTORY]
+ )
+
+ self.treestore[old_folder_iter][0] = new_split[-1] + '/'
+ reparent_iter(self.treestore, old_folder_iter, parent)
+ else:
+ child_itr = self.treestore.iter_children(old_folder_iter)
+ reparent_iter(
+ self.treestore,
+ child_itr,
+ old_folder_iter_parent,
+ move_siblings=True,
+ )
+
+ # We need to check if the old_folder_iter no longer has children
+ # and if so, we delete it
+ self.remove_childless_folders(old_folder_iter)
+
+ def _on_torrentremoved_event(self, torrent_id):
+ if torrent_id in self.files_list:
+ del self.files_list[torrent_id]
+
+ def _on_drag_data_get_data(self, treeview, context, selection, target_id, etime):
+ paths = self.listview.get_selection().get_selected_rows()[1]
+ selection.set_text(json.dumps([str(path) for path in paths]), -1)
+
+ def _on_drag_data_received_data(
+ self, treeview, context, x, y, selection, info, etime
+ ):
+ try:
+ selected = json.loads(selection.get_data())
+ except TypeError:
+ log.debug('Invalid selection data: %s', selection.get_data())
+ return
+ log.debug('selection.data: %s', selected)
+ drop_info = treeview.get_dest_row_at_pos(x, y)
+ model = treeview.get_model()
+ if drop_info:
+ itr = model.get_iter(drop_info[0])
+ parent_iter = model.iter_parent(itr)
+ parent_path = ''
+ if model[itr][5] == -1:
+ parent_path += model[itr][0]
+
+ while parent_iter:
+ parent_path = model[parent_iter][0] + parent_path
+ parent_iter = model.iter_parent(parent_iter)
+
+ if model[selected[0]][5] == -1:
+ log.debug('parent_path: %s', parent_path)
+ log.debug('rename_to: %s', parent_path + model[selected[0]][0])
+ # Get the full path of the folder we want to rename
+ pp = ''
+ itr = self.treestore.iter_parent(self.treestore.get_iter(selected[0]))
+ while itr:
+ pp = self.treestore[itr][0] + pp
+ itr = self.treestore.iter_parent(itr)
+ client.core.rename_folder(
+ self.torrent_id,
+ pp + model[selected[0]][0],
+ parent_path + model[selected[0]][0],
+ )
+ else:
+ # [(index, filepath), ...]
+ to_rename = []
+ for s in selected:
+ to_rename.append((model[s][5], parent_path + model[s][0]))
+ log.debug('to_rename: %s', to_rename)
+ client.core.rename_files(self.torrent_id, to_rename)
diff --git a/deluge/ui/gtk3/filtertreeview.py b/deluge/ui/gtk3/filtertreeview.py
new file mode 100644
index 0000000..bd781e0
--- /dev/null
+++ b/deluge/ui/gtk3/filtertreeview.py
@@ -0,0 +1,378 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+# 2008 Andrew Resch <andrewresch@gmail.com>
+# 2014 Calum Lind <calumlind@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os
+import warnings
+
+from gi.repository import Gtk
+from gi.repository.GdkPixbuf import Pixbuf
+from gi.repository.Pango import EllipsizeMode
+
+import deluge.component as component
+from deluge.common import TORRENT_STATE, decode_bytes, resource_filename
+from deluge.configmanager import ConfigManager
+from deluge.ui.client import client
+
+from .common import get_pixbuf, get_pixbuf_at_size
+
+log = logging.getLogger(__name__)
+
+STATE_PIX = {
+ 'All': 'all',
+ 'Downloading': 'downloading',
+ 'Seeding': 'seeding',
+ 'Paused': 'inactive',
+ 'Checking': 'checking',
+ 'Queued': 'queued',
+ 'Error': 'alert',
+ 'Active': 'active',
+ 'Allocating': 'checking',
+ 'Moving': 'checking',
+}
+
+TRACKER_PIX = {'All': 'tracker_all', 'Error': 'tracker_warning'}
+
+FILTER_COLUMN = 5
+
+
+class FilterTreeView(component.Component):
+ def __init__(self):
+ component.Component.__init__(self, 'FilterTreeView', interval=2)
+ self.config = ConfigManager('gtk3ui.conf')
+
+ self.tracker_icons = component.get('TrackerIcons')
+
+ self.sidebar = component.get('SideBar')
+ self.treeview = Gtk.TreeView()
+ self.sidebar.add_tab(self.treeview, 'filters', 'Filters')
+
+ # set filter to all when hidden:
+ self.sidebar.notebook.connect('hide', self._on_hide)
+
+ # Create the treestore
+ # cat, value, label, count, pixmap, visible
+ self.treestore = Gtk.TreeStore(str, str, str, int, Pixbuf, bool)
+
+ # Create the column and cells
+ column = Gtk.TreeViewColumn('Filters')
+ column.set_sizing(Gtk.TreeViewColumnSizing.AUTOSIZE)
+ # icon cell
+ self.cell_pix = Gtk.CellRendererPixbuf()
+ column.pack_start(self.cell_pix, expand=False)
+ column.add_attribute(self.cell_pix, 'pixbuf', 4)
+ # label cell
+ cell_label = Gtk.CellRendererText()
+ cell_label.set_property('ellipsize', EllipsizeMode.END)
+ column.pack_start(cell_label, expand=True)
+ column.set_cell_data_func(cell_label, self.render_cell_data, None)
+ # count cell
+ self.cell_count = Gtk.CellRendererText()
+ self.cell_count.set_property('xalign', 1.0)
+ self.cell_count.set_padding(3, 0)
+ column.pack_start(self.cell_count, expand=False)
+
+ self.treeview.append_column(column)
+
+ # Style
+ self.treeview.set_show_expanders(True)
+ self.treeview.set_headers_visible(False)
+ self.treeview.set_level_indentation(-21)
+ # Force theme to use expander-size so we don't cut out entries due to indentation hack.
+ Gtk.rc_parse_string(
+ """style "treeview-style" {GtkTreeView::expander-size = 7}
+ class "GtkTreeView" style "treeview-style" """
+ )
+
+ self.treeview.set_model(self.treestore)
+ self.treeview.get_selection().connect('changed', self.on_selection_changed)
+ self.create_model_filter()
+
+ self.treeview.connect('button-press-event', self.on_button_press_event)
+
+ # colors using current theme.
+ style_ctx = component.get('MainWindow').window.get_style_context()
+ self.colour_background = style_ctx.get_background_color(Gtk.StateFlags.NORMAL)
+ self.colour_foreground = style_ctx.get_color(Gtk.StateFlags.NORMAL)
+
+ # filtertree menu
+ builder = Gtk.Builder()
+ builder.add_from_file(
+ resource_filename(__package__, os.path.join('glade', 'filtertree_menu.ui'))
+ )
+ self.menu = builder.get_object('filtertree_menu')
+ builder.connect_signals(self)
+
+ self.default_menu_items = self.menu.get_children()
+
+ # add Cat nodes:
+ self.cat_nodes = {}
+ self.filters = {}
+
+ def start(self):
+ self.cat_nodes = {}
+ self.filters = {}
+ # initial order of state filter:
+ self.cat_nodes['state'] = self.treestore.append(
+ None, ['cat', 'state', _('States'), 0, None, False]
+ )
+ for state in ['All', 'Active'] + TORRENT_STATE:
+ self.update_row('state', state, 0, _(state))
+
+ self.cat_nodes['tracker_host'] = self.treestore.append(
+ None, ['cat', 'tracker_host', _('Trackers'), 0, None, False]
+ )
+ self.update_row('tracker_host', 'All', 0, _('All'))
+ self.update_row('tracker_host', 'Error', 0, _('Error'))
+ self.update_row('tracker_host', '', 0, _('None'))
+
+ self.cat_nodes['owner'] = self.treestore.append(
+ None, ['cat', 'owner', _('Owner'), 0, None, False]
+ )
+ self.update_row('owner', 'localclient', 0, _('Admin'))
+ self.update_row('owner', '', 0, _('None'))
+
+ # We set to this expand the rows on start-up
+ self.expand_rows = True
+
+ self.selected_path = None
+
+ def stop(self):
+ self.treestore.clear()
+
+ def create_model_filter(self):
+ self.model_filter = self.treestore.filter_new()
+ self.model_filter.set_visible_column(FILTER_COLUMN)
+ self.treeview.set_model(self.model_filter)
+
+ def cb_update_filter_tree(self, filter_items):
+ # create missing cat_nodes
+ for cat in filter_items:
+ if cat not in self.cat_nodes:
+ label = _(cat)
+ if cat == 'label':
+ label = _('Labels')
+ self.cat_nodes[cat] = self.treestore.append(
+ None, ['cat', cat, label, 0, None, False]
+ )
+
+ # update rows
+ visible_filters = []
+ for cat, filters in filter_items.items():
+ for value, count in filters:
+ self.update_row(cat, value, count)
+ visible_filters.append((cat, value))
+
+ # hide root-categories not returned by core-part of the plugin.
+ for cat in self.cat_nodes:
+ self.treestore.set_value(
+ self.cat_nodes[cat],
+ FILTER_COLUMN,
+ True if cat in filter_items else False,
+ )
+
+ # hide items not returned by core-plugin.
+ for f in self.filters:
+ if f not in visible_filters:
+ self.treestore.set_value(self.filters[f], FILTER_COLUMN, False)
+
+ if self.expand_rows:
+ self.treeview.expand_all()
+ self.expand_rows = False
+
+ if not self.selected_path:
+ self.select_default_filter()
+
+ def update_row(self, cat, value, count, label=None):
+ def on_get_icon(icon):
+ if icon:
+ self.set_row_image(cat, value, icon.get_filename())
+
+ if (cat, value) in self.filters:
+ row = self.filters[(cat, value)]
+ self.treestore.set_value(row, 3, count)
+ else:
+ pix = self.get_pixmap(cat, value)
+
+ if value == '':
+ if cat == 'label':
+ label = _('No Label')
+ elif cat == 'owner':
+ label = _('No Owner')
+ elif not label and value:
+ label = _(value)
+
+ row = self.treestore.append(
+ self.cat_nodes[cat], [cat, value, label, count, pix, True]
+ )
+ self.filters[(cat, value)] = row
+
+ if cat == 'tracker_host' and value not in ('All', 'Error') and value:
+ d = self.tracker_icons.fetch(value)
+ d.addCallback(on_get_icon)
+
+ self.treestore.set_value(row, FILTER_COLUMN, True)
+ return row
+
+ def render_cell_data(self, column, cell, model, row, data):
+ cat = model.get_value(row, 0)
+ label = decode_bytes(model.get_value(row, 2))
+ count = model.get_value(row, 3)
+
+ # Supress Warning: g_object_set_qdata: assertion `G_IS_OBJECT (object)' failed
+ original_filters = warnings.filters[:]
+ warnings.simplefilter('ignore')
+ try:
+ pix = model.get_value(row, 4)
+ finally:
+ warnings.filters = original_filters
+
+ self.cell_pix.set_property('visible', True if pix else False)
+
+ if cat == 'cat':
+ self.cell_count.set_property('visible', False)
+ cell.set_padding(10, 2)
+ label = '<b>%s</b>' % label
+ else:
+ count_txt = '<small>%s</small>' % count
+ self.cell_count.set_property('markup', count_txt)
+ self.cell_count.set_property('visible', True)
+ cell.set_padding(2, 1)
+ cell.set_property('markup', label)
+
+ def get_pixmap(self, cat, value):
+ pix = None
+ if cat == 'state':
+ pix = STATE_PIX.get(value, None)
+ elif cat == 'tracker_host':
+ pix = TRACKER_PIX.get(value, None)
+
+ if pix:
+ return get_pixbuf('%s16.png' % pix)
+
+ def set_row_image(self, cat, value, filename):
+ pix = get_pixbuf_at_size(filename, 16)
+ row = self.filters[(cat, value)]
+ self.treestore.set_value(row, 4, pix)
+ return False
+
+ def on_selection_changed(self, selection):
+ try:
+ (model, row) = self.treeview.get_selection().get_selected()
+ if not row:
+ log.debug('nothing selected')
+ return
+
+ cat = model.get_value(row, 0)
+ value = model.get_value(row, 1)
+
+ filter_dict = {cat: [value]}
+ if value == 'All' or cat == 'cat':
+ filter_dict = {}
+
+ component.get('TorrentView').set_filter(filter_dict)
+
+ self.selected_path = model.get_path(row)
+
+ except Exception as ex:
+ log.debug(ex)
+ # paths is likely None .. so lets return None
+ return None
+
+ def update(self):
+ try:
+ hide_cat = []
+ if not self.config['sidebar_show_trackers']:
+ hide_cat.append('tracker_host')
+ if not self.config['sidebar_show_owners']:
+ hide_cat.append('owner')
+ client.core.get_filter_tree(
+ self.config['sidebar_show_zero'], hide_cat
+ ).addCallback(self.cb_update_filter_tree)
+ except Exception as ex:
+ log.debug(ex)
+
+ # Callbacks #
+ def on_button_press_event(self, widget, event):
+ """This is a callback for showing the right-click context menu."""
+ x, y = event.get_coords()
+ path = self.treeview.get_path_at_pos(int(x), int(y))
+ if not path:
+ return
+ path = path[0]
+ cat = self.model_filter[path][0]
+
+ if event.button == 1:
+ # Prevent selecting a category label
+ if cat == 'cat':
+ if self.treeview.row_expanded(path):
+ self.treeview.collapse_row(path)
+ else:
+ self.treeview.expand_row(path, False)
+ if not self.selected_path:
+ self.select_default_filter()
+ else:
+ self.treeview.get_selection().select_path(self.selected_path)
+ return True
+
+ elif event.button == 3:
+ # assign current cat, value to self:
+ x, y = event.get_coords()
+ path = self.treeview.get_path_at_pos(int(x), int(y))
+ if not path:
+ return
+ row = self.model_filter.get_iter(path[0])
+ self.cat = self.model_filter.get_value(row, 0)
+ self.value = self.model_filter.get_value(row, 1)
+ self.count = self.model_filter.get_value(row, 3)
+
+ # Show the pop-up menu
+ self.set_menu_sensitivity()
+ self.menu.hide()
+ self.menu.popup(None, None, None, None, event.button, event.time)
+ self.menu.show()
+
+ if cat == 'cat':
+ # Do not select the row
+ return True
+
+ def set_menu_sensitivity(self):
+ # select-all/pause/resume
+ sensitive = self.cat != 'cat' and self.count != 0
+ for item in self.default_menu_items:
+ item.set_sensitive(sensitive)
+
+ def select_all(self):
+ """For use in popup menu."""
+ component.get('TorrentView').treeview.get_selection().select_all()
+
+ def on_select_all(self, event):
+ self.select_all()
+
+ def on_pause_all(self, event):
+ self.select_all()
+ func = getattr(component.get('MenuBar'), 'on_menuitem_%s_activate' % 'pause')
+ func(event)
+
+ def on_resume_all(self, event):
+ self.select_all()
+ func = getattr(component.get('MenuBar'), 'on_menuitem_%s_activate' % 'resume')
+ func(event)
+
+ def _on_hide(self, *args):
+ self.select_default_filter()
+
+ def select_default_filter(self):
+ row = self.filters[('state', 'All')]
+ path = self.treestore.get_path(row)
+ self.treeview.get_selection().select_path(path)
diff --git a/deluge/ui/gtk3/glade/add_torrent_dialog.infohash.ui b/deluge/ui/gtk3/glade/add_torrent_dialog.infohash.ui
new file mode 100644
index 0000000..a7a8cae
--- /dev/null
+++ b/deluge/ui/gtk3/glade/add_torrent_dialog.infohash.ui
@@ -0,0 +1,219 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkDialog" id="dialog_infohash">
+ <property name="width_request">462</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Add Infohash</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="decorated">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_magnet_add_cancel">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_magnet_add_ok">
+ <property name="label" translatable="yes">_OK</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkBox" id="hbox8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image_dialog_magnet">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-revert-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">From Infohash</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="hseparator2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label23">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Infohash:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_hash">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="activates_default">True</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label22">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Trackers:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow3">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTextView" id="text_trackers">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">button_magnet_add_cancel</action-widget>
+ <action-widget response="-5">button_magnet_add_ok</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/add_torrent_dialog.ui b/deluge/ui/gtk3/glade/add_torrent_dialog.ui
new file mode 100644
index 0000000..4d36803
--- /dev/null
+++ b/deluge/ui/gtk3/glade/add_torrent_dialog.ui
@@ -0,0 +1,1039 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="lower">-1</property>
+ <property name="upper">9999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment2">
+ <property name="lower">-1</property>
+ <property name="upper">9999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment3">
+ <property name="lower">-1</property>
+ <property name="upper">9999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment4">
+ <property name="lower">-1</property>
+ <property name="upper">9999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkDialog" id="dialog_add_torrent">
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Add Torrents</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_cancel">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_cancel_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_add">
+ <property name="label" translatable="yes">_Add</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_add_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkPaned" id="vpaned1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">3</property>
+ <property name="left_padding">5</property>
+ <property name="right_padding">5</property>
+ <child>
+ <object class="GtkBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <property name="min_content_height">50</property>
+ <child>
+ <object class="GtkTreeView" id="listview_torrents">
+ <property name="height_request">71</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeselection1"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">center</property>
+ <child>
+ <object class="GtkButton" id="button_file">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_button_file_clicked" swapped="no"/>
+ <child>
+ <object class="GtkBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkImage" id="image3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-open-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_File</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_url">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_button_url_clicked" swapped="no"/>
+ <child>
+ <object class="GtkBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkImage" id="image4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">insert-link-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_URL</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_hash">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_button_hash_clicked" swapped="no"/>
+ <child>
+ <object class="GtkBox" id="hbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkImage" id="image5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-revert-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Info_hash</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_remove">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_button_remove_clicked" swapped="no"/>
+ <child>
+ <object class="GtkBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">4</property>
+ <child>
+ <object class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">list-remove-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Remove</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">5</property>
+ <property name="right_padding">5</property>
+ <child>
+ <object class="GtkNotebook" id="notebook1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_border">False</property>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkAlignment" id="alignment9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">5</property>
+ <child>
+ <object class="GtkBox" id="prefetch_hbox">
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkSpinner" id="prefetch_spinner">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="active">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="prefetch_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Please wait for files...</property>
+ <property name="xalign">0</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow2">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="border_width">2</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="listview_files">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="border_width">1</property>
+ <property name="headers_visible">False</property>
+ <property name="enable_tree_lines">True</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeselection2"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkBox" id="hbox11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImage" id="image9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-open-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Fi_les</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">3</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">3</property>
+ <child>
+ <object class="GtkFrame" id="frame7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="bottom_padding">1</property>
+ <property name="left_padding">5</property>
+ <property name="right_padding">5</property>
+ <child>
+ <object class="GtkBox" id="hbox_download_location_chooser">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label17">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Download Folder</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">5</property>
+ <property name="right_padding">5</property>
+ <child>
+ <object class="GtkBox" id="hbox_move_completed_chooser">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_move_completed">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="margin_right">10</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_chk_move_completed_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Move Complete Folder</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="separator1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkFrame" id="frame6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">7</property>
+ <property name="right_padding">12</property>
+ <child>
+ <object class="GtkBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_paused">
+ <property name="label" translatable="yes">Add In _Paused State</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_prioritize">
+ <property name="label" translatable="yes">Prioritize First/Last Pieces</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_sequential_download">
+ <property name="label" translatable="yes">Sequential Download</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text" translatable="yes">When enabled, the piece picker will pick pieces in
+sequence instead of rarest first.
+
+Enabling sequential download will affect the piece
+distribution negatively in the swarm. It should be
+used sparingly.</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_seed_mode">
+ <property name="label" translatable="yes">Skip File Hash Check</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_markup">Useful if adding a complete torrent for seeding.</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_super_seeding">
+ <property name="label" translatable="yes">Super Seeding</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_markup">Useful if adding a complete torrent for seeding.</property>
+ <property name="halign">start</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_pre_alloc">
+ <property name="label" translatable="yes">Preallocate Disk Space</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Preallocate the disk space for the torrent files</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label_item">
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">5</property>
+ <child>
+ <object class="GtkGrid" id="table1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">12</property>
+ <child>
+ <object class="GtkSpinButton" id="spin_maxup">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment2</property>
+ <property name="update_policy">if-valid</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_maxconnections">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment3</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_maxupslots">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment4</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_maxdown">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment1</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Maximum torrent download speed</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Down Speed:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Maximum torrent upload speed</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Up Speed:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Maximum torrent connections</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Connections:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Maximum torrent upload slots</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Upload Slots:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Bandwidth</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="right_padding">5</property>
+ <child>
+ <object class="GtkButton" id="button_apply">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_button_apply_clicked" swapped="no"/>
+ <child>
+ <object class="GtkBox" id="hbox7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImage" id="image7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="xalign">1</property>
+ <property name="icon_name">emblem-ok-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label18">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Apply To All</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="button_revert">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_button_revert_clicked" swapped="no"/>
+ <child>
+ <object class="GtkBox" id="hbox10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImage" id="image8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-revert-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label19">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Revert To Defaults</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkBox" id="hbox12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImage" id="image10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-properties-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label20">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Options</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button_cancel</action-widget>
+ <action-widget response="0">button_add</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkListStore" id="liststore1"/>
+</interface>
diff --git a/deluge/ui/gtk3/glade/add_torrent_dialog.url.ui b/deluge/ui/gtk3/glade/add_torrent_dialog.url.ui
new file mode 100644
index 0000000..ecbd0f7
--- /dev/null
+++ b/deluge/ui/gtk3/glade/add_torrent_dialog.url.ui
@@ -0,0 +1,176 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkDialog" id="url_dialog">
+ <property name="width_request">462</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Add URL</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="decorated">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_add_cancel">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_add_ok">
+ <property name="label" translatable="yes">_OK</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkBox" id="hbox7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">list-add-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">From URL</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="hseparator3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label21">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">URL:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_url">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="activates_default">True</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">button_add_cancel</action-widget>
+ <action-widget response="-5">button_add_ok</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/connect_peer_dialog.ui b/deluge/ui/gtk3/glade/connect_peer_dialog.ui
new file mode 100644
index 0000000..f5e9337
--- /dev/null
+++ b/deluge/ui/gtk3/glade/connect_peer_dialog.ui
@@ -0,0 +1,154 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkDialog" id="connect_peer_dialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Add Peer</property>
+ <property name="window_position">mouse</property>
+ <property name="type_hint">dialog</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="skip_pager_hint">True</property>
+ <property name="decorated">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button2">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button1">
+ <property name="label" translatable="yes">_OK</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">list-add-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Add Peer</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="hseparator1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="txt_ip">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="activates_default">True</property>
+ <property name="width_chars">39</property>
+ <property name="text" translatable="yes">hostname:port</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button2</action-widget>
+ <action-widget response="1">button1</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/connection_manager.addhost.ui b/deluge/ui/gtk3/glade/connection_manager.addhost.ui
new file mode 100644
index 0000000..641a71c
--- /dev/null
+++ b/deluge/ui/gtk3/glade/connection_manager.addhost.ui
@@ -0,0 +1,237 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkAdjustment" id="adjustment_port">
+ <property name="upper">65535</property>
+ <property name="value">58846</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkDialog" id="addhost_dialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Add Host</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_addhost_cancel">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_addhost_add">
+ <property name="label" translatable="yes">_Save</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Hostname:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">1</property>
+ <child>
+ <object class="GtkEntry" id="entry_hostname">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="activates_default">True</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <signal name="paste-clipboard" handler="on_entry_host_paste_clipboard" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Port:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spinbutton_port">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="max_length">5</property>
+ <property name="invisible_char">•</property>
+ <property name="width_chars">5</property>
+ <property name="progress_pulse_step">1</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_port</property>
+ <property name="climb_rate">1</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="table1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">5</property>
+ <child>
+ <object class="GtkEntry" id="entry_password">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ <property name="invisible_char">•</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">5</property>
+ <child>
+ <object class="GtkEntry" id="entry_username">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Username:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Password:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button_addhost_cancel</action-widget>
+ <action-widget response="1">button_addhost_add</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/connection_manager.ui b/deluge/ui/gtk3/glade/connection_manager.ui
new file mode 100644
index 0000000..11516aa
--- /dev/null
+++ b/deluge/ui/gtk3/glade/connection_manager.ui
@@ -0,0 +1,394 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkListStore" id="liststore_hostlist">
+ <columns>
+ <!-- column-name host_id -->
+ <column type="gchararray"/>
+ <!-- column-name hostname -->
+ <column type="gchararray"/>
+ <!-- column-name port -->
+ <column type="gint"/>
+ <!-- column-name username -->
+ <column type="gchararray"/>
+ <!-- column-name password -->
+ <column type="gchararray"/>
+ <!-- column-name status -->
+ <column type="gchararray"/>
+ <!-- column-name version -->
+ <column type="gchararray"/>
+ <!-- column-name status_i18n -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkDialog" id="connection_manager">
+ <property name="can_focus">False</property>
+ <property name="has_focus">True</property>
+ <property name="is_focus">True</property>
+ <property name="title" translatable="yes">Connection Manager</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="default_width">300</property>
+ <property name="default_height">250</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_close">
+ <property name="label" translatable="yes">_Close</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_close_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_connect">
+ <property name="label" translatable="yes">C_onnect</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_connect_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">5</property>
+ <property name="right_padding">5</property>
+ <child>
+ <object class="GtkViewport" id="viewport1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkTreeView" id="treeview_hostlist">
+ <property name="height_request">80</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="model">liststore_hostlist</property>
+ <signal name="row-activated" handler="on_hostlist_row_activated" swapped="no"/>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeselection"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="padding">3</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButtonBox" id="hbuttonbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="homogeneous">True</property>
+ <property name="layout_style">start</property>
+ <child>
+ <object class="GtkButton" id="button_addhost">
+ <property name="label" translatable="yes">_Add</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_addhost_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_edithost">
+ <property name="label" translatable="yes">_Edit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_edithost_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_removehost">
+ <property name="label" translatable="yes">_Remove</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_removehost_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_refresh">
+ <property name="label" translatable="yes">_Refresh</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_refresh_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="button_startdaemon">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_button_startdaemon_clicked" swapped="no"/>
+ <child>
+ <object class="GtkBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkImage" id="image_startdaemon">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">system-run-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">3</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_startdaemon">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Start Daemon</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="pack_type">end</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Deluge Daemons</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkExpander" id="expander1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="expanded">True</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">5</property>
+ <property name="right_padding">5</property>
+ <child>
+ <object class="GtkBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_autoconnect">
+ <property name="label" translatable="yes">Auto-connect to selected Daemon</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_chk_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_autostart">
+ <property name="label" translatable="yes">Auto-start localhost daemon (if required)</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_chk_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_donotshow">
+ <property name="label" translatable="yes">Hide this dialog</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_chk_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Startup Options</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button_close</action-widget>
+ <action-widget response="0">button_connect</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/create_torrent_dialog.progress.ui b/deluge/ui/gtk3/glade/create_torrent_dialog.progress.ui
new file mode 100644
index 0000000..e46ef17
--- /dev/null
+++ b/deluge/ui/gtk3/glade/create_torrent_dialog.progress.ui
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkDialog" id="progress_dialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Creating Torrent</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="type_hint">dialog</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkProgressBar" id="progressbar">
+ <property name="width_request">200</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/create_torrent_dialog.remote_path.ui b/deluge/ui/gtk3/glade/create_torrent_dialog.remote_path.ui
new file mode 100644
index 0000000..dc7b7e9
--- /dev/null
+++ b/deluge/ui/gtk3/glade/create_torrent_dialog.remote_path.ui
@@ -0,0 +1,176 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkDialog" id="remote_path_dialog">
+ <property name="width_request">462</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Enter Remote Path</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="decorated">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_add_cancel">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_add_ok">
+ <property name="label" translatable="yes">_OK</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkBox" id="hbox7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">network-workgroup-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Remote Path</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="hseparator3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label21">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Path:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_path">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="activates_default">True</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">button_add_cancel</action-widget>
+ <action-widget response="-5">button_add_ok</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/create_torrent_dialog.remote_save.ui b/deluge/ui/gtk3/glade/create_torrent_dialog.remote_save.ui
new file mode 100644
index 0000000..a380718
--- /dev/null
+++ b/deluge/ui/gtk3/glade/create_torrent_dialog.remote_save.ui
@@ -0,0 +1,176 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkDialog" id="remote_save_dialog">
+ <property name="width_request">462</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Save .torrent as</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="decorated">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_add_cancel1">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_add_ok1">
+ <property name="label" translatable="yes">_OK</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkBox" id="hbox15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">network-workgroup-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Save .torrent file</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="hseparator2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Path:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_save_path">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="activates_default">True</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">button_add_cancel1</action-widget>
+ <action-widget response="-5">button_add_ok1</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/create_torrent_dialog.ui b/deluge/ui/gtk3/glade/create_torrent_dialog.ui
new file mode 100644
index 0000000..c27a4b8
--- /dev/null
+++ b/deluge/ui/gtk3/glade/create_torrent_dialog.ui
@@ -0,0 +1,847 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkListStore" id="liststore1">
+ <columns>
+ <!-- column-name item -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">32 KiB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">64 KiB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">128 KiB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">256 KiB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">512 KiB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">1 MiB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">2 MiB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">4 MiB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">8 MiB</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">16 MiB</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkWindow" id="create_torrent_dialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Create Torrent</property>
+ <property name="window_position">center-on-parent</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-new-symbolic</property>
+ <property name="icon_size">5</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Create Torrent</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.2"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="hseparator1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">12</property>
+ <child>
+ <object class="GtkBox" id="vbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="treeview_files">
+ <property name="height_request">30</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeselection1"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox" id="hbuttonbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">center</property>
+ <child>
+ <object class="GtkButton" id="button_file">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_button_file_clicked" swapped="no"/>
+ <child>
+ <object class="GtkBox" id="hbox8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-new-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_File</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_folder">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_button_folder_clicked" swapped="no"/>
+ <child>
+ <object class="GtkBox" id="hbox9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">folder-open-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Fol_der</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_remote_path">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_button_remote_path_clicked" swapped="no"/>
+ <child>
+ <object class="GtkBox" id="hbox10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">network-workgroup-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Remote Path</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Files</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="notebook1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="bottom_padding">5</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">12</property>
+ <child>
+ <object class="GtkBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Author:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_author">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Comments:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_comments">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkBox" id="hbox11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">dialog-information-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Info</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="bottom_padding">5</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">12</property>
+ <child>
+ <object class="GtkBox" id="hbox6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow3">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="tracker_treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeselection2"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox" id="vbuttonbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">1</property>
+ <property name="layout_style">center</property>
+ <child>
+ <object class="GtkButton" id="button_up">
+ <property name="label" translatable="yes">_Up</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_up_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_add">
+ <property name="label" translatable="yes">_Add</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_add_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_remove">
+ <property name="label" translatable="yes">_Remove</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_remove_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_down">
+ <property name="label" translatable="yes">_Down</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_down_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkBox" id="hbox12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">text-editor-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Trackers</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="bottom_padding">5</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">12</property>
+ <child>
+ <object class="GtkViewport" id="viewport1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <child>
+ <object class="GtkTextView" id="textview_webseeds">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkBox" id="hbox13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">network-workgroup-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Webseeds</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="bottom_padding">5</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">12</property>
+ <child>
+ <object class="GtkBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkBox" id="hbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Piece Size:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combo_piece_size">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">liststore1</property>
+ <property name="active">2</property>
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_private_flag">
+ <property name="label" translatable="yes">Set Private Flag</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_add_to_session">
+ <property name="label" translatable="yes">Add this torrent to the session</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkBox" id="hbox14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">preferences-other-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Options</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_cancel">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_cancel_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_save">
+ <property name="label" translatable="yes">_Save</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_save_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/edit_trackers.add.ui b/deluge/ui/gtk3/glade/edit_trackers.add.ui
new file mode 100644
index 0000000..39d1978
--- /dev/null
+++ b/deluge/ui/gtk3/glade/edit_trackers.add.ui
@@ -0,0 +1,183 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkDialog" id="add_tracker_dialog">
+ <property name="width_request">400</property>
+ <property name="height_request">200</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Add Tracker</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_add_cancel">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_add_cancel_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_add_ok">
+ <property name="label" translatable="yes">_OK</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_add_ok_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">list-add-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Add Trackers</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="hseparator1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Trackers:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTextView" id="textview_trackers">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="left_margin">1</property>
+ <property name="right_margin">1</property>
+ <property name="accepts_tab">False</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">button_add_cancel</action-widget>
+ <action-widget response="-5">button_add_ok</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/edit_trackers.edit.ui b/deluge/ui/gtk3/glade/edit_trackers.edit.ui
new file mode 100644
index 0000000..2521e8f
--- /dev/null
+++ b/deluge/ui/gtk3/glade/edit_trackers.edit.ui
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkDialog" id="edit_tracker_entry">
+ <property name="width_request">400</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Edit Tracker</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_add_cancel1">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_edit_cancel_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_add_ok1">
+ <property name="label" translatable="yes">_OK</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_edit_ok_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">text-editor-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Edit Tracker</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="hseparator2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Tracker:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_edit_tracker">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="activates_default">True</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button_add_cancel1</action-widget>
+ <action-widget response="1">button_add_ok1</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/edit_trackers.ui b/deluge/ui/gtk3/glade/edit_trackers.ui
new file mode 100644
index 0000000..9b77a9b
--- /dev/null
+++ b/deluge/ui/gtk3/glade/edit_trackers.ui
@@ -0,0 +1,247 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkDialog" id="edit_trackers_dialog">
+ <property name="width_request">400</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Edit Trackers</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="default_width">400</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <signal name="configure-event" handler="on_edit_trackers_dialog_configure_event" swapped="no"/>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_cancel">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_ok">
+ <property name="label" translatable="yes">_OK</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkBox" id="hbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkImage" id="image3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">text-editor-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Edit Trackers</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.2"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow3">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="tracker_treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeselection"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox" id="vbuttonbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">1</property>
+ <property name="layout_style">center</property>
+ <child>
+ <object class="GtkButton" id="button_up">
+ <property name="label" translatable="yes">_Up</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_up_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_add">
+ <property name="label" translatable="yes">_Add</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_add_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_exit">
+ <property name="label" translatable="yes">_Edit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_edit_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_remove">
+ <property name="label" translatable="yes">_Remove</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_remove_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_down">
+ <property name="label" translatable="yes">_Down</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_down_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button_cancel</action-widget>
+ <action-widget response="1">button_ok</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/filtertree_menu.ui b/deluge/ui/gtk3/glade/filtertree_menu.ui
new file mode 100644
index 0000000..d2861e1
--- /dev/null
+++ b/deluge/ui/gtk3/glade/filtertree_menu.ui
@@ -0,0 +1,60 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkImage" id="image22">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">edit-select-all-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="menu-item-image22">
+ <property name="can_focus">False</property>
+ <property name="icon_name">media-playback-pause-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="menu-item-image23">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">media-playback-start-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkMenu" id="filtertree_menu">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="select_all">
+ <property name="label" translatable="yes">_Select All</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">image22</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_select_all" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_pause">
+ <property name="label" translatable="yes">_Pause All</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">menu-item-image22</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_pause_all" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_resume">
+ <property name="label" translatable="yes">Resu_me All</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Resume selected torrents.</property>
+ <property name="use_underline">True</property>
+ <property name="image">menu-item-image23</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_resume_all" swapped="no"/>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/main_window.new_release.ui b/deluge/ui/gtk3/glade/main_window.new_release.ui
new file mode 100644
index 0000000..f9c7fd5
--- /dev/null
+++ b/deluge/ui/gtk3/glade/main_window.new_release.ui
@@ -0,0 +1,249 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkDialog" id="new_release_dialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">New Release</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="icon_name">deluge</property>
+ <property name="type_hint">dialog</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_close_new_release">
+ <property name="label" translatable="yes">_Close</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_goto_downloads">
+ <property name="label" translatable="yes">_Goto Website</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">10</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image_new_release">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">image-missing</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label23">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">New Release Available!</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.2"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="hseparator2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">5</property>
+ <child>
+ <object class="GtkGrid" id="table2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">2</property>
+ <property name="column_spacing">10</property>
+ <child>
+ <object class="GtkLabel" id="label_available_version">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_available_version_text">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Available Version:</property>
+ <attributes>
+ <attribute name="style" value="italic"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_client_version">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_server_version">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_server_version_text">
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Server Version</property>
+ <attributes>
+ <attribute name="style" value="italic"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_client_version_text">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Current Version:</property>
+ <attributes>
+ <attribute name="style" value="italic"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_do_not_show_new_release">
+ <property name="label" translatable="yes">Do not show this dialog in the future</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button_close_new_release</action-widget>
+ <action-widget response="0">button_goto_downloads</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/main_window.tabs.menu_file.ui b/deluge/ui/gtk3/glade/main_window.tabs.menu_file.ui
new file mode 100644
index 0000000..dd5d66b
--- /dev/null
+++ b/deluge/ui/gtk3/glade/main_window.tabs.menu_file.ui
@@ -0,0 +1,138 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">zoom-fit-best-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">action-unavailable-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="image3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">go-next-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="image4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">go-up-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="image5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">go-down-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="image6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-open-symbolic</property>
+ </object>
+ <object class="GtkImage" id="image7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">folder-open-symbolic</property>
+ </object>
+ <object class="GtkMenu" id="menu_file_tab">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_open_file">
+ <property name="label" translatable="yes">_Open File</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">image6</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_open_file_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_show_file">
+ <property name="label" translatable="yes">_Show Folder</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">image7</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_show_file_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="menuitem3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_expand_all">
+ <property name="label" translatable="yes">_Expand All</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">image1</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_expand_all_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="menuitem_priority_sep">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_skip">
+ <property name="label" translatable="yes">_Skip</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">image2</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_skip_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_low">
+ <property name="label" translatable="yes">_Low</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">image5</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_low_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_normal">
+ <property name="label" translatable="yes">_Normal</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">image3</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_normal_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_high">
+ <property name="label" translatable="yes">_High</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">image4</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_high_activate" swapped="no"/>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/main_window.tabs.menu_peer.ui b/deluge/ui/gtk3/glade/main_window.tabs.menu_peer.ui
new file mode 100644
index 0000000..d35ef77
--- /dev/null
+++ b/deluge/ui/gtk3/glade/main_window.tabs.menu_peer.ui
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">list-add-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkMenu" id="menu_peer_tab">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="add_peer_menuitem">
+ <property name="label" translatable="yes">_Add Peer</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Add a peer by its IP</property>
+ <property name="use_underline">True</property>
+ <property name="image">image1</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_add_peer_activate" swapped="no"/>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/main_window.tabs.ui b/deluge/ui/gtk3/glade/main_window.tabs.ui
new file mode 100644
index 0000000..30bd395
--- /dev/null
+++ b/deluge/ui/gtk3/glade/main_window.tabs.ui
@@ -0,0 +1,1679 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkAdjustment" id="spin_max_connections_adjustment">
+ <property name="lower">-1</property>
+ <property name="upper">999999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ <signal name="value-changed" handler="on_spin_value_changed" swapped="no"/>
+ </object>
+ <object class="GtkAdjustment" id="spin_max_download_adjustment">
+ <property name="lower">-1</property>
+ <property name="upper">2097151</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ <signal name="value-changed" handler="on_spin_value_changed" swapped="no"/>
+ </object>
+ <object class="GtkAdjustment" id="spin_max_upload_adjustment">
+ <property name="lower">-1</property>
+ <property name="upper">2097151</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ <signal name="value-changed" handler="on_spin_value_changed" swapped="no"/>
+ </object>
+ <object class="GtkAdjustment" id="spin_max_upload_slots_adjustment">
+ <property name="lower">-1</property>
+ <property name="upper">999999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ <signal name="value-changed" handler="on_spin_value_changed" swapped="no"/>
+ </object>
+ <object class="GtkAdjustment" id="spin_stop_ratio_adjustment">
+ <property name="upper">999999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ <signal name="value-changed" handler="on_spin_value_changed" swapped="no"/>
+ </object>
+ <object class="GtkWindow" id="tabs">
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="dummy_nb_see_main_win_torrent_info">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tab_pos">left</property>
+ <child>
+ <object class="GtkScrolledWindow" id="status_tab">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkViewport" id="viewport1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment43">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="bottom_padding">2</property>
+ <property name="left_padding">10</property>
+ <property name="right_padding">10</property>
+ <child>
+ <object class="GtkBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkBox" id="status_progress_vbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkProgressBar" id="progressbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="pulse_step">0.10000000149</property>
+ <property name="show_text">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkGrid" id="table8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">5</property>
+ <property name="column_spacing">10</property>
+ <child>
+ <object class="GtkLabel" id="summary_total_uploaded">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="width_chars">20</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_total_downloaded">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_upload_speed">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="width_chars">15</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_seed_rank">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_availability">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">word-char</property>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_share_ratio">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_peers">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="width_chars">10</property>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_eta">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">5</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_active_time">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">5</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_seed_time">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">5</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_last_transfer">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">char</property>
+ </object>
+ <packing>
+ <property name="left_attach">5</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_last_seen_complete">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">char</property>
+ </object>
+ <packing>
+ <property name="left_attach">5</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label42">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Down Speed:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label43">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Up Speed:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label38">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Downloaded:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label39">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Uploaded:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_seeds">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="width_chars">10</property>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_download_speed">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="width_chars">15</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_seeds">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Seeds:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_peers">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Peers:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label41">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Share Ratio:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_availablity">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Availability:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_seed_rank">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Seed Rank:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_eta">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">ETA Time:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">4</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_last_transfer">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Last Transfer:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">4</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_active_time">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Active Time:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">4</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_last_seen_complete">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Complete Seen:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">4</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_seed_time">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Seeding Time:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">4</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="status_tab_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="ypad">1</property>
+ <property name="label" translatable="yes">_Status</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="details_tab">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkViewport" id="viewport2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment54">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="bottom_padding">2</property>
+ <property name="left_padding">10</property>
+ <property name="right_padding">15</property>
+ <child>
+ <object class="GtkGrid" id="table_details">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">5</property>
+ <property name="column_spacing">10</property>
+ <child>
+ <object class="GtkLabel" id="summary_name">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">word-char</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_total_size">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_num_files">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_completed">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ </object>
+ <packing>
+ <property name="left_attach">4</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_date_added">
+ <property name="width_request">100</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ </object>
+ <packing>
+ <property name="left_attach">4</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_pieces">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Pieces:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_pieces">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ </object>
+ <packing>
+ <property name="left_attach">4</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_hash">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="wrap">True</property>
+ <property name="selectable">True</property>
+ <property name="width_chars">40</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_torrent_path">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">char</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">start</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_comments">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="wrap_mode">char</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_creator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="wrap_mode">char</property>
+ <property name="selectable">True</property>
+ <property name="ellipsize">end</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_name">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Name:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_path">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Download Folder:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_date_added">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Added:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_total_size">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Total Size:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_num_files">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Total Files:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_hash">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Hash:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_creator">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Created By:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_comments">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Comments:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_completed">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Completed:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">3</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="vseparator5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">2</property>
+ <property name="height">3</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="details_tab_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="ypad">1</property>
+ <property name="label" translatable="yes">_Details</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="files_tab">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkTreeView" id="files_listview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeselection1"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="files_tab_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="ypad">1</property>
+ <property name="label" translatable="yes">Fi_les</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="peers_tab">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkTreeView" id="peers_listview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeselection2"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="peers_tab_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="ypad">1</property>
+ <property name="label" translatable="yes">_Peers</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="options_tab">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkViewport" id="viewport3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="bottom_padding">2</property>
+ <property name="left_padding">5</property>
+ <property name="right_padding">15</property>
+ <child>
+ <object class="GtkGrid" id="table6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkAlignment" id="alignment8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="left_padding">5</property>
+ <property name="right_padding">5</property>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label_owner">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Owner:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_owner">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="wrap_mode">char</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">10</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_shared">
+ <property name="label" translatable="yes">Shared</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_chk_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_prioritize_first_last">
+ <property name="label" translatable="yes">Prioritize First/Last</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_chk_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_sequential_download">
+ <property name="label" translatable="yes">Sequential Download</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_chk_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_super_seeding">
+ <property name="label" translatable="yes">Super Seeding</property>
+ <property name="use_action_appearance">False</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_chk_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_move_completed">
+ <property name="label" translatable="yes">Move completed:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_chk_move_completed_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox_move_completed_path_chooser">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="margin_top">15</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_auto_managed">
+ <property name="label" translatable="yes">Auto Managed</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_chk_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_stop_at_ratio">
+ <property name="label" translatable="yes">Stop seed at ratio:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_chk_stop_at_ratio_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">10</property>
+ <property name="right_padding">10</property>
+ <child>
+ <object class="GtkSpinButton" id="spin_stop_ratio">
+ <property name="width_request">50</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">spin_stop_ratio_adjustment</property>
+ <property name="digits">1</property>
+ <property name="numeric">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_remove_at_ratio">
+ <property name="label" translatable="yes">Remove at ratio</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_chk_toggled" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="hseparator1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">7</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="button_apply">
+ <property name="label" translatable="yes">_Apply</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_apply_clicked" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkGrid" id="table1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="row_spacing">2</property>
+ <property name="column_spacing">4</property>
+ <child>
+ <object class="GtkSpinButton" id="spin_max_connections">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">spin_max_connections_adjustment</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_max_upload">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">spin_max_upload_adjustment</property>
+ <property name="digits">1</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_max_download">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">spin_max_download_adjustment</property>
+ <property name="climb_rate">1</property>
+ <property name="digits">1</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Upload Speed:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">K/s</property>
+ <property name="ellipsize">start</property>
+ <attributes>
+ <attribute name="scale" value="0.90000000000000002"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">K/s</property>
+ <attributes>
+ <attribute name="scale" value="0.90000000000000002"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_max_upload_slots">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">spin_max_upload_slots_adjustment</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Download Speed:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Connections:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Upload Slots:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Bandwidth Limits</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="options_tab_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="ypad">1</property>
+ <property name="label" translatable="yes">_Options</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="position">4</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="trackers_tab">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkViewport" id="viewport4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="bottom_padding">2</property>
+ <property name="left_padding">10</property>
+ <property name="right_padding">15</property>
+ <child>
+ <object class="GtkGrid" id="table2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">5</property>
+ <property name="column_spacing">10</property>
+ <child>
+ <object class="GtkLabel" id="label_tracker">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Current Tracker:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_tracker">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_next_announce">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="wrap">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_tracker_status">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="wrap">True</property>
+ <property name="wrap_mode">char</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_tracker_total">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="summary_private">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="wrap_mode">char</property>
+ <property name="selectable">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_tracker_total">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Total Trackers:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_tracker_status">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Tracker Status:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_next_announce">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Next Announce:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_private">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Private Torrent:</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment_edit_trackers">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">start</property>
+ <property name="top_padding">5</property>
+ <child>
+ <object class="GtkButton" id="button_edit_trackers">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_button_edit_trackers_clicked" swapped="no"/>
+ <child>
+ <object class="GtkLabel" id="label_edit_trackers">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Edit Trackers</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="trackers_tab_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="ypad">1</property>
+ <property name="label" translatable="yes">_Trackers</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="position">5</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/main_window.ui b/deluge/ui/gtk3/glade/main_window.ui
new file mode 100644
index 0000000..43d8bf0
--- /dev/null
+++ b/deluge/ui/gtk3/glade/main_window.ui
@@ -0,0 +1,796 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkImage" id="about-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">help-about-symbolic</property>
+ </object>
+ <object class="GtkAccelGroup" id="accelgroup1"/>
+ <object class="GtkImage" id="add-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">list-add-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="lower">-1</property>
+ <property name="upper">999999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment2">
+ <property name="lower">-1</property>
+ <property name="upper">99999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment3">
+ <property name="lower">-1</property>
+ <property name="upper">999999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment4">
+ <property name="lower">-1</property>
+ <property name="upper">999999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment5">
+ <property name="upper">99999</property>
+ <property name="value">2</property>
+ <property name="step_increment">0.10000000000000001</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkImage" id="connection-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">preferences-system-network-symbolic</property>
+ </object>
+ <object class="GtkImage" id="image4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">go-up-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="image5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">go-top-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="image6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">zoom-fit-best-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="new-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-new-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="prefs-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">preferences-system-symbolic</property>
+ </object>
+ <object class="GtkImage" id="quit-daemon-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">system-shutdown-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="quit_image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">application-exit-symbolic</property>
+ </object>
+ <object class="GtkWindow" id="main_window">
+ <property name="can_focus">False</property>
+ <property name="title">Deluge</property>
+ <accel-groups>
+ <group name="accelgroup1"/>
+ </accel-groups>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkMenuBar" id="menubar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkMenuItem" id="menu_file">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_File</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menuitem1_menu1">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_addtorrent">
+ <property name="label" translatable="yes">_Add Torrent</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">add-image</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_addtorrent_activate" swapped="no"/>
+ <accelerator key="O" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_createtorrent">
+ <property name="label" translatable="yes">_Create Torrent</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">new-image</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_createtorrent_activate" swapped="no"/>
+ <accelerator key="N" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem">
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_quitdaemon">
+ <property name="label" translatable="yes">Quit &amp; _Shutdown Daemon</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">quit-daemon-image</property>
+ <property name="use_stock">False</property>
+ <property name="always_show_image">True</property>
+ <signal name="activate" handler="on_menuitem_quitdaemon_activate" swapped="no"/>
+ <accelerator key="Q" signal="activate" modifiers="GDK_SHIFT_MASK | GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_quit">
+ <property name="label" translatable="yes">_Quit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">quit_image</property>
+ <property name="use_stock">False</property>
+ <property name="accel_group">accelgroup1</property>
+ <signal name="activate" handler="on_menuitem_quit_activate" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menu_edit">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Edit</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_preferences">
+ <property name="label" translatable="yes">_Preferences</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">prefs-image</property>
+ <property name="use_stock">False</property>
+ <property name="accel_group">accelgroup1</property>
+ <signal name="activate" handler="on_menuitem_preferences_activate" swapped="no"/>
+ <accelerator key="P" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_connectionmanager">
+ <property name="label" translatable="yes">_Connection Manager</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">connection-image</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_connectionmanager_activate" swapped="no"/>
+ <accelerator key="M" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menu_torrent">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Torrent</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menu_view">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_View</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkCheckMenuItem" id="menuitem_toolbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Toolbar</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <signal name="toggled" handler="on_menuitem_toolbar_toggled" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckMenuItem" id="menuitem_sidebar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Sidebar</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <signal name="toggled" handler="on_menuitem_sidebar_toggled" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckMenuItem" id="menuitem_statusbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Status_bar</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <signal name="toggled" handler="on_menuitem_statusbar_toggled" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="menuitem1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menu_tabs">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">T_abs</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menu_columns">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Columns</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="find_menuitem">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Find ...</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_search_filter_toggle" swapped="no"/>
+ <accelerator key="f" signal="activate" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menu_Sidebar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="no_show_all">True</property>
+ <property name="label" translatable="yes">S_idebar</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menu3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="no_show_all">True</property>
+ <child>
+ <object class="GtkCheckMenuItem" id="sidebar_show_zero">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Show _Zero Hits</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <signal name="toggled" handler="on_menuitem_sidebar_zero_toggled" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckMenuItem" id="sidebar_show_trackers">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Show _Trackers</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <signal name="toggled" handler="on_menuitem_sidebar_trackers_toggled" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkCheckMenuItem" id="sidebar_show_owners">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Show _Owners</property>
+ <property name="active">True</property>
+ <signal name="toggled" handler="on_menuitem_sidebar_owners_toggled" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menu_help">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Help</property>
+ <property name="use_underline">True</property>
+ <child type="submenu">
+ <object class="GtkMenu" id="menuitem2_menu1">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_homepage">
+ <property name="label" translatable="yes">_Homepage</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_homepage_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_faq">
+ <property name="label" translatable="yes">_FAQ</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Frequently Asked Questions</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_faq_activate" swapped="no"/>
+ <accelerator key="F1" signal="activate"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_community">
+ <property name="label" translatable="yes">_Community</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="use_stock">False</property>
+ <property name="always_show_image">True</property>
+ <signal name="activate" handler="on_menuitem_community_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem56">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_about">
+ <property name="label" translatable="yes">_About</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">about-image</property>
+ <property name="use_stock">False</property>
+ <property name="accel_group">accelgroup1</property>
+ <signal name="activate" handler="on_menuitem_about_activate" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolbar" id="toolbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_size">2</property>
+ <child>
+ <object class="GtkToolButton" id="toolbutton_add">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text" translatable="yes">Add torrent</property>
+ <property name="label" translatable="yes">Add Torrent</property>
+ <property name="icon_name">list-add-symbolic</property>
+ <signal name="clicked" handler="on_toolbutton_add_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="toolbutton_remove">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text" translatable="yes">Remove torrent</property>
+ <property name="label" translatable="yes">Remove Torrent</property>
+ <property name="icon_name">list-remove-symbolic</property>
+ <signal name="clicked" handler="on_toolbutton_remove_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="toolbutton_filter">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Filter torrents by name.
+This will filter torrents for the current selection on the sidebar.</property>
+ <property name="label" translatable="yes">Filter</property>
+ <property name="icon_name">system-search-symbolic</property>
+ <signal name="clicked" handler="on_search_filter_toggle" swapped="no"/>
+ <accelerator key="f" signal="clicked" modifiers="GDK_CONTROL_MASK"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="separatortoolitem1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="toolbutton_pause">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text" translatable="yes">Pause the selected torrents</property>
+ <property name="label" translatable="yes">Pause</property>
+ <property name="icon_name">media-playback-pause-symbolic</property>
+ <signal name="clicked" handler="on_toolbutton_pause_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="toolbutton_resume">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text" translatable="yes">Resume the selected torrents</property>
+ <property name="label" translatable="yes">Resume</property>
+ <property name="icon_name">media-playback-start-symbolic</property>
+ <signal name="clicked" handler="on_toolbutton_resume_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="separatortoolitem2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="toolbutton_queue_up">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text" translatable="yes">Queue Torrent Up</property>
+ <property name="label" translatable="yes">Queue Up</property>
+ <property name="icon_name">go-up-symbolic</property>
+ <signal name="clicked" handler="on_toolbutton_queue_up_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="toolbutton_queue_down">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text" translatable="yes">Queue Torrent Down</property>
+ <property name="label" translatable="yes">Queue Down</property>
+ <property name="icon_name">go-down-symbolic</property>
+ <signal name="clicked" handler="on_toolbutton_queue_down_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparatorToolItem" id="toolbutton1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="toolbutton_preferences">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text" translatable="yes">Preferences</property>
+ <property name="label" translatable="yes">Preferences</property>
+ <property name="icon_name">preferences-system-symbolic</property>
+ <signal name="clicked" handler="on_toolbutton_preferences_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToolButton" id="toolbutton_connectionmanager">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text" translatable="yes">Connection Manager</property>
+ <property name="label" translatable="yes">Connection Manager</property>
+ <property name="icon_name">preferences-system-network-symbolic</property>
+ <signal name="clicked" handler="on_toolbutton_connectionmanager_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="homogeneous">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <child>
+ <object class="GtkPaned" id="tabsbar_pane">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkPaned" id="sidebar_pane">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkNotebook" id="sidebar_notebook">
+ <property name="can_focus">True</property>
+ <property name="scrollable">True</property>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="search_box">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkButton" id="close_search_button">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text" translatable="yes">Close</property>
+ <property name="relief">none</property>
+ <signal name="clicked" handler="on_close_search_button_clicked" swapped="no"/>
+ <child>
+ <object class="GtkImage" id="close_search_image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">window-close-symbolic</property>
+ <property name="icon_size">2</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Filter:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">1</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="search_torrents_entry">
+ <property name="width_request">350</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text" translatable="yes">Filter torrents by name.
+This will filter torrents for the current selection on the sidebar.</property>
+ <property name="invisible_char">•</property>
+ <property name="truncate_multiline">True</property>
+ <property name="caps_lock_warning">False</property>
+ <property name="secondary_icon_name">edit-clear-symbolic</property>
+ <property name="primary_icon_sensitive">False</property>
+ <property name="secondary_icon_tooltip_text" translatable="yes">Clear the search</property>
+ <property name="secondary_icon_tooltip_markup" translatable="yes">Clear the search</property>
+ <signal name="changed" handler="on_search_torrents_entry_changed" swapped="no"/>
+ <signal name="icon-press" handler="on_search_torrents_entry_icon_press" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">1</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="search_torrents_match">
+ <property name="label" translatable="yes">_Match Case</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_search_torrents_match_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="padding">1</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">out</property>
+ <child>
+ <object class="GtkTreeView" id="torrent_view">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="rules_hint">True</property>
+ <property name="enable_search">False</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="torrent_info">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tab_pos">left</property>
+ <property name="show_border">False</property>
+ <property name="scrollable">True</property>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkStatusbar" id="statusbar">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/move_storage_dialog.ui b/deluge/ui/gtk3/glade/move_storage_dialog.ui
new file mode 100644
index 0000000..542d40a
--- /dev/null
+++ b/deluge/ui/gtk3/glade/move_storage_dialog.ui
@@ -0,0 +1,163 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkDialog" id="move_storage_dialog">
+ <property name="width_request">500</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Move Download Folder</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_cancel">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_ok">
+ <property name="label" translatable="yes">_OK</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-save-as-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Move the torrent(s) download folder.</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="hseparator1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox_entry">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Destination:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">button_cancel</action-widget>
+ <action-widget response="-5">button_ok</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/other_dialog.ui b/deluge/ui/gtk3/glade/other_dialog.ui
new file mode 100644
index 0000000..26d3d08
--- /dev/null
+++ b/deluge/ui/gtk3/glade/other_dialog.ui
@@ -0,0 +1,190 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="lower">-1</property>
+ <property name="upper">2097151</property>
+ <property name="value">-1</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkDialog" id="other_dialog">
+ <property name="app_paintable">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="modal">True</property>
+ <property name="window_position">mouse</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="skip_pager_hint">True</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button3">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button4">
+ <property name="label" translatable="yes">_OK</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkImage" id="image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_header">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label">label</property>
+ <property name="use_markup">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="hseparator2">
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkSpinButton" id="spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="max_length">6</property>
+ <property name="activates_default">True</property>
+ <property name="width_chars">6</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment1</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_type">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">label</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">button3</action-widget>
+ <action-widget response="-5">button4</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/path_combo_chooser.ui b/deluge/ui/gtk3/glade/path_combo_chooser.ui
new file mode 100644
index 0000000..f79685d
--- /dev/null
+++ b/deluge/ui/gtk3/glade/path_combo_chooser.ui
@@ -0,0 +1,1002 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkAdjustment" id="adjustment1">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment2">
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment3">
+ <property name="lower">-1</property>
+ <property name="upper">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkDialog" id="completion_config_dialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Properties</property>
+ <property name="modal">True</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <signal name="delete-event" handler="on_completion_config_dialog_delete_event" swapped="no"/>
+ <signal name="key-release-event" handler="on_completion_config_dialog_key_release_event" swapped="no"/>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="config_dialog_button_close">
+ <property name="label" translatable="yes">Close</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_config_dialog_button_close_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="config_general_frame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="bottom_padding">3</property>
+ <property name="left_padding">15</property>
+ <property name="right_padding">10</property>
+ <child>
+ <object class="GtkBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="visible_rows_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Max drop down rows</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="visible_rows_spinbutton">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="max_length">2</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment3</property>
+ <property name="climb_rate">1</property>
+ <property name="numeric">True</property>
+ <signal name="value-changed" handler="on_visible_rows_spinbutton_value_changed" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;b&gt;General&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="bottom_padding">3</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">5</property>
+ <child>
+ <object class="GtkBox" id="vbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="show_path_entry_checkbutton">
+ <property name="label" translatable="yes">Show path entry</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_show_path_entry_checkbutton_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="show_filechooser_checkbutton">
+ <property name="label" translatable="yes">Show file chooser</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_show_filechooser_checkbutton_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkCheckButton" id="show_folder_name_on_button_checkbutton">
+ <property name="label" translatable="yes">Show folder name</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_show_folder_name_on_button_checkbutton_toggled" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Path Chooser Type</property>
+ <property name="use_markup">True</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="bottom_padding">3</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">5</property>
+ <child>
+ <object class="GtkGrid" id="table2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">5</property>
+ <property name="column_spacing">5</property>
+ <child>
+ <object class="GtkCheckButton" id="enable_auto_completion_checkbutton">
+ <property name="label" translatable="yes">Enable autocomplete</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_enable_auto_completion_checkbutton_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="show_hidden_files_checkbutton">
+ <property name="label" translatable="yes">Show hidden files</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_show_hidden_files_checkbutton_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="set_completion_accelerator_button">
+ <property name="label" translatable="yes">Set new key</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Press this key to set new key accelerators to trigger auto-complete</property>
+ <signal name="activate" handler="on_set_completion_accelerator_button_pressed" swapped="no"/>
+ <signal name="clicked" handler="on_set_completion_accelerator_button_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Autocomplete</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="config_short_cuts_frame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">etched-out</property>
+ <child>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">4</property>
+ <property name="bottom_padding">5</property>
+ <property name="left_padding">15</property>
+ <property name="right_padding">5</property>
+ <child>
+ <object class="GtkGrid" id="table3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">2</property>
+ <property name="column_spacing">10</property>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Autocomplete</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Save path</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Ctrl+S</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="completion_accelerator_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Ctrl+E</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Ctrl+R</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Ctrl+H</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Ctrl+D</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Edit path</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Remove path</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="margin_right">8</property>
+ <property name="label" translatable="yes">Toggle hidden files</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Default path</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">5</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Shortcuts</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">config_dialog_button_close</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkWindow" id="combobox_window">
+ <property name="can_focus">False</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkBox" id="entry_combobox_hbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">3</property>
+ <signal name="realize" handler="on_entry_combobox_hbox_realize" swapped="no"/>
+ <child>
+ <object class="GtkFileChooserButton" id="filechooser_button">
+ <property name="width_request">160</property>
+ <property name="can_focus">False</property>
+ <property name="no_show_all">True</property>
+ <property name="action">select-folder</property>
+ <property name="local_only">False</property>
+ <property name="preview_widget_active">False</property>
+ <property name="title" translatable="yes">Select a Directory</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_open_dialog">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_button_open_dialog_clicked" swapped="no"/>
+ <child>
+ <object class="GtkBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImage" id="image2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">folder-open-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="folder_name_label">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_text">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <signal name="changed" handler="on_entry_text_changed" swapped="no"/>
+ <signal name="delete-text" handler="on_entry_text_delete_text" swapped="no"/>
+ <signal name="focus-out-event" handler="on_entry_text_focus_out_event" swapped="no"/>
+ <signal name="insert-text" handler="on_entry_text_insert_text" swapped="yes"/>
+ <signal name="key-press-event" handler="on_entry_text_key_press_event" swapped="no"/>
+ <signal name="scroll-event" handler="on_entry_text_scroll_event" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkToggleButton" id="button_toggle_dropdown">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Saved paths</property>
+ <signal name="button-press-event" handler="on_button_toggle_dropdown_button_press_event" swapped="no"/>
+ <signal name="scroll-event" handler="on_button_toggle_dropdown_scroll_event" swapped="no"/>
+ <signal name="toggled" handler="on_button_toggle_dropdown_toggled" swapped="no"/>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="height_request">15</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="homogeneous">True</property>
+ <child>
+ <object class="GtkArrow" id="arrow2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="arrow_type">down</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkListStore" id="completion_tree_store">
+ <columns>
+ <!-- column-name text -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkWindow" id="completion_popup_window">
+ <property name="can_focus">False</property>
+ <property name="type">popup</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">combo</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="skip_pager_hint">True</property>
+ <property name="decorated">False</property>
+ <property name="deletable">False</property>
+ <signal name="button-press-event" handler="on_completion_popup_window_button_press_event" swapped="no"/>
+ <signal name="focus-out-event" handler="on_completion_popup_window_focus_out_event" swapped="no"/>
+ <signal name="key-press-event" handler="on_completion_popup_window_key_press_event" swapped="no"/>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkBox" id="popup_content_box1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">3</property>
+ <property name="spacing">1</property>
+ <child>
+ <object class="GtkScrolledWindow" id="completion_scrolled_window">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="completion_treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="model">completion_tree_store</property>
+ <property name="headers_visible">False</property>
+ <property name="headers_clickable">False</property>
+ <property name="enable_search">False</property>
+ <property name="search_column">0</property>
+ <property name="show_expanders">False</property>
+ <signal name="button-press-event" handler="on_completion_treeview_mouse_button_press_event" swapped="no"/>
+ <signal name="key-press-event" handler="on_completion_treeview_key_press_event" swapped="no"/>
+ <signal name="motion-notify-event" handler="on_completion_treeview_motion_notify_event" swapped="no"/>
+ <signal name="scroll-event" handler="on_completion_treeview_scroll_event" swapped="no"/>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeselection1"/>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="completion_treeview_column">
+ <property name="sizing">autosize</property>
+ <property name="fixed_width">129</property>
+ <property name="title" translatable="yes">column</property>
+ <property name="expand">True</property>
+ <child>
+ <object class="GtkCellRendererText" id="completion_cellrenderertext"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <object class="GtkDialog" id="filechooserdialog">
+ <property name="can_focus">False</property>
+ <property name="title" translatable="yes">Choose a folder</property>
+ <property name="modal">True</property>
+ <property name="window_position">center</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="box1">
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="buttonbox1">
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="filechooser_button_cancel">
+ <property name="label" translatable="yes">Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="filechooser_button_open">
+ <property name="label" translatable="yes">Open</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFileChooserWidget" id="filechooser_widget">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="action">select-folder</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="2">filechooser_button_cancel</action-widget>
+ <action-widget response="0">filechooser_button_open</action-widget>
+ </action-widgets>
+ </object>
+ <object class="GtkListStore" id="stored_values_tree_store">
+ <columns>
+ <!-- column-name text -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkWindow" id="stored_values_popup_window">
+ <property name="can_focus">False</property>
+ <property name="type">popup</property>
+ <property name="resizable">False</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">popup-menu</property>
+ <property name="skip_taskbar_hint">True</property>
+ <property name="skip_pager_hint">True</property>
+ <property name="decorated">False</property>
+ <property name="deletable">False</property>
+ <signal name="button-press-event" handler="on_stored_values_popup_window_button_press_event" swapped="no"/>
+ <signal name="focus-out-event" handler="on_stored_values_popup_window_focus_out_event" swapped="no"/>
+ <signal name="hide" handler="on_stored_values_popup_window_hide" swapped="no"/>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="border_width">3</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="hscrollbar_policy">never</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="stored_values_treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="model">stored_values_tree_store</property>
+ <property name="headers_visible">False</property>
+ <property name="headers_clickable">False</property>
+ <property name="search_column">0</property>
+ <property name="show_expanders">False</property>
+ <signal name="button-press-event" handler="on_stored_values_treeview_mouse_button_press_event" swapped="no"/>
+ <signal name="key-press-event" handler="on_stored_values_treeview_key_press_event" swapped="no"/>
+ <signal name="key-release-event" handler="on_stored_values_treeview_key_release_event" swapped="no"/>
+ <signal name="scroll-event" handler="on_stored_values_treeview_scroll_event" swapped="no"/>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeselection2"/>
+ </child>
+ <child>
+ <object class="GtkTreeViewColumn" id="stored_values_treeview_column">
+ <property name="sizing">autosize</property>
+ <property name="fixed_width">127</property>
+ <property name="title" translatable="yes">column</property>
+ <property name="expand">True</property>
+ <child>
+ <object class="GtkCellRendererText" id="stored_values_cellrenderertext">
+ <signal name="edited" handler="on_cellrenderertext_edited" swapped="no"/>
+ </object>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkButtonBox" id="buttonbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">1</property>
+ <property name="layout_style">start</property>
+ <signal name="key-press-event" handler="on_buttonbox_key_press_event" swapped="no"/>
+ <child>
+ <object class="GtkButton" id="button_add">
+ <property name="label" translatable="yes">Add</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Add the current entry value to the list</property>
+ <signal name="clicked" handler="on_button_add_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_edit">
+ <property name="label" translatable="yes">Edit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Edit the selected entry</property>
+ <signal name="clicked" handler="on_button_edit_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_remove">
+ <property name="label" translatable="yes">Remove</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Remove the selected entry</property>
+ <signal name="clicked" handler="on_button_remove_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_up">
+ <property name="label" translatable="yes">Up</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Move the selected entry up</property>
+ <signal name="clicked" handler="on_button_up_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_down">
+ <property name="label" translatable="yes">Down</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Move the selected entry down</property>
+ <signal name="clicked" handler="on_button_down_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_default">
+ <property name="label" translatable="yes">Default</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">No default path set</property>
+ <signal name="clicked" handler="on_button_default_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_properties">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Open properties dialog</property>
+ <signal name="clicked" handler="on_button_properties_clicked" swapped="no"/>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-properties-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">2</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/preferences_dialog.ui b/deluge/ui/gtk3/glade/preferences_dialog.ui
new file mode 100644
index 0000000..e1bbc74
--- /dev/null
+++ b/deluge/ui/gtk3/glade/preferences_dialog.ui
@@ -0,0 +1,5020 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkAdjustment" id="adjustment_cache_expiry">
+ <property name="lower">1</property>
+ <property name="upper">32000</property>
+ <property name="value">60</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_cache_size">
+ <property name="upper">999999</property>
+ <property name="value">100</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_share_ratio">
+ <property name="lower">0.5</property>
+ <property name="upper">100</property>
+ <property name="value">2</property>
+ <property name="step_increment">0.10000000000000001</property>
+ <property name="page_increment">1</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_share_ratio_limit">
+ <property name="lower">-1</property>
+ <property name="upper">100</property>
+ <property name="value">1.5</property>
+ <property name="step_increment">0.10000000000000001</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_active">
+ <property name="lower">-1</property>
+ <property name="upper">9999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_daemon_port">
+ <property name="upper">65535</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_downloading">
+ <property name="lower">-1</property>
+ <property name="upper">9999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_incoming_port">
+ <property name="upper">65535</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_max_conn_global">
+ <property name="lower">-1</property>
+ <property name="upper">9999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_max_conn_per_sec">
+ <property name="lower">-1</property>
+ <property name="upper">9999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_max_conn_per_torrent">
+ <property name="lower">-1</property>
+ <property name="upper">9999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_max_download">
+ <property name="lower">-1</property>
+ <property name="upper">2097151</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_max_download_per_torrent">
+ <property name="lower">-1</property>
+ <property name="upper">2097151</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_max_half_open_conn">
+ <property name="lower">-1</property>
+ <property name="upper">9999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_max_upload">
+ <property name="lower">-1</property>
+ <property name="upper">2097151</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_max_upload_per_torrent">
+ <property name="lower">-1</property>
+ <property name="upper">2097151</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_max_upload_slots_global">
+ <property name="lower">-1</property>
+ <property name="upper">9999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_max_upload_slots_per_torrent">
+ <property name="lower">-1</property>
+ <property name="upper">9999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_outgoing_port_max">
+ <property name="upper">65535</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_outgoing_port_min">
+ <property name="upper">65535</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_proxy_port">
+ <property name="upper">65535</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_seed_time_limit">
+ <property name="lower">-1</property>
+ <property name="upper">9999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_spin_seeding">
+ <property name="lower">-1</property>
+ <property name="upper">9999</property>
+ <property name="step_increment">1</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkAdjustment" id="adjustment_time_ratio_limit">
+ <property name="lower">-1</property>
+ <property name="upper">100</property>
+ <property name="value">6</property>
+ <property name="step_increment">0.10000000000000001</property>
+ <property name="page_increment">10</property>
+ </object>
+ <object class="GtkListStore" id="liststore1">
+ <columns>
+ <!-- column-name item -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Forced</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Enabled</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Disabled</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="liststore2">
+ <columns>
+ <!-- column-name item -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Handshake</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Full Stream</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Either</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="liststore3">
+ <columns>
+ <!-- column-name item -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">Forced</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Enabled</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Disabled</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkListStore" id="liststore8">
+ <columns>
+ <!-- column-name language_code -->
+ <column type="gchararray"/>
+ <!-- column-name language_name -->
+ <column type="gchararray"/>
+ </columns>
+ </object>
+ <object class="GtkListStore" id="liststore_proxy">
+ <columns>
+ <!-- column-name item -->
+ <column type="gchararray"/>
+ </columns>
+ <data>
+ <row>
+ <col id="0" translatable="yes">None</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Socks4</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Socks5</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">Socks5 Auth</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">HTTP</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">HTTP Auth</col>
+ </row>
+ <row>
+ <col id="0" translatable="yes">I2P</col>
+ </row>
+ </data>
+ </object>
+ <object class="GtkDialog" id="pref_dialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Preferences</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="default_width">450</property>
+ <property name="default_height">500</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <signal name="configure-event" handler="on_pref_dialog_configure_event" swapped="no"/>
+ <signal name="delete-event" handler="on_pref_dialog_delete_event" swapped="no"/>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_cancel">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_cancel_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_apply">
+ <property name="label" translatable="yes">_Apply</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_apply_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_ok">
+ <property name="label" translatable="yes">_OK</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_ok_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkPaned" id="hpaned1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkViewport" id="viewport6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <child>
+ <object class="GtkTreeView" id="treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeselection1"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkNotebook" id="notebook">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="show_tabs">False</property>
+ <property name="scrollable">True</property>
+ <child>
+ <object class="GtkScrolledWindow" id="InterfaceScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkViewport" id="interface_viewport">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkBox" id="interface_vbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkFrame" id="frame_app_mode">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment_app_mode">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">3</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkBox" id="hbox_app_mode">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkRadioButton" id="radio_standalone">
+ <property name="label" translatable="yes">Standalone</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">The standalone self-contained application</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="radio_thinclient">
+ <property name="label" translatable="yes">Thin Client</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Connect to a Deluge daemon (deluged)</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radio_standalone</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">7</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label_client_mode">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Application Mode</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame29">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment47">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="bottom_padding">2</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkBox" id="vbox27">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_show_rate_in_title">
+ <property name="label" translatable="yes">Show session speed in titlebar</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_focus_main_window_on_add">
+ <property name="label" translatable="yes">Focus window when adding torrent</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="events">GDK_POINTER_MOTION_MASK | GDK_POINTER_MOTION_HINT_MASK | GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="piecesbar_toggle">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">The pieces bar
+will increase bandwidth use between client
+and daemon (does not apply in Standalone mode).</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_piecesbar_toggle_toggled" swapped="no"/>
+ <child>
+ <object class="GtkLabel" id="label62">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Show a pieces bar in Status tab</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkExpander" id="piecebar_colors_expander">
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkAlignment" id="alignment58">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">25</property>
+ <child>
+ <object class="GtkGrid" id="table6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">1</property>
+ <property name="column_spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label66">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Completed:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="completed_color">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="color-set" handler="on_completed_color_set" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label67">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Downloading:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="downloading_color">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="color-set" handler="on_downloading_color_set" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label69">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Waiting:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="waiting_color">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="color-set" handler="on_waiting_color_set" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label70">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Missing:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkColorButton" id="missing_color">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="color-set" handler="on_missing_color_set" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="revert_color_completed">
+ <property name="label" translatable="yes">_Revert</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Revert color to default</property>
+ <property name="use_underline">True</property>
+ <property name="image_position">right</property>
+ <signal name="clicked" handler="on_revert_color_completed_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="revert_color_downloading">
+ <property name="label" translatable="yes">_Revert</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Revert color to default</property>
+ <property name="use_underline">True</property>
+ <property name="image_position">right</property>
+ <signal name="clicked" handler="on_revert_color_downloading_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="revert_color_waiting">
+ <property name="label" translatable="yes">_Revert</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Revert color to default</property>
+ <property name="use_underline">True</property>
+ <property name="image_position">right</property>
+ <signal name="clicked" handler="on_revert_color_waiting_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="revert_color_missing">
+ <property name="label" translatable="yes">_Revert</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="tooltip_text" translatable="yes">Revert color to default</property>
+ <property name="use_underline">True</property>
+ <property name="image_position">right</property>
+ <signal name="clicked" handler="on_revert_color_missing_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label73">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Piece Colors</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label106">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Main Window</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="bottom_padding">2</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkBox" id="vbox17">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_use_tray">
+ <property name="label" translatable="yes">Enable system tray icon</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment_tray_type">
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="hbox_tray_type">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkRadioButton" id="radio_appind">
+ <property name="label" translatable="yes">App Indicator</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radio_systray</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">10</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="radio_systray">
+ <property name="label" translatable="yes">Systray</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment_min_on_close">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_min_on_close">
+ <property name="label" translatable="yes">Minimize to tray on close</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment_start_in_tray">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_start_in_tray">
+ <property name="label" translatable="yes">Start in tray</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment_lock_tray">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="bottom_padding">3</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_lock_tray">
+ <property name="label" translatable="yes">Password protect system tray</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle" swapped="no"/>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment_tray_password">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">20</property>
+ <child>
+ <object class="GtkBox" id="hbox_tray_password">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="password_label">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Password:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="txt_tray_password">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ <property name="width_chars">16</property>
+ <property name="text">********</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label52">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">System Tray</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame19">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment35">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkBox" id="vbox18">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkAlignment" id="alignment36">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_show_new_releases">
+ <property name="label" translatable="yes">Notify about new releases</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label43">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Updates</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="checkbutton_language">
+ <property name="label" translatable="yes">System Default</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_checkbutton_language_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">20</property>
+ <child>
+ <object class="GtkComboBox" id="combobox_language">
+ <property name="can_focus">False</property>
+ <property name="model">liststore8</property>
+ <property name="active">0</property>
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext8"/>
+ <attributes>
+ <attribute name="text">1</attribute>
+ </attributes>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">&lt;b&gt;Languge&lt;/b&gt;</property>
+ <property name="use_markup">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label29">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label">page 6</property>
+ </object>
+ <packing>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="DownloadsScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkViewport" id="viewport2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkBox" id="downloads_vbox">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkFrame" id="frame5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="bottom_padding">2</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkGrid" id="table9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">1</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_move_completed">
+ <property name="label" translatable="yes">Move completed to:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_copy_torrent_file">
+ <property name="label" translatable="yes">Copy of .torrent files to:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_del_copy_torrent_file">
+ <property name="label" translatable="yes">Delete copy of torrent file on remove</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Delete the copy of the torrent file created when the torrent is removed</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label19">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Download to:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">15</property>
+ <child>
+ <object class="GtkBox" id="hbox_download_to_path_chooser">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">15</property>
+ <child>
+ <object class="GtkBox" id="hbox_move_completed_to_path_chooser">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment30">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">15</property>
+ <child>
+ <object class="GtkBox" id="hbox_copy_torrent_files_path_chooser">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">5</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Download Folders</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="bottom_padding">2</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="newvbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_prioritize_first_last_pieces">
+ <property name="label" translatable="yes">Prioritize first and last pieces of torrent</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Prioritize first and last pieces of files in torrent</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_sequential_download">
+ <property name="label" translatable="yes">Sequential download</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">When enabled, the piece picker will pick pieces in
+sequence instead of rarest first.
+
+Enabling sequential download will affect the piece
+distribution negatively in the swarm. It should be
+used sparingly.</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_add_paused">
+ <property name="label" translatable="yes">Add torrents in Paused state</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_pre_allocation">
+ <property name="label" translatable="yes">Pre-allocate disk space</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Pre-allocate the disk space for the torrent files</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Add Torrent Options</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame18">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="bottom_padding">2</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="vbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_show_dialog">
+ <property name="label" translatable="yes">Always show</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment19">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_focus_dialog">
+ <property name="label" translatable="yes">Bring the dialog to focus</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label47">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Add Torrents Dialog</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">3</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label20">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label">page 7</property>
+ </object>
+ <packing>
+ <property name="position">1</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="BandwidthScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkViewport" id="viewport3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkBox" id="vbox7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkFrame" id="frame11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="bottom_padding">2</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="vbox21">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkGrid" id="table1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">10</property>
+ <child>
+ <object class="GtkSpinButton" id="spin_max_connections_per_second">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_max_conn_per_sec</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_max_half_open_connections">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_max_half_open_conn</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label58">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Connection Attempts per Second:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label57">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Half-Open Connections:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">The maximum number of connections allowed. Set -1 for unlimited.</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Connections:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">The maximum upload slots for all torrents. Set -1 for unlimited.</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Upload Slots:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_max_connections_global">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">The maximum number of connections allowed. Set -1 for unlimited.</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_max_conn_global</property>
+ <property name="climb_rate">1</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="numeric">True</property>
+ <property name="update_policy">if-valid</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">The maximum download speed for all torrents. Set -1 for unlimited.</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Download Speed:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_max_download">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">The maximum download speed for all torrents. Set -1 for unlimited.</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_max_download</property>
+ <property name="climb_rate">1</property>
+ <property name="digits">1</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_max_upload">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">The maximum upload speed for all torrents. Set -1 for unlimited.</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_max_upload</property>
+ <property name="climb_rate">1</property>
+ <property name="digits">1</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_max_upload_slots_global">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">The maximum upload slots for all torrents. Set -1 for unlimited.</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_max_upload_slots_global</property>
+ <property name="climb_rate">1</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label22">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">KiB/s</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label23">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">KiB/s</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label17">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">The maximum upload speed for all torrents. Set -1 for unlimited.</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Upload Speed:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment28">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">5</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_ignore_limits_on_local_network">
+ <property name="label" translatable="yes">Ignore limits on local network</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment40">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">5</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_rate_limit_ip_overhead">
+ <property name="label" translatable="yes">Rate limit IP overhead</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">If checked, the estimated TCP/IP overhead is drained from the rate limiters, to avoid exceeding the limits with the total traffic</property>
+ <property name="halign">start</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label37">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Global Bandwidth Limits</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment18">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="bottom_padding">2</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkGrid" id="table4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">10</property>
+ <child>
+ <object class="GtkSpinButton" id="spin_max_upload_slots_per_torrent">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">The maximum upload slots per torrent. Set -1 for unlimited.</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_max_upload_slots_per_torrent</property>
+ <property name="climb_rate">1</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_max_connections_per_torrent">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">The maximum number of connections per torrent. Set -1 for unlimited.</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_max_conn_per_torrent</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label18">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Connections:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label24">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Upload Slots:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label31">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Download Speed:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label32">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Upload Speed:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_max_download_per_torrent">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">The maximum number download speed per torrent. Set -1 for unlimited.</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_max_download_per_torrent</property>
+ <property name="digits">1</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_max_upload_per_torrent">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">The maximum upload speed per torrent. Set -1 for unlimited.</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_max_upload_per_torrent</property>
+ <property name="digits">1</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">KiB/s</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label21">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="valign">start</property>
+ <property name="label" translatable="yes">KiB/s</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label33">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Per-Torrent Bandwidth Limits</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label38">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label">page 8</property>
+ </object>
+ <packing>
+ <property name="position">2</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="QueueScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkViewport" id="viewport9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <child>
+ <object class="GtkBox" id="queue_prefs_box2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkFrame" id="frame10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment22">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="vbox6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_queue_new_top">
+ <property name="label" translatable="yes">Queue to top</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label36">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">New Torrents</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment23">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="vbox20">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkGrid" id="table3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">10</property>
+ <child>
+ <object class="GtkSpinButton" id="spin_active">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_active</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_seeding">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_seeding</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label27">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Seeding:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label48">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Total:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_downloading">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_downloading</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label42">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Downloading:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_dont_count_slow_torrents">
+ <property name="label" translatable="yes">Ignore slow torrents</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Torrents not transfering any data do not count towards download/seeding active count.</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_auto_manage_prefer_seeds">
+ <property name="label" translatable="yes">Prefer seeding torrents</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Give preference to seeding torrents over downloading torrents.</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label49">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Active Torrents</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame21">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment24">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkGrid" id="table2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">10</property>
+ <child>
+ <object class="GtkLabel" id="label53">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Share Ratio:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label54">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Time Ratio:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label55">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Time (m):</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_seed_time_limit">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_seed_time_limit</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_seed_time_ratio_limit">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_time_ratio_limit</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_share_ratio_limit">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_share_ratio_limit</property>
+ <property name="digits">2</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label50">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Seeding Rotation</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="vbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_share_ratio">
+ <property name="label" translatable="yes">Share Ratio:</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_share_ratio">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">•</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_share_ratio</property>
+ <property name="digits">2</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">10</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkBox" id="vbox8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkRadioButton" id="radio_pause_ratio">
+ <property name="label" translatable="yes">Pause Torrent</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkRadioButton" id="radio_remove_ratio">
+ <property name="label" translatable="yes">Remove Torrent</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <property name="group">radio_pause_ratio</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="padding">3</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label25">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Share Ratio Reached</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label64">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label">page 10</property>
+ </object>
+ <packing>
+ <property name="position">3</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="NetworkScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkViewport" id="viewport1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkBox" id="vbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkFrame" id="frame31">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment51">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkEntry" id="entry_interface">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">The IP address of the interface to listen for incoming bittorrent connections on. Leave this empty if you want to use the default.</property>
+ <property name="max_length">15</property>
+ <property name="width_chars">15</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label110">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Incoming Address</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkBox" id="vbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkSpinButton" id="spin_incoming_port">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="max_length">5</property>
+ <property name="max_width_chars">6</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_incoming_port</property>
+ <property name="climb_rate">1</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_random_incoming_port">
+ <property name="label" translatable="yes">Random</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Uses random ports in range 49152 to 65525</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="hbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Active Port:</property>
+ <property name="justify">right</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="active_port_label">
+ <property name="width_request">50</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label">0</property>
+ <property name="width_chars">5</property>
+ <attributes>
+ <attribute name="style" value="italic"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_testport">
+ <property name="label" translatable="yes">Test Active Port</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="image_position">right</property>
+ <signal name="clicked" handler="on_test_port_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment48">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkImage" id="port_img">
+ <property name="can_focus">False</property>
+ <property name="icon_name">dialog-question-symbolic</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Incoming Port</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment17">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkEntry" id="entry_outgoing_interface">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">
+The network interface name or IP address for outgoing BitTorrent connections. (Leave empty for default.)
+ </property>
+ <property name="max_length">15</property>
+ <property name="invisible_char">●</property>
+ <property name="width_chars">15</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Outgoing Interface</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame26">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment34">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="box1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_random_outgoing_ports">
+ <property name="label" translatable="yes">Random</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Uses random ports in range 49152 to 65525</property>
+ <property name="xalign">0</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_toggle" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkLabel" id="label77">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">From:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_outgoing_port_min">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="max_length">5</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_outgoing_port_min</property>
+ <property name="climb_rate">1</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label78">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">To:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_outgoing_port_max">
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="max_length">5</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_outgoing_port_max</property>
+ <property name="climb_rate">1</property>
+ <property name="snap_to_ticks">True</property>
+ <property name="numeric">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label79">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Outgoing Ports</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkBox" id="hbox6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkGrid" id="table7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">6</property>
+ <property name="column_spacing">5</property>
+ <child>
+ <object class="GtkComboBox" id="combo_encout">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">liststore3</property>
+ <signal name="changed" handler="on_combo_encryption_changed" swapped="no"/>
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext3"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combo_encin">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">liststore1</property>
+ <signal name="changed" handler="on_combo_encryption_changed" swapped="no"/>
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext1"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Outgoing:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Incoming:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkBox" id="hbox15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Level:</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combo_enclevel">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="valign">center</property>
+ <property name="model">liststore2</property>
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext2"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Encryption</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">4</property>
+ <property name="bottom_padding">2</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkGrid" id="table8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="row_spacing">1</property>
+ <property name="column_spacing">6</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_upnp">
+ <property name="label" translatable="yes">UPnP</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Universal Plug and Play</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_natpmp">
+ <property name="label" translatable="yes">NAT-PMP</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">NAT Port Mapping Protocol</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_utpex">
+ <property name="label" translatable="yes">Peer Exchange</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Exchanges peers between clients. (Disabling requires restart)</property>
+ <property name="use_underline">True</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_lsd">
+ <property name="label" translatable="yes">LSD</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Local Service Discovery finds local peers on your network.</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_dht">
+ <property name="label" translatable="yes">DHT</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Distributed hash table may improve the amount of active connections.</property>
+ <property name="use_underline">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label_peer_tos">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Peer TOS Byte:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_peer_tos">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="max_length">4</property>
+ <property name="invisible_char">•</property>
+ <property name="width_chars">1</property>
+ <property name="text">0x00</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">2</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Network Extras</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">5</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label65">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label">page 11</property>
+ </object>
+ <packing>
+ <property name="position">4</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="ProxyScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkViewport" id="viewport10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkBox" id="vbox26">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkFrame" id="frame_proxy">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment29">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">12</property>
+ <child>
+ <object class="GtkGrid" id="table11">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label_proxy_pass">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Password:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_proxy_pass">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="visibility">False</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_proxy_host">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Hostname:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_proxy_host">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <signal name="paste-clipboard" handler="on_entry_proxy_host_paste_clipboard" swapped="no"/>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_proxy_port">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Port:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment_proxy_port">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkSpinButton" id="spin_proxy_port">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_proxy_port</property>
+ <property name="numeric">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_proxy_user">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkComboBox" id="combo_proxy_type">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="model">liststore_proxy</property>
+ <signal name="changed" handler="on_combo_proxy_type_changed" swapped="no"/>
+ <child>
+ <object class="GtkCellRendererText" id="cellrenderertext4"/>
+ <attributes>
+ <attribute name="text">0</attribute>
+ </attributes>
+ </child>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_proxy_user">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Username:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_proxy_host_resolve">
+ <property name="label" translatable="yes">Proxy Hostnames</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Hostnames should be attempted to be resolved through
+the proxy instead of using the local DNS service</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">5</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_proxy_peer_conn">
+ <property name="label" translatable="yes">Proxy Peers</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Proxy peer and web seed connections.</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">6</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_proxy_tracker_conn">
+ <property name="label" translatable="yes">Proxy Trackers</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="active">True</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">7</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label_proxy">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Proxy</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame_anon_mode">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkBox" id="vbox9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkAlignment" id="alignment_force_proxy">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">12</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_force_proxy">
+ <property name="label" translatable="yes">Force Proxy Use</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkAlignment" id="alignment_anon_mode">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">12</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_anonymous_mode">
+ <property name="label" translatable="yes">Hide Client Identity</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="tooltip_text" translatable="yes">Attempt to hide client identity and only use proxy for incoming connections.</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label_force_proxy">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Force Proxy</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label71">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label">page 11</property>
+ </object>
+ <packing>
+ <property name="position">5</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="CacheScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkViewport" id="viewport13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkBox" id="vbox31">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkFrame" id="frame32">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment53">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkGrid" id="table14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label114">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Cache Size (16 KiB blocks):</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label115">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text" translatable="yes">The number of seconds from the last cached write to a piece in the write cache, to when it's forcefully flushed to disk. Default is 60 seconds.</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Cache Expiry (seconds):</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_cache_size">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="invisible_char">●</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_cache_size</property>
+ <property name="numeric">True</property>
+ <property name="update_policy">if-valid</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_cache_expiry">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="max_length">5</property>
+ <property name="invisible_char">●</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_cache_expiry</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label112">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Settings</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame33">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment54">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="vbox32">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkFrame" id="frame34">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment55">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkGrid" id="table18">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label116">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">The total number of 16 KiB blocks written to disk since this session was started.</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Blocks Written:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label120">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">The total number of write operations performed since this session was started.</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Writes:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label124">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">The ratio (blocks_written - writes) / blocks_written represents the number of saved write operations per total write operations, i.e. a kind of cache hit ratio for the write cache.</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Write Cache Hit Ratio:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_cache_num_blocks_written">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_cache_write_ops">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_cache_write_hit_ratio">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label132">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Write</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame35">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment56">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkGrid" id="table19">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label118">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">The number of blocks that were requested from the bittorrent engine (from peers), that were served from disk or cache.</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Blocks Read:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label119">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">The number of blocks that were served from cache.</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Blocks Read Hit:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label122">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">The cache hit ratio for the read cache.</property>
+ <property name="label" translatable="yes">Read Cache Hit Ratio:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_cache_num_blocks_read">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_cache_num_blocks_cache_hits">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_cache_read_hit_ratio">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label121">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="has_tooltip">True</property>
+ <property name="tooltip_text" translatable="yes">The total number of read operations performed since this session was started.</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Reads:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_cache_read_ops">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label133">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Read</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame36">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment57">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkGrid" id="table20">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label123">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">The number of 16 KiB blocks currently in the disk cache. This includes both read and write cache.</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Cache Size:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label117">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Read Cache Size:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_cache_disk_blocks_in_use">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_cache_read_cache_blocks">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label134">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Size</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox" id="hbuttonbox3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">start</property>
+ <child>
+ <object class="GtkButton" id="button_cache_refresh">
+ <property name="label" translatable="yes">_Refresh</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_cache_refresh_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label113">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Status</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">6</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <object class="GtkLabel" id="label72">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label">page 12</property>
+ </object>
+ <packing>
+ <property name="position">6</property>
+ <property name="tab_fill">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="OtherScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkViewport" id="viewport8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkBox" id="vbox16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkFrame" id="frame20">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment37">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">40</property>
+ <child>
+ <object class="GtkBox" id="vbox19">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkLabel" id="label44">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Help us improve Deluge by sending us your Python version, PyGTK version, OS and processor types. Absolutely no other information is sent.</property>
+ <property name="wrap">True</property>
+ <property name="xalign">0</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">2</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_send_info">
+ <property name="label" translatable="yes">Yes, please send anonymous statistics</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label45">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">System Information</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame30">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment49">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="box20">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label109">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Location:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkEntry" id="entry_geoip">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="tooltip_text" translatable="yes">If Deluge cannot find the database file at this location it will fallback to using DNS to resolve the peer's country.</property>
+ <property name="invisible_char">●</property>
+ <property name="truncate_multiline">True</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label108">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">GeoIP Database</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame17">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment25">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkButtonBox" id="hbuttonbox4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">start</property>
+ <child>
+ <object class="GtkButton" id="button_associate_magnet">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_button_associate_magnet_clicked" swapped="no"/>
+ <child>
+ <object class="GtkBox" id="hbox16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">2</property>
+ <child>
+ <object class="GtkImage" id="image_magnet">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">image-missing</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label60">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Associate with Deluge</property>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Magnet Links</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">7</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="DaemonScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkViewport" id="viewport7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkBox" id="vbox13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkFrame" id="frame14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment20">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">2</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkBox" id="vbox14">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkBox" id="hbox12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label41">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Daemon port:</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkSpinButton" id="spin_daemon_port">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="max_width_chars">6</property>
+ <property name="primary_icon_activatable">False</property>
+ <property name="secondary_icon_activatable">False</property>
+ <property name="adjustment">adjustment_spin_daemon_port</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label35">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Port</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame15">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment21">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_allow_remote_connections">
+ <property name="label" translatable="yes">Allow Remote Connections</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label39">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Connections</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="frame22">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment27">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="top_padding">5</property>
+ <property name="left_padding">10</property>
+ <child>
+ <object class="GtkCheckButton" id="chk_new_releases">
+ <property name="label" translatable="yes">Periodically check the website for new releases</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label56">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Other</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">5</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkFrame" id="AccountsFrame">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment33">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <property name="right_padding">20</property>
+ <child>
+ <object class="GtkBox" id="hbox22">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkButtonBox" id="vbuttonbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">5</property>
+ <property name="homogeneous">True</property>
+ <property name="layout_style">start</property>
+ <child>
+ <object class="GtkButton" id="accounts_add">
+ <property name="label" translatable="yes">_Add</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_accounts_add_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="accounts_edit">
+ <property name="label" translatable="yes">_Edit</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_accounts_edit_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="accounts_delete">
+ <property name="label" translatable="yes">_Delete</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_accounts_delete_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="padding">4</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <property name="min_content_height">150</property>
+ <child>
+ <object class="GtkTreeView" id="accounts_listview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeselection2"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label61">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Accounts</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">8</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="PluginsScrolledWindow">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkViewport" id="viewport5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkBox" id="vbox28">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkPaned" id="vpaned1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="orientation">vertical</property>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow12">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="plugin_listview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeselection3"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">False</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow11">
+ <property name="height_request">115</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <child>
+ <object class="GtkViewport" id="viewport12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="resize_mode">queue</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkFrame" id="frame8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label_xalign">0</property>
+ <property name="shadow_type">none</property>
+ <child>
+ <object class="GtkAlignment" id="alignment26">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="left_padding">12</property>
+ <child>
+ <object class="GtkGrid" id="table10">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="column_spacing">5</property>
+ <child>
+ <object class="GtkLabel" id="label_plugin_details">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_plugin_version">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_plugin_author">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label85">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Details:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label84">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Version:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label82">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Author:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label86">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Homepage:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label87">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ <property name="label" translatable="yes">Author Email:</property>
+ </object>
+ <packing>
+ <property name="left_attach">0</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_plugin_homepage">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">3</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_plugin_email">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="halign">start</property>
+ </object>
+ <packing>
+ <property name="left_attach">1</property>
+ <property name="top_attach">2</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ <child type="label">
+ <object class="GtkLabel" id="label81">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Info</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ </attributes>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">False</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">center</property>
+ <child>
+ <object class="GtkButton" id="button_plugin_install">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_button_plugin_install_clicked" swapped="no"/>
+ <child>
+ <object class="GtkLabel" id="label28">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Install</property>
+ <property name="use_markup">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_rescan_plugins">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_button_rescan_plugins_clicked" swapped="no"/>
+ <child>
+ <object class="GtkLabel" id="label30">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Refresh</property>
+ <property name="use_markup">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_find_plugins">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <signal name="clicked" handler="on_button_find_plugins_clicked" swapped="no"/>
+ <child>
+ <object class="GtkLabel" id="label107">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Find More...</property>
+ <property name="use_markup">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="position">9</property>
+ </packing>
+ </child>
+ <child type="tab">
+ <placeholder/>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child type="tab">
+ <placeholder/>
+ </child>
+ </object>
+ <packing>
+ <property name="resize">True</property>
+ <property name="shrink">True</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button_cancel</action-widget>
+ <action-widget response="0">button_apply</action-widget>
+ <action-widget response="0">button_ok</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/queuedtorrents.ui b/deluge/ui/gtk3/glade/queuedtorrents.ui
new file mode 100644
index 0000000..528ef64
--- /dev/null
+++ b/deluge/ui/gtk3/glade/queuedtorrents.ui
@@ -0,0 +1,211 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkDialog" id="queued_torrents_dialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Queued Torrents</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="default_width">450</property>
+ <property name="default_height">300</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="button_close">
+ <property name="label" translatable="yes">_Close</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_close_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_add">
+ <property name="label" translatable="yes">_Add</property>
+ <property name="visible">True</property>
+ <property name="sensitive">False</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="is_focus">True</property>
+ <property name="can_default">True</property>
+ <property name="has_default">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_add_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">5</property>
+ <child>
+ <object class="GtkBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="spacing">10</property>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">list-add-symbolic</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Add Queued Torrents</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.2"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkScrolledWindow" id="scrolledwindow1">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="shadow_type">in</property>
+ <child>
+ <object class="GtkTreeView" id="treeview">
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="headers_visible">False</property>
+ <child internal-child="selection">
+ <object class="GtkTreeSelection" id="treeselection"/>
+ </child>
+ </object>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButtonBox" id="hbuttonbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">start</property>
+ <child>
+ <object class="GtkButton" id="button_remove">
+ <property name="label" translatable="yes">_Remove</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_remove_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="button_clear">
+ <property name="label" translatable="yes">_Clear</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ <signal name="clicked" handler="on_button_clear_clicked" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkCheckButton" id="chk_autoadd">
+ <property name="label" translatable="yes">Automatically add torrents on connect</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">False</property>
+ <property name="halign">start</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_chk_autoadd_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">3</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="0">button_close</action-widget>
+ <action-widget response="0">button_add</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/remove_torrent_dialog.ui b/deluge/ui/gtk3/glade/remove_torrent_dialog.ui
new file mode 100644
index 0000000..4a92037
--- /dev/null
+++ b/deluge/ui/gtk3/glade/remove_torrent_dialog.ui
@@ -0,0 +1,187 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkDialog" id="remove_torrent_dialog">
+ <property name="can_focus">False</property>
+ <property name="border_width">5</property>
+ <property name="title" translatable="yes">Remove Torrent</property>
+ <property name="resizable">False</property>
+ <property name="modal">True</property>
+ <property name="window_position">center-on-parent</property>
+ <property name="destroy_with_parent">True</property>
+ <property name="type_hint">dialog</property>
+ <child>
+ <placeholder/>
+ </child>
+ <child internal-child="vbox">
+ <object class="GtkBox" id="dialog-vbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="orientation">vertical</property>
+ <property name="spacing">2</property>
+ <child internal-child="action_area">
+ <object class="GtkButtonBox" id="dialog-action_area1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="layout_style">end</property>
+ <child>
+ <object class="GtkButton" id="cancel_button">
+ <property name="label" translatable="yes">_Cancel</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="has_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkButton" id="ok_button">
+ <property name="label" translatable="yes">_OK</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="receives_default">True</property>
+ <property name="use_underline">True</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="pack_type">end</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">dialog-warning-symbolic</property>
+ <property name="icon_size">6</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_title">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Remove the selected torrent(s)?</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="1.3"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">True</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="label_torrents">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="ellipsize">end</property>
+ <attributes>
+ <attribute name="style" value="italic"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">True</property>
+ <property name="fill">False</property>
+ <property name="position">2</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ <child>
+ <object class="GtkSeparator" id="hseparator2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">True</property>
+ <property name="position">4</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkBox" id="hbox2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkCheckButton" id="delete_files">
+ <property name="label" translatable="yes">Include downloaded files</property>
+ <property name="visible">True</property>
+ <property name="can_focus">True</property>
+ <property name="focus_on_click">False</property>
+ <property name="receives_default">False</property>
+ <property name="draw_indicator">True</property>
+ <signal name="toggled" handler="on_delete_files_toggled" swapped="no"/>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">0</property>
+ </packing>
+ </child>
+ <child>
+ <object class="GtkLabel" id="warning_label">
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">(This is permanent!)</property>
+ <attributes>
+ <attribute name="weight" value="bold"/>
+ <attribute name="scale" value="0.90000000000000002"/>
+ </attributes>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="position">1</property>
+ </packing>
+ </child>
+ </object>
+ <packing>
+ <property name="expand">False</property>
+ <property name="fill">False</property>
+ <property name="padding">1</property>
+ <property name="position">5</property>
+ </packing>
+ </child>
+ <child>
+ <placeholder/>
+ </child>
+ </object>
+ </child>
+ <action-widgets>
+ <action-widget response="-6">cancel_button</action-widget>
+ <action-widget response="-5">ok_button</action-widget>
+ </action-widgets>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/torrent_menu.options.ui b/deluge/ui/gtk3/glade/torrent_menu.options.ui
new file mode 100644
index 0000000..df34445
--- /dev/null
+++ b/deluge/ui/gtk3/glade/torrent_menu.options.ui
@@ -0,0 +1,104 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkImage" id="download-limit-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">deluge-downloading</property>
+ </object>
+ <object class="GtkImage" id="max-connections-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">network-transmit-receive-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="upload-limit-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">deluge-seeding</property>
+ </object>
+ <object class="GtkImage" id="upload-slots-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">view-sort-descending-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkMenu" id="options_torrent_menu">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_down_speed">
+ <property name="label" translatable="yes">_Download Speed Limit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">download-limit-image</property>
+ <property name="use_stock">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_up_speed">
+ <property name="label" translatable="yes">_Upload Speed Limit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">upload-limit-image</property>
+ <property name="use_stock">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_max_connections">
+ <property name="label" translatable="yes">_Connection Limit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">max-connections-image</property>
+ <property name="use_stock">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_upload_slots">
+ <property name="label" translatable="yes">Upload _Slot Limit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">upload-slots-image</property>
+ <property name="use_stock">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem_stop_seed_at_ratio">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">Stop seed at _ratio</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem_auto_managed">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Auto Managed</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem_super_seeding">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_action_appearance">False</property>
+ <property name="label" translatable="yes">_Super Seeding</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkMenuItem" id="menuitem_change_owner">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Change Ownership</property>
+ <property name="use_underline">True</property>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/torrent_menu.queue.ui b/deluge/ui/gtk3/glade/torrent_menu.queue.ui
new file mode 100644
index 0000000..a120af2
--- /dev/null
+++ b/deluge/ui/gtk3/glade/torrent_menu.queue.ui
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkImage" id="image5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">go-top-symbolic</property>
+ </object>
+ <object class="GtkImage" id="image6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">go-up-symbolic</property>
+ </object>
+ <object class="GtkImage" id="image7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">go-down-symbolic</property>
+ </object>
+ <object class="GtkImage" id="image8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">go-bottom-symbolic</property>
+ </object>
+ <object class="GtkMenu" id="queue_torrent_menu">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_queue_top">
+ <property name="label" translatable="yes">Top</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="image">image5</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_queue_top_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_queue_up">
+ <property name="label" translatable="yes">Up</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="image">image6</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_queue_up_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_queue_down">
+ <property name="label" translatable="yes">Down</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="image">image7</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_queue_down_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_queue_bottom">
+ <property name="label" translatable="yes">Bottom</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="image">image8</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_queue_bottom_activate" swapped="no"/>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/torrent_menu.ui b/deluge/ui/gtk3/glade/torrent_menu.ui
new file mode 100644
index 0000000..c1b77b4
--- /dev/null
+++ b/deluge/ui/gtk3/glade/torrent_menu.ui
@@ -0,0 +1,225 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkAccelGroup" id="accelgroup1"/>
+ <object class="GtkImage" id="image1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">preferences-system-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="menu-item-image1">
+ <property name="can_focus">False</property>
+ <property name="icon_name">media-playback-stop-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="menu-item-image12">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-edit-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="menu-item-image13">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">media-playback-start-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="menu-item-image14">
+ <property name="can_focus">False</property>
+ <property name="icon_name">media-playback-pause-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="menu-item-image19">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">media-playlist-shuffle-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="menu-item-image2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">media-playlist-repeat-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="menu-item-image5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">view-refresh-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="menu-item-image6">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">edit-redo-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="menu-item-image7">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">folder-open-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="menu-item-image8">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">document-save-as-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="menu-item-image9">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">list-remove-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkMenu" id="torrent_menu">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_open_folder">
+ <property name="label" translatable="yes">_Open Download Folder</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">menu-item-image7</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_open_folder_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separator4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_pause">
+ <property name="label" translatable="yes">_Pause</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">menu-item-image14</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_pause_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_resume">
+ <property name="label" translatable="yes">Resu_me</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="tooltip_text" translatable="yes">Resume selected torrents.</property>
+ <property name="use_underline">True</property>
+ <property name="image">menu-item-image13</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_resume_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separator2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_options">
+ <property name="label" translatable="yes">Opt_ions</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">image1</property>
+ <property name="use_stock">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separator_menuitem16">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_queue">
+ <property name="label" translatable="yes">_Queue</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">menu-item-image19</property>
+ <property name="use_stock">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="menuitem1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_updatetracker">
+ <property name="label" translatable="yes">_Update Tracker</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">menu-item-image5</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_updatetracker_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_edittrackers">
+ <property name="label" translatable="yes">_Edit Trackers</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">menu-item-image12</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_edittrackers_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separator1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_remove">
+ <property name="label" translatable="yes">_Remove Torrent</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">menu-item-image9</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_remove_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separator3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_recheck">
+ <property name="label" translatable="yes">_Force Re-check</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">menu-item-image6</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_recheck_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_move">
+ <property name="label" translatable="yes">_Move Download Folder</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">menu-item-image8</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_move_activate" swapped="no"/>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/glade/tray_menu.ui b/deluge/ui/gtk3/glade/tray_menu.ui
new file mode 100644
index 0000000..060b7a0
--- /dev/null
+++ b/deluge/ui/gtk3/glade/tray_menu.ui
@@ -0,0 +1,167 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Generated with glade 3.22.1 -->
+<interface>
+ <requires lib="gtk+" version="3.0"/>
+ <object class="GtkAccelGroup" id="accelgroup1"/>
+ <object class="GtkImage" id="add-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">list-add-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="download-limit-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="pause-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">media-playback-stop-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="quit-daemon-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">system-shutdown-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="quit-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">application-exit-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="resume-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_name">media-playlist-repeat-symbolic</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkImage" id="upload-limit-image">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="icon_size">1</property>
+ </object>
+ <object class="GtkMenu" id="tray_menu">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <child>
+ <object class="GtkCheckMenuItem" id="menuitem_show_deluge">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="label" translatable="yes">_Show Deluge</property>
+ <property name="use_underline">True</property>
+ <signal name="activate" handler="on_menuitem_show_deluge_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem1">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_add_torrent">
+ <property name="label" translatable="yes">_Add Torrent</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">add-image</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_add_torrent_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem2">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_pause_session">
+ <property name="label" translatable="yes">_Pause Session</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">pause-image</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_pause_session_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_resume_session">
+ <property name="label" translatable="yes">_Resume Session</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">resume-image</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_resume_session_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem3">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_download_limit">
+ <property name="label" translatable="yes">_Download Speed Limit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">download-limit-image</property>
+ <property name="use_stock">False</property>
+ <property name="always_show_image">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_upload_limit">
+ <property name="label" translatable="yes">_Upload Speed Limit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">upload-limit-image</property>
+ <property name="use_stock">False</property>
+ <property name="always_show_image">True</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem4">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_quitdaemon">
+ <property name="label" translatable="yes">Quit &amp; Shutdown Daemon</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">quit-daemon-image</property>
+ <property name="use_stock">False</property>
+ <signal name="activate" handler="on_menuitem_quitdaemon_activate" swapped="no"/>
+ </object>
+ </child>
+ <child>
+ <object class="GtkSeparatorMenuItem" id="separatormenuitem5">
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ </object>
+ </child>
+ <child>
+ <object class="GtkImageMenuItem" id="menuitem_quit">
+ <property name="label" translatable="yes">_Quit</property>
+ <property name="visible">True</property>
+ <property name="can_focus">False</property>
+ <property name="use_underline">True</property>
+ <property name="image">quit-image</property>
+ <property name="use_stock">False</property>
+ <property name="accel_group">accelgroup1</property>
+ <signal name="activate" handler="on_menuitem_quit_activate" swapped="no"/>
+ </object>
+ </child>
+ </object>
+</interface>
diff --git a/deluge/ui/gtk3/gtkui.py b/deluge/ui/gtk3/gtkui.py
new file mode 100644
index 0000000..d93bd2e
--- /dev/null
+++ b/deluge/ui/gtk3/gtkui.py
@@ -0,0 +1,391 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+# pylint: disable=wrong-import-position
+
+from __future__ import division, unicode_literals
+
+import logging
+import os
+import signal
+import sys
+import time
+
+import gi # isort:skip (Required before Gtk import).
+
+gi.require_version('Gtk', '3.0') # NOQA: E402
+gi.require_version('Gdk', '3.0') # NOQA: E402
+
+# isort:imports-thirdparty
+from gi.repository.GLib import set_prgname
+from gi.repository.Gtk import Builder, ResponseType
+from twisted.internet import defer, gtk3reactor
+from twisted.internet.error import ReactorAlreadyInstalledError
+from twisted.internet.task import LoopingCall
+
+try:
+ # Install twisted reactor, before any other modules import reactor.
+ reactor = gtk3reactor.install()
+except ReactorAlreadyInstalledError:
+ # Running unit tests so trial already installed a rector
+ from twisted.internet import reactor
+
+# isort:imports-firstparty
+import deluge.component as component
+from deluge.common import (
+ fsize,
+ fspeed,
+ get_default_download_dir,
+ osx_check,
+ windows_check,
+)
+from deluge.configmanager import ConfigManager, get_config_dir
+from deluge.error import DaemonRunningError
+from deluge.i18n import I18N_DOMAIN, set_language, setup_translation
+from deluge.ui.client import client
+from deluge.ui.hostlist import LOCALHOST
+from deluge.ui.sessionproxy import SessionProxy
+from deluge.ui.tracker_icons import TrackerIcons
+
+# isort:imports-localfolder
+from .addtorrentdialog import AddTorrentDialog
+from .common import associate_magnet_links, windowing
+from .connectionmanager import ConnectionManager
+from .dialogs import YesNoDialog
+from .filtertreeview import FilterTreeView
+from .ipcinterface import IPCInterface, process_args
+from .mainwindow import MainWindow
+from .menubar import MenuBar
+from .pluginmanager import PluginManager
+from .preferences import Preferences
+from .queuedtorrents import QueuedTorrents
+from .sidebar import SideBar
+from .statusbar import StatusBar
+from .systemtray import SystemTray
+from .toolbar import ToolBar
+from .torrentdetails import TorrentDetails
+from .torrentview import TorrentView
+
+set_prgname('deluge')
+log = logging.getLogger(__name__)
+
+try:
+ from setproctitle import setproctitle, getproctitle
+except ImportError:
+
+ def setproctitle(title):
+ return
+
+ def getproctitle():
+ return
+
+
+DEFAULT_PREFS = {
+ 'standalone': True,
+ 'interactive_add': True,
+ 'focus_add_dialog': True,
+ 'enable_system_tray': True,
+ 'close_to_tray': False,
+ 'start_in_tray': False,
+ 'enable_appindicator': False,
+ 'lock_tray': False,
+ 'tray_password': '',
+ 'check_new_releases': True,
+ 'default_load_path': None,
+ 'window_maximized': False,
+ 'window_x_pos': 0,
+ 'window_y_pos': 0,
+ 'window_width': 640,
+ 'window_height': 480,
+ 'pref_dialog_width': None,
+ 'pref_dialog_height': None,
+ 'edit_trackers_dialog_width': None,
+ 'edit_trackers_dialog_height': None,
+ 'tray_download_speed_list': [5.0, 10.0, 30.0, 80.0, 300.0],
+ 'tray_upload_speed_list': [5.0, 10.0, 30.0, 80.0, 300.0],
+ 'connection_limit_list': [50, 100, 200, 300, 500],
+ 'enabled_plugins': [],
+ 'show_connection_manager_on_start': True,
+ 'autoconnect': False,
+ 'autoconnect_host_id': None,
+ 'autostart_localhost': False,
+ 'autoadd_queued': False,
+ 'choose_directory_dialog_path': get_default_download_dir(),
+ 'show_new_releases': True,
+ 'show_sidebar': True,
+ 'show_toolbar': True,
+ 'show_statusbar': True,
+ 'show_tabsbar': True,
+ 'tabsbar_position': 235,
+ 'sidebar_show_zero': False,
+ 'sidebar_show_trackers': True,
+ 'sidebar_show_owners': True,
+ 'sidebar_position': 170,
+ 'show_rate_in_title': False,
+ 'createtorrent.trackers': [],
+ 'show_piecesbar': False,
+ 'pieces_color_missing': [65535, 0, 0],
+ 'pieces_color_waiting': [4874, 56494, 0],
+ 'pieces_color_downloading': [65535, 55255, 0],
+ 'pieces_color_completed': [4883, 26985, 56540],
+ 'focus_main_window_on_add': True,
+ 'language': None,
+}
+
+
+class GtkUI(object):
+ def __init__(self, args):
+ # Setup gtkbuilder/glade translation
+ setup_translation()
+ Builder().set_translation_domain(I18N_DOMAIN)
+
+ # Setup signals
+ def on_die(*args):
+ log.debug('OS signal "die" caught with args: %s', args)
+ reactor.stop()
+
+ self.osxapp = None
+ if windows_check():
+ from win32api import SetConsoleCtrlHandler
+
+ SetConsoleCtrlHandler(on_die, True)
+ log.debug('Win32 "die" handler registered')
+ elif osx_check() and windowing('quartz'):
+ try:
+ import gtkosx_application
+ except ImportError:
+ pass
+ else:
+ self.osxapp = gtkosx_application.gtkosx_application_get()
+ self.osxapp.connect('NSApplicationWillTerminate', on_die)
+ log.debug('OSX quartz "die" handler registered')
+
+ # Set process name again to fix gtk issue
+ setproctitle(getproctitle())
+
+ # Attempt to register a magnet URI handler with gconf, but do not overwrite
+ # if already set by another program.
+ associate_magnet_links(False)
+
+ # Make sure gtk3ui.conf has at least the defaults set
+ self.config = ConfigManager('gtk3ui.conf', DEFAULT_PREFS)
+
+ # Make sure the gtkui state folder has been created
+ if not os.path.exists(os.path.join(get_config_dir(), 'gtk3ui_state')):
+ os.makedirs(os.path.join(get_config_dir(), 'gtk3ui_state'))
+
+ # Set language
+ if self.config['language'] is not None:
+ set_language(self.config['language'])
+
+ # Start the IPC Interface before anything else.. Just in case we are
+ # already running.
+ self.queuedtorrents = QueuedTorrents()
+ self.ipcinterface = IPCInterface(args.torrents)
+
+ # We make sure that the UI components start once we get a core URI
+ client.set_disconnect_callback(self.__on_disconnect)
+
+ self.trackericons = TrackerIcons()
+ self.sessionproxy = SessionProxy()
+ # Initialize various components of the gtkui
+ self.mainwindow = MainWindow()
+ self.menubar = MenuBar()
+ self.toolbar = ToolBar()
+ self.torrentview = TorrentView()
+ self.torrentdetails = TorrentDetails()
+ self.sidebar = SideBar()
+ self.filtertreeview = FilterTreeView()
+ self.preferences = Preferences()
+ self.systemtray = SystemTray()
+ self.statusbar = StatusBar()
+ self.addtorrentdialog = AddTorrentDialog()
+
+ if self.osxapp:
+
+ def nsapp_open_file(osxapp, filename):
+ # Ignore command name which is raised at app launch (python opening main script).
+ if filename == sys.argv[0]:
+ return True
+ process_args([filename])
+
+ self.osxapp.connect('NSApplicationOpenFile', nsapp_open_file)
+ from .menubar_osx import menubar_osx
+
+ menubar_osx(self, self.osxapp)
+ self.osxapp.ready()
+
+ # Initalize the plugins
+ self.plugins = PluginManager()
+
+ # Show the connection manager
+ self.connectionmanager = ConnectionManager()
+
+ # Setup RPC stats logging
+ # daemon_bps: time, bytes_sent, bytes_recv
+ self.daemon_bps = (0, 0, 0)
+ self.rpc_stats = LoopingCall(self.log_rpc_stats)
+ self.closing = False
+
+ # Twisted catches signals to terminate, so have it call a pre_shutdown method.
+ reactor.addSystemEventTrigger('before', 'gtkui_close', self.close)
+
+ def gtkui_sigint_handler(num, frame):
+ log.debug('SIGINT signal caught, firing event: gtkui_close')
+ reactor.callLater(0, reactor.fireSystemEvent, 'gtkui_close')
+
+ signal.signal(signal.SIGINT, gtkui_sigint_handler)
+
+ def start(self):
+ reactor.callWhenRunning(self._on_reactor_start)
+ reactor.run()
+ # Reactor is not running. Any async callbacks (Deferreds) can no longer
+ # be processed from this point on.
+
+ def shutdown(self, *args, **kwargs):
+ log.debug('GTKUI shutting down...')
+ # Shutdown all components
+ if client.is_standalone:
+ return component.shutdown()
+
+ @defer.inlineCallbacks
+ def close(self):
+ if self.closing:
+ return
+ self.closing = True
+ # Make sure the config is saved.
+ self.config.save()
+ # Ensure columns state is saved
+ self.torrentview.save_state()
+ # Shut down components
+ yield self.shutdown()
+
+ # The gtk modal dialogs (e.g. Preferences) can prevent the application
+ # quitting, so force exiting by destroying MainWindow. Must be done here
+ # to avoid hanging when quitting with SIGINT (CTRL-C).
+ self.mainwindow.window.destroy()
+
+ reactor.stop()
+
+ # Restart the application after closing if MainWindow restart attribute set.
+ if component.get('MainWindow').restart:
+ os.execv(sys.argv[0], sys.argv)
+
+ def log_rpc_stats(self):
+ """Log RPC statistics for thinclient mode."""
+ if not client.connected():
+ return
+
+ t = time.time()
+ recv = client.get_bytes_recv()
+ sent = client.get_bytes_sent()
+ delta_time = t - self.daemon_bps[0]
+ delta_sent = sent - self.daemon_bps[1]
+ delta_recv = recv - self.daemon_bps[2]
+ self.daemon_bps = (t, sent, recv)
+ sent_rate = fspeed(delta_sent / delta_time)
+ recv_rate = fspeed(delta_recv / delta_time)
+ log.debug(
+ 'RPC: Sent %s (%s) Recv %s (%s)',
+ fsize(sent),
+ sent_rate,
+ fsize(recv),
+ recv_rate,
+ )
+
+ def _on_reactor_start(self):
+ log.debug('_on_reactor_start')
+ self.mainwindow.first_show()
+
+ if not self.config['standalone']:
+ return self._start_thinclient()
+
+ err_msg = ''
+ try:
+ client.start_standalone()
+ except DaemonRunningError:
+ err_msg = _(
+ 'A Deluge daemon (deluged) is already running.\n'
+ 'To use Standalone mode, stop local daemon and restart Deluge.'
+ )
+ except ImportError as ex:
+ if 'No module named libtorrent' in str(ex):
+ err_msg = _(
+ 'Only Thin Client mode is available because libtorrent is not installed.\n'
+ 'To use Standalone mode, please install libtorrent package.'
+ )
+ else:
+ log.exception(ex)
+ err_msg = _(
+ 'Only Thin Client mode is available due to unknown Import Error.\n'
+ 'To use Standalone mode, please see logs for error details.'
+ )
+ except Exception as ex:
+ log.exception(ex)
+ err_msg = _(
+ 'Only Thin Client mode is available due to unknown Import Error.\n'
+ 'To use Standalone mode, please see logs for error details.'
+ )
+ else:
+ component.start()
+ return
+
+ def on_dialog_response(response):
+ """User response to switching mode dialog."""
+ if response == ResponseType.YES:
+ # Turning off standalone
+ self.config['standalone'] = False
+ self._start_thinclient()
+ else:
+ # User want keep Standalone Mode so just quit.
+ self.mainwindow.quit()
+
+ # An error occurred so ask user to switch from Standalone to Thin Client mode.
+ err_msg += '\n\n' + _('Continue in Thin Client mode?')
+ d = YesNoDialog(_('Change User Interface Mode'), err_msg).run()
+ d.addCallback(on_dialog_response)
+
+ def _start_thinclient(self):
+ """Start the gtkui in thinclient mode"""
+ if log.isEnabledFor(logging.DEBUG):
+ self.rpc_stats.start(10)
+
+ # Check to see if we need to start the localhost daemon
+ if self.config['autostart_localhost']:
+
+ def on_localhost_status(status_info, port):
+ if status_info[1] == 'Offline':
+ log.debug('Autostarting localhost: %s', host_config[0:3])
+ self.connectionmanager.start_daemon(port, get_config_dir())
+
+ for host_config in self.connectionmanager.hostlist.config['hosts']:
+ if host_config[1] in LOCALHOST:
+ d = self.connectionmanager.hostlist.get_host_status(host_config[0])
+ d.addCallback(on_localhost_status, host_config[2])
+ break
+
+ # Autoconnect to a host
+ if self.config['autoconnect']:
+ for host_config in self.connectionmanager.hostlist.config['hosts']:
+ host_id, host, port, user, __ = host_config
+ if host_id == self.config['autoconnect_host_id']:
+ log.debug('Trying to connect to %s@%s:%s', user, host, port)
+ self.connectionmanager._connect(host_id, try_counter=6)
+ break
+
+ if self.config['show_connection_manager_on_start']:
+ # Dialog is blocking so call last.
+ self.connectionmanager.show()
+
+ def __on_disconnect(self):
+ """
+ Called when disconnected from the daemon. We basically just stop all
+ the components here.
+ """
+ self.daemon_bps = (0, 0, 0)
+ component.stop()
diff --git a/deluge/ui/gtk3/ipcinterface.py b/deluge/ui/gtk3/ipcinterface.py
new file mode 100644
index 0000000..dc51a87
--- /dev/null
+++ b/deluge/ui/gtk3/ipcinterface.py
@@ -0,0 +1,230 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008-2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os
+import sys
+from base64 import b64encode
+from glob import glob
+from tempfile import mkstemp
+
+import rencode
+import twisted.internet.error
+from twisted.internet import reactor
+from twisted.internet.protocol import ClientFactory, Factory, Protocol, connectionDone
+
+import deluge.component as component
+from deluge.common import decode_bytes, is_magnet, is_url, windows_check
+from deluge.configmanager import ConfigManager, get_config_dir
+from deluge.ui.client import client
+
+try:
+ from urllib.parse import urlparse
+ from urllib.request import url2pathname
+except ImportError:
+ # PY2 fallback
+ from urlparse import urlparse # pylint: disable=ungrouped-imports
+ from urllib import url2pathname # pylint: disable=ungrouped-imports
+
+log = logging.getLogger(__name__)
+
+
+class IPCProtocolServer(Protocol):
+ def __init__(self):
+ pass
+
+ def dataReceived(self, data): # NOQA: N802
+ config = ConfigManager('gtk3ui.conf')
+ data = rencode.loads(data, decode_utf8=True)
+ if not data or config['focus_main_window_on_add']:
+ component.get('MainWindow').present()
+ process_args(data)
+
+
+class IPCProtocolClient(Protocol):
+ def __init__(self):
+ pass
+
+ def connectionMade(self): # NOQA: N802
+ self.transport.write(rencode.dumps(self.factory.args))
+ self.transport.loseConnection()
+
+ def connectionLost(self, reason=connectionDone): # NOQA: N802
+ reactor.stop()
+ self.factory.stop = True
+
+
+class IPCClientFactory(ClientFactory):
+ protocol = IPCProtocolClient
+
+ def __init__(self):
+ self.stop = False
+
+ def clientConnectionFailed(self, connector, reason): # NOQA: N802
+ log.warning('Connection to running instance failed.')
+ reactor.stop()
+
+
+class IPCInterface(component.Component):
+ def __init__(self, args):
+ component.Component.__init__(self, 'IPCInterface')
+ self.listener = None
+ ipc_dir = get_config_dir('ipc')
+ if not os.path.exists(ipc_dir):
+ os.makedirs(ipc_dir)
+ socket = os.path.join(ipc_dir, 'deluge-gtk')
+ if windows_check():
+ # If we're on windows we need to check the global mutex to see if deluge is
+ # already running.
+ import win32event
+ import win32api
+ import winerror
+
+ self.mutex = win32event.CreateMutex(None, False, 'deluge')
+ if win32api.GetLastError() != winerror.ERROR_ALREADY_EXISTS:
+ # Create listen socket
+ self.factory = Factory()
+ self.factory.protocol = IPCProtocolServer
+ import random
+
+ port = random.randrange(20000, 65535)
+ self.listener = reactor.listenTCP(port, self.factory)
+ # Store the port number in the socket file
+ with open(socket, 'w') as _file:
+ _file.write(str(port))
+ # We need to process any args when starting this process
+ process_args(args)
+ else:
+ # Send to existing deluge process
+ with open(socket) as _file:
+ port = int(_file.readline())
+ self.factory = ClientFactory()
+ self.factory.args = args
+ self.factory.protocol = IPCProtocolClient
+ reactor.connectTCP('127.0.0.1', port, self.factory)
+ reactor.run()
+ sys.exit(0)
+ else:
+ # Find and remove any restart tempfiles
+ restart_tempfile = glob(os.path.join(ipc_dir, 'restart.*'))
+ for f in restart_tempfile:
+ os.remove(f)
+ lockfile = socket + '.lock'
+ log.debug('Checking if lockfile exists: %s', lockfile)
+ if os.path.lexists(lockfile):
+
+ def delete_lockfile():
+ log.debug('Delete stale lockfile.')
+ try:
+ os.remove(lockfile)
+ os.remove(socket)
+ except OSError as ex:
+ log.error('Failed to delete lockfile: %s', ex)
+
+ try:
+ os.kill(int(os.readlink(lockfile)), 0)
+ except OSError:
+ delete_lockfile()
+ else:
+ if restart_tempfile:
+ log.warning(
+ 'Found running PID but it is not a Deluge process, removing lockfile...'
+ )
+ delete_lockfile()
+ try:
+ self.factory = Factory()
+ self.factory.protocol = IPCProtocolServer
+ self.listener = reactor.listenUNIX(socket, self.factory, wantPID=True)
+ except twisted.internet.error.CannotListenError as ex:
+ log.info(
+ 'Deluge is already running! Sending arguments to running instance...'
+ )
+ self.factory = IPCClientFactory()
+ self.factory.args = args
+ reactor.connectUNIX(socket, self.factory, checkPID=True)
+ reactor.run()
+ if self.factory.stop:
+ log.info('Success sending arguments to running Deluge.')
+ from gi.repository.Gdk import notify_startup_complete
+
+ notify_startup_complete()
+ sys.exit(0)
+ else:
+ if restart_tempfile:
+ log.error('Deluge restart failed: %s', ex)
+ sys.exit(1)
+ else:
+ log.warning('Restarting Deluge... (%s)', ex)
+ # Create a tempfile to keep track of restart
+ mkstemp(prefix='restart.', dir=ipc_dir)
+ os.execv(sys.argv[0], sys.argv)
+ else:
+ process_args(args)
+
+ def shutdown(self):
+ if windows_check():
+ import win32api
+
+ win32api.CloseHandle(self.mutex)
+ if self.listener:
+ return self.listener.stopListening()
+
+
+def process_args(args):
+ """Process arguments sent to already running Deluge"""
+ # Make sure args is a list
+ args = list(args)
+ log.debug('Processing args from other process: %s', args)
+ if not client.connected():
+ # We're not connected so add these to the queue
+ log.debug('Not connected to host.. Adding to queue.')
+ component.get('QueuedTorrents').add_to_queue(args)
+ return
+ config = ConfigManager('gtk3ui.conf')
+
+ for arg in args:
+ if not arg.strip():
+ continue
+ log.debug('arg: %s', arg)
+
+ if is_url(arg):
+ log.debug('Attempting to add url (%s) from external source...', arg)
+ if config['interactive_add']:
+ component.get('AddTorrentDialog').add_from_url(arg)
+ component.get('AddTorrentDialog').show(config['focus_add_dialog'])
+ else:
+ client.core.add_torrent_url(arg, None)
+
+ elif is_magnet(arg):
+ log.debug('Attempting to add magnet (%s) from external source...', arg)
+ if config['interactive_add']:
+ component.get('AddTorrentDialog').add_from_magnets([arg])
+ component.get('AddTorrentDialog').show(config['focus_add_dialog'])
+ else:
+ client.core.add_torrent_magnet(arg, {})
+
+ else:
+ log.debug('Attempting to add file (%s) from external source...', arg)
+ if urlparse(arg).scheme == 'file':
+ arg = url2pathname(urlparse(arg).path)
+ path = os.path.abspath(decode_bytes(arg))
+
+ if not os.path.exists(path):
+ log.error('No such file: %s', path)
+ continue
+
+ if config['interactive_add']:
+ component.get('AddTorrentDialog').add_from_files([path])
+ component.get('AddTorrentDialog').show(config['focus_add_dialog'])
+ else:
+ with open(path, 'rb') as _file:
+ filedump = b64encode(_file.read())
+ client.core.add_torrent_file(os.path.split(path)[-1], filedump, None)
diff --git a/deluge/ui/gtk3/listview.py b/deluge/ui/gtk3/listview.py
new file mode 100644
index 0000000..666bb67
--- /dev/null
+++ b/deluge/ui/gtk3/listview.py
@@ -0,0 +1,838 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+from gi.repository import GObject, Gtk
+
+from deluge.common import PY2, decode_bytes
+
+from .common import cmp, load_pickled_state_file, save_pickled_state_file
+
+log = logging.getLogger(__name__)
+
+
+class ListViewColumnState(object):
+ """Class used for saving/loading column state."""
+
+ def __init__(self, name, position, width, visible, sort, sort_order):
+ self.name = name
+ self.position = position
+ self.width = width
+ self.visible = visible
+ self.sort = sort
+ self.sort_order = sort_order
+
+
+class ListView(object):
+ """ListView is used to make custom GtkTreeViews. It supports the adding
+ and removing of columns, creating a menu for a column toggle list and
+ support for 'status_field's which are used while updating the columns data.
+ """
+
+ class ListViewColumn(object):
+ """Holds information regarding a column in the ListView"""
+
+ def __init__(self, name, column_indices):
+ # Name is how a column is identified and is also the header
+ self.name = name
+ # Column_indices holds the indexes to the liststore_columns that
+ # this column utilizes. It is stored as a list.
+ self.column_indices = column_indices
+ # Column is a reference to the GtkTreeViewColumn object
+ self.column = None
+ # This is the name of the status field that the column will query
+ # the core for if an update is called.
+ self.status_field = None
+ # If column is 'hidden' then it will not be visible and will not
+ # show up in any menu listing; it cannot be shown ever.
+ self.hidden = False
+ # If this is set, it is used to sort the model
+ self.sort_func = None
+ self.sort_id = None
+ # Values needed to update TreeViewColumns
+ self.column_type = None
+ self.renderer = None
+ self.text_index = 0
+ self.value_index = 0
+ self.pixbuf_index = 0
+ self.data_func = None
+
+ class TreeviewColumn(Gtk.TreeViewColumn, object):
+ """
+ TreeViewColumn does not signal right-click events, and we need them
+ This subclass is equivalent to TreeViewColumn, but it signals these events
+
+ Most of the code of this class comes from Quod Libet (http://www.sacredchao.net/quodlibet)
+ """
+
+ __gsignals__ = {
+ 'button-press-event'
+ if not PY2
+ else b'button-press-event': (GObject.SIGNAL_RUN_LAST, None, (object,))
+ }
+
+ def __init__(self, title=None, cell_renderer=None, **args):
+ """ Constructor, see Gtk.TreeViewColumn """
+ Gtk.TreeViewColumn.__init__(self, title, cell_renderer, **args)
+ label = Gtk.Label(label=title)
+ self.set_widget(label)
+ label.show()
+ label.__realize = label.connect('realize', self.on_realize)
+ self.title = title
+ self.data_func = None
+ self.data_func_data = None
+ self.cell_renderer = None
+
+ def on_realize(self, widget):
+ widget.disconnect(widget.__realize)
+ del widget.__realize
+ button = widget.get_ancestor(Gtk.Button)
+ if button is not None:
+ button.connect('button-press-event', self.on_button_pressed)
+
+ def on_button_pressed(self, widget, event):
+ self.emit('button-press-event', event)
+
+ def set_cell_data_func_attributes(self, cell_renderer, func, func_data=None):
+ """Store the values to be set by set_cell_data_func"""
+ self.data_func = func
+ self.data_func_data = func_data
+ self.cell_renderer = cell_renderer
+
+ def set_visible(self, visible):
+ Gtk.TreeViewColumn.set_visible(self, visible)
+ if self.data_func:
+ if not visible:
+ # Set data function to None to prevent unecessary calls when column is hidden
+ self.set_cell_data_func(self.cell_renderer, None, func_data=None)
+ else:
+ self.set_cell_data_func(
+ self.cell_renderer, self.data_func, self.data_func_data
+ )
+
+ def set_col_attributes(self, renderer, add=True, **kw):
+ if add is True:
+ for attr, value in kw.items():
+ self.add_attribute(renderer, attr, value)
+ else:
+ self.set_attributes(renderer, **kw)
+
+ def __init__(self, treeview_widget=None, state_file=None):
+ log.debug('ListView initialized..')
+
+ if treeview_widget is not None:
+ # User supplied a treeview widget
+ self.treeview = treeview_widget
+ else:
+ self.treeview = Gtk.TreeView()
+
+ self.treeview.set_enable_search(True)
+ self.treeview.set_search_equal_func(self.on_keypress_search_by_name, None)
+
+ if state_file:
+ self.load_state(state_file)
+
+ self.liststore = None
+ self.model_filter = None
+
+ self.treeview.set_rules_hint(True)
+ self.treeview.set_reorderable(False)
+ self.treeview.set_rubber_banding(True) # Enable mouse multi-row selection.
+ self.treeview.get_selection().set_mode(Gtk.SelectionMode.MULTIPLE)
+
+ # Dictionary of 'header' or 'name' to ListViewColumn object
+ self.columns = {}
+ # Column_index keeps track of the order of the visible columns.
+ self.column_index = []
+ # The column types for the list store.. this may have more entries than
+ # visible columns due to some columns utilizing more than 1 liststore
+ # column and some columns being hidden.
+ self.liststore_columns = []
+ # The GtkMenu that is created after every addition, removal or reorder
+ self.menu = None
+ # A list of menus that self.menu will be a submenu of everytime it is
+ # created.
+ self.checklist_menus = []
+
+ # Store removed columns state. This is needed for plugins that remove
+ # their columns prior to having the state list saved on shutdown.
+ self.removed_columns_state = []
+
+ # Since gtk TreeModelSort doesn't do stable sort, remember last sort order so we can
+ self.last_sort_order = {}
+ self.unique_column_id = None
+ self.default_sort_column_id = None
+
+ # Create the model filter and column
+ self.add_bool_column('filter', hidden=True)
+
+ def create_model_filter(self):
+ """create new filter-model
+ must be called after listview.create_new_liststore
+ """
+ model_filter = self.liststore.filter_new()
+ model_filter.set_visible_column(self.columns['filter'].column_indices[0])
+ self.model_filter = Gtk.TreeModelSort(model=model_filter)
+ self.model_filter.connect('sort-column-changed', self.on_model_sort_changed)
+ self.model_filter.connect('row-inserted', self.on_model_row_inserted)
+ self.treeview.set_model(self.model_filter)
+ self.set_sort_functions()
+ self.set_model_sort()
+
+ def set_model_sort(self):
+ column_state = self.get_sort_column_from_state()
+ if column_state:
+ self.treeview.get_model().set_sort_column_id(
+ column_state.sort, column_state.sort_order
+ )
+ # Using the default sort column
+ elif self.default_sort_column_id:
+ self.model_filter.set_sort_column_id(
+ self.default_sort_column_id, Gtk.SortType.ASCENDING
+ )
+ self.model_filter.set_default_sort_func(
+ self.generic_sort_func, self.get_column_index(_('Added'))[0]
+ )
+
+ def get_sort_column_from_state(self):
+ """Find the first (should only be one) state with sort enabled"""
+ if self.state is None:
+ return None
+ for column_state in self.state:
+ if column_state.sort is not None and column_state.sort > -1:
+ return column_state
+ return None
+
+ def on_model_sort_changed(self, model):
+ if self.unique_column_id:
+ self.last_sort_order = {}
+
+ def record_position(model, path, _iter, data):
+ unique_id = model[_iter][self.unique_column_id]
+ self.last_sort_order[unique_id] = int(str(path))
+
+ model.foreach(record_position, None)
+
+ def on_model_row_inserted(self, model, path, _iter):
+ if self.unique_column_id:
+ self.last_sort_order.setdefault(
+ model[_iter][self.unique_column_id], len(model) - 1
+ )
+
+ def stabilize_sort_func(self, sort_func):
+ def stabilized(model, iter1, iter2, data):
+ result = sort_func(model, iter1, iter2, data)
+ if result == 0 and self.unique_column_id:
+ unique1 = model[iter1][self.unique_column_id]
+ unique2 = model[iter2][self.unique_column_id]
+ if unique1 in self.last_sort_order and unique2 in self.last_sort_order:
+ result = cmp(
+ self.last_sort_order[unique1], self.last_sort_order[unique2]
+ )
+ # If all else fails, fall back to sorting by unique column
+ if result == 0:
+ result = cmp(unique1, unique2)
+
+ return result
+
+ return stabilized
+
+ def generic_sort_func(self, model, iter1, iter2, data):
+ return cmp(model[iter1][data], model[iter2][data])
+
+ def set_sort_functions(self):
+ for column in self.columns.values():
+ sort_func = column.sort_func or self.generic_sort_func
+ self.model_filter.set_sort_func(
+ column.sort_id, self.stabilize_sort_func(sort_func), column.sort_id
+ )
+
+ def create_column_state(self, column, position=None):
+ if not position:
+ # Find the position
+ for index, c in enumerate(self.treeview.get_columns()):
+ if column.get_title() == c.get_title():
+ position = index
+ break
+ sort = None
+ if self.model_filter:
+ sort_id, order = self.model_filter.get_sort_column_id()
+ col_title = decode_bytes(column.get_title())
+ if self.get_column_name(sort_id) == col_title:
+ sort = sort_id
+
+ return ListViewColumnState(
+ column.get_title(),
+ position,
+ column.get_width(),
+ column.get_visible(),
+ sort,
+ int(column.get_sort_order()),
+ )
+
+ def save_state(self, filename):
+ """Saves the listview state (column positions and visibility) to
+ filename."""
+ # A list of ListViewColumnStates
+ state = []
+
+ # Workaround for all zero widths after removing column on shutdown
+ if not any(c.get_width() for c in self.treeview.get_columns()):
+ return
+
+ # Get the list of TreeViewColumns from the TreeView
+ for counter, column in enumerate(self.treeview.get_columns()):
+ # Append a new column state to the state list
+ state.append(self.create_column_state(column, counter))
+
+ state += self.removed_columns_state
+
+ self.state = state
+ save_pickled_state_file(filename, state)
+
+ def load_state(self, filename):
+ """Load the listview state from filename."""
+ self.state = load_pickled_state_file(filename)
+
+ def set_treeview(self, treeview_widget):
+ """Set the treeview widget that this listview uses."""
+ self.treeview = treeview_widget
+ self.treeview.set_model(self.liststore)
+ return
+
+ def get_column_index(self, name):
+ """Get the liststore column indices belonging to this column.
+ Will return a list.
+ """
+ return self.columns[name].column_indices
+
+ def get_column_name(self, index):
+ """Get the header name for a liststore column index"""
+ for key, value in self.columns.items():
+ if index in value.column_indices:
+ return key
+
+ def get_state_field_column(self, field):
+ """Returns the column number for the state field"""
+ for column in self.columns:
+ if self.columns[column].status_field is None:
+ continue
+
+ for f in self.columns[column].status_field:
+ if field == f:
+ return self.columns[column].column_indices[
+ self.columns[column].status_field.index(f)
+ ]
+
+ def on_menuitem_toggled(self, widget):
+ """Callback for the generated column menuitems."""
+ # Get the column name from the widget
+ name = widget.get_child().get_text()
+
+ # Set the column's visibility based on the widgets active state
+ try:
+ self.columns[name].column.set_visible(widget.get_active())
+ except KeyError:
+ self.columns[decode_bytes(name)].column.set_visible(widget.get_active())
+ return
+
+ def on_treeview_header_right_clicked(self, column, event):
+ if event.button == 3:
+ self.menu.popup(None, None, None, None, event.button, event.get_time())
+
+ def register_checklist_menu(self, menu):
+ """Register a checklist menu with the listview. It will automatically
+ attach any new checklist menu it makes to this menu.
+ """
+ self.checklist_menus.append(menu)
+
+ def create_checklist_menu(self):
+ """Creates a menu used for toggling the display of columns."""
+ menu = self.menu = Gtk.Menu()
+ # Iterate through the column_index list to preserve order
+ for name in self.column_index:
+ column = self.columns[name]
+ # If the column is hidden, then we do not want to show it in the
+ # menu.
+ if column.hidden is True:
+ continue
+ menuitem = Gtk.CheckMenuItem.new_with_label(column.name)
+ # If the column is currently visible, make sure it's set active
+ # (or checked) in the menu.
+ if column.column.get_visible() is True:
+ menuitem.set_active(True)
+ # Connect to the 'toggled' event
+ menuitem.connect('toggled', self.on_menuitem_toggled)
+ # Add the new checkmenuitem to the menu
+ menu.append(menuitem)
+
+ # Attach this new menu to all the checklist_menus
+ for _menu in self.checklist_menus:
+ _menu.set_submenu(menu)
+ _menu.show_all()
+ return menu
+
+ def create_new_liststore(self):
+ """Creates a new GtkListStore based on the liststore_columns list"""
+ # Create a new liststore with added column and move the data from the
+ # old one to the new one.
+ new_list = Gtk.ListStore(*tuple(self.liststore_columns))
+
+ # This function is used in the liststore.foreach method with user_data
+ # being the new liststore and the columns list
+ def copy_row(model, path, row, user_data):
+ new_list, columns = user_data
+ new_row = new_list.append()
+ for column in range(len(columns)):
+ # Get the current value of the column for this row
+ value = model.get_value(row, column)
+ # Set the value of this row and column in the new liststore
+ new_list.set_value(new_row, column, value)
+
+ # Do the actual row copy
+ if self.liststore is not None:
+ self.liststore.foreach(copy_row, (new_list, self.columns))
+
+ self.liststore = new_list
+
+ def update_treeview_column(self, header, add=True):
+ """Update TreeViewColumn based on ListView column mappings"""
+ column = self.columns[header]
+ tree_column = self.columns[header].column
+
+ if column.column_type == 'text':
+ if add:
+ tree_column.pack_start(column.renderer, True)
+ tree_column.set_col_attributes(
+ column.renderer, add=add, text=column.column_indices[column.text_index]
+ )
+ elif column.column_type == 'bool':
+ if add:
+ tree_column.pack_start(column.renderer, True)
+ tree_column.set_col_attributes(
+ column.renderer, active=column.column_indices[0]
+ )
+ elif column.column_type == 'func':
+ if add:
+ tree_column.pack_start(column.renderer, True)
+ indice_arg = column.column_indices[0]
+ if len(column.column_indices) > 1:
+ indice_arg = tuple(column.column_indices)
+ tree_column.set_cell_data_func(
+ column.renderer, column.data_func, indice_arg
+ )
+ elif column.column_type == 'progress':
+ if add:
+ tree_column.pack_start(column.renderer, True)
+ if column.data_func is None:
+ tree_column.set_col_attributes(
+ column.renderer,
+ add=add,
+ text=column.column_indices[column.text_index],
+ value=column.column_indices[column.value_index],
+ )
+ else:
+ tree_column.set_cell_data_func(
+ column.renderer, column.data_func, tuple(column.column_indices)
+ )
+ elif column.column_type == 'texticon':
+ if add:
+ tree_column.pack_start(column.renderer[column.pixbuf_index], False)
+ tree_column.pack_start(column.renderer[column.text_index], True)
+ tree_column.set_col_attributes(
+ column.renderer[column.text_index],
+ add=add,
+ text=column.column_indices[column.text_index],
+ )
+ if column.data_func is not None:
+ tree_column.set_cell_data_func(
+ column.renderer[column.pixbuf_index],
+ column.data_func,
+ column.column_indices[column.pixbuf_index],
+ )
+ return True
+
+ def remove_column(self, header):
+ """Removes the column with the name 'header' from the listview"""
+ # Store a copy of this columns state in case it's re-added
+ state = self.create_column_state(self.columns[header].column)
+ self.removed_columns_state.append(state)
+ # Only remove column if column is associated with the treeview. This avoids
+ # warning on shutdown when GTKUI is closed before plugins try to remove columns
+ if self.columns[header].column.get_tree_view() is not None:
+ self.treeview.remove_column(self.columns[header].column)
+ # Get the column indices
+ column_indices = self.columns[header].column_indices
+ # Delete the column
+ del self.columns[header]
+ self.column_index.remove(header)
+ # Shift the column_indices values of those columns affected by the
+ # removal. Any column_indices > the one removed.
+ for column in self.columns.values():
+ if column.column_indices[0] > column_indices[0]:
+ # We need to shift this column_indices
+ for i, index in enumerate(column.column_indices):
+ column.column_indices[i] = index - len(column_indices)
+ # Update the associated TreeViewColumn
+ self.update_treeview_column(column.name, add=False)
+
+ # Remove from the liststore columns list
+ for index in sorted(column_indices, reverse=True):
+ del self.liststore_columns[index]
+
+ # Create a new liststore
+ self.create_new_liststore()
+ # Create new model for the treeview
+ self.create_model_filter()
+
+ # Re-create the menu
+ self.create_checklist_menu()
+ return
+
+ def add_column(
+ self,
+ header,
+ render,
+ col_types,
+ hidden,
+ position,
+ status_field,
+ sortid,
+ text=0,
+ value=0,
+ pixbuf=0,
+ function=None,
+ column_type=None,
+ sort_func=None,
+ tooltip=None,
+ default=True,
+ unique=False,
+ default_sort=False,
+ ):
+ """Adds a column to the ListView"""
+ # Add the column types to liststore_columns
+ column_indices = []
+ if isinstance(col_types, list):
+ for col_type in col_types:
+ self.liststore_columns.append(col_type)
+ column_indices.append(len(self.liststore_columns) - 1)
+ else:
+ self.liststore_columns.append(col_types)
+ column_indices.append(len(self.liststore_columns) - 1)
+
+ # Add to the index list so we know the order of the visible columns.
+ if position is not None:
+ self.column_index.insert(position, header)
+ else:
+ self.column_index.append(header)
+
+ # Create a new column object and add it to the list
+ column = self.TreeviewColumn(header)
+ self.columns[header] = self.ListViewColumn(header, column_indices)
+ self.columns[header].column = column
+ self.columns[header].status_field = status_field
+ self.columns[header].sort_func = sort_func
+ self.columns[header].sort_id = column_indices[sortid]
+ # Store creation details
+ self.columns[header].column_type = column_type
+ self.columns[header].renderer = render
+ self.columns[header].text_index = text
+ self.columns[header].value_index = value
+ self.columns[header].pixbuf_index = pixbuf
+ self.columns[header].data_func = function
+
+ if unique:
+ self.unique_column_id = column_indices[sortid]
+ if default_sort:
+ self.default_sort_column_id = column_indices[sortid]
+
+ # Create a new list with the added column
+ self.create_new_liststore()
+
+ # Happens only on columns added after the torrent list has been loaded
+ if self.model_filter:
+ self.create_model_filter()
+
+ if column_type is None:
+ return
+
+ self.update_treeview_column(header)
+
+ column.set_sort_column_id(self.columns[header].column_indices[sortid])
+ column.set_clickable(True)
+ column.set_resizable(True)
+ column.set_expand(False)
+ column.set_min_width(20)
+ column.set_reorderable(True)
+ column.set_visible(not hidden)
+ column.connect('button-press-event', self.on_treeview_header_right_clicked)
+
+ if tooltip:
+ column.get_widget().set_tooltip_markup(tooltip)
+
+ # Check for loaded state and apply
+ column_in_state = False
+ if self.state is not None:
+ for column_state in self.state:
+ if header == decode_bytes(column_state.name):
+ # We found a loaded state
+ column_in_state = True
+ if column_state.width > 0:
+ column.set_sizing(Gtk.TreeViewColumnSizing.FIXED)
+ column.set_fixed_width(column_state.width)
+
+ column.set_visible(column_state.visible)
+ position = column_state.position
+ break
+
+ # Set this column to not visible if its not in the state and
+ # its not supposed to be shown by default
+ if not column_in_state and not default and not hidden:
+ column.set_visible(False)
+
+ if position is not None:
+ self.treeview.insert_column(column, position)
+ else:
+ self.treeview.append_column(column)
+
+ # Set hidden in the column
+ self.columns[header].hidden = hidden
+ self.columns[header].column = column
+ # Re-create the menu item because of the new column
+ self.create_checklist_menu()
+
+ return True
+
+ def add_text_column(
+ self,
+ header,
+ col_type=str,
+ hidden=False,
+ position=None,
+ status_field=None,
+ sortid=0,
+ column_type='text',
+ sort_func=None,
+ tooltip=None,
+ default=True,
+ unique=False,
+ default_sort=False,
+ ):
+ """Add a text column to the listview. Only the header name is required.
+ """
+ render = Gtk.CellRendererText()
+ self.add_column(
+ header,
+ render,
+ col_type,
+ hidden,
+ position,
+ status_field,
+ sortid,
+ column_type=column_type,
+ sort_func=sort_func,
+ tooltip=tooltip,
+ default=default,
+ unique=unique,
+ default_sort=default_sort,
+ )
+
+ return True
+
+ def add_bool_column(
+ self,
+ header,
+ col_type=bool,
+ hidden=False,
+ position=None,
+ status_field=None,
+ sortid=0,
+ column_type='bool',
+ tooltip=None,
+ default=True,
+ ):
+ """Add a bool column to the listview"""
+ render = Gtk.CellRendererToggle()
+ self.add_column(
+ header,
+ render,
+ col_type,
+ hidden,
+ position,
+ status_field,
+ sortid,
+ column_type=column_type,
+ tooltip=tooltip,
+ default=default,
+ )
+
+ def add_func_column(
+ self,
+ header,
+ function,
+ col_types,
+ sortid=0,
+ hidden=False,
+ position=None,
+ status_field=None,
+ column_type='func',
+ sort_func=None,
+ tooltip=None,
+ default=True,
+ ):
+ """Add a function column to the listview. Need a header name, the
+ function and the column types."""
+
+ render = Gtk.CellRendererText()
+ self.add_column(
+ header,
+ render,
+ col_types,
+ hidden,
+ position,
+ status_field,
+ sortid,
+ column_type=column_type,
+ function=function,
+ sort_func=sort_func,
+ tooltip=tooltip,
+ default=default,
+ )
+
+ return True
+
+ def add_progress_column(
+ self,
+ header,
+ col_types=None,
+ sortid=0,
+ hidden=False,
+ position=None,
+ status_field=None,
+ function=None,
+ column_type='progress',
+ tooltip=None,
+ sort_func=None,
+ default=True,
+ ):
+ """Add a progress column to the listview."""
+
+ if col_types is None:
+ col_types = [float, str]
+ render = Gtk.CellRendererProgress()
+ self.add_column(
+ header,
+ render,
+ col_types,
+ hidden,
+ position,
+ status_field,
+ sortid,
+ function=function,
+ column_type=column_type,
+ value=0,
+ text=1,
+ tooltip=tooltip,
+ sort_func=sort_func,
+ default=default,
+ )
+
+ return True
+
+ def add_texticon_column(
+ self,
+ header,
+ col_types=None,
+ sortid=1,
+ hidden=False,
+ position=None,
+ status_field=None,
+ column_type='texticon',
+ function=None,
+ sort_func=None,
+ tooltip=None,
+ default=True,
+ default_sort=False,
+ ):
+ """Adds a texticon column to the listview."""
+ if col_types is None:
+ col_types = [str, str]
+ render1 = Gtk.CellRendererPixbuf()
+ render2 = Gtk.CellRendererText()
+
+ self.add_column(
+ header,
+ (render1, render2),
+ col_types,
+ hidden,
+ position,
+ status_field,
+ sortid,
+ column_type=column_type,
+ function=function,
+ pixbuf=0,
+ text=1,
+ tooltip=tooltip,
+ sort_func=sort_func,
+ default=default,
+ default_sort=default_sort,
+ )
+
+ return True
+
+ def on_keypress_search_by_name(self, model, column, key, _iter):
+ torrent_name_col = self.columns[_('Name')].column_indices[1]
+ return not model[_iter][torrent_name_col].lower().startswith(key.lower())
+
+ def restore_columns_order_from_state(self):
+ if self.state is None:
+ # No state file exists, so, no reordering can be done
+ return
+ columns = self.treeview.get_columns()
+
+ def find_column(header):
+ for column in columns:
+ if column.get_title() == header:
+ return column
+
+ restored_columns = []
+ for col_state in self.state:
+ if col_state.name in restored_columns:
+ # Duplicate column in state!?!?!?
+ continue
+ elif not col_state.visible:
+ # Column is not visible, no need to reposition
+ continue
+
+ try:
+ column_at_position = columns[col_state.position]
+ except IndexError:
+ # Ignore extra columns from Plugins in col_state
+ continue
+ if col_state.name == column_at_position.get_title():
+ # It's in the right position
+ continue
+ column = find_column(col_state.name)
+ if not column:
+ log.debug(
+ 'Could not find column matching "%s" on state.', col_state.name
+ )
+ # The cases where I've found that the column could not be found
+ # is when not using the english locale, ie, the default one, or
+ # when changing locales between runs.
+ # On the next load, all should be fine
+ continue
+ self.treeview.move_column_after(column, column_at_position)
+ # Get columns again to keep reordering since positions have changed
+ columns = self.treeview.get_columns()
+ restored_columns.append(col_state.name)
+ self.create_new_liststore()
diff --git a/deluge/ui/gtk3/mainwindow.py b/deluge/ui/gtk3/mainwindow.py
new file mode 100644
index 0000000..5b4240c
--- /dev/null
+++ b/deluge/ui/gtk3/mainwindow.py
@@ -0,0 +1,381 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os.path
+from hashlib import sha1 as sha
+
+import gi
+from gi.repository import Gtk
+from gi.repository.Gdk import DragAction, WindowState
+from twisted.internet import reactor
+from twisted.internet.error import ReactorNotRunning
+
+import deluge.component as component
+from deluge.common import decode_bytes, fspeed, resource_filename
+from deluge.configmanager import ConfigManager
+from deluge.ui.client import client
+
+from .common import get_deluge_icon, windowing
+from .dialogs import PasswordDialog
+from .ipcinterface import process_args
+
+GdkX11 = None
+Wnck = None
+if windowing('X11'):
+ try:
+ from gi.repository import GdkX11
+ except ImportError:
+ pass
+
+ try:
+ gi.require_version('Wnck', '3.0')
+ from gi.repository import Wnck
+ except (ImportError, ValueError):
+ pass
+
+log = logging.getLogger(__name__)
+
+
+class _GtkBuilderSignalsHolder(object):
+ def connect_signals(self, mapping_or_class):
+
+ if isinstance(mapping_or_class, dict):
+ for name, handler in mapping_or_class.items():
+ if hasattr(self, name):
+ raise RuntimeError(
+ 'A handler for signal %r has already been registered: %s'
+ % (name, getattr(self, name))
+ )
+ setattr(self, name, handler)
+ else:
+ for name in dir(mapping_or_class):
+ if not name.startswith('on_'):
+ continue
+ if hasattr(self, name):
+ raise RuntimeError(
+ 'A handler for signal %r has already been registered: %s'
+ % (name, getattr(self, name))
+ )
+ setattr(self, name, getattr(mapping_or_class, name))
+
+
+class MainWindow(component.Component):
+ def __init__(self):
+ if Wnck:
+ self.screen = Wnck.Screen.get_default()
+ component.Component.__init__(self, 'MainWindow', interval=2)
+ self.config = ConfigManager('gtk3ui.conf')
+ self.main_builder = Gtk.Builder()
+
+ # Patch this GtkBuilder to avoid connecting signals from elsewhere
+ #
+ # Think about splitting up mainwindow gtkbuilder file into the necessary parts
+ # to avoid GtkBuilder monkey patch. Those parts would then need adding to mainwindow 'by hand'.
+ self.gtk_builder_signals_holder = _GtkBuilderSignalsHolder()
+ # FIXME: The deepcopy has been removed: copy.deepcopy(self.main_builder.connect_signals)
+ self.main_builder.prev_connect_signals = self.main_builder.connect_signals
+
+ def patched_connect_signals(*a, **k):
+ raise RuntimeError(
+ 'In order to connect signals to this GtkBuilder instance please use '
+ '"component.get(\'MainWindow\').connect_signals()"'
+ )
+
+ self.main_builder.connect_signals = patched_connect_signals
+
+ # Get Gtk Builder files Main Window, New release dialog, and Tabs.
+ ui_filenames = [
+ 'main_window.ui',
+ 'main_window.new_release.ui',
+ 'main_window.tabs.ui',
+ 'main_window.tabs.menu_file.ui',
+ 'main_window.tabs.menu_peer.ui',
+ ]
+ for filename in ui_filenames:
+ self.main_builder.add_from_file(
+ resource_filename(__package__, os.path.join('glade', filename))
+ )
+
+ self.window = self.main_builder.get_object('main_window')
+ self.window.set_icon(get_deluge_icon())
+ self.tabsbar_pane = self.main_builder.get_object('tabsbar_pane')
+ self.sidebar_pane = self.main_builder.get_object('sidebar_pane')
+
+ # Keep a list of components to pause and resume when changing window state.
+ self.child_components = ['TorrentView', 'StatusBar', 'TorrentDetails']
+
+ # Load the window state
+ self.load_window_state()
+
+ # Keep track of window minimization state so we don't update UI when it is minimized.
+ self.is_minimized = False
+ self.restart = False
+
+ self.window.drag_dest_set(
+ Gtk.DestDefaults.ALL,
+ [Gtk.TargetEntry.new(target='text/uri-list', flags=0, info=80)],
+ DragAction.COPY,
+ )
+
+ # Connect events
+ self.window.connect('window-state-event', self.on_window_state_event)
+ self.window.connect('configure-event', self.on_window_configure_event)
+ self.window.connect('delete-event', self.on_window_delete_event)
+ self.window.connect('drag-data-received', self.on_drag_data_received_event)
+ self.tabsbar_pane.connect(
+ 'notify::position', self.on_tabsbar_pane_position_event
+ )
+ self.sidebar_pane.connect(
+ 'notify::position', self.on_sidebar_pane_position_event
+ )
+ self.window.connect('draw', self.on_expose_event)
+
+ self.config.register_set_function(
+ 'show_rate_in_title', self._on_set_show_rate_in_title, apply_now=False
+ )
+
+ client.register_event_handler(
+ 'NewVersionAvailableEvent', self.on_newversionavailable_event
+ )
+
+ def connect_signals(self, mapping_or_class):
+ self.gtk_builder_signals_holder.connect_signals(mapping_or_class)
+
+ def first_show(self):
+ self.main_builder.prev_connect_signals(self.gtk_builder_signals_holder)
+ self.sidebar_pane.set_position(self.config['sidebar_position'])
+ self.tabsbar_pane.set_position(self.config['tabsbar_position'])
+
+ if not (
+ self.config['start_in_tray'] and self.config['enable_system_tray']
+ ) and not self.window.get_property('visible'):
+ log.debug('Showing window')
+ self.show()
+
+ while Gtk.events_pending():
+ Gtk.main_iteration()
+
+ def show(self):
+ component.resume(self.child_components)
+ self.window.show()
+
+ def hide(self):
+ component.get('TorrentView').save_state()
+ component.pause(self.child_components)
+ self.save_position()
+ self.window.hide()
+
+ def present(self):
+ def restore():
+ # Restore the proper x,y coords for the window prior to showing it
+ component.resume(self.child_components)
+ timestamp = self.get_timestamp()
+ if windowing('X11'):
+ # Use present with X11 set_user_time since
+ # present_with_time is inconsistent.
+ self.window.present()
+ self.window.get_window().set_user_time(timestamp)
+ else:
+ self.window.present_with_time(timestamp)
+ self.load_window_state()
+
+ if self.config['lock_tray'] and not self.visible():
+ dialog = PasswordDialog(_('Enter your password to show Deluge...'))
+
+ def on_dialog_response(response_id):
+ if response_id == Gtk.ResponseType.OK:
+ if (
+ self.config['tray_password']
+ == sha(decode_bytes(dialog.get_password()).encode()).hexdigest()
+ ):
+ restore()
+
+ dialog.run().addCallback(on_dialog_response)
+ else:
+ restore()
+
+ def get_timestamp(self):
+ """Returns the timestamp for the windowing server."""
+ timestamp = 0
+ gdk_window = self.window.get_window()
+ if GdkX11 and isinstance(gdk_window, GdkX11.X11Window):
+ timestamp = GdkX11.x11_get_server_time(gdk_window)
+ return timestamp
+
+ def active(self):
+ """Returns True if the window is active, False if not."""
+ return self.window.is_active()
+
+ def visible(self):
+ """Returns True if window is visible, False if not."""
+ return self.window.get_visible()
+
+ def get_builder(self):
+ """Returns a reference to the main window GTK builder object."""
+ return self.main_builder
+
+ def quit(self, shutdown=False, restart=False): # noqa: A003 python builtin
+ """Quits the GtkUI application.
+
+ Args:
+ shutdown (bool): Whether or not to shutdown the daemon as well.
+ restart (bool): Whether or not to restart the application after closing.
+
+ """
+
+ def quit_gtkui():
+ def stop_gtk_reactor(result=None):
+ self.restart = restart
+ try:
+ reactor.callLater(0, reactor.fireSystemEvent, 'gtkui_close')
+ except ReactorNotRunning:
+ log.debug('Attempted to stop the reactor but it is not running...')
+
+ if shutdown:
+ client.daemon.shutdown().addCallback(stop_gtk_reactor)
+ elif not client.is_standalone() and client.connected():
+ client.disconnect().addCallback(stop_gtk_reactor)
+ else:
+ stop_gtk_reactor()
+
+ if self.config['lock_tray'] and not self.visible():
+ dialog = PasswordDialog(_('Enter your password to Quit Deluge...'))
+
+ def on_dialog_response(response_id):
+ if response_id == Gtk.ResponseType.OK:
+ if (
+ self.config['tray_password']
+ == sha(decode_bytes(dialog.get_password()).encode()).hexdigest()
+ ):
+ quit_gtkui()
+
+ dialog.run().addCallback(on_dialog_response)
+ else:
+ quit_gtkui()
+
+ def load_window_state(self):
+ if (
+ self.config['window_x_pos'] == -32000
+ or self.config['window_x_pos'] == -32000
+ ):
+ self.config['window_x_pos'] = self.config['window_y_pos'] = 0
+
+ self.window.move(self.config['window_x_pos'], self.config['window_y_pos'])
+ self.window.resize(self.config['window_width'], self.config['window_height'])
+ if self.config['window_maximized']:
+ self.window.maximize()
+
+ def save_position(self):
+ self.config['window_maximized'] = self.window.props.is_maximized
+ if not self.config['window_maximized'] and self.visible():
+ self.config['window_x_pos'], self.config[
+ 'window_y_pos'
+ ] = self.window.get_position()
+ self.config['window_width'], self.config[
+ 'window_height'
+ ] = self.window.get_size()
+
+ def on_window_configure_event(self, widget, event):
+ self.save_position()
+
+ def on_window_state_event(self, widget, event):
+ if event.changed_mask & WindowState.ICONIFIED:
+ if event.new_window_state & WindowState.ICONIFIED:
+ log.debug('MainWindow is minimized..')
+ component.get('TorrentView').save_state()
+ component.pause(self.child_components)
+ self.is_minimized = True
+ else:
+ log.debug('MainWindow is not minimized..')
+ component.resume(self.child_components)
+ self.is_minimized = False
+ return False
+
+ def on_window_delete_event(self, widget, event):
+ if self.config['close_to_tray'] and self.config['enable_system_tray']:
+ self.hide()
+ else:
+ self.quit()
+
+ return True
+
+ def on_tabsbar_pane_position_event(self, obj, param):
+ self.config['tabsbar_position'] = self.tabsbar_pane.get_position()
+
+ def on_sidebar_pane_position_event(self, obj, param):
+ self.config['sidebar_position'] = self.sidebar_pane.get_position()
+
+ def on_drag_data_received_event(
+ self, widget, drag_context, x, y, selection_data, info, timestamp
+ ):
+ log.debug('Selection(s) dropped on main window %s', selection_data.get_text())
+ if selection_data.get_uris():
+ process_args(selection_data.get_uris())
+ else:
+ process_args(selection_data.get_text().split())
+ drag_context.finish(True, True, timestamp)
+
+ def on_expose_event(self, widget, event):
+ component.get('SystemTray').blink(False)
+
+ def stop(self):
+ self.window.set_title('Deluge')
+
+ def update(self):
+ # Update the window title
+ def _on_get_session_status(status):
+ download_rate = fspeed(
+ status['payload_download_rate'], precision=0, shortform=True
+ )
+ upload_rate = fspeed(
+ status['payload_upload_rate'], precision=0, shortform=True
+ )
+ self.window.set_title(
+ _('D: {download_rate} U: {upload_rate} - Deluge').format(
+ download_rate=download_rate, upload_rate=upload_rate
+ )
+ )
+
+ if self.config['show_rate_in_title']:
+ client.core.get_session_status(
+ ['payload_download_rate', 'payload_upload_rate']
+ ).addCallback(_on_get_session_status)
+
+ def _on_set_show_rate_in_title(self, key, value):
+ if value:
+ self.update()
+ else:
+ self.window.set_title(_('Deluge'))
+
+ def on_newversionavailable_event(self, new_version):
+ if self.config['show_new_releases']:
+ from .new_release_dialog import NewReleaseDialog
+
+ reactor.callLater(5.0, NewReleaseDialog().show, new_version)
+
+ def is_on_active_workspace(self):
+ """Determines if MainWindow is on the active workspace.
+
+ Returns:
+ bool: True if on active workspace (or wnck module not available), otherwise False.
+
+ """
+
+ if Wnck:
+ self.screen.force_update()
+ win = Wnck.Window.get(self.window.get_window().get_xid())
+ if win:
+ active_wksp = win.get_screen().get_active_workspace()
+ if active_wksp:
+ return win.is_on_workspace(active_wksp)
+ return False
+ return True
diff --git a/deluge/ui/gtk3/menubar.py b/deluge/ui/gtk3/menubar.py
new file mode 100644
index 0000000..e09f394
--- /dev/null
+++ b/deluge/ui/gtk3/menubar.py
@@ -0,0 +1,614 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
+# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+
+from __future__ import unicode_literals
+
+import logging
+import os.path
+
+from gi.repository import Gtk
+
+import deluge.common
+import deluge.component as component
+from deluge.configmanager import ConfigManager
+from deluge.ui.client import client
+
+from .dialogs import ErrorDialog, OtherDialog
+from .path_chooser import PathChooser
+
+log = logging.getLogger(__name__)
+
+
+class MenuBar(component.Component):
+ def __init__(self):
+ log.debug('MenuBar init..')
+ component.Component.__init__(self, 'MenuBar')
+ self.mainwindow = component.get('MainWindow')
+ self.main_builder = self.mainwindow.get_builder()
+ self.config = ConfigManager('gtk3ui.conf')
+
+ self.builder = Gtk.Builder()
+ # Get the torrent menu from the gtk builder file
+ self.builder.add_from_file(
+ deluge.common.resource_filename(
+ __package__, os.path.join('glade', 'torrent_menu.ui')
+ )
+ )
+ # Get the torrent options menu from the gtk builder file
+ self.builder.add_from_file(
+ deluge.common.resource_filename(
+ __package__, os.path.join('glade', 'torrent_menu.options.ui')
+ )
+ )
+ # Get the torrent queue menu from the gtk builder file
+ self.builder.add_from_file(
+ deluge.common.resource_filename(
+ __package__, os.path.join('glade', 'torrent_menu.queue.ui')
+ )
+ )
+
+ # Attach queue torrent menu
+ torrent_queue_menu = self.builder.get_object('queue_torrent_menu')
+ self.builder.get_object('menuitem_queue').set_submenu(torrent_queue_menu)
+ # Attach options torrent menu
+ torrent_options_menu = self.builder.get_object('options_torrent_menu')
+ self.builder.get_object('menuitem_options').set_submenu(torrent_options_menu)
+
+ self.builder.get_object('download-limit-image').set_from_file(
+ deluge.common.get_pixmap('downloading16.png')
+ )
+ self.builder.get_object('upload-limit-image').set_from_file(
+ deluge.common.get_pixmap('seeding16.png')
+ )
+
+ for menuitem in (
+ 'menuitem_down_speed',
+ 'menuitem_up_speed',
+ 'menuitem_max_connections',
+ 'menuitem_upload_slots',
+ ):
+ submenu = Gtk.Menu()
+ item = Gtk.MenuItem.new_with_label(_('Set Unlimited'))
+ item.set_name(menuitem)
+ item.connect('activate', self.on_menuitem_set_unlimited)
+ submenu.append(item)
+ item = Gtk.MenuItem.new_with_label(_('Other...'))
+ item.set_name(menuitem)
+ item.connect('activate', self.on_menuitem_set_other)
+ submenu.append(item)
+ submenu.show_all()
+ self.builder.get_object(menuitem).set_submenu(submenu)
+
+ submenu = Gtk.Menu()
+ item = Gtk.MenuItem.new_with_label(_('On'))
+ item.connect('activate', self.on_menuitem_set_automanaged_on)
+ submenu.append(item)
+ item = Gtk.MenuItem.new_with_label(_('Off'))
+ item.connect('activate', self.on_menuitem_set_automanaged_off)
+ submenu.append(item)
+ submenu.show_all()
+ self.builder.get_object('menuitem_auto_managed').set_submenu(submenu)
+
+ submenu = Gtk.Menu()
+ item = Gtk.MenuItem.new_with_label(_('Disable'))
+ item.connect('activate', self.on_menuitem_set_stop_seed_at_ratio_disable)
+ submenu.append(item)
+ item = Gtk.MenuItem.new_with_label(_('Enable...'))
+ item.set_name('menuitem_stop_seed_at_ratio')
+ item.connect('activate', self.on_menuitem_set_other)
+ submenu.append(item)
+ submenu.show_all()
+ self.builder.get_object('menuitem_stop_seed_at_ratio').set_submenu(submenu)
+
+ self.torrentmenu = self.builder.get_object('torrent_menu')
+ self.menu_torrent = self.main_builder.get_object('menu_torrent')
+
+ # Attach the torrent_menu to the Torrent file menu
+ self.menu_torrent.set_submenu(self.torrentmenu)
+
+ # Make sure the view menuitems are showing the correct active state
+ self.main_builder.get_object('menuitem_toolbar').set_active(
+ self.config['show_toolbar']
+ )
+ self.main_builder.get_object('menuitem_sidebar').set_active(
+ self.config['show_sidebar']
+ )
+ self.main_builder.get_object('menuitem_statusbar').set_active(
+ self.config['show_statusbar']
+ )
+ self.main_builder.get_object('sidebar_show_zero').set_active(
+ self.config['sidebar_show_zero']
+ )
+ self.main_builder.get_object('sidebar_show_trackers').set_active(
+ self.config['sidebar_show_trackers']
+ )
+ self.main_builder.get_object('sidebar_show_owners').set_active(
+ self.config['sidebar_show_owners']
+ )
+
+ # Connect main window Signals #
+ self.mainwindow.connect_signals(self)
+
+ # Connect menubar signals
+ self.builder.connect_signals(self)
+
+ self.change_sensitivity = ['menuitem_addtorrent']
+
+ def start(self):
+ for widget in self.change_sensitivity:
+ self.main_builder.get_object(widget).set_sensitive(True)
+
+ # Only show open_folder menuitem and separator if connected to a localhost daemon.
+ localhost_items = ['menuitem_open_folder', 'separator4']
+ if client.is_localhost():
+ for widget in localhost_items:
+ self.builder.get_object(widget).show()
+ self.builder.get_object(widget).set_no_show_all(False)
+ else:
+ for widget in localhost_items:
+ self.builder.get_object(widget).hide()
+ self.builder.get_object(widget).set_no_show_all(True)
+
+ self.main_builder.get_object('separatormenuitem').set_visible(
+ not self.config['standalone']
+ )
+ self.main_builder.get_object('menuitem_quitdaemon').set_visible(
+ not self.config['standalone']
+ )
+ self.main_builder.get_object('menuitem_connectionmanager').set_visible(
+ not self.config['standalone']
+ )
+
+ # Show the Torrent menu because we're connected to a host
+ self.menu_torrent.show()
+
+ if client.get_auth_level() == deluge.common.AUTH_LEVEL_ADMIN:
+ # Get known accounts to allow changing ownership
+ client.core.get_known_accounts().addCallback(
+ self._on_known_accounts
+ ).addErrback(self._on_known_accounts_fail)
+
+ client.register_event_handler(
+ 'TorrentStateChangedEvent', self.on_torrentstatechanged_event
+ )
+ client.register_event_handler(
+ 'TorrentResumedEvent', self.on_torrentresumed_event
+ )
+ client.register_event_handler('SessionPausedEvent', self.on_sessionpaused_event)
+ client.register_event_handler(
+ 'SessionResumedEvent', self.on_sessionresumed_event
+ )
+
+ def stop(self):
+ log.debug('MenuBar stopping')
+
+ client.deregister_event_handler(
+ 'TorrentStateChangedEvent', self.on_torrentstatechanged_event
+ )
+ client.deregister_event_handler(
+ 'TorrentResumedEvent', self.on_torrentresumed_event
+ )
+ client.deregister_event_handler(
+ 'SessionPausedEvent', self.on_sessionpaused_event
+ )
+ client.deregister_event_handler(
+ 'SessionResumedEvent', self.on_sessionresumed_event
+ )
+
+ for widget in self.change_sensitivity:
+ self.main_builder.get_object(widget).set_sensitive(False)
+
+ # Hide the Torrent menu
+ self.menu_torrent.hide()
+
+ self.main_builder.get_object('separatormenuitem').hide()
+ self.main_builder.get_object('menuitem_quitdaemon').hide()
+
+ def update_menu(self):
+ selected = component.get('TorrentView').get_selected_torrents()
+ if not selected or len(selected) == 0:
+ # No torrent is selected. Disable the 'Torrents' menu
+ self.menu_torrent.set_sensitive(False)
+ return
+
+ self.menu_torrent.set_sensitive(True)
+ # XXX: Should also update Pause/Resume/Remove menuitems.
+ # Any better way than duplicating toolbar.py:update_buttons in here?
+
+ def add_torrentmenu_separator(self):
+ sep = Gtk.SeparatorMenuItem()
+ self.torrentmenu.append(sep)
+ sep.show()
+ return sep
+
+ # Callbacks #
+ def on_torrentstatechanged_event(self, torrent_id, state):
+ if state == 'Paused':
+ self.update_menu()
+
+ def on_torrentresumed_event(self, torrent_id):
+ self.update_menu()
+
+ def on_sessionpaused_event(self):
+ self.update_menu()
+
+ def on_sessionresumed_event(self):
+ self.update_menu()
+
+ # File Menu #
+ def on_menuitem_addtorrent_activate(self, data=None):
+ log.debug('on_menuitem_addtorrent_activate')
+ component.get('AddTorrentDialog').show()
+
+ def on_menuitem_createtorrent_activate(self, data=None):
+ log.debug('on_menuitem_createtorrent_activate')
+ from .createtorrentdialog import CreateTorrentDialog
+
+ CreateTorrentDialog().show()
+
+ def on_menuitem_quitdaemon_activate(self, data=None):
+ log.debug('on_menuitem_quitdaemon_activate')
+ self.mainwindow.quit(shutdown=True)
+
+ def on_menuitem_quit_activate(self, data=None):
+ log.debug('on_menuitem_quit_activate')
+ self.mainwindow.quit()
+
+ # Edit Menu #
+ def on_menuitem_preferences_activate(self, data=None):
+ log.debug('on_menuitem_preferences_activate')
+ component.get('Preferences').show()
+
+ def on_menuitem_connectionmanager_activate(self, data=None):
+ log.debug('on_menuitem_connectionmanager_activate')
+ component.get('ConnectionManager').show()
+
+ # Torrent Menu #
+ def on_menuitem_pause_activate(self, data=None):
+ log.debug('on_menuitem_pause_activate')
+ client.core.pause_torrents(component.get('TorrentView').get_selected_torrents())
+
+ def on_menuitem_resume_activate(self, data=None):
+ log.debug('on_menuitem_resume_activate')
+ client.core.resume_torrents(
+ component.get('TorrentView').get_selected_torrents()
+ )
+
+ def on_menuitem_updatetracker_activate(self, data=None):
+ log.debug('on_menuitem_updatetracker_activate')
+ client.core.force_reannounce(
+ component.get('TorrentView').get_selected_torrents()
+ )
+
+ def on_menuitem_edittrackers_activate(self, data=None):
+ log.debug('on_menuitem_edittrackers_activate')
+ from .edittrackersdialog import EditTrackersDialog
+
+ dialog = EditTrackersDialog(
+ component.get('TorrentView').get_selected_torrent(), self.mainwindow.window
+ )
+ dialog.run()
+
+ def on_menuitem_remove_activate(self, data=None):
+ log.debug('on_menuitem_remove_activate')
+ torrent_ids = component.get('TorrentView').get_selected_torrents()
+ if torrent_ids:
+ from .removetorrentdialog import RemoveTorrentDialog
+
+ RemoveTorrentDialog(torrent_ids).run()
+
+ def on_menuitem_recheck_activate(self, data=None):
+ log.debug('on_menuitem_recheck_activate')
+ client.core.force_recheck(component.get('TorrentView').get_selected_torrents())
+
+ def on_menuitem_open_folder_activate(self, data=None):
+ log.debug('on_menuitem_open_folder')
+
+ def _on_torrent_status(status):
+ timestamp = component.get('MainWindow').get_timestamp()
+ path = os.path.join(
+ status['download_location'], status['files'][0]['path'].split('/')[0]
+ )
+ deluge.common.show_file(path, timestamp=timestamp)
+
+ for torrent_id in component.get('TorrentView').get_selected_torrents():
+ component.get('SessionProxy').get_torrent_status(
+ torrent_id, ['download_location', 'files']
+ ).addCallback(_on_torrent_status)
+
+ def on_menuitem_move_activate(self, data=None):
+ log.debug('on_menuitem_move_activate')
+ component.get('SessionProxy').get_torrent_status(
+ component.get('TorrentView').get_selected_torrent(), ['download_location']
+ ).addCallback(self.show_move_storage_dialog)
+
+ def show_move_storage_dialog(self, status):
+ log.debug('show_move_storage_dialog')
+ builder = Gtk.Builder()
+ builder.add_from_file(
+ deluge.common.resource_filename(
+ __package__, os.path.join('glade', 'move_storage_dialog.ui')
+ )
+ )
+ # Keep it referenced:
+ # https://bugzilla.gnome.org/show_bug.cgi?id=546802
+ self.move_storage_dialog = builder.get_object('move_storage_dialog')
+ self.move_storage_dialog.set_transient_for(self.mainwindow.window)
+ self.move_storage_dialog_hbox = builder.get_object('hbox_entry')
+ self.move_storage_path_chooser = PathChooser(
+ 'move_completed_paths_list', self.move_storage_dialog
+ )
+ self.move_storage_dialog_hbox.add(self.move_storage_path_chooser)
+ self.move_storage_dialog_hbox.show_all()
+ self.move_storage_path_chooser.set_text(status['download_location'])
+
+ def on_dialog_response_event(widget, response_id):
+ def on_core_result(result):
+ # Delete references
+ self.move_storage_dialog.hide()
+ del self.move_storage_dialog
+ del self.move_storage_dialog_hbox
+
+ if response_id == Gtk.ResponseType.CANCEL:
+ on_core_result(None)
+
+ if response_id == Gtk.ResponseType.OK:
+ log.debug(
+ 'Moving torrents to %s', self.move_storage_path_chooser.get_text()
+ )
+ path = self.move_storage_path_chooser.get_text()
+ client.core.move_storage(
+ component.get('TorrentView').get_selected_torrents(), path
+ ).addCallback(on_core_result)
+
+ self.move_storage_dialog.connect('response', on_dialog_response_event)
+ self.move_storage_dialog.show()
+
+ def on_menuitem_queue_top_activate(self, value):
+ log.debug('on_menuitem_queue_top_activate')
+ client.core.queue_top(component.get('TorrentView').get_selected_torrents())
+
+ def on_menuitem_queue_up_activate(self, value):
+ log.debug('on_menuitem_queue_up_activate')
+ client.core.queue_up(component.get('TorrentView').get_selected_torrents())
+
+ def on_menuitem_queue_down_activate(self, value):
+ log.debug('on_menuitem_queue_down_activate')
+ client.core.queue_down(component.get('TorrentView').get_selected_torrents())
+
+ def on_menuitem_queue_bottom_activate(self, value):
+ log.debug('on_menuitem_queue_bottom_activate')
+ client.core.queue_bottom(component.get('TorrentView').get_selected_torrents())
+
+ # View Menu #
+ def on_menuitem_toolbar_toggled(self, value):
+ log.debug('on_menuitem_toolbar_toggled')
+ component.get('ToolBar').visible(value.get_active())
+
+ def on_menuitem_sidebar_toggled(self, value):
+ log.debug('on_menuitem_sidebar_toggled')
+ component.get('SideBar').visible(value.get_active())
+
+ def on_menuitem_statusbar_toggled(self, value):
+ log.debug('on_menuitem_statusbar_toggled')
+ component.get('StatusBar').visible(value.get_active())
+
+ # Help Menu #
+ def on_menuitem_homepage_activate(self, data=None):
+ log.debug('on_menuitem_homepage_activate')
+ deluge.common.open_url_in_browser('http://deluge-torrent.org')
+
+ def on_menuitem_faq_activate(self, data=None):
+ log.debug('on_menuitem_faq_activate')
+ deluge.common.open_url_in_browser('http://dev.deluge-torrent.org/wiki/Faq')
+
+ def on_menuitem_community_activate(self, data=None):
+ log.debug('on_menuitem_community_activate')
+ deluge.common.open_url_in_browser('http://forum.deluge-torrent.org/')
+
+ def on_menuitem_about_activate(self, data=None):
+ log.debug('on_menuitem_about_activate')
+ from .aboutdialog import AboutDialog
+
+ AboutDialog().run()
+
+ def on_menuitem_set_unlimited(self, widget):
+ log.debug('widget name: %s', widget.get_name())
+ funcs = {
+ 'menuitem_down_speed': 'max_download_speed',
+ 'menuitem_up_speed': 'max_upload_speed',
+ 'menuitem_max_connections': 'max_connections',
+ 'menuitem_upload_slots': 'max_upload_slots',
+ }
+ if widget.get_name() in funcs:
+ torrent_ids = component.get('TorrentView').get_selected_torrents()
+ client.core.set_torrent_options(torrent_ids, {funcs[widget.get_name()]: -1})
+
+ def on_menuitem_set_other(self, widget):
+ log.debug('widget name: %s', widget.get_name())
+ status_map = {
+ 'menuitem_down_speed': ['max_download_speed', 'max_download_speed'],
+ 'menuitem_up_speed': ['max_upload_speed', 'max_upload_speed'],
+ 'menuitem_max_connections': ['max_connections', 'max_connections_global'],
+ 'menuitem_upload_slots': ['max_upload_slots', 'max_upload_slots_global'],
+ 'menuitem_stop_seed_at_ratio': ['stop_ratio', 'stop_seed_ratio'],
+ }
+
+ other_dialog_info = {
+ 'menuitem_down_speed': [
+ _('Download Speed Limit'),
+ _('Set the maximum download speed'),
+ _('KiB/s'),
+ 'downloading.svg',
+ ],
+ 'menuitem_up_speed': [
+ _('Upload Speed Limit'),
+ _('Set the maximum upload speed'),
+ _('KiB/s'),
+ 'seeding.svg',
+ ],
+ 'menuitem_max_connections': [
+ _('Incoming Connections'),
+ _('Set the maximum incoming connections'),
+ '',
+ 'network-transmit-receive-symbolic',
+ ],
+ 'menuitem_upload_slots': [
+ _('Peer Upload Slots'),
+ _('Set the maximum upload slots'),
+ '',
+ 'view-sort-descending-symbolic',
+ ],
+ 'menuitem_stop_seed_at_ratio': [
+ _('Stop Seed At Ratio'),
+ 'Stop torrent seeding at ratio',
+ '',
+ None,
+ ],
+ }
+
+ core_key = status_map[widget.get_name()][0]
+ core_key_global = status_map[widget.get_name()][1]
+
+ def _on_torrent_status(status):
+ other_dialog = other_dialog_info[widget.get_name()]
+ # Add the default using status value
+ if status:
+ other_dialog.append(status[core_key_global])
+
+ def set_value(value):
+ if value is not None:
+ if value == 0:
+ value += -1
+ options = {core_key: value}
+ if core_key == 'stop_ratio':
+ options['stop_at_ratio'] = True
+ client.core.set_torrent_options(torrent_ids, options)
+
+ dialog = OtherDialog(*other_dialog)
+ dialog.run().addCallback(set_value)
+
+ torrent_ids = component.get('TorrentView').get_selected_torrents()
+ if len(torrent_ids) == 1:
+ core_key_global = core_key
+ d = component.get('SessionProxy').get_torrent_status(
+ torrent_ids[0], [core_key]
+ )
+ else:
+ d = client.core.get_config_values([core_key_global])
+ d.addCallback(_on_torrent_status)
+
+ def on_menuitem_set_automanaged_on(self, widget):
+ client.core.set_torrent_options(
+ component.get('TorrentView').get_selected_torrents(), {'auto_managed': True}
+ )
+
+ def on_menuitem_set_automanaged_off(self, widget):
+ client.core.set_torrent_options(
+ component.get('TorrentView').get_selected_torrents(),
+ {'auto_managed': False},
+ )
+
+ def on_menuitem_set_stop_seed_at_ratio_disable(self, widget):
+ client.core.set_torrent_options(
+ component.get('TorrentView').get_selected_torrents(),
+ {'stop_at_ratio': False},
+ )
+
+ def on_menuitem_sidebar_zero_toggled(self, widget):
+ self.config['sidebar_show_zero'] = widget.get_active()
+ component.get('FilterTreeView').update()
+
+ def on_menuitem_sidebar_trackers_toggled(self, widget):
+ self.config['sidebar_show_trackers'] = widget.get_active()
+ component.get('FilterTreeView').update()
+
+ def on_menuitem_sidebar_owners_toggled(self, widget):
+ self.config['sidebar_show_owners'] = widget.get_active()
+ component.get('FilterTreeView').update()
+
+ def _on_known_accounts(self, known_accounts):
+ known_accounts_to_log = []
+ for account in known_accounts:
+ account_to_log = {}
+ for key, value in account.copy().items():
+ if key == 'password':
+ value = '*' * len(value)
+ account_to_log[key] = value
+ known_accounts_to_log.append(account_to_log)
+ log.debug('_on_known_accounts: %s', known_accounts_to_log)
+ if len(known_accounts) <= 1:
+ return
+
+ self.builder.get_object('menuitem_change_owner').set_visible(True)
+
+ self.change_owner_submenu = Gtk.Menu()
+ self.change_owner_submenu_items = {}
+ maingroup = Gtk.RadioMenuItem()
+
+ self.change_owner_submenu_items[None] = Gtk.RadioMenuItem(maingroup)
+
+ for account in known_accounts:
+ username = account['username']
+ item = Gtk.RadioMenuItem.new_with_label(maingroup, username)
+ self.change_owner_submenu_items[username] = item
+ self.change_owner_submenu.append(item)
+ item.connect('toggled', self._on_change_owner_toggled, username)
+
+ self.change_owner_submenu.show_all()
+ self.change_owner_submenu_items[None].set_active(True)
+ self.change_owner_submenu_items[None].hide()
+ self.builder.get_object('menuitem_change_owner').connect(
+ 'activate', self._on_change_owner_submenu_active
+ )
+ self.builder.get_object('menuitem_change_owner').set_submenu(
+ self.change_owner_submenu
+ )
+
+ def _on_known_accounts_fail(self, reason):
+ self.builder.get_object('menuitem_change_owner').set_visible(False)
+
+ def _on_change_owner_submenu_active(self, widget):
+ log.debug('_on_change_owner_submenu_active')
+ selected = component.get('TorrentView').get_selected_torrents()
+ if len(selected) > 1:
+ self.change_owner_submenu_items[None].set_active(True)
+ return
+
+ torrent_owner = component.get('TorrentView').get_torrent_status(selected[0])[
+ 'owner'
+ ]
+ for username, item in self.change_owner_submenu_items.items():
+ item.set_active(username == torrent_owner)
+
+ def _on_change_owner_toggled(self, widget, username):
+ log.debug('_on_change_owner_toggled')
+ update_torrents = []
+ selected = component.get('TorrentView').get_selected_torrents()
+ for torrent_id in selected:
+ torrent_status = component.get('TorrentView').get_torrent_status(torrent_id)
+ if torrent_status['owner'] != username:
+ update_torrents.append(torrent_id)
+
+ if update_torrents:
+ log.debug('Setting torrent owner "%s" on %s', username, update_torrents)
+
+ def failed_change_owner(failure):
+ ErrorDialog(
+ _('Ownership Change Error'),
+ _('There was an error while trying changing ownership.'),
+ self.mainwindow.window,
+ details=failure.value.logable(),
+ ).run()
+
+ client.core.set_torrent_options(
+ update_torrents, {'owner': username}
+ ).addErrback(failed_change_owner)
diff --git a/deluge/ui/gtk3/menubar_osx.py b/deluge/ui/gtk3/menubar_osx.py
new file mode 100644
index 0000000..1df6fab
--- /dev/null
+++ b/deluge/ui/gtk3/menubar_osx.py
@@ -0,0 +1,87 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+from gi.repository.Gdk import ModifierType
+from gi.repository.Gtk import SeparatorMenuItem, accel_groups_from_object
+from gi.repository.Gtk.AccelFlags import VISIBLE
+
+from deluge.configmanager import ConfigManager
+
+
+def accel_swap(item, group, skey, smod, dkey, dmod):
+ # Accel map hack broken, see ticket #3078
+ # item.remove_accelerator(group, ord(skey), smod)
+ item.add_accelerator('activate', group, ord(dkey), dmod, VISIBLE)
+
+
+def accel_meta(item, group, key):
+ accel_swap(item, group, key, ModifierType.CONTROL_MASK, key, ModifierType.META_MASK)
+
+
+def menubar_osx(gtkui, osxapp):
+ main_builder = gtkui.mainwindow.get_builder()
+ menubar = main_builder.get_object('menubar')
+ group = accel_groups_from_object(gtkui.mainwindow.window)[0]
+
+ config = ConfigManager('gtk3ui.conf')
+
+ # NOTE: accel maps doesn't work with glade file format
+ # because of libglade not setting MenuItem accel groups
+ # That's why we remove / set accelerators by hand... (dirty)
+ # Clean solution: migrate glades files to gtkbuilder format
+ file_menu = main_builder.get_object('menu_file').get_submenu()
+ file_items = file_menu.get_children()
+ accel_meta(file_items[0], group, 'o')
+ accel_meta(file_items[1], group, 'n')
+ quit_all_item = file_items[3]
+ accel_swap(
+ quit_all_item,
+ group,
+ 'q',
+ ModifierType.SHIFT_MASK | ModifierType.CONTROL_MASK,
+ 'q',
+ ModifierType.SHIFT_MASK | ModifierType.META_MASK,
+ )
+ for item in range(2, len(file_items)): # remove quits
+ file_menu.remove(file_items[item])
+
+ menu_widget = main_builder.get_object('menu_edit')
+ edit_menu = menu_widget.get_submenu()
+ edit_items = edit_menu.get_children()
+ pref_item = edit_items[0]
+ accel_swap(
+ pref_item, group, 'p', ModifierType.CONTROL_MASK, ',', ModifierType.META_MASK
+ )
+ edit_menu.remove(pref_item)
+
+ conn_item = edit_items[1]
+ accel_meta(conn_item, group, 'm')
+ edit_menu.remove(conn_item)
+
+ menubar.remove(menu_widget)
+
+ help_menu = main_builder.get_object('menu_help').get_submenu()
+ help_items = help_menu.get_children()
+ about_item = help_items[4]
+ help_menu.remove(about_item)
+ help_menu.remove(help_items[3]) # separator
+
+ menubar.hide()
+ osxapp.set_menu_bar(menubar)
+ # populate app menu
+ osxapp.insert_app_menu_item(about_item, 0)
+ osxapp.insert_app_menu_item(SeparatorMenuItem(), 1)
+ osxapp.insert_app_menu_item(pref_item, 2)
+ if not config['standalone']:
+ osxapp.insert_app_menu_item(conn_item, 3)
+ if quit_all_item.get_visible():
+ osxapp.insert_app_menu_item(SeparatorMenuItem(), 4)
+ osxapp.insert_app_menu_item(quit_all_item, 5)
diff --git a/deluge/ui/gtk3/new_release_dialog.py b/deluge/ui/gtk3/new_release_dialog.py
new file mode 100644
index 0000000..6aa3282
--- /dev/null
+++ b/deluge/ui/gtk3/new_release_dialog.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+from gi.repository.Gtk import IconSize
+
+import deluge.common
+import deluge.component as component
+from deluge.configmanager import ConfigManager
+from deluge.ui.client import client
+
+
+class NewReleaseDialog(object):
+ def __init__(self):
+ pass
+
+ def show(self, available_version):
+ self.config = ConfigManager('gtk3ui.conf')
+ main_builder = component.get('MainWindow').get_builder()
+ self.dialog = main_builder.get_object('new_release_dialog')
+ # Set the version labels
+ if deluge.common.windows_check() or deluge.common.osx_check():
+ main_builder.get_object('image_new_release').set_from_file(
+ deluge.common.get_pixmap('deluge16.png')
+ )
+ else:
+ main_builder.get_object('image_new_release').set_from_icon_name(
+ 'deluge', IconSize.LARGE_TOOLBAR
+ )
+ main_builder.get_object('label_available_version').set_text(available_version)
+ main_builder.get_object('label_client_version').set_text(
+ deluge.common.get_version()
+ )
+ self.chk_not_show_dialog = main_builder.get_object(
+ 'chk_do_not_show_new_release'
+ )
+ main_builder.get_object('button_goto_downloads').connect(
+ 'clicked', self._on_button_goto_downloads
+ )
+ main_builder.get_object('button_close_new_release').connect(
+ 'clicked', self._on_button_close_new_release
+ )
+
+ if client.connected():
+
+ def on_info(version):
+ main_builder.get_object('label_server_version').set_text(version)
+ main_builder.get_object('label_server_version').show()
+ main_builder.get_object('label_server_version_text').show()
+
+ if not client.is_standalone():
+ main_builder.get_object('label_client_version_text').set_label(
+ _('<i>Client Version</i>')
+ )
+ client.daemon.info().addCallback(on_info)
+
+ self.dialog.show()
+
+ def _on_button_goto_downloads(self, widget):
+ deluge.common.open_url_in_browser('http://deluge-torrent.org')
+ self.config['show_new_releases'] = not self.chk_not_show_dialog.get_active()
+ self.dialog.destroy()
+
+ def _on_button_close_new_release(self, widget):
+ self.config['show_new_releases'] = not self.chk_not_show_dialog.get_active()
+ self.dialog.destroy()
diff --git a/deluge/ui/gtk3/options_tab.py b/deluge/ui/gtk3/options_tab.py
new file mode 100644
index 0000000..6a25fd1
--- /dev/null
+++ b/deluge/ui/gtk3/options_tab.py
@@ -0,0 +1,222 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
+# 2017 Calum Lind <calumlind+deluge@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+from gi.repository.Gdk import keyval_name
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from .path_chooser import PathChooser
+from .torrentdetails import Tab
+
+
+class OptionsTab(Tab):
+ def __init__(self):
+ super(OptionsTab, self).__init__('Options', 'options_tab', 'options_tab_label')
+
+ self.prev_torrent_ids = None
+ self.prev_status = None
+ self.inconsistent_keys = []
+
+ # Create TabWidget items with widget id, get/set func name, status key.
+ self.add_tab_widget('spin_max_download', 'value', ['max_download_speed'])
+ self.add_tab_widget('spin_max_upload', 'value', ['max_upload_speed'])
+ self.add_tab_widget('spin_max_connections', 'value_as_int', ['max_connections'])
+ self.add_tab_widget(
+ 'spin_max_upload_slots', 'value_as_int', ['max_upload_slots']
+ )
+ self.add_tab_widget(
+ 'chk_prioritize_first_last', 'active', ['prioritize_first_last_pieces']
+ )
+ self.add_tab_widget(
+ 'chk_sequential_download', 'active', ['sequential_download']
+ )
+ self.add_tab_widget('chk_auto_managed', 'active', ['auto_managed'])
+ self.add_tab_widget('chk_stop_at_ratio', 'active', ['stop_at_ratio'])
+ self.add_tab_widget('chk_remove_at_ratio', 'active', ['remove_at_ratio'])
+ self.add_tab_widget('spin_stop_ratio', 'value', ['stop_ratio'])
+ self.add_tab_widget('chk_move_completed', 'active', ['move_completed'])
+ self.add_tab_widget('chk_shared', 'active', ['shared'])
+ self.add_tab_widget('summary_owner', 'text', ['owner'])
+ self.add_tab_widget('chk_super_seeding', 'active', ['super_seeding'])
+
+ # Connect key press event for spin widgets.
+ for widget_id in self.tab_widgets:
+ if widget_id.startswith('spin_'):
+ self.tab_widgets[widget_id].obj.connect(
+ 'key-press-event', self.on_key_press_event
+ )
+
+ self.button_apply = self.main_builder.get_object('button_apply')
+
+ self.move_completed_path_chooser = PathChooser(
+ 'move_completed_paths_list', parent=component.get('MainWindow').window
+ )
+ self.move_completed_path_chooser.set_sensitive(
+ self.tab_widgets['chk_move_completed'].obj.get_active()
+ )
+ self.move_completed_path_chooser.connect(
+ 'text-changed', self.on_path_chooser_text_changed_event
+ )
+ self.status_keys.append('move_completed_path')
+
+ self.move_completed_hbox = self.main_builder.get_object(
+ 'hbox_move_completed_path_chooser'
+ )
+ self.move_completed_hbox.add(self.move_completed_path_chooser)
+ self.move_completed_hbox.show_all()
+
+ component.get('MainWindow').connect_signals(self)
+
+ def start(self):
+ pass
+
+ def stop(self):
+ pass
+
+ def clear(self):
+ self.prev_torrent_ids = None
+ self.prev_status = None
+ self.inconsistent_keys = []
+
+ def update(self):
+ torrent_ids = component.get('TorrentView').get_selected_torrents()
+
+ # Set True if torrent(s) selected in torrentview, else False.
+ self._child_widget.set_sensitive(bool(torrent_ids))
+
+ if torrent_ids:
+ if torrent_ids != self.prev_torrent_ids:
+ self.clear()
+
+ component.get('SessionProxy').get_torrents_status(
+ {'id': torrent_ids}, self.status_keys
+ ).addCallback(self.parse_torrents_statuses)
+
+ self.prev_torrent_ids = torrent_ids
+
+ def parse_torrents_statuses(self, statuses):
+ """Finds common status values to all torrrents in statuses.
+
+ Values which differ are replaced with config values.
+
+
+ Args:
+ statuses (dict): A status dict of {torrent_id: {key: value}}.
+
+ Returns:
+ dict: A single status dict.
+
+ """
+ status = {}
+ if len(statuses) == 1:
+ # A single torrent so pop torrent status.
+ status = statuses.popitem()[1]
+ self.button_apply.set_label('_Apply')
+ else:
+ for status_key in self.status_keys:
+ prev_value = None
+ for idx, status in enumerate(statuses.values()):
+ if idx == 0:
+ prev_value = status[status_key]
+ continue
+ elif status[status_key] != prev_value:
+ self.inconsistent_keys.append(status_key)
+ break
+ status[status_key] = prev_value
+ self.button_apply.set_label(_('_Apply to selected'))
+
+ self.on_get_torrent_status(status)
+
+ def on_get_torrent_status(self, new_status):
+ # So we don't overwrite the user's unapplied changes we only
+ # want to update values that have been applied in the core.
+ if self.prev_status is None:
+ self.prev_status = dict.fromkeys(new_status, None)
+
+ if new_status != self.prev_status:
+ for widget in self.tab_widgets.values():
+ status_key = widget.status_keys[0]
+ status_value = new_status[status_key]
+ if status_value != self.prev_status[status_key]:
+ set_func = 'set_' + widget.func.replace('_as_int', '')
+ getattr(widget.obj, set_func)(status_value)
+ if set_func == 'set_active':
+ widget.obj.set_inconsistent(
+ status_key in self.inconsistent_keys
+ )
+
+ if (
+ new_status['move_completed_path']
+ != self.prev_status['move_completed_path']
+ ):
+ text = new_status['move_completed_path']
+ self.move_completed_path_chooser.set_text(
+ text, cursor_end=False, default_text=True
+ )
+
+ # Update sensitivity of widgets.
+ self.tab_widgets['spin_stop_ratio'].obj.set_sensitive(
+ new_status['stop_at_ratio']
+ )
+ self.tab_widgets['chk_remove_at_ratio'].obj.set_sensitive(
+ new_status['stop_at_ratio']
+ )
+
+ # Ensure apply button sensitivity is set False.
+ self.button_apply.set_sensitive(False)
+ self.prev_status = new_status
+
+ # === Widget signal handlers === #
+
+ def on_button_apply_clicked(self, button):
+ options = {}
+ for widget in self.tab_widgets.values():
+ status_key = widget.status_keys[0]
+ if status_key == 'owner':
+ continue # A label so read-only
+ widget_value = getattr(widget.obj, 'get_' + widget.func)()
+ if widget_value != self.prev_status[status_key] or (
+ status_key in self.inconsistent_keys
+ and not widget.obj.get_inconsistent()
+ ):
+ options[status_key] = widget_value
+
+ if options.get('move_completed', False):
+ options['move_completed_path'] = self.move_completed_path_chooser.get_text()
+
+ client.core.set_torrent_options(self.prev_torrent_ids, options)
+ self.button_apply.set_sensitive(False)
+
+ def on_chk_move_completed_toggled(self, widget):
+ self.move_completed_path_chooser.set_sensitive(widget.get_active())
+ self.on_chk_toggled(widget)
+
+ def on_chk_stop_at_ratio_toggled(self, widget):
+ self.tab_widgets['spin_stop_ratio'].obj.set_sensitive(widget.get_active())
+ self.tab_widgets['chk_remove_at_ratio'].obj.set_sensitive(widget.get_active())
+ self.on_chk_toggled(widget)
+
+ def on_chk_toggled(self, widget):
+ widget.set_inconsistent(False)
+ self.button_apply.set_sensitive(True)
+
+ def on_spin_value_changed(self, widget):
+ self.button_apply.set_sensitive(True)
+
+ def on_key_press_event(self, widget, event):
+ keyname = keyval_name(event.keyval).lstrip('KP_').lower()
+ if keyname.isdigit() or keyname in ['period', 'minus', 'delete', 'backspace']:
+ self.button_apply.set_sensitive(True)
+
+ def on_path_chooser_text_changed_event(self, widget, path):
+ self.button_apply.set_sensitive(True)
diff --git a/deluge/ui/gtk3/path_chooser.py b/deluge/ui/gtk3/path_chooser.py
new file mode 100644
index 0000000..b722841
--- /dev/null
+++ b/deluge/ui/gtk3/path_chooser.py
@@ -0,0 +1,201 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2013 Bro <bro.development@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from .path_combo_chooser import PathChooserComboBox
+
+log = logging.getLogger(__name__)
+
+
+def singleton(cls):
+ instances = {}
+
+ def getinstance():
+ if cls not in instances:
+ instances[cls] = cls()
+ return instances[cls]
+
+ return getinstance
+
+
+@singleton
+class PathChoosersHandler(component.Component):
+ def __init__(self, paths_config_key=None):
+ # self.chooser_name = "PathChooser_%d" % (len(PathChooser.path_choosers) +1)
+ component.Component.__init__(self, 'PathChoosersHandler')
+ self.path_choosers = []
+ self.paths_list_keys = []
+ self.config_properties = {}
+ self.started = False
+ self.config_keys_to_funcs_mapping = {
+ 'path_chooser_show_chooser_button_on_localhost': 'filechooser_button_visible',
+ 'path_chooser_show_path_entry': 'path_entry_visible',
+ 'path_chooser_auto_complete_enabled': 'auto_complete_enabled',
+ 'path_chooser_show_folder_name': 'show_folder_name_on_button',
+ 'path_chooser_accelerator_string': 'accelerator_string',
+ 'path_chooser_show_hidden_files': 'show_hidden_files',
+ 'path_chooser_max_popup_rows': 'max_popup_rows',
+ }
+
+ def start(self):
+ self.started = True
+ self.update_config_from_core()
+
+ def stop(self):
+ self.started = False
+
+ def update_config_from_core(self):
+ def _on_config_values(config):
+ self.config_properties.update(config)
+ for chooser in self.path_choosers:
+ chooser.set_config(config)
+
+ keys = list(self.config_keys_to_funcs_mapping)
+ keys += self.paths_list_keys
+ client.core.get_config_values(keys).addCallback(_on_config_values)
+
+ def register_chooser(self, chooser):
+ chooser.config_key_funcs = {}
+ for key in self.config_keys_to_funcs_mapping:
+ chooser.config_key_funcs[key] = [None, None]
+ chooser.config_key_funcs[key][0] = getattr(
+ chooser, 'get_%s' % self.config_keys_to_funcs_mapping[key]
+ )
+ chooser.config_key_funcs[key][1] = getattr(
+ chooser, 'set_%s' % self.config_keys_to_funcs_mapping[key]
+ )
+
+ self.path_choosers.append(chooser)
+ if chooser.paths_config_key not in self.paths_list_keys:
+ self.paths_list_keys.append(chooser.paths_config_key)
+ if self.started:
+ self.update_config_from_core()
+ else:
+ chooser.set_config(self.config_properties)
+
+ def set_value_for_path_choosers(self, value, key):
+ for chooser in self.path_choosers:
+ chooser.config_key_funcs[key][1](value)
+
+ # Save to core
+ if key != 'path_chooser_max_popup_rows':
+ client.core.set_config({key: value})
+ else:
+ # Since the max rows value can be changed fast with a spinbutton, we
+ # delay saving to core until the values hasn't been changed in 1 second.
+ self.max_rows_value_set = value
+
+ def update(value_):
+ # The value hasn't been changed in one second, so save to core
+ if self.max_rows_value_set == value_:
+ client.core.set_config({'path_chooser_max_popup_rows': value})
+
+ from twisted.internet import reactor
+
+ reactor.callLater(1, update, value)
+
+ def on_list_values_changed(self, values, key, caller):
+ # Save to core
+ config = {key: values}
+ client.core.set_config(config)
+ # Set the values on all path choosers with that key
+ for chooser in self.path_choosers:
+ # Found chooser with values from 'key'
+ if chooser.paths_config_key == key:
+ chooser.set_values(values)
+
+ def get_config_keys(self):
+ keys = list(self.config_keys_to_funcs_mapping)
+ keys += self.paths_list_keys
+ return keys
+
+
+class PathChooser(PathChooserComboBox):
+ def __init__(self, paths_config_key=None, parent=None):
+ self.paths_config_key = paths_config_key
+ super(PathChooser, self).__init__(parent=parent)
+ self.chooser_handler = PathChoosersHandler()
+ self.chooser_handler.register_chooser(self)
+ self.set_auto_completer_func(self.on_completion)
+ self.connect('list-values-changed', self.on_list_values_changed_event)
+ self.connect(
+ 'auto-complete-enabled-toggled', self.on_auto_complete_enabled_toggled
+ )
+ self.connect('show-filechooser-toggled', self.on_show_filechooser_toggled)
+ self.connect(
+ 'show-folder-name-on-button', self.on_show_folder_on_button_toggled
+ )
+ self.connect('show-path-entry-toggled', self.on_show_path_entry_toggled)
+ self.connect('accelerator-set', self.on_accelerator_set)
+ self.connect('max-rows-changed', self.on_max_rows_changed)
+ self.connect('show-hidden-files-toggled', self.on_show_hidden_files_toggled)
+
+ def on_auto_complete_enabled_toggled(self, widget, value):
+ self.chooser_handler.set_value_for_path_choosers(
+ value, 'path_chooser_auto_complete_enabled'
+ )
+
+ def on_show_filechooser_toggled(self, widget, value):
+ self.chooser_handler.set_value_for_path_choosers(
+ value, 'path_chooser_show_chooser_button_on_localhost'
+ )
+
+ def on_show_folder_on_button_toggled(self, widget, value):
+ self.chooser_handler.set_value_for_path_choosers(
+ value, 'path_chooser_show_folder_name'
+ )
+
+ def on_show_path_entry_toggled(self, widget, value):
+ self.chooser_handler.set_value_for_path_choosers(
+ value, 'path_chooser_show_path_entry'
+ )
+
+ def on_accelerator_set(self, widget, value):
+ self.chooser_handler.set_value_for_path_choosers(
+ value, 'path_chooser_accelerator_string'
+ )
+
+ def on_show_hidden_files_toggled(self, widget, value):
+ self.chooser_handler.set_value_for_path_choosers(
+ value, 'path_chooser_show_hidden_files'
+ )
+
+ def on_max_rows_changed(self, widget, value):
+ self.chooser_handler.set_value_for_path_choosers(
+ value, 'path_chooser_max_popup_rows'
+ )
+
+ def on_list_values_changed_event(self, widget, values):
+ self.chooser_handler.on_list_values_changed(values, self.paths_config_key, self)
+
+ def set_config(self, config):
+ self.config = config
+ for key in self.config_key_funcs:
+ if key in config:
+ try:
+ self.config_key_funcs[key][1](config[key])
+ except TypeError as ex:
+ log.warning('TypeError: %s', ex)
+
+ # Set the saved paths
+ if self.paths_config_key and self.paths_config_key in config:
+ self.set_values(config[self.paths_config_key])
+
+ def on_completion(self, args):
+ def on_paths_cb(args):
+ self.complete(args)
+
+ d = client.core.get_completion_paths(args)
+ d.addCallback(on_paths_cb)
diff --git a/deluge/ui/gtk3/path_combo_chooser.py b/deluge/ui/gtk3/path_combo_chooser.py
new file mode 100755
index 0000000..c26289d
--- /dev/null
+++ b/deluge/ui/gtk3/path_combo_chooser.py
@@ -0,0 +1,1742 @@
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2013 Bro <bro.development@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import division, print_function, unicode_literals
+
+import os
+import warnings
+
+from gi.repository import Gdk, GObject, Gtk
+from gi.repository.GObject import SignalFlags
+
+from deluge.common import PY2, resource_filename
+from deluge.path_chooser_common import get_completion_paths
+
+# Filter the pygobject signal warning:
+# g_value_get_int: assertion 'G_VALUE_HOLDS_INT (value)' failed.
+# See: https://gitlab.gnome.org/GNOME/pygobject/issues/12
+warnings.filterwarnings('ignore', '.*g_value_get_int.*G_VALUE_HOLDS_INT.*', Warning)
+
+
+def is_ascii_value(keyval, ascii_key):
+ try:
+ # Set show/hide hidden files
+ if chr(keyval) == ascii_key:
+ return True
+ except ValueError:
+ # Not in ascii range
+ pass
+ return False
+
+
+def key_is_up(keyval):
+ return keyval == Gdk.KEY_Up or keyval == Gdk.KEY_KP_Up
+
+
+def key_is_down(keyval):
+ return keyval == Gdk.KEY_Down or keyval == Gdk.KEY_KP_Down
+
+
+def key_is_up_or_down(keyval):
+ return key_is_up(keyval) or key_is_down(keyval)
+
+
+def key_is_pgup_or_pgdown(keyval):
+ return keyval == Gdk.KEY_Page_Down or keyval == Gdk.KEY_Page_Up
+
+
+def key_is_enter(keyval):
+ return keyval == Gdk.KEY_Return or keyval == Gdk.KEY_KP_Enter
+
+
+def path_without_trailing_path_sep(path):
+ while path.endswith('/') or path.endswith('\\'):
+ if path == '/':
+ return path
+ path = path[0:-1]
+ return path
+
+
+class ValueList(object):
+
+ paths_without_trailing_path_sep = False
+
+ def get_values_count(self):
+ return len(self.tree_store)
+
+ def get_values(self):
+ """
+ Returns the values in the list.
+ """
+ values = []
+ for row in self.tree_store:
+ values.append(row[0])
+ return values
+
+ def add_values(
+ self, paths, append=True, scroll_to_row=False, clear=False, emit_signal=False
+ ):
+ """
+ Add paths to the liststore
+
+ :param paths: the paths to add
+ :type paths: list
+ :param append: if the values should be appended or inserted
+ :type append: boolean
+ :param scroll_to_row: if the treeview should scroll to the new row
+ :type scroll_to_row: boolean
+
+ """
+ if clear:
+ self.tree_store.clear()
+
+ for path in paths:
+ if self.paths_without_trailing_path_sep:
+ path = path_without_trailing_path_sep(path)
+ if append:
+ tree_iter = self.tree_store.append([path])
+ else:
+ tree_iter = self.tree_store.insert(0, [path])
+
+ if scroll_to_row:
+ self.treeview.grab_focus()
+ tree_path = self.tree_store.get_path(tree_iter)
+ # Scroll to path
+ self.handle_list_scroll(path=tree_path)
+
+ if emit_signal:
+ self.emit('list-value-added', paths)
+ self.emit('list-values-changed', self.get_values())
+
+ def set_values(self, paths, scroll_to_row=False, preserve_selection=True):
+ """
+ Add paths to the liststore
+
+ :param paths: the paths to add
+ :type paths: list
+ :param scroll_to_row: if the treeview should scroll to the new row
+ :type scroll_to_row: boolean
+
+ """
+ if not (isinstance(paths, list) or isinstance(paths, tuple)):
+ return
+ sel = None
+ if preserve_selection:
+ sel = self.get_selection_path()
+ self.add_values(paths, scroll_to_row=scroll_to_row, clear=True)
+ if sel:
+ self.treeview.get_selection().select_path(sel)
+
+ def get_selection_path(self):
+ """Returns the (first) selected path from a treeview"""
+ tree_selection = self.treeview.get_selection()
+ model, tree_paths = tree_selection.get_selected_rows()
+ if len(tree_paths) > 0:
+ return tree_paths[0]
+ return None
+
+ def get_selected_value(self):
+ path = self.get_selection_path()
+ if path:
+ return self.tree_store[path][0]
+ return None
+
+ def remove_selected_path(self):
+ path = self.get_selection_path()
+ if path:
+ path_value = self.tree_store[path][0]
+ del self.tree_store[path]
+ index = path[0]
+ # The last row was deleted
+ if index == len(self.tree_store):
+ index -= 1
+ if index >= 0:
+ path = (index,)
+ self.treeview.set_cursor(path)
+ self.set_path_selected(path)
+ self.emit('list-value-removed', path_value)
+ self.emit('list-values-changed', self.get_values())
+
+ def set_selected_value(self, value, select_first=False):
+ """
+ Select the row of the list with value
+
+ :param value: the value to be selected
+ :type value: str
+ :param select_first: if the first item should be selected if the value if not found.
+ :type select_first: boolean
+
+ """
+ for i, row in enumerate(self.tree_store):
+ if row[0] == value:
+ self.treeview.set_cursor((i))
+ return
+ # The value was not found
+ if select_first:
+ self.treeview.set_cursor((0,))
+ else:
+ self.treeview.get_selection().unselect_all()
+
+ def set_path_selected(self, path):
+ self.treeview.get_selection().select_path(path)
+
+ def on_value_list_treeview_key_press_event(self, widget, event):
+ """
+ Mimics Combobox behavior
+
+ Escape or Alt+Up: Close
+ Enter or Return : Select
+ """
+ keyval = event.keyval
+ state = event.get_state() & Gtk.accelerator_get_default_mod_mask()
+ alt_up = (state == Gdk.ModifierType.MOD1_MASK) and key_is_up(keyval)
+ if keyval == Gdk.KEY_Escape or alt_up:
+ self.popdown()
+ return True
+ # Set entry value to the selected row
+ elif key_is_enter(keyval):
+ path = self.get_selection_path()
+ if path:
+ self.set_entry_value(path, popdown=True)
+ return True
+ return False
+
+ def on_treeview_mouse_button_press_event(self, treeview, event, double_click=False):
+ """
+ When left clicking twice, the row value is set for the text entry
+ and the popup is closed.
+
+ """
+ # This is left click
+ if event.button != 3:
+ # Double clicked a row, set this as the entry value
+ # and close the popup
+ if (double_click and event.type == Gdk.EventType._2BUTTON_PRESS) or (
+ not double_click and event.type == Gdk.EventType.BUTTON_PRESS
+ ):
+ path = self.get_selection_path()
+ if path:
+ self.set_entry_value(path, popdown=True)
+ return True
+ return False
+
+ def handle_list_scroll(
+ self, _next=None, path=None, set_entry=False, swap=False, scroll_window=False
+ ):
+ """
+ Handles changes to the row selection.
+
+ :param _next: the direction to change selection. True means down and False means up.
+ None means no change.
+ :type _next: boolean/None
+ :param path: the current path. If None, the currently selected path is used.
+ :type path: tuple
+ :param set_entry: if the new value should be set in the text entry.
+ :type set_entry: boolean
+ :param swap: if the old and new value should be swapped
+ :type swap: boolean
+
+ """
+ if scroll_window:
+ adjustment = self.completion_scrolled_window.get_vadjustment()
+
+ visible_rows_height = self.get_values_count()
+ if visible_rows_height > self.max_visible_rows:
+ visible_rows_height = self.max_visible_rows
+
+ visible_rows_height *= self.row_height
+ value = adjustment.get_value()
+
+ # Max adjustment value
+ max_value = adjustment.get_upper() - visible_rows_height
+ # Set adjustment increment to 3 times the row height
+ adjustment.set_step_increment(self.row_height * 3)
+
+ if _next:
+ # If number of values is less than max rows, no scroll
+ if self.get_values_count() < self.max_visible_rows:
+ return
+ value += adjustment.get_step_increment()
+ if value > max_value:
+ value = max_value
+ else:
+ value -= adjustment.get_step_increment()
+ if value < 0:
+ value = 0
+ adjustment.set_value(value)
+ return
+
+ if path is None:
+ path = self.get_selection_path()
+ if not path:
+ # These options require a selected path
+ if set_entry or swap:
+ return
+ # This is a regular scroll, not setting value in entry or swapping rows,
+ # so we find a path value anyways
+ path = (0,)
+ cursor = self.treeview.get_cursor()
+ if cursor is not None and cursor[0] is not None:
+ path = cursor[0]
+ else:
+ # Since cursor is none, we won't advance the index
+ _next = None
+
+ # If _next is None, we won't change the selection
+ if _next is not None:
+ # We move the selection either one up or down.
+ # If we reach end of list, we wrap
+ index = path[0] if path else 0
+ index = index + 1 if _next else index - 1
+ if index >= len(self.tree_store):
+ index = 0
+ elif index < 0:
+ index = len(self.tree_store) - 1
+
+ # We have the index for the new path
+ new_path = index
+ if swap:
+ p1 = self.tree_store[path][0]
+ p2 = self.tree_store[new_path][0]
+ self.tree_store.swap(
+ self.tree_store.get_iter(path), self.tree_store.get_iter(new_path)
+ )
+ self.emit('list-values-reordered', [p1, p2])
+ self.emit('list-values-changed', self.get_values())
+ path = new_path
+
+ self.treeview.set_cursor(path)
+ self.treeview.get_selection().select_path(path)
+ if set_entry:
+ self.set_entry_value(path)
+
+
+class StoredValuesList(ValueList):
+ def __init__(self):
+ self.tree_store = self.builder.get_object('stored_values_tree_store')
+ self.tree_column = self.builder.get_object('stored_values_treeview_column')
+ self.rendererText = self.builder.get_object('stored_values_cellrenderertext')
+ self.paths_without_trailing_path_sep = False
+
+ # Add signal handlers
+ self.signal_handlers[
+ 'on_stored_values_treeview_mouse_button_press_event'
+ ] = self.on_treeview_mouse_button_press_event
+
+ self.signal_handlers[
+ 'on_stored_values_treeview_key_press_event'
+ ] = self.on_stored_values_treeview_key_press_event
+ self.signal_handlers[
+ 'on_stored_values_treeview_key_release_event'
+ ] = self.on_stored_values_treeview_key_release_event
+
+ self.signal_handlers[
+ 'on_cellrenderertext_edited'
+ ] = self.on_cellrenderertext_edited
+
+ def on_cellrenderertext_edited(self, cellrenderertext, path, new_text):
+ """
+ Callback on the 'edited' signal.
+
+ Sets the new text in the path and disables editing on the renderer.
+ """
+ new_text = path_without_trailing_path_sep(new_text)
+ self.tree_store[path][0] = new_text
+ self.rendererText.set_property('editable', False)
+
+ def on_edit_path(self, path, column):
+ """
+ Starts editing on the provided path
+
+ :param path: the paths to edit
+ :type path: tuple
+ :param column: the column to edit
+ :type column: Gtk.TreeViewColumn
+
+ """
+ self.rendererText.set_property('editable', True)
+ self.treeview.grab_focus()
+ self.treeview.set_cursor(path, column=column, start_editing=True)
+
+ def on_treeview_mouse_button_press_event(self, treeview, event):
+ """
+ Shows popup on selected row when right clicking
+ When left clicking twice, the row value is set for the text entry
+ and the popup is closed.
+
+ """
+ # This is left click
+ if event.button != 3:
+ super(StoredValuesList, self).on_treeview_mouse_button_press_event(
+ treeview, event, double_click=True
+ )
+ return False
+
+ # This is right click, create popup menu for this row
+ x = int(event.x)
+ y = int(event.y)
+ time = event.time
+ pthinfo = treeview.get_path_at_pos(x, y)
+ if pthinfo is not None:
+ path, col, cellx, celly = pthinfo
+ treeview.grab_focus()
+ treeview.set_cursor(path, col, 0)
+
+ self.path_list_popup = Gtk.Menu()
+ menuitem_edit = Gtk.MenuItem.new_with_label('Edit path')
+ self.path_list_popup.append(menuitem_edit)
+ menuitem_remove = Gtk.MenuItem.new_with_label('Remove path')
+ self.path_list_popup.append(menuitem_remove)
+
+ def on_edit_clicked(widget, path):
+ self.on_edit_path(path, self.tree_column)
+
+ def on_remove_clicked(widget, path):
+ self.remove_selected_path()
+
+ menuitem_edit.connect('activate', on_edit_clicked, path)
+ menuitem_remove.connect('activate', on_remove_clicked, path)
+ self.path_list_popup.popup(None, None, None, path, event.button, time)
+ self.path_list_popup.show_all()
+
+ def remove_selected_path(self):
+ ValueList.remove_selected_path(self)
+ # Resize popup
+ PathChooserPopup.popup(self)
+
+ def on_stored_values_treeview_key_press_event(self, widget, event):
+ super(StoredValuesList, self).on_value_list_treeview_key_press_event(
+ widget, event
+ )
+ # Prevent the default event handler to move the cursor in the list
+ if key_is_up_or_down(event.keyval):
+ return True
+
+ def on_stored_values_treeview_key_release_event(self, widget, event):
+ """
+ Mimics Combobox behavior
+
+ Escape or Alt+Up: Close
+ Enter or Return : Select
+
+ """
+ keyval = event.keyval
+ ctrl = event.get_state() & Gdk.ModifierType.CONTROL_MASK
+
+ # Edit selected row
+ if keyval in [Gdk.KEY_Left, Gdk.KEY_Right, Gdk.KEY_space]:
+ path = self.get_selection_path()
+ if path:
+ self.on_edit_path(path, self.tree_column)
+ elif key_is_up_or_down(keyval):
+ # Swap the row value
+ if event.get_state() & Gdk.ModifierType.CONTROL_MASK:
+ self.handle_list_scroll(_next=key_is_down(keyval), swap=True)
+ else:
+ self.handle_list_scroll(_next=key_is_down(keyval))
+ elif key_is_pgup_or_pgdown(event.keyval):
+ # The cursor has been changed by the default key-press-event handler
+ # so set the path of the cursor selected
+ self.set_path_selected(self.treeview.get_cursor()[0])
+ elif ctrl:
+ # Handle key bindings for manipulating the list
+ # Remove the selected entry
+ if is_ascii_value(keyval, 'r'):
+ self.remove_selected_path()
+ return True
+ # Add current value to saved list
+ elif is_ascii_value(keyval, 's'):
+ super(
+ PathChooserComboBox, self
+ ).add_current_value_to_saved_list() # pylint: disable=bad-super-call
+ return True
+ # Edit selected value
+ elif is_ascii_value(keyval, 'e'):
+ self.edit_selected_path()
+ return True
+
+
+class CompletionList(ValueList):
+ def __init__(self):
+ self.tree_store = self.builder.get_object('completion_tree_store')
+ self.tree_column = self.builder.get_object('completion_treeview_column')
+ self.rendererText = self.builder.get_object('completion_cellrenderertext')
+ self.completion_scrolled_window = self.builder.get_object(
+ 'completion_scrolled_window'
+ )
+ self.signal_handlers[
+ 'on_completion_treeview_key_press_event'
+ ] = self.on_completion_treeview_key_press_event
+ self.signal_handlers[
+ 'on_completion_treeview_motion_notify_event'
+ ] = self.on_completion_treeview_motion_notify_event
+
+ # Add super class signal handler
+ self.signal_handlers['on_completion_treeview_mouse_button_press_event'] = super(
+ CompletionList, self
+ ).on_treeview_mouse_button_press_event
+
+ def reduce_values(self, prefix):
+ """
+ Reduce the values in the liststore to those starting with the prefix.
+
+ :param prefix: the prefix to be matched
+ :type paths: string
+
+ """
+ values = self.get_values()
+ matching_values = []
+ for v in values:
+ if v.startswith(prefix):
+ matching_values.append(v)
+ self.add_values(matching_values, clear=True)
+
+ def on_completion_treeview_key_press_event(self, widget, event):
+ ret = super(CompletionList, self).on_value_list_treeview_key_press_event(
+ widget, event
+ )
+ if ret:
+ return ret
+ keyval = event.keyval
+ ctrl = event.get_state() & Gdk.ModifierType.CONTROL_MASK
+ if key_is_up_or_down(keyval):
+ self.handle_list_scroll(_next=key_is_down(keyval))
+ return True
+ elif ctrl:
+ # Set show/hide hidden files
+ if is_ascii_value(keyval, 'h'):
+ self.path_entry.set_show_hidden_files(
+ not self.path_entry.get_show_hidden_files(), do_completion=True
+ )
+ return True
+
+ def on_completion_treeview_motion_notify_event(self, widget, event):
+ if event.is_hint:
+ x, y, state = event.window.get_pointer()
+ else:
+ x = event.x
+ y = event.y
+
+ path = self.treeview.get_path_at_pos(int(x), int(y))
+ if path:
+ self.handle_list_scroll(path=path[0], _next=None)
+
+
+class PathChooserPopup(object):
+ """This creates the popop window for the ComboEntry."""
+
+ def __init__(self, min_visible_rows, max_visible_rows, popup_alignment_widget):
+ self.min_visible_rows = min_visible_rows
+ # Maximum number of rows to display without scrolling
+ self.set_max_popup_rows(max_visible_rows)
+ self.popup_window.realize()
+ self.alignment_widget = popup_alignment_widget
+ self.popup_buttonbox = (
+ None
+ ) # If set, the height of this widget is the minimum height
+
+ def popup(self):
+ """Make the popup visible."""
+ # Entry is not yet visible
+ if not self.path_entry.get_realized():
+ return
+ self.set_window_position_and_size()
+
+ def popdown(self):
+ if not self.is_popped_up():
+ return
+ if not self.path_entry.get_realized():
+ return
+ self.popup_window.grab_remove()
+ self.popup_window.hide()
+
+ def is_popped_up(self):
+ """Check if window is popped up.
+
+ Returns:
+ bool: True if popped up, False otherwise.
+
+ """
+ return self.popup_window.get_mapped()
+
+ def set_window_position_and_size(self):
+ if len(self.tree_store) < self.min_visible_rows:
+ return False
+ x, y, width, height = self.get_position()
+ self.popup_window.set_size_request(width, height)
+ self.popup_window.resize(width, height)
+ self.popup_window.move(x, y)
+ self.popup_window.show_all()
+
+ def get_position(self):
+ """
+ Returns the size of the popup window and the coordinates on the screen.
+
+ """
+
+ # Necessary for the first call, to make treeview.size_request give sensible values
+ # self.popup_window.realize()
+ self.treeview.realize()
+
+ # We start with the coordinates of the parent window
+ z, x, y = self.path_entry.get_window().get_origin()
+
+ # Add the position of the alignment_widget relative to the parent window.
+ x += self.alignment_widget.get_allocation().x
+ y += self.alignment_widget.get_allocation().y
+
+ height_extra = 8
+ buttonbox_width = 0
+ height = self.popup_window.get_preferred_height()[1]
+ width = self.popup_window.get_preferred_width()[1]
+
+ if self.popup_buttonbox:
+ buttonbox_height = max(
+ self.popup_buttonbox.get_preferred_height()[1],
+ self.popup_buttonbox.get_allocation().height,
+ )
+ buttonbox_width = max(
+ self.popup_buttonbox.get_preferred_width()[1],
+ self.popup_buttonbox.get_allocation().width,
+ )
+ treeview_width = self.treeview.get_preferred_width()[1]
+ # After removing an element from the tree store, self.treeview.get_preferred_width()[0]
+ # returns -1 for some reason, so the requested width cannot be used until the treeview
+ # has been displayed once.
+ if treeview_width != -1:
+ width = treeview_width + buttonbox_width
+ # The list is empty, so ignore initial popup width request
+ # Will be set to the minimum width next
+ elif len(self.tree_store) == 0:
+ width = 0
+
+ if width < self.alignment_widget.get_allocation().width:
+ width = self.alignment_widget.get_allocation().width
+
+ # 10 is extra spacing
+ content_width = self.treeview.get_preferred_width()[1] + buttonbox_width + 10
+
+ # Adjust height according to number of list items
+ if len(self.tree_store) > 0 and self.max_visible_rows > 0:
+ # The height for one row in the list
+ self.row_height = self.treeview.get_preferred_height()[1] / len(
+ self.tree_store
+ )
+ # Set height to number of rows
+ height = len(self.tree_store) * self.row_height + height_extra
+ # Adjust the height according to the max number of rows
+ max_height = self.row_height * self.max_visible_rows
+ # Restrict height to max_visible_rows
+ if max_height + height_extra < height:
+ height = max_height
+ height += height_extra
+ # Increase width because of vertical scrollbar
+ content_width += 15
+
+ if self.popup_buttonbox:
+ # Minimum height is the height of the button box
+ if height < buttonbox_height + height_extra:
+ height = buttonbox_height + height_extra
+
+ if content_width > width:
+ width = content_width
+
+ screen = self.path_entry.get_screen()
+ monitor_num = screen.get_monitor_at_window(self.path_entry.get_window())
+ monitor = screen.get_monitor_geometry(monitor_num)
+
+ if x < monitor.x:
+ x = monitor.x
+ elif x + width > monitor.x + monitor.width:
+ x = monitor.x + monitor.width - width
+
+ # Set the position
+ if (
+ y + self.path_entry.get_allocation().height + height
+ <= monitor.y + monitor.height
+ ):
+ y += self.path_entry.get_allocation().height
+ # Not enough space downwards on the screen
+ elif y - height >= monitor.y:
+ y -= height
+ elif (
+ monitor.y + monitor.height - (y + self.path_entry.get_allocation().height)
+ > y - monitor.y
+ ):
+ y += self.path_entry.get_allocation().height
+ height = monitor.y + monitor.height - y
+ else:
+ height = y - monitor.y
+ y = monitor.y
+
+ return x, y, width, height
+
+ def popup_grab_window(self):
+ activate_time = 0
+ if (
+ Gdk.pointer_grab(
+ self.popup_window.get_window(),
+ True,
+ (
+ Gdk.EventMask.BUTTON_PRESS_MASK
+ | Gdk.EventMask.BUTTON_RELEASE_MASK
+ | Gdk.EventMask.POINTER_MOTION_MASK
+ ),
+ None,
+ None,
+ activate_time,
+ )
+ == 0
+ ):
+ if (
+ Gdk.keyboard_grab(self.popup_window.get_window(), True, activate_time)
+ == 0
+ ):
+ return True
+ else:
+ self.popup_window.get_window().get_display().pointer_ungrab(
+ activate_time
+ )
+ return False
+ return False
+
+ def set_entry_value(self, path, popdown=False):
+ """
+
+ Sets the text of the entry to the value in path
+ """
+ self.path_entry.set_text(
+ self.tree_store[path][0], set_file_chooser_folder=True, trigger_event=True
+ )
+ if popdown:
+ self.popdown()
+
+ def set_max_popup_rows(self, rows):
+ try:
+ int(rows)
+ except Exception:
+ self.max_visible_rows = 20
+ return
+ self.max_visible_rows = rows
+
+ def get_max_popup_rows(self):
+ return self.max_visible_rows
+
+ #################
+ # Callbacks
+ #################
+
+ def on_popup_window_button_press_event(self, window, event):
+ # If we're clicking outside of the window close the popup
+ allocation = self.popup_window.get_allocation()
+
+ if (event.x < allocation.x or event.x > allocation.width) or (
+ event.y < allocation.y or event.y > allocation.height
+ ):
+ self.popdown()
+
+
+class StoredValuesPopup(StoredValuesList, PathChooserPopup):
+ """
+
+ The stored values popup
+
+ """
+
+ def __init__(self, builder, path_entry, max_visible_rows, popup_alignment_widget):
+ self.builder = builder
+ self.treeview = self.builder.get_object('stored_values_treeview')
+ self.popup_window = self.builder.get_object('stored_values_popup_window')
+ self.button_default = self.builder.get_object('button_default')
+ self.path_entry = path_entry
+ self.text_entry = path_entry.text_entry
+
+ self.signal_handlers = {}
+ PathChooserPopup.__init__(self, 0, max_visible_rows, popup_alignment_widget)
+ StoredValuesList.__init__(self)
+
+ self.popup_buttonbox = self.builder.get_object('buttonbox')
+
+ # Add signal handlers
+ self.signal_handlers[
+ 'on_buttonbox_key_press_event'
+ ] = self.on_buttonbox_key_press_event
+ self.signal_handlers[
+ 'on_stored_values_treeview_scroll_event'
+ ] = self.on_scroll_event
+ self.signal_handlers[
+ 'on_button_toggle_dropdown_scroll_event'
+ ] = self.on_scroll_event
+ self.signal_handlers['on_entry_text_scroll_event'] = self.on_scroll_event
+ self.signal_handlers[
+ 'on_stored_values_popup_window_focus_out_event'
+ ] = self.on_stored_values_popup_window_focus_out_event
+ # For when clicking outside the popup
+ self.signal_handlers[
+ 'on_stored_values_popup_window_button_press_event'
+ ] = self.on_popup_window_button_press_event
+
+ # Buttons for manipulating the list
+ self.signal_handlers['on_button_add_clicked'] = self.on_button_add_clicked
+ self.signal_handlers['on_button_edit_clicked'] = self.on_button_edit_clicked
+ self.signal_handlers['on_button_remove_clicked'] = self.on_button_remove_clicked
+ self.signal_handlers['on_button_up_clicked'] = self.on_button_up_clicked
+ self.signal_handlers['on_button_down_clicked'] = self.on_button_down_clicked
+ self.signal_handlers[
+ 'on_button_default_clicked'
+ ] = self.on_button_default_clicked
+ self.signal_handlers[
+ 'on_button_properties_clicked'
+ ] = self.path_entry._on_button_properties_clicked
+
+ def popup(self):
+ """
+ Makes the popup visible.
+
+ """
+ # Calling super popup
+ PathChooserPopup.popup(self)
+ self.popup_window.grab_focus()
+
+ if not self.treeview.has_focus():
+ self.treeview.grab_focus()
+ if not self.popup_grab_window():
+ self.popup_window.hide()
+ return
+
+ self.popup_window.grab_add()
+ # Set value selected if it exists
+ self.set_selected_value(
+ path_without_trailing_path_sep(self.path_entry.get_text())
+ )
+
+ #################
+ # Callbacks
+ #################
+
+ def on_stored_values_popup_window_focus_out_event(self, entry, event):
+ """
+ Popup sometimes loses the focus to the text entry, e.g. when right click
+ shows a popup menu on a row. This regains the focus.
+ """
+ self.popup_grab_window()
+ return True
+
+ def on_scroll_event(self, widget, event):
+ """
+ Handles scroll events from text entry, toggle button and treeview
+
+ """
+
+ swap = event.get_state() & Gdk.ModifierType.CONTROL_MASK
+ scroll_window = event.get_state() & Gdk.ModifierType.SHIFT_MASK
+ self.handle_list_scroll(
+ _next=event.direction == Gdk.ScrollDirection.DOWN,
+ set_entry=widget != self.treeview,
+ swap=swap,
+ scroll_window=scroll_window,
+ )
+ return True
+
+ def on_buttonbox_key_press_event(self, widget, event):
+ """
+ Handles when Escape or ALT+arrow up is pressed when focus
+ is on any of the buttons in the popup
+ """
+ keyval = event.keyval
+ state = event.get_state() & Gtk.accelerator_get_default_mod_mask()
+ if keyval == Gdk.KEY_Escape or (
+ key_is_up(keyval) and state == Gdk.ModifierType.MOD1_MASK
+ ):
+ self.popdown()
+ return True
+ return False
+
+ # --------------------------------------------------
+ # Funcs and callbacks on the buttons to manipulate the list
+ # --------------------------------------------------
+ def add_current_value_to_saved_list(self):
+ text = self.path_entry.get_text()
+ text = path_without_trailing_path_sep(text)
+ values = self.get_values()
+ if text in values:
+ # Make the matching value selected
+ self.set_selected_value(text)
+ self.handle_list_scroll()
+ return True
+ self.add_values([text], scroll_to_row=True, append=False, emit_signal=True)
+
+ def edit_selected_path(self):
+ path = self.get_selection_path()
+ if path:
+ self.on_edit_path(path, self.tree_column)
+
+ def on_button_add_clicked(self, widget):
+ self.add_current_value_to_saved_list()
+ self.popup()
+
+ def on_button_edit_clicked(self, widget):
+ self.edit_selected_path()
+
+ def on_button_remove_clicked(self, widget):
+ self.remove_selected_path()
+ return True
+
+ def on_button_up_clicked(self, widget):
+ self.handle_list_scroll(_next=False, swap=True)
+
+ def on_button_down_clicked(self, widget):
+ self.handle_list_scroll(_next=True, swap=True)
+
+ def on_button_default_clicked(self, widget):
+ if self.default_text:
+ self.set_text(self.default_text, trigger_event=True)
+
+
+class PathCompletionPopup(CompletionList, PathChooserPopup):
+ """
+
+ The auto completion popup
+
+ """
+
+ def __init__(self, builder, path_entry, max_visible_rows):
+ self.builder = builder
+ self.treeview = self.builder.get_object('completion_treeview')
+ self.popup_window = self.builder.get_object('completion_popup_window')
+ self.path_entry = path_entry
+ self.text_entry = path_entry.text_entry
+ self.show_hidden_files = False
+
+ self.signal_handlers = {}
+ PathChooserPopup.__init__(self, 1, max_visible_rows, self.text_entry)
+ CompletionList.__init__(self)
+
+ # Add signal handlers
+ self.signal_handlers[
+ 'on_completion_treeview_scroll_event'
+ ] = self.on_scroll_event
+ self.signal_handlers[
+ 'on_completion_popup_window_focus_out_event'
+ ] = self.on_completion_popup_window_focus_out_event
+
+ # For when clicking outside the popup
+ self.signal_handlers[
+ 'on_completion_popup_window_button_press_event'
+ ] = self.on_popup_window_button_press_event
+
+ def popup(self):
+ """
+ Makes the popup visible.
+
+ """
+ PathChooserPopup.popup(self)
+ self.popup_window.grab_focus()
+
+ if not self.treeview.has_focus():
+ self.treeview.grab_focus()
+
+ if not self.popup_grab_window():
+ self.popup_window.hide()
+ return
+
+ self.popup_window.grab_add()
+ self.text_entry.grab_focus()
+ self.text_entry.set_position(len(self.path_entry.text_entry.get_text()))
+ self.set_selected_value(
+ path_without_trailing_path_sep(self.path_entry.get_text()),
+ select_first=True,
+ )
+
+ #################
+ # Callbacks
+ #################
+
+ def on_completion_popup_window_focus_out_event(self, entry, event):
+ """
+ Popup sometimes loses the focus to the text entry, e.g. when right click
+ shows a popup menu on a row. This regains the focus.
+ """
+ self.popup_grab_window()
+ return True
+
+ def on_scroll_event(self, widget, event):
+ """
+ Handles scroll events from the treeview
+
+ """
+ x, y = event.window.get_pointer()
+ self.handle_list_scroll(
+ _next=event.direction == Gdk.ScrollDirection.DOWN,
+ set_entry=widget != self.treeview,
+ scroll_window=True,
+ )
+ path = self.treeview.get_path_at_pos(int(x), int(y))
+ if path:
+ self.handle_list_scroll(path=path[0], _next=None)
+ return True
+
+
+class PathAutoCompleter(object):
+ def __init__(self, builder, path_entry, max_visible_rows):
+ self.completion_popup = PathCompletionPopup(
+ builder, path_entry, max_visible_rows
+ )
+ self.path_entry = path_entry
+ self.dirs_cache = {}
+ self.use_popup = False
+ self.auto_complete_enabled = True
+ self.signal_handlers = self.completion_popup.signal_handlers
+
+ self.signal_handlers[
+ 'on_completion_popup_window_key_press_event'
+ ] = self.on_completion_popup_window_key_press_event
+ self.signal_handlers[
+ 'on_entry_text_delete_text'
+ ] = self.on_entry_text_delete_text
+ self.signal_handlers[
+ 'on_entry_text_insert_text'
+ ] = self.on_entry_text_insert_text
+ self.accelerator_string = Gtk.accelerator_name(Gdk.KEY_Tab, 0)
+
+ def on_entry_text_insert_text(self, entry, new_text, new_text_length, position):
+ if self.path_entry.get_realized():
+ cur_text = self.path_entry.get_text()
+ pos = entry.get_position()
+ new_complete_text = cur_text[:pos] + new_text + cur_text[pos:]
+ # Remove all values from the list that do not start with new_complete_text
+ self.completion_popup.reduce_values(new_complete_text)
+ self.completion_popup.set_selected_value(
+ new_complete_text, select_first=True
+ )
+ if self.completion_popup.is_popped_up():
+ self.completion_popup.set_window_position_and_size()
+
+ def on_entry_text_delete_text(self, entry, start, end):
+ """
+ Do completion when characters are removed
+
+ """
+ if self.completion_popup.is_popped_up():
+ cur_text = self.path_entry.get_text()
+ new_complete_text = cur_text[:start] + cur_text[end:]
+ self.do_completion(value=new_complete_text, forward_completion=False)
+
+ def set_use_popup(self, use):
+ self.use_popup = use
+
+ def on_completion_popup_window_key_press_event(self, entry, event):
+ """
+ Handles key pressed events on the auto-completion popup window
+ """
+ # If on_completion_treeview_key_press_event handles the event, do nothing
+ ret = self.completion_popup.on_completion_treeview_key_press_event(entry, event)
+ if ret:
+ return ret
+ keyval = event.keyval
+ state = event.get_state() & Gtk.accelerator_get_default_mod_mask()
+ if (
+ self.is_auto_completion_accelerator(keyval, state)
+ and self.auto_complete_enabled
+ ):
+ values_count = self.completion_popup.get_values_count()
+ if values_count == 1:
+ self.do_completion()
+ else:
+ self.completion_popup.handle_list_scroll(_next=True)
+ return True
+ # Buggy stuff (in pygobject?) causing type mismatch between EventKey and GdkEvent. Convert manually...
+ n = Gdk.Event()
+ n.type = event.type
+ n.window = event.window
+ n.send_event = event.send_event
+ n.time = event.time
+ n.state = event.state
+ n.keyval = event.keyval
+ n.length = event.length
+ n.string = event.string
+ n.hardware_keycode = event.hardware_keycode
+ n.group = event.group
+ n.is_modifier = event.is_modifier
+ self.path_entry.text_entry.emit('key-press-event', n)
+
+ def is_auto_completion_accelerator(self, keyval, state):
+ return Gtk.accelerator_name(keyval, state) == self.accelerator_string
+
+ def do_completion(self, value=None, forward_completion=True):
+ if not value:
+ value = self.path_entry.get_text()
+ self.path_entry.text_entry.set_position(len(value))
+ opts = {}
+ opts['show_hidden_files'] = self.completion_popup.show_hidden_files
+ opts['completion_text'] = value
+ opts['forward_completion'] = forward_completion
+ self._start_completion(opts)
+
+ def _start_completion(self, args):
+ args = get_completion_paths(args)
+ self._end_completion(args)
+
+ def _end_completion(self, args):
+ value = args['completion_text']
+ paths = args['paths']
+
+ if args['forward_completion']:
+ common_prefix = os.path.commonprefix(paths)
+ if len(common_prefix) > len(value):
+ self.path_entry.set_text(
+ common_prefix, set_file_chooser_folder=True, trigger_event=True
+ )
+
+ self.path_entry.text_entry.set_position(len(self.path_entry.get_text()))
+ self.completion_popup.set_values(paths, preserve_selection=True)
+
+ if self.use_popup and len(paths) > 1:
+ self.completion_popup.popup()
+ elif self.completion_popup.is_popped_up() and args['forward_completion']:
+ self.completion_popup.popdown()
+
+
+class PathChooserComboBox(Gtk.Box, StoredValuesPopup, GObject.GObject):
+
+ __gsignals__ = {
+ signal
+ if not PY2
+ else signal.encode(): (SignalFlags.RUN_FIRST, GObject.TYPE_NONE, (object,))
+ for signal in [
+ 'text-changed',
+ 'accelerator-set',
+ 'max-rows-changed',
+ 'list-value-added',
+ 'list-value-removed',
+ 'list-values-changed',
+ 'list-values-reordered',
+ 'show-path-entry-toggled',
+ 'show-filechooser-toggled',
+ 'show-hidden-files-toggled',
+ 'show-folder-name-on-button',
+ 'auto-complete-enabled-toggled',
+ ]
+ }
+
+ def __init__(
+ self,
+ max_visible_rows=20,
+ auto_complete=True,
+ use_completer_popup=True,
+ parent=None,
+ ):
+ Gtk.Box.__init__(self)
+ GObject.GObject.__init__(self)
+ self.list_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, spacing=0)
+ self._stored_values_popping_down = False
+ self.filechooser_visible = True
+ self.filechooser_enabled = True
+ self.path_entry_visible = True
+ self.properties_enabled = True
+ self.show_folder_name_on_button = False
+ self.setting_accelerator_key = False
+ self.builder = Gtk.Builder()
+ self.parent = parent
+ self.popup_buttonbox = self.builder.get_object('buttonbox')
+ self.builder.add_from_file(
+ resource_filename(
+ __package__, os.path.join('glade', 'path_combo_chooser.ui')
+ )
+ )
+ self.button_toggle = self.builder.get_object('button_toggle_dropdown')
+ self.text_entry = self.builder.get_object('entry_text')
+ self.open_filechooser_dialog_button = self.builder.get_object(
+ 'button_open_dialog'
+ )
+ self.filechooser_button = self.open_filechooser_dialog_button
+ self.filechooserdialog = self.builder.get_object('filechooserdialog')
+ self.filechooserdialog.set_transient_for(self.parent)
+ self.filechooser_widget = self.builder.get_object('filechooser_widget')
+ self.folder_name_label = self.builder.get_object('folder_name_label')
+ self.default_text = None
+ self.button_properties = self.builder.get_object('button_properties')
+
+ self.combobox_window = self.builder.get_object('combobox_window')
+ self.combo_hbox = self.builder.get_object('entry_combobox_hbox')
+ # Change the parent of the hbox from the glade Window to this hbox.
+ self.combobox_window.remove(self.combo_hbox)
+ self.combobox_window = self.get_window()
+ self.add(self.combo_hbox)
+ StoredValuesPopup.__init__(
+ self, self.builder, self, max_visible_rows, self.combo_hbox
+ )
+
+ self.tooltips = Gtk.Tooltip()
+ self.auto_completer = PathAutoCompleter(self.builder, self, max_visible_rows)
+ self.auto_completer.set_use_popup(use_completer_popup)
+ self.auto_completer.auto_complete_enabled = auto_complete
+ self._setup_config_dialog()
+
+ signal_handlers = {
+ 'on_button_toggle_dropdown_toggled': self._on_button_toggle_dropdown_toggled,
+ 'on_entry_text_key_press_event': self._on_entry_text_key_press_event,
+ 'on_stored_values_popup_window_hide': self._on_stored_values_popup_window_hide,
+ 'on_button_toggle_dropdown_button_press_event': self._on_button_toggle_dropdown_button_press_event,
+ 'on_entry_combobox_hbox_realize': self._on_entry_combobox_hbox_realize,
+ 'on_button_open_dialog_clicked': self._on_button_open_dialog_clicked,
+ 'on_entry_text_focus_out_event': self._on_entry_text_focus_out_event,
+ 'on_entry_text_changed': self.on_entry_text_changed,
+ }
+ signal_handlers.update(self.signal_handlers)
+ signal_handlers.update(self.auto_completer.signal_handlers)
+ signal_handlers.update(self.config_dialog_signal_handlers)
+ self.builder.connect_signals(signal_handlers)
+
+ def get_text(self):
+ """
+ Get the current text in the Entry
+ """
+ return self.text_entry.get_text()
+
+ def set_text(
+ self,
+ text,
+ set_file_chooser_folder=True,
+ cursor_end=True,
+ default_text=False,
+ trigger_event=False,
+ ):
+ """
+ Set the text for the entry.
+
+ """
+ old_text = self.text_entry.get_text()
+ # We must block the "delete-text" signal to avoid the signal handler being called
+ self.text_entry.handler_block_by_func(
+ self.auto_completer.on_entry_text_delete_text
+ )
+ self.text_entry.set_text(text)
+ self.text_entry.handler_unblock_by_func(
+ self.auto_completer.on_entry_text_delete_text
+ )
+
+ self.text_entry.select_region(0, 0)
+ self.text_entry.set_position(len(text) if cursor_end else 0)
+ self.set_selected_value(text, select_first=True)
+ self.combo_hbox.set_tooltip_text(text)
+ if default_text:
+ self.default_text = text
+ self.button_default.set_tooltip_text(
+ 'Restore the default value in the text entry:\n%s' % self.default_text
+ )
+ self.button_default.set_sensitive(True)
+ # Set text for the filechooser dialog button
+ folder_name = ''
+ if self.show_folder_name_on_button or not self.path_entry_visible:
+ folder_name = path_without_trailing_path_sep(text)
+ if folder_name != '/' and os.path.basename(folder_name):
+ folder_name = os.path.basename(folder_name)
+ self.folder_name_label.set_text(folder_name)
+ # Only trigger event if text has changed
+ if old_text != text and trigger_event:
+ self.on_entry_text_changed(self.text_entry)
+
+ def set_sensitive(self, sensitive):
+ """
+ Set the path chooser widgets sensitive
+
+ :param sensitive: if the widget should be sensitive
+ :type sensitive: bool
+
+ """
+ self.text_entry.set_sensitive(sensitive)
+ self.filechooser_button.set_sensitive(sensitive)
+ self.button_toggle.set_sensitive(sensitive)
+
+ def get_accelerator_string(self):
+ return self.auto_completer.accelerator_string
+
+ def set_accelerator_string(self, accelerator):
+ """
+ Set the accelerator string to trigger auto-completion
+ """
+ if accelerator is None:
+ return
+ try:
+ # Verify that the accelerator can be parsed
+ keyval, mask = Gtk.accelerator_parse(self.auto_completer.accelerator_string)
+ self.auto_completer.accelerator_string = accelerator
+ except TypeError as ex:
+ raise TypeError('TypeError when setting accelerator string: %s' % ex)
+
+ def get_auto_complete_enabled(self):
+ return self.auto_completer.auto_complete_enabled
+
+ def set_auto_complete_enabled(self, enable):
+ if not isinstance(enable, bool):
+ return
+ self.auto_completer.auto_complete_enabled = enable
+
+ def get_show_folder_name_on_button(self):
+ return self.show_folder_name_on_button
+
+ def set_show_folder_name_on_button(self, show):
+ if not isinstance(show, bool):
+ return
+ self.show_folder_name_on_button = show
+ self._set_path_entry_filechooser_widths()
+
+ def get_filechooser_button_enabled(self):
+ return self.filechooser_enabled
+
+ def set_filechooser_button_enabled(self, enable):
+ """
+ Enable/disable the filechooser button.
+
+ By setting filechooser disabled, in will not be possible
+ to change the settings in the properties.
+ """
+ if not isinstance(enable, bool):
+ return
+ self.filechooser_enabled = enable
+ if not enable:
+ self.set_filechooser_button_visible(False, update=False)
+
+ def get_filechooser_button_visible(self):
+ return self.filechooser_visible
+
+ def set_filechooser_button_visible(self, visible, update=True):
+ """
+ Set file chooser button entry visible
+ """
+ if not isinstance(visible, bool):
+ return
+ if update:
+ self.filechooser_visible = visible
+ if visible and not self.filechooser_enabled:
+ return
+ if visible:
+ self.filechooser_button.show()
+ else:
+ self.filechooser_button.hide()
+ # Update width properties
+ self._set_path_entry_filechooser_widths()
+
+ def get_path_entry_visible(self):
+ return self.path_entry_visible
+
+ def set_path_entry_visible(self, visible):
+ """
+ Set the path entry visible
+ """
+ if not isinstance(visible, bool):
+ return
+ self.path_entry_visible = visible
+ if visible:
+ self.text_entry.show()
+ else:
+ self.text_entry.hide()
+ self._set_path_entry_filechooser_widths()
+
+ def get_show_hidden_files(self):
+ return self.auto_completer.completion_popup.show_hidden_files
+
+ def set_show_hidden_files(self, show, do_completion=False, emit_event=False):
+ """
+ Enable/disable showing hidden files on path completion
+ """
+ if not isinstance(show, bool):
+ return
+ self.auto_completer.completion_popup.show_hidden_files = show
+ if do_completion:
+ self.auto_completer.do_completion()
+ if emit_event:
+ self.emit('show-hidden-files-toggled', show)
+
+ def set_enable_properties(self, enable):
+ """
+ Enable/disable the config properties
+ """
+ if not isinstance(enable, bool):
+ return
+ self.properties_enabled = enable
+ if self.properties_enabled:
+ self.popup_buttonbox.add(self.button_properties)
+ else:
+ self.popup_buttonbox.remove(self.button_properties)
+
+ def set_auto_completer_func(self, func):
+ """
+ Set the function to be called when the auto completion
+ accelerator is triggered.
+ """
+ self.auto_completer._start_completion = func
+
+ def complete(self, args):
+ """
+ Perform the auto completion with the provided paths
+ """
+ self.auto_completer._end_completion(args)
+
+ ##############
+ # Callbacks and internal functions
+ ##############
+
+ def on_entry_text_changed(self, entry):
+ self.emit('text-changed', self.get_text())
+
+ def _on_entry_text_focus_out_event(self, widget, event):
+ # FIXME: This causes text to be deselected on right-click.
+ # Update text on the button label
+ self.set_text(self.get_text())
+
+ def _set_path_entry_filechooser_widths(self):
+ if self.path_entry_visible:
+ self.combo_hbox.set_child_packing(
+ self.filechooser_button, 0, 0, 0, Gtk.PackType.START
+ )
+ width, height = self.folder_name_label.get_size_request()
+ width = 120
+ if not self.show_folder_name_on_button:
+ width = 0
+ self.folder_name_label.set_size_request(width, height)
+ self.combo_hbox.set_child_packing(
+ self.filechooser_button, 0, 0, 0, Gtk.PackType.START
+ )
+ else:
+ self.combo_hbox.set_child_packing(
+ self.filechooser_button, 1, 1, 0, Gtk.PackType.START
+ )
+ self.folder_name_label.set_size_request(-1, -1)
+ # Update text on the button label
+ self.set_text(self.get_text())
+
+ def _on_entry_combobox_hbox_realize(self, widget):
+ """ Must do this when the widget is realized """
+ self.set_filechooser_button_visible(self.filechooser_visible)
+ self.set_path_entry_visible(self.path_entry_visible)
+
+ def _on_button_open_dialog_clicked(self, widget):
+ self.filechooser_widget.set_current_folder(self.get_text())
+ response_id = self.filechooserdialog.run()
+
+ if response_id == 0:
+ text = self.filechooser_widget.get_filename()
+ self.set_text(text, trigger_event=True)
+ self.filechooserdialog.hide()
+
+ def _on_entry_text_key_press_event(self, widget, event):
+ """
+ Listen to key events on the entry widget.
+
+ Arrow up/down will change the value of the entry according to the
+ current selection in the list.
+ Enter will show the popup.
+
+ Return True whenever we want no other event listeners to be called.
+
+ """
+ # on_entry_text_key_press_event Errors follow here when pressing ALT key while popup is visible")
+ keyval = event.keyval
+ state = event.get_state() & Gtk.accelerator_get_default_mod_mask()
+ ctrl = event.get_state() & Gdk.ModifierType.CONTROL_MASK
+
+ # Select new row with arrow up/down is pressed
+ if key_is_up_or_down(keyval):
+ self.handle_list_scroll(_next=key_is_down(keyval), set_entry=True)
+ return True
+ elif self.auto_completer.is_auto_completion_accelerator(keyval, state):
+ if self.auto_completer.auto_complete_enabled:
+ self.auto_completer.do_completion()
+ return True
+ # Show popup when Enter is pressed
+ elif key_is_enter(keyval):
+ # This sets the toggle active which results in
+ # on_button_toggle_dropdown_toggled being called which initiates the popup
+ self.button_toggle.set_active(True)
+ return True
+ elif ctrl:
+ # Swap the show hidden files value on CTRL-h
+ if is_ascii_value(keyval, 'h'):
+ # Set show/hide hidden files
+ self.set_show_hidden_files(
+ not self.get_show_hidden_files(), emit_event=True
+ )
+ return True
+ elif is_ascii_value(keyval, 's'):
+ super(PathChooserComboBox, self).add_current_value_to_saved_list()
+ return True
+ elif is_ascii_value(keyval, 'd'):
+ # Set the default value in the text entry
+ self.set_text(self.default_text, trigger_event=True)
+ return True
+ return False
+
+ def _on_button_toggle_dropdown_toggled(self, button):
+ """
+ Shows the popup when clicking the toggle button.
+ """
+ if self._stored_values_popping_down:
+ return
+ self.popup()
+
+ def _on_stored_values_popup_window_hide(self, popup):
+ """Make sure the button toggle is removed when popup is closed"""
+ self._stored_values_popping_down = True
+ self.button_toggle.set_active(False)
+ self._stored_values_popping_down = False
+
+ ##############
+ # Config dialog
+ ##############
+
+ def _on_button_toggle_dropdown_button_press_event(self, widget, event):
+ """Show config when right clicking dropdown toggle button"""
+ if not self.properties_enabled:
+ return False
+ # This is right click
+ if event.button == 3:
+ self._on_button_properties_clicked(widget)
+ return True
+
+ def _on_button_properties_clicked(self, widget):
+ self.popdown()
+ self.enable_completion.set_active(self.get_auto_complete_enabled())
+ # Set the value of the label to the current accelerator
+ keyval, mask = Gtk.accelerator_parse(self.auto_completer.accelerator_string)
+ self.accelerator_label.set_text(Gtk.accelerator_get_label(keyval, mask))
+ self.visible_rows.set_value(self.get_max_popup_rows())
+ self.show_filechooser_checkbutton.set_active(
+ self.get_filechooser_button_visible()
+ )
+ self.show_path_entry_checkbutton.set_active(self.path_entry_visible)
+ self.show_hidden_files_checkbutton.set_active(self.get_show_hidden_files())
+ self.show_folder_name_on_button_checkbutton.set_active(
+ self.get_show_folder_name_on_button()
+ )
+ self._set_properties_widgets_sensitive(True)
+ self.config_dialog.show_all()
+
+ def _set_properties_widgets_sensitive(self, val):
+ self.enable_completion.set_sensitive(val)
+ self.config_short_cuts_frame.set_sensitive(val)
+ self.config_general_frame.set_sensitive(val)
+ self.show_hidden_files_checkbutton.set_sensitive(val)
+
+ def _setup_config_dialog(self):
+ self.config_dialog = self.builder.get_object('completion_config_dialog')
+ self.enable_completion = self.builder.get_object(
+ 'enable_auto_completion_checkbutton'
+ )
+ self.show_filechooser_checkbutton = self.builder.get_object(
+ 'show_filechooser_checkbutton'
+ )
+ self.show_path_entry_checkbutton = self.builder.get_object(
+ 'show_path_entry_checkbutton'
+ )
+ set_key_button = self.builder.get_object('set_completion_accelerator_button')
+ default_set_accelerator_tooltip = set_key_button.get_tooltip_text()
+ self.config_short_cuts_frame = self.builder.get_object(
+ 'config_short_cuts_frame'
+ )
+ self.config_general_frame = self.builder.get_object('config_general_frame')
+ self.accelerator_label = self.builder.get_object('completion_accelerator_label')
+ self.visible_rows = self.builder.get_object('visible_rows_spinbutton')
+ self.visible_rows_label = self.builder.get_object('visible_rows_label')
+ self.show_hidden_files_checkbutton = self.builder.get_object(
+ 'show_hidden_files_checkbutton'
+ )
+ self.show_folder_name_on_button_checkbutton = self.builder.get_object(
+ 'show_folder_name_on_button_checkbutton'
+ )
+ self.config_dialog.set_transient_for(self.parent)
+
+ def on_close(widget, event=None):
+ if not self.setting_accelerator_key:
+ self.config_dialog.hide()
+ else:
+ stop_setting_accelerator()
+ return True
+
+ def on_enable_completion_toggled(widget):
+ self.set_auto_complete_enabled(self.enable_completion.get_active())
+ self.emit(
+ 'auto-complete-enabled-toggled', self.enable_completion.get_active()
+ )
+
+ def on_show_filechooser_toggled(widget):
+ self.set_filechooser_button_visible(
+ self.show_filechooser_checkbutton.get_active()
+ )
+ self.emit(
+ 'show-filechooser-toggled',
+ self.show_filechooser_checkbutton.get_active(),
+ )
+ self.show_folder_name_on_button_checkbutton.set_sensitive(
+ self.show_path_entry_checkbutton.get_active()
+ and self.show_filechooser_checkbutton.get_active()
+ )
+ if not self.filechooser_visible and not self.path_entry_visible:
+ self.show_path_entry_checkbutton.set_active(True)
+ on_show_path_entry_toggled(None)
+
+ def on_show_path_entry_toggled(widget):
+ self.set_path_entry_visible(self.show_path_entry_checkbutton.get_active())
+ self.emit(
+ 'show-path-entry-toggled', self.show_path_entry_checkbutton.get_active()
+ )
+ self.show_folder_name_on_button_checkbutton.set_sensitive(
+ self.show_path_entry_checkbutton.get_active()
+ and self.show_filechooser_checkbutton.get_active()
+ )
+ if not self.filechooser_visible and not self.path_entry_visible:
+ self.show_filechooser_checkbutton.set_active(True)
+ on_show_filechooser_toggled(None)
+
+ def on_show_folder_name_on_button(widget):
+ self.set_show_folder_name_on_button(
+ self.show_folder_name_on_button_checkbutton.get_active()
+ )
+ self._set_path_entry_filechooser_widths()
+ self.emit(
+ 'show-folder-name-on-button',
+ self.show_folder_name_on_button_checkbutton.get_active(),
+ )
+
+ def on_show_hidden_files_toggled(widget):
+ self.set_show_hidden_files(
+ self.show_hidden_files_checkbutton.get_active(), emit_event=True
+ )
+
+ def on_max_rows_changed(widget):
+ self.set_max_popup_rows(self.visible_rows.get_value_as_int())
+ self.emit('max-rows-changed', self.visible_rows.get_value_as_int())
+
+ def set_accelerator(widget):
+ self.setting_accelerator_key = True
+ set_key_button.set_tooltip_text(
+ 'Press the accelerator keys for triggering auto-completion'
+ )
+ self._set_properties_widgets_sensitive(False)
+ return True
+
+ def stop_setting_accelerator():
+ self.setting_accelerator_key = False
+ self._set_properties_widgets_sensitive(True)
+ set_key_button.set_active(False)
+ # Restore default tooltip
+ set_key_button.set_tooltip_text(default_set_accelerator_tooltip)
+
+ def on_completion_config_dialog_key_release_event(widget, event):
+ # We are listening for a new key
+ if set_key_button.get_active():
+ state = event.get_state() & Gtk.accelerator_get_default_mod_mask()
+ accelerator_mask = state.numerator
+ # If e.g. only CTRL key is pressed.
+ if not Gtk.accelerator_valid(event.keyval, accelerator_mask):
+ accelerator_mask = 0
+ self.auto_completer.accelerator_string = Gtk.accelerator_name(
+ event.keyval, accelerator_mask
+ )
+ self.accelerator_label.set_text(
+ Gtk.accelerator_get_label(event.keyval, accelerator_mask)
+ )
+ self.emit('accelerator-set', self.auto_completer.accelerator_string)
+ stop_setting_accelerator()
+ return True
+ else:
+ keyval = event.keyval
+ ctrl = event.get_state() & Gdk.ModifierType.CONTROL_MASK
+ if ctrl:
+ # Set show/hide hidden files
+ if is_ascii_value(keyval, 'h'):
+ self.show_hidden_files_checkbutton.set_active(
+ not self.get_show_hidden_files()
+ )
+ return True
+
+ def on_set_completion_accelerator_button_clicked(widget):
+ if not set_key_button.get_active():
+ stop_setting_accelerator()
+ return True
+
+ self.config_dialog_signal_handlers = {
+ 'on_enable_auto_completion_checkbutton_toggled': on_enable_completion_toggled,
+ 'on_show_filechooser_checkbutton_toggled': on_show_filechooser_toggled,
+ 'on_show_path_entry_checkbutton_toggled': on_show_path_entry_toggled,
+ 'on_show_folder_name_on_button_checkbutton_toggled': on_show_folder_name_on_button,
+ 'on_config_dialog_button_close_clicked': on_close,
+ 'on_visible_rows_spinbutton_value_changed': on_max_rows_changed,
+ 'on_completion_config_dialog_delete_event': on_close,
+ 'on_set_completion_accelerator_button_pressed': set_accelerator,
+ 'on_completion_config_dialog_key_release_event': on_completion_config_dialog_key_release_event,
+ 'on_set_completion_accelerator_button_clicked': on_set_completion_accelerator_button_clicked,
+ 'on_show_hidden_files_checkbutton_toggled': on_show_hidden_files_toggled,
+ }
+
+
+GObject.type_register(PathChooserComboBox)
+
+
+if __name__ == '__main__':
+ import signal
+
+ # necessary to exit with CTRL-C (https://bugzilla.gnome.org/show_bug.cgi?id=622084)
+ signal.signal(signal.SIGINT, signal.SIG_DFL)
+ import sys
+
+ w = Gtk.Window()
+ w.set_position(Gtk.WindowPosition.CENTER)
+ w.set_size_request(600, -1)
+ w.set_title('ComboEntry example')
+ w.connect('delete-event', Gtk.main_quit)
+
+ box1 = Gtk.Box.new(Gtk.Orientation.VERTICAL, spacing=0)
+
+ def get_resource2(filename):
+ return '%s/glade/%s' % (os.path.abspath(os.path.dirname(sys.argv[0])), filename)
+
+ # Override get_resource which fetches from deluge install
+ # get_resource = get_resource2
+
+ entry1 = PathChooserComboBox(max_visible_rows=15)
+ entry2 = PathChooserComboBox()
+
+ box1.add(entry1)
+ box1.add(entry2)
+
+ test_paths = [
+ '/home/bro/Downloads',
+ '/media/Movies-HD',
+ '/media/torrent/in',
+ '/media/Live-show/Misc',
+ '/media/Live-show/Consert',
+ '/media/Series/1/',
+ '/media/Series/2',
+ '/media/Series/17',
+ '/media/Series/18',
+ '/media/Series/19',
+ ]
+
+ entry1.add_values(test_paths)
+ entry1.set_text('/home/bro/', default_text=True)
+ entry2.set_text(
+ '/home/bro/programmer/deluge/deluge-yarss-plugin/build/lib/yarss2/include/bs4/tests/',
+ cursor_end=False,
+ )
+
+ entry2.set_filechooser_button_visible(False)
+ # entry2.set_enable_properties(False)
+ entry2.set_filechooser_button_enabled(False)
+
+ def list_value_added_event(widget, values):
+ print('Current list values:', widget.get_values())
+
+ entry1.connect('list-value-added', list_value_added_event)
+ entry2.connect('list-value-added', list_value_added_event)
+ w.add(box1)
+ w.show_all()
+ Gtk.main()
diff --git a/deluge/ui/gtk3/peers_tab.py b/deluge/ui/gtk3/peers_tab.py
new file mode 100644
index 0000000..33395b9
--- /dev/null
+++ b/deluge/ui/gtk3/peers_tab.py
@@ -0,0 +1,394 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os.path
+
+from gi.repository.GdkPixbuf import Pixbuf
+from gi.repository.Gtk import (
+ Builder,
+ CellRendererPixbuf,
+ CellRendererProgress,
+ CellRendererText,
+ ListStore,
+ TreeViewColumn,
+ TreeViewColumnSizing,
+)
+
+import deluge.common
+import deluge.component as component
+from deluge.ui.client import client
+from deluge.ui.countries import COUNTRIES
+
+from .common import (
+ icon_downloading,
+ icon_seeding,
+ load_pickled_state_file,
+ save_pickled_state_file,
+)
+from .torrentdetails import Tab
+from .torrentview_data_funcs import (
+ cell_data_peer_progress,
+ cell_data_speed_down,
+ cell_data_speed_up,
+)
+
+try:
+ from future_builtins import zip
+except ImportError:
+ # Ignore on Py3.
+ pass
+
+log = logging.getLogger(__name__)
+
+
+class PeersTab(Tab):
+ def __init__(self):
+ super(PeersTab, self).__init__('Peers', 'peers_tab', 'peers_tab_label')
+
+ self.peer_menu = self.main_builder.get_object('menu_peer_tab')
+ component.get('MainWindow').connect_signals(self)
+
+ self.listview = self.main_builder.get_object('peers_listview')
+ self.listview.props.has_tooltip = True
+ self.listview.connect('button-press-event', self._on_button_press_event)
+ self.listview.connect('query-tooltip', self._on_query_tooltip)
+
+ # flag, ip, client, downspd, upspd, country code, int_ip, seed/peer icon, progress
+ self.liststore = ListStore(
+ Pixbuf, str, str, int, int, str, float, Pixbuf, float
+ )
+ self.cached_flag_pixbufs = {}
+
+ self.seed_pixbuf = icon_seeding
+ self.peer_pixbuf = icon_downloading
+
+ # key is ip address, item is row iter
+ self.peers = {}
+
+ # Country column
+ column = TreeViewColumn()
+ render = CellRendererPixbuf()
+ column.pack_start(render, False)
+ column.add_attribute(render, 'pixbuf', 0)
+ column.set_sort_column_id(5)
+ column.set_clickable(True)
+ column.set_resizable(True)
+ column.set_expand(False)
+ column.set_min_width(20)
+ column.set_reorderable(True)
+ self.listview.append_column(column)
+
+ # Address column
+ column = TreeViewColumn(_('Address'))
+ render = CellRendererPixbuf()
+ column.pack_start(render, False)
+ column.add_attribute(render, 'pixbuf', 7)
+ render = CellRendererText()
+ column.pack_start(render, False)
+ column.add_attribute(render, 'text', 1)
+ column.set_sort_column_id(6)
+ column.set_clickable(True)
+ column.set_resizable(True)
+ column.set_expand(False)
+ column.set_min_width(100)
+ column.set_reorderable(True)
+ self.listview.append_column(column)
+
+ # Client column
+ column = TreeViewColumn(_('Client'))
+ render = CellRendererText()
+ column.pack_start(render, False)
+ column.add_attribute(render, 'text', 2)
+ column.set_sort_column_id(2)
+ column.set_clickable(True)
+ column.set_resizable(True)
+ column.set_expand(False)
+ column.set_min_width(100)
+ column.set_reorderable(True)
+ self.listview.append_column(column)
+
+ # Progress column
+ column = TreeViewColumn(_('Progress'))
+ render = CellRendererProgress()
+ column.pack_start(render, True)
+ column.set_cell_data_func(render, cell_data_peer_progress, 8)
+ column.set_sort_column_id(8)
+ column.set_clickable(True)
+ column.set_resizable(True)
+ column.set_expand(False)
+ column.set_min_width(100)
+ column.set_reorderable(True)
+ self.listview.append_column(column)
+
+ # Down Speed column
+ column = TreeViewColumn(_('Down Speed'))
+ render = CellRendererText()
+ column.pack_start(render, False)
+ column.set_cell_data_func(render, cell_data_speed_down, 3)
+ column.set_sort_column_id(3)
+ column.set_clickable(True)
+ column.set_resizable(True)
+ column.set_expand(False)
+ column.set_min_width(50)
+ column.set_reorderable(True)
+ self.listview.append_column(column)
+
+ # Up Speed column
+ column = TreeViewColumn(_('Up Speed'))
+ render = CellRendererText()
+ column.pack_start(render, False)
+ column.set_cell_data_func(render, cell_data_speed_up, 4)
+ column.set_sort_column_id(4)
+ column.set_clickable(True)
+ column.set_resizable(True)
+ column.set_expand(False)
+ column.set_min_width(50)
+ # Bugfix: Last column needs max_width set to stop scrollbar appearing
+ column.set_max_width(150)
+ column.set_reorderable(True)
+ self.listview.append_column(column)
+
+ self.listview.set_model(self.liststore)
+
+ self.load_state()
+
+ self.torrent_id = None
+
+ def save_state(self):
+ # Get the current sort order of the view
+ column_id, sort_order = self.liststore.get_sort_column_id()
+
+ # Setup state dict
+ state = {
+ 'columns': {},
+ 'sort_id': column_id,
+ 'sort_order': int(sort_order) if sort_order else None,
+ }
+
+ for index, column in enumerate(self.listview.get_columns()):
+ state['columns'][column.get_title()] = {
+ 'position': index,
+ 'width': column.get_width(),
+ }
+ save_pickled_state_file('peers_tab.state', state)
+
+ def load_state(self):
+ state = load_pickled_state_file('peers_tab.state')
+
+ if state is None:
+ return
+
+ if len(state['columns']) != len(self.listview.get_columns()):
+ log.warning('peers_tab.state is not compatible! rejecting..')
+ return
+
+ if state['sort_id'] and state['sort_order'] is not None:
+ self.liststore.set_sort_column_id(state['sort_id'], state['sort_order'])
+
+ for (index, column) in enumerate(self.listview.get_columns()):
+ cname = column.get_title()
+ if cname in state['columns']:
+ cstate = state['columns'][cname]
+ column.set_sizing(TreeViewColumnSizing.FIXED)
+ column.set_fixed_width(cstate['width'] if cstate['width'] > 0 else 10)
+ if state['sort_id'] == index and state['sort_order'] is not None:
+ column.set_sort_indicator(True)
+ column.set_sort_order(state['sort_order'])
+ if cstate['position'] != index:
+ # Column is in wrong position
+ if cstate['position'] == 0:
+ self.listview.move_column_after(column, None)
+ elif (
+ self.listview.get_columns()[cstate['position'] - 1].get_title()
+ != cname
+ ):
+ self.listview.move_column_after(
+ column, self.listview.get_columns()[cstate['position'] - 1]
+ )
+
+ def update(self):
+ # Get the first selected torrent
+ torrent_id = component.get('TorrentView').get_selected_torrents()
+
+ # Only use the first torrent in the list or return if None selected
+ if len(torrent_id) != 0:
+ torrent_id = torrent_id[0]
+ else:
+ # No torrent is selected in the torrentview
+ self.liststore.clear()
+ return
+
+ if torrent_id != self.torrent_id:
+ # We only want to do this if the torrent_id has changed
+ self.liststore.clear()
+ self.peers = {}
+ self.torrent_id = torrent_id
+
+ component.get('SessionProxy').get_torrent_status(
+ torrent_id, ['peers']
+ ).addCallback(self._on_get_torrent_status)
+
+ def get_flag_pixbuf(self, country):
+ if not country.strip():
+ return None
+
+ if country not in self.cached_flag_pixbufs:
+ # We haven't created a pixbuf for this country yet
+ try:
+ self.cached_flag_pixbufs[country] = Pixbuf.new_from_file(
+ deluge.common.resource_filename(
+ 'deluge',
+ os.path.join(
+ 'ui', 'data', 'pixmaps', 'flags', country.lower() + '.png'
+ ),
+ )
+ )
+ except Exception as ex:
+ log.debug('Unable to load flag: %s', ex)
+ return None
+
+ return self.cached_flag_pixbufs[country]
+
+ def _on_get_torrent_status(self, status):
+ new_ips = set()
+ for peer in status['peers']:
+ new_ips.add(peer['ip'])
+ if peer['ip'] in self.peers:
+ # We already have this peer in our list, so lets just update it
+ row = self.peers[peer['ip']]
+ if not self.liststore.iter_is_valid(row):
+ # This iter is invalid, delete it and continue to next iteration
+ del self.peers[peer['ip']]
+ continue
+ values = self.liststore.get(row, 3, 4, 5, 7, 8)
+ if peer['down_speed'] != values[0]:
+ self.liststore.set_value(row, 3, peer['down_speed'])
+ if peer['up_speed'] != values[1]:
+ self.liststore.set_value(row, 4, peer['up_speed'])
+ if peer['country'] != values[2]:
+ self.liststore.set_value(row, 5, peer['country'])
+ self.liststore.set_value(
+ row, 0, self.get_flag_pixbuf(peer['country'])
+ )
+ if peer['seed']:
+ icon = self.seed_pixbuf
+ else:
+ icon = self.peer_pixbuf
+
+ if icon != values[3]:
+ self.liststore.set_value(row, 7, icon)
+
+ if peer['progress'] != values[4]:
+ self.liststore.set_value(row, 8, peer['progress'])
+ else:
+ # Peer is not in list so we need to add it
+
+ # Create an int IP address for sorting purposes
+ if peer['ip'].count(':') == 1:
+ # This is an IPv4 address
+ ip_int = sum(
+ int(byte) << shift
+ for byte, shift in zip(
+ peer['ip'].split(':')[0].split('.'), (24, 16, 8, 0)
+ )
+ )
+ peer_ip = peer['ip']
+ else:
+ # This is an IPv6 address
+ import socket
+ import binascii
+
+ # Split out the :port
+ ip = ':'.join(peer['ip'].split(':')[:-1])
+ ip_int = int(
+ binascii.hexlify(socket.inet_pton(socket.AF_INET6, ip)), 16
+ )
+ peer_ip = '[%s]:%s' % (ip, peer['ip'].split(':')[-1])
+
+ if peer['seed']:
+ icon = self.seed_pixbuf
+ else:
+ icon = self.peer_pixbuf
+
+ row = self.liststore.append(
+ [
+ self.get_flag_pixbuf(peer['country']),
+ peer_ip,
+ peer['client'],
+ peer['down_speed'],
+ peer['up_speed'],
+ peer['country'],
+ float(ip_int),
+ icon,
+ peer['progress'],
+ ]
+ )
+
+ self.peers[peer['ip']] = row
+
+ # Now we need to remove any ips that were not in status["peers"] list
+ for ip in set(self.peers).difference(new_ips):
+ self.liststore.remove(self.peers[ip])
+ del self.peers[ip]
+
+ def clear(self):
+ self.liststore.clear()
+
+ def _on_button_press_event(self, widget, event):
+ """This is a callback for showing the right-click context menu."""
+ log.debug('on_button_press_event')
+ # We only care about right-clicks
+ if self.torrent_id and event.button == 3:
+ self.peer_menu.popup(None, None, None, None, event.button, event.time)
+ return True
+
+ def _on_query_tooltip(self, widget, x, y, keyboard_tip, tooltip):
+ is_tooltip, x, y, model, path, _iter = widget.get_tooltip_context(
+ x, y, keyboard_tip
+ )
+ if is_tooltip:
+ country_code = model.get(_iter, 5)[0]
+ if country_code != ' ' and country_code in COUNTRIES:
+ tooltip.set_text(COUNTRIES[country_code])
+ # widget here is self.listview
+ widget.set_tooltip_cell(tooltip, path, widget.get_column(0), None)
+ return True
+ return False
+
+ def on_menuitem_add_peer_activate(self, menuitem):
+ """This is a callback for manually adding a peer"""
+ log.debug('on_menuitem_add_peer')
+ builder = Builder()
+ builder.add_from_file(
+ deluge.common.resource_filename(
+ __package__, os.path.join('glade', 'connect_peer_dialog.ui')
+ )
+ )
+ peer_dialog = builder.get_object('connect_peer_dialog')
+ txt_ip = builder.get_object('txt_ip')
+ response = peer_dialog.run()
+ if response:
+ value = txt_ip.get_text()
+ if value and ':' in value:
+ if ']' in value:
+ # ipv6
+ ip = value.split(']')[0][1:]
+ port = value.split(']')[1][1:]
+ else:
+ # ipv4
+ ip = value.split(':')[0]
+ port = value.split(':')[1]
+ if deluge.common.is_ip(ip):
+ log.debug('adding peer %s to %s', value, self.torrent_id)
+ client.core.connect_peer(self.torrent_id, ip, port)
+ peer_dialog.destroy()
+ return True
diff --git a/deluge/ui/gtk3/piecesbar.py b/deluge/ui/gtk3/piecesbar.py
new file mode 100644
index 0000000..ba03e55
--- /dev/null
+++ b/deluge/ui/gtk3/piecesbar.py
@@ -0,0 +1,235 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import division, unicode_literals
+
+from math import pi
+
+import gi # isort:skip (Version check required before import).
+
+gi.require_version('PangoCairo', '1.0') # NOQA: E402
+gi.require_version('cairo', '1.0') # NOQA: E402
+
+# isort:imports-thirdparty
+import cairo # Backward compat cairo <= 1.15
+from gi.repository import PangoCairo
+from gi.repository.Gtk import DrawingArea, ProgressBar, StateFlags
+from gi.repository.Pango import SCALE, Weight
+
+# isort:imports-firstparty
+from deluge.common import PY2
+from deluge.configmanager import ConfigManager
+
+COLOR_STATES = ['missing', 'waiting', 'downloading', 'completed']
+
+
+class PiecesBar(DrawingArea):
+ # Draw in response to an draw
+ __gsignals__ = {'draw': 'override'} if not PY2 else {b'draw': b'override'}
+
+ def __init__(self):
+ super(PiecesBar, self).__init__()
+ # Get progress bar styles, in order to keep font consistency
+ pb = ProgressBar()
+ pb_style = pb.get_style_context()
+ self.text_font = pb_style.get_property('font', StateFlags.NORMAL)
+ self.text_font.set_weight(Weight.BOLD)
+ # Done with the ProgressBar styles, don't keep refs of it
+ del pb, pb_style
+
+ self.set_size_request(-1, 25)
+ self.gtkui_config = ConfigManager('gtk3ui.conf')
+
+ self.width = self.prev_width = 0
+ self.height = self.prev_height = 0
+ self.pieces = self.prev_pieces = ()
+ self.num_pieces = None
+ self.text = self.prev_text = ''
+ self.fraction = self.prev_fraction = 0
+ self.progress_overlay = self.text_overlay = self.pieces_overlay = None
+ self.cr = None
+
+ self.connect('size-allocate', self.do_size_allocate_event)
+ self.show()
+
+ def do_size_allocate_event(self, widget, size):
+ self.prev_width = self.width
+ self.width = size.width
+ self.prev_height = self.height
+ self.height = size.height
+
+ # Handle the draw by drawing
+ def do_draw(self, event):
+ # Create cairo context
+ self.cr = self.props.window.cairo_create()
+ self.cr.set_line_width(max(self.cr.device_to_user_distance(0.5, 0.5)))
+
+ # Restrict Cairo to the exposed area; avoid extra work
+ self.roundcorners_clipping()
+
+ self.draw_pieces()
+ self.draw_progress_overlay()
+ self.write_text()
+ self.roundcorners_border()
+
+ # Drawn once, update width, height
+ if self.resized():
+ self.prev_width = self.width
+ self.prev_height = self.height
+
+ def roundcorners_clipping(self):
+ self.create_roundcorners_subpath(self.cr, 0, 0, self.width, self.height)
+ self.cr.clip()
+
+ def roundcorners_border(self):
+ self.create_roundcorners_subpath(
+ self.cr, 0.5, 0.5, self.width - 1, self.height - 1
+ )
+ self.cr.set_source_rgba(0, 0, 0, 0.9)
+ self.cr.stroke()
+
+ @staticmethod
+ def create_roundcorners_subpath(ctx, x, y, width, height):
+ aspect = 1.0
+ corner_radius = height / 10
+ radius = corner_radius / aspect
+ degrees = pi / 180
+ ctx.new_sub_path()
+ ctx.arc(x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees)
+ ctx.arc(
+ x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees
+ )
+ ctx.arc(x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees)
+ ctx.arc(x + radius, y + radius, radius, 180 * degrees, 270 * degrees)
+ ctx.close_path()
+ return ctx
+
+ def draw_pieces(self):
+ if not self.num_pieces:
+ # Nothing to draw.
+ return
+
+ if (
+ self.resized()
+ or self.pieces != self.prev_pieces
+ or self.pieces_overlay is None
+ ):
+ # Need to recreate the cache drawing
+ self.pieces_overlay = cairo.ImageSurface(
+ cairo.FORMAT_ARGB32, self.width, self.height
+ )
+ ctx = cairo.Context(self.pieces_overlay)
+
+ if self.pieces:
+ pieces = self.pieces
+ elif self.num_pieces:
+ # Completed torrents do not send any pieces so create list using 'completed' state.
+ pieces = [COLOR_STATES.index('completed')] * self.num_pieces
+ start_pos = 0
+ piece_width = self.width / len(pieces)
+ pieces_colors = [
+ [
+ color / 65535
+ for color in self.gtkui_config['pieces_color_%s' % state]
+ ]
+ for state in COLOR_STATES
+ ]
+ for state in pieces:
+ ctx.set_source_rgb(*pieces_colors[state])
+ ctx.rectangle(start_pos, 0, piece_width, self.height)
+ ctx.fill()
+ start_pos += piece_width
+
+ self.cr.set_source_surface(self.pieces_overlay)
+ self.cr.paint()
+
+ def draw_progress_overlay(self):
+ if not self.text:
+ # Nothing useful to draw, return now!
+ return
+
+ if (
+ self.resized()
+ or self.fraction != self.prev_fraction
+ or self.progress_overlay is None
+ ):
+ # Need to recreate the cache drawing
+ self.progress_overlay = cairo.ImageSurface(
+ cairo.FORMAT_ARGB32, self.width, self.height
+ )
+ ctx = cairo.Context(self.progress_overlay)
+ ctx.set_source_rgba(0.1, 0.1, 0.1, 0.3) # Transparent
+ ctx.rectangle(0, 0, self.width * self.fraction, self.height)
+ ctx.fill()
+ self.cr.set_source_surface(self.progress_overlay)
+ self.cr.paint()
+
+ def write_text(self):
+ if not self.text:
+ # Nothing useful to draw, return now!
+ return
+
+ if self.resized() or self.text != self.prev_text or self.text_overlay is None:
+ # Need to recreate the cache drawing
+ self.text_overlay = cairo.ImageSurface(
+ cairo.FORMAT_ARGB32, self.width, self.height
+ )
+ ctx = cairo.Context(self.text_overlay)
+ pl = PangoCairo.create_layout(ctx)
+ pl.set_font_description(self.text_font)
+ pl.set_width(-1) # No text wrapping
+ pl.set_text(self.text, -1)
+ plsize = pl.get_size()
+ text_width = plsize[0] // SCALE
+ text_height = plsize[1] // SCALE
+ area_width_without_text = self.width - text_width
+ area_height_without_text = self.height - text_height
+ ctx.move_to(area_width_without_text // 2, area_height_without_text // 2)
+ ctx.set_source_rgb(1, 1, 1)
+ PangoCairo.update_layout(ctx, pl)
+ PangoCairo.show_layout(ctx, pl)
+ self.cr.set_source_surface(self.text_overlay)
+ self.cr.paint()
+
+ def resized(self):
+ return self.prev_width != self.width or self.prev_height != self.height
+
+ def set_fraction(self, fraction):
+ self.prev_fraction = self.fraction
+ self.fraction = fraction
+
+ def get_fraction(self):
+ return self.fraction
+
+ def get_text(self):
+ return self.text
+
+ def set_text(self, text):
+ self.prev_text = self.text
+ self.text = text
+
+ def set_pieces(self, pieces, num_pieces):
+ self.prev_pieces = self.pieces
+ self.pieces = pieces
+ self.num_pieces = num_pieces
+
+ def get_pieces(self):
+ return self.pieces
+
+ def clear(self):
+ self.pieces = self.prev_pieces = ()
+ self.num_pieces = None
+ self.text = self.prev_text = ''
+ self.fraction = self.prev_fraction = 0
+ self.progress_overlay = self.text_overlay = self.pieces_overlay = None
+ self.cr = None
+ self.update()
+
+ def update(self):
+ self.queue_draw()
diff --git a/deluge/ui/gtk3/pluginmanager.py b/deluge/ui/gtk3/pluginmanager.py
new file mode 100644
index 0000000..d60f8d3
--- /dev/null
+++ b/deluge/ui/gtk3/pluginmanager.py
@@ -0,0 +1,137 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+import deluge.component as component
+import deluge.pluginmanagerbase
+from deluge.configmanager import ConfigManager
+from deluge.ui.client import client
+
+log = logging.getLogger(__name__)
+
+
+class PluginManager(deluge.pluginmanagerbase.PluginManagerBase, component.Component):
+ def __init__(self):
+ component.Component.__init__(self, 'PluginManager')
+ self.config = ConfigManager('gtk3ui.conf')
+ deluge.pluginmanagerbase.PluginManagerBase.__init__(
+ self, 'gtk3ui.conf', 'deluge.plugin.gtk3ui'
+ )
+
+ self.hooks = {'on_apply_prefs': [], 'on_show_prefs': []}
+
+ client.register_event_handler(
+ 'PluginEnabledEvent', self._on_plugin_enabled_event
+ )
+ client.register_event_handler(
+ 'PluginDisabledEvent', self._on_plugin_disabled_event
+ )
+
+ def register_hook(self, hook, function):
+ """Register a hook function with the plugin manager"""
+ try:
+ self.hooks[hook].append(function)
+ except KeyError:
+ log.warning('Plugin attempting to register invalid hook.')
+
+ def deregister_hook(self, hook, function):
+ """Deregisters a hook function"""
+ try:
+ self.hooks[hook].remove(function)
+ except KeyError:
+ log.warning('Unable to deregister hook %s', hook)
+
+ def start(self):
+ """Start the plugin manager"""
+ # Update the enabled_plugins from the core
+ client.core.get_enabled_plugins().addCallback(self._on_get_enabled_plugins)
+ for instance in self.plugins.values():
+ component.start([instance.plugin._component_name])
+
+ def stop(self):
+ # Disable the plugins
+ self.disable_plugins()
+
+ def update(self):
+ pass
+
+ def _on_get_enabled_plugins(self, enabled_plugins):
+ log.debug('Core has these plugins enabled: %s', enabled_plugins)
+ for plugin in enabled_plugins:
+ self.enable_plugin(plugin)
+
+ def _on_plugin_enabled_event(self, name):
+ try:
+ self.enable_plugin(name)
+ except Exception as ex:
+ log.warning('Failed to enable plugin "%s": ex: %s', name, ex)
+
+ self.run_on_show_prefs()
+
+ def _on_plugin_disabled_event(self, name):
+ self.disable_plugin(name)
+
+ # Hook functions
+ def run_on_show_prefs(self):
+ """This hook is run before the user is shown the preferences dialog.
+ It is designed so that plugins can update their preference page with
+ the config."""
+ log.debug('run_on_show_prefs')
+ for function in self.hooks['on_show_prefs']:
+ function()
+
+ def run_on_apply_prefs(self):
+ """This hook is run after the user clicks Apply or OK in the preferences
+ dialog.
+ """
+ log.debug('run_on_apply_prefs')
+ for function in self.hooks['on_apply_prefs']:
+ function()
+
+ # Plugin functions.. will likely move to own class..
+
+ def add_torrentview_text_column(self, *args, **kwargs):
+ return component.get('TorrentView').add_text_column(*args, **kwargs)
+
+ def remove_torrentview_column(self, *args):
+ return component.get('TorrentView').remove_column(*args)
+
+ def add_toolbar_separator(self):
+ return component.get('ToolBar').add_separator()
+
+ def add_toolbar_button(self, *args, **kwargs):
+ return component.get('ToolBar').add_toolbutton(*args, **kwargs)
+
+ def remove_toolbar_button(self, *args):
+ return component.get('ToolBar').remove(*args)
+
+ def add_torrentmenu_menu(self, *args):
+ return component.get('MenuBar').torrentmenu.append(*args)
+
+ def add_torrentmenu_separator(self):
+ return component.get('MenuBar').add_torrentmenu_separator()
+
+ def remove_torrentmenu_item(self, *args):
+ return component.get('MenuBar').torrentmenu.remove(*args)
+
+ def add_preferences_page(self, *args):
+ return component.get('Preferences').add_page(*args)
+
+ def remove_preferences_page(self, *args):
+ return component.get('Preferences').remove_page(*args)
+
+ def update_torrent_view(self, *args):
+ return component.get('TorrentView').update(*args)
+
+ def get_selected_torrents(self):
+ """Returns a list of the selected torrent_ids"""
+ return component.get('TorrentView').get_selected_torrents()
diff --git a/deluge/ui/gtk3/preferences.py b/deluge/ui/gtk3/preferences.py
new file mode 100644
index 0000000..b196128
--- /dev/null
+++ b/deluge/ui/gtk3/preferences.py
@@ -0,0 +1,1527 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
+# Copyright (C) 2011 Pedro Algarvio <pedro@algarvio.me>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os
+from hashlib import sha1 as sha
+
+from gi import require_version
+from gi.repository import Gtk
+from gi.repository.Gdk import Color
+
+import deluge.common
+import deluge.component as component
+from deluge.configmanager import ConfigManager, get_config_dir
+from deluge.error import AuthManagerError, NotAuthorizedError
+from deluge.i18n import get_languages
+from deluge.ui.client import client
+from deluge.ui.common import DISK_CACHE_KEYS, PREFS_CATOG_TRANS
+
+from .common import associate_magnet_links, get_clipboard_text, get_deluge_icon
+from .dialogs import AccountDialog, ErrorDialog, InformationDialog, YesNoDialog
+from .path_chooser import PathChooser
+
+try:
+ from urllib.parse import urlparse
+except ImportError:
+ # PY2 fallback
+ from urlparse import urlparse # pylint: disable=ungrouped-imports
+
+try:
+ require_version('AppIndicator3', '0.1')
+ from gi.repository import AppIndicator3 # noqa: F401
+except (ImportError, ValueError):
+ appindicator = False
+else:
+ appindicator = True
+
+log = logging.getLogger(__name__)
+
+ACCOUNTS_USERNAME, ACCOUNTS_LEVEL, ACCOUNTS_PASSWORD = list(range(3))
+COLOR_MISSING, COLOR_WAITING, COLOR_DOWNLOADING, COLOR_COMPLETED = list(range(4))
+
+COLOR_STATES = {
+ 'missing': COLOR_MISSING,
+ 'waiting': COLOR_WAITING,
+ 'downloading': COLOR_DOWNLOADING,
+ 'completed': COLOR_COMPLETED,
+}
+
+
+class Preferences(component.Component):
+ def __init__(self):
+ component.Component.__init__(self, 'Preferences')
+ self.builder = Gtk.Builder()
+ self.builder.add_from_file(
+ deluge.common.resource_filename(
+ __package__, os.path.join('glade', 'preferences_dialog.ui')
+ )
+ )
+ self.pref_dialog = self.builder.get_object('pref_dialog')
+ self.pref_dialog.set_transient_for(component.get('MainWindow').window)
+ self.pref_dialog.set_icon(get_deluge_icon())
+ self.treeview = self.builder.get_object('treeview')
+ self.notebook = self.builder.get_object('notebook')
+ self.gtkui_config = ConfigManager('gtk3ui.conf')
+ self.window_open = False
+
+ self.load_pref_dialog_state()
+
+ self.builder.get_object('image_magnet').set_from_file(
+ deluge.common.get_pixmap('magnet.png')
+ )
+
+ # Hide the unused associate magnet button on OSX see: #2420
+ if deluge.common.osx_check():
+ self.builder.get_object('button_associate_magnet').hide()
+
+ # Setup the liststore for the categories (tab pages)
+ self.liststore = Gtk.ListStore(int, str, str)
+ self.treeview.set_model(self.liststore)
+ render = Gtk.CellRendererText()
+ column = Gtk.TreeViewColumn(None, render, text=2)
+ self.treeview.append_column(column)
+
+ # Add the default categories
+ prefs_categories = (
+ 'interface',
+ 'downloads',
+ 'bandwidth',
+ 'queue',
+ 'network',
+ 'proxy',
+ 'cache',
+ 'other',
+ 'daemon',
+ 'plugins',
+ )
+ for idx, category in enumerate(prefs_categories):
+ self.liststore.append([idx, category, PREFS_CATOG_TRANS[category]])
+
+ # Add and set separator after Plugins.
+ def set_separator(model, _iter, data=None):
+ entry = deluge.common.decode_bytes(model.get_value(_iter, 1))
+ if entry == '_separator_':
+ return True
+
+ self.treeview.set_row_separator_func(set_separator, None)
+ self.liststore.append([len(self.liststore), '_separator_', ''])
+ # Add a dummy notebook page to keep indexing synced with liststore.
+ self.notebook.append_page(Gtk.HSeparator())
+
+ # Setup accounts tab lisview
+ self.accounts_levels_mapping = None
+ self.accounts_authlevel = self.builder.get_object('accounts_authlevel')
+ self.accounts_liststore = Gtk.ListStore(str, str, str, int)
+ self.accounts_liststore.set_sort_column_id(
+ ACCOUNTS_USERNAME, Gtk.SortType.ASCENDING
+ )
+ self.accounts_listview = self.builder.get_object('accounts_listview')
+ self.accounts_listview.append_column(
+ Gtk.TreeViewColumn(
+ _('Username'), Gtk.CellRendererText(), text=ACCOUNTS_USERNAME
+ )
+ )
+ self.accounts_listview.append_column(
+ Gtk.TreeViewColumn(_('Level'), Gtk.CellRendererText(), text=ACCOUNTS_LEVEL)
+ )
+ password_column = Gtk.TreeViewColumn(
+ 'password', Gtk.CellRendererText(), text=ACCOUNTS_PASSWORD
+ )
+ self.accounts_listview.append_column(password_column)
+ password_column.set_visible(False)
+ self.accounts_listview.set_model(self.accounts_liststore)
+
+ self.accounts_listview.get_selection().connect(
+ 'changed', self.on_accounts_selection_changed
+ )
+ self.accounts_frame = self.builder.get_object('AccountsFrame')
+
+ # Setup plugin tab listview
+ # The third entry is for holding translated plugin names
+ self.plugin_liststore = Gtk.ListStore(str, bool, str)
+ self.plugin_liststore.set_sort_column_id(0, Gtk.SortType.ASCENDING)
+ self.plugin_listview = self.builder.get_object('plugin_listview')
+ self.plugin_listview.set_model(self.plugin_liststore)
+ render = Gtk.CellRendererToggle()
+ render.connect('toggled', self.on_plugin_toggled)
+ render.set_property('activatable', True)
+ self.plugin_listview.append_column(
+ Gtk.TreeViewColumn(_('Enabled'), render, active=1)
+ )
+ self.plugin_listview.append_column(
+ Gtk.TreeViewColumn(_('Plugin'), Gtk.CellRendererText(), text=2)
+ )
+
+ # Connect to the 'changed' event of TreeViewSelection to get selection
+ # changes.
+ self.treeview.get_selection().connect('changed', self.on_selection_changed)
+
+ self.plugin_listview.get_selection().connect(
+ 'changed', self.on_plugin_selection_changed
+ )
+
+ self.builder.connect_signals(self)
+
+ # Radio buttons to choose between systray and appindicator
+ self.builder.get_object('alignment_tray_type').set_visible(appindicator)
+
+ from .gtkui import DEFAULT_PREFS
+
+ self.COLOR_DEFAULTS = {}
+ for key in ('missing', 'waiting', 'downloading', 'completed'):
+ self.COLOR_DEFAULTS[key] = DEFAULT_PREFS['pieces_color_%s' % key][:]
+ del DEFAULT_PREFS
+
+ # These get updated by requests done to the core
+ self.all_plugins = []
+ self.enabled_plugins = []
+
+ self.setup_path_choosers()
+ self.load_languages()
+
+ def setup_path_choosers(self):
+ self.download_location_hbox = self.builder.get_object(
+ 'hbox_download_to_path_chooser'
+ )
+ self.download_location_path_chooser = PathChooser(
+ 'download_location_paths_list', parent=self.pref_dialog
+ )
+ self.download_location_hbox.add(self.download_location_path_chooser)
+ self.download_location_hbox.show_all()
+
+ self.move_completed_hbox = self.builder.get_object(
+ 'hbox_move_completed_to_path_chooser'
+ )
+ self.move_completed_path_chooser = PathChooser(
+ 'move_completed_paths_list', parent=self.pref_dialog
+ )
+ self.move_completed_hbox.add(self.move_completed_path_chooser)
+ self.move_completed_hbox.show_all()
+
+ self.copy_torrents_to_hbox = self.builder.get_object(
+ 'hbox_copy_torrent_files_path_chooser'
+ )
+ self.copy_torrent_files_path_chooser = PathChooser(
+ 'copy_torrent_files_to_paths_list', parent=self.pref_dialog
+ )
+ self.copy_torrents_to_hbox.add(self.copy_torrent_files_path_chooser)
+ self.copy_torrents_to_hbox.show_all()
+
+ def load_languages(self):
+ self.language_combo = self.builder.get_object('combobox_language')
+ self.language_checkbox = self.builder.get_object('checkbutton_language')
+ lang_model = self.language_combo.get_model()
+ langs = get_languages()
+ index = -1
+ for i, l in enumerate(langs):
+ lang_code, name = l
+ lang_model.append([lang_code, name])
+ if self.gtkui_config['language'] == lang_code:
+ index = i
+
+ if self.gtkui_config['language'] is None:
+ self.language_checkbox.set_active(True)
+ self.language_combo.set_visible(False)
+ else:
+ self.language_combo.set_visible(True)
+ if index != -1:
+ self.language_combo.set_active(index)
+
+ def __del__(self):
+ del self.gtkui_config
+
+ def add_page(self, name, widget):
+ """Add a another page to the notebook"""
+ # Create a header and scrolled window for the preferences tab
+ parent = widget.get_parent()
+ if parent:
+ parent.remove(widget)
+ vbox = Gtk.Box.new(Gtk.Orientation.VERTICAL, spacing=0)
+ label = Gtk.Label()
+ label.set_use_markup(True)
+ label.set_markup('<b><i><big>' + name + '</big></i></b>')
+ label.set_alignment(0.00, 0.50)
+ label.set_padding(10, 10)
+ vbox.pack_start(label, False, True, 0)
+ sep = Gtk.HSeparator()
+ vbox.pack_start(sep, False, True, 0)
+ align = Gtk.Alignment()
+ align.set_padding(5, 0, 0, 0)
+ align.set(0, 0, 1, 1)
+ align.add(widget)
+ vbox.pack_start(align, True, True, 0)
+ scrolled = Gtk.ScrolledWindow()
+ viewport = Gtk.Viewport()
+ viewport.set_shadow_type(Gtk.ShadowType.NONE)
+ viewport.add(vbox)
+ scrolled.add(viewport)
+ scrolled.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
+ scrolled.show_all()
+ # Add this page to the notebook
+ index = self.notebook.append_page(scrolled, None)
+ self.liststore.append([index, name, _(name)])
+ return name
+
+ def remove_page(self, name):
+ """Removes a page from the notebook"""
+ self.page_num_to_remove = None
+ self.iter_to_remove = None
+
+ def on_foreach_row(model, path, _iter, user_data):
+ row_name = deluge.common.decode_bytes(model.get_value(_iter, 1))
+ if row_name == user_data:
+ # This is the row we need to remove
+ self.page_num_to_remove = model.get_value(_iter, 0)
+ self.iter_to_remove = _iter
+ # Return True to stop foreach iterating
+ return True
+
+ self.liststore.foreach(on_foreach_row, name)
+
+ # Remove the page and row
+ if self.page_num_to_remove is not None:
+ self.notebook.remove_page(self.page_num_to_remove)
+ if self.iter_to_remove is not None:
+ self.liststore.remove(self.iter_to_remove)
+
+ # We need to re-adjust the index values for the remaining pages
+ for idx, __ in enumerate(self.liststore):
+ self.liststore[idx][0] = idx
+
+ def show(self, page=None):
+ """Page should be the string in the left list.. ie, 'Network' or
+ 'Bandwidth'"""
+ self.window_open = True
+ if page is not None:
+ for (index, string, __) in self.liststore:
+ if page == string:
+ self.treeview.get_selection().select_path(index)
+ break
+
+ component.get('PluginManager').run_on_show_prefs()
+
+ # Update the preferences dialog to reflect current config settings
+ self.core_config = {}
+ if client.connected():
+ self._get_accounts_tab_data()
+
+ def on_get_config(config):
+ self.core_config = config
+ client.core.get_available_plugins().addCallback(
+ on_get_available_plugins
+ )
+
+ def on_get_available_plugins(plugins):
+ self.all_plugins = plugins
+ client.core.get_enabled_plugins().addCallback(on_get_enabled_plugins)
+
+ def on_get_enabled_plugins(plugins):
+ self.enabled_plugins = plugins
+ client.core.get_listen_port().addCallback(on_get_listen_port)
+
+ def on_get_listen_port(port):
+ self.active_port = port
+ client.core.get_session_status(DISK_CACHE_KEYS).addCallback(
+ on_get_session_status
+ )
+
+ def on_get_session_status(status):
+ self.cache_status = status
+ self._show()
+
+ # This starts a series of client.core requests prior to showing the window
+ client.core.get_config().addCallback(on_get_config)
+ else:
+ self._show()
+
+ def start(self):
+ if self.window_open:
+ self.show()
+
+ def stop(self):
+ self.core_config = None
+ if self.window_open:
+ self._show()
+
+ def _show(self):
+ self.is_connected = self.core_config != {} and self.core_config is not None
+ core_widgets = {
+ 'chk_move_completed': ('active', 'move_completed'),
+ 'chk_copy_torrent_file': ('active', 'copy_torrent_file'),
+ 'chk_del_copy_torrent_file': ('active', 'del_copy_torrent_file'),
+ 'chk_pre_allocation': ('active', 'pre_allocate_storage'),
+ 'chk_prioritize_first_last_pieces': (
+ 'active',
+ 'prioritize_first_last_pieces',
+ ),
+ 'chk_sequential_download': ('active', 'sequential_download'),
+ 'chk_add_paused': ('active', 'add_paused'),
+ 'active_port_label': ('text', lambda: str(self.active_port)),
+ 'spin_incoming_port': (
+ 'value',
+ lambda: self.core_config['listen_ports'][0],
+ ),
+ 'chk_random_incoming_port': ('active', 'random_port'),
+ 'spin_outgoing_port_min': (
+ 'value',
+ lambda: self.core_config['outgoing_ports'][0],
+ ),
+ 'spin_outgoing_port_max': (
+ 'value',
+ lambda: self.core_config['outgoing_ports'][1],
+ ),
+ 'chk_random_outgoing_ports': ('active', 'random_outgoing_ports'),
+ 'entry_interface': ('text', 'listen_interface'),
+ 'entry_outgoing_interface': ('text', 'outgoing_interface'),
+ 'entry_peer_tos': ('text', 'peer_tos'),
+ 'chk_dht': ('active', 'dht'),
+ 'chk_upnp': ('active', 'upnp'),
+ 'chk_natpmp': ('active', 'natpmp'),
+ 'chk_utpex': ('active', 'utpex'),
+ 'chk_lsd': ('active', 'lsd'),
+ 'chk_new_releases': ('active', 'new_release_check'),
+ 'chk_send_info': ('active', 'send_info'),
+ 'entry_geoip': ('text', 'geoip_db_location'),
+ 'combo_encin': ('active', 'enc_in_policy'),
+ 'combo_encout': ('active', 'enc_out_policy'),
+ 'combo_enclevel': ('active', 'enc_level'),
+ 'spin_max_connections_global': ('value', 'max_connections_global'),
+ 'spin_max_download': ('value', 'max_download_speed'),
+ 'spin_max_upload': ('value', 'max_upload_speed'),
+ 'spin_max_upload_slots_global': ('value', 'max_upload_slots_global'),
+ 'spin_max_half_open_connections': ('value', 'max_connections_per_second'),
+ 'spin_max_connections_per_second': ('value', 'max_connections_per_second'),
+ 'chk_ignore_limits_on_local_network': (
+ 'active',
+ 'ignore_limits_on_local_network',
+ ),
+ 'chk_rate_limit_ip_overhead': ('active', 'rate_limit_ip_overhead'),
+ 'spin_max_connections_per_torrent': (
+ 'value',
+ 'max_connections_per_torrent',
+ ),
+ 'spin_max_upload_slots_per_torrent': (
+ 'value',
+ 'max_upload_slots_per_torrent',
+ ),
+ 'spin_max_download_per_torrent': (
+ 'value',
+ 'max_download_speed_per_torrent',
+ ),
+ 'spin_max_upload_per_torrent': ('value', 'max_upload_speed_per_torrent'),
+ 'spin_daemon_port': ('value', 'daemon_port'),
+ 'chk_allow_remote_connections': ('active', 'allow_remote'),
+ 'spin_active': ('value', 'max_active_limit'),
+ 'spin_seeding': ('value', 'max_active_seeding'),
+ 'spin_downloading': ('value', 'max_active_downloading'),
+ 'chk_dont_count_slow_torrents': ('active', 'dont_count_slow_torrents'),
+ 'chk_auto_manage_prefer_seeds': ('active', 'auto_manage_prefer_seeds'),
+ 'chk_queue_new_top': ('active', 'queue_new_to_top'),
+ 'spin_share_ratio_limit': ('value', 'share_ratio_limit'),
+ 'spin_seed_time_ratio_limit': ('value', 'seed_time_ratio_limit'),
+ 'spin_seed_time_limit': ('value', 'seed_time_limit'),
+ 'chk_share_ratio': ('active', 'stop_seed_at_ratio'),
+ 'spin_share_ratio': ('value', 'stop_seed_ratio'),
+ 'radio_pause_ratio': ('active', 'stop_seed_at_ratio'),
+ 'radio_remove_ratio': ('active', 'remove_seed_at_ratio'),
+ 'spin_cache_size': ('value', 'cache_size'),
+ 'spin_cache_expiry': ('value', 'cache_expiry'),
+ 'combo_proxy_type': ('active', lambda: self.core_config['proxy']['type']),
+ 'entry_proxy_user': ('text', lambda: self.core_config['proxy']['username']),
+ 'entry_proxy_pass': ('text', lambda: self.core_config['proxy']['password']),
+ 'entry_proxy_host': ('text', lambda: self.core_config['proxy']['hostname']),
+ 'spin_proxy_port': ('value', lambda: self.core_config['proxy']['port']),
+ 'chk_proxy_host_resolve': (
+ 'active',
+ lambda: self.core_config['proxy']['proxy_hostnames'],
+ ),
+ 'chk_proxy_peer_conn': (
+ 'active',
+ lambda: self.core_config['proxy']['proxy_peer_connections'],
+ ),
+ 'chk_proxy_tracker_conn': (
+ 'active',
+ lambda: self.core_config['proxy']['proxy_tracker_connections'],
+ ),
+ 'chk_force_proxy': (
+ 'active',
+ lambda: self.core_config['proxy']['force_proxy'],
+ ),
+ 'chk_anonymous_mode': (
+ 'active',
+ lambda: self.core_config['proxy']['anonymous_mode'],
+ ),
+ 'accounts_add': (None, None),
+ 'accounts_listview': (None, None),
+ 'button_cache_refresh': (None, None),
+ 'button_plugin_install': (None, None),
+ 'button_rescan_plugins': (None, None),
+ 'button_find_plugins': (None, None),
+ 'button_testport': (None, None),
+ 'plugin_listview': (None, None),
+ }
+
+ core_widgets[self.download_location_path_chooser] = (
+ 'path_chooser',
+ 'download_location',
+ )
+ core_widgets[self.move_completed_path_chooser] = (
+ 'path_chooser',
+ 'move_completed_path',
+ )
+ core_widgets[self.copy_torrent_files_path_chooser] = (
+ 'path_chooser',
+ 'torrentfiles_location',
+ )
+
+ # Update the widgets accordingly
+ for key in core_widgets:
+ modifier = core_widgets[key][0]
+ try:
+ widget = self.builder.get_object(key)
+ except TypeError:
+ widget = key
+
+ widget.set_sensitive(self.is_connected)
+
+ if self.is_connected:
+ value = core_widgets[key][1]
+ try:
+ value = self.core_config[value]
+ except KeyError:
+ if callable(value):
+ value = value()
+ elif modifier:
+ value = {
+ 'active': False,
+ 'not_active': False,
+ 'value': 0,
+ 'text': '',
+ 'path_chooser': '',
+ }[modifier]
+
+ if modifier == 'active':
+ widget.set_active(value)
+ elif modifier == 'not_active':
+ widget.set_active(not value)
+ elif modifier == 'value':
+ widget.set_value(float(value))
+ elif modifier == 'text':
+ if value is None:
+ value = ''
+ widget.set_text(value)
+ elif modifier == 'path_chooser':
+ widget.set_text(value, cursor_end=False, default_text=True)
+
+ if self.is_connected:
+ for key in core_widgets:
+ try:
+ widget = self.builder.get_object(key)
+ except TypeError:
+ widget = key
+ # Update the toggle status if necessary
+ self.on_toggle(widget)
+
+ # Downloads tab #
+ self.builder.get_object('chk_show_dialog').set_active(
+ self.gtkui_config['interactive_add']
+ )
+ self.builder.get_object('chk_focus_dialog').set_active(
+ self.gtkui_config['focus_add_dialog']
+ )
+
+ # Interface tab #
+ self.builder.get_object('chk_use_tray').set_active(
+ self.gtkui_config['enable_system_tray']
+ )
+ self.builder.get_object('chk_min_on_close').set_active(
+ self.gtkui_config['close_to_tray']
+ )
+ self.builder.get_object('chk_start_in_tray').set_active(
+ self.gtkui_config['start_in_tray']
+ )
+ self.builder.get_object('radio_appind').set_active(
+ self.gtkui_config['enable_appindicator']
+ )
+ self.builder.get_object('chk_lock_tray').set_active(
+ self.gtkui_config['lock_tray']
+ )
+ self.builder.get_object('radio_standalone').set_active(
+ self.gtkui_config['standalone']
+ )
+ self.builder.get_object('radio_thinclient').set_active(
+ not self.gtkui_config['standalone']
+ )
+ self.builder.get_object('chk_show_rate_in_title').set_active(
+ self.gtkui_config['show_rate_in_title']
+ )
+ self.builder.get_object('chk_focus_main_window_on_add').set_active(
+ self.gtkui_config['focus_main_window_on_add']
+ )
+ self.builder.get_object('piecesbar_toggle').set_active(
+ self.gtkui_config['show_piecesbar']
+ )
+ self.__set_color('completed', from_config=True)
+ self.__set_color('downloading', from_config=True)
+ self.__set_color('waiting', from_config=True)
+ self.__set_color('missing', from_config=True)
+
+ # Other tab #
+ self.builder.get_object('chk_show_new_releases').set_active(
+ self.gtkui_config['show_new_releases']
+ )
+
+ # Cache tab #
+ if client.connected():
+ self.__update_cache_status()
+
+ # Plugins tab #
+ all_plugins = self.all_plugins
+ enabled_plugins = self.enabled_plugins
+ # Clear the existing list so we don't duplicate entries.
+ self.plugin_liststore.clear()
+ # Iterate through the lists and add them to the liststore
+ for plugin in all_plugins:
+ enabled = plugin in enabled_plugins
+ row = self.plugin_liststore.append()
+ self.plugin_liststore.set_value(row, 0, plugin)
+ self.plugin_liststore.set_value(row, 1, enabled)
+ self.plugin_liststore.set_value(row, 2, _(plugin))
+
+ # Now show the dialog
+ self.pref_dialog.show()
+
+ def set_config(self, hide=False):
+ """
+ Sets all altered config values in the core.
+
+ :param hide: bool, if True, will not re-show the dialog and will hide it instead
+ """
+
+ # Get the values from the dialog
+ new_core_config = {}
+ new_gtkui_config = {}
+
+ # Downloads tab #
+ new_gtkui_config['interactive_add'] = self.builder.get_object(
+ 'chk_show_dialog'
+ ).get_active()
+ new_gtkui_config['focus_add_dialog'] = self.builder.get_object(
+ 'chk_focus_dialog'
+ ).get_active()
+
+ for state in ('missing', 'waiting', 'downloading', 'completed'):
+ color = self.builder.get_object('%s_color' % state).get_color()
+ new_gtkui_config['pieces_color_%s' % state] = [
+ color.red,
+ color.green,
+ color.blue,
+ ]
+
+ new_core_config['copy_torrent_file'] = self.builder.get_object(
+ 'chk_copy_torrent_file'
+ ).get_active()
+ new_core_config['del_copy_torrent_file'] = self.builder.get_object(
+ 'chk_del_copy_torrent_file'
+ ).get_active()
+ new_core_config['move_completed'] = self.builder.get_object(
+ 'chk_move_completed'
+ ).get_active()
+
+ new_core_config[
+ 'download_location'
+ ] = self.download_location_path_chooser.get_text()
+ new_core_config[
+ 'move_completed_path'
+ ] = self.move_completed_path_chooser.get_text()
+ new_core_config[
+ 'torrentfiles_location'
+ ] = self.copy_torrent_files_path_chooser.get_text()
+ new_core_config['prioritize_first_last_pieces'] = self.builder.get_object(
+ 'chk_prioritize_first_last_pieces'
+ ).get_active()
+ new_core_config['sequential_download'] = self.builder.get_object(
+ 'chk_sequential_download'
+ ).get_active()
+ new_core_config['add_paused'] = self.builder.get_object(
+ 'chk_add_paused'
+ ).get_active()
+ new_core_config['pre_allocate_storage'] = self.builder.get_object(
+ 'chk_pre_allocation'
+ ).get_active()
+
+ # Network tab #
+ listen_ports = [
+ self.builder.get_object('spin_incoming_port').get_value_as_int()
+ ] * 2
+ new_core_config['listen_ports'] = listen_ports
+ new_core_config['random_port'] = self.builder.get_object(
+ 'chk_random_incoming_port'
+ ).get_active()
+ outgoing_ports = (
+ self.builder.get_object('spin_outgoing_port_min').get_value_as_int(),
+ self.builder.get_object('spin_outgoing_port_max').get_value_as_int(),
+ )
+ new_core_config['outgoing_ports'] = outgoing_ports
+ new_core_config['random_outgoing_ports'] = self.builder.get_object(
+ 'chk_random_outgoing_ports'
+ ).get_active()
+ incoming_address = self.builder.get_object('entry_interface').get_text().strip()
+ if deluge.common.is_ip(incoming_address) or not incoming_address:
+ new_core_config['listen_interface'] = incoming_address
+ new_core_config['outgoing_interface'] = (
+ self.builder.get_object('entry_outgoing_interface').get_text().strip()
+ )
+ new_core_config['peer_tos'] = self.builder.get_object(
+ 'entry_peer_tos'
+ ).get_text()
+ new_core_config['dht'] = self.builder.get_object('chk_dht').get_active()
+ new_core_config['upnp'] = self.builder.get_object('chk_upnp').get_active()
+ new_core_config['natpmp'] = self.builder.get_object('chk_natpmp').get_active()
+ new_core_config['utpex'] = self.builder.get_object('chk_utpex').get_active()
+ new_core_config['lsd'] = self.builder.get_object('chk_lsd').get_active()
+ new_core_config['enc_in_policy'] = self.builder.get_object(
+ 'combo_encin'
+ ).get_active()
+ new_core_config['enc_out_policy'] = self.builder.get_object(
+ 'combo_encout'
+ ).get_active()
+ new_core_config['enc_level'] = self.builder.get_object(
+ 'combo_enclevel'
+ ).get_active()
+
+ # Bandwidth tab #
+ new_core_config['max_connections_global'] = self.builder.get_object(
+ 'spin_max_connections_global'
+ ).get_value_as_int()
+ new_core_config['max_download_speed'] = self.builder.get_object(
+ 'spin_max_download'
+ ).get_value()
+ new_core_config['max_upload_speed'] = self.builder.get_object(
+ 'spin_max_upload'
+ ).get_value()
+ new_core_config['max_upload_slots_global'] = self.builder.get_object(
+ 'spin_max_upload_slots_global'
+ ).get_value_as_int()
+ new_core_config['max_half_open_connections'] = self.builder.get_object(
+ 'spin_max_half_open_connections'
+ ).get_value_as_int()
+ new_core_config['max_connections_per_second'] = self.builder.get_object(
+ 'spin_max_connections_per_second'
+ ).get_value_as_int()
+ new_core_config['max_connections_per_torrent'] = self.builder.get_object(
+ 'spin_max_connections_per_torrent'
+ ).get_value_as_int()
+ new_core_config['max_upload_slots_per_torrent'] = self.builder.get_object(
+ 'spin_max_upload_slots_per_torrent'
+ ).get_value_as_int()
+ new_core_config['max_upload_speed_per_torrent'] = self.builder.get_object(
+ 'spin_max_upload_per_torrent'
+ ).get_value()
+ new_core_config['max_download_speed_per_torrent'] = self.builder.get_object(
+ 'spin_max_download_per_torrent'
+ ).get_value()
+ new_core_config['ignore_limits_on_local_network'] = self.builder.get_object(
+ 'chk_ignore_limits_on_local_network'
+ ).get_active()
+ new_core_config['rate_limit_ip_overhead'] = self.builder.get_object(
+ 'chk_rate_limit_ip_overhead'
+ ).get_active()
+
+ # Interface tab #
+ new_gtkui_config['enable_system_tray'] = self.builder.get_object(
+ 'chk_use_tray'
+ ).get_active()
+ new_gtkui_config['close_to_tray'] = self.builder.get_object(
+ 'chk_min_on_close'
+ ).get_active()
+ new_gtkui_config['start_in_tray'] = self.builder.get_object(
+ 'chk_start_in_tray'
+ ).get_active()
+ new_gtkui_config['enable_appindicator'] = self.builder.get_object(
+ 'radio_appind'
+ ).get_active()
+ new_gtkui_config['lock_tray'] = self.builder.get_object(
+ 'chk_lock_tray'
+ ).get_active()
+ passhex = sha(
+ deluge.common.decode_bytes(
+ self.builder.get_object('txt_tray_password').get_text()
+ ).encode()
+ ).hexdigest()
+ if passhex != 'c07eb5a8c0dc7bb81c217b67f11c3b7a5e95ffd7':
+ new_gtkui_config['tray_password'] = passhex
+
+ was_standalone = self.gtkui_config['standalone']
+ new_gtkui_standalone = self.builder.get_object('radio_standalone').get_active()
+ new_gtkui_config['standalone'] = new_gtkui_standalone
+
+ new_gtkui_config['show_rate_in_title'] = self.builder.get_object(
+ 'chk_show_rate_in_title'
+ ).get_active()
+ new_gtkui_config['focus_main_window_on_add'] = self.builder.get_object(
+ 'chk_focus_main_window_on_add'
+ ).get_active()
+
+ # Other tab #
+ new_gtkui_config['show_new_releases'] = self.builder.get_object(
+ 'chk_show_new_releases'
+ ).get_active()
+ new_core_config['send_info'] = self.builder.get_object(
+ 'chk_send_info'
+ ).get_active()
+ new_core_config['geoip_db_location'] = self.builder.get_object(
+ 'entry_geoip'
+ ).get_text()
+
+ # Daemon tab #
+ new_core_config['daemon_port'] = self.builder.get_object(
+ 'spin_daemon_port'
+ ).get_value_as_int()
+ new_core_config['allow_remote'] = self.builder.get_object(
+ 'chk_allow_remote_connections'
+ ).get_active()
+ new_core_config['new_release_check'] = self.builder.get_object(
+ 'chk_new_releases'
+ ).get_active()
+
+ # Proxy tab #
+ new_core_config['proxy'] = {
+ 'type': self.builder.get_object('combo_proxy_type').get_active(),
+ 'username': self.builder.get_object('entry_proxy_user').get_text(),
+ 'password': self.builder.get_object('entry_proxy_pass').get_text(),
+ 'hostname': self.builder.get_object('entry_proxy_host').get_text(),
+ 'port': self.builder.get_object('spin_proxy_port').get_value_as_int(),
+ 'proxy_hostnames': self.builder.get_object(
+ 'chk_proxy_host_resolve'
+ ).get_active(),
+ 'proxy_peer_connections': self.builder.get_object(
+ 'chk_proxy_peer_conn'
+ ).get_active(),
+ 'proxy_tracker_connections': self.builder.get_object(
+ 'chk_proxy_tracker_conn'
+ ).get_active(),
+ 'force_proxy': self.builder.get_object('chk_force_proxy').get_active(),
+ 'anonymous_mode': self.builder.get_object(
+ 'chk_anonymous_mode'
+ ).get_active(),
+ }
+
+ # Queue tab #
+ new_core_config['queue_new_to_top'] = self.builder.get_object(
+ 'chk_queue_new_top'
+ ).get_active()
+ new_core_config['max_active_seeding'] = self.builder.get_object(
+ 'spin_seeding'
+ ).get_value_as_int()
+ new_core_config['max_active_downloading'] = self.builder.get_object(
+ 'spin_downloading'
+ ).get_value_as_int()
+ new_core_config['max_active_limit'] = self.builder.get_object(
+ 'spin_active'
+ ).get_value_as_int()
+ new_core_config['dont_count_slow_torrents'] = self.builder.get_object(
+ 'chk_dont_count_slow_torrents'
+ ).get_active()
+ new_core_config['auto_manage_prefer_seeds'] = self.builder.get_object(
+ 'chk_auto_manage_prefer_seeds'
+ ).get_active()
+ new_core_config['stop_seed_at_ratio'] = self.builder.get_object(
+ 'chk_share_ratio'
+ ).get_active()
+ new_core_config['remove_seed_at_ratio'] = self.builder.get_object(
+ 'radio_remove_ratio'
+ ).get_active()
+ new_core_config['stop_seed_ratio'] = self.builder.get_object(
+ 'spin_share_ratio'
+ ).get_value()
+ new_core_config['share_ratio_limit'] = self.builder.get_object(
+ 'spin_share_ratio_limit'
+ ).get_value()
+ new_core_config['seed_time_ratio_limit'] = self.builder.get_object(
+ 'spin_seed_time_ratio_limit'
+ ).get_value()
+ new_core_config['seed_time_limit'] = self.builder.get_object(
+ 'spin_seed_time_limit'
+ ).get_value()
+
+ # Cache tab #
+ new_core_config['cache_size'] = self.builder.get_object(
+ 'spin_cache_size'
+ ).get_value_as_int()
+ new_core_config['cache_expiry'] = self.builder.get_object(
+ 'spin_cache_expiry'
+ ).get_value_as_int()
+
+ # Run plugin hook to apply preferences
+ component.get('PluginManager').run_on_apply_prefs()
+
+ # Language
+ if self.language_checkbox.get_active():
+ new_gtkui_config['language'] = None
+ else:
+ active = self.language_combo.get_active()
+ if active == -1:
+ dialog = InformationDialog(
+ _('Attention'), _('You must choose a language')
+ )
+ dialog.run()
+ return
+ else:
+ model = self.language_combo.get_model()
+ new_gtkui_config['language'] = model.get(model.get_iter(active), 0)[0]
+
+ if new_gtkui_config['language'] != self.gtkui_config['language']:
+ dialog = InformationDialog(
+ _('Attention'),
+ _('You must now restart the deluge UI for the changes to take effect.'),
+ )
+ dialog.run()
+
+ # GtkUI
+ for key in new_gtkui_config:
+ # The values do not match so this needs to be updated
+ if self.gtkui_config[key] != new_gtkui_config[key]:
+ self.gtkui_config[key] = new_gtkui_config[key]
+
+ # Core
+ if client.connected():
+ # Only do this if we're connected to a daemon
+ config_to_set = {}
+ for key in new_core_config:
+ # The values do not match so this needs to be updated
+ if self.core_config[key] != new_core_config[key]:
+ config_to_set[key] = new_core_config[key]
+
+ if config_to_set:
+ # Set each changed config value in the core
+ client.core.set_config(config_to_set)
+ client.force_call(True)
+ # Update the configuration
+ self.core_config.update(config_to_set)
+
+ if hide:
+ self.hide()
+ else:
+ # Re-show the dialog to make sure everything has been updated
+ self.show()
+
+ if was_standalone != new_gtkui_standalone:
+
+ def on_response(response):
+ if response == Gtk.ResponseType.YES:
+ shutdown_daemon = (
+ not client.is_standalone()
+ and client.connected()
+ and client.is_localhost()
+ )
+ component.get('MainWindow').quit(
+ shutdown=shutdown_daemon, restart=True
+ )
+ else:
+ self.gtkui_config['standalone'] = not new_gtkui_standalone
+ self.builder.get_object('radio_standalone').set_active(
+ self.gtkui_config['standalone']
+ )
+ self.builder.get_object('radio_thinclient').set_active(
+ not self.gtkui_config['standalone']
+ )
+
+ mode = 'Thinclient' if was_standalone else 'Standalone'
+ dialog = YesNoDialog(
+ _('Switching Deluge Client Mode...'),
+ _('Do you want to restart to use %s mode?' % mode),
+ )
+ dialog.run().addCallback(on_response)
+
+ def hide(self):
+ self.window_open = False
+ self.builder.get_object('port_img').hide()
+ self.pref_dialog.hide()
+
+ def __update_cache_status(self):
+ # Updates the cache status labels with the info in the dict
+ cache_labels = (
+ 'label_cache_read_ops',
+ 'label_cache_write_ops',
+ 'label_cache_num_blocks_read',
+ 'label_cache_num_blocks_written',
+ 'label_cache_read_hit_ratio',
+ 'label_cache_write_hit_ratio',
+ 'label_cache_num_blocks_cache_hits',
+ 'label_cache_disk_blocks_in_use',
+ 'label_cache_read_cache_blocks',
+ )
+
+ for widget_name in cache_labels:
+ widget = self.builder.get_object(widget_name)
+ key = widget_name[len('label_cache_') :]
+ if not widget_name.endswith('ratio'):
+ key = 'disk.' + key
+ value = self.cache_status.get(key, 0)
+ if isinstance(value, float):
+ value = '%.2f' % value
+ else:
+ value = str(value)
+
+ widget.set_text(value)
+
+ def on_button_cache_refresh_clicked(self, widget):
+ def on_get_session_status(status):
+ self.cache_status = status
+ self.__update_cache_status()
+
+ client.core.get_session_status(DISK_CACHE_KEYS).addCallback(
+ on_get_session_status
+ )
+
+ def on_pref_dialog_delete_event(self, widget, event):
+ self.hide()
+ return True
+
+ def load_pref_dialog_state(self):
+ w = self.gtkui_config['pref_dialog_width']
+ h = self.gtkui_config['pref_dialog_height']
+ if w is not None and h is not None:
+ self.pref_dialog.resize(w, h)
+
+ def on_pref_dialog_configure_event(self, widget, event):
+ self.gtkui_config['pref_dialog_width'] = event.width
+ self.gtkui_config['pref_dialog_height'] = event.height
+
+ def on_toggle(self, widget):
+ """Handles widget sensitivity based on radio/check button values."""
+ try:
+ value = widget.get_active()
+ except Exception:
+ return
+
+ path_choosers = {
+ 'download_location_path_chooser': self.download_location_path_chooser,
+ 'move_completed_path_chooser': self.move_completed_path_chooser,
+ 'torrentfiles_location_path_chooser': self.copy_torrent_files_path_chooser,
+ }
+
+ dependents = {
+ 'chk_show_dialog': {'chk_focus_dialog': True},
+ 'chk_random_incoming_port': {'spin_incoming_port': False},
+ 'chk_random_outgoing_ports': {
+ 'spin_outgoing_port_min': False,
+ 'spin_outgoing_port_max': False,
+ },
+ 'chk_use_tray': {
+ 'radio_appind': True,
+ 'radio_systray': True,
+ 'chk_min_on_close': True,
+ 'chk_start_in_tray': True,
+ 'alignment_tray_type': True,
+ 'chk_lock_tray': True,
+ },
+ 'chk_lock_tray': {'txt_tray_password': True, 'password_label': True},
+ 'radio_open_folder_custom': {
+ 'combo_file_manager': False,
+ 'txt_open_folder_location': True,
+ },
+ 'chk_move_completed': {'move_completed_path_chooser': True},
+ 'chk_copy_torrent_file': {
+ 'torrentfiles_location_path_chooser': True,
+ 'chk_del_copy_torrent_file': True,
+ },
+ 'chk_share_ratio': {
+ 'spin_share_ratio': True,
+ 'radio_pause_ratio': True,
+ 'radio_remove_ratio': True,
+ },
+ }
+
+ def update_dependent_widgets(name, value):
+ dependency = dependents[name]
+ for dep in dependency:
+ if dep in path_choosers:
+ depwidget = path_choosers[dep]
+ else:
+ depwidget = self.builder.get_object(dep)
+ sensitive = [not value, value][dependency[dep]]
+ depwidget.set_sensitive(sensitive)
+ if dep in dependents:
+ update_dependent_widgets(dep, depwidget.get_active() and sensitive)
+
+ for key in dependents:
+ if widget != self.builder.get_object(key):
+ continue
+ update_dependent_widgets(key, value)
+
+ def on_button_ok_clicked(self, data):
+ log.debug('on_button_ok_clicked')
+ self.set_config(hide=True)
+ return True
+
+ def on_button_apply_clicked(self, data):
+ log.debug('on_button_apply_clicked')
+ self.set_config()
+
+ def on_button_cancel_clicked(self, data):
+ log.debug('on_button_cancel_clicked')
+ self.hide()
+ return True
+
+ def on_selection_changed(self, treeselection):
+ # Show the correct notebook page based on what row is selected.
+ (model, row) = treeselection.get_selected()
+ try:
+ if model.get_value(row, 1) == 'daemon':
+ # Let's see update the accounts related stuff
+ if client.connected():
+ self._get_accounts_tab_data()
+ self.notebook.set_current_page(model.get_value(row, 0))
+ except TypeError:
+ pass
+
+ def on_test_port_clicked(self, data):
+ log.debug('on_test_port_clicked')
+
+ def on_get_test(status):
+ if status:
+ self.builder.get_object('port_img').set_from_icon_name(
+ 'emblem-ok-symbolic', Gtk.IconSize.MENU
+ )
+ self.builder.get_object('port_img').show()
+ else:
+ self.builder.get_object('port_img').set_from_icon_name(
+ 'dialog-warning-symbolic', Gtk.IconSize.MENU
+ )
+ self.builder.get_object('port_img').show()
+
+ client.core.test_listen_port().addCallback(on_get_test)
+ # XXX: Consider using gtk.Spinner() instead of the loading gif
+ # It requires gtk.ver > 2.12
+ self.builder.get_object('port_img').set_from_file(
+ deluge.common.get_pixmap('loading.gif')
+ )
+ self.builder.get_object('port_img').show()
+ client.force_call()
+
+ def on_plugin_toggled(self, renderer, path):
+ row = self.plugin_liststore.get_iter_from_string(path)
+ name = self.plugin_liststore.get_value(row, 0)
+ value = self.plugin_liststore.get_value(row, 1)
+ log.debug('on_plugin_toggled - %s: %s', name, value)
+ self.plugin_liststore.set_value(row, 1, not value)
+ if not value:
+ d = client.core.enable_plugin(name)
+ else:
+ d = client.core.disable_plugin(name)
+
+ def on_plugin_action(arg):
+ if not value and arg is False:
+ log.warning('Failed to enable plugin: %s', name)
+ self.plugin_liststore.set_value(row, 1, False)
+
+ d.addBoth(on_plugin_action)
+
+ def on_plugin_selection_changed(self, treeselection):
+ log.debug('on_plugin_selection_changed')
+ (model, itr) = treeselection.get_selected()
+ if not itr:
+ return
+ name = model[itr][0]
+ plugin_info = component.get('PluginManager').get_plugin_info(name)
+ self.builder.get_object('label_plugin_author').set_text(plugin_info['Author'])
+ self.builder.get_object('label_plugin_version').set_text(plugin_info['Version'])
+ self.builder.get_object('label_plugin_email').set_text(
+ plugin_info['Author-email']
+ )
+ self.builder.get_object('label_plugin_homepage').set_text(
+ plugin_info['Home-page']
+ )
+ self.builder.get_object('label_plugin_details').set_text(
+ plugin_info['Description']
+ )
+
+ def on_button_plugin_install_clicked(self, widget):
+ log.debug('on_button_plugin_install_clicked')
+ chooser = Gtk.FileChooserDialog(
+ _('Select the Plugin'),
+ self.pref_dialog,
+ Gtk.FileChooserAction.OPEN,
+ buttons=(
+ _('_Cancel'),
+ Gtk.ResponseType.CANCEL,
+ _('_Open'),
+ Gtk.ResponseType.OK,
+ ),
+ )
+
+ chooser.set_transient_for(self.pref_dialog)
+ chooser.set_select_multiple(False)
+ chooser.set_property('skip-taskbar-hint', True)
+
+ file_filter = Gtk.FileFilter()
+ file_filter.set_name(_('Plugin Eggs'))
+ file_filter.add_pattern('*.' + 'egg')
+ chooser.add_filter(file_filter)
+
+ # Run the dialog
+ response = chooser.run()
+
+ if response == Gtk.ResponseType.OK:
+ filepath = deluge.common.decode_bytes(chooser.get_filename())
+ else:
+ chooser.destroy()
+ return
+
+ from base64 import b64encode
+ import shutil
+
+ filename = os.path.split(filepath)[1]
+ shutil.copyfile(filepath, os.path.join(get_config_dir(), 'plugins', filename))
+
+ component.get('PluginManager').scan_for_plugins()
+
+ if not client.is_localhost():
+ # We need to send this plugin to the daemon
+ with open(filepath, 'rb') as _file:
+ filedump = b64encode(_file.read())
+ client.core.upload_plugin(filename, filedump)
+
+ client.core.rescan_plugins()
+ chooser.destroy()
+ # We need to re-show the preferences dialog to show the new plugins
+ self.show()
+
+ def on_button_rescan_plugins_clicked(self, widget):
+ component.get('PluginManager').scan_for_plugins()
+ if client.connected():
+ client.core.rescan_plugins()
+ self.show()
+
+ def on_button_find_plugins_clicked(self, widget):
+ deluge.common.open_url_in_browser('http://dev.deluge-torrent.org/wiki/Plugins')
+
+ def on_combo_encryption_changed(self, widget):
+ combo_encin = self.builder.get_object('combo_encin').get_active()
+ combo_encout = self.builder.get_object('combo_encout').get_active()
+ combo_enclevel = self.builder.get_object('combo_enclevel')
+
+ # If incoming and outgoing both set to disabled, disable level combobox
+ if combo_encin == 2 and combo_encout == 2:
+ combo_enclevel.set_sensitive(False)
+ elif self.is_connected:
+ combo_enclevel.set_sensitive(True)
+
+ def on_combo_proxy_type_changed(self, widget):
+ proxy_type = self.builder.get_object('combo_proxy_type').get_active()
+ proxy_entries = [
+ 'label_proxy_host',
+ 'entry_proxy_host',
+ 'label_proxy_port',
+ 'spin_proxy_port',
+ 'label_proxy_pass',
+ 'entry_proxy_pass',
+ 'label_proxy_user',
+ 'entry_proxy_user',
+ 'chk_proxy_host_resolve',
+ 'chk_proxy_peer_conn',
+ 'chk_proxy_tracker_conn',
+ ]
+
+ # 0: None, 1: Socks4, 2: Socks5, 3: Socks5 Auth, 4: HTTP, 5: HTTP Auth, 6: I2P
+ show_entries = []
+ if proxy_type > 0:
+ show_entries.extend(
+ [
+ 'label_proxy_host',
+ 'entry_proxy_host',
+ 'label_proxy_port',
+ 'spin_proxy_port',
+ 'chk_proxy_peer_conn',
+ 'chk_proxy_tracker_conn',
+ ]
+ )
+ if proxy_type in (3, 5):
+ show_entries.extend(
+ [
+ 'label_proxy_pass',
+ 'entry_proxy_pass',
+ 'label_proxy_user',
+ 'entry_proxy_user',
+ ]
+ )
+ if proxy_type in (2, 3, 4, 5):
+ show_entries.extend(['chk_proxy_host_resolve'])
+
+ for entry in proxy_entries:
+ if entry in show_entries:
+ self.builder.get_object(entry).show()
+ else:
+ self.builder.get_object(entry).hide()
+
+ def on_entry_proxy_host_paste_clipboard(self, widget):
+ text = get_clipboard_text()
+ log.debug('on_entry_proxy_host_paste-clipboard: got paste: %s', text)
+ text = text if '//' in text else '//' + text
+ parsed = urlparse(text)
+ if parsed.hostname:
+ widget.set_text(parsed.hostname)
+ widget.emit_stop_by_name('paste-clipboard')
+ if parsed.port:
+ self.builder.get_object('spin_proxy_port').set_value(parsed.port)
+ if parsed.username:
+ self.builder.get_object('entry_proxy_user').set_text(parsed.username)
+ if parsed.password:
+ self.builder.get_object('entry_proxy_pass').set_text(parsed.password)
+
+ def on_button_associate_magnet_clicked(self, widget):
+ associate_magnet_links(True)
+
+ def _get_accounts_tab_data(self):
+ def on_ok(accounts):
+ self.accounts_frame.show()
+ self.on_get_known_accounts(accounts)
+
+ def on_fail(failure):
+ if failure.type == NotAuthorizedError:
+ self.accounts_frame.hide()
+ else:
+ ErrorDialog(
+ _('Server Side Error'),
+ _('An error occurred on the server'),
+ parent=self.pref_dialog,
+ details=failure.getErrorMessage(),
+ ).run()
+
+ client.core.get_known_accounts().addCallback(on_ok).addErrback(on_fail)
+
+ def on_get_known_accounts(self, known_accounts):
+ known_accounts_to_log = []
+ for account in known_accounts:
+ account_to_log = {}
+ for key, value in account.copy().items():
+ if key == 'password':
+ value = '*' * len(value)
+ account_to_log[key] = value
+ known_accounts_to_log.append(account_to_log)
+ log.debug('on_known_accounts: %s', known_accounts_to_log)
+
+ self.accounts_liststore.clear()
+
+ for account in known_accounts:
+ accounts_iter = self.accounts_liststore.append()
+ self.accounts_liststore.set_value(
+ accounts_iter, ACCOUNTS_USERNAME, account['username']
+ )
+ self.accounts_liststore.set_value(
+ accounts_iter, ACCOUNTS_LEVEL, account['authlevel']
+ )
+ self.accounts_liststore.set_value(
+ accounts_iter, ACCOUNTS_PASSWORD, account['password']
+ )
+
+ def on_accounts_selection_changed(self, treeselection):
+ log.debug('on_accounts_selection_changed')
+ (model, itr) = treeselection.get_selected()
+ if not itr:
+ return
+ username = model[itr][0]
+ if username:
+ self.builder.get_object('accounts_edit').set_sensitive(True)
+ self.builder.get_object('accounts_delete').set_sensitive(True)
+ else:
+ self.builder.get_object('accounts_edit').set_sensitive(False)
+ self.builder.get_object('accounts_delete').set_sensitive(False)
+
+ def on_accounts_add_clicked(self, widget):
+ dialog = AccountDialog(
+ levels_mapping=client.auth_levels_mapping, parent=self.pref_dialog
+ )
+
+ def dialog_finished(response_id):
+ username = dialog.get_username()
+ password = dialog.get_password()
+ authlevel = dialog.get_authlevel()
+
+ def add_ok(rv):
+ accounts_iter = self.accounts_liststore.append()
+ self.accounts_liststore.set_value(
+ accounts_iter, ACCOUNTS_USERNAME, username
+ )
+ self.accounts_liststore.set_value(
+ accounts_iter, ACCOUNTS_LEVEL, authlevel
+ )
+ self.accounts_liststore.set_value(
+ accounts_iter, ACCOUNTS_PASSWORD, password
+ )
+
+ def add_fail(failure):
+ if failure.type == AuthManagerError:
+ ErrorDialog(
+ _('Error Adding Account'),
+ _('Authentication failed'),
+ parent=self.pref_dialog,
+ details=failure.getErrorMessage(),
+ ).run()
+ else:
+ ErrorDialog(
+ _('Error Adding Account'),
+ _('An error occurred while adding account'),
+ parent=self.pref_dialog,
+ details=failure.getErrorMessage(),
+ ).run()
+
+ if response_id == Gtk.ResponseType.OK:
+ client.core.create_account(username, password, authlevel).addCallback(
+ add_ok
+ ).addErrback(add_fail)
+
+ dialog.run().addCallback(dialog_finished)
+
+ def on_accounts_edit_clicked(self, widget):
+ (model, itr) = self.accounts_listview.get_selection().get_selected()
+ if not itr:
+ return
+
+ dialog = AccountDialog(
+ model[itr][ACCOUNTS_USERNAME],
+ model[itr][ACCOUNTS_PASSWORD],
+ model[itr][ACCOUNTS_LEVEL],
+ levels_mapping=client.auth_levels_mapping,
+ parent=self.pref_dialog,
+ )
+
+ def dialog_finished(response_id):
+ def update_ok(rc):
+ model.set_value(itr, ACCOUNTS_PASSWORD, dialog.get_username())
+ model.set_value(itr, ACCOUNTS_LEVEL, dialog.get_authlevel())
+
+ def update_fail(failure):
+ ErrorDialog(
+ _('Error Updating Account'),
+ _('An error occurred while updating account'),
+ parent=self.pref_dialog,
+ details=failure.getErrorMessage(),
+ ).run()
+
+ if response_id == Gtk.ResponseType.OK:
+ client.core.update_account(
+ dialog.get_username(), dialog.get_password(), dialog.get_authlevel()
+ ).addCallback(update_ok).addErrback(update_fail)
+
+ dialog.run().addCallback(dialog_finished)
+
+ def on_accounts_delete_clicked(self, widget):
+ (model, itr) = self.accounts_listview.get_selection().get_selected()
+ if not itr:
+ return
+
+ username = model[itr][0]
+ header = _('Remove Account')
+ text = _(
+ 'Are you sure you want to remove the account with the '
+ 'username "%(username)s"?' % {'username': username}
+ )
+ dialog = YesNoDialog(header, text, parent=self.pref_dialog)
+
+ def dialog_finished(response_id):
+ def remove_ok(rc):
+ model.remove(itr)
+
+ def remove_fail(failure):
+ if failure.type == AuthManagerError:
+ ErrorDialog(
+ _('Error Removing Account'),
+ _('Auhentication failed'),
+ parent=self.pref_dialog,
+ details=failure.getErrorMessage(),
+ ).run()
+ else:
+ ErrorDialog(
+ _('Error Removing Account'),
+ _('An error occurred while removing account'),
+ parent=self.pref_dialog,
+ details=failure.getErrorMessage(),
+ ).run()
+
+ if response_id == Gtk.ResponseType.YES:
+ client.core.remove_account(username).addCallback(remove_ok).addErrback(
+ remove_fail
+ )
+
+ dialog.run().addCallback(dialog_finished)
+
+ def on_piecesbar_toggle_toggled(self, widget):
+ self.gtkui_config['show_piecesbar'] = widget.get_active()
+ colors_widget = self.builder.get_object('piecebar_colors_expander')
+ colors_widget.set_visible(widget.get_active())
+
+ def on_checkbutton_language_toggled(self, widget):
+ self.language_combo.set_visible(not self.language_checkbox.get_active())
+
+ def on_completed_color_set(self, widget):
+ self.__set_color('completed')
+
+ def on_revert_color_completed_clicked(self, widget):
+ self.__revert_color('completed')
+
+ def on_downloading_color_set(self, widget):
+ self.__set_color('downloading')
+
+ def on_revert_color_downloading_clicked(self, widget):
+ self.__revert_color('downloading')
+
+ def on_waiting_color_set(self, widget):
+ self.__set_color('waiting')
+
+ def on_revert_color_waiting_clicked(self, widget):
+ self.__revert_color('waiting')
+
+ def on_missing_color_set(self, widget):
+ self.__set_color('missing')
+
+ def on_revert_color_missing_clicked(self, widget):
+ self.__revert_color('missing')
+
+ def __set_color(self, state, from_config=False):
+ if from_config:
+ color = Color(*self.gtkui_config['pieces_color_%s' % state])
+ log.debug(
+ 'Setting %r color state from config to %s',
+ state,
+ (color.red, color.green, color.blue),
+ )
+ self.builder.get_object('%s_color' % state).set_color(color)
+ else:
+ color = self.builder.get_object('%s_color' % state).get_color()
+ log.debug(
+ 'Setting %r color state to %s',
+ state,
+ (color.red, color.green, color.blue),
+ )
+ self.gtkui_config['pieces_color_%s' % state] = [
+ color.red,
+ color.green,
+ color.blue,
+ ]
+ self.gtkui_config.save()
+ self.gtkui_config.apply_set_functions('pieces_colors')
+
+ self.builder.get_object('revert_color_%s' % state).set_sensitive(
+ [color.red, color.green, color.blue] != self.COLOR_DEFAULTS[state]
+ )
+
+ def __revert_color(self, state, from_config=False):
+ log.debug('Reverting %r color state', state)
+ self.builder.get_object('%s_color' % state).set_color(
+ Color(*self.COLOR_DEFAULTS[state])
+ )
+ self.builder.get_object('revert_color_%s' % state).set_sensitive(False)
+ self.gtkui_config.apply_set_functions('pieces_colors')
diff --git a/deluge/ui/gtk3/queuedtorrents.py b/deluge/ui/gtk3/queuedtorrents.py
new file mode 100644
index 0000000..0f08c24
--- /dev/null
+++ b/deluge/ui/gtk3/queuedtorrents.py
@@ -0,0 +1,168 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os.path
+
+from gi.repository.GLib import timeout_add
+from gi.repository.Gtk import Builder, CellRendererText, ListStore, TreeViewColumn
+
+import deluge.common
+import deluge.component as component
+from deluge.configmanager import ConfigManager
+
+from .common import get_logo
+from .ipcinterface import process_args
+
+log = logging.getLogger(__name__)
+
+
+class QueuedTorrents(component.Component):
+ def __init__(self):
+ component.Component.__init__(
+ self, 'QueuedTorrents', depend=['StatusBar', 'AddTorrentDialog']
+ )
+ self.queue = []
+ self.status_item = None
+
+ self.config = ConfigManager('gtk3ui.conf')
+ self.builder = Builder()
+ self.builder.add_from_file(
+ deluge.common.resource_filename(
+ __package__, os.path.join('glade', 'queuedtorrents.ui')
+ )
+ )
+ self.builder.get_object('chk_autoadd').set_active(self.config['autoadd_queued'])
+ self.dialog = self.builder.get_object('queued_torrents_dialog')
+ self.dialog.set_icon(get_logo(32))
+
+ self.builder.connect_signals(self)
+
+ self.treeview = self.builder.get_object('treeview')
+ self.treeview.append_column(
+ TreeViewColumn(_('Torrent'), CellRendererText(), text=0)
+ )
+
+ self.liststore = ListStore(str, str)
+ self.treeview.set_model(self.liststore)
+ self.treeview.set_tooltip_column(1)
+
+ def run(self):
+ self.dialog.set_transient_for(component.get('MainWindow').window)
+ self.dialog.show()
+
+ def start(self):
+ if len(self.queue) == 0:
+ return
+
+ # Make sure status bar info is showing
+ self.update_status_bar()
+
+ # We only want the add button sensitive if we're connected to a host
+ self.builder.get_object('button_add').set_sensitive(True)
+
+ if self.config['autoadd_queued'] or self.config['standalone']:
+ self.on_button_add_clicked(None)
+ else:
+ self.run()
+
+ def stop(self):
+ # We only want the add button sensitive if we're connected to a host
+ self.builder.get_object('button_add').set_sensitive(False)
+ self.update_status_bar()
+
+ def add_to_queue(self, torrents):
+ """Adds the list of torrents to the queue"""
+ # Add to the queue while removing duplicates
+ self.queue = list(set(self.queue + torrents))
+
+ # Update the liststore
+ self.liststore.clear()
+ for torrent in self.queue:
+ if deluge.common.is_magnet(torrent):
+ magnet = deluge.common.get_magnet_info(torrent)
+ self.liststore.append([magnet['name'], torrent])
+ else:
+ self.liststore.append([os.path.split(torrent)[1], torrent])
+
+ # Update the status bar
+ self.update_status_bar()
+
+ def update_status_bar(self):
+ """Attempts to update status bar"""
+ # If there are no queued torrents.. remove statusbar widgets and return
+ if len(self.queue) == 0:
+ if self.status_item is not None:
+ component.get('StatusBar').remove_item(self.status_item)
+ self.status_item = None
+ return False
+
+ try:
+ component.get('StatusBar')
+ except Exception:
+ # The statusbar hasn't been loaded yet, so we'll add a timer to
+ # update it later.
+ timeout_add(100, self.update_status_bar)
+ return False
+
+ # Set the label text for statusbar
+ if len(self.queue) > 1:
+ label = str(len(self.queue)) + _(' Torrents Queued')
+ else:
+ label = str(len(self.queue)) + _(' Torrent Queued')
+
+ # Add the statusbar items if needed, or just modify the label if they
+ # have already been added.
+ if self.status_item is None:
+ self.status_item = component.get('StatusBar').add_item(
+ icon='view-sort-descending',
+ text=label,
+ callback=self.on_statusbar_click,
+ )
+ else:
+ self.status_item.set_text(label)
+
+ # We return False so the timer stops
+ return False
+
+ def on_statusbar_click(self, widget, event):
+ log.debug('on_statusbar_click')
+ self.run()
+
+ def on_button_remove_clicked(self, widget):
+ selected = self.treeview.get_selection().get_selected()[1]
+ if selected is not None:
+ path = self.liststore.get_value(selected, 1)
+ self.liststore.remove(selected)
+ self.queue.remove(path)
+ self.update_status_bar()
+
+ def on_button_clear_clicked(self, widget):
+ self.liststore.clear()
+ del self.queue[:]
+ self.update_status_bar()
+
+ def on_button_close_clicked(self, widget):
+ self.dialog.hide()
+
+ def on_button_add_clicked(self, widget):
+ # Add all the torrents in the liststore
+ def add_torrent(model, path, _iter, data):
+ torrent_path = deluge.common.decode_bytes(model.get_value(_iter, 1))
+ process_args([torrent_path])
+
+ self.liststore.foreach(add_torrent, None)
+ del self.queue[:]
+ self.dialog.hide()
+ self.update_status_bar()
+
+ def on_chk_autoadd_toggled(self, widget):
+ self.config['autoadd_queued'] = widget.get_active()
diff --git a/deluge/ui/gtk3/removetorrentdialog.py b/deluge/ui/gtk3/removetorrentdialog.py
new file mode 100644
index 0000000..48806a5
--- /dev/null
+++ b/deluge/ui/gtk3/removetorrentdialog.py
@@ -0,0 +1,93 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007-2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os
+
+from gi.repository import Gtk
+
+import deluge.common
+import deluge.component as component
+from deluge.ui.client import client
+
+log = logging.getLogger(__name__)
+
+
+class RemoveTorrentDialog(object):
+ """
+ This class is used to create and show a Remove Torrent Dialog.
+
+ :param torrent_ids: the torrent_ids to remove
+ :type torrent_ids: list of torrent_ids
+
+ :raises TypeError: if `torrent_id` is not a sequence type
+ :raises ValueError: if `torrent_id` contains no torrent_ids or is None
+
+ """
+
+ def __init__(self, torrent_ids, delete_files=False):
+ if not isinstance(torrent_ids, list) and not isinstance(torrent_ids, tuple):
+ raise TypeError('requires a list of torrent_ids')
+
+ if not torrent_ids:
+ raise ValueError('requires a list of torrent_ids')
+
+ self.__torrent_ids = torrent_ids
+
+ self.builder = Gtk.Builder()
+ self.builder.add_from_file(
+ deluge.common.resource_filename(
+ __package__, os.path.join('glade', 'remove_torrent_dialog.ui')
+ )
+ )
+
+ self.__dialog = self.builder.get_object('remove_torrent_dialog')
+ self.__dialog.set_transient_for(component.get('MainWindow').window)
+
+ self.builder.connect_signals(self)
+ self.builder.get_object('delete_files').set_active(delete_files)
+ label_title = self.builder.get_object('label_title')
+ label_torrents = self.builder.get_object('label_torrents')
+ num_torrents = len(self.__torrent_ids)
+ if num_torrents == 1:
+ label_torrents.set_markup(
+ component.get('TorrentView').get_torrent_status(self.__torrent_ids[0])[
+ 'name'
+ ]
+ )
+ else:
+ label_title.set_markup(_('Remove the selected torrents?'))
+ label_torrents.set_markup(_('Total of %s torrents selected') % num_torrents)
+
+ def on_delete_files_toggled(self, widget):
+ self.builder.get_object('warning_label').set_visible(widget.get_active())
+
+ def __remove_torrents(self, remove_data):
+ # Unselect all to avoid issues with the selection changed event
+ component.get('TorrentView').treeview.get_selection().unselect_all()
+
+ def on_removed_finished(errors):
+ if errors:
+ log.info('Error(s) occured when trying to delete torrent(s).')
+ for t_id, e_msg in errors:
+ log.warning('Error removing torrent %s : %s', t_id, e_msg)
+
+ d = client.core.remove_torrents(self.__torrent_ids, remove_data)
+ d.addCallback(on_removed_finished)
+
+ def run(self):
+ """
+ Shows the dialog and awaits for user input. The user can select to
+ remove the torrent(s) from the session with or without their data.
+ """
+ if self.__dialog.run() == Gtk.ResponseType.OK:
+ self.__remove_torrents(self.builder.get_object('delete_files').get_active())
+ self.__dialog.destroy()
diff --git a/deluge/ui/gtk3/sidebar.py b/deluge/ui/gtk3/sidebar.py
new file mode 100644
index 0000000..1d75191
--- /dev/null
+++ b/deluge/ui/gtk3/sidebar.py
@@ -0,0 +1,73 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
+# Copyright (C) 2008 Martijn Voncken <mvoncken@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+from gi.repository.Gtk import Label, PolicyType, ScrolledWindow
+
+import deluge.component as component
+from deluge.configmanager import ConfigManager
+
+log = logging.getLogger(__name__)
+
+
+class SideBar(component.Component):
+ """
+ manages the sidebar-tabs.
+ purpose : plugins
+ """
+
+ def __init__(self):
+ component.Component.__init__(self, 'SideBar')
+ main_builder = component.get('MainWindow').get_builder()
+ self.notebook = main_builder.get_object('sidebar_notebook')
+ self.config = ConfigManager('gtk3ui.conf')
+
+ # Tabs holds references to the Tab widgets by their name
+ self.tabs = {}
+
+ # Hide if necessary
+ self.visible(self.config['show_sidebar'])
+
+ def visible(self, visible):
+ self.notebook.show() if visible else self.notebook.hide()
+ self.config['show_sidebar'] = visible
+
+ def add_tab(self, widget, tab_name, label):
+ """Adds a tab object to the notebook."""
+ log.debug('add tab: %s', tab_name)
+ self.tabs[tab_name] = widget
+ scrolled = ScrolledWindow()
+ scrolled.set_policy(PolicyType.AUTOMATIC, PolicyType.AUTOMATIC)
+ scrolled.add(widget)
+ self.notebook.insert_page(scrolled, Label(label=label), -1)
+ scrolled.show_all()
+
+ self.after_update()
+
+ def remove_tab(self, tab_name):
+ """Removes a tab by name."""
+ self.notebook.remove_page(self.notebook.page_num(self.tabs[tab_name]))
+ del self.tabs[tab_name]
+
+ self.after_update()
+
+ def after_update(self):
+ # If there are no tabs visible, then do not show the notebook
+ if len(self.tabs) == 0:
+ self.visible(False)
+
+ # If there is 1 tab, hide the tab-headers
+ if len(self.tabs) == 1:
+ self.notebook.set_show_tabs(False)
+ else:
+ self.notebook.set_show_tabs(True)
diff --git a/deluge/ui/gtk3/status_tab.py b/deluge/ui/gtk3/status_tab.py
new file mode 100644
index 0000000..fab6719
--- /dev/null
+++ b/deluge/ui/gtk3/status_tab.py
@@ -0,0 +1,162 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import division, unicode_literals
+
+import logging
+
+import deluge.component as component
+from deluge.common import decode_bytes, fpeer
+from deluge.configmanager import ConfigManager
+
+from .piecesbar import PiecesBar
+from .tab_data_funcs import (
+ fdate_or_never,
+ fpcnt,
+ fratio,
+ fseed_rank_or_dash,
+ fspeed_max,
+ ftime_or_dash,
+ ftotal_sized,
+)
+from .torrentdetails import Tab, TabWidget
+
+log = logging.getLogger(__name__)
+
+
+class StatusTab(Tab):
+ def __init__(self):
+ super(StatusTab, self).__init__('Status', 'status_tab', 'status_tab_label')
+
+ self.config = ConfigManager('gtk3ui.conf')
+
+ self.progressbar = self.main_builder.get_object('progressbar')
+ self.piecesbar = None
+
+ self.add_tab_widget('summary_availability', fratio, ('distributed_copies',))
+ self.add_tab_widget(
+ 'summary_total_downloaded',
+ ftotal_sized,
+ ('all_time_download', 'total_payload_download'),
+ )
+ self.add_tab_widget(
+ 'summary_total_uploaded',
+ ftotal_sized,
+ ('total_uploaded', 'total_payload_upload'),
+ )
+ self.add_tab_widget(
+ 'summary_download_speed',
+ fspeed_max,
+ ('download_payload_rate', 'max_download_speed'),
+ )
+ self.add_tab_widget(
+ 'summary_upload_speed',
+ fspeed_max,
+ ('upload_payload_rate', 'max_upload_speed'),
+ )
+ self.add_tab_widget('summary_seeds', fpeer, ('num_seeds', 'total_seeds'))
+ self.add_tab_widget('summary_peers', fpeer, ('num_peers', 'total_peers'))
+ self.add_tab_widget('summary_eta', ftime_or_dash, ('eta',))
+ self.add_tab_widget('summary_share_ratio', fratio, ('ratio',))
+ self.add_tab_widget('summary_active_time', ftime_or_dash, ('active_time',))
+ self.add_tab_widget('summary_seed_time', ftime_or_dash, ('seeding_time',))
+ self.add_tab_widget(
+ 'summary_seed_rank', fseed_rank_or_dash, ('seed_rank', 'seeding_time')
+ )
+ self.add_tab_widget('progressbar', fpcnt, ('progress', 'state', 'message'))
+ self.add_tab_widget(
+ 'summary_last_seen_complete', fdate_or_never, ('last_seen_complete',)
+ )
+ self.add_tab_widget(
+ 'summary_last_transfer', ftime_or_dash, ('time_since_transfer',)
+ )
+
+ self.config.register_set_function(
+ 'show_piecesbar', self.on_show_piecesbar_config_changed, apply_now=True
+ )
+
+ def update(self):
+ # Get the first selected torrent
+ selected = component.get('TorrentView').get_selected_torrent()
+
+ if not selected:
+ # No torrent is selected in the torrentview
+ self.clear()
+ return
+
+ # Get the torrent status
+ status_keys = self.status_keys
+ if self.config['show_piecesbar']:
+ status_keys.extend(['pieces', 'num_pieces'])
+
+ component.get('SessionProxy').get_torrent_status(
+ selected, status_keys
+ ).addCallback(self._on_get_torrent_status)
+
+ def _on_get_torrent_status(self, status):
+ # Check to see if we got valid data from the core
+ if not status:
+ return
+
+ # Update all the label widgets
+ for widget in self.tab_widgets.values():
+ txt = self.widget_status_as_fstr(widget, status)
+ if decode_bytes(widget[0].get_text()) != txt:
+ widget[0].set_text(txt)
+
+ # Update progress bar seperately as it's a special case (not a label).
+ fraction = status['progress'] / 100
+
+ if self.config['show_piecesbar']:
+ if self.piecesbar.get_fraction() != fraction:
+ self.piecesbar.set_fraction(fraction)
+ if (
+ status['state'] != 'Checking'
+ and self.piecesbar.get_pieces() != status['pieces']
+ ):
+ # Skip pieces assignment if checking torrent.
+ self.piecesbar.set_pieces(status['pieces'], status['num_pieces'])
+ self.piecesbar.update()
+ else:
+ if self.progressbar.get_fraction() != fraction:
+ self.progressbar.set_fraction(fraction)
+
+ def on_show_piecesbar_config_changed(self, key, show):
+ if show:
+ self.show_piecesbar()
+ else:
+ self.hide_piecesbar()
+
+ def show_piecesbar(self):
+ if self.piecesbar is None:
+ self.piecesbar = PiecesBar()
+ self.main_builder.get_object('status_progress_vbox').pack_start(
+ self.piecesbar, False, False, 0
+ )
+ self.tab_widgets['piecesbar'] = TabWidget(
+ self.piecesbar, fpcnt, ('progress', 'state', 'message')
+ )
+ self.piecesbar.show()
+ self.progressbar.hide()
+
+ def hide_piecesbar(self):
+ self.progressbar.show()
+ if self.piecesbar:
+ self.piecesbar.hide()
+ self.tab_widgets.pop('piecesbar', None)
+ self.piecesbar = None
+
+ def clear(self):
+ for widget in self.tab_widgets.values():
+ widget[0].set_text('')
+
+ if self.config['show_piecesbar']:
+ self.piecesbar.clear()
+ else:
+ self.progressbar.set_fraction(0)
diff --git a/deluge/ui/gtk3/statusbar.py b/deluge/ui/gtk3/statusbar.py
new file mode 100644
index 0000000..265e7c8
--- /dev/null
+++ b/deluge/ui/gtk3/statusbar.py
@@ -0,0 +1,578 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007-2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import division, unicode_literals
+
+import logging
+
+from gi.repository import Gtk
+from gi.repository.GLib import timeout_add
+
+import deluge.component as component
+from deluge.common import fsize, fspeed, get_pixmap
+from deluge.configmanager import ConfigManager
+from deluge.ui.client import client
+
+from .common import build_menu_radio_list
+from .dialogs import OtherDialog
+
+log = logging.getLogger(__name__)
+
+
+class StatusBarItem(object):
+ def __init__(
+ self,
+ image=None,
+ stock=None,
+ icon=None,
+ text=None,
+ markup=False,
+ callback=None,
+ tooltip=None,
+ ):
+ self._widgets = []
+ self._ebox = Gtk.EventBox()
+ self._hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, spacing=3)
+ self._image = Gtk.Image()
+ self._label = Gtk.Label()
+ if image or icon or stock:
+ self._hbox.add(self._image)
+ self._hbox.add(self._label)
+ self._ebox.add(self._hbox)
+
+ # Add image from file or stock
+ if image:
+ self.set_image_from_file(image)
+ if stock:
+ self.set_image_from_stock(stock)
+ if icon:
+ self.set_image_from_icon(icon)
+
+ # Add text
+ if markup:
+ self.set_markup(text)
+ else:
+ self.set_text(text)
+
+ if callback is not None:
+ self.set_callback(callback)
+
+ if tooltip:
+ self.set_tooltip(tooltip)
+
+ self.show_all()
+
+ def set_callback(self, callback):
+ self._ebox.connect('button-press-event', callback)
+
+ def show_all(self):
+ self._ebox.show()
+ self._hbox.show()
+ self._image.show()
+
+ def set_image_from_file(self, image):
+ self._image.set_from_file(image)
+
+ def set_image_from_stock(self, stock):
+ self._image.set_from_stock(stock, Gtk.IconSize.MENU)
+
+ def set_image_from_icon(self, icon):
+ self._image.set_from_icon_name(icon, Gtk.IconSize.MENU)
+
+ def set_text(self, text):
+ if not text:
+ self._label.hide()
+ elif self._label.get_text() != text:
+ self._label.set_text(text)
+ self._label.show()
+
+ def set_markup(self, text):
+ if not text:
+ self._label.hide()
+ elif self._label.get_text() != text:
+ self._label.set_markup(text)
+ self._label.show()
+
+ def set_tooltip(self, tip):
+ if self._ebox.get_tooltip_text() != tip:
+ self._ebox.set_tooltip_text(tip)
+
+ def get_widgets(self):
+ return self._widgets
+
+ def get_eventbox(self):
+ return self._ebox
+
+ def get_text(self):
+ return self._label.get_text()
+
+
+class StatusBar(component.Component):
+ def __init__(self):
+ component.Component.__init__(self, 'StatusBar', interval=3)
+ main_builder = component.get('MainWindow').get_builder()
+ self.statusbar = main_builder.get_object('statusbar')
+ self.config = ConfigManager('gtk3ui.conf')
+
+ # Status variables that are updated via callback
+ self.max_connections_global = -1
+ self.num_connections = 0
+ self.max_download_speed = -1.0
+ self.download_rate = ''
+ self.max_upload_speed = -1.0
+ self.upload_rate = ''
+ self.dht_nodes = 0
+ self.dht_status = False
+ self.health = False
+ self.download_protocol_rate = 0.0
+ self.upload_protocol_rate = 0.0
+
+ self.config_value_changed_dict = {
+ 'max_connections_global': self._on_max_connections_global,
+ 'max_download_speed': self._on_max_download_speed,
+ 'max_upload_speed': self._on_max_upload_speed,
+ 'dht': self._on_dht,
+ }
+ self.current_warnings = []
+ # Add hbox to the statusbar after removing the initial label widget
+ self.hbox = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, spacing=10)
+ align = Gtk.Alignment()
+ align.set_padding(2, 0, 3, 0)
+ align.add(self.hbox)
+ frame = self.statusbar.get_children()[0]
+ frame.remove(frame.get_children()[0])
+ frame.add(align)
+ self.statusbar.show_all()
+ # Create the not connected item
+ self.not_connected_item = StatusBarItem(
+ icon='network-offline-symbolic',
+ text=_('Not Connected'),
+ callback=self._on_notconnected_item_clicked,
+ )
+ # Show the not connected status bar
+ self.show_not_connected()
+
+ # Hide if necessary
+ self.visible(self.config['show_statusbar'])
+
+ client.register_event_handler(
+ 'ConfigValueChangedEvent', self.on_configvaluechanged_event
+ )
+
+ def start(self):
+ # Add in images and labels
+ self.remove_item(self.not_connected_item)
+
+ self.connections_item = self.add_item(
+ icon='network-transmit-receive-symbolic',
+ callback=self._on_connection_item_clicked,
+ tooltip=_('Connections (Limit)'),
+ pack_start=True,
+ )
+
+ self.download_item = self.add_item(
+ image=get_pixmap('downloading16.png'),
+ callback=self._on_download_item_clicked,
+ tooltip=_('Download Speed (Limit)'),
+ pack_start=True,
+ )
+
+ self.upload_item = self.add_item(
+ image=get_pixmap('seeding16.png'),
+ callback=self._on_upload_item_clicked,
+ tooltip=_('Upload Speed (Limit)'),
+ pack_start=True,
+ )
+
+ self.traffic_item = self.add_item(
+ image=get_pixmap('traffic16.png'),
+ callback=self._on_traffic_item_clicked,
+ tooltip=_('Protocol Traffic (Down:Up)'),
+ pack_start=True,
+ )
+
+ self.dht_item = StatusBarItem(
+ image=get_pixmap('dht16.png'), tooltip=_('DHT Nodes')
+ )
+
+ self.diskspace_item = self.add_item(
+ icon='drive-harddisk-symbolic',
+ callback=self._on_diskspace_item_clicked,
+ tooltip=_('Free Disk Space'),
+ pack_start=True,
+ )
+
+ self.external_ip_item = self.add_item(
+ tooltip=_('External IP Address'),
+ text=_('<b>IP</b> <small>%s</small>') % _('n/a'),
+ markup=True,
+ pack_start=True,
+ )
+
+ self.health_item = self.add_item(
+ icon='network-error-symbolic',
+ text=_('<b><small>Port Issue</small></b>'),
+ markup=True,
+ tooltip=_('No incoming connections, check port forwarding'),
+ callback=self._on_health_icon_clicked,
+ )
+
+ self.health = False
+
+ def update_config_values(configs):
+ self._on_max_connections_global(configs['max_connections_global'])
+ self._on_max_download_speed(configs['max_download_speed'])
+ self._on_max_upload_speed(configs['max_upload_speed'])
+ self._on_dht(configs['dht'])
+
+ # Get some config values
+ client.core.get_config_values(
+ ['max_connections_global', 'max_download_speed', 'max_upload_speed', 'dht']
+ ).addCallback(update_config_values)
+
+ def stop(self):
+ # When stopped, we just show the not connected thingy
+ try:
+ self.remove_item(self.connections_item)
+ self.remove_item(self.dht_item)
+ self.remove_item(self.download_item)
+ self.remove_item(self.upload_item)
+ self.remove_item(self.not_connected_item)
+ self.remove_item(self.health_item)
+ self.remove_item(self.traffic_item)
+ self.remove_item(self.diskspace_item)
+ self.remove_item(self.external_ip_item)
+ except Exception as ex:
+ log.debug('Unable to remove StatusBar item: %s', ex)
+ self.show_not_connected()
+
+ def visible(self, visible):
+ if visible:
+ self.statusbar.show()
+ else:
+ self.statusbar.hide()
+
+ self.config['show_statusbar'] = visible
+
+ def show_not_connected(self):
+ self.hbox.pack_start(self.not_connected_item.get_eventbox(), False, False, 0)
+
+ def add_item(
+ self,
+ image=None,
+ stock=None,
+ icon=None,
+ text=None,
+ markup=False,
+ callback=None,
+ tooltip=None,
+ pack_start=False,
+ ):
+ """Adds an item to the status bar"""
+ # The return tuple.. we return whatever widgets we add
+ item = StatusBarItem(image, stock, icon, text, markup, callback, tooltip)
+ if pack_start:
+ self.hbox.pack_start(item.get_eventbox(), False, False, 0)
+ else:
+ self.hbox.pack_end(item.get_eventbox(), False, False, 0)
+ return item
+
+ def remove_item(self, item):
+ """Removes an item from the statusbar"""
+ if item.get_eventbox() in self.hbox.get_children():
+ try:
+ self.hbox.remove(item.get_eventbox())
+ except Exception as ex:
+ log.debug('Unable to remove widget: %s', ex)
+
+ def add_timeout_item(
+ self, seconds=3, image=None, stock=None, icon=None, text=None, callback=None
+ ):
+ """Adds an item to the StatusBar for seconds"""
+ item = self.add_item(image, stock, icon, text, callback)
+ # Start a timer to remove this item in seconds
+ timeout_add(seconds * 1000, self.remove_item, item)
+
+ def display_warning(self, text, callback=None):
+ """Displays a warning to the user in the status bar"""
+ if text not in self.current_warnings:
+ item = self.add_item(
+ icon='dialog-warning-symbolic', text=text, callback=callback
+ )
+ self.current_warnings.append(text)
+ timeout_add(3000, self.remove_warning, item)
+
+ def remove_warning(self, item):
+ self.current_warnings.remove(item.get_text())
+ self.remove_item(item)
+
+ def clear_statusbar(self):
+ def remove(child):
+ self.hbox.remove(child)
+
+ self.hbox.foreach(remove)
+
+ def send_status_request(self):
+ # Sends an async request for data from the core
+ keys = [
+ 'num_peers',
+ 'upload_rate',
+ 'download_rate',
+ 'payload_upload_rate',
+ 'payload_download_rate',
+ ]
+
+ if self.dht_status:
+ keys.append('dht_nodes')
+
+ if not self.health:
+ keys.append('has_incoming_connections')
+
+ client.core.get_session_status(keys).addCallback(self._on_get_session_status)
+ client.core.get_free_space().addCallback(self._on_get_free_space)
+ client.core.get_external_ip().addCallback(self._on_get_external_ip)
+
+ def on_configvaluechanged_event(self, key, value):
+ """
+ This is called when we receive a ConfigValueChangedEvent from
+ the core.
+ """
+ if key in self.config_value_changed_dict:
+ self.config_value_changed_dict[key](value)
+
+ def _on_max_connections_global(self, max_connections):
+ self.max_connections_global = max_connections
+ self.update_connections_label()
+
+ def _on_dht(self, value):
+ self.dht_status = value
+ if value:
+ self.hbox.pack_start(self.dht_item.get_eventbox(), False, False, 0)
+ self.send_status_request()
+ else:
+ self.remove_item(self.dht_item)
+
+ def _on_get_session_status(self, status):
+ self.download_rate = fspeed(
+ status['payload_download_rate'], precision=0, shortform=True
+ )
+ self.upload_rate = fspeed(
+ status['payload_upload_rate'], precision=0, shortform=True
+ )
+ self.download_protocol_rate = (
+ status['download_rate'] - status['payload_download_rate']
+ ) // 1024
+ self.upload_protocol_rate = (
+ status['upload_rate'] - status['payload_upload_rate']
+ ) // 1024
+ self.num_connections = status['num_peers']
+ self.update_download_label()
+ self.update_upload_label()
+ self.update_traffic_label()
+ self.update_connections_label()
+
+ if 'dht_nodes' in status:
+ self.dht_nodes = status['dht_nodes']
+ self.update_dht_label()
+
+ if 'has_incoming_connections' in status:
+ self.health = status['has_incoming_connections']
+ if self.health:
+ self.remove_item(self.health_item)
+
+ def _on_get_free_space(self, space):
+ if space >= 0:
+ self.diskspace_item.set_markup(
+ '<small>%s</small>' % fsize(space, shortform=True)
+ )
+ else:
+ self.diskspace_item.set_markup(
+ '<span foreground="red">' + _('Error') + '</span>'
+ )
+
+ def _on_max_download_speed(self, max_download_speed):
+ self.max_download_speed = max_download_speed
+ self.update_download_label()
+
+ def _on_max_upload_speed(self, max_upload_speed):
+ self.max_upload_speed = max_upload_speed
+ self.update_upload_label()
+
+ def _on_get_external_ip(self, external_ip):
+ ip = external_ip if external_ip else _('n/a')
+ self.external_ip_item.set_markup(_('<b>IP</b> <small>%s</small>') % ip)
+
+ def update_connections_label(self):
+ # Set the max connections label
+ if self.max_connections_global < 0:
+ label_string = '%s' % self.num_connections
+ else:
+ label_string = '%s <small>(%s)</small>' % (
+ self.num_connections,
+ self.max_connections_global,
+ )
+
+ self.connections_item.set_markup(label_string)
+
+ if self.num_connections:
+ self.connections_item.set_image_from_icon(
+ 'network-transmit-receive-symbolic'
+ )
+ else:
+ self.connections_item.set_image_from_icon('network-idle-symbolic')
+
+ def update_dht_label(self):
+ # Set the max connections label
+ self.dht_item.set_markup('<small>%s</small>' % (self.dht_nodes))
+
+ def update_download_label(self):
+ # Set the download speed label
+ if self.max_download_speed <= 0:
+ label_string = self.download_rate
+ else:
+ label_string = '%s <small>(%i %s)</small>' % (
+ self.download_rate,
+ self.max_download_speed,
+ _('K/s'),
+ )
+
+ self.download_item.set_markup(label_string)
+
+ def update_upload_label(self):
+ # Set the upload speed label
+ if self.max_upload_speed <= 0:
+ label_string = self.upload_rate
+ else:
+ label_string = '%s <small>(%i %s)</small>' % (
+ self.upload_rate,
+ self.max_upload_speed,
+ _('K/s'),
+ )
+
+ self.upload_item.set_markup(label_string)
+
+ def update_traffic_label(self):
+ label_string = '<small>%i:%i %s</small>' % (
+ self.download_protocol_rate,
+ self.upload_protocol_rate,
+ _('K/s'),
+ )
+ self.traffic_item.set_markup(label_string)
+
+ def update(self):
+ self.send_status_request()
+
+ def set_limit_value(self, widget, core_key):
+ log.debug('_on_set_unlimit_other %s', core_key)
+ other_dialog_info = {
+ 'max_download_speed': (
+ _('Download Speed Limit'),
+ _('Set the maximum download speed'),
+ _('K/s'),
+ 'downloading.svg',
+ self.max_download_speed,
+ ),
+ 'max_upload_speed': (
+ _('Upload Speed Limit'),
+ _('Set the maximum upload speed'),
+ _('K/s'),
+ 'seeding.svg',
+ self.max_upload_speed,
+ ),
+ 'max_connections_global': (
+ _('Incoming Connections'),
+ _('Set the maximum incoming connections'),
+ '',
+ 'network-transmit-receive-symbolic',
+ self.max_connections_global,
+ ),
+ }
+
+ def set_value(value):
+ log.debug('value: %s', value)
+ if value is None:
+ return
+ elif value == 0:
+ value = -1
+ # Set the config in the core
+ if value != getattr(self, core_key):
+ client.core.set_config({core_key: value})
+
+ if widget.get_name() == 'unlimited':
+ set_value(-1)
+ elif widget.get_name() == 'other':
+
+ def dialog_finished(response_id):
+ if response_id == Gtk.ResponseType.OK:
+ set_value(dialog.get_value())
+
+ dialog = OtherDialog(*other_dialog_info[core_key])
+ dialog.run().addCallback(set_value)
+ else:
+ value = widget.get_children()[0].get_text().split(' ')[0]
+ set_value(value)
+
+ def _on_download_item_clicked(self, widget, event):
+ self.menu = build_menu_radio_list(
+ self.config['tray_download_speed_list'],
+ self._on_set_download_speed,
+ self.max_download_speed,
+ _('K/s'),
+ show_notset=True,
+ show_other=True,
+ )
+ self.menu.show_all()
+ self.menu.popup(None, None, None, None, event.button, event.time)
+
+ def _on_set_download_speed(self, widget):
+ log.debug('_on_set_download_speed')
+ self.set_limit_value(widget, 'max_download_speed')
+
+ def _on_upload_item_clicked(self, widget, event):
+ self.menu = build_menu_radio_list(
+ self.config['tray_upload_speed_list'],
+ self._on_set_upload_speed,
+ self.max_upload_speed,
+ _('K/s'),
+ show_notset=True,
+ show_other=True,
+ )
+ self.menu.show_all()
+ self.menu.popup(None, None, None, None, event.button, event.time)
+
+ def _on_set_upload_speed(self, widget):
+ log.debug('_on_set_upload_speed')
+ self.set_limit_value(widget, 'max_upload_speed')
+
+ def _on_connection_item_clicked(self, widget, event):
+ self.menu = build_menu_radio_list(
+ self.config['connection_limit_list'],
+ self._on_set_connection_limit,
+ self.max_connections_global,
+ show_notset=True,
+ show_other=True,
+ )
+ self.menu.show_all()
+ self.menu.popup(None, None, None, None, event.button, event.time)
+
+ def _on_set_connection_limit(self, widget):
+ log.debug('_on_set_connection_limit')
+ self.set_limit_value(widget, 'max_connections_global')
+
+ def _on_health_icon_clicked(self, widget, event):
+ component.get('Preferences').show('network')
+
+ def _on_notconnected_item_clicked(self, widget, event):
+ component.get('ConnectionManager').show()
+
+ def _on_traffic_item_clicked(self, widget, event):
+ component.get('Preferences').show('network')
+
+ def _on_diskspace_item_clicked(self, widget, event):
+ component.get('Preferences').show('downloads')
diff --git a/deluge/ui/gtk3/systemtray.py b/deluge/ui/gtk3/systemtray.py
new file mode 100644
index 0000000..f851f32
--- /dev/null
+++ b/deluge/ui/gtk3/systemtray.py
@@ -0,0 +1,445 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os
+
+from gi import require_version
+from gi.repository.Gtk import Builder, RadioMenuItem, StatusIcon
+
+import deluge.component as component
+from deluge.common import (
+ fspeed,
+ get_pixmap,
+ osx_check,
+ resource_filename,
+ windows_check,
+)
+from deluge.configmanager import ConfigManager
+from deluge.ui.client import client
+
+from .common import build_menu_radio_list, get_logo
+from .dialogs import OtherDialog
+
+try:
+ require_version('AppIndicator3', '0.1')
+ from gi.repository import AppIndicator3
+except (ValueError, ImportError):
+ AppIndicator3 = None
+
+log = logging.getLogger(__name__)
+
+
+class SystemTray(component.Component):
+ def __init__(self):
+ component.Component.__init__(self, 'SystemTray', interval=4)
+ self.mainwindow = component.get('MainWindow')
+ self.config = ConfigManager('gtk3ui.conf')
+ # List of widgets that need to be hidden when not connected to a host
+ self.hide_widget_list = [
+ 'menuitem_add_torrent',
+ 'menuitem_pause_session',
+ 'menuitem_resume_session',
+ 'menuitem_download_limit',
+ 'menuitem_upload_limit',
+ 'menuitem_quitdaemon',
+ 'separatormenuitem1',
+ 'separatormenuitem2',
+ 'separatormenuitem3',
+ 'separatormenuitem4',
+ ]
+ self.config.register_set_function(
+ 'enable_system_tray', self.on_enable_system_tray_set
+ )
+ # bit of a hack to prevent function from doing something on startup
+ self.__enabled_set_once = False
+ self.config.register_set_function(
+ 'enable_appindicator', self.on_enable_appindicator_set
+ )
+
+ self.max_download_speed = -1.0
+ self.download_rate = 0.0
+ self.max_upload_speed = -1.0
+ self.upload_rate = 0.0
+
+ self.config_value_changed_dict = {
+ 'max_download_speed': self._on_max_download_speed,
+ 'max_upload_speed': self._on_max_upload_speed,
+ }
+
+ def enable(self):
+ """Enables the system tray icon."""
+ self.builder = Builder()
+ self.builder.add_from_file(
+ resource_filename(__package__, os.path.join('glade', 'tray_menu.ui'))
+ )
+
+ self.builder.connect_signals(self)
+
+ self.tray_menu = self.builder.get_object('tray_menu')
+
+ if AppIndicator3 and self.config['enable_appindicator']:
+ log.debug('Enabling the Application Indicator...')
+ self.indicator = AppIndicator3.Indicator.new(
+ 'deluge',
+ 'deluge-panel',
+ AppIndicator3.IndicatorCategory.APPLICATION_STATUS,
+ )
+ self.indicator.set_property('title', _('Deluge'))
+
+ # Pass the menu to the Application Indicator
+ self.indicator.set_menu(self.tray_menu)
+
+ # Make sure the status of the Show Window MenuItem is correct
+ self._sig_win_hide = self.mainwindow.window.connect(
+ 'hide', self._on_window_hide
+ )
+ self._sig_win_show = self.mainwindow.window.connect(
+ 'show', self._on_window_show
+ )
+ if self.mainwindow.visible():
+ self.builder.get_object('menuitem_show_deluge').set_active(True)
+ else:
+ self.builder.get_object('menuitem_show_deluge').set_active(False)
+
+ # Show the Application Indicator
+ self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
+
+ else:
+ log.debug('Enabling the system tray icon..')
+ if windows_check() or osx_check():
+ self.tray = StatusIcon.new_from_pixbuf(get_logo(32))
+ else:
+ self.tray = StatusIcon.new_from_icon_name('deluge-panel')
+
+ self.tray.connect('activate', self.on_tray_clicked)
+ self.tray.connect('popup-menu', self.on_tray_popup)
+
+ self.builder.get_object('download-limit-image').set_from_file(
+ get_pixmap('downloading16.png')
+ )
+ self.builder.get_object('upload-limit-image').set_from_file(
+ get_pixmap('seeding16.png')
+ )
+
+ client.register_event_handler(
+ 'ConfigValueChangedEvent', self.config_value_changed
+ )
+ if client.connected():
+ # We're connected so we need to get some values from the core
+ self.__start()
+ else:
+ # Hide menu widgets because we're not connected to a host.
+ for widget in self.hide_widget_list:
+ self.builder.get_object(widget).hide()
+
+ def __start(self):
+ if self.config['enable_system_tray']:
+
+ if self.config['standalone']:
+ try:
+ self.hide_widget_list.remove('menuitem_quitdaemon')
+ self.hide_widget_list.remove('separatormenuitem4')
+ except ValueError:
+ pass
+ self.builder.get_object('menuitem_quitdaemon').hide()
+ self.builder.get_object('separatormenuitem4').hide()
+
+ # Show widgets in the hide list because we've connected to a host
+ for widget in self.hide_widget_list:
+ self.builder.get_object(widget).show()
+
+ # Build the bandwidth speed limit menus
+ self.build_tray_bwsetsubmenu()
+
+ # Get some config values
+ def update_config_values(configs):
+ self._on_max_download_speed(configs['max_download_speed'])
+ self._on_max_upload_speed(configs['max_upload_speed'])
+
+ client.core.get_config_values(
+ ['max_download_speed', 'max_upload_speed']
+ ).addCallback(update_config_values)
+
+ def start(self):
+ self.__start()
+
+ def stop(self):
+ if self.config['enable_system_tray'] and not self.config['enable_appindicator']:
+ try:
+ # Hide widgets in hide list because we're not connected to a host
+ for widget in self.hide_widget_list:
+ self.builder.get_object(widget).hide()
+ except Exception as ex:
+ log.debug('Unable to hide system tray menu widgets: %s', ex)
+
+ self.tray.set_tooltip_text(_('Deluge') + '\n' + _('Not Connected...'))
+
+ def shutdown(self):
+ if self.config['enable_system_tray']:
+ if AppIndicator3 and self.config['enable_appindicator']:
+ self.indicator.set_status(AppIndicator3.IndicatorStatus.PASSIVE)
+ else:
+ self.tray.set_visible(False)
+
+ def send_status_request(self):
+ client.core.get_session_status(
+ ['payload_upload_rate', 'payload_download_rate']
+ ).addCallback(self._on_get_session_status)
+
+ def config_value_changed(self, key, value):
+ """This is called when we received a config_value_changed signal from
+ the core."""
+ if key in self.config_value_changed_dict:
+ self.config_value_changed_dict[key](value)
+
+ def _on_max_download_speed(self, max_download_speed):
+ if self.max_download_speed != max_download_speed:
+ self.max_download_speed = max_download_speed
+ self.build_tray_bwsetsubmenu()
+
+ def _on_max_upload_speed(self, max_upload_speed):
+ if self.max_upload_speed != max_upload_speed:
+ self.max_upload_speed = max_upload_speed
+ self.build_tray_bwsetsubmenu()
+
+ def _on_get_session_status(self, status):
+ self.download_rate = fspeed(status['payload_download_rate'], shortform=True)
+ self.upload_rate = fspeed(status['payload_upload_rate'], shortform=True)
+
+ def update(self):
+ if not self.config['enable_system_tray']:
+ return
+
+ # Tool tip text not available for appindicator
+ if AppIndicator3 and self.config['enable_appindicator']:
+ if self.mainwindow.visible():
+ self.builder.get_object('menuitem_show_deluge').set_active(True)
+ else:
+ self.builder.get_object('menuitem_show_deluge').set_active(False)
+ return
+
+ # Set the tool tip text
+ max_download_speed = self.max_download_speed
+ max_upload_speed = self.max_upload_speed
+
+ if max_download_speed == -1:
+ max_download_speed = _('Unlimited')
+ else:
+ max_download_speed = '%s %s' % (max_download_speed, _('K/s'))
+ if max_upload_speed == -1:
+ max_upload_speed = _('Unlimited')
+ else:
+ max_upload_speed = '%s %s' % (max_upload_speed, _('K/s'))
+
+ msg = '%s\n%s: %s (%s)\n%s: %s (%s)' % (
+ _('Deluge'),
+ _('Down'),
+ self.download_rate,
+ max_download_speed,
+ _('Up'),
+ self.upload_rate,
+ max_upload_speed,
+ )
+
+ # Set the tooltip
+ self.tray.set_tooltip_text(msg)
+
+ self.send_status_request()
+
+ def build_tray_bwsetsubmenu(self):
+ # Create the Download speed list sub-menu
+ submenu_bwdownset = build_menu_radio_list(
+ self.config['tray_download_speed_list'],
+ self.on_tray_setbwdown,
+ self.max_download_speed,
+ _('K/s'),
+ show_notset=True,
+ show_other=True,
+ )
+
+ # Create the Upload speed list sub-menu
+ submenu_bwupset = build_menu_radio_list(
+ self.config['tray_upload_speed_list'],
+ self.on_tray_setbwup,
+ self.max_upload_speed,
+ _('K/s'),
+ show_notset=True,
+ show_other=True,
+ )
+ # Add the sub-menus to the tray menu
+ self.builder.get_object('menuitem_download_limit').set_submenu(
+ submenu_bwdownset
+ )
+ self.builder.get_object('menuitem_upload_limit').set_submenu(submenu_bwupset)
+
+ # Show the sub-menus for all to see
+ submenu_bwdownset.show_all()
+ submenu_bwupset.show_all()
+
+ def disable(self, invert_app_ind_conf=False):
+ """Disables the system tray icon or Appindicator."""
+ try:
+ if invert_app_ind_conf:
+ app_ind_conf = not self.config['enable_appindicator']
+ else:
+ app_ind_conf = self.config['enable_appindicator']
+ if AppIndicator3 and app_ind_conf:
+ if hasattr(self, '_sig_win_hide'):
+ self.mainwindow.window.disconnect(self._sig_win_hide)
+ self.mainwindow.window.disconnect(self._sig_win_show)
+ log.debug('Disabling the application indicator..')
+
+ self.indicator.set_status(AppIndicator3.IndicatorStatus.PASSIVE)
+ del self.indicator
+ else:
+ log.debug('Disabling the system tray icon..')
+ self.tray.set_visible(False)
+ del self.tray
+ del self.builder
+ del self.tray_menu
+ except Exception as ex:
+ log.debug('Unable to disable system tray: %s', ex)
+
+ def blink(self, value):
+ try:
+ self.tray.set_blinking(value)
+ except AttributeError:
+ # If self.tray is not defined then ignore. This happens when the
+ # tray icon is not being used.
+ pass
+
+ def on_enable_system_tray_set(self, key, value):
+ """Called whenever the 'enable_system_tray' config key is modified"""
+ if value:
+ self.enable()
+ else:
+ self.disable()
+
+ def on_enable_appindicator_set(self, key, value):
+ """Called whenever the 'enable_appindicator' config key is modified"""
+ if self.__enabled_set_once:
+ self.disable(True)
+ self.enable()
+ self.__enabled_set_once = True
+
+ def on_tray_clicked(self, icon):
+ """Called when the tray icon is left clicked."""
+ self.blink(False)
+
+ if self.mainwindow.active():
+ self.mainwindow.hide()
+ else:
+ self.mainwindow.present()
+
+ def on_tray_popup(self, status_icon, button, activate_time):
+ """Called when the tray icon is right clicked."""
+ self.blink(False)
+
+ if self.mainwindow.visible():
+ self.builder.get_object('menuitem_show_deluge').set_active(True)
+ else:
+ self.builder.get_object('menuitem_show_deluge').set_active(False)
+
+ popup_function = StatusIcon.position_menu
+ if windows_check() or osx_check():
+ popup_function = None
+ button = 0
+ self.tray_menu.popup(
+ None, None, popup_function, status_icon, button, activate_time
+ )
+
+ def on_menuitem_show_deluge_activate(self, menuitem):
+ log.debug('on_menuitem_show_deluge_activate')
+ if menuitem.get_active() and not self.mainwindow.visible():
+ self.mainwindow.present()
+ elif not menuitem.get_active() and self.mainwindow.visible():
+ self.mainwindow.hide()
+
+ def on_menuitem_add_torrent_activate(self, menuitem):
+ log.debug('on_menuitem_add_torrent_activate')
+ component.get('AddTorrentDialog').show()
+
+ def on_menuitem_pause_session_activate(self, menuitem):
+ log.debug('on_menuitem_pause_session_activate')
+ client.core.pause_session()
+
+ def on_menuitem_resume_session_activate(self, menuitem):
+ log.debug('on_menuitem_resume_session_activate')
+ client.core.resume_session()
+
+ def on_menuitem_quit_activate(self, menuitem):
+ log.debug('on_menuitem_quit_activate')
+ self.mainwindow.quit()
+
+ def on_menuitem_quitdaemon_activate(self, menuitem):
+ log.debug('on_menuitem_quitdaemon_activate')
+ self.mainwindow.quit(shutdown=True)
+
+ def on_tray_setbwdown(self, widget, data=None):
+ if isinstance(widget, RadioMenuItem):
+ # ignore previous radiomenuitem value
+ if not widget.get_active():
+ return
+ self.setbwlimit(
+ widget,
+ _('Download Speed Limit'),
+ _('Set the maximum download speed'),
+ 'max_download_speed',
+ 'tray_download_speed_list',
+ self.max_download_speed,
+ 'downloading.svg',
+ )
+
+ def on_tray_setbwup(self, widget, data=None):
+ if isinstance(widget, RadioMenuItem):
+ # ignore previous radiomenuitem value
+ if not widget.get_active():
+ return
+ self.setbwlimit(
+ widget,
+ _('Upload Speed Limit'),
+ _('Set the maximum upload speed'),
+ 'max_upload_speed',
+ 'tray_upload_speed_list',
+ self.max_upload_speed,
+ 'seeding.svg',
+ )
+
+ def _on_window_hide(self, widget, data=None):
+ """_on_window_hide - update the menuitem's status"""
+ log.debug('_on_window_hide')
+ self.builder.get_object('menuitem_show_deluge').set_active(False)
+
+ def _on_window_show(self, widget, data=None):
+ """_on_window_show - update the menuitem's status"""
+ log.debug('_on_window_show')
+ self.builder.get_object('menuitem_show_deluge').set_active(True)
+
+ def setbwlimit(self, widget, header, text, core_key, ui_key, default, image):
+ """Sets the bandwidth limit based on the user selection."""
+
+ def set_value(value):
+ log.debug('setbwlimit: %s', value)
+ if value is None:
+ return
+ elif value == 0:
+ value = -1
+ # Set the config in the core
+ client.core.set_config({core_key: value})
+
+ if widget.get_name() == 'unlimited':
+ set_value(-1)
+ elif widget.get_name() == 'other':
+ dialog = OtherDialog(header, text, _('K/s'), image, default)
+ dialog.run().addCallback(set_value)
+ else:
+ set_value(widget.get_children()[0].get_text().split(' ')[0])
diff --git a/deluge/ui/gtk3/tab_data_funcs.py b/deluge/ui/gtk3/tab_data_funcs.py
new file mode 100644
index 0000000..6fa0ba5
--- /dev/null
+++ b/deluge/ui/gtk3/tab_data_funcs.py
@@ -0,0 +1,96 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+from deluge.common import fdate, fsize, fspeed, ftime
+from deluge.ui.common import TRACKER_STATUS_TRANSLATION
+
+
+def ftotal_sized(first, second):
+ return '%s (%s)' % (fsize(first, shortform=True), fsize(second, shortform=True))
+
+
+def fratio(value):
+ return ('%.3f' % value).rstrip('0').rstrip('.') if value > 0 else '∞'
+
+
+def fpcnt(value, state, message):
+ state_i18n = _(state)
+ if state not in ('Error', 'Seeding') and value < 100:
+ percent = '{:.2f}'.format(value).rstrip('0').rstrip('.')
+ return _('{state} {percent}%').format(state=state_i18n, percent=percent)
+ elif state == 'Error':
+ return _('{state}: {err_msg}').format(state=state_i18n, err_msg=message)
+ else:
+ return state_i18n
+
+
+def fspeed_max(value, max_value=-1):
+ value = fspeed(value, shortform=True)
+ return '%s (%s %s)' % (value, max_value, _('K/s')) if max_value > -1 else value
+
+
+def fdate_or_never(value):
+ """Display value as date, eg 05/05/08 or Never"""
+ return fdate(value, date_only=True) if value > 0 else _('Never')
+
+
+def fdate_or_dash(value):
+ """Display value as date, eg 05/05/08 or dash"""
+ if value > 0.0:
+ return fdate(value)
+ else:
+ return '-'
+
+
+def ftime_or_dash(value):
+ """Display value as time, eg 2h 30m or dash"""
+ if value > 0:
+ return ftime(value)
+ elif value == 0:
+ return '-'
+ else:
+ return '∞'
+
+
+def fseed_rank_or_dash(seed_rank, seeding_time):
+ """Display value if seeding otherwise dash"""
+
+ if seeding_time > 0:
+ if seed_rank >= 1000:
+ return '%i k' % (seed_rank // 1000)
+ else:
+ return str(seed_rank)
+ else:
+ return '-'
+
+
+def fpieces_num_size(num_pieces, piece_size):
+ return '%s (%s)' % (num_pieces, fsize(piece_size, precision=0))
+
+
+def fcount(value):
+ return '%s' % len(value)
+
+
+def ftranslate(text):
+ if text in TRACKER_STATUS_TRANSLATION:
+ text = _(text)
+ elif text:
+ for status in TRACKER_STATUS_TRANSLATION:
+ if status in text:
+ text = text.replace(status, _(status))
+ break
+ return text
+
+
+def fyes_no(value):
+ """Return Yes or No to bool value"""
+ return _('Yes') if value else _('No')
diff --git a/deluge/ui/gtk3/toolbar.py b/deluge/ui/gtk3/toolbar.py
new file mode 100644
index 0000000..7bc029e
--- /dev/null
+++ b/deluge/ui/gtk3/toolbar.py
@@ -0,0 +1,134 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007-2009 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+from gi.repository.Gtk import SeparatorToolItem, ToolButton
+
+import deluge.component as component
+from deluge.configmanager import ConfigManager
+
+log = logging.getLogger(__name__)
+
+
+class ToolBar(component.Component):
+ def __init__(self):
+ component.Component.__init__(self, 'ToolBar')
+ log.debug('ToolBar Init..')
+ mainwindow = component.get('MainWindow')
+ self.main_builder = mainwindow.get_builder()
+ self.toolbar = self.main_builder.get_object('toolbar')
+ self.config = ConfigManager('gtk3ui.conf')
+ # Connect main window Signals #
+ mainwindow.connect_signals(self)
+ self.change_sensitivity = [
+ 'toolbutton_add',
+ 'toolbutton_remove',
+ 'toolbutton_pause',
+ 'toolbutton_resume',
+ 'toolbutton_queue_up',
+ 'toolbutton_queue_down',
+ 'toolbutton_filter',
+ 'find_menuitem',
+ ]
+
+ # Hide if necessary
+ self.visible(self.config['show_toolbar'])
+
+ def start(self):
+ self.main_builder.get_object('toolbutton_connectionmanager').set_visible(
+ not self.config['standalone']
+ )
+
+ for widget in self.change_sensitivity:
+ self.main_builder.get_object(widget).set_sensitive(True)
+
+ def stop(self):
+ for widget in self.change_sensitivity:
+ self.main_builder.get_object(widget).set_sensitive(False)
+
+ def visible(self, visible):
+ if visible:
+ self.toolbar.show()
+ else:
+ self.toolbar.hide()
+
+ self.config['show_toolbar'] = visible
+
+ def add_toolbutton(
+ self, callback, label=None, image=None, stock=None, tooltip=None
+ ):
+ """Adds a toolbutton to the toolbar"""
+ toolbutton = ToolButton()
+ if stock is not None:
+ toolbutton.set_stock_id(stock)
+ if label is not None:
+ toolbutton.set_label(label)
+ if image is not None:
+ toolbutton.set_icon_widget(image)
+ if tooltip is not None:
+ toolbutton.set_tooltip_text(tooltip)
+
+ toolbutton.connect('clicked', callback)
+ self.toolbar.insert(toolbutton, -1)
+ toolbutton.show_all()
+
+ return toolbutton
+
+ def add_separator(self, position=None):
+ """Adds a separator toolitem"""
+ sep = SeparatorToolItem()
+ if position is not None:
+ self.toolbar.insert(sep, position)
+ else:
+ self.toolbar.insert(sep, -1)
+
+ sep.show()
+
+ return sep
+
+ def remove(self, widget):
+ """Removes a widget from the toolbar"""
+ self.toolbar.remove(widget)
+
+ # Callbacks (Uses the menubar's callback) #
+
+ def on_toolbutton_add_clicked(self, data):
+ log.debug('on_toolbutton_add_clicked')
+ component.get('MenuBar').on_menuitem_addtorrent_activate(data)
+
+ def on_toolbutton_remove_clicked(self, data):
+ log.debug('on_toolbutton_remove_clicked')
+ component.get('MenuBar').on_menuitem_remove_activate(data)
+
+ def on_toolbutton_pause_clicked(self, data):
+ log.debug('on_toolbutton_pause_clicked')
+ component.get('MenuBar').on_menuitem_pause_activate(data)
+
+ def on_toolbutton_resume_clicked(self, data):
+ log.debug('on_toolbutton_resume_clicked')
+ component.get('MenuBar').on_menuitem_resume_activate(data)
+
+ def on_toolbutton_preferences_clicked(self, data):
+ log.debug('on_toolbutton_preferences_clicked')
+ component.get('MenuBar').on_menuitem_preferences_activate(data)
+
+ def on_toolbutton_connectionmanager_clicked(self, data):
+ log.debug('on_toolbutton_connectionmanager_clicked')
+ component.get('MenuBar').on_menuitem_connectionmanager_activate(data)
+
+ def on_toolbutton_queue_up_clicked(self, data):
+ log.debug('on_toolbutton_queue_up_clicked')
+ component.get('MenuBar').on_menuitem_queue_up_activate(data)
+
+ def on_toolbutton_queue_down_clicked(self, data):
+ log.debug('on_toolbutton_queue_down_clicked')
+ component.get('MenuBar').on_menuitem_queue_down_activate(data)
diff --git a/deluge/ui/gtk3/torrentdetails.py b/deluge/ui/gtk3/torrentdetails.py
new file mode 100644
index 0000000..29e0193
--- /dev/null
+++ b/deluge/ui/gtk3/torrentdetails.py
@@ -0,0 +1,458 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+
+"""The torrent details component shows info about the selected torrent."""
+from __future__ import unicode_literals
+
+import logging
+from collections import namedtuple
+
+from gi.repository.Gtk import CheckMenuItem, Menu, SeparatorMenuItem
+
+import deluge.component as component
+from deluge.ui.client import client
+
+from .common import load_pickled_state_file, save_pickled_state_file
+
+log = logging.getLogger(__name__)
+
+TabWidget = namedtuple('TabWidget', ('obj', 'func', 'status_keys'))
+
+
+class Tab(object):
+ def __init__(self, name=None, child_widget=None, tab_label=None):
+ self._name = name
+ self.is_visible = True
+ self.position = -1
+ self.weight = -1
+
+ self.main_builder = component.get('MainWindow').get_builder()
+ self._child_widget = (
+ self.main_builder.get_object(child_widget) if child_widget else None
+ )
+ self._tab_label = self.main_builder.get_object(tab_label) if tab_label else None
+
+ self.tab_widgets = {}
+ self.status_keys = []
+
+ def get_name(self):
+ return self._name
+
+ def get_child_widget(self):
+ parent = self._child_widget.get_parent()
+ if parent is not None:
+ parent.remove(self._child_widget)
+
+ return self._child_widget
+
+ def get_tab_label(self):
+ parent = self._tab_label.get_parent()
+ log.debug('parent: %s', parent)
+ if parent is not None:
+ parent.remove(self._tab_label)
+
+ return self._tab_label
+
+ def widget_status_as_fstr(self, widget, status):
+ """Use TabWidget status_key and func to format status string.
+
+ Args:
+ widget (TabWidget): A tuple of widget object, func and status_keys.
+ status (dict): Torrent status dict.
+
+ Returns:
+ str: The formatted status string.
+ """
+ try:
+ if widget.func is None:
+ txt = status[widget.status_keys[0]]
+ else:
+ args = [status[key] for key in widget.status_keys]
+ txt = widget.func(*args)
+ except KeyError as ex:
+ log.warning('Unable to get status value: %s', ex)
+ txt = ''
+ return txt
+
+ def add_tab_widget(self, widget_id, format_func, status_keys):
+ """Create TabWidget item in tab_widgets dictionary.
+
+ Args:
+ widget_id (str): The widget id used to retrieve widget from mainwindow builder.
+ format_func (str): A func name related to widget e.g. string label formatter.
+ status_keys (list): List of status keys to lookup for the widget.
+
+ """
+ widget_obj = self.main_builder.get_object(widget_id)
+ self.status_keys.extend(status_keys)
+ # Store the widget in a tab_widgets dict with name as key for faster lookup.
+ self.tab_widgets[widget_id] = TabWidget(widget_obj, format_func, status_keys)
+
+
+class TorrentDetails(component.Component):
+ def __init__(self):
+ component.Component.__init__(self, 'TorrentDetails', interval=2)
+ main_builder = component.get('MainWindow').get_builder()
+
+ self.notebook = main_builder.get_object('torrent_info')
+
+ # This is the menu item we'll attach the tabs checklist menu to
+ self.menu_tabs = main_builder.get_object('menu_tabs')
+
+ self.notebook.connect('switch-page', self._on_switch_page)
+
+ # Tabs holds references to the Tab objects by their name
+ self.tabs = {}
+
+ # Add the default tabs
+ from .status_tab import StatusTab
+ from .details_tab import DetailsTab
+ from .files_tab import FilesTab
+ from .peers_tab import PeersTab
+ from .options_tab import OptionsTab
+ from .trackers_tab import TrackersTab
+
+ default_tabs = {
+ 'Status': StatusTab,
+ 'Details': DetailsTab,
+ 'Files': FilesTab,
+ 'Peers': PeersTab,
+ 'Options': OptionsTab,
+ 'Trackers': TrackersTab,
+ }
+
+ # tab_name, visible
+ default_order = [
+ ('Status', True),
+ ('Details', True),
+ ('Options', True),
+ ('Files', True),
+ ('Peers', True),
+ ('Trackers', True),
+ ]
+
+ self.translate_tabs = {
+ 'All': _('_All'),
+ 'Status': _('_Status'),
+ 'Details': _('_Details'),
+ 'Files': _('Fi_les'),
+ 'Peers': _('_Peers'),
+ 'Options': _('_Options'),
+ 'Trackers': _('_Trackers'),
+ }
+
+ # Get the state from saved file
+ state = self.load_state()
+
+ if state:
+ for item in state:
+ if not isinstance(item, tuple):
+ log.debug('Old tabs.state, using default..')
+ state = None
+ break
+
+ # The state is a list of tab_names in the order they should appear
+ if state is None:
+ # Set the default order
+ state = default_order
+
+ # We need to rename the tab in the state for backwards compat
+ self.state = [
+ (tab_name.replace('Statistics', 'Status'), visible)
+ for tab_name, visible in state
+ ]
+
+ for tab in default_tabs.values():
+ self.add_tab(tab(), generate_menu=False)
+
+ # Generate the checklist menu
+ self.generate_menu()
+
+ self.config = component.get('MainWindow').config
+ self.visible(self.config['show_tabsbar'])
+
+ def tab_insert_position(self, weight):
+ """Returns the position a tab with a given weight should be inserted in"""
+ # Determine insert position based on weight
+ # weights is a list of visible tab names in weight order
+
+ weights = sorted(
+ (tab.weight, name) for name, tab in self.tabs.items() if tab.is_visible
+ )
+
+ log.debug('weights: %s', weights)
+ log.debug('weight of tab: %s', weight)
+
+ position = -1
+ for w, name in weights:
+ if w >= weight:
+ position = self.tabs[name].position
+ log.debug('Found pos %d', position)
+ break
+ return position
+
+ def add_tab(self, tab, generate_menu=True, visible=None):
+ name = tab.get_name()
+
+ # find position of tab in self.state, this is the tab weight
+ weight = None
+ for w, item in enumerate(self.state):
+ if item[0] == name:
+ weight = w
+ if visible is None:
+ visible = item[1]
+ break
+
+ if weight is None:
+ if visible is None:
+ visible = True
+ weight = len(self.state)
+ self.state.append((name, visible))
+
+ tab.weight = weight
+
+ if visible:
+ tab.is_visible = True
+ # add the tab at position guided by the weight
+ insert_pos = self.tab_insert_position(weight)
+ log.debug('Trying to insert tab at %d', insert_pos)
+ pos = self.notebook.insert_page(
+ tab.get_child_widget(), tab.get_tab_label(), insert_pos
+ )
+ log.debug('Tab inserted at %d', pos)
+ tab.position = pos
+ if not self.notebook.get_property('visible'):
+ # If the notebook isn't visible, show it
+ self.visible(True)
+ else:
+ tab.is_visible = False
+
+ self.tabs[name] = tab
+ if name not in self.translate_tabs:
+ self.translate_tabs[name] = _(name)
+
+ self.regenerate_positions()
+ if generate_menu:
+ self.generate_menu()
+
+ def regenerate_positions(self):
+ """Sync the positions in the tab, with the position stored in the tab object"""
+ for tab in self.tabs:
+ page_num = self.notebook.page_num(self.tabs[tab]._child_widget)
+ if page_num > -1:
+ self.tabs[tab].position = page_num
+
+ def remove_tab(self, tab_name):
+ """Removes a tab by name."""
+ self.notebook.remove_page(self.tabs[tab_name].position)
+ del self.tabs[tab_name]
+ self.regenerate_positions()
+ self.generate_menu()
+
+ # If there are no tabs visible, then do not show the notebook
+ if len(self.tabs) == 0:
+ self.visible(False)
+
+ def hide_all_tabs(self):
+ """Hides all tabs"""
+ log.debug('n_pages: %s', self.notebook.get_n_pages())
+ for n in range(self.notebook.get_n_pages() - 1, -1, -1):
+ self.notebook.remove_page(n)
+
+ for tab in self.tabs:
+ self.tabs[tab].is_visible = False
+ log.debug('n_pages: %s', self.notebook.get_n_pages())
+ self.generate_menu()
+ self.visible(False)
+
+ def show_all_tabs(self):
+ """Shows all tabs"""
+ for tab in self.tabs:
+ if not self.tabs[tab].is_visible:
+ self.show_tab(tab, generate_menu=False)
+ self.generate_menu()
+
+ def hide_tab(self, tab_name):
+ """Hides tab by name"""
+ self.tabs[tab_name].is_visible = False
+ self.notebook.remove_page(self.tabs[tab_name].position)
+ self.regenerate_positions()
+ self.generate_menu()
+
+ show = False
+ for name, tab in self.tabs.items():
+ show = show or tab.is_visible
+
+ self.visible(show)
+
+ def show_tab(self, tab_name, generate_menu=True):
+ log.debug(
+ '%s\n%s\n%s',
+ self.tabs[tab_name].get_child_widget(),
+ self.tabs[tab_name].get_tab_label(),
+ self.tabs[tab_name].position,
+ )
+
+ position = self.tab_insert_position(self.tabs[tab_name].weight)
+
+ log.debug('position: %s', position)
+ self.notebook.insert_page(
+ self.tabs[tab_name].get_child_widget(),
+ self.tabs[tab_name].get_tab_label(),
+ position,
+ )
+ self.tabs[tab_name].is_visible = True
+ self.regenerate_positions()
+ if generate_menu:
+ self.generate_menu()
+ self.visible(True)
+
+ def generate_menu(self):
+ """Generates the checklist menu for all the tabs and attaches it"""
+ menu = Menu()
+ # Create 'All' menuitem and a separator
+ menuitem = CheckMenuItem.new_with_mnemonic(self.translate_tabs['All'])
+ menuitem.set_name('All')
+
+ all_tabs = True
+ for key in self.tabs:
+ if not self.tabs[key].is_visible:
+ all_tabs = False
+ break
+ menuitem.set_active(all_tabs)
+ menuitem.connect('toggled', self._on_menuitem_toggled)
+
+ menu.append(menuitem)
+
+ menuitem = SeparatorMenuItem()
+ menu.append(menuitem)
+
+ # Create a list in order of tabs to create menu
+ menuitem_list = []
+ for tab_name in self.tabs:
+ menuitem_list.append((self.tabs[tab_name].weight, tab_name))
+ menuitem_list.sort()
+
+ for pos, name in menuitem_list:
+ menuitem = CheckMenuItem.new_with_mnemonic(self.translate_tabs[name])
+ menuitem.set_name(name)
+ menuitem.set_active(self.tabs[name].is_visible)
+ menuitem.connect('toggled', self._on_menuitem_toggled)
+ menu.append(menuitem)
+
+ self.menu_tabs.set_submenu(menu)
+ self.menu_tabs.show_all()
+
+ def visible(self, visible):
+ self.notebook.show() if visible else self.notebook.hide()
+ self.config['show_tabsbar'] = visible
+
+ def set_tab_visible(self, tab_name, visible):
+ """Sets the tab to visible"""
+ log.debug('set_tab_visible name: %s visible: %s', tab_name, visible)
+ if visible and not self.tabs[tab_name].is_visible:
+ self.show_tab(tab_name)
+ elif not visible and self.tabs[tab_name].is_visible:
+ self.hide_tab(tab_name)
+
+ def start(self):
+ for tab in self.tabs.values():
+ try:
+ tab.start()
+ except AttributeError:
+ pass
+
+ def stop(self):
+ self.clear()
+ for tab in self.tabs.values():
+ try:
+ tab.stop()
+ except AttributeError:
+ pass
+
+ def shutdown(self):
+ # Save the state of the tabs
+ for tab in self.tabs:
+ try:
+ self.tabs[tab].save_state()
+ except AttributeError:
+ pass
+
+ # Save tabs state
+ self.save_state()
+
+ def update(self, page_num=None):
+ if len(component.get('TorrentView').get_selected_torrents()) == 0:
+ # No torrents selected, so just clear
+ self.clear()
+
+ if self.notebook.get_property('visible'):
+ if page_num is None:
+ page_num = self.notebook.get_current_page()
+ try:
+ # Get the tab name
+ name = None
+ for tab in self.tabs:
+ if (
+ self.tabs[tab].position == page_num
+ and self.tabs[tab].is_visible
+ ):
+ name = tab
+ except IndexError:
+ return
+ # Update the tab that is in view
+ if name:
+ self.tabs[name].update()
+
+ def clear(self):
+ # Get the tab name
+ try:
+ page_num = self.notebook.get_current_page()
+ name = None
+ for tab in self.tabs:
+ if self.tabs[tab].position == page_num and self.tabs[tab].is_visible:
+ name = tab
+ if name:
+ self.tabs[name].clear()
+ except Exception as ex:
+ log.debug('Unable to clear torrentdetails: %s', ex)
+
+ def _on_switch_page(self, notebook, page, page_num):
+ self.update(page_num)
+ client.force_call(False)
+
+ def _on_menuitem_toggled(self, widget):
+ # Get the tab name
+ name = widget.get_name()
+ if name == 'All':
+ if widget.get_active():
+ self.show_all_tabs()
+ else:
+ self.hide_all_tabs()
+ return
+
+ self.set_tab_visible(name, widget.get_active())
+
+ def save_state(self):
+ """We save the state, which is basically the tab_index list"""
+ # Update the visiblity status of all tabs
+ # Leave tabs we dont know anything about it the state as they
+ # might come from a plugin
+ for i, (name, visible) in enumerate(self.state):
+ log.debug('Testing name: %s', name)
+ if name in self.tabs:
+ self.state[i] = (name, self.tabs[name].is_visible)
+ log.debug('Set to %s', self.state[i])
+ state = self.state
+
+ save_pickled_state_file('tabs.state', state)
+
+ def load_state(self):
+ return load_pickled_state_file('tabs.state')
diff --git a/deluge/ui/gtk3/torrentview.py b/deluge/ui/gtk3/torrentview.py
new file mode 100644
index 0000000..fcc6edf
--- /dev/null
+++ b/deluge/ui/gtk3/torrentview.py
@@ -0,0 +1,934 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+"""The torrent view component that lists all torrents in the session."""
+from __future__ import unicode_literals
+
+import logging
+from locale import strcoll
+
+from gi.repository.Gdk import ModifierType, keyval_name
+from gi.repository.GLib import idle_add
+from gi.repository.GObject import TYPE_UINT64
+from gi.repository.Gtk import EntryIconPosition
+from twisted.internet import reactor
+
+import deluge.component as component
+from deluge.common import decode_bytes
+from deluge.ui.client import client
+
+from . import torrentview_data_funcs as funcs
+from .common import cmp
+from .listview import ListView
+from .removetorrentdialog import RemoveTorrentDialog
+
+log = logging.getLogger(__name__)
+
+try:
+ CTRL_ALT_MASK = ModifierType.CONTROL_MASK | ModifierType.MOD1_MASK
+except TypeError:
+ # Sphinx AutoDoc has a mock issue with Gdk masks.
+ pass
+
+
+def str_nocase_sort(model, iter1, iter2, data):
+ """Sort string column data using ISO 14651 in lowercase.
+
+ Uses locale.strcoll which (allegedly) uses ISO 14651. Compares first
+ value with second and returns -1, 0, 1 for where it should be placed.
+
+ """
+ v1 = model[iter1][data]
+ v2 = model[iter2][data]
+ # Catch any values of None from model.
+ v1 = v1.lower() if v1 else ''
+ v2 = v2.lower() if v2 else ''
+ return strcoll(v1, v2)
+
+
+def queue_peer_seed_sort_function(v1, v2):
+ if v1 == v2:
+ return 0
+ if v2 < 0:
+ return -1
+ if v1 < 0:
+ return 1
+ if v1 > v2:
+ return 1
+ if v2 > v1:
+ return -1
+
+
+def queue_column_sort(model, iter1, iter2, data):
+ v1 = model[iter1][data]
+ v2 = model[iter2][data]
+ return queue_peer_seed_sort_function(v1, v2)
+
+
+def eta_column_sort(model, iter1, iter2, data):
+ v1 = model[iter1][data]
+ v2 = model[iter2][data]
+ if v1 == v2:
+ return 0
+ if v1 == 0:
+ return 1
+ if v2 == 0:
+ return -1
+ if v1 > v2:
+ return 1
+ if v2 > v1:
+ return -1
+
+
+def seed_peer_column_sort(model, iter1, iter2, data):
+ v1 = model[iter1][data] # num seeds/peers
+ v3 = model[iter2][data] # num seeds/peers
+ if v1 == v3:
+ v2 = model[iter1][data + 1] # total seeds/peers
+ v4 = model[iter2][data + 1] # total seeds/peers
+ return queue_peer_seed_sort_function(v2, v4)
+ return queue_peer_seed_sort_function(v1, v3)
+
+
+def progress_sort(model, iter1, iter2, sort_column_id):
+ progress1 = model[iter1][sort_column_id]
+ progress2 = model[iter2][sort_column_id]
+ # Progress value is equal, so sort on state
+ if progress1 == progress2:
+ state1 = model[iter1][sort_column_id + 1]
+ state2 = model[iter2][sort_column_id + 1]
+ return cmp(state1, state2)
+ return cmp(progress1, progress2)
+
+
+class SearchBox(object):
+ def __init__(self, torrentview):
+ self.torrentview = torrentview
+ mainwindow = component.get('MainWindow')
+ main_builder = mainwindow.get_builder()
+
+ self.visible = False
+ self.search_pending = self.prefiltered = None
+
+ self.search_box = main_builder.get_object('search_box')
+ self.search_torrents_entry = main_builder.get_object('search_torrents_entry')
+ self.close_search_button = main_builder.get_object('close_search_button')
+ self.match_search_button = main_builder.get_object('search_torrents_match')
+ mainwindow.connect_signals(self)
+
+ def show(self):
+ self.visible = True
+ self.search_box.show_all()
+ self.search_torrents_entry.grab_focus()
+
+ def hide(self):
+ self.visible = False
+ self.clear_search()
+ self.search_box.hide()
+ self.search_pending = self.prefiltered = None
+
+ def clear_search(self):
+ if self.search_pending and self.search_pending.active():
+ self.search_pending.cancel()
+
+ if self.prefiltered:
+ filter_column = self.torrentview.columns['filter'].column_indices[0]
+ torrent_id_column = self.torrentview.columns['torrent_id'].column_indices[0]
+ for row in self.torrentview.liststore:
+ torrent_id = row[torrent_id_column]
+
+ if torrent_id in self.prefiltered:
+ # Reset to previous filter state
+ self.prefiltered.pop(self.prefiltered.index(torrent_id))
+ row[filter_column] = not row[filter_column]
+
+ self.prefiltered = None
+
+ self.search_torrents_entry.set_text('')
+ if self.torrentview.filter and 'name' in self.torrentview.filter:
+ self.torrentview.filter.pop('name', None)
+ self.search_pending = reactor.callLater(0.5, self.torrentview.update)
+
+ def set_search_filter(self):
+ if self.search_pending and self.search_pending.active():
+ self.search_pending.cancel()
+
+ if self.torrentview.filter and 'name' in self.torrentview.filter:
+ self.torrentview.filter.pop('name', None)
+
+ elif self.torrentview.filter is None:
+ self.torrentview.filter = {}
+
+ search_string = self.search_torrents_entry.get_text()
+ if not search_string:
+ self.clear_search()
+ else:
+ if self.match_search_button.get_active():
+ search_string += '::match'
+ self.torrentview.filter['name'] = search_string
+ self.prefilter_torrentview()
+
+ def prefilter_torrentview(self):
+ filter_column = self.torrentview.columns['filter'].column_indices[0]
+ torrent_id_column = self.torrentview.columns['torrent_id'].column_indices[0]
+ torrent_name_column = self.torrentview.columns[_('Name')].column_indices[1]
+
+ match_case = self.match_search_button.get_active()
+ if match_case:
+ search_string = self.search_torrents_entry.get_text()
+ else:
+ search_string = self.search_torrents_entry.get_text().lower()
+
+ if self.prefiltered is None:
+ self.prefiltered = []
+
+ for row in self.torrentview.liststore:
+ torrent_id = row[torrent_id_column]
+
+ if torrent_id in self.prefiltered:
+ # Reset to previous filter state
+ self.prefiltered.pop(self.prefiltered.index(torrent_id))
+ row[filter_column] = not row[filter_column]
+
+ if not row[filter_column]:
+ # Row is not visible(filtered out, but not by our filter), skip it
+ continue
+
+ if match_case:
+ torrent_name = row[torrent_name_column]
+ else:
+ torrent_name = row[torrent_name_column].lower()
+
+ if search_string in torrent_name and not row[filter_column]:
+ row[filter_column] = True
+ self.prefiltered.append(torrent_id)
+ elif search_string not in torrent_name and row[filter_column]:
+ row[filter_column] = False
+ self.prefiltered.append(torrent_id)
+
+ def on_close_search_button_clicked(self, widget):
+ self.hide()
+
+ def on_search_filter_toggle(self, widget):
+ if self.visible:
+ self.hide()
+ else:
+ self.show()
+
+ def on_search_torrents_match_toggled(self, widget):
+ if self.search_torrents_entry.get_text():
+ self.set_search_filter()
+ self.search_pending = reactor.callLater(0.7, self.torrentview.update)
+
+ def on_search_torrents_entry_icon_press(self, entry, icon, event):
+ if icon != EntryIconPosition.SECONDARY:
+ return
+ self.clear_search()
+
+ def on_search_torrents_entry_changed(self, widget):
+ self.set_search_filter()
+ self.search_pending = reactor.callLater(0.7, self.torrentview.update)
+
+
+class TorrentView(ListView, component.Component):
+ """TorrentView handles the listing of torrents."""
+
+ def __init__(self):
+ component.Component.__init__(
+ self, 'TorrentView', interval=2, depend=['SessionProxy']
+ )
+ main_builder = component.get('MainWindow').get_builder()
+ # Call the ListView constructor
+ ListView.__init__(
+ self, main_builder.get_object('torrent_view'), 'torrentview.state'
+ )
+ log.debug('TorrentView Init..')
+
+ # If we have gotten the state yet
+ self.got_state = False
+
+ # This is where status updates are put
+ self.status = {}
+
+ # We keep a copy of the previous status to compare for changes
+ self.prev_status = {}
+
+ # Register the columns menu with the listview so it gets updated accordingly.
+ self.register_checklist_menu(main_builder.get_object('menu_columns'))
+
+ # Add the columns to the listview
+ self.add_text_column('torrent_id', hidden=True, unique=True)
+ self.add_bool_column('dirty', hidden=True)
+ self.add_func_column(
+ '#',
+ funcs.cell_data_queue,
+ [int],
+ status_field=['queue'],
+ sort_func=queue_column_sort,
+ )
+ self.add_texticon_column(
+ _('Name'),
+ status_field=['state', 'name'],
+ function=funcs.cell_data_statusicon,
+ sort_func=str_nocase_sort,
+ default_sort=True,
+ )
+ self.add_func_column(
+ _('Size'),
+ funcs.cell_data_size,
+ [TYPE_UINT64],
+ status_field=['total_wanted'],
+ )
+ self.add_func_column(
+ _('Downloaded'),
+ funcs.cell_data_size,
+ [TYPE_UINT64],
+ status_field=['all_time_download'],
+ default=False,
+ )
+ self.add_func_column(
+ _('Uploaded'),
+ funcs.cell_data_size,
+ [TYPE_UINT64],
+ status_field=['total_uploaded'],
+ default=False,
+ )
+ self.add_func_column(
+ _('Remaining'),
+ funcs.cell_data_size,
+ [TYPE_UINT64],
+ status_field=['total_remaining'],
+ default=False,
+ )
+ self.add_progress_column(
+ _('Progress'),
+ status_field=['progress', 'state'],
+ col_types=[float, str],
+ function=funcs.cell_data_progress,
+ sort_func=progress_sort,
+ )
+ self.add_func_column(
+ _('Seeds'),
+ funcs.cell_data_peer,
+ [int, int],
+ status_field=['num_seeds', 'total_seeds'],
+ sort_func=seed_peer_column_sort,
+ default=False,
+ )
+ self.add_func_column(
+ _('Peers'),
+ funcs.cell_data_peer,
+ [int, int],
+ status_field=['num_peers', 'total_peers'],
+ sort_func=seed_peer_column_sort,
+ default=False,
+ )
+ self.add_func_column(
+ _('Seeds:Peers'),
+ funcs.cell_data_ratio_seeds_peers,
+ [float],
+ status_field=['seeds_peers_ratio'],
+ default=False,
+ )
+ self.add_func_column(
+ _('Down Speed'),
+ funcs.cell_data_speed_down,
+ [int],
+ status_field=['download_payload_rate'],
+ )
+ self.add_func_column(
+ _('Up Speed'),
+ funcs.cell_data_speed_up,
+ [int],
+ status_field=['upload_payload_rate'],
+ )
+ self.add_func_column(
+ _('Down Limit'),
+ funcs.cell_data_speed_limit_down,
+ [float],
+ status_field=['max_download_speed'],
+ default=False,
+ )
+ self.add_func_column(
+ _('Up Limit'),
+ funcs.cell_data_speed_limit_up,
+ [float],
+ status_field=['max_upload_speed'],
+ default=False,
+ )
+ self.add_func_column(
+ _('ETA'),
+ funcs.cell_data_time,
+ [int],
+ status_field=['eta'],
+ sort_func=eta_column_sort,
+ )
+ self.add_func_column(
+ _('Ratio'),
+ funcs.cell_data_ratio_ratio,
+ [float],
+ status_field=['ratio'],
+ default=False,
+ )
+ self.add_func_column(
+ _('Avail'),
+ funcs.cell_data_ratio_avail,
+ [float],
+ status_field=['distributed_copies'],
+ default=False,
+ )
+ self.add_func_column(
+ _('Added'),
+ funcs.cell_data_date_added,
+ [int],
+ status_field=['time_added'],
+ default=False,
+ )
+ self.add_func_column(
+ _('Completed'),
+ funcs.cell_data_date_completed,
+ [int],
+ status_field=['completed_time'],
+ default=False,
+ )
+ self.add_func_column(
+ _('Complete Seen'),
+ funcs.cell_data_date_or_never,
+ [int],
+ status_field=['last_seen_complete'],
+ default=False,
+ )
+ self.add_texticon_column(
+ _('Tracker'),
+ function=funcs.cell_data_trackericon,
+ status_field=['tracker_host', 'tracker_host'],
+ default=False,
+ )
+ self.add_text_column(
+ _('Download Folder'), status_field=['download_location'], default=False
+ )
+ self.add_text_column(_('Owner'), status_field=['owner'], default=False)
+ self.add_bool_column(
+ _('Shared'),
+ status_field=['shared'],
+ default=False,
+ tooltip=_('Torrent is shared between other Deluge users or not.'),
+ )
+ self.restore_columns_order_from_state()
+
+ # Set filter to None for now
+ self.filter = None
+
+ # Connect Signals #
+ # Connect to the 'button-press-event' to know when to bring up the
+ # torrent menu popup.
+ self.treeview.connect('button-press-event', self.on_button_press_event)
+ # Connect to the 'key-press-event' to know when the bring up the
+ # torrent menu popup via keypress.
+ self.treeview.connect('key-release-event', self.on_key_press_event)
+ # Connect to the 'changed' event of TreeViewSelection to get selection
+ # changes.
+ self.treeview.get_selection().connect('changed', self.on_selection_changed)
+
+ self.treeview.connect('drag-drop', self.on_drag_drop)
+ self.treeview.connect('drag_data_received', self.on_drag_data_received)
+ self.treeview.connect('key-press-event', self.on_key_press_event)
+ self.treeview.connect('columns-changed', self.on_columns_changed_event)
+
+ self.search_box = SearchBox(self)
+ self.permanent_status_keys = ['owner']
+ self.columns_to_update = []
+
+ def start(self):
+ """Start the torrentview"""
+ # We need to get the core session state to know which torrents are in
+ # the session so we can add them to our list.
+ # Only get the status fields required for the visible columns
+ status_fields = []
+ for listview_column in self.columns.values():
+ if listview_column.column.get_visible():
+ if not listview_column.status_field:
+ continue
+ status_fields.extend(listview_column.status_field)
+ component.get('SessionProxy').get_torrents_status(
+ {}, status_fields
+ ).addCallback(self._on_session_state)
+
+ client.register_event_handler(
+ 'TorrentStateChangedEvent', self.on_torrentstatechanged_event
+ )
+ client.register_event_handler('TorrentAddedEvent', self.on_torrentadded_event)
+ client.register_event_handler(
+ 'TorrentRemovedEvent', self.on_torrentremoved_event
+ )
+ client.register_event_handler('SessionPausedEvent', self.on_sessionpaused_event)
+ client.register_event_handler(
+ 'SessionResumedEvent', self.on_sessionresumed_event
+ )
+ client.register_event_handler(
+ 'TorrentQueueChangedEvent', self.on_torrentqueuechanged_event
+ )
+
+ def _on_session_state(self, state):
+ self.add_rows(state)
+ self.got_state = True
+ # Update the view right away with our status
+ self.status = state
+ self.set_columns_to_update()
+ self.update_view(load_new_list=True)
+ self.select_first_row()
+
+ def stop(self):
+ """Stops the torrentview"""
+ client.deregister_event_handler(
+ 'TorrentStateChangedEvent', self.on_torrentstatechanged_event
+ )
+ client.deregister_event_handler('TorrentAddedEvent', self.on_torrentadded_event)
+ client.deregister_event_handler(
+ 'TorrentRemovedEvent', self.on_torrentremoved_event
+ )
+ client.deregister_event_handler(
+ 'SessionPausedEvent', self.on_sessionpaused_event
+ )
+ client.deregister_event_handler(
+ 'SessionResumedEvent', self.on_sessionresumed_event
+ )
+ client.deregister_event_handler(
+ 'TorrentQueueChangedEvent', self.on_torrentqueuechanged_event
+ )
+
+ if self.treeview.get_selection():
+ self.treeview.get_selection().unselect_all()
+
+ # Save column state before clearing liststore
+ # so column sort details are correctly saved.
+ self.save_state()
+ self.liststore.clear()
+ self.prev_status = {}
+ self.filter = None
+ self.search_box.hide()
+
+ def shutdown(self):
+ """Called when GtkUi is exiting"""
+ pass
+
+ def save_state(self):
+ """
+ Saves the state of the torrent view.
+ """
+ if component.get('MainWindow').visible():
+ ListView.save_state(self, 'torrentview.state')
+
+ def remove_column(self, header):
+ """Removes the column with the name 'header' from the torrentview"""
+ self.save_state()
+ ListView.remove_column(self, header)
+
+ def set_filter(self, filter_dict):
+ """
+ Sets filters for the torrentview..
+
+ see: core.get_torrents_status
+ """
+ search_filter = self.filter and self.filter.get('name', None) or None
+ self.filter = dict(filter_dict) # Copied version of filter_dict.
+ if search_filter and 'name' not in filter_dict:
+ self.filter['name'] = search_filter
+ self.update(select_row=True)
+
+ def set_columns_to_update(self, columns=None):
+ status_keys = []
+ self.columns_to_update = []
+
+ if columns is None:
+ # We need to iterate through all columns
+ columns = list(self.columns)
+
+ # Iterate through supplied list of columns to update
+ for column in columns:
+ # Make sure column is visible and has 'status_field' set.
+ # If not, we can ignore it.
+ if (
+ self.columns[column].column.get_visible() is True
+ and self.columns[column].hidden is False
+ and self.columns[column].status_field is not None
+ ):
+ for field in self.columns[column].status_field:
+ status_keys.append(field)
+ self.columns_to_update.append(column)
+
+ # Remove duplicates
+ self.columns_to_update = list(set(self.columns_to_update))
+ status_keys = list(set(status_keys + self.permanent_status_keys))
+ return status_keys
+
+ def send_status_request(self, columns=None, select_row=False):
+ # Store the 'status_fields' we need to send to core
+ status_keys = self.set_columns_to_update(columns)
+
+ # If there is nothing in status_keys then we must not continue
+ if status_keys is []:
+ return
+
+ # Remove duplicates from status_key list
+ status_keys = list(set(status_keys))
+
+ # Request the statuses for all these torrent_ids, this is async so we
+ # will deal with the return in a signal callback.
+ d = (
+ component.get('SessionProxy')
+ .get_torrents_status(self.filter, status_keys)
+ .addCallback(self._on_get_torrents_status)
+ )
+ if select_row:
+ d.addCallback(self.select_first_row)
+
+ def select_first_row(self, ignored=None):
+ """
+ Set the first row in the list selected if a selection does
+ not already exist
+ """
+ rows = self.treeview.get_selection().get_selected_rows()[1]
+ # Only select row if noe rows are selected
+ if not rows:
+ self.treeview.get_selection().select_path((0,))
+
+ def update(self, select_row=False):
+ """
+ Sends a status request to core and updates the torrent list with the result.
+
+ :param select_row: if the first row in the list should be selected if
+ no rows are already selected.
+ :type select_row: boolean
+
+ """
+ if self.got_state:
+ if (
+ self.search_box.search_pending is not None
+ and self.search_box.search_pending.active()
+ ):
+ # An update request is scheduled, let's wait for that one
+ return
+ # Send a status request
+ idle_add(self.send_status_request, None, select_row)
+
+ def update_view(self, load_new_list=False):
+ """Update the torrent view model with data we've received."""
+ filter_column = self.columns['filter'].column_indices[0]
+ status = self.status
+
+ if not load_new_list:
+ # Freeze notications while updating
+ self.treeview.freeze_child_notify()
+
+ # Get the columns to update from one of the torrents
+ if status:
+ torrent_id = list(status)[0]
+ fields_to_update = []
+ for column in self.columns_to_update:
+ column_index = self.get_column_index(column)
+ for i, status_field in enumerate(self.columns[column].status_field):
+ # Only use columns that the torrent has in the state
+ if status_field in status[torrent_id]:
+ fields_to_update.append((column_index[i], status_field))
+
+ for row in self.liststore:
+ torrent_id = row[self.columns['torrent_id'].column_indices[0]]
+ # We expect the torrent_id to be in status and prev_status,
+ # as it will be as long as the list isn't changed by the user
+
+ torrent_id_in_status = False
+ try:
+ torrent_status = status[torrent_id]
+ torrent_id_in_status = True
+ if torrent_status == self.prev_status[torrent_id]:
+ # The status dict is the same, so do nothing to update for this torrent
+ continue
+ except KeyError:
+ pass
+
+ if not torrent_id_in_status:
+ if row[filter_column] is True:
+ row[filter_column] = False
+ else:
+ if row[filter_column] is False:
+ row[filter_column] = True
+
+ # Find the fields to update
+ to_update = []
+ for i, status_field in fields_to_update:
+ row_value = status[torrent_id][status_field]
+ if decode_bytes(row[i]) != row_value:
+ to_update.append(i)
+ to_update.append(row_value)
+ # Update fields in the liststore
+ if to_update:
+ self.liststore.set(row.iter, *to_update)
+
+ if load_new_list:
+ # Create the model filter. This sets the model for the treeview and enables sorting.
+ self.create_model_filter()
+ else:
+ self.treeview.thaw_child_notify()
+
+ component.get('MenuBar').update_menu()
+ self.prev_status = status
+
+ def _on_get_torrents_status(self, status, select_row=False):
+ """Callback function for get_torrents_status(). 'status' should be a
+ dictionary of {torrent_id: {key, value}}."""
+ self.status = status
+ if self.search_box.prefiltered is not None:
+ self.search_box.prefiltered = None
+
+ if self.status == self.prev_status and self.prev_status:
+ # We do not bother updating since the status hasn't changed
+ self.prev_status = self.status
+ return
+ self.update_view()
+
+ def add_rows(self, torrent_ids):
+ """Accepts a list of torrent_ids to add to self.liststore"""
+ torrent_id_column = self.columns['torrent_id'].column_indices[0]
+ dirty_column = self.columns['dirty'].column_indices[0]
+ filter_column = self.columns['filter'].column_indices[0]
+ for torrent_id in torrent_ids:
+ # Insert a new row to the liststore
+ row = self.liststore.append()
+ self.liststore.set(
+ row,
+ torrent_id_column,
+ torrent_id,
+ dirty_column,
+ True,
+ filter_column,
+ True,
+ )
+
+ def remove_row(self, torrent_id):
+ """Removes a row with torrent_id"""
+ for row in self.liststore:
+ if row[self.columns['torrent_id'].column_indices[0]] == torrent_id:
+ self.liststore.remove(row.iter)
+ # Force an update of the torrentview
+ self.update(select_row=True)
+ break
+
+ def mark_dirty(self, torrent_id=None):
+ for row in self.liststore:
+ if (
+ not torrent_id
+ or row[self.columns['torrent_id'].column_indices[0]] == torrent_id
+ ):
+ # log.debug('marking %s dirty', torrent_id)
+ row[self.columns['dirty'].column_indices[0]] = True
+ if torrent_id:
+ break
+
+ def get_selected_torrent(self):
+ """Returns a torrent_id or None. If multiple torrents are selected,
+ it will return the torrent_id of the first one."""
+ selected = self.get_selected_torrents()
+ if selected:
+ return selected[0]
+ else:
+ return selected
+
+ def get_selected_torrents(self):
+ """Returns a list of selected torrents or None"""
+ torrent_ids = []
+ try:
+ paths = self.treeview.get_selection().get_selected_rows()[1]
+ except AttributeError:
+ # paths is likely None .. so lets return []
+ return []
+ try:
+ for path in paths:
+ try:
+ row = self.treeview.get_model().get_iter(path)
+ except Exception as ex:
+ log.debug('Unable to get iter from path: %s', ex)
+ continue
+
+ child_row = self.treeview.get_model().convert_iter_to_child_iter(row)
+ child_row = (
+ self.treeview.get_model()
+ .get_model()
+ .convert_iter_to_child_iter(child_row)
+ )
+ if self.liststore.iter_is_valid(child_row):
+ try:
+ value = self.liststore.get_value(
+ child_row, self.columns['torrent_id'].column_indices[0]
+ )
+ except Exception as ex:
+ log.debug('Unable to get value from row: %s', ex)
+ else:
+ torrent_ids.append(value)
+ if len(torrent_ids) == 0:
+ return []
+
+ return torrent_ids
+ except (ValueError, TypeError):
+ return []
+
+ def get_torrent_status(self, torrent_id):
+ """Returns data stored in self.status, it may not be complete"""
+ try:
+ return self.status[torrent_id]
+ except KeyError:
+ return {}
+
+ def get_visible_torrents(self):
+ return list(self.status)
+
+ # Callbacks #
+ def on_button_press_event(self, widget, event):
+ """This is a callback for showing the right-click context menu."""
+ log.debug('on_button_press_event')
+ # We only care about right-clicks
+ if event.button == 3 and event.window == self.treeview.get_bin_window():
+ x, y = event.get_coords()
+ path = self.treeview.get_path_at_pos(int(x), int(y))
+ if not path:
+ return
+ row = self.model_filter.get_iter(path[0])
+
+ if self.get_selected_torrents():
+ if (
+ self.model_filter.get_value(
+ row, self.columns['torrent_id'].column_indices[0]
+ )
+ not in self.get_selected_torrents()
+ ):
+ self.treeview.get_selection().unselect_all()
+ self.treeview.get_selection().select_iter(row)
+ else:
+ self.treeview.get_selection().select_iter(row)
+ torrentmenu = component.get('MenuBar').torrentmenu
+ torrentmenu.popup(None, None, None, None, event.button, event.time)
+ return True
+
+ def on_selection_changed(self, treeselection):
+ """This callback is know when the selection has changed."""
+ log.debug('on_selection_changed')
+ component.get('TorrentDetails').update()
+ component.get('MenuBar').update_menu()
+
+ def on_drag_drop(self, widget, drag_context, x, y, timestamp):
+ widget.stop_emission('drag-drop')
+
+ def on_drag_data_received(
+ self, widget, drag_context, x, y, selection_data, info, timestamp
+ ):
+ widget.stop_emission('drag_data_received')
+
+ def on_columns_changed_event(self, treeview):
+ log.debug('Treeview Columns Changed')
+ self.save_state()
+
+ def on_torrentadded_event(self, torrent_id, from_state):
+ self.add_rows([torrent_id])
+ self.update(select_row=True)
+
+ def on_torrentremoved_event(self, torrent_id):
+ self.remove_row(torrent_id)
+
+ def on_torrentstatechanged_event(self, torrent_id, state):
+ # Update the torrents state
+ for row in self.liststore:
+ if torrent_id != row[self.columns['torrent_id'].column_indices[0]]:
+ continue
+
+ for name in self.columns_to_update:
+ if not self.columns[name].status_field:
+ continue
+ for idx, status_field in enumerate(self.columns[name].status_field):
+ # Update all columns that use the state field to current state
+ if status_field != 'state':
+ continue
+ row[self.get_column_index(name)[idx]] = state
+
+ if self.filter.get('state', None) is not None:
+ # We have a filter set, let's see if theres anything to hide
+ # and remove from status
+ if (
+ torrent_id in self.status
+ and self.status[torrent_id]['state'] != state
+ ):
+ row[self.columns['filter'].column_indices[0]] = False
+ del self.status[torrent_id]
+
+ self.mark_dirty(torrent_id)
+
+ def on_sessionpaused_event(self):
+ self.mark_dirty()
+ self.update()
+
+ def on_sessionresumed_event(self):
+ self.mark_dirty()
+ self.update(select_row=True)
+
+ def on_torrentqueuechanged_event(self):
+ self.mark_dirty()
+ self.update()
+
+ # Handle keyboard shortcuts
+ def on_key_press_event(self, widget, event):
+ keyname = keyval_name(event.keyval)
+ if keyname is not None:
+ func = getattr(self, 'keypress_' + keyname.lower(), None)
+ if func:
+ return func(event)
+
+ def keypress_up(self, event):
+ """Handle any Up arrow keypresses"""
+ log.debug('keypress_up')
+ torrents = self.get_selected_torrents()
+ if not torrents:
+ return
+
+ # Move queue position up with Ctrl+Alt or Ctrl+Alt+Shift
+ if event.get_state() & CTRL_ALT_MASK:
+ if event.get_state() & ModifierType.SHIFT_MASK:
+ client.core.queue_top(torrents)
+ else:
+ client.core.queue_up(torrents)
+
+ def keypress_down(self, event):
+ """Handle any Down arrow keypresses"""
+ log.debug('keypress_down')
+ torrents = self.get_selected_torrents()
+ if not torrents:
+ return
+
+ # Move queue position down with Ctrl+Alt or Ctrl+Alt+Shift
+ if event.get_state() & CTRL_ALT_MASK:
+ if event.get_state() & ModifierType.SHIFT_MASK:
+ client.core.queue_bottom(torrents)
+ else:
+ client.core.queue_down(torrents)
+
+ def keypress_delete(self, event):
+ log.debug('keypress_delete')
+ torrents = self.get_selected_torrents()
+ if torrents:
+ if event.get_state() & ModifierType.SHIFT_MASK:
+ RemoveTorrentDialog(torrents, delete_files=True).run()
+ else:
+ RemoveTorrentDialog(torrents).run()
+
+ def keypress_menu(self, event):
+ log.debug('keypress_menu')
+ if not self.get_selected_torrent():
+ return
+
+ torrentmenu = component.get('MenuBar').torrentmenu
+ torrentmenu.popup(None, None, None, None, 3, event.time)
+ return True
diff --git a/deluge/ui/gtk3/torrentview_data_funcs.py b/deluge/ui/gtk3/torrentview_data_funcs.py
new file mode 100644
index 0000000..8bd1f9c
--- /dev/null
+++ b/deluge/ui/gtk3/torrentview_data_funcs.py
@@ -0,0 +1,285 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007, 2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import print_function, unicode_literals
+
+import warnings
+from functools import partial
+
+import deluge.common as common
+import deluge.component as component
+
+from .common import (
+ create_blank_pixbuf,
+ get_pixbuf_at_size,
+ icon_alert,
+ icon_checking,
+ icon_downloading,
+ icon_inactive,
+ icon_queued,
+ icon_seeding,
+)
+
+# Holds the info for which status icon to display based on TORRENT_STATE
+ICON_STATE = {
+ 'Allocating': icon_checking,
+ 'Checking': icon_checking,
+ 'Downloading': icon_downloading,
+ 'Seeding': icon_seeding,
+ 'Paused': icon_inactive,
+ 'Error': icon_alert,
+ 'Queued': icon_queued,
+ 'Moving': icon_checking,
+}
+
+# Cache the key used to calculate the current value set for the specific cell
+# renderer. This is much cheaper than fetch the current value and test if
+# it's equal.
+func_last_value = {
+ 'cell_data_time': None,
+ 'cell_data_ratio_seeds_peers': None,
+ 'cell_data_ratio_ratio': None,
+ 'cell_data_ratio_avail': None,
+ 'cell_data_date_added': None,
+ 'cell_data_date_completed': None,
+ 'cell_data_date_or_never': None,
+ 'cell_data_speed_limit_down': None,
+ 'cell_data_speed_limit_up': None,
+ 'cell_data_trackericon': None,
+ 'cell_data_statusicon': None,
+ 'cell_data_queue': None,
+ 'cell_data_progress': [None, None],
+ 'cell_data_peer_progress': None,
+}
+
+
+def cell_data_statusicon(column, cell, model, row, data):
+ """Display text with an icon"""
+ try:
+ state = model.get_value(row, data)
+
+ if func_last_value['cell_data_statusicon'] == state:
+ return
+ func_last_value['cell_data_statusicon'] = state
+
+ icon = ICON_STATE[state]
+
+ # Supress Warning: g_object_set_qdata: assertion `G_IS_OBJECT (object)' failed
+ original_filters = warnings.filters[:]
+ warnings.simplefilter('ignore')
+ try:
+ cell.set_property('pixbuf', icon)
+ finally:
+ warnings.filters = original_filters
+
+ except KeyError:
+ pass
+
+
+def set_tracker_icon(tracker_icon, cell):
+ if tracker_icon:
+ pixbuf = tracker_icon.get_cached_icon()
+ if pixbuf is None:
+ pixbuf = get_pixbuf_at_size(tracker_icon.get_filename(), 16)
+ tracker_icon.set_cached_icon(pixbuf)
+ else:
+ pixbuf = create_blank_pixbuf()
+
+ # Suppress Warning: g_object_set_qdata: assertion `G_IS_OBJECT (object)' failed
+ with warnings.catch_warnings():
+ warnings.simplefilter('ignore')
+ cell.set_property('pixbuf', pixbuf)
+
+
+def cell_data_trackericon(column, cell, model, row, data):
+ host = model[row][data]
+
+ if func_last_value['cell_data_trackericon'] == host:
+ return
+ if host:
+ if not component.get('TrackerIcons').has(host):
+ # Set blank icon while waiting for the icon to be loaded
+ set_tracker_icon(None, cell)
+ component.get('TrackerIcons').fetch(host)
+ func_last_value['cell_data_trackericon'] = None
+ else:
+ set_tracker_icon(component.get('TrackerIcons').get(host), cell)
+ # Only set the last value when we have found the icon
+ func_last_value['cell_data_trackericon'] = host
+ else:
+ set_tracker_icon(None, cell)
+ func_last_value['cell_data_trackericon'] = None
+
+
+def cell_data_progress(column, cell, model, row, data):
+ """Display progress bar with text"""
+ (value, state_str) = model.get(row, *data)
+ if func_last_value['cell_data_progress'][0] != value:
+ func_last_value['cell_data_progress'][0] = value
+ cell.set_property('value', value)
+
+ # Marked for translate states text are in filtertreeview
+ textstr = _(state_str)
+ if state_str not in ('Error', 'Seeding') and value < 100:
+ textstr = '%s %i%%' % (textstr, value)
+
+ if func_last_value['cell_data_progress'][1] != textstr:
+ func_last_value['cell_data_progress'][1] = textstr
+ cell.set_property('text', textstr)
+
+
+def cell_data_peer_progress(column, cell, model, row, data):
+ value = model.get_value(row, data) * 100
+ if func_last_value['cell_data_peer_progress'] != value:
+ func_last_value['cell_data_peer_progress'] = value
+ cell.set_property('value', value)
+ cell.set_property('text', '%i%%' % value)
+
+
+def cell_data_queue(column, cell, model, row, data):
+ value = model.get_value(row, data)
+
+ if func_last_value['cell_data_queue'] == value:
+ return
+ func_last_value['cell_data_queue'] = value
+
+ if value < 0:
+ cell.set_property('text', '')
+ else:
+ cell.set_property('text', str(value + 1))
+
+
+def cell_data_speed(cell, model, row, data):
+ """Display value as a speed, eg. 2 KiB/s"""
+ speed = model.get_value(row, data)
+
+ if speed > 0:
+ speed_str = common.fspeed(speed, shortform=True)
+ cell.set_property(
+ 'markup', '{0} <small>{1}</small>'.format(*tuple(speed_str.split()))
+ )
+ else:
+ cell.set_property('text', '')
+
+
+def cell_data_speed_down(column, cell, model, row, data):
+ """Display value as a speed, eg. 2 KiB/s"""
+ cell_data_speed(cell, model, row, data)
+
+
+def cell_data_speed_up(column, cell, model, row, data):
+ """Display value as a speed, eg. 2 KiB/s"""
+ cell_data_speed(cell, model, row, data)
+
+
+def cell_data_speed_limit(cell, model, row, data, cache_key):
+ """Display value as a speed, eg. 2 KiB/s"""
+ speed = model.get_value(row, data)
+
+ if func_last_value[cache_key] == speed:
+ return
+ func_last_value[cache_key] = speed
+
+ if speed > 0:
+ speed_str = common.fspeed(speed * 1024, shortform=True)
+ cell.set_property(
+ 'markup', '{0} <small>{1}</small>'.format(*tuple(speed_str.split()))
+ )
+ else:
+ cell.set_property('text', '')
+
+
+def cell_data_speed_limit_down(column, cell, model, row, data):
+ cell_data_speed_limit(cell, model, row, data, 'cell_data_speed_limit_down')
+
+
+def cell_data_speed_limit_up(column, cell, model, row, data):
+ cell_data_speed_limit(cell, model, row, data, 'cell_data_speed_limit_up')
+
+
+def cell_data_size(column, cell, model, row, data):
+ """Display value in terms of size, eg. 2 MB"""
+ size = model.get_value(row, data)
+ cell.set_property('text', common.fsize(size, shortform=True))
+
+
+def cell_data_peer(column, cell, model, row, data):
+ """Display values as 'value1 (value2)'"""
+ (first, second) = model.get(row, *data)
+ # Only display a (total) if second is greater than -1
+ if second > -1:
+ cell.set_property('text', '%d (%d)' % (first, second))
+ else:
+ cell.set_property('text', '%d' % first)
+
+
+def cell_data_time(column, cell, model, row, data):
+ """Display value as time, eg 1m10s"""
+ time = model.get_value(row, data)
+ if func_last_value['cell_data_time'] == time:
+ return
+ func_last_value['cell_data_time'] = time
+
+ if time <= 0:
+ time_str = ''
+ else:
+ time_str = common.ftime(time)
+ cell.set_property('text', time_str)
+
+
+def cell_data_ratio(cell, model, row, data, cache_key):
+ """Display value as a ratio with a precision of 2."""
+ ratio = model.get_value(row, data)
+ # Previous value in cell is the same as for this value, so ignore
+ if func_last_value[cache_key] == ratio:
+ return
+ func_last_value[cache_key] = ratio
+ cell.set_property(
+ 'text', '∞' if ratio < 0 else ('%.1f' % ratio).rstrip('0').rstrip('.')
+ )
+
+
+def cell_data_ratio_seeds_peers(column, cell, model, row, data):
+ cell_data_ratio(cell, model, row, data, 'cell_data_ratio_seeds_peers')
+
+
+def cell_data_ratio_ratio(column, cell, model, row, data):
+ cell_data_ratio(cell, model, row, data, 'cell_data_ratio_ratio')
+
+
+def cell_data_ratio_avail(column, cell, model, row, data):
+ cell_data_ratio(cell, model, row, data, 'cell_data_ratio_avail')
+
+
+def cell_data_date(column, cell, model, row, data, key):
+ """Display value as date, eg 05/05/08"""
+ date = model.get_value(row, data)
+
+ if func_last_value[key] == date:
+ return
+ func_last_value[key] = date
+
+ date_str = common.fdate(date, date_only=True) if date > 0 else ''
+ cell.set_property('text', date_str)
+
+
+cell_data_date_added = partial(cell_data_date, key='cell_data_date_added')
+cell_data_date_completed = partial(cell_data_date, key='cell_data_date_completed')
+
+
+def cell_data_date_or_never(column, cell, model, row, data):
+ """Display value as date, eg 05/05/08 or Never"""
+ value = model.get_value(row, data)
+
+ if func_last_value['cell_data_date_or_never'] == value:
+ return
+ func_last_value['cell_data_date_or_never'] = value
+
+ date_str = common.fdate(value, date_only=True) if value > 0 else _('Never')
+ cell.set_property('text', date_str)
diff --git a/deluge/ui/gtk3/trackers_tab.py b/deluge/ui/gtk3/trackers_tab.py
new file mode 100644
index 0000000..d83b995
--- /dev/null
+++ b/deluge/ui/gtk3/trackers_tab.py
@@ -0,0 +1,74 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2008 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+import deluge.component as component
+from deluge.common import ftime
+
+from .tab_data_funcs import fcount, ftranslate, fyes_no
+from .torrentdetails import Tab
+
+log = logging.getLogger(__name__)
+
+
+class TrackersTab(Tab):
+ def __init__(self):
+ super(TrackersTab, self).__init__(
+ 'Trackers', 'trackers_tab', 'trackers_tab_label'
+ )
+
+ self.add_tab_widget('summary_next_announce', ftime, ('next_announce',))
+ self.add_tab_widget('summary_tracker', None, ('tracker_host',))
+ self.add_tab_widget('summary_tracker_status', ftranslate, ('tracker_status',))
+ self.add_tab_widget('summary_tracker_total', fcount, ('trackers',))
+ self.add_tab_widget('summary_private', fyes_no, ('private',))
+
+ component.get('MainWindow').connect_signals(self)
+
+ def update(self):
+ # Get the first selected torrent
+ selected = component.get('TorrentView').get_selected_torrents()
+
+ # Only use the first torrent in the list or return if None selected
+ if selected:
+ selected = selected[0]
+ else:
+ self.clear()
+ return
+
+ session = component.get('SessionProxy')
+ session.get_torrent_status(selected, self.status_keys).addCallback(
+ self._on_get_torrent_status
+ )
+
+ def _on_get_torrent_status(self, status):
+ # Check to see if we got valid data from the core
+ if not status:
+ return
+
+ # Update all the tab label widgets
+ for widget in self.tab_widgets.values():
+ txt = self.widget_status_as_fstr(widget, status)
+ if widget.obj.get_text() != txt:
+ widget.obj.set_text(txt)
+
+ def clear(self):
+ for widget in self.tab_widgets.values():
+ widget.obj.set_text('')
+
+ def on_button_edit_trackers_clicked(self, button):
+ torrent_id = component.get('TorrentView').get_selected_torrent()
+ if torrent_id:
+ from .edittrackersdialog import EditTrackersDialog
+
+ dialog = EditTrackersDialog(torrent_id, component.get('MainWindow').window)
+ dialog.run()
diff --git a/deluge/ui/hostlist.py b/deluge/ui/hostlist.py
new file mode 100644
index 0000000..ee4c7df
--- /dev/null
+++ b/deluge/ui/hostlist.py
@@ -0,0 +1,292 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) Calum Lind 2017 <calumlind+deluge@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os
+import uuid
+from socket import gaierror, gethostbyname
+
+from twisted.internet import defer
+
+from deluge.common import get_localhost_auth
+from deluge.config import Config
+from deluge.configmanager import get_config_dir
+from deluge.ui.client import Client, client
+
+log = logging.getLogger(__name__)
+
+DEFAULT_HOST = '127.0.0.1'
+DEFAULT_PORT = 58846
+LOCALHOST = ('127.0.0.1', 'localhost')
+
+
+def default_hostlist():
+ """Create a new hosts key for hostlist with a localhost entry"""
+ host_id = uuid.uuid4().hex
+ username, password = get_localhost_auth()
+ return {'hosts': [(host_id, DEFAULT_HOST, DEFAULT_PORT, username, password)]}
+
+
+def validate_host_info(hostname, port):
+ """Checks that hostname and port are valid.
+
+ Args:
+ hostname (str): The IP or hostname of the deluge daemon.
+ port (int): The port of the deluge daemon.
+
+ Raises:
+ ValueError: Host details are not valid with reason.
+ """
+
+ try:
+ gethostbyname(hostname)
+ except gaierror as ex:
+ raise ValueError('Host %s: %s', hostname, ex.args[1])
+
+ if not isinstance(port, int):
+ raise ValueError('Invalid port. Must be an integer')
+
+
+def migrate_hostlist(old_filename, new_filename):
+ """Check for old hostlist filename and save details to new filename"""
+ old_hostlist = get_config_dir(old_filename)
+ if os.path.isfile(old_hostlist):
+ config_v2 = Config(old_filename, config_dir=get_config_dir())
+ config_v2.save(get_config_dir(new_filename))
+ del config_v2
+
+ try:
+ os.rename(old_hostlist, old_hostlist + '.old')
+ except OSError as ex:
+ log.exception(ex)
+
+ try:
+ os.remove(old_hostlist + '.bak')
+ except OSError:
+ pass
+
+
+def migrate_config_2_to_3(config):
+ """Mirgrates old hostlist config files to new file version"""
+ localclient_username, localclient_password = get_localhost_auth()
+ if not localclient_username:
+ # Nothing to do here, there's no auth file
+ return
+ for idx, (__, host, __, username, __) in enumerate(config['hosts'][:]):
+ if host in LOCALHOST and not username:
+ config['hosts'][idx][3] = localclient_username
+ config['hosts'][idx][4] = localclient_password
+ return config
+
+
+class HostList(object):
+ """This class contains methods for adding, removing and looking up hosts in hostlist.conf."""
+
+ def __init__(self):
+ migrate_hostlist('hostlist.conf.1.2', 'hostlist.conf')
+ self.config = Config(
+ 'hostlist.conf',
+ default_hostlist(),
+ config_dir=get_config_dir(),
+ file_version=3,
+ )
+ self.config.run_converter((1, 2), 3, migrate_config_2_to_3)
+ self.config.save()
+
+ def check_info_exists(self, hostname, port, username, skip_host_id=None):
+ """Check for exising host entries with the same details.
+
+ Args:
+ hostname (str): The IP or hostname of the deluge daemon.
+ port (int): The port of the deluge daemon.
+ username (str): The username to login to the daemon with.
+ skip_host_id (str): A host_id to skip to check if other hosts match details.
+
+ Raises:
+ ValueError: Host details already exist.
+
+ """
+ for host_entry in self.config['hosts']:
+ if (hostname, port, username) == (
+ host_entry[1],
+ host_entry[2],
+ host_entry[3],
+ ):
+ if skip_host_id is not None and skip_host_id == host_entry[0]:
+ continue
+ raise ValueError('Host details already in hostlist')
+
+ def add_host(self, hostname, port, username, password):
+ """Add a new host to hostlist.
+
+ Args:
+ hostname (str): The IP or hostname of the deluge daemon.
+ port (int): The port of the deluge daemon.
+ username (str): The username to login to the daemon with.
+ password (str): The password to login to the daemon with.
+
+ Returns:
+ str: The new host id.
+ """
+ if (
+ not password and not username or username == 'localclient'
+ ) and hostname in LOCALHOST:
+ username, password = get_localhost_auth()
+
+ validate_host_info(hostname, port)
+ self.check_info_exists(hostname, port, username)
+ host_id = uuid.uuid4().hex
+ self.config['hosts'].append((host_id, hostname, port, username, password))
+ self.config.save()
+ return host_id
+
+ def get_host_info(self, host_id):
+ """Get the host details for host_id.
+
+ Args:
+ host_id (str): The host id to get info on.
+
+ Returns:
+ list: A list of (host_id, hostname, port, username).
+
+ """
+ for host_entry in self.config['hosts']:
+ if host_entry[0] == host_id:
+ return host_entry[0:4]
+ else:
+ return []
+
+ def get_hosts_info(self):
+ """Get information of all the hosts in the hostlist.
+
+ Returns:
+ list of lists: Host information in the format [(host_id, hostname, port, username)].
+
+ """
+ return [host_entry[0:4] for host_entry in self.config['hosts']]
+
+ def get_host_status(self, host_id):
+ """Gets the current status (online/offline) of the host
+
+ Args:
+ host_id (str): The host id to check status of.
+
+ Returns:
+ tuple: A tuple of strings (host_id, status, version).
+
+ """
+ status_offline = (host_id, 'Offline', '')
+
+ def on_connect(result, c, host_id):
+ """Successfully connected to a daemon"""
+
+ def on_info(info, c):
+ c.disconnect()
+ return host_id, 'Online', info
+
+ def on_info_fail(reason, c):
+ c.disconnect()
+ return status_offline
+
+ return c.daemon.info().addCallback(on_info, c).addErrback(on_info_fail, c)
+
+ def on_connect_failed(reason, host_id):
+ """Connection to daemon failed"""
+ log.debug('Host status failed for %s: %s', host_id, reason)
+ return status_offline
+
+ try:
+ host_id, host, port, user = self.get_host_info(host_id)
+ except ValueError:
+ log.warning('Problem getting host_id info from hostlist')
+ return status_offline
+
+ try:
+ ip = gethostbyname(host)
+ except gaierror as ex:
+ log.error('Error resolving host %s to ip: %s', host, ex.args[1])
+ return status_offline
+
+ host_conn_info = (
+ ip,
+ port,
+ 'localclient' if not user and host in LOCALHOST else user,
+ )
+ if client.connected() and host_conn_info == client.connection_info():
+ # Currently connected to host_id daemon.
+ def on_info(info, host_id):
+ log.debug('Client connected, query info: %s', info)
+ return host_id, 'Connected', info
+
+ return client.daemon.info().addCallback(on_info, host_id)
+ else:
+ # Attempt to connect to daemon with host_id details.
+ c = Client()
+ d = c.connect(host, port, skip_authentication=True)
+ d.addCallback(on_connect, c, host_id)
+ d.addErrback(on_connect_failed, host_id)
+ return d
+
+ def update_host(self, host_id, hostname, port, username, password):
+ """Update the supplied host id with new connection details.
+
+ Args:
+ host_id (str): The host id to update.
+ hostname (str): The new IP or hostname of the deluge daemon.
+ port (int): The new port of the deluge daemon.
+ username (str): The new username to login to the daemon with.
+ password (str): The new password to login to the daemon with.
+
+ """
+ validate_host_info(hostname, port)
+ self.check_info_exists(hostname, port, username, skip_host_id=host_id)
+
+ if (
+ not password and not username or username == 'localclient'
+ ) and hostname in LOCALHOST:
+ username, password = get_localhost_auth()
+
+ for idx, host_entry in enumerate(self.config['hosts']):
+ if host_id == host_entry[0]:
+ self.config['hosts'][idx] = host_id, hostname, port, username, password
+ self.config.save()
+ return True
+ return False
+
+ def remove_host(self, host_id):
+ """Removes the host entry from hostlist config.
+
+ Args:
+ host_id (str): The host id to remove.
+
+ Returns:
+ bool: True is successfully removed, False otherwise.
+
+ """
+ for host_entry in self.config['hosts']:
+ if host_id == host_entry[0]:
+ self.config['hosts'].remove(host_entry)
+ self.config.save()
+ return True
+ else:
+ return False
+
+ def add_default_host(self):
+ self.add_host(DEFAULT_HOST, DEFAULT_PORT, *get_localhost_auth())
+
+ def connect_host(self, host_id):
+ """Connect to host daemon"""
+ for host_entry in self.config['hosts']:
+ if host_entry[0] == host_id:
+ __, host, port, username, password = host_entry
+ return client.connect(host, port, username, password)
+
+ return defer.fail(Exception('Bad host id'))
diff --git a/deluge/ui/sessionproxy.py b/deluge/ui/sessionproxy.py
new file mode 100644
index 0000000..5af8e79
--- /dev/null
+++ b/deluge/ui/sessionproxy.py
@@ -0,0 +1,278 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2010 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+from __future__ import unicode_literals
+
+import logging
+from time import time
+
+from twisted.internet.defer import maybeDeferred, succeed
+
+import deluge.component as component
+from deluge.ui.client import client
+
+log = logging.getLogger(__name__)
+
+
+class SessionProxy(component.Component):
+ """
+ The SessionProxy component is used to cache session information client-side
+ to reduce the number of RPCs needed to provide a rich user interface.
+
+ It will query the Core for only changes in the status of the torrents
+ and will try to satisfy client requests from the cache.
+
+ """
+
+ def __init__(self):
+ log.debug('SessionProxy init..')
+ component.Component.__init__(self, 'SessionProxy', interval=5)
+
+ # Set the cache time in seconds
+ # This is how long data will be valid before re-fetching from the core
+ self.cache_time = 1.5
+
+ # Hold the torrents' status.. {torrent_id: [time, {status_dict}], ...}
+ self.torrents = {}
+
+ # Holds the time of the last key update.. {torrent_id: {key1, time, ...}, ...}
+ self.cache_times = {}
+
+ def start(self):
+ client.register_event_handler(
+ 'TorrentStateChangedEvent', self.on_torrent_state_changed
+ )
+ client.register_event_handler('TorrentRemovedEvent', self.on_torrent_removed)
+ client.register_event_handler('TorrentAddedEvent', self.on_torrent_added)
+
+ def on_get_session_state(torrent_ids):
+ for torrent_id in torrent_ids:
+ # Let's at least store the torrent ids with empty statuses
+ # so that upcoming queries or status updates don't throw errors.
+ self.torrents.setdefault(torrent_id, [time(), {}])
+ self.cache_times.setdefault(torrent_id, {})
+ return torrent_ids
+
+ return client.core.get_session_state().addCallback(on_get_session_state)
+
+ def stop(self):
+ client.deregister_event_handler(
+ 'TorrentStateChangedEvent', self.on_torrent_state_changed
+ )
+ client.deregister_event_handler('TorrentRemovedEvent', self.on_torrent_removed)
+ client.deregister_event_handler('TorrentAddedEvent', self.on_torrent_added)
+ self.torrents = {}
+
+ def create_status_dict(self, torrent_ids, keys):
+ """
+ Creates a status dict from the cache.
+
+ :param torrent_ids: the torrent_ids
+ :type torrent_ids: list of strings
+ :param keys: the status keys
+ :type keys: list of strings
+
+ :returns: a dict with the status information for the *torrent_ids*
+ :rtype: dict
+
+ """
+ sd = {}
+ keys = set(keys)
+ keys_len = (
+ -1
+ ) # The number of keys for the current cache (not the len of keys_diff_cached)
+ keys_diff_cached = []
+
+ for torrent_id in torrent_ids:
+ try:
+ if keys:
+ sd[torrent_id] = self.torrents[torrent_id][1].copy()
+
+ # Have to remove the keys that weren't requested
+ if len(sd[torrent_id]) == keys_len:
+ # If the number of keys are equal they are the same keys
+ # so we use the cached diff of the keys we need to remove
+ keys_to_remove = keys_diff_cached
+ else:
+ # Not the same keys so create a new diff
+ keys_to_remove = set(sd[torrent_id]) - keys
+ # Update the cached diff
+ keys_diff_cached = keys_to_remove
+ keys_len = len(sd[torrent_id])
+
+ # Usually there are no keys to remove, so it's cheaper with
+ # this if-test than a for-loop with no iterations.
+ if keys_to_remove:
+ for k in keys_to_remove:
+ del sd[torrent_id][k]
+ else:
+ sd[torrent_id] = dict(self.torrents[torrent_id][1])
+ except KeyError:
+ continue
+ return sd
+
+ def get_torrent_status(self, torrent_id, keys):
+ """
+ Get a status dict for one torrent.
+
+ :param torrent_id: the torrent_id
+ :type torrent_id: string
+ :param keys: the status keys
+ :type keys: list of strings
+
+ :returns: a dict of status information
+ :rtype: dict
+
+ """
+ if torrent_id in self.torrents:
+ # Keep track of keys we need to request from the core
+ keys_to_get = []
+ if not keys:
+ keys = list(self.torrents[torrent_id][1])
+
+ for key in keys:
+ if (
+ time() - self.cache_times[torrent_id].get(key, 0.0)
+ > self.cache_time
+ ):
+ keys_to_get.append(key)
+ if not keys_to_get:
+ return succeed(self.create_status_dict([torrent_id], keys)[torrent_id])
+ else:
+ d = client.core.get_torrent_status(torrent_id, keys_to_get, True)
+
+ def on_status(result, torrent_id):
+ t = time()
+ self.torrents[torrent_id][0] = t
+ self.torrents[torrent_id][1].update(result)
+ for key in keys_to_get:
+ self.cache_times[torrent_id][key] = t
+ return self.create_status_dict([torrent_id], keys)[torrent_id]
+
+ return d.addCallback(on_status, torrent_id)
+ else:
+ d = client.core.get_torrent_status(torrent_id, keys, True)
+
+ def on_status(result):
+ if result:
+ t = time()
+ self.torrents[torrent_id] = (t, result)
+ self.cache_times[torrent_id] = {}
+ for key in result:
+ self.cache_times[torrent_id][key] = t
+
+ return result
+
+ return d.addCallback(on_status)
+
+ def get_torrents_status(self, filter_dict, keys):
+ """
+ Get a dict of torrent statuses.
+
+ The filter can take 2 keys, *state* and *id*. The state filter can be
+ one of the torrent states or the special one *Active*. The *id* key is
+ simply a list of torrent_ids.
+
+ :param filter_dict: the filter used for this query
+ :type filter_dict: dict
+ :param keys: the status keys
+ :type keys: list of strings
+
+ :returns: a dict of torrent_ids and their status dicts
+ :rtype: dict
+
+ """
+ # Helper functions and callbacks ---------------------------------------
+ def on_status(result, torrent_ids, keys):
+ # Update the internal torrent status dict with the update values
+ t = time()
+ for key, value in result.items():
+ try:
+ self.torrents[key][0] = t
+ self.torrents[key][1].update(value)
+ for k in value:
+ self.cache_times[key][k] = t
+ except KeyError:
+ # The torrent was removed
+ continue
+
+ # Create the status dict
+ if not torrent_ids:
+ torrent_ids = list(result)
+
+ return self.create_status_dict(torrent_ids, keys)
+
+ def find_torrents_to_fetch(torrent_ids):
+ to_fetch = []
+ t = time()
+ for torrent_id in torrent_ids:
+ torrent = self.torrents[torrent_id]
+ if t - torrent[0] > self.cache_time:
+ to_fetch.append(torrent_id)
+ else:
+ # We need to check if a key is expired
+ for key in keys:
+ if (
+ t - self.cache_times[torrent_id].get(key, 0.0)
+ > self.cache_time
+ ):
+ to_fetch.append(torrent_id)
+ break
+
+ return to_fetch
+
+ # -----------------------------------------------------------------------
+
+ if not filter_dict:
+ # This means we want all the torrents status
+ # We get a list of any torrent_ids with expired status dicts
+ torrents_list = list(self.torrents)
+ to_fetch = find_torrents_to_fetch(torrents_list)
+ if to_fetch:
+ d = client.core.get_torrents_status({'id': to_fetch}, keys, True)
+ return d.addCallback(on_status, torrents_list, keys)
+
+ # Don't need to fetch anything
+ return maybeDeferred(self.create_status_dict, torrents_list, keys)
+
+ if len(filter_dict) == 1 and 'id' in filter_dict:
+ # At this point we should have a filter with just "id" in it
+ to_fetch = find_torrents_to_fetch(filter_dict['id'])
+ if to_fetch:
+ d = client.core.get_torrents_status({'id': to_fetch}, keys, True)
+ return d.addCallback(on_status, filter_dict['id'], keys)
+ else:
+ # Don't need to fetch anything, so just return data from the cache
+ return maybeDeferred(self.create_status_dict, filter_dict['id'], keys)
+ else:
+ # This is a keyworded filter so lets just pass it onto the core
+ # XXX: Add more caching here.
+ d = client.core.get_torrents_status(filter_dict, keys, True)
+ return d.addCallback(on_status, None, keys)
+
+ def on_torrent_state_changed(self, torrent_id, state):
+ if torrent_id in self.torrents:
+ self.torrents[torrent_id][1].setdefault('state', state)
+ self.cache_times.setdefault(torrent_id, {}).update(state=time())
+
+ def on_torrent_added(self, torrent_id, from_state):
+ self.torrents[torrent_id] = [time() - self.cache_time - 1, {}]
+ self.cache_times[torrent_id] = {}
+
+ def on_status(status):
+ self.torrents[torrent_id][1].update(status)
+ t = time()
+ for key in status:
+ self.cache_times[torrent_id][key] = t
+
+ client.core.get_torrent_status(torrent_id, []).addCallback(on_status)
+
+ def on_torrent_removed(self, torrent_id):
+ if torrent_id in self.torrents:
+ del self.torrents[torrent_id]
+ del self.cache_times[torrent_id]
diff --git a/deluge/ui/tracker_icons.py b/deluge/ui/tracker_icons.py
new file mode 100644
index 0000000..c10cd2f
--- /dev/null
+++ b/deluge/ui/tracker_icons.py
@@ -0,0 +1,662 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2010 John Garland <johnnybg+deluge@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os
+from tempfile import mkstemp
+
+from twisted.internet import defer, threads
+from twisted.web.error import PageRedirect
+from twisted.web.resource import ForbiddenResource, NoResource
+
+from deluge.component import Component
+from deluge.configmanager import get_config_dir
+from deluge.decorators import proxy
+from deluge.httpdownloader import download_file
+
+try:
+ from html.parser import HTMLParser
+ from urllib.parse import urljoin, urlparse
+except ImportError:
+ # PY2 fallback
+ from HTMLParser import HTMLParser
+ from urlparse import urljoin, urlparse # pylint: disable=ungrouped-imports
+
+try:
+ from PIL import Image
+except ImportError:
+ Image = None
+
+log = logging.getLogger(__name__)
+
+
+class TrackerIcon(object):
+ """
+ Represents a tracker's icon
+ """
+
+ def __init__(self, filename):
+ """
+ Initialises a new TrackerIcon object
+
+ :param filename: the filename of the icon
+ :type filename: string
+ """
+ self.filename = os.path.abspath(filename)
+ self.mimetype = extension_to_mimetype(self.filename.rpartition('.')[2])
+ self.data = None
+ self.icon_cache = None
+
+ def __eq__(self, other):
+ """
+ Compares this TrackerIcon with another to determine if they're equal
+
+ :param other: the TrackerIcon to compare to
+ :type other: TrackerIcon
+ :returns: whether or not they're equal
+ :rtype: boolean
+ """
+ return (
+ os.path.samefile(self.filename, other.filename)
+ or self.get_mimetype() == other.get_mimetype()
+ and self.get_data() == other.get_data()
+ )
+
+ def get_mimetype(self):
+ """
+ Returns the mimetype of this TrackerIcon's image
+
+ :returns: the mimetype of the image
+ :rtype: string
+ """
+ return self.mimetype
+
+ def get_data(self):
+ """
+ Returns the TrackerIcon's image data as a string
+
+ :returns: the image data
+ :rtype: string
+ """
+ if not self.data:
+ with open(self.filename, 'rb') as _file:
+ self.data = _file.read()
+ return self.data
+
+ def get_filename(self, full=True):
+ """
+ Returns the TrackerIcon image's filename
+
+ :param full: an (optional) arg to indicate whether or not to
+ return the full path
+ :type full: boolean
+ :returns: the path of the TrackerIcon's image
+ :rtype: string
+ """
+ return self.filename if full else os.path.basename(self.filename)
+
+ def set_cached_icon(self, data):
+ """
+ Set the cached icon data.
+
+ """
+ self.icon_cache = data
+
+ def get_cached_icon(self):
+ """
+ Returns the cached icon data.
+
+ """
+ return self.icon_cache
+
+
+class TrackerIcons(Component):
+ """
+ A TrackerIcon factory class
+ """
+
+ def __init__(self, icon_dir=None, no_icon=None):
+ """
+ Initialises a new TrackerIcons object
+
+ :param icon_dir: the (optional) directory of where to store the icons
+ :type icon_dir: string
+ :param no_icon: the (optional) path name of the icon to show when no icon
+ can be fetched
+ :type no_icon: string
+ """
+ Component.__init__(self, 'TrackerIcons')
+ if not icon_dir:
+ icon_dir = get_config_dir('icons')
+ self.dir = icon_dir
+ if not os.path.isdir(self.dir):
+ os.makedirs(self.dir)
+
+ self.icons = {}
+ for icon in os.listdir(self.dir):
+ if icon != no_icon:
+ host = icon_name_to_host(icon)
+ try:
+ self.icons[host] = TrackerIcon(os.path.join(self.dir, icon))
+ except KeyError:
+ log.warning('invalid icon %s', icon)
+ if no_icon:
+ self.icons[None] = TrackerIcon(no_icon)
+ else:
+ self.icons[None] = None
+ self.icons[''] = self.icons[None]
+
+ self.pending = {}
+ self.redirects = {}
+
+ def has(self, host):
+ """
+ Returns True or False if the tracker icon for the given host exists or not.
+
+ :param host: the host for the TrackerIcon
+ :type host: string
+ :returns: True or False
+ :rtype: bool
+ """
+ return host.lower() in self.icons
+
+ def get(self, host):
+ """
+ Returns a TrackerIcon for the given tracker's host
+ from the icon cache.
+
+ :param host: the host for the TrackerIcon
+ :type host: string
+ :returns: the TrackerIcon for the host
+ :rtype: TrackerIcon
+ """
+ host = host.lower()
+ if host in self.icons:
+ return self.icons[host]
+ else:
+ return None
+
+ def fetch(self, host):
+ """
+ Fetches (downloads) the icon for the given host.
+ When the icon is downloaded a callback is fired
+ on the the queue of callers to this function.
+
+ :param host: the host to obtain the TrackerIcon for
+ :type host: string
+ :returns: a Deferred which fires with the TrackerIcon for the given host
+ :rtype: Deferred
+ """
+ host = host.lower()
+ if host in self.icons:
+ # We already have it, so let's return it
+ d = defer.succeed(self.icons[host])
+ elif host in self.pending:
+ # We're in the middle of getting it
+ # Add ourselves to the waiting list
+ d = defer.Deferred()
+ self.pending[host].append(d)
+ else:
+ # We need to fetch it
+ self.pending[host] = []
+ # Start callback chain
+ d = self.download_page(host)
+ d.addCallbacks(
+ self.on_download_page_complete,
+ self.on_download_page_fail,
+ errbackArgs=(host,),
+ )
+ d.addCallback(self.parse_html_page)
+ d.addCallbacks(
+ self.on_parse_complete, self.on_parse_fail, callbackArgs=(host,)
+ )
+ d.addCallback(self.download_icon, host)
+ d.addCallbacks(
+ self.on_download_icon_complete,
+ self.on_download_icon_fail,
+ callbackArgs=(host,),
+ errbackArgs=(host,),
+ )
+ d.addCallback(self.resize_icon)
+ d.addCallback(self.store_icon, host)
+ return d
+
+ def download_page(self, host, url=None):
+ """
+ Downloads a tracker host's page
+ If no url is provided, it bases the url on the host
+
+ :param host: the tracker host
+ :type host: string
+ :param url: the (optional) url of the host
+ :type url: string
+ :returns: the filename of the tracker host's page
+ :rtype: Deferred
+ """
+ if not url:
+ url = self.host_to_url(host)
+ log.debug('Downloading %s %s', host, url)
+ tmp_fd, tmp_file = mkstemp(prefix='deluge_ticon.')
+ os.close(tmp_fd)
+ return download_file(url, tmp_file, force_filename=True, handle_redirects=False)
+
+ def on_download_page_complete(self, page):
+ """
+ Runs any download clean up functions
+
+ :param page: the page that finished downloading
+ :type page: string
+ :returns: the page that finished downloading
+ :rtype: string
+ """
+ log.debug('Finished downloading %s', page)
+ return page
+
+ def on_download_page_fail(self, f, host):
+ """
+ Recovers from download error
+
+ :param f: the failure that occurred
+ :type f: Failure
+ :param host: the name of the host whose page failed to download
+ :type host: string
+ :returns: a Deferred if recovery was possible
+ else the original failure
+ :rtype: Deferred or Failure
+ """
+ error_msg = f.getErrorMessage()
+ log.debug('Error downloading page: %s', error_msg)
+ d = f
+ if f.check(PageRedirect):
+ # Handle redirect errors
+ location = urljoin(self.host_to_url(host), error_msg.split(' to ')[1])
+ self.redirects[host] = url_to_host(location)
+ d = self.download_page(host, url=location)
+ d.addCallbacks(
+ self.on_download_page_complete,
+ self.on_download_page_fail,
+ errbackArgs=(host,),
+ )
+
+ return d
+
+ @proxy(threads.deferToThread)
+ def parse_html_page(self, page):
+ """
+ Parses the html page for favicons
+
+ :param page: the page to parse
+ :type page: string
+ :returns: a Deferred which callbacks a list of available favicons (url, type)
+ :rtype: Deferred
+ """
+ with open(page, 'r') as _file:
+ parser = FaviconParser()
+ for line in _file:
+ parser.feed(line)
+ if parser.left_head:
+ break
+ parser.close()
+ try:
+ os.remove(page)
+ except OSError as ex:
+ log.warning('Could not remove temp file: %s', ex)
+
+ return parser.get_icons()
+
+ def on_parse_complete(self, icons, host):
+ """
+ Runs any parse clean up functions
+
+ :param icons: the icons that were extracted from the page
+ :type icons: list
+ :param host: the host the icons are for
+ :type host: string
+ :returns: the icons that were extracted from the page
+ :rtype: list
+ """
+ log.debug('Parse Complete, got icons for %s: %s', host, icons)
+ url = self.host_to_url(host)
+ icons = [(urljoin(url, icon), mimetype) for icon, mimetype in icons]
+ log.debug('Icon urls from %s: %s', host, icons)
+ return icons
+
+ def on_parse_fail(self, f):
+ """
+ Recovers from a parse error
+
+ :param f: the failure that occurred
+ :type f: Failure
+ :returns: a Deferred if recovery was possible
+ else the original failure
+ :rtype: Deferred or Failure
+ """
+ log.debug('Error parsing page: %s', f.getErrorMessage())
+ return f
+
+ def download_icon(self, icons, host):
+ """
+ Downloads the first available icon from icons
+
+ :param icons: a list of icons
+ :type icons: list
+ :param host: the tracker's host name
+ :type host: string
+ :returns: a Deferred which fires with the downloaded icon's filename
+ :rtype: Deferred
+ """
+ if len(icons) == 0:
+ raise NoIconsError('empty icons list')
+ (url, mimetype) = icons.pop(0)
+ d = download_file(
+ url,
+ os.path.join(self.dir, host_to_icon_name(host, mimetype)),
+ force_filename=True,
+ )
+ d.addCallback(self.check_icon_is_valid)
+ if icons:
+ d.addErrback(self.on_download_icon_fail, host, icons)
+ return d
+
+ @proxy(threads.deferToThread)
+ def check_icon_is_valid(self, icon_name):
+ """
+ Performs a sanity check on icon_name
+
+ :param icon_name: the name of the icon to check
+ :type icon_name: string
+ :returns: the name of the validated icon
+ :rtype: string
+ :raises: InvalidIconError
+ """
+
+ if Image:
+ try:
+ with Image.open(icon_name):
+ pass
+ except IOError as ex:
+ raise InvalidIconError(ex)
+ else:
+ if not os.path.getsize(icon_name):
+ raise InvalidIconError('empty icon')
+
+ return icon_name
+
+ def on_download_icon_complete(self, icon_name, host):
+ """
+ Runs any download cleanup functions
+
+ :param icon_name: the filename of the icon that finished downloading
+ :type icon_name: string
+ :param host: the host the icon completed to download for
+ :type host: string
+ :returns: the icon that finished downloading
+ :rtype: TrackerIcon
+ """
+ log.debug('Successfully downloaded from %s: %s', host, icon_name)
+ return TrackerIcon(icon_name)
+
+ def on_download_icon_fail(self, f, host, icons=None):
+ """
+ Recovers from a download error
+
+ :param f: the failure that occurred
+ :type f: Failure
+ :param host: the host the icon failed to download for
+ :type host: string
+ :param icons: the (optional) list of remaining icons
+ :type icons: list
+ :returns: a Deferred if recovery was possible
+ else the original failure
+ :rtype: Deferred or Failure
+ """
+ if not icons:
+ icons = []
+ error_msg = f.getErrorMessage()
+ log.debug('Error downloading icon from %s: %s', host, error_msg)
+ d = f
+ if f.check(PageRedirect):
+ # Handle redirect errors
+ location = urljoin(self.host_to_url(host), error_msg.split(' to ')[1])
+ d = self.download_icon(
+ [(location, extension_to_mimetype(location.rpartition('.')[2]))]
+ + icons,
+ host,
+ )
+ if not icons:
+ d.addCallbacks(
+ self.on_download_icon_complete,
+ self.on_download_icon_fail,
+ callbackArgs=(host,),
+ errbackArgs=(host,),
+ )
+ elif f.check(NoResource, ForbiddenResource) and icons:
+ d = self.download_icon(icons, host)
+ elif f.check(NoIconsError):
+ # No icons, try favicon.ico as an act of desperation
+ d = self.download_icon(
+ [
+ (
+ urljoin(self.host_to_url(host), 'favicon.ico'),
+ extension_to_mimetype('ico'),
+ )
+ ],
+ host,
+ )
+ d.addCallbacks(
+ self.on_download_icon_complete,
+ self.on_download_icon_fail,
+ callbackArgs=(host,),
+ errbackArgs=(host,),
+ )
+ else:
+ # No icons :(
+ # Return the None Icon
+ d = self.icons[None]
+
+ return d
+
+ @proxy(threads.deferToThread)
+ def resize_icon(self, icon):
+ """
+ Resizes the given icon to be 16x16 pixels
+
+ :param icon: the icon to resize
+ :type icon: TrackerIcon
+ :returns: the resized icon
+ :rtype: TrackerIcon
+ """
+ # Requires Pillow(PIL) to resize.
+ if icon and Image:
+ filename = icon.get_filename()
+ with Image.open(filename) as img:
+ if img.size > (16, 16):
+ new_filename = filename.rpartition('.')[0] + '.png'
+ img = img.resize((16, 16), Image.ANTIALIAS)
+ img.save(new_filename)
+ if new_filename != filename:
+ os.remove(filename)
+ icon = TrackerIcon(new_filename)
+ return icon
+
+ def store_icon(self, icon, host):
+ """
+ Stores the icon for the given host
+ Callbacks any pending deferreds waiting on this icon
+
+ :param icon: the icon to store
+ :type icon: TrackerIcon or None
+ :param host: the host to store it for
+ :type host: string
+ :returns: the stored icon
+ :rtype: TrackerIcon or None
+ """
+ self.icons[host] = icon
+ for d in self.pending[host]:
+ d.callback(icon)
+ del self.pending[host]
+ return icon
+
+ def host_to_url(self, host):
+ """
+ Given a host, returns the URL to fetch
+
+ :param host: the tracker host
+ :type host: string
+ :returns: the url of the tracker
+ :rtype: string
+ """
+ if host in self.redirects:
+ host = self.redirects[host]
+ return 'http://%s/' % host
+
+
+# ------- HELPER CLASSES ------
+
+
+class FaviconParser(HTMLParser):
+ """
+ A HTMLParser which extracts favicons from a HTML page
+ """
+
+ def __init__(self):
+ self.icons = []
+ self.left_head = False
+ HTMLParser.__init__(self)
+
+ def handle_starttag(self, tag, attrs):
+ if (
+ tag == 'link'
+ and ('rel', 'icon') in attrs
+ or ('rel', 'shortcut icon') in attrs
+ ):
+ href = None
+ icon_type = None
+ for attr, value in attrs:
+ if attr == 'href':
+ href = value
+ elif attr == 'type':
+ icon_type = value
+ if href:
+ try:
+ mimetype = extension_to_mimetype(href.rpartition('.')[2])
+ except KeyError:
+ pass
+ else:
+ icon_type = mimetype
+ if icon_type:
+ self.icons.append((href, icon_type))
+
+ def handle_endtag(self, tag):
+ if tag == 'head':
+ self.left_head = True
+
+ def get_icons(self):
+ """
+ Returns a list of favicons extracted from the HTML page
+
+ :returns: a list of favicons
+ :rtype: list
+ """
+ return self.icons
+
+
+# ------ HELPER FUNCTIONS ------
+
+
+def url_to_host(url):
+ """
+ Given a URL, returns the host it belongs to
+
+ :param url: the URL in question
+ :type url: string
+ :returns: the host of the given URL
+ :rtype: string
+ """
+ return urlparse(url).hostname
+
+
+def host_to_icon_name(host, mimetype):
+ """
+ Given a host, returns the appropriate icon name
+
+ :param host: the host in question
+ :type host: string
+ :param mimetype: the mimetype of the icon
+ :type mimetype: string
+ :returns: the icon's filename
+ :rtype: string
+
+ """
+ return host + '.' + mimetype_to_extension(mimetype)
+
+
+def icon_name_to_host(icon):
+ """
+ Given a host's icon name, returns the host name
+
+ :param icon: the icon name
+ :type icon: string
+ :returns: the host name
+ :rtype: string
+ """
+ return icon.rpartition('.')[0]
+
+
+MIME_MAP = {
+ 'image/gif': 'gif',
+ 'image/jpeg': 'jpg',
+ 'image/png': 'png',
+ 'image/vnd.microsoft.icon': 'ico',
+ 'image/x-icon': 'ico',
+ 'gif': 'image/gif',
+ 'jpg': 'image/jpeg',
+ 'jpeg': 'image/jpeg',
+ 'png': 'image/png',
+ 'ico': 'image/vnd.microsoft.icon',
+}
+
+
+def mimetype_to_extension(mimetype):
+ """
+ Given a mimetype, returns the appropriate filename extension
+
+ :param mimetype: the mimetype
+ :type mimetype: string
+ :returns: the filename extension for the given mimetype
+ :rtype: string
+ :raises KeyError: if given an invalid mimetype
+ """
+ return MIME_MAP[mimetype.lower()]
+
+
+def extension_to_mimetype(extension):
+ """
+ Given a filename extension, returns the appropriate mimetype
+
+ :param extension: the filename extension
+ :type extension: string
+ :returns: the mimetype for the given filename extension
+ :rtype: string
+ :raises KeyError: if given an invalid filename extension
+ """
+ return MIME_MAP[extension.lower()]
+
+
+# ------ EXCEPTIONS ------
+
+
+class NoIconsError(Exception):
+ pass
+
+
+class InvalidIconError(Exception):
+ pass
diff --git a/deluge/ui/ui.py b/deluge/ui/ui.py
new file mode 100644
index 0000000..0986ec7
--- /dev/null
+++ b/deluge/ui/ui.py
@@ -0,0 +1,72 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+
+import deluge.common
+import deluge.configmanager
+import deluge.log
+from deluge.argparserbase import ArgParserBase
+from deluge.i18n import setup_translation
+
+log = logging.getLogger(__name__)
+
+try:
+ from setproctitle import setproctitle
+except ImportError:
+
+ def setproctitle(title):
+ return
+
+
+class UI(object):
+ """
+ Base class for UI implementations.
+
+ """
+
+ cmd_description = """Override with command description"""
+
+ def __init__(self, name, **kwargs):
+ self.__name = name
+ self.ui_args = kwargs.pop('ui_args', None)
+ setup_translation()
+ self.__parser = ArgParserBase(**kwargs)
+
+ def parse_args(self, parser, args=None):
+ options = parser.parse_args(args)
+ if not hasattr(options, 'remaining'):
+ options.remaining = []
+ return options
+
+ @property
+ def name(self):
+ return self.__name
+
+ @property
+ def parser(self):
+ return self.__parser
+
+ @property
+ def options(self):
+ return self.__options
+
+ def start(self, parser=None):
+ args = deluge.common.unicode_argv()[1:]
+ if parser is None:
+ parser = self.parser
+ self.__options = self.parse_args(parser, args)
+
+ setproctitle('deluge-%s' % self.__name)
+
+ log.info('Deluge ui %s', deluge.common.get_version())
+ log.debug('options: %s', self.__options)
+ log.info('Starting %s ui..', self.__name)
diff --git a/deluge/ui/ui_entry.py b/deluge/ui/ui_entry.py
new file mode 100644
index 0000000..71ce837
--- /dev/null
+++ b/deluge/ui/ui_entry.py
@@ -0,0 +1,143 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2007 Andrew Resch <andrewresch@gmail.com>
+# Copyright (C) 2010 Pedro Algarvio <pedro@algarvio.me>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+# The main starting point for the program. This function is called when the
+# user runs the command 'deluge'.
+
+"""Main starting point for Deluge"""
+from __future__ import unicode_literals
+
+import argparse
+import logging
+import os
+import sys
+
+import pkg_resources
+
+import deluge.common
+import deluge.configmanager
+from deluge.argparserbase import ArgParserBase
+from deluge.i18n import setup_translation
+
+DEFAULT_PREFS = {'default_ui': 'gtk'}
+
+AMBIGUOUS_CMD_ARGS = ('-h', '--help', '-v', '-V', '--version')
+
+
+def start_ui():
+ """Entry point for ui script"""
+ setup_translation()
+
+ # Get the registered UI entry points
+ ui_entrypoints = {}
+ for entrypoint in pkg_resources.iter_entry_points('deluge.ui'):
+ try:
+ ui_entrypoints[entrypoint.name] = entrypoint.load()
+ except ImportError:
+ # Unable to load entrypoint so skip adding it.
+ pass
+
+ ui_titles = sorted(ui_entrypoints)
+
+ def add_ui_options_group(_parser):
+ """Function to enable reuse of UI Options group"""
+ group = _parser.add_argument_group(_('UI Options'))
+ group.add_argument(
+ '-s',
+ '--set-default-ui',
+ dest='default_ui',
+ choices=ui_titles,
+ help=_('Set the default UI to be run, when no UI is specified'),
+ )
+ return _parser
+
+ # Setup parser with Common Options and add UI Options group.
+ parser = add_ui_options_group(ArgParserBase())
+
+ # Parse and handle common/process group options
+ options = parser.parse_known_ui_args(sys.argv, withhold=AMBIGUOUS_CMD_ARGS)
+
+ config = deluge.configmanager.ConfigManager('ui.conf', DEFAULT_PREFS)
+ log = logging.getLogger(__name__)
+ log.info('Deluge ui %s', deluge.common.get_version())
+
+ if options.default_ui:
+ config['default_ui'] = options.default_ui
+ config.save()
+ log.info('The default UI has been changed to %s', options.default_ui)
+ sys.exit(0)
+
+ default_ui = config['default_ui']
+ config.save() # Save in case config didn't already exist.
+ del config
+
+ # We have parsed and got the config dir needed to get the default UI
+ # Now create a parser for choosing the UI. We reuse the ui option group for
+ # parsing to succeed and the text displayed to user, but result is not used.
+ parser = add_ui_options_group(ArgParserBase(common_help=True))
+
+ # Create subparser for each registered UI. Empty title is used to remove unwanted positional text.
+ subparsers = parser.add_subparsers(
+ dest='selected_ui',
+ metavar='{%s} [UI args]' % ','.join(ui_titles),
+ title=None,
+ help=_('Alternative UI to launch, with optional ui args \n (default UI: *)'),
+ )
+ for ui in ui_titles:
+ parser_ui = subparsers.add_parser(
+ ui,
+ common_help=False,
+ help=getattr(ui_entrypoints[ui], 'cmd_description', ''),
+ )
+ parser_ui.add_argument('ui_args', nargs=argparse.REMAINDER)
+ # If the UI is set as default, indicate this in help by prefixing with a star.
+ subactions = subparsers._get_subactions()
+ prefix = '*' if ui == default_ui else ' '
+ subactions[-1].metavar = '%s %s' % (prefix, ui)
+
+ # Insert a default UI subcommand unless one of the ambiguous_args are specified
+ parser.set_default_subparser(default_ui, abort_opts=AMBIGUOUS_CMD_ARGS)
+
+ # Only parse known arguments to leave the UIs to show a help message if parsing fails.
+ options, remaining = parser.parse_known_args()
+ selected_ui = options.selected_ui
+ ui_args = remaining + options.ui_args
+
+ # Remove the UI argument before launching the UI.
+ sys.argv.remove(selected_ui)
+
+ try:
+ ui = ui_entrypoints[selected_ui](
+ prog='%s %s' % (os.path.basename(sys.argv[0]), selected_ui), ui_args=ui_args
+ )
+ except KeyError:
+ log.error(
+ 'Unable to find chosen UI: "%s". Please choose a different UI '
+ 'or use "--set-default-ui" to change default UI.',
+ selected_ui,
+ )
+ except ImportError as ex:
+ import traceback
+
+ error_type, error_value, tb = sys.exc_info()
+ stack = traceback.extract_tb(tb)
+ last_frame = stack[-1]
+ if last_frame[0] == __file__:
+ log.error(
+ 'Unable to find chosen UI: "%s". Please choose a different UI '
+ 'or use "--set-default-ui" to change default UI.',
+ selected_ui,
+ )
+ else:
+ log.exception(ex)
+ log.error('Encountered an error launching the request UI: %s', selected_ui)
+ sys.exit(1)
+ else:
+ ui.start()
diff --git a/deluge/ui/web/__init__.py b/deluge/ui/web/__init__.py
new file mode 100644
index 0000000..0be7eed
--- /dev/null
+++ b/deluge/ui/web/__init__.py
@@ -0,0 +1,8 @@
+from __future__ import unicode_literals
+
+from deluge.ui.web.web import Web
+
+
+def start():
+ web = Web()
+ web.start()
diff --git a/deluge/ui/web/auth.py b/deluge/ui/web/auth.py
new file mode 100644
index 0000000..fa95049
--- /dev/null
+++ b/deluge/ui/web/auth.py
@@ -0,0 +1,257 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import hashlib
+import logging
+import os
+import time
+from datetime import datetime, timedelta
+from email.utils import formatdate
+
+from twisted.internet.task import LoopingCall
+
+from deluge.common import AUTH_LEVEL_ADMIN, AUTH_LEVEL_NONE
+from deluge.error import NotAuthorizedError
+from deluge.ui.web.json_api import JSONComponent, export
+
+log = logging.getLogger(__name__)
+
+
+def make_checksum(session_id):
+ checksum = 0
+ for value in [ord(char) for char in session_id]:
+ checksum += value
+ return checksum
+
+
+def get_session_id(session_id):
+ """
+ Checks a session id against its checksum
+ """
+ if not session_id:
+ return None
+
+ try:
+ checksum = int(session_id[-4:])
+ session_id = session_id[:-4]
+
+ if checksum == make_checksum(session_id):
+ return session_id
+ return None
+ except Exception as ex:
+ log.exception(ex)
+ return None
+
+
+def make_expires(timeout):
+ dt = timedelta(seconds=timeout)
+ expires = time.mktime((datetime.now() + dt).timetuple())
+ expires_str = formatdate(timeval=expires, localtime=False, usegmt=True)
+ return expires, expires_str
+
+
+class Auth(JSONComponent):
+ """
+ The component that implements authentification into the JSON interface.
+ """
+
+ def __init__(self, config):
+ super(Auth, self).__init__('Auth')
+ self.worker = LoopingCall(self._clean_sessions)
+ self.config = config
+
+ def start(self):
+ self.worker.start(5)
+
+ def stop(self):
+ self.worker.stop()
+
+ def _clean_sessions(self):
+ now = time.gmtime()
+ for session_id in list(self.config['sessions']):
+ session = self.config['sessions'][session_id]
+
+ if 'expires' not in session:
+ del self.config['sessions'][session_id]
+ continue
+
+ if time.gmtime(session['expires']) < now:
+ del self.config['sessions'][session_id]
+ continue
+
+ def _create_session(self, request, login='admin'):
+ """
+ Creates a new session.
+
+ :param login: the username of the user logging in, currently \
+ only for future use currently.
+ :type login: string
+ """
+ m = hashlib.sha256()
+ m.update(os.urandom(32))
+ session_id = m.hexdigest()
+
+ expires, expires_str = make_expires(self.config['session_timeout'])
+ checksum = str(make_checksum(session_id))
+
+ request.addCookie(
+ b'_session_id',
+ session_id + checksum,
+ path=request.base + b'json',
+ expires=expires_str,
+ )
+
+ log.debug('Creating session for %s', login)
+
+ if isinstance(self.config['sessions'], list):
+ self.config['sessions'] = {}
+
+ self.config['sessions'][session_id] = {
+ 'login': login,
+ 'level': AUTH_LEVEL_ADMIN,
+ 'expires': expires,
+ }
+ return True
+
+ def check_password(self, password):
+ config = self.config
+ if 'pwd_sha1' not in config.config:
+ log.debug('Failed to find config login details.')
+ return False
+
+ s = hashlib.sha1()
+ s.update(config['pwd_salt'].encode('utf8'))
+ s.update(password.encode('utf8'))
+ return s.hexdigest() == config['pwd_sha1']
+
+ def check_request(self, request, method=None, level=None):
+ """
+ Check to ensure that a request is authorised to call the specified
+ method of authentication level.
+
+ :param request: The HTTP request in question
+ :type request: twisted.web.http.Request
+ :param method: Check the specified method
+ :type method: function
+ :param level: Check the specified auth level
+ :type level: integer
+
+ :raises: Exception
+ """
+ cookie_sess_id = request.getCookie(b'_session_id')
+ if cookie_sess_id:
+ session_id = get_session_id(cookie_sess_id.decode())
+ else:
+ session_id = None
+
+ if session_id not in self.config['sessions']:
+ auth_level = AUTH_LEVEL_NONE
+ session_id = None
+ else:
+ session = self.config['sessions'][session_id]
+ auth_level = session['level']
+ expires, expires_str = make_expires(self.config['session_timeout'])
+ session['expires'] = expires
+
+ _session_id = request.getCookie(b'_session_id')
+ request.addCookie(
+ b'_session_id',
+ _session_id,
+ path=request.base + b'json',
+ expires=expires_str.encode('utf8'),
+ )
+
+ if method:
+ if not hasattr(method, '_json_export'):
+ raise Exception('Not an exported method')
+
+ method_level = getattr(method, '_json_auth_level')
+ if method_level is None:
+ raise Exception('Method has no auth level')
+
+ level = method_level
+
+ if level is None:
+ raise Exception('No level specified to check against')
+
+ request.auth_level = auth_level
+ request.session_id = session_id
+
+ if auth_level < level:
+ raise NotAuthorizedError(auth_level, level)
+
+ def _change_password(self, new_password):
+ """
+ Change the password. This is to allow the UI to change/reset a
+ password.
+
+ :param new_password: the password to change to
+ :type new_password: string
+ """
+ log.debug('Changing password')
+ salt = hashlib.sha1(os.urandom(32)).hexdigest()
+ s = hashlib.sha1(salt.encode('utf-8'))
+ s.update(new_password.encode('utf8'))
+ self.config['pwd_salt'] = salt
+ self.config['pwd_sha1'] = s.hexdigest()
+ return True
+
+ @export
+ def change_password(self, old_password, new_password):
+ """
+ Change the password.
+
+ :param old_password: the current password
+ :type old_password: string
+ :param new_password: the password to change to
+ :type new_password: string
+ """
+ if not self.check_password(old_password):
+ return False
+ return self._change_password(new_password)
+
+ @export(AUTH_LEVEL_NONE)
+ def check_session(self, session_id=None):
+ """
+ Check a session to see if it's still valid.
+
+ :returns: True if the session is valid, False if not.
+ :rtype: booleon
+ """
+ return __request__.session_id is not None
+
+ @export
+ def delete_session(self):
+ """
+ Removes a session.
+
+ :param session_id: the id for the session to remove
+ :type session_id: string
+ """
+ del self.config['sessions'][__request__.session_id]
+ return True
+
+ @export(AUTH_LEVEL_NONE)
+ def login(self, password):
+ """
+ Test a password to see if it's valid.
+
+ :param password: the password to test
+ :type password: string
+ :returns: a session id or False
+ :rtype: string or False
+ """
+ if self.check_password(password):
+ log.info('Login success (ClientIP %s)', __request__.getClientIP())
+ return self._create_session(__request__)
+ else:
+ log.error('Login failed (ClientIP %s)', __request__.getClientIP())
+ return False
diff --git a/deluge/ui/web/common.py b/deluge/ui/web/common.py
new file mode 100644
index 0000000..475f335
--- /dev/null
+++ b/deluge/ui/web/common.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import gettext
+
+from mako.template import Template as MakoTemplate
+
+from deluge.common import PY2, get_version
+
+
+def _(text):
+ text_local = gettext.gettext(text)
+ if PY2:
+ return text_local.decode('utf-8')
+ return text_local
+
+
+def escape(text):
+ """
+ Used by gettext.js template to escape any translated language strings that
+ might contain newlines or quotes as they would break the script.
+ """
+ text = text.replace("'", "\\'")
+ text = text.replace('\r\n', '\\n')
+ text = text.replace('\r', '\\n')
+ text = text.replace('\n', '\\n')
+ return text
+
+
+class Template(MakoTemplate):
+ """
+ A template that adds some built-ins to the rendering
+ """
+
+ builtins = {'_': _, 'escape': escape, 'version': get_version()}
+
+ def render(self, *args, **data):
+ data.update(self.builtins)
+ rendered = MakoTemplate.render_unicode(self, *args, **data)
+ return rendered.encode('utf-8')
diff --git a/deluge/ui/web/css/deluge.css b/deluge/ui/web/css/deluge.css
new file mode 100644
index 0000000..b9fa03e
--- /dev/null
+++ b/deluge/ui/web/css/deluge.css
@@ -0,0 +1,568 @@
+html,
+body {
+ font: normal 11px arial, tahoma, helvetica, sans-serif;
+ margin: 0;
+ padding: 0;
+ border: 0 none;
+ overflow: hidden;
+ height: 100%;
+}
+
+input {
+ color: Black;
+}
+
+/* remove dotted line on buttons in Firefox */
+button::-moz-focus-inner {
+ border: 0;
+}
+
+.x-deluge-main-panel {
+ background-image: url('../icons/deluge.png') !important;
+}
+
+.x-deluge-logo {
+ background-image: url('../ui_images/deluge-about.png');
+}
+
+#tbar-deluge-text * {
+ color: black !important;
+ font-weight: bold;
+}
+
+#tbar-deluge-text {
+ opacity: 1 !important;
+}
+
+.deluge-torrents td,
+.x-deluge-peers td {
+ height: 16px;
+ line-height: 16px;
+}
+
+.deluge-torrents .torrent-name,
+.x-deluge-peer,
+.x-deluge-seed {
+ padding-left: 20px;
+ background-repeat: no-repeat;
+}
+
+.deluge-torrents .deluge-torrent-progress {
+ text-align: center;
+}
+
+/* Icon classes */
+.x-deluge-all {
+ background-image: url('../icons/all.png');
+}
+.x-deluge-active {
+ background-image: url('../icons/active.png');
+}
+.x-deluge-downloading,
+.x-btn .x-deluge-downloading,
+.x-deluge-peer {
+ background-image: url('../icons/downloading.png');
+}
+.x-deluge-seeding,
+.x-btn .x-deluge-seeding,
+.deluge-torrents .seeding,
+.x-deluge-seed {
+ background-image: url('../icons/seeding.png');
+}
+.x-deluge-queued,
+.x-btn .x-deluge-queued,
+.deluge-torrents .queued {
+ background-image: url('../icons/queued.png');
+}
+.x-deluge-paused,
+.x-btn .x-deluge-paused,
+.deluge-torrents .paused {
+ background-image: url('../icons/inactive.png');
+}
+.x-deluge-error,
+.deluge-torrents .error {
+ background-image: url('../icons/alert.png');
+}
+.x-deluge-checking,
+.deluge-torrents .checking {
+ background-image: url('../icons/checking.png');
+}
+.x-deluge-dht,
+.x-btn .x-deluge-dht {
+ background-image: url('../icons/dht.png');
+}
+.x-deluge-preferences,
+.x-btn .x-deluge-preferences {
+ background-image: url('../icons/preferences.png');
+}
+.x-deluge-connections,
+.x-btn .x-deluge-connections {
+ background-image: url('../icons/connections.png');
+}
+.x-deluge-connection-manager,
+.x-btn .x-deluge-connection-manager {
+ background-image: url('../icons/connection_manager.png');
+}
+.x-deluge-traffic,
+.x-btn .x-deluge-traffic {
+ background-image: url('../icons/traffic.png');
+}
+.x-deluge-edit-trackers,
+.x-btn .x-deluge-edit-trackers {
+ background-image: url('../icons/edit_trackers.png');
+}
+.x-deluge-freespace,
+.x-btn .x-deluge-freespace {
+ background-image: url('../icons/drive.png');
+}
+
+.x-deluge-install-plugin,
+.x-btn .x-deluge-install-plugin {
+ background-image: url('../icons/install_plugin.png');
+}
+.x-deluge-find-more,
+.x-btn .x-deluge-find-more {
+ background-image: url('../icons/find_more.png');
+}
+
+/* Torrent Details */
+#torrentDetails dl,
+#torrentDetails dl.singleline {
+ float: left;
+ min-height: 120px;
+}
+
+#torrentDetails dl dt,
+dl.singleline dt {
+ float: left;
+ font-weight: bold;
+ height: 19px;
+}
+
+#torrentDetails dl dd,
+dl.singleline dd {
+ margin-left: 100px;
+ width: 140px;
+ height: 19px;
+}
+
+dl.singleline {
+ float: left;
+}
+
+dl.singleline dt {
+ width: 80px;
+}
+
+dl.singleline dd {
+ margin-left: 85px;
+ width: auto;
+}
+
+.x-deluge-plugins {
+ background: White;
+}
+
+/* Torrent Details - Status Tab */
+.x-deluge-status-progressbar {
+ margin: 5px;
+}
+
+.x-deluge-status {
+ margin: 10px;
+}
+
+.x-deluge-status dd.downloaded,
+.x-deluge-status dd.uploaded,
+.x-deluge-status dd.share,
+.x-deluge-status dd.announce,
+.x-deluge-status dd.tracker_status {
+ width: 200px;
+ margin-left: 100px;
+}
+
+.x-deluge-status dd.downspeed,
+.x-deluge-status dd.upspeed,
+.x-deluge-status dd.eta,
+.x-deluge-status dd.pieces {
+ margin-left: 100px;
+}
+
+.x-deluge-status dd.active_time,
+.x-deluge-status dd.seeding_time,
+.x-deluge-status dd.seed_rank,
+.x-deluge-status dd.time_added {
+ width: 100px;
+}
+.x-deluge-status dd.last_seen_complete {
+ width: 100px;
+}
+
+/* Torrent Details - Details Tab */
+#torrentDetails dd.torrent_name,
+#torrentDetails dd.status,
+#torrentDetails dd.tracker,
+#torrentDetails dd.path,
+#torrentDetails dd.comment {
+ width: auto;
+}
+
+.detailsPanel .x-panel-header {
+ height: 0;
+ padding: 0;
+ border: 0;
+}
+
+.detailsPanel .x-tool {
+ height: 15px;
+ z-index: 1;
+ position: fixed;
+ right: 0;
+ margin: 5px 10px;
+}
+
+/* Login Window */
+.x-deluge-login-window-icon {
+ background: url('../icons/login.png') no-repeat 2px;
+}
+
+/* Remove Window */
+.x-deluge-remove-window-icon {
+ background: url('../icons/remove.png') no-repeat 2px;
+}
+
+/* Add Window */
+.x-deluge-add-window-icon {
+ background: url('../icons/add.png') no-repeat 2px;
+}
+
+.x-deluge-add-torrent-name {
+ line-height: 20px;
+}
+
+.x-deluge-add-torrent-name-loading {
+ padding-left: 20px;
+ line-height: 20px;
+ background: url('/themes/default/tree/loading.gif') no-repeat 2px;
+}
+
+/* Add Url Window */
+.x-deluge-add-file,
+.x-btn .x-deluge-add-file {
+ background: url('../icons/add_file.png') no-repeat 2px;
+}
+
+.x-deluge-add-url-window-icon {
+ background: url('../icons/add_url.png') no-repeat 2px;
+}
+
+/* Connect Window */
+.x-deluge-connect-window-icon {
+ background: url('../icons/connection_manager.png') no-repeat 2px;
+}
+
+/* Statusbar */
+.x-deluge-statusbar {
+ background: no-repeat 2px !important;
+ padding-left: 20px !important;
+}
+
+.x-not-connected {
+ background-image: url('../icons/error.png') !important;
+}
+
+.x-connected {
+ background-image: none !important;
+}
+
+/* Styles for renderered progress bars */
+.x-progress-renderered .x-progress-bar {
+ height: 16px;
+}
+
+.x-progress-renderered .x-progress-bar .x-progress-text {
+ margin-top: -1px;
+ height: 18px;
+}
+
+/* Adjust progressbar for torrent files tree */
+.x-tree .x-progress-wrap {
+ width: 100px;
+}
+
+.x-tree .x-progress-renderered .x-progress-inner {
+ height: 12px;
+}
+
+.x-tree .x-progress-renderered .x-progress-bar {
+ height: 12px;
+}
+
+.x-tree .x-progress-renderered .x-progress-text {
+ vertical-align: top;
+ height: 12px;
+ font-size: 11px;
+ font-weight: normal;
+ padding: 0px;
+ margin-top: 0px;
+}
+
+.x-tree .x-progress-renderered .x-progress-bar .x-progress-text {
+ margin-top: 0px;
+}
+
+/* Files TreeGrid */
+.x-treegrid-root-table {
+ border-right: 1px solid;
+}
+
+.x-treegrid-root-node {
+ overflow: auto;
+}
+
+.x-treegrid-hd-hidden {
+ visibility: hidden;
+ border: 0;
+ width: 0;
+}
+
+.x-treegrid-col {
+ border-bottom: 1px solid;
+ height: 20px;
+ overflow: hidden;
+ vertical-align: top;
+ -o-text-overflow: ellipsis;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.x-treegrid-text {
+ padding-left: 4px;
+ -moz-user-select: none;
+ -khtml-user-select: none;
+}
+
+.x-treegrid-resizer {
+ border-left: 1px solid;
+ border-right: 1px solid;
+ position: absolute;
+ left: 0;
+ top: 0;
+}
+
+.x-treegrid-header-inner {
+ overflow: hidden;
+}
+
+.x-treegrid-root-table,
+.x-treegrid-col {
+ border-color: #ededed;
+}
+
+.x-treegrid-resizer {
+ border-left-color: #555;
+ border-right-color: #555;
+}
+
+/* Options Tab Styles */
+.x-deluge-options-label {
+ margin-right: 10px;
+}
+
+.x-deluge-indent-checkbox {
+ padding-left: 10px;
+}
+
+/* Sidebar styles */
+#sidebar .x-grid3-col-filter {
+ height: 16px;
+ line-height: 16px;
+ padding: 2px;
+ cursor: pointer;
+}
+
+#sidebar .x-deluge-filter {
+ background-repeat: no-repeat;
+ background-size: contain;
+ background-position: left;
+ padding-left: 20px;
+ line-height: 16px;
+}
+
+#sidebar .x-list-selected em {
+ font-weight: bold;
+}
+
+/* MessageBox icon styles */
+.x-deluge-icon-warning {
+ background: url('../icons/warning.png') no-repeat 2px;
+}
+
+.x-deluge-icon-error {
+ background: url('../icons/error.png') no-repeat 2px;
+}
+
+.x-tree-node-leaf .x-deluge-file {
+ background-image: url('../icons/document.png');
+}
+
+.x-deluge-add-file-checkbox {
+ margin-top: 2px;
+}
+
+/* Filepriority styles */
+.x-no-download,
+.x-low-download,
+.x-normal-download,
+.x-high-download,
+.x-mixed-download {
+ padding-left: 20px;
+ background-repeat: no-repeat;
+ line-height: 16px;
+}
+
+.x-no-download {
+ background-image: url(../icons/no_download.png);
+}
+
+.x-low-download {
+ background-image: url(../icons/low.png);
+}
+
+.x-normal-download {
+ background-image: url(../icons/normal.png);
+}
+
+.x-high-download {
+ background-image: url(../icons/high.png);
+}
+
+.x-mixed-download {
+ /*background-image: url(../icons/mixed.png);*/
+}
+
+/**
+ * Deluge Default Icon Set
+ * n.b. this needs to be forked out at some point
+ */
+
+.icon-create {
+ background-image: url('../icons/create.png') !important;
+}
+
+.icon-add {
+ background-image: url('../icons/add.png') !important;
+}
+
+.icon-add-url {
+ background-image: url('../icons/add_url.png') !important;
+}
+
+.icon-add-magnet {
+ background-image: url('../icons/add_magnet.png') !important;
+}
+
+.icon-pause {
+ background-image: url('../icons/pause.png') !important;
+}
+
+.icon-resume {
+ background-image: url('../icons/start.png') !important;
+}
+
+.icon-options {
+ background-image: url('../icons/preferences.png') !important;
+}
+
+.icon-queue {
+ background-image: url('../icons/queue.png') !important;
+}
+
+.icon-top {
+ background-image: url('../icons/top.png') !important;
+}
+
+.icon-up {
+ background-image: url('../icons/up.png') !important;
+}
+
+.icon-down {
+ background-image: url('../icons/down.png') !important;
+}
+
+.icon-bottom {
+ background-image: url('../icons/bottom.png') !important;
+}
+
+.icon-update-tracker {
+ background-image: url('../icons/update.png') !important;
+}
+
+.icon-edit-trackers,
+.icon-edit {
+ background-image: url('../icons/edit_trackers.png') !important;
+}
+
+.icon-remove {
+ background-image: url('../icons/remove.png') !important;
+}
+
+.icon-recheck {
+ background-image: url('../icons/recheck.png') !important;
+}
+
+.icon-move {
+ background-image: url('../icons/move.png') !important;
+}
+
+.icon-help {
+ background-image: url('../icons/help.png') !important;
+}
+
+.icon-logout {
+ background-image: url('../icons/logout.png') !important;
+}
+
+.icon-back {
+ background-image: url('../icons/back.png') !important;
+}
+
+.icon-forward {
+ background-image: url('../icons/forward.png') !important;
+}
+
+.icon-home {
+ background-image: url('../icons/home.png') !important;
+}
+
+.icon-ok {
+ background-image: url('../icons/ok.png') !important;
+}
+
+.icon-error {
+ background-image: url('../icons/error.png') !important;
+}
+
+.icon-upload-slots {
+ background-image: url('../icons/upload_slots.png') !important;
+}
+
+.icon-expand-all {
+ background-image: url('../icons/expand_all.png') !important;
+}
+
+.icon-do-not-download {
+ background-image: url('../icons/no_download.png') !important;
+}
+
+.icon-low {
+ background-image: url('../icons/low.png') !important;
+}
+
+.icon-normal {
+ background-image: url('../icons/normal.png') !important;
+}
+
+.icon-high {
+ background-image: url('../icons/high.png') !important;
+}
diff --git a/deluge/ui/web/css/ext-all-notheme.css b/deluge/ui/web/css/ext-all-notheme.css
new file mode 100644
index 0000000..40bac82
--- /dev/null
+++ b/deluge/ui/web/css/ext-all-notheme.css
@@ -0,0 +1,5349 @@
+/*
+This file is part of Ext JS 3.4
+
+Copyright (c) 2011-2013 Sencha Inc
+
+Contact: http://www.sencha.com/contact
+
+GNU General Public License Usage
+This file may be used under the terms of the GNU General Public License version 3.0 as
+published by the Free Software Foundation and appearing in the file LICENSE included in the
+packaging of this file.
+
+Please review the following information to ensure the GNU General Public License version 3.0
+requirements will be met: http://www.gnu.org/copyleft/gpl.html.
+
+If you are unsure which license is appropriate for your use, please contact the sales department
+at http://www.sencha.com/contact.
+
+Build date: 2013-04-03 15:07:25
+*/
+html,body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,form,fieldset,input,p,blockquote,th,td{margin:0;padding:0;}img,body,html{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}ol,ul {list-style:none;}caption,th {text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;}q:before,q:after{content:'';}
+
+.ext-forced-border-box, .ext-forced-border-box * {
+ -moz-box-sizing: border-box;
+ -ms-box-sizing: border-box;
+ -webkit-box-sizing: border-box;
+}
+.ext-el-mask {
+ z-index: 100;
+ position: absolute;
+ top:0;
+ left:0;
+ -moz-opacity: 0.5;
+ opacity: .50;
+ filter: alpha(opacity=50);
+ width: 100%;
+ height: 100%;
+ zoom: 1;
+}
+
+.ext-el-mask-msg {
+ z-index: 20001;
+ position: absolute;
+ top: 0;
+ left: 0;
+ border:1px solid;
+ background:repeat-x 0 -16px;
+ padding:2px;
+}
+
+.ext-el-mask-msg div {
+ padding:5px 10px 5px 10px;
+ border:1px solid;
+ cursor:wait;
+}
+
+.ext-shim {
+ position:absolute;
+ visibility:hidden;
+ left:0;
+ top:0;
+ overflow:hidden;
+}
+
+.ext-ie .ext-shim {
+ filter: alpha(opacity=0);
+}
+
+.ext-ie6 .ext-shim {
+ margin-left: 5px;
+ margin-top: 3px;
+}
+
+.x-mask-loading div {
+ padding:5px 10px 5px 25px;
+ background:no-repeat 5px 5px;
+ line-height:16px;
+}
+
+/* class for hiding elements without using display:none */
+.x-hidden, .x-hide-offsets {
+ position:absolute !important;
+ left:-10000px;
+ top:-10000px;
+ visibility:hidden;
+}
+
+.x-hide-display {
+ display:none !important;
+}
+
+.x-hide-nosize,
+.x-hide-nosize * /* Emulate display:none for children */
+ {
+ height:0px!important;
+ width:0px!important;
+ visibility:hidden!important;
+ border:none!important;
+ zoom:1;
+}
+
+.x-hide-visibility {
+ visibility:hidden !important;
+}
+
+.x-masked {
+ overflow: hidden !important;
+}
+.x-masked-relative {
+ position: relative !important;
+}
+
+.x-masked select, .x-masked object, .x-masked embed {
+ visibility: hidden;
+}
+
+.x-layer {
+ visibility: hidden;
+}
+
+.x-unselectable, .x-unselectable * {
+ user-select: none;
+ -o-user-select: none;
+ -ms-user-select: none;
+ -moz-user-select: -moz-none;
+ -webkit-user-select: none;
+ cursor:default;
+}
+
+.x-repaint {
+ zoom: 1;
+ background-color: transparent;
+ -moz-outline: none;
+ outline: none;
+}
+
+.x-item-disabled {
+ cursor: default;
+ opacity: .6;
+ -moz-opacity: .6;
+ filter: alpha(opacity=60);
+}
+
+.x-item-disabled * {
+ cursor: default !important;
+}
+
+.x-form-radio-group .x-item-disabled {
+ filter: none;
+}
+
+.x-splitbar-proxy {
+ position: absolute;
+ visibility: hidden;
+ z-index: 20001;
+ zoom: 1;
+ line-height: 1px;
+ font-size: 1px;
+ overflow: hidden;
+}
+
+.x-splitbar-h, .x-splitbar-proxy-h {
+ cursor: e-resize;
+ cursor: col-resize;
+}
+
+.x-splitbar-v, .x-splitbar-proxy-v {
+ cursor: s-resize;
+ cursor: row-resize;
+}
+
+.x-color-palette {
+ width: 150px;
+ height: 92px;
+ cursor: pointer;
+}
+
+.x-color-palette a {
+ border: 1px solid;
+ float: left;
+ padding: 2px;
+ text-decoration: none;
+ -moz-outline: 0 none;
+ outline: 0 none;
+ cursor: pointer;
+}
+
+.x-color-palette a:hover, .x-color-palette a.x-color-palette-sel {
+ border: 1px solid;
+}
+
+.x-color-palette em {
+ display: block;
+ border: 1px solid;
+}
+
+.x-color-palette em span {
+ cursor: pointer;
+ display: block;
+ height: 10px;
+ line-height: 10px;
+ width: 10px;
+}
+
+.x-ie-shadow {
+ display: none;
+ position: absolute;
+ overflow: hidden;
+ left:0;
+ top:0;
+ zoom:1;
+}
+
+.x-shadow {
+ display: none;
+ position: absolute;
+ overflow: hidden;
+ left:0;
+ top:0;
+}
+
+.x-shadow * {
+ overflow: hidden;
+}
+
+.x-shadow * {
+ padding: 0;
+ border: 0;
+ margin: 0;
+ clear: none;
+ zoom: 1;
+}
+
+/* top bottom */
+.x-shadow .xstc, .x-shadow .xsbc {
+ height: 6px;
+ float: left;
+}
+
+/* corners */
+.x-shadow .xstl, .x-shadow .xstr, .x-shadow .xsbl, .x-shadow .xsbr {
+ width: 6px;
+ height: 6px;
+ float: left;
+}
+
+/* sides */
+.x-shadow .xsc {
+ width: 100%;
+}
+
+.x-shadow .xsml, .x-shadow .xsmr {
+ width: 6px;
+ float: left;
+ height: 100%;
+}
+
+.x-shadow .xsmc {
+ float: left;
+ height: 100%;
+ background-color: transparent;
+}
+
+.x-shadow .xst, .x-shadow .xsb {
+ height: 6px;
+ overflow: hidden;
+ width: 100%;
+}
+
+.x-shadow .xsml {
+ background: transparent repeat-y 0 0;
+}
+
+.x-shadow .xsmr {
+ background: transparent repeat-y -6px 0;
+}
+
+.x-shadow .xstl {
+ background: transparent no-repeat 0 0;
+}
+
+.x-shadow .xstc {
+ background: transparent repeat-x 0 -30px;
+}
+
+.x-shadow .xstr {
+ background: transparent repeat-x 0 -18px;
+}
+
+.x-shadow .xsbl {
+ background: transparent no-repeat 0 -12px;
+}
+
+.x-shadow .xsbc {
+ background: transparent repeat-x 0 -36px;
+}
+
+.x-shadow .xsbr {
+ background: transparent repeat-x 0 -6px;
+}
+
+.loading-indicator {
+ background: no-repeat left;
+ padding-left: 20px;
+ line-height: 16px;
+ margin: 3px;
+}
+
+.x-text-resize {
+ position: absolute;
+ left: -1000px;
+ top: -1000px;
+ visibility: hidden;
+ zoom: 1;
+}
+
+.x-drag-overlay {
+ width: 100%;
+ height: 100%;
+ display: none;
+ position: absolute;
+ left: 0;
+ top: 0;
+ background-image:url(../images/default/s.gif);
+ z-index: 20000;
+}
+
+.x-clear {
+ clear:both;
+ height:0;
+ overflow:hidden;
+ line-height:0;
+ font-size:0;
+}
+
+.x-spotlight {
+ z-index: 8999;
+ position: absolute;
+ top:0;
+ left:0;
+ -moz-opacity: 0.5;
+ opacity: .50;
+ filter: alpha(opacity=50);
+ width:0;
+ height:0;
+ zoom: 1;
+}
+
+#x-history-frame {
+ position:absolute;
+ top:-1px;
+ left:0;
+ width:1px;
+ height:1px;
+ visibility:hidden;
+}
+
+#x-history-field {
+ position:absolute;
+ top:0;
+ left:-1px;
+ width:1px;
+ height:1px;
+ visibility:hidden;
+}
+.x-resizable-handle {
+ position:absolute;
+ z-index:100;
+ /* ie needs these */
+ font-size:1px;
+ line-height:6px;
+ overflow:hidden;
+ filter:alpha(opacity=0);
+ opacity:0;
+ zoom:1;
+}
+
+.x-resizable-handle-east{
+ width:6px;
+ cursor:e-resize;
+ right:0;
+ top:0;
+ height:100%;
+}
+
+.ext-ie .x-resizable-handle-east {
+ margin-right:-1px; /*IE rounding error*/
+}
+
+.x-resizable-handle-south{
+ width:100%;
+ cursor:s-resize;
+ left:0;
+ bottom:0;
+ height:6px;
+}
+
+.ext-ie .x-resizable-handle-south {
+ margin-bottom:-1px; /*IE rounding error*/
+}
+
+.x-resizable-handle-west{
+ width:6px;
+ cursor:w-resize;
+ left:0;
+ top:0;
+ height:100%;
+}
+
+.x-resizable-handle-north{
+ width:100%;
+ cursor:n-resize;
+ left:0;
+ top:0;
+ height:6px;
+}
+
+.x-resizable-handle-southeast{
+ width:6px;
+ cursor:se-resize;
+ right:0;
+ bottom:0;
+ height:6px;
+ z-index:101;
+}
+
+.x-resizable-handle-northwest{
+ width:6px;
+ cursor:nw-resize;
+ left:0;
+ top:0;
+ height:6px;
+ z-index:101;
+}
+
+.x-resizable-handle-northeast{
+ width:6px;
+ cursor:ne-resize;
+ right:0;
+ top:0;
+ height:6px;
+ z-index:101;
+}
+
+.x-resizable-handle-southwest{
+ width:6px;
+ cursor:sw-resize;
+ left:0;
+ bottom:0;
+ height:6px;
+ z-index:101;
+}
+
+.x-resizable-over .x-resizable-handle, .x-resizable-pinned .x-resizable-handle{
+ filter:alpha(opacity=100);
+ opacity:1;
+}
+
+.x-resizable-over .x-resizable-handle-east, .x-resizable-pinned .x-resizable-handle-east,
+.x-resizable-over .x-resizable-handle-west, .x-resizable-pinned .x-resizable-handle-west
+{
+ background-position: left;
+}
+
+.x-resizable-over .x-resizable-handle-south, .x-resizable-pinned .x-resizable-handle-south,
+.x-resizable-over .x-resizable-handle-north, .x-resizable-pinned .x-resizable-handle-north
+{
+ background-position: top;
+}
+
+.x-resizable-over .x-resizable-handle-southeast, .x-resizable-pinned .x-resizable-handle-southeast{
+ background-position: top left;
+}
+
+.x-resizable-over .x-resizable-handle-northwest, .x-resizable-pinned .x-resizable-handle-northwest{
+ background-position:bottom right;
+}
+
+.x-resizable-over .x-resizable-handle-northeast, .x-resizable-pinned .x-resizable-handle-northeast{
+ background-position: bottom left;
+}
+
+.x-resizable-over .x-resizable-handle-southwest, .x-resizable-pinned .x-resizable-handle-southwest{
+ background-position: top right;
+}
+
+.x-resizable-proxy{
+ border: 1px dashed;
+ position:absolute;
+ overflow:hidden;
+ display:none;
+ left:0;
+ top:0;
+ z-index:50000;
+}
+
+.x-resizable-overlay{
+ width:100%;
+ height:100%;
+ display:none;
+ position:absolute;
+ left:0;
+ top:0;
+ z-index:200000;
+ -moz-opacity: 0;
+ opacity:0;
+ filter: alpha(opacity=0);
+}
+.x-tab-panel {
+ overflow:hidden;
+}
+
+.x-tab-panel-header, .x-tab-panel-footer {
+ border: 1px solid;
+ overflow:hidden;
+ zoom:1;
+}
+
+.x-tab-panel-header {
+ border: 1px solid;
+ padding-bottom: 2px;
+}
+
+.x-tab-panel-footer {
+ border: 1px solid;
+ padding-top: 2px;
+}
+
+.x-tab-strip-wrap {
+ width:100%;
+ overflow:hidden;
+ position:relative;
+ zoom:1;
+}
+
+ul.x-tab-strip {
+ display:block;
+ width:5000px;
+ zoom:1;
+}
+
+ul.x-tab-strip-top{
+ padding-top: 1px;
+ background: repeat-x bottom;
+ border-bottom: 1px solid;
+}
+
+ul.x-tab-strip-bottom{
+ padding-bottom: 1px;
+ background: repeat-x top;
+ border-top: 1px solid;
+ border-bottom: 0 none;
+}
+
+.x-tab-panel-header-plain .x-tab-strip-top {
+ background:transparent !important;
+ padding-top:0 !important;
+}
+
+.x-tab-panel-header-plain {
+ background:transparent !important;
+ border-width:0 !important;
+ padding-bottom:0 !important;
+}
+
+.x-tab-panel-header-plain .x-tab-strip-spacer,
+.x-tab-panel-footer-plain .x-tab-strip-spacer {
+ border:1px solid;
+ height:2px;
+ font-size:1px;
+ line-height:1px;
+}
+
+.x-tab-panel-header-plain .x-tab-strip-spacer {
+ border-top: 0 none;
+}
+
+.x-tab-panel-footer-plain .x-tab-strip-spacer {
+ border-bottom: 0 none;
+}
+
+.x-tab-panel-footer-plain .x-tab-strip-bottom {
+ background:transparent !important;
+ padding-bottom:0 !important;
+}
+
+.x-tab-panel-footer-plain {
+ background:transparent !important;
+ border-width:0 !important;
+ padding-top:0 !important;
+}
+
+.ext-border-box .x-tab-panel-header-plain .x-tab-strip-spacer,
+.ext-border-box .x-tab-panel-footer-plain .x-tab-strip-spacer {
+ height:3px;
+}
+
+ul.x-tab-strip li {
+ float:left;
+ margin-left:2px;
+}
+
+ul.x-tab-strip li.x-tab-edge {
+ float:left;
+ margin:0 !important;
+ padding:0 !important;
+ border:0 none !important;
+ font-size:1px !important;
+ line-height:1px !important;
+ overflow:hidden;
+ zoom:1;
+ background:transparent !important;
+ width:1px;
+}
+
+.x-tab-strip a, .x-tab-strip span, .x-tab-strip em {
+ display:block;
+}
+
+.x-tab-strip a {
+ text-decoration:none !important;
+ -moz-outline: none;
+ outline: none;
+ cursor:pointer;
+}
+
+.x-tab-strip-inner {
+ overflow:hidden;
+ text-overflow: ellipsis;
+}
+
+.x-tab-strip span.x-tab-strip-text {
+ white-space: nowrap;
+ cursor:pointer;
+ padding:4px 0;
+}
+
+.x-tab-strip-top .x-tab-with-icon .x-tab-right {
+ padding-left:6px;
+}
+
+.x-tab-strip .x-tab-with-icon span.x-tab-strip-text {
+ padding-left:20px;
+ background-position: 0 3px;
+ background-repeat: no-repeat;
+}
+
+.x-tab-strip-active, .x-tab-strip-active a.x-tab-right {
+ cursor:default;
+}
+
+.x-tab-strip-active span.x-tab-strip-text {
+ cursor:default;
+}
+
+.x-tab-strip-disabled .x-tabs-text {
+ cursor:default;
+}
+
+.x-tab-panel-body {
+ overflow:hidden;
+}
+
+.x-tab-panel-bwrap {
+ overflow:hidden;
+}
+
+.ext-ie .x-tab-strip .x-tab-right {
+ position:relative;
+}
+
+.x-tab-strip-top .x-tab-strip-active .x-tab-right {
+ margin-bottom:-1px;
+}
+
+/*
+ * For IE8/9 in quirks mode
+ */
+.ext-ie8 .x-tab-strip li {
+ position: relative;
+}
+.ext-border-box .ext-ie8 .x-tab-strip-top .x-tab-right, .ext-border-box .ext-ie9 .x-tab-strip-top .x-tab-right {
+ top: 1px;
+}
+.ext-ie8 .x-tab-strip-top, .ext-ie9 .x-tab-strip-top {
+ padding-top: 1px;
+}
+.ext-border-box .ext-ie8 .x-tab-strip-top, .ext-border-box .ext-ie9 .x-tab-strip-top {
+ padding-top: 0;
+}
+.ext-ie8 .x-tab-strip .x-tab-strip-closable a.x-tab-strip-close, .ext-ie9 .x-tab-strip .x-tab-strip-closable a.x-tab-strip-close {
+ top:3px;
+}
+.ext-border-box .ext-ie8 .x-tab-strip .x-tab-strip-closable a.x-tab-strip-close,
+.ext-border-box .ext-ie9 .x-tab-strip .x-tab-strip-closable a.x-tab-strip-close {
+ top:4px;
+}
+.ext-ie8 .x-tab-strip-bottom .x-tab-right, .ext-ie9 .x-tab-strip-bottom .x-tab-right{
+ top:0;
+}
+
+
+.x-tab-strip-top .x-tab-strip-active .x-tab-right span.x-tab-strip-text {
+ padding-bottom:5px;
+}
+
+.x-tab-strip-bottom .x-tab-strip-active .x-tab-right {
+ margin-top:-1px;
+}
+
+.x-tab-strip-bottom .x-tab-strip-active .x-tab-right span.x-tab-strip-text {
+ padding-top:5px;
+}
+
+.x-tab-strip-top .x-tab-right {
+ background: transparent no-repeat 0 -51px;
+ padding-left:10px;
+}
+
+.x-tab-strip-top .x-tab-left {
+ background: transparent no-repeat right -351px;
+ padding-right:10px;
+}
+
+.x-tab-strip-top .x-tab-strip-inner {
+ background: transparent repeat-x 0 -201px;
+}
+
+.x-tab-strip-top .x-tab-strip-over .x-tab-right {
+ background-position:0 -101px;
+}
+
+.x-tab-strip-top .x-tab-strip-over .x-tab-left {
+ background-position:right -401px;
+}
+
+.x-tab-strip-top .x-tab-strip-over .x-tab-strip-inner {
+ background-position:0 -251px;
+}
+
+.x-tab-strip-top .x-tab-strip-active .x-tab-right {
+ background-position: 0 0;
+}
+
+.x-tab-strip-top .x-tab-strip-active .x-tab-left {
+ background-position: right -301px;
+}
+
+.x-tab-strip-top .x-tab-strip-active .x-tab-strip-inner {
+ background-position: 0 -151px;
+}
+
+.x-tab-strip-bottom .x-tab-right {
+ background: no-repeat bottom right;
+}
+
+.x-tab-strip-bottom .x-tab-left {
+ background: no-repeat bottom left;
+}
+
+.x-tab-strip-bottom .x-tab-strip-active .x-tab-right {
+ background: no-repeat bottom right;
+}
+
+.x-tab-strip-bottom .x-tab-strip-active .x-tab-left {
+ background: no-repeat bottom left;
+}
+
+.x-tab-strip-bottom .x-tab-left {
+ margin-right: 3px;
+ padding:0 10px;
+}
+
+.x-tab-strip-bottom .x-tab-right {
+ padding:0;
+}
+
+.x-tab-strip .x-tab-strip-close {
+ display:none;
+}
+
+.x-tab-strip-closable {
+ position:relative;
+}
+
+.x-tab-strip-closable .x-tab-left {
+ padding-right:19px;
+}
+
+.x-tab-strip .x-tab-strip-closable a.x-tab-strip-close {
+ opacity:.6;
+ -moz-opacity:.6;
+ background-repeat:no-repeat;
+ display:block;
+ width:11px;
+ height:11px;
+ position:absolute;
+ top:3px;
+ right:3px;
+ cursor:pointer;
+ z-index:2;
+}
+
+.x-tab-strip .x-tab-strip-active a.x-tab-strip-close {
+ opacity:.8;
+ -moz-opacity:.8;
+}
+.x-tab-strip .x-tab-strip-closable a.x-tab-strip-close:hover{
+ opacity:1;
+ -moz-opacity:1;
+}
+
+.x-tab-panel-body {
+ border: 1px solid;
+}
+
+.x-tab-panel-body-top {
+ border-top: 0 none;
+}
+
+.x-tab-panel-body-bottom {
+ border-bottom: 0 none;
+}
+
+.x-tab-scroller-left {
+ background: transparent no-repeat -18px 0;
+ border-bottom: 1px solid;
+ width:18px;
+ position:absolute;
+ left:0;
+ top:0;
+ z-index:10;
+ cursor:pointer;
+}
+.x-tab-scroller-left-over {
+ background-position: 0 0;
+}
+
+.x-tab-scroller-left-disabled {
+ background-position: -18px 0;
+ opacity:.5;
+ -moz-opacity:.5;
+ filter:alpha(opacity=50);
+ cursor:default;
+}
+
+.x-tab-scroller-right {
+ background: transparent no-repeat 0 0;
+ border-bottom: 1px solid;
+ width:18px;
+ position:absolute;
+ right:0;
+ top:0;
+ z-index:10;
+ cursor:pointer;
+}
+
+.x-tab-scroller-right-over {
+ background-position: -18px 0;
+}
+
+.x-tab-scroller-right-disabled {
+ background-position: 0 0;
+ opacity:.5;
+ -moz-opacity:.5;
+ filter:alpha(opacity=50);
+ cursor:default;
+}
+
+.x-tab-scrolling-bottom .x-tab-scroller-left, .x-tab-scrolling-bottom .x-tab-scroller-right{
+ margin-top: 1px;
+}
+
+.x-tab-scrolling .x-tab-strip-wrap {
+ margin-left:18px;
+ margin-right:18px;
+}
+
+.x-tab-scrolling {
+ position:relative;
+}
+
+.x-tab-panel-bbar .x-toolbar {
+ border:1px solid;
+ border-top:0 none;
+ overflow:hidden;
+ padding:2px;
+}
+
+.x-tab-panel-tbar .x-toolbar {
+ border:1px solid;
+ border-top:0 none;
+ overflow:hidden;
+ padding:2px;
+}/* all fields */
+.x-form-field{
+ margin: 0 0 0 0;
+}
+
+.ext-webkit *:focus{
+ outline: none !important;
+}
+
+/* ---- text fields ---- */
+.x-form-text, textarea.x-form-field{
+ padding:1px 3px;
+ background:repeat-x 0 0;
+ border:1px solid;
+}
+
+textarea.x-form-field {
+ padding:2px 3px;
+}
+
+.x-form-text, .ext-ie .x-form-file {
+ height:22px;
+ line-height:18px;
+ vertical-align:middle;
+}
+
+.ext-ie6 .x-form-text, .ext-ie7 .x-form-text {
+ margin:-1px 0; /* ie bogus margin bug */
+ height:22px; /* ie quirks */
+ line-height:18px;
+}
+
+.x-quirks .ext-ie9 .x-form-text {
+ height: 22px;
+ padding-top: 3px;
+ padding-bottom: 0px;
+}
+
+/* Ugly hacks for the bogus 1px margin bug in IE9 quirks */
+.x-quirks .ext-ie9 .x-input-wrapper .x-form-text,
+.x-quirks .ext-ie9 .x-form-field-trigger-wrap .x-form-text {
+ margin-top: -1px;
+ margin-bottom: -1px;
+}
+.x-quirks .ext-ie9 .x-input-wrapper .x-form-element {
+ margin-bottom: -1px;
+}
+
+.ext-ie6 .x-form-field-wrap .x-form-file-btn, .ext-ie7 .x-form-field-wrap .x-form-file-btn {
+ top: -1px; /* because of all these margin hacks, these buttons are off by one pixel in IE6,7 */
+}
+
+.ext-ie6 textarea.x-form-field, .ext-ie7 textarea.x-form-field {
+ margin:-1px 0; /* ie bogus margin bug */
+}
+
+.ext-strict .x-form-text {
+ height:18px;
+}
+
+.ext-safari.ext-mac textarea.x-form-field {
+ margin-bottom:-2px; /* another bogus margin bug, safari/mac only */
+}
+
+/*
+.ext-strict .ext-ie8 .x-form-text, .ext-strict .ext-ie8 textarea.x-form-field {
+ margin-bottom: 1px;
+}
+*/
+
+.ext-gecko .x-form-text , .ext-ie8 .x-form-text {
+ padding-top:2px; /* FF won't center the text vertically */
+ padding-bottom:0;
+}
+
+.ext-ie6 .x-form-composite .x-form-text.x-box-item, .ext-ie7 .x-form-composite .x-form-text.x-box-item {
+ margin: 0 !important; /* clear ie bogus margin bug fix */
+}
+
+textarea {
+ resize: none; /* Disable browser resizable textarea */
+}
+
+/* select boxes */
+.x-form-select-one {
+ height:20px;
+ line-height:18px;
+ vertical-align:middle;
+ border: 1px solid;
+}
+
+/* multi select boxes */
+
+/* --- TODO --- */
+
+/* 2.0.2 style */
+.x-form-check-wrap {
+ line-height:18px;
+ height: auto;
+}
+
+.ext-ie .x-form-check-wrap input {
+ width:15px;
+ height:15px;
+}
+
+.x-form-check-wrap input{
+ vertical-align: bottom;
+}
+
+.x-editor .x-form-check-wrap {
+ padding:3px;
+}
+
+.x-editor .x-form-checkbox {
+ height:13px;
+}
+
+.x-form-check-group-label {
+ border-bottom: 1px solid;
+ margin-bottom: 5px;
+ padding-left: 3px !important;
+ float: none !important;
+}
+
+/* wrapped fields and triggers */
+.x-form-field-wrap .x-form-trigger{
+ width:17px;
+ height:21px;
+ border:0;
+ background:transparent no-repeat 0 0;
+ cursor:pointer;
+ border-bottom: 1px solid;
+ position:absolute;
+ top:0;
+}
+
+.x-form-field-wrap .x-form-date-trigger, .x-form-field-wrap .x-form-clear-trigger, .x-form-field-wrap .x-form-search-trigger{
+ cursor:pointer;
+}
+
+.x-form-field-wrap .x-form-twin-triggers .x-form-trigger{
+ position:static;
+ top:auto;
+ vertical-align:top;
+}
+
+.x-form-field-wrap {
+ position:relative;
+ left:0;top:0;
+ text-align: left;
+ zoom:1;
+ white-space: nowrap;
+}
+
+.ext-strict .ext-ie8 .x-toolbar-cell .x-form-field-trigger-wrap .x-form-trigger {
+ right: 0; /* IE8 Strict mode trigger bug */
+}
+
+.x-form-field-wrap .x-form-trigger-over{
+ background-position:-17px 0;
+}
+
+.x-form-field-wrap .x-form-trigger-click{
+ background-position:-34px 0;
+}
+
+.x-trigger-wrap-focus .x-form-trigger{
+ background-position:-51px 0;
+}
+
+.x-trigger-wrap-focus .x-form-trigger-over{
+ background-position:-68px 0;
+}
+
+.x-trigger-wrap-focus .x-form-trigger-click{
+ background-position:-85px 0;
+}
+
+.x-trigger-wrap-focus .x-form-trigger{
+ border-bottom: 1px solid;
+}
+
+.x-item-disabled .x-form-trigger-over{
+ background-position:0 0 !important;
+ border-bottom: 1px solid;
+}
+
+.x-item-disabled .x-form-trigger-click{
+ background-position:0 0 !important;
+ border-bottom: 1px solid;
+}
+
+.x-trigger-noedit{
+ cursor:pointer;
+}
+
+/* field focus style */
+.x-form-focus, textarea.x-form-focus{
+ border: 1px solid;
+}
+
+/* invalid fields */
+.x-form-invalid, textarea.x-form-invalid{
+ background:repeat-x bottom;
+ border: 1px solid;
+}
+
+.x-form-inner-invalid, textarea.x-form-inner-invalid{
+ background:repeat-x bottom;
+}
+
+/* editors */
+.x-editor {
+ visibility:hidden;
+ padding:0;
+ margin:0;
+}
+
+.x-form-grow-sizer {
+ left: -10000px;
+ padding: 8px 3px;
+ position: absolute;
+ visibility:hidden;
+ top: -10000px;
+ white-space: pre-wrap;
+ white-space: -moz-pre-wrap;
+ white-space: -pre-wrap;
+ white-space: -o-pre-wrap;
+ word-wrap: break-word;
+ zoom:1;
+}
+
+.x-form-grow-sizer p {
+ margin:0 !important;
+ border:0 none !important;
+ padding:0 !important;
+}
+
+/* Form Items CSS */
+
+.x-form-item {
+ display:block;
+ margin-bottom:4px;
+ zoom:1;
+}
+
+.x-form-item label.x-form-item-label {
+ display:block;
+ float:left;
+ width:100px;
+ padding:3px;
+ padding-left:0;
+ clear:left;
+ z-index:2;
+ position:relative;
+}
+
+.x-form-element {
+ padding-left:105px;
+ position:relative;
+}
+
+.x-form-invalid-msg {
+ padding:2px;
+ padding-left:18px;
+ background: transparent no-repeat 0 2px;
+ line-height:16px;
+ width:200px;
+}
+
+.x-form-label-left label.x-form-item-label {
+ text-align:left;
+}
+
+.x-form-label-right label.x-form-item-label {
+ text-align:right;
+}
+
+.x-form-label-top .x-form-item label.x-form-item-label {
+ width:auto;
+ float:none;
+ clear:none;
+ display:inline;
+ margin-bottom:4px;
+ position:static;
+}
+
+.x-form-label-top .x-form-element {
+ padding-left:0;
+ padding-top:4px;
+}
+
+.x-form-label-top .x-form-item {
+ padding-bottom:4px;
+}
+
+/* Editor small font for grid, toolbar and tree */
+.x-small-editor .x-form-text {
+ height:20px;
+ line-height:16px;
+ vertical-align:middle;
+}
+
+.ext-ie6 .x-small-editor .x-form-text, .ext-ie7 .x-small-editor .x-form-text {
+ margin-top:-1px !important; /* ie bogus margin bug */
+ margin-bottom:-1px !important;
+ height:20px !important; /* ie quirks */
+ line-height:16px !important;
+}
+
+.ext-strict .x-small-editor .x-form-text {
+ height:16px !important;
+}
+
+.ext-ie6 .x-small-editor .x-form-text, .ext-ie7 .x-small-editor .x-form-text {
+ height:20px;
+ line-height:16px;
+}
+
+.ext-border-box .x-small-editor .x-form-text {
+ height:20px;
+}
+
+.x-small-editor .x-form-select-one {
+ height:20px;
+ line-height:16px;
+ vertical-align:middle;
+}
+
+.x-small-editor .x-form-num-field {
+ text-align:right;
+}
+
+.x-small-editor .x-form-field-wrap .x-form-trigger{
+ height:19px;
+}
+
+.ext-webkit .x-small-editor .x-form-text{padding-top:3px;font-size:100%;}
+
+.ext-strict .ext-webkit .x-small-editor .x-form-text{
+ height:14px !important;
+}
+
+.x-form-clear {
+ clear:both;
+ height:0;
+ overflow:hidden;
+ line-height:0;
+ font-size:0;
+}
+.x-form-clear-left {
+ clear:left;
+ height:0;
+ overflow:hidden;
+ line-height:0;
+ font-size:0;
+}
+
+.ext-ie6 .x-form-check-wrap input, .ext-border-box .x-form-check-wrap input{
+ margin-top: 3px;
+}
+
+.x-form-cb-label {
+ position: relative;
+ margin-left:4px;
+ top: 2px;
+}
+
+.ext-ie .x-form-cb-label{
+ top: 1px;
+}
+
+.ext-ie6 .x-form-cb-label, .ext-border-box .x-form-cb-label{
+ top: 3px;
+}
+
+.x-form-display-field{
+ padding-top: 2px;
+}
+
+.ext-gecko .x-form-display-field, .ext-strict .ext-ie7 .x-form-display-field{
+ padding-top: 1px;
+}
+
+.ext-ie .x-form-display-field{
+ padding-top: 3px;
+}
+
+.ext-strict .ext-ie8 .x-form-display-field{
+ padding-top: 0;
+}
+
+.x-form-column {
+ float:left;
+ padding:0;
+ margin:0;
+ width:48%;
+ overflow:hidden;
+ zoom:1;
+}
+
+/* buttons */
+.x-form .x-form-btns-ct .x-btn{
+ float:right;
+ clear:none;
+}
+
+.x-form .x-form-btns-ct .x-form-btns td {
+ border:0;
+ padding:0;
+}
+
+.x-form .x-form-btns-ct .x-form-btns-right table{
+ float:right;
+ clear:none;
+}
+
+.x-form .x-form-btns-ct .x-form-btns-left table{
+ float:left;
+ clear:none;
+}
+
+.x-form .x-form-btns-ct .x-form-btns-center{
+ text-align:center; /*ie*/
+}
+
+.x-form .x-form-btns-ct .x-form-btns-center table{
+ margin:0 auto; /*everyone else*/
+}
+
+.x-form .x-form-btns-ct table td.x-form-btn-td{
+ padding:3px;
+}
+
+.x-form .x-form-btns-ct .x-btn-focus .x-btn-left{
+ background-position:0 -147px;
+}
+
+.x-form .x-form-btns-ct .x-btn-focus .x-btn-right{
+ background-position:0 -168px;
+}
+
+.x-form .x-form-btns-ct .x-btn-focus .x-btn-center{
+ background-position:0 -189px;
+}
+
+.x-form .x-form-btns-ct .x-btn-click .x-btn-center{
+ background-position:0 -126px;
+}
+
+.x-form .x-form-btns-ct .x-btn-click .x-btn-right{
+ background-position:0 -84px;
+}
+
+.x-form .x-form-btns-ct .x-btn-click .x-btn-left{
+ background-position:0 -63px;
+}
+
+.x-form-invalid-icon {
+ width:16px;
+ height:18px;
+ visibility:hidden;
+ position:absolute;
+ left:0;
+ top:0;
+ display:block;
+ background:transparent no-repeat 0 2px;
+}
+
+/* fieldsets */
+.x-fieldset {
+ border:1px solid;
+ padding:10px;
+ margin-bottom:10px;
+ display:block; /* preserve margins in IE */
+}
+
+/* make top of checkbox/tools visible in webkit */
+.ext-webkit .x-fieldset-header {
+ padding-top: 1px;
+}
+
+.ext-ie .x-fieldset legend {
+ margin-bottom:10px;
+}
+
+.ext-strict .ext-ie9 .x-fieldset legend.x-fieldset-header {
+ padding-top: 1px;
+}
+
+.ext-ie .x-fieldset {
+ padding-top: 0;
+ padding-bottom:10px;
+}
+
+.x-fieldset legend .x-tool-toggle {
+ margin-right:3px;
+ margin-left:0;
+ float:left !important;
+}
+
+.x-fieldset legend input {
+ margin-right:3px;
+ float:left !important;
+ height:13px;
+ width:13px;
+}
+
+fieldset.x-panel-collapsed {
+ padding-bottom:0 !important;
+ border-width: 1px 1px 0 1px !important;
+ border-left-color: transparent;
+ border-right-color: transparent;
+}
+
+.ext-ie6 fieldset.x-panel-collapsed{
+ padding-bottom:0 !important;
+ border-width: 1px 0 0 0 !important;
+ margin-left: 1px;
+ margin-right: 1px;
+}
+
+fieldset.x-panel-collapsed .x-fieldset-bwrap {
+ visibility:hidden;
+ position:absolute;
+ left:-1000px;
+ top:-1000px;
+}
+
+.ext-ie .x-fieldset-bwrap {
+ zoom:1;
+}
+
+.x-fieldset-noborder {
+ border:0px none transparent;
+}
+
+.x-fieldset-noborder legend {
+ margin-left:-3px;
+}
+
+/* IE legend positioning bug */
+.ext-ie .x-fieldset-noborder legend {
+ position: relative;
+ margin-bottom:23px;
+}
+.ext-ie .x-fieldset-noborder legend span {
+ position: absolute;
+ left:16px;
+}
+
+.ext-gecko .x-window-body .x-form-item {
+ -moz-outline: none;
+ outline: none;
+ overflow: auto;
+}
+
+.ext-mac.ext-gecko .x-window-body .x-form-item {
+ overflow:hidden;
+}
+
+.ext-gecko .x-form-item {
+ -moz-outline: none;
+ outline: none;
+}
+
+.x-hide-label label.x-form-item-label {
+ display:none;
+}
+
+.x-hide-label .x-form-element {
+ padding-left: 0 !important;
+}
+
+.x-form-label-top .x-hide-label label.x-form-item-label{
+ display: none;
+}
+
+.x-fieldset {
+ overflow:hidden;
+}
+
+.x-fieldset-bwrap {
+ overflow:hidden;
+ zoom:1;
+}
+
+.x-fieldset-body {
+ overflow:hidden;
+}
+.x-btn{
+ cursor:pointer;
+ white-space: nowrap;
+}
+
+.x-btn button{
+ border:0 none;
+ background-color:transparent;
+ padding-left:3px;
+ padding-right:3px;
+ cursor:pointer;
+ margin:0;
+ overflow:visible;
+ width:auto;
+ -moz-outline:0 none;
+ outline:0 none;
+}
+
+* html .ext-ie .x-btn button {
+ width:1px;
+}
+
+.ext-gecko .x-btn button, .ext-webkit .x-btn button {
+ padding-left:0;
+ padding-right:0;
+}
+
+.ext-gecko .x-btn button::-moz-focus-inner {
+ padding:0;
+}
+
+.ext-ie .x-btn button {
+ padding-top:2px;
+}
+
+.x-btn td {
+ padding:0 !important;
+}
+
+.x-btn-text {
+ cursor:pointer;
+ white-space: nowrap;
+ padding:0;
+}
+
+/* icon placement and sizing styles */
+
+/* Only text */
+.x-btn-noicon .x-btn-small .x-btn-text{
+ height: 16px;
+}
+
+.x-btn-noicon .x-btn-medium .x-btn-text{
+ height: 24px;
+}
+
+.x-btn-noicon .x-btn-large .x-btn-text{
+ height: 32px;
+}
+
+/* Only icons */
+.x-btn-icon .x-btn-text{
+ background-position: center;
+ background-repeat: no-repeat;
+}
+
+.x-btn-icon .x-btn-small .x-btn-text{
+ height: 16px;
+ width: 16px;
+}
+
+.x-btn-icon .x-btn-medium .x-btn-text{
+ height: 24px;
+ width: 24px;
+}
+
+.x-btn-icon .x-btn-large .x-btn-text{
+ height: 32px;
+ width: 32px;
+}
+
+/* Icons and text */
+/* left */
+.x-btn-text-icon .x-btn-icon-small-left .x-btn-text{
+ background-position: 0 center;
+ background-repeat: no-repeat;
+ padding-left:18px;
+ height:16px;
+}
+
+.x-btn-text-icon .x-btn-icon-medium-left .x-btn-text{
+ background-position: 0 center;
+ background-repeat: no-repeat;
+ padding-left:26px;
+ height:24px;
+}
+
+.x-btn-text-icon .x-btn-icon-large-left .x-btn-text{
+ background-position: 0 center;
+ background-repeat: no-repeat;
+ padding-left:34px;
+ height:32px;
+}
+
+/* top */
+.x-btn-text-icon .x-btn-icon-small-top .x-btn-text{
+ background-position: center 0;
+ background-repeat: no-repeat;
+ padding-top:18px;
+}
+
+.x-btn-text-icon .x-btn-icon-medium-top .x-btn-text{
+ background-position: center 0;
+ background-repeat: no-repeat;
+ padding-top:26px;
+}
+
+.x-btn-text-icon .x-btn-icon-large-top .x-btn-text{
+ background-position: center 0;
+ background-repeat: no-repeat;
+ padding-top:34px;
+}
+
+/* right */
+.x-btn-text-icon .x-btn-icon-small-right .x-btn-text{
+ background-position: right center;
+ background-repeat: no-repeat;
+ padding-right:18px;
+ height:16px;
+}
+
+.x-btn-text-icon .x-btn-icon-medium-right .x-btn-text{
+ background-position: right center;
+ background-repeat: no-repeat;
+ padding-right:26px;
+ height:24px;
+}
+
+.x-btn-text-icon .x-btn-icon-large-right .x-btn-text{
+ background-position: right center;
+ background-repeat: no-repeat;
+ padding-right:34px;
+ height:32px;
+}
+
+/* bottom */
+.x-btn-text-icon .x-btn-icon-small-bottom .x-btn-text{
+ background-position: center bottom;
+ background-repeat: no-repeat;
+ padding-bottom:18px;
+}
+
+.x-btn-text-icon .x-btn-icon-medium-bottom .x-btn-text{
+ background-position: center bottom;
+ background-repeat: no-repeat;
+ padding-bottom:26px;
+}
+
+.x-btn-text-icon .x-btn-icon-large-bottom .x-btn-text{
+ background-position: center bottom;
+ background-repeat: no-repeat;
+ padding-bottom:34px;
+}
+
+/* background positioning */
+.x-btn-tr i, .x-btn-tl i, .x-btn-mr i, .x-btn-ml i, .x-btn-br i, .x-btn-bl i{
+ font-size:1px;
+ line-height:1px;
+ width:3px;
+ display:block;
+ overflow:hidden;
+}
+
+.x-btn-tr i, .x-btn-tl i, .x-btn-br i, .x-btn-bl i{
+ height:3px;
+}
+
+.x-btn-tl{
+ width:3px;
+ height:3px;
+ background:no-repeat 0 0;
+}
+.x-btn-tr{
+ width:3px;
+ height:3px;
+ background:no-repeat -3px 0;
+}
+.x-btn-tc{
+ height:3px;
+ background:repeat-x 0 -6px;
+}
+
+.x-btn-ml{
+ width:3px;
+ background:no-repeat 0 -24px;
+}
+.x-btn-mr{
+ width:3px;
+ background:no-repeat -3px -24px;
+}
+
+.x-btn-mc{
+ background:repeat-x 0 -1096px;
+ vertical-align: middle;
+ text-align:center;
+ padding:0 5px;
+ cursor:pointer;
+ white-space:nowrap;
+}
+
+/* Fixes an issue with the button height */
+.ext-strict .ext-ie6 .x-btn-mc, .ext-strict .ext-ie7 .x-btn-mc {
+ height: 100%;
+}
+
+.x-btn-bl{
+ width:3px;
+ height:3px;
+ background:no-repeat 0 -3px;
+}
+
+.x-btn-br{
+ width:3px;
+ height:3px;
+ background:no-repeat -3px -3px;
+}
+
+.x-btn-bc{
+ height:3px;
+ background:repeat-x 0 -15px;
+}
+
+.x-btn-over .x-btn-tl{
+ background-position: -6px 0;
+}
+
+.x-btn-over .x-btn-tr{
+ background-position: -9px 0;
+}
+
+.x-btn-over .x-btn-tc{
+ background-position: 0 -9px;
+}
+
+.x-btn-over .x-btn-ml{
+ background-position: -6px -24px;
+}
+
+.x-btn-over .x-btn-mr{
+ background-position: -9px -24px;
+}
+
+.x-btn-over .x-btn-mc{
+ background-position: 0 -2168px;
+}
+
+.x-btn-over .x-btn-bl{
+ background-position: -6px -3px;
+}
+
+.x-btn-over .x-btn-br{
+ background-position: -9px -3px;
+}
+
+.x-btn-over .x-btn-bc{
+ background-position: 0 -18px;
+}
+
+.x-btn-click .x-btn-tl, .x-btn-menu-active .x-btn-tl, .x-btn-pressed .x-btn-tl{
+ background-position: -12px 0;
+}
+
+.x-btn-click .x-btn-tr, .x-btn-menu-active .x-btn-tr, .x-btn-pressed .x-btn-tr{
+ background-position: -15px 0;
+}
+
+.x-btn-click .x-btn-tc, .x-btn-menu-active .x-btn-tc, .x-btn-pressed .x-btn-tc{
+ background-position: 0 -12px;
+}
+
+.x-btn-click .x-btn-ml, .x-btn-menu-active .x-btn-ml, .x-btn-pressed .x-btn-ml{
+ background-position: -12px -24px;
+}
+
+.x-btn-click .x-btn-mr, .x-btn-menu-active .x-btn-mr, .x-btn-pressed .x-btn-mr{
+ background-position: -15px -24px;
+}
+
+.x-btn-click .x-btn-mc, .x-btn-menu-active .x-btn-mc, .x-btn-pressed .x-btn-mc{
+ background-position: 0 -3240px;
+}
+
+.x-btn-click .x-btn-bl, .x-btn-menu-active .x-btn-bl, .x-btn-pressed .x-btn-bl{
+ background-position: -12px -3px;
+}
+
+.x-btn-click .x-btn-br, .x-btn-menu-active .x-btn-br, .x-btn-pressed .x-btn-br{
+ background-position: -15px -3px;
+}
+
+.x-btn-click .x-btn-bc, .x-btn-menu-active .x-btn-bc, .x-btn-pressed .x-btn-bc{
+ background-position: 0 -21px;
+}
+
+.x-btn-disabled *{
+ cursor:default !important;
+}
+
+
+/* With a menu arrow */
+/* right */
+.x-btn-mc em.x-btn-arrow {
+ display:block;
+ background:transparent no-repeat right center;
+ padding-right:10px;
+}
+
+.x-btn-mc em.x-btn-split {
+ display:block;
+ background:transparent no-repeat right center;
+ padding-right:14px;
+}
+
+/* bottom */
+.x-btn-mc em.x-btn-arrow-bottom {
+ display:block;
+ background:transparent no-repeat center bottom;
+ padding-bottom:14px;
+}
+
+.x-btn-mc em.x-btn-split-bottom {
+ display:block;
+ background:transparent no-repeat center bottom;
+ padding-bottom:14px;
+}
+
+/* height adjustment class */
+.x-btn-as-arrow .x-btn-mc em {
+ display:block;
+ background-color:transparent;
+ padding-bottom:14px;
+}
+
+/* groups */
+.x-btn-group {
+ padding:1px;
+}
+
+.x-btn-group-header {
+ padding:2px;
+ text-align:center;
+}
+
+.x-btn-group-tc {
+ background: transparent repeat-x 0 0;
+ overflow:hidden;
+}
+
+.x-btn-group-tl {
+ background: transparent no-repeat 0 0;
+ padding-left:3px;
+ zoom:1;
+}
+
+.x-btn-group-tr {
+ background: transparent no-repeat right 0;
+ zoom:1;
+ padding-right:3px;
+}
+
+.x-btn-group-bc {
+ background: transparent repeat-x 0 bottom;
+ zoom:1;
+}
+
+.x-btn-group-bc .x-panel-footer {
+ zoom:1;
+}
+
+.x-btn-group-bl {
+ background: transparent no-repeat 0 bottom;
+ padding-left:3px;
+ zoom:1;
+}
+
+.x-btn-group-br {
+ background: transparent no-repeat right bottom;
+ padding-right:3px;
+ zoom:1;
+}
+
+.x-btn-group-mc {
+ border:0 none;
+ padding:1px 0 0 0;
+ margin:0;
+}
+
+.x-btn-group-mc .x-btn-group-body {
+ background-color:transparent;
+ border: 0 none;
+}
+
+.x-btn-group-ml {
+ background: transparent repeat-y 0 0;
+ padding-left:3px;
+ zoom:1;
+}
+
+.x-btn-group-mr {
+ background: transparent repeat-y right 0;
+ padding-right:3px;
+ zoom:1;
+}
+
+.x-btn-group-bc .x-btn-group-footer {
+ padding-bottom:6px;
+}
+
+.x-panel-nofooter .x-btn-group-bc {
+ height:3px;
+ font-size:0;
+ line-height:0;
+}
+
+.x-btn-group-bwrap {
+ overflow:hidden;
+ zoom:1;
+}
+
+.x-btn-group-body {
+ overflow:hidden;
+ zoom:1;
+}
+
+.x-btn-group-notitle .x-btn-group-tc {
+ background: transparent repeat-x 0 0;
+ overflow:hidden;
+ height:2px;
+}.x-toolbar{
+ border-style:solid;
+ border-width:0 0 1px 0;
+ display: block;
+ padding:2px;
+ background:repeat-x top left;
+ position:relative;
+ left:0;
+ top:0;
+ zoom:1;
+ overflow:hidden;
+}
+
+.x-toolbar-left {
+ width: 100%;
+}
+
+.x-toolbar .x-item-disabled .x-btn-icon {
+ opacity: .35;
+ -moz-opacity: .35;
+ filter: alpha(opacity=35);
+}
+
+.x-toolbar td {
+ vertical-align:middle;
+}
+
+.x-toolbar td,.x-toolbar span,.x-toolbar input,.x-toolbar div,.x-toolbar select,.x-toolbar label{
+ white-space: nowrap;
+}
+
+.x-toolbar .x-item-disabled {
+ cursor:default;
+ opacity:.6;
+ -moz-opacity:.6;
+ filter:alpha(opacity=60);
+}
+
+.x-toolbar .x-item-disabled * {
+ cursor:default;
+}
+
+.x-toolbar .x-toolbar-cell {
+ vertical-align:middle;
+}
+
+.x-toolbar .x-btn-tl, .x-toolbar .x-btn-tr, .x-toolbar .x-btn-tc, .x-toolbar .x-btn-ml, .x-toolbar .x-btn-mr,
+.x-toolbar .x-btn-mc, .x-toolbar .x-btn-bl, .x-toolbar .x-btn-br, .x-toolbar .x-btn-bc
+{
+ background-position: 500px 500px;
+}
+
+/* These rules are duplicated from button.css to give priority of x-toolbar rules above */
+.x-toolbar .x-btn-over .x-btn-tl{
+ background-position: -6px 0;
+}
+
+.x-toolbar .x-btn-over .x-btn-tr{
+ background-position: -9px 0;
+}
+
+.x-toolbar .x-btn-over .x-btn-tc{
+ background-position: 0 -9px;
+}
+
+.x-toolbar .x-btn-over .x-btn-ml{
+ background-position: -6px -24px;
+}
+
+.x-toolbar .x-btn-over .x-btn-mr{
+ background-position: -9px -24px;
+}
+
+.x-toolbar .x-btn-over .x-btn-mc{
+ background-position: 0 -2168px;
+}
+
+.x-toolbar .x-btn-over .x-btn-bl{
+ background-position: -6px -3px;
+}
+
+.x-toolbar .x-btn-over .x-btn-br{
+ background-position: -9px -3px;
+}
+
+.x-toolbar .x-btn-over .x-btn-bc{
+ background-position: 0 -18px;
+}
+
+.x-toolbar .x-btn-click .x-btn-tl, .x-toolbar .x-btn-menu-active .x-btn-tl, .x-toolbar .x-btn-pressed .x-btn-tl{
+ background-position: -12px 0;
+}
+
+.x-toolbar .x-btn-click .x-btn-tr, .x-toolbar .x-btn-menu-active .x-btn-tr, .x-toolbar .x-btn-pressed .x-btn-tr{
+ background-position: -15px 0;
+}
+
+.x-toolbar .x-btn-click .x-btn-tc, .x-toolbar .x-btn-menu-active .x-btn-tc, .x-toolbar .x-btn-pressed .x-btn-tc{
+ background-position: 0 -12px;
+}
+
+.x-toolbar .x-btn-click .x-btn-ml, .x-toolbar .x-btn-menu-active .x-btn-ml, .x-toolbar .x-btn-pressed .x-btn-ml{
+ background-position: -12px -24px;
+}
+
+.x-toolbar .x-btn-click .x-btn-mr, .x-toolbar .x-btn-menu-active .x-btn-mr, .x-toolbar .x-btn-pressed .x-btn-mr{
+ background-position: -15px -24px;
+}
+
+.x-toolbar .x-btn-click .x-btn-mc, .x-toolbar .x-btn-menu-active .x-btn-mc, .x-toolbar .x-btn-pressed .x-btn-mc{
+ background-position: 0 -3240px;
+}
+
+.x-toolbar .x-btn-click .x-btn-bl, .x-toolbar .x-btn-menu-active .x-btn-bl, .x-toolbar .x-btn-pressed .x-btn-bl{
+ background-position: -12px -3px;
+}
+
+.x-toolbar .x-btn-click .x-btn-br, .x-toolbar .x-btn-menu-active .x-btn-br, .x-toolbar .x-btn-pressed .x-btn-br{
+ background-position: -15px -3px;
+}
+
+.x-toolbar .x-btn-click .x-btn-bc, .x-toolbar .x-btn-menu-active .x-btn-bc, .x-toolbar .x-btn-pressed .x-btn-bc{
+ background-position: 0 -21px;
+}
+
+.x-toolbar div.xtb-text{
+ padding:2px 2px 0;
+ line-height:16px;
+ display:block;
+}
+
+.x-toolbar .xtb-sep {
+ background-position: center;
+ background-repeat: no-repeat;
+ display: block;
+ font-size: 1px;
+ height: 16px;
+ width:4px;
+ overflow: hidden;
+ cursor:default;
+ margin: 0 2px 0;
+ border:0;
+}
+
+.x-toolbar .xtb-spacer {
+ width:2px;
+}
+
+/* Paging Toolbar */
+.x-tbar-page-number{
+ width:30px;
+ height:14px;
+}
+
+.ext-ie .x-tbar-page-number{
+ margin-top: 2px;
+}
+
+.x-paging-info {
+ position:absolute;
+ top:5px;
+ right: 8px;
+}
+
+/* floating */
+.x-toolbar-ct {
+ width:100%;
+}
+
+.x-toolbar-right td {
+ text-align: center;
+}
+
+.x-panel-tbar, .x-panel-bbar, .x-window-tbar, .x-window-bbar, .x-tab-panel-tbar, .x-tab-panel-bbar, .x-plain-tbar, .x-plain-bbar {
+ overflow:hidden;
+ zoom:1;
+}
+
+.x-toolbar-more .x-btn-small .x-btn-text{
+ height: 16px;
+ width: 12px;
+}
+
+.x-toolbar-more em.x-btn-arrow {
+ display:inline;
+ background-color:transparent;
+ padding-right:0;
+}
+
+.x-toolbar-more .x-btn-mc em.x-btn-arrow {
+ background-image: none;
+}
+
+div.x-toolbar-no-items {
+ color:gray !important;
+ padding:5px 10px !important;
+}
+
+/* fix ie toolbar form items */
+.ext-border-box .x-toolbar-cell .x-form-text {
+ margin-bottom:-1px !important;
+}
+
+.ext-border-box .x-toolbar-cell .x-form-field-wrap .x-form-text {
+ margin:0 !important;
+}
+
+.ext-ie .x-toolbar-cell .x-form-field-wrap {
+ height:21px;
+}
+
+.ext-ie .x-toolbar-cell .x-form-text {
+ position:relative;
+ top:-1px;
+}
+
+.ext-strict .ext-ie8 .x-toolbar-cell .x-form-field-trigger-wrap .x-form-text, .ext-strict .ext-ie .x-toolbar-cell .x-form-text {
+ top: 0px;
+}
+
+.x-toolbar-right td .x-form-field-trigger-wrap{
+ text-align: left;
+}
+
+.x-toolbar-cell .x-form-checkbox, .x-toolbar-cell .x-form-radio{
+ margin-top: 5px;
+}
+
+.x-toolbar-cell .x-form-cb-label{
+ vertical-align: bottom;
+ top: 1px;
+}
+
+.ext-ie .x-toolbar-cell .x-form-checkbox, .ext-ie .x-toolbar-cell .x-form-radio{
+ margin-top: 4px;
+}
+
+.ext-ie .x-toolbar-cell .x-form-cb-label{
+ top: 0;
+}
+/* Grid3 styles */
+.x-grid3 {
+ position:relative;
+ overflow:hidden;
+}
+
+.x-grid-panel .x-panel-body {
+ overflow:hidden !important;
+}
+
+.x-grid-panel .x-panel-mc .x-panel-body {
+ border:1px solid;
+}
+
+.x-grid3 table {
+ table-layout:fixed;
+}
+
+.x-grid3-viewport{
+ overflow:hidden;
+}
+
+.x-grid3-hd-row td, .x-grid3-row td, .x-grid3-summary-row td{
+ -moz-outline: none;
+ outline: none;
+ -moz-user-focus: normal;
+}
+
+.x-grid3-row td, .x-grid3-summary-row td {
+ line-height:13px;
+ vertical-align: top;
+ padding-left:1px;
+ padding-right:1px;
+ -moz-user-select: none;
+ -khtml-user-select:none;
+ -webkit-user-select:ignore;
+}
+
+.x-grid3-cell{
+ -moz-user-select: none;
+ -khtml-user-select:none;
+ -webkit-user-select:ignore;
+}
+
+.x-grid3-hd-row td {
+ line-height:15px;
+ vertical-align:middle;
+ border-left:1px solid;
+ border-right:1px solid;
+}
+
+.x-grid3-hd-row .x-grid3-marker-hd {
+ padding:3px;
+}
+
+.x-grid3-row .x-grid3-marker {
+ padding:3px;
+}
+
+.x-grid3-cell-inner, .x-grid3-hd-inner{
+ overflow:hidden;
+ -o-text-overflow: ellipsis;
+ text-overflow: ellipsis;
+ padding:3px 3px 3px 5px;
+ white-space: nowrap;
+}
+
+/* ActionColumn, reduce padding to accommodate 16x16 icons in normal row height */
+.x-action-col-cell .x-grid3-cell-inner {
+ padding-top: 1px;
+ padding-bottom: 1px;
+}
+
+.x-action-col-icon {
+ cursor: pointer;
+}
+
+.x-grid3-hd-inner {
+ position:relative;
+ cursor:inherit;
+ padding:4px 3px 4px 5px;
+}
+
+.x-grid3-row-body {
+ white-space:normal;
+}
+
+.x-grid3-body-cell {
+ -moz-outline:0 none;
+ outline:0 none;
+}
+
+/* IE Quirks to clip */
+.ext-ie .x-grid3-cell-inner, .ext-ie .x-grid3-hd-inner{
+ width:100%;
+}
+
+/* reverse above in strict mode */
+.ext-strict .x-grid3-cell-inner, .ext-strict .x-grid3-hd-inner{
+ width:auto;
+}
+
+.x-grid-row-loading {
+ background: no-repeat center center;
+}
+
+.x-grid-page {
+ overflow:hidden;
+}
+
+.x-grid3-row {
+ cursor: default;
+ border: 1px solid;
+ width:100%;
+}
+
+.x-grid3-row-over {
+ border:1px solid;
+ background: repeat-x left top;
+}
+
+.x-grid3-resize-proxy {
+ width:1px;
+ left:0;
+ cursor: e-resize;
+ cursor: col-resize;
+ position:absolute;
+ top:0;
+ height:100px;
+ overflow:hidden;
+ visibility:hidden;
+ border:0 none;
+ z-index:7;
+}
+
+.x-grid3-resize-marker {
+ width:1px;
+ left:0;
+ position:absolute;
+ top:0;
+ height:100px;
+ overflow:hidden;
+ visibility:hidden;
+ border:0 none;
+ z-index:7;
+}
+
+.x-grid3-focus {
+ position:absolute;
+ left:0;
+ top:0;
+ width:1px;
+ height:1px;
+ line-height:1px;
+ font-size:1px;
+ -moz-outline:0 none;
+ outline:0 none;
+ -moz-user-select: text;
+ -khtml-user-select: text;
+ -webkit-user-select:ignore;
+}
+
+/* header styles */
+.x-grid3-header{
+ background: repeat-x 0 bottom;
+ cursor:default;
+ zoom:1;
+ padding:1px 0 0 0;
+}
+
+.x-grid3-header-pop {
+ border-left:1px solid;
+ float:right;
+ clear:none;
+}
+
+.x-grid3-header-pop-inner {
+ border-left:1px solid;
+ width:14px;
+ height:19px;
+ background: transparent no-repeat center center;
+}
+
+.ext-ie .x-grid3-header-pop-inner {
+ width:15px;
+}
+
+.ext-strict .x-grid3-header-pop-inner {
+ width:14px;
+}
+
+.x-grid3-header-inner {
+ overflow:hidden;
+ zoom:1;
+ float:left;
+}
+
+.x-grid3-header-offset {
+ padding-left:1px;
+ text-align: left;
+}
+
+td.x-grid3-hd-over, td.sort-desc, td.sort-asc, td.x-grid3-hd-menu-open {
+ border-left:1px solid;
+ border-right:1px solid;
+}
+
+td.x-grid3-hd-over .x-grid3-hd-inner, td.sort-desc .x-grid3-hd-inner, td.sort-asc .x-grid3-hd-inner, td.x-grid3-hd-menu-open .x-grid3-hd-inner {
+ background: repeat-x left bottom;
+
+}
+
+.x-grid3-sort-icon{
+ background-repeat: no-repeat;
+ display: none;
+ height: 4px;
+ width: 13px;
+ margin-left:3px;
+ vertical-align: middle;
+}
+
+.sort-asc .x-grid3-sort-icon, .sort-desc .x-grid3-sort-icon {
+ display: inline;
+}
+
+/* Header position fixes for IE strict mode */
+.ext-strict .ext-ie .x-grid3-header-inner, .ext-strict .ext-ie6 .x-grid3-hd {
+ position:relative;
+}
+
+.ext-strict .ext-ie6 .x-grid3-hd-inner{
+ position:static;
+}
+
+/* Body Styles */
+.x-grid3-body {
+ zoom:1;
+}
+
+.x-grid3-scroller {
+ overflow:auto;
+ zoom:1;
+ position:relative;
+}
+
+.x-grid3-cell-text, .x-grid3-hd-text {
+ display: block;
+ padding: 3px 5px 3px 5px;
+ -moz-user-select: none;
+ -khtml-user-select: none;
+ -webkit-user-select:ignore;
+}
+
+.x-grid3-split {
+ background-position: center;
+ background-repeat: no-repeat;
+ cursor: e-resize;
+ cursor: col-resize;
+ display: block;
+ font-size: 1px;
+ height: 16px;
+ overflow: hidden;
+ position: absolute;
+ top: 2px;
+ width: 6px;
+ z-index: 3;
+}
+
+/* Column Reorder DD */
+.x-dd-drag-proxy .x-grid3-hd-inner{
+ background: repeat-x left bottom;
+ width:120px;
+ padding:3px;
+ border:1px solid;
+ overflow:hidden;
+}
+
+.col-move-top, .col-move-bottom{
+ width:9px;
+ height:9px;
+ position:absolute;
+ top:0;
+ line-height:1px;
+ font-size:1px;
+ overflow:hidden;
+ visibility:hidden;
+ z-index:20000;
+ background:transparent no-repeat left top;
+}
+
+/* Selection Styles */
+.x-grid3-row-selected {
+ border:1px dotted;
+}
+
+.x-grid3-locked td.x-grid3-row-marker, .x-grid3-locked .x-grid3-row-selected td.x-grid3-row-marker{
+ background: repeat-x 0 bottom !important;
+ vertical-align:middle !important;
+ padding:0;
+ border-top:1px solid;
+ border-bottom:none !important;
+ border-right:1px solid !important;
+ text-align:center;
+}
+
+.x-grid3-locked td.x-grid3-row-marker div, .x-grid3-locked .x-grid3-row-selected td.x-grid3-row-marker div{
+ padding:0 4px;
+ text-align:center;
+}
+
+/* dirty cells */
+.x-grid3-dirty-cell {
+ background: transparent no-repeat 0 0;
+}
+
+/* Grid Toolbars */
+.x-grid3-topbar, .x-grid3-bottombar{
+ overflow:hidden;
+ display:none;
+ zoom:1;
+ position:relative;
+}
+
+.x-grid3-topbar .x-toolbar{
+ border-right:0 none;
+}
+
+.x-grid3-bottombar .x-toolbar{
+ border-right:0 none;
+ border-bottom:0 none;
+ border-top:1px solid;
+}
+
+/* Props Grid Styles */
+.x-props-grid .x-grid3-cell{
+ padding:1px;
+}
+
+.x-props-grid .x-grid3-td-name .x-grid3-cell-inner{
+ background:transparent repeat-y -16px !important;
+ padding-left:12px;
+}
+
+.x-props-grid .x-grid3-body .x-grid3-td-name{
+ padding:1px;
+ padding-right:0;
+ border:0 none;
+ border-right:1px solid;
+}
+
+/* dd */
+.x-grid3-col-dd {
+ border:0 none;
+ padding:0;
+ background-color:transparent;
+}
+
+.x-dd-drag-ghost .x-grid3-dd-wrap {
+ padding:1px 3px 3px 1px;
+}
+
+.x-grid3-hd {
+ -moz-user-select:none;
+ -khtml-user-select:none;
+ -webkit-user-select:ignore;
+}
+
+.x-grid3-hd-btn {
+ display:none;
+ position:absolute;
+ width:14px;
+ background:no-repeat left center;
+ right:0;
+ top:0;
+ z-index:2;
+ cursor:pointer;
+}
+
+.x-grid3-hd-over .x-grid3-hd-btn, .x-grid3-hd-menu-open .x-grid3-hd-btn {
+ display:block;
+}
+
+a.x-grid3-hd-btn:hover {
+ background-position:-14px center;
+}
+
+/* Expanders */
+.x-grid3-body .x-grid3-td-expander {
+ background:transparent repeat-y right;
+}
+
+.x-grid3-body .x-grid3-td-expander .x-grid3-cell-inner {
+ padding:0 !important;
+ height:100%;
+}
+
+.x-grid3-row-expander {
+ width:100%;
+ height:18px;
+ background-position:4px 2px;
+ background-repeat:no-repeat;
+ background-color:transparent;
+}
+
+.x-grid3-row-collapsed .x-grid3-row-expander {
+ background-position:4px 2px;
+}
+
+.x-grid3-row-expanded .x-grid3-row-expander {
+ background-position:-21px 2px;
+}
+
+.x-grid3-row-collapsed .x-grid3-row-body {
+ display:none !important;
+}
+
+.x-grid3-row-expanded .x-grid3-row-body {
+ display:block !important;
+}
+
+/* Checkers */
+.x-grid3-body .x-grid3-td-checker {
+ background:transparent repeat-y right;
+}
+
+.x-grid3-body .x-grid3-td-checker .x-grid3-cell-inner, .x-grid3-header .x-grid3-td-checker .x-grid3-hd-inner {
+ padding:0 !important;
+ height:100%;
+}
+
+.x-grid3-row-checker, .x-grid3-hd-checker {
+ width:100%;
+ height:18px;
+ background-position:2px 2px;
+ background-repeat:no-repeat;
+ background-color:transparent;
+}
+
+.x-grid3-row .x-grid3-row-checker {
+ background-position:2px 2px;
+}
+
+.x-grid3-row-selected .x-grid3-row-checker, .x-grid3-hd-checker-on .x-grid3-hd-checker,.x-grid3-row-checked .x-grid3-row-checker {
+ background-position:-23px 2px;
+}
+
+.x-grid3-hd-checker {
+ background-position:2px 1px;
+}
+
+.ext-border-box .x-grid3-hd-checker {
+ background-position:2px 3px;
+}
+
+.x-grid3-hd-checker-on .x-grid3-hd-checker {
+ background-position:-23px 1px;
+}
+
+.ext-border-box .x-grid3-hd-checker-on .x-grid3-hd-checker {
+ background-position:-23px 3px;
+}
+
+/* Numberer */
+.x-grid3-body .x-grid3-td-numberer {
+ background:transparent repeat-y right;
+}
+
+.x-grid3-body .x-grid3-td-numberer .x-grid3-cell-inner {
+ padding:3px 5px 0 0 !important;
+ text-align:right;
+}
+
+/* Row Icon */
+
+.x-grid3-body .x-grid3-td-row-icon {
+ background:transparent repeat-y right;
+ vertical-align:top;
+ text-align:center;
+}
+
+.x-grid3-body .x-grid3-td-row-icon .x-grid3-cell-inner {
+ padding:0 !important;
+ background-position:center center;
+ background-repeat:no-repeat;
+ width:16px;
+ height:16px;
+ margin-left:2px;
+ margin-top:3px;
+}
+
+/* All specials */
+.x-grid3-body .x-grid3-row-selected .x-grid3-td-numberer,
+.x-grid3-body .x-grid3-row-selected .x-grid3-td-checker,
+.x-grid3-body .x-grid3-row-selected .x-grid3-td-expander {
+ background:transparent repeat-y right;
+}
+
+.x-grid3-body .x-grid3-check-col-td .x-grid3-cell-inner {
+ padding: 1px 0 0 0 !important;
+}
+
+.x-grid3-check-col {
+ width:100%;
+ height:16px;
+ background-position:center center;
+ background-repeat:no-repeat;
+ background-color:transparent;
+}
+
+.x-grid3-check-col-on {
+ width:100%;
+ height:16px;
+ background-position:center center;
+ background-repeat:no-repeat;
+ background-color:transparent;
+}
+
+/* Grouping classes */
+.x-grid-group, .x-grid-group-body, .x-grid-group-hd {
+ zoom:1;
+}
+
+.x-grid-group-hd {
+ border-bottom: 2px solid;
+ cursor:pointer;
+ padding-top:6px;
+}
+
+.x-grid-group-hd div.x-grid-group-title {
+ background:transparent no-repeat 3px 3px;
+ padding:4px 4px 4px 17px;
+}
+
+.x-grid-group-collapsed .x-grid-group-body {
+ display:none;
+}
+
+.ext-ie6 .x-grid3 .x-editor .x-form-text, .ext-ie7 .x-grid3 .x-editor .x-form-text {
+ position:relative;
+ top:-1px;
+}
+
+.x-grid-editor .x-form-check-wrap {
+ text-align: center;
+ margin-top: -4px;
+}
+
+.ext-ie .x-props-grid .x-editor .x-form-text {
+ position:static;
+ top:0;
+}
+
+.x-grid-empty {
+ padding:10px;
+}
+
+/* fix floating toolbar issue */
+.ext-ie7 .x-grid-panel .x-panel-bbar {
+ position:relative;
+}
+
+
+/* Reset position to static when Grid Panel has been framed */
+/* to resolve 'snapping' from top to bottom behavior. */
+/* @forumThread 86656 */
+.ext-ie7 .x-grid-panel .x-panel-mc .x-panel-bbar {
+ position: static;
+}
+
+.ext-ie6 .x-grid3-header {
+ position: relative;
+}
+
+/* Fix WebKit bug in Grids */
+.ext-webkit .x-grid-panel .x-panel-bwrap{
+ -webkit-user-select:none;
+}
+.ext-webkit .x-tbar-page-number{
+ -webkit-user-select:ignore;
+}
+/* end*/
+
+/* column lines */
+.x-grid-with-col-lines .x-grid3-row td.x-grid3-cell {
+ padding-right:0;
+ border-right:1px solid;
+}
+.x-pivotgrid .x-grid3-header-offset table {
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.x-pivotgrid .x-grid3-header-offset table td {
+ padding: 4px 3px 4px 5px;
+ text-align: center;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ font-size: 11px;
+ line-height: 13px;
+ font-family: tahoma;
+}
+
+.x-pivotgrid .x-grid3-row-headers {
+ display: block;
+ float: left;
+}
+
+.x-pivotgrid .x-grid3-row-headers table {
+ height: 100%;
+ width: 100%;
+ border-collapse: collapse;
+}
+
+.x-pivotgrid .x-grid3-row-headers table td {
+ height: 18px;
+ padding: 2px 7px 0 0;
+ text-align: right;
+ text-overflow: ellipsis;
+ font-size: 11px;
+ font-family: tahoma;
+}
+
+.ext-gecko .x-pivotgrid .x-grid3-row-headers table td {
+ height: 21px;
+}
+
+.x-grid3-header-title {
+ top: 0%;
+ left: 0%;
+ position: absolute;
+ text-align: center;
+ vertical-align: middle;
+ font-family: tahoma;
+ font-size: 11px;
+ padding: auto 1px;
+ display: table-cell;
+}
+
+.x-grid3-header-title span {
+ position: absolute;
+ top: 50%;
+ left: 0%;
+ width: 100%;
+ margin-top: -6px;
+}.x-dd-drag-proxy{
+ position:absolute;
+ left:0;
+ top:0;
+ visibility:hidden;
+ z-index:15000;
+}
+
+.x-dd-drag-ghost{
+ -moz-opacity: 0.85;
+ opacity:.85;
+ filter: alpha(opacity=85);
+ border: 1px solid;
+ padding:3px;
+ padding-left:20px;
+ white-space:nowrap;
+}
+
+.x-dd-drag-repair .x-dd-drag-ghost{
+ -moz-opacity: 0.4;
+ opacity:.4;
+ filter: alpha(opacity=40);
+ border:0 none;
+ padding:0;
+ background-color:transparent;
+}
+
+.x-dd-drag-repair .x-dd-drop-icon{
+ visibility:hidden;
+}
+
+.x-dd-drop-icon{
+ position:absolute;
+ top:3px;
+ left:3px;
+ display:block;
+ width:16px;
+ height:16px;
+ background-color:transparent;
+ background-position: center;
+ background-repeat: no-repeat;
+ z-index:1;
+}
+
+.x-view-selector {
+ position:absolute;
+ left:0;
+ top:0;
+ width:0;
+ border:1px dotted;
+ opacity: .5;
+ -moz-opacity: .5;
+ filter:alpha(opacity=50);
+ zoom:1;
+}.ext-strict .ext-ie .x-tree .x-panel-bwrap{
+ position:relative;
+ overflow:hidden;
+}
+
+.x-tree-icon, .x-tree-ec-icon, .x-tree-elbow-line, .x-tree-elbow, .x-tree-elbow-end, .x-tree-elbow-plus, .x-tree-elbow-minus, .x-tree-elbow-end-plus, .x-tree-elbow-end-minus{
+ border: 0 none;
+ height: 18px;
+ margin: 0;
+ padding: 0;
+ vertical-align: top;
+ width: 16px;
+ background-repeat: no-repeat;
+}
+
+.x-tree-node-collapsed .x-tree-node-icon, .x-tree-node-expanded .x-tree-node-icon, .x-tree-node-leaf .x-tree-node-icon{
+ border: 0 none;
+ height: 18px;
+ margin: 0;
+ padding: 0;
+ vertical-align: top;
+ width: 16px;
+ background-position:center;
+ background-repeat: no-repeat;
+}
+
+.ext-ie .x-tree-node-indent img, .ext-ie .x-tree-node-icon, .ext-ie .x-tree-ec-icon {
+ vertical-align: middle !important;
+}
+
+.ext-strict .ext-ie8 .x-tree-node-indent img, .ext-strict .ext-ie8 .x-tree-node-icon, .ext-strict .ext-ie8 .x-tree-ec-icon {
+ vertical-align: top !important;
+}
+
+/* checkboxes */
+
+input.x-tree-node-cb {
+ margin-left:1px;
+ height: 19px;
+ vertical-align: bottom;
+}
+
+.ext-ie input.x-tree-node-cb {
+ margin-left:0;
+ margin-top: 1px;
+ width: 16px;
+ height: 16px;
+ vertical-align: middle;
+}
+
+.ext-strict .ext-ie8 input.x-tree-node-cb{
+ margin: 1px 1px;
+ height: 14px;
+ vertical-align: bottom;
+}
+
+.ext-strict .ext-ie8 input.x-tree-node-cb + a{
+ vertical-align: bottom;
+}
+
+.ext-opera input.x-tree-node-cb {
+ height: 14px;
+ vertical-align: middle;
+}
+
+.x-tree-noicon .x-tree-node-icon{
+ width:0; height:0;
+}
+
+/* No line styles */
+.x-tree-no-lines .x-tree-elbow{
+ background-color:transparent;
+}
+
+.x-tree-no-lines .x-tree-elbow-end{
+ background-color:transparent;
+}
+
+.x-tree-no-lines .x-tree-elbow-line{
+ background-color:transparent;
+}
+
+/* Arrows */
+.x-tree-arrows .x-tree-elbow{
+ background-color:transparent;
+}
+
+.x-tree-arrows .x-tree-elbow-plus{
+ background:transparent no-repeat 0 0;
+}
+
+.x-tree-arrows .x-tree-elbow-minus{
+ background:transparent no-repeat -16px 0;
+}
+
+.x-tree-arrows .x-tree-elbow-end{
+ background-color:transparent;
+}
+
+.x-tree-arrows .x-tree-elbow-end-plus{
+ background:transparent no-repeat 0 0;
+}
+
+.x-tree-arrows .x-tree-elbow-end-minus{
+ background:transparent no-repeat -16px 0;
+}
+
+.x-tree-arrows .x-tree-elbow-line{
+ background-color:transparent;
+}
+
+.x-tree-arrows .x-tree-ec-over .x-tree-elbow-plus{
+ background-position:-32px 0;
+}
+
+.x-tree-arrows .x-tree-ec-over .x-tree-elbow-minus{
+ background-position:-48px 0;
+}
+
+.x-tree-arrows .x-tree-ec-over .x-tree-elbow-end-plus{
+ background-position:-32px 0;
+}
+
+.x-tree-arrows .x-tree-ec-over .x-tree-elbow-end-minus{
+ background-position:-48px 0;
+}
+
+.x-tree-elbow-plus, .x-tree-elbow-minus, .x-tree-elbow-end-plus, .x-tree-elbow-end-minus{
+ cursor:pointer;
+}
+
+.ext-ie ul.x-tree-node-ct{
+ font-size:0;
+ line-height:0;
+ zoom:1;
+}
+
+.x-tree-node{
+ white-space: nowrap;
+}
+
+.x-tree-node-el {
+ line-height:18px;
+ cursor:pointer;
+}
+
+.x-tree-node a, .x-dd-drag-ghost a{
+ text-decoration:none;
+ -khtml-user-select:none;
+ -moz-user-select:none;
+ -webkit-user-select:ignore;
+ -kthml-user-focus:normal;
+ -moz-user-focus:normal;
+ -moz-outline: 0 none;
+ outline:0 none;
+}
+
+.x-tree-node a span, .x-dd-drag-ghost a span{
+ text-decoration:none;
+ padding:1px 3px 1px 2px;
+}
+
+.x-tree-node .x-tree-node-disabled .x-tree-node-icon{
+ -moz-opacity: 0.5;
+ opacity:.5;
+ filter: alpha(opacity=50);
+}
+
+.x-tree-node .x-tree-node-inline-icon{
+ background-color:transparent;
+}
+
+.x-tree-node a:hover, .x-dd-drag-ghost a:hover{
+ text-decoration:none;
+}
+
+.x-tree-node div.x-tree-drag-insert-below{
+ border-bottom:1px dotted;
+}
+
+.x-tree-node div.x-tree-drag-insert-above{
+ border-top:1px dotted;
+}
+
+.x-tree-dd-underline .x-tree-node div.x-tree-drag-insert-below{
+ border-bottom:0 none;
+}
+
+.x-tree-dd-underline .x-tree-node div.x-tree-drag-insert-above{
+ border-top:0 none;
+}
+
+.x-tree-dd-underline .x-tree-node div.x-tree-drag-insert-below a{
+ border-bottom:2px solid;
+}
+
+.x-tree-dd-underline .x-tree-node div.x-tree-drag-insert-above a{
+ border-top:2px solid;
+}
+
+.x-tree-node .x-tree-drag-append a span{
+ border:1px dotted;
+}
+
+.x-dd-drag-ghost .x-tree-node-indent, .x-dd-drag-ghost .x-tree-ec-icon{
+ display:none !important;
+}
+
+/* Fix for ie rootVisible:false issue */
+.x-tree-root-ct {
+ zoom:1;
+}
+.x-date-picker {
+ border: 1px solid;
+ border-top:0 none;
+ position:relative;
+}
+
+.x-date-picker a {
+ -moz-outline:0 none;
+ outline:0 none;
+}
+
+.x-date-inner, .x-date-inner td, .x-date-inner th{
+ border-collapse:separate;
+}
+
+.x-date-middle,.x-date-left,.x-date-right {
+ background: repeat-x 0 -83px;
+ overflow:hidden;
+}
+
+.x-date-middle .x-btn-tc,.x-date-middle .x-btn-tl,.x-date-middle .x-btn-tr,
+.x-date-middle .x-btn-mc,.x-date-middle .x-btn-ml,.x-date-middle .x-btn-mr,
+.x-date-middle .x-btn-bc,.x-date-middle .x-btn-bl,.x-date-middle .x-btn-br{
+ background:transparent !important;
+ vertical-align:middle;
+}
+
+.x-date-middle .x-btn-mc em.x-btn-arrow {
+ background:transparent no-repeat right 0;
+}
+
+.x-date-right, .x-date-left {
+ width:18px;
+}
+
+.x-date-right{
+ text-align:right;
+}
+
+.x-date-middle {
+ padding-top:2px;
+ padding-bottom:2px;
+ width:130px; /* FF3 */
+}
+
+.x-date-right a, .x-date-left a{
+ display:block;
+ width:16px;
+ height:16px;
+ background-position: center;
+ background-repeat: no-repeat;
+ cursor:pointer;
+ -moz-opacity: 0.6;
+ opacity:.6;
+ filter: alpha(opacity=60);
+}
+
+.x-date-right a:hover, .x-date-left a:hover{
+ -moz-opacity: 1;
+ opacity:1;
+ filter: alpha(opacity=100);
+}
+
+.x-item-disabled .x-date-right a:hover, .x-item-disabled .x-date-left a:hover{
+ -moz-opacity: 0.6;
+ opacity:.6;
+ filter: alpha(opacity=60);
+}
+
+.x-date-right a {
+ margin-right:2px;
+ text-decoration:none !important;
+}
+
+.x-date-left a{
+ margin-left:2px;
+ text-decoration:none !important;
+}
+
+table.x-date-inner {
+ width: 100%;
+ table-layout:fixed;
+}
+
+.ext-webkit table.x-date-inner{
+ /* Fix for webkit browsers */
+ width: 175px;
+}
+
+
+.x-date-inner th {
+ width:25px;
+}
+
+.x-date-inner th {
+ background: repeat-x left top;
+ text-align:right !important;
+ border-bottom: 1px solid;
+ cursor:default;
+ padding:0;
+ border-collapse:separate;
+}
+
+.x-date-inner th span {
+ display:block;
+ padding:2px;
+ padding-right:7px;
+}
+
+.x-date-inner td {
+ border: 1px solid;
+ text-align:right;
+ padding:0;
+}
+
+.x-date-inner a {
+ padding:2px 5px;
+ display:block;
+ text-decoration:none;
+ text-align:right;
+ zoom:1;
+}
+
+.x-date-inner .x-date-active{
+ cursor:pointer;
+ color:black;
+}
+
+.x-date-inner .x-date-selected a{
+ background: repeat-x left top;
+ border:1px solid;
+ padding:1px 4px;
+}
+
+.x-date-inner .x-date-today a{
+ border: 1px solid;
+ padding:1px 4px;
+}
+
+.x-date-inner .x-date-prevday a,.x-date-inner .x-date-nextday a {
+ text-decoration:none !important;
+}
+
+.x-date-bottom {
+ padding:4px;
+ border-top: 1px solid;
+ background: repeat-x left top;
+}
+
+.x-date-inner a:hover, .x-date-inner .x-date-disabled a:hover{
+ text-decoration:none !important;
+}
+
+.x-item-disabled .x-date-inner a:hover{
+ background: none;
+}
+
+.x-date-inner .x-date-disabled a {
+ cursor:default;
+}
+
+.x-date-menu .x-menu-item {
+ padding:1px 24px 1px 4px;
+ white-space: nowrap;
+}
+
+.x-date-menu .x-menu-item .x-menu-item-icon {
+ width:10px;
+ height:10px;
+ margin-right:5px;
+ background-position:center -4px !important;
+}
+
+.x-date-mp {
+ position:absolute;
+ left:0;
+ top:0;
+ display:none;
+}
+
+.x-date-mp td {
+ padding:2px;
+ font:normal 11px arial, helvetica,tahoma,sans-serif;
+}
+
+td.x-date-mp-month,td.x-date-mp-year,td.x-date-mp-ybtn {
+ border: 0 none;
+ text-align:center;
+ vertical-align: middle;
+ width:25%;
+}
+
+.x-date-mp-ok {
+ margin-right:3px;
+}
+
+.x-date-mp-btns button {
+ text-decoration:none;
+ text-align:center;
+ text-decoration:none !important;
+ border:1px solid;
+ padding:1px 3px 1px;
+ cursor:pointer;
+}
+
+.x-date-mp-btns {
+ background: repeat-x left top;
+}
+
+.x-date-mp-btns td {
+ border-top: 1px solid;
+ text-align:center;
+}
+
+td.x-date-mp-month a,td.x-date-mp-year a {
+ display:block;
+ padding:2px 4px;
+ text-decoration:none;
+ text-align:center;
+}
+
+td.x-date-mp-month a:hover,td.x-date-mp-year a:hover {
+ text-decoration:none;
+ cursor:pointer;
+}
+
+td.x-date-mp-sel a {
+ padding:1px 3px;
+ background: repeat-x left top;
+ border:1px solid;
+}
+
+.x-date-mp-ybtn a {
+ overflow:hidden;
+ width:15px;
+ height:15px;
+ cursor:pointer;
+ background:transparent no-repeat;
+ display:block;
+ margin:0 auto;
+}
+
+.x-date-mp-ybtn a.x-date-mp-next {
+ background-position:0 -120px;
+}
+
+.x-date-mp-ybtn a.x-date-mp-next:hover {
+ background-position:-15px -120px;
+}
+
+.x-date-mp-ybtn a.x-date-mp-prev {
+ background-position:0 -105px;
+}
+
+.x-date-mp-ybtn a.x-date-mp-prev:hover {
+ background-position:-15px -105px;
+}
+
+.x-date-mp-ybtn {
+ text-align:center;
+}
+
+td.x-date-mp-sep {
+ border-right:1px solid;
+}.x-tip{
+ position: absolute;
+ top: 0;
+ left:0;
+ visibility: hidden;
+ z-index: 20002;
+ border:0 none;
+}
+
+.x-tip .x-tip-close{
+ height: 15px;
+ float:right;
+ width: 15px;
+ margin:0 0 2px 2px;
+ cursor:pointer;
+ display:none;
+}
+
+.x-tip .x-tip-tc {
+ background: transparent no-repeat 0 -62px;
+ padding-top:3px;
+ overflow:hidden;
+ zoom:1;
+}
+
+.x-tip .x-tip-tl {
+ background: transparent no-repeat 0 0;
+ padding-left:6px;
+ overflow:hidden;
+ zoom:1;
+}
+
+.x-tip .x-tip-tr {
+ background: transparent no-repeat right 0;
+ padding-right:6px;
+ overflow:hidden;
+ zoom:1;
+}
+
+.x-tip .x-tip-bc {
+ background: transparent no-repeat 0 -121px;
+ height:3px;
+ overflow:hidden;
+}
+
+.x-tip .x-tip-bl {
+ background: transparent no-repeat 0 -59px;
+ padding-left:6px;
+ zoom:1;
+}
+
+.x-tip .x-tip-br {
+ background: transparent no-repeat right -59px;
+ padding-right:6px;
+ zoom:1;
+}
+
+.x-tip .x-tip-mc {
+ border:0 none;
+}
+
+.x-tip .x-tip-ml {
+ background: no-repeat 0 -124px;
+ padding-left:6px;
+ zoom:1;
+}
+
+.x-tip .x-tip-mr {
+ background: transparent no-repeat right -124px;
+ padding-right:6px;
+ zoom:1;
+}
+
+.ext-ie .x-tip .x-tip-header,.ext-ie .x-tip .x-tip-tc {
+ font-size:0;
+ line-height:0;
+}
+
+.ext-border-box .x-tip .x-tip-header, .ext-border-box .x-tip .x-tip-tc{
+ line-height: 1px;
+}
+
+.x-tip .x-tip-header-text {
+ padding:0;
+ margin:0 0 2px 0;
+}
+
+.x-tip .x-tip-body {
+ margin:0 !important;
+ line-height:14px;
+ padding:0;
+}
+
+.x-tip .x-tip-body .loading-indicator {
+ margin:0;
+}
+
+.x-tip-draggable .x-tip-header,.x-tip-draggable .x-tip-header-text {
+ cursor:move;
+}
+
+.x-form-invalid-tip .x-tip-tc {
+ background: repeat-x 0 -12px;
+ padding-top:6px;
+}
+
+.x-form-invalid-tip .x-tip-bc {
+ background: repeat-x 0 -18px;
+ height:6px;
+}
+
+.x-form-invalid-tip .x-tip-bl {
+ background: no-repeat 0 -6px;
+}
+
+.x-form-invalid-tip .x-tip-br {
+ background: no-repeat right -6px;
+}
+
+.x-form-invalid-tip .x-tip-body {
+ padding:2px;
+}
+
+.x-form-invalid-tip .x-tip-body {
+ padding-left:24px;
+ background:transparent no-repeat 2px 2px;
+}
+
+.x-tip-anchor {
+ position: absolute;
+ width: 9px;
+ height: 10px;
+ overflow:hidden;
+ background: transparent no-repeat 0 0;
+ zoom:1;
+}
+.x-tip-anchor-bottom {
+ background-position: -9px 0;
+}
+.x-tip-anchor-right {
+ background-position: -18px 0;
+ width: 10px;
+}
+.x-tip-anchor-left {
+ background-position: -28px 0;
+ width: 10px;
+}.x-menu {
+ z-index: 15000;
+ zoom: 1;
+ background: repeat-y;
+}
+
+.x-menu-floating{
+ border: 1px solid;
+}
+
+.x-menu a {
+ text-decoration: none !important;
+}
+
+.ext-ie .x-menu {
+ zoom:1;
+ overflow:hidden;
+}
+
+.x-menu-list{
+ padding: 2px;
+ background-color:transparent;
+ border:0 none;
+ overflow:hidden;
+ overflow-y: hidden;
+}
+
+.ext-strict .ext-ie .x-menu-list{
+ position: relative;
+}
+
+.x-menu li{
+ line-height:100%;
+}
+
+.x-menu li.x-menu-sep-li{
+ font-size:1px;
+ line-height:1px;
+}
+
+.x-menu-list-item{
+ white-space: nowrap;
+ display:block;
+ padding:1px;
+}
+
+.x-menu-item{
+ -moz-user-select: none;
+ -khtml-user-select:none;
+ -webkit-user-select:ignore;
+}
+
+.x-menu-item-arrow{
+ background:transparent no-repeat right;
+}
+
+.x-menu-sep {
+ display:block;
+ font-size:1px;
+ line-height:1px;
+ margin: 2px 3px;
+ border-bottom:1px solid;
+ overflow:hidden;
+}
+
+.x-menu-focus {
+ position:absolute;
+ left:-1px;
+ top:-1px;
+ width:1px;
+ height:1px;
+ line-height:1px;
+ font-size:1px;
+ -moz-outline:0 none;
+ outline:0 none;
+ -moz-user-select: none;
+ -khtml-user-select:none;
+ -webkit-user-select:ignore;
+ overflow:hidden;
+ display:block;
+}
+
+a.x-menu-item {
+ cursor: pointer;
+ display: block;
+ line-height: 16px;
+ outline-color: -moz-use-text-color;
+ outline-style: none;
+ outline-width: 0;
+ padding: 3px 21px 3px 27px;
+ position: relative;
+ text-decoration: none;
+ white-space: nowrap;
+}
+
+.x-menu-item-active {
+ background-repeat: repeat-x;
+ background-position: left bottom;
+ border-style:solid;
+ border-width: 1px 0;
+ margin:0 1px;
+ padding: 0;
+}
+
+.x-menu-item-active a.x-menu-item {
+ border-style:solid;
+ border-width:0 1px;
+ margin:0 -1px;
+}
+
+.x-menu-item-icon {
+ border: 0 none;
+ height: 16px;
+ padding: 0;
+ vertical-align: top;
+ width: 16px;
+ position: absolute;
+ left: 3px;
+ top: 3px;
+ margin: 0;
+ background-position:center;
+}
+
+.ext-ie .x-menu-item-icon {
+ left: -24px;
+}
+.ext-strict .x-menu-item-icon {
+ left: 3px;
+}
+
+.ext-ie6 .x-menu-item-icon {
+ left: -24px;
+}
+
+.ext-ie .x-menu-item-icon {
+ vertical-align: middle;
+}
+
+.x-menu-check-item .x-menu-item-icon{
+ background: transparent no-repeat center;
+}
+
+.x-menu-group-item .x-menu-item-icon{
+ background-color: transparent;
+}
+
+.x-menu-item-checked .x-menu-group-item .x-menu-item-icon{
+ background: transparent no-repeat center;
+}
+
+.x-date-menu .x-menu-list{
+ padding: 0;
+}
+
+.x-menu-date-item{
+ padding:0;
+}
+
+.x-menu .x-color-palette, .x-menu .x-date-picker{
+ margin-left: 26px;
+ margin-right:4px;
+}
+
+.x-menu .x-date-picker{
+ border:1px solid;
+ margin-top:2px;
+ margin-bottom:2px;
+}
+
+.x-menu-plain .x-color-palette, .x-menu-plain .x-date-picker{
+ margin: 0;
+ border: 0 none;
+}
+
+.x-date-menu {
+ padding:0 !important;
+}
+
+/*
+ * fixes separator visibility problem in IE 6
+ */
+.ext-strict .ext-ie6 .x-menu-sep-li {
+ padding: 3px 4px;
+}
+.ext-strict .ext-ie6 .x-menu-sep {
+ margin: 0;
+ height: 1px;
+}
+
+/*
+ * Fixes an issue with "fat" separators in webkit
+ */
+.ext-webkit .x-menu-sep{
+ height: 1px;
+}
+
+/*
+ * Ugly mess to remove the white border under the picker
+ */
+.ext-ie .x-date-menu{
+ height: 199px;
+}
+
+.ext-strict .ext-ie .x-date-menu, .ext-border-box .ext-ie8 .x-date-menu{
+ height: 197px;
+}
+
+.ext-strict .ext-ie7 .x-date-menu{
+ height: 195px;
+}
+
+.ext-strict .ext-ie8 .x-date-menu{
+ height: auto;
+}
+
+.x-cycle-menu .x-menu-item-checked {
+ border:1px dotted !important;
+ padding:0;
+}
+
+.x-menu .x-menu-scroller {
+ width: 100%;
+ background-repeat:no-repeat;
+ background-position:center;
+ height:8px;
+ line-height: 8px;
+ cursor:pointer;
+ margin: 0;
+ padding: 0;
+}
+
+.x-menu .x-menu-scroller-active{
+ height: 6px;
+ line-height: 6px;
+}
+
+.x-menu-list-item-indent{
+ padding-left: 27px;
+}/*
+ Creates rounded, raised boxes like on the Ext website - the markup isn't pretty:
+ <div class="x-box-blue">
+ <div class="x-box-tl"><div class="x-box-tr"><div class="x-box-tc"></div></div></div>
+ <div class="x-box-ml"><div class="x-box-mr"><div class="x-box-mc">
+ <h3>YOUR TITLE HERE (optional)</h3>
+ <div>YOUR CONTENT HERE</div>
+ </div></div></div>
+ <div class="x-box-bl"><div class="x-box-br"><div class="x-box-bc"></div></div></div>
+ </div>
+ */
+
+.x-box-tl {
+ background: transparent no-repeat 0 0;
+ zoom:1;
+}
+
+.x-box-tc {
+ height: 8px;
+ background: transparent repeat-x 0 0;
+ overflow: hidden;
+}
+
+.x-box-tr {
+ background: transparent no-repeat right -8px;
+}
+
+.x-box-ml {
+ background: transparent repeat-y 0;
+ padding-left: 4px;
+ overflow: hidden;
+ zoom:1;
+}
+
+.x-box-mc {
+ background: repeat-x 0 -16px;
+ padding: 4px 10px;
+}
+
+.x-box-mc h3 {
+ margin: 0 0 4px 0;
+ zoom:1;
+}
+
+.x-box-mr {
+ background: transparent repeat-y right;
+ padding-right: 4px;
+ overflow: hidden;
+}
+
+.x-box-bl {
+ background: transparent no-repeat 0 -16px;
+ zoom:1;
+}
+
+.x-box-bc {
+ background: transparent repeat-x 0 -8px;
+ height: 8px;
+ overflow: hidden;
+}
+
+.x-box-br {
+ background: transparent no-repeat right -24px;
+}
+
+.x-box-tl, .x-box-bl {
+ padding-left: 8px;
+ overflow: hidden;
+}
+
+.x-box-tr, .x-box-br {
+ padding-right: 8px;
+ overflow: hidden;
+}.x-combo-list {
+ border:1px solid;
+ zoom:1;
+ overflow:hidden;
+}
+
+.x-combo-list-inner {
+ overflow:auto;
+ position:relative; /* for calculating scroll offsets */
+ zoom:1;
+ overflow-x:hidden;
+}
+
+.x-combo-list-hd {
+ border-bottom:1px solid;
+ padding:3px;
+}
+
+.x-resizable-pinned .x-combo-list-inner {
+ border-bottom:1px solid;
+}
+
+.x-combo-list-item {
+ padding:2px;
+ border:1px solid;
+ white-space: nowrap;
+ overflow:hidden;
+ text-overflow: ellipsis;
+}
+
+.x-combo-list .x-combo-selected{
+ border:1px dotted !important;
+ cursor:pointer;
+}
+
+.x-combo-list .x-toolbar {
+ border-top:1px solid;
+ border-bottom:0 none;
+}.x-panel {
+ border-style: solid;
+ border-width:0;
+}
+
+.x-panel-header {
+ overflow:hidden;
+ zoom:1;
+ padding:5px 3px 4px 5px;
+ border:1px solid;
+ line-height: 15px;
+ background: transparent repeat-x 0 -1px;
+}
+
+.x-panel-body {
+ border:1px solid;
+ border-top:0 none;
+ overflow:hidden;
+ position: relative; /* added for item scroll positioning */
+}
+
+.x-panel-bbar .x-toolbar, .x-panel-tbar .x-toolbar {
+ border:1px solid;
+ border-top:0 none;
+ overflow:hidden;
+ padding:2px;
+}
+
+.x-panel-tbar-noheader .x-toolbar, .x-panel-mc .x-panel-tbar .x-toolbar {
+ border-top:1px solid;
+ border-bottom: 0 none;
+}
+
+.x-panel-body-noheader, .x-panel-mc .x-panel-body {
+ border-top:1px solid;
+}
+
+.x-panel-header {
+ overflow:hidden;
+ zoom:1;
+}
+
+.x-panel-tl .x-panel-header {
+ padding:5px 0 4px 0;
+ border:0 none;
+ background:transparent no-repeat;
+}
+
+.x-panel-tl .x-panel-icon, .x-window-tl .x-panel-icon {
+ padding-left:20px !important;
+ background-repeat:no-repeat;
+ background-position:0 4px;
+ zoom:1;
+}
+
+.x-panel-inline-icon {
+ width:16px;
+ height:16px;
+ background-repeat:no-repeat;
+ background-position:0 0;
+ vertical-align:middle;
+ margin-right:4px;
+ margin-top:-1px;
+ margin-bottom:-1px;
+}
+
+.x-panel-tc {
+ background: transparent repeat-x 0 0;
+ overflow:hidden;
+}
+
+/* fix ie7 strict mode bug */
+.ext-strict .ext-ie7 .x-panel-tc {
+ overflow: visible;
+}
+
+.x-panel-tl {
+ background: transparent no-repeat 0 0;
+ padding-left:6px;
+ zoom:1;
+ border-bottom:1px solid;
+}
+
+.x-panel-tr {
+ background: transparent no-repeat right 0;
+ zoom:1;
+ padding-right:6px;
+}
+
+.x-panel-bc {
+ background: transparent repeat-x 0 bottom;
+ zoom:1;
+}
+
+.x-panel-bc .x-panel-footer {
+ zoom:1;
+}
+
+.x-panel-bl {
+ background: transparent no-repeat 0 bottom;
+ padding-left:6px;
+ zoom:1;
+}
+
+.x-panel-br {
+ background: transparent no-repeat right bottom;
+ padding-right:6px;
+ zoom:1;
+}
+
+.x-panel-mc {
+ border:0 none;
+ padding:0;
+ margin:0;
+ padding-top:6px;
+}
+
+.x-panel-mc .x-panel-body {
+ background-color:transparent;
+ border: 0 none;
+}
+
+.x-panel-ml {
+ background: repeat-y 0 0;
+ padding-left:6px;
+ zoom:1;
+}
+
+.x-panel-mr {
+ background: transparent repeat-y right 0;
+ padding-right:6px;
+ zoom:1;
+}
+
+.x-panel-bc .x-panel-footer {
+ padding-bottom:6px;
+}
+
+.x-panel-nofooter .x-panel-bc, .x-panel-nofooter .x-window-bc {
+ height:6px;
+ font-size:0;
+ line-height:0;
+}
+
+.x-panel-bwrap {
+ overflow:hidden;
+ zoom:1;
+ left:0;
+ top:0;
+}
+.x-panel-body {
+ overflow:hidden;
+ zoom:1;
+}
+
+.x-panel-collapsed .x-resizable-handle{
+ display:none;
+}
+
+.ext-gecko .x-panel-animated div {
+ overflow:hidden !important;
+}
+
+/* Plain */
+.x-plain-body {
+ overflow:hidden;
+}
+
+.x-plain-bbar .x-toolbar {
+ overflow:hidden;
+ padding:2px;
+}
+
+.x-plain-tbar .x-toolbar {
+ overflow:hidden;
+ padding:2px;
+}
+
+.x-plain-bwrap {
+ overflow:hidden;
+ zoom:1;
+}
+
+.x-plain {
+ overflow:hidden;
+}
+
+/* Tools */
+.x-tool {
+ overflow:hidden;
+ width:15px;
+ height:15px;
+ float:right;
+ cursor:pointer;
+ background:transparent no-repeat;
+ margin-left:2px;
+}
+
+/* expand / collapse tools */
+.x-tool-toggle {
+ background-position:0 -60px;
+}
+
+.x-tool-toggle-over {
+ background-position:-15px -60px;
+}
+
+.x-panel-collapsed .x-tool-toggle {
+ background-position:0 -75px;
+}
+
+.x-panel-collapsed .x-tool-toggle-over {
+ background-position:-15px -75px;
+}
+
+
+.x-tool-close {
+ background-position:0 -0;
+}
+
+.x-tool-close-over {
+ background-position:-15px 0;
+}
+
+.x-tool-minimize {
+ background-position:0 -15px;
+}
+
+.x-tool-minimize-over {
+ background-position:-15px -15px;
+}
+
+.x-tool-maximize {
+ background-position:0 -30px;
+}
+
+.x-tool-maximize-over {
+ background-position:-15px -30px;
+}
+
+.x-tool-restore {
+ background-position:0 -45px;
+}
+
+.x-tool-restore-over {
+ background-position:-15px -45px;
+}
+
+.x-tool-gear {
+ background-position:0 -90px;
+}
+
+.x-tool-gear-over {
+ background-position:-15px -90px;
+}
+
+.x-tool-prev {
+ background-position:0 -105px;
+}
+
+.x-tool-prev-over {
+ background-position:-15px -105px;
+}
+
+.x-tool-next {
+ background-position:0 -120px;
+}
+
+.x-tool-next-over {
+ background-position:-15px -120px;
+}
+
+.x-tool-pin {
+ background-position:0 -135px;
+}
+
+.x-tool-pin-over {
+ background-position:-15px -135px;
+}
+
+.x-tool-unpin {
+ background-position:0 -150px;
+}
+
+.x-tool-unpin-over {
+ background-position:-15px -150px;
+}
+
+.x-tool-right {
+ background-position:0 -165px;
+}
+
+.x-tool-right-over {
+ background-position:-15px -165px;
+}
+
+.x-tool-left {
+ background-position:0 -180px;
+}
+
+.x-tool-left-over {
+ background-position:-15px -180px;
+}
+
+.x-tool-down {
+ background-position:0 -195px;
+}
+
+.x-tool-down-over {
+ background-position:-15px -195px;
+}
+
+.x-tool-up {
+ background-position:0 -210px;
+}
+
+.x-tool-up-over {
+ background-position:-15px -210px;
+}
+
+.x-tool-refresh {
+ background-position:0 -225px;
+}
+
+.x-tool-refresh-over {
+ background-position:-15px -225px;
+}
+
+.x-tool-plus {
+ background-position:0 -240px;
+}
+
+.x-tool-plus-over {
+ background-position:-15px -240px;
+}
+
+.x-tool-minus {
+ background-position:0 -255px;
+}
+
+.x-tool-minus-over {
+ background-position:-15px -255px;
+}
+
+.x-tool-search {
+ background-position:0 -270px;
+}
+
+.x-tool-search-over {
+ background-position:-15px -270px;
+}
+
+.x-tool-save {
+ background-position:0 -285px;
+}
+
+.x-tool-save-over {
+ background-position:-15px -285px;
+}
+
+.x-tool-help {
+ background-position:0 -300px;
+}
+
+.x-tool-help-over {
+ background-position:-15px -300px;
+}
+
+.x-tool-print {
+ background-position:0 -315px;
+}
+
+.x-tool-print-over {
+ background-position:-15px -315px;
+}
+
+.x-tool-expand {
+ background-position:0 -330px;
+}
+
+.x-tool-expand-over {
+ background-position:-15px -330px;
+}
+
+.x-tool-collapse {
+ background-position:0 -345px;
+}
+
+.x-tool-collapse-over {
+ background-position:-15px -345px;
+}
+
+.x-tool-resize {
+ background-position:0 -360px;
+}
+
+.x-tool-resize-over {
+ background-position:-15px -360px;
+}
+
+.x-tool-move {
+ background-position:0 -375px;
+}
+
+.x-tool-move-over {
+ background-position:-15px -375px;
+}
+
+/* Ghosting */
+.x-panel-ghost {
+ z-index:12000;
+ overflow:hidden;
+ position:absolute;
+ left:0;top:0;
+ opacity:.65;
+ -moz-opacity:.65;
+ filter:alpha(opacity=65);
+}
+
+.x-panel-ghost ul {
+ margin:0;
+ padding:0;
+ overflow:hidden;
+ font-size:0;
+ line-height:0;
+ border:1px solid;
+ border-top:0 none;
+ display:block;
+}
+
+.x-panel-ghost * {
+ cursor:move !important;
+}
+
+.x-panel-dd-spacer {
+ border:2px dashed;
+}
+
+/* Buttons */
+.x-panel-btns {
+ padding:5px;
+ overflow:hidden;
+}
+
+.x-panel-btns td.x-toolbar-cell{
+ padding:3px;
+}
+
+.x-panel-btns .x-btn-focus .x-btn-left{
+ background-position:0 -147px;
+}
+
+.x-panel-btns .x-btn-focus .x-btn-right{
+ background-position:0 -168px;
+}
+
+.x-panel-btns .x-btn-focus .x-btn-center{
+ background-position:0 -189px;
+}
+
+.x-panel-btns .x-btn-over .x-btn-left{
+ background-position:0 -63px;
+}
+
+.x-panel-btns .x-btn-over .x-btn-right{
+ background-position:0 -84px;
+}
+
+.x-panel-btns .x-btn-over .x-btn-center{
+ background-position:0 -105px;
+}
+
+.x-panel-btns .x-btn-click .x-btn-center{
+ background-position:0 -126px;
+}
+
+.x-panel-btns .x-btn-click .x-btn-right{
+ background-position:0 -84px;
+}
+
+.x-panel-btns .x-btn-click .x-btn-left{
+ background-position:0 -63px;
+}
+
+.x-panel-fbar td,.x-panel-fbar span,.x-panel-fbar input,.x-panel-fbar div,.x-panel-fbar select,.x-panel-fbar label{
+ white-space: nowrap;
+}
+/**
+ * W3C Suggested Default style sheet for HTML 4
+ * http://www.w3.org/TR/CSS21/sample.html
+ *
+ * Resets for Ext.Panel @cfg normal: true
+ */
+.x-panel-reset .x-panel-body html,
+.x-panel-reset .x-panel-body address,
+.x-panel-reset .x-panel-body blockquote,
+.x-panel-reset .x-panel-body body,
+.x-panel-reset .x-panel-body dd,
+.x-panel-reset .x-panel-body div,
+.x-panel-reset .x-panel-body dl,
+.x-panel-reset .x-panel-body dt,
+.x-panel-reset .x-panel-body fieldset,
+.x-panel-reset .x-panel-body form,
+.x-panel-reset .x-panel-body frame, frameset,
+.x-panel-reset .x-panel-body h1,
+.x-panel-reset .x-panel-body h2,
+.x-panel-reset .x-panel-body h3,
+.x-panel-reset .x-panel-body h4,
+.x-panel-reset .x-panel-body h5,
+.x-panel-reset .x-panel-body h6,
+.x-panel-reset .x-panel-body noframes,
+.x-panel-reset .x-panel-body ol,
+.x-panel-reset .x-panel-body p,
+.x-panel-reset .x-panel-body ul,
+.x-panel-reset .x-panel-body center,
+.x-panel-reset .x-panel-body dir,
+.x-panel-reset .x-panel-body hr,
+.x-panel-reset .x-panel-body menu,
+.x-panel-reset .x-panel-body pre { display: block }
+.x-panel-reset .x-panel-body li { display: list-item }
+.x-panel-reset .x-panel-body head { display: none }
+.x-panel-reset .x-panel-body table { display: table }
+.x-panel-reset .x-panel-body tr { display: table-row }
+.x-panel-reset .x-panel-body thead { display: table-header-group }
+.x-panel-reset .x-panel-body tbody { display: table-row-group }
+.x-panel-reset .x-panel-body tfoot { display: table-footer-group }
+.x-panel-reset .x-panel-body col { display: table-column }
+.x-panel-reset .x-panel-body colgroup { display: table-column-group }
+.x-panel-reset .x-panel-body td,
+.x-panel-reset .x-panel-body th { display: table-cell }
+.x-panel-reset .x-panel-body caption { display: table-caption }
+.x-panel-reset .x-panel-body th { font-weight: bolder; text-align: center }
+.x-panel-reset .x-panel-body caption { text-align: center }
+.x-panel-reset .x-panel-body body { margin: 8px }
+.x-panel-reset .x-panel-body h1 { font-size: 2em; margin: .67em 0 }
+.x-panel-reset .x-panel-body h2 { font-size: 1.5em; margin: .75em 0 }
+.x-panel-reset .x-panel-body h3 { font-size: 1.17em; margin: .83em 0 }
+.x-panel-reset .x-panel-body h4,
+.x-panel-reset .x-panel-body p,
+.x-panel-reset .x-panel-body blockquote,
+.x-panel-reset .x-panel-body ul,
+.x-panel-reset .x-panel-body fieldset,
+.x-panel-reset .x-panel-body form,
+.x-panel-reset .x-panel-body ol,
+.x-panel-reset .x-panel-body dl,
+.x-panel-reset .x-panel-body dir,
+.x-panel-reset .x-panel-body menu { margin: 1.12em 0 }
+.x-panel-reset .x-panel-body h5 { font-size: .83em; margin: 1.5em 0 }
+.x-panel-reset .x-panel-body h6 { font-size: .75em; margin: 1.67em 0 }
+.x-panel-reset .x-panel-body h1,
+.x-panel-reset .x-panel-body h2,
+.x-panel-reset .x-panel-body h3,
+.x-panel-reset .x-panel-body h4,
+.x-panel-reset .x-panel-body h5,
+.x-panel-reset .x-panel-body h6,
+.x-panel-reset .x-panel-body b,
+.x-panel-reset .x-panel-body strong { font-weight: bolder }
+.x-panel-reset .x-panel-body blockquote { margin-left: 40px; margin-right: 40px }
+.x-panel-reset .x-panel-body i,
+.x-panel-reset .x-panel-body cite,
+.x-panel-reset .x-panel-body em,
+.x-panel-reset .x-panel-body var,
+.x-panel-reset .x-panel-body address { font-style: italic }
+.x-panel-reset .x-panel-body pre,
+.x-panel-reset .x-panel-body tt,
+.x-panel-reset .x-panel-body code,
+.x-panel-reset .x-panel-body kbd,
+.x-panel-reset .x-panel-body samp { font-family: monospace }
+.x-panel-reset .x-panel-body pre { white-space: pre }
+.x-panel-reset .x-panel-body button,
+.x-panel-reset .x-panel-body textarea,
+.x-panel-reset .x-panel-body input,
+.x-panel-reset .x-panel-body select { display: inline-block }
+.x-panel-reset .x-panel-body big { font-size: 1.17em }
+.x-panel-reset .x-panel-body small,
+.x-panel-reset .x-panel-body sub,
+.x-panel-reset .x-panel-body sup { font-size: .83em }
+.x-panel-reset .x-panel-body sub { vertical-align: sub }
+.x-panel-reset .x-panel-body sup { vertical-align: super }
+.x-panel-reset .x-panel-body table { border-spacing: 2px; }
+.x-panel-reset .x-panel-body thead,
+.x-panel-reset .x-panel-body tbody,
+.x-panel-reset .x-panel-body tfoot { vertical-align: middle }
+.x-panel-reset .x-panel-body td,
+.x-panel-reset .x-panel-body th { vertical-align: inherit }
+.x-panel-reset .x-panel-body s,
+.x-panel-reset .x-panel-body strike,
+.x-panel-reset .x-panel-body del { text-decoration: line-through }
+.x-panel-reset .x-panel-body hr { border: 1px inset }
+.x-panel-reset .x-panel-body ol,
+.x-panel-reset .x-panel-body ul,
+.x-panel-reset .x-panel-body dir,
+.x-panel-reset .x-panel-body menu,
+.x-panel-reset .x-panel-body dd { margin-left: 40px }
+.x-panel-reset .x-panel-body ul, .x-panel-reset .x-panel-body menu, .x-panel-reset .x-panel-body dir { list-style-type: disc;}
+.x-panel-reset .x-panel-body ol { list-style-type: decimal }
+.x-panel-reset .x-panel-body ol ul,
+.x-panel-reset .x-panel-body ul ol,
+.x-panel-reset .x-panel-body ul ul,
+.x-panel-reset .x-panel-body ol ol { margin-top: 0; margin-bottom: 0 }
+.x-panel-reset .x-panel-body u,
+.x-panel-reset .x-panel-body ins { text-decoration: underline }
+.x-panel-reset .x-panel-body br:before { content: "\A" }
+.x-panel-reset .x-panel-body :before, .x-panel-reset .x-panel-body :after { white-space: pre-line }
+.x-panel-reset .x-panel-body center { text-align: center }
+.x-panel-reset .x-panel-body :link, .x-panel-reset .x-panel-body :visited { text-decoration: underline }
+.x-panel-reset .x-panel-body :focus { outline: invert dotted thin }
+
+/* Begin bidirectionality settings (do not change) */
+.x-panel-reset .x-panel-body BDO[DIR="ltr"] { direction: ltr; unicode-bidi: bidi-override }
+.x-panel-reset .x-panel-body BDO[DIR="rtl"] { direction: rtl; unicode-bidi: bidi-override }
+.x-window {
+ zoom:1;
+}
+
+.x-window .x-window-handle {
+ opacity:0;
+ -moz-opacity:0;
+ filter:alpha(opacity=0);
+}
+
+.x-window-proxy {
+ border:1px solid;
+ z-index:12000;
+ overflow:hidden;
+ position:absolute;
+ left:0;top:0;
+ display:none;
+ opacity:.5;
+ -moz-opacity:.5;
+ filter:alpha(opacity=50);
+}
+
+.x-window-header {
+ overflow:hidden;
+ zoom:1;
+}
+
+.x-window-bwrap {
+ z-index:1;
+ position:relative;
+ zoom:1;
+ left:0;top:0;
+}
+
+.x-window-tl .x-window-header {
+ padding:5px 0 4px 0;
+}
+
+.x-window-header-text {
+ cursor:pointer;
+}
+
+.x-window-tc {
+ background: transparent repeat-x 0 0;
+ overflow:hidden;
+ zoom:1;
+}
+
+.x-window-tl {
+ background: transparent no-repeat 0 0;
+ padding-left:6px;
+ zoom:1;
+ z-index:1;
+ position:relative;
+}
+
+.x-window-tr {
+ background: transparent no-repeat right 0;
+ padding-right:6px;
+}
+
+.x-window-bc {
+ background: transparent repeat-x 0 bottom;
+ zoom:1;
+}
+
+.x-window-bc .x-window-footer {
+ padding-bottom:6px;
+ zoom:1;
+ font-size:0;
+ line-height:0;
+}
+
+.x-window-bl {
+ background: transparent no-repeat 0 bottom;
+ padding-left:6px;
+ zoom:1;
+}
+
+.x-window-br {
+ background: transparent no-repeat right bottom;
+ padding-right:6px;
+ zoom:1;
+}
+
+.x-window-mc {
+ border:1px solid;
+ padding:0;
+ margin:0;
+}
+
+.x-window-ml {
+ background: transparent repeat-y 0 0;
+ padding-left:6px;
+ zoom:1;
+}
+
+.x-window-mr {
+ background: transparent repeat-y right 0;
+ padding-right:6px;
+ zoom:1;
+}
+
+.x-window-body {
+ overflow:hidden;
+}
+
+.x-window-bwrap {
+ overflow:hidden;
+}
+
+.x-window-maximized .x-window-bl, .x-window-maximized .x-window-br,
+ .x-window-maximized .x-window-ml, .x-window-maximized .x-window-mr,
+ .x-window-maximized .x-window-tl, .x-window-maximized .x-window-tr {
+ padding:0;
+}
+
+.x-window-maximized .x-window-footer {
+ padding-bottom:0;
+}
+
+.x-window-maximized .x-window-tc {
+ padding-left:3px;
+ padding-right:3px;
+}
+
+.x-window-maximized .x-window-mc {
+ border-left:0 none;
+ border-right:0 none;
+}
+
+.x-window-tbar .x-toolbar, .x-window-bbar .x-toolbar {
+ border-left:0 none;
+ border-right: 0 none;
+}
+
+.x-window-bbar .x-toolbar {
+ border-top:1px solid;
+ border-bottom:0 none;
+}
+
+.x-window-draggable, .x-window-draggable .x-window-header-text {
+ cursor:move;
+}
+
+.x-window-maximized .x-window-draggable, .x-window-maximized .x-window-draggable .x-window-header-text {
+ cursor:default;
+}
+
+.x-window-body {
+ background-color:transparent;
+}
+
+.x-panel-ghost .x-window-tl {
+ border-bottom:1px solid;
+}
+
+.x-panel-collapsed .x-window-tl {
+ border-bottom:1px solid;
+}
+
+.x-window-maximized-ct {
+ overflow:hidden;
+}
+
+.x-window-maximized .x-window-handle {
+ display:none;
+}
+
+.x-window-sizing-ghost ul {
+ border:0 none !important;
+}
+
+.x-dlg-focus{
+ -moz-outline:0 none;
+ outline:0 none;
+ width:0;
+ height:0;
+ overflow:hidden;
+ position:absolute;
+ top:0;
+ left:0;
+}
+
+.ext-webkit .x-dlg-focus{
+ width: 1px;
+ height: 1px;
+}
+
+.x-dlg-mask{
+ z-index:10000;
+ display:none;
+ position:absolute;
+ top:0;
+ left:0;
+ -moz-opacity: 0.5;
+ opacity:.50;
+ filter: alpha(opacity=50);
+}
+
+body.ext-ie6.x-body-masked select {
+ visibility:hidden;
+}
+
+body.ext-ie6.x-body-masked .x-window select {
+ visibility:visible;
+}
+
+.x-window-plain .x-window-mc {
+ border: 1px solid;
+}
+
+.x-window-plain .x-window-body {
+ border: 1px solid;
+ background:transparent !important;
+}.x-html-editor-wrap {
+ border:1px solid;
+}
+
+.x-html-editor-tb .x-btn-text {
+ background:transparent no-repeat;
+}
+
+.x-html-editor-tb .x-edit-bold, .x-menu-item img.x-edit-bold {
+ background-position:0 0;
+ background-image:url(../images/default/editor/tb-sprite.gif);
+}
+
+.x-html-editor-tb .x-edit-italic, .x-menu-item img.x-edit-italic {
+ background-position:-16px 0;
+ background-image:url(../images/default/editor/tb-sprite.gif);
+}
+
+.x-html-editor-tb .x-edit-underline, .x-menu-item img.x-edit-underline {
+ background-position:-32px 0;
+ background-image:url(../images/default/editor/tb-sprite.gif);
+}
+
+.x-html-editor-tb .x-edit-forecolor, .x-menu-item img.x-edit-forecolor {
+ background-position:-160px 0;
+ background-image:url(../images/default/editor/tb-sprite.gif);
+}
+
+.x-html-editor-tb .x-edit-backcolor, .x-menu-item img.x-edit-backcolor {
+ background-position:-176px 0;
+ background-image:url(../images/default/editor/tb-sprite.gif);
+}
+
+.x-html-editor-tb .x-edit-justifyleft, .x-menu-item img.x-edit-justifyleft {
+ background-position:-112px 0;
+ background-image:url(../images/default/editor/tb-sprite.gif);
+}
+
+.x-html-editor-tb .x-edit-justifycenter, .x-menu-item img.x-edit-justifycenter {
+ background-position:-128px 0;
+ background-image:url(../images/default/editor/tb-sprite.gif);
+}
+
+.x-html-editor-tb .x-edit-justifyright, .x-menu-item img.x-edit-justifyright {
+ background-position:-144px 0;
+ background-image:url(../images/default/editor/tb-sprite.gif);
+}
+
+.x-html-editor-tb .x-edit-insertorderedlist, .x-menu-item img.x-edit-insertorderedlist {
+ background-position:-80px 0;
+ background-image:url(../images/default/editor/tb-sprite.gif);
+}
+
+.x-html-editor-tb .x-edit-insertunorderedlist, .x-menu-item img.x-edit-insertunorderedlist {
+ background-position:-96px 0;
+ background-image:url(../images/default/editor/tb-sprite.gif);
+}
+
+.x-html-editor-tb .x-edit-increasefontsize, .x-menu-item img.x-edit-increasefontsize {
+ background-position:-48px 0;
+ background-image:url(../images/default/editor/tb-sprite.gif);
+}
+
+.x-html-editor-tb .x-edit-decreasefontsize, .x-menu-item img.x-edit-decreasefontsize {
+ background-position:-64px 0;
+ background-image:url(../images/default/editor/tb-sprite.gif);
+}
+
+.x-html-editor-tb .x-edit-sourceedit, .x-menu-item img.x-edit-sourceedit {
+ background-position:-192px 0;
+ background-image:url(../images/default/editor/tb-sprite.gif);
+}
+
+.x-html-editor-tb .x-edit-createlink, .x-menu-item img.x-edit-createlink {
+ background-position:-208px 0;
+ background-image:url(../images/default/editor/tb-sprite.gif);
+}
+
+.x-html-editor-tip .x-tip-bd .x-tip-bd-inner {
+ padding:5px;
+ padding-bottom:1px;
+}
+
+.x-html-editor-tb .x-toolbar {
+ position:static !important;
+}.x-panel-noborder .x-panel-body-noborder {
+ border-width:0;
+}
+
+.x-panel-noborder .x-panel-header-noborder {
+ border-width:0 0 1px;
+ border-style:solid;
+}
+
+.x-panel-noborder .x-panel-tbar-noborder .x-toolbar {
+ border-width:0 0 1px;
+ border-style:solid;
+}
+
+.x-panel-noborder .x-panel-bbar-noborder .x-toolbar {
+ border-width:1px 0 0 0;
+ border-style:solid;
+}
+
+.x-window-noborder .x-window-mc {
+ border-width:0;
+}
+
+.x-window-plain .x-window-body-noborder {
+ border-width:0;
+}
+
+.x-tab-panel-noborder .x-tab-panel-body-noborder {
+ border-width:0;
+}
+
+.x-tab-panel-noborder .x-tab-panel-header-noborder {
+ border-width: 0 0 1px 0;
+}
+
+.x-tab-panel-noborder .x-tab-panel-footer-noborder {
+ border-width: 1px 0 0 0;
+}
+
+.x-tab-panel-bbar-noborder .x-toolbar {
+ border-width: 1px 0 0 0;
+ border-style:solid;
+}
+
+.x-tab-panel-tbar-noborder .x-toolbar {
+ border-width:0 0 1px;
+ border-style:solid;
+}.x-border-layout-ct {
+ position: relative;
+}
+
+.x-border-panel {
+ position:absolute;
+ left:0;
+ top:0;
+}
+
+.x-tool-collapse-south {
+ background-position:0 -195px;
+}
+
+.x-tool-collapse-south-over {
+ background-position:-15px -195px;
+}
+
+.x-tool-collapse-north {
+ background-position:0 -210px;
+}
+
+.x-tool-collapse-north-over {
+ background-position:-15px -210px;
+}
+
+.x-tool-collapse-west {
+ background-position:0 -180px;
+}
+
+.x-tool-collapse-west-over {
+ background-position:-15px -180px;
+}
+
+.x-tool-collapse-east {
+ background-position:0 -165px;
+}
+
+.x-tool-collapse-east-over {
+ background-position:-15px -165px;
+}
+
+.x-tool-expand-south {
+ background-position:0 -210px;
+}
+
+.x-tool-expand-south-over {
+ background-position:-15px -210px;
+}
+
+.x-tool-expand-north {
+ background-position:0 -195px;
+}
+.x-tool-expand-north-over {
+ background-position:-15px -195px;
+}
+
+.x-tool-expand-west {
+ background-position:0 -165px;
+}
+
+.x-tool-expand-west-over {
+ background-position:-15px -165px;
+}
+
+.x-tool-expand-east {
+ background-position:0 -180px;
+}
+
+.x-tool-expand-east-over {
+ background-position:-15px -180px;
+}
+
+.x-tool-expand-north, .x-tool-expand-south {
+ float:right;
+ margin:3px;
+}
+
+.x-tool-expand-east, .x-tool-expand-west {
+ float:none;
+ margin:3px 2px;
+}
+
+.x-accordion-hd .x-tool-toggle {
+ background-position:0 -255px;
+}
+
+.x-accordion-hd .x-tool-toggle-over {
+ background-position:-15px -255px;
+}
+
+.x-panel-collapsed .x-accordion-hd .x-tool-toggle {
+ background-position:0 -240px;
+}
+
+.x-panel-collapsed .x-accordion-hd .x-tool-toggle-over {
+ background-position:-15px -240px;
+}
+
+.x-accordion-hd {
+ padding-top:4px;
+ padding-bottom:3px;
+ border-top:0 none;
+ background: transparent repeat-x 0 -9px;
+}
+
+.x-layout-collapsed{
+ position:absolute;
+ left:-10000px;
+ top:-10000px;
+ visibility:hidden;
+ width:20px;
+ height:20px;
+ overflow:hidden;
+ border:1px solid;
+ z-index:20;
+}
+
+.ext-border-box .x-layout-collapsed{
+ width:22px;
+ height:22px;
+}
+
+.x-layout-collapsed-over{
+ cursor:pointer;
+}
+
+.x-layout-collapsed-west .x-layout-collapsed-tools, .x-layout-collapsed-east .x-layout-collapsed-tools{
+ position:absolute;
+ top:0;
+ left:0;
+ width:20px;
+ height:20px;
+}
+
+
+.x-layout-split{
+ position:absolute;
+ height:5px;
+ width:5px;
+ line-height:1px;
+ font-size:1px;
+ z-index:3;
+ background-color:transparent;
+}
+
+/* IE6 strict won't drag w/out a color */
+.ext-strict .ext-ie6 .x-layout-split{
+ background-color: #fff !important;
+ filter: alpha(opacity=1);
+}
+
+.x-layout-split-h{
+ background-image:url(../images/default/s.gif);
+ background-position: left;
+}
+
+.x-layout-split-v{
+ background-image:url(../images/default/s.gif);
+ background-position: top;
+}
+
+.x-column-layout-ct {
+ overflow:hidden;
+ zoom:1;
+}
+
+.x-column {
+ float:left;
+ padding:0;
+ margin:0;
+ overflow:hidden;
+ zoom:1;
+}
+
+.x-column-inner {
+ overflow:hidden;
+ zoom:1;
+}
+
+/* mini mode */
+.x-layout-mini {
+ position:absolute;
+ top:0;
+ left:0;
+ display:block;
+ width:5px;
+ height:35px;
+ cursor:pointer;
+ opacity:.5;
+ -moz-opacity:.5;
+ filter:alpha(opacity=50);
+}
+
+.x-layout-mini-over, .x-layout-collapsed-over .x-layout-mini{
+ opacity:1;
+ -moz-opacity:1;
+ filter:none;
+}
+
+.x-layout-split-west .x-layout-mini {
+ top:48%;
+}
+
+.x-layout-split-east .x-layout-mini {
+ top:48%;
+}
+
+.x-layout-split-north .x-layout-mini {
+ left:48%;
+ height:5px;
+ width:35px;
+}
+
+.x-layout-split-south .x-layout-mini {
+ left:48%;
+ height:5px;
+ width:35px;
+}
+
+.x-layout-cmini-west .x-layout-mini {
+ top:48%;
+}
+
+.x-layout-cmini-east .x-layout-mini {
+ top:48%;
+}
+
+.x-layout-cmini-north .x-layout-mini {
+ left:48%;
+ height:5px;
+ width:35px;
+}
+
+.x-layout-cmini-south .x-layout-mini {
+ left:48%;
+ height:5px;
+ width:35px;
+}
+
+.x-layout-cmini-west, .x-layout-cmini-east {
+ border:0 none;
+ width:5px !important;
+ padding:0;
+ background-color:transparent;
+}
+
+.x-layout-cmini-north, .x-layout-cmini-south {
+ border:0 none;
+ height:5px !important;
+ padding:0;
+ background-color:transparent;
+}
+
+.x-viewport, .x-viewport body {
+ margin: 0;
+ padding: 0;
+ border: 0 none;
+ overflow: hidden;
+ height: 100%;
+}
+
+.x-abs-layout-item {
+ position:absolute;
+ left:0;
+ top:0;
+}
+
+.ext-ie input.x-abs-layout-item, .ext-ie textarea.x-abs-layout-item {
+ margin:0;
+}
+
+.x-box-layout-ct {
+ overflow:hidden;
+ zoom:1;
+}
+
+.x-box-inner {
+ overflow:hidden;
+ zoom:1;
+ position:relative;
+ left:0;
+ top:0;
+}
+
+.x-box-item {
+ position:absolute;
+ left:0;
+ top:0;
+}.x-progress-wrap {
+ border:1px solid;
+ overflow:hidden;
+}
+
+.x-progress-inner {
+ height:18px;
+ background:repeat-x;
+ position:relative;
+}
+
+.x-progress-bar {
+ height:18px;
+ float:left;
+ width:0;
+ background: repeat-x left center;
+ border-top:1px solid;
+ border-bottom:1px solid;
+ border-right:1px solid;
+}
+
+.x-progress-text {
+ padding:1px 5px;
+ overflow:hidden;
+ position:absolute;
+ left:0;
+ text-align:center;
+}
+
+.x-progress-text-back {
+ line-height:16px;
+}
+
+.ext-ie .x-progress-text-back {
+ line-height:15px;
+}
+
+.ext-strict .ext-ie7 .x-progress-text-back{
+ width: 100%;
+}
+.x-list-header{
+ background: repeat-x 0 bottom;
+ cursor:default;
+ zoom:1;
+ height:22px;
+}
+
+.x-list-header-inner div {
+ display:block;
+ float:left;
+ overflow:hidden;
+ -o-text-overflow: ellipsis;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+}
+
+.x-list-header-inner div em {
+ display:block;
+ border-left:1px solid;
+ padding:4px 4px;
+ overflow:hidden;
+ -moz-user-select: none;
+ -khtml-user-select: none;
+ line-height:14px;
+}
+
+.x-list-body {
+ overflow:auto;
+ overflow-x:hidden;
+ overflow-y:auto;
+ zoom:1;
+ float: left;
+ width: 100%;
+}
+
+.x-list-body dl {
+ zoom:1;
+}
+
+.x-list-body dt {
+ display:block;
+ float:left;
+ overflow:hidden;
+ -o-text-overflow: ellipsis;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ cursor:pointer;
+ zoom:1;
+}
+
+.x-list-body dt em {
+ display:block;
+ padding:3px 4px;
+ overflow:hidden;
+ -moz-user-select: none;
+ -khtml-user-select: none;
+}
+
+.x-list-resizer {
+ border-left:1px solid;
+ border-right:1px solid;
+ position:absolute;
+ left:0;
+ top:0;
+}
+
+.x-list-header-inner em.sort-asc {
+ background: transparent no-repeat center 0;
+ border-style:solid;
+ border-width: 0 1px 1px;
+ padding-bottom:3px;
+}
+
+.x-list-header-inner em.sort-desc {
+ background: transparent no-repeat center -23px;
+ border-style:solid;
+ border-width: 0 1px 1px;
+ padding-bottom:3px;
+}
+
+/* Shared styles */
+.x-slider {
+ zoom:1;
+}
+
+.x-slider-inner {
+ position:relative;
+ left:0;
+ top:0;
+ overflow:visible;
+ zoom:1;
+}
+
+.x-slider-focus {
+ position:absolute;
+ left:0;
+ top:0;
+ width:1px;
+ height:1px;
+ line-height:1px;
+ font-size:1px;
+ -moz-outline:0 none;
+ outline:0 none;
+ -moz-user-select: none;
+ -khtml-user-select:none;
+ -webkit-user-select:ignore;
+ display:block;
+ overflow:hidden;
+}
+
+/* Horizontal styles */
+.x-slider-horz {
+ padding-left:7px;
+ background:transparent no-repeat 0 -22px;
+}
+
+.x-slider-horz .x-slider-end {
+ padding-right:7px;
+ zoom:1;
+ background:transparent no-repeat right -44px;
+}
+
+.x-slider-horz .x-slider-inner {
+ background:transparent repeat-x 0 0;
+ height:22px;
+}
+
+.x-slider-horz .x-slider-thumb {
+ width:14px;
+ height:15px;
+ position:absolute;
+ left:0;
+ top:3px;
+ background:transparent no-repeat 0 0;
+}
+
+.x-slider-horz .x-slider-thumb-over {
+ background-position: -14px -15px;
+}
+
+.x-slider-horz .x-slider-thumb-drag {
+ background-position: -28px -30px;
+}
+
+/* Vertical styles */
+.x-slider-vert {
+ padding-top:7px;
+ background:transparent no-repeat -44px 0;
+ width:22px;
+}
+
+.x-slider-vert .x-slider-end {
+ padding-bottom:7px;
+ zoom:1;
+ background:transparent no-repeat -22px bottom;
+}
+
+.x-slider-vert .x-slider-inner {
+ background:transparent repeat-y 0 0;
+}
+
+.x-slider-vert .x-slider-thumb {
+ width:15px;
+ height:14px;
+ position:absolute;
+ left:3px;
+ bottom:0;
+ background:transparent no-repeat 0 0;
+}
+
+.x-slider-vert .x-slider-thumb-over {
+ background-position: -15px -14px;
+}
+
+.x-slider-vert .x-slider-thumb-drag {
+ background-position: -30px -28px;
+}.x-window-dlg .x-window-body {
+ border:0 none !important;
+ padding:5px 10px;
+ overflow:hidden !important;
+}
+
+.x-window-dlg .x-window-mc {
+ border:0 none !important;
+}
+
+.x-window-dlg .ext-mb-input {
+ margin-top:4px;
+ width:95%;
+}
+
+.x-window-dlg .ext-mb-textarea {
+ margin-top:4px;
+}
+
+.x-window-dlg .x-progress-wrap {
+ margin-top:4px;
+}
+
+.ext-ie .x-window-dlg .x-progress-wrap {
+ margin-top:6px;
+}
+
+.x-window-dlg .x-msg-box-wait {
+ background:transparent no-repeat left;
+ display:block;
+ width:300px;
+ padding-left:18px;
+ line-height:18px;
+}
+
+.x-window-dlg .ext-mb-icon {
+ float:left;
+ width:47px;
+ height:32px;
+}
+
+.x-window-dlg .x-dlg-icon .ext-mb-content{
+ zoom: 1;
+ margin-left: 47px;
+}
+
+.x-window-dlg .ext-mb-info, .x-window-dlg .ext-mb-warning, .x-window-dlg .ext-mb-question, .x-window-dlg .ext-mb-error {
+ background:transparent no-repeat top left;
+}
+
+.ext-gecko2 .ext-mb-fix-cursor {
+ overflow:auto;
+}
diff --git a/deluge/ui/web/css/ext-extensions-debug.css b/deluge/ui/web/css/ext-extensions-debug.css
new file mode 100644
index 0000000..89a41fd
--- /dev/null
+++ b/deluge/ui/web/css/ext-extensions-debug.css
@@ -0,0 +1,261 @@
+/*
+ * ColumnTree styles
+ */
+.x-column-tree .x-tree-node {
+ zoom:1;
+}
+.x-column-tree .x-tree-node-el {
+ /*border-bottom:1px solid #eee; borders? */
+ zoom:1;
+}
+.x-column-tree .x-tree-selected {
+ background: #d9e8fb;
+}
+.x-column-tree .x-tree-node a {
+ line-height:18px;
+ vertical-align:middle;
+}
+.x-column-tree .x-tree-node a span{
+
+}
+.x-column-tree .x-tree-node .x-tree-selected a span{
+ background:transparent;
+ color:#000;
+}
+.x-tree-col {
+ float:left;
+ overflow:hidden;
+ padding:0 1px;
+ zoom:1;
+}
+
+.x-tree-col-text, .x-tree-hd-text {
+ overflow:hidden;
+ -o-text-overflow: ellipsis;
+ text-overflow: ellipsis;
+ padding:3px 3px 3px 5px;
+ white-space: nowrap;
+ font:normal 11px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-tree-headers {
+ background: #f9f9f9 url(../../resources/images/default/grid/grid3-hrow.gif) repeat-x 0 bottom;
+ cursor:default;
+ zoom:1;
+}
+
+.x-tree-hd {
+ float:left;
+ overflow:hidden;
+ border-left:1px solid #eee;
+ border-right:1px solid #d0d0d0;
+}
+
+/*
+ * FileUploadField component styles
+ */
+.x-form-file-wrap {
+ position: relative;
+ height: 22px;
+}
+.x-form-file-wrap .x-form-file {
+ position: absolute;
+ right: 0;
+ -moz-opacity: 0;
+ filter:alpha(opacity: 0);
+ opacity: 0;
+ z-index: 2;
+ height: 22px;
+ cursor: pointer;
+}
+.x-form-file-wrap .x-btn {
+ position: absolute;
+ right: 0;
+ z-index: 1;
+}
+.x-form-file-wrap .x-form-file-text {
+ position: absolute;
+ left: 0;
+ z-index: 3;
+ color: #777;
+}
+
+/*
+ * Spinner styles
+ */
+.x-form-spinner-proxy{
+ /*background-color:#ff00cc;*/
+}
+.x-form-field-wrap .x-form-spinner-trigger {
+ background: transparent url(../images/spinner.gif) no-repeat 0 0;
+}
+
+.x-form-field-wrap .x-form-spinner-overup{
+ background-position:-17px 0;
+}
+.x-form-field-wrap .x-form-spinner-clickup{
+ background-position:-34px 0;
+}
+.x-form-field-wrap .x-form-spinner-overdown{
+ background-position:-51px 0;
+}
+.x-form-field-wrap .x-form-spinner-clickdown{
+ background-position:-68px 0;
+}
+
+
+.x-trigger-wrap-focus .x-form-spinner-trigger{
+ background-position:-85px 0;
+}
+.x-trigger-wrap-focus .x-form-spinner-overup{
+ background-position:-102px 0;
+}
+.x-trigger-wrap-focus .x-form-spinner-clickup{
+ background-position:-119px 0;
+}
+.x-trigger-wrap-focus .x-form-spinner-overdown{
+ background-position:-136px 0;
+}
+.x-trigger-wrap-focus .x-form-spinner-clickdown{
+ background-position:-153px 0;
+}
+.x-trigger-wrap-focus .x-form-trigger{
+ border-bottom: 1px solid #7eadd9;
+}
+
+.x-form-field-wrap .x-form-spinner-splitter {
+ line-height:1px;
+ font-size:1px;
+ background:transparent url(../images/spinner-split.gif) no-repeat 0 0;
+ position:absolute;
+ cursor: n-resize;
+}
+.x-trigger-wrap-focus .x-form-spinner-splitter{
+ background-position:-14px 0;
+}
+
+.x-form-uxspinner .x-form-field-wrap {
+ height: 24px;
+}
+
+/*
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+/* StatusBar - structure */
+.x-statusbar .x-status-text {
+ cursor: default;
+/*
+ height: 21px;
+ line-height: 21px;
+ padding: 0 4px;
+*/
+}
+.x-statusbar .x-status-busy {
+ padding-left: 25px !important;
+ background: transparent no-repeat 3px 2px;
+}
+
+.x-toolbar div.xtb-text
+
+.x-statusbar .x-status-text-panel {
+ border-top: 1px solid;
+ border-right: 1px solid;
+ border-bottom: 1px solid;
+ border-left: 1px solid;
+ padding: 2px 8px 2px 5px;
+}
+
+/* StatusBar word processor example styles */
+
+#word-status .x-status-text-panel .spacer {
+ width: 60px;
+ font-size:0;
+ line-height:0;
+}
+#word-status .x-status-busy {
+ padding-left: 25px !important;
+ background: transparent no-repeat 3px 2px;
+}
+#word-status .x-status-saved {
+ padding-left: 25px !important;
+ background: transparent no-repeat 3px 2px;
+}
+
+/* StatusBar form validation example styles */
+
+.x-statusbar .x-status-error {
+ cursor: pointer;
+ padding-left: 25px !important;
+ background: transparent no-repeat 3px 2px;
+}
+.x-statusbar .x-status-valid {
+ padding-left: 25px !important;
+ background: transparent no-repeat 3px 2px;
+}
+.x-status-error-list {
+ font: 11px tahoma,arial,verdana,sans-serif;
+ position: absolute;
+ z-index: 9999;
+ border-top: 1px solid;
+ border-right: 1px solid;
+ border-bottom: 1px solid;
+ border-left: 1px solid;
+ padding: 5px 10px;
+}
+.x-status-error-list li {
+ cursor: pointer;
+ list-style: disc;
+ margin-left: 10px;
+}
+.x-status-error-list li a {
+ text-decoration: none;
+}
+.x-status-error-list li a:hover {
+ text-decoration: underline;
+}
+
+
+/* *********************************************************** */
+/* *********************************************************** */
+/* *********************************************************** */
+
+
+/* StatusBar - visual */
+
+.x-statusbar .x-status-busy {
+ background-image: url(../images/loading.gif);
+}
+.x-statusbar .x-status-text-panel {
+ border-color: #99bbe8 #fff #fff #99bbe8;
+}
+
+/* StatusBar word processor example styles */
+
+#word-status .x-status-text {
+ color: #777;
+}
+#word-status .x-status-busy {
+ background-image: url(../images/saving.gif);
+}
+#word-status .x-status-saved {
+ background-image: url(../images/saved.png);
+}
+
+/* StatusBar form validation example styles */
+
+.x-statusbar .x-status-error {
+ color: #C33;
+ background-image: url(../images/exclamation.gif);
+}
+.x-statusbar .x-status-valid {
+ background-image: url(../images/accept.png);
+}
+.x-status-error-list {
+ border-color: #C33;
+}
+.x-status-error-list li a {
+ color: #15428B;
+}
diff --git a/deluge/ui/web/css/ext-extensions.css b/deluge/ui/web/css/ext-extensions.css
new file mode 100644
index 0000000..63df411
--- /dev/null
+++ b/deluge/ui/web/css/ext-extensions.css
@@ -0,0 +1 @@
+ .x-column-tree .x-tree-node{zoom:1}.x-column-tree .x-tree-node-el{zoom:1}.x-column-tree .x-tree-selected{background:#d9e8fb}.x-column-tree .x-tree-node a{line-height:18px;vertical-align:middle}.x-column-tree .x-tree-node .x-tree-selected a span{background:transparent;color:#000}.x-tree-col{float:left;overflow:hidden;padding:0 1px;zoom:1}.x-tree-col-text,.x-tree-hd-text{overflow:hidden;-o-text-overflow:ellipsis;text-overflow:ellipsis;padding:3px 3px 3px 5px;white-space:nowrap;font:normal 11px arial,tahoma,helvetica,sans-serif}.x-tree-headers{background:#f9f9f9 url(../../resources/images/default/grid/grid3-hrow.gif) repeat-x 0 bottom;cursor:default;zoom:1}.x-tree-hd{float:left;overflow:hidden;border-left:1px solid #eee;border-right:1px solid #d0d0d0}.x-form-file-wrap{position:relative;height:22px}.x-form-file-wrap .x-form-file{position:absolute;right:0;-moz-opacity:0;filter:alpha(opacity:0);opacity:0;z-index:2;height:22px;cursor:pointer}.x-form-file-wrap .x-btn{position:absolute;right:0;z-index:1}.x-form-file-wrap .x-form-file-text{position:absolute;left:0;z-index:3;color:#777}.x-form-field-wrap .x-form-spinner-trigger{background:transparent url(../images/spinner.gif) no-repeat 0 0}.x-form-field-wrap .x-form-spinner-overup{background-position:-17px 0}.x-form-field-wrap .x-form-spinner-clickup{background-position:-34px 0}.x-form-field-wrap .x-form-spinner-overdown{background-position:-51px 0}.x-form-field-wrap .x-form-spinner-clickdown{background-position:-68px 0}.x-trigger-wrap-focus .x-form-spinner-trigger{background-position:-85px 0}.x-trigger-wrap-focus .x-form-spinner-overup{background-position:-102px 0}.x-trigger-wrap-focus .x-form-spinner-clickup{background-position:-119px 0}.x-trigger-wrap-focus .x-form-spinner-overdown{background-position:-136px 0}.x-trigger-wrap-focus .x-form-spinner-clickdown{background-position:-153px 0}.x-trigger-wrap-focus .x-form-trigger{border-bottom:1px solid #7eadd9}.x-form-field-wrap .x-form-spinner-splitter{line-height:1px;font-size:1px;background:transparent url(../images/spinner-split.gif) no-repeat 0 0;position:absolute;cursor:n-resize}.x-trigger-wrap-focus .x-form-spinner-splitter{background-position:-14px 0}.x-form-uxspinner .x-form-field-wrap{height:24px}.x-statusbar .x-status-text{cursor:default}.x-statusbar .x-status-busy{padding-left:25px!important;background:transparent no-repeat 3px 2px}.x-toolbar div.xtb-text .x-statusbar .x-status-text-panel{border-top:1px solid;border-right:1px solid;border-bottom:1px solid;border-left:1px solid;padding:2px 8px 2px 5px}#word-status .x-status-text-panel .spacer{width:60px;font-size:0;line-height:0}#word-status .x-status-busy{padding-left:25px!important;background:transparent no-repeat 3px 2px}#word-status .x-status-saved{padding-left:25px!important;background:transparent no-repeat 3px 2px}.x-statusbar .x-status-error{cursor:pointer;padding-left:25px!important;background:transparent no-repeat 3px 2px}.x-statusbar .x-status-valid{padding-left:25px!important;background:transparent no-repeat 3px 2px}.x-status-error-list{font:11px tahoma,arial,verdana,sans-serif;position:absolute;z-index:9999;border-top:1px solid;border-right:1px solid;border-bottom:1px solid;border-left:1px solid;padding:5px 10px}.x-status-error-list li{cursor:pointer;list-style:disc;margin-left:10px}.x-status-error-list li a{text-decoration:none}.x-status-error-list li a:hover{text-decoration:underline}.x-statusbar .x-status-busy{background-image:url(../images/loading.gif)}.x-statusbar .x-status-text-panel{border-color:#99bbe8 #fff #fff #99bbe8}#word-status .x-status-text{color:#777}#word-status .x-status-busy{background-image:url(../images/saving.gif)}#word-status .x-status-saved{background-image:url(../images/saved.png)}.x-statusbar .x-status-error{color:#C33;background-image:url(../images/exclamation.gif)}.x-statusbar .x-status-valid{background-image:url(../images/accept.png)}.x-status-error-list{border-color:#C33}.x-status-error-list li a{color:#15428b} \ No newline at end of file
diff --git a/deluge/ui/web/icons/active.png b/deluge/ui/web/icons/active.png
new file mode 100644
index 0000000..daa4f64
--- /dev/null
+++ b/deluge/ui/web/icons/active.png
Binary files differ
diff --git a/deluge/ui/web/icons/add.png b/deluge/ui/web/icons/add.png
new file mode 100644
index 0000000..3bc06ca
--- /dev/null
+++ b/deluge/ui/web/icons/add.png
Binary files differ
diff --git a/deluge/ui/web/icons/add_file.png b/deluge/ui/web/icons/add_file.png
new file mode 100644
index 0000000..6a05745
--- /dev/null
+++ b/deluge/ui/web/icons/add_file.png
Binary files differ
diff --git a/deluge/ui/web/icons/add_magnet.png b/deluge/ui/web/icons/add_magnet.png
new file mode 100644
index 0000000..c015b18
--- /dev/null
+++ b/deluge/ui/web/icons/add_magnet.png
Binary files differ
diff --git a/deluge/ui/web/icons/add_url.png b/deluge/ui/web/icons/add_url.png
new file mode 100644
index 0000000..74dc99f
--- /dev/null
+++ b/deluge/ui/web/icons/add_url.png
Binary files differ
diff --git a/deluge/ui/web/icons/alert.png b/deluge/ui/web/icons/alert.png
new file mode 100644
index 0000000..7036638
--- /dev/null
+++ b/deluge/ui/web/icons/alert.png
Binary files differ
diff --git a/deluge/ui/web/icons/all.png b/deluge/ui/web/icons/all.png
new file mode 100644
index 0000000..c63f8df
--- /dev/null
+++ b/deluge/ui/web/icons/all.png
Binary files differ
diff --git a/deluge/ui/web/icons/back.png b/deluge/ui/web/icons/back.png
new file mode 100644
index 0000000..fdcf9f0
--- /dev/null
+++ b/deluge/ui/web/icons/back.png
Binary files differ
diff --git a/deluge/ui/web/icons/bottom.png b/deluge/ui/web/icons/bottom.png
new file mode 100644
index 0000000..ca32c1f
--- /dev/null
+++ b/deluge/ui/web/icons/bottom.png
Binary files differ
diff --git a/deluge/ui/web/icons/checking.png b/deluge/ui/web/icons/checking.png
new file mode 100644
index 0000000..7487352
--- /dev/null
+++ b/deluge/ui/web/icons/checking.png
Binary files differ
diff --git a/deluge/ui/web/icons/connection_manager.png b/deluge/ui/web/icons/connection_manager.png
new file mode 100644
index 0000000..a033306
--- /dev/null
+++ b/deluge/ui/web/icons/connection_manager.png
Binary files differ
diff --git a/deluge/ui/web/icons/connections.png b/deluge/ui/web/icons/connections.png
new file mode 100644
index 0000000..f0b660b
--- /dev/null
+++ b/deluge/ui/web/icons/connections.png
Binary files differ
diff --git a/deluge/ui/web/icons/create.png b/deluge/ui/web/icons/create.png
new file mode 100644
index 0000000..9008c98
--- /dev/null
+++ b/deluge/ui/web/icons/create.png
Binary files differ
diff --git a/deluge/ui/web/icons/deluge-192.png b/deluge/ui/web/icons/deluge-192.png
new file mode 100644
index 0000000..b49a318
--- /dev/null
+++ b/deluge/ui/web/icons/deluge-192.png
Binary files differ
diff --git a/deluge/ui/web/icons/deluge-32.png b/deluge/ui/web/icons/deluge-32.png
new file mode 100644
index 0000000..6787fa3
--- /dev/null
+++ b/deluge/ui/web/icons/deluge-32.png
Binary files differ
diff --git a/deluge/ui/web/icons/deluge-512.png b/deluge/ui/web/icons/deluge-512.png
new file mode 100644
index 0000000..866f6b9
--- /dev/null
+++ b/deluge/ui/web/icons/deluge-512.png
Binary files differ
diff --git a/deluge/ui/web/icons/deluge-apple-180.png b/deluge/ui/web/icons/deluge-apple-180.png
new file mode 100644
index 0000000..82f7be2
--- /dev/null
+++ b/deluge/ui/web/icons/deluge-apple-180.png
Binary files differ
diff --git a/deluge/ui/web/icons/deluge.png b/deluge/ui/web/icons/deluge.png
new file mode 100644
index 0000000..2f4ae4c
--- /dev/null
+++ b/deluge/ui/web/icons/deluge.png
Binary files differ
diff --git a/deluge/ui/web/icons/dht.png b/deluge/ui/web/icons/dht.png
new file mode 100644
index 0000000..363ee0c
--- /dev/null
+++ b/deluge/ui/web/icons/dht.png
Binary files differ
diff --git a/deluge/ui/web/icons/document.png b/deluge/ui/web/icons/document.png
new file mode 100644
index 0000000..5ee0018
--- /dev/null
+++ b/deluge/ui/web/icons/document.png
Binary files differ
diff --git a/deluge/ui/web/icons/down.png b/deluge/ui/web/icons/down.png
new file mode 100644
index 0000000..68be2b3
--- /dev/null
+++ b/deluge/ui/web/icons/down.png
Binary files differ
diff --git a/deluge/ui/web/icons/downloading.png b/deluge/ui/web/icons/downloading.png
new file mode 100644
index 0000000..ec58cb5
--- /dev/null
+++ b/deluge/ui/web/icons/downloading.png
Binary files differ
diff --git a/deluge/ui/web/icons/drive.png b/deluge/ui/web/icons/drive.png
new file mode 100644
index 0000000..6fd688e
--- /dev/null
+++ b/deluge/ui/web/icons/drive.png
Binary files differ
diff --git a/deluge/ui/web/icons/edit_trackers.png b/deluge/ui/web/icons/edit_trackers.png
new file mode 100644
index 0000000..80455ec
--- /dev/null
+++ b/deluge/ui/web/icons/edit_trackers.png
Binary files differ
diff --git a/deluge/ui/web/icons/error.png b/deluge/ui/web/icons/error.png
new file mode 100644
index 0000000..20ad66a
--- /dev/null
+++ b/deluge/ui/web/icons/error.png
Binary files differ
diff --git a/deluge/ui/web/icons/expand_all.png b/deluge/ui/web/icons/expand_all.png
new file mode 100644
index 0000000..050419f
--- /dev/null
+++ b/deluge/ui/web/icons/expand_all.png
Binary files differ
diff --git a/deluge/ui/web/icons/favicon.ico b/deluge/ui/web/icons/favicon.ico
new file mode 100644
index 0000000..4e6f124
--- /dev/null
+++ b/deluge/ui/web/icons/favicon.ico
Binary files differ
diff --git a/deluge/ui/web/icons/find_more.png b/deluge/ui/web/icons/find_more.png
new file mode 100644
index 0000000..199d73e
--- /dev/null
+++ b/deluge/ui/web/icons/find_more.png
Binary files differ
diff --git a/deluge/ui/web/icons/forward.png b/deluge/ui/web/icons/forward.png
new file mode 100644
index 0000000..2e55489
--- /dev/null
+++ b/deluge/ui/web/icons/forward.png
Binary files differ
diff --git a/deluge/ui/web/icons/help.png b/deluge/ui/web/icons/help.png
new file mode 100644
index 0000000..0566ae0
--- /dev/null
+++ b/deluge/ui/web/icons/help.png
Binary files differ
diff --git a/deluge/ui/web/icons/high.png b/deluge/ui/web/icons/high.png
new file mode 100644
index 0000000..4b2b1ff
--- /dev/null
+++ b/deluge/ui/web/icons/high.png
Binary files differ
diff --git a/deluge/ui/web/icons/home.png b/deluge/ui/web/icons/home.png
new file mode 100644
index 0000000..a319df6
--- /dev/null
+++ b/deluge/ui/web/icons/home.png
Binary files differ
diff --git a/deluge/ui/web/icons/inactive.png b/deluge/ui/web/icons/inactive.png
new file mode 100644
index 0000000..b56213e
--- /dev/null
+++ b/deluge/ui/web/icons/inactive.png
Binary files differ
diff --git a/deluge/ui/web/icons/install_plugin.png b/deluge/ui/web/icons/install_plugin.png
new file mode 100644
index 0000000..aff31e7
--- /dev/null
+++ b/deluge/ui/web/icons/install_plugin.png
Binary files differ
diff --git a/deluge/ui/web/icons/login.png b/deluge/ui/web/icons/login.png
new file mode 100644
index 0000000..c06d0d1
--- /dev/null
+++ b/deluge/ui/web/icons/login.png
Binary files differ
diff --git a/deluge/ui/web/icons/logout.png b/deluge/ui/web/icons/logout.png
new file mode 100644
index 0000000..2f53a65
--- /dev/null
+++ b/deluge/ui/web/icons/logout.png
Binary files differ
diff --git a/deluge/ui/web/icons/low.png b/deluge/ui/web/icons/low.png
new file mode 100644
index 0000000..ff669db
--- /dev/null
+++ b/deluge/ui/web/icons/low.png
Binary files differ
diff --git a/deluge/ui/web/icons/move.png b/deluge/ui/web/icons/move.png
new file mode 100644
index 0000000..ea83832
--- /dev/null
+++ b/deluge/ui/web/icons/move.png
Binary files differ
diff --git a/deluge/ui/web/icons/no_download.png b/deluge/ui/web/icons/no_download.png
new file mode 100644
index 0000000..206d436
--- /dev/null
+++ b/deluge/ui/web/icons/no_download.png
Binary files differ
diff --git a/deluge/ui/web/icons/normal.png b/deluge/ui/web/icons/normal.png
new file mode 100644
index 0000000..2e55489
--- /dev/null
+++ b/deluge/ui/web/icons/normal.png
Binary files differ
diff --git a/deluge/ui/web/icons/ok.png b/deluge/ui/web/icons/ok.png
new file mode 100644
index 0000000..33eb7db
--- /dev/null
+++ b/deluge/ui/web/icons/ok.png
Binary files differ
diff --git a/deluge/ui/web/icons/pause.png b/deluge/ui/web/icons/pause.png
new file mode 100644
index 0000000..8fdd6bc
--- /dev/null
+++ b/deluge/ui/web/icons/pause.png
Binary files differ
diff --git a/deluge/ui/web/icons/preferences.png b/deluge/ui/web/icons/preferences.png
new file mode 100644
index 0000000..7d6deb2
--- /dev/null
+++ b/deluge/ui/web/icons/preferences.png
Binary files differ
diff --git a/deluge/ui/web/icons/queue.png b/deluge/ui/web/icons/queue.png
new file mode 100644
index 0000000..3e4b4be
--- /dev/null
+++ b/deluge/ui/web/icons/queue.png
Binary files differ
diff --git a/deluge/ui/web/icons/queued.png b/deluge/ui/web/icons/queued.png
new file mode 100644
index 0000000..f9f7454
--- /dev/null
+++ b/deluge/ui/web/icons/queued.png
Binary files differ
diff --git a/deluge/ui/web/icons/recheck.png b/deluge/ui/web/icons/recheck.png
new file mode 100644
index 0000000..199d73e
--- /dev/null
+++ b/deluge/ui/web/icons/recheck.png
Binary files differ
diff --git a/deluge/ui/web/icons/remove.png b/deluge/ui/web/icons/remove.png
new file mode 100644
index 0000000..3f91191
--- /dev/null
+++ b/deluge/ui/web/icons/remove.png
Binary files differ
diff --git a/deluge/ui/web/icons/seeding.png b/deluge/ui/web/icons/seeding.png
new file mode 100644
index 0000000..fce70a8
--- /dev/null
+++ b/deluge/ui/web/icons/seeding.png
Binary files differ
diff --git a/deluge/ui/web/icons/start.png b/deluge/ui/web/icons/start.png
new file mode 100644
index 0000000..ff669db
--- /dev/null
+++ b/deluge/ui/web/icons/start.png
Binary files differ
diff --git a/deluge/ui/web/icons/top.png b/deluge/ui/web/icons/top.png
new file mode 100644
index 0000000..850656f
--- /dev/null
+++ b/deluge/ui/web/icons/top.png
Binary files differ
diff --git a/deluge/ui/web/icons/traffic.png b/deluge/ui/web/icons/traffic.png
new file mode 100644
index 0000000..ecd8720
--- /dev/null
+++ b/deluge/ui/web/icons/traffic.png
Binary files differ
diff --git a/deluge/ui/web/icons/up.png b/deluge/ui/web/icons/up.png
new file mode 100644
index 0000000..223e733
--- /dev/null
+++ b/deluge/ui/web/icons/up.png
Binary files differ
diff --git a/deluge/ui/web/icons/update.png b/deluge/ui/web/icons/update.png
new file mode 100644
index 0000000..0ff6d45
--- /dev/null
+++ b/deluge/ui/web/icons/update.png
Binary files differ
diff --git a/deluge/ui/web/icons/upload_slots.png b/deluge/ui/web/icons/upload_slots.png
new file mode 100644
index 0000000..0e7000d
--- /dev/null
+++ b/deluge/ui/web/icons/upload_slots.png
Binary files differ
diff --git a/deluge/ui/web/icons/warning.png b/deluge/ui/web/icons/warning.png
new file mode 100644
index 0000000..f66feda
--- /dev/null
+++ b/deluge/ui/web/icons/warning.png
Binary files differ
diff --git a/deluge/ui/web/images/s.gif b/deluge/ui/web/images/s.gif
new file mode 100644
index 0000000..1d11fa9
--- /dev/null
+++ b/deluge/ui/web/images/s.gif
Binary files differ
diff --git a/deluge/ui/web/images/spinner-split.gif b/deluge/ui/web/images/spinner-split.gif
new file mode 100644
index 0000000..7281146
--- /dev/null
+++ b/deluge/ui/web/images/spinner-split.gif
Binary files differ
diff --git a/deluge/ui/web/images/spinner.gif b/deluge/ui/web/images/spinner.gif
new file mode 100644
index 0000000..4e72f53
--- /dev/null
+++ b/deluge/ui/web/images/spinner.gif
Binary files differ
diff --git a/deluge/ui/web/index.html b/deluge/ui/web/index.html
new file mode 100644
index 0000000..08c6d8b
--- /dev/null
+++ b/deluge/ui/web/index.html
@@ -0,0 +1,48 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Deluge WebUI ${version}</title>
+
+ <link rel="icon" sizes="16x16" type="image/png"
+ href="${base}icons/deluge.png"/>
+ <link rel="icon" sizes="32x32" type="image/png"
+ href="${base}icons/deluge-32.png"/>
+ <!-- For Chrome, Android, iOS and Windows touch shortcuts: -->
+ <link rel="icon" sizes="192x192" type="image/png"
+ href="${base}icons/deluge-192.png"/>
+ <link rel="icon" sizes="512x512" type="image/png"
+ href="${base}icons/deluge-512.png"/>
+ <link rel="apple-touch-icon-precomposed"
+ href="${base}icons/deluge-apple-180.png"/>
+ <meta name="msapplication-TileColor" content="#599eee">
+ <meta name="theme-color" content="#599eee">
+
+ <!-- Stylesheets -->
+ % for stylesheet in stylesheets:
+ <link rel="stylesheet" type="text/css" href="${base}${stylesheet}"/>
+ % endfor
+
+ <script type="text/javascript">
+ deluge = {
+ author: 'Deluge Team',
+ version: '${version}',
+ config: ${js_config}
+ }
+ </script>
+
+ <!-- Javascript -->
+ % for script in scripts:
+ <script type="text/javascript" src="${base}${script}"></script>
+ % endfor
+ <script type="text/javascript">
+ Deluge.debug = ${debug};
+ </script>
+ </head>
+ <body>
+ <div style="background-image: url('${base}themes/images/default/tree/loading.gif');"></div>
+
+ <!-- Preload icon classes -->
+ <div class="ext-mb-error"></div>
+ <div class="icon-ok"></div>
+ </body>
+</html>
diff --git a/deluge/ui/web/js/deluge-all-debug.js b/deluge/ui/web/js/deluge-all-debug.js
new file mode 100644
index 0000000..afbbabe
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all-debug.js
@@ -0,0 +1,9868 @@
+/**
+ * Deluge.add.Window.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge.add');
+
+/**
+ * @class Deluge.add.Window
+ * @extends Ext.Window
+ * Base class for an add Window
+ */
+Deluge.add.Window = Ext.extend(Ext.Window, {
+ initComponent: function() {
+ Deluge.add.Window.superclass.initComponent.call(this);
+ this.addEvents('beforeadd', 'add');
+ },
+
+ /**
+ * Create an id for the torrent before we have any info about it.
+ */
+ createTorrentId: function() {
+ return new Date().getTime().toString();
+ },
+});
+/**
+ * Deluge.add.AddWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Ext.namespace('Deluge.add');
+
+// This override allows file upload buttons to contain icons
+Ext.override(Ext.ux.form.FileUploadField, {
+ onRender: function(ct, position) {
+ Ext.ux.form.FileUploadField.superclass.onRender.call(
+ this,
+ ct,
+ position
+ );
+
+ this.wrap = this.el.wrap({ cls: 'x-form-field-wrap x-form-file-wrap' });
+ this.el.addClass('x-form-file-text');
+ this.el.dom.removeAttribute('name');
+ this.createFileInput();
+
+ var btnCfg = Ext.applyIf(this.buttonCfg || {}, {
+ text: this.buttonText,
+ });
+ this.button = new Ext.Button(
+ Ext.apply(btnCfg, {
+ renderTo: this.wrap,
+ cls:
+ 'x-form-file-btn' +
+ (btnCfg.iconCls ? ' x-btn-text-icon' : ''),
+ })
+ );
+
+ if (this.buttonOnly) {
+ this.el.hide();
+ this.wrap.setWidth(this.button.getEl().getWidth());
+ }
+
+ this.bindListeners();
+ this.resizeEl = this.positionEl = this.wrap;
+ },
+});
+
+Deluge.add.AddWindow = Ext.extend(Deluge.add.Window, {
+ title: _('Add Torrents'),
+ layout: 'border',
+ width: 470,
+ height: 450,
+ bodyStyle: 'padding: 10px 5px;',
+ buttonAlign: 'right',
+ closeAction: 'hide',
+ closable: true,
+ plain: true,
+ iconCls: 'x-deluge-add-window-icon',
+
+ initComponent: function() {
+ Deluge.add.AddWindow.superclass.initComponent.call(this);
+
+ this.addButton(_('Cancel'), this.onCancelClick, this);
+ this.addButton(_('Add'), this.onAddClick, this);
+
+ function torrentRenderer(value, p, r) {
+ if (r.data['info_hash']) {
+ return String.format(
+ '<div class="x-deluge-add-torrent-name">{0}</div>',
+ value
+ );
+ } else {
+ return String.format(
+ '<div class="x-deluge-add-torrent-name-loading">{0}</div>',
+ value
+ );
+ }
+ }
+
+ this.list = new Ext.list.ListView({
+ store: new Ext.data.SimpleStore({
+ fields: [
+ { name: 'info_hash', mapping: 1 },
+ { name: 'text', mapping: 2 },
+ ],
+ id: 0,
+ }),
+ columns: [
+ {
+ id: 'torrent',
+ width: 150,
+ sortable: true,
+ renderer: torrentRenderer,
+ dataIndex: 'text',
+ },
+ ],
+ stripeRows: true,
+ singleSelect: true,
+ listeners: {
+ selectionchange: {
+ fn: this.onSelect,
+ scope: this,
+ },
+ },
+ hideHeaders: true,
+ autoExpandColumn: 'torrent',
+ height: '100%',
+ autoScroll: true,
+ });
+
+ this.add({
+ region: 'center',
+ items: [this.list],
+ border: false,
+ bbar: new Ext.Toolbar({
+ items: [
+ {
+ id: 'fileUploadForm',
+ xtype: 'form',
+ layout: 'fit',
+ baseCls: 'x-plain',
+ fileUpload: true,
+ items: [
+ {
+ buttonOnly: true,
+ xtype: 'fileuploadfield',
+ id: 'torrentFile',
+ name: 'file',
+ multiple: true,
+ buttonCfg: {
+ iconCls: 'x-deluge-add-file',
+ text: _('File'),
+ },
+ listeners: {
+ scope: this,
+ fileselected: this.onFileSelected,
+ },
+ },
+ ],
+ },
+ {
+ text: _('Url'),
+ iconCls: 'icon-add-url',
+ handler: this.onUrl,
+ scope: this,
+ },
+ {
+ text: _('Infohash'),
+ iconCls: 'icon-add-magnet',
+ hidden: true,
+ disabled: true,
+ },
+ '->',
+ {
+ text: _('Remove'),
+ iconCls: 'icon-remove',
+ handler: this.onRemove,
+ scope: this,
+ },
+ ],
+ }),
+ });
+
+ this.fileUploadForm = Ext.getCmp('fileUploadForm').getForm();
+ this.optionsPanel = this.add(new Deluge.add.OptionsPanel());
+ this.on('hide', this.onHide, this);
+ this.on('show', this.onShow, this);
+ },
+
+ clear: function() {
+ this.list.getStore().removeAll();
+ this.optionsPanel.clear();
+ // Reset upload form so handler fires when a canceled file is reselected
+ this.fileUploadForm.reset();
+ },
+
+ onAddClick: function() {
+ var torrents = [];
+ if (!this.list) return;
+ this.list.getStore().each(function(r) {
+ var id = r.get('info_hash');
+ torrents.push({
+ path: this.optionsPanel.getFilename(id),
+ options: this.optionsPanel.getOptions(id),
+ });
+ }, this);
+
+ deluge.client.web.add_torrents(torrents, {
+ success: function(result) {},
+ });
+ this.clear();
+ this.hide();
+ },
+
+ onCancelClick: function() {
+ this.clear();
+ this.hide();
+ },
+
+ onFile: function() {
+ if (!this.file) this.file = new Deluge.add.FileWindow();
+ this.file.show();
+ },
+
+ onHide: function() {
+ this.optionsPanel.setActiveTab(0);
+ this.optionsPanel.files.setDisabled(true);
+ this.optionsPanel.form.setDisabled(true);
+ },
+
+ onRemove: function() {
+ if (!this.list.getSelectionCount()) return;
+ var torrent = this.list.getSelectedRecords()[0];
+ if (!torrent) return;
+ this.list.getStore().remove(torrent);
+ this.optionsPanel.clear();
+
+ if (this.torrents && this.torrents[torrent.id])
+ delete this.torrents[torrent.id];
+ },
+
+ onSelect: function(list, selections) {
+ if (selections.length) {
+ var record = this.list.getRecord(selections[0]);
+ this.optionsPanel.setTorrent(record.get('info_hash'));
+ } else {
+ this.optionsPanel.files.setDisabled(true);
+ this.optionsPanel.form.setDisabled(true);
+ }
+ },
+
+ onShow: function() {
+ if (!this.url) {
+ this.url = new Deluge.add.UrlWindow();
+ this.url.on('beforeadd', this.onTorrentBeforeAdd, this);
+ this.url.on('add', this.onTorrentAdd, this);
+ }
+
+ this.optionsPanel.form.getDefaults();
+ },
+
+ onFileSelected: function() {
+ if (this.fileUploadForm.isValid()) {
+ var torrentIds = [];
+ var files = this.fileUploadForm.findField('torrentFile').value;
+ var randomId = this.createTorrentId();
+ Array.prototype.forEach.call(
+ files,
+ function(file, i) {
+ // Append index for batch of unique torrentIds.
+ var torrentId = randomId + i.toString();
+ torrentIds.push(torrentId);
+ this.onTorrentBeforeAdd(torrentId, file.name);
+ }.bind(this)
+ );
+ this.fileUploadForm.submit({
+ url: deluge.config.base + 'upload',
+ waitMsg: _('Uploading your torrent...'),
+ success: this.onUploadSuccess,
+ scope: this,
+ torrentIds: torrentIds,
+ });
+ }
+ },
+
+ onUploadSuccess: function(fp, upload) {
+ if (!upload.result.success) {
+ this.clear();
+ return;
+ }
+
+ upload.result.files.forEach(
+ function(filename, i) {
+ deluge.client.web.get_torrent_info(filename, {
+ success: this.onGotInfo,
+ scope: this,
+ filename: filename,
+ torrentId: upload.options.torrentIds[i],
+ });
+ }.bind(this)
+ );
+ this.fileUploadForm.reset();
+ },
+
+ onGotInfo: function(info, obj, response, request) {
+ info.filename = request.options.filename;
+ torrentId = request.options.torrentId;
+ this.onTorrentAdd(torrentId, info);
+ },
+
+ onTorrentBeforeAdd: function(torrentId, text) {
+ var store = this.list.getStore();
+ store.loadData([[torrentId, null, text]], true);
+ },
+
+ onTorrentAdd: function(torrentId, info) {
+ var r = this.list.getStore().getById(torrentId);
+ if (!info) {
+ Ext.MessageBox.show({
+ title: _('Error'),
+ msg: _('Not a valid torrent'),
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ icon: Ext.MessageBox.ERROR,
+ iconCls: 'x-deluge-icon-error',
+ });
+ this.list.getStore().remove(r);
+ } else {
+ r.set('info_hash', info['info_hash']);
+ r.set('text', info['name']);
+ this.list.getStore().commitChanges();
+ this.optionsPanel.addTorrent(info);
+ this.list.select(r);
+ }
+ },
+
+ onUrl: function(button, event) {
+ this.url.show();
+ },
+});
+/**
+ * Deluge.add.FilesTab.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge.add');
+
+/**
+ * @class Deluge.add.FilesTab
+ * @extends Ext.ux.tree.TreeGrid
+ */
+Deluge.add.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
+ layout: 'fit',
+ title: _('Files'),
+
+ autoScroll: false,
+ animate: false,
+ border: false,
+ disabled: true,
+ rootVisible: false,
+
+ columns: [
+ {
+ header: _('Filename'),
+ width: 295,
+ dataIndex: 'filename',
+ },
+ {
+ header: _('Size'),
+ width: 60,
+ dataIndex: 'size',
+ tpl: new Ext.XTemplate('{size:this.fsize}', {
+ fsize: function(v) {
+ return fsize(v);
+ },
+ }),
+ },
+ {
+ header: _('Download'),
+ width: 65,
+ dataIndex: 'download',
+ tpl: new Ext.XTemplate('{download:this.format}', {
+ format: function(v) {
+ return (
+ '<div rel="chkbox" class="x-grid3-check-col' +
+ (v ? '-on' : '') +
+ '"> </div>'
+ );
+ },
+ }),
+ },
+ ],
+
+ initComponent: function() {
+ Deluge.add.FilesTab.superclass.initComponent.call(this);
+ this.on('click', this.onNodeClick, this);
+ },
+
+ clearFiles: function() {
+ var root = this.getRootNode();
+ if (!root.hasChildNodes()) return;
+ root.cascade(function(node) {
+ if (!node.parentNode || !node.getOwnerTree()) return;
+ node.remove();
+ });
+ },
+
+ setDownload: function(node, value, suppress) {
+ node.attributes.download = value;
+ node.ui.updateColumns();
+
+ if (node.isLeaf()) {
+ if (!suppress) {
+ return this.fireEvent('fileschecked', [node], value, !value);
+ }
+ } else {
+ var nodes = [node];
+ node.cascade(function(n) {
+ n.attributes.download = value;
+ n.ui.updateColumns();
+ nodes.push(n);
+ }, this);
+ if (!suppress) {
+ return this.fireEvent('fileschecked', nodes, value, !value);
+ }
+ }
+ },
+
+ onNodeClick: function(node, e) {
+ var el = new Ext.Element(e.target);
+ if (el.getAttribute('rel') == 'chkbox') {
+ this.setDownload(node, !node.attributes.download);
+ }
+ },
+});
+/**
+ * Deluge.add.Infohash.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Ext.deluge.add');
+/**
+ * Deluge.add.OptionsPanel.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge.add');
+
+Deluge.add.OptionsPanel = Ext.extend(Ext.TabPanel, {
+ torrents: {},
+
+ // layout options
+ region: 'south',
+ border: false,
+ activeTab: 0,
+ height: 265,
+
+ initComponent: function() {
+ Deluge.add.OptionsPanel.superclass.initComponent.call(this);
+ this.files = this.add(new Deluge.add.FilesTab());
+ this.form = this.add(new Deluge.add.OptionsTab());
+
+ this.files.on('fileschecked', this.onFilesChecked, this);
+ },
+
+ addTorrent: function(torrent) {
+ this.torrents[torrent['info_hash']] = torrent;
+ var fileIndexes = {};
+ this.walkFileTree(
+ torrent['files_tree'],
+ function(filename, type, entry, parent) {
+ if (type != 'file') return;
+ fileIndexes[entry.index] = entry.download;
+ },
+ this
+ );
+
+ var priorities = [];
+ Ext.each(Ext.keys(fileIndexes), function(index) {
+ priorities[index] = fileIndexes[index];
+ });
+
+ var oldId = this.form.optionsManager.changeId(
+ torrent['info_hash'],
+ true
+ );
+ this.form.optionsManager.setDefault('file_priorities', priorities);
+ this.form.optionsManager.changeId(oldId, true);
+ },
+
+ clear: function() {
+ this.files.clearFiles();
+ this.form.optionsManager.resetAll();
+ },
+
+ getFilename: function(torrentId) {
+ return this.torrents[torrentId]['filename'];
+ },
+
+ getOptions: function(torrentId) {
+ var oldId = this.form.optionsManager.changeId(torrentId, true);
+ var options = this.form.optionsManager.get();
+ this.form.optionsManager.changeId(oldId, true);
+ Ext.each(options['file_priorities'], function(priority, index) {
+ options['file_priorities'][index] = priority ? 1 : 0;
+ });
+ return options;
+ },
+
+ setTorrent: function(torrentId) {
+ if (!torrentId) return;
+
+ this.torrentId = torrentId;
+ this.form.optionsManager.changeId(torrentId);
+
+ this.files.clearFiles();
+ var root = this.files.getRootNode();
+ var priorities = this.form.optionsManager.get('file_priorities');
+
+ this.form.setDisabled(false);
+
+ if (this.torrents[torrentId]['files_tree']) {
+ this.walkFileTree(
+ this.torrents[torrentId]['files_tree'],
+ function(filename, type, entry, parentNode) {
+ var node = new Ext.tree.TreeNode({
+ download: entry.index ? priorities[entry.index] : true,
+ filename: filename,
+ fileindex: entry.index,
+ leaf: type != 'dir',
+ size: entry.length,
+ });
+ parentNode.appendChild(node);
+ if (type == 'dir') return node;
+ },
+ this,
+ root
+ );
+ root.firstChild.expand();
+ this.files.setDisabled(false);
+ this.files.show();
+ } else {
+ // Files tab is empty so show options tab
+ this.form.show();
+ this.files.setDisabled(true);
+ }
+ },
+
+ walkFileTree: function(files, callback, scope, parentNode) {
+ for (var filename in files.contents) {
+ var entry = files.contents[filename];
+ var type = entry.type;
+
+ if (scope) {
+ var ret = callback.apply(scope, [
+ filename,
+ type,
+ entry,
+ parentNode,
+ ]);
+ } else {
+ var ret = callback(filename, type, entry, parentNode);
+ }
+
+ if (type == 'dir') this.walkFileTree(entry, callback, scope, ret);
+ }
+ },
+
+ onFilesChecked: function(nodes, newValue, oldValue) {
+ Ext.each(
+ nodes,
+ function(node) {
+ if (node.attributes.fileindex < 0) return;
+ var priorities = this.form.optionsManager.get(
+ 'file_priorities'
+ );
+ priorities[node.attributes.fileindex] = newValue;
+ this.form.optionsManager.update('file_priorities', priorities);
+ },
+ this
+ );
+ },
+});
+/**
+ * Deluge.add.OptionsPanel.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge.add');
+
+/**
+ * @class Deluge.add.OptionsTab
+ * @extends Ext.form.FormPanel
+ */
+Deluge.add.OptionsTab = Ext.extend(Ext.form.FormPanel, {
+ title: _('Options'),
+ height: 170,
+ border: false,
+ bodyStyle: 'padding: 5px',
+ disabled: true,
+ labelWidth: 1,
+
+ initComponent: function() {
+ Deluge.add.OptionsTab.superclass.initComponent.call(this);
+
+ this.optionsManager = new Deluge.MultiOptionsManager();
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ title: _('Download Folder'),
+ border: false,
+ autoHeight: true,
+ defaultType: 'textfield',
+ labelWidth: 1,
+ fieldLabel: '',
+ style: 'padding: 5px 0; margin-bottom: 0;',
+ });
+ this.optionsManager.bind(
+ 'download_location',
+ fieldset.add({
+ fieldLabel: '',
+ name: 'download_location',
+ anchor: '95%',
+ labelSeparator: '',
+ })
+ );
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ title: _('Move Completed Folder'),
+ border: false,
+ autoHeight: true,
+ defaultType: 'togglefield',
+ labelWidth: 1,
+ fieldLabel: '',
+ style: 'padding: 5px 0; margin-bottom: 0;',
+ });
+ var field = fieldset.add({
+ fieldLabel: '',
+ name: 'move_completed_path',
+ anchor: '98%',
+ });
+ this.optionsManager.bind('move_completed', field.toggle);
+ this.optionsManager.bind('move_completed_path', field.input);
+
+ var panel = this.add({
+ border: false,
+ layout: 'column',
+ defaultType: 'fieldset',
+ });
+
+ fieldset = panel.add({
+ title: _('Bandwidth'),
+ border: false,
+ autoHeight: true,
+ bodyStyle: 'padding: 2px 5px',
+ labelWidth: 105,
+ width: 200,
+ defaultType: 'spinnerfield',
+ style: 'padding-right: 10px;',
+ });
+ this.optionsManager.bind(
+ 'max_download_speed',
+ fieldset.add({
+ fieldLabel: _('Max Down Speed'),
+ name: 'max_download_speed',
+ width: 60,
+ })
+ );
+ this.optionsManager.bind(
+ 'max_upload_speed',
+ fieldset.add({
+ fieldLabel: _('Max Up Speed'),
+ name: 'max_upload_speed',
+ width: 60,
+ })
+ );
+ this.optionsManager.bind(
+ 'max_connections',
+ fieldset.add({
+ fieldLabel: _('Max Connections'),
+ name: 'max_connections',
+ width: 60,
+ })
+ );
+ this.optionsManager.bind(
+ 'max_upload_slots',
+ fieldset.add({
+ fieldLabel: _('Max Upload Slots'),
+ name: 'max_upload_slots',
+ width: 60,
+ })
+ );
+
+ fieldset = panel.add({
+ // title: _('General'),
+ border: false,
+ autoHeight: true,
+ defaultType: 'checkbox',
+ });
+ this.optionsManager.bind(
+ 'add_paused',
+ fieldset.add({
+ name: 'add_paused',
+ boxLabel: _('Add In Paused State'),
+ fieldLabel: '',
+ labelSeparator: '',
+ })
+ );
+ this.optionsManager.bind(
+ 'prioritize_first_last_pieces',
+ fieldset.add({
+ name: 'prioritize_first_last_pieces',
+ boxLabel: _('Prioritize First/Last Pieces'),
+ fieldLabel: '',
+ labelSeparator: '',
+ })
+ );
+ this.optionsManager.bind(
+ 'sequential_download',
+ fieldset.add({
+ name: 'sequential_download',
+ boxLabel: _('Sequential Download'),
+ fieldLabel: '',
+ labelSeparator: '',
+ })
+ );
+ this.optionsManager.bind(
+ 'seed_mode',
+ fieldset.add({
+ name: 'seed_mode',
+ boxLabel: _('Skip File Hash Check'),
+ fieldLabel: '',
+ labelSeparator: '',
+ })
+ );
+ this.optionsManager.bind(
+ 'super_seeding',
+ fieldset.add({
+ name: 'super_seeding',
+ boxLabel: _('Super Seed'),
+ fieldLabel: '',
+ labelSeparator: '',
+ })
+ );
+ this.optionsManager.bind(
+ 'pre_allocate_storage',
+ fieldset.add({
+ name: 'pre_allocate_storage',
+ boxLabel: _('Preallocate Disk Space'),
+ fieldLabel: '',
+ labelSeparator: '',
+ })
+ );
+ },
+
+ getDefaults: function() {
+ var keys = [
+ 'add_paused',
+ 'pre_allocate_storage',
+ 'download_location',
+ 'max_connections_per_torrent',
+ 'max_download_speed_per_torrent',
+ 'move_completed',
+ 'move_completed_path',
+ 'max_upload_slots_per_torrent',
+ 'max_upload_speed_per_torrent',
+ 'prioritize_first_last_pieces',
+ 'sequential_download',
+ ];
+
+ deluge.client.core.get_config_values(keys, {
+ success: function(config) {
+ var options = {
+ file_priorities: [],
+ add_paused: config.add_paused,
+ sequential_download: config.sequential_download,
+ pre_allocate_storage: config.pre_allocate_storage,
+ download_location: config.download_location,
+ move_completed: config.move_completed,
+ move_completed_path: config.move_completed_path,
+ max_connections: config.max_connections_per_torrent,
+ max_download_speed: config.max_download_speed_per_torrent,
+ max_upload_slots: config.max_upload_slots_per_torrent,
+ max_upload_speed: config.max_upload_speed_per_torrent,
+ prioritize_first_last_pieces:
+ config.prioritize_first_last_pieces,
+ seed_mode: false,
+ super_seeding: false,
+ };
+ this.optionsManager.options = options;
+ this.optionsManager.resetAll();
+ },
+ scope: this,
+ });
+ },
+});
+/**
+ * Deluge.add.UrlWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Ext.namespace('Deluge.add');
+Deluge.add.UrlWindow = Ext.extend(Deluge.add.Window, {
+ title: _('Add from Url'),
+ modal: true,
+ plain: true,
+ layout: 'fit',
+ width: 350,
+ height: 155,
+
+ buttonAlign: 'center',
+ closeAction: 'hide',
+ bodyStyle: 'padding: 10px 5px;',
+ iconCls: 'x-deluge-add-url-window-icon',
+
+ initComponent: function() {
+ Deluge.add.UrlWindow.superclass.initComponent.call(this);
+ this.addButton(_('Add'), this.onAddClick, this);
+
+ var form = this.add({
+ xtype: 'form',
+ defaultType: 'textfield',
+ baseCls: 'x-plain',
+ labelWidth: 55,
+ });
+
+ this.urlField = form.add({
+ fieldLabel: _('Url'),
+ id: 'url',
+ name: 'url',
+ width: '97%',
+ });
+ this.urlField.on('specialkey', this.onAdd, this);
+
+ this.cookieField = form.add({
+ fieldLabel: _('Cookies'),
+ id: 'cookies',
+ name: 'cookies',
+ width: '97%',
+ });
+ this.cookieField.on('specialkey', this.onAdd, this);
+ },
+
+ onAddClick: function(field, e) {
+ if (
+ (field.id == 'url' || field.id == 'cookies') &&
+ e.getKey() != e.ENTER
+ )
+ return;
+
+ var field = this.urlField;
+ var url = field.getValue();
+ var cookies = this.cookieField.getValue();
+ var torrentId = this.createTorrentId();
+
+ if (url.indexOf('magnet:?') == 0 && url.indexOf('xt=urn:btih') > -1) {
+ deluge.client.web.get_magnet_info(url, {
+ success: this.onGotInfo,
+ scope: this,
+ filename: url,
+ torrentId: torrentId,
+ });
+ } else {
+ deluge.client.web.download_torrent_from_url(url, cookies, {
+ success: this.onDownload,
+ scope: this,
+ torrentId: torrentId,
+ });
+ }
+
+ this.hide();
+ this.urlField.setValue('');
+ this.fireEvent('beforeadd', torrentId, url);
+ },
+
+ onDownload: function(filename, obj, resp, req) {
+ deluge.client.web.get_torrent_info(filename, {
+ success: this.onGotInfo,
+ scope: this,
+ filename: filename,
+ torrentId: req.options.torrentId,
+ });
+ },
+
+ onGotInfo: function(info, obj, response, request) {
+ info['filename'] = request.options.filename;
+ this.fireEvent('add', request.options.torrentId, info);
+ },
+});
+/**
+ * Deluge.data.SortTypes.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.data');
+
+/**
+ * Common sort functions that can be used for data Stores.
+ *
+ * @author Damien Churchill <damoxc@gmail.com>
+ * @version 1.3
+ *
+ * @class Deluge.data.SortTypes
+ * @singleton
+ */
+Deluge.data.SortTypes = {
+ // prettier-ignore
+ asIPAddress: function(value) {
+ var d = value.match(
+ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\:(\d+)/
+ );
+ return ((+d[1] * 256 + (+d[2])) * 256 + (+d[3])) * 256 + (+d[4]);
+ },
+
+ asQueuePosition: function(value) {
+ return value > -1 ? value : Number.MAX_VALUE;
+ },
+
+ asName: function(value) {
+ return String(value).toLowerCase();
+ },
+};
+/**
+ * Deluge.data.PeerRecord.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.data');
+
+/**
+ * Deluge.data.Peer record
+ *
+ * @author Damien Churchill <damoxc@gmail.com>
+ * @version 1.3
+ *
+ * @class Deluge.data.Peer
+ * @extends Ext.data.Record
+ * @constructor
+ * @param {Object} data The peer data
+ */
+Deluge.data.Peer = Ext.data.Record.create([
+ {
+ name: 'country',
+ type: 'string',
+ },
+ {
+ name: 'ip',
+ type: 'string',
+ sortType: Deluge.data.SortTypes.asIPAddress,
+ },
+ {
+ name: 'client',
+ type: 'string',
+ },
+ {
+ name: 'progress',
+ type: 'float',
+ },
+ {
+ name: 'down_speed',
+ type: 'int',
+ },
+ {
+ name: 'up_speed',
+ type: 'int',
+ },
+ {
+ name: 'seed',
+ type: 'int',
+ },
+]);
+/**
+ * Deluge.data.TorrentRecord.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.data');
+
+/**
+ * Deluge.data.Torrent record
+ *
+ * @author Damien Churchill <damoxc@gmail.com>
+ * @version 1.3
+ *
+ * @class Deluge.data.Torrent
+ * @extends Ext.data.Record
+ * @constructor
+ * @param {Object} data The torrents data
+ */
+Deluge.data.Torrent = Ext.data.Record.create([
+ {
+ name: 'queue',
+ type: 'int',
+ },
+ {
+ name: 'name',
+ type: 'string',
+ sortType: Deluge.data.SortTypes.asName,
+ },
+ {
+ name: 'total_wanted',
+ type: 'int',
+ },
+ {
+ name: 'state',
+ type: 'string',
+ },
+ {
+ name: 'progress',
+ type: 'int',
+ },
+ {
+ name: 'num_seeds',
+ type: 'int',
+ },
+ {
+ name: 'total_seeds',
+ type: 'int',
+ },
+ {
+ name: 'num_peers',
+ type: 'int',
+ },
+ {
+ name: 'total_peers',
+ type: 'int',
+ },
+ {
+ name: 'download_payload_rate',
+ type: 'int',
+ },
+ {
+ name: 'upload_payload_rate',
+ type: 'int',
+ },
+ {
+ name: 'eta',
+ type: 'int',
+ },
+ {
+ name: 'ratio',
+ type: 'float',
+ },
+ {
+ name: 'distributed_copies',
+ type: 'float',
+ },
+ {
+ name: 'time_added',
+ type: 'int',
+ },
+ {
+ name: 'tracker_host',
+ type: 'string',
+ },
+ {
+ name: 'save_path',
+ type: 'string',
+ },
+ {
+ name: 'total_done',
+ type: 'int',
+ },
+ {
+ name: 'total_uploaded',
+ type: 'int',
+ },
+ {
+ name: 'total_remaining',
+ type: 'int',
+ },
+ {
+ name: 'max_download_speed',
+ type: 'int',
+ },
+ {
+ name: 'max_upload_speed',
+ type: 'int',
+ },
+ {
+ name: 'seeds_peers_ratio',
+ type: 'float',
+ },
+ {
+ name: 'time_since_transfer',
+ type: 'int',
+ },
+]);
+/**
+ * Deluge.details.DetailsPanel.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.details');
+
+/**
+ * @class Deluge.details.DetailsPanel
+ */
+Deluge.details.DetailsPanel = Ext.extend(Ext.TabPanel, {
+ id: 'torrentDetails',
+ activeTab: 0,
+
+ initComponent: function() {
+ Deluge.details.DetailsPanel.superclass.initComponent.call(this);
+ this.add(new Deluge.details.StatusTab());
+ this.add(new Deluge.details.DetailsTab());
+ this.add(new Deluge.details.FilesTab());
+ this.add(new Deluge.details.PeersTab());
+ this.add(new Deluge.details.OptionsTab());
+ },
+
+ clear: function() {
+ this.items.each(function(panel) {
+ if (panel.clear) {
+ panel.clear.defer(100, panel);
+ panel.disable();
+ }
+ });
+ },
+
+ update: function(tab) {
+ var torrent = deluge.torrents.getSelected();
+ if (!torrent) {
+ this.clear();
+ return;
+ }
+
+ this.items.each(function(tab) {
+ if (tab.disabled) tab.enable();
+ });
+
+ tab = tab || this.getActiveTab();
+ if (tab.update) tab.update(torrent.id);
+ },
+
+ /* Event Handlers */
+
+ // We need to add the events in onRender since Deluge.Torrents has not been created yet.
+ onRender: function(ct, position) {
+ Deluge.details.DetailsPanel.superclass.onRender.call(
+ this,
+ ct,
+ position
+ );
+ deluge.events.on('disconnect', this.clear, this);
+ deluge.torrents.on('rowclick', this.onTorrentsClick, this);
+ this.on('tabchange', this.onTabChange, this);
+
+ deluge.torrents.getSelectionModel().on(
+ 'selectionchange',
+ function(selModel) {
+ if (!selModel.hasSelection()) this.clear();
+ },
+ this
+ );
+ },
+
+ onTabChange: function(panel, tab) {
+ this.update(tab);
+ },
+
+ onTorrentsClick: function(grid, rowIndex, e) {
+ this.update();
+ },
+});
+/**
+ * Deluge.Details.Details.js
+ * The details tab displayed in the details panel.
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Deluge.details.DetailsTab = Ext.extend(Ext.Panel, {
+ title: _('Details'),
+
+ fields: {},
+ autoScroll: true,
+ queuedItems: {},
+
+ oldData: {},
+
+ initComponent: function() {
+ Deluge.details.DetailsTab.superclass.initComponent.call(this);
+ this.addItem('torrent_name', _('Name:'));
+ this.addItem('hash', _('Hash:'));
+ this.addItem('path', _('Download Folder:'));
+ this.addItem('size', _('Total Size:'));
+ this.addItem('files', _('Total Files:'));
+ this.addItem('comment', _('Comment:'));
+ this.addItem('status', _('Status:'));
+ this.addItem('tracker', _('Tracker:'));
+ this.addItem('creator', _('Created By:'));
+ },
+
+ onRender: function(ct, position) {
+ Deluge.details.DetailsTab.superclass.onRender.call(this, ct, position);
+ this.body.setStyle('padding', '10px');
+ this.dl = Ext.DomHelper.append(this.body, { tag: 'dl' }, true);
+
+ for (var id in this.queuedItems) {
+ this.doAddItem(id, this.queuedItems[id]);
+ }
+ },
+
+ addItem: function(id, label) {
+ if (!this.rendered) {
+ this.queuedItems[id] = label;
+ } else {
+ this.doAddItem(id, label);
+ }
+ },
+
+ // private
+ doAddItem: function(id, label) {
+ Ext.DomHelper.append(this.dl, { tag: 'dt', cls: id, html: label });
+ this.fields[id] = Ext.DomHelper.append(
+ this.dl,
+ { tag: 'dd', cls: id, html: '' },
+ true
+ );
+ },
+
+ clear: function() {
+ if (!this.fields) return;
+ for (var k in this.fields) {
+ this.fields[k].dom.innerHTML = '';
+ }
+ this.oldData = {};
+ },
+
+ update: function(torrentId) {
+ deluge.client.web.get_torrent_status(torrentId, Deluge.Keys.Details, {
+ success: this.onRequestComplete,
+ scope: this,
+ torrentId: torrentId,
+ });
+ },
+
+ onRequestComplete: function(torrent, request, response, options) {
+ var data = {
+ torrent_name: torrent.name,
+ hash: options.options.torrentId,
+ path: torrent.download_location,
+ size: fsize(torrent.total_size),
+ files: torrent.num_files,
+ status: torrent.message,
+ tracker: torrent.tracker_host,
+ comment: torrent.comment,
+ creator: torrent.creator,
+ };
+
+ for (var field in this.fields) {
+ if (!Ext.isDefined(data[field])) continue; // This is a field we are not responsible for.
+ if (data[field] == this.oldData[field]) continue;
+ this.fields[field].dom.innerHTML = Ext.escapeHTML(data[field]);
+ }
+ this.oldData = data;
+ },
+});
+/**
+ * Deluge.details.FilesTab.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
+ title: _('Files'),
+
+ rootVisible: false,
+
+ columns: [
+ {
+ header: _('Filename'),
+ width: 330,
+ dataIndex: 'filename',
+ },
+ {
+ header: _('Size'),
+ width: 150,
+ dataIndex: 'size',
+ tpl: new Ext.XTemplate('{size:this.fsize}', {
+ fsize: function(v) {
+ return fsize(v);
+ },
+ }),
+ },
+ {
+ xtype: 'tgrendercolumn',
+ header: _('Progress'),
+ width: 150,
+ dataIndex: 'progress',
+ renderer: function(v) {
+ var progress = v * 100;
+ return Deluge.progressBar(
+ progress,
+ this.col.width,
+ progress.toFixed(2) + '%',
+ 0
+ );
+ },
+ },
+ {
+ header: _('Priority'),
+ width: 150,
+ dataIndex: 'priority',
+ tpl: new Ext.XTemplate(
+ '<tpl if="!isNaN(priority)">' +
+ '<div class="{priority:this.getClass}">' +
+ '{priority:this.getName}' +
+ '</div></tpl>',
+ {
+ getClass: function(v) {
+ return FILE_PRIORITY_CSS[v];
+ },
+
+ getName: function(v) {
+ return _(FILE_PRIORITY[v]);
+ },
+ }
+ ),
+ },
+ ],
+
+ selModel: new Ext.tree.MultiSelectionModel(),
+
+ initComponent: function() {
+ Deluge.details.FilesTab.superclass.initComponent.call(this);
+ this.setRootNode(new Ext.tree.TreeNode({ text: _('Files') }));
+ },
+
+ clear: function() {
+ var root = this.getRootNode();
+ if (!root.hasChildNodes()) return;
+ root.cascade(function(node) {
+ var parentNode = node.parentNode;
+ if (!parentNode) return;
+ if (!parentNode.ownerTree) return;
+ parentNode.removeChild(node);
+ });
+ },
+
+ createFileTree: function(files) {
+ function walk(files, parentNode) {
+ for (var file in files.contents) {
+ var item = files.contents[file];
+ if (item.type == 'dir') {
+ walk(
+ item,
+ parentNode.appendChild(
+ new Ext.tree.TreeNode({
+ text: file,
+ filename: file,
+ size: item.size,
+ progress: item.progress,
+ priority: item.priority,
+ })
+ )
+ );
+ } else {
+ parentNode.appendChild(
+ new Ext.tree.TreeNode({
+ text: file,
+ filename: file,
+ fileIndex: item.index,
+ size: item.size,
+ progress: item.progress,
+ priority: item.priority,
+ leaf: true,
+ iconCls: 'x-deluge-file',
+ uiProvider: Ext.ux.tree.TreeGridNodeUI,
+ })
+ );
+ }
+ }
+ }
+ var root = this.getRootNode();
+ walk(files, root);
+ root.firstChild.expand();
+ },
+
+ update: function(torrentId) {
+ if (this.torrentId != torrentId) {
+ this.clear();
+ this.torrentId = torrentId;
+ }
+
+ deluge.client.web.get_torrent_files(torrentId, {
+ success: this.onRequestComplete,
+ scope: this,
+ torrentId: torrentId,
+ });
+ },
+
+ updateFileTree: function(files) {
+ function walk(files, parentNode) {
+ for (var file in files.contents) {
+ var item = files.contents[file];
+ var node = parentNode.findChild('filename', file);
+ node.attributes.size = item.size;
+ node.attributes.progress = item.progress;
+ node.attributes.priority = item.priority;
+ node.ui.updateColumns();
+ if (item.type == 'dir') {
+ walk(item, node);
+ }
+ }
+ }
+ walk(files, this.getRootNode());
+ },
+
+ onRender: function(ct, position) {
+ Deluge.details.FilesTab.superclass.onRender.call(this, ct, position);
+ deluge.menus.filePriorities.on('itemclick', this.onItemClick, this);
+ this.on('contextmenu', this.onContextMenu, this);
+ this.sorter = new Ext.tree.TreeSorter(this, {
+ folderSort: true,
+ });
+ },
+
+ onContextMenu: function(node, e) {
+ e.stopEvent();
+ var selModel = this.getSelectionModel();
+ if (selModel.getSelectedNodes().length < 2) {
+ selModel.clearSelections();
+ node.select();
+ }
+ deluge.menus.filePriorities.showAt(e.getPoint());
+ },
+
+ onItemClick: function(baseItem, e) {
+ switch (baseItem.id) {
+ case 'expandAll':
+ this.expandAll();
+ break;
+ default:
+ var indexes = {};
+ var walk = function(node) {
+ if (Ext.isEmpty(node.attributes.fileIndex)) return;
+ indexes[node.attributes.fileIndex] =
+ node.attributes.priority;
+ };
+ this.getRootNode().cascade(walk);
+
+ var nodes = this.getSelectionModel().getSelectedNodes();
+ Ext.each(nodes, function(node) {
+ if (!node.isLeaf()) {
+ var setPriorities = function(node) {
+ if (Ext.isEmpty(node.attributes.fileIndex)) return;
+ indexes[node.attributes.fileIndex] =
+ baseItem.filePriority;
+ };
+ node.cascade(setPriorities);
+ } else if (!Ext.isEmpty(node.attributes.fileIndex)) {
+ indexes[node.attributes.fileIndex] =
+ baseItem.filePriority;
+ return;
+ }
+ });
+
+ var priorities = new Array(Ext.keys(indexes).length);
+ for (var index in indexes) {
+ priorities[index] = indexes[index];
+ }
+
+ deluge.client.core.set_torrent_options(
+ [this.torrentId],
+ { file_priorities: priorities },
+ {
+ success: function() {
+ Ext.each(nodes, function(node) {
+ node.setColumnValue(3, baseItem.filePriority);
+ });
+ },
+ scope: this,
+ }
+ );
+ break;
+ }
+ },
+
+ onRequestComplete: function(files, options) {
+ if (!this.getRootNode().hasChildNodes()) {
+ this.createFileTree(files);
+ } else {
+ this.updateFileTree(files);
+ }
+ },
+});
+/**
+ * Deluge.details.OptionsTab.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Deluge.details.OptionsTab = Ext.extend(Ext.form.FormPanel, {
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ autoScroll: true,
+ bodyStyle: 'padding: 5px;',
+ border: false,
+ cls: 'x-deluge-options',
+ defaults: {
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ },
+ deferredRender: false,
+ layout: 'column',
+ title: _('Options'),
+ },
+ config
+ );
+ Deluge.details.OptionsTab.superclass.constructor.call(this, config);
+ },
+
+ initComponent: function() {
+ Deluge.details.OptionsTab.superclass.initComponent.call(this);
+
+ (this.fieldsets = {}), (this.fields = {});
+ this.optionsManager = new Deluge.MultiOptionsManager({
+ options: {
+ max_download_speed: -1,
+ max_upload_speed: -1,
+ max_connections: -1,
+ max_upload_slots: -1,
+ auto_managed: false,
+ stop_at_ratio: false,
+ stop_ratio: 2.0,
+ remove_at_ratio: false,
+ move_completed: false,
+ move_completed_path: '',
+ private: false,
+ prioritize_first_last: false,
+ super_seeding: false,
+ },
+ });
+
+ /*
+ * Bandwidth Options
+ */
+ this.fieldsets.bandwidth = this.add({
+ xtype: 'fieldset',
+ defaultType: 'spinnerfield',
+ bodyStyle: 'padding: 5px',
+
+ layout: 'table',
+ layoutConfig: { columns: 3 },
+ labelWidth: 150,
+
+ style: 'margin-left: 10px; margin-right: 5px; padding: 5px',
+ title: _('Bandwidth'),
+ width: 250,
+ });
+
+ /*
+ * Max Download Speed
+ */
+ this.fieldsets.bandwidth.add({
+ xtype: 'label',
+ text: _('Max Download Speed:'),
+ forId: 'max_download_speed',
+ cls: 'x-deluge-options-label',
+ });
+ this.fields.max_download_speed = this.fieldsets.bandwidth.add({
+ id: 'max_download_speed',
+ name: 'max_download_speed',
+ width: 70,
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 1,
+ minValue: -1,
+ maxValue: 99999,
+ },
+ });
+ this.fieldsets.bandwidth.add({
+ xtype: 'label',
+ text: _('KiB/s'),
+ style: 'margin-left: 10px',
+ });
+
+ /*
+ * Max Upload Speed
+ */
+ this.fieldsets.bandwidth.add({
+ xtype: 'label',
+ text: _('Max Upload Speed:'),
+ forId: 'max_upload_speed',
+ cls: 'x-deluge-options-label',
+ });
+ this.fields.max_upload_speed = this.fieldsets.bandwidth.add({
+ id: 'max_upload_speed',
+ name: 'max_upload_speed',
+ width: 70,
+ value: -1,
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 1,
+ minValue: -1,
+ maxValue: 99999,
+ },
+ });
+ this.fieldsets.bandwidth.add({
+ xtype: 'label',
+ text: _('KiB/s'),
+ style: 'margin-left: 10px',
+ });
+
+ /*
+ * Max Connections
+ */
+ this.fieldsets.bandwidth.add({
+ xtype: 'label',
+ text: _('Max Connections:'),
+ forId: 'max_connections',
+ cls: 'x-deluge-options-label',
+ });
+ this.fields.max_connections = this.fieldsets.bandwidth.add({
+ id: 'max_connections',
+ name: 'max_connections',
+ width: 70,
+ value: -1,
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 99999,
+ },
+ colspan: 2,
+ });
+
+ /*
+ * Max Upload Slots
+ */
+ this.fieldsets.bandwidth.add({
+ xtype: 'label',
+ text: _('Max Upload Slots:'),
+ forId: 'max_upload_slots',
+ cls: 'x-deluge-options-label',
+ });
+ this.fields.max_upload_slots = this.fieldsets.bandwidth.add({
+ id: 'max_upload_slots',
+ name: 'max_upload_slots',
+ width: 70,
+ value: -1,
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 99999,
+ },
+ colspan: 2,
+ });
+
+ /*
+ * Queue Options
+ */
+ this.fieldsets.queue = this.add({
+ xtype: 'fieldset',
+ title: _('Queue'),
+ style: 'margin-left: 5px; margin-right: 5px; padding: 5px',
+ width: 210,
+
+ layout: 'table',
+ layoutConfig: { columns: 2 },
+ labelWidth: 0,
+
+ defaults: {
+ fieldLabel: '',
+ labelSeparator: '',
+ },
+ });
+
+ this.fields.auto_managed = this.fieldsets.queue.add({
+ xtype: 'checkbox',
+ fieldLabel: '',
+ labelSeparator: '',
+ name: 'is_auto_managed',
+ boxLabel: _('Auto Managed'),
+ width: 200,
+ colspan: 2,
+ });
+
+ this.fields.stop_at_ratio = this.fieldsets.queue.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ id: 'stop_at_ratio',
+ width: 120,
+ boxLabel: _('Stop seed at ratio:'),
+ handler: this.onStopRatioChecked,
+ scope: this,
+ });
+
+ this.fields.stop_ratio = this.fieldsets.queue.add({
+ xtype: 'spinnerfield',
+ id: 'stop_ratio',
+ name: 'stop_ratio',
+ disabled: true,
+ width: 50,
+ value: 2.0,
+ strategy: {
+ xtype: 'number',
+ minValue: -1,
+ maxValue: 99999,
+ incrementValue: 0.1,
+ alternateIncrementValue: 1,
+ decimalPrecision: 1,
+ },
+ });
+
+ this.fields.remove_at_ratio = this.fieldsets.queue.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ id: 'remove_at_ratio',
+ ctCls: 'x-deluge-indent-checkbox',
+ bodyStyle: 'padding-left: 10px',
+ boxLabel: _('Remove at ratio'),
+ disabled: true,
+ colspan: 2,
+ });
+
+ this.fields.move_completed = this.fieldsets.queue.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ id: 'move_completed',
+ boxLabel: _('Move Completed:'),
+ colspan: 2,
+ handler: this.onMoveCompletedChecked,
+ scope: this,
+ });
+
+ this.fields.move_completed_path = this.fieldsets.queue.add({
+ xtype: 'textfield',
+ fieldLabel: '',
+ id: 'move_completed_path',
+ colspan: 3,
+ bodyStyle: 'margin-left: 20px',
+ width: 180,
+ disabled: true,
+ });
+
+ /*
+ * General Options
+ */
+ this.rightColumn = this.add({
+ border: false,
+ autoHeight: true,
+ style: 'margin-left: 5px',
+ width: 210,
+ });
+
+ this.fieldsets.general = this.rightColumn.add({
+ xtype: 'fieldset',
+ autoHeight: true,
+ defaultType: 'checkbox',
+ title: _('General'),
+ layout: 'form',
+ });
+
+ this.fields['private'] = this.fieldsets.general.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('Private'),
+ id: 'private',
+ disabled: true,
+ });
+
+ this.fields.prioritize_first_last = this.fieldsets.general.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('Prioritize First/Last'),
+ id: 'prioritize_first_last',
+ });
+
+ this.fields.super_seeding = this.fieldsets.general.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('Super Seeding'),
+ id: 'super_seeding',
+ });
+
+ // Bind the fields so the options manager can manage them.
+ for (var id in this.fields) {
+ this.optionsManager.bind(id, this.fields[id]);
+ }
+
+ /*
+ * Buttons
+ */
+ this.buttonPanel = this.rightColumn.add({
+ layout: 'hbox',
+ xtype: 'panel',
+ border: false,
+ });
+
+ /*
+ * Edit Trackers button
+ */
+ this.buttonPanel.add({
+ id: 'edit_trackers',
+ xtype: 'button',
+ text: _('Edit Trackers'),
+ cls: 'x-btn-text-icon',
+ iconCls: 'x-deluge-edit-trackers',
+ border: false,
+ width: 100,
+ handler: this.onEditTrackers,
+ scope: this,
+ });
+
+ /*
+ * Apply button
+ */
+ this.buttonPanel.add({
+ id: 'apply',
+ xtype: 'button',
+ text: _('Apply'),
+ style: 'margin-left: 10px;',
+ border: false,
+ width: 100,
+ handler: this.onApply,
+ scope: this,
+ });
+ },
+
+ onRender: function(ct, position) {
+ Deluge.details.OptionsTab.superclass.onRender.call(this, ct, position);
+
+ // This is another hack I think, so keep an eye out here when upgrading.
+ this.layout = new Ext.layout.ColumnLayout();
+ this.layout.setContainer(this);
+ this.doLayout();
+ },
+
+ clear: function() {
+ if (this.torrentId == null) return;
+ this.torrentId = null;
+ this.optionsManager.changeId(null);
+ },
+
+ reset: function() {
+ if (this.torrentId) this.optionsManager.reset();
+ },
+
+ update: function(torrentId) {
+ if (this.torrentId && !torrentId) this.clear(); // we want to clear the pane if we get a null torrent torrentIds
+
+ if (!torrentId) return; // We do not care about null torrentIds.
+
+ if (this.torrentId != torrentId) {
+ this.torrentId = torrentId;
+ this.optionsManager.changeId(torrentId);
+ }
+ deluge.client.web.get_torrent_status(torrentId, Deluge.Keys.Options, {
+ success: this.onRequestComplete,
+ scope: this,
+ });
+ },
+
+ onApply: function() {
+ var changed = this.optionsManager.getDirty();
+ deluge.client.core.set_torrent_options([this.torrentId], changed, {
+ success: function() {
+ this.optionsManager.commit();
+ },
+ scope: this,
+ });
+ },
+
+ onEditTrackers: function() {
+ deluge.editTrackers.show();
+ },
+
+ onMoveCompletedChecked: function(checkbox, checked) {
+ this.fields.move_completed_path.setDisabled(!checked);
+
+ if (!checked) return;
+ this.fields.move_completed_path.focus();
+ },
+
+ onStopRatioChecked: function(checkbox, checked) {
+ this.fields.remove_at_ratio.setDisabled(!checked);
+ this.fields.stop_ratio.setDisabled(!checked);
+ },
+
+ onRequestComplete: function(torrent, options) {
+ this.fields['private'].setValue(torrent['private']);
+ this.fields['private'].setDisabled(true);
+ delete torrent['private'];
+ torrent['auto_managed'] = torrent['is_auto_managed'];
+ torrent['prioritize_first_last_pieces'] =
+ torrent['prioritize_first_last'];
+ this.optionsManager.setDefault(torrent);
+ var stop_at_ratio = this.optionsManager.get('stop_at_ratio');
+ this.fields.remove_at_ratio.setDisabled(!stop_at_ratio);
+ this.fields.stop_ratio.setDisabled(!stop_at_ratio);
+ this.fields.move_completed_path.setDisabled(
+ !this.optionsManager.get('move_completed')
+ );
+ },
+});
+/**
+ * Deluge.details.PeersTab.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+(function() {
+ function flagRenderer(value) {
+ if (!value.replace(' ', '').replace(' ', '')) {
+ return '';
+ }
+ return String.format(
+ '<img src="{0}flag/{1}" />',
+ deluge.config.base,
+ value
+ );
+ }
+ function peerAddressRenderer(value, p, record) {
+ var seed =
+ record.data['seed'] == 1024 ? 'x-deluge-seed' : 'x-deluge-peer';
+ // Modify display of IPv6 to include brackets
+ var peer_ip = value.split(':');
+ if (peer_ip.length > 2) {
+ var port = peer_ip.pop();
+ var ip = peer_ip.join(':');
+ value = '[' + ip + ']:' + port;
+ }
+ return String.format('<div class="{0}">{1}</div>', seed, value);
+ }
+ function peerProgressRenderer(value) {
+ var progress = (value * 100).toFixed(0);
+ return Deluge.progressBar(progress, this.width - 8, progress + '%');
+ }
+
+ Deluge.details.PeersTab = Ext.extend(Ext.grid.GridPanel, {
+ // fast way to figure out if we have a peer already.
+ peers: {},
+
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ title: _('Peers'),
+ cls: 'x-deluge-peers',
+ store: new Ext.data.Store({
+ reader: new Ext.data.JsonReader(
+ {
+ idProperty: 'ip',
+ root: 'peers',
+ },
+ Deluge.data.Peer
+ ),
+ }),
+ columns: [
+ {
+ header: '&nbsp;',
+ width: 30,
+ sortable: true,
+ renderer: flagRenderer,
+ dataIndex: 'country',
+ },
+ {
+ header: _('Address'),
+ width: 125,
+ sortable: true,
+ renderer: peerAddressRenderer,
+ dataIndex: 'ip',
+ },
+ {
+ header: _('Client'),
+ width: 125,
+ sortable: true,
+ renderer: fplain,
+ dataIndex: 'client',
+ },
+ {
+ header: _('Progress'),
+ width: 150,
+ sortable: true,
+ renderer: peerProgressRenderer,
+ dataIndex: 'progress',
+ },
+ {
+ header: _('Down Speed'),
+ width: 100,
+ sortable: true,
+ renderer: fspeed,
+ dataIndex: 'down_speed',
+ },
+ {
+ header: _('Up Speed'),
+ width: 100,
+ sortable: true,
+ renderer: fspeed,
+ dataIndex: 'up_speed',
+ },
+ ],
+ stripeRows: true,
+ deferredRender: false,
+ autoScroll: true,
+ },
+ config
+ );
+ Deluge.details.PeersTab.superclass.constructor.call(this, config);
+ },
+
+ clear: function() {
+ this.getStore().removeAll();
+ this.peers = {};
+ },
+
+ update: function(torrentId) {
+ deluge.client.web.get_torrent_status(torrentId, Deluge.Keys.Peers, {
+ success: this.onRequestComplete,
+ scope: this,
+ });
+ },
+
+ onRequestComplete: function(torrent, options) {
+ if (!torrent) return;
+
+ var store = this.getStore();
+ var newPeers = [];
+ var addresses = {};
+
+ // Go through the peers updating and creating peer records
+ Ext.each(
+ torrent.peers,
+ function(peer) {
+ if (this.peers[peer.ip]) {
+ var record = store.getById(peer.ip);
+ record.beginEdit();
+ for (var k in peer) {
+ if (record.get(k) != peer[k]) {
+ record.set(k, peer[k]);
+ }
+ }
+ record.endEdit();
+ } else {
+ this.peers[peer.ip] = 1;
+ newPeers.push(new Deluge.data.Peer(peer, peer.ip));
+ }
+ addresses[peer.ip] = 1;
+ },
+ this
+ );
+ store.add(newPeers);
+
+ // Remove any peers that should not be left in the store.
+ store.each(function(record) {
+ if (!addresses[record.id]) {
+ store.remove(record);
+ delete this.peers[record.id];
+ }
+ }, this);
+ store.commitChanges();
+
+ var sortState = store.getSortState();
+ if (!sortState) return;
+ store.sort(sortState.field, sortState.direction);
+ },
+ });
+})();
+/**
+ * Deluge.details.StatusTab.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge.details');
+
+/**
+ * @class Deluge.details.StatusTab
+ * @extends Ext.Panel
+ */
+Deluge.details.StatusTab = Ext.extend(Ext.Panel, {
+ title: _('Status'),
+ autoScroll: true,
+
+ onRender: function(ct, position) {
+ Deluge.details.StatusTab.superclass.onRender.call(this, ct, position);
+
+ this.progressBar = this.add({
+ xtype: 'progress',
+ cls: 'x-deluge-status-progressbar',
+ });
+
+ this.status = this.add({
+ cls: 'x-deluge-status',
+ id: 'deluge-details-status',
+
+ border: false,
+ width: 1000,
+ listeners: {
+ render: {
+ fn: function(panel) {
+ panel.load({
+ url: deluge.config.base + 'render/tab_status.html',
+ text: _('Loading') + '...',
+ });
+ panel
+ .getUpdater()
+ .on('update', this.onPanelUpdate, this);
+ },
+ scope: this,
+ },
+ },
+ });
+ },
+
+ clear: function() {
+ this.progressBar.updateProgress(0, ' ');
+ for (var k in this.fields) {
+ this.fields[k].innerHTML = '';
+ }
+ },
+
+ update: function(torrentId) {
+ if (!this.fields) this.getFields();
+ deluge.client.web.get_torrent_status(torrentId, Deluge.Keys.Status, {
+ success: this.onRequestComplete,
+ scope: this,
+ });
+ },
+
+ onPanelUpdate: function(el, response) {
+ this.fields = {};
+ Ext.each(
+ Ext.query('dd', this.status.body.dom),
+ function(field) {
+ this.fields[field.className] = field;
+ },
+ this
+ );
+ },
+
+ onRequestComplete: function(status) {
+ seeds =
+ status.total_seeds > -1
+ ? status.num_seeds + ' (' + status.total_seeds + ')'
+ : status.num_seeds;
+ peers =
+ status.total_peers > -1
+ ? status.num_peers + ' (' + status.total_peers + ')'
+ : status.num_peers;
+ last_seen_complete =
+ status.last_seen_complete > 0.0
+ ? fdate(status.last_seen_complete)
+ : 'Never';
+ completed_time =
+ status.completed_time > 0.0 ? fdate(status.completed_time) : '';
+
+ var data = {
+ downloaded: fsize(status.total_done, true),
+ uploaded: fsize(status.total_uploaded, true),
+ share: status.ratio == -1 ? '&infin;' : status.ratio.toFixed(3),
+ announce: ftime(status.next_announce),
+ tracker_status: status.tracker_status,
+ downspeed: status.download_payload_rate
+ ? fspeed(status.download_payload_rate)
+ : '0.0 KiB/s',
+ upspeed: status.upload_payload_rate
+ ? fspeed(status.upload_payload_rate)
+ : '0.0 KiB/s',
+ eta: status.eta < 0 ? '&infin;' : ftime(status.eta),
+ pieces: status.num_pieces + ' (' + fsize(status.piece_length) + ')',
+ seeds: seeds,
+ peers: peers,
+ avail: status.distributed_copies.toFixed(3),
+ active_time: ftime(status.active_time),
+ seeding_time: ftime(status.seeding_time),
+ seed_rank: status.seed_rank,
+ time_added: fdate(status.time_added),
+ last_seen_complete: last_seen_complete,
+ completed_time: completed_time,
+ time_since_transfer: ftime(status.time_since_transfer),
+ };
+ data.auto_managed = _(status.is_auto_managed ? 'True' : 'False');
+
+ var translate_tracker_status = {
+ Error: _('Error'),
+ Warning: _('Warning'),
+ 'Announce OK': _('Announce OK'),
+ 'Announce Sent': _('Announce Sent'),
+ };
+ for (var key in translate_tracker_status) {
+ if (data.tracker_status.indexOf(key) != -1) {
+ data.tracker_status = data.tracker_status.replace(
+ key,
+ translate_tracker_status[key]
+ );
+ break;
+ }
+ }
+
+ data.downloaded +=
+ ' (' +
+ (status.total_payload_download
+ ? fsize(status.total_payload_download)
+ : '0.0 KiB') +
+ ')';
+ data.uploaded +=
+ ' (' +
+ (status.total_payload_upload
+ ? fsize(status.total_payload_upload)
+ : '0.0 KiB') +
+ ')';
+
+ for (var field in this.fields) {
+ this.fields[field].innerHTML = data[field];
+ }
+ var text = status.state + ' ' + status.progress.toFixed(2) + '%';
+ this.progressBar.updateProgress(status.progress / 100.0, text);
+ },
+});
+/**
+ * Deluge.preferences.BandwidthPage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Bandwidth
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Bandwidth = Ext.extend(Ext.form.FormPanel, {
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ border: false,
+ title: _('Bandwidth'),
+ header: false,
+ layout: 'form',
+ labelWidth: 10,
+ },
+ config
+ );
+ Deluge.preferences.Bandwidth.superclass.constructor.call(this, config);
+ },
+
+ initComponent: function() {
+ Deluge.preferences.Bandwidth.superclass.initComponent.call(this);
+
+ var om = deluge.preferences.getOptionsManager();
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Global Bandwidth Usage'),
+ labelWidth: 200,
+ defaultType: 'spinnerfield',
+ defaults: {
+ minValue: -1,
+ maxValue: 99999,
+ },
+ style: 'margin-bottom: 0px; padding-bottom: 0px;',
+ autoHeight: true,
+ });
+ om.bind(
+ 'max_connections_global',
+ fieldset.add({
+ name: 'max_connections_global',
+ fieldLabel: _('Maximum Connections:'),
+ labelSeparator: '',
+ width: 80,
+ value: -1,
+ decimalPrecision: 0,
+ })
+ );
+ om.bind(
+ 'max_upload_slots_global',
+ fieldset.add({
+ name: 'max_upload_slots_global',
+ fieldLabel: _('Maximum Upload Slots'),
+ labelSeparator: '',
+ width: 80,
+ value: -1,
+ decimalPrecision: 0,
+ })
+ );
+ om.bind(
+ 'max_download_speed',
+ fieldset.add({
+ name: 'max_download_speed',
+ fieldLabel: _('Maximum Download Speed (KiB/s):'),
+ labelSeparator: '',
+ width: 80,
+ value: -1.0,
+ decimalPrecision: 1,
+ })
+ );
+ om.bind(
+ 'max_upload_speed',
+ fieldset.add({
+ name: 'max_upload_speed',
+ fieldLabel: _('Maximum Upload Speed (KiB/s):'),
+ labelSeparator: '',
+ width: 80,
+ value: -1.0,
+ decimalPrecision: 1,
+ })
+ );
+ om.bind(
+ 'max_half_open_connections',
+ fieldset.add({
+ name: 'max_half_open_connections',
+ fieldLabel: _('Maximum Half-Open Connections:'),
+ labelSeparator: '',
+ width: 80,
+ value: -1,
+ decimalPrecision: 0,
+ })
+ );
+ om.bind(
+ 'max_connections_per_second',
+ fieldset.add({
+ name: 'max_connections_per_second',
+ fieldLabel: _('Maximum Connection Attempts per Second:'),
+ labelSeparator: '',
+ width: 80,
+ value: -1,
+ decimalPrecision: 0,
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: '',
+ defaultType: 'checkbox',
+ style:
+ 'padding-top: 0px; padding-bottom: 5px; margin-top: 0px; margin-bottom: 0px;',
+ autoHeight: true,
+ });
+ om.bind(
+ 'ignore_limits_on_local_network',
+ fieldset.add({
+ name: 'ignore_limits_on_local_network',
+ height: 22,
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('Ignore limits on local network'),
+ })
+ );
+ om.bind(
+ 'rate_limit_ip_overhead',
+ fieldset.add({
+ name: 'rate_limit_ip_overhead',
+ height: 22,
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('Rate limit IP overhead'),
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Per Torrent Bandwidth Usage'),
+ style: 'margin-bottom: 0px; padding-bottom: 0px;',
+ defaultType: 'spinnerfield',
+ labelWidth: 200,
+ defaults: {
+ minValue: -1,
+ maxValue: 99999,
+ },
+ autoHeight: true,
+ });
+ om.bind(
+ 'max_connections_per_torrent',
+ fieldset.add({
+ name: 'max_connections_per_torrent',
+ fieldLabel: _('Maximum Connections:'),
+ labelSeparator: '',
+ width: 80,
+ value: -1,
+ decimalPrecision: 0,
+ })
+ );
+ om.bind(
+ 'max_upload_slots_per_torrent',
+ fieldset.add({
+ name: 'max_upload_slots_per_torrent',
+ fieldLabel: _('Maximum Upload Slots:'),
+ labelSeparator: '',
+ width: 80,
+ value: -1,
+ decimalPrecision: 0,
+ })
+ );
+ om.bind(
+ 'max_download_speed_per_torrent',
+ fieldset.add({
+ name: 'max_download_speed_per_torrent',
+ fieldLabel: _('Maximum Download Speed (KiB/s):'),
+ labelSeparator: '',
+ width: 80,
+ value: -1,
+ decimalPrecision: 0,
+ })
+ );
+ om.bind(
+ 'max_upload_speed_per_torrent',
+ fieldset.add({
+ name: 'max_upload_speed_per_torrent',
+ fieldLabel: _('Maximum Upload Speed (KiB/s):'),
+ labelSeparator: '',
+ width: 80,
+ value: -1,
+ decimalPrecision: 0,
+ })
+ );
+ },
+});
+/**
+ * Deluge.preferences.CachePage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Cache
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Cache = Ext.extend(Ext.form.FormPanel, {
+ border: false,
+ title: _('Cache'),
+ header: false,
+ layout: 'form',
+
+ initComponent: function() {
+ Deluge.preferences.Cache.superclass.initComponent.call(this);
+
+ var om = deluge.preferences.getOptionsManager();
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Settings'),
+ autoHeight: true,
+ labelWidth: 180,
+ defaultType: 'spinnerfield',
+ defaults: {
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 999999,
+ },
+ });
+ om.bind(
+ 'cache_size',
+ fieldset.add({
+ fieldLabel: _('Cache Size (16 KiB Blocks):'),
+ labelSeparator: '',
+ name: 'cache_size',
+ width: 60,
+ value: 512,
+ })
+ );
+ om.bind(
+ 'cache_expiry',
+ fieldset.add({
+ fieldLabel: _('Cache Expiry (seconds):'),
+ labelSeparator: '',
+ name: 'cache_expiry',
+ width: 60,
+ value: 60,
+ })
+ );
+ },
+});
+/**
+ * Deluge.preferences.DaemonPage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Daemon
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Daemon = Ext.extend(Ext.form.FormPanel, {
+ border: false,
+ title: _('Daemon'),
+ header: false,
+ layout: 'form',
+
+ initComponent: function() {
+ Deluge.preferences.Daemon.superclass.initComponent.call(this);
+
+ var om = deluge.preferences.getOptionsManager();
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Port'),
+ autoHeight: true,
+ defaultType: 'spinnerfield',
+ });
+ om.bind(
+ 'daemon_port',
+ fieldset.add({
+ fieldLabel: _('Daemon port:'),
+ labelSeparator: '',
+ name: 'daemon_port',
+ value: 58846,
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 99999,
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Connections'),
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ });
+ om.bind(
+ 'allow_remote',
+ fieldset.add({
+ fieldLabel: '',
+ height: 22,
+ labelSeparator: '',
+ boxLabel: _('Allow Remote Connections'),
+ name: 'allow_remote',
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Other'),
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ });
+ om.bind(
+ 'new_release_check',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ height: 40,
+ boxLabel: _('Periodically check the website for new releases'),
+ id: 'new_release_check',
+ })
+ );
+ },
+});
+/**
+ * Deluge.preferences.DownloadsPage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Downloads
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Downloads = Ext.extend(Ext.FormPanel, {
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ border: false,
+ title: _('Downloads'),
+ header: false,
+ layout: 'form',
+ autoHeight: true,
+ width: 320,
+ },
+ config
+ );
+ Deluge.preferences.Downloads.superclass.constructor.call(this, config);
+ },
+
+ initComponent: function() {
+ Deluge.preferences.Downloads.superclass.initComponent.call(this);
+
+ var optMan = deluge.preferences.getOptionsManager();
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Folders'),
+ labelWidth: 150,
+ defaultType: 'togglefield',
+ autoHeight: true,
+ labelAlign: 'top',
+ width: 300,
+ style: 'margin-bottom: 5px; padding-bottom: 5px;',
+ });
+
+ optMan.bind(
+ 'download_location',
+ fieldset.add({
+ xtype: 'textfield',
+ name: 'download_location',
+ fieldLabel: _('Download to:'),
+ labelSeparator: '',
+ width: 280,
+ })
+ );
+
+ var field = fieldset.add({
+ name: 'move_completed_path',
+ fieldLabel: _('Move completed to:'),
+ labelSeparator: '',
+ width: 280,
+ });
+ optMan.bind('move_completed', field.toggle);
+ optMan.bind('move_completed_path', field.input);
+
+ field = fieldset.add({
+ name: 'torrentfiles_location',
+ fieldLabel: _('Copy of .torrent files to:'),
+ labelSeparator: '',
+ width: 280,
+ });
+ optMan.bind('copy_torrent_file', field.toggle);
+ optMan.bind('torrentfiles_location', field.input);
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Options'),
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ style: 'margin-bottom: 0; padding-bottom: 0;',
+ width: 280,
+ });
+ optMan.bind(
+ 'prioritize_first_last_pieces',
+ fieldset.add({
+ name: 'prioritize_first_last_pieces',
+ labelSeparator: '',
+ height: 22,
+ boxLabel: _('Prioritize first and last pieces of torrent'),
+ })
+ );
+ optMan.bind(
+ 'sequential_download',
+ fieldset.add({
+ name: 'sequential_download',
+ labelSeparator: '',
+ height: 22,
+ boxLabel: _('Sequential download'),
+ })
+ );
+ optMan.bind(
+ 'add_paused',
+ fieldset.add({
+ name: 'add_paused',
+ labelSeparator: '',
+ height: 22,
+ boxLabel: _('Add torrents in Paused state'),
+ })
+ );
+ optMan.bind(
+ 'pre_allocate_storage',
+ fieldset.add({
+ name: 'pre_allocate_storage',
+ labelSeparator: '',
+ height: 22,
+ boxLabel: _('Pre-allocate disk space'),
+ })
+ );
+ },
+});
+/**
+ * Deluge.preferences.EncryptionPage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Encryption
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Encryption = Ext.extend(Ext.form.FormPanel, {
+ border: false,
+ title: _('Encryption'),
+ header: false,
+
+ initComponent: function() {
+ Deluge.preferences.Encryption.superclass.initComponent.call(this);
+
+ var optMan = deluge.preferences.getOptionsManager();
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Settings'),
+ header: false,
+ autoHeight: true,
+ defaultType: 'combo',
+ width: 300,
+ });
+ optMan.bind(
+ 'enc_in_policy',
+ fieldset.add({
+ fieldLabel: _('Incoming:'),
+ labelSeparator: '',
+ mode: 'local',
+ width: 150,
+ store: new Ext.data.ArrayStore({
+ fields: ['id', 'text'],
+ data: [
+ [0, _('Forced')],
+ [1, _('Enabled')],
+ [2, _('Disabled')],
+ ],
+ }),
+ editable: false,
+ triggerAction: 'all',
+ valueField: 'id',
+ displayField: 'text',
+ })
+ );
+ optMan.bind(
+ 'enc_out_policy',
+ fieldset.add({
+ fieldLabel: _('Outgoing:'),
+ labelSeparator: '',
+ mode: 'local',
+ width: 150,
+ store: new Ext.data.SimpleStore({
+ fields: ['id', 'text'],
+ data: [
+ [0, _('Forced')],
+ [1, _('Enabled')],
+ [2, _('Disabled')],
+ ],
+ }),
+ editable: false,
+ triggerAction: 'all',
+ valueField: 'id',
+ displayField: 'text',
+ })
+ );
+ optMan.bind(
+ 'enc_level',
+ fieldset.add({
+ fieldLabel: _('Level:'),
+ labelSeparator: '',
+ mode: 'local',
+ width: 150,
+ store: new Ext.data.SimpleStore({
+ fields: ['id', 'text'],
+ data: [
+ [0, _('Handshake')],
+ [1, _('Full Stream')],
+ [2, _('Either')],
+ ],
+ }),
+ editable: false,
+ triggerAction: 'all',
+ valueField: 'id',
+ displayField: 'text',
+ })
+ );
+ },
+});
+/**
+ * Deluge.preferences.InstallPluginWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.InstallPluginWindow
+ * @extends Ext.Window
+ */
+Deluge.preferences.InstallPluginWindow = Ext.extend(Ext.Window, {
+ title: _('Install Plugin'),
+ layout: 'fit',
+ height: 115,
+ width: 350,
+ constrainHeader: true,
+ bodyStyle: 'padding: 10px 5px;',
+ buttonAlign: 'center',
+ closeAction: 'hide',
+ iconCls: 'x-deluge-install-plugin',
+ modal: true,
+ plain: true,
+
+ initComponent: function() {
+ Deluge.preferences.InstallPluginWindow.superclass.initComponent.call(
+ this
+ );
+ this.addButton(_('Install'), this.onInstall, this);
+
+ this.form = this.add({
+ xtype: 'form',
+ baseCls: 'x-plain',
+ labelWidth: 70,
+ autoHeight: true,
+ fileUpload: true,
+ items: [
+ {
+ xtype: 'fileuploadfield',
+ width: 240,
+ emptyText: _('Select an egg'),
+ fieldLabel: _('Plugin Egg'),
+ name: 'file',
+ buttonCfg: {
+ text: _('Browse...'),
+ },
+ },
+ ],
+ });
+ },
+
+ onInstall: function(field, e) {
+ this.form.getForm().submit({
+ url: deluge.config.base + 'upload',
+ waitMsg: _('Uploading your plugin...'),
+ success: this.onUploadSuccess,
+ scope: this,
+ });
+ },
+
+ onUploadPlugin: function(info, obj, response, request) {
+ this.fireEvent('pluginadded');
+ },
+
+ onUploadSuccess: function(fp, upload) {
+ this.hide();
+ if (upload.result.success) {
+ var filename = this.form.getForm().getFieldValues().file;
+ filename = filename.split('\\').slice(-1)[0];
+ var path = upload.result.files[0];
+ this.form.getForm().setValues({ file: '' });
+ deluge.client.web.upload_plugin(filename, path, {
+ success: this.onUploadPlugin,
+ scope: this,
+ filename: filename,
+ });
+ }
+ },
+});
+/**
+ * Deluge.preferences.InterfacePage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Interface
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Interface = Ext.extend(Ext.form.FormPanel, {
+ border: false,
+ title: _('Interface'),
+ header: false,
+ layout: 'form',
+
+ initComponent: function() {
+ Deluge.preferences.Interface.superclass.initComponent.call(this);
+
+ var om = (this.optionsManager = new Deluge.OptionsManager());
+ this.on('show', this.onPageShow, this);
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Interface'),
+ style: 'margin-bottom: 0px; padding-bottom: 5px; padding-top: 5px',
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ defaults: {
+ height: 17,
+ fieldLabel: '',
+ labelSeparator: '',
+ },
+ });
+ om.bind(
+ 'show_session_speed',
+ fieldset.add({
+ name: 'show_session_speed',
+ boxLabel: _('Show session speed in titlebar'),
+ })
+ );
+ om.bind(
+ 'sidebar_show_zero',
+ fieldset.add({
+ name: 'sidebar_show_zero',
+ boxLabel: _('Show filters with zero torrents'),
+ })
+ );
+ om.bind(
+ 'sidebar_multiple_filters',
+ fieldset.add({
+ name: 'sidebar_multiple_filters',
+ boxLabel: _('Allow the use of multiple filters at once'),
+ })
+ );
+
+ var languagePanel = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Language'),
+ style: 'margin-bottom: 0px; padding-bottom: 5px; padding-top: 5px',
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ });
+ this.language = om.bind(
+ 'language',
+ languagePanel.add({
+ xtype: 'combo',
+ labelSeparator: '',
+ name: 'language',
+ mode: 'local',
+ width: 200,
+ store: new Ext.data.ArrayStore({
+ fields: ['id', 'text'],
+ }),
+ editable: false,
+ triggerAction: 'all',
+ valueField: 'id',
+ displayField: 'text',
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('WebUI Password'),
+ style: 'margin-bottom: 0px; padding-bottom: 5px; padding-top: 5px',
+ autoHeight: true,
+ labelWidth: 100,
+ defaultType: 'textfield',
+ defaults: {
+ width: 100,
+ inputType: 'password',
+ labelStyle: 'padding-left: 5px',
+ height: 20,
+ labelSeparator: '',
+ },
+ });
+
+ this.oldPassword = fieldset.add({
+ name: 'old_password',
+ fieldLabel: _('Old:'),
+ });
+ this.newPassword = fieldset.add({
+ name: 'new_password',
+ fieldLabel: _('New:'),
+ });
+ this.confirmPassword = fieldset.add({
+ name: 'confirm_password',
+ fieldLabel: _('Confirm:'),
+ });
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Server'),
+ style: 'padding-top: 5px; margin-bottom: 0px; padding-bottom: 5px',
+ autoHeight: true,
+ labelWidth: 100,
+ defaultType: 'spinnerfield',
+ defaults: {
+ labelSeparator: '',
+ labelStyle: 'padding-left: 5px',
+ height: 20,
+ width: 80,
+ },
+ });
+ om.bind(
+ 'session_timeout',
+ fieldset.add({
+ name: 'session_timeout',
+ fieldLabel: _('Session Timeout:'),
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 99999,
+ })
+ );
+ om.bind(
+ 'port',
+ fieldset.add({
+ name: 'port',
+ fieldLabel: _('Port:'),
+ decimalPrecision: 0,
+ minValue: 1,
+ maxValue: 65535,
+ })
+ );
+ this.httpsField = om.bind(
+ 'https',
+ fieldset.add({
+ xtype: 'checkbox',
+ name: 'https',
+ hideLabel: true,
+ width: 300,
+ style: 'margin-left: 5px',
+ boxLabel: _(
+ 'Enable SSL (paths relative to Deluge config folder)'
+ ),
+ })
+ );
+ this.httpsField.on('check', this.onSSLCheck, this);
+ this.pkeyField = om.bind(
+ 'pkey',
+ fieldset.add({
+ xtype: 'textfield',
+ disabled: true,
+ name: 'pkey',
+ width: 180,
+ fieldLabel: _('Private Key:'),
+ })
+ );
+ this.certField = om.bind(
+ 'cert',
+ fieldset.add({
+ xtype: 'textfield',
+ disabled: true,
+ name: 'cert',
+ width: 180,
+ fieldLabel: _('Certificate:'),
+ })
+ );
+ },
+
+ onApply: function() {
+ var changed = this.optionsManager.getDirty();
+ if (!Ext.isObjectEmpty(changed)) {
+ deluge.client.web.set_config(changed, {
+ success: this.onSetConfig,
+ scope: this,
+ });
+
+ for (var key in deluge.config) {
+ deluge.config[key] = this.optionsManager.get(key);
+ }
+ if ('language' in changed) {
+ Ext.Msg.show({
+ title: _('WebUI Language Changed'),
+ msg: _(
+ 'Do you want to refresh the page now to use the new language?'
+ ),
+ buttons: {
+ yes: _('Refresh'),
+ no: _('Close'),
+ },
+ multiline: false,
+ fn: function(btnText) {
+ if (btnText === 'yes') location.reload();
+ },
+ icon: Ext.MessageBox.QUESTION,
+ });
+ }
+ }
+ if (this.oldPassword.getValue() || this.newPassword.getValue()) {
+ this.onPasswordChange();
+ }
+ },
+
+ onOk: function() {
+ this.onApply();
+ },
+
+ onGotConfig: function(config) {
+ this.optionsManager.set(config);
+ },
+
+ onGotLanguages: function(info, obj, response, request) {
+ info.unshift(['', _('System Default')]);
+ this.language.store.loadData(info);
+ this.language.setValue(this.optionsManager.get('language'));
+ },
+
+ onPasswordChange: function() {
+ var newPassword = this.newPassword.getValue();
+ if (newPassword != this.confirmPassword.getValue()) {
+ Ext.MessageBox.show({
+ title: _('Invalid Password'),
+ msg: _("Your passwords don't match!"),
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ icon: Ext.MessageBox.ERROR,
+ iconCls: 'x-deluge-icon-error',
+ });
+ return;
+ }
+
+ var oldPassword = this.oldPassword.getValue();
+ deluge.client.auth.change_password(oldPassword, newPassword, {
+ success: function(result) {
+ if (!result) {
+ Ext.MessageBox.show({
+ title: _('Password'),
+ msg: _('Your old password was incorrect!'),
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ icon: Ext.MessageBox.ERROR,
+ iconCls: 'x-deluge-icon-error',
+ });
+ this.oldPassword.setValue('');
+ } else {
+ Ext.MessageBox.show({
+ title: _('Change Successful'),
+ msg: _('Your password was successfully changed!'),
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ icon: Ext.MessageBox.INFO,
+ iconCls: 'x-deluge-icon-info',
+ });
+ this.oldPassword.setValue('');
+ this.newPassword.setValue('');
+ this.confirmPassword.setValue('');
+ }
+ },
+ scope: this,
+ });
+ },
+
+ onSetConfig: function() {
+ this.optionsManager.commit();
+ },
+
+ onPageShow: function() {
+ deluge.client.web.get_config({
+ success: this.onGotConfig,
+ scope: this,
+ });
+ deluge.client.webutils.get_languages({
+ success: this.onGotLanguages,
+ scope: this,
+ });
+ },
+
+ onSSLCheck: function(e, checked) {
+ this.pkeyField.setDisabled(!checked);
+ this.certField.setDisabled(!checked);
+ },
+});
+/**
+ * Deluge.preferences.NetworkPage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+// custom Vtype for vtype:'IPAddress'
+Ext.apply(Ext.form.VTypes, {
+ IPAddress: function(v) {
+ return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(v);
+ },
+ IPAddressText: 'Must be a numeric IP address',
+ IPAddressMask: /[\d\.]/i,
+});
+
+/**
+ * @class Deluge.preferences.Network
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Network = Ext.extend(Ext.form.FormPanel, {
+ border: false,
+ layout: 'form',
+ title: _('Network'),
+ header: false,
+
+ initComponent: function() {
+ Deluge.preferences.Network.superclass.initComponent.call(this);
+ var optMan = deluge.preferences.getOptionsManager();
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Incoming Address'),
+ style: 'margin-bottom: 5px; padding-bottom: 0px;',
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'textfield',
+ });
+ optMan.bind(
+ 'listen_interface',
+ fieldset.add({
+ name: 'listen_interface',
+ fieldLabel: '',
+ labelSeparator: '',
+ width: 200,
+ vtype: 'IPAddress',
+ })
+ );
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Incoming Port'),
+ style: 'margin-bottom: 5px; padding-bottom: 0px;',
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ });
+ optMan.bind(
+ 'random_port',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('Use Random Port'),
+ name: 'random_port',
+ height: 22,
+ listeners: {
+ check: {
+ fn: function(e, checked) {
+ this.listenPort.setDisabled(checked);
+ },
+ scope: this,
+ },
+ },
+ })
+ );
+
+ this.listenPort = fieldset.add({
+ xtype: 'spinnerfield',
+ name: 'listen_port',
+ fieldLabel: '',
+ labelSeparator: '',
+ width: 75,
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 0,
+ minValue: 0,
+ maxValue: 65535,
+ },
+ });
+ optMan.bind('listen_ports', this.listenPort);
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Outgoing Interface'),
+ style: 'margin-bottom: 5px; padding-bottom: 0px;',
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'textfield',
+ });
+ optMan.bind(
+ 'outgoing_interface',
+ fieldset.add({
+ name: 'outgoing_interface',
+ fieldLabel: '',
+ labelSeparator: '',
+ width: 40,
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Outgoing Ports'),
+ style: 'margin-bottom: 5px; padding-bottom: 0px;',
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ });
+ optMan.bind(
+ 'random_outgoing_ports',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('Use Random Ports'),
+ name: 'random_outgoing_ports',
+ height: 22,
+ listeners: {
+ check: {
+ fn: function(e, checked) {
+ this.outgoingPorts.setDisabled(checked);
+ },
+ scope: this,
+ },
+ },
+ })
+ );
+ this.outgoingPorts = fieldset.add({
+ xtype: 'spinnergroup',
+ name: 'outgoing_ports',
+ fieldLabel: '',
+ labelSeparator: '',
+ colCfg: {
+ labelWidth: 40,
+ style: 'margin-right: 10px;',
+ },
+ items: [
+ {
+ fieldLabel: _('From:'),
+ labelSeparator: '',
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 0,
+ minValue: 0,
+ maxValue: 65535,
+ },
+ },
+ {
+ fieldLabel: _('To:'),
+ labelSeparator: '',
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 0,
+ minValue: 0,
+ maxValue: 65535,
+ },
+ },
+ ],
+ });
+ optMan.bind('outgoing_ports', this.outgoingPorts);
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Network Extras'),
+ autoHeight: true,
+ layout: 'table',
+ layoutConfig: {
+ columns: 3,
+ },
+ defaultType: 'checkbox',
+ });
+ optMan.bind(
+ 'upnp',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('UPnP'),
+ name: 'upnp',
+ })
+ );
+ optMan.bind(
+ 'natpmp',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('NAT-PMP'),
+ ctCls: 'x-deluge-indent-checkbox',
+ name: 'natpmp',
+ })
+ );
+ optMan.bind(
+ 'utpex',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('Peer Exchange'),
+ ctCls: 'x-deluge-indent-checkbox',
+ name: 'utpex',
+ })
+ );
+ optMan.bind(
+ 'lsd',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('LSD'),
+ name: 'lsd',
+ })
+ );
+ optMan.bind(
+ 'dht',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('DHT'),
+ ctCls: 'x-deluge-indent-checkbox',
+ name: 'dht',
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Type Of Service'),
+ style: 'margin-bottom: 5px; padding-bottom: 0px;',
+ bodyStyle: 'margin: 0px; padding: 0px',
+ autoHeight: true,
+ defaultType: 'textfield',
+ });
+ optMan.bind(
+ 'peer_tos',
+ fieldset.add({
+ name: 'peer_tos',
+ fieldLabel: _('Peer TOS Byte:'),
+ labelSeparator: '',
+ width: 40,
+ })
+ );
+ },
+});
+/**
+ * Deluge.preferences.OtherPage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Other
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Other = Ext.extend(Ext.form.FormPanel, {
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ border: false,
+ title: _('Other'),
+ header: false,
+ layout: 'form',
+ },
+ config
+ );
+ Deluge.preferences.Other.superclass.constructor.call(this, config);
+ },
+
+ initComponent: function() {
+ Deluge.preferences.Other.superclass.initComponent.call(this);
+
+ var optMan = deluge.preferences.getOptionsManager();
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Updates'),
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ });
+ optMan.bind(
+ 'new_release_check',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ height: 22,
+ name: 'new_release_check',
+ boxLabel: _('Be alerted about new releases'),
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('System Information'),
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ });
+ fieldset.add({
+ xtype: 'panel',
+ border: false,
+ bodyCfg: {
+ html: _(
+ 'Help us improve Deluge by sending us your Python version, PyGTK version, OS and processor types. Absolutely no other information is sent.'
+ ),
+ },
+ });
+ optMan.bind(
+ 'send_info',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ height: 22,
+ boxLabel: _('Yes, please send anonymous statistics'),
+ name: 'send_info',
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('GeoIP Database'),
+ autoHeight: true,
+ labelWidth: 80,
+ defaultType: 'textfield',
+ });
+ optMan.bind(
+ 'geoip_db_location',
+ fieldset.add({
+ name: 'geoip_db_location',
+ fieldLabel: _('Path:'),
+ labelSeparator: '',
+ width: 200,
+ })
+ );
+ },
+});
+/**
+ * Deluge.preferences.PluginsPage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Plugins
+ * @extends Ext.Panel
+ */
+Deluge.preferences.Plugins = Ext.extend(Ext.Panel, {
+ layout: 'border',
+ title: _('Plugins'),
+ header: false,
+ border: false,
+ cls: 'x-deluge-plugins',
+
+ pluginTemplate: new Ext.Template(
+ '<dl class="singleline">' +
+ '<dt>' +
+ _('Author:') +
+ '</dt><dd>{author}</dd>' +
+ '<dt>' +
+ _('Version:') +
+ '</dt><dd>{version}</dd>' +
+ '<dt>' +
+ _('Author Email:') +
+ '</dt><dd>{email}</dd>' +
+ '<dt>' +
+ _('Homepage:') +
+ '</dt><dd>{homepage}</dd>' +
+ '<dt>' +
+ _('Details:') +
+ '</dt><dd style="white-space:normal">{details}</dd>' +
+ '</dl>'
+ ),
+
+ initComponent: function() {
+ Deluge.preferences.Plugins.superclass.initComponent.call(this);
+ this.defaultValues = {
+ version: '',
+ email: '',
+ homepage: '',
+ details: '',
+ };
+ this.pluginTemplate.compile();
+
+ var checkboxRenderer = function(v, p, record) {
+ p.css += ' x-grid3-check-col-td';
+ return (
+ '<div class="x-grid3-check-col' + (v ? '-on' : '') + '"> </div>'
+ );
+ };
+
+ this.list = this.add({
+ xtype: 'listview',
+ store: new Ext.data.ArrayStore({
+ fields: [
+ { name: 'enabled', mapping: 0 },
+ { name: 'plugin', mapping: 1, sortType: 'asUCString' },
+ ],
+ }),
+ columns: [
+ {
+ id: 'enabled',
+ header: _('Enabled'),
+ width: 0.2,
+ sortable: true,
+ tpl: new Ext.XTemplate('{enabled:this.getCheckbox}', {
+ getCheckbox: function(v) {
+ return (
+ '<div class="x-grid3-check-col' +
+ (v ? '-on' : '') +
+ '" rel="chkbox"> </div>'
+ );
+ },
+ }),
+ dataIndex: 'enabled',
+ },
+ {
+ id: 'plugin',
+ header: _('Plugin'),
+ width: 0.8,
+ sortable: true,
+ dataIndex: 'plugin',
+ },
+ ],
+ singleSelect: true,
+ autoExpandColumn: 'plugin',
+ listeners: {
+ selectionchange: { fn: this.onPluginSelect, scope: this },
+ },
+ });
+
+ this.panel = this.add({
+ region: 'center',
+ autoScroll: true,
+ items: [this.list],
+ bbar: new Ext.Toolbar({
+ items: [
+ {
+ cls: 'x-btn-text-icon',
+ iconCls: 'x-deluge-install-plugin',
+ text: _('Install'),
+ handler: this.onInstallPluginWindow,
+ scope: this,
+ },
+ '->',
+ {
+ cls: 'x-btn-text-icon',
+ text: _('Find More'),
+ iconCls: 'x-deluge-find-more',
+ handler: this.onFindMorePlugins,
+ scope: this,
+ },
+ ],
+ }),
+ });
+
+ var pp = (this.pluginInfo = this.add({
+ xtype: 'panel',
+ border: false,
+ height: 100,
+ region: 'south',
+ padding: '5',
+ autoScroll: true,
+ bodyCfg: {
+ style: 'white-space: nowrap',
+ },
+ }));
+
+ this.pluginInfo.on('render', this.onPluginInfoRender, this);
+ this.list.on('click', this.onNodeClick, this);
+ deluge.preferences.on('show', this.onPreferencesShow, this);
+ deluge.events.on('PluginDisabledEvent', this.onPluginDisabled, this);
+ deluge.events.on('PluginEnabledEvent', this.onPluginEnabled, this);
+ },
+
+ disablePlugin: function(plugin) {
+ deluge.client.core.disable_plugin(plugin);
+ },
+
+ enablePlugin: function(plugin) {
+ deluge.client.core.enable_plugin(plugin);
+ },
+
+ setInfo: function(plugin) {
+ if (!this.pluginInfo.rendered) return;
+ var values = plugin || this.defaultValues;
+ this.pluginInfo.body.dom.innerHTML = this.pluginTemplate.apply(values);
+ },
+
+ updatePlugins: function() {
+ var onGotAvailablePlugins = function(plugins) {
+ this.availablePlugins = plugins.sort(function(a, b) {
+ return a.toLowerCase().localeCompare(b.toLowerCase());
+ });
+
+ deluge.client.core.get_enabled_plugins({
+ success: onGotEnabledPlugins,
+ scope: this,
+ });
+ };
+
+ var onGotEnabledPlugins = function(plugins) {
+ this.enabledPlugins = plugins;
+ this.onGotPlugins();
+ };
+
+ deluge.client.core.get_available_plugins({
+ success: onGotAvailablePlugins,
+ scope: this,
+ });
+ },
+
+ updatePluginsGrid: function() {
+ var plugins = [];
+ Ext.each(
+ this.availablePlugins,
+ function(plugin) {
+ if (this.enabledPlugins.indexOf(plugin) > -1) {
+ plugins.push([true, plugin]);
+ } else {
+ plugins.push([false, plugin]);
+ }
+ },
+ this
+ );
+ this.list.getStore().loadData(plugins);
+ },
+
+ onNodeClick: function(dv, index, node, e) {
+ var el = new Ext.Element(e.target);
+ if (el.getAttribute('rel') != 'chkbox') return;
+
+ var r = dv.getStore().getAt(index);
+ if (r.get('plugin') == 'WebUi') return;
+ r.set('enabled', !r.get('enabled'));
+ r.commit();
+ if (r.get('enabled')) {
+ this.enablePlugin(r.get('plugin'));
+ } else {
+ this.disablePlugin(r.get('plugin'));
+ }
+ },
+
+ onFindMorePlugins: function() {
+ window.open('http://dev.deluge-torrent.org/wiki/Plugins');
+ },
+
+ onGotPlugins: function() {
+ this.setInfo();
+ this.updatePluginsGrid();
+ },
+
+ onGotPluginInfo: function(info) {
+ var values = {
+ author: info['Author'],
+ version: info['Version'],
+ email: info['Author-email'],
+ homepage: info['Home-page'],
+ details: info['Description'],
+ };
+ this.setInfo(values);
+ delete info;
+ },
+
+ onInstallPluginWindow: function() {
+ if (!this.installWindow) {
+ this.installWindow = new Deluge.preferences.InstallPluginWindow();
+ this.installWindow.on('pluginadded', this.onPluginInstall, this);
+ }
+ this.installWindow.show();
+ },
+
+ onPluginEnabled: function(pluginName) {
+ var index = this.list.getStore().find('plugin', pluginName);
+ if (index == -1) return;
+ var plugin = this.list.getStore().getAt(index);
+ plugin.set('enabled', true);
+ plugin.commit();
+ },
+
+ onPluginDisabled: function(pluginName) {
+ var index = this.list.getStore().find('plugin', pluginName);
+ if (index == -1) return;
+ var plugin = this.list.getStore().getAt(index);
+ plugin.set('enabled', false);
+ plugin.commit();
+ },
+
+ onPluginInstall: function() {
+ this.updatePlugins();
+ },
+
+ onPluginSelect: function(dv, selections) {
+ if (selections.length == 0) return;
+ var r = dv.getRecords(selections)[0];
+ deluge.client.web.get_plugin_info(r.get('plugin'), {
+ success: this.onGotPluginInfo,
+ scope: this,
+ });
+ },
+
+ onPreferencesShow: function() {
+ this.updatePlugins();
+ },
+
+ onPluginInfoRender: function(ct, position) {
+ this.setInfo();
+ },
+});
+/**
+ * Deluge.preferences.PreferencesWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+PreferencesRecord = Ext.data.Record.create([{ name: 'name', type: 'string' }]);
+
+/**
+ * @class Deluge.preferences.PreferencesWindow
+ * @extends Ext.Window
+ */
+Deluge.preferences.PreferencesWindow = Ext.extend(Ext.Window, {
+ /**
+ * @property {String} currentPage The currently selected page.
+ */
+ currentPage: null,
+
+ title: _('Preferences'),
+ layout: 'border',
+ width: 485,
+ height: 500,
+ border: false,
+ constrainHeader: true,
+ buttonAlign: 'right',
+ closeAction: 'hide',
+ closable: true,
+ iconCls: 'x-deluge-preferences',
+ plain: true,
+ resizable: false,
+
+ pages: {},
+
+ initComponent: function() {
+ Deluge.preferences.PreferencesWindow.superclass.initComponent.call(
+ this
+ );
+
+ this.list = new Ext.list.ListView({
+ store: new Ext.data.Store(),
+ columns: [
+ {
+ id: 'name',
+ renderer: fplain,
+ dataIndex: 'name',
+ },
+ ],
+ singleSelect: true,
+ listeners: {
+ selectionchange: {
+ fn: this.onPageSelect,
+ scope: this,
+ },
+ },
+ hideHeaders: true,
+ autoExpandColumn: 'name',
+ deferredRender: false,
+ autoScroll: true,
+ collapsible: true,
+ });
+ this.add({
+ region: 'west',
+ items: [this.list],
+ width: 120,
+ margins: '0 5 0 0',
+ cmargins: '0 5 0 0',
+ });
+
+ this.configPanel = this.add({
+ type: 'container',
+ autoDestroy: false,
+ region: 'center',
+ layout: 'card',
+ layoutConfig: {
+ deferredRender: true,
+ },
+ autoScroll: true,
+ width: 300,
+ });
+
+ this.addButton(_('Close'), this.onClose, this);
+ this.addButton(_('Apply'), this.onApply, this);
+ this.addButton(_('OK'), this.onOk, this);
+
+ this.optionsManager = new Deluge.OptionsManager();
+ this.on('afterrender', this.onAfterRender, this);
+ this.on('show', this.onShow, this);
+
+ this.initPages();
+ },
+
+ initPages: function() {
+ deluge.preferences = this;
+ this.addPage(new Deluge.preferences.Downloads());
+ this.addPage(new Deluge.preferences.Network());
+ this.addPage(new Deluge.preferences.Encryption());
+ this.addPage(new Deluge.preferences.Bandwidth());
+ this.addPage(new Deluge.preferences.Interface());
+ this.addPage(new Deluge.preferences.Other());
+ this.addPage(new Deluge.preferences.Daemon());
+ this.addPage(new Deluge.preferences.Queue());
+ this.addPage(new Deluge.preferences.Proxy());
+ this.addPage(new Deluge.preferences.Cache());
+ this.addPage(new Deluge.preferences.Plugins());
+ },
+
+ onApply: function(e) {
+ var changed = this.optionsManager.getDirty();
+ if (!Ext.isObjectEmpty(changed)) {
+ // Workaround for only displaying single listen port but still pass array to core.
+ if ('listen_ports' in changed) {
+ changed.listen_ports = [
+ changed.listen_ports,
+ changed.listen_ports,
+ ];
+ }
+ deluge.client.core.set_config(changed, {
+ success: this.onSetConfig,
+ scope: this,
+ });
+ }
+
+ for (var page in this.pages) {
+ if (this.pages[page].onApply) this.pages[page].onApply();
+ }
+ },
+
+ /**
+ * Return the options manager for the preferences window.
+ * @returns {Deluge.OptionsManager} the options manager
+ */
+ getOptionsManager: function() {
+ return this.optionsManager;
+ },
+
+ /**
+ * Adds a page to the preferences window.
+ * @param {Mixed} page
+ */
+ addPage: function(page) {
+ var store = this.list.getStore();
+ var name = page.title;
+ store.add([new PreferencesRecord({ name: name })]);
+ page['bodyStyle'] = 'padding: 5px';
+ page.preferences = this;
+ this.pages[name] = this.configPanel.add(page);
+ this.pages[name].index = -1;
+ return this.pages[name];
+ },
+
+ /**
+ * Removes a preferences page from the window.
+ * @param {mixed} name
+ */
+ removePage: function(page) {
+ var name = page.title;
+ var store = this.list.getStore();
+ store.removeAt(store.find('name', name));
+ this.configPanel.remove(page);
+ delete this.pages[page.title];
+ },
+
+ /**
+ * Select which preferences page is displayed.
+ * @param {String} page The page name to change to
+ */
+ selectPage: function(page) {
+ if (this.pages[page].index < 0) {
+ this.pages[page].index = this.configPanel.items.indexOf(
+ this.pages[page]
+ );
+ }
+ this.list.select(this.pages[page].index);
+ },
+
+ // private
+ doSelectPage: function(page) {
+ if (this.pages[page].index < 0) {
+ this.pages[page].index = this.configPanel.items.indexOf(
+ this.pages[page]
+ );
+ }
+ this.configPanel.getLayout().setActiveItem(this.pages[page].index);
+ this.currentPage = page;
+ },
+
+ // private
+ onGotConfig: function(config) {
+ this.getOptionsManager().set(config);
+ },
+
+ // private
+ onPageSelect: function(list, selections) {
+ var r = list.getRecord(selections[0]);
+ this.doSelectPage(r.get('name'));
+ },
+
+ // private
+ onSetConfig: function() {
+ this.getOptionsManager().commit();
+ },
+
+ // private
+ onAfterRender: function() {
+ if (!this.list.getSelectionCount()) {
+ this.list.select(0);
+ }
+ this.configPanel.getLayout().setActiveItem(0);
+ },
+
+ // private
+ onShow: function() {
+ if (!deluge.client.core) return;
+ deluge.client.core.get_config({
+ success: this.onGotConfig,
+ scope: this,
+ });
+ },
+
+ // private
+ onClose: function() {
+ this.hide();
+ },
+
+ // private
+ onOk: function() {
+ var changed = this.optionsManager.getDirty();
+ if (!Ext.isObjectEmpty(changed)) {
+ deluge.client.core.set_config(changed, {
+ success: this.onSetConfig,
+ scope: this,
+ });
+ }
+
+ for (var page in this.pages) {
+ if (this.pages[page].onOk) this.pages[page].onOk();
+ }
+
+ this.hide();
+ },
+});
+/**
+ * Deluge.preferences.ProxyField.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.ProxyField
+ * @extends Ext.form.FieldSet
+ */
+Deluge.preferences.ProxyField = Ext.extend(Ext.form.FieldSet, {
+ border: false,
+ autoHeight: true,
+ labelWidth: 70,
+
+ initComponent: function() {
+ Deluge.preferences.ProxyField.superclass.initComponent.call(this);
+ this.proxyType = this.add({
+ xtype: 'combo',
+ fieldLabel: _('Type:'),
+ labelSeparator: '',
+ name: 'proxytype',
+ mode: 'local',
+ width: 150,
+ store: new Ext.data.ArrayStore({
+ fields: ['id', 'text'],
+ data: [
+ [0, _('None')],
+ [1, _('Socks4')],
+ [2, _('Socks5')],
+ [3, _('Socks5 Auth')],
+ [4, _('HTTP')],
+ [5, _('HTTP Auth')],
+ [6, _('I2P')],
+ ],
+ }),
+ editable: false,
+ triggerAction: 'all',
+ valueField: 'id',
+ displayField: 'text',
+ });
+ this.proxyType.on('change', this.onFieldChange, this);
+ this.proxyType.on('select', this.onTypeSelect, this);
+
+ this.hostname = this.add({
+ xtype: 'textfield',
+ name: 'hostname',
+ fieldLabel: _('Host:'),
+ labelSeparator: '',
+ width: 220,
+ });
+ this.hostname.on('change', this.onFieldChange, this);
+
+ this.port = this.add({
+ xtype: 'spinnerfield',
+ name: 'port',
+ fieldLabel: _('Port:'),
+ labelSeparator: '',
+ width: 80,
+ decimalPrecision: 0,
+ minValue: 0,
+ maxValue: 65535,
+ });
+ this.port.on('change', this.onFieldChange, this);
+
+ this.username = this.add({
+ xtype: 'textfield',
+ name: 'username',
+ fieldLabel: _('Username:'),
+ labelSeparator: '',
+ width: 220,
+ });
+ this.username.on('change', this.onFieldChange, this);
+
+ this.password = this.add({
+ xtype: 'textfield',
+ name: 'password',
+ fieldLabel: _('Password:'),
+ labelSeparator: '',
+ inputType: 'password',
+ width: 220,
+ });
+ this.password.on('change', this.onFieldChange, this);
+
+ this.proxy_host_resolve = this.add({
+ xtype: 'checkbox',
+ name: 'proxy_host_resolve',
+ fieldLabel: '',
+ boxLabel: _('Proxy Hostnames'),
+ width: 220,
+ });
+ this.proxy_host_resolve.on('change', this.onFieldChange, this);
+
+ this.proxy_peer_conn = this.add({
+ xtype: 'checkbox',
+ name: 'proxy_peer_conn',
+ fieldLabel: '',
+ boxLabel: _('Proxy Peers'),
+ width: 220,
+ });
+ this.proxy_peer_conn.on('change', this.onFieldChange, this);
+
+ this.proxy_tracker_conn = this.add({
+ xtype: 'checkbox',
+ name: 'proxy_tracker_conn',
+ fieldLabel: '',
+ boxLabel: _('Proxy Trackers'),
+ width: 220,
+ });
+ this.proxy_tracker_conn.on('change', this.onFieldChange, this);
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Force Proxy'),
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ style: 'padding-left: 0px; margin-top: 10px',
+ });
+
+ this.force_proxy = fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ height: 20,
+ name: 'force_proxy',
+ boxLabel: _('Force Use of Proxy'),
+ });
+ this.force_proxy.on('change', this.onFieldChange, this);
+
+ this.anonymous_mode = fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ height: 20,
+ name: 'anonymous_mode',
+ boxLabel: _('Hide Client Identity'),
+ });
+ this.anonymous_mode.on('change', this.onFieldChange, this);
+
+ this.setting = false;
+ },
+
+ getName: function() {
+ return this.initialConfig.name;
+ },
+
+ getValue: function() {
+ return {
+ type: this.proxyType.getValue(),
+ hostname: this.hostname.getValue(),
+ port: Number(this.port.getValue()),
+ username: this.username.getValue(),
+ password: this.password.getValue(),
+ proxy_hostnames: this.proxy_host_resolve.getValue(),
+ proxy_peer_connections: this.proxy_peer_conn.getValue(),
+ proxy_tracker_connections: this.proxy_tracker_conn.getValue(),
+ force_proxy: this.force_proxy.getValue(),
+ anonymous_mode: this.anonymous_mode.getValue(),
+ };
+ },
+
+ // Set the values of the proxies
+ setValue: function(value) {
+ this.setting = true;
+ this.proxyType.setValue(value['type']);
+ var index = this.proxyType.getStore().find('id', value['type']);
+ var record = this.proxyType.getStore().getAt(index);
+
+ this.hostname.setValue(value['hostname']);
+ this.port.setValue(value['port']);
+ this.username.setValue(value['username']);
+ this.password.setValue(value['password']);
+ this.proxy_host_resolve.setValue(value['proxy_hostnames']);
+ this.proxy_peer_conn.setValue(value['proxy_peer_connections']);
+ this.proxy_tracker_conn.setValue(value['proxy_tracker_connections']);
+ this.force_proxy.setValue(value['force_proxy']);
+ this.anonymous_mode.setValue(value['anonymous_mode']);
+
+ this.onTypeSelect(this.type, record, index);
+ this.setting = false;
+ },
+
+ onFieldChange: function(field, newValue, oldValue) {
+ if (this.setting) return;
+ var newValues = this.getValue();
+ var oldValues = Ext.apply({}, newValues);
+ oldValues[field.getName()] = oldValue;
+
+ this.fireEvent('change', this, newValues, oldValues);
+ },
+
+ onTypeSelect: function(combo, record, index) {
+ var typeId = record.get('id');
+ if (typeId > 0) {
+ this.hostname.show();
+ this.port.show();
+ this.proxy_peer_conn.show();
+ this.proxy_tracker_conn.show();
+ if (typeId > 1 && typeId < 6) {
+ this.proxy_host_resolve.show();
+ } else {
+ this.proxy_host_resolve.hide();
+ }
+ } else {
+ this.hostname.hide();
+ this.port.hide();
+ this.proxy_host_resolve.hide();
+ this.proxy_peer_conn.hide();
+ this.proxy_tracker_conn.hide();
+ }
+
+ if (typeId == 3 || typeId == 5) {
+ this.username.show();
+ this.password.show();
+ } else {
+ this.username.hide();
+ this.password.hide();
+ }
+ },
+});
+/**
+ * Deluge.preferences.ProxyPage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Proxy
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Proxy = Ext.extend(Ext.form.FormPanel, {
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ border: false,
+ title: _('Proxy'),
+ header: false,
+ layout: 'form',
+ autoScroll: true,
+ },
+ config
+ );
+ Deluge.preferences.Proxy.superclass.constructor.call(this, config);
+ },
+
+ initComponent: function() {
+ Deluge.preferences.Proxy.superclass.initComponent.call(this);
+ this.proxy = this.add(
+ new Deluge.preferences.ProxyField({
+ title: _('Proxy'),
+ name: 'proxy',
+ })
+ );
+ this.proxy.on('change', this.onProxyChange, this);
+ deluge.preferences.getOptionsManager().bind('proxy', this.proxy);
+ },
+
+ getValue: function() {
+ return {
+ proxy: this.proxy.getValue(),
+ };
+ },
+
+ setValue: function(value) {
+ for (var proxy in value) {
+ this[proxy].setValue(value[proxy]);
+ }
+ },
+
+ onProxyChange: function(field, newValue, oldValue) {
+ var newValues = this.getValue();
+ var oldValues = Ext.apply({}, newValues);
+ oldValues[field.getName()] = oldValue;
+
+ this.fireEvent('change', this, newValues, oldValues);
+ },
+});
+/**
+ * Deluge.preferences.QueuePage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Queue
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Queue = Ext.extend(Ext.form.FormPanel, {
+ border: false,
+ title: _('Queue'),
+ header: false,
+ layout: 'form',
+
+ initComponent: function() {
+ Deluge.preferences.Queue.superclass.initComponent.call(this);
+
+ var om = deluge.preferences.getOptionsManager();
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('New Torrents'),
+ style: 'padding-top: 5px; margin-bottom: 0px;',
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ });
+ om.bind(
+ 'queue_new_to_top',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ height: 22,
+ boxLabel: _('Queue to top'),
+ name: 'queue_new_to_top',
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Active Torrents'),
+ autoHeight: true,
+ labelWidth: 150,
+ defaultType: 'spinnerfield',
+ style: 'padding-top: 5px; margin-bottom: 0px',
+ });
+ om.bind(
+ 'max_active_limit',
+ fieldset.add({
+ fieldLabel: _('Total:'),
+ labelSeparator: '',
+ name: 'max_active_limit',
+ value: 8,
+ width: 80,
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 99999,
+ })
+ );
+ om.bind(
+ 'max_active_downloading',
+ fieldset.add({
+ fieldLabel: _('Downloading:'),
+ labelSeparator: '',
+ name: 'max_active_downloading',
+ value: 3,
+ width: 80,
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 99999,
+ })
+ );
+ om.bind(
+ 'max_active_seeding',
+ fieldset.add({
+ fieldLabel: _('Seeding:'),
+ labelSeparator: '',
+ name: 'max_active_seeding',
+ value: 5,
+ width: 80,
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 99999,
+ })
+ );
+ om.bind(
+ 'dont_count_slow_torrents',
+ fieldset.add({
+ xtype: 'checkbox',
+ name: 'dont_count_slow_torrents',
+ height: 22,
+ hideLabel: true,
+ boxLabel: _('Ignore slow torrents'),
+ })
+ );
+ om.bind(
+ 'auto_manage_prefer_seeds',
+ fieldset.add({
+ xtype: 'checkbox',
+ name: 'auto_manage_prefer_seeds',
+ hideLabel: true,
+ boxLabel: _('Prefer seeding torrents'),
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Seeding Rotation'),
+ autoHeight: true,
+ labelWidth: 150,
+ defaultType: 'spinnerfield',
+ style: 'padding-top: 5px; margin-bottom: 0px',
+ });
+ om.bind(
+ 'share_ratio_limit',
+ fieldset.add({
+ fieldLabel: _('Share Ratio:'),
+ labelSeparator: '',
+ name: 'share_ratio_limit',
+ value: 8,
+ width: 80,
+ incrementValue: 0.1,
+ minValue: -1,
+ maxValue: 99999,
+ alternateIncrementValue: 1,
+ decimalPrecision: 2,
+ })
+ );
+ om.bind(
+ 'seed_time_ratio_limit',
+ fieldset.add({
+ fieldLabel: _('Time Ratio:'),
+ labelSeparator: '',
+ name: 'seed_time_ratio_limit',
+ value: 3,
+ width: 80,
+ incrementValue: 0.1,
+ minValue: -1,
+ maxValue: 99999,
+ alternateIncrementValue: 1,
+ decimalPrecision: 2,
+ })
+ );
+ om.bind(
+ 'seed_time_limit',
+ fieldset.add({
+ fieldLabel: _('Time (m):'),
+ labelSeparator: '',
+ name: 'seed_time_limit',
+ value: 5,
+ width: 80,
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 99999,
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ autoHeight: true,
+ style: 'padding-top: 5px; margin-bottom: 0px',
+ title: _('Share Ratio Reached'),
+
+ layout: 'table',
+ layoutConfig: { columns: 2 },
+ labelWidth: 0,
+ defaultType: 'checkbox',
+
+ defaults: {
+ fieldLabel: '',
+ labelSeparator: '',
+ },
+ });
+ this.stopAtRatio = fieldset.add({
+ name: 'stop_seed_at_ratio',
+ boxLabel: _('Share Ratio:'),
+ });
+ this.stopAtRatio.on('check', this.onStopRatioCheck, this);
+ om.bind('stop_seed_at_ratio', this.stopAtRatio);
+
+ this.stopRatio = fieldset.add({
+ xtype: 'spinnerfield',
+ name: 'stop_seed_ratio',
+ ctCls: 'x-deluge-indent-checkbox',
+ disabled: true,
+ value: '2.0',
+ width: 60,
+ incrementValue: 0.1,
+ minValue: -1,
+ maxValue: 99999,
+ alternateIncrementValue: 1,
+ decimalPrecision: 2,
+ });
+ om.bind('stop_seed_ratio', this.stopRatio);
+
+ this.removeAtRatio = fieldset.add({
+ xtype: 'radiogroup',
+ columns: 1,
+ colspan: 2,
+ disabled: true,
+ style: 'margin-left: 10px',
+ items: [
+ {
+ boxLabel: _('Pause torrent'),
+ name: 'at_ratio',
+ inputValue: false,
+ checked: true,
+ },
+ {
+ boxLabel: _('Remove torrent'),
+ name: 'at_ratio',
+ inputValue: true,
+ },
+ ],
+ });
+ om.bind('remove_seed_at_ratio', this.removeAtRatio);
+ },
+
+ onStopRatioCheck: function(e, checked) {
+ this.stopRatio.setDisabled(!checked);
+ this.removeAtRatio.setDisabled(!checked);
+ },
+});
+/**
+ * Deluge.StatusbarMenu.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+/**
+ * Menu that handles setting the statusbar limits correctly.
+ * @class Deluge.StatusbarMenu
+ * @extends Ext.menu.Menu
+ */
+Deluge.StatusbarMenu = Ext.extend(Ext.menu.Menu, {
+ initComponent: function() {
+ Deluge.StatusbarMenu.superclass.initComponent.call(this);
+ this.otherWin = new Deluge.OtherLimitWindow(
+ this.initialConfig.otherWin || {}
+ );
+
+ this.items.each(function(item) {
+ if (item.getXType() != 'menucheckitem') return;
+ if (item.value == 'other') {
+ item.on('click', this.onOtherClicked, this);
+ } else {
+ item.on('checkchange', this.onLimitChanged, this);
+ }
+ }, this);
+ },
+
+ setValue: function(value) {
+ var beenSet = false;
+ // set the new value
+ this.value = value = value == 0 ? -1 : value;
+
+ var other = null;
+ // uncheck all items
+ this.items.each(function(item) {
+ if (item.setChecked) {
+ item.suspendEvents();
+ if (item.value == value) {
+ item.setChecked(true);
+ beenSet = true;
+ } else {
+ item.setChecked(false);
+ }
+ item.resumeEvents();
+ }
+
+ if (item.value == 'other') other = item;
+ });
+
+ if (beenSet) return;
+
+ other.suspendEvents();
+ other.setChecked(true);
+ other.resumeEvents();
+ },
+
+ onLimitChanged: function(item, checked) {
+ if (!checked || item.value == 'other') return; // We do not care about unchecked or other.
+ var config = {};
+ config[item.group] = item.value;
+ deluge.client.core.set_config(config, {
+ success: function() {
+ deluge.ui.update();
+ },
+ });
+ },
+
+ onOtherClicked: function(item, e) {
+ this.otherWin.group = item.group;
+ this.otherWin.setValue(this.value);
+ this.otherWin.show();
+ },
+});
+/**
+ * Deluge.OptionsManager.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge');
+
+/**
+ * @class Deluge.OptionsManager
+ * @extends Ext.util.Observable
+ * A class that can be used to manage options throughout the ui.
+ * @constructor
+ * Creates a new OptionsManager
+ * @param {Object} config Configuration options
+ */
+Deluge.OptionsManager = Ext.extend(Ext.util.Observable, {
+ constructor: function(config) {
+ config = config || {};
+ this.binds = {};
+ this.changed = {};
+ this.options = (config && config['options']) || {};
+ this.focused = null;
+
+ this.addEvents({
+ /**
+ * @event add
+ * Fires when an option is added
+ */
+ add: true,
+
+ /**
+ * @event changed
+ * Fires when an option is changed
+ * @param {String} option The changed option
+ * @param {Mixed} value The options new value
+ * @param {Mixed} oldValue The options old value
+ */
+ changed: true,
+
+ /**
+ * @event reset
+ * Fires when the options are reset
+ */
+ reset: true,
+ });
+ this.on('changed', this.onChange, this);
+
+ Deluge.OptionsManager.superclass.constructor.call(this);
+ },
+
+ /**
+ * Add a set of default options and values to the options manager
+ * @param {Object} options The default options.
+ */
+ addOptions: function(options) {
+ this.options = Ext.applyIf(this.options, options);
+ },
+
+ /**
+ * Binds a form field to the specified option.
+ * @param {String} option
+ * @param {Ext.form.Field} field
+ */
+ bind: function(option, field) {
+ this.binds[option] = this.binds[option] || [];
+ this.binds[option].push(field);
+ field._doption = option;
+
+ field.on('focus', this.onFieldFocus, this);
+ field.on('blur', this.onFieldBlur, this);
+ field.on('change', this.onFieldChange, this);
+ field.on('check', this.onFieldChange, this);
+ field.on('spin', this.onFieldChange, this);
+ return field;
+ },
+
+ /**
+ * Changes all the changed values to be the default values
+ */
+ commit: function() {
+ this.options = Ext.apply(this.options, this.changed);
+ this.reset();
+ },
+
+ /**
+ * Converts the value so it matches the originals type
+ * @param {Mixed} oldValue The original value
+ * @param {Mixed} value The new value to convert
+ */
+ convertValueType: function(oldValue, value) {
+ if (Ext.type(oldValue) != Ext.type(value)) {
+ switch (Ext.type(oldValue)) {
+ case 'string':
+ value = String(value);
+ break;
+ case 'number':
+ value = Number(value);
+ break;
+ case 'boolean':
+ if (Ext.type(value) == 'string') {
+ value = value.toLowerCase();
+ value =
+ value == 'true' || value == '1' || value == 'on'
+ ? true
+ : false;
+ } else {
+ value = Boolean(value);
+ }
+ break;
+ }
+ }
+ return value;
+ },
+
+ /**
+ * Get the value for an option or options.
+ * @param {String} [option] A single option or an array of options to return.
+ * @returns {Object} the options value.
+ */
+ get: function() {
+ if (arguments.length == 1) {
+ var option = arguments[0];
+ return this.isDirty(option)
+ ? this.changed[option]
+ : this.options[option];
+ } else {
+ var options = {};
+ Ext.each(
+ arguments,
+ function(option) {
+ if (!this.has(option)) return;
+ options[option] = this.isDirty(option)
+ ? this.changed[option]
+ : this.options[option];
+ },
+ this
+ );
+ return options;
+ }
+ },
+
+ /**
+ * Get the default value for an option or options.
+ * @param {String|Array} [option] A single option or an array of options to return.
+ * @returns {Object} the value of the option
+ */
+ getDefault: function(option) {
+ return this.options[option];
+ },
+
+ /**
+ * Returns the dirty (changed) values.
+ * @returns {Object} the changed options
+ */
+ getDirty: function() {
+ return this.changed;
+ },
+
+ /**
+ * @param {String} [option] The option to check
+ * @returns {Boolean} true if the option has been changed from the default.
+ */
+ isDirty: function(option) {
+ return !Ext.isEmpty(this.changed[option]);
+ },
+
+ /**
+ * Check to see if an option exists in the options manager
+ * @param {String} option
+ * @returns {Boolean} true if the option exists, else false.
+ */
+ has: function(option) {
+ return this.options[option];
+ },
+
+ /**
+ * Reset the options back to the default values.
+ */
+ reset: function() {
+ this.changed = {};
+ },
+
+ /**
+ * Sets the value of specified option(s) for the passed in id.
+ * @param {String} option
+ * @param {Object} value The value for the option
+ */
+ set: function(option, value) {
+ if (option === undefined) {
+ return;
+ } else if (typeof option == 'object') {
+ var options = option;
+ this.options = Ext.apply(this.options, options);
+ for (var option in options) {
+ this.onChange(option, options[option]);
+ }
+ } else {
+ this.options[option] = value;
+ this.onChange(option, value);
+ }
+ },
+
+ /**
+ * Update the value for the specified option and id.
+ * @param {String/Object} option or options to update
+ * @param {Object} [value];
+ */
+ update: function(option, value) {
+ if (option === undefined) {
+ return;
+ } else if (value === undefined) {
+ for (var key in option) {
+ this.update(key, option[key]);
+ }
+ } else {
+ var defaultValue = this.getDefault(option);
+ value = this.convertValueType(defaultValue, value);
+
+ var oldValue = this.get(option);
+ if (oldValue == value) return;
+
+ if (defaultValue == value) {
+ if (this.isDirty(option)) delete this.changed[option];
+ this.fireEvent('changed', option, value, oldValue);
+ return;
+ }
+
+ this.changed[option] = value;
+ this.fireEvent('changed', option, value, oldValue);
+ }
+ },
+
+ /**
+ * Lets the option manager know when a field is blurred so if a value
+ * so value changing operations can continue on that field.
+ */
+ onFieldBlur: function(field, event) {
+ if (this.focused == field) {
+ this.focused = null;
+ }
+ },
+
+ /**
+ * Stops a form fields value from being blocked by the change functions
+ * @param {Ext.form.Field} field
+ * @private
+ */
+ onFieldChange: function(field, event) {
+ if (field.field) field = field.field; // fix for spinners
+ this.update(field._doption, field.getValue());
+ },
+
+ /**
+ * Lets the option manager know when a field is focused so if a value changing
+ * operation is performed it will not change the value of the field.
+ */
+ onFieldFocus: function(field, event) {
+ this.focused = field;
+ },
+
+ onChange: function(option, newValue, oldValue) {
+ // If we don't have a bind there's nothing to do.
+ if (Ext.isEmpty(this.binds[option])) return;
+ Ext.each(
+ this.binds[option],
+ function(bind) {
+ // The field is currently focused so we do not want to change it.
+ if (bind == this.focused) return;
+ // Set the form field to the new value.
+ bind.setValue(newValue);
+ },
+ this
+ );
+ },
+});
+/**
+ * Deluge.AboutWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Ext.namespace('Deluge.about');
+
+/**
+ * @class Deluge.about.AboutWindow
+ * @extends Ext.Window
+ */
+Deluge.about.AboutWindow = Ext.extend(Ext.Window, {
+ id: 'AboutWindow',
+ title: _('About Deluge'),
+ height: 330,
+ width: 270,
+ iconCls: 'x-deluge-main-panel',
+ resizable: false,
+ plain: true,
+ layout: {
+ type: 'vbox',
+ align: 'center',
+ },
+ buttonAlign: 'center',
+
+ initComponent: function() {
+ Deluge.about.AboutWindow.superclass.initComponent.call(this);
+ this.addEvents({
+ build_ready: true,
+ });
+
+ var self = this;
+ var libtorrent = function() {
+ deluge.client.core.get_libtorrent_version({
+ success: function(lt_version) {
+ comment += '<br/>' + _('libtorrent:') + ' ' + lt_version;
+ Ext.getCmp('about_comment').setText(comment, false);
+ self.fireEvent('build_ready');
+ },
+ });
+ };
+
+ var client_version = deluge.version;
+
+ var comment =
+ _(
+ 'A peer-to-peer file sharing program\nutilizing the BitTorrent protocol.'
+ ).replace('\n', '<br/>') +
+ '<br/><br/>' +
+ _('Client:') +
+ ' ' +
+ client_version +
+ '<br/>';
+ deluge.client.web.connected({
+ success: function(connected) {
+ if (connected) {
+ deluge.client.daemon.get_version({
+ success: function(server_version) {
+ comment +=
+ _('Server:') + ' ' + server_version + '<br/>';
+ libtorrent();
+ },
+ });
+ } else {
+ this.fireEvent('build_ready');
+ }
+ },
+ failure: function() {
+ this.fireEvent('build_ready');
+ },
+ scope: this,
+ });
+
+ this.add([
+ {
+ xtype: 'box',
+ style: 'padding-top: 5px',
+ height: 80,
+ width: 240,
+ cls: 'x-deluge-logo',
+ hideLabel: true,
+ },
+ {
+ xtype: 'label',
+ style: 'padding-top: 10px; font-weight: bold; font-size: 16px;',
+ text: _('Deluge') + ' ' + client_version,
+ },
+ {
+ xtype: 'label',
+ id: 'about_comment',
+ style: 'padding-top: 10px; text-align:center; font-size: 12px;',
+ html: comment,
+ },
+ {
+ xtype: 'label',
+ style: 'padding-top: 10px; font-size: 10px;',
+ text: _('Copyright 2007-2018 Deluge Team'),
+ },
+ {
+ xtype: 'label',
+ style: 'padding-top: 5px; font-size: 12px;',
+ html:
+ '<a href="https://deluge-torrent.org" target="_blank">deluge-torrent.org</a>',
+ },
+ ]);
+ this.addButton(_('Close'), this.onCloseClick, this);
+ },
+
+ show: function() {
+ this.on('build_ready', function() {
+ Deluge.about.AboutWindow.superclass.show.call(this);
+ });
+ },
+
+ onCloseClick: function() {
+ this.close();
+ },
+});
+
+Ext.namespace('Deluge');
+
+Deluge.About = function() {
+ new Deluge.about.AboutWindow().show();
+};
+/**
+ * Deluge.AddConnectionWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+/**
+ * @class Deluge.AddConnectionWindow
+ * @extends Ext.Window
+ */
+Deluge.AddConnectionWindow = Ext.extend(Ext.Window, {
+ title: _('Add Connection'),
+ iconCls: 'x-deluge-add-window-icon',
+
+ layout: 'fit',
+ width: 300,
+ height: 195,
+ constrainHeader: true,
+ bodyStyle: 'padding: 10px 5px;',
+ closeAction: 'hide',
+
+ initComponent: function() {
+ Deluge.AddConnectionWindow.superclass.initComponent.call(this);
+
+ this.addEvents('hostadded');
+
+ this.addButton(_('Close'), this.hide, this);
+ this.addButton(_('Add'), this.onAddClick, this);
+
+ this.on('hide', this.onHide, this);
+
+ this.form = this.add({
+ xtype: 'form',
+ defaultType: 'textfield',
+ baseCls: 'x-plain',
+ labelWidth: 60,
+ items: [
+ {
+ fieldLabel: _('Host:'),
+ labelSeparator: '',
+ name: 'host',
+ anchor: '75%',
+ value: '',
+ },
+ {
+ xtype: 'spinnerfield',
+ fieldLabel: _('Port:'),
+ labelSeparator: '',
+ name: 'port',
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 65535,
+ },
+ value: '58846',
+ anchor: '40%',
+ },
+ {
+ fieldLabel: _('Username:'),
+ labelSeparator: '',
+ name: 'username',
+ anchor: '75%',
+ value: '',
+ },
+ {
+ fieldLabel: _('Password:'),
+ labelSeparator: '',
+ anchor: '75%',
+ name: 'password',
+ inputType: 'password',
+ value: '',
+ },
+ ],
+ });
+ },
+
+ onAddClick: function() {
+ var values = this.form.getForm().getValues();
+ deluge.client.web.add_host(
+ values.host,
+ Number(values.port),
+ values.username,
+ values.password,
+ {
+ success: function(result) {
+ if (!result[0]) {
+ Ext.MessageBox.show({
+ title: _('Error'),
+ msg: String.format(
+ _('Unable to add host: {0}'),
+ result[1]
+ ),
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ icon: Ext.MessageBox.ERROR,
+ iconCls: 'x-deluge-icon-error',
+ });
+ } else {
+ this.fireEvent('hostadded');
+ }
+ this.hide();
+ },
+ scope: this,
+ }
+ );
+ },
+
+ onHide: function() {
+ this.form.getForm().reset();
+ },
+});
+/**
+ * Deluge.AddTrackerWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+// Custom VType validator for tracker urls
+var trackerUrlTest = /(((^https?)|(^udp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
+Ext.apply(Ext.form.VTypes, {
+ trackerUrl: function(val, field) {
+ return trackerUrlTest.test(val);
+ },
+ trackerUrlText: 'Not a valid tracker url',
+});
+
+/**
+ * @class Deluge.AddTrackerWindow
+ * @extends Ext.Window
+ */
+Deluge.AddTrackerWindow = Ext.extend(Ext.Window, {
+ title: _('Add Tracker'),
+ layout: 'fit',
+ width: 375,
+ height: 150,
+ plain: true,
+ closable: true,
+ resizable: false,
+ constrainHeader: true,
+ bodyStyle: 'padding: 5px',
+ buttonAlign: 'right',
+ closeAction: 'hide',
+ iconCls: 'x-deluge-edit-trackers',
+
+ initComponent: function() {
+ Deluge.AddTrackerWindow.superclass.initComponent.call(this);
+
+ this.addButton(_('Cancel'), this.onCancelClick, this);
+ this.addButton(_('Add'), this.onAddClick, this);
+ this.addEvents('add');
+
+ this.form = this.add({
+ xtype: 'form',
+ defaultType: 'textarea',
+ baseCls: 'x-plain',
+ labelWidth: 55,
+ items: [
+ {
+ fieldLabel: _('Trackers:'),
+ labelSeparator: '',
+ name: 'trackers',
+ anchor: '100%',
+ },
+ ],
+ });
+ },
+
+ onAddClick: function() {
+ var trackers = this.form
+ .getForm()
+ .findField('trackers')
+ .getValue();
+ trackers = trackers.split('\n');
+
+ var cleaned = [];
+ Ext.each(
+ trackers,
+ function(tracker) {
+ if (Ext.form.VTypes.trackerUrl(tracker)) {
+ cleaned.push(tracker);
+ }
+ },
+ this
+ );
+ this.fireEvent('add', cleaned);
+ this.hide();
+ this.form
+ .getForm()
+ .findField('trackers')
+ .setValue('');
+ },
+
+ onCancelClick: function() {
+ this.form
+ .getForm()
+ .findField('trackers')
+ .setValue('');
+ this.hide();
+ },
+});
+/**
+ * Deluge.Client.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Ext.ux.util');
+
+/**
+ * A class that connects to a json-rpc resource and adds the available
+ * methods as functions to the class instance.
+ * @class Ext.ux.util.RpcClient
+ * @namespace Ext.ux.util
+ */
+Ext.ux.util.RpcClient = Ext.extend(Ext.util.Observable, {
+ _components: [],
+
+ _methods: [],
+
+ _requests: {},
+
+ _url: null,
+
+ _optionKeys: ['scope', 'success', 'failure'],
+
+ /**
+ * @event connected
+ * Fires when the client has retrieved the list of methods from the server.
+ * @param {Ext.ux.util.RpcClient} this
+ */
+ constructor: function(config) {
+ Ext.ux.util.RpcClient.superclass.constructor.call(this, config);
+ this._url = config.url || null;
+ this._id = 0;
+
+ this.addEvents(
+ // raw events
+ 'connected',
+ 'error'
+ );
+ this.reloadMethods();
+ },
+
+ reloadMethods: function() {
+ this._execute('system.listMethods', {
+ success: this._setMethods,
+ scope: this,
+ });
+ },
+
+ _execute: function(method, options) {
+ options = options || {};
+ options.params = options.params || [];
+ options.id = this._id;
+
+ var request = Ext.encode({
+ method: method,
+ params: options.params,
+ id: options.id,
+ });
+ this._id++;
+
+ return Ext.Ajax.request({
+ url: this._url,
+ method: 'POST',
+ success: this._onSuccess,
+ failure: this._onFailure,
+ scope: this,
+ jsonData: request,
+ options: options,
+ });
+ },
+
+ _onFailure: function(response, requestOptions) {
+ var options = requestOptions.options;
+ errorObj = {
+ id: options.id,
+ result: null,
+ error: {
+ msg: 'HTTP: ' + response.status + ' ' + response.statusText,
+ code: 255,
+ },
+ };
+
+ this.fireEvent('error', errorObj, response, requestOptions);
+
+ if (Ext.type(options.failure) != 'function') return;
+ if (options.scope) {
+ options.failure.call(
+ options.scope,
+ errorObj,
+ response,
+ requestOptions
+ );
+ } else {
+ options.failure(errorObj, response, requestOptions);
+ }
+ },
+
+ _onSuccess: function(response, requestOptions) {
+ var responseObj = Ext.decode(response.responseText);
+ var options = requestOptions.options;
+ if (responseObj.error) {
+ this.fireEvent('error', responseObj, response, requestOptions);
+
+ if (Ext.type(options.failure) != 'function') return;
+ if (options.scope) {
+ options.failure.call(
+ options.scope,
+ responseObj,
+ response,
+ requestOptions
+ );
+ } else {
+ options.failure(responseObj, response, requestOptions);
+ }
+ } else {
+ if (Ext.type(options.success) != 'function') return;
+ if (options.scope) {
+ options.success.call(
+ options.scope,
+ responseObj.result,
+ responseObj,
+ response,
+ requestOptions
+ );
+ } else {
+ options.success(
+ responseObj.result,
+ responseObj,
+ response,
+ requestOptions
+ );
+ }
+ }
+ },
+
+ _parseArgs: function(args) {
+ var params = [];
+ Ext.each(args, function(arg) {
+ params.push(arg);
+ });
+
+ var options = params[params.length - 1];
+ if (Ext.type(options) == 'object') {
+ var keys = Ext.keys(options),
+ isOption = false;
+
+ Ext.each(this._optionKeys, function(key) {
+ if (keys.indexOf(key) > -1) isOption = true;
+ });
+
+ if (isOption) {
+ params.remove(options);
+ } else {
+ options = {};
+ }
+ } else {
+ options = {};
+ }
+ options.params = params;
+ return options;
+ },
+
+ _setMethods: function(methods) {
+ var components = {},
+ self = this;
+
+ Ext.each(methods, function(method) {
+ var parts = method.split('.');
+ var component = components[parts[0]] || {};
+
+ var fn = function() {
+ var options = self._parseArgs(arguments);
+ return self._execute(method, options);
+ };
+ component[parts[1]] = fn;
+ components[parts[0]] = component;
+ });
+
+ for (var name in components) {
+ self[name] = components[name];
+ }
+ Ext.each(
+ this._components,
+ function(component) {
+ if (!component in components) {
+ delete this[component];
+ }
+ },
+ this
+ );
+ this._components = Ext.keys(components);
+ this.fireEvent('connected', this);
+ },
+});
+/**
+ * Deluge.ConnectionManager.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Deluge.ConnectionManager = Ext.extend(Ext.Window, {
+ layout: 'fit',
+ width: 300,
+ height: 220,
+ bodyStyle: 'padding: 10px 5px;',
+ buttonAlign: 'right',
+ closeAction: 'hide',
+ closable: true,
+ plain: true,
+ constrainHeader: true,
+ title: _('Connection Manager'),
+ iconCls: 'x-deluge-connect-window-icon',
+
+ initComponent: function() {
+ Deluge.ConnectionManager.superclass.initComponent.call(this);
+ this.on('hide', this.onHide, this);
+ this.on('show', this.onShow, this);
+
+ deluge.events.on('login', this.onLogin, this);
+ deluge.events.on('logout', this.onLogout, this);
+
+ this.addButton(_('Close'), this.onClose, this);
+ this.addButton(_('Connect'), this.onConnect, this);
+
+ this.list = new Ext.list.ListView({
+ store: new Ext.data.ArrayStore({
+ fields: [
+ { name: 'status', mapping: 4 },
+ { name: 'host', mapping: 1 },
+ { name: 'port', mapping: 2 },
+ { name: 'user', mapping: 3 },
+ { name: 'version', mapping: 5 },
+ ],
+ id: 0,
+ }),
+ columns: [
+ {
+ header: _('Status'),
+ width: 0.24,
+ sortable: true,
+ tpl: new Ext.XTemplate(
+ '<tpl if="status == \'Online\'">',
+ _('Online'),
+ '</tpl>',
+ '<tpl if="status == \'Offline\'">',
+ _('Offline'),
+ '</tpl>',
+ '<tpl if="status == \'Connected\'">',
+ _('Connected'),
+ '</tpl>'
+ ),
+ dataIndex: 'status',
+ },
+ {
+ id: 'host',
+ header: _('Host'),
+ width: 0.51,
+ sortable: true,
+ tpl: '{user}@{host}:{port}',
+ dataIndex: 'host',
+ },
+ {
+ header: _('Version'),
+ width: 0.25,
+ sortable: true,
+ tpl: '<tpl if="version">{version}</tpl>',
+ dataIndex: 'version',
+ },
+ ],
+ singleSelect: true,
+ listeners: {
+ selectionchange: { fn: this.onSelectionChanged, scope: this },
+ },
+ });
+
+ this.panel = this.add({
+ autoScroll: true,
+ items: [this.list],
+ bbar: new Ext.Toolbar({
+ buttons: [
+ {
+ id: 'cm-add',
+ cls: 'x-btn-text-icon',
+ text: _('Add'),
+ iconCls: 'icon-add',
+ handler: this.onAddClick,
+ scope: this,
+ },
+ {
+ id: 'cm-edit',
+ cls: 'x-btn-text-icon',
+ text: _('Edit'),
+ iconCls: 'icon-edit',
+ handler: this.onEditClick,
+ scope: this,
+ },
+ {
+ id: 'cm-remove',
+ cls: 'x-btn-text-icon',
+ text: _('Remove'),
+ iconCls: 'icon-remove',
+ handler: this.onRemoveClick,
+ disabled: true,
+ scope: this,
+ },
+ '->',
+ {
+ id: 'cm-stop',
+ cls: 'x-btn-text-icon',
+ text: _('Stop Daemon'),
+ iconCls: 'icon-error',
+ handler: this.onStopClick,
+ disabled: true,
+ scope: this,
+ },
+ ],
+ }),
+ });
+ this.update = this.update.createDelegate(this);
+ },
+
+ /**
+ * Check to see if the the web interface is currently connected
+ * to a Deluge Daemon and show the Connection Manager if not.
+ */
+ checkConnected: function() {
+ deluge.client.web.connected({
+ success: function(connected) {
+ if (connected) {
+ deluge.events.fire('connect');
+ } else {
+ this.show();
+ }
+ },
+ scope: this,
+ });
+ },
+
+ disconnect: function(show) {
+ deluge.events.fire('disconnect');
+ if (show) {
+ if (this.isVisible()) return;
+ this.show();
+ }
+ },
+
+ loadHosts: function() {
+ deluge.client.web.get_hosts({
+ success: this.onGetHosts,
+ scope: this,
+ });
+ },
+
+ update: function() {
+ this.list.getStore().each(function(r) {
+ deluge.client.web.get_host_status(r.id, {
+ success: this.onGetHostStatus,
+ scope: this,
+ });
+ }, this);
+ },
+
+ /**
+ * Updates the buttons in the Connection Manager UI according to the
+ * passed in records host state.
+ * @param {Ext.data.Record} record The hosts record to update the UI for
+ */
+ updateButtons: function(record) {
+ var button = this.buttons[1],
+ status = record.get('status');
+
+ // Update the Connect/Disconnect button
+ button.enable();
+ if (status.toLowerCase() == 'connected') {
+ button.setText(_('Disconnect'));
+ } else {
+ button.setText(_('Connect'));
+ if (status.toLowerCase() != 'online') button.disable();
+ }
+
+ // Update the Stop/Start Daemon button
+ if (
+ status.toLowerCase() == 'connected' ||
+ status.toLowerCase() == 'online'
+ ) {
+ this.stopHostButton.enable();
+ this.stopHostButton.setText(_('Stop Daemon'));
+ } else {
+ if (
+ record.get('host') == '127.0.0.1' ||
+ record.get('host') == 'localhost'
+ ) {
+ this.stopHostButton.enable();
+ this.stopHostButton.setText(_('Start Daemon'));
+ } else {
+ this.stopHostButton.disable();
+ }
+ }
+ },
+
+ // private
+ onAddClick: function(button, e) {
+ if (!this.addWindow) {
+ this.addWindow = new Deluge.AddConnectionWindow();
+ this.addWindow.on('hostadded', this.onHostChange, this);
+ }
+ this.addWindow.show();
+ },
+
+ // private
+ onEditClick: function(button, e) {
+ var connection = this.list.getSelectedRecords()[0];
+ if (!connection) return;
+
+ if (!this.editWindow) {
+ this.editWindow = new Deluge.EditConnectionWindow();
+ this.editWindow.on('hostedited', this.onHostChange, this);
+ }
+ this.editWindow.show(connection);
+ },
+
+ // private
+ onHostChange: function() {
+ this.loadHosts();
+ },
+
+ // private
+ onClose: function(e) {
+ this.hide();
+ },
+
+ // private
+ onConnect: function(e) {
+ var selected = this.list.getSelectedRecords()[0];
+ if (!selected) return;
+
+ var me = this;
+ var disconnect = function() {
+ deluge.client.web.disconnect({
+ success: function(result) {
+ this.update(this);
+ deluge.events.fire('disconnect');
+ },
+ scope: me,
+ });
+ };
+
+ if (selected.get('status').toLowerCase() == 'connected') {
+ disconnect();
+ } else {
+ if (
+ this.list
+ .getStore()
+ .find('status', 'Connected', 0, false, false) > -1
+ ) {
+ disconnect();
+ }
+
+ var id = selected.id;
+ deluge.client.web.connect(id, {
+ success: function(methods) {
+ deluge.client.reloadMethods();
+ deluge.client.on(
+ 'connected',
+ function(e) {
+ deluge.events.fire('connect');
+ },
+ this,
+ { single: true }
+ );
+ },
+ });
+ this.hide();
+ }
+ },
+
+ // private
+ onGetHosts: function(hosts) {
+ this.list.getStore().loadData(hosts);
+ Ext.each(
+ hosts,
+ function(host) {
+ deluge.client.web.get_host_status(host[0], {
+ success: this.onGetHostStatus,
+ scope: this,
+ });
+ },
+ this
+ );
+ },
+
+ // private
+ onGetHostStatus: function(host) {
+ var record = this.list.getStore().getById(host[0]);
+ record.set('status', host[1]);
+ record.set('version', host[2]);
+ record.commit();
+ var selected = this.list.getSelectedRecords()[0];
+ if (!selected) return;
+ if (selected == record) this.updateButtons(record);
+ },
+
+ // private
+ onHide: function() {
+ if (this.running) window.clearInterval(this.running);
+ },
+
+ // private
+ onLogin: function() {
+ if (deluge.config.first_login) {
+ Ext.MessageBox.confirm(
+ _('Change Default Password'),
+ _(
+ 'We recommend changing the default password.<br><br>Would you like to change it now?'
+ ),
+ function(res) {
+ this.checkConnected();
+ if (res == 'yes') {
+ deluge.preferences.show();
+ deluge.preferences.selectPage('Interface');
+ }
+ deluge.client.web.set_config({ first_login: false });
+ },
+ this
+ );
+ } else {
+ this.checkConnected();
+ }
+ },
+
+ // private
+ onLogout: function() {
+ this.disconnect();
+ if (!this.hidden && this.rendered) {
+ this.hide();
+ }
+ },
+
+ // private
+ onRemoveClick: function(button) {
+ var connection = this.list.getSelectedRecords()[0];
+ if (!connection) return;
+
+ deluge.client.web.remove_host(connection.id, {
+ success: function(result) {
+ if (!result) {
+ Ext.MessageBox.show({
+ title: _('Error'),
+ msg: result[1],
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ icon: Ext.MessageBox.ERROR,
+ iconCls: 'x-deluge-icon-error',
+ });
+ } else {
+ this.list.getStore().remove(connection);
+ }
+ },
+ scope: this,
+ });
+ },
+
+ // private
+ onSelectionChanged: function(list, selections) {
+ if (selections[0]) {
+ this.editHostButton.enable();
+ this.removeHostButton.enable();
+ this.stopHostButton.enable();
+ this.stopHostButton.setText(_('Stop Daemon'));
+ this.updateButtons(this.list.getRecord(selections[0]));
+ } else {
+ this.editHostButton.disable();
+ this.removeHostButton.disable();
+ this.stopHostButton.disable();
+ }
+ },
+
+ // FIXME: Find out why this is being fired twice
+ // private
+ onShow: function() {
+ if (!this.addHostButton) {
+ var bbar = this.panel.getBottomToolbar();
+ this.addHostButton = bbar.items.get('cm-add');
+ this.editHostButton = bbar.items.get('cm-edit');
+ this.removeHostButton = bbar.items.get('cm-remove');
+ this.stopHostButton = bbar.items.get('cm-stop');
+ }
+ this.loadHosts();
+ if (this.running) return;
+ this.running = window.setInterval(this.update, 2000, this);
+ },
+
+ // private
+ onStopClick: function(button, e) {
+ var connection = this.list.getSelectedRecords()[0];
+ if (!connection) return;
+
+ if (connection.get('status') == 'Offline') {
+ // This means we need to start the daemon
+ deluge.client.web.start_daemon(connection.get('port'));
+ } else {
+ // This means we need to stop the daemon
+ deluge.client.web.stop_daemon(connection.id, {
+ success: function(result) {
+ if (!result[0]) {
+ Ext.MessageBox.show({
+ title: _('Error'),
+ msg: result[1],
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ icon: Ext.MessageBox.ERROR,
+ iconCls: 'x-deluge-icon-error',
+ });
+ }
+ },
+ });
+ }
+ },
+});
+/**
+ * Deluge.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+// Setup the state manager
+Ext.state.Manager.setProvider(
+ new Ext.state.CookieProvider({
+ /**
+ * By default, cookies will expire after 7 days. Provide
+ * an expiry date 10 years in the future to approximate
+ * a cookie that does not expire.
+ */
+ expires: new Date(
+ new Date().getTime() + 1000 * 60 * 60 * 24 * 365 * 10
+ ),
+ })
+);
+
+// Add some additional functions to ext and setup some of the
+// configurable parameters
+Ext.apply(Ext, {
+ escapeHTML: function(text) {
+ text = String(text)
+ .replace('<', '&lt;')
+ .replace('>', '&gt;');
+ return text.replace('&', '&amp;');
+ },
+
+ isObjectEmpty: function(obj) {
+ for (var i in obj) {
+ return false;
+ }
+ return true;
+ },
+
+ areObjectsEqual: function(obj1, obj2) {
+ var equal = true;
+ if (!obj1 || !obj2) return false;
+ for (var i in obj1) {
+ if (obj1[i] != obj2[i]) {
+ equal = false;
+ }
+ }
+ return equal;
+ },
+
+ keys: function(obj) {
+ var keys = [];
+ for (var i in obj)
+ if (obj.hasOwnProperty(i)) {
+ keys.push(i);
+ }
+ return keys;
+ },
+
+ values: function(obj) {
+ var values = [];
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ values.push(obj[i]);
+ }
+ }
+ return values;
+ },
+
+ splat: function(obj) {
+ var type = Ext.type(obj);
+ return type ? (type != 'array' ? [obj] : obj) : [];
+ },
+});
+Ext.getKeys = Ext.keys;
+Ext.BLANK_IMAGE_URL = deluge.config.base + 'images/s.gif';
+Ext.USE_NATIVE_JSON = true;
+
+// Create the Deluge namespace
+Ext.apply(Deluge, {
+ // private
+ pluginStore: {},
+
+ // private
+ progressTpl:
+ '<div class="x-progress-wrap x-progress-renderered">' +
+ '<div class="x-progress-inner">' +
+ '<div style="width: {2}px" class="x-progress-bar">' +
+ '<div style="z-index: 99; width: {3}px" class="x-progress-text">' +
+ '<div style="width: {1}px;">{0}</div>' +
+ '</div>' +
+ '</div>' +
+ '<div class="x-progress-text x-progress-text-back">' +
+ '<div style="width: {1}px;">{0}</div>' +
+ '</div>' +
+ '</div>' +
+ '</div>',
+
+ /**
+ * A method to create a progress bar that can be used by renderers
+ * to display a bar within a grid or tree.
+ * @param {Number} progress The bars progress
+ * @param {Number} width The width of the bar
+ * @param {String} text The text to display on the bar
+ * @param {Number} modified Amount to subtract from the width allowing for fixes
+ */
+ progressBar: function(progress, width, text, modifier) {
+ modifier = Ext.value(modifier, 10);
+ var progressWidth = ((width / 100.0) * progress).toFixed(0);
+ var barWidth = progressWidth - 1;
+ var textWidth =
+ progressWidth - modifier > 0 ? progressWidth - modifier : 0;
+ return String.format(
+ Deluge.progressTpl,
+ text,
+ width,
+ barWidth,
+ textWidth
+ );
+ },
+
+ /**
+ * Constructs a new instance of the specified plugin.
+ * @param {String} name The plugin name to create
+ */
+ createPlugin: function(name) {
+ return new Deluge.pluginStore[name]();
+ },
+
+ /**
+ * Check to see if a plugin has been registered.
+ * @param {String} name The plugin name to check
+ */
+ hasPlugin: function(name) {
+ return Deluge.pluginStore[name] ? true : false;
+ },
+
+ /**
+ * Register a plugin with the Deluge interface.
+ * @param {String} name The plugin name to register
+ * @param {Plugin} plugin The plugin to register
+ */
+ registerPlugin: function(name, plugin) {
+ Deluge.pluginStore[name] = plugin;
+ },
+});
+
+// Setup a space for plugins to insert themselves
+deluge.plugins = {};
+
+// Hinting for gettext_gen.py
+// _('Skip')
+// _('Low')
+// _('Normal')
+// _('High')
+// _('Mixed')
+FILE_PRIORITY = {
+ 0: 'Skip',
+ 1: 'Low',
+ 2: 'Low',
+ 3: 'Low',
+ 4: 'Normal',
+ 5: 'High',
+ 6: 'High',
+ 7: 'High',
+ 9: 'Mixed',
+ Skip: 0,
+ Low: 1,
+ Normal: 4,
+ High: 7,
+ Mixed: 9,
+};
+
+FILE_PRIORITY_CSS = {
+ 0: 'x-no-download',
+ 1: 'x-low-download',
+ 2: 'x-low-download',
+ 3: 'x-low-download',
+ 4: 'x-normal-download',
+ 5: 'x-high-download',
+ 6: 'x-high-download',
+ 7: 'x-high-download',
+ 9: 'x-mixed-download',
+};
+/**
+ * Deluge.EditConnectionWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+/**
+ * @class Deluge.EditConnectionWindow
+ * @extends Ext.Window
+ */
+Deluge.EditConnectionWindow = Ext.extend(Ext.Window, {
+ title: _('Edit Connection'),
+ iconCls: 'x-deluge-add-window-icon',
+
+ layout: 'fit',
+ width: 300,
+ height: 195,
+ constrainHeader: true,
+ bodyStyle: 'padding: 10px 5px;',
+ closeAction: 'hide',
+
+ initComponent: function() {
+ Deluge.EditConnectionWindow.superclass.initComponent.call(this);
+
+ this.addEvents('hostedited');
+
+ this.addButton(_('Close'), this.hide, this);
+ this.addButton(_('Edit'), this.onEditClick, this);
+
+ this.on('hide', this.onHide, this);
+
+ this.form = this.add({
+ xtype: 'form',
+ defaultType: 'textfield',
+ baseCls: 'x-plain',
+ labelWidth: 60,
+ items: [
+ {
+ fieldLabel: _('Host:'),
+ labelSeparator: '',
+ name: 'host',
+ anchor: '75%',
+ value: '',
+ },
+ {
+ xtype: 'spinnerfield',
+ fieldLabel: _('Port:'),
+ labelSeparator: '',
+ name: 'port',
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 0,
+ minValue: 0,
+ maxValue: 65535,
+ },
+ anchor: '40%',
+ value: 58846,
+ },
+ {
+ fieldLabel: _('Username:'),
+ labelSeparator: '',
+ name: 'username',
+ anchor: '75%',
+ value: '',
+ },
+ {
+ fieldLabel: _('Password:'),
+ labelSeparator: '',
+ anchor: '75%',
+ name: 'password',
+ inputType: 'password',
+ value: '',
+ },
+ ],
+ });
+ },
+
+ show: function(connection) {
+ Deluge.EditConnectionWindow.superclass.show.call(this);
+
+ this.form
+ .getForm()
+ .findField('host')
+ .setValue(connection.get('host'));
+ this.form
+ .getForm()
+ .findField('port')
+ .setValue(connection.get('port'));
+ this.form
+ .getForm()
+ .findField('username')
+ .setValue(connection.get('user'));
+ this.host_id = connection.id;
+ },
+
+ onEditClick: function() {
+ var values = this.form.getForm().getValues();
+ deluge.client.web.edit_host(
+ this.host_id,
+ values.host,
+ Number(values.port),
+ values.username,
+ values.password,
+ {
+ success: function(result) {
+ if (!result) {
+ console.log(result);
+ Ext.MessageBox.show({
+ title: _('Error'),
+ msg: String.format(_('Unable to edit host')),
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ icon: Ext.MessageBox.ERROR,
+ iconCls: 'x-deluge-icon-error',
+ });
+ } else {
+ this.fireEvent('hostedited');
+ }
+ this.hide();
+ },
+ scope: this,
+ }
+ );
+ },
+
+ onHide: function() {
+ this.form.getForm().reset();
+ },
+});
+/**
+ * Deluge.EditTrackerWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+/**
+ * @class Deluge.EditTrackerWindow
+ * @extends Ext.Window
+ */
+Deluge.EditTrackerWindow = Ext.extend(Ext.Window, {
+ title: _('Edit Tracker'),
+ layout: 'fit',
+ width: 375,
+ height: 110,
+ plain: true,
+ closable: true,
+ resizable: false,
+ constrainHeader: true,
+ bodyStyle: 'padding: 5px',
+ buttonAlign: 'right',
+ closeAction: 'hide',
+ iconCls: 'x-deluge-edit-trackers',
+
+ initComponent: function() {
+ Deluge.EditTrackerWindow.superclass.initComponent.call(this);
+
+ this.addButton(_('Cancel'), this.onCancelClick, this);
+ this.addButton(_('Save'), this.onSaveClick, this);
+ this.on('hide', this.onHide, this);
+
+ this.form = this.add({
+ xtype: 'form',
+ defaultType: 'textfield',
+ baseCls: 'x-plain',
+ labelWidth: 55,
+ items: [
+ {
+ fieldLabel: _('Tracker:'),
+ labelSeparator: '',
+ name: 'tracker',
+ anchor: '100%',
+ },
+ ],
+ });
+ },
+
+ show: function(record) {
+ Deluge.EditTrackerWindow.superclass.show.call(this);
+
+ this.record = record;
+ this.form
+ .getForm()
+ .findField('tracker')
+ .setValue(record.data['url']);
+ },
+
+ onCancelClick: function() {
+ this.hide();
+ },
+
+ onHide: function() {
+ this.form
+ .getForm()
+ .findField('tracker')
+ .setValue('');
+ },
+
+ onSaveClick: function() {
+ var url = this.form
+ .getForm()
+ .findField('tracker')
+ .getValue();
+ this.record.set('url', url);
+ this.record.commit();
+ this.hide();
+ },
+});
+/**
+ * Deluge.EditTrackers.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+/**
+ * @class Deluge.EditTrackerWindow
+ * @extends Ext.Window
+ */
+Deluge.EditTrackersWindow = Ext.extend(Ext.Window, {
+ title: _('Edit Trackers'),
+ layout: 'fit',
+ width: 350,
+ height: 220,
+ plain: true,
+ closable: true,
+ resizable: true,
+ constrainHeader: true,
+
+ bodyStyle: 'padding: 5px',
+ buttonAlign: 'right',
+ closeAction: 'hide',
+ iconCls: 'x-deluge-edit-trackers',
+
+ initComponent: function() {
+ Deluge.EditTrackersWindow.superclass.initComponent.call(this);
+
+ this.addButton(_('Cancel'), this.onCancelClick, this);
+ this.addButton(_('OK'), this.onOkClick, this);
+ this.addEvents('save');
+
+ this.on('show', this.onShow, this);
+ this.on('save', this.onSave, this);
+
+ this.addWindow = new Deluge.AddTrackerWindow();
+ this.addWindow.on('add', this.onAddTrackers, this);
+ this.editWindow = new Deluge.EditTrackerWindow();
+
+ this.list = new Ext.list.ListView({
+ store: new Ext.data.JsonStore({
+ root: 'trackers',
+ fields: ['tier', 'url'],
+ }),
+ columns: [
+ {
+ header: _('Tier'),
+ width: 0.1,
+ dataIndex: 'tier',
+ },
+ {
+ header: _('Tracker'),
+ width: 0.9,
+ dataIndex: 'url',
+ },
+ ],
+ columnSort: {
+ sortClasses: ['', ''],
+ },
+ stripeRows: true,
+ singleSelect: true,
+ listeners: {
+ dblclick: { fn: this.onListNodeDblClicked, scope: this },
+ selectionchange: { fn: this.onSelect, scope: this },
+ },
+ });
+
+ this.panel = this.add({
+ items: [this.list],
+ autoScroll: true,
+ bbar: new Ext.Toolbar({
+ items: [
+ {
+ text: _('Up'),
+ iconCls: 'icon-up',
+ handler: this.onUpClick,
+ scope: this,
+ },
+ {
+ text: _('Down'),
+ iconCls: 'icon-down',
+ handler: this.onDownClick,
+ scope: this,
+ },
+ '->',
+ {
+ text: _('Add'),
+ iconCls: 'icon-add',
+ handler: this.onAddClick,
+ scope: this,
+ },
+ {
+ text: _('Edit'),
+ iconCls: 'icon-edit-trackers',
+ handler: this.onEditClick,
+ scope: this,
+ },
+ {
+ text: _('Remove'),
+ iconCls: 'icon-remove',
+ handler: this.onRemoveClick,
+ scope: this,
+ },
+ ],
+ }),
+ });
+ },
+
+ onAddClick: function() {
+ this.addWindow.show();
+ },
+
+ onAddTrackers: function(trackers) {
+ var store = this.list.getStore();
+ Ext.each(
+ trackers,
+ function(tracker) {
+ var duplicate = false,
+ heightestTier = -1;
+ store.each(function(record) {
+ if (record.get('tier') > heightestTier) {
+ heightestTier = record.get('tier');
+ }
+ if (tracker == record.get('tracker')) {
+ duplicate = true;
+ return false;
+ }
+ }, this);
+ if (duplicate) return;
+ store.add(
+ new store.recordType({
+ tier: heightestTier + 1,
+ url: tracker,
+ })
+ );
+ },
+ this
+ );
+ },
+
+ onCancelClick: function() {
+ this.hide();
+ },
+
+ onEditClick: function() {
+ var selected = this.list.getSelectedRecords()[0];
+ if (!selected) return;
+ this.editWindow.show(selected);
+ },
+
+ onHide: function() {
+ this.list.getStore().removeAll();
+ },
+
+ onListNodeDblClicked: function(list, index, node, e) {
+ this.editWindow.show(this.list.getRecord(node));
+ },
+
+ onOkClick: function() {
+ var trackers = [];
+ this.list.getStore().each(function(record) {
+ trackers.push({
+ tier: record.get('tier'),
+ url: record.get('url'),
+ });
+ }, this);
+
+ deluge.client.core.set_torrent_trackers(this.torrentId, trackers, {
+ failure: this.onSaveFail,
+ scope: this,
+ });
+
+ this.hide();
+ },
+
+ onRemoveClick: function() {
+ // Remove from the grid
+ var selected = this.list.getSelectedRecords()[0];
+ if (!selected) return;
+ this.list.getStore().remove(selected);
+ },
+
+ onRequestComplete: function(status) {
+ this.list.getStore().loadData(status);
+ this.list.getStore().sort('tier', 'ASC');
+ },
+
+ onSaveFail: function() {},
+
+ onSelect: function(list) {
+ if (list.getSelectionCount()) {
+ this.panel
+ .getBottomToolbar()
+ .items.get(4)
+ .enable();
+ }
+ },
+
+ onShow: function() {
+ this.panel
+ .getBottomToolbar()
+ .items.get(4)
+ .disable();
+ var r = deluge.torrents.getSelected();
+ this.torrentId = r.id;
+ deluge.client.core.get_torrent_status(r.id, ['trackers'], {
+ success: this.onRequestComplete,
+ scope: this,
+ });
+ },
+
+ onDownClick: function() {
+ var r = this.list.getSelectedRecords()[0];
+ if (!r) return;
+
+ r.set('tier', r.get('tier') + 1);
+ r.store.sort('tier', 'ASC');
+ r.store.commitChanges();
+
+ this.list.select(r.store.indexOf(r));
+ },
+
+ onUpClick: function() {
+ var r = this.list.getSelectedRecords()[0];
+ if (!r) return;
+
+ if (r.get('tier') == 0) return;
+ r.set('tier', r.get('tier') - 1);
+ r.store.sort('tier', 'ASC');
+ r.store.commitChanges();
+
+ this.list.select(r.store.indexOf(r));
+ },
+});
+/**
+ * Deluge.EventsManager.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+/**
+ * @class Deluge.EventsManager
+ * @extends Ext.util.Observable
+ * <p>Deluge.EventsManager is instantated as <tt>deluge.events</tt> and can be used by components of the UI to fire global events</p>
+ * Class for holding global events that occur within the UI.
+ */
+Deluge.EventsManager = Ext.extend(Ext.util.Observable, {
+ constructor: function() {
+ this.toRegister = [];
+ this.on('login', this.onLogin, this);
+ Deluge.EventsManager.superclass.constructor.call(this);
+ },
+
+ /**
+ * Append an event handler to this object.
+ */
+ addListener: function(eventName, fn, scope, o) {
+ this.addEvents(eventName);
+ if (/[A-Z]/.test(eventName.substring(0, 1))) {
+ if (!deluge.client) {
+ this.toRegister.push(eventName);
+ } else {
+ deluge.client.web.register_event_listener(eventName);
+ }
+ }
+ Deluge.EventsManager.superclass.addListener.call(
+ this,
+ eventName,
+ fn,
+ scope,
+ o
+ );
+ },
+
+ getEvents: function() {
+ deluge.client.web.get_events({
+ success: this.onGetEventsSuccess,
+ failure: this.onGetEventsFailure,
+ scope: this,
+ });
+ },
+
+ /**
+ * Starts the EventsManagerManager checking for events.
+ */
+ start: function() {
+ Ext.each(this.toRegister, function(eventName) {
+ deluge.client.web.register_event_listener(eventName);
+ });
+ this.running = true;
+ this.errorCount = 0;
+ this.getEvents();
+ },
+
+ /**
+ * Stops the EventsManagerManager checking for events.
+ */
+ stop: function() {
+ this.running = false;
+ },
+
+ // private
+ onLogin: function() {
+ this.start();
+ },
+
+ onGetEventsSuccess: function(events) {
+ if (!this.running) return;
+ if (events) {
+ Ext.each(
+ events,
+ function(event) {
+ var name = event[0],
+ args = event[1];
+ args.splice(0, 0, name);
+ this.fireEvent.apply(this, args);
+ },
+ this
+ );
+ }
+ this.getEvents();
+ },
+
+ // private
+ onGetEventsFailure: function(result, error) {
+ // the request timed out or we had a communication failure
+ if (!this.running) return;
+ if (!error.isTimeout && this.errorCount++ >= 3) {
+ this.stop();
+ return;
+ }
+ this.getEvents();
+ },
+});
+
+/**
+ * Appends an event handler to this object (shorthand for {@link #addListener})
+ * @method
+ */
+Deluge.EventsManager.prototype.on = Deluge.EventsManager.prototype.addListener;
+
+/**
+ * Fires the specified event with the passed parameters (minus the
+ * event name).
+ * @method
+ */
+Deluge.EventsManager.prototype.fire = Deluge.EventsManager.prototype.fireEvent;
+deluge.events = new Deluge.EventsManager();
+/**
+ * Deluge.FileBrowser.js
+ *
+ * Copyright (c) Damien Churchill 2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Ext.namespace('Deluge');
+Deluge.FileBrowser = Ext.extend(Ext.Window, {
+ title: _('File Browser'),
+
+ width: 500,
+ height: 400,
+
+ initComponent: function() {
+ Deluge.FileBrowser.superclass.initComponent.call(this);
+
+ this.add({
+ xtype: 'toolbar',
+ items: [
+ {
+ text: _('Back'),
+ iconCls: 'icon-back',
+ },
+ {
+ text: _('Forward'),
+ iconCls: 'icon-forward',
+ },
+ {
+ text: _('Up'),
+ iconCls: 'icon-up',
+ },
+ {
+ text: _('Home'),
+ iconCls: 'icon-home',
+ },
+ ],
+ });
+ },
+});
+/**
+ * Deluge.FilterPanel.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+/**
+ * @class Deluge.FilterPanel
+ * @extends Ext.list.ListView
+ */
+Deluge.FilterPanel = Ext.extend(Ext.Panel, {
+ autoScroll: true,
+
+ border: false,
+
+ show_zero: null,
+
+ initComponent: function() {
+ Deluge.FilterPanel.superclass.initComponent.call(this);
+ this.filterType = this.initialConfig.filter;
+ var title = '';
+ if (this.filterType == 'state') {
+ title = _('States');
+ } else if (this.filterType == 'tracker_host') {
+ title = _('Trackers');
+ } else if (this.filterType == 'owner') {
+ title = _('Owner');
+ } else if (this.filterType == 'label') {
+ title = _('Labels');
+ } else {
+ (title = this.filterType.replace('_', ' ')),
+ (parts = title.split(' ')),
+ (title = '');
+ Ext.each(parts, function(p) {
+ fl = p.substring(0, 1).toUpperCase();
+ title += fl + p.substring(1) + ' ';
+ });
+ }
+ this.setTitle(_(title));
+
+ if (Deluge.FilterPanel.templates[this.filterType]) {
+ var tpl = Deluge.FilterPanel.templates[this.filterType];
+ } else {
+ var tpl =
+ '<div class="x-deluge-filter x-deluge-{filter:lowercase}">{filter} ({count})</div>';
+ }
+
+ this.list = this.add({
+ xtype: 'listview',
+ singleSelect: true,
+ hideHeaders: true,
+ reserveScrollOffset: true,
+ store: new Ext.data.ArrayStore({
+ idIndex: 0,
+ fields: ['filter', 'count'],
+ }),
+ columns: [
+ {
+ id: 'filter',
+ sortable: false,
+ tpl: tpl,
+ dataIndex: 'filter',
+ },
+ ],
+ });
+ this.relayEvents(this.list, ['selectionchange']);
+ },
+
+ /**
+ * Return the currently selected filter state
+ * @returns {String} the current filter state
+ */
+ getState: function() {
+ if (!this.list.getSelectionCount()) return;
+
+ var state = this.list.getSelectedRecords()[0];
+ if (!state) return;
+ if (state.id == 'All') return;
+ return state.id;
+ },
+
+ /**
+ * Return the current states in the filter
+ */
+ getStates: function() {
+ return this.states;
+ },
+
+ /**
+ * Return the Store for the ListView of the FilterPanel
+ * @returns {Ext.data.Store} the ListView store
+ */
+ getStore: function() {
+ return this.list.getStore();
+ },
+
+ /**
+ * Update the states in the FilterPanel
+ */
+ updateStates: function(states) {
+ this.states = {};
+ Ext.each(
+ states,
+ function(state) {
+ this.states[state[0]] = state[1];
+ },
+ this
+ );
+
+ var show_zero =
+ this.show_zero == null
+ ? deluge.config.sidebar_show_zero
+ : this.show_zero;
+ if (!show_zero) {
+ var newStates = [];
+ Ext.each(states, function(state) {
+ if (state[1] > 0 || state[0] == 'All') {
+ newStates.push(state);
+ }
+ });
+ states = newStates;
+ }
+
+ var store = this.getStore();
+ var filters = {};
+ Ext.each(
+ states,
+ function(s, i) {
+ var record = store.getById(s[0]);
+ if (!record) {
+ record = new store.recordType({
+ filter: s[0],
+ count: s[1],
+ });
+ record.id = s[0];
+ store.insert(i, record);
+ }
+ record.beginEdit();
+ record.set('filter', _(s[0]));
+ record.set('count', s[1]);
+ record.endEdit();
+ filters[s[0]] = true;
+ },
+ this
+ );
+
+ store.each(function(record) {
+ if (filters[record.id]) return;
+ store.remove(record);
+ var selected = this.list.getSelectedRecords()[0];
+ if (!selected) return;
+ if (selected.id == record.id) {
+ this.list.select(0);
+ }
+ }, this);
+
+ store.commitChanges();
+
+ if (!this.list.getSelectionCount()) {
+ this.list.select(0);
+ }
+ },
+});
+
+Deluge.FilterPanel.templates = {
+ tracker_host:
+ '<div class="x-deluge-filter" style="background-image: url(' +
+ deluge.config.base +
+ 'tracker/{filter});">{filter} ({count})</div>',
+};
+/**
+ * Deluge.Formatters.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+/**
+ * A collection of functions for string formatting values.
+ * @class Deluge.Formatters
+ * @author Damien Churchill <damoxc@gmail.com>
+ * @version 1.3
+ * @singleton
+ */
+Deluge.Formatters = {
+ /**
+ * Formats a date string in the date representation of the current locale,
+ * based on the systems timezone.
+ *
+ * @param {Number} timestamp time in seconds since the Epoch.
+ * @return {String} a string in the date representation of the current locale
+ * or "" if seconds < 0.
+ */
+ date: function(timestamp) {
+ function zeroPad(num, count) {
+ var numZeropad = num + '';
+ while (numZeropad.length < count) {
+ numZeropad = '0' + numZeropad;
+ }
+ return numZeropad;
+ }
+ timestamp = timestamp * 1000;
+ var date = new Date(timestamp);
+ return String.format(
+ '{0}/{1}/{2} {3}:{4}:{5}',
+ zeroPad(date.getDate(), 2),
+ zeroPad(date.getMonth() + 1, 2),
+ date.getFullYear(),
+ zeroPad(date.getHours(), 2),
+ zeroPad(date.getMinutes(), 2),
+ zeroPad(date.getSeconds(), 2)
+ );
+ },
+
+ /**
+ * Formats the bytes value into a string with KiB, MiB or GiB units.
+ *
+ * @param {Number} bytes the filesize in bytes
+ * @param {Boolean} showZero pass in true to displays 0 values
+ * @return {String} formatted string with KiB, MiB or GiB units.
+ */
+ size: function(bytes, showZero) {
+ if (!bytes && !showZero) return '';
+ bytes = bytes / 1024.0;
+
+ if (bytes < 1024) {
+ return bytes.toFixed(1) + ' KiB';
+ } else {
+ bytes = bytes / 1024;
+ }
+
+ if (bytes < 1024) {
+ return bytes.toFixed(1) + ' MiB';
+ } else {
+ bytes = bytes / 1024;
+ }
+
+ return bytes.toFixed(1) + ' GiB';
+ },
+
+ /**
+ * Formats the bytes value into a string with K, M or G units.
+ *
+ * @param {Number} bytes the filesize in bytes
+ * @param {Boolean} showZero pass in true to displays 0 values
+ * @return {String} formatted string with K, M or G units.
+ */
+ sizeShort: function(bytes, showZero) {
+ if (!bytes && !showZero) return '';
+ bytes = bytes / 1024.0;
+
+ if (bytes < 1024) {
+ return bytes.toFixed(1) + ' K';
+ } else {
+ bytes = bytes / 1024;
+ }
+
+ if (bytes < 1024) {
+ return bytes.toFixed(1) + ' M';
+ } else {
+ bytes = bytes / 1024;
+ }
+
+ return bytes.toFixed(1) + ' G';
+ },
+
+ /**
+ * Formats a string to display a transfer speed utilizing {@link #size}
+ *
+ * @param {Number} bytes the number of bytes per second
+ * @param {Boolean} showZero pass in true to displays 0 values
+ * @return {String} formatted string with KiB, MiB or GiB units.
+ */
+ speed: function(bytes, showZero) {
+ return !bytes && !showZero ? '' : fsize(bytes, showZero) + '/s';
+ },
+
+ /**
+ * Formats a string to show time in a human readable form.
+ *
+ * @param {Number} time the number of seconds
+ * @return {String} a formatted time string. will return '' if seconds == 0
+ */
+ timeRemaining: function(time) {
+ if (time <= 0) {
+ return '&infin;';
+ }
+ time = time.toFixed(0);
+ if (time < 60) {
+ return time + 's';
+ } else {
+ time = time / 60;
+ }
+
+ if (time < 60) {
+ var minutes = Math.floor(time);
+ var seconds = Math.round(60 * (time - minutes));
+ if (seconds > 0) {
+ return minutes + 'm ' + seconds + 's';
+ } else {
+ return minutes + 'm';
+ }
+ } else {
+ time = time / 60;
+ }
+
+ if (time < 24) {
+ var hours = Math.floor(time);
+ var minutes = Math.round(60 * (time - hours));
+ if (minutes > 0) {
+ return hours + 'h ' + minutes + 'm';
+ } else {
+ return hours + 'h';
+ }
+ } else {
+ time = time / 24;
+ }
+
+ var days = Math.floor(time);
+ var hours = Math.round(24 * (time - days));
+ if (hours > 0) {
+ return days + 'd ' + hours + 'h';
+ } else {
+ return days + 'd';
+ }
+ },
+
+ /**
+ * Simply returns the value untouched, for when no formatting is required.
+ *
+ * @param {Mixed} value the value to be displayed
+ * @return the untouched value.
+ */
+ plain: function(value) {
+ return value;
+ },
+
+ cssClassEscape: function(value) {
+ return value.toLowerCase().replace('.', '_');
+ },
+};
+var fsize = Deluge.Formatters.size;
+var fsize_short = Deluge.Formatters.sizeShort;
+var fspeed = Deluge.Formatters.speed;
+var ftime = Deluge.Formatters.timeRemaining;
+var fdate = Deluge.Formatters.date;
+var fplain = Deluge.Formatters.plain;
+Ext.util.Format.cssClassEscape = Deluge.Formatters.cssClassEscape;
+/**
+ * Deluge.Keys.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+/**
+ * @description The torrent status keys that are commonly used around the UI.
+ * @class Deluge.Keys
+ * @singleton
+ */
+Deluge.Keys = {
+ /**
+ * Keys that are used within the torrent grid.
+ * <pre>['queue', 'name', 'total_wanted', 'state', 'progress', 'num_seeds',
+ * 'total_seeds', 'num_peers', 'total_peers', 'download_payload_rate',
+ * 'upload_payload_rate', 'eta', 'ratio', 'distributed_copies',
+ * 'is_auto_managed', 'time_added', 'tracker_host', 'download_location', 'last_seen_complete',
+ * 'total_done', 'total_uploaded', 'max_download_speed', 'max_upload_speed',
+ * 'seeds_peers_ratio', 'total_remaining', 'completed_time', 'time_since_transfer']</pre>
+ */
+ Grid: [
+ 'queue',
+ 'name',
+ 'total_wanted',
+ 'state',
+ 'progress',
+ 'num_seeds',
+ 'total_seeds',
+ 'num_peers',
+ 'total_peers',
+ 'download_payload_rate',
+ 'upload_payload_rate',
+ 'eta',
+ 'ratio',
+ 'distributed_copies',
+ 'is_auto_managed',
+ 'time_added',
+ 'tracker_host',
+ 'download_location',
+ 'last_seen_complete',
+ 'total_done',
+ 'total_uploaded',
+ 'max_download_speed',
+ 'max_upload_speed',
+ 'seeds_peers_ratio',
+ 'total_remaining',
+ 'completed_time',
+ 'time_since_transfer',
+ ],
+
+ /**
+ * Keys used in the status tab of the statistics panel.
+ * These get updated to include the keys in {@link #Grid}.
+ * <pre>['total_done', 'total_payload_download', 'total_uploaded',
+ * 'total_payload_upload', 'next_announce', 'tracker_status', 'num_pieces',
+ * 'piece_length', 'is_auto_managed', 'active_time', 'seeding_time', 'time_since_transfer',
+ * 'seed_rank', 'last_seen_complete', 'completed_time', 'owner', 'public', 'shared']</pre>
+ */
+ Status: [
+ 'total_done',
+ 'total_payload_download',
+ 'total_uploaded',
+ 'total_payload_upload',
+ 'next_announce',
+ 'tracker_status',
+ 'num_pieces',
+ 'piece_length',
+ 'is_auto_managed',
+ 'active_time',
+ 'seeding_time',
+ 'time_since_transfer',
+ 'seed_rank',
+ 'last_seen_complete',
+ 'completed_time',
+ 'owner',
+ 'public',
+ 'shared',
+ ],
+
+ /**
+ * Keys used in the files tab of the statistics panel.
+ * <pre>['files', 'file_progress', 'file_priorities']</pre>
+ */
+ Files: ['files', 'file_progress', 'file_priorities'],
+
+ /**
+ * Keys used in the peers tab of the statistics panel.
+ * <pre>['peers']</pre>
+ */
+ Peers: ['peers'],
+
+ /**
+ * Keys used in the details tab of the statistics panel.
+ */
+ Details: [
+ 'name',
+ 'download_location',
+ 'total_size',
+ 'num_files',
+ 'message',
+ 'tracker_host',
+ 'comment',
+ 'creator',
+ ],
+
+ /**
+ * Keys used in the options tab of the statistics panel.
+ * <pre>['max_download_speed', 'max_upload_speed', 'max_connections', 'max_upload_slots',
+ * 'is_auto_managed', 'stop_at_ratio', 'stop_ratio', 'remove_at_ratio', 'private',
+ * 'prioritize_first_last']</pre>
+ */
+ Options: [
+ 'max_download_speed',
+ 'max_upload_speed',
+ 'max_connections',
+ 'max_upload_slots',
+ 'is_auto_managed',
+ 'stop_at_ratio',
+ 'stop_ratio',
+ 'remove_at_ratio',
+ 'private',
+ 'prioritize_first_last',
+ 'move_completed',
+ 'move_completed_path',
+ 'super_seeding',
+ ],
+};
+
+// Merge the grid and status keys together as the status keys contain all the
+// grid ones.
+Ext.each(Deluge.Keys.Grid, function(key) {
+ Deluge.Keys.Status.push(key);
+});
+/**
+ * Deluge.LoginWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Deluge.LoginWindow = Ext.extend(Ext.Window, {
+ firstShow: true,
+ bodyStyle: 'padding: 10px 5px;',
+ buttonAlign: 'center',
+ closable: false,
+ closeAction: 'hide',
+ iconCls: 'x-deluge-login-window-icon',
+ layout: 'fit',
+ modal: true,
+ plain: true,
+ resizable: false,
+ title: _('Login'),
+ width: 300,
+ height: 120,
+
+ initComponent: function() {
+ Deluge.LoginWindow.superclass.initComponent.call(this);
+ this.on('show', this.onShow, this);
+
+ this.addButton({
+ text: _('Login'),
+ handler: this.onLogin,
+ scope: this,
+ });
+
+ this.form = this.add({
+ xtype: 'form',
+ baseCls: 'x-plain',
+ labelWidth: 120,
+ labelAlign: 'right',
+ defaults: { width: 110 },
+ defaultType: 'textfield',
+ });
+
+ this.passwordField = this.form.add({
+ xtype: 'textfield',
+ fieldLabel: _('Password:'),
+ labelSeparator: '',
+ grow: true,
+ growMin: '110',
+ growMax: '145',
+ id: '_password',
+ name: 'password',
+ inputType: 'password',
+ });
+ this.passwordField.on('specialkey', this.onSpecialKey, this);
+ },
+
+ logout: function() {
+ deluge.events.fire('logout');
+ deluge.client.auth.delete_session({
+ success: function(result) {
+ this.show(true);
+ },
+ scope: this,
+ });
+ },
+
+ show: function(skipCheck) {
+ if (this.firstShow) {
+ deluge.client.on('error', this.onClientError, this);
+ this.firstShow = false;
+ }
+
+ if (skipCheck) {
+ return Deluge.LoginWindow.superclass.show.call(this);
+ }
+
+ deluge.client.auth.check_session({
+ success: function(result) {
+ if (result) {
+ deluge.events.fire('login');
+ } else {
+ this.show(true);
+ }
+ },
+ failure: function(result) {
+ this.show(true);
+ },
+ scope: this,
+ });
+ },
+
+ onSpecialKey: function(field, e) {
+ if (e.getKey() == 13) this.onLogin();
+ },
+
+ onLogin: function() {
+ var passwordField = this.passwordField;
+ deluge.client.auth.login(passwordField.getValue(), {
+ success: function(result) {
+ if (result) {
+ deluge.events.fire('login');
+ this.hide();
+ passwordField.setRawValue('');
+ } else {
+ Ext.MessageBox.show({
+ title: _('Login Failed'),
+ msg: _('You entered an incorrect password'),
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ fn: function() {
+ passwordField.focus(true, 10);
+ },
+ icon: Ext.MessageBox.WARNING,
+ iconCls: 'x-deluge-icon-warning',
+ });
+ }
+ },
+ scope: this,
+ });
+ },
+
+ onClientError: function(errorObj, response, requestOptions) {
+ if (errorObj.error.code == 1) {
+ deluge.events.fire('logout');
+ this.show(true);
+ }
+ },
+
+ onShow: function() {
+ this.passwordField.focus(true, 300);
+ },
+});
+/**
+ * Deluge.Menus.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+deluge.menus = {
+ onTorrentActionSetOpt: function(item, e) {
+ var ids = deluge.torrents.getSelectedIds();
+ var action = item.initialConfig.torrentAction;
+ var opts = {};
+ opts[action[0]] = action[1];
+ deluge.client.core.set_torrent_options(ids, opts);
+ },
+
+ onTorrentActionMethod: function(item, e) {
+ var ids = deluge.torrents.getSelectedIds();
+ var action = item.initialConfig.torrentAction;
+ deluge.client.core[action](ids, {
+ success: function() {
+ deluge.ui.update();
+ },
+ });
+ },
+
+ onTorrentActionShow: function(item, e) {
+ var ids = deluge.torrents.getSelectedIds();
+ var action = item.initialConfig.torrentAction;
+ switch (action) {
+ case 'edit_trackers':
+ deluge.editTrackers.show();
+ break;
+ case 'remove':
+ deluge.removeWindow.show(ids);
+ break;
+ case 'move':
+ deluge.moveStorage.show(ids);
+ break;
+ }
+ },
+};
+
+deluge.menus.torrent = new Ext.menu.Menu({
+ id: 'torrentMenu',
+ items: [
+ {
+ torrentAction: 'pause_torrent',
+ text: _('Pause'),
+ iconCls: 'icon-pause',
+ handler: deluge.menus.onTorrentActionMethod,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: 'resume_torrent',
+ text: _('Resume'),
+ iconCls: 'icon-resume',
+ handler: deluge.menus.onTorrentActionMethod,
+ scope: deluge.menus,
+ },
+ '-',
+ {
+ text: _('Options'),
+ iconCls: 'icon-options',
+ hideOnClick: false,
+ menu: new Ext.menu.Menu({
+ items: [
+ {
+ text: _('D/L Speed Limit'),
+ iconCls: 'x-deluge-downloading',
+ hideOnClick: false,
+ menu: new Ext.menu.Menu({
+ items: [
+ {
+ torrentAction: ['max_download_speed', 5],
+ text: _('5 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_download_speed', 10],
+ text: _('10 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_download_speed', 30],
+ text: _('30 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_download_speed', 80],
+ text: _('80 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_download_speed', 300],
+ text: _('300 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_download_speed', -1],
+ text: _('Unlimited'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ ],
+ }),
+ },
+ {
+ text: _('U/L Speed Limit'),
+ iconCls: 'x-deluge-seeding',
+ hideOnClick: false,
+ menu: new Ext.menu.Menu({
+ items: [
+ {
+ torrentAction: ['max_upload_speed', 5],
+ text: _('5 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_speed', 10],
+ text: _('10 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_speed', 30],
+ text: _('30 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_speed', 80],
+ text: _('80 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_speed', 300],
+ text: _('300 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_speed', -1],
+ text: _('Unlimited'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ ],
+ }),
+ },
+ {
+ text: _('Connection Limit'),
+ iconCls: 'x-deluge-connections',
+ hideOnClick: false,
+ menu: new Ext.menu.Menu({
+ items: [
+ {
+ torrentAction: ['max_connections', 50],
+ text: '50',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_connections', 100],
+ text: '100',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_connections', 200],
+ text: '200',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_connections', 300],
+ text: '300',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_connections', 500],
+ text: '500',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_connections', -1],
+ text: _('Unlimited'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ ],
+ }),
+ },
+ {
+ text: _('Upload Slot Limit'),
+ iconCls: 'icon-upload-slots',
+ hideOnClick: false,
+ menu: new Ext.menu.Menu({
+ items: [
+ {
+ torrentAction: ['max_upload_slots', 0],
+ text: '0',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_slots', 1],
+ text: '1',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_slots', 2],
+ text: '2',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_slots', 3],
+ text: '3',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_slots', 5],
+ text: '5',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_slots', -1],
+ text: _('Unlimited'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ ],
+ }),
+ },
+ {
+ id: 'auto_managed',
+ text: _('Auto Managed'),
+ hideOnClick: false,
+ menu: new Ext.menu.Menu({
+ items: [
+ {
+ torrentAction: ['auto_managed', true],
+ text: _('On'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['auto_managed', false],
+ text: _('Off'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ ],
+ }),
+ },
+ ],
+ }),
+ },
+ '-',
+ {
+ text: _('Queue'),
+ iconCls: 'icon-queue',
+ hideOnClick: false,
+ menu: new Ext.menu.Menu({
+ items: [
+ {
+ torrentAction: 'queue_top',
+ text: _('Top'),
+ iconCls: 'icon-top',
+ handler: deluge.menus.onTorrentActionMethod,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: 'queue_up',
+ text: _('Up'),
+ iconCls: 'icon-up',
+ handler: deluge.menus.onTorrentActionMethod,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: 'queue_down',
+ text: _('Down'),
+ iconCls: 'icon-down',
+ handler: deluge.menus.onTorrentActionMethod,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: 'queue_bottom',
+ text: _('Bottom'),
+ iconCls: 'icon-bottom',
+ handler: deluge.menus.onTorrentActionMethod,
+ scope: deluge.menus,
+ },
+ ],
+ }),
+ },
+ '-',
+ {
+ torrentAction: 'force_reannounce',
+ text: _('Update Tracker'),
+ iconCls: 'icon-update-tracker',
+ handler: deluge.menus.onTorrentActionMethod,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: 'edit_trackers',
+ text: _('Edit Trackers'),
+ iconCls: 'icon-edit-trackers',
+ handler: deluge.menus.onTorrentActionShow,
+ scope: deluge.menus,
+ },
+ '-',
+ {
+ torrentAction: 'remove',
+ text: _('Remove Torrent'),
+ iconCls: 'icon-remove',
+ handler: deluge.menus.onTorrentActionShow,
+ scope: deluge.menus,
+ },
+ '-',
+ {
+ torrentAction: 'force_recheck',
+ text: _('Force Recheck'),
+ iconCls: 'icon-recheck',
+ handler: deluge.menus.onTorrentActionMethod,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: 'move',
+ text: _('Move Download Folder'),
+ iconCls: 'icon-move',
+ handler: deluge.menus.onTorrentActionShow,
+ scope: deluge.menus,
+ },
+ ],
+});
+
+deluge.menus.filePriorities = new Ext.menu.Menu({
+ id: 'filePrioritiesMenu',
+ items: [
+ {
+ id: 'expandAll',
+ text: _('Expand All'),
+ iconCls: 'icon-expand-all',
+ },
+ '-',
+ {
+ id: 'skip',
+ text: _('Skip'),
+ iconCls: 'icon-do-not-download',
+ filePriority: FILE_PRIORITY['Skip'],
+ },
+ {
+ id: 'low',
+ text: _('Low'),
+ iconCls: 'icon-low',
+ filePriority: FILE_PRIORITY['Low'],
+ },
+ {
+ id: 'normal',
+ text: _('Normal'),
+ iconCls: 'icon-normal',
+ filePriority: FILE_PRIORITY['Normal'],
+ },
+ {
+ id: 'high',
+ text: _('High'),
+ iconCls: 'icon-high',
+ filePriority: FILE_PRIORITY['High'],
+ },
+ ],
+});
+/**
+ * Deluge.MoveStorage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Ext.namespace('Deluge');
+Deluge.MoveStorage = Ext.extend(Ext.Window, {
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ title: _('Move Download Folder'),
+ width: 375,
+ height: 110,
+ layout: 'fit',
+ buttonAlign: 'right',
+ closeAction: 'hide',
+ closable: true,
+ iconCls: 'x-deluge-move-storage',
+ plain: true,
+ constrainHeader: true,
+ resizable: false,
+ },
+ config
+ );
+ Deluge.MoveStorage.superclass.constructor.call(this, config);
+ },
+
+ initComponent: function() {
+ Deluge.MoveStorage.superclass.initComponent.call(this);
+
+ this.addButton(_('Cancel'), this.onCancel, this);
+ this.addButton(_('Move'), this.onMove, this);
+
+ this.form = this.add({
+ xtype: 'form',
+ border: false,
+ defaultType: 'textfield',
+ width: 300,
+ bodyStyle: 'padding: 5px',
+ });
+
+ this.moveLocation = this.form.add({
+ fieldLabel: _('Download Folder'),
+ name: 'location',
+ width: 240,
+ });
+ //this.form.add({
+ // xtype: 'button',
+ // text: _('Browse'),
+ // handler: function() {
+ // if (!this.fileBrowser) {
+ // this.fileBrowser = new Deluge.FileBrowser();
+ // }
+ // this.fileBrowser.show();
+ // },
+ // scope: this
+ //});
+ },
+
+ hide: function() {
+ Deluge.MoveStorage.superclass.hide.call(this);
+ this.torrentIds = null;
+ },
+
+ show: function(torrentIds) {
+ Deluge.MoveStorage.superclass.show.call(this);
+ this.torrentIds = torrentIds;
+ },
+
+ onCancel: function() {
+ this.hide();
+ },
+
+ onMove: function() {
+ var dest = this.moveLocation.getValue();
+ deluge.client.core.move_storage(this.torrentIds, dest);
+ this.hide();
+ },
+});
+deluge.moveStorage = new Deluge.MoveStorage();
+/**
+ * Deluge.MultiOptionsManager.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+/**
+ * @description A class that can be used to manage options throughout the ui.
+ * @namespace Deluge
+ * @class Deluge.MultiOptionsManager
+ * @extends Deluge.OptionsManager
+ */
+Deluge.MultiOptionsManager = Ext.extend(Deluge.OptionsManager, {
+ constructor: function(config) {
+ this.currentId = null;
+ this.stored = {};
+ Deluge.MultiOptionsManager.superclass.constructor.call(this, config);
+ },
+
+ /**
+ * Changes bound fields to use the specified id.
+ * @param {String} id
+ */
+ changeId: function(id, dontUpdateBinds) {
+ var oldId = this.currentId;
+ this.currentId = id;
+ if (!dontUpdateBinds) {
+ for (var option in this.options) {
+ if (!this.binds[option]) continue;
+ Ext.each(
+ this.binds[option],
+ function(bind) {
+ bind.setValue(this.get(option));
+ },
+ this
+ );
+ }
+ }
+ return oldId;
+ },
+
+ /**
+ * Changes all the changed values to be the default values
+ * @param {String} id
+ */
+ commit: function() {
+ this.stored[this.currentId] = Ext.apply(
+ this.stored[this.currentId],
+ this.changed[this.currentId]
+ );
+ this.reset();
+ },
+
+ /**
+ * Get the value for an option
+ * @param {String/Array} option A single option or an array of options to return.
+ * @returns {Object} the options value.
+ */
+ get: function() {
+ if (arguments.length == 1) {
+ var option = arguments[0];
+ return this.isDirty(option)
+ ? this.changed[this.currentId][option]
+ : this.getDefault(option);
+ } else if (arguments.length == 0) {
+ var options = {};
+ for (var option in this.options) {
+ options[option] = this.isDirty(option)
+ ? this.changed[this.currentId][option]
+ : this.getDefault(option);
+ }
+ return options;
+ } else {
+ var options = {};
+ Ext.each(
+ arguments,
+ function(option) {
+ options[option] = this.isDirty(option)
+ ? this.changed[this.currentId][option]
+ : this.getDefault(option);
+ },
+ this
+ );
+ return options;
+ }
+ },
+
+ /**
+ * Get the default value for an option.
+ * @param {String} option A single option.
+ * @returns {Object} the value of the option
+ */
+ getDefault: function(option) {
+ return this.has(option)
+ ? this.stored[this.currentId][option]
+ : this.options[option];
+ },
+
+ /**
+ * Returns the dirty (changed) values.
+ * @returns {Object} the changed options
+ */
+ getDirty: function() {
+ return this.changed[this.currentId] ? this.changed[this.currentId] : {};
+ },
+
+ /**
+ * Check to see if the option has been changed.
+ * @param {String} option
+ * @returns {Boolean} true if the option has been changed, else false.
+ */
+ isDirty: function(option) {
+ return (
+ this.changed[this.currentId] &&
+ !Ext.isEmpty(this.changed[this.currentId][option])
+ );
+ },
+
+ /**
+ * Check to see if an id has had an option set to something other than the
+ * default value.
+ * @param {String} option
+ * @returns {Boolean} true if the id has an option, else false.
+ */
+ has: function(option) {
+ return (
+ this.stored[this.currentId] &&
+ !Ext.isEmpty(this.stored[this.currentId][option])
+ );
+ },
+
+ /**
+ * Reset the options back to the default values for the specified id.
+ */
+ reset: function() {
+ if (this.changed[this.currentId]) delete this.changed[this.currentId];
+ if (this.stored[this.currentId]) delete this.stored[this.currentId];
+ },
+
+ /**
+ * Reset the options back to their defaults for all ids.
+ */
+ resetAll: function() {
+ this.changed = {};
+ this.stored = {};
+ this.changeId(null);
+ },
+
+ /**
+ * Sets the value of specified option for the passed in id.
+ * @param {String} id
+ * @param {String} option
+ * @param {Object} value The value for the option
+ */
+ setDefault: function(option, value) {
+ if (option === undefined) {
+ return;
+ } else if (value === undefined) {
+ for (var key in option) {
+ this.setDefault(key, option[key]);
+ }
+ } else {
+ var oldValue = this.getDefault(option);
+ value = this.convertValueType(oldValue, value);
+
+ // If the value is the same as the old value there is
+ // no point in setting it again.
+ if (oldValue == value) return;
+
+ // Store the new default
+ if (!this.stored[this.currentId]) this.stored[this.currentId] = {};
+ this.stored[this.currentId][option] = value;
+
+ if (!this.isDirty(option)) {
+ this.fireEvent('changed', option, value, oldValue);
+ }
+ }
+ },
+
+ /**
+ * Update the value for the specified option and id.
+ * @param {String} id
+ * @param {String/Object} option or options to update
+ * @param {Object} [value];
+ */
+ update: function(option, value) {
+ if (option === undefined) {
+ return;
+ } else if (value === undefined) {
+ for (var key in option) {
+ this.update(key, option[key]);
+ }
+ } else {
+ if (!this.changed[this.currentId])
+ this.changed[this.currentId] = {};
+
+ var defaultValue = this.getDefault(option);
+ value = this.convertValueType(defaultValue, value);
+
+ var oldValue = this.get(option);
+ if (oldValue == value) return;
+
+ if (defaultValue == value) {
+ if (this.isDirty(option))
+ delete this.changed[this.currentId][option];
+ this.fireEvent('changed', option, value, oldValue);
+ return;
+ } else {
+ this.changed[this.currentId][option] = value;
+ this.fireEvent('changed', option, value, oldValue);
+ }
+ }
+ },
+});
+/**
+ * Deluge.OtherLimitWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+/**
+ * @class Deluge.OtherLimitWindow
+ * @extends Ext.Window
+ */
+Deluge.OtherLimitWindow = Ext.extend(Ext.Window, {
+ layout: 'fit',
+ width: 210,
+ height: 100,
+ constrainHeader: true,
+ closeAction: 'hide',
+
+ initComponent: function() {
+ Deluge.OtherLimitWindow.superclass.initComponent.call(this);
+ this.form = this.add({
+ xtype: 'form',
+ baseCls: 'x-plain',
+ bodyStyle: 'padding: 5px',
+ layout: 'hbox',
+ layoutConfig: {
+ pack: 'start',
+ },
+ items: [
+ {
+ xtype: 'spinnerfield',
+ name: 'limit',
+ },
+ ],
+ });
+ if (this.initialConfig.unit) {
+ this.form.add({
+ border: false,
+ baseCls: 'x-plain',
+ bodyStyle: 'padding: 5px',
+ html: this.initialConfig.unit,
+ });
+ } else {
+ this.setSize(180, 100);
+ }
+
+ this.addButton(_('Cancel'), this.onCancelClick, this);
+ this.addButton(_('OK'), this.onOkClick, this);
+ this.afterMethod('show', this.doFocusField, this);
+ },
+
+ setValue: function(value) {
+ this.form.getForm().setValues({ limit: value });
+ },
+
+ onCancelClick: function() {
+ this.form.getForm().reset();
+ this.hide();
+ },
+
+ onOkClick: function() {
+ var config = {};
+ config[this.group] = this.form.getForm().getValues().limit;
+ deluge.client.core.set_config(config, {
+ success: function() {
+ deluge.ui.update();
+ },
+ });
+ this.hide();
+ },
+
+ doFocusField: function() {
+ this.form
+ .getForm()
+ .findField('limit')
+ .focus(true, 10);
+ },
+});
+/**
+ * Deluge.Plugin.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+/**
+ * @class Deluge.Plugin
+ * @extends Ext.util.Observable
+ */
+Deluge.Plugin = Ext.extend(Ext.util.Observable, {
+ /**
+ * The plugins name
+ * @property name
+ * @type {String}
+ */
+ name: null,
+
+ constructor: function(config) {
+ this.isDelugePlugin = true;
+ this.addEvents({
+ /**
+ * @event enabled
+ * @param {Plugin} plugin the plugin instance
+ */
+ enabled: true,
+
+ /**
+ * @event disabled
+ * @param {Plugin} plugin the plugin instance
+ */
+ disabled: true,
+ });
+ Deluge.Plugin.superclass.constructor.call(this, config);
+ },
+
+ /**
+ * Disables the plugin, firing the "{@link #disabled}" event and
+ * then executing the plugins clean up method onDisabled.
+ */
+ disable: function() {
+ this.fireEvent('disabled', this);
+ if (this.onDisable) this.onDisable();
+ },
+
+ /**
+ * Enables the plugin, firing the "{@link #enabled}" event and
+ * then executes the plugins setup method, onEnabled.
+ */
+ enable: function() {
+ deluge.client.reloadMethods();
+ this.fireEvent('enable', this);
+ if (this.onEnable) this.onEnable();
+ },
+
+ registerTorrentStatus: function(key, header, options) {
+ options = options || {};
+ var cc = options.colCfg || {},
+ sc = options.storeCfg || {};
+ sc = Ext.apply(sc, { name: key });
+ deluge.torrents.meta.fields.push(sc);
+ deluge.torrents.getStore().reader.onMetaChange(deluge.torrents.meta);
+
+ cc = Ext.apply(cc, {
+ header: header,
+ dataIndex: key,
+ });
+ var cols = deluge.torrents.columns.slice(0);
+ cols.push(cc);
+ deluge.torrents.colModel.setConfig(cols);
+ deluge.torrents.columns = cols;
+
+ Deluge.Keys.Grid.push(key);
+ deluge.torrents.getView().refresh(true);
+ },
+
+ deregisterTorrentStatus: function(key) {
+ var fields = [];
+ Ext.each(deluge.torrents.meta.fields, function(field) {
+ if (field.name != key) fields.push(field);
+ });
+ deluge.torrents.meta.fields = fields;
+ deluge.torrents.getStore().reader.onMetaChange(deluge.torrents.meta);
+
+ var cols = [];
+ Ext.each(deluge.torrents.columns, function(col) {
+ if (col.dataIndex != key) cols.push(col);
+ });
+ deluge.torrents.colModel.setConfig(cols);
+ deluge.torrents.columns = cols;
+
+ var keys = [];
+ Ext.each(Deluge.Keys.Grid, function(k) {
+ if (k == key) keys.push(k);
+ });
+ Deluge.Keys.Grid = keys;
+ deluge.torrents.getView().refresh(true);
+ },
+});
+
+Ext.ns('Deluge.plugins');
+/**
+ * Deluge.RemoveWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+/**
+ * @class Deluge.RemoveWindow
+ * @extends Ext.Window
+ */
+Deluge.RemoveWindow = Ext.extend(Ext.Window, {
+ title: _('Remove Torrent'),
+ layout: 'fit',
+ width: 350,
+ height: 100,
+ constrainHeader: true,
+ buttonAlign: 'right',
+ closeAction: 'hide',
+ closable: true,
+ iconCls: 'x-deluge-remove-window-icon',
+ plain: true,
+
+ bodyStyle: 'padding: 5px; padding-left: 10px;',
+ html: 'Are you sure you wish to remove the torrent (s)?',
+
+ initComponent: function() {
+ Deluge.RemoveWindow.superclass.initComponent.call(this);
+ this.addButton(_('Cancel'), this.onCancel, this);
+ this.addButton(_('Remove With Data'), this.onRemoveData, this);
+ this.addButton(_('Remove Torrent'), this.onRemove, this);
+ },
+
+ remove: function(removeData) {
+ deluge.client.core.remove_torrents(this.torrentIds, removeData, {
+ success: function(result) {
+ if (result == true) {
+ console.log(
+ 'Error(s) occured when trying to delete torrent(s).'
+ );
+ }
+ this.onRemoved(this.torrentIds);
+ },
+ scope: this,
+ torrentIds: this.torrentIds,
+ });
+ },
+
+ show: function(ids) {
+ Deluge.RemoveWindow.superclass.show.call(this);
+ this.torrentIds = ids;
+ },
+
+ onCancel: function() {
+ this.hide();
+ this.torrentIds = null;
+ },
+
+ onRemove: function() {
+ this.remove(false);
+ },
+
+ onRemoveData: function() {
+ this.remove(true);
+ },
+
+ onRemoved: function(torrentIds) {
+ deluge.events.fire('torrentsRemoved', torrentIds);
+ this.hide();
+ deluge.ui.update();
+ },
+});
+
+deluge.removeWindow = new Deluge.RemoveWindow();
+/**
+ * Deluge.Sidebar.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+// These are just so gen_gettext.py will pick up the strings
+// _('State')
+// _('Tracker Host')
+
+/**
+ * @class Deluge.Sidebar
+ * @author Damien Churchill <damoxc@gmail.com>
+ * @version 1.3
+ */
+Deluge.Sidebar = Ext.extend(Ext.Panel, {
+ // private
+ panels: {},
+
+ // private
+ selected: null,
+
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ id: 'sidebar',
+ region: 'west',
+ cls: 'deluge-sidebar',
+ title: _('Filters'),
+ layout: 'accordion',
+ split: true,
+ width: 200,
+ minSize: 100,
+ collapsible: true,
+ },
+ config
+ );
+ Deluge.Sidebar.superclass.constructor.call(this, config);
+ },
+
+ // private
+ initComponent: function() {
+ Deluge.Sidebar.superclass.initComponent.call(this);
+ deluge.events.on('disconnect', this.onDisconnect, this);
+ },
+
+ createFilter: function(filter, states) {
+ var panel = new Deluge.FilterPanel({
+ filter: filter,
+ });
+ panel.on('selectionchange', function(view, nodes) {
+ deluge.ui.update();
+ });
+ this.add(panel);
+
+ this.doLayout();
+ this.panels[filter] = panel;
+
+ panel.header.on('click', function(header) {
+ if (!deluge.config.sidebar_multiple_filters) {
+ deluge.ui.update();
+ }
+ if (!panel.list.getSelectionCount()) {
+ panel.list.select(0);
+ }
+ });
+ this.fireEvent('filtercreate', this, panel);
+
+ panel.updateStates(states);
+ this.fireEvent('afterfiltercreate', this, panel);
+ },
+
+ getFilter: function(filter) {
+ return this.panels[filter];
+ },
+
+ getFilterStates: function() {
+ var states = {};
+
+ if (deluge.config.sidebar_multiple_filters) {
+ // Grab the filters from each of the filter panels
+ this.items.each(function(panel) {
+ var state = panel.getState();
+ if (state == null) return;
+ states[panel.filterType] = state;
+ }, this);
+ } else {
+ var panel = this.getLayout().activeItem;
+ if (panel) {
+ var state = panel.getState();
+ if (!state == null) return;
+ states[panel.filterType] = state;
+ }
+ }
+
+ return states;
+ },
+
+ hasFilter: function(filter) {
+ return this.panels[filter] ? true : false;
+ },
+
+ // private
+ onDisconnect: function() {
+ for (var filter in this.panels) {
+ this.remove(this.panels[filter]);
+ }
+ this.panels = {};
+ this.selected = null;
+ },
+
+ onFilterSelect: function(selModel, rowIndex, record) {
+ deluge.ui.update();
+ },
+
+ update: function(filters) {
+ for (var filter in filters) {
+ var states = filters[filter];
+ if (Ext.getKeys(this.panels).indexOf(filter) > -1) {
+ this.panels[filter].updateStates(states);
+ } else {
+ this.createFilter(filter, states);
+ }
+ }
+
+ // Perform a cleanup of fitlers that are not enabled any more.
+ Ext.each(
+ Ext.keys(this.panels),
+ function(filter) {
+ if (Ext.keys(filters).indexOf(filter) == -1) {
+ // We need to remove the panel
+ this.remove(this.panels[filter]);
+ this.doLayout();
+ delete this.panels[filter];
+ }
+ },
+ this
+ );
+ },
+});
+/**
+ * Deluge.Statusbar.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge');
+
+Deluge.Statusbar = Ext.extend(Ext.ux.StatusBar, {
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ id: 'deluge-statusbar',
+ defaultIconCls: 'x-deluge-statusbar x-not-connected',
+ defaultText: _('Not Connected'),
+ },
+ config
+ );
+ Deluge.Statusbar.superclass.constructor.call(this, config);
+ },
+
+ initComponent: function() {
+ Deluge.Statusbar.superclass.initComponent.call(this);
+
+ deluge.events.on('connect', this.onConnect, this);
+ deluge.events.on('disconnect', this.onDisconnect, this);
+ },
+
+ createButtons: function() {
+ this.buttons = this.add(
+ {
+ id: 'statusbar-connections',
+ text: ' ',
+ cls: 'x-btn-text-icon',
+ iconCls: 'x-deluge-connections',
+ tooltip: _('Connections'),
+ menu: new Deluge.StatusbarMenu({
+ items: [
+ {
+ text: '50',
+ value: '50',
+ group: 'max_connections_global',
+ checked: false,
+ },
+ {
+ text: '100',
+ value: '100',
+ group: 'max_connections_global',
+ checked: false,
+ },
+ {
+ text: '200',
+ value: '200',
+ group: 'max_connections_global',
+ checked: false,
+ },
+ {
+ text: '300',
+ value: '300',
+ group: 'max_connections_global',
+ checked: false,
+ },
+ {
+ text: '500',
+ value: '500',
+ group: 'max_connections_global',
+ checked: false,
+ },
+ {
+ text: _('Unlimited'),
+ value: '-1',
+ group: 'max_connections_global',
+ checked: false,
+ },
+ '-',
+ {
+ text: _('Other'),
+ value: 'other',
+ group: 'max_connections_global',
+ checked: false,
+ },
+ ],
+ otherWin: {
+ title: _('Set Maximum Connections'),
+ },
+ }),
+ },
+ '-',
+ {
+ id: 'statusbar-downspeed',
+ text: ' ',
+ cls: 'x-btn-text-icon',
+ iconCls: 'x-deluge-downloading',
+ tooltip: _('Download Speed'),
+ menu: new Deluge.StatusbarMenu({
+ items: [
+ {
+ value: '5',
+ text: _('5 KiB/s'),
+ group: 'max_download_speed',
+ checked: false,
+ },
+ {
+ value: '10',
+ text: _('10 KiB/s'),
+ group: 'max_download_speed',
+ checked: false,
+ },
+ {
+ value: '30',
+ text: _('30 KiB/s'),
+ group: 'max_download_speed',
+ checked: false,
+ },
+ {
+ value: '80',
+ text: _('80 KiB/s'),
+ group: 'max_download_speed',
+ checked: false,
+ },
+ {
+ value: '300',
+ text: _('300 KiB/s'),
+ group: 'max_download_speed',
+ checked: false,
+ },
+ {
+ value: '-1',
+ text: _('Unlimited'),
+ group: 'max_download_speed',
+ checked: false,
+ },
+ '-',
+ {
+ value: 'other',
+ text: _('Other'),
+ group: 'max_download_speed',
+ checked: false,
+ },
+ ],
+ otherWin: {
+ title: _('Set Maximum Download Speed'),
+ unit: _('KiB/s'),
+ },
+ }),
+ },
+ '-',
+ {
+ id: 'statusbar-upspeed',
+ text: ' ',
+ cls: 'x-btn-text-icon',
+ iconCls: 'x-deluge-seeding',
+ tooltip: _('Upload Speed'),
+ menu: new Deluge.StatusbarMenu({
+ items: [
+ {
+ value: '5',
+ text: _('5 KiB/s'),
+ group: 'max_upload_speed',
+ checked: false,
+ },
+ {
+ value: '10',
+ text: _('10 KiB/s'),
+ group: 'max_upload_speed',
+ checked: false,
+ },
+ {
+ value: '30',
+ text: _('30 KiB/s'),
+ group: 'max_upload_speed',
+ checked: false,
+ },
+ {
+ value: '80',
+ text: _('80 KiB/s'),
+ group: 'max_upload_speed',
+ checked: false,
+ },
+ {
+ value: '300',
+ text: _('300 KiB/s'),
+ group: 'max_upload_speed',
+ checked: false,
+ },
+ {
+ value: '-1',
+ text: _('Unlimited'),
+ group: 'max_upload_speed',
+ checked: false,
+ },
+ '-',
+ {
+ value: 'other',
+ text: _('Other'),
+ group: 'max_upload_speed',
+ checked: false,
+ },
+ ],
+ otherWin: {
+ title: _('Set Maximum Upload Speed'),
+ unit: _('KiB/s'),
+ },
+ }),
+ },
+ '-',
+ {
+ id: 'statusbar-traffic',
+ text: ' ',
+ cls: 'x-btn-text-icon',
+ iconCls: 'x-deluge-traffic',
+ tooltip: _('Protocol Traffic Download/Upload'),
+ handler: function() {
+ deluge.preferences.show();
+ deluge.preferences.selectPage('Network');
+ },
+ },
+ '-',
+ {
+ id: 'statusbar-externalip',
+ text: ' ',
+ cls: 'x-btn-text',
+ tooltip: _('External IP Address'),
+ },
+ '-',
+ {
+ id: 'statusbar-dht',
+ text: ' ',
+ cls: 'x-btn-text-icon',
+ iconCls: 'x-deluge-dht',
+ tooltip: _('DHT Nodes'),
+ },
+ '-',
+ {
+ id: 'statusbar-freespace',
+ text: ' ',
+ cls: 'x-btn-text-icon',
+ iconCls: 'x-deluge-freespace',
+ tooltip: _('Freespace in download folder'),
+ handler: function() {
+ deluge.preferences.show();
+ deluge.preferences.selectPage('Downloads');
+ },
+ }
+ );
+ this.created = true;
+ },
+
+ onConnect: function() {
+ this.setStatus({
+ iconCls: 'x-connected',
+ text: '',
+ });
+ if (!this.created) {
+ this.createButtons();
+ } else {
+ Ext.each(this.buttons, function(item) {
+ item.show();
+ item.enable();
+ });
+ }
+ this.doLayout();
+ },
+
+ onDisconnect: function() {
+ this.clearStatus({ useDefaults: true });
+ Ext.each(this.buttons, function(item) {
+ item.hide();
+ item.disable();
+ });
+ this.doLayout();
+ },
+
+ update: function(stats) {
+ if (!stats) return;
+
+ function addSpeed(val) {
+ return val + ' KiB/s';
+ }
+
+ var updateStat = function(name, config) {
+ var item = this.items.get('statusbar-' + name);
+ if (config.limit.value > 0) {
+ var value = config.value.formatter
+ ? config.value.formatter(config.value.value, true)
+ : config.value.value;
+ var limit = config.limit.formatter
+ ? config.limit.formatter(config.limit.value, true)
+ : config.limit.value;
+ var str = String.format(config.format, value, limit);
+ } else {
+ var str = config.value.formatter
+ ? config.value.formatter(config.value.value, true)
+ : config.value.value;
+ }
+ item.setText(str);
+
+ if (!item.menu) return;
+ item.menu.setValue(config.limit.value);
+ }.createDelegate(this);
+
+ updateStat('connections', {
+ value: { value: stats.num_connections },
+ limit: { value: stats.max_num_connections },
+ format: '{0} ({1})',
+ });
+
+ updateStat('downspeed', {
+ value: {
+ value: stats.download_rate,
+ formatter: Deluge.Formatters.speed,
+ },
+ limit: {
+ value: stats.max_download,
+ formatter: addSpeed,
+ },
+ format: '{0} ({1})',
+ });
+
+ updateStat('upspeed', {
+ value: {
+ value: stats.upload_rate,
+ formatter: Deluge.Formatters.speed,
+ },
+ limit: {
+ value: stats.max_upload,
+ formatter: addSpeed,
+ },
+ format: '{0} ({1})',
+ });
+
+ updateStat('traffic', {
+ value: {
+ value: stats.download_protocol_rate,
+ formatter: Deluge.Formatters.speed,
+ },
+ limit: {
+ value: stats.upload_protocol_rate,
+ formatter: Deluge.Formatters.speed,
+ },
+ format: '{0}/{1}',
+ });
+
+ this.items.get('statusbar-dht').setText(stats.dht_nodes);
+ this.items
+ .get('statusbar-freespace')
+ .setText(
+ stats.free_space >= 0 ? fsize(stats.free_space) : _('Error')
+ );
+ this.items
+ .get('statusbar-externalip')
+ .setText(
+ String.format(
+ _('<b>IP</b> {0}'),
+ stats.external_ip ? stats.external_ip : _('n/a')
+ )
+ );
+ },
+});
+/**
+ * Deluge.Toolbar.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+/**
+ * An extension of the <tt>Ext.Toolbar</tt> class that provides an extensible toolbar for Deluge.
+ * @class Deluge.Toolbar
+ * @extends Ext.Toolbar
+ */
+Deluge.Toolbar = Ext.extend(Ext.Toolbar, {
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ items: [
+ {
+ id: 'tbar-deluge-text',
+ text: _('Deluge'),
+ iconCls: 'x-deluge-main-panel',
+ handler: this.onAboutClick,
+ },
+ new Ext.Toolbar.Separator(),
+ {
+ id: 'create',
+ disabled: true,
+ hidden: true,
+ text: _('Create'),
+ iconCls: 'icon-create',
+ handler: this.onTorrentAction,
+ },
+ {
+ id: 'add',
+ disabled: true,
+ text: _('Add'),
+ iconCls: 'icon-add',
+ handler: this.onTorrentAdd,
+ },
+ {
+ id: 'remove',
+ disabled: true,
+ text: _('Remove'),
+ iconCls: 'icon-remove',
+ handler: this.onTorrentAction,
+ },
+ new Ext.Toolbar.Separator(),
+ {
+ id: 'pause',
+ disabled: true,
+ text: _('Pause'),
+ iconCls: 'icon-pause',
+ handler: this.onTorrentAction,
+ },
+ {
+ id: 'resume',
+ disabled: true,
+ text: _('Resume'),
+ iconCls: 'icon-resume',
+ handler: this.onTorrentAction,
+ },
+ new Ext.Toolbar.Separator(),
+ {
+ id: 'up',
+ cls: 'x-btn-text-icon',
+ disabled: true,
+ text: _('Up'),
+ iconCls: 'icon-up',
+ handler: this.onTorrentAction,
+ },
+ {
+ id: 'down',
+ disabled: true,
+ text: _('Down'),
+ iconCls: 'icon-down',
+ handler: this.onTorrentAction,
+ },
+ new Ext.Toolbar.Separator(),
+ {
+ id: 'preferences',
+ text: _('Preferences'),
+ iconCls: 'x-deluge-preferences',
+ handler: this.onPreferencesClick,
+ scope: this,
+ },
+ {
+ id: 'connectionman',
+ text: _('Connection Manager'),
+ iconCls: 'x-deluge-connection-manager',
+ handler: this.onConnectionManagerClick,
+ scope: this,
+ },
+ '->',
+ {
+ id: 'help',
+ iconCls: 'icon-help',
+ text: _('Help'),
+ handler: this.onHelpClick,
+ scope: this,
+ },
+ {
+ id: 'logout',
+ iconCls: 'icon-logout',
+ disabled: true,
+ text: _('Logout'),
+ handler: this.onLogout,
+ scope: this,
+ },
+ ],
+ },
+ config
+ );
+ Deluge.Toolbar.superclass.constructor.call(this, config);
+ },
+
+ connectedButtons: ['add', 'remove', 'pause', 'resume', 'up', 'down'],
+
+ initComponent: function() {
+ Deluge.Toolbar.superclass.initComponent.call(this);
+ deluge.events.on('connect', this.onConnect, this);
+ deluge.events.on('login', this.onLogin, this);
+ },
+
+ onConnect: function() {
+ Ext.each(
+ this.connectedButtons,
+ function(buttonId) {
+ this.items.get(buttonId).enable();
+ },
+ this
+ );
+ },
+
+ onDisconnect: function() {
+ Ext.each(
+ this.connectedButtons,
+ function(buttonId) {
+ this.items.get(buttonId).disable();
+ },
+ this
+ );
+ },
+
+ onLogin: function() {
+ this.items.get('logout').enable();
+ },
+
+ onLogout: function() {
+ this.items.get('logout').disable();
+ deluge.login.logout();
+ },
+
+ onConnectionManagerClick: function() {
+ deluge.connectionManager.show();
+ },
+
+ onHelpClick: function() {
+ window.open('http://dev.deluge-torrent.org/wiki/UserGuide');
+ },
+
+ onAboutClick: function() {
+ var about = new Deluge.about.AboutWindow();
+ about.show();
+ },
+
+ onPreferencesClick: function() {
+ deluge.preferences.show();
+ },
+
+ onTorrentAction: function(item) {
+ var selection = deluge.torrents.getSelections();
+ var ids = [];
+ Ext.each(selection, function(record) {
+ ids.push(record.id);
+ });
+
+ switch (item.id) {
+ case 'remove':
+ deluge.removeWindow.show(ids);
+ break;
+ case 'pause':
+ case 'resume':
+ deluge.client.core[item.id + '_torrent'](ids, {
+ success: function() {
+ deluge.ui.update();
+ },
+ });
+ break;
+ case 'up':
+ case 'down':
+ deluge.client.core['queue_' + item.id](ids, {
+ success: function() {
+ deluge.ui.update();
+ },
+ });
+ break;
+ }
+ },
+
+ onTorrentAdd: function() {
+ deluge.add.show();
+ },
+});
+/**
+ * Deluge.TorrentGrid.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+(function() {
+ /* Renderers for the Torrent Grid */
+ function queueRenderer(value) {
+ return value == -1 ? '' : value + 1;
+ }
+ function torrentNameRenderer(value, p, r) {
+ return String.format(
+ '<div class="torrent-name x-deluge-{0}">{1}</div>',
+ r.data['state'].toLowerCase(),
+ value
+ );
+ }
+ function torrentSpeedRenderer(value) {
+ if (!value) return;
+ return fspeed(value);
+ }
+ function torrentLimitRenderer(value) {
+ if (value == -1) return '';
+ return fspeed(value * 1024.0);
+ }
+ function torrentProgressRenderer(value, p, r) {
+ value = new Number(value);
+ var progress = value;
+ var text = _(r.data['state']) + ' ' + value.toFixed(2) + '%';
+ if (this.style) {
+ var style = this.style;
+ } else {
+ var style = p.style;
+ }
+ var width = new Number(style.match(/\w+:\s*(\d+)\w+/)[1]);
+ return Deluge.progressBar(value, width - 8, text);
+ }
+ function seedsRenderer(value, p, r) {
+ if (r.data['total_seeds'] > -1) {
+ return String.format('{0} ({1})', value, r.data['total_seeds']);
+ } else {
+ return value;
+ }
+ }
+ function peersRenderer(value, p, r) {
+ if (r.data['total_peers'] > -1) {
+ return String.format('{0} ({1})', value, r.data['total_peers']);
+ } else {
+ return value;
+ }
+ }
+ function availRenderer(value, p, r) {
+ return value < 0 ? '&infin;' : parseFloat(new Number(value).toFixed(3));
+ }
+ function trackerRenderer(value, p, r) {
+ return String.format(
+ '<div style="background: url(' +
+ deluge.config.base +
+ 'tracker/{0}) no-repeat; padding-left: 20px;">{0}</div>',
+ value
+ );
+ }
+
+ function etaSorter(eta) {
+ return eta * -1;
+ }
+
+ function dateOrNever(date) {
+ return date > 0.0 ? fdate(date) : _('Never');
+ }
+
+ function timeOrInf(time) {
+ return time < 0 ? '&infin;' : ftime(time);
+ }
+
+ /**
+ * Deluge.TorrentGrid Class
+ *
+ * @author Damien Churchill <damoxc@gmail.com>
+ * @version 1.3
+ *
+ * @class Deluge.TorrentGrid
+ * @extends Ext.grid.GridPanel
+ * @constructor
+ * @param {Object} config Configuration options
+ */
+ Deluge.TorrentGrid = Ext.extend(Ext.grid.GridPanel, {
+ // object to store contained torrent ids
+ torrents: {},
+
+ columns: [
+ {
+ id: 'queue',
+ header: '#',
+ width: 30,
+ sortable: true,
+ renderer: queueRenderer,
+ dataIndex: 'queue',
+ },
+ {
+ id: 'name',
+ header: _('Name'),
+ width: 150,
+ sortable: true,
+ renderer: torrentNameRenderer,
+ dataIndex: 'name',
+ },
+ {
+ header: _('Size'),
+ width: 75,
+ sortable: true,
+ renderer: fsize,
+ dataIndex: 'total_wanted',
+ },
+ {
+ header: _('Progress'),
+ width: 150,
+ sortable: true,
+ renderer: torrentProgressRenderer,
+ dataIndex: 'progress',
+ },
+ {
+ header: _('Seeds'),
+ hidden: true,
+ width: 60,
+ sortable: true,
+ renderer: seedsRenderer,
+ dataIndex: 'num_seeds',
+ },
+ {
+ header: _('Peers'),
+ hidden: true,
+ width: 60,
+ sortable: true,
+ renderer: peersRenderer,
+ dataIndex: 'num_peers',
+ },
+ {
+ header: _('Down Speed'),
+ width: 80,
+ sortable: true,
+ renderer: torrentSpeedRenderer,
+ dataIndex: 'download_payload_rate',
+ },
+ {
+ header: _('Up Speed'),
+ width: 80,
+ sortable: true,
+ renderer: torrentSpeedRenderer,
+ dataIndex: 'upload_payload_rate',
+ },
+ {
+ header: _('ETA'),
+ width: 60,
+ sortable: true,
+ renderer: timeOrInf,
+ dataIndex: 'eta',
+ },
+ {
+ header: _('Ratio'),
+ hidden: true,
+ width: 60,
+ sortable: true,
+ renderer: availRenderer,
+ dataIndex: 'ratio',
+ },
+ {
+ header: _('Avail'),
+ hidden: true,
+ width: 60,
+ sortable: true,
+ renderer: availRenderer,
+ dataIndex: 'distributed_copies',
+ },
+ {
+ header: _('Added'),
+ hidden: true,
+ width: 80,
+ sortable: true,
+ renderer: fdate,
+ dataIndex: 'time_added',
+ },
+ {
+ header: _('Complete Seen'),
+ hidden: true,
+ width: 80,
+ sortable: true,
+ renderer: dateOrNever,
+ dataIndex: 'last_seen_complete',
+ },
+ {
+ header: _('Completed'),
+ hidden: true,
+ width: 80,
+ sortable: true,
+ renderer: dateOrNever,
+ dataIndex: 'completed_time',
+ },
+ {
+ header: _('Tracker'),
+ hidden: true,
+ width: 120,
+ sortable: true,
+ renderer: trackerRenderer,
+ dataIndex: 'tracker_host',
+ },
+ {
+ header: _('Download Folder'),
+ hidden: true,
+ width: 120,
+ sortable: true,
+ renderer: fplain,
+ dataIndex: 'download_location',
+ },
+ {
+ header: _('Owner'),
+ width: 80,
+ sortable: true,
+ renderer: fplain,
+ dataIndex: 'owner',
+ },
+ {
+ header: _('Public'),
+ hidden: true,
+ width: 80,
+ sortable: true,
+ renderer: fplain,
+ dataIndex: 'public',
+ },
+ {
+ header: _('Shared'),
+ hidden: true,
+ width: 80,
+ sortable: true,
+ renderer: fplain,
+ dataIndex: 'shared',
+ },
+ {
+ header: _('Downloaded'),
+ hidden: true,
+ width: 75,
+ sortable: true,
+ renderer: fsize,
+ dataIndex: 'total_done',
+ },
+ {
+ header: _('Uploaded'),
+ hidden: true,
+ width: 75,
+ sortable: true,
+ renderer: fsize,
+ dataIndex: 'total_uploaded',
+ },
+ {
+ header: _('Remaining'),
+ hidden: true,
+ width: 75,
+ sortable: true,
+ renderer: fsize,
+ dataIndex: 'total_remaining',
+ },
+ {
+ header: _('Down Limit'),
+ hidden: true,
+ width: 75,
+ sortable: true,
+ renderer: torrentLimitRenderer,
+ dataIndex: 'max_download_speed',
+ },
+ {
+ header: _('Up Limit'),
+ hidden: true,
+ width: 75,
+ sortable: true,
+ renderer: torrentLimitRenderer,
+ dataIndex: 'max_upload_speed',
+ },
+ {
+ header: _('Seeds:Peers'),
+ hidden: true,
+ width: 75,
+ sortable: true,
+ renderer: availRenderer,
+ dataIndex: 'seeds_peers_ratio',
+ },
+ {
+ header: _('Last Transfer'),
+ hidden: true,
+ width: 75,
+ sortable: true,
+ renderer: ftime,
+ dataIndex: 'time_since_transfer',
+ },
+ ],
+
+ meta: {
+ root: 'torrents',
+ idProperty: 'id',
+ fields: [
+ {
+ name: 'queue',
+ sortType: Deluge.data.SortTypes.asQueuePosition,
+ },
+ { name: 'name', sortType: Deluge.data.SortTypes.asName },
+ { name: 'total_wanted', type: 'int' },
+ { name: 'state' },
+ { name: 'progress', type: 'float' },
+ { name: 'num_seeds', type: 'int' },
+ { name: 'total_seeds', type: 'int' },
+ { name: 'num_peers', type: 'int' },
+ { name: 'total_peers', type: 'int' },
+ { name: 'download_payload_rate', type: 'int' },
+ { name: 'upload_payload_rate', type: 'int' },
+ { name: 'eta', type: 'int', sortType: etaSorter },
+ { name: 'ratio', type: 'float' },
+ { name: 'distributed_copies', type: 'float' },
+ { name: 'time_added', type: 'int' },
+ { name: 'tracker_host' },
+ { name: 'download_location' },
+ { name: 'total_done', type: 'int' },
+ { name: 'total_uploaded', type: 'int' },
+ { name: 'total_remaining', type: 'int' },
+ { name: 'max_download_speed', type: 'int' },
+ { name: 'max_upload_speed', type: 'int' },
+ { name: 'seeds_peers_ratio', type: 'float' },
+ { name: 'time_since_transfer', type: 'int' },
+ ],
+ },
+
+ keys: [
+ {
+ key: 'a',
+ ctrl: true,
+ stopEvent: true,
+ handler: function() {
+ deluge.torrents.getSelectionModel().selectAll();
+ },
+ },
+ {
+ key: [46],
+ stopEvent: true,
+ handler: function() {
+ ids = deluge.torrents.getSelectedIds();
+ deluge.removeWindow.show(ids);
+ },
+ },
+ ],
+
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ id: 'torrentGrid',
+ store: new Ext.data.JsonStore(this.meta),
+ columns: this.columns,
+ keys: this.keys,
+ region: 'center',
+ cls: 'deluge-torrents',
+ stripeRows: true,
+ autoExpandColumn: 'name',
+ autoExpandMin: 150,
+ deferredRender: false,
+ autoScroll: true,
+ stateful: true,
+ view: new Ext.ux.grid.BufferView({
+ rowHeight: 26,
+ scrollDelay: false,
+ }),
+ },
+ config
+ );
+ Deluge.TorrentGrid.superclass.constructor.call(this, config);
+ },
+
+ initComponent: function() {
+ Deluge.TorrentGrid.superclass.initComponent.call(this);
+ deluge.events.on('torrentsRemoved', this.onTorrentsRemoved, this);
+ deluge.events.on('disconnect', this.onDisconnect, this);
+
+ this.on('rowcontextmenu', function(grid, rowIndex, e) {
+ e.stopEvent();
+ var selection = grid.getSelectionModel();
+ if (!selection.isSelected(rowIndex)) {
+ selection.selectRow(rowIndex);
+ }
+ deluge.menus.torrent.showAt(e.getPoint());
+ });
+ },
+
+ /**
+ * Returns the record representing the torrent at the specified index.
+ *
+ * @param index {int} The row index of the torrent you wish to retrieve.
+ * @return {Ext.data.Record} The record representing the torrent.
+ */
+ getTorrent: function(index) {
+ return this.getStore().getAt(index);
+ },
+
+ /**
+ * Returns the currently selected record.
+ * @ return {Array/Ext.data.Record} The record(s) representing the rows
+ */
+ getSelected: function() {
+ return this.getSelectionModel().getSelected();
+ },
+
+ /**
+ * Returns the currently selected records.
+ */
+ getSelections: function() {
+ return this.getSelectionModel().getSelections();
+ },
+
+ /**
+ * Return the currently selected torrent id.
+ * @return {String} The currently selected id.
+ */
+ getSelectedId: function() {
+ return this.getSelectionModel().getSelected().id;
+ },
+
+ /**
+ * Return the currently selected torrent ids.
+ * @return {Array} The currently selected ids.
+ */
+ getSelectedIds: function() {
+ var ids = [];
+ Ext.each(this.getSelectionModel().getSelections(), function(r) {
+ ids.push(r.id);
+ });
+ return ids;
+ },
+
+ update: function(torrents, wipe) {
+ var store = this.getStore();
+
+ // Need to perform a complete reload of the torrent grid.
+ if (wipe) {
+ store.removeAll();
+ this.torrents = {};
+ }
+
+ var newTorrents = [];
+
+ // Update and add any new torrents.
+ for (var t in torrents) {
+ var torrent = torrents[t];
+
+ if (this.torrents[t]) {
+ var record = store.getById(t);
+ record.beginEdit();
+ for (var k in torrent) {
+ if (record.get(k) != torrent[k]) {
+ record.set(k, torrent[k]);
+ }
+ }
+ record.endEdit();
+ } else {
+ var record = new Deluge.data.Torrent(torrent);
+ record.id = t;
+ this.torrents[t] = 1;
+ newTorrents.push(record);
+ }
+ }
+ store.add(newTorrents);
+
+ // Remove any torrents that should not be in the store.
+ store.each(function(record) {
+ if (!torrents[record.id]) {
+ store.remove(record);
+ delete this.torrents[record.id];
+ }
+ }, this);
+ store.commitChanges();
+
+ var sortState = store.getSortState();
+ if (!sortState) return;
+ store.sort(sortState.field, sortState.direction);
+ },
+
+ // private
+ onDisconnect: function() {
+ this.getStore().removeAll();
+ this.torrents = {};
+ },
+
+ // private
+ onTorrentsRemoved: function(torrentIds) {
+ var selModel = this.getSelectionModel();
+ Ext.each(
+ torrentIds,
+ function(torrentId) {
+ var record = this.getStore().getById(torrentId);
+ if (selModel.isSelected(record)) {
+ selModel.deselectRow(this.getStore().indexOf(record));
+ }
+ this.getStore().remove(record);
+ delete this.torrents[torrentId];
+ },
+ this
+ );
+ },
+ });
+ deluge.torrents = new Deluge.TorrentGrid();
+})();
+/**
+ * Deluge.UI.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+/** Dummy translation arrays so Torrent states are available for gettext.js and Translators.
+ *
+ * All entries in deluge.common.TORRENT_STATE should be added here.
+ *
+ * No need to import these, just simply use the `_()` function around a status variable.
+ */
+var TORRENT_STATE_TRANSLATION = [
+ _('All'),
+ _('Active'),
+ _('Allocating'),
+ _('Checking'),
+ _('Downloading'),
+ _('Seeding'),
+ _('Paused'),
+ _('Checking'),
+ _('Queued'),
+ _('Error'),
+];
+
+/**
+ * @static
+ * @class Deluge.UI
+ * The controller for the whole interface, that ties all the components
+ * together and handles the 2 second poll.
+ */
+deluge.ui = {
+ errorCount: 0,
+
+ filters: null,
+
+ /**
+ * @description Create all the interface components, the json-rpc client
+ * and set up various events that the UI will utilise.
+ */
+ initialize: function() {
+ deluge.add = new Deluge.add.AddWindow();
+ deluge.details = new Deluge.details.DetailsPanel();
+ deluge.connectionManager = new Deluge.ConnectionManager();
+ deluge.editTrackers = new Deluge.EditTrackersWindow();
+ deluge.login = new Deluge.LoginWindow();
+ deluge.preferences = new Deluge.preferences.PreferencesWindow();
+ deluge.sidebar = new Deluge.Sidebar();
+ deluge.statusbar = new Deluge.Statusbar();
+ deluge.toolbar = new Deluge.Toolbar();
+
+ this.detailsPanel = new Ext.Panel({
+ id: 'detailsPanel',
+ cls: 'detailsPanel',
+ region: 'south',
+ split: true,
+ height: 215,
+ minSize: 100,
+ collapsible: true,
+ layout: 'fit',
+ items: [deluge.details],
+ });
+
+ this.MainPanel = new Ext.Panel({
+ id: 'mainPanel',
+ iconCls: 'x-deluge-main-panel',
+ layout: 'border',
+ border: false,
+ tbar: deluge.toolbar,
+ items: [deluge.sidebar, this.detailsPanel, deluge.torrents],
+ bbar: deluge.statusbar,
+ });
+
+ this.Viewport = new Ext.Viewport({
+ layout: 'fit',
+ items: [this.MainPanel],
+ });
+
+ deluge.events.on('connect', this.onConnect, this);
+ deluge.events.on('disconnect', this.onDisconnect, this);
+ deluge.events.on('PluginDisabledEvent', this.onPluginDisabled, this);
+ deluge.events.on('PluginEnabledEvent', this.onPluginEnabled, this);
+ deluge.client = new Ext.ux.util.RpcClient({
+ url: deluge.config.base + 'json',
+ });
+
+ // enable all the already active plugins
+ for (var plugin in Deluge.pluginStore) {
+ plugin = Deluge.createPlugin(plugin);
+ plugin.enable();
+ deluge.plugins[plugin.name] = plugin;
+ }
+
+ // Initialize quicktips so all the tooltip configs start working.
+ Ext.QuickTips.init();
+
+ deluge.client.on(
+ 'connected',
+ function(e) {
+ deluge.login.show();
+ },
+ this,
+ { single: true }
+ );
+
+ this.update = this.update.createDelegate(this);
+ this.checkConnection = this.checkConnection.createDelegate(this);
+
+ this.originalTitle = document.title;
+ },
+
+ checkConnection: function() {
+ deluge.client.web.connected({
+ success: this.onConnectionSuccess,
+ failure: this.onConnectionError,
+ scope: this,
+ });
+ },
+
+ update: function() {
+ var filters = deluge.sidebar.getFilterStates();
+ this.oldFilters = this.filters;
+ this.filters = filters;
+
+ deluge.client.web.update_ui(Deluge.Keys.Grid, filters, {
+ success: this.onUpdate,
+ failure: this.onUpdateError,
+ scope: this,
+ });
+ deluge.details.update();
+ },
+
+ onConnectionError: function(error) {},
+
+ onConnectionSuccess: function(result) {
+ deluge.statusbar.setStatus({
+ iconCls: 'x-deluge-statusbar icon-ok',
+ text: _('Connection restored'),
+ });
+ clearInterval(this.checking);
+ if (!result) {
+ deluge.connectionManager.show();
+ }
+ },
+
+ onUpdateError: function(error) {
+ if (this.errorCount == 2) {
+ Ext.MessageBox.show({
+ title: _('Lost Connection'),
+ msg: _('The connection to the webserver has been lost!'),
+ buttons: Ext.MessageBox.OK,
+ icon: Ext.MessageBox.ERROR,
+ });
+ deluge.events.fire('disconnect');
+ deluge.statusbar.setStatus({
+ text: _('Lost connection to webserver'),
+ });
+ this.checking = setInterval(this.checkConnection, 2000);
+ }
+ this.errorCount++;
+ },
+
+ /**
+ * @static
+ * @private
+ * Updates the various components in the interface.
+ */
+ onUpdate: function(data) {
+ if (!data['connected']) {
+ deluge.connectionManager.disconnect(true);
+ return;
+ }
+
+ if (deluge.config.show_session_speed) {
+ document.title =
+ 'D: ' +
+ fsize_short(data['stats'].download_rate, true) +
+ ' U: ' +
+ fsize_short(data['stats'].upload_rate, true) +
+ ' - ' +
+ this.originalTitle;
+ }
+ if (Ext.areObjectsEqual(this.filters, this.oldFilters)) {
+ deluge.torrents.update(data['torrents']);
+ } else {
+ deluge.torrents.update(data['torrents'], true);
+ }
+ deluge.statusbar.update(data['stats']);
+ deluge.sidebar.update(data['filters']);
+ this.errorCount = 0;
+ },
+
+ /**
+ * @static
+ * @private
+ * Start the Deluge UI polling the server and update the interface.
+ */
+ onConnect: function() {
+ if (!this.running) {
+ this.running = setInterval(this.update, 2000);
+ this.update();
+ }
+ deluge.client.web.get_plugins({
+ success: this.onGotPlugins,
+ scope: this,
+ });
+ },
+
+ /**
+ * @static
+ * @private
+ */
+ onDisconnect: function() {
+ this.stop();
+ },
+
+ onGotPlugins: function(plugins) {
+ Ext.each(
+ plugins.enabled_plugins,
+ function(plugin) {
+ if (deluge.plugins[plugin]) return;
+ deluge.client.web.get_plugin_resources(plugin, {
+ success: this.onGotPluginResources,
+ scope: this,
+ });
+ },
+ this
+ );
+ },
+
+ onPluginEnabled: function(pluginName) {
+ if (deluge.plugins[pluginName]) {
+ deluge.plugins[pluginName].enable();
+ } else {
+ deluge.client.web.get_plugin_resources(pluginName, {
+ success: this.onGotPluginResources,
+ scope: this,
+ });
+ }
+ },
+
+ onGotPluginResources: function(resources) {
+ var scripts = Deluge.debug
+ ? resources.debug_scripts
+ : resources.scripts;
+ Ext.each(
+ scripts,
+ function(script) {
+ Ext.ux.JSLoader({
+ url: deluge.config.base + script,
+ onLoad: this.onPluginLoaded,
+ pluginName: resources.name,
+ });
+ },
+ this
+ );
+ },
+
+ onPluginDisabled: function(pluginName) {
+ if (deluge.plugins[pluginName]) deluge.plugins[pluginName].disable();
+ },
+
+ onPluginLoaded: function(options) {
+ // This could happen if the plugin has multiple scripts
+ if (!Deluge.hasPlugin(options.pluginName)) return;
+
+ // Enable the plugin
+ plugin = Deluge.createPlugin(options.pluginName);
+ plugin.enable();
+ deluge.plugins[plugin.name] = plugin;
+ },
+
+ /**
+ * @static
+ * Stop the Deluge UI polling the server and clear the interface.
+ */
+ stop: function() {
+ if (this.running) {
+ clearInterval(this.running);
+ this.running = false;
+ deluge.torrents.getStore().removeAll();
+ }
+ },
+};
+
+Ext.onReady(function(e) {
+ deluge.ui.initialize();
+});
diff --git a/deluge/ui/web/js/deluge-all/.order b/deluge/ui/web/js/deluge-all/.order
new file mode 100644
index 0000000..4b6be43
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/.order
@@ -0,0 +1,2 @@
++ OptionsManager.js
++ StatusbarMenu.js
diff --git a/deluge/ui/web/js/deluge-all/AboutWindow.js b/deluge/ui/web/js/deluge-all/AboutWindow.js
new file mode 100644
index 0000000..5376d05
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/AboutWindow.js
@@ -0,0 +1,129 @@
+/**
+ * Deluge.AboutWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Ext.namespace('Deluge.about');
+
+/**
+ * @class Deluge.about.AboutWindow
+ * @extends Ext.Window
+ */
+Deluge.about.AboutWindow = Ext.extend(Ext.Window, {
+ id: 'AboutWindow',
+ title: _('About Deluge'),
+ height: 330,
+ width: 270,
+ iconCls: 'x-deluge-main-panel',
+ resizable: false,
+ plain: true,
+ layout: {
+ type: 'vbox',
+ align: 'center',
+ },
+ buttonAlign: 'center',
+
+ initComponent: function() {
+ Deluge.about.AboutWindow.superclass.initComponent.call(this);
+ this.addEvents({
+ build_ready: true,
+ });
+
+ var self = this;
+ var libtorrent = function() {
+ deluge.client.core.get_libtorrent_version({
+ success: function(lt_version) {
+ comment += '<br/>' + _('libtorrent:') + ' ' + lt_version;
+ Ext.getCmp('about_comment').setText(comment, false);
+ self.fireEvent('build_ready');
+ },
+ });
+ };
+
+ var client_version = deluge.version;
+
+ var comment =
+ _(
+ 'A peer-to-peer file sharing program\nutilizing the BitTorrent protocol.'
+ ).replace('\n', '<br/>') +
+ '<br/><br/>' +
+ _('Client:') +
+ ' ' +
+ client_version +
+ '<br/>';
+ deluge.client.web.connected({
+ success: function(connected) {
+ if (connected) {
+ deluge.client.daemon.get_version({
+ success: function(server_version) {
+ comment +=
+ _('Server:') + ' ' + server_version + '<br/>';
+ libtorrent();
+ },
+ });
+ } else {
+ this.fireEvent('build_ready');
+ }
+ },
+ failure: function() {
+ this.fireEvent('build_ready');
+ },
+ scope: this,
+ });
+
+ this.add([
+ {
+ xtype: 'box',
+ style: 'padding-top: 5px',
+ height: 80,
+ width: 240,
+ cls: 'x-deluge-logo',
+ hideLabel: true,
+ },
+ {
+ xtype: 'label',
+ style: 'padding-top: 10px; font-weight: bold; font-size: 16px;',
+ text: _('Deluge') + ' ' + client_version,
+ },
+ {
+ xtype: 'label',
+ id: 'about_comment',
+ style: 'padding-top: 10px; text-align:center; font-size: 12px;',
+ html: comment,
+ },
+ {
+ xtype: 'label',
+ style: 'padding-top: 10px; font-size: 10px;',
+ text: _('Copyright 2007-2018 Deluge Team'),
+ },
+ {
+ xtype: 'label',
+ style: 'padding-top: 5px; font-size: 12px;',
+ html:
+ '<a href="https://deluge-torrent.org" target="_blank">deluge-torrent.org</a>',
+ },
+ ]);
+ this.addButton(_('Close'), this.onCloseClick, this);
+ },
+
+ show: function() {
+ this.on('build_ready', function() {
+ Deluge.about.AboutWindow.superclass.show.call(this);
+ });
+ },
+
+ onCloseClick: function() {
+ this.close();
+ },
+});
+
+Ext.namespace('Deluge');
+
+Deluge.About = function() {
+ new Deluge.about.AboutWindow().show();
+};
diff --git a/deluge/ui/web/js/deluge-all/AddConnectionWindow.js b/deluge/ui/web/js/deluge-all/AddConnectionWindow.js
new file mode 100644
index 0000000..6d26370
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/AddConnectionWindow.js
@@ -0,0 +1,117 @@
+/**
+ * Deluge.AddConnectionWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+/**
+ * @class Deluge.AddConnectionWindow
+ * @extends Ext.Window
+ */
+Deluge.AddConnectionWindow = Ext.extend(Ext.Window, {
+ title: _('Add Connection'),
+ iconCls: 'x-deluge-add-window-icon',
+
+ layout: 'fit',
+ width: 300,
+ height: 195,
+ constrainHeader: true,
+ bodyStyle: 'padding: 10px 5px;',
+ closeAction: 'hide',
+
+ initComponent: function() {
+ Deluge.AddConnectionWindow.superclass.initComponent.call(this);
+
+ this.addEvents('hostadded');
+
+ this.addButton(_('Close'), this.hide, this);
+ this.addButton(_('Add'), this.onAddClick, this);
+
+ this.on('hide', this.onHide, this);
+
+ this.form = this.add({
+ xtype: 'form',
+ defaultType: 'textfield',
+ baseCls: 'x-plain',
+ labelWidth: 60,
+ items: [
+ {
+ fieldLabel: _('Host:'),
+ labelSeparator: '',
+ name: 'host',
+ anchor: '75%',
+ value: '',
+ },
+ {
+ xtype: 'spinnerfield',
+ fieldLabel: _('Port:'),
+ labelSeparator: '',
+ name: 'port',
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 65535,
+ },
+ value: '58846',
+ anchor: '40%',
+ },
+ {
+ fieldLabel: _('Username:'),
+ labelSeparator: '',
+ name: 'username',
+ anchor: '75%',
+ value: '',
+ },
+ {
+ fieldLabel: _('Password:'),
+ labelSeparator: '',
+ anchor: '75%',
+ name: 'password',
+ inputType: 'password',
+ value: '',
+ },
+ ],
+ });
+ },
+
+ onAddClick: function() {
+ var values = this.form.getForm().getValues();
+ deluge.client.web.add_host(
+ values.host,
+ Number(values.port),
+ values.username,
+ values.password,
+ {
+ success: function(result) {
+ if (!result[0]) {
+ Ext.MessageBox.show({
+ title: _('Error'),
+ msg: String.format(
+ _('Unable to add host: {0}'),
+ result[1]
+ ),
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ icon: Ext.MessageBox.ERROR,
+ iconCls: 'x-deluge-icon-error',
+ });
+ } else {
+ this.fireEvent('hostadded');
+ }
+ this.hide();
+ },
+ scope: this,
+ }
+ );
+ },
+
+ onHide: function() {
+ this.form.getForm().reset();
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/AddTrackerWindow.js b/deluge/ui/web/js/deluge-all/AddTrackerWindow.js
new file mode 100644
index 0000000..c9c835d
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/AddTrackerWindow.js
@@ -0,0 +1,94 @@
+/**
+ * Deluge.AddTrackerWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+// Custom VType validator for tracker urls
+var trackerUrlTest = /(((^https?)|(^udp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
+Ext.apply(Ext.form.VTypes, {
+ trackerUrl: function(val, field) {
+ return trackerUrlTest.test(val);
+ },
+ trackerUrlText: 'Not a valid tracker url',
+});
+
+/**
+ * @class Deluge.AddTrackerWindow
+ * @extends Ext.Window
+ */
+Deluge.AddTrackerWindow = Ext.extend(Ext.Window, {
+ title: _('Add Tracker'),
+ layout: 'fit',
+ width: 375,
+ height: 150,
+ plain: true,
+ closable: true,
+ resizable: false,
+ constrainHeader: true,
+ bodyStyle: 'padding: 5px',
+ buttonAlign: 'right',
+ closeAction: 'hide',
+ iconCls: 'x-deluge-edit-trackers',
+
+ initComponent: function() {
+ Deluge.AddTrackerWindow.superclass.initComponent.call(this);
+
+ this.addButton(_('Cancel'), this.onCancelClick, this);
+ this.addButton(_('Add'), this.onAddClick, this);
+ this.addEvents('add');
+
+ this.form = this.add({
+ xtype: 'form',
+ defaultType: 'textarea',
+ baseCls: 'x-plain',
+ labelWidth: 55,
+ items: [
+ {
+ fieldLabel: _('Trackers:'),
+ labelSeparator: '',
+ name: 'trackers',
+ anchor: '100%',
+ },
+ ],
+ });
+ },
+
+ onAddClick: function() {
+ var trackers = this.form
+ .getForm()
+ .findField('trackers')
+ .getValue();
+ trackers = trackers.split('\n');
+
+ var cleaned = [];
+ Ext.each(
+ trackers,
+ function(tracker) {
+ if (Ext.form.VTypes.trackerUrl(tracker)) {
+ cleaned.push(tracker);
+ }
+ },
+ this
+ );
+ this.fireEvent('add', cleaned);
+ this.hide();
+ this.form
+ .getForm()
+ .findField('trackers')
+ .setValue('');
+ },
+
+ onCancelClick: function() {
+ this.form
+ .getForm()
+ .findField('trackers')
+ .setValue('');
+ this.hide();
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/Client.js b/deluge/ui/web/js/deluge-all/Client.js
new file mode 100644
index 0000000..bcabbae
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/Client.js
@@ -0,0 +1,199 @@
+/**
+ * Deluge.Client.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Ext.ux.util');
+
+/**
+ * A class that connects to a json-rpc resource and adds the available
+ * methods as functions to the class instance.
+ * @class Ext.ux.util.RpcClient
+ * @namespace Ext.ux.util
+ */
+Ext.ux.util.RpcClient = Ext.extend(Ext.util.Observable, {
+ _components: [],
+
+ _methods: [],
+
+ _requests: {},
+
+ _url: null,
+
+ _optionKeys: ['scope', 'success', 'failure'],
+
+ /**
+ * @event connected
+ * Fires when the client has retrieved the list of methods from the server.
+ * @param {Ext.ux.util.RpcClient} this
+ */
+ constructor: function(config) {
+ Ext.ux.util.RpcClient.superclass.constructor.call(this, config);
+ this._url = config.url || null;
+ this._id = 0;
+
+ this.addEvents(
+ // raw events
+ 'connected',
+ 'error'
+ );
+ this.reloadMethods();
+ },
+
+ reloadMethods: function() {
+ this._execute('system.listMethods', {
+ success: this._setMethods,
+ scope: this,
+ });
+ },
+
+ _execute: function(method, options) {
+ options = options || {};
+ options.params = options.params || [];
+ options.id = this._id;
+
+ var request = Ext.encode({
+ method: method,
+ params: options.params,
+ id: options.id,
+ });
+ this._id++;
+
+ return Ext.Ajax.request({
+ url: this._url,
+ method: 'POST',
+ success: this._onSuccess,
+ failure: this._onFailure,
+ scope: this,
+ jsonData: request,
+ options: options,
+ });
+ },
+
+ _onFailure: function(response, requestOptions) {
+ var options = requestOptions.options;
+ errorObj = {
+ id: options.id,
+ result: null,
+ error: {
+ msg: 'HTTP: ' + response.status + ' ' + response.statusText,
+ code: 255,
+ },
+ };
+
+ this.fireEvent('error', errorObj, response, requestOptions);
+
+ if (Ext.type(options.failure) != 'function') return;
+ if (options.scope) {
+ options.failure.call(
+ options.scope,
+ errorObj,
+ response,
+ requestOptions
+ );
+ } else {
+ options.failure(errorObj, response, requestOptions);
+ }
+ },
+
+ _onSuccess: function(response, requestOptions) {
+ var responseObj = Ext.decode(response.responseText);
+ var options = requestOptions.options;
+ if (responseObj.error) {
+ this.fireEvent('error', responseObj, response, requestOptions);
+
+ if (Ext.type(options.failure) != 'function') return;
+ if (options.scope) {
+ options.failure.call(
+ options.scope,
+ responseObj,
+ response,
+ requestOptions
+ );
+ } else {
+ options.failure(responseObj, response, requestOptions);
+ }
+ } else {
+ if (Ext.type(options.success) != 'function') return;
+ if (options.scope) {
+ options.success.call(
+ options.scope,
+ responseObj.result,
+ responseObj,
+ response,
+ requestOptions
+ );
+ } else {
+ options.success(
+ responseObj.result,
+ responseObj,
+ response,
+ requestOptions
+ );
+ }
+ }
+ },
+
+ _parseArgs: function(args) {
+ var params = [];
+ Ext.each(args, function(arg) {
+ params.push(arg);
+ });
+
+ var options = params[params.length - 1];
+ if (Ext.type(options) == 'object') {
+ var keys = Ext.keys(options),
+ isOption = false;
+
+ Ext.each(this._optionKeys, function(key) {
+ if (keys.indexOf(key) > -1) isOption = true;
+ });
+
+ if (isOption) {
+ params.remove(options);
+ } else {
+ options = {};
+ }
+ } else {
+ options = {};
+ }
+ options.params = params;
+ return options;
+ },
+
+ _setMethods: function(methods) {
+ var components = {},
+ self = this;
+
+ Ext.each(methods, function(method) {
+ var parts = method.split('.');
+ var component = components[parts[0]] || {};
+
+ var fn = function() {
+ var options = self._parseArgs(arguments);
+ return self._execute(method, options);
+ };
+ component[parts[1]] = fn;
+ components[parts[0]] = component;
+ });
+
+ for (var name in components) {
+ self[name] = components[name];
+ }
+ Ext.each(
+ this._components,
+ function(component) {
+ if (!component in components) {
+ delete this[component];
+ }
+ },
+ this
+ );
+ this._components = Ext.keys(components);
+ this.fireEvent('connected', this);
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/ConnectionManager.js b/deluge/ui/web/js/deluge-all/ConnectionManager.js
new file mode 100644
index 0000000..001e46b
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/ConnectionManager.js
@@ -0,0 +1,429 @@
+/**
+ * Deluge.ConnectionManager.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Deluge.ConnectionManager = Ext.extend(Ext.Window, {
+ layout: 'fit',
+ width: 300,
+ height: 220,
+ bodyStyle: 'padding: 10px 5px;',
+ buttonAlign: 'right',
+ closeAction: 'hide',
+ closable: true,
+ plain: true,
+ constrainHeader: true,
+ title: _('Connection Manager'),
+ iconCls: 'x-deluge-connect-window-icon',
+
+ initComponent: function() {
+ Deluge.ConnectionManager.superclass.initComponent.call(this);
+ this.on('hide', this.onHide, this);
+ this.on('show', this.onShow, this);
+
+ deluge.events.on('login', this.onLogin, this);
+ deluge.events.on('logout', this.onLogout, this);
+
+ this.addButton(_('Close'), this.onClose, this);
+ this.addButton(_('Connect'), this.onConnect, this);
+
+ this.list = new Ext.list.ListView({
+ store: new Ext.data.ArrayStore({
+ fields: [
+ { name: 'status', mapping: 4 },
+ { name: 'host', mapping: 1 },
+ { name: 'port', mapping: 2 },
+ { name: 'user', mapping: 3 },
+ { name: 'version', mapping: 5 },
+ ],
+ id: 0,
+ }),
+ columns: [
+ {
+ header: _('Status'),
+ width: 0.24,
+ sortable: true,
+ tpl: new Ext.XTemplate(
+ '<tpl if="status == \'Online\'">',
+ _('Online'),
+ '</tpl>',
+ '<tpl if="status == \'Offline\'">',
+ _('Offline'),
+ '</tpl>',
+ '<tpl if="status == \'Connected\'">',
+ _('Connected'),
+ '</tpl>'
+ ),
+ dataIndex: 'status',
+ },
+ {
+ id: 'host',
+ header: _('Host'),
+ width: 0.51,
+ sortable: true,
+ tpl: '{user}@{host}:{port}',
+ dataIndex: 'host',
+ },
+ {
+ header: _('Version'),
+ width: 0.25,
+ sortable: true,
+ tpl: '<tpl if="version">{version}</tpl>',
+ dataIndex: 'version',
+ },
+ ],
+ singleSelect: true,
+ listeners: {
+ selectionchange: { fn: this.onSelectionChanged, scope: this },
+ },
+ });
+
+ this.panel = this.add({
+ autoScroll: true,
+ items: [this.list],
+ bbar: new Ext.Toolbar({
+ buttons: [
+ {
+ id: 'cm-add',
+ cls: 'x-btn-text-icon',
+ text: _('Add'),
+ iconCls: 'icon-add',
+ handler: this.onAddClick,
+ scope: this,
+ },
+ {
+ id: 'cm-edit',
+ cls: 'x-btn-text-icon',
+ text: _('Edit'),
+ iconCls: 'icon-edit',
+ handler: this.onEditClick,
+ scope: this,
+ },
+ {
+ id: 'cm-remove',
+ cls: 'x-btn-text-icon',
+ text: _('Remove'),
+ iconCls: 'icon-remove',
+ handler: this.onRemoveClick,
+ disabled: true,
+ scope: this,
+ },
+ '->',
+ {
+ id: 'cm-stop',
+ cls: 'x-btn-text-icon',
+ text: _('Stop Daemon'),
+ iconCls: 'icon-error',
+ handler: this.onStopClick,
+ disabled: true,
+ scope: this,
+ },
+ ],
+ }),
+ });
+ this.update = this.update.createDelegate(this);
+ },
+
+ /**
+ * Check to see if the the web interface is currently connected
+ * to a Deluge Daemon and show the Connection Manager if not.
+ */
+ checkConnected: function() {
+ deluge.client.web.connected({
+ success: function(connected) {
+ if (connected) {
+ deluge.events.fire('connect');
+ } else {
+ this.show();
+ }
+ },
+ scope: this,
+ });
+ },
+
+ disconnect: function(show) {
+ deluge.events.fire('disconnect');
+ if (show) {
+ if (this.isVisible()) return;
+ this.show();
+ }
+ },
+
+ loadHosts: function() {
+ deluge.client.web.get_hosts({
+ success: this.onGetHosts,
+ scope: this,
+ });
+ },
+
+ update: function() {
+ this.list.getStore().each(function(r) {
+ deluge.client.web.get_host_status(r.id, {
+ success: this.onGetHostStatus,
+ scope: this,
+ });
+ }, this);
+ },
+
+ /**
+ * Updates the buttons in the Connection Manager UI according to the
+ * passed in records host state.
+ * @param {Ext.data.Record} record The hosts record to update the UI for
+ */
+ updateButtons: function(record) {
+ var button = this.buttons[1],
+ status = record.get('status');
+
+ // Update the Connect/Disconnect button
+ button.enable();
+ if (status.toLowerCase() == 'connected') {
+ button.setText(_('Disconnect'));
+ } else {
+ button.setText(_('Connect'));
+ if (status.toLowerCase() != 'online') button.disable();
+ }
+
+ // Update the Stop/Start Daemon button
+ if (
+ status.toLowerCase() == 'connected' ||
+ status.toLowerCase() == 'online'
+ ) {
+ this.stopHostButton.enable();
+ this.stopHostButton.setText(_('Stop Daemon'));
+ } else {
+ if (
+ record.get('host') == '127.0.0.1' ||
+ record.get('host') == 'localhost'
+ ) {
+ this.stopHostButton.enable();
+ this.stopHostButton.setText(_('Start Daemon'));
+ } else {
+ this.stopHostButton.disable();
+ }
+ }
+ },
+
+ // private
+ onAddClick: function(button, e) {
+ if (!this.addWindow) {
+ this.addWindow = new Deluge.AddConnectionWindow();
+ this.addWindow.on('hostadded', this.onHostChange, this);
+ }
+ this.addWindow.show();
+ },
+
+ // private
+ onEditClick: function(button, e) {
+ var connection = this.list.getSelectedRecords()[0];
+ if (!connection) return;
+
+ if (!this.editWindow) {
+ this.editWindow = new Deluge.EditConnectionWindow();
+ this.editWindow.on('hostedited', this.onHostChange, this);
+ }
+ this.editWindow.show(connection);
+ },
+
+ // private
+ onHostChange: function() {
+ this.loadHosts();
+ },
+
+ // private
+ onClose: function(e) {
+ this.hide();
+ },
+
+ // private
+ onConnect: function(e) {
+ var selected = this.list.getSelectedRecords()[0];
+ if (!selected) return;
+
+ var me = this;
+ var disconnect = function() {
+ deluge.client.web.disconnect({
+ success: function(result) {
+ this.update(this);
+ deluge.events.fire('disconnect');
+ },
+ scope: me,
+ });
+ };
+
+ if (selected.get('status').toLowerCase() == 'connected') {
+ disconnect();
+ } else {
+ if (
+ this.list
+ .getStore()
+ .find('status', 'Connected', 0, false, false) > -1
+ ) {
+ disconnect();
+ }
+
+ var id = selected.id;
+ deluge.client.web.connect(id, {
+ success: function(methods) {
+ deluge.client.reloadMethods();
+ deluge.client.on(
+ 'connected',
+ function(e) {
+ deluge.events.fire('connect');
+ },
+ this,
+ { single: true }
+ );
+ },
+ });
+ this.hide();
+ }
+ },
+
+ // private
+ onGetHosts: function(hosts) {
+ this.list.getStore().loadData(hosts);
+ Ext.each(
+ hosts,
+ function(host) {
+ deluge.client.web.get_host_status(host[0], {
+ success: this.onGetHostStatus,
+ scope: this,
+ });
+ },
+ this
+ );
+ },
+
+ // private
+ onGetHostStatus: function(host) {
+ var record = this.list.getStore().getById(host[0]);
+ record.set('status', host[1]);
+ record.set('version', host[2]);
+ record.commit();
+ var selected = this.list.getSelectedRecords()[0];
+ if (!selected) return;
+ if (selected == record) this.updateButtons(record);
+ },
+
+ // private
+ onHide: function() {
+ if (this.running) window.clearInterval(this.running);
+ },
+
+ // private
+ onLogin: function() {
+ if (deluge.config.first_login) {
+ Ext.MessageBox.confirm(
+ _('Change Default Password'),
+ _(
+ 'We recommend changing the default password.<br><br>Would you like to change it now?'
+ ),
+ function(res) {
+ this.checkConnected();
+ if (res == 'yes') {
+ deluge.preferences.show();
+ deluge.preferences.selectPage('Interface');
+ }
+ deluge.client.web.set_config({ first_login: false });
+ },
+ this
+ );
+ } else {
+ this.checkConnected();
+ }
+ },
+
+ // private
+ onLogout: function() {
+ this.disconnect();
+ if (!this.hidden && this.rendered) {
+ this.hide();
+ }
+ },
+
+ // private
+ onRemoveClick: function(button) {
+ var connection = this.list.getSelectedRecords()[0];
+ if (!connection) return;
+
+ deluge.client.web.remove_host(connection.id, {
+ success: function(result) {
+ if (!result) {
+ Ext.MessageBox.show({
+ title: _('Error'),
+ msg: result[1],
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ icon: Ext.MessageBox.ERROR,
+ iconCls: 'x-deluge-icon-error',
+ });
+ } else {
+ this.list.getStore().remove(connection);
+ }
+ },
+ scope: this,
+ });
+ },
+
+ // private
+ onSelectionChanged: function(list, selections) {
+ if (selections[0]) {
+ this.editHostButton.enable();
+ this.removeHostButton.enable();
+ this.stopHostButton.enable();
+ this.stopHostButton.setText(_('Stop Daemon'));
+ this.updateButtons(this.list.getRecord(selections[0]));
+ } else {
+ this.editHostButton.disable();
+ this.removeHostButton.disable();
+ this.stopHostButton.disable();
+ }
+ },
+
+ // FIXME: Find out why this is being fired twice
+ // private
+ onShow: function() {
+ if (!this.addHostButton) {
+ var bbar = this.panel.getBottomToolbar();
+ this.addHostButton = bbar.items.get('cm-add');
+ this.editHostButton = bbar.items.get('cm-edit');
+ this.removeHostButton = bbar.items.get('cm-remove');
+ this.stopHostButton = bbar.items.get('cm-stop');
+ }
+ this.loadHosts();
+ if (this.running) return;
+ this.running = window.setInterval(this.update, 2000, this);
+ },
+
+ // private
+ onStopClick: function(button, e) {
+ var connection = this.list.getSelectedRecords()[0];
+ if (!connection) return;
+
+ if (connection.get('status') == 'Offline') {
+ // This means we need to start the daemon
+ deluge.client.web.start_daemon(connection.get('port'));
+ } else {
+ // This means we need to stop the daemon
+ deluge.client.web.stop_daemon(connection.id, {
+ success: function(result) {
+ if (!result[0]) {
+ Ext.MessageBox.show({
+ title: _('Error'),
+ msg: result[1],
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ icon: Ext.MessageBox.ERROR,
+ iconCls: 'x-deluge-icon-error',
+ });
+ }
+ },
+ });
+ }
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/Deluge.js b/deluge/ui/web/js/deluge-all/Deluge.js
new file mode 100644
index 0000000..31b9947
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/Deluge.js
@@ -0,0 +1,186 @@
+/**
+ * Deluge.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+// Setup the state manager
+Ext.state.Manager.setProvider(
+ new Ext.state.CookieProvider({
+ /**
+ * By default, cookies will expire after 7 days. Provide
+ * an expiry date 10 years in the future to approximate
+ * a cookie that does not expire.
+ */
+ expires: new Date(
+ new Date().getTime() + 1000 * 60 * 60 * 24 * 365 * 10
+ ),
+ })
+);
+
+// Add some additional functions to ext and setup some of the
+// configurable parameters
+Ext.apply(Ext, {
+ escapeHTML: function(text) {
+ text = String(text)
+ .replace('<', '&lt;')
+ .replace('>', '&gt;');
+ return text.replace('&', '&amp;');
+ },
+
+ isObjectEmpty: function(obj) {
+ for (var i in obj) {
+ return false;
+ }
+ return true;
+ },
+
+ areObjectsEqual: function(obj1, obj2) {
+ var equal = true;
+ if (!obj1 || !obj2) return false;
+ for (var i in obj1) {
+ if (obj1[i] != obj2[i]) {
+ equal = false;
+ }
+ }
+ return equal;
+ },
+
+ keys: function(obj) {
+ var keys = [];
+ for (var i in obj)
+ if (obj.hasOwnProperty(i)) {
+ keys.push(i);
+ }
+ return keys;
+ },
+
+ values: function(obj) {
+ var values = [];
+ for (var i in obj) {
+ if (obj.hasOwnProperty(i)) {
+ values.push(obj[i]);
+ }
+ }
+ return values;
+ },
+
+ splat: function(obj) {
+ var type = Ext.type(obj);
+ return type ? (type != 'array' ? [obj] : obj) : [];
+ },
+});
+Ext.getKeys = Ext.keys;
+Ext.BLANK_IMAGE_URL = deluge.config.base + 'images/s.gif';
+Ext.USE_NATIVE_JSON = true;
+
+// Create the Deluge namespace
+Ext.apply(Deluge, {
+ // private
+ pluginStore: {},
+
+ // private
+ progressTpl:
+ '<div class="x-progress-wrap x-progress-renderered">' +
+ '<div class="x-progress-inner">' +
+ '<div style="width: {2}px" class="x-progress-bar">' +
+ '<div style="z-index: 99; width: {3}px" class="x-progress-text">' +
+ '<div style="width: {1}px;">{0}</div>' +
+ '</div>' +
+ '</div>' +
+ '<div class="x-progress-text x-progress-text-back">' +
+ '<div style="width: {1}px;">{0}</div>' +
+ '</div>' +
+ '</div>' +
+ '</div>',
+
+ /**
+ * A method to create a progress bar that can be used by renderers
+ * to display a bar within a grid or tree.
+ * @param {Number} progress The bars progress
+ * @param {Number} width The width of the bar
+ * @param {String} text The text to display on the bar
+ * @param {Number} modified Amount to subtract from the width allowing for fixes
+ */
+ progressBar: function(progress, width, text, modifier) {
+ modifier = Ext.value(modifier, 10);
+ var progressWidth = ((width / 100.0) * progress).toFixed(0);
+ var barWidth = progressWidth - 1;
+ var textWidth =
+ progressWidth - modifier > 0 ? progressWidth - modifier : 0;
+ return String.format(
+ Deluge.progressTpl,
+ text,
+ width,
+ barWidth,
+ textWidth
+ );
+ },
+
+ /**
+ * Constructs a new instance of the specified plugin.
+ * @param {String} name The plugin name to create
+ */
+ createPlugin: function(name) {
+ return new Deluge.pluginStore[name]();
+ },
+
+ /**
+ * Check to see if a plugin has been registered.
+ * @param {String} name The plugin name to check
+ */
+ hasPlugin: function(name) {
+ return Deluge.pluginStore[name] ? true : false;
+ },
+
+ /**
+ * Register a plugin with the Deluge interface.
+ * @param {String} name The plugin name to register
+ * @param {Plugin} plugin The plugin to register
+ */
+ registerPlugin: function(name, plugin) {
+ Deluge.pluginStore[name] = plugin;
+ },
+});
+
+// Setup a space for plugins to insert themselves
+deluge.plugins = {};
+
+// Hinting for gettext_gen.py
+// _('Skip')
+// _('Low')
+// _('Normal')
+// _('High')
+// _('Mixed')
+FILE_PRIORITY = {
+ 0: 'Skip',
+ 1: 'Low',
+ 2: 'Low',
+ 3: 'Low',
+ 4: 'Normal',
+ 5: 'High',
+ 6: 'High',
+ 7: 'High',
+ 9: 'Mixed',
+ Skip: 0,
+ Low: 1,
+ Normal: 4,
+ High: 7,
+ Mixed: 9,
+};
+
+FILE_PRIORITY_CSS = {
+ 0: 'x-no-download',
+ 1: 'x-low-download',
+ 2: 'x-low-download',
+ 3: 'x-low-download',
+ 4: 'x-normal-download',
+ 5: 'x-high-download',
+ 6: 'x-high-download',
+ 7: 'x-high-download',
+ 9: 'x-mixed-download',
+};
diff --git a/deluge/ui/web/js/deluge-all/EditConnectionWindow.js b/deluge/ui/web/js/deluge-all/EditConnectionWindow.js
new file mode 100644
index 0000000..63bd305
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/EditConnectionWindow.js
@@ -0,0 +1,134 @@
+/**
+ * Deluge.EditConnectionWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+/**
+ * @class Deluge.EditConnectionWindow
+ * @extends Ext.Window
+ */
+Deluge.EditConnectionWindow = Ext.extend(Ext.Window, {
+ title: _('Edit Connection'),
+ iconCls: 'x-deluge-add-window-icon',
+
+ layout: 'fit',
+ width: 300,
+ height: 195,
+ constrainHeader: true,
+ bodyStyle: 'padding: 10px 5px;',
+ closeAction: 'hide',
+
+ initComponent: function() {
+ Deluge.EditConnectionWindow.superclass.initComponent.call(this);
+
+ this.addEvents('hostedited');
+
+ this.addButton(_('Close'), this.hide, this);
+ this.addButton(_('Edit'), this.onEditClick, this);
+
+ this.on('hide', this.onHide, this);
+
+ this.form = this.add({
+ xtype: 'form',
+ defaultType: 'textfield',
+ baseCls: 'x-plain',
+ labelWidth: 60,
+ items: [
+ {
+ fieldLabel: _('Host:'),
+ labelSeparator: '',
+ name: 'host',
+ anchor: '75%',
+ value: '',
+ },
+ {
+ xtype: 'spinnerfield',
+ fieldLabel: _('Port:'),
+ labelSeparator: '',
+ name: 'port',
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 0,
+ minValue: 0,
+ maxValue: 65535,
+ },
+ anchor: '40%',
+ value: 58846,
+ },
+ {
+ fieldLabel: _('Username:'),
+ labelSeparator: '',
+ name: 'username',
+ anchor: '75%',
+ value: '',
+ },
+ {
+ fieldLabel: _('Password:'),
+ labelSeparator: '',
+ anchor: '75%',
+ name: 'password',
+ inputType: 'password',
+ value: '',
+ },
+ ],
+ });
+ },
+
+ show: function(connection) {
+ Deluge.EditConnectionWindow.superclass.show.call(this);
+
+ this.form
+ .getForm()
+ .findField('host')
+ .setValue(connection.get('host'));
+ this.form
+ .getForm()
+ .findField('port')
+ .setValue(connection.get('port'));
+ this.form
+ .getForm()
+ .findField('username')
+ .setValue(connection.get('user'));
+ this.host_id = connection.id;
+ },
+
+ onEditClick: function() {
+ var values = this.form.getForm().getValues();
+ deluge.client.web.edit_host(
+ this.host_id,
+ values.host,
+ Number(values.port),
+ values.username,
+ values.password,
+ {
+ success: function(result) {
+ if (!result) {
+ console.log(result);
+ Ext.MessageBox.show({
+ title: _('Error'),
+ msg: String.format(_('Unable to edit host')),
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ icon: Ext.MessageBox.ERROR,
+ iconCls: 'x-deluge-icon-error',
+ });
+ } else {
+ this.fireEvent('hostedited');
+ }
+ this.hide();
+ },
+ scope: this,
+ }
+ );
+ },
+
+ onHide: function() {
+ this.form.getForm().reset();
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/EditTrackerWindow.js b/deluge/ui/web/js/deluge-all/EditTrackerWindow.js
new file mode 100644
index 0000000..82bc32c
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/EditTrackerWindow.js
@@ -0,0 +1,83 @@
+/**
+ * Deluge.EditTrackerWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+/**
+ * @class Deluge.EditTrackerWindow
+ * @extends Ext.Window
+ */
+Deluge.EditTrackerWindow = Ext.extend(Ext.Window, {
+ title: _('Edit Tracker'),
+ layout: 'fit',
+ width: 375,
+ height: 110,
+ plain: true,
+ closable: true,
+ resizable: false,
+ constrainHeader: true,
+ bodyStyle: 'padding: 5px',
+ buttonAlign: 'right',
+ closeAction: 'hide',
+ iconCls: 'x-deluge-edit-trackers',
+
+ initComponent: function() {
+ Deluge.EditTrackerWindow.superclass.initComponent.call(this);
+
+ this.addButton(_('Cancel'), this.onCancelClick, this);
+ this.addButton(_('Save'), this.onSaveClick, this);
+ this.on('hide', this.onHide, this);
+
+ this.form = this.add({
+ xtype: 'form',
+ defaultType: 'textfield',
+ baseCls: 'x-plain',
+ labelWidth: 55,
+ items: [
+ {
+ fieldLabel: _('Tracker:'),
+ labelSeparator: '',
+ name: 'tracker',
+ anchor: '100%',
+ },
+ ],
+ });
+ },
+
+ show: function(record) {
+ Deluge.EditTrackerWindow.superclass.show.call(this);
+
+ this.record = record;
+ this.form
+ .getForm()
+ .findField('tracker')
+ .setValue(record.data['url']);
+ },
+
+ onCancelClick: function() {
+ this.hide();
+ },
+
+ onHide: function() {
+ this.form
+ .getForm()
+ .findField('tracker')
+ .setValue('');
+ },
+
+ onSaveClick: function() {
+ var url = this.form
+ .getForm()
+ .findField('tracker')
+ .getValue();
+ this.record.set('url', url);
+ this.record.commit();
+ this.hide();
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/EditTrackersWindow.js b/deluge/ui/web/js/deluge-all/EditTrackersWindow.js
new file mode 100644
index 0000000..47ffa86
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/EditTrackersWindow.js
@@ -0,0 +1,239 @@
+/**
+ * Deluge.EditTrackers.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+/**
+ * @class Deluge.EditTrackerWindow
+ * @extends Ext.Window
+ */
+Deluge.EditTrackersWindow = Ext.extend(Ext.Window, {
+ title: _('Edit Trackers'),
+ layout: 'fit',
+ width: 350,
+ height: 220,
+ plain: true,
+ closable: true,
+ resizable: true,
+ constrainHeader: true,
+
+ bodyStyle: 'padding: 5px',
+ buttonAlign: 'right',
+ closeAction: 'hide',
+ iconCls: 'x-deluge-edit-trackers',
+
+ initComponent: function() {
+ Deluge.EditTrackersWindow.superclass.initComponent.call(this);
+
+ this.addButton(_('Cancel'), this.onCancelClick, this);
+ this.addButton(_('OK'), this.onOkClick, this);
+ this.addEvents('save');
+
+ this.on('show', this.onShow, this);
+ this.on('save', this.onSave, this);
+
+ this.addWindow = new Deluge.AddTrackerWindow();
+ this.addWindow.on('add', this.onAddTrackers, this);
+ this.editWindow = new Deluge.EditTrackerWindow();
+
+ this.list = new Ext.list.ListView({
+ store: new Ext.data.JsonStore({
+ root: 'trackers',
+ fields: ['tier', 'url'],
+ }),
+ columns: [
+ {
+ header: _('Tier'),
+ width: 0.1,
+ dataIndex: 'tier',
+ },
+ {
+ header: _('Tracker'),
+ width: 0.9,
+ dataIndex: 'url',
+ },
+ ],
+ columnSort: {
+ sortClasses: ['', ''],
+ },
+ stripeRows: true,
+ singleSelect: true,
+ listeners: {
+ dblclick: { fn: this.onListNodeDblClicked, scope: this },
+ selectionchange: { fn: this.onSelect, scope: this },
+ },
+ });
+
+ this.panel = this.add({
+ items: [this.list],
+ autoScroll: true,
+ bbar: new Ext.Toolbar({
+ items: [
+ {
+ text: _('Up'),
+ iconCls: 'icon-up',
+ handler: this.onUpClick,
+ scope: this,
+ },
+ {
+ text: _('Down'),
+ iconCls: 'icon-down',
+ handler: this.onDownClick,
+ scope: this,
+ },
+ '->',
+ {
+ text: _('Add'),
+ iconCls: 'icon-add',
+ handler: this.onAddClick,
+ scope: this,
+ },
+ {
+ text: _('Edit'),
+ iconCls: 'icon-edit-trackers',
+ handler: this.onEditClick,
+ scope: this,
+ },
+ {
+ text: _('Remove'),
+ iconCls: 'icon-remove',
+ handler: this.onRemoveClick,
+ scope: this,
+ },
+ ],
+ }),
+ });
+ },
+
+ onAddClick: function() {
+ this.addWindow.show();
+ },
+
+ onAddTrackers: function(trackers) {
+ var store = this.list.getStore();
+ Ext.each(
+ trackers,
+ function(tracker) {
+ var duplicate = false,
+ heightestTier = -1;
+ store.each(function(record) {
+ if (record.get('tier') > heightestTier) {
+ heightestTier = record.get('tier');
+ }
+ if (tracker == record.get('tracker')) {
+ duplicate = true;
+ return false;
+ }
+ }, this);
+ if (duplicate) return;
+ store.add(
+ new store.recordType({
+ tier: heightestTier + 1,
+ url: tracker,
+ })
+ );
+ },
+ this
+ );
+ },
+
+ onCancelClick: function() {
+ this.hide();
+ },
+
+ onEditClick: function() {
+ var selected = this.list.getSelectedRecords()[0];
+ if (!selected) return;
+ this.editWindow.show(selected);
+ },
+
+ onHide: function() {
+ this.list.getStore().removeAll();
+ },
+
+ onListNodeDblClicked: function(list, index, node, e) {
+ this.editWindow.show(this.list.getRecord(node));
+ },
+
+ onOkClick: function() {
+ var trackers = [];
+ this.list.getStore().each(function(record) {
+ trackers.push({
+ tier: record.get('tier'),
+ url: record.get('url'),
+ });
+ }, this);
+
+ deluge.client.core.set_torrent_trackers(this.torrentId, trackers, {
+ failure: this.onSaveFail,
+ scope: this,
+ });
+
+ this.hide();
+ },
+
+ onRemoveClick: function() {
+ // Remove from the grid
+ var selected = this.list.getSelectedRecords()[0];
+ if (!selected) return;
+ this.list.getStore().remove(selected);
+ },
+
+ onRequestComplete: function(status) {
+ this.list.getStore().loadData(status);
+ this.list.getStore().sort('tier', 'ASC');
+ },
+
+ onSaveFail: function() {},
+
+ onSelect: function(list) {
+ if (list.getSelectionCount()) {
+ this.panel
+ .getBottomToolbar()
+ .items.get(4)
+ .enable();
+ }
+ },
+
+ onShow: function() {
+ this.panel
+ .getBottomToolbar()
+ .items.get(4)
+ .disable();
+ var r = deluge.torrents.getSelected();
+ this.torrentId = r.id;
+ deluge.client.core.get_torrent_status(r.id, ['trackers'], {
+ success: this.onRequestComplete,
+ scope: this,
+ });
+ },
+
+ onDownClick: function() {
+ var r = this.list.getSelectedRecords()[0];
+ if (!r) return;
+
+ r.set('tier', r.get('tier') + 1);
+ r.store.sort('tier', 'ASC');
+ r.store.commitChanges();
+
+ this.list.select(r.store.indexOf(r));
+ },
+
+ onUpClick: function() {
+ var r = this.list.getSelectedRecords()[0];
+ if (!r) return;
+
+ if (r.get('tier') == 0) return;
+ r.set('tier', r.get('tier') - 1);
+ r.store.sort('tier', 'ASC');
+ r.store.commitChanges();
+
+ this.list.select(r.store.indexOf(r));
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/EventsManager.js b/deluge/ui/web/js/deluge-all/EventsManager.js
new file mode 100644
index 0000000..1714339
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/EventsManager.js
@@ -0,0 +1,118 @@
+/**
+ * Deluge.EventsManager.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+/**
+ * @class Deluge.EventsManager
+ * @extends Ext.util.Observable
+ * <p>Deluge.EventsManager is instantated as <tt>deluge.events</tt> and can be used by components of the UI to fire global events</p>
+ * Class for holding global events that occur within the UI.
+ */
+Deluge.EventsManager = Ext.extend(Ext.util.Observable, {
+ constructor: function() {
+ this.toRegister = [];
+ this.on('login', this.onLogin, this);
+ Deluge.EventsManager.superclass.constructor.call(this);
+ },
+
+ /**
+ * Append an event handler to this object.
+ */
+ addListener: function(eventName, fn, scope, o) {
+ this.addEvents(eventName);
+ if (/[A-Z]/.test(eventName.substring(0, 1))) {
+ if (!deluge.client) {
+ this.toRegister.push(eventName);
+ } else {
+ deluge.client.web.register_event_listener(eventName);
+ }
+ }
+ Deluge.EventsManager.superclass.addListener.call(
+ this,
+ eventName,
+ fn,
+ scope,
+ o
+ );
+ },
+
+ getEvents: function() {
+ deluge.client.web.get_events({
+ success: this.onGetEventsSuccess,
+ failure: this.onGetEventsFailure,
+ scope: this,
+ });
+ },
+
+ /**
+ * Starts the EventsManagerManager checking for events.
+ */
+ start: function() {
+ Ext.each(this.toRegister, function(eventName) {
+ deluge.client.web.register_event_listener(eventName);
+ });
+ this.running = true;
+ this.errorCount = 0;
+ this.getEvents();
+ },
+
+ /**
+ * Stops the EventsManagerManager checking for events.
+ */
+ stop: function() {
+ this.running = false;
+ },
+
+ // private
+ onLogin: function() {
+ this.start();
+ },
+
+ onGetEventsSuccess: function(events) {
+ if (!this.running) return;
+ if (events) {
+ Ext.each(
+ events,
+ function(event) {
+ var name = event[0],
+ args = event[1];
+ args.splice(0, 0, name);
+ this.fireEvent.apply(this, args);
+ },
+ this
+ );
+ }
+ this.getEvents();
+ },
+
+ // private
+ onGetEventsFailure: function(result, error) {
+ // the request timed out or we had a communication failure
+ if (!this.running) return;
+ if (!error.isTimeout && this.errorCount++ >= 3) {
+ this.stop();
+ return;
+ }
+ this.getEvents();
+ },
+});
+
+/**
+ * Appends an event handler to this object (shorthand for {@link #addListener})
+ * @method
+ */
+Deluge.EventsManager.prototype.on = Deluge.EventsManager.prototype.addListener;
+
+/**
+ * Fires the specified event with the passed parameters (minus the
+ * event name).
+ * @method
+ */
+Deluge.EventsManager.prototype.fire = Deluge.EventsManager.prototype.fireEvent;
+deluge.events = new Deluge.EventsManager();
diff --git a/deluge/ui/web/js/deluge-all/FileBrowser.js b/deluge/ui/web/js/deluge-all/FileBrowser.js
new file mode 100644
index 0000000..72962a6
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/FileBrowser.js
@@ -0,0 +1,43 @@
+/**
+ * Deluge.FileBrowser.js
+ *
+ * Copyright (c) Damien Churchill 2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Ext.namespace('Deluge');
+Deluge.FileBrowser = Ext.extend(Ext.Window, {
+ title: _('File Browser'),
+
+ width: 500,
+ height: 400,
+
+ initComponent: function() {
+ Deluge.FileBrowser.superclass.initComponent.call(this);
+
+ this.add({
+ xtype: 'toolbar',
+ items: [
+ {
+ text: _('Back'),
+ iconCls: 'icon-back',
+ },
+ {
+ text: _('Forward'),
+ iconCls: 'icon-forward',
+ },
+ {
+ text: _('Up'),
+ iconCls: 'icon-up',
+ },
+ {
+ text: _('Home'),
+ iconCls: 'icon-home',
+ },
+ ],
+ });
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/FilterPanel.js b/deluge/ui/web/js/deluge-all/FilterPanel.js
new file mode 100644
index 0000000..2362dbb
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/FilterPanel.js
@@ -0,0 +1,175 @@
+/**
+ * Deluge.FilterPanel.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+/**
+ * @class Deluge.FilterPanel
+ * @extends Ext.list.ListView
+ */
+Deluge.FilterPanel = Ext.extend(Ext.Panel, {
+ autoScroll: true,
+
+ border: false,
+
+ show_zero: null,
+
+ initComponent: function() {
+ Deluge.FilterPanel.superclass.initComponent.call(this);
+ this.filterType = this.initialConfig.filter;
+ var title = '';
+ if (this.filterType == 'state') {
+ title = _('States');
+ } else if (this.filterType == 'tracker_host') {
+ title = _('Trackers');
+ } else if (this.filterType == 'owner') {
+ title = _('Owner');
+ } else if (this.filterType == 'label') {
+ title = _('Labels');
+ } else {
+ (title = this.filterType.replace('_', ' ')),
+ (parts = title.split(' ')),
+ (title = '');
+ Ext.each(parts, function(p) {
+ fl = p.substring(0, 1).toUpperCase();
+ title += fl + p.substring(1) + ' ';
+ });
+ }
+ this.setTitle(_(title));
+
+ if (Deluge.FilterPanel.templates[this.filterType]) {
+ var tpl = Deluge.FilterPanel.templates[this.filterType];
+ } else {
+ var tpl =
+ '<div class="x-deluge-filter x-deluge-{filter:lowercase}">{filter} ({count})</div>';
+ }
+
+ this.list = this.add({
+ xtype: 'listview',
+ singleSelect: true,
+ hideHeaders: true,
+ reserveScrollOffset: true,
+ store: new Ext.data.ArrayStore({
+ idIndex: 0,
+ fields: ['filter', 'count'],
+ }),
+ columns: [
+ {
+ id: 'filter',
+ sortable: false,
+ tpl: tpl,
+ dataIndex: 'filter',
+ },
+ ],
+ });
+ this.relayEvents(this.list, ['selectionchange']);
+ },
+
+ /**
+ * Return the currently selected filter state
+ * @returns {String} the current filter state
+ */
+ getState: function() {
+ if (!this.list.getSelectionCount()) return;
+
+ var state = this.list.getSelectedRecords()[0];
+ if (!state) return;
+ if (state.id == 'All') return;
+ return state.id;
+ },
+
+ /**
+ * Return the current states in the filter
+ */
+ getStates: function() {
+ return this.states;
+ },
+
+ /**
+ * Return the Store for the ListView of the FilterPanel
+ * @returns {Ext.data.Store} the ListView store
+ */
+ getStore: function() {
+ return this.list.getStore();
+ },
+
+ /**
+ * Update the states in the FilterPanel
+ */
+ updateStates: function(states) {
+ this.states = {};
+ Ext.each(
+ states,
+ function(state) {
+ this.states[state[0]] = state[1];
+ },
+ this
+ );
+
+ var show_zero =
+ this.show_zero == null
+ ? deluge.config.sidebar_show_zero
+ : this.show_zero;
+ if (!show_zero) {
+ var newStates = [];
+ Ext.each(states, function(state) {
+ if (state[1] > 0 || state[0] == 'All') {
+ newStates.push(state);
+ }
+ });
+ states = newStates;
+ }
+
+ var store = this.getStore();
+ var filters = {};
+ Ext.each(
+ states,
+ function(s, i) {
+ var record = store.getById(s[0]);
+ if (!record) {
+ record = new store.recordType({
+ filter: s[0],
+ count: s[1],
+ });
+ record.id = s[0];
+ store.insert(i, record);
+ }
+ record.beginEdit();
+ record.set('filter', _(s[0]));
+ record.set('count', s[1]);
+ record.endEdit();
+ filters[s[0]] = true;
+ },
+ this
+ );
+
+ store.each(function(record) {
+ if (filters[record.id]) return;
+ store.remove(record);
+ var selected = this.list.getSelectedRecords()[0];
+ if (!selected) return;
+ if (selected.id == record.id) {
+ this.list.select(0);
+ }
+ }, this);
+
+ store.commitChanges();
+
+ if (!this.list.getSelectionCount()) {
+ this.list.select(0);
+ }
+ },
+});
+
+Deluge.FilterPanel.templates = {
+ tracker_host:
+ '<div class="x-deluge-filter" style="background-image: url(' +
+ deluge.config.base +
+ 'tracker/{filter});">{filter} ({count})</div>',
+};
diff --git a/deluge/ui/web/js/deluge-all/Formatters.js b/deluge/ui/web/js/deluge-all/Formatters.js
new file mode 100644
index 0000000..a511f34
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/Formatters.js
@@ -0,0 +1,181 @@
+/**
+ * Deluge.Formatters.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+/**
+ * A collection of functions for string formatting values.
+ * @class Deluge.Formatters
+ * @author Damien Churchill <damoxc@gmail.com>
+ * @version 1.3
+ * @singleton
+ */
+Deluge.Formatters = {
+ /**
+ * Formats a date string in the date representation of the current locale,
+ * based on the systems timezone.
+ *
+ * @param {Number} timestamp time in seconds since the Epoch.
+ * @return {String} a string in the date representation of the current locale
+ * or "" if seconds < 0.
+ */
+ date: function(timestamp) {
+ function zeroPad(num, count) {
+ var numZeropad = num + '';
+ while (numZeropad.length < count) {
+ numZeropad = '0' + numZeropad;
+ }
+ return numZeropad;
+ }
+ timestamp = timestamp * 1000;
+ var date = new Date(timestamp);
+ return String.format(
+ '{0}/{1}/{2} {3}:{4}:{5}',
+ zeroPad(date.getDate(), 2),
+ zeroPad(date.getMonth() + 1, 2),
+ date.getFullYear(),
+ zeroPad(date.getHours(), 2),
+ zeroPad(date.getMinutes(), 2),
+ zeroPad(date.getSeconds(), 2)
+ );
+ },
+
+ /**
+ * Formats the bytes value into a string with KiB, MiB or GiB units.
+ *
+ * @param {Number} bytes the filesize in bytes
+ * @param {Boolean} showZero pass in true to displays 0 values
+ * @return {String} formatted string with KiB, MiB or GiB units.
+ */
+ size: function(bytes, showZero) {
+ if (!bytes && !showZero) return '';
+ bytes = bytes / 1024.0;
+
+ if (bytes < 1024) {
+ return bytes.toFixed(1) + ' KiB';
+ } else {
+ bytes = bytes / 1024;
+ }
+
+ if (bytes < 1024) {
+ return bytes.toFixed(1) + ' MiB';
+ } else {
+ bytes = bytes / 1024;
+ }
+
+ return bytes.toFixed(1) + ' GiB';
+ },
+
+ /**
+ * Formats the bytes value into a string with K, M or G units.
+ *
+ * @param {Number} bytes the filesize in bytes
+ * @param {Boolean} showZero pass in true to displays 0 values
+ * @return {String} formatted string with K, M or G units.
+ */
+ sizeShort: function(bytes, showZero) {
+ if (!bytes && !showZero) return '';
+ bytes = bytes / 1024.0;
+
+ if (bytes < 1024) {
+ return bytes.toFixed(1) + ' K';
+ } else {
+ bytes = bytes / 1024;
+ }
+
+ if (bytes < 1024) {
+ return bytes.toFixed(1) + ' M';
+ } else {
+ bytes = bytes / 1024;
+ }
+
+ return bytes.toFixed(1) + ' G';
+ },
+
+ /**
+ * Formats a string to display a transfer speed utilizing {@link #size}
+ *
+ * @param {Number} bytes the number of bytes per second
+ * @param {Boolean} showZero pass in true to displays 0 values
+ * @return {String} formatted string with KiB, MiB or GiB units.
+ */
+ speed: function(bytes, showZero) {
+ return !bytes && !showZero ? '' : fsize(bytes, showZero) + '/s';
+ },
+
+ /**
+ * Formats a string to show time in a human readable form.
+ *
+ * @param {Number} time the number of seconds
+ * @return {String} a formatted time string. will return '' if seconds == 0
+ */
+ timeRemaining: function(time) {
+ if (time <= 0) {
+ return '&infin;';
+ }
+ time = time.toFixed(0);
+ if (time < 60) {
+ return time + 's';
+ } else {
+ time = time / 60;
+ }
+
+ if (time < 60) {
+ var minutes = Math.floor(time);
+ var seconds = Math.round(60 * (time - minutes));
+ if (seconds > 0) {
+ return minutes + 'm ' + seconds + 's';
+ } else {
+ return minutes + 'm';
+ }
+ } else {
+ time = time / 60;
+ }
+
+ if (time < 24) {
+ var hours = Math.floor(time);
+ var minutes = Math.round(60 * (time - hours));
+ if (minutes > 0) {
+ return hours + 'h ' + minutes + 'm';
+ } else {
+ return hours + 'h';
+ }
+ } else {
+ time = time / 24;
+ }
+
+ var days = Math.floor(time);
+ var hours = Math.round(24 * (time - days));
+ if (hours > 0) {
+ return days + 'd ' + hours + 'h';
+ } else {
+ return days + 'd';
+ }
+ },
+
+ /**
+ * Simply returns the value untouched, for when no formatting is required.
+ *
+ * @param {Mixed} value the value to be displayed
+ * @return the untouched value.
+ */
+ plain: function(value) {
+ return value;
+ },
+
+ cssClassEscape: function(value) {
+ return value.toLowerCase().replace('.', '_');
+ },
+};
+var fsize = Deluge.Formatters.size;
+var fsize_short = Deluge.Formatters.sizeShort;
+var fspeed = Deluge.Formatters.speed;
+var ftime = Deluge.Formatters.timeRemaining;
+var fdate = Deluge.Formatters.date;
+var fplain = Deluge.Formatters.plain;
+Ext.util.Format.cssClassEscape = Deluge.Formatters.cssClassEscape;
diff --git a/deluge/ui/web/js/deluge-all/Keys.js b/deluge/ui/web/js/deluge-all/Keys.js
new file mode 100644
index 0000000..25cf38b
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/Keys.js
@@ -0,0 +1,138 @@
+/**
+ * Deluge.Keys.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+/**
+ * @description The torrent status keys that are commonly used around the UI.
+ * @class Deluge.Keys
+ * @singleton
+ */
+Deluge.Keys = {
+ /**
+ * Keys that are used within the torrent grid.
+ * <pre>['queue', 'name', 'total_wanted', 'state', 'progress', 'num_seeds',
+ * 'total_seeds', 'num_peers', 'total_peers', 'download_payload_rate',
+ * 'upload_payload_rate', 'eta', 'ratio', 'distributed_copies',
+ * 'is_auto_managed', 'time_added', 'tracker_host', 'download_location', 'last_seen_complete',
+ * 'total_done', 'total_uploaded', 'max_download_speed', 'max_upload_speed',
+ * 'seeds_peers_ratio', 'total_remaining', 'completed_time', 'time_since_transfer']</pre>
+ */
+ Grid: [
+ 'queue',
+ 'name',
+ 'total_wanted',
+ 'state',
+ 'progress',
+ 'num_seeds',
+ 'total_seeds',
+ 'num_peers',
+ 'total_peers',
+ 'download_payload_rate',
+ 'upload_payload_rate',
+ 'eta',
+ 'ratio',
+ 'distributed_copies',
+ 'is_auto_managed',
+ 'time_added',
+ 'tracker_host',
+ 'download_location',
+ 'last_seen_complete',
+ 'total_done',
+ 'total_uploaded',
+ 'max_download_speed',
+ 'max_upload_speed',
+ 'seeds_peers_ratio',
+ 'total_remaining',
+ 'completed_time',
+ 'time_since_transfer',
+ ],
+
+ /**
+ * Keys used in the status tab of the statistics panel.
+ * These get updated to include the keys in {@link #Grid}.
+ * <pre>['total_done', 'total_payload_download', 'total_uploaded',
+ * 'total_payload_upload', 'next_announce', 'tracker_status', 'num_pieces',
+ * 'piece_length', 'is_auto_managed', 'active_time', 'seeding_time', 'time_since_transfer',
+ * 'seed_rank', 'last_seen_complete', 'completed_time', 'owner', 'public', 'shared']</pre>
+ */
+ Status: [
+ 'total_done',
+ 'total_payload_download',
+ 'total_uploaded',
+ 'total_payload_upload',
+ 'next_announce',
+ 'tracker_status',
+ 'num_pieces',
+ 'piece_length',
+ 'is_auto_managed',
+ 'active_time',
+ 'seeding_time',
+ 'time_since_transfer',
+ 'seed_rank',
+ 'last_seen_complete',
+ 'completed_time',
+ 'owner',
+ 'public',
+ 'shared',
+ ],
+
+ /**
+ * Keys used in the files tab of the statistics panel.
+ * <pre>['files', 'file_progress', 'file_priorities']</pre>
+ */
+ Files: ['files', 'file_progress', 'file_priorities'],
+
+ /**
+ * Keys used in the peers tab of the statistics panel.
+ * <pre>['peers']</pre>
+ */
+ Peers: ['peers'],
+
+ /**
+ * Keys used in the details tab of the statistics panel.
+ */
+ Details: [
+ 'name',
+ 'download_location',
+ 'total_size',
+ 'num_files',
+ 'message',
+ 'tracker_host',
+ 'comment',
+ 'creator',
+ ],
+
+ /**
+ * Keys used in the options tab of the statistics panel.
+ * <pre>['max_download_speed', 'max_upload_speed', 'max_connections', 'max_upload_slots',
+ * 'is_auto_managed', 'stop_at_ratio', 'stop_ratio', 'remove_at_ratio', 'private',
+ * 'prioritize_first_last']</pre>
+ */
+ Options: [
+ 'max_download_speed',
+ 'max_upload_speed',
+ 'max_connections',
+ 'max_upload_slots',
+ 'is_auto_managed',
+ 'stop_at_ratio',
+ 'stop_ratio',
+ 'remove_at_ratio',
+ 'private',
+ 'prioritize_first_last',
+ 'move_completed',
+ 'move_completed_path',
+ 'super_seeding',
+ ],
+};
+
+// Merge the grid and status keys together as the status keys contain all the
+// grid ones.
+Ext.each(Deluge.Keys.Grid, function(key) {
+ Deluge.Keys.Status.push(key);
+});
diff --git a/deluge/ui/web/js/deluge-all/LoginWindow.js b/deluge/ui/web/js/deluge-all/LoginWindow.js
new file mode 100644
index 0000000..964f5ff
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/LoginWindow.js
@@ -0,0 +1,134 @@
+/**
+ * Deluge.LoginWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Deluge.LoginWindow = Ext.extend(Ext.Window, {
+ firstShow: true,
+ bodyStyle: 'padding: 10px 5px;',
+ buttonAlign: 'center',
+ closable: false,
+ closeAction: 'hide',
+ iconCls: 'x-deluge-login-window-icon',
+ layout: 'fit',
+ modal: true,
+ plain: true,
+ resizable: false,
+ title: _('Login'),
+ width: 300,
+ height: 120,
+
+ initComponent: function() {
+ Deluge.LoginWindow.superclass.initComponent.call(this);
+ this.on('show', this.onShow, this);
+
+ this.addButton({
+ text: _('Login'),
+ handler: this.onLogin,
+ scope: this,
+ });
+
+ this.form = this.add({
+ xtype: 'form',
+ baseCls: 'x-plain',
+ labelWidth: 120,
+ labelAlign: 'right',
+ defaults: { width: 110 },
+ defaultType: 'textfield',
+ });
+
+ this.passwordField = this.form.add({
+ xtype: 'textfield',
+ fieldLabel: _('Password:'),
+ labelSeparator: '',
+ grow: true,
+ growMin: '110',
+ growMax: '145',
+ id: '_password',
+ name: 'password',
+ inputType: 'password',
+ });
+ this.passwordField.on('specialkey', this.onSpecialKey, this);
+ },
+
+ logout: function() {
+ deluge.events.fire('logout');
+ deluge.client.auth.delete_session({
+ success: function(result) {
+ this.show(true);
+ },
+ scope: this,
+ });
+ },
+
+ show: function(skipCheck) {
+ if (this.firstShow) {
+ deluge.client.on('error', this.onClientError, this);
+ this.firstShow = false;
+ }
+
+ if (skipCheck) {
+ return Deluge.LoginWindow.superclass.show.call(this);
+ }
+
+ deluge.client.auth.check_session({
+ success: function(result) {
+ if (result) {
+ deluge.events.fire('login');
+ } else {
+ this.show(true);
+ }
+ },
+ failure: function(result) {
+ this.show(true);
+ },
+ scope: this,
+ });
+ },
+
+ onSpecialKey: function(field, e) {
+ if (e.getKey() == 13) this.onLogin();
+ },
+
+ onLogin: function() {
+ var passwordField = this.passwordField;
+ deluge.client.auth.login(passwordField.getValue(), {
+ success: function(result) {
+ if (result) {
+ deluge.events.fire('login');
+ this.hide();
+ passwordField.setRawValue('');
+ } else {
+ Ext.MessageBox.show({
+ title: _('Login Failed'),
+ msg: _('You entered an incorrect password'),
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ fn: function() {
+ passwordField.focus(true, 10);
+ },
+ icon: Ext.MessageBox.WARNING,
+ iconCls: 'x-deluge-icon-warning',
+ });
+ }
+ },
+ scope: this,
+ });
+ },
+
+ onClientError: function(errorObj, response, requestOptions) {
+ if (errorObj.error.code == 1) {
+ deluge.events.fire('logout');
+ this.show(true);
+ }
+ },
+
+ onShow: function() {
+ this.passwordField.focus(true, 300);
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/Menus.js b/deluge/ui/web/js/deluge-all/Menus.js
new file mode 100644
index 0000000..529c6cc
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/Menus.js
@@ -0,0 +1,388 @@
+/**
+ * Deluge.Menus.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+deluge.menus = {
+ onTorrentActionSetOpt: function(item, e) {
+ var ids = deluge.torrents.getSelectedIds();
+ var action = item.initialConfig.torrentAction;
+ var opts = {};
+ opts[action[0]] = action[1];
+ deluge.client.core.set_torrent_options(ids, opts);
+ },
+
+ onTorrentActionMethod: function(item, e) {
+ var ids = deluge.torrents.getSelectedIds();
+ var action = item.initialConfig.torrentAction;
+ deluge.client.core[action](ids, {
+ success: function() {
+ deluge.ui.update();
+ },
+ });
+ },
+
+ onTorrentActionShow: function(item, e) {
+ var ids = deluge.torrents.getSelectedIds();
+ var action = item.initialConfig.torrentAction;
+ switch (action) {
+ case 'edit_trackers':
+ deluge.editTrackers.show();
+ break;
+ case 'remove':
+ deluge.removeWindow.show(ids);
+ break;
+ case 'move':
+ deluge.moveStorage.show(ids);
+ break;
+ }
+ },
+};
+
+deluge.menus.torrent = new Ext.menu.Menu({
+ id: 'torrentMenu',
+ items: [
+ {
+ torrentAction: 'pause_torrent',
+ text: _('Pause'),
+ iconCls: 'icon-pause',
+ handler: deluge.menus.onTorrentActionMethod,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: 'resume_torrent',
+ text: _('Resume'),
+ iconCls: 'icon-resume',
+ handler: deluge.menus.onTorrentActionMethod,
+ scope: deluge.menus,
+ },
+ '-',
+ {
+ text: _('Options'),
+ iconCls: 'icon-options',
+ hideOnClick: false,
+ menu: new Ext.menu.Menu({
+ items: [
+ {
+ text: _('D/L Speed Limit'),
+ iconCls: 'x-deluge-downloading',
+ hideOnClick: false,
+ menu: new Ext.menu.Menu({
+ items: [
+ {
+ torrentAction: ['max_download_speed', 5],
+ text: _('5 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_download_speed', 10],
+ text: _('10 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_download_speed', 30],
+ text: _('30 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_download_speed', 80],
+ text: _('80 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_download_speed', 300],
+ text: _('300 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_download_speed', -1],
+ text: _('Unlimited'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ ],
+ }),
+ },
+ {
+ text: _('U/L Speed Limit'),
+ iconCls: 'x-deluge-seeding',
+ hideOnClick: false,
+ menu: new Ext.menu.Menu({
+ items: [
+ {
+ torrentAction: ['max_upload_speed', 5],
+ text: _('5 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_speed', 10],
+ text: _('10 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_speed', 30],
+ text: _('30 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_speed', 80],
+ text: _('80 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_speed', 300],
+ text: _('300 KiB/s'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_speed', -1],
+ text: _('Unlimited'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ ],
+ }),
+ },
+ {
+ text: _('Connection Limit'),
+ iconCls: 'x-deluge-connections',
+ hideOnClick: false,
+ menu: new Ext.menu.Menu({
+ items: [
+ {
+ torrentAction: ['max_connections', 50],
+ text: '50',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_connections', 100],
+ text: '100',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_connections', 200],
+ text: '200',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_connections', 300],
+ text: '300',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_connections', 500],
+ text: '500',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_connections', -1],
+ text: _('Unlimited'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ ],
+ }),
+ },
+ {
+ text: _('Upload Slot Limit'),
+ iconCls: 'icon-upload-slots',
+ hideOnClick: false,
+ menu: new Ext.menu.Menu({
+ items: [
+ {
+ torrentAction: ['max_upload_slots', 0],
+ text: '0',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_slots', 1],
+ text: '1',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_slots', 2],
+ text: '2',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_slots', 3],
+ text: '3',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_slots', 5],
+ text: '5',
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['max_upload_slots', -1],
+ text: _('Unlimited'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ ],
+ }),
+ },
+ {
+ id: 'auto_managed',
+ text: _('Auto Managed'),
+ hideOnClick: false,
+ menu: new Ext.menu.Menu({
+ items: [
+ {
+ torrentAction: ['auto_managed', true],
+ text: _('On'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: ['auto_managed', false],
+ text: _('Off'),
+ handler: deluge.menus.onTorrentActionSetOpt,
+ scope: deluge.menus,
+ },
+ ],
+ }),
+ },
+ ],
+ }),
+ },
+ '-',
+ {
+ text: _('Queue'),
+ iconCls: 'icon-queue',
+ hideOnClick: false,
+ menu: new Ext.menu.Menu({
+ items: [
+ {
+ torrentAction: 'queue_top',
+ text: _('Top'),
+ iconCls: 'icon-top',
+ handler: deluge.menus.onTorrentActionMethod,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: 'queue_up',
+ text: _('Up'),
+ iconCls: 'icon-up',
+ handler: deluge.menus.onTorrentActionMethod,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: 'queue_down',
+ text: _('Down'),
+ iconCls: 'icon-down',
+ handler: deluge.menus.onTorrentActionMethod,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: 'queue_bottom',
+ text: _('Bottom'),
+ iconCls: 'icon-bottom',
+ handler: deluge.menus.onTorrentActionMethod,
+ scope: deluge.menus,
+ },
+ ],
+ }),
+ },
+ '-',
+ {
+ torrentAction: 'force_reannounce',
+ text: _('Update Tracker'),
+ iconCls: 'icon-update-tracker',
+ handler: deluge.menus.onTorrentActionMethod,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: 'edit_trackers',
+ text: _('Edit Trackers'),
+ iconCls: 'icon-edit-trackers',
+ handler: deluge.menus.onTorrentActionShow,
+ scope: deluge.menus,
+ },
+ '-',
+ {
+ torrentAction: 'remove',
+ text: _('Remove Torrent'),
+ iconCls: 'icon-remove',
+ handler: deluge.menus.onTorrentActionShow,
+ scope: deluge.menus,
+ },
+ '-',
+ {
+ torrentAction: 'force_recheck',
+ text: _('Force Recheck'),
+ iconCls: 'icon-recheck',
+ handler: deluge.menus.onTorrentActionMethod,
+ scope: deluge.menus,
+ },
+ {
+ torrentAction: 'move',
+ text: _('Move Download Folder'),
+ iconCls: 'icon-move',
+ handler: deluge.menus.onTorrentActionShow,
+ scope: deluge.menus,
+ },
+ ],
+});
+
+deluge.menus.filePriorities = new Ext.menu.Menu({
+ id: 'filePrioritiesMenu',
+ items: [
+ {
+ id: 'expandAll',
+ text: _('Expand All'),
+ iconCls: 'icon-expand-all',
+ },
+ '-',
+ {
+ id: 'skip',
+ text: _('Skip'),
+ iconCls: 'icon-do-not-download',
+ filePriority: FILE_PRIORITY['Skip'],
+ },
+ {
+ id: 'low',
+ text: _('Low'),
+ iconCls: 'icon-low',
+ filePriority: FILE_PRIORITY['Low'],
+ },
+ {
+ id: 'normal',
+ text: _('Normal'),
+ iconCls: 'icon-normal',
+ filePriority: FILE_PRIORITY['Normal'],
+ },
+ {
+ id: 'high',
+ text: _('High'),
+ iconCls: 'icon-high',
+ filePriority: FILE_PRIORITY['High'],
+ },
+ ],
+});
diff --git a/deluge/ui/web/js/deluge-all/MoveStorage.js b/deluge/ui/web/js/deluge-all/MoveStorage.js
new file mode 100644
index 0000000..208031f
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/MoveStorage.js
@@ -0,0 +1,85 @@
+/**
+ * Deluge.MoveStorage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Ext.namespace('Deluge');
+Deluge.MoveStorage = Ext.extend(Ext.Window, {
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ title: _('Move Download Folder'),
+ width: 375,
+ height: 110,
+ layout: 'fit',
+ buttonAlign: 'right',
+ closeAction: 'hide',
+ closable: true,
+ iconCls: 'x-deluge-move-storage',
+ plain: true,
+ constrainHeader: true,
+ resizable: false,
+ },
+ config
+ );
+ Deluge.MoveStorage.superclass.constructor.call(this, config);
+ },
+
+ initComponent: function() {
+ Deluge.MoveStorage.superclass.initComponent.call(this);
+
+ this.addButton(_('Cancel'), this.onCancel, this);
+ this.addButton(_('Move'), this.onMove, this);
+
+ this.form = this.add({
+ xtype: 'form',
+ border: false,
+ defaultType: 'textfield',
+ width: 300,
+ bodyStyle: 'padding: 5px',
+ });
+
+ this.moveLocation = this.form.add({
+ fieldLabel: _('Download Folder'),
+ name: 'location',
+ width: 240,
+ });
+ //this.form.add({
+ // xtype: 'button',
+ // text: _('Browse'),
+ // handler: function() {
+ // if (!this.fileBrowser) {
+ // this.fileBrowser = new Deluge.FileBrowser();
+ // }
+ // this.fileBrowser.show();
+ // },
+ // scope: this
+ //});
+ },
+
+ hide: function() {
+ Deluge.MoveStorage.superclass.hide.call(this);
+ this.torrentIds = null;
+ },
+
+ show: function(torrentIds) {
+ Deluge.MoveStorage.superclass.show.call(this);
+ this.torrentIds = torrentIds;
+ },
+
+ onCancel: function() {
+ this.hide();
+ },
+
+ onMove: function() {
+ var dest = this.moveLocation.getValue();
+ deluge.client.core.move_storage(this.torrentIds, dest);
+ this.hide();
+ },
+});
+deluge.moveStorage = new Deluge.MoveStorage();
diff --git a/deluge/ui/web/js/deluge-all/MultiOptionsManager.js b/deluge/ui/web/js/deluge-all/MultiOptionsManager.js
new file mode 100644
index 0000000..1cd7d19
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/MultiOptionsManager.js
@@ -0,0 +1,218 @@
+/**
+ * Deluge.MultiOptionsManager.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+/**
+ * @description A class that can be used to manage options throughout the ui.
+ * @namespace Deluge
+ * @class Deluge.MultiOptionsManager
+ * @extends Deluge.OptionsManager
+ */
+Deluge.MultiOptionsManager = Ext.extend(Deluge.OptionsManager, {
+ constructor: function(config) {
+ this.currentId = null;
+ this.stored = {};
+ Deluge.MultiOptionsManager.superclass.constructor.call(this, config);
+ },
+
+ /**
+ * Changes bound fields to use the specified id.
+ * @param {String} id
+ */
+ changeId: function(id, dontUpdateBinds) {
+ var oldId = this.currentId;
+ this.currentId = id;
+ if (!dontUpdateBinds) {
+ for (var option in this.options) {
+ if (!this.binds[option]) continue;
+ Ext.each(
+ this.binds[option],
+ function(bind) {
+ bind.setValue(this.get(option));
+ },
+ this
+ );
+ }
+ }
+ return oldId;
+ },
+
+ /**
+ * Changes all the changed values to be the default values
+ * @param {String} id
+ */
+ commit: function() {
+ this.stored[this.currentId] = Ext.apply(
+ this.stored[this.currentId],
+ this.changed[this.currentId]
+ );
+ this.reset();
+ },
+
+ /**
+ * Get the value for an option
+ * @param {String/Array} option A single option or an array of options to return.
+ * @returns {Object} the options value.
+ */
+ get: function() {
+ if (arguments.length == 1) {
+ var option = arguments[0];
+ return this.isDirty(option)
+ ? this.changed[this.currentId][option]
+ : this.getDefault(option);
+ } else if (arguments.length == 0) {
+ var options = {};
+ for (var option in this.options) {
+ options[option] = this.isDirty(option)
+ ? this.changed[this.currentId][option]
+ : this.getDefault(option);
+ }
+ return options;
+ } else {
+ var options = {};
+ Ext.each(
+ arguments,
+ function(option) {
+ options[option] = this.isDirty(option)
+ ? this.changed[this.currentId][option]
+ : this.getDefault(option);
+ },
+ this
+ );
+ return options;
+ }
+ },
+
+ /**
+ * Get the default value for an option.
+ * @param {String} option A single option.
+ * @returns {Object} the value of the option
+ */
+ getDefault: function(option) {
+ return this.has(option)
+ ? this.stored[this.currentId][option]
+ : this.options[option];
+ },
+
+ /**
+ * Returns the dirty (changed) values.
+ * @returns {Object} the changed options
+ */
+ getDirty: function() {
+ return this.changed[this.currentId] ? this.changed[this.currentId] : {};
+ },
+
+ /**
+ * Check to see if the option has been changed.
+ * @param {String} option
+ * @returns {Boolean} true if the option has been changed, else false.
+ */
+ isDirty: function(option) {
+ return (
+ this.changed[this.currentId] &&
+ !Ext.isEmpty(this.changed[this.currentId][option])
+ );
+ },
+
+ /**
+ * Check to see if an id has had an option set to something other than the
+ * default value.
+ * @param {String} option
+ * @returns {Boolean} true if the id has an option, else false.
+ */
+ has: function(option) {
+ return (
+ this.stored[this.currentId] &&
+ !Ext.isEmpty(this.stored[this.currentId][option])
+ );
+ },
+
+ /**
+ * Reset the options back to the default values for the specified id.
+ */
+ reset: function() {
+ if (this.changed[this.currentId]) delete this.changed[this.currentId];
+ if (this.stored[this.currentId]) delete this.stored[this.currentId];
+ },
+
+ /**
+ * Reset the options back to their defaults for all ids.
+ */
+ resetAll: function() {
+ this.changed = {};
+ this.stored = {};
+ this.changeId(null);
+ },
+
+ /**
+ * Sets the value of specified option for the passed in id.
+ * @param {String} id
+ * @param {String} option
+ * @param {Object} value The value for the option
+ */
+ setDefault: function(option, value) {
+ if (option === undefined) {
+ return;
+ } else if (value === undefined) {
+ for (var key in option) {
+ this.setDefault(key, option[key]);
+ }
+ } else {
+ var oldValue = this.getDefault(option);
+ value = this.convertValueType(oldValue, value);
+
+ // If the value is the same as the old value there is
+ // no point in setting it again.
+ if (oldValue == value) return;
+
+ // Store the new default
+ if (!this.stored[this.currentId]) this.stored[this.currentId] = {};
+ this.stored[this.currentId][option] = value;
+
+ if (!this.isDirty(option)) {
+ this.fireEvent('changed', option, value, oldValue);
+ }
+ }
+ },
+
+ /**
+ * Update the value for the specified option and id.
+ * @param {String} id
+ * @param {String/Object} option or options to update
+ * @param {Object} [value];
+ */
+ update: function(option, value) {
+ if (option === undefined) {
+ return;
+ } else if (value === undefined) {
+ for (var key in option) {
+ this.update(key, option[key]);
+ }
+ } else {
+ if (!this.changed[this.currentId])
+ this.changed[this.currentId] = {};
+
+ var defaultValue = this.getDefault(option);
+ value = this.convertValueType(defaultValue, value);
+
+ var oldValue = this.get(option);
+ if (oldValue == value) return;
+
+ if (defaultValue == value) {
+ if (this.isDirty(option))
+ delete this.changed[this.currentId][option];
+ this.fireEvent('changed', option, value, oldValue);
+ return;
+ } else {
+ this.changed[this.currentId][option] = value;
+ this.fireEvent('changed', option, value, oldValue);
+ }
+ }
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/OptionsManager.js b/deluge/ui/web/js/deluge-all/OptionsManager.js
new file mode 100644
index 0000000..a1c4e65
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/OptionsManager.js
@@ -0,0 +1,279 @@
+/**
+ * Deluge.OptionsManager.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge');
+
+/**
+ * @class Deluge.OptionsManager
+ * @extends Ext.util.Observable
+ * A class that can be used to manage options throughout the ui.
+ * @constructor
+ * Creates a new OptionsManager
+ * @param {Object} config Configuration options
+ */
+Deluge.OptionsManager = Ext.extend(Ext.util.Observable, {
+ constructor: function(config) {
+ config = config || {};
+ this.binds = {};
+ this.changed = {};
+ this.options = (config && config['options']) || {};
+ this.focused = null;
+
+ this.addEvents({
+ /**
+ * @event add
+ * Fires when an option is added
+ */
+ add: true,
+
+ /**
+ * @event changed
+ * Fires when an option is changed
+ * @param {String} option The changed option
+ * @param {Mixed} value The options new value
+ * @param {Mixed} oldValue The options old value
+ */
+ changed: true,
+
+ /**
+ * @event reset
+ * Fires when the options are reset
+ */
+ reset: true,
+ });
+ this.on('changed', this.onChange, this);
+
+ Deluge.OptionsManager.superclass.constructor.call(this);
+ },
+
+ /**
+ * Add a set of default options and values to the options manager
+ * @param {Object} options The default options.
+ */
+ addOptions: function(options) {
+ this.options = Ext.applyIf(this.options, options);
+ },
+
+ /**
+ * Binds a form field to the specified option.
+ * @param {String} option
+ * @param {Ext.form.Field} field
+ */
+ bind: function(option, field) {
+ this.binds[option] = this.binds[option] || [];
+ this.binds[option].push(field);
+ field._doption = option;
+
+ field.on('focus', this.onFieldFocus, this);
+ field.on('blur', this.onFieldBlur, this);
+ field.on('change', this.onFieldChange, this);
+ field.on('check', this.onFieldChange, this);
+ field.on('spin', this.onFieldChange, this);
+ return field;
+ },
+
+ /**
+ * Changes all the changed values to be the default values
+ */
+ commit: function() {
+ this.options = Ext.apply(this.options, this.changed);
+ this.reset();
+ },
+
+ /**
+ * Converts the value so it matches the originals type
+ * @param {Mixed} oldValue The original value
+ * @param {Mixed} value The new value to convert
+ */
+ convertValueType: function(oldValue, value) {
+ if (Ext.type(oldValue) != Ext.type(value)) {
+ switch (Ext.type(oldValue)) {
+ case 'string':
+ value = String(value);
+ break;
+ case 'number':
+ value = Number(value);
+ break;
+ case 'boolean':
+ if (Ext.type(value) == 'string') {
+ value = value.toLowerCase();
+ value =
+ value == 'true' || value == '1' || value == 'on'
+ ? true
+ : false;
+ } else {
+ value = Boolean(value);
+ }
+ break;
+ }
+ }
+ return value;
+ },
+
+ /**
+ * Get the value for an option or options.
+ * @param {String} [option] A single option or an array of options to return.
+ * @returns {Object} the options value.
+ */
+ get: function() {
+ if (arguments.length == 1) {
+ var option = arguments[0];
+ return this.isDirty(option)
+ ? this.changed[option]
+ : this.options[option];
+ } else {
+ var options = {};
+ Ext.each(
+ arguments,
+ function(option) {
+ if (!this.has(option)) return;
+ options[option] = this.isDirty(option)
+ ? this.changed[option]
+ : this.options[option];
+ },
+ this
+ );
+ return options;
+ }
+ },
+
+ /**
+ * Get the default value for an option or options.
+ * @param {String|Array} [option] A single option or an array of options to return.
+ * @returns {Object} the value of the option
+ */
+ getDefault: function(option) {
+ return this.options[option];
+ },
+
+ /**
+ * Returns the dirty (changed) values.
+ * @returns {Object} the changed options
+ */
+ getDirty: function() {
+ return this.changed;
+ },
+
+ /**
+ * @param {String} [option] The option to check
+ * @returns {Boolean} true if the option has been changed from the default.
+ */
+ isDirty: function(option) {
+ return !Ext.isEmpty(this.changed[option]);
+ },
+
+ /**
+ * Check to see if an option exists in the options manager
+ * @param {String} option
+ * @returns {Boolean} true if the option exists, else false.
+ */
+ has: function(option) {
+ return this.options[option];
+ },
+
+ /**
+ * Reset the options back to the default values.
+ */
+ reset: function() {
+ this.changed = {};
+ },
+
+ /**
+ * Sets the value of specified option(s) for the passed in id.
+ * @param {String} option
+ * @param {Object} value The value for the option
+ */
+ set: function(option, value) {
+ if (option === undefined) {
+ return;
+ } else if (typeof option == 'object') {
+ var options = option;
+ this.options = Ext.apply(this.options, options);
+ for (var option in options) {
+ this.onChange(option, options[option]);
+ }
+ } else {
+ this.options[option] = value;
+ this.onChange(option, value);
+ }
+ },
+
+ /**
+ * Update the value for the specified option and id.
+ * @param {String/Object} option or options to update
+ * @param {Object} [value];
+ */
+ update: function(option, value) {
+ if (option === undefined) {
+ return;
+ } else if (value === undefined) {
+ for (var key in option) {
+ this.update(key, option[key]);
+ }
+ } else {
+ var defaultValue = this.getDefault(option);
+ value = this.convertValueType(defaultValue, value);
+
+ var oldValue = this.get(option);
+ if (oldValue == value) return;
+
+ if (defaultValue == value) {
+ if (this.isDirty(option)) delete this.changed[option];
+ this.fireEvent('changed', option, value, oldValue);
+ return;
+ }
+
+ this.changed[option] = value;
+ this.fireEvent('changed', option, value, oldValue);
+ }
+ },
+
+ /**
+ * Lets the option manager know when a field is blurred so if a value
+ * so value changing operations can continue on that field.
+ */
+ onFieldBlur: function(field, event) {
+ if (this.focused == field) {
+ this.focused = null;
+ }
+ },
+
+ /**
+ * Stops a form fields value from being blocked by the change functions
+ * @param {Ext.form.Field} field
+ * @private
+ */
+ onFieldChange: function(field, event) {
+ if (field.field) field = field.field; // fix for spinners
+ this.update(field._doption, field.getValue());
+ },
+
+ /**
+ * Lets the option manager know when a field is focused so if a value changing
+ * operation is performed it will not change the value of the field.
+ */
+ onFieldFocus: function(field, event) {
+ this.focused = field;
+ },
+
+ onChange: function(option, newValue, oldValue) {
+ // If we don't have a bind there's nothing to do.
+ if (Ext.isEmpty(this.binds[option])) return;
+ Ext.each(
+ this.binds[option],
+ function(bind) {
+ // The field is currently focused so we do not want to change it.
+ if (bind == this.focused) return;
+ // Set the form field to the new value.
+ bind.setValue(newValue);
+ },
+ this
+ );
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/OtherLimitWindow.js b/deluge/ui/web/js/deluge-all/OtherLimitWindow.js
new file mode 100644
index 0000000..3e5880f
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/OtherLimitWindow.js
@@ -0,0 +1,82 @@
+/**
+ * Deluge.OtherLimitWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+/**
+ * @class Deluge.OtherLimitWindow
+ * @extends Ext.Window
+ */
+Deluge.OtherLimitWindow = Ext.extend(Ext.Window, {
+ layout: 'fit',
+ width: 210,
+ height: 100,
+ constrainHeader: true,
+ closeAction: 'hide',
+
+ initComponent: function() {
+ Deluge.OtherLimitWindow.superclass.initComponent.call(this);
+ this.form = this.add({
+ xtype: 'form',
+ baseCls: 'x-plain',
+ bodyStyle: 'padding: 5px',
+ layout: 'hbox',
+ layoutConfig: {
+ pack: 'start',
+ },
+ items: [
+ {
+ xtype: 'spinnerfield',
+ name: 'limit',
+ },
+ ],
+ });
+ if (this.initialConfig.unit) {
+ this.form.add({
+ border: false,
+ baseCls: 'x-plain',
+ bodyStyle: 'padding: 5px',
+ html: this.initialConfig.unit,
+ });
+ } else {
+ this.setSize(180, 100);
+ }
+
+ this.addButton(_('Cancel'), this.onCancelClick, this);
+ this.addButton(_('OK'), this.onOkClick, this);
+ this.afterMethod('show', this.doFocusField, this);
+ },
+
+ setValue: function(value) {
+ this.form.getForm().setValues({ limit: value });
+ },
+
+ onCancelClick: function() {
+ this.form.getForm().reset();
+ this.hide();
+ },
+
+ onOkClick: function() {
+ var config = {};
+ config[this.group] = this.form.getForm().getValues().limit;
+ deluge.client.core.set_config(config, {
+ success: function() {
+ deluge.ui.update();
+ },
+ });
+ this.hide();
+ },
+
+ doFocusField: function() {
+ this.form
+ .getForm()
+ .findField('limit')
+ .focus(true, 10);
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/Plugin.js b/deluge/ui/web/js/deluge-all/Plugin.js
new file mode 100644
index 0000000..af2cda4
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/Plugin.js
@@ -0,0 +1,106 @@
+/**
+ * Deluge.Plugin.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+/**
+ * @class Deluge.Plugin
+ * @extends Ext.util.Observable
+ */
+Deluge.Plugin = Ext.extend(Ext.util.Observable, {
+ /**
+ * The plugins name
+ * @property name
+ * @type {String}
+ */
+ name: null,
+
+ constructor: function(config) {
+ this.isDelugePlugin = true;
+ this.addEvents({
+ /**
+ * @event enabled
+ * @param {Plugin} plugin the plugin instance
+ */
+ enabled: true,
+
+ /**
+ * @event disabled
+ * @param {Plugin} plugin the plugin instance
+ */
+ disabled: true,
+ });
+ Deluge.Plugin.superclass.constructor.call(this, config);
+ },
+
+ /**
+ * Disables the plugin, firing the "{@link #disabled}" event and
+ * then executing the plugins clean up method onDisabled.
+ */
+ disable: function() {
+ this.fireEvent('disabled', this);
+ if (this.onDisable) this.onDisable();
+ },
+
+ /**
+ * Enables the plugin, firing the "{@link #enabled}" event and
+ * then executes the plugins setup method, onEnabled.
+ */
+ enable: function() {
+ deluge.client.reloadMethods();
+ this.fireEvent('enable', this);
+ if (this.onEnable) this.onEnable();
+ },
+
+ registerTorrentStatus: function(key, header, options) {
+ options = options || {};
+ var cc = options.colCfg || {},
+ sc = options.storeCfg || {};
+ sc = Ext.apply(sc, { name: key });
+ deluge.torrents.meta.fields.push(sc);
+ deluge.torrents.getStore().reader.onMetaChange(deluge.torrents.meta);
+
+ cc = Ext.apply(cc, {
+ header: header,
+ dataIndex: key,
+ });
+ var cols = deluge.torrents.columns.slice(0);
+ cols.push(cc);
+ deluge.torrents.colModel.setConfig(cols);
+ deluge.torrents.columns = cols;
+
+ Deluge.Keys.Grid.push(key);
+ deluge.torrents.getView().refresh(true);
+ },
+
+ deregisterTorrentStatus: function(key) {
+ var fields = [];
+ Ext.each(deluge.torrents.meta.fields, function(field) {
+ if (field.name != key) fields.push(field);
+ });
+ deluge.torrents.meta.fields = fields;
+ deluge.torrents.getStore().reader.onMetaChange(deluge.torrents.meta);
+
+ var cols = [];
+ Ext.each(deluge.torrents.columns, function(col) {
+ if (col.dataIndex != key) cols.push(col);
+ });
+ deluge.torrents.colModel.setConfig(cols);
+ deluge.torrents.columns = cols;
+
+ var keys = [];
+ Ext.each(Deluge.Keys.Grid, function(k) {
+ if (k == key) keys.push(k);
+ });
+ Deluge.Keys.Grid = keys;
+ deluge.torrents.getView().refresh(true);
+ },
+});
+
+Ext.ns('Deluge.plugins');
diff --git a/deluge/ui/web/js/deluge-all/RemoveWindow.js b/deluge/ui/web/js/deluge-all/RemoveWindow.js
new file mode 100644
index 0000000..a629008
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/RemoveWindow.js
@@ -0,0 +1,77 @@
+/**
+ * Deluge.RemoveWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+/**
+ * @class Deluge.RemoveWindow
+ * @extends Ext.Window
+ */
+Deluge.RemoveWindow = Ext.extend(Ext.Window, {
+ title: _('Remove Torrent'),
+ layout: 'fit',
+ width: 350,
+ height: 100,
+ constrainHeader: true,
+ buttonAlign: 'right',
+ closeAction: 'hide',
+ closable: true,
+ iconCls: 'x-deluge-remove-window-icon',
+ plain: true,
+
+ bodyStyle: 'padding: 5px; padding-left: 10px;',
+ html: 'Are you sure you wish to remove the torrent (s)?',
+
+ initComponent: function() {
+ Deluge.RemoveWindow.superclass.initComponent.call(this);
+ this.addButton(_('Cancel'), this.onCancel, this);
+ this.addButton(_('Remove With Data'), this.onRemoveData, this);
+ this.addButton(_('Remove Torrent'), this.onRemove, this);
+ },
+
+ remove: function(removeData) {
+ deluge.client.core.remove_torrents(this.torrentIds, removeData, {
+ success: function(result) {
+ if (result == true) {
+ console.log(
+ 'Error(s) occured when trying to delete torrent(s).'
+ );
+ }
+ this.onRemoved(this.torrentIds);
+ },
+ scope: this,
+ torrentIds: this.torrentIds,
+ });
+ },
+
+ show: function(ids) {
+ Deluge.RemoveWindow.superclass.show.call(this);
+ this.torrentIds = ids;
+ },
+
+ onCancel: function() {
+ this.hide();
+ this.torrentIds = null;
+ },
+
+ onRemove: function() {
+ this.remove(false);
+ },
+
+ onRemoveData: function() {
+ this.remove(true);
+ },
+
+ onRemoved: function(torrentIds) {
+ deluge.events.fire('torrentsRemoved', torrentIds);
+ this.hide();
+ deluge.ui.update();
+ },
+});
+
+deluge.removeWindow = new Deluge.RemoveWindow();
diff --git a/deluge/ui/web/js/deluge-all/Sidebar.js b/deluge/ui/web/js/deluge-all/Sidebar.js
new file mode 100644
index 0000000..74c3ecb
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/Sidebar.js
@@ -0,0 +1,144 @@
+/**
+ * Deluge.Sidebar.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+// These are just so gen_gettext.py will pick up the strings
+// _('State')
+// _('Tracker Host')
+
+/**
+ * @class Deluge.Sidebar
+ * @author Damien Churchill <damoxc@gmail.com>
+ * @version 1.3
+ */
+Deluge.Sidebar = Ext.extend(Ext.Panel, {
+ // private
+ panels: {},
+
+ // private
+ selected: null,
+
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ id: 'sidebar',
+ region: 'west',
+ cls: 'deluge-sidebar',
+ title: _('Filters'),
+ layout: 'accordion',
+ split: true,
+ width: 200,
+ minSize: 100,
+ collapsible: true,
+ },
+ config
+ );
+ Deluge.Sidebar.superclass.constructor.call(this, config);
+ },
+
+ // private
+ initComponent: function() {
+ Deluge.Sidebar.superclass.initComponent.call(this);
+ deluge.events.on('disconnect', this.onDisconnect, this);
+ },
+
+ createFilter: function(filter, states) {
+ var panel = new Deluge.FilterPanel({
+ filter: filter,
+ });
+ panel.on('selectionchange', function(view, nodes) {
+ deluge.ui.update();
+ });
+ this.add(panel);
+
+ this.doLayout();
+ this.panels[filter] = panel;
+
+ panel.header.on('click', function(header) {
+ if (!deluge.config.sidebar_multiple_filters) {
+ deluge.ui.update();
+ }
+ if (!panel.list.getSelectionCount()) {
+ panel.list.select(0);
+ }
+ });
+ this.fireEvent('filtercreate', this, panel);
+
+ panel.updateStates(states);
+ this.fireEvent('afterfiltercreate', this, panel);
+ },
+
+ getFilter: function(filter) {
+ return this.panels[filter];
+ },
+
+ getFilterStates: function() {
+ var states = {};
+
+ if (deluge.config.sidebar_multiple_filters) {
+ // Grab the filters from each of the filter panels
+ this.items.each(function(panel) {
+ var state = panel.getState();
+ if (state == null) return;
+ states[panel.filterType] = state;
+ }, this);
+ } else {
+ var panel = this.getLayout().activeItem;
+ if (panel) {
+ var state = panel.getState();
+ if (!state == null) return;
+ states[panel.filterType] = state;
+ }
+ }
+
+ return states;
+ },
+
+ hasFilter: function(filter) {
+ return this.panels[filter] ? true : false;
+ },
+
+ // private
+ onDisconnect: function() {
+ for (var filter in this.panels) {
+ this.remove(this.panels[filter]);
+ }
+ this.panels = {};
+ this.selected = null;
+ },
+
+ onFilterSelect: function(selModel, rowIndex, record) {
+ deluge.ui.update();
+ },
+
+ update: function(filters) {
+ for (var filter in filters) {
+ var states = filters[filter];
+ if (Ext.getKeys(this.panels).indexOf(filter) > -1) {
+ this.panels[filter].updateStates(states);
+ } else {
+ this.createFilter(filter, states);
+ }
+ }
+
+ // Perform a cleanup of fitlers that are not enabled any more.
+ Ext.each(
+ Ext.keys(this.panels),
+ function(filter) {
+ if (Ext.keys(filters).indexOf(filter) == -1) {
+ // We need to remove the panel
+ this.remove(this.panels[filter]);
+ this.doLayout();
+ delete this.panels[filter];
+ }
+ },
+ this
+ );
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/Statusbar.js b/deluge/ui/web/js/deluge-all/Statusbar.js
new file mode 100644
index 0000000..c2327be
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/Statusbar.js
@@ -0,0 +1,362 @@
+/**
+ * Deluge.Statusbar.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge');
+
+Deluge.Statusbar = Ext.extend(Ext.ux.StatusBar, {
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ id: 'deluge-statusbar',
+ defaultIconCls: 'x-deluge-statusbar x-not-connected',
+ defaultText: _('Not Connected'),
+ },
+ config
+ );
+ Deluge.Statusbar.superclass.constructor.call(this, config);
+ },
+
+ initComponent: function() {
+ Deluge.Statusbar.superclass.initComponent.call(this);
+
+ deluge.events.on('connect', this.onConnect, this);
+ deluge.events.on('disconnect', this.onDisconnect, this);
+ },
+
+ createButtons: function() {
+ this.buttons = this.add(
+ {
+ id: 'statusbar-connections',
+ text: ' ',
+ cls: 'x-btn-text-icon',
+ iconCls: 'x-deluge-connections',
+ tooltip: _('Connections'),
+ menu: new Deluge.StatusbarMenu({
+ items: [
+ {
+ text: '50',
+ value: '50',
+ group: 'max_connections_global',
+ checked: false,
+ },
+ {
+ text: '100',
+ value: '100',
+ group: 'max_connections_global',
+ checked: false,
+ },
+ {
+ text: '200',
+ value: '200',
+ group: 'max_connections_global',
+ checked: false,
+ },
+ {
+ text: '300',
+ value: '300',
+ group: 'max_connections_global',
+ checked: false,
+ },
+ {
+ text: '500',
+ value: '500',
+ group: 'max_connections_global',
+ checked: false,
+ },
+ {
+ text: _('Unlimited'),
+ value: '-1',
+ group: 'max_connections_global',
+ checked: false,
+ },
+ '-',
+ {
+ text: _('Other'),
+ value: 'other',
+ group: 'max_connections_global',
+ checked: false,
+ },
+ ],
+ otherWin: {
+ title: _('Set Maximum Connections'),
+ },
+ }),
+ },
+ '-',
+ {
+ id: 'statusbar-downspeed',
+ text: ' ',
+ cls: 'x-btn-text-icon',
+ iconCls: 'x-deluge-downloading',
+ tooltip: _('Download Speed'),
+ menu: new Deluge.StatusbarMenu({
+ items: [
+ {
+ value: '5',
+ text: _('5 KiB/s'),
+ group: 'max_download_speed',
+ checked: false,
+ },
+ {
+ value: '10',
+ text: _('10 KiB/s'),
+ group: 'max_download_speed',
+ checked: false,
+ },
+ {
+ value: '30',
+ text: _('30 KiB/s'),
+ group: 'max_download_speed',
+ checked: false,
+ },
+ {
+ value: '80',
+ text: _('80 KiB/s'),
+ group: 'max_download_speed',
+ checked: false,
+ },
+ {
+ value: '300',
+ text: _('300 KiB/s'),
+ group: 'max_download_speed',
+ checked: false,
+ },
+ {
+ value: '-1',
+ text: _('Unlimited'),
+ group: 'max_download_speed',
+ checked: false,
+ },
+ '-',
+ {
+ value: 'other',
+ text: _('Other'),
+ group: 'max_download_speed',
+ checked: false,
+ },
+ ],
+ otherWin: {
+ title: _('Set Maximum Download Speed'),
+ unit: _('KiB/s'),
+ },
+ }),
+ },
+ '-',
+ {
+ id: 'statusbar-upspeed',
+ text: ' ',
+ cls: 'x-btn-text-icon',
+ iconCls: 'x-deluge-seeding',
+ tooltip: _('Upload Speed'),
+ menu: new Deluge.StatusbarMenu({
+ items: [
+ {
+ value: '5',
+ text: _('5 KiB/s'),
+ group: 'max_upload_speed',
+ checked: false,
+ },
+ {
+ value: '10',
+ text: _('10 KiB/s'),
+ group: 'max_upload_speed',
+ checked: false,
+ },
+ {
+ value: '30',
+ text: _('30 KiB/s'),
+ group: 'max_upload_speed',
+ checked: false,
+ },
+ {
+ value: '80',
+ text: _('80 KiB/s'),
+ group: 'max_upload_speed',
+ checked: false,
+ },
+ {
+ value: '300',
+ text: _('300 KiB/s'),
+ group: 'max_upload_speed',
+ checked: false,
+ },
+ {
+ value: '-1',
+ text: _('Unlimited'),
+ group: 'max_upload_speed',
+ checked: false,
+ },
+ '-',
+ {
+ value: 'other',
+ text: _('Other'),
+ group: 'max_upload_speed',
+ checked: false,
+ },
+ ],
+ otherWin: {
+ title: _('Set Maximum Upload Speed'),
+ unit: _('KiB/s'),
+ },
+ }),
+ },
+ '-',
+ {
+ id: 'statusbar-traffic',
+ text: ' ',
+ cls: 'x-btn-text-icon',
+ iconCls: 'x-deluge-traffic',
+ tooltip: _('Protocol Traffic Download/Upload'),
+ handler: function() {
+ deluge.preferences.show();
+ deluge.preferences.selectPage('Network');
+ },
+ },
+ '-',
+ {
+ id: 'statusbar-externalip',
+ text: ' ',
+ cls: 'x-btn-text',
+ tooltip: _('External IP Address'),
+ },
+ '-',
+ {
+ id: 'statusbar-dht',
+ text: ' ',
+ cls: 'x-btn-text-icon',
+ iconCls: 'x-deluge-dht',
+ tooltip: _('DHT Nodes'),
+ },
+ '-',
+ {
+ id: 'statusbar-freespace',
+ text: ' ',
+ cls: 'x-btn-text-icon',
+ iconCls: 'x-deluge-freespace',
+ tooltip: _('Freespace in download folder'),
+ handler: function() {
+ deluge.preferences.show();
+ deluge.preferences.selectPage('Downloads');
+ },
+ }
+ );
+ this.created = true;
+ },
+
+ onConnect: function() {
+ this.setStatus({
+ iconCls: 'x-connected',
+ text: '',
+ });
+ if (!this.created) {
+ this.createButtons();
+ } else {
+ Ext.each(this.buttons, function(item) {
+ item.show();
+ item.enable();
+ });
+ }
+ this.doLayout();
+ },
+
+ onDisconnect: function() {
+ this.clearStatus({ useDefaults: true });
+ Ext.each(this.buttons, function(item) {
+ item.hide();
+ item.disable();
+ });
+ this.doLayout();
+ },
+
+ update: function(stats) {
+ if (!stats) return;
+
+ function addSpeed(val) {
+ return val + ' KiB/s';
+ }
+
+ var updateStat = function(name, config) {
+ var item = this.items.get('statusbar-' + name);
+ if (config.limit.value > 0) {
+ var value = config.value.formatter
+ ? config.value.formatter(config.value.value, true)
+ : config.value.value;
+ var limit = config.limit.formatter
+ ? config.limit.formatter(config.limit.value, true)
+ : config.limit.value;
+ var str = String.format(config.format, value, limit);
+ } else {
+ var str = config.value.formatter
+ ? config.value.formatter(config.value.value, true)
+ : config.value.value;
+ }
+ item.setText(str);
+
+ if (!item.menu) return;
+ item.menu.setValue(config.limit.value);
+ }.createDelegate(this);
+
+ updateStat('connections', {
+ value: { value: stats.num_connections },
+ limit: { value: stats.max_num_connections },
+ format: '{0} ({1})',
+ });
+
+ updateStat('downspeed', {
+ value: {
+ value: stats.download_rate,
+ formatter: Deluge.Formatters.speed,
+ },
+ limit: {
+ value: stats.max_download,
+ formatter: addSpeed,
+ },
+ format: '{0} ({1})',
+ });
+
+ updateStat('upspeed', {
+ value: {
+ value: stats.upload_rate,
+ formatter: Deluge.Formatters.speed,
+ },
+ limit: {
+ value: stats.max_upload,
+ formatter: addSpeed,
+ },
+ format: '{0} ({1})',
+ });
+
+ updateStat('traffic', {
+ value: {
+ value: stats.download_protocol_rate,
+ formatter: Deluge.Formatters.speed,
+ },
+ limit: {
+ value: stats.upload_protocol_rate,
+ formatter: Deluge.Formatters.speed,
+ },
+ format: '{0}/{1}',
+ });
+
+ this.items.get('statusbar-dht').setText(stats.dht_nodes);
+ this.items
+ .get('statusbar-freespace')
+ .setText(
+ stats.free_space >= 0 ? fsize(stats.free_space) : _('Error')
+ );
+ this.items
+ .get('statusbar-externalip')
+ .setText(
+ String.format(
+ _('<b>IP</b> {0}'),
+ stats.external_ip ? stats.external_ip : _('n/a')
+ )
+ );
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/StatusbarMenu.js b/deluge/ui/web/js/deluge-all/StatusbarMenu.js
new file mode 100644
index 0000000..b988253
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/StatusbarMenu.js
@@ -0,0 +1,79 @@
+/**
+ * Deluge.StatusbarMenu.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge');
+
+/**
+ * Menu that handles setting the statusbar limits correctly.
+ * @class Deluge.StatusbarMenu
+ * @extends Ext.menu.Menu
+ */
+Deluge.StatusbarMenu = Ext.extend(Ext.menu.Menu, {
+ initComponent: function() {
+ Deluge.StatusbarMenu.superclass.initComponent.call(this);
+ this.otherWin = new Deluge.OtherLimitWindow(
+ this.initialConfig.otherWin || {}
+ );
+
+ this.items.each(function(item) {
+ if (item.getXType() != 'menucheckitem') return;
+ if (item.value == 'other') {
+ item.on('click', this.onOtherClicked, this);
+ } else {
+ item.on('checkchange', this.onLimitChanged, this);
+ }
+ }, this);
+ },
+
+ setValue: function(value) {
+ var beenSet = false;
+ // set the new value
+ this.value = value = value == 0 ? -1 : value;
+
+ var other = null;
+ // uncheck all items
+ this.items.each(function(item) {
+ if (item.setChecked) {
+ item.suspendEvents();
+ if (item.value == value) {
+ item.setChecked(true);
+ beenSet = true;
+ } else {
+ item.setChecked(false);
+ }
+ item.resumeEvents();
+ }
+
+ if (item.value == 'other') other = item;
+ });
+
+ if (beenSet) return;
+
+ other.suspendEvents();
+ other.setChecked(true);
+ other.resumeEvents();
+ },
+
+ onLimitChanged: function(item, checked) {
+ if (!checked || item.value == 'other') return; // We do not care about unchecked or other.
+ var config = {};
+ config[item.group] = item.value;
+ deluge.client.core.set_config(config, {
+ success: function() {
+ deluge.ui.update();
+ },
+ });
+ },
+
+ onOtherClicked: function(item, e) {
+ this.otherWin.group = item.group;
+ this.otherWin.setValue(this.value);
+ this.otherWin.show();
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/Toolbar.js b/deluge/ui/web/js/deluge-all/Toolbar.js
new file mode 100644
index 0000000..d51818b
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/Toolbar.js
@@ -0,0 +1,206 @@
+/**
+ * Deluge.Toolbar.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+/**
+ * An extension of the <tt>Ext.Toolbar</tt> class that provides an extensible toolbar for Deluge.
+ * @class Deluge.Toolbar
+ * @extends Ext.Toolbar
+ */
+Deluge.Toolbar = Ext.extend(Ext.Toolbar, {
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ items: [
+ {
+ id: 'tbar-deluge-text',
+ text: _('Deluge'),
+ iconCls: 'x-deluge-main-panel',
+ handler: this.onAboutClick,
+ },
+ new Ext.Toolbar.Separator(),
+ {
+ id: 'create',
+ disabled: true,
+ hidden: true,
+ text: _('Create'),
+ iconCls: 'icon-create',
+ handler: this.onTorrentAction,
+ },
+ {
+ id: 'add',
+ disabled: true,
+ text: _('Add'),
+ iconCls: 'icon-add',
+ handler: this.onTorrentAdd,
+ },
+ {
+ id: 'remove',
+ disabled: true,
+ text: _('Remove'),
+ iconCls: 'icon-remove',
+ handler: this.onTorrentAction,
+ },
+ new Ext.Toolbar.Separator(),
+ {
+ id: 'pause',
+ disabled: true,
+ text: _('Pause'),
+ iconCls: 'icon-pause',
+ handler: this.onTorrentAction,
+ },
+ {
+ id: 'resume',
+ disabled: true,
+ text: _('Resume'),
+ iconCls: 'icon-resume',
+ handler: this.onTorrentAction,
+ },
+ new Ext.Toolbar.Separator(),
+ {
+ id: 'up',
+ cls: 'x-btn-text-icon',
+ disabled: true,
+ text: _('Up'),
+ iconCls: 'icon-up',
+ handler: this.onTorrentAction,
+ },
+ {
+ id: 'down',
+ disabled: true,
+ text: _('Down'),
+ iconCls: 'icon-down',
+ handler: this.onTorrentAction,
+ },
+ new Ext.Toolbar.Separator(),
+ {
+ id: 'preferences',
+ text: _('Preferences'),
+ iconCls: 'x-deluge-preferences',
+ handler: this.onPreferencesClick,
+ scope: this,
+ },
+ {
+ id: 'connectionman',
+ text: _('Connection Manager'),
+ iconCls: 'x-deluge-connection-manager',
+ handler: this.onConnectionManagerClick,
+ scope: this,
+ },
+ '->',
+ {
+ id: 'help',
+ iconCls: 'icon-help',
+ text: _('Help'),
+ handler: this.onHelpClick,
+ scope: this,
+ },
+ {
+ id: 'logout',
+ iconCls: 'icon-logout',
+ disabled: true,
+ text: _('Logout'),
+ handler: this.onLogout,
+ scope: this,
+ },
+ ],
+ },
+ config
+ );
+ Deluge.Toolbar.superclass.constructor.call(this, config);
+ },
+
+ connectedButtons: ['add', 'remove', 'pause', 'resume', 'up', 'down'],
+
+ initComponent: function() {
+ Deluge.Toolbar.superclass.initComponent.call(this);
+ deluge.events.on('connect', this.onConnect, this);
+ deluge.events.on('login', this.onLogin, this);
+ },
+
+ onConnect: function() {
+ Ext.each(
+ this.connectedButtons,
+ function(buttonId) {
+ this.items.get(buttonId).enable();
+ },
+ this
+ );
+ },
+
+ onDisconnect: function() {
+ Ext.each(
+ this.connectedButtons,
+ function(buttonId) {
+ this.items.get(buttonId).disable();
+ },
+ this
+ );
+ },
+
+ onLogin: function() {
+ this.items.get('logout').enable();
+ },
+
+ onLogout: function() {
+ this.items.get('logout').disable();
+ deluge.login.logout();
+ },
+
+ onConnectionManagerClick: function() {
+ deluge.connectionManager.show();
+ },
+
+ onHelpClick: function() {
+ window.open('http://dev.deluge-torrent.org/wiki/UserGuide');
+ },
+
+ onAboutClick: function() {
+ var about = new Deluge.about.AboutWindow();
+ about.show();
+ },
+
+ onPreferencesClick: function() {
+ deluge.preferences.show();
+ },
+
+ onTorrentAction: function(item) {
+ var selection = deluge.torrents.getSelections();
+ var ids = [];
+ Ext.each(selection, function(record) {
+ ids.push(record.id);
+ });
+
+ switch (item.id) {
+ case 'remove':
+ deluge.removeWindow.show(ids);
+ break;
+ case 'pause':
+ case 'resume':
+ deluge.client.core[item.id + '_torrent'](ids, {
+ success: function() {
+ deluge.ui.update();
+ },
+ });
+ break;
+ case 'up':
+ case 'down':
+ deluge.client.core['queue_' + item.id](ids, {
+ success: function() {
+ deluge.ui.update();
+ },
+ });
+ break;
+ }
+ },
+
+ onTorrentAdd: function() {
+ deluge.add.show();
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/TorrentGrid.js b/deluge/ui/web/js/deluge-all/TorrentGrid.js
new file mode 100644
index 0000000..b0e0c5e
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/TorrentGrid.js
@@ -0,0 +1,510 @@
+/**
+ * Deluge.TorrentGrid.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+(function() {
+ /* Renderers for the Torrent Grid */
+ function queueRenderer(value) {
+ return value == -1 ? '' : value + 1;
+ }
+ function torrentNameRenderer(value, p, r) {
+ return String.format(
+ '<div class="torrent-name x-deluge-{0}">{1}</div>',
+ r.data['state'].toLowerCase(),
+ value
+ );
+ }
+ function torrentSpeedRenderer(value) {
+ if (!value) return;
+ return fspeed(value);
+ }
+ function torrentLimitRenderer(value) {
+ if (value == -1) return '';
+ return fspeed(value * 1024.0);
+ }
+ function torrentProgressRenderer(value, p, r) {
+ value = new Number(value);
+ var progress = value;
+ var text = _(r.data['state']) + ' ' + value.toFixed(2) + '%';
+ if (this.style) {
+ var style = this.style;
+ } else {
+ var style = p.style;
+ }
+ var width = new Number(style.match(/\w+:\s*(\d+)\w+/)[1]);
+ return Deluge.progressBar(value, width - 8, text);
+ }
+ function seedsRenderer(value, p, r) {
+ if (r.data['total_seeds'] > -1) {
+ return String.format('{0} ({1})', value, r.data['total_seeds']);
+ } else {
+ return value;
+ }
+ }
+ function peersRenderer(value, p, r) {
+ if (r.data['total_peers'] > -1) {
+ return String.format('{0} ({1})', value, r.data['total_peers']);
+ } else {
+ return value;
+ }
+ }
+ function availRenderer(value, p, r) {
+ return value < 0 ? '&infin;' : parseFloat(new Number(value).toFixed(3));
+ }
+ function trackerRenderer(value, p, r) {
+ return String.format(
+ '<div style="background: url(' +
+ deluge.config.base +
+ 'tracker/{0}) no-repeat; padding-left: 20px;">{0}</div>',
+ value
+ );
+ }
+
+ function etaSorter(eta) {
+ return eta * -1;
+ }
+
+ function dateOrNever(date) {
+ return date > 0.0 ? fdate(date) : _('Never');
+ }
+
+ function timeOrInf(time) {
+ return time < 0 ? '&infin;' : ftime(time);
+ }
+
+ /**
+ * Deluge.TorrentGrid Class
+ *
+ * @author Damien Churchill <damoxc@gmail.com>
+ * @version 1.3
+ *
+ * @class Deluge.TorrentGrid
+ * @extends Ext.grid.GridPanel
+ * @constructor
+ * @param {Object} config Configuration options
+ */
+ Deluge.TorrentGrid = Ext.extend(Ext.grid.GridPanel, {
+ // object to store contained torrent ids
+ torrents: {},
+
+ columns: [
+ {
+ id: 'queue',
+ header: '#',
+ width: 30,
+ sortable: true,
+ renderer: queueRenderer,
+ dataIndex: 'queue',
+ },
+ {
+ id: 'name',
+ header: _('Name'),
+ width: 150,
+ sortable: true,
+ renderer: torrentNameRenderer,
+ dataIndex: 'name',
+ },
+ {
+ header: _('Size'),
+ width: 75,
+ sortable: true,
+ renderer: fsize,
+ dataIndex: 'total_wanted',
+ },
+ {
+ header: _('Progress'),
+ width: 150,
+ sortable: true,
+ renderer: torrentProgressRenderer,
+ dataIndex: 'progress',
+ },
+ {
+ header: _('Seeds'),
+ hidden: true,
+ width: 60,
+ sortable: true,
+ renderer: seedsRenderer,
+ dataIndex: 'num_seeds',
+ },
+ {
+ header: _('Peers'),
+ hidden: true,
+ width: 60,
+ sortable: true,
+ renderer: peersRenderer,
+ dataIndex: 'num_peers',
+ },
+ {
+ header: _('Down Speed'),
+ width: 80,
+ sortable: true,
+ renderer: torrentSpeedRenderer,
+ dataIndex: 'download_payload_rate',
+ },
+ {
+ header: _('Up Speed'),
+ width: 80,
+ sortable: true,
+ renderer: torrentSpeedRenderer,
+ dataIndex: 'upload_payload_rate',
+ },
+ {
+ header: _('ETA'),
+ width: 60,
+ sortable: true,
+ renderer: timeOrInf,
+ dataIndex: 'eta',
+ },
+ {
+ header: _('Ratio'),
+ hidden: true,
+ width: 60,
+ sortable: true,
+ renderer: availRenderer,
+ dataIndex: 'ratio',
+ },
+ {
+ header: _('Avail'),
+ hidden: true,
+ width: 60,
+ sortable: true,
+ renderer: availRenderer,
+ dataIndex: 'distributed_copies',
+ },
+ {
+ header: _('Added'),
+ hidden: true,
+ width: 80,
+ sortable: true,
+ renderer: fdate,
+ dataIndex: 'time_added',
+ },
+ {
+ header: _('Complete Seen'),
+ hidden: true,
+ width: 80,
+ sortable: true,
+ renderer: dateOrNever,
+ dataIndex: 'last_seen_complete',
+ },
+ {
+ header: _('Completed'),
+ hidden: true,
+ width: 80,
+ sortable: true,
+ renderer: dateOrNever,
+ dataIndex: 'completed_time',
+ },
+ {
+ header: _('Tracker'),
+ hidden: true,
+ width: 120,
+ sortable: true,
+ renderer: trackerRenderer,
+ dataIndex: 'tracker_host',
+ },
+ {
+ header: _('Download Folder'),
+ hidden: true,
+ width: 120,
+ sortable: true,
+ renderer: fplain,
+ dataIndex: 'download_location',
+ },
+ {
+ header: _('Owner'),
+ width: 80,
+ sortable: true,
+ renderer: fplain,
+ dataIndex: 'owner',
+ },
+ {
+ header: _('Public'),
+ hidden: true,
+ width: 80,
+ sortable: true,
+ renderer: fplain,
+ dataIndex: 'public',
+ },
+ {
+ header: _('Shared'),
+ hidden: true,
+ width: 80,
+ sortable: true,
+ renderer: fplain,
+ dataIndex: 'shared',
+ },
+ {
+ header: _('Downloaded'),
+ hidden: true,
+ width: 75,
+ sortable: true,
+ renderer: fsize,
+ dataIndex: 'total_done',
+ },
+ {
+ header: _('Uploaded'),
+ hidden: true,
+ width: 75,
+ sortable: true,
+ renderer: fsize,
+ dataIndex: 'total_uploaded',
+ },
+ {
+ header: _('Remaining'),
+ hidden: true,
+ width: 75,
+ sortable: true,
+ renderer: fsize,
+ dataIndex: 'total_remaining',
+ },
+ {
+ header: _('Down Limit'),
+ hidden: true,
+ width: 75,
+ sortable: true,
+ renderer: torrentLimitRenderer,
+ dataIndex: 'max_download_speed',
+ },
+ {
+ header: _('Up Limit'),
+ hidden: true,
+ width: 75,
+ sortable: true,
+ renderer: torrentLimitRenderer,
+ dataIndex: 'max_upload_speed',
+ },
+ {
+ header: _('Seeds:Peers'),
+ hidden: true,
+ width: 75,
+ sortable: true,
+ renderer: availRenderer,
+ dataIndex: 'seeds_peers_ratio',
+ },
+ {
+ header: _('Last Transfer'),
+ hidden: true,
+ width: 75,
+ sortable: true,
+ renderer: ftime,
+ dataIndex: 'time_since_transfer',
+ },
+ ],
+
+ meta: {
+ root: 'torrents',
+ idProperty: 'id',
+ fields: [
+ {
+ name: 'queue',
+ sortType: Deluge.data.SortTypes.asQueuePosition,
+ },
+ { name: 'name', sortType: Deluge.data.SortTypes.asName },
+ { name: 'total_wanted', type: 'int' },
+ { name: 'state' },
+ { name: 'progress', type: 'float' },
+ { name: 'num_seeds', type: 'int' },
+ { name: 'total_seeds', type: 'int' },
+ { name: 'num_peers', type: 'int' },
+ { name: 'total_peers', type: 'int' },
+ { name: 'download_payload_rate', type: 'int' },
+ { name: 'upload_payload_rate', type: 'int' },
+ { name: 'eta', type: 'int', sortType: etaSorter },
+ { name: 'ratio', type: 'float' },
+ { name: 'distributed_copies', type: 'float' },
+ { name: 'time_added', type: 'int' },
+ { name: 'tracker_host' },
+ { name: 'download_location' },
+ { name: 'total_done', type: 'int' },
+ { name: 'total_uploaded', type: 'int' },
+ { name: 'total_remaining', type: 'int' },
+ { name: 'max_download_speed', type: 'int' },
+ { name: 'max_upload_speed', type: 'int' },
+ { name: 'seeds_peers_ratio', type: 'float' },
+ { name: 'time_since_transfer', type: 'int' },
+ ],
+ },
+
+ keys: [
+ {
+ key: 'a',
+ ctrl: true,
+ stopEvent: true,
+ handler: function() {
+ deluge.torrents.getSelectionModel().selectAll();
+ },
+ },
+ {
+ key: [46],
+ stopEvent: true,
+ handler: function() {
+ ids = deluge.torrents.getSelectedIds();
+ deluge.removeWindow.show(ids);
+ },
+ },
+ ],
+
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ id: 'torrentGrid',
+ store: new Ext.data.JsonStore(this.meta),
+ columns: this.columns,
+ keys: this.keys,
+ region: 'center',
+ cls: 'deluge-torrents',
+ stripeRows: true,
+ autoExpandColumn: 'name',
+ autoExpandMin: 150,
+ deferredRender: false,
+ autoScroll: true,
+ stateful: true,
+ view: new Ext.ux.grid.BufferView({
+ rowHeight: 26,
+ scrollDelay: false,
+ }),
+ },
+ config
+ );
+ Deluge.TorrentGrid.superclass.constructor.call(this, config);
+ },
+
+ initComponent: function() {
+ Deluge.TorrentGrid.superclass.initComponent.call(this);
+ deluge.events.on('torrentsRemoved', this.onTorrentsRemoved, this);
+ deluge.events.on('disconnect', this.onDisconnect, this);
+
+ this.on('rowcontextmenu', function(grid, rowIndex, e) {
+ e.stopEvent();
+ var selection = grid.getSelectionModel();
+ if (!selection.isSelected(rowIndex)) {
+ selection.selectRow(rowIndex);
+ }
+ deluge.menus.torrent.showAt(e.getPoint());
+ });
+ },
+
+ /**
+ * Returns the record representing the torrent at the specified index.
+ *
+ * @param index {int} The row index of the torrent you wish to retrieve.
+ * @return {Ext.data.Record} The record representing the torrent.
+ */
+ getTorrent: function(index) {
+ return this.getStore().getAt(index);
+ },
+
+ /**
+ * Returns the currently selected record.
+ * @ return {Array/Ext.data.Record} The record(s) representing the rows
+ */
+ getSelected: function() {
+ return this.getSelectionModel().getSelected();
+ },
+
+ /**
+ * Returns the currently selected records.
+ */
+ getSelections: function() {
+ return this.getSelectionModel().getSelections();
+ },
+
+ /**
+ * Return the currently selected torrent id.
+ * @return {String} The currently selected id.
+ */
+ getSelectedId: function() {
+ return this.getSelectionModel().getSelected().id;
+ },
+
+ /**
+ * Return the currently selected torrent ids.
+ * @return {Array} The currently selected ids.
+ */
+ getSelectedIds: function() {
+ var ids = [];
+ Ext.each(this.getSelectionModel().getSelections(), function(r) {
+ ids.push(r.id);
+ });
+ return ids;
+ },
+
+ update: function(torrents, wipe) {
+ var store = this.getStore();
+
+ // Need to perform a complete reload of the torrent grid.
+ if (wipe) {
+ store.removeAll();
+ this.torrents = {};
+ }
+
+ var newTorrents = [];
+
+ // Update and add any new torrents.
+ for (var t in torrents) {
+ var torrent = torrents[t];
+
+ if (this.torrents[t]) {
+ var record = store.getById(t);
+ record.beginEdit();
+ for (var k in torrent) {
+ if (record.get(k) != torrent[k]) {
+ record.set(k, torrent[k]);
+ }
+ }
+ record.endEdit();
+ } else {
+ var record = new Deluge.data.Torrent(torrent);
+ record.id = t;
+ this.torrents[t] = 1;
+ newTorrents.push(record);
+ }
+ }
+ store.add(newTorrents);
+
+ // Remove any torrents that should not be in the store.
+ store.each(function(record) {
+ if (!torrents[record.id]) {
+ store.remove(record);
+ delete this.torrents[record.id];
+ }
+ }, this);
+ store.commitChanges();
+
+ var sortState = store.getSortState();
+ if (!sortState) return;
+ store.sort(sortState.field, sortState.direction);
+ },
+
+ // private
+ onDisconnect: function() {
+ this.getStore().removeAll();
+ this.torrents = {};
+ },
+
+ // private
+ onTorrentsRemoved: function(torrentIds) {
+ var selModel = this.getSelectionModel();
+ Ext.each(
+ torrentIds,
+ function(torrentId) {
+ var record = this.getStore().getById(torrentId);
+ if (selModel.isSelected(record)) {
+ selModel.deselectRow(this.getStore().indexOf(record));
+ }
+ this.getStore().remove(record);
+ delete this.torrents[torrentId];
+ },
+ this
+ );
+ },
+ });
+ deluge.torrents = new Deluge.TorrentGrid();
+})();
diff --git a/deluge/ui/web/js/deluge-all/UI.js b/deluge/ui/web/js/deluge-all/UI.js
new file mode 100644
index 0000000..dec4850
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/UI.js
@@ -0,0 +1,292 @@
+/**
+ * Deluge.UI.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+/** Dummy translation arrays so Torrent states are available for gettext.js and Translators.
+ *
+ * All entries in deluge.common.TORRENT_STATE should be added here.
+ *
+ * No need to import these, just simply use the `_()` function around a status variable.
+ */
+var TORRENT_STATE_TRANSLATION = [
+ _('All'),
+ _('Active'),
+ _('Allocating'),
+ _('Checking'),
+ _('Downloading'),
+ _('Seeding'),
+ _('Paused'),
+ _('Checking'),
+ _('Queued'),
+ _('Error'),
+];
+
+/**
+ * @static
+ * @class Deluge.UI
+ * The controller for the whole interface, that ties all the components
+ * together and handles the 2 second poll.
+ */
+deluge.ui = {
+ errorCount: 0,
+
+ filters: null,
+
+ /**
+ * @description Create all the interface components, the json-rpc client
+ * and set up various events that the UI will utilise.
+ */
+ initialize: function() {
+ deluge.add = new Deluge.add.AddWindow();
+ deluge.details = new Deluge.details.DetailsPanel();
+ deluge.connectionManager = new Deluge.ConnectionManager();
+ deluge.editTrackers = new Deluge.EditTrackersWindow();
+ deluge.login = new Deluge.LoginWindow();
+ deluge.preferences = new Deluge.preferences.PreferencesWindow();
+ deluge.sidebar = new Deluge.Sidebar();
+ deluge.statusbar = new Deluge.Statusbar();
+ deluge.toolbar = new Deluge.Toolbar();
+
+ this.detailsPanel = new Ext.Panel({
+ id: 'detailsPanel',
+ cls: 'detailsPanel',
+ region: 'south',
+ split: true,
+ height: 215,
+ minSize: 100,
+ collapsible: true,
+ layout: 'fit',
+ items: [deluge.details],
+ });
+
+ this.MainPanel = new Ext.Panel({
+ id: 'mainPanel',
+ iconCls: 'x-deluge-main-panel',
+ layout: 'border',
+ border: false,
+ tbar: deluge.toolbar,
+ items: [deluge.sidebar, this.detailsPanel, deluge.torrents],
+ bbar: deluge.statusbar,
+ });
+
+ this.Viewport = new Ext.Viewport({
+ layout: 'fit',
+ items: [this.MainPanel],
+ });
+
+ deluge.events.on('connect', this.onConnect, this);
+ deluge.events.on('disconnect', this.onDisconnect, this);
+ deluge.events.on('PluginDisabledEvent', this.onPluginDisabled, this);
+ deluge.events.on('PluginEnabledEvent', this.onPluginEnabled, this);
+ deluge.client = new Ext.ux.util.RpcClient({
+ url: deluge.config.base + 'json',
+ });
+
+ // enable all the already active plugins
+ for (var plugin in Deluge.pluginStore) {
+ plugin = Deluge.createPlugin(plugin);
+ plugin.enable();
+ deluge.plugins[plugin.name] = plugin;
+ }
+
+ // Initialize quicktips so all the tooltip configs start working.
+ Ext.QuickTips.init();
+
+ deluge.client.on(
+ 'connected',
+ function(e) {
+ deluge.login.show();
+ },
+ this,
+ { single: true }
+ );
+
+ this.update = this.update.createDelegate(this);
+ this.checkConnection = this.checkConnection.createDelegate(this);
+
+ this.originalTitle = document.title;
+ },
+
+ checkConnection: function() {
+ deluge.client.web.connected({
+ success: this.onConnectionSuccess,
+ failure: this.onConnectionError,
+ scope: this,
+ });
+ },
+
+ update: function() {
+ var filters = deluge.sidebar.getFilterStates();
+ this.oldFilters = this.filters;
+ this.filters = filters;
+
+ deluge.client.web.update_ui(Deluge.Keys.Grid, filters, {
+ success: this.onUpdate,
+ failure: this.onUpdateError,
+ scope: this,
+ });
+ deluge.details.update();
+ },
+
+ onConnectionError: function(error) {},
+
+ onConnectionSuccess: function(result) {
+ deluge.statusbar.setStatus({
+ iconCls: 'x-deluge-statusbar icon-ok',
+ text: _('Connection restored'),
+ });
+ clearInterval(this.checking);
+ if (!result) {
+ deluge.connectionManager.show();
+ }
+ },
+
+ onUpdateError: function(error) {
+ if (this.errorCount == 2) {
+ Ext.MessageBox.show({
+ title: _('Lost Connection'),
+ msg: _('The connection to the webserver has been lost!'),
+ buttons: Ext.MessageBox.OK,
+ icon: Ext.MessageBox.ERROR,
+ });
+ deluge.events.fire('disconnect');
+ deluge.statusbar.setStatus({
+ text: _('Lost connection to webserver'),
+ });
+ this.checking = setInterval(this.checkConnection, 2000);
+ }
+ this.errorCount++;
+ },
+
+ /**
+ * @static
+ * @private
+ * Updates the various components in the interface.
+ */
+ onUpdate: function(data) {
+ if (!data['connected']) {
+ deluge.connectionManager.disconnect(true);
+ return;
+ }
+
+ if (deluge.config.show_session_speed) {
+ document.title =
+ 'D: ' +
+ fsize_short(data['stats'].download_rate, true) +
+ ' U: ' +
+ fsize_short(data['stats'].upload_rate, true) +
+ ' - ' +
+ this.originalTitle;
+ }
+ if (Ext.areObjectsEqual(this.filters, this.oldFilters)) {
+ deluge.torrents.update(data['torrents']);
+ } else {
+ deluge.torrents.update(data['torrents'], true);
+ }
+ deluge.statusbar.update(data['stats']);
+ deluge.sidebar.update(data['filters']);
+ this.errorCount = 0;
+ },
+
+ /**
+ * @static
+ * @private
+ * Start the Deluge UI polling the server and update the interface.
+ */
+ onConnect: function() {
+ if (!this.running) {
+ this.running = setInterval(this.update, 2000);
+ this.update();
+ }
+ deluge.client.web.get_plugins({
+ success: this.onGotPlugins,
+ scope: this,
+ });
+ },
+
+ /**
+ * @static
+ * @private
+ */
+ onDisconnect: function() {
+ this.stop();
+ },
+
+ onGotPlugins: function(plugins) {
+ Ext.each(
+ plugins.enabled_plugins,
+ function(plugin) {
+ if (deluge.plugins[plugin]) return;
+ deluge.client.web.get_plugin_resources(plugin, {
+ success: this.onGotPluginResources,
+ scope: this,
+ });
+ },
+ this
+ );
+ },
+
+ onPluginEnabled: function(pluginName) {
+ if (deluge.plugins[pluginName]) {
+ deluge.plugins[pluginName].enable();
+ } else {
+ deluge.client.web.get_plugin_resources(pluginName, {
+ success: this.onGotPluginResources,
+ scope: this,
+ });
+ }
+ },
+
+ onGotPluginResources: function(resources) {
+ var scripts = Deluge.debug
+ ? resources.debug_scripts
+ : resources.scripts;
+ Ext.each(
+ scripts,
+ function(script) {
+ Ext.ux.JSLoader({
+ url: deluge.config.base + script,
+ onLoad: this.onPluginLoaded,
+ pluginName: resources.name,
+ });
+ },
+ this
+ );
+ },
+
+ onPluginDisabled: function(pluginName) {
+ if (deluge.plugins[pluginName]) deluge.plugins[pluginName].disable();
+ },
+
+ onPluginLoaded: function(options) {
+ // This could happen if the plugin has multiple scripts
+ if (!Deluge.hasPlugin(options.pluginName)) return;
+
+ // Enable the plugin
+ plugin = Deluge.createPlugin(options.pluginName);
+ plugin.enable();
+ deluge.plugins[plugin.name] = plugin;
+ },
+
+ /**
+ * @static
+ * Stop the Deluge UI polling the server and clear the interface.
+ */
+ stop: function() {
+ if (this.running) {
+ clearInterval(this.running);
+ this.running = false;
+ deluge.torrents.getStore().removeAll();
+ }
+ },
+};
+
+Ext.onReady(function(e) {
+ deluge.ui.initialize();
+});
diff --git a/deluge/ui/web/js/deluge-all/add/.order b/deluge/ui/web/js/deluge-all/add/.order
new file mode 100644
index 0000000..dbd1ab9
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/add/.order
@@ -0,0 +1 @@
++ Window.js
diff --git a/deluge/ui/web/js/deluge-all/add/AddWindow.js b/deluge/ui/web/js/deluge-all/add/AddWindow.js
new file mode 100644
index 0000000..89803f3
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/add/AddWindow.js
@@ -0,0 +1,321 @@
+/**
+ * Deluge.add.AddWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Ext.namespace('Deluge.add');
+
+// This override allows file upload buttons to contain icons
+Ext.override(Ext.ux.form.FileUploadField, {
+ onRender: function(ct, position) {
+ Ext.ux.form.FileUploadField.superclass.onRender.call(
+ this,
+ ct,
+ position
+ );
+
+ this.wrap = this.el.wrap({ cls: 'x-form-field-wrap x-form-file-wrap' });
+ this.el.addClass('x-form-file-text');
+ this.el.dom.removeAttribute('name');
+ this.createFileInput();
+
+ var btnCfg = Ext.applyIf(this.buttonCfg || {}, {
+ text: this.buttonText,
+ });
+ this.button = new Ext.Button(
+ Ext.apply(btnCfg, {
+ renderTo: this.wrap,
+ cls:
+ 'x-form-file-btn' +
+ (btnCfg.iconCls ? ' x-btn-text-icon' : ''),
+ })
+ );
+
+ if (this.buttonOnly) {
+ this.el.hide();
+ this.wrap.setWidth(this.button.getEl().getWidth());
+ }
+
+ this.bindListeners();
+ this.resizeEl = this.positionEl = this.wrap;
+ },
+});
+
+Deluge.add.AddWindow = Ext.extend(Deluge.add.Window, {
+ title: _('Add Torrents'),
+ layout: 'border',
+ width: 470,
+ height: 450,
+ bodyStyle: 'padding: 10px 5px;',
+ buttonAlign: 'right',
+ closeAction: 'hide',
+ closable: true,
+ plain: true,
+ iconCls: 'x-deluge-add-window-icon',
+
+ initComponent: function() {
+ Deluge.add.AddWindow.superclass.initComponent.call(this);
+
+ this.addButton(_('Cancel'), this.onCancelClick, this);
+ this.addButton(_('Add'), this.onAddClick, this);
+
+ function torrentRenderer(value, p, r) {
+ if (r.data['info_hash']) {
+ return String.format(
+ '<div class="x-deluge-add-torrent-name">{0}</div>',
+ value
+ );
+ } else {
+ return String.format(
+ '<div class="x-deluge-add-torrent-name-loading">{0}</div>',
+ value
+ );
+ }
+ }
+
+ this.list = new Ext.list.ListView({
+ store: new Ext.data.SimpleStore({
+ fields: [
+ { name: 'info_hash', mapping: 1 },
+ { name: 'text', mapping: 2 },
+ ],
+ id: 0,
+ }),
+ columns: [
+ {
+ id: 'torrent',
+ width: 150,
+ sortable: true,
+ renderer: torrentRenderer,
+ dataIndex: 'text',
+ },
+ ],
+ stripeRows: true,
+ singleSelect: true,
+ listeners: {
+ selectionchange: {
+ fn: this.onSelect,
+ scope: this,
+ },
+ },
+ hideHeaders: true,
+ autoExpandColumn: 'torrent',
+ height: '100%',
+ autoScroll: true,
+ });
+
+ this.add({
+ region: 'center',
+ items: [this.list],
+ border: false,
+ bbar: new Ext.Toolbar({
+ items: [
+ {
+ id: 'fileUploadForm',
+ xtype: 'form',
+ layout: 'fit',
+ baseCls: 'x-plain',
+ fileUpload: true,
+ items: [
+ {
+ buttonOnly: true,
+ xtype: 'fileuploadfield',
+ id: 'torrentFile',
+ name: 'file',
+ multiple: true,
+ buttonCfg: {
+ iconCls: 'x-deluge-add-file',
+ text: _('File'),
+ },
+ listeners: {
+ scope: this,
+ fileselected: this.onFileSelected,
+ },
+ },
+ ],
+ },
+ {
+ text: _('Url'),
+ iconCls: 'icon-add-url',
+ handler: this.onUrl,
+ scope: this,
+ },
+ {
+ text: _('Infohash'),
+ iconCls: 'icon-add-magnet',
+ hidden: true,
+ disabled: true,
+ },
+ '->',
+ {
+ text: _('Remove'),
+ iconCls: 'icon-remove',
+ handler: this.onRemove,
+ scope: this,
+ },
+ ],
+ }),
+ });
+
+ this.fileUploadForm = Ext.getCmp('fileUploadForm').getForm();
+ this.optionsPanel = this.add(new Deluge.add.OptionsPanel());
+ this.on('hide', this.onHide, this);
+ this.on('show', this.onShow, this);
+ },
+
+ clear: function() {
+ this.list.getStore().removeAll();
+ this.optionsPanel.clear();
+ // Reset upload form so handler fires when a canceled file is reselected
+ this.fileUploadForm.reset();
+ },
+
+ onAddClick: function() {
+ var torrents = [];
+ if (!this.list) return;
+ this.list.getStore().each(function(r) {
+ var id = r.get('info_hash');
+ torrents.push({
+ path: this.optionsPanel.getFilename(id),
+ options: this.optionsPanel.getOptions(id),
+ });
+ }, this);
+
+ deluge.client.web.add_torrents(torrents, {
+ success: function(result) {},
+ });
+ this.clear();
+ this.hide();
+ },
+
+ onCancelClick: function() {
+ this.clear();
+ this.hide();
+ },
+
+ onFile: function() {
+ if (!this.file) this.file = new Deluge.add.FileWindow();
+ this.file.show();
+ },
+
+ onHide: function() {
+ this.optionsPanel.setActiveTab(0);
+ this.optionsPanel.files.setDisabled(true);
+ this.optionsPanel.form.setDisabled(true);
+ },
+
+ onRemove: function() {
+ if (!this.list.getSelectionCount()) return;
+ var torrent = this.list.getSelectedRecords()[0];
+ if (!torrent) return;
+ this.list.getStore().remove(torrent);
+ this.optionsPanel.clear();
+
+ if (this.torrents && this.torrents[torrent.id])
+ delete this.torrents[torrent.id];
+ },
+
+ onSelect: function(list, selections) {
+ if (selections.length) {
+ var record = this.list.getRecord(selections[0]);
+ this.optionsPanel.setTorrent(record.get('info_hash'));
+ } else {
+ this.optionsPanel.files.setDisabled(true);
+ this.optionsPanel.form.setDisabled(true);
+ }
+ },
+
+ onShow: function() {
+ if (!this.url) {
+ this.url = new Deluge.add.UrlWindow();
+ this.url.on('beforeadd', this.onTorrentBeforeAdd, this);
+ this.url.on('add', this.onTorrentAdd, this);
+ }
+
+ this.optionsPanel.form.getDefaults();
+ },
+
+ onFileSelected: function() {
+ if (this.fileUploadForm.isValid()) {
+ var torrentIds = [];
+ var files = this.fileUploadForm.findField('torrentFile').value;
+ var randomId = this.createTorrentId();
+ Array.prototype.forEach.call(
+ files,
+ function(file, i) {
+ // Append index for batch of unique torrentIds.
+ var torrentId = randomId + i.toString();
+ torrentIds.push(torrentId);
+ this.onTorrentBeforeAdd(torrentId, file.name);
+ }.bind(this)
+ );
+ this.fileUploadForm.submit({
+ url: deluge.config.base + 'upload',
+ waitMsg: _('Uploading your torrent...'),
+ success: this.onUploadSuccess,
+ scope: this,
+ torrentIds: torrentIds,
+ });
+ }
+ },
+
+ onUploadSuccess: function(fp, upload) {
+ if (!upload.result.success) {
+ this.clear();
+ return;
+ }
+
+ upload.result.files.forEach(
+ function(filename, i) {
+ deluge.client.web.get_torrent_info(filename, {
+ success: this.onGotInfo,
+ scope: this,
+ filename: filename,
+ torrentId: upload.options.torrentIds[i],
+ });
+ }.bind(this)
+ );
+ this.fileUploadForm.reset();
+ },
+
+ onGotInfo: function(info, obj, response, request) {
+ info.filename = request.options.filename;
+ torrentId = request.options.torrentId;
+ this.onTorrentAdd(torrentId, info);
+ },
+
+ onTorrentBeforeAdd: function(torrentId, text) {
+ var store = this.list.getStore();
+ store.loadData([[torrentId, null, text]], true);
+ },
+
+ onTorrentAdd: function(torrentId, info) {
+ var r = this.list.getStore().getById(torrentId);
+ if (!info) {
+ Ext.MessageBox.show({
+ title: _('Error'),
+ msg: _('Not a valid torrent'),
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ icon: Ext.MessageBox.ERROR,
+ iconCls: 'x-deluge-icon-error',
+ });
+ this.list.getStore().remove(r);
+ } else {
+ r.set('info_hash', info['info_hash']);
+ r.set('text', info['name']);
+ this.list.getStore().commitChanges();
+ this.optionsPanel.addTorrent(info);
+ this.list.select(r);
+ }
+ },
+
+ onUrl: function(button, event) {
+ this.url.show();
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/add/FilesTab.js b/deluge/ui/web/js/deluge-all/add/FilesTab.js
new file mode 100644
index 0000000..a433ad6
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/add/FilesTab.js
@@ -0,0 +1,99 @@
+/**
+ * Deluge.add.FilesTab.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge.add');
+
+/**
+ * @class Deluge.add.FilesTab
+ * @extends Ext.ux.tree.TreeGrid
+ */
+Deluge.add.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
+ layout: 'fit',
+ title: _('Files'),
+
+ autoScroll: false,
+ animate: false,
+ border: false,
+ disabled: true,
+ rootVisible: false,
+
+ columns: [
+ {
+ header: _('Filename'),
+ width: 295,
+ dataIndex: 'filename',
+ },
+ {
+ header: _('Size'),
+ width: 60,
+ dataIndex: 'size',
+ tpl: new Ext.XTemplate('{size:this.fsize}', {
+ fsize: function(v) {
+ return fsize(v);
+ },
+ }),
+ },
+ {
+ header: _('Download'),
+ width: 65,
+ dataIndex: 'download',
+ tpl: new Ext.XTemplate('{download:this.format}', {
+ format: function(v) {
+ return (
+ '<div rel="chkbox" class="x-grid3-check-col' +
+ (v ? '-on' : '') +
+ '"> </div>'
+ );
+ },
+ }),
+ },
+ ],
+
+ initComponent: function() {
+ Deluge.add.FilesTab.superclass.initComponent.call(this);
+ this.on('click', this.onNodeClick, this);
+ },
+
+ clearFiles: function() {
+ var root = this.getRootNode();
+ if (!root.hasChildNodes()) return;
+ root.cascade(function(node) {
+ if (!node.parentNode || !node.getOwnerTree()) return;
+ node.remove();
+ });
+ },
+
+ setDownload: function(node, value, suppress) {
+ node.attributes.download = value;
+ node.ui.updateColumns();
+
+ if (node.isLeaf()) {
+ if (!suppress) {
+ return this.fireEvent('fileschecked', [node], value, !value);
+ }
+ } else {
+ var nodes = [node];
+ node.cascade(function(n) {
+ n.attributes.download = value;
+ n.ui.updateColumns();
+ nodes.push(n);
+ }, this);
+ if (!suppress) {
+ return this.fireEvent('fileschecked', nodes, value, !value);
+ }
+ }
+ },
+
+ onNodeClick: function(node, e) {
+ var el = new Ext.Element(e.target);
+ if (el.getAttribute('rel') == 'chkbox') {
+ this.setDownload(node, !node.attributes.download);
+ }
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/add/Infohash.js b/deluge/ui/web/js/deluge-all/add/Infohash.js
new file mode 100644
index 0000000..0105e02
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/add/Infohash.js
@@ -0,0 +1,10 @@
+/**
+ * Deluge.add.Infohash.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Ext.deluge.add');
diff --git a/deluge/ui/web/js/deluge-all/add/OptionsPanel.js b/deluge/ui/web/js/deluge-all/add/OptionsPanel.js
new file mode 100644
index 0000000..3dfb6f8
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/add/OptionsPanel.js
@@ -0,0 +1,146 @@
+/**
+ * Deluge.add.OptionsPanel.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge.add');
+
+Deluge.add.OptionsPanel = Ext.extend(Ext.TabPanel, {
+ torrents: {},
+
+ // layout options
+ region: 'south',
+ border: false,
+ activeTab: 0,
+ height: 265,
+
+ initComponent: function() {
+ Deluge.add.OptionsPanel.superclass.initComponent.call(this);
+ this.files = this.add(new Deluge.add.FilesTab());
+ this.form = this.add(new Deluge.add.OptionsTab());
+
+ this.files.on('fileschecked', this.onFilesChecked, this);
+ },
+
+ addTorrent: function(torrent) {
+ this.torrents[torrent['info_hash']] = torrent;
+ var fileIndexes = {};
+ this.walkFileTree(
+ torrent['files_tree'],
+ function(filename, type, entry, parent) {
+ if (type != 'file') return;
+ fileIndexes[entry.index] = entry.download;
+ },
+ this
+ );
+
+ var priorities = [];
+ Ext.each(Ext.keys(fileIndexes), function(index) {
+ priorities[index] = fileIndexes[index];
+ });
+
+ var oldId = this.form.optionsManager.changeId(
+ torrent['info_hash'],
+ true
+ );
+ this.form.optionsManager.setDefault('file_priorities', priorities);
+ this.form.optionsManager.changeId(oldId, true);
+ },
+
+ clear: function() {
+ this.files.clearFiles();
+ this.form.optionsManager.resetAll();
+ },
+
+ getFilename: function(torrentId) {
+ return this.torrents[torrentId]['filename'];
+ },
+
+ getOptions: function(torrentId) {
+ var oldId = this.form.optionsManager.changeId(torrentId, true);
+ var options = this.form.optionsManager.get();
+ this.form.optionsManager.changeId(oldId, true);
+ Ext.each(options['file_priorities'], function(priority, index) {
+ options['file_priorities'][index] = priority ? 1 : 0;
+ });
+ return options;
+ },
+
+ setTorrent: function(torrentId) {
+ if (!torrentId) return;
+
+ this.torrentId = torrentId;
+ this.form.optionsManager.changeId(torrentId);
+
+ this.files.clearFiles();
+ var root = this.files.getRootNode();
+ var priorities = this.form.optionsManager.get('file_priorities');
+
+ this.form.setDisabled(false);
+
+ if (this.torrents[torrentId]['files_tree']) {
+ this.walkFileTree(
+ this.torrents[torrentId]['files_tree'],
+ function(filename, type, entry, parentNode) {
+ var node = new Ext.tree.TreeNode({
+ download: entry.index ? priorities[entry.index] : true,
+ filename: filename,
+ fileindex: entry.index,
+ leaf: type != 'dir',
+ size: entry.length,
+ });
+ parentNode.appendChild(node);
+ if (type == 'dir') return node;
+ },
+ this,
+ root
+ );
+ root.firstChild.expand();
+ this.files.setDisabled(false);
+ this.files.show();
+ } else {
+ // Files tab is empty so show options tab
+ this.form.show();
+ this.files.setDisabled(true);
+ }
+ },
+
+ walkFileTree: function(files, callback, scope, parentNode) {
+ for (var filename in files.contents) {
+ var entry = files.contents[filename];
+ var type = entry.type;
+
+ if (scope) {
+ var ret = callback.apply(scope, [
+ filename,
+ type,
+ entry,
+ parentNode,
+ ]);
+ } else {
+ var ret = callback(filename, type, entry, parentNode);
+ }
+
+ if (type == 'dir') this.walkFileTree(entry, callback, scope, ret);
+ }
+ },
+
+ onFilesChecked: function(nodes, newValue, oldValue) {
+ Ext.each(
+ nodes,
+ function(node) {
+ if (node.attributes.fileindex < 0) return;
+ var priorities = this.form.optionsManager.get(
+ 'file_priorities'
+ );
+ priorities[node.attributes.fileindex] = newValue;
+ this.form.optionsManager.update('file_priorities', priorities);
+ },
+ this
+ );
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/add/OptionsTab.js b/deluge/ui/web/js/deluge-all/add/OptionsTab.js
new file mode 100644
index 0000000..e897b17
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/add/OptionsTab.js
@@ -0,0 +1,217 @@
+/**
+ * Deluge.add.OptionsPanel.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge.add');
+
+/**
+ * @class Deluge.add.OptionsTab
+ * @extends Ext.form.FormPanel
+ */
+Deluge.add.OptionsTab = Ext.extend(Ext.form.FormPanel, {
+ title: _('Options'),
+ height: 170,
+ border: false,
+ bodyStyle: 'padding: 5px',
+ disabled: true,
+ labelWidth: 1,
+
+ initComponent: function() {
+ Deluge.add.OptionsTab.superclass.initComponent.call(this);
+
+ this.optionsManager = new Deluge.MultiOptionsManager();
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ title: _('Download Folder'),
+ border: false,
+ autoHeight: true,
+ defaultType: 'textfield',
+ labelWidth: 1,
+ fieldLabel: '',
+ style: 'padding: 5px 0; margin-bottom: 0;',
+ });
+ this.optionsManager.bind(
+ 'download_location',
+ fieldset.add({
+ fieldLabel: '',
+ name: 'download_location',
+ anchor: '95%',
+ labelSeparator: '',
+ })
+ );
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ title: _('Move Completed Folder'),
+ border: false,
+ autoHeight: true,
+ defaultType: 'togglefield',
+ labelWidth: 1,
+ fieldLabel: '',
+ style: 'padding: 5px 0; margin-bottom: 0;',
+ });
+ var field = fieldset.add({
+ fieldLabel: '',
+ name: 'move_completed_path',
+ anchor: '98%',
+ });
+ this.optionsManager.bind('move_completed', field.toggle);
+ this.optionsManager.bind('move_completed_path', field.input);
+
+ var panel = this.add({
+ border: false,
+ layout: 'column',
+ defaultType: 'fieldset',
+ });
+
+ fieldset = panel.add({
+ title: _('Bandwidth'),
+ border: false,
+ autoHeight: true,
+ bodyStyle: 'padding: 2px 5px',
+ labelWidth: 105,
+ width: 200,
+ defaultType: 'spinnerfield',
+ style: 'padding-right: 10px;',
+ });
+ this.optionsManager.bind(
+ 'max_download_speed',
+ fieldset.add({
+ fieldLabel: _('Max Down Speed'),
+ name: 'max_download_speed',
+ width: 60,
+ })
+ );
+ this.optionsManager.bind(
+ 'max_upload_speed',
+ fieldset.add({
+ fieldLabel: _('Max Up Speed'),
+ name: 'max_upload_speed',
+ width: 60,
+ })
+ );
+ this.optionsManager.bind(
+ 'max_connections',
+ fieldset.add({
+ fieldLabel: _('Max Connections'),
+ name: 'max_connections',
+ width: 60,
+ })
+ );
+ this.optionsManager.bind(
+ 'max_upload_slots',
+ fieldset.add({
+ fieldLabel: _('Max Upload Slots'),
+ name: 'max_upload_slots',
+ width: 60,
+ })
+ );
+
+ fieldset = panel.add({
+ // title: _('General'),
+ border: false,
+ autoHeight: true,
+ defaultType: 'checkbox',
+ });
+ this.optionsManager.bind(
+ 'add_paused',
+ fieldset.add({
+ name: 'add_paused',
+ boxLabel: _('Add In Paused State'),
+ fieldLabel: '',
+ labelSeparator: '',
+ })
+ );
+ this.optionsManager.bind(
+ 'prioritize_first_last_pieces',
+ fieldset.add({
+ name: 'prioritize_first_last_pieces',
+ boxLabel: _('Prioritize First/Last Pieces'),
+ fieldLabel: '',
+ labelSeparator: '',
+ })
+ );
+ this.optionsManager.bind(
+ 'sequential_download',
+ fieldset.add({
+ name: 'sequential_download',
+ boxLabel: _('Sequential Download'),
+ fieldLabel: '',
+ labelSeparator: '',
+ })
+ );
+ this.optionsManager.bind(
+ 'seed_mode',
+ fieldset.add({
+ name: 'seed_mode',
+ boxLabel: _('Skip File Hash Check'),
+ fieldLabel: '',
+ labelSeparator: '',
+ })
+ );
+ this.optionsManager.bind(
+ 'super_seeding',
+ fieldset.add({
+ name: 'super_seeding',
+ boxLabel: _('Super Seed'),
+ fieldLabel: '',
+ labelSeparator: '',
+ })
+ );
+ this.optionsManager.bind(
+ 'pre_allocate_storage',
+ fieldset.add({
+ name: 'pre_allocate_storage',
+ boxLabel: _('Preallocate Disk Space'),
+ fieldLabel: '',
+ labelSeparator: '',
+ })
+ );
+ },
+
+ getDefaults: function() {
+ var keys = [
+ 'add_paused',
+ 'pre_allocate_storage',
+ 'download_location',
+ 'max_connections_per_torrent',
+ 'max_download_speed_per_torrent',
+ 'move_completed',
+ 'move_completed_path',
+ 'max_upload_slots_per_torrent',
+ 'max_upload_speed_per_torrent',
+ 'prioritize_first_last_pieces',
+ 'sequential_download',
+ ];
+
+ deluge.client.core.get_config_values(keys, {
+ success: function(config) {
+ var options = {
+ file_priorities: [],
+ add_paused: config.add_paused,
+ sequential_download: config.sequential_download,
+ pre_allocate_storage: config.pre_allocate_storage,
+ download_location: config.download_location,
+ move_completed: config.move_completed,
+ move_completed_path: config.move_completed_path,
+ max_connections: config.max_connections_per_torrent,
+ max_download_speed: config.max_download_speed_per_torrent,
+ max_upload_slots: config.max_upload_slots_per_torrent,
+ max_upload_speed: config.max_upload_speed_per_torrent,
+ prioritize_first_last_pieces:
+ config.prioritize_first_last_pieces,
+ seed_mode: false,
+ super_seeding: false,
+ };
+ this.optionsManager.options = options;
+ this.optionsManager.resetAll();
+ },
+ scope: this,
+ });
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/add/UrlWindow.js b/deluge/ui/web/js/deluge-all/add/UrlWindow.js
new file mode 100644
index 0000000..d3a9a69
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/add/UrlWindow.js
@@ -0,0 +1,98 @@
+/**
+ * Deluge.add.UrlWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Ext.namespace('Deluge.add');
+Deluge.add.UrlWindow = Ext.extend(Deluge.add.Window, {
+ title: _('Add from Url'),
+ modal: true,
+ plain: true,
+ layout: 'fit',
+ width: 350,
+ height: 155,
+
+ buttonAlign: 'center',
+ closeAction: 'hide',
+ bodyStyle: 'padding: 10px 5px;',
+ iconCls: 'x-deluge-add-url-window-icon',
+
+ initComponent: function() {
+ Deluge.add.UrlWindow.superclass.initComponent.call(this);
+ this.addButton(_('Add'), this.onAddClick, this);
+
+ var form = this.add({
+ xtype: 'form',
+ defaultType: 'textfield',
+ baseCls: 'x-plain',
+ labelWidth: 55,
+ });
+
+ this.urlField = form.add({
+ fieldLabel: _('Url'),
+ id: 'url',
+ name: 'url',
+ width: '97%',
+ });
+ this.urlField.on('specialkey', this.onAdd, this);
+
+ this.cookieField = form.add({
+ fieldLabel: _('Cookies'),
+ id: 'cookies',
+ name: 'cookies',
+ width: '97%',
+ });
+ this.cookieField.on('specialkey', this.onAdd, this);
+ },
+
+ onAddClick: function(field, e) {
+ if (
+ (field.id == 'url' || field.id == 'cookies') &&
+ e.getKey() != e.ENTER
+ )
+ return;
+
+ var field = this.urlField;
+ var url = field.getValue();
+ var cookies = this.cookieField.getValue();
+ var torrentId = this.createTorrentId();
+
+ if (url.indexOf('magnet:?') == 0 && url.indexOf('xt=urn:btih') > -1) {
+ deluge.client.web.get_magnet_info(url, {
+ success: this.onGotInfo,
+ scope: this,
+ filename: url,
+ torrentId: torrentId,
+ });
+ } else {
+ deluge.client.web.download_torrent_from_url(url, cookies, {
+ success: this.onDownload,
+ scope: this,
+ torrentId: torrentId,
+ });
+ }
+
+ this.hide();
+ this.urlField.setValue('');
+ this.fireEvent('beforeadd', torrentId, url);
+ },
+
+ onDownload: function(filename, obj, resp, req) {
+ deluge.client.web.get_torrent_info(filename, {
+ success: this.onGotInfo,
+ scope: this,
+ filename: filename,
+ torrentId: req.options.torrentId,
+ });
+ },
+
+ onGotInfo: function(info, obj, response, request) {
+ info['filename'] = request.options.filename;
+ this.fireEvent('add', request.options.torrentId, info);
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/add/Window.js b/deluge/ui/web/js/deluge-all/add/Window.js
new file mode 100644
index 0000000..206b3ee
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/add/Window.js
@@ -0,0 +1,29 @@
+/**
+ * Deluge.add.Window.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge.add');
+
+/**
+ * @class Deluge.add.Window
+ * @extends Ext.Window
+ * Base class for an add Window
+ */
+Deluge.add.Window = Ext.extend(Ext.Window, {
+ initComponent: function() {
+ Deluge.add.Window.superclass.initComponent.call(this);
+ this.addEvents('beforeadd', 'add');
+ },
+
+ /**
+ * Create an id for the torrent before we have any info about it.
+ */
+ createTorrentId: function() {
+ return new Date().getTime().toString();
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/data/.order b/deluge/ui/web/js/deluge-all/data/.order
new file mode 100644
index 0000000..f9befc4
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/data/.order
@@ -0,0 +1 @@
++ SortTypes.js
diff --git a/deluge/ui/web/js/deluge-all/data/PeerRecord.js b/deluge/ui/web/js/deluge-all/data/PeerRecord.js
new file mode 100644
index 0000000..7f33769
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/data/PeerRecord.js
@@ -0,0 +1,53 @@
+/**
+ * Deluge.data.PeerRecord.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.data');
+
+/**
+ * Deluge.data.Peer record
+ *
+ * @author Damien Churchill <damoxc@gmail.com>
+ * @version 1.3
+ *
+ * @class Deluge.data.Peer
+ * @extends Ext.data.Record
+ * @constructor
+ * @param {Object} data The peer data
+ */
+Deluge.data.Peer = Ext.data.Record.create([
+ {
+ name: 'country',
+ type: 'string',
+ },
+ {
+ name: 'ip',
+ type: 'string',
+ sortType: Deluge.data.SortTypes.asIPAddress,
+ },
+ {
+ name: 'client',
+ type: 'string',
+ },
+ {
+ name: 'progress',
+ type: 'float',
+ },
+ {
+ name: 'down_speed',
+ type: 'int',
+ },
+ {
+ name: 'up_speed',
+ type: 'int',
+ },
+ {
+ name: 'seed',
+ type: 'int',
+ },
+]);
diff --git a/deluge/ui/web/js/deluge-all/data/SortTypes.js b/deluge/ui/web/js/deluge-all/data/SortTypes.js
new file mode 100644
index 0000000..199f895
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/data/SortTypes.js
@@ -0,0 +1,37 @@
+/**
+ * Deluge.data.SortTypes.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.data');
+
+/**
+ * Common sort functions that can be used for data Stores.
+ *
+ * @author Damien Churchill <damoxc@gmail.com>
+ * @version 1.3
+ *
+ * @class Deluge.data.SortTypes
+ * @singleton
+ */
+Deluge.data.SortTypes = {
+ // prettier-ignore
+ asIPAddress: function(value) {
+ var d = value.match(
+ /(\d{1,3})\.(\d{1,3})\.(\d{1,3})\.(\d{1,3})\:(\d+)/
+ );
+ return ((+d[1] * 256 + (+d[2])) * 256 + (+d[3])) * 256 + (+d[4]);
+ },
+
+ asQueuePosition: function(value) {
+ return value > -1 ? value : Number.MAX_VALUE;
+ },
+
+ asName: function(value) {
+ return String(value).toLowerCase();
+ },
+};
diff --git a/deluge/ui/web/js/deluge-all/data/TorrentRecord.js b/deluge/ui/web/js/deluge-all/data/TorrentRecord.js
new file mode 100644
index 0000000..e510234
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/data/TorrentRecord.js
@@ -0,0 +1,121 @@
+/**
+ * Deluge.data.TorrentRecord.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.data');
+
+/**
+ * Deluge.data.Torrent record
+ *
+ * @author Damien Churchill <damoxc@gmail.com>
+ * @version 1.3
+ *
+ * @class Deluge.data.Torrent
+ * @extends Ext.data.Record
+ * @constructor
+ * @param {Object} data The torrents data
+ */
+Deluge.data.Torrent = Ext.data.Record.create([
+ {
+ name: 'queue',
+ type: 'int',
+ },
+ {
+ name: 'name',
+ type: 'string',
+ sortType: Deluge.data.SortTypes.asName,
+ },
+ {
+ name: 'total_wanted',
+ type: 'int',
+ },
+ {
+ name: 'state',
+ type: 'string',
+ },
+ {
+ name: 'progress',
+ type: 'int',
+ },
+ {
+ name: 'num_seeds',
+ type: 'int',
+ },
+ {
+ name: 'total_seeds',
+ type: 'int',
+ },
+ {
+ name: 'num_peers',
+ type: 'int',
+ },
+ {
+ name: 'total_peers',
+ type: 'int',
+ },
+ {
+ name: 'download_payload_rate',
+ type: 'int',
+ },
+ {
+ name: 'upload_payload_rate',
+ type: 'int',
+ },
+ {
+ name: 'eta',
+ type: 'int',
+ },
+ {
+ name: 'ratio',
+ type: 'float',
+ },
+ {
+ name: 'distributed_copies',
+ type: 'float',
+ },
+ {
+ name: 'time_added',
+ type: 'int',
+ },
+ {
+ name: 'tracker_host',
+ type: 'string',
+ },
+ {
+ name: 'save_path',
+ type: 'string',
+ },
+ {
+ name: 'total_done',
+ type: 'int',
+ },
+ {
+ name: 'total_uploaded',
+ type: 'int',
+ },
+ {
+ name: 'total_remaining',
+ type: 'int',
+ },
+ {
+ name: 'max_download_speed',
+ type: 'int',
+ },
+ {
+ name: 'max_upload_speed',
+ type: 'int',
+ },
+ {
+ name: 'seeds_peers_ratio',
+ type: 'float',
+ },
+ {
+ name: 'time_since_transfer',
+ type: 'int',
+ },
+]);
diff --git a/deluge/ui/web/js/deluge-all/details/DetailsPanel.js b/deluge/ui/web/js/deluge-all/details/DetailsPanel.js
new file mode 100644
index 0000000..1c51de4
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/details/DetailsPanel.js
@@ -0,0 +1,81 @@
+/**
+ * Deluge.details.DetailsPanel.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.details');
+
+/**
+ * @class Deluge.details.DetailsPanel
+ */
+Deluge.details.DetailsPanel = Ext.extend(Ext.TabPanel, {
+ id: 'torrentDetails',
+ activeTab: 0,
+
+ initComponent: function() {
+ Deluge.details.DetailsPanel.superclass.initComponent.call(this);
+ this.add(new Deluge.details.StatusTab());
+ this.add(new Deluge.details.DetailsTab());
+ this.add(new Deluge.details.FilesTab());
+ this.add(new Deluge.details.PeersTab());
+ this.add(new Deluge.details.OptionsTab());
+ },
+
+ clear: function() {
+ this.items.each(function(panel) {
+ if (panel.clear) {
+ panel.clear.defer(100, panel);
+ panel.disable();
+ }
+ });
+ },
+
+ update: function(tab) {
+ var torrent = deluge.torrents.getSelected();
+ if (!torrent) {
+ this.clear();
+ return;
+ }
+
+ this.items.each(function(tab) {
+ if (tab.disabled) tab.enable();
+ });
+
+ tab = tab || this.getActiveTab();
+ if (tab.update) tab.update(torrent.id);
+ },
+
+ /* Event Handlers */
+
+ // We need to add the events in onRender since Deluge.Torrents has not been created yet.
+ onRender: function(ct, position) {
+ Deluge.details.DetailsPanel.superclass.onRender.call(
+ this,
+ ct,
+ position
+ );
+ deluge.events.on('disconnect', this.clear, this);
+ deluge.torrents.on('rowclick', this.onTorrentsClick, this);
+ this.on('tabchange', this.onTabChange, this);
+
+ deluge.torrents.getSelectionModel().on(
+ 'selectionchange',
+ function(selModel) {
+ if (!selModel.hasSelection()) this.clear();
+ },
+ this
+ );
+ },
+
+ onTabChange: function(panel, tab) {
+ this.update(tab);
+ },
+
+ onTorrentsClick: function(grid, rowIndex, e) {
+ this.update();
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/details/DetailsTab.js b/deluge/ui/web/js/deluge-all/details/DetailsTab.js
new file mode 100644
index 0000000..84929ae
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/details/DetailsTab.js
@@ -0,0 +1,98 @@
+/**
+ * Deluge.Details.Details.js
+ * The details tab displayed in the details panel.
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Deluge.details.DetailsTab = Ext.extend(Ext.Panel, {
+ title: _('Details'),
+
+ fields: {},
+ autoScroll: true,
+ queuedItems: {},
+
+ oldData: {},
+
+ initComponent: function() {
+ Deluge.details.DetailsTab.superclass.initComponent.call(this);
+ this.addItem('torrent_name', _('Name:'));
+ this.addItem('hash', _('Hash:'));
+ this.addItem('path', _('Download Folder:'));
+ this.addItem('size', _('Total Size:'));
+ this.addItem('files', _('Total Files:'));
+ this.addItem('comment', _('Comment:'));
+ this.addItem('status', _('Status:'));
+ this.addItem('tracker', _('Tracker:'));
+ this.addItem('creator', _('Created By:'));
+ },
+
+ onRender: function(ct, position) {
+ Deluge.details.DetailsTab.superclass.onRender.call(this, ct, position);
+ this.body.setStyle('padding', '10px');
+ this.dl = Ext.DomHelper.append(this.body, { tag: 'dl' }, true);
+
+ for (var id in this.queuedItems) {
+ this.doAddItem(id, this.queuedItems[id]);
+ }
+ },
+
+ addItem: function(id, label) {
+ if (!this.rendered) {
+ this.queuedItems[id] = label;
+ } else {
+ this.doAddItem(id, label);
+ }
+ },
+
+ // private
+ doAddItem: function(id, label) {
+ Ext.DomHelper.append(this.dl, { tag: 'dt', cls: id, html: label });
+ this.fields[id] = Ext.DomHelper.append(
+ this.dl,
+ { tag: 'dd', cls: id, html: '' },
+ true
+ );
+ },
+
+ clear: function() {
+ if (!this.fields) return;
+ for (var k in this.fields) {
+ this.fields[k].dom.innerHTML = '';
+ }
+ this.oldData = {};
+ },
+
+ update: function(torrentId) {
+ deluge.client.web.get_torrent_status(torrentId, Deluge.Keys.Details, {
+ success: this.onRequestComplete,
+ scope: this,
+ torrentId: torrentId,
+ });
+ },
+
+ onRequestComplete: function(torrent, request, response, options) {
+ var data = {
+ torrent_name: torrent.name,
+ hash: options.options.torrentId,
+ path: torrent.download_location,
+ size: fsize(torrent.total_size),
+ files: torrent.num_files,
+ status: torrent.message,
+ tracker: torrent.tracker_host,
+ comment: torrent.comment,
+ creator: torrent.creator,
+ };
+
+ for (var field in this.fields) {
+ if (!Ext.isDefined(data[field])) continue; // This is a field we are not responsible for.
+ if (data[field] == this.oldData[field]) continue;
+ this.fields[field].dom.innerHTML = Ext.escapeHTML(data[field]);
+ }
+ this.oldData = data;
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/details/FilesTab.js b/deluge/ui/web/js/deluge-all/details/FilesTab.js
new file mode 100644
index 0000000..3a212fa
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/details/FilesTab.js
@@ -0,0 +1,233 @@
+/**
+ * Deluge.details.FilesTab.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Deluge.details.FilesTab = Ext.extend(Ext.ux.tree.TreeGrid, {
+ title: _('Files'),
+
+ rootVisible: false,
+
+ columns: [
+ {
+ header: _('Filename'),
+ width: 330,
+ dataIndex: 'filename',
+ },
+ {
+ header: _('Size'),
+ width: 150,
+ dataIndex: 'size',
+ tpl: new Ext.XTemplate('{size:this.fsize}', {
+ fsize: function(v) {
+ return fsize(v);
+ },
+ }),
+ },
+ {
+ xtype: 'tgrendercolumn',
+ header: _('Progress'),
+ width: 150,
+ dataIndex: 'progress',
+ renderer: function(v) {
+ var progress = v * 100;
+ return Deluge.progressBar(
+ progress,
+ this.col.width,
+ progress.toFixed(2) + '%',
+ 0
+ );
+ },
+ },
+ {
+ header: _('Priority'),
+ width: 150,
+ dataIndex: 'priority',
+ tpl: new Ext.XTemplate(
+ '<tpl if="!isNaN(priority)">' +
+ '<div class="{priority:this.getClass}">' +
+ '{priority:this.getName}' +
+ '</div></tpl>',
+ {
+ getClass: function(v) {
+ return FILE_PRIORITY_CSS[v];
+ },
+
+ getName: function(v) {
+ return _(FILE_PRIORITY[v]);
+ },
+ }
+ ),
+ },
+ ],
+
+ selModel: new Ext.tree.MultiSelectionModel(),
+
+ initComponent: function() {
+ Deluge.details.FilesTab.superclass.initComponent.call(this);
+ this.setRootNode(new Ext.tree.TreeNode({ text: _('Files') }));
+ },
+
+ clear: function() {
+ var root = this.getRootNode();
+ if (!root.hasChildNodes()) return;
+ root.cascade(function(node) {
+ var parentNode = node.parentNode;
+ if (!parentNode) return;
+ if (!parentNode.ownerTree) return;
+ parentNode.removeChild(node);
+ });
+ },
+
+ createFileTree: function(files) {
+ function walk(files, parentNode) {
+ for (var file in files.contents) {
+ var item = files.contents[file];
+ if (item.type == 'dir') {
+ walk(
+ item,
+ parentNode.appendChild(
+ new Ext.tree.TreeNode({
+ text: file,
+ filename: file,
+ size: item.size,
+ progress: item.progress,
+ priority: item.priority,
+ })
+ )
+ );
+ } else {
+ parentNode.appendChild(
+ new Ext.tree.TreeNode({
+ text: file,
+ filename: file,
+ fileIndex: item.index,
+ size: item.size,
+ progress: item.progress,
+ priority: item.priority,
+ leaf: true,
+ iconCls: 'x-deluge-file',
+ uiProvider: Ext.ux.tree.TreeGridNodeUI,
+ })
+ );
+ }
+ }
+ }
+ var root = this.getRootNode();
+ walk(files, root);
+ root.firstChild.expand();
+ },
+
+ update: function(torrentId) {
+ if (this.torrentId != torrentId) {
+ this.clear();
+ this.torrentId = torrentId;
+ }
+
+ deluge.client.web.get_torrent_files(torrentId, {
+ success: this.onRequestComplete,
+ scope: this,
+ torrentId: torrentId,
+ });
+ },
+
+ updateFileTree: function(files) {
+ function walk(files, parentNode) {
+ for (var file in files.contents) {
+ var item = files.contents[file];
+ var node = parentNode.findChild('filename', file);
+ node.attributes.size = item.size;
+ node.attributes.progress = item.progress;
+ node.attributes.priority = item.priority;
+ node.ui.updateColumns();
+ if (item.type == 'dir') {
+ walk(item, node);
+ }
+ }
+ }
+ walk(files, this.getRootNode());
+ },
+
+ onRender: function(ct, position) {
+ Deluge.details.FilesTab.superclass.onRender.call(this, ct, position);
+ deluge.menus.filePriorities.on('itemclick', this.onItemClick, this);
+ this.on('contextmenu', this.onContextMenu, this);
+ this.sorter = new Ext.tree.TreeSorter(this, {
+ folderSort: true,
+ });
+ },
+
+ onContextMenu: function(node, e) {
+ e.stopEvent();
+ var selModel = this.getSelectionModel();
+ if (selModel.getSelectedNodes().length < 2) {
+ selModel.clearSelections();
+ node.select();
+ }
+ deluge.menus.filePriorities.showAt(e.getPoint());
+ },
+
+ onItemClick: function(baseItem, e) {
+ switch (baseItem.id) {
+ case 'expandAll':
+ this.expandAll();
+ break;
+ default:
+ var indexes = {};
+ var walk = function(node) {
+ if (Ext.isEmpty(node.attributes.fileIndex)) return;
+ indexes[node.attributes.fileIndex] =
+ node.attributes.priority;
+ };
+ this.getRootNode().cascade(walk);
+
+ var nodes = this.getSelectionModel().getSelectedNodes();
+ Ext.each(nodes, function(node) {
+ if (!node.isLeaf()) {
+ var setPriorities = function(node) {
+ if (Ext.isEmpty(node.attributes.fileIndex)) return;
+ indexes[node.attributes.fileIndex] =
+ baseItem.filePriority;
+ };
+ node.cascade(setPriorities);
+ } else if (!Ext.isEmpty(node.attributes.fileIndex)) {
+ indexes[node.attributes.fileIndex] =
+ baseItem.filePriority;
+ return;
+ }
+ });
+
+ var priorities = new Array(Ext.keys(indexes).length);
+ for (var index in indexes) {
+ priorities[index] = indexes[index];
+ }
+
+ deluge.client.core.set_torrent_options(
+ [this.torrentId],
+ { file_priorities: priorities },
+ {
+ success: function() {
+ Ext.each(nodes, function(node) {
+ node.setColumnValue(3, baseItem.filePriority);
+ });
+ },
+ scope: this,
+ }
+ );
+ break;
+ }
+ },
+
+ onRequestComplete: function(files, options) {
+ if (!this.getRootNode().hasChildNodes()) {
+ this.createFileTree(files);
+ } else {
+ this.updateFileTree(files);
+ }
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/details/OptionsTab.js b/deluge/ui/web/js/deluge-all/details/OptionsTab.js
new file mode 100644
index 0000000..b11486b
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/details/OptionsTab.js
@@ -0,0 +1,417 @@
+/**
+ * Deluge.details.OptionsTab.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Deluge.details.OptionsTab = Ext.extend(Ext.form.FormPanel, {
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ autoScroll: true,
+ bodyStyle: 'padding: 5px;',
+ border: false,
+ cls: 'x-deluge-options',
+ defaults: {
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ },
+ deferredRender: false,
+ layout: 'column',
+ title: _('Options'),
+ },
+ config
+ );
+ Deluge.details.OptionsTab.superclass.constructor.call(this, config);
+ },
+
+ initComponent: function() {
+ Deluge.details.OptionsTab.superclass.initComponent.call(this);
+
+ (this.fieldsets = {}), (this.fields = {});
+ this.optionsManager = new Deluge.MultiOptionsManager({
+ options: {
+ max_download_speed: -1,
+ max_upload_speed: -1,
+ max_connections: -1,
+ max_upload_slots: -1,
+ auto_managed: false,
+ stop_at_ratio: false,
+ stop_ratio: 2.0,
+ remove_at_ratio: false,
+ move_completed: false,
+ move_completed_path: '',
+ private: false,
+ prioritize_first_last: false,
+ super_seeding: false,
+ },
+ });
+
+ /*
+ * Bandwidth Options
+ */
+ this.fieldsets.bandwidth = this.add({
+ xtype: 'fieldset',
+ defaultType: 'spinnerfield',
+ bodyStyle: 'padding: 5px',
+
+ layout: 'table',
+ layoutConfig: { columns: 3 },
+ labelWidth: 150,
+
+ style: 'margin-left: 10px; margin-right: 5px; padding: 5px',
+ title: _('Bandwidth'),
+ width: 250,
+ });
+
+ /*
+ * Max Download Speed
+ */
+ this.fieldsets.bandwidth.add({
+ xtype: 'label',
+ text: _('Max Download Speed:'),
+ forId: 'max_download_speed',
+ cls: 'x-deluge-options-label',
+ });
+ this.fields.max_download_speed = this.fieldsets.bandwidth.add({
+ id: 'max_download_speed',
+ name: 'max_download_speed',
+ width: 70,
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 1,
+ minValue: -1,
+ maxValue: 99999,
+ },
+ });
+ this.fieldsets.bandwidth.add({
+ xtype: 'label',
+ text: _('KiB/s'),
+ style: 'margin-left: 10px',
+ });
+
+ /*
+ * Max Upload Speed
+ */
+ this.fieldsets.bandwidth.add({
+ xtype: 'label',
+ text: _('Max Upload Speed:'),
+ forId: 'max_upload_speed',
+ cls: 'x-deluge-options-label',
+ });
+ this.fields.max_upload_speed = this.fieldsets.bandwidth.add({
+ id: 'max_upload_speed',
+ name: 'max_upload_speed',
+ width: 70,
+ value: -1,
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 1,
+ minValue: -1,
+ maxValue: 99999,
+ },
+ });
+ this.fieldsets.bandwidth.add({
+ xtype: 'label',
+ text: _('KiB/s'),
+ style: 'margin-left: 10px',
+ });
+
+ /*
+ * Max Connections
+ */
+ this.fieldsets.bandwidth.add({
+ xtype: 'label',
+ text: _('Max Connections:'),
+ forId: 'max_connections',
+ cls: 'x-deluge-options-label',
+ });
+ this.fields.max_connections = this.fieldsets.bandwidth.add({
+ id: 'max_connections',
+ name: 'max_connections',
+ width: 70,
+ value: -1,
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 99999,
+ },
+ colspan: 2,
+ });
+
+ /*
+ * Max Upload Slots
+ */
+ this.fieldsets.bandwidth.add({
+ xtype: 'label',
+ text: _('Max Upload Slots:'),
+ forId: 'max_upload_slots',
+ cls: 'x-deluge-options-label',
+ });
+ this.fields.max_upload_slots = this.fieldsets.bandwidth.add({
+ id: 'max_upload_slots',
+ name: 'max_upload_slots',
+ width: 70,
+ value: -1,
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 99999,
+ },
+ colspan: 2,
+ });
+
+ /*
+ * Queue Options
+ */
+ this.fieldsets.queue = this.add({
+ xtype: 'fieldset',
+ title: _('Queue'),
+ style: 'margin-left: 5px; margin-right: 5px; padding: 5px',
+ width: 210,
+
+ layout: 'table',
+ layoutConfig: { columns: 2 },
+ labelWidth: 0,
+
+ defaults: {
+ fieldLabel: '',
+ labelSeparator: '',
+ },
+ });
+
+ this.fields.auto_managed = this.fieldsets.queue.add({
+ xtype: 'checkbox',
+ fieldLabel: '',
+ labelSeparator: '',
+ name: 'is_auto_managed',
+ boxLabel: _('Auto Managed'),
+ width: 200,
+ colspan: 2,
+ });
+
+ this.fields.stop_at_ratio = this.fieldsets.queue.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ id: 'stop_at_ratio',
+ width: 120,
+ boxLabel: _('Stop seed at ratio:'),
+ handler: this.onStopRatioChecked,
+ scope: this,
+ });
+
+ this.fields.stop_ratio = this.fieldsets.queue.add({
+ xtype: 'spinnerfield',
+ id: 'stop_ratio',
+ name: 'stop_ratio',
+ disabled: true,
+ width: 50,
+ value: 2.0,
+ strategy: {
+ xtype: 'number',
+ minValue: -1,
+ maxValue: 99999,
+ incrementValue: 0.1,
+ alternateIncrementValue: 1,
+ decimalPrecision: 1,
+ },
+ });
+
+ this.fields.remove_at_ratio = this.fieldsets.queue.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ id: 'remove_at_ratio',
+ ctCls: 'x-deluge-indent-checkbox',
+ bodyStyle: 'padding-left: 10px',
+ boxLabel: _('Remove at ratio'),
+ disabled: true,
+ colspan: 2,
+ });
+
+ this.fields.move_completed = this.fieldsets.queue.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ id: 'move_completed',
+ boxLabel: _('Move Completed:'),
+ colspan: 2,
+ handler: this.onMoveCompletedChecked,
+ scope: this,
+ });
+
+ this.fields.move_completed_path = this.fieldsets.queue.add({
+ xtype: 'textfield',
+ fieldLabel: '',
+ id: 'move_completed_path',
+ colspan: 3,
+ bodyStyle: 'margin-left: 20px',
+ width: 180,
+ disabled: true,
+ });
+
+ /*
+ * General Options
+ */
+ this.rightColumn = this.add({
+ border: false,
+ autoHeight: true,
+ style: 'margin-left: 5px',
+ width: 210,
+ });
+
+ this.fieldsets.general = this.rightColumn.add({
+ xtype: 'fieldset',
+ autoHeight: true,
+ defaultType: 'checkbox',
+ title: _('General'),
+ layout: 'form',
+ });
+
+ this.fields['private'] = this.fieldsets.general.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('Private'),
+ id: 'private',
+ disabled: true,
+ });
+
+ this.fields.prioritize_first_last = this.fieldsets.general.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('Prioritize First/Last'),
+ id: 'prioritize_first_last',
+ });
+
+ this.fields.super_seeding = this.fieldsets.general.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('Super Seeding'),
+ id: 'super_seeding',
+ });
+
+ // Bind the fields so the options manager can manage them.
+ for (var id in this.fields) {
+ this.optionsManager.bind(id, this.fields[id]);
+ }
+
+ /*
+ * Buttons
+ */
+ this.buttonPanel = this.rightColumn.add({
+ layout: 'hbox',
+ xtype: 'panel',
+ border: false,
+ });
+
+ /*
+ * Edit Trackers button
+ */
+ this.buttonPanel.add({
+ id: 'edit_trackers',
+ xtype: 'button',
+ text: _('Edit Trackers'),
+ cls: 'x-btn-text-icon',
+ iconCls: 'x-deluge-edit-trackers',
+ border: false,
+ width: 100,
+ handler: this.onEditTrackers,
+ scope: this,
+ });
+
+ /*
+ * Apply button
+ */
+ this.buttonPanel.add({
+ id: 'apply',
+ xtype: 'button',
+ text: _('Apply'),
+ style: 'margin-left: 10px;',
+ border: false,
+ width: 100,
+ handler: this.onApply,
+ scope: this,
+ });
+ },
+
+ onRender: function(ct, position) {
+ Deluge.details.OptionsTab.superclass.onRender.call(this, ct, position);
+
+ // This is another hack I think, so keep an eye out here when upgrading.
+ this.layout = new Ext.layout.ColumnLayout();
+ this.layout.setContainer(this);
+ this.doLayout();
+ },
+
+ clear: function() {
+ if (this.torrentId == null) return;
+ this.torrentId = null;
+ this.optionsManager.changeId(null);
+ },
+
+ reset: function() {
+ if (this.torrentId) this.optionsManager.reset();
+ },
+
+ update: function(torrentId) {
+ if (this.torrentId && !torrentId) this.clear(); // we want to clear the pane if we get a null torrent torrentIds
+
+ if (!torrentId) return; // We do not care about null torrentIds.
+
+ if (this.torrentId != torrentId) {
+ this.torrentId = torrentId;
+ this.optionsManager.changeId(torrentId);
+ }
+ deluge.client.web.get_torrent_status(torrentId, Deluge.Keys.Options, {
+ success: this.onRequestComplete,
+ scope: this,
+ });
+ },
+
+ onApply: function() {
+ var changed = this.optionsManager.getDirty();
+ deluge.client.core.set_torrent_options([this.torrentId], changed, {
+ success: function() {
+ this.optionsManager.commit();
+ },
+ scope: this,
+ });
+ },
+
+ onEditTrackers: function() {
+ deluge.editTrackers.show();
+ },
+
+ onMoveCompletedChecked: function(checkbox, checked) {
+ this.fields.move_completed_path.setDisabled(!checked);
+
+ if (!checked) return;
+ this.fields.move_completed_path.focus();
+ },
+
+ onStopRatioChecked: function(checkbox, checked) {
+ this.fields.remove_at_ratio.setDisabled(!checked);
+ this.fields.stop_ratio.setDisabled(!checked);
+ },
+
+ onRequestComplete: function(torrent, options) {
+ this.fields['private'].setValue(torrent['private']);
+ this.fields['private'].setDisabled(true);
+ delete torrent['private'];
+ torrent['auto_managed'] = torrent['is_auto_managed'];
+ torrent['prioritize_first_last_pieces'] =
+ torrent['prioritize_first_last'];
+ this.optionsManager.setDefault(torrent);
+ var stop_at_ratio = this.optionsManager.get('stop_at_ratio');
+ this.fields.remove_at_ratio.setDisabled(!stop_at_ratio);
+ this.fields.stop_ratio.setDisabled(!stop_at_ratio);
+ this.fields.move_completed_path.setDisabled(
+ !this.optionsManager.get('move_completed')
+ );
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/details/PeersTab.js b/deluge/ui/web/js/deluge-all/details/PeersTab.js
new file mode 100644
index 0000000..515e533
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/details/PeersTab.js
@@ -0,0 +1,166 @@
+/**
+ * Deluge.details.PeersTab.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+(function() {
+ function flagRenderer(value) {
+ if (!value.replace(' ', '').replace(' ', '')) {
+ return '';
+ }
+ return String.format(
+ '<img src="{0}flag/{1}" />',
+ deluge.config.base,
+ value
+ );
+ }
+ function peerAddressRenderer(value, p, record) {
+ var seed =
+ record.data['seed'] == 1024 ? 'x-deluge-seed' : 'x-deluge-peer';
+ // Modify display of IPv6 to include brackets
+ var peer_ip = value.split(':');
+ if (peer_ip.length > 2) {
+ var port = peer_ip.pop();
+ var ip = peer_ip.join(':');
+ value = '[' + ip + ']:' + port;
+ }
+ return String.format('<div class="{0}">{1}</div>', seed, value);
+ }
+ function peerProgressRenderer(value) {
+ var progress = (value * 100).toFixed(0);
+ return Deluge.progressBar(progress, this.width - 8, progress + '%');
+ }
+
+ Deluge.details.PeersTab = Ext.extend(Ext.grid.GridPanel, {
+ // fast way to figure out if we have a peer already.
+ peers: {},
+
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ title: _('Peers'),
+ cls: 'x-deluge-peers',
+ store: new Ext.data.Store({
+ reader: new Ext.data.JsonReader(
+ {
+ idProperty: 'ip',
+ root: 'peers',
+ },
+ Deluge.data.Peer
+ ),
+ }),
+ columns: [
+ {
+ header: '&nbsp;',
+ width: 30,
+ sortable: true,
+ renderer: flagRenderer,
+ dataIndex: 'country',
+ },
+ {
+ header: _('Address'),
+ width: 125,
+ sortable: true,
+ renderer: peerAddressRenderer,
+ dataIndex: 'ip',
+ },
+ {
+ header: _('Client'),
+ width: 125,
+ sortable: true,
+ renderer: fplain,
+ dataIndex: 'client',
+ },
+ {
+ header: _('Progress'),
+ width: 150,
+ sortable: true,
+ renderer: peerProgressRenderer,
+ dataIndex: 'progress',
+ },
+ {
+ header: _('Down Speed'),
+ width: 100,
+ sortable: true,
+ renderer: fspeed,
+ dataIndex: 'down_speed',
+ },
+ {
+ header: _('Up Speed'),
+ width: 100,
+ sortable: true,
+ renderer: fspeed,
+ dataIndex: 'up_speed',
+ },
+ ],
+ stripeRows: true,
+ deferredRender: false,
+ autoScroll: true,
+ },
+ config
+ );
+ Deluge.details.PeersTab.superclass.constructor.call(this, config);
+ },
+
+ clear: function() {
+ this.getStore().removeAll();
+ this.peers = {};
+ },
+
+ update: function(torrentId) {
+ deluge.client.web.get_torrent_status(torrentId, Deluge.Keys.Peers, {
+ success: this.onRequestComplete,
+ scope: this,
+ });
+ },
+
+ onRequestComplete: function(torrent, options) {
+ if (!torrent) return;
+
+ var store = this.getStore();
+ var newPeers = [];
+ var addresses = {};
+
+ // Go through the peers updating and creating peer records
+ Ext.each(
+ torrent.peers,
+ function(peer) {
+ if (this.peers[peer.ip]) {
+ var record = store.getById(peer.ip);
+ record.beginEdit();
+ for (var k in peer) {
+ if (record.get(k) != peer[k]) {
+ record.set(k, peer[k]);
+ }
+ }
+ record.endEdit();
+ } else {
+ this.peers[peer.ip] = 1;
+ newPeers.push(new Deluge.data.Peer(peer, peer.ip));
+ }
+ addresses[peer.ip] = 1;
+ },
+ this
+ );
+ store.add(newPeers);
+
+ // Remove any peers that should not be left in the store.
+ store.each(function(record) {
+ if (!addresses[record.id]) {
+ store.remove(record);
+ delete this.peers[record.id];
+ }
+ }, this);
+ store.commitChanges();
+
+ var sortState = store.getSortState();
+ if (!sortState) return;
+ store.sort(sortState.field, sortState.direction);
+ },
+ });
+})();
diff --git a/deluge/ui/web/js/deluge-all/details/StatusTab.js b/deluge/ui/web/js/deluge-all/details/StatusTab.js
new file mode 100644
index 0000000..a8753bb
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/details/StatusTab.js
@@ -0,0 +1,155 @@
+/**
+ * Deluge.details.StatusTab.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge.details');
+
+/**
+ * @class Deluge.details.StatusTab
+ * @extends Ext.Panel
+ */
+Deluge.details.StatusTab = Ext.extend(Ext.Panel, {
+ title: _('Status'),
+ autoScroll: true,
+
+ onRender: function(ct, position) {
+ Deluge.details.StatusTab.superclass.onRender.call(this, ct, position);
+
+ this.progressBar = this.add({
+ xtype: 'progress',
+ cls: 'x-deluge-status-progressbar',
+ });
+
+ this.status = this.add({
+ cls: 'x-deluge-status',
+ id: 'deluge-details-status',
+
+ border: false,
+ width: 1000,
+ listeners: {
+ render: {
+ fn: function(panel) {
+ panel.load({
+ url: deluge.config.base + 'render/tab_status.html',
+ text: _('Loading') + '...',
+ });
+ panel
+ .getUpdater()
+ .on('update', this.onPanelUpdate, this);
+ },
+ scope: this,
+ },
+ },
+ });
+ },
+
+ clear: function() {
+ this.progressBar.updateProgress(0, ' ');
+ for (var k in this.fields) {
+ this.fields[k].innerHTML = '';
+ }
+ },
+
+ update: function(torrentId) {
+ if (!this.fields) this.getFields();
+ deluge.client.web.get_torrent_status(torrentId, Deluge.Keys.Status, {
+ success: this.onRequestComplete,
+ scope: this,
+ });
+ },
+
+ onPanelUpdate: function(el, response) {
+ this.fields = {};
+ Ext.each(
+ Ext.query('dd', this.status.body.dom),
+ function(field) {
+ this.fields[field.className] = field;
+ },
+ this
+ );
+ },
+
+ onRequestComplete: function(status) {
+ seeds =
+ status.total_seeds > -1
+ ? status.num_seeds + ' (' + status.total_seeds + ')'
+ : status.num_seeds;
+ peers =
+ status.total_peers > -1
+ ? status.num_peers + ' (' + status.total_peers + ')'
+ : status.num_peers;
+ last_seen_complete =
+ status.last_seen_complete > 0.0
+ ? fdate(status.last_seen_complete)
+ : 'Never';
+ completed_time =
+ status.completed_time > 0.0 ? fdate(status.completed_time) : '';
+
+ var data = {
+ downloaded: fsize(status.total_done, true),
+ uploaded: fsize(status.total_uploaded, true),
+ share: status.ratio == -1 ? '&infin;' : status.ratio.toFixed(3),
+ announce: ftime(status.next_announce),
+ tracker_status: status.tracker_status,
+ downspeed: status.download_payload_rate
+ ? fspeed(status.download_payload_rate)
+ : '0.0 KiB/s',
+ upspeed: status.upload_payload_rate
+ ? fspeed(status.upload_payload_rate)
+ : '0.0 KiB/s',
+ eta: status.eta < 0 ? '&infin;' : ftime(status.eta),
+ pieces: status.num_pieces + ' (' + fsize(status.piece_length) + ')',
+ seeds: seeds,
+ peers: peers,
+ avail: status.distributed_copies.toFixed(3),
+ active_time: ftime(status.active_time),
+ seeding_time: ftime(status.seeding_time),
+ seed_rank: status.seed_rank,
+ time_added: fdate(status.time_added),
+ last_seen_complete: last_seen_complete,
+ completed_time: completed_time,
+ time_since_transfer: ftime(status.time_since_transfer),
+ };
+ data.auto_managed = _(status.is_auto_managed ? 'True' : 'False');
+
+ var translate_tracker_status = {
+ Error: _('Error'),
+ Warning: _('Warning'),
+ 'Announce OK': _('Announce OK'),
+ 'Announce Sent': _('Announce Sent'),
+ };
+ for (var key in translate_tracker_status) {
+ if (data.tracker_status.indexOf(key) != -1) {
+ data.tracker_status = data.tracker_status.replace(
+ key,
+ translate_tracker_status[key]
+ );
+ break;
+ }
+ }
+
+ data.downloaded +=
+ ' (' +
+ (status.total_payload_download
+ ? fsize(status.total_payload_download)
+ : '0.0 KiB') +
+ ')';
+ data.uploaded +=
+ ' (' +
+ (status.total_payload_upload
+ ? fsize(status.total_payload_upload)
+ : '0.0 KiB') +
+ ')';
+
+ for (var field in this.fields) {
+ this.fields[field].innerHTML = data[field];
+ }
+ var text = status.state + ' ' + status.progress.toFixed(2) + '%';
+ this.progressBar.updateProgress(status.progress / 100.0, text);
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/preferences/BandwidthPage.js b/deluge/ui/web/js/deluge-all/preferences/BandwidthPage.js
new file mode 100644
index 0000000..031104c
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/preferences/BandwidthPage.js
@@ -0,0 +1,203 @@
+/**
+ * Deluge.preferences.BandwidthPage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Bandwidth
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Bandwidth = Ext.extend(Ext.form.FormPanel, {
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ border: false,
+ title: _('Bandwidth'),
+ header: false,
+ layout: 'form',
+ labelWidth: 10,
+ },
+ config
+ );
+ Deluge.preferences.Bandwidth.superclass.constructor.call(this, config);
+ },
+
+ initComponent: function() {
+ Deluge.preferences.Bandwidth.superclass.initComponent.call(this);
+
+ var om = deluge.preferences.getOptionsManager();
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Global Bandwidth Usage'),
+ labelWidth: 200,
+ defaultType: 'spinnerfield',
+ defaults: {
+ minValue: -1,
+ maxValue: 99999,
+ },
+ style: 'margin-bottom: 0px; padding-bottom: 0px;',
+ autoHeight: true,
+ });
+ om.bind(
+ 'max_connections_global',
+ fieldset.add({
+ name: 'max_connections_global',
+ fieldLabel: _('Maximum Connections:'),
+ labelSeparator: '',
+ width: 80,
+ value: -1,
+ decimalPrecision: 0,
+ })
+ );
+ om.bind(
+ 'max_upload_slots_global',
+ fieldset.add({
+ name: 'max_upload_slots_global',
+ fieldLabel: _('Maximum Upload Slots'),
+ labelSeparator: '',
+ width: 80,
+ value: -1,
+ decimalPrecision: 0,
+ })
+ );
+ om.bind(
+ 'max_download_speed',
+ fieldset.add({
+ name: 'max_download_speed',
+ fieldLabel: _('Maximum Download Speed (KiB/s):'),
+ labelSeparator: '',
+ width: 80,
+ value: -1.0,
+ decimalPrecision: 1,
+ })
+ );
+ om.bind(
+ 'max_upload_speed',
+ fieldset.add({
+ name: 'max_upload_speed',
+ fieldLabel: _('Maximum Upload Speed (KiB/s):'),
+ labelSeparator: '',
+ width: 80,
+ value: -1.0,
+ decimalPrecision: 1,
+ })
+ );
+ om.bind(
+ 'max_half_open_connections',
+ fieldset.add({
+ name: 'max_half_open_connections',
+ fieldLabel: _('Maximum Half-Open Connections:'),
+ labelSeparator: '',
+ width: 80,
+ value: -1,
+ decimalPrecision: 0,
+ })
+ );
+ om.bind(
+ 'max_connections_per_second',
+ fieldset.add({
+ name: 'max_connections_per_second',
+ fieldLabel: _('Maximum Connection Attempts per Second:'),
+ labelSeparator: '',
+ width: 80,
+ value: -1,
+ decimalPrecision: 0,
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: '',
+ defaultType: 'checkbox',
+ style:
+ 'padding-top: 0px; padding-bottom: 5px; margin-top: 0px; margin-bottom: 0px;',
+ autoHeight: true,
+ });
+ om.bind(
+ 'ignore_limits_on_local_network',
+ fieldset.add({
+ name: 'ignore_limits_on_local_network',
+ height: 22,
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('Ignore limits on local network'),
+ })
+ );
+ om.bind(
+ 'rate_limit_ip_overhead',
+ fieldset.add({
+ name: 'rate_limit_ip_overhead',
+ height: 22,
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('Rate limit IP overhead'),
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Per Torrent Bandwidth Usage'),
+ style: 'margin-bottom: 0px; padding-bottom: 0px;',
+ defaultType: 'spinnerfield',
+ labelWidth: 200,
+ defaults: {
+ minValue: -1,
+ maxValue: 99999,
+ },
+ autoHeight: true,
+ });
+ om.bind(
+ 'max_connections_per_torrent',
+ fieldset.add({
+ name: 'max_connections_per_torrent',
+ fieldLabel: _('Maximum Connections:'),
+ labelSeparator: '',
+ width: 80,
+ value: -1,
+ decimalPrecision: 0,
+ })
+ );
+ om.bind(
+ 'max_upload_slots_per_torrent',
+ fieldset.add({
+ name: 'max_upload_slots_per_torrent',
+ fieldLabel: _('Maximum Upload Slots:'),
+ labelSeparator: '',
+ width: 80,
+ value: -1,
+ decimalPrecision: 0,
+ })
+ );
+ om.bind(
+ 'max_download_speed_per_torrent',
+ fieldset.add({
+ name: 'max_download_speed_per_torrent',
+ fieldLabel: _('Maximum Download Speed (KiB/s):'),
+ labelSeparator: '',
+ width: 80,
+ value: -1,
+ decimalPrecision: 0,
+ })
+ );
+ om.bind(
+ 'max_upload_speed_per_torrent',
+ fieldset.add({
+ name: 'max_upload_speed_per_torrent',
+ fieldLabel: _('Maximum Upload Speed (KiB/s):'),
+ labelSeparator: '',
+ width: 80,
+ value: -1,
+ decimalPrecision: 0,
+ })
+ );
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/preferences/CachePage.js b/deluge/ui/web/js/deluge-all/preferences/CachePage.js
new file mode 100644
index 0000000..2c84c7b
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/preferences/CachePage.js
@@ -0,0 +1,61 @@
+/**
+ * Deluge.preferences.CachePage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Cache
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Cache = Ext.extend(Ext.form.FormPanel, {
+ border: false,
+ title: _('Cache'),
+ header: false,
+ layout: 'form',
+
+ initComponent: function() {
+ Deluge.preferences.Cache.superclass.initComponent.call(this);
+
+ var om = deluge.preferences.getOptionsManager();
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Settings'),
+ autoHeight: true,
+ labelWidth: 180,
+ defaultType: 'spinnerfield',
+ defaults: {
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 999999,
+ },
+ });
+ om.bind(
+ 'cache_size',
+ fieldset.add({
+ fieldLabel: _('Cache Size (16 KiB Blocks):'),
+ labelSeparator: '',
+ name: 'cache_size',
+ width: 60,
+ value: 512,
+ })
+ );
+ om.bind(
+ 'cache_expiry',
+ fieldset.add({
+ fieldLabel: _('Cache Expiry (seconds):'),
+ labelSeparator: '',
+ name: 'cache_expiry',
+ width: 60,
+ value: 60,
+ })
+ );
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/preferences/DaemonPage.js b/deluge/ui/web/js/deluge-all/preferences/DaemonPage.js
new file mode 100644
index 0000000..38f5750
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/preferences/DaemonPage.js
@@ -0,0 +1,85 @@
+/**
+ * Deluge.preferences.DaemonPage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Daemon
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Daemon = Ext.extend(Ext.form.FormPanel, {
+ border: false,
+ title: _('Daemon'),
+ header: false,
+ layout: 'form',
+
+ initComponent: function() {
+ Deluge.preferences.Daemon.superclass.initComponent.call(this);
+
+ var om = deluge.preferences.getOptionsManager();
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Port'),
+ autoHeight: true,
+ defaultType: 'spinnerfield',
+ });
+ om.bind(
+ 'daemon_port',
+ fieldset.add({
+ fieldLabel: _('Daemon port:'),
+ labelSeparator: '',
+ name: 'daemon_port',
+ value: 58846,
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 99999,
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Connections'),
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ });
+ om.bind(
+ 'allow_remote',
+ fieldset.add({
+ fieldLabel: '',
+ height: 22,
+ labelSeparator: '',
+ boxLabel: _('Allow Remote Connections'),
+ name: 'allow_remote',
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Other'),
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ });
+ om.bind(
+ 'new_release_check',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ height: 40,
+ boxLabel: _('Periodically check the website for new releases'),
+ id: 'new_release_check',
+ })
+ );
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/preferences/DownloadsPage.js b/deluge/ui/web/js/deluge-all/preferences/DownloadsPage.js
new file mode 100644
index 0000000..bba5e47
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/preferences/DownloadsPage.js
@@ -0,0 +1,124 @@
+/**
+ * Deluge.preferences.DownloadsPage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Downloads
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Downloads = Ext.extend(Ext.FormPanel, {
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ border: false,
+ title: _('Downloads'),
+ header: false,
+ layout: 'form',
+ autoHeight: true,
+ width: 320,
+ },
+ config
+ );
+ Deluge.preferences.Downloads.superclass.constructor.call(this, config);
+ },
+
+ initComponent: function() {
+ Deluge.preferences.Downloads.superclass.initComponent.call(this);
+
+ var optMan = deluge.preferences.getOptionsManager();
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Folders'),
+ labelWidth: 150,
+ defaultType: 'togglefield',
+ autoHeight: true,
+ labelAlign: 'top',
+ width: 300,
+ style: 'margin-bottom: 5px; padding-bottom: 5px;',
+ });
+
+ optMan.bind(
+ 'download_location',
+ fieldset.add({
+ xtype: 'textfield',
+ name: 'download_location',
+ fieldLabel: _('Download to:'),
+ labelSeparator: '',
+ width: 280,
+ })
+ );
+
+ var field = fieldset.add({
+ name: 'move_completed_path',
+ fieldLabel: _('Move completed to:'),
+ labelSeparator: '',
+ width: 280,
+ });
+ optMan.bind('move_completed', field.toggle);
+ optMan.bind('move_completed_path', field.input);
+
+ field = fieldset.add({
+ name: 'torrentfiles_location',
+ fieldLabel: _('Copy of .torrent files to:'),
+ labelSeparator: '',
+ width: 280,
+ });
+ optMan.bind('copy_torrent_file', field.toggle);
+ optMan.bind('torrentfiles_location', field.input);
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Options'),
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ style: 'margin-bottom: 0; padding-bottom: 0;',
+ width: 280,
+ });
+ optMan.bind(
+ 'prioritize_first_last_pieces',
+ fieldset.add({
+ name: 'prioritize_first_last_pieces',
+ labelSeparator: '',
+ height: 22,
+ boxLabel: _('Prioritize first and last pieces of torrent'),
+ })
+ );
+ optMan.bind(
+ 'sequential_download',
+ fieldset.add({
+ name: 'sequential_download',
+ labelSeparator: '',
+ height: 22,
+ boxLabel: _('Sequential download'),
+ })
+ );
+ optMan.bind(
+ 'add_paused',
+ fieldset.add({
+ name: 'add_paused',
+ labelSeparator: '',
+ height: 22,
+ boxLabel: _('Add torrents in Paused state'),
+ })
+ );
+ optMan.bind(
+ 'pre_allocate_storage',
+ fieldset.add({
+ name: 'pre_allocate_storage',
+ labelSeparator: '',
+ height: 22,
+ boxLabel: _('Pre-allocate disk space'),
+ })
+ );
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/preferences/EncryptionPage.js b/deluge/ui/web/js/deluge-all/preferences/EncryptionPage.js
new file mode 100644
index 0000000..af5ad51
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/preferences/EncryptionPage.js
@@ -0,0 +1,99 @@
+/**
+ * Deluge.preferences.EncryptionPage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Encryption
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Encryption = Ext.extend(Ext.form.FormPanel, {
+ border: false,
+ title: _('Encryption'),
+ header: false,
+
+ initComponent: function() {
+ Deluge.preferences.Encryption.superclass.initComponent.call(this);
+
+ var optMan = deluge.preferences.getOptionsManager();
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Settings'),
+ header: false,
+ autoHeight: true,
+ defaultType: 'combo',
+ width: 300,
+ });
+ optMan.bind(
+ 'enc_in_policy',
+ fieldset.add({
+ fieldLabel: _('Incoming:'),
+ labelSeparator: '',
+ mode: 'local',
+ width: 150,
+ store: new Ext.data.ArrayStore({
+ fields: ['id', 'text'],
+ data: [
+ [0, _('Forced')],
+ [1, _('Enabled')],
+ [2, _('Disabled')],
+ ],
+ }),
+ editable: false,
+ triggerAction: 'all',
+ valueField: 'id',
+ displayField: 'text',
+ })
+ );
+ optMan.bind(
+ 'enc_out_policy',
+ fieldset.add({
+ fieldLabel: _('Outgoing:'),
+ labelSeparator: '',
+ mode: 'local',
+ width: 150,
+ store: new Ext.data.SimpleStore({
+ fields: ['id', 'text'],
+ data: [
+ [0, _('Forced')],
+ [1, _('Enabled')],
+ [2, _('Disabled')],
+ ],
+ }),
+ editable: false,
+ triggerAction: 'all',
+ valueField: 'id',
+ displayField: 'text',
+ })
+ );
+ optMan.bind(
+ 'enc_level',
+ fieldset.add({
+ fieldLabel: _('Level:'),
+ labelSeparator: '',
+ mode: 'local',
+ width: 150,
+ store: new Ext.data.SimpleStore({
+ fields: ['id', 'text'],
+ data: [
+ [0, _('Handshake')],
+ [1, _('Full Stream')],
+ [2, _('Either')],
+ ],
+ }),
+ editable: false,
+ triggerAction: 'all',
+ valueField: 'id',
+ displayField: 'text',
+ })
+ );
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/preferences/InstallPluginWindow.js b/deluge/ui/web/js/deluge-all/preferences/InstallPluginWindow.js
new file mode 100644
index 0000000..c394664
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/preferences/InstallPluginWindow.js
@@ -0,0 +1,83 @@
+/**
+ * Deluge.preferences.InstallPluginWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.InstallPluginWindow
+ * @extends Ext.Window
+ */
+Deluge.preferences.InstallPluginWindow = Ext.extend(Ext.Window, {
+ title: _('Install Plugin'),
+ layout: 'fit',
+ height: 115,
+ width: 350,
+ constrainHeader: true,
+ bodyStyle: 'padding: 10px 5px;',
+ buttonAlign: 'center',
+ closeAction: 'hide',
+ iconCls: 'x-deluge-install-plugin',
+ modal: true,
+ plain: true,
+
+ initComponent: function() {
+ Deluge.preferences.InstallPluginWindow.superclass.initComponent.call(
+ this
+ );
+ this.addButton(_('Install'), this.onInstall, this);
+
+ this.form = this.add({
+ xtype: 'form',
+ baseCls: 'x-plain',
+ labelWidth: 70,
+ autoHeight: true,
+ fileUpload: true,
+ items: [
+ {
+ xtype: 'fileuploadfield',
+ width: 240,
+ emptyText: _('Select an egg'),
+ fieldLabel: _('Plugin Egg'),
+ name: 'file',
+ buttonCfg: {
+ text: _('Browse...'),
+ },
+ },
+ ],
+ });
+ },
+
+ onInstall: function(field, e) {
+ this.form.getForm().submit({
+ url: deluge.config.base + 'upload',
+ waitMsg: _('Uploading your plugin...'),
+ success: this.onUploadSuccess,
+ scope: this,
+ });
+ },
+
+ onUploadPlugin: function(info, obj, response, request) {
+ this.fireEvent('pluginadded');
+ },
+
+ onUploadSuccess: function(fp, upload) {
+ this.hide();
+ if (upload.result.success) {
+ var filename = this.form.getForm().getFieldValues().file;
+ filename = filename.split('\\').slice(-1)[0];
+ var path = upload.result.files[0];
+ this.form.getForm().setValues({ file: '' });
+ deluge.client.web.upload_plugin(filename, path, {
+ success: this.onUploadPlugin,
+ scope: this,
+ filename: filename,
+ });
+ }
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/preferences/InterfacePage.js b/deluge/ui/web/js/deluge-all/preferences/InterfacePage.js
new file mode 100644
index 0000000..f5b0290
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/preferences/InterfacePage.js
@@ -0,0 +1,304 @@
+/**
+ * Deluge.preferences.InterfacePage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Interface
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Interface = Ext.extend(Ext.form.FormPanel, {
+ border: false,
+ title: _('Interface'),
+ header: false,
+ layout: 'form',
+
+ initComponent: function() {
+ Deluge.preferences.Interface.superclass.initComponent.call(this);
+
+ var om = (this.optionsManager = new Deluge.OptionsManager());
+ this.on('show', this.onPageShow, this);
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Interface'),
+ style: 'margin-bottom: 0px; padding-bottom: 5px; padding-top: 5px',
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ defaults: {
+ height: 17,
+ fieldLabel: '',
+ labelSeparator: '',
+ },
+ });
+ om.bind(
+ 'show_session_speed',
+ fieldset.add({
+ name: 'show_session_speed',
+ boxLabel: _('Show session speed in titlebar'),
+ })
+ );
+ om.bind(
+ 'sidebar_show_zero',
+ fieldset.add({
+ name: 'sidebar_show_zero',
+ boxLabel: _('Show filters with zero torrents'),
+ })
+ );
+ om.bind(
+ 'sidebar_multiple_filters',
+ fieldset.add({
+ name: 'sidebar_multiple_filters',
+ boxLabel: _('Allow the use of multiple filters at once'),
+ })
+ );
+
+ var languagePanel = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Language'),
+ style: 'margin-bottom: 0px; padding-bottom: 5px; padding-top: 5px',
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ });
+ this.language = om.bind(
+ 'language',
+ languagePanel.add({
+ xtype: 'combo',
+ labelSeparator: '',
+ name: 'language',
+ mode: 'local',
+ width: 200,
+ store: new Ext.data.ArrayStore({
+ fields: ['id', 'text'],
+ }),
+ editable: false,
+ triggerAction: 'all',
+ valueField: 'id',
+ displayField: 'text',
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('WebUI Password'),
+ style: 'margin-bottom: 0px; padding-bottom: 5px; padding-top: 5px',
+ autoHeight: true,
+ labelWidth: 100,
+ defaultType: 'textfield',
+ defaults: {
+ width: 100,
+ inputType: 'password',
+ labelStyle: 'padding-left: 5px',
+ height: 20,
+ labelSeparator: '',
+ },
+ });
+
+ this.oldPassword = fieldset.add({
+ name: 'old_password',
+ fieldLabel: _('Old:'),
+ });
+ this.newPassword = fieldset.add({
+ name: 'new_password',
+ fieldLabel: _('New:'),
+ });
+ this.confirmPassword = fieldset.add({
+ name: 'confirm_password',
+ fieldLabel: _('Confirm:'),
+ });
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Server'),
+ style: 'padding-top: 5px; margin-bottom: 0px; padding-bottom: 5px',
+ autoHeight: true,
+ labelWidth: 100,
+ defaultType: 'spinnerfield',
+ defaults: {
+ labelSeparator: '',
+ labelStyle: 'padding-left: 5px',
+ height: 20,
+ width: 80,
+ },
+ });
+ om.bind(
+ 'session_timeout',
+ fieldset.add({
+ name: 'session_timeout',
+ fieldLabel: _('Session Timeout:'),
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 99999,
+ })
+ );
+ om.bind(
+ 'port',
+ fieldset.add({
+ name: 'port',
+ fieldLabel: _('Port:'),
+ decimalPrecision: 0,
+ minValue: 1,
+ maxValue: 65535,
+ })
+ );
+ this.httpsField = om.bind(
+ 'https',
+ fieldset.add({
+ xtype: 'checkbox',
+ name: 'https',
+ hideLabel: true,
+ width: 300,
+ style: 'margin-left: 5px',
+ boxLabel: _(
+ 'Enable SSL (paths relative to Deluge config folder)'
+ ),
+ })
+ );
+ this.httpsField.on('check', this.onSSLCheck, this);
+ this.pkeyField = om.bind(
+ 'pkey',
+ fieldset.add({
+ xtype: 'textfield',
+ disabled: true,
+ name: 'pkey',
+ width: 180,
+ fieldLabel: _('Private Key:'),
+ })
+ );
+ this.certField = om.bind(
+ 'cert',
+ fieldset.add({
+ xtype: 'textfield',
+ disabled: true,
+ name: 'cert',
+ width: 180,
+ fieldLabel: _('Certificate:'),
+ })
+ );
+ },
+
+ onApply: function() {
+ var changed = this.optionsManager.getDirty();
+ if (!Ext.isObjectEmpty(changed)) {
+ deluge.client.web.set_config(changed, {
+ success: this.onSetConfig,
+ scope: this,
+ });
+
+ for (var key in deluge.config) {
+ deluge.config[key] = this.optionsManager.get(key);
+ }
+ if ('language' in changed) {
+ Ext.Msg.show({
+ title: _('WebUI Language Changed'),
+ msg: _(
+ 'Do you want to refresh the page now to use the new language?'
+ ),
+ buttons: {
+ yes: _('Refresh'),
+ no: _('Close'),
+ },
+ multiline: false,
+ fn: function(btnText) {
+ if (btnText === 'yes') location.reload();
+ },
+ icon: Ext.MessageBox.QUESTION,
+ });
+ }
+ }
+ if (this.oldPassword.getValue() || this.newPassword.getValue()) {
+ this.onPasswordChange();
+ }
+ },
+
+ onOk: function() {
+ this.onApply();
+ },
+
+ onGotConfig: function(config) {
+ this.optionsManager.set(config);
+ },
+
+ onGotLanguages: function(info, obj, response, request) {
+ info.unshift(['', _('System Default')]);
+ this.language.store.loadData(info);
+ this.language.setValue(this.optionsManager.get('language'));
+ },
+
+ onPasswordChange: function() {
+ var newPassword = this.newPassword.getValue();
+ if (newPassword != this.confirmPassword.getValue()) {
+ Ext.MessageBox.show({
+ title: _('Invalid Password'),
+ msg: _("Your passwords don't match!"),
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ icon: Ext.MessageBox.ERROR,
+ iconCls: 'x-deluge-icon-error',
+ });
+ return;
+ }
+
+ var oldPassword = this.oldPassword.getValue();
+ deluge.client.auth.change_password(oldPassword, newPassword, {
+ success: function(result) {
+ if (!result) {
+ Ext.MessageBox.show({
+ title: _('Password'),
+ msg: _('Your old password was incorrect!'),
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ icon: Ext.MessageBox.ERROR,
+ iconCls: 'x-deluge-icon-error',
+ });
+ this.oldPassword.setValue('');
+ } else {
+ Ext.MessageBox.show({
+ title: _('Change Successful'),
+ msg: _('Your password was successfully changed!'),
+ buttons: Ext.MessageBox.OK,
+ modal: false,
+ icon: Ext.MessageBox.INFO,
+ iconCls: 'x-deluge-icon-info',
+ });
+ this.oldPassword.setValue('');
+ this.newPassword.setValue('');
+ this.confirmPassword.setValue('');
+ }
+ },
+ scope: this,
+ });
+ },
+
+ onSetConfig: function() {
+ this.optionsManager.commit();
+ },
+
+ onPageShow: function() {
+ deluge.client.web.get_config({
+ success: this.onGotConfig,
+ scope: this,
+ });
+ deluge.client.webutils.get_languages({
+ success: this.onGotLanguages,
+ scope: this,
+ });
+ },
+
+ onSSLCheck: function(e, checked) {
+ this.pkeyField.setDisabled(!checked);
+ this.certField.setDisabled(!checked);
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/preferences/NetworkPage.js b/deluge/ui/web/js/deluge-all/preferences/NetworkPage.js
new file mode 100644
index 0000000..651ba4f
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/preferences/NetworkPage.js
@@ -0,0 +1,257 @@
+/**
+ * Deluge.preferences.NetworkPage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+// custom Vtype for vtype:'IPAddress'
+Ext.apply(Ext.form.VTypes, {
+ IPAddress: function(v) {
+ return /^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/.test(v);
+ },
+ IPAddressText: 'Must be a numeric IP address',
+ IPAddressMask: /[\d\.]/i,
+});
+
+/**
+ * @class Deluge.preferences.Network
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Network = Ext.extend(Ext.form.FormPanel, {
+ border: false,
+ layout: 'form',
+ title: _('Network'),
+ header: false,
+
+ initComponent: function() {
+ Deluge.preferences.Network.superclass.initComponent.call(this);
+ var optMan = deluge.preferences.getOptionsManager();
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Incoming Address'),
+ style: 'margin-bottom: 5px; padding-bottom: 0px;',
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'textfield',
+ });
+ optMan.bind(
+ 'listen_interface',
+ fieldset.add({
+ name: 'listen_interface',
+ fieldLabel: '',
+ labelSeparator: '',
+ width: 200,
+ vtype: 'IPAddress',
+ })
+ );
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Incoming Port'),
+ style: 'margin-bottom: 5px; padding-bottom: 0px;',
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ });
+ optMan.bind(
+ 'random_port',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('Use Random Port'),
+ name: 'random_port',
+ height: 22,
+ listeners: {
+ check: {
+ fn: function(e, checked) {
+ this.listenPort.setDisabled(checked);
+ },
+ scope: this,
+ },
+ },
+ })
+ );
+
+ this.listenPort = fieldset.add({
+ xtype: 'spinnerfield',
+ name: 'listen_port',
+ fieldLabel: '',
+ labelSeparator: '',
+ width: 75,
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 0,
+ minValue: 0,
+ maxValue: 65535,
+ },
+ });
+ optMan.bind('listen_ports', this.listenPort);
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Outgoing Interface'),
+ style: 'margin-bottom: 5px; padding-bottom: 0px;',
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'textfield',
+ });
+ optMan.bind(
+ 'outgoing_interface',
+ fieldset.add({
+ name: 'outgoing_interface',
+ fieldLabel: '',
+ labelSeparator: '',
+ width: 40,
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Outgoing Ports'),
+ style: 'margin-bottom: 5px; padding-bottom: 0px;',
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ });
+ optMan.bind(
+ 'random_outgoing_ports',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('Use Random Ports'),
+ name: 'random_outgoing_ports',
+ height: 22,
+ listeners: {
+ check: {
+ fn: function(e, checked) {
+ this.outgoingPorts.setDisabled(checked);
+ },
+ scope: this,
+ },
+ },
+ })
+ );
+ this.outgoingPorts = fieldset.add({
+ xtype: 'spinnergroup',
+ name: 'outgoing_ports',
+ fieldLabel: '',
+ labelSeparator: '',
+ colCfg: {
+ labelWidth: 40,
+ style: 'margin-right: 10px;',
+ },
+ items: [
+ {
+ fieldLabel: _('From:'),
+ labelSeparator: '',
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 0,
+ minValue: 0,
+ maxValue: 65535,
+ },
+ },
+ {
+ fieldLabel: _('To:'),
+ labelSeparator: '',
+ strategy: {
+ xtype: 'number',
+ decimalPrecision: 0,
+ minValue: 0,
+ maxValue: 65535,
+ },
+ },
+ ],
+ });
+ optMan.bind('outgoing_ports', this.outgoingPorts);
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Network Extras'),
+ autoHeight: true,
+ layout: 'table',
+ layoutConfig: {
+ columns: 3,
+ },
+ defaultType: 'checkbox',
+ });
+ optMan.bind(
+ 'upnp',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('UPnP'),
+ name: 'upnp',
+ })
+ );
+ optMan.bind(
+ 'natpmp',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('NAT-PMP'),
+ ctCls: 'x-deluge-indent-checkbox',
+ name: 'natpmp',
+ })
+ );
+ optMan.bind(
+ 'utpex',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('Peer Exchange'),
+ ctCls: 'x-deluge-indent-checkbox',
+ name: 'utpex',
+ })
+ );
+ optMan.bind(
+ 'lsd',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('LSD'),
+ name: 'lsd',
+ })
+ );
+ optMan.bind(
+ 'dht',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ boxLabel: _('DHT'),
+ ctCls: 'x-deluge-indent-checkbox',
+ name: 'dht',
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Type Of Service'),
+ style: 'margin-bottom: 5px; padding-bottom: 0px;',
+ bodyStyle: 'margin: 0px; padding: 0px',
+ autoHeight: true,
+ defaultType: 'textfield',
+ });
+ optMan.bind(
+ 'peer_tos',
+ fieldset.add({
+ name: 'peer_tos',
+ fieldLabel: _('Peer TOS Byte:'),
+ labelSeparator: '',
+ width: 40,
+ })
+ );
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/preferences/OtherPage.js b/deluge/ui/web/js/deluge-all/preferences/OtherPage.js
new file mode 100644
index 0000000..1538203
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/preferences/OtherPage.js
@@ -0,0 +1,100 @@
+/**
+ * Deluge.preferences.OtherPage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Other
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Other = Ext.extend(Ext.form.FormPanel, {
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ border: false,
+ title: _('Other'),
+ header: false,
+ layout: 'form',
+ },
+ config
+ );
+ Deluge.preferences.Other.superclass.constructor.call(this, config);
+ },
+
+ initComponent: function() {
+ Deluge.preferences.Other.superclass.initComponent.call(this);
+
+ var optMan = deluge.preferences.getOptionsManager();
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Updates'),
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ });
+ optMan.bind(
+ 'new_release_check',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ height: 22,
+ name: 'new_release_check',
+ boxLabel: _('Be alerted about new releases'),
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('System Information'),
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ });
+ fieldset.add({
+ xtype: 'panel',
+ border: false,
+ bodyCfg: {
+ html: _(
+ 'Help us improve Deluge by sending us your Python version, PyGTK version, OS and processor types. Absolutely no other information is sent.'
+ ),
+ },
+ });
+ optMan.bind(
+ 'send_info',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ height: 22,
+ boxLabel: _('Yes, please send anonymous statistics'),
+ name: 'send_info',
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('GeoIP Database'),
+ autoHeight: true,
+ labelWidth: 80,
+ defaultType: 'textfield',
+ });
+ optMan.bind(
+ 'geoip_db_location',
+ fieldset.add({
+ name: 'geoip_db_location',
+ fieldLabel: _('Path:'),
+ labelSeparator: '',
+ width: 200,
+ })
+ );
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/preferences/PluginsPage.js b/deluge/ui/web/js/deluge-all/preferences/PluginsPage.js
new file mode 100644
index 0000000..e22fc7f
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/preferences/PluginsPage.js
@@ -0,0 +1,277 @@
+/**
+ * Deluge.preferences.PluginsPage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Plugins
+ * @extends Ext.Panel
+ */
+Deluge.preferences.Plugins = Ext.extend(Ext.Panel, {
+ layout: 'border',
+ title: _('Plugins'),
+ header: false,
+ border: false,
+ cls: 'x-deluge-plugins',
+
+ pluginTemplate: new Ext.Template(
+ '<dl class="singleline">' +
+ '<dt>' +
+ _('Author:') +
+ '</dt><dd>{author}</dd>' +
+ '<dt>' +
+ _('Version:') +
+ '</dt><dd>{version}</dd>' +
+ '<dt>' +
+ _('Author Email:') +
+ '</dt><dd>{email}</dd>' +
+ '<dt>' +
+ _('Homepage:') +
+ '</dt><dd>{homepage}</dd>' +
+ '<dt>' +
+ _('Details:') +
+ '</dt><dd style="white-space:normal">{details}</dd>' +
+ '</dl>'
+ ),
+
+ initComponent: function() {
+ Deluge.preferences.Plugins.superclass.initComponent.call(this);
+ this.defaultValues = {
+ version: '',
+ email: '',
+ homepage: '',
+ details: '',
+ };
+ this.pluginTemplate.compile();
+
+ var checkboxRenderer = function(v, p, record) {
+ p.css += ' x-grid3-check-col-td';
+ return (
+ '<div class="x-grid3-check-col' + (v ? '-on' : '') + '"> </div>'
+ );
+ };
+
+ this.list = this.add({
+ xtype: 'listview',
+ store: new Ext.data.ArrayStore({
+ fields: [
+ { name: 'enabled', mapping: 0 },
+ { name: 'plugin', mapping: 1, sortType: 'asUCString' },
+ ],
+ }),
+ columns: [
+ {
+ id: 'enabled',
+ header: _('Enabled'),
+ width: 0.2,
+ sortable: true,
+ tpl: new Ext.XTemplate('{enabled:this.getCheckbox}', {
+ getCheckbox: function(v) {
+ return (
+ '<div class="x-grid3-check-col' +
+ (v ? '-on' : '') +
+ '" rel="chkbox"> </div>'
+ );
+ },
+ }),
+ dataIndex: 'enabled',
+ },
+ {
+ id: 'plugin',
+ header: _('Plugin'),
+ width: 0.8,
+ sortable: true,
+ dataIndex: 'plugin',
+ },
+ ],
+ singleSelect: true,
+ autoExpandColumn: 'plugin',
+ listeners: {
+ selectionchange: { fn: this.onPluginSelect, scope: this },
+ },
+ });
+
+ this.panel = this.add({
+ region: 'center',
+ autoScroll: true,
+ items: [this.list],
+ bbar: new Ext.Toolbar({
+ items: [
+ {
+ cls: 'x-btn-text-icon',
+ iconCls: 'x-deluge-install-plugin',
+ text: _('Install'),
+ handler: this.onInstallPluginWindow,
+ scope: this,
+ },
+ '->',
+ {
+ cls: 'x-btn-text-icon',
+ text: _('Find More'),
+ iconCls: 'x-deluge-find-more',
+ handler: this.onFindMorePlugins,
+ scope: this,
+ },
+ ],
+ }),
+ });
+
+ var pp = (this.pluginInfo = this.add({
+ xtype: 'panel',
+ border: false,
+ height: 100,
+ region: 'south',
+ padding: '5',
+ autoScroll: true,
+ bodyCfg: {
+ style: 'white-space: nowrap',
+ },
+ }));
+
+ this.pluginInfo.on('render', this.onPluginInfoRender, this);
+ this.list.on('click', this.onNodeClick, this);
+ deluge.preferences.on('show', this.onPreferencesShow, this);
+ deluge.events.on('PluginDisabledEvent', this.onPluginDisabled, this);
+ deluge.events.on('PluginEnabledEvent', this.onPluginEnabled, this);
+ },
+
+ disablePlugin: function(plugin) {
+ deluge.client.core.disable_plugin(plugin);
+ },
+
+ enablePlugin: function(plugin) {
+ deluge.client.core.enable_plugin(plugin);
+ },
+
+ setInfo: function(plugin) {
+ if (!this.pluginInfo.rendered) return;
+ var values = plugin || this.defaultValues;
+ this.pluginInfo.body.dom.innerHTML = this.pluginTemplate.apply(values);
+ },
+
+ updatePlugins: function() {
+ var onGotAvailablePlugins = function(plugins) {
+ this.availablePlugins = plugins.sort(function(a, b) {
+ return a.toLowerCase().localeCompare(b.toLowerCase());
+ });
+
+ deluge.client.core.get_enabled_plugins({
+ success: onGotEnabledPlugins,
+ scope: this,
+ });
+ };
+
+ var onGotEnabledPlugins = function(plugins) {
+ this.enabledPlugins = plugins;
+ this.onGotPlugins();
+ };
+
+ deluge.client.core.get_available_plugins({
+ success: onGotAvailablePlugins,
+ scope: this,
+ });
+ },
+
+ updatePluginsGrid: function() {
+ var plugins = [];
+ Ext.each(
+ this.availablePlugins,
+ function(plugin) {
+ if (this.enabledPlugins.indexOf(plugin) > -1) {
+ plugins.push([true, plugin]);
+ } else {
+ plugins.push([false, plugin]);
+ }
+ },
+ this
+ );
+ this.list.getStore().loadData(plugins);
+ },
+
+ onNodeClick: function(dv, index, node, e) {
+ var el = new Ext.Element(e.target);
+ if (el.getAttribute('rel') != 'chkbox') return;
+
+ var r = dv.getStore().getAt(index);
+ if (r.get('plugin') == 'WebUi') return;
+ r.set('enabled', !r.get('enabled'));
+ r.commit();
+ if (r.get('enabled')) {
+ this.enablePlugin(r.get('plugin'));
+ } else {
+ this.disablePlugin(r.get('plugin'));
+ }
+ },
+
+ onFindMorePlugins: function() {
+ window.open('http://dev.deluge-torrent.org/wiki/Plugins');
+ },
+
+ onGotPlugins: function() {
+ this.setInfo();
+ this.updatePluginsGrid();
+ },
+
+ onGotPluginInfo: function(info) {
+ var values = {
+ author: info['Author'],
+ version: info['Version'],
+ email: info['Author-email'],
+ homepage: info['Home-page'],
+ details: info['Description'],
+ };
+ this.setInfo(values);
+ delete info;
+ },
+
+ onInstallPluginWindow: function() {
+ if (!this.installWindow) {
+ this.installWindow = new Deluge.preferences.InstallPluginWindow();
+ this.installWindow.on('pluginadded', this.onPluginInstall, this);
+ }
+ this.installWindow.show();
+ },
+
+ onPluginEnabled: function(pluginName) {
+ var index = this.list.getStore().find('plugin', pluginName);
+ if (index == -1) return;
+ var plugin = this.list.getStore().getAt(index);
+ plugin.set('enabled', true);
+ plugin.commit();
+ },
+
+ onPluginDisabled: function(pluginName) {
+ var index = this.list.getStore().find('plugin', pluginName);
+ if (index == -1) return;
+ var plugin = this.list.getStore().getAt(index);
+ plugin.set('enabled', false);
+ plugin.commit();
+ },
+
+ onPluginInstall: function() {
+ this.updatePlugins();
+ },
+
+ onPluginSelect: function(dv, selections) {
+ if (selections.length == 0) return;
+ var r = dv.getRecords(selections)[0];
+ deluge.client.web.get_plugin_info(r.get('plugin'), {
+ success: this.onGotPluginInfo,
+ scope: this,
+ });
+ },
+
+ onPreferencesShow: function() {
+ this.updatePlugins();
+ },
+
+ onPluginInfoRender: function(ct, position) {
+ this.setInfo();
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/preferences/PreferencesWindow.js b/deluge/ui/web/js/deluge-all/preferences/PreferencesWindow.js
new file mode 100644
index 0000000..1bc98a8
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/preferences/PreferencesWindow.js
@@ -0,0 +1,246 @@
+/**
+ * Deluge.preferences.PreferencesWindow.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+PreferencesRecord = Ext.data.Record.create([{ name: 'name', type: 'string' }]);
+
+/**
+ * @class Deluge.preferences.PreferencesWindow
+ * @extends Ext.Window
+ */
+Deluge.preferences.PreferencesWindow = Ext.extend(Ext.Window, {
+ /**
+ * @property {String} currentPage The currently selected page.
+ */
+ currentPage: null,
+
+ title: _('Preferences'),
+ layout: 'border',
+ width: 485,
+ height: 500,
+ border: false,
+ constrainHeader: true,
+ buttonAlign: 'right',
+ closeAction: 'hide',
+ closable: true,
+ iconCls: 'x-deluge-preferences',
+ plain: true,
+ resizable: false,
+
+ pages: {},
+
+ initComponent: function() {
+ Deluge.preferences.PreferencesWindow.superclass.initComponent.call(
+ this
+ );
+
+ this.list = new Ext.list.ListView({
+ store: new Ext.data.Store(),
+ columns: [
+ {
+ id: 'name',
+ renderer: fplain,
+ dataIndex: 'name',
+ },
+ ],
+ singleSelect: true,
+ listeners: {
+ selectionchange: {
+ fn: this.onPageSelect,
+ scope: this,
+ },
+ },
+ hideHeaders: true,
+ autoExpandColumn: 'name',
+ deferredRender: false,
+ autoScroll: true,
+ collapsible: true,
+ });
+ this.add({
+ region: 'west',
+ items: [this.list],
+ width: 120,
+ margins: '0 5 0 0',
+ cmargins: '0 5 0 0',
+ });
+
+ this.configPanel = this.add({
+ type: 'container',
+ autoDestroy: false,
+ region: 'center',
+ layout: 'card',
+ layoutConfig: {
+ deferredRender: true,
+ },
+ autoScroll: true,
+ width: 300,
+ });
+
+ this.addButton(_('Close'), this.onClose, this);
+ this.addButton(_('Apply'), this.onApply, this);
+ this.addButton(_('OK'), this.onOk, this);
+
+ this.optionsManager = new Deluge.OptionsManager();
+ this.on('afterrender', this.onAfterRender, this);
+ this.on('show', this.onShow, this);
+
+ this.initPages();
+ },
+
+ initPages: function() {
+ deluge.preferences = this;
+ this.addPage(new Deluge.preferences.Downloads());
+ this.addPage(new Deluge.preferences.Network());
+ this.addPage(new Deluge.preferences.Encryption());
+ this.addPage(new Deluge.preferences.Bandwidth());
+ this.addPage(new Deluge.preferences.Interface());
+ this.addPage(new Deluge.preferences.Other());
+ this.addPage(new Deluge.preferences.Daemon());
+ this.addPage(new Deluge.preferences.Queue());
+ this.addPage(new Deluge.preferences.Proxy());
+ this.addPage(new Deluge.preferences.Cache());
+ this.addPage(new Deluge.preferences.Plugins());
+ },
+
+ onApply: function(e) {
+ var changed = this.optionsManager.getDirty();
+ if (!Ext.isObjectEmpty(changed)) {
+ // Workaround for only displaying single listen port but still pass array to core.
+ if ('listen_ports' in changed) {
+ changed.listen_ports = [
+ changed.listen_ports,
+ changed.listen_ports,
+ ];
+ }
+ deluge.client.core.set_config(changed, {
+ success: this.onSetConfig,
+ scope: this,
+ });
+ }
+
+ for (var page in this.pages) {
+ if (this.pages[page].onApply) this.pages[page].onApply();
+ }
+ },
+
+ /**
+ * Return the options manager for the preferences window.
+ * @returns {Deluge.OptionsManager} the options manager
+ */
+ getOptionsManager: function() {
+ return this.optionsManager;
+ },
+
+ /**
+ * Adds a page to the preferences window.
+ * @param {Mixed} page
+ */
+ addPage: function(page) {
+ var store = this.list.getStore();
+ var name = page.title;
+ store.add([new PreferencesRecord({ name: name })]);
+ page['bodyStyle'] = 'padding: 5px';
+ page.preferences = this;
+ this.pages[name] = this.configPanel.add(page);
+ this.pages[name].index = -1;
+ return this.pages[name];
+ },
+
+ /**
+ * Removes a preferences page from the window.
+ * @param {mixed} name
+ */
+ removePage: function(page) {
+ var name = page.title;
+ var store = this.list.getStore();
+ store.removeAt(store.find('name', name));
+ this.configPanel.remove(page);
+ delete this.pages[page.title];
+ },
+
+ /**
+ * Select which preferences page is displayed.
+ * @param {String} page The page name to change to
+ */
+ selectPage: function(page) {
+ if (this.pages[page].index < 0) {
+ this.pages[page].index = this.configPanel.items.indexOf(
+ this.pages[page]
+ );
+ }
+ this.list.select(this.pages[page].index);
+ },
+
+ // private
+ doSelectPage: function(page) {
+ if (this.pages[page].index < 0) {
+ this.pages[page].index = this.configPanel.items.indexOf(
+ this.pages[page]
+ );
+ }
+ this.configPanel.getLayout().setActiveItem(this.pages[page].index);
+ this.currentPage = page;
+ },
+
+ // private
+ onGotConfig: function(config) {
+ this.getOptionsManager().set(config);
+ },
+
+ // private
+ onPageSelect: function(list, selections) {
+ var r = list.getRecord(selections[0]);
+ this.doSelectPage(r.get('name'));
+ },
+
+ // private
+ onSetConfig: function() {
+ this.getOptionsManager().commit();
+ },
+
+ // private
+ onAfterRender: function() {
+ if (!this.list.getSelectionCount()) {
+ this.list.select(0);
+ }
+ this.configPanel.getLayout().setActiveItem(0);
+ },
+
+ // private
+ onShow: function() {
+ if (!deluge.client.core) return;
+ deluge.client.core.get_config({
+ success: this.onGotConfig,
+ scope: this,
+ });
+ },
+
+ // private
+ onClose: function() {
+ this.hide();
+ },
+
+ // private
+ onOk: function() {
+ var changed = this.optionsManager.getDirty();
+ if (!Ext.isObjectEmpty(changed)) {
+ deluge.client.core.set_config(changed, {
+ success: this.onSetConfig,
+ scope: this,
+ });
+ }
+
+ for (var page in this.pages) {
+ if (this.pages[page].onOk) this.pages[page].onOk();
+ }
+
+ this.hide();
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/preferences/ProxyField.js b/deluge/ui/web/js/deluge-all/preferences/ProxyField.js
new file mode 100644
index 0000000..6d500ba
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/preferences/ProxyField.js
@@ -0,0 +1,225 @@
+/**
+ * Deluge.preferences.ProxyField.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.ProxyField
+ * @extends Ext.form.FieldSet
+ */
+Deluge.preferences.ProxyField = Ext.extend(Ext.form.FieldSet, {
+ border: false,
+ autoHeight: true,
+ labelWidth: 70,
+
+ initComponent: function() {
+ Deluge.preferences.ProxyField.superclass.initComponent.call(this);
+ this.proxyType = this.add({
+ xtype: 'combo',
+ fieldLabel: _('Type:'),
+ labelSeparator: '',
+ name: 'proxytype',
+ mode: 'local',
+ width: 150,
+ store: new Ext.data.ArrayStore({
+ fields: ['id', 'text'],
+ data: [
+ [0, _('None')],
+ [1, _('Socks4')],
+ [2, _('Socks5')],
+ [3, _('Socks5 Auth')],
+ [4, _('HTTP')],
+ [5, _('HTTP Auth')],
+ [6, _('I2P')],
+ ],
+ }),
+ editable: false,
+ triggerAction: 'all',
+ valueField: 'id',
+ displayField: 'text',
+ });
+ this.proxyType.on('change', this.onFieldChange, this);
+ this.proxyType.on('select', this.onTypeSelect, this);
+
+ this.hostname = this.add({
+ xtype: 'textfield',
+ name: 'hostname',
+ fieldLabel: _('Host:'),
+ labelSeparator: '',
+ width: 220,
+ });
+ this.hostname.on('change', this.onFieldChange, this);
+
+ this.port = this.add({
+ xtype: 'spinnerfield',
+ name: 'port',
+ fieldLabel: _('Port:'),
+ labelSeparator: '',
+ width: 80,
+ decimalPrecision: 0,
+ minValue: 0,
+ maxValue: 65535,
+ });
+ this.port.on('change', this.onFieldChange, this);
+
+ this.username = this.add({
+ xtype: 'textfield',
+ name: 'username',
+ fieldLabel: _('Username:'),
+ labelSeparator: '',
+ width: 220,
+ });
+ this.username.on('change', this.onFieldChange, this);
+
+ this.password = this.add({
+ xtype: 'textfield',
+ name: 'password',
+ fieldLabel: _('Password:'),
+ labelSeparator: '',
+ inputType: 'password',
+ width: 220,
+ });
+ this.password.on('change', this.onFieldChange, this);
+
+ this.proxy_host_resolve = this.add({
+ xtype: 'checkbox',
+ name: 'proxy_host_resolve',
+ fieldLabel: '',
+ boxLabel: _('Proxy Hostnames'),
+ width: 220,
+ });
+ this.proxy_host_resolve.on('change', this.onFieldChange, this);
+
+ this.proxy_peer_conn = this.add({
+ xtype: 'checkbox',
+ name: 'proxy_peer_conn',
+ fieldLabel: '',
+ boxLabel: _('Proxy Peers'),
+ width: 220,
+ });
+ this.proxy_peer_conn.on('change', this.onFieldChange, this);
+
+ this.proxy_tracker_conn = this.add({
+ xtype: 'checkbox',
+ name: 'proxy_tracker_conn',
+ fieldLabel: '',
+ boxLabel: _('Proxy Trackers'),
+ width: 220,
+ });
+ this.proxy_tracker_conn.on('change', this.onFieldChange, this);
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Force Proxy'),
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ style: 'padding-left: 0px; margin-top: 10px',
+ });
+
+ this.force_proxy = fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ height: 20,
+ name: 'force_proxy',
+ boxLabel: _('Force Use of Proxy'),
+ });
+ this.force_proxy.on('change', this.onFieldChange, this);
+
+ this.anonymous_mode = fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ height: 20,
+ name: 'anonymous_mode',
+ boxLabel: _('Hide Client Identity'),
+ });
+ this.anonymous_mode.on('change', this.onFieldChange, this);
+
+ this.setting = false;
+ },
+
+ getName: function() {
+ return this.initialConfig.name;
+ },
+
+ getValue: function() {
+ return {
+ type: this.proxyType.getValue(),
+ hostname: this.hostname.getValue(),
+ port: Number(this.port.getValue()),
+ username: this.username.getValue(),
+ password: this.password.getValue(),
+ proxy_hostnames: this.proxy_host_resolve.getValue(),
+ proxy_peer_connections: this.proxy_peer_conn.getValue(),
+ proxy_tracker_connections: this.proxy_tracker_conn.getValue(),
+ force_proxy: this.force_proxy.getValue(),
+ anonymous_mode: this.anonymous_mode.getValue(),
+ };
+ },
+
+ // Set the values of the proxies
+ setValue: function(value) {
+ this.setting = true;
+ this.proxyType.setValue(value['type']);
+ var index = this.proxyType.getStore().find('id', value['type']);
+ var record = this.proxyType.getStore().getAt(index);
+
+ this.hostname.setValue(value['hostname']);
+ this.port.setValue(value['port']);
+ this.username.setValue(value['username']);
+ this.password.setValue(value['password']);
+ this.proxy_host_resolve.setValue(value['proxy_hostnames']);
+ this.proxy_peer_conn.setValue(value['proxy_peer_connections']);
+ this.proxy_tracker_conn.setValue(value['proxy_tracker_connections']);
+ this.force_proxy.setValue(value['force_proxy']);
+ this.anonymous_mode.setValue(value['anonymous_mode']);
+
+ this.onTypeSelect(this.type, record, index);
+ this.setting = false;
+ },
+
+ onFieldChange: function(field, newValue, oldValue) {
+ if (this.setting) return;
+ var newValues = this.getValue();
+ var oldValues = Ext.apply({}, newValues);
+ oldValues[field.getName()] = oldValue;
+
+ this.fireEvent('change', this, newValues, oldValues);
+ },
+
+ onTypeSelect: function(combo, record, index) {
+ var typeId = record.get('id');
+ if (typeId > 0) {
+ this.hostname.show();
+ this.port.show();
+ this.proxy_peer_conn.show();
+ this.proxy_tracker_conn.show();
+ if (typeId > 1 && typeId < 6) {
+ this.proxy_host_resolve.show();
+ } else {
+ this.proxy_host_resolve.hide();
+ }
+ } else {
+ this.hostname.hide();
+ this.port.hide();
+ this.proxy_host_resolve.hide();
+ this.proxy_peer_conn.hide();
+ this.proxy_tracker_conn.hide();
+ }
+
+ if (typeId == 3 || typeId == 5) {
+ this.username.show();
+ this.password.show();
+ } else {
+ this.username.hide();
+ this.password.hide();
+ }
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/preferences/ProxyPage.js b/deluge/ui/web/js/deluge-all/preferences/ProxyPage.js
new file mode 100644
index 0000000..4d3c402
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/preferences/ProxyPage.js
@@ -0,0 +1,62 @@
+/**
+ * Deluge.preferences.ProxyPage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Proxy
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Proxy = Ext.extend(Ext.form.FormPanel, {
+ constructor: function(config) {
+ config = Ext.apply(
+ {
+ border: false,
+ title: _('Proxy'),
+ header: false,
+ layout: 'form',
+ autoScroll: true,
+ },
+ config
+ );
+ Deluge.preferences.Proxy.superclass.constructor.call(this, config);
+ },
+
+ initComponent: function() {
+ Deluge.preferences.Proxy.superclass.initComponent.call(this);
+ this.proxy = this.add(
+ new Deluge.preferences.ProxyField({
+ title: _('Proxy'),
+ name: 'proxy',
+ })
+ );
+ this.proxy.on('change', this.onProxyChange, this);
+ deluge.preferences.getOptionsManager().bind('proxy', this.proxy);
+ },
+
+ getValue: function() {
+ return {
+ proxy: this.proxy.getValue(),
+ };
+ },
+
+ setValue: function(value) {
+ for (var proxy in value) {
+ this[proxy].setValue(value[proxy]);
+ }
+ },
+
+ onProxyChange: function(field, newValue, oldValue) {
+ var newValues = this.getValue();
+ var oldValues = Ext.apply({}, newValues);
+ oldValues[field.getName()] = oldValue;
+
+ this.fireEvent('change', this, newValues, oldValues);
+ },
+});
diff --git a/deluge/ui/web/js/deluge-all/preferences/QueuePage.js b/deluge/ui/web/js/deluge-all/preferences/QueuePage.js
new file mode 100644
index 0000000..db2da7c
--- /dev/null
+++ b/deluge/ui/web/js/deluge-all/preferences/QueuePage.js
@@ -0,0 +1,234 @@
+/**
+ * Deluge.preferences.QueuePage.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Deluge.preferences');
+
+/**
+ * @class Deluge.preferences.Queue
+ * @extends Ext.form.FormPanel
+ */
+Deluge.preferences.Queue = Ext.extend(Ext.form.FormPanel, {
+ border: false,
+ title: _('Queue'),
+ header: false,
+ layout: 'form',
+
+ initComponent: function() {
+ Deluge.preferences.Queue.superclass.initComponent.call(this);
+
+ var om = deluge.preferences.getOptionsManager();
+
+ var fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('New Torrents'),
+ style: 'padding-top: 5px; margin-bottom: 0px;',
+ autoHeight: true,
+ labelWidth: 1,
+ defaultType: 'checkbox',
+ });
+ om.bind(
+ 'queue_new_to_top',
+ fieldset.add({
+ fieldLabel: '',
+ labelSeparator: '',
+ height: 22,
+ boxLabel: _('Queue to top'),
+ name: 'queue_new_to_top',
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Active Torrents'),
+ autoHeight: true,
+ labelWidth: 150,
+ defaultType: 'spinnerfield',
+ style: 'padding-top: 5px; margin-bottom: 0px',
+ });
+ om.bind(
+ 'max_active_limit',
+ fieldset.add({
+ fieldLabel: _('Total:'),
+ labelSeparator: '',
+ name: 'max_active_limit',
+ value: 8,
+ width: 80,
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 99999,
+ })
+ );
+ om.bind(
+ 'max_active_downloading',
+ fieldset.add({
+ fieldLabel: _('Downloading:'),
+ labelSeparator: '',
+ name: 'max_active_downloading',
+ value: 3,
+ width: 80,
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 99999,
+ })
+ );
+ om.bind(
+ 'max_active_seeding',
+ fieldset.add({
+ fieldLabel: _('Seeding:'),
+ labelSeparator: '',
+ name: 'max_active_seeding',
+ value: 5,
+ width: 80,
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 99999,
+ })
+ );
+ om.bind(
+ 'dont_count_slow_torrents',
+ fieldset.add({
+ xtype: 'checkbox',
+ name: 'dont_count_slow_torrents',
+ height: 22,
+ hideLabel: true,
+ boxLabel: _('Ignore slow torrents'),
+ })
+ );
+ om.bind(
+ 'auto_manage_prefer_seeds',
+ fieldset.add({
+ xtype: 'checkbox',
+ name: 'auto_manage_prefer_seeds',
+ hideLabel: true,
+ boxLabel: _('Prefer seeding torrents'),
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ title: _('Seeding Rotation'),
+ autoHeight: true,
+ labelWidth: 150,
+ defaultType: 'spinnerfield',
+ style: 'padding-top: 5px; margin-bottom: 0px',
+ });
+ om.bind(
+ 'share_ratio_limit',
+ fieldset.add({
+ fieldLabel: _('Share Ratio:'),
+ labelSeparator: '',
+ name: 'share_ratio_limit',
+ value: 8,
+ width: 80,
+ incrementValue: 0.1,
+ minValue: -1,
+ maxValue: 99999,
+ alternateIncrementValue: 1,
+ decimalPrecision: 2,
+ })
+ );
+ om.bind(
+ 'seed_time_ratio_limit',
+ fieldset.add({
+ fieldLabel: _('Time Ratio:'),
+ labelSeparator: '',
+ name: 'seed_time_ratio_limit',
+ value: 3,
+ width: 80,
+ incrementValue: 0.1,
+ minValue: -1,
+ maxValue: 99999,
+ alternateIncrementValue: 1,
+ decimalPrecision: 2,
+ })
+ );
+ om.bind(
+ 'seed_time_limit',
+ fieldset.add({
+ fieldLabel: _('Time (m):'),
+ labelSeparator: '',
+ name: 'seed_time_limit',
+ value: 5,
+ width: 80,
+ decimalPrecision: 0,
+ minValue: -1,
+ maxValue: 99999,
+ })
+ );
+
+ fieldset = this.add({
+ xtype: 'fieldset',
+ border: false,
+ autoHeight: true,
+ style: 'padding-top: 5px; margin-bottom: 0px',
+ title: _('Share Ratio Reached'),
+
+ layout: 'table',
+ layoutConfig: { columns: 2 },
+ labelWidth: 0,
+ defaultType: 'checkbox',
+
+ defaults: {
+ fieldLabel: '',
+ labelSeparator: '',
+ },
+ });
+ this.stopAtRatio = fieldset.add({
+ name: 'stop_seed_at_ratio',
+ boxLabel: _('Share Ratio:'),
+ });
+ this.stopAtRatio.on('check', this.onStopRatioCheck, this);
+ om.bind('stop_seed_at_ratio', this.stopAtRatio);
+
+ this.stopRatio = fieldset.add({
+ xtype: 'spinnerfield',
+ name: 'stop_seed_ratio',
+ ctCls: 'x-deluge-indent-checkbox',
+ disabled: true,
+ value: '2.0',
+ width: 60,
+ incrementValue: 0.1,
+ minValue: -1,
+ maxValue: 99999,
+ alternateIncrementValue: 1,
+ decimalPrecision: 2,
+ });
+ om.bind('stop_seed_ratio', this.stopRatio);
+
+ this.removeAtRatio = fieldset.add({
+ xtype: 'radiogroup',
+ columns: 1,
+ colspan: 2,
+ disabled: true,
+ style: 'margin-left: 10px',
+ items: [
+ {
+ boxLabel: _('Pause torrent'),
+ name: 'at_ratio',
+ inputValue: false,
+ checked: true,
+ },
+ {
+ boxLabel: _('Remove torrent'),
+ name: 'at_ratio',
+ inputValue: true,
+ },
+ ],
+ });
+ om.bind('remove_seed_at_ratio', this.removeAtRatio);
+ },
+
+ onStopRatioCheck: function(e, checked) {
+ this.stopRatio.setDisabled(!checked);
+ this.removeAtRatio.setDisabled(!checked);
+ },
+});
diff --git a/deluge/ui/web/js/extjs/ext-all-debug.js b/deluge/ui/web/js/extjs/ext-all-debug.js
new file mode 100644
index 0000000..0b15e6e
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-all-debug.js
@@ -0,0 +1,52270 @@
+/*
+This file is part of Ext JS 3.4
+
+Copyright (c) 2011-2013 Sencha Inc
+
+Contact: http://www.sencha.com/contact
+
+GNU General Public License Usage
+This file may be used under the terms of the GNU General Public License version 3.0 as
+published by the Free Software Foundation and appearing in the file LICENSE included in the
+packaging of this file.
+
+Please review the following information to ensure the GNU General Public License version 3.0
+requirements will be met: http://www.gnu.org/copyleft/gpl.html.
+
+If you are unsure which license is appropriate for your use, please contact the sales department
+at http://www.sencha.com/contact.
+
+Build date: 2013-04-03 15:07:25
+*/
+(function(){
+
+var EXTUTIL = Ext.util,
+ EACH = Ext.each,
+ TRUE = true,
+ FALSE = false;
+
+EXTUTIL.Observable = function(){
+
+ var me = this, e = me.events;
+ if(me.listeners){
+ me.on(me.listeners);
+ delete me.listeners;
+ }
+ me.events = e || {};
+};
+
+EXTUTIL.Observable.prototype = {
+
+ filterOptRe : /^(?:scope|delay|buffer|single)$/,
+
+
+ fireEvent : function(){
+ var a = Array.prototype.slice.call(arguments, 0),
+ ename = a[0].toLowerCase(),
+ me = this,
+ ret = TRUE,
+ ce = me.events[ename],
+ cc,
+ q,
+ c;
+ if (me.eventsSuspended === TRUE) {
+ if (q = me.eventQueue) {
+ q.push(a);
+ }
+ }
+ else if(typeof ce == 'object') {
+ if (ce.bubble){
+ if(ce.fire.apply(ce, a.slice(1)) === FALSE) {
+ return FALSE;
+ }
+ c = me.getBubbleTarget && me.getBubbleTarget();
+ if(c && c.enableBubble) {
+ cc = c.events[ename];
+ if(!cc || typeof cc != 'object' || !cc.bubble) {
+ c.enableBubble(ename);
+ }
+ return c.fireEvent.apply(c, a);
+ }
+ }
+ else {
+ a.shift();
+ ret = ce.fire.apply(ce, a);
+ }
+ }
+ return ret;
+ },
+
+
+ addListener : function(eventName, fn, scope, o){
+ var me = this,
+ e,
+ oe,
+ ce;
+
+ if (typeof eventName == 'object') {
+ o = eventName;
+ for (e in o) {
+ oe = o[e];
+ if (!me.filterOptRe.test(e)) {
+ me.addListener(e, oe.fn || oe, oe.scope || o.scope, oe.fn ? oe : o);
+ }
+ }
+ } else {
+ eventName = eventName.toLowerCase();
+ ce = me.events[eventName] || TRUE;
+ if (typeof ce == 'boolean') {
+ me.events[eventName] = ce = new EXTUTIL.Event(me, eventName);
+ }
+ ce.addListener(fn, scope, typeof o == 'object' ? o : {});
+ }
+ },
+
+
+ removeListener : function(eventName, fn, scope){
+ var ce = this.events[eventName.toLowerCase()];
+ if (typeof ce == 'object') {
+ ce.removeListener(fn, scope);
+ }
+ },
+
+
+ purgeListeners : function(){
+ var events = this.events,
+ evt,
+ key;
+ for(key in events){
+ evt = events[key];
+ if(typeof evt == 'object'){
+ evt.clearListeners();
+ }
+ }
+ },
+
+
+ addEvents : function(o){
+ var me = this;
+ me.events = me.events || {};
+ if (typeof o == 'string') {
+ var a = arguments,
+ i = a.length;
+ while(i--) {
+ me.events[a[i]] = me.events[a[i]] || TRUE;
+ }
+ } else {
+ Ext.applyIf(me.events, o);
+ }
+ },
+
+
+ hasListener : function(eventName){
+ var e = this.events[eventName.toLowerCase()];
+ return typeof e == 'object' && e.listeners.length > 0;
+ },
+
+
+ suspendEvents : function(queueSuspended){
+ this.eventsSuspended = TRUE;
+ if(queueSuspended && !this.eventQueue){
+ this.eventQueue = [];
+ }
+ },
+
+
+ resumeEvents : function(){
+ var me = this,
+ queued = me.eventQueue || [];
+ me.eventsSuspended = FALSE;
+ delete me.eventQueue;
+ EACH(queued, function(e) {
+ me.fireEvent.apply(me, e);
+ });
+ }
+};
+
+var OBSERVABLE = EXTUTIL.Observable.prototype;
+
+OBSERVABLE.on = OBSERVABLE.addListener;
+
+OBSERVABLE.un = OBSERVABLE.removeListener;
+
+
+EXTUTIL.Observable.releaseCapture = function(o){
+ o.fireEvent = OBSERVABLE.fireEvent;
+};
+
+function createTargeted(h, o, scope){
+ return function(){
+ if(o.target == arguments[0]){
+ h.apply(scope, Array.prototype.slice.call(arguments, 0));
+ }
+ };
+};
+
+function createBuffered(h, o, l, scope){
+ l.task = new EXTUTIL.DelayedTask();
+ return function(){
+ l.task.delay(o.buffer, h, scope, Array.prototype.slice.call(arguments, 0));
+ };
+};
+
+function createSingle(h, e, fn, scope){
+ return function(){
+ e.removeListener(fn, scope);
+ return h.apply(scope, arguments);
+ };
+};
+
+function createDelayed(h, o, l, scope){
+ return function(){
+ var task = new EXTUTIL.DelayedTask(),
+ args = Array.prototype.slice.call(arguments, 0);
+ if(!l.tasks) {
+ l.tasks = [];
+ }
+ l.tasks.push(task);
+ task.delay(o.delay || 10, function(){
+ l.tasks.remove(task);
+ h.apply(scope, args);
+ }, scope);
+ };
+};
+
+EXTUTIL.Event = function(obj, name){
+ this.name = name;
+ this.obj = obj;
+ this.listeners = [];
+};
+
+EXTUTIL.Event.prototype = {
+ addListener : function(fn, scope, options){
+ var me = this,
+ l;
+ scope = scope || me.obj;
+ if(!me.isListening(fn, scope)){
+ l = me.createListener(fn, scope, options);
+ if(me.firing){
+ me.listeners = me.listeners.slice(0);
+ }
+ me.listeners.push(l);
+ }
+ },
+
+ createListener: function(fn, scope, o){
+ o = o || {};
+ scope = scope || this.obj;
+ var l = {
+ fn: fn,
+ scope: scope,
+ options: o
+ }, h = fn;
+ if(o.target){
+ h = createTargeted(h, o, scope);
+ }
+ if(o.delay){
+ h = createDelayed(h, o, l, scope);
+ }
+ if(o.single){
+ h = createSingle(h, this, fn, scope);
+ }
+ if(o.buffer){
+ h = createBuffered(h, o, l, scope);
+ }
+ l.fireFn = h;
+ return l;
+ },
+
+ findListener : function(fn, scope){
+ var list = this.listeners,
+ i = list.length,
+ l;
+
+ scope = scope || this.obj;
+ while(i--){
+ l = list[i];
+ if(l){
+ if(l.fn == fn && l.scope == scope){
+ return i;
+ }
+ }
+ }
+ return -1;
+ },
+
+ isListening : function(fn, scope){
+ return this.findListener(fn, scope) != -1;
+ },
+
+ removeListener : function(fn, scope){
+ var index,
+ l,
+ k,
+ me = this,
+ ret = FALSE;
+ if((index = me.findListener(fn, scope)) != -1){
+ if (me.firing) {
+ me.listeners = me.listeners.slice(0);
+ }
+ l = me.listeners[index];
+ if(l.task) {
+ l.task.cancel();
+ delete l.task;
+ }
+ k = l.tasks && l.tasks.length;
+ if(k) {
+ while(k--) {
+ l.tasks[k].cancel();
+ }
+ delete l.tasks;
+ }
+ me.listeners.splice(index, 1);
+ ret = TRUE;
+ }
+ return ret;
+ },
+
+
+ clearListeners : function(){
+ var me = this,
+ l = me.listeners,
+ i = l.length;
+ while(i--) {
+ me.removeListener(l[i].fn, l[i].scope);
+ }
+ },
+
+ fire : function(){
+ var me = this,
+ listeners = me.listeners,
+ len = listeners.length,
+ i = 0,
+ l;
+
+ if(len > 0){
+ me.firing = TRUE;
+ var args = Array.prototype.slice.call(arguments, 0);
+ for (; i < len; i++) {
+ l = listeners[i];
+ if(l && l.fireFn.apply(l.scope || me.obj || window, args) === FALSE) {
+ return (me.firing = FALSE);
+ }
+ }
+ }
+ me.firing = FALSE;
+ return TRUE;
+ }
+
+};
+})();
+
+Ext.DomHelper = function(){
+ var tempTableEl = null,
+ emptyTags = /^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,
+ tableRe = /^table|tbody|tr|td$/i,
+ confRe = /tag|children|cn|html$/i,
+ tableElRe = /td|tr|tbody/i,
+ cssRe = /([a-z0-9-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,
+ endRe = /end/i,
+ pub,
+
+ afterbegin = 'afterbegin',
+ afterend = 'afterend',
+ beforebegin = 'beforebegin',
+ beforeend = 'beforeend',
+ ts = '<table>',
+ te = '</table>',
+ tbs = ts+'<tbody>',
+ tbe = '</tbody>'+te,
+ trs = tbs + '<tr>',
+ tre = '</tr>'+tbe;
+
+
+ function doInsert(el, o, returnElement, pos, sibling, append){
+ var newNode = pub.insertHtml(pos, Ext.getDom(el), createHtml(o));
+ return returnElement ? Ext.get(newNode, true) : newNode;
+ }
+
+
+ function createHtml(o){
+ var b = '',
+ attr,
+ val,
+ key,
+ cn;
+
+ if(typeof o == "string"){
+ b = o;
+ } else if (Ext.isArray(o)) {
+ for (var i=0; i < o.length; i++) {
+ if(o[i]) {
+ b += createHtml(o[i]);
+ }
+ };
+ } else {
+ b += '<' + (o.tag = o.tag || 'div');
+ for (attr in o) {
+ val = o[attr];
+ if(!confRe.test(attr)){
+ if (typeof val == "object") {
+ b += ' ' + attr + '="';
+ for (key in val) {
+ b += key + ':' + val[key] + ';';
+ };
+ b += '"';
+ }else{
+ b += ' ' + ({cls : 'class', htmlFor : 'for'}[attr] || attr) + '="' + val + '"';
+ }
+ }
+ };
+
+ if (emptyTags.test(o.tag)) {
+ b += '/>';
+ } else {
+ b += '>';
+ if ((cn = o.children || o.cn)) {
+ b += createHtml(cn);
+ } else if(o.html){
+ b += o.html;
+ }
+ b += '</' + o.tag + '>';
+ }
+ }
+ return b;
+ }
+
+ function ieTable(depth, s, h, e){
+ tempTableEl.innerHTML = [s, h, e].join('');
+ var i = -1,
+ el = tempTableEl,
+ ns;
+ while(++i < depth){
+ el = el.firstChild;
+ }
+
+ if(ns = el.nextSibling){
+ var df = document.createDocumentFragment();
+ while(el){
+ ns = el.nextSibling;
+ df.appendChild(el);
+ el = ns;
+ }
+ el = df;
+ }
+ return el;
+ }
+
+
+ function insertIntoTable(tag, where, el, html) {
+ var node,
+ before;
+
+ tempTableEl = tempTableEl || document.createElement('div');
+
+ if(tag == 'td' && (where == afterbegin || where == beforeend) ||
+ !tableElRe.test(tag) && (where == beforebegin || where == afterend)) {
+ return;
+ }
+ before = where == beforebegin ? el :
+ where == afterend ? el.nextSibling :
+ where == afterbegin ? el.firstChild : null;
+
+ if (where == beforebegin || where == afterend) {
+ el = el.parentNode;
+ }
+
+ if (tag == 'td' || (tag == 'tr' && (where == beforeend || where == afterbegin))) {
+ node = ieTable(4, trs, html, tre);
+ } else if ((tag == 'tbody' && (where == beforeend || where == afterbegin)) ||
+ (tag == 'tr' && (where == beforebegin || where == afterend))) {
+ node = ieTable(3, tbs, html, tbe);
+ } else {
+ node = ieTable(2, ts, html, te);
+ }
+ el.insertBefore(node, before);
+ return node;
+ }
+
+
+ function createContextualFragment(html){
+ var div = document.createElement("div"),
+ fragment = document.createDocumentFragment(),
+ i = 0,
+ length, childNodes;
+
+ div.innerHTML = html;
+ childNodes = div.childNodes;
+ length = childNodes.length;
+
+ for (; i < length; i++) {
+ fragment.appendChild(childNodes[i].cloneNode(true));
+ }
+
+ return fragment;
+ }
+
+ pub = {
+
+ markup : function(o){
+ return createHtml(o);
+ },
+
+
+ applyStyles : function(el, styles){
+ if (styles) {
+ var matches;
+
+ el = Ext.fly(el);
+ if (typeof styles == "function") {
+ styles = styles.call();
+ }
+ if (typeof styles == "string") {
+
+ cssRe.lastIndex = 0;
+ while ((matches = cssRe.exec(styles))) {
+ el.setStyle(matches[1], matches[2]);
+ }
+ } else if (typeof styles == "object") {
+ el.setStyle(styles);
+ }
+ }
+ },
+
+ insertHtml : function(where, el, html){
+ var hash = {},
+ hashVal,
+ range,
+ rangeEl,
+ setStart,
+ frag,
+ rs;
+
+ where = where.toLowerCase();
+
+ hash[beforebegin] = ['BeforeBegin', 'previousSibling'];
+ hash[afterend] = ['AfterEnd', 'nextSibling'];
+
+ if (el.insertAdjacentHTML) {
+ if(tableRe.test(el.tagName) && (rs = insertIntoTable(el.tagName.toLowerCase(), where, el, html))){
+ return rs;
+ }
+
+ hash[afterbegin] = ['AfterBegin', 'firstChild'];
+ hash[beforeend] = ['BeforeEnd', 'lastChild'];
+ if ((hashVal = hash[where])) {
+ el.insertAdjacentHTML(hashVal[0], html);
+ return el[hashVal[1]];
+ }
+ } else {
+ range = el.ownerDocument.createRange();
+ setStart = 'setStart' + (endRe.test(where) ? 'After' : 'Before');
+ if (hash[where]) {
+ range[setStart](el);
+ if (!range.createContextualFragment) {
+ frag = createContextualFragment(html);
+ }
+ else {
+ frag = range.createContextualFragment(html);
+ }
+ el.parentNode.insertBefore(frag, where == beforebegin ? el : el.nextSibling);
+ return el[(where == beforebegin ? 'previous' : 'next') + 'Sibling'];
+ } else {
+ rangeEl = (where == afterbegin ? 'first' : 'last') + 'Child';
+ if (el.firstChild) {
+ range[setStart](el[rangeEl]);
+ if (!range.createContextualFragment) {
+ frag = createContextualFragment(html);
+ }
+ else {
+ frag = range.createContextualFragment(html);
+ }
+ if(where == afterbegin){
+ el.insertBefore(frag, el.firstChild);
+ }else{
+ el.appendChild(frag);
+ }
+ } else {
+ el.innerHTML = html;
+ }
+ return el[rangeEl];
+ }
+ }
+ throw 'Illegal insertion point -> "' + where + '"';
+ },
+
+
+ insertBefore : function(el, o, returnElement){
+ return doInsert(el, o, returnElement, beforebegin);
+ },
+
+
+ insertAfter : function(el, o, returnElement){
+ return doInsert(el, o, returnElement, afterend, 'nextSibling');
+ },
+
+
+ insertFirst : function(el, o, returnElement){
+ return doInsert(el, o, returnElement, afterbegin, 'firstChild');
+ },
+
+
+ append : function(el, o, returnElement){
+ return doInsert(el, o, returnElement, beforeend, '', true);
+ },
+
+
+ overwrite : function(el, o, returnElement){
+ el = Ext.getDom(el);
+ el.innerHTML = createHtml(o);
+ return returnElement ? Ext.get(el.firstChild) : el.firstChild;
+ },
+
+ createHtml : createHtml
+ };
+ return pub;
+}();
+
+Ext.Template = function(html){
+ var me = this,
+ a = arguments,
+ buf = [],
+ v;
+
+ if (Ext.isArray(html)) {
+ html = html.join("");
+ } else if (a.length > 1) {
+ for(var i = 0, len = a.length; i < len; i++){
+ v = a[i];
+ if(typeof v == 'object'){
+ Ext.apply(me, v);
+ } else {
+ buf.push(v);
+ }
+ };
+ html = buf.join('');
+ }
+
+
+ me.html = html;
+
+ if (me.compiled) {
+ me.compile();
+ }
+};
+Ext.Template.prototype = {
+
+ re : /\{([\w\-]+)\}/g,
+
+
+
+ applyTemplate : function(values){
+ var me = this;
+
+ return me.compiled ?
+ me.compiled(values) :
+ me.html.replace(me.re, function(m, name){
+ return values[name] !== undefined ? values[name] : "";
+ });
+ },
+
+
+ set : function(html, compile){
+ var me = this;
+ me.html = html;
+ me.compiled = null;
+ return compile ? me.compile() : me;
+ },
+
+
+ compile : function(){
+ var me = this,
+ sep = Ext.isGecko ? "+" : ",";
+
+ function fn(m, name){
+ name = "values['" + name + "']";
+ return "'"+ sep + '(' + name + " == undefined ? '' : " + name + ')' + sep + "'";
+ }
+
+ eval("this.compiled = function(values){ return " + (Ext.isGecko ? "'" : "['") +
+ me.html.replace(/\\/g, '\\\\').replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn) +
+ (Ext.isGecko ? "';};" : "'].join('');};"));
+ return me;
+ },
+
+
+ insertFirst: function(el, values, returnElement){
+ return this.doInsert('afterBegin', el, values, returnElement);
+ },
+
+
+ insertBefore: function(el, values, returnElement){
+ return this.doInsert('beforeBegin', el, values, returnElement);
+ },
+
+
+ insertAfter : function(el, values, returnElement){
+ return this.doInsert('afterEnd', el, values, returnElement);
+ },
+
+
+ append : function(el, values, returnElement){
+ return this.doInsert('beforeEnd', el, values, returnElement);
+ },
+
+ doInsert : function(where, el, values, returnEl){
+ el = Ext.getDom(el);
+ var newNode = Ext.DomHelper.insertHtml(where, el, this.applyTemplate(values));
+ return returnEl ? Ext.get(newNode, true) : newNode;
+ },
+
+
+ overwrite : function(el, values, returnElement){
+ el = Ext.getDom(el);
+ el.innerHTML = this.applyTemplate(values);
+ return returnElement ? Ext.get(el.firstChild, true) : el.firstChild;
+ }
+};
+
+Ext.Template.prototype.apply = Ext.Template.prototype.applyTemplate;
+
+
+Ext.Template.from = function(el, config){
+ el = Ext.getDom(el);
+ return new Ext.Template(el.value || el.innerHTML, config || '');
+};
+
+
+Ext.DomQuery = function(){
+ var cache = {},
+ simpleCache = {},
+ valueCache = {},
+ nonSpace = /\S/,
+ trimRe = /^\s+|\s+$/g,
+ tplRe = /\{(\d+)\}/g,
+ modeRe = /^(\s?[\/>+~]\s?|\s|$)/,
+ tagTokenRe = /^(#)?([\w\-\*]+)/,
+ nthRe = /(\d*)n\+?(\d*)/,
+ nthRe2 = /\D/,
+
+
+
+ isIE = window.ActiveXObject ? true : false,
+ key = 30803;
+
+
+
+ eval("var batch = 30803;");
+
+
+
+ function child(parent, index){
+ var i = 0,
+ n = parent.firstChild;
+ while(n){
+ if(n.nodeType == 1){
+ if(++i == index){
+ return n;
+ }
+ }
+ n = n.nextSibling;
+ }
+ return null;
+ }
+
+
+ function next(n){
+ while((n = n.nextSibling) && n.nodeType != 1);
+ return n;
+ }
+
+
+ function prev(n){
+ while((n = n.previousSibling) && n.nodeType != 1);
+ return n;
+ }
+
+
+
+ function children(parent){
+ var n = parent.firstChild,
+ nodeIndex = -1,
+ nextNode;
+ while(n){
+ nextNode = n.nextSibling;
+
+ if(n.nodeType == 3 && !nonSpace.test(n.nodeValue)){
+ parent.removeChild(n);
+ }else{
+
+ n.nodeIndex = ++nodeIndex;
+ }
+ n = nextNode;
+ }
+ return this;
+ }
+
+
+
+
+ function byClassName(nodeSet, cls){
+ if(!cls){
+ return nodeSet;
+ }
+ var result = [], ri = -1;
+ for(var i = 0, ci; ci = nodeSet[i]; i++){
+ if((' '+ci.className+' ').indexOf(cls) != -1){
+ result[++ri] = ci;
+ }
+ }
+ return result;
+ };
+
+ function attrValue(n, attr){
+
+ if(!n.tagName && typeof n.length != "undefined"){
+ n = n[0];
+ }
+ if(!n){
+ return null;
+ }
+
+ if(attr == "for"){
+ return n.htmlFor;
+ }
+ if(attr == "class" || attr == "className"){
+ return n.className;
+ }
+ return n.getAttribute(attr) || n[attr];
+
+ };
+
+
+
+
+
+ function getNodes(ns, mode, tagName){
+ var result = [], ri = -1, cs;
+ if(!ns){
+ return result;
+ }
+ tagName = tagName || "*";
+
+ if(typeof ns.getElementsByTagName != "undefined"){
+ ns = [ns];
+ }
+
+
+
+ if(!mode){
+ for(var i = 0, ni; ni = ns[i]; i++){
+ cs = ni.getElementsByTagName(tagName);
+ for(var j = 0, ci; ci = cs[j]; j++){
+ result[++ri] = ci;
+ }
+ }
+
+
+ } else if(mode == "/" || mode == ">"){
+ var utag = tagName.toUpperCase();
+ for(var i = 0, ni, cn; ni = ns[i]; i++){
+ cn = ni.childNodes;
+ for(var j = 0, cj; cj = cn[j]; j++){
+ if(cj.nodeName == utag || cj.nodeName == tagName || tagName == '*'){
+ result[++ri] = cj;
+ }
+ }
+ }
+
+
+ }else if(mode == "+"){
+ var utag = tagName.toUpperCase();
+ for(var i = 0, n; n = ns[i]; i++){
+ while((n = n.nextSibling) && n.nodeType != 1);
+ if(n && (n.nodeName == utag || n.nodeName == tagName || tagName == '*')){
+ result[++ri] = n;
+ }
+ }
+
+
+ }else if(mode == "~"){
+ var utag = tagName.toUpperCase();
+ for(var i = 0, n; n = ns[i]; i++){
+ while((n = n.nextSibling)){
+ if (n.nodeName == utag || n.nodeName == tagName || tagName == '*'){
+ result[++ri] = n;
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ function concat(a, b){
+ if(b.slice){
+ return a.concat(b);
+ }
+ for(var i = 0, l = b.length; i < l; i++){
+ a[a.length] = b[i];
+ }
+ return a;
+ }
+
+ function byTag(cs, tagName){
+ if(cs.tagName || cs == document){
+ cs = [cs];
+ }
+ if(!tagName){
+ return cs;
+ }
+ var result = [], ri = -1;
+ tagName = tagName.toLowerCase();
+ for(var i = 0, ci; ci = cs[i]; i++){
+ if(ci.nodeType == 1 && ci.tagName.toLowerCase() == tagName){
+ result[++ri] = ci;
+ }
+ }
+ return result;
+ }
+
+ function byId(cs, id){
+ if(cs.tagName || cs == document){
+ cs = [cs];
+ }
+ if(!id){
+ return cs;
+ }
+ var result = [], ri = -1;
+ for(var i = 0, ci; ci = cs[i]; i++){
+ if(ci && ci.id == id){
+ result[++ri] = ci;
+ return result;
+ }
+ }
+ return result;
+ }
+
+
+
+ function byAttribute(cs, attr, value, op, custom){
+ var result = [],
+ ri = -1,
+ useGetStyle = custom == "{",
+ fn = Ext.DomQuery.operators[op],
+ a,
+ xml,
+ hasXml;
+
+ for(var i = 0, ci; ci = cs[i]; i++){
+
+ if(ci.nodeType != 1){
+ continue;
+ }
+
+ if(!hasXml){
+ xml = Ext.DomQuery.isXml(ci);
+ hasXml = true;
+ }
+
+
+ if(!xml){
+ if(useGetStyle){
+ a = Ext.DomQuery.getStyle(ci, attr);
+ } else if (attr == "class" || attr == "className"){
+ a = ci.className;
+ } else if (attr == "for"){
+ a = ci.htmlFor;
+ } else if (attr == "href"){
+
+
+ a = ci.getAttribute("href", 2);
+ } else{
+ a = ci.getAttribute(attr);
+ }
+ }else{
+ a = ci.getAttribute(attr);
+ }
+ if((fn && fn(a, value)) || (!fn && a)){
+ result[++ri] = ci;
+ }
+ }
+ return result;
+ }
+
+ function byPseudo(cs, name, value){
+ return Ext.DomQuery.pseudos[name](cs, value);
+ }
+
+ function nodupIEXml(cs){
+ var d = ++key,
+ r;
+ cs[0].setAttribute("_nodup", d);
+ r = [cs[0]];
+ for(var i = 1, len = cs.length; i < len; i++){
+ var c = cs[i];
+ if(!c.getAttribute("_nodup") != d){
+ c.setAttribute("_nodup", d);
+ r[r.length] = c;
+ }
+ }
+ for(var i = 0, len = cs.length; i < len; i++){
+ cs[i].removeAttribute("_nodup");
+ }
+ return r;
+ }
+
+ function nodup(cs){
+ if(!cs){
+ return [];
+ }
+ var len = cs.length, c, i, r = cs, cj, ri = -1;
+ if(!len || typeof cs.nodeType != "undefined" || len == 1){
+ return cs;
+ }
+ if(isIE && typeof cs[0].selectSingleNode != "undefined"){
+ return nodupIEXml(cs);
+ }
+ var d = ++key;
+ cs[0]._nodup = d;
+ for(i = 1; c = cs[i]; i++){
+ if(c._nodup != d){
+ c._nodup = d;
+ }else{
+ r = [];
+ for(var j = 0; j < i; j++){
+ r[++ri] = cs[j];
+ }
+ for(j = i+1; cj = cs[j]; j++){
+ if(cj._nodup != d){
+ cj._nodup = d;
+ r[++ri] = cj;
+ }
+ }
+ return r;
+ }
+ }
+ return r;
+ }
+
+ function quickDiffIEXml(c1, c2){
+ var d = ++key,
+ r = [];
+ for(var i = 0, len = c1.length; i < len; i++){
+ c1[i].setAttribute("_qdiff", d);
+ }
+ for(var i = 0, len = c2.length; i < len; i++){
+ if(c2[i].getAttribute("_qdiff") != d){
+ r[r.length] = c2[i];
+ }
+ }
+ for(var i = 0, len = c1.length; i < len; i++){
+ c1[i].removeAttribute("_qdiff");
+ }
+ return r;
+ }
+
+ function quickDiff(c1, c2){
+ var len1 = c1.length,
+ d = ++key,
+ r = [];
+ if(!len1){
+ return c2;
+ }
+ if(isIE && typeof c1[0].selectSingleNode != "undefined"){
+ return quickDiffIEXml(c1, c2);
+ }
+ for(var i = 0; i < len1; i++){
+ c1[i]._qdiff = d;
+ }
+ for(var i = 0, len = c2.length; i < len; i++){
+ if(c2[i]._qdiff != d){
+ r[r.length] = c2[i];
+ }
+ }
+ return r;
+ }
+
+ function quickId(ns, mode, root, id){
+ if(ns == root){
+ var d = root.ownerDocument || root;
+ return d.getElementById(id);
+ }
+ ns = getNodes(ns, mode, "*");
+ return byId(ns, id);
+ }
+
+ return {
+ getStyle : function(el, name){
+ return Ext.fly(el).getStyle(name);
+ },
+
+ compile : function(path, type){
+ type = type || "select";
+
+
+ var fn = ["var f = function(root){\n var mode; ++batch; var n = root || document;\n"],
+ mode,
+ lastPath,
+ matchers = Ext.DomQuery.matchers,
+ matchersLn = matchers.length,
+ modeMatch,
+
+ lmode = path.match(modeRe);
+
+ if(lmode && lmode[1]){
+ fn[fn.length] = 'mode="'+lmode[1].replace(trimRe, "")+'";';
+ path = path.replace(lmode[1], "");
+ }
+
+
+ while(path.substr(0, 1)=="/"){
+ path = path.substr(1);
+ }
+
+ while(path && lastPath != path){
+ lastPath = path;
+ var tokenMatch = path.match(tagTokenRe);
+ if(type == "select"){
+ if(tokenMatch){
+
+ if(tokenMatch[1] == "#"){
+ fn[fn.length] = 'n = quickId(n, mode, root, "'+tokenMatch[2]+'");';
+ }else{
+ fn[fn.length] = 'n = getNodes(n, mode, "'+tokenMatch[2]+'");';
+ }
+ path = path.replace(tokenMatch[0], "");
+ }else if(path.substr(0, 1) != '@'){
+ fn[fn.length] = 'n = getNodes(n, mode, "*");';
+ }
+
+ }else{
+ if(tokenMatch){
+ if(tokenMatch[1] == "#"){
+ fn[fn.length] = 'n = byId(n, "'+tokenMatch[2]+'");';
+ }else{
+ fn[fn.length] = 'n = byTag(n, "'+tokenMatch[2]+'");';
+ }
+ path = path.replace(tokenMatch[0], "");
+ }
+ }
+ while(!(modeMatch = path.match(modeRe))){
+ var matched = false;
+ for(var j = 0; j < matchersLn; j++){
+ var t = matchers[j];
+ var m = path.match(t.re);
+ if(m){
+ fn[fn.length] = t.select.replace(tplRe, function(x, i){
+ return m[i];
+ });
+ path = path.replace(m[0], "");
+ matched = true;
+ break;
+ }
+ }
+
+ if(!matched){
+ throw 'Error parsing selector, parsing failed at "' + path + '"';
+ }
+ }
+ if(modeMatch[1]){
+ fn[fn.length] = 'mode="'+modeMatch[1].replace(trimRe, "")+'";';
+ path = path.replace(modeMatch[1], "");
+ }
+ }
+
+ fn[fn.length] = "return nodup(n);\n}";
+
+
+ eval(fn.join(""));
+ return f;
+ },
+
+
+ jsSelect: function(path, root, type){
+
+ root = root || document;
+
+ if(typeof root == "string"){
+ root = document.getElementById(root);
+ }
+ var paths = path.split(","),
+ results = [];
+
+
+ for(var i = 0, len = paths.length; i < len; i++){
+ var subPath = paths[i].replace(trimRe, "");
+
+ if(!cache[subPath]){
+ cache[subPath] = Ext.DomQuery.compile(subPath);
+ if(!cache[subPath]){
+ throw subPath + " is not a valid selector";
+ }
+ }
+ var result = cache[subPath](root);
+ if(result && result != document){
+ results = results.concat(result);
+ }
+ }
+
+
+
+ if(paths.length > 1){
+ return nodup(results);
+ }
+ return results;
+ },
+ isXml: function(el) {
+ var docEl = (el ? el.ownerDocument || el : 0).documentElement;
+ return docEl ? docEl.nodeName !== "HTML" : false;
+ },
+ select : document.querySelectorAll ? function(path, root, type) {
+ root = root || document;
+ if (!Ext.DomQuery.isXml(root)) {
+ try {
+ var cs = root.querySelectorAll(path);
+ return Ext.toArray(cs);
+ }
+ catch (ex) {}
+ }
+ return Ext.DomQuery.jsSelect.call(this, path, root, type);
+ } : function(path, root, type) {
+ return Ext.DomQuery.jsSelect.call(this, path, root, type);
+ },
+
+
+ selectNode : function(path, root){
+ return Ext.DomQuery.select(path, root)[0];
+ },
+
+
+ selectValue : function(path, root, defaultValue){
+ path = path.replace(trimRe, "");
+ if(!valueCache[path]){
+ valueCache[path] = Ext.DomQuery.compile(path, "select");
+ }
+ var n = valueCache[path](root), v;
+ n = n[0] ? n[0] : n;
+
+
+
+
+
+ if (typeof n.normalize == 'function') n.normalize();
+
+ v = (n && n.firstChild ? n.firstChild.nodeValue : null);
+ return ((v === null||v === undefined||v==='') ? defaultValue : v);
+ },
+
+
+ selectNumber : function(path, root, defaultValue){
+ var v = Ext.DomQuery.selectValue(path, root, defaultValue || 0);
+ return parseFloat(v);
+ },
+
+
+ is : function(el, ss){
+ if(typeof el == "string"){
+ el = document.getElementById(el);
+ }
+ var isArray = Ext.isArray(el),
+ result = Ext.DomQuery.filter(isArray ? el : [el], ss);
+ return isArray ? (result.length == el.length) : (result.length > 0);
+ },
+
+
+ filter : function(els, ss, nonMatches){
+ ss = ss.replace(trimRe, "");
+ if(!simpleCache[ss]){
+ simpleCache[ss] = Ext.DomQuery.compile(ss, "simple");
+ }
+ var result = simpleCache[ss](els);
+ return nonMatches ? quickDiff(result, els) : result;
+ },
+
+
+ matchers : [{
+ re: /^\.([\w\-]+)/,
+ select: 'n = byClassName(n, " {1} ");'
+ }, {
+ re: /^\:([\w\-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,
+ select: 'n = byPseudo(n, "{1}", "{2}");'
+ },{
+ re: /^(?:([\[\{])(?:@)?([\w\-]+)\s?(?:(=|.=)\s?(["']?)(.*?)\4)?[\]\}])/,
+ select: 'n = byAttribute(n, "{2}", "{5}", "{3}", "{1}");'
+ }, {
+ re: /^#([\w\-]+)/,
+ select: 'n = byId(n, "{1}");'
+ },{
+ re: /^@([\w\-]+)/,
+ select: 'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'
+ }
+ ],
+
+ /**
+ * Collection of operator comparison functions. The default operators are =, !=, ^=, $=, *=, %=, |= and ~=.
+ * New operators can be added as long as the match the format <i>c</i>= where <i>c</i> is any character other than space, &gt; &lt;.
+ */
+ operators : {
+ "=" : function(a, v){
+ return a == v;
+ },
+ "!=" : function(a, v){
+ return a != v;
+ },
+ "^=" : function(a, v){
+ return a && a.substr(0, v.length) == v;
+ },
+ "$=" : function(a, v){
+ return a && a.substr(a.length-v.length) == v;
+ },
+ "*=" : function(a, v){
+ return a && a.indexOf(v) !== -1;
+ },
+ "%=" : function(a, v){
+ return (a % v) == 0;
+ },
+ "|=" : function(a, v){
+ return a && (a == v || a.substr(0, v.length+1) == v+'-');
+ },
+ "~=" : function(a, v){
+ return a && (' '+a+' ').indexOf(' '+v+' ') != -1;
+ }
+ },
+
+ /**
+ * <p>Object hash of "pseudo class" filter functions which are used when filtering selections. Each function is passed
+ * two parameters:</p><div class="mdetail-params"><ul>
+ * <li><b>c</b> : Array<div class="sub-desc">An Array of DOM elements to filter.</div></li>
+ * <li><b>v</b> : String<div class="sub-desc">The argument (if any) supplied in the selector.</div></li>
+ * </ul></div>
+ * <p>A filter function returns an Array of DOM elements which conform to the pseudo class.</p>
+ * <p>In addition to the provided pseudo classes listed above such as <code>first-child</code> and <code>nth-child</code>,
+ * developers may add additional, custom psuedo class filters to select elements according to application-specific requirements.</p>
+ * <p>For example, to filter <code>&lt;a></code> elements to only return links to <i>external</i> resources:</p>
+ * <code><pre>
+Ext.DomQuery.pseudos.external = function(c, v){
+ var r = [], ri = -1;
+ for(var i = 0, ci; ci = c[i]; i++){
+// Include in result set only if it's a link to an external resource
+ if(ci.hostname != location.hostname){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+};</pre></code>
+ * Then external links could be gathered with the following statement:<code><pre>
+var externalLinks = Ext.select("a:external");
+</code></pre>
+ */
+ pseudos : {
+ "first-child" : function(c){
+ var r = [], ri = -1, n;
+ for(var i = 0, ci; ci = n = c[i]; i++){
+ while((n = n.previousSibling) && n.nodeType != 1);
+ if(!n){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "last-child" : function(c){
+ var r = [], ri = -1, n;
+ for(var i = 0, ci; ci = n = c[i]; i++){
+ while((n = n.nextSibling) && n.nodeType != 1);
+ if(!n){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "nth-child" : function(c, a) {
+ var r = [], ri = -1,
+ m = nthRe.exec(a == "even" && "2n" || a == "odd" && "2n+1" || !nthRe2.test(a) && "n+" + a || a),
+ f = (m[1] || 1) - 0, l = m[2] - 0;
+ for(var i = 0, n; n = c[i]; i++){
+ var pn = n.parentNode;
+ if (batch != pn._batch) {
+ var j = 0;
+ for(var cn = pn.firstChild; cn; cn = cn.nextSibling){
+ if(cn.nodeType == 1){
+ cn.nodeIndex = ++j;
+ }
+ }
+ pn._batch = batch;
+ }
+ if (f == 1) {
+ if (l == 0 || n.nodeIndex == l){
+ r[++ri] = n;
+ }
+ } else if ((n.nodeIndex + l) % f == 0){
+ r[++ri] = n;
+ }
+ }
+
+ return r;
+ },
+
+ "only-child" : function(c){
+ var r = [], ri = -1;;
+ for(var i = 0, ci; ci = c[i]; i++){
+ if(!prev(ci) && !next(ci)){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "empty" : function(c){
+ var r = [], ri = -1;
+ for(var i = 0, ci; ci = c[i]; i++){
+ var cns = ci.childNodes, j = 0, cn, empty = true;
+ while(cn = cns[j]){
+ ++j;
+ if(cn.nodeType == 1 || cn.nodeType == 3){
+ empty = false;
+ break;
+ }
+ }
+ if(empty){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "contains" : function(c, v){
+ var r = [], ri = -1;
+ for(var i = 0, ci; ci = c[i]; i++){
+ if((ci.textContent||ci.innerText||'').indexOf(v) != -1){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "nodeValue" : function(c, v){
+ var r = [], ri = -1;
+ for(var i = 0, ci; ci = c[i]; i++){
+ if(ci.firstChild && ci.firstChild.nodeValue == v){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "checked" : function(c){
+ var r = [], ri = -1;
+ for(var i = 0, ci; ci = c[i]; i++){
+ if(ci.checked == true){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "not" : function(c, ss){
+ return Ext.DomQuery.filter(c, ss, true);
+ },
+
+ "any" : function(c, selectors){
+ var ss = selectors.split('|'),
+ r = [], ri = -1, s;
+ for(var i = 0, ci; ci = c[i]; i++){
+ for(var j = 0; s = ss[j]; j++){
+ if(Ext.DomQuery.is(ci, s)){
+ r[++ri] = ci;
+ break;
+ }
+ }
+ }
+ return r;
+ },
+
+ "odd" : function(c){
+ return this["nth-child"](c, "odd");
+ },
+
+ "even" : function(c){
+ return this["nth-child"](c, "even");
+ },
+
+ "nth" : function(c, a){
+ return c[a-1] || [];
+ },
+
+ "first" : function(c){
+ return c[0] || [];
+ },
+
+ "last" : function(c){
+ return c[c.length-1] || [];
+ },
+
+ "has" : function(c, ss){
+ var s = Ext.DomQuery.select,
+ r = [], ri = -1;
+ for(var i = 0, ci; ci = c[i]; i++){
+ if(s(ss, ci).length > 0){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "next" : function(c, ss){
+ var is = Ext.DomQuery.is,
+ r = [], ri = -1;
+ for(var i = 0, ci; ci = c[i]; i++){
+ var n = next(ci);
+ if(n && is(n, ss)){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ },
+
+ "prev" : function(c, ss){
+ var is = Ext.DomQuery.is,
+ r = [], ri = -1;
+ for(var i = 0, ci; ci = c[i]; i++){
+ var n = prev(ci);
+ if(n && is(n, ss)){
+ r[++ri] = ci;
+ }
+ }
+ return r;
+ }
+ }
+ };
+}();
+
+/**
+ * Selects an array of DOM nodes by CSS/XPath selector. Shorthand of {@link Ext.DomQuery#select}
+ * @param {String} path The selector/xpath query
+ * @param {Node} root (optional) The start of the query (defaults to document).
+ * @return {Array}
+ * @member Ext
+ * @method query
+ */
+Ext.query = Ext.DomQuery.select;
+/**
+ * @class Ext.util.DelayedTask
+ * <p> The DelayedTask class provides a convenient way to "buffer" the execution of a method,
+ * performing setTimeout where a new timeout cancels the old timeout. When called, the
+ * task will wait the specified time period before executing. If durng that time period,
+ * the task is called again, the original call will be cancelled. This continues so that
+ * the function is only called a single time for each iteration.</p>
+ * <p>This method is especially useful for things like detecting whether a user has finished
+ * typing in a text field. An example would be performing validation on a keypress. You can
+ * use this class to buffer the keypress events for a certain number of milliseconds, and
+ * perform only if they stop for that amount of time. Usage:</p><pre><code>
+var task = new Ext.util.DelayedTask(function(){
+ alert(Ext.getDom('myInputField').value.length);
+});
+// Wait 500ms before calling our function. If the user presses another key
+// during that 500ms, it will be cancelled and we'll wait another 500ms.
+Ext.get('myInputField').on('keypress', function(){
+ task.{@link #delay}(500);
+});
+ * </code></pre>
+ * <p>Note that we are using a DelayedTask here to illustrate a point. The configuration
+ * option <tt>buffer</tt> for {@link Ext.util.Observable#addListener addListener/on} will
+ * also setup a delayed task for you to buffer events.</p>
+ * @constructor The parameters to this constructor serve as defaults and are not required.
+ * @param {Function} fn (optional) The default function to call.
+ * @param {Object} scope (optional) The default scope (The <code><b>this</b></code> reference) in which the
+ * function is called. If not specified, <code>this</code> will refer to the browser window.
+ * @param {Array} args (optional) The default Array of arguments.
+ */
+Ext.util.DelayedTask = function(fn, scope, args){
+ var me = this,
+ id,
+ call = function(){
+ clearInterval(id);
+ id = null;
+ fn.apply(scope, args || []);
+ };
+
+ /**
+ * Cancels any pending timeout and queues a new one
+ * @param {Number} delay The milliseconds to delay
+ * @param {Function} newFn (optional) Overrides function passed to constructor
+ * @param {Object} newScope (optional) Overrides scope passed to constructor. Remember that if no scope
+ * is specified, <code>this</code> will refer to the browser window.
+ * @param {Array} newArgs (optional) Overrides args passed to constructor
+ */
+ me.delay = function(delay, newFn, newScope, newArgs){
+ me.cancel();
+ fn = newFn || fn;
+ scope = newScope || scope;
+ args = newArgs || args;
+ id = setInterval(call, delay);
+ };
+
+ /**
+ * Cancel the last queued timeout
+ */
+ me.cancel = function(){
+ if(id){
+ clearInterval(id);
+ id = null;
+ }
+ };
+};/**
+ * @class Ext.Element
+ * <p>Encapsulates a DOM element, adding simple DOM manipulation facilities, normalizing for browser differences.</p>
+ * <p>All instances of this class inherit the methods of {@link Ext.Fx} making visual effects easily available to all DOM elements.</p>
+ * <p>Note that the events documented in this class are not Ext events, they encapsulate browser events. To
+ * access the underlying browser event, see {@link Ext.EventObject#browserEvent}. Some older
+ * browsers may not support the full range of events. Which events are supported is beyond the control of ExtJs.</p>
+ * Usage:<br>
+<pre><code>
+// by id
+var el = Ext.get("my-div");
+
+// by DOM element reference
+var el = Ext.get(myDivElement);
+</code></pre>
+ * <b>Animations</b><br />
+ * <p>When an element is manipulated, by default there is no animation.</p>
+ * <pre><code>
+var el = Ext.get("my-div");
+
+// no animation
+el.setWidth(100);
+ * </code></pre>
+ * <p>Many of the functions for manipulating an element have an optional "animate" parameter. This
+ * parameter can be specified as boolean (<tt>true</tt>) for default animation effects.</p>
+ * <pre><code>
+// default animation
+el.setWidth(100, true);
+ * </code></pre>
+ *
+ * <p>To configure the effects, an object literal with animation options to use as the Element animation
+ * configuration object can also be specified. Note that the supported Element animation configuration
+ * options are a subset of the {@link Ext.Fx} animation options specific to Fx effects. The supported
+ * Element animation configuration options are:</p>
+<pre>
+Option Default Description
+--------- -------- ---------------------------------------------
+{@link Ext.Fx#duration duration} .35 The duration of the animation in seconds
+{@link Ext.Fx#easing easing} easeOut The easing method
+{@link Ext.Fx#callback callback} none A function to execute when the anim completes
+{@link Ext.Fx#scope scope} this The scope (this) of the callback function
+</pre>
+ *
+ * <pre><code>
+// Element animation options object
+var opt = {
+ {@link Ext.Fx#duration duration}: 1,
+ {@link Ext.Fx#easing easing}: 'elasticIn',
+ {@link Ext.Fx#callback callback}: this.foo,
+ {@link Ext.Fx#scope scope}: this
+};
+// animation with some options set
+el.setWidth(100, opt);
+ * </code></pre>
+ * <p>The Element animation object being used for the animation will be set on the options
+ * object as "anim", which allows you to stop or manipulate the animation. Here is an example:</p>
+ * <pre><code>
+// using the "anim" property to get the Anim object
+if(opt.anim.isAnimated()){
+ opt.anim.stop();
+}
+ * </code></pre>
+ * <p>Also see the <tt>{@link #animate}</tt> method for another animation technique.</p>
+ * <p><b> Composite (Collections of) Elements</b></p>
+ * <p>For working with collections of Elements, see {@link Ext.CompositeElement}</p>
+ * @constructor Create a new Element directly.
+ * @param {String/HTMLElement} element
+ * @param {Boolean} forceNew (optional) By default the constructor checks to see if there is already an instance of this element in the cache and if there is it returns the same instance. This will skip that check (useful for extending this class).
+ */
+(function(){
+var DOC = document;
+
+Ext.Element = function(element, forceNew){
+ var dom = typeof element == "string" ?
+ DOC.getElementById(element) : element,
+ id;
+
+ if(!dom) return null;
+
+ id = dom.id;
+
+ if(!forceNew && id && Ext.elCache[id]){ // element object already exists
+ return Ext.elCache[id].el;
+ }
+
+ /**
+ * The DOM element
+ * @type HTMLElement
+ */
+ this.dom = dom;
+
+ /**
+ * The DOM element ID
+ * @type String
+ */
+ this.id = id || Ext.id(dom);
+};
+
+var DH = Ext.DomHelper,
+ El = Ext.Element,
+ EC = Ext.elCache;
+
+El.prototype = {
+ /**
+ * Sets the passed attributes as attributes of this element (a style attribute can be a string, object or function)
+ * @param {Object} o The object with the attributes
+ * @param {Boolean} useSet (optional) false to override the default setAttribute to use expandos.
+ * @return {Ext.Element} this
+ */
+ set : function(o, useSet){
+ var el = this.dom,
+ attr,
+ val,
+ useSet = (useSet !== false) && !!el.setAttribute;
+
+ for (attr in o) {
+ if (o.hasOwnProperty(attr)) {
+ val = o[attr];
+ if (attr == 'style') {
+ DH.applyStyles(el, val);
+ } else if (attr == 'cls') {
+ el.className = val;
+ } else if (useSet) {
+ el.setAttribute(attr, val);
+ } else {
+ el[attr] = val;
+ }
+ }
+ }
+ return this;
+ },
+
+// Mouse events
+ /**
+ * @event click
+ * Fires when a mouse click is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event contextmenu
+ * Fires when a right click is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event dblclick
+ * Fires when a mouse double click is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event mousedown
+ * Fires when a mousedown is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event mouseup
+ * Fires when a mouseup is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event mouseover
+ * Fires when a mouseover is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event mousemove
+ * Fires when a mousemove is detected with the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event mouseout
+ * Fires when a mouseout is detected with the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event mouseenter
+ * Fires when the mouse enters the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event mouseleave
+ * Fires when the mouse leaves the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+
+// Keyboard events
+ /**
+ * @event keypress
+ * Fires when a keypress is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event keydown
+ * Fires when a keydown is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event keyup
+ * Fires when a keyup is detected within the element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+
+
+// HTML frame/object events
+ /**
+ * @event load
+ * Fires when the user agent finishes loading all content within the element. Only supported by window, frames, objects and images.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event unload
+ * Fires when the user agent removes all content from a window or frame. For elements, it fires when the target element or any of its content has been removed.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event abort
+ * Fires when an object/image is stopped from loading before completely loaded.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event error
+ * Fires when an object/image/frame cannot be loaded properly.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event resize
+ * Fires when a document view is resized.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event scroll
+ * Fires when a document view is scrolled.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+
+// Form events
+ /**
+ * @event select
+ * Fires when a user selects some text in a text field, including input and textarea.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event change
+ * Fires when a control loses the input focus and its value has been modified since gaining focus.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event submit
+ * Fires when a form is submitted.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event reset
+ * Fires when a form is reset.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event focus
+ * Fires when an element receives focus either via the pointing device or by tab navigation.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event blur
+ * Fires when an element loses focus either via the pointing device or by tabbing navigation.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+
+// User Interface events
+ /**
+ * @event DOMFocusIn
+ * Where supported. Similar to HTML focus event, but can be applied to any focusable element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMFocusOut
+ * Where supported. Similar to HTML blur event, but can be applied to any focusable element.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMActivate
+ * Where supported. Fires when an element is activated, for instance, through a mouse click or a keypress.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+
+// DOM Mutation events
+ /**
+ * @event DOMSubtreeModified
+ * Where supported. Fires when the subtree is modified.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMNodeInserted
+ * Where supported. Fires when a node has been added as a child of another node.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMNodeRemoved
+ * Where supported. Fires when a descendant node of the element is removed.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMNodeRemovedFromDocument
+ * Where supported. Fires when a node is being removed from a document.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMNodeInsertedIntoDocument
+ * Where supported. Fires when a node is being inserted into a document.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMAttrModified
+ * Where supported. Fires when an attribute has been modified.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+ /**
+ * @event DOMCharacterDataModified
+ * Where supported. Fires when the character data has been modified.
+ * @param {Ext.EventObject} e The {@link Ext.EventObject} encapsulating the DOM event.
+ * @param {HtmlElement} t The target of the event.
+ * @param {Object} o The options configuration passed to the {@link #addListener} call.
+ */
+
+ /**
+ * The default unit to append to CSS values where a unit isn't provided (defaults to px).
+ * @type String
+ */
+ defaultUnit : "px",
+
+ /**
+ * Returns true if this element matches the passed simple selector (e.g. div.some-class or span:first-child)
+ * @param {String} selector The simple selector to test
+ * @return {Boolean} True if this element matches the selector, else false
+ */
+ is : function(simpleSelector){
+ return Ext.DomQuery.is(this.dom, simpleSelector);
+ },
+
+ /**
+ * Tries to focus the element. Any exceptions are caught and ignored.
+ * @param {Number} defer (optional) Milliseconds to defer the focus
+ * @return {Ext.Element} this
+ */
+ focus : function(defer, /* private */ dom) {
+ var me = this,
+ dom = dom || me.dom;
+ try{
+ if(Number(defer)){
+ me.focus.defer(defer, null, [null, dom]);
+ }else{
+ dom.focus();
+ }
+ }catch(e){}
+ return me;
+ },
+
+ /**
+ * Tries to blur the element. Any exceptions are caught and ignored.
+ * @return {Ext.Element} this
+ */
+ blur : function() {
+ try{
+ this.dom.blur();
+ }catch(e){}
+ return this;
+ },
+
+ /**
+ * Returns the value of the "value" attribute
+ * @param {Boolean} asNumber true to parse the value as a number
+ * @return {String/Number}
+ */
+ getValue : function(asNumber){
+ var val = this.dom.value;
+ return asNumber ? parseInt(val, 10) : val;
+ },
+
+ /**
+ * Appends an event handler to this element. The shorthand version {@link #on} is equivalent.
+ * @param {String} eventName The name of event to handle.
+ * @param {Function} fn The handler function the event invokes. This function is passed
+ * the following parameters:<ul>
+ * <li><b>evt</b> : EventObject<div class="sub-desc">The {@link Ext.EventObject EventObject} describing the event.</div></li>
+ * <li><b>el</b> : HtmlElement<div class="sub-desc">The DOM element which was the target of the event.
+ * Note that this may be filtered by using the <tt>delegate</tt> option.</div></li>
+ * <li><b>o</b> : Object<div class="sub-desc">The options object from the addListener call.</div></li>
+ * </ul>
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the handler function is executed.
+ * <b>If omitted, defaults to this Element.</b>.
+ * @param {Object} options (optional) An object containing handler configuration properties.
+ * This may contain any of the following properties:<ul>
+ * <li><b>scope</b> Object : <div class="sub-desc">The scope (<code><b>this</b></code> reference) in which the handler function is executed.
+ * <b>If omitted, defaults to this Element.</b></div></li>
+ * <li><b>delegate</b> String: <div class="sub-desc">A simple selector to filter the target or look for a descendant of the target. See below for additional details.</div></li>
+ * <li><b>stopEvent</b> Boolean: <div class="sub-desc">True to stop the event. That is stop propagation, and prevent the default action.</div></li>
+ * <li><b>preventDefault</b> Boolean: <div class="sub-desc">True to prevent the default action</div></li>
+ * <li><b>stopPropagation</b> Boolean: <div class="sub-desc">True to prevent event propagation</div></li>
+ * <li><b>normalized</b> Boolean: <div class="sub-desc">False to pass a browser event to the handler function instead of an Ext.EventObject</div></li>
+ * <li><b>target</b> Ext.Element: <div class="sub-desc">Only call the handler if the event was fired on the target Element, <i>not</i> if the event was bubbled up from a child node.</div></li>
+ * <li><b>delay</b> Number: <div class="sub-desc">The number of milliseconds to delay the invocation of the handler after the event fires.</div></li>
+ * <li><b>single</b> Boolean: <div class="sub-desc">True to add a handler to handle just the next firing of the event, and then remove itself.</div></li>
+ * <li><b>buffer</b> Number: <div class="sub-desc">Causes the handler to be scheduled to run in an {@link Ext.util.DelayedTask} delayed
+ * by the specified number of milliseconds. If the event fires again within that time, the original
+ * handler is <em>not</em> invoked, but the new handler is scheduled in its place.</div></li>
+ * </ul><br>
+ * <p>
+ * <b>Combining Options</b><br>
+ * In the following examples, the shorthand form {@link #on} is used rather than the more verbose
+ * addListener. The two are equivalent. Using the options argument, it is possible to combine different
+ * types of listeners:<br>
+ * <br>
+ * A delayed, one-time listener that auto stops the event and adds a custom argument (forumId) to the
+ * options object. The options object is available as the third parameter in the handler function.<div style="margin: 5px 20px 20px;">
+ * Code:<pre><code>
+el.on('click', this.onClick, this, {
+ single: true,
+ delay: 100,
+ stopEvent : true,
+ forumId: 4
+});</code></pre></p>
+ * <p>
+ * <b>Attaching multiple handlers in 1 call</b><br>
+ * The method also allows for a single argument to be passed which is a config object containing properties
+ * which specify multiple handlers.</p>
+ * <p>
+ * Code:<pre><code>
+el.on({
+ 'click' : {
+ fn: this.onClick,
+ scope: this,
+ delay: 100
+ },
+ 'mouseover' : {
+ fn: this.onMouseOver,
+ scope: this
+ },
+ 'mouseout' : {
+ fn: this.onMouseOut,
+ scope: this
+ }
+});</code></pre>
+ * <p>
+ * Or a shorthand syntax:<br>
+ * Code:<pre><code></p>
+el.on({
+ 'click' : this.onClick,
+ 'mouseover' : this.onMouseOver,
+ 'mouseout' : this.onMouseOut,
+ scope: this
+});
+ * </code></pre></p>
+ * <p><b>delegate</b></p>
+ * <p>This is a configuration option that you can pass along when registering a handler for
+ * an event to assist with event delegation. Event delegation is a technique that is used to
+ * reduce memory consumption and prevent exposure to memory-leaks. By registering an event
+ * for a container element as opposed to each element within a container. By setting this
+ * configuration option to a simple selector, the target element will be filtered to look for
+ * a descendant of the target.
+ * For example:<pre><code>
+// using this markup:
+&lt;div id='elId'>
+ &lt;p id='p1'>paragraph one&lt;/p>
+ &lt;p id='p2' class='clickable'>paragraph two&lt;/p>
+ &lt;p id='p3'>paragraph three&lt;/p>
+&lt;/div>
+// utilize event delegation to registering just one handler on the container element:
+el = Ext.get('elId');
+el.on(
+ 'click',
+ function(e,t) {
+ // handle click
+ console.info(t.id); // 'p2'
+ },
+ this,
+ {
+ // filter the target element to be a descendant with the class 'clickable'
+ delegate: '.clickable'
+ }
+);
+ * </code></pre></p>
+ * @return {Ext.Element} this
+ */
+ addListener : function(eventName, fn, scope, options){
+ Ext.EventManager.on(this.dom, eventName, fn, scope || this, options);
+ return this;
+ },
+
+ /**
+ * Removes an event handler from this element. The shorthand version {@link #un} is equivalent.
+ * <b>Note</b>: if a <i>scope</i> was explicitly specified when {@link #addListener adding} the
+ * listener, the same scope must be specified here.
+ * Example:
+ * <pre><code>
+el.removeListener('click', this.handlerFn);
+// or
+el.un('click', this.handlerFn);
+</code></pre>
+ * @param {String} eventName The name of the event from which to remove the handler.
+ * @param {Function} fn The handler function to remove. <b>This must be a reference to the function passed into the {@link #addListener} call.</b>
+ * @param {Object} scope If a scope (<b><code>this</code></b> reference) was specified when the listener was added,
+ * then this must refer to the same object.
+ * @return {Ext.Element} this
+ */
+ removeListener : function(eventName, fn, scope){
+ Ext.EventManager.removeListener(this.dom, eventName, fn, scope || this);
+ return this;
+ },
+
+ /**
+ * Removes all previous added listeners from this element
+ * @return {Ext.Element} this
+ */
+ removeAllListeners : function(){
+ Ext.EventManager.removeAll(this.dom);
+ return this;
+ },
+
+ /**
+ * Recursively removes all previous added listeners from this element and its children
+ * @return {Ext.Element} this
+ */
+ purgeAllListeners : function() {
+ Ext.EventManager.purgeElement(this, true);
+ return this;
+ },
+ /**
+ * @private Test if size has a unit, otherwise appends the default
+ */
+ addUnits : function(size){
+ if(size === "" || size == "auto" || size === undefined){
+ size = size || '';
+ } else if(!isNaN(size) || !unitPattern.test(size)){
+ size = size + (this.defaultUnit || 'px');
+ }
+ return size;
+ },
+
+ /**
+ * <p>Updates the <a href="http:
+ * from a specified URL. Note that this is subject to the <a href="http://en.wikipedia.org/wiki/Same_origin_policy">Same Origin Policy</a></p>
+ * <p>Updating innerHTML of an element will <b>not</b> execute embedded <tt>&lt;script></tt> elements. This is a browser restriction.</p>
+ * @param {Mixed} options. Either a sring containing the URL from which to load the HTML, or an {@link Ext.Ajax#request} options object specifying
+ * exactly how to request the HTML.
+ * @return {Ext.Element} this
+ */
+ load : function(url, params, cb){
+ Ext.Ajax.request(Ext.apply({
+ params: params,
+ url: url.url || url,
+ callback: cb,
+ el: this.dom,
+ indicatorText: url.indicatorText || ''
+ }, Ext.isObject(url) ? url : {}));
+ return this;
+ },
+
+
+ isBorderBox : function(){
+ return Ext.isBorderBox || Ext.isForcedBorderBox || noBoxAdjust[(this.dom.tagName || "").toLowerCase()];
+ },
+
+
+ remove : function(){
+ var me = this,
+ dom = me.dom;
+
+ if (dom) {
+ delete me.dom;
+ Ext.removeNode(dom);
+ }
+ },
+
+
+ hover : function(overFn, outFn, scope, options){
+ var me = this;
+ me.on('mouseenter', overFn, scope || me.dom, options);
+ me.on('mouseleave', outFn, scope || me.dom, options);
+ return me;
+ },
+
+
+ contains : function(el){
+ return !el ? false : Ext.lib.Dom.isAncestor(this.dom, el.dom ? el.dom : el);
+ },
+
+
+ getAttributeNS : function(ns, name){
+ return this.getAttribute(name, ns);
+ },
+
+
+ getAttribute: (function(){
+ var test = document.createElement('table'),
+ isBrokenOnTable = false,
+ hasGetAttribute = 'getAttribute' in test,
+ unknownRe = /undefined|unknown/;
+
+ if (hasGetAttribute) {
+
+ try {
+ test.getAttribute('ext:qtip');
+ } catch (e) {
+ isBrokenOnTable = true;
+ }
+
+ return function(name, ns) {
+ var el = this.dom,
+ value;
+
+ if (el.getAttributeNS) {
+ value = el.getAttributeNS(ns, name) || null;
+ }
+
+ if (value == null) {
+ if (ns) {
+ if (isBrokenOnTable && el.tagName.toUpperCase() == 'TABLE') {
+ try {
+ value = el.getAttribute(ns + ':' + name);
+ } catch (e) {
+ value = '';
+ }
+ } else {
+ value = el.getAttribute(ns + ':' + name);
+ }
+ } else {
+ value = el.getAttribute(name) || el[name];
+ }
+ }
+ return value || '';
+ };
+ } else {
+ return function(name, ns) {
+ var el = this.om,
+ value,
+ attribute;
+
+ if (ns) {
+ attribute = el[ns + ':' + name];
+ value = unknownRe.test(typeof attribute) ? undefined : attribute;
+ } else {
+ value = el[name];
+ }
+ return value || '';
+ };
+ }
+ test = null;
+ })(),
+
+
+ update : function(html) {
+ if (this.dom) {
+ this.dom.innerHTML = html;
+ }
+ return this;
+ }
+};
+
+var ep = El.prototype;
+
+El.addMethods = function(o){
+ Ext.apply(ep, o);
+};
+
+
+ep.on = ep.addListener;
+
+
+ep.un = ep.removeListener;
+
+
+ep.autoBoxAdjust = true;
+
+
+var unitPattern = /\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,
+ docEl;
+
+
+El.get = function(el){
+ var ex,
+ elm,
+ id;
+ if(!el){ return null; }
+ if (typeof el == "string") {
+ if (!(elm = DOC.getElementById(el))) {
+ return null;
+ }
+ if (EC[el] && EC[el].el) {
+ ex = EC[el].el;
+ ex.dom = elm;
+ } else {
+ ex = El.addToCache(new El(elm));
+ }
+ return ex;
+ } else if (el.tagName) {
+ if(!(id = el.id)){
+ id = Ext.id(el);
+ }
+ if (EC[id] && EC[id].el) {
+ ex = EC[id].el;
+ ex.dom = el;
+ } else {
+ ex = El.addToCache(new El(el));
+ }
+ return ex;
+ } else if (el instanceof El) {
+ if(el != docEl){
+
+
+
+
+ if (Ext.isIE && (el.id == undefined || el.id == '')) {
+ el.dom = el.dom;
+ } else {
+ el.dom = DOC.getElementById(el.id) || el.dom;
+ }
+ }
+ return el;
+ } else if(el.isComposite) {
+ return el;
+ } else if(Ext.isArray(el)) {
+ return El.select(el);
+ } else if(el == DOC) {
+
+ if(!docEl){
+ var f = function(){};
+ f.prototype = El.prototype;
+ docEl = new f();
+ docEl.dom = DOC;
+ }
+ return docEl;
+ }
+ return null;
+};
+
+El.addToCache = function(el, id){
+ id = id || el.id;
+ EC[id] = {
+ el: el,
+ data: {},
+ events: {}
+ };
+ return el;
+};
+
+
+El.data = function(el, key, value){
+ el = El.get(el);
+ if (!el) {
+ return null;
+ }
+ var c = EC[el.id].data;
+ if(arguments.length == 2){
+ return c[key];
+ }else{
+ return (c[key] = value);
+ }
+};
+
+
+
+
+function garbageCollect(){
+ if(!Ext.enableGarbageCollector){
+ clearInterval(El.collectorThreadId);
+ } else {
+ var eid,
+ el,
+ d,
+ o;
+
+ for(eid in EC){
+ o = EC[eid];
+ if(o.skipGC){
+ Ext.EventManager.removeFromSpecialCache(o.el);
+ continue;
+ }
+ el = o.el;
+ d = el.dom;
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ if(!d || !d.parentNode || (!d.offsetParent && !DOC.getElementById(eid))){
+ if(Ext.enableListenerCollection){
+ Ext.EventManager.removeAll(d);
+ }
+ delete EC[eid];
+ }
+ }
+
+ if (Ext.isIE) {
+ var t = {};
+ for (eid in EC) {
+ t[eid] = EC[eid];
+ }
+ EC = Ext.elCache = t;
+ }
+ }
+}
+El.collectorThreadId = setInterval(garbageCollect, 30000);
+
+var flyFn = function(){};
+flyFn.prototype = El.prototype;
+
+
+El.Flyweight = function(dom){
+ this.dom = dom;
+};
+
+El.Flyweight.prototype = new flyFn();
+El.Flyweight.prototype.isFlyweight = true;
+El._flyweights = {};
+
+
+El.fly = function(el, named){
+ var ret = null;
+ named = named || '_global';
+
+ if (el = Ext.getDom(el)) {
+ (El._flyweights[named] = El._flyweights[named] || new El.Flyweight()).dom = el;
+ ret = El._flyweights[named];
+ }
+ return ret;
+};
+
+
+Ext.get = El.get;
+
+
+Ext.fly = El.fly;
+
+
+var noBoxAdjust = Ext.isStrict ? {
+ select:1
+} : {
+ input:1, select:1, textarea:1
+};
+if(Ext.isIE || Ext.isGecko){
+ noBoxAdjust['button'] = 1;
+}
+
+})();
+
+Ext.Element.addMethods(function(){
+ var PARENTNODE = 'parentNode',
+ NEXTSIBLING = 'nextSibling',
+ PREVIOUSSIBLING = 'previousSibling',
+ DQ = Ext.DomQuery,
+ GET = Ext.get;
+
+ return {
+
+ findParent : function(simpleSelector, maxDepth, returnEl){
+ var p = this.dom,
+ b = document.body,
+ depth = 0,
+ stopEl;
+ if(Ext.isGecko && Object.prototype.toString.call(p) == '[object XULElement]') {
+ return null;
+ }
+ maxDepth = maxDepth || 50;
+ if (isNaN(maxDepth)) {
+ stopEl = Ext.getDom(maxDepth);
+ maxDepth = Number.MAX_VALUE;
+ }
+ while(p && p.nodeType == 1 && depth < maxDepth && p != b && p != stopEl){
+ if(DQ.is(p, simpleSelector)){
+ return returnEl ? GET(p) : p;
+ }
+ depth++;
+ p = p.parentNode;
+ }
+ return null;
+ },
+
+
+ findParentNode : function(simpleSelector, maxDepth, returnEl){
+ var p = Ext.fly(this.dom.parentNode, '_internal');
+ return p ? p.findParent(simpleSelector, maxDepth, returnEl) : null;
+ },
+
+
+ up : function(simpleSelector, maxDepth){
+ return this.findParentNode(simpleSelector, maxDepth, true);
+ },
+
+
+ select : function(selector){
+ return Ext.Element.select(selector, this.dom);
+ },
+
+
+ query : function(selector){
+ return DQ.select(selector, this.dom);
+ },
+
+
+ child : function(selector, returnDom){
+ var n = DQ.selectNode(selector, this.dom);
+ return returnDom ? n : GET(n);
+ },
+
+
+ down : function(selector, returnDom){
+ var n = DQ.selectNode(" > " + selector, this.dom);
+ return returnDom ? n : GET(n);
+ },
+
+
+ parent : function(selector, returnDom){
+ return this.matchNode(PARENTNODE, PARENTNODE, selector, returnDom);
+ },
+
+
+ next : function(selector, returnDom){
+ return this.matchNode(NEXTSIBLING, NEXTSIBLING, selector, returnDom);
+ },
+
+
+ prev : function(selector, returnDom){
+ return this.matchNode(PREVIOUSSIBLING, PREVIOUSSIBLING, selector, returnDom);
+ },
+
+
+
+ first : function(selector, returnDom){
+ return this.matchNode(NEXTSIBLING, 'firstChild', selector, returnDom);
+ },
+
+
+ last : function(selector, returnDom){
+ return this.matchNode(PREVIOUSSIBLING, 'lastChild', selector, returnDom);
+ },
+
+ matchNode : function(dir, start, selector, returnDom){
+ var n = this.dom[start];
+ while(n){
+ if(n.nodeType == 1 && (!selector || DQ.is(n, selector))){
+ return !returnDom ? GET(n) : n;
+ }
+ n = n[dir];
+ }
+ return null;
+ }
+ };
+}());
+Ext.Element.addMethods(
+function() {
+ var GETDOM = Ext.getDom,
+ GET = Ext.get,
+ DH = Ext.DomHelper;
+
+ return {
+
+ appendChild: function(el){
+ return GET(el).appendTo(this);
+ },
+
+
+ appendTo: function(el){
+ GETDOM(el).appendChild(this.dom);
+ return this;
+ },
+
+
+ insertBefore: function(el){
+ (el = GETDOM(el)).parentNode.insertBefore(this.dom, el);
+ return this;
+ },
+
+
+ insertAfter: function(el){
+ (el = GETDOM(el)).parentNode.insertBefore(this.dom, el.nextSibling);
+ return this;
+ },
+
+
+ insertFirst: function(el, returnDom){
+ el = el || {};
+ if(el.nodeType || el.dom || typeof el == 'string'){
+ el = GETDOM(el);
+ this.dom.insertBefore(el, this.dom.firstChild);
+ return !returnDom ? GET(el) : el;
+ }else{
+ return this.createChild(el, this.dom.firstChild, returnDom);
+ }
+ },
+
+
+ replace: function(el){
+ el = GET(el);
+ this.insertBefore(el);
+ el.remove();
+ return this;
+ },
+
+
+ replaceWith: function(el){
+ var me = this;
+
+ if(el.nodeType || el.dom || typeof el == 'string'){
+ el = GETDOM(el);
+ me.dom.parentNode.insertBefore(el, me.dom);
+ }else{
+ el = DH.insertBefore(me.dom, el);
+ }
+
+ delete Ext.elCache[me.id];
+ Ext.removeNode(me.dom);
+ me.id = Ext.id(me.dom = el);
+ Ext.Element.addToCache(me.isFlyweight ? new Ext.Element(me.dom) : me);
+ return me;
+ },
+
+
+ createChild: function(config, insertBefore, returnDom){
+ config = config || {tag:'div'};
+ return insertBefore ?
+ DH.insertBefore(insertBefore, config, returnDom !== true) :
+ DH[!this.dom.firstChild ? 'overwrite' : 'append'](this.dom, config, returnDom !== true);
+ },
+
+
+ wrap: function(config, returnDom){
+ var newEl = DH.insertBefore(this.dom, config || {tag: "div"}, !returnDom);
+ newEl.dom ? newEl.dom.appendChild(this.dom) : newEl.appendChild(this.dom);
+ return newEl;
+ },
+
+
+ insertHtml : function(where, html, returnEl){
+ var el = DH.insertHtml(where, this.dom, html);
+ return returnEl ? Ext.get(el) : el;
+ }
+ };
+}());
+Ext.Element.addMethods(function(){
+
+ var supports = Ext.supports,
+ propCache = {},
+ camelRe = /(-[a-z])/gi,
+ view = document.defaultView,
+ opacityRe = /alpha\(opacity=(.*)\)/i,
+ trimRe = /^\s+|\s+$/g,
+ EL = Ext.Element,
+ spacesRe = /\s+/,
+ wordsRe = /\w/g,
+ PADDING = "padding",
+ MARGIN = "margin",
+ BORDER = "border",
+ LEFT = "-left",
+ RIGHT = "-right",
+ TOP = "-top",
+ BOTTOM = "-bottom",
+ WIDTH = "-width",
+ MATH = Math,
+ HIDDEN = 'hidden',
+ ISCLIPPED = 'isClipped',
+ OVERFLOW = 'overflow',
+ OVERFLOWX = 'overflow-x',
+ OVERFLOWY = 'overflow-y',
+ ORIGINALCLIP = 'originalClip',
+
+ borders = {l: BORDER + LEFT + WIDTH, r: BORDER + RIGHT + WIDTH, t: BORDER + TOP + WIDTH, b: BORDER + BOTTOM + WIDTH},
+ paddings = {l: PADDING + LEFT, r: PADDING + RIGHT, t: PADDING + TOP, b: PADDING + BOTTOM},
+ margins = {l: MARGIN + LEFT, r: MARGIN + RIGHT, t: MARGIN + TOP, b: MARGIN + BOTTOM},
+ data = Ext.Element.data;
+
+
+
+ function camelFn(m, a) {
+ return a.charAt(1).toUpperCase();
+ }
+
+ function chkCache(prop) {
+ return propCache[prop] || (propCache[prop] = prop == 'float' ? (supports.cssFloat ? 'cssFloat' : 'styleFloat') : prop.replace(camelRe, camelFn));
+ }
+
+ return {
+
+ adjustWidth : function(width) {
+ var me = this;
+ var isNum = (typeof width == "number");
+ if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
+ width -= (me.getBorderWidth("lr") + me.getPadding("lr"));
+ }
+ return (isNum && width < 0) ? 0 : width;
+ },
+
+
+ adjustHeight : function(height) {
+ var me = this;
+ var isNum = (typeof height == "number");
+ if(isNum && me.autoBoxAdjust && !me.isBorderBox()){
+ height -= (me.getBorderWidth("tb") + me.getPadding("tb"));
+ }
+ return (isNum && height < 0) ? 0 : height;
+ },
+
+
+
+ addClass : function(className){
+ var me = this,
+ i,
+ len,
+ v,
+ cls = [];
+
+ if (!Ext.isArray(className)) {
+ if (typeof className == 'string' && !this.hasClass(className)) {
+ me.dom.className += " " + className;
+ }
+ }
+ else {
+ for (i = 0, len = className.length; i < len; i++) {
+ v = className[i];
+ if (typeof v == 'string' && (' ' + me.dom.className + ' ').indexOf(' ' + v + ' ') == -1) {
+ cls.push(v);
+ }
+ }
+ if (cls.length) {
+ me.dom.className += " " + cls.join(" ");
+ }
+ }
+ return me;
+ },
+
+
+ removeClass : function(className){
+ var me = this,
+ i,
+ idx,
+ len,
+ cls,
+ elClasses;
+ if (!Ext.isArray(className)){
+ className = [className];
+ }
+ if (me.dom && me.dom.className) {
+ elClasses = me.dom.className.replace(trimRe, '').split(spacesRe);
+ for (i = 0, len = className.length; i < len; i++) {
+ cls = className[i];
+ if (typeof cls == 'string') {
+ cls = cls.replace(trimRe, '');
+ idx = elClasses.indexOf(cls);
+ if (idx != -1) {
+ elClasses.splice(idx, 1);
+ }
+ }
+ }
+ me.dom.className = elClasses.join(" ");
+ }
+ return me;
+ },
+
+
+ radioClass : function(className){
+ var cn = this.dom.parentNode.childNodes,
+ v,
+ i,
+ len;
+ className = Ext.isArray(className) ? className : [className];
+ for (i = 0, len = cn.length; i < len; i++) {
+ v = cn[i];
+ if (v && v.nodeType == 1) {
+ Ext.fly(v, '_internal').removeClass(className);
+ }
+ };
+ return this.addClass(className);
+ },
+
+
+ toggleClass : function(className){
+ return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
+ },
+
+
+ hasClass : function(className){
+ return className && (' '+this.dom.className+' ').indexOf(' '+className+' ') != -1;
+ },
+
+
+ replaceClass : function(oldClassName, newClassName){
+ return this.removeClass(oldClassName).addClass(newClassName);
+ },
+
+ isStyle : function(style, val) {
+ return this.getStyle(style) == val;
+ },
+
+
+ getStyle : function(){
+ return view && view.getComputedStyle ?
+ function(prop){
+ var el = this.dom,
+ v,
+ cs,
+ out,
+ display;
+
+ if(el == document){
+ return null;
+ }
+ prop = chkCache(prop);
+ out = (v = el.style[prop]) ? v :
+ (cs = view.getComputedStyle(el, "")) ? cs[prop] : null;
+
+
+
+ if(prop == 'marginRight' && out != '0px' && !supports.correctRightMargin){
+ display = el.style.display;
+ el.style.display = 'inline-block';
+ out = view.getComputedStyle(el, '').marginRight;
+ el.style.display = display;
+ }
+
+ if(prop == 'backgroundColor' && out == 'rgba(0, 0, 0, 0)' && !supports.correctTransparentColor){
+ out = 'transparent';
+ }
+ return out;
+ } :
+ function(prop){
+ var el = this.dom,
+ m,
+ cs;
+
+ if(el == document) return null;
+ if (prop == 'opacity') {
+ if (el.style.filter.match) {
+ if(m = el.style.filter.match(opacityRe)){
+ var fv = parseFloat(m[1]);
+ if(!isNaN(fv)){
+ return fv ? fv / 100 : 0;
+ }
+ }
+ }
+ return 1;
+ }
+ prop = chkCache(prop);
+ return el.style[prop] || ((cs = el.currentStyle) ? cs[prop] : null);
+ };
+ }(),
+
+
+ getColor : function(attr, defaultValue, prefix){
+ var v = this.getStyle(attr),
+ color = (typeof prefix != 'undefined') ? prefix : '#',
+ h;
+
+ if(!v || (/transparent|inherit/.test(v))) {
+ return defaultValue;
+ }
+ if(/^r/.test(v)){
+ Ext.each(v.slice(4, v.length -1).split(','), function(s){
+ h = parseInt(s, 10);
+ color += (h < 16 ? '0' : '') + h.toString(16);
+ });
+ }else{
+ v = v.replace('#', '');
+ color += v.length == 3 ? v.replace(/^(\w)(\w)(\w)$/, '$1$1$2$2$3$3') : v;
+ }
+ return(color.length > 5 ? color.toLowerCase() : defaultValue);
+ },
+
+
+ setStyle : function(prop, value){
+ var tmp, style;
+
+ if (typeof prop != 'object') {
+ tmp = {};
+ tmp[prop] = value;
+ prop = tmp;
+ }
+ for (style in prop) {
+ value = prop[style];
+ style == 'opacity' ?
+ this.setOpacity(value) :
+ this.dom.style[chkCache(style)] = value;
+ }
+ return this;
+ },
+
+
+ setOpacity : function(opacity, animate){
+ var me = this,
+ s = me.dom.style;
+
+ if(!animate || !me.anim){
+ if(Ext.isIE9m){
+ var opac = opacity < 1 ? 'alpha(opacity=' + opacity * 100 + ')' : '',
+ val = s.filter.replace(opacityRe, '').replace(trimRe, '');
+
+ s.zoom = 1;
+ s.filter = val + (val.length > 0 ? ' ' : '') + opac;
+ }else{
+ s.opacity = opacity;
+ }
+ }else{
+ me.anim({opacity: {to: opacity}}, me.preanim(arguments, 1), null, .35, 'easeIn');
+ }
+ return me;
+ },
+
+
+ clearOpacity : function(){
+ var style = this.dom.style;
+ if(Ext.isIE9m){
+ if(!Ext.isEmpty(style.filter)){
+ style.filter = style.filter.replace(opacityRe, '').replace(trimRe, '');
+ }
+ }else{
+ style.opacity = style['-moz-opacity'] = style['-khtml-opacity'] = '';
+ }
+ return this;
+ },
+
+
+ getHeight : function(contentHeight){
+ var me = this,
+ dom = me.dom,
+ hidden = Ext.isIE9m && me.isStyle('display', 'none'),
+ h = MATH.max(dom.offsetHeight, hidden ? 0 : dom.clientHeight) || 0;
+
+ h = !contentHeight ? h : h - me.getBorderWidth("tb") - me.getPadding("tb");
+ return h < 0 ? 0 : h;
+ },
+
+
+ getWidth : function(contentWidth){
+ var me = this,
+ dom = me.dom,
+ hidden = Ext.isIE9m && me.isStyle('display', 'none'),
+ w = MATH.max(dom.offsetWidth, hidden ? 0 : dom.clientWidth) || 0;
+ w = !contentWidth ? w : w - me.getBorderWidth("lr") - me.getPadding("lr");
+ return w < 0 ? 0 : w;
+ },
+
+
+ setWidth : function(width, animate){
+ var me = this;
+ width = me.adjustWidth(width);
+ !animate || !me.anim ?
+ me.dom.style.width = me.addUnits(width) :
+ me.anim({width : {to : width}}, me.preanim(arguments, 1));
+ return me;
+ },
+
+
+ setHeight : function(height, animate){
+ var me = this;
+ height = me.adjustHeight(height);
+ !animate || !me.anim ?
+ me.dom.style.height = me.addUnits(height) :
+ me.anim({height : {to : height}}, me.preanim(arguments, 1));
+ return me;
+ },
+
+
+ getBorderWidth : function(side){
+ return this.addStyles(side, borders);
+ },
+
+
+ getPadding : function(side){
+ return this.addStyles(side, paddings);
+ },
+
+
+ clip : function(){
+ var me = this,
+ dom = me.dom;
+
+ if(!data(dom, ISCLIPPED)){
+ data(dom, ISCLIPPED, true);
+ data(dom, ORIGINALCLIP, {
+ o: me.getStyle(OVERFLOW),
+ x: me.getStyle(OVERFLOWX),
+ y: me.getStyle(OVERFLOWY)
+ });
+ me.setStyle(OVERFLOW, HIDDEN);
+ me.setStyle(OVERFLOWX, HIDDEN);
+ me.setStyle(OVERFLOWY, HIDDEN);
+ }
+ return me;
+ },
+
+
+ unclip : function(){
+ var me = this,
+ dom = me.dom;
+
+ if(data(dom, ISCLIPPED)){
+ data(dom, ISCLIPPED, false);
+ var o = data(dom, ORIGINALCLIP);
+ if(o.o){
+ me.setStyle(OVERFLOW, o.o);
+ }
+ if(o.x){
+ me.setStyle(OVERFLOWX, o.x);
+ }
+ if(o.y){
+ me.setStyle(OVERFLOWY, o.y);
+ }
+ }
+ return me;
+ },
+
+
+ addStyles : function(sides, styles){
+ var ttlSize = 0,
+ sidesArr = sides.match(wordsRe),
+ side,
+ size,
+ i,
+ len = sidesArr.length;
+ for (i = 0; i < len; i++) {
+ side = sidesArr[i];
+ size = side && parseInt(this.getStyle(styles[side]), 10);
+ if (size) {
+ ttlSize += MATH.abs(size);
+ }
+ }
+ return ttlSize;
+ },
+
+ margins : margins
+ };
+}()
+);
+
+(function(){
+var D = Ext.lib.Dom,
+ LEFT = "left",
+ RIGHT = "right",
+ TOP = "top",
+ BOTTOM = "bottom",
+ POSITION = "position",
+ STATIC = "static",
+ RELATIVE = "relative",
+ AUTO = "auto",
+ ZINDEX = "z-index";
+
+Ext.Element.addMethods({
+
+ getX : function(){
+ return D.getX(this.dom);
+ },
+
+
+ getY : function(){
+ return D.getY(this.dom);
+ },
+
+
+ getXY : function(){
+ return D.getXY(this.dom);
+ },
+
+
+ getOffsetsTo : function(el){
+ var o = this.getXY(),
+ e = Ext.fly(el, '_internal').getXY();
+ return [o[0]-e[0],o[1]-e[1]];
+ },
+
+
+ setX : function(x, animate){
+ return this.setXY([x, this.getY()], this.animTest(arguments, animate, 1));
+ },
+
+
+ setY : function(y, animate){
+ return this.setXY([this.getX(), y], this.animTest(arguments, animate, 1));
+ },
+
+
+ setLeft : function(left){
+ this.setStyle(LEFT, this.addUnits(left));
+ return this;
+ },
+
+
+ setTop : function(top){
+ this.setStyle(TOP, this.addUnits(top));
+ return this;
+ },
+
+
+ setRight : function(right){
+ this.setStyle(RIGHT, this.addUnits(right));
+ return this;
+ },
+
+
+ setBottom : function(bottom){
+ this.setStyle(BOTTOM, this.addUnits(bottom));
+ return this;
+ },
+
+
+ setXY : function(pos, animate){
+ var me = this;
+ if(!animate || !me.anim){
+ D.setXY(me.dom, pos);
+ }else{
+ me.anim({points: {to: pos}}, me.preanim(arguments, 1), 'motion');
+ }
+ return me;
+ },
+
+
+ setLocation : function(x, y, animate){
+ return this.setXY([x, y], this.animTest(arguments, animate, 2));
+ },
+
+
+ moveTo : function(x, y, animate){
+ return this.setXY([x, y], this.animTest(arguments, animate, 2));
+ },
+
+
+ getLeft : function(local){
+ return !local ? this.getX() : parseInt(this.getStyle(LEFT), 10) || 0;
+ },
+
+
+ getRight : function(local){
+ var me = this;
+ return !local ? me.getX() + me.getWidth() : (me.getLeft(true) + me.getWidth()) || 0;
+ },
+
+
+ getTop : function(local) {
+ return !local ? this.getY() : parseInt(this.getStyle(TOP), 10) || 0;
+ },
+
+
+ getBottom : function(local){
+ var me = this;
+ return !local ? me.getY() + me.getHeight() : (me.getTop(true) + me.getHeight()) || 0;
+ },
+
+
+ position : function(pos, zIndex, x, y){
+ var me = this;
+
+ if(!pos && me.isStyle(POSITION, STATIC)){
+ me.setStyle(POSITION, RELATIVE);
+ } else if(pos) {
+ me.setStyle(POSITION, pos);
+ }
+ if(zIndex){
+ me.setStyle(ZINDEX, zIndex);
+ }
+ if(x || y) me.setXY([x || false, y || false]);
+ },
+
+
+ clearPositioning : function(value){
+ value = value || '';
+ this.setStyle({
+ left : value,
+ right : value,
+ top : value,
+ bottom : value,
+ "z-index" : "",
+ position : STATIC
+ });
+ return this;
+ },
+
+
+ getPositioning : function(){
+ var l = this.getStyle(LEFT);
+ var t = this.getStyle(TOP);
+ return {
+ "position" : this.getStyle(POSITION),
+ "left" : l,
+ "right" : l ? "" : this.getStyle(RIGHT),
+ "top" : t,
+ "bottom" : t ? "" : this.getStyle(BOTTOM),
+ "z-index" : this.getStyle(ZINDEX)
+ };
+ },
+
+
+ setPositioning : function(pc){
+ var me = this,
+ style = me.dom.style;
+
+ me.setStyle(pc);
+
+ if(pc.right == AUTO){
+ style.right = "";
+ }
+ if(pc.bottom == AUTO){
+ style.bottom = "";
+ }
+
+ return me;
+ },
+
+
+ translatePoints : function(x, y){
+ y = isNaN(x[1]) ? y : x[1];
+ x = isNaN(x[0]) ? x : x[0];
+ var me = this,
+ relative = me.isStyle(POSITION, RELATIVE),
+ o = me.getXY(),
+ l = parseInt(me.getStyle(LEFT), 10),
+ t = parseInt(me.getStyle(TOP), 10);
+
+ l = !isNaN(l) ? l : (relative ? 0 : me.dom.offsetLeft);
+ t = !isNaN(t) ? t : (relative ? 0 : me.dom.offsetTop);
+
+ return {left: (x - o[0] + l), top: (y - o[1] + t)};
+ },
+
+ animTest : function(args, animate, i) {
+ return !!animate && this.preanim ? this.preanim(args, i) : false;
+ }
+});
+})();
+Ext.Element.addMethods({
+
+ isScrollable : function(){
+ var dom = this.dom;
+ return dom.scrollHeight > dom.clientHeight || dom.scrollWidth > dom.clientWidth;
+ },
+
+
+ scrollTo : function(side, value){
+ this.dom["scroll" + (/top/i.test(side) ? "Top" : "Left")] = value;
+ return this;
+ },
+
+
+ getScroll : function(){
+ var d = this.dom,
+ doc = document,
+ body = doc.body,
+ docElement = doc.documentElement,
+ l,
+ t,
+ ret;
+
+ if(d == doc || d == body){
+ if(Ext.isIE && Ext.isStrict){
+ l = docElement.scrollLeft;
+ t = docElement.scrollTop;
+ }else{
+ l = window.pageXOffset;
+ t = window.pageYOffset;
+ }
+ ret = {left: l || (body ? body.scrollLeft : 0), top: t || (body ? body.scrollTop : 0)};
+ }else{
+ ret = {left: d.scrollLeft, top: d.scrollTop};
+ }
+ return ret;
+ }
+});
+
+Ext.Element.VISIBILITY = 1;
+
+Ext.Element.DISPLAY = 2;
+
+
+Ext.Element.OFFSETS = 3;
+
+
+Ext.Element.ASCLASS = 4;
+
+
+Ext.Element.visibilityCls = 'x-hide-nosize';
+
+Ext.Element.addMethods(function(){
+ var El = Ext.Element,
+ OPACITY = "opacity",
+ VISIBILITY = "visibility",
+ DISPLAY = "display",
+ HIDDEN = "hidden",
+ OFFSETS = "offsets",
+ ASCLASS = "asclass",
+ NONE = "none",
+ NOSIZE = 'nosize',
+ ORIGINALDISPLAY = 'originalDisplay',
+ VISMODE = 'visibilityMode',
+ ISVISIBLE = 'isVisible',
+ data = El.data,
+ getDisplay = function(dom){
+ var d = data(dom, ORIGINALDISPLAY);
+ if(d === undefined){
+ data(dom, ORIGINALDISPLAY, d = '');
+ }
+ return d;
+ },
+ getVisMode = function(dom){
+ var m = data(dom, VISMODE);
+ if(m === undefined){
+ data(dom, VISMODE, m = 1);
+ }
+ return m;
+ };
+
+ return {
+
+ originalDisplay : "",
+ visibilityMode : 1,
+
+
+ setVisibilityMode : function(visMode){
+ data(this.dom, VISMODE, visMode);
+ return this;
+ },
+
+
+ animate : function(args, duration, onComplete, easing, animType){
+ this.anim(args, {duration: duration, callback: onComplete, easing: easing}, animType);
+ return this;
+ },
+
+
+ anim : function(args, opt, animType, defaultDur, defaultEase, cb){
+ animType = animType || 'run';
+ opt = opt || {};
+ var me = this,
+ anim = Ext.lib.Anim[animType](
+ me.dom,
+ args,
+ (opt.duration || defaultDur) || .35,
+ (opt.easing || defaultEase) || 'easeOut',
+ function(){
+ if(cb) cb.call(me);
+ if(opt.callback) opt.callback.call(opt.scope || me, me, opt);
+ },
+ me
+ );
+ opt.anim = anim;
+ return anim;
+ },
+
+
+ preanim : function(a, i){
+ return !a[i] ? false : (typeof a[i] == 'object' ? a[i]: {duration: a[i+1], callback: a[i+2], easing: a[i+3]});
+ },
+
+
+ isVisible : function() {
+ var me = this,
+ dom = me.dom,
+ visible = data(dom, ISVISIBLE);
+
+ if(typeof visible == 'boolean'){
+ return visible;
+ }
+
+ visible = !me.isStyle(VISIBILITY, HIDDEN) &&
+ !me.isStyle(DISPLAY, NONE) &&
+ !((getVisMode(dom) == El.ASCLASS) && me.hasClass(me.visibilityCls || El.visibilityCls));
+
+ data(dom, ISVISIBLE, visible);
+ return visible;
+ },
+
+
+ setVisible : function(visible, animate){
+ var me = this, isDisplay, isVisibility, isOffsets, isNosize,
+ dom = me.dom,
+ visMode = getVisMode(dom);
+
+
+
+ if (typeof animate == 'string'){
+ switch (animate) {
+ case DISPLAY:
+ visMode = El.DISPLAY;
+ break;
+ case VISIBILITY:
+ visMode = El.VISIBILITY;
+ break;
+ case OFFSETS:
+ visMode = El.OFFSETS;
+ break;
+ case NOSIZE:
+ case ASCLASS:
+ visMode = El.ASCLASS;
+ break;
+ }
+ me.setVisibilityMode(visMode);
+ animate = false;
+ }
+
+ if (!animate || !me.anim) {
+ if(visMode == El.ASCLASS ){
+
+ me[visible?'removeClass':'addClass'](me.visibilityCls || El.visibilityCls);
+
+ } else if (visMode == El.DISPLAY){
+
+ return me.setDisplayed(visible);
+
+ } else if (visMode == El.OFFSETS){
+
+ if (!visible){
+ me.hideModeStyles = {
+ position: me.getStyle('position'),
+ top: me.getStyle('top'),
+ left: me.getStyle('left')
+ };
+ me.applyStyles({position: 'absolute', top: '-10000px', left: '-10000px'});
+ } else {
+ me.applyStyles(me.hideModeStyles || {position: '', top: '', left: ''});
+ delete me.hideModeStyles;
+ }
+
+ }else{
+ me.fixDisplay();
+ dom.style.visibility = visible ? "visible" : HIDDEN;
+ }
+ }else{
+
+ if(visible){
+ me.setOpacity(.01);
+ me.setVisible(true);
+ }
+ me.anim({opacity: { to: (visible?1:0) }},
+ me.preanim(arguments, 1),
+ null,
+ .35,
+ 'easeIn',
+ function(){
+ visible || me.setVisible(false).setOpacity(1);
+ });
+ }
+ data(dom, ISVISIBLE, visible);
+ return me;
+ },
+
+
+
+ hasMetrics : function(){
+ var dom = this.dom;
+ return this.isVisible() || (getVisMode(dom) == El.VISIBILITY);
+ },
+
+
+ toggle : function(animate){
+ var me = this;
+ me.setVisible(!me.isVisible(), me.preanim(arguments, 0));
+ return me;
+ },
+
+
+ setDisplayed : function(value) {
+ if(typeof value == "boolean"){
+ value = value ? getDisplay(this.dom) : NONE;
+ }
+ this.setStyle(DISPLAY, value);
+ return this;
+ },
+
+
+ fixDisplay : function(){
+ var me = this;
+ if(me.isStyle(DISPLAY, NONE)){
+ me.setStyle(VISIBILITY, HIDDEN);
+ me.setStyle(DISPLAY, getDisplay(this.dom));
+ if(me.isStyle(DISPLAY, NONE)){
+ me.setStyle(DISPLAY, "block");
+ }
+ }
+ },
+
+
+ hide : function(animate){
+
+ if (typeof animate == 'string'){
+ this.setVisible(false, animate);
+ return this;
+ }
+ this.setVisible(false, this.preanim(arguments, 0));
+ return this;
+ },
+
+
+ show : function(animate){
+
+ if (typeof animate == 'string'){
+ this.setVisible(true, animate);
+ return this;
+ }
+ this.setVisible(true, this.preanim(arguments, 0));
+ return this;
+ }
+ };
+}());(function(){
+
+ var NULL = null,
+ UNDEFINED = undefined,
+ TRUE = true,
+ FALSE = false,
+ SETX = "setX",
+ SETY = "setY",
+ SETXY = "setXY",
+ LEFT = "left",
+ BOTTOM = "bottom",
+ TOP = "top",
+ RIGHT = "right",
+ HEIGHT = "height",
+ WIDTH = "width",
+ POINTS = "points",
+ HIDDEN = "hidden",
+ ABSOLUTE = "absolute",
+ VISIBLE = "visible",
+ MOTION = "motion",
+ POSITION = "position",
+ EASEOUT = "easeOut",
+
+ flyEl = new Ext.Element.Flyweight(),
+ queues = {},
+ getObject = function(o){
+ return o || {};
+ },
+ fly = function(dom){
+ flyEl.dom = dom;
+ flyEl.id = Ext.id(dom);
+ return flyEl;
+ },
+
+ getQueue = function(id){
+ if(!queues[id]){
+ queues[id] = [];
+ }
+ return queues[id];
+ },
+ setQueue = function(id, value){
+ queues[id] = value;
+ };
+
+
+Ext.enableFx = TRUE;
+
+
+Ext.Fx = {
+
+
+
+ switchStatements : function(key, fn, argHash){
+ return fn.apply(this, argHash[key]);
+ },
+
+
+ slideIn : function(anchor, o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ st = dom.style,
+ xy,
+ r,
+ b,
+ wrap,
+ after,
+ st,
+ args,
+ pt,
+ bw,
+ bh;
+
+ anchor = anchor || "t";
+
+ me.queueFx(o, function(){
+ xy = fly(dom).getXY();
+
+ fly(dom).fixDisplay();
+
+
+ r = fly(dom).getFxRestore();
+ b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight};
+ b.right = b.x + b.width;
+ b.bottom = b.y + b.height;
+
+
+ fly(dom).setWidth(b.width).setHeight(b.height);
+
+
+ wrap = fly(dom).fxWrap(r.pos, o, HIDDEN);
+
+ st.visibility = VISIBLE;
+ st.position = ABSOLUTE;
+
+
+ function after(){
+ fly(dom).fxUnwrap(wrap, r.pos, o);
+ st.width = r.width;
+ st.height = r.height;
+ fly(dom).afterFx(o);
+ }
+
+
+ pt = {to: [b.x, b.y]};
+ bw = {to: b.width};
+ bh = {to: b.height};
+
+ function argCalc(wrap, style, ww, wh, sXY, sXYval, s1, s2, w, h, p){
+ var ret = {};
+ fly(wrap).setWidth(ww).setHeight(wh);
+ if(fly(wrap)[sXY]){
+ fly(wrap)[sXY](sXYval);
+ }
+ style[s1] = style[s2] = "0";
+ if(w){
+ ret.width = w;
+ }
+ if(h){
+ ret.height = h;
+ }
+ if(p){
+ ret.points = p;
+ }
+ return ret;
+ };
+
+ args = fly(dom).switchStatements(anchor.toLowerCase(), argCalc, {
+ t : [wrap, st, b.width, 0, NULL, NULL, LEFT, BOTTOM, NULL, bh, NULL],
+ l : [wrap, st, 0, b.height, NULL, NULL, RIGHT, TOP, bw, NULL, NULL],
+ r : [wrap, st, b.width, b.height, SETX, b.right, LEFT, TOP, NULL, NULL, pt],
+ b : [wrap, st, b.width, b.height, SETY, b.bottom, LEFT, TOP, NULL, bh, pt],
+ tl : [wrap, st, 0, 0, NULL, NULL, RIGHT, BOTTOM, bw, bh, pt],
+ bl : [wrap, st, 0, 0, SETY, b.y + b.height, RIGHT, TOP, bw, bh, pt],
+ br : [wrap, st, 0, 0, SETXY, [b.right, b.bottom], LEFT, TOP, bw, bh, pt],
+ tr : [wrap, st, 0, 0, SETX, b.x + b.width, LEFT, BOTTOM, bw, bh, pt]
+ });
+
+ st.visibility = VISIBLE;
+ fly(wrap).show();
+
+ arguments.callee.anim = fly(wrap).fxanim(args,
+ o,
+ MOTION,
+ .5,
+ EASEOUT,
+ after);
+ });
+ return me;
+ },
+
+
+ slideOut : function(anchor, o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ st = dom.style,
+ xy = me.getXY(),
+ wrap,
+ r,
+ b,
+ a,
+ zero = {to: 0};
+
+ anchor = anchor || "t";
+
+ me.queueFx(o, function(){
+
+
+ r = fly(dom).getFxRestore();
+ b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight};
+ b.right = b.x + b.width;
+ b.bottom = b.y + b.height;
+
+
+ fly(dom).setWidth(b.width).setHeight(b.height);
+
+
+ wrap = fly(dom).fxWrap(r.pos, o, VISIBLE);
+
+ st.visibility = VISIBLE;
+ st.position = ABSOLUTE;
+ fly(wrap).setWidth(b.width).setHeight(b.height);
+
+ function after(){
+ o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
+ fly(dom).fxUnwrap(wrap, r.pos, o);
+ st.width = r.width;
+ st.height = r.height;
+ fly(dom).afterFx(o);
+ }
+
+ function argCalc(style, s1, s2, p1, v1, p2, v2, p3, v3){
+ var ret = {};
+
+ style[s1] = style[s2] = "0";
+ ret[p1] = v1;
+ if(p2){
+ ret[p2] = v2;
+ }
+ if(p3){
+ ret[p3] = v3;
+ }
+
+ return ret;
+ };
+
+ a = fly(dom).switchStatements(anchor.toLowerCase(), argCalc, {
+ t : [st, LEFT, BOTTOM, HEIGHT, zero],
+ l : [st, RIGHT, TOP, WIDTH, zero],
+ r : [st, LEFT, TOP, WIDTH, zero, POINTS, {to : [b.right, b.y]}],
+ b : [st, LEFT, TOP, HEIGHT, zero, POINTS, {to : [b.x, b.bottom]}],
+ tl : [st, RIGHT, BOTTOM, WIDTH, zero, HEIGHT, zero],
+ bl : [st, RIGHT, TOP, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.x, b.bottom]}],
+ br : [st, LEFT, TOP, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.x + b.width, b.bottom]}],
+ tr : [st, LEFT, BOTTOM, WIDTH, zero, HEIGHT, zero, POINTS, {to : [b.right, b.y]}]
+ });
+
+ arguments.callee.anim = fly(wrap).fxanim(a,
+ o,
+ MOTION,
+ .5,
+ EASEOUT,
+ after);
+ });
+ return me;
+ },
+
+
+ puff : function(o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ st = dom.style,
+ width,
+ height,
+ r;
+
+ me.queueFx(o, function(){
+ width = fly(dom).getWidth();
+ height = fly(dom).getHeight();
+ fly(dom).clearOpacity();
+ fly(dom).show();
+
+
+ r = fly(dom).getFxRestore();
+
+ function after(){
+ o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
+ fly(dom).clearOpacity();
+ fly(dom).setPositioning(r.pos);
+ st.width = r.width;
+ st.height = r.height;
+ st.fontSize = '';
+ fly(dom).afterFx(o);
+ }
+
+ arguments.callee.anim = fly(dom).fxanim({
+ width : {to : fly(dom).adjustWidth(width * 2)},
+ height : {to : fly(dom).adjustHeight(height * 2)},
+ points : {by : [-width * .5, -height * .5]},
+ opacity : {to : 0},
+ fontSize: {to : 200, unit: "%"}
+ },
+ o,
+ MOTION,
+ .5,
+ EASEOUT,
+ after);
+ });
+ return me;
+ },
+
+
+ switchOff : function(o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ st = dom.style,
+ r;
+
+ me.queueFx(o, function(){
+ fly(dom).clearOpacity();
+ fly(dom).clip();
+
+
+ r = fly(dom).getFxRestore();
+
+ function after(){
+ o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
+ fly(dom).clearOpacity();
+ fly(dom).setPositioning(r.pos);
+ st.width = r.width;
+ st.height = r.height;
+ fly(dom).afterFx(o);
+ };
+
+ fly(dom).fxanim({opacity : {to : 0.3}},
+ NULL,
+ NULL,
+ .1,
+ NULL,
+ function(){
+ fly(dom).clearOpacity();
+ (function(){
+ fly(dom).fxanim({
+ height : {to : 1},
+ points : {by : [0, fly(dom).getHeight() * .5]}
+ },
+ o,
+ MOTION,
+ 0.3,
+ 'easeIn',
+ after);
+ }).defer(100);
+ });
+ });
+ return me;
+ },
+
+
+ highlight : function(color, o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ attr = o.attr || "backgroundColor",
+ a = {},
+ restore;
+
+ me.queueFx(o, function(){
+ fly(dom).clearOpacity();
+ fly(dom).show();
+
+ function after(){
+ dom.style[attr] = restore;
+ fly(dom).afterFx(o);
+ }
+ restore = dom.style[attr];
+ a[attr] = {from: color || "ffff9c", to: o.endColor || fly(dom).getColor(attr) || "ffffff"};
+ arguments.callee.anim = fly(dom).fxanim(a,
+ o,
+ 'color',
+ 1,
+ 'easeIn',
+ after);
+ });
+ return me;
+ },
+
+
+ frame : function(color, count, o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ proxy,
+ active;
+
+ me.queueFx(o, function(){
+ color = color || '#C3DAF9';
+ if(color.length == 6){
+ color = '#' + color;
+ }
+ count = count || 1;
+ fly(dom).show();
+
+ var xy = fly(dom).getXY(),
+ b = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: dom.offsetWidth, height: dom.offsetHeight},
+ queue = function(){
+ proxy = fly(document.body || document.documentElement).createChild({
+ style:{
+ position : ABSOLUTE,
+ 'z-index': 35000,
+ border : '0px solid ' + color
+ }
+ });
+ return proxy.queueFx({}, animFn);
+ };
+
+
+ arguments.callee.anim = {
+ isAnimated: true,
+ stop: function() {
+ count = 0;
+ proxy.stopFx();
+ }
+ };
+
+ function animFn(){
+ var scale = Ext.isBorderBox ? 2 : 1;
+ active = proxy.anim({
+ top : {from : b.y, to : b.y - 20},
+ left : {from : b.x, to : b.x - 20},
+ borderWidth : {from : 0, to : 10},
+ opacity : {from : 1, to : 0},
+ height : {from : b.height, to : b.height + 20 * scale},
+ width : {from : b.width, to : b.width + 20 * scale}
+ },{
+ duration: o.duration || 1,
+ callback: function() {
+ proxy.remove();
+ --count > 0 ? queue() : fly(dom).afterFx(o);
+ }
+ });
+ arguments.callee.anim = {
+ isAnimated: true,
+ stop: function(){
+ active.stop();
+ }
+ };
+ };
+ queue();
+ });
+ return me;
+ },
+
+
+ pause : function(seconds){
+ var dom = this.dom,
+ t;
+
+ this.queueFx({}, function(){
+ t = setTimeout(function(){
+ fly(dom).afterFx({});
+ }, seconds * 1000);
+ arguments.callee.anim = {
+ isAnimated: true,
+ stop: function(){
+ clearTimeout(t);
+ fly(dom).afterFx({});
+ }
+ };
+ });
+ return this;
+ },
+
+
+ fadeIn : function(o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ to = o.endOpacity || 1;
+
+ me.queueFx(o, function(){
+ fly(dom).setOpacity(0);
+ fly(dom).fixDisplay();
+ dom.style.visibility = VISIBLE;
+ arguments.callee.anim = fly(dom).fxanim({opacity:{to:to}},
+ o, NULL, .5, EASEOUT, function(){
+ if(to == 1){
+ fly(dom).clearOpacity();
+ }
+ fly(dom).afterFx(o);
+ });
+ });
+ return me;
+ },
+
+
+ fadeOut : function(o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ style = dom.style,
+ to = o.endOpacity || 0;
+
+ me.queueFx(o, function(){
+ arguments.callee.anim = fly(dom).fxanim({
+ opacity : {to : to}},
+ o,
+ NULL,
+ .5,
+ EASEOUT,
+ function(){
+ if(to == 0){
+ Ext.Element.data(dom, 'visibilityMode') == Ext.Element.DISPLAY || o.useDisplay ?
+ style.display = "none" :
+ style.visibility = HIDDEN;
+
+ fly(dom).clearOpacity();
+ }
+ fly(dom).afterFx(o);
+ });
+ });
+ return me;
+ },
+
+
+ scale : function(w, h, o){
+ this.shift(Ext.apply({}, o, {
+ width: w,
+ height: h
+ }));
+ return this;
+ },
+
+
+ shift : function(o){
+ o = getObject(o);
+ var dom = this.dom,
+ a = {};
+
+ this.queueFx(o, function(){
+ for (var prop in o) {
+ if (o[prop] != UNDEFINED) {
+ a[prop] = {to : o[prop]};
+ }
+ }
+
+ a.width ? a.width.to = fly(dom).adjustWidth(o.width) : a;
+ a.height ? a.height.to = fly(dom).adjustWidth(o.height) : a;
+
+ if (a.x || a.y || a.xy) {
+ a.points = a.xy ||
+ {to : [ a.x ? a.x.to : fly(dom).getX(),
+ a.y ? a.y.to : fly(dom).getY()]};
+ }
+
+ arguments.callee.anim = fly(dom).fxanim(a,
+ o,
+ MOTION,
+ .35,
+ EASEOUT,
+ function(){
+ fly(dom).afterFx(o);
+ });
+ });
+ return this;
+ },
+
+
+ ghost : function(anchor, o){
+ o = getObject(o);
+ var me = this,
+ dom = me.dom,
+ st = dom.style,
+ a = {opacity: {to: 0}, points: {}},
+ pt = a.points,
+ r,
+ w,
+ h;
+
+ anchor = anchor || "b";
+
+ me.queueFx(o, function(){
+
+ r = fly(dom).getFxRestore();
+ w = fly(dom).getWidth();
+ h = fly(dom).getHeight();
+
+ function after(){
+ o.useDisplay ? fly(dom).setDisplayed(FALSE) : fly(dom).hide();
+ fly(dom).clearOpacity();
+ fly(dom).setPositioning(r.pos);
+ st.width = r.width;
+ st.height = r.height;
+ fly(dom).afterFx(o);
+ }
+
+ pt.by = fly(dom).switchStatements(anchor.toLowerCase(), function(v1,v2){ return [v1, v2];}, {
+ t : [0, -h],
+ l : [-w, 0],
+ r : [w, 0],
+ b : [0, h],
+ tl : [-w, -h],
+ bl : [-w, h],
+ br : [w, h],
+ tr : [w, -h]
+ });
+
+ arguments.callee.anim = fly(dom).fxanim(a,
+ o,
+ MOTION,
+ .5,
+ EASEOUT, after);
+ });
+ return me;
+ },
+
+
+ syncFx : function(){
+ var me = this;
+ me.fxDefaults = Ext.apply(me.fxDefaults || {}, {
+ block : FALSE,
+ concurrent : TRUE,
+ stopFx : FALSE
+ });
+ return me;
+ },
+
+
+ sequenceFx : function(){
+ var me = this;
+ me.fxDefaults = Ext.apply(me.fxDefaults || {}, {
+ block : FALSE,
+ concurrent : FALSE,
+ stopFx : FALSE
+ });
+ return me;
+ },
+
+
+ nextFx : function(){
+ var ef = getQueue(this.dom.id)[0];
+ if(ef){
+ ef.call(this);
+ }
+ },
+
+
+ hasActiveFx : function(){
+ return getQueue(this.dom.id)[0];
+ },
+
+
+ stopFx : function(finish){
+ var me = this,
+ id = me.dom.id;
+ if(me.hasActiveFx()){
+ var cur = getQueue(id)[0];
+ if(cur && cur.anim){
+ if(cur.anim.isAnimated){
+ setQueue(id, [cur]);
+ cur.anim.stop(finish !== undefined ? finish : TRUE);
+ }else{
+ setQueue(id, []);
+ }
+ }
+ }
+ return me;
+ },
+
+
+ beforeFx : function(o){
+ if(this.hasActiveFx() && !o.concurrent){
+ if(o.stopFx){
+ this.stopFx();
+ return TRUE;
+ }
+ return FALSE;
+ }
+ return TRUE;
+ },
+
+
+ hasFxBlock : function(){
+ var q = getQueue(this.dom.id);
+ return q && q[0] && q[0].block;
+ },
+
+
+ queueFx : function(o, fn){
+ var me = fly(this.dom);
+ if(!me.hasFxBlock()){
+ Ext.applyIf(o, me.fxDefaults);
+ if(!o.concurrent){
+ var run = me.beforeFx(o);
+ fn.block = o.block;
+ getQueue(me.dom.id).push(fn);
+ if(run){
+ me.nextFx();
+ }
+ }else{
+ fn.call(me);
+ }
+ }
+ return me;
+ },
+
+
+ fxWrap : function(pos, o, vis){
+ var dom = this.dom,
+ wrap,
+ wrapXY;
+ if(!o.wrap || !(wrap = Ext.getDom(o.wrap))){
+ if(o.fixPosition){
+ wrapXY = fly(dom).getXY();
+ }
+ var div = document.createElement("div");
+ div.style.visibility = vis;
+ wrap = dom.parentNode.insertBefore(div, dom);
+ fly(wrap).setPositioning(pos);
+ if(fly(wrap).isStyle(POSITION, "static")){
+ fly(wrap).position("relative");
+ }
+ fly(dom).clearPositioning('auto');
+ fly(wrap).clip();
+ wrap.appendChild(dom);
+ if(wrapXY){
+ fly(wrap).setXY(wrapXY);
+ }
+ }
+ return wrap;
+ },
+
+
+ fxUnwrap : function(wrap, pos, o){
+ var dom = this.dom;
+ fly(dom).clearPositioning();
+ fly(dom).setPositioning(pos);
+ if(!o.wrap){
+ var pn = fly(wrap).dom.parentNode;
+ pn.insertBefore(dom, wrap);
+ fly(wrap).remove();
+ }
+ },
+
+
+ getFxRestore : function(){
+ var st = this.dom.style;
+ return {pos: this.getPositioning(), width: st.width, height : st.height};
+ },
+
+
+ afterFx : function(o){
+ var dom = this.dom,
+ id = dom.id;
+ if(o.afterStyle){
+ fly(dom).setStyle(o.afterStyle);
+ }
+ if(o.afterCls){
+ fly(dom).addClass(o.afterCls);
+ }
+ if(o.remove == TRUE){
+ fly(dom).remove();
+ }
+ if(o.callback){
+ o.callback.call(o.scope, fly(dom));
+ }
+ if(!o.concurrent){
+ getQueue(id).shift();
+ fly(dom).nextFx();
+ }
+ },
+
+
+ fxanim : function(args, opt, animType, defaultDur, defaultEase, cb){
+ animType = animType || 'run';
+ opt = opt || {};
+ var anim = Ext.lib.Anim[animType](
+ this.dom,
+ args,
+ (opt.duration || defaultDur) || .35,
+ (opt.easing || defaultEase) || EASEOUT,
+ cb,
+ this
+ );
+ opt.anim = anim;
+ return anim;
+ }
+};
+
+
+Ext.Fx.resize = Ext.Fx.scale;
+
+
+
+Ext.Element.addMethods(Ext.Fx);
+})();
+
+Ext.CompositeElementLite = function(els, root){
+
+ this.elements = [];
+ this.add(els, root);
+ this.el = new Ext.Element.Flyweight();
+};
+
+Ext.CompositeElementLite.prototype = {
+ isComposite: true,
+
+
+ getElement : function(el){
+
+ var e = this.el;
+ e.dom = el;
+ e.id = el.id;
+ return e;
+ },
+
+
+ transformElement : function(el){
+ return Ext.getDom(el);
+ },
+
+
+ getCount : function(){
+ return this.elements.length;
+ },
+
+ add : function(els, root){
+ var me = this,
+ elements = me.elements;
+ if(!els){
+ return this;
+ }
+ if(typeof els == "string"){
+ els = Ext.Element.selectorFunction(els, root);
+ }else if(els.isComposite){
+ els = els.elements;
+ }else if(!Ext.isIterable(els)){
+ els = [els];
+ }
+
+ for(var i = 0, len = els.length; i < len; ++i){
+ elements.push(me.transformElement(els[i]));
+ }
+ return me;
+ },
+
+ invoke : function(fn, args){
+ var me = this,
+ els = me.elements,
+ len = els.length,
+ e,
+ i;
+
+ for(i = 0; i < len; i++) {
+ e = els[i];
+ if(e){
+ Ext.Element.prototype[fn].apply(me.getElement(e), args);
+ }
+ }
+ return me;
+ },
+
+ item : function(index){
+ var me = this,
+ el = me.elements[index],
+ out = null;
+
+ if(el){
+ out = me.getElement(el);
+ }
+ return out;
+ },
+
+
+ addListener : function(eventName, handler, scope, opt){
+ var els = this.elements,
+ len = els.length,
+ i, e;
+
+ for(i = 0; i<len; i++) {
+ e = els[i];
+ if(e) {
+ Ext.EventManager.on(e, eventName, handler, scope || e, opt);
+ }
+ }
+ return this;
+ },
+
+ each : function(fn, scope){
+ var me = this,
+ els = me.elements,
+ len = els.length,
+ i, e;
+
+ for(i = 0; i<len; i++) {
+ e = els[i];
+ if(e){
+ e = this.getElement(e);
+ if(fn.call(scope || e, e, me, i) === false){
+ break;
+ }
+ }
+ }
+ return me;
+ },
+
+
+ fill : function(els){
+ var me = this;
+ me.elements = [];
+ me.add(els);
+ return me;
+ },
+
+
+ filter : function(selector){
+ var els = [],
+ me = this,
+ fn = Ext.isFunction(selector) ? selector
+ : function(el){
+ return el.is(selector);
+ };
+
+ me.each(function(el, self, i) {
+ if (fn(el, i) !== false) {
+ els[els.length] = me.transformElement(el);
+ }
+ });
+
+ me.elements = els;
+ return me;
+ },
+
+
+ indexOf : function(el){
+ return this.elements.indexOf(this.transformElement(el));
+ },
+
+
+ replaceElement : function(el, replacement, domReplace){
+ var index = !isNaN(el) ? el : this.indexOf(el),
+ d;
+ if(index > -1){
+ replacement = Ext.getDom(replacement);
+ if(domReplace){
+ d = this.elements[index];
+ d.parentNode.insertBefore(replacement, d);
+ Ext.removeNode(d);
+ }
+ this.elements.splice(index, 1, replacement);
+ }
+ return this;
+ },
+
+
+ clear : function(){
+ this.elements = [];
+ }
+};
+
+Ext.CompositeElementLite.prototype.on = Ext.CompositeElementLite.prototype.addListener;
+
+
+Ext.CompositeElementLite.importElementMethods = function() {
+ var fnName,
+ ElProto = Ext.Element.prototype,
+ CelProto = Ext.CompositeElementLite.prototype;
+
+ for (fnName in ElProto) {
+ if (typeof ElProto[fnName] == 'function'){
+ (function(fnName) {
+ CelProto[fnName] = CelProto[fnName] || function() {
+ return this.invoke(fnName, arguments);
+ };
+ }).call(CelProto, fnName);
+
+ }
+ }
+};
+
+Ext.CompositeElementLite.importElementMethods();
+
+if(Ext.DomQuery){
+ Ext.Element.selectorFunction = Ext.DomQuery.select;
+}
+
+
+Ext.Element.select = function(selector, root){
+ var els;
+ if(typeof selector == "string"){
+ els = Ext.Element.selectorFunction(selector, root);
+ }else if(selector.length !== undefined){
+ els = selector;
+ }else{
+ throw "Invalid selector";
+ }
+ return new Ext.CompositeElementLite(els);
+};
+
+Ext.select = Ext.Element.select;
+(function(){
+ var BEFOREREQUEST = "beforerequest",
+ REQUESTCOMPLETE = "requestcomplete",
+ REQUESTEXCEPTION = "requestexception",
+ UNDEFINED = undefined,
+ LOAD = 'load',
+ POST = 'POST',
+ GET = 'GET',
+ WINDOW = window;
+
+
+ Ext.data.Connection = function(config){
+ Ext.apply(this, config);
+ this.addEvents(
+
+ BEFOREREQUEST,
+
+ REQUESTCOMPLETE,
+
+ REQUESTEXCEPTION
+ );
+ Ext.data.Connection.superclass.constructor.call(this);
+ };
+
+ Ext.extend(Ext.data.Connection, Ext.util.Observable, {
+
+
+
+
+
+ timeout : 30000,
+
+ autoAbort:false,
+
+
+ disableCaching: true,
+
+
+ disableCachingParam: '_dc',
+
+
+ request : function(o){
+ var me = this;
+ if(me.fireEvent(BEFOREREQUEST, me, o)){
+ if (o.el) {
+ if(!Ext.isEmpty(o.indicatorText)){
+ me.indicatorText = '<div class="loading-indicator">'+o.indicatorText+"</div>";
+ }
+ if(me.indicatorText) {
+ Ext.getDom(o.el).innerHTML = me.indicatorText;
+ }
+ o.success = (Ext.isFunction(o.success) ? o.success : function(){}).createInterceptor(function(response) {
+ Ext.getDom(o.el).innerHTML = response.responseText;
+ });
+ }
+
+ var p = o.params,
+ url = o.url || me.url,
+ method,
+ cb = {success: me.handleResponse,
+ failure: me.handleFailure,
+ scope: me,
+ argument: {options: o},
+ timeout : Ext.num(o.timeout, me.timeout)
+ },
+ form,
+ serForm;
+
+
+ if (Ext.isFunction(p)) {
+ p = p.call(o.scope||WINDOW, o);
+ }
+
+ p = Ext.urlEncode(me.extraParams, Ext.isObject(p) ? Ext.urlEncode(p) : p);
+
+ if (Ext.isFunction(url)) {
+ url = url.call(o.scope || WINDOW, o);
+ }
+
+ if((form = Ext.getDom(o.form))){
+ url = url || form.action;
+ if(o.isUpload || (/multipart\/form-data/i.test(form.getAttribute("enctype")))) {
+ return me.doFormUpload.call(me, o, p, url);
+ }
+ serForm = Ext.lib.Ajax.serializeForm(form);
+ p = p ? (p + '&' + serForm) : serForm;
+ }
+
+ method = o.method || me.method || ((p || o.xmlData || o.jsonData) ? POST : GET);
+
+ if(method === GET && (me.disableCaching && o.disableCaching !== false) || o.disableCaching === true){
+ var dcp = o.disableCachingParam || me.disableCachingParam;
+ url = Ext.urlAppend(url, dcp + '=' + (new Date().getTime()));
+ }
+
+ o.headers = Ext.applyIf(o.headers || {}, me.defaultHeaders || {});
+
+ if(o.autoAbort === true || me.autoAbort) {
+ me.abort();
+ }
+
+ if((method == GET || o.xmlData || o.jsonData) && p){
+ url = Ext.urlAppend(url, p);
+ p = '';
+ }
+ return (me.transId = Ext.lib.Ajax.request(method, url, cb, p, o));
+ }else{
+ return o.callback ? o.callback.apply(o.scope, [o,UNDEFINED,UNDEFINED]) : null;
+ }
+ },
+
+
+ isLoading : function(transId){
+ return transId ? Ext.lib.Ajax.isCallInProgress(transId) : !! this.transId;
+ },
+
+
+ abort : function(transId){
+ if(transId || this.isLoading()){
+ Ext.lib.Ajax.abort(transId || this.transId);
+ }
+ },
+
+
+ handleResponse : function(response){
+ this.transId = false;
+ var options = response.argument.options;
+ response.argument = options ? options.argument : null;
+ this.fireEvent(REQUESTCOMPLETE, this, response, options);
+ if(options.success){
+ options.success.call(options.scope, response, options);
+ }
+ if(options.callback){
+ options.callback.call(options.scope, options, true, response);
+ }
+ },
+
+
+ handleFailure : function(response, e){
+ this.transId = false;
+ var options = response.argument.options;
+ response.argument = options ? options.argument : null;
+ this.fireEvent(REQUESTEXCEPTION, this, response, options, e);
+ if(options.failure){
+ options.failure.call(options.scope, response, options);
+ }
+ if(options.callback){
+ options.callback.call(options.scope, options, false, response);
+ }
+ },
+
+
+ doFormUpload : function(o, ps, url){
+ var id = Ext.id(),
+ doc = document,
+ frame = doc.createElement('iframe'),
+ form = Ext.getDom(o.form),
+ hiddens = [],
+ hd,
+ encoding = 'multipart/form-data',
+ buf = {
+ target: form.target,
+ method: form.method,
+ encoding: form.encoding,
+ enctype: form.enctype,
+ action: form.action
+ };
+
+
+ Ext.fly(frame).set({
+ id: id,
+ name: id,
+ cls: 'x-hidden',
+ src: Ext.SSL_SECURE_URL
+ });
+
+ doc.body.appendChild(frame);
+
+
+ if(Ext.isIE){
+ document.frames[id].name = id;
+ }
+
+
+ Ext.fly(form).set({
+ target: id,
+ method: POST,
+ enctype: encoding,
+ encoding: encoding,
+ action: url || buf.action
+ });
+
+
+ Ext.iterate(Ext.urlDecode(ps, false), function(k, v){
+ hd = doc.createElement('input');
+ Ext.fly(hd).set({
+ type: 'hidden',
+ value: v,
+ name: k
+ });
+ form.appendChild(hd);
+ hiddens.push(hd);
+ });
+
+ function cb(){
+ var me = this,
+
+ r = {responseText : '',
+ responseXML : null,
+ argument : o.argument},
+ doc,
+ firstChild;
+
+ try{
+ doc = frame.contentWindow.document || frame.contentDocument || WINDOW.frames[id].document;
+ if(doc){
+ if(doc.body){
+ if(/textarea/i.test((firstChild = doc.body.firstChild || {}).tagName)){
+ r.responseText = firstChild.value;
+ }else{
+ r.responseText = doc.body.innerHTML;
+ }
+ }
+
+ r.responseXML = doc.XMLDocument || doc;
+ }
+ }
+ catch(e) {}
+
+ Ext.EventManager.removeListener(frame, LOAD, cb, me);
+
+ me.fireEvent(REQUESTCOMPLETE, me, r, o);
+
+ function runCallback(fn, scope, args){
+ if(Ext.isFunction(fn)){
+ fn.apply(scope, args);
+ }
+ }
+
+ runCallback(o.success, o.scope, [r, o]);
+ runCallback(o.callback, o.scope, [o, true, r]);
+
+ if(!me.debugUploads){
+ setTimeout(function(){Ext.removeNode(frame);}, 100);
+ }
+ }
+
+ Ext.EventManager.on(frame, LOAD, cb, this);
+ form.submit();
+
+ Ext.fly(form).set(buf);
+ Ext.each(hiddens, function(h) {
+ Ext.removeNode(h);
+ });
+ }
+ });
+})();
+
+
+Ext.Ajax = new Ext.data.Connection({
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ autoAbort : false,
+
+
+ serializeForm : function(form){
+ return Ext.lib.Ajax.serializeForm(form);
+ }
+});
+
+Ext.util.JSON = new (function(){
+ var useHasOwn = !!{}.hasOwnProperty,
+ isNative = function() {
+ var useNative = null;
+
+ return function() {
+ if (useNative === null) {
+ useNative = Ext.USE_NATIVE_JSON && window.JSON && JSON.toString() == '[object JSON]';
+ }
+
+ return useNative;
+ };
+ }(),
+ pad = function(n) {
+ return n < 10 ? "0" + n : n;
+ },
+ doDecode = function(json){
+ return json ? eval("(" + json + ")") : "";
+ },
+ doEncode = function(o){
+ if(!Ext.isDefined(o) || o === null){
+ return "null";
+ }else if(Ext.isArray(o)){
+ return encodeArray(o);
+ }else if(Ext.isDate(o)){
+ return Ext.util.JSON.encodeDate(o);
+ }else if(Ext.isString(o)){
+ return encodeString(o);
+ }else if(typeof o == "number"){
+
+ return isFinite(o) ? String(o) : "null";
+ }else if(Ext.isBoolean(o)){
+ return String(o);
+ }else {
+ var a = ["{"], b, i, v;
+ for (i in o) {
+
+ if(!o.getElementsByTagName){
+ if(!useHasOwn || o.hasOwnProperty(i)) {
+ v = o[i];
+ switch (typeof v) {
+ case "undefined":
+ case "function":
+ case "unknown":
+ break;
+ default:
+ if(b){
+ a.push(',');
+ }
+ a.push(doEncode(i), ":",
+ v === null ? "null" : doEncode(v));
+ b = true;
+ }
+ }
+ }
+ }
+ a.push("}");
+ return a.join("");
+ }
+ },
+ m = {
+ "\b": '\\b',
+ "\t": '\\t',
+ "\n": '\\n',
+ "\f": '\\f',
+ "\r": '\\r',
+ '"' : '\\"',
+ "\\": '\\\\'
+ },
+ encodeString = function(s){
+ if (/["\\\x00-\x1f]/.test(s)) {
+ return '"' + s.replace(/([\x00-\x1f\\"])/g, function(a, b) {
+ var c = m[b];
+ if(c){
+ return c;
+ }
+ c = b.charCodeAt();
+ return "\\u00" +
+ Math.floor(c / 16).toString(16) +
+ (c % 16).toString(16);
+ }) + '"';
+ }
+ return '"' + s + '"';
+ },
+ encodeArray = function(o){
+ var a = ["["], b, i, l = o.length, v;
+ for (i = 0; i < l; i += 1) {
+ v = o[i];
+ switch (typeof v) {
+ case "undefined":
+ case "function":
+ case "unknown":
+ break;
+ default:
+ if (b) {
+ a.push(',');
+ }
+ a.push(v === null ? "null" : Ext.util.JSON.encode(v));
+ b = true;
+ }
+ }
+ a.push("]");
+ return a.join("");
+ };
+
+
+ this.encodeDate = function(o){
+ return '"' + o.getFullYear() + "-" +
+ pad(o.getMonth() + 1) + "-" +
+ pad(o.getDate()) + "T" +
+ pad(o.getHours()) + ":" +
+ pad(o.getMinutes()) + ":" +
+ pad(o.getSeconds()) + '"';
+ };
+
+
+ this.encode = function() {
+ var ec;
+ return function(o) {
+ if (!ec) {
+
+ ec = isNative() ? JSON.stringify : doEncode;
+ }
+ return ec(o);
+ };
+ }();
+
+
+
+ this.decode = function() {
+ var dc;
+ return function(json) {
+ if (!dc) {
+
+ dc = isNative() ? JSON.parse : doDecode;
+ }
+ return dc(json);
+ };
+ }();
+
+})();
+
+Ext.encode = Ext.util.JSON.encode;
+
+Ext.decode = Ext.util.JSON.decode;
+
+Ext.EventManager = function(){
+ var docReadyEvent,
+ docReadyProcId,
+ docReadyState = false,
+ DETECT_NATIVE = Ext.isGecko || Ext.isWebKit || Ext.isSafari || Ext.isIE10p,
+ E = Ext.lib.Event,
+ D = Ext.lib.Dom,
+ DOC = document,
+ WINDOW = window,
+ DOMCONTENTLOADED = "DOMContentLoaded",
+ COMPLETE = 'complete',
+ propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
+
+ specialElCache = [];
+
+ function getId(el){
+ var id = false,
+ i = 0,
+ len = specialElCache.length,
+ skip = false,
+ o;
+
+ if (el) {
+ if (el.getElementById || el.navigator) {
+
+ for(; i < len; ++i){
+ o = specialElCache[i];
+ if(o.el === el){
+ id = o.id;
+ break;
+ }
+ }
+ if(!id){
+
+ id = Ext.id(el);
+ specialElCache.push({
+ id: id,
+ el: el
+ });
+ skip = true;
+ }
+ }else{
+ id = Ext.id(el);
+ }
+ if(!Ext.elCache[id]){
+ Ext.Element.addToCache(new Ext.Element(el), id);
+ if(skip){
+ Ext.elCache[id].skipGC = true;
+ }
+ }
+ }
+ return id;
+ }
+
+
+ function addListener(el, ename, fn, task, wrap, scope){
+ el = Ext.getDom(el);
+ var id = getId(el),
+ es = Ext.elCache[id].events,
+ wfn;
+
+ wfn = E.on(el, ename, wrap);
+ es[ename] = es[ename] || [];
+
+
+ es[ename].push([fn, wrap, scope, wfn, task]);
+
+
+
+
+
+ if(el.addEventListener && ename == "mousewheel"){
+ var args = ["DOMMouseScroll", wrap, false];
+ el.addEventListener.apply(el, args);
+ Ext.EventManager.addListener(WINDOW, 'unload', function(){
+ el.removeEventListener.apply(el, args);
+ });
+ }
+
+
+ if(el == DOC && ename == "mousedown"){
+ Ext.EventManager.stoppedMouseDownEvent.addListener(wrap);
+ }
+ }
+
+ function doScrollChk(){
+
+ if(window != top){
+ return false;
+ }
+
+ try{
+ DOC.documentElement.doScroll('left');
+ }catch(e){
+ return false;
+ }
+
+ fireDocReady();
+ return true;
+ }
+
+ function checkReadyState(e){
+
+ if(Ext.isIE9m && doScrollChk()){
+ return true;
+ }
+ if(DOC.readyState == COMPLETE){
+ fireDocReady();
+ return true;
+ }
+ docReadyState || (docReadyProcId = setTimeout(arguments.callee, 2));
+ return false;
+ }
+
+ var styles;
+ function checkStyleSheets(e){
+ styles || (styles = Ext.query('style, link[rel=stylesheet]'));
+ if(styles.length == DOC.styleSheets.length){
+ fireDocReady();
+ return true;
+ }
+ docReadyState || (docReadyProcId = setTimeout(arguments.callee, 2));
+ return false;
+ }
+
+ function OperaDOMContentLoaded(e){
+ DOC.removeEventListener(DOMCONTENTLOADED, arguments.callee, false);
+ checkStyleSheets();
+ }
+
+ function fireDocReady(e){
+ if(!docReadyState){
+ docReadyState = true;
+
+ if(docReadyProcId){
+ clearTimeout(docReadyProcId);
+ }
+ if(DETECT_NATIVE) {
+ DOC.removeEventListener(DOMCONTENTLOADED, fireDocReady, false);
+ }
+ if(Ext.isIE9m && checkReadyState.bindIE){
+ DOC.detachEvent('onreadystatechange', checkReadyState);
+ }
+ E.un(WINDOW, "load", arguments.callee);
+ }
+ if(docReadyEvent && !Ext.isReady){
+ Ext.isReady = true;
+ docReadyEvent.fire();
+ docReadyEvent.listeners = [];
+ }
+
+ }
+
+ function initDocReady(){
+ docReadyEvent || (docReadyEvent = new Ext.util.Event());
+ if (DETECT_NATIVE) {
+ DOC.addEventListener(DOMCONTENTLOADED, fireDocReady, false);
+ }
+
+ if (Ext.isIE9m){
+
+
+ if(!checkReadyState()){
+ checkReadyState.bindIE = true;
+ DOC.attachEvent('onreadystatechange', checkReadyState);
+ }
+
+ }else if(Ext.isOpera ){
+
+
+
+ (DOC.readyState == COMPLETE && checkStyleSheets()) ||
+ DOC.addEventListener(DOMCONTENTLOADED, OperaDOMContentLoaded, false);
+
+ }else if (Ext.isWebKit){
+
+ checkReadyState();
+ }
+
+ E.on(WINDOW, "load", fireDocReady);
+ }
+
+ function createTargeted(h, o){
+ return function(){
+ var args = Ext.toArray(arguments);
+ if(o.target == Ext.EventObject.setEvent(args[0]).target){
+ h.apply(this, args);
+ }
+ };
+ }
+
+ function createBuffered(h, o, task){
+ return function(e){
+
+ task.delay(o.buffer, h, null, [new Ext.EventObjectImpl(e)]);
+ };
+ }
+
+ function createSingle(h, el, ename, fn, scope){
+ return function(e){
+ Ext.EventManager.removeListener(el, ename, fn, scope);
+ h(e);
+ };
+ }
+
+ function createDelayed(h, o, fn){
+ return function(e){
+ var task = new Ext.util.DelayedTask(h);
+ if(!fn.tasks) {
+ fn.tasks = [];
+ }
+ fn.tasks.push(task);
+ task.delay(o.delay || 10, h, null, [new Ext.EventObjectImpl(e)]);
+ };
+ }
+
+ function listen(element, ename, opt, fn, scope){
+ var o = (!opt || typeof opt == "boolean") ? {} : opt,
+ el = Ext.getDom(element), task;
+
+ fn = fn || o.fn;
+ scope = scope || o.scope;
+
+ if(!el){
+ throw "Error listening for \"" + ename + '\". Element "' + element + '" doesn\'t exist.';
+ }
+ function h(e){
+
+ if(!Ext){
+ return;
+ }
+ e = Ext.EventObject.setEvent(e);
+ var t;
+ if (o.delegate) {
+ if(!(t = e.getTarget(o.delegate, el))){
+ return;
+ }
+ } else {
+ t = e.target;
+ }
+ if (o.stopEvent) {
+ e.stopEvent();
+ }
+ if (o.preventDefault) {
+ e.preventDefault();
+ }
+ if (o.stopPropagation) {
+ e.stopPropagation();
+ }
+ if (o.normalized === false) {
+ e = e.browserEvent;
+ }
+
+ fn.call(scope || el, e, t, o);
+ }
+ if(o.target){
+ h = createTargeted(h, o);
+ }
+ if(o.delay){
+ h = createDelayed(h, o, fn);
+ }
+ if(o.single){
+ h = createSingle(h, el, ename, fn, scope);
+ }
+ if(o.buffer){
+ task = new Ext.util.DelayedTask(h);
+ h = createBuffered(h, o, task);
+ }
+
+ addListener(el, ename, fn, task, h, scope);
+ return h;
+ }
+
+ var pub = {
+
+ addListener : function(element, eventName, fn, scope, options){
+ if(typeof eventName == 'object'){
+ var o = eventName, e, val;
+ for(e in o){
+ val = o[e];
+ if(!propRe.test(e)){
+ if(Ext.isFunction(val)){
+
+ listen(element, e, o, val, o.scope);
+ }else{
+
+ listen(element, e, val);
+ }
+ }
+ }
+ } else {
+ listen(element, eventName, options, fn, scope);
+ }
+ },
+
+
+ removeListener : function(el, eventName, fn, scope){
+ el = Ext.getDom(el);
+ var id = getId(el),
+ f = el && (Ext.elCache[id].events)[eventName] || [],
+ wrap, i, l, k, len, fnc;
+
+ for (i = 0, len = f.length; i < len; i++) {
+
+
+ if (Ext.isArray(fnc = f[i]) && fnc[0] == fn && (!scope || fnc[2] == scope)) {
+ if(fnc[4]) {
+ fnc[4].cancel();
+ }
+ k = fn.tasks && fn.tasks.length;
+ if(k) {
+ while(k--) {
+ fn.tasks[k].cancel();
+ }
+ delete fn.tasks;
+ }
+ wrap = fnc[1];
+ E.un(el, eventName, E.extAdapter ? fnc[3] : wrap);
+
+
+ if(wrap && el.addEventListener && eventName == "mousewheel"){
+ el.removeEventListener("DOMMouseScroll", wrap, false);
+ }
+
+
+ if(wrap && el == DOC && eventName == "mousedown"){
+ Ext.EventManager.stoppedMouseDownEvent.removeListener(wrap);
+ }
+
+ f.splice(i, 1);
+ if (f.length === 0) {
+ delete Ext.elCache[id].events[eventName];
+ }
+ for (k in Ext.elCache[id].events) {
+ return false;
+ }
+ Ext.elCache[id].events = {};
+ return false;
+ }
+ }
+ },
+
+
+ removeAll : function(el){
+ el = Ext.getDom(el);
+ var id = getId(el),
+ ec = Ext.elCache[id] || {},
+ es = ec.events || {},
+ f, i, len, ename, fn, k, wrap;
+
+ for(ename in es){
+ if(es.hasOwnProperty(ename)){
+ f = es[ename];
+
+ for (i = 0, len = f.length; i < len; i++) {
+ fn = f[i];
+ if(fn[4]) {
+ fn[4].cancel();
+ }
+ if(fn[0].tasks && (k = fn[0].tasks.length)) {
+ while(k--) {
+ fn[0].tasks[k].cancel();
+ }
+ delete fn.tasks;
+ }
+ wrap = fn[1];
+ E.un(el, ename, E.extAdapter ? fn[3] : wrap);
+
+
+ if(el.addEventListener && wrap && ename == "mousewheel"){
+ el.removeEventListener("DOMMouseScroll", wrap, false);
+ }
+
+
+ if(wrap && el == DOC && ename == "mousedown"){
+ Ext.EventManager.stoppedMouseDownEvent.removeListener(wrap);
+ }
+ }
+ }
+ }
+ if (Ext.elCache[id]) {
+ Ext.elCache[id].events = {};
+ }
+ },
+
+ getListeners : function(el, eventName) {
+ el = Ext.getDom(el);
+ var id = getId(el),
+ ec = Ext.elCache[id] || {},
+ es = ec.events || {},
+ results = [];
+ if (es && es[eventName]) {
+ return es[eventName];
+ } else {
+ return null;
+ }
+ },
+
+ removeFromSpecialCache: function(o) {
+ var i = 0,
+ len = specialElCache.length;
+
+ for (; i < len; ++i) {
+ if (specialElCache[i].el == o) {
+ specialElCache.splice(i, 1);
+ }
+ }
+ },
+
+ purgeElement : function(el, recurse, eventName) {
+ el = Ext.getDom(el);
+ var id = getId(el),
+ ec = Ext.elCache[id] || {},
+ es = ec.events || {},
+ i, f, len;
+ if (eventName) {
+ if (es && es.hasOwnProperty(eventName)) {
+ f = es[eventName];
+ for (i = 0, len = f.length; i < len; i++) {
+ Ext.EventManager.removeListener(el, eventName, f[i][0]);
+ }
+ }
+ } else {
+ Ext.EventManager.removeAll(el);
+ }
+ if (recurse && el && el.childNodes) {
+ for (i = 0, len = el.childNodes.length; i < len; i++) {
+ Ext.EventManager.purgeElement(el.childNodes[i], recurse, eventName);
+ }
+ }
+ },
+
+ _unload : function() {
+ var el;
+ for (el in Ext.elCache) {
+ Ext.EventManager.removeAll(el);
+ }
+ delete Ext.elCache;
+ delete Ext.Element._flyweights;
+
+
+ var c,
+ conn,
+ tid,
+ ajax = Ext.lib.Ajax;
+ (typeof ajax.conn == 'object') ? conn = ajax.conn : conn = {};
+ for (tid in conn) {
+ c = conn[tid];
+ if (c) {
+ ajax.abort({conn: c, tId: tid});
+ }
+ }
+ },
+
+ onDocumentReady : function(fn, scope, options){
+ if (Ext.isReady) {
+ docReadyEvent || (docReadyEvent = new Ext.util.Event());
+ docReadyEvent.addListener(fn, scope, options);
+ docReadyEvent.fire();
+ docReadyEvent.listeners = [];
+ } else {
+ if (!docReadyEvent) {
+ initDocReady();
+ }
+ options = options || {};
+ options.delay = options.delay || 1;
+ docReadyEvent.addListener(fn, scope, options);
+ }
+ },
+
+
+ fireDocReady : fireDocReady
+ };
+
+ pub.on = pub.addListener;
+
+ pub.un = pub.removeListener;
+
+ pub.stoppedMouseDownEvent = new Ext.util.Event();
+ return pub;
+}();
+
+Ext.onReady = Ext.EventManager.onDocumentReady;
+
+
+
+(function(){
+ var initExtCss = function() {
+
+ var bd = document.body || document.getElementsByTagName('body')[0];
+ if (!bd) {
+ return false;
+ }
+
+ var cls = [];
+
+ if (Ext.isIE) {
+
+ if (!Ext.isIE10p) {
+ cls.push('ext-ie');
+ }
+ if (Ext.isIE6) {
+ cls.push('ext-ie6');
+ } else if (Ext.isIE7) {
+ cls.push('ext-ie7', 'ext-ie7m');
+ } else if (Ext.isIE8) {
+ cls.push('ext-ie8', 'ext-ie8m');
+ } else if (Ext.isIE9) {
+ cls.push('ext-ie9', 'ext-ie9m');
+ } else if (Ext.isIE10) {
+ cls.push('ext-ie10');
+ }
+ }
+
+ if (Ext.isGecko) {
+ if (Ext.isGecko2) {
+ cls.push('ext-gecko2');
+ } else {
+ cls.push('ext-gecko3');
+ }
+ }
+
+ if (Ext.isOpera) {
+ cls.push('ext-opera');
+ }
+
+ if (Ext.isWebKit) {
+ cls.push('ext-webkit');
+ }
+
+ if (Ext.isSafari) {
+ cls.push("ext-safari " + (Ext.isSafari2 ? 'ext-safari2' : (Ext.isSafari3 ? 'ext-safari3' : 'ext-safari4')));
+ } else if(Ext.isChrome) {
+ cls.push("ext-chrome");
+ }
+
+ if (Ext.isMac) {
+ cls.push("ext-mac");
+ }
+ if (Ext.isLinux) {
+ cls.push("ext-linux");
+ }
+
+
+ if (Ext.isStrict || Ext.isBorderBox) {
+ var p = bd.parentNode;
+ if (p) {
+ if (!Ext.isStrict) {
+ Ext.fly(p, '_internal').addClass('x-quirks');
+ if (Ext.isIE9m && !Ext.isStrict) {
+ Ext.isIEQuirks = true;
+ }
+ }
+ Ext.fly(p, '_internal').addClass(((Ext.isStrict && Ext.isIE ) || (!Ext.enableForcedBoxModel && !Ext.isIE)) ? ' ext-strict' : ' ext-border-box');
+ }
+ }
+
+
+ if (Ext.enableForcedBoxModel && !Ext.isIE) {
+ Ext.isForcedBorderBox = true;
+ cls.push("ext-forced-border-box");
+ }
+
+ Ext.fly(bd, '_internal').addClass(cls);
+ return true;
+ };
+
+ if (!initExtCss()) {
+ Ext.onReady(initExtCss);
+ }
+})();
+
+
+(function(){
+
+ var supports = Ext.apply(Ext.supports, {
+
+ correctRightMargin: true,
+
+
+ correctTransparentColor: true,
+
+
+ cssFloat: true
+ });
+
+ var supportTests = function(){
+ var div = document.createElement('div'),
+ doc = document,
+ view,
+ last;
+
+ div.innerHTML = '<div style="height:30px;width:50px;"><div style="height:20px;width:20px;"></div></div><div style="float:left;background-color:transparent;">';
+ doc.body.appendChild(div);
+ last = div.lastChild;
+
+ if((view = doc.defaultView)){
+ if(view.getComputedStyle(div.firstChild.firstChild, null).marginRight != '0px'){
+ supports.correctRightMargin = false;
+ }
+ if(view.getComputedStyle(last, null).backgroundColor != 'transparent'){
+ supports.correctTransparentColor = false;
+ }
+ }
+ supports.cssFloat = !!last.style.cssFloat;
+ doc.body.removeChild(div);
+ };
+
+ if (Ext.isReady) {
+ supportTests();
+ } else {
+ Ext.onReady(supportTests);
+ }
+})();
+
+
+
+Ext.EventObject = function(){
+ var E = Ext.lib.Event,
+ clickRe = /(dbl)?click/,
+
+ safariKeys = {
+ 3 : 13,
+ 63234 : 37,
+ 63235 : 39,
+ 63232 : 38,
+ 63233 : 40,
+ 63276 : 33,
+ 63277 : 34,
+ 63272 : 46,
+ 63273 : 36,
+ 63275 : 35
+ },
+
+ btnMap = Ext.isIE ? {1:0,4:1,2:2} : {0:0,1:1,2:2};
+
+ Ext.EventObjectImpl = function(e){
+ if(e){
+ this.setEvent(e.browserEvent || e);
+ }
+ };
+
+ Ext.EventObjectImpl.prototype = {
+
+ setEvent : function(e){
+ var me = this;
+ if(e == me || (e && e.browserEvent)){
+ return e;
+ }
+ me.browserEvent = e;
+ if(e){
+
+ me.button = e.button ? btnMap[e.button] : (e.which ? e.which - 1 : -1);
+ if(clickRe.test(e.type) && me.button == -1){
+ me.button = 0;
+ }
+ me.type = e.type;
+ me.shiftKey = e.shiftKey;
+
+ me.ctrlKey = e.ctrlKey || e.metaKey || false;
+ me.altKey = e.altKey;
+
+ me.keyCode = e.keyCode;
+ me.charCode = e.charCode;
+
+ me.target = E.getTarget(e);
+
+ me.xy = E.getXY(e);
+ }else{
+ me.button = -1;
+ me.shiftKey = false;
+ me.ctrlKey = false;
+ me.altKey = false;
+ me.keyCode = 0;
+ me.charCode = 0;
+ me.target = null;
+ me.xy = [0, 0];
+ }
+ return me;
+ },
+
+
+ stopEvent : function(){
+ var me = this;
+ if(me.browserEvent){
+ if(me.browserEvent.type == 'mousedown'){
+ Ext.EventManager.stoppedMouseDownEvent.fire(me);
+ }
+ E.stopEvent(me.browserEvent);
+ }
+ },
+
+
+ preventDefault : function(){
+ if(this.browserEvent){
+ E.preventDefault(this.browserEvent);
+ }
+ },
+
+
+ stopPropagation : function(){
+ var me = this;
+ if(me.browserEvent){
+ if(me.browserEvent.type == 'mousedown'){
+ Ext.EventManager.stoppedMouseDownEvent.fire(me);
+ }
+ E.stopPropagation(me.browserEvent);
+ }
+ },
+
+
+ getCharCode : function(){
+ return this.charCode || this.keyCode;
+ },
+
+
+ getKey : function(){
+ return this.normalizeKey(this.keyCode || this.charCode);
+ },
+
+
+ normalizeKey: function(k){
+ return Ext.isSafari ? (safariKeys[k] || k) : k;
+ },
+
+
+ getPageX : function(){
+ return this.xy[0];
+ },
+
+
+ getPageY : function(){
+ return this.xy[1];
+ },
+
+
+ getXY : function(){
+ return this.xy;
+ },
+
+
+ getTarget : function(selector, maxDepth, returnEl){
+ return selector ? Ext.fly(this.target).findParent(selector, maxDepth, returnEl) : (returnEl ? Ext.get(this.target) : this.target);
+ },
+
+
+ getRelatedTarget : function(){
+ return this.browserEvent ? E.getRelatedTarget(this.browserEvent) : null;
+ },
+
+
+ getWheelDelta : function(){
+ var e = this.browserEvent;
+ var delta = 0;
+ if(e.wheelDelta){
+ delta = e.wheelDelta/120;
+ }else if(e.detail){
+ delta = -e.detail/3;
+ }
+ return delta;
+ },
+
+
+ within : function(el, related, allowEl){
+ if(el){
+ var t = this[related ? "getRelatedTarget" : "getTarget"]();
+ return t && ((allowEl ? (t == Ext.getDom(el)) : false) || Ext.fly(el).contains(t));
+ }
+ return false;
+ }
+ };
+
+ return new Ext.EventObjectImpl();
+}();
+Ext.Loader = Ext.apply({}, {
+
+ load: function(fileList, callback, scope, preserveOrder) {
+ var scope = scope || this,
+ head = document.getElementsByTagName("head")[0],
+ fragment = document.createDocumentFragment(),
+ numFiles = fileList.length,
+ loadedFiles = 0,
+ me = this;
+
+
+ var loadFileIndex = function(index) {
+ head.appendChild(
+ me.buildScriptTag(fileList[index], onFileLoaded)
+ );
+ };
+
+
+ var onFileLoaded = function() {
+ loadedFiles ++;
+
+
+ if (numFiles == loadedFiles && typeof callback == 'function') {
+ callback.call(scope);
+ } else {
+ if (preserveOrder === true) {
+ loadFileIndex(loadedFiles);
+ }
+ }
+ };
+
+ if (preserveOrder === true) {
+ loadFileIndex.call(this, 0);
+ } else {
+
+ Ext.each(fileList, function(file, index) {
+ fragment.appendChild(
+ this.buildScriptTag(file, onFileLoaded)
+ );
+ }, this);
+
+ head.appendChild(fragment);
+ }
+ },
+
+
+ buildScriptTag: function(filename, callback) {
+ var script = document.createElement('script');
+ script.type = "text/javascript";
+ script.src = filename;
+
+
+ if (script.readyState) {
+ script.onreadystatechange = function() {
+ if (script.readyState == "loaded" || script.readyState == "complete") {
+ script.onreadystatechange = null;
+ callback();
+ }
+ };
+ } else {
+ script.onload = callback;
+ }
+
+ return script;
+ }
+});
+
+
+Ext.ns("Ext.grid", "Ext.list", "Ext.dd", "Ext.tree", "Ext.form", "Ext.menu",
+ "Ext.state", "Ext.layout.boxOverflow", "Ext.app", "Ext.ux", "Ext.chart", "Ext.direct", "Ext.slider");
+
+
+Ext.apply(Ext, function(){
+ var E = Ext,
+ idSeed = 0,
+ scrollWidth = null;
+
+ return {
+
+ emptyFn : function(){},
+
+
+ BLANK_IMAGE_URL : Ext.isIE6 || Ext.isIE7 || Ext.isAir ?
+ 'http:/' + '/www.extjs.com/s.gif' :
+ 'data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==',
+
+ extendX : function(supr, fn){
+ return Ext.extend(supr, fn(supr.prototype));
+ },
+
+
+ getDoc : function(){
+ return Ext.get(document);
+ },
+
+
+ num : function(v, defaultValue){
+ v = Number(Ext.isEmpty(v) || Ext.isArray(v) || typeof v == 'boolean' || (typeof v == 'string' && v.trim().length == 0) ? NaN : v);
+ return isNaN(v) ? defaultValue : v;
+ },
+
+
+ value : function(v, defaultValue, allowBlank){
+ return Ext.isEmpty(v, allowBlank) ? defaultValue : v;
+ },
+
+
+ escapeRe : function(s) {
+ return s.replace(/([-.*+?^${}()|[\]\/\\])/g, "\\$1");
+ },
+
+ sequence : function(o, name, fn, scope){
+ o[name] = o[name].createSequence(fn, scope);
+ },
+
+
+ addBehaviors : function(o){
+ if(!Ext.isReady){
+ Ext.onReady(function(){
+ Ext.addBehaviors(o);
+ });
+ } else {
+ var cache = {},
+ parts,
+ b,
+ s;
+ for (b in o) {
+ if ((parts = b.split('@'))[1]) {
+ s = parts[0];
+ if(!cache[s]){
+ cache[s] = Ext.select(s);
+ }
+ cache[s].on(parts[1], o[b]);
+ }
+ }
+ cache = null;
+ }
+ },
+
+
+ getScrollBarWidth: function(force){
+ if(!Ext.isReady){
+ return 0;
+ }
+
+ if(force === true || scrollWidth === null){
+
+ var div = Ext.getBody().createChild('<div class="x-hide-offsets" style="width:100px;height:50px;overflow:hidden;"><div style="height:200px;"></div></div>'),
+ child = div.child('div', true);
+ var w1 = child.offsetWidth;
+ div.setStyle('overflow', (Ext.isWebKit || Ext.isGecko) ? 'auto' : 'scroll');
+ var w2 = child.offsetWidth;
+ div.remove();
+
+ scrollWidth = w1 - w2 + 2;
+ }
+ return scrollWidth;
+ },
+
+
+
+ combine : function(){
+ var as = arguments, l = as.length, r = [];
+ for(var i = 0; i < l; i++){
+ var a = as[i];
+ if(Ext.isArray(a)){
+ r = r.concat(a);
+ }else if(a.length !== undefined && !a.substr){
+ r = r.concat(Array.prototype.slice.call(a, 0));
+ }else{
+ r.push(a);
+ }
+ }
+ return r;
+ },
+
+
+ copyTo : function(dest, source, names){
+ if(typeof names == 'string'){
+ names = names.split(/[,;\s]/);
+ }
+ Ext.each(names, function(name){
+ if(source.hasOwnProperty(name)){
+ dest[name] = source[name];
+ }
+ }, this);
+ return dest;
+ },
+
+
+ destroy : function(){
+ Ext.each(arguments, function(arg){
+ if(arg){
+ if(Ext.isArray(arg)){
+ this.destroy.apply(this, arg);
+ }else if(typeof arg.destroy == 'function'){
+ arg.destroy();
+ }else if(arg.dom){
+ arg.remove();
+ }
+ }
+ }, this);
+ },
+
+
+ destroyMembers : function(o, arg1, arg2, etc){
+ for(var i = 1, a = arguments, len = a.length; i < len; i++) {
+ Ext.destroy(o[a[i]]);
+ delete o[a[i]];
+ }
+ },
+
+
+ clean : function(arr){
+ var ret = [];
+ Ext.each(arr, function(v){
+ if(!!v){
+ ret.push(v);
+ }
+ });
+ return ret;
+ },
+
+
+ unique : function(arr){
+ var ret = [],
+ collect = {};
+
+ Ext.each(arr, function(v) {
+ if(!collect[v]){
+ ret.push(v);
+ }
+ collect[v] = true;
+ });
+ return ret;
+ },
+
+
+ flatten : function(arr){
+ var worker = [];
+ function rFlatten(a) {
+ Ext.each(a, function(v) {
+ if(Ext.isArray(v)){
+ rFlatten(v);
+ }else{
+ worker.push(v);
+ }
+ });
+ return worker;
+ }
+ return rFlatten(arr);
+ },
+
+
+ min : function(arr, comp){
+ var ret = arr[0];
+ comp = comp || function(a,b){ return a < b ? -1 : 1; };
+ Ext.each(arr, function(v) {
+ ret = comp(ret, v) == -1 ? ret : v;
+ });
+ return ret;
+ },
+
+
+ max : function(arr, comp){
+ var ret = arr[0];
+ comp = comp || function(a,b){ return a > b ? 1 : -1; };
+ Ext.each(arr, function(v) {
+ ret = comp(ret, v) == 1 ? ret : v;
+ });
+ return ret;
+ },
+
+
+ mean : function(arr){
+ return arr.length > 0 ? Ext.sum(arr) / arr.length : undefined;
+ },
+
+
+ sum : function(arr){
+ var ret = 0;
+ Ext.each(arr, function(v) {
+ ret += v;
+ });
+ return ret;
+ },
+
+
+ partition : function(arr, truth){
+ var ret = [[],[]];
+ Ext.each(arr, function(v, i, a) {
+ ret[ (truth && truth(v, i, a)) || (!truth && v) ? 0 : 1].push(v);
+ });
+ return ret;
+ },
+
+
+ invoke : function(arr, methodName){
+ var ret = [],
+ args = Array.prototype.slice.call(arguments, 2);
+ Ext.each(arr, function(v,i) {
+ if (v && typeof v[methodName] == 'function') {
+ ret.push(v[methodName].apply(v, args));
+ } else {
+ ret.push(undefined);
+ }
+ });
+ return ret;
+ },
+
+
+ pluck : function(arr, prop){
+ var ret = [];
+ Ext.each(arr, function(v) {
+ ret.push( v[prop] );
+ });
+ return ret;
+ },
+
+
+ zip : function(){
+ var parts = Ext.partition(arguments, function( val ){ return typeof val != 'function'; }),
+ arrs = parts[0],
+ fn = parts[1][0],
+ len = Ext.max(Ext.pluck(arrs, "length")),
+ ret = [];
+
+ for (var i = 0; i < len; i++) {
+ ret[i] = [];
+ if(fn){
+ ret[i] = fn.apply(fn, Ext.pluck(arrs, i));
+ }else{
+ for (var j = 0, aLen = arrs.length; j < aLen; j++){
+ ret[i].push( arrs[j][i] );
+ }
+ }
+ }
+ return ret;
+ },
+
+
+ getCmp : function(id){
+ return Ext.ComponentMgr.get(id);
+ },
+
+
+ useShims: E.isIE6 || (E.isMac && E.isGecko2),
+
+
+
+ type : function(o){
+ if(o === undefined || o === null){
+ return false;
+ }
+ if(o.htmlElement){
+ return 'element';
+ }
+ var t = typeof o;
+ if(t == 'object' && o.nodeName) {
+ switch(o.nodeType) {
+ case 1: return 'element';
+ case 3: return (/\S/).test(o.nodeValue) ? 'textnode' : 'whitespace';
+ }
+ }
+ if(t == 'object' || t == 'function') {
+ switch(o.constructor) {
+ case Array: return 'array';
+ case RegExp: return 'regexp';
+ case Date: return 'date';
+ }
+ if(typeof o.length == 'number' && typeof o.item == 'function') {
+ return 'nodelist';
+ }
+ }
+ return t;
+ },
+
+ intercept : function(o, name, fn, scope){
+ o[name] = o[name].createInterceptor(fn, scope);
+ },
+
+
+ callback : function(cb, scope, args, delay){
+ if(typeof cb == 'function'){
+ if(delay){
+ cb.defer(delay, scope, args || []);
+ }else{
+ cb.apply(scope, args || []);
+ }
+ }
+ }
+ };
+}());
+
+
+Ext.apply(Function.prototype, {
+
+ createSequence : function(fcn, scope){
+ var method = this;
+ return (typeof fcn != 'function') ?
+ this :
+ function(){
+ var retval = method.apply(this || window, arguments);
+ fcn.apply(scope || this || window, arguments);
+ return retval;
+ };
+ }
+});
+
+
+
+Ext.applyIf(String, {
+
+
+ escape : function(string) {
+ return string.replace(/('|\\)/g, "\\$1");
+ },
+
+
+ leftPad : function (val, size, ch) {
+ var result = String(val);
+ if(!ch) {
+ ch = " ";
+ }
+ while (result.length < size) {
+ result = ch + result;
+ }
+ return result;
+ }
+});
+
+
+String.prototype.toggle = function(value, other){
+ return this == value ? other : value;
+};
+
+
+String.prototype.trim = function(){
+ var re = /^\s+|\s+$/g;
+ return function(){ return this.replace(re, ""); };
+}();
+
+
+
+Date.prototype.getElapsed = function(date) {
+ return Math.abs((date || new Date()).getTime()-this.getTime());
+};
+
+
+
+Ext.applyIf(Number.prototype, {
+
+ constrain : function(min, max){
+ return Math.min(Math.max(this, min), max);
+ }
+});
+Ext.lib.Dom.getRegion = function(el) {
+ return Ext.lib.Region.getRegion(el);
+}; Ext.lib.Region = function(t, r, b, l) {
+ var me = this;
+ me.top = t;
+ me[1] = t;
+ me.right = r;
+ me.bottom = b;
+ me.left = l;
+ me[0] = l;
+ };
+
+ Ext.lib.Region.prototype = {
+ contains : function(region) {
+ var me = this;
+ return ( region.left >= me.left &&
+ region.right <= me.right &&
+ region.top >= me.top &&
+ region.bottom <= me.bottom );
+
+ },
+
+ getArea : function() {
+ var me = this;
+ return ( (me.bottom - me.top) * (me.right - me.left) );
+ },
+
+ intersect : function(region) {
+ var me = this,
+ t = Math.max(me.top, region.top),
+ r = Math.min(me.right, region.right),
+ b = Math.min(me.bottom, region.bottom),
+ l = Math.max(me.left, region.left);
+
+ if (b >= t && r >= l) {
+ return new Ext.lib.Region(t, r, b, l);
+ }
+ },
+
+ union : function(region) {
+ var me = this,
+ t = Math.min(me.top, region.top),
+ r = Math.max(me.right, region.right),
+ b = Math.max(me.bottom, region.bottom),
+ l = Math.min(me.left, region.left);
+
+ return new Ext.lib.Region(t, r, b, l);
+ },
+
+ constrainTo : function(r) {
+ var me = this;
+ me.top = me.top.constrain(r.top, r.bottom);
+ me.bottom = me.bottom.constrain(r.top, r.bottom);
+ me.left = me.left.constrain(r.left, r.right);
+ me.right = me.right.constrain(r.left, r.right);
+ return me;
+ },
+
+ adjust : function(t, l, b, r) {
+ var me = this;
+ me.top += t;
+ me.left += l;
+ me.right += r;
+ me.bottom += b;
+ return me;
+ }
+ };
+
+ Ext.lib.Region.getRegion = function(el) {
+ var p = Ext.lib.Dom.getXY(el),
+ t = p[1],
+ r = p[0] + el.offsetWidth,
+ b = p[1] + el.offsetHeight,
+ l = p[0];
+
+ return new Ext.lib.Region(t, r, b, l);
+ }; Ext.lib.Point = function(x, y) {
+ if (Ext.isArray(x)) {
+ y = x[1];
+ x = x[0];
+ }
+ var me = this;
+ me.x = me.right = me.left = me[0] = x;
+ me.y = me.top = me.bottom = me[1] = y;
+ };
+
+ Ext.lib.Point.prototype = new Ext.lib.Region();
+
+Ext.apply(Ext.DomHelper,
+function(){
+ var pub,
+ afterbegin = 'afterbegin',
+ afterend = 'afterend',
+ beforebegin = 'beforebegin',
+ beforeend = 'beforeend',
+ confRe = /tag|children|cn|html$/i;
+
+
+ function doInsert(el, o, returnElement, pos, sibling, append){
+ el = Ext.getDom(el);
+ var newNode;
+ if (pub.useDom) {
+ newNode = createDom(o, null);
+ if (append) {
+ el.appendChild(newNode);
+ } else {
+ (sibling == 'firstChild' ? el : el.parentNode).insertBefore(newNode, el[sibling] || el);
+ }
+ } else {
+ newNode = Ext.DomHelper.insertHtml(pos, el, Ext.DomHelper.createHtml(o));
+ }
+ return returnElement ? Ext.get(newNode, true) : newNode;
+ }
+
+
+
+ function createDom(o, parentNode){
+ var el,
+ doc = document,
+ useSet,
+ attr,
+ val,
+ cn;
+
+ if (Ext.isArray(o)) {
+ el = doc.createDocumentFragment();
+ for (var i = 0, l = o.length; i < l; i++) {
+ createDom(o[i], el);
+ }
+ } else if (typeof o == 'string') {
+ el = doc.createTextNode(o);
+ } else {
+ el = doc.createElement( o.tag || 'div' );
+ useSet = !!el.setAttribute;
+ for (var attr in o) {
+ if(!confRe.test(attr)){
+ val = o[attr];
+ if(attr == 'cls'){
+ el.className = val;
+ }else{
+ if(useSet){
+ el.setAttribute(attr, val);
+ }else{
+ el[attr] = val;
+ }
+ }
+ }
+ }
+ Ext.DomHelper.applyStyles(el, o.style);
+
+ if ((cn = o.children || o.cn)) {
+ createDom(cn, el);
+ } else if (o.html) {
+ el.innerHTML = o.html;
+ }
+ }
+ if(parentNode){
+ parentNode.appendChild(el);
+ }
+ return el;
+ }
+
+ pub = {
+
+ createTemplate : function(o){
+ var html = Ext.DomHelper.createHtml(o);
+ return new Ext.Template(html);
+ },
+
+
+ useDom : false,
+
+
+ insertBefore : function(el, o, returnElement){
+ return doInsert(el, o, returnElement, beforebegin);
+ },
+
+
+ insertAfter : function(el, o, returnElement){
+ return doInsert(el, o, returnElement, afterend, 'nextSibling');
+ },
+
+
+ insertFirst : function(el, o, returnElement){
+ return doInsert(el, o, returnElement, afterbegin, 'firstChild');
+ },
+
+
+ append: function(el, o, returnElement){
+ return doInsert(el, o, returnElement, beforeend, '', true);
+ },
+
+
+ createDom: createDom
+ };
+ return pub;
+}());
+
+Ext.apply(Ext.Template.prototype, {
+
+ disableFormats : false,
+
+
+
+ re : /\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,
+ argsRe : /^\s*['"](.*)["']\s*$/,
+ compileARe : /\\/g,
+ compileBRe : /(\r\n|\n)/g,
+ compileCRe : /'/g,
+
+ /**
+ * Returns an HTML fragment of this template with the specified values applied.
+ * @param {Object/Array} values The template values. Can be an array if your params are numeric (i.e. {0}) or an object (i.e. {foo: 'bar'})
+ * @return {String} The HTML fragment
+ * @hide repeat doc
+ */
+ applyTemplate : function(values){
+ var me = this,
+ useF = me.disableFormats !== true,
+ fm = Ext.util.Format,
+ tpl = me;
+
+ if(me.compiled){
+ return me.compiled(values);
+ }
+ function fn(m, name, format, args){
+ if (format && useF) {
+ if (format.substr(0, 5) == "this.") {
+ return tpl.call(format.substr(5), values[name], values);
+ } else {
+ if (args) {
+ // quoted values are required for strings in compiled templates,
+ // but for non compiled we need to strip them
+ // quoted reversed for jsmin
+ var re = me.argsRe;
+ args = args.split(',');
+ for(var i = 0, len = args.length; i < len; i++){
+ args[i] = args[i].replace(re, "$1");
+ }
+ args = [values[name]].concat(args);
+ } else {
+ args = [values[name]];
+ }
+ return fm[format].apply(fm, args);
+ }
+ } else {
+ return values[name] !== undefined ? values[name] : "";
+ }
+ }
+ return me.html.replace(me.re, fn);
+ },
+
+ /**
+ * Compiles the template into an internal function, eliminating the RegEx overhead.
+ * @return {Ext.Template} this
+ * @hide repeat doc
+ */
+ compile : function(){
+ var me = this,
+ fm = Ext.util.Format,
+ useF = me.disableFormats !== true,
+ sep = Ext.isGecko ? "+" : ",",
+ body;
+
+ function fn(m, name, format, args){
+ if(format && useF){
+ args = args ? ',' + args : "";
+ if(format.substr(0, 5) != "this."){
+ format = "fm." + format + '(';
+ }else{
+ format = 'this.call("'+ format.substr(5) + '", ';
+ args = ", values";
+ }
+ }else{
+ args= ''; format = "(values['" + name + "'] == undefined ? '' : ";
+ }
+ return "'"+ sep + format + "values['" + name + "']" + args + ")"+sep+"'";
+ }
+
+ // branched to use + in gecko and [].join() in others
+ if(Ext.isGecko){
+ body = "this.compiled = function(values){ return '" +
+ me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn) +
+ "';};";
+ }else{
+ body = ["this.compiled = function(values){ return ['"];
+ body.push(me.html.replace(me.compileARe, '\\\\').replace(me.compileBRe, '\\n').replace(me.compileCRe, "\\'").replace(me.re, fn));
+ body.push("'].join('');};");
+ body = body.join('');
+ }
+ eval(body);
+ return me;
+ },
+
+ // private function used to call members
+ call : function(fnName, value, allValues){
+ return this[fnName](value, allValues);
+ }
+});
+Ext.Template.prototype.apply = Ext.Template.prototype.applyTemplate;
+/**
+ * @class Ext.util.Functions
+ * @singleton
+ */
+Ext.util.Functions = {
+ /**
+ * Creates an interceptor function. The passed function is called before the original one. If it returns false,
+ * the original one is not called. The resulting function returns the results of the original function.
+ * The passed function is called with the parameters of the original function. Example usage:
+ * <pre><code>
+var sayHi = function(name){
+ alert('Hi, ' + name);
+}
+
+sayHi('Fred'); // alerts "Hi, Fred"
+
+// create a new function that validates input without
+// directly modifying the original function:
+var sayHiToFriend = Ext.createInterceptor(sayHi, function(name){
+ return name == 'Brian';
+});
+
+sayHiToFriend('Fred'); // no alert
+sayHiToFriend('Brian'); // alerts "Hi, Brian"
+ </code></pre>
+ * @param {Function} origFn The original function.
+ * @param {Function} newFn The function to call before the original
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the passed function is executed.
+ * <b>If omitted, defaults to the scope in which the original function is called or the browser window.</b>
+ * @return {Function} The new function
+ */
+ createInterceptor: function(origFn, newFn, scope) {
+ var method = origFn;
+ if (!Ext.isFunction(newFn)) {
+ return origFn;
+ }
+ else {
+ return function() {
+ var me = this,
+ args = arguments;
+ newFn.target = me;
+ newFn.method = origFn;
+ return (newFn.apply(scope || me || window, args) !== false) ?
+ origFn.apply(me || window, args) :
+ null;
+ };
+ }
+ },
+
+ /**
+ * Creates a delegate (callback) that sets the scope to obj.
+ * Call directly on any function. Example: <code>Ext.createDelegate(this.myFunction, this, [arg1, arg2])</code>
+ * Will create a function that is automatically scoped to obj so that the <tt>this</tt> variable inside the
+ * callback points to obj. Example usage:
+ * <pre><code>
+var sayHi = function(name){
+ // Note this use of "this.text" here. This function expects to
+ // execute within a scope that contains a text property. In this
+ // example, the "this" variable is pointing to the btn object that
+ // was passed in createDelegate below.
+ alert('Hi, ' + name + '. You clicked the "' + this.text + '" button.');
+}
+
+var btn = new Ext.Button({
+ text: 'Say Hi',
+ renderTo: Ext.getBody()
+});
+
+// This callback will execute in the scope of the
+// button instance. Clicking the button alerts
+// "Hi, Fred. You clicked the "Say Hi" button."
+btn.on('click', Ext.createDelegate(sayHi, btn, ['Fred']));
+ </code></pre>
+ * @param {Function} fn The function to delegate.
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
+ * <b>If omitted, defaults to the browser window.</b>
+ * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
+ * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
+ * if a number the args are inserted at the specified position
+ * @return {Function} The new function
+ */
+ createDelegate: function(fn, obj, args, appendArgs) {
+ if (!Ext.isFunction(fn)) {
+ return fn;
+ }
+ return function() {
+ var callArgs = args || arguments;
+ if (appendArgs === true) {
+ callArgs = Array.prototype.slice.call(arguments, 0);
+ callArgs = callArgs.concat(args);
+ }
+ else if (Ext.isNumber(appendArgs)) {
+ callArgs = Array.prototype.slice.call(arguments, 0);
+ // copy arguments first
+ var applyArgs = [appendArgs, 0].concat(args);
+ // create method call params
+ Array.prototype.splice.apply(callArgs, applyArgs);
+ // splice them in
+ }
+ return fn.apply(obj || window, callArgs);
+ };
+ },
+
+ /**
+ * Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage:
+ * <pre><code>
+var sayHi = function(name){
+ alert('Hi, ' + name);
+}
+
+// executes immediately:
+sayHi('Fred');
+
+// executes after 2 seconds:
+Ext.defer(sayHi, 2000, this, ['Fred']);
+
+// this syntax is sometimes useful for deferring
+// execution of an anonymous function:
+Ext.defer(function(){
+ alert('Anonymous');
+}, 100);
+ </code></pre>
+ * @param {Function} fn The function to defer.
+ * @param {Number} millis The number of milliseconds for the setTimeout call (if less than or equal to 0 the function is executed immediately)
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
+ * <b>If omitted, defaults to the browser window.</b>
+ * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
+ * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
+ * if a number the args are inserted at the specified position
+ * @return {Number} The timeout id that can be used with clearTimeout
+ */
+ defer: function(fn, millis, obj, args, appendArgs) {
+ fn = Ext.util.Functions.createDelegate(fn, obj, args, appendArgs);
+ if (millis > 0) {
+ return setTimeout(fn, millis);
+ }
+ fn();
+ return 0;
+ },
+
+
+ /**
+ * Create a combined function call sequence of the original function + the passed function.
+ * The resulting function returns the results of the original function.
+ * The passed fcn is called with the parameters of the original function. Example usage:
+ *
+
+var sayHi = function(name){
+ alert('Hi, ' + name);
+}
+
+sayHi('Fred'); // alerts "Hi, Fred"
+
+var sayGoodbye = Ext.createSequence(sayHi, function(name){
+ alert('Bye, ' + name);
+});
+
+sayGoodbye('Fred'); // both alerts show
+
+ * @param {Function} origFn The original function.
+ * @param {Function} newFn The function to sequence
+ * @param {Object} scope (optional) The scope (this reference) in which the passed function is executed.
+ * If omitted, defaults to the scope in which the original function is called or the browser window.
+ * @return {Function} The new function
+ */
+ createSequence: function(origFn, newFn, scope) {
+ if (!Ext.isFunction(newFn)) {
+ return origFn;
+ }
+ else {
+ return function() {
+ var retval = origFn.apply(this || window, arguments);
+ newFn.apply(scope || this || window, arguments);
+ return retval;
+ };
+ }
+ }
+};
+
+/**
+ * Shorthand for {@link Ext.util.Functions#defer}
+ * @param {Function} fn The function to defer.
+ * @param {Number} millis The number of milliseconds for the setTimeout call (if less than or equal to 0 the function is executed immediately)
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
+ * <b>If omitted, defaults to the browser window.</b>
+ * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
+ * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
+ * if a number the args are inserted at the specified position
+ * @return {Number} The timeout id that can be used with clearTimeout
+ * @member Ext
+ * @method defer
+ */
+
+Ext.defer = Ext.util.Functions.defer;
+
+/**
+ * Shorthand for {@link Ext.util.Functions#createInterceptor}
+ * @param {Function} origFn The original function.
+ * @param {Function} newFn The function to call before the original
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the passed function is executed.
+ * <b>If omitted, defaults to the scope in which the original function is called or the browser window.</b>
+ * @return {Function} The new function
+ * @member Ext
+ * @method createInterceptor
+ */
+
+Ext.createInterceptor = Ext.util.Functions.createInterceptor;
+
+/**
+ * Shorthand for {@link Ext.util.Functions#createSequence}
+ * @param {Function} origFn The original function.
+ * @param {Function} newFn The function to sequence
+ * @param {Object} scope (optional) The scope (this reference) in which the passed function is executed.
+ * If omitted, defaults to the scope in which the original function is called or the browser window.
+ * @return {Function} The new function
+ * @member Ext
+ * @method createSequence
+ */
+
+Ext.createSequence = Ext.util.Functions.createSequence;
+
+/**
+ * Shorthand for {@link Ext.util.Functions#createDelegate}
+ * @param {Function} fn The function to delegate.
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
+ * <b>If omitted, defaults to the browser window.</b>
+ * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
+ * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
+ * if a number the args are inserted at the specified position
+ * @return {Function} The new function
+ * @member Ext
+ * @method createDelegate
+ */
+Ext.createDelegate = Ext.util.Functions.createDelegate;
+/**
+ * @class Ext.util.Observable
+ */
+Ext.apply(Ext.util.Observable.prototype, function(){
+ // this is considered experimental (along with beforeMethod, afterMethod, removeMethodListener?)
+ // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
+ // private
+ function getMethodEvent(method){
+ var e = (this.methodEvents = this.methodEvents ||
+ {})[method], returnValue, v, cancel, obj = this;
+
+ if (!e) {
+ this.methodEvents[method] = e = {};
+ e.originalFn = this[method];
+ e.methodName = method;
+ e.before = [];
+ e.after = [];
+
+ var makeCall = function(fn, scope, args){
+ if((v = fn.apply(scope || obj, args)) !== undefined){
+ if (typeof v == 'object') {
+ if(v.returnValue !== undefined){
+ returnValue = v.returnValue;
+ }else{
+ returnValue = v;
+ }
+ cancel = !!v.cancel;
+ }
+ else
+ if (v === false) {
+ cancel = true;
+ }
+ else {
+ returnValue = v;
+ }
+ }
+ };
+
+ this[method] = function(){
+ var args = Array.prototype.slice.call(arguments, 0),
+ b;
+ returnValue = v = undefined;
+ cancel = false;
+
+ for(var i = 0, len = e.before.length; i < len; i++){
+ b = e.before[i];
+ makeCall(b.fn, b.scope, args);
+ if (cancel) {
+ return returnValue;
+ }
+ }
+
+ if((v = e.originalFn.apply(obj, args)) !== undefined){
+ returnValue = v;
+ }
+
+ for(var i = 0, len = e.after.length; i < len; i++){
+ b = e.after[i];
+ makeCall(b.fn, b.scope, args);
+ if (cancel) {
+ return returnValue;
+ }
+ }
+ return returnValue;
+ };
+ }
+ return e;
+ }
+
+ return {
+ // these are considered experimental
+ // allows for easier interceptor and sequences, including cancelling and overwriting the return value of the call
+ // adds an 'interceptor' called before the original method
+ beforeMethod : function(method, fn, scope){
+ getMethodEvent.call(this, method).before.push({
+ fn: fn,
+ scope: scope
+ });
+ },
+
+ // adds a 'sequence' called after the original method
+ afterMethod : function(method, fn, scope){
+ getMethodEvent.call(this, method).after.push({
+ fn: fn,
+ scope: scope
+ });
+ },
+
+ removeMethodListener: function(method, fn, scope){
+ var e = this.getMethodEvent(method);
+ for(var i = 0, len = e.before.length; i < len; i++){
+ if(e.before[i].fn == fn && e.before[i].scope == scope){
+ e.before.splice(i, 1);
+ return;
+ }
+ }
+ for(var i = 0, len = e.after.length; i < len; i++){
+ if(e.after[i].fn == fn && e.after[i].scope == scope){
+ e.after.splice(i, 1);
+ return;
+ }
+ }
+ },
+
+ /**
+ * Relays selected events from the specified Observable as if the events were fired by <tt><b>this</b></tt>.
+ * @param {Object} o The Observable whose events this object is to relay.
+ * @param {Array} events Array of event names to relay.
+ */
+ relayEvents : function(o, events){
+ var me = this;
+ function createHandler(ename){
+ return function(){
+ return me.fireEvent.apply(me, [ename].concat(Array.prototype.slice.call(arguments, 0)));
+ };
+ }
+ for(var i = 0, len = events.length; i < len; i++){
+ var ename = events[i];
+ me.events[ename] = me.events[ename] || true;
+ o.on(ename, createHandler(ename), me);
+ }
+ },
+
+ /**
+ * <p>Enables events fired by this Observable to bubble up an owner hierarchy by calling
+ * <code>this.getBubbleTarget()</code> if present. There is no implementation in the Observable base class.</p>
+ * <p>This is commonly used by Ext.Components to bubble events to owner Containers. See {@link Ext.Component.getBubbleTarget}. The default
+ * implementation in Ext.Component returns the Component's immediate owner. But if a known target is required, this can be overridden to
+ * access the required target more quickly.</p>
+ * <p>Example:</p><pre><code>
+Ext.override(Ext.form.Field, {
+
+ initComponent : Ext.form.Field.prototype.initComponent.createSequence(function() {
+ this.enableBubble('change');
+ }),
+
+
+ getBubbleTarget : function() {
+ if (!this.formPanel) {
+ this.formPanel = this.findParentByType('form');
+ }
+ return this.formPanel;
+ }
+});
+
+var myForm = new Ext.formPanel({
+ title: 'User Details',
+ items: [{
+ ...
+ }],
+ listeners: {
+ change: function() {
+
+ myForm.header.setStyle('color', 'red');
+ }
+ }
+});
+</code></pre>
+ * @param {String/Array} events The event name to bubble, or an Array of event names.
+ */
+ enableBubble : function(events){
+ var me = this;
+ if(!Ext.isEmpty(events)){
+ events = Ext.isArray(events) ? events : Array.prototype.slice.call(arguments, 0);
+ for(var i = 0, len = events.length; i < len; i++){
+ var ename = events[i];
+ ename = ename.toLowerCase();
+ var ce = me.events[ename] || true;
+ if (typeof ce == 'boolean') {
+ ce = new Ext.util.Event(me, ename);
+ me.events[ename] = ce;
+ }
+ ce.bubble = true;
+ }
+ }
+ }
+ };
+}());
+
+
+
+Ext.util.Observable.capture = function(o, fn, scope){
+ o.fireEvent = o.fireEvent.createInterceptor(fn, scope);
+};
+
+
+
+Ext.util.Observable.observeClass = function(c, listeners){
+ if(c){
+ if(!c.fireEvent){
+ Ext.apply(c, new Ext.util.Observable());
+ Ext.util.Observable.capture(c.prototype, c.fireEvent, c);
+ }
+ if(typeof listeners == 'object'){
+ c.on(listeners);
+ }
+ return c;
+ }
+};
+
+Ext.apply(Ext.EventManager, function(){
+ var resizeEvent,
+ resizeTask,
+ textEvent,
+ textSize,
+ D = Ext.lib.Dom,
+ propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,
+ unload = Ext.EventManager._unload,
+ curWidth = 0,
+ curHeight = 0,
+
+
+
+ useKeydown = Ext.isWebKit ?
+ Ext.num(navigator.userAgent.match(/AppleWebKit\/(\d+)/)[1]) >= 525 :
+ !((Ext.isGecko && !Ext.isWindows) || Ext.isOpera);
+
+ return {
+ _unload: function(){
+ Ext.EventManager.un(window, "resize", this.fireWindowResize, this);
+ unload.call(Ext.EventManager);
+ },
+
+
+ doResizeEvent: function(){
+ var h = D.getViewHeight(),
+ w = D.getViewWidth();
+
+
+ if(curHeight != h || curWidth != w){
+ resizeEvent.fire(curWidth = w, curHeight = h);
+ }
+ },
+
+
+ onWindowResize : function(fn, scope, options){
+ if(!resizeEvent){
+ resizeEvent = new Ext.util.Event();
+ resizeTask = new Ext.util.DelayedTask(this.doResizeEvent);
+ Ext.EventManager.on(window, "resize", this.fireWindowResize, this);
+ }
+ resizeEvent.addListener(fn, scope, options);
+ },
+
+
+ fireWindowResize : function(){
+ if(resizeEvent){
+ resizeTask.delay(100);
+ }
+ },
+
+
+ onTextResize : function(fn, scope, options){
+ if(!textEvent){
+ textEvent = new Ext.util.Event();
+ var textEl = new Ext.Element(document.createElement('div'));
+ textEl.dom.className = 'x-text-resize';
+ textEl.dom.innerHTML = 'X';
+ textEl.appendTo(document.body);
+ textSize = textEl.dom.offsetHeight;
+ setInterval(function(){
+ if(textEl.dom.offsetHeight != textSize){
+ textEvent.fire(textSize, textSize = textEl.dom.offsetHeight);
+ }
+ }, this.textResizeInterval);
+ }
+ textEvent.addListener(fn, scope, options);
+ },
+
+
+ removeResizeListener : function(fn, scope){
+ if(resizeEvent){
+ resizeEvent.removeListener(fn, scope);
+ }
+ },
+
+
+ fireResize : function(){
+ if(resizeEvent){
+ resizeEvent.fire(D.getViewWidth(), D.getViewHeight());
+ }
+ },
+
+
+ textResizeInterval : 50,
+
+
+ ieDeferSrc : false,
+
+
+ getKeyEvent : function(){
+ return useKeydown ? 'keydown' : 'keypress';
+ },
+
+
+
+ useKeydown: useKeydown
+ };
+}());
+
+Ext.EventManager.on = Ext.EventManager.addListener;
+
+
+Ext.apply(Ext.EventObjectImpl.prototype, {
+
+ BACKSPACE: 8,
+
+ TAB: 9,
+
+ NUM_CENTER: 12,
+
+ ENTER: 13,
+
+ RETURN: 13,
+
+ SHIFT: 16,
+
+ CTRL: 17,
+ CONTROL : 17,
+
+ ALT: 18,
+
+ PAUSE: 19,
+
+ CAPS_LOCK: 20,
+
+ ESC: 27,
+
+ SPACE: 32,
+
+ PAGE_UP: 33,
+ PAGEUP : 33,
+
+ PAGE_DOWN: 34,
+ PAGEDOWN : 34,
+
+ END: 35,
+
+ HOME: 36,
+
+ LEFT: 37,
+
+ UP: 38,
+
+ RIGHT: 39,
+
+ DOWN: 40,
+
+ PRINT_SCREEN: 44,
+
+ INSERT: 45,
+
+ DELETE: 46,
+
+ ZERO: 48,
+
+ ONE: 49,
+
+ TWO: 50,
+
+ THREE: 51,
+
+ FOUR: 52,
+
+ FIVE: 53,
+
+ SIX: 54,
+
+ SEVEN: 55,
+
+ EIGHT: 56,
+
+ NINE: 57,
+
+ A: 65,
+
+ B: 66,
+
+ C: 67,
+
+ D: 68,
+
+ E: 69,
+
+ F: 70,
+
+ G: 71,
+
+ H: 72,
+
+ I: 73,
+
+ J: 74,
+
+ K: 75,
+
+ L: 76,
+
+ M: 77,
+
+ N: 78,
+
+ O: 79,
+
+ P: 80,
+
+ Q: 81,
+
+ R: 82,
+
+ S: 83,
+
+ T: 84,
+
+ U: 85,
+
+ V: 86,
+
+ W: 87,
+
+ X: 88,
+
+ Y: 89,
+
+ Z: 90,
+
+ CONTEXT_MENU: 93,
+
+ NUM_ZERO: 96,
+
+ NUM_ONE: 97,
+
+ NUM_TWO: 98,
+
+ NUM_THREE: 99,
+
+ NUM_FOUR: 100,
+
+ NUM_FIVE: 101,
+
+ NUM_SIX: 102,
+
+ NUM_SEVEN: 103,
+
+ NUM_EIGHT: 104,
+
+ NUM_NINE: 105,
+
+ NUM_MULTIPLY: 106,
+
+ NUM_PLUS: 107,
+
+ NUM_MINUS: 109,
+
+ NUM_PERIOD: 110,
+
+ NUM_DIVISION: 111,
+
+ F1: 112,
+
+ F2: 113,
+
+ F3: 114,
+
+ F4: 115,
+
+ F5: 116,
+
+ F6: 117,
+
+ F7: 118,
+
+ F8: 119,
+
+ F9: 120,
+
+ F10: 121,
+
+ F11: 122,
+
+ F12: 123,
+
+
+ isNavKeyPress : function(){
+ var me = this,
+ k = this.normalizeKey(me.keyCode);
+ return (k >= 33 && k <= 40) ||
+ k == me.RETURN ||
+ k == me.TAB ||
+ k == me.ESC;
+ },
+
+ isSpecialKey : function(){
+ var k = this.normalizeKey(this.keyCode);
+ return (this.type == 'keypress' && this.ctrlKey) ||
+ this.isNavKeyPress() ||
+ (k == this.BACKSPACE) ||
+ (k >= 16 && k <= 20) ||
+ (k >= 44 && k <= 46);
+ },
+
+ getPoint : function(){
+ return new Ext.lib.Point(this.xy[0], this.xy[1]);
+ },
+
+
+ hasModifier : function(){
+ return ((this.ctrlKey || this.altKey) || this.shiftKey);
+ }
+});
+Ext.Element.addMethods({
+
+ swallowEvent : function(eventName, preventDefault) {
+ var me = this;
+ function fn(e) {
+ e.stopPropagation();
+ if (preventDefault) {
+ e.preventDefault();
+ }
+ }
+
+ if (Ext.isArray(eventName)) {
+ Ext.each(eventName, function(e) {
+ me.on(e, fn);
+ });
+ return me;
+ }
+ me.on(eventName, fn);
+ return me;
+ },
+
+
+ relayEvent : function(eventName, observable) {
+ this.on(eventName, function(e) {
+ observable.fireEvent(eventName, e);
+ });
+ },
+
+
+ clean : function(forceReclean) {
+ var me = this,
+ dom = me.dom,
+ n = dom.firstChild,
+ ni = -1;
+
+ if (Ext.Element.data(dom, 'isCleaned') && forceReclean !== true) {
+ return me;
+ }
+
+ while (n) {
+ var nx = n.nextSibling;
+ if (n.nodeType == 3 && !(/\S/.test(n.nodeValue))) {
+ dom.removeChild(n);
+ } else {
+ n.nodeIndex = ++ni;
+ }
+ n = nx;
+ }
+
+ Ext.Element.data(dom, 'isCleaned', true);
+ return me;
+ },
+
+
+ load : function() {
+ var updateManager = this.getUpdater();
+ updateManager.update.apply(updateManager, arguments);
+
+ return this;
+ },
+
+
+ getUpdater : function() {
+ return this.updateManager || (this.updateManager = new Ext.Updater(this));
+ },
+
+
+ update : function(html, loadScripts, callback) {
+ if (!this.dom) {
+ return this;
+ }
+ html = html || "";
+
+ if (loadScripts !== true) {
+ this.dom.innerHTML = html;
+ if (typeof callback == 'function') {
+ callback();
+ }
+ return this;
+ }
+
+ var id = Ext.id(),
+ dom = this.dom;
+
+ html += '<span id="' + id + '"></span>';
+
+ Ext.lib.Event.onAvailable(id, function() {
+ var DOC = document,
+ hd = DOC.getElementsByTagName("head")[0],
+ re = /(?:<script([^>]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig,
+ srcRe = /\ssrc=([\'\"])(.*?)\1/i,
+ typeRe = /\stype=([\'\"])(.*?)\1/i,
+ match,
+ attrs,
+ srcMatch,
+ typeMatch,
+ el,
+ s;
+
+ while ((match = re.exec(html))) {
+ attrs = match[1];
+ srcMatch = attrs ? attrs.match(srcRe) : false;
+ if (srcMatch && srcMatch[2]) {
+ s = DOC.createElement("script");
+ s.src = srcMatch[2];
+ typeMatch = attrs.match(typeRe);
+ if (typeMatch && typeMatch[2]) {
+ s.type = typeMatch[2];
+ }
+ hd.appendChild(s);
+ } else if (match[2] && match[2].length > 0) {
+ if (window.execScript) {
+ window.execScript(match[2]);
+ } else {
+ window.eval(match[2]);
+ }
+ }
+ }
+
+ el = DOC.getElementById(id);
+ if (el) {
+ Ext.removeNode(el);
+ }
+
+ if (typeof callback == 'function') {
+ callback();
+ }
+ });
+ dom.innerHTML = html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig, "");
+ return this;
+ },
+
+
+ removeAllListeners : function() {
+ this.removeAnchor();
+ Ext.EventManager.removeAll(this.dom);
+ return this;
+ },
+
+
+ createProxy : function(config, renderTo, matchBox) {
+ config = (typeof config == 'object') ? config : {tag : "div", cls: config};
+
+ var me = this,
+ proxy = renderTo ? Ext.DomHelper.append(renderTo, config, true) :
+ Ext.DomHelper.insertBefore(me.dom, config, true);
+
+ if (matchBox && me.setBox && me.getBox) {
+ proxy.setBox(me.getBox());
+ }
+ return proxy;
+ }
+});
+
+Ext.Element.prototype.getUpdateManager = Ext.Element.prototype.getUpdater;
+
+Ext.Element.addMethods({
+
+ getAnchorXY : function(anchor, local, s){
+
+
+ anchor = (anchor || "tl").toLowerCase();
+ s = s || {};
+
+ var me = this,
+ vp = me.dom == document.body || me.dom == document,
+ w = s.width || vp ? Ext.lib.Dom.getViewWidth() : me.getWidth(),
+ h = s.height || vp ? Ext.lib.Dom.getViewHeight() : me.getHeight(),
+ xy,
+ r = Math.round,
+ o = me.getXY(),
+ scroll = me.getScroll(),
+ extraX = vp ? scroll.left : !local ? o[0] : 0,
+ extraY = vp ? scroll.top : !local ? o[1] : 0,
+ hash = {
+ c : [r(w * 0.5), r(h * 0.5)],
+ t : [r(w * 0.5), 0],
+ l : [0, r(h * 0.5)],
+ r : [w, r(h * 0.5)],
+ b : [r(w * 0.5), h],
+ tl : [0, 0],
+ bl : [0, h],
+ br : [w, h],
+ tr : [w, 0]
+ };
+
+ xy = hash[anchor];
+ return [xy[0] + extraX, xy[1] + extraY];
+ },
+
+
+ anchorTo : function(el, alignment, offsets, animate, monitorScroll, callback){
+ var me = this,
+ dom = me.dom,
+ scroll = !Ext.isEmpty(monitorScroll),
+ action = function(){
+ Ext.fly(dom).alignTo(el, alignment, offsets, animate);
+ Ext.callback(callback, Ext.fly(dom));
+ },
+ anchor = this.getAnchor();
+
+
+ this.removeAnchor();
+ Ext.apply(anchor, {
+ fn: action,
+ scroll: scroll
+ });
+
+ Ext.EventManager.onWindowResize(action, null);
+
+ if(scroll){
+ Ext.EventManager.on(window, 'scroll', action, null,
+ {buffer: !isNaN(monitorScroll) ? monitorScroll : 50});
+ }
+ action.call(me);
+ return me;
+ },
+
+
+ removeAnchor : function(){
+ var me = this,
+ anchor = this.getAnchor();
+
+ if(anchor && anchor.fn){
+ Ext.EventManager.removeResizeListener(anchor.fn);
+ if(anchor.scroll){
+ Ext.EventManager.un(window, 'scroll', anchor.fn);
+ }
+ delete anchor.fn;
+ }
+ return me;
+ },
+
+
+ getAnchor : function(){
+ var data = Ext.Element.data,
+ dom = this.dom;
+ if (!dom) {
+ return;
+ }
+ var anchor = data(dom, '_anchor');
+
+ if(!anchor){
+ anchor = data(dom, '_anchor', {});
+ }
+ return anchor;
+ },
+
+
+ getAlignToXY : function(el, p, o){
+ el = Ext.get(el);
+
+ if(!el || !el.dom){
+ throw "Element.alignToXY with an element that doesn't exist";
+ }
+
+ o = o || [0,0];
+ p = (!p || p == "?" ? "tl-bl?" : (!(/-/).test(p) && p !== "" ? "tl-" + p : p || "tl-bl")).toLowerCase();
+
+ var me = this,
+ d = me.dom,
+ a1,
+ a2,
+ x,
+ y,
+
+ w,
+ h,
+ r,
+ dw = Ext.lib.Dom.getViewWidth() -10,
+ dh = Ext.lib.Dom.getViewHeight()-10,
+ p1y,
+ p1x,
+ p2y,
+ p2x,
+ swapY,
+ swapX,
+ doc = document,
+ docElement = doc.documentElement,
+ docBody = doc.body,
+ scrollX = (docElement.scrollLeft || docBody.scrollLeft || 0)+5,
+ scrollY = (docElement.scrollTop || docBody.scrollTop || 0)+5,
+ c = false,
+ p1 = "",
+ p2 = "",
+ m = p.match(/^([a-z]+)-([a-z]+)(\?)?$/);
+
+ if(!m){
+ throw "Element.alignTo with an invalid alignment " + p;
+ }
+
+ p1 = m[1];
+ p2 = m[2];
+ c = !!m[3];
+
+
+
+ a1 = me.getAnchorXY(p1, true);
+ a2 = el.getAnchorXY(p2, false);
+
+ x = a2[0] - a1[0] + o[0];
+ y = a2[1] - a1[1] + o[1];
+
+ if(c){
+ w = me.getWidth();
+ h = me.getHeight();
+ r = el.getRegion();
+
+
+
+ p1y = p1.charAt(0);
+ p1x = p1.charAt(p1.length-1);
+ p2y = p2.charAt(0);
+ p2x = p2.charAt(p2.length-1);
+ swapY = ((p1y=="t" && p2y=="b") || (p1y=="b" && p2y=="t"));
+ swapX = ((p1x=="r" && p2x=="l") || (p1x=="l" && p2x=="r"));
+
+
+ if (x + w > dw + scrollX) {
+ x = swapX ? r.left-w : dw+scrollX-w;
+ }
+ if (x < scrollX) {
+ x = swapX ? r.right : scrollX;
+ }
+ if (y + h > dh + scrollY) {
+ y = swapY ? r.top-h : dh+scrollY-h;
+ }
+ if (y < scrollY){
+ y = swapY ? r.bottom : scrollY;
+ }
+ }
+ return [x,y];
+ },
+
+
+ alignTo : function(element, position, offsets, animate){
+ var me = this;
+ return me.setXY(me.getAlignToXY(element, position, offsets),
+ me.preanim && !!animate ? me.preanim(arguments, 3) : false);
+ },
+
+
+ adjustForConstraints : function(xy, parent, offsets){
+ return this.getConstrainToXY(parent || document, false, offsets, xy) || xy;
+ },
+
+
+ getConstrainToXY : function(el, local, offsets, proposedXY){
+ var os = {top:0, left:0, bottom:0, right: 0};
+
+ return function(el, local, offsets, proposedXY){
+ el = Ext.get(el);
+ offsets = offsets ? Ext.applyIf(offsets, os) : os;
+
+ var vw, vh, vx = 0, vy = 0;
+ if(el.dom == document.body || el.dom == document){
+ vw =Ext.lib.Dom.getViewWidth();
+ vh = Ext.lib.Dom.getViewHeight();
+ }else{
+ vw = el.dom.clientWidth;
+ vh = el.dom.clientHeight;
+ if(!local){
+ var vxy = el.getXY();
+ vx = vxy[0];
+ vy = vxy[1];
+ }
+ }
+
+ var s = el.getScroll();
+
+ vx += offsets.left + s.left;
+ vy += offsets.top + s.top;
+
+ vw -= offsets.right;
+ vh -= offsets.bottom;
+
+ var vr = vx + vw,
+ vb = vy + vh,
+ xy = proposedXY || (!local ? this.getXY() : [this.getLeft(true), this.getTop(true)]),
+ x = xy[0], y = xy[1],
+ offset = this.getConstrainOffset(),
+ w = this.dom.offsetWidth + offset,
+ h = this.dom.offsetHeight + offset;
+
+
+ var moved = false;
+
+
+ if((x + w) > vr){
+ x = vr - w;
+ moved = true;
+ }
+ if((y + h) > vb){
+ y = vb - h;
+ moved = true;
+ }
+
+ if(x < vx){
+ x = vx;
+ moved = true;
+ }
+ if(y < vy){
+ y = vy;
+ moved = true;
+ }
+ return moved ? [x, y] : false;
+ };
+ }(),
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ getConstrainOffset : function(){
+ return 0;
+ },
+
+
+ getCenterXY : function(){
+ return this.getAlignToXY(document, 'c-c');
+ },
+
+
+ center : function(centerIn){
+ return this.alignTo(centerIn || document, 'c-c');
+ }
+});
+
+Ext.Element.addMethods({
+
+ select : function(selector, unique){
+ return Ext.Element.select(selector, unique, this.dom);
+ }
+});
+Ext.apply(Ext.Element.prototype, function() {
+ var GETDOM = Ext.getDom,
+ GET = Ext.get,
+ DH = Ext.DomHelper;
+
+ return {
+
+ insertSibling: function(el, where, returnDom){
+ var me = this,
+ rt,
+ isAfter = (where || 'before').toLowerCase() == 'after',
+ insertEl;
+
+ if(Ext.isArray(el)){
+ insertEl = me;
+ Ext.each(el, function(e) {
+ rt = Ext.fly(insertEl, '_internal').insertSibling(e, where, returnDom);
+ if(isAfter){
+ insertEl = rt;
+ }
+ });
+ return rt;
+ }
+
+ el = el || {};
+
+ if(el.nodeType || el.dom){
+ rt = me.dom.parentNode.insertBefore(GETDOM(el), isAfter ? me.dom.nextSibling : me.dom);
+ if (!returnDom) {
+ rt = GET(rt);
+ }
+ }else{
+ if (isAfter && !me.dom.nextSibling) {
+ rt = DH.append(me.dom.parentNode, el, !returnDom);
+ } else {
+ rt = DH[isAfter ? 'insertAfter' : 'insertBefore'](me.dom, el, !returnDom);
+ }
+ }
+ return rt;
+ }
+ };
+}());
+
+
+Ext.Element.boxMarkup = '<div class="{0}-tl"><div class="{0}-tr"><div class="{0}-tc"></div></div></div><div class="{0}-ml"><div class="{0}-mr"><div class="{0}-mc"></div></div></div><div class="{0}-bl"><div class="{0}-br"><div class="{0}-bc"></div></div></div>';
+
+Ext.Element.addMethods(function(){
+ var INTERNAL = "_internal",
+ pxMatch = /(\d+\.?\d+)px/;
+ return {
+
+ applyStyles : function(style){
+ Ext.DomHelper.applyStyles(this.dom, style);
+ return this;
+ },
+
+
+ getStyles : function(){
+ var ret = {};
+ Ext.each(arguments, function(v) {
+ ret[v] = this.getStyle(v);
+ },
+ this);
+ return ret;
+ },
+
+
+ setOverflow : function(v){
+ var dom = this.dom;
+ if(v=='auto' && Ext.isMac && Ext.isGecko2){
+ dom.style.overflow = 'hidden';
+ (function(){dom.style.overflow = 'auto';}).defer(1);
+ }else{
+ dom.style.overflow = v;
+ }
+ },
+
+
+ boxWrap : function(cls){
+ cls = cls || 'x-box';
+ var el = Ext.get(this.insertHtml("beforeBegin", "<div class='" + cls + "'>" + String.format(Ext.Element.boxMarkup, cls) + "</div>"));
+ Ext.DomQuery.selectNode('.' + cls + '-mc', el.dom).appendChild(this.dom);
+ return el;
+ },
+
+
+ setSize : function(width, height, animate){
+ var me = this;
+ if(typeof width == 'object'){
+ height = width.height;
+ width = width.width;
+ }
+ width = me.adjustWidth(width);
+ height = me.adjustHeight(height);
+ if(!animate || !me.anim){
+ me.dom.style.width = me.addUnits(width);
+ me.dom.style.height = me.addUnits(height);
+ }else{
+ me.anim({width: {to: width}, height: {to: height}}, me.preanim(arguments, 2));
+ }
+ return me;
+ },
+
+
+ getComputedHeight : function(){
+ var me = this,
+ h = Math.max(me.dom.offsetHeight, me.dom.clientHeight);
+ if(!h){
+ h = parseFloat(me.getStyle('height')) || 0;
+ if(!me.isBorderBox()){
+ h += me.getFrameWidth('tb');
+ }
+ }
+ return h;
+ },
+
+
+ getComputedWidth : function(){
+ var w = Math.max(this.dom.offsetWidth, this.dom.clientWidth);
+ if(!w){
+ w = parseFloat(this.getStyle('width')) || 0;
+ if(!this.isBorderBox()){
+ w += this.getFrameWidth('lr');
+ }
+ }
+ return w;
+ },
+
+
+ getFrameWidth : function(sides, onlyContentBox){
+ return onlyContentBox && this.isBorderBox() ? 0 : (this.getPadding(sides) + this.getBorderWidth(sides));
+ },
+
+
+ addClassOnOver : function(className){
+ this.hover(
+ function(){
+ Ext.fly(this, INTERNAL).addClass(className);
+ },
+ function(){
+ Ext.fly(this, INTERNAL).removeClass(className);
+ }
+ );
+ return this;
+ },
+
+
+ addClassOnFocus : function(className){
+ this.on("focus", function(){
+ Ext.fly(this, INTERNAL).addClass(className);
+ }, this.dom);
+ this.on("blur", function(){
+ Ext.fly(this, INTERNAL).removeClass(className);
+ }, this.dom);
+ return this;
+ },
+
+
+ addClassOnClick : function(className){
+ var dom = this.dom;
+ this.on("mousedown", function(){
+ Ext.fly(dom, INTERNAL).addClass(className);
+ var d = Ext.getDoc(),
+ fn = function(){
+ Ext.fly(dom, INTERNAL).removeClass(className);
+ d.removeListener("mouseup", fn);
+ };
+ d.on("mouseup", fn);
+ });
+ return this;
+ },
+
+
+
+ getViewSize : function(){
+ var doc = document,
+ d = this.dom,
+ isDoc = (d == doc || d == doc.body);
+
+
+ if (isDoc) {
+ var extdom = Ext.lib.Dom;
+ return {
+ width : extdom.getViewWidth(),
+ height : extdom.getViewHeight()
+ };
+
+
+ } else {
+ return {
+ width : d.clientWidth,
+ height : d.clientHeight
+ };
+ }
+ },
+
+
+
+ getStyleSize : function(){
+ var me = this,
+ w, h,
+ doc = document,
+ d = this.dom,
+ isDoc = (d == doc || d == doc.body),
+ s = d.style;
+
+
+ if (isDoc) {
+ var extdom = Ext.lib.Dom;
+ return {
+ width : extdom.getViewWidth(),
+ height : extdom.getViewHeight()
+ };
+ }
+
+ if(s.width && s.width != 'auto'){
+ w = parseFloat(s.width);
+ if(me.isBorderBox()){
+ w -= me.getFrameWidth('lr');
+ }
+ }
+
+ if(s.height && s.height != 'auto'){
+ h = parseFloat(s.height);
+ if(me.isBorderBox()){
+ h -= me.getFrameWidth('tb');
+ }
+ }
+
+ return {width: w || me.getWidth(true), height: h || me.getHeight(true)};
+ },
+
+
+ getSize : function(contentSize){
+ return {width: this.getWidth(contentSize), height: this.getHeight(contentSize)};
+ },
+
+
+ repaint : function(){
+ var dom = this.dom;
+ this.addClass("x-repaint");
+ setTimeout(function(){
+ Ext.fly(dom).removeClass("x-repaint");
+ }, 1);
+ return this;
+ },
+
+
+ unselectable : function(){
+ this.dom.unselectable = "on";
+ return this.swallowEvent("selectstart", true).
+ addClass("x-unselectable");
+ },
+
+
+ getMargins : function(side){
+ var me = this,
+ key,
+ hash = {t:"top", l:"left", r:"right", b: "bottom"},
+ o = {};
+
+ if (!side) {
+ for (key in me.margins){
+ o[hash[key]] = parseFloat(me.getStyle(me.margins[key])) || 0;
+ }
+ return o;
+ } else {
+ return me.addStyles.call(me, side, me.margins);
+ }
+ }
+ };
+}());
+
+Ext.Element.addMethods({
+
+ setBox : function(box, adjust, animate){
+ var me = this,
+ w = box.width,
+ h = box.height;
+ if((adjust && !me.autoBoxAdjust) && !me.isBorderBox()){
+ w -= (me.getBorderWidth("lr") + me.getPadding("lr"));
+ h -= (me.getBorderWidth("tb") + me.getPadding("tb"));
+ }
+ me.setBounds(box.x, box.y, w, h, me.animTest.call(me, arguments, animate, 2));
+ return me;
+ },
+
+
+ getBox : function(contentBox, local) {
+ var me = this,
+ xy,
+ left,
+ top,
+ getBorderWidth = me.getBorderWidth,
+ getPadding = me.getPadding,
+ l,
+ r,
+ t,
+ b;
+ if(!local){
+ xy = me.getXY();
+ }else{
+ left = parseInt(me.getStyle("left"), 10) || 0;
+ top = parseInt(me.getStyle("top"), 10) || 0;
+ xy = [left, top];
+ }
+ var el = me.dom, w = el.offsetWidth, h = el.offsetHeight, bx;
+ if(!contentBox){
+ bx = {x: xy[0], y: xy[1], 0: xy[0], 1: xy[1], width: w, height: h};
+ }else{
+ l = getBorderWidth.call(me, "l") + getPadding.call(me, "l");
+ r = getBorderWidth.call(me, "r") + getPadding.call(me, "r");
+ t = getBorderWidth.call(me, "t") + getPadding.call(me, "t");
+ b = getBorderWidth.call(me, "b") + getPadding.call(me, "b");
+ bx = {x: xy[0]+l, y: xy[1]+t, 0: xy[0]+l, 1: xy[1]+t, width: w-(l+r), height: h-(t+b)};
+ }
+ bx.right = bx.x + bx.width;
+ bx.bottom = bx.y + bx.height;
+ return bx;
+ },
+
+
+ move : function(direction, distance, animate){
+ var me = this,
+ xy = me.getXY(),
+ x = xy[0],
+ y = xy[1],
+ left = [x - distance, y],
+ right = [x + distance, y],
+ top = [x, y - distance],
+ bottom = [x, y + distance],
+ hash = {
+ l : left,
+ left : left,
+ r : right,
+ right : right,
+ t : top,
+ top : top,
+ up : top,
+ b : bottom,
+ bottom : bottom,
+ down : bottom
+ };
+
+ direction = direction.toLowerCase();
+ me.moveTo(hash[direction][0], hash[direction][1], me.animTest.call(me, arguments, animate, 2));
+ },
+
+
+ setLeftTop : function(left, top){
+ var me = this,
+ style = me.dom.style;
+ style.left = me.addUnits(left);
+ style.top = me.addUnits(top);
+ return me;
+ },
+
+
+ getRegion : function(){
+ return Ext.lib.Dom.getRegion(this.dom);
+ },
+
+
+ setBounds : function(x, y, width, height, animate){
+ var me = this;
+ if (!animate || !me.anim) {
+ me.setSize(width, height);
+ me.setLocation(x, y);
+ } else {
+ me.anim({points: {to: [x, y]},
+ width: {to: me.adjustWidth(width)},
+ height: {to: me.adjustHeight(height)}},
+ me.preanim(arguments, 4),
+ 'motion');
+ }
+ return me;
+ },
+
+
+ setRegion : function(region, animate) {
+ return this.setBounds(region.left, region.top, region.right-region.left, region.bottom-region.top, this.animTest.call(this, arguments, animate, 1));
+ }
+});
+Ext.Element.addMethods({
+
+ scrollTo : function(side, value, animate) {
+
+ var top = /top/i.test(side),
+ me = this,
+ dom = me.dom,
+ prop;
+ if (!animate || !me.anim) {
+
+ prop = 'scroll' + (top ? 'Top' : 'Left');
+ dom[prop] = value;
+ }
+ else {
+
+ prop = 'scroll' + (top ? 'Left' : 'Top');
+ me.anim({scroll: {to: top ? [dom[prop], value] : [value, dom[prop]]}}, me.preanim(arguments, 2), 'scroll');
+ }
+ return me;
+ },
+
+
+ scrollIntoView : function(container, hscroll) {
+ var c = Ext.getDom(container) || Ext.getBody().dom,
+ el = this.dom,
+ o = this.getOffsetsTo(c),
+ l = o[0] + c.scrollLeft,
+ t = o[1] + c.scrollTop,
+ b = t + el.offsetHeight,
+ r = l + el.offsetWidth,
+ ch = c.clientHeight,
+ ct = parseInt(c.scrollTop, 10),
+ cl = parseInt(c.scrollLeft, 10),
+ cb = ct + ch,
+ cr = cl + c.clientWidth;
+
+ if (el.offsetHeight > ch || t < ct) {
+ c.scrollTop = t;
+ }
+ else if (b > cb) {
+ c.scrollTop = b-ch;
+ }
+
+ c.scrollTop = c.scrollTop;
+
+ if (hscroll !== false) {
+ if (el.offsetWidth > c.clientWidth || l < cl) {
+ c.scrollLeft = l;
+ }
+ else if (r > cr) {
+ c.scrollLeft = r - c.clientWidth;
+ }
+ c.scrollLeft = c.scrollLeft;
+ }
+ return this;
+ },
+
+
+ scrollChildIntoView : function(child, hscroll) {
+ Ext.fly(child, '_scrollChildIntoView').scrollIntoView(this, hscroll);
+ },
+
+
+ scroll : function(direction, distance, animate) {
+ if (!this.isScrollable()) {
+ return false;
+ }
+ var el = this.dom,
+ l = el.scrollLeft, t = el.scrollTop,
+ w = el.scrollWidth, h = el.scrollHeight,
+ cw = el.clientWidth, ch = el.clientHeight,
+ scrolled = false, v,
+ hash = {
+ l: Math.min(l + distance, w-cw),
+ r: v = Math.max(l - distance, 0),
+ t: Math.max(t - distance, 0),
+ b: Math.min(t + distance, h-ch)
+ };
+ hash.d = hash.b;
+ hash.u = hash.t;
+
+ direction = direction.substr(0, 1);
+ if ((v = hash[direction]) > -1) {
+ scrolled = true;
+ this.scrollTo(direction == 'l' || direction == 'r' ? 'left' : 'top', v, this.preanim(arguments, 2));
+ }
+ return scrolled;
+ }
+});
+Ext.Element.addMethods(
+ function() {
+ var VISIBILITY = "visibility",
+ DISPLAY = "display",
+ HIDDEN = "hidden",
+ NONE = "none",
+ XMASKED = "x-masked",
+ XMASKEDRELATIVE = "x-masked-relative",
+ data = Ext.Element.data;
+
+ return {
+
+ isVisible : function(deep) {
+ var vis = !this.isStyle(VISIBILITY, HIDDEN) && !this.isStyle(DISPLAY, NONE),
+ p = this.dom.parentNode;
+
+ if (deep !== true || !vis) {
+ return vis;
+ }
+
+ while (p && !(/^body/i.test(p.tagName))) {
+ if (!Ext.fly(p, '_isVisible').isVisible()) {
+ return false;
+ }
+ p = p.parentNode;
+ }
+ return true;
+ },
+
+
+ isDisplayed : function() {
+ return !this.isStyle(DISPLAY, NONE);
+ },
+
+
+ enableDisplayMode : function(display) {
+ this.setVisibilityMode(Ext.Element.DISPLAY);
+
+ if (!Ext.isEmpty(display)) {
+ data(this.dom, 'originalDisplay', display);
+ }
+
+ return this;
+ },
+
+
+ mask : function(msg, msgCls) {
+ var me = this,
+ dom = me.dom,
+ dh = Ext.DomHelper,
+ EXTELMASKMSG = "ext-el-mask-msg",
+ el,
+ mask;
+
+ if (!/^body/i.test(dom.tagName) && me.getStyle('position') == 'static') {
+ me.addClass(XMASKEDRELATIVE);
+ }
+ if (el = data(dom, 'maskMsg')) {
+ el.remove();
+ }
+ if (el = data(dom, 'mask')) {
+ el.remove();
+ }
+
+ mask = dh.append(dom, {cls : "ext-el-mask"}, true);
+ data(dom, 'mask', mask);
+
+ me.addClass(XMASKED);
+ mask.setDisplayed(true);
+
+ if (typeof msg == 'string') {
+ var mm = dh.append(dom, {cls : EXTELMASKMSG, cn:{tag:'div'}}, true);
+ data(dom, 'maskMsg', mm);
+ mm.dom.className = msgCls ? EXTELMASKMSG + " " + msgCls : EXTELMASKMSG;
+ mm.dom.firstChild.innerHTML = msg;
+ mm.setDisplayed(true);
+ mm.center(me);
+ }
+
+
+ if (Ext.isIE && !(Ext.isIE7 && Ext.isStrict) && me.getStyle('height') == 'auto') {
+ mask.setSize(undefined, me.getHeight());
+ }
+
+ return mask;
+ },
+
+
+ unmask : function() {
+ var me = this,
+ dom = me.dom,
+ mask = data(dom, 'mask'),
+ maskMsg = data(dom, 'maskMsg');
+
+ if (mask) {
+ if (maskMsg) {
+ maskMsg.remove();
+ data(dom, 'maskMsg', undefined);
+ }
+
+ mask.remove();
+ data(dom, 'mask', undefined);
+ me.removeClass([XMASKED, XMASKEDRELATIVE]);
+ }
+ },
+
+
+ isMasked : function() {
+ var m = data(this.dom, 'mask');
+ return m && m.isVisible();
+ },
+
+
+ createShim : function() {
+ var el = document.createElement('iframe'),
+ shim;
+
+ el.frameBorder = '0';
+ el.className = 'ext-shim';
+ el.src = Ext.SSL_SECURE_URL;
+ shim = Ext.get(this.dom.parentNode.insertBefore(el, this.dom));
+ shim.autoBoxAdjust = false;
+ return shim;
+ }
+ };
+ }()
+);
+Ext.Element.addMethods({
+
+ addKeyListener : function(key, fn, scope){
+ var config;
+ if(typeof key != 'object' || Ext.isArray(key)){
+ config = {
+ key: key,
+ fn: fn,
+ scope: scope
+ };
+ }else{
+ config = {
+ key : key.key,
+ shift : key.shift,
+ ctrl : key.ctrl,
+ alt : key.alt,
+ fn: fn,
+ scope: scope
+ };
+ }
+ return new Ext.KeyMap(this, config);
+ },
+
+
+ addKeyMap : function(config){
+ return new Ext.KeyMap(this, config);
+ }
+});
+
+
+
+Ext.CompositeElementLite.importElementMethods();
+Ext.apply(Ext.CompositeElementLite.prototype, {
+ addElements : function(els, root){
+ if(!els){
+ return this;
+ }
+ if(typeof els == "string"){
+ els = Ext.Element.selectorFunction(els, root);
+ }
+ var yels = this.elements;
+ Ext.each(els, function(e) {
+ yels.push(Ext.get(e));
+ });
+ return this;
+ },
+
+
+ first : function(){
+ return this.item(0);
+ },
+
+
+ last : function(){
+ return this.item(this.getCount()-1);
+ },
+
+
+ contains : function(el){
+ return this.indexOf(el) != -1;
+ },
+
+
+ removeElement : function(keys, removeDom){
+ var me = this,
+ els = this.elements,
+ el;
+ Ext.each(keys, function(val){
+ if ((el = (els[val] || els[val = me.indexOf(val)]))) {
+ if(removeDom){
+ if(el.dom){
+ el.remove();
+ }else{
+ Ext.removeNode(el);
+ }
+ }
+ els.splice(val, 1);
+ }
+ });
+ return this;
+ }
+});
+
+Ext.CompositeElement = Ext.extend(Ext.CompositeElementLite, {
+
+ constructor : function(els, root){
+ this.elements = [];
+ this.add(els, root);
+ },
+
+
+ getElement : function(el){
+
+ return el;
+ },
+
+
+ transformElement : function(el){
+ return Ext.get(el);
+ }
+
+
+
+
+
+
+});
+
+
+Ext.Element.select = function(selector, unique, root){
+ var els;
+ if(typeof selector == "string"){
+ els = Ext.Element.selectorFunction(selector, root);
+ }else if(selector.length !== undefined){
+ els = selector;
+ }else{
+ throw "Invalid selector";
+ }
+
+ return (unique === true) ? new Ext.CompositeElement(els) : new Ext.CompositeElementLite(els);
+};
+
+
+Ext.select = Ext.Element.select;
+Ext.UpdateManager = Ext.Updater = Ext.extend(Ext.util.Observable,
+function() {
+ var BEFOREUPDATE = "beforeupdate",
+ UPDATE = "update",
+ FAILURE = "failure";
+
+
+ function processSuccess(response){
+ var me = this;
+ me.transaction = null;
+ if (response.argument.form && response.argument.reset) {
+ try {
+ response.argument.form.reset();
+ } catch(e){}
+ }
+ if (me.loadScripts) {
+ me.renderer.render(me.el, response, me,
+ updateComplete.createDelegate(me, [response]));
+ } else {
+ me.renderer.render(me.el, response, me);
+ updateComplete.call(me, response);
+ }
+ }
+
+
+ function updateComplete(response, type, success){
+ this.fireEvent(type || UPDATE, this.el, response);
+ if(Ext.isFunction(response.argument.callback)){
+ response.argument.callback.call(response.argument.scope, this.el, Ext.isEmpty(success) ? true : false, response, response.argument.options);
+ }
+ }
+
+
+ function processFailure(response){
+ updateComplete.call(this, response, FAILURE, !!(this.transaction = null));
+ }
+
+ return {
+ constructor: function(el, forceNew){
+ var me = this;
+ el = Ext.get(el);
+ if(!forceNew && el.updateManager){
+ return el.updateManager;
+ }
+
+ me.el = el;
+
+ me.defaultUrl = null;
+
+ me.addEvents(
+
+ BEFOREUPDATE,
+
+ UPDATE,
+
+ FAILURE
+ );
+
+ Ext.apply(me, Ext.Updater.defaults);
+
+
+
+
+
+
+
+
+ me.transaction = null;
+
+ me.refreshDelegate = me.refresh.createDelegate(me);
+
+ me.updateDelegate = me.update.createDelegate(me);
+
+ me.formUpdateDelegate = (me.formUpdate || function(){}).createDelegate(me);
+
+
+ me.renderer = me.renderer || me.getDefaultRenderer();
+
+ Ext.Updater.superclass.constructor.call(me);
+ },
+
+
+ setRenderer : function(renderer){
+ this.renderer = renderer;
+ },
+
+
+ getRenderer : function(){
+ return this.renderer;
+ },
+
+
+ getDefaultRenderer: function() {
+ return new Ext.Updater.BasicRenderer();
+ },
+
+
+ setDefaultUrl : function(defaultUrl){
+ this.defaultUrl = defaultUrl;
+ },
+
+
+ getEl : function(){
+ return this.el;
+ },
+
+
+ update : function(url, params, callback, discardUrl){
+ var me = this,
+ cfg,
+ callerScope;
+
+ if(me.fireEvent(BEFOREUPDATE, me.el, url, params) !== false){
+ if(Ext.isObject(url)){
+ cfg = url;
+ url = cfg.url;
+ params = params || cfg.params;
+ callback = callback || cfg.callback;
+ discardUrl = discardUrl || cfg.discardUrl;
+ callerScope = cfg.scope;
+ if(!Ext.isEmpty(cfg.nocache)){me.disableCaching = cfg.nocache;};
+ if(!Ext.isEmpty(cfg.text)){me.indicatorText = '<div class="loading-indicator">'+cfg.text+"</div>";};
+ if(!Ext.isEmpty(cfg.scripts)){me.loadScripts = cfg.scripts;};
+ if(!Ext.isEmpty(cfg.timeout)){me.timeout = cfg.timeout;};
+ }
+ me.showLoading();
+
+ if(!discardUrl){
+ me.defaultUrl = url;
+ }
+ if(Ext.isFunction(url)){
+ url = url.call(me);
+ }
+
+ var o = Ext.apply({}, {
+ url : url,
+ params: (Ext.isFunction(params) && callerScope) ? params.createDelegate(callerScope) : params,
+ success: processSuccess,
+ failure: processFailure,
+ scope: me,
+ callback: undefined,
+ timeout: (me.timeout*1000),
+ disableCaching: me.disableCaching,
+ argument: {
+ "options": cfg,
+ "url": url,
+ "form": null,
+ "callback": callback,
+ "scope": callerScope || window,
+ "params": params
+ }
+ }, cfg);
+
+ me.transaction = Ext.Ajax.request(o);
+ }
+ },
+
+
+ formUpdate : function(form, url, reset, callback){
+ var me = this;
+ if(me.fireEvent(BEFOREUPDATE, me.el, form, url) !== false){
+ if(Ext.isFunction(url)){
+ url = url.call(me);
+ }
+ form = Ext.getDom(form);
+ me.transaction = Ext.Ajax.request({
+ form: form,
+ url:url,
+ success: processSuccess,
+ failure: processFailure,
+ scope: me,
+ timeout: (me.timeout*1000),
+ argument: {
+ "url": url,
+ "form": form,
+ "callback": callback,
+ "reset": reset
+ }
+ });
+ me.showLoading.defer(1, me);
+ }
+ },
+
+
+ startAutoRefresh : function(interval, url, params, callback, refreshNow){
+ var me = this;
+ if(refreshNow){
+ me.update(url || me.defaultUrl, params, callback, true);
+ }
+ if(me.autoRefreshProcId){
+ clearInterval(me.autoRefreshProcId);
+ }
+ me.autoRefreshProcId = setInterval(me.update.createDelegate(me, [url || me.defaultUrl, params, callback, true]), interval * 1000);
+ },
+
+
+ stopAutoRefresh : function(){
+ if(this.autoRefreshProcId){
+ clearInterval(this.autoRefreshProcId);
+ delete this.autoRefreshProcId;
+ }
+ },
+
+
+ isAutoRefreshing : function(){
+ return !!this.autoRefreshProcId;
+ },
+
+
+ showLoading : function(){
+ if(this.showLoadIndicator){
+ this.el.dom.innerHTML = this.indicatorText;
+ }
+ },
+
+
+ abort : function(){
+ if(this.transaction){
+ Ext.Ajax.abort(this.transaction);
+ }
+ },
+
+
+ isUpdating : function(){
+ return this.transaction ? Ext.Ajax.isLoading(this.transaction) : false;
+ },
+
+
+ refresh : function(callback){
+ if(this.defaultUrl){
+ this.update(this.defaultUrl, null, callback, true);
+ }
+ }
+ };
+}());
+
+
+Ext.Updater.defaults = {
+
+ timeout : 30,
+
+ disableCaching : false,
+
+ showLoadIndicator : true,
+
+ indicatorText : '<div class="loading-indicator">Loading...</div>',
+
+ loadScripts : false,
+
+ sslBlankUrl : Ext.SSL_SECURE_URL
+};
+
+
+
+Ext.Updater.updateElement = function(el, url, params, options){
+ var um = Ext.get(el).getUpdater();
+ Ext.apply(um, options);
+ um.update(url, params, options ? options.callback : null);
+};
+
+
+Ext.Updater.BasicRenderer = function(){};
+
+Ext.Updater.BasicRenderer.prototype = {
+
+ render : function(el, response, updateManager, callback){
+ el.update(response.responseText, updateManager.loadScripts, callback);
+ }
+};
+
+
+
+(function() {
+
+
+Date.useStrict = false;
+
+
+
+
+
+function xf(format) {
+ var args = Array.prototype.slice.call(arguments, 1);
+ return format.replace(/\{(\d+)\}/g, function(m, i) {
+ return args[i];
+ });
+}
+
+
+
+Date.formatCodeToRegex = function(character, currentGroup) {
+
+ var p = Date.parseCodes[character];
+
+ if (p) {
+ p = typeof p == 'function'? p() : p;
+ Date.parseCodes[character] = p;
+ }
+
+ return p ? Ext.applyIf({
+ c: p.c ? xf(p.c, currentGroup || "{0}") : p.c
+ }, p) : {
+ g:0,
+ c:null,
+ s:Ext.escapeRe(character)
+ };
+};
+
+
+var $f = Date.formatCodeToRegex;
+
+Ext.apply(Date, {
+
+ parseFunctions: {
+ "M$": function(input, strict) {
+
+
+ var re = new RegExp('\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/');
+ var r = (input || '').match(re);
+ return r? new Date(((r[1] || '') + r[2]) * 1) : null;
+ }
+ },
+ parseRegexes: [],
+
+
+ formatFunctions: {
+ "M$": function() {
+
+ return '\\/Date(' + this.getTime() + ')\\/';
+ }
+ },
+
+ y2kYear : 50,
+
+
+ MILLI : "ms",
+
+
+ SECOND : "s",
+
+
+ MINUTE : "mi",
+
+
+ HOUR : "h",
+
+
+ DAY : "d",
+
+
+ MONTH : "mo",
+
+
+ YEAR : "y",
+
+
+ defaults: {},
+
+
+ dayNames : [
+ "Sunday",
+ "Monday",
+ "Tuesday",
+ "Wednesday",
+ "Thursday",
+ "Friday",
+ "Saturday"
+ ],
+
+
+ monthNames : [
+ "January",
+ "February",
+ "March",
+ "April",
+ "May",
+ "June",
+ "July",
+ "August",
+ "September",
+ "October",
+ "November",
+ "December"
+ ],
+
+
+ monthNumbers : {
+ Jan:0,
+ Feb:1,
+ Mar:2,
+ Apr:3,
+ May:4,
+ Jun:5,
+ Jul:6,
+ Aug:7,
+ Sep:8,
+ Oct:9,
+ Nov:10,
+ Dec:11
+ },
+
+
+ getShortMonthName : function(month) {
+ return Date.monthNames[month].substring(0, 3);
+ },
+
+
+ getShortDayName : function(day) {
+ return Date.dayNames[day].substring(0, 3);
+ },
+
+
+ getMonthNumber : function(name) {
+
+ return Date.monthNumbers[name.substring(0, 1).toUpperCase() + name.substring(1, 3).toLowerCase()];
+ },
+
+
+ formatContainsHourInfo : (function(){
+ var stripEscapeRe = /(\\.)/g,
+ hourInfoRe = /([gGhHisucUOPZ]|M\$)/;
+ return function(format){
+ return hourInfoRe.test(format.replace(stripEscapeRe, ''));
+ };
+ })(),
+
+
+ formatCodes : {
+ d: "String.leftPad(this.getDate(), 2, '0')",
+ D: "Date.getShortDayName(this.getDay())",
+ j: "this.getDate()",
+ l: "Date.dayNames[this.getDay()]",
+ N: "(this.getDay() ? this.getDay() : 7)",
+ S: "this.getSuffix()",
+ w: "this.getDay()",
+ z: "this.getDayOfYear()",
+ W: "String.leftPad(this.getWeekOfYear(), 2, '0')",
+ F: "Date.monthNames[this.getMonth()]",
+ m: "String.leftPad(this.getMonth() + 1, 2, '0')",
+ M: "Date.getShortMonthName(this.getMonth())",
+ n: "(this.getMonth() + 1)",
+ t: "this.getDaysInMonth()",
+ L: "(this.isLeapYear() ? 1 : 0)",
+ o: "(this.getFullYear() + (this.getWeekOfYear() == 1 && this.getMonth() > 0 ? +1 : (this.getWeekOfYear() >= 52 && this.getMonth() < 11 ? -1 : 0)))",
+ Y: "String.leftPad(this.getFullYear(), 4, '0')",
+ y: "('' + this.getFullYear()).substring(2, 4)",
+ a: "(this.getHours() < 12 ? 'am' : 'pm')",
+ A: "(this.getHours() < 12 ? 'AM' : 'PM')",
+ g: "((this.getHours() % 12) ? this.getHours() % 12 : 12)",
+ G: "this.getHours()",
+ h: "String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",
+ H: "String.leftPad(this.getHours(), 2, '0')",
+ i: "String.leftPad(this.getMinutes(), 2, '0')",
+ s: "String.leftPad(this.getSeconds(), 2, '0')",
+ u: "String.leftPad(this.getMilliseconds(), 3, '0')",
+ O: "this.getGMTOffset()",
+ P: "this.getGMTOffset(true)",
+ T: "this.getTimezone()",
+ Z: "(this.getTimezoneOffset() * -60)",
+
+ c: function() {
+ for (var c = "Y-m-dTH:i:sP", code = [], i = 0, l = c.length; i < l; ++i) {
+ var e = c.charAt(i);
+ code.push(e == "T" ? "'T'" : Date.getFormatCode(e));
+ }
+ return code.join(" + ");
+ },
+
+
+ U: "Math.round(this.getTime() / 1000)"
+ },
+
+
+ isValid : function(y, m, d, h, i, s, ms) {
+
+ h = h || 0;
+ i = i || 0;
+ s = s || 0;
+ ms = ms || 0;
+
+
+ var dt = new Date(y < 100 ? 100 : y, m - 1, d, h, i, s, ms).add(Date.YEAR, y < 100 ? y - 100 : 0);
+
+ return y == dt.getFullYear() &&
+ m == dt.getMonth() + 1 &&
+ d == dt.getDate() &&
+ h == dt.getHours() &&
+ i == dt.getMinutes() &&
+ s == dt.getSeconds() &&
+ ms == dt.getMilliseconds();
+ },
+
+
+ parseDate : function(input, format, strict) {
+ var p = Date.parseFunctions;
+ if (p[format] == null) {
+ Date.createParser(format);
+ }
+ return p[format](input, Ext.isDefined(strict) ? strict : Date.useStrict);
+ },
+
+
+ getFormatCode : function(character) {
+ var f = Date.formatCodes[character];
+
+ if (f) {
+ f = typeof f == 'function'? f() : f;
+ Date.formatCodes[character] = f;
+ }
+
+
+ return f || ("'" + String.escape(character) + "'");
+ },
+
+
+ createFormat : function(format) {
+ var code = [],
+ special = false,
+ ch = '';
+
+ for (var i = 0; i < format.length; ++i) {
+ ch = format.charAt(i);
+ if (!special && ch == "\\") {
+ special = true;
+ } else if (special) {
+ special = false;
+ code.push("'" + String.escape(ch) + "'");
+ } else {
+ code.push(Date.getFormatCode(ch));
+ }
+ }
+ Date.formatFunctions[format] = new Function("return " + code.join('+'));
+ },
+
+
+ createParser : function() {
+ var code = [
+ "var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,",
+ "def = Date.defaults,",
+ "results = String(input).match(Date.parseRegexes[{0}]);",
+
+ "if(results){",
+ "{1}",
+
+ "if(u != null){",
+ "v = new Date(u * 1000);",
+ "}else{",
+
+
+
+ "dt = (new Date()).clearTime();",
+
+
+ "y = Ext.num(y, Ext.num(def.y, dt.getFullYear()));",
+ "m = Ext.num(m, Ext.num(def.m - 1, dt.getMonth()));",
+ "d = Ext.num(d, Ext.num(def.d, dt.getDate()));",
+
+
+ "h = Ext.num(h, Ext.num(def.h, dt.getHours()));",
+ "i = Ext.num(i, Ext.num(def.i, dt.getMinutes()));",
+ "s = Ext.num(s, Ext.num(def.s, dt.getSeconds()));",
+ "ms = Ext.num(ms, Ext.num(def.ms, dt.getMilliseconds()));",
+
+ "if(z >= 0 && y >= 0){",
+
+
+
+
+
+ "v = new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms).add(Date.YEAR, y < 100 ? y - 100 : 0);",
+
+
+ "v = !strict? v : (strict === true && (z <= 364 || (v.isLeapYear() && z <= 365))? v.add(Date.DAY, z) : null);",
+ "}else if(strict === true && !Date.isValid(y, m + 1, d, h, i, s, ms)){",
+ "v = null;",
+ "}else{",
+
+
+ "v = new Date(y < 100 ? 100 : y, m, d, h, i, s, ms).add(Date.YEAR, y < 100 ? y - 100 : 0);",
+ "}",
+ "}",
+ "}",
+
+ "if(v){",
+
+ "if(zz != null){",
+
+ "v = v.add(Date.SECOND, -v.getTimezoneOffset() * 60 - zz);",
+ "}else if(o){",
+
+ "v = v.add(Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));",
+ "}",
+ "}",
+
+ "return v;"
+ ].join('\n');
+
+ return function(format) {
+ var regexNum = Date.parseRegexes.length,
+ currentGroup = 1,
+ calc = [],
+ regex = [],
+ special = false,
+ ch = "",
+ i = 0,
+ obj,
+ last;
+
+ for (; i < format.length; ++i) {
+ ch = format.charAt(i);
+ if (!special && ch == "\\") {
+ special = true;
+ } else if (special) {
+ special = false;
+ regex.push(String.escape(ch));
+ } else {
+ obj = $f(ch, currentGroup);
+ currentGroup += obj.g;
+ regex.push(obj.s);
+ if (obj.g && obj.c) {
+ if (obj.calcLast) {
+ last = obj.c;
+ } else {
+ calc.push(obj.c);
+ }
+ }
+ }
+ }
+
+ if (last) {
+ calc.push(last);
+ }
+
+ Date.parseRegexes[regexNum] = new RegExp("^" + regex.join('') + "$", 'i');
+ Date.parseFunctions[format] = new Function("input", "strict", xf(code, regexNum, calc.join('')));
+ };
+ }(),
+
+
+ parseCodes : {
+
+ d: {
+ g:1,
+ c:"d = parseInt(results[{0}], 10);\n",
+ s:"(\\d{2})"
+ },
+ j: {
+ g:1,
+ c:"d = parseInt(results[{0}], 10);\n",
+ s:"(\\d{1,2})"
+ },
+ D: function() {
+ for (var a = [], i = 0; i < 7; a.push(Date.getShortDayName(i)), ++i);
+ return {
+ g:0,
+ c:null,
+ s:"(?:" + a.join("|") +")"
+ };
+ },
+ l: function() {
+ return {
+ g:0,
+ c:null,
+ s:"(?:" + Date.dayNames.join("|") + ")"
+ };
+ },
+ N: {
+ g:0,
+ c:null,
+ s:"[1-7]"
+ },
+ S: {
+ g:0,
+ c:null,
+ s:"(?:st|nd|rd|th)"
+ },
+ w: {
+ g:0,
+ c:null,
+ s:"[0-6]"
+ },
+ z: {
+ g:1,
+ c:"z = parseInt(results[{0}], 10);\n",
+ s:"(\\d{1,3})"
+ },
+ W: {
+ g:0,
+ c:null,
+ s:"(?:\\d{2})"
+ },
+ F: function() {
+ return {
+ g:1,
+ c:"m = parseInt(Date.getMonthNumber(results[{0}]), 10);\n",
+ s:"(" + Date.monthNames.join("|") + ")"
+ };
+ },
+ M: function() {
+ for (var a = [], i = 0; i < 12; a.push(Date.getShortMonthName(i)), ++i);
+ return Ext.applyIf({
+ s:"(" + a.join("|") + ")"
+ }, $f("F"));
+ },
+ m: {
+ g:1,
+ c:"m = parseInt(results[{0}], 10) - 1;\n",
+ s:"(\\d{2})"
+ },
+ n: {
+ g:1,
+ c:"m = parseInt(results[{0}], 10) - 1;\n",
+ s:"(\\d{1,2})"
+ },
+ t: {
+ g:0,
+ c:null,
+ s:"(?:\\d{2})"
+ },
+ L: {
+ g:0,
+ c:null,
+ s:"(?:1|0)"
+ },
+ o: function() {
+ return $f("Y");
+ },
+ Y: {
+ g:1,
+ c:"y = parseInt(results[{0}], 10);\n",
+ s:"(\\d{4})"
+ },
+ y: {
+ g:1,
+ c:"var ty = parseInt(results[{0}], 10);\n"
+ + "y = ty > Date.y2kYear ? 1900 + ty : 2000 + ty;\n",
+ s:"(\\d{1,2})"
+ },
+
+ a: function(){
+ return $f("A");
+ },
+ A: {
+
+ calcLast: true,
+ g:1,
+ c:"if (/(am)/i.test(results[{0}])) {\n"
+ + "if (!h || h == 12) { h = 0; }\n"
+ + "} else { if (!h || h < 12) { h = (h || 0) + 12; }}",
+ s:"(AM|PM|am|pm)"
+ },
+ g: function() {
+ return $f("G");
+ },
+ G: {
+ g:1,
+ c:"h = parseInt(results[{0}], 10);\n",
+ s:"(\\d{1,2})"
+ },
+ h: function() {
+ return $f("H");
+ },
+ H: {
+ g:1,
+ c:"h = parseInt(results[{0}], 10);\n",
+ s:"(\\d{2})"
+ },
+ i: {
+ g:1,
+ c:"i = parseInt(results[{0}], 10);\n",
+ s:"(\\d{2})"
+ },
+ s: {
+ g:1,
+ c:"s = parseInt(results[{0}], 10);\n",
+ s:"(\\d{2})"
+ },
+ u: {
+ g:1,
+ c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",
+ s:"(\\d+)"
+ },
+ O: {
+ g:1,
+ c:[
+ "o = results[{0}];",
+ "var sn = o.substring(0,1),",
+ "hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),",
+ "mn = o.substring(3,5) % 60;",
+ "o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + String.leftPad(hr, 2, '0') + String.leftPad(mn, 2, '0')) : null;\n"
+ ].join("\n"),
+ s: "([+\-]\\d{4})"
+ },
+ P: {
+ g:1,
+ c:[
+ "o = results[{0}];",
+ "var sn = o.substring(0,1),",
+ "hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),",
+ "mn = o.substring(4,6) % 60;",
+ "o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + String.leftPad(hr, 2, '0') + String.leftPad(mn, 2, '0')) : null;\n"
+ ].join("\n"),
+ s: "([+\-]\\d{2}:\\d{2})"
+ },
+ T: {
+ g:0,
+ c:null,
+ s:"[A-Z]{1,4}"
+ },
+ Z: {
+ g:1,
+ c:"zz = results[{0}] * 1;\n"
+ + "zz = (-43200 <= zz && zz <= 50400)? zz : null;\n",
+ s:"([+\-]?\\d{1,5})"
+ },
+ c: function() {
+ var calc = [],
+ arr = [
+ $f("Y", 1),
+ $f("m", 2),
+ $f("d", 3),
+ $f("h", 4),
+ $f("i", 5),
+ $f("s", 6),
+ {c:"ms = results[7] || '0'; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n"},
+ {c:[
+ "if(results[8]) {",
+ "if(results[8] == 'Z'){",
+ "zz = 0;",
+ "}else if (results[8].indexOf(':') > -1){",
+ $f("P", 8).c,
+ "}else{",
+ $f("O", 8).c,
+ "}",
+ "}"
+ ].join('\n')}
+ ];
+
+ for (var i = 0, l = arr.length; i < l; ++i) {
+ calc.push(arr[i].c);
+ }
+
+ return {
+ g:1,
+ c:calc.join(""),
+ s:[
+ arr[0].s,
+ "(?:", "-", arr[1].s,
+ "(?:", "-", arr[2].s,
+ "(?:",
+ "(?:T| )?",
+ arr[3].s, ":", arr[4].s,
+ "(?::", arr[5].s, ")?",
+ "(?:(?:\\.|,)(\\d+))?",
+ "(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?",
+ ")?",
+ ")?",
+ ")?"
+ ].join("")
+ };
+ },
+ U: {
+ g:1,
+ c:"u = parseInt(results[{0}], 10);\n",
+ s:"(-?\\d+)"
+ }
+ }
+});
+
+}());
+
+Ext.apply(Date.prototype, {
+
+ dateFormat : function(format) {
+ if (Date.formatFunctions[format] == null) {
+ Date.createFormat(format);
+ }
+ return Date.formatFunctions[format].call(this);
+ },
+
+
+ getTimezone : function() {
+
+
+
+
+
+
+
+
+
+
+
+
+ return this.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/, "$1$2").replace(/[^A-Z]/g, "");
+ },
+
+
+ getGMTOffset : function(colon) {
+ return (this.getTimezoneOffset() > 0 ? "-" : "+")
+ + String.leftPad(Math.floor(Math.abs(this.getTimezoneOffset()) / 60), 2, "0")
+ + (colon ? ":" : "")
+ + String.leftPad(Math.abs(this.getTimezoneOffset() % 60), 2, "0");
+ },
+
+
+ getDayOfYear: function() {
+ var num = 0,
+ d = this.clone(),
+ m = this.getMonth(),
+ i;
+
+ for (i = 0, d.setDate(1), d.setMonth(0); i < m; d.setMonth(++i)) {
+ num += d.getDaysInMonth();
+ }
+ return num + this.getDate() - 1;
+ },
+
+
+ getWeekOfYear : function() {
+
+ var ms1d = 864e5,
+ ms7d = 7 * ms1d;
+
+ return function() {
+ var DC3 = Date.UTC(this.getFullYear(), this.getMonth(), this.getDate() + 3) / ms1d,
+ AWN = Math.floor(DC3 / 7),
+ Wyr = new Date(AWN * ms7d).getUTCFullYear();
+
+ return AWN - Math.floor(Date.UTC(Wyr, 0, 7) / ms7d) + 1;
+ };
+ }(),
+
+
+ isLeapYear : function() {
+ var year = this.getFullYear();
+ return !!((year & 3) == 0 && (year % 100 || (year % 400 == 0 && year)));
+ },
+
+
+ getFirstDayOfMonth : function() {
+ var day = (this.getDay() - (this.getDate() - 1)) % 7;
+ return (day < 0) ? (day + 7) : day;
+ },
+
+
+ getLastDayOfMonth : function() {
+ return this.getLastDateOfMonth().getDay();
+ },
+
+
+
+ getFirstDateOfMonth : function() {
+ return new Date(this.getFullYear(), this.getMonth(), 1);
+ },
+
+
+ getLastDateOfMonth : function() {
+ return new Date(this.getFullYear(), this.getMonth(), this.getDaysInMonth());
+ },
+
+
+ getDaysInMonth: function() {
+ var daysInMonth = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
+
+ return function() {
+ var m = this.getMonth();
+
+ return m == 1 && this.isLeapYear() ? 29 : daysInMonth[m];
+ };
+ }(),
+
+
+ getSuffix : function() {
+ switch (this.getDate()) {
+ case 1:
+ case 21:
+ case 31:
+ return "st";
+ case 2:
+ case 22:
+ return "nd";
+ case 3:
+ case 23:
+ return "rd";
+ default:
+ return "th";
+ }
+ },
+
+
+ clone : function() {
+ return new Date(this.getTime());
+ },
+
+
+ isDST : function() {
+
+
+ return new Date(this.getFullYear(), 0, 1).getTimezoneOffset() != this.getTimezoneOffset();
+ },
+
+
+ clearTime : function(clone) {
+ if (clone) {
+ return this.clone().clearTime();
+ }
+
+
+ var d = this.getDate();
+
+
+ this.setHours(0);
+ this.setMinutes(0);
+ this.setSeconds(0);
+ this.setMilliseconds(0);
+
+ if (this.getDate() != d) {
+
+
+
+
+ for (var hr = 1, c = this.add(Date.HOUR, hr); c.getDate() != d; hr++, c = this.add(Date.HOUR, hr));
+
+ this.setDate(d);
+ this.setHours(c.getHours());
+ }
+
+ return this;
+ },
+
+
+ add : function(interval, value) {
+ var d = this.clone();
+ if (!interval || value === 0) return d;
+
+ switch(interval.toLowerCase()) {
+ case Date.MILLI:
+ d.setMilliseconds(this.getMilliseconds() + value);
+ break;
+ case Date.SECOND:
+ d.setSeconds(this.getSeconds() + value);
+ break;
+ case Date.MINUTE:
+ d.setMinutes(this.getMinutes() + value);
+ break;
+ case Date.HOUR:
+ d.setHours(this.getHours() + value);
+ break;
+ case Date.DAY:
+ d.setDate(this.getDate() + value);
+ break;
+ case Date.MONTH:
+ var day = this.getDate();
+ if (day > 28) {
+ day = Math.min(day, this.getFirstDateOfMonth().add('mo', value).getLastDateOfMonth().getDate());
+ }
+ d.setDate(day);
+ d.setMonth(this.getMonth() + value);
+ break;
+ case Date.YEAR:
+ d.setFullYear(this.getFullYear() + value);
+ break;
+ }
+ return d;
+ },
+
+
+ between : function(start, end) {
+ var t = this.getTime();
+ return start.getTime() <= t && t <= end.getTime();
+ }
+});
+
+
+
+Date.prototype.format = Date.prototype.dateFormat;
+
+
+
+if (Ext.isSafari && (navigator.userAgent.match(/WebKit\/(\d+)/)[1] || NaN) < 420) {
+ Ext.apply(Date.prototype, {
+ _xMonth : Date.prototype.setMonth,
+ _xDate : Date.prototype.setDate,
+
+
+
+ setMonth : function(num) {
+ if (num <= -1) {
+ var n = Math.ceil(-num),
+ back_year = Math.ceil(n / 12),
+ month = (n % 12) ? 12 - n % 12 : 0;
+
+ this.setFullYear(this.getFullYear() - back_year);
+
+ return this._xMonth(month);
+ } else {
+ return this._xMonth(num);
+ }
+ },
+
+
+
+
+ setDate : function(d) {
+
+
+ return this.setTime(this.getTime() - (this.getDate() - d) * 864e5);
+ }
+ });
+}
+
+
+
+
+
+Ext.util.MixedCollection = function(allowFunctions, keyFn){
+ this.items = [];
+ this.map = {};
+ this.keys = [];
+ this.length = 0;
+ this.addEvents(
+
+ 'clear',
+
+ 'add',
+
+ 'replace',
+
+ 'remove',
+ 'sort'
+ );
+ this.allowFunctions = allowFunctions === true;
+ if(keyFn){
+ this.getKey = keyFn;
+ }
+ Ext.util.MixedCollection.superclass.constructor.call(this);
+};
+
+Ext.extend(Ext.util.MixedCollection, Ext.util.Observable, {
+
+
+ allowFunctions : false,
+
+
+ add : function(key, o){
+ if(arguments.length == 1){
+ o = arguments[0];
+ key = this.getKey(o);
+ }
+ if(typeof key != 'undefined' && key !== null){
+ var old = this.map[key];
+ if(typeof old != 'undefined'){
+ return this.replace(key, o);
+ }
+ this.map[key] = o;
+ }
+ this.length++;
+ this.items.push(o);
+ this.keys.push(key);
+ this.fireEvent('add', this.length-1, o, key);
+ return o;
+ },
+
+
+ getKey : function(o){
+ return o.id;
+ },
+
+
+ replace : function(key, o){
+ if(arguments.length == 1){
+ o = arguments[0];
+ key = this.getKey(o);
+ }
+ var old = this.map[key];
+ if(typeof key == 'undefined' || key === null || typeof old == 'undefined'){
+ return this.add(key, o);
+ }
+ var index = this.indexOfKey(key);
+ this.items[index] = o;
+ this.map[key] = o;
+ this.fireEvent('replace', key, old, o);
+ return o;
+ },
+
+
+ addAll : function(objs){
+ if(arguments.length > 1 || Ext.isArray(objs)){
+ var args = arguments.length > 1 ? arguments : objs;
+ for(var i = 0, len = args.length; i < len; i++){
+ this.add(args[i]);
+ }
+ }else{
+ for(var key in objs){
+ if(this.allowFunctions || typeof objs[key] != 'function'){
+ this.add(key, objs[key]);
+ }
+ }
+ }
+ },
+
+
+ each : function(fn, scope){
+ var items = [].concat(this.items);
+ for(var i = 0, len = items.length; i < len; i++){
+ if(fn.call(scope || items[i], items[i], i, len) === false){
+ break;
+ }
+ }
+ },
+
+
+ eachKey : function(fn, scope){
+ for(var i = 0, len = this.keys.length; i < len; i++){
+ fn.call(scope || window, this.keys[i], this.items[i], i, len);
+ }
+ },
+
+
+ find : function(fn, scope){
+ for(var i = 0, len = this.items.length; i < len; i++){
+ if(fn.call(scope || window, this.items[i], this.keys[i])){
+ return this.items[i];
+ }
+ }
+ return null;
+ },
+
+
+ insert : function(index, key, o){
+ if(arguments.length == 2){
+ o = arguments[1];
+ key = this.getKey(o);
+ }
+ if(this.containsKey(key)){
+ this.suspendEvents();
+ this.removeKey(key);
+ this.resumeEvents();
+ }
+ if(index >= this.length){
+ return this.add(key, o);
+ }
+ this.length++;
+ this.items.splice(index, 0, o);
+ if(typeof key != 'undefined' && key !== null){
+ this.map[key] = o;
+ }
+ this.keys.splice(index, 0, key);
+ this.fireEvent('add', index, o, key);
+ return o;
+ },
+
+
+ remove : function(o){
+ return this.removeAt(this.indexOf(o));
+ },
+
+
+ removeAt : function(index){
+ if(index < this.length && index >= 0){
+ this.length--;
+ var o = this.items[index];
+ this.items.splice(index, 1);
+ var key = this.keys[index];
+ if(typeof key != 'undefined'){
+ delete this.map[key];
+ }
+ this.keys.splice(index, 1);
+ this.fireEvent('remove', o, key);
+ return o;
+ }
+ return false;
+ },
+
+
+ removeKey : function(key){
+ return this.removeAt(this.indexOfKey(key));
+ },
+
+
+ getCount : function(){
+ return this.length;
+ },
+
+
+ indexOf : function(o){
+ return this.items.indexOf(o);
+ },
+
+
+ indexOfKey : function(key){
+ return this.keys.indexOf(key);
+ },
+
+
+ item : function(key){
+ var mk = this.map[key],
+ item = mk !== undefined ? mk : (typeof key == 'number') ? this.items[key] : undefined;
+ return typeof item != 'function' || this.allowFunctions ? item : null;
+ },
+
+
+ itemAt : function(index){
+ return this.items[index];
+ },
+
+
+ key : function(key){
+ return this.map[key];
+ },
+
+
+ contains : function(o){
+ return this.indexOf(o) != -1;
+ },
+
+
+ containsKey : function(key){
+ return typeof this.map[key] != 'undefined';
+ },
+
+
+ clear : function(){
+ this.length = 0;
+ this.items = [];
+ this.keys = [];
+ this.map = {};
+ this.fireEvent('clear');
+ },
+
+
+ first : function(){
+ return this.items[0];
+ },
+
+
+ last : function(){
+ return this.items[this.length-1];
+ },
+
+
+ _sort : function(property, dir, fn){
+ var i, len,
+ dsc = String(dir).toUpperCase() == 'DESC' ? -1 : 1,
+
+
+ c = [],
+ keys = this.keys,
+ items = this.items;
+
+
+ fn = fn || function(a, b) {
+ return a - b;
+ };
+
+
+ for(i = 0, len = items.length; i < len; i++){
+ c[c.length] = {
+ key : keys[i],
+ value: items[i],
+ index: i
+ };
+ }
+
+
+ c.sort(function(a, b){
+ var v = fn(a[property], b[property]) * dsc;
+ if(v === 0){
+ v = (a.index < b.index ? -1 : 1);
+ }
+ return v;
+ });
+
+
+ for(i = 0, len = c.length; i < len; i++){
+ items[i] = c[i].value;
+ keys[i] = c[i].key;
+ }
+
+ this.fireEvent('sort', this);
+ },
+
+
+ sort : function(dir, fn){
+ this._sort('value', dir, fn);
+ },
+
+
+ reorder: function(mapping) {
+ this.suspendEvents();
+
+ var items = this.items,
+ index = 0,
+ length = items.length,
+ order = [],
+ remaining = [],
+ oldIndex;
+
+
+ for (oldIndex in mapping) {
+ order[mapping[oldIndex]] = items[oldIndex];
+ }
+
+ for (index = 0; index < length; index++) {
+ if (mapping[index] == undefined) {
+ remaining.push(items[index]);
+ }
+ }
+
+ for (index = 0; index < length; index++) {
+ if (order[index] == undefined) {
+ order[index] = remaining.shift();
+ }
+ }
+
+ this.clear();
+ this.addAll(order);
+
+ this.resumeEvents();
+ this.fireEvent('sort', this);
+ },
+
+
+ keySort : function(dir, fn){
+ this._sort('key', dir, fn || function(a, b){
+ var v1 = String(a).toUpperCase(), v2 = String(b).toUpperCase();
+ return v1 > v2 ? 1 : (v1 < v2 ? -1 : 0);
+ });
+ },
+
+
+ getRange : function(start, end){
+ var items = this.items;
+ if(items.length < 1){
+ return [];
+ }
+ start = start || 0;
+ end = Math.min(typeof end == 'undefined' ? this.length-1 : end, this.length-1);
+ var i, r = [];
+ if(start <= end){
+ for(i = start; i <= end; i++) {
+ r[r.length] = items[i];
+ }
+ }else{
+ for(i = start; i >= end; i--) {
+ r[r.length] = items[i];
+ }
+ }
+ return r;
+ },
+
+
+ filter : function(property, value, anyMatch, caseSensitive){
+ if(Ext.isEmpty(value, false)){
+ return this.clone();
+ }
+ value = this.createValueMatcher(value, anyMatch, caseSensitive);
+ return this.filterBy(function(o){
+ return o && value.test(o[property]);
+ });
+ },
+
+
+ filterBy : function(fn, scope){
+ var r = new Ext.util.MixedCollection();
+ r.getKey = this.getKey;
+ var k = this.keys, it = this.items;
+ for(var i = 0, len = it.length; i < len; i++){
+ if(fn.call(scope||this, it[i], k[i])){
+ r.add(k[i], it[i]);
+ }
+ }
+ return r;
+ },
+
+
+ findIndex : function(property, value, start, anyMatch, caseSensitive){
+ if(Ext.isEmpty(value, false)){
+ return -1;
+ }
+ value = this.createValueMatcher(value, anyMatch, caseSensitive);
+ return this.findIndexBy(function(o){
+ return o && value.test(o[property]);
+ }, null, start);
+ },
+
+
+ findIndexBy : function(fn, scope, start){
+ var k = this.keys, it = this.items;
+ for(var i = (start||0), len = it.length; i < len; i++){
+ if(fn.call(scope||this, it[i], k[i])){
+ return i;
+ }
+ }
+ return -1;
+ },
+
+
+ createValueMatcher : function(value, anyMatch, caseSensitive, exactMatch) {
+ if (!value.exec) {
+ var er = Ext.escapeRe;
+ value = String(value);
+
+ if (anyMatch === true) {
+ value = er(value);
+ } else {
+ value = '^' + er(value);
+ if (exactMatch === true) {
+ value += '$';
+ }
+ }
+ value = new RegExp(value, caseSensitive ? '' : 'i');
+ }
+ return value;
+ },
+
+
+ clone : function(){
+ var r = new Ext.util.MixedCollection();
+ var k = this.keys, it = this.items;
+ for(var i = 0, len = it.length; i < len; i++){
+ r.add(k[i], it[i]);
+ }
+ r.getKey = this.getKey;
+ return r;
+ }
+});
+
+Ext.util.MixedCollection.prototype.get = Ext.util.MixedCollection.prototype.item;
+
+Ext.AbstractManager = Ext.extend(Object, {
+ typeName: 'type',
+
+ constructor: function(config) {
+ Ext.apply(this, config || {});
+
+
+ this.all = new Ext.util.MixedCollection();
+
+ this.types = {};
+ },
+
+
+ get : function(id){
+ return this.all.get(id);
+ },
+
+
+ register: function(item) {
+ this.all.add(item);
+ },
+
+
+ unregister: function(item) {
+ this.all.remove(item);
+ },
+
+
+ registerType : function(type, cls){
+ this.types[type] = cls;
+ cls[this.typeName] = type;
+ },
+
+
+ isRegistered : function(type){
+ return this.types[type] !== undefined;
+ },
+
+
+ create: function(config, defaultType) {
+ var type = config[this.typeName] || config.type || defaultType,
+ Constructor = this.types[type];
+
+ if (Constructor == undefined) {
+ throw new Error(String.format("The '{0}' type has not been registered with this manager", type));
+ }
+
+ return new Constructor(config);
+ },
+
+
+ onAvailable : function(id, fn, scope){
+ var all = this.all;
+
+ all.on("add", function(index, o){
+ if (o.id == id) {
+ fn.call(scope || o, o);
+ all.un("add", fn, scope);
+ }
+ });
+ }
+});
+Ext.util.Format = function() {
+ var trimRe = /^\s+|\s+$/g,
+ stripTagsRE = /<\/?[^>]+>/gi,
+ stripScriptsRe = /(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,
+ nl2brRe = /\r?\n/g;
+
+ return {
+
+ ellipsis : function(value, len, word) {
+ if (value && value.length > len) {
+ if (word) {
+ var vs = value.substr(0, len - 2),
+ index = Math.max(vs.lastIndexOf(' '), vs.lastIndexOf('.'), vs.lastIndexOf('!'), vs.lastIndexOf('?'));
+ if (index == -1 || index < (len - 15)) {
+ return value.substr(0, len - 3) + "...";
+ } else {
+ return vs.substr(0, index) + "...";
+ }
+ } else {
+ return value.substr(0, len - 3) + "...";
+ }
+ }
+ return value;
+ },
+
+
+ undef : function(value) {
+ return value !== undefined ? value : "";
+ },
+
+
+ defaultValue : function(value, defaultValue) {
+ if (!defaultValue && defaultValue !== 0) {
+ defaultValue = '';
+ }
+ return value !== undefined && value !== '' ? value : defaultValue;
+ },
+
+
+ htmlEncode : function(value) {
+ return !value ? value : String(value).replace(/&/g, "&amp;").replace(/>/g, "&gt;").replace(/</g, "&lt;").replace(/"/g, "&quot;");
+ },
+
+
+ htmlDecode : function(value) {
+ return !value ? value : String(value).replace(/&gt;/g, ">").replace(/&lt;/g, "<").replace(/&quot;/g, '"').replace(/&amp;/g, "&");
+ },
+
+
+ trim : function(value) {
+ return String(value).replace(trimRe, "");
+ },
+
+
+ substr : function(value, start, length) {
+ return String(value).substr(start, length);
+ },
+
+
+ lowercase : function(value) {
+ return String(value).toLowerCase();
+ },
+
+
+ uppercase : function(value) {
+ return String(value).toUpperCase();
+ },
+
+
+ capitalize : function(value) {
+ return !value ? value : value.charAt(0).toUpperCase() + value.substr(1).toLowerCase();
+ },
+
+
+ call : function(value, fn) {
+ if (arguments.length > 2) {
+ var args = Array.prototype.slice.call(arguments, 2);
+ args.unshift(value);
+ return eval(fn).apply(window, args);
+ } else {
+ return eval(fn).call(window, value);
+ }
+ },
+
+
+ usMoney : function(v) {
+ v = (Math.round((v-0)*100))/100;
+ v = (v == Math.floor(v)) ? v + ".00" : ((v*10 == Math.floor(v*10)) ? v + "0" : v);
+ v = String(v);
+ var ps = v.split('.'),
+ whole = ps[0],
+ sub = ps[1] ? '.'+ ps[1] : '.00',
+ r = /(\d+)(\d{3})/;
+ while (r.test(whole)) {
+ whole = whole.replace(r, '$1' + ',' + '$2');
+ }
+ v = whole + sub;
+ if (v.charAt(0) == '-') {
+ return '-$' + v.substr(1);
+ }
+ return "$" + v;
+ },
+
+
+ date : function(v, format) {
+ if (!v) {
+ return "";
+ }
+ if (!Ext.isDate(v)) {
+ v = new Date(Date.parse(v));
+ }
+ return v.dateFormat(format || "m/d/Y");
+ },
+
+
+ dateRenderer : function(format) {
+ return function(v) {
+ return Ext.util.Format.date(v, format);
+ };
+ },
+
+
+ stripTags : function(v) {
+ return !v ? v : String(v).replace(stripTagsRE, "");
+ },
+
+
+ stripScripts : function(v) {
+ return !v ? v : String(v).replace(stripScriptsRe, "");
+ },
+
+
+ fileSize : function(size) {
+ if (size < 1024) {
+ return size + " bytes";
+ } else if (size < 1048576) {
+ return (Math.round(((size*10) / 1024))/10) + " KB";
+ } else {
+ return (Math.round(((size*10) / 1048576))/10) + " MB";
+ }
+ },
+
+
+ math : function(){
+ var fns = {};
+
+ return function(v, a){
+ if (!fns[a]) {
+ fns[a] = new Function('v', 'return v ' + a + ';');
+ }
+ return fns[a](v);
+ };
+ }(),
+
+
+ round : function(value, precision) {
+ var result = Number(value);
+ if (typeof precision == 'number') {
+ precision = Math.pow(10, precision);
+ result = Math.round(value * precision) / precision;
+ }
+ return result;
+ },
+
+
+ number: function(v, format) {
+ if (!format) {
+ return v;
+ }
+ v = Ext.num(v, NaN);
+ if (isNaN(v)) {
+ return '';
+ }
+ var comma = ',',
+ dec = '.',
+ i18n = false,
+ neg = v < 0;
+
+ v = Math.abs(v);
+ if (format.substr(format.length - 2) == '/i') {
+ format = format.substr(0, format.length - 2);
+ i18n = true;
+ comma = '.';
+ dec = ',';
+ }
+
+ var hasComma = format.indexOf(comma) != -1,
+ psplit = (i18n ? format.replace(/[^\d\,]/g, '') : format.replace(/[^\d\.]/g, '')).split(dec);
+
+ if (1 < psplit.length) {
+ v = v.toFixed(psplit[1].length);
+ } else if(2 < psplit.length) {
+ throw ('NumberFormatException: invalid format, formats should have no more than 1 period: ' + format);
+ } else {
+ v = v.toFixed(0);
+ }
+
+ var fnum = v.toString();
+
+ psplit = fnum.split('.');
+
+ if (hasComma) {
+ var cnum = psplit[0],
+ parr = [],
+ j = cnum.length,
+ m = Math.floor(j / 3),
+ n = cnum.length % 3 || 3,
+ i;
+
+ for (i = 0; i < j; i += n) {
+ if (i != 0) {
+ n = 3;
+ }
+
+ parr[parr.length] = cnum.substr(i, n);
+ m -= 1;
+ }
+ fnum = parr.join(comma);
+ if (psplit[1]) {
+ fnum += dec + psplit[1];
+ }
+ } else {
+ if (psplit[1]) {
+ fnum = psplit[0] + dec + psplit[1];
+ }
+ }
+
+ return (neg ? '-' : '') + format.replace(/[\d,?\.?]+/, fnum);
+ },
+
+
+ numberRenderer : function(format) {
+ return function(v) {
+ return Ext.util.Format.number(v, format);
+ };
+ },
+
+
+ plural : function(v, s, p) {
+ return v +' ' + (v == 1 ? s : (p ? p : s+'s'));
+ },
+
+
+ nl2br : function(v) {
+ return Ext.isEmpty(v) ? '' : v.replace(nl2brRe, '<br/>');
+ }
+ };
+}();
+
+Ext.XTemplate = function(){
+ Ext.XTemplate.superclass.constructor.apply(this, arguments);
+
+ var me = this,
+ s = me.html,
+ re = /<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,
+ nameRe = /^<tpl\b[^>]*?for="(.*?)"/,
+ ifRe = /^<tpl\b[^>]*?if="(.*?)"/,
+ execRe = /^<tpl\b[^>]*?exec="(.*?)"/,
+ m,
+ id = 0,
+ tpls = [],
+ VALUES = 'values',
+ PARENT = 'parent',
+ XINDEX = 'xindex',
+ XCOUNT = 'xcount',
+ RETURN = 'return ',
+ WITHVALUES = 'with(values){ ';
+
+ s = ['<tpl>', s, '</tpl>'].join('');
+
+ while((m = s.match(re))){
+ var m2 = m[0].match(nameRe),
+ m3 = m[0].match(ifRe),
+ m4 = m[0].match(execRe),
+ exp = null,
+ fn = null,
+ exec = null,
+ name = m2 && m2[1] ? m2[1] : '';
+
+ if (m3) {
+ exp = m3 && m3[1] ? m3[1] : null;
+ if(exp){
+ fn = new Function(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES + RETURN +(Ext.util.Format.htmlDecode(exp))+'; }');
+ }
+ }
+ if (m4) {
+ exp = m4 && m4[1] ? m4[1] : null;
+ if(exp){
+ exec = new Function(VALUES, PARENT, XINDEX, XCOUNT, WITHVALUES +(Ext.util.Format.htmlDecode(exp))+'; }');
+ }
+ }
+ if(name){
+ switch(name){
+ case '.': name = new Function(VALUES, PARENT, WITHVALUES + RETURN + VALUES + '; }'); break;
+ case '..': name = new Function(VALUES, PARENT, WITHVALUES + RETURN + PARENT + '; }'); break;
+ default: name = new Function(VALUES, PARENT, WITHVALUES + RETURN + name + '; }');
+ }
+ }
+ tpls.push({
+ id: id,
+ target: name,
+ exec: exec,
+ test: fn,
+ body: m[1]||''
+ });
+ s = s.replace(m[0], '{xtpl'+ id + '}');
+ ++id;
+ }
+ for(var i = tpls.length-1; i >= 0; --i){
+ me.compileTpl(tpls[i]);
+ }
+ me.master = tpls[tpls.length-1];
+ me.tpls = tpls;
+};
+Ext.extend(Ext.XTemplate, Ext.Template, {
+
+ re : /\{([\w\-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\\]\s?[\d\.\+\-\*\\\(\)]+)?\}/g,
+
+ codeRe : /\{\[((?:\\\]|.|\n)*?)\]\}/g,
+
+
+ applySubTemplate : function(id, values, parent, xindex, xcount){
+ var me = this,
+ len,
+ t = me.tpls[id],
+ vs,
+ buf = [];
+ if ((t.test && !t.test.call(me, values, parent, xindex, xcount)) ||
+ (t.exec && t.exec.call(me, values, parent, xindex, xcount))) {
+ return '';
+ }
+ vs = t.target ? t.target.call(me, values, parent) : values;
+ len = vs.length;
+ parent = t.target ? values : parent;
+ if(t.target && Ext.isArray(vs)){
+ for(var i = 0, len = vs.length; i < len; i++){
+ buf[buf.length] = t.compiled.call(me, vs[i], parent, i+1, len);
+ }
+ return buf.join('');
+ }
+ return t.compiled.call(me, vs, parent, xindex, xcount);
+ },
+
+
+ compileTpl : function(tpl){
+ var fm = Ext.util.Format,
+ useF = this.disableFormats !== true,
+ sep = Ext.isGecko ? "+" : ",",
+ body;
+
+ function fn(m, name, format, args, math){
+ if(name.substr(0, 4) == 'xtpl'){
+ return "'"+ sep +'this.applySubTemplate('+name.substr(4)+', values, parent, xindex, xcount)'+sep+"'";
+ }
+ var v;
+ if(name === '.'){
+ v = 'values';
+ }else if(name === '#'){
+ v = 'xindex';
+ }else if(name.indexOf('.') != -1){
+ v = name;
+ }else{
+ v = "values['" + name + "']";
+ }
+ if(math){
+ v = '(' + v + math + ')';
+ }
+ if (format && useF) {
+ args = args ? ',' + args : "";
+ if(format.substr(0, 5) != "this."){
+ format = "fm." + format + '(';
+ }else{
+ format = 'this.call("'+ format.substr(5) + '", ';
+ args = ", values";
+ }
+ } else {
+ args= ''; format = "("+v+" === undefined ? '' : ";
+ }
+ return "'"+ sep + format + v + args + ")"+sep+"'";
+ }
+
+ function codeFn(m, code){
+
+ return "'" + sep + '(' + code.replace(/\\'/g, "'") + ')' + sep + "'";
+ }
+
+
+ if(Ext.isGecko){
+ body = "tpl.compiled = function(values, parent, xindex, xcount){ return '" +
+ tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn).replace(this.codeRe, codeFn) +
+ "';};";
+ }else{
+ body = ["tpl.compiled = function(values, parent, xindex, xcount){ return ['"];
+ body.push(tpl.body.replace(/(\r\n|\n)/g, '\\n').replace(/'/g, "\\'").replace(this.re, fn).replace(this.codeRe, codeFn));
+ body.push("'].join('');};");
+ body = body.join('');
+ }
+ eval(body);
+ return this;
+ },
+
+
+ applyTemplate : function(values){
+ return this.master.compiled.call(this, values, {}, 1, 1);
+ },
+
+
+ compile : function(){return this;}
+
+
+
+
+
+});
+
+Ext.XTemplate.prototype.apply = Ext.XTemplate.prototype.applyTemplate;
+
+
+Ext.XTemplate.from = function(el){
+ el = Ext.getDom(el);
+ return new Ext.XTemplate(el.value || el.innerHTML);
+};
+
+Ext.util.CSS = function(){
+ var rules = null;
+ var doc = document;
+
+ var camelRe = /(-[a-z])/gi;
+ var camelFn = function(m, a){ return a.charAt(1).toUpperCase(); };
+
+ return {
+
+ createStyleSheet : function(cssText, id){
+ var ss;
+ var head = doc.getElementsByTagName("head")[0];
+ var rules = doc.createElement("style");
+ rules.setAttribute("type", "text/css");
+ if(id){
+ rules.setAttribute("id", id);
+ }
+ if(Ext.isIE){
+ head.appendChild(rules);
+ ss = rules.styleSheet;
+ ss.cssText = cssText;
+ }else{
+ try{
+ rules.appendChild(doc.createTextNode(cssText));
+ }catch(e){
+ rules.cssText = cssText;
+ }
+ head.appendChild(rules);
+ ss = rules.styleSheet ? rules.styleSheet : (rules.sheet || doc.styleSheets[doc.styleSheets.length-1]);
+ }
+ this.cacheStyleSheet(ss);
+ return ss;
+ },
+
+
+ removeStyleSheet : function(id){
+ var existing = doc.getElementById(id);
+ if(existing){
+ existing.parentNode.removeChild(existing);
+ }
+ },
+
+
+ swapStyleSheet : function(id, url){
+ this.removeStyleSheet(id);
+ var ss = doc.createElement("link");
+ ss.setAttribute("rel", "stylesheet");
+ ss.setAttribute("type", "text/css");
+ ss.setAttribute("id", id);
+ ss.setAttribute("href", url);
+ doc.getElementsByTagName("head")[0].appendChild(ss);
+ },
+
+
+ refreshCache : function(){
+ return this.getRules(true);
+ },
+
+
+ cacheStyleSheet : function(ss){
+ if(!rules){
+ rules = {};
+ }
+ try{
+ var ssRules = ss.cssRules || ss.rules;
+ for(var j = ssRules.length-1; j >= 0; --j){
+ rules[ssRules[j].selectorText.toLowerCase()] = ssRules[j];
+ }
+ }catch(e){}
+ },
+
+
+ getRules : function(refreshCache){
+ if(rules === null || refreshCache){
+ rules = {};
+ var ds = doc.styleSheets;
+ for(var i =0, len = ds.length; i < len; i++){
+ try{
+ this.cacheStyleSheet(ds[i]);
+ }catch(e){}
+ }
+ }
+ return rules;
+ },
+
+
+ getRule : function(selector, refreshCache){
+ var rs = this.getRules(refreshCache);
+ if(!Ext.isArray(selector)){
+ return rs[selector.toLowerCase()];
+ }
+ for(var i = 0; i < selector.length; i++){
+ if(rs[selector[i]]){
+ return rs[selector[i].toLowerCase()];
+ }
+ }
+ return null;
+ },
+
+
+
+ updateRule : function(selector, property, value){
+ if(!Ext.isArray(selector)){
+ var rule = this.getRule(selector);
+ if(rule){
+ rule.style[property.replace(camelRe, camelFn)] = value;
+ return true;
+ }
+ }else{
+ for(var i = 0; i < selector.length; i++){
+ if(this.updateRule(selector[i], property, value)){
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ };
+}();
+Ext.util.ClickRepeater = Ext.extend(Ext.util.Observable, {
+
+ constructor : function(el, config){
+ this.el = Ext.get(el);
+ this.el.unselectable();
+
+ Ext.apply(this, config);
+
+ this.addEvents(
+
+ "mousedown",
+
+ "click",
+
+ "mouseup"
+ );
+
+ if(!this.disabled){
+ this.disabled = true;
+ this.enable();
+ }
+
+
+ if(this.handler){
+ this.on("click", this.handler, this.scope || this);
+ }
+
+ Ext.util.ClickRepeater.superclass.constructor.call(this);
+ },
+
+ interval : 20,
+ delay: 250,
+ preventDefault : true,
+ stopDefault : false,
+ timer : 0,
+
+
+ enable: function(){
+ if(this.disabled){
+ this.el.on('mousedown', this.handleMouseDown, this);
+ if (Ext.isIE){
+ this.el.on('dblclick', this.handleDblClick, this);
+ }
+ if(this.preventDefault || this.stopDefault){
+ this.el.on('click', this.eventOptions, this);
+ }
+ }
+ this.disabled = false;
+ },
+
+
+ disable: function( force){
+ if(force || !this.disabled){
+ clearTimeout(this.timer);
+ if(this.pressClass){
+ this.el.removeClass(this.pressClass);
+ }
+ Ext.getDoc().un('mouseup', this.handleMouseUp, this);
+ this.el.removeAllListeners();
+ }
+ this.disabled = true;
+ },
+
+
+ setDisabled: function(disabled){
+ this[disabled ? 'disable' : 'enable']();
+ },
+
+ eventOptions: function(e){
+ if(this.preventDefault){
+ e.preventDefault();
+ }
+ if(this.stopDefault){
+ e.stopEvent();
+ }
+ },
+
+
+ destroy : function() {
+ this.disable(true);
+ Ext.destroy(this.el);
+ this.purgeListeners();
+ },
+
+ handleDblClick : function(e){
+ clearTimeout(this.timer);
+ this.el.blur();
+
+ this.fireEvent("mousedown", this, e);
+ this.fireEvent("click", this, e);
+ },
+
+
+ handleMouseDown : function(e){
+ clearTimeout(this.timer);
+ this.el.blur();
+ if(this.pressClass){
+ this.el.addClass(this.pressClass);
+ }
+ this.mousedownTime = new Date();
+
+ Ext.getDoc().on("mouseup", this.handleMouseUp, this);
+ this.el.on("mouseout", this.handleMouseOut, this);
+
+ this.fireEvent("mousedown", this, e);
+ this.fireEvent("click", this, e);
+
+
+ if (this.accelerate) {
+ this.delay = 400;
+ }
+ this.timer = this.click.defer(this.delay || this.interval, this, [e]);
+ },
+
+
+ click : function(e){
+ this.fireEvent("click", this, e);
+ this.timer = this.click.defer(this.accelerate ?
+ this.easeOutExpo(this.mousedownTime.getElapsed(),
+ 400,
+ -390,
+ 12000) :
+ this.interval, this, [e]);
+ },
+
+ easeOutExpo : function (t, b, c, d) {
+ return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
+ },
+
+
+ handleMouseOut : function(){
+ clearTimeout(this.timer);
+ if(this.pressClass){
+ this.el.removeClass(this.pressClass);
+ }
+ this.el.on("mouseover", this.handleMouseReturn, this);
+ },
+
+
+ handleMouseReturn : function(){
+ this.el.un("mouseover", this.handleMouseReturn, this);
+ if(this.pressClass){
+ this.el.addClass(this.pressClass);
+ }
+ this.click();
+ },
+
+
+ handleMouseUp : function(e){
+ clearTimeout(this.timer);
+ this.el.un("mouseover", this.handleMouseReturn, this);
+ this.el.un("mouseout", this.handleMouseOut, this);
+ Ext.getDoc().un("mouseup", this.handleMouseUp, this);
+ this.el.removeClass(this.pressClass);
+ this.fireEvent("mouseup", this, e);
+ }
+});
+Ext.KeyNav = function(el, config){
+ this.el = Ext.get(el);
+ Ext.apply(this, config);
+ if(!this.disabled){
+ this.disabled = true;
+ this.enable();
+ }
+};
+
+Ext.KeyNav.prototype = {
+
+ disabled : false,
+
+ defaultEventAction: "stopEvent",
+
+ forceKeyDown : false,
+
+
+ relay : function(e){
+ var k = e.getKey(),
+ h = this.keyToHandler[k];
+ if(h && this[h]){
+ if(this.doRelay(e, this[h], h) !== true){
+ e[this.defaultEventAction]();
+ }
+ }
+ },
+
+
+ doRelay : function(e, h, hname){
+ return h.call(this.scope || this, e, hname);
+ },
+
+
+ enter : false,
+ left : false,
+ right : false,
+ up : false,
+ down : false,
+ tab : false,
+ esc : false,
+ pageUp : false,
+ pageDown : false,
+ del : false,
+ home : false,
+ end : false,
+ space : false,
+
+
+ keyToHandler : {
+ 37 : "left",
+ 39 : "right",
+ 38 : "up",
+ 40 : "down",
+ 33 : "pageUp",
+ 34 : "pageDown",
+ 46 : "del",
+ 36 : "home",
+ 35 : "end",
+ 13 : "enter",
+ 27 : "esc",
+ 9 : "tab",
+ 32 : "space"
+ },
+
+ stopKeyUp: function(e) {
+ var k = e.getKey();
+
+ if (k >= 37 && k <= 40) {
+
+
+ e.stopEvent();
+ }
+ },
+
+
+ destroy: function(){
+ this.disable();
+ },
+
+
+ enable: function() {
+ if (this.disabled) {
+ if (Ext.isSafari2) {
+
+ this.el.on('keyup', this.stopKeyUp, this);
+ }
+
+ this.el.on(this.isKeydown()? 'keydown' : 'keypress', this.relay, this);
+ this.disabled = false;
+ }
+ },
+
+
+ disable: function() {
+ if (!this.disabled) {
+ if (Ext.isSafari2) {
+
+ this.el.un('keyup', this.stopKeyUp, this);
+ }
+
+ this.el.un(this.isKeydown()? 'keydown' : 'keypress', this.relay, this);
+ this.disabled = true;
+ }
+ },
+
+
+ setDisabled : function(disabled){
+ this[disabled ? "disable" : "enable"]();
+ },
+
+
+ isKeydown: function(){
+ return this.forceKeyDown || Ext.EventManager.useKeydown;
+ }
+};
+
+Ext.KeyMap = function(el, config, eventName){
+ this.el = Ext.get(el);
+ this.eventName = eventName || "keydown";
+ this.bindings = [];
+ if(config){
+ this.addBinding(config);
+ }
+ this.enable();
+};
+
+Ext.KeyMap.prototype = {
+
+ stopEvent : false,
+
+
+ addBinding : function(config){
+ if(Ext.isArray(config)){
+ Ext.each(config, function(c){
+ this.addBinding(c);
+ }, this);
+ return;
+ }
+ var keyCode = config.key,
+ fn = config.fn || config.handler,
+ scope = config.scope;
+
+ if (config.stopEvent) {
+ this.stopEvent = config.stopEvent;
+ }
+
+ if(typeof keyCode == "string"){
+ var ks = [];
+ var keyString = keyCode.toUpperCase();
+ for(var j = 0, len = keyString.length; j < len; j++){
+ ks.push(keyString.charCodeAt(j));
+ }
+ keyCode = ks;
+ }
+ var keyArray = Ext.isArray(keyCode);
+
+ var handler = function(e){
+ if(this.checkModifiers(config, e)){
+ var k = e.getKey();
+ if(keyArray){
+ for(var i = 0, len = keyCode.length; i < len; i++){
+ if(keyCode[i] == k){
+ if(this.stopEvent){
+ e.stopEvent();
+ }
+ fn.call(scope || window, k, e);
+ return;
+ }
+ }
+ }else{
+ if(k == keyCode){
+ if(this.stopEvent){
+ e.stopEvent();
+ }
+ fn.call(scope || window, k, e);
+ }
+ }
+ }
+ };
+ this.bindings.push(handler);
+ },
+
+
+ checkModifiers: function(config, e){
+ var val, key, keys = ['shift', 'ctrl', 'alt'];
+ for (var i = 0, len = keys.length; i < len; ++i){
+ key = keys[i];
+ val = config[key];
+ if(!(val === undefined || (val === e[key + 'Key']))){
+ return false;
+ }
+ }
+ return true;
+ },
+
+
+ on : function(key, fn, scope){
+ var keyCode, shift, ctrl, alt;
+ if(typeof key == "object" && !Ext.isArray(key)){
+ keyCode = key.key;
+ shift = key.shift;
+ ctrl = key.ctrl;
+ alt = key.alt;
+ }else{
+ keyCode = key;
+ }
+ this.addBinding({
+ key: keyCode,
+ shift: shift,
+ ctrl: ctrl,
+ alt: alt,
+ fn: fn,
+ scope: scope
+ });
+ },
+
+
+ handleKeyDown : function(e){
+ if(this.enabled){
+ var b = this.bindings;
+ for(var i = 0, len = b.length; i < len; i++){
+ b[i].call(this, e);
+ }
+ }
+ },
+
+
+ isEnabled : function(){
+ return this.enabled;
+ },
+
+
+ enable: function(){
+ if(!this.enabled){
+ this.el.on(this.eventName, this.handleKeyDown, this);
+ this.enabled = true;
+ }
+ },
+
+
+ disable: function(){
+ if(this.enabled){
+ this.el.removeListener(this.eventName, this.handleKeyDown, this);
+ this.enabled = false;
+ }
+ },
+
+
+ setDisabled : function(disabled){
+ this[disabled ? "disable" : "enable"]();
+ }
+};
+Ext.util.TextMetrics = function(){
+ var shared;
+ return {
+
+ measure : function(el, text, fixedWidth){
+ if(!shared){
+ shared = Ext.util.TextMetrics.Instance(el, fixedWidth);
+ }
+ shared.bind(el);
+ shared.setFixedWidth(fixedWidth || 'auto');
+ return shared.getSize(text);
+ },
+
+
+ createInstance : function(el, fixedWidth){
+ return Ext.util.TextMetrics.Instance(el, fixedWidth);
+ }
+ };
+}();
+
+Ext.util.TextMetrics.Instance = function(bindTo, fixedWidth){
+ var ml = new Ext.Element(document.createElement('div'));
+ document.body.appendChild(ml.dom);
+ ml.position('absolute');
+ ml.setLeftTop(-1000, -1000);
+ ml.hide();
+
+ if(fixedWidth){
+ ml.setWidth(fixedWidth);
+ }
+
+ var instance = {
+
+ getSize : function(text){
+ ml.update(text);
+ var s = ml.getSize();
+ ml.update('');
+ return s;
+ },
+
+
+ bind : function(el){
+ ml.setStyle(
+ Ext.fly(el).getStyles('font-size','font-style', 'font-weight', 'font-family','line-height', 'text-transform', 'letter-spacing')
+ );
+ },
+
+
+ setFixedWidth : function(width){
+ ml.setWidth(width);
+ },
+
+
+ getWidth : function(text){
+ ml.dom.style.width = 'auto';
+ return this.getSize(text).width;
+ },
+
+
+ getHeight : function(text){
+ return this.getSize(text).height;
+ }
+ };
+
+ instance.bind(bindTo);
+
+ return instance;
+};
+
+Ext.Element.addMethods({
+
+ getTextWidth : function(text, min, max){
+ return (Ext.util.TextMetrics.measure(this.dom, Ext.value(text, this.dom.innerHTML, true)).width).constrain(min || 0, max || 1000000);
+ }
+});
+
+Ext.util.Cookies = {
+
+ set : function(name, value){
+ var argv = arguments;
+ var argc = arguments.length;
+ var expires = (argc > 2) ? argv[2] : null;
+ var path = (argc > 3) ? argv[3] : '/';
+ var domain = (argc > 4) ? argv[4] : null;
+ var secure = (argc > 5) ? argv[5] : false;
+ document.cookie = name + "=" + escape(value) + ((expires === null) ? "" : ("; expires=" + expires.toGMTString())) + ((path === null) ? "" : ("; path=" + path)) + ((domain === null) ? "" : ("; domain=" + domain)) + ((secure === true) ? "; secure" : "");
+ },
+
+
+ get : function(name){
+ var arg = name + "=";
+ var alen = arg.length;
+ var clen = document.cookie.length;
+ var i = 0;
+ var j = 0;
+ while(i < clen){
+ j = i + alen;
+ if(document.cookie.substring(i, j) == arg){
+ return Ext.util.Cookies.getCookieVal(j);
+ }
+ i = document.cookie.indexOf(" ", i) + 1;
+ if(i === 0){
+ break;
+ }
+ }
+ return null;
+ },
+
+
+ clear : function(name){
+ if(Ext.util.Cookies.get(name)){
+ document.cookie = name + "=" + "; expires=Thu, 01-Jan-70 00:00:01 GMT";
+ }
+ },
+
+ getCookieVal : function(offset){
+ var endstr = document.cookie.indexOf(";", offset);
+ if(endstr == -1){
+ endstr = document.cookie.length;
+ }
+ return unescape(document.cookie.substring(offset, endstr));
+ }
+};
+Ext.handleError = function(e) {
+ throw e;
+};
+
+
+Ext.Error = function(message) {
+
+ this.message = (this.lang[message]) ? this.lang[message] : message;
+};
+
+Ext.Error.prototype = new Error();
+Ext.apply(Ext.Error.prototype, {
+
+ lang: {},
+
+ name: 'Ext.Error',
+
+ getName : function() {
+ return this.name;
+ },
+
+ getMessage : function() {
+ return this.message;
+ },
+
+ toJson : function() {
+ return Ext.encode(this);
+ }
+});
+
+Ext.ComponentMgr = function(){
+ var all = new Ext.util.MixedCollection();
+ var types = {};
+ var ptypes = {};
+
+ return {
+
+ register : function(c){
+ all.add(c);
+ },
+
+
+ unregister : function(c){
+ all.remove(c);
+ },
+
+
+ get : function(id){
+ return all.get(id);
+ },
+
+
+ onAvailable : function(id, fn, scope){
+ all.on("add", function(index, o){
+ if(o.id == id){
+ fn.call(scope || o, o);
+ all.un("add", fn, scope);
+ }
+ });
+ },
+
+
+ all : all,
+
+
+ types : types,
+
+
+ ptypes: ptypes,
+
+
+ isRegistered : function(xtype){
+ return types[xtype] !== undefined;
+ },
+
+
+ isPluginRegistered : function(ptype){
+ return ptypes[ptype] !== undefined;
+ },
+
+
+ registerType : function(xtype, cls){
+ types[xtype] = cls;
+ cls.xtype = xtype;
+ },
+
+
+ create : function(config, defaultType){
+ return config.render ? config : new types[config.xtype || defaultType](config);
+ },
+
+
+ registerPlugin : function(ptype, cls){
+ ptypes[ptype] = cls;
+ cls.ptype = ptype;
+ },
+
+
+ createPlugin : function(config, defaultType){
+ var PluginCls = ptypes[config.ptype || defaultType];
+ if (PluginCls.init) {
+ return PluginCls;
+ } else {
+ return new PluginCls(config);
+ }
+ }
+ };
+}();
+
+
+Ext.reg = Ext.ComponentMgr.registerType;
+
+Ext.preg = Ext.ComponentMgr.registerPlugin;
+
+Ext.create = Ext.ComponentMgr.create;
+Ext.Component = function(config){
+ config = config || {};
+ if(config.initialConfig){
+ if(config.isAction){
+ this.baseAction = config;
+ }
+ config = config.initialConfig;
+ }else if(config.tagName || config.dom || Ext.isString(config)){
+ config = {applyTo: config, id: config.id || config};
+ }
+
+
+ this.initialConfig = config;
+
+ Ext.apply(this, config);
+ this.addEvents(
+
+ 'added',
+
+ 'disable',
+
+ 'enable',
+
+ 'beforeshow',
+
+ 'show',
+
+ 'beforehide',
+
+ 'hide',
+
+ 'removed',
+
+ 'beforerender',
+
+ 'render',
+
+ 'afterrender',
+
+ 'beforedestroy',
+
+ 'destroy',
+
+ 'beforestaterestore',
+
+ 'staterestore',
+
+ 'beforestatesave',
+
+ 'statesave'
+ );
+ this.getId();
+ Ext.ComponentMgr.register(this);
+ Ext.Component.superclass.constructor.call(this);
+
+ if(this.baseAction){
+ this.baseAction.addComponent(this);
+ }
+
+ this.initComponent();
+
+ if(this.plugins){
+ if(Ext.isArray(this.plugins)){
+ for(var i = 0, len = this.plugins.length; i < len; i++){
+ this.plugins[i] = this.initPlugin(this.plugins[i]);
+ }
+ }else{
+ this.plugins = this.initPlugin(this.plugins);
+ }
+ }
+
+ if(this.stateful !== false){
+ this.initState();
+ }
+
+ if(this.applyTo){
+ this.applyToMarkup(this.applyTo);
+ delete this.applyTo;
+ }else if(this.renderTo){
+ this.render(this.renderTo);
+ delete this.renderTo;
+ }
+};
+
+
+Ext.Component.AUTO_ID = 1000;
+
+Ext.extend(Ext.Component, Ext.util.Observable, {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ disabled : false,
+
+ hidden : false,
+
+
+
+
+
+
+
+ autoEl : 'div',
+
+
+ disabledClass : 'x-item-disabled',
+
+ allowDomMove : true,
+
+ autoShow : false,
+
+ hideMode : 'display',
+
+ hideParent : false,
+
+
+
+
+
+ rendered : false,
+
+
+
+
+
+
+
+ tplWriteMode : 'overwrite',
+
+
+
+
+ bubbleEvents: [],
+
+
+
+ ctype : 'Ext.Component',
+
+
+ actionMode : 'el',
+
+
+ getActionEl : function(){
+ return this[this.actionMode];
+ },
+
+ initPlugin : function(p){
+ if(p.ptype && !Ext.isFunction(p.init)){
+ p = Ext.ComponentMgr.createPlugin(p);
+ }else if(Ext.isString(p)){
+ p = Ext.ComponentMgr.createPlugin({
+ ptype: p
+ });
+ }
+ p.init(this);
+ return p;
+ },
+
+
+ initComponent : function(){
+
+ if(this.listeners){
+ this.on(this.listeners);
+ delete this.listeners;
+ }
+ this.enableBubble(this.bubbleEvents);
+ },
+
+
+ render : function(container, position){
+ if(!this.rendered && this.fireEvent('beforerender', this) !== false){
+ if(!container && this.el){
+ this.el = Ext.get(this.el);
+ container = this.el.dom.parentNode;
+ this.allowDomMove = false;
+ }
+ this.container = Ext.get(container);
+ if(this.ctCls){
+ this.container.addClass(this.ctCls);
+ }
+ this.rendered = true;
+ if(position !== undefined){
+ if(Ext.isNumber(position)){
+ position = this.container.dom.childNodes[position];
+ }else{
+ position = Ext.getDom(position);
+ }
+ }
+ this.onRender(this.container, position || null);
+ if(this.autoShow){
+ this.el.removeClass(['x-hidden','x-hide-' + this.hideMode]);
+ }
+ if(this.cls){
+ this.el.addClass(this.cls);
+ delete this.cls;
+ }
+ if(this.style){
+ this.el.applyStyles(this.style);
+ delete this.style;
+ }
+ if(this.overCls){
+ this.el.addClassOnOver(this.overCls);
+ }
+ this.fireEvent('render', this);
+
+
+
+
+ var contentTarget = this.getContentTarget();
+ if (this.html){
+ contentTarget.update(Ext.DomHelper.markup(this.html));
+ delete this.html;
+ }
+ if (this.contentEl){
+ var ce = Ext.getDom(this.contentEl);
+ Ext.fly(ce).removeClass(['x-hidden', 'x-hide-display']);
+ contentTarget.appendChild(ce);
+ }
+ if (this.tpl) {
+ if (!this.tpl.compile) {
+ this.tpl = new Ext.XTemplate(this.tpl);
+ }
+ if (this.data) {
+ this.tpl[this.tplWriteMode](contentTarget, this.data);
+ delete this.data;
+ }
+ }
+ this.afterRender(this.container);
+
+
+ if(this.hidden){
+
+ this.doHide();
+ }
+ if(this.disabled){
+
+ this.disable(true);
+ }
+
+ if(this.stateful !== false){
+ this.initStateEvents();
+ }
+ this.fireEvent('afterrender', this);
+ }
+ return this;
+ },
+
+
+
+ update: function(htmlOrData, loadScripts, cb) {
+ var contentTarget = this.getContentTarget();
+ if (this.tpl && typeof htmlOrData !== "string") {
+ this.tpl[this.tplWriteMode](contentTarget, htmlOrData || {});
+ } else {
+ var html = Ext.isObject(htmlOrData) ? Ext.DomHelper.markup(htmlOrData) : htmlOrData;
+ contentTarget.update(html, loadScripts, cb);
+ }
+ },
+
+
+
+ onAdded : function(container, pos) {
+ this.ownerCt = container;
+ this.initRef();
+ this.fireEvent('added', this, container, pos);
+ },
+
+
+ onRemoved : function() {
+ this.removeRef();
+ this.fireEvent('removed', this, this.ownerCt);
+ delete this.ownerCt;
+ },
+
+
+ initRef : function() {
+
+ if(this.ref && !this.refOwner){
+ var levels = this.ref.split('/'),
+ last = levels.length,
+ i = 0,
+ t = this;
+
+ while(t && i < last){
+ t = t.ownerCt;
+ ++i;
+ }
+ if(t){
+ t[this.refName = levels[--i]] = this;
+
+ this.refOwner = t;
+ }
+ }
+ },
+
+ removeRef : function() {
+ if (this.refOwner && this.refName) {
+ delete this.refOwner[this.refName];
+ delete this.refOwner;
+ }
+ },
+
+
+ initState : function(){
+ if(Ext.state.Manager){
+ var id = this.getStateId();
+ if(id){
+ var state = Ext.state.Manager.get(id);
+ if(state){
+ if(this.fireEvent('beforestaterestore', this, state) !== false){
+ this.applyState(Ext.apply({}, state));
+ this.fireEvent('staterestore', this, state);
+ }
+ }
+ }
+ }
+ },
+
+
+ getStateId : function(){
+ return this.stateId || ((/^(ext-comp-|ext-gen)/).test(String(this.id)) ? null : this.id);
+ },
+
+
+ initStateEvents : function(){
+ if(this.stateEvents){
+ for(var i = 0, e; e = this.stateEvents[i]; i++){
+ this.on(e, this.saveState, this, {delay:100});
+ }
+ }
+ },
+
+
+ applyState : function(state){
+ if(state){
+ Ext.apply(this, state);
+ }
+ },
+
+
+ getState : function(){
+ return null;
+ },
+
+
+ saveState : function(){
+ if(Ext.state.Manager && this.stateful !== false){
+ var id = this.getStateId();
+ if(id){
+ var state = this.getState();
+ if(this.fireEvent('beforestatesave', this, state) !== false){
+ Ext.state.Manager.set(id, state);
+ this.fireEvent('statesave', this, state);
+ }
+ }
+ }
+ },
+
+
+ applyToMarkup : function(el){
+ this.allowDomMove = false;
+ this.el = Ext.get(el);
+ this.render(this.el.dom.parentNode);
+ },
+
+
+ addClass : function(cls){
+ if(this.el){
+ this.el.addClass(cls);
+ }else{
+ this.cls = this.cls ? this.cls + ' ' + cls : cls;
+ }
+ return this;
+ },
+
+
+ removeClass : function(cls){
+ if(this.el){
+ this.el.removeClass(cls);
+ }else if(this.cls){
+ this.cls = this.cls.split(' ').remove(cls).join(' ');
+ }
+ return this;
+ },
+
+
+
+ onRender : function(ct, position){
+ if(!this.el && this.autoEl){
+ if(Ext.isString(this.autoEl)){
+ this.el = document.createElement(this.autoEl);
+ }else{
+ var div = document.createElement('div');
+ Ext.DomHelper.overwrite(div, this.autoEl);
+ this.el = div.firstChild;
+ }
+ if (!this.el.id) {
+ this.el.id = this.getId();
+ }
+ }
+ if(this.el){
+ this.el = Ext.get(this.el);
+ if(this.allowDomMove !== false){
+ ct.dom.insertBefore(this.el.dom, position);
+ if (div) {
+ Ext.removeNode(div);
+ div = null;
+ }
+ }
+ }
+ },
+
+
+ getAutoCreate : function(){
+ var cfg = Ext.isObject(this.autoCreate) ?
+ this.autoCreate : Ext.apply({}, this.defaultAutoCreate);
+ if(this.id && !cfg.id){
+ cfg.id = this.id;
+ }
+ return cfg;
+ },
+
+
+ afterRender : Ext.emptyFn,
+
+
+ destroy : function(){
+ if(!this.isDestroyed){
+ if(this.fireEvent('beforedestroy', this) !== false){
+ this.destroying = true;
+ this.beforeDestroy();
+ if(this.ownerCt && this.ownerCt.remove){
+ this.ownerCt.remove(this, false);
+ }
+ if(this.rendered){
+ this.el.remove();
+ if(this.actionMode == 'container' || this.removeMode == 'container'){
+ this.container.remove();
+ }
+ }
+
+ if(this.focusTask && this.focusTask.cancel){
+ this.focusTask.cancel();
+ }
+ this.onDestroy();
+ Ext.ComponentMgr.unregister(this);
+ this.fireEvent('destroy', this);
+ this.purgeListeners();
+ this.destroying = false;
+ this.isDestroyed = true;
+ }
+ }
+ },
+
+ deleteMembers : function(){
+ var args = arguments;
+ for(var i = 0, len = args.length; i < len; ++i){
+ delete this[args[i]];
+ }
+ },
+
+
+ beforeDestroy : Ext.emptyFn,
+
+
+ onDestroy : Ext.emptyFn,
+
+
+ getEl : function(){
+ return this.el;
+ },
+
+
+ getContentTarget : function(){
+ return this.el;
+ },
+
+
+ getId : function(){
+ return this.id || (this.id = 'ext-comp-' + (++Ext.Component.AUTO_ID));
+ },
+
+
+ getItemId : function(){
+ return this.itemId || this.getId();
+ },
+
+
+ focus : function(selectText, delay){
+ if(delay){
+ this.focusTask = new Ext.util.DelayedTask(this.focus, this, [selectText, false]);
+ this.focusTask.delay(Ext.isNumber(delay) ? delay : 10);
+ return this;
+ }
+ if(this.rendered && !this.isDestroyed){
+ this.el.focus();
+ if(selectText === true){
+ this.el.dom.select();
+ }
+ }
+ return this;
+ },
+
+
+ blur : function(){
+ if(this.rendered){
+ this.el.blur();
+ }
+ return this;
+ },
+
+
+ disable : function( silent){
+ if(this.rendered){
+ this.onDisable();
+ }
+ this.disabled = true;
+ if(silent !== true){
+ this.fireEvent('disable', this);
+ }
+ return this;
+ },
+
+
+ onDisable : function(){
+ this.getActionEl().addClass(this.disabledClass);
+ this.el.dom.disabled = true;
+ },
+
+
+ enable : function(){
+ if(this.rendered){
+ this.onEnable();
+ }
+ this.disabled = false;
+ this.fireEvent('enable', this);
+ return this;
+ },
+
+
+ onEnable : function(){
+ this.getActionEl().removeClass(this.disabledClass);
+ this.el.dom.disabled = false;
+ },
+
+
+ setDisabled : function(disabled){
+ return this[disabled ? 'disable' : 'enable']();
+ },
+
+
+ show : function(){
+ if(this.fireEvent('beforeshow', this) !== false){
+ this.hidden = false;
+ if(this.autoRender){
+ this.render(Ext.isBoolean(this.autoRender) ? Ext.getBody() : this.autoRender);
+ }
+ if(this.rendered){
+ this.onShow();
+ }
+ this.fireEvent('show', this);
+ }
+ return this;
+ },
+
+
+ onShow : function(){
+ this.getVisibilityEl().removeClass('x-hide-' + this.hideMode);
+ },
+
+
+ hide : function(){
+ if(this.fireEvent('beforehide', this) !== false){
+ this.doHide();
+ this.fireEvent('hide', this);
+ }
+ return this;
+ },
+
+
+ doHide: function(){
+ this.hidden = true;
+ if(this.rendered){
+ this.onHide();
+ }
+ },
+
+
+ onHide : function(){
+ this.getVisibilityEl().addClass('x-hide-' + this.hideMode);
+ },
+
+
+ getVisibilityEl : function(){
+ return this.hideParent ? this.container : this.getActionEl();
+ },
+
+
+ setVisible : function(visible){
+ return this[visible ? 'show' : 'hide']();
+ },
+
+
+ isVisible : function(){
+ return this.rendered && this.getVisibilityEl().isVisible();
+ },
+
+
+ cloneConfig : function(overrides){
+ overrides = overrides || {};
+ var id = overrides.id || Ext.id();
+ var cfg = Ext.applyIf(overrides, this.initialConfig);
+ cfg.id = id;
+ return new this.constructor(cfg);
+ },
+
+
+ getXType : function(){
+ return this.constructor.xtype;
+ },
+
+
+ isXType : function(xtype, shallow){
+
+ if (Ext.isFunction(xtype)){
+ xtype = xtype.xtype;
+ }else if (Ext.isObject(xtype)){
+ xtype = xtype.constructor.xtype;
+ }
+
+ return !shallow ? ('/' + this.getXTypes() + '/').indexOf('/' + xtype + '/') != -1 : this.constructor.xtype == xtype;
+ },
+
+
+ getXTypes : function(){
+ var tc = this.constructor;
+ if(!tc.xtypes){
+ var c = [], sc = this;
+ while(sc && sc.constructor.xtype){
+ c.unshift(sc.constructor.xtype);
+ sc = sc.constructor.superclass;
+ }
+ tc.xtypeChain = c;
+ tc.xtypes = c.join('/');
+ }
+ return tc.xtypes;
+ },
+
+
+ findParentBy : function(fn) {
+ for (var p = this.ownerCt; (p != null) && !fn(p, this); p = p.ownerCt);
+ return p || null;
+ },
+
+
+ findParentByType : function(xtype, shallow){
+ return this.findParentBy(function(c){
+ return c.isXType(xtype, shallow);
+ });
+ },
+
+
+ bubble : function(fn, scope, args){
+ var p = this;
+ while(p){
+ if(fn.apply(scope || p, args || [p]) === false){
+ break;
+ }
+ p = p.ownerCt;
+ }
+ return this;
+ },
+
+
+ getPositionEl : function(){
+ return this.positionEl || this.el;
+ },
+
+
+ purgeListeners : function(){
+ Ext.Component.superclass.purgeListeners.call(this);
+ if(this.mons){
+ this.on('beforedestroy', this.clearMons, this, {single: true});
+ }
+ },
+
+
+ clearMons : function(){
+ Ext.each(this.mons, function(m){
+ m.item.un(m.ename, m.fn, m.scope);
+ }, this);
+ this.mons = [];
+ },
+
+
+ createMons: function(){
+ if(!this.mons){
+ this.mons = [];
+ this.on('beforedestroy', this.clearMons, this, {single: true});
+ }
+ },
+
+
+ mon : function(item, ename, fn, scope, opt){
+ this.createMons();
+ if(Ext.isObject(ename)){
+ var propRe = /^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/;
+
+ var o = ename;
+ for(var e in o){
+ if(propRe.test(e)){
+ continue;
+ }
+ if(Ext.isFunction(o[e])){
+
+ this.mons.push({
+ item: item, ename: e, fn: o[e], scope: o.scope
+ });
+ item.on(e, o[e], o.scope, o);
+ }else{
+
+ this.mons.push({
+ item: item, ename: e, fn: o[e], scope: o.scope
+ });
+ item.on(e, o[e]);
+ }
+ }
+ return;
+ }
+
+ this.mons.push({
+ item: item, ename: ename, fn: fn, scope: scope
+ });
+ item.on(ename, fn, scope, opt);
+ },
+
+
+ mun : function(item, ename, fn, scope){
+ var found, mon;
+ this.createMons();
+ for(var i = 0, len = this.mons.length; i < len; ++i){
+ mon = this.mons[i];
+ if(item === mon.item && ename == mon.ename && fn === mon.fn && scope === mon.scope){
+ this.mons.splice(i, 1);
+ item.un(ename, fn, scope);
+ found = true;
+ break;
+ }
+ }
+ return found;
+ },
+
+
+ nextSibling : function(){
+ if(this.ownerCt){
+ var index = this.ownerCt.items.indexOf(this);
+ if(index != -1 && index+1 < this.ownerCt.items.getCount()){
+ return this.ownerCt.items.itemAt(index+1);
+ }
+ }
+ return null;
+ },
+
+
+ previousSibling : function(){
+ if(this.ownerCt){
+ var index = this.ownerCt.items.indexOf(this);
+ if(index > 0){
+ return this.ownerCt.items.itemAt(index-1);
+ }
+ }
+ return null;
+ },
+
+
+ getBubbleTarget : function(){
+ return this.ownerCt;
+ }
+});
+
+Ext.reg('component', Ext.Component);
+
+Ext.Action = Ext.extend(Object, {
+
+
+
+
+
+
+
+
+ constructor : function(config){
+ this.initialConfig = config;
+ this.itemId = config.itemId = (config.itemId || config.id || Ext.id());
+ this.items = [];
+ },
+
+
+ isAction : true,
+
+
+ setText : function(text){
+ this.initialConfig.text = text;
+ this.callEach('setText', [text]);
+ },
+
+
+ getText : function(){
+ return this.initialConfig.text;
+ },
+
+
+ setIconClass : function(cls){
+ this.initialConfig.iconCls = cls;
+ this.callEach('setIconClass', [cls]);
+ },
+
+
+ getIconClass : function(){
+ return this.initialConfig.iconCls;
+ },
+
+
+ setDisabled : function(v){
+ this.initialConfig.disabled = v;
+ this.callEach('setDisabled', [v]);
+ },
+
+
+ enable : function(){
+ this.setDisabled(false);
+ },
+
+
+ disable : function(){
+ this.setDisabled(true);
+ },
+
+
+ isDisabled : function(){
+ return this.initialConfig.disabled;
+ },
+
+
+ setHidden : function(v){
+ this.initialConfig.hidden = v;
+ this.callEach('setVisible', [!v]);
+ },
+
+
+ show : function(){
+ this.setHidden(false);
+ },
+
+
+ hide : function(){
+ this.setHidden(true);
+ },
+
+
+ isHidden : function(){
+ return this.initialConfig.hidden;
+ },
+
+
+ setHandler : function(fn, scope){
+ this.initialConfig.handler = fn;
+ this.initialConfig.scope = scope;
+ this.callEach('setHandler', [fn, scope]);
+ },
+
+
+ each : function(fn, scope){
+ Ext.each(this.items, fn, scope);
+ },
+
+
+ callEach : function(fnName, args){
+ var cs = this.items;
+ for(var i = 0, len = cs.length; i < len; i++){
+ cs[i][fnName].apply(cs[i], args);
+ }
+ },
+
+
+ addComponent : function(comp){
+ this.items.push(comp);
+ comp.on('destroy', this.removeComponent, this);
+ },
+
+
+ removeComponent : function(comp){
+ this.items.remove(comp);
+ },
+
+
+ execute : function(){
+ this.initialConfig.handler.apply(this.initialConfig.scope || window, arguments);
+ }
+});
+
+(function(){
+Ext.Layer = function(config, existingEl){
+ config = config || {};
+ var dh = Ext.DomHelper,
+ cp = config.parentEl, pel = cp ? Ext.getDom(cp) : document.body;
+
+ if (existingEl) {
+ this.dom = Ext.getDom(existingEl);
+ }
+ if(!this.dom){
+ var o = config.dh || {tag: 'div', cls: 'x-layer'};
+ this.dom = dh.append(pel, o);
+ }
+ if(config.cls){
+ this.addClass(config.cls);
+ }
+ this.constrain = config.constrain !== false;
+ this.setVisibilityMode(Ext.Element.VISIBILITY);
+ if(config.id){
+ this.id = this.dom.id = config.id;
+ }else{
+ this.id = Ext.id(this.dom);
+ }
+ this.zindex = config.zindex || this.getZIndex();
+ this.position('absolute', this.zindex);
+ if(config.shadow){
+ this.shadowOffset = config.shadowOffset || 4;
+ this.shadow = new Ext.Shadow({
+ offset : this.shadowOffset,
+ mode : config.shadow
+ });
+ }else{
+ this.shadowOffset = 0;
+ }
+ this.useShim = config.shim !== false && Ext.useShims;
+ this.useDisplay = config.useDisplay;
+ this.hide();
+};
+
+var supr = Ext.Element.prototype;
+
+
+var shims = [];
+
+Ext.extend(Ext.Layer, Ext.Element, {
+
+ getZIndex : function(){
+ return this.zindex || parseInt((this.getShim() || this).getStyle('z-index'), 10) || 11000;
+ },
+
+ getShim : function(){
+ if(!this.useShim){
+ return null;
+ }
+ if(this.shim){
+ return this.shim;
+ }
+ var shim = shims.shift();
+ if(!shim){
+ shim = this.createShim();
+ shim.enableDisplayMode('block');
+ shim.dom.style.display = 'none';
+ shim.dom.style.visibility = 'visible';
+ }
+ var pn = this.dom.parentNode;
+ if(shim.dom.parentNode != pn){
+ pn.insertBefore(shim.dom, this.dom);
+ }
+ shim.setStyle('z-index', this.getZIndex()-2);
+ this.shim = shim;
+ return shim;
+ },
+
+ hideShim : function(){
+ if(this.shim){
+ this.shim.setDisplayed(false);
+ shims.push(this.shim);
+ delete this.shim;
+ }
+ },
+
+ disableShadow : function(){
+ if(this.shadow){
+ this.shadowDisabled = true;
+ this.shadow.hide();
+ this.lastShadowOffset = this.shadowOffset;
+ this.shadowOffset = 0;
+ }
+ },
+
+ enableShadow : function(show){
+ if(this.shadow){
+ this.shadowDisabled = false;
+ if(Ext.isDefined(this.lastShadowOffset)) {
+ this.shadowOffset = this.lastShadowOffset;
+ delete this.lastShadowOffset;
+ }
+ if(show){
+ this.sync(true);
+ }
+ }
+ },
+
+
+
+
+ sync : function(doShow){
+ var shadow = this.shadow;
+ if(!this.updating && this.isVisible() && (shadow || this.useShim)){
+ var shim = this.getShim(),
+ w = this.getWidth(),
+ h = this.getHeight(),
+ l = this.getLeft(true),
+ t = this.getTop(true);
+
+ if(shadow && !this.shadowDisabled){
+ if(doShow && !shadow.isVisible()){
+ shadow.show(this);
+ }else{
+ shadow.realign(l, t, w, h);
+ }
+ if(shim){
+ if(doShow){
+ shim.show();
+ }
+
+ var shadowAdj = shadow.el.getXY(), shimStyle = shim.dom.style,
+ shadowSize = shadow.el.getSize();
+ shimStyle.left = (shadowAdj[0])+'px';
+ shimStyle.top = (shadowAdj[1])+'px';
+ shimStyle.width = (shadowSize.width)+'px';
+ shimStyle.height = (shadowSize.height)+'px';
+ }
+ }else if(shim){
+ if(doShow){
+ shim.show();
+ }
+ shim.setSize(w, h);
+ shim.setLeftTop(l, t);
+ }
+ }
+ },
+
+
+ destroy : function(){
+ this.hideShim();
+ if(this.shadow){
+ this.shadow.hide();
+ }
+ this.removeAllListeners();
+ Ext.removeNode(this.dom);
+ delete this.dom;
+ },
+
+ remove : function(){
+ this.destroy();
+ },
+
+
+ beginUpdate : function(){
+ this.updating = true;
+ },
+
+
+ endUpdate : function(){
+ this.updating = false;
+ this.sync(true);
+ },
+
+
+ hideUnders : function(negOffset){
+ if(this.shadow){
+ this.shadow.hide();
+ }
+ this.hideShim();
+ },
+
+
+ constrainXY : function(){
+ if(this.constrain){
+ var vw = Ext.lib.Dom.getViewWidth(),
+ vh = Ext.lib.Dom.getViewHeight();
+ var s = Ext.getDoc().getScroll();
+
+ var xy = this.getXY();
+ var x = xy[0], y = xy[1];
+ var so = this.shadowOffset;
+ var w = this.dom.offsetWidth+so, h = this.dom.offsetHeight+so;
+
+ var moved = false;
+
+ if((x + w) > vw+s.left){
+ x = vw - w - so;
+ moved = true;
+ }
+ if((y + h) > vh+s.top){
+ y = vh - h - so;
+ moved = true;
+ }
+
+ if(x < s.left){
+ x = s.left;
+ moved = true;
+ }
+ if(y < s.top){
+ y = s.top;
+ moved = true;
+ }
+ if(moved){
+ if(this.avoidY){
+ var ay = this.avoidY;
+ if(y <= ay && (y+h) >= ay){
+ y = ay-h-5;
+ }
+ }
+ xy = [x, y];
+ this.storeXY(xy);
+ supr.setXY.call(this, xy);
+ this.sync();
+ }
+ }
+ return this;
+ },
+
+ getConstrainOffset : function(){
+ return this.shadowOffset;
+ },
+
+ isVisible : function(){
+ return this.visible;
+ },
+
+
+ showAction : function(){
+ this.visible = true;
+ if(this.useDisplay === true){
+ this.setDisplayed('');
+ }else if(this.lastXY){
+ supr.setXY.call(this, this.lastXY);
+ }else if(this.lastLT){
+ supr.setLeftTop.call(this, this.lastLT[0], this.lastLT[1]);
+ }
+ },
+
+
+ hideAction : function(){
+ this.visible = false;
+ if(this.useDisplay === true){
+ this.setDisplayed(false);
+ }else{
+ this.setLeftTop(-10000,-10000);
+ }
+ },
+
+
+ setVisible : function(v, a, d, c, e){
+ if(v){
+ this.showAction();
+ }
+ if(a && v){
+ var cb = function(){
+ this.sync(true);
+ if(c){
+ c();
+ }
+ }.createDelegate(this);
+ supr.setVisible.call(this, true, true, d, cb, e);
+ }else{
+ if(!v){
+ this.hideUnders(true);
+ }
+ var cb = c;
+ if(a){
+ cb = function(){
+ this.hideAction();
+ if(c){
+ c();
+ }
+ }.createDelegate(this);
+ }
+ supr.setVisible.call(this, v, a, d, cb, e);
+ if(v){
+ this.sync(true);
+ }else if(!a){
+ this.hideAction();
+ }
+ }
+ return this;
+ },
+
+ storeXY : function(xy){
+ delete this.lastLT;
+ this.lastXY = xy;
+ },
+
+ storeLeftTop : function(left, top){
+ delete this.lastXY;
+ this.lastLT = [left, top];
+ },
+
+
+ beforeFx : function(){
+ this.beforeAction();
+ return Ext.Layer.superclass.beforeFx.apply(this, arguments);
+ },
+
+
+ afterFx : function(){
+ Ext.Layer.superclass.afterFx.apply(this, arguments);
+ this.sync(this.isVisible());
+ },
+
+
+ beforeAction : function(){
+ if(!this.updating && this.shadow){
+ this.shadow.hide();
+ }
+ },
+
+
+ setLeft : function(left){
+ this.storeLeftTop(left, this.getTop(true));
+ supr.setLeft.apply(this, arguments);
+ this.sync();
+ return this;
+ },
+
+ setTop : function(top){
+ this.storeLeftTop(this.getLeft(true), top);
+ supr.setTop.apply(this, arguments);
+ this.sync();
+ return this;
+ },
+
+ setLeftTop : function(left, top){
+ this.storeLeftTop(left, top);
+ supr.setLeftTop.apply(this, arguments);
+ this.sync();
+ return this;
+ },
+
+ setXY : function(xy, a, d, c, e){
+ this.fixDisplay();
+ this.beforeAction();
+ this.storeXY(xy);
+ var cb = this.createCB(c);
+ supr.setXY.call(this, xy, a, d, cb, e);
+ if(!a){
+ cb();
+ }
+ return this;
+ },
+
+
+ createCB : function(c){
+ var el = this;
+ return function(){
+ el.constrainXY();
+ el.sync(true);
+ if(c){
+ c();
+ }
+ };
+ },
+
+
+ setX : function(x, a, d, c, e){
+ this.setXY([x, this.getY()], a, d, c, e);
+ return this;
+ },
+
+
+ setY : function(y, a, d, c, e){
+ this.setXY([this.getX(), y], a, d, c, e);
+ return this;
+ },
+
+
+ setSize : function(w, h, a, d, c, e){
+ this.beforeAction();
+ var cb = this.createCB(c);
+ supr.setSize.call(this, w, h, a, d, cb, e);
+ if(!a){
+ cb();
+ }
+ return this;
+ },
+
+
+ setWidth : function(w, a, d, c, e){
+ this.beforeAction();
+ var cb = this.createCB(c);
+ supr.setWidth.call(this, w, a, d, cb, e);
+ if(!a){
+ cb();
+ }
+ return this;
+ },
+
+
+ setHeight : function(h, a, d, c, e){
+ this.beforeAction();
+ var cb = this.createCB(c);
+ supr.setHeight.call(this, h, a, d, cb, e);
+ if(!a){
+ cb();
+ }
+ return this;
+ },
+
+
+ setBounds : function(x, y, w, h, a, d, c, e){
+ this.beforeAction();
+ var cb = this.createCB(c);
+ if(!a){
+ this.storeXY([x, y]);
+ supr.setXY.call(this, [x, y]);
+ supr.setSize.call(this, w, h, a, d, cb, e);
+ cb();
+ }else{
+ supr.setBounds.call(this, x, y, w, h, a, d, cb, e);
+ }
+ return this;
+ },
+
+
+ setZIndex : function(zindex){
+ this.zindex = zindex;
+ this.setStyle('z-index', zindex + 2);
+ if(this.shadow){
+ this.shadow.setZIndex(zindex + 1);
+ }
+ if(this.shim){
+ this.shim.setStyle('z-index', zindex);
+ }
+ return this;
+ }
+});
+})();
+
+Ext.Shadow = function(config) {
+ Ext.apply(this, config);
+ if (typeof this.mode != "string") {
+ this.mode = this.defaultMode;
+ }
+ var o = this.offset,
+ a = {
+ h: 0
+ },
+ rad = Math.floor(this.offset / 2);
+ switch (this.mode.toLowerCase()) {
+
+ case "drop":
+ a.w = 0;
+ a.l = a.t = o;
+ a.t -= 1;
+ if (Ext.isIE9m) {
+ a.l -= this.offset + rad;
+ a.t -= this.offset + rad;
+ a.w -= rad;
+ a.h -= rad;
+ a.t += 1;
+ }
+ break;
+ case "sides":
+ a.w = (o * 2);
+ a.l = -o;
+ a.t = o - 1;
+ if (Ext.isIE9m) {
+ a.l -= (this.offset - rad);
+ a.t -= this.offset + rad;
+ a.l += 1;
+ a.w -= (this.offset - rad) * 2;
+ a.w -= rad + 1;
+ a.h -= 1;
+ }
+ break;
+ case "frame":
+ a.w = a.h = (o * 2);
+ a.l = a.t = -o;
+ a.t += 1;
+ a.h -= 2;
+ if (Ext.isIE9m) {
+ a.l -= (this.offset - rad);
+ a.t -= (this.offset - rad);
+ a.l += 1;
+ a.w -= (this.offset + rad + 1);
+ a.h -= (this.offset + rad);
+ a.h += 1;
+ }
+ break;
+ };
+
+ this.adjusts = a;
+};
+
+Ext.Shadow.prototype = {
+
+
+ offset: 4,
+
+
+ defaultMode: "drop",
+
+
+ show: function(target) {
+ target = Ext.get(target);
+ if (!this.el) {
+ this.el = Ext.Shadow.Pool.pull();
+ if (this.el.dom.nextSibling != target.dom) {
+ this.el.insertBefore(target);
+ }
+ }
+ this.el.setStyle("z-index", this.zIndex || parseInt(target.getStyle("z-index"), 10) - 1);
+ if (Ext.isIE9m) {
+ this.el.dom.style.filter = "progid:DXImageTransform.Microsoft.alpha(opacity=50) progid:DXImageTransform.Microsoft.Blur(pixelradius=" + (this.offset) + ")";
+ }
+ this.realign(
+ target.getLeft(true),
+ target.getTop(true),
+ target.getWidth(),
+ target.getHeight()
+ );
+ this.el.dom.style.display = "block";
+ },
+
+
+ isVisible: function() {
+ return this.el ? true: false;
+ },
+
+
+ realign: function(l, t, w, h) {
+ if (!this.el) {
+ return;
+ }
+ var a = this.adjusts,
+ d = this.el.dom,
+ s = d.style,
+ iea = 0,
+ sw = (w + a.w),
+ sh = (h + a.h),
+ sws = sw + "px",
+ shs = sh + "px",
+ cn,
+ sww;
+ s.left = (l + a.l) + "px";
+ s.top = (t + a.t) + "px";
+ if (s.width != sws || s.height != shs) {
+ s.width = sws;
+ s.height = shs;
+ if (!Ext.isIE9m) {
+ cn = d.childNodes;
+ sww = Math.max(0, (sw - 12)) + "px";
+ cn[0].childNodes[1].style.width = sww;
+ cn[1].childNodes[1].style.width = sww;
+ cn[2].childNodes[1].style.width = sww;
+ cn[1].style.height = Math.max(0, (sh - 12)) + "px";
+ }
+ }
+ },
+
+
+ hide: function() {
+ if (this.el) {
+ this.el.dom.style.display = "none";
+ Ext.Shadow.Pool.push(this.el);
+ delete this.el;
+ }
+ },
+
+
+ setZIndex: function(z) {
+ this.zIndex = z;
+ if (this.el) {
+ this.el.setStyle("z-index", z);
+ }
+ }
+};
+
+
+Ext.Shadow.Pool = function() {
+ var p = [],
+ markup = Ext.isIE9m ?
+ '<div class="x-ie-shadow"></div>':
+ '<div class="x-shadow"><div class="xst"><div class="xstl"></div><div class="xstc"></div><div class="xstr"></div></div><div class="xsc"><div class="xsml"></div><div class="xsmc"></div><div class="xsmr"></div></div><div class="xsb"><div class="xsbl"></div><div class="xsbc"></div><div class="xsbr"></div></div></div>';
+ return {
+ pull: function() {
+ var sh = p.shift();
+ if (!sh) {
+ sh = Ext.get(Ext.DomHelper.insertHtml("beforeBegin", document.body.firstChild, markup));
+ sh.autoBoxAdjust = false;
+ }
+ return sh;
+ },
+
+ push: function(sh) {
+ p.push(sh);
+ }
+ };
+}();
+Ext.BoxComponent = Ext.extend(Ext.Component, {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ initComponent : function(){
+ Ext.BoxComponent.superclass.initComponent.call(this);
+ this.addEvents(
+
+ 'resize',
+
+ 'move'
+ );
+ },
+
+
+ boxReady : false,
+
+ deferHeight: false,
+
+
+ setSize : function(w, h){
+
+
+ if(typeof w == 'object'){
+ h = w.height;
+ w = w.width;
+ }
+ if (Ext.isDefined(w) && Ext.isDefined(this.boxMinWidth) && (w < this.boxMinWidth)) {
+ w = this.boxMinWidth;
+ }
+ if (Ext.isDefined(h) && Ext.isDefined(this.boxMinHeight) && (h < this.boxMinHeight)) {
+ h = this.boxMinHeight;
+ }
+ if (Ext.isDefined(w) && Ext.isDefined(this.boxMaxWidth) && (w > this.boxMaxWidth)) {
+ w = this.boxMaxWidth;
+ }
+ if (Ext.isDefined(h) && Ext.isDefined(this.boxMaxHeight) && (h > this.boxMaxHeight)) {
+ h = this.boxMaxHeight;
+ }
+
+ if(!this.boxReady){
+ this.width = w;
+ this.height = h;
+ return this;
+ }
+
+
+ if(this.cacheSizes !== false && this.lastSize && this.lastSize.width == w && this.lastSize.height == h){
+ return this;
+ }
+ this.lastSize = {width: w, height: h};
+ var adj = this.adjustSize(w, h),
+ aw = adj.width,
+ ah = adj.height,
+ rz;
+ if(aw !== undefined || ah !== undefined){
+ rz = this.getResizeEl();
+ if(!this.deferHeight && aw !== undefined && ah !== undefined){
+ rz.setSize(aw, ah);
+ }else if(!this.deferHeight && ah !== undefined){
+ rz.setHeight(ah);
+ }else if(aw !== undefined){
+ rz.setWidth(aw);
+ }
+ this.onResize(aw, ah, w, h);
+ this.fireEvent('resize', this, aw, ah, w, h);
+ }
+ return this;
+ },
+
+
+ setWidth : function(width){
+ return this.setSize(width);
+ },
+
+
+ setHeight : function(height){
+ return this.setSize(undefined, height);
+ },
+
+
+ getSize : function(){
+ return this.getResizeEl().getSize();
+ },
+
+
+ getWidth : function(){
+ return this.getResizeEl().getWidth();
+ },
+
+
+ getHeight : function(){
+ return this.getResizeEl().getHeight();
+ },
+
+
+ getOuterSize : function(){
+ var el = this.getResizeEl();
+ return {width: el.getWidth() + el.getMargins('lr'),
+ height: el.getHeight() + el.getMargins('tb')};
+ },
+
+
+ getPosition : function(local){
+ var el = this.getPositionEl();
+ if(local === true){
+ return [el.getLeft(true), el.getTop(true)];
+ }
+ return this.xy || el.getXY();
+ },
+
+
+ getBox : function(local){
+ var pos = this.getPosition(local);
+ var s = this.getSize();
+ s.x = pos[0];
+ s.y = pos[1];
+ return s;
+ },
+
+
+ updateBox : function(box){
+ this.setSize(box.width, box.height);
+ this.setPagePosition(box.x, box.y);
+ return this;
+ },
+
+
+ getResizeEl : function(){
+ return this.resizeEl || this.el;
+ },
+
+
+ setAutoScroll : function(scroll){
+ if(this.rendered){
+ this.getContentTarget().setOverflow(scroll ? 'auto' : '');
+ }
+ this.autoScroll = scroll;
+ return this;
+ },
+
+
+ setPosition : function(x, y){
+ if(x && typeof x[1] == 'number'){
+ y = x[1];
+ x = x[0];
+ }
+ this.x = x;
+ this.y = y;
+ if(!this.boxReady){
+ return this;
+ }
+ var adj = this.adjustPosition(x, y);
+ var ax = adj.x, ay = adj.y;
+
+ var el = this.getPositionEl();
+ if(ax !== undefined || ay !== undefined){
+ if(ax !== undefined && ay !== undefined){
+ el.setLeftTop(ax, ay);
+ }else if(ax !== undefined){
+ el.setLeft(ax);
+ }else if(ay !== undefined){
+ el.setTop(ay);
+ }
+ this.onPosition(ax, ay);
+ this.fireEvent('move', this, ax, ay);
+ }
+ return this;
+ },
+
+
+ setPagePosition : function(x, y){
+ if(x && typeof x[1] == 'number'){
+ y = x[1];
+ x = x[0];
+ }
+ this.pageX = x;
+ this.pageY = y;
+ if(!this.boxReady){
+ return;
+ }
+ if(x === undefined || y === undefined){
+ return;
+ }
+ var p = this.getPositionEl().translatePoints(x, y);
+ this.setPosition(p.left, p.top);
+ return this;
+ },
+
+
+ afterRender : function(){
+ Ext.BoxComponent.superclass.afterRender.call(this);
+ if(this.resizeEl){
+ this.resizeEl = Ext.get(this.resizeEl);
+ }
+ if(this.positionEl){
+ this.positionEl = Ext.get(this.positionEl);
+ }
+ this.boxReady = true;
+ Ext.isDefined(this.autoScroll) && this.setAutoScroll(this.autoScroll);
+ this.setSize(this.width, this.height);
+ if(this.x || this.y){
+ this.setPosition(this.x, this.y);
+ }else if(this.pageX || this.pageY){
+ this.setPagePosition(this.pageX, this.pageY);
+ }
+ },
+
+
+ syncSize : function(){
+ delete this.lastSize;
+ this.setSize(this.autoWidth ? undefined : this.getResizeEl().getWidth(), this.autoHeight ? undefined : this.getResizeEl().getHeight());
+ return this;
+ },
+
+
+ onResize : function(adjWidth, adjHeight, rawWidth, rawHeight){
+ },
+
+
+ onPosition : function(x, y){
+
+ },
+
+
+ adjustSize : function(w, h){
+ if(this.autoWidth){
+ w = 'auto';
+ }
+ if(this.autoHeight){
+ h = 'auto';
+ }
+ return {width : w, height: h};
+ },
+
+
+ adjustPosition : function(x, y){
+ return {x : x, y: y};
+ }
+});
+Ext.reg('box', Ext.BoxComponent);
+
+
+
+Ext.Spacer = Ext.extend(Ext.BoxComponent, {
+ autoEl:'div'
+});
+Ext.reg('spacer', Ext.Spacer);
+Ext.SplitBar = function(dragElement, resizingElement, orientation, placement, existingProxy){
+
+
+ this.el = Ext.get(dragElement, true);
+ this.el.unselectable();
+
+ this.resizingEl = Ext.get(resizingElement, true);
+
+
+ this.orientation = orientation || Ext.SplitBar.HORIZONTAL;
+
+
+
+ this.minSize = 0;
+
+
+ this.maxSize = 2000;
+
+
+ this.animate = false;
+
+
+ this.useShim = false;
+
+
+ this.shim = null;
+
+ if(!existingProxy){
+
+ this.proxy = Ext.SplitBar.createProxy(this.orientation);
+ }else{
+ this.proxy = Ext.get(existingProxy).dom;
+ }
+
+ this.dd = new Ext.dd.DDProxy(this.el.dom.id, "XSplitBars", {dragElId : this.proxy.id});
+
+
+ this.dd.b4StartDrag = this.onStartProxyDrag.createDelegate(this);
+
+
+ this.dd.endDrag = this.onEndProxyDrag.createDelegate(this);
+
+
+ this.dragSpecs = {};
+
+
+ this.adapter = new Ext.SplitBar.BasicLayoutAdapter();
+ this.adapter.init(this);
+
+ if(this.orientation == Ext.SplitBar.HORIZONTAL){
+
+ this.placement = placement || (this.el.getX() > this.resizingEl.getX() ? Ext.SplitBar.LEFT : Ext.SplitBar.RIGHT);
+ this.el.addClass("x-splitbar-h");
+ }else{
+
+ this.placement = placement || (this.el.getY() > this.resizingEl.getY() ? Ext.SplitBar.TOP : Ext.SplitBar.BOTTOM);
+ this.el.addClass("x-splitbar-v");
+ }
+
+ this.addEvents(
+
+ "resize",
+
+ "moved",
+
+ "beforeresize",
+
+ "beforeapply"
+ );
+
+ Ext.SplitBar.superclass.constructor.call(this);
+};
+
+Ext.extend(Ext.SplitBar, Ext.util.Observable, {
+ onStartProxyDrag : function(x, y){
+ this.fireEvent("beforeresize", this);
+ this.overlay = Ext.DomHelper.append(document.body, {cls: "x-drag-overlay", html: "&#160;"}, true);
+ this.overlay.unselectable();
+ this.overlay.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));
+ this.overlay.show();
+ Ext.get(this.proxy).setDisplayed("block");
+ var size = this.adapter.getElementSize(this);
+ this.activeMinSize = this.getMinimumSize();
+ this.activeMaxSize = this.getMaximumSize();
+ var c1 = size - this.activeMinSize;
+ var c2 = Math.max(this.activeMaxSize - size, 0);
+ if(this.orientation == Ext.SplitBar.HORIZONTAL){
+ this.dd.resetConstraints();
+ this.dd.setXConstraint(
+ this.placement == Ext.SplitBar.LEFT ? c1 : c2,
+ this.placement == Ext.SplitBar.LEFT ? c2 : c1,
+ this.tickSize
+ );
+ this.dd.setYConstraint(0, 0);
+ }else{
+ this.dd.resetConstraints();
+ this.dd.setXConstraint(0, 0);
+ this.dd.setYConstraint(
+ this.placement == Ext.SplitBar.TOP ? c1 : c2,
+ this.placement == Ext.SplitBar.TOP ? c2 : c1,
+ this.tickSize
+ );
+ }
+ this.dragSpecs.startSize = size;
+ this.dragSpecs.startPoint = [x, y];
+ Ext.dd.DDProxy.prototype.b4StartDrag.call(this.dd, x, y);
+ },
+
+
+ onEndProxyDrag : function(e){
+ Ext.get(this.proxy).setDisplayed(false);
+ var endPoint = Ext.lib.Event.getXY(e);
+ if(this.overlay){
+ Ext.destroy(this.overlay);
+ delete this.overlay;
+ }
+ var newSize;
+ if(this.orientation == Ext.SplitBar.HORIZONTAL){
+ newSize = this.dragSpecs.startSize +
+ (this.placement == Ext.SplitBar.LEFT ?
+ endPoint[0] - this.dragSpecs.startPoint[0] :
+ this.dragSpecs.startPoint[0] - endPoint[0]
+ );
+ }else{
+ newSize = this.dragSpecs.startSize +
+ (this.placement == Ext.SplitBar.TOP ?
+ endPoint[1] - this.dragSpecs.startPoint[1] :
+ this.dragSpecs.startPoint[1] - endPoint[1]
+ );
+ }
+ newSize = Math.min(Math.max(newSize, this.activeMinSize), this.activeMaxSize);
+ if(newSize != this.dragSpecs.startSize){
+ if(this.fireEvent('beforeapply', this, newSize) !== false){
+ this.adapter.setElementSize(this, newSize);
+ this.fireEvent("moved", this, newSize);
+ this.fireEvent("resize", this, newSize);
+ }
+ }
+ },
+
+
+ getAdapter : function(){
+ return this.adapter;
+ },
+
+
+ setAdapter : function(adapter){
+ this.adapter = adapter;
+ this.adapter.init(this);
+ },
+
+
+ getMinimumSize : function(){
+ return this.minSize;
+ },
+
+
+ setMinimumSize : function(minSize){
+ this.minSize = minSize;
+ },
+
+
+ getMaximumSize : function(){
+ return this.maxSize;
+ },
+
+
+ setMaximumSize : function(maxSize){
+ this.maxSize = maxSize;
+ },
+
+
+ setCurrentSize : function(size){
+ var oldAnimate = this.animate;
+ this.animate = false;
+ this.adapter.setElementSize(this, size);
+ this.animate = oldAnimate;
+ },
+
+
+ destroy : function(removeEl){
+ Ext.destroy(this.shim, Ext.get(this.proxy));
+ this.dd.unreg();
+ if(removeEl){
+ this.el.remove();
+ }
+ this.purgeListeners();
+ }
+});
+
+
+Ext.SplitBar.createProxy = function(dir){
+ var proxy = new Ext.Element(document.createElement("div"));
+ document.body.appendChild(proxy.dom);
+ proxy.unselectable();
+ var cls = 'x-splitbar-proxy';
+ proxy.addClass(cls + ' ' + (dir == Ext.SplitBar.HORIZONTAL ? cls +'-h' : cls + '-v'));
+ return proxy.dom;
+};
+
+
+Ext.SplitBar.BasicLayoutAdapter = function(){
+};
+
+Ext.SplitBar.BasicLayoutAdapter.prototype = {
+
+ init : function(s){
+
+ },
+
+ getElementSize : function(s){
+ if(s.orientation == Ext.SplitBar.HORIZONTAL){
+ return s.resizingEl.getWidth();
+ }else{
+ return s.resizingEl.getHeight();
+ }
+ },
+
+
+ setElementSize : function(s, newSize, onComplete){
+ if(s.orientation == Ext.SplitBar.HORIZONTAL){
+ if(!s.animate){
+ s.resizingEl.setWidth(newSize);
+ if(onComplete){
+ onComplete(s, newSize);
+ }
+ }else{
+ s.resizingEl.setWidth(newSize, true, .1, onComplete, 'easeOut');
+ }
+ }else{
+
+ if(!s.animate){
+ s.resizingEl.setHeight(newSize);
+ if(onComplete){
+ onComplete(s, newSize);
+ }
+ }else{
+ s.resizingEl.setHeight(newSize, true, .1, onComplete, 'easeOut');
+ }
+ }
+ }
+};
+
+
+Ext.SplitBar.AbsoluteLayoutAdapter = function(container){
+ this.basic = new Ext.SplitBar.BasicLayoutAdapter();
+ this.container = Ext.get(container);
+};
+
+Ext.SplitBar.AbsoluteLayoutAdapter.prototype = {
+ init : function(s){
+ this.basic.init(s);
+ },
+
+ getElementSize : function(s){
+ return this.basic.getElementSize(s);
+ },
+
+ setElementSize : function(s, newSize, onComplete){
+ this.basic.setElementSize(s, newSize, this.moveSplitter.createDelegate(this, [s]));
+ },
+
+ moveSplitter : function(s){
+ var yes = Ext.SplitBar;
+ switch(s.placement){
+ case yes.LEFT:
+ s.el.setX(s.resizingEl.getRight());
+ break;
+ case yes.RIGHT:
+ s.el.setStyle("right", (this.container.getWidth() - s.resizingEl.getLeft()) + "px");
+ break;
+ case yes.TOP:
+ s.el.setY(s.resizingEl.getBottom());
+ break;
+ case yes.BOTTOM:
+ s.el.setY(s.resizingEl.getTop() - s.el.getHeight());
+ break;
+ }
+ }
+};
+
+
+Ext.SplitBar.VERTICAL = 1;
+
+
+Ext.SplitBar.HORIZONTAL = 2;
+
+
+Ext.SplitBar.LEFT = 1;
+
+
+Ext.SplitBar.RIGHT = 2;
+
+
+Ext.SplitBar.TOP = 3;
+
+
+Ext.SplitBar.BOTTOM = 4;
+
+Ext.Container = Ext.extend(Ext.BoxComponent, {
+
+
+
+
+ bufferResize: 50,
+
+
+
+
+
+
+
+ autoDestroy : true,
+
+
+ forceLayout: false,
+
+
+
+ defaultType : 'panel',
+
+
+ resizeEvent: 'resize',
+
+
+ bubbleEvents: ['add', 'remove'],
+
+
+ initComponent : function(){
+ Ext.Container.superclass.initComponent.call(this);
+
+ this.addEvents(
+
+ 'afterlayout',
+
+ 'beforeadd',
+
+ 'beforeremove',
+
+ 'add',
+
+ 'remove'
+ );
+
+
+ var items = this.items;
+ if(items){
+ delete this.items;
+ this.add(items);
+ }
+ },
+
+
+ initItems : function(){
+ if(!this.items){
+ this.items = new Ext.util.MixedCollection(false, this.getComponentId);
+ this.getLayout();
+ }
+ },
+
+
+ setLayout : function(layout){
+ if(this.layout && this.layout != layout){
+ this.layout.setContainer(null);
+ }
+ this.layout = layout;
+ this.initItems();
+ layout.setContainer(this);
+ },
+
+ afterRender: function(){
+
+
+ Ext.Container.superclass.afterRender.call(this);
+ if(!this.layout){
+ this.layout = 'auto';
+ }
+ if(Ext.isObject(this.layout) && !this.layout.layout){
+ this.layoutConfig = this.layout;
+ this.layout = this.layoutConfig.type;
+ }
+ if(Ext.isString(this.layout)){
+ this.layout = new Ext.Container.LAYOUTS[this.layout.toLowerCase()](this.layoutConfig);
+ }
+ this.setLayout(this.layout);
+
+
+ if(this.activeItem !== undefined && this.layout.setActiveItem){
+ var item = this.activeItem;
+ delete this.activeItem;
+ this.layout.setActiveItem(item);
+ }
+
+
+ if(!this.ownerCt){
+ this.doLayout(false, true);
+ }
+
+
+
+ if(this.monitorResize === true){
+ Ext.EventManager.onWindowResize(this.doLayout, this, [false]);
+ }
+ },
+
+
+ getLayoutTarget : function(){
+ return this.el;
+ },
+
+
+ getComponentId : function(comp){
+ return comp.getItemId();
+ },
+
+
+ add : function(comp){
+ this.initItems();
+ var args = arguments.length > 1;
+ if(args || Ext.isArray(comp)){
+ var result = [];
+ Ext.each(args ? arguments : comp, function(c){
+ result.push(this.add(c));
+ }, this);
+ return result;
+ }
+ var c = this.lookupComponent(this.applyDefaults(comp));
+ var index = this.items.length;
+ if(this.fireEvent('beforeadd', this, c, index) !== false && this.onBeforeAdd(c) !== false){
+ this.items.add(c);
+
+ c.onAdded(this, index);
+ this.onAdd(c);
+ this.fireEvent('add', this, c, index);
+ }
+ return c;
+ },
+
+ onAdd : function(c){
+
+ },
+
+
+ onAdded : function(container, pos) {
+
+ this.ownerCt = container;
+ this.initRef();
+
+ this.cascade(function(c){
+ c.initRef();
+ });
+ this.fireEvent('added', this, container, pos);
+ },
+
+
+ insert : function(index, comp) {
+ var args = arguments,
+ length = args.length,
+ result = [],
+ i, c;
+
+ this.initItems();
+
+ if (length > 2) {
+ for (i = length - 1; i >= 1; --i) {
+ result.push(this.insert(index, args[i]));
+ }
+ return result;
+ }
+
+ c = this.lookupComponent(this.applyDefaults(comp));
+ index = Math.min(index, this.items.length);
+
+ if (this.fireEvent('beforeadd', this, c, index) !== false && this.onBeforeAdd(c) !== false) {
+ if (c.ownerCt == this) {
+ this.items.remove(c);
+ }
+ this.items.insert(index, c);
+ c.onAdded(this, index);
+ this.onAdd(c);
+ this.fireEvent('add', this, c, index);
+ }
+
+ return c;
+ },
+
+
+ applyDefaults : function(c){
+ var d = this.defaults;
+ if(d){
+ if(Ext.isFunction(d)){
+ d = d.call(this, c);
+ }
+ if(Ext.isString(c)){
+ c = Ext.ComponentMgr.get(c);
+ Ext.apply(c, d);
+ }else if(!c.events){
+ Ext.applyIf(c.isAction ? c.initialConfig : c, d);
+ }else{
+ Ext.apply(c, d);
+ }
+ }
+ return c;
+ },
+
+
+ onBeforeAdd : function(item){
+ if(item.ownerCt){
+ item.ownerCt.remove(item, false);
+ }
+ if(this.hideBorders === true){
+ item.border = (item.border === true);
+ }
+ },
+
+
+ remove : function(comp, autoDestroy){
+ this.initItems();
+ var c = this.getComponent(comp);
+ if(c && this.fireEvent('beforeremove', this, c) !== false){
+ this.doRemove(c, autoDestroy);
+ this.fireEvent('remove', this, c);
+ }
+ return c;
+ },
+
+ onRemove: function(c){
+
+ },
+
+
+ doRemove: function(c, autoDestroy){
+ var l = this.layout,
+ hasLayout = l && this.rendered;
+
+ if(hasLayout){
+ l.onRemove(c);
+ }
+ this.items.remove(c);
+ c.onRemoved();
+ this.onRemove(c);
+ if(autoDestroy === true || (autoDestroy !== false && this.autoDestroy)){
+ c.destroy();
+ }
+ if(hasLayout){
+ l.afterRemove(c);
+ }
+ },
+
+
+ removeAll: function(autoDestroy){
+ this.initItems();
+ var item, rem = [], items = [];
+ this.items.each(function(i){
+ rem.push(i);
+ });
+ for (var i = 0, len = rem.length; i < len; ++i){
+ item = rem[i];
+ this.remove(item, autoDestroy);
+ if(item.ownerCt !== this){
+ items.push(item);
+ }
+ }
+ return items;
+ },
+
+
+ getComponent : function(comp){
+ if(Ext.isObject(comp)){
+ comp = comp.getItemId();
+ }
+ return this.items.get(comp);
+ },
+
+
+ lookupComponent : function(comp){
+ if(Ext.isString(comp)){
+ return Ext.ComponentMgr.get(comp);
+ }else if(!comp.events){
+ return this.createComponent(comp);
+ }
+ return comp;
+ },
+
+
+ createComponent : function(config, defaultType){
+ if (config.render) {
+ return config;
+ }
+
+
+ var c = Ext.create(Ext.apply({
+ ownerCt: this
+ }, config), defaultType || this.defaultType);
+ delete c.initialConfig.ownerCt;
+ delete c.ownerCt;
+ return c;
+ },
+
+
+ canLayout : function() {
+ var el = this.getVisibilityEl();
+ return el && el.dom && !el.isStyle("display", "none");
+ },
+
+
+
+ doLayout : function(shallow, force){
+ var rendered = this.rendered,
+ forceLayout = force || this.forceLayout;
+
+ if(this.collapsed || !this.canLayout()){
+ this.deferLayout = this.deferLayout || !shallow;
+ if(!forceLayout){
+ return;
+ }
+ shallow = shallow && !this.deferLayout;
+ } else {
+ delete this.deferLayout;
+ }
+ if(rendered && this.layout){
+ this.layout.layout();
+ }
+ if(shallow !== true && this.items){
+ var cs = this.items.items;
+ for(var i = 0, len = cs.length; i < len; i++){
+ var c = cs[i];
+ if(c.doLayout){
+ c.doLayout(false, forceLayout);
+ }
+ }
+ }
+ if(rendered){
+ this.onLayout(shallow, forceLayout);
+ }
+
+ this.hasLayout = true;
+ delete this.forceLayout;
+ },
+
+ onLayout : Ext.emptyFn,
+
+
+ shouldBufferLayout: function(){
+
+ var hl = this.hasLayout;
+ if(this.ownerCt){
+
+ return hl ? !this.hasLayoutPending() : false;
+ }
+
+ return hl;
+ },
+
+
+ hasLayoutPending: function(){
+
+ var pending = false;
+ this.ownerCt.bubble(function(c){
+ if(c.layoutPending){
+ pending = true;
+ return false;
+ }
+ });
+ return pending;
+ },
+
+ onShow : function(){
+
+ Ext.Container.superclass.onShow.call(this);
+
+ if(Ext.isDefined(this.deferLayout)){
+ delete this.deferLayout;
+ this.doLayout(true);
+ }
+ },
+
+
+ getLayout : function(){
+ if(!this.layout){
+ var layout = new Ext.layout.AutoLayout(this.layoutConfig);
+ this.setLayout(layout);
+ }
+ return this.layout;
+ },
+
+
+ beforeDestroy : function(){
+ var c;
+ if(this.items){
+ while(c = this.items.first()){
+ this.doRemove(c, true);
+ }
+ }
+ if(this.monitorResize){
+ Ext.EventManager.removeResizeListener(this.doLayout, this);
+ }
+ Ext.destroy(this.layout);
+ Ext.Container.superclass.beforeDestroy.call(this);
+ },
+
+
+ cascade : function(fn, scope, args){
+ if(fn.apply(scope || this, args || [this]) !== false){
+ if(this.items){
+ var cs = this.items.items;
+ for(var i = 0, len = cs.length; i < len; i++){
+ if(cs[i].cascade){
+ cs[i].cascade(fn, scope, args);
+ }else{
+ fn.apply(scope || cs[i], args || [cs[i]]);
+ }
+ }
+ }
+ }
+ return this;
+ },
+
+
+ findById : function(id){
+ var m = null,
+ ct = this;
+ this.cascade(function(c){
+ if(ct != c && c.id === id){
+ m = c;
+ return false;
+ }
+ });
+ return m;
+ },
+
+
+ findByType : function(xtype, shallow){
+ return this.findBy(function(c){
+ return c.isXType(xtype, shallow);
+ });
+ },
+
+
+ find : function(prop, value){
+ return this.findBy(function(c){
+ return c[prop] === value;
+ });
+ },
+
+
+ findBy : function(fn, scope){
+ var m = [], ct = this;
+ this.cascade(function(c){
+ if(ct != c && fn.call(scope || c, c, ct) === true){
+ m.push(c);
+ }
+ });
+ return m;
+ },
+
+
+ get : function(key){
+ return this.getComponent(key);
+ }
+});
+
+Ext.Container.LAYOUTS = {};
+Ext.reg('container', Ext.Container);
+
+Ext.layout.ContainerLayout = Ext.extend(Object, {
+
+
+
+
+
+
+ monitorResize:false,
+
+ activeItem : null,
+
+ constructor : function(config){
+ this.id = Ext.id(null, 'ext-layout-');
+ Ext.apply(this, config);
+ },
+
+ type: 'container',
+
+
+ IEMeasureHack : function(target, viewFlag) {
+ var tChildren = target.dom.childNodes, tLen = tChildren.length, c, d = [], e, i, ret;
+ for (i = 0 ; i < tLen ; i++) {
+ c = tChildren[i];
+ e = Ext.get(c);
+ if (e) {
+ d[i] = e.getStyle('display');
+ e.setStyle({display: 'none'});
+ }
+ }
+ ret = target ? target.getViewSize(viewFlag) : {};
+ for (i = 0 ; i < tLen ; i++) {
+ c = tChildren[i];
+ e = Ext.get(c);
+ if (e) {
+ e.setStyle({display: d[i]});
+ }
+ }
+ return ret;
+ },
+
+
+ getLayoutTargetSize : Ext.EmptyFn,
+
+
+ layout : function(){
+ var ct = this.container, target = ct.getLayoutTarget();
+ if(!(this.hasLayout || Ext.isEmpty(this.targetCls))){
+ target.addClass(this.targetCls);
+ }
+ this.onLayout(ct, target);
+ ct.fireEvent('afterlayout', ct, this);
+ },
+
+
+ onLayout : function(ct, target){
+ this.renderAll(ct, target);
+ },
+
+
+ isValidParent : function(c, target){
+ return target && c.getPositionEl().dom.parentNode == (target.dom || target);
+ },
+
+
+ renderAll : function(ct, target){
+ var items = ct.items.items, i, c, len = items.length;
+ for(i = 0; i < len; i++) {
+ c = items[i];
+ if(c && (!c.rendered || !this.isValidParent(c, target))){
+ this.renderItem(c, i, target);
+ }
+ }
+ },
+
+
+ renderItem : function(c, position, target){
+ if (c) {
+ if (!c.rendered) {
+ c.render(target, position);
+ this.configureItem(c);
+ } else if (!this.isValidParent(c, target)) {
+ if (Ext.isNumber(position)) {
+ position = target.dom.childNodes[position];
+ }
+
+ target.dom.insertBefore(c.getPositionEl().dom, position || null);
+ c.container = target;
+ this.configureItem(c);
+ }
+ }
+ },
+
+
+
+ getRenderedItems: function(ct){
+ var t = ct.getLayoutTarget(), cti = ct.items.items, len = cti.length, i, c, items = [];
+ for (i = 0; i < len; i++) {
+ if((c = cti[i]).rendered && this.isValidParent(c, t) && c.shouldLayout !== false){
+ items.push(c);
+ }
+ };
+ return items;
+ },
+
+
+ configureItem: function(c){
+ if (this.extraCls) {
+ var t = c.getPositionEl ? c.getPositionEl() : c;
+ t.addClass(this.extraCls);
+ }
+
+
+ if (c.doLayout && this.forceLayout) {
+ c.doLayout();
+ }
+ if (this.renderHidden && c != this.activeItem) {
+ c.hide();
+ }
+ },
+
+ onRemove: function(c){
+ if(this.activeItem == c){
+ delete this.activeItem;
+ }
+ if(c.rendered && this.extraCls){
+ var t = c.getPositionEl ? c.getPositionEl() : c;
+ t.removeClass(this.extraCls);
+ }
+ },
+
+ afterRemove: function(c){
+ if(c.removeRestore){
+ c.removeMode = 'container';
+ delete c.removeRestore;
+ }
+ },
+
+
+ onResize: function(){
+ var ct = this.container,
+ b;
+ if(ct.collapsed){
+ return;
+ }
+ if(b = ct.bufferResize && ct.shouldBufferLayout()){
+ if(!this.resizeTask){
+ this.resizeTask = new Ext.util.DelayedTask(this.runLayout, this);
+ this.resizeBuffer = Ext.isNumber(b) ? b : 50;
+ }
+ ct.layoutPending = true;
+ this.resizeTask.delay(this.resizeBuffer);
+ }else{
+ this.runLayout();
+ }
+ },
+
+ runLayout: function(){
+ var ct = this.container;
+ this.layout();
+ ct.onLayout();
+ delete ct.layoutPending;
+ },
+
+
+ setContainer : function(ct){
+
+ if(this.monitorResize && ct != this.container){
+ var old = this.container;
+ if(old){
+ old.un(old.resizeEvent, this.onResize, this);
+ }
+ if(ct){
+ ct.on(ct.resizeEvent, this.onResize, this);
+ }
+ }
+ this.container = ct;
+ },
+
+
+ parseMargins : function(v){
+ if (Ext.isNumber(v)) {
+ v = v.toString();
+ }
+ var ms = v.split(' '),
+ len = ms.length;
+
+ if (len == 1) {
+ ms[1] = ms[2] = ms[3] = ms[0];
+ } else if(len == 2) {
+ ms[2] = ms[0];
+ ms[3] = ms[1];
+ } else if(len == 3) {
+ ms[3] = ms[1];
+ }
+
+ return {
+ top :parseInt(ms[0], 10) || 0,
+ right :parseInt(ms[1], 10) || 0,
+ bottom:parseInt(ms[2], 10) || 0,
+ left :parseInt(ms[3], 10) || 0
+ };
+ },
+
+
+ fieldTpl: (function() {
+ var t = new Ext.Template(
+ '<div class="x-form-item {itemCls}" tabIndex="-1">',
+ '<label for="{id}" style="{labelStyle}" class="x-form-item-label">{label}{labelSeparator}</label>',
+ '<div class="x-form-element" id="x-form-el-{id}" style="{elementStyle}">',
+ '</div><div class="{clearCls}"></div>',
+ '</div>'
+ );
+ t.disableFormats = true;
+ return t.compile();
+ })(),
+
+
+ destroy : function(){
+
+ if(this.resizeTask && this.resizeTask.cancel){
+ this.resizeTask.cancel();
+ }
+ if(this.container) {
+ this.container.un(this.container.resizeEvent, this.onResize, this);
+ }
+ if(!Ext.isEmpty(this.targetCls)){
+ var target = this.container.getLayoutTarget();
+ if(target){
+ target.removeClass(this.targetCls);
+ }
+ }
+ }
+});
+Ext.layout.AutoLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ type: 'auto',
+
+ monitorResize: true,
+
+ onLayout : function(ct, target){
+ Ext.layout.AutoLayout.superclass.onLayout.call(this, ct, target);
+ var cs = this.getRenderedItems(ct), len = cs.length, i, c;
+ for(i = 0; i < len; i++){
+ c = cs[i];
+ if (c.doLayout){
+
+ c.doLayout(true);
+ }
+ }
+ }
+});
+
+Ext.Container.LAYOUTS['auto'] = Ext.layout.AutoLayout;
+
+Ext.layout.FitLayout = Ext.extend(Ext.layout.ContainerLayout, {
+
+ monitorResize:true,
+
+ type: 'fit',
+
+ getLayoutTargetSize : function() {
+ var target = this.container.getLayoutTarget();
+ if (!target) {
+ return {};
+ }
+
+ return target.getStyleSize();
+ },
+
+
+ onLayout : function(ct, target){
+ Ext.layout.FitLayout.superclass.onLayout.call(this, ct, target);
+ if(!ct.collapsed){
+ this.setItemSize(this.activeItem || ct.items.itemAt(0), this.getLayoutTargetSize());
+ }
+ },
+
+
+ setItemSize : function(item, size){
+ if(item && size.height > 0){
+ item.setSize(size);
+ }
+ }
+});
+Ext.Container.LAYOUTS['fit'] = Ext.layout.FitLayout;
+Ext.layout.CardLayout = Ext.extend(Ext.layout.FitLayout, {
+
+ deferredRender : false,
+
+
+ layoutOnCardChange : false,
+
+
+
+ renderHidden : true,
+
+ type: 'card',
+
+
+ setActiveItem : function(item){
+ var ai = this.activeItem,
+ ct = this.container;
+ item = ct.getComponent(item);
+
+
+ if(item && ai != item){
+
+
+ if(ai){
+ ai.hide();
+ if (ai.hidden !== true) {
+ return false;
+ }
+ ai.fireEvent('deactivate', ai);
+ }
+
+ var layout = item.doLayout && (this.layoutOnCardChange || !item.rendered);
+
+
+ this.activeItem = item;
+
+
+
+ delete item.deferLayout;
+
+
+ item.show();
+
+ this.layout();
+
+ if(layout){
+ item.doLayout();
+ }
+ item.fireEvent('activate', item);
+ }
+ },
+
+
+ renderAll : function(ct, target){
+ if(this.deferredRender){
+ this.renderItem(this.activeItem, undefined, target);
+ }else{
+ Ext.layout.CardLayout.superclass.renderAll.call(this, ct, target);
+ }
+ }
+});
+Ext.Container.LAYOUTS['card'] = Ext.layout.CardLayout;
+
+Ext.layout.AnchorLayout = Ext.extend(Ext.layout.ContainerLayout, {
+
+
+
+ monitorResize : true,
+
+ type : 'anchor',
+
+
+ defaultAnchor : '100%',
+
+ parseAnchorRE : /^(r|right|b|bottom)$/i,
+
+
+ getLayoutTargetSize : function() {
+ var target = this.container.getLayoutTarget(), ret = {};
+ if (target) {
+ ret = target.getViewSize();
+
+
+
+
+ if (Ext.isIE9m && Ext.isStrict && ret.width == 0){
+ ret = target.getStyleSize();
+ }
+ ret.width -= target.getPadding('lr');
+ ret.height -= target.getPadding('tb');
+ }
+ return ret;
+ },
+
+
+ onLayout : function(container, target) {
+ Ext.layout.AnchorLayout.superclass.onLayout.call(this, container, target);
+
+ var size = this.getLayoutTargetSize(),
+ containerWidth = size.width,
+ containerHeight = size.height,
+ overflow = target.getStyle('overflow'),
+ components = this.getRenderedItems(container),
+ len = components.length,
+ boxes = [],
+ box,
+ anchorWidth,
+ anchorHeight,
+ component,
+ anchorSpec,
+ calcWidth,
+ calcHeight,
+ anchorsArray,
+ totalHeight = 0,
+ i,
+ el;
+
+ if(containerWidth < 20 && containerHeight < 20){
+ return;
+ }
+
+
+ if(container.anchorSize) {
+ if(typeof container.anchorSize == 'number') {
+ anchorWidth = container.anchorSize;
+ } else {
+ anchorWidth = container.anchorSize.width;
+ anchorHeight = container.anchorSize.height;
+ }
+ } else {
+ anchorWidth = container.initialConfig.width;
+ anchorHeight = container.initialConfig.height;
+ }
+
+ for(i = 0; i < len; i++) {
+ component = components[i];
+ el = component.getPositionEl();
+
+
+ if (!component.anchor && component.items && !Ext.isNumber(component.width) && !(Ext.isIE6 && Ext.isStrict)){
+ component.anchor = this.defaultAnchor;
+ }
+
+ if(component.anchor) {
+ anchorSpec = component.anchorSpec;
+
+ if(!anchorSpec){
+ anchorsArray = component.anchor.split(' ');
+ component.anchorSpec = anchorSpec = {
+ right: this.parseAnchor(anchorsArray[0], component.initialConfig.width, anchorWidth),
+ bottom: this.parseAnchor(anchorsArray[1], component.initialConfig.height, anchorHeight)
+ };
+ }
+ calcWidth = anchorSpec.right ? this.adjustWidthAnchor(anchorSpec.right(containerWidth) - el.getMargins('lr'), component) : undefined;
+ calcHeight = anchorSpec.bottom ? this.adjustHeightAnchor(anchorSpec.bottom(containerHeight) - el.getMargins('tb'), component) : undefined;
+
+ if(calcWidth || calcHeight) {
+ boxes.push({
+ component: component,
+ width: calcWidth || undefined,
+ height: calcHeight || undefined
+ });
+ }
+ }
+ }
+ for (i = 0, len = boxes.length; i < len; i++) {
+ box = boxes[i];
+ box.component.setSize(box.width, box.height);
+ }
+
+ if (overflow && overflow != 'hidden' && !this.adjustmentPass) {
+ var newTargetSize = this.getLayoutTargetSize();
+ if (newTargetSize.width != size.width || newTargetSize.height != size.height){
+ this.adjustmentPass = true;
+ this.onLayout(container, target);
+ }
+ }
+
+ delete this.adjustmentPass;
+ },
+
+
+ parseAnchor : function(a, start, cstart) {
+ if (a && a != 'none') {
+ var last;
+
+ if (this.parseAnchorRE.test(a)) {
+ var diff = cstart - start;
+ return function(v){
+ if(v !== last){
+ last = v;
+ return v - diff;
+ }
+ };
+
+ } else if(a.indexOf('%') != -1) {
+ var ratio = parseFloat(a.replace('%', ''))*.01;
+ return function(v){
+ if(v !== last){
+ last = v;
+ return Math.floor(v*ratio);
+ }
+ };
+
+ } else {
+ a = parseInt(a, 10);
+ if (!isNaN(a)) {
+ return function(v) {
+ if (v !== last) {
+ last = v;
+ return v + a;
+ }
+ };
+ }
+ }
+ }
+ return false;
+ },
+
+
+ adjustWidthAnchor : function(value, comp){
+ return value;
+ },
+
+
+ adjustHeightAnchor : function(value, comp){
+ return value;
+ }
+
+
+});
+Ext.Container.LAYOUTS['anchor'] = Ext.layout.AnchorLayout;
+
+Ext.layout.ColumnLayout = Ext.extend(Ext.layout.ContainerLayout, {
+
+ monitorResize:true,
+
+ type: 'column',
+
+ extraCls: 'x-column',
+
+ scrollOffset : 0,
+
+
+
+ targetCls: 'x-column-layout-ct',
+
+ isValidParent : function(c, target){
+ return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom;
+ },
+
+ getLayoutTargetSize : function() {
+ var target = this.container.getLayoutTarget(), ret;
+ if (target) {
+ ret = target.getViewSize();
+
+
+
+
+ if (Ext.isIE9m && Ext.isStrict && ret.width == 0){
+ ret = target.getStyleSize();
+ }
+
+ ret.width -= target.getPadding('lr');
+ ret.height -= target.getPadding('tb');
+ }
+ return ret;
+ },
+
+ renderAll : function(ct, target) {
+ if(!this.innerCt){
+
+
+ this.innerCt = target.createChild({cls:'x-column-inner'});
+ this.innerCt.createChild({cls:'x-clear'});
+ }
+ Ext.layout.ColumnLayout.superclass.renderAll.call(this, ct, this.innerCt);
+ },
+
+
+ onLayout : function(ct, target){
+ var cs = ct.items.items,
+ len = cs.length,
+ c,
+ i,
+ m,
+ margins = [];
+
+ this.renderAll(ct, target);
+
+ var size = this.getLayoutTargetSize();
+
+ if (Ext.isIE9m && (size.width < 1 && size.height < 1)) {
+ return;
+ }
+
+ var w = size.width - this.scrollOffset,
+ h = size.height,
+ pw = w;
+
+ this.innerCt.setWidth(w);
+
+
+
+
+ for(i = 0; i < len; i++){
+ c = cs[i];
+ m = c.getPositionEl().getMargins('lr');
+ margins[i] = m;
+ if(!c.columnWidth){
+ pw -= (c.getWidth() + m);
+ }
+ }
+
+ pw = pw < 0 ? 0 : pw;
+
+ for(i = 0; i < len; i++){
+ c = cs[i];
+ m = margins[i];
+ if(c.columnWidth){
+ c.setSize(Math.floor(c.columnWidth * pw) - m);
+ }
+ }
+
+
+
+ if (Ext.isIE9m) {
+ if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) {
+ var ts = this.getLayoutTargetSize();
+ if (ts.width != size.width){
+ this.adjustmentPass = true;
+ this.onLayout(ct, target);
+ }
+ }
+ }
+ delete this.adjustmentPass;
+ }
+
+
+});
+
+Ext.Container.LAYOUTS['column'] = Ext.layout.ColumnLayout;
+
+Ext.layout.BorderLayout = Ext.extend(Ext.layout.ContainerLayout, {
+
+ monitorResize:true,
+
+ rendered : false,
+
+ type: 'border',
+
+ targetCls: 'x-border-layout-ct',
+
+ getLayoutTargetSize : function() {
+ var target = this.container.getLayoutTarget();
+ return target ? target.getViewSize() : {};
+ },
+
+
+ onLayout : function(ct, target){
+ var collapsed, i, c, pos, items = ct.items.items, len = items.length;
+ if(!this.rendered){
+ collapsed = [];
+ for(i = 0; i < len; i++) {
+ c = items[i];
+ pos = c.region;
+ if(c.collapsed){
+ collapsed.push(c);
+ }
+ c.collapsed = false;
+ if(!c.rendered){
+ c.render(target, i);
+ c.getPositionEl().addClass('x-border-panel');
+ }
+ this[pos] = pos != 'center' && c.split ?
+ new Ext.layout.BorderLayout.SplitRegion(this, c.initialConfig, pos) :
+ new Ext.layout.BorderLayout.Region(this, c.initialConfig, pos);
+ this[pos].render(target, c);
+ }
+ this.rendered = true;
+ }
+
+ var size = this.getLayoutTargetSize();
+ if(size.width < 20 || size.height < 20){
+ if(collapsed){
+ this.restoreCollapsed = collapsed;
+ }
+ return;
+ }else if(this.restoreCollapsed){
+ collapsed = this.restoreCollapsed;
+ delete this.restoreCollapsed;
+ }
+
+ var w = size.width, h = size.height,
+ centerW = w, centerH = h, centerY = 0, centerX = 0,
+ n = this.north, s = this.south, west = this.west, e = this.east, c = this.center,
+ b, m, totalWidth, totalHeight;
+ if(!c && Ext.layout.BorderLayout.WARN !== false){
+ throw 'No center region defined in BorderLayout ' + ct.id;
+ }
+
+ if(n && n.isVisible()){
+ b = n.getSize();
+ m = n.getMargins();
+ b.width = w - (m.left+m.right);
+ b.x = m.left;
+ b.y = m.top;
+ centerY = b.height + b.y + m.bottom;
+ centerH -= centerY;
+ n.applyLayout(b);
+ }
+ if(s && s.isVisible()){
+ b = s.getSize();
+ m = s.getMargins();
+ b.width = w - (m.left+m.right);
+ b.x = m.left;
+ totalHeight = (b.height + m.top + m.bottom);
+ b.y = h - totalHeight + m.top;
+ centerH -= totalHeight;
+ s.applyLayout(b);
+ }
+ if(west && west.isVisible()){
+ b = west.getSize();
+ m = west.getMargins();
+ b.height = centerH - (m.top+m.bottom);
+ b.x = m.left;
+ b.y = centerY + m.top;
+ totalWidth = (b.width + m.left + m.right);
+ centerX += totalWidth;
+ centerW -= totalWidth;
+ west.applyLayout(b);
+ }
+ if(e && e.isVisible()){
+ b = e.getSize();
+ m = e.getMargins();
+ b.height = centerH - (m.top+m.bottom);
+ totalWidth = (b.width + m.left + m.right);
+ b.x = w - totalWidth + m.left;
+ b.y = centerY + m.top;
+ centerW -= totalWidth;
+ e.applyLayout(b);
+ }
+ if(c){
+ m = c.getMargins();
+ var centerBox = {
+ x: centerX + m.left,
+ y: centerY + m.top,
+ width: centerW - (m.left+m.right),
+ height: centerH - (m.top+m.bottom)
+ };
+ c.applyLayout(centerBox);
+ }
+ if(collapsed){
+ for(i = 0, len = collapsed.length; i < len; i++){
+ collapsed[i].collapse(false);
+ }
+ }
+ if(Ext.isIE9m && Ext.isStrict){
+ target.repaint();
+ }
+
+ if (i = target.getStyle('overflow') && i != 'hidden' && !this.adjustmentPass) {
+ var ts = this.getLayoutTargetSize();
+ if (ts.width != size.width || ts.height != size.height){
+ this.adjustmentPass = true;
+ this.onLayout(ct, target);
+ }
+ }
+ delete this.adjustmentPass;
+ },
+
+ destroy: function() {
+ var r = ['north', 'south', 'east', 'west'], i, region;
+ for (i = 0; i < r.length; i++) {
+ region = this[r[i]];
+ if(region){
+ if(region.destroy){
+ region.destroy();
+ }else if (region.split){
+ region.split.destroy(true);
+ }
+ }
+ }
+ Ext.layout.BorderLayout.superclass.destroy.call(this);
+ }
+
+
+});
+
+
+Ext.layout.BorderLayout.Region = function(layout, config, pos){
+ Ext.apply(this, config);
+ this.layout = layout;
+ this.position = pos;
+ this.state = {};
+ if(typeof this.margins == 'string'){
+ this.margins = this.layout.parseMargins(this.margins);
+ }
+ this.margins = Ext.applyIf(this.margins || {}, this.defaultMargins);
+ if(this.collapsible){
+ if(typeof this.cmargins == 'string'){
+ this.cmargins = this.layout.parseMargins(this.cmargins);
+ }
+ if(this.collapseMode == 'mini' && !this.cmargins){
+ this.cmargins = {left:0,top:0,right:0,bottom:0};
+ }else{
+ this.cmargins = Ext.applyIf(this.cmargins || {},
+ pos == 'north' || pos == 'south' ? this.defaultNSCMargins : this.defaultEWCMargins);
+ }
+ }
+};
+
+Ext.layout.BorderLayout.Region.prototype = {
+
+
+
+
+
+
+ collapsible : false,
+
+ split:false,
+
+ floatable: true,
+
+ minWidth:50,
+
+ minHeight:50,
+
+
+ defaultMargins : {left:0,top:0,right:0,bottom:0},
+
+ defaultNSCMargins : {left:5,top:5,right:5,bottom:5},
+
+ defaultEWCMargins : {left:5,top:0,right:5,bottom:0},
+ floatingZIndex: 100,
+
+
+ isCollapsed : false,
+
+
+
+
+
+
+ render : function(ct, p){
+ this.panel = p;
+ p.el.enableDisplayMode();
+ this.targetEl = ct;
+ this.el = p.el;
+
+ var gs = p.getState, ps = this.position;
+ p.getState = function(){
+ return Ext.apply(gs.call(p) || {}, this.state);
+ }.createDelegate(this);
+
+ if(ps != 'center'){
+ p.allowQueuedExpand = false;
+ p.on({
+ beforecollapse: this.beforeCollapse,
+ collapse: this.onCollapse,
+ beforeexpand: this.beforeExpand,
+ expand: this.onExpand,
+ hide: this.onHide,
+ show: this.onShow,
+ scope: this
+ });
+ if(this.collapsible || this.floatable){
+ p.collapseEl = 'el';
+ p.slideAnchor = this.getSlideAnchor();
+ }
+ if(p.tools && p.tools.toggle){
+ p.tools.toggle.addClass('x-tool-collapse-'+ps);
+ p.tools.toggle.addClassOnOver('x-tool-collapse-'+ps+'-over');
+ }
+ }
+ },
+
+
+ getCollapsedEl : function(){
+ if(!this.collapsedEl){
+ if(!this.toolTemplate){
+ var tt = new Ext.Template(
+ '<div class="x-tool x-tool-{id}">&#160;</div>'
+ );
+ tt.disableFormats = true;
+ tt.compile();
+ Ext.layout.BorderLayout.Region.prototype.toolTemplate = tt;
+ }
+ this.collapsedEl = this.targetEl.createChild({
+ cls: "x-layout-collapsed x-layout-collapsed-"+this.position,
+ id: this.panel.id + '-xcollapsed'
+ });
+ this.collapsedEl.enableDisplayMode('block');
+
+ if(this.collapseMode == 'mini'){
+ this.collapsedEl.addClass('x-layout-cmini-'+this.position);
+ this.miniCollapsedEl = this.collapsedEl.createChild({
+ cls: "x-layout-mini x-layout-mini-"+this.position, html: "&#160;"
+ });
+ this.miniCollapsedEl.addClassOnOver('x-layout-mini-over');
+ this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
+ this.collapsedEl.on('click', this.onExpandClick, this, {stopEvent:true});
+ }else {
+ if(this.collapsible !== false && !this.hideCollapseTool) {
+ var t = this.expandToolEl = this.toolTemplate.append(
+ this.collapsedEl.dom,
+ {id:'expand-'+this.position}, true);
+ t.addClassOnOver('x-tool-expand-'+this.position+'-over');
+ t.on('click', this.onExpandClick, this, {stopEvent:true});
+ }
+ if(this.floatable !== false || this.titleCollapse){
+ this.collapsedEl.addClassOnOver("x-layout-collapsed-over");
+ this.collapsedEl.on("click", this[this.floatable ? 'collapseClick' : 'onExpandClick'], this);
+ }
+ }
+ }
+ return this.collapsedEl;
+ },
+
+
+ onExpandClick : function(e){
+ if(this.isSlid){
+ this.panel.expand(false);
+ }else{
+ this.panel.expand();
+ }
+ },
+
+
+ onCollapseClick : function(e){
+ this.panel.collapse();
+ },
+
+
+ beforeCollapse : function(p, animate){
+ this.lastAnim = animate;
+ if(this.splitEl){
+ this.splitEl.hide();
+ }
+ this.getCollapsedEl().show();
+ var el = this.panel.getEl();
+ this.originalZIndex = el.getStyle('z-index');
+ el.setStyle('z-index', 100);
+ this.isCollapsed = true;
+ this.layout.layout();
+ },
+
+
+ onCollapse : function(animate){
+ this.panel.el.setStyle('z-index', 1);
+ if(this.lastAnim === false || this.panel.animCollapse === false){
+ this.getCollapsedEl().dom.style.visibility = 'visible';
+ }else{
+ this.getCollapsedEl().slideIn(this.panel.slideAnchor, {duration:.2});
+ }
+ this.state.collapsed = true;
+ this.panel.saveState();
+ },
+
+
+ beforeExpand : function(animate){
+ if(this.isSlid){
+ this.afterSlideIn();
+ }
+ var c = this.getCollapsedEl();
+ this.el.show();
+ if(this.position == 'east' || this.position == 'west'){
+ this.panel.setSize(undefined, c.getHeight());
+ }else{
+ this.panel.setSize(c.getWidth(), undefined);
+ }
+ c.hide();
+ c.dom.style.visibility = 'hidden';
+ this.panel.el.setStyle('z-index', this.floatingZIndex);
+ },
+
+
+ onExpand : function(){
+ this.isCollapsed = false;
+ if(this.splitEl){
+ this.splitEl.show();
+ }
+ this.layout.layout();
+ this.panel.el.setStyle('z-index', this.originalZIndex);
+ this.state.collapsed = false;
+ this.panel.saveState();
+ },
+
+
+ collapseClick : function(e){
+ if(this.isSlid){
+ e.stopPropagation();
+ this.slideIn();
+ }else{
+ e.stopPropagation();
+ this.slideOut();
+ }
+ },
+
+
+ onHide : function(){
+ if(this.isCollapsed){
+ this.getCollapsedEl().hide();
+ }else if(this.splitEl){
+ this.splitEl.hide();
+ }
+ },
+
+
+ onShow : function(){
+ if(this.isCollapsed){
+ this.getCollapsedEl().show();
+ }else if(this.splitEl){
+ this.splitEl.show();
+ }
+ },
+
+
+ isVisible : function(){
+ return !this.panel.hidden;
+ },
+
+
+ getMargins : function(){
+ return this.isCollapsed && this.cmargins ? this.cmargins : this.margins;
+ },
+
+
+ getSize : function(){
+ return this.isCollapsed ? this.getCollapsedEl().getSize() : this.panel.getSize();
+ },
+
+
+ setPanel : function(panel){
+ this.panel = panel;
+ },
+
+
+ getMinWidth: function(){
+ return this.minWidth;
+ },
+
+
+ getMinHeight: function(){
+ return this.minHeight;
+ },
+
+
+ applyLayoutCollapsed : function(box){
+ var ce = this.getCollapsedEl();
+ ce.setLeftTop(box.x, box.y);
+ ce.setSize(box.width, box.height);
+ },
+
+
+ applyLayout : function(box){
+ if(this.isCollapsed){
+ this.applyLayoutCollapsed(box);
+ }else{
+ this.panel.setPosition(box.x, box.y);
+ this.panel.setSize(box.width, box.height);
+ }
+ },
+
+
+ beforeSlide: function(){
+ this.panel.beforeEffect();
+ },
+
+
+ afterSlide : function(){
+ this.panel.afterEffect();
+ },
+
+
+ initAutoHide : function(){
+ if(this.autoHide !== false){
+ if(!this.autoHideHd){
+ this.autoHideSlideTask = new Ext.util.DelayedTask(this.slideIn, this);
+ this.autoHideHd = {
+ "mouseout": function(e){
+ if(!e.within(this.el, true)){
+ this.autoHideSlideTask.delay(500);
+ }
+ },
+ "mouseover" : function(e){
+ this.autoHideSlideTask.cancel();
+ },
+ scope : this
+ };
+ }
+ this.el.on(this.autoHideHd);
+ this.collapsedEl.on(this.autoHideHd);
+ }
+ },
+
+
+ clearAutoHide : function(){
+ if(this.autoHide !== false){
+ this.el.un("mouseout", this.autoHideHd.mouseout);
+ this.el.un("mouseover", this.autoHideHd.mouseover);
+ this.collapsedEl.un("mouseout", this.autoHideHd.mouseout);
+ this.collapsedEl.un("mouseover", this.autoHideHd.mouseover);
+ }
+ },
+
+
+ clearMonitor : function(){
+ Ext.getDoc().un("click", this.slideInIf, this);
+ },
+
+
+ slideOut : function(){
+ if(this.isSlid || this.el.hasActiveFx()){
+ return;
+ }
+ this.isSlid = true;
+ var ts = this.panel.tools, dh, pc;
+ if(ts && ts.toggle){
+ ts.toggle.hide();
+ }
+ this.el.show();
+
+
+ pc = this.panel.collapsed;
+ this.panel.collapsed = false;
+
+ if(this.position == 'east' || this.position == 'west'){
+
+ dh = this.panel.deferHeight;
+ this.panel.deferHeight = false;
+
+ this.panel.setSize(undefined, this.collapsedEl.getHeight());
+
+
+ this.panel.deferHeight = dh;
+ }else{
+ this.panel.setSize(this.collapsedEl.getWidth(), undefined);
+ }
+
+
+ this.panel.collapsed = pc;
+
+ this.restoreLT = [this.el.dom.style.left, this.el.dom.style.top];
+ this.el.alignTo(this.collapsedEl, this.getCollapseAnchor());
+ this.el.setStyle("z-index", this.floatingZIndex+2);
+ this.panel.el.replaceClass('x-panel-collapsed', 'x-panel-floating');
+ if(this.animFloat !== false){
+ this.beforeSlide();
+ this.el.slideIn(this.getSlideAnchor(), {
+ callback: function(){
+ this.afterSlide();
+ this.initAutoHide();
+ Ext.getDoc().on("click", this.slideInIf, this);
+ },
+ scope: this,
+ block: true
+ });
+ }else{
+ this.initAutoHide();
+ Ext.getDoc().on("click", this.slideInIf, this);
+ }
+ },
+
+
+ afterSlideIn : function(){
+ this.clearAutoHide();
+ this.isSlid = false;
+ this.clearMonitor();
+ this.el.setStyle("z-index", "");
+ this.panel.el.replaceClass('x-panel-floating', 'x-panel-collapsed');
+ this.el.dom.style.left = this.restoreLT[0];
+ this.el.dom.style.top = this.restoreLT[1];
+
+ var ts = this.panel.tools;
+ if(ts && ts.toggle){
+ ts.toggle.show();
+ }
+ },
+
+
+ slideIn : function(cb){
+ if(!this.isSlid || this.el.hasActiveFx()){
+ Ext.callback(cb);
+ return;
+ }
+ this.isSlid = false;
+ if(this.animFloat !== false){
+ this.beforeSlide();
+ this.el.slideOut(this.getSlideAnchor(), {
+ callback: function(){
+ this.el.hide();
+ this.afterSlide();
+ this.afterSlideIn();
+ Ext.callback(cb);
+ },
+ scope: this,
+ block: true
+ });
+ }else{
+ this.el.hide();
+ this.afterSlideIn();
+ }
+ },
+
+
+ slideInIf : function(e){
+ if(!e.within(this.el)){
+ this.slideIn();
+ }
+ },
+
+
+ anchors : {
+ "west" : "left",
+ "east" : "right",
+ "north" : "top",
+ "south" : "bottom"
+ },
+
+
+ sanchors : {
+ "west" : "l",
+ "east" : "r",
+ "north" : "t",
+ "south" : "b"
+ },
+
+
+ canchors : {
+ "west" : "tl-tr",
+ "east" : "tr-tl",
+ "north" : "tl-bl",
+ "south" : "bl-tl"
+ },
+
+
+ getAnchor : function(){
+ return this.anchors[this.position];
+ },
+
+
+ getCollapseAnchor : function(){
+ return this.canchors[this.position];
+ },
+
+
+ getSlideAnchor : function(){
+ return this.sanchors[this.position];
+ },
+
+
+ getAlignAdj : function(){
+ var cm = this.cmargins;
+ switch(this.position){
+ case "west":
+ return [0, 0];
+ break;
+ case "east":
+ return [0, 0];
+ break;
+ case "north":
+ return [0, 0];
+ break;
+ case "south":
+ return [0, 0];
+ break;
+ }
+ },
+
+
+ getExpandAdj : function(){
+ var c = this.collapsedEl, cm = this.cmargins;
+ switch(this.position){
+ case "west":
+ return [-(cm.right+c.getWidth()+cm.left), 0];
+ break;
+ case "east":
+ return [cm.right+c.getWidth()+cm.left, 0];
+ break;
+ case "north":
+ return [0, -(cm.top+cm.bottom+c.getHeight())];
+ break;
+ case "south":
+ return [0, cm.top+cm.bottom+c.getHeight()];
+ break;
+ }
+ },
+
+ destroy : function(){
+ if (this.autoHideSlideTask && this.autoHideSlideTask.cancel){
+ this.autoHideSlideTask.cancel();
+ }
+ Ext.destroyMembers(this, 'miniCollapsedEl', 'collapsedEl', 'expandToolEl');
+ }
+};
+
+
+Ext.layout.BorderLayout.SplitRegion = function(layout, config, pos){
+ Ext.layout.BorderLayout.SplitRegion.superclass.constructor.call(this, layout, config, pos);
+
+ this.applyLayout = this.applyFns[pos];
+};
+
+Ext.extend(Ext.layout.BorderLayout.SplitRegion, Ext.layout.BorderLayout.Region, {
+
+
+ splitTip : "Drag to resize.",
+
+ collapsibleSplitTip : "Drag to resize. Double click to hide.",
+
+ useSplitTips : false,
+
+
+ splitSettings : {
+ north : {
+ orientation: Ext.SplitBar.VERTICAL,
+ placement: Ext.SplitBar.TOP,
+ maxFn : 'getVMaxSize',
+ minProp: 'minHeight',
+ maxProp: 'maxHeight'
+ },
+ south : {
+ orientation: Ext.SplitBar.VERTICAL,
+ placement: Ext.SplitBar.BOTTOM,
+ maxFn : 'getVMaxSize',
+ minProp: 'minHeight',
+ maxProp: 'maxHeight'
+ },
+ east : {
+ orientation: Ext.SplitBar.HORIZONTAL,
+ placement: Ext.SplitBar.RIGHT,
+ maxFn : 'getHMaxSize',
+ minProp: 'minWidth',
+ maxProp: 'maxWidth'
+ },
+ west : {
+ orientation: Ext.SplitBar.HORIZONTAL,
+ placement: Ext.SplitBar.LEFT,
+ maxFn : 'getHMaxSize',
+ minProp: 'minWidth',
+ maxProp: 'maxWidth'
+ }
+ },
+
+
+ applyFns : {
+ west : function(box){
+ if(this.isCollapsed){
+ return this.applyLayoutCollapsed(box);
+ }
+ var sd = this.splitEl.dom, s = sd.style;
+ this.panel.setPosition(box.x, box.y);
+ var sw = sd.offsetWidth;
+ s.left = (box.x+box.width-sw)+'px';
+ s.top = (box.y)+'px';
+ s.height = Math.max(0, box.height)+'px';
+ this.panel.setSize(box.width-sw, box.height);
+ },
+ east : function(box){
+ if(this.isCollapsed){
+ return this.applyLayoutCollapsed(box);
+ }
+ var sd = this.splitEl.dom, s = sd.style;
+ var sw = sd.offsetWidth;
+ this.panel.setPosition(box.x+sw, box.y);
+ s.left = (box.x)+'px';
+ s.top = (box.y)+'px';
+ s.height = Math.max(0, box.height)+'px';
+ this.panel.setSize(box.width-sw, box.height);
+ },
+ north : function(box){
+ if(this.isCollapsed){
+ return this.applyLayoutCollapsed(box);
+ }
+ var sd = this.splitEl.dom, s = sd.style;
+ var sh = sd.offsetHeight;
+ this.panel.setPosition(box.x, box.y);
+ s.left = (box.x)+'px';
+ s.top = (box.y+box.height-sh)+'px';
+ s.width = Math.max(0, box.width)+'px';
+ this.panel.setSize(box.width, box.height-sh);
+ },
+ south : function(box){
+ if(this.isCollapsed){
+ return this.applyLayoutCollapsed(box);
+ }
+ var sd = this.splitEl.dom, s = sd.style;
+ var sh = sd.offsetHeight;
+ this.panel.setPosition(box.x, box.y+sh);
+ s.left = (box.x)+'px';
+ s.top = (box.y)+'px';
+ s.width = Math.max(0, box.width)+'px';
+ this.panel.setSize(box.width, box.height-sh);
+ }
+ },
+
+
+ render : function(ct, p){
+ Ext.layout.BorderLayout.SplitRegion.superclass.render.call(this, ct, p);
+
+ var ps = this.position;
+
+ this.splitEl = ct.createChild({
+ cls: "x-layout-split x-layout-split-"+ps, html: "&#160;",
+ id: this.panel.id + '-xsplit'
+ });
+
+ if(this.collapseMode == 'mini'){
+ this.miniSplitEl = this.splitEl.createChild({
+ cls: "x-layout-mini x-layout-mini-"+ps, html: "&#160;"
+ });
+ this.miniSplitEl.addClassOnOver('x-layout-mini-over');
+ this.miniSplitEl.on('click', this.onCollapseClick, this, {stopEvent:true});
+ }
+
+ var s = this.splitSettings[ps];
+
+ this.split = new Ext.SplitBar(this.splitEl.dom, p.el, s.orientation);
+ this.split.tickSize = this.tickSize;
+ this.split.placement = s.placement;
+ this.split.getMaximumSize = this[s.maxFn].createDelegate(this);
+ this.split.minSize = this.minSize || this[s.minProp];
+ this.split.on("beforeapply", this.onSplitMove, this);
+ this.split.useShim = this.useShim === true;
+ this.maxSize = this.maxSize || this[s.maxProp];
+
+ if(p.hidden){
+ this.splitEl.hide();
+ }
+
+ if(this.useSplitTips){
+ this.splitEl.dom.title = this.collapsible ? this.collapsibleSplitTip : this.splitTip;
+ }
+ if(this.collapsible){
+ this.splitEl.on("dblclick", this.onCollapseClick, this);
+ }
+ },
+
+
+ getSize : function(){
+ if(this.isCollapsed){
+ return this.collapsedEl.getSize();
+ }
+ var s = this.panel.getSize();
+ if(this.position == 'north' || this.position == 'south'){
+ s.height += this.splitEl.dom.offsetHeight;
+ }else{
+ s.width += this.splitEl.dom.offsetWidth;
+ }
+ return s;
+ },
+
+
+ getHMaxSize : function(){
+ var cmax = this.maxSize || 10000;
+ var center = this.layout.center;
+ return Math.min(cmax, (this.el.getWidth()+center.el.getWidth())-center.getMinWidth());
+ },
+
+
+ getVMaxSize : function(){
+ var cmax = this.maxSize || 10000;
+ var center = this.layout.center;
+ return Math.min(cmax, (this.el.getHeight()+center.el.getHeight())-center.getMinHeight());
+ },
+
+
+ onSplitMove : function(split, newSize){
+ var s = this.panel.getSize();
+ this.lastSplitSize = newSize;
+ if(this.position == 'north' || this.position == 'south'){
+ this.panel.setSize(s.width, newSize);
+ this.state.height = newSize;
+ }else{
+ this.panel.setSize(newSize, s.height);
+ this.state.width = newSize;
+ }
+ this.layout.layout();
+ this.panel.saveState();
+ return false;
+ },
+
+
+ getSplitBar : function(){
+ return this.split;
+ },
+
+
+ destroy : function() {
+ Ext.destroy(this.miniSplitEl, this.split, this.splitEl);
+ Ext.layout.BorderLayout.SplitRegion.superclass.destroy.call(this);
+ }
+});
+
+Ext.Container.LAYOUTS['border'] = Ext.layout.BorderLayout;
+
+Ext.layout.FormLayout = Ext.extend(Ext.layout.AnchorLayout, {
+
+
+ labelSeparator : ':',
+
+
+
+
+ trackLabels: true,
+
+ type: 'form',
+
+ onRemove: function(c){
+ Ext.layout.FormLayout.superclass.onRemove.call(this, c);
+ if(this.trackLabels){
+ c.un('show', this.onFieldShow, this);
+ c.un('hide', this.onFieldHide, this);
+ }
+
+ var el = c.getPositionEl(),
+ ct = c.getItemCt && c.getItemCt();
+ if (c.rendered && ct) {
+ if (el && el.dom) {
+ el.insertAfter(ct);
+ }
+ Ext.destroy(ct);
+ Ext.destroyMembers(c, 'label', 'itemCt');
+ if (c.customItemCt) {
+ Ext.destroyMembers(c, 'getItemCt', 'customItemCt');
+ }
+ }
+ },
+
+
+ setContainer : function(ct){
+ Ext.layout.FormLayout.superclass.setContainer.call(this, ct);
+ ct.labelAlign = ct.labelAlign || this.labelAlign;
+ if (ct.labelAlign) {
+ ct.addClass('x-form-label-' + ct.labelAlign);
+ }
+
+ if (ct.hideLabels || this.hideLabels) {
+ Ext.apply(this, {
+ labelStyle: 'display:none',
+ elementStyle: 'padding-left:0;',
+ labelAdjust: 0
+ });
+ } else {
+ this.labelSeparator = Ext.isDefined(ct.labelSeparator) ? ct.labelSeparator : this.labelSeparator;
+ ct.labelWidth = ct.labelWidth || this.labelWidth || 100;
+ if(Ext.isNumber(ct.labelWidth)){
+ var pad = ct.labelPad || this.labelPad;
+ pad = Ext.isNumber(pad) ? pad : 5;
+ Ext.apply(this, {
+ labelAdjust: ct.labelWidth + pad,
+ labelStyle: 'width:' + ct.labelWidth + 'px;',
+ elementStyle: 'padding-left:' + (ct.labelWidth + pad) + 'px'
+ });
+ }
+ if(ct.labelAlign == 'top'){
+ Ext.apply(this, {
+ labelStyle: 'width:auto;',
+ labelAdjust: 0,
+ elementStyle: 'padding-left:0;'
+ });
+ }
+ }
+ },
+
+
+ isHide: function(c){
+ return c.hideLabel || this.container.hideLabels;
+ },
+
+ onFieldShow: function(c){
+ c.getItemCt().removeClass('x-hide-' + c.hideMode);
+
+
+ if (c.isComposite) {
+ c.doLayout();
+ }
+ },
+
+ onFieldHide: function(c){
+ c.getItemCt().addClass('x-hide-' + c.hideMode);
+ },
+
+
+ getLabelStyle: function(s){
+ var ls = '', items = [this.labelStyle, s];
+ for (var i = 0, len = items.length; i < len; ++i){
+ if (items[i]){
+ ls += items[i];
+ if (ls.substr(-1, 1) != ';'){
+ ls += ';';
+ }
+ }
+ }
+ return ls;
+ },
+
+
+
+
+ renderItem : function(c, position, target){
+ if(c && (c.isFormField || c.fieldLabel) && c.inputType != 'hidden'){
+ var args = this.getTemplateArgs(c);
+ if(Ext.isNumber(position)){
+ position = target.dom.childNodes[position] || null;
+ }
+ if(position){
+ c.itemCt = this.fieldTpl.insertBefore(position, args, true);
+ }else{
+ c.itemCt = this.fieldTpl.append(target, args, true);
+ }
+ if(!c.getItemCt){
+
+
+ Ext.apply(c, {
+ getItemCt: function(){
+ return c.itemCt;
+ },
+ customItemCt: true
+ });
+ }
+ c.label = c.getItemCt().child('label.x-form-item-label');
+ if(!c.rendered){
+ c.render('x-form-el-' + c.id);
+ }else if(!this.isValidParent(c, target)){
+ Ext.fly('x-form-el-' + c.id).appendChild(c.getPositionEl());
+ }
+ if(this.trackLabels){
+ if(c.hidden){
+ this.onFieldHide(c);
+ }
+ c.on({
+ scope: this,
+ show: this.onFieldShow,
+ hide: this.onFieldHide
+ });
+ }
+ this.configureItem(c);
+ }else {
+ Ext.layout.FormLayout.superclass.renderItem.apply(this, arguments);
+ }
+ },
+
+
+ getTemplateArgs: function(field) {
+ var noLabelSep = !field.fieldLabel || field.hideLabel,
+ itemCls = (field.itemCls || this.container.itemCls || '') + (field.hideLabel ? ' x-hide-label' : '');
+
+
+ if (Ext.isIE9 && Ext.isIEQuirks && field instanceof Ext.form.TextField) {
+ itemCls += ' x-input-wrapper';
+ }
+
+ return {
+ id : field.id,
+ label : field.fieldLabel,
+ itemCls : itemCls,
+ clearCls : field.clearCls || 'x-form-clear-left',
+ labelStyle : this.getLabelStyle(field.labelStyle),
+ elementStyle : this.elementStyle || '',
+ labelSeparator: noLabelSep ? '' : (Ext.isDefined(field.labelSeparator) ? field.labelSeparator : this.labelSeparator)
+ };
+ },
+
+
+ adjustWidthAnchor: function(value, c){
+ if(c.label && !this.isHide(c) && (this.container.labelAlign != 'top')){
+ var adjust = Ext.isIE6 || Ext.isIEQuirks;
+ return value - this.labelAdjust + (adjust ? -3 : 0);
+ }
+ return value;
+ },
+
+ adjustHeightAnchor : function(value, c){
+ if(c.label && !this.isHide(c) && (this.container.labelAlign == 'top')){
+ return value - c.label.getHeight();
+ }
+ return value;
+ },
+
+
+ isValidParent : function(c, target){
+ return target && this.container.getEl().contains(c.getPositionEl());
+ }
+
+
+});
+
+Ext.Container.LAYOUTS['form'] = Ext.layout.FormLayout;
+
+Ext.layout.AccordionLayout = Ext.extend(Ext.layout.FitLayout, {
+
+ fill : true,
+
+ autoWidth : true,
+
+ titleCollapse : true,
+
+ hideCollapseTool : false,
+
+ collapseFirst : false,
+
+ animate : false,
+
+ sequence : false,
+
+ activeOnTop : false,
+
+ type: 'accordion',
+
+ renderItem : function(c){
+ if(this.animate === false){
+ c.animCollapse = false;
+ }
+ c.collapsible = true;
+ if(this.autoWidth){
+ c.autoWidth = true;
+ }
+ if(this.titleCollapse){
+ c.titleCollapse = true;
+ }
+ if(this.hideCollapseTool){
+ c.hideCollapseTool = true;
+ }
+ if(this.collapseFirst !== undefined){
+ c.collapseFirst = this.collapseFirst;
+ }
+ if(!this.activeItem && !c.collapsed){
+ this.setActiveItem(c, true);
+ }else if(this.activeItem && this.activeItem != c){
+ c.collapsed = true;
+ }
+ Ext.layout.AccordionLayout.superclass.renderItem.apply(this, arguments);
+ c.header.addClass('x-accordion-hd');
+ c.on('beforeexpand', this.beforeExpand, this);
+ },
+
+ onRemove: function(c){
+ Ext.layout.AccordionLayout.superclass.onRemove.call(this, c);
+ if(c.rendered){
+ c.header.removeClass('x-accordion-hd');
+ }
+ c.un('beforeexpand', this.beforeExpand, this);
+ },
+
+
+ beforeExpand : function(p, anim){
+ var ai = this.activeItem;
+ if(ai){
+ if(this.sequence){
+ delete this.activeItem;
+ if (!ai.collapsed){
+ ai.collapse({callback:function(){
+ p.expand(anim || true);
+ }, scope: this});
+ return false;
+ }
+ }else{
+ ai.collapse(this.animate);
+ }
+ }
+ this.setActive(p);
+ if(this.activeOnTop){
+ p.el.dom.parentNode.insertBefore(p.el.dom, p.el.dom.parentNode.firstChild);
+ }
+
+ this.layout();
+ },
+
+
+ setItemSize : function(item, size){
+ if(this.fill && item){
+ var hh = 0, i, ct = this.getRenderedItems(this.container), len = ct.length, p;
+
+ for (i = 0; i < len; i++) {
+ if((p = ct[i]) != item && !p.hidden){
+ hh += p.header.getHeight();
+ }
+ };
+
+ size.height -= hh;
+
+
+ item.setSize(size);
+ }
+ },
+
+
+ setActiveItem : function(item){
+ this.setActive(item, true);
+ },
+
+
+ setActive : function(item, expand){
+ var ai = this.activeItem;
+ item = this.container.getComponent(item);
+ if(ai != item){
+ if(item.rendered && item.collapsed && expand){
+ item.expand();
+ }else{
+ if(ai){
+ ai.fireEvent('deactivate', ai);
+ }
+ this.activeItem = item;
+ item.fireEvent('activate', item);
+ }
+ }
+ }
+});
+Ext.Container.LAYOUTS.accordion = Ext.layout.AccordionLayout;
+
+
+Ext.layout.Accordion = Ext.layout.AccordionLayout;
+Ext.layout.TableLayout = Ext.extend(Ext.layout.ContainerLayout, {
+
+
+
+ monitorResize:false,
+
+ type: 'table',
+
+ targetCls: 'x-table-layout-ct',
+
+
+ tableAttrs:null,
+
+
+ setContainer : function(ct){
+ Ext.layout.TableLayout.superclass.setContainer.call(this, ct);
+
+ this.currentRow = 0;
+ this.currentColumn = 0;
+ this.cells = [];
+ },
+
+
+ onLayout : function(ct, target){
+ var cs = ct.items.items, len = cs.length, c, i;
+
+ if(!this.table){
+ target.addClass('x-table-layout-ct');
+
+ this.table = target.createChild(
+ Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true);
+ }
+ this.renderAll(ct, target);
+ },
+
+
+ getRow : function(index){
+ var row = this.table.tBodies[0].childNodes[index];
+ if(!row){
+ row = document.createElement('tr');
+ this.table.tBodies[0].appendChild(row);
+ }
+ return row;
+ },
+
+
+ getNextCell : function(c){
+ var cell = this.getNextNonSpan(this.currentColumn, this.currentRow);
+ var curCol = this.currentColumn = cell[0], curRow = this.currentRow = cell[1];
+ for(var rowIndex = curRow; rowIndex < curRow + (c.rowspan || 1); rowIndex++){
+ if(!this.cells[rowIndex]){
+ this.cells[rowIndex] = [];
+ }
+ for(var colIndex = curCol; colIndex < curCol + (c.colspan || 1); colIndex++){
+ this.cells[rowIndex][colIndex] = true;
+ }
+ }
+ var td = document.createElement('td');
+ if(c.cellId){
+ td.id = c.cellId;
+ }
+ var cls = 'x-table-layout-cell';
+ if(c.cellCls){
+ cls += ' ' + c.cellCls;
+ }
+ td.className = cls;
+ if(c.colspan){
+ td.colSpan = c.colspan;
+ }
+ if(c.rowspan){
+ td.rowSpan = c.rowspan;
+ }
+ this.getRow(curRow).appendChild(td);
+ return td;
+ },
+
+
+ getNextNonSpan: function(colIndex, rowIndex){
+ var cols = this.columns;
+ while((cols && colIndex >= cols) || (this.cells[rowIndex] && this.cells[rowIndex][colIndex])) {
+ if(cols && colIndex >= cols){
+ rowIndex++;
+ colIndex = 0;
+ }else{
+ colIndex++;
+ }
+ }
+ return [colIndex, rowIndex];
+ },
+
+
+ renderItem : function(c, position, target){
+
+ if(!this.table){
+ this.table = target.createChild(
+ Ext.apply({tag:'table', cls:'x-table-layout', cellspacing: 0, cn: {tag: 'tbody'}}, this.tableAttrs), null, true);
+ }
+ if(c && !c.rendered){
+ c.render(this.getNextCell(c));
+ this.configureItem(c);
+ }else if(c && !this.isValidParent(c, target)){
+ var container = this.getNextCell(c);
+ container.insertBefore(c.getPositionEl().dom, null);
+ c.container = Ext.get(container);
+ this.configureItem(c);
+ }
+ },
+
+
+ isValidParent : function(c, target){
+ return c.getPositionEl().up('table', 5).dom.parentNode === (target.dom || target);
+ },
+
+ destroy: function(){
+ delete this.table;
+ Ext.layout.TableLayout.superclass.destroy.call(this);
+ }
+
+
+});
+
+Ext.Container.LAYOUTS['table'] = Ext.layout.TableLayout;
+Ext.layout.AbsoluteLayout = Ext.extend(Ext.layout.AnchorLayout, {
+
+ extraCls: 'x-abs-layout-item',
+
+ type: 'absolute',
+
+ onLayout : function(ct, target){
+ target.position();
+ this.paddingLeft = target.getPadding('l');
+ this.paddingTop = target.getPadding('t');
+ Ext.layout.AbsoluteLayout.superclass.onLayout.call(this, ct, target);
+ },
+
+
+ adjustWidthAnchor : function(value, comp){
+ return value ? value - comp.getPosition(true)[0] + this.paddingLeft : value;
+ },
+
+
+ adjustHeightAnchor : function(value, comp){
+ return value ? value - comp.getPosition(true)[1] + this.paddingTop : value;
+ }
+
+});
+Ext.Container.LAYOUTS['absolute'] = Ext.layout.AbsoluteLayout;
+
+Ext.layout.BoxLayout = Ext.extend(Ext.layout.ContainerLayout, {
+
+ defaultMargins : {left:0,top:0,right:0,bottom:0},
+
+ padding : '0',
+
+ pack : 'start',
+
+
+ monitorResize : true,
+ type: 'box',
+ scrollOffset : 0,
+ extraCls : 'x-box-item',
+ targetCls : 'x-box-layout-ct',
+ innerCls : 'x-box-inner',
+
+ constructor : function(config){
+ Ext.layout.BoxLayout.superclass.constructor.call(this, config);
+
+ if (Ext.isString(this.defaultMargins)) {
+ this.defaultMargins = this.parseMargins(this.defaultMargins);
+ }
+
+ var handler = this.overflowHandler;
+
+ if (typeof handler == 'string') {
+ handler = {
+ type: handler
+ };
+ }
+
+ var handlerType = 'none';
+ if (handler && handler.type != undefined) {
+ handlerType = handler.type;
+ }
+
+ var constructor = Ext.layout.boxOverflow[handlerType];
+ if (constructor[this.type]) {
+ constructor = constructor[this.type];
+ }
+
+ this.overflowHandler = new constructor(this, handler);
+ },
+
+
+ onLayout: function(container, target) {
+ Ext.layout.BoxLayout.superclass.onLayout.call(this, container, target);
+
+ var tSize = this.getLayoutTargetSize(),
+ items = this.getVisibleItems(container),
+ calcs = this.calculateChildBoxes(items, tSize),
+ boxes = calcs.boxes,
+ meta = calcs.meta;
+
+
+ if (tSize.width > 0) {
+ var handler = this.overflowHandler,
+ method = meta.tooNarrow ? 'handleOverflow' : 'clearOverflow';
+
+ var results = handler[method](calcs, tSize);
+
+ if (results) {
+ if (results.targetSize) {
+ tSize = results.targetSize;
+ }
+
+ if (results.recalculate) {
+ items = this.getVisibleItems(container);
+ calcs = this.calculateChildBoxes(items, tSize);
+ boxes = calcs.boxes;
+ }
+ }
+ }
+
+
+ this.layoutTargetLastSize = tSize;
+
+
+ this.childBoxCache = calcs;
+
+ this.updateInnerCtSize(tSize, calcs);
+ this.updateChildBoxes(boxes);
+
+
+ this.handleTargetOverflow(tSize, container, target);
+ },
+
+
+ updateChildBoxes: function(boxes) {
+ for (var i = 0, length = boxes.length; i < length; i++) {
+ var box = boxes[i],
+ comp = box.component;
+
+ if (box.dirtySize) {
+ comp.setSize(box.width, box.height);
+ }
+
+ if (isNaN(box.left) || isNaN(box.top)) {
+ continue;
+ }
+
+ comp.setPosition(box.left, box.top);
+ }
+ },
+
+
+ updateInnerCtSize: function(tSize, calcs) {
+ var align = this.align,
+ padding = this.padding,
+ width = tSize.width,
+ height = tSize.height;
+
+ if (this.type == 'hbox') {
+ var innerCtWidth = width,
+ innerCtHeight = calcs.meta.maxHeight + padding.top + padding.bottom;
+
+ if (align == 'stretch') {
+ innerCtHeight = height;
+ } else if (align == 'middle') {
+ innerCtHeight = Math.max(height, innerCtHeight);
+ }
+ } else {
+ var innerCtHeight = height,
+ innerCtWidth = calcs.meta.maxWidth + padding.left + padding.right;
+
+ if (align == 'stretch') {
+ innerCtWidth = width;
+ } else if (align == 'center') {
+ innerCtWidth = Math.max(width, innerCtWidth);
+ }
+ }
+
+ this.innerCt.setSize(innerCtWidth || undefined, innerCtHeight || undefined);
+ },
+
+
+ handleTargetOverflow: function(previousTargetSize, container, target) {
+ var overflow = target.getStyle('overflow');
+
+ if (overflow && overflow != 'hidden' &&!this.adjustmentPass) {
+ var newTargetSize = this.getLayoutTargetSize();
+ if (newTargetSize.width != previousTargetSize.width || newTargetSize.height != previousTargetSize.height){
+ this.adjustmentPass = true;
+ this.onLayout(container, target);
+ }
+ }
+
+ delete this.adjustmentPass;
+ },
+
+
+ isValidParent : function(c, target) {
+ return this.innerCt && c.getPositionEl().dom.parentNode == this.innerCt.dom;
+ },
+
+
+ getVisibleItems: function(ct) {
+ var ct = ct || this.container,
+ t = ct.getLayoutTarget(),
+ cti = ct.items.items,
+ len = cti.length,
+
+ i, c, items = [];
+
+ for (i = 0; i < len; i++) {
+ if((c = cti[i]).rendered && this.isValidParent(c, t) && c.hidden !== true && c.collapsed !== true && c.shouldLayout !== false){
+ items.push(c);
+ }
+ }
+
+ return items;
+ },
+
+
+ renderAll : function(ct, target) {
+ if (!this.innerCt) {
+
+ this.innerCt = target.createChild({cls:this.innerCls});
+ this.padding = this.parseMargins(this.padding);
+ }
+ Ext.layout.BoxLayout.superclass.renderAll.call(this, ct, this.innerCt);
+ },
+
+ getLayoutTargetSize : function() {
+ var target = this.container.getLayoutTarget(), ret;
+
+ if (target) {
+ ret = target.getViewSize();
+
+
+
+
+ if (Ext.isIE9m && Ext.isStrict && ret.width == 0){
+ ret = target.getStyleSize();
+ }
+
+ ret.width -= target.getPadding('lr');
+ ret.height -= target.getPadding('tb');
+ }
+
+ return ret;
+ },
+
+
+ renderItem : function(c) {
+ if(Ext.isString(c.margins)){
+ c.margins = this.parseMargins(c.margins);
+ }else if(!c.margins){
+ c.margins = this.defaultMargins;
+ }
+ Ext.layout.BoxLayout.superclass.renderItem.apply(this, arguments);
+ },
+
+
+ destroy: function() {
+ Ext.destroy(this.overflowHandler);
+
+ Ext.layout.BoxLayout.superclass.destroy.apply(this, arguments);
+ }
+});
+
+
+
+Ext.layout.boxOverflow.None = Ext.extend(Object, {
+ constructor: function(layout, config) {
+ this.layout = layout;
+
+ Ext.apply(this, config || {});
+ },
+
+ handleOverflow: Ext.emptyFn,
+
+ clearOverflow: Ext.emptyFn
+});
+
+
+Ext.layout.boxOverflow.none = Ext.layout.boxOverflow.None;
+
+Ext.layout.boxOverflow.Menu = Ext.extend(Ext.layout.boxOverflow.None, {
+
+ afterCls: 'x-strip-right',
+
+
+ noItemsMenuText : '<div class="x-toolbar-no-items">(None)</div>',
+
+ constructor: function(layout) {
+ Ext.layout.boxOverflow.Menu.superclass.constructor.apply(this, arguments);
+
+
+ this.menuItems = [];
+ },
+
+
+ createInnerElements: function() {
+ if (!this.afterCt) {
+ this.afterCt = this.layout.innerCt.insertSibling({cls: this.afterCls}, 'before');
+ }
+ },
+
+
+ clearOverflow: function(calculations, targetSize) {
+ var newWidth = targetSize.width + (this.afterCt ? this.afterCt.getWidth() : 0),
+ items = this.menuItems;
+
+ this.hideTrigger();
+
+ for (var index = 0, length = items.length; index < length; index++) {
+ items.pop().component.show();
+ }
+
+ return {
+ targetSize: {
+ height: targetSize.height,
+ width : newWidth
+ }
+ };
+ },
+
+
+ showTrigger: function() {
+ this.createMenu();
+ this.menuTrigger.show();
+ },
+
+
+ hideTrigger: function() {
+ if (this.menuTrigger != undefined) {
+ this.menuTrigger.hide();
+ }
+ },
+
+
+ beforeMenuShow: function(menu) {
+ var items = this.menuItems,
+ len = items.length,
+ item,
+ prev;
+
+ var needsSep = function(group, item){
+ return group.isXType('buttongroup') && !(item instanceof Ext.Toolbar.Separator);
+ };
+
+ this.clearMenu();
+ menu.removeAll();
+
+ for (var i = 0; i < len; i++) {
+ item = items[i].component;
+
+ if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
+ menu.add('-');
+ }
+
+ this.addComponentToMenu(menu, item);
+ prev = item;
+ }
+
+
+ if (menu.items.length < 1) {
+ menu.add(this.noItemsMenuText);
+ }
+ },
+
+
+ createMenuConfig : function(component, hideOnClick){
+ var config = Ext.apply({}, component.initialConfig),
+ group = component.toggleGroup;
+
+ Ext.copyTo(config, component, [
+ 'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu'
+ ]);
+
+ Ext.apply(config, {
+ text : component.overflowText || component.text,
+ hideOnClick: hideOnClick
+ });
+
+ if (group || component.enableToggle) {
+ Ext.apply(config, {
+ group : group,
+ checked: component.pressed,
+ listeners: {
+ checkchange: function(item, checked){
+ component.toggle(checked);
+ }
+ }
+ });
+ }
+
+ delete config.ownerCt;
+ delete config.xtype;
+ delete config.id;
+
+ return config;
+ },
+
+
+ addComponentToMenu : function(menu, component) {
+ if (component instanceof Ext.Toolbar.Separator) {
+ menu.add('-');
+
+ } else if (Ext.isFunction(component.isXType)) {
+ if (component.isXType('splitbutton')) {
+ menu.add(this.createMenuConfig(component, true));
+
+ } else if (component.isXType('button')) {
+ menu.add(this.createMenuConfig(component, !component.menu));
+
+ } else if (component.isXType('buttongroup')) {
+ component.items.each(function(item){
+ this.addComponentToMenu(menu, item);
+ }, this);
+ }
+ }
+ },
+
+
+ clearMenu : function(){
+ var menu = this.moreMenu;
+ if (menu && menu.items) {
+ menu.items.each(function(item){
+ delete item.menu;
+ });
+ }
+ },
+
+
+ createMenu: function() {
+ if (!this.menuTrigger) {
+ this.createInnerElements();
+
+
+ this.menu = new Ext.menu.Menu({
+ ownerCt : this.layout.container,
+ listeners: {
+ scope: this,
+ beforeshow: this.beforeMenuShow
+ }
+ });
+
+
+ this.menuTrigger = new Ext.Button({
+ iconCls : 'x-toolbar-more-icon',
+ cls : 'x-toolbar-more',
+ menu : this.menu,
+ renderTo: this.afterCt
+ });
+ }
+ },
+
+
+ destroy: function() {
+ Ext.destroy(this.menu, this.menuTrigger);
+ }
+});
+
+Ext.layout.boxOverflow.menu = Ext.layout.boxOverflow.Menu;
+
+
+
+Ext.layout.boxOverflow.HorizontalMenu = Ext.extend(Ext.layout.boxOverflow.Menu, {
+
+ constructor: function() {
+ Ext.layout.boxOverflow.HorizontalMenu.superclass.constructor.apply(this, arguments);
+
+ var me = this,
+ layout = me.layout,
+ origFunction = layout.calculateChildBoxes;
+
+ layout.calculateChildBoxes = function(visibleItems, targetSize) {
+ var calcs = origFunction.apply(layout, arguments),
+ meta = calcs.meta,
+ items = me.menuItems;
+
+
+
+ var hiddenWidth = 0;
+ for (var index = 0, length = items.length; index < length; index++) {
+ hiddenWidth += items[index].width;
+ }
+
+ meta.minimumWidth += hiddenWidth;
+ meta.tooNarrow = meta.minimumWidth > targetSize.width;
+
+ return calcs;
+ };
+ },
+
+ handleOverflow: function(calculations, targetSize) {
+ this.showTrigger();
+
+ var newWidth = targetSize.width - this.afterCt.getWidth(),
+ boxes = calculations.boxes,
+ usedWidth = 0,
+ recalculate = false;
+
+
+ for (var index = 0, length = boxes.length; index < length; index++) {
+ usedWidth += boxes[index].width;
+ }
+
+ var spareWidth = newWidth - usedWidth,
+ showCount = 0;
+
+
+ for (var index = 0, length = this.menuItems.length; index < length; index++) {
+ var hidden = this.menuItems[index],
+ comp = hidden.component,
+ width = hidden.width;
+
+ if (width < spareWidth) {
+ comp.show();
+
+ spareWidth -= width;
+ showCount ++;
+ recalculate = true;
+ } else {
+ break;
+ }
+ }
+
+ if (recalculate) {
+ this.menuItems = this.menuItems.slice(showCount);
+ } else {
+ for (var i = boxes.length - 1; i >= 0; i--) {
+ var item = boxes[i].component,
+ right = boxes[i].left + boxes[i].width;
+
+ if (right >= newWidth) {
+ this.menuItems.unshift({
+ component: item,
+ width : boxes[i].width
+ });
+
+ item.hide();
+ } else {
+ break;
+ }
+ }
+ }
+
+ if (this.menuItems.length == 0) {
+ this.hideTrigger();
+ }
+
+ return {
+ targetSize: {
+ height: targetSize.height,
+ width : newWidth
+ },
+ recalculate: recalculate
+ };
+ }
+});
+
+Ext.layout.boxOverflow.menu.hbox = Ext.layout.boxOverflow.HorizontalMenu;
+Ext.layout.boxOverflow.Scroller = Ext.extend(Ext.layout.boxOverflow.None, {
+
+ animateScroll: true,
+
+
+ scrollIncrement: 100,
+
+
+ wheelIncrement: 3,
+
+
+ scrollRepeatInterval: 400,
+
+
+ scrollDuration: 0.4,
+
+
+ beforeCls: 'x-strip-left',
+
+
+ afterCls: 'x-strip-right',
+
+
+ scrollerCls: 'x-strip-scroller',
+
+
+ beforeScrollerCls: 'x-strip-scroller-left',
+
+
+ afterScrollerCls: 'x-strip-scroller-right',
+
+
+ createWheelListener: function() {
+ this.layout.innerCt.on({
+ scope : this,
+ mousewheel: function(e) {
+ e.stopEvent();
+
+ this.scrollBy(e.getWheelDelta() * this.wheelIncrement * -1, false);
+ }
+ });
+ },
+
+
+ handleOverflow: function(calculations, targetSize) {
+ this.createInnerElements();
+ this.showScrollers();
+ },
+
+
+ clearOverflow: function() {
+ this.hideScrollers();
+ },
+
+
+ showScrollers: function() {
+ this.createScrollers();
+
+ this.beforeScroller.show();
+ this.afterScroller.show();
+
+ this.updateScrollButtons();
+ },
+
+
+ hideScrollers: function() {
+ if (this.beforeScroller != undefined) {
+ this.beforeScroller.hide();
+ this.afterScroller.hide();
+ }
+ },
+
+
+ createScrollers: function() {
+ if (!this.beforeScroller && !this.afterScroller) {
+ var before = this.beforeCt.createChild({
+ cls: String.format("{0} {1} ", this.scrollerCls, this.beforeScrollerCls)
+ });
+
+ var after = this.afterCt.createChild({
+ cls: String.format("{0} {1}", this.scrollerCls, this.afterScrollerCls)
+ });
+
+ before.addClassOnOver(this.beforeScrollerCls + '-hover');
+ after.addClassOnOver(this.afterScrollerCls + '-hover');
+
+ before.setVisibilityMode(Ext.Element.DISPLAY);
+ after.setVisibilityMode(Ext.Element.DISPLAY);
+
+ this.beforeRepeater = new Ext.util.ClickRepeater(before, {
+ interval: this.scrollRepeatInterval,
+ handler : this.scrollLeft,
+ scope : this
+ });
+
+ this.afterRepeater = new Ext.util.ClickRepeater(after, {
+ interval: this.scrollRepeatInterval,
+ handler : this.scrollRight,
+ scope : this
+ });
+
+
+ this.beforeScroller = before;
+
+
+ this.afterScroller = after;
+ }
+ },
+
+
+ destroy: function() {
+ Ext.destroy(this.beforeScroller, this.afterScroller, this.beforeRepeater, this.afterRepeater, this.beforeCt, this.afterCt);
+ },
+
+
+ scrollBy: function(delta, animate) {
+ this.scrollTo(this.getScrollPosition() + delta, animate);
+ },
+
+
+ getItem: function(item) {
+ if (Ext.isString(item)) {
+ item = Ext.getCmp(item);
+ } else if (Ext.isNumber(item)) {
+ item = this.items[item];
+ }
+
+ return item;
+ },
+
+
+ getScrollAnim: function() {
+ return {
+ duration: this.scrollDuration,
+ callback: this.updateScrollButtons,
+ scope : this
+ };
+ },
+
+
+ updateScrollButtons: function() {
+ if (this.beforeScroller == undefined || this.afterScroller == undefined) {
+ return;
+ }
+
+ var beforeMeth = this.atExtremeBefore() ? 'addClass' : 'removeClass',
+ afterMeth = this.atExtremeAfter() ? 'addClass' : 'removeClass',
+ beforeCls = this.beforeScrollerCls + '-disabled',
+ afterCls = this.afterScrollerCls + '-disabled';
+
+ this.beforeScroller[beforeMeth](beforeCls);
+ this.afterScroller[afterMeth](afterCls);
+ this.scrolling = false;
+ },
+
+
+ atExtremeBefore: function() {
+ return this.getScrollPosition() === 0;
+ },
+
+
+ scrollLeft: function(animate) {
+ this.scrollBy(-this.scrollIncrement, animate);
+ },
+
+
+ scrollRight: function(animate) {
+ this.scrollBy(this.scrollIncrement, animate);
+ },
+
+
+ scrollToItem: function(item, animate) {
+ item = this.getItem(item);
+
+ if (item != undefined) {
+ var visibility = this.getItemVisibility(item);
+
+ if (!visibility.fullyVisible) {
+ var box = item.getBox(true, true),
+ newX = box.x;
+
+ if (visibility.hiddenRight) {
+ newX -= (this.layout.innerCt.getWidth() - box.width);
+ }
+
+ this.scrollTo(newX, animate);
+ }
+ }
+ },
+
+
+ getItemVisibility: function(item) {
+ var box = this.getItem(item).getBox(true, true),
+ itemLeft = box.x,
+ itemRight = box.x + box.width,
+ scrollLeft = this.getScrollPosition(),
+ scrollRight = this.layout.innerCt.getWidth() + scrollLeft;
+
+ return {
+ hiddenLeft : itemLeft < scrollLeft,
+ hiddenRight : itemRight > scrollRight,
+ fullyVisible: itemLeft > scrollLeft && itemRight < scrollRight
+ };
+ }
+});
+
+Ext.layout.boxOverflow.scroller = Ext.layout.boxOverflow.Scroller;
+
+
+
+Ext.layout.boxOverflow.VerticalScroller = Ext.extend(Ext.layout.boxOverflow.Scroller, {
+ scrollIncrement: 75,
+ wheelIncrement : 2,
+
+ handleOverflow: function(calculations, targetSize) {
+ Ext.layout.boxOverflow.VerticalScroller.superclass.handleOverflow.apply(this, arguments);
+
+ return {
+ targetSize: {
+ height: targetSize.height - (this.beforeCt.getHeight() + this.afterCt.getHeight()),
+ width : targetSize.width
+ }
+ };
+ },
+
+
+ createInnerElements: function() {
+ var target = this.layout.innerCt;
+
+
+
+ if (!this.beforeCt) {
+ this.beforeCt = target.insertSibling({cls: this.beforeCls}, 'before');
+ this.afterCt = target.insertSibling({cls: this.afterCls}, 'after');
+
+ this.createWheelListener();
+ }
+ },
+
+
+ scrollTo: function(position, animate) {
+ var oldPosition = this.getScrollPosition(),
+ newPosition = position.constrain(0, this.getMaxScrollBottom());
+
+ if (newPosition != oldPosition && !this.scrolling) {
+ if (animate == undefined) {
+ animate = this.animateScroll;
+ }
+
+ this.layout.innerCt.scrollTo('top', newPosition, animate ? this.getScrollAnim() : false);
+
+ if (animate) {
+ this.scrolling = true;
+ } else {
+ this.scrolling = false;
+ this.updateScrollButtons();
+ }
+ }
+ },
+
+
+ getScrollPosition: function(){
+ return parseInt(this.layout.innerCt.dom.scrollTop, 10) || 0;
+ },
+
+
+ getMaxScrollBottom: function() {
+ return this.layout.innerCt.dom.scrollHeight - this.layout.innerCt.getHeight();
+ },
+
+
+ atExtremeAfter: function() {
+ return this.getScrollPosition() >= this.getMaxScrollBottom();
+ }
+});
+
+Ext.layout.boxOverflow.scroller.vbox = Ext.layout.boxOverflow.VerticalScroller;
+
+
+
+Ext.layout.boxOverflow.HorizontalScroller = Ext.extend(Ext.layout.boxOverflow.Scroller, {
+ handleOverflow: function(calculations, targetSize) {
+ Ext.layout.boxOverflow.HorizontalScroller.superclass.handleOverflow.apply(this, arguments);
+
+ return {
+ targetSize: {
+ height: targetSize.height,
+ width : targetSize.width - (this.beforeCt.getWidth() + this.afterCt.getWidth())
+ }
+ };
+ },
+
+
+ createInnerElements: function() {
+ var target = this.layout.innerCt;
+
+
+
+ if (!this.beforeCt) {
+ this.afterCt = target.insertSibling({cls: this.afterCls}, 'before');
+ this.beforeCt = target.insertSibling({cls: this.beforeCls}, 'before');
+
+ this.createWheelListener();
+ }
+ },
+
+
+ scrollTo: function(position, animate) {
+ var oldPosition = this.getScrollPosition(),
+ newPosition = position.constrain(0, this.getMaxScrollRight());
+
+ if (newPosition != oldPosition && !this.scrolling) {
+ if (animate == undefined) {
+ animate = this.animateScroll;
+ }
+
+ this.layout.innerCt.scrollTo('left', newPosition, animate ? this.getScrollAnim() : false);
+
+ if (animate) {
+ this.scrolling = true;
+ } else {
+ this.scrolling = false;
+ this.updateScrollButtons();
+ }
+ }
+ },
+
+
+ getScrollPosition: function(){
+ return parseInt(this.layout.innerCt.dom.scrollLeft, 10) || 0;
+ },
+
+
+ getMaxScrollRight: function() {
+ return this.layout.innerCt.dom.scrollWidth - this.layout.innerCt.getWidth();
+ },
+
+
+ atExtremeAfter: function() {
+ return this.getScrollPosition() >= this.getMaxScrollRight();
+ }
+});
+
+Ext.layout.boxOverflow.scroller.hbox = Ext.layout.boxOverflow.HorizontalScroller;
+Ext.layout.HBoxLayout = Ext.extend(Ext.layout.BoxLayout, {
+
+ align: 'top',
+
+ type : 'hbox',
+
+
+
+
+
+ calculateChildBoxes: function(visibleItems, targetSize) {
+ var visibleCount = visibleItems.length,
+
+ padding = this.padding,
+ topOffset = padding.top,
+ leftOffset = padding.left,
+ paddingVert = topOffset + padding.bottom,
+ paddingHoriz = leftOffset + padding.right,
+
+ width = targetSize.width - this.scrollOffset,
+ height = targetSize.height,
+ availHeight = Math.max(0, height - paddingVert),
+
+ isStart = this.pack == 'start',
+ isCenter = this.pack == 'center',
+ isEnd = this.pack == 'end',
+
+ nonFlexWidth = 0,
+ maxHeight = 0,
+ totalFlex = 0,
+ desiredWidth = 0,
+ minimumWidth = 0,
+
+
+ boxes = [],
+
+
+ child, childWidth, childHeight, childSize, childMargins, canLayout, i, calcs, flexedWidth,
+ horizMargins, vertMargins, stretchHeight;
+
+
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ childHeight = child.height;
+ childWidth = child.width;
+ canLayout = !child.hasLayout && typeof child.doLayout == 'function';
+
+
+ if (typeof childWidth != 'number') {
+
+
+ if (child.flex && !childWidth) {
+ totalFlex += child.flex;
+
+
+ } else {
+
+
+ if (!childWidth && canLayout) {
+ child.doLayout();
+ }
+
+ childSize = child.getSize();
+ childWidth = childSize.width;
+ childHeight = childSize.height;
+ }
+ }
+
+ childMargins = child.margins;
+ horizMargins = childMargins.left + childMargins.right;
+
+ nonFlexWidth += horizMargins + (childWidth || 0);
+ desiredWidth += horizMargins + (child.flex ? child.minWidth || 0 : childWidth);
+ minimumWidth += horizMargins + (child.minWidth || childWidth || 0);
+
+
+ if (typeof childHeight != 'number') {
+ if (canLayout) {
+ child.doLayout();
+ }
+ childHeight = child.getHeight();
+ }
+
+ maxHeight = Math.max(maxHeight, childHeight + childMargins.top + childMargins.bottom);
+
+
+ boxes.push({
+ component: child,
+ height : childHeight || undefined,
+ width : childWidth || undefined
+ });
+ }
+
+ var shortfall = desiredWidth - width,
+ tooNarrow = minimumWidth > width;
+
+
+ var availableWidth = Math.max(0, width - nonFlexWidth - paddingHoriz);
+
+ if (tooNarrow) {
+ for (i = 0; i < visibleCount; i++) {
+ boxes[i].width = visibleItems[i].minWidth || visibleItems[i].width || boxes[i].width;
+ }
+ } else {
+
+
+ if (shortfall > 0) {
+ var minWidths = [];
+
+
+
+
+ for (var index = 0, length = visibleCount; index < length; index++) {
+ var item = visibleItems[index],
+ minWidth = item.minWidth || 0;
+
+
+
+ if (item.flex) {
+ boxes[index].width = minWidth;
+ } else {
+ minWidths.push({
+ minWidth : minWidth,
+ available: boxes[index].width - minWidth,
+ index : index
+ });
+ }
+ }
+
+
+ minWidths.sort(function(a, b) {
+ return a.available > b.available ? 1 : -1;
+ });
+
+
+ for (var i = 0, length = minWidths.length; i < length; i++) {
+ var itemIndex = minWidths[i].index;
+
+ if (itemIndex == undefined) {
+ continue;
+ }
+
+ var item = visibleItems[itemIndex],
+ box = boxes[itemIndex],
+ oldWidth = box.width,
+ minWidth = item.minWidth,
+ newWidth = Math.max(minWidth, oldWidth - Math.ceil(shortfall / (length - i))),
+ reduction = oldWidth - newWidth;
+
+ boxes[itemIndex].width = newWidth;
+ shortfall -= reduction;
+ }
+ } else {
+
+ var remainingWidth = availableWidth,
+ remainingFlex = totalFlex;
+
+
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ calcs = boxes[i];
+
+ childMargins = child.margins;
+ vertMargins = childMargins.top + childMargins.bottom;
+
+ if (isStart && child.flex && !child.width) {
+ flexedWidth = Math.ceil((child.flex / remainingFlex) * remainingWidth);
+ remainingWidth -= flexedWidth;
+ remainingFlex -= child.flex;
+
+ calcs.width = flexedWidth;
+ calcs.dirtySize = true;
+ }
+ }
+ }
+ }
+
+ if (isCenter) {
+ leftOffset += availableWidth / 2;
+ } else if (isEnd) {
+ leftOffset += availableWidth;
+ }
+
+
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ calcs = boxes[i];
+
+ childMargins = child.margins;
+ leftOffset += childMargins.left;
+ vertMargins = childMargins.top + childMargins.bottom;
+
+ calcs.left = leftOffset;
+ calcs.top = topOffset + childMargins.top;
+
+ switch (this.align) {
+ case 'stretch':
+ stretchHeight = availHeight - vertMargins;
+ calcs.height = stretchHeight.constrain(child.minHeight || 0, child.maxHeight || 1000000);
+ calcs.dirtySize = true;
+ break;
+ case 'stretchmax':
+ stretchHeight = maxHeight - vertMargins;
+ calcs.height = stretchHeight.constrain(child.minHeight || 0, child.maxHeight || 1000000);
+ calcs.dirtySize = true;
+ break;
+ case 'middle':
+ var diff = availHeight - calcs.height - vertMargins;
+ if (diff > 0) {
+ calcs.top = topOffset + vertMargins + (diff / 2);
+ }
+ }
+
+ leftOffset += calcs.width + childMargins.right;
+ }
+
+ return {
+ boxes: boxes,
+ meta : {
+ maxHeight : maxHeight,
+ nonFlexWidth: nonFlexWidth,
+ desiredWidth: desiredWidth,
+ minimumWidth: minimumWidth,
+ shortfall : desiredWidth - width,
+ tooNarrow : tooNarrow
+ }
+ };
+ }
+});
+
+Ext.Container.LAYOUTS.hbox = Ext.layout.HBoxLayout;
+Ext.layout.VBoxLayout = Ext.extend(Ext.layout.BoxLayout, {
+
+ align : 'left',
+ type: 'vbox',
+
+
+
+
+
+
+ calculateChildBoxes: function(visibleItems, targetSize) {
+ var visibleCount = visibleItems.length,
+
+ padding = this.padding,
+ topOffset = padding.top,
+ leftOffset = padding.left,
+ paddingVert = topOffset + padding.bottom,
+ paddingHoriz = leftOffset + padding.right,
+
+ width = targetSize.width - this.scrollOffset,
+ height = targetSize.height,
+ availWidth = Math.max(0, width - paddingHoriz),
+
+ isStart = this.pack == 'start',
+ isCenter = this.pack == 'center',
+ isEnd = this.pack == 'end',
+
+ nonFlexHeight= 0,
+ maxWidth = 0,
+ totalFlex = 0,
+ desiredHeight= 0,
+ minimumHeight= 0,
+
+
+ boxes = [],
+
+
+ child, childWidth, childHeight, childSize, childMargins, canLayout, i, calcs, flexedHeight,
+ horizMargins, vertMargins, stretchWidth, length;
+
+
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ childHeight = child.height;
+ childWidth = child.width;
+ canLayout = !child.hasLayout && typeof child.doLayout == 'function';
+
+
+ if (typeof childHeight != 'number') {
+
+
+ if (child.flex && !childHeight) {
+ totalFlex += child.flex;
+
+
+ } else {
+
+
+ if (!childHeight && canLayout) {
+ child.doLayout();
+ }
+
+ childSize = child.getSize();
+ childWidth = childSize.width;
+ childHeight = childSize.height;
+ }
+ }
+
+ childMargins = child.margins;
+ vertMargins = childMargins.top + childMargins.bottom;
+
+ nonFlexHeight += vertMargins + (childHeight || 0);
+ desiredHeight += vertMargins + (child.flex ? child.minHeight || 0 : childHeight);
+ minimumHeight += vertMargins + (child.minHeight || childHeight || 0);
+
+
+ if (typeof childWidth != 'number') {
+ if (canLayout) {
+ child.doLayout();
+ }
+ childWidth = child.getWidth();
+ }
+
+ maxWidth = Math.max(maxWidth, childWidth + childMargins.left + childMargins.right);
+
+
+ boxes.push({
+ component: child,
+ height : childHeight || undefined,
+ width : childWidth || undefined
+ });
+ }
+
+ var shortfall = desiredHeight - height,
+ tooNarrow = minimumHeight > height;
+
+
+ var availableHeight = Math.max(0, (height - nonFlexHeight - paddingVert));
+
+ if (tooNarrow) {
+ for (i = 0, length = visibleCount; i < length; i++) {
+ boxes[i].height = visibleItems[i].minHeight || visibleItems[i].height || boxes[i].height;
+ }
+ } else {
+
+
+ if (shortfall > 0) {
+ var minHeights = [];
+
+
+
+
+ for (var index = 0, length = visibleCount; index < length; index++) {
+ var item = visibleItems[index],
+ minHeight = item.minHeight || 0;
+
+
+
+ if (item.flex) {
+ boxes[index].height = minHeight;
+ } else {
+ minHeights.push({
+ minHeight: minHeight,
+ available: boxes[index].height - minHeight,
+ index : index
+ });
+ }
+ }
+
+
+ minHeights.sort(function(a, b) {
+ return a.available > b.available ? 1 : -1;
+ });
+
+
+ for (var i = 0, length = minHeights.length; i < length; i++) {
+ var itemIndex = minHeights[i].index;
+
+ if (itemIndex == undefined) {
+ continue;
+ }
+
+ var item = visibleItems[itemIndex],
+ box = boxes[itemIndex],
+ oldHeight = box.height,
+ minHeight = item.minHeight,
+ newHeight = Math.max(minHeight, oldHeight - Math.ceil(shortfall / (length - i))),
+ reduction = oldHeight - newHeight;
+
+ boxes[itemIndex].height = newHeight;
+ shortfall -= reduction;
+ }
+ } else {
+
+ var remainingHeight = availableHeight,
+ remainingFlex = totalFlex;
+
+
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ calcs = boxes[i];
+
+ childMargins = child.margins;
+ horizMargins = childMargins.left + childMargins.right;
+
+ if (isStart && child.flex && !child.height) {
+ flexedHeight = Math.ceil((child.flex / remainingFlex) * remainingHeight);
+ remainingHeight -= flexedHeight;
+ remainingFlex -= child.flex;
+
+ calcs.height = flexedHeight;
+ calcs.dirtySize = true;
+ }
+ }
+ }
+ }
+
+ if (isCenter) {
+ topOffset += availableHeight / 2;
+ } else if (isEnd) {
+ topOffset += availableHeight;
+ }
+
+
+ for (i = 0; i < visibleCount; i++) {
+ child = visibleItems[i];
+ calcs = boxes[i];
+
+ childMargins = child.margins;
+ topOffset += childMargins.top;
+ horizMargins = childMargins.left + childMargins.right;
+
+
+ calcs.left = leftOffset + childMargins.left;
+ calcs.top = topOffset;
+
+ switch (this.align) {
+ case 'stretch':
+ stretchWidth = availWidth - horizMargins;
+ calcs.width = stretchWidth.constrain(child.minWidth || 0, child.maxWidth || 1000000);
+ calcs.dirtySize = true;
+ break;
+ case 'stretchmax':
+ stretchWidth = maxWidth - horizMargins;
+ calcs.width = stretchWidth.constrain(child.minWidth || 0, child.maxWidth || 1000000);
+ calcs.dirtySize = true;
+ break;
+ case 'center':
+ var diff = availWidth - calcs.width - horizMargins;
+ if (diff > 0) {
+ calcs.left = leftOffset + horizMargins + (diff / 2);
+ }
+ }
+
+ topOffset += calcs.height + childMargins.bottom;
+ }
+
+ return {
+ boxes: boxes,
+ meta : {
+ maxWidth : maxWidth,
+ nonFlexHeight: nonFlexHeight,
+ desiredHeight: desiredHeight,
+ minimumHeight: minimumHeight,
+ shortfall : desiredHeight - height,
+ tooNarrow : tooNarrow
+ }
+ };
+ }
+});
+
+Ext.Container.LAYOUTS.vbox = Ext.layout.VBoxLayout;
+
+Ext.layout.ToolbarLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ monitorResize : true,
+
+ type: 'toolbar',
+
+
+ triggerWidth: 18,
+
+
+ noItemsMenuText : '<div class="x-toolbar-no-items">(None)</div>',
+
+
+ lastOverflow: false,
+
+
+ tableHTML: [
+ '<table cellspacing="0" class="x-toolbar-ct">',
+ '<tbody>',
+ '<tr>',
+ '<td class="x-toolbar-left" align="{0}">',
+ '<table cellspacing="0">',
+ '<tbody>',
+ '<tr class="x-toolbar-left-row"></tr>',
+ '</tbody>',
+ '</table>',
+ '</td>',
+ '<td class="x-toolbar-right" align="right">',
+ '<table cellspacing="0" class="x-toolbar-right-ct">',
+ '<tbody>',
+ '<tr>',
+ '<td>',
+ '<table cellspacing="0">',
+ '<tbody>',
+ '<tr class="x-toolbar-right-row"></tr>',
+ '</tbody>',
+ '</table>',
+ '</td>',
+ '<td>',
+ '<table cellspacing="0">',
+ '<tbody>',
+ '<tr class="x-toolbar-extras-row"></tr>',
+ '</tbody>',
+ '</table>',
+ '</td>',
+ '</tr>',
+ '</tbody>',
+ '</table>',
+ '</td>',
+ '</tr>',
+ '</tbody>',
+ '</table>'
+ ].join(""),
+
+
+ onLayout : function(ct, target) {
+
+ if (!this.leftTr) {
+ var align = ct.buttonAlign == 'center' ? 'center' : 'left';
+
+ target.addClass('x-toolbar-layout-ct');
+ target.insertHtml('beforeEnd', String.format(this.tableHTML, align));
+
+ this.leftTr = target.child('tr.x-toolbar-left-row', true);
+ this.rightTr = target.child('tr.x-toolbar-right-row', true);
+ this.extrasTr = target.child('tr.x-toolbar-extras-row', true);
+
+ if (this.hiddenItem == undefined) {
+
+ this.hiddenItems = [];
+ }
+ }
+
+ var side = ct.buttonAlign == 'right' ? this.rightTr : this.leftTr,
+ items = ct.items.items,
+ position = 0;
+
+
+ for (var i = 0, len = items.length, c; i < len; i++, position++) {
+ c = items[i];
+
+ if (c.isFill) {
+ side = this.rightTr;
+ position = -1;
+ } else if (!c.rendered) {
+ c.render(this.insertCell(c, side, position));
+ this.configureItem(c);
+ } else {
+ if (!c.xtbHidden && !this.isValidParent(c, side.childNodes[position])) {
+ var td = this.insertCell(c, side, position);
+ td.appendChild(c.getPositionEl().dom);
+ c.container = Ext.get(td);
+ }
+ }
+ }
+
+
+ this.cleanup(this.leftTr);
+ this.cleanup(this.rightTr);
+ this.cleanup(this.extrasTr);
+ this.fitToSize(target);
+ },
+
+
+ cleanup : function(el) {
+ var cn = el.childNodes, i, c;
+
+ for (i = cn.length-1; i >= 0 && (c = cn[i]); i--) {
+ if (!c.firstChild) {
+ el.removeChild(c);
+ }
+ }
+ },
+
+
+ insertCell : function(c, target, position) {
+ var td = document.createElement('td');
+ td.className = 'x-toolbar-cell';
+
+ target.insertBefore(td, target.childNodes[position] || null);
+
+ return td;
+ },
+
+
+ hideItem : function(item) {
+ this.hiddenItems.push(item);
+
+ item.xtbHidden = true;
+ item.xtbWidth = item.getPositionEl().dom.parentNode.offsetWidth;
+ item.hide();
+ },
+
+
+ unhideItem : function(item) {
+ item.show();
+ item.xtbHidden = false;
+ this.hiddenItems.remove(item);
+ },
+
+
+ getItemWidth : function(c) {
+ return c.hidden ? (c.xtbWidth || 0) : c.getPositionEl().dom.parentNode.offsetWidth;
+ },
+
+
+ fitToSize : function(target) {
+ if (this.container.enableOverflow === false) {
+ return;
+ }
+
+ var width = target.dom.clientWidth,
+ tableWidth = target.dom.firstChild.offsetWidth,
+ clipWidth = width - this.triggerWidth,
+ lastWidth = this.lastWidth || 0,
+
+ hiddenItems = this.hiddenItems,
+ hasHiddens = hiddenItems.length != 0,
+ isLarger = width >= lastWidth;
+
+ this.lastWidth = width;
+
+ if (tableWidth > width || (hasHiddens && isLarger)) {
+ var items = this.container.items.items,
+ len = items.length,
+ loopWidth = 0,
+ item;
+
+ for (var i = 0; i < len; i++) {
+ item = items[i];
+
+ if (!item.isFill) {
+ loopWidth += this.getItemWidth(item);
+ if (loopWidth > clipWidth) {
+ if (!(item.hidden || item.xtbHidden)) {
+ this.hideItem(item);
+ }
+ } else if (item.xtbHidden) {
+ this.unhideItem(item);
+ }
+ }
+ }
+ }
+
+
+ hasHiddens = hiddenItems.length != 0;
+
+ if (hasHiddens) {
+ this.initMore();
+
+ if (!this.lastOverflow) {
+ this.container.fireEvent('overflowchange', this.container, true);
+ this.lastOverflow = true;
+ }
+ } else if (this.more) {
+ this.clearMenu();
+ this.more.destroy();
+ delete this.more;
+
+ if (this.lastOverflow) {
+ this.container.fireEvent('overflowchange', this.container, false);
+ this.lastOverflow = false;
+ }
+ }
+ },
+
+
+ createMenuConfig : function(component, hideOnClick){
+ var config = Ext.apply({}, component.initialConfig),
+ group = component.toggleGroup;
+
+ Ext.copyTo(config, component, [
+ 'iconCls', 'icon', 'itemId', 'disabled', 'handler', 'scope', 'menu'
+ ]);
+
+ Ext.apply(config, {
+ text : component.overflowText || component.text,
+ hideOnClick: hideOnClick
+ });
+
+ if (group || component.enableToggle) {
+ Ext.apply(config, {
+ group : group,
+ checked: component.pressed,
+ listeners: {
+ checkchange: function(item, checked){
+ component.toggle(checked);
+ }
+ }
+ });
+ }
+
+ delete config.ownerCt;
+ delete config.xtype;
+ delete config.id;
+
+ return config;
+ },
+
+
+ addComponentToMenu : function(menu, component) {
+ if (component instanceof Ext.Toolbar.Separator) {
+ menu.add('-');
+
+ } else if (Ext.isFunction(component.isXType)) {
+ if (component.isXType('splitbutton')) {
+ menu.add(this.createMenuConfig(component, true));
+
+ } else if (component.isXType('button')) {
+ menu.add(this.createMenuConfig(component, !component.menu));
+
+ } else if (component.isXType('buttongroup')) {
+ component.items.each(function(item){
+ this.addComponentToMenu(menu, item);
+ }, this);
+ }
+ }
+ },
+
+
+ clearMenu : function(){
+ var menu = this.moreMenu;
+ if (menu && menu.items) {
+ menu.items.each(function(item){
+ delete item.menu;
+ });
+ }
+ },
+
+
+ beforeMoreShow : function(menu) {
+ var items = this.container.items.items,
+ len = items.length,
+ item,
+ prev;
+
+ var needsSep = function(group, item){
+ return group.isXType('buttongroup') && !(item instanceof Ext.Toolbar.Separator);
+ };
+
+ this.clearMenu();
+ menu.removeAll();
+ for (var i = 0; i < len; i++) {
+ item = items[i];
+ if (item.xtbHidden) {
+ if (prev && (needsSep(item, prev) || needsSep(prev, item))) {
+ menu.add('-');
+ }
+ this.addComponentToMenu(menu, item);
+ prev = item;
+ }
+ }
+
+
+ if (menu.items.length < 1) {
+ menu.add(this.noItemsMenuText);
+ }
+ },
+
+
+ initMore : function(){
+ if (!this.more) {
+
+ this.moreMenu = new Ext.menu.Menu({
+ ownerCt : this.container,
+ listeners: {
+ beforeshow: this.beforeMoreShow,
+ scope: this
+ }
+ });
+
+
+ this.more = new Ext.Button({
+ iconCls: 'x-toolbar-more-icon',
+ cls : 'x-toolbar-more',
+ menu : this.moreMenu,
+ ownerCt: this.container
+ });
+
+ var td = this.insertCell(this.more, this.extrasTr, 100);
+ this.more.render(td);
+ }
+ },
+
+ destroy : function(){
+ Ext.destroy(this.more, this.moreMenu);
+ delete this.leftTr;
+ delete this.rightTr;
+ delete this.extrasTr;
+ Ext.layout.ToolbarLayout.superclass.destroy.call(this);
+ }
+});
+
+Ext.Container.LAYOUTS.toolbar = Ext.layout.ToolbarLayout;
+
+ Ext.layout.MenuLayout = Ext.extend(Ext.layout.ContainerLayout, {
+ monitorResize : true,
+
+ type: 'menu',
+
+ setContainer : function(ct){
+ this.monitorResize = !ct.floating;
+
+
+ ct.on('autosize', this.doAutoSize, this);
+ Ext.layout.MenuLayout.superclass.setContainer.call(this, ct);
+ },
+
+ renderItem : function(c, position, target){
+ if (!this.itemTpl) {
+ this.itemTpl = Ext.layout.MenuLayout.prototype.itemTpl = new Ext.XTemplate(
+ '<li id="{itemId}" class="{itemCls}">',
+ '<tpl if="needsIcon">',
+ '<img alt="{altText}" src="{icon}" class="{iconCls}"/>',
+ '</tpl>',
+ '</li>'
+ );
+ }
+
+ if(c && !c.rendered){
+ if(Ext.isNumber(position)){
+ position = target.dom.childNodes[position];
+ }
+ var a = this.getItemArgs(c);
+
+
+ c.render(c.positionEl = position ?
+ this.itemTpl.insertBefore(position, a, true) :
+ this.itemTpl.append(target, a, true));
+
+
+ c.positionEl.menuItemId = c.getItemId();
+
+
+
+ if (!a.isMenuItem && a.needsIcon) {
+ c.positionEl.addClass('x-menu-list-item-indent');
+ }
+ this.configureItem(c);
+ }else if(c && !this.isValidParent(c, target)){
+ if(Ext.isNumber(position)){
+ position = target.dom.childNodes[position];
+ }
+ target.dom.insertBefore(c.getActionEl().dom, position || null);
+ }
+ },
+
+ getItemArgs : function(c) {
+ var isMenuItem = c instanceof Ext.menu.Item,
+ canHaveIcon = !(isMenuItem || c instanceof Ext.menu.Separator);
+
+ return {
+ isMenuItem: isMenuItem,
+ needsIcon: canHaveIcon && (c.icon || c.iconCls),
+ icon: c.icon || Ext.BLANK_IMAGE_URL,
+ iconCls: 'x-menu-item-icon ' + (c.iconCls || ''),
+ itemId: 'x-menu-el-' + c.id,
+ itemCls: 'x-menu-list-item ',
+ altText: c.altText || ''
+ };
+ },
+
+
+ isValidParent : function(c, target) {
+ return c.el.up('li.x-menu-list-item', 5).dom.parentNode === (target.dom || target);
+ },
+
+ onLayout : function(ct, target){
+ Ext.layout.MenuLayout.superclass.onLayout.call(this, ct, target);
+ this.doAutoSize();
+ },
+
+ doAutoSize : function(){
+ var ct = this.container, w = ct.width;
+ if(ct.floating){
+ if(w){
+ ct.setWidth(w);
+ }else if(Ext.isIE9m){
+ ct.setWidth(Ext.isStrict && (Ext.isIE7 || Ext.isIE8 || Ext.isIE9) ? 'auto' : ct.minWidth);
+ var el = ct.getEl(), t = el.dom.offsetWidth;
+ ct.setWidth(ct.getLayoutTarget().getWidth() + el.getFrameWidth('lr'));
+ }
+ }
+ }
+});
+Ext.Container.LAYOUTS['menu'] = Ext.layout.MenuLayout;
+
+Ext.Viewport = Ext.extend(Ext.Container, {
+
+
+
+
+
+
+
+
+
+
+
+
+ initComponent : function() {
+ Ext.Viewport.superclass.initComponent.call(this);
+ document.getElementsByTagName('html')[0].className += ' x-viewport';
+ this.el = Ext.getBody();
+ this.el.setHeight = Ext.emptyFn;
+ this.el.setWidth = Ext.emptyFn;
+ this.el.setSize = Ext.emptyFn;
+ this.el.dom.scroll = 'no';
+ this.allowDomMove = false;
+ this.autoWidth = true;
+ this.autoHeight = true;
+ Ext.EventManager.onWindowResize(this.fireResize, this);
+ this.renderTo = this.el;
+ },
+
+ fireResize : function(w, h){
+ this.fireEvent('resize', this, w, h, w, h);
+ }
+});
+Ext.reg('viewport', Ext.Viewport);
+
+Ext.Panel = Ext.extend(Ext.Container, {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ baseCls : 'x-panel',
+
+ collapsedCls : 'x-panel-collapsed',
+
+ maskDisabled : true,
+
+ animCollapse : Ext.enableFx,
+
+ headerAsText : true,
+
+ buttonAlign : 'right',
+
+ collapsed : false,
+
+ collapseFirst : true,
+
+ minButtonWidth : 75,
+
+
+ elements : 'body',
+
+ preventBodyReset : false,
+
+
+ padding: undefined,
+
+
+ resizeEvent: 'bodyresize',
+
+
+
+
+ toolTarget : 'header',
+ collapseEl : 'bwrap',
+ slideAnchor : 't',
+ disabledClass : '',
+
+
+ deferHeight : true,
+
+ expandDefaults: {
+ duration : 0.25
+ },
+
+ collapseDefaults : {
+ duration : 0.25
+ },
+
+
+ initComponent : function(){
+ Ext.Panel.superclass.initComponent.call(this);
+
+ this.addEvents(
+
+ 'bodyresize',
+
+ 'titlechange',
+
+ 'iconchange',
+
+ 'collapse',
+
+ 'expand',
+
+ 'beforecollapse',
+
+ 'beforeexpand',
+
+ 'beforeclose',
+
+ 'close',
+
+ 'activate',
+
+ 'deactivate'
+ );
+
+ if(this.unstyled){
+ this.baseCls = 'x-plain';
+ }
+
+
+ this.toolbars = [];
+
+ if(this.tbar){
+ this.elements += ',tbar';
+ this.topToolbar = this.createToolbar(this.tbar);
+ this.tbar = null;
+
+ }
+ if(this.bbar){
+ this.elements += ',bbar';
+ this.bottomToolbar = this.createToolbar(this.bbar);
+ this.bbar = null;
+ }
+
+ if(this.header === true){
+ this.elements += ',header';
+ this.header = null;
+ }else if(this.headerCfg || (this.title && this.header !== false)){
+ this.elements += ',header';
+ }
+
+ if(this.footerCfg || this.footer === true){
+ this.elements += ',footer';
+ this.footer = null;
+ }
+
+ if(this.buttons){
+ this.fbar = this.buttons;
+ this.buttons = null;
+ }
+ if(this.fbar){
+ this.createFbar(this.fbar);
+ }
+ if(this.autoLoad){
+ this.on('render', this.doAutoLoad, this, {delay:10});
+ }
+ },
+
+
+ createFbar : function(fbar){
+ var min = this.minButtonWidth;
+ this.elements += ',footer';
+ this.fbar = this.createToolbar(fbar, {
+ buttonAlign: this.buttonAlign,
+ toolbarCls: 'x-panel-fbar',
+ enableOverflow: false,
+ defaults: function(c){
+ return {
+ minWidth: c.minWidth || min
+ };
+ }
+ });
+
+
+
+ this.fbar.items.each(function(c){
+ c.minWidth = c.minWidth || this.minButtonWidth;
+ }, this);
+ this.buttons = this.fbar.items.items;
+ },
+
+
+ createToolbar: function(tb, options){
+ var result;
+
+ if(Ext.isArray(tb)){
+ tb = {
+ items: tb
+ };
+ }
+ result = tb.events ? Ext.apply(tb, options) : this.createComponent(Ext.apply({}, tb, options), 'toolbar');
+ this.toolbars.push(result);
+ return result;
+ },
+
+
+ createElement : function(name, pnode){
+ if(this[name]){
+ pnode.appendChild(this[name].dom);
+ return;
+ }
+
+ if(name === 'bwrap' || this.elements.indexOf(name) != -1){
+ if(this[name+'Cfg']){
+ this[name] = Ext.fly(pnode).createChild(this[name+'Cfg']);
+ }else{
+ var el = document.createElement('div');
+ el.className = this[name+'Cls'];
+ this[name] = Ext.get(pnode.appendChild(el));
+ }
+ if(this[name+'CssClass']){
+ this[name].addClass(this[name+'CssClass']);
+ }
+ if(this[name+'Style']){
+ this[name].applyStyles(this[name+'Style']);
+ }
+ }
+ },
+
+
+ onRender : function(ct, position){
+ Ext.Panel.superclass.onRender.call(this, ct, position);
+ this.createClasses();
+
+ var el = this.el,
+ d = el.dom,
+ bw,
+ ts;
+
+
+ if(this.collapsible && !this.hideCollapseTool){
+ this.tools = this.tools ? this.tools.slice(0) : [];
+ this.tools[this.collapseFirst?'unshift':'push']({
+ id: 'toggle',
+ handler : this.toggleCollapse,
+ scope: this
+ });
+ }
+
+ if(this.tools){
+ ts = this.tools;
+ this.elements += (this.header !== false) ? ',header' : '';
+ }
+ this.tools = {};
+
+ el.addClass(this.baseCls);
+ if(d.firstChild){
+ this.header = el.down('.'+this.headerCls);
+ this.bwrap = el.down('.'+this.bwrapCls);
+ var cp = this.bwrap ? this.bwrap : el;
+ this.tbar = cp.down('.'+this.tbarCls);
+ this.body = cp.down('.'+this.bodyCls);
+ this.bbar = cp.down('.'+this.bbarCls);
+ this.footer = cp.down('.'+this.footerCls);
+ this.fromMarkup = true;
+ }
+ if (this.preventBodyReset === true) {
+ el.addClass('x-panel-reset');
+ }
+ if(this.cls){
+ el.addClass(this.cls);
+ }
+
+ if(this.buttons){
+ this.elements += ',footer';
+ }
+
+
+
+
+ if(this.frame){
+ el.insertHtml('afterBegin', String.format(Ext.Element.boxMarkup, this.baseCls));
+
+ this.createElement('header', d.firstChild.firstChild.firstChild);
+ this.createElement('bwrap', d);
+
+
+ bw = this.bwrap.dom;
+ var ml = d.childNodes[1], bl = d.childNodes[2];
+ bw.appendChild(ml);
+ bw.appendChild(bl);
+
+ var mc = bw.firstChild.firstChild.firstChild;
+ this.createElement('tbar', mc);
+ this.createElement('body', mc);
+ this.createElement('bbar', mc);
+ this.createElement('footer', bw.lastChild.firstChild.firstChild);
+
+ if(!this.footer){
+ this.bwrap.dom.lastChild.className += ' x-panel-nofooter';
+ }
+
+ this.ft = Ext.get(this.bwrap.dom.lastChild);
+ this.mc = Ext.get(mc);
+ }else{
+ this.createElement('header', d);
+ this.createElement('bwrap', d);
+
+
+ bw = this.bwrap.dom;
+ this.createElement('tbar', bw);
+ this.createElement('body', bw);
+ this.createElement('bbar', bw);
+ this.createElement('footer', bw);
+
+ if(!this.header){
+ this.body.addClass(this.bodyCls + '-noheader');
+ if(this.tbar){
+ this.tbar.addClass(this.tbarCls + '-noheader');
+ }
+ }
+ }
+
+ if(Ext.isDefined(this.padding)){
+ this.body.setStyle('padding', this.body.addUnits(this.padding));
+ }
+
+ if(this.border === false){
+ this.el.addClass(this.baseCls + '-noborder');
+ this.body.addClass(this.bodyCls + '-noborder');
+ if(this.header){
+ this.header.addClass(this.headerCls + '-noborder');
+ }
+ if(this.footer){
+ this.footer.addClass(this.footerCls + '-noborder');
+ }
+ if(this.tbar){
+ this.tbar.addClass(this.tbarCls + '-noborder');
+ }
+ if(this.bbar){
+ this.bbar.addClass(this.bbarCls + '-noborder');
+ }
+ }
+
+ if(this.bodyBorder === false){
+ this.body.addClass(this.bodyCls + '-noborder');
+ }
+
+ this.bwrap.enableDisplayMode('block');
+
+ if(this.header){
+ this.header.unselectable();
+
+
+ if(this.headerAsText){
+ this.header.dom.innerHTML =
+ '<span class="' + this.headerTextCls + '">'+this.header.dom.innerHTML+'</span>';
+
+ if(this.iconCls){
+ this.setIconClass(this.iconCls);
+ }
+ }
+ }
+
+ if(this.floating){
+ this.makeFloating(this.floating);
+ }
+
+ if(this.collapsible && this.titleCollapse && this.header){
+ this.mon(this.header, 'click', this.toggleCollapse, this);
+ this.header.setStyle('cursor', 'pointer');
+ }
+ if(ts){
+ this.addTool.apply(this, ts);
+ }
+
+
+ if(this.fbar){
+ this.footer.addClass('x-panel-btns');
+ this.fbar.ownerCt = this;
+ this.fbar.render(this.footer);
+ this.footer.createChild({cls:'x-clear'});
+ }
+ if(this.tbar && this.topToolbar){
+ this.topToolbar.ownerCt = this;
+ this.topToolbar.render(this.tbar);
+ }
+ if(this.bbar && this.bottomToolbar){
+ this.bottomToolbar.ownerCt = this;
+ this.bottomToolbar.render(this.bbar);
+ }
+ },
+
+
+ setIconClass : function(cls){
+ var old = this.iconCls;
+ this.iconCls = cls;
+ if(this.rendered && this.header){
+ if(this.frame){
+ this.header.addClass('x-panel-icon');
+ this.header.replaceClass(old, this.iconCls);
+ }else{
+ var hd = this.header,
+ img = hd.child('img.x-panel-inline-icon');
+ if(img){
+ Ext.fly(img).replaceClass(old, this.iconCls);
+ }else{
+ var hdspan = hd.child('span.' + this.headerTextCls);
+ if (hdspan) {
+ Ext.DomHelper.insertBefore(hdspan.dom, {
+ tag:'img', alt: '', src: Ext.BLANK_IMAGE_URL, cls:'x-panel-inline-icon '+this.iconCls
+ });
+ }
+ }
+ }
+ }
+ this.fireEvent('iconchange', this, cls, old);
+ },
+
+
+ makeFloating : function(cfg){
+ this.floating = true;
+ this.el = new Ext.Layer(Ext.apply({}, cfg, {
+ shadow: Ext.isDefined(this.shadow) ? this.shadow : 'sides',
+ shadowOffset: this.shadowOffset,
+ constrain:false,
+ shim: this.shim === false ? false : undefined
+ }), this.el);
+ },
+
+
+ getTopToolbar : function(){
+ return this.topToolbar;
+ },
+
+
+ getBottomToolbar : function(){
+ return this.bottomToolbar;
+ },
+
+
+ getFooterToolbar : function() {
+ return this.fbar;
+ },
+
+
+ addButton : function(config, handler, scope){
+ if(!this.fbar){
+ this.createFbar([]);
+ }
+ if(handler){
+ if(Ext.isString(config)){
+ config = {text: config};
+ }
+ config = Ext.apply({
+ handler: handler,
+ scope: scope
+ }, config);
+ }
+ return this.fbar.add(config);
+ },
+
+
+ addTool : function(){
+ if(!this.rendered){
+ if(!this.tools){
+ this.tools = [];
+ }
+ Ext.each(arguments, function(arg){
+ this.tools.push(arg);
+ }, this);
+ return;
+ }
+
+ if(!this[this.toolTarget]){
+ return;
+ }
+ if(!this.toolTemplate){
+
+ var tt = new Ext.Template(
+ '<div class="x-tool x-tool-{id}">&#160;</div>'
+ );
+ tt.disableFormats = true;
+ tt.compile();
+ Ext.Panel.prototype.toolTemplate = tt;
+ }
+ for(var i = 0, a = arguments, len = a.length; i < len; i++) {
+ var tc = a[i];
+ if(!this.tools[tc.id]){
+ var overCls = 'x-tool-'+tc.id+'-over';
+ var t = this.toolTemplate.insertFirst(this[this.toolTarget], tc, true);
+ this.tools[tc.id] = t;
+ t.enableDisplayMode('block');
+ this.mon(t, 'click', this.createToolHandler(t, tc, overCls, this));
+ if(tc.on){
+ this.mon(t, tc.on);
+ }
+ if(tc.hidden){
+ t.hide();
+ }
+ if(tc.qtip){
+ if(Ext.isObject(tc.qtip)){
+ Ext.QuickTips.register(Ext.apply({
+ target: t.id
+ }, tc.qtip));
+ } else {
+ t.dom.qtip = tc.qtip;
+ }
+ }
+ t.addClassOnOver(overCls);
+ }
+ }
+ },
+
+ onLayout : function(shallow, force){
+ Ext.Panel.superclass.onLayout.apply(this, arguments);
+ if(this.hasLayout && this.toolbars.length > 0){
+ Ext.each(this.toolbars, function(tb){
+ tb.doLayout(undefined, force);
+ });
+ this.syncHeight();
+ }
+ },
+
+ syncHeight : function(){
+ var h = this.toolbarHeight,
+ bd = this.body,
+ lsh = this.lastSize.height,
+ sz;
+
+ if(this.autoHeight || !Ext.isDefined(lsh) || lsh == 'auto'){
+ return;
+ }
+
+
+ if(h != this.getToolbarHeight()){
+ h = Math.max(0, lsh - this.getFrameHeight());
+ bd.setHeight(h);
+ sz = bd.getSize();
+ this.toolbarHeight = this.getToolbarHeight();
+ this.onBodyResize(sz.width, sz.height);
+ }
+ },
+
+
+ onShow : function(){
+ if(this.floating){
+ return this.el.show();
+ }
+ Ext.Panel.superclass.onShow.call(this);
+ },
+
+
+ onHide : function(){
+ if(this.floating){
+ return this.el.hide();
+ }
+ Ext.Panel.superclass.onHide.call(this);
+ },
+
+
+ createToolHandler : function(t, tc, overCls, panel){
+ return function(e){
+ t.removeClass(overCls);
+ if(tc.stopEvent !== false){
+ e.stopEvent();
+ }
+ if(tc.handler){
+ tc.handler.call(tc.scope || t, e, t, panel, tc);
+ }
+ };
+ },
+
+
+ afterRender : function(){
+ if(this.floating && !this.hidden){
+ this.el.show();
+ }
+ if(this.title){
+ this.setTitle(this.title);
+ }
+ Ext.Panel.superclass.afterRender.call(this);
+ if (this.collapsed) {
+ this.collapsed = false;
+ this.collapse(false);
+ }
+ this.initEvents();
+ },
+
+
+ getKeyMap : function(){
+ if(!this.keyMap){
+ this.keyMap = new Ext.KeyMap(this.el, this.keys);
+ }
+ return this.keyMap;
+ },
+
+
+ initEvents : function(){
+ if(this.keys){
+ this.getKeyMap();
+ }
+ if(this.draggable){
+ this.initDraggable();
+ }
+ if(this.toolbars.length > 0){
+ Ext.each(this.toolbars, function(tb){
+ tb.doLayout();
+ tb.on({
+ scope: this,
+ afterlayout: this.syncHeight,
+ remove: this.syncHeight
+ });
+ }, this);
+ this.syncHeight();
+ }
+
+ },
+
+
+ initDraggable : function(){
+
+ this.dd = new Ext.Panel.DD(this, Ext.isBoolean(this.draggable) ? null : this.draggable);
+ },
+
+
+ beforeEffect : function(anim){
+ if(this.floating){
+ this.el.beforeAction();
+ }
+ if(anim !== false){
+ this.el.addClass('x-panel-animated');
+ }
+ },
+
+
+ afterEffect : function(anim){
+ this.syncShadow();
+ this.el.removeClass('x-panel-animated');
+ },
+
+
+ createEffect : function(a, cb, scope){
+ var o = {
+ scope:scope,
+ block:true
+ };
+ if(a === true){
+ o.callback = cb;
+ return o;
+ }else if(!a.callback){
+ o.callback = cb;
+ }else {
+ o.callback = function(){
+ cb.call(scope);
+ Ext.callback(a.callback, a.scope);
+ };
+ }
+ return Ext.applyIf(o, a);
+ },
+
+
+ collapse : function(animate){
+ if(this.collapsed || this.el.hasFxBlock() || this.fireEvent('beforecollapse', this, animate) === false){
+ return;
+ }
+ var doAnim = animate === true || (animate !== false && this.animCollapse);
+ this.beforeEffect(doAnim);
+ this.onCollapse(doAnim, animate);
+ return this;
+ },
+
+
+ onCollapse : function(doAnim, animArg){
+ if(doAnim){
+ this[this.collapseEl].slideOut(this.slideAnchor,
+ Ext.apply(this.createEffect(animArg||true, this.afterCollapse, this),
+ this.collapseDefaults));
+ }else{
+ this[this.collapseEl].hide(this.hideMode);
+ this.afterCollapse(false);
+ }
+ },
+
+
+ afterCollapse : function(anim){
+ this.collapsed = true;
+ this.el.addClass(this.collapsedCls);
+ if(anim !== false){
+ this[this.collapseEl].hide(this.hideMode);
+ }
+ this.afterEffect(anim);
+
+
+ this.cascade(function(c) {
+ if (c.lastSize) {
+ c.lastSize = { width: undefined, height: undefined };
+ }
+ });
+ this.fireEvent('collapse', this);
+ },
+
+
+ expand : function(animate){
+ if(!this.collapsed || this.el.hasFxBlock() || this.fireEvent('beforeexpand', this, animate) === false){
+ return;
+ }
+ var doAnim = animate === true || (animate !== false && this.animCollapse);
+ this.el.removeClass(this.collapsedCls);
+ this.beforeEffect(doAnim);
+ this.onExpand(doAnim, animate);
+ return this;
+ },
+
+
+ onExpand : function(doAnim, animArg){
+ if(doAnim){
+ this[this.collapseEl].slideIn(this.slideAnchor,
+ Ext.apply(this.createEffect(animArg||true, this.afterExpand, this),
+ this.expandDefaults));
+ }else{
+ this[this.collapseEl].show(this.hideMode);
+ this.afterExpand(false);
+ }
+ },
+
+
+ afterExpand : function(anim){
+ this.collapsed = false;
+ if(anim !== false){
+ this[this.collapseEl].show(this.hideMode);
+ }
+ this.afterEffect(anim);
+ if (this.deferLayout) {
+ delete this.deferLayout;
+ this.doLayout(true);
+ }
+ this.fireEvent('expand', this);
+ },
+
+
+ toggleCollapse : function(animate){
+ this[this.collapsed ? 'expand' : 'collapse'](animate);
+ return this;
+ },
+
+
+ onDisable : function(){
+ if(this.rendered && this.maskDisabled){
+ this.el.mask();
+ }
+ Ext.Panel.superclass.onDisable.call(this);
+ },
+
+
+ onEnable : function(){
+ if(this.rendered && this.maskDisabled){
+ this.el.unmask();
+ }
+ Ext.Panel.superclass.onEnable.call(this);
+ },
+
+
+ onResize : function(adjWidth, adjHeight, rawWidth, rawHeight){
+ var w = adjWidth,
+ h = adjHeight;
+
+ if(Ext.isDefined(w) || Ext.isDefined(h)){
+ if(!this.collapsed){
+
+
+
+
+ if(Ext.isNumber(w)){
+ this.body.setWidth(w = this.adjustBodyWidth(w - this.getFrameWidth()));
+ } else if (w == 'auto') {
+ w = this.body.setWidth('auto').dom.offsetWidth;
+ } else {
+ w = this.body.dom.offsetWidth;
+ }
+
+ if(this.tbar){
+ this.tbar.setWidth(w);
+ if(this.topToolbar){
+ this.topToolbar.setSize(w);
+ }
+ }
+ if(this.bbar){
+ this.bbar.setWidth(w);
+ if(this.bottomToolbar){
+ this.bottomToolbar.setSize(w);
+
+ if (Ext.isIE9m) {
+ this.bbar.setStyle('position', 'static');
+ this.bbar.setStyle('position', '');
+ }
+ }
+ }
+ if(this.footer){
+ this.footer.setWidth(w);
+ if(this.fbar){
+ this.fbar.setSize(Ext.isIE9m ? (w - this.footer.getFrameWidth('lr')) : 'auto');
+ }
+ }
+
+
+ if(Ext.isNumber(h)){
+ h = Math.max(0, h - this.getFrameHeight());
+
+ this.body.setHeight(h);
+ }else if(h == 'auto'){
+ this.body.setHeight(h);
+ }
+
+ if(this.disabled && this.el._mask){
+ this.el._mask.setSize(this.el.dom.clientWidth, this.el.getHeight());
+ }
+ }else{
+
+ this.queuedBodySize = {width: w, height: h};
+ if(!this.queuedExpand && this.allowQueuedExpand !== false){
+ this.queuedExpand = true;
+ this.on('expand', function(){
+ delete this.queuedExpand;
+ this.onResize(this.queuedBodySize.width, this.queuedBodySize.height);
+ }, this, {single:true});
+ }
+ }
+ this.onBodyResize(w, h);
+ }
+ this.syncShadow();
+ Ext.Panel.superclass.onResize.call(this, adjWidth, adjHeight, rawWidth, rawHeight);
+
+ },
+
+
+ onBodyResize: function(w, h){
+ this.fireEvent('bodyresize', this, w, h);
+ },
+
+
+ getToolbarHeight: function(){
+ var h = 0;
+ if(this.rendered){
+ Ext.each(this.toolbars, function(tb){
+ h += tb.getHeight();
+ }, this);
+ }
+ return h;
+ },
+
+
+ adjustBodyHeight : function(h){
+ return h;
+ },
+
+
+ adjustBodyWidth : function(w){
+ return w;
+ },
+
+
+ onPosition : function(){
+ this.syncShadow();
+ },
+
+
+ getFrameWidth : function(){
+ var w = this.el.getFrameWidth('lr') + this.bwrap.getFrameWidth('lr');
+
+ if(this.frame){
+ var l = this.bwrap.dom.firstChild;
+ w += (Ext.fly(l).getFrameWidth('l') + Ext.fly(l.firstChild).getFrameWidth('r'));
+ w += this.mc.getFrameWidth('lr');
+ }
+ return w;
+ },
+
+
+ getFrameHeight : function() {
+ var h = this.el.getFrameWidth('tb') + this.bwrap.getFrameWidth('tb');
+ h += (this.tbar ? this.tbar.getHeight() : 0) +
+ (this.bbar ? this.bbar.getHeight() : 0);
+
+ if(this.frame){
+ h += this.el.dom.firstChild.offsetHeight + this.ft.dom.offsetHeight + this.mc.getFrameWidth('tb');
+ }else{
+ h += (this.header ? this.header.getHeight() : 0) +
+ (this.footer ? this.footer.getHeight() : 0);
+ }
+ return h;
+ },
+
+
+ getInnerWidth : function(){
+ return this.getSize().width - this.getFrameWidth();
+ },
+
+
+ getInnerHeight : function(){
+ return this.body.getHeight();
+
+ },
+
+
+ syncShadow : function(){
+ if(this.floating){
+ this.el.sync(true);
+ }
+ },
+
+
+ getLayoutTarget : function(){
+ return this.body;
+ },
+
+
+ getContentTarget : function(){
+ return this.body;
+ },
+
+
+ setTitle : function(title, iconCls){
+ this.title = title;
+ if(this.header && this.headerAsText){
+ this.header.child('span').update(title);
+ }
+ if(iconCls){
+ this.setIconClass(iconCls);
+ }
+ this.fireEvent('titlechange', this, title);
+ return this;
+ },
+
+
+ getUpdater : function(){
+ return this.body.getUpdater();
+ },
+
+
+ load : function(){
+ var um = this.body.getUpdater();
+ um.update.apply(um, arguments);
+ return this;
+ },
+
+
+ beforeDestroy : function(){
+ Ext.Panel.superclass.beforeDestroy.call(this);
+ if(this.header){
+ this.header.removeAllListeners();
+ }
+ if(this.tools){
+ for(var k in this.tools){
+ Ext.destroy(this.tools[k]);
+ }
+ }
+ if(this.toolbars.length > 0){
+ Ext.each(this.toolbars, function(tb){
+ tb.un('afterlayout', this.syncHeight, this);
+ tb.un('remove', this.syncHeight, this);
+ }, this);
+ }
+ if(Ext.isArray(this.buttons)){
+ while(this.buttons.length) {
+ Ext.destroy(this.buttons[0]);
+ }
+ }
+ if(this.rendered){
+ Ext.destroy(
+ this.ft,
+ this.header,
+ this.footer,
+ this.tbar,
+ this.bbar,
+ this.body,
+ this.mc,
+ this.bwrap,
+ this.dd
+ );
+ if (this.fbar) {
+ Ext.destroy(
+ this.fbar,
+ this.fbar.el
+ );
+ }
+ }
+ Ext.destroy(this.toolbars);
+ },
+
+
+ createClasses : function(){
+ this.headerCls = this.baseCls + '-header';
+ this.headerTextCls = this.baseCls + '-header-text';
+ this.bwrapCls = this.baseCls + '-bwrap';
+ this.tbarCls = this.baseCls + '-tbar';
+ this.bodyCls = this.baseCls + '-body';
+ this.bbarCls = this.baseCls + '-bbar';
+ this.footerCls = this.baseCls + '-footer';
+ },
+
+
+ createGhost : function(cls, useShim, appendTo){
+ var el = document.createElement('div');
+ el.className = 'x-panel-ghost ' + (cls ? cls : '');
+ if(this.header){
+ el.appendChild(this.el.dom.firstChild.cloneNode(true));
+ }
+ Ext.fly(el.appendChild(document.createElement('ul'))).setHeight(this.bwrap.getHeight());
+ el.style.width = this.el.dom.offsetWidth + 'px';;
+ if(!appendTo){
+ this.container.dom.appendChild(el);
+ }else{
+ Ext.getDom(appendTo).appendChild(el);
+ }
+ if(useShim !== false && this.el.useShim !== false){
+ var layer = new Ext.Layer({shadow:false, useDisplay:true, constrain:false}, el);
+ layer.show();
+ return layer;
+ }else{
+ return new Ext.Element(el);
+ }
+ },
+
+
+ doAutoLoad : function(){
+ var u = this.body.getUpdater();
+ if(this.renderer){
+ u.setRenderer(this.renderer);
+ }
+ u.update(Ext.isObject(this.autoLoad) ? this.autoLoad : {url: this.autoLoad});
+ },
+
+
+ getTool : function(id) {
+ return this.tools[id];
+ }
+
+
+});
+Ext.reg('panel', Ext.Panel);
+
+Ext.Editor = function(field, config){
+ if(field.field){
+ this.field = Ext.create(field.field, 'textfield');
+ config = Ext.apply({}, field);
+ delete config.field;
+ }else{
+ this.field = field;
+ }
+ Ext.Editor.superclass.constructor.call(this, config);
+};
+
+Ext.extend(Ext.Editor, Ext.Component, {
+
+
+ allowBlur: true,
+
+
+
+
+
+ value : "",
+
+ alignment: "c-c?",
+
+ offsets: [0, 0],
+
+ shadow : "frame",
+
+ constrain : false,
+
+ swallowKeys : true,
+
+ completeOnEnter : true,
+
+ cancelOnEsc : true,
+
+ updateEl : false,
+
+ initComponent : function(){
+ Ext.Editor.superclass.initComponent.call(this);
+ this.addEvents(
+
+ "beforestartedit",
+
+ "startedit",
+
+ "beforecomplete",
+
+ "complete",
+
+ "canceledit",
+
+ "specialkey"
+ );
+ },
+
+
+ onRender : function(ct, position){
+ this.el = new Ext.Layer({
+ shadow: this.shadow,
+ cls: "x-editor",
+ parentEl : ct,
+ shim : this.shim,
+ shadowOffset: this.shadowOffset || 4,
+ id: this.id,
+ constrain: this.constrain
+ });
+ if(this.zIndex){
+ this.el.setZIndex(this.zIndex);
+ }
+ this.el.setStyle("overflow", Ext.isGecko ? "auto" : "hidden");
+ if(this.field.msgTarget != 'title'){
+ this.field.msgTarget = 'qtip';
+ }
+ this.field.inEditor = true;
+ this.mon(this.field, {
+ scope: this,
+ blur: this.onBlur,
+ specialkey: this.onSpecialKey
+ });
+ if(this.field.grow){
+ this.mon(this.field, "autosize", this.el.sync, this.el, {delay:1});
+ }
+ this.field.render(this.el).show();
+ this.field.getEl().dom.name = '';
+ if(this.swallowKeys){
+ this.field.el.swallowEvent([
+ 'keypress',
+ 'keydown'
+ ]);
+ }
+ },
+
+
+ onSpecialKey : function(field, e){
+ var key = e.getKey(),
+ complete = this.completeOnEnter && key == e.ENTER,
+ cancel = this.cancelOnEsc && key == e.ESC;
+ if(complete || cancel){
+ e.stopEvent();
+ if(complete){
+ this.completeEdit();
+ }else{
+ this.cancelEdit();
+ }
+ if(field.triggerBlur){
+ field.triggerBlur();
+ }
+ }
+ this.fireEvent('specialkey', field, e);
+ },
+
+
+ startEdit : function(el, value){
+ if(this.editing){
+ this.completeEdit();
+ }
+ this.boundEl = Ext.get(el);
+ var v = value !== undefined ? value : this.boundEl.dom.innerHTML;
+ if(!this.rendered){
+ this.render(this.parentEl || document.body);
+ }
+ if(this.fireEvent("beforestartedit", this, this.boundEl, v) !== false){
+ this.startValue = v;
+ this.field.reset();
+ this.field.setValue(v);
+ this.realign(true);
+ this.editing = true;
+ this.show();
+ }
+ },
+
+
+ doAutoSize : function(){
+ if(this.autoSize){
+ var sz = this.boundEl.getSize(),
+ fs = this.field.getSize();
+
+ switch(this.autoSize){
+ case "width":
+ this.setSize(sz.width, fs.height);
+ break;
+ case "height":
+ this.setSize(fs.width, sz.height);
+ break;
+ case "none":
+ this.setSize(fs.width, fs.height);
+ break;
+ default:
+ this.setSize(sz.width, sz.height);
+ }
+ }
+ },
+
+
+ setSize : function(w, h){
+ delete this.field.lastSize;
+ this.field.setSize(w, h);
+ if(this.el){
+
+ if(Ext.isGecko2 || Ext.isOpera || (Ext.isIE7 && Ext.isStrict)){
+
+ this.el.setSize(w, h);
+ }
+ this.el.sync();
+ }
+ },
+
+
+ realign : function(autoSize){
+ if(autoSize === true){
+ this.doAutoSize();
+ }
+ this.el.alignTo(this.boundEl, this.alignment, this.offsets);
+ },
+
+
+ completeEdit : function(remainVisible){
+ if(!this.editing){
+ return;
+ }
+
+ if (this.field.assertValue) {
+ this.field.assertValue();
+ }
+ var v = this.getValue();
+ if(!this.field.isValid()){
+ if(this.revertInvalid !== false){
+ this.cancelEdit(remainVisible);
+ }
+ return;
+ }
+ if(String(v) === String(this.startValue) && this.ignoreNoChange){
+ this.hideEdit(remainVisible);
+ return;
+ }
+ if(this.fireEvent("beforecomplete", this, v, this.startValue) !== false){
+ v = this.getValue();
+ if(this.updateEl && this.boundEl){
+ this.boundEl.update(v);
+ }
+ this.hideEdit(remainVisible);
+ this.fireEvent("complete", this, v, this.startValue);
+ }
+ },
+
+
+ onShow : function(){
+ this.el.show();
+ if(this.hideEl !== false){
+ this.boundEl.hide();
+ }
+ this.field.show().focus(false, true);
+ this.fireEvent("startedit", this.boundEl, this.startValue);
+ },
+
+
+ cancelEdit : function(remainVisible){
+ if(this.editing){
+ var v = this.getValue();
+ this.setValue(this.startValue);
+ this.hideEdit(remainVisible);
+ this.fireEvent("canceledit", this, v, this.startValue);
+ }
+ },
+
+
+ hideEdit: function(remainVisible){
+ if(remainVisible !== true){
+ this.editing = false;
+ this.hide();
+ }
+ },
+
+
+ onBlur : function(){
+
+ if(this.allowBlur === true && this.editing && this.selectSameEditor !== true){
+ this.completeEdit();
+ }
+ },
+
+
+ onHide : function(){
+ if(this.editing){
+ this.completeEdit();
+ return;
+ }
+ this.field.blur();
+ if(this.field.collapse){
+ this.field.collapse();
+ }
+ this.el.hide();
+ if(this.hideEl !== false){
+ this.boundEl.show();
+ }
+ },
+
+
+ setValue : function(v){
+ this.field.setValue(v);
+ },
+
+
+ getValue : function(){
+ return this.field.getValue();
+ },
+
+ beforeDestroy : function(){
+ Ext.destroyMembers(this, 'field');
+
+ delete this.parentEl;
+ delete this.boundEl;
+ }
+});
+Ext.reg('editor', Ext.Editor);
+
+Ext.ColorPalette = Ext.extend(Ext.Component, {
+
+
+ itemCls : 'x-color-palette',
+
+ value : null,
+
+ clickEvent :'click',
+
+ ctype : 'Ext.ColorPalette',
+
+
+ allowReselect : false,
+
+
+ colors : [
+ '000000', '993300', '333300', '003300', '003366', '000080', '333399', '333333',
+ '800000', 'FF6600', '808000', '008000', '008080', '0000FF', '666699', '808080',
+ 'FF0000', 'FF9900', '99CC00', '339966', '33CCCC', '3366FF', '800080', '969696',
+ 'FF00FF', 'FFCC00', 'FFFF00', '00FF00', '00FFFF', '00CCFF', '993366', 'C0C0C0',
+ 'FF99CC', 'FFCC99', 'FFFF99', 'CCFFCC', 'CCFFFF', '99CCFF', 'CC99FF', 'FFFFFF'
+ ],
+
+
+
+
+
+ initComponent : function(){
+ Ext.ColorPalette.superclass.initComponent.call(this);
+ this.addEvents(
+
+ 'select'
+ );
+
+ if(this.handler){
+ this.on('select', this.handler, this.scope, true);
+ }
+ },
+
+
+ onRender : function(container, position){
+ this.autoEl = {
+ tag: 'div',
+ cls: this.itemCls
+ };
+ Ext.ColorPalette.superclass.onRender.call(this, container, position);
+ var t = this.tpl || new Ext.XTemplate(
+ '<tpl for="."><a href="#" class="color-{.}" hidefocus="on"><em><span style="background:#{.}" class="x-unselectable" unselectable="on">&#160;</span></em></a></tpl>'
+ );
+ t.overwrite(this.el, this.colors);
+ this.mon(this.el, this.clickEvent, this.handleClick, this, {delegate: 'a'});
+ if(this.clickEvent != 'click'){
+ this.mon(this.el, 'click', Ext.emptyFn, this, {delegate: 'a', preventDefault: true});
+ }
+ },
+
+
+ afterRender : function(){
+ Ext.ColorPalette.superclass.afterRender.call(this);
+ if(this.value){
+ var s = this.value;
+ this.value = null;
+ this.select(s, true);
+ }
+ },
+
+
+ handleClick : function(e, t){
+ e.preventDefault();
+ if(!this.disabled){
+ var c = t.className.match(/(?:^|\s)color-(.{6})(?:\s|$)/)[1];
+ this.select(c.toUpperCase());
+ }
+ },
+
+
+ select : function(color, suppressEvent){
+ color = color.replace('#', '');
+ if(color != this.value || this.allowReselect){
+ var el = this.el;
+ if(this.value){
+ el.child('a.color-'+this.value).removeClass('x-color-palette-sel');
+ }
+ el.child('a.color-'+color).addClass('x-color-palette-sel');
+ this.value = color;
+ if(suppressEvent !== true){
+ this.fireEvent('select', this, color);
+ }
+ }
+ }
+
+
+});
+Ext.reg('colorpalette', Ext.ColorPalette);
+Ext.DatePicker = Ext.extend(Ext.BoxComponent, {
+
+ todayText : 'Today',
+
+ okText : '&#160;OK&#160;',
+
+ cancelText : 'Cancel',
+
+
+
+ todayTip : '{0} (Spacebar)',
+
+ minText : 'This date is before the minimum date',
+
+ maxText : 'This date is after the maximum date',
+
+ format : 'm/d/y',
+
+ disabledDaysText : 'Disabled',
+
+ disabledDatesText : 'Disabled',
+
+ monthNames : Date.monthNames,
+
+ dayNames : Date.dayNames,
+
+ nextText : 'Next Month (Control+Right)',
+
+ prevText : 'Previous Month (Control+Left)',
+
+ monthYearText : 'Choose a month (Control+Up/Down to move years)',
+
+ startDay : 0,
+
+ showToday : true,
+
+
+
+
+
+
+
+
+ focusOnSelect: true,
+
+
+
+ initHour: 12,
+
+
+ initComponent : function(){
+ Ext.DatePicker.superclass.initComponent.call(this);
+
+ this.value = this.value ?
+ this.value.clearTime(true) : new Date().clearTime();
+
+ this.addEvents(
+
+ 'select'
+ );
+
+ if(this.handler){
+ this.on('select', this.handler, this.scope || this);
+ }
+
+ this.initDisabledDays();
+ },
+
+
+ initDisabledDays : function(){
+ if(!this.disabledDatesRE && this.disabledDates){
+ var dd = this.disabledDates,
+ len = dd.length - 1,
+ re = '(?:';
+
+ Ext.each(dd, function(d, i){
+ re += Ext.isDate(d) ? '^' + Ext.escapeRe(d.dateFormat(this.format)) + '$' : dd[i];
+ if(i != len){
+ re += '|';
+ }
+ }, this);
+ this.disabledDatesRE = new RegExp(re + ')');
+ }
+ },
+
+
+ setDisabledDates : function(dd){
+ if(Ext.isArray(dd)){
+ this.disabledDates = dd;
+ this.disabledDatesRE = null;
+ }else{
+ this.disabledDatesRE = dd;
+ }
+ this.initDisabledDays();
+ this.update(this.value, true);
+ },
+
+
+ setDisabledDays : function(dd){
+ this.disabledDays = dd;
+ this.update(this.value, true);
+ },
+
+
+ setMinDate : function(dt){
+ this.minDate = dt;
+ this.update(this.value, true);
+ },
+
+
+ setMaxDate : function(dt){
+ this.maxDate = dt;
+ this.update(this.value, true);
+ },
+
+
+ setValue : function(value){
+ this.value = value.clearTime(true);
+ this.update(this.value);
+ },
+
+
+ getValue : function(){
+ return this.value;
+ },
+
+
+ focus : function(){
+ this.update(this.activeDate);
+ },
+
+
+ onEnable: function(initial){
+ Ext.DatePicker.superclass.onEnable.call(this);
+ this.doDisabled(false);
+ this.update(initial ? this.value : this.activeDate);
+ if(Ext.isIE9m){
+ this.el.repaint();
+ }
+
+ },
+
+
+ onDisable : function(){
+ Ext.DatePicker.superclass.onDisable.call(this);
+ this.doDisabled(true);
+ if(Ext.isIE9m && !Ext.isIE8){
+
+ Ext.each([].concat(this.textNodes, this.el.query('th span')), function(el){
+ Ext.fly(el).repaint();
+ });
+ }
+ },
+
+
+ doDisabled : function(disabled){
+ this.keyNav.setDisabled(disabled);
+ this.prevRepeater.setDisabled(disabled);
+ this.nextRepeater.setDisabled(disabled);
+ if(this.showToday){
+ this.todayKeyListener.setDisabled(disabled);
+ this.todayBtn.setDisabled(disabled);
+ }
+ },
+
+
+ onRender : function(container, position){
+ var m = [
+ '<table cellspacing="0">',
+ '<tr><td class="x-date-left"><a href="#" title="', this.prevText ,'">&#160;</a></td><td class="x-date-middle" align="center"></td><td class="x-date-right"><a href="#" title="', this.nextText ,'">&#160;</a></td></tr>',
+ '<tr><td colspan="3"><table class="x-date-inner" cellspacing="0"><thead><tr>'],
+ dn = this.dayNames,
+ i;
+ for(i = 0; i < 7; i++){
+ var d = this.startDay+i;
+ if(d > 6){
+ d = d-7;
+ }
+ m.push('<th><span>', dn[d].substr(0,1), '</span></th>');
+ }
+ m[m.length] = '</tr></thead><tbody><tr>';
+ for(i = 0; i < 42; i++) {
+ if(i % 7 === 0 && i !== 0){
+ m[m.length] = '</tr><tr>';
+ }
+ m[m.length] = '<td><a href="#" hidefocus="on" class="x-date-date" tabIndex="1"><em><span></span></em></a></td>';
+ }
+ m.push('</tr></tbody></table></td></tr>',
+ this.showToday ? '<tr><td colspan="3" class="x-date-bottom" align="center"></td></tr>' : '',
+ '</table><div class="x-date-mp"></div>');
+
+ var el = document.createElement('div');
+ el.className = 'x-date-picker';
+ el.innerHTML = m.join('');
+
+ container.dom.insertBefore(el, position);
+
+ this.el = Ext.get(el);
+ this.eventEl = Ext.get(el.firstChild);
+
+ this.prevRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-left a'), {
+ handler: this.showPrevMonth,
+ scope: this,
+ preventDefault:true,
+ stopDefault:true
+ });
+
+ this.nextRepeater = new Ext.util.ClickRepeater(this.el.child('td.x-date-right a'), {
+ handler: this.showNextMonth,
+ scope: this,
+ preventDefault:true,
+ stopDefault:true
+ });
+
+ this.monthPicker = this.el.down('div.x-date-mp');
+ this.monthPicker.enableDisplayMode('block');
+
+ this.keyNav = new Ext.KeyNav(this.eventEl, {
+ 'left' : function(e){
+ if(e.ctrlKey){
+ this.showPrevMonth();
+ }else{
+ this.update(this.activeDate.add('d', -1));
+ }
+ },
+
+ 'right' : function(e){
+ if(e.ctrlKey){
+ this.showNextMonth();
+ }else{
+ this.update(this.activeDate.add('d', 1));
+ }
+ },
+
+ 'up' : function(e){
+ if(e.ctrlKey){
+ this.showNextYear();
+ }else{
+ this.update(this.activeDate.add('d', -7));
+ }
+ },
+
+ 'down' : function(e){
+ if(e.ctrlKey){
+ this.showPrevYear();
+ }else{
+ this.update(this.activeDate.add('d', 7));
+ }
+ },
+
+ 'pageUp' : function(e){
+ this.showNextMonth();
+ },
+
+ 'pageDown' : function(e){
+ this.showPrevMonth();
+ },
+
+ 'enter' : function(e){
+ e.stopPropagation();
+ return true;
+ },
+
+ scope : this
+ });
+
+ this.el.unselectable();
+
+ this.cells = this.el.select('table.x-date-inner tbody td');
+ this.textNodes = this.el.query('table.x-date-inner tbody span');
+
+ this.mbtn = new Ext.Button({
+ text: '&#160;',
+ tooltip: this.monthYearText,
+ renderTo: this.el.child('td.x-date-middle', true)
+ });
+ this.mbtn.el.child('em').addClass('x-btn-arrow');
+
+ if(this.showToday){
+ this.todayKeyListener = this.eventEl.addKeyListener(Ext.EventObject.SPACE, this.selectToday, this);
+ var today = (new Date()).dateFormat(this.format);
+ this.todayBtn = new Ext.Button({
+ renderTo: this.el.child('td.x-date-bottom', true),
+ text: String.format(this.todayText, today),
+ tooltip: String.format(this.todayTip, today),
+ handler: this.selectToday,
+ scope: this
+ });
+ }
+ this.mon(this.eventEl, 'mousewheel', this.handleMouseWheel, this);
+ this.mon(this.eventEl, 'click', this.handleDateClick, this, {delegate: 'a.x-date-date'});
+ this.mon(this.mbtn, 'click', this.showMonthPicker, this);
+ this.onEnable(true);
+ },
+
+
+ createMonthPicker : function(){
+ if(!this.monthPicker.dom.firstChild){
+ var buf = ['<table border="0" cellspacing="0">'];
+ for(var i = 0; i < 6; i++){
+ buf.push(
+ '<tr><td class="x-date-mp-month"><a href="#">', Date.getShortMonthName(i), '</a></td>',
+ '<td class="x-date-mp-month x-date-mp-sep"><a href="#">', Date.getShortMonthName(i + 6), '</a></td>',
+ i === 0 ?
+ '<td class="x-date-mp-ybtn" align="center"><a class="x-date-mp-prev"></a></td><td class="x-date-mp-ybtn" align="center"><a class="x-date-mp-next"></a></td></tr>' :
+ '<td class="x-date-mp-year"><a href="#"></a></td><td class="x-date-mp-year"><a href="#"></a></td></tr>'
+ );
+ }
+ buf.push(
+ '<tr class="x-date-mp-btns"><td colspan="4"><button type="button" class="x-date-mp-ok">',
+ this.okText,
+ '</button><button type="button" class="x-date-mp-cancel">',
+ this.cancelText,
+ '</button></td></tr>',
+ '</table>'
+ );
+ this.monthPicker.update(buf.join(''));
+
+ this.mon(this.monthPicker, 'click', this.onMonthClick, this);
+ this.mon(this.monthPicker, 'dblclick', this.onMonthDblClick, this);
+
+ this.mpMonths = this.monthPicker.select('td.x-date-mp-month');
+ this.mpYears = this.monthPicker.select('td.x-date-mp-year');
+
+ this.mpMonths.each(function(m, a, i){
+ i += 1;
+ if((i%2) === 0){
+ m.dom.xmonth = 5 + Math.round(i * 0.5);
+ }else{
+ m.dom.xmonth = Math.round((i-1) * 0.5);
+ }
+ });
+ }
+ },
+
+
+ showMonthPicker : function(){
+ if(!this.disabled){
+ this.createMonthPicker();
+ var size = this.el.getSize();
+ this.monthPicker.setSize(size);
+ this.monthPicker.child('table').setSize(size);
+
+ this.mpSelMonth = (this.activeDate || this.value).getMonth();
+ this.updateMPMonth(this.mpSelMonth);
+ this.mpSelYear = (this.activeDate || this.value).getFullYear();
+ this.updateMPYear(this.mpSelYear);
+
+ this.monthPicker.slideIn('t', {duration:0.2});
+ }
+ },
+
+
+ updateMPYear : function(y){
+ this.mpyear = y;
+ var ys = this.mpYears.elements;
+ for(var i = 1; i <= 10; i++){
+ var td = ys[i-1], y2;
+ if((i%2) === 0){
+ y2 = y + Math.round(i * 0.5);
+ td.firstChild.innerHTML = y2;
+ td.xyear = y2;
+ }else{
+ y2 = y - (5-Math.round(i * 0.5));
+ td.firstChild.innerHTML = y2;
+ td.xyear = y2;
+ }
+ this.mpYears.item(i-1)[y2 == this.mpSelYear ? 'addClass' : 'removeClass']('x-date-mp-sel');
+ }
+ },
+
+
+ updateMPMonth : function(sm){
+ this.mpMonths.each(function(m, a, i){
+ m[m.dom.xmonth == sm ? 'addClass' : 'removeClass']('x-date-mp-sel');
+ });
+ },
+
+
+ selectMPMonth : function(m){
+
+ },
+
+
+ onMonthClick : function(e, t){
+ e.stopEvent();
+ var el = new Ext.Element(t), pn;
+ if(el.is('button.x-date-mp-cancel')){
+ this.hideMonthPicker();
+ }
+ else if(el.is('button.x-date-mp-ok')){
+ var d = new Date(this.mpSelYear, this.mpSelMonth, (this.activeDate || this.value).getDate());
+ if(d.getMonth() != this.mpSelMonth){
+
+ d = new Date(this.mpSelYear, this.mpSelMonth, 1).getLastDateOfMonth();
+ }
+ this.update(d);
+ this.hideMonthPicker();
+ }
+ else if((pn = el.up('td.x-date-mp-month', 2))){
+ this.mpMonths.removeClass('x-date-mp-sel');
+ pn.addClass('x-date-mp-sel');
+ this.mpSelMonth = pn.dom.xmonth;
+ }
+ else if((pn = el.up('td.x-date-mp-year', 2))){
+ this.mpYears.removeClass('x-date-mp-sel');
+ pn.addClass('x-date-mp-sel');
+ this.mpSelYear = pn.dom.xyear;
+ }
+ else if(el.is('a.x-date-mp-prev')){
+ this.updateMPYear(this.mpyear-10);
+ }
+ else if(el.is('a.x-date-mp-next')){
+ this.updateMPYear(this.mpyear+10);
+ }
+ },
+
+
+ onMonthDblClick : function(e, t){
+ e.stopEvent();
+ var el = new Ext.Element(t), pn;
+ if((pn = el.up('td.x-date-mp-month', 2))){
+ this.update(new Date(this.mpSelYear, pn.dom.xmonth, (this.activeDate || this.value).getDate()));
+ this.hideMonthPicker();
+ }
+ else if((pn = el.up('td.x-date-mp-year', 2))){
+ this.update(new Date(pn.dom.xyear, this.mpSelMonth, (this.activeDate || this.value).getDate()));
+ this.hideMonthPicker();
+ }
+ },
+
+
+ hideMonthPicker : function(disableAnim){
+ if(this.monthPicker){
+ if(disableAnim === true){
+ this.monthPicker.hide();
+ }else{
+ this.monthPicker.slideOut('t', {duration:0.2});
+ }
+ }
+ },
+
+
+ showPrevMonth : function(e){
+ this.update(this.activeDate.add('mo', -1));
+ },
+
+
+ showNextMonth : function(e){
+ this.update(this.activeDate.add('mo', 1));
+ },
+
+
+ showPrevYear : function(){
+ this.update(this.activeDate.add('y', -1));
+ },
+
+
+ showNextYear : function(){
+ this.update(this.activeDate.add('y', 1));
+ },
+
+
+ handleMouseWheel : function(e){
+ e.stopEvent();
+ if(!this.disabled){
+ var delta = e.getWheelDelta();
+ if(delta > 0){
+ this.showPrevMonth();
+ } else if(delta < 0){
+ this.showNextMonth();
+ }
+ }
+ },
+
+
+ handleDateClick : function(e, t){
+ e.stopEvent();
+ if(!this.disabled && t.dateValue && !Ext.fly(t.parentNode).hasClass('x-date-disabled')){
+ this.cancelFocus = this.focusOnSelect === false;
+ this.setValue(new Date(t.dateValue));
+ delete this.cancelFocus;
+ this.fireEvent('select', this, this.value);
+ }
+ },
+
+
+ selectToday : function(){
+ if(this.todayBtn && !this.todayBtn.disabled){
+ this.setValue(new Date().clearTime());
+ this.fireEvent('select', this, this.value);
+ }
+ },
+
+
+ update : function(date, forceRefresh){
+ if(this.rendered){
+ var vd = this.activeDate, vis = this.isVisible();
+ this.activeDate = date;
+ if(!forceRefresh && vd && this.el){
+ var t = date.getTime();
+ if(vd.getMonth() == date.getMonth() && vd.getFullYear() == date.getFullYear()){
+ this.cells.removeClass('x-date-selected');
+ this.cells.each(function(c){
+ if(c.dom.firstChild.dateValue == t){
+ c.addClass('x-date-selected');
+ if(vis && !this.cancelFocus){
+ Ext.fly(c.dom.firstChild).focus(50);
+ }
+ return false;
+ }
+ }, this);
+ return;
+ }
+ }
+ var days = date.getDaysInMonth(),
+ firstOfMonth = date.getFirstDateOfMonth(),
+ startingPos = firstOfMonth.getDay()-this.startDay;
+
+ if(startingPos < 0){
+ startingPos += 7;
+ }
+ days += startingPos;
+
+ var pm = date.add('mo', -1),
+ prevStart = pm.getDaysInMonth()-startingPos,
+ cells = this.cells.elements,
+ textEls = this.textNodes,
+
+ d = (new Date(pm.getFullYear(), pm.getMonth(), prevStart, this.initHour)),
+ today = new Date().clearTime().getTime(),
+ sel = date.clearTime(true).getTime(),
+ min = this.minDate ? this.minDate.clearTime(true) : Number.NEGATIVE_INFINITY,
+ max = this.maxDate ? this.maxDate.clearTime(true) : Number.POSITIVE_INFINITY,
+ ddMatch = this.disabledDatesRE,
+ ddText = this.disabledDatesText,
+ ddays = this.disabledDays ? this.disabledDays.join('') : false,
+ ddaysText = this.disabledDaysText,
+ format = this.format;
+
+ if(this.showToday){
+ var td = new Date().clearTime(),
+ disable = (td < min || td > max ||
+ (ddMatch && format && ddMatch.test(td.dateFormat(format))) ||
+ (ddays && ddays.indexOf(td.getDay()) != -1));
+
+ if(!this.disabled){
+ this.todayBtn.setDisabled(disable);
+ this.todayKeyListener[disable ? 'disable' : 'enable']();
+ }
+ }
+
+ var setCellClass = function(cal, cell){
+ cell.title = '';
+ var t = d.clearTime(true).getTime();
+ cell.firstChild.dateValue = t;
+ if(t == today){
+ cell.className += ' x-date-today';
+ cell.title = cal.todayText;
+ }
+ if(t == sel){
+ cell.className += ' x-date-selected';
+ if(vis){
+ Ext.fly(cell.firstChild).focus(50);
+ }
+ }
+
+ if(t < min) {
+ cell.className = ' x-date-disabled';
+ cell.title = cal.minText;
+ return;
+ }
+ if(t > max) {
+ cell.className = ' x-date-disabled';
+ cell.title = cal.maxText;
+ return;
+ }
+ if(ddays){
+ if(ddays.indexOf(d.getDay()) != -1){
+ cell.title = ddaysText;
+ cell.className = ' x-date-disabled';
+ }
+ }
+ if(ddMatch && format){
+ var fvalue = d.dateFormat(format);
+ if(ddMatch.test(fvalue)){
+ cell.title = ddText.replace('%0', fvalue);
+ cell.className = ' x-date-disabled';
+ }
+ }
+ };
+
+ var i = 0;
+ for(; i < startingPos; i++) {
+ textEls[i].innerHTML = (++prevStart);
+ d.setDate(d.getDate()+1);
+ cells[i].className = 'x-date-prevday';
+ setCellClass(this, cells[i]);
+ }
+ for(; i < days; i++){
+ var intDay = i - startingPos + 1;
+ textEls[i].innerHTML = (intDay);
+ d.setDate(d.getDate()+1);
+ cells[i].className = 'x-date-active';
+ setCellClass(this, cells[i]);
+ }
+ var extraDays = 0;
+ for(; i < 42; i++) {
+ textEls[i].innerHTML = (++extraDays);
+ d.setDate(d.getDate()+1);
+ cells[i].className = 'x-date-nextday';
+ setCellClass(this, cells[i]);
+ }
+
+ this.mbtn.setText(this.monthNames[date.getMonth()] + ' ' + date.getFullYear());
+
+ if(!this.internalRender){
+ var main = this.el.dom.firstChild,
+ w = main.offsetWidth;
+ this.el.setWidth(w + this.el.getBorderWidth('lr'));
+ Ext.fly(main).setWidth(w);
+ this.internalRender = true;
+
+
+
+ if(Ext.isOpera && !this.secondPass){
+ main.rows[0].cells[1].style.width = (w - (main.rows[0].cells[0].offsetWidth+main.rows[0].cells[2].offsetWidth)) + 'px';
+ this.secondPass = true;
+ this.update.defer(10, this, [date]);
+ }
+ }
+ }
+ },
+
+
+ beforeDestroy : function() {
+ if(this.rendered){
+ Ext.destroy(
+ this.keyNav,
+ this.monthPicker,
+ this.eventEl,
+ this.mbtn,
+ this.nextRepeater,
+ this.prevRepeater,
+ this.cells.el,
+ this.todayBtn
+ );
+ delete this.textNodes;
+ delete this.cells.elements;
+ }
+ }
+
+
+});
+
+Ext.reg('datepicker', Ext.DatePicker);
+
+Ext.LoadMask = function(el, config){
+ this.el = Ext.get(el);
+ Ext.apply(this, config);
+ if(this.store){
+ this.store.on({
+ scope: this,
+ beforeload: this.onBeforeLoad,
+ load: this.onLoad,
+ exception: this.onLoad
+ });
+ this.removeMask = Ext.value(this.removeMask, false);
+ }else{
+ var um = this.el.getUpdater();
+ um.showLoadIndicator = false;
+ um.on({
+ scope: this,
+ beforeupdate: this.onBeforeLoad,
+ update: this.onLoad,
+ failure: this.onLoad
+ });
+ this.removeMask = Ext.value(this.removeMask, true);
+ }
+};
+
+Ext.LoadMask.prototype = {
+
+
+
+ msg : 'Loading...',
+
+ msgCls : 'x-mask-loading',
+
+
+ disabled: false,
+
+
+ disable : function(){
+ this.disabled = true;
+ },
+
+
+ enable : function(){
+ this.disabled = false;
+ },
+
+
+ onLoad : function(){
+ this.el.unmask(this.removeMask);
+ },
+
+
+ onBeforeLoad : function(){
+ if(!this.disabled){
+ this.el.mask(this.msg, this.msgCls);
+ }
+ },
+
+
+ show: function(){
+ this.onBeforeLoad();
+ },
+
+
+ hide: function(){
+ this.onLoad();
+ },
+
+
+ destroy : function(){
+ if(this.store){
+ this.store.un('beforeload', this.onBeforeLoad, this);
+ this.store.un('load', this.onLoad, this);
+ this.store.un('exception', this.onLoad, this);
+ }else{
+ var um = this.el.getUpdater();
+ um.un('beforeupdate', this.onBeforeLoad, this);
+ um.un('update', this.onLoad, this);
+ um.un('failure', this.onLoad, this);
+ }
+ }
+};
+Ext.slider.Thumb = Ext.extend(Object, {
+
+
+ dragging: false,
+
+
+ constructor: function(config) {
+
+ Ext.apply(this, config || {}, {
+ cls: 'x-slider-thumb',
+
+
+ constrain: false
+ });
+
+ Ext.slider.Thumb.superclass.constructor.call(this, config);
+
+ if (this.slider.vertical) {
+ Ext.apply(this, Ext.slider.Thumb.Vertical);
+ }
+ },
+
+
+ render: function() {
+ this.el = this.slider.innerEl.insertFirst({cls: this.cls});
+
+ this.initEvents();
+ },
+
+
+ enable: function() {
+ this.disabled = false;
+ this.el.removeClass(this.slider.disabledClass);
+ },
+
+
+ disable: function() {
+ this.disabled = true;
+ this.el.addClass(this.slider.disabledClass);
+ },
+
+
+ initEvents: function() {
+ var el = this.el;
+
+ el.addClassOnOver('x-slider-thumb-over');
+
+ this.tracker = new Ext.dd.DragTracker({
+ onBeforeStart: this.onBeforeDragStart.createDelegate(this),
+ onStart : this.onDragStart.createDelegate(this),
+ onDrag : this.onDrag.createDelegate(this),
+ onEnd : this.onDragEnd.createDelegate(this),
+ tolerance : 3,
+ autoStart : 300
+ });
+
+ this.tracker.initEl(el);
+ },
+
+
+ onBeforeDragStart : function(e) {
+ if (this.disabled) {
+ return false;
+ } else {
+ this.slider.promoteThumb(this);
+ return true;
+ }
+ },
+
+
+ onDragStart: function(e){
+ this.el.addClass('x-slider-thumb-drag');
+ this.dragging = true;
+ this.dragStartValue = this.value;
+
+ this.slider.fireEvent('dragstart', this.slider, e, this);
+ },
+
+
+ onDrag: function(e) {
+ var slider = this.slider,
+ index = this.index,
+ newValue = this.getNewValue();
+
+ if (this.constrain) {
+ var above = slider.thumbs[index + 1],
+ below = slider.thumbs[index - 1];
+
+ if (below != undefined && newValue <= below.value) newValue = below.value;
+ if (above != undefined && newValue >= above.value) newValue = above.value;
+ }
+
+ slider.setValue(index, newValue, false);
+ slider.fireEvent('drag', slider, e, this);
+ },
+
+ getNewValue: function() {
+ var slider = this.slider,
+ pos = slider.innerEl.translatePoints(this.tracker.getXY());
+
+ return Ext.util.Format.round(slider.reverseValue(pos.left), slider.decimalPrecision);
+ },
+
+
+ onDragEnd: function(e) {
+ var slider = this.slider,
+ value = this.value;
+
+ this.el.removeClass('x-slider-thumb-drag');
+
+ this.dragging = false;
+ slider.fireEvent('dragend', slider, e);
+
+ if (this.dragStartValue != value) {
+ slider.fireEvent('changecomplete', slider, value, this);
+ }
+ },
+
+
+ destroy: function(){
+ Ext.destroyMembers(this, 'tracker', 'el');
+ }
+});
+
+
+Ext.slider.MultiSlider = Ext.extend(Ext.BoxComponent, {
+
+
+ vertical: false,
+
+ minValue: 0,
+
+ maxValue: 100,
+
+ decimalPrecision: 0,
+
+ keyIncrement: 1,
+
+ increment: 0,
+
+
+ clickRange: [5,15],
+
+
+ clickToChange : true,
+
+ animate: true,
+
+ constrainThumbs: true,
+
+
+ topThumbZIndex: 10000,
+
+
+ initComponent : function(){
+ if(!Ext.isDefined(this.value)){
+ this.value = this.minValue;
+ }
+
+
+ this.thumbs = [];
+
+ Ext.slider.MultiSlider.superclass.initComponent.call(this);
+
+ this.keyIncrement = Math.max(this.increment, this.keyIncrement);
+ this.addEvents(
+
+ 'beforechange',
+
+
+ 'change',
+
+
+ 'changecomplete',
+
+
+ 'dragstart',
+
+
+ 'drag',
+
+
+ 'dragend'
+ );
+
+
+ if (this.values == undefined || Ext.isEmpty(this.values)) this.values = [0];
+
+ var values = this.values;
+
+ for (var i=0; i < values.length; i++) {
+ this.addThumb(values[i]);
+ }
+
+ if(this.vertical){
+ Ext.apply(this, Ext.slider.Vertical);
+ }
+ },
+
+
+ addThumb: function(value) {
+ var thumb = new Ext.slider.Thumb({
+ value : value,
+ slider : this,
+ index : this.thumbs.length,
+ constrain: this.constrainThumbs
+ });
+ this.thumbs.push(thumb);
+
+
+ if (this.rendered) thumb.render();
+ },
+
+
+ promoteThumb: function(topThumb) {
+ var thumbs = this.thumbs,
+ zIndex, thumb;
+
+ for (var i = 0, j = thumbs.length; i < j; i++) {
+ thumb = thumbs[i];
+
+ if (thumb == topThumb) {
+ zIndex = this.topThumbZIndex;
+ } else {
+ zIndex = '';
+ }
+
+ thumb.el.setStyle('zIndex', zIndex);
+ }
+ },
+
+
+ onRender : function() {
+ this.autoEl = {
+ cls: 'x-slider ' + (this.vertical ? 'x-slider-vert' : 'x-slider-horz'),
+ cn : {
+ cls: 'x-slider-end',
+ cn : {
+ cls:'x-slider-inner',
+ cn : [{tag:'a', cls:'x-slider-focus', href:"#", tabIndex: '-1', hidefocus:'on'}]
+ }
+ }
+ };
+
+ Ext.slider.MultiSlider.superclass.onRender.apply(this, arguments);
+
+ this.endEl = this.el.first();
+ this.innerEl = this.endEl.first();
+ this.focusEl = this.innerEl.child('.x-slider-focus');
+
+
+ for (var i=0; i < this.thumbs.length; i++) {
+ this.thumbs[i].render();
+ }
+
+
+ var thumb = this.innerEl.child('.x-slider-thumb');
+ this.halfThumb = (this.vertical ? thumb.getHeight() : thumb.getWidth()) / 2;
+
+ this.initEvents();
+ },
+
+
+ initEvents : function(){
+ this.mon(this.el, {
+ scope : this,
+ mousedown: this.onMouseDown,
+ keydown : this.onKeyDown
+ });
+
+ this.focusEl.swallowEvent("click", true);
+ },
+
+
+ onMouseDown : function(e){
+ if(this.disabled){
+ return;
+ }
+
+
+ var thumbClicked = false;
+ for (var i=0; i < this.thumbs.length; i++) {
+ thumbClicked = thumbClicked || e.target == this.thumbs[i].el.dom;
+ }
+
+ if (this.clickToChange && !thumbClicked) {
+ var local = this.innerEl.translatePoints(e.getXY());
+ this.onClickChange(local);
+ }
+ this.focus();
+ },
+
+
+ onClickChange : function(local) {
+ if (local.top > this.clickRange[0] && local.top < this.clickRange[1]) {
+
+ var thumb = this.getNearest(local, 'left'),
+ index = thumb.index;
+
+ this.setValue(index, Ext.util.Format.round(this.reverseValue(local.left), this.decimalPrecision), undefined, true);
+ }
+ },
+
+
+ getNearest: function(local, prop) {
+ var localValue = prop == 'top' ? this.innerEl.getHeight() - local[prop] : local[prop],
+ clickValue = this.reverseValue(localValue),
+ nearestDistance = (this.maxValue - this.minValue) + 5,
+ index = 0,
+ nearest = null;
+
+ for (var i=0; i < this.thumbs.length; i++) {
+ var thumb = this.thumbs[i],
+ value = thumb.value,
+ dist = Math.abs(value - clickValue);
+
+ if (Math.abs(dist <= nearestDistance)) {
+ nearest = thumb;
+ index = i;
+ nearestDistance = dist;
+ }
+ }
+ return nearest;
+ },
+
+
+ onKeyDown : function(e){
+
+ if(this.disabled || this.thumbs.length !== 1){
+ e.preventDefault();
+ return;
+ }
+ var k = e.getKey(),
+ val;
+ switch(k){
+ case e.UP:
+ case e.RIGHT:
+ e.stopEvent();
+ val = e.ctrlKey ? this.maxValue : this.getValue(0) + this.keyIncrement;
+ this.setValue(0, val, undefined, true);
+ break;
+ case e.DOWN:
+ case e.LEFT:
+ e.stopEvent();
+ val = e.ctrlKey ? this.minValue : this.getValue(0) - this.keyIncrement;
+ this.setValue(0, val, undefined, true);
+ break;
+ default:
+ e.preventDefault();
+ }
+ },
+
+
+ doSnap : function(value){
+ if (!(this.increment && value)) {
+ return value;
+ }
+ var newValue = value,
+ inc = this.increment,
+ m = value % inc;
+ if (m != 0) {
+ newValue -= m;
+ if (m * 2 >= inc) {
+ newValue += inc;
+ } else if (m * 2 < -inc) {
+ newValue -= inc;
+ }
+ }
+ return newValue.constrain(this.minValue, this.maxValue);
+ },
+
+
+ afterRender : function(){
+ Ext.slider.MultiSlider.superclass.afterRender.apply(this, arguments);
+
+ for (var i=0; i < this.thumbs.length; i++) {
+ var thumb = this.thumbs[i];
+
+ if (thumb.value !== undefined) {
+ var v = this.normalizeValue(thumb.value);
+
+ if (v !== thumb.value) {
+
+ this.setValue(i, v, false);
+ } else {
+ this.moveThumb(i, this.translateValue(v), false);
+ }
+ }
+ };
+ },
+
+
+ getRatio : function(){
+ var w = this.innerEl.getWidth(),
+ v = this.maxValue - this.minValue;
+ return v == 0 ? w : (w/v);
+ },
+
+
+ normalizeValue : function(v){
+ v = this.doSnap(v);
+ v = Ext.util.Format.round(v, this.decimalPrecision);
+ v = v.constrain(this.minValue, this.maxValue);
+ return v;
+ },
+
+
+ setMinValue : function(val){
+ this.minValue = val;
+ var i = 0,
+ thumbs = this.thumbs,
+ len = thumbs.length,
+ t;
+
+ for(; i < len; ++i){
+ t = thumbs[i];
+ t.value = t.value < val ? val : t.value;
+ }
+ this.syncThumb();
+ },
+
+
+ setMaxValue : function(val){
+ this.maxValue = val;
+ var i = 0,
+ thumbs = this.thumbs,
+ len = thumbs.length,
+ t;
+
+ for(; i < len; ++i){
+ t = thumbs[i];
+ t.value = t.value > val ? val : t.value;
+ }
+ this.syncThumb();
+ },
+
+
+ setValue : function(index, v, animate, changeComplete) {
+ var thumb = this.thumbs[index],
+ el = thumb.el;
+
+ v = this.normalizeValue(v);
+
+ if (v !== thumb.value && this.fireEvent('beforechange', this, v, thumb.value, thumb) !== false) {
+ thumb.value = v;
+ if(this.rendered){
+ this.moveThumb(index, this.translateValue(v), animate !== false);
+ this.fireEvent('change', this, v, thumb);
+ if(changeComplete){
+ this.fireEvent('changecomplete', this, v, thumb);
+ }
+ }
+ }
+ },
+
+
+ translateValue : function(v) {
+ var ratio = this.getRatio();
+ return (v * ratio) - (this.minValue * ratio) - this.halfThumb;
+ },
+
+
+ reverseValue : function(pos){
+ var ratio = this.getRatio();
+ return (pos + (this.minValue * ratio)) / ratio;
+ },
+
+
+ moveThumb: function(index, v, animate){
+ var thumb = this.thumbs[index].el;
+
+ if(!animate || this.animate === false){
+ thumb.setLeft(v);
+ }else{
+ thumb.shift({left: v, stopFx: true, duration:.35});
+ }
+ },
+
+
+ focus : function(){
+ this.focusEl.focus(10);
+ },
+
+
+ onResize : function(w, h){
+ var thumbs = this.thumbs,
+ len = thumbs.length,
+ i = 0;
+
+
+ for(; i < len; ++i){
+ thumbs[i].el.stopFx();
+ }
+
+ if(Ext.isNumber(w)){
+ this.innerEl.setWidth(w - (this.el.getPadding('l') + this.endEl.getPadding('r')));
+ }
+ this.syncThumb();
+ Ext.slider.MultiSlider.superclass.onResize.apply(this, arguments);
+ },
+
+
+ onDisable: function(){
+ Ext.slider.MultiSlider.superclass.onDisable.call(this);
+
+ for (var i=0; i < this.thumbs.length; i++) {
+ var thumb = this.thumbs[i],
+ el = thumb.el;
+
+ thumb.disable();
+
+ if(Ext.isIE){
+
+
+ var xy = el.getXY();
+ el.hide();
+
+ this.innerEl.addClass(this.disabledClass).dom.disabled = true;
+
+ if (!this.thumbHolder) {
+ this.thumbHolder = this.endEl.createChild({cls: 'x-slider-thumb ' + this.disabledClass});
+ }
+
+ this.thumbHolder.show().setXY(xy);
+ }
+ }
+ },
+
+
+ onEnable: function(){
+ Ext.slider.MultiSlider.superclass.onEnable.call(this);
+
+ for (var i=0; i < this.thumbs.length; i++) {
+ var thumb = this.thumbs[i],
+ el = thumb.el;
+
+ thumb.enable();
+
+ if (Ext.isIE) {
+ this.innerEl.removeClass(this.disabledClass).dom.disabled = false;
+
+ if (this.thumbHolder) this.thumbHolder.hide();
+
+ el.show();
+ this.syncThumb();
+ }
+ }
+ },
+
+
+ syncThumb : function() {
+ if (this.rendered) {
+ for (var i=0; i < this.thumbs.length; i++) {
+ this.moveThumb(i, this.translateValue(this.thumbs[i].value));
+ }
+ }
+ },
+
+
+ getValue : function(index) {
+ return this.thumbs[index].value;
+ },
+
+
+ getValues: function() {
+ var values = [];
+
+ for (var i=0; i < this.thumbs.length; i++) {
+ values.push(this.thumbs[i].value);
+ }
+
+ return values;
+ },
+
+
+ beforeDestroy : function(){
+ var thumbs = this.thumbs;
+ for(var i = 0, len = thumbs.length; i < len; ++i){
+ thumbs[i].destroy();
+ thumbs[i] = null;
+ }
+ Ext.destroyMembers(this, 'endEl', 'innerEl', 'focusEl', 'thumbHolder');
+ Ext.slider.MultiSlider.superclass.beforeDestroy.call(this);
+ }
+});
+
+Ext.reg('multislider', Ext.slider.MultiSlider);
+
+
+Ext.slider.SingleSlider = Ext.extend(Ext.slider.MultiSlider, {
+ constructor: function(config) {
+ config = config || {};
+
+ Ext.applyIf(config, {
+ values: [config.value || 0]
+ });
+
+ Ext.slider.SingleSlider.superclass.constructor.call(this, config);
+ },
+
+
+ getValue: function() {
+
+ return Ext.slider.SingleSlider.superclass.getValue.call(this, 0);
+ },
+
+
+ setValue: function(value, animate) {
+ var args = Ext.toArray(arguments),
+ len = args.length;
+
+
+
+
+ if (len == 1 || (len <= 3 && typeof arguments[1] != 'number')) {
+ args.unshift(0);
+ }
+
+ return Ext.slider.SingleSlider.superclass.setValue.apply(this, args);
+ },
+
+
+ syncThumb : function() {
+ return Ext.slider.SingleSlider.superclass.syncThumb.apply(this, [0].concat(arguments));
+ },
+
+
+ getNearest : function(){
+
+ return this.thumbs[0];
+ }
+});
+
+
+Ext.Slider = Ext.slider.SingleSlider;
+
+Ext.reg('slider', Ext.slider.SingleSlider);
+
+
+Ext.slider.Vertical = {
+ onResize : function(w, h){
+ this.innerEl.setHeight(h - (this.el.getPadding('t') + this.endEl.getPadding('b')));
+ this.syncThumb();
+ },
+
+ getRatio : function(){
+ var h = this.innerEl.getHeight(),
+ v = this.maxValue - this.minValue;
+ return h/v;
+ },
+
+ moveThumb: function(index, v, animate) {
+ var thumb = this.thumbs[index],
+ el = thumb.el;
+
+ if (!animate || this.animate === false) {
+ el.setBottom(v);
+ } else {
+ el.shift({bottom: v, stopFx: true, duration:.35});
+ }
+ },
+
+ onClickChange : function(local) {
+ if (local.left > this.clickRange[0] && local.left < this.clickRange[1]) {
+ var thumb = this.getNearest(local, 'top'),
+ index = thumb.index,
+ value = this.minValue + this.reverseValue(this.innerEl.getHeight() - local.top);
+
+ this.setValue(index, Ext.util.Format.round(value, this.decimalPrecision), undefined, true);
+ }
+ }
+};
+
+
+Ext.slider.Thumb.Vertical = {
+ getNewValue: function() {
+ var slider = this.slider,
+ innerEl = slider.innerEl,
+ pos = innerEl.translatePoints(this.tracker.getXY()),
+ bottom = innerEl.getHeight() - pos.top;
+
+ return slider.minValue + Ext.util.Format.round(bottom / slider.getRatio(), slider.decimalPrecision);
+ }
+};
+
+Ext.ProgressBar = Ext.extend(Ext.BoxComponent, {
+
+ baseCls : 'x-progress',
+
+
+ animate : false,
+
+
+ waitTimer : null,
+
+
+ initComponent : function(){
+ Ext.ProgressBar.superclass.initComponent.call(this);
+ this.addEvents(
+
+ "update"
+ );
+ },
+
+
+ onRender : function(ct, position){
+ var tpl = new Ext.Template(
+ '<div class="{cls}-wrap">',
+ '<div class="{cls}-inner">',
+ '<div class="{cls}-bar">',
+ '<div class="{cls}-text">',
+ '<div>&#160;</div>',
+ '</div>',
+ '</div>',
+ '<div class="{cls}-text {cls}-text-back">',
+ '<div>&#160;</div>',
+ '</div>',
+ '</div>',
+ '</div>'
+ );
+
+ this.el = position ? tpl.insertBefore(position, {cls: this.baseCls}, true)
+ : tpl.append(ct, {cls: this.baseCls}, true);
+
+ if(this.id){
+ this.el.dom.id = this.id;
+ }
+ var inner = this.el.dom.firstChild;
+ this.progressBar = Ext.get(inner.firstChild);
+
+ if(this.textEl){
+
+ this.textEl = Ext.get(this.textEl);
+ delete this.textTopEl;
+ }else{
+
+ this.textTopEl = Ext.get(this.progressBar.dom.firstChild);
+ var textBackEl = Ext.get(inner.childNodes[1]);
+ this.textTopEl.setStyle("z-index", 99).addClass('x-hidden');
+ this.textEl = new Ext.CompositeElement([this.textTopEl.dom.firstChild, textBackEl.dom.firstChild]);
+ this.textEl.setWidth(inner.offsetWidth);
+ }
+ this.progressBar.setHeight(inner.offsetHeight);
+ },
+
+
+ afterRender : function(){
+ Ext.ProgressBar.superclass.afterRender.call(this);
+ if(this.value){
+ this.updateProgress(this.value, this.text);
+ }else{
+ this.updateText(this.text);
+ }
+ },
+
+
+ updateProgress : function(value, text, animate){
+ this.value = value || 0;
+ if(text){
+ this.updateText(text);
+ }
+ if(this.rendered && !this.isDestroyed){
+ var w = Math.floor(value*this.el.dom.firstChild.offsetWidth);
+ this.progressBar.setWidth(w, animate === true || (animate !== false && this.animate));
+ if(this.textTopEl){
+
+ this.textTopEl.removeClass('x-hidden').setWidth(w);
+ }
+ }
+ this.fireEvent('update', this, value, text);
+ return this;
+ },
+
+
+ wait : function(o){
+ if(!this.waitTimer){
+ var scope = this;
+ o = o || {};
+ this.updateText(o.text);
+ this.waitTimer = Ext.TaskMgr.start({
+ run: function(i){
+ var inc = o.increment || 10;
+ i -= 1;
+ this.updateProgress(((((i+inc)%inc)+1)*(100/inc))*0.01, null, o.animate);
+ },
+ interval: o.interval || 1000,
+ duration: o.duration,
+ onStop: function(){
+ if(o.fn){
+ o.fn.apply(o.scope || this);
+ }
+ this.reset();
+ },
+ scope: scope
+ });
+ }
+ return this;
+ },
+
+
+ isWaiting : function(){
+ return this.waitTimer !== null;
+ },
+
+
+ updateText : function(text){
+ this.text = text || '&#160;';
+ if(this.rendered){
+ this.textEl.update(this.text);
+ }
+ return this;
+ },
+
+
+ syncProgressBar : function(){
+ if(this.value){
+ this.updateProgress(this.value, this.text);
+ }
+ return this;
+ },
+
+
+ setSize : function(w, h){
+ Ext.ProgressBar.superclass.setSize.call(this, w, h);
+ if(this.textTopEl){
+ var inner = this.el.dom.firstChild;
+ this.textEl.setSize(inner.offsetWidth, inner.offsetHeight);
+ }
+ this.syncProgressBar();
+ return this;
+ },
+
+
+ reset : function(hide){
+ this.updateProgress(0);
+ if(this.textTopEl){
+ this.textTopEl.addClass('x-hidden');
+ }
+ this.clearTimer();
+ if(hide === true){
+ this.hide();
+ }
+ return this;
+ },
+
+
+ clearTimer : function(){
+ if(this.waitTimer){
+ this.waitTimer.onStop = null;
+ Ext.TaskMgr.stop(this.waitTimer);
+ this.waitTimer = null;
+ }
+ },
+
+ onDestroy: function(){
+ this.clearTimer();
+ if(this.rendered){
+ if(this.textEl.isComposite){
+ this.textEl.clear();
+ }
+ Ext.destroyMembers(this, 'textEl', 'progressBar', 'textTopEl');
+ }
+ Ext.ProgressBar.superclass.onDestroy.call(this);
+ }
+});
+Ext.reg('progress', Ext.ProgressBar);
+
+(function() {
+
+var Event=Ext.EventManager;
+var Dom=Ext.lib.Dom;
+
+
+Ext.dd.DragDrop = function(id, sGroup, config) {
+ if(id) {
+ this.init(id, sGroup, config);
+ }
+};
+
+Ext.dd.DragDrop.prototype = {
+
+
+
+
+ id: null,
+
+
+ config: null,
+
+
+ dragElId: null,
+
+
+ handleElId: null,
+
+
+ invalidHandleTypes: null,
+
+
+ invalidHandleIds: null,
+
+
+ invalidHandleClasses: null,
+
+
+ startPageX: 0,
+
+
+ startPageY: 0,
+
+
+ groups: null,
+
+
+ locked: false,
+
+
+ lock: function() {
+ this.locked = true;
+ },
+
+
+ moveOnly: false,
+
+
+ unlock: function() {
+ this.locked = false;
+ },
+
+
+ isTarget: true,
+
+
+ padding: null,
+
+
+ _domRef: null,
+
+
+ __ygDragDrop: true,
+
+
+ constrainX: false,
+
+
+ constrainY: false,
+
+
+ minX: 0,
+
+
+ maxX: 0,
+
+
+ minY: 0,
+
+
+ maxY: 0,
+
+
+ maintainOffset: false,
+
+
+ xTicks: null,
+
+
+ yTicks: null,
+
+
+ primaryButtonOnly: true,
+
+
+ available: false,
+
+
+ hasOuterHandles: false,
+
+
+ b4StartDrag: function(x, y) { },
+
+
+ startDrag: function(x, y) { },
+
+
+ b4Drag: function(e) { },
+
+
+ onDrag: function(e) { },
+
+
+ onDragEnter: function(e, id) { },
+
+
+ b4DragOver: function(e) { },
+
+
+ onDragOver: function(e, id) { },
+
+
+ b4DragOut: function(e) { },
+
+
+ onDragOut: function(e, id) { },
+
+
+ b4DragDrop: function(e) { },
+
+
+ onDragDrop: function(e, id) { },
+
+
+ onInvalidDrop: function(e) { },
+
+
+ b4EndDrag: function(e) { },
+
+
+ endDrag: function(e) { },
+
+
+ b4MouseDown: function(e) { },
+
+
+ onMouseDown: function(e) { },
+
+
+ onMouseUp: function(e) { },
+
+
+ onAvailable: function () {
+ },
+
+
+ defaultPadding : {left:0, right:0, top:0, bottom:0},
+
+
+ constrainTo : function(constrainTo, pad, inContent){
+ if(Ext.isNumber(pad)){
+ pad = {left: pad, right:pad, top:pad, bottom:pad};
+ }
+ pad = pad || this.defaultPadding;
+ var b = Ext.get(this.getEl()).getBox(),
+ ce = Ext.get(constrainTo),
+ s = ce.getScroll(),
+ c,
+ cd = ce.dom;
+ if(cd == document.body){
+ c = { x: s.left, y: s.top, width: Ext.lib.Dom.getViewWidth(), height: Ext.lib.Dom.getViewHeight()};
+ }else{
+ var xy = ce.getXY();
+ c = {x : xy[0], y: xy[1], width: cd.clientWidth, height: cd.clientHeight};
+ }
+
+
+ var topSpace = b.y - c.y,
+ leftSpace = b.x - c.x;
+
+ this.resetConstraints();
+ this.setXConstraint(leftSpace - (pad.left||0),
+ c.width - leftSpace - b.width - (pad.right||0),
+ this.xTickSize
+ );
+ this.setYConstraint(topSpace - (pad.top||0),
+ c.height - topSpace - b.height - (pad.bottom||0),
+ this.yTickSize
+ );
+ },
+
+
+ getEl: function() {
+ if (!this._domRef) {
+ this._domRef = Ext.getDom(this.id);
+ }
+
+ return this._domRef;
+ },
+
+
+ getDragEl: function() {
+ return Ext.getDom(this.dragElId);
+ },
+
+
+ init: function(id, sGroup, config) {
+ this.initTarget(id, sGroup, config);
+ Event.on(this.id, "mousedown", this.handleMouseDown, this);
+
+ },
+
+
+ initTarget: function(id, sGroup, config) {
+
+
+ this.config = config || {};
+
+
+ this.DDM = Ext.dd.DDM;
+
+ this.groups = {};
+
+
+
+ if (typeof id !== "string") {
+ id = Ext.id(id);
+ }
+
+
+ this.id = id;
+
+
+ this.addToGroup((sGroup) ? sGroup : "default");
+
+
+
+ this.handleElId = id;
+
+
+ this.setDragElId(id);
+
+
+ this.invalidHandleTypes = { A: "A" };
+ this.invalidHandleIds = {};
+ this.invalidHandleClasses = [];
+
+ this.applyConfig();
+
+ this.handleOnAvailable();
+ },
+
+
+ applyConfig: function() {
+
+
+
+ this.padding = this.config.padding || [0, 0, 0, 0];
+ this.isTarget = (this.config.isTarget !== false);
+ this.maintainOffset = (this.config.maintainOffset);
+ this.primaryButtonOnly = (this.config.primaryButtonOnly !== false);
+
+ },
+
+
+ handleOnAvailable: function() {
+ this.available = true;
+ this.resetConstraints();
+ this.onAvailable();
+ },
+
+
+ setPadding: function(iTop, iRight, iBot, iLeft) {
+
+ if (!iRight && 0 !== iRight) {
+ this.padding = [iTop, iTop, iTop, iTop];
+ } else if (!iBot && 0 !== iBot) {
+ this.padding = [iTop, iRight, iTop, iRight];
+ } else {
+ this.padding = [iTop, iRight, iBot, iLeft];
+ }
+ },
+
+
+ setInitPosition: function(diffX, diffY) {
+ var el = this.getEl();
+
+ if (!this.DDM.verifyEl(el)) {
+ return;
+ }
+
+ var dx = diffX || 0;
+ var dy = diffY || 0;
+
+ var p = Dom.getXY( el );
+
+ this.initPageX = p[0] - dx;
+ this.initPageY = p[1] - dy;
+
+ this.lastPageX = p[0];
+ this.lastPageY = p[1];
+
+ this.setStartPosition(p);
+ },
+
+
+ setStartPosition: function(pos) {
+ var p = pos || Dom.getXY( this.getEl() );
+ this.deltaSetXY = null;
+
+ this.startPageX = p[0];
+ this.startPageY = p[1];
+ },
+
+
+ addToGroup: function(sGroup) {
+ this.groups[sGroup] = true;
+ this.DDM.regDragDrop(this, sGroup);
+ },
+
+
+ removeFromGroup: function(sGroup) {
+ if (this.groups[sGroup]) {
+ delete this.groups[sGroup];
+ }
+
+ this.DDM.removeDDFromGroup(this, sGroup);
+ },
+
+
+ setDragElId: function(id) {
+ this.dragElId = id;
+ },
+
+
+ setHandleElId: function(id) {
+ if (typeof id !== "string") {
+ id = Ext.id(id);
+ }
+ this.handleElId = id;
+ this.DDM.regHandle(this.id, id);
+ },
+
+
+ setOuterHandleElId: function(id) {
+ if (typeof id !== "string") {
+ id = Ext.id(id);
+ }
+ Event.on(id, "mousedown",
+ this.handleMouseDown, this);
+ this.setHandleElId(id);
+
+ this.hasOuterHandles = true;
+ },
+
+
+ unreg: function() {
+ Event.un(this.id, "mousedown",
+ this.handleMouseDown);
+ this._domRef = null;
+ this.DDM._remove(this);
+ },
+
+ destroy : function(){
+ this.unreg();
+ },
+
+
+ isLocked: function() {
+ return (this.DDM.isLocked() || this.locked);
+ },
+
+
+ handleMouseDown: function(e, oDD){
+ if (this.primaryButtonOnly && e.button != 0) {
+ return;
+ }
+
+ if (this.isLocked()) {
+ return;
+ }
+
+ this.DDM.refreshCache(this.groups);
+
+ var pt = new Ext.lib.Point(Ext.lib.Event.getPageX(e), Ext.lib.Event.getPageY(e));
+ if (!this.hasOuterHandles && !this.DDM.isOverTarget(pt, this) ) {
+ } else {
+ if (this.clickValidator(e)) {
+
+
+ this.setStartPosition();
+
+ this.b4MouseDown(e);
+ this.onMouseDown(e);
+
+ this.DDM.handleMouseDown(e, this);
+
+ if (this.preventDefault || this.stopPropagation) {
+ if (this.preventDefault) {
+ e.preventDefault();
+ }
+ if (this.stopPropagation) {
+ e.stopPropagation();
+ }
+ } else {
+ this.DDM.stopEvent(e);
+ }
+ } else {
+
+
+ }
+ }
+ },
+
+ clickValidator: function(e) {
+ var target = e.getTarget();
+ return ( this.isValidHandleChild(target) &&
+ (this.id == this.handleElId ||
+ this.DDM.handleWasClicked(target, this.id)) );
+ },
+
+
+ addInvalidHandleType: function(tagName) {
+ var type = tagName.toUpperCase();
+ this.invalidHandleTypes[type] = type;
+ },
+
+
+ addInvalidHandleId: function(id) {
+ if (typeof id !== "string") {
+ id = Ext.id(id);
+ }
+ this.invalidHandleIds[id] = id;
+ },
+
+
+ addInvalidHandleClass: function(cssClass) {
+ this.invalidHandleClasses.push(cssClass);
+ },
+
+
+ removeInvalidHandleType: function(tagName) {
+ var type = tagName.toUpperCase();
+
+ delete this.invalidHandleTypes[type];
+ },
+
+
+ removeInvalidHandleId: function(id) {
+ if (typeof id !== "string") {
+ id = Ext.id(id);
+ }
+ delete this.invalidHandleIds[id];
+ },
+
+
+ removeInvalidHandleClass: function(cssClass) {
+ for (var i=0, len=this.invalidHandleClasses.length; i<len; ++i) {
+ if (this.invalidHandleClasses[i] == cssClass) {
+ delete this.invalidHandleClasses[i];
+ }
+ }
+ },
+
+
+ isValidHandleChild: function(node) {
+
+ var valid = true;
+
+ var nodeName;
+ try {
+ nodeName = node.nodeName.toUpperCase();
+ } catch(e) {
+ nodeName = node.nodeName;
+ }
+ valid = valid && !this.invalidHandleTypes[nodeName];
+ valid = valid && !this.invalidHandleIds[node.id];
+
+ for (var i=0, len=this.invalidHandleClasses.length; valid && i<len; ++i) {
+ valid = !Ext.fly(node).hasClass(this.invalidHandleClasses[i]);
+ }
+
+
+ return valid;
+
+ },
+
+
+ setXTicks: function(iStartX, iTickSize) {
+ this.xTicks = [];
+ this.xTickSize = iTickSize;
+
+ var tickMap = {};
+
+ for (var i = this.initPageX; i >= this.minX; i = i - iTickSize) {
+ if (!tickMap[i]) {
+ this.xTicks[this.xTicks.length] = i;
+ tickMap[i] = true;
+ }
+ }
+
+ for (i = this.initPageX; i <= this.maxX; i = i + iTickSize) {
+ if (!tickMap[i]) {
+ this.xTicks[this.xTicks.length] = i;
+ tickMap[i] = true;
+ }
+ }
+
+ this.xTicks.sort(this.DDM.numericSort) ;
+ },
+
+
+ setYTicks: function(iStartY, iTickSize) {
+ this.yTicks = [];
+ this.yTickSize = iTickSize;
+
+ var tickMap = {};
+
+ for (var i = this.initPageY; i >= this.minY; i = i - iTickSize) {
+ if (!tickMap[i]) {
+ this.yTicks[this.yTicks.length] = i;
+ tickMap[i] = true;
+ }
+ }
+
+ for (i = this.initPageY; i <= this.maxY; i = i + iTickSize) {
+ if (!tickMap[i]) {
+ this.yTicks[this.yTicks.length] = i;
+ tickMap[i] = true;
+ }
+ }
+
+ this.yTicks.sort(this.DDM.numericSort) ;
+ },
+
+
+ setXConstraint: function(iLeft, iRight, iTickSize) {
+ this.leftConstraint = iLeft;
+ this.rightConstraint = iRight;
+
+ this.minX = this.initPageX - iLeft;
+ this.maxX = this.initPageX + iRight;
+ if (iTickSize) { this.setXTicks(this.initPageX, iTickSize); }
+
+ this.constrainX = true;
+ },
+
+
+ clearConstraints: function() {
+ this.constrainX = false;
+ this.constrainY = false;
+ this.clearTicks();
+ },
+
+
+ clearTicks: function() {
+ this.xTicks = null;
+ this.yTicks = null;
+ this.xTickSize = 0;
+ this.yTickSize = 0;
+ },
+
+
+ setYConstraint: function(iUp, iDown, iTickSize) {
+ this.topConstraint = iUp;
+ this.bottomConstraint = iDown;
+
+ this.minY = this.initPageY - iUp;
+ this.maxY = this.initPageY + iDown;
+ if (iTickSize) { this.setYTicks(this.initPageY, iTickSize); }
+
+ this.constrainY = true;
+
+ },
+
+
+ resetConstraints: function() {
+
+ if (this.initPageX || this.initPageX === 0) {
+
+ var dx = (this.maintainOffset) ? this.lastPageX - this.initPageX : 0;
+ var dy = (this.maintainOffset) ? this.lastPageY - this.initPageY : 0;
+
+ this.setInitPosition(dx, dy);
+
+
+ } else {
+ this.setInitPosition();
+ }
+
+ if (this.constrainX) {
+ this.setXConstraint( this.leftConstraint,
+ this.rightConstraint,
+ this.xTickSize );
+ }
+
+ if (this.constrainY) {
+ this.setYConstraint( this.topConstraint,
+ this.bottomConstraint,
+ this.yTickSize );
+ }
+ },
+
+
+ getTick: function(val, tickArray) {
+ if (!tickArray) {
+
+
+ return val;
+ } else if (tickArray[0] >= val) {
+
+
+ return tickArray[0];
+ } else {
+ for (var i=0, len=tickArray.length; i<len; ++i) {
+ var next = i + 1;
+ if (tickArray[next] && tickArray[next] >= val) {
+ var diff1 = val - tickArray[i];
+ var diff2 = tickArray[next] - val;
+ return (diff2 > diff1) ? tickArray[i] : tickArray[next];
+ }
+ }
+
+
+
+ return tickArray[tickArray.length - 1];
+ }
+ },
+
+
+ toString: function() {
+ return ("DragDrop " + this.id);
+ }
+
+};
+
+})();
+
+
+
+
+if (!Ext.dd.DragDropMgr) {
+
+
+Ext.dd.DragDropMgr = function() {
+
+ var Event = Ext.EventManager;
+
+ return {
+
+
+ ids: {},
+
+
+ handleIds: {},
+
+
+ dragCurrent: null,
+
+
+ dragOvers: {},
+
+
+ deltaX: 0,
+
+
+ deltaY: 0,
+
+
+ preventDefault: true,
+
+
+ stopPropagation: true,
+
+
+ initialized: false,
+
+
+ locked: false,
+
+
+ init: function() {
+ this.initialized = true;
+ },
+
+
+ POINT: 0,
+
+
+ INTERSECT: 1,
+
+
+ mode: 0,
+
+
+ notifyOccluded: false,
+
+
+ _execOnAll: function(sMethod, args) {
+ for (var i in this.ids) {
+ for (var j in this.ids[i]) {
+ var oDD = this.ids[i][j];
+ if (! this.isTypeOfDD(oDD)) {
+ continue;
+ }
+ oDD[sMethod].apply(oDD, args);
+ }
+ }
+ },
+
+
+ _onLoad: function() {
+
+ this.init();
+
+
+ Event.on(document, "mouseup", this.handleMouseUp, this, true);
+ Event.on(document, "mousemove", this.handleMouseMove, this, true);
+ Event.on(window, "unload", this._onUnload, this, true);
+ Event.on(window, "resize", this._onResize, this, true);
+
+
+ },
+
+
+ _onResize: function(e) {
+ this._execOnAll("resetConstraints", []);
+ },
+
+
+ lock: function() { this.locked = true; },
+
+
+ unlock: function() { this.locked = false; },
+
+
+ isLocked: function() { return this.locked; },
+
+
+ locationCache: {},
+
+
+ useCache: true,
+
+
+ clickPixelThresh: 3,
+
+
+ clickTimeThresh: 350,
+
+
+ dragThreshMet: false,
+
+
+ clickTimeout: null,
+
+
+ startX: 0,
+
+
+ startY: 0,
+
+
+ regDragDrop: function(oDD, sGroup) {
+ if (!this.initialized) { this.init(); }
+
+ if (!this.ids[sGroup]) {
+ this.ids[sGroup] = {};
+ }
+ this.ids[sGroup][oDD.id] = oDD;
+ },
+
+
+ removeDDFromGroup: function(oDD, sGroup) {
+ if (!this.ids[sGroup]) {
+ this.ids[sGroup] = {};
+ }
+
+ var obj = this.ids[sGroup];
+ if (obj && obj[oDD.id]) {
+ delete obj[oDD.id];
+ }
+ },
+
+
+ _remove: function(oDD) {
+ for (var g in oDD.groups) {
+ if (g && this.ids[g] && this.ids[g][oDD.id]) {
+ delete this.ids[g][oDD.id];
+ }
+ }
+ delete this.handleIds[oDD.id];
+ },
+
+
+ regHandle: function(sDDId, sHandleId) {
+ if (!this.handleIds[sDDId]) {
+ this.handleIds[sDDId] = {};
+ }
+ this.handleIds[sDDId][sHandleId] = sHandleId;
+ },
+
+
+ isDragDrop: function(id) {
+ return ( this.getDDById(id) ) ? true : false;
+ },
+
+
+ getRelated: function(p_oDD, bTargetsOnly) {
+ var oDDs = [];
+ for (var i in p_oDD.groups) {
+ for (var j in this.ids[i]) {
+ var dd = this.ids[i][j];
+ if (! this.isTypeOfDD(dd)) {
+ continue;
+ }
+ if (!bTargetsOnly || dd.isTarget) {
+ oDDs[oDDs.length] = dd;
+ }
+ }
+ }
+
+ return oDDs;
+ },
+
+
+ isLegalTarget: function (oDD, oTargetDD) {
+ var targets = this.getRelated(oDD, true);
+ for (var i=0, len=targets.length;i<len;++i) {
+ if (targets[i].id == oTargetDD.id) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+
+ isTypeOfDD: function (oDD) {
+ return (oDD && oDD.__ygDragDrop);
+ },
+
+
+ isHandle: function(sDDId, sHandleId) {
+ return ( this.handleIds[sDDId] &&
+ this.handleIds[sDDId][sHandleId] );
+ },
+
+
+ getDDById: function(id) {
+ for (var i in this.ids) {
+ if (this.ids[i][id]) {
+ return this.ids[i][id];
+ }
+ }
+ return null;
+ },
+
+
+ handleMouseDown: function(e, oDD) {
+ if(Ext.QuickTips){
+ Ext.QuickTips.ddDisable();
+ }
+ if(this.dragCurrent){
+
+
+ this.handleMouseUp(e);
+ }
+
+ this.currentTarget = e.getTarget();
+ this.dragCurrent = oDD;
+
+ var el = oDD.getEl();
+
+
+ this.startX = e.getPageX();
+ this.startY = e.getPageY();
+
+ this.deltaX = this.startX - el.offsetLeft;
+ this.deltaY = this.startY - el.offsetTop;
+
+ this.dragThreshMet = false;
+
+ this.clickTimeout = setTimeout(
+ function() {
+ var DDM = Ext.dd.DDM;
+ DDM.startDrag(DDM.startX, DDM.startY);
+ },
+ this.clickTimeThresh );
+ },
+
+
+ startDrag: function(x, y) {
+ clearTimeout(this.clickTimeout);
+ if (this.dragCurrent) {
+ this.dragCurrent.b4StartDrag(x, y);
+ this.dragCurrent.startDrag(x, y);
+ }
+ this.dragThreshMet = true;
+ },
+
+
+ handleMouseUp: function(e) {
+
+ if(Ext.QuickTips){
+ Ext.QuickTips.ddEnable();
+ }
+ if (! this.dragCurrent) {
+ return;
+ }
+
+ clearTimeout(this.clickTimeout);
+
+ if (this.dragThreshMet) {
+ this.fireEvents(e, true);
+ } else {
+ }
+
+ this.stopDrag(e);
+
+ this.stopEvent(e);
+ },
+
+
+ stopEvent: function(e){
+ if(this.stopPropagation) {
+ e.stopPropagation();
+ }
+
+ if (this.preventDefault) {
+ e.preventDefault();
+ }
+ },
+
+
+ stopDrag: function(e) {
+
+ if (this.dragCurrent) {
+ if (this.dragThreshMet) {
+ this.dragCurrent.b4EndDrag(e);
+ this.dragCurrent.endDrag(e);
+ }
+
+ this.dragCurrent.onMouseUp(e);
+ }
+
+ this.dragCurrent = null;
+ this.dragOvers = {};
+ },
+
+
+ handleMouseMove: function(e) {
+ if (! this.dragCurrent) {
+ return true;
+ }
+
+
+
+ if (Ext.isIE && (e.button !== 0 && e.button !== 1 && e.button !== 2)) {
+ this.stopEvent(e);
+ return this.handleMouseUp(e);
+ }
+
+ if (!this.dragThreshMet) {
+ var diffX = Math.abs(this.startX - e.getPageX());
+ var diffY = Math.abs(this.startY - e.getPageY());
+ if (diffX > this.clickPixelThresh ||
+ diffY > this.clickPixelThresh) {
+ this.startDrag(this.startX, this.startY);
+ }
+ }
+
+ if (this.dragThreshMet) {
+ this.dragCurrent.b4Drag(e);
+ this.dragCurrent.onDrag(e);
+ if(!this.dragCurrent.moveOnly){
+ this.fireEvents(e, false);
+ }
+ }
+
+ this.stopEvent(e);
+
+ return true;
+ },
+
+
+ fireEvents: function(e, isDrop) {
+ var me = this,
+ dragCurrent = me.dragCurrent,
+ mousePoint = e.getPoint(),
+ overTarget,
+ overTargetEl,
+ allTargets = [],
+ oldOvers = [],
+ outEvts = [],
+ overEvts = [],
+ dropEvts = [],
+ enterEvts = [],
+ needsSort,
+ i,
+ len,
+ sGroup;
+
+
+
+ if (!dragCurrent || dragCurrent.isLocked()) {
+ return;
+ }
+
+
+
+ for (i in me.dragOvers) {
+ overTarget = me.dragOvers[i];
+
+ if (! me.isTypeOfDD(overTarget)) {
+ continue;
+ }
+
+ if (! this.isOverTarget(mousePoint, overTarget, me.mode)) {
+ outEvts.push( overTarget );
+ }
+
+ oldOvers[i] = true;
+ delete me.dragOvers[i];
+ }
+
+
+
+
+ for (sGroup in dragCurrent.groups) {
+
+ if ("string" != typeof sGroup) {
+ continue;
+ }
+
+
+ for (i in me.ids[sGroup]) {
+ overTarget = me.ids[sGroup][i];
+
+
+
+
+
+
+ if (me.isTypeOfDD(overTarget) &&
+ (overTargetEl = overTarget.getEl()) &&
+ (overTarget.isTarget) &&
+ (!overTarget.isLocked()) &&
+ ((overTarget != dragCurrent) || (dragCurrent.ignoreSelf === false))) {
+
+
+ if ((overTarget.zIndex = me.getZIndex(overTargetEl)) !== -1) {
+ needsSort = true;
+ }
+ allTargets.push(overTarget);
+ }
+ }
+ }
+
+
+ if (needsSort) {
+ allTargets.sort(me.byZIndex);
+ }
+
+
+
+ for (i = 0, len = allTargets.length; i < len; i++) {
+ overTarget = allTargets[i];
+
+
+ if (me.isOverTarget(mousePoint, overTarget, me.mode)) {
+
+ if (isDrop) {
+ dropEvts.push( overTarget );
+
+ } else {
+
+ if (!oldOvers[overTarget.id]) {
+ enterEvts.push( overTarget );
+
+ } else {
+ overEvts.push( overTarget );
+ }
+ me.dragOvers[overTarget.id] = overTarget;
+ }
+
+
+ if (!me.notifyOccluded) {
+ break;
+ }
+ }
+ }
+
+ if (me.mode) {
+ if (outEvts.length) {
+ dragCurrent.b4DragOut(e, outEvts);
+ dragCurrent.onDragOut(e, outEvts);
+ }
+
+ if (enterEvts.length) {
+ dragCurrent.onDragEnter(e, enterEvts);
+ }
+
+ if (overEvts.length) {
+ dragCurrent.b4DragOver(e, overEvts);
+ dragCurrent.onDragOver(e, overEvts);
+ }
+
+ if (dropEvts.length) {
+ dragCurrent.b4DragDrop(e, dropEvts);
+ dragCurrent.onDragDrop(e, dropEvts);
+ }
+
+ } else {
+
+ for (i=0, len=outEvts.length; i<len; ++i) {
+ dragCurrent.b4DragOut(e, outEvts[i].id);
+ dragCurrent.onDragOut(e, outEvts[i].id);
+ }
+
+
+ for (i=0,len=enterEvts.length; i<len; ++i) {
+
+ dragCurrent.onDragEnter(e, enterEvts[i].id);
+ }
+
+
+ for (i=0,len=overEvts.length; i<len; ++i) {
+ dragCurrent.b4DragOver(e, overEvts[i].id);
+ dragCurrent.onDragOver(e, overEvts[i].id);
+ }
+
+
+ for (i=0, len=dropEvts.length; i<len; ++i) {
+ dragCurrent.b4DragDrop(e, dropEvts[i].id);
+ dragCurrent.onDragDrop(e, dropEvts[i].id);
+ }
+
+ }
+
+
+ if (isDrop && !dropEvts.length) {
+ dragCurrent.onInvalidDrop(e);
+ }
+ },
+
+
+ getZIndex: function(element) {
+ var body = document.body,
+ z,
+ zIndex = -1;
+
+ element = Ext.getDom(element);
+ while (element !== body) {
+ if (!isNaN(z = Number(Ext.fly(element).getStyle('zIndex')))) {
+ zIndex = z;
+ }
+ element = element.parentNode;
+ }
+ return zIndex;
+ },
+
+
+ byZIndex: function(d1, d2) {
+ return d1.zIndex < d2.zIndex;
+ },
+
+
+ getBestMatch: function(dds) {
+ var winner = null;
+
+
+
+
+
+
+ var len = dds.length;
+
+ if (len == 1) {
+ winner = dds[0];
+ } else {
+
+ for (var i=0; i<len; ++i) {
+ var dd = dds[i];
+
+
+
+ if (dd.cursorIsOver) {
+ winner = dd;
+ break;
+
+ } else {
+ if (!winner ||
+ winner.overlap.getArea() < dd.overlap.getArea()) {
+ winner = dd;
+ }
+ }
+ }
+ }
+
+ return winner;
+ },
+
+
+ refreshCache: function(groups) {
+ for (var sGroup in groups) {
+ if ("string" != typeof sGroup) {
+ continue;
+ }
+ for (var i in this.ids[sGroup]) {
+ var oDD = this.ids[sGroup][i];
+
+ if (this.isTypeOfDD(oDD)) {
+
+ var loc = this.getLocation(oDD);
+ if (loc) {
+ this.locationCache[oDD.id] = loc;
+ } else {
+ delete this.locationCache[oDD.id];
+
+
+
+ }
+ }
+ }
+ }
+ },
+
+
+ verifyEl: function(el) {
+ if (el) {
+ var parent;
+ if(Ext.isIE){
+ try{
+ parent = el.offsetParent;
+ }catch(e){}
+ }else{
+ parent = el.offsetParent;
+ }
+ if (parent) {
+ return true;
+ }
+ }
+
+ return false;
+ },
+
+
+ getLocation: function(oDD) {
+ if (! this.isTypeOfDD(oDD)) {
+ return null;
+ }
+
+ var el = oDD.getEl(), pos, x1, x2, y1, y2, t, r, b, l, region;
+
+ try {
+ pos= Ext.lib.Dom.getXY(el);
+ } catch (e) { }
+
+ if (!pos) {
+ return null;
+ }
+
+ x1 = pos[0];
+ x2 = x1 + el.offsetWidth;
+ y1 = pos[1];
+ y2 = y1 + el.offsetHeight;
+
+ t = y1 - oDD.padding[0];
+ r = x2 + oDD.padding[1];
+ b = y2 + oDD.padding[2];
+ l = x1 - oDD.padding[3];
+
+ return new Ext.lib.Region( t, r, b, l );
+ },
+
+
+ isOverTarget: function(pt, oTarget, intersect) {
+
+ var loc = this.locationCache[oTarget.id];
+ if (!loc || !this.useCache) {
+ loc = this.getLocation(oTarget);
+ this.locationCache[oTarget.id] = loc;
+
+ }
+
+ if (!loc) {
+ return false;
+ }
+
+ oTarget.cursorIsOver = loc.contains( pt );
+
+
+
+
+
+
+ var dc = this.dragCurrent;
+ if (!dc || !dc.getTargetCoord ||
+ (!intersect && !dc.constrainX && !dc.constrainY)) {
+ return oTarget.cursorIsOver;
+ }
+
+ oTarget.overlap = null;
+
+
+
+
+
+ var pos = dc.getTargetCoord(pt.x, pt.y);
+
+ var el = dc.getDragEl();
+ var curRegion = new Ext.lib.Region( pos.y,
+ pos.x + el.offsetWidth,
+ pos.y + el.offsetHeight,
+ pos.x );
+
+ var overlap = curRegion.intersect(loc);
+
+ if (overlap) {
+ oTarget.overlap = overlap;
+ return (intersect) ? true : oTarget.cursorIsOver;
+ } else {
+ return false;
+ }
+ },
+
+
+ _onUnload: function(e, me) {
+ Event.removeListener(document, "mouseup", this.handleMouseUp, this);
+ Event.removeListener(document, "mousemove", this.handleMouseMove, this);
+ Event.removeListener(window, "resize", this._onResize, this);
+ Ext.dd.DragDropMgr.unregAll();
+ },
+
+
+ unregAll: function() {
+
+ if (this.dragCurrent) {
+ this.stopDrag();
+ this.dragCurrent = null;
+ }
+
+ this._execOnAll("unreg", []);
+
+ for (var i in this.elementCache) {
+ delete this.elementCache[i];
+ }
+
+ this.elementCache = {};
+ this.ids = {};
+ },
+
+
+ elementCache: {},
+
+
+ getElWrapper: function(id) {
+ var oWrapper = this.elementCache[id];
+ if (!oWrapper || !oWrapper.el) {
+ oWrapper = this.elementCache[id] =
+ new this.ElementWrapper(Ext.getDom(id));
+ }
+ return oWrapper;
+ },
+
+
+ getElement: function(id) {
+ return Ext.getDom(id);
+ },
+
+
+ getCss: function(id) {
+ var el = Ext.getDom(id);
+ return (el) ? el.style : null;
+ },
+
+
+ ElementWrapper: function(el) {
+
+ this.el = el || null;
+
+ this.id = this.el && el.id;
+
+ this.css = this.el && el.style;
+ },
+
+
+ getPosX: function(el) {
+ return Ext.lib.Dom.getX(el);
+ },
+
+
+ getPosY: function(el) {
+ return Ext.lib.Dom.getY(el);
+ },
+
+
+ swapNode: function(n1, n2) {
+ if (n1.swapNode) {
+ n1.swapNode(n2);
+ } else {
+ var p = n2.parentNode;
+ var s = n2.nextSibling;
+
+ if (s == n1) {
+ p.insertBefore(n1, n2);
+ } else if (n2 == n1.nextSibling) {
+ p.insertBefore(n2, n1);
+ } else {
+ n1.parentNode.replaceChild(n2, n1);
+ p.insertBefore(n1, s);
+ }
+ }
+ },
+
+
+ getScroll: function () {
+ var t, l, dde=document.documentElement, db=document.body;
+ if (dde && (dde.scrollTop || dde.scrollLeft)) {
+ t = dde.scrollTop;
+ l = dde.scrollLeft;
+ } else if (db) {
+ t = db.scrollTop;
+ l = db.scrollLeft;
+ } else {
+
+ }
+ return { top: t, left: l };
+ },
+
+
+ getStyle: function(el, styleProp) {
+ return Ext.fly(el).getStyle(styleProp);
+ },
+
+
+ getScrollTop: function () {
+ return this.getScroll().top;
+ },
+
+
+ getScrollLeft: function () {
+ return this.getScroll().left;
+ },
+
+
+ moveToEl: function (moveEl, targetEl) {
+ var aCoord = Ext.lib.Dom.getXY(targetEl);
+ Ext.lib.Dom.setXY(moveEl, aCoord);
+ },
+
+
+ numericSort: function(a, b) {
+ return (a - b);
+ },
+
+
+ _timeoutCount: 0,
+
+
+ _addListeners: function() {
+ var DDM = Ext.dd.DDM;
+ if ( Ext.lib.Event && document ) {
+ DDM._onLoad();
+ } else {
+ if (DDM._timeoutCount > 2000) {
+ } else {
+ setTimeout(DDM._addListeners, 10);
+ if (document && document.body) {
+ DDM._timeoutCount += 1;
+ }
+ }
+ }
+ },
+
+
+ handleWasClicked: function(node, id) {
+ if (this.isHandle(id, node.id)) {
+ return true;
+ } else {
+
+ var p = node.parentNode;
+
+ while (p) {
+ if (this.isHandle(id, p.id)) {
+ return true;
+ } else {
+ p = p.parentNode;
+ }
+ }
+ }
+
+ return false;
+ }
+
+ };
+
+}();
+
+
+Ext.dd.DDM = Ext.dd.DragDropMgr;
+Ext.dd.DDM._addListeners();
+
+}
+
+
+Ext.dd.DD = function(id, sGroup, config) {
+ if (id) {
+ this.init(id, sGroup, config);
+ }
+};
+
+Ext.extend(Ext.dd.DD, Ext.dd.DragDrop, {
+
+
+ scroll: true,
+
+
+ autoOffset: function(iPageX, iPageY) {
+ var x = iPageX - this.startPageX;
+ var y = iPageY - this.startPageY;
+ this.setDelta(x, y);
+ },
+
+
+ setDelta: function(iDeltaX, iDeltaY) {
+ this.deltaX = iDeltaX;
+ this.deltaY = iDeltaY;
+ },
+
+
+ setDragElPos: function(iPageX, iPageY) {
+
+
+
+ var el = this.getDragEl();
+ this.alignElWithMouse(el, iPageX, iPageY);
+ },
+
+
+ alignElWithMouse: function(el, iPageX, iPageY) {
+ var oCoord = this.getTargetCoord(iPageX, iPageY);
+ var fly = el.dom ? el : Ext.fly(el, '_dd');
+ if (!this.deltaSetXY) {
+ var aCoord = [oCoord.x, oCoord.y];
+ fly.setXY(aCoord);
+ var newLeft = fly.getLeft(true);
+ var newTop = fly.getTop(true);
+ this.deltaSetXY = [ newLeft - oCoord.x, newTop - oCoord.y ];
+ } else {
+ fly.setLeftTop(oCoord.x + this.deltaSetXY[0], oCoord.y + this.deltaSetXY[1]);
+ }
+
+ this.cachePosition(oCoord.x, oCoord.y);
+ this.autoScroll(oCoord.x, oCoord.y, el.offsetHeight, el.offsetWidth);
+ return oCoord;
+ },
+
+
+ cachePosition: function(iPageX, iPageY) {
+ if (iPageX) {
+ this.lastPageX = iPageX;
+ this.lastPageY = iPageY;
+ } else {
+ var aCoord = Ext.lib.Dom.getXY(this.getEl());
+ this.lastPageX = aCoord[0];
+ this.lastPageY = aCoord[1];
+ }
+ },
+
+
+ autoScroll: function(x, y, h, w) {
+
+ if (this.scroll) {
+
+ var clientH = Ext.lib.Dom.getViewHeight();
+
+
+ var clientW = Ext.lib.Dom.getViewWidth();
+
+
+ var st = this.DDM.getScrollTop();
+
+
+ var sl = this.DDM.getScrollLeft();
+
+
+ var bot = h + y;
+
+
+ var right = w + x;
+
+
+
+
+ var toBot = (clientH + st - y - this.deltaY);
+
+
+ var toRight = (clientW + sl - x - this.deltaX);
+
+
+
+
+ var thresh = 40;
+
+
+
+
+ var scrAmt = (document.all) ? 80 : 30;
+
+
+
+ if ( bot > clientH && toBot < thresh ) {
+ window.scrollTo(sl, st + scrAmt);
+ }
+
+
+
+ if ( y < st && st > 0 && y - st < thresh ) {
+ window.scrollTo(sl, st - scrAmt);
+ }
+
+
+
+ if ( right > clientW && toRight < thresh ) {
+ window.scrollTo(sl + scrAmt, st);
+ }
+
+
+
+ if ( x < sl && sl > 0 && x - sl < thresh ) {
+ window.scrollTo(sl - scrAmt, st);
+ }
+ }
+ },
+
+
+ getTargetCoord: function(iPageX, iPageY) {
+ var x = iPageX - this.deltaX;
+ var y = iPageY - this.deltaY;
+
+ if (this.constrainX) {
+ if (x < this.minX) { x = this.minX; }
+ if (x > this.maxX) { x = this.maxX; }
+ }
+
+ if (this.constrainY) {
+ if (y < this.minY) { y = this.minY; }
+ if (y > this.maxY) { y = this.maxY; }
+ }
+
+ x = this.getTick(x, this.xTicks);
+ y = this.getTick(y, this.yTicks);
+
+
+ return {x:x, y:y};
+ },
+
+
+ applyConfig: function() {
+ Ext.dd.DD.superclass.applyConfig.call(this);
+ this.scroll = (this.config.scroll !== false);
+ },
+
+
+ b4MouseDown: function(e) {
+
+ this.autoOffset(e.getPageX(),
+ e.getPageY());
+ },
+
+
+ b4Drag: function(e) {
+ this.setDragElPos(e.getPageX(),
+ e.getPageY());
+ },
+
+ toString: function() {
+ return ("DD " + this.id);
+ }
+
+
+
+
+
+
+});
+
+Ext.dd.DDProxy = function(id, sGroup, config) {
+ if (id) {
+ this.init(id, sGroup, config);
+ this.initFrame();
+ }
+};
+
+
+Ext.dd.DDProxy.dragElId = "ygddfdiv";
+
+Ext.extend(Ext.dd.DDProxy, Ext.dd.DD, {
+
+
+ resizeFrame: true,
+
+
+ centerFrame: false,
+
+
+ createFrame: function() {
+ var self = this;
+ var body = document.body;
+
+ if (!body || !body.firstChild) {
+ setTimeout( function() { self.createFrame(); }, 50 );
+ return;
+ }
+
+ var div = this.getDragEl();
+
+ if (!div) {
+ div = document.createElement("div");
+ div.id = this.dragElId;
+ var s = div.style;
+
+ s.position = "absolute";
+ s.visibility = "hidden";
+ s.cursor = "move";
+ s.border = "2px solid #aaa";
+ s.zIndex = 999;
+
+
+
+
+ body.insertBefore(div, body.firstChild);
+ }
+ },
+
+
+ initFrame: function() {
+ this.createFrame();
+ },
+
+ applyConfig: function() {
+ Ext.dd.DDProxy.superclass.applyConfig.call(this);
+
+ this.resizeFrame = (this.config.resizeFrame !== false);
+ this.centerFrame = (this.config.centerFrame);
+ this.setDragElId(this.config.dragElId || Ext.dd.DDProxy.dragElId);
+ },
+
+
+ showFrame: function(iPageX, iPageY) {
+ var el = this.getEl();
+ var dragEl = this.getDragEl();
+ var s = dragEl.style;
+
+ this._resizeProxy();
+
+ if (this.centerFrame) {
+ this.setDelta( Math.round(parseInt(s.width, 10)/2),
+ Math.round(parseInt(s.height, 10)/2) );
+ }
+
+ this.setDragElPos(iPageX, iPageY);
+
+ Ext.fly(dragEl).show();
+ },
+
+
+ _resizeProxy: function() {
+ if (this.resizeFrame) {
+ var el = this.getEl();
+ Ext.fly(this.getDragEl()).setSize(el.offsetWidth, el.offsetHeight);
+ }
+ },
+
+
+ b4MouseDown: function(e) {
+ var x = e.getPageX();
+ var y = e.getPageY();
+ this.autoOffset(x, y);
+ this.setDragElPos(x, y);
+ },
+
+
+ b4StartDrag: function(x, y) {
+
+ this.showFrame(x, y);
+ },
+
+
+ b4EndDrag: function(e) {
+ Ext.fly(this.getDragEl()).hide();
+ },
+
+
+
+
+ endDrag: function(e) {
+
+ var lel = this.getEl();
+ var del = this.getDragEl();
+
+
+ del.style.visibility = "";
+
+ this.beforeMove();
+
+
+ lel.style.visibility = "hidden";
+ Ext.dd.DDM.moveToEl(lel, del);
+ del.style.visibility = "hidden";
+ lel.style.visibility = "";
+
+ this.afterDrag();
+ },
+
+ beforeMove : function(){
+
+ },
+
+ afterDrag : function(){
+
+ },
+
+ toString: function() {
+ return ("DDProxy " + this.id);
+ }
+
+});
+
+Ext.dd.DDTarget = function(id, sGroup, config) {
+ if (id) {
+ this.initTarget(id, sGroup, config);
+ }
+};
+
+
+Ext.extend(Ext.dd.DDTarget, Ext.dd.DragDrop, {
+
+ getDragEl: Ext.emptyFn,
+
+ isValidHandleChild: Ext.emptyFn,
+
+ startDrag: Ext.emptyFn,
+
+ endDrag: Ext.emptyFn,
+
+ onDrag: Ext.emptyFn,
+
+ onDragDrop: Ext.emptyFn,
+
+ onDragEnter: Ext.emptyFn,
+
+ onDragOut: Ext.emptyFn,
+
+ onDragOver: Ext.emptyFn,
+
+ onInvalidDrop: Ext.emptyFn,
+
+ onMouseDown: Ext.emptyFn,
+
+ onMouseUp: Ext.emptyFn,
+
+ setXConstraint: Ext.emptyFn,
+
+ setYConstraint: Ext.emptyFn,
+
+ resetConstraints: Ext.emptyFn,
+
+ clearConstraints: Ext.emptyFn,
+
+ clearTicks: Ext.emptyFn,
+
+ setInitPosition: Ext.emptyFn,
+
+ setDragElId: Ext.emptyFn,
+
+ setHandleElId: Ext.emptyFn,
+
+ setOuterHandleElId: Ext.emptyFn,
+
+ addInvalidHandleClass: Ext.emptyFn,
+
+ addInvalidHandleId: Ext.emptyFn,
+
+ addInvalidHandleType: Ext.emptyFn,
+
+ removeInvalidHandleClass: Ext.emptyFn,
+
+ removeInvalidHandleId: Ext.emptyFn,
+
+ removeInvalidHandleType: Ext.emptyFn,
+
+ toString: function() {
+ return ("DDTarget " + this.id);
+ }
+});
+Ext.dd.DragTracker = Ext.extend(Ext.util.Observable, {
+
+ active: false,
+
+ tolerance: 5,
+
+ autoStart: false,
+
+ constructor : function(config){
+ Ext.apply(this, config);
+ this.addEvents(
+
+ 'mousedown',
+
+ 'mouseup',
+
+ 'mousemove',
+
+ 'dragstart',
+
+ 'dragend',
+
+ 'drag'
+ );
+
+ this.dragRegion = new Ext.lib.Region(0,0,0,0);
+
+ if(this.el){
+ this.initEl(this.el);
+ }
+ Ext.dd.DragTracker.superclass.constructor.call(this, config);
+ },
+
+ initEl: function(el){
+ this.el = Ext.get(el);
+ el.on('mousedown', this.onMouseDown, this,
+ this.delegate ? {delegate: this.delegate} : undefined);
+ },
+
+ destroy : function(){
+ this.el.un('mousedown', this.onMouseDown, this);
+ delete this.el;
+ },
+
+ onMouseDown: function(e, target){
+ if(this.fireEvent('mousedown', this, e) !== false && this.onBeforeStart(e) !== false){
+ this.startXY = this.lastXY = e.getXY();
+ this.dragTarget = this.delegate ? target : this.el.dom;
+ if(this.preventDefault !== false){
+ e.preventDefault();
+ }
+ Ext.getDoc().on({
+ scope: this,
+ mouseup: this.onMouseUp,
+ mousemove: this.onMouseMove,
+ selectstart: this.stopSelect
+ });
+ if(this.autoStart){
+ this.timer = this.triggerStart.defer(this.autoStart === true ? 1000 : this.autoStart, this, [e]);
+ }
+ }
+ },
+
+ onMouseMove: function(e, target){
+
+ var ieCheck = Ext.isIE6 || Ext.isIE7 || Ext.isIE8;
+ if(this.active && ieCheck && !e.browserEvent.button){
+ e.preventDefault();
+ this.onMouseUp(e);
+ return;
+ }
+
+ e.preventDefault();
+ var xy = e.getXY(), s = this.startXY;
+ this.lastXY = xy;
+ if(!this.active){
+ if(Math.abs(s[0]-xy[0]) > this.tolerance || Math.abs(s[1]-xy[1]) > this.tolerance){
+ this.triggerStart(e);
+ }else{
+ return;
+ }
+ }
+ this.fireEvent('mousemove', this, e);
+ this.onDrag(e);
+ this.fireEvent('drag', this, e);
+ },
+
+ onMouseUp: function(e) {
+ var doc = Ext.getDoc(),
+ wasActive = this.active;
+
+ doc.un('mousemove', this.onMouseMove, this);
+ doc.un('mouseup', this.onMouseUp, this);
+ doc.un('selectstart', this.stopSelect, this);
+ e.preventDefault();
+ this.clearStart();
+ this.active = false;
+ delete this.elRegion;
+ this.fireEvent('mouseup', this, e);
+ if(wasActive){
+ this.onEnd(e);
+ this.fireEvent('dragend', this, e);
+ }
+ },
+
+ triggerStart: function(e) {
+ this.clearStart();
+ this.active = true;
+ this.onStart(e);
+ this.fireEvent('dragstart', this, e);
+ },
+
+ clearStart : function() {
+ if(this.timer){
+ clearTimeout(this.timer);
+ delete this.timer;
+ }
+ },
+
+ stopSelect : function(e) {
+ e.stopEvent();
+ return false;
+ },
+
+
+ onBeforeStart : function(e) {
+
+ },
+
+
+ onStart : function(xy) {
+
+ },
+
+
+ onDrag : function(e) {
+
+ },
+
+
+ onEnd : function(e) {
+
+ },
+
+
+ getDragTarget : function(){
+ return this.dragTarget;
+ },
+
+ getDragCt : function(){
+ return this.el;
+ },
+
+ getXY : function(constrain){
+ return constrain ?
+ this.constrainModes[constrain].call(this, this.lastXY) : this.lastXY;
+ },
+
+ getOffset : function(constrain){
+ var xy = this.getXY(constrain),
+ s = this.startXY;
+ return [s[0]-xy[0], s[1]-xy[1]];
+ },
+
+ constrainModes: {
+ 'point' : function(xy){
+
+ if(!this.elRegion){
+ this.elRegion = this.getDragCt().getRegion();
+ }
+
+ var dr = this.dragRegion;
+
+ dr.left = xy[0];
+ dr.top = xy[1];
+ dr.right = xy[0];
+ dr.bottom = xy[1];
+
+ dr.constrainTo(this.elRegion);
+
+ return [dr.left, dr.top];
+ }
+ }
+});
+Ext.dd.ScrollManager = function(){
+ var ddm = Ext.dd.DragDropMgr;
+ var els = {};
+ var dragEl = null;
+ var proc = {};
+
+ var onStop = function(e){
+ dragEl = null;
+ clearProc();
+ };
+
+ var triggerRefresh = function(){
+ if(ddm.dragCurrent){
+ ddm.refreshCache(ddm.dragCurrent.groups);
+ }
+ };
+
+ var doScroll = function(){
+ if(ddm.dragCurrent){
+ var dds = Ext.dd.ScrollManager;
+ var inc = proc.el.ddScrollConfig ?
+ proc.el.ddScrollConfig.increment : dds.increment;
+ if(!dds.animate){
+ if(proc.el.scroll(proc.dir, inc)){
+ triggerRefresh();
+ }
+ }else{
+ proc.el.scroll(proc.dir, inc, true, dds.animDuration, triggerRefresh);
+ }
+ }
+ };
+
+ var clearProc = function(){
+ if(proc.id){
+ clearInterval(proc.id);
+ }
+ proc.id = 0;
+ proc.el = null;
+ proc.dir = "";
+ };
+
+ var startProc = function(el, dir){
+ clearProc();
+ proc.el = el;
+ proc.dir = dir;
+ var group = el.ddScrollConfig ? el.ddScrollConfig.ddGroup : undefined,
+ freq = (el.ddScrollConfig && el.ddScrollConfig.frequency)
+ ? el.ddScrollConfig.frequency
+ : Ext.dd.ScrollManager.frequency;
+
+ if (group === undefined || ddm.dragCurrent.ddGroup == group) {
+ proc.id = setInterval(doScroll, freq);
+ }
+ };
+
+ var onFire = function(e, isDrop){
+ if(isDrop || !ddm.dragCurrent){ return; }
+ var dds = Ext.dd.ScrollManager;
+ if(!dragEl || dragEl != ddm.dragCurrent){
+ dragEl = ddm.dragCurrent;
+
+ dds.refreshCache();
+ }
+
+ var xy = Ext.lib.Event.getXY(e);
+ var pt = new Ext.lib.Point(xy[0], xy[1]);
+ for(var id in els){
+ var el = els[id], r = el._region;
+ var c = el.ddScrollConfig ? el.ddScrollConfig : dds;
+ if(r && r.contains(pt) && el.isScrollable()){
+ if(r.bottom - pt.y <= c.vthresh){
+ if(proc.el != el){
+ startProc(el, "down");
+ }
+ return;
+ }else if(r.right - pt.x <= c.hthresh){
+ if(proc.el != el){
+ startProc(el, "left");
+ }
+ return;
+ }else if(pt.y - r.top <= c.vthresh){
+ if(proc.el != el){
+ startProc(el, "up");
+ }
+ return;
+ }else if(pt.x - r.left <= c.hthresh){
+ if(proc.el != el){
+ startProc(el, "right");
+ }
+ return;
+ }
+ }
+ }
+ clearProc();
+ };
+
+ ddm.fireEvents = ddm.fireEvents.createSequence(onFire, ddm);
+ ddm.stopDrag = ddm.stopDrag.createSequence(onStop, ddm);
+
+ return {
+
+ register : function(el){
+ if(Ext.isArray(el)){
+ for(var i = 0, len = el.length; i < len; i++) {
+ this.register(el[i]);
+ }
+ }else{
+ el = Ext.get(el);
+ els[el.id] = el;
+ }
+ },
+
+
+ unregister : function(el){
+ if(Ext.isArray(el)){
+ for(var i = 0, len = el.length; i < len; i++) {
+ this.unregister(el[i]);
+ }
+ }else{
+ el = Ext.get(el);
+ delete els[el.id];
+ }
+ },
+
+
+ vthresh : 25,
+
+ hthresh : 25,
+
+
+ increment : 100,
+
+
+ frequency : 500,
+
+
+ animate: true,
+
+
+ animDuration: .4,
+
+
+ ddGroup: undefined,
+
+
+ refreshCache : function(){
+ for(var id in els){
+ if(typeof els[id] == 'object'){
+ els[id]._region = els[id].getRegion();
+ }
+ }
+ }
+ };
+}();
+Ext.dd.Registry = function(){
+ var elements = {};
+ var handles = {};
+ var autoIdSeed = 0;
+
+ var getId = function(el, autogen){
+ if(typeof el == "string"){
+ return el;
+ }
+ var id = el.id;
+ if(!id && autogen !== false){
+ id = "extdd-" + (++autoIdSeed);
+ el.id = id;
+ }
+ return id;
+ };
+
+ return {
+
+ register : function(el, data){
+ data = data || {};
+ if(typeof el == "string"){
+ el = document.getElementById(el);
+ }
+ data.ddel = el;
+ elements[getId(el)] = data;
+ if(data.isHandle !== false){
+ handles[data.ddel.id] = data;
+ }
+ if(data.handles){
+ var hs = data.handles;
+ for(var i = 0, len = hs.length; i < len; i++){
+ handles[getId(hs[i])] = data;
+ }
+ }
+ },
+
+
+ unregister : function(el){
+ var id = getId(el, false);
+ var data = elements[id];
+ if(data){
+ delete elements[id];
+ if(data.handles){
+ var hs = data.handles;
+ for(var i = 0, len = hs.length; i < len; i++){
+ delete handles[getId(hs[i], false)];
+ }
+ }
+ }
+ },
+
+
+ getHandle : function(id){
+ if(typeof id != "string"){
+ id = id.id;
+ }
+ return handles[id];
+ },
+
+
+ getHandleFromEvent : function(e){
+ var t = Ext.lib.Event.getTarget(e);
+ return t ? handles[t.id] : null;
+ },
+
+
+ getTarget : function(id){
+ if(typeof id != "string"){
+ id = id.id;
+ }
+ return elements[id];
+ },
+
+
+ getTargetFromEvent : function(e){
+ var t = Ext.lib.Event.getTarget(e);
+ return t ? elements[t.id] || handles[t.id] : null;
+ }
+ };
+}();
+Ext.dd.StatusProxy = function(config){
+ Ext.apply(this, config);
+ this.id = this.id || Ext.id();
+ this.el = new Ext.Layer({
+ dh: {
+ id: this.id, tag: "div", cls: "x-dd-drag-proxy "+this.dropNotAllowed, children: [
+ {tag: "div", cls: "x-dd-drop-icon"},
+ {tag: "div", cls: "x-dd-drag-ghost"}
+ ]
+ },
+ shadow: !config || config.shadow !== false
+ });
+ this.ghost = Ext.get(this.el.dom.childNodes[1]);
+ this.dropStatus = this.dropNotAllowed;
+};
+
+Ext.dd.StatusProxy.prototype = {
+
+ dropAllowed : "x-dd-drop-ok",
+
+ dropNotAllowed : "x-dd-drop-nodrop",
+
+
+ setStatus : function(cssClass){
+ cssClass = cssClass || this.dropNotAllowed;
+ if(this.dropStatus != cssClass){
+ this.el.replaceClass(this.dropStatus, cssClass);
+ this.dropStatus = cssClass;
+ }
+ },
+
+
+ reset : function(clearGhost){
+ this.el.dom.className = "x-dd-drag-proxy " + this.dropNotAllowed;
+ this.dropStatus = this.dropNotAllowed;
+ if(clearGhost){
+ this.ghost.update("");
+ }
+ },
+
+
+ update : function(html){
+ if(typeof html == "string"){
+ this.ghost.update(html);
+ }else{
+ this.ghost.update("");
+ html.style.margin = "0";
+ this.ghost.dom.appendChild(html);
+ }
+ var el = this.ghost.dom.firstChild;
+ if(el){
+ Ext.fly(el).setStyle('float', 'none');
+ }
+ },
+
+
+ getEl : function(){
+ return this.el;
+ },
+
+
+ getGhost : function(){
+ return this.ghost;
+ },
+
+
+ hide : function(clear){
+ this.el.hide();
+ if(clear){
+ this.reset(true);
+ }
+ },
+
+
+ stop : function(){
+ if(this.anim && this.anim.isAnimated && this.anim.isAnimated()){
+ this.anim.stop();
+ }
+ },
+
+
+ show : function(){
+ this.el.show();
+ },
+
+
+ sync : function(){
+ this.el.sync();
+ },
+
+
+ repair : function(xy, callback, scope){
+ this.callback = callback;
+ this.scope = scope;
+ if(xy && this.animRepair !== false){
+ this.el.addClass("x-dd-drag-repair");
+ this.el.hideUnders(true);
+ this.anim = this.el.shift({
+ duration: this.repairDuration || .5,
+ easing: 'easeOut',
+ xy: xy,
+ stopFx: true,
+ callback: this.afterRepair,
+ scope: this
+ });
+ }else{
+ this.afterRepair();
+ }
+ },
+
+
+ afterRepair : function(){
+ this.hide(true);
+ if(typeof this.callback == "function"){
+ this.callback.call(this.scope || this);
+ }
+ this.callback = null;
+ this.scope = null;
+ },
+
+ destroy: function(){
+ Ext.destroy(this.ghost, this.el);
+ }
+};
+Ext.dd.DragSource = function(el, config){
+ this.el = Ext.get(el);
+ if(!this.dragData){
+ this.dragData = {};
+ }
+
+ Ext.apply(this, config);
+
+ if(!this.proxy){
+ this.proxy = new Ext.dd.StatusProxy();
+ }
+ Ext.dd.DragSource.superclass.constructor.call(this, this.el.dom, this.ddGroup || this.group,
+ {dragElId : this.proxy.id, resizeFrame: false, isTarget: false, scroll: this.scroll === true});
+
+ this.dragging = false;
+};
+
+Ext.extend(Ext.dd.DragSource, Ext.dd.DDProxy, {
+
+
+ dropAllowed : "x-dd-drop-ok",
+
+ dropNotAllowed : "x-dd-drop-nodrop",
+
+
+ getDragData : function(e){
+ return this.dragData;
+ },
+
+
+ onDragEnter : function(e, id){
+ var target = Ext.dd.DragDropMgr.getDDById(id);
+ this.cachedTarget = target;
+ if(this.beforeDragEnter(target, e, id) !== false){
+ if(target.isNotifyTarget){
+ var status = target.notifyEnter(this, e, this.dragData);
+ this.proxy.setStatus(status);
+ }else{
+ this.proxy.setStatus(this.dropAllowed);
+ }
+
+ if(this.afterDragEnter){
+
+ this.afterDragEnter(target, e, id);
+ }
+ }
+ },
+
+
+ beforeDragEnter : function(target, e, id){
+ return true;
+ },
+
+
+ alignElWithMouse: function() {
+ Ext.dd.DragSource.superclass.alignElWithMouse.apply(this, arguments);
+ this.proxy.sync();
+ },
+
+
+ onDragOver : function(e, id){
+ var target = this.cachedTarget || Ext.dd.DragDropMgr.getDDById(id);
+ if(this.beforeDragOver(target, e, id) !== false){
+ if(target.isNotifyTarget){
+ var status = target.notifyOver(this, e, this.dragData);
+ this.proxy.setStatus(status);
+ }
+
+ if(this.afterDragOver){
+
+ this.afterDragOver(target, e, id);
+ }
+ }
+ },
+
+
+ beforeDragOver : function(target, e, id){
+ return true;
+ },
+
+
+ onDragOut : function(e, id){
+ var target = this.cachedTarget || Ext.dd.DragDropMgr.getDDById(id);
+ if(this.beforeDragOut(target, e, id) !== false){
+ if(target.isNotifyTarget){
+ target.notifyOut(this, e, this.dragData);
+ }
+ this.proxy.reset();
+ if(this.afterDragOut){
+
+ this.afterDragOut(target, e, id);
+ }
+ }
+ this.cachedTarget = null;
+ },
+
+
+ beforeDragOut : function(target, e, id){
+ return true;
+ },
+
+
+ onDragDrop : function(e, id){
+ var target = this.cachedTarget || Ext.dd.DragDropMgr.getDDById(id);
+ if(this.beforeDragDrop(target, e, id) !== false){
+ if(target.isNotifyTarget){
+ if(target.notifyDrop(this, e, this.dragData)){
+ this.onValidDrop(target, e, id);
+ }else{
+ this.onInvalidDrop(target, e, id);
+ }
+ }else{
+ this.onValidDrop(target, e, id);
+ }
+
+ if(this.afterDragDrop){
+
+ this.afterDragDrop(target, e, id);
+ }
+ }
+ delete this.cachedTarget;
+ },
+
+
+ beforeDragDrop : function(target, e, id){
+ return true;
+ },
+
+
+ onValidDrop : function(target, e, id){
+ this.hideProxy();
+ if(this.afterValidDrop){
+
+ this.afterValidDrop(target, e, id);
+ }
+ },
+
+
+ getRepairXY : function(e, data){
+ return this.el.getXY();
+ },
+
+
+ onInvalidDrop : function(target, e, id){
+ this.beforeInvalidDrop(target, e, id);
+ if(this.cachedTarget){
+ if(this.cachedTarget.isNotifyTarget){
+ this.cachedTarget.notifyOut(this, e, this.dragData);
+ }
+ this.cacheTarget = null;
+ }
+ this.proxy.repair(this.getRepairXY(e, this.dragData), this.afterRepair, this);
+
+ if(this.afterInvalidDrop){
+
+ this.afterInvalidDrop(e, id);
+ }
+ },
+
+
+ afterRepair : function(){
+ if(Ext.enableFx){
+ this.el.highlight(this.hlColor || "c3daf9");
+ }
+ this.dragging = false;
+ },
+
+
+ beforeInvalidDrop : function(target, e, id){
+ return true;
+ },
+
+
+ handleMouseDown : function(e){
+ if(this.dragging) {
+ return;
+ }
+ var data = this.getDragData(e);
+ if(data && this.onBeforeDrag(data, e) !== false){
+ this.dragData = data;
+ this.proxy.stop();
+ Ext.dd.DragSource.superclass.handleMouseDown.apply(this, arguments);
+ }
+ },
+
+
+ onBeforeDrag : function(data, e){
+ return true;
+ },
+
+
+ onStartDrag : Ext.emptyFn,
+
+
+ startDrag : function(x, y){
+ this.proxy.reset();
+ this.dragging = true;
+ this.proxy.update("");
+ this.onInitDrag(x, y);
+ this.proxy.show();
+ },
+
+
+ onInitDrag : function(x, y){
+ var clone = this.el.dom.cloneNode(true);
+ clone.id = Ext.id();
+ this.proxy.update(clone);
+ this.onStartDrag(x, y);
+ return true;
+ },
+
+
+ getProxy : function(){
+ return this.proxy;
+ },
+
+
+ hideProxy : function(){
+ this.proxy.hide();
+ this.proxy.reset(true);
+ this.dragging = false;
+ },
+
+
+ triggerCacheRefresh : function(){
+ Ext.dd.DDM.refreshCache(this.groups);
+ },
+
+
+ b4EndDrag: function(e) {
+ },
+
+
+ endDrag : function(e){
+ this.onEndDrag(this.dragData, e);
+ },
+
+
+ onEndDrag : function(data, e){
+ },
+
+
+ autoOffset : function(x, y) {
+ this.setDelta(-12, -20);
+ },
+
+ destroy: function(){
+ Ext.dd.DragSource.superclass.destroy.call(this);
+ Ext.destroy(this.proxy);
+ }
+});
+Ext.dd.DropTarget = Ext.extend(Ext.dd.DDTarget, {
+
+ constructor : function(el, config){
+ this.el = Ext.get(el);
+
+ Ext.apply(this, config);
+
+ if(this.containerScroll){
+ Ext.dd.ScrollManager.register(this.el);
+ }
+
+ Ext.dd.DropTarget.superclass.constructor.call(this, this.el.dom, this.ddGroup || this.group,
+ {isTarget: true});
+ },
+
+
+
+
+ dropAllowed : "x-dd-drop-ok",
+
+ dropNotAllowed : "x-dd-drop-nodrop",
+
+
+ isTarget : true,
+
+
+ isNotifyTarget : true,
+
+
+ notifyEnter : function(dd, e, data){
+ if(this.overClass){
+ this.el.addClass(this.overClass);
+ }
+ return this.dropAllowed;
+ },
+
+
+ notifyOver : function(dd, e, data){
+ return this.dropAllowed;
+ },
+
+
+ notifyOut : function(dd, e, data){
+ if(this.overClass){
+ this.el.removeClass(this.overClass);
+ }
+ },
+
+
+ notifyDrop : function(dd, e, data){
+ return false;
+ },
+
+ destroy : function(){
+ Ext.dd.DropTarget.superclass.destroy.call(this);
+ if(this.containerScroll){
+ Ext.dd.ScrollManager.unregister(this.el);
+ }
+ }
+});
+Ext.dd.DragZone = Ext.extend(Ext.dd.DragSource, {
+
+ constructor : function(el, config){
+ Ext.dd.DragZone.superclass.constructor.call(this, el, config);
+ if(this.containerScroll){
+ Ext.dd.ScrollManager.register(this.el);
+ }
+ },
+
+
+
+
+
+
+ getDragData : function(e){
+ return Ext.dd.Registry.getHandleFromEvent(e);
+ },
+
+
+ onInitDrag : function(x, y){
+ this.proxy.update(this.dragData.ddel.cloneNode(true));
+ this.onStartDrag(x, y);
+ return true;
+ },
+
+
+ afterRepair : function(){
+ if(Ext.enableFx){
+ Ext.Element.fly(this.dragData.ddel).highlight(this.hlColor || "c3daf9");
+ }
+ this.dragging = false;
+ },
+
+
+ getRepairXY : function(e){
+ return Ext.Element.fly(this.dragData.ddel).getXY();
+ },
+
+ destroy : function(){
+ Ext.dd.DragZone.superclass.destroy.call(this);
+ if(this.containerScroll){
+ Ext.dd.ScrollManager.unregister(this.el);
+ }
+ }
+});
+Ext.dd.DropZone = function(el, config){
+ Ext.dd.DropZone.superclass.constructor.call(this, el, config);
+};
+
+Ext.extend(Ext.dd.DropZone, Ext.dd.DropTarget, {
+
+ getTargetFromEvent : function(e){
+ return Ext.dd.Registry.getTargetFromEvent(e);
+ },
+
+
+ onNodeEnter : function(n, dd, e, data){
+
+ },
+
+
+ onNodeOver : function(n, dd, e, data){
+ return this.dropAllowed;
+ },
+
+
+ onNodeOut : function(n, dd, e, data){
+
+ },
+
+
+ onNodeDrop : function(n, dd, e, data){
+ return false;
+ },
+
+
+ onContainerOver : function(dd, e, data){
+ return this.dropNotAllowed;
+ },
+
+
+ onContainerDrop : function(dd, e, data){
+ return false;
+ },
+
+
+ notifyEnter : function(dd, e, data){
+ return this.dropNotAllowed;
+ },
+
+
+ notifyOver : function(dd, e, data){
+ var n = this.getTargetFromEvent(e);
+ if(!n){
+ if(this.lastOverNode){
+ this.onNodeOut(this.lastOverNode, dd, e, data);
+ this.lastOverNode = null;
+ }
+ return this.onContainerOver(dd, e, data);
+ }
+ if(this.lastOverNode != n){
+ if(this.lastOverNode){
+ this.onNodeOut(this.lastOverNode, dd, e, data);
+ }
+ this.onNodeEnter(n, dd, e, data);
+ this.lastOverNode = n;
+ }
+ return this.onNodeOver(n, dd, e, data);
+ },
+
+
+ notifyOut : function(dd, e, data){
+ if(this.lastOverNode){
+ this.onNodeOut(this.lastOverNode, dd, e, data);
+ this.lastOverNode = null;
+ }
+ },
+
+
+ notifyDrop : function(dd, e, data){
+ if(this.lastOverNode){
+ this.onNodeOut(this.lastOverNode, dd, e, data);
+ this.lastOverNode = null;
+ }
+ var n = this.getTargetFromEvent(e);
+ return n ?
+ this.onNodeDrop(n, dd, e, data) :
+ this.onContainerDrop(dd, e, data);
+ },
+
+
+ triggerCacheRefresh : function(){
+ Ext.dd.DDM.refreshCache(this.groups);
+ }
+});
+Ext.Element.addMethods({
+
+ initDD : function(group, config, overrides){
+ var dd = new Ext.dd.DD(Ext.id(this.dom), group, config);
+ return Ext.apply(dd, overrides);
+ },
+
+
+ initDDProxy : function(group, config, overrides){
+ var dd = new Ext.dd.DDProxy(Ext.id(this.dom), group, config);
+ return Ext.apply(dd, overrides);
+ },
+
+
+ initDDTarget : function(group, config, overrides){
+ var dd = new Ext.dd.DDTarget(Ext.id(this.dom), group, config);
+ return Ext.apply(dd, overrides);
+ }
+});
+
+Ext.data.Api = (function() {
+
+
+
+
+
+ var validActions = {};
+
+ return {
+
+ actions : {
+ create : 'create',
+ read : 'read',
+ update : 'update',
+ destroy : 'destroy'
+ },
+
+
+ restActions : {
+ create : 'POST',
+ read : 'GET',
+ update : 'PUT',
+ destroy : 'DELETE'
+ },
+
+
+ isAction : function(action) {
+ return (Ext.data.Api.actions[action]) ? true : false;
+ },
+
+
+ getVerb : function(name) {
+ if (validActions[name]) {
+ return validActions[name];
+ }
+ for (var verb in this.actions) {
+ if (this.actions[verb] === name) {
+ validActions[name] = verb;
+ break;
+ }
+ }
+ return (validActions[name] !== undefined) ? validActions[name] : null;
+ },
+
+
+ isValid : function(api){
+ var invalid = [];
+ var crud = this.actions;
+ for (var action in api) {
+ if (!(action in crud)) {
+ invalid.push(action);
+ }
+ }
+ return (!invalid.length) ? true : invalid;
+ },
+
+
+ hasUniqueUrl : function(proxy, verb) {
+ var url = (proxy.api[verb]) ? proxy.api[verb].url : null;
+ var unique = true;
+ for (var action in proxy.api) {
+ if ((unique = (action === verb) ? true : (proxy.api[action].url != url) ? true : false) === false) {
+ break;
+ }
+ }
+ return unique;
+ },
+
+
+ prepare : function(proxy) {
+ if (!proxy.api) {
+ proxy.api = {};
+ }
+ for (var verb in this.actions) {
+ var action = this.actions[verb];
+ proxy.api[action] = proxy.api[action] || proxy.url || proxy.directFn;
+ if (typeof(proxy.api[action]) == 'string') {
+ proxy.api[action] = {
+ url: proxy.api[action],
+ method: (proxy.restful === true) ? Ext.data.Api.restActions[action] : undefined
+ };
+ }
+ }
+ },
+
+
+ restify : function(proxy) {
+ proxy.restful = true;
+ for (var verb in this.restActions) {
+ proxy.api[this.actions[verb]].method ||
+ (proxy.api[this.actions[verb]].method = this.restActions[verb]);
+ }
+
+
+ proxy.onWrite = proxy.onWrite.createInterceptor(function(action, o, response, rs) {
+ var reader = o.reader;
+ var res = new Ext.data.Response({
+ action: action,
+ raw: response
+ });
+
+ switch (response.status) {
+ case 200:
+ return true;
+ break;
+ case 201:
+ if (Ext.isEmpty(res.raw.responseText)) {
+ res.success = true;
+ } else {
+
+ return true;
+ }
+ break;
+ case 204:
+ res.success = true;
+ res.data = null;
+ break;
+ default:
+ return true;
+ break;
+ }
+ if (res.success === true) {
+ this.fireEvent("write", this, action, res.data, res, rs, o.request.arg);
+ } else {
+ this.fireEvent('exception', this, 'remote', action, o, res, rs);
+ }
+ o.request.callback.call(o.request.scope, res.data, res, res.success);
+
+ return false;
+ }, proxy);
+ }
+ };
+})();
+
+
+Ext.data.Response = function(params, response) {
+ Ext.apply(this, params, {
+ raw: response
+ });
+};
+Ext.data.Response.prototype = {
+ message : null,
+ success : false,
+ status : null,
+ root : null,
+ raw : null,
+
+ getMessage : function() {
+ return this.message;
+ },
+ getSuccess : function() {
+ return this.success;
+ },
+ getStatus : function() {
+ return this.status;
+ },
+ getRoot : function() {
+ return this.root;
+ },
+ getRawResponse : function() {
+ return this.raw;
+ }
+};
+
+
+Ext.data.Api.Error = Ext.extend(Ext.Error, {
+ constructor : function(message, arg) {
+ this.arg = arg;
+ Ext.Error.call(this, message);
+ },
+ name: 'Ext.data.Api'
+});
+Ext.apply(Ext.data.Api.Error.prototype, {
+ lang: {
+ 'action-url-undefined': 'No fallback url defined for this action. When defining a DataProxy api, please be sure to define an url for each CRUD action in Ext.data.Api.actions or define a default url in addition to your api-configuration.',
+ 'invalid': 'received an invalid API-configuration. Please ensure your proxy API-configuration contains only the actions defined in Ext.data.Api.actions',
+ 'invalid-url': 'Invalid url. Please review your proxy configuration.',
+ 'execute': 'Attempted to execute an unknown action. Valid API actions are defined in Ext.data.Api.actions"'
+ }
+});
+
+
+
+
+Ext.data.SortTypes = {
+
+ none : function(s){
+ return s;
+ },
+
+
+ stripTagsRE : /<\/?[^>]+>/gi,
+
+
+ asText : function(s){
+ return String(s).replace(this.stripTagsRE, "");
+ },
+
+
+ asUCText : function(s){
+ return String(s).toUpperCase().replace(this.stripTagsRE, "");
+ },
+
+
+ asUCString : function(s) {
+ return String(s).toUpperCase();
+ },
+
+
+ asDate : function(s) {
+ if(!s){
+ return 0;
+ }
+ if(Ext.isDate(s)){
+ return s.getTime();
+ }
+ return Date.parse(String(s));
+ },
+
+
+ asFloat : function(s) {
+ var val = parseFloat(String(s).replace(/,/g, ""));
+ return isNaN(val) ? 0 : val;
+ },
+
+
+ asInt : function(s) {
+ var val = parseInt(String(s).replace(/,/g, ""), 10);
+ return isNaN(val) ? 0 : val;
+ }
+};
+Ext.data.Record = function(data, id){
+
+ this.id = (id || id === 0) ? id : Ext.data.Record.id(this);
+ this.data = data || {};
+};
+
+
+Ext.data.Record.create = function(o){
+ var f = Ext.extend(Ext.data.Record, {});
+ var p = f.prototype;
+ p.fields = new Ext.util.MixedCollection(false, function(field){
+ return field.name;
+ });
+ for(var i = 0, len = o.length; i < len; i++){
+ p.fields.add(new Ext.data.Field(o[i]));
+ }
+ f.getField = function(name){
+ return p.fields.get(name);
+ };
+ return f;
+};
+
+Ext.data.Record.PREFIX = 'ext-record';
+Ext.data.Record.AUTO_ID = 1;
+Ext.data.Record.EDIT = 'edit';
+Ext.data.Record.REJECT = 'reject';
+Ext.data.Record.COMMIT = 'commit';
+
+
+
+Ext.data.Record.id = function(rec) {
+ rec.phantom = true;
+ return [Ext.data.Record.PREFIX, '-', Ext.data.Record.AUTO_ID++].join('');
+};
+
+Ext.data.Record.prototype = {
+
+
+
+
+
+
+ dirty : false,
+ editing : false,
+ error : null,
+
+ modified : null,
+
+ phantom : false,
+
+
+ join : function(store){
+
+ this.store = store;
+ },
+
+
+ set : function(name, value){
+ var encode = Ext.isPrimitive(value) ? String : Ext.encode;
+ if(encode(this.data[name]) == encode(value)) {
+ return;
+ }
+ this.dirty = true;
+ if(!this.modified){
+ this.modified = {};
+ }
+ if(this.modified[name] === undefined){
+ this.modified[name] = this.data[name];
+ }
+ this.data[name] = value;
+ if(!this.editing){
+ this.afterEdit();
+ }
+ },
+
+
+ afterEdit : function(){
+ if (this.store != undefined && typeof this.store.afterEdit == "function") {
+ this.store.afterEdit(this);
+ }
+ },
+
+
+ afterReject : function(){
+ if(this.store){
+ this.store.afterReject(this);
+ }
+ },
+
+
+ afterCommit : function(){
+ if(this.store){
+ this.store.afterCommit(this);
+ }
+ },
+
+
+ get : function(name){
+ return this.data[name];
+ },
+
+
+ beginEdit : function(){
+ this.editing = true;
+ this.modified = this.modified || {};
+ },
+
+
+ cancelEdit : function(){
+ this.editing = false;
+ delete this.modified;
+ },
+
+
+ endEdit : function(){
+ this.editing = false;
+ if(this.dirty){
+ this.afterEdit();
+ }
+ },
+
+
+ reject : function(silent){
+ var m = this.modified;
+ for(var n in m){
+ if(typeof m[n] != "function"){
+ this.data[n] = m[n];
+ }
+ }
+ this.dirty = false;
+ delete this.modified;
+ this.editing = false;
+ if(silent !== true){
+ this.afterReject();
+ }
+ },
+
+
+ commit : function(silent){
+ this.dirty = false;
+ delete this.modified;
+ this.editing = false;
+ if(silent !== true){
+ this.afterCommit();
+ }
+ },
+
+
+ getChanges : function(){
+ var m = this.modified, cs = {};
+ for(var n in m){
+ if(m.hasOwnProperty(n)){
+ cs[n] = this.data[n];
+ }
+ }
+ return cs;
+ },
+
+
+ hasError : function(){
+ return this.error !== null;
+ },
+
+
+ clearError : function(){
+ this.error = null;
+ },
+
+
+ copy : function(newId) {
+ return new this.constructor(Ext.apply({}, this.data), newId || this.id);
+ },
+
+
+ isModified : function(fieldName){
+ return !!(this.modified && this.modified.hasOwnProperty(fieldName));
+ },
+
+
+ isValid : function() {
+ return this.fields.find(function(f) {
+ return (f.allowBlank === false && Ext.isEmpty(this.data[f.name])) ? true : false;
+ },this) ? false : true;
+ },
+
+
+ markDirty : function(){
+ this.dirty = true;
+ if(!this.modified){
+ this.modified = {};
+ }
+ this.fields.each(function(f) {
+ this.modified[f.name] = this.data[f.name];
+ },this);
+ }
+};
+
+Ext.StoreMgr = Ext.apply(new Ext.util.MixedCollection(), {
+
+
+
+ register : function(){
+ for(var i = 0, s; (s = arguments[i]); i++){
+ this.add(s);
+ }
+ },
+
+
+ unregister : function(){
+ for(var i = 0, s; (s = arguments[i]); i++){
+ this.remove(this.lookup(s));
+ }
+ },
+
+
+ lookup : function(id){
+ if(Ext.isArray(id)){
+ var fields = ['field1'], expand = !Ext.isArray(id[0]);
+ if(!expand){
+ for(var i = 2, len = id[0].length; i <= len; ++i){
+ fields.push('field' + i);
+ }
+ }
+ return new Ext.data.ArrayStore({
+ fields: fields,
+ data: id,
+ expandData: expand,
+ autoDestroy: true,
+ autoCreated: true
+
+ });
+ }
+ return Ext.isObject(id) ? (id.events ? id : Ext.create(id, 'store')) : this.get(id);
+ },
+
+
+ getKey : function(o){
+ return o.storeId;
+ }
+});
+Ext.data.Store = Ext.extend(Ext.util.Observable, {
+
+
+
+
+
+
+
+ writer : undefined,
+
+
+
+ remoteSort : false,
+
+
+ autoDestroy : false,
+
+
+ pruneModifiedRecords : false,
+
+
+ lastOptions : null,
+
+
+ autoSave : true,
+
+
+ batch : true,
+
+
+ restful: false,
+
+
+ paramNames : undefined,
+
+
+ defaultParamNames : {
+ start : 'start',
+ limit : 'limit',
+ sort : 'sort',
+ dir : 'dir'
+ },
+
+ isDestroyed: false,
+ hasMultiSort: false,
+
+
+ batchKey : '_ext_batch_',
+
+ constructor : function(config){
+
+
+
+
+ this.data = new Ext.util.MixedCollection(false);
+ this.data.getKey = function(o){
+ return o.id;
+ };
+
+
+
+ this.removed = [];
+
+ if(config && config.data){
+ this.inlineData = config.data;
+ delete config.data;
+ }
+
+ Ext.apply(this, config);
+
+
+ this.baseParams = Ext.isObject(this.baseParams) ? this.baseParams : {};
+
+ this.paramNames = Ext.applyIf(this.paramNames || {}, this.defaultParamNames);
+
+ if((this.url || this.api) && !this.proxy){
+ this.proxy = new Ext.data.HttpProxy({url: this.url, api: this.api});
+ }
+
+ if (this.restful === true && this.proxy) {
+
+
+ this.batch = false;
+ Ext.data.Api.restify(this.proxy);
+ }
+
+ if(this.reader){
+ if(!this.recordType){
+ this.recordType = this.reader.recordType;
+ }
+ if(this.reader.onMetaChange){
+ this.reader.onMetaChange = this.reader.onMetaChange.createSequence(this.onMetaChange, this);
+ }
+ if (this.writer) {
+ if (this.writer instanceof(Ext.data.DataWriter) === false) {
+ this.writer = this.buildWriter(this.writer);
+ }
+ this.writer.meta = this.reader.meta;
+ this.pruneModifiedRecords = true;
+ }
+ }
+
+
+
+ if(this.recordType){
+
+ this.fields = this.recordType.prototype.fields;
+ }
+ this.modified = [];
+
+ this.addEvents(
+
+ 'datachanged',
+
+ 'metachange',
+
+ 'add',
+
+ 'remove',
+
+ 'update',
+
+ 'clear',
+
+ 'exception',
+
+ 'beforeload',
+
+ 'load',
+
+ 'loadexception',
+
+ 'beforewrite',
+
+ 'write',
+
+ 'beforesave',
+
+ 'save'
+
+ );
+
+ if(this.proxy){
+
+ this.relayEvents(this.proxy, ['loadexception', 'exception']);
+ }
+
+ if (this.writer) {
+ this.on({
+ scope: this,
+ add: this.createRecords,
+ remove: this.destroyRecord,
+ update: this.updateRecord,
+ clear: this.onClear
+ });
+ }
+
+ this.sortToggle = {};
+ if(this.sortField){
+ this.setDefaultSort(this.sortField, this.sortDir);
+ }else if(this.sortInfo){
+ this.setDefaultSort(this.sortInfo.field, this.sortInfo.direction);
+ }
+
+ Ext.data.Store.superclass.constructor.call(this);
+
+ if(this.id){
+ this.storeId = this.id;
+ delete this.id;
+ }
+ if(this.storeId){
+ Ext.StoreMgr.register(this);
+ }
+ if(this.inlineData){
+ this.loadData(this.inlineData);
+ delete this.inlineData;
+ }else if(this.autoLoad){
+ this.load.defer(10, this, [
+ typeof this.autoLoad == 'object' ?
+ this.autoLoad : undefined]);
+ }
+
+ this.batchCounter = 0;
+ this.batches = {};
+ },
+
+
+ buildWriter : function(config) {
+ var klass = undefined,
+ type = (config.format || 'json').toLowerCase();
+ switch (type) {
+ case 'json':
+ klass = Ext.data.JsonWriter;
+ break;
+ case 'xml':
+ klass = Ext.data.XmlWriter;
+ break;
+ default:
+ klass = Ext.data.JsonWriter;
+ }
+ return new klass(config);
+ },
+
+
+ destroy : function(){
+ if(!this.isDestroyed){
+ if(this.storeId){
+ Ext.StoreMgr.unregister(this);
+ }
+ this.clearData();
+ this.data = null;
+ Ext.destroy(this.proxy);
+ this.reader = this.writer = null;
+ this.purgeListeners();
+ this.isDestroyed = true;
+ }
+ },
+
+
+ add : function(records) {
+ var i, len, record, index;
+
+ records = [].concat(records);
+ if (records.length < 1) {
+ return;
+ }
+
+ for (i = 0, len = records.length; i < len; i++) {
+ record = records[i];
+
+ record.join(this);
+
+ if (record.dirty || record.phantom) {
+ this.modified.push(record);
+ }
+ }
+
+ index = this.data.length;
+ this.data.addAll(records);
+
+ if (this.snapshot) {
+ this.snapshot.addAll(records);
+ }
+
+ this.fireEvent('add', this, records, index);
+ },
+
+
+ addSorted : function(record){
+ var index = this.findInsertIndex(record);
+ this.insert(index, record);
+ },
+
+
+ doUpdate: function(rec){
+ var id = rec.id;
+
+ this.getById(id).join(null);
+
+ this.data.replace(id, rec);
+ if (this.snapshot) {
+ this.snapshot.replace(id, rec);
+ }
+ rec.join(this);
+ this.fireEvent('update', this, rec, Ext.data.Record.COMMIT);
+ },
+
+
+ remove : function(record){
+ if(Ext.isArray(record)){
+ Ext.each(record, function(r){
+ this.remove(r);
+ }, this);
+ return;
+ }
+ var index = this.data.indexOf(record);
+ if(index > -1){
+ record.join(null);
+ this.data.removeAt(index);
+ }
+ if(this.pruneModifiedRecords){
+ this.modified.remove(record);
+ }
+ if(this.snapshot){
+ this.snapshot.remove(record);
+ }
+ if(index > -1){
+ this.fireEvent('remove', this, record, index);
+ }
+ },
+
+
+ removeAt : function(index){
+ this.remove(this.getAt(index));
+ },
+
+
+ removeAll : function(silent){
+ var items = [];
+ this.each(function(rec){
+ items.push(rec);
+ });
+ this.clearData();
+ if(this.snapshot){
+ this.snapshot.clear();
+ }
+ if(this.pruneModifiedRecords){
+ this.modified = [];
+ }
+ if (silent !== true) {
+ this.fireEvent('clear', this, items);
+ }
+ },
+
+
+ onClear: function(store, records){
+ Ext.each(records, function(rec, index){
+ this.destroyRecord(this, rec, index);
+ }, this);
+ },
+
+
+ insert : function(index, records) {
+ var i, len, record;
+
+ records = [].concat(records);
+ for (i = 0, len = records.length; i < len; i++) {
+ record = records[i];
+
+ this.data.insert(index + i, record);
+ record.join(this);
+
+ if (record.dirty || record.phantom) {
+ this.modified.push(record);
+ }
+ }
+
+ if (this.snapshot) {
+ this.snapshot.addAll(records);
+ }
+
+ this.fireEvent('add', this, records, index);
+ },
+
+
+ indexOf : function(record){
+ return this.data.indexOf(record);
+ },
+
+
+ indexOfId : function(id){
+ return this.data.indexOfKey(id);
+ },
+
+
+ getById : function(id){
+ return (this.snapshot || this.data).key(id);
+ },
+
+
+ getAt : function(index){
+ return this.data.itemAt(index);
+ },
+
+
+ getRange : function(start, end){
+ return this.data.getRange(start, end);
+ },
+
+
+ storeOptions : function(o){
+ o = Ext.apply({}, o);
+ delete o.callback;
+ delete o.scope;
+ this.lastOptions = o;
+ },
+
+
+ clearData: function(){
+ this.data.each(function(rec) {
+ rec.join(null);
+ });
+ this.data.clear();
+ },
+
+
+ load : function(options) {
+ options = Ext.apply({}, options);
+ this.storeOptions(options);
+ if(this.sortInfo && this.remoteSort){
+ var pn = this.paramNames;
+ options.params = Ext.apply({}, options.params);
+ options.params[pn.sort] = this.sortInfo.field;
+ options.params[pn.dir] = this.sortInfo.direction;
+ }
+ try {
+ return this.execute('read', null, options);
+ } catch(e) {
+ this.handleException(e);
+ return false;
+ }
+ },
+
+
+ updateRecord : function(store, record, action) {
+ if (action == Ext.data.Record.EDIT && this.autoSave === true && (!record.phantom || (record.phantom && record.isValid()))) {
+ this.save();
+ }
+ },
+
+
+ createRecords : function(store, records, index) {
+ var modified = this.modified,
+ length = records.length,
+ record, i;
+
+ for (i = 0; i < length; i++) {
+ record = records[i];
+
+ if (record.phantom && record.isValid()) {
+ record.markDirty();
+
+ if (modified.indexOf(record) == -1) {
+ modified.push(record);
+ }
+ }
+ }
+ if (this.autoSave === true) {
+ this.save();
+ }
+ },
+
+
+ destroyRecord : function(store, record, index) {
+ if (this.modified.indexOf(record) != -1) {
+ this.modified.remove(record);
+ }
+ if (!record.phantom) {
+ this.removed.push(record);
+
+
+
+
+ record.lastIndex = index;
+
+ if (this.autoSave === true) {
+ this.save();
+ }
+ }
+ },
+
+
+ execute : function(action, rs, options, batch) {
+
+ if (!Ext.data.Api.isAction(action)) {
+ throw new Ext.data.Api.Error('execute', action);
+ }
+
+ options = Ext.applyIf(options||{}, {
+ params: {}
+ });
+ if(batch !== undefined){
+ this.addToBatch(batch);
+ }
+
+
+ var doRequest = true;
+
+ if (action === 'read') {
+ doRequest = this.fireEvent('beforeload', this, options);
+ Ext.applyIf(options.params, this.baseParams);
+ }
+ else {
+
+
+ if (this.writer.listful === true && this.restful !== true) {
+ rs = (Ext.isArray(rs)) ? rs : [rs];
+ }
+
+ else if (Ext.isArray(rs) && rs.length == 1) {
+ rs = rs.shift();
+ }
+
+ if ((doRequest = this.fireEvent('beforewrite', this, action, rs, options)) !== false) {
+ this.writer.apply(options.params, this.baseParams, action, rs);
+ }
+ }
+ if (doRequest !== false) {
+
+ if (this.writer && this.proxy.url && !this.proxy.restful && !Ext.data.Api.hasUniqueUrl(this.proxy, action)) {
+ options.params.xaction = action;
+ }
+
+
+
+
+
+ this.proxy.request(Ext.data.Api.actions[action], rs, options.params, this.reader, this.createCallback(action, rs, batch), this, options);
+ }
+ return doRequest;
+ },
+
+
+ save : function() {
+ if (!this.writer) {
+ throw new Ext.data.Store.Error('writer-undefined');
+ }
+
+ var queue = [],
+ len,
+ trans,
+ batch,
+ data = {},
+ i;
+
+ if(this.removed.length){
+ queue.push(['destroy', this.removed]);
+ }
+
+
+ var rs = [].concat(this.getModifiedRecords());
+ if(rs.length){
+
+ var phantoms = [];
+ for(i = rs.length-1; i >= 0; i--){
+ if(rs[i].phantom === true){
+ var rec = rs.splice(i, 1).shift();
+ if(rec.isValid()){
+ phantoms.push(rec);
+ }
+ }else if(!rs[i].isValid()){
+ rs.splice(i,1);
+ }
+ }
+
+ if(phantoms.length){
+ queue.push(['create', phantoms]);
+ }
+
+
+ if(rs.length){
+ queue.push(['update', rs]);
+ }
+ }
+ len = queue.length;
+ if(len){
+ batch = ++this.batchCounter;
+ for(i = 0; i < len; ++i){
+ trans = queue[i];
+ data[trans[0]] = trans[1];
+ }
+ if(this.fireEvent('beforesave', this, data) !== false){
+ for(i = 0; i < len; ++i){
+ trans = queue[i];
+ this.doTransaction(trans[0], trans[1], batch);
+ }
+ return batch;
+ }
+ }
+ return -1;
+ },
+
+
+ doTransaction : function(action, rs, batch) {
+ function transaction(records) {
+ try{
+ this.execute(action, records, undefined, batch);
+ }catch (e){
+ this.handleException(e);
+ }
+ }
+ if(this.batch === false){
+ for(var i = 0, len = rs.length; i < len; i++){
+ transaction.call(this, rs[i]);
+ }
+ }else{
+ transaction.call(this, rs);
+ }
+ },
+
+
+ addToBatch : function(batch){
+ var b = this.batches,
+ key = this.batchKey + batch,
+ o = b[key];
+
+ if(!o){
+ b[key] = o = {
+ id: batch,
+ count: 0,
+ data: {}
+ };
+ }
+ ++o.count;
+ },
+
+ removeFromBatch : function(batch, action, data){
+ var b = this.batches,
+ key = this.batchKey + batch,
+ o = b[key],
+ arr;
+
+
+ if(o){
+ arr = o.data[action] || [];
+ o.data[action] = arr.concat(data);
+ if(o.count === 1){
+ data = o.data;
+ delete b[key];
+ this.fireEvent('save', this, batch, data);
+ }else{
+ --o.count;
+ }
+ }
+ },
+
+
+
+ createCallback : function(action, rs, batch) {
+ var actions = Ext.data.Api.actions;
+ return (action == 'read') ? this.loadRecords : function(data, response, success) {
+
+ this['on' + Ext.util.Format.capitalize(action) + 'Records'](success, rs, [].concat(data));
+
+ if (success === true) {
+ this.fireEvent('write', this, action, data, response, rs);
+ }
+ this.removeFromBatch(batch, action, data);
+ };
+ },
+
+
+
+
+ clearModified : function(rs) {
+ if (Ext.isArray(rs)) {
+ for (var n=rs.length-1;n>=0;n--) {
+ this.modified.splice(this.modified.indexOf(rs[n]), 1);
+ }
+ } else {
+ this.modified.splice(this.modified.indexOf(rs), 1);
+ }
+ },
+
+
+ reMap : function(record) {
+ if (Ext.isArray(record)) {
+ for (var i = 0, len = record.length; i < len; i++) {
+ this.reMap(record[i]);
+ }
+ } else {
+ delete this.data.map[record._phid];
+ this.data.map[record.id] = record;
+ var index = this.data.keys.indexOf(record._phid);
+ this.data.keys.splice(index, 1, record.id);
+ delete record._phid;
+ }
+ },
+
+
+ onCreateRecords : function(success, rs, data) {
+ if (success === true) {
+ try {
+ this.reader.realize(rs, data);
+ }
+ catch (e) {
+ this.handleException(e);
+ if (Ext.isArray(rs)) {
+
+ this.onCreateRecords(success, rs, data);
+ }
+ }
+ }
+ },
+
+
+ onUpdateRecords : function(success, rs, data) {
+ if (success === true) {
+ try {
+ this.reader.update(rs, data);
+ } catch (e) {
+ this.handleException(e);
+ if (Ext.isArray(rs)) {
+
+ this.onUpdateRecords(success, rs, data);
+ }
+ }
+ }
+ },
+
+
+ onDestroyRecords : function(success, rs, data) {
+
+ rs = (rs instanceof Ext.data.Record) ? [rs] : [].concat(rs);
+ for (var i=0,len=rs.length;i<len;i++) {
+ this.removed.splice(this.removed.indexOf(rs[i]), 1);
+ }
+ if (success === false) {
+
+
+ for (i=rs.length-1;i>=0;i--) {
+ this.insert(rs[i].lastIndex, rs[i]);
+ }
+ }
+ },
+
+
+ handleException : function(e) {
+
+ Ext.handleError(e);
+ },
+
+
+ reload : function(options){
+ this.load(Ext.applyIf(options||{}, this.lastOptions));
+ },
+
+
+
+ loadRecords : function(o, options, success){
+ var i, len;
+
+ if (this.isDestroyed === true) {
+ return;
+ }
+ if(!o || success === false){
+ if(success !== false){
+ this.fireEvent('load', this, [], options);
+ }
+ if(options.callback){
+ options.callback.call(options.scope || this, [], options, false, o);
+ }
+ return;
+ }
+ var r = o.records, t = o.totalRecords || r.length;
+ if(!options || options.add !== true){
+ if(this.pruneModifiedRecords){
+ this.modified = [];
+ }
+ for(i = 0, len = r.length; i < len; i++){
+ r[i].join(this);
+ }
+ if(this.snapshot){
+ this.data = this.snapshot;
+ delete this.snapshot;
+ }
+ this.clearData();
+ this.data.addAll(r);
+ this.totalLength = t;
+ this.applySort();
+ this.fireEvent('datachanged', this);
+ }else{
+ var toAdd = [],
+ rec,
+ cnt = 0;
+ for(i = 0, len = r.length; i < len; ++i){
+ rec = r[i];
+ if(this.indexOfId(rec.id) > -1){
+ this.doUpdate(rec);
+ }else{
+ toAdd.push(rec);
+ ++cnt;
+ }
+ }
+ this.totalLength = Math.max(t, this.data.length + cnt);
+ this.add(toAdd);
+ }
+ this.fireEvent('load', this, r, options);
+ if(options.callback){
+ options.callback.call(options.scope || this, r, options, true);
+ }
+ },
+
+
+ loadData : function(o, append){
+ var r = this.reader.readRecords(o);
+ this.loadRecords(r, {add: append}, true);
+ },
+
+
+ getCount : function(){
+ return this.data.length || 0;
+ },
+
+
+ getTotalCount : function(){
+ return this.totalLength || 0;
+ },
+
+
+ getSortState : function(){
+ return this.sortInfo;
+ },
+
+
+ applySort : function(){
+ if ((this.sortInfo || this.multiSortInfo) && !this.remoteSort) {
+ this.sortData();
+ }
+ },
+
+
+ sortData : function() {
+ var sortInfo = this.hasMultiSort ? this.multiSortInfo : this.sortInfo,
+ direction = sortInfo.direction || "ASC",
+ sorters = sortInfo.sorters,
+ sortFns = [];
+
+
+ if (!this.hasMultiSort) {
+ sorters = [{direction: direction, field: sortInfo.field}];
+ }
+
+
+ for (var i=0, j = sorters.length; i < j; i++) {
+ sortFns.push(this.createSortFunction(sorters[i].field, sorters[i].direction));
+ }
+
+ if (sortFns.length == 0) {
+ return;
+ }
+
+
+
+ var directionModifier = direction.toUpperCase() == "DESC" ? -1 : 1;
+
+
+ var fn = function(r1, r2) {
+ var result = sortFns[0].call(this, r1, r2);
+
+
+ if (sortFns.length > 1) {
+ for (var i=1, j = sortFns.length; i < j; i++) {
+ result = result || sortFns[i].call(this, r1, r2);
+ }
+ }
+
+ return directionModifier * result;
+ };
+
+
+ this.data.sort(direction, fn);
+ if (this.snapshot && this.snapshot != this.data) {
+ this.snapshot.sort(direction, fn);
+ }
+ },
+
+
+ createSortFunction: function(field, direction) {
+ direction = direction || "ASC";
+ var directionModifier = direction.toUpperCase() == "DESC" ? -1 : 1;
+
+ var sortType = this.fields.get(field).sortType;
+
+
+
+ return function(r1, r2) {
+ var v1 = sortType(r1.data[field]),
+ v2 = sortType(r2.data[field]);
+
+ return directionModifier * (v1 > v2 ? 1 : (v1 < v2 ? -1 : 0));
+ };
+ },
+
+
+ setDefaultSort : function(field, dir) {
+ dir = dir ? dir.toUpperCase() : 'ASC';
+ this.sortInfo = {field: field, direction: dir};
+ this.sortToggle[field] = dir;
+ },
+
+
+ sort : function(fieldName, dir) {
+ if (Ext.isArray(arguments[0])) {
+ return this.multiSort.call(this, fieldName, dir);
+ } else {
+ return this.singleSort(fieldName, dir);
+ }
+ },
+
+
+ singleSort: function(fieldName, dir) {
+ var field = this.fields.get(fieldName);
+ if (!field) {
+ return false;
+ }
+
+ var name = field.name,
+ sortInfo = this.sortInfo || null,
+ sortToggle = this.sortToggle ? this.sortToggle[name] : null;
+
+ if (!dir) {
+ if (sortInfo && sortInfo.field == name) {
+ dir = (this.sortToggle[name] || 'ASC').toggle('ASC', 'DESC');
+ } else {
+ dir = field.sortDir;
+ }
+ }
+
+ this.sortToggle[name] = dir;
+ this.sortInfo = {field: name, direction: dir};
+ this.hasMultiSort = false;
+
+ if (this.remoteSort) {
+ if (!this.load(this.lastOptions)) {
+ if (sortToggle) {
+ this.sortToggle[name] = sortToggle;
+ }
+ if (sortInfo) {
+ this.sortInfo = sortInfo;
+ }
+ }
+ } else {
+ this.applySort();
+ this.fireEvent('datachanged', this);
+ }
+ return true;
+ },
+
+
+ multiSort: function(sorters, direction) {
+ this.hasMultiSort = true;
+ direction = direction || "ASC";
+
+
+ if (this.multiSortInfo && direction == this.multiSortInfo.direction) {
+ direction = direction.toggle("ASC", "DESC");
+ }
+
+
+ this.multiSortInfo = {
+ sorters : sorters,
+ direction: direction
+ };
+
+ if (this.remoteSort) {
+ this.singleSort(sorters[0].field, sorters[0].direction);
+
+ } else {
+ this.applySort();
+ this.fireEvent('datachanged', this);
+ }
+ },
+
+
+ each : function(fn, scope){
+ this.data.each(fn, scope);
+ },
+
+
+ getModifiedRecords : function(){
+ return this.modified;
+ },
+
+
+ sum : function(property, start, end){
+ var rs = this.data.items, v = 0;
+ start = start || 0;
+ end = (end || end === 0) ? end : rs.length-1;
+
+ for(var i = start; i <= end; i++){
+ v += (rs[i].data[property] || 0);
+ }
+ return v;
+ },
+
+
+ createFilterFn : function(property, value, anyMatch, caseSensitive, exactMatch){
+ if(Ext.isEmpty(value, false)){
+ return false;
+ }
+ value = this.data.createValueMatcher(value, anyMatch, caseSensitive, exactMatch);
+ return function(r) {
+ return value.test(r.data[property]);
+ };
+ },
+
+
+ createMultipleFilterFn: function(filters) {
+ return function(record) {
+ var isMatch = true;
+
+ for (var i=0, j = filters.length; i < j; i++) {
+ var filter = filters[i],
+ fn = filter.fn,
+ scope = filter.scope;
+
+ isMatch = isMatch && fn.call(scope, record);
+ }
+
+ return isMatch;
+ };
+ },
+
+
+ filter : function(property, value, anyMatch, caseSensitive, exactMatch){
+ var fn;
+
+ if (Ext.isObject(property)) {
+ property = [property];
+ }
+
+ if (Ext.isArray(property)) {
+ var filters = [];
+
+
+ for (var i=0, j = property.length; i < j; i++) {
+ var filter = property[i],
+ func = filter.fn,
+ scope = filter.scope || this;
+
+
+ if (!Ext.isFunction(func)) {
+ func = this.createFilterFn(filter.property, filter.value, filter.anyMatch, filter.caseSensitive, filter.exactMatch);
+ }
+
+ filters.push({fn: func, scope: scope});
+ }
+
+ fn = this.createMultipleFilterFn(filters);
+ } else {
+
+ fn = this.createFilterFn(property, value, anyMatch, caseSensitive, exactMatch);
+ }
+
+ return fn ? this.filterBy(fn) : this.clearFilter();
+ },
+
+
+ filterBy : function(fn, scope){
+ this.snapshot = this.snapshot || this.data;
+ this.data = this.queryBy(fn, scope || this);
+ this.fireEvent('datachanged', this);
+ },
+
+
+ clearFilter : function(suppressEvent){
+ if(this.isFiltered()){
+ this.data = this.snapshot;
+ delete this.snapshot;
+ if(suppressEvent !== true){
+ this.fireEvent('datachanged', this);
+ }
+ }
+ },
+
+
+ isFiltered : function(){
+ return !!this.snapshot && this.snapshot != this.data;
+ },
+
+
+ query : function(property, value, anyMatch, caseSensitive){
+ var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
+ return fn ? this.queryBy(fn) : this.data.clone();
+ },
+
+
+ queryBy : function(fn, scope){
+ var data = this.snapshot || this.data;
+ return data.filterBy(fn, scope||this);
+ },
+
+
+ find : function(property, value, start, anyMatch, caseSensitive){
+ var fn = this.createFilterFn(property, value, anyMatch, caseSensitive);
+ return fn ? this.data.findIndexBy(fn, null, start) : -1;
+ },
+
+
+ findExact: function(property, value, start){
+ return this.data.findIndexBy(function(rec){
+ return rec.get(property) === value;
+ }, this, start);
+ },
+
+
+ findBy : function(fn, scope, start){
+ return this.data.findIndexBy(fn, scope, start);
+ },
+
+
+ collect : function(dataIndex, allowNull, bypassFilter){
+ var d = (bypassFilter === true && this.snapshot) ?
+ this.snapshot.items : this.data.items;
+ var v, sv, r = [], l = {};
+ for(var i = 0, len = d.length; i < len; i++){
+ v = d[i].data[dataIndex];
+ sv = String(v);
+ if((allowNull || !Ext.isEmpty(v)) && !l[sv]){
+ l[sv] = true;
+ r[r.length] = v;
+ }
+ }
+ return r;
+ },
+
+
+ afterEdit : function(record){
+ if(this.modified.indexOf(record) == -1){
+ this.modified.push(record);
+ }
+ this.fireEvent('update', this, record, Ext.data.Record.EDIT);
+ },
+
+
+ afterReject : function(record){
+ this.modified.remove(record);
+ this.fireEvent('update', this, record, Ext.data.Record.REJECT);
+ },
+
+
+ afterCommit : function(record){
+ this.modified.remove(record);
+ this.fireEvent('update', this, record, Ext.data.Record.COMMIT);
+ },
+
+
+ commitChanges : function(){
+ var modified = this.modified.slice(0),
+ length = modified.length,
+ i;
+
+ for (i = 0; i < length; i++){
+ modified[i].commit();
+ }
+
+ this.modified = [];
+ this.removed = [];
+ },
+
+
+ rejectChanges : function() {
+ var modified = this.modified.slice(0),
+ removed = this.removed.slice(0).reverse(),
+ mLength = modified.length,
+ rLength = removed.length,
+ i;
+
+ for (i = 0; i < mLength; i++) {
+ modified[i].reject();
+ }
+
+ for (i = 0; i < rLength; i++) {
+ this.insert(removed[i].lastIndex || 0, removed[i]);
+ removed[i].reject();
+ }
+
+ this.modified = [];
+ this.removed = [];
+ },
+
+
+ onMetaChange : function(meta){
+ this.recordType = this.reader.recordType;
+ this.fields = this.recordType.prototype.fields;
+ delete this.snapshot;
+ if(this.reader.meta.sortInfo){
+ this.sortInfo = this.reader.meta.sortInfo;
+ }else if(this.sortInfo && !this.fields.get(this.sortInfo.field)){
+ delete this.sortInfo;
+ }
+ if(this.writer){
+ this.writer.meta = this.reader.meta;
+ }
+ this.modified = [];
+ this.fireEvent('metachange', this, this.reader.meta);
+ },
+
+
+ findInsertIndex : function(record){
+ this.suspendEvents();
+ var data = this.data.clone();
+ this.data.add(record);
+ this.applySort();
+ var index = this.data.indexOf(record);
+ this.data = data;
+ this.resumeEvents();
+ return index;
+ },
+
+
+ setBaseParam : function (name, value){
+ this.baseParams = this.baseParams || {};
+ this.baseParams[name] = value;
+ }
+});
+
+Ext.reg('store', Ext.data.Store);
+
+
+Ext.data.Store.Error = Ext.extend(Ext.Error, {
+ name: 'Ext.data.Store'
+});
+Ext.apply(Ext.data.Store.Error.prototype, {
+ lang: {
+ 'writer-undefined' : 'Attempted to execute a write-action without a DataWriter installed.'
+ }
+});
+
+Ext.data.Field = Ext.extend(Object, {
+
+ constructor : function(config){
+ if(Ext.isString(config)){
+ config = {name: config};
+ }
+ Ext.apply(this, config);
+
+ var types = Ext.data.Types,
+ st = this.sortType,
+ t;
+
+ if(this.type){
+ if(Ext.isString(this.type)){
+ this.type = Ext.data.Types[this.type.toUpperCase()] || types.AUTO;
+ }
+ }else{
+ this.type = types.AUTO;
+ }
+
+
+ if(Ext.isString(st)){
+ this.sortType = Ext.data.SortTypes[st];
+ }else if(Ext.isEmpty(st)){
+ this.sortType = this.type.sortType;
+ }
+
+ if(!this.convert){
+ this.convert = this.type.convert;
+ }
+ },
+
+
+
+
+
+ dateFormat: null,
+
+
+ useNull: false,
+
+
+ defaultValue: "",
+
+ mapping: null,
+
+ sortType : null,
+
+ sortDir : "ASC",
+
+ allowBlank : true
+});
+
+Ext.data.DataReader = function(meta, recordType){
+
+ this.meta = meta;
+
+ this.recordType = Ext.isArray(recordType) ?
+ Ext.data.Record.create(recordType) : recordType;
+
+
+ if (this.recordType){
+ this.buildExtractors();
+ }
+};
+
+Ext.data.DataReader.prototype = {
+
+
+ getTotal: Ext.emptyFn,
+
+ getRoot: Ext.emptyFn,
+
+ getMessage: Ext.emptyFn,
+
+ getSuccess: Ext.emptyFn,
+
+ getId: Ext.emptyFn,
+
+ buildExtractors : Ext.emptyFn,
+
+ extractValues : Ext.emptyFn,
+
+
+ realize: function(rs, data){
+ if (Ext.isArray(rs)) {
+ for (var i = rs.length - 1; i >= 0; i--) {
+
+ if (Ext.isArray(data)) {
+ this.realize(rs.splice(i,1).shift(), data.splice(i,1).shift());
+ }
+ else {
+
+
+ this.realize(rs.splice(i,1).shift(), data);
+ }
+ }
+ }
+ else {
+
+ if (Ext.isArray(data) && data.length == 1) {
+ data = data.shift();
+ }
+ if (!this.isData(data)) {
+
+
+ throw new Ext.data.DataReader.Error('realize', rs);
+ }
+ rs.phantom = false;
+ rs._phid = rs.id;
+ rs.id = this.getId(data);
+ rs.data = data;
+
+ rs.commit();
+ rs.store.reMap(rs);
+ }
+ },
+
+
+ update : function(rs, data) {
+ if (Ext.isArray(rs)) {
+ for (var i=rs.length-1; i >= 0; i--) {
+ if (Ext.isArray(data)) {
+ this.update(rs.splice(i,1).shift(), data.splice(i,1).shift());
+ }
+ else {
+
+
+ this.update(rs.splice(i,1).shift(), data);
+ }
+ }
+ }
+ else {
+
+ if (Ext.isArray(data) && data.length == 1) {
+ data = data.shift();
+ }
+ if (this.isData(data)) {
+ rs.data = Ext.apply(rs.data, data);
+ }
+ rs.commit();
+ }
+ },
+
+
+ extractData : function(root, returnRecords) {
+
+ var rawName = (this instanceof Ext.data.JsonReader) ? 'json' : 'node';
+
+ var rs = [];
+
+
+
+ if (this.isData(root) && !(this instanceof Ext.data.XmlReader)) {
+ root = [root];
+ }
+ var f = this.recordType.prototype.fields,
+ fi = f.items,
+ fl = f.length,
+ rs = [];
+ if (returnRecords === true) {
+ var Record = this.recordType;
+ for (var i = 0; i < root.length; i++) {
+ var n = root[i];
+ var record = new Record(this.extractValues(n, fi, fl), this.getId(n));
+ record[rawName] = n;
+ rs.push(record);
+ }
+ }
+ else {
+ for (var i = 0; i < root.length; i++) {
+ var data = this.extractValues(root[i], fi, fl);
+ data[this.meta.idProperty] = this.getId(root[i]);
+ rs.push(data);
+ }
+ }
+ return rs;
+ },
+
+
+ isData : function(data) {
+ return (data && Ext.isObject(data) && !Ext.isEmpty(this.getId(data))) ? true : false;
+ },
+
+
+ onMetaChange : function(meta){
+ delete this.ef;
+ this.meta = meta;
+ this.recordType = Ext.data.Record.create(meta.fields);
+ this.buildExtractors();
+ }
+};
+
+
+Ext.data.DataReader.Error = Ext.extend(Ext.Error, {
+ constructor : function(message, arg) {
+ this.arg = arg;
+ Ext.Error.call(this, message);
+ },
+ name: 'Ext.data.DataReader'
+});
+Ext.apply(Ext.data.DataReader.Error.prototype, {
+ lang : {
+ 'update': "#update received invalid data from server. Please see docs for DataReader#update and review your DataReader configuration.",
+ 'realize': "#realize was called with invalid remote-data. Please see the docs for DataReader#realize and review your DataReader configuration.",
+ 'invalid-response': "#readResponse received an invalid response from the server."
+ }
+});
+
+Ext.data.DataWriter = function(config){
+ Ext.apply(this, config);
+};
+Ext.data.DataWriter.prototype = {
+
+
+ writeAllFields : false,
+
+ listful : false,
+
+
+ apply : function(params, baseParams, action, rs) {
+ var data = [],
+ renderer = action + 'Record';
+
+ if (Ext.isArray(rs)) {
+ Ext.each(rs, function(rec){
+ data.push(this[renderer](rec));
+ }, this);
+ }
+ else if (rs instanceof Ext.data.Record) {
+ data = this[renderer](rs);
+ }
+ this.render(params, baseParams, data);
+ },
+
+
+ render : Ext.emptyFn,
+
+
+ updateRecord : Ext.emptyFn,
+
+
+ createRecord : Ext.emptyFn,
+
+
+ destroyRecord : Ext.emptyFn,
+
+
+ toHash : function(rec, config) {
+ var map = rec.fields.map,
+ data = {},
+ raw = (this.writeAllFields === false && rec.phantom === false) ? rec.getChanges() : rec.data,
+ m;
+ Ext.iterate(raw, function(prop, value){
+ if((m = map[prop])){
+ data[m.mapping ? m.mapping : m.name] = value;
+ }
+ });
+
+
+
+ if (rec.phantom) {
+ if (rec.fields.containsKey(this.meta.idProperty) && Ext.isEmpty(rec.data[this.meta.idProperty])) {
+ delete data[this.meta.idProperty];
+ }
+ } else {
+ data[this.meta.idProperty] = rec.id;
+ }
+ return data;
+ },
+
+
+ toArray : function(data) {
+ var fields = [];
+ Ext.iterate(data, function(k, v) {fields.push({name: k, value: v});},this);
+ return fields;
+ }
+};
+Ext.data.DataProxy = function(conn){
+
+
+ conn = conn || {};
+
+
+
+
+
+ this.api = conn.api;
+ this.url = conn.url;
+ this.restful = conn.restful;
+ this.listeners = conn.listeners;
+
+
+ this.prettyUrls = conn.prettyUrls;
+
+
+
+ this.addEvents(
+
+ 'exception',
+
+ 'beforeload',
+
+ 'load',
+
+ 'loadexception',
+
+ 'beforewrite',
+
+ 'write'
+ );
+ Ext.data.DataProxy.superclass.constructor.call(this);
+
+
+ try {
+ Ext.data.Api.prepare(this);
+ } catch (e) {
+ if (e instanceof Ext.data.Api.Error) {
+ e.toConsole();
+ }
+ }
+
+ Ext.data.DataProxy.relayEvents(this, ['beforewrite', 'write', 'exception']);
+};
+
+Ext.extend(Ext.data.DataProxy, Ext.util.Observable, {
+
+ restful: false,
+
+
+ setApi : function() {
+ if (arguments.length == 1) {
+ var valid = Ext.data.Api.isValid(arguments[0]);
+ if (valid === true) {
+ this.api = arguments[0];
+ }
+ else {
+ throw new Ext.data.Api.Error('invalid', valid);
+ }
+ }
+ else if (arguments.length == 2) {
+ if (!Ext.data.Api.isAction(arguments[0])) {
+ throw new Ext.data.Api.Error('invalid', arguments[0]);
+ }
+ this.api[arguments[0]] = arguments[1];
+ }
+ Ext.data.Api.prepare(this);
+ },
+
+
+ isApiAction : function(action) {
+ return (this.api[action]) ? true : false;
+ },
+
+
+ request : function(action, rs, params, reader, callback, scope, options) {
+ if (!this.api[action] && !this.load) {
+ throw new Ext.data.DataProxy.Error('action-undefined', action);
+ }
+ params = params || {};
+ if ((action === Ext.data.Api.actions.read) ? this.fireEvent("beforeload", this, params) : this.fireEvent("beforewrite", this, action, rs, params) !== false) {
+ this.doRequest.apply(this, arguments);
+ }
+ else {
+ callback.call(scope || this, null, options, false);
+ }
+ },
+
+
+
+ load : null,
+
+
+ doRequest : function(action, rs, params, reader, callback, scope, options) {
+
+
+
+ this.load(params, reader, callback, scope, options);
+ },
+
+
+ onRead : Ext.emptyFn,
+
+ onWrite : Ext.emptyFn,
+
+ buildUrl : function(action, record) {
+ record = record || null;
+
+
+
+
+ var url = (this.conn && this.conn.url) ? this.conn.url : (this.api[action]) ? this.api[action].url : this.url;
+ if (!url) {
+ throw new Ext.data.Api.Error('invalid-url', action);
+ }
+
+
+
+
+
+
+
+ var provides = null;
+ var m = url.match(/(.*)(\.json|\.xml|\.html)$/);
+ if (m) {
+ provides = m[2];
+ url = m[1];
+ }
+
+ if ((this.restful === true || this.prettyUrls === true) && record instanceof Ext.data.Record && !record.phantom) {
+ url += '/' + record.id;
+ }
+ return (provides === null) ? url : url + provides;
+ },
+
+
+ destroy: function(){
+ this.purgeListeners();
+ }
+});
+
+
+
+Ext.apply(Ext.data.DataProxy, Ext.util.Observable.prototype);
+Ext.util.Observable.call(Ext.data.DataProxy);
+
+
+Ext.data.DataProxy.Error = Ext.extend(Ext.Error, {
+ constructor : function(message, arg) {
+ this.arg = arg;
+ Ext.Error.call(this, message);
+ },
+ name: 'Ext.data.DataProxy'
+});
+Ext.apply(Ext.data.DataProxy.Error.prototype, {
+ lang: {
+ 'action-undefined': "DataProxy attempted to execute an API-action but found an undefined url / function. Please review your Proxy url/api-configuration.",
+ 'api-invalid': 'Recieved an invalid API-configuration. Please ensure your proxy API-configuration contains only the actions from Ext.data.Api.actions.'
+ }
+});
+
+
+
+Ext.data.Request = function(params) {
+ Ext.apply(this, params);
+};
+Ext.data.Request.prototype = {
+
+ action : undefined,
+
+ rs : undefined,
+
+ params: undefined,
+
+ callback : Ext.emptyFn,
+
+ scope : undefined,
+
+ reader : undefined
+};
+
+Ext.data.Response = function(params) {
+ Ext.apply(this, params);
+};
+Ext.data.Response.prototype = {
+
+ action: undefined,
+
+ success : undefined,
+
+ message : undefined,
+
+ data: undefined,
+
+ raw: undefined,
+
+ records: undefined
+};
+
+Ext.data.ScriptTagProxy = function(config){
+ Ext.apply(this, config);
+
+ Ext.data.ScriptTagProxy.superclass.constructor.call(this, config);
+
+ this.head = document.getElementsByTagName("head")[0];
+
+
+};
+
+Ext.data.ScriptTagProxy.TRANS_ID = 1000;
+
+Ext.extend(Ext.data.ScriptTagProxy, Ext.data.DataProxy, {
+
+
+ timeout : 30000,
+
+ callbackParam : "callback",
+
+ nocache : true,
+
+
+ doRequest : function(action, rs, params, reader, callback, scope, arg) {
+ var p = Ext.urlEncode(Ext.apply(params, this.extraParams));
+
+ var url = this.buildUrl(action, rs);
+ if (!url) {
+ throw new Ext.data.Api.Error('invalid-url', url);
+ }
+ url = Ext.urlAppend(url, p);
+
+ if(this.nocache){
+ url = Ext.urlAppend(url, '_dc=' + (new Date().getTime()));
+ }
+ var transId = ++Ext.data.ScriptTagProxy.TRANS_ID;
+ var trans = {
+ id : transId,
+ action: action,
+ cb : "stcCallback"+transId,
+ scriptId : "stcScript"+transId,
+ params : params,
+ arg : arg,
+ url : url,
+ callback : callback,
+ scope : scope,
+ reader : reader
+ };
+ window[trans.cb] = this.createCallback(action, rs, trans);
+ url += String.format("&{0}={1}", this.callbackParam, trans.cb);
+ if(this.autoAbort !== false){
+ this.abort();
+ }
+
+ trans.timeoutId = this.handleFailure.defer(this.timeout, this, [trans]);
+
+ var script = document.createElement("script");
+ script.setAttribute("src", url);
+ script.setAttribute("type", "text/javascript");
+ script.setAttribute("id", trans.scriptId);
+ this.head.appendChild(script);
+
+ this.trans = trans;
+ },
+
+
+ createCallback : function(action, rs, trans) {
+ var self = this;
+ return function(res) {
+ self.trans = false;
+ self.destroyTrans(trans, true);
+ if (action === Ext.data.Api.actions.read) {
+ self.onRead.call(self, action, trans, res);
+ } else {
+ self.onWrite.call(self, action, trans, res, rs);
+ }
+ };
+ },
+
+ onRead : function(action, trans, res) {
+ var result;
+ try {
+ result = trans.reader.readRecords(res);
+ }catch(e){
+
+ this.fireEvent("loadexception", this, trans, res, e);
+
+ this.fireEvent('exception', this, 'response', action, trans, res, e);
+ trans.callback.call(trans.scope||window, null, trans.arg, false);
+ return;
+ }
+ if (result.success === false) {
+
+ this.fireEvent('loadexception', this, trans, res);
+
+ this.fireEvent('exception', this, 'remote', action, trans, res, null);
+ } else {
+ this.fireEvent("load", this, res, trans.arg);
+ }
+ trans.callback.call(trans.scope||window, result, trans.arg, result.success);
+ },
+
+ onWrite : function(action, trans, response, rs) {
+ var reader = trans.reader;
+ try {
+
+ var res = reader.readResponse(action, response);
+ } catch (e) {
+ this.fireEvent('exception', this, 'response', action, trans, res, e);
+ trans.callback.call(trans.scope||window, null, res, false);
+ return;
+ }
+ if(!res.success === true){
+ this.fireEvent('exception', this, 'remote', action, trans, res, rs);
+ trans.callback.call(trans.scope||window, null, res, false);
+ return;
+ }
+ this.fireEvent("write", this, action, res.data, res, rs, trans.arg );
+ trans.callback.call(trans.scope||window, res.data, res, true);
+ },
+
+
+ isLoading : function(){
+ return this.trans ? true : false;
+ },
+
+
+ abort : function(){
+ if(this.isLoading()){
+ this.destroyTrans(this.trans);
+ }
+ },
+
+
+ destroyTrans : function(trans, isLoaded){
+ this.head.removeChild(document.getElementById(trans.scriptId));
+ clearTimeout(trans.timeoutId);
+ if(isLoaded){
+ window[trans.cb] = undefined;
+ try{
+ delete window[trans.cb];
+ }catch(e){}
+ }else{
+
+ window[trans.cb] = function(){
+ window[trans.cb] = undefined;
+ try{
+ delete window[trans.cb];
+ }catch(e){}
+ };
+ }
+ },
+
+
+ handleFailure : function(trans){
+ this.trans = false;
+ this.destroyTrans(trans, false);
+ if (trans.action === Ext.data.Api.actions.read) {
+
+ this.fireEvent("loadexception", this, null, trans.arg);
+ }
+
+ this.fireEvent('exception', this, 'response', trans.action, {
+ response: null,
+ options: trans.arg
+ });
+ trans.callback.call(trans.scope||window, null, trans.arg, false);
+ },
+
+
+ destroy: function(){
+ this.abort();
+ Ext.data.ScriptTagProxy.superclass.destroy.call(this);
+ }
+});
+Ext.data.HttpProxy = function(conn){
+ Ext.data.HttpProxy.superclass.constructor.call(this, conn);
+
+
+ this.conn = conn;
+
+
+
+
+
+ this.conn.url = null;
+
+ this.useAjax = !conn || !conn.events;
+
+
+ var actions = Ext.data.Api.actions;
+ this.activeRequest = {};
+ for (var verb in actions) {
+ this.activeRequest[actions[verb]] = undefined;
+ }
+};
+
+Ext.extend(Ext.data.HttpProxy, Ext.data.DataProxy, {
+
+ getConnection : function() {
+ return this.useAjax ? Ext.Ajax : this.conn;
+ },
+
+
+ setUrl : function(url, makePermanent) {
+ this.conn.url = url;
+ if (makePermanent === true) {
+ this.url = url;
+ this.api = null;
+ Ext.data.Api.prepare(this);
+ }
+ },
+
+
+ doRequest : function(action, rs, params, reader, cb, scope, arg) {
+ var o = {
+ method: (this.api[action]) ? this.api[action]['method'] : undefined,
+ request: {
+ callback : cb,
+ scope : scope,
+ arg : arg
+ },
+ reader: reader,
+ callback : this.createCallback(action, rs),
+ scope: this
+ };
+
+
+
+ if (params.jsonData) {
+ o.jsonData = params.jsonData;
+ } else if (params.xmlData) {
+ o.xmlData = params.xmlData;
+ } else {
+ o.params = params || {};
+ }
+
+
+
+ this.conn.url = this.buildUrl(action, rs);
+
+ if(this.useAjax){
+
+ Ext.applyIf(o, this.conn);
+
+
+ if (action == Ext.data.Api.actions.read && this.activeRequest[action]) {
+ Ext.Ajax.abort(this.activeRequest[action]);
+ }
+ this.activeRequest[action] = Ext.Ajax.request(o);
+ }else{
+ this.conn.request(o);
+ }
+
+ this.conn.url = null;
+ },
+
+
+ createCallback : function(action, rs) {
+ return function(o, success, response) {
+ this.activeRequest[action] = undefined;
+ if (!success) {
+ if (action === Ext.data.Api.actions.read) {
+
+
+ this.fireEvent('loadexception', this, o, response);
+ }
+ this.fireEvent('exception', this, 'response', action, o, response);
+ o.request.callback.call(o.request.scope, null, o.request.arg, false);
+ return;
+ }
+ if (action === Ext.data.Api.actions.read) {
+ this.onRead(action, o, response);
+ } else {
+ this.onWrite(action, o, response, rs);
+ }
+ };
+ },
+
+
+ onRead : function(action, o, response) {
+ var result;
+ try {
+ result = o.reader.read(response);
+ }catch(e){
+
+
+ this.fireEvent('loadexception', this, o, response, e);
+
+ this.fireEvent('exception', this, 'response', action, o, response, e);
+ o.request.callback.call(o.request.scope, null, o.request.arg, false);
+ return;
+ }
+ if (result.success === false) {
+
+
+ this.fireEvent('loadexception', this, o, response);
+
+
+ var res = o.reader.readResponse(action, response);
+ this.fireEvent('exception', this, 'remote', action, o, res, null);
+ }
+ else {
+ this.fireEvent('load', this, o, o.request.arg);
+ }
+
+
+
+ o.request.callback.call(o.request.scope, result, o.request.arg, result.success);
+ },
+
+ onWrite : function(action, o, response, rs) {
+ var reader = o.reader;
+ var res;
+ try {
+ res = reader.readResponse(action, response);
+ } catch (e) {
+ this.fireEvent('exception', this, 'response', action, o, response, e);
+ o.request.callback.call(o.request.scope, null, o.request.arg, false);
+ return;
+ }
+ if (res.success === true) {
+ this.fireEvent('write', this, action, res.data, res, rs, o.request.arg);
+ } else {
+ this.fireEvent('exception', this, 'remote', action, o, res, rs);
+ }
+
+
+
+ o.request.callback.call(o.request.scope, res.data, res, res.success);
+ },
+
+
+ destroy: function(){
+ if(!this.useAjax){
+ this.conn.abort();
+ }else if(this.activeRequest){
+ var actions = Ext.data.Api.actions;
+ for (var verb in actions) {
+ if(this.activeRequest[actions[verb]]){
+ Ext.Ajax.abort(this.activeRequest[actions[verb]]);
+ }
+ }
+ }
+ Ext.data.HttpProxy.superclass.destroy.call(this);
+ }
+});
+Ext.data.MemoryProxy = function(data){
+
+ var api = {};
+ api[Ext.data.Api.actions.read] = true;
+ Ext.data.MemoryProxy.superclass.constructor.call(this, {
+ api: api
+ });
+ this.data = data;
+};
+
+Ext.extend(Ext.data.MemoryProxy, Ext.data.DataProxy, {
+
+
+
+ doRequest : function(action, rs, params, reader, callback, scope, arg) {
+
+ params = params || {};
+ var result;
+ try {
+ result = reader.readRecords(this.data);
+ }catch(e){
+
+ this.fireEvent("loadexception", this, null, arg, e);
+
+ this.fireEvent('exception', this, 'response', action, arg, null, e);
+ callback.call(scope, null, arg, false);
+ return;
+ }
+ callback.call(scope, result, arg, true);
+ }
+});
+Ext.data.Types = new function(){
+ var st = Ext.data.SortTypes;
+ Ext.apply(this, {
+
+ stripRe: /[\$,%]/g,
+
+
+ AUTO: {
+ convert: function(v){ return v; },
+ sortType: st.none,
+ type: 'auto'
+ },
+
+
+ STRING: {
+ convert: function(v){ return (v === undefined || v === null) ? '' : String(v); },
+ sortType: st.asUCString,
+ type: 'string'
+ },
+
+
+ INT: {
+ convert: function(v){
+ return v !== undefined && v !== null && v !== '' ?
+ parseInt(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
+ },
+ sortType: st.none,
+ type: 'int'
+ },
+
+
+ FLOAT: {
+ convert: function(v){
+ return v !== undefined && v !== null && v !== '' ?
+ parseFloat(String(v).replace(Ext.data.Types.stripRe, ''), 10) : (this.useNull ? null : 0);
+ },
+ sortType: st.none,
+ type: 'float'
+ },
+
+
+ BOOL: {
+ convert: function(v){ return v === true || v === 'true' || v == 1; },
+ sortType: st.none,
+ type: 'bool'
+ },
+
+
+ DATE: {
+ convert: function(v){
+ var df = this.dateFormat;
+ if(!v){
+ return null;
+ }
+ if(Ext.isDate(v)){
+ return v;
+ }
+ if(df){
+ if(df == 'timestamp'){
+ return new Date(v*1000);
+ }
+ if(df == 'time'){
+ return new Date(parseInt(v, 10));
+ }
+ return Date.parseDate(v, df);
+ }
+ var parsed = Date.parse(v);
+ return parsed ? new Date(parsed) : null;
+ },
+ sortType: st.asDate,
+ type: 'date'
+ }
+ });
+
+ Ext.apply(this, {
+
+ BOOLEAN: this.BOOL,
+
+ INTEGER: this.INT,
+
+ NUMBER: this.FLOAT
+ });
+};
+Ext.data.JsonWriter = Ext.extend(Ext.data.DataWriter, {
+
+ encode : true,
+
+ encodeDelete: false,
+
+ constructor : function(config){
+ Ext.data.JsonWriter.superclass.constructor.call(this, config);
+ },
+
+
+ render : function(params, baseParams, data) {
+ if (this.encode === true) {
+
+ Ext.apply(params, baseParams);
+ params[this.meta.root] = Ext.encode(data);
+ } else {
+
+ var jdata = Ext.apply({}, baseParams);
+ jdata[this.meta.root] = data;
+ params.jsonData = jdata;
+ }
+ },
+
+ createRecord : function(rec) {
+ return this.toHash(rec);
+ },
+
+ updateRecord : function(rec) {
+ return this.toHash(rec);
+
+ },
+
+ destroyRecord : function(rec){
+ if(this.encodeDelete){
+ var data = {};
+ data[this.meta.idProperty] = rec.id;
+ return data;
+ }else{
+ return rec.id;
+ }
+ }
+});
+Ext.data.JsonReader = function(meta, recordType){
+ meta = meta || {};
+
+
+
+
+ Ext.applyIf(meta, {
+ idProperty: 'id',
+ successProperty: 'success',
+ totalProperty: 'total'
+ });
+
+ Ext.data.JsonReader.superclass.constructor.call(this, meta, recordType || meta.fields);
+};
+Ext.extend(Ext.data.JsonReader, Ext.data.DataReader, {
+
+
+ read : function(response){
+ var json = response.responseText;
+ var o = Ext.decode(json);
+ if(!o) {
+ throw {message: 'JsonReader.read: Json object not found'};
+ }
+ return this.readRecords(o);
+ },
+
+
+
+ readResponse : function(action, response) {
+ var o = (response.responseText !== undefined) ? Ext.decode(response.responseText) : response;
+ if(!o) {
+ throw new Ext.data.JsonReader.Error('response');
+ }
+
+ var root = this.getRoot(o),
+ success = this.getSuccess(o);
+ if (success && action === Ext.data.Api.actions.create) {
+ var def = Ext.isDefined(root);
+ if (def && Ext.isEmpty(root)) {
+ throw new Ext.data.JsonReader.Error('root-empty', this.meta.root);
+ }
+ else if (!def) {
+ throw new Ext.data.JsonReader.Error('root-undefined-response', this.meta.root);
+ }
+ }
+
+
+ var res = new Ext.data.Response({
+ action: action,
+ success: success,
+ data: (root) ? this.extractData(root, false) : [],
+ message: this.getMessage(o),
+ raw: o
+ });
+
+
+ if (Ext.isEmpty(res.success)) {
+ throw new Ext.data.JsonReader.Error('successProperty-response', this.meta.successProperty);
+ }
+ return res;
+ },
+
+
+ readRecords : function(o){
+
+ this.jsonData = o;
+ if(o.metaData){
+ this.onMetaChange(o.metaData);
+ }
+ var s = this.meta, Record = this.recordType,
+ f = Record.prototype.fields, fi = f.items, fl = f.length, v;
+
+ var root = this.getRoot(o), c = root.length, totalRecords = c, success = true;
+ if(s.totalProperty){
+ v = parseInt(this.getTotal(o), 10);
+ if(!isNaN(v)){
+ totalRecords = v;
+ }
+ }
+ if(s.successProperty){
+ v = this.getSuccess(o);
+ if(v === false || v === 'false'){
+ success = false;
+ }
+ }
+
+
+ return {
+ success : success,
+ records : this.extractData(root, true),
+ totalRecords : totalRecords
+ };
+ },
+
+
+ buildExtractors : function() {
+ if(this.ef){
+ return;
+ }
+ var s = this.meta, Record = this.recordType,
+ f = Record.prototype.fields, fi = f.items, fl = f.length;
+
+ if(s.totalProperty) {
+ this.getTotal = this.createAccessor(s.totalProperty);
+ }
+ if(s.successProperty) {
+ this.getSuccess = this.createAccessor(s.successProperty);
+ }
+ if (s.messageProperty) {
+ this.getMessage = this.createAccessor(s.messageProperty);
+ }
+ this.getRoot = s.root ? this.createAccessor(s.root) : function(p){return p;};
+ if (s.id || s.idProperty) {
+ var g = this.createAccessor(s.id || s.idProperty);
+ this.getId = function(rec) {
+ var r = g(rec);
+ return (r === undefined || r === '') ? null : r;
+ };
+ } else {
+ this.getId = function(){return null;};
+ }
+ var ef = [];
+ for(var i = 0; i < fl; i++){
+ f = fi[i];
+ var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping : f.name;
+ ef.push(this.createAccessor(map));
+ }
+ this.ef = ef;
+ },
+
+
+ simpleAccess : function(obj, subsc) {
+ return obj[subsc];
+ },
+
+
+ createAccessor : function(){
+ var re = /[\[\.]/;
+ return function(expr) {
+ if(Ext.isEmpty(expr)){
+ return Ext.emptyFn;
+ }
+ if(Ext.isFunction(expr)){
+ return expr;
+ }
+ var i = String(expr).search(re);
+ if(i >= 0){
+ return new Function('obj', 'return obj' + (i > 0 ? '.' : '') + expr);
+ }
+ return function(obj){
+ return obj[expr];
+ };
+
+ };
+ }(),
+
+
+ extractValues : function(data, items, len) {
+ var f, values = {};
+ for(var j = 0; j < len; j++){
+ f = items[j];
+ var v = this.ef[j](data);
+ values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue, data);
+ }
+ return values;
+ }
+});
+
+
+Ext.data.JsonReader.Error = Ext.extend(Ext.Error, {
+ constructor : function(message, arg) {
+ this.arg = arg;
+ Ext.Error.call(this, message);
+ },
+ name : 'Ext.data.JsonReader'
+});
+Ext.apply(Ext.data.JsonReader.Error.prototype, {
+ lang: {
+ 'response': 'An error occurred while json-decoding your server response',
+ 'successProperty-response': 'Could not locate your "successProperty" in your server response. Please review your JsonReader config to ensure the config-property "successProperty" matches the property in your server-response. See the JsonReader docs.',
+ 'root-undefined-config': 'Your JsonReader was configured without a "root" property. Please review your JsonReader config and make sure to define the root property. See the JsonReader docs.',
+ 'idProperty-undefined' : 'Your JsonReader was configured without an "idProperty" Please review your JsonReader configuration and ensure the "idProperty" is set (e.g.: "id"). See the JsonReader docs.',
+ 'root-empty': 'Data was expected to be returned by the server in the "root" property of the response. Please review your JsonReader configuration to ensure the "root" property matches that returned in the server-response. See JsonReader docs.'
+ }
+});
+
+Ext.data.ArrayReader = Ext.extend(Ext.data.JsonReader, {
+
+
+
+
+ readRecords : function(o){
+ this.arrayData = o;
+ var s = this.meta,
+ sid = s ? Ext.num(s.idIndex, s.id) : null,
+ recordType = this.recordType,
+ fields = recordType.prototype.fields,
+ records = [],
+ success = true,
+ v;
+
+ var root = this.getRoot(o);
+
+ for(var i = 0, len = root.length; i < len; i++) {
+ var n = root[i],
+ values = {},
+ id = ((sid || sid === 0) && n[sid] !== undefined && n[sid] !== "" ? n[sid] : null);
+ for(var j = 0, jlen = fields.length; j < jlen; j++) {
+ var f = fields.items[j],
+ k = f.mapping !== undefined && f.mapping !== null ? f.mapping : j;
+ v = n[k] !== undefined ? n[k] : f.defaultValue;
+ v = f.convert(v, n);
+ values[f.name] = v;
+ }
+ var record = new recordType(values, id);
+ record.json = n;
+ records[records.length] = record;
+ }
+
+ var totalRecords = records.length;
+
+ if(s.totalProperty) {
+ v = parseInt(this.getTotal(o), 10);
+ if(!isNaN(v)) {
+ totalRecords = v;
+ }
+ }
+ if(s.successProperty){
+ v = this.getSuccess(o);
+ if(v === false || v === 'false'){
+ success = false;
+ }
+ }
+
+ return {
+ success : success,
+ records : records,
+ totalRecords : totalRecords
+ };
+ }
+});
+Ext.data.ArrayStore = Ext.extend(Ext.data.Store, {
+
+ constructor: function(config){
+ Ext.data.ArrayStore.superclass.constructor.call(this, Ext.apply(config, {
+ reader: new Ext.data.ArrayReader(config)
+ }));
+ },
+
+ loadData : function(data, append){
+ if(this.expandData === true){
+ var r = [];
+ for(var i = 0, len = data.length; i < len; i++){
+ r[r.length] = [data[i]];
+ }
+ data = r;
+ }
+ Ext.data.ArrayStore.superclass.loadData.call(this, data, append);
+ }
+});
+Ext.reg('arraystore', Ext.data.ArrayStore);
+
+
+Ext.data.SimpleStore = Ext.data.ArrayStore;
+Ext.reg('simplestore', Ext.data.SimpleStore);
+Ext.data.JsonStore = Ext.extend(Ext.data.Store, {
+
+ constructor: function(config){
+ Ext.data.JsonStore.superclass.constructor.call(this, Ext.apply(config, {
+ reader: new Ext.data.JsonReader(config)
+ }));
+ }
+});
+Ext.reg('jsonstore', Ext.data.JsonStore);
+Ext.data.XmlWriter = function(params) {
+ Ext.data.XmlWriter.superclass.constructor.apply(this, arguments);
+
+ this.tpl = (typeof(this.tpl) === 'string') ? new Ext.XTemplate(this.tpl).compile() : this.tpl.compile();
+};
+Ext.extend(Ext.data.XmlWriter, Ext.data.DataWriter, {
+
+ documentRoot: 'xrequest',
+
+ forceDocumentRoot: false,
+
+ root: 'records',
+
+ xmlVersion : '1.0',
+
+ xmlEncoding: 'ISO-8859-15',
+
+
+ tpl: '<tpl for="."><\u003fxml version="{version}" encoding="{encoding}"\u003f><tpl if="documentRoot"><{documentRoot}><tpl for="baseParams"><tpl for="."><{name}>{value}</{name}></tpl></tpl></tpl><tpl if="records.length&gt;1"><{root}></tpl><tpl for="records"><{parent.record}><tpl for="."><{name}>{value}</{name}></tpl></{parent.record}></tpl><tpl if="records.length&gt;1"></{root}></tpl><tpl if="documentRoot"></{documentRoot}></tpl></tpl>',
+
+
+
+ render : function(params, baseParams, data) {
+ baseParams = this.toArray(baseParams);
+ params.xmlData = this.tpl.applyTemplate({
+ version: this.xmlVersion,
+ encoding: this.xmlEncoding,
+ documentRoot: (baseParams.length > 0 || this.forceDocumentRoot === true) ? this.documentRoot : false,
+ record: this.meta.record,
+ root: this.root,
+ baseParams: baseParams,
+ records: (Ext.isArray(data[0])) ? data : [data]
+ });
+ },
+
+
+ createRecord : function(rec) {
+ return this.toArray(this.toHash(rec));
+ },
+
+
+ updateRecord : function(rec) {
+ return this.toArray(this.toHash(rec));
+
+ },
+
+ destroyRecord : function(rec) {
+ var data = {};
+ data[this.meta.idProperty] = rec.id;
+ return this.toArray(data);
+ }
+});
+
+Ext.data.XmlReader = function(meta, recordType){
+ meta = meta || {};
+
+
+ Ext.applyIf(meta, {
+ idProperty: meta.idProperty || meta.idPath || meta.id,
+ successProperty: meta.successProperty || meta.success
+ });
+
+ Ext.data.XmlReader.superclass.constructor.call(this, meta, recordType || meta.fields);
+};
+Ext.extend(Ext.data.XmlReader, Ext.data.DataReader, {
+
+ read : function(response){
+ var doc = response.responseXML;
+ if(!doc) {
+ throw {message: "XmlReader.read: XML Document not available"};
+ }
+ return this.readRecords(doc);
+ },
+
+
+ readRecords : function(doc){
+
+ this.xmlData = doc;
+
+ var root = doc.documentElement || doc,
+ q = Ext.DomQuery,
+ totalRecords = 0,
+ success = true;
+
+ if(this.meta.totalProperty){
+ totalRecords = this.getTotal(root, 0);
+ }
+ if(this.meta.successProperty){
+ success = this.getSuccess(root);
+ }
+
+ var records = this.extractData(q.select(this.meta.record, root), true);
+
+
+ return {
+ success : success,
+ records : records,
+ totalRecords : totalRecords || records.length
+ };
+ },
+
+
+ readResponse : function(action, response) {
+ var q = Ext.DomQuery,
+ doc = response.responseXML,
+ root = doc.documentElement || doc;
+
+
+ var res = new Ext.data.Response({
+ action: action,
+ success : this.getSuccess(root),
+ message: this.getMessage(root),
+ data: this.extractData(q.select(this.meta.record, root) || q.select(this.meta.root, root), false),
+ raw: doc
+ });
+
+ if (Ext.isEmpty(res.success)) {
+ throw new Ext.data.DataReader.Error('successProperty-response', this.meta.successProperty);
+ }
+
+
+ if (action === Ext.data.Api.actions.create) {
+ var def = Ext.isDefined(res.data);
+ if (def && Ext.isEmpty(res.data)) {
+ throw new Ext.data.JsonReader.Error('root-empty', this.meta.root);
+ }
+ else if (!def) {
+ throw new Ext.data.JsonReader.Error('root-undefined-response', this.meta.root);
+ }
+ }
+ return res;
+ },
+
+ getSuccess : function() {
+ return true;
+ },
+
+
+ buildExtractors : function() {
+ if(this.ef){
+ return;
+ }
+ var s = this.meta,
+ Record = this.recordType,
+ f = Record.prototype.fields,
+ fi = f.items,
+ fl = f.length;
+
+ if(s.totalProperty) {
+ this.getTotal = this.createAccessor(s.totalProperty);
+ }
+ if(s.successProperty) {
+ this.getSuccess = this.createAccessor(s.successProperty);
+ }
+ if (s.messageProperty) {
+ this.getMessage = this.createAccessor(s.messageProperty);
+ }
+ this.getRoot = function(res) {
+ return (!Ext.isEmpty(res[this.meta.record])) ? res[this.meta.record] : res[this.meta.root];
+ };
+ if (s.idPath || s.idProperty) {
+ var g = this.createAccessor(s.idPath || s.idProperty);
+ this.getId = function(rec) {
+ var id = g(rec) || rec.id;
+ return (id === undefined || id === '') ? null : id;
+ };
+ } else {
+ this.getId = function(){return null;};
+ }
+ var ef = [];
+ for(var i = 0; i < fl; i++){
+ f = fi[i];
+ var map = (f.mapping !== undefined && f.mapping !== null) ? f.mapping : f.name;
+ ef.push(this.createAccessor(map));
+ }
+ this.ef = ef;
+ },
+
+
+ createAccessor : function(){
+ var q = Ext.DomQuery;
+ return function(key) {
+ if (Ext.isFunction(key)) {
+ return key;
+ }
+ switch(key) {
+ case this.meta.totalProperty:
+ return function(root, def){
+ return q.selectNumber(key, root, def);
+ };
+ break;
+ case this.meta.successProperty:
+ return function(root, def) {
+ var sv = q.selectValue(key, root, true);
+ var success = sv !== false && sv !== 'false';
+ return success;
+ };
+ break;
+ default:
+ return function(root, def) {
+ return q.selectValue(key, root, def);
+ };
+ break;
+ }
+ };
+ }(),
+
+
+ extractValues : function(data, items, len) {
+ var f, values = {};
+ for(var j = 0; j < len; j++){
+ f = items[j];
+ var v = this.ef[j](data);
+ values[f.name] = f.convert((v !== undefined) ? v : f.defaultValue, data);
+ }
+ return values;
+ }
+});
+Ext.data.XmlStore = Ext.extend(Ext.data.Store, {
+
+ constructor: function(config){
+ Ext.data.XmlStore.superclass.constructor.call(this, Ext.apply(config, {
+ reader: new Ext.data.XmlReader(config)
+ }));
+ }
+});
+Ext.reg('xmlstore', Ext.data.XmlStore);
+Ext.data.GroupingStore = Ext.extend(Ext.data.Store, {
+
+
+ constructor: function(config) {
+ config = config || {};
+
+
+
+
+
+ this.hasMultiSort = true;
+ this.multiSortInfo = this.multiSortInfo || {sorters: []};
+
+ var sorters = this.multiSortInfo.sorters,
+ groupField = config.groupField || this.groupField,
+ sortInfo = config.sortInfo || this.sortInfo,
+ groupDir = config.groupDir || this.groupDir;
+
+
+ if(groupField){
+ sorters.push({
+ field : groupField,
+ direction: groupDir
+ });
+ }
+
+
+ if (sortInfo) {
+ sorters.push(sortInfo);
+ }
+
+ Ext.data.GroupingStore.superclass.constructor.call(this, config);
+
+ this.addEvents(
+
+ 'groupchange'
+ );
+
+ this.applyGroupField();
+ },
+
+
+
+ remoteGroup : false,
+
+ groupOnSort:false,
+
+
+ groupDir : 'ASC',
+
+
+ clearGrouping : function(){
+ this.groupField = false;
+
+ if(this.remoteGroup){
+ if(this.baseParams){
+ delete this.baseParams.groupBy;
+ delete this.baseParams.groupDir;
+ }
+ var lo = this.lastOptions;
+ if(lo && lo.params){
+ delete lo.params.groupBy;
+ delete lo.params.groupDir;
+ }
+
+ this.reload();
+ }else{
+ this.sort();
+ this.fireEvent('datachanged', this);
+ }
+ },
+
+
+ groupBy : function(field, forceRegroup, direction) {
+ direction = direction ? (String(direction).toUpperCase() == 'DESC' ? 'DESC' : 'ASC') : this.groupDir;
+
+ if (this.groupField == field && this.groupDir == direction && !forceRegroup) {
+ return;
+ }
+
+
+
+ var sorters = this.multiSortInfo.sorters;
+ if (sorters.length > 0 && sorters[0].field == this.groupField) {
+ sorters.shift();
+ }
+
+ this.groupField = field;
+ this.groupDir = direction;
+ this.applyGroupField();
+
+ var fireGroupEvent = function() {
+ this.fireEvent('groupchange', this, this.getGroupState());
+ };
+
+ if (this.groupOnSort) {
+ this.sort(field, direction);
+ fireGroupEvent.call(this);
+ return;
+ }
+
+ if (this.remoteGroup) {
+ this.on('load', fireGroupEvent, this, {single: true});
+ this.reload();
+ } else {
+ this.sort(sorters);
+ fireGroupEvent.call(this);
+ }
+ },
+
+
+
+ sort : function(fieldName, dir) {
+ if (this.remoteSort) {
+ return Ext.data.GroupingStore.superclass.sort.call(this, fieldName, dir);
+ }
+
+ var sorters = [];
+
+
+ if (Ext.isArray(arguments[0])) {
+ sorters = arguments[0];
+ } else if (fieldName == undefined) {
+
+
+ sorters = this.sortInfo ? [this.sortInfo] : [];
+ } else {
+
+
+ var field = this.fields.get(fieldName);
+ if (!field) return false;
+
+ var name = field.name,
+ sortInfo = this.sortInfo || null,
+ sortToggle = this.sortToggle ? this.sortToggle[name] : null;
+
+ if (!dir) {
+ if (sortInfo && sortInfo.field == name) {
+ dir = (this.sortToggle[name] || 'ASC').toggle('ASC', 'DESC');
+ } else {
+ dir = field.sortDir;
+ }
+ }
+
+ this.sortToggle[name] = dir;
+ this.sortInfo = {field: name, direction: dir};
+
+ sorters = [this.sortInfo];
+ }
+
+
+ if (this.groupField) {
+ sorters.unshift({direction: this.groupDir, field: this.groupField});
+ }
+
+ return this.multiSort.call(this, sorters, dir);
+ },
+
+
+ applyGroupField: function(){
+ if (this.remoteGroup) {
+ if(!this.baseParams){
+ this.baseParams = {};
+ }
+
+ Ext.apply(this.baseParams, {
+ groupBy : this.groupField,
+ groupDir: this.groupDir
+ });
+
+ var lo = this.lastOptions;
+ if (lo && lo.params) {
+ lo.params.groupDir = this.groupDir;
+
+
+ delete lo.params.groupBy;
+ }
+ }
+ },
+
+
+ applyGrouping : function(alwaysFireChange){
+ if(this.groupField !== false){
+ this.groupBy(this.groupField, true, this.groupDir);
+ return true;
+ }else{
+ if(alwaysFireChange === true){
+ this.fireEvent('datachanged', this);
+ }
+ return false;
+ }
+ },
+
+
+ getGroupState : function(){
+ return this.groupOnSort && this.groupField !== false ?
+ (this.sortInfo ? this.sortInfo.field : undefined) : this.groupField;
+ }
+});
+Ext.reg('groupingstore', Ext.data.GroupingStore);
+
+Ext.data.DirectProxy = function(config){
+ Ext.apply(this, config);
+ if(typeof this.paramOrder == 'string'){
+ this.paramOrder = this.paramOrder.split(/[\s,|]/);
+ }
+ Ext.data.DirectProxy.superclass.constructor.call(this, config);
+};
+
+Ext.extend(Ext.data.DirectProxy, Ext.data.DataProxy, {
+
+ paramOrder: undefined,
+
+
+ paramsAsHash: true,
+
+
+ directFn : undefined,
+
+
+ doRequest : function(action, rs, params, reader, callback, scope, options) {
+ var args = [],
+ directFn = this.api[action] || this.directFn;
+
+ switch (action) {
+ case Ext.data.Api.actions.create:
+ args.push(params.jsonData);
+ break;
+ case Ext.data.Api.actions.read:
+
+ if(directFn.directCfg.method.len > 0){
+ if(this.paramOrder){
+ for(var i = 0, len = this.paramOrder.length; i < len; i++){
+ args.push(params[this.paramOrder[i]]);
+ }
+ }else if(this.paramsAsHash){
+ args.push(params);
+ }
+ }
+ break;
+ case Ext.data.Api.actions.update:
+ args.push(params.jsonData);
+ break;
+ case Ext.data.Api.actions.destroy:
+ args.push(params.jsonData);
+ break;
+ }
+
+ var trans = {
+ params : params || {},
+ request: {
+ callback : callback,
+ scope : scope,
+ arg : options
+ },
+ reader: reader
+ };
+
+ args.push(this.createCallback(action, rs, trans), this);
+ directFn.apply(window, args);
+ },
+
+
+ createCallback : function(action, rs, trans) {
+ var me = this;
+ return function(result, res) {
+ if (!res.status) {
+
+ if (action === Ext.data.Api.actions.read) {
+ me.fireEvent("loadexception", me, trans, res, null);
+ }
+ me.fireEvent('exception', me, 'remote', action, trans, res, null);
+ trans.request.callback.call(trans.request.scope, null, trans.request.arg, false);
+ return;
+ }
+ if (action === Ext.data.Api.actions.read) {
+ me.onRead(action, trans, result, res);
+ } else {
+ me.onWrite(action, trans, result, res, rs);
+ }
+ };
+ },
+
+
+ onRead : function(action, trans, result, res) {
+ var records;
+ try {
+ records = trans.reader.readRecords(result);
+ }
+ catch (ex) {
+
+ this.fireEvent("loadexception", this, trans, res, ex);
+
+ this.fireEvent('exception', this, 'response', action, trans, res, ex);
+ trans.request.callback.call(trans.request.scope, null, trans.request.arg, false);
+ return;
+ }
+ this.fireEvent("load", this, res, trans.request.arg);
+ trans.request.callback.call(trans.request.scope, records, trans.request.arg, true);
+ },
+
+ onWrite : function(action, trans, result, res, rs) {
+ var data = trans.reader.extractData(trans.reader.getRoot(result), false);
+ var success = trans.reader.getSuccess(result);
+ success = (success !== false);
+ if (success){
+ this.fireEvent("write", this, action, data, res, rs, trans.request.arg);
+ }else{
+ this.fireEvent('exception', this, 'remote', action, trans, result, rs);
+ }
+ trans.request.callback.call(trans.request.scope, data, res, success);
+ }
+});
+
+Ext.data.DirectStore = Ext.extend(Ext.data.Store, {
+ constructor : function(config){
+
+ var c = Ext.apply({}, {
+ batchTransactions: false
+ }, config);
+ Ext.data.DirectStore.superclass.constructor.call(this, Ext.apply(c, {
+ proxy: Ext.isDefined(c.proxy) ? c.proxy : new Ext.data.DirectProxy(Ext.copyTo({}, c, 'paramOrder,paramsAsHash,directFn,api')),
+ reader: (!Ext.isDefined(c.reader) && c.fields) ? new Ext.data.JsonReader(Ext.copyTo({}, c, 'totalProperty,root,idProperty'), c.fields) : c.reader
+ }));
+ }
+});
+Ext.reg('directstore', Ext.data.DirectStore);
+
+Ext.Direct = Ext.extend(Ext.util.Observable, {
+
+
+
+ exceptions: {
+ TRANSPORT: 'xhr',
+ PARSE: 'parse',
+ LOGIN: 'login',
+ SERVER: 'exception'
+ },
+
+
+ constructor: function(){
+ this.addEvents(
+
+ 'event',
+
+ 'exception'
+ );
+ this.transactions = {};
+ this.providers = {};
+ },
+
+
+ addProvider : function(provider){
+ var a = arguments;
+ if(a.length > 1){
+ for(var i = 0, len = a.length; i < len; i++){
+ this.addProvider(a[i]);
+ }
+ return;
+ }
+
+
+ if(!provider.events){
+ provider = new Ext.Direct.PROVIDERS[provider.type](provider);
+ }
+ provider.id = provider.id || Ext.id();
+ this.providers[provider.id] = provider;
+
+ provider.on('data', this.onProviderData, this);
+ provider.on('exception', this.onProviderException, this);
+
+
+ if(!provider.isConnected()){
+ provider.connect();
+ }
+
+ return provider;
+ },
+
+
+ getProvider : function(id){
+ return this.providers[id];
+ },
+
+ removeProvider : function(id){
+ var provider = id.id ? id : this.providers[id];
+ provider.un('data', this.onProviderData, this);
+ provider.un('exception', this.onProviderException, this);
+ delete this.providers[provider.id];
+ return provider;
+ },
+
+ addTransaction: function(t){
+ this.transactions[t.tid] = t;
+ return t;
+ },
+
+ removeTransaction: function(t){
+ delete this.transactions[t.tid || t];
+ return t;
+ },
+
+ getTransaction: function(tid){
+ return this.transactions[tid.tid || tid];
+ },
+
+ onProviderData : function(provider, e){
+ if(Ext.isArray(e)){
+ for(var i = 0, len = e.length; i < len; i++){
+ this.onProviderData(provider, e[i]);
+ }
+ return;
+ }
+ if(e.name && e.name != 'event' && e.name != 'exception'){
+ this.fireEvent(e.name, e);
+ }else if(e.type == 'exception'){
+ this.fireEvent('exception', e);
+ }
+ this.fireEvent('event', e, provider);
+ },
+
+ createEvent : function(response, extraProps){
+ return new Ext.Direct.eventTypes[response.type](Ext.apply(response, extraProps));
+ }
+});
+
+Ext.Direct = new Ext.Direct();
+
+Ext.Direct.TID = 1;
+Ext.Direct.PROVIDERS = {};
+Ext.Direct.Transaction = function(config){
+ Ext.apply(this, config);
+ this.tid = ++Ext.Direct.TID;
+ this.retryCount = 0;
+};
+Ext.Direct.Transaction.prototype = {
+ send: function(){
+ this.provider.queueTransaction(this);
+ },
+
+ retry: function(){
+ this.retryCount++;
+ this.send();
+ },
+
+ getProvider: function(){
+ return this.provider;
+ }
+};Ext.Direct.Event = function(config){
+ Ext.apply(this, config);
+};
+
+Ext.Direct.Event.prototype = {
+ status: true,
+ getData: function(){
+ return this.data;
+ }
+};
+
+Ext.Direct.RemotingEvent = Ext.extend(Ext.Direct.Event, {
+ type: 'rpc',
+ getTransaction: function(){
+ return this.transaction || Ext.Direct.getTransaction(this.tid);
+ }
+});
+
+Ext.Direct.ExceptionEvent = Ext.extend(Ext.Direct.RemotingEvent, {
+ status: false,
+ type: 'exception'
+});
+
+Ext.Direct.eventTypes = {
+ 'rpc': Ext.Direct.RemotingEvent,
+ 'event': Ext.Direct.Event,
+ 'exception': Ext.Direct.ExceptionEvent
+};
+
+Ext.direct.Provider = Ext.extend(Ext.util.Observable, {
+
+
+
+ priority: 1,
+
+
+
+
+ constructor : function(config){
+ Ext.apply(this, config);
+ this.addEvents(
+
+ 'connect',
+
+ 'disconnect',
+
+ 'data',
+
+ 'exception'
+ );
+ Ext.direct.Provider.superclass.constructor.call(this, config);
+ },
+
+
+ isConnected: function(){
+ return false;
+ },
+
+
+ connect: Ext.emptyFn,
+
+
+ disconnect: Ext.emptyFn
+});
+
+Ext.direct.JsonProvider = Ext.extend(Ext.direct.Provider, {
+ parseResponse: function(xhr){
+ if(!Ext.isEmpty(xhr.responseText)){
+ if(typeof xhr.responseText == 'object'){
+ return xhr.responseText;
+ }
+ return Ext.decode(xhr.responseText);
+ }
+ return null;
+ },
+
+ getEvents: function(xhr){
+ var data = null;
+ try{
+ data = this.parseResponse(xhr);
+ }catch(e){
+ var event = new Ext.Direct.ExceptionEvent({
+ data: e,
+ xhr: xhr,
+ code: Ext.Direct.exceptions.PARSE,
+ message: 'Error parsing json response: \n\n ' + data
+ });
+ return [event];
+ }
+ var events = [];
+ if(Ext.isArray(data)){
+ for(var i = 0, len = data.length; i < len; i++){
+ events.push(Ext.Direct.createEvent(data[i]));
+ }
+ }else{
+ events.push(Ext.Direct.createEvent(data));
+ }
+ return events;
+ }
+});
+Ext.direct.PollingProvider = Ext.extend(Ext.direct.JsonProvider, {
+
+
+ priority: 3,
+
+
+ interval: 3000,
+
+
+
+
+
+
+ constructor : function(config){
+ Ext.direct.PollingProvider.superclass.constructor.call(this, config);
+ this.addEvents(
+
+ 'beforepoll',
+
+ 'poll'
+ );
+ },
+
+
+ isConnected: function(){
+ return !!this.pollTask;
+ },
+
+
+ connect: function(){
+ if(this.url && !this.pollTask){
+ this.pollTask = Ext.TaskMgr.start({
+ run: function(){
+ if(this.fireEvent('beforepoll', this) !== false){
+ if(typeof this.url == 'function'){
+ this.url(this.baseParams);
+ }else{
+ Ext.Ajax.request({
+ url: this.url,
+ callback: this.onData,
+ scope: this,
+ params: this.baseParams
+ });
+ }
+ }
+ },
+ interval: this.interval,
+ scope: this
+ });
+ this.fireEvent('connect', this);
+ }else if(!this.url){
+ throw 'Error initializing PollingProvider, no url configured.';
+ }
+ },
+
+
+ disconnect: function(){
+ if(this.pollTask){
+ Ext.TaskMgr.stop(this.pollTask);
+ delete this.pollTask;
+ this.fireEvent('disconnect', this);
+ }
+ },
+
+
+ onData: function(opt, success, xhr){
+ if(success){
+ var events = this.getEvents(xhr);
+ for(var i = 0, len = events.length; i < len; i++){
+ var e = events[i];
+ this.fireEvent('data', this, e);
+ }
+ }else{
+ var e = new Ext.Direct.ExceptionEvent({
+ data: e,
+ code: Ext.Direct.exceptions.TRANSPORT,
+ message: 'Unable to connect to the server.',
+ xhr: xhr
+ });
+ this.fireEvent('data', this, e);
+ }
+ }
+});
+
+Ext.Direct.PROVIDERS['polling'] = Ext.direct.PollingProvider;
+Ext.direct.RemotingProvider = Ext.extend(Ext.direct.JsonProvider, {
+
+
+
+
+
+
+
+
+
+ enableBuffer: 10,
+
+
+ maxRetries: 1,
+
+
+ timeout: undefined,
+
+ constructor : function(config){
+ Ext.direct.RemotingProvider.superclass.constructor.call(this, config);
+ this.addEvents(
+
+ 'beforecall',
+
+ 'call'
+ );
+ this.namespace = (Ext.isString(this.namespace)) ? Ext.ns(this.namespace) : this.namespace || window;
+ this.transactions = {};
+ this.callBuffer = [];
+ },
+
+
+ initAPI : function(){
+ var o = this.actions;
+ for(var c in o){
+ var cls = this.namespace[c] || (this.namespace[c] = {}),
+ ms = o[c];
+ for(var i = 0, len = ms.length; i < len; i++){
+ var m = ms[i];
+ cls[m.name] = this.createMethod(c, m);
+ }
+ }
+ },
+
+
+ isConnected: function(){
+ return !!this.connected;
+ },
+
+ connect: function(){
+ if(this.url){
+ this.initAPI();
+ this.connected = true;
+ this.fireEvent('connect', this);
+ }else if(!this.url){
+ throw 'Error initializing RemotingProvider, no url configured.';
+ }
+ },
+
+ disconnect: function(){
+ if(this.connected){
+ this.connected = false;
+ this.fireEvent('disconnect', this);
+ }
+ },
+
+ onData: function(opt, success, xhr){
+ if(success){
+ var events = this.getEvents(xhr);
+ for(var i = 0, len = events.length; i < len; i++){
+ var e = events[i],
+ t = this.getTransaction(e);
+ this.fireEvent('data', this, e);
+ if(t){
+ this.doCallback(t, e, true);
+ Ext.Direct.removeTransaction(t);
+ }
+ }
+ }else{
+ var ts = [].concat(opt.ts);
+ for(var i = 0, len = ts.length; i < len; i++){
+ var t = this.getTransaction(ts[i]);
+ if(t && t.retryCount < this.maxRetries){
+ t.retry();
+ }else{
+ var e = new Ext.Direct.ExceptionEvent({
+ data: e,
+ transaction: t,
+ code: Ext.Direct.exceptions.TRANSPORT,
+ message: 'Unable to connect to the server.',
+ xhr: xhr
+ });
+ this.fireEvent('data', this, e);
+ if(t){
+ this.doCallback(t, e, false);
+ Ext.Direct.removeTransaction(t);
+ }
+ }
+ }
+ }
+ },
+
+ getCallData: function(t){
+ return {
+ action: t.action,
+ method: t.method,
+ data: t.data,
+ type: 'rpc',
+ tid: t.tid
+ };
+ },
+
+ doSend : function(data){
+ var o = {
+ url: this.url,
+ callback: this.onData,
+ scope: this,
+ ts: data,
+ timeout: this.timeout
+ }, callData;
+
+ if(Ext.isArray(data)){
+ callData = [];
+ for(var i = 0, len = data.length; i < len; i++){
+ callData.push(this.getCallData(data[i]));
+ }
+ }else{
+ callData = this.getCallData(data);
+ }
+
+ if(this.enableUrlEncode){
+ var params = {};
+ params[Ext.isString(this.enableUrlEncode) ? this.enableUrlEncode : 'data'] = Ext.encode(callData);
+ o.params = params;
+ }else{
+ o.jsonData = callData;
+ }
+ Ext.Ajax.request(o);
+ },
+
+ combineAndSend : function(){
+ var len = this.callBuffer.length;
+ if(len > 0){
+ this.doSend(len == 1 ? this.callBuffer[0] : this.callBuffer);
+ this.callBuffer = [];
+ }
+ },
+
+ queueTransaction: function(t){
+ if(t.form){
+ this.processForm(t);
+ return;
+ }
+ this.callBuffer.push(t);
+ if(this.enableBuffer){
+ if(!this.callTask){
+ this.callTask = new Ext.util.DelayedTask(this.combineAndSend, this);
+ }
+ this.callTask.delay(Ext.isNumber(this.enableBuffer) ? this.enableBuffer : 10);
+ }else{
+ this.combineAndSend();
+ }
+ },
+
+ doCall : function(c, m, args){
+ var data = null, hs = args[m.len], scope = args[m.len+1];
+
+ if(m.len !== 0){
+ data = args.slice(0, m.len);
+ }
+
+ var t = new Ext.Direct.Transaction({
+ provider: this,
+ args: args,
+ action: c,
+ method: m.name,
+ data: data,
+ cb: scope && Ext.isFunction(hs) ? hs.createDelegate(scope) : hs
+ });
+
+ if(this.fireEvent('beforecall', this, t, m) !== false){
+ Ext.Direct.addTransaction(t);
+ this.queueTransaction(t);
+ this.fireEvent('call', this, t, m);
+ }
+ },
+
+ doForm : function(c, m, form, callback, scope){
+ var t = new Ext.Direct.Transaction({
+ provider: this,
+ action: c,
+ method: m.name,
+ args:[form, callback, scope],
+ cb: scope && Ext.isFunction(callback) ? callback.createDelegate(scope) : callback,
+ isForm: true
+ });
+
+ if(this.fireEvent('beforecall', this, t, m) !== false){
+ Ext.Direct.addTransaction(t);
+ var isUpload = String(form.getAttribute("enctype")).toLowerCase() == 'multipart/form-data',
+ params = {
+ extTID: t.tid,
+ extAction: c,
+ extMethod: m.name,
+ extType: 'rpc',
+ extUpload: String(isUpload)
+ };
+
+
+
+ Ext.apply(t, {
+ form: Ext.getDom(form),
+ isUpload: isUpload,
+ params: callback && Ext.isObject(callback.params) ? Ext.apply(params, callback.params) : params
+ });
+ this.fireEvent('call', this, t, m);
+ this.processForm(t);
+ }
+ },
+
+ processForm: function(t){
+ Ext.Ajax.request({
+ url: this.url,
+ params: t.params,
+ callback: this.onData,
+ scope: this,
+ form: t.form,
+ isUpload: t.isUpload,
+ ts: t
+ });
+ },
+
+ createMethod : function(c, m){
+ var f;
+ if(!m.formHandler){
+ f = function(){
+ this.doCall(c, m, Array.prototype.slice.call(arguments, 0));
+ }.createDelegate(this);
+ }else{
+ f = function(form, callback, scope){
+ this.doForm(c, m, form, callback, scope);
+ }.createDelegate(this);
+ }
+ f.directCfg = {
+ action: c,
+ method: m
+ };
+ return f;
+ },
+
+ getTransaction: function(opt){
+ return opt && opt.tid ? Ext.Direct.getTransaction(opt.tid) : null;
+ },
+
+ doCallback: function(t, e){
+ var fn = e.status ? 'success' : 'failure';
+ if(t && t.cb){
+ var hs = t.cb,
+ result = Ext.isDefined(e.result) ? e.result : e.data;
+ if(Ext.isFunction(hs)){
+ hs(result, e);
+ } else{
+ Ext.callback(hs[fn], hs.scope, [result, e]);
+ Ext.callback(hs.callback, hs.scope, [result, e]);
+ }
+ }
+ }
+});
+Ext.Direct.PROVIDERS['remoting'] = Ext.direct.RemotingProvider;
+Ext.Resizable = Ext.extend(Ext.util.Observable, {
+
+ constructor: function(el, config){
+ this.el = Ext.get(el);
+ if(config && config.wrap){
+ config.resizeChild = this.el;
+ this.el = this.el.wrap(typeof config.wrap == 'object' ? config.wrap : {cls:'xresizable-wrap'});
+ this.el.id = this.el.dom.id = config.resizeChild.id + '-rzwrap';
+ this.el.setStyle('overflow', 'hidden');
+ this.el.setPositioning(config.resizeChild.getPositioning());
+ config.resizeChild.clearPositioning();
+ if(!config.width || !config.height){
+ var csize = config.resizeChild.getSize();
+ this.el.setSize(csize.width, csize.height);
+ }
+ if(config.pinned && !config.adjustments){
+ config.adjustments = 'auto';
+ }
+ }
+
+
+ this.proxy = this.el.createProxy({tag: 'div', cls: 'x-resizable-proxy', id: this.el.id + '-rzproxy'}, Ext.getBody());
+ this.proxy.unselectable();
+ this.proxy.enableDisplayMode('block');
+
+ Ext.apply(this, config);
+
+ if(this.pinned){
+ this.disableTrackOver = true;
+ this.el.addClass('x-resizable-pinned');
+ }
+
+ var position = this.el.getStyle('position');
+ if(position != 'absolute' && position != 'fixed'){
+ this.el.setStyle('position', 'relative');
+ }
+ if(!this.handles){
+ this.handles = 's,e,se';
+ if(this.multiDirectional){
+ this.handles += ',n,w';
+ }
+ }
+ if(this.handles == 'all'){
+ this.handles = 'n s e w ne nw se sw';
+ }
+ var hs = this.handles.split(/\s*?[,;]\s*?| /);
+ var ps = Ext.Resizable.positions;
+ for(var i = 0, len = hs.length; i < len; i++){
+ if(hs[i] && ps[hs[i]]){
+ var pos = ps[hs[i]];
+ this[pos] = new Ext.Resizable.Handle(this, pos, this.disableTrackOver, this.transparent, this.handleCls);
+ }
+ }
+
+ this.corner = this.southeast;
+
+ if(this.handles.indexOf('n') != -1 || this.handles.indexOf('w') != -1){
+ this.updateBox = true;
+ }
+
+ this.activeHandle = null;
+
+ if(this.resizeChild){
+ if(typeof this.resizeChild == 'boolean'){
+ this.resizeChild = Ext.get(this.el.dom.firstChild, true);
+ }else{
+ this.resizeChild = Ext.get(this.resizeChild, true);
+ }
+ }
+
+ if(this.adjustments == 'auto'){
+ var rc = this.resizeChild;
+ var hw = this.west, he = this.east, hn = this.north, hs = this.south;
+ if(rc && (hw || hn)){
+ rc.position('relative');
+ rc.setLeft(hw ? hw.el.getWidth() : 0);
+ rc.setTop(hn ? hn.el.getHeight() : 0);
+ }
+ this.adjustments = [
+ (he ? -he.el.getWidth() : 0) + (hw ? -hw.el.getWidth() : 0),
+ (hn ? -hn.el.getHeight() : 0) + (hs ? -hs.el.getHeight() : 0) -1
+ ];
+ }
+
+ if(this.draggable){
+ this.dd = this.dynamic ?
+ this.el.initDD(null) : this.el.initDDProxy(null, {dragElId: this.proxy.id});
+ this.dd.setHandleElId(this.resizeChild ? this.resizeChild.id : this.el.id);
+ if(this.constrainTo){
+ this.dd.constrainTo(this.constrainTo);
+ }
+ }
+
+ this.addEvents(
+
+ 'beforeresize',
+
+ 'resize'
+ );
+
+ if(this.width !== null && this.height !== null){
+ this.resizeTo(this.width, this.height);
+ }else{
+ this.updateChildSize();
+ }
+ if(Ext.isIE){
+ this.el.dom.style.zoom = 1;
+ }
+ Ext.Resizable.superclass.constructor.call(this);
+ },
+
+
+ adjustments : [0, 0],
+
+ animate : false,
+
+
+ disableTrackOver : false,
+
+ draggable: false,
+
+ duration : 0.35,
+
+ dynamic : false,
+
+ easing : 'easeOutStrong',
+
+ enabled : true,
+
+
+ handles : false,
+
+ multiDirectional : false,
+
+ height : null,
+
+ width : null,
+
+ heightIncrement : 0,
+
+ widthIncrement : 0,
+
+ minHeight : 5,
+
+ minWidth : 5,
+
+ maxHeight : 10000,
+
+ maxWidth : 10000,
+
+ minX: 0,
+
+ minY: 0,
+
+ pinned : false,
+
+ preserveRatio : false,
+
+ resizeChild : false,
+
+ transparent: false,
+
+
+
+
+
+
+ resizeTo : function(width, height){
+ this.el.setSize(width, height);
+ this.updateChildSize();
+ this.fireEvent('resize', this, width, height, null);
+ },
+
+
+ startSizing : function(e, handle){
+ this.fireEvent('beforeresize', this, e);
+ if(this.enabled){
+
+ if(!this.overlay){
+ this.overlay = this.el.createProxy({tag: 'div', cls: 'x-resizable-overlay', html: '&#160;'}, Ext.getBody());
+ this.overlay.unselectable();
+ this.overlay.enableDisplayMode('block');
+ this.overlay.on({
+ scope: this,
+ mousemove: this.onMouseMove,
+ mouseup: this.onMouseUp
+ });
+ }
+ this.overlay.setStyle('cursor', handle.el.getStyle('cursor'));
+
+ this.resizing = true;
+ this.startBox = this.el.getBox();
+ this.startPoint = e.getXY();
+ this.offsets = [(this.startBox.x + this.startBox.width) - this.startPoint[0],
+ (this.startBox.y + this.startBox.height) - this.startPoint[1]];
+
+ this.overlay.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));
+ this.overlay.show();
+
+ if(this.constrainTo) {
+ var ct = Ext.get(this.constrainTo);
+ this.resizeRegion = ct.getRegion().adjust(
+ ct.getFrameWidth('t'),
+ ct.getFrameWidth('l'),
+ -ct.getFrameWidth('b'),
+ -ct.getFrameWidth('r')
+ );
+ }
+
+ this.proxy.setStyle('visibility', 'hidden');
+ this.proxy.show();
+ this.proxy.setBox(this.startBox);
+ if(!this.dynamic){
+ this.proxy.setStyle('visibility', 'visible');
+ }
+ }
+ },
+
+
+ onMouseDown : function(handle, e){
+ if(this.enabled){
+ e.stopEvent();
+ this.activeHandle = handle;
+ this.startSizing(e, handle);
+ }
+ },
+
+
+ onMouseUp : function(e){
+ this.activeHandle = null;
+ var size = this.resizeElement();
+ this.resizing = false;
+ this.handleOut();
+ this.overlay.hide();
+ this.proxy.hide();
+ this.fireEvent('resize', this, size.width, size.height, e);
+ },
+
+
+ updateChildSize : function(){
+ if(this.resizeChild){
+ var el = this.el;
+ var child = this.resizeChild;
+ var adj = this.adjustments;
+ if(el.dom.offsetWidth){
+ var b = el.getSize(true);
+ child.setSize(b.width+adj[0], b.height+adj[1]);
+ }
+
+
+
+
+ if(Ext.isIE9m){
+ setTimeout(function(){
+ if(el.dom.offsetWidth){
+ var b = el.getSize(true);
+ child.setSize(b.width+adj[0], b.height+adj[1]);
+ }
+ }, 10);
+ }
+ }
+ },
+
+
+ snap : function(value, inc, min){
+ if(!inc || !value){
+ return value;
+ }
+ var newValue = value;
+ var m = value % inc;
+ if(m > 0){
+ if(m > (inc/2)){
+ newValue = value + (inc-m);
+ }else{
+ newValue = value - m;
+ }
+ }
+ return Math.max(min, newValue);
+ },
+
+
+ resizeElement : function(){
+ var box = this.proxy.getBox();
+ if(this.updateBox){
+ this.el.setBox(box, false, this.animate, this.duration, null, this.easing);
+ }else{
+ this.el.setSize(box.width, box.height, this.animate, this.duration, null, this.easing);
+ }
+ this.updateChildSize();
+ if(!this.dynamic){
+ this.proxy.hide();
+ }
+ if(this.draggable && this.constrainTo){
+ this.dd.resetConstraints();
+ this.dd.constrainTo(this.constrainTo);
+ }
+ return box;
+ },
+
+
+ constrain : function(v, diff, m, mx){
+ if(v - diff < m){
+ diff = v - m;
+ }else if(v - diff > mx){
+ diff = v - mx;
+ }
+ return diff;
+ },
+
+
+ onMouseMove : function(e){
+ if(this.enabled && this.activeHandle){
+ try{
+
+ if(this.resizeRegion && !this.resizeRegion.contains(e.getPoint())) {
+ return;
+ }
+
+
+ var curSize = this.curSize || this.startBox,
+ x = this.startBox.x, y = this.startBox.y,
+ ox = x,
+ oy = y,
+ w = curSize.width,
+ h = curSize.height,
+ ow = w,
+ oh = h,
+ mw = this.minWidth,
+ mh = this.minHeight,
+ mxw = this.maxWidth,
+ mxh = this.maxHeight,
+ wi = this.widthIncrement,
+ hi = this.heightIncrement,
+ eventXY = e.getXY(),
+ diffX = -(this.startPoint[0] - Math.max(this.minX, eventXY[0])),
+ diffY = -(this.startPoint[1] - Math.max(this.minY, eventXY[1])),
+ pos = this.activeHandle.position,
+ tw,
+ th;
+
+ switch(pos){
+ case 'east':
+ w += diffX;
+ w = Math.min(Math.max(mw, w), mxw);
+ break;
+ case 'south':
+ h += diffY;
+ h = Math.min(Math.max(mh, h), mxh);
+ break;
+ case 'southeast':
+ w += diffX;
+ h += diffY;
+ w = Math.min(Math.max(mw, w), mxw);
+ h = Math.min(Math.max(mh, h), mxh);
+ break;
+ case 'north':
+ diffY = this.constrain(h, diffY, mh, mxh);
+ y += diffY;
+ h -= diffY;
+ break;
+ case 'west':
+ diffX = this.constrain(w, diffX, mw, mxw);
+ x += diffX;
+ w -= diffX;
+ break;
+ case 'northeast':
+ w += diffX;
+ w = Math.min(Math.max(mw, w), mxw);
+ diffY = this.constrain(h, diffY, mh, mxh);
+ y += diffY;
+ h -= diffY;
+ break;
+ case 'northwest':
+ diffX = this.constrain(w, diffX, mw, mxw);
+ diffY = this.constrain(h, diffY, mh, mxh);
+ y += diffY;
+ h -= diffY;
+ x += diffX;
+ w -= diffX;
+ break;
+ case 'southwest':
+ diffX = this.constrain(w, diffX, mw, mxw);
+ h += diffY;
+ h = Math.min(Math.max(mh, h), mxh);
+ x += diffX;
+ w -= diffX;
+ break;
+ }
+
+ var sw = this.snap(w, wi, mw);
+ var sh = this.snap(h, hi, mh);
+ if(sw != w || sh != h){
+ switch(pos){
+ case 'northeast':
+ y -= sh - h;
+ break;
+ case 'north':
+ y -= sh - h;
+ break;
+ case 'southwest':
+ x -= sw - w;
+ break;
+ case 'west':
+ x -= sw - w;
+ break;
+ case 'northwest':
+ x -= sw - w;
+ y -= sh - h;
+ break;
+ }
+ w = sw;
+ h = sh;
+ }
+
+ if(this.preserveRatio){
+ switch(pos){
+ case 'southeast':
+ case 'east':
+ h = oh * (w/ow);
+ h = Math.min(Math.max(mh, h), mxh);
+ w = ow * (h/oh);
+ break;
+ case 'south':
+ w = ow * (h/oh);
+ w = Math.min(Math.max(mw, w), mxw);
+ h = oh * (w/ow);
+ break;
+ case 'northeast':
+ w = ow * (h/oh);
+ w = Math.min(Math.max(mw, w), mxw);
+ h = oh * (w/ow);
+ break;
+ case 'north':
+ tw = w;
+ w = ow * (h/oh);
+ w = Math.min(Math.max(mw, w), mxw);
+ h = oh * (w/ow);
+ x += (tw - w) / 2;
+ break;
+ case 'southwest':
+ h = oh * (w/ow);
+ h = Math.min(Math.max(mh, h), mxh);
+ tw = w;
+ w = ow * (h/oh);
+ x += tw - w;
+ break;
+ case 'west':
+ th = h;
+ h = oh * (w/ow);
+ h = Math.min(Math.max(mh, h), mxh);
+ y += (th - h) / 2;
+ tw = w;
+ w = ow * (h/oh);
+ x += tw - w;
+ break;
+ case 'northwest':
+ tw = w;
+ th = h;
+ h = oh * (w/ow);
+ h = Math.min(Math.max(mh, h), mxh);
+ w = ow * (h/oh);
+ y += th - h;
+ x += tw - w;
+ break;
+
+ }
+ }
+ this.proxy.setBounds(x, y, w, h);
+ if(this.dynamic){
+ this.resizeElement();
+ }
+ }catch(ex){}
+ }
+ },
+
+
+ handleOver : function(){
+ if(this.enabled){
+ this.el.addClass('x-resizable-over');
+ }
+ },
+
+
+ handleOut : function(){
+ if(!this.resizing){
+ this.el.removeClass('x-resizable-over');
+ }
+ },
+
+
+ getEl : function(){
+ return this.el;
+ },
+
+
+ getResizeChild : function(){
+ return this.resizeChild;
+ },
+
+
+ destroy : function(removeEl){
+ Ext.destroy(this.dd, this.overlay, this.proxy);
+ this.overlay = null;
+ this.proxy = null;
+
+ var ps = Ext.Resizable.positions;
+ for(var k in ps){
+ if(typeof ps[k] != 'function' && this[ps[k]]){
+ this[ps[k]].destroy();
+ }
+ }
+ if(removeEl){
+ this.el.update('');
+ Ext.destroy(this.el);
+ this.el = null;
+ }
+ this.purgeListeners();
+ },
+
+ syncHandleHeight : function(){
+ var h = this.el.getHeight(true);
+ if(this.west){
+ this.west.el.setHeight(h);
+ }
+ if(this.east){
+ this.east.el.setHeight(h);
+ }
+ }
+});
+
+
+
+Ext.Resizable.positions = {
+ n: 'north', s: 'south', e: 'east', w: 'west', se: 'southeast', sw: 'southwest', nw: 'northwest', ne: 'northeast'
+};
+
+Ext.Resizable.Handle = Ext.extend(Object, {
+ constructor : function(rz, pos, disableTrackOver, transparent, cls){
+ if(!this.tpl){
+
+ var tpl = Ext.DomHelper.createTemplate(
+ {tag: 'div', cls: 'x-resizable-handle x-resizable-handle-{0}'}
+ );
+ tpl.compile();
+ Ext.Resizable.Handle.prototype.tpl = tpl;
+ }
+ this.position = pos;
+ this.rz = rz;
+ this.el = this.tpl.append(rz.el.dom, [this.position], true);
+ this.el.unselectable();
+ if(transparent){
+ this.el.setOpacity(0);
+ }
+ if(!Ext.isEmpty(cls)){
+ this.el.addClass(cls);
+ }
+ this.el.on('mousedown', this.onMouseDown, this);
+ if(!disableTrackOver){
+ this.el.on({
+ scope: this,
+ mouseover: this.onMouseOver,
+ mouseout: this.onMouseOut
+ });
+ }
+ },
+
+
+ afterResize : function(rz){
+
+ },
+
+ onMouseDown : function(e){
+ this.rz.onMouseDown(this, e);
+ },
+
+ onMouseOver : function(e){
+ this.rz.handleOver(this, e);
+ },
+
+ onMouseOut : function(e){
+ this.rz.handleOut(this, e);
+ },
+
+ destroy : function(){
+ Ext.destroy(this.el);
+ this.el = null;
+ }
+});
+
+Ext.Window = Ext.extend(Ext.Panel, {
+
+
+
+
+
+
+
+
+
+
+
+
+ baseCls : 'x-window',
+
+ resizable : true,
+
+ draggable : true,
+
+ closable : true,
+
+ closeAction : 'close',
+
+ constrain : false,
+
+ constrainHeader : false,
+
+ plain : false,
+
+ minimizable : false,
+
+ maximizable : false,
+
+ minHeight : 100,
+
+ minWidth : 200,
+
+ expandOnShow : true,
+
+
+ showAnimDuration: 0.25,
+
+
+ hideAnimDuration: 0.25,
+
+
+ collapsible : false,
+
+
+ initHidden : undefined,
+
+
+ hidden : true,
+
+
+
+
+
+
+ elements : 'header,body',
+
+ frame : true,
+
+ floating : true,
+
+
+ initComponent : function(){
+ this.initTools();
+ Ext.Window.superclass.initComponent.call(this);
+ this.addEvents(
+
+
+
+ 'resize',
+
+ 'maximize',
+
+ 'minimize',
+
+ 'restore'
+ );
+
+ if(Ext.isDefined(this.initHidden)){
+ this.hidden = this.initHidden;
+ }
+ if(this.hidden === false){
+ this.hidden = true;
+ this.show();
+ }
+ },
+
+
+ getState : function(){
+ return Ext.apply(Ext.Window.superclass.getState.call(this) || {}, this.getBox(true));
+ },
+
+
+ onRender : function(ct, position){
+ Ext.Window.superclass.onRender.call(this, ct, position);
+
+ if(this.plain){
+ this.el.addClass('x-window-plain');
+ }
+
+
+ this.focusEl = this.el.createChild({
+ tag: 'a', href:'#', cls:'x-dlg-focus',
+ tabIndex:'-1', html: '&#160;'});
+ this.focusEl.swallowEvent('click', true);
+
+ this.proxy = this.el.createProxy('x-window-proxy');
+ this.proxy.enableDisplayMode('block');
+
+ if(this.modal){
+ this.mask = this.container.createChild({cls:'ext-el-mask'}, this.el.dom);
+ this.mask.enableDisplayMode('block');
+ this.mask.hide();
+ this.mon(this.mask, 'click', this.focus, this);
+ }
+ if(this.maximizable){
+ this.mon(this.header, 'dblclick', this.toggleMaximize, this);
+ }
+ },
+
+
+ initEvents : function(){
+ Ext.Window.superclass.initEvents.call(this);
+ if(this.animateTarget){
+ this.setAnimateTarget(this.animateTarget);
+ }
+
+ if(this.resizable){
+ this.resizer = new Ext.Resizable(this.el, {
+ minWidth: this.minWidth,
+ minHeight:this.minHeight,
+ handles: this.resizeHandles || 'all',
+ pinned: true,
+ resizeElement : this.resizerAction,
+ handleCls: 'x-window-handle'
+ });
+ this.resizer.window = this;
+ this.mon(this.resizer, 'beforeresize', this.beforeResize, this);
+ }
+
+ if(this.draggable){
+ this.header.addClass('x-window-draggable');
+ }
+ this.mon(this.el, 'mousedown', this.toFront, this);
+ this.manager = this.manager || Ext.WindowMgr;
+ this.manager.register(this);
+ if(this.maximized){
+ this.maximized = false;
+ this.maximize();
+ }
+ if(this.closable){
+ var km = this.getKeyMap();
+ km.on(27, this.onEsc, this);
+ km.disable();
+ }
+ },
+
+ initDraggable : function(){
+
+ this.dd = new Ext.Window.DD(this);
+ },
+
+
+ onEsc : function(k, e){
+ if (this.activeGhost) {
+ this.unghost();
+ }
+ e.stopEvent();
+ this[this.closeAction]();
+ },
+
+
+ beforeDestroy : function(){
+ if(this.rendered){
+ this.hide();
+ this.clearAnchor();
+ Ext.destroy(
+ this.focusEl,
+ this.resizer,
+ this.dd,
+ this.proxy,
+ this.mask
+ );
+ }
+ Ext.Window.superclass.beforeDestroy.call(this);
+ },
+
+
+ onDestroy : function(){
+ if(this.manager){
+ this.manager.unregister(this);
+ }
+ Ext.Window.superclass.onDestroy.call(this);
+ },
+
+
+ initTools : function(){
+ if(this.minimizable){
+ this.addTool({
+ id: 'minimize',
+ handler: this.minimize.createDelegate(this, [])
+ });
+ }
+ if(this.maximizable){
+ this.addTool({
+ id: 'maximize',
+ handler: this.maximize.createDelegate(this, [])
+ });
+ this.addTool({
+ id: 'restore',
+ handler: this.restore.createDelegate(this, []),
+ hidden:true
+ });
+ }
+ if(this.closable){
+ this.addTool({
+ id: 'close',
+ handler: this[this.closeAction].createDelegate(this, [])
+ });
+ }
+ },
+
+
+ resizerAction : function(){
+ var box = this.proxy.getBox();
+ this.proxy.hide();
+ this.window.handleResize(box);
+ return box;
+ },
+
+
+ beforeResize : function(){
+ this.resizer.minHeight = Math.max(this.minHeight, this.getFrameHeight() + 40);
+ this.resizer.minWidth = Math.max(this.minWidth, this.getFrameWidth() + 40);
+ this.resizeBox = this.el.getBox();
+ },
+
+
+ updateHandles : function(){
+ if(Ext.isIE9m && this.resizer){
+ this.resizer.syncHandleHeight();
+ this.el.repaint();
+ }
+ },
+
+
+ handleResize : function(box){
+ var rz = this.resizeBox;
+ if(rz.x != box.x || rz.y != box.y){
+ this.updateBox(box);
+ }else{
+ this.setSize(box);
+ if (Ext.isIE6 && Ext.isStrict) {
+ this.doLayout();
+ }
+ }
+ this.focus();
+ this.updateHandles();
+ this.saveState();
+ },
+
+
+ focus : function(){
+ var f = this.focusEl,
+ db = this.defaultButton,
+ t = typeof db,
+ el,
+ ct;
+ if(Ext.isDefined(db)){
+ if(Ext.isNumber(db) && this.fbar){
+ f = this.fbar.items.get(db);
+ }else if(Ext.isString(db)){
+ f = Ext.getCmp(db);
+ }else{
+ f = db;
+ }
+ el = f.getEl();
+ ct = Ext.getDom(this.container);
+ if (el && ct) {
+ if (ct != document.body && !Ext.lib.Region.getRegion(ct).contains(Ext.lib.Region.getRegion(el.dom))){
+ return;
+ }
+ }
+ }
+ f = f || this.focusEl;
+ f.focus.defer(10, f);
+ },
+
+
+ setAnimateTarget : function(el){
+ el = Ext.get(el);
+ this.animateTarget = el;
+ },
+
+
+ beforeShow : function(){
+ delete this.el.lastXY;
+ delete this.el.lastLT;
+ if(this.x === undefined || this.y === undefined){
+ var xy = this.el.getAlignToXY(this.container, 'c-c');
+ var pos = this.el.translatePoints(xy[0], xy[1]);
+ this.x = this.x === undefined? pos.left : this.x;
+ this.y = this.y === undefined? pos.top : this.y;
+ }
+ this.el.setLeftTop(this.x, this.y);
+
+ if(this.expandOnShow){
+ this.expand(false);
+ }
+
+ if(this.modal){
+ Ext.getBody().addClass('x-body-masked');
+ this.mask.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));
+ this.mask.show();
+ }
+ },
+
+
+ show : function(animateTarget, cb, scope){
+ if(!this.rendered){
+ this.render(Ext.getBody());
+ }
+ if(this.hidden === false){
+ this.toFront();
+ return this;
+ }
+ if(this.fireEvent('beforeshow', this) === false){
+ return this;
+ }
+ if(cb){
+ this.on('show', cb, scope, {single:true});
+ }
+ this.hidden = false;
+ if(Ext.isDefined(animateTarget)){
+ this.setAnimateTarget(animateTarget);
+ }
+ this.beforeShow();
+ if(this.animateTarget){
+ this.animShow();
+ }else{
+ this.afterShow();
+ }
+ return this;
+ },
+
+
+ afterShow : function(isAnim){
+ if (this.isDestroyed){
+ return false;
+ }
+ this.proxy.hide();
+ this.el.setStyle('display', 'block');
+ this.el.show();
+ if(this.maximized){
+ this.fitContainer();
+ }
+ if(Ext.isMac && Ext.isGecko2){
+ this.cascade(this.setAutoScroll);
+ }
+
+ if(this.monitorResize || this.modal || this.constrain || this.constrainHeader){
+ Ext.EventManager.onWindowResize(this.onWindowResize, this);
+ }
+ this.doConstrain();
+ this.doLayout();
+ if(this.keyMap){
+ this.keyMap.enable();
+ }
+ this.toFront();
+ this.updateHandles();
+ if(isAnim && (Ext.isIE || Ext.isWebKit)){
+ var sz = this.getSize();
+ this.onResize(sz.width, sz.height);
+ }
+ this.onShow();
+ this.fireEvent('show', this);
+ },
+
+
+ animShow : function(){
+ this.proxy.show();
+ this.proxy.setBox(this.animateTarget.getBox());
+ this.proxy.setOpacity(0);
+ var b = this.getBox();
+ this.el.setStyle('display', 'none');
+ this.proxy.shift(Ext.apply(b, {
+ callback: this.afterShow.createDelegate(this, [true], false),
+ scope: this,
+ easing: 'easeNone',
+ duration: this.showAnimDuration,
+ opacity: 0.5
+ }));
+ },
+
+
+ hide : function(animateTarget, cb, scope){
+ if(this.hidden || this.fireEvent('beforehide', this) === false){
+ return this;
+ }
+ if(cb){
+ this.on('hide', cb, scope, {single:true});
+ }
+ this.hidden = true;
+ if(animateTarget !== undefined){
+ this.setAnimateTarget(animateTarget);
+ }
+ if(this.modal){
+ this.mask.hide();
+ Ext.getBody().removeClass('x-body-masked');
+ }
+ if(this.animateTarget){
+ this.animHide();
+ }else{
+ this.el.hide();
+ this.afterHide();
+ }
+ return this;
+ },
+
+
+ afterHide : function(){
+ this.proxy.hide();
+ if(this.monitorResize || this.modal || this.constrain || this.constrainHeader){
+ Ext.EventManager.removeResizeListener(this.onWindowResize, this);
+ }
+ if(this.keyMap){
+ this.keyMap.disable();
+ }
+ this.onHide();
+ this.fireEvent('hide', this);
+ },
+
+
+ animHide : function(){
+ this.proxy.setOpacity(0.5);
+ this.proxy.show();
+ var tb = this.getBox(false);
+ this.proxy.setBox(tb);
+ this.el.hide();
+ this.proxy.shift(Ext.apply(this.animateTarget.getBox(), {
+ callback: this.afterHide,
+ scope: this,
+ duration: this.hideAnimDuration,
+ easing: 'easeNone',
+ opacity: 0
+ }));
+ },
+
+
+ onShow : Ext.emptyFn,
+
+
+ onHide : Ext.emptyFn,
+
+
+ onWindowResize : function(){
+ if(this.maximized){
+ this.fitContainer();
+ }
+ if(this.modal){
+ this.mask.setSize('100%', '100%');
+ var force = this.mask.dom.offsetHeight;
+ this.mask.setSize(Ext.lib.Dom.getViewWidth(true), Ext.lib.Dom.getViewHeight(true));
+ }
+ this.doConstrain();
+ },
+
+
+ doConstrain : function(){
+ if(this.constrain || this.constrainHeader){
+ var offsets;
+ if(this.constrain){
+ offsets = {
+ right:this.el.shadowOffset,
+ left:this.el.shadowOffset,
+ bottom:this.el.shadowOffset
+ };
+ }else {
+ var s = this.getSize();
+ offsets = {
+ right:-(s.width - 100),
+ bottom:-(s.height - 25 + this.el.getConstrainOffset())
+ };
+ }
+
+ var xy = this.el.getConstrainToXY(this.container, true, offsets);
+ if(xy){
+ this.setPosition(xy[0], xy[1]);
+ }
+ }
+ },
+
+
+ ghost : function(cls){
+ var ghost = this.createGhost(cls);
+ var box = this.getBox(true);
+ ghost.setLeftTop(box.x, box.y);
+ ghost.setWidth(box.width);
+ this.el.hide();
+ this.activeGhost = ghost;
+ return ghost;
+ },
+
+
+ unghost : function(show, matchPosition){
+ if(!this.activeGhost) {
+ return;
+ }
+ if(show !== false){
+ this.el.show();
+ this.focus.defer(10, this);
+ if(Ext.isMac && Ext.isGecko2){
+ this.cascade(this.setAutoScroll);
+ }
+ }
+ if(matchPosition !== false){
+ this.setPosition(this.activeGhost.getLeft(true), this.activeGhost.getTop(true));
+ }
+ this.activeGhost.hide();
+ this.activeGhost.remove();
+ delete this.activeGhost;
+ },
+
+
+ minimize : function(){
+ this.fireEvent('minimize', this);
+ return this;
+ },
+
+
+ close : function(){
+ if(this.fireEvent('beforeclose', this) !== false){
+ if(this.hidden){
+ this.doClose();
+ }else{
+ this.hide(null, this.doClose, this);
+ }
+ }
+ },
+
+
+ doClose : function(){
+ this.fireEvent('close', this);
+ this.destroy();
+ },
+
+
+ maximize : function(){
+ if(!this.maximized){
+ this.expand(false);
+ this.restoreSize = this.getSize();
+ this.restorePos = this.getPosition(true);
+ if (this.maximizable){
+ this.tools.maximize.hide();
+ this.tools.restore.show();
+ }
+ this.maximized = true;
+ this.el.disableShadow();
+
+ if(this.dd){
+ this.dd.lock();
+ }
+ if(this.collapsible){
+ this.tools.toggle.hide();
+ }
+ this.el.addClass('x-window-maximized');
+ this.container.addClass('x-window-maximized-ct');
+
+ this.setPosition(0, 0);
+ this.fitContainer();
+ this.fireEvent('maximize', this);
+ }
+ return this;
+ },
+
+
+ restore : function(){
+ if(this.maximized){
+ var t = this.tools;
+ this.el.removeClass('x-window-maximized');
+ if(t.restore){
+ t.restore.hide();
+ }
+ if(t.maximize){
+ t.maximize.show();
+ }
+ this.setPosition(this.restorePos[0], this.restorePos[1]);
+ this.setSize(this.restoreSize.width, this.restoreSize.height);
+ delete this.restorePos;
+ delete this.restoreSize;
+ this.maximized = false;
+ this.el.enableShadow(true);
+
+ if(this.dd){
+ this.dd.unlock();
+ }
+ if(this.collapsible && t.toggle){
+ t.toggle.show();
+ }
+ this.container.removeClass('x-window-maximized-ct');
+
+ this.doConstrain();
+ this.fireEvent('restore', this);
+ }
+ return this;
+ },
+
+
+ toggleMaximize : function(){
+ return this[this.maximized ? 'restore' : 'maximize']();
+ },
+
+
+ fitContainer : function(){
+ var vs = this.container.getViewSize(false);
+ this.setSize(vs.width, vs.height);
+ },
+
+
+
+ setZIndex : function(index){
+ if(this.modal){
+ this.mask.setStyle('z-index', index);
+ }
+ this.el.setZIndex(++index);
+ index += 5;
+
+ if(this.resizer){
+ this.resizer.proxy.setStyle('z-index', ++index);
+ }
+
+ this.lastZIndex = index;
+ },
+
+
+ alignTo : function(element, position, offsets){
+ var xy = this.el.getAlignToXY(element, position, offsets);
+ this.setPagePosition(xy[0], xy[1]);
+ return this;
+ },
+
+
+ anchorTo : function(el, alignment, offsets, monitorScroll){
+ this.clearAnchor();
+ this.anchorTarget = {
+ el: el,
+ alignment: alignment,
+ offsets: offsets
+ };
+
+ Ext.EventManager.onWindowResize(this.doAnchor, this);
+ var tm = typeof monitorScroll;
+ if(tm != 'undefined'){
+ Ext.EventManager.on(window, 'scroll', this.doAnchor, this,
+ {buffer: tm == 'number' ? monitorScroll : 50});
+ }
+ return this.doAnchor();
+ },
+
+
+ doAnchor : function(){
+ var o = this.anchorTarget;
+ this.alignTo(o.el, o.alignment, o.offsets);
+ return this;
+ },
+
+
+ clearAnchor : function(){
+ if(this.anchorTarget){
+ Ext.EventManager.removeResizeListener(this.doAnchor, this);
+ Ext.EventManager.un(window, 'scroll', this.doAnchor, this);
+ delete this.anchorTarget;
+ }
+ return this;
+ },
+
+
+ toFront : function(e){
+ if(this.manager.bringToFront(this)){
+ if(!e || !e.getTarget().focus){
+ this.focus();
+ }
+ }
+ return this;
+ },
+
+
+ setActive : function(active){
+ if(active){
+ if(!this.maximized){
+ this.el.enableShadow(true);
+ }
+ this.fireEvent('activate', this);
+ }else{
+ this.el.disableShadow();
+ this.fireEvent('deactivate', this);
+ }
+ },
+
+
+ toBack : function(){
+ this.manager.sendToBack(this);
+ return this;
+ },
+
+
+ center : function(){
+ var xy = this.el.getAlignToXY(this.container, 'c-c');
+ this.setPagePosition(xy[0], xy[1]);
+ return this;
+ }
+
+
+});
+Ext.reg('window', Ext.Window);
+
+
+Ext.Window.DD = Ext.extend(Ext.dd.DD, {
+
+ constructor : function(win){
+ this.win = win;
+ Ext.Window.DD.superclass.constructor.call(this, win.el.id, 'WindowDD-'+win.id);
+ this.setHandleElId(win.header.id);
+ this.scroll = false;
+ },
+
+ moveOnly:true,
+ headerOffsets:[100, 25],
+ startDrag : function(){
+ var w = this.win;
+ this.proxy = w.ghost(w.initialConfig.cls);
+ if(w.constrain !== false){
+ var so = w.el.shadowOffset;
+ this.constrainTo(w.container, {right: so, left: so, bottom: so});
+ }else if(w.constrainHeader !== false){
+ var s = this.proxy.getSize();
+ this.constrainTo(w.container, {right: -(s.width-this.headerOffsets[0]), bottom: -(s.height-this.headerOffsets[1])});
+ }
+ },
+ b4Drag : Ext.emptyFn,
+
+ onDrag : function(e){
+ this.alignElWithMouse(this.proxy, e.getPageX(), e.getPageY());
+ },
+
+ endDrag : function(e){
+ this.win.unghost();
+ this.win.saveState();
+ }
+});
+
+Ext.WindowGroup = function(){
+ var list = {};
+ var accessList = [];
+ var front = null;
+
+
+ var sortWindows = function(d1, d2){
+ return (!d1._lastAccess || d1._lastAccess < d2._lastAccess) ? -1 : 1;
+ };
+
+
+ var orderWindows = function(){
+ var a = accessList, len = a.length;
+ if(len > 0){
+ a.sort(sortWindows);
+ var seed = a[0].manager.zseed;
+ for(var i = 0; i < len; i++){
+ var win = a[i];
+ if(win && !win.hidden){
+ win.setZIndex(seed + (i*10));
+ }
+ }
+ }
+ activateLast();
+ };
+
+
+ var setActiveWin = function(win){
+ if(win != front){
+ if(front){
+ front.setActive(false);
+ }
+ front = win;
+ if(win){
+ win.setActive(true);
+ }
+ }
+ };
+
+
+ var activateLast = function(){
+ for(var i = accessList.length-1; i >=0; --i) {
+ if(!accessList[i].hidden){
+ setActiveWin(accessList[i]);
+ return;
+ }
+ }
+
+ setActiveWin(null);
+ };
+
+ return {
+
+ zseed : 9000,
+
+
+ register : function(win){
+ if(win.manager){
+ win.manager.unregister(win);
+ }
+ win.manager = this;
+
+ list[win.id] = win;
+ accessList.push(win);
+ win.on('hide', activateLast);
+ },
+
+
+ unregister : function(win){
+ delete win.manager;
+ delete list[win.id];
+ win.un('hide', activateLast);
+ accessList.remove(win);
+ },
+
+
+ get : function(id){
+ return typeof id == "object" ? id : list[id];
+ },
+
+
+ bringToFront : function(win){
+ win = this.get(win);
+ if(win != front){
+ win._lastAccess = new Date().getTime();
+ orderWindows();
+ return true;
+ }
+ return false;
+ },
+
+
+ sendToBack : function(win){
+ win = this.get(win);
+ win._lastAccess = -(new Date().getTime());
+ orderWindows();
+ return win;
+ },
+
+
+ hideAll : function(){
+ for(var id in list){
+ if(list[id] && typeof list[id] != "function" && list[id].isVisible()){
+ list[id].hide();
+ }
+ }
+ },
+
+
+ getActive : function(){
+ return front;
+ },
+
+
+ getBy : function(fn, scope){
+ var r = [];
+ for(var i = accessList.length-1; i >=0; --i) {
+ var win = accessList[i];
+ if(fn.call(scope||win, win) !== false){
+ r.push(win);
+ }
+ }
+ return r;
+ },
+
+
+ each : function(fn, scope){
+ for(var id in list){
+ if(list[id] && typeof list[id] != "function"){
+ if(fn.call(scope || list[id], list[id]) === false){
+ return;
+ }
+ }
+ }
+ }
+ };
+};
+
+
+
+Ext.WindowMgr = new Ext.WindowGroup();
+Ext.MessageBox = function(){
+ var dlg, opt, mask, waitTimer,
+ bodyEl, msgEl, textboxEl, textareaEl, progressBar, pp, iconEl, spacerEl,
+ buttons, activeTextEl, bwidth, bufferIcon = '', iconCls = '',
+ buttonNames = ['ok', 'yes', 'no', 'cancel'];
+
+
+ var handleButton = function(button){
+ buttons[button].blur();
+ if(dlg.isVisible()){
+ dlg.hide();
+ handleHide();
+ Ext.callback(opt.fn, opt.scope||window, [button, activeTextEl.dom.value, opt], 1);
+ }
+ };
+
+
+ var handleHide = function(){
+ if(opt && opt.cls){
+ dlg.el.removeClass(opt.cls);
+ }
+ progressBar.reset();
+ };
+
+
+ var handleEsc = function(d, k, e){
+ if(opt && opt.closable !== false){
+ dlg.hide();
+ handleHide();
+ }
+ if(e){
+ e.stopEvent();
+ }
+ };
+
+
+ var updateButtons = function(b){
+ var width = 0,
+ cfg;
+ if(!b){
+ Ext.each(buttonNames, function(name){
+ buttons[name].hide();
+ });
+ return width;
+ }
+ dlg.footer.dom.style.display = '';
+ Ext.iterate(buttons, function(name, btn){
+ cfg = b[name];
+ if(cfg){
+ btn.show();
+ btn.setText(Ext.isString(cfg) ? cfg : Ext.MessageBox.buttonText[name]);
+ width += btn.getEl().getWidth() + 15;
+ }else{
+ btn.hide();
+ }
+ });
+ return width;
+ };
+
+ return {
+
+ getDialog : function(titleText){
+ if(!dlg){
+ var btns = [];
+
+ buttons = {};
+ Ext.each(buttonNames, function(name){
+ btns.push(buttons[name] = new Ext.Button({
+ text: this.buttonText[name],
+ handler: handleButton.createCallback(name),
+ hideMode: 'offsets'
+ }));
+ }, this);
+ dlg = new Ext.Window({
+ autoCreate : true,
+ title:titleText,
+ resizable:false,
+ constrain:true,
+ constrainHeader:true,
+ minimizable : false,
+ maximizable : false,
+ stateful: false,
+ modal: true,
+ shim:true,
+ buttonAlign:"center",
+ width:400,
+ height:100,
+ minHeight: 80,
+ plain:true,
+ footer:true,
+ closable:true,
+ close : function(){
+ if(opt && opt.buttons && opt.buttons.no && !opt.buttons.cancel){
+ handleButton("no");
+ }else{
+ handleButton("cancel");
+ }
+ },
+ fbar: new Ext.Toolbar({
+ items: btns,
+ enableOverflow: false
+ })
+ });
+ dlg.render(document.body);
+ dlg.getEl().addClass('x-window-dlg');
+ mask = dlg.mask;
+ bodyEl = dlg.body.createChild({
+ html:'<div class="ext-mb-icon"></div><div class="ext-mb-content"><span class="ext-mb-text"></span><br /><div class="ext-mb-fix-cursor"><input type="text" class="ext-mb-input" /><textarea class="ext-mb-textarea"></textarea></div></div>'
+ });
+ iconEl = Ext.get(bodyEl.dom.firstChild);
+ var contentEl = bodyEl.dom.childNodes[1];
+ msgEl = Ext.get(contentEl.firstChild);
+ textboxEl = Ext.get(contentEl.childNodes[2].firstChild);
+ textboxEl.enableDisplayMode();
+ textboxEl.addKeyListener([10,13], function(){
+ if(dlg.isVisible() && opt && opt.buttons){
+ if(opt.buttons.ok){
+ handleButton("ok");
+ }else if(opt.buttons.yes){
+ handleButton("yes");
+ }
+ }
+ });
+ textareaEl = Ext.get(contentEl.childNodes[2].childNodes[1]);
+ textareaEl.enableDisplayMode();
+ progressBar = new Ext.ProgressBar({
+ renderTo:bodyEl
+ });
+ bodyEl.createChild({cls:'x-clear'});
+ }
+ return dlg;
+ },
+
+
+ updateText : function(text){
+ if(!dlg.isVisible() && !opt.width){
+ dlg.setSize(this.maxWidth, 100);
+ }
+
+ msgEl.update(text ? text + ' ' : '&#160;');
+
+ var iw = iconCls != '' ? (iconEl.getWidth() + iconEl.getMargins('lr')) : 0,
+ mw = msgEl.getWidth() + msgEl.getMargins('lr'),
+ fw = dlg.getFrameWidth('lr'),
+ bw = dlg.body.getFrameWidth('lr'),
+ w;
+
+ w = Math.max(Math.min(opt.width || iw+mw+fw+bw, opt.maxWidth || this.maxWidth),
+ Math.max(opt.minWidth || this.minWidth, bwidth || 0));
+
+ if(opt.prompt === true){
+ activeTextEl.setWidth(w-iw-fw-bw);
+ }
+ if(opt.progress === true || opt.wait === true){
+ progressBar.setSize(w-iw-fw-bw);
+ }
+ if(Ext.isIE9m && w == bwidth){
+ w += 4;
+ }
+ msgEl.update(text || '&#160;');
+ dlg.setSize(w, 'auto').center();
+ return this;
+ },
+
+
+ updateProgress : function(value, progressText, msg){
+ progressBar.updateProgress(value, progressText);
+ if(msg){
+ this.updateText(msg);
+ }
+ return this;
+ },
+
+
+ isVisible : function(){
+ return dlg && dlg.isVisible();
+ },
+
+
+ hide : function(){
+ var proxy = dlg ? dlg.activeGhost : null;
+ if(this.isVisible() || proxy){
+ dlg.hide();
+ handleHide();
+ if (proxy){
+
+
+ dlg.unghost(false, false);
+ }
+ }
+ return this;
+ },
+
+
+ show : function(options){
+ if(this.isVisible()){
+ this.hide();
+ }
+ opt = options;
+ var d = this.getDialog(opt.title || "&#160;");
+
+ d.setTitle(opt.title || "&#160;");
+ var allowClose = (opt.closable !== false && opt.progress !== true && opt.wait !== true);
+ d.tools.close.setDisplayed(allowClose);
+ activeTextEl = textboxEl;
+ opt.prompt = opt.prompt || (opt.multiline ? true : false);
+ if(opt.prompt){
+ if(opt.multiline){
+ textboxEl.hide();
+ textareaEl.show();
+ textareaEl.setHeight(Ext.isNumber(opt.multiline) ? opt.multiline : this.defaultTextHeight);
+ activeTextEl = textareaEl;
+ }else{
+ textboxEl.show();
+ textareaEl.hide();
+ }
+ }else{
+ textboxEl.hide();
+ textareaEl.hide();
+ }
+ activeTextEl.dom.value = opt.value || "";
+ if(opt.prompt){
+ d.focusEl = activeTextEl;
+ }else{
+ var bs = opt.buttons;
+ var db = null;
+ if(bs && bs.ok){
+ db = buttons["ok"];
+ }else if(bs && bs.yes){
+ db = buttons["yes"];
+ }
+ if (db){
+ d.focusEl = db;
+ }
+ }
+ if(Ext.isDefined(opt.iconCls)){
+ d.setIconClass(opt.iconCls);
+ }
+ this.setIcon(Ext.isDefined(opt.icon) ? opt.icon : bufferIcon);
+ bwidth = updateButtons(opt.buttons);
+ progressBar.setVisible(opt.progress === true || opt.wait === true);
+ this.updateProgress(0, opt.progressText);
+ this.updateText(opt.msg);
+ if(opt.cls){
+ d.el.addClass(opt.cls);
+ }
+ d.proxyDrag = opt.proxyDrag === true;
+ d.modal = opt.modal !== false;
+ d.mask = opt.modal !== false ? mask : false;
+ if(!d.isVisible()){
+
+ document.body.appendChild(dlg.el.dom);
+ d.setAnimateTarget(opt.animEl);
+
+ d.on('show', function(){
+ if(allowClose === true){
+ d.keyMap.enable();
+ }else{
+ d.keyMap.disable();
+ }
+ }, this, {single:true});
+ d.show(opt.animEl);
+ }
+ if(opt.wait === true){
+ progressBar.wait(opt.waitConfig);
+ }
+ return this;
+ },
+
+
+ setIcon : function(icon){
+ if(!dlg){
+ bufferIcon = icon;
+ return;
+ }
+ bufferIcon = undefined;
+ if(icon && icon != ''){
+ iconEl.removeClass('x-hidden');
+ iconEl.replaceClass(iconCls, icon);
+ bodyEl.addClass('x-dlg-icon');
+ iconCls = icon;
+ }else{
+ iconEl.replaceClass(iconCls, 'x-hidden');
+ bodyEl.removeClass('x-dlg-icon');
+ iconCls = '';
+ }
+ return this;
+ },
+
+
+ progress : function(title, msg, progressText){
+ this.show({
+ title : title,
+ msg : msg,
+ buttons: false,
+ progress:true,
+ closable:false,
+ minWidth: this.minProgressWidth,
+ progressText: progressText
+ });
+ return this;
+ },
+
+
+ wait : function(msg, title, config){
+ this.show({
+ title : title,
+ msg : msg,
+ buttons: false,
+ closable:false,
+ wait:true,
+ modal:true,
+ minWidth: this.minProgressWidth,
+ waitConfig: config
+ });
+ return this;
+ },
+
+
+ alert : function(title, msg, fn, scope){
+ this.show({
+ title : title,
+ msg : msg,
+ buttons: this.OK,
+ fn: fn,
+ scope : scope,
+ minWidth: this.minWidth
+ });
+ return this;
+ },
+
+
+ confirm : function(title, msg, fn, scope){
+ this.show({
+ title : title,
+ msg : msg,
+ buttons: this.YESNO,
+ fn: fn,
+ scope : scope,
+ icon: this.QUESTION,
+ minWidth: this.minWidth
+ });
+ return this;
+ },
+
+
+ prompt : function(title, msg, fn, scope, multiline, value){
+ this.show({
+ title : title,
+ msg : msg,
+ buttons: this.OKCANCEL,
+ fn: fn,
+ minWidth: this.minPromptWidth,
+ scope : scope,
+ prompt:true,
+ multiline: multiline,
+ value: value
+ });
+ return this;
+ },
+
+
+ OK : {ok:true},
+
+ CANCEL : {cancel:true},
+
+ OKCANCEL : {ok:true, cancel:true},
+
+ YESNO : {yes:true, no:true},
+
+ YESNOCANCEL : {yes:true, no:true, cancel:true},
+
+ INFO : 'ext-mb-info',
+
+ WARNING : 'ext-mb-warning',
+
+ QUESTION : 'ext-mb-question',
+
+ ERROR : 'ext-mb-error',
+
+
+ defaultTextHeight : 75,
+
+ maxWidth : 600,
+
+ minWidth : 100,
+
+ minProgressWidth : 250,
+
+ minPromptWidth: 250,
+
+ buttonText : {
+ ok : "OK",
+ cancel : "Cancel",
+ yes : "Yes",
+ no : "No"
+ }
+ };
+}();
+
+
+Ext.Msg = Ext.MessageBox;
+Ext.dd.PanelProxy = Ext.extend(Object, {
+
+ constructor : function(panel, config){
+ this.panel = panel;
+ this.id = this.panel.id +'-ddproxy';
+ Ext.apply(this, config);
+ },
+
+
+ insertProxy : true,
+
+
+ setStatus : Ext.emptyFn,
+ reset : Ext.emptyFn,
+ update : Ext.emptyFn,
+ stop : Ext.emptyFn,
+ sync: Ext.emptyFn,
+
+
+ getEl : function(){
+ return this.ghost;
+ },
+
+
+ getGhost : function(){
+ return this.ghost;
+ },
+
+
+ getProxy : function(){
+ return this.proxy;
+ },
+
+
+ hide : function(){
+ if(this.ghost){
+ if(this.proxy){
+ this.proxy.remove();
+ delete this.proxy;
+ }
+ this.panel.el.dom.style.display = '';
+ this.ghost.remove();
+ delete this.ghost;
+ }
+ },
+
+
+ show : function(){
+ if(!this.ghost){
+ this.ghost = this.panel.createGhost(this.panel.initialConfig.cls, undefined, Ext.getBody());
+ this.ghost.setXY(this.panel.el.getXY());
+ if(this.insertProxy){
+ this.proxy = this.panel.el.insertSibling({cls:'x-panel-dd-spacer'});
+ this.proxy.setSize(this.panel.getSize());
+ }
+ this.panel.el.dom.style.display = 'none';
+ }
+ },
+
+
+ repair : function(xy, callback, scope){
+ this.hide();
+ if(typeof callback == "function"){
+ callback.call(scope || this);
+ }
+ },
+
+
+ moveProxy : function(parentNode, before){
+ if(this.proxy){
+ parentNode.insertBefore(this.proxy.dom, before);
+ }
+ }
+});
+
+
+Ext.Panel.DD = Ext.extend(Ext.dd.DragSource, {
+
+ constructor : function(panel, cfg){
+ this.panel = panel;
+ this.dragData = {panel: panel};
+ this.proxy = new Ext.dd.PanelProxy(panel, cfg);
+ Ext.Panel.DD.superclass.constructor.call(this, panel.el, cfg);
+ var h = panel.header,
+ el = panel.body;
+ if(h){
+ this.setHandleElId(h.id);
+ el = panel.header;
+ }
+ el.setStyle('cursor', 'move');
+ this.scroll = false;
+ },
+
+ showFrame: Ext.emptyFn,
+ startDrag: Ext.emptyFn,
+ b4StartDrag: function(x, y) {
+ this.proxy.show();
+ },
+ b4MouseDown: function(e) {
+ var x = e.getPageX(),
+ y = e.getPageY();
+ this.autoOffset(x, y);
+ },
+ onInitDrag : function(x, y){
+ this.onStartDrag(x, y);
+ return true;
+ },
+ createFrame : Ext.emptyFn,
+ getDragEl : function(e){
+ return this.proxy.ghost.dom;
+ },
+ endDrag : function(e){
+ this.proxy.hide();
+ this.panel.saveState();
+ },
+
+ autoOffset : function(x, y) {
+ x -= this.startPageX;
+ y -= this.startPageY;
+ this.setDelta(x, y);
+ }
+});
+Ext.state.Provider = Ext.extend(Ext.util.Observable, {
+
+ constructor : function(){
+
+ this.addEvents("statechange");
+ this.state = {};
+ Ext.state.Provider.superclass.constructor.call(this);
+ },
+
+
+ get : function(name, defaultValue){
+ return typeof this.state[name] == "undefined" ?
+ defaultValue : this.state[name];
+ },
+
+
+ clear : function(name){
+ delete this.state[name];
+ this.fireEvent("statechange", this, name, null);
+ },
+
+
+ set : function(name, value){
+ this.state[name] = value;
+ this.fireEvent("statechange", this, name, value);
+ },
+
+
+ decodeValue : function(cookie){
+
+ var re = /^(a|n|d|b|s|o|e)\:(.*)$/,
+ matches = re.exec(unescape(cookie)),
+ all,
+ type,
+ v,
+ kv;
+ if(!matches || !matches[1]){
+ return;
+ }
+ type = matches[1];
+ v = matches[2];
+ switch(type){
+ case 'e':
+ return null;
+ case 'n':
+ return parseFloat(v);
+ case 'd':
+ return new Date(Date.parse(v));
+ case 'b':
+ return (v == '1');
+ case 'a':
+ all = [];
+ if(v != ''){
+ Ext.each(v.split('^'), function(val){
+ all.push(this.decodeValue(val));
+ }, this);
+ }
+ return all;
+ case 'o':
+ all = {};
+ if(v != ''){
+ Ext.each(v.split('^'), function(val){
+ kv = val.split('=');
+ all[kv[0]] = this.decodeValue(kv[1]);
+ }, this);
+ }
+ return all;
+ default:
+ return v;
+ }
+ },
+
+
+ encodeValue : function(v){
+ var enc,
+ flat = '',
+ i = 0,
+ len,
+ key;
+ if(v == null){
+ return 'e:1';
+ }else if(typeof v == 'number'){
+ enc = 'n:' + v;
+ }else if(typeof v == 'boolean'){
+ enc = 'b:' + (v ? '1' : '0');
+ }else if(Ext.isDate(v)){
+ enc = 'd:' + v.toGMTString();
+ }else if(Ext.isArray(v)){
+ for(len = v.length; i < len; i++){
+ flat += this.encodeValue(v[i]);
+ if(i != len - 1){
+ flat += '^';
+ }
+ }
+ enc = 'a:' + flat;
+ }else if(typeof v == 'object'){
+ for(key in v){
+ if(typeof v[key] != 'function' && v[key] !== undefined){
+ flat += key + '=' + this.encodeValue(v[key]) + '^';
+ }
+ }
+ enc = 'o:' + flat.substring(0, flat.length-1);
+ }else{
+ enc = 's:' + v;
+ }
+ return escape(enc);
+ }
+});
+
+Ext.state.Manager = function(){
+ var provider = new Ext.state.Provider();
+
+ return {
+
+ setProvider : function(stateProvider){
+ provider = stateProvider;
+ },
+
+
+ get : function(key, defaultValue){
+ return provider.get(key, defaultValue);
+ },
+
+
+ set : function(key, value){
+ provider.set(key, value);
+ },
+
+
+ clear : function(key){
+ provider.clear(key);
+ },
+
+
+ getProvider : function(){
+ return provider;
+ }
+ };
+}();
+
+Ext.state.CookieProvider = Ext.extend(Ext.state.Provider, {
+
+ constructor : function(config){
+ Ext.state.CookieProvider.superclass.constructor.call(this);
+ this.path = "/";
+ this.expires = new Date(new Date().getTime()+(1000*60*60*24*7));
+ this.domain = null;
+ this.secure = false;
+ Ext.apply(this, config);
+ this.state = this.readCookies();
+ },
+
+
+ set : function(name, value){
+ if(typeof value == "undefined" || value === null){
+ this.clear(name);
+ return;
+ }
+ this.setCookie(name, value);
+ Ext.state.CookieProvider.superclass.set.call(this, name, value);
+ },
+
+
+ clear : function(name){
+ this.clearCookie(name);
+ Ext.state.CookieProvider.superclass.clear.call(this, name);
+ },
+
+
+ readCookies : function(){
+ var cookies = {},
+ c = document.cookie + ";",
+ re = /\s?(.*?)=(.*?);/g,
+ matches,
+ name,
+ value;
+ while((matches = re.exec(c)) != null){
+ name = matches[1];
+ value = matches[2];
+ if(name && name.substring(0,3) == "ys-"){
+ cookies[name.substr(3)] = this.decodeValue(value);
+ }
+ }
+ return cookies;
+ },
+
+
+ setCookie : function(name, value){
+ document.cookie = "ys-"+ name + "=" + this.encodeValue(value) +
+ ((this.expires == null) ? "" : ("; expires=" + this.expires.toGMTString())) +
+ ((this.path == null) ? "" : ("; path=" + this.path)) +
+ ((this.domain == null) ? "" : ("; domain=" + this.domain)) +
+ ((this.secure == true) ? "; secure" : "");
+ },
+
+
+ clearCookie : function(name){
+ document.cookie = "ys-" + name + "=null; expires=Thu, 01-Jan-70 00:00:01 GMT" +
+ ((this.path == null) ? "" : ("; path=" + this.path)) +
+ ((this.domain == null) ? "" : ("; domain=" + this.domain)) +
+ ((this.secure == true) ? "; secure" : "");
+ }
+});
+Ext.DataView = Ext.extend(Ext.BoxComponent, {
+
+
+
+
+
+
+
+
+
+ selectedClass : "x-view-selected",
+
+ emptyText : "",
+
+
+ deferEmptyText: true,
+
+ trackOver: false,
+
+
+ blockRefresh: false,
+
+
+ last: false,
+
+
+ initComponent : function(){
+ Ext.DataView.superclass.initComponent.call(this);
+ if(Ext.isString(this.tpl) || Ext.isArray(this.tpl)){
+ this.tpl = new Ext.XTemplate(this.tpl);
+ }
+
+ this.addEvents(
+
+ "beforeclick",
+
+ "click",
+
+ "mouseenter",
+
+ "mouseleave",
+
+ "containerclick",
+
+ "dblclick",
+
+ "contextmenu",
+
+ "containercontextmenu",
+
+ "selectionchange",
+
+
+ "beforeselect"
+ );
+
+ this.store = Ext.StoreMgr.lookup(this.store);
+ this.all = new Ext.CompositeElementLite();
+ this.selected = new Ext.CompositeElementLite();
+ },
+
+
+ afterRender : function(){
+ Ext.DataView.superclass.afterRender.call(this);
+
+ this.mon(this.getTemplateTarget(), {
+ "click": this.onClick,
+ "dblclick": this.onDblClick,
+ "contextmenu": this.onContextMenu,
+ scope:this
+ });
+
+ if(this.overClass || this.trackOver){
+ this.mon(this.getTemplateTarget(), {
+ "mouseover": this.onMouseOver,
+ "mouseout": this.onMouseOut,
+ scope:this
+ });
+ }
+
+ if(this.store){
+ this.bindStore(this.store, true);
+ }
+ },
+
+
+ refresh : function() {
+ this.clearSelections(false, true);
+ var el = this.getTemplateTarget(),
+ records = this.store.getRange();
+
+ el.update('');
+ if(records.length < 1){
+ if(!this.deferEmptyText || this.hasSkippedEmptyText){
+ el.update(this.emptyText);
+ }
+ this.all.clear();
+ }else{
+ this.tpl.overwrite(el, this.collectData(records, 0));
+ this.all.fill(Ext.query(this.itemSelector, el.dom));
+ this.updateIndexes(0);
+ }
+ this.hasSkippedEmptyText = true;
+ },
+
+ getTemplateTarget: function(){
+ return this.el;
+ },
+
+
+ prepareData : function(data){
+ return data;
+ },
+
+
+ collectData : function(records, startIndex){
+ var r = [],
+ i = 0,
+ len = records.length;
+ for(; i < len; i++){
+ r[r.length] = this.prepareData(records[i].data, startIndex + i, records[i]);
+ }
+ return r;
+ },
+
+
+ bufferRender : function(records, index){
+ var div = document.createElement('div');
+ this.tpl.overwrite(div, this.collectData(records, index));
+ return Ext.query(this.itemSelector, div);
+ },
+
+
+ onUpdate : function(ds, record){
+ var index = this.store.indexOf(record);
+ if(index > -1){
+ var sel = this.isSelected(index),
+ original = this.all.elements[index],
+ node = this.bufferRender([record], index)[0];
+
+ this.all.replaceElement(index, node, true);
+ if(sel){
+ this.selected.replaceElement(original, node);
+ this.all.item(index).addClass(this.selectedClass);
+ }
+ this.updateIndexes(index, index);
+ }
+ },
+
+
+ onAdd : function(ds, records, index){
+ if(this.all.getCount() === 0){
+ this.refresh();
+ return;
+ }
+ var nodes = this.bufferRender(records, index), n, a = this.all.elements;
+ if(index < this.all.getCount()){
+ n = this.all.item(index).insertSibling(nodes, 'before', true);
+ a.splice.apply(a, [index, 0].concat(nodes));
+ }else{
+ n = this.all.last().insertSibling(nodes, 'after', true);
+ a.push.apply(a, nodes);
+ }
+ this.updateIndexes(index);
+ },
+
+
+ onRemove : function(ds, record, index){
+ this.deselect(index);
+ this.all.removeElement(index, true);
+ this.updateIndexes(index);
+ if (this.store.getCount() === 0){
+ this.refresh();
+ }
+ },
+
+
+ refreshNode : function(index){
+ this.onUpdate(this.store, this.store.getAt(index));
+ },
+
+
+ updateIndexes : function(startIndex, endIndex){
+ var ns = this.all.elements;
+ startIndex = startIndex || 0;
+ endIndex = endIndex || ((endIndex === 0) ? 0 : (ns.length - 1));
+ for(var i = startIndex; i <= endIndex; i++){
+ ns[i].viewIndex = i;
+ }
+ },
+
+
+ getStore : function(){
+ return this.store;
+ },
+
+
+ bindStore : function(store, initial){
+ if(!initial && this.store){
+ if(store !== this.store && this.store.autoDestroy){
+ this.store.destroy();
+ }else{
+ this.store.un("beforeload", this.onBeforeLoad, this);
+ this.store.un("datachanged", this.onDataChanged, this);
+ this.store.un("add", this.onAdd, this);
+ this.store.un("remove", this.onRemove, this);
+ this.store.un("update", this.onUpdate, this);
+ this.store.un("clear", this.refresh, this);
+ }
+ if(!store){
+ this.store = null;
+ }
+ }
+ if(store){
+ store = Ext.StoreMgr.lookup(store);
+ store.on({
+ scope: this,
+ beforeload: this.onBeforeLoad,
+ datachanged: this.onDataChanged,
+ add: this.onAdd,
+ remove: this.onRemove,
+ update: this.onUpdate,
+ clear: this.refresh
+ });
+ }
+ this.store = store;
+ if(store){
+ this.refresh();
+ }
+ },
+
+
+ onDataChanged: function() {
+ if (this.blockRefresh !== true) {
+ this.refresh.apply(this, arguments);
+ }
+ },
+
+
+ findItemFromChild : function(node){
+ return Ext.fly(node).findParent(this.itemSelector, this.getTemplateTarget());
+ },
+
+
+ onClick : function(e){
+ var item = e.getTarget(this.itemSelector, this.getTemplateTarget()),
+ index;
+ if(item){
+ index = this.indexOf(item);
+ if(this.onItemClick(item, index, e) !== false){
+ this.fireEvent("click", this, index, item, e);
+ }
+ }else{
+ if(this.fireEvent("containerclick", this, e) !== false){
+ this.onContainerClick(e);
+ }
+ }
+ },
+
+ onContainerClick : function(e){
+ this.clearSelections();
+ },
+
+
+ onContextMenu : function(e){
+ var item = e.getTarget(this.itemSelector, this.getTemplateTarget());
+ if(item){
+ this.fireEvent("contextmenu", this, this.indexOf(item), item, e);
+ }else{
+ this.fireEvent("containercontextmenu", this, e);
+ }
+ },
+
+
+ onDblClick : function(e){
+ var item = e.getTarget(this.itemSelector, this.getTemplateTarget());
+ if(item){
+ this.fireEvent("dblclick", this, this.indexOf(item), item, e);
+ }
+ },
+
+
+ onMouseOver : function(e){
+ var item = e.getTarget(this.itemSelector, this.getTemplateTarget());
+ if(item && item !== this.lastItem){
+ this.lastItem = item;
+ Ext.fly(item).addClass(this.overClass);
+ this.fireEvent("mouseenter", this, this.indexOf(item), item, e);
+ }
+ },
+
+
+ onMouseOut : function(e){
+ if(this.lastItem){
+ if(!e.within(this.lastItem, true, true)){
+ Ext.fly(this.lastItem).removeClass(this.overClass);
+ this.fireEvent("mouseleave", this, this.indexOf(this.lastItem), this.lastItem, e);
+ delete this.lastItem;
+ }
+ }
+ },
+
+
+ onItemClick : function(item, index, e){
+ if(this.fireEvent("beforeclick", this, index, item, e) === false){
+ return false;
+ }
+ if(this.multiSelect){
+ this.doMultiSelection(item, index, e);
+ e.preventDefault();
+ }else if(this.singleSelect){
+ this.doSingleSelection(item, index, e);
+ e.preventDefault();
+ }
+ return true;
+ },
+
+
+ doSingleSelection : function(item, index, e){
+ if(e.ctrlKey && this.isSelected(index)){
+ this.deselect(index);
+ }else{
+ this.select(index, false);
+ }
+ },
+
+
+ doMultiSelection : function(item, index, e){
+ if(e.shiftKey && this.last !== false){
+ var last = this.last;
+ this.selectRange(last, index, e.ctrlKey);
+ this.last = last;
+ }else{
+ if((e.ctrlKey||this.simpleSelect) && this.isSelected(index)){
+ this.deselect(index);
+ }else{
+ this.select(index, e.ctrlKey || e.shiftKey || this.simpleSelect);
+ }
+ }
+ },
+
+
+ getSelectionCount : function(){
+ return this.selected.getCount();
+ },
+
+
+ getSelectedNodes : function(){
+ return this.selected.elements;
+ },
+
+
+ getSelectedIndexes : function(){
+ var indexes = [],
+ selected = this.selected.elements,
+ i = 0,
+ len = selected.length;
+
+ for(; i < len; i++){
+ indexes.push(selected[i].viewIndex);
+ }
+ return indexes;
+ },
+
+
+ getSelectedRecords : function(){
+ return this.getRecords(this.selected.elements);
+ },
+
+
+ getRecords : function(nodes){
+ var records = [],
+ i = 0,
+ len = nodes.length;
+
+ for(; i < len; i++){
+ records[records.length] = this.store.getAt(nodes[i].viewIndex);
+ }
+ return records;
+ },
+
+
+ getRecord : function(node){
+ return this.store.getAt(node.viewIndex);
+ },
+
+
+ clearSelections : function(suppressEvent, skipUpdate){
+ if((this.multiSelect || this.singleSelect) && this.selected.getCount() > 0){
+ if(!skipUpdate){
+ this.selected.removeClass(this.selectedClass);
+ }
+ this.selected.clear();
+ this.last = false;
+ if(!suppressEvent){
+ this.fireEvent("selectionchange", this, this.selected.elements);
+ }
+ }
+ },
+
+
+ isSelected : function(node){
+ return this.selected.contains(this.getNode(node));
+ },
+
+
+ deselect : function(node){
+ if(this.isSelected(node)){
+ node = this.getNode(node);
+ this.selected.removeElement(node);
+ if(this.last == node.viewIndex){
+ this.last = false;
+ }
+ Ext.fly(node).removeClass(this.selectedClass);
+ this.fireEvent("selectionchange", this, this.selected.elements);
+ }
+ },
+
+
+ select : function(nodeInfo, keepExisting, suppressEvent){
+ if(Ext.isArray(nodeInfo)){
+ if(!keepExisting){
+ this.clearSelections(true);
+ }
+ for(var i = 0, len = nodeInfo.length; i < len; i++){
+ this.select(nodeInfo[i], true, true);
+ }
+ if(!suppressEvent){
+ this.fireEvent("selectionchange", this, this.selected.elements);
+ }
+ } else{
+ var node = this.getNode(nodeInfo);
+ if(!keepExisting){
+ this.clearSelections(true);
+ }
+ if(node && !this.isSelected(node)){
+ if(this.fireEvent("beforeselect", this, node, this.selected.elements) !== false){
+ Ext.fly(node).addClass(this.selectedClass);
+ this.selected.add(node);
+ this.last = node.viewIndex;
+ if(!suppressEvent){
+ this.fireEvent("selectionchange", this, this.selected.elements);
+ }
+ }
+ }
+ }
+ },
+
+
+ selectRange : function(start, end, keepExisting){
+ if(!keepExisting){
+ this.clearSelections(true);
+ }
+ this.select(this.getNodes(start, end), true);
+ },
+
+
+ getNode : function(nodeInfo){
+ if(Ext.isString(nodeInfo)){
+ return document.getElementById(nodeInfo);
+ }else if(Ext.isNumber(nodeInfo)){
+ return this.all.elements[nodeInfo];
+ }else if(nodeInfo instanceof Ext.data.Record){
+ var idx = this.store.indexOf(nodeInfo);
+ return this.all.elements[idx];
+ }
+ return nodeInfo;
+ },
+
+
+ getNodes : function(start, end){
+ var ns = this.all.elements,
+ nodes = [],
+ i;
+
+ start = start || 0;
+ end = !Ext.isDefined(end) ? Math.max(ns.length - 1, 0) : end;
+ if(start <= end){
+ for(i = start; i <= end && ns[i]; i++){
+ nodes.push(ns[i]);
+ }
+ } else{
+ for(i = start; i >= end && ns[i]; i--){
+ nodes.push(ns[i]);
+ }
+ }
+ return nodes;
+ },
+
+
+ indexOf : function(node){
+ node = this.getNode(node);
+ if(Ext.isNumber(node.viewIndex)){
+ return node.viewIndex;
+ }
+ return this.all.indexOf(node);
+ },
+
+
+ onBeforeLoad : function(){
+ if(this.loadingText){
+ this.clearSelections(false, true);
+ this.getTemplateTarget().update('<div class="loading-indicator">'+this.loadingText+'</div>');
+ this.all.clear();
+ }
+ },
+
+ onDestroy : function(){
+ this.all.clear();
+ this.selected.clear();
+ Ext.DataView.superclass.onDestroy.call(this);
+ this.bindStore(null);
+ }
+});
+
+
+Ext.DataView.prototype.setStore = Ext.DataView.prototype.bindStore;
+
+Ext.reg('dataview', Ext.DataView);
+
+Ext.list.ListView = Ext.extend(Ext.DataView, {
+
+
+
+ itemSelector: 'dl',
+
+ selectedClass:'x-list-selected',
+
+ overClass:'x-list-over',
+
+
+ scrollOffset : undefined,
+
+ columnResize: true,
+
+
+ columnSort: true,
+
+
+
+ maxColumnWidth: Ext.isIE9m ? 99 : 100,
+
+ initComponent : function(){
+ if(this.columnResize){
+ this.colResizer = new Ext.list.ColumnResizer(this.colResizer);
+ this.colResizer.init(this);
+ }
+ if(this.columnSort){
+ this.colSorter = new Ext.list.Sorter(this.columnSort);
+ this.colSorter.init(this);
+ }
+ if(!this.internalTpl){
+ this.internalTpl = new Ext.XTemplate(
+ '<div class="x-list-header"><div class="x-list-header-inner">',
+ '<tpl for="columns">',
+ '<div style="width:{[values.width*100]}%;text-align:{align};"><em class="x-unselectable" unselectable="on" id="',this.id, '-xlhd-{#}">',
+ '{header}',
+ '</em></div>',
+ '</tpl>',
+ '<div class="x-clear"></div>',
+ '</div></div>',
+ '<div class="x-list-body"><div class="x-list-body-inner">',
+ '</div></div>'
+ );
+ }
+ if(!this.tpl){
+ this.tpl = new Ext.XTemplate(
+ '<tpl for="rows">',
+ '<dl>',
+ '<tpl for="parent.columns">',
+ '<dt style="width:{[values.width*100]}%;text-align:{align};">',
+ '<em unselectable="on"<tpl if="cls"> class="{cls}</tpl>">',
+ '{[values.tpl.apply(parent)]}',
+ '</em></dt>',
+ '</tpl>',
+ '<div class="x-clear"></div>',
+ '</dl>',
+ '</tpl>'
+ );
+ };
+
+ var cs = this.columns,
+ allocatedWidth = 0,
+ colsWithWidth = 0,
+ len = cs.length,
+ columns = [];
+
+ for(var i = 0; i < len; i++){
+ var c = cs[i];
+ if(!c.isColumn) {
+ c.xtype = c.xtype ? (/^lv/.test(c.xtype) ? c.xtype : 'lv' + c.xtype) : 'lvcolumn';
+ c = Ext.create(c);
+ }
+ if(c.width) {
+ allocatedWidth += c.width*100;
+ if(allocatedWidth > this.maxColumnWidth){
+ c.width -= (allocatedWidth - this.maxColumnWidth) / 100;
+ }
+ colsWithWidth++;
+ }
+ columns.push(c);
+ }
+
+ cs = this.columns = columns;
+
+
+ if(colsWithWidth < len){
+ var remaining = len - colsWithWidth;
+ if(allocatedWidth < this.maxColumnWidth){
+ var perCol = ((this.maxColumnWidth-allocatedWidth) / remaining)/100;
+ for(var j = 0; j < len; j++){
+ var c = cs[j];
+ if(!c.width){
+ c.width = perCol;
+ }
+ }
+ }
+ }
+ Ext.list.ListView.superclass.initComponent.call(this);
+ },
+
+ onRender : function(){
+ this.autoEl = {
+ cls: 'x-list-wrap'
+ };
+ Ext.list.ListView.superclass.onRender.apply(this, arguments);
+
+ this.internalTpl.overwrite(this.el, {columns: this.columns});
+
+ this.innerBody = Ext.get(this.el.dom.childNodes[1].firstChild);
+ this.innerHd = Ext.get(this.el.dom.firstChild.firstChild);
+
+ if(this.hideHeaders){
+ this.el.dom.firstChild.style.display = 'none';
+ }
+ },
+
+ getTemplateTarget : function(){
+ return this.innerBody;
+ },
+
+
+ collectData : function(){
+ var rs = Ext.list.ListView.superclass.collectData.apply(this, arguments);
+ return {
+ columns: this.columns,
+ rows: rs
+ };
+ },
+
+ verifyInternalSize : function(){
+ if(this.lastSize){
+ this.onResize(this.lastSize.width, this.lastSize.height);
+ }
+ },
+
+
+ onResize : function(w, h){
+ var body = this.innerBody.dom,
+ header = this.innerHd.dom,
+ scrollWidth = w - Ext.num(this.scrollOffset, Ext.getScrollBarWidth()) + 'px',
+ parentNode;
+
+ if(!body){
+ return;
+ }
+ parentNode = body.parentNode;
+ if(Ext.isNumber(w)){
+ if(this.reserveScrollOffset || ((parentNode.offsetWidth - parentNode.clientWidth) > 10)){
+ body.style.width = scrollWidth;
+ header.style.width = scrollWidth;
+ }else{
+ body.style.width = w + 'px';
+ header.style.width = w + 'px';
+ setTimeout(function(){
+ if((parentNode.offsetWidth - parentNode.clientWidth) > 10){
+ body.style.width = scrollWidth;
+ header.style.width = scrollWidth;
+ }
+ }, 10);
+ }
+ }
+ if(Ext.isNumber(h)){
+ parentNode.style.height = Math.max(0, h - header.parentNode.offsetHeight) + 'px';
+ }
+ },
+
+ updateIndexes : function(){
+ Ext.list.ListView.superclass.updateIndexes.apply(this, arguments);
+ this.verifyInternalSize();
+ },
+
+ findHeaderIndex : function(header){
+ header = header.dom || header;
+ var parentNode = header.parentNode,
+ children = parentNode.parentNode.childNodes,
+ i = 0,
+ c;
+ for(; c = children[i]; i++){
+ if(c == parentNode){
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ setHdWidths : function(){
+ var els = this.innerHd.dom.getElementsByTagName('div'),
+ i = 0,
+ columns = this.columns,
+ len = columns.length;
+
+ for(; i < len; i++){
+ els[i].style.width = (columns[i].width*100) + '%';
+ }
+ }
+});
+
+Ext.reg('listview', Ext.list.ListView);
+
+
+Ext.ListView = Ext.list.ListView;
+Ext.list.Column = Ext.extend(Object, {
+
+ isColumn: true,
+
+
+ align: 'left',
+
+ header: '',
+
+
+ width: null,
+
+
+ cls: '',
+
+
+
+
+
+ constructor : function(c){
+ if(!c.tpl){
+ c.tpl = new Ext.XTemplate('{' + c.dataIndex + '}');
+ }
+ else if(Ext.isString(c.tpl)){
+ c.tpl = new Ext.XTemplate(c.tpl);
+ }
+
+ Ext.apply(this, c);
+ }
+});
+
+Ext.reg('lvcolumn', Ext.list.Column);
+
+
+Ext.list.NumberColumn = Ext.extend(Ext.list.Column, {
+
+ format: '0,000.00',
+
+ constructor : function(c) {
+ c.tpl = c.tpl || new Ext.XTemplate('{' + c.dataIndex + ':number("' + (c.format || this.format) + '")}');
+ Ext.list.NumberColumn.superclass.constructor.call(this, c);
+ }
+});
+
+Ext.reg('lvnumbercolumn', Ext.list.NumberColumn);
+
+
+Ext.list.DateColumn = Ext.extend(Ext.list.Column, {
+ format: 'm/d/Y',
+ constructor : function(c) {
+ c.tpl = c.tpl || new Ext.XTemplate('{' + c.dataIndex + ':date("' + (c.format || this.format) + '")}');
+ Ext.list.DateColumn.superclass.constructor.call(this, c);
+ }
+});
+Ext.reg('lvdatecolumn', Ext.list.DateColumn);
+
+
+Ext.list.BooleanColumn = Ext.extend(Ext.list.Column, {
+
+ trueText: 'true',
+
+ falseText: 'false',
+
+ undefinedText: '&#160;',
+
+ constructor : function(c) {
+ c.tpl = c.tpl || new Ext.XTemplate('{' + c.dataIndex + ':this.format}');
+
+ var t = this.trueText, f = this.falseText, u = this.undefinedText;
+ c.tpl.format = function(v){
+ if(v === undefined){
+ return u;
+ }
+ if(!v || v === 'false'){
+ return f;
+ }
+ return t;
+ };
+
+ Ext.list.DateColumn.superclass.constructor.call(this, c);
+ }
+});
+
+Ext.reg('lvbooleancolumn', Ext.list.BooleanColumn);
+Ext.list.ColumnResizer = Ext.extend(Ext.util.Observable, {
+
+ minPct: .05,
+
+ constructor: function(config){
+ Ext.apply(this, config);
+ Ext.list.ColumnResizer.superclass.constructor.call(this);
+ },
+ init : function(listView){
+ this.view = listView;
+ listView.on('render', this.initEvents, this);
+ },
+
+ initEvents : function(view){
+ view.mon(view.innerHd, 'mousemove', this.handleHdMove, this);
+ this.tracker = new Ext.dd.DragTracker({
+ onBeforeStart: this.onBeforeStart.createDelegate(this),
+ onStart: this.onStart.createDelegate(this),
+ onDrag: this.onDrag.createDelegate(this),
+ onEnd: this.onEnd.createDelegate(this),
+ tolerance: 3,
+ autoStart: 300
+ });
+ this.tracker.initEl(view.innerHd);
+ view.on('beforedestroy', this.tracker.destroy, this.tracker);
+ },
+
+ handleHdMove : function(e, t){
+ var handleWidth = 5,
+ x = e.getPageX(),
+ header = e.getTarget('em', 3, true);
+ if(header){
+ var region = header.getRegion(),
+ style = header.dom.style,
+ parentNode = header.dom.parentNode;
+
+ if(x - region.left <= handleWidth && parentNode != parentNode.parentNode.firstChild){
+ this.activeHd = Ext.get(parentNode.previousSibling.firstChild);
+ style.cursor = Ext.isWebKit ? 'e-resize' : 'col-resize';
+ } else if(region.right - x <= handleWidth && parentNode != parentNode.parentNode.lastChild.previousSibling){
+ this.activeHd = header;
+ style.cursor = Ext.isWebKit ? 'w-resize' : 'col-resize';
+ } else{
+ delete this.activeHd;
+ style.cursor = '';
+ }
+ }
+ },
+
+ onBeforeStart : function(e){
+ this.dragHd = this.activeHd;
+ return !!this.dragHd;
+ },
+
+ onStart: function(e){
+
+ var me = this,
+ view = me.view,
+ dragHeader = me.dragHd,
+ x = me.tracker.getXY()[0];
+
+ me.proxy = view.el.createChild({cls:'x-list-resizer'});
+ me.dragX = dragHeader.getX();
+ me.headerIndex = view.findHeaderIndex(dragHeader);
+
+ me.headersDisabled = view.disableHeaders;
+ view.disableHeaders = true;
+
+ me.proxy.setHeight(view.el.getHeight());
+ me.proxy.setX(me.dragX);
+ me.proxy.setWidth(x - me.dragX);
+
+ this.setBoundaries();
+
+ },
+
+
+ setBoundaries: function(relativeX){
+ var view = this.view,
+ headerIndex = this.headerIndex,
+ width = view.innerHd.getWidth(),
+ relativeX = view.innerHd.getX(),
+ minWidth = Math.ceil(width * this.minPct),
+ maxWidth = width - minWidth,
+ numColumns = view.columns.length,
+ headers = view.innerHd.select('em', true),
+ minX = minWidth + relativeX,
+ maxX = maxWidth + relativeX,
+ header;
+
+ if (numColumns == 2) {
+ this.minX = minX;
+ this.maxX = maxX;
+ }else{
+ header = headers.item(headerIndex + 2);
+ this.minX = headers.item(headerIndex).getX() + minWidth;
+ this.maxX = header ? header.getX() - minWidth : maxX;
+ if (headerIndex == 0) {
+
+ this.minX = minX;
+ } else if (headerIndex == numColumns - 2) {
+
+ this.maxX = maxX;
+ }
+ }
+ },
+
+ onDrag: function(e){
+ var me = this,
+ cursorX = me.tracker.getXY()[0].constrain(me.minX, me.maxX);
+
+ me.proxy.setWidth(cursorX - this.dragX);
+ },
+
+ onEnd: function(e){
+
+ var newWidth = this.proxy.getWidth(),
+ index = this.headerIndex,
+ view = this.view,
+ columns = view.columns,
+ width = view.innerHd.getWidth(),
+ newPercent = Math.ceil(newWidth * view.maxColumnWidth / width) / 100,
+ disabled = this.headersDisabled,
+ headerCol = columns[index],
+ otherCol = columns[index + 1],
+ totalPercent = headerCol.width + otherCol.width;
+
+ this.proxy.remove();
+
+ headerCol.width = newPercent;
+ otherCol.width = totalPercent - newPercent;
+
+ delete this.dragHd;
+ view.setHdWidths();
+ view.refresh();
+
+ setTimeout(function(){
+ view.disableHeaders = disabled;
+ }, 100);
+ }
+});
+
+
+Ext.ListView.ColumnResizer = Ext.list.ColumnResizer;
+Ext.list.Sorter = Ext.extend(Ext.util.Observable, {
+
+ sortClasses : ["sort-asc", "sort-desc"],
+
+ constructor: function(config){
+ Ext.apply(this, config);
+ Ext.list.Sorter.superclass.constructor.call(this);
+ },
+
+ init : function(listView){
+ this.view = listView;
+ listView.on('render', this.initEvents, this);
+ },
+
+ initEvents : function(view){
+ view.mon(view.innerHd, 'click', this.onHdClick, this);
+ view.innerHd.setStyle('cursor', 'pointer');
+ view.mon(view.store, 'datachanged', this.updateSortState, this);
+ this.updateSortState.defer(10, this, [view.store]);
+ },
+
+ updateSortState : function(store){
+ var state = store.getSortState();
+ if(!state){
+ return;
+ }
+ this.sortState = state;
+ var cs = this.view.columns, sortColumn = -1;
+ for(var i = 0, len = cs.length; i < len; i++){
+ if(cs[i].dataIndex == state.field){
+ sortColumn = i;
+ break;
+ }
+ }
+ if(sortColumn != -1){
+ var sortDir = state.direction;
+ this.updateSortIcon(sortColumn, sortDir);
+ }
+ },
+
+ updateSortIcon : function(col, dir){
+ var sc = this.sortClasses;
+ var hds = this.view.innerHd.select('em').removeClass(sc);
+ hds.item(col).addClass(sc[dir == "DESC" ? 1 : 0]);
+ },
+
+ onHdClick : function(e){
+ var hd = e.getTarget('em', 3);
+ if(hd && !this.view.disableHeaders){
+ var index = this.view.findHeaderIndex(hd);
+ this.view.store.sort(this.view.columns[index].dataIndex);
+ }
+ }
+});
+
+
+Ext.ListView.Sorter = Ext.list.Sorter;
+Ext.TabPanel = Ext.extend(Ext.Panel, {
+
+
+
+ deferredRender : true,
+
+ tabWidth : 120,
+
+ minTabWidth : 30,
+
+ resizeTabs : false,
+
+ enableTabScroll : false,
+
+ scrollIncrement : 0,
+
+ scrollRepeatInterval : 400,
+
+ scrollDuration : 0.35,
+
+ animScroll : true,
+
+ tabPosition : 'top',
+
+ baseCls : 'x-tab-panel',
+
+ autoTabs : false,
+
+ autoTabSelector : 'div.x-tab',
+
+ activeTab : undefined,
+
+ tabMargin : 2,
+
+ plain : false,
+
+ wheelIncrement : 20,
+
+
+ idDelimiter : '__',
+
+
+ itemCls : 'x-tab-item',
+
+
+ elements : 'body',
+ headerAsText : false,
+ frame : false,
+ hideBorders :true,
+
+
+ initComponent : function(){
+ this.frame = false;
+ Ext.TabPanel.superclass.initComponent.call(this);
+ this.addEvents(
+
+ 'beforetabchange',
+
+ 'tabchange',
+
+ 'contextmenu'
+ );
+
+ this.setLayout(new Ext.layout.CardLayout(Ext.apply({
+ layoutOnCardChange: this.layoutOnTabChange,
+ deferredRender: this.deferredRender
+ }, this.layoutConfig)));
+
+ if(this.tabPosition == 'top'){
+ this.elements += ',header';
+ this.stripTarget = 'header';
+ }else {
+ this.elements += ',footer';
+ this.stripTarget = 'footer';
+ }
+ if(!this.stack){
+ this.stack = Ext.TabPanel.AccessStack();
+ }
+ this.initItems();
+ },
+
+
+ onRender : function(ct, position){
+ Ext.TabPanel.superclass.onRender.call(this, ct, position);
+
+ if(this.plain){
+ var pos = this.tabPosition == 'top' ? 'header' : 'footer';
+ this[pos].addClass('x-tab-panel-'+pos+'-plain');
+ }
+
+ var st = this[this.stripTarget];
+
+ this.stripWrap = st.createChild({cls:'x-tab-strip-wrap', cn:{
+ tag:'ul', cls:'x-tab-strip x-tab-strip-'+this.tabPosition}});
+
+ var beforeEl = (this.tabPosition=='bottom' ? this.stripWrap : null);
+ st.createChild({cls:'x-tab-strip-spacer'}, beforeEl);
+ this.strip = new Ext.Element(this.stripWrap.dom.firstChild);
+
+
+ this.edge = this.strip.createChild({tag:'li', cls:'x-tab-edge', cn: [{tag: 'span', cls: 'x-tab-strip-text', cn: '&#160;'}]});
+ this.strip.createChild({cls:'x-clear'});
+
+ this.body.addClass('x-tab-panel-body-'+this.tabPosition);
+
+
+ if(!this.itemTpl){
+ var tt = new Ext.Template(
+ '<li class="{cls}" id="{id}"><a class="x-tab-strip-close"></a>',
+ '<a class="x-tab-right" href="#"><em class="x-tab-left">',
+ '<span class="x-tab-strip-inner"><span class="x-tab-strip-text {iconCls}">{text}</span></span>',
+ '</em></a></li>'
+ );
+ tt.disableFormats = true;
+ tt.compile();
+ Ext.TabPanel.prototype.itemTpl = tt;
+ }
+
+ this.items.each(this.initTab, this);
+ },
+
+
+ afterRender : function(){
+ Ext.TabPanel.superclass.afterRender.call(this);
+ if(this.autoTabs){
+ this.readTabs(false);
+ }
+ if(this.activeTab !== undefined){
+ var item = Ext.isObject(this.activeTab) ? this.activeTab : this.items.get(this.activeTab);
+ delete this.activeTab;
+ this.setActiveTab(item);
+ }
+ },
+
+
+ initEvents : function(){
+ Ext.TabPanel.superclass.initEvents.call(this);
+ this.mon(this.strip, {
+ scope: this,
+ mousedown: this.onStripMouseDown,
+ contextmenu: this.onStripContextMenu
+ });
+ if(this.enableTabScroll){
+ this.mon(this.strip, 'mousewheel', this.onWheel, this);
+ }
+ },
+
+
+ findTargets : function(e){
+ var item = null,
+ itemEl = e.getTarget('li:not(.x-tab-edge)', this.strip);
+
+ if(itemEl){
+ item = this.getComponent(itemEl.id.split(this.idDelimiter)[1]);
+ if(item.disabled){
+ return {
+ close : null,
+ item : null,
+ el : null
+ };
+ }
+ }
+ return {
+ close : e.getTarget('.x-tab-strip-close', this.strip),
+ item : item,
+ el : itemEl
+ };
+ },
+
+
+ onStripMouseDown : function(e){
+ if(e.button !== 0){
+ return;
+ }
+ e.preventDefault();
+ var t = this.findTargets(e);
+ if(t.close){
+ if (t.item.fireEvent('beforeclose', t.item) !== false) {
+ t.item.fireEvent('close', t.item);
+ this.remove(t.item);
+ }
+ return;
+ }
+ if(t.item && t.item != this.activeTab){
+ this.setActiveTab(t.item);
+ }
+ },
+
+
+ onStripContextMenu : function(e){
+ e.preventDefault();
+ var t = this.findTargets(e);
+ if(t.item){
+ this.fireEvent('contextmenu', this, t.item, e);
+ }
+ },
+
+
+ readTabs : function(removeExisting){
+ if(removeExisting === true){
+ this.items.each(function(item){
+ this.remove(item);
+ }, this);
+ }
+ var tabs = this.el.query(this.autoTabSelector);
+ for(var i = 0, len = tabs.length; i < len; i++){
+ var tab = tabs[i],
+ title = tab.getAttribute('title');
+ tab.removeAttribute('title');
+ this.add({
+ title: title,
+ contentEl: tab
+ });
+ }
+ },
+
+
+ initTab : function(item, index){
+ var before = this.strip.dom.childNodes[index],
+ p = this.getTemplateArgs(item),
+ el = before ?
+ this.itemTpl.insertBefore(before, p) :
+ this.itemTpl.append(this.strip, p),
+ cls = 'x-tab-strip-over',
+ tabEl = Ext.get(el);
+
+ tabEl.hover(function(){
+ if(!item.disabled){
+ tabEl.addClass(cls);
+ }
+ }, function(){
+ tabEl.removeClass(cls);
+ });
+
+ if(item.tabTip){
+ tabEl.child('span.x-tab-strip-text', true).qtip = item.tabTip;
+ }
+ item.tabEl = el;
+
+
+ tabEl.select('a').on('click', function(e){
+ if(!e.getPageX()){
+ this.onStripMouseDown(e);
+ }
+ }, this, {preventDefault: true});
+
+ item.on({
+ scope: this,
+ disable: this.onItemDisabled,
+ enable: this.onItemEnabled,
+ titlechange: this.onItemTitleChanged,
+ iconchange: this.onItemIconChanged,
+ beforeshow: this.onBeforeShowItem
+ });
+ },
+
+
+
+
+ getTemplateArgs : function(item) {
+ var cls = item.closable ? 'x-tab-strip-closable' : '';
+ if(item.disabled){
+ cls += ' x-item-disabled';
+ }
+ if(item.iconCls){
+ cls += ' x-tab-with-icon';
+ }
+ if(item.tabCls){
+ cls += ' ' + item.tabCls;
+ }
+
+ return {
+ id: this.id + this.idDelimiter + item.getItemId(),
+ text: item.title,
+ cls: cls,
+ iconCls: item.iconCls || ''
+ };
+ },
+
+
+ onAdd : function(c){
+ Ext.TabPanel.superclass.onAdd.call(this, c);
+ if(this.rendered){
+ var items = this.items;
+ this.initTab(c, items.indexOf(c));
+ this.delegateUpdates();
+ }
+ },
+
+
+ onBeforeAdd : function(item){
+ var existing = item.events ? (this.items.containsKey(item.getItemId()) ? item : null) : this.items.get(item);
+ if(existing){
+ this.setActiveTab(item);
+ return false;
+ }
+ Ext.TabPanel.superclass.onBeforeAdd.apply(this, arguments);
+ var es = item.elements;
+ item.elements = es ? es.replace(',header', '') : es;
+ item.border = (item.border === true);
+ },
+
+
+ onRemove : function(c){
+ var te = Ext.get(c.tabEl);
+
+ if(te){
+ te.select('a').removeAllListeners();
+ Ext.destroy(te);
+ }
+ Ext.TabPanel.superclass.onRemove.call(this, c);
+ this.stack.remove(c);
+ delete c.tabEl;
+ c.un('disable', this.onItemDisabled, this);
+ c.un('enable', this.onItemEnabled, this);
+ c.un('titlechange', this.onItemTitleChanged, this);
+ c.un('iconchange', this.onItemIconChanged, this);
+ c.un('beforeshow', this.onBeforeShowItem, this);
+ if(c == this.activeTab){
+ var next = this.stack.next();
+ if(next){
+ this.setActiveTab(next);
+ }else if(this.items.getCount() > 0){
+ this.setActiveTab(0);
+ }else{
+ this.setActiveTab(null);
+ }
+ }
+ if(!this.destroying){
+ this.delegateUpdates();
+ }
+ },
+
+
+ onBeforeShowItem : function(item){
+ if(item != this.activeTab){
+ this.setActiveTab(item);
+ return false;
+ }
+ },
+
+
+ onItemDisabled : function(item){
+ var el = this.getTabEl(item);
+ if(el){
+ Ext.fly(el).addClass('x-item-disabled');
+ }
+ this.stack.remove(item);
+ },
+
+
+ onItemEnabled : function(item){
+ var el = this.getTabEl(item);
+ if(el){
+ Ext.fly(el).removeClass('x-item-disabled');
+ }
+ },
+
+
+ onItemTitleChanged : function(item){
+ var el = this.getTabEl(item);
+ if(el){
+ Ext.fly(el).child('span.x-tab-strip-text', true).innerHTML = item.title;
+ this.delegateUpdates();
+ }
+ },
+
+
+ onItemIconChanged : function(item, iconCls, oldCls){
+ var el = this.getTabEl(item);
+ if(el){
+ el = Ext.get(el);
+ el.child('span.x-tab-strip-text').replaceClass(oldCls, iconCls);
+ el[Ext.isEmpty(iconCls) ? 'removeClass' : 'addClass']('x-tab-with-icon');
+ this.delegateUpdates();
+ }
+ },
+
+
+ getTabEl : function(item){
+ var c = this.getComponent(item);
+ return c ? c.tabEl : null;
+ },
+
+
+ onResize : function(){
+ Ext.TabPanel.superclass.onResize.apply(this, arguments);
+ this.delegateUpdates();
+ },
+
+
+ beginUpdate : function(){
+ this.suspendUpdates = true;
+ },
+
+
+ endUpdate : function(){
+ this.suspendUpdates = false;
+ this.delegateUpdates();
+ },
+
+
+ hideTabStripItem : function(item){
+ item = this.getComponent(item);
+ var el = this.getTabEl(item);
+ if(el){
+ el.style.display = 'none';
+ this.delegateUpdates();
+ }
+ this.stack.remove(item);
+ },
+
+
+ unhideTabStripItem : function(item){
+ item = this.getComponent(item);
+ var el = this.getTabEl(item);
+ if(el){
+ el.style.display = '';
+ this.delegateUpdates();
+ }
+ },
+
+
+ delegateUpdates : function(){
+ var rendered = this.rendered;
+ if(this.suspendUpdates){
+ return;
+ }
+ if(this.resizeTabs && rendered){
+ this.autoSizeTabs();
+ }
+ if(this.enableTabScroll && rendered){
+ this.autoScrollTabs();
+ }
+ },
+
+
+ autoSizeTabs : function(){
+ var count = this.items.length,
+ ce = this.tabPosition != 'bottom' ? 'header' : 'footer',
+ ow = this[ce].dom.offsetWidth,
+ aw = this[ce].dom.clientWidth;
+
+ if(!this.resizeTabs || count < 1 || !aw){
+ return;
+ }
+
+ var each = Math.max(Math.min(Math.floor((aw-4) / count) - this.tabMargin, this.tabWidth), this.minTabWidth);
+ this.lastTabWidth = each;
+ var lis = this.strip.query('li:not(.x-tab-edge)');
+ for(var i = 0, len = lis.length; i < len; i++) {
+ var li = lis[i],
+ inner = Ext.fly(li).child('.x-tab-strip-inner', true),
+ tw = li.offsetWidth,
+ iw = inner.offsetWidth;
+ inner.style.width = (each - (tw-iw)) + 'px';
+ }
+ },
+
+
+ adjustBodyWidth : function(w){
+ if(this.header){
+ this.header.setWidth(w);
+ }
+ if(this.footer){
+ this.footer.setWidth(w);
+ }
+ return w;
+ },
+
+
+ setActiveTab : function(item){
+ item = this.getComponent(item);
+ if(this.fireEvent('beforetabchange', this, item, this.activeTab) === false){
+ return;
+ }
+ if(!this.rendered){
+ this.activeTab = item;
+ return;
+ }
+ if(this.activeTab != item){
+ if(this.activeTab){
+ var oldEl = this.getTabEl(this.activeTab);
+ if(oldEl){
+ Ext.fly(oldEl).removeClass('x-tab-strip-active');
+ }
+ }
+ this.activeTab = item;
+ if(item){
+ var el = this.getTabEl(item);
+ Ext.fly(el).addClass('x-tab-strip-active');
+ this.stack.add(item);
+
+ this.layout.setActiveItem(item);
+
+ this.delegateUpdates();
+ if(this.scrolling){
+ this.scrollToTab(item, this.animScroll);
+ }
+ }
+ this.fireEvent('tabchange', this, item);
+ }
+ },
+
+
+ getActiveTab : function(){
+ return this.activeTab || null;
+ },
+
+
+ getItem : function(item){
+ return this.getComponent(item);
+ },
+
+
+ autoScrollTabs : function(){
+ this.pos = this.tabPosition=='bottom' ? this.footer : this.header;
+ var count = this.items.length,
+ ow = this.pos.dom.offsetWidth,
+ tw = this.pos.dom.clientWidth,
+ wrap = this.stripWrap,
+ wd = wrap.dom,
+ cw = wd.offsetWidth,
+ pos = this.getScrollPos(),
+ l = this.edge.getOffsetsTo(this.stripWrap)[0] + pos;
+
+ if(!this.enableTabScroll || cw < 20){
+ return;
+ }
+ if(count == 0 || l <= tw){
+
+ wd.scrollLeft = 0;
+ wrap.setWidth(tw);
+ if(this.scrolling){
+ this.scrolling = false;
+ this.pos.removeClass('x-tab-scrolling');
+ this.scrollLeft.hide();
+ this.scrollRight.hide();
+
+ if(Ext.isAir || Ext.isWebKit){
+ wd.style.marginLeft = '';
+ wd.style.marginRight = '';
+ }
+ }
+ }else{
+ if(!this.scrolling){
+ this.pos.addClass('x-tab-scrolling');
+
+ if(Ext.isAir || Ext.isWebKit){
+ wd.style.marginLeft = '18px';
+ wd.style.marginRight = '18px';
+ }
+ }
+ tw -= wrap.getMargins('lr');
+ wrap.setWidth(tw > 20 ? tw : 20);
+ if(!this.scrolling){
+ if(!this.scrollLeft){
+ this.createScrollers();
+ }else{
+ this.scrollLeft.show();
+ this.scrollRight.show();
+ }
+ }
+ this.scrolling = true;
+ if(pos > (l-tw)){
+ wd.scrollLeft = l-tw;
+ }else{
+ this.scrollToTab(this.activeTab, false);
+ }
+ this.updateScrollButtons();
+ }
+ },
+
+
+ createScrollers : function(){
+ this.pos.addClass('x-tab-scrolling-' + this.tabPosition);
+ var h = this.stripWrap.dom.offsetHeight;
+
+
+ var sl = this.pos.insertFirst({
+ cls:'x-tab-scroller-left'
+ });
+ sl.setHeight(h);
+ sl.addClassOnOver('x-tab-scroller-left-over');
+ this.leftRepeater = new Ext.util.ClickRepeater(sl, {
+ interval : this.scrollRepeatInterval,
+ handler: this.onScrollLeft,
+ scope: this
+ });
+ this.scrollLeft = sl;
+
+
+ var sr = this.pos.insertFirst({
+ cls:'x-tab-scroller-right'
+ });
+ sr.setHeight(h);
+ sr.addClassOnOver('x-tab-scroller-right-over');
+ this.rightRepeater = new Ext.util.ClickRepeater(sr, {
+ interval : this.scrollRepeatInterval,
+ handler: this.onScrollRight,
+ scope: this
+ });
+ this.scrollRight = sr;
+ },
+
+
+ getScrollWidth : function(){
+ return this.edge.getOffsetsTo(this.stripWrap)[0] + this.getScrollPos();
+ },
+
+
+ getScrollPos : function(){
+ return parseInt(this.stripWrap.dom.scrollLeft, 10) || 0;
+ },
+
+
+ getScrollArea : function(){
+ return parseInt(this.stripWrap.dom.clientWidth, 10) || 0;
+ },
+
+
+ getScrollAnim : function(){
+ return {duration:this.scrollDuration, callback: this.updateScrollButtons, scope: this};
+ },
+
+
+ getScrollIncrement : function(){
+ return this.scrollIncrement || (this.resizeTabs ? this.lastTabWidth+2 : 100);
+ },
+
+
+
+ scrollToTab : function(item, animate){
+ if(!item){
+ return;
+ }
+ var el = this.getTabEl(item),
+ pos = this.getScrollPos(),
+ area = this.getScrollArea(),
+ left = Ext.fly(el).getOffsetsTo(this.stripWrap)[0] + pos,
+ right = left + el.offsetWidth;
+ if(left < pos){
+ this.scrollTo(left, animate);
+ }else if(right > (pos + area)){
+ this.scrollTo(right - area, animate);
+ }
+ },
+
+
+ scrollTo : function(pos, animate){
+ this.stripWrap.scrollTo('left', pos, animate ? this.getScrollAnim() : false);
+ if(!animate){
+ this.updateScrollButtons();
+ }
+ },
+
+ onWheel : function(e){
+ var d = e.getWheelDelta()*this.wheelIncrement*-1;
+ e.stopEvent();
+
+ var pos = this.getScrollPos(),
+ newpos = pos + d,
+ sw = this.getScrollWidth()-this.getScrollArea();
+
+ var s = Math.max(0, Math.min(sw, newpos));
+ if(s != pos){
+ this.scrollTo(s, false);
+ }
+ },
+
+
+ onScrollRight : function(){
+ var sw = this.getScrollWidth()-this.getScrollArea(),
+ pos = this.getScrollPos(),
+ s = Math.min(sw, pos + this.getScrollIncrement());
+ if(s != pos){
+ this.scrollTo(s, this.animScroll);
+ }
+ },
+
+
+ onScrollLeft : function(){
+ var pos = this.getScrollPos(),
+ s = Math.max(0, pos - this.getScrollIncrement());
+ if(s != pos){
+ this.scrollTo(s, this.animScroll);
+ }
+ },
+
+
+ updateScrollButtons : function(){
+ var pos = this.getScrollPos();
+ this.scrollLeft[pos === 0 ? 'addClass' : 'removeClass']('x-tab-scroller-left-disabled');
+ this.scrollRight[pos >= (this.getScrollWidth()-this.getScrollArea()) ? 'addClass' : 'removeClass']('x-tab-scroller-right-disabled');
+ },
+
+
+ beforeDestroy : function() {
+ Ext.destroy(this.leftRepeater, this.rightRepeater);
+ this.deleteMembers('strip', 'edge', 'scrollLeft', 'scrollRight', 'stripWrap');
+ this.activeTab = null;
+ Ext.TabPanel.superclass.beforeDestroy.apply(this);
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+});
+Ext.reg('tabpanel', Ext.TabPanel);
+
+
+Ext.TabPanel.prototype.activate = Ext.TabPanel.prototype.setActiveTab;
+
+
+Ext.TabPanel.AccessStack = function(){
+ var items = [];
+ return {
+ add : function(item){
+ items.push(item);
+ if(items.length > 10){
+ items.shift();
+ }
+ },
+
+ remove : function(item){
+ var s = [];
+ for(var i = 0, len = items.length; i < len; i++) {
+ if(items[i] != item){
+ s.push(items[i]);
+ }
+ }
+ items = s;
+ },
+
+ next : function(){
+ return items.pop();
+ }
+ };
+};
+
+Ext.Button = Ext.extend(Ext.BoxComponent, {
+
+ hidden : false,
+
+ disabled : false,
+
+ pressed : false,
+
+
+
+
+
+
+ enableToggle : false,
+
+
+
+ menuAlign : 'tl-bl?',
+
+
+
+
+ type : 'button',
+
+
+ menuClassTarget : 'tr:nth(2)',
+
+
+ clickEvent : 'click',
+
+
+ handleMouseEvents : true,
+
+
+ tooltipType : 'qtip',
+
+
+ buttonSelector : 'button:first-child',
+
+
+ scale : 'small',
+
+
+
+
+ iconAlign : 'left',
+
+
+ arrowAlign : 'right',
+
+
+
+
+
+
+ initComponent : function(){
+ if(this.menu){
+
+
+ if (Ext.isArray(this.menu)){
+ this.menu = { items: this.menu };
+ }
+
+
+
+ if (Ext.isObject(this.menu)){
+ this.menu.ownerCt = this;
+ }
+
+ this.menu = Ext.menu.MenuMgr.get(this.menu);
+ this.menu.ownerCt = undefined;
+ }
+
+ Ext.Button.superclass.initComponent.call(this);
+
+ this.addEvents(
+
+ 'click',
+
+ 'toggle',
+
+ 'mouseover',
+
+ 'mouseout',
+
+ 'menushow',
+
+ 'menuhide',
+
+ 'menutriggerover',
+
+ 'menutriggerout'
+ );
+
+ if(Ext.isString(this.toggleGroup)){
+ this.enableToggle = true;
+ }
+ },
+
+
+ getTemplateArgs : function(){
+ return [this.type, 'x-btn-' + this.scale + ' x-btn-icon-' + this.scale + '-' + this.iconAlign, this.getMenuClass(), this.cls, this.id];
+ },
+
+
+ setButtonClass : function(){
+ if(this.useSetClass){
+ if(!Ext.isEmpty(this.oldCls)){
+ this.el.removeClass([this.oldCls, 'x-btn-pressed']);
+ }
+ this.oldCls = (this.iconCls || this.icon) ? (this.text ? 'x-btn-text-icon' : 'x-btn-icon') : 'x-btn-noicon';
+ this.el.addClass([this.oldCls, this.pressed ? 'x-btn-pressed' : null]);
+ }
+ },
+
+
+ getMenuClass : function(){
+ return this.menu ? (this.arrowAlign != 'bottom' ? 'x-btn-arrow' : 'x-btn-arrow-bottom') : '';
+ },
+
+
+ onRender : function(ct, position){
+ if(!this.template){
+ if(!Ext.Button.buttonTemplate){
+
+ Ext.Button.buttonTemplate = new Ext.Template(
+ '<table id="{4}" cellspacing="0" class="x-btn {3}"><tbody class="{1}">',
+ '<tr><td class="x-btn-tl"><i>&#160;</i></td><td class="x-btn-tc"></td><td class="x-btn-tr"><i>&#160;</i></td></tr>',
+ '<tr><td class="x-btn-ml"><i>&#160;</i></td><td class="x-btn-mc"><em class="{2} x-unselectable" unselectable="on"><button type="{0}"></button></em></td><td class="x-btn-mr"><i>&#160;</i></td></tr>',
+ '<tr><td class="x-btn-bl"><i>&#160;</i></td><td class="x-btn-bc"></td><td class="x-btn-br"><i>&#160;</i></td></tr>',
+ '</tbody></table>');
+ Ext.Button.buttonTemplate.compile();
+ }
+ this.template = Ext.Button.buttonTemplate;
+ }
+
+ var btn, targs = this.getTemplateArgs();
+
+ if(position){
+ btn = this.template.insertBefore(position, targs, true);
+ }else{
+ btn = this.template.append(ct, targs, true);
+ }
+
+ this.btnEl = btn.child(this.buttonSelector);
+ this.mon(this.btnEl, {
+ scope: this,
+ focus: this.onFocus,
+ blur: this.onBlur
+ });
+
+ this.initButtonEl(btn, this.btnEl);
+
+ Ext.ButtonToggleMgr.register(this);
+ },
+
+
+ initButtonEl : function(btn, btnEl){
+ this.el = btn;
+ this.setIcon(this.icon);
+ this.setText(this.text);
+ this.setIconClass(this.iconCls);
+ if(Ext.isDefined(this.tabIndex)){
+ btnEl.dom.tabIndex = this.tabIndex;
+ }
+ if(this.tooltip){
+ this.setTooltip(this.tooltip, true);
+ }
+
+ if(this.handleMouseEvents){
+ this.mon(btn, {
+ scope: this,
+ mouseover: this.onMouseOver,
+ mousedown: this.onMouseDown
+ });
+
+
+
+ }
+
+ if(this.menu){
+ this.mon(this.menu, {
+ scope: this,
+ show: this.onMenuShow,
+ hide: this.onMenuHide
+ });
+ }
+
+ if(this.repeat){
+ var repeater = new Ext.util.ClickRepeater(btn, Ext.isObject(this.repeat) ? this.repeat : {});
+ this.mon(repeater, 'click', this.onRepeatClick, this);
+ }else{
+ this.mon(btn, this.clickEvent, this.onClick, this);
+ }
+ },
+
+
+ afterRender : function(){
+ Ext.Button.superclass.afterRender.call(this);
+ this.useSetClass = true;
+ this.setButtonClass();
+ this.doc = Ext.getDoc();
+ this.doAutoWidth();
+ },
+
+
+ setIconClass : function(cls){
+ this.iconCls = cls;
+ if(this.el){
+ this.btnEl.dom.className = '';
+ this.btnEl.addClass(['x-btn-text', cls || '']);
+ this.setButtonClass();
+ }
+ return this;
+ },
+
+
+ setTooltip : function(tooltip, initial){
+ if(this.rendered){
+ if(!initial){
+ this.clearTip();
+ }
+ if(Ext.isObject(tooltip)){
+ Ext.QuickTips.register(Ext.apply({
+ target: this.btnEl.id
+ }, tooltip));
+ this.tooltip = tooltip;
+ }else{
+ this.btnEl.dom[this.tooltipType] = tooltip;
+ }
+ }else{
+ this.tooltip = tooltip;
+ }
+ return this;
+ },
+
+
+ clearTip : function(){
+ if(Ext.isObject(this.tooltip)){
+ Ext.QuickTips.unregister(this.btnEl);
+ }
+ },
+
+
+ beforeDestroy : function(){
+ if(this.rendered){
+ this.clearTip();
+ }
+ if(this.menu && this.destroyMenu !== false) {
+ Ext.destroy(this.btnEl, this.menu);
+ }
+ Ext.destroy(this.repeater);
+ },
+
+
+ onDestroy : function(){
+ if(this.rendered){
+ this.doc.un('mouseover', this.monitorMouseOver, this);
+ this.doc.un('mouseup', this.onMouseUp, this);
+ delete this.doc;
+ delete this.btnEl;
+ Ext.ButtonToggleMgr.unregister(this);
+ }
+ Ext.Button.superclass.onDestroy.call(this);
+ },
+
+
+ doAutoWidth : function(){
+ if(this.autoWidth !== false && this.el && this.text && this.width === undefined){
+ this.el.setWidth('auto');
+ if(Ext.isIE7 && Ext.isStrict){
+ var ib = this.btnEl;
+ if(ib && ib.getWidth() > 20){
+ ib.clip();
+ ib.setWidth(Ext.util.TextMetrics.measure(ib, this.text).width+ib.getFrameWidth('lr'));
+ }
+ }
+ if(this.minWidth){
+ if(this.el.getWidth() < this.minWidth){
+ this.el.setWidth(this.minWidth);
+ }
+ }
+ }
+ },
+
+
+ setHandler : function(handler, scope){
+ this.handler = handler;
+ this.scope = scope;
+ return this;
+ },
+
+
+ setText : function(text){
+ this.text = text;
+ if(this.el){
+ this.btnEl.update(text || '&#160;');
+ this.setButtonClass();
+ }
+ this.doAutoWidth();
+ return this;
+ },
+
+
+ setIcon : function(icon){
+ this.icon = icon;
+ if(this.el){
+ this.btnEl.setStyle('background-image', icon ? 'url(' + icon + ')' : '');
+ this.setButtonClass();
+ }
+ return this;
+ },
+
+
+ getText : function(){
+ return this.text;
+ },
+
+
+ toggle : function(state, suppressEvent){
+ state = state === undefined ? !this.pressed : !!state;
+ if(state != this.pressed){
+ if(this.rendered){
+ this.el[state ? 'addClass' : 'removeClass']('x-btn-pressed');
+ }
+ this.pressed = state;
+ if(!suppressEvent){
+ this.fireEvent('toggle', this, state);
+ if(this.toggleHandler){
+ this.toggleHandler.call(this.scope || this, this, state);
+ }
+ }
+ }
+ return this;
+ },
+
+
+ onDisable : function(){
+ this.onDisableChange(true);
+ },
+
+
+ onEnable : function(){
+ this.onDisableChange(false);
+ },
+
+ onDisableChange : function(disabled){
+ if(this.el){
+ if(!Ext.isIE6 || !this.text){
+ this.el[disabled ? 'addClass' : 'removeClass'](this.disabledClass);
+ }
+ this.el.dom.disabled = disabled;
+ }
+ this.disabled = disabled;
+ },
+
+
+ showMenu : function(){
+ if(this.rendered && this.menu){
+ if(this.tooltip){
+ Ext.QuickTips.getQuickTip().cancelShow(this.btnEl);
+ }
+ if(this.menu.isVisible()){
+ this.menu.hide();
+ }
+ this.menu.ownerCt = this;
+ this.menu.show(this.el, this.menuAlign);
+ }
+ return this;
+ },
+
+
+ hideMenu : function(){
+ if(this.hasVisibleMenu()){
+ this.menu.hide();
+ }
+ return this;
+ },
+
+
+ hasVisibleMenu : function(){
+ return this.menu && this.menu.ownerCt == this && this.menu.isVisible();
+ },
+
+
+ onRepeatClick : function(repeat, e){
+ this.onClick(e);
+ },
+
+
+ onClick : function(e){
+ if(e){
+ e.preventDefault();
+ }
+ if(e.button !== 0){
+ return;
+ }
+ if(!this.disabled){
+ this.doToggle();
+ if(this.menu && !this.hasVisibleMenu() && !this.ignoreNextClick){
+ this.showMenu();
+ }
+ this.fireEvent('click', this, e);
+ if(this.handler){
+
+ this.handler.call(this.scope || this, this, e);
+ }
+ }
+ },
+
+
+ doToggle: function(){
+ if (this.enableToggle && (this.allowDepress !== false || !this.pressed)) {
+ this.toggle();
+ }
+ },
+
+
+ isMenuTriggerOver : function(e, internal){
+ return this.menu && !internal;
+ },
+
+
+ isMenuTriggerOut : function(e, internal){
+ return this.menu && !internal;
+ },
+
+
+ onMouseOver : function(e){
+ if(!this.disabled){
+ var internal = e.within(this.el, true);
+ if(!internal){
+ this.el.addClass('x-btn-over');
+ if(!this.monitoringMouseOver){
+ this.doc.on('mouseover', this.monitorMouseOver, this);
+ this.monitoringMouseOver = true;
+ }
+ this.fireEvent('mouseover', this, e);
+ }
+ if(this.isMenuTriggerOver(e, internal)){
+ this.fireEvent('menutriggerover', this, this.menu, e);
+ }
+ }
+ },
+
+
+ monitorMouseOver : function(e){
+ if(e.target != this.el.dom && !e.within(this.el)){
+ if(this.monitoringMouseOver){
+ this.doc.un('mouseover', this.monitorMouseOver, this);
+ this.monitoringMouseOver = false;
+ }
+ this.onMouseOut(e);
+ }
+ },
+
+
+ onMouseOut : function(e){
+ var internal = e.within(this.el) && e.target != this.el.dom;
+ this.el.removeClass('x-btn-over');
+ this.fireEvent('mouseout', this, e);
+ if(this.isMenuTriggerOut(e, internal)){
+ this.fireEvent('menutriggerout', this, this.menu, e);
+ }
+ },
+
+ focus : function() {
+ this.btnEl.focus();
+ },
+
+ blur : function() {
+ this.btnEl.blur();
+ },
+
+
+ onFocus : function(e){
+ if(!this.disabled){
+ this.el.addClass('x-btn-focus');
+ }
+ },
+
+ onBlur : function(e){
+ this.el.removeClass('x-btn-focus');
+ },
+
+
+ getClickEl : function(e, isUp){
+ return this.el;
+ },
+
+
+ onMouseDown : function(e){
+ if(!this.disabled && e.button === 0){
+ this.getClickEl(e).addClass('x-btn-click');
+ this.doc.on('mouseup', this.onMouseUp, this);
+ }
+ },
+
+ onMouseUp : function(e){
+ if(e.button === 0){
+ this.getClickEl(e, true).removeClass('x-btn-click');
+ this.doc.un('mouseup', this.onMouseUp, this);
+ }
+ },
+
+ onMenuShow : function(e){
+ if(this.menu.ownerCt == this){
+ this.menu.ownerCt = this;
+ this.ignoreNextClick = 0;
+ this.el.addClass('x-btn-menu-active');
+ this.fireEvent('menushow', this, this.menu);
+ }
+ },
+
+ onMenuHide : function(e){
+ if(this.menu.ownerCt == this){
+ this.el.removeClass('x-btn-menu-active');
+ this.ignoreNextClick = this.restoreClick.defer(250, this);
+ this.fireEvent('menuhide', this, this.menu);
+ delete this.menu.ownerCt;
+ }
+ },
+
+
+ restoreClick : function(){
+ this.ignoreNextClick = 0;
+ }
+
+
+
+
+
+
+
+});
+Ext.reg('button', Ext.Button);
+
+
+Ext.ButtonToggleMgr = function(){
+ var groups = {};
+
+ function toggleGroup(btn, state){
+ if(state){
+ var g = groups[btn.toggleGroup];
+ for(var i = 0, l = g.length; i < l; i++){
+ if(g[i] != btn){
+ g[i].toggle(false);
+ }
+ }
+ }
+ }
+
+ return {
+ register : function(btn){
+ if(!btn.toggleGroup){
+ return;
+ }
+ var g = groups[btn.toggleGroup];
+ if(!g){
+ g = groups[btn.toggleGroup] = [];
+ }
+ g.push(btn);
+ btn.on('toggle', toggleGroup);
+ },
+
+ unregister : function(btn){
+ if(!btn.toggleGroup){
+ return;
+ }
+ var g = groups[btn.toggleGroup];
+ if(g){
+ g.remove(btn);
+ btn.un('toggle', toggleGroup);
+ }
+ },
+
+
+ getPressed : function(group){
+ var g = groups[group];
+ if(g){
+ for(var i = 0, len = g.length; i < len; i++){
+ if(g[i].pressed === true){
+ return g[i];
+ }
+ }
+ }
+ return null;
+ }
+ };
+}();
+
+Ext.SplitButton = Ext.extend(Ext.Button, {
+
+ arrowSelector : 'em',
+ split: true,
+
+
+ initComponent : function(){
+ Ext.SplitButton.superclass.initComponent.call(this);
+
+ this.addEvents("arrowclick");
+ },
+
+
+ onRender : function(){
+ Ext.SplitButton.superclass.onRender.apply(this, arguments);
+ if(this.arrowTooltip){
+ this.el.child(this.arrowSelector).dom[this.tooltipType] = this.arrowTooltip;
+ }
+ },
+
+
+ setArrowHandler : function(handler, scope){
+ this.arrowHandler = handler;
+ this.scope = scope;
+ },
+
+ getMenuClass : function(){
+ return 'x-btn-split' + (this.arrowAlign == 'bottom' ? '-bottom' : '');
+ },
+
+ isClickOnArrow : function(e){
+ if (this.arrowAlign != 'bottom') {
+ var visBtn = this.el.child('em.x-btn-split');
+ var right = visBtn.getRegion().right - visBtn.getPadding('r');
+ return e.getPageX() > right;
+ } else {
+ return e.getPageY() > this.btnEl.getRegion().bottom;
+ }
+ },
+
+
+ onClick : function(e, t){
+ e.preventDefault();
+ if(!this.disabled){
+ if(this.isClickOnArrow(e)){
+ if(this.menu && !this.menu.isVisible() && !this.ignoreNextClick){
+ this.showMenu();
+ }
+ this.fireEvent("arrowclick", this, e);
+ if(this.arrowHandler){
+ this.arrowHandler.call(this.scope || this, this, e);
+ }
+ }else{
+ this.doToggle();
+ this.fireEvent("click", this, e);
+ if(this.handler){
+ this.handler.call(this.scope || this, this, e);
+ }
+ }
+ }
+ },
+
+
+ isMenuTriggerOver : function(e){
+ return this.menu && e.target.tagName == this.arrowSelector;
+ },
+
+
+ isMenuTriggerOut : function(e, internal){
+ return this.menu && e.target.tagName != this.arrowSelector;
+ }
+});
+
+Ext.reg('splitbutton', Ext.SplitButton);
+Ext.CycleButton = Ext.extend(Ext.SplitButton, {
+
+
+
+
+
+
+
+
+ getItemText : function(item){
+ if(item && this.showText === true){
+ var text = '';
+ if(this.prependText){
+ text += this.prependText;
+ }
+ text += item.text;
+ return text;
+ }
+ return undefined;
+ },
+
+
+ setActiveItem : function(item, suppressEvent){
+ if(!Ext.isObject(item)){
+ item = this.menu.getComponent(item);
+ }
+ if(item){
+ if(!this.rendered){
+ this.text = this.getItemText(item);
+ this.iconCls = item.iconCls;
+ }else{
+ var t = this.getItemText(item);
+ if(t){
+ this.setText(t);
+ }
+ this.setIconClass(item.iconCls);
+ }
+ this.activeItem = item;
+ if(!item.checked){
+ item.setChecked(true, suppressEvent);
+ }
+ if(this.forceIcon){
+ this.setIconClass(this.forceIcon);
+ }
+ if(!suppressEvent){
+ this.fireEvent('change', this, item);
+ }
+ }
+ },
+
+
+ getActiveItem : function(){
+ return this.activeItem;
+ },
+
+
+ initComponent : function(){
+ this.addEvents(
+
+ "change"
+ );
+
+ if(this.changeHandler){
+ this.on('change', this.changeHandler, this.scope||this);
+ delete this.changeHandler;
+ }
+
+ this.itemCount = this.items.length;
+
+ this.menu = {cls:'x-cycle-menu', items:[]};
+ var checked = 0;
+ Ext.each(this.items, function(item, i){
+ Ext.apply(item, {
+ group: item.group || this.id,
+ itemIndex: i,
+ checkHandler: this.checkHandler,
+ scope: this,
+ checked: item.checked || false
+ });
+ this.menu.items.push(item);
+ if(item.checked){
+ checked = i;
+ }
+ }, this);
+ Ext.CycleButton.superclass.initComponent.call(this);
+ this.on('click', this.toggleSelected, this);
+ this.setActiveItem(checked, true);
+ },
+
+
+ checkHandler : function(item, pressed){
+ if(pressed){
+ this.setActiveItem(item);
+ }
+ },
+
+
+ toggleSelected : function(){
+ var m = this.menu;
+ m.render();
+
+ if(!m.hasLayout){
+ m.doLayout();
+ }
+
+ var nextIdx, checkItem;
+ for (var i = 1; i < this.itemCount; i++) {
+ nextIdx = (this.activeItem.itemIndex + i) % this.itemCount;
+
+ checkItem = m.items.itemAt(nextIdx);
+
+ if (!checkItem.disabled) {
+ checkItem.setChecked(true);
+ break;
+ }
+ }
+ }
+});
+Ext.reg('cycle', Ext.CycleButton);
+Ext.Toolbar = function(config){
+ if(Ext.isArray(config)){
+ config = {items: config, layout: 'toolbar'};
+ } else {
+ config = Ext.apply({
+ layout: 'toolbar'
+ }, config);
+ if(config.buttons) {
+ config.items = config.buttons;
+ }
+ }
+ Ext.Toolbar.superclass.constructor.call(this, config);
+};
+
+(function(){
+
+var T = Ext.Toolbar;
+
+Ext.extend(T, Ext.Container, {
+
+ defaultType: 'button',
+
+
+
+ enableOverflow : false,
+
+
+
+
+ trackMenus : true,
+ internalDefaults: {removeMode: 'container', hideParent: true},
+ toolbarCls: 'x-toolbar',
+
+ initComponent : function(){
+ T.superclass.initComponent.call(this);
+
+
+ this.addEvents('overflowchange');
+ },
+
+
+ onRender : function(ct, position){
+ if(!this.el){
+ if(!this.autoCreate){
+ this.autoCreate = {
+ cls: this.toolbarCls + ' x-small-editor'
+ };
+ }
+ this.el = ct.createChild(Ext.apply({ id: this.id },this.autoCreate), position);
+ Ext.Toolbar.superclass.onRender.apply(this, arguments);
+ }
+ },
+
+
+
+
+ lookupComponent : function(c){
+ if(Ext.isString(c)){
+ if(c == '-'){
+ c = new T.Separator();
+ }else if(c == ' '){
+ c = new T.Spacer();
+ }else if(c == '->'){
+ c = new T.Fill();
+ }else{
+ c = new T.TextItem(c);
+ }
+ this.applyDefaults(c);
+ }else{
+ if(c.isFormField || c.render){
+ c = this.createComponent(c);
+ }else if(c.tag){
+ c = new T.Item({autoEl: c});
+ }else if(c.tagName){
+ c = new T.Item({el:c});
+ }else if(Ext.isObject(c)){
+ c = c.xtype ? this.createComponent(c) : this.constructButton(c);
+ }
+ }
+ return c;
+ },
+
+
+ applyDefaults : function(c){
+ if(!Ext.isString(c)){
+ c = Ext.Toolbar.superclass.applyDefaults.call(this, c);
+ var d = this.internalDefaults;
+ if(c.events){
+ Ext.applyIf(c.initialConfig, d);
+ Ext.apply(c, d);
+ }else{
+ Ext.applyIf(c, d);
+ }
+ }
+ return c;
+ },
+
+
+ addSeparator : function(){
+ return this.add(new T.Separator());
+ },
+
+
+ addSpacer : function(){
+ return this.add(new T.Spacer());
+ },
+
+
+ addFill : function(){
+ this.add(new T.Fill());
+ },
+
+
+ addElement : function(el){
+ return this.addItem(new T.Item({el:el}));
+ },
+
+
+ addItem : function(item){
+ return this.add.apply(this, arguments);
+ },
+
+
+ addButton : function(config){
+ if(Ext.isArray(config)){
+ var buttons = [];
+ for(var i = 0, len = config.length; i < len; i++) {
+ buttons.push(this.addButton(config[i]));
+ }
+ return buttons;
+ }
+ return this.add(this.constructButton(config));
+ },
+
+
+ addText : function(text){
+ return this.addItem(new T.TextItem(text));
+ },
+
+
+ addDom : function(config){
+ return this.add(new T.Item({autoEl: config}));
+ },
+
+
+ addField : function(field){
+ return this.add(field);
+ },
+
+
+ insertButton : function(index, item){
+ if(Ext.isArray(item)){
+ var buttons = [];
+ for(var i = 0, len = item.length; i < len; i++) {
+ buttons.push(this.insertButton(index + i, item[i]));
+ }
+ return buttons;
+ }
+ return Ext.Toolbar.superclass.insert.call(this, index, item);
+ },
+
+
+ trackMenu : function(item, remove){
+ if(this.trackMenus && item.menu){
+ var method = remove ? 'mun' : 'mon';
+ this[method](item, 'menutriggerover', this.onButtonTriggerOver, this);
+ this[method](item, 'menushow', this.onButtonMenuShow, this);
+ this[method](item, 'menuhide', this.onButtonMenuHide, this);
+ }
+ },
+
+
+ constructButton : function(item){
+ var b = item.events ? item : this.createComponent(item, item.split ? 'splitbutton' : this.defaultType);
+ return b;
+ },
+
+
+ onAdd : function(c){
+ Ext.Toolbar.superclass.onAdd.call(this);
+ this.trackMenu(c);
+ if(this.disabled){
+ c.disable();
+ }
+ },
+
+
+ onRemove : function(c){
+ Ext.Toolbar.superclass.onRemove.call(this);
+ if (c == this.activeMenuBtn) {
+ delete this.activeMenuBtn;
+ }
+ this.trackMenu(c, true);
+ },
+
+
+ onDisable : function(){
+ this.items.each(function(item){
+ if(item.disable){
+ item.disable();
+ }
+ });
+ },
+
+
+ onEnable : function(){
+ this.items.each(function(item){
+ if(item.enable){
+ item.enable();
+ }
+ });
+ },
+
+
+ onButtonTriggerOver : function(btn){
+ if(this.activeMenuBtn && this.activeMenuBtn != btn){
+ this.activeMenuBtn.hideMenu();
+ btn.showMenu();
+ this.activeMenuBtn = btn;
+ }
+ },
+
+
+ onButtonMenuShow : function(btn){
+ this.activeMenuBtn = btn;
+ },
+
+
+ onButtonMenuHide : function(btn){
+ delete this.activeMenuBtn;
+ }
+});
+Ext.reg('toolbar', Ext.Toolbar);
+
+
+T.Item = Ext.extend(Ext.BoxComponent, {
+ hideParent: true,
+ enable:Ext.emptyFn,
+ disable:Ext.emptyFn,
+ focus:Ext.emptyFn
+
+});
+Ext.reg('tbitem', T.Item);
+
+
+T.Separator = Ext.extend(T.Item, {
+ onRender : function(ct, position){
+ this.el = ct.createChild({tag:'span', cls:'xtb-sep'}, position);
+ }
+});
+Ext.reg('tbseparator', T.Separator);
+
+
+T.Spacer = Ext.extend(T.Item, {
+
+
+ onRender : function(ct, position){
+ this.el = ct.createChild({tag:'div', cls:'xtb-spacer', style: this.width?'width:'+this.width+'px':''}, position);
+ }
+});
+Ext.reg('tbspacer', T.Spacer);
+
+
+T.Fill = Ext.extend(T.Item, {
+
+ render : Ext.emptyFn,
+ isFill : true
+});
+Ext.reg('tbfill', T.Fill);
+
+
+T.TextItem = Ext.extend(T.Item, {
+
+
+ constructor: function(config){
+ T.TextItem.superclass.constructor.call(this, Ext.isString(config) ? {text: config} : config);
+ },
+
+
+ onRender : function(ct, position) {
+ this.autoEl = {cls: 'xtb-text', html: this.text || ''};
+ T.TextItem.superclass.onRender.call(this, ct, position);
+ },
+
+
+ setText : function(t) {
+ if(this.rendered){
+ this.el.update(t);
+ }else{
+ this.text = t;
+ }
+ }
+});
+Ext.reg('tbtext', T.TextItem);
+
+
+T.Button = Ext.extend(Ext.Button, {});
+T.SplitButton = Ext.extend(Ext.SplitButton, {});
+Ext.reg('tbbutton', T.Button);
+Ext.reg('tbsplit', T.SplitButton);
+
+})();
+
+Ext.ButtonGroup = Ext.extend(Ext.Panel, {
+
+
+ baseCls: 'x-btn-group',
+
+ layout:'table',
+ defaultType: 'button',
+
+ frame: true,
+ internalDefaults: {removeMode: 'container', hideParent: true},
+
+ initComponent : function(){
+ this.layoutConfig = this.layoutConfig || {};
+ Ext.applyIf(this.layoutConfig, {
+ columns : this.columns
+ });
+ if(!this.title){
+ this.addClass('x-btn-group-notitle');
+ }
+ this.on('afterlayout', this.onAfterLayout, this);
+ Ext.ButtonGroup.superclass.initComponent.call(this);
+ },
+
+ applyDefaults : function(c){
+ c = Ext.ButtonGroup.superclass.applyDefaults.call(this, c);
+ var d = this.internalDefaults;
+ if(c.events){
+ Ext.applyIf(c.initialConfig, d);
+ Ext.apply(c, d);
+ }else{
+ Ext.applyIf(c, d);
+ }
+ return c;
+ },
+
+ onAfterLayout : function(){
+ var bodyWidth = this.body.getFrameWidth('lr') + this.body.dom.firstChild.offsetWidth;
+ this.body.setWidth(bodyWidth);
+ this.el.setWidth(bodyWidth + this.getFrameWidth());
+ }
+
+});
+
+Ext.reg('buttongroup', Ext.ButtonGroup);
+
+(function() {
+
+var T = Ext.Toolbar;
+
+Ext.PagingToolbar = Ext.extend(Ext.Toolbar, {
+
+
+
+ pageSize : 20,
+
+
+ displayMsg : 'Displaying {0} - {1} of {2}',
+
+ emptyMsg : 'No data to display',
+
+ beforePageText : 'Page',
+
+ afterPageText : 'of {0}',
+
+ firstText : 'First Page',
+
+ prevText : 'Previous Page',
+
+ nextText : 'Next Page',
+
+ lastText : 'Last Page',
+
+ refreshText : 'Refresh',
+
+
+
+
+
+
+
+ initComponent : function(){
+ var pagingItems = [this.first = new T.Button({
+ tooltip: this.firstText,
+ overflowText: this.firstText,
+ iconCls: 'x-tbar-page-first',
+ disabled: true,
+ handler: this.moveFirst,
+ scope: this
+ }), this.prev = new T.Button({
+ tooltip: this.prevText,
+ overflowText: this.prevText,
+ iconCls: 'x-tbar-page-prev',
+ disabled: true,
+ handler: this.movePrevious,
+ scope: this
+ }), '-', this.beforePageText,
+ this.inputItem = new Ext.form.NumberField({
+ cls: 'x-tbar-page-number',
+ allowDecimals: false,
+ allowNegative: false,
+ enableKeyEvents: true,
+ selectOnFocus: true,
+ submitValue: false,
+ listeners: {
+ scope: this,
+ keydown: this.onPagingKeyDown,
+ blur: this.onPagingBlur
+ }
+ }), this.afterTextItem = new T.TextItem({
+ text: String.format(this.afterPageText, 1)
+ }), '-', this.next = new T.Button({
+ tooltip: this.nextText,
+ overflowText: this.nextText,
+ iconCls: 'x-tbar-page-next',
+ disabled: true,
+ handler: this.moveNext,
+ scope: this
+ }), this.last = new T.Button({
+ tooltip: this.lastText,
+ overflowText: this.lastText,
+ iconCls: 'x-tbar-page-last',
+ disabled: true,
+ handler: this.moveLast,
+ scope: this
+ }), '-', this.refresh = new T.Button({
+ tooltip: this.refreshText,
+ overflowText: this.refreshText,
+ iconCls: 'x-tbar-loading',
+ handler: this.doRefresh,
+ scope: this
+ })];
+
+
+ var userItems = this.items || this.buttons || [];
+ if (this.prependButtons) {
+ this.items = userItems.concat(pagingItems);
+ }else{
+ this.items = pagingItems.concat(userItems);
+ }
+ delete this.buttons;
+ if(this.displayInfo){
+ this.items.push('->');
+ this.items.push(this.displayItem = new T.TextItem({}));
+ }
+ Ext.PagingToolbar.superclass.initComponent.call(this);
+ this.addEvents(
+
+ 'change',
+
+ 'beforechange'
+ );
+ this.on('afterlayout', this.onFirstLayout, this, {single: true});
+ this.cursor = 0;
+ this.bindStore(this.store, true);
+ },
+
+
+ onFirstLayout : function(){
+ if(this.dsLoaded){
+ this.onLoad.apply(this, this.dsLoaded);
+ }
+ },
+
+
+ updateInfo : function(){
+ if(this.displayItem){
+ var count = this.store.getCount();
+ var msg = count == 0 ?
+ this.emptyMsg :
+ String.format(
+ this.displayMsg,
+ this.cursor+1, this.cursor+count, this.store.getTotalCount()
+ );
+ this.displayItem.setText(msg);
+ }
+ },
+
+
+ onLoad : function(store, r, o){
+ if(!this.rendered){
+ this.dsLoaded = [store, r, o];
+ return;
+ }
+ var p = this.getParams();
+ this.cursor = (o.params && o.params[p.start]) ? o.params[p.start] : 0;
+ var d = this.getPageData(), ap = d.activePage, ps = d.pages;
+
+ this.afterTextItem.setText(String.format(this.afterPageText, d.pages));
+ this.inputItem.setValue(ap);
+ this.first.setDisabled(ap == 1);
+ this.prev.setDisabled(ap == 1);
+ this.next.setDisabled(ap == ps);
+ this.last.setDisabled(ap == ps);
+ this.refresh.enable();
+ this.updateInfo();
+ this.fireEvent('change', this, d);
+ },
+
+
+ getPageData : function(){
+ var total = this.store.getTotalCount();
+ return {
+ total : total,
+ activePage : Math.ceil((this.cursor+this.pageSize)/this.pageSize),
+ pages : total < this.pageSize ? 1 : Math.ceil(total/this.pageSize)
+ };
+ },
+
+
+ changePage : function(page){
+ this.doLoad(((page-1) * this.pageSize).constrain(0, this.store.getTotalCount()));
+ },
+
+
+ onLoadError : function(){
+ if(!this.rendered){
+ return;
+ }
+ this.refresh.enable();
+ },
+
+
+ readPage : function(d){
+ var v = this.inputItem.getValue(), pageNum;
+ if (!v || isNaN(pageNum = parseInt(v, 10))) {
+ this.inputItem.setValue(d.activePage);
+ return false;
+ }
+ return pageNum;
+ },
+
+ onPagingFocus : function(){
+ this.inputItem.select();
+ },
+
+
+ onPagingBlur : function(e){
+ this.inputItem.setValue(this.getPageData().activePage);
+ },
+
+
+ onPagingKeyDown : function(field, e){
+ var k = e.getKey(), d = this.getPageData(), pageNum;
+ if (k == e.RETURN) {
+ e.stopEvent();
+ pageNum = this.readPage(d);
+ if(pageNum !== false){
+ pageNum = Math.min(Math.max(1, pageNum), d.pages) - 1;
+ this.doLoad(pageNum * this.pageSize);
+ }
+ }else if (k == e.HOME || k == e.END){
+ e.stopEvent();
+ pageNum = k == e.HOME ? 1 : d.pages;
+ field.setValue(pageNum);
+ }else if (k == e.UP || k == e.PAGEUP || k == e.DOWN || k == e.PAGEDOWN){
+ e.stopEvent();
+ if((pageNum = this.readPage(d))){
+ var increment = e.shiftKey ? 10 : 1;
+ if(k == e.DOWN || k == e.PAGEDOWN){
+ increment *= -1;
+ }
+ pageNum += increment;
+ if(pageNum >= 1 & pageNum <= d.pages){
+ field.setValue(pageNum);
+ }
+ }
+ }
+ },
+
+
+ getParams : function(){
+
+ return this.paramNames || this.store.paramNames;
+ },
+
+
+ beforeLoad : function(){
+ if(this.rendered && this.refresh){
+ this.refresh.disable();
+ }
+ },
+
+
+ doLoad : function(start){
+ var o = {}, pn = this.getParams();
+ o[pn.start] = start;
+ o[pn.limit] = this.pageSize;
+ if(this.fireEvent('beforechange', this, o) !== false){
+ this.store.load({params:o});
+ }
+ },
+
+
+ moveFirst : function(){
+ this.doLoad(0);
+ },
+
+
+ movePrevious : function(){
+ this.doLoad(Math.max(0, this.cursor-this.pageSize));
+ },
+
+
+ moveNext : function(){
+ this.doLoad(this.cursor+this.pageSize);
+ },
+
+
+ moveLast : function(){
+ var total = this.store.getTotalCount(),
+ extra = total % this.pageSize;
+
+ this.doLoad(extra ? (total - extra) : total - this.pageSize);
+ },
+
+
+ doRefresh : function(){
+ this.doLoad(this.cursor);
+ },
+
+
+ bindStore : function(store, initial){
+ var doLoad;
+ if(!initial && this.store){
+ if(store !== this.store && this.store.autoDestroy){
+ this.store.destroy();
+ }else{
+ this.store.un('beforeload', this.beforeLoad, this);
+ this.store.un('load', this.onLoad, this);
+ this.store.un('exception', this.onLoadError, this);
+ }
+ if(!store){
+ this.store = null;
+ }
+ }
+ if(store){
+ store = Ext.StoreMgr.lookup(store);
+ store.on({
+ scope: this,
+ beforeload: this.beforeLoad,
+ load: this.onLoad,
+ exception: this.onLoadError
+ });
+ doLoad = true;
+ }
+ this.store = store;
+ if(doLoad){
+ this.onLoad(store, null, {});
+ }
+ },
+
+
+ unbind : function(store){
+ this.bindStore(null);
+ },
+
+
+ bind : function(store){
+ this.bindStore(store);
+ },
+
+
+ onDestroy : function(){
+ this.bindStore(null);
+ Ext.PagingToolbar.superclass.onDestroy.call(this);
+ }
+});
+
+})();
+Ext.reg('paging', Ext.PagingToolbar);
+Ext.History = (function () {
+ var iframe, hiddenField;
+ var ready = false;
+ var currentToken;
+
+ function getHash() {
+ var href = location.href, i = href.indexOf("#"),
+ hash = i >= 0 ? href.substr(i + 1) : null;
+
+ if (Ext.isGecko) {
+ hash = decodeURIComponent(hash);
+ }
+ return hash;
+ }
+
+ function doSave() {
+ hiddenField.value = currentToken;
+ }
+
+ function handleStateChange(token) {
+ currentToken = token;
+ Ext.History.fireEvent('change', token);
+ }
+
+ function updateIFrame (token) {
+ var html = ['<html><body><div id="state">',Ext.util.Format.htmlEncode(token),'</div></body></html>'].join('');
+ try {
+ var doc = iframe.contentWindow.document;
+ doc.open();
+ doc.write(html);
+ doc.close();
+ return true;
+ } catch (e) {
+ return false;
+ }
+ }
+
+ function checkIFrame() {
+ if (!iframe.contentWindow || !iframe.contentWindow.document) {
+ setTimeout(checkIFrame, 10);
+ return;
+ }
+
+ var doc = iframe.contentWindow.document;
+ var elem = doc.getElementById("state");
+ var token = elem ? elem.innerText : null;
+
+ var hash = getHash();
+
+ setInterval(function () {
+
+ doc = iframe.contentWindow.document;
+ elem = doc.getElementById("state");
+
+ var newtoken = elem ? elem.innerText : null;
+
+ var newHash = getHash();
+
+ if (newtoken !== token) {
+ token = newtoken;
+ handleStateChange(token);
+ location.hash = token;
+ hash = token;
+ doSave();
+ } else if (newHash !== hash) {
+ hash = newHash;
+ updateIFrame(newHash);
+ }
+
+ }, 50);
+
+ ready = true;
+
+ Ext.History.fireEvent('ready', Ext.History);
+ }
+
+ function startUp() {
+ currentToken = hiddenField.value ? hiddenField.value : getHash();
+
+ if (Ext.isIE) {
+ checkIFrame();
+ } else {
+ var hash = getHash();
+ setInterval(function () {
+ var newHash = getHash();
+ if (newHash !== hash) {
+ hash = newHash;
+ handleStateChange(hash);
+ doSave();
+ }
+ }, 50);
+ ready = true;
+ Ext.History.fireEvent('ready', Ext.History);
+ }
+ }
+
+ return {
+
+ fieldId: 'x-history-field',
+
+ iframeId: 'x-history-frame',
+
+ events:{},
+
+
+ init: function (onReady, scope) {
+ if(ready) {
+ Ext.callback(onReady, scope, [this]);
+ return;
+ }
+ if(!Ext.isReady){
+ Ext.onReady(function(){
+ Ext.History.init(onReady, scope);
+ });
+ return;
+ }
+ hiddenField = Ext.getDom(Ext.History.fieldId);
+ if (Ext.isIE) {
+ iframe = Ext.getDom(Ext.History.iframeId);
+ }
+ this.addEvents(
+
+ 'ready',
+
+ 'change'
+ );
+ if(onReady){
+ this.on('ready', onReady, scope, {single:true});
+ }
+ startUp();
+ },
+
+
+ add: function (token, preventDup) {
+ if(preventDup !== false){
+ if(this.getToken() == token){
+ return true;
+ }
+ }
+ if (Ext.isIE) {
+ return updateIFrame(token);
+ } else {
+ location.hash = token;
+ return true;
+ }
+ },
+
+
+ back: function(){
+ history.go(-1);
+ },
+
+
+ forward: function(){
+ history.go(1);
+ },
+
+
+ getToken: function() {
+ return ready ? currentToken : getHash();
+ }
+ };
+})();
+Ext.apply(Ext.History, new Ext.util.Observable());
+Ext.Tip = Ext.extend(Ext.Panel, {
+
+
+
+ minWidth : 40,
+
+ maxWidth : 300,
+
+ shadow : "sides",
+
+ defaultAlign : "tl-bl?",
+ autoRender: true,
+ quickShowInterval : 250,
+
+
+ frame:true,
+ hidden:true,
+ baseCls: 'x-tip',
+ floating:{shadow:true,shim:true,useDisplay:true,constrain:false},
+ autoHeight:true,
+
+ closeAction: 'hide',
+
+
+ initComponent : function(){
+ Ext.Tip.superclass.initComponent.call(this);
+ if(this.closable && !this.title){
+ this.elements += ',header';
+ }
+ },
+
+
+ afterRender : function(){
+ Ext.Tip.superclass.afterRender.call(this);
+ if(this.closable){
+ this.addTool({
+ id: 'close',
+ handler: this[this.closeAction],
+ scope: this
+ });
+ }
+ },
+
+
+ showAt : function(xy){
+ Ext.Tip.superclass.show.call(this);
+ if(this.measureWidth !== false && (!this.initialConfig || typeof this.initialConfig.width != 'number')){
+ this.doAutoWidth();
+ }
+ if(this.constrainPosition){
+ xy = this.el.adjustForConstraints(xy);
+ }
+ this.setPagePosition(xy[0], xy[1]);
+ },
+
+
+ doAutoWidth : function(adjust){
+ adjust = adjust || 0;
+ var bw = this.body.getTextWidth();
+ if(this.title){
+ bw = Math.max(bw, this.header.child('span').getTextWidth(this.title));
+ }
+ bw += this.getFrameWidth() + (this.closable ? 20 : 0) + this.body.getPadding("lr") + adjust;
+ this.setWidth(bw.constrain(this.minWidth, this.maxWidth));
+
+
+ if(Ext.isIE7 && !this.repainted){
+ this.el.repaint();
+ this.repainted = true;
+ }
+ },
+
+
+ showBy : function(el, pos){
+ if(!this.rendered){
+ this.render(Ext.getBody());
+ }
+ this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign));
+ },
+
+ initDraggable : function(){
+ this.dd = new Ext.Tip.DD(this, typeof this.draggable == 'boolean' ? null : this.draggable);
+ this.header.addClass('x-tip-draggable');
+ }
+});
+
+Ext.reg('tip', Ext.Tip);
+
+
+Ext.Tip.DD = function(tip, config){
+ Ext.apply(this, config);
+ this.tip = tip;
+ Ext.Tip.DD.superclass.constructor.call(this, tip.el.id, 'WindowDD-'+tip.id);
+ this.setHandleElId(tip.header.id);
+ this.scroll = false;
+};
+
+Ext.extend(Ext.Tip.DD, Ext.dd.DD, {
+ moveOnly:true,
+ scroll:false,
+ headerOffsets:[100, 25],
+ startDrag : function(){
+ this.tip.el.disableShadow();
+ },
+ endDrag : function(e){
+ this.tip.el.enableShadow(true);
+ }
+});
+Ext.ToolTip = Ext.extend(Ext.Tip, {
+
+
+
+
+ showDelay : 500,
+
+ hideDelay : 200,
+
+ dismissDelay : 5000,
+
+
+ trackMouse : false,
+
+ anchorToTarget : true,
+
+ anchorOffset : 0,
+
+
+
+ targetCounter : 0,
+
+ constrainPosition : false,
+
+
+ initComponent : function(){
+ Ext.ToolTip.superclass.initComponent.call(this);
+ this.lastActive = new Date();
+ this.initTarget(this.target);
+ this.origAnchor = this.anchor;
+ },
+
+
+ onRender : function(ct, position){
+ Ext.ToolTip.superclass.onRender.call(this, ct, position);
+ this.anchorCls = 'x-tip-anchor-' + this.getAnchorPosition();
+ this.anchorEl = this.el.createChild({
+ cls: 'x-tip-anchor ' + this.anchorCls
+ });
+ },
+
+
+ afterRender : function(){
+ Ext.ToolTip.superclass.afterRender.call(this);
+ this.anchorEl.setStyle('z-index', this.el.getZIndex() + 1).setVisibilityMode(Ext.Element.DISPLAY);
+ },
+
+
+ initTarget : function(target){
+ var t;
+ if((t = Ext.get(target))){
+ if(this.target){
+ var tg = Ext.get(this.target);
+ this.mun(tg, 'mouseover', this.onTargetOver, this);
+ this.mun(tg, 'mouseout', this.onTargetOut, this);
+ this.mun(tg, 'mousemove', this.onMouseMove, this);
+ }
+ this.mon(t, {
+ mouseover: this.onTargetOver,
+ mouseout: this.onTargetOut,
+ mousemove: this.onMouseMove,
+ scope: this
+ });
+ this.target = t;
+ }
+ if(this.anchor){
+ this.anchorTarget = this.target;
+ }
+ },
+
+
+ onMouseMove : function(e){
+ var t = this.delegate ? e.getTarget(this.delegate) : this.triggerElement = true;
+ if (t) {
+ this.targetXY = e.getXY();
+ if (t === this.triggerElement) {
+ if(!this.hidden && this.trackMouse){
+ this.setPagePosition(this.getTargetXY());
+ }
+ } else {
+ this.hide();
+ this.lastActive = new Date(0);
+ this.onTargetOver(e);
+ }
+ } else if (!this.closable && this.isVisible()) {
+ this.hide();
+ }
+ },
+
+
+ getTargetXY : function(){
+ if(this.delegate){
+ this.anchorTarget = this.triggerElement;
+ }
+ if(this.anchor){
+ this.targetCounter++;
+ var offsets = this.getOffsets(),
+ xy = (this.anchorToTarget && !this.trackMouse) ? this.el.getAlignToXY(this.anchorTarget, this.getAnchorAlign()) : this.targetXY,
+ dw = Ext.lib.Dom.getViewWidth() - 5,
+ dh = Ext.lib.Dom.getViewHeight() - 5,
+ de = document.documentElement,
+ bd = document.body,
+ scrollX = (de.scrollLeft || bd.scrollLeft || 0) + 5,
+ scrollY = (de.scrollTop || bd.scrollTop || 0) + 5,
+ axy = [xy[0] + offsets[0], xy[1] + offsets[1]],
+ sz = this.getSize();
+
+ this.anchorEl.removeClass(this.anchorCls);
+
+ if(this.targetCounter < 2){
+ if(axy[0] < scrollX){
+ if(this.anchorToTarget){
+ this.defaultAlign = 'l-r';
+ if(this.mouseOffset){this.mouseOffset[0] *= -1;}
+ }
+ this.anchor = 'left';
+ return this.getTargetXY();
+ }
+ if(axy[0]+sz.width > dw){
+ if(this.anchorToTarget){
+ this.defaultAlign = 'r-l';
+ if(this.mouseOffset){this.mouseOffset[0] *= -1;}
+ }
+ this.anchor = 'right';
+ return this.getTargetXY();
+ }
+ if(axy[1] < scrollY){
+ if(this.anchorToTarget){
+ this.defaultAlign = 't-b';
+ if(this.mouseOffset){this.mouseOffset[1] *= -1;}
+ }
+ this.anchor = 'top';
+ return this.getTargetXY();
+ }
+ if(axy[1]+sz.height > dh){
+ if(this.anchorToTarget){
+ this.defaultAlign = 'b-t';
+ if(this.mouseOffset){this.mouseOffset[1] *= -1;}
+ }
+ this.anchor = 'bottom';
+ return this.getTargetXY();
+ }
+ }
+
+ this.anchorCls = 'x-tip-anchor-'+this.getAnchorPosition();
+ this.anchorEl.addClass(this.anchorCls);
+ this.targetCounter = 0;
+ return axy;
+ }else{
+ var mouseOffset = this.getMouseOffset();
+ return [this.targetXY[0]+mouseOffset[0], this.targetXY[1]+mouseOffset[1]];
+ }
+ },
+
+ getMouseOffset : function(){
+ var offset = this.anchor ? [0,0] : [15,18];
+ if(this.mouseOffset){
+ offset[0] += this.mouseOffset[0];
+ offset[1] += this.mouseOffset[1];
+ }
+ return offset;
+ },
+
+
+ getAnchorPosition : function(){
+ if(this.anchor){
+ this.tipAnchor = this.anchor.charAt(0);
+ }else{
+ var m = this.defaultAlign.match(/^([a-z]+)-([a-z]+)(\?)?$/);
+ if(!m){
+ throw 'AnchorTip.defaultAlign is invalid';
+ }
+ this.tipAnchor = m[1].charAt(0);
+ }
+
+ switch(this.tipAnchor){
+ case 't': return 'top';
+ case 'b': return 'bottom';
+ case 'r': return 'right';
+ }
+ return 'left';
+ },
+
+
+ getAnchorAlign : function(){
+ switch(this.anchor){
+ case 'top' : return 'tl-bl';
+ case 'left' : return 'tl-tr';
+ case 'right': return 'tr-tl';
+ default : return 'bl-tl';
+ }
+ },
+
+
+ getOffsets : function(){
+ var offsets,
+ ap = this.getAnchorPosition().charAt(0);
+ if(this.anchorToTarget && !this.trackMouse){
+ switch(ap){
+ case 't':
+ offsets = [0, 9];
+ break;
+ case 'b':
+ offsets = [0, -13];
+ break;
+ case 'r':
+ offsets = [-13, 0];
+ break;
+ default:
+ offsets = [9, 0];
+ break;
+ }
+ }else{
+ switch(ap){
+ case 't':
+ offsets = [-15-this.anchorOffset, 30];
+ break;
+ case 'b':
+ offsets = [-19-this.anchorOffset, -13-this.el.dom.offsetHeight];
+ break;
+ case 'r':
+ offsets = [-15-this.el.dom.offsetWidth, -13-this.anchorOffset];
+ break;
+ default:
+ offsets = [25, -13-this.anchorOffset];
+ break;
+ }
+ }
+ var mouseOffset = this.getMouseOffset();
+ offsets[0] += mouseOffset[0];
+ offsets[1] += mouseOffset[1];
+
+ return offsets;
+ },
+
+
+ onTargetOver : function(e){
+ if(this.disabled || e.within(this.target.dom, true)){
+ return;
+ }
+ var t = e.getTarget(this.delegate);
+ if (t) {
+ this.triggerElement = t;
+ this.clearTimer('hide');
+ this.targetXY = e.getXY();
+ this.delayShow();
+ }
+ },
+
+
+ delayShow : function(){
+ if(this.hidden && !this.showTimer){
+ if(this.lastActive.getElapsed() < this.quickShowInterval){
+ this.show();
+ }else{
+ this.showTimer = this.show.defer(this.showDelay, this);
+ }
+ }else if(!this.hidden && this.autoHide !== false){
+ this.show();
+ }
+ },
+
+
+ onTargetOut : function(e){
+ if(this.disabled || e.within(this.target.dom, true)){
+ return;
+ }
+ this.clearTimer('show');
+ if(this.autoHide !== false){
+ this.delayHide();
+ }
+ },
+
+
+ delayHide : function(){
+ if(!this.hidden && !this.hideTimer){
+ this.hideTimer = this.hide.defer(this.hideDelay, this);
+ }
+ },
+
+
+ hide: function(){
+ this.clearTimer('dismiss');
+ this.lastActive = new Date();
+ if(this.anchorEl){
+ this.anchorEl.hide();
+ }
+ Ext.ToolTip.superclass.hide.call(this);
+ delete this.triggerElement;
+ },
+
+
+ show : function(){
+ if(this.anchor){
+
+
+ this.showAt([-1000,-1000]);
+ this.origConstrainPosition = this.constrainPosition;
+ this.constrainPosition = false;
+ this.anchor = this.origAnchor;
+ }
+ this.showAt(this.getTargetXY());
+
+ if(this.anchor){
+ this.anchorEl.show();
+ this.syncAnchor();
+ this.constrainPosition = this.origConstrainPosition;
+ }else{
+ this.anchorEl.hide();
+ }
+ },
+
+
+ showAt : function(xy){
+ this.lastActive = new Date();
+ this.clearTimers();
+ Ext.ToolTip.superclass.showAt.call(this, xy);
+ if(this.dismissDelay && this.autoHide !== false){
+ this.dismissTimer = this.hide.defer(this.dismissDelay, this);
+ }
+ if(this.anchor && !this.anchorEl.isVisible()){
+ this.syncAnchor();
+ this.anchorEl.show();
+ }else{
+ this.anchorEl.hide();
+ }
+ },
+
+
+ syncAnchor : function(){
+ var anchorPos, targetPos, offset;
+ switch(this.tipAnchor.charAt(0)){
+ case 't':
+ anchorPos = 'b';
+ targetPos = 'tl';
+ offset = [20+this.anchorOffset, 2];
+ break;
+ case 'r':
+ anchorPos = 'l';
+ targetPos = 'tr';
+ offset = [-2, 11+this.anchorOffset];
+ break;
+ case 'b':
+ anchorPos = 't';
+ targetPos = 'bl';
+ offset = [20+this.anchorOffset, -2];
+ break;
+ default:
+ anchorPos = 'r';
+ targetPos = 'tl';
+ offset = [2, 11+this.anchorOffset];
+ break;
+ }
+ this.anchorEl.alignTo(this.el, anchorPos+'-'+targetPos, offset);
+ },
+
+
+ setPagePosition : function(x, y){
+ Ext.ToolTip.superclass.setPagePosition.call(this, x, y);
+ if(this.anchor){
+ this.syncAnchor();
+ }
+ },
+
+
+ clearTimer : function(name){
+ name = name + 'Timer';
+ clearTimeout(this[name]);
+ delete this[name];
+ },
+
+
+ clearTimers : function(){
+ this.clearTimer('show');
+ this.clearTimer('dismiss');
+ this.clearTimer('hide');
+ },
+
+
+ onShow : function(){
+ Ext.ToolTip.superclass.onShow.call(this);
+ Ext.getDoc().on('mousedown', this.onDocMouseDown, this);
+ },
+
+
+ onHide : function(){
+ Ext.ToolTip.superclass.onHide.call(this);
+ Ext.getDoc().un('mousedown', this.onDocMouseDown, this);
+ },
+
+
+ onDocMouseDown : function(e){
+ if(this.autoHide !== true && !this.closable && !e.within(this.el.dom)){
+ this.disable();
+ this.doEnable.defer(100, this);
+ }
+ },
+
+
+ doEnable : function(){
+ if(!this.isDestroyed){
+ this.enable();
+ }
+ },
+
+
+ onDisable : function(){
+ this.clearTimers();
+ this.hide();
+ },
+
+
+ adjustPosition : function(x, y){
+ if(this.constrainPosition){
+ var ay = this.targetXY[1], h = this.getSize().height;
+ if(y <= ay && (y+h) >= ay){
+ y = ay-h-5;
+ }
+ }
+ return {x : x, y: y};
+ },
+
+ beforeDestroy : function(){
+ this.clearTimers();
+ Ext.destroy(this.anchorEl);
+ delete this.anchorEl;
+ delete this.target;
+ delete this.anchorTarget;
+ delete this.triggerElement;
+ Ext.ToolTip.superclass.beforeDestroy.call(this);
+ },
+
+
+ onDestroy : function(){
+ Ext.getDoc().un('mousedown', this.onDocMouseDown, this);
+ Ext.ToolTip.superclass.onDestroy.call(this);
+ }
+});
+
+Ext.reg('tooltip', Ext.ToolTip);
+Ext.QuickTip = Ext.extend(Ext.ToolTip, {
+
+
+ interceptTitles : false,
+
+
+ tagConfig : {
+ namespace : "ext",
+ attribute : "qtip",
+ width : "qwidth",
+ target : "target",
+ title : "qtitle",
+ hide : "hide",
+ cls : "qclass",
+ align : "qalign",
+ anchor : "anchor"
+ },
+
+
+ initComponent : function(){
+ this.target = this.target || Ext.getDoc();
+ this.targets = this.targets || {};
+ Ext.QuickTip.superclass.initComponent.call(this);
+ },
+
+
+ register : function(config){
+ var cs = Ext.isArray(config) ? config : arguments;
+ for(var i = 0, len = cs.length; i < len; i++){
+ var c = cs[i];
+ var target = c.target;
+ if(target){
+ if(Ext.isArray(target)){
+ for(var j = 0, jlen = target.length; j < jlen; j++){
+ this.targets[Ext.id(target[j])] = c;
+ }
+ } else{
+ this.targets[Ext.id(target)] = c;
+ }
+ }
+ }
+ },
+
+
+ unregister : function(el){
+ delete this.targets[Ext.id(el)];
+ },
+
+
+ cancelShow: function(el){
+ var at = this.activeTarget;
+ el = Ext.get(el).dom;
+ if(this.isVisible()){
+ if(at && at.el == el){
+ this.hide();
+ }
+ }else if(at && at.el == el){
+ this.clearTimer('show');
+ }
+ },
+
+ getTipCfg: function(e) {
+ var t = e.getTarget(),
+ ttp,
+ cfg;
+ if(this.interceptTitles && t.title && Ext.isString(t.title)){
+ ttp = t.title;
+ t.qtip = ttp;
+ t.removeAttribute("title");
+ e.preventDefault();
+ }else{
+ cfg = this.tagConfig;
+ ttp = t.qtip || Ext.fly(t).getAttribute(cfg.attribute, cfg.namespace);
+ }
+ return ttp;
+ },
+
+
+ onTargetOver : function(e){
+ if(this.disabled){
+ return;
+ }
+ this.targetXY = e.getXY();
+ var t = e.getTarget();
+ if(!t || t.nodeType !== 1 || t == document || t == document.body){
+ return;
+ }
+ if(this.activeTarget && ((t == this.activeTarget.el) || Ext.fly(this.activeTarget.el).contains(t))){
+ this.clearTimer('hide');
+ this.show();
+ return;
+ }
+ if(t && this.targets[t.id]){
+ this.activeTarget = this.targets[t.id];
+ this.activeTarget.el = t;
+ this.anchor = this.activeTarget.anchor;
+ if(this.anchor){
+ this.anchorTarget = t;
+ }
+ this.delayShow();
+ return;
+ }
+ var ttp, et = Ext.fly(t), cfg = this.tagConfig, ns = cfg.namespace;
+ if(ttp = this.getTipCfg(e)){
+ var autoHide = et.getAttribute(cfg.hide, ns);
+ this.activeTarget = {
+ el: t,
+ text: ttp,
+ width: et.getAttribute(cfg.width, ns),
+ autoHide: autoHide != "user" && autoHide !== 'false',
+ title: et.getAttribute(cfg.title, ns),
+ cls: et.getAttribute(cfg.cls, ns),
+ align: et.getAttribute(cfg.align, ns)
+
+ };
+ this.anchor = et.getAttribute(cfg.anchor, ns);
+ if(this.anchor){
+ this.anchorTarget = t;
+ }
+ this.delayShow();
+ }
+ },
+
+
+ onTargetOut : function(e){
+
+
+ if (this.activeTarget && e.within(this.activeTarget.el) && !this.getTipCfg(e)) {
+ return;
+ }
+
+ this.clearTimer('show');
+ if(this.autoHide !== false){
+ this.delayHide();
+ }
+ },
+
+
+ showAt : function(xy){
+ var t = this.activeTarget;
+ if(t){
+ if(!this.rendered){
+ this.render(Ext.getBody());
+ this.activeTarget = t;
+ }
+ if(t.width){
+ this.setWidth(t.width);
+ this.body.setWidth(this.adjustBodyWidth(t.width - this.getFrameWidth()));
+ this.measureWidth = false;
+ } else{
+ this.measureWidth = true;
+ }
+ this.setTitle(t.title || '');
+ this.body.update(t.text);
+ this.autoHide = t.autoHide;
+ this.dismissDelay = t.dismissDelay || this.dismissDelay;
+ if(this.lastCls){
+ this.el.removeClass(this.lastCls);
+ delete this.lastCls;
+ }
+ if(t.cls){
+ this.el.addClass(t.cls);
+ this.lastCls = t.cls;
+ }
+ if(this.anchor){
+ this.constrainPosition = false;
+ }else if(t.align){
+ xy = this.el.getAlignToXY(t.el, t.align);
+ this.constrainPosition = false;
+ }else{
+ this.constrainPosition = true;
+ }
+ }
+ Ext.QuickTip.superclass.showAt.call(this, xy);
+ },
+
+
+ hide: function(){
+ delete this.activeTarget;
+ Ext.QuickTip.superclass.hide.call(this);
+ }
+});
+Ext.reg('quicktip', Ext.QuickTip);
+Ext.QuickTips = function(){
+ var tip,
+ disabled = false;
+
+ return {
+
+ init : function(autoRender){
+ if(!tip){
+ if(!Ext.isReady){
+ Ext.onReady(function(){
+ Ext.QuickTips.init(autoRender);
+ });
+ return;
+ }
+ tip = new Ext.QuickTip({
+ elements:'header,body',
+ disabled: disabled
+ });
+ if(autoRender !== false){
+ tip.render(Ext.getBody());
+ }
+ }
+ },
+
+
+ ddDisable : function(){
+
+ if(tip && !disabled){
+ tip.disable();
+ }
+ },
+
+
+ ddEnable : function(){
+
+ if(tip && !disabled){
+ tip.enable();
+ }
+ },
+
+
+ enable : function(){
+ if(tip){
+ tip.enable();
+ }
+ disabled = false;
+ },
+
+
+ disable : function(){
+ if(tip){
+ tip.disable();
+ }
+ disabled = true;
+ },
+
+
+ isEnabled : function(){
+ return tip !== undefined && !tip.disabled;
+ },
+
+
+ getQuickTip : function(){
+ return tip;
+ },
+
+
+ register : function(){
+ tip.register.apply(tip, arguments);
+ },
+
+
+ unregister : function(){
+ tip.unregister.apply(tip, arguments);
+ },
+
+
+ tips : function(){
+ tip.register.apply(tip, arguments);
+ }
+ };
+}();
+Ext.slider.Tip = Ext.extend(Ext.Tip, {
+ minWidth: 10,
+ offsets : [0, -10],
+
+ init: function(slider) {
+ slider.on({
+ scope : this,
+ dragstart: this.onSlide,
+ drag : this.onSlide,
+ dragend : this.hide,
+ destroy : this.destroy
+ });
+ },
+
+
+ onSlide : function(slider, e, thumb) {
+ this.show();
+ this.body.update(this.getText(thumb));
+ this.doAutoWidth();
+ this.el.alignTo(thumb.el, 'b-t?', this.offsets);
+ },
+
+
+ getText : function(thumb) {
+ return String(thumb.value);
+ }
+});
+
+
+Ext.ux.SliderTip = Ext.slider.Tip;
+Ext.tree.TreePanel = Ext.extend(Ext.Panel, {
+ rootVisible : true,
+ animate : Ext.enableFx,
+ lines : true,
+ enableDD : false,
+ hlDrop : Ext.enableFx,
+ pathSeparator : '/',
+
+
+ bubbleEvents : [],
+
+ initComponent : function(){
+ Ext.tree.TreePanel.superclass.initComponent.call(this);
+
+ if(!this.eventModel){
+ this.eventModel = new Ext.tree.TreeEventModel(this);
+ }
+
+
+ var l = this.loader;
+ if(!l){
+ l = new Ext.tree.TreeLoader({
+ dataUrl: this.dataUrl,
+ requestMethod: this.requestMethod
+ });
+ }else if(Ext.isObject(l) && !l.load){
+ l = new Ext.tree.TreeLoader(l);
+ }
+ this.loader = l;
+
+ this.nodeHash = {};
+
+
+ if(this.root){
+ var r = this.root;
+ delete this.root;
+ this.setRootNode(r);
+ }
+
+
+ this.addEvents(
+
+
+ 'append',
+
+ 'remove',
+
+ 'movenode',
+
+ 'insert',
+
+ 'beforeappend',
+
+ 'beforeremove',
+
+ 'beforemovenode',
+
+ 'beforeinsert',
+
+
+ 'beforeload',
+
+ 'load',
+
+ 'textchange',
+
+ 'beforeexpandnode',
+
+ 'beforecollapsenode',
+
+ 'expandnode',
+
+ 'disabledchange',
+
+ 'collapsenode',
+
+ 'beforeclick',
+
+ 'click',
+
+ 'containerclick',
+
+ 'checkchange',
+
+ 'beforedblclick',
+
+ 'dblclick',
+
+ 'containerdblclick',
+
+ 'contextmenu',
+
+ 'containercontextmenu',
+
+ 'beforechildrenrendered',
+
+ 'startdrag',
+
+ 'enddrag',
+
+ 'dragdrop',
+
+ 'beforenodedrop',
+
+ 'nodedrop',
+
+ 'nodedragover'
+ );
+ if(this.singleExpand){
+ this.on('beforeexpandnode', this.restrictExpand, this);
+ }
+ },
+
+
+ proxyNodeEvent : function(ename, a1, a2, a3, a4, a5, a6){
+ if(ename == 'collapse' || ename == 'expand' || ename == 'beforecollapse' || ename == 'beforeexpand' || ename == 'move' || ename == 'beforemove'){
+ ename = ename+'node';
+ }
+
+ return this.fireEvent(ename, a1, a2, a3, a4, a5, a6);
+ },
+
+
+
+ getRootNode : function(){
+ return this.root;
+ },
+
+
+ setRootNode : function(node){
+ this.destroyRoot();
+ if(!node.render){
+ node = this.loader.createNode(node);
+ }
+ this.root = node;
+ node.ownerTree = this;
+ node.isRoot = true;
+ this.registerNode(node);
+ if(!this.rootVisible){
+ var uiP = node.attributes.uiProvider;
+ node.ui = uiP ? new uiP(node) : new Ext.tree.RootTreeNodeUI(node);
+ }
+ if(this.innerCt){
+ this.clearInnerCt();
+ this.renderRoot();
+ }
+ return node;
+ },
+
+ clearInnerCt : function(){
+ this.innerCt.update('');
+ },
+
+
+ renderRoot : function(){
+ this.root.render();
+ if(!this.rootVisible){
+ this.root.renderChildren();
+ }
+ },
+
+
+ getNodeById : function(id){
+ return this.nodeHash[id];
+ },
+
+
+ registerNode : function(node){
+ this.nodeHash[node.id] = node;
+ },
+
+
+ unregisterNode : function(node){
+ delete this.nodeHash[node.id];
+ },
+
+
+ toString : function(){
+ return '[Tree'+(this.id?' '+this.id:'')+']';
+ },
+
+
+ restrictExpand : function(node){
+ var p = node.parentNode;
+ if(p){
+ if(p.expandedChild && p.expandedChild.parentNode == p){
+ p.expandedChild.collapse();
+ }
+ p.expandedChild = node;
+ }
+ },
+
+
+ getChecked : function(a, startNode){
+ startNode = startNode || this.root;
+ var r = [];
+ var f = function(){
+ if(this.attributes.checked){
+ r.push(!a ? this : (a == 'id' ? this.id : this.attributes[a]));
+ }
+ };
+ startNode.cascade(f);
+ return r;
+ },
+
+
+ getLoader : function(){
+ return this.loader;
+ },
+
+
+ expandAll : function(){
+ this.root.expand(true);
+ },
+
+
+ collapseAll : function(){
+ this.root.collapse(true);
+ },
+
+
+ getSelectionModel : function(){
+ if(!this.selModel){
+ this.selModel = new Ext.tree.DefaultSelectionModel();
+ }
+ return this.selModel;
+ },
+
+
+ expandPath : function(path, attr, callback){
+ if(Ext.isEmpty(path)){
+ if(callback){
+ callback(false, undefined);
+ }
+ return;
+ }
+ attr = attr || 'id';
+ var keys = path.split(this.pathSeparator);
+ var curNode = this.root;
+ if(curNode.attributes[attr] != keys[1]){
+ if(callback){
+ callback(false, null);
+ }
+ return;
+ }
+ var index = 1;
+ var f = function(){
+ if(++index == keys.length){
+ if(callback){
+ callback(true, curNode);
+ }
+ return;
+ }
+ var c = curNode.findChild(attr, keys[index]);
+ if(!c){
+ if(callback){
+ callback(false, curNode);
+ }
+ return;
+ }
+ curNode = c;
+ c.expand(false, false, f);
+ };
+ curNode.expand(false, false, f);
+ },
+
+
+ selectPath : function(path, attr, callback){
+ if(Ext.isEmpty(path)){
+ if(callback){
+ callback(false, undefined);
+ }
+ return;
+ }
+ attr = attr || 'id';
+ var keys = path.split(this.pathSeparator),
+ v = keys.pop();
+ if(keys.length > 1){
+ var f = function(success, node){
+ if(success && node){
+ var n = node.findChild(attr, v);
+ if(n){
+ n.select();
+ if(callback){
+ callback(true, n);
+ }
+ }else if(callback){
+ callback(false, n);
+ }
+ }else{
+ if(callback){
+ callback(false, n);
+ }
+ }
+ };
+ this.expandPath(keys.join(this.pathSeparator), attr, f);
+ }else{
+ this.root.select();
+ if(callback){
+ callback(true, this.root);
+ }
+ }
+ },
+
+
+ getTreeEl : function(){
+ return this.body;
+ },
+
+
+ onRender : function(ct, position){
+ Ext.tree.TreePanel.superclass.onRender.call(this, ct, position);
+ this.el.addClass('x-tree');
+ this.innerCt = this.body.createChild({tag:'ul',
+ cls:'x-tree-root-ct ' +
+ (this.useArrows ? 'x-tree-arrows' : this.lines ? 'x-tree-lines' : 'x-tree-no-lines')});
+ },
+
+
+ initEvents : function(){
+ Ext.tree.TreePanel.superclass.initEvents.call(this);
+
+ if(this.containerScroll){
+ Ext.dd.ScrollManager.register(this.body);
+ }
+ if((this.enableDD || this.enableDrop) && !this.dropZone){
+
+ this.dropZone = new Ext.tree.TreeDropZone(this, this.dropConfig || {
+ ddGroup: this.ddGroup || 'TreeDD', appendOnly: this.ddAppendOnly === true
+ });
+ }
+ if((this.enableDD || this.enableDrag) && !this.dragZone){
+
+ this.dragZone = new Ext.tree.TreeDragZone(this, this.dragConfig || {
+ ddGroup: this.ddGroup || 'TreeDD',
+ scroll: this.ddScroll
+ });
+ }
+ this.getSelectionModel().init(this);
+ },
+
+
+ afterRender : function(){
+ Ext.tree.TreePanel.superclass.afterRender.call(this);
+ this.renderRoot();
+ },
+
+ beforeDestroy : function(){
+ if(this.rendered){
+ Ext.dd.ScrollManager.unregister(this.body);
+ Ext.destroy(this.dropZone, this.dragZone);
+ }
+ this.destroyRoot();
+ Ext.destroy(this.loader);
+ this.nodeHash = this.root = this.loader = null;
+ Ext.tree.TreePanel.superclass.beforeDestroy.call(this);
+ },
+
+
+ destroyRoot : function(){
+ if(this.root && this.root.destroy){
+ this.root.destroy(true);
+ }
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+});
+
+Ext.tree.TreePanel.nodeTypes = {};
+
+Ext.reg('treepanel', Ext.tree.TreePanel);Ext.tree.TreeEventModel = function(tree){
+ this.tree = tree;
+ this.tree.on('render', this.initEvents, this);
+};
+
+Ext.tree.TreeEventModel.prototype = {
+ initEvents : function(){
+ var t = this.tree;
+
+ if(t.trackMouseOver !== false){
+ t.mon(t.innerCt, {
+ scope: this,
+ mouseover: this.delegateOver,
+ mouseout: this.delegateOut
+ });
+ }
+ t.mon(t.getTreeEl(), {
+ scope: this,
+ click: this.delegateClick,
+ dblclick: this.delegateDblClick,
+ contextmenu: this.delegateContextMenu
+ });
+ },
+
+ getNode : function(e){
+ var t;
+ if(t = e.getTarget('.x-tree-node-el', 10)){
+ var id = Ext.fly(t, '_treeEvents').getAttribute('tree-node-id', 'ext');
+ if(id){
+ return this.tree.getNodeById(id);
+ }
+ }
+ return null;
+ },
+
+ getNodeTarget : function(e){
+ var t = e.getTarget('.x-tree-node-icon', 1);
+ if(!t){
+ t = e.getTarget('.x-tree-node-el', 6);
+ }
+ return t;
+ },
+
+ delegateOut : function(e, t){
+ if(!this.beforeEvent(e)){
+ return;
+ }
+ if(e.getTarget('.x-tree-ec-icon', 1)){
+ var n = this.getNode(e);
+ this.onIconOut(e, n);
+ if(n == this.lastEcOver){
+ delete this.lastEcOver;
+ }
+ }
+ if((t = this.getNodeTarget(e)) && !e.within(t, true)){
+ this.onNodeOut(e, this.getNode(e));
+ }
+ },
+
+ delegateOver : function(e, t){
+ if(!this.beforeEvent(e)){
+ return;
+ }
+ if(Ext.isGecko && !this.trackingDoc){
+ Ext.getBody().on('mouseover', this.trackExit, this);
+ this.trackingDoc = true;
+ }
+ if(this.lastEcOver){
+ this.onIconOut(e, this.lastEcOver);
+ delete this.lastEcOver;
+ }
+ if(e.getTarget('.x-tree-ec-icon', 1)){
+ this.lastEcOver = this.getNode(e);
+ this.onIconOver(e, this.lastEcOver);
+ }
+ if(t = this.getNodeTarget(e)){
+ this.onNodeOver(e, this.getNode(e));
+ }
+ },
+
+ trackExit : function(e){
+ if(this.lastOverNode){
+ if(this.lastOverNode.ui && !e.within(this.lastOverNode.ui.getEl())){
+ this.onNodeOut(e, this.lastOverNode);
+ }
+ delete this.lastOverNode;
+ Ext.getBody().un('mouseover', this.trackExit, this);
+ this.trackingDoc = false;
+ }
+
+ },
+
+ delegateClick : function(e, t){
+ if(this.beforeEvent(e)){
+ if(e.getTarget('input[type=checkbox]', 1)){
+ this.onCheckboxClick(e, this.getNode(e));
+ }else if(e.getTarget('.x-tree-ec-icon', 1)){
+ this.onIconClick(e, this.getNode(e));
+ }else if(this.getNodeTarget(e)){
+ this.onNodeClick(e, this.getNode(e));
+ }
+ }else{
+ this.checkContainerEvent(e, 'click');
+ }
+ },
+
+ delegateDblClick : function(e, t){
+ if(this.beforeEvent(e)){
+ if(this.getNodeTarget(e)){
+ this.onNodeDblClick(e, this.getNode(e));
+ }
+ }else{
+ this.checkContainerEvent(e, 'dblclick');
+ }
+ },
+
+ delegateContextMenu : function(e, t){
+ if(this.beforeEvent(e)){
+ if(this.getNodeTarget(e)){
+ this.onNodeContextMenu(e, this.getNode(e));
+ }
+ }else{
+ this.checkContainerEvent(e, 'contextmenu');
+ }
+ },
+
+ checkContainerEvent: function(e, type){
+ if(this.disabled){
+ e.stopEvent();
+ return false;
+ }
+ this.onContainerEvent(e, type);
+ },
+
+ onContainerEvent: function(e, type){
+ this.tree.fireEvent('container' + type, this.tree, e);
+ },
+
+ onNodeClick : function(e, node){
+ node.ui.onClick(e);
+ },
+
+ onNodeOver : function(e, node){
+ this.lastOverNode = node;
+ node.ui.onOver(e);
+ },
+
+ onNodeOut : function(e, node){
+ node.ui.onOut(e);
+ },
+
+ onIconOver : function(e, node){
+ node.ui.addClass('x-tree-ec-over');
+ },
+
+ onIconOut : function(e, node){
+ node.ui.removeClass('x-tree-ec-over');
+ },
+
+ onIconClick : function(e, node){
+ node.ui.ecClick(e);
+ },
+
+ onCheckboxClick : function(e, node){
+ node.ui.onCheckChange(e);
+ },
+
+ onNodeDblClick : function(e, node){
+ node.ui.onDblClick(e);
+ },
+
+ onNodeContextMenu : function(e, node){
+ node.ui.onContextMenu(e);
+ },
+
+ beforeEvent : function(e){
+ var node = this.getNode(e);
+ if(this.disabled || !node || !node.ui){
+ e.stopEvent();
+ return false;
+ }
+ return true;
+ },
+
+ disable: function(){
+ this.disabled = true;
+ },
+
+ enable: function(){
+ this.disabled = false;
+ }
+};
+Ext.tree.DefaultSelectionModel = Ext.extend(Ext.util.Observable, {
+
+ constructor : function(config){
+ this.selNode = null;
+
+ this.addEvents(
+
+ 'selectionchange',
+
+
+ 'beforeselect'
+ );
+
+ Ext.apply(this, config);
+ Ext.tree.DefaultSelectionModel.superclass.constructor.call(this);
+ },
+
+ init : function(tree){
+ this.tree = tree;
+ tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this);
+ tree.on('click', this.onNodeClick, this);
+ },
+
+ onNodeClick : function(node, e){
+ this.select(node);
+ },
+
+
+ select : function(node, selectNextNode){
+
+ if (!Ext.fly(node.ui.wrap).isVisible() && selectNextNode) {
+ return selectNextNode.call(this, node);
+ }
+ var last = this.selNode;
+ if(node == last){
+ node.ui.onSelectedChange(true);
+ }else if(this.fireEvent('beforeselect', this, node, last) !== false){
+ if(last && last.ui){
+ last.ui.onSelectedChange(false);
+ }
+ this.selNode = node;
+ node.ui.onSelectedChange(true);
+ this.fireEvent('selectionchange', this, node, last);
+ }
+ return node;
+ },
+
+
+ unselect : function(node, silent){
+ if(this.selNode == node){
+ this.clearSelections(silent);
+ }
+ },
+
+
+ clearSelections : function(silent){
+ var n = this.selNode;
+ if(n){
+ n.ui.onSelectedChange(false);
+ this.selNode = null;
+ if(silent !== true){
+ this.fireEvent('selectionchange', this, null);
+ }
+ }
+ return n;
+ },
+
+
+ getSelectedNode : function(){
+ return this.selNode;
+ },
+
+
+ isSelected : function(node){
+ return this.selNode == node;
+ },
+
+
+ selectPrevious : function( s){
+ if(!(s = s || this.selNode || this.lastSelNode)){
+ return null;
+ }
+
+ var ps = s.previousSibling;
+ if(ps){
+ if(!ps.isExpanded() || ps.childNodes.length < 1){
+ return this.select(ps, this.selectPrevious);
+ } else{
+ var lc = ps.lastChild;
+ while(lc && lc.isExpanded() && Ext.fly(lc.ui.wrap).isVisible() && lc.childNodes.length > 0){
+ lc = lc.lastChild;
+ }
+ return this.select(lc, this.selectPrevious);
+ }
+ } else if(s.parentNode && (this.tree.rootVisible || !s.parentNode.isRoot)){
+ return this.select(s.parentNode, this.selectPrevious);
+ }
+ return null;
+ },
+
+
+ selectNext : function( s){
+ if(!(s = s || this.selNode || this.lastSelNode)){
+ return null;
+ }
+
+ if(s.firstChild && s.isExpanded() && Ext.fly(s.ui.wrap).isVisible()){
+ return this.select(s.firstChild, this.selectNext);
+ }else if(s.nextSibling){
+ return this.select(s.nextSibling, this.selectNext);
+ }else if(s.parentNode){
+ var newS = null;
+ s.parentNode.bubble(function(){
+ if(this.nextSibling){
+ newS = this.getOwnerTree().selModel.select(this.nextSibling, this.selectNext);
+ return false;
+ }
+ });
+ return newS;
+ }
+ return null;
+ },
+
+ onKeyDown : function(e){
+ var s = this.selNode || this.lastSelNode;
+
+ var sm = this;
+ if(!s){
+ return;
+ }
+ var k = e.getKey();
+ switch(k){
+ case e.DOWN:
+ e.stopEvent();
+ this.selectNext();
+ break;
+ case e.UP:
+ e.stopEvent();
+ this.selectPrevious();
+ break;
+ case e.RIGHT:
+ e.preventDefault();
+ if(s.hasChildNodes()){
+ if(!s.isExpanded()){
+ s.expand();
+ }else if(s.firstChild){
+ this.select(s.firstChild, e);
+ }
+ }
+ break;
+ case e.LEFT:
+ e.preventDefault();
+ if(s.hasChildNodes() && s.isExpanded()){
+ s.collapse();
+ }else if(s.parentNode && (this.tree.rootVisible || s.parentNode != this.tree.getRootNode())){
+ this.select(s.parentNode, e);
+ }
+ break;
+ };
+ }
+});
+
+
+Ext.tree.MultiSelectionModel = Ext.extend(Ext.util.Observable, {
+
+ constructor : function(config){
+ this.selNodes = [];
+ this.selMap = {};
+ this.addEvents(
+
+ 'selectionchange'
+ );
+ Ext.apply(this, config);
+ Ext.tree.MultiSelectionModel.superclass.constructor.call(this);
+ },
+
+ init : function(tree){
+ this.tree = tree;
+ tree.mon(tree.getTreeEl(), 'keydown', this.onKeyDown, this);
+ tree.on('click', this.onNodeClick, this);
+ },
+
+ onNodeClick : function(node, e){
+ if(e.ctrlKey && this.isSelected(node)){
+ this.unselect(node);
+ }else{
+ this.select(node, e, e.ctrlKey);
+ }
+ },
+
+
+ select : function(node, e, keepExisting){
+ if(keepExisting !== true){
+ this.clearSelections(true);
+ }
+ if(this.isSelected(node)){
+ this.lastSelNode = node;
+ return node;
+ }
+ this.selNodes.push(node);
+ this.selMap[node.id] = node;
+ this.lastSelNode = node;
+ node.ui.onSelectedChange(true);
+ this.fireEvent('selectionchange', this, this.selNodes);
+ return node;
+ },
+
+
+ unselect : function(node){
+ if(this.selMap[node.id]){
+ node.ui.onSelectedChange(false);
+ var sn = this.selNodes;
+ var index = sn.indexOf(node);
+ if(index != -1){
+ this.selNodes.splice(index, 1);
+ }
+ delete this.selMap[node.id];
+ this.fireEvent('selectionchange', this, this.selNodes);
+ }
+ },
+
+
+ clearSelections : function(suppressEvent){
+ var sn = this.selNodes;
+ if(sn.length > 0){
+ for(var i = 0, len = sn.length; i < len; i++){
+ sn[i].ui.onSelectedChange(false);
+ }
+ this.selNodes = [];
+ this.selMap = {};
+ if(suppressEvent !== true){
+ this.fireEvent('selectionchange', this, this.selNodes);
+ }
+ }
+ },
+
+
+ isSelected : function(node){
+ return this.selMap[node.id] ? true : false;
+ },
+
+
+ getSelectedNodes : function(){
+ return this.selNodes.concat([]);
+ },
+
+ onKeyDown : Ext.tree.DefaultSelectionModel.prototype.onKeyDown,
+
+ selectNext : Ext.tree.DefaultSelectionModel.prototype.selectNext,
+
+ selectPrevious : Ext.tree.DefaultSelectionModel.prototype.selectPrevious
+});
+Ext.data.Tree = Ext.extend(Ext.util.Observable, {
+
+ constructor: function(root){
+ this.nodeHash = {};
+
+ this.root = null;
+ if(root){
+ this.setRootNode(root);
+ }
+ this.addEvents(
+
+ "append",
+
+ "remove",
+
+ "move",
+
+ "insert",
+
+ "beforeappend",
+
+ "beforeremove",
+
+ "beforemove",
+
+ "beforeinsert"
+ );
+ Ext.data.Tree.superclass.constructor.call(this);
+ },
+
+
+ pathSeparator: "/",
+
+
+ proxyNodeEvent : function(){
+ return this.fireEvent.apply(this, arguments);
+ },
+
+
+ getRootNode : function(){
+ return this.root;
+ },
+
+
+ setRootNode : function(node){
+ this.root = node;
+ node.ownerTree = this;
+ node.isRoot = true;
+ this.registerNode(node);
+ return node;
+ },
+
+
+ getNodeById : function(id){
+ return this.nodeHash[id];
+ },
+
+
+ registerNode : function(node){
+ this.nodeHash[node.id] = node;
+ },
+
+
+ unregisterNode : function(node){
+ delete this.nodeHash[node.id];
+ },
+
+ toString : function(){
+ return "[Tree"+(this.id?" "+this.id:"")+"]";
+ }
+});
+
+
+Ext.data.Node = Ext.extend(Ext.util.Observable, {
+
+ constructor: function(attributes){
+
+ this.attributes = attributes || {};
+ this.leaf = this.attributes.leaf;
+
+ this.id = this.attributes.id;
+ if(!this.id){
+ this.id = Ext.id(null, "xnode-");
+ this.attributes.id = this.id;
+ }
+
+ this.childNodes = [];
+
+ this.parentNode = null;
+
+ this.firstChild = null;
+
+ this.lastChild = null;
+
+ this.previousSibling = null;
+
+ this.nextSibling = null;
+
+ this.addEvents({
+
+ "append" : true,
+
+ "remove" : true,
+
+ "move" : true,
+
+ "insert" : true,
+
+ "beforeappend" : true,
+
+ "beforeremove" : true,
+
+ "beforemove" : true,
+
+ "beforeinsert" : true
+ });
+ this.listeners = this.attributes.listeners;
+ Ext.data.Node.superclass.constructor.call(this);
+ },
+
+
+ fireEvent : function(evtName){
+
+ if(Ext.data.Node.superclass.fireEvent.apply(this, arguments) === false){
+ return false;
+ }
+
+ var ot = this.getOwnerTree();
+ if(ot){
+ if(ot.proxyNodeEvent.apply(ot, arguments) === false){
+ return false;
+ }
+ }
+ return true;
+ },
+
+
+ isLeaf : function(){
+ return this.leaf === true;
+ },
+
+
+ setFirstChild : function(node){
+ this.firstChild = node;
+ },
+
+
+ setLastChild : function(node){
+ this.lastChild = node;
+ },
+
+
+
+ isLast : function(){
+ return (!this.parentNode ? true : this.parentNode.lastChild == this);
+ },
+
+
+ isFirst : function(){
+ return (!this.parentNode ? true : this.parentNode.firstChild == this);
+ },
+
+
+ hasChildNodes : function(){
+ return !this.isLeaf() && this.childNodes.length > 0;
+ },
+
+
+ isExpandable : function(){
+ return this.attributes.expandable || this.hasChildNodes();
+ },
+
+
+ appendChild : function(node){
+ var multi = false;
+ if(Ext.isArray(node)){
+ multi = node;
+ }else if(arguments.length > 1){
+ multi = arguments;
+ }
+
+ if(multi){
+ for(var i = 0, len = multi.length; i < len; i++) {
+ this.appendChild(multi[i]);
+ }
+ }else{
+ if(this.fireEvent("beforeappend", this.ownerTree, this, node) === false){
+ return false;
+ }
+ var index = this.childNodes.length;
+ var oldParent = node.parentNode;
+
+ if(oldParent){
+ if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index) === false){
+ return false;
+ }
+ oldParent.removeChild(node);
+ }
+ index = this.childNodes.length;
+ if(index === 0){
+ this.setFirstChild(node);
+ }
+ this.childNodes.push(node);
+ node.parentNode = this;
+ var ps = this.childNodes[index-1];
+ if(ps){
+ node.previousSibling = ps;
+ ps.nextSibling = node;
+ }else{
+ node.previousSibling = null;
+ }
+ node.nextSibling = null;
+ this.setLastChild(node);
+ node.setOwnerTree(this.getOwnerTree());
+ this.fireEvent("append", this.ownerTree, this, node, index);
+ if(oldParent){
+ node.fireEvent("move", this.ownerTree, node, oldParent, this, index);
+ }
+ return node;
+ }
+ },
+
+
+ removeChild : function(node, destroy){
+ var index = this.childNodes.indexOf(node);
+ if(index == -1){
+ return false;
+ }
+ if(this.fireEvent("beforeremove", this.ownerTree, this, node) === false){
+ return false;
+ }
+
+
+ this.childNodes.splice(index, 1);
+
+
+ if(node.previousSibling){
+ node.previousSibling.nextSibling = node.nextSibling;
+ }
+ if(node.nextSibling){
+ node.nextSibling.previousSibling = node.previousSibling;
+ }
+
+
+ if(this.firstChild == node){
+ this.setFirstChild(node.nextSibling);
+ }
+ if(this.lastChild == node){
+ this.setLastChild(node.previousSibling);
+ }
+
+ this.fireEvent("remove", this.ownerTree, this, node);
+ if(destroy){
+ node.destroy(true);
+ }else{
+ node.clear();
+ }
+ return node;
+ },
+
+
+ clear : function(destroy){
+
+ this.setOwnerTree(null, destroy);
+ this.parentNode = this.previousSibling = this.nextSibling = null;
+ if(destroy){
+ this.firstChild = this.lastChild = null;
+ }
+ },
+
+
+ destroy : function( silent){
+
+ if(silent === true){
+ this.purgeListeners();
+ this.clear(true);
+ Ext.each(this.childNodes, function(n){
+ n.destroy(true);
+ });
+ this.childNodes = null;
+ }else{
+ this.remove(true);
+ }
+ },
+
+
+ insertBefore : function(node, refNode){
+ if(!refNode){
+ return this.appendChild(node);
+ }
+
+ if(node == refNode){
+ return false;
+ }
+
+ if(this.fireEvent("beforeinsert", this.ownerTree, this, node, refNode) === false){
+ return false;
+ }
+ var index = this.childNodes.indexOf(refNode);
+ var oldParent = node.parentNode;
+ var refIndex = index;
+
+
+ if(oldParent == this && this.childNodes.indexOf(node) < index){
+ refIndex--;
+ }
+
+
+ if(oldParent){
+ if(node.fireEvent("beforemove", node.getOwnerTree(), node, oldParent, this, index, refNode) === false){
+ return false;
+ }
+ oldParent.removeChild(node);
+ }
+ if(refIndex === 0){
+ this.setFirstChild(node);
+ }
+ this.childNodes.splice(refIndex, 0, node);
+ node.parentNode = this;
+ var ps = this.childNodes[refIndex-1];
+ if(ps){
+ node.previousSibling = ps;
+ ps.nextSibling = node;
+ }else{
+ node.previousSibling = null;
+ }
+ node.nextSibling = refNode;
+ refNode.previousSibling = node;
+ node.setOwnerTree(this.getOwnerTree());
+ this.fireEvent("insert", this.ownerTree, this, node, refNode);
+ if(oldParent){
+ node.fireEvent("move", this.ownerTree, node, oldParent, this, refIndex, refNode);
+ }
+ return node;
+ },
+
+
+ remove : function(destroy){
+ if (this.parentNode) {
+ this.parentNode.removeChild(this, destroy);
+ }
+ return this;
+ },
+
+
+ removeAll : function(destroy){
+ var cn = this.childNodes,
+ n;
+ while((n = cn[0])){
+ this.removeChild(n, destroy);
+ }
+ return this;
+ },
+
+
+ item : function(index){
+ return this.childNodes[index];
+ },
+
+
+ replaceChild : function(newChild, oldChild){
+ var s = oldChild ? oldChild.nextSibling : null;
+ this.removeChild(oldChild);
+ this.insertBefore(newChild, s);
+ return oldChild;
+ },
+
+
+ indexOf : function(child){
+ return this.childNodes.indexOf(child);
+ },
+
+
+ getOwnerTree : function(){
+
+ if(!this.ownerTree){
+ var p = this;
+ while(p){
+ if(p.ownerTree){
+ this.ownerTree = p.ownerTree;
+ break;
+ }
+ p = p.parentNode;
+ }
+ }
+ return this.ownerTree;
+ },
+
+
+ getDepth : function(){
+ var depth = 0;
+ var p = this;
+ while(p.parentNode){
+ ++depth;
+ p = p.parentNode;
+ }
+ return depth;
+ },
+
+
+ setOwnerTree : function(tree, destroy){
+
+ if(tree != this.ownerTree){
+ if(this.ownerTree){
+ this.ownerTree.unregisterNode(this);
+ }
+ this.ownerTree = tree;
+
+ if(destroy !== true){
+ Ext.each(this.childNodes, function(n){
+ n.setOwnerTree(tree);
+ });
+ }
+ if(tree){
+ tree.registerNode(this);
+ }
+ }
+ },
+
+
+ setId: function(id){
+ if(id !== this.id){
+ var t = this.ownerTree;
+ if(t){
+ t.unregisterNode(this);
+ }
+ this.id = this.attributes.id = id;
+ if(t){
+ t.registerNode(this);
+ }
+ this.onIdChange(id);
+ }
+ },
+
+
+ onIdChange: Ext.emptyFn,
+
+
+ getPath : function(attr){
+ attr = attr || "id";
+ var p = this.parentNode;
+ var b = [this.attributes[attr]];
+ while(p){
+ b.unshift(p.attributes[attr]);
+ p = p.parentNode;
+ }
+ var sep = this.getOwnerTree().pathSeparator;
+ return sep + b.join(sep);
+ },
+
+
+ bubble : function(fn, scope, args){
+ var p = this;
+ while(p){
+ if(fn.apply(scope || p, args || [p]) === false){
+ break;
+ }
+ p = p.parentNode;
+ }
+ },
+
+
+ cascade : function(fn, scope, args){
+ if(fn.apply(scope || this, args || [this]) !== false){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ cs[i].cascade(fn, scope, args);
+ }
+ }
+ },
+
+
+ eachChild : function(fn, scope, args){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ if(fn.apply(scope || cs[i], args || [cs[i]]) === false){
+ break;
+ }
+ }
+ },
+
+
+ findChild : function(attribute, value, deep){
+ return this.findChildBy(function(){
+ return this.attributes[attribute] == value;
+ }, null, deep);
+ },
+
+
+ findChildBy : function(fn, scope, deep){
+ var cs = this.childNodes,
+ len = cs.length,
+ i = 0,
+ n,
+ res;
+ for(; i < len; i++){
+ n = cs[i];
+ if(fn.call(scope || n, n) === true){
+ return n;
+ }else if (deep){
+ res = n.findChildBy(fn, scope, deep);
+ if(res != null){
+ return res;
+ }
+ }
+
+ }
+ return null;
+ },
+
+
+ sort : function(fn, scope){
+ var cs = this.childNodes;
+ var len = cs.length;
+ if(len > 0){
+ var sortFn = scope ? function(){fn.apply(scope, arguments);} : fn;
+ cs.sort(sortFn);
+ for(var i = 0; i < len; i++){
+ var n = cs[i];
+ n.previousSibling = cs[i-1];
+ n.nextSibling = cs[i+1];
+ if(i === 0){
+ this.setFirstChild(n);
+ }
+ if(i == len-1){
+ this.setLastChild(n);
+ }
+ }
+ }
+ },
+
+
+ contains : function(node){
+ return node.isAncestor(this);
+ },
+
+
+ isAncestor : function(node){
+ var p = this.parentNode;
+ while(p){
+ if(p == node){
+ return true;
+ }
+ p = p.parentNode;
+ }
+ return false;
+ },
+
+ toString : function(){
+ return "[Node"+(this.id?" "+this.id:"")+"]";
+ }
+});
+Ext.tree.TreeNode = Ext.extend(Ext.data.Node, {
+
+ constructor : function(attributes){
+ attributes = attributes || {};
+ if(Ext.isString(attributes)){
+ attributes = {text: attributes};
+ }
+ this.childrenRendered = false;
+ this.rendered = false;
+ Ext.tree.TreeNode.superclass.constructor.call(this, attributes);
+ this.expanded = attributes.expanded === true;
+ this.isTarget = attributes.isTarget !== false;
+ this.draggable = attributes.draggable !== false && attributes.allowDrag !== false;
+ this.allowChildren = attributes.allowChildren !== false && attributes.allowDrop !== false;
+
+
+ this.text = attributes.text;
+
+ this.disabled = attributes.disabled === true;
+
+ this.hidden = attributes.hidden === true;
+
+ this.addEvents(
+
+ 'textchange',
+
+ 'beforeexpand',
+
+ 'beforecollapse',
+
+ 'expand',
+
+ 'disabledchange',
+
+ 'collapse',
+
+ 'beforeclick',
+
+ 'click',
+
+ 'checkchange',
+
+ 'beforedblclick',
+
+ 'dblclick',
+
+ 'contextmenu',
+
+ 'beforechildrenrendered'
+ );
+
+ var uiClass = this.attributes.uiProvider || this.defaultUI || Ext.tree.TreeNodeUI;
+
+
+ this.ui = new uiClass(this);
+ },
+
+ preventHScroll : true,
+
+ isExpanded : function(){
+ return this.expanded;
+ },
+
+
+ getUI : function(){
+ return this.ui;
+ },
+
+ getLoader : function(){
+ var owner;
+ return this.loader || ((owner = this.getOwnerTree()) && owner.loader ? owner.loader : (this.loader = new Ext.tree.TreeLoader()));
+ },
+
+
+ setFirstChild : function(node){
+ var of = this.firstChild;
+ Ext.tree.TreeNode.superclass.setFirstChild.call(this, node);
+ if(this.childrenRendered && of && node != of){
+ of.renderIndent(true, true);
+ }
+ if(this.rendered){
+ this.renderIndent(true, true);
+ }
+ },
+
+
+ setLastChild : function(node){
+ var ol = this.lastChild;
+ Ext.tree.TreeNode.superclass.setLastChild.call(this, node);
+ if(this.childrenRendered && ol && node != ol){
+ ol.renderIndent(true, true);
+ }
+ if(this.rendered){
+ this.renderIndent(true, true);
+ }
+ },
+
+
+
+ appendChild : function(n){
+ if(!n.render && !Ext.isArray(n)){
+ n = this.getLoader().createNode(n);
+ }
+ var node = Ext.tree.TreeNode.superclass.appendChild.call(this, n);
+ if(node && this.childrenRendered){
+ node.render();
+ }
+ this.ui.updateExpandIcon();
+ return node;
+ },
+
+
+ removeChild : function(node, destroy){
+ this.ownerTree.getSelectionModel().unselect(node);
+ Ext.tree.TreeNode.superclass.removeChild.apply(this, arguments);
+
+ if(!destroy){
+ var rendered = node.ui.rendered;
+
+ if(rendered){
+ node.ui.remove();
+ }
+ if(rendered && this.childNodes.length < 1){
+ this.collapse(false, false);
+ }else{
+ this.ui.updateExpandIcon();
+ }
+ if(!this.firstChild && !this.isHiddenRoot()){
+ this.childrenRendered = false;
+ }
+ }
+ return node;
+ },
+
+
+ insertBefore : function(node, refNode){
+ if(!node.render){
+ node = this.getLoader().createNode(node);
+ }
+ var newNode = Ext.tree.TreeNode.superclass.insertBefore.call(this, node, refNode);
+ if(newNode && refNode && this.childrenRendered){
+ node.render();
+ }
+ this.ui.updateExpandIcon();
+ return newNode;
+ },
+
+
+ setText : function(text){
+ var oldText = this.text;
+ this.text = this.attributes.text = text;
+ if(this.rendered){
+ this.ui.onTextChange(this, text, oldText);
+ }
+ this.fireEvent('textchange', this, text, oldText);
+ },
+
+
+ setIconCls : function(cls){
+ var old = this.attributes.iconCls;
+ this.attributes.iconCls = cls;
+ if(this.rendered){
+ this.ui.onIconClsChange(this, cls, old);
+ }
+ },
+
+
+ setTooltip : function(tip, title){
+ this.attributes.qtip = tip;
+ this.attributes.qtipTitle = title;
+ if(this.rendered){
+ this.ui.onTipChange(this, tip, title);
+ }
+ },
+
+
+ setIcon : function(icon){
+ this.attributes.icon = icon;
+ if(this.rendered){
+ this.ui.onIconChange(this, icon);
+ }
+ },
+
+
+ setHref : function(href, target){
+ this.attributes.href = href;
+ this.attributes.hrefTarget = target;
+ if(this.rendered){
+ this.ui.onHrefChange(this, href, target);
+ }
+ },
+
+
+ setCls : function(cls){
+ var old = this.attributes.cls;
+ this.attributes.cls = cls;
+ if(this.rendered){
+ this.ui.onClsChange(this, cls, old);
+ }
+ },
+
+
+ select : function(){
+ var t = this.getOwnerTree();
+ if(t){
+ t.getSelectionModel().select(this);
+ }
+ },
+
+
+ unselect : function(silent){
+ var t = this.getOwnerTree();
+ if(t){
+ t.getSelectionModel().unselect(this, silent);
+ }
+ },
+
+
+ isSelected : function(){
+ var t = this.getOwnerTree();
+ return t ? t.getSelectionModel().isSelected(this) : false;
+ },
+
+
+ expand : function(deep, anim, callback, scope){
+ if(!this.expanded){
+ if(this.fireEvent('beforeexpand', this, deep, anim) === false){
+ return;
+ }
+ if(!this.childrenRendered){
+ this.renderChildren();
+ }
+ this.expanded = true;
+ if(!this.isHiddenRoot() && (this.getOwnerTree().animate && anim !== false) || anim){
+ this.ui.animExpand(function(){
+ this.fireEvent('expand', this);
+ this.runCallback(callback, scope || this, [this]);
+ if(deep === true){
+ this.expandChildNodes(true, true);
+ }
+ }.createDelegate(this));
+ return;
+ }else{
+ this.ui.expand();
+ this.fireEvent('expand', this);
+ this.runCallback(callback, scope || this, [this]);
+ }
+ }else{
+ this.runCallback(callback, scope || this, [this]);
+ }
+ if(deep === true){
+ this.expandChildNodes(true);
+ }
+ },
+
+ runCallback : function(cb, scope, args){
+ if(Ext.isFunction(cb)){
+ cb.apply(scope, args);
+ }
+ },
+
+ isHiddenRoot : function(){
+ return this.isRoot && !this.getOwnerTree().rootVisible;
+ },
+
+
+ collapse : function(deep, anim, callback, scope){
+ if(this.expanded && !this.isHiddenRoot()){
+ if(this.fireEvent('beforecollapse', this, deep, anim) === false){
+ return;
+ }
+ this.expanded = false;
+ if((this.getOwnerTree().animate && anim !== false) || anim){
+ this.ui.animCollapse(function(){
+ this.fireEvent('collapse', this);
+ this.runCallback(callback, scope || this, [this]);
+ if(deep === true){
+ this.collapseChildNodes(true);
+ }
+ }.createDelegate(this));
+ return;
+ }else{
+ this.ui.collapse();
+ this.fireEvent('collapse', this);
+ this.runCallback(callback, scope || this, [this]);
+ }
+ }else if(!this.expanded){
+ this.runCallback(callback, scope || this, [this]);
+ }
+ if(deep === true){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ cs[i].collapse(true, false);
+ }
+ }
+ },
+
+
+ delayedExpand : function(delay){
+ if(!this.expandProcId){
+ this.expandProcId = this.expand.defer(delay, this);
+ }
+ },
+
+
+ cancelExpand : function(){
+ if(this.expandProcId){
+ clearTimeout(this.expandProcId);
+ }
+ this.expandProcId = false;
+ },
+
+
+ toggle : function(){
+ if(this.expanded){
+ this.collapse();
+ }else{
+ this.expand();
+ }
+ },
+
+
+ ensureVisible : function(callback, scope){
+ var tree = this.getOwnerTree();
+ tree.expandPath(this.parentNode ? this.parentNode.getPath() : this.getPath(), false, function(){
+ var node = tree.getNodeById(this.id);
+ tree.getTreeEl().scrollChildIntoView(node.ui.anchor);
+ this.runCallback(callback, scope || this, [this]);
+ }.createDelegate(this));
+ },
+
+
+ expandChildNodes : function(deep, anim) {
+ var cs = this.childNodes,
+ i,
+ len = cs.length;
+ for (i = 0; i < len; i++) {
+ cs[i].expand(deep, anim);
+ }
+ },
+
+
+ collapseChildNodes : function(deep){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++) {
+ cs[i].collapse(deep);
+ }
+ },
+
+
+ disable : function(){
+ this.disabled = true;
+ this.unselect();
+ if(this.rendered && this.ui.onDisableChange){
+ this.ui.onDisableChange(this, true);
+ }
+ this.fireEvent('disabledchange', this, true);
+ },
+
+
+ enable : function(){
+ this.disabled = false;
+ if(this.rendered && this.ui.onDisableChange){
+ this.ui.onDisableChange(this, false);
+ }
+ this.fireEvent('disabledchange', this, false);
+ },
+
+
+ renderChildren : function(suppressEvent){
+ if(suppressEvent !== false){
+ this.fireEvent('beforechildrenrendered', this);
+ }
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++){
+ cs[i].render(true);
+ }
+ this.childrenRendered = true;
+ },
+
+
+ sort : function(fn, scope){
+ Ext.tree.TreeNode.superclass.sort.apply(this, arguments);
+ if(this.childrenRendered){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++){
+ cs[i].render(true);
+ }
+ }
+ },
+
+
+ render : function(bulkRender){
+ this.ui.render(bulkRender);
+ if(!this.rendered){
+
+ this.getOwnerTree().registerNode(this);
+ this.rendered = true;
+ if(this.expanded){
+ this.expanded = false;
+ this.expand(false, false);
+ }
+ }
+ },
+
+
+ renderIndent : function(deep, refresh){
+ if(refresh){
+ this.ui.childIndent = null;
+ }
+ this.ui.renderIndent();
+ if(deep === true && this.childrenRendered){
+ var cs = this.childNodes;
+ for(var i = 0, len = cs.length; i < len; i++){
+ cs[i].renderIndent(true, refresh);
+ }
+ }
+ },
+
+ beginUpdate : function(){
+ this.childrenRendered = false;
+ },
+
+ endUpdate : function(){
+ if(this.expanded && this.rendered){
+ this.renderChildren();
+ }
+ },
+
+
+ destroy : function(silent){
+ if(silent === true){
+ this.unselect(true);
+ }
+ Ext.tree.TreeNode.superclass.destroy.call(this, silent);
+ Ext.destroy(this.ui, this.loader);
+ this.ui = this.loader = null;
+ },
+
+
+ onIdChange : function(id){
+ this.ui.onIdChange(id);
+ }
+});
+
+Ext.tree.TreePanel.nodeTypes.node = Ext.tree.TreeNode;
+ Ext.tree.AsyncTreeNode = function(config){
+ this.loaded = config && config.loaded === true;
+ this.loading = false;
+ Ext.tree.AsyncTreeNode.superclass.constructor.apply(this, arguments);
+
+ this.addEvents('beforeload', 'load');
+
+
+};
+Ext.extend(Ext.tree.AsyncTreeNode, Ext.tree.TreeNode, {
+ expand : function(deep, anim, callback, scope){
+ if(this.loading){
+ var timer;
+ var f = function(){
+ if(!this.loading){
+ clearInterval(timer);
+ this.expand(deep, anim, callback, scope);
+ }
+ }.createDelegate(this);
+ timer = setInterval(f, 200);
+ return;
+ }
+ if(!this.loaded){
+ if(this.fireEvent("beforeload", this) === false){
+ return;
+ }
+ this.loading = true;
+ this.ui.beforeLoad(this);
+ var loader = this.loader || this.attributes.loader || this.getOwnerTree().getLoader();
+ if(loader){
+ loader.load(this, this.loadComplete.createDelegate(this, [deep, anim, callback, scope]), this);
+ return;
+ }
+ }
+ Ext.tree.AsyncTreeNode.superclass.expand.call(this, deep, anim, callback, scope);
+ },
+
+
+ isLoading : function(){
+ return this.loading;
+ },
+
+ loadComplete : function(deep, anim, callback, scope){
+ this.loading = false;
+ this.loaded = true;
+ this.ui.afterLoad(this);
+ this.fireEvent("load", this);
+ this.expand(deep, anim, callback, scope);
+ },
+
+
+ isLoaded : function(){
+ return this.loaded;
+ },
+
+ hasChildNodes : function(){
+ if(!this.isLeaf() && !this.loaded){
+ return true;
+ }else{
+ return Ext.tree.AsyncTreeNode.superclass.hasChildNodes.call(this);
+ }
+ },
+
+
+ reload : function(callback, scope){
+ this.collapse(false, false);
+ while(this.firstChild){
+ this.removeChild(this.firstChild).destroy();
+ }
+ this.childrenRendered = false;
+ this.loaded = false;
+ if(this.isHiddenRoot()){
+ this.expanded = false;
+ }
+ this.expand(false, false, callback, scope);
+ }
+});
+
+Ext.tree.TreePanel.nodeTypes.async = Ext.tree.AsyncTreeNode;
+Ext.tree.TreeNodeUI = Ext.extend(Object, {
+
+ constructor : function(node){
+ Ext.apply(this, {
+ node: node,
+ rendered: false,
+ animating: false,
+ wasLeaf: true,
+ ecc: 'x-tree-ec-icon x-tree-elbow',
+ emptyIcon: Ext.BLANK_IMAGE_URL
+ });
+ },
+
+
+ removeChild : function(node){
+ if(this.rendered){
+ this.ctNode.removeChild(node.ui.getEl());
+ }
+ },
+
+
+ beforeLoad : function(){
+ this.addClass("x-tree-node-loading");
+ },
+
+
+ afterLoad : function(){
+ this.removeClass("x-tree-node-loading");
+ },
+
+
+ onTextChange : function(node, text, oldText){
+ if(this.rendered){
+ this.textNode.innerHTML = text;
+ }
+ },
+
+
+ onIconClsChange : function(node, cls, oldCls){
+ if(this.rendered){
+ Ext.fly(this.iconNode).replaceClass(oldCls, cls);
+ }
+ },
+
+
+ onIconChange : function(node, icon){
+ if(this.rendered){
+
+ var empty = Ext.isEmpty(icon);
+ this.iconNode.src = empty ? this.emptyIcon : icon;
+ Ext.fly(this.iconNode)[empty ? 'removeClass' : 'addClass']('x-tree-node-inline-icon');
+ }
+ },
+
+
+ onTipChange : function(node, tip, title){
+ if(this.rendered){
+ var hasTitle = Ext.isDefined(title);
+ if(this.textNode.setAttributeNS){
+ this.textNode.setAttributeNS("ext", "qtip", tip);
+ if(hasTitle){
+ this.textNode.setAttributeNS("ext", "qtitle", title);
+ }
+ }else{
+ this.textNode.setAttribute("ext:qtip", tip);
+ if(hasTitle){
+ this.textNode.setAttribute("ext:qtitle", title);
+ }
+ }
+ }
+ },
+
+
+ onHrefChange : function(node, href, target){
+ if(this.rendered){
+ this.anchor.href = this.getHref(href);
+ if(Ext.isDefined(target)){
+ this.anchor.target = target;
+ }
+ }
+ },
+
+
+ onClsChange : function(node, cls, oldCls){
+ if(this.rendered){
+ Ext.fly(this.elNode).replaceClass(oldCls, cls);
+ }
+ },
+
+
+ onDisableChange : function(node, state){
+ this.disabled = state;
+ if (this.checkbox) {
+ this.checkbox.disabled = state;
+ }
+ this[state ? 'addClass' : 'removeClass']('x-tree-node-disabled');
+ },
+
+
+ onSelectedChange : function(state){
+ if(state){
+ this.focus();
+ this.addClass("x-tree-selected");
+ }else{
+
+ this.removeClass("x-tree-selected");
+ }
+ },
+
+
+ onMove : function(tree, node, oldParent, newParent, index, refNode){
+ this.childIndent = null;
+ if(this.rendered){
+ var targetNode = newParent.ui.getContainer();
+ if(!targetNode){
+ this.holder = document.createElement("div");
+ this.holder.appendChild(this.wrap);
+ return;
+ }
+ var insertBefore = refNode ? refNode.ui.getEl() : null;
+ if(insertBefore){
+ targetNode.insertBefore(this.wrap, insertBefore);
+ }else{
+ targetNode.appendChild(this.wrap);
+ }
+ this.node.renderIndent(true, oldParent != newParent);
+ }
+ },
+
+
+ addClass : function(cls){
+ if(this.elNode){
+ Ext.fly(this.elNode).addClass(cls);
+ }
+ },
+
+
+ removeClass : function(cls){
+ if(this.elNode){
+ Ext.fly(this.elNode).removeClass(cls);
+ }
+ },
+
+
+ remove : function(){
+ if(this.rendered){
+ this.holder = document.createElement("div");
+ this.holder.appendChild(this.wrap);
+ }
+ },
+
+
+ fireEvent : function(){
+ return this.node.fireEvent.apply(this.node, arguments);
+ },
+
+
+ initEvents : function(){
+ this.node.on("move", this.onMove, this);
+
+ if(this.node.disabled){
+ this.onDisableChange(this.node, true);
+ }
+ if(this.node.hidden){
+ this.hide();
+ }
+ var ot = this.node.getOwnerTree();
+ var dd = ot.enableDD || ot.enableDrag || ot.enableDrop;
+ if(dd && (!this.node.isRoot || ot.rootVisible)){
+ Ext.dd.Registry.register(this.elNode, {
+ node: this.node,
+ handles: this.getDDHandles(),
+ isHandle: false
+ });
+ }
+ },
+
+
+ getDDHandles : function(){
+ return [this.iconNode, this.textNode, this.elNode];
+ },
+
+
+ hide : function(){
+ this.node.hidden = true;
+ if(this.wrap){
+ this.wrap.style.display = "none";
+ }
+ },
+
+
+ show : function(){
+ this.node.hidden = false;
+ if(this.wrap){
+ this.wrap.style.display = "";
+ }
+ },
+
+
+ onContextMenu : function(e){
+ if (this.node.hasListener("contextmenu") || this.node.getOwnerTree().hasListener("contextmenu")) {
+ e.preventDefault();
+ this.focus();
+ this.fireEvent("contextmenu", this.node, e);
+ }
+ },
+
+
+ onClick : function(e){
+ if(this.dropping){
+ e.stopEvent();
+ return;
+ }
+ if(this.fireEvent("beforeclick", this.node, e) !== false){
+ var a = e.getTarget('a');
+ if(!this.disabled && this.node.attributes.href && a){
+ this.fireEvent("click", this.node, e);
+ return;
+ }else if(a && e.ctrlKey){
+ e.stopEvent();
+ }
+ e.preventDefault();
+ if(this.disabled){
+ return;
+ }
+
+ if(this.node.attributes.singleClickExpand && !this.animating && this.node.isExpandable()){
+ this.node.toggle();
+ }
+
+ this.fireEvent("click", this.node, e);
+ }else{
+ e.stopEvent();
+ }
+ },
+
+
+ onDblClick : function(e){
+ e.preventDefault();
+ if(this.disabled){
+ return;
+ }
+ if(this.fireEvent("beforedblclick", this.node, e) !== false){
+ if(this.checkbox){
+ this.toggleCheck();
+ }
+ if(!this.animating && this.node.isExpandable()){
+ this.node.toggle();
+ }
+ this.fireEvent("dblclick", this.node, e);
+ }
+ },
+
+ onOver : function(e){
+ this.addClass('x-tree-node-over');
+ },
+
+ onOut : function(e){
+ this.removeClass('x-tree-node-over');
+ },
+
+
+ onCheckChange : function(){
+ var checked = this.checkbox.checked;
+
+ this.checkbox.defaultChecked = checked;
+ this.node.attributes.checked = checked;
+ this.fireEvent('checkchange', this.node, checked);
+ },
+
+
+ ecClick : function(e){
+ if(!this.animating && this.node.isExpandable()){
+ this.node.toggle();
+ }
+ },
+
+
+ startDrop : function(){
+ this.dropping = true;
+ },
+
+
+ endDrop : function(){
+ setTimeout(function(){
+ this.dropping = false;
+ }.createDelegate(this), 50);
+ },
+
+
+ expand : function(){
+ this.updateExpandIcon();
+ this.ctNode.style.display = "";
+ },
+
+
+ focus : function(){
+ if(!this.node.preventHScroll){
+ try{this.anchor.focus();
+ }catch(e){}
+ }else{
+ try{
+ var noscroll = this.node.getOwnerTree().getTreeEl().dom;
+ var l = noscroll.scrollLeft;
+ this.anchor.focus();
+ noscroll.scrollLeft = l;
+ }catch(e){}
+ }
+ },
+
+
+ toggleCheck : function(value){
+ var cb = this.checkbox;
+ if(cb){
+ cb.checked = (value === undefined ? !cb.checked : value);
+ this.onCheckChange();
+ }
+ },
+
+
+ blur : function(){
+ try{
+ this.anchor.blur();
+ }catch(e){}
+ },
+
+
+ animExpand : function(callback){
+ var ct = Ext.get(this.ctNode);
+ ct.stopFx();
+ if(!this.node.isExpandable()){
+ this.updateExpandIcon();
+ this.ctNode.style.display = "";
+ Ext.callback(callback);
+ return;
+ }
+ this.animating = true;
+ this.updateExpandIcon();
+
+ ct.slideIn('t', {
+ callback : function(){
+ this.animating = false;
+ Ext.callback(callback);
+ },
+ scope: this,
+ duration: this.node.ownerTree.duration || .25
+ });
+ },
+
+
+ highlight : function(){
+ var tree = this.node.getOwnerTree();
+ Ext.fly(this.wrap).highlight(
+ tree.hlColor || "C3DAF9",
+ {endColor: tree.hlBaseColor}
+ );
+ },
+
+
+ collapse : function(){
+ this.updateExpandIcon();
+ this.ctNode.style.display = "none";
+ },
+
+
+ animCollapse : function(callback){
+ var ct = Ext.get(this.ctNode);
+ ct.enableDisplayMode('block');
+ ct.stopFx();
+
+ this.animating = true;
+ this.updateExpandIcon();
+
+ ct.slideOut('t', {
+ callback : function(){
+ this.animating = false;
+ Ext.callback(callback);
+ },
+ scope: this,
+ duration: this.node.ownerTree.duration || .25
+ });
+ },
+
+
+ getContainer : function(){
+ return this.ctNode;
+ },
+
+
+ getEl : function(){
+ return this.wrap;
+ },
+
+
+ appendDDGhost : function(ghostNode){
+ ghostNode.appendChild(this.elNode.cloneNode(true));
+ },
+
+
+ getDDRepairXY : function(){
+ return Ext.lib.Dom.getXY(this.iconNode);
+ },
+
+
+ onRender : function(){
+ this.render();
+ },
+
+
+ render : function(bulkRender){
+ var n = this.node, a = n.attributes;
+ var targetNode = n.parentNode ?
+ n.parentNode.ui.getContainer() : n.ownerTree.innerCt.dom;
+
+ if(!this.rendered){
+ this.rendered = true;
+
+ this.renderElements(n, a, targetNode, bulkRender);
+
+ if(a.qtip){
+ this.onTipChange(n, a.qtip, a.qtipTitle);
+ }else if(a.qtipCfg){
+ a.qtipCfg.target = Ext.id(this.textNode);
+ Ext.QuickTips.register(a.qtipCfg);
+ }
+ this.initEvents();
+ if(!this.node.expanded){
+ this.updateExpandIcon(true);
+ }
+ }else{
+ if(bulkRender === true) {
+ targetNode.appendChild(this.wrap);
+ }
+ }
+ },
+
+
+ renderElements : function(n, a, targetNode, bulkRender){
+
+ this.indentMarkup = n.parentNode ? n.parentNode.ui.getChildIndent() : '';
+
+ var cb = Ext.isBoolean(a.checked),
+ nel,
+ href = this.getHref(a.href),
+ buf = ['<li class="x-tree-node"><div ext:tree-node-id="',n.id,'" class="x-tree-node-el x-tree-node-leaf x-unselectable ', a.cls,'" unselectable="on">',
+ '<span class="x-tree-node-indent">',this.indentMarkup,"</span>",
+ '<img alt="" src="', this.emptyIcon, '" class="x-tree-ec-icon x-tree-elbow" />',
+ '<img alt="" src="', a.icon || this.emptyIcon, '" class="x-tree-node-icon',(a.icon ? " x-tree-node-inline-icon" : ""),(a.iconCls ? " "+a.iconCls : ""),'" unselectable="on" />',
+ cb ? ('<input class="x-tree-node-cb" type="checkbox" ' + (a.checked ? 'checked="checked" />' : '/>')) : '',
+ '<a hidefocus="on" class="x-tree-node-anchor" href="',href,'" tabIndex="1" ',
+ a.hrefTarget ? ' target="'+a.hrefTarget+'"' : "", '><span unselectable="on">',n.text,"</span></a></div>",
+ '<ul class="x-tree-node-ct" style="display:none;"></ul>',
+ "</li>"].join('');
+
+ if(bulkRender !== true && n.nextSibling && (nel = n.nextSibling.ui.getEl())){
+ this.wrap = Ext.DomHelper.insertHtml("beforeBegin", nel, buf);
+ }else{
+ this.wrap = Ext.DomHelper.insertHtml("beforeEnd", targetNode, buf);
+ }
+
+ this.elNode = this.wrap.childNodes[0];
+ this.ctNode = this.wrap.childNodes[1];
+ var cs = this.elNode.childNodes;
+ this.indentNode = cs[0];
+ this.ecNode = cs[1];
+ this.iconNode = cs[2];
+ var index = 3;
+ if(cb){
+ this.checkbox = cs[3];
+
+ this.checkbox.defaultChecked = this.checkbox.checked;
+ index++;
+ }
+ this.anchor = cs[index];
+ this.textNode = cs[index].firstChild;
+ },
+
+
+ getHref : function(href){
+ return Ext.isEmpty(href) ? (Ext.isGecko ? '' : '#') : href;
+ },
+
+
+ getAnchor : function(){
+ return this.anchor;
+ },
+
+
+ getTextEl : function(){
+ return this.textNode;
+ },
+
+
+ getIconEl : function(){
+ return this.iconNode;
+ },
+
+
+ isChecked : function(){
+ return this.checkbox ? this.checkbox.checked : false;
+ },
+
+
+ updateExpandIcon : function(){
+ if(this.rendered){
+ var n = this.node,
+ c1,
+ c2,
+ cls = n.isLast() ? "x-tree-elbow-end" : "x-tree-elbow",
+ hasChild = n.hasChildNodes();
+ if(hasChild || n.attributes.expandable){
+ if(n.expanded){
+ cls += "-minus";
+ c1 = "x-tree-node-collapsed";
+ c2 = "x-tree-node-expanded";
+ }else{
+ cls += "-plus";
+ c1 = "x-tree-node-expanded";
+ c2 = "x-tree-node-collapsed";
+ }
+ if(this.wasLeaf){
+ this.removeClass("x-tree-node-leaf");
+ this.wasLeaf = false;
+ }
+ if(this.c1 != c1 || this.c2 != c2){
+ Ext.fly(this.elNode).replaceClass(c1, c2);
+ this.c1 = c1; this.c2 = c2;
+ }
+ }else{
+ if(!this.wasLeaf){
+ Ext.fly(this.elNode).replaceClass("x-tree-node-expanded", "x-tree-node-collapsed");
+ delete this.c1;
+ delete this.c2;
+ this.wasLeaf = true;
+ }
+ }
+ var ecc = "x-tree-ec-icon "+cls;
+ if(this.ecc != ecc){
+ this.ecNode.className = ecc;
+ this.ecc = ecc;
+ }
+ }
+ },
+
+
+ onIdChange: function(id){
+ if(this.rendered){
+ this.elNode.setAttribute('ext:tree-node-id', id);
+ }
+ },
+
+
+ getChildIndent : function(){
+ if(!this.childIndent){
+ var buf = [],
+ p = this.node;
+ while(p){
+ if(!p.isRoot || (p.isRoot && p.ownerTree.rootVisible)){
+ if(!p.isLast()) {
+ buf.unshift('<img alt="" src="'+this.emptyIcon+'" class="x-tree-elbow-line" />');
+ } else {
+ buf.unshift('<img alt="" src="'+this.emptyIcon+'" class="x-tree-icon" />');
+ }
+ }
+ p = p.parentNode;
+ }
+ this.childIndent = buf.join("");
+ }
+ return this.childIndent;
+ },
+
+
+ renderIndent : function(){
+ if(this.rendered){
+ var indent = "",
+ p = this.node.parentNode;
+ if(p){
+ indent = p.ui.getChildIndent();
+ }
+ if(this.indentMarkup != indent){
+ this.indentNode.innerHTML = indent;
+ this.indentMarkup = indent;
+ }
+ this.updateExpandIcon();
+ }
+ },
+
+ destroy : function(){
+ if(this.elNode){
+ Ext.dd.Registry.unregister(this.elNode.id);
+ }
+
+ Ext.each(['textnode', 'anchor', 'checkbox', 'indentNode', 'ecNode', 'iconNode', 'elNode', 'ctNode', 'wrap', 'holder'], function(el){
+ if(this[el]){
+ Ext.fly(this[el]).remove();
+ delete this[el];
+ }
+ }, this);
+ delete this.node;
+ }
+});
+
+
+Ext.tree.RootTreeNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
+
+ render : function(){
+ if(!this.rendered){
+ var targetNode = this.node.ownerTree.innerCt.dom;
+ this.node.expanded = true;
+ targetNode.innerHTML = '<div class="x-tree-root-node"></div>';
+ this.wrap = this.ctNode = targetNode.firstChild;
+ }
+ },
+ collapse : Ext.emptyFn,
+ expand : Ext.emptyFn
+});
+Ext.tree.TreeLoader = function(config){
+ this.baseParams = {};
+ Ext.apply(this, config);
+
+ this.addEvents(
+
+ "beforeload",
+
+ "load",
+
+ "loadexception"
+ );
+ Ext.tree.TreeLoader.superclass.constructor.call(this);
+ if(Ext.isString(this.paramOrder)){
+ this.paramOrder = this.paramOrder.split(/[\s,|]/);
+ }
+};
+
+Ext.extend(Ext.tree.TreeLoader, Ext.util.Observable, {
+
+
+
+
+
+
+
+ uiProviders : {},
+
+
+ clearOnLoad : true,
+
+
+ paramOrder: undefined,
+
+
+ paramsAsHash: false,
+
+
+ nodeParameter: 'node',
+
+
+ directFn : undefined,
+
+
+ load : function(node, callback, scope){
+ if(this.clearOnLoad){
+ while(node.firstChild){
+ node.removeChild(node.firstChild);
+ }
+ }
+ if(this.doPreload(node)){
+ this.runCallback(callback, scope || node, [node]);
+ }else if(this.directFn || this.dataUrl || this.url){
+ this.requestData(node, callback, scope || node);
+ }
+ },
+
+ doPreload : function(node){
+ if(node.attributes.children){
+ if(node.childNodes.length < 1){
+ var cs = node.attributes.children;
+ node.beginUpdate();
+ for(var i = 0, len = cs.length; i < len; i++){
+ var cn = node.appendChild(this.createNode(cs[i]));
+ if(this.preloadChildren){
+ this.doPreload(cn);
+ }
+ }
+ node.endUpdate();
+ }
+ return true;
+ }
+ return false;
+ },
+
+ getParams: function(node){
+ var bp = Ext.apply({}, this.baseParams),
+ np = this.nodeParameter,
+ po = this.paramOrder;
+
+ np && (bp[ np ] = node.id);
+
+ if(this.directFn){
+ var buf = [node.id];
+ if(po){
+
+ if(np && po.indexOf(np) > -1){
+ buf = [];
+ }
+
+ for(var i = 0, len = po.length; i < len; i++){
+ buf.push(bp[ po[i] ]);
+ }
+ }else if(this.paramsAsHash){
+ buf = [bp];
+ }
+ return buf;
+ }else{
+ return bp;
+ }
+ },
+
+ requestData : function(node, callback, scope){
+ if(this.fireEvent("beforeload", this, node, callback) !== false){
+ if(this.directFn){
+ var args = this.getParams(node);
+ args.push(this.processDirectResponse.createDelegate(this, [{callback: callback, node: node, scope: scope}], true));
+ this.directFn.apply(window, args);
+ }else{
+ this.transId = Ext.Ajax.request({
+ method:this.requestMethod,
+ url: this.dataUrl||this.url,
+ success: this.handleResponse,
+ failure: this.handleFailure,
+ scope: this,
+ argument: {callback: callback, node: node, scope: scope},
+ params: this.getParams(node)
+ });
+ }
+ }else{
+
+
+ this.runCallback(callback, scope || node, []);
+ }
+ },
+
+ processDirectResponse: function(result, response, args){
+ if(response.status){
+ this.handleResponse({
+ responseData: Ext.isArray(result) ? result : null,
+ responseText: result,
+ argument: args
+ });
+ }else{
+ this.handleFailure({
+ argument: args
+ });
+ }
+ },
+
+
+ runCallback: function(cb, scope, args){
+ if(Ext.isFunction(cb)){
+ cb.apply(scope, args);
+ }
+ },
+
+ isLoading : function(){
+ return !!this.transId;
+ },
+
+ abort : function(){
+ if(this.isLoading()){
+ Ext.Ajax.abort(this.transId);
+ }
+ },
+
+
+ createNode : function(attr){
+
+ if(this.baseAttrs){
+ Ext.applyIf(attr, this.baseAttrs);
+ }
+ if(this.applyLoader !== false && !attr.loader){
+ attr.loader = this;
+ }
+ if(Ext.isString(attr.uiProvider)){
+ attr.uiProvider = this.uiProviders[attr.uiProvider] || eval(attr.uiProvider);
+ }
+ if(attr.nodeType){
+ return new Ext.tree.TreePanel.nodeTypes[attr.nodeType](attr);
+ }else{
+ return attr.leaf ?
+ new Ext.tree.TreeNode(attr) :
+ new Ext.tree.AsyncTreeNode(attr);
+ }
+ },
+
+ processResponse : function(response, node, callback, scope){
+ var json = response.responseText;
+ try {
+ var o = response.responseData || Ext.decode(json);
+ node.beginUpdate();
+ for(var i = 0, len = o.length; i < len; i++){
+ var n = this.createNode(o[i]);
+ if(n){
+ node.appendChild(n);
+ }
+ }
+ node.endUpdate();
+ this.runCallback(callback, scope || node, [node]);
+ }catch(e){
+ this.handleFailure(response);
+ }
+ },
+
+ handleResponse : function(response){
+ this.transId = false;
+ var a = response.argument;
+ this.processResponse(response, a.node, a.callback, a.scope);
+ this.fireEvent("load", this, a.node, response);
+ },
+
+ handleFailure : function(response){
+ this.transId = false;
+ var a = response.argument;
+ this.fireEvent("loadexception", this, a.node, response);
+ this.runCallback(a.callback, a.scope || a.node, [a.node]);
+ },
+
+ destroy : function(){
+ this.abort();
+ this.purgeListeners();
+ }
+});
+Ext.tree.TreeFilter = function(tree, config){
+ this.tree = tree;
+ this.filtered = {};
+ Ext.apply(this, config);
+};
+
+Ext.tree.TreeFilter.prototype = {
+ clearBlank:false,
+ reverse:false,
+ autoClear:false,
+ remove:false,
+
+
+ filter : function(value, attr, startNode){
+ attr = attr || "text";
+ var f;
+ if(typeof value == "string"){
+ var vlen = value.length;
+
+ if(vlen == 0 && this.clearBlank){
+ this.clear();
+ return;
+ }
+ value = value.toLowerCase();
+ f = function(n){
+ return n.attributes[attr].substr(0, vlen).toLowerCase() == value;
+ };
+ }else if(value.exec){
+ f = function(n){
+ return value.test(n.attributes[attr]);
+ };
+ }else{
+ throw 'Illegal filter type, must be string or regex';
+ }
+ this.filterBy(f, null, startNode);
+ },
+
+
+ filterBy : function(fn, scope, startNode){
+ startNode = startNode || this.tree.root;
+ if(this.autoClear){
+ this.clear();
+ }
+ var af = this.filtered, rv = this.reverse;
+ var f = function(n){
+ if(n == startNode){
+ return true;
+ }
+ if(af[n.id]){
+ return false;
+ }
+ var m = fn.call(scope || n, n);
+ if(!m || rv){
+ af[n.id] = n;
+ n.ui.hide();
+ return false;
+ }
+ return true;
+ };
+ startNode.cascade(f);
+ if(this.remove){
+ for(var id in af){
+ if(typeof id != "function"){
+ var n = af[id];
+ if(n && n.parentNode){
+ n.parentNode.removeChild(n);
+ }
+ }
+ }
+ }
+ },
+
+
+ clear : function(){
+ var t = this.tree;
+ var af = this.filtered;
+ for(var id in af){
+ if(typeof id != "function"){
+ var n = af[id];
+ if(n){
+ n.ui.show();
+ }
+ }
+ }
+ this.filtered = {};
+ }
+};
+
+Ext.tree.TreeSorter = Ext.extend(Object, {
+
+ constructor: function(tree, config){
+
+
+
+
+
+
+
+ Ext.apply(this, config);
+ tree.on({
+ scope: this,
+ beforechildrenrendered: this.doSort,
+ append: this.updateSort,
+ insert: this.updateSort,
+ textchange: this.updateSortParent
+ });
+
+ var desc = this.dir && this.dir.toLowerCase() == 'desc',
+ prop = this.property || 'text',
+ sortType = this.sortType,
+ folderSort = this.folderSort,
+ caseSensitive = this.caseSensitive === true,
+ leafAttr = this.leafAttr || 'leaf';
+
+ if(Ext.isString(sortType)){
+ sortType = Ext.data.SortTypes[sortType];
+ }
+ this.sortFn = function(n1, n2){
+ var attr1 = n1.attributes,
+ attr2 = n2.attributes;
+
+ if(folderSort){
+ if(attr1[leafAttr] && !attr2[leafAttr]){
+ return 1;
+ }
+ if(!attr1[leafAttr] && attr2[leafAttr]){
+ return -1;
+ }
+ }
+ var prop1 = attr1[prop],
+ prop2 = attr2[prop],
+ v1 = sortType ? sortType(prop1, n1) : (caseSensitive ? prop1 : prop1.toUpperCase()),
+ v2 = sortType ? sortType(prop2, n2) : (caseSensitive ? prop2 : prop2.toUpperCase());
+
+ if(v1 < v2){
+ return desc ? 1 : -1;
+ }else if(v1 > v2){
+ return desc ? -1 : 1;
+ }
+ return 0;
+ };
+ },
+
+ doSort : function(node){
+ node.sort(this.sortFn);
+ },
+
+ updateSort : function(tree, node){
+ if(node.childrenRendered){
+ this.doSort.defer(1, this, [node]);
+ }
+ },
+
+ updateSortParent : function(node){
+ var p = node.parentNode;
+ if(p && p.childrenRendered){
+ this.doSort.defer(1, this, [p]);
+ }
+ }
+});
+
+if(Ext.dd.DropZone){
+
+Ext.tree.TreeDropZone = function(tree, config){
+
+ this.allowParentInsert = config.allowParentInsert || false;
+
+ this.allowContainerDrop = config.allowContainerDrop || false;
+
+ this.appendOnly = config.appendOnly || false;
+
+ Ext.tree.TreeDropZone.superclass.constructor.call(this, tree.getTreeEl(), config);
+
+ this.tree = tree;
+
+ this.dragOverData = {};
+
+ this.lastInsertClass = "x-tree-no-status";
+};
+
+Ext.extend(Ext.tree.TreeDropZone, Ext.dd.DropZone, {
+
+ ddGroup : "TreeDD",
+
+
+ expandDelay : 1000,
+
+
+ expandNode : function(node){
+ if(node.hasChildNodes() && !node.isExpanded()){
+ node.expand(false, null, this.triggerCacheRefresh.createDelegate(this));
+ }
+ },
+
+
+ queueExpand : function(node){
+ this.expandProcId = this.expandNode.defer(this.expandDelay, this, [node]);
+ },
+
+
+ cancelExpand : function(){
+ if(this.expandProcId){
+ clearTimeout(this.expandProcId);
+ this.expandProcId = false;
+ }
+ },
+
+
+ isValidDropPoint : function(n, pt, dd, e, data){
+ if(!n || !data){ return false; }
+ var targetNode = n.node;
+ var dropNode = data.node;
+
+ if(!(targetNode && targetNode.isTarget && pt)){
+ return false;
+ }
+ if(pt == "append" && targetNode.allowChildren === false){
+ return false;
+ }
+ if((pt == "above" || pt == "below") && (targetNode.parentNode && targetNode.parentNode.allowChildren === false)){
+ return false;
+ }
+ if(dropNode && (targetNode == dropNode || dropNode.contains(targetNode))){
+ return false;
+ }
+
+ var overEvent = this.dragOverData;
+ overEvent.tree = this.tree;
+ overEvent.target = targetNode;
+ overEvent.data = data;
+ overEvent.point = pt;
+ overEvent.source = dd;
+ overEvent.rawEvent = e;
+ overEvent.dropNode = dropNode;
+ overEvent.cancel = false;
+ var result = this.tree.fireEvent("nodedragover", overEvent);
+ return overEvent.cancel === false && result !== false;
+ },
+
+
+ getDropPoint : function(e, n, dd){
+ var tn = n.node;
+ if(tn.isRoot){
+ return tn.allowChildren !== false ? "append" : false;
+ }
+ var dragEl = n.ddel;
+ var t = Ext.lib.Dom.getY(dragEl), b = t + dragEl.offsetHeight;
+ var y = Ext.lib.Event.getPageY(e);
+ var noAppend = tn.allowChildren === false || tn.isLeaf();
+ if(this.appendOnly || tn.parentNode.allowChildren === false){
+ return noAppend ? false : "append";
+ }
+ var noBelow = false;
+ if(!this.allowParentInsert){
+ noBelow = tn.hasChildNodes() && tn.isExpanded();
+ }
+ var q = (b - t) / (noAppend ? 2 : 3);
+ if(y >= t && y < (t + q)){
+ return "above";
+ }else if(!noBelow && (noAppend || y >= b-q && y <= b)){
+ return "below";
+ }else{
+ return "append";
+ }
+ },
+
+
+ onNodeEnter : function(n, dd, e, data){
+ this.cancelExpand();
+ },
+
+ onContainerOver : function(dd, e, data) {
+ if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) {
+ return this.dropAllowed;
+ }
+ return this.dropNotAllowed;
+ },
+
+
+ onNodeOver : function(n, dd, e, data){
+ var pt = this.getDropPoint(e, n, dd);
+ var node = n.node;
+
+
+ if(!this.expandProcId && pt == "append" && node.hasChildNodes() && !n.node.isExpanded()){
+ this.queueExpand(node);
+ }else if(pt != "append"){
+ this.cancelExpand();
+ }
+
+
+ var returnCls = this.dropNotAllowed;
+ if(this.isValidDropPoint(n, pt, dd, e, data)){
+ if(pt){
+ var el = n.ddel;
+ var cls;
+ if(pt == "above"){
+ returnCls = n.node.isFirst() ? "x-tree-drop-ok-above" : "x-tree-drop-ok-between";
+ cls = "x-tree-drag-insert-above";
+ }else if(pt == "below"){
+ returnCls = n.node.isLast() ? "x-tree-drop-ok-below" : "x-tree-drop-ok-between";
+ cls = "x-tree-drag-insert-below";
+ }else{
+ returnCls = "x-tree-drop-ok-append";
+ cls = "x-tree-drag-append";
+ }
+ if(this.lastInsertClass != cls){
+ Ext.fly(el).replaceClass(this.lastInsertClass, cls);
+ this.lastInsertClass = cls;
+ }
+ }
+ }
+ return returnCls;
+ },
+
+
+ onNodeOut : function(n, dd, e, data){
+ this.cancelExpand();
+ this.removeDropIndicators(n);
+ },
+
+
+ onNodeDrop : function(n, dd, e, data){
+ var point = this.getDropPoint(e, n, dd);
+ var targetNode = n.node;
+ targetNode.ui.startDrop();
+ if(!this.isValidDropPoint(n, point, dd, e, data)){
+ targetNode.ui.endDrop();
+ return false;
+ }
+
+ var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, point, e) : null);
+ return this.processDrop(targetNode, data, point, dd, e, dropNode);
+ },
+
+ onContainerDrop : function(dd, e, data){
+ if (this.allowContainerDrop && this.isValidDropPoint({ ddel: this.tree.getRootNode().ui.elNode, node: this.tree.getRootNode() }, "append", dd, e, data)) {
+ var targetNode = this.tree.getRootNode();
+ targetNode.ui.startDrop();
+ var dropNode = data.node || (dd.getTreeNode ? dd.getTreeNode(data, targetNode, 'append', e) : null);
+ return this.processDrop(targetNode, data, 'append', dd, e, dropNode);
+ }
+ return false;
+ },
+
+
+ processDrop: function(target, data, point, dd, e, dropNode){
+ var dropEvent = {
+ tree : this.tree,
+ target: target,
+ data: data,
+ point: point,
+ source: dd,
+ rawEvent: e,
+ dropNode: dropNode,
+ cancel: !dropNode,
+ dropStatus: false
+ };
+ var retval = this.tree.fireEvent("beforenodedrop", dropEvent);
+ if(retval === false || dropEvent.cancel === true || !dropEvent.dropNode){
+ target.ui.endDrop();
+ return dropEvent.dropStatus;
+ }
+
+ target = dropEvent.target;
+ if(point == 'append' && !target.isExpanded()){
+ target.expand(false, null, function(){
+ this.completeDrop(dropEvent);
+ }.createDelegate(this));
+ }else{
+ this.completeDrop(dropEvent);
+ }
+ return true;
+ },
+
+
+ completeDrop : function(de){
+ var ns = de.dropNode, p = de.point, t = de.target;
+ if(!Ext.isArray(ns)){
+ ns = [ns];
+ }
+ var n;
+ for(var i = 0, len = ns.length; i < len; i++){
+ n = ns[i];
+ if(p == "above"){
+ t.parentNode.insertBefore(n, t);
+ }else if(p == "below"){
+ t.parentNode.insertBefore(n, t.nextSibling);
+ }else{
+ t.appendChild(n);
+ }
+ }
+ n.ui.focus();
+ if(Ext.enableFx && this.tree.hlDrop){
+ n.ui.highlight();
+ }
+ t.ui.endDrop();
+ this.tree.fireEvent("nodedrop", de);
+ },
+
+
+ afterNodeMoved : function(dd, data, e, targetNode, dropNode){
+ if(Ext.enableFx && this.tree.hlDrop){
+ dropNode.ui.focus();
+ dropNode.ui.highlight();
+ }
+ this.tree.fireEvent("nodedrop", this.tree, targetNode, data, dd, e);
+ },
+
+
+ getTree : function(){
+ return this.tree;
+ },
+
+
+ removeDropIndicators : function(n){
+ if(n && n.ddel){
+ var el = n.ddel;
+ Ext.fly(el).removeClass([
+ "x-tree-drag-insert-above",
+ "x-tree-drag-insert-below",
+ "x-tree-drag-append"]);
+ this.lastInsertClass = "_noclass";
+ }
+ },
+
+
+ beforeDragDrop : function(target, e, id){
+ this.cancelExpand();
+ return true;
+ },
+
+
+ afterRepair : function(data){
+ if(data && Ext.enableFx){
+ data.node.ui.highlight();
+ }
+ this.hideProxy();
+ }
+});
+
+}
+if(Ext.dd.DragZone){
+Ext.tree.TreeDragZone = function(tree, config){
+ Ext.tree.TreeDragZone.superclass.constructor.call(this, tree.innerCt, config);
+
+ this.tree = tree;
+};
+
+Ext.extend(Ext.tree.TreeDragZone, Ext.dd.DragZone, {
+
+ ddGroup : "TreeDD",
+
+
+ onBeforeDrag : function(data, e){
+ var n = data.node;
+ return n && n.draggable && !n.disabled;
+ },
+
+
+ onInitDrag : function(e){
+ var data = this.dragData;
+ this.tree.getSelectionModel().select(data.node);
+ this.tree.eventModel.disable();
+ this.proxy.update("");
+ data.node.ui.appendDDGhost(this.proxy.ghost.dom);
+ this.tree.fireEvent("startdrag", this.tree, data.node, e);
+ },
+
+
+ getRepairXY : function(e, data){
+ return data.node.ui.getDDRepairXY();
+ },
+
+
+ onEndDrag : function(data, e){
+ this.tree.eventModel.enable.defer(100, this.tree.eventModel);
+ this.tree.fireEvent("enddrag", this.tree, data.node, e);
+ },
+
+
+ onValidDrop : function(dd, e, id){
+ this.tree.fireEvent("dragdrop", this.tree, this.dragData.node, dd, e);
+ this.hideProxy();
+ },
+
+
+ beforeInvalidDrop : function(e, id){
+
+ var sm = this.tree.getSelectionModel();
+ sm.clearSelections();
+ sm.select(this.dragData.node);
+ },
+
+
+ afterRepair : function(){
+ if (Ext.enableFx && this.tree.hlDrop) {
+ Ext.Element.fly(this.dragData.ddel).highlight(this.hlColor || "c3daf9");
+ }
+ this.dragging = false;
+ }
+});
+}
+Ext.tree.TreeEditor = function(tree, fc, config){
+ fc = fc || {};
+ var field = fc.events ? fc : new Ext.form.TextField(fc);
+
+ Ext.tree.TreeEditor.superclass.constructor.call(this, field, config);
+
+ this.tree = tree;
+
+ if(!tree.rendered){
+ tree.on('render', this.initEditor, this);
+ }else{
+ this.initEditor(tree);
+ }
+};
+
+Ext.extend(Ext.tree.TreeEditor, Ext.Editor, {
+
+ alignment: "l-l",
+
+ autoSize: false,
+
+ hideEl : false,
+
+ cls: "x-small-editor x-tree-editor",
+
+ shim:false,
+
+ shadow:"frame",
+
+ maxWidth: 250,
+
+ editDelay : 350,
+
+ initEditor : function(tree){
+ tree.on({
+ scope : this,
+ beforeclick: this.beforeNodeClick,
+ dblclick : this.onNodeDblClick
+ });
+
+ this.on({
+ scope : this,
+ complete : this.updateNode,
+ beforestartedit: this.fitToTree,
+ specialkey : this.onSpecialKey
+ });
+
+ this.on('startedit', this.bindScroll, this, {delay:10});
+ },
+
+
+ fitToTree : function(ed, el){
+ var td = this.tree.getTreeEl().dom, nd = el.dom;
+ if(td.scrollLeft > nd.offsetLeft){
+ td.scrollLeft = nd.offsetLeft;
+ }
+ var w = Math.min(
+ this.maxWidth,
+ (td.clientWidth > 20 ? td.clientWidth : td.offsetWidth) - Math.max(0, nd.offsetLeft-td.scrollLeft) - 5);
+ this.setSize(w, '');
+ },
+
+
+ triggerEdit : function(node, defer){
+ this.completeEdit();
+ if(node.attributes.editable !== false){
+
+ this.editNode = node;
+ if(this.tree.autoScroll){
+ Ext.fly(node.ui.getEl()).scrollIntoView(this.tree.body);
+ }
+ var value = node.text || '';
+ if (!Ext.isGecko && Ext.isEmpty(node.text)){
+ node.setText('&#160;');
+ }
+ this.autoEditTimer = this.startEdit.defer(this.editDelay, this, [node.ui.textNode, value]);
+ return false;
+ }
+ },
+
+
+ bindScroll : function(){
+ this.tree.getTreeEl().on('scroll', this.cancelEdit, this);
+ },
+
+
+ beforeNodeClick : function(node, e){
+ clearTimeout(this.autoEditTimer);
+ if(this.tree.getSelectionModel().isSelected(node)){
+ e.stopEvent();
+ return this.triggerEdit(node);
+ }
+ },
+
+ onNodeDblClick : function(node, e){
+ clearTimeout(this.autoEditTimer);
+ },
+
+
+ updateNode : function(ed, value){
+ this.tree.getTreeEl().un('scroll', this.cancelEdit, this);
+ this.editNode.setText(value);
+ },
+
+
+ onHide : function(){
+ Ext.tree.TreeEditor.superclass.onHide.call(this);
+ if(this.editNode){
+ this.editNode.ui.focus.defer(50, this.editNode.ui);
+ }
+ },
+
+
+ onSpecialKey : function(field, e){
+ var k = e.getKey();
+ if(k == e.ESC){
+ e.stopEvent();
+ this.cancelEdit();
+ }else if(k == e.ENTER && !e.hasModifier()){
+ e.stopEvent();
+ this.completeEdit();
+ }
+ },
+
+ onDestroy : function(){
+ clearTimeout(this.autoEditTimer);
+ Ext.tree.TreeEditor.superclass.onDestroy.call(this);
+ var tree = this.tree;
+ tree.un('beforeclick', this.beforeNodeClick, this);
+ tree.un('dblclick', this.onNodeDblClick, this);
+ }
+});
+
+var swfobject = function() {
+
+ var UNDEF = "undefined",
+ OBJECT = "object",
+ SHOCKWAVE_FLASH = "Shockwave Flash",
+ SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash",
+ FLASH_MIME_TYPE = "application/x-shockwave-flash",
+ EXPRESS_INSTALL_ID = "SWFObjectExprInst",
+ ON_READY_STATE_CHANGE = "onreadystatechange",
+
+ win = window,
+ doc = document,
+ nav = navigator,
+
+ plugin = false,
+ domLoadFnArr = [main],
+ regObjArr = [],
+ objIdArr = [],
+ listenersArr = [],
+ storedAltContent,
+ storedAltContentId,
+ storedCallbackFn,
+ storedCallbackObj,
+ isDomLoaded = false,
+ isExpressInstallActive = false,
+ dynamicStylesheet,
+ dynamicStylesheetMedia,
+ autoHideShow = true,
+
+
+ ua = function() {
+ var w3cdom = typeof doc.getElementById != UNDEF && typeof doc.getElementsByTagName != UNDEF && typeof doc.createElement != UNDEF,
+ u = nav.userAgent.toLowerCase(),
+ p = nav.platform.toLowerCase(),
+ windows = p ? (/win/).test(p) : /win/.test(u),
+ mac = p ? (/mac/).test(p) : /mac/.test(u),
+ webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false,
+ ie = !+"\v1",
+ playerVersion = [0,0,0],
+ d = null;
+ if (typeof nav.plugins != UNDEF && typeof nav.plugins[SHOCKWAVE_FLASH] == OBJECT) {
+ d = nav.plugins[SHOCKWAVE_FLASH].description;
+ if (d && !(typeof nav.mimeTypes != UNDEF && nav.mimeTypes[FLASH_MIME_TYPE] && !nav.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) {
+ plugin = true;
+ ie = false;
+ d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1");
+ playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10);
+ playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), 10);
+ playerVersion[2] = /[a-zA-Z]/.test(d) ? parseInt(d.replace(/^.*[a-zA-Z]+(.*)$/, "$1"), 10) : 0;
+ }
+ }
+ else if (typeof win.ActiveXObject != UNDEF) {
+ try {
+ var a = new ActiveXObject(SHOCKWAVE_FLASH_AX);
+ if (a) {
+ d = a.GetVariable("$version");
+ if (d) {
+ ie = true;
+ d = d.split(" ")[1].split(",");
+ playerVersion = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
+ }
+ }
+ }
+ catch(e) {}
+ }
+ return { w3:w3cdom, pv:playerVersion, wk:webkit, ie:ie, win:windows, mac:mac };
+ }(),
+
+
+ onDomLoad = function() {
+ if (!ua.w3) { return; }
+ if ((typeof doc.readyState != UNDEF && doc.readyState == "complete") || (typeof doc.readyState == UNDEF && (doc.getElementsByTagName("body")[0] || doc.body))) {
+ callDomLoadFunctions();
+ }
+ if (!isDomLoaded) {
+ if (typeof doc.addEventListener != UNDEF) {
+ doc.addEventListener("DOMContentLoaded", callDomLoadFunctions, false);
+ }
+ if (ua.ie && ua.win) {
+ doc.attachEvent(ON_READY_STATE_CHANGE, function() {
+ if (doc.readyState == "complete") {
+ doc.detachEvent(ON_READY_STATE_CHANGE, arguments.callee);
+ callDomLoadFunctions();
+ }
+ });
+ if (win == top) {
+ (function(){
+ if (isDomLoaded) { return; }
+ try {
+ doc.documentElement.doScroll("left");
+ }
+ catch(e) {
+ setTimeout(arguments.callee, 0);
+ return;
+ }
+ callDomLoadFunctions();
+ })();
+ }
+ }
+ if (ua.wk) {
+ (function(){
+ if (isDomLoaded) { return; }
+ if (!(/loaded|complete/).test(doc.readyState)) {
+ setTimeout(arguments.callee, 0);
+ return;
+ }
+ callDomLoadFunctions();
+ })();
+ }
+ addLoadEvent(callDomLoadFunctions);
+ }
+ }();
+
+ function callDomLoadFunctions() {
+ if (isDomLoaded) { return; }
+ try {
+ var t = doc.getElementsByTagName("body")[0].appendChild(createElement("span"));
+ t.parentNode.removeChild(t);
+ }
+ catch (e) { return; }
+ isDomLoaded = true;
+ var dl = domLoadFnArr.length;
+ for (var i = 0; i < dl; i++) {
+ domLoadFnArr[i]();
+ }
+ }
+
+ function addDomLoadEvent(fn) {
+ if (isDomLoaded) {
+ fn();
+ }
+ else {
+ domLoadFnArr[domLoadFnArr.length] = fn;
+ }
+ }
+
+
+ function addLoadEvent(fn) {
+ if (typeof win.addEventListener != UNDEF) {
+ win.addEventListener("load", fn, false);
+ }
+ else if (typeof doc.addEventListener != UNDEF) {
+ doc.addEventListener("load", fn, false);
+ }
+ else if (typeof win.attachEvent != UNDEF) {
+ addListener(win, "onload", fn);
+ }
+ else if (typeof win.onload == "function") {
+ var fnOld = win.onload;
+ win.onload = function() {
+ fnOld();
+ fn();
+ };
+ }
+ else {
+ win.onload = fn;
+ }
+ }
+
+
+ function main() {
+ //~ if (plugin) {
+ //~ testPlayerVersion();
+ //~ }
+ //~ else {
+ matchVersions();
+ //~ }
+ }
+
+
+ function testPlayerVersion() {
+ var b = doc.getElementsByTagName("body")[0];
+ var o = createElement(OBJECT);
+ o.setAttribute("type", FLASH_MIME_TYPE);
+ var t = b.appendChild(o);
+ if (t) {
+ var counter = 0;
+ (function(){
+ if (typeof t.GetVariable != UNDEF) {
+ var d = t.GetVariable("$version");
+ if (d) {
+ d = d.split(" ")[1].split(",");
+ ua.pv = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
+ }
+ }
+ else if (counter < 10) {
+ counter++;
+ setTimeout(arguments.callee, 10);
+ return;
+ }
+ b.removeChild(o);
+ t = null;
+ matchVersions();
+ })();
+ }
+ else {
+ matchVersions();
+ }
+ }
+
+
+ function matchVersions() {
+ var rl = regObjArr.length;
+ if (rl > 0) {
+ for (var i = 0; i < rl; i++) {
+ var id = regObjArr[i].id;
+ var cb = regObjArr[i].callbackFn;
+ var cbObj = {success:false, id:id};
+ if (ua.pv[0] > 0) {
+ var obj = getElementById(id);
+ if (obj) {
+ if (hasPlayerVersion(regObjArr[i].swfVersion) && !(ua.wk && ua.wk < 312)) {
+ setVisibility(id, true);
+ if (cb) {
+ cbObj.success = true;
+ cbObj.ref = getObjectById(id);
+ cb(cbObj);
+ }
+ }
+ else if (regObjArr[i].expressInstall && canExpressInstall()) {
+ var att = {};
+ att.data = regObjArr[i].expressInstall;
+ att.width = obj.getAttribute("width") || "0";
+ att.height = obj.getAttribute("height") || "0";
+ if (obj.getAttribute("class")) { att.styleclass = obj.getAttribute("class"); }
+ if (obj.getAttribute("align")) { att.align = obj.getAttribute("align"); }
+
+ var par = {};
+ var p = obj.getElementsByTagName("param");
+ var pl = p.length;
+ for (var j = 0; j < pl; j++) {
+ if (p[j].getAttribute("name").toLowerCase() != "movie") {
+ par[p[j].getAttribute("name")] = p[j].getAttribute("value");
+ }
+ }
+ showExpressInstall(att, par, id, cb);
+ }
+ else {
+ displayAltContent(obj);
+ if (cb) { cb(cbObj); }
+ }
+ }
+ }
+ else {
+ setVisibility(id, true);
+ if (cb) {
+ var o = getObjectById(id);
+ if (o && typeof o.SetVariable != UNDEF) {
+ cbObj.success = true;
+ cbObj.ref = o;
+ }
+ cb(cbObj);
+ }
+ }
+ }
+ }
+ }
+
+ function getObjectById(objectIdStr) {
+ var r = null;
+ var o = getElementById(objectIdStr);
+ if (o && o.nodeName == "OBJECT") {
+ if (typeof o.SetVariable != UNDEF) {
+ r = o;
+ }
+ else {
+ var n = o.getElementsByTagName(OBJECT)[0];
+ if (n) {
+ r = n;
+ }
+ }
+ }
+ return r;
+ }
+
+
+ function canExpressInstall() {
+ return !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac) && !(ua.wk && ua.wk < 312);
+ }
+
+
+ function showExpressInstall(att, par, replaceElemIdStr, callbackFn) {
+ isExpressInstallActive = true;
+ storedCallbackFn = callbackFn || null;
+ storedCallbackObj = {success:false, id:replaceElemIdStr};
+ var obj = getElementById(replaceElemIdStr);
+ if (obj) {
+ if (obj.nodeName == "OBJECT") {
+ storedAltContent = abstractAltContent(obj);
+ storedAltContentId = null;
+ }
+ else {
+ storedAltContent = obj;
+ storedAltContentId = replaceElemIdStr;
+ }
+ att.id = EXPRESS_INSTALL_ID;
+ if (typeof att.width == UNDEF || (!(/%$/).test(att.width) && parseInt(att.width, 10) < 310)) {
+ att.width = "310";
+ }
+
+ if (typeof att.height == UNDEF || (!(/%$/).test(att.height) && parseInt(att.height, 10) < 137)) {
+ att.height = "137";
+ }
+ doc.title = doc.title.slice(0, 47) + " - Flash Player Installation";
+ var pt = ua.ie && ua.win ? "ActiveX" : "PlugIn",
+ fv = "MMredirectURL=" + win.location.toString().replace(/&/g,"%26") + "&MMplayerType=" + pt + "&MMdoctitle=" + doc.title;
+ if (typeof par.flashvars != UNDEF) {
+ par.flashvars += "&" + fv;
+ }
+ else {
+ par.flashvars = fv;
+ }
+
+
+ if (ua.ie && ua.win && obj.readyState != 4) {
+ var newObj = createElement("div");
+ replaceElemIdStr += "SWFObjectNew";
+ newObj.setAttribute("id", replaceElemIdStr);
+ obj.parentNode.insertBefore(newObj, obj);
+ obj.style.display = "none";
+ (function(){
+ if (obj.readyState == 4) {
+ obj.parentNode.removeChild(obj);
+ }
+ else {
+ setTimeout(arguments.callee, 10);
+ }
+ })();
+ }
+ createSWF(att, par, replaceElemIdStr);
+ }
+ }
+
+
+ function displayAltContent(obj) {
+ if (ua.ie && ua.win && obj.readyState != 4) {
+
+
+ var el = createElement("div");
+ obj.parentNode.insertBefore(el, obj);
+ el.parentNode.replaceChild(abstractAltContent(obj), el);
+ obj.style.display = "none";
+ (function(){
+ if (obj.readyState == 4) {
+ obj.parentNode.removeChild(obj);
+ }
+ else {
+ setTimeout(arguments.callee, 10);
+ }
+ })();
+ }
+ else {
+ obj.parentNode.replaceChild(abstractAltContent(obj), obj);
+ }
+ }
+
+ function abstractAltContent(obj) {
+ var ac = createElement("div");
+ if (ua.win && ua.ie) {
+ ac.innerHTML = obj.innerHTML;
+ }
+ else {
+ var nestedObj = obj.getElementsByTagName(OBJECT)[0];
+ if (nestedObj) {
+ var c = nestedObj.childNodes;
+ if (c) {
+ var cl = c.length;
+ for (var i = 0; i < cl; i++) {
+ if (!(c[i].nodeType == 1 && c[i].nodeName == "PARAM") && !(c[i].nodeType == 8)) {
+ ac.appendChild(c[i].cloneNode(true));
+ }
+ }
+ }
+ }
+ }
+ return ac;
+ }
+
+
+ function createSWF(attObj, parObj, id) {
+ var r, el = getElementById(id);
+ if (ua.wk && ua.wk < 312) { return r; }
+ if (el) {
+ if (typeof attObj.id == UNDEF) {
+ attObj.id = id;
+ }
+ if (ua.ie && ua.win) {
+ var att = "";
+ for (var i in attObj) {
+ if (attObj[i] != Object.prototype[i]) {
+ if (i.toLowerCase() == "data") {
+ parObj.movie = attObj[i];
+ }
+ else if (i.toLowerCase() == "styleclass") {
+ att += ' class="' + attObj[i] + '"';
+ }
+ else if (i.toLowerCase() != "classid") {
+ att += ' ' + i + '="' + attObj[i] + '"';
+ }
+ }
+ }
+ var par = "";
+ for (var j in parObj) {
+ if (parObj[j] != Object.prototype[j]) {
+ par += '<param name="' + j + '" value="' + parObj[j] + '" />';
+ }
+ }
+ el.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"' + att + '>' + par + '</object>';
+ objIdArr[objIdArr.length] = attObj.id;
+ r = getElementById(attObj.id);
+ }
+ else {
+ var o = createElement(OBJECT);
+ o.setAttribute("type", FLASH_MIME_TYPE);
+ for (var m in attObj) {
+ if (attObj[m] != Object.prototype[m]) {
+ if (m.toLowerCase() == "styleclass") {
+ o.setAttribute("class", attObj[m]);
+ }
+ else if (m.toLowerCase() != "classid") {
+ o.setAttribute(m, attObj[m]);
+ }
+ }
+ }
+ for (var n in parObj) {
+ if (parObj[n] != Object.prototype[n] && n.toLowerCase() != "movie") {
+ createObjParam(o, n, parObj[n]);
+ }
+ }
+ el.parentNode.replaceChild(o, el);
+ r = o;
+ }
+ }
+ return r;
+ }
+
+ function createObjParam(el, pName, pValue) {
+ var p = createElement("param");
+ p.setAttribute("name", pName);
+ p.setAttribute("value", pValue);
+ el.appendChild(p);
+ }
+
+
+ function removeSWF(id) {
+ var obj = getElementById(id);
+ if (obj && obj.nodeName == "OBJECT") {
+ if (ua.ie && ua.win) {
+ obj.style.display = "none";
+ (function(){
+ if (obj.readyState == 4) {
+ removeObjectInIE(id);
+ }
+ else {
+ setTimeout(arguments.callee, 10);
+ }
+ })();
+ }
+ else {
+ obj.parentNode.removeChild(obj);
+ }
+ }
+ }
+
+ function removeObjectInIE(id) {
+ var obj = getElementById(id);
+ if (obj) {
+ for (var i in obj) {
+ if (typeof obj[i] == "function") {
+ obj[i] = null;
+ }
+ }
+ obj.parentNode.removeChild(obj);
+ }
+ }
+
+
+ function getElementById(id) {
+ var el = null;
+ try {
+ el = doc.getElementById(id);
+ }
+ catch (e) {}
+ return el;
+ }
+
+ function createElement(el) {
+ return doc.createElement(el);
+ }
+
+
+ function addListener(target, eventType, fn) {
+ target.attachEvent(eventType, fn);
+ listenersArr[listenersArr.length] = [target, eventType, fn];
+ }
+
+
+ function hasPlayerVersion(rv) {
+ var pv = ua.pv, v = rv.split(".");
+ v[0] = parseInt(v[0], 10);
+ v[1] = parseInt(v[1], 10) || 0;
+ v[2] = parseInt(v[2], 10) || 0;
+ return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false;
+ }
+
+
+ function createCSS(sel, decl, media, newStyle) {
+ if (ua.ie && ua.mac) { return; }
+ var h = doc.getElementsByTagName("head")[0];
+ if (!h) { return; }
+ var m = (media && typeof media == "string") ? media : "screen";
+ if (newStyle) {
+ dynamicStylesheet = null;
+ dynamicStylesheetMedia = null;
+ }
+ if (!dynamicStylesheet || dynamicStylesheetMedia != m) {
+
+ var s = createElement("style");
+ s.setAttribute("type", "text/css");
+ s.setAttribute("media", m);
+ dynamicStylesheet = h.appendChild(s);
+ if (ua.ie && ua.win && typeof doc.styleSheets != UNDEF && doc.styleSheets.length > 0) {
+ dynamicStylesheet = doc.styleSheets[doc.styleSheets.length - 1];
+ }
+ dynamicStylesheetMedia = m;
+ }
+
+ if (ua.ie && ua.win) {
+ if (dynamicStylesheet && typeof dynamicStylesheet.addRule == OBJECT) {
+ dynamicStylesheet.addRule(sel, decl);
+ }
+ }
+ else {
+ if (dynamicStylesheet && typeof doc.createTextNode != UNDEF) {
+ dynamicStylesheet.appendChild(doc.createTextNode(sel + " {" + decl + "}"));
+ }
+ }
+ }
+
+ function setVisibility(id, isVisible) {
+ if (!autoHideShow) { return; }
+ var v = isVisible ? "visible" : "hidden";
+ if (isDomLoaded && getElementById(id)) {
+ getElementById(id).style.visibility = v;
+ }
+ else {
+ createCSS("#" + id, "visibility:" + v);
+ }
+ }
+
+
+ function urlEncodeIfNecessary(s) {
+ var regex = /[\\\"<>\.;]/;
+ var hasBadChars = regex.exec(s) != null;
+ return hasBadChars && typeof encodeURIComponent != UNDEF ? encodeURIComponent(s) : s;
+ }
+
+
+ var cleanup = function() {
+ if (ua.ie && ua.win) {
+ window.attachEvent("onunload", function() {
+
+ var ll = listenersArr.length;
+ for (var i = 0; i < ll; i++) {
+ listenersArr[i][0].detachEvent(listenersArr[i][1], listenersArr[i][2]);
+ }
+
+ var il = objIdArr.length;
+ for (var j = 0; j < il; j++) {
+ removeSWF(objIdArr[j]);
+ }
+
+ for (var k in ua) {
+ ua[k] = null;
+ }
+ ua = null;
+ for (var l in swfobject) {
+ swfobject[l] = null;
+ }
+ swfobject = null;
+ window.detachEvent('onunload', arguments.callee);
+ });
+ }
+ }();
+
+ return {
+
+ registerObject: function(objectIdStr, swfVersionStr, xiSwfUrlStr, callbackFn) {
+ if (ua.w3 && objectIdStr && swfVersionStr) {
+ var regObj = {};
+ regObj.id = objectIdStr;
+ regObj.swfVersion = swfVersionStr;
+ regObj.expressInstall = xiSwfUrlStr;
+ regObj.callbackFn = callbackFn;
+ regObjArr[regObjArr.length] = regObj;
+ setVisibility(objectIdStr, false);
+ }
+ else if (callbackFn) {
+ callbackFn({success:false, id:objectIdStr});
+ }
+ },
+
+ getObjectById: function(objectIdStr) {
+ if (ua.w3) {
+ return getObjectById(objectIdStr);
+ }
+ },
+
+ embedSWF: function(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj, callbackFn) {
+ var callbackObj = {success:false, id:replaceElemIdStr};
+ if (ua.w3 && !(ua.wk && ua.wk < 312) && swfUrlStr && replaceElemIdStr && widthStr && heightStr && swfVersionStr) {
+ setVisibility(replaceElemIdStr, false);
+ addDomLoadEvent(function() {
+ widthStr += "";
+ heightStr += "";
+ var att = {};
+ if (attObj && typeof attObj === OBJECT) {
+ for (var i in attObj) {
+ att[i] = attObj[i];
+ }
+ }
+ att.data = swfUrlStr;
+ att.width = widthStr;
+ att.height = heightStr;
+ var par = {};
+ if (parObj && typeof parObj === OBJECT) {
+ for (var j in parObj) {
+ par[j] = parObj[j];
+ }
+ }
+ if (flashvarsObj && typeof flashvarsObj === OBJECT) {
+ for (var k in flashvarsObj) {
+ if (typeof par.flashvars != UNDEF) {
+ par.flashvars += "&" + k + "=" + flashvarsObj[k];
+ }
+ else {
+ par.flashvars = k + "=" + flashvarsObj[k];
+ }
+ }
+ }
+ if (hasPlayerVersion(swfVersionStr)) {
+ var obj = createSWF(att, par, replaceElemIdStr);
+ if (att.id == replaceElemIdStr) {
+ setVisibility(replaceElemIdStr, true);
+ }
+ callbackObj.success = true;
+ callbackObj.ref = obj;
+ }
+ else if (xiSwfUrlStr && canExpressInstall()) {
+ att.data = xiSwfUrlStr;
+ showExpressInstall(att, par, replaceElemIdStr, callbackFn);
+ return;
+ }
+ else {
+ setVisibility(replaceElemIdStr, true);
+ }
+ if (callbackFn) { callbackFn(callbackObj); }
+ });
+ }
+ else if (callbackFn) { callbackFn(callbackObj); }
+ },
+
+ switchOffAutoHideShow: function() {
+ autoHideShow = false;
+ },
+
+ ua: ua,
+
+ getFlashPlayerVersion: function() {
+ return { major:ua.pv[0], minor:ua.pv[1], release:ua.pv[2] };
+ },
+
+ hasFlashPlayerVersion: hasPlayerVersion,
+
+ createSWF: function(attObj, parObj, replaceElemIdStr) {
+ if (ua.w3) {
+ return createSWF(attObj, parObj, replaceElemIdStr);
+ }
+ else {
+ return undefined;
+ }
+ },
+
+ showExpressInstall: function(att, par, replaceElemIdStr, callbackFn) {
+ if (ua.w3 && canExpressInstall()) {
+ showExpressInstall(att, par, replaceElemIdStr, callbackFn);
+ }
+ },
+
+ removeSWF: function(objElemIdStr) {
+ if (ua.w3) {
+ removeSWF(objElemIdStr);
+ }
+ },
+
+ createCSS: function(selStr, declStr, mediaStr, newStyleBoolean) {
+ if (ua.w3) {
+ createCSS(selStr, declStr, mediaStr, newStyleBoolean);
+ }
+ },
+
+ addDomLoadEvent: addDomLoadEvent,
+
+ addLoadEvent: addLoadEvent,
+
+ getQueryParamValue: function(param) {
+ var q = doc.location.search || doc.location.hash;
+ if (q) {
+ if (/\?/.test(q)) { q = q.split("?")[1]; }
+ if (param == null) {
+ return urlEncodeIfNecessary(q);
+ }
+ var pairs = q.split("&");
+ for (var i = 0; i < pairs.length; i++) {
+ if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) {
+ return urlEncodeIfNecessary(pairs[i].substring((pairs[i].indexOf("=") + 1)));
+ }
+ }
+ }
+ return "";
+ },
+
+
+ expressInstallCallback: function() {
+ if (isExpressInstallActive) {
+ var obj = getElementById(EXPRESS_INSTALL_ID);
+ if (obj && storedAltContent) {
+ obj.parentNode.replaceChild(storedAltContent, obj);
+ if (storedAltContentId) {
+ setVisibility(storedAltContentId, true);
+ if (ua.ie && ua.win) { storedAltContent.style.display = "block"; }
+ }
+ if (storedCallbackFn) { storedCallbackFn(storedCallbackObj); }
+ }
+ isExpressInstallActive = false;
+ }
+ }
+ };
+}();
+
+Ext.FlashComponent = Ext.extend(Ext.BoxComponent, {
+
+ flashVersion : '9.0.115',
+
+
+ backgroundColor: '#ffffff',
+
+
+ wmode: 'opaque',
+
+
+ flashVars: undefined,
+
+
+ flashParams: undefined,
+
+
+ url: undefined,
+ swfId : undefined,
+ swfWidth: '100%',
+ swfHeight: '100%',
+
+
+ expressInstall: false,
+
+ initComponent : function(){
+ Ext.FlashComponent.superclass.initComponent.call(this);
+
+ this.addEvents(
+
+ 'initialize'
+ );
+ },
+
+ onRender : function(){
+ Ext.FlashComponent.superclass.onRender.apply(this, arguments);
+
+ var params = Ext.apply({
+ allowScriptAccess: 'always',
+ bgcolor: this.backgroundColor,
+ wmode: this.wmode
+ }, this.flashParams), vars = Ext.apply({
+ allowedDomain: document.location.hostname,
+ YUISwfId: this.getId(),
+ YUIBridgeCallback: 'Ext.FlashEventProxy.onEvent'
+ }, this.flashVars);
+
+ new swfobject.embedSWF(this.url, this.id, this.swfWidth, this.swfHeight, this.flashVersion,
+ this.expressInstall ? Ext.FlashComponent.EXPRESS_INSTALL_URL : undefined, vars, params);
+
+ this.swf = Ext.getDom(this.id);
+ this.el = Ext.get(this.swf);
+ },
+
+ getSwfId : function(){
+ return this.swfId || (this.swfId = "extswf" + (++Ext.Component.AUTO_ID));
+ },
+
+ getId : function(){
+ return this.id || (this.id = "extflashcmp" + (++Ext.Component.AUTO_ID));
+ },
+
+ onFlashEvent : function(e){
+ switch(e.type){
+ case "swfReady":
+ this.initSwf();
+ return;
+ case "log":
+ return;
+ }
+ e.component = this;
+ this.fireEvent(e.type.toLowerCase().replace(/event$/, ''), e);
+ },
+
+ initSwf : function(){
+ this.onSwfReady(!!this.isInitialized);
+ this.isInitialized = true;
+ this.fireEvent('initialize', this);
+ },
+
+ beforeDestroy: function(){
+ if(this.rendered){
+ swfobject.removeSWF(this.swf.id);
+ }
+ Ext.FlashComponent.superclass.beforeDestroy.call(this);
+ },
+
+ onSwfReady : Ext.emptyFn
+});
+
+
+Ext.FlashComponent.EXPRESS_INSTALL_URL = 'http:/' + '/swfobject.googlecode.com/svn/trunk/swfobject/expressInstall.swf';
+
+Ext.reg('flash', Ext.FlashComponent);
+Ext.FlashEventProxy = {
+ onEvent : function(id, e){
+ var fp = Ext.getCmp(id);
+ if(fp){
+ fp.onFlashEvent(e);
+ }else{
+ arguments.callee.defer(10, this, [id, e]);
+ }
+ }
+};
+
+ Ext.chart.Chart = Ext.extend(Ext.FlashComponent, {
+ refreshBuffer: 100,
+
+
+
+
+ chartStyle: {
+ padding: 10,
+ animationEnabled: true,
+ font: {
+ name: 'Tahoma',
+ color: 0x444444,
+ size: 11
+ },
+ dataTip: {
+ padding: 5,
+ border: {
+ color: 0x99bbe8,
+ size:1
+ },
+ background: {
+ color: 0xDAE7F6,
+ alpha: .9
+ },
+ font: {
+ name: 'Tahoma',
+ color: 0x15428B,
+ size: 10,
+ bold: true
+ }
+ }
+ },
+
+
+
+
+ extraStyle: null,
+
+
+ seriesStyles: null,
+
+
+ disableCaching: Ext.isIE || Ext.isOpera,
+ disableCacheParam: '_dc',
+
+ initComponent : function(){
+ Ext.chart.Chart.superclass.initComponent.call(this);
+ if(!this.url){
+ this.url = Ext.chart.Chart.CHART_URL;
+ }
+ if(this.disableCaching){
+ this.url = Ext.urlAppend(this.url, String.format('{0}={1}', this.disableCacheParam, new Date().getTime()));
+ }
+ this.addEvents(
+ 'itemmouseover',
+ 'itemmouseout',
+ 'itemclick',
+ 'itemdoubleclick',
+ 'itemdragstart',
+ 'itemdrag',
+ 'itemdragend',
+
+ 'beforerefresh',
+
+ 'refresh'
+ );
+ this.store = Ext.StoreMgr.lookup(this.store);
+ },
+
+
+ setStyle: function(name, value){
+ this.swf.setStyle(name, Ext.encode(value));
+ },
+
+
+ setStyles: function(styles){
+ this.swf.setStyles(Ext.encode(styles));
+ },
+
+
+ setSeriesStyles: function(styles){
+ this.seriesStyles = styles;
+ var s = [];
+ Ext.each(styles, function(style){
+ s.push(Ext.encode(style));
+ });
+ this.swf.setSeriesStyles(s);
+ },
+
+ setCategoryNames : function(names){
+ this.swf.setCategoryNames(names);
+ },
+
+ setLegendRenderer : function(fn, scope){
+ var chart = this;
+ scope = scope || chart;
+ chart.removeFnProxy(chart.legendFnName);
+ chart.legendFnName = chart.createFnProxy(function(name){
+ return fn.call(scope, name);
+ });
+ chart.swf.setLegendLabelFunction(chart.legendFnName);
+ },
+
+ setTipRenderer : function(fn, scope){
+ var chart = this;
+ scope = scope || chart;
+ chart.removeFnProxy(chart.tipFnName);
+ chart.tipFnName = chart.createFnProxy(function(item, index, series){
+ var record = chart.store.getAt(index);
+ return fn.call(scope, chart, record, index, series);
+ });
+ chart.swf.setDataTipFunction(chart.tipFnName);
+ },
+
+ setSeries : function(series){
+ this.series = series;
+ this.refresh();
+ },
+
+
+ bindStore : function(store, initial){
+ if(!initial && this.store){
+ if(store !== this.store && this.store.autoDestroy){
+ this.store.destroy();
+ }else{
+ this.store.un("datachanged", this.refresh, this);
+ this.store.un("add", this.delayRefresh, this);
+ this.store.un("remove", this.delayRefresh, this);
+ this.store.un("update", this.delayRefresh, this);
+ this.store.un("clear", this.refresh, this);
+ }
+ }
+ if(store){
+ store = Ext.StoreMgr.lookup(store);
+ store.on({
+ scope: this,
+ datachanged: this.refresh,
+ add: this.delayRefresh,
+ remove: this.delayRefresh,
+ update: this.delayRefresh,
+ clear: this.refresh
+ });
+ }
+ this.store = store;
+ if(store && !initial){
+ this.refresh();
+ }
+ },
+
+ onSwfReady : function(isReset){
+ Ext.chart.Chart.superclass.onSwfReady.call(this, isReset);
+ var ref;
+ this.swf.setType(this.type);
+
+ if(this.chartStyle){
+ this.setStyles(Ext.apply({}, this.extraStyle, this.chartStyle));
+ }
+
+ if(this.categoryNames){
+ this.setCategoryNames(this.categoryNames);
+ }
+
+ if(this.tipRenderer){
+ ref = this.getFunctionRef(this.tipRenderer);
+ this.setTipRenderer(ref.fn, ref.scope);
+ }
+ if(this.legendRenderer){
+ ref = this.getFunctionRef(this.legendRenderer);
+ this.setLegendRenderer(ref.fn, ref.scope);
+ }
+ if(!isReset){
+ this.bindStore(this.store, true);
+ }
+ this.refresh.defer(10, this);
+ },
+
+ delayRefresh : function(){
+ if(!this.refreshTask){
+ this.refreshTask = new Ext.util.DelayedTask(this.refresh, this);
+ }
+ this.refreshTask.delay(this.refreshBuffer);
+ },
+
+ refresh : function(){
+ if(this.fireEvent('beforerefresh', this) !== false){
+ var styleChanged = false;
+
+ var data = [], rs = this.store.data.items;
+ for(var j = 0, len = rs.length; j < len; j++){
+ data[j] = rs[j].data;
+ }
+
+
+ var dataProvider = [];
+ var seriesCount = 0;
+ var currentSeries = null;
+ var i = 0;
+ if(this.series){
+ seriesCount = this.series.length;
+ for(i = 0; i < seriesCount; i++){
+ currentSeries = this.series[i];
+ var clonedSeries = {};
+ for(var prop in currentSeries){
+ if(prop == "style" && currentSeries.style !== null){
+ clonedSeries.style = Ext.encode(currentSeries.style);
+ styleChanged = true;
+
+
+
+
+ } else{
+ clonedSeries[prop] = currentSeries[prop];
+ }
+ }
+ dataProvider.push(clonedSeries);
+ }
+ }
+
+ if(seriesCount > 0){
+ for(i = 0; i < seriesCount; i++){
+ currentSeries = dataProvider[i];
+ if(!currentSeries.type){
+ currentSeries.type = this.type;
+ }
+ currentSeries.dataProvider = data;
+ }
+ } else{
+ dataProvider.push({type: this.type, dataProvider: data});
+ }
+ this.swf.setDataProvider(dataProvider);
+ if(this.seriesStyles){
+ this.setSeriesStyles(this.seriesStyles);
+ }
+ this.fireEvent('refresh', this);
+ }
+ },
+
+
+ createFnProxy : function(fn){
+ var fnName = 'extFnProxy' + (++Ext.chart.Chart.PROXY_FN_ID);
+ Ext.chart.Chart.proxyFunction[fnName] = fn;
+ return 'Ext.chart.Chart.proxyFunction.' + fnName;
+ },
+
+
+ removeFnProxy : function(fn){
+ if(!Ext.isEmpty(fn)){
+ fn = fn.replace('Ext.chart.Chart.proxyFunction.', '');
+ delete Ext.chart.Chart.proxyFunction[fn];
+ }
+ },
+
+
+ getFunctionRef : function(val){
+ if(Ext.isFunction(val)){
+ return {
+ fn: val,
+ scope: this
+ };
+ }else{
+ return {
+ fn: val.fn,
+ scope: val.scope || this
+ };
+ }
+ },
+
+
+ onDestroy: function(){
+ if (this.refreshTask && this.refreshTask.cancel){
+ this.refreshTask.cancel();
+ }
+ Ext.chart.Chart.superclass.onDestroy.call(this);
+ this.bindStore(null);
+ this.removeFnProxy(this.tipFnName);
+ this.removeFnProxy(this.legendFnName);
+ }
+});
+Ext.reg('chart', Ext.chart.Chart);
+Ext.chart.Chart.PROXY_FN_ID = 0;
+Ext.chart.Chart.proxyFunction = {};
+
+
+Ext.chart.Chart.CHART_URL = 'http:/' + '/yui.yahooapis.com/2.8.2/build/charts/assets/charts.swf';
+
+
+Ext.chart.PieChart = Ext.extend(Ext.chart.Chart, {
+ type: 'pie',
+
+ onSwfReady : function(isReset){
+ Ext.chart.PieChart.superclass.onSwfReady.call(this, isReset);
+
+ this.setDataField(this.dataField);
+ this.setCategoryField(this.categoryField);
+ },
+
+ setDataField : function(field){
+ this.dataField = field;
+ this.swf.setDataField(field);
+ },
+
+ setCategoryField : function(field){
+ this.categoryField = field;
+ this.swf.setCategoryField(field);
+ }
+});
+Ext.reg('piechart', Ext.chart.PieChart);
+
+
+Ext.chart.CartesianChart = Ext.extend(Ext.chart.Chart, {
+ onSwfReady : function(isReset){
+ Ext.chart.CartesianChart.superclass.onSwfReady.call(this, isReset);
+ this.labelFn = [];
+ if(this.xField){
+ this.setXField(this.xField);
+ }
+ if(this.yField){
+ this.setYField(this.yField);
+ }
+ if(this.xAxis){
+ this.setXAxis(this.xAxis);
+ }
+ if(this.xAxes){
+ this.setXAxes(this.xAxes);
+ }
+ if(this.yAxis){
+ this.setYAxis(this.yAxis);
+ }
+ if(this.yAxes){
+ this.setYAxes(this.yAxes);
+ }
+ if(Ext.isDefined(this.constrainViewport)){
+ this.swf.setConstrainViewport(this.constrainViewport);
+ }
+ },
+
+ setXField : function(value){
+ this.xField = value;
+ this.swf.setHorizontalField(value);
+ },
+
+ setYField : function(value){
+ this.yField = value;
+ this.swf.setVerticalField(value);
+ },
+
+ setXAxis : function(value){
+ this.xAxis = this.createAxis('xAxis', value);
+ this.swf.setHorizontalAxis(this.xAxis);
+ },
+
+ setXAxes : function(value){
+ var axis;
+ for(var i = 0; i < value.length; i++) {
+ axis = this.createAxis('xAxis' + i, value[i]);
+ this.swf.setHorizontalAxis(axis);
+ }
+ },
+
+ setYAxis : function(value){
+ this.yAxis = this.createAxis('yAxis', value);
+ this.swf.setVerticalAxis(this.yAxis);
+ },
+
+ setYAxes : function(value){
+ var axis;
+ for(var i = 0; i < value.length; i++) {
+ axis = this.createAxis('yAxis' + i, value[i]);
+ this.swf.setVerticalAxis(axis);
+ }
+ },
+
+ createAxis : function(axis, value){
+ var o = Ext.apply({}, value),
+ ref,
+ old;
+
+ if(this[axis]){
+ old = this[axis].labelFunction;
+ this.removeFnProxy(old);
+ this.labelFn.remove(old);
+ }
+ if(o.labelRenderer){
+ ref = this.getFunctionRef(o.labelRenderer);
+ o.labelFunction = this.createFnProxy(function(v){
+ return ref.fn.call(ref.scope, v);
+ });
+ delete o.labelRenderer;
+ this.labelFn.push(o.labelFunction);
+ }
+ if(axis.indexOf('xAxis') > -1 && o.position == 'left'){
+ o.position = 'bottom';
+ }
+ return o;
+ },
+
+ onDestroy : function(){
+ Ext.chart.CartesianChart.superclass.onDestroy.call(this);
+ Ext.each(this.labelFn, function(fn){
+ this.removeFnProxy(fn);
+ }, this);
+ }
+});
+Ext.reg('cartesianchart', Ext.chart.CartesianChart);
+
+
+Ext.chart.LineChart = Ext.extend(Ext.chart.CartesianChart, {
+ type: 'line'
+});
+Ext.reg('linechart', Ext.chart.LineChart);
+
+
+Ext.chart.ColumnChart = Ext.extend(Ext.chart.CartesianChart, {
+ type: 'column'
+});
+Ext.reg('columnchart', Ext.chart.ColumnChart);
+
+
+Ext.chart.StackedColumnChart = Ext.extend(Ext.chart.CartesianChart, {
+ type: 'stackcolumn'
+});
+Ext.reg('stackedcolumnchart', Ext.chart.StackedColumnChart);
+
+
+Ext.chart.BarChart = Ext.extend(Ext.chart.CartesianChart, {
+ type: 'bar'
+});
+Ext.reg('barchart', Ext.chart.BarChart);
+
+
+Ext.chart.StackedBarChart = Ext.extend(Ext.chart.CartesianChart, {
+ type: 'stackbar'
+});
+Ext.reg('stackedbarchart', Ext.chart.StackedBarChart);
+
+
+
+
+Ext.chart.Axis = function(config){
+ Ext.apply(this, config);
+};
+
+Ext.chart.Axis.prototype =
+{
+
+ type: null,
+
+
+ orientation: "horizontal",
+
+
+ reverse: false,
+
+
+ labelFunction: null,
+
+
+ hideOverlappingLabels: true,
+
+
+ labelSpacing: 2
+};
+
+
+Ext.chart.NumericAxis = Ext.extend(Ext.chart.Axis, {
+ type: "numeric",
+
+
+ minimum: NaN,
+
+
+ maximum: NaN,
+
+
+ majorUnit: NaN,
+
+
+ minorUnit: NaN,
+
+
+ snapToUnits: true,
+
+
+ alwaysShowZero: true,
+
+
+ scale: "linear",
+
+
+ roundMajorUnit: true,
+
+
+ calculateByLabelSize: true,
+
+
+ position: 'left',
+
+
+ adjustMaximumByMajorUnit: true,
+
+
+ adjustMinimumByMajorUnit: true
+
+});
+
+
+Ext.chart.TimeAxis = Ext.extend(Ext.chart.Axis, {
+ type: "time",
+
+
+ minimum: null,
+
+
+ maximum: null,
+
+
+ majorUnit: NaN,
+
+
+ majorTimeUnit: null,
+
+
+ minorUnit: NaN,
+
+
+ minorTimeUnit: null,
+
+
+ snapToUnits: true,
+
+
+ stackingEnabled: false,
+
+
+ calculateByLabelSize: true
+
+});
+
+
+Ext.chart.CategoryAxis = Ext.extend(Ext.chart.Axis, {
+ type: "category",
+
+
+ categoryNames: null,
+
+
+ calculateCategoryCount: false
+
+});
+
+
+Ext.chart.Series = function(config) { Ext.apply(this, config); };
+
+Ext.chart.Series.prototype =
+{
+
+ type: null,
+
+
+ displayName: null
+};
+
+
+Ext.chart.CartesianSeries = Ext.extend(Ext.chart.Series, {
+
+ xField: null,
+
+
+ yField: null,
+
+
+ showInLegend: true,
+
+
+ axis: 'primary'
+});
+
+
+Ext.chart.ColumnSeries = Ext.extend(Ext.chart.CartesianSeries, {
+ type: "column"
+});
+
+
+Ext.chart.LineSeries = Ext.extend(Ext.chart.CartesianSeries, {
+ type: "line"
+});
+
+
+Ext.chart.BarSeries = Ext.extend(Ext.chart.CartesianSeries, {
+ type: "bar"
+});
+
+
+
+Ext.chart.PieSeries = Ext.extend(Ext.chart.Series, {
+ type: "pie",
+ dataField: null,
+ categoryField: null
+});
+Ext.menu.Menu = Ext.extend(Ext.Container, {
+
+
+
+ minWidth : 120,
+
+ shadow : 'sides',
+
+ subMenuAlign : 'tl-tr?',
+
+ defaultAlign : 'tl-bl?',
+
+ allowOtherMenus : false,
+
+ ignoreParentClicks : false,
+
+ enableScrolling : true,
+
+ maxHeight : null,
+
+ scrollIncrement : 24,
+
+ showSeparator : true,
+
+ defaultOffsets : [0, 0],
+
+
+ plain : false,
+
+
+ floating : true,
+
+
+
+ zIndex: 15000,
+
+
+ hidden : true,
+
+
+ layout : 'menu',
+ hideMode : 'offsets',
+ scrollerHeight : 8,
+ autoLayout : true,
+ defaultType : 'menuitem',
+ bufferResize : false,
+
+ initComponent : function(){
+ if(Ext.isArray(this.initialConfig)){
+ Ext.apply(this, {items:this.initialConfig});
+ }
+ this.addEvents(
+
+ 'click',
+
+ 'mouseover',
+
+ 'mouseout',
+
+ 'itemclick'
+ );
+ Ext.menu.MenuMgr.register(this);
+ if(this.floating){
+ Ext.EventManager.onWindowResize(this.hide, this);
+ }else{
+ if(this.initialConfig.hidden !== false){
+ this.hidden = false;
+ }
+ this.internalDefaults = {hideOnClick: false};
+ }
+ Ext.menu.Menu.superclass.initComponent.call(this);
+ if(this.autoLayout){
+ var fn = this.doLayout.createDelegate(this, []);
+ this.on({
+ add: fn,
+ remove: fn
+ });
+ }
+ },
+
+
+ getLayoutTarget : function() {
+ return this.ul;
+ },
+
+
+ onRender : function(ct, position){
+ if(!ct){
+ ct = Ext.getBody();
+ }
+
+ var dh = {
+ id: this.getId(),
+ cls: 'x-menu ' + ((this.floating) ? 'x-menu-floating x-layer ' : '') + (this.cls || '') + (this.plain ? ' x-menu-plain' : '') + (this.showSeparator ? '' : ' x-menu-nosep'),
+ style: this.style,
+ cn: [
+ {tag: 'a', cls: 'x-menu-focus', href: '#', onclick: 'return false;', tabIndex: '-1'},
+ {tag: 'ul', cls: 'x-menu-list'}
+ ]
+ };
+ if(this.floating){
+ this.el = new Ext.Layer({
+ shadow: this.shadow,
+ dh: dh,
+ constrain: false,
+ parentEl: ct,
+ zindex: this.zIndex
+ });
+ }else{
+ this.el = ct.createChild(dh);
+ }
+ Ext.menu.Menu.superclass.onRender.call(this, ct, position);
+
+ if(!this.keyNav){
+ this.keyNav = new Ext.menu.MenuNav(this);
+ }
+
+ this.focusEl = this.el.child('a.x-menu-focus');
+ this.ul = this.el.child('ul.x-menu-list');
+ this.mon(this.ul, {
+ scope: this,
+ click: this.onClick,
+ mouseover: this.onMouseOver,
+ mouseout: this.onMouseOut
+ });
+ if(this.enableScrolling){
+ this.mon(this.el, {
+ scope: this,
+ delegate: '.x-menu-scroller',
+ click: this.onScroll,
+ mouseover: this.deactivateActive
+ });
+ }
+ },
+
+
+ findTargetItem : function(e){
+ var t = e.getTarget('.x-menu-list-item', this.ul, true);
+ if(t && t.menuItemId){
+ return this.items.get(t.menuItemId);
+ }
+ },
+
+
+ onClick : function(e){
+ var t = this.findTargetItem(e);
+ if(t){
+ if(t.isFormField){
+ this.setActiveItem(t);
+ }else if(t instanceof Ext.menu.BaseItem){
+ if(t.menu && this.ignoreParentClicks){
+ t.expandMenu();
+ e.preventDefault();
+ }else if(t.onClick){
+ t.onClick(e);
+ this.fireEvent('click', this, t, e);
+ }
+ }
+ }
+ },
+
+
+ setActiveItem : function(item, autoExpand){
+ if(item != this.activeItem){
+ this.deactivateActive();
+ if((this.activeItem = item).isFormField){
+ item.focus();
+ }else{
+ item.activate(autoExpand);
+ }
+ }else if(autoExpand){
+ item.expandMenu();
+ }
+ },
+
+ deactivateActive : function(){
+ var a = this.activeItem;
+ if(a){
+ if(a.isFormField){
+
+ if(a.collapse){
+ a.collapse();
+ }
+ }else{
+ a.deactivate();
+ }
+ delete this.activeItem;
+ }
+ },
+
+
+ tryActivate : function(start, step){
+ var items = this.items;
+ for(var i = start, len = items.length; i >= 0 && i < len; i+= step){
+ var item = items.get(i);
+ if(item.isVisible() && !item.disabled && (item.canActivate || item.isFormField)){
+ this.setActiveItem(item, false);
+ return item;
+ }
+ }
+ return false;
+ },
+
+
+ onMouseOver : function(e){
+ var t = this.findTargetItem(e);
+ if(t){
+ if(t.canActivate && !t.disabled){
+ this.setActiveItem(t, true);
+ }
+ }
+ this.over = true;
+ this.fireEvent('mouseover', this, e, t);
+ },
+
+
+ onMouseOut : function(e){
+ var t = this.findTargetItem(e);
+ if(t){
+ if(t == this.activeItem && t.shouldDeactivate && t.shouldDeactivate(e)){
+ this.activeItem.deactivate();
+ delete this.activeItem;
+ }
+ }
+ this.over = false;
+ this.fireEvent('mouseout', this, e, t);
+ },
+
+
+ onScroll : function(e, t){
+ if(e){
+ e.stopEvent();
+ }
+ var ul = this.ul.dom, top = Ext.fly(t).is('.x-menu-scroller-top');
+ ul.scrollTop += this.scrollIncrement * (top ? -1 : 1);
+ if(top ? ul.scrollTop <= 0 : ul.scrollTop + this.activeMax >= ul.scrollHeight){
+ this.onScrollerOut(null, t);
+ }
+ },
+
+
+ onScrollerIn : function(e, t){
+ var ul = this.ul.dom, top = Ext.fly(t).is('.x-menu-scroller-top');
+ if(top ? ul.scrollTop > 0 : ul.scrollTop + this.activeMax < ul.scrollHeight){
+ Ext.fly(t).addClass(['x-menu-item-active', 'x-menu-scroller-active']);
+ }
+ },
+
+
+ onScrollerOut : function(e, t){
+ Ext.fly(t).removeClass(['x-menu-item-active', 'x-menu-scroller-active']);
+ },
+
+
+ show : function(el, pos, parentMenu){
+ if(this.floating){
+ this.parentMenu = parentMenu;
+ if(!this.el){
+ this.render();
+ this.doLayout(false, true);
+ }
+ this.showAt(this.el.getAlignToXY(el, pos || this.defaultAlign, this.defaultOffsets), parentMenu);
+ }else{
+ Ext.menu.Menu.superclass.show.call(this);
+ }
+ },
+
+
+ showAt : function(xy, parentMenu){
+ if(this.fireEvent('beforeshow', this) !== false){
+ this.parentMenu = parentMenu;
+ if(!this.el){
+ this.render();
+ }
+ if(this.enableScrolling){
+
+ this.el.setXY(xy);
+
+ xy[1] = this.constrainScroll(xy[1]);
+ xy = [this.el.adjustForConstraints(xy)[0], xy[1]];
+ }else{
+
+ xy = this.el.adjustForConstraints(xy);
+ }
+ this.el.setXY(xy);
+ this.el.show();
+ Ext.menu.Menu.superclass.onShow.call(this);
+ if(Ext.isIE9m){
+
+ this.fireEvent('autosize', this);
+ if(!Ext.isIE8){
+ this.el.repaint();
+ }
+ }
+ this.hidden = false;
+ this.focus();
+ this.fireEvent('show', this);
+ }
+ },
+
+ constrainScroll : function(y){
+ var max, full = this.ul.setHeight('auto').getHeight(),
+ returnY = y, normalY, parentEl, scrollTop, viewHeight;
+ if(this.floating){
+ parentEl = Ext.fly(this.el.dom.parentNode);
+ scrollTop = parentEl.getScroll().top;
+ viewHeight = parentEl.getViewSize().height;
+
+
+ normalY = y - scrollTop;
+ max = this.maxHeight ? this.maxHeight : viewHeight - normalY;
+ if(full > viewHeight) {
+ max = viewHeight;
+
+ returnY = y - normalY;
+ } else if(max < full) {
+ returnY = y - (full - max);
+ max = full;
+ }
+ }else{
+ max = this.getHeight();
+ }
+
+ if (this.maxHeight){
+ max = Math.min(this.maxHeight, max);
+ }
+ if(full > max && max > 0){
+ this.activeMax = max - this.scrollerHeight * 2 - this.el.getFrameWidth('tb') - Ext.num(this.el.shadowOffset, 0);
+ this.ul.setHeight(this.activeMax);
+ this.createScrollers();
+ this.el.select('.x-menu-scroller').setDisplayed('');
+ }else{
+ this.ul.setHeight(full);
+ this.el.select('.x-menu-scroller').setDisplayed('none');
+ }
+ this.ul.dom.scrollTop = 0;
+ return returnY;
+ },
+
+ createScrollers : function(){
+ if(!this.scroller){
+ this.scroller = {
+ pos: 0,
+ top: this.el.insertFirst({
+ tag: 'div',
+ cls: 'x-menu-scroller x-menu-scroller-top',
+ html: '&#160;'
+ }),
+ bottom: this.el.createChild({
+ tag: 'div',
+ cls: 'x-menu-scroller x-menu-scroller-bottom',
+ html: '&#160;'
+ })
+ };
+ this.scroller.top.hover(this.onScrollerIn, this.onScrollerOut, this);
+ this.scroller.topRepeater = new Ext.util.ClickRepeater(this.scroller.top, {
+ listeners: {
+ click: this.onScroll.createDelegate(this, [null, this.scroller.top], false)
+ }
+ });
+ this.scroller.bottom.hover(this.onScrollerIn, this.onScrollerOut, this);
+ this.scroller.bottomRepeater = new Ext.util.ClickRepeater(this.scroller.bottom, {
+ listeners: {
+ click: this.onScroll.createDelegate(this, [null, this.scroller.bottom], false)
+ }
+ });
+ }
+ },
+
+ onLayout : function(){
+ if(this.isVisible()){
+ if(this.enableScrolling){
+ this.constrainScroll(this.el.getTop());
+ }
+ if(this.floating){
+ this.el.sync();
+ }
+ }
+ },
+
+ focus : function(){
+ if(!this.hidden){
+ this.doFocus.defer(50, this);
+ }
+ },
+
+ doFocus : function(){
+ if(!this.hidden){
+ this.focusEl.focus();
+ }
+ },
+
+
+ hide : function(deep){
+ if (!this.isDestroyed) {
+ this.deepHide = deep;
+ Ext.menu.Menu.superclass.hide.call(this);
+ delete this.deepHide;
+ }
+ },
+
+
+ onHide : function(){
+ Ext.menu.Menu.superclass.onHide.call(this);
+ this.deactivateActive();
+ if(this.el && this.floating){
+ this.el.hide();
+ }
+ var pm = this.parentMenu;
+ if(this.deepHide === true && pm){
+ if(pm.floating){
+ pm.hide(true);
+ }else{
+ pm.deactivateActive();
+ }
+ }
+ },
+
+
+ lookupComponent : function(c){
+ if(Ext.isString(c)){
+ c = (c == 'separator' || c == '-') ? new Ext.menu.Separator() : new Ext.menu.TextItem(c);
+ this.applyDefaults(c);
+ }else{
+ if(Ext.isObject(c)){
+ c = this.getMenuItem(c);
+ }else if(c.tagName || c.el){
+ c = new Ext.BoxComponent({
+ el: c
+ });
+ }
+ }
+ return c;
+ },
+
+ applyDefaults : function(c) {
+ if (!Ext.isString(c)) {
+ c = Ext.menu.Menu.superclass.applyDefaults.call(this, c);
+ var d = this.internalDefaults;
+ if(d){
+ if(c.events){
+ Ext.applyIf(c.initialConfig, d);
+ Ext.apply(c, d);
+ }else{
+ Ext.applyIf(c, d);
+ }
+ }
+ }
+ return c;
+ },
+
+
+ getMenuItem : function(config) {
+ config.ownerCt = this;
+
+ if (!config.isXType) {
+ if (!config.xtype && Ext.isBoolean(config.checked)) {
+ return new Ext.menu.CheckItem(config);
+ }
+ return Ext.create(config, this.defaultType);
+ }
+ return config;
+ },
+
+
+ addSeparator : function() {
+ return this.add(new Ext.menu.Separator());
+ },
+
+
+ addElement : function(el) {
+ return this.add(new Ext.menu.BaseItem({
+ el: el
+ }));
+ },
+
+
+ addItem : function(item) {
+ return this.add(item);
+ },
+
+
+ addMenuItem : function(config) {
+ return this.add(this.getMenuItem(config));
+ },
+
+
+ addText : function(text){
+ return this.add(new Ext.menu.TextItem(text));
+ },
+
+
+ onDestroy : function(){
+ Ext.EventManager.removeResizeListener(this.hide, this);
+ var pm = this.parentMenu;
+ if(pm && pm.activeChild == this){
+ delete pm.activeChild;
+ }
+ delete this.parentMenu;
+ Ext.menu.Menu.superclass.onDestroy.call(this);
+ Ext.menu.MenuMgr.unregister(this);
+ if(this.keyNav) {
+ this.keyNav.disable();
+ }
+ var s = this.scroller;
+ if(s){
+ Ext.destroy(s.topRepeater, s.bottomRepeater, s.top, s.bottom);
+ }
+ Ext.destroy(
+ this.el,
+ this.focusEl,
+ this.ul
+ );
+ }
+});
+
+Ext.reg('menu', Ext.menu.Menu);
+
+
+Ext.menu.MenuNav = Ext.extend(Ext.KeyNav, function(){
+ function up(e, m){
+ if(!m.tryActivate(m.items.indexOf(m.activeItem)-1, -1)){
+ m.tryActivate(m.items.length-1, -1);
+ }
+ }
+ function down(e, m){
+ if(!m.tryActivate(m.items.indexOf(m.activeItem)+1, 1)){
+ m.tryActivate(0, 1);
+ }
+ }
+ return {
+ constructor : function(menu){
+ Ext.menu.MenuNav.superclass.constructor.call(this, menu.el);
+ this.scope = this.menu = menu;
+ },
+
+ doRelay : function(e, h){
+ var k = e.getKey();
+
+ if (this.menu.activeItem && this.menu.activeItem.isFormField && k != e.TAB) {
+ return false;
+ }
+ if(!this.menu.activeItem && e.isNavKeyPress() && k != e.SPACE && k != e.RETURN){
+ this.menu.tryActivate(0, 1);
+ return false;
+ }
+ return h.call(this.scope || this, e, this.menu);
+ },
+
+ tab: function(e, m) {
+ e.stopEvent();
+ if (e.shiftKey) {
+ up(e, m);
+ } else {
+ down(e, m);
+ }
+ },
+
+ up : up,
+
+ down : down,
+
+ right : function(e, m){
+ if(m.activeItem){
+ m.activeItem.expandMenu(true);
+ }
+ },
+
+ left : function(e, m){
+ m.hide();
+ if(m.parentMenu && m.parentMenu.activeItem){
+ m.parentMenu.activeItem.activate();
+ }
+ },
+
+ enter : function(e, m){
+ if(m.activeItem){
+ e.stopPropagation();
+ m.activeItem.onClick(e);
+ m.fireEvent('click', this, m.activeItem);
+ return true;
+ }
+ }
+ };
+}());
+
+Ext.menu.MenuMgr = function(){
+ var menus,
+ active,
+ map,
+ groups = {},
+ attached = false,
+ lastShow = new Date();
+
+
+
+ function init(){
+ menus = {};
+ active = new Ext.util.MixedCollection();
+ map = Ext.getDoc().addKeyListener(27, hideAll);
+ map.disable();
+ }
+
+
+ function hideAll(){
+ if(active && active.length > 0){
+ var c = active.clone();
+ c.each(function(m){
+ m.hide();
+ });
+ return true;
+ }
+ return false;
+ }
+
+
+ function onHide(m){
+ active.remove(m);
+ if(active.length < 1){
+ map.disable();
+ Ext.getDoc().un("mousedown", onMouseDown);
+ attached = false;
+ }
+ }
+
+
+ function onShow(m){
+ var last = active.last();
+ lastShow = new Date();
+ active.add(m);
+ if(!attached){
+ map.enable();
+ Ext.getDoc().on("mousedown", onMouseDown);
+ attached = true;
+ }
+ if(m.parentMenu){
+ m.getEl().setZIndex(parseInt(m.parentMenu.getEl().getStyle("z-index"), 10) + 3);
+ m.parentMenu.activeChild = m;
+ }else if(last && !last.isDestroyed && last.isVisible()){
+ m.getEl().setZIndex(parseInt(last.getEl().getStyle("z-index"), 10) + 3);
+ }
+ }
+
+
+ function onBeforeHide(m){
+ if(m.activeChild){
+ m.activeChild.hide();
+ }
+ if(m.autoHideTimer){
+ clearTimeout(m.autoHideTimer);
+ delete m.autoHideTimer;
+ }
+ }
+
+
+ function onBeforeShow(m){
+ var pm = m.parentMenu;
+ if(!pm && !m.allowOtherMenus){
+ hideAll();
+ }else if(pm && pm.activeChild){
+ pm.activeChild.hide();
+ }
+ }
+
+
+ function onMouseDown(e){
+ if(lastShow.getElapsed() > 50 && active.length > 0 && !e.getTarget(".x-menu")){
+ hideAll();
+ }
+ }
+
+ return {
+
+
+ hideAll : function(){
+ return hideAll();
+ },
+
+
+ register : function(menu){
+ if(!menus){
+ init();
+ }
+ menus[menu.id] = menu;
+ menu.on({
+ beforehide: onBeforeHide,
+ hide: onHide,
+ beforeshow: onBeforeShow,
+ show: onShow
+ });
+ },
+
+
+ get : function(menu){
+ if(typeof menu == "string"){
+ if(!menus){
+ return null;
+ }
+ return menus[menu];
+ }else if(menu.events){
+ return menu;
+ }else if(typeof menu.length == 'number'){
+ return new Ext.menu.Menu({items:menu});
+ }else{
+ return Ext.create(menu, 'menu');
+ }
+ },
+
+
+ unregister : function(menu){
+ delete menus[menu.id];
+ menu.un("beforehide", onBeforeHide);
+ menu.un("hide", onHide);
+ menu.un("beforeshow", onBeforeShow);
+ menu.un("show", onShow);
+ },
+
+
+ registerCheckable : function(menuItem){
+ var g = menuItem.group;
+ if(g){
+ if(!groups[g]){
+ groups[g] = [];
+ }
+ groups[g].push(menuItem);
+ }
+ },
+
+
+ unregisterCheckable : function(menuItem){
+ var g = menuItem.group;
+ if(g){
+ groups[g].remove(menuItem);
+ }
+ },
+
+
+ onCheckChange: function(item, state){
+ if(item.group && state){
+ var group = groups[item.group],
+ i = 0,
+ len = group.length,
+ current;
+
+ for(; i < len; i++){
+ current = group[i];
+ if(current != item){
+ current.setChecked(false);
+ }
+ }
+ }
+ },
+
+ getCheckedItem : function(groupId){
+ var g = groups[groupId];
+ if(g){
+ for(var i = 0, l = g.length; i < l; i++){
+ if(g[i].checked){
+ return g[i];
+ }
+ }
+ }
+ return null;
+ },
+
+ setCheckedItem : function(groupId, itemId){
+ var g = groups[groupId];
+ if(g){
+ for(var i = 0, l = g.length; i < l; i++){
+ if(g[i].id == itemId){
+ g[i].setChecked(true);
+ }
+ }
+ }
+ return null;
+ }
+ };
+}();
+
+Ext.menu.BaseItem = Ext.extend(Ext.Component, {
+
+
+
+
+ canActivate : false,
+
+ activeClass : "x-menu-item-active",
+
+ hideOnClick : true,
+
+ clickHideDelay : 1,
+
+
+ ctype : "Ext.menu.BaseItem",
+
+
+ actionMode : "container",
+
+ initComponent : function(){
+ Ext.menu.BaseItem.superclass.initComponent.call(this);
+ this.addEvents(
+
+ 'click',
+
+ 'activate',
+
+ 'deactivate'
+ );
+ if(this.handler){
+ this.on("click", this.handler, this.scope);
+ }
+ },
+
+
+ onRender : function(container, position){
+ Ext.menu.BaseItem.superclass.onRender.apply(this, arguments);
+ if(this.ownerCt && this.ownerCt instanceof Ext.menu.Menu){
+ this.parentMenu = this.ownerCt;
+ }else{
+ this.container.addClass('x-menu-list-item');
+ this.mon(this.el, {
+ scope: this,
+ click: this.onClick,
+ mouseenter: this.activate,
+ mouseleave: this.deactivate
+ });
+ }
+ },
+
+
+ setHandler : function(handler, scope){
+ if(this.handler){
+ this.un("click", this.handler, this.scope);
+ }
+ this.on("click", this.handler = handler, this.scope = scope);
+ },
+
+
+ onClick : function(e){
+ if(!this.disabled && this.fireEvent("click", this, e) !== false
+ && (this.parentMenu && this.parentMenu.fireEvent("itemclick", this, e) !== false)){
+ this.handleClick(e);
+ }else{
+ e.stopEvent();
+ }
+ },
+
+
+ activate : function(){
+ if(this.disabled){
+ return false;
+ }
+ var li = this.container;
+ li.addClass(this.activeClass);
+ this.region = li.getRegion().adjust(2, 2, -2, -2);
+ this.fireEvent("activate", this);
+ return true;
+ },
+
+
+ deactivate : function(){
+ this.container.removeClass(this.activeClass);
+ this.fireEvent("deactivate", this);
+ },
+
+
+ shouldDeactivate : function(e){
+ return !this.region || !this.region.contains(e.getPoint());
+ },
+
+
+ handleClick : function(e){
+ var pm = this.parentMenu;
+ if(this.hideOnClick){
+ if(pm.floating){
+ this.clickHideDelayTimer = pm.hide.defer(this.clickHideDelay, pm, [true]);
+ }else{
+ pm.deactivateActive();
+ }
+ }
+ },
+
+ beforeDestroy: function(){
+ clearTimeout(this.clickHideDelayTimer);
+ Ext.menu.BaseItem.superclass.beforeDestroy.call(this);
+ },
+
+
+ expandMenu : Ext.emptyFn,
+
+
+ hideMenu : Ext.emptyFn
+});
+Ext.reg('menubaseitem', Ext.menu.BaseItem);
+Ext.menu.TextItem = Ext.extend(Ext.menu.BaseItem, {
+
+
+ hideOnClick : false,
+
+ itemCls : "x-menu-text",
+
+ constructor : function(config) {
+ if (typeof config == 'string') {
+ config = {
+ text: config
+ };
+ }
+ Ext.menu.TextItem.superclass.constructor.call(this, config);
+ },
+
+
+ onRender : function() {
+ var s = document.createElement("span");
+ s.className = this.itemCls;
+ s.innerHTML = this.text;
+ this.el = s;
+ Ext.menu.TextItem.superclass.onRender.apply(this, arguments);
+ }
+});
+Ext.reg('menutextitem', Ext.menu.TextItem);
+Ext.menu.Separator = Ext.extend(Ext.menu.BaseItem, {
+
+ itemCls : "x-menu-sep",
+
+ hideOnClick : false,
+
+
+ activeClass: '',
+
+
+ onRender : function(li){
+ var s = document.createElement("span");
+ s.className = this.itemCls;
+ s.innerHTML = "&#160;";
+ this.el = s;
+ li.addClass("x-menu-sep-li");
+ Ext.menu.Separator.superclass.onRender.apply(this, arguments);
+ }
+});
+Ext.reg('menuseparator', Ext.menu.Separator);
+Ext.menu.Item = Ext.extend(Ext.menu.BaseItem, {
+
+
+
+
+
+
+
+
+ itemCls : 'x-menu-item',
+
+ canActivate : true,
+
+ showDelay: 200,
+
+
+ altText: '',
+
+
+ hideDelay: 200,
+
+
+ ctype: 'Ext.menu.Item',
+
+ initComponent : function(){
+ Ext.menu.Item.superclass.initComponent.call(this);
+ if(this.menu){
+
+
+ if (Ext.isArray(this.menu)){
+ this.menu = { items: this.menu };
+ }
+
+
+
+ if (Ext.isObject(this.menu)){
+ this.menu.ownerCt = this;
+ }
+
+ this.menu = Ext.menu.MenuMgr.get(this.menu);
+ this.menu.ownerCt = undefined;
+ }
+ },
+
+
+ onRender : function(container, position){
+ if (!this.itemTpl) {
+ this.itemTpl = Ext.menu.Item.prototype.itemTpl = new Ext.XTemplate(
+ '<a id="{id}" class="{cls} x-unselectable" hidefocus="true" unselectable="on" href="{href}"',
+ '<tpl if="hrefTarget">',
+ ' target="{hrefTarget}"',
+ '</tpl>',
+ '>',
+ '<img alt="{altText}" src="{icon}" class="x-menu-item-icon {iconCls}"/>',
+ '<span class="x-menu-item-text">{text}</span>',
+ '</a>'
+ );
+ }
+ var a = this.getTemplateArgs();
+ this.el = position ? this.itemTpl.insertBefore(position, a, true) : this.itemTpl.append(container, a, true);
+ this.iconEl = this.el.child('img.x-menu-item-icon');
+ this.textEl = this.el.child('.x-menu-item-text');
+ if(!this.href) {
+ this.mon(this.el, 'click', Ext.emptyFn, null, { preventDefault: true });
+ }
+ Ext.menu.Item.superclass.onRender.call(this, container, position);
+ },
+
+ getTemplateArgs: function() {
+ return {
+ id: this.id,
+ cls: this.itemCls + (this.menu ? ' x-menu-item-arrow' : '') + (this.cls ? ' ' + this.cls : ''),
+ href: this.href || '#',
+ hrefTarget: this.hrefTarget,
+ icon: this.icon || Ext.BLANK_IMAGE_URL,
+ iconCls: this.iconCls || '',
+ text: this.itemText||this.text||'&#160;',
+ altText: this.altText || ''
+ };
+ },
+
+
+ setText : function(text){
+ this.text = text||'&#160;';
+ if(this.rendered){
+ this.textEl.update(this.text);
+ this.parentMenu.layout.doAutoSize();
+ }
+ },
+
+
+ setIconClass : function(cls){
+ var oldCls = this.iconCls;
+ this.iconCls = cls;
+ if(this.rendered){
+ this.iconEl.replaceClass(oldCls, this.iconCls);
+ }
+ },
+
+
+ beforeDestroy: function(){
+ clearTimeout(this.showTimer);
+ clearTimeout(this.hideTimer);
+ if (this.menu){
+ delete this.menu.ownerCt;
+ this.menu.destroy();
+ }
+ Ext.menu.Item.superclass.beforeDestroy.call(this);
+ },
+
+
+ handleClick : function(e){
+ if(!this.href){
+ e.stopEvent();
+ }
+ Ext.menu.Item.superclass.handleClick.apply(this, arguments);
+ },
+
+
+ activate : function(autoExpand){
+ if(Ext.menu.Item.superclass.activate.apply(this, arguments)){
+ this.focus();
+ if(autoExpand){
+ this.expandMenu();
+ }
+ }
+ return true;
+ },
+
+
+ shouldDeactivate : function(e){
+ if(Ext.menu.Item.superclass.shouldDeactivate.call(this, e)){
+ if(this.menu && this.menu.isVisible()){
+ return !this.menu.getEl().getRegion().contains(e.getPoint());
+ }
+ return true;
+ }
+ return false;
+ },
+
+
+ deactivate : function(){
+ Ext.menu.Item.superclass.deactivate.apply(this, arguments);
+ this.hideMenu();
+ },
+
+
+ expandMenu : function(autoActivate){
+ if(!this.disabled && this.menu){
+ clearTimeout(this.hideTimer);
+ delete this.hideTimer;
+ if(!this.menu.isVisible() && !this.showTimer){
+ this.showTimer = this.deferExpand.defer(this.showDelay, this, [autoActivate]);
+ }else if (this.menu.isVisible() && autoActivate){
+ this.menu.tryActivate(0, 1);
+ }
+ }
+ },
+
+
+ deferExpand : function(autoActivate){
+ delete this.showTimer;
+ this.menu.show(this.container, this.parentMenu.subMenuAlign || 'tl-tr?', this.parentMenu);
+ if(autoActivate){
+ this.menu.tryActivate(0, 1);
+ }
+ },
+
+
+ hideMenu : function(){
+ clearTimeout(this.showTimer);
+ delete this.showTimer;
+ if(!this.hideTimer && this.menu && this.menu.isVisible()){
+ this.hideTimer = this.deferHide.defer(this.hideDelay, this);
+ }
+ },
+
+
+ deferHide : function(){
+ delete this.hideTimer;
+ if(this.menu.over){
+ this.parentMenu.setActiveItem(this, false);
+ }else{
+ this.menu.hide();
+ }
+ }
+});
+Ext.reg('menuitem', Ext.menu.Item);
+Ext.menu.CheckItem = Ext.extend(Ext.menu.Item, {
+
+
+ itemCls : "x-menu-item x-menu-check-item",
+
+ groupClass : "x-menu-group-item",
+
+
+ checked: false,
+
+
+ ctype: "Ext.menu.CheckItem",
+
+ initComponent : function(){
+ Ext.menu.CheckItem.superclass.initComponent.call(this);
+ this.addEvents(
+
+ "beforecheckchange" ,
+
+ "checkchange"
+ );
+
+ if(this.checkHandler){
+ this.on('checkchange', this.checkHandler, this.scope);
+ }
+ Ext.menu.MenuMgr.registerCheckable(this);
+ },
+
+
+ onRender : function(c){
+ Ext.menu.CheckItem.superclass.onRender.apply(this, arguments);
+ if(this.group){
+ this.el.addClass(this.groupClass);
+ }
+ if(this.checked){
+ this.checked = false;
+ this.setChecked(true, true);
+ }
+ },
+
+
+ destroy : function(){
+ Ext.menu.MenuMgr.unregisterCheckable(this);
+ Ext.menu.CheckItem.superclass.destroy.apply(this, arguments);
+ },
+
+
+ setChecked : function(state, suppressEvent){
+ var suppress = suppressEvent === true;
+ if(this.checked != state && (suppress || this.fireEvent("beforecheckchange", this, state) !== false)){
+ Ext.menu.MenuMgr.onCheckChange(this, state);
+ if(this.container){
+ this.container[state ? "addClass" : "removeClass"]("x-menu-item-checked");
+ }
+ this.checked = state;
+ if(!suppress){
+ this.fireEvent("checkchange", this, state);
+ }
+ }
+ },
+
+
+ handleClick : function(e){
+ if(!this.disabled && !(this.checked && this.group)){
+ this.setChecked(!this.checked);
+ }
+ Ext.menu.CheckItem.superclass.handleClick.apply(this, arguments);
+ }
+});
+Ext.reg('menucheckitem', Ext.menu.CheckItem);
+ Ext.menu.DateMenu = Ext.extend(Ext.menu.Menu, {
+
+ enableScrolling : false,
+
+
+
+ hideOnClick : true,
+
+
+ pickerId : null,
+
+
+
+
+ cls : 'x-date-menu',
+
+
+
+
+
+ initComponent : function(){
+ this.on('beforeshow', this.onBeforeShow, this);
+ if(this.strict = (Ext.isIE7 && Ext.isStrict)){
+ this.on('show', this.onShow, this, {single: true, delay: 20});
+ }
+ Ext.apply(this, {
+ plain: true,
+ showSeparator: false,
+ items: this.picker = new Ext.DatePicker(Ext.applyIf({
+ internalRender: this.strict || !Ext.isIE9m,
+ ctCls: 'x-menu-date-item',
+ id: this.pickerId
+ }, this.initialConfig))
+ });
+ this.picker.purgeListeners();
+ Ext.menu.DateMenu.superclass.initComponent.call(this);
+
+ this.relayEvents(this.picker, ['select']);
+ this.on('show', this.picker.focus, this.picker);
+ this.on('select', this.menuHide, this);
+ if(this.handler){
+ this.on('select', this.handler, this.scope || this);
+ }
+ },
+
+ menuHide : function() {
+ if(this.hideOnClick){
+ this.hide(true);
+ }
+ },
+
+ onBeforeShow : function(){
+ if(this.picker){
+ this.picker.hideMonthPicker(true);
+ }
+ },
+
+ onShow : function(){
+ var el = this.picker.getEl();
+ el.setWidth(el.getWidth());
+ }
+ });
+ Ext.reg('datemenu', Ext.menu.DateMenu);
+
+ Ext.menu.ColorMenu = Ext.extend(Ext.menu.Menu, {
+
+ enableScrolling : false,
+
+
+
+
+ hideOnClick : true,
+
+ cls : 'x-color-menu',
+
+
+ paletteId : null,
+
+
+
+
+
+
+
+
+
+
+ initComponent : function(){
+ Ext.apply(this, {
+ plain: true,
+ showSeparator: false,
+ items: this.palette = new Ext.ColorPalette(Ext.applyIf({
+ id: this.paletteId
+ }, this.initialConfig))
+ });
+ this.palette.purgeListeners();
+ Ext.menu.ColorMenu.superclass.initComponent.call(this);
+
+ this.relayEvents(this.palette, ['select']);
+ this.on('select', this.menuHide, this);
+ if(this.handler){
+ this.on('select', this.handler, this.scope || this);
+ }
+ },
+
+ menuHide : function(){
+ if(this.hideOnClick){
+ this.hide(true);
+ }
+ }
+});
+Ext.reg('colormenu', Ext.menu.ColorMenu);
+
+Ext.form.Field = Ext.extend(Ext.BoxComponent, {
+
+
+
+
+
+
+
+
+ invalidClass : 'x-form-invalid',
+
+ invalidText : 'The value in this field is invalid',
+
+ focusClass : 'x-form-focus',
+
+
+ validationEvent : 'keyup',
+
+ validateOnBlur : true,
+
+ validationDelay : 250,
+
+ defaultAutoCreate : {tag: 'input', type: 'text', size: '20', autocomplete: 'off'},
+
+ fieldClass : 'x-form-field',
+
+ msgTarget : 'qtip',
+
+ msgFx : 'normal',
+
+ readOnly : false,
+
+ disabled : false,
+
+ submitValue: true,
+
+
+ isFormField : true,
+
+
+ msgDisplay: '',
+
+
+ hasFocus : false,
+
+
+ initComponent : function(){
+ Ext.form.Field.superclass.initComponent.call(this);
+ this.addEvents(
+
+ 'focus',
+
+ 'blur',
+
+ 'specialkey',
+
+ 'change',
+
+ 'invalid',
+
+ 'valid'
+ );
+ },
+
+
+ getName : function(){
+ return this.rendered && this.el.dom.name ? this.el.dom.name : this.name || this.id || '';
+ },
+
+
+ onRender : function(ct, position){
+ if(!this.el){
+ var cfg = this.getAutoCreate();
+
+ if(!cfg.name){
+ cfg.name = this.name || this.id;
+ }
+ if(this.inputType){
+ cfg.type = this.inputType;
+ }
+ this.autoEl = cfg;
+ }
+ Ext.form.Field.superclass.onRender.call(this, ct, position);
+ if(this.submitValue === false){
+ this.el.dom.removeAttribute('name');
+ }
+ var type = this.el.dom.type;
+ if(type){
+ if(type == 'password'){
+ type = 'text';
+ }
+ this.el.addClass('x-form-'+type);
+ }
+ if(this.readOnly){
+ this.setReadOnly(true);
+ }
+ if(this.tabIndex !== undefined){
+ this.el.dom.setAttribute('tabIndex', this.tabIndex);
+ }
+
+ this.el.addClass([this.fieldClass, this.cls]);
+ },
+
+
+ getItemCt : function(){
+ return this.itemCt;
+ },
+
+
+ initValue : function(){
+ if(this.value !== undefined){
+ this.setValue(this.value);
+ }else if(!Ext.isEmpty(this.el.dom.value) && this.el.dom.value != this.emptyText){
+ this.setValue(this.el.dom.value);
+ }
+
+ this.originalValue = this.getValue();
+ },
+
+
+ isDirty : function() {
+ if(this.disabled || !this.rendered) {
+ return false;
+ }
+ return String(this.getValue()) !== String(this.originalValue);
+ },
+
+
+ setReadOnly : function(readOnly){
+ if(this.rendered){
+ this.el.dom.readOnly = readOnly;
+ }
+ this.readOnly = readOnly;
+ },
+
+
+ afterRender : function(){
+ Ext.form.Field.superclass.afterRender.call(this);
+ this.initEvents();
+ this.initValue();
+ },
+
+
+ fireKey : function(e){
+ if(e.isSpecialKey()){
+ this.fireEvent('specialkey', this, e);
+ }
+ },
+
+
+ reset : function(){
+ this.setValue(this.originalValue);
+ this.clearInvalid();
+ },
+
+
+ initEvents : function(){
+ this.mon(this.el, Ext.EventManager.getKeyEvent(), this.fireKey, this);
+ this.mon(this.el, 'focus', this.onFocus, this);
+
+
+
+ this.mon(this.el, 'blur', this.onBlur, this, this.inEditor ? {buffer:10} : null);
+ },
+
+
+ preFocus: Ext.emptyFn,
+
+
+ onFocus : function(){
+ this.preFocus();
+ if(this.focusClass){
+ this.el.addClass(this.focusClass);
+ }
+ if(!this.hasFocus){
+ this.hasFocus = true;
+
+ this.startValue = this.getValue();
+ this.fireEvent('focus', this);
+ }
+ },
+
+
+ beforeBlur : Ext.emptyFn,
+
+
+ onBlur : function(){
+ this.beforeBlur();
+ if(this.focusClass){
+ this.el.removeClass(this.focusClass);
+ }
+ this.hasFocus = false;
+ if(this.validationEvent !== false && (this.validateOnBlur || this.validationEvent == 'blur')){
+ this.validate();
+ }
+ var v = this.getValue();
+ if(String(v) !== String(this.startValue)){
+ this.fireEvent('change', this, v, this.startValue);
+ }
+ this.fireEvent('blur', this);
+ this.postBlur();
+ },
+
+
+ postBlur : Ext.emptyFn,
+
+
+ isValid : function(preventMark){
+ if(this.disabled){
+ return true;
+ }
+ var restore = this.preventMark;
+ this.preventMark = preventMark === true;
+ var v = this.validateValue(this.processValue(this.getRawValue()), preventMark);
+ this.preventMark = restore;
+ return v;
+ },
+
+
+ validate : function(){
+ if(this.disabled || this.validateValue(this.processValue(this.getRawValue()))){
+ this.clearInvalid();
+ return true;
+ }
+ return false;
+ },
+
+
+ processValue : function(value){
+ return value;
+ },
+
+
+ validateValue : function(value) {
+
+ var error = this.getErrors(value)[0];
+
+ if (error == undefined) {
+ return true;
+ } else {
+ this.markInvalid(error);
+ return false;
+ }
+ },
+
+
+ getErrors: function() {
+ return [];
+ },
+
+
+ getActiveError : function(){
+ return this.activeError || '';
+ },
+
+
+ markInvalid : function(msg){
+
+ if (this.rendered && !this.preventMark) {
+ msg = msg || this.invalidText;
+
+ var mt = this.getMessageHandler();
+ if(mt){
+ mt.mark(this, msg);
+ }else if(this.msgTarget){
+ this.el.addClass(this.invalidClass);
+ var t = Ext.getDom(this.msgTarget);
+ if(t){
+ t.innerHTML = msg;
+ t.style.display = this.msgDisplay;
+ }
+ }
+ }
+
+ this.setActiveError(msg);
+ },
+
+
+ clearInvalid : function(){
+
+ if (this.rendered && !this.preventMark) {
+ this.el.removeClass(this.invalidClass);
+ var mt = this.getMessageHandler();
+ if(mt){
+ mt.clear(this);
+ }else if(this.msgTarget){
+ this.el.removeClass(this.invalidClass);
+ var t = Ext.getDom(this.msgTarget);
+ if(t){
+ t.innerHTML = '';
+ t.style.display = 'none';
+ }
+ }
+ }
+
+ this.unsetActiveError();
+ },
+
+
+ setActiveError: function(msg, suppressEvent) {
+ this.activeError = msg;
+ if (suppressEvent !== true) this.fireEvent('invalid', this, msg);
+ },
+
+
+ unsetActiveError: function(suppressEvent) {
+ delete this.activeError;
+ if (suppressEvent !== true) this.fireEvent('valid', this);
+ },
+
+
+ getMessageHandler : function(){
+ return Ext.form.MessageTargets[this.msgTarget];
+ },
+
+
+ getErrorCt : function(){
+ return this.el.findParent('.x-form-element', 5, true) ||
+ this.el.findParent('.x-form-field-wrap', 5, true);
+ },
+
+
+ alignErrorEl : function(){
+ this.errorEl.setWidth(this.getErrorCt().getWidth(true) - 20);
+ },
+
+
+ alignErrorIcon : function(){
+ this.errorIcon.alignTo(this.el, 'tl-tr', [2, 0]);
+ },
+
+
+ getRawValue : function(){
+ var v = this.rendered ? this.el.getValue() : Ext.value(this.value, '');
+ if(v === this.emptyText){
+ v = '';
+ }
+ return v;
+ },
+
+
+ getValue : function(){
+ if(!this.rendered) {
+ return this.value;
+ }
+ var v = this.el.getValue();
+ if(v === this.emptyText || v === undefined){
+ v = '';
+ }
+ return v;
+ },
+
+
+ setRawValue : function(v){
+ return this.rendered ? (this.el.dom.value = (Ext.isEmpty(v) ? '' : v)) : '';
+ },
+
+
+ setValue : function(v){
+ this.value = v;
+ if(this.rendered){
+ this.el.dom.value = (Ext.isEmpty(v) ? '' : v);
+ this.validate();
+ }
+ return this;
+ },
+
+
+ append : function(v){
+ this.setValue([this.getValue(), v].join(''));
+ }
+
+
+
+
+
+});
+
+
+Ext.form.MessageTargets = {
+ 'qtip' : {
+ mark: function(field, msg){
+ field.el.addClass(field.invalidClass);
+ field.el.dom.qtip = msg;
+ field.el.dom.qclass = 'x-form-invalid-tip';
+ if(Ext.QuickTips){
+ Ext.QuickTips.enable();
+ }
+ },
+ clear: function(field){
+ field.el.removeClass(field.invalidClass);
+ field.el.dom.qtip = '';
+ }
+ },
+ 'title' : {
+ mark: function(field, msg){
+ field.el.addClass(field.invalidClass);
+ field.el.dom.title = msg;
+ },
+ clear: function(field){
+ field.el.dom.title = '';
+ }
+ },
+ 'under' : {
+ mark: function(field, msg){
+ field.el.addClass(field.invalidClass);
+ if(!field.errorEl){
+ var elp = field.getErrorCt();
+ if(!elp){
+ field.el.dom.title = msg;
+ return;
+ }
+ field.errorEl = elp.createChild({cls:'x-form-invalid-msg'});
+ field.on('resize', field.alignErrorEl, field);
+ field.on('destroy', function(){
+ Ext.destroy(this.errorEl);
+ }, field);
+ }
+ field.alignErrorEl();
+ field.errorEl.update(msg);
+ Ext.form.Field.msgFx[field.msgFx].show(field.errorEl, field);
+ },
+ clear: function(field){
+ field.el.removeClass(field.invalidClass);
+ if(field.errorEl){
+ Ext.form.Field.msgFx[field.msgFx].hide(field.errorEl, field);
+ }else{
+ field.el.dom.title = '';
+ }
+ }
+ },
+ 'side' : {
+ mark: function(field, msg){
+ field.el.addClass(field.invalidClass);
+ if(!field.errorIcon){
+ var elp = field.getErrorCt();
+
+ if(!elp){
+ field.el.dom.title = msg;
+ return;
+ }
+ field.errorIcon = elp.createChild({cls:'x-form-invalid-icon'});
+ if (field.ownerCt) {
+ field.ownerCt.on('afterlayout', field.alignErrorIcon, field);
+ field.ownerCt.on('expand', field.alignErrorIcon, field);
+ }
+ field.on('resize', field.alignErrorIcon, field);
+ field.on('destroy', function(){
+ Ext.destroy(this.errorIcon);
+ }, field);
+ }
+ field.alignErrorIcon();
+ field.errorIcon.dom.qtip = msg;
+ field.errorIcon.dom.qclass = 'x-form-invalid-tip';
+ field.errorIcon.show();
+ },
+ clear: function(field){
+ field.el.removeClass(field.invalidClass);
+ if(field.errorIcon){
+ field.errorIcon.dom.qtip = '';
+ field.errorIcon.hide();
+ }else{
+ field.el.dom.title = '';
+ }
+ }
+ }
+};
+
+
+Ext.form.Field.msgFx = {
+ normal : {
+ show: function(msgEl, f){
+ msgEl.setDisplayed('block');
+ },
+
+ hide : function(msgEl, f){
+ msgEl.setDisplayed(false).update('');
+ }
+ },
+
+ slide : {
+ show: function(msgEl, f){
+ msgEl.slideIn('t', {stopFx:true});
+ },
+
+ hide : function(msgEl, f){
+ msgEl.slideOut('t', {stopFx:true,useDisplay:true});
+ }
+ },
+
+ slideRight : {
+ show: function(msgEl, f){
+ msgEl.fixDisplay();
+ msgEl.alignTo(f.el, 'tl-tr');
+ msgEl.slideIn('l', {stopFx:true});
+ },
+
+ hide : function(msgEl, f){
+ msgEl.slideOut('l', {stopFx:true,useDisplay:true});
+ }
+ }
+};
+Ext.reg('field', Ext.form.Field);
+
+Ext.form.TextField = Ext.extend(Ext.form.Field, {
+
+
+
+ grow : false,
+
+ growMin : 30,
+
+ growMax : 800,
+
+ vtype : null,
+
+ maskRe : null,
+
+ disableKeyFilter : false,
+
+ allowBlank : true,
+
+ minLength : 0,
+
+ maxLength : Number.MAX_VALUE,
+
+ minLengthText : 'The minimum length for this field is {0}',
+
+ maxLengthText : 'The maximum length for this field is {0}',
+
+ selectOnFocus : false,
+
+ blankText : 'This field is required',
+
+ validator : null,
+
+ regex : null,
+
+ regexText : '',
+
+ emptyText : null,
+
+ emptyClass : 'x-form-empty-field',
+
+
+
+ initComponent : function(){
+ Ext.form.TextField.superclass.initComponent.call(this);
+ this.addEvents(
+
+ 'autosize',
+
+
+ 'keydown',
+
+ 'keyup',
+
+ 'keypress'
+ );
+ },
+
+
+ initEvents : function(){
+ Ext.form.TextField.superclass.initEvents.call(this);
+ if(this.validationEvent == 'keyup'){
+ this.validationTask = new Ext.util.DelayedTask(this.validate, this);
+ this.mon(this.el, 'keyup', this.filterValidation, this);
+ }
+ else if(this.validationEvent !== false && this.validationEvent != 'blur'){
+ this.mon(this.el, this.validationEvent, this.validate, this, {buffer: this.validationDelay});
+ }
+ if(this.selectOnFocus || this.emptyText){
+ this.mon(this.el, 'mousedown', this.onMouseDown, this);
+
+ if(this.emptyText){
+ this.applyEmptyText();
+ }
+ }
+ if(this.maskRe || (this.vtype && this.disableKeyFilter !== true && (this.maskRe = Ext.form.VTypes[this.vtype+'Mask']))){
+ this.mon(this.el, 'keypress', this.filterKeys, this);
+ }
+ if(this.grow){
+ this.mon(this.el, 'keyup', this.onKeyUpBuffered, this, {buffer: 50});
+ this.mon(this.el, 'click', this.autoSize, this);
+ }
+ if(this.enableKeyEvents){
+ this.mon(this.el, {
+ scope: this,
+ keyup: this.onKeyUp,
+ keydown: this.onKeyDown,
+ keypress: this.onKeyPress
+ });
+ }
+ },
+
+ onMouseDown: function(e){
+ if(!this.hasFocus){
+ this.mon(this.el, 'mouseup', Ext.emptyFn, this, { single: true, preventDefault: true });
+ }
+ },
+
+ processValue : function(value){
+ if(this.stripCharsRe){
+ var newValue = value.replace(this.stripCharsRe, '');
+ if(newValue !== value){
+ this.setRawValue(newValue);
+ return newValue;
+ }
+ }
+ return value;
+ },
+
+ filterValidation : function(e){
+ if(!e.isNavKeyPress()){
+ this.validationTask.delay(this.validationDelay);
+ }
+ },
+
+
+ onDisable: function(){
+ Ext.form.TextField.superclass.onDisable.call(this);
+ if(Ext.isIE){
+ this.el.dom.unselectable = 'on';
+ }
+ },
+
+
+ onEnable: function(){
+ Ext.form.TextField.superclass.onEnable.call(this);
+ if(Ext.isIE){
+ this.el.dom.unselectable = '';
+ }
+ },
+
+
+ onKeyUpBuffered : function(e){
+ if(this.doAutoSize(e)){
+ this.autoSize();
+ }
+ },
+
+
+ doAutoSize : function(e){
+ return !e.isNavKeyPress();
+ },
+
+
+ onKeyUp : function(e){
+ this.fireEvent('keyup', this, e);
+ },
+
+
+ onKeyDown : function(e){
+ this.fireEvent('keydown', this, e);
+ },
+
+
+ onKeyPress : function(e){
+ this.fireEvent('keypress', this, e);
+ },
+
+
+ reset : function(){
+ Ext.form.TextField.superclass.reset.call(this);
+ this.applyEmptyText();
+ },
+
+ applyEmptyText : function(){
+ if(this.rendered && this.emptyText && this.getRawValue().length < 1 && !this.hasFocus){
+ this.setRawValue(this.emptyText);
+ this.el.addClass(this.emptyClass);
+ }
+ },
+
+
+ preFocus : function(){
+ var el = this.el,
+ isEmpty;
+ if(this.emptyText){
+ if(el.dom.value == this.emptyText){
+ this.setRawValue('');
+ isEmpty = true;
+ }
+ el.removeClass(this.emptyClass);
+ }
+ if(this.selectOnFocus || isEmpty){
+ el.dom.select();
+ }
+ },
+
+
+ postBlur : function(){
+ this.applyEmptyText();
+ },
+
+
+ filterKeys : function(e){
+ if(e.ctrlKey){
+ return;
+ }
+ var k = e.getKey();
+ if(Ext.isGecko && (e.isNavKeyPress() || k == e.BACKSPACE || (k == e.DELETE && e.button == -1))){
+ return;
+ }
+ var cc = String.fromCharCode(e.getCharCode());
+ if(!Ext.isGecko && e.isSpecialKey() && !cc){
+ return;
+ }
+ if(!this.maskRe.test(cc)){
+ e.stopEvent();
+ }
+ },
+
+ setValue : function(v){
+ if(this.emptyText && this.el && !Ext.isEmpty(v)){
+ this.el.removeClass(this.emptyClass);
+ }
+ Ext.form.TextField.superclass.setValue.apply(this, arguments);
+ this.applyEmptyText();
+ this.autoSize();
+ return this;
+ },
+
+
+ getErrors: function(value) {
+ var errors = Ext.form.TextField.superclass.getErrors.apply(this, arguments);
+
+ value = Ext.isDefined(value) ? value : this.processValue(this.getRawValue());
+
+ if (Ext.isFunction(this.validator)) {
+ var msg = this.validator(value);
+ if (msg !== true) {
+ errors.push(msg);
+ }
+ }
+
+ if (value.length < 1 || value === this.emptyText) {
+ if (this.allowBlank) {
+
+ return errors;
+ } else {
+ errors.push(this.blankText);
+ }
+ }
+
+ if (!this.allowBlank && (value.length < 1 || value === this.emptyText)) {
+ errors.push(this.blankText);
+ }
+
+ if (value.length < this.minLength) {
+ errors.push(String.format(this.minLengthText, this.minLength));
+ }
+
+ if (value.length > this.maxLength) {
+ errors.push(String.format(this.maxLengthText, this.maxLength));
+ }
+
+ if (this.vtype) {
+ var vt = Ext.form.VTypes;
+ if(!vt[this.vtype](value, this)){
+ errors.push(this.vtypeText || vt[this.vtype +'Text']);
+ }
+ }
+
+ if (this.regex && !this.regex.test(value)) {
+ errors.push(this.regexText);
+ }
+
+ return errors;
+ },
+
+
+ selectText : function(start, end){
+ var v = this.getRawValue();
+ var doFocus = false;
+ if(v.length > 0){
+ start = start === undefined ? 0 : start;
+ end = end === undefined ? v.length : end;
+ var d = this.el.dom;
+ if(d.setSelectionRange){
+ d.setSelectionRange(start, end);
+ }else if(d.createTextRange){
+ var range = d.createTextRange();
+ range.moveStart('character', start);
+ range.moveEnd('character', end-v.length);
+ range.select();
+ }
+ doFocus = Ext.isGecko || Ext.isOpera;
+ }else{
+ doFocus = true;
+ }
+ if(doFocus){
+ this.focus();
+ }
+ },
+
+
+ autoSize : function(){
+ if(!this.grow || !this.rendered){
+ return;
+ }
+ if(!this.metrics){
+ this.metrics = Ext.util.TextMetrics.createInstance(this.el);
+ }
+ var el = this.el;
+ var v = el.dom.value;
+ var d = document.createElement('div');
+ d.appendChild(document.createTextNode(v));
+ v = d.innerHTML;
+ Ext.removeNode(d);
+ d = null;
+ v += '&#160;';
+ var w = Math.min(this.growMax, Math.max(this.metrics.getWidth(v) + 10, this.growMin));
+ this.el.setWidth(w);
+ this.fireEvent('autosize', this, w);
+ },
+
+ onDestroy: function(){
+ if(this.validationTask){
+ this.validationTask.cancel();
+ this.validationTask = null;
+ }
+ Ext.form.TextField.superclass.onDestroy.call(this);
+ }
+});
+Ext.reg('textfield', Ext.form.TextField);
+
+Ext.form.TriggerField = Ext.extend(Ext.form.TextField, {
+
+
+
+ defaultAutoCreate : {tag: "input", type: "text", size: "16", autocomplete: "off"},
+
+ hideTrigger:false,
+
+ editable: true,
+
+ readOnly: false,
+
+ wrapFocusClass: 'x-trigger-wrap-focus',
+
+ autoSize: Ext.emptyFn,
+
+ monitorTab : true,
+
+ deferHeight : true,
+
+ mimicing : false,
+
+ actionMode: 'wrap',
+
+ defaultTriggerWidth: 17,
+
+
+ onResize : function(w, h){
+ Ext.form.TriggerField.superclass.onResize.call(this, w, h);
+ var tw = this.getTriggerWidth();
+ if(Ext.isNumber(w)){
+ this.el.setWidth(w - tw);
+ }
+ this.wrap.setWidth(this.el.getWidth() + tw);
+ },
+
+ getTriggerWidth: function(){
+ var tw = this.trigger.getWidth();
+ if(!this.hideTrigger && !this.readOnly && tw === 0){
+ tw = this.defaultTriggerWidth;
+ }
+ return tw;
+ },
+
+
+ alignErrorIcon : function(){
+ if(this.wrap){
+ this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
+ }
+ },
+
+
+ onRender : function(ct, position){
+ this.doc = Ext.isIE ? Ext.getBody() : Ext.getDoc();
+ Ext.form.TriggerField.superclass.onRender.call(this, ct, position);
+
+ this.wrap = this.el.wrap({cls: 'x-form-field-wrap x-form-field-trigger-wrap'});
+ this.trigger = this.wrap.createChild(this.triggerConfig ||
+ {tag: "img", src: Ext.BLANK_IMAGE_URL, alt: "", cls: "x-form-trigger " + this.triggerClass});
+ this.initTrigger();
+ if(!this.width){
+ this.wrap.setWidth(this.el.getWidth()+this.trigger.getWidth());
+ }
+ this.resizeEl = this.positionEl = this.wrap;
+ },
+
+ getWidth: function() {
+ return(this.el.getWidth() + this.trigger.getWidth());
+ },
+
+ updateEditState: function(){
+ if(this.rendered){
+ if (this.readOnly) {
+ this.el.dom.readOnly = true;
+ this.el.addClass('x-trigger-noedit');
+ this.mun(this.el, 'click', this.onTriggerClick, this);
+ this.trigger.setDisplayed(false);
+ } else {
+ if (!this.editable) {
+ this.el.dom.readOnly = true;
+ this.el.addClass('x-trigger-noedit');
+ this.mon(this.el, 'click', this.onTriggerClick, this);
+ } else {
+ this.el.dom.readOnly = false;
+ this.el.removeClass('x-trigger-noedit');
+ this.mun(this.el, 'click', this.onTriggerClick, this);
+ }
+ this.trigger.setDisplayed(!this.hideTrigger);
+ }
+ this.onResize(this.width || this.wrap.getWidth());
+ }
+ },
+
+
+ setHideTrigger: function(hideTrigger){
+ if(hideTrigger != this.hideTrigger){
+ this.hideTrigger = hideTrigger;
+ this.updateEditState();
+ }
+ },
+
+
+ setEditable: function(editable){
+ if(editable != this.editable){
+ this.editable = editable;
+ this.updateEditState();
+ }
+ },
+
+
+ setReadOnly: function(readOnly){
+ if(readOnly != this.readOnly){
+ this.readOnly = readOnly;
+ this.updateEditState();
+ }
+ },
+
+ afterRender : function(){
+ Ext.form.TriggerField.superclass.afterRender.call(this);
+ this.updateEditState();
+ },
+
+
+ initTrigger : function(){
+ this.mon(this.trigger, 'click', this.onTriggerClick, this, {preventDefault:true});
+ this.trigger.addClassOnOver('x-form-trigger-over');
+ this.trigger.addClassOnClick('x-form-trigger-click');
+ },
+
+
+ onDestroy : function(){
+ Ext.destroy(this.trigger, this.wrap);
+ if (this.mimicing){
+ this.doc.un('mousedown', this.mimicBlur, this);
+ }
+ delete this.doc;
+ Ext.form.TriggerField.superclass.onDestroy.call(this);
+ },
+
+
+ onFocus : function(){
+ Ext.form.TriggerField.superclass.onFocus.call(this);
+ if(!this.mimicing){
+ this.wrap.addClass(this.wrapFocusClass);
+ this.mimicing = true;
+ this.doc.on('mousedown', this.mimicBlur, this, {delay: 10});
+ if(this.monitorTab){
+ this.on('specialkey', this.checkTab, this);
+ }
+ }
+ },
+
+
+ checkTab : function(me, e){
+ if(e.getKey() == e.TAB){
+ this.triggerBlur();
+ }
+ },
+
+
+ onBlur : Ext.emptyFn,
+
+
+ mimicBlur : function(e){
+ if(!this.isDestroyed && !this.wrap.contains(e.target) && this.validateBlur(e)){
+ this.triggerBlur();
+ }
+ },
+
+
+ triggerBlur : function(){
+ this.mimicing = false;
+ this.doc.un('mousedown', this.mimicBlur, this);
+ if(this.monitorTab && this.el){
+ this.un('specialkey', this.checkTab, this);
+ }
+ Ext.form.TriggerField.superclass.onBlur.call(this);
+ if(this.wrap){
+ this.wrap.removeClass(this.wrapFocusClass);
+ }
+ },
+
+ beforeBlur : Ext.emptyFn,
+
+
+
+ validateBlur : function(e){
+ return true;
+ },
+
+
+ onTriggerClick : Ext.emptyFn
+
+
+
+
+});
+
+
+Ext.form.TwinTriggerField = Ext.extend(Ext.form.TriggerField, {
+
+
+
+
+ initComponent : function(){
+ Ext.form.TwinTriggerField.superclass.initComponent.call(this);
+
+ this.triggerConfig = {
+ tag:'span', cls:'x-form-twin-triggers', cn:[
+ {tag: "img", src: Ext.BLANK_IMAGE_URL, alt: "", cls: "x-form-trigger " + this.trigger1Class},
+ {tag: "img", src: Ext.BLANK_IMAGE_URL, alt: "", cls: "x-form-trigger " + this.trigger2Class}
+ ]};
+ },
+
+ getTrigger : function(index){
+ return this.triggers[index];
+ },
+
+ afterRender: function(){
+ Ext.form.TwinTriggerField.superclass.afterRender.call(this);
+ var triggers = this.triggers,
+ i = 0,
+ len = triggers.length;
+
+ for(; i < len; ++i){
+ if(this['hideTrigger' + (i + 1)]){
+ triggers[i].hide();
+ }
+
+ }
+ },
+
+ initTrigger : function(){
+ var ts = this.trigger.select('.x-form-trigger', true),
+ triggerField = this;
+
+ ts.each(function(t, all, index){
+ var triggerIndex = 'Trigger'+(index+1);
+ t.hide = function(){
+ var w = triggerField.wrap.getWidth();
+ this.dom.style.display = 'none';
+ triggerField.el.setWidth(w-triggerField.trigger.getWidth());
+ triggerField['hidden' + triggerIndex] = true;
+ };
+ t.show = function(){
+ var w = triggerField.wrap.getWidth();
+ this.dom.style.display = '';
+ triggerField.el.setWidth(w-triggerField.trigger.getWidth());
+ triggerField['hidden' + triggerIndex] = false;
+ };
+ this.mon(t, 'click', this['on'+triggerIndex+'Click'], this, {preventDefault:true});
+ t.addClassOnOver('x-form-trigger-over');
+ t.addClassOnClick('x-form-trigger-click');
+ }, this);
+ this.triggers = ts.elements;
+ },
+
+ getTriggerWidth: function(){
+ var tw = 0;
+ Ext.each(this.triggers, function(t, index){
+ var triggerIndex = 'Trigger' + (index + 1),
+ w = t.getWidth();
+ if(w === 0 && !this['hidden' + triggerIndex]){
+ tw += this.defaultTriggerWidth;
+ }else{
+ tw += w;
+ }
+ }, this);
+ return tw;
+ },
+
+
+ onDestroy : function() {
+ Ext.destroy(this.triggers);
+ Ext.form.TwinTriggerField.superclass.onDestroy.call(this);
+ },
+
+
+ onTrigger1Click : Ext.emptyFn,
+
+ onTrigger2Click : Ext.emptyFn
+});
+Ext.reg('trigger', Ext.form.TriggerField);
+Ext.reg('twintrigger', Ext.form.TwinTriggerField);
+Ext.form.TextArea = Ext.extend(Ext.form.TextField, {
+
+ growMin : 60,
+
+ growMax: 1000,
+ growAppend : '&#160;\n&#160;',
+
+ enterIsSpecial : false,
+
+
+ preventScrollbars: false,
+
+
+
+ onRender : function(ct, position){
+ if(!this.el){
+ this.defaultAutoCreate = {
+ tag: "textarea",
+ style:"width:100px;height:60px;",
+ autocomplete: "off"
+ };
+ }
+ Ext.form.TextArea.superclass.onRender.call(this, ct, position);
+ if(this.grow){
+ this.textSizeEl = Ext.DomHelper.append(document.body, {
+ tag: "pre", cls: "x-form-grow-sizer"
+ });
+ if(this.preventScrollbars){
+ this.el.setStyle("overflow", "hidden");
+ }
+ this.el.setHeight(this.growMin);
+ }
+ },
+
+ onDestroy : function(){
+ Ext.removeNode(this.textSizeEl);
+ Ext.form.TextArea.superclass.onDestroy.call(this);
+ },
+
+ fireKey : function(e){
+ if(e.isSpecialKey() && (this.enterIsSpecial || (e.getKey() != e.ENTER || e.hasModifier()))){
+ this.fireEvent("specialkey", this, e);
+ }
+ },
+
+
+ doAutoSize : function(e){
+ return !e.isNavKeyPress() || e.getKey() == e.ENTER;
+ },
+
+
+ filterValidation: function(e) {
+ if(!e.isNavKeyPress() || (!this.enterIsSpecial && e.keyCode == e.ENTER)){
+ this.validationTask.delay(this.validationDelay);
+ }
+ },
+
+
+ autoSize: function(){
+ if(!this.grow || !this.textSizeEl){
+ return;
+ }
+ var el = this.el,
+ v = Ext.util.Format.htmlEncode(el.dom.value),
+ ts = this.textSizeEl,
+ h;
+
+ Ext.fly(ts).setWidth(this.el.getWidth());
+ if(v.length < 1){
+ v = "&#160;&#160;";
+ }else{
+ v += this.growAppend;
+ if(Ext.isIE){
+ v = v.replace(/\n/g, '&#160;<br />');
+ }
+ }
+ ts.innerHTML = v;
+ h = Math.min(this.growMax, Math.max(ts.offsetHeight, this.growMin));
+ if(h != this.lastHeight){
+ this.lastHeight = h;
+ this.el.setHeight(h);
+ this.fireEvent("autosize", this, h);
+ }
+ }
+});
+Ext.reg('textarea', Ext.form.TextArea);
+Ext.form.NumberField = Ext.extend(Ext.form.TextField, {
+
+
+
+ fieldClass: "x-form-field x-form-num-field",
+
+
+ allowDecimals : true,
+
+
+ decimalSeparator : ".",
+
+
+ decimalPrecision : 2,
+
+
+ allowNegative : true,
+
+
+ minValue : Number.NEGATIVE_INFINITY,
+
+
+ maxValue : Number.MAX_VALUE,
+
+
+ minText : "The minimum value for this field is {0}",
+
+
+ maxText : "The maximum value for this field is {0}",
+
+
+ nanText : "{0} is not a valid number",
+
+
+ baseChars : "0123456789",
+
+
+ autoStripChars: false,
+
+
+ initEvents : function() {
+ var allowed = this.baseChars + '';
+ if (this.allowDecimals) {
+ allowed += this.decimalSeparator;
+ }
+ if (this.allowNegative) {
+ allowed += '-';
+ }
+ allowed = Ext.escapeRe(allowed);
+ this.maskRe = new RegExp('[' + allowed + ']');
+ if (this.autoStripChars) {
+ this.stripCharsRe = new RegExp('[^' + allowed + ']', 'gi');
+ }
+
+ Ext.form.NumberField.superclass.initEvents.call(this);
+ },
+
+
+ getErrors: function(value) {
+ var errors = Ext.form.NumberField.superclass.getErrors.apply(this, arguments);
+
+ value = Ext.isDefined(value) ? value : this.processValue(this.getRawValue());
+
+ if (value.length < 1) {
+ return errors;
+ }
+
+ value = String(value).replace(this.decimalSeparator, ".");
+
+ if(isNaN(value)){
+ errors.push(String.format(this.nanText, value));
+ }
+
+ var num = this.parseValue(value);
+
+ if (num < this.minValue) {
+ errors.push(String.format(this.minText, this.minValue));
+ }
+
+ if (num > this.maxValue) {
+ errors.push(String.format(this.maxText, this.maxValue));
+ }
+
+ return errors;
+ },
+
+ getValue : function() {
+ return this.fixPrecision(this.parseValue(Ext.form.NumberField.superclass.getValue.call(this)));
+ },
+
+ setValue : function(v) {
+ v = Ext.isNumber(v) ? v : parseFloat(String(v).replace(this.decimalSeparator, "."));
+ v = this.fixPrecision(v);
+ v = isNaN(v) ? '' : String(v).replace(".", this.decimalSeparator);
+ return Ext.form.NumberField.superclass.setValue.call(this, v);
+ },
+
+
+ setMinValue : function(value) {
+ this.minValue = Ext.num(value, Number.NEGATIVE_INFINITY);
+ },
+
+
+ setMaxValue : function(value) {
+ this.maxValue = Ext.num(value, Number.MAX_VALUE);
+ },
+
+
+ parseValue : function(value) {
+ value = parseFloat(String(value).replace(this.decimalSeparator, "."));
+ return isNaN(value) ? '' : value;
+ },
+
+
+ fixPrecision : function(value) {
+ var nan = isNaN(value);
+
+ if (!this.allowDecimals || this.decimalPrecision == -1 || nan || !value) {
+ return nan ? '' : value;
+ }
+
+ return parseFloat(parseFloat(value).toFixed(this.decimalPrecision));
+ },
+
+ beforeBlur : function() {
+ var v = this.parseValue(this.getRawValue());
+
+ if (!Ext.isEmpty(v)) {
+ this.setValue(v);
+ }
+ }
+});
+
+Ext.reg('numberfield', Ext.form.NumberField);
+
+Ext.form.DateField = Ext.extend(Ext.form.TriggerField, {
+
+ format : "m/d/Y",
+
+ altFormats : "m/d/Y|n/j/Y|n/j/y|m/j/y|n/d/y|m/j/Y|n/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d|Y-m-d|n-j|n/j",
+
+ disabledDaysText : "Disabled",
+
+ disabledDatesText : "Disabled",
+
+ minText : "The date in this field must be equal to or after {0}",
+
+ maxText : "The date in this field must be equal to or before {0}",
+
+ invalidText : "{0} is not a valid date - it must be in the format {1}",
+
+ triggerClass : 'x-form-date-trigger',
+
+ showToday : true,
+
+
+ startDay : 0,
+
+
+
+
+
+
+
+
+ defaultAutoCreate : {tag: "input", type: "text", size: "10", autocomplete: "off"},
+
+
+
+ initTime: '12',
+
+ initTimeFormat: 'H',
+
+
+ safeParse : function(value, format) {
+ if (Date.formatContainsHourInfo(format)) {
+
+ return Date.parseDate(value, format);
+ } else {
+
+ var parsedDate = Date.parseDate(value + ' ' + this.initTime, format + ' ' + this.initTimeFormat);
+
+ if (parsedDate) {
+ return parsedDate.clearTime();
+ }
+ }
+ },
+
+ initComponent : function(){
+ Ext.form.DateField.superclass.initComponent.call(this);
+
+ this.addEvents(
+
+ 'select'
+ );
+
+ if(Ext.isString(this.minValue)){
+ this.minValue = this.parseDate(this.minValue);
+ }
+ if(Ext.isString(this.maxValue)){
+ this.maxValue = this.parseDate(this.maxValue);
+ }
+ this.disabledDatesRE = null;
+ this.initDisabledDays();
+ },
+
+ initEvents: function() {
+ Ext.form.DateField.superclass.initEvents.call(this);
+ this.keyNav = new Ext.KeyNav(this.el, {
+ "down": function(e) {
+ this.onTriggerClick();
+ },
+ scope: this,
+ forceKeyDown: true
+ });
+ },
+
+
+
+ initDisabledDays : function(){
+ if(this.disabledDates){
+ var dd = this.disabledDates,
+ len = dd.length - 1,
+ re = "(?:";
+
+ Ext.each(dd, function(d, i){
+ re += Ext.isDate(d) ? '^' + Ext.escapeRe(d.dateFormat(this.format)) + '$' : dd[i];
+ if(i != len){
+ re += '|';
+ }
+ }, this);
+ this.disabledDatesRE = new RegExp(re + ')');
+ }
+ },
+
+
+ setDisabledDates : function(dd){
+ this.disabledDates = dd;
+ this.initDisabledDays();
+ if(this.menu){
+ this.menu.picker.setDisabledDates(this.disabledDatesRE);
+ }
+ },
+
+
+ setDisabledDays : function(dd){
+ this.disabledDays = dd;
+ if(this.menu){
+ this.menu.picker.setDisabledDays(dd);
+ }
+ },
+
+
+ setMinValue : function(dt){
+ this.minValue = (Ext.isString(dt) ? this.parseDate(dt) : dt);
+ if(this.menu){
+ this.menu.picker.setMinDate(this.minValue);
+ }
+ },
+
+
+ setMaxValue : function(dt){
+ this.maxValue = (Ext.isString(dt) ? this.parseDate(dt) : dt);
+ if(this.menu){
+ this.menu.picker.setMaxDate(this.maxValue);
+ }
+ },
+
+
+ getErrors: function(value) {
+ var errors = Ext.form.DateField.superclass.getErrors.apply(this, arguments);
+
+ value = this.formatDate(value || this.processValue(this.getRawValue()));
+
+ if (value.length < 1) {
+ return errors;
+ }
+
+ var svalue = value;
+ value = this.parseDate(value);
+ if (!value) {
+ errors.push(String.format(this.invalidText, svalue, this.format));
+ return errors;
+ }
+
+ var time = value.getTime();
+ if (this.minValue && time < this.minValue.clearTime().getTime()) {
+ errors.push(String.format(this.minText, this.formatDate(this.minValue)));
+ }
+
+ if (this.maxValue && time > this.maxValue.clearTime().getTime()) {
+ errors.push(String.format(this.maxText, this.formatDate(this.maxValue)));
+ }
+
+ if (this.disabledDays) {
+ var day = value.getDay();
+
+ for(var i = 0; i < this.disabledDays.length; i++) {
+ if (day === this.disabledDays[i]) {
+ errors.push(this.disabledDaysText);
+ break;
+ }
+ }
+ }
+
+ var fvalue = this.formatDate(value);
+ if (this.disabledDatesRE && this.disabledDatesRE.test(fvalue)) {
+ errors.push(String.format(this.disabledDatesText, fvalue));
+ }
+
+ return errors;
+ },
+
+
+
+ validateBlur : function(){
+ return !this.menu || !this.menu.isVisible();
+ },
+
+
+ getValue : function(){
+ return this.parseDate(Ext.form.DateField.superclass.getValue.call(this)) || "";
+ },
+
+
+ setValue : function(date){
+ return Ext.form.DateField.superclass.setValue.call(this, this.formatDate(this.parseDate(date)));
+ },
+
+
+ parseDate : function(value) {
+ if(!value || Ext.isDate(value)){
+ return value;
+ }
+
+ var v = this.safeParse(value, this.format),
+ af = this.altFormats,
+ afa = this.altFormatsArray;
+
+ if (!v && af) {
+ afa = afa || af.split("|");
+
+ for (var i = 0, len = afa.length; i < len && !v; i++) {
+ v = this.safeParse(value, afa[i]);
+ }
+ }
+ return v;
+ },
+
+
+ onDestroy : function(){
+ Ext.destroy(this.menu, this.keyNav);
+ Ext.form.DateField.superclass.onDestroy.call(this);
+ },
+
+
+ formatDate : function(date){
+ return Ext.isDate(date) ? date.dateFormat(this.format) : date;
+ },
+
+
+
+
+ onTriggerClick : function(){
+ if(this.disabled){
+ return;
+ }
+ if(this.menu == null){
+ this.menu = new Ext.menu.DateMenu({
+ hideOnClick: false,
+ focusOnSelect: false
+ });
+ }
+ this.onFocus();
+ Ext.apply(this.menu.picker, {
+ minDate : this.minValue,
+ maxDate : this.maxValue,
+ disabledDatesRE : this.disabledDatesRE,
+ disabledDatesText : this.disabledDatesText,
+ disabledDays : this.disabledDays,
+ disabledDaysText : this.disabledDaysText,
+ format : this.format,
+ showToday : this.showToday,
+ startDay: this.startDay,
+ minText : String.format(this.minText, this.formatDate(this.minValue)),
+ maxText : String.format(this.maxText, this.formatDate(this.maxValue))
+ });
+ this.menu.picker.setValue(this.getValue() || new Date());
+ this.menu.show(this.el, "tl-bl?");
+ this.menuEvents('on');
+ },
+
+
+ menuEvents: function(method){
+ this.menu[method]('select', this.onSelect, this);
+ this.menu[method]('hide', this.onMenuHide, this);
+ this.menu[method]('show', this.onFocus, this);
+ },
+
+ onSelect: function(m, d){
+ this.setValue(d);
+ this.fireEvent('select', this, d);
+ this.menu.hide();
+ },
+
+ onMenuHide: function(){
+ this.focus(false, 60);
+ this.menuEvents('un');
+ },
+
+
+ beforeBlur : function(){
+ var v = this.parseDate(this.getRawValue());
+ if(v){
+ this.setValue(v);
+ }
+ }
+
+
+
+
+
+});
+Ext.reg('datefield', Ext.form.DateField);
+
+Ext.form.DisplayField = Ext.extend(Ext.form.Field, {
+ validationEvent : false,
+ validateOnBlur : false,
+ defaultAutoCreate : {tag: "div"},
+
+ fieldClass : "x-form-display-field",
+
+ htmlEncode: false,
+
+
+ initEvents : Ext.emptyFn,
+
+ isValid : function(){
+ return true;
+ },
+
+ validate : function(){
+ return true;
+ },
+
+ getRawValue : function(){
+ var v = this.rendered ? this.el.dom.innerHTML : Ext.value(this.value, '');
+ if(v === this.emptyText){
+ v = '';
+ }
+ if(this.htmlEncode){
+ v = Ext.util.Format.htmlDecode(v);
+ }
+ return v;
+ },
+
+ getValue : function(){
+ return this.getRawValue();
+ },
+
+ getName: function() {
+ return this.name;
+ },
+
+ setRawValue : function(v){
+ if(this.htmlEncode){
+ v = Ext.util.Format.htmlEncode(v);
+ }
+ return this.rendered ? (this.el.dom.innerHTML = (Ext.isEmpty(v) ? '' : v)) : (this.value = v);
+ },
+
+ setValue : function(v){
+ this.setRawValue(v);
+ return this;
+ }
+
+
+
+
+
+
+});
+
+Ext.reg('displayfield', Ext.form.DisplayField);
+
+Ext.form.ComboBox = Ext.extend(Ext.form.TriggerField, {
+
+
+
+
+
+
+
+ defaultAutoCreate : {tag: "input", type: "text", size: "24", autocomplete: "off"},
+
+
+
+
+
+
+
+ listClass : '',
+
+ selectedClass : 'x-combo-selected',
+
+ listEmptyText: '',
+
+ triggerClass : 'x-form-arrow-trigger',
+
+ shadow : 'sides',
+
+ listAlign : 'tl-bl?',
+
+ maxHeight : 300,
+
+ minHeight : 90,
+
+ triggerAction : 'query',
+
+ minChars : 4,
+
+ autoSelect : true,
+
+ typeAhead : false,
+
+ queryDelay : 500,
+
+ pageSize : 0,
+
+ selectOnFocus : false,
+
+ queryParam : 'query',
+
+ loadingText : 'Loading...',
+
+ resizable : false,
+
+ handleHeight : 8,
+
+ allQuery: '',
+
+ mode: 'remote',
+
+ minListWidth : 70,
+
+ forceSelection : false,
+
+ typeAheadDelay : 250,
+
+
+
+ lazyInit : true,
+
+
+ clearFilterOnReset : true,
+
+
+ submitValue: undefined,
+
+
+
+
+ initComponent : function(){
+ Ext.form.ComboBox.superclass.initComponent.call(this);
+ this.addEvents(
+
+ 'expand',
+
+ 'collapse',
+
+
+ 'beforeselect',
+
+ 'select',
+
+ 'beforequery'
+ );
+ if(this.transform){
+ var s = Ext.getDom(this.transform);
+ if(!this.hiddenName){
+ this.hiddenName = s.name;
+ }
+ if(!this.store){
+ this.mode = 'local';
+ var d = [], opts = s.options;
+ for(var i = 0, len = opts.length;i < len; i++){
+ var o = opts[i],
+ value = (o.hasAttribute ? o.hasAttribute('value') : o.getAttributeNode('value').specified) ? o.value : o.text;
+ if(o.selected && Ext.isEmpty(this.value, true)) {
+ this.value = value;
+ }
+ d.push([value, o.text]);
+ }
+ this.store = new Ext.data.ArrayStore({
+ idIndex: 0,
+ fields: ['value', 'text'],
+ data : d,
+ autoDestroy: true
+ });
+ this.valueField = 'value';
+ this.displayField = 'text';
+ }
+ s.name = Ext.id();
+ if(!this.lazyRender){
+ this.target = true;
+ this.el = Ext.DomHelper.insertBefore(s, this.autoCreate || this.defaultAutoCreate);
+ this.render(this.el.parentNode, s);
+ }
+ Ext.removeNode(s);
+ }
+
+ else if(this.store){
+ this.store = Ext.StoreMgr.lookup(this.store);
+ if(this.store.autoCreated){
+ this.displayField = this.valueField = 'field1';
+ if(!this.store.expandData){
+ this.displayField = 'field2';
+ }
+ this.mode = 'local';
+ }
+ }
+
+ this.selectedIndex = -1;
+ if(this.mode == 'local'){
+ if(!Ext.isDefined(this.initialConfig.queryDelay)){
+ this.queryDelay = 10;
+ }
+ if(!Ext.isDefined(this.initialConfig.minChars)){
+ this.minChars = 0;
+ }
+ }
+ },
+
+
+ onRender : function(ct, position){
+ if(this.hiddenName && !Ext.isDefined(this.submitValue)){
+ this.submitValue = false;
+ }
+ Ext.form.ComboBox.superclass.onRender.call(this, ct, position);
+ if(this.hiddenName){
+ this.hiddenField = this.el.insertSibling({tag:'input', type:'hidden', name: this.hiddenName,
+ id: (this.hiddenId || Ext.id())}, 'before', true);
+
+ }
+ if(Ext.isGecko){
+ this.el.dom.setAttribute('autocomplete', 'off');
+ }
+
+ if(!this.lazyInit){
+ this.initList();
+ }else{
+ this.on('focus', this.initList, this, {single: true});
+ }
+ },
+
+
+ initValue : function(){
+ Ext.form.ComboBox.superclass.initValue.call(this);
+ if(this.hiddenField){
+ this.hiddenField.value =
+ Ext.value(Ext.isDefined(this.hiddenValue) ? this.hiddenValue : this.value, '');
+ }
+ },
+
+ getParentZIndex : function(){
+ var zindex;
+ if (this.ownerCt){
+ this.findParentBy(function(ct){
+ zindex = parseInt(ct.getPositionEl().getStyle('z-index'), 10);
+ return !!zindex;
+ });
+ }
+ return zindex;
+ },
+
+ getZIndex : function(listParent){
+ listParent = listParent || Ext.getDom(this.getListParent() || Ext.getBody());
+ var zindex = parseInt(Ext.fly(listParent).getStyle('z-index'), 10);
+ if(!zindex){
+ zindex = this.getParentZIndex();
+ }
+ return (zindex || 12000) + 5;
+ },
+
+
+ initList : function(){
+ if(!this.list){
+ var cls = 'x-combo-list',
+ listParent = Ext.getDom(this.getListParent() || Ext.getBody());
+
+ this.list = new Ext.Layer({
+ parentEl: listParent,
+ shadow: this.shadow,
+ cls: [cls, this.listClass].join(' '),
+ constrain:false,
+ zindex: this.getZIndex(listParent)
+ });
+
+ var lw = this.listWidth || Math.max(this.wrap.getWidth(), this.minListWidth);
+ this.list.setSize(lw, 0);
+ this.list.swallowEvent('mousewheel');
+ this.assetHeight = 0;
+ if(this.syncFont !== false){
+ this.list.setStyle('font-size', this.el.getStyle('font-size'));
+ }
+ if(this.title){
+ this.header = this.list.createChild({cls:cls+'-hd', html: this.title});
+ this.assetHeight += this.header.getHeight();
+ }
+
+ this.innerList = this.list.createChild({cls:cls+'-inner'});
+ this.mon(this.innerList, 'mouseover', this.onViewOver, this);
+ this.mon(this.innerList, 'mousemove', this.onViewMove, this);
+ this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
+
+ if(this.pageSize){
+ this.footer = this.list.createChild({cls:cls+'-ft'});
+ this.pageTb = new Ext.PagingToolbar({
+ store: this.store,
+ pageSize: this.pageSize,
+ renderTo:this.footer
+ });
+ this.assetHeight += this.footer.getHeight();
+ }
+
+ if(!this.tpl){
+
+ this.tpl = '<tpl for="."><div class="'+cls+'-item">{' + this.displayField + '}</div></tpl>';
+
+ }
+
+
+ this.view = new Ext.DataView({
+ applyTo: this.innerList,
+ tpl: this.tpl,
+ singleSelect: true,
+ selectedClass: this.selectedClass,
+ itemSelector: this.itemSelector || '.' + cls + '-item',
+ emptyText: this.listEmptyText,
+ deferEmptyText: false
+ });
+
+ this.mon(this.view, {
+ containerclick : this.onViewClick,
+ click : this.onViewClick,
+ scope :this
+ });
+
+ this.bindStore(this.store, true);
+
+ if(this.resizable){
+ this.resizer = new Ext.Resizable(this.list, {
+ pinned:true, handles:'se'
+ });
+ this.mon(this.resizer, 'resize', function(r, w, h){
+ this.maxHeight = h-this.handleHeight-this.list.getFrameWidth('tb')-this.assetHeight;
+ this.listWidth = w;
+ this.innerList.setWidth(w - this.list.getFrameWidth('lr'));
+ this.restrictHeight();
+ }, this);
+
+ this[this.pageSize?'footer':'innerList'].setStyle('margin-bottom', this.handleHeight+'px');
+ }
+ }
+ },
+
+
+ getListParent : function() {
+ return document.body;
+ },
+
+
+ getStore : function(){
+ return this.store;
+ },
+
+
+ bindStore : function(store, initial){
+ if(this.store && !initial){
+ if(this.store !== store && this.store.autoDestroy){
+ this.store.destroy();
+ }else{
+ this.store.un('beforeload', this.onBeforeLoad, this);
+ this.store.un('load', this.onLoad, this);
+ this.store.un('exception', this.collapse, this);
+ }
+ if(!store){
+ this.store = null;
+ if(this.view){
+ this.view.bindStore(null);
+ }
+ if(this.pageTb){
+ this.pageTb.bindStore(null);
+ }
+ }
+ }
+ if(store){
+ if(!initial) {
+ this.lastQuery = null;
+ if(this.pageTb) {
+ this.pageTb.bindStore(store);
+ }
+ }
+
+ this.store = Ext.StoreMgr.lookup(store);
+ this.store.on({
+ scope: this,
+ beforeload: this.onBeforeLoad,
+ load: this.onLoad,
+ exception: this.collapse
+ });
+
+ if(this.view){
+ this.view.bindStore(store);
+ }
+ }
+ },
+
+ reset : function(){
+ if(this.clearFilterOnReset && this.mode == 'local'){
+ this.store.clearFilter();
+ }
+ Ext.form.ComboBox.superclass.reset.call(this);
+ },
+
+
+ initEvents : function(){
+ Ext.form.ComboBox.superclass.initEvents.call(this);
+
+
+ this.keyNav = new Ext.KeyNav(this.el, {
+ "up" : function(e){
+ this.inKeyMode = true;
+ this.selectPrev();
+ },
+
+ "down" : function(e){
+ if(!this.isExpanded()){
+ this.onTriggerClick();
+ }else{
+ this.inKeyMode = true;
+ this.selectNext();
+ }
+ },
+
+ "enter" : function(e){
+ this.onViewClick();
+ },
+
+ "esc" : function(e){
+ this.collapse();
+ },
+
+ "tab" : function(e){
+ if (this.forceSelection === true) {
+ this.collapse();
+ } else {
+ this.onViewClick(false);
+ }
+ return true;
+ },
+
+ scope : this,
+
+ doRelay : function(e, h, hname){
+ if(hname == 'down' || this.scope.isExpanded()){
+
+ var relay = Ext.KeyNav.prototype.doRelay.apply(this, arguments);
+ if((((Ext.isIE9 && Ext.isStrict) || Ext.isIE10p) || !Ext.isIE) && Ext.EventManager.useKeydown){
+
+ this.scope.fireKey(e);
+ }
+ return relay;
+ }
+ return true;
+ },
+
+ forceKeyDown : true,
+ defaultEventAction: 'stopEvent'
+ });
+ this.queryDelay = Math.max(this.queryDelay || 10,
+ this.mode == 'local' ? 10 : 250);
+ this.dqTask = new Ext.util.DelayedTask(this.initQuery, this);
+ if(this.typeAhead){
+ this.taTask = new Ext.util.DelayedTask(this.onTypeAhead, this);
+ }
+ if(!this.enableKeyEvents){
+ this.mon(this.el, 'keyup', this.onKeyUp, this);
+ }
+ },
+
+
+
+ onDestroy : function(){
+ if (this.dqTask){
+ this.dqTask.cancel();
+ this.dqTask = null;
+ }
+ this.bindStore(null);
+ Ext.destroy(
+ this.resizer,
+ this.view,
+ this.pageTb,
+ this.list
+ );
+ Ext.destroyMembers(this, 'hiddenField');
+ Ext.form.ComboBox.superclass.onDestroy.call(this);
+ },
+
+
+ fireKey : function(e){
+ if (!this.isExpanded()) {
+ Ext.form.ComboBox.superclass.fireKey.call(this, e);
+ }
+ },
+
+
+ onResize : function(w, h){
+ Ext.form.ComboBox.superclass.onResize.apply(this, arguments);
+ if(!isNaN(w) && this.isVisible() && this.list){
+ this.doResize(w);
+ }else{
+ this.bufferSize = w;
+ }
+ },
+
+ doResize: function(w){
+ if(!Ext.isDefined(this.listWidth)){
+ var lw = Math.max(w, this.minListWidth);
+ this.list.setWidth(lw);
+ this.innerList.setWidth(lw - this.list.getFrameWidth('lr'));
+ }
+ },
+
+
+ onEnable : function(){
+ Ext.form.ComboBox.superclass.onEnable.apply(this, arguments);
+ if(this.hiddenField){
+ this.hiddenField.disabled = false;
+ }
+ },
+
+
+ onDisable : function(){
+ Ext.form.ComboBox.superclass.onDisable.apply(this, arguments);
+ if(this.hiddenField){
+ this.hiddenField.disabled = true;
+ }
+ },
+
+
+ onBeforeLoad : function(){
+ if(!this.hasFocus){
+ return;
+ }
+ this.innerList.update(this.loadingText ?
+ '<div class="loading-indicator">'+this.loadingText+'</div>' : '');
+ this.restrictHeight();
+ this.selectedIndex = -1;
+ },
+
+
+ onLoad : function(){
+ if(!this.hasFocus){
+ return;
+ }
+ if(this.store.getCount() > 0 || this.listEmptyText){
+ this.expand();
+ this.restrictHeight();
+ if(this.lastQuery == this.allQuery){
+ if(this.editable){
+ this.el.dom.select();
+ }
+
+ if(this.autoSelect !== false && !this.selectByValue(this.value, true)){
+ this.select(0, true);
+ }
+ }else{
+ if(this.autoSelect !== false){
+ this.selectNext();
+ }
+ if(this.typeAhead && this.lastKey != Ext.EventObject.BACKSPACE && this.lastKey != Ext.EventObject.DELETE){
+ this.taTask.delay(this.typeAheadDelay);
+ }
+ }
+ }else{
+ this.collapse();
+ }
+
+ },
+
+
+ onTypeAhead : function(){
+ if(this.store.getCount() > 0){
+ var r = this.store.getAt(0);
+ var newValue = r.data[this.displayField];
+ var len = newValue.length;
+ var selStart = this.getRawValue().length;
+ if(selStart != len){
+ this.setRawValue(newValue);
+ this.selectText(selStart, newValue.length);
+ }
+ }
+ },
+
+
+ assertValue : function(){
+ var val = this.getRawValue(),
+ rec;
+
+ if(this.valueField && Ext.isDefined(this.value)){
+ rec = this.findRecord(this.valueField, this.value);
+ }
+ if(!rec || rec.get(this.displayField) != val){
+ rec = this.findRecord(this.displayField, val);
+ }
+ if(!rec && this.forceSelection){
+ if(val.length > 0 && val != this.emptyText){
+ this.el.dom.value = Ext.value(this.lastSelectionText, '');
+ this.applyEmptyText();
+ }else{
+ this.clearValue();
+ }
+ }else{
+ if(rec && this.valueField){
+
+
+
+ if (this.value == val){
+ return;
+ }
+ val = rec.get(this.valueField || this.displayField);
+ }
+ this.setValue(val);
+ }
+ },
+
+
+ onSelect : function(record, index){
+ if(this.fireEvent('beforeselect', this, record, index) !== false){
+ this.setValue(record.data[this.valueField || this.displayField]);
+ this.collapse();
+ this.fireEvent('select', this, record, index);
+ }
+ },
+
+
+ getName: function(){
+ var hf = this.hiddenField;
+ return hf && hf.name ? hf.name : this.hiddenName || Ext.form.ComboBox.superclass.getName.call(this);
+ },
+
+
+ getValue : function(){
+ if(this.valueField){
+ return Ext.isDefined(this.value) ? this.value : '';
+ }else{
+ return Ext.form.ComboBox.superclass.getValue.call(this);
+ }
+ },
+
+
+ clearValue : function(){
+ if(this.hiddenField){
+ this.hiddenField.value = '';
+ }
+ this.setRawValue('');
+ this.lastSelectionText = '';
+ this.applyEmptyText();
+ this.value = '';
+ },
+
+
+ setValue : function(v){
+ var text = v;
+ if(this.valueField){
+ var r = this.findRecord(this.valueField, v);
+ if(r){
+ text = r.data[this.displayField];
+ }else if(Ext.isDefined(this.valueNotFoundText)){
+ text = this.valueNotFoundText;
+ }
+ }
+ this.lastSelectionText = text;
+ if(this.hiddenField){
+ this.hiddenField.value = Ext.value(v, '');
+ }
+ Ext.form.ComboBox.superclass.setValue.call(this, text);
+ this.value = v;
+ return this;
+ },
+
+
+ findRecord : function(prop, value){
+ var record;
+ if(this.store.getCount() > 0){
+ this.store.each(function(r){
+ if(r.data[prop] == value){
+ record = r;
+ return false;
+ }
+ });
+ }
+ return record;
+ },
+
+
+ onViewMove : function(e, t){
+ this.inKeyMode = false;
+ },
+
+
+ onViewOver : function(e, t){
+ if(this.inKeyMode){
+ return;
+ }
+ var item = this.view.findItemFromChild(t);
+ if(item){
+ var index = this.view.indexOf(item);
+ this.select(index, false);
+ }
+ },
+
+
+ onViewClick : function(doFocus){
+ var index = this.view.getSelectedIndexes()[0],
+ s = this.store,
+ r = s.getAt(index);
+ if(r){
+ this.onSelect(r, index);
+ }else {
+ this.collapse();
+ }
+ if(doFocus !== false){
+ this.el.focus();
+ }
+ },
+
+
+
+ restrictHeight : function(){
+ this.innerList.dom.style.height = '';
+ var inner = this.innerList.dom,
+ pad = this.list.getFrameWidth('tb') + (this.resizable ? this.handleHeight : 0) + this.assetHeight,
+ h = Math.max(inner.clientHeight, inner.offsetHeight, inner.scrollHeight),
+ ha = this.getPosition()[1]-Ext.getBody().getScroll().top,
+ hb = Ext.lib.Dom.getViewHeight()-ha-this.getSize().height,
+ space = Math.max(ha, hb, this.minHeight || 0)-this.list.shadowOffset-pad-5;
+
+ h = Math.min(h, space, this.maxHeight);
+
+ this.innerList.setHeight(h);
+ this.list.beginUpdate();
+ this.list.setHeight(h+pad);
+ this.list.alignTo.apply(this.list, [this.el].concat(this.listAlign));
+ this.list.endUpdate();
+ },
+
+
+ isExpanded : function(){
+ return this.list && this.list.isVisible();
+ },
+
+
+ selectByValue : function(v, scrollIntoView){
+ if(!Ext.isEmpty(v, true)){
+ var r = this.findRecord(this.valueField || this.displayField, v);
+ if(r){
+ this.select(this.store.indexOf(r), scrollIntoView);
+ return true;
+ }
+ }
+ return false;
+ },
+
+
+ select : function(index, scrollIntoView){
+ this.selectedIndex = index;
+ this.view.select(index);
+ if(scrollIntoView !== false){
+ var el = this.view.getNode(index);
+ if(el){
+ this.innerList.scrollChildIntoView(el, false);
+ }
+ }
+
+ },
+
+
+ selectNext : function(){
+ var ct = this.store.getCount();
+ if(ct > 0){
+ if(this.selectedIndex == -1){
+ this.select(0);
+ }else if(this.selectedIndex < ct-1){
+ this.select(this.selectedIndex+1);
+ }
+ }
+ },
+
+
+ selectPrev : function(){
+ var ct = this.store.getCount();
+ if(ct > 0){
+ if(this.selectedIndex == -1){
+ this.select(0);
+ }else if(this.selectedIndex !== 0){
+ this.select(this.selectedIndex-1);
+ }
+ }
+ },
+
+
+ onKeyUp : function(e){
+ var k = e.getKey();
+ if(this.editable !== false && this.readOnly !== true && (k == e.BACKSPACE || !e.isSpecialKey())){
+
+ this.lastKey = k;
+ this.dqTask.delay(this.queryDelay);
+ }
+ Ext.form.ComboBox.superclass.onKeyUp.call(this, e);
+ },
+
+
+ validateBlur : function(){
+ return !this.list || !this.list.isVisible();
+ },
+
+
+ initQuery : function(){
+ this.doQuery(this.getRawValue());
+ },
+
+
+ beforeBlur : function(){
+ this.assertValue();
+ },
+
+
+ postBlur : function(){
+ Ext.form.ComboBox.superclass.postBlur.call(this);
+ this.collapse();
+ this.inKeyMode = false;
+ },
+
+
+ doQuery : function(q, forceAll){
+ q = Ext.isEmpty(q) ? '' : q;
+ var qe = {
+ query: q,
+ forceAll: forceAll,
+ combo: this,
+ cancel:false
+ };
+ if(this.fireEvent('beforequery', qe)===false || qe.cancel){
+ return false;
+ }
+ q = qe.query;
+ forceAll = qe.forceAll;
+ if(forceAll === true || (q.length >= this.minChars)){
+ if(this.lastQuery !== q){
+ this.lastQuery = q;
+ if(this.mode == 'local'){
+ this.selectedIndex = -1;
+ if(forceAll){
+ this.store.clearFilter();
+ }else{
+ this.store.filter(this.displayField, q);
+ }
+ this.onLoad();
+ }else{
+ this.store.baseParams[this.queryParam] = q;
+ this.store.load({
+ params: this.getParams(q)
+ });
+ this.expand();
+ }
+ }else{
+ this.selectedIndex = -1;
+ this.onLoad();
+ }
+ }
+ },
+
+
+ getParams : function(q){
+ var params = {},
+ paramNames = this.store.paramNames;
+ if(this.pageSize){
+ params[paramNames.start] = 0;
+ params[paramNames.limit] = this.pageSize;
+ }
+ return params;
+ },
+
+
+ collapse : function(){
+ if(!this.isExpanded()){
+ return;
+ }
+ this.list.hide();
+ Ext.getDoc().un('mousewheel', this.collapseIf, this);
+ Ext.getDoc().un('mousedown', this.collapseIf, this);
+ this.fireEvent('collapse', this);
+ },
+
+
+ collapseIf : function(e){
+ if(!this.isDestroyed && !e.within(this.wrap) && !e.within(this.list)){
+ this.collapse();
+ }
+ },
+
+
+ expand : function(){
+ if(this.isExpanded() || !this.hasFocus){
+ return;
+ }
+
+ if(this.title || this.pageSize){
+ this.assetHeight = 0;
+ if(this.title){
+ this.assetHeight += this.header.getHeight();
+ }
+ if(this.pageSize){
+ this.assetHeight += this.footer.getHeight();
+ }
+ }
+
+ if(this.bufferSize){
+ this.doResize(this.bufferSize);
+ delete this.bufferSize;
+ }
+ this.list.alignTo.apply(this.list, [this.el].concat(this.listAlign));
+
+
+ this.list.setZIndex(this.getZIndex());
+ this.list.show();
+ if(Ext.isGecko2){
+ this.innerList.setOverflow('auto');
+ }
+ this.mon(Ext.getDoc(), {
+ scope: this,
+ mousewheel: this.collapseIf,
+ mousedown: this.collapseIf
+ });
+ this.fireEvent('expand', this);
+ },
+
+
+
+
+ onTriggerClick : function(){
+ if(this.readOnly || this.disabled){
+ return;
+ }
+ if(this.isExpanded()){
+ this.collapse();
+ this.el.focus();
+ }else {
+ this.onFocus({});
+ if(this.triggerAction == 'all') {
+ this.doQuery(this.allQuery, true);
+ } else {
+ this.doQuery(this.getRawValue());
+ }
+ this.el.focus();
+ }
+ }
+
+
+
+
+
+
+});
+Ext.reg('combo', Ext.form.ComboBox);
+
+Ext.form.Checkbox = Ext.extend(Ext.form.Field, {
+
+ focusClass : undefined,
+
+ fieldClass : 'x-form-field',
+
+ checked : false,
+
+ boxLabel: '&#160;',
+
+ defaultAutoCreate : { tag: 'input', type: 'checkbox', autocomplete: 'off'},
+
+
+
+
+
+ actionMode : 'wrap',
+
+
+ initComponent : function(){
+ Ext.form.Checkbox.superclass.initComponent.call(this);
+ this.addEvents(
+
+ 'check'
+ );
+ },
+
+
+ onResize : function(){
+ Ext.form.Checkbox.superclass.onResize.apply(this, arguments);
+ if(!this.boxLabel && !this.fieldLabel){
+ this.el.alignTo(this.wrap, 'c-c');
+ }
+ },
+
+
+ initEvents : function(){
+ Ext.form.Checkbox.superclass.initEvents.call(this);
+ this.mon(this.el, {
+ scope: this,
+ click: this.onClick,
+ change: this.onClick
+ });
+ },
+
+
+ markInvalid : Ext.emptyFn,
+
+ clearInvalid : Ext.emptyFn,
+
+
+ onRender : function(ct, position){
+ Ext.form.Checkbox.superclass.onRender.call(this, ct, position);
+ if(this.inputValue !== undefined){
+ this.el.dom.value = this.inputValue;
+ }
+ this.wrap = this.el.wrap({cls: 'x-form-check-wrap'});
+ if(this.boxLabel){
+ this.wrap.createChild({tag: 'label', htmlFor: this.el.id, cls: 'x-form-cb-label', html: this.boxLabel});
+ }
+ if(this.checked){
+ this.setValue(true);
+ }else{
+ this.checked = this.el.dom.checked;
+ }
+
+ if (Ext.isIEQuirks) {
+ this.wrap.repaint();
+ }
+ this.resizeEl = this.positionEl = this.wrap;
+ },
+
+
+ onDestroy : function(){
+ Ext.destroy(this.wrap);
+ Ext.form.Checkbox.superclass.onDestroy.call(this);
+ },
+
+
+ initValue : function() {
+ this.originalValue = this.getValue();
+ },
+
+
+ getValue : function(){
+ if(this.rendered){
+ return this.el.dom.checked;
+ }
+ return this.checked;
+ },
+
+
+ onClick : function(){
+ if(this.el.dom.checked != this.checked){
+ this.setValue(this.el.dom.checked);
+ }
+ },
+
+
+ setValue : function(v){
+ var checked = this.checked,
+ inputVal = this.inputValue;
+
+ if (v === false) {
+ this.checked = false;
+ } else {
+ this.checked = (v === true || v === 'true' || v == '1' || (inputVal ? v == inputVal : String(v).toLowerCase() == 'on'));
+ }
+
+ if(this.rendered){
+ this.el.dom.checked = this.checked;
+ this.el.dom.defaultChecked = this.checked;
+ }
+ if(checked != this.checked){
+ this.fireEvent('check', this, this.checked);
+ if(this.handler){
+ this.handler.call(this.scope || this, this, this.checked);
+ }
+ }
+ return this;
+ }
+});
+Ext.reg('checkbox', Ext.form.Checkbox);
+
+Ext.form.CheckboxGroup = Ext.extend(Ext.form.Field, {
+
+
+ columns : 'auto',
+
+ vertical : false,
+
+ allowBlank : true,
+
+ blankText : "You must select at least one item in this group",
+
+
+ defaultType : 'checkbox',
+
+
+ groupCls : 'x-form-check-group',
+
+
+ initComponent: function(){
+ this.addEvents(
+
+ 'change'
+ );
+ this.on('change', this.validate, this);
+ Ext.form.CheckboxGroup.superclass.initComponent.call(this);
+ },
+
+
+ onRender : function(ct, position){
+ if(!this.el){
+ var panelCfg = {
+ autoEl: {
+ id: this.id
+ },
+ cls: this.groupCls,
+ layout: 'column',
+ renderTo: ct,
+ bufferResize: false
+ };
+ var colCfg = {
+ xtype: 'container',
+ defaultType: this.defaultType,
+ layout: 'form',
+ defaults: {
+ hideLabel: true,
+ anchor: '100%'
+ }
+ };
+
+ if(this.items[0].items){
+
+
+
+ Ext.apply(panelCfg, {
+ layoutConfig: {columns: this.items.length},
+ defaults: this.defaults,
+ items: this.items
+ });
+ for(var i=0, len=this.items.length; i<len; i++){
+ Ext.applyIf(this.items[i], colCfg);
+ }
+
+ }else{
+
+
+
+
+ var numCols, cols = [];
+
+ if(typeof this.columns == 'string'){
+ this.columns = this.items.length;
+ }
+ if(!Ext.isArray(this.columns)){
+ var cs = [];
+ for(var i=0; i<this.columns; i++){
+ cs.push((100/this.columns)*.01);
+ }
+ this.columns = cs;
+ }
+
+ numCols = this.columns.length;
+
+
+ for(var i=0; i<numCols; i++){
+ var cc = Ext.apply({items:[]}, colCfg);
+ cc[this.columns[i] <= 1 ? 'columnWidth' : 'width'] = this.columns[i];
+ if(this.defaults){
+ cc.defaults = Ext.apply(cc.defaults || {}, this.defaults);
+ }
+ cols.push(cc);
+ };
+
+
+ if(this.vertical){
+ var rows = Math.ceil(this.items.length / numCols), ri = 0;
+ for(var i=0, len=this.items.length; i<len; i++){
+ if(i>0 && i%rows==0){
+ ri++;
+ }
+ if(this.items[i].fieldLabel){
+ this.items[i].hideLabel = false;
+ }
+ cols[ri].items.push(this.items[i]);
+ };
+ }else{
+ for(var i=0, len=this.items.length; i<len; i++){
+ var ci = i % numCols;
+ if(this.items[i].fieldLabel){
+ this.items[i].hideLabel = false;
+ }
+ cols[ci].items.push(this.items[i]);
+ };
+ }
+
+ Ext.apply(panelCfg, {
+ layoutConfig: {columns: numCols},
+ items: cols
+ });
+ }
+
+ this.panel = new Ext.Container(panelCfg);
+ this.panel.ownerCt = this;
+ this.el = this.panel.getEl();
+
+ if(this.forId && this.itemCls){
+ var l = this.el.up(this.itemCls).child('label', true);
+ if(l){
+ l.setAttribute('htmlFor', this.forId);
+ }
+ }
+
+ var fields = this.panel.findBy(function(c){
+ return c.isFormField;
+ }, this);
+
+ this.items = new Ext.util.MixedCollection();
+ this.items.addAll(fields);
+ }
+ Ext.form.CheckboxGroup.superclass.onRender.call(this, ct, position);
+ },
+
+ initValue : function(){
+ if(this.value){
+ this.setValue.apply(this, this.buffered ? this.value : [this.value]);
+ delete this.buffered;
+ delete this.value;
+ }
+ },
+
+ afterRender : function(){
+ Ext.form.CheckboxGroup.superclass.afterRender.call(this);
+ this.eachItem(function(item){
+ item.on('check', this.fireChecked, this);
+ item.inGroup = true;
+ });
+ },
+
+
+ doLayout: function(){
+
+ if(this.rendered){
+ this.panel.forceLayout = this.ownerCt.forceLayout;
+ this.panel.doLayout();
+ }
+ },
+
+
+ fireChecked: function(){
+ var arr = [];
+ this.eachItem(function(item){
+ if(item.checked){
+ arr.push(item);
+ }
+ });
+ this.fireEvent('change', this, arr);
+ },
+
+
+ getErrors: function() {
+ var errors = Ext.form.CheckboxGroup.superclass.getErrors.apply(this, arguments);
+
+ if (!this.allowBlank) {
+ var blank = true;
+
+ this.eachItem(function(f){
+ if (f.checked) {
+ return (blank = false);
+ }
+ });
+
+ if (blank) errors.push(this.blankText);
+ }
+
+ return errors;
+ },
+
+
+ isDirty: function(){
+
+ if (this.disabled || !this.rendered) {
+ return false;
+ }
+
+ var dirty = false;
+
+ this.eachItem(function(item){
+ if(item.isDirty()){
+ dirty = true;
+ return false;
+ }
+ });
+
+ return dirty;
+ },
+
+
+ setReadOnly : function(readOnly){
+ if(this.rendered){
+ this.eachItem(function(item){
+ item.setReadOnly(readOnly);
+ });
+ }
+ this.readOnly = readOnly;
+ },
+
+
+ onDisable : function(){
+ this.eachItem(function(item){
+ item.disable();
+ });
+ },
+
+
+ onEnable : function(){
+ this.eachItem(function(item){
+ item.enable();
+ });
+ },
+
+
+ onResize : function(w, h){
+ this.panel.setSize(w, h);
+ this.panel.doLayout();
+ },
+
+
+ reset : function(){
+ if (this.originalValue) {
+
+ this.eachItem(function(c){
+ if(c.setValue){
+ c.setValue(false);
+ c.originalValue = c.getValue();
+ }
+ });
+
+
+ this.resetOriginal = true;
+ this.setValue(this.originalValue);
+ delete this.resetOriginal;
+ } else {
+ this.eachItem(function(c){
+ if(c.reset){
+ c.reset();
+ }
+ });
+ }
+
+
+ (function() {
+ this.clearInvalid();
+ }).defer(50, this);
+ },
+
+
+ setValue: function(){
+ if(this.rendered){
+ this.onSetValue.apply(this, arguments);
+ }else{
+ this.buffered = true;
+ this.value = arguments;
+ }
+ return this;
+ },
+
+
+ onSetValue: function(id, value){
+ if(arguments.length == 1){
+ if(Ext.isArray(id)){
+ Ext.each(id, function(val, idx){
+ if (Ext.isObject(val) && val.setValue){
+ val.setValue(true);
+ if (this.resetOriginal === true) {
+ val.originalValue = val.getValue();
+ }
+ } else {
+ var item = this.items.itemAt(idx);
+ if(item){
+ item.setValue(val);
+ }
+ }
+ }, this);
+ }else if(Ext.isObject(id)){
+
+ for(var i in id){
+ var f = this.getBox(i);
+ if(f){
+ f.setValue(id[i]);
+ }
+ }
+ }else{
+ this.setValueForItem(id);
+ }
+ }else{
+ var f = this.getBox(id);
+ if(f){
+ f.setValue(value);
+ }
+ }
+ },
+
+
+ beforeDestroy: function(){
+ Ext.destroy(this.panel);
+ if (!this.rendered) {
+ Ext.destroy(this.items);
+ }
+ Ext.form.CheckboxGroup.superclass.beforeDestroy.call(this);
+
+ },
+
+ setValueForItem : function(val){
+ val = String(val).split(',');
+ this.eachItem(function(item){
+ if(val.indexOf(item.inputValue)> -1){
+ item.setValue(true);
+ }
+ });
+ },
+
+
+ getBox : function(id){
+ var box = null;
+ this.eachItem(function(f){
+ if(id == f || f.dataIndex == id || f.id == id || f.getName() == id){
+ box = f;
+ return false;
+ }
+ });
+ return box;
+ },
+
+
+ getValue : function(){
+ var out = [];
+ this.eachItem(function(item){
+ if(item.checked){
+ out.push(item);
+ }
+ });
+ return out;
+ },
+
+
+ eachItem: function(fn, scope) {
+ if(this.items && this.items.each){
+ this.items.each(fn, scope || this);
+ }
+ },
+
+
+
+
+ getRawValue : Ext.emptyFn,
+
+
+ setRawValue : Ext.emptyFn
+
+});
+
+Ext.reg('checkboxgroup', Ext.form.CheckboxGroup);
+
+Ext.form.CompositeField = Ext.extend(Ext.form.Field, {
+
+
+ defaultMargins: '0 5 0 0',
+
+
+ skipLastItemMargin: true,
+
+
+ isComposite: true,
+
+
+ combineErrors: true,
+
+
+ labelConnector: ', ',
+
+
+
+
+
+ initComponent: function() {
+ var labels = [],
+ items = this.items,
+ item;
+
+ for (var i=0, j = items.length; i < j; i++) {
+ item = items[i];
+
+ if (!Ext.isEmpty(item.ref)){
+ item.ref = '../' + item.ref;
+ }
+
+ labels.push(item.fieldLabel);
+
+
+ Ext.applyIf(item, this.defaults);
+
+
+ if (!(i == j - 1 && this.skipLastItemMargin)) {
+ Ext.applyIf(item, {margins: this.defaultMargins});
+ }
+ }
+
+ this.fieldLabel = this.fieldLabel || this.buildLabel(labels);
+
+
+ this.fieldErrors = new Ext.util.MixedCollection(true, function(item) {
+ return item.field;
+ });
+
+ this.fieldErrors.on({
+ scope : this,
+ add : this.updateInvalidMark,
+ remove : this.updateInvalidMark,
+ replace: this.updateInvalidMark
+ });
+
+ Ext.form.CompositeField.superclass.initComponent.apply(this, arguments);
+
+ this.innerCt = new Ext.Container({
+ layout : 'hbox',
+ items : this.items,
+ cls : 'x-form-composite',
+ defaultMargins: '0 3 0 0',
+ ownerCt: this
+ });
+ delete this.innerCt.ownerCt;
+
+ var fields = this.innerCt.findBy(function(c) {
+ return c.isFormField;
+ }, this);
+
+
+ this.items = new Ext.util.MixedCollection();
+ this.items.addAll(fields);
+
+ },
+
+
+ onRender: function(ct, position) {
+ if (!this.el) {
+
+ var innerCt = this.innerCt;
+ innerCt.render(ct);
+ this.innerCt.ownerCt = this;
+
+ this.el = innerCt.getEl();
+
+
+
+ if (this.combineErrors) {
+ this.eachItem(function(field) {
+ Ext.apply(field, {
+ markInvalid : this.onFieldMarkInvalid.createDelegate(this, [field], 0),
+ clearInvalid: this.onFieldClearInvalid.createDelegate(this, [field], 0)
+ });
+ });
+ }
+
+
+ var l = this.el.parent().parent().child('label', true);
+ if (l) {
+ l.setAttribute('for', this.items.items[0].id);
+ }
+ }
+
+ Ext.form.CompositeField.superclass.onRender.apply(this, arguments);
+ },
+
+
+ onFieldMarkInvalid: function(field, message) {
+ var name = field.getName(),
+ error = {
+ field: name,
+ errorName: field.fieldLabel || name,
+ error: message
+ };
+
+ this.fieldErrors.replace(name, error);
+
+ if (!field.preventMark) {
+ field.el.addClass(field.invalidClass);
+ }
+ },
+
+
+ onFieldClearInvalid: function(field) {
+ this.fieldErrors.removeKey(field.getName());
+
+ field.el.removeClass(field.invalidClass);
+ },
+
+
+ updateInvalidMark: function() {
+ var ieStrict = Ext.isIE6 && Ext.isStrict;
+
+ if (this.fieldErrors.length == 0) {
+ this.clearInvalid();
+
+
+ if (ieStrict) {
+ this.clearInvalid.defer(50, this);
+ }
+ } else {
+ var message = this.buildCombinedErrorMessage(this.fieldErrors.items);
+
+ this.sortErrors();
+ this.markInvalid(message);
+
+
+ if (ieStrict) {
+ this.markInvalid(message);
+ }
+ }
+ },
+
+
+ validateValue: function(value, preventMark) {
+ var valid = true;
+
+ this.eachItem(function(field) {
+ if (!field.isValid(preventMark)) {
+ valid = false;
+ }
+ });
+
+ return valid;
+ },
+
+
+ buildCombinedErrorMessage: function(errors) {
+ var combined = [],
+ error;
+
+ for (var i = 0, j = errors.length; i < j; i++) {
+ error = errors[i];
+
+ combined.push(String.format("{0}: {1}", error.errorName, error.error));
+ }
+
+ return combined.join("<br />");
+ },
+
+
+ sortErrors: function() {
+ var fields = this.items;
+
+ this.fieldErrors.sort("ASC", function(a, b) {
+ var findByName = function(key) {
+ return function(field) {
+ return field.getName() == key;
+ };
+ };
+
+ var aIndex = fields.findIndexBy(findByName(a.field)),
+ bIndex = fields.findIndexBy(findByName(b.field));
+
+ return aIndex < bIndex ? -1 : 1;
+ });
+ },
+
+
+ reset: function() {
+ this.eachItem(function(item) {
+ item.reset();
+ });
+
+
+
+ (function() {
+ this.clearInvalid();
+ }).defer(50, this);
+ },
+
+
+ clearInvalidChildren: function() {
+ this.eachItem(function(item) {
+ item.clearInvalid();
+ });
+ },
+
+
+ buildLabel: function(segments) {
+ return Ext.clean(segments).join(this.labelConnector);
+ },
+
+
+ isDirty: function(){
+
+ if (this.disabled || !this.rendered) {
+ return false;
+ }
+
+ var dirty = false;
+ this.eachItem(function(item){
+ if(item.isDirty()){
+ dirty = true;
+ return false;
+ }
+ });
+ return dirty;
+ },
+
+
+ eachItem: function(fn, scope) {
+ if(this.items && this.items.each){
+ this.items.each(fn, scope || this);
+ }
+ },
+
+
+ onResize: function(adjWidth, adjHeight, rawWidth, rawHeight) {
+ var innerCt = this.innerCt;
+
+ if (this.rendered && innerCt.rendered) {
+ innerCt.setSize(adjWidth, adjHeight);
+ }
+
+ Ext.form.CompositeField.superclass.onResize.apply(this, arguments);
+ },
+
+
+ doLayout: function(shallow, force) {
+ if (this.rendered) {
+ var innerCt = this.innerCt;
+
+ innerCt.forceLayout = this.ownerCt.forceLayout;
+ innerCt.doLayout(shallow, force);
+ }
+ },
+
+
+ beforeDestroy: function(){
+ Ext.destroy(this.innerCt);
+
+ Ext.form.CompositeField.superclass.beforeDestroy.call(this);
+ },
+
+
+ setReadOnly : function(readOnly) {
+ if (readOnly == undefined) {
+ readOnly = true;
+ }
+ readOnly = !!readOnly;
+
+ if(this.rendered){
+ this.eachItem(function(item){
+ item.setReadOnly(readOnly);
+ });
+ }
+ this.readOnly = readOnly;
+ },
+
+ onShow : function() {
+ Ext.form.CompositeField.superclass.onShow.call(this);
+ this.doLayout();
+ },
+
+
+ onDisable : function(){
+ this.eachItem(function(item){
+ item.disable();
+ });
+ },
+
+
+ onEnable : function(){
+ this.eachItem(function(item){
+ item.enable();
+ });
+ }
+});
+
+Ext.reg('compositefield', Ext.form.CompositeField);
+Ext.form.Radio = Ext.extend(Ext.form.Checkbox, {
+ inputType: 'radio',
+
+
+ markInvalid : Ext.emptyFn,
+
+ clearInvalid : Ext.emptyFn,
+
+
+ getGroupValue : function(){
+ var p = this.el.up('form') || Ext.getBody();
+ var c = p.child('input[name="'+this.el.dom.name+'"]:checked', true);
+ return c ? c.value : null;
+ },
+
+
+ setValue : function(v){
+ var checkEl,
+ els,
+ radio;
+ if (typeof v == 'boolean') {
+ Ext.form.Radio.superclass.setValue.call(this, v);
+ } else if (this.rendered) {
+ checkEl = this.getCheckEl();
+ radio = checkEl.child('input[name="' + this.el.dom.name + '"][value="' + v + '"]', true);
+ if(radio){
+ Ext.getCmp(radio.id).setValue(true);
+ }
+ }
+ if(this.rendered && this.checked){
+ checkEl = checkEl || this.getCheckEl();
+ els = this.getCheckEl().select('input[name="' + this.el.dom.name + '"]');
+ els.each(function(el){
+ if(el.dom.id != this.id){
+ Ext.getCmp(el.dom.id).setValue(false);
+ }
+ }, this);
+ }
+ return this;
+ },
+
+
+ getCheckEl: function(){
+ if(this.inGroup){
+ return this.el.up('.x-form-radio-group');
+ }
+ return this.el.up('form') || Ext.getBody();
+ }
+});
+Ext.reg('radio', Ext.form.Radio);
+
+Ext.form.RadioGroup = Ext.extend(Ext.form.CheckboxGroup, {
+
+
+ allowBlank : true,
+
+ blankText : 'You must select one item in this group',
+
+
+ defaultType : 'radio',
+
+
+ groupCls : 'x-form-radio-group',
+
+
+
+
+ getValue : function(){
+ var out = null;
+ this.eachItem(function(item){
+ if(item.checked){
+ out = item;
+ return false;
+ }
+ });
+ return out;
+ },
+
+
+ onSetValue : function(id, value){
+ if(arguments.length > 1){
+ var f = this.getBox(id);
+ if(f){
+ f.setValue(value);
+ if(f.checked){
+ this.eachItem(function(item){
+ if (item !== f){
+ item.setValue(false);
+ }
+ });
+ }
+ }
+ }else{
+ this.setValueForItem(id);
+ }
+ },
+
+ setValueForItem : function(val){
+ val = String(val).split(',')[0];
+ this.eachItem(function(item){
+ item.setValue(val == item.inputValue);
+ });
+ },
+
+
+ fireChecked : function(){
+ if(!this.checkTask){
+ this.checkTask = new Ext.util.DelayedTask(this.bufferChecked, this);
+ }
+ this.checkTask.delay(10);
+ },
+
+
+ bufferChecked : function(){
+ var out = null;
+ this.eachItem(function(item){
+ if(item.checked){
+ out = item;
+ return false;
+ }
+ });
+ this.fireEvent('change', this, out);
+ },
+
+ onDestroy : function(){
+ if(this.checkTask){
+ this.checkTask.cancel();
+ this.checkTask = null;
+ }
+ Ext.form.RadioGroup.superclass.onDestroy.call(this);
+ }
+
+});
+
+Ext.reg('radiogroup', Ext.form.RadioGroup);
+
+Ext.form.Hidden = Ext.extend(Ext.form.Field, {
+
+ inputType : 'hidden',
+
+ shouldLayout: false,
+
+
+ onRender : function(){
+ Ext.form.Hidden.superclass.onRender.apply(this, arguments);
+ },
+
+
+ initEvents : function(){
+ this.originalValue = this.getValue();
+ },
+
+
+ setSize : Ext.emptyFn,
+ setWidth : Ext.emptyFn,
+ setHeight : Ext.emptyFn,
+ setPosition : Ext.emptyFn,
+ setPagePosition : Ext.emptyFn,
+ markInvalid : Ext.emptyFn,
+ clearInvalid : Ext.emptyFn
+});
+Ext.reg('hidden', Ext.form.Hidden);
+Ext.form.BasicForm = Ext.extend(Ext.util.Observable, {
+
+ constructor: function(el, config){
+ Ext.apply(this, config);
+ if(Ext.isString(this.paramOrder)){
+ this.paramOrder = this.paramOrder.split(/[\s,|]/);
+ }
+
+ this.items = new Ext.util.MixedCollection(false, function(o){
+ return o.getItemId();
+ });
+ this.addEvents(
+
+ 'beforeaction',
+
+ 'actionfailed',
+
+ 'actioncomplete'
+ );
+
+ if(el){
+ this.initEl(el);
+ }
+ Ext.form.BasicForm.superclass.constructor.call(this);
+ },
+
+
+
+
+
+
+
+
+ timeout: 30,
+
+
+
+
+ paramOrder: undefined,
+
+
+ paramsAsHash: false,
+
+
+ waitTitle: 'Please Wait...',
+
+
+ activeAction : null,
+
+
+ trackResetOnLoad : false,
+
+
+
+
+
+ initEl : function(el){
+ this.el = Ext.get(el);
+ this.id = this.el.id || Ext.id();
+ if(!this.standardSubmit){
+ this.el.on('submit', this.onSubmit, this);
+ }
+ this.el.addClass('x-form');
+ },
+
+
+ getEl: function(){
+ return this.el;
+ },
+
+
+ onSubmit : function(e){
+ e.stopEvent();
+ },
+
+
+ destroy: function(bound){
+ if(bound !== true){
+ this.items.each(function(f){
+ Ext.destroy(f);
+ });
+ Ext.destroy(this.el);
+ }
+ this.items.clear();
+ this.purgeListeners();
+ },
+
+
+ isValid : function(){
+ var valid = true;
+ this.items.each(function(f){
+ if(!f.validate()){
+ valid = false;
+ }
+ });
+ return valid;
+ },
+
+
+ isDirty : function(){
+ var dirty = false;
+ this.items.each(function(f){
+ if(f.isDirty()){
+ dirty = true;
+ return false;
+ }
+ });
+ return dirty;
+ },
+
+
+ doAction : function(action, options){
+ if(Ext.isString(action)){
+ action = new Ext.form.Action.ACTION_TYPES[action](this, options);
+ }
+ if(this.fireEvent('beforeaction', this, action) !== false){
+ this.beforeAction(action);
+ action.run.defer(100, action);
+ }
+ return this;
+ },
+
+
+ submit : function(options){
+ options = options || {};
+ if(this.standardSubmit){
+ var v = options.clientValidation === false || this.isValid();
+ if(v){
+ var el = this.el.dom;
+ if(this.url && Ext.isEmpty(el.action)){
+ el.action = this.url;
+ }
+ el.submit();
+ }
+ return v;
+ }
+ var submitAction = String.format('{0}submit', this.api ? 'direct' : '');
+ this.doAction(submitAction, options);
+ return this;
+ },
+
+
+ load : function(options){
+ var loadAction = String.format('{0}load', this.api ? 'direct' : '');
+ this.doAction(loadAction, options);
+ return this;
+ },
+
+
+ updateRecord : function(record){
+ record.beginEdit();
+ var fs = record.fields,
+ field,
+ value;
+ fs.each(function(f){
+ field = this.findField(f.name);
+ if(field){
+ value = field.getValue();
+ if (Ext.type(value) !== false && value.getGroupValue) {
+ value = value.getGroupValue();
+ } else if ( field.eachItem ) {
+ value = [];
+ field.eachItem(function(item){
+ value.push(item.getValue());
+ });
+ }
+ record.set(f.name, value);
+ }
+ }, this);
+ record.endEdit();
+ return this;
+ },
+
+
+ loadRecord : function(record){
+ this.setValues(record.data);
+ return this;
+ },
+
+
+ beforeAction : function(action){
+
+ this.items.each(function(f){
+ if(f.isFormField && f.syncValue){
+ f.syncValue();
+ }
+ });
+ var o = action.options;
+ if(o.waitMsg){
+ if(this.waitMsgTarget === true){
+ this.el.mask(o.waitMsg, 'x-mask-loading');
+ }else if(this.waitMsgTarget){
+ this.waitMsgTarget = Ext.get(this.waitMsgTarget);
+ this.waitMsgTarget.mask(o.waitMsg, 'x-mask-loading');
+ }else{
+ Ext.MessageBox.wait(o.waitMsg, o.waitTitle || this.waitTitle);
+ }
+ }
+ },
+
+
+ afterAction : function(action, success){
+ this.activeAction = null;
+ var o = action.options;
+ if(o.waitMsg){
+ if(this.waitMsgTarget === true){
+ this.el.unmask();
+ }else if(this.waitMsgTarget){
+ this.waitMsgTarget.unmask();
+ }else{
+ Ext.MessageBox.updateProgress(1);
+ Ext.MessageBox.hide();
+ }
+ }
+ if(success){
+ if(o.reset){
+ this.reset();
+ }
+ Ext.callback(o.success, o.scope, [this, action]);
+ this.fireEvent('actioncomplete', this, action);
+ }else{
+ Ext.callback(o.failure, o.scope, [this, action]);
+ this.fireEvent('actionfailed', this, action);
+ }
+ },
+
+
+ findField : function(id) {
+ var field = this.items.get(id);
+
+ if (!Ext.isObject(field)) {
+
+ var findMatchingField = function(f) {
+ if (f.isFormField) {
+ if (f.dataIndex == id || f.id == id || f.getName() == id) {
+ field = f;
+ return false;
+ } else if (f.isComposite) {
+ return f.items.each(findMatchingField);
+ } else if (f instanceof Ext.form.CheckboxGroup && f.rendered) {
+ return f.eachItem(findMatchingField);
+ }
+ }
+ };
+
+ this.items.each(findMatchingField);
+ }
+ return field || null;
+ },
+
+
+
+ markInvalid : function(errors){
+ if (Ext.isArray(errors)) {
+ for(var i = 0, len = errors.length; i < len; i++){
+ var fieldError = errors[i];
+ var f = this.findField(fieldError.id);
+ if(f){
+ f.markInvalid(fieldError.msg);
+ }
+ }
+ } else {
+ var field, id;
+ for(id in errors){
+ if(!Ext.isFunction(errors[id]) && (field = this.findField(id))){
+ field.markInvalid(errors[id]);
+ }
+ }
+ }
+
+ return this;
+ },
+
+
+ setValues : function(values){
+ if(Ext.isArray(values)){
+ for(var i = 0, len = values.length; i < len; i++){
+ var v = values[i];
+ var f = this.findField(v.id);
+ if(f){
+ f.setValue(v.value);
+ if(this.trackResetOnLoad){
+ f.originalValue = f.getValue();
+ }
+ }
+ }
+ }else{
+ var field, id;
+ for(id in values){
+ if(!Ext.isFunction(values[id]) && (field = this.findField(id))){
+ field.setValue(values[id]);
+ if(this.trackResetOnLoad){
+ field.originalValue = field.getValue();
+ }
+ }
+ }
+ }
+ return this;
+ },
+
+
+ getValues : function(asString){
+ var fs = Ext.lib.Ajax.serializeForm(this.el.dom);
+ if(asString === true){
+ return fs;
+ }
+ return Ext.urlDecode(fs);
+ },
+
+
+ getFieldValues : function(dirtyOnly){
+ var o = {},
+ n,
+ key,
+ val;
+ this.items.each(function(f) {
+ if (!f.disabled && (dirtyOnly !== true || f.isDirty())) {
+ n = f.getName();
+ key = o[n];
+ val = f.getValue();
+
+ if(Ext.isDefined(key)){
+ if(Ext.isArray(key)){
+ o[n].push(val);
+ }else{
+ o[n] = [key, val];
+ }
+ }else{
+ o[n] = val;
+ }
+ }
+ });
+ return o;
+ },
+
+
+ clearInvalid : function(){
+ this.items.each(function(f){
+ f.clearInvalid();
+ });
+ return this;
+ },
+
+
+ reset : function(){
+ this.items.each(function(f){
+ f.reset();
+ });
+ return this;
+ },
+
+
+ add : function(){
+ this.items.addAll(Array.prototype.slice.call(arguments, 0));
+ return this;
+ },
+
+
+ remove : function(field){
+ this.items.remove(field);
+ return this;
+ },
+
+
+ cleanDestroyed : function() {
+ this.items.filterBy(function(o) { return !!o.isDestroyed; }).each(this.remove, this);
+ },
+
+
+ render : function(){
+ this.items.each(function(f){
+ if(f.isFormField && !f.rendered && document.getElementById(f.id)){
+ f.applyToMarkup(f.id);
+ }
+ });
+ return this;
+ },
+
+
+ applyToFields : function(o){
+ this.items.each(function(f){
+ Ext.apply(f, o);
+ });
+ return this;
+ },
+
+
+ applyIfToFields : function(o){
+ this.items.each(function(f){
+ Ext.applyIf(f, o);
+ });
+ return this;
+ },
+
+ callFieldMethod : function(fnName, args){
+ args = args || [];
+ this.items.each(function(f){
+ if(Ext.isFunction(f[fnName])){
+ f[fnName].apply(f, args);
+ }
+ });
+ return this;
+ }
+});
+
+
+Ext.BasicForm = Ext.form.BasicForm;
+
+Ext.FormPanel = Ext.extend(Ext.Panel, {
+
+
+
+
+
+
+
+
+
+
+ minButtonWidth : 75,
+
+
+ labelAlign : 'left',
+
+
+ monitorValid : false,
+
+
+ monitorPoll : 200,
+
+
+ layout : 'form',
+
+
+ initComponent : function(){
+ this.form = this.createForm();
+ Ext.FormPanel.superclass.initComponent.call(this);
+
+ this.bodyCfg = {
+ tag: 'form',
+ cls: this.baseCls + '-body',
+ method : this.method || 'POST',
+ id : this.formId || Ext.id()
+ };
+ if(this.fileUpload) {
+ this.bodyCfg.enctype = 'multipart/form-data';
+ }
+ this.initItems();
+
+ this.addEvents(
+
+ 'clientvalidation'
+ );
+
+ this.relayEvents(this.form, ['beforeaction', 'actionfailed', 'actioncomplete']);
+ },
+
+
+ createForm : function(){
+ var config = Ext.applyIf({listeners: {}}, this.initialConfig);
+ return new Ext.form.BasicForm(null, config);
+ },
+
+
+ initFields : function(){
+ var f = this.form;
+ var formPanel = this;
+ var fn = function(c){
+ if(formPanel.isField(c)){
+ f.add(c);
+ }else if(c.findBy && c != formPanel){
+ formPanel.applySettings(c);
+
+ if(c.items && c.items.each){
+ c.items.each(fn, this);
+ }
+ }
+ };
+ this.items.each(fn, this);
+ },
+
+
+ applySettings: function(c){
+ var ct = c.ownerCt;
+ Ext.applyIf(c, {
+ labelAlign: ct.labelAlign,
+ labelWidth: ct.labelWidth,
+ itemCls: ct.itemCls
+ });
+ },
+
+
+ getLayoutTarget : function(){
+ return this.form.el;
+ },
+
+
+ getForm : function(){
+ return this.form;
+ },
+
+
+ onRender : function(ct, position){
+ this.initFields();
+ Ext.FormPanel.superclass.onRender.call(this, ct, position);
+ this.form.initEl(this.body);
+ },
+
+
+ beforeDestroy : function(){
+ this.stopMonitoring();
+ this.form.destroy(true);
+ Ext.FormPanel.superclass.beforeDestroy.call(this);
+ },
+
+
+ isField : function(c) {
+ return !!c.setValue && !!c.getValue && !!c.markInvalid && !!c.clearInvalid;
+ },
+
+
+ initEvents : function(){
+ Ext.FormPanel.superclass.initEvents.call(this);
+
+ this.on({
+ scope: this,
+ add: this.onAddEvent,
+ remove: this.onRemoveEvent
+ });
+ if(this.monitorValid){
+ this.startMonitoring();
+ }
+ },
+
+
+ onAdd: function(c){
+ Ext.FormPanel.superclass.onAdd.call(this, c);
+ this.processAdd(c);
+ },
+
+
+ onAddEvent: function(ct, c){
+ if(ct !== this){
+ this.processAdd(c);
+ }
+ },
+
+
+ processAdd : function(c){
+
+ if(this.isField(c)){
+ this.form.add(c);
+
+ }else if(c.findBy){
+ this.applySettings(c);
+ this.form.add.apply(this.form, c.findBy(this.isField));
+ }
+ },
+
+
+ onRemove: function(c){
+ Ext.FormPanel.superclass.onRemove.call(this, c);
+ this.processRemove(c);
+ },
+
+ onRemoveEvent: function(ct, c){
+ if(ct !== this){
+ this.processRemove(c);
+ }
+ },
+
+
+ processRemove: function(c){
+ if(!this.destroying){
+
+ if(this.isField(c)){
+ this.form.remove(c);
+
+ }else if (c.findBy){
+ Ext.each(c.findBy(this.isField), this.form.remove, this.form);
+
+ this.form.cleanDestroyed();
+ }
+ }
+ },
+
+
+ startMonitoring : function(){
+ if(!this.validTask){
+ this.validTask = new Ext.util.TaskRunner();
+ this.validTask.start({
+ run : this.bindHandler,
+ interval : this.monitorPoll || 200,
+ scope: this
+ });
+ }
+ },
+
+
+ stopMonitoring : function(){
+ if(this.validTask){
+ this.validTask.stopAll();
+ this.validTask = null;
+ }
+ },
+
+
+ load : function(){
+ this.form.load.apply(this.form, arguments);
+ },
+
+
+ onDisable : function(){
+ Ext.FormPanel.superclass.onDisable.call(this);
+ if(this.form){
+ this.form.items.each(function(){
+ this.disable();
+ });
+ }
+ },
+
+
+ onEnable : function(){
+ Ext.FormPanel.superclass.onEnable.call(this);
+ if(this.form){
+ this.form.items.each(function(){
+ this.enable();
+ });
+ }
+ },
+
+
+ bindHandler : function(){
+ var valid = true;
+ this.form.items.each(function(f){
+ if(!f.isValid(true)){
+ valid = false;
+ return false;
+ }
+ });
+ if(this.fbar){
+ var fitems = this.fbar.items.items;
+ for(var i = 0, len = fitems.length; i < len; i++){
+ var btn = fitems[i];
+ if(btn.formBind === true && btn.disabled === valid){
+ btn.setDisabled(!valid);
+ }
+ }
+ }
+ this.fireEvent('clientvalidation', this, valid);
+ }
+});
+Ext.reg('form', Ext.FormPanel);
+
+Ext.form.FormPanel = Ext.FormPanel;
+
+Ext.form.FieldSet = Ext.extend(Ext.Panel, {
+
+
+
+
+
+
+ baseCls : 'x-fieldset',
+
+ layout : 'form',
+
+ animCollapse : false,
+
+
+ onRender : function(ct, position){
+ if(!this.el){
+ this.el = document.createElement('fieldset');
+ this.el.id = this.id;
+ if (this.title || this.header || this.checkboxToggle) {
+ this.el.appendChild(document.createElement('legend')).className = this.baseCls + '-header';
+ }
+ }
+
+ Ext.form.FieldSet.superclass.onRender.call(this, ct, position);
+
+ if(this.checkboxToggle){
+ var o = typeof this.checkboxToggle == 'object' ?
+ this.checkboxToggle :
+ {tag: 'input', type: 'checkbox', name: this.checkboxName || this.id+'-checkbox'};
+ this.checkbox = this.header.insertFirst(o);
+ this.checkbox.dom.checked = !this.collapsed;
+ this.mon(this.checkbox, 'click', this.onCheckClick, this);
+ }
+ },
+
+
+ onCollapse : function(doAnim, animArg){
+ if(this.checkbox){
+ this.checkbox.dom.checked = false;
+ }
+ Ext.form.FieldSet.superclass.onCollapse.call(this, doAnim, animArg);
+
+ },
+
+
+ onExpand : function(doAnim, animArg){
+ if(this.checkbox){
+ this.checkbox.dom.checked = true;
+ }
+ Ext.form.FieldSet.superclass.onExpand.call(this, doAnim, animArg);
+ },
+
+
+ onCheckClick : function(){
+ this[this.checkbox.dom.checked ? 'expand' : 'collapse']();
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+});
+Ext.reg('fieldset', Ext.form.FieldSet);
+
+Ext.form.HtmlEditor = Ext.extend(Ext.form.Field, {
+
+ enableFormat : true,
+
+ enableFontSize : true,
+
+ enableColors : true,
+
+ enableAlignments : true,
+
+ enableLists : true,
+
+ enableSourceEdit : true,
+
+ enableLinks : true,
+
+ enableFont : true,
+
+ createLinkText : 'Please enter the URL for the link:',
+
+ defaultLinkValue : 'http:/'+'/',
+
+ fontFamilies : [
+ 'Arial',
+ 'Courier New',
+ 'Tahoma',
+ 'Times New Roman',
+ 'Verdana'
+ ],
+ defaultFont: 'tahoma',
+
+ defaultValue: (Ext.isOpera || Ext.isIE6) ? '&#160;' : '&#8203;',
+
+
+ actionMode: 'wrap',
+ validationEvent : false,
+ deferHeight: true,
+ initialized : false,
+ activated : false,
+ sourceEditMode : false,
+ onFocus : Ext.emptyFn,
+ iframePad:3,
+ hideMode:'offsets',
+ defaultAutoCreate : {
+ tag: "textarea",
+ style:"width:500px;height:300px;",
+ autocomplete: "off"
+ },
+
+
+ initComponent : function(){
+ this.addEvents(
+
+ 'initialize',
+
+ 'activate',
+
+ 'beforesync',
+
+ 'beforepush',
+
+ 'sync',
+
+ 'push',
+
+ 'editmodechange'
+ );
+ Ext.form.HtmlEditor.superclass.initComponent.call(this);
+ },
+
+
+ createFontOptions : function(){
+ var buf = [], fs = this.fontFamilies, ff, lc;
+ for(var i = 0, len = fs.length; i< len; i++){
+ ff = fs[i];
+ lc = ff.toLowerCase();
+ buf.push(
+ '<option value="',lc,'" style="font-family:',ff,';"',
+ (this.defaultFont == lc ? ' selected="true">' : '>'),
+ ff,
+ '</option>'
+ );
+ }
+ return buf.join('');
+ },
+
+
+ createToolbar : function(editor){
+ var items = [];
+ var tipsEnabled = Ext.QuickTips && Ext.QuickTips.isEnabled();
+
+
+ function btn(id, toggle, handler){
+ return {
+ itemId : id,
+ cls : 'x-btn-icon',
+ iconCls: 'x-edit-'+id,
+ enableToggle:toggle !== false,
+ scope: editor,
+ handler:handler||editor.relayBtnCmd,
+ clickEvent:'mousedown',
+ tooltip: tipsEnabled ? editor.buttonTips[id] || undefined : undefined,
+ overflowText: editor.buttonTips[id].title || undefined,
+ tabIndex:-1
+ };
+ }
+
+
+ if(this.enableFont && !Ext.isSafari2){
+ var fontSelectItem = new Ext.Toolbar.Item({
+ autoEl: {
+ tag:'select',
+ cls:'x-font-select',
+ html: this.createFontOptions()
+ }
+ });
+
+ items.push(
+ fontSelectItem,
+ '-'
+ );
+ }
+
+ if(this.enableFormat){
+ items.push(
+ btn('bold'),
+ btn('italic'),
+ btn('underline')
+ );
+ }
+
+ if(this.enableFontSize){
+ items.push(
+ '-',
+ btn('increasefontsize', false, this.adjustFont),
+ btn('decreasefontsize', false, this.adjustFont)
+ );
+ }
+
+ if(this.enableColors){
+ items.push(
+ '-', {
+ itemId:'forecolor',
+ cls:'x-btn-icon',
+ iconCls: 'x-edit-forecolor',
+ clickEvent:'mousedown',
+ tooltip: tipsEnabled ? editor.buttonTips.forecolor || undefined : undefined,
+ tabIndex:-1,
+ menu : new Ext.menu.ColorMenu({
+ allowReselect: true,
+ focus: Ext.emptyFn,
+ value:'000000',
+ plain:true,
+ listeners: {
+ scope: this,
+ select: function(cp, color){
+ this.execCmd('forecolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
+ this.deferFocus();
+ }
+ },
+ clickEvent:'mousedown'
+ })
+ }, {
+ itemId:'backcolor',
+ cls:'x-btn-icon',
+ iconCls: 'x-edit-backcolor',
+ clickEvent:'mousedown',
+ tooltip: tipsEnabled ? editor.buttonTips.backcolor || undefined : undefined,
+ tabIndex:-1,
+ menu : new Ext.menu.ColorMenu({
+ focus: Ext.emptyFn,
+ value:'FFFFFF',
+ plain:true,
+ allowReselect: true,
+ listeners: {
+ scope: this,
+ select: function(cp, color){
+ if(Ext.isGecko){
+ this.execCmd('useCSS', false);
+ this.execCmd('hilitecolor', color);
+ this.execCmd('useCSS', true);
+ this.deferFocus();
+ }else{
+ this.execCmd(Ext.isOpera ? 'hilitecolor' : 'backcolor', Ext.isWebKit || Ext.isIE ? '#'+color : color);
+ this.deferFocus();
+ }
+ }
+ },
+ clickEvent:'mousedown'
+ })
+ }
+ );
+ }
+
+ if(this.enableAlignments){
+ items.push(
+ '-',
+ btn('justifyleft'),
+ btn('justifycenter'),
+ btn('justifyright')
+ );
+ }
+
+ if(!Ext.isSafari2){
+ if(this.enableLinks){
+ items.push(
+ '-',
+ btn('createlink', false, this.createLink)
+ );
+ }
+
+ if(this.enableLists){
+ items.push(
+ '-',
+ btn('insertorderedlist'),
+ btn('insertunorderedlist')
+ );
+ }
+ if(this.enableSourceEdit){
+ items.push(
+ '-',
+ btn('sourceedit', true, function(btn){
+ this.toggleSourceEdit(!this.sourceEditMode);
+ })
+ );
+ }
+ }
+
+
+ var tb = new Ext.Toolbar({
+ renderTo: this.wrap.dom.firstChild,
+ items: items
+ });
+
+ if (fontSelectItem) {
+ this.fontSelect = fontSelectItem.el;
+
+ this.mon(this.fontSelect, 'change', function(){
+ var font = this.fontSelect.dom.value;
+ this.relayCmd('fontname', font);
+ this.deferFocus();
+ }, this);
+ }
+
+
+ this.mon(tb.el, 'click', function(e){
+ e.preventDefault();
+ });
+
+ this.tb = tb;
+ this.tb.doLayout();
+ },
+
+ onDisable: function(){
+ this.wrap.mask();
+ Ext.form.HtmlEditor.superclass.onDisable.call(this);
+ },
+
+ onEnable: function(){
+ this.wrap.unmask();
+ Ext.form.HtmlEditor.superclass.onEnable.call(this);
+ },
+
+ setReadOnly: function(readOnly){
+
+ Ext.form.HtmlEditor.superclass.setReadOnly.call(this, readOnly);
+ if(this.initialized){
+ if(Ext.isIE){
+ this.getEditorBody().contentEditable = !readOnly;
+ }else{
+ this.setDesignMode(!readOnly);
+ }
+ var bd = this.getEditorBody();
+ if(bd){
+ bd.style.cursor = this.readOnly ? 'default' : 'text';
+ }
+ this.disableItems(readOnly);
+ }
+ },
+
+
+ getDocMarkup : function(){
+ var h = Ext.fly(this.iframe).getHeight() - this.iframePad * 2;
+ return String.format('<html><head><style type="text/css">body{border: 0; margin: 0; padding: {0}px; height: {1}px; cursor: text}</style></head><body></body></html>', this.iframePad, h);
+ },
+
+
+ getEditorBody : function(){
+ var doc = this.getDoc();
+ return doc.body || doc.documentElement;
+ },
+
+
+ getDoc : function(){
+ return Ext.isIE ? this.getWin().document : (this.iframe.contentDocument || this.getWin().document);
+ },
+
+
+ getWin : function(){
+ return Ext.isIE ? this.iframe.contentWindow : window.frames[this.iframe.name];
+ },
+
+
+ onRender : function(ct, position){
+ Ext.form.HtmlEditor.superclass.onRender.call(this, ct, position);
+ this.el.dom.style.border = '0 none';
+ this.el.dom.setAttribute('tabIndex', -1);
+ this.el.addClass('x-hidden');
+ if(Ext.isIE){
+ this.el.applyStyles('margin-top:-1px;margin-bottom:-1px;');
+ }
+ this.wrap = this.el.wrap({
+ cls:'x-html-editor-wrap', cn:{cls:'x-html-editor-tb'}
+ });
+
+ this.createToolbar(this);
+
+ this.disableItems(true);
+
+ this.tb.doLayout();
+
+ this.createIFrame();
+
+ if(!this.width){
+ var sz = this.el.getSize();
+ this.setSize(sz.width, this.height || sz.height);
+ }
+ this.resizeEl = this.positionEl = this.wrap;
+ },
+
+ createIFrame: function(){
+ var iframe = document.createElement('iframe');
+ iframe.name = Ext.id();
+ iframe.frameBorder = '0';
+ iframe.style.overflow = 'auto';
+ iframe.src = Ext.SSL_SECURE_URL;
+
+ this.wrap.dom.appendChild(iframe);
+ this.iframe = iframe;
+
+ this.monitorTask = Ext.TaskMgr.start({
+ run: this.checkDesignMode,
+ scope: this,
+ interval:100
+ });
+ },
+
+ initFrame : function(){
+ Ext.TaskMgr.stop(this.monitorTask);
+ var doc = this.getDoc();
+ this.win = this.getWin();
+
+ doc.open();
+ doc.write(this.getDocMarkup());
+ doc.close();
+
+ this.readyTask = {
+ run : function(){
+ var doc = this.getDoc();
+ if(doc.body || doc.readyState == 'complete'){
+ Ext.TaskMgr.stop(this.readyTask);
+ this.setDesignMode(true);
+ this.initEditor.defer(10, this);
+ }
+ },
+ interval : 10,
+ duration:10000,
+ scope: this
+ };
+ Ext.TaskMgr.start(this.readyTask);
+ },
+
+
+ checkDesignMode : function(){
+ if(this.wrap && this.wrap.dom.offsetWidth){
+ var doc = this.getDoc();
+ if(!doc){
+ return;
+ }
+ if(!doc.editorInitialized || this.getDesignMode() != 'on'){
+ this.initFrame();
+ }
+ }
+ },
+
+
+ setDesignMode : function(mode){
+ var doc = this.getDoc();
+ if (doc) {
+ if(this.readOnly){
+ mode = false;
+ }
+ doc.designMode = (/on|true/i).test(String(mode).toLowerCase()) ?'on':'off';
+ }
+
+ },
+
+
+ getDesignMode : function(){
+ var doc = this.getDoc();
+ if(!doc){ return ''; }
+ return String(doc.designMode).toLowerCase();
+
+ },
+
+ disableItems: function(disabled){
+ if(this.fontSelect){
+ this.fontSelect.dom.disabled = disabled;
+ }
+ this.tb.items.each(function(item){
+ if(item.getItemId() != 'sourceedit'){
+ item.setDisabled(disabled);
+ }
+ });
+ },
+
+
+ onResize : function(w, h){
+ Ext.form.HtmlEditor.superclass.onResize.apply(this, arguments);
+ if(this.el && this.iframe){
+ if(Ext.isNumber(w)){
+ var aw = w - this.wrap.getFrameWidth('lr');
+ this.el.setWidth(aw);
+ this.tb.setWidth(aw);
+ this.iframe.style.width = Math.max(aw, 0) + 'px';
+ }
+ if(Ext.isNumber(h)){
+ var ah = h - this.wrap.getFrameWidth('tb') - this.tb.el.getHeight();
+ this.el.setHeight(ah);
+ this.iframe.style.height = Math.max(ah, 0) + 'px';
+ var bd = this.getEditorBody();
+ if(bd){
+ bd.style.height = Math.max((ah - (this.iframePad*2)), 0) + 'px';
+ }
+ }
+ }
+ },
+
+
+ toggleSourceEdit : function(sourceEditMode){
+ var iframeHeight,
+ elHeight;
+
+ if (sourceEditMode === undefined) {
+ sourceEditMode = !this.sourceEditMode;
+ }
+ this.sourceEditMode = sourceEditMode === true;
+ var btn = this.tb.getComponent('sourceedit');
+
+ if (btn.pressed !== this.sourceEditMode) {
+ btn.toggle(this.sourceEditMode);
+ if (!btn.xtbHidden) {
+ return;
+ }
+ }
+ if (this.sourceEditMode) {
+
+ this.previousSize = this.getSize();
+
+ iframeHeight = Ext.get(this.iframe).getHeight();
+
+ this.disableItems(true);
+ this.syncValue();
+ this.iframe.className = 'x-hidden';
+ this.el.removeClass('x-hidden');
+ this.el.dom.removeAttribute('tabIndex');
+ this.el.focus();
+ this.el.dom.style.height = iframeHeight + 'px';
+ }
+ else {
+ elHeight = parseInt(this.el.dom.style.height, 10);
+ if (this.initialized) {
+ this.disableItems(this.readOnly);
+ }
+ this.pushValue();
+ this.iframe.className = '';
+ this.el.addClass('x-hidden');
+ this.el.dom.setAttribute('tabIndex', -1);
+ this.deferFocus();
+
+ this.setSize(this.previousSize);
+ delete this.previousSize;
+ this.iframe.style.height = elHeight + 'px';
+ }
+ this.fireEvent('editmodechange', this, this.sourceEditMode);
+ },
+
+
+ createLink : function() {
+ var url = prompt(this.createLinkText, this.defaultLinkValue);
+ if(url && url != 'http:/'+'/'){
+ this.relayCmd('createlink', url);
+ }
+ },
+
+
+ initEvents : function(){
+ this.originalValue = this.getValue();
+ },
+
+
+ markInvalid : Ext.emptyFn,
+
+
+ clearInvalid : Ext.emptyFn,
+
+
+ setValue : function(v){
+ Ext.form.HtmlEditor.superclass.setValue.call(this, v);
+ this.pushValue();
+ return this;
+ },
+
+
+ cleanHtml: function(html) {
+ html = String(html);
+ if(Ext.isWebKit){
+ html = html.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi, '');
+ }
+
+
+ if(html.charCodeAt(0) == this.defaultValue.replace(/\D/g, '')){
+ html = html.substring(1);
+ }
+ return html;
+ },
+
+
+ syncValue : function(){
+ if(this.initialized){
+ var bd = this.getEditorBody();
+ var html = bd.innerHTML;
+ if(Ext.isWebKit){
+ var bs = bd.getAttribute('style');
+ var m = bs.match(/text-align:(.*?);/i);
+ if(m && m[1]){
+ html = '<div style="'+m[0]+'">' + html + '</div>';
+ }
+ }
+ html = this.cleanHtml(html);
+ if(this.fireEvent('beforesync', this, html) !== false){
+ this.el.dom.value = html;
+ this.fireEvent('sync', this, html);
+ }
+ }
+ },
+
+
+ getValue : function() {
+ this[this.sourceEditMode ? 'pushValue' : 'syncValue']();
+ return Ext.form.HtmlEditor.superclass.getValue.call(this);
+ },
+
+
+ pushValue : function(){
+ if(this.initialized){
+ var v = this.el.dom.value;
+ if(!this.activated && v.length < 1){
+ v = this.defaultValue;
+ }
+ if(this.fireEvent('beforepush', this, v) !== false){
+ this.getEditorBody().innerHTML = v;
+ if(Ext.isGecko){
+
+ this.setDesignMode(false);
+ this.setDesignMode(true);
+ }
+ this.fireEvent('push', this, v);
+ }
+
+ }
+ },
+
+
+ deferFocus : function(){
+ this.focus.defer(10, this);
+ },
+
+
+ focus : function(){
+ if(this.win && !this.sourceEditMode){
+ this.win.focus();
+ }else{
+ this.el.focus();
+ }
+ },
+
+
+ initEditor : function(){
+
+ try{
+ var dbody = this.getEditorBody(),
+ ss = this.el.getStyles('font-size', 'font-family', 'background-image', 'background-repeat', 'background-color', 'color'),
+ doc,
+ fn;
+
+ ss['background-attachment'] = 'fixed';
+ dbody.bgProperties = 'fixed';
+
+ Ext.DomHelper.applyStyles(dbody, ss);
+
+ doc = this.getDoc();
+
+ if(doc){
+ try{
+ Ext.EventManager.removeAll(doc);
+ }catch(e){}
+ }
+
+
+ fn = this.onEditorEvent.createDelegate(this);
+ Ext.EventManager.on(doc, {
+ mousedown: fn,
+ dblclick: fn,
+ click: fn,
+ keyup: fn,
+ buffer:100
+ });
+
+ if(Ext.isGecko){
+ Ext.EventManager.on(doc, 'keypress', this.applyCommand, this);
+ }
+ if(Ext.isIE || Ext.isWebKit || Ext.isOpera){
+ Ext.EventManager.on(doc, 'keydown', this.fixKeys, this);
+ }
+ doc.editorInitialized = true;
+ this.initialized = true;
+ this.pushValue();
+ this.setReadOnly(this.readOnly);
+ this.fireEvent('initialize', this);
+ }catch(e){}
+ },
+
+
+ beforeDestroy : function(){
+ if(this.monitorTask){
+ Ext.TaskMgr.stop(this.monitorTask);
+ }
+ if(this.readyTask){
+ Ext.TaskMgr.stop(this.readyTask);
+ }
+ if(this.rendered){
+ Ext.destroy(this.tb);
+ var doc = this.getDoc();
+ Ext.EventManager.removeFromSpecialCache(doc);
+ if(doc){
+ try{
+ Ext.EventManager.removeAll(doc);
+ for (var prop in doc){
+ delete doc[prop];
+ }
+ }catch(e){}
+ }
+ if(this.wrap){
+ this.wrap.dom.innerHTML = '';
+ this.wrap.remove();
+ }
+ }
+ Ext.form.HtmlEditor.superclass.beforeDestroy.call(this);
+ },
+
+
+ onFirstFocus : function(){
+ this.activated = true;
+ this.disableItems(this.readOnly);
+ if(Ext.isGecko){
+ this.win.focus();
+ var s = this.win.getSelection();
+ if(!s.focusNode || s.focusNode.nodeType != 3){
+ var r = s.getRangeAt(0);
+ r.selectNodeContents(this.getEditorBody());
+ r.collapse(true);
+ this.deferFocus();
+ }
+ try{
+ this.execCmd('useCSS', true);
+ this.execCmd('styleWithCSS', false);
+ }catch(e){}
+ }
+ this.fireEvent('activate', this);
+ },
+
+
+ adjustFont: function(btn){
+ var adjust = btn.getItemId() == 'increasefontsize' ? 1 : -1,
+ doc = this.getDoc(),
+ v = parseInt(doc.queryCommandValue('FontSize') || 2, 10);
+ if((Ext.isSafari && !Ext.isSafari2) || Ext.isChrome || Ext.isAir){
+
+
+ if(v <= 10){
+ v = 1 + adjust;
+ }else if(v <= 13){
+ v = 2 + adjust;
+ }else if(v <= 16){
+ v = 3 + adjust;
+ }else if(v <= 18){
+ v = 4 + adjust;
+ }else if(v <= 24){
+ v = 5 + adjust;
+ }else {
+ v = 6 + adjust;
+ }
+ v = v.constrain(1, 6);
+ }else{
+ if(Ext.isSafari){
+ adjust *= 2;
+ }
+ v = Math.max(1, v+adjust) + (Ext.isSafari ? 'px' : 0);
+ }
+ this.execCmd('FontSize', v);
+ },
+
+
+ onEditorEvent : function(e){
+ this.updateToolbar();
+ },
+
+
+
+ updateToolbar: function(){
+
+ if(this.readOnly){
+ return;
+ }
+
+ if(!this.activated){
+ this.onFirstFocus();
+ return;
+ }
+
+ var btns = this.tb.items.map,
+ doc = this.getDoc();
+
+ if(this.enableFont && !Ext.isSafari2){
+ var name = (doc.queryCommandValue('FontName')||this.defaultFont).toLowerCase();
+ if(name != this.fontSelect.dom.value){
+ this.fontSelect.dom.value = name;
+ }
+ }
+ if(this.enableFormat){
+ btns.bold.toggle(doc.queryCommandState('bold'));
+ btns.italic.toggle(doc.queryCommandState('italic'));
+ btns.underline.toggle(doc.queryCommandState('underline'));
+ }
+ if(this.enableAlignments){
+ btns.justifyleft.toggle(doc.queryCommandState('justifyleft'));
+ btns.justifycenter.toggle(doc.queryCommandState('justifycenter'));
+ btns.justifyright.toggle(doc.queryCommandState('justifyright'));
+ }
+ if(!Ext.isSafari2 && this.enableLists){
+ btns.insertorderedlist.toggle(doc.queryCommandState('insertorderedlist'));
+ btns.insertunorderedlist.toggle(doc.queryCommandState('insertunorderedlist'));
+ }
+
+ Ext.menu.MenuMgr.hideAll();
+
+ this.syncValue();
+ },
+
+
+ relayBtnCmd : function(btn){
+ this.relayCmd(btn.getItemId());
+ },
+
+
+ relayCmd : function(cmd, value){
+ (function(){
+ this.focus();
+ this.execCmd(cmd, value);
+ this.updateToolbar();
+ }).defer(10, this);
+ },
+
+
+ execCmd : function(cmd, value){
+ var doc = this.getDoc();
+ doc.execCommand(cmd, false, value === undefined ? null : value);
+ this.syncValue();
+ },
+
+
+ applyCommand : function(e){
+ if(e.ctrlKey){
+ var c = e.getCharCode(), cmd;
+ if(c > 0){
+ c = String.fromCharCode(c);
+ switch(c){
+ case 'b':
+ cmd = 'bold';
+ break;
+ case 'i':
+ cmd = 'italic';
+ break;
+ case 'u':
+ cmd = 'underline';
+ break;
+ }
+ if(cmd){
+ this.win.focus();
+ this.execCmd(cmd);
+ this.deferFocus();
+ e.preventDefault();
+ }
+ }
+ }
+ },
+
+
+ insertAtCursor : function(text){
+ if(!this.activated){
+ return;
+ }
+ if(Ext.isIE){
+ this.win.focus();
+ var doc = this.getDoc(),
+ r = doc.selection.createRange();
+ if(r){
+ r.pasteHTML(text);
+ this.syncValue();
+ this.deferFocus();
+ }
+ }else{
+ this.win.focus();
+ this.execCmd('InsertHTML', text);
+ this.deferFocus();
+ }
+ },
+
+
+ fixKeys : function(){
+ if(Ext.isIE){
+ return function(e){
+ var k = e.getKey(),
+ doc = this.getDoc(),
+ r;
+ if(k == e.TAB){
+ e.stopEvent();
+ r = doc.selection.createRange();
+ if(r){
+ r.collapse(true);
+ r.pasteHTML('&nbsp;&nbsp;&nbsp;&nbsp;');
+ this.deferFocus();
+ }
+ }else if(k == e.ENTER){
+ r = doc.selection.createRange();
+ if(r){
+ var target = r.parentElement();
+ if(!target || target.tagName.toLowerCase() != 'li'){
+ e.stopEvent();
+ r.pasteHTML('<br />');
+ r.collapse(false);
+ r.select();
+ }
+ }
+ }
+ };
+ }else if(Ext.isOpera){
+ return function(e){
+ var k = e.getKey();
+ if(k == e.TAB){
+ e.stopEvent();
+ this.win.focus();
+ this.execCmd('InsertHTML','&nbsp;&nbsp;&nbsp;&nbsp;');
+ this.deferFocus();
+ }
+ };
+ }else if(Ext.isWebKit){
+ return function(e){
+ var k = e.getKey();
+ if(k == e.TAB){
+ e.stopEvent();
+ this.execCmd('InsertText','\t');
+ this.deferFocus();
+ }else if(k == e.ENTER){
+ e.stopEvent();
+ this.execCmd('InsertHtml','<br /><br />');
+ this.deferFocus();
+ }
+ };
+ }
+ }(),
+
+
+ getToolbar : function(){
+ return this.tb;
+ },
+
+
+ buttonTips : {
+ bold : {
+ title: 'Bold (Ctrl+B)',
+ text: 'Make the selected text bold.',
+ cls: 'x-html-editor-tip'
+ },
+ italic : {
+ title: 'Italic (Ctrl+I)',
+ text: 'Make the selected text italic.',
+ cls: 'x-html-editor-tip'
+ },
+ underline : {
+ title: 'Underline (Ctrl+U)',
+ text: 'Underline the selected text.',
+ cls: 'x-html-editor-tip'
+ },
+ increasefontsize : {
+ title: 'Grow Text',
+ text: 'Increase the font size.',
+ cls: 'x-html-editor-tip'
+ },
+ decreasefontsize : {
+ title: 'Shrink Text',
+ text: 'Decrease the font size.',
+ cls: 'x-html-editor-tip'
+ },
+ backcolor : {
+ title: 'Text Highlight Color',
+ text: 'Change the background color of the selected text.',
+ cls: 'x-html-editor-tip'
+ },
+ forecolor : {
+ title: 'Font Color',
+ text: 'Change the color of the selected text.',
+ cls: 'x-html-editor-tip'
+ },
+ justifyleft : {
+ title: 'Align Text Left',
+ text: 'Align text to the left.',
+ cls: 'x-html-editor-tip'
+ },
+ justifycenter : {
+ title: 'Center Text',
+ text: 'Center text in the editor.',
+ cls: 'x-html-editor-tip'
+ },
+ justifyright : {
+ title: 'Align Text Right',
+ text: 'Align text to the right.',
+ cls: 'x-html-editor-tip'
+ },
+ insertunorderedlist : {
+ title: 'Bullet List',
+ text: 'Start a bulleted list.',
+ cls: 'x-html-editor-tip'
+ },
+ insertorderedlist : {
+ title: 'Numbered List',
+ text: 'Start a numbered list.',
+ cls: 'x-html-editor-tip'
+ },
+ createlink : {
+ title: 'Hyperlink',
+ text: 'Make the selected text a hyperlink.',
+ cls: 'x-html-editor-tip'
+ },
+ sourceedit : {
+ title: 'Source Edit',
+ text: 'Switch to source editing mode.',
+ cls: 'x-html-editor-tip'
+ }
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+});
+Ext.reg('htmleditor', Ext.form.HtmlEditor);
+
+Ext.form.TimeField = Ext.extend(Ext.form.ComboBox, {
+
+ minValue : undefined,
+
+ maxValue : undefined,
+
+ minText : "The time in this field must be equal to or after {0}",
+
+ maxText : "The time in this field must be equal to or before {0}",
+
+ invalidText : "{0} is not a valid time",
+
+ format : "g:i A",
+
+ altFormats : "g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H|gi a|hi a|giA|hiA|gi A|hi A",
+
+ increment: 15,
+
+
+ mode: 'local',
+
+ triggerAction: 'all',
+
+ typeAhead: false,
+
+
+
+
+ initDate: '1/1/2008',
+
+ initDateFormat: 'j/n/Y',
+
+
+ initComponent : function(){
+ if(Ext.isDefined(this.minValue)){
+ this.setMinValue(this.minValue, true);
+ }
+ if(Ext.isDefined(this.maxValue)){
+ this.setMaxValue(this.maxValue, true);
+ }
+ if(!this.store){
+ this.generateStore(true);
+ }
+ Ext.form.TimeField.superclass.initComponent.call(this);
+ },
+
+
+ setMinValue: function(value, initial){
+ this.setLimit(value, true, initial);
+ return this;
+ },
+
+
+ setMaxValue: function(value, initial){
+ this.setLimit(value, false, initial);
+ return this;
+ },
+
+
+ generateStore: function(initial){
+ var min = this.minValue || new Date(this.initDate).clearTime(),
+ max = this.maxValue || new Date(this.initDate).clearTime().add('mi', (24 * 60) - 1),
+ times = [];
+
+ while(min <= max){
+ times.push(min.dateFormat(this.format));
+ min = min.add('mi', this.increment);
+ }
+ this.bindStore(times, initial);
+ },
+
+
+ setLimit: function(value, isMin, initial){
+ var d;
+ if(Ext.isString(value)){
+ d = this.parseDate(value);
+ }else if(Ext.isDate(value)){
+ d = value;
+ }
+ if(d){
+ var val = new Date(this.initDate).clearTime();
+ val.setHours(d.getHours(), d.getMinutes(), d.getSeconds(), d.getMilliseconds());
+ this[isMin ? 'minValue' : 'maxValue'] = val;
+ if(!initial){
+ this.generateStore();
+ }
+ }
+ },
+
+
+ getValue : function(){
+ var v = Ext.form.TimeField.superclass.getValue.call(this);
+ return this.formatDate(this.parseDate(v)) || '';
+ },
+
+
+ setValue : function(value){
+ return Ext.form.TimeField.superclass.setValue.call(this, this.formatDate(this.parseDate(value)));
+ },
+
+
+ validateValue : Ext.form.DateField.prototype.validateValue,
+
+ formatDate : Ext.form.DateField.prototype.formatDate,
+
+ parseDate: function(value) {
+ if (!value || Ext.isDate(value)) {
+ return value;
+ }
+
+ var id = this.initDate + ' ',
+ idf = this.initDateFormat + ' ',
+ v = Date.parseDate(id + value, idf + this.format),
+ af = this.altFormats;
+
+ if (!v && af) {
+ if (!this.altFormatsArray) {
+ this.altFormatsArray = af.split("|");
+ }
+ for (var i = 0, afa = this.altFormatsArray, len = afa.length; i < len && !v; i++) {
+ v = Date.parseDate(id + value, idf + afa[i]);
+ }
+ }
+
+ return v;
+ }
+});
+Ext.reg('timefield', Ext.form.TimeField);
+Ext.form.SliderField = Ext.extend(Ext.form.Field, {
+
+
+ useTips : true,
+
+
+ tipText : null,
+
+
+ actionMode: 'wrap',
+
+
+ initComponent : function() {
+ var cfg = Ext.copyTo({
+ id: this.id + '-slider'
+ }, this.initialConfig, ['vertical', 'minValue', 'maxValue', 'decimalPrecision', 'keyIncrement', 'increment', 'clickToChange', 'animate']);
+
+
+ if (this.useTips) {
+ var plug = this.tipText ? {getText: this.tipText} : {};
+ cfg.plugins = [new Ext.slider.Tip(plug)];
+ }
+ this.slider = new Ext.Slider(cfg);
+ Ext.form.SliderField.superclass.initComponent.call(this);
+ },
+
+
+ onRender : function(ct, position){
+ this.autoCreate = {
+ id: this.id,
+ name: this.name,
+ type: 'hidden',
+ tag: 'input'
+ };
+ Ext.form.SliderField.superclass.onRender.call(this, ct, position);
+ this.wrap = this.el.wrap({cls: 'x-form-field-wrap'});
+ this.resizeEl = this.positionEl = this.wrap;
+ this.slider.render(this.wrap);
+ },
+
+
+ onResize : function(w, h, aw, ah){
+ Ext.form.SliderField.superclass.onResize.call(this, w, h, aw, ah);
+ this.slider.setSize(w, h);
+ },
+
+
+ initEvents : function(){
+ Ext.form.SliderField.superclass.initEvents.call(this);
+ this.slider.on('change', this.onChange, this);
+ },
+
+
+ onChange : function(slider, v){
+ this.setValue(v, undefined, true);
+ },
+
+
+ onEnable : function(){
+ Ext.form.SliderField.superclass.onEnable.call(this);
+ this.slider.enable();
+ },
+
+
+ onDisable : function(){
+ Ext.form.SliderField.superclass.onDisable.call(this);
+ this.slider.disable();
+ },
+
+
+ beforeDestroy : function(){
+ Ext.destroy(this.slider);
+ Ext.form.SliderField.superclass.beforeDestroy.call(this);
+ },
+
+
+ alignErrorIcon : function(){
+ this.errorIcon.alignTo(this.slider.el, 'tl-tr', [2, 0]);
+ },
+
+
+ setMinValue : function(v){
+ this.slider.setMinValue(v);
+ return this;
+ },
+
+
+ setMaxValue : function(v){
+ this.slider.setMaxValue(v);
+ return this;
+ },
+
+
+ setValue : function(v, animate, silent){
+
+
+ if(!silent){
+ this.slider.setValue(v, animate);
+ }
+ return Ext.form.SliderField.superclass.setValue.call(this, this.slider.getValue());
+ },
+
+
+ getValue : function(){
+ return this.slider.getValue();
+ }
+});
+
+Ext.reg('sliderfield', Ext.form.SliderField);
+Ext.form.Label = Ext.extend(Ext.BoxComponent, {
+
+
+
+
+
+ onRender : function(ct, position){
+ if(!this.el){
+ this.el = document.createElement('label');
+ this.el.id = this.getId();
+ this.el.innerHTML = this.text ? Ext.util.Format.htmlEncode(this.text) : (this.html || '');
+ if(this.forId){
+ this.el.setAttribute('for', this.forId);
+ }
+ }
+ Ext.form.Label.superclass.onRender.call(this, ct, position);
+ },
+
+
+ setText : function(t, encode){
+ var e = encode === false;
+ this[!e ? 'text' : 'html'] = t;
+ delete this[e ? 'text' : 'html'];
+ if(this.rendered){
+ this.el.dom.innerHTML = encode !== false ? Ext.util.Format.htmlEncode(t) : t;
+ }
+ return this;
+ }
+});
+
+Ext.reg('label', Ext.form.Label);
+Ext.form.Action = function(form, options){
+ this.form = form;
+ this.options = options || {};
+};
+
+
+Ext.form.Action.CLIENT_INVALID = 'client';
+
+Ext.form.Action.SERVER_INVALID = 'server';
+
+Ext.form.Action.CONNECT_FAILURE = 'connect';
+
+Ext.form.Action.LOAD_FAILURE = 'load';
+
+Ext.form.Action.prototype = {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ type : 'default',
+
+
+
+
+
+ run : function(options){
+
+ },
+
+
+ success : function(response){
+
+ },
+
+
+ handleResponse : function(response){
+
+ },
+
+
+ failure : function(response){
+ this.response = response;
+ this.failureType = Ext.form.Action.CONNECT_FAILURE;
+ this.form.afterAction(this, false);
+ },
+
+
+
+
+ processResponse : function(response){
+ this.response = response;
+ if(!response.responseText && !response.responseXML){
+ return true;
+ }
+ this.result = this.handleResponse(response);
+ return this.result;
+ },
+
+ decodeResponse: function(response) {
+ try {
+ return Ext.decode(response.responseText);
+ } catch(e) {
+ return false;
+ }
+ },
+
+
+ getUrl : function(appendParams){
+ var url = this.options.url || this.form.url || this.form.el.dom.action;
+ if(appendParams){
+ var p = this.getParams();
+ if(p){
+ url = Ext.urlAppend(url, p);
+ }
+ }
+ return url;
+ },
+
+
+ getMethod : function(){
+ return (this.options.method || this.form.method || this.form.el.dom.method || 'POST').toUpperCase();
+ },
+
+
+ getParams : function(){
+ var bp = this.form.baseParams;
+ var p = this.options.params;
+ if(p){
+ if(typeof p == "object"){
+ p = Ext.urlEncode(Ext.applyIf(p, bp));
+ }else if(typeof p == 'string' && bp){
+ p += '&' + Ext.urlEncode(bp);
+ }
+ }else if(bp){
+ p = Ext.urlEncode(bp);
+ }
+ return p;
+ },
+
+
+ createCallback : function(opts){
+ var opts = opts || {};
+ return {
+ success: this.success,
+ failure: this.failure,
+ scope: this,
+ timeout: (opts.timeout*1000) || (this.form.timeout*1000),
+ upload: this.form.fileUpload ? this.success : undefined
+ };
+ }
+};
+
+
+Ext.form.Action.Submit = function(form, options){
+ Ext.form.Action.Submit.superclass.constructor.call(this, form, options);
+};
+
+Ext.extend(Ext.form.Action.Submit, Ext.form.Action, {
+
+
+ type : 'submit',
+
+
+ run : function(){
+ var o = this.options,
+ method = this.getMethod(),
+ isGet = method == 'GET';
+ if(o.clientValidation === false || this.form.isValid()){
+ if (o.submitEmptyText === false) {
+ var fields = this.form.items,
+ emptyFields = [],
+ setupEmptyFields = function(f){
+ if (f.el.getValue() == f.emptyText) {
+ emptyFields.push(f);
+ f.el.dom.value = "";
+ }
+ if(f.isComposite && f.rendered){
+ f.items.each(setupEmptyFields);
+ }
+ };
+
+ fields.each(setupEmptyFields);
+ }
+ Ext.Ajax.request(Ext.apply(this.createCallback(o), {
+ form:this.form.el.dom,
+ url:this.getUrl(isGet),
+ method: method,
+ headers: o.headers,
+ params:!isGet ? this.getParams() : null,
+ isUpload: this.form.fileUpload
+ }));
+ if (o.submitEmptyText === false) {
+ Ext.each(emptyFields, function(f) {
+ if (f.applyEmptyText) {
+ f.applyEmptyText();
+ }
+ });
+ }
+ }else if (o.clientValidation !== false){
+ this.failureType = Ext.form.Action.CLIENT_INVALID;
+ this.form.afterAction(this, false);
+ }
+ },
+
+
+ success : function(response){
+ var result = this.processResponse(response);
+ if(result === true || result.success){
+ this.form.afterAction(this, true);
+ return;
+ }
+ if(result.errors){
+ this.form.markInvalid(result.errors);
+ }
+ this.failureType = Ext.form.Action.SERVER_INVALID;
+ this.form.afterAction(this, false);
+ },
+
+
+ handleResponse : function(response){
+ if(this.form.errorReader){
+ var rs = this.form.errorReader.read(response);
+ var errors = [];
+ if(rs.records){
+ for(var i = 0, len = rs.records.length; i < len; i++) {
+ var r = rs.records[i];
+ errors[i] = r.data;
+ }
+ }
+ if(errors.length < 1){
+ errors = null;
+ }
+ return {
+ success : rs.success,
+ errors : errors
+ };
+ }
+ return this.decodeResponse(response);
+ }
+});
+
+
+
+Ext.form.Action.Load = function(form, options){
+ Ext.form.Action.Load.superclass.constructor.call(this, form, options);
+ this.reader = this.form.reader;
+};
+
+Ext.extend(Ext.form.Action.Load, Ext.form.Action, {
+
+ type : 'load',
+
+
+ run : function(){
+ Ext.Ajax.request(Ext.apply(
+ this.createCallback(this.options), {
+ method:this.getMethod(),
+ url:this.getUrl(false),
+ headers: this.options.headers,
+ params:this.getParams()
+ }));
+ },
+
+
+ success : function(response){
+ var result = this.processResponse(response);
+ if(result === true || !result.success || !result.data){
+ this.failureType = Ext.form.Action.LOAD_FAILURE;
+ this.form.afterAction(this, false);
+ return;
+ }
+ this.form.clearInvalid();
+ this.form.setValues(result.data);
+ this.form.afterAction(this, true);
+ },
+
+
+ handleResponse : function(response){
+ if(this.form.reader){
+ var rs = this.form.reader.read(response);
+ var data = rs.records && rs.records[0] ? rs.records[0].data : null;
+ return {
+ success : rs.success,
+ data : data
+ };
+ }
+ return this.decodeResponse(response);
+ }
+});
+
+
+
+
+Ext.form.Action.DirectLoad = Ext.extend(Ext.form.Action.Load, {
+ constructor: function(form, opts) {
+ Ext.form.Action.DirectLoad.superclass.constructor.call(this, form, opts);
+ },
+ type : 'directload',
+
+ run : function(){
+ var args = this.getParams();
+ args.push(this.success, this);
+ this.form.api.load.apply(window, args);
+ },
+
+ getParams : function() {
+ var buf = [], o = {};
+ var bp = this.form.baseParams;
+ var p = this.options.params;
+ Ext.apply(o, p, bp);
+ var paramOrder = this.form.paramOrder;
+ if(paramOrder){
+ for(var i = 0, len = paramOrder.length; i < len; i++){
+ buf.push(o[paramOrder[i]]);
+ }
+ }else if(this.form.paramsAsHash){
+ buf.push(o);
+ }
+ return buf;
+ },
+
+
+
+ processResponse : function(result) {
+ this.result = result;
+ return result;
+ },
+
+ success : function(response, trans){
+ if(trans.type == Ext.Direct.exceptions.SERVER){
+ response = {};
+ }
+ Ext.form.Action.DirectLoad.superclass.success.call(this, response);
+ }
+});
+
+
+Ext.form.Action.DirectSubmit = Ext.extend(Ext.form.Action.Submit, {
+ constructor : function(form, opts) {
+ Ext.form.Action.DirectSubmit.superclass.constructor.call(this, form, opts);
+ },
+ type : 'directsubmit',
+
+ run : function(){
+ var o = this.options;
+ if(o.clientValidation === false || this.form.isValid()){
+
+
+ this.success.params = this.getParams();
+ this.form.api.submit(this.form.el.dom, this.success, this);
+ }else if (o.clientValidation !== false){
+ this.failureType = Ext.form.Action.CLIENT_INVALID;
+ this.form.afterAction(this, false);
+ }
+ },
+
+ getParams : function() {
+ var o = {};
+ var bp = this.form.baseParams;
+ var p = this.options.params;
+ Ext.apply(o, p, bp);
+ return o;
+ },
+
+
+
+ processResponse : function(result) {
+ this.result = result;
+ return result;
+ },
+
+ success : function(response, trans){
+ if(trans.type == Ext.Direct.exceptions.SERVER){
+ response = {};
+ }
+ Ext.form.Action.DirectSubmit.superclass.success.call(this, response);
+ }
+});
+
+Ext.form.Action.ACTION_TYPES = {
+ 'load' : Ext.form.Action.Load,
+ 'submit' : Ext.form.Action.Submit,
+ 'directload' : Ext.form.Action.DirectLoad,
+ 'directsubmit' : Ext.form.Action.DirectSubmit
+};
+
+Ext.form.VTypes = function(){
+
+ var alpha = /^[a-zA-Z_]+$/,
+ alphanum = /^[a-zA-Z0-9_]+$/,
+ email = /^(\w+)([\-+.\'][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/,
+ url = /(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;
+
+
+ return {
+
+ 'email' : function(v){
+ return email.test(v);
+ },
+
+ 'emailText' : 'This field should be an e-mail address in the format "user@example.com"',
+
+ 'emailMask' : /[a-z0-9_\.\-\+\'@]/i,
+
+ /**
+ * The function used to validate URLs
+ * @param {String} value The URL
+ * @return {Boolean} true if the RegExp test passed, and false if not.
+ */
+ 'url' : function(v){
+ return url.test(v);
+ },
+ /**
+ * The error text to display when the url validation function returns false. Defaults to:
+ * <tt>'This field should be a URL in the format "http:/'+'/www.example.com"'</tt>
+ * @type String
+ */
+ 'urlText' : 'This field should be a URL in the format "http:/'+'/www.example.com"',
+
+ /**
+ * The function used to validate alpha values
+ * @param {String} value The value
+ * @return {Boolean} true if the RegExp test passed, and false if not.
+ */
+ 'alpha' : function(v){
+ return alpha.test(v);
+ },
+ /**
+ * The error text to display when the alpha validation function returns false. Defaults to:
+ * <tt>'This field should only contain letters and _'</tt>
+ * @type String
+ */
+ 'alphaText' : 'This field should only contain letters and _',
+ /**
+ * The keystroke filter mask to be applied on alpha input. Defaults to:
+ * <tt>/[a-z_]/i</tt>
+ * @type RegExp
+ */
+ 'alphaMask' : /[a-z_]/i,
+
+ /**
+ * The function used to validate alphanumeric values
+ * @param {String} value The value
+ * @return {Boolean} true if the RegExp test passed, and false if not.
+ */
+ 'alphanum' : function(v){
+ return alphanum.test(v);
+ },
+ /**
+ * The error text to display when the alphanumeric validation function returns false. Defaults to:
+ * <tt>'This field should only contain letters, numbers and _'</tt>
+ * @type String
+ */
+ 'alphanumText' : 'This field should only contain letters, numbers and _',
+ /**
+ * The keystroke filter mask to be applied on alphanumeric input. Defaults to:
+ * <tt>/[a-z0-9_]/i</tt>
+ * @type RegExp
+ */
+ 'alphanumMask' : /[a-z0-9_]/i
+ };
+}();
+/**
+ * @class Ext.grid.GridPanel
+ * @extends Ext.Panel
+ * <p>This class represents the primary interface of a component based grid control to represent data
+ * in a tabular format of rows and columns. The GridPanel is composed of the following:</p>
+ * <div class="mdetail-params"><ul>
+ * <li><b>{@link Ext.data.Store Store}</b> : The Model holding the data records (rows)
+ * <div class="sub-desc"></div></li>
+ * <li><b>{@link Ext.grid.ColumnModel Column model}</b> : Column makeup
+ * <div class="sub-desc"></div></li>
+ * <li><b>{@link Ext.grid.GridView View}</b> : Encapsulates the user interface
+ * <div class="sub-desc"></div></li>
+ * <li><b>{@link Ext.grid.AbstractSelectionModel selection model}</b> : Selection behavior
+ * <div class="sub-desc"></div></li>
+ * </ul></div>
+ * <p>Example usage:</p>
+ * <pre><code>
+var grid = new Ext.grid.GridPanel({
+ {@link #store}: new {@link Ext.data.Store}({
+ {@link Ext.data.Store#autoDestroy autoDestroy}: true,
+ {@link Ext.data.Store#reader reader}: reader,
+ {@link Ext.data.Store#data data}: xg.dummyData
+ }),
+ {@link #colModel}: new {@link Ext.grid.ColumnModel}({
+ {@link Ext.grid.ColumnModel#defaults defaults}: {
+ width: 120,
+ sortable: true
+ },
+ {@link Ext.grid.ColumnModel#columns columns}: [
+ {id: 'company', header: 'Company', width: 200, sortable: true, dataIndex: 'company'},
+ {header: 'Price', renderer: Ext.util.Format.usMoney, dataIndex: 'price'},
+ {header: 'Change', dataIndex: 'change'},
+ {header: '% Change', dataIndex: 'pctChange'},
+ // instead of specifying renderer: Ext.util.Format.dateRenderer('m/d/Y') use xtype
+ {
+ header: 'Last Updated', width: 135, dataIndex: 'lastChange',
+ xtype: 'datecolumn', format: 'M d, Y'
+ }
+ ]
+ }),
+ {@link #viewConfig}: {
+ {@link Ext.grid.GridView#forceFit forceFit}: true,
+
+// Return CSS class to apply to rows depending upon data values
+ {@link Ext.grid.GridView#getRowClass getRowClass}: function(record, index) {
+ var c = record.{@link Ext.data.Record#get get}('change');
+ if (c < 0) {
+ return 'price-fall';
+ } else if (c > 0) {
+ return 'price-rise';
+ }
+ }
+ },
+ {@link #sm}: new Ext.grid.RowSelectionModel({singleSelect:true}),
+ width: 600,
+ height: 300,
+ frame: true,
+ title: 'Framed with Row Selection and Horizontal Scrolling',
+ iconCls: 'icon-grid'
+});
+ * </code></pre>
+ * <p><b><u>Notes:</u></b></p>
+ * <div class="mdetail-params"><ul>
+ * <li>Although this class inherits many configuration options from base classes, some of them
+ * (such as autoScroll, autoWidth, layout, items, etc) are not used by this class, and will
+ * have no effect.</li>
+ * <li>A grid <b>requires</b> a width in which to scroll its columns, and a height in which to
+ * scroll its rows. These dimensions can either be set explicitly through the
+ * <tt>{@link Ext.BoxComponent#height height}</tt> and <tt>{@link Ext.BoxComponent#width width}</tt>
+ * configuration options or implicitly set by using the grid as a child item of a
+ * {@link Ext.Container Container} which will have a {@link Ext.Container#layout layout manager}
+ * provide the sizing of its child items (for example the Container of the Grid may specify
+ * <tt>{@link Ext.Container#layout layout}:'fit'</tt>).</li>
+ * <li>To access the data in a Grid, it is necessary to use the data model encapsulated
+ * by the {@link #store Store}. See the {@link #cellclick} event for more details.</li>
+ * </ul></div>
+ * @constructor
+ * @param {Object} config The config object
+ * @xtype grid
+ */
+Ext.grid.GridPanel = Ext.extend(Ext.Panel, {
+ /**
+ * @cfg {String} autoExpandColumn
+ * <p>The <tt>{@link Ext.grid.Column#id id}</tt> of a {@link Ext.grid.Column column} in
+ * this grid that should expand to fill unused space. This value specified here can not
+ * be <tt>0</tt>.</p>
+ * <br><p><b>Note</b>: If the Grid's {@link Ext.grid.GridView view} is configured with
+ * <tt>{@link Ext.grid.GridView#forceFit forceFit}=true</tt> the <tt>autoExpandColumn</tt>
+ * is ignored. See {@link Ext.grid.Column}.<tt>{@link Ext.grid.Column#width width}</tt>
+ * for additional details.</p>
+ * <p>See <tt>{@link #autoExpandMax}</tt> and <tt>{@link #autoExpandMin}</tt> also.</p>
+ */
+ autoExpandColumn : false,
+
+
+ autoExpandMax : 1000,
+
+
+ autoExpandMin : 50,
+
+
+ columnLines : false,
+
+
+
+
+
+
+ ddText : '{0} selected row{1}',
+
+
+
+
+ deferRowRender : true,
+
+
+
+
+ enableColumnHide : true,
+
+
+ enableColumnMove : true,
+
+
+ enableDragDrop : false,
+
+
+ enableHdMenu : true,
+
+
+
+ loadMask : false,
+
+
+
+ minColumnWidth : 25,
+
+
+
+
+
+ stripeRows : false,
+
+
+ trackMouseOver : true,
+
+
+ stateEvents : ['columnmove', 'columnresize', 'sortchange', 'groupchange'],
+
+
+ view : null,
+
+
+ bubbleEvents: [],
+
+
+
+
+ rendered : false,
+
+
+ viewReady : false,
+
+
+ initComponent : function() {
+ Ext.grid.GridPanel.superclass.initComponent.call(this);
+
+ if (this.columnLines) {
+ this.cls = (this.cls || '') + ' x-grid-with-col-lines';
+ }
+
+
+ this.autoScroll = false;
+ this.autoWidth = false;
+
+ if(Ext.isArray(this.columns)){
+ this.colModel = new Ext.grid.ColumnModel(this.columns);
+ delete this.columns;
+ }
+
+
+ if(this.ds){
+ this.store = this.ds;
+ delete this.ds;
+ }
+ if(this.cm){
+ this.colModel = this.cm;
+ delete this.cm;
+ }
+ if(this.sm){
+ this.selModel = this.sm;
+ delete this.sm;
+ }
+ this.store = Ext.StoreMgr.lookup(this.store);
+
+ this.addEvents(
+
+
+ 'click',
+
+ 'dblclick',
+
+ 'contextmenu',
+
+ 'mousedown',
+
+ 'mouseup',
+
+ 'mouseover',
+
+ 'mouseout',
+
+ 'keypress',
+
+ 'keydown',
+
+
+
+ 'cellmousedown',
+
+ 'rowmousedown',
+
+ 'headermousedown',
+
+
+ 'groupmousedown',
+
+
+ 'rowbodymousedown',
+
+
+ 'containermousedown',
+
+
+ 'cellclick',
+
+ 'celldblclick',
+
+ 'rowclick',
+
+ 'rowdblclick',
+
+ 'headerclick',
+
+ 'headerdblclick',
+
+ 'groupclick',
+
+ 'groupdblclick',
+
+ 'containerclick',
+
+ 'containerdblclick',
+
+
+ 'rowbodyclick',
+
+ 'rowbodydblclick',
+
+
+ 'rowcontextmenu',
+
+ 'cellcontextmenu',
+
+ 'headercontextmenu',
+
+ 'groupcontextmenu',
+
+ 'containercontextmenu',
+
+ 'rowbodycontextmenu',
+
+ 'bodyscroll',
+
+ 'columnresize',
+
+ 'columnmove',
+
+ 'sortchange',
+
+ 'groupchange',
+
+ 'reconfigure',
+
+ 'viewready'
+ );
+ },
+
+
+ onRender : function(ct, position){
+ Ext.grid.GridPanel.superclass.onRender.apply(this, arguments);
+
+ var c = this.getGridEl();
+
+ this.el.addClass('x-grid-panel');
+
+ this.mon(c, {
+ scope: this,
+ mousedown: this.onMouseDown,
+ click: this.onClick,
+ dblclick: this.onDblClick,
+ contextmenu: this.onContextMenu
+ });
+
+ this.relayEvents(c, ['mousedown','mouseup','mouseover','mouseout','keypress', 'keydown']);
+
+ var view = this.getView();
+ view.init(this);
+ view.render();
+ this.getSelectionModel().init(this);
+ },
+
+
+ initEvents : function(){
+ Ext.grid.GridPanel.superclass.initEvents.call(this);
+
+ if(this.loadMask){
+ this.loadMask = new Ext.LoadMask(this.bwrap,
+ Ext.apply({store:this.store}, this.loadMask));
+ }
+ },
+
+ initStateEvents : function(){
+ Ext.grid.GridPanel.superclass.initStateEvents.call(this);
+ this.mon(this.colModel, 'hiddenchange', this.saveState, this, {delay: 100});
+ },
+
+ applyState : function(state){
+ var cm = this.colModel,
+ cs = state.columns,
+ store = this.store,
+ s,
+ c,
+ colIndex;
+
+ if(cs){
+ for(var i = 0, len = cs.length; i < len; i++){
+ s = cs[i];
+ c = cm.getColumnById(s.id);
+ if(c){
+ colIndex = cm.getIndexById(s.id);
+ cm.setState(colIndex, {
+ hidden: s.hidden,
+ width: s.width,
+ sortable: c.sortable,
+ editable: c.editable
+ });
+ if(colIndex != i){
+ cm.moveColumn(colIndex, i);
+ }
+ }
+ }
+ }
+ if(store){
+ s = state.sort;
+ if(s){
+ store[store.remoteSort ? 'setDefaultSort' : 'sort'](s.field, s.direction);
+ }
+ s = state.group;
+ if(store.groupBy){
+ if(s){
+ store.groupBy(s);
+ }else{
+ store.clearGrouping();
+ }
+ }
+
+ }
+ var o = Ext.apply({}, state);
+ delete o.columns;
+ delete o.sort;
+ Ext.grid.GridPanel.superclass.applyState.call(this, o);
+ },
+
+ getState : function(){
+ var o = {columns: []},
+ store = this.store,
+ ss,
+ gs;
+
+ for(var i = 0, c; (c = this.colModel.config[i]); i++){
+ o.columns[i] = {
+ id: c.id,
+ width: c.width
+ };
+ if(c.hidden){
+ o.columns[i].hidden = true;
+ }
+ }
+ if(store){
+ ss = store.getSortState();
+ if(ss){
+ o.sort = ss;
+ }
+ if(store.getGroupState){
+ gs = store.getGroupState();
+ if(gs){
+ o.group = gs;
+ }
+ }
+ }
+ return o;
+ },
+
+
+ afterRender : function(){
+ Ext.grid.GridPanel.superclass.afterRender.call(this);
+ var v = this.view;
+ this.on('bodyresize', v.layout, v);
+ v.layout(true);
+ if(this.deferRowRender){
+ if (!this.deferRowRenderTask){
+ this.deferRowRenderTask = new Ext.util.DelayedTask(v.afterRender, this.view);
+ }
+ this.deferRowRenderTask.delay(10);
+ }else{
+ v.afterRender();
+ }
+ this.viewReady = true;
+ },
+
+
+ reconfigure : function(store, colModel){
+ var rendered = this.rendered;
+ if(rendered){
+ if(this.loadMask){
+ this.loadMask.destroy();
+ this.loadMask = new Ext.LoadMask(this.bwrap,
+ Ext.apply({}, {store:store}, this.initialConfig.loadMask));
+ }
+ }
+ if(this.view){
+ this.view.initData(store, colModel);
+ }
+ this.store = store;
+ this.colModel = colModel;
+ if(rendered){
+ this.view.refresh(true);
+ }
+ this.fireEvent('reconfigure', this, store, colModel);
+ },
+
+
+ onDestroy : function(){
+ if (this.deferRowRenderTask && this.deferRowRenderTask.cancel){
+ this.deferRowRenderTask.cancel();
+ }
+ if(this.rendered){
+ Ext.destroy(this.view, this.loadMask);
+ }else if(this.store && this.store.autoDestroy){
+ this.store.destroy();
+ }
+ Ext.destroy(this.colModel, this.selModel);
+ this.store = this.selModel = this.colModel = this.view = this.loadMask = null;
+ Ext.grid.GridPanel.superclass.onDestroy.call(this);
+ },
+
+
+ processEvent : function(name, e){
+ this.view.processEvent(name, e);
+ },
+
+
+ onClick : function(e){
+ this.processEvent('click', e);
+ },
+
+
+ onMouseDown : function(e){
+ this.processEvent('mousedown', e);
+ },
+
+
+ onContextMenu : function(e, t){
+ this.processEvent('contextmenu', e);
+ },
+
+
+ onDblClick : function(e){
+ this.processEvent('dblclick', e);
+ },
+
+
+ walkCells : function(row, col, step, fn, scope){
+ var cm = this.colModel,
+ clen = cm.getColumnCount(),
+ ds = this.store,
+ rlen = ds.getCount(),
+ first = true;
+
+ if(step < 0){
+ if(col < 0){
+ row--;
+ first = false;
+ }
+ while(row >= 0){
+ if(!first){
+ col = clen-1;
+ }
+ first = false;
+ while(col >= 0){
+ if(fn.call(scope || this, row, col, cm) === true){
+ return [row, col];
+ }
+ col--;
+ }
+ row--;
+ }
+ } else {
+ if(col >= clen){
+ row++;
+ first = false;
+ }
+ while(row < rlen){
+ if(!first){
+ col = 0;
+ }
+ first = false;
+ while(col < clen){
+ if(fn.call(scope || this, row, col, cm) === true){
+ return [row, col];
+ }
+ col++;
+ }
+ row++;
+ }
+ }
+ return null;
+ },
+
+
+ getGridEl : function(){
+ return this.body;
+ },
+
+
+ stopEditing : Ext.emptyFn,
+
+
+ getSelectionModel : function(){
+ if(!this.selModel){
+ this.selModel = new Ext.grid.RowSelectionModel(
+ this.disableSelection ? {selectRow: Ext.emptyFn} : null);
+ }
+ return this.selModel;
+ },
+
+
+ getStore : function(){
+ return this.store;
+ },
+
+
+ getColumnModel : function(){
+ return this.colModel;
+ },
+
+
+ getView : function() {
+ if (!this.view) {
+ this.view = new Ext.grid.GridView(this.viewConfig);
+ }
+
+ return this.view;
+ },
+
+ getDragDropText : function(){
+ var count = this.selModel.getCount ? this.selModel.getCount() : 1;
+ return String.format(this.ddText, count, count == 1 ? '' : 's');
+ }
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+});
+Ext.reg('grid', Ext.grid.GridPanel);
+Ext.grid.PivotGrid = Ext.extend(Ext.grid.GridPanel, {
+
+
+ aggregator: 'sum',
+
+
+ renderer: undefined,
+
+
+
+
+
+
+
+
+ initComponent: function() {
+ Ext.grid.PivotGrid.superclass.initComponent.apply(this, arguments);
+
+ this.initAxes();
+
+
+ this.enableColumnResize = false;
+
+ this.viewConfig = Ext.apply(this.viewConfig || {}, {
+ forceFit: true
+ });
+
+
+
+ this.colModel = new Ext.grid.ColumnModel({});
+ },
+
+
+ getAggregator: function() {
+ if (typeof this.aggregator == 'string') {
+ return Ext.grid.PivotAggregatorMgr.types[this.aggregator];
+ } else {
+ return this.aggregator;
+ }
+ },
+
+
+ setAggregator: function(aggregator) {
+ this.aggregator = aggregator;
+ },
+
+
+ setMeasure: function(measure) {
+ this.measure = measure;
+ },
+
+
+ setLeftAxis: function(axis, refresh) {
+
+ this.leftAxis = axis;
+
+ if (refresh) {
+ this.view.refresh();
+ }
+ },
+
+
+ setTopAxis: function(axis, refresh) {
+
+ this.topAxis = axis;
+
+ if (refresh) {
+ this.view.refresh();
+ }
+ },
+
+
+ initAxes: function() {
+ var PivotAxis = Ext.grid.PivotAxis;
+
+ if (!(this.leftAxis instanceof PivotAxis)) {
+ this.setLeftAxis(new PivotAxis({
+ orientation: 'vertical',
+ dimensions : this.leftAxis || [],
+ store : this.store
+ }));
+ };
+
+ if (!(this.topAxis instanceof PivotAxis)) {
+ this.setTopAxis(new PivotAxis({
+ orientation: 'horizontal',
+ dimensions : this.topAxis || [],
+ store : this.store
+ }));
+ };
+ },
+
+
+ extractData: function() {
+ var records = this.store.data.items,
+ recCount = records.length,
+ cells = [],
+ record, i, j, k;
+
+ if (recCount == 0) {
+ return [];
+ }
+
+ var leftTuples = this.leftAxis.getTuples(),
+ leftCount = leftTuples.length,
+ topTuples = this.topAxis.getTuples(),
+ topCount = topTuples.length,
+ aggregator = this.getAggregator();
+
+ for (i = 0; i < recCount; i++) {
+ record = records[i];
+
+ for (j = 0; j < leftCount; j++) {
+ cells[j] = cells[j] || [];
+
+ if (leftTuples[j].matcher(record) === true) {
+ for (k = 0; k < topCount; k++) {
+ cells[j][k] = cells[j][k] || [];
+
+ if (topTuples[k].matcher(record)) {
+ cells[j][k].push(record);
+ }
+ }
+ }
+ }
+ }
+
+ var rowCount = cells.length,
+ colCount, row;
+
+ for (i = 0; i < rowCount; i++) {
+ row = cells[i];
+ colCount = row.length;
+
+ for (j = 0; j < colCount; j++) {
+ cells[i][j] = aggregator(cells[i][j], this.measure);
+ }
+ }
+
+ return cells;
+ },
+
+
+ getView: function() {
+ if (!this.view) {
+ this.view = new Ext.grid.PivotGridView(this.viewConfig);
+ }
+
+ return this.view;
+ }
+});
+
+Ext.reg('pivotgrid', Ext.grid.PivotGrid);
+
+
+Ext.grid.PivotAggregatorMgr = new Ext.AbstractManager();
+
+Ext.grid.PivotAggregatorMgr.registerType('sum', function(records, measure) {
+ var length = records.length,
+ total = 0,
+ i;
+
+ for (i = 0; i < length; i++) {
+ total += records[i].get(measure);
+ }
+
+ return total;
+});
+
+Ext.grid.PivotAggregatorMgr.registerType('avg', function(records, measure) {
+ var length = records.length,
+ total = 0,
+ i;
+
+ for (i = 0; i < length; i++) {
+ total += records[i].get(measure);
+ }
+
+ return (total / length) || 'n/a';
+});
+
+Ext.grid.PivotAggregatorMgr.registerType('min', function(records, measure) {
+ var data = [],
+ length = records.length,
+ i;
+
+ for (i = 0; i < length; i++) {
+ data.push(records[i].get(measure));
+ }
+
+ return Math.min.apply(this, data) || 'n/a';
+});
+
+Ext.grid.PivotAggregatorMgr.registerType('max', function(records, measure) {
+ var data = [],
+ length = records.length,
+ i;
+
+ for (i = 0; i < length; i++) {
+ data.push(records[i].get(measure));
+ }
+
+ return Math.max.apply(this, data) || 'n/a';
+});
+
+Ext.grid.PivotAggregatorMgr.registerType('count', function(records, measure) {
+ return records.length;
+});
+Ext.grid.GridView = Ext.extend(Ext.util.Observable, {
+
+
+
+
+
+
+
+
+
+
+
+ deferEmptyText : true,
+
+
+ scrollOffset : undefined,
+
+
+ autoFill : false,
+
+
+ forceFit : false,
+
+
+ sortClasses : ['sort-asc', 'sort-desc'],
+
+
+ sortAscText : 'Sort Ascending',
+
+
+ sortDescText : 'Sort Descending',
+
+
+ hideSortIcons: false,
+
+
+ columnsText : 'Columns',
+
+
+ selectedRowClass : 'x-grid3-row-selected',
+
+
+ borderWidth : 2,
+ tdClass : 'x-grid3-cell',
+ hdCls : 'x-grid3-hd',
+
+
+
+ markDirty : true,
+
+
+ cellSelectorDepth : 4,
+
+
+ rowSelectorDepth : 10,
+
+
+ rowBodySelectorDepth : 10,
+
+
+ cellSelector : 'td.x-grid3-cell',
+
+
+ rowSelector : 'div.x-grid3-row',
+
+
+ rowBodySelector : 'div.x-grid3-row-body',
+
+
+ firstRowCls: 'x-grid3-row-first',
+ lastRowCls: 'x-grid3-row-last',
+ rowClsRe: /(?:^|\s+)x-grid3-row-(first|last|alt)(?:\s+|$)/g,
+
+
+ headerMenuOpenCls: 'x-grid3-hd-menu-open',
+
+
+ rowOverCls: 'x-grid3-row-over',
+
+ constructor : function(config) {
+ Ext.apply(this, config);
+
+
+ this.addEvents(
+
+ 'beforerowremoved',
+
+
+ 'beforerowsinserted',
+
+
+ 'beforerefresh',
+
+
+ 'rowremoved',
+
+
+ 'rowsinserted',
+
+
+ 'rowupdated',
+
+
+ 'refresh'
+ );
+
+ Ext.grid.GridView.superclass.constructor.call(this);
+ },
+
+
+
+
+ masterTpl: new Ext.Template(
+ '<div class="x-grid3" hidefocus="true">',
+ '<div class="x-grid3-viewport">',
+ '<div class="x-grid3-header">',
+ '<div class="x-grid3-header-inner">',
+ '<div class="x-grid3-header-offset" style="{ostyle}">{header}</div>',
+ '</div>',
+ '<div class="x-clear"></div>',
+ '</div>',
+ '<div class="x-grid3-scroller">',
+ '<div class="x-grid3-body" style="{bstyle}">{body}</div>',
+ '<a href="#" class="x-grid3-focus" tabIndex="-1"></a>',
+ '</div>',
+ '</div>',
+ '<div class="x-grid3-resize-marker">&#160;</div>',
+ '<div class="x-grid3-resize-proxy">&#160;</div>',
+ '</div>'
+ ),
+
+
+ headerTpl: new Ext.Template(
+ '<table border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
+ '<thead>',
+ '<tr class="x-grid3-hd-row">{cells}</tr>',
+ '</thead>',
+ '</table>'
+ ),
+
+
+ bodyTpl: new Ext.Template('{rows}'),
+
+
+ cellTpl: new Ext.Template(
+ '<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}" tabIndex="0" {cellAttr}>',
+ '<div class="x-grid3-cell-inner x-grid3-col-{id} x-unselectable" unselectable="on" {attr}>{value}</div>',
+ '</td>'
+ ),
+
+
+ initTemplates : function() {
+ var templates = this.templates || {},
+ template, name,
+
+ headerCellTpl = new Ext.Template(
+ '<td class="x-grid3-hd x-grid3-cell x-grid3-td-{id} {css}" style="{style}">',
+ '<div {tooltip} {attr} class="x-grid3-hd-inner x-grid3-hd-{id}" unselectable="on" style="{istyle}">',
+ this.grid.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '',
+ '{value}',
+ '<img alt="" class="x-grid3-sort-icon" src="', Ext.BLANK_IMAGE_URL, '" />',
+ '</div>',
+ '</td>'
+ ),
+
+ rowBodyText = [
+ '<tr class="x-grid3-row-body-tr" style="{bodyStyle}">',
+ '<td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on">',
+ '<div class="x-grid3-row-body">{body}</div>',
+ '</td>',
+ '</tr>'
+ ].join(""),
+
+ innerText = [
+ '<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
+ '<tbody>',
+ '<tr>{cells}</tr>',
+ this.enableRowBody ? rowBodyText : '',
+ '</tbody>',
+ '</table>'
+ ].join("");
+
+ Ext.applyIf(templates, {
+ hcell : headerCellTpl,
+ cell : this.cellTpl,
+ body : this.bodyTpl,
+ header : this.headerTpl,
+ master : this.masterTpl,
+ row : new Ext.Template('<div class="x-grid3-row {alt}" style="{tstyle}">' + innerText + '</div>'),
+ rowInner: new Ext.Template(innerText)
+ });
+
+ for (name in templates) {
+ template = templates[name];
+
+ if (template && Ext.isFunction(template.compile) && !template.compiled) {
+ template.disableFormats = true;
+ template.compile();
+ }
+ }
+
+ this.templates = templates;
+ this.colRe = new RegExp('x-grid3-td-([^\\s]+)', '');
+ },
+
+
+ fly : function(el) {
+ if (!this._flyweight) {
+ this._flyweight = new Ext.Element.Flyweight(document.body);
+ }
+ this._flyweight.dom = el;
+ return this._flyweight;
+ },
+
+
+ getEditorParent : function() {
+ return this.scroller.dom;
+ },
+
+
+ initElements : function() {
+ var Element = Ext.Element,
+ el = Ext.get(this.grid.getGridEl().dom.firstChild),
+ mainWrap = new Element(el.child('div.x-grid3-viewport')),
+ mainHd = new Element(mainWrap.child('div.x-grid3-header')),
+ scroller = new Element(mainWrap.child('div.x-grid3-scroller'));
+
+ if (this.grid.hideHeaders) {
+ mainHd.setDisplayed(false);
+ }
+
+ if (this.forceFit) {
+ scroller.setStyle('overflow-x', 'hidden');
+ }
+
+
+
+ Ext.apply(this, {
+ el : el,
+ mainWrap: mainWrap,
+ scroller: scroller,
+ mainHd : mainHd,
+ innerHd : mainHd.child('div.x-grid3-header-inner').dom,
+ mainBody: new Element(Element.fly(scroller).child('div.x-grid3-body')),
+ focusEl : new Element(Element.fly(scroller).child('a')),
+
+ resizeMarker: new Element(el.child('div.x-grid3-resize-marker')),
+ resizeProxy : new Element(el.child('div.x-grid3-resize-proxy'))
+ });
+
+ this.focusEl.swallowEvent('click', true);
+ },
+
+
+ getRows : function() {
+ return this.hasRows() ? this.mainBody.dom.childNodes : [];
+ },
+
+
+
+
+ findCell : function(el) {
+ if (!el) {
+ return false;
+ }
+ return this.fly(el).findParent(this.cellSelector, this.cellSelectorDepth);
+ },
+
+
+ findCellIndex : function(el, requiredCls) {
+ var cell = this.findCell(el),
+ hasCls;
+
+ if (cell) {
+ hasCls = this.fly(cell).hasClass(requiredCls);
+ if (!requiredCls || hasCls) {
+ return this.getCellIndex(cell);
+ }
+ }
+ return false;
+ },
+
+
+ getCellIndex : function(el) {
+ if (el) {
+ var match = el.className.match(this.colRe);
+
+ if (match && match[1]) {
+ return this.cm.getIndexById(match[1]);
+ }
+ }
+ return false;
+ },
+
+
+ findHeaderCell : function(el) {
+ var cell = this.findCell(el);
+ return cell && this.fly(cell).hasClass(this.hdCls) ? cell : null;
+ },
+
+
+ findHeaderIndex : function(el){
+ return this.findCellIndex(el, this.hdCls);
+ },
+
+
+ findRow : function(el) {
+ if (!el) {
+ return false;
+ }
+ return this.fly(el).findParent(this.rowSelector, this.rowSelectorDepth);
+ },
+
+
+ findRowIndex : function(el) {
+ var row = this.findRow(el);
+ return row ? row.rowIndex : false;
+ },
+
+
+ findRowBody : function(el) {
+ if (!el) {
+ return false;
+ }
+
+ return this.fly(el).findParent(this.rowBodySelector, this.rowBodySelectorDepth);
+ },
+
+
+
+
+ getRow : function(row) {
+ return this.getRows()[row];
+ },
+
+
+ getCell : function(row, col) {
+ return Ext.fly(this.getRow(row)).query(this.cellSelector)[col];
+ },
+
+
+ getHeaderCell : function(index) {
+ return this.mainHd.dom.getElementsByTagName('td')[index];
+ },
+
+
+
+
+ addRowClass : function(rowId, cls) {
+ var row = this.getRow(rowId);
+ if (row) {
+ this.fly(row).addClass(cls);
+ }
+ },
+
+
+ removeRowClass : function(row, cls) {
+ var r = this.getRow(row);
+ if(r){
+ this.fly(r).removeClass(cls);
+ }
+ },
+
+
+ removeRow : function(row) {
+ Ext.removeNode(this.getRow(row));
+ this.syncFocusEl(row);
+ },
+
+
+ removeRows : function(firstRow, lastRow) {
+ var bd = this.mainBody.dom,
+ rowIndex;
+
+ for (rowIndex = firstRow; rowIndex <= lastRow; rowIndex++){
+ Ext.removeNode(bd.childNodes[firstRow]);
+ }
+
+ this.syncFocusEl(firstRow);
+ },
+
+
+
+
+ getScrollState : function() {
+ var sb = this.scroller.dom;
+
+ return {
+ left: sb.scrollLeft,
+ top : sb.scrollTop
+ };
+ },
+
+
+ restoreScroll : function(state) {
+ var sb = this.scroller.dom;
+ sb.scrollLeft = state.left;
+ sb.scrollTop = state.top;
+ },
+
+
+ scrollToTop : function() {
+ var dom = this.scroller.dom;
+
+ dom.scrollTop = 0;
+ dom.scrollLeft = 0;
+ },
+
+
+ syncScroll : function() {
+ this.syncHeaderScroll();
+ var mb = this.scroller.dom;
+ this.grid.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop);
+ },
+
+
+ syncHeaderScroll : function() {
+ var innerHd = this.innerHd,
+ scrollLeft = this.scroller.dom.scrollLeft;
+
+ innerHd.scrollLeft = scrollLeft;
+ innerHd.scrollLeft = scrollLeft;
+ },
+
+
+ updateSortIcon : function(col, dir) {
+ var sortClasses = this.sortClasses,
+ sortClass = sortClasses[dir == "DESC" ? 1 : 0],
+ headers = this.mainHd.select('td').removeClass(sortClasses);
+
+ headers.item(col).addClass(sortClass);
+ },
+
+
+ updateAllColumnWidths : function() {
+ var totalWidth = this.getTotalWidth(),
+ colCount = this.cm.getColumnCount(),
+ rows = this.getRows(),
+ rowCount = rows.length,
+ widths = [],
+ row, rowFirstChild, trow, i, j;
+
+ for (i = 0; i < colCount; i++) {
+ widths[i] = this.getColumnWidth(i);
+ this.getHeaderCell(i).style.width = widths[i];
+ }
+
+ this.updateHeaderWidth();
+
+ for (i = 0; i < rowCount; i++) {
+ row = rows[i];
+ row.style.width = totalWidth;
+ rowFirstChild = row.firstChild;
+
+ if (rowFirstChild) {
+ rowFirstChild.style.width = totalWidth;
+ trow = rowFirstChild.rows[0];
+
+ for (j = 0; j < colCount; j++) {
+ trow.childNodes[j].style.width = widths[j];
+ }
+ }
+ }
+
+ this.onAllColumnWidthsUpdated(widths, totalWidth);
+ },
+
+
+ updateColumnWidth : function(column, width) {
+ var columnWidth = this.getColumnWidth(column),
+ totalWidth = this.getTotalWidth(),
+ headerCell = this.getHeaderCell(column),
+ nodes = this.getRows(),
+ nodeCount = nodes.length,
+ row, i, firstChild;
+
+ this.updateHeaderWidth();
+ headerCell.style.width = columnWidth;
+
+ for (i = 0; i < nodeCount; i++) {
+ row = nodes[i];
+ firstChild = row.firstChild;
+
+ row.style.width = totalWidth;
+ if (firstChild) {
+ firstChild.style.width = totalWidth;
+ firstChild.rows[0].childNodes[column].style.width = columnWidth;
+ }
+ }
+
+ this.onColumnWidthUpdated(column, columnWidth, totalWidth);
+ },
+
+
+ updateColumnHidden : function(col, hidden) {
+ var totalWidth = this.getTotalWidth(),
+ display = hidden ? 'none' : '',
+ headerCell = this.getHeaderCell(col),
+ nodes = this.getRows(),
+ nodeCount = nodes.length,
+ row, rowFirstChild, i;
+
+ this.updateHeaderWidth();
+ headerCell.style.display = display;
+
+ for (i = 0; i < nodeCount; i++) {
+ row = nodes[i];
+ row.style.width = totalWidth;
+ rowFirstChild = row.firstChild;
+
+ if (rowFirstChild) {
+ rowFirstChild.style.width = totalWidth;
+ rowFirstChild.rows[0].childNodes[col].style.display = display;
+ }
+ }
+
+ this.onColumnHiddenUpdated(col, hidden, totalWidth);
+ delete this.lastViewWidth;
+ this.layout();
+ },
+
+
+ doRender : function(columns, records, store, startRow, colCount, stripe) {
+ var templates = this.templates,
+ cellTemplate = templates.cell,
+ rowTemplate = templates.row,
+ last = colCount - 1,
+ tstyle = 'width:' + this.getTotalWidth() + ';',
+
+ rowBuffer = [],
+ colBuffer = [],
+ rowParams = {tstyle: tstyle},
+ meta = {},
+ len = records.length,
+ alt,
+ column,
+ record, i, j, rowIndex;
+
+
+ for (j = 0; j < len; j++) {
+ record = records[j];
+ colBuffer = [];
+
+ rowIndex = j + startRow;
+
+
+ for (i = 0; i < colCount; i++) {
+ column = columns[i];
+
+ meta.id = column.id;
+ meta.css = i === 0 ? 'x-grid3-cell-first ' : (i == last ? 'x-grid3-cell-last ' : '');
+ meta.attr = meta.cellAttr = '';
+ meta.style = column.style;
+ meta.value = column.renderer.call(column.scope, record.data[column.name], meta, record, rowIndex, i, store);
+
+ if (Ext.isEmpty(meta.value)) {
+ meta.value = '&#160;';
+ }
+
+ if (this.markDirty && record.dirty && typeof record.modified[column.name] != 'undefined') {
+ meta.css += ' x-grid3-dirty-cell';
+ }
+
+ colBuffer[colBuffer.length] = cellTemplate.apply(meta);
+ }
+
+ alt = [];
+
+ if (stripe && ((rowIndex + 1) % 2 === 0)) {
+ alt[0] = 'x-grid3-row-alt';
+ }
+
+ if (record.dirty) {
+ alt[1] = ' x-grid3-dirty-row';
+ }
+
+ rowParams.cols = colCount;
+
+ if (this.getRowClass) {
+ alt[2] = this.getRowClass(record, rowIndex, rowParams, store);
+ }
+
+ rowParams.alt = alt.join(' ');
+ rowParams.cells = colBuffer.join('');
+
+ rowBuffer[rowBuffer.length] = rowTemplate.apply(rowParams);
+ }
+
+ return rowBuffer.join('');
+ },
+
+
+ processRows : function(startRow, skipStripe) {
+ if (!this.ds || this.ds.getCount() < 1) {
+ return;
+ }
+
+ var rows = this.getRows(),
+ length = rows.length,
+ row, i;
+
+ skipStripe = skipStripe || !this.grid.stripeRows;
+ startRow = startRow || 0;
+
+ for (i = 0; i < length; i++) {
+ row = rows[i];
+ if (row) {
+ row.rowIndex = i;
+ if (!skipStripe) {
+ row.className = row.className.replace(this.rowClsRe, ' ');
+ if ((i + 1) % 2 === 0){
+ row.className += ' x-grid3-row-alt';
+ }
+ }
+ }
+ }
+
+
+ if (startRow === 0) {
+ Ext.fly(rows[0]).addClass(this.firstRowCls);
+ }
+
+ Ext.fly(rows[length - 1]).addClass(this.lastRowCls);
+ },
+
+
+ afterRender : function() {
+ if (!this.ds || !this.cm) {
+ return;
+ }
+
+ this.mainBody.dom.innerHTML = this.renderBody() || '&#160;';
+ this.processRows(0, true);
+
+ if (this.deferEmptyText !== true) {
+ this.applyEmptyText();
+ }
+
+ this.grid.fireEvent('viewready', this.grid);
+ },
+
+
+ afterRenderUI: function() {
+ var grid = this.grid;
+
+ this.initElements();
+
+
+ Ext.fly(this.innerHd).on('click', this.handleHdDown, this);
+
+ this.mainHd.on({
+ scope : this,
+ mouseover: this.handleHdOver,
+ mouseout : this.handleHdOut,
+ mousemove: this.handleHdMove
+ });
+
+ this.scroller.on('scroll', this.syncScroll, this);
+
+ if (grid.enableColumnResize !== false) {
+ this.splitZone = new Ext.grid.GridView.SplitDragZone(grid, this.mainHd.dom);
+ }
+
+ if (grid.enableColumnMove) {
+ this.columnDrag = new Ext.grid.GridView.ColumnDragZone(grid, this.innerHd);
+ this.columnDrop = new Ext.grid.HeaderDropZone(grid, this.mainHd.dom);
+ }
+
+ if (grid.enableHdMenu !== false) {
+ this.hmenu = new Ext.menu.Menu({id: grid.id + '-hctx'});
+ this.hmenu.add(
+ {itemId:'asc', text: this.sortAscText, cls: 'xg-hmenu-sort-asc'},
+ {itemId:'desc', text: this.sortDescText, cls: 'xg-hmenu-sort-desc'}
+ );
+
+ if (grid.enableColumnHide !== false) {
+ this.colMenu = new Ext.menu.Menu({id:grid.id + '-hcols-menu'});
+ this.colMenu.on({
+ scope : this,
+ beforeshow: this.beforeColMenuShow,
+ itemclick : this.handleHdMenuClick
+ });
+ this.hmenu.add({
+ itemId: 'sortSep',
+ xtype: 'menuseparator'
+ }, {
+ itemId:'columns',
+ hideOnClick: false,
+ text: this.columnsText,
+ menu: this.colMenu,
+ iconCls: 'x-cols-icon'
+ });
+ }
+
+ this.hmenu.on('itemclick', this.handleHdMenuClick, this);
+ }
+
+ if (grid.trackMouseOver) {
+ this.mainBody.on({
+ scope : this,
+ mouseover: this.onRowOver,
+ mouseout : this.onRowOut
+ });
+ }
+
+ if (grid.enableDragDrop || grid.enableDrag) {
+ this.dragZone = new Ext.grid.GridDragZone(grid, {
+ ddGroup : grid.ddGroup || 'GridDD'
+ });
+ }
+
+ this.updateHeaderSortState();
+ },
+
+
+ renderUI : function() {
+ var templates = this.templates;
+
+ return templates.master.apply({
+ body : templates.body.apply({rows:'&#160;'}),
+ header: this.renderHeaders(),
+ ostyle: 'width:' + this.getOffsetWidth() + ';',
+ bstyle: 'width:' + this.getTotalWidth() + ';'
+ });
+ },
+
+
+ processEvent : function(name, e) {
+ var target = e.getTarget(),
+ grid = this.grid,
+ header = this.findHeaderIndex(target),
+ row, cell, col, body;
+
+ grid.fireEvent(name, e);
+
+ if (header !== false) {
+ grid.fireEvent('header' + name, grid, header, e);
+ } else {
+ row = this.findRowIndex(target);
+
+
+
+
+ if (row !== false) {
+ cell = this.findCellIndex(target);
+ if (cell !== false) {
+ col = grid.colModel.getColumnAt(cell);
+ if (grid.fireEvent('cell' + name, grid, row, cell, e) !== false) {
+ if (!col || (col.processEvent && (col.processEvent(name, e, grid, row, cell) !== false))) {
+ grid.fireEvent('row' + name, grid, row, e);
+ }
+ }
+ } else {
+ if (grid.fireEvent('row' + name, grid, row, e) !== false) {
+ (body = this.findRowBody(target)) && grid.fireEvent('rowbody' + name, grid, row, e);
+ }
+ }
+ } else {
+ grid.fireEvent('container' + name, grid, e);
+ }
+ }
+ },
+
+
+ layout : function(initial) {
+ if (!this.mainBody) {
+ return;
+ }
+
+ var grid = this.grid,
+ gridEl = grid.getGridEl(),
+ gridSize = gridEl.getSize(true),
+ gridWidth = gridSize.width,
+ gridHeight = gridSize.height,
+ scroller = this.scroller,
+ scrollStyle, headerHeight, scrollHeight;
+
+ if (gridWidth < 20 || gridHeight < 20) {
+ return;
+ }
+
+ if (grid.autoHeight) {
+ scrollStyle = scroller.dom.style;
+ scrollStyle.overflow = 'visible';
+
+ if (Ext.isWebKit) {
+ scrollStyle.position = 'static';
+ }
+ } else {
+ this.el.setSize(gridWidth, gridHeight);
+
+ headerHeight = this.mainHd.getHeight();
+ scrollHeight = gridHeight - headerHeight;
+
+ scroller.setSize(gridWidth, scrollHeight);
+
+ if (this.innerHd) {
+ this.innerHd.style.width = (gridWidth) + "px";
+ }
+ }
+
+ if (this.forceFit || (initial === true && this.autoFill)) {
+ if (this.lastViewWidth != gridWidth) {
+ this.fitColumns(false, false);
+ this.lastViewWidth = gridWidth;
+ }
+ } else {
+ this.autoExpand();
+ this.syncHeaderScroll();
+ }
+
+ this.onLayout(gridWidth, scrollHeight);
+ },
+
+
+
+ onLayout : function(vw, vh) {
+
+ },
+
+ onColumnWidthUpdated : function(col, w, tw) {
+
+ },
+
+ onAllColumnWidthsUpdated : function(ws, tw) {
+
+ },
+
+ onColumnHiddenUpdated : function(col, hidden, tw) {
+
+ },
+
+ updateColumnText : function(col, text) {
+
+ },
+
+ afterMove : function(colIndex) {
+
+ },
+
+
+
+ init : function(grid) {
+ this.grid = grid;
+
+ this.initTemplates();
+ this.initData(grid.store, grid.colModel);
+ this.initUI(grid);
+ },
+
+
+ getColumnId : function(index){
+ return this.cm.getColumnId(index);
+ },
+
+
+ getOffsetWidth : function() {
+ return (this.cm.getTotalWidth() + this.getScrollOffset()) + 'px';
+ },
+
+
+ getScrollOffset: function() {
+ return Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
+ },
+
+
+ renderHeaders : function() {
+ var colModel = this.cm,
+ templates = this.templates,
+ headerTpl = templates.hcell,
+ properties = {},
+ colCount = colModel.getColumnCount(),
+ last = colCount - 1,
+ cells = [],
+ i, cssCls;
+
+ for (i = 0; i < colCount; i++) {
+ if (i == 0) {
+ cssCls = 'x-grid3-cell-first ';
+ } else {
+ cssCls = i == last ? 'x-grid3-cell-last ' : '';
+ }
+
+ properties = {
+ id : colModel.getColumnId(i),
+ value : colModel.getColumnHeader(i) || '',
+ style : this.getColumnStyle(i, true),
+ css : cssCls,
+ tooltip: this.getColumnTooltip(i)
+ };
+
+ if (colModel.config[i].align == 'right') {
+ properties.istyle = 'padding-right: 16px;';
+ } else {
+ delete properties.istyle;
+ }
+
+ cells[i] = headerTpl.apply(properties);
+ }
+
+ return templates.header.apply({
+ cells : cells.join(""),
+ tstyle: String.format("width: {0};", this.getTotalWidth())
+ });
+ },
+
+
+ getColumnTooltip : function(i) {
+ var tooltip = this.cm.getColumnTooltip(i);
+ if (tooltip) {
+ if (Ext.QuickTips.isEnabled()) {
+ return 'ext:qtip="' + tooltip + '"';
+ } else {
+ return 'title="' + tooltip + '"';
+ }
+ }
+
+ return '';
+ },
+
+
+ beforeUpdate : function() {
+ this.grid.stopEditing(true);
+ },
+
+
+ updateHeaders : function() {
+ this.innerHd.firstChild.innerHTML = this.renderHeaders();
+
+ this.updateHeaderWidth(false);
+ },
+
+
+ updateHeaderWidth: function(updateMain) {
+ var innerHdChild = this.innerHd.firstChild,
+ totalWidth = this.getTotalWidth();
+
+ innerHdChild.style.width = this.getOffsetWidth();
+ innerHdChild.firstChild.style.width = totalWidth;
+
+ if (updateMain !== false) {
+ this.mainBody.dom.style.width = totalWidth;
+ }
+ },
+
+
+ focusRow : function(row) {
+ this.focusCell(row, 0, false);
+ },
+
+
+ focusCell : function(row, col, hscroll) {
+ this.syncFocusEl(this.ensureVisible(row, col, hscroll));
+
+ var focusEl = this.focusEl;
+
+ if (Ext.isGecko) {
+ focusEl.focus();
+ } else {
+ focusEl.focus.defer(1, focusEl);
+ }
+ },
+
+
+ resolveCell : function(row, col, hscroll) {
+ if (!Ext.isNumber(row)) {
+ row = row.rowIndex;
+ }
+
+ if (!this.ds) {
+ return null;
+ }
+
+ if (row < 0 || row >= this.ds.getCount()) {
+ return null;
+ }
+ col = (col !== undefined ? col : 0);
+
+ var rowEl = this.getRow(row),
+ colModel = this.cm,
+ colCount = colModel.getColumnCount(),
+ cellEl;
+
+ if (!(hscroll === false && col === 0)) {
+ while (col < colCount && colModel.isHidden(col)) {
+ col++;
+ }
+
+ cellEl = this.getCell(row, col);
+ }
+
+ return {row: rowEl, cell: cellEl};
+ },
+
+
+ getResolvedXY : function(resolved) {
+ if (!resolved) {
+ return null;
+ }
+
+ var cell = resolved.cell,
+ row = resolved.row;
+
+ if (cell) {
+ return Ext.fly(cell).getXY();
+ } else {
+ return [this.el.getX(), Ext.fly(row).getY()];
+ }
+ },
+
+
+ syncFocusEl : function(row, col, hscroll) {
+ var xy = row;
+
+ if (!Ext.isArray(xy)) {
+ row = Math.min(row, Math.max(0, this.getRows().length-1));
+
+ if (isNaN(row)) {
+ return;
+ }
+
+ xy = this.getResolvedXY(this.resolveCell(row, col, hscroll));
+ }
+
+ this.focusEl.setXY(xy || this.scroller.getXY());
+ },
+
+
+ ensureVisible : function(row, col, hscroll) {
+ var resolved = this.resolveCell(row, col, hscroll);
+
+ if (!resolved || !resolved.row) {
+ return null;
+ }
+
+ var rowEl = resolved.row,
+ cellEl = resolved.cell,
+ c = this.scroller.dom,
+ p = rowEl,
+ ctop = 0,
+ stop = this.el.dom;
+
+ while (p && p != stop) {
+ ctop += p.offsetTop;
+ p = p.offsetParent;
+ }
+
+ ctop -= this.mainHd.dom.offsetHeight;
+ stop = parseInt(c.scrollTop, 10);
+
+ var cbot = ctop + rowEl.offsetHeight,
+ ch = c.clientHeight,
+ sbot = stop + ch;
+
+
+ if (ctop < stop) {
+ c.scrollTop = ctop;
+ } else if(cbot > sbot) {
+ c.scrollTop = cbot-ch;
+ }
+
+ if (hscroll !== false) {
+ var cleft = parseInt(cellEl.offsetLeft, 10),
+ cright = cleft + cellEl.offsetWidth,
+ sleft = parseInt(c.scrollLeft, 10),
+ sright = sleft + c.clientWidth;
+
+ if (cleft < sleft) {
+ c.scrollLeft = cleft;
+ } else if(cright > sright) {
+ c.scrollLeft = cright-c.clientWidth;
+ }
+ }
+
+ return this.getResolvedXY(resolved);
+ },
+
+
+ insertRows : function(dm, firstRow, lastRow, isUpdate) {
+ var last = dm.getCount() - 1;
+ if( !isUpdate && firstRow === 0 && lastRow >= last) {
+ this.fireEvent('beforerowsinserted', this, firstRow, lastRow);
+ this.refresh();
+ this.fireEvent('rowsinserted', this, firstRow, lastRow);
+ } else {
+ if (!isUpdate) {
+ this.fireEvent('beforerowsinserted', this, firstRow, lastRow);
+ }
+ var html = this.renderRows(firstRow, lastRow),
+ before = this.getRow(firstRow);
+ if (before) {
+ if(firstRow === 0){
+ Ext.fly(this.getRow(0)).removeClass(this.firstRowCls);
+ }
+ Ext.DomHelper.insertHtml('beforeBegin', before, html);
+ } else {
+ var r = this.getRow(last - 1);
+ if(r){
+ Ext.fly(r).removeClass(this.lastRowCls);
+ }
+ Ext.DomHelper.insertHtml('beforeEnd', this.mainBody.dom, html);
+ }
+ if (!isUpdate) {
+ this.processRows(firstRow);
+ this.fireEvent('rowsinserted', this, firstRow, lastRow);
+ } else if (firstRow === 0 || firstRow >= last) {
+
+ Ext.fly(this.getRow(firstRow)).addClass(firstRow === 0 ? this.firstRowCls : this.lastRowCls);
+ }
+ }
+ this.syncFocusEl(firstRow);
+ },
+
+
+ deleteRows : function(dm, firstRow, lastRow) {
+ if (dm.getRowCount() < 1) {
+ this.refresh();
+ } else {
+ this.fireEvent('beforerowsdeleted', this, firstRow, lastRow);
+
+ this.removeRows(firstRow, lastRow);
+
+ this.processRows(firstRow);
+ this.fireEvent('rowsdeleted', this, firstRow, lastRow);
+ }
+ },
+
+
+ getColumnStyle : function(colIndex, isHeader) {
+ var colModel = this.cm,
+ colConfig = colModel.config,
+ style = isHeader ? '' : colConfig[colIndex].css || '',
+ align = colConfig[colIndex].align;
+
+ style += String.format("width: {0};", this.getColumnWidth(colIndex));
+
+ if (colModel.isHidden(colIndex)) {
+ style += 'display: none; ';
+ }
+
+ if (align) {
+ style += String.format("text-align: {0};", align);
+ }
+
+ return style;
+ },
+
+
+ getColumnWidth : function(column) {
+ var columnWidth = this.cm.getColumnWidth(column),
+ borderWidth = this.borderWidth;
+
+ if (Ext.isNumber(columnWidth)) {
+ if (Ext.isBorderBox) {
+ return columnWidth + "px";
+ } else {
+ return Math.max(columnWidth - borderWidth, 0) + "px";
+ }
+ } else {
+ return columnWidth;
+ }
+ },
+
+
+ getTotalWidth : function() {
+ return this.cm.getTotalWidth() + 'px';
+ },
+
+
+ fitColumns : function(preventRefresh, onlyExpand, omitColumn) {
+ var grid = this.grid,
+ colModel = this.cm,
+ totalColWidth = colModel.getTotalWidth(false),
+ gridWidth = this.getGridInnerWidth(),
+ extraWidth = gridWidth - totalColWidth,
+ columns = [],
+ extraCol = 0,
+ width = 0,
+ colWidth, fraction, i;
+
+
+ if (gridWidth < 20 || extraWidth === 0) {
+ return false;
+ }
+
+ var visibleColCount = colModel.getColumnCount(true),
+ totalColCount = colModel.getColumnCount(false),
+ adjCount = visibleColCount - (Ext.isNumber(omitColumn) ? 1 : 0);
+
+ if (adjCount === 0) {
+ adjCount = 1;
+ omitColumn = undefined;
+ }
+
+
+ for (i = 0; i < totalColCount; i++) {
+ if (!colModel.isFixed(i) && i !== omitColumn) {
+ colWidth = colModel.getColumnWidth(i);
+ columns.push(i, colWidth);
+
+ if (!colModel.isHidden(i)) {
+ extraCol = i;
+ width += colWidth;
+ }
+ }
+ }
+
+ fraction = (gridWidth - colModel.getTotalWidth()) / width;
+
+ while (columns.length) {
+ colWidth = columns.pop();
+ i = columns.pop();
+
+ colModel.setColumnWidth(i, Math.max(grid.minColumnWidth, Math.floor(colWidth + colWidth * fraction)), true);
+ }
+
+
+ totalColWidth = colModel.getTotalWidth(false);
+
+ if (totalColWidth > gridWidth) {
+ var adjustCol = (adjCount == visibleColCount) ? extraCol : omitColumn,
+ newWidth = Math.max(1, colModel.getColumnWidth(adjustCol) - (totalColWidth - gridWidth));
+
+ colModel.setColumnWidth(adjustCol, newWidth, true);
+ }
+
+ if (preventRefresh !== true) {
+ this.updateAllColumnWidths();
+ }
+
+ return true;
+ },
+
+
+ autoExpand : function(preventUpdate) {
+ var grid = this.grid,
+ colModel = this.cm,
+ gridWidth = this.getGridInnerWidth(),
+ totalColumnWidth = colModel.getTotalWidth(false),
+ autoExpandColumn = grid.autoExpandColumn;
+
+ if (!this.userResized && autoExpandColumn) {
+ if (gridWidth != totalColumnWidth) {
+
+ var colIndex = colModel.getIndexById(autoExpandColumn),
+ currentWidth = colModel.getColumnWidth(colIndex),
+ desiredWidth = gridWidth - totalColumnWidth + currentWidth,
+ newWidth = Math.min(Math.max(desiredWidth, grid.autoExpandMin), grid.autoExpandMax);
+
+ if (currentWidth != newWidth) {
+ colModel.setColumnWidth(colIndex, newWidth, true);
+
+ if (preventUpdate !== true) {
+ this.updateColumnWidth(colIndex, newWidth);
+ }
+ }
+ }
+ }
+ },
+
+
+ getGridInnerWidth: function() {
+ return this.grid.getGridEl().getWidth(true) - this.getScrollOffset();
+ },
+
+
+ getColumnData : function() {
+ var columns = [],
+ colModel = this.cm,
+ colCount = colModel.getColumnCount(),
+ fields = this.ds.fields,
+ i, name;
+
+ for (i = 0; i < colCount; i++) {
+ name = colModel.getDataIndex(i);
+
+ columns[i] = {
+ name : Ext.isDefined(name) ? name : (fields.get(i) ? fields.get(i).name : undefined),
+ renderer: colModel.getRenderer(i),
+ scope : colModel.getRendererScope(i),
+ id : colModel.getColumnId(i),
+ style : this.getColumnStyle(i)
+ };
+ }
+
+ return columns;
+ },
+
+
+ renderRows : function(startRow, endRow) {
+ var grid = this.grid,
+ store = grid.store,
+ stripe = grid.stripeRows,
+ colModel = grid.colModel,
+ colCount = colModel.getColumnCount(),
+ rowCount = store.getCount(),
+ records;
+
+ if (rowCount < 1) {
+ return '';
+ }
+
+ startRow = startRow || 0;
+ endRow = Ext.isDefined(endRow) ? endRow : rowCount - 1;
+ records = store.getRange(startRow, endRow);
+
+ return this.doRender(this.getColumnData(), records, store, startRow, colCount, stripe);
+ },
+
+
+ renderBody : function(){
+ var markup = this.renderRows() || '&#160;';
+ return this.templates.body.apply({rows: markup});
+ },
+
+
+ refreshRow: function(record) {
+ var store = this.ds,
+ colCount = this.cm.getColumnCount(),
+ columns = this.getColumnData(),
+ last = colCount - 1,
+ cls = ['x-grid3-row'],
+ rowParams = {
+ tstyle: String.format("width: {0};", this.getTotalWidth())
+ },
+ colBuffer = [],
+ cellTpl = this.templates.cell,
+ rowIndex, row, column, meta, css, i;
+
+ if (Ext.isNumber(record)) {
+ rowIndex = record;
+ record = store.getAt(rowIndex);
+ } else {
+ rowIndex = store.indexOf(record);
+ }
+
+
+ if (!record || rowIndex < 0) {
+ return;
+ }
+
+
+ for (i = 0; i < colCount; i++) {
+ column = columns[i];
+
+ if (i == 0) {
+ css = 'x-grid3-cell-first';
+ } else {
+ css = (i == last) ? 'x-grid3-cell-last ' : '';
+ }
+
+ meta = {
+ id : column.id,
+ style : column.style,
+ css : css,
+ attr : "",
+ cellAttr: ""
+ };
+
+ meta.value = column.renderer.call(column.scope, record.data[column.name], meta, record, rowIndex, i, store);
+
+ if (Ext.isEmpty(meta.value)) {
+ meta.value = '&#160;';
+ }
+
+ if (this.markDirty && record.dirty && typeof record.modified[column.name] != 'undefined') {
+ meta.css += ' x-grid3-dirty-cell';
+ }
+
+ colBuffer[i] = cellTpl.apply(meta);
+ }
+
+ row = this.getRow(rowIndex);
+ row.className = '';
+
+ if (this.grid.stripeRows && ((rowIndex + 1) % 2 === 0)) {
+ cls.push('x-grid3-row-alt');
+ }
+
+ if (this.getRowClass) {
+ rowParams.cols = colCount;
+ cls.push(this.getRowClass(record, rowIndex, rowParams, store));
+ }
+
+ this.fly(row).addClass(cls).setStyle(rowParams.tstyle);
+ rowParams.cells = colBuffer.join("");
+ row.innerHTML = this.templates.rowInner.apply(rowParams);
+
+ this.fireEvent('rowupdated', this, rowIndex, record);
+ },
+
+
+ refresh : function(headersToo) {
+ this.fireEvent('beforerefresh', this);
+ this.grid.stopEditing(true);
+
+ var result = this.renderBody();
+ this.mainBody.update(result).setWidth(this.getTotalWidth());
+ if (headersToo === true) {
+ this.updateHeaders();
+ this.updateHeaderSortState();
+ }
+ this.processRows(0, true);
+ this.layout();
+ this.applyEmptyText();
+ this.fireEvent('refresh', this);
+ },
+
+
+ applyEmptyText : function() {
+ if (this.emptyText && !this.hasRows()) {
+ this.mainBody.update('<div class="x-grid-empty">' + this.emptyText + '</div>');
+ }
+ },
+
+
+ updateHeaderSortState : function() {
+ var state = this.ds.getSortState();
+ if (!state) {
+ return;
+ }
+
+ if (!this.sortState || (this.sortState.field != state.field || this.sortState.direction != state.direction)) {
+ this.grid.fireEvent('sortchange', this.grid, state);
+ }
+
+ this.sortState = state;
+
+ var sortColumn = this.cm.findColumnIndex(state.field);
+ if (sortColumn != -1) {
+ var sortDir = state.direction;
+ this.updateSortIcon(sortColumn, sortDir);
+ }
+ },
+
+
+ clearHeaderSortState : function() {
+ if (!this.sortState) {
+ return;
+ }
+ this.grid.fireEvent('sortchange', this.grid, null);
+ this.mainHd.select('td').removeClass(this.sortClasses);
+ delete this.sortState;
+ },
+
+
+ destroy : function() {
+ var me = this,
+ grid = me.grid,
+ gridEl = grid.getGridEl(),
+ dragZone = me.dragZone,
+ splitZone = me.splitZone,
+ columnDrag = me.columnDrag,
+ columnDrop = me.columnDrop,
+ scrollToTopTask = me.scrollToTopTask,
+ columnDragData,
+ columnDragProxy;
+
+ if (scrollToTopTask && scrollToTopTask.cancel) {
+ scrollToTopTask.cancel();
+ }
+
+ Ext.destroyMembers(me, 'colMenu', 'hmenu');
+
+ me.initData(null, null);
+ me.purgeListeners();
+
+ Ext.fly(me.innerHd).un("click", me.handleHdDown, me);
+
+ if (grid.enableColumnMove) {
+ columnDragData = columnDrag.dragData;
+ columnDragProxy = columnDrag.proxy;
+ Ext.destroy(
+ columnDrag.el,
+ columnDragProxy.ghost,
+ columnDragProxy.el,
+ columnDrop.el,
+ columnDrop.proxyTop,
+ columnDrop.proxyBottom,
+ columnDragData.ddel,
+ columnDragData.header
+ );
+
+ if (columnDragProxy.anim) {
+ Ext.destroy(columnDragProxy.anim);
+ }
+
+ delete columnDragProxy.ghost;
+ delete columnDragData.ddel;
+ delete columnDragData.header;
+ columnDrag.destroy();
+
+ delete Ext.dd.DDM.locationCache[columnDrag.id];
+ delete columnDrag._domRef;
+
+ delete columnDrop.proxyTop;
+ delete columnDrop.proxyBottom;
+ columnDrop.destroy();
+ delete Ext.dd.DDM.locationCache["gridHeader" + gridEl.id];
+ delete columnDrop._domRef;
+ delete Ext.dd.DDM.ids[columnDrop.ddGroup];
+ }
+
+ if (splitZone) {
+ splitZone.destroy();
+ delete splitZone._domRef;
+ delete Ext.dd.DDM.ids["gridSplitters" + gridEl.id];
+ }
+
+ Ext.fly(me.innerHd).removeAllListeners();
+ Ext.removeNode(me.innerHd);
+ delete me.innerHd;
+
+ Ext.destroy(
+ me.el,
+ me.mainWrap,
+ me.mainHd,
+ me.scroller,
+ me.mainBody,
+ me.focusEl,
+ me.resizeMarker,
+ me.resizeProxy,
+ me.activeHdBtn,
+ me._flyweight,
+ dragZone,
+ splitZone
+ );
+
+ delete grid.container;
+
+ if (dragZone) {
+ dragZone.destroy();
+ }
+
+ Ext.dd.DDM.currentTarget = null;
+ delete Ext.dd.DDM.locationCache[gridEl.id];
+
+ Ext.EventManager.removeResizeListener(me.onWindowResize, me);
+ },
+
+
+ onDenyColumnHide : function() {
+
+ },
+
+
+ render : function() {
+ if (this.autoFill) {
+ var ct = this.grid.ownerCt;
+
+ if (ct && ct.getLayout()) {
+ ct.on('afterlayout', function() {
+ this.fitColumns(true, true);
+ this.updateHeaders();
+ this.updateHeaderSortState();
+ }, this, {single: true});
+ }
+ } else if (this.forceFit) {
+ this.fitColumns(true, false);
+ } else if (this.grid.autoExpandColumn) {
+ this.autoExpand(true);
+ }
+
+ this.grid.getGridEl().dom.innerHTML = this.renderUI();
+
+ this.afterRenderUI();
+ },
+
+
+
+
+ initData : function(newStore, newColModel) {
+ var me = this;
+
+ if (me.ds) {
+ var oldStore = me.ds;
+
+ oldStore.un('add', me.onAdd, me);
+ oldStore.un('load', me.onLoad, me);
+ oldStore.un('clear', me.onClear, me);
+ oldStore.un('remove', me.onRemove, me);
+ oldStore.un('update', me.onUpdate, me);
+ oldStore.un('datachanged', me.onDataChange, me);
+
+ if (oldStore !== newStore && oldStore.autoDestroy) {
+ oldStore.destroy();
+ }
+ }
+
+ if (newStore) {
+ newStore.on({
+ scope : me,
+ load : me.onLoad,
+ add : me.onAdd,
+ remove : me.onRemove,
+ update : me.onUpdate,
+ clear : me.onClear,
+ datachanged: me.onDataChange
+ });
+ }
+
+ if (me.cm) {
+ var oldColModel = me.cm;
+
+ oldColModel.un('configchange', me.onColConfigChange, me);
+ oldColModel.un('widthchange', me.onColWidthChange, me);
+ oldColModel.un('headerchange', me.onHeaderChange, me);
+ oldColModel.un('hiddenchange', me.onHiddenChange, me);
+ oldColModel.un('columnmoved', me.onColumnMove, me);
+ }
+
+ if (newColModel) {
+ delete me.lastViewWidth;
+
+ newColModel.on({
+ scope : me,
+ configchange: me.onColConfigChange,
+ widthchange : me.onColWidthChange,
+ headerchange: me.onHeaderChange,
+ hiddenchange: me.onHiddenChange,
+ columnmoved : me.onColumnMove
+ });
+ }
+
+ me.ds = newStore;
+ me.cm = newColModel;
+ },
+
+
+ onDataChange : function(){
+ this.refresh(true);
+ this.updateHeaderSortState();
+ this.syncFocusEl(0);
+ },
+
+
+ onClear : function() {
+ this.refresh();
+ this.syncFocusEl(0);
+ },
+
+
+ onUpdate : function(store, record) {
+ this.refreshRow(record);
+ },
+
+
+ onAdd : function(store, records, index) {
+ this.insertRows(store, index, index + (records.length-1));
+ },
+
+
+ onRemove : function(store, record, index, isUpdate) {
+ if (isUpdate !== true) {
+ this.fireEvent('beforerowremoved', this, index, record);
+ }
+
+ this.removeRow(index);
+
+ if (isUpdate !== true) {
+ this.processRows(index);
+ this.applyEmptyText();
+ this.fireEvent('rowremoved', this, index, record);
+ }
+ },
+
+
+ onLoad : function() {
+ if (Ext.isGecko) {
+ if (!this.scrollToTopTask) {
+ this.scrollToTopTask = new Ext.util.DelayedTask(this.scrollToTop, this);
+ }
+ this.scrollToTopTask.delay(1);
+ } else {
+ this.scrollToTop();
+ }
+ },
+
+
+ onColWidthChange : function(cm, col, width) {
+ this.updateColumnWidth(col, width);
+ },
+
+
+ onHeaderChange : function(cm, col, text) {
+ this.updateHeaders();
+ },
+
+
+ onHiddenChange : function(cm, col, hidden) {
+ this.updateColumnHidden(col, hidden);
+ },
+
+
+ onColumnMove : function(cm, oldIndex, newIndex) {
+ this.indexMap = null;
+ this.refresh(true);
+ this.restoreScroll(this.getScrollState());
+
+ this.afterMove(newIndex);
+ this.grid.fireEvent('columnmove', oldIndex, newIndex);
+ },
+
+
+ onColConfigChange : function() {
+ delete this.lastViewWidth;
+ this.indexMap = null;
+ this.refresh(true);
+ },
+
+
+
+ initUI : function(grid) {
+ grid.on('headerclick', this.onHeaderClick, this);
+ },
+
+
+ initEvents : Ext.emptyFn,
+
+
+ onHeaderClick : function(g, index) {
+ if (this.headersDisabled || !this.cm.isSortable(index)) {
+ return;
+ }
+ g.stopEditing(true);
+ g.store.sort(this.cm.getDataIndex(index));
+ },
+
+
+ onRowOver : function(e, target) {
+ var row = this.findRowIndex(target);
+
+ if (row !== false) {
+ this.addRowClass(row, this.rowOverCls);
+ }
+ },
+
+
+ onRowOut : function(e, target) {
+ var row = this.findRowIndex(target);
+
+ if (row !== false && !e.within(this.getRow(row), true)) {
+ this.removeRowClass(row, this.rowOverCls);
+ }
+ },
+
+
+ onRowSelect : function(row) {
+ this.addRowClass(row, this.selectedRowClass);
+ },
+
+
+ onRowDeselect : function(row) {
+ this.removeRowClass(row, this.selectedRowClass);
+ },
+
+
+ onCellSelect : function(row, col) {
+ var cell = this.getCell(row, col);
+ if (cell) {
+ this.fly(cell).addClass('x-grid3-cell-selected');
+ }
+ },
+
+
+ onCellDeselect : function(row, col) {
+ var cell = this.getCell(row, col);
+ if (cell) {
+ this.fly(cell).removeClass('x-grid3-cell-selected');
+ }
+ },
+
+
+ handleWheel : function(e) {
+ e.stopPropagation();
+ },
+
+
+ onColumnSplitterMoved : function(cellIndex, width) {
+ this.userResized = true;
+ this.grid.colModel.setColumnWidth(cellIndex, width, true);
+
+ if (this.forceFit) {
+ this.fitColumns(true, false, cellIndex);
+ this.updateAllColumnWidths();
+ } else {
+ this.updateColumnWidth(cellIndex, width);
+ this.syncHeaderScroll();
+ }
+
+ this.grid.fireEvent('columnresize', cellIndex, width);
+ },
+
+
+ beforeColMenuShow : function() {
+ var colModel = this.cm,
+ colCount = colModel.getColumnCount(),
+ colMenu = this.colMenu,
+ i;
+
+ colMenu.removeAll();
+
+ for (i = 0; i < colCount; i++) {
+ if (colModel.config[i].hideable !== false) {
+ colMenu.add(new Ext.menu.CheckItem({
+ text : colModel.getColumnHeader(i),
+ itemId : 'col-' + colModel.getColumnId(i),
+ checked : !colModel.isHidden(i),
+ disabled : colModel.config[i].hideable === false,
+ hideOnClick: false
+ }));
+ }
+ }
+ },
+
+
+ handleHdMenuClick : function(item) {
+ var store = this.ds,
+ dataIndex = this.cm.getDataIndex(this.hdCtxIndex);
+
+ switch (item.getItemId()) {
+ case 'asc':
+ store.sort(dataIndex, 'ASC');
+ break;
+ case 'desc':
+ store.sort(dataIndex, 'DESC');
+ break;
+ default:
+ this.handleHdMenuClickDefault(item);
+ }
+ return true;
+ },
+
+
+ handleHdMenuClickDefault: function(item) {
+ var colModel = this.cm,
+ itemId = item.getItemId(),
+ index = colModel.getIndexById(itemId.substr(4));
+
+ if (index != -1) {
+ if (item.checked && colModel.getColumnsBy(this.isHideableColumn, this).length <= 1) {
+ this.onDenyColumnHide();
+ return;
+ }
+ colModel.setHidden(index, item.checked);
+ }
+ },
+
+
+ handleHdDown : function(e, target) {
+ if (Ext.fly(target).hasClass('x-grid3-hd-btn')) {
+ e.stopEvent();
+
+ var colModel = this.cm,
+ header = this.findHeaderCell(target),
+ index = this.getCellIndex(header),
+ sortable = colModel.isSortable(index),
+ menu = this.hmenu,
+ menuItems = menu.items,
+ menuCls = this.headerMenuOpenCls,
+ sep;
+
+ this.hdCtxIndex = index;
+
+ Ext.fly(header).addClass(menuCls);
+ if (this.hideSortIcons) {
+ menuItems.get('asc').setVisible(sortable);
+ menuItems.get('desc').setVisible(sortable);
+ sep = menuItems.get('sortSep');
+ if (sep) {
+ sep.setVisible(sortable);
+ }
+ } else {
+ menuItems.get('asc').setDisabled(!sortable);
+ menuItems.get('desc').setDisabled(!sortable);
+ }
+
+ menu.on('hide', function() {
+ Ext.fly(header).removeClass(menuCls);
+ }, this, {single:true});
+
+ menu.show(target, 'tl-bl?');
+ }
+ },
+
+
+ handleHdMove : function(e) {
+ var header = this.findHeaderCell(this.activeHdRef);
+
+ if (header && !this.headersDisabled) {
+ var handleWidth = this.splitHandleWidth || 5,
+ activeRegion = this.activeHdRegion,
+ headerStyle = header.style,
+ colModel = this.cm,
+ cursor = '',
+ pageX = e.getPageX();
+
+ if (this.grid.enableColumnResize !== false) {
+ var activeHeaderIndex = this.activeHdIndex,
+ previousVisible = this.getPreviousVisible(activeHeaderIndex),
+ currentResizable = colModel.isResizable(activeHeaderIndex),
+ previousResizable = previousVisible && colModel.isResizable(previousVisible),
+ inLeftResizer = pageX - activeRegion.left <= handleWidth,
+ inRightResizer = activeRegion.right - pageX <= (!this.activeHdBtn ? handleWidth : 2);
+
+ if (inLeftResizer && previousResizable) {
+ cursor = Ext.isAir ? 'move' : Ext.isWebKit ? 'e-resize' : 'col-resize';
+ } else if (inRightResizer && currentResizable) {
+ cursor = Ext.isAir ? 'move' : Ext.isWebKit ? 'w-resize' : 'col-resize';
+ }
+ }
+
+ headerStyle.cursor = cursor;
+ }
+ },
+
+
+ getPreviousVisible: function(index) {
+ while (index > 0) {
+ if (!this.cm.isHidden(index - 1)) {
+ return index;
+ }
+ index--;
+ }
+ return undefined;
+ },
+
+
+ handleHdOver : function(e, target) {
+ var header = this.findHeaderCell(target);
+
+ if (header && !this.headersDisabled) {
+ var fly = this.fly(header);
+
+ this.activeHdRef = target;
+ this.activeHdIndex = this.getCellIndex(header);
+ this.activeHdRegion = fly.getRegion();
+
+ if (!this.isMenuDisabled(this.activeHdIndex, fly)) {
+ fly.addClass('x-grid3-hd-over');
+ this.activeHdBtn = fly.child('.x-grid3-hd-btn');
+
+ if (this.activeHdBtn) {
+ this.activeHdBtn.dom.style.height = (header.firstChild.offsetHeight - 1) + 'px';
+ }
+ }
+ }
+ },
+
+
+ handleHdOut : function(e, target) {
+ var header = this.findHeaderCell(target);
+
+ if (header && (!Ext.isIE9m || !e.within(header, true))) {
+ this.activeHdRef = null;
+ this.fly(header).removeClass('x-grid3-hd-over');
+ header.style.cursor = '';
+ }
+ },
+
+
+ isMenuDisabled: function(cellIndex, el) {
+ return this.cm.isMenuDisabled(cellIndex);
+ },
+
+
+ hasRows : function() {
+ var fc = this.mainBody.dom.firstChild;
+ return fc && fc.nodeType == 1 && fc.className != 'x-grid-empty';
+ },
+
+
+ isHideableColumn : function(c) {
+ return !c.hidden;
+ },
+
+
+ bind : function(d, c) {
+ this.initData(d, c);
+ }
+});
+
+
+
+
+Ext.grid.GridView.SplitDragZone = Ext.extend(Ext.dd.DDProxy, {
+
+ constructor: function(grid, hd){
+ this.grid = grid;
+ this.view = grid.getView();
+ this.marker = this.view.resizeMarker;
+ this.proxy = this.view.resizeProxy;
+ Ext.grid.GridView.SplitDragZone.superclass.constructor.call(this, hd,
+ 'gridSplitters' + this.grid.getGridEl().id, {
+ dragElId : Ext.id(this.proxy.dom), resizeFrame:false
+ });
+ this.scroll = false;
+ this.hw = this.view.splitHandleWidth || 5;
+ },
+
+ b4StartDrag : function(x, y){
+ this.dragHeadersDisabled = this.view.headersDisabled;
+ this.view.headersDisabled = true;
+ var h = this.view.mainWrap.getHeight();
+ this.marker.setHeight(h);
+ this.marker.show();
+ this.marker.alignTo(this.view.getHeaderCell(this.cellIndex), 'tl-tl', [-2, 0]);
+ this.proxy.setHeight(h);
+ var w = this.cm.getColumnWidth(this.cellIndex),
+ minw = Math.max(w-this.grid.minColumnWidth, 0);
+ this.resetConstraints();
+ this.setXConstraint(minw, 1000);
+ this.setYConstraint(0, 0);
+ this.minX = x - minw;
+ this.maxX = x + 1000;
+ this.startPos = x;
+ Ext.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
+ },
+
+ allowHeaderDrag : function(e){
+ return true;
+ },
+
+ handleMouseDown : function(e){
+ var t = this.view.findHeaderCell(e.getTarget());
+ if(t && this.allowHeaderDrag(e)){
+ var xy = this.view.fly(t).getXY(),
+ x = xy[0],
+ exy = e.getXY(),
+ ex = exy[0],
+ w = t.offsetWidth,
+ adjust = false;
+
+ if((ex - x) <= this.hw){
+ adjust = -1;
+ }else if((x+w) - ex <= this.hw){
+ adjust = 0;
+ }
+ if(adjust !== false){
+ this.cm = this.grid.colModel;
+ var ci = this.view.getCellIndex(t);
+ if(adjust == -1){
+ if (ci + adjust < 0) {
+ return;
+ }
+ while(this.cm.isHidden(ci+adjust)){
+ --adjust;
+ if(ci+adjust < 0){
+ return;
+ }
+ }
+ }
+ this.cellIndex = ci+adjust;
+ this.split = t.dom;
+ if(this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)){
+ Ext.grid.GridView.SplitDragZone.superclass.handleMouseDown.apply(this, arguments);
+ }
+ }else if(this.view.columnDrag){
+ this.view.columnDrag.callHandleMouseDown(e);
+ }
+ }
+ },
+
+ endDrag : function(e){
+ this.marker.hide();
+ var v = this.view,
+ endX = Math.max(this.minX, e.getPageX()),
+ diff = endX - this.startPos,
+ disabled = this.dragHeadersDisabled;
+
+ v.onColumnSplitterMoved(this.cellIndex, this.cm.getColumnWidth(this.cellIndex)+diff);
+ setTimeout(function(){
+ v.headersDisabled = disabled;
+ }, 50);
+ },
+
+ autoOffset : function(){
+ this.setDelta(0,0);
+ }
+});
+
+Ext.grid.PivotGridView = Ext.extend(Ext.grid.GridView, {
+
+
+ colHeaderCellCls: 'grid-hd-group-cell',
+
+
+ title: '',
+
+
+
+
+ getColumnHeaders: function() {
+ return this.grid.topAxis.buildHeaders();;
+ },
+
+
+ getRowHeaders: function() {
+ return this.grid.leftAxis.buildHeaders();
+ },
+
+
+ renderRows : function(startRow, endRow) {
+ var grid = this.grid,
+ rows = grid.extractData(),
+ rowCount = rows.length,
+ templates = this.templates,
+ renderer = grid.renderer,
+ hasRenderer = typeof renderer == 'function',
+ getCellCls = this.getCellCls,
+ hasGetCellCls = typeof getCellCls == 'function',
+ cellTemplate = templates.cell,
+ rowTemplate = templates.row,
+ rowBuffer = [],
+ meta = {},
+ tstyle = 'width:' + this.getGridInnerWidth() + 'px;',
+ colBuffer, colCount, column, i, row;
+
+ startRow = startRow || 0;
+ endRow = Ext.isDefined(endRow) ? endRow : rowCount - 1;
+
+ for (i = 0; i < rowCount; i++) {
+ row = rows[i];
+ colCount = row.length;
+ colBuffer = [];
+
+
+ for (var j = 0; j < colCount; j++) {
+
+ meta.id = i + '-' + j;
+ meta.css = j === 0 ? 'x-grid3-cell-first ' : (j == (colCount - 1) ? 'x-grid3-cell-last ' : '');
+ meta.attr = meta.cellAttr = '';
+ meta.value = row[j];
+
+ if (Ext.isEmpty(meta.value)) {
+ meta.value = '&#160;';
+ }
+
+ if (hasRenderer) {
+ meta.value = renderer(meta.value);
+ }
+
+ if (hasGetCellCls) {
+ meta.css += getCellCls(meta.value) + ' ';
+ }
+
+ colBuffer[colBuffer.length] = cellTemplate.apply(meta);
+ }
+
+ rowBuffer[rowBuffer.length] = rowTemplate.apply({
+ tstyle: tstyle,
+ cols : colCount,
+ cells : colBuffer.join(""),
+ alt : ''
+ });
+ }
+
+ return rowBuffer.join("");
+ },
+
+
+ masterTpl: new Ext.Template(
+ '<div class="x-grid3 x-pivotgrid" hidefocus="true">',
+ '<div class="x-grid3-viewport">',
+ '<div class="x-grid3-header">',
+ '<div class="x-grid3-header-title"><span>{title}</span></div>',
+ '<div class="x-grid3-header-inner">',
+ '<div class="x-grid3-header-offset" style="{ostyle}"></div>',
+ '</div>',
+ '<div class="x-clear"></div>',
+ '</div>',
+ '<div class="x-grid3-scroller">',
+ '<div class="x-grid3-row-headers"></div>',
+ '<div class="x-grid3-body" style="{bstyle}">{body}</div>',
+ '<a href="#" class="x-grid3-focus" tabIndex="-1"></a>',
+ '</div>',
+ '</div>',
+ '<div class="x-grid3-resize-marker">&#160;</div>',
+ '<div class="x-grid3-resize-proxy">&#160;</div>',
+ '</div>'
+ ),
+
+
+ initTemplates: function() {
+ Ext.grid.PivotGridView.superclass.initTemplates.apply(this, arguments);
+
+ var templates = this.templates || {};
+ if (!templates.gcell) {
+ templates.gcell = new Ext.XTemplate(
+ '<td class="x-grid3-hd x-grid3-gcell x-grid3-td-{id} ux-grid-hd-group-row-{row} ' + this.colHeaderCellCls + '" style="{style}">',
+ '<div {tooltip} class="x-grid3-hd-inner x-grid3-hd-{id} x-unselectable" unselectable="on" style="{istyle}">',
+ this.grid.enableHdMenu ? '<a class="x-grid3-hd-btn" href="#"></a>' : '', '{value}',
+ '</div>',
+ '</td>'
+ );
+ }
+
+ this.templates = templates;
+ this.hrowRe = new RegExp("ux-grid-hd-group-row-(\\d+)", "");
+ },
+
+
+ initElements: function() {
+ Ext.grid.PivotGridView.superclass.initElements.apply(this, arguments);
+
+
+ this.rowHeadersEl = new Ext.Element(this.scroller.child('div.x-grid3-row-headers'));
+
+
+ this.headerTitleEl = new Ext.Element(this.mainHd.child('div.x-grid3-header-title'));
+ },
+
+
+ getGridInnerWidth: function() {
+ var previousWidth = Ext.grid.PivotGridView.superclass.getGridInnerWidth.apply(this, arguments);
+
+ return previousWidth - this.getTotalRowHeaderWidth();
+ },
+
+
+ getTotalRowHeaderWidth: function() {
+ var headers = this.getRowHeaders(),
+ length = headers.length,
+ total = 0,
+ i;
+
+ for (i = 0; i< length; i++) {
+ total += headers[i].width;
+ }
+
+ return total;
+ },
+
+
+ getTotalColumnHeaderHeight: function() {
+ return this.getColumnHeaders().length * 21;
+ },
+
+
+ getCellIndex : function(el) {
+ if (el) {
+ var match = el.className.match(this.colRe),
+ data;
+
+ if (match && (data = match[1])) {
+ return parseInt(data.split('-')[1], 10);
+ }
+ }
+ return false;
+ },
+
+
+
+ renderUI : function() {
+ var templates = this.templates,
+ innerWidth = this.getGridInnerWidth();
+
+ return templates.master.apply({
+ body : templates.body.apply({rows:'&#160;'}),
+ ostyle: 'width:' + innerWidth + 'px',
+ bstyle: 'width:' + innerWidth + 'px'
+ });
+ },
+
+
+ onLayout: function(width, height) {
+ Ext.grid.PivotGridView.superclass.onLayout.apply(this, arguments);
+
+ var width = this.getGridInnerWidth();
+
+ this.resizeColumnHeaders(width);
+ this.resizeAllRows(width);
+ },
+
+
+ refresh : function(headersToo) {
+ this.fireEvent('beforerefresh', this);
+ this.grid.stopEditing(true);
+
+ var result = this.renderBody();
+ this.mainBody.update(result).setWidth(this.getGridInnerWidth());
+ if (headersToo === true) {
+ this.updateHeaders();
+ this.updateHeaderSortState();
+ }
+ this.processRows(0, true);
+ this.layout();
+ this.applyEmptyText();
+ this.fireEvent('refresh', this);
+ },
+
+
+ renderHeaders: Ext.emptyFn,
+
+
+ fitColumns: Ext.emptyFn,
+
+
+ resizeColumnHeaders: function(width) {
+ var topAxis = this.grid.topAxis;
+
+ if (topAxis.rendered) {
+ topAxis.el.setWidth(width);
+ }
+ },
+
+
+ resizeRowHeaders: function() {
+ var rowHeaderWidth = this.getTotalRowHeaderWidth(),
+ marginStyle = String.format("margin-left: {0}px;", rowHeaderWidth);
+
+ this.rowHeadersEl.setWidth(rowHeaderWidth);
+ this.mainBody.applyStyles(marginStyle);
+ Ext.fly(this.innerHd).applyStyles(marginStyle);
+
+ this.headerTitleEl.setWidth(rowHeaderWidth);
+ this.headerTitleEl.setHeight(this.getTotalColumnHeaderHeight());
+ },
+
+
+ resizeAllRows: function(width) {
+ var rows = this.getRows(),
+ length = rows.length,
+ i;
+
+ for (i = 0; i < length; i++) {
+ Ext.fly(rows[i]).setWidth(width);
+ Ext.fly(rows[i]).child('table').setWidth(width);
+ }
+ },
+
+
+ updateHeaders: function() {
+ this.renderGroupRowHeaders();
+ this.renderGroupColumnHeaders();
+ },
+
+
+ renderGroupRowHeaders: function() {
+ var leftAxis = this.grid.leftAxis;
+
+ this.resizeRowHeaders();
+ leftAxis.rendered = false;
+ leftAxis.render(this.rowHeadersEl);
+
+ this.setTitle(this.title);
+ },
+
+
+ setTitle: function(title) {
+ this.headerTitleEl.child('span').dom.innerHTML = title;
+ },
+
+
+ renderGroupColumnHeaders: function() {
+ var topAxis = this.grid.topAxis;
+
+ topAxis.rendered = false;
+ topAxis.render(this.innerHd.firstChild);
+ },
+
+
+ isMenuDisabled: function(cellIndex, el) {
+ return true;
+ }
+});
+Ext.grid.PivotAxis = Ext.extend(Ext.Component, {
+
+ orientation: 'horizontal',
+
+
+ defaultHeaderWidth: 80,
+
+
+ paddingWidth: 7,
+
+
+ setDimensions: function(dimensions) {
+ this.dimensions = dimensions;
+ },
+
+
+ onRender: function(ct, position) {
+ var rows = this.orientation == 'horizontal'
+ ? this.renderHorizontalRows()
+ : this.renderVerticalRows();
+
+ this.el = Ext.DomHelper.overwrite(ct.dom, {tag: 'table', cn: rows}, true);
+ },
+
+
+ renderHorizontalRows: function() {
+ var headers = this.buildHeaders(),
+ rowCount = headers.length,
+ rows = [],
+ cells, cols, colCount, i, j;
+
+ for (i = 0; i < rowCount; i++) {
+ cells = [];
+ cols = headers[i].items;
+ colCount = cols.length;
+
+ for (j = 0; j < colCount; j++) {
+ cells.push({
+ tag: 'td',
+ html: cols[j].header,
+ colspan: cols[j].span
+ });
+ }
+
+ rows[i] = {
+ tag: 'tr',
+ cn: cells
+ };
+ }
+
+ return rows;
+ },
+
+
+ renderVerticalRows: function() {
+ var headers = this.buildHeaders(),
+ colCount = headers.length,
+ rowCells = [],
+ rows = [],
+ rowCount, col, row, colWidth, i, j;
+
+ for (i = 0; i < colCount; i++) {
+ col = headers[i];
+ colWidth = col.width || 80;
+ rowCount = col.items.length;
+
+ for (j = 0; j < rowCount; j++) {
+ row = col.items[j];
+
+ rowCells[row.start] = rowCells[row.start] || [];
+ rowCells[row.start].push({
+ tag : 'td',
+ html : row.header,
+ rowspan: row.span,
+ width : Ext.isBorderBox ? colWidth : colWidth - this.paddingWidth
+ });
+ }
+ }
+
+ rowCount = rowCells.length;
+ for (i = 0; i < rowCount; i++) {
+ rows[i] = {
+ tag: 'tr',
+ cn : rowCells[i]
+ };
+ }
+
+ return rows;
+ },
+
+
+ getTuples: function() {
+ var newStore = new Ext.data.Store({});
+
+ newStore.data = this.store.data.clone();
+ newStore.fields = this.store.fields;
+
+ var sorters = [],
+ dimensions = this.dimensions,
+ length = dimensions.length,
+ i;
+
+ for (i = 0; i < length; i++) {
+ sorters.push({
+ field : dimensions[i].dataIndex,
+ direction: dimensions[i].direction || 'ASC'
+ });
+ }
+
+ newStore.sort(sorters);
+
+ var records = newStore.data.items,
+ hashes = [],
+ tuples = [],
+ recData, hash, info, data, key;
+
+ length = records.length;
+
+ for (i = 0; i < length; i++) {
+ info = this.getRecordInfo(records[i]);
+ data = info.data;
+ hash = "";
+
+ for (key in data) {
+ hash += data[key] + '---';
+ }
+
+ if (hashes.indexOf(hash) == -1) {
+ hashes.push(hash);
+ tuples.push(info);
+ }
+ }
+
+ newStore.destroy();
+
+ return tuples;
+ },
+
+
+ getRecordInfo: function(record) {
+ var dimensions = this.dimensions,
+ length = dimensions.length,
+ data = {},
+ dimension, dataIndex, i;
+
+
+ for (i = 0; i < length; i++) {
+ dimension = dimensions[i];
+ dataIndex = dimension.dataIndex;
+
+ data[dataIndex] = record.get(dataIndex);
+ }
+
+
+
+ var createMatcherFunction = function(data) {
+ return function(record) {
+ for (var dataIndex in data) {
+ if (record.get(dataIndex) != data[dataIndex]) {
+ return false;
+ }
+ }
+
+ return true;
+ };
+ };
+
+ return {
+ data: data,
+ matcher: createMatcherFunction(data)
+ };
+ },
+
+
+ buildHeaders: function() {
+ var tuples = this.getTuples(),
+ rowCount = tuples.length,
+ dimensions = this.dimensions,
+ dimension,
+ colCount = dimensions.length,
+ headers = [],
+ tuple, rows, currentHeader, previousHeader, span, start, isLast, changed, i, j;
+
+ for (i = 0; i < colCount; i++) {
+ dimension = dimensions[i];
+ rows = [];
+ span = 0;
+ start = 0;
+
+ for (j = 0; j < rowCount; j++) {
+ tuple = tuples[j];
+ isLast = j == (rowCount - 1);
+ currentHeader = tuple.data[dimension.dataIndex];
+
+
+ changed = previousHeader != undefined && previousHeader != currentHeader;
+ if (i > 0 && j > 0) {
+ changed = changed || tuple.data[dimensions[i-1].dataIndex] != tuples[j-1].data[dimensions[i-1].dataIndex];
+ }
+
+ if (changed) {
+ rows.push({
+ header: previousHeader,
+ span : span,
+ start : start
+ });
+
+ start += span;
+ span = 0;
+ }
+
+ if (isLast) {
+ rows.push({
+ header: currentHeader,
+ span : span + 1,
+ start : start
+ });
+
+ start += span;
+ span = 0;
+ }
+
+ previousHeader = currentHeader;
+ span++;
+ }
+
+ headers.push({
+ items: rows,
+ width: dimension.width || this.defaultHeaderWidth
+ });
+
+ previousHeader = undefined;
+ }
+
+ return headers;
+ }
+});
+
+
+Ext.grid.HeaderDragZone = Ext.extend(Ext.dd.DragZone, {
+ maxDragWidth: 120,
+
+ constructor : function(grid, hd, hd2){
+ this.grid = grid;
+ this.view = grid.getView();
+ this.ddGroup = "gridHeader" + this.grid.getGridEl().id;
+ Ext.grid.HeaderDragZone.superclass.constructor.call(this, hd);
+ if(hd2){
+ this.setHandleElId(Ext.id(hd));
+ this.setOuterHandleElId(Ext.id(hd2));
+ }
+ this.scroll = false;
+ },
+
+ getDragData : function(e){
+ var t = Ext.lib.Event.getTarget(e),
+ h = this.view.findHeaderCell(t);
+ if(h){
+ return {ddel: h.firstChild, header:h};
+ }
+ return false;
+ },
+
+ onInitDrag : function(e){
+
+ this.dragHeadersDisabled = this.view.headersDisabled;
+ this.view.headersDisabled = true;
+ var clone = this.dragData.ddel.cloneNode(true);
+ clone.id = Ext.id();
+ clone.style.width = Math.min(this.dragData.header.offsetWidth,this.maxDragWidth) + "px";
+ this.proxy.update(clone);
+ return true;
+ },
+
+ afterValidDrop : function(){
+ this.completeDrop();
+ },
+
+ afterInvalidDrop : function(){
+ this.completeDrop();
+ },
+
+ completeDrop: function(){
+ var v = this.view,
+ disabled = this.dragHeadersDisabled;
+ setTimeout(function(){
+ v.headersDisabled = disabled;
+ }, 50);
+ }
+});
+
+
+
+Ext.grid.HeaderDropZone = Ext.extend(Ext.dd.DropZone, {
+ proxyOffsets : [-4, -9],
+ fly: Ext.Element.fly,
+
+ constructor : function(grid, hd, hd2){
+ this.grid = grid;
+ this.view = grid.getView();
+
+ this.proxyTop = Ext.DomHelper.append(document.body, {
+ cls:"col-move-top", html:"&#160;"
+ }, true);
+ this.proxyBottom = Ext.DomHelper.append(document.body, {
+ cls:"col-move-bottom", html:"&#160;"
+ }, true);
+ this.proxyTop.hide = this.proxyBottom.hide = function(){
+ this.setLeftTop(-100,-100);
+ this.setStyle("visibility", "hidden");
+ };
+ this.ddGroup = "gridHeader" + this.grid.getGridEl().id;
+
+
+ Ext.grid.HeaderDropZone.superclass.constructor.call(this, grid.getGridEl().dom);
+ },
+
+ getTargetFromEvent : function(e){
+ var t = Ext.lib.Event.getTarget(e),
+ cindex = this.view.findCellIndex(t);
+ if(cindex !== false){
+ return this.view.getHeaderCell(cindex);
+ }
+ },
+
+ nextVisible : function(h){
+ var v = this.view, cm = this.grid.colModel;
+ h = h.nextSibling;
+ while(h){
+ if(!cm.isHidden(v.getCellIndex(h))){
+ return h;
+ }
+ h = h.nextSibling;
+ }
+ return null;
+ },
+
+ prevVisible : function(h){
+ var v = this.view, cm = this.grid.colModel;
+ h = h.prevSibling;
+ while(h){
+ if(!cm.isHidden(v.getCellIndex(h))){
+ return h;
+ }
+ h = h.prevSibling;
+ }
+ return null;
+ },
+
+ positionIndicator : function(h, n, e){
+ var x = Ext.lib.Event.getPageX(e),
+ r = Ext.lib.Dom.getRegion(n.firstChild),
+ px,
+ pt,
+ py = r.top + this.proxyOffsets[1];
+ if((r.right - x) <= (r.right-r.left)/2){
+ px = r.right+this.view.borderWidth;
+ pt = "after";
+ }else{
+ px = r.left;
+ pt = "before";
+ }
+
+ if(this.grid.colModel.isFixed(this.view.getCellIndex(n))){
+ return false;
+ }
+
+ px += this.proxyOffsets[0];
+ this.proxyTop.setLeftTop(px, py);
+ this.proxyTop.show();
+ if(!this.bottomOffset){
+ this.bottomOffset = this.view.mainHd.getHeight();
+ }
+ this.proxyBottom.setLeftTop(px, py+this.proxyTop.dom.offsetHeight+this.bottomOffset);
+ this.proxyBottom.show();
+ return pt;
+ },
+
+ onNodeEnter : function(n, dd, e, data){
+ if(data.header != n){
+ this.positionIndicator(data.header, n, e);
+ }
+ },
+
+ onNodeOver : function(n, dd, e, data){
+ var result = false;
+ if(data.header != n){
+ result = this.positionIndicator(data.header, n, e);
+ }
+ if(!result){
+ this.proxyTop.hide();
+ this.proxyBottom.hide();
+ }
+ return result ? this.dropAllowed : this.dropNotAllowed;
+ },
+
+ onNodeOut : function(n, dd, e, data){
+ this.proxyTop.hide();
+ this.proxyBottom.hide();
+ },
+
+ onNodeDrop : function(n, dd, e, data){
+ var h = data.header;
+ if(h != n){
+ var cm = this.grid.colModel,
+ x = Ext.lib.Event.getPageX(e),
+ r = Ext.lib.Dom.getRegion(n.firstChild),
+ pt = (r.right - x) <= ((r.right-r.left)/2) ? "after" : "before",
+ oldIndex = this.view.getCellIndex(h),
+ newIndex = this.view.getCellIndex(n);
+ if(pt == "after"){
+ newIndex++;
+ }
+ if(oldIndex < newIndex){
+ newIndex--;
+ }
+ cm.moveColumn(oldIndex, newIndex);
+ return true;
+ }
+ return false;
+ }
+});
+
+Ext.grid.GridView.ColumnDragZone = Ext.extend(Ext.grid.HeaderDragZone, {
+
+ constructor : function(grid, hd){
+ Ext.grid.GridView.ColumnDragZone.superclass.constructor.call(this, grid, hd, null);
+ this.proxy.el.addClass('x-grid3-col-dd');
+ },
+
+ handleMouseDown : function(e){
+ },
+
+ callHandleMouseDown : function(e){
+ Ext.grid.GridView.ColumnDragZone.superclass.handleMouseDown.call(this, e);
+ }
+});
+
+Ext.grid.SplitDragZone = Ext.extend(Ext.dd.DDProxy, {
+ fly: Ext.Element.fly,
+
+ constructor : function(grid, hd, hd2){
+ this.grid = grid;
+ this.view = grid.getView();
+ this.proxy = this.view.resizeProxy;
+ Ext.grid.SplitDragZone.superclass.constructor.call(this, hd,
+ "gridSplitters" + this.grid.getGridEl().id, {
+ dragElId : Ext.id(this.proxy.dom), resizeFrame:false
+ });
+ this.setHandleElId(Ext.id(hd));
+ this.setOuterHandleElId(Ext.id(hd2));
+ this.scroll = false;
+ },
+
+ b4StartDrag : function(x, y){
+ this.view.headersDisabled = true;
+ this.proxy.setHeight(this.view.mainWrap.getHeight());
+ var w = this.cm.getColumnWidth(this.cellIndex);
+ var minw = Math.max(w-this.grid.minColumnWidth, 0);
+ this.resetConstraints();
+ this.setXConstraint(minw, 1000);
+ this.setYConstraint(0, 0);
+ this.minX = x - minw;
+ this.maxX = x + 1000;
+ this.startPos = x;
+ Ext.dd.DDProxy.prototype.b4StartDrag.call(this, x, y);
+ },
+
+
+ handleMouseDown : function(e){
+ var ev = Ext.EventObject.setEvent(e);
+ var t = this.fly(ev.getTarget());
+ if(t.hasClass("x-grid-split")){
+ this.cellIndex = this.view.getCellIndex(t.dom);
+ this.split = t.dom;
+ this.cm = this.grid.colModel;
+ if(this.cm.isResizable(this.cellIndex) && !this.cm.isFixed(this.cellIndex)){
+ Ext.grid.SplitDragZone.superclass.handleMouseDown.apply(this, arguments);
+ }
+ }
+ },
+
+ endDrag : function(e){
+ this.view.headersDisabled = false;
+ var endX = Math.max(this.minX, Ext.lib.Event.getPageX(e));
+ var diff = endX - this.startPos;
+ this.view.onColumnSplitterMoved(this.cellIndex, this.cm.getColumnWidth(this.cellIndex)+diff);
+ },
+
+ autoOffset : function(){
+ this.setDelta(0,0);
+ }
+});
+Ext.grid.GridDragZone = function(grid, config){
+ this.view = grid.getView();
+ Ext.grid.GridDragZone.superclass.constructor.call(this, this.view.mainBody.dom, config);
+ this.scroll = false;
+ this.grid = grid;
+ this.ddel = document.createElement('div');
+ this.ddel.className = 'x-grid-dd-wrap';
+
+ this.preventDefault = true;
+};
+
+Ext.extend(Ext.grid.GridDragZone, Ext.dd.DragZone, {
+ ddGroup : "GridDD",
+
+
+ getDragData : function(e){
+ var t = Ext.lib.Event.getTarget(e),
+ sm,
+ rowIndex = this.view.findRowIndex(t),
+ cellIndex,
+ selectedCell,
+ selection;
+
+ if (rowIndex !== false){
+ sm = this.grid.selModel;
+
+
+
+ if (sm.getSelectedCell) {
+ cellIndex = this.view.findCellIndex(t);
+ selectedCell = sm.getSelectedCell();
+ if (!selectedCell || selectedCell[0] !== rowIndex || selectedCell[1] !== cellIndex) {
+ sm.handleMouseDown(this.grid, rowIndex, cellIndex, e);
+ }
+ if (this.grid.dragCell) {
+
+ selection = sm.getSelectedCell();
+ if (!this.grid.hasOwnProperty('ddText')) {
+ this.grid.ddText = '{0} selected cell{1}';
+ }
+ } else {
+
+ selection = [this.grid.store.getAt(rowIndex)];
+ }
+ } else {
+ if(!sm.isSelected(rowIndex) || e.hasModifier()){
+ sm.handleMouseDown(this.grid, rowIndex, e);
+ }
+ selection = sm.getSelections();
+ }
+ return {grid: this.grid, ddel: this.ddel, rowIndex: rowIndex, selections: selection};
+ }
+ return false;
+ },
+
+
+ onInitDrag : function(e){
+ var data = this.dragData;
+ this.ddel.innerHTML = this.grid.getDragDropText();
+ this.proxy.update(this.ddel);
+
+ },
+
+
+ afterRepair : function(){
+ this.dragging = false;
+ },
+
+
+ getRepairXY : function(e, data){
+ return false;
+ },
+
+ onEndDrag : function(data, e){
+
+ },
+
+ onValidDrop : function(dd, e, id){
+
+ this.hideProxy();
+ },
+
+ beforeInvalidDrop : function(e, id){
+
+ }
+});
+
+Ext.grid.ColumnModel = Ext.extend(Ext.util.Observable, {
+
+ defaultWidth: 100,
+
+
+ defaultSortable: false,
+
+
+
+
+
+ constructor : function(config) {
+
+ if (config.columns) {
+ Ext.apply(this, config);
+ this.setConfig(config.columns, true);
+ } else {
+ this.setConfig(config, true);
+ }
+
+ this.addEvents(
+
+ "widthchange",
+
+
+ "headerchange",
+
+
+ "hiddenchange",
+
+
+ "columnmoved",
+
+
+ "configchange"
+ );
+
+ Ext.grid.ColumnModel.superclass.constructor.call(this);
+ },
+
+
+ getColumnId : function(index) {
+ return this.config[index].id;
+ },
+
+ getColumnAt : function(index) {
+ return this.config[index];
+ },
+
+
+ setConfig : function(config, initial) {
+ var i, c, len;
+
+ if (!initial) {
+ delete this.totalWidth;
+
+ for (i = 0, len = this.config.length; i < len; i++) {
+ c = this.config[i];
+
+ if (c.setEditor) {
+
+ c.setEditor(null);
+ }
+ }
+ }
+
+
+ this.defaults = Ext.apply({
+ width: this.defaultWidth,
+ sortable: this.defaultSortable
+ }, this.defaults);
+
+ this.config = config;
+ this.lookup = {};
+
+ for (i = 0, len = config.length; i < len; i++) {
+ c = Ext.applyIf(config[i], this.defaults);
+
+
+ if (Ext.isEmpty(c.id)) {
+ c.id = i;
+ }
+
+ if (!c.isColumn) {
+ var Cls = Ext.grid.Column.types[c.xtype || 'gridcolumn'];
+ c = new Cls(c);
+ config[i] = c;
+ }
+
+ this.lookup[c.id] = c;
+ }
+
+ if (!initial) {
+ this.fireEvent('configchange', this);
+ }
+ },
+
+
+ getColumnById : function(id) {
+ return this.lookup[id];
+ },
+
+
+ getIndexById : function(id) {
+ for (var i = 0, len = this.config.length; i < len; i++) {
+ if (this.config[i].id == id) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+
+ moveColumn : function(oldIndex, newIndex) {
+ var config = this.config,
+ c = config[oldIndex];
+
+ config.splice(oldIndex, 1);
+ config.splice(newIndex, 0, c);
+ this.dataMap = null;
+ this.fireEvent("columnmoved", this, oldIndex, newIndex);
+ },
+
+
+ getColumnCount : function(visibleOnly) {
+ var length = this.config.length,
+ c = 0,
+ i;
+
+ if (visibleOnly === true) {
+ for (i = 0; i < length; i++) {
+ if (!this.isHidden(i)) {
+ c++;
+ }
+ }
+
+ return c;
+ }
+
+ return length;
+ },
+
+
+ getColumnsBy : function(fn, scope) {
+ var config = this.config,
+ length = config.length,
+ result = [],
+ i, c;
+
+ for (i = 0; i < length; i++){
+ c = config[i];
+
+ if (fn.call(scope || this, c, i) === true) {
+ result[result.length] = c;
+ }
+ }
+
+ return result;
+ },
+
+
+ isSortable : function(col) {
+ return !!this.config[col].sortable;
+ },
+
+
+ isMenuDisabled : function(col) {
+ return !!this.config[col].menuDisabled;
+ },
+
+
+ getRenderer : function(col) {
+ return this.config[col].renderer || Ext.grid.ColumnModel.defaultRenderer;
+ },
+
+ getRendererScope : function(col) {
+ return this.config[col].scope;
+ },
+
+
+ setRenderer : function(col, fn) {
+ this.config[col].renderer = fn;
+ },
+
+
+ getColumnWidth : function(col) {
+ var width = this.config[col].width;
+ if(typeof width != 'number'){
+ width = this.defaultWidth;
+ }
+ return width;
+ },
+
+
+ setColumnWidth : function(col, width, suppressEvent) {
+ this.config[col].width = width;
+ this.totalWidth = null;
+
+ if (!suppressEvent) {
+ this.fireEvent("widthchange", this, col, width);
+ }
+ },
+
+
+ getTotalWidth : function(includeHidden) {
+ if (!this.totalWidth) {
+ this.totalWidth = 0;
+ for (var i = 0, len = this.config.length; i < len; i++) {
+ if (includeHidden || !this.isHidden(i)) {
+ this.totalWidth += this.getColumnWidth(i);
+ }
+ }
+ }
+ return this.totalWidth;
+ },
+
+
+ getColumnHeader : function(col) {
+ return this.config[col].header;
+ },
+
+
+ setColumnHeader : function(col, header) {
+ this.config[col].header = header;
+ this.fireEvent("headerchange", this, col, header);
+ },
+
+
+ getColumnTooltip : function(col) {
+ return this.config[col].tooltip;
+ },
+
+ setColumnTooltip : function(col, tooltip) {
+ this.config[col].tooltip = tooltip;
+ },
+
+
+ getDataIndex : function(col) {
+ return this.config[col].dataIndex;
+ },
+
+
+ setDataIndex : function(col, dataIndex) {
+ this.config[col].dataIndex = dataIndex;
+ },
+
+
+ findColumnIndex : function(dataIndex) {
+ var c = this.config;
+ for(var i = 0, len = c.length; i < len; i++){
+ if(c[i].dataIndex == dataIndex){
+ return i;
+ }
+ }
+ return -1;
+ },
+
+
+ isCellEditable : function(colIndex, rowIndex) {
+ var c = this.config[colIndex],
+ ed = c.editable;
+
+
+ return !!(ed || (!Ext.isDefined(ed) && c.editor));
+ },
+
+
+ getCellEditor : function(colIndex, rowIndex) {
+ return this.config[colIndex].getCellEditor(rowIndex);
+ },
+
+
+ setEditable : function(col, editable) {
+ this.config[col].editable = editable;
+ },
+
+
+ isHidden : function(colIndex) {
+ return !!this.config[colIndex].hidden;
+ },
+
+
+ isFixed : function(colIndex) {
+ return !!this.config[colIndex].fixed;
+ },
+
+
+ isResizable : function(colIndex) {
+ return colIndex >= 0 && this.config[colIndex].resizable !== false && this.config[colIndex].fixed !== true;
+ },
+
+
+ setHidden : function(colIndex, hidden) {
+ var c = this.config[colIndex];
+ if(c.hidden !== hidden){
+ c.hidden = hidden;
+ this.totalWidth = null;
+ this.fireEvent("hiddenchange", this, colIndex, hidden);
+ }
+ },
+
+
+ setEditor : function(col, editor) {
+ this.config[col].setEditor(editor);
+ },
+
+
+ destroy : function() {
+ var length = this.config.length,
+ i = 0;
+
+ for (; i < length; i++){
+ this.config[i].destroy();
+ }
+ delete this.config;
+ delete this.lookup;
+ this.purgeListeners();
+ },
+
+
+ setState : function(col, state) {
+ state = Ext.applyIf(state, this.defaults);
+ Ext.apply(this.config[col], state);
+ }
+});
+
+
+Ext.grid.ColumnModel.defaultRenderer = function(value) {
+ if (typeof value == "string" && value.length < 1) {
+ return "&#160;";
+ }
+ return value;
+};
+Ext.grid.AbstractSelectionModel = Ext.extend(Ext.util.Observable, {
+
+
+ constructor : function(){
+ this.locked = false;
+ Ext.grid.AbstractSelectionModel.superclass.constructor.call(this);
+ },
+
+
+ init : function(grid){
+ this.grid = grid;
+ if(this.lockOnInit){
+ delete this.lockOnInit;
+ this.locked = false;
+ this.lock();
+ }
+ this.initEvents();
+ },
+
+
+ lock : function(){
+ if(!this.locked){
+ this.locked = true;
+
+ var g = this.grid;
+ if(g){
+ g.getView().on({
+ scope: this,
+ beforerefresh: this.sortUnLock,
+ refresh: this.sortLock
+ });
+ }else{
+ this.lockOnInit = true;
+ }
+ }
+ },
+
+
+ sortLock : function() {
+ this.locked = true;
+ },
+
+
+ sortUnLock : function() {
+ this.locked = false;
+ },
+
+
+ unlock : function(){
+ if(this.locked){
+ this.locked = false;
+ var g = this.grid,
+ gv;
+
+
+ if(g){
+ gv = g.getView();
+ gv.un('beforerefresh', this.sortUnLock, this);
+ gv.un('refresh', this.sortLock, this);
+ }else{
+ delete this.lockOnInit;
+ }
+ }
+ },
+
+
+ isLocked : function(){
+ return this.locked;
+ },
+
+ destroy: function(){
+ this.unlock();
+ this.purgeListeners();
+ }
+});
+Ext.grid.RowSelectionModel = Ext.extend(Ext.grid.AbstractSelectionModel, {
+
+ singleSelect : false,
+
+ constructor : function(config){
+ Ext.apply(this, config);
+ this.selections = new Ext.util.MixedCollection(false, function(o){
+ return o.id;
+ });
+
+ this.last = false;
+ this.lastActive = false;
+
+ this.addEvents(
+
+ 'selectionchange',
+
+ 'beforerowselect',
+
+ 'rowselect',
+
+ 'rowdeselect'
+ );
+ Ext.grid.RowSelectionModel.superclass.constructor.call(this);
+ },
+
+
+
+ initEvents : function(){
+
+ if(!this.grid.enableDragDrop && !this.grid.enableDrag){
+ this.grid.on('rowmousedown', this.handleMouseDown, this);
+ }
+
+ this.rowNav = new Ext.KeyNav(this.grid.getGridEl(), {
+ up: this.onKeyPress,
+ down: this.onKeyPress,
+ scope: this
+ });
+
+ this.grid.getView().on({
+ scope: this,
+ refresh: this.onRefresh,
+ rowupdated: this.onRowUpdated,
+ rowremoved: this.onRemove
+ });
+ },
+
+ onKeyPress : function(e, name){
+ var up = name == 'up',
+ method = up ? 'selectPrevious' : 'selectNext',
+ add = up ? -1 : 1,
+ last;
+ if(!e.shiftKey || this.singleSelect){
+ this[method](false);
+ }else if(this.last !== false && this.lastActive !== false){
+ last = this.last;
+ this.selectRange(this.last, this.lastActive + add);
+ this.grid.getView().focusRow(this.lastActive);
+ if(last !== false){
+ this.last = last;
+ }
+ }else{
+ this.selectFirstRow();
+ }
+ },
+
+
+ onRefresh : function(){
+ var ds = this.grid.store,
+ s = this.getSelections(),
+ i = 0,
+ len = s.length,
+ index, r;
+
+ this.silent = true;
+ this.clearSelections(true);
+ for(; i < len; i++){
+ r = s[i];
+ if((index = ds.indexOfId(r.id)) != -1){
+ this.selectRow(index, true);
+ }
+ }
+ if(s.length != this.selections.getCount()){
+ this.fireEvent('selectionchange', this);
+ }
+ this.silent = false;
+ },
+
+
+ onRemove : function(v, index, r){
+ if(this.selections.remove(r) !== false){
+ this.fireEvent('selectionchange', this);
+ }
+ },
+
+
+ onRowUpdated : function(v, index, r){
+ if(this.isSelected(r)){
+ v.onRowSelect(index);
+ }
+ },
+
+
+ selectRecords : function(records, keepExisting){
+ if(!keepExisting){
+ this.clearSelections();
+ }
+ var ds = this.grid.store,
+ i = 0,
+ len = records.length;
+ for(; i < len; i++){
+ this.selectRow(ds.indexOf(records[i]), true);
+ }
+ },
+
+
+ getCount : function(){
+ return this.selections.length;
+ },
+
+
+ selectFirstRow : function(){
+ this.selectRow(0);
+ },
+
+
+ selectLastRow : function(keepExisting){
+ this.selectRow(this.grid.store.getCount() - 1, keepExisting);
+ },
+
+
+ selectNext : function(keepExisting){
+ if(this.hasNext()){
+ this.selectRow(this.last+1, keepExisting);
+ this.grid.getView().focusRow(this.last);
+ return true;
+ }
+ return false;
+ },
+
+
+ selectPrevious : function(keepExisting){
+ if(this.hasPrevious()){
+ this.selectRow(this.last-1, keepExisting);
+ this.grid.getView().focusRow(this.last);
+ return true;
+ }
+ return false;
+ },
+
+
+ hasNext : function(){
+ return this.last !== false && (this.last+1) < this.grid.store.getCount();
+ },
+
+
+ hasPrevious : function(){
+ return !!this.last;
+ },
+
+
+
+ getSelections : function(){
+ return [].concat(this.selections.items);
+ },
+
+
+ getSelected : function(){
+ return this.selections.itemAt(0);
+ },
+
+
+ each : function(fn, scope){
+ var s = this.getSelections(),
+ i = 0,
+ len = s.length;
+
+ for(; i < len; i++){
+ if(fn.call(scope || this, s[i], i) === false){
+ return false;
+ }
+ }
+ return true;
+ },
+
+
+ clearSelections : function(fast){
+ if(this.isLocked()){
+ return;
+ }
+ if(fast !== true){
+ var ds = this.grid.store,
+ s = this.selections;
+ s.each(function(r){
+ this.deselectRow(ds.indexOfId(r.id));
+ }, this);
+ s.clear();
+ }else{
+ this.selections.clear();
+ }
+ this.last = false;
+ },
+
+
+
+ selectAll : function(){
+ if(this.isLocked()){
+ return;
+ }
+ this.selections.clear();
+ for(var i = 0, len = this.grid.store.getCount(); i < len; i++){
+ this.selectRow(i, true);
+ }
+ },
+
+
+ hasSelection : function(){
+ return this.selections.length > 0;
+ },
+
+
+ isSelected : function(index){
+ var r = Ext.isNumber(index) ? this.grid.store.getAt(index) : index;
+ return (r && this.selections.key(r.id) ? true : false);
+ },
+
+
+ isIdSelected : function(id){
+ return (this.selections.key(id) ? true : false);
+ },
+
+
+ handleMouseDown : function(g, rowIndex, e){
+ if(e.button !== 0 || this.isLocked()){
+ return;
+ }
+ var view = this.grid.getView();
+ if(e.shiftKey && !this.singleSelect && this.last !== false){
+ var last = this.last;
+ this.selectRange(last, rowIndex, e.ctrlKey);
+ this.last = last;
+ view.focusRow(rowIndex);
+ }else{
+ var isSelected = this.isSelected(rowIndex);
+ if(e.ctrlKey && isSelected){
+ this.deselectRow(rowIndex);
+ }else if(!isSelected || this.getCount() > 1){
+ this.selectRow(rowIndex, e.ctrlKey || e.shiftKey);
+ view.focusRow(rowIndex);
+ }
+ }
+ },
+
+
+ selectRows : function(rows, keepExisting){
+ if(!keepExisting){
+ this.clearSelections();
+ }
+ for(var i = 0, len = rows.length; i < len; i++){
+ this.selectRow(rows[i], true);
+ }
+ },
+
+
+ selectRange : function(startRow, endRow, keepExisting){
+ var i;
+ if(this.isLocked()){
+ return;
+ }
+ if(!keepExisting){
+ this.clearSelections();
+ }
+ if(startRow <= endRow){
+ for(i = startRow; i <= endRow; i++){
+ this.selectRow(i, true);
+ }
+ }else{
+ for(i = startRow; i >= endRow; i--){
+ this.selectRow(i, true);
+ }
+ }
+ },
+
+
+ deselectRange : function(startRow, endRow, preventViewNotify){
+ if(this.isLocked()){
+ return;
+ }
+ for(var i = startRow; i <= endRow; i++){
+ this.deselectRow(i, preventViewNotify);
+ }
+ },
+
+
+ selectRow : function(index, keepExisting, preventViewNotify){
+ if(this.isLocked() || (index < 0 || index >= this.grid.store.getCount()) || (keepExisting && this.isSelected(index))){
+ return;
+ }
+ var r = this.grid.store.getAt(index);
+ if(r && this.fireEvent('beforerowselect', this, index, keepExisting, r) !== false){
+ if(!keepExisting || this.singleSelect){
+ this.clearSelections();
+ }
+ this.selections.add(r);
+ this.last = this.lastActive = index;
+ if(!preventViewNotify){
+ this.grid.getView().onRowSelect(index);
+ }
+ if(!this.silent){
+ this.fireEvent('rowselect', this, index, r);
+ this.fireEvent('selectionchange', this);
+ }
+ }
+ },
+
+
+ deselectRow : function(index, preventViewNotify){
+ if(this.isLocked()){
+ return;
+ }
+ if(this.last == index){
+ this.last = false;
+ }
+ if(this.lastActive == index){
+ this.lastActive = false;
+ }
+ var r = this.grid.store.getAt(index);
+ if(r){
+ this.selections.remove(r);
+ if(!preventViewNotify){
+ this.grid.getView().onRowDeselect(index);
+ }
+ this.fireEvent('rowdeselect', this, index, r);
+ this.fireEvent('selectionchange', this);
+ }
+ },
+
+
+ acceptsNav : function(row, col, cm){
+ return !cm.isHidden(col) && cm.isCellEditable(col, row);
+ },
+
+
+ onEditorKey : function(field, e){
+ var k = e.getKey(),
+ newCell,
+ g = this.grid,
+ last = g.lastEdit,
+ ed = g.activeEditor,
+ shift = e.shiftKey,
+ ae, last, r, c;
+
+ if(k == e.TAB){
+ e.stopEvent();
+ ed.completeEdit();
+ if(shift){
+ newCell = g.walkCells(ed.row, ed.col-1, -1, this.acceptsNav, this);
+ }else{
+ newCell = g.walkCells(ed.row, ed.col+1, 1, this.acceptsNav, this);
+ }
+ }else if(k == e.ENTER){
+ if(this.moveEditorOnEnter !== false){
+ if(shift){
+ newCell = g.walkCells(last.row - 1, last.col, -1, this.acceptsNav, this);
+ }else{
+ newCell = g.walkCells(last.row + 1, last.col, 1, this.acceptsNav, this);
+ }
+ }
+ }
+ if(newCell){
+ r = newCell[0];
+ c = newCell[1];
+
+ this.onEditorSelect(r, last.row);
+
+ if(g.isEditor && g.editing){
+ ae = g.activeEditor;
+ if(ae && ae.field.triggerBlur){
+
+ ae.field.triggerBlur();
+ }
+ }
+ g.startEditing(r, c);
+ }
+ },
+
+ onEditorSelect: function(row, lastRow){
+ if(lastRow != row){
+ this.selectRow(row);
+ }
+ },
+
+ destroy : function(){
+ Ext.destroy(this.rowNav);
+ this.rowNav = null;
+ Ext.grid.RowSelectionModel.superclass.destroy.call(this);
+ }
+});
+
+Ext.grid.Column = Ext.extend(Ext.util.Observable, {
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ isColumn : true,
+
+ constructor : function(config){
+ Ext.apply(this, config);
+
+ if(Ext.isString(this.renderer)){
+ this.renderer = Ext.util.Format[this.renderer];
+ }else if(Ext.isObject(this.renderer)){
+ this.scope = this.renderer.scope;
+ this.renderer = this.renderer.fn;
+ }
+ if(!this.scope){
+ this.scope = this;
+ }
+
+ var ed = this.editor;
+ delete this.editor;
+ this.setEditor(ed);
+ this.addEvents(
+
+ 'click',
+
+ 'contextmenu',
+
+ 'dblclick',
+
+ 'mousedown'
+ );
+ Ext.grid.Column.superclass.constructor.call(this);
+ },
+
+
+ processEvent : function(name, e, grid, rowIndex, colIndex){
+ return this.fireEvent(name, this, grid, rowIndex, e);
+ },
+
+
+ destroy: function() {
+ if(this.setEditor){
+ this.setEditor(null);
+ }
+ this.purgeListeners();
+ },
+
+
+ renderer : function(value){
+ return value;
+ },
+
+
+ getEditor: function(rowIndex){
+ return this.editable !== false ? this.editor : null;
+ },
+
+
+ setEditor : function(editor){
+ var ed = this.editor;
+ if(ed){
+ if(ed.gridEditor){
+ ed.gridEditor.destroy();
+ delete ed.gridEditor;
+ }else{
+ ed.destroy();
+ }
+ }
+ this.editor = null;
+ if(editor){
+
+ if(!editor.isXType){
+ editor = Ext.create(editor, 'textfield');
+ }
+ this.editor = editor;
+ }
+ },
+
+
+ getCellEditor: function(rowIndex){
+ var ed = this.getEditor(rowIndex);
+ if(ed){
+ if(!ed.startEdit){
+ if(!ed.gridEditor){
+ ed.gridEditor = new Ext.grid.GridEditor(ed);
+ }
+ ed = ed.gridEditor;
+ }
+ }
+ return ed;
+ }
+});
+
+
+Ext.grid.BooleanColumn = Ext.extend(Ext.grid.Column, {
+
+ trueText: 'true',
+
+ falseText: 'false',
+
+ undefinedText: '&#160;',
+
+ constructor: function(cfg){
+ Ext.grid.BooleanColumn.superclass.constructor.call(this, cfg);
+ var t = this.trueText, f = this.falseText, u = this.undefinedText;
+ this.renderer = function(v){
+ if(v === undefined){
+ return u;
+ }
+ if(!v || v === 'false'){
+ return f;
+ }
+ return t;
+ };
+ }
+});
+
+
+Ext.grid.NumberColumn = Ext.extend(Ext.grid.Column, {
+
+ format : '0,000.00',
+ constructor: function(cfg){
+ Ext.grid.NumberColumn.superclass.constructor.call(this, cfg);
+ this.renderer = Ext.util.Format.numberRenderer(this.format);
+ }
+});
+
+
+Ext.grid.DateColumn = Ext.extend(Ext.grid.Column, {
+
+ format : 'm/d/Y',
+ constructor: function(cfg){
+ Ext.grid.DateColumn.superclass.constructor.call(this, cfg);
+ this.renderer = Ext.util.Format.dateRenderer(this.format);
+ }
+});
+
+
+Ext.grid.TemplateColumn = Ext.extend(Ext.grid.Column, {
+
+ constructor: function(cfg){
+ Ext.grid.TemplateColumn.superclass.constructor.call(this, cfg);
+ var tpl = (!Ext.isPrimitive(this.tpl) && this.tpl.compile) ? this.tpl : new Ext.XTemplate(this.tpl);
+ this.renderer = function(value, p, r){
+ return tpl.apply(r.data);
+ };
+ this.tpl = tpl;
+ }
+});
+
+
+Ext.grid.ActionColumn = Ext.extend(Ext.grid.Column, {
+
+
+
+
+
+
+
+
+ header: '&#160;',
+
+ actionIdRe: /x-action-col-(\d+)/,
+
+
+ altText: '',
+
+ constructor: function(cfg) {
+ var me = this,
+ items = cfg.items || (me.items = [me]),
+ l = items.length,
+ i,
+ item;
+
+ Ext.grid.ActionColumn.superclass.constructor.call(me, cfg);
+
+
+
+ me.renderer = function(v, meta) {
+
+ v = Ext.isFunction(cfg.renderer) ? cfg.renderer.apply(this, arguments)||'' : '';
+
+ meta.css += ' x-action-col-cell';
+ for (i = 0; i < l; i++) {
+ item = items[i];
+ v += '<img alt="' + (item.altText || me.altText) + '" src="' + (item.icon || Ext.BLANK_IMAGE_URL) +
+ '" class="x-action-col-icon x-action-col-' + String(i) + ' ' + (item.iconCls || '') +
+ ' ' + (Ext.isFunction(item.getClass) ? item.getClass.apply(item.scope||this.scope||this, arguments) : '') + '"' +
+ ((item.tooltip) ? ' ext:qtip="' + item.tooltip + '"' : '') + ' />';
+ }
+ return v;
+ };
+ },
+
+ destroy: function() {
+ delete this.items;
+ delete this.renderer;
+ return Ext.grid.ActionColumn.superclass.destroy.apply(this, arguments);
+ },
+
+
+ processEvent : function(name, e, grid, rowIndex, colIndex){
+ var m = e.getTarget().className.match(this.actionIdRe),
+ item, fn;
+ if (m && (item = this.items[parseInt(m[1], 10)])) {
+ if (name == 'click') {
+ (fn = item.handler || this.handler) && fn.call(item.scope||this.scope||this, grid, rowIndex, colIndex, item, e);
+ } else if ((name == 'mousedown') && (item.stopSelection !== false)) {
+ return false;
+ }
+ }
+ return Ext.grid.ActionColumn.superclass.processEvent.apply(this, arguments);
+ }
+});
+
+
+Ext.grid.Column.types = {
+ gridcolumn : Ext.grid.Column,
+ booleancolumn: Ext.grid.BooleanColumn,
+ numbercolumn: Ext.grid.NumberColumn,
+ datecolumn: Ext.grid.DateColumn,
+ templatecolumn: Ext.grid.TemplateColumn,
+ actioncolumn: Ext.grid.ActionColumn
+};
+Ext.grid.RowNumberer = Ext.extend(Object, {
+
+ header: "",
+
+ width: 23,
+
+ sortable: false,
+
+ constructor : function(config){
+ Ext.apply(this, config);
+ if(this.rowspan){
+ this.renderer = this.renderer.createDelegate(this);
+ }
+ },
+
+
+ fixed:true,
+ hideable: false,
+ menuDisabled:true,
+ dataIndex: '',
+ id: 'numberer',
+ rowspan: undefined,
+
+
+ renderer : function(v, p, record, rowIndex){
+ if(this.rowspan){
+ p.cellAttr = 'rowspan="'+this.rowspan+'"';
+ }
+ return rowIndex+1;
+ }
+});
+Ext.grid.CheckboxSelectionModel = Ext.extend(Ext.grid.RowSelectionModel, {
+
+
+
+ header : '<div class="x-grid3-hd-checker">&#160;</div>',
+
+ width : 20,
+
+ sortable : false,
+
+
+ menuDisabled : true,
+ fixed : true,
+ hideable: false,
+ dataIndex : '',
+ id : 'checker',
+ isColumn: true,
+
+ constructor : function(){
+ Ext.grid.CheckboxSelectionModel.superclass.constructor.apply(this, arguments);
+ if(this.checkOnly){
+ this.handleMouseDown = Ext.emptyFn;
+ }
+ },
+
+
+ initEvents : function(){
+ Ext.grid.CheckboxSelectionModel.superclass.initEvents.call(this);
+ this.grid.on('render', function(){
+ Ext.fly(this.grid.getView().innerHd).on('mousedown', this.onHdMouseDown, this);
+ }, this);
+ },
+
+
+ processEvent : function(name, e, grid, rowIndex, colIndex){
+ if (name == 'mousedown') {
+ this.onMouseDown(e, e.getTarget());
+ return false;
+ } else {
+ return Ext.grid.Column.prototype.processEvent.apply(this, arguments);
+ }
+ },
+
+
+ onMouseDown : function(e, t){
+ if(e.button === 0 && t.className == 'x-grid3-row-checker'){
+ e.stopEvent();
+ var row = e.getTarget('.x-grid3-row');
+ if(row){
+ var index = row.rowIndex;
+ if(this.isSelected(index)){
+ this.deselectRow(index);
+ }else{
+ this.selectRow(index, true);
+ this.grid.getView().focusRow(index);
+ }
+ }
+ }
+ },
+
+
+ onHdMouseDown : function(e, t) {
+ if(t.className == 'x-grid3-hd-checker'){
+ e.stopEvent();
+ var hd = Ext.fly(t.parentNode);
+ var isChecked = hd.hasClass('x-grid3-hd-checker-on');
+ if(isChecked){
+ hd.removeClass('x-grid3-hd-checker-on');
+ this.clearSelections();
+ }else{
+ hd.addClass('x-grid3-hd-checker-on');
+ this.selectAll();
+ }
+ }
+ },
+
+
+ renderer : function(v, p, record){
+ return '<div class="x-grid3-row-checker">&#160;</div>';
+ },
+
+ onEditorSelect: function(row, lastRow){
+ if(lastRow != row && !this.checkOnly){
+ this.selectRow(row);
+ }
+ }
+});
+Ext.grid.CellSelectionModel = Ext.extend(Ext.grid.AbstractSelectionModel, {
+
+ constructor : function(config){
+ Ext.apply(this, config);
+
+ this.selection = null;
+
+ this.addEvents(
+
+ "beforecellselect",
+
+ "cellselect",
+
+ "selectionchange"
+ );
+
+ Ext.grid.CellSelectionModel.superclass.constructor.call(this);
+ },
+
+
+ initEvents : function(){
+ this.grid.on('cellmousedown', this.handleMouseDown, this);
+ this.grid.on(Ext.EventManager.getKeyEvent(), this.handleKeyDown, this);
+ this.grid.getView().on({
+ scope: this,
+ refresh: this.onViewChange,
+ rowupdated: this.onRowUpdated,
+ beforerowremoved: this.clearSelections,
+ beforerowsinserted: this.clearSelections
+ });
+ if(this.grid.isEditor){
+ this.grid.on('beforeedit', this.beforeEdit, this);
+ }
+ },
+
+
+ beforeEdit : function(e){
+ this.select(e.row, e.column, false, true, e.record);
+ },
+
+
+ onRowUpdated : function(v, index, r){
+ if(this.selection && this.selection.record == r){
+ v.onCellSelect(index, this.selection.cell[1]);
+ }
+ },
+
+
+ onViewChange : function(){
+ this.clearSelections(true);
+ },
+
+
+ getSelectedCell : function(){
+ return this.selection ? this.selection.cell : null;
+ },
+
+
+ clearSelections : function(preventNotify){
+ var s = this.selection;
+ if(s){
+ if(preventNotify !== true){
+ this.grid.view.onCellDeselect(s.cell[0], s.cell[1]);
+ }
+ this.selection = null;
+ this.fireEvent("selectionchange", this, null);
+ }
+ },
+
+
+ hasSelection : function(){
+ return this.selection ? true : false;
+ },
+
+
+ handleMouseDown : function(g, row, cell, e){
+ if(e.button !== 0 || this.isLocked()){
+ return;
+ }
+ this.select(row, cell);
+ },
+
+
+ select : function(rowIndex, colIndex, preventViewNotify, preventFocus, r){
+ if(this.fireEvent("beforecellselect", this, rowIndex, colIndex) !== false){
+ this.clearSelections();
+ r = r || this.grid.store.getAt(rowIndex);
+ this.selection = {
+ record : r,
+ cell : [rowIndex, colIndex]
+ };
+ if(!preventViewNotify){
+ var v = this.grid.getView();
+ v.onCellSelect(rowIndex, colIndex);
+ if(preventFocus !== true){
+ v.focusCell(rowIndex, colIndex);
+ }
+ }
+ this.fireEvent("cellselect", this, rowIndex, colIndex);
+ this.fireEvent("selectionchange", this, this.selection);
+ }
+ },
+
+
+ isSelectable : function(rowIndex, colIndex, cm){
+ return !cm.isHidden(colIndex);
+ },
+
+
+ onEditorKey: function(field, e){
+ if(e.getKey() == e.TAB){
+ this.handleKeyDown(e);
+ }
+ },
+
+
+ handleKeyDown : function(e){
+ if(!e.isNavKeyPress()){
+ return;
+ }
+
+ var k = e.getKey(),
+ g = this.grid,
+ s = this.selection,
+ sm = this,
+ walk = function(row, col, step){
+ return g.walkCells(
+ row,
+ col,
+ step,
+ g.isEditor && g.editing ? sm.acceptsNav : sm.isSelectable,
+ sm
+ );
+ },
+ cell, newCell, r, c, ae;
+
+ switch(k){
+ case e.ESC:
+ case e.PAGE_UP:
+ case e.PAGE_DOWN:
+
+ break;
+ default:
+
+ e.stopEvent();
+ break;
+ }
+
+ if(!s){
+ cell = walk(0, 0, 1);
+ if(cell){
+ this.select(cell[0], cell[1]);
+ }
+ return;
+ }
+
+ cell = s.cell;
+ r = cell[0];
+ c = cell[1];
+
+ switch(k){
+ case e.TAB:
+ if(e.shiftKey){
+ newCell = walk(r, c - 1, -1);
+ }else{
+ newCell = walk(r, c + 1, 1);
+ }
+ break;
+ case e.DOWN:
+ newCell = walk(r + 1, c, 1);
+ break;
+ case e.UP:
+ newCell = walk(r - 1, c, -1);
+ break;
+ case e.RIGHT:
+ newCell = walk(r, c + 1, 1);
+ break;
+ case e.LEFT:
+ newCell = walk(r, c - 1, -1);
+ break;
+ case e.ENTER:
+ if (g.isEditor && !g.editing) {
+ g.startEditing(r, c);
+ return;
+ }
+ break;
+ }
+
+ if(newCell){
+
+ r = newCell[0];
+ c = newCell[1];
+
+ this.select(r, c);
+
+ if(g.isEditor && g.editing){
+ ae = g.activeEditor;
+ if(ae && ae.field.triggerBlur){
+
+ ae.field.triggerBlur();
+ }
+ g.startEditing(r, c);
+ }
+ }
+ },
+
+ acceptsNav : function(row, col, cm){
+ return !cm.isHidden(col) && cm.isCellEditable(col, row);
+ }
+});
+Ext.grid.EditorGridPanel = Ext.extend(Ext.grid.GridPanel, {
+
+ clicksToEdit: 2,
+
+
+ forceValidation: false,
+
+
+ isEditor : true,
+
+ detectEdit: false,
+
+
+ autoEncode : false,
+
+
+
+ trackMouseOver: false,
+
+
+ initComponent : function(){
+ Ext.grid.EditorGridPanel.superclass.initComponent.call(this);
+
+ if(!this.selModel){
+
+ this.selModel = new Ext.grid.CellSelectionModel();
+ }
+
+ this.activeEditor = null;
+
+ this.addEvents(
+
+ "beforeedit",
+
+ "afteredit",
+
+ "validateedit"
+ );
+ },
+
+
+ initEvents : function(){
+ Ext.grid.EditorGridPanel.superclass.initEvents.call(this);
+
+ this.getGridEl().on('mousewheel', this.stopEditing.createDelegate(this, [true]), this);
+ this.on('columnresize', this.stopEditing, this, [true]);
+
+ if(this.clicksToEdit == 1){
+ this.on("cellclick", this.onCellDblClick, this);
+ }else {
+ var view = this.getView();
+ if(this.clicksToEdit == 'auto' && view.mainBody){
+ view.mainBody.on('mousedown', this.onAutoEditClick, this);
+ }
+ this.on('celldblclick', this.onCellDblClick, this);
+ }
+ },
+
+ onResize : function(){
+ Ext.grid.EditorGridPanel.superclass.onResize.apply(this, arguments);
+ var ae = this.activeEditor;
+ if(this.editing && ae){
+ ae.realign(true);
+ }
+ },
+
+
+ onCellDblClick : function(g, row, col){
+ this.startEditing(row, col);
+ },
+
+
+ onAutoEditClick : function(e, t){
+ if(e.button !== 0){
+ return;
+ }
+ var row = this.view.findRowIndex(t),
+ col = this.view.findCellIndex(t);
+ if(row !== false && col !== false){
+ this.stopEditing();
+ if(this.selModel.getSelectedCell){
+ var sc = this.selModel.getSelectedCell();
+ if(sc && sc[0] === row && sc[1] === col){
+ this.startEditing(row, col);
+ }
+ }else{
+ if(this.selModel.isSelected(row)){
+ this.startEditing(row, col);
+ }
+ }
+ }
+ },
+
+
+ onEditComplete : function(ed, value, startValue){
+ this.editing = false;
+ this.lastActiveEditor = this.activeEditor;
+ this.activeEditor = null;
+
+ var r = ed.record,
+ field = this.colModel.getDataIndex(ed.col);
+ value = this.postEditValue(value, startValue, r, field);
+ if(this.forceValidation === true || String(value) !== String(startValue)){
+ var e = {
+ grid: this,
+ record: r,
+ field: field,
+ originalValue: startValue,
+ value: value,
+ row: ed.row,
+ column: ed.col,
+ cancel:false
+ };
+ if(this.fireEvent("validateedit", e) !== false && !e.cancel && String(value) !== String(startValue)){
+ r.set(field, e.value);
+ delete e.cancel;
+ this.fireEvent("afteredit", e);
+ }
+ }
+ this.view.focusCell(ed.row, ed.col);
+ },
+
+
+ startEditing : function(row, col){
+ this.stopEditing();
+ if(this.colModel.isCellEditable(col, row)){
+ this.view.ensureVisible(row, col, true);
+ var r = this.store.getAt(row),
+ field = this.colModel.getDataIndex(col),
+ e = {
+ grid: this,
+ record: r,
+ field: field,
+ value: r.data[field],
+ row: row,
+ column: col,
+ cancel:false
+ };
+ if(this.fireEvent("beforeedit", e) !== false && !e.cancel){
+ this.editing = true;
+ var ed = this.colModel.getCellEditor(col, row);
+ if(!ed){
+ return;
+ }
+ if(!ed.rendered){
+ ed.parentEl = this.view.getEditorParent(ed);
+ ed.on({
+ scope: this,
+ render: {
+ fn: function(c){
+ c.field.focus(false, true);
+ },
+ single: true,
+ scope: this
+ },
+ specialkey: function(field, e){
+ this.getSelectionModel().onEditorKey(field, e);
+ },
+ complete: this.onEditComplete,
+ canceledit: this.stopEditing.createDelegate(this, [true])
+ });
+ }
+ Ext.apply(ed, {
+ row : row,
+ col : col,
+ record : r
+ });
+ this.lastEdit = {
+ row: row,
+ col: col
+ };
+ this.activeEditor = ed;
+ if (ed.field.isXType('checkbox')) {
+ ed.allowBlur = false;
+ this.setupCheckbox(ed.field);
+ }
+
+
+ ed.selectSameEditor = (this.activeEditor == this.lastActiveEditor);
+ var v = this.preEditValue(r, field);
+ ed.startEdit(this.view.getCell(row, col).firstChild, Ext.isDefined(v) ? v : '');
+
+
+ (function(){
+ delete ed.selectSameEditor;
+ }).defer(50);
+ }
+ }
+ },
+
+ setupCheckbox: function(field){
+ var me = this,
+ fn = function() {
+ field.el.on('click', me.onCheckClick, me, {single: true});
+ };
+ if (field.rendered) {
+ fn();
+ } else {
+ field.on('render', fn, null, {single: true});
+ }
+ },
+
+ onCheckClick: function(){
+ var ed = this.activeEditor;
+ ed.allowBlur = true;
+ ed.field.focus(false, 10);
+ },
+
+
+ preEditValue : function(r, field){
+ var value = r.data[field];
+ return this.autoEncode && Ext.isString(value) ? Ext.util.Format.htmlDecode(value) : value;
+ },
+
+
+ postEditValue : function(value, originalValue, r, field){
+ return this.autoEncode && Ext.isString(value) ? Ext.util.Format.htmlEncode(value) : value;
+ },
+
+
+ stopEditing : function(cancel){
+ if(this.editing){
+
+ var ae = this.lastActiveEditor = this.activeEditor;
+ if(ae){
+ ae[cancel === true ? 'cancelEdit' : 'completeEdit']();
+ this.view.focusCell(ae.row, ae.col);
+ }
+ this.activeEditor = null;
+ }
+ this.editing = false;
+ }
+});
+Ext.reg('editorgrid', Ext.grid.EditorGridPanel);
+
+Ext.grid.GridEditor = function(field, config){
+ Ext.grid.GridEditor.superclass.constructor.call(this, field, config);
+ field.monitorTab = false;
+};
+
+Ext.extend(Ext.grid.GridEditor, Ext.Editor, {
+ alignment: "tl-tl",
+ autoSize: "width",
+ hideEl : false,
+ cls: "x-small-editor x-grid-editor",
+ shim:false,
+ shadow:false
+});
+Ext.grid.PropertyRecord = Ext.data.Record.create([
+ {name:'name',type:'string'}, 'value'
+]);
+
+
+Ext.grid.PropertyStore = Ext.extend(Ext.util.Observable, {
+
+ constructor : function(grid, source){
+ this.grid = grid;
+ this.store = new Ext.data.Store({
+ recordType : Ext.grid.PropertyRecord
+ });
+ this.store.on('update', this.onUpdate, this);
+ if(source){
+ this.setSource(source);
+ }
+ Ext.grid.PropertyStore.superclass.constructor.call(this);
+ },
+
+
+ setSource : function(o){
+ this.source = o;
+ this.store.removeAll();
+ var data = [];
+ for(var k in o){
+ if(this.isEditableValue(o[k])){
+ data.push(new Ext.grid.PropertyRecord({name: k, value: o[k]}, k));
+ }
+ }
+ this.store.loadRecords({records: data}, {}, true);
+ },
+
+
+ onUpdate : function(ds, record, type){
+ if(type == Ext.data.Record.EDIT){
+ var v = record.data.value;
+ var oldValue = record.modified.value;
+ if(this.grid.fireEvent('beforepropertychange', this.source, record.id, v, oldValue) !== false){
+ this.source[record.id] = v;
+ record.commit();
+ this.grid.fireEvent('propertychange', this.source, record.id, v, oldValue);
+ }else{
+ record.reject();
+ }
+ }
+ },
+
+
+ getProperty : function(row){
+ return this.store.getAt(row);
+ },
+
+
+ isEditableValue: function(val){
+ return Ext.isPrimitive(val) || Ext.isDate(val);
+ },
+
+
+ setValue : function(prop, value, create){
+ var r = this.getRec(prop);
+ if(r){
+ r.set('value', value);
+ this.source[prop] = value;
+ }else if(create){
+
+ this.source[prop] = value;
+ r = new Ext.grid.PropertyRecord({name: prop, value: value}, prop);
+ this.store.add(r);
+
+ }
+ },
+
+
+ remove : function(prop){
+ var r = this.getRec(prop);
+ if(r){
+ this.store.remove(r);
+ delete this.source[prop];
+ }
+ },
+
+
+ getRec : function(prop){
+ return this.store.getById(prop);
+ },
+
+
+ getSource : function(){
+ return this.source;
+ }
+});
+
+
+Ext.grid.PropertyColumnModel = Ext.extend(Ext.grid.ColumnModel, {
+
+ nameText : 'Name',
+ valueText : 'Value',
+ dateFormat : 'm/j/Y',
+ trueText: 'true',
+ falseText: 'false',
+
+ constructor : function(grid, store){
+ var g = Ext.grid,
+ f = Ext.form;
+
+ this.grid = grid;
+ g.PropertyColumnModel.superclass.constructor.call(this, [
+ {header: this.nameText, width:50, sortable: true, dataIndex:'name', id: 'name', menuDisabled:true},
+ {header: this.valueText, width:50, resizable:false, dataIndex: 'value', id: 'value', menuDisabled:true}
+ ]);
+ this.store = store;
+
+ var bfield = new f.Field({
+ autoCreate: {tag: 'select', children: [
+ {tag: 'option', value: 'true', html: this.trueText},
+ {tag: 'option', value: 'false', html: this.falseText}
+ ]},
+ getValue : function(){
+ return this.el.dom.value == 'true';
+ }
+ });
+ this.editors = {
+ 'date' : new g.GridEditor(new f.DateField({selectOnFocus:true})),
+ 'string' : new g.GridEditor(new f.TextField({selectOnFocus:true})),
+ 'number' : new g.GridEditor(new f.NumberField({selectOnFocus:true, style:'text-align:left;'})),
+ 'boolean' : new g.GridEditor(bfield, {
+ autoSize: 'both'
+ })
+ };
+ this.renderCellDelegate = this.renderCell.createDelegate(this);
+ this.renderPropDelegate = this.renderProp.createDelegate(this);
+ },
+
+
+ renderDate : function(dateVal){
+ return dateVal.dateFormat(this.dateFormat);
+ },
+
+
+ renderBool : function(bVal){
+ return this[bVal ? 'trueText' : 'falseText'];
+ },
+
+
+ isCellEditable : function(colIndex, rowIndex){
+ return colIndex == 1;
+ },
+
+
+ getRenderer : function(col){
+ return col == 1 ?
+ this.renderCellDelegate : this.renderPropDelegate;
+ },
+
+
+ renderProp : function(v){
+ return this.getPropertyName(v);
+ },
+
+
+ renderCell : function(val, meta, rec){
+ var renderer = this.grid.customRenderers[rec.get('name')];
+ if(renderer){
+ return renderer.apply(this, arguments);
+ }
+ var rv = val;
+ if(Ext.isDate(val)){
+ rv = this.renderDate(val);
+ }else if(typeof val == 'boolean'){
+ rv = this.renderBool(val);
+ }
+ return Ext.util.Format.htmlEncode(rv);
+ },
+
+
+ getPropertyName : function(name){
+ var pn = this.grid.propertyNames;
+ return pn && pn[name] ? pn[name] : name;
+ },
+
+
+ getCellEditor : function(colIndex, rowIndex){
+ var p = this.store.getProperty(rowIndex),
+ n = p.data.name,
+ val = p.data.value;
+ if(this.grid.customEditors[n]){
+ return this.grid.customEditors[n];
+ }
+ if(Ext.isDate(val)){
+ return this.editors.date;
+ }else if(typeof val == 'number'){
+ return this.editors.number;
+ }else if(typeof val == 'boolean'){
+ return this.editors['boolean'];
+ }else{
+ return this.editors.string;
+ }
+ },
+
+
+ destroy : function(){
+ Ext.grid.PropertyColumnModel.superclass.destroy.call(this);
+ this.destroyEditors(this.editors);
+ this.destroyEditors(this.grid.customEditors);
+ },
+
+ destroyEditors: function(editors){
+ for(var ed in editors){
+ Ext.destroy(editors[ed]);
+ }
+ }
+});
+
+
+Ext.grid.PropertyGrid = Ext.extend(Ext.grid.EditorGridPanel, {
+
+
+
+
+
+
+ enableColumnMove:false,
+ stripeRows:false,
+ trackMouseOver: false,
+ clicksToEdit:1,
+ enableHdMenu : false,
+ viewConfig : {
+ forceFit:true
+ },
+
+
+ initComponent : function(){
+ this.customRenderers = this.customRenderers || {};
+ this.customEditors = this.customEditors || {};
+ this.lastEditRow = null;
+ var store = new Ext.grid.PropertyStore(this);
+ this.propStore = store;
+ var cm = new Ext.grid.PropertyColumnModel(this, store);
+ store.store.sort('name', 'ASC');
+ this.addEvents(
+
+ 'beforepropertychange',
+
+ 'propertychange'
+ );
+ this.cm = cm;
+ this.ds = store.store;
+ Ext.grid.PropertyGrid.superclass.initComponent.call(this);
+
+ this.mon(this.selModel, 'beforecellselect', function(sm, rowIndex, colIndex){
+ if(colIndex === 0){
+ this.startEditing.defer(200, this, [rowIndex, 1]);
+ return false;
+ }
+ }, this);
+ },
+
+
+ onRender : function(){
+ Ext.grid.PropertyGrid.superclass.onRender.apply(this, arguments);
+
+ this.getGridEl().addClass('x-props-grid');
+ },
+
+
+ afterRender: function(){
+ Ext.grid.PropertyGrid.superclass.afterRender.apply(this, arguments);
+ if(this.source){
+ this.setSource(this.source);
+ }
+ },
+
+
+ setSource : function(source){
+ this.propStore.setSource(source);
+ },
+
+
+ getSource : function(){
+ return this.propStore.getSource();
+ },
+
+
+ setProperty : function(prop, value, create){
+ this.propStore.setValue(prop, value, create);
+ },
+
+
+ removeProperty : function(prop){
+ this.propStore.remove(prop);
+ }
+
+
+
+
+
+});
+Ext.reg("propertygrid", Ext.grid.PropertyGrid);
+
+Ext.grid.GroupingView = Ext.extend(Ext.grid.GridView, {
+
+
+ groupByText : 'Group By This Field',
+
+ showGroupsText : 'Show in Groups',
+
+ hideGroupedColumn : false,
+
+ showGroupName : true,
+
+ startCollapsed : false,
+
+ enableGrouping : true,
+
+ enableGroupingMenu : true,
+
+ enableNoGroups : true,
+
+ emptyGroupText : '(None)',
+
+ ignoreAdd : false,
+
+ groupTextTpl : '{text}',
+
+
+ groupMode: 'value',
+
+
+
+
+ cancelEditOnToggle: true,
+
+
+ initTemplates : function(){
+ Ext.grid.GroupingView.superclass.initTemplates.call(this);
+ this.state = {};
+
+ var sm = this.grid.getSelectionModel();
+ sm.on(sm.selectRow ? 'beforerowselect' : 'beforecellselect',
+ this.onBeforeRowSelect, this);
+
+ if(!this.startGroup){
+ this.startGroup = new Ext.XTemplate(
+ '<div id="{groupId}" class="x-grid-group {cls}">',
+ '<div id="{groupId}-hd" class="x-grid-group-hd" style="{style}"><div class="x-grid-group-title">', this.groupTextTpl ,'</div></div>',
+ '<div id="{groupId}-bd" class="x-grid-group-body">'
+ );
+ }
+ this.startGroup.compile();
+
+ if (!this.endGroup) {
+ this.endGroup = '</div></div>';
+ }
+ },
+
+
+ findGroup : function(el){
+ return Ext.fly(el).up('.x-grid-group', this.mainBody.dom);
+ },
+
+
+ getGroups : function(){
+ return this.hasRows() ? this.mainBody.dom.childNodes : [];
+ },
+
+
+ onAdd : function(ds, records, index) {
+ if (this.canGroup() && !this.ignoreAdd) {
+ var ss = this.getScrollState();
+ this.fireEvent('beforerowsinserted', ds, index, index + (records.length-1));
+ this.refresh();
+ this.restoreScroll(ss);
+ this.fireEvent('rowsinserted', ds, index, index + (records.length-1));
+ } else if (!this.canGroup()) {
+ Ext.grid.GroupingView.superclass.onAdd.apply(this, arguments);
+ }
+ },
+
+
+ onRemove : function(ds, record, index, isUpdate){
+ Ext.grid.GroupingView.superclass.onRemove.apply(this, arguments);
+ var g = document.getElementById(record._groupId);
+ if(g && g.childNodes[1].childNodes.length < 1){
+ Ext.removeNode(g);
+ }
+ this.applyEmptyText();
+ },
+
+
+ refreshRow : function(record){
+ if(this.ds.getCount()==1){
+ this.refresh();
+ }else{
+ this.isUpdating = true;
+ Ext.grid.GroupingView.superclass.refreshRow.apply(this, arguments);
+ this.isUpdating = false;
+ }
+ },
+
+
+ beforeMenuShow : function(){
+ var item, items = this.hmenu.items, disabled = this.cm.config[this.hdCtxIndex].groupable === false;
+ if((item = items.get('groupBy'))){
+ item.setDisabled(disabled);
+ }
+ if((item = items.get('showGroups'))){
+ item.setDisabled(disabled);
+ item.setChecked(this.canGroup(), true);
+ }
+ },
+
+
+ renderUI : function(){
+ var markup = Ext.grid.GroupingView.superclass.renderUI.call(this);
+
+ if(this.enableGroupingMenu && this.hmenu){
+ this.hmenu.add('-',{
+ itemId:'groupBy',
+ text: this.groupByText,
+ handler: this.onGroupByClick,
+ scope: this,
+ iconCls:'x-group-by-icon'
+ });
+ if(this.enableNoGroups){
+ this.hmenu.add({
+ itemId:'showGroups',
+ text: this.showGroupsText,
+ checked: true,
+ checkHandler: this.onShowGroupsClick,
+ scope: this
+ });
+ }
+ this.hmenu.on('beforeshow', this.beforeMenuShow, this);
+ }
+ return markup;
+ },
+
+ processEvent: function(name, e){
+ Ext.grid.GroupingView.superclass.processEvent.call(this, name, e);
+ var hd = e.getTarget('.x-grid-group-hd', this.mainBody);
+ if(hd){
+
+ var field = this.getGroupField(),
+ prefix = this.getPrefix(field),
+ groupValue = hd.id.substring(prefix.length),
+ emptyRe = new RegExp('gp-' + Ext.escapeRe(field) + '--hd');
+
+
+ groupValue = groupValue.substr(0, groupValue.length - 3);
+
+
+ if(groupValue || emptyRe.test(hd.id)){
+ this.grid.fireEvent('group' + name, this.grid, field, groupValue, e);
+ }
+ if(name == 'mousedown' && e.button == 0){
+ this.toggleGroup(hd.parentNode);
+ }
+ }
+
+ },
+
+
+ onGroupByClick : function(){
+ var grid = this.grid;
+ this.enableGrouping = true;
+ grid.store.groupBy(this.cm.getDataIndex(this.hdCtxIndex));
+ grid.fireEvent('groupchange', grid, grid.store.getGroupState());
+ this.beforeMenuShow();
+ this.refresh();
+ },
+
+
+ onShowGroupsClick : function(mi, checked){
+ this.enableGrouping = checked;
+ if(checked){
+ this.onGroupByClick();
+ }else{
+ this.grid.store.clearGrouping();
+ this.grid.fireEvent('groupchange', this, null);
+ }
+ },
+
+
+ toggleRowIndex : function(rowIndex, expanded){
+ if(!this.canGroup()){
+ return;
+ }
+ var row = this.getRow(rowIndex);
+ if(row){
+ this.toggleGroup(this.findGroup(row), expanded);
+ }
+ },
+
+
+ toggleGroup : function(group, expanded){
+ var gel = Ext.get(group),
+ id = Ext.util.Format.htmlEncode(gel.id);
+
+ expanded = Ext.isDefined(expanded) ? expanded : gel.hasClass('x-grid-group-collapsed');
+ if(this.state[id] !== expanded){
+ if (this.cancelEditOnToggle !== false) {
+ this.grid.stopEditing(true);
+ }
+ this.state[id] = expanded;
+ gel[expanded ? 'removeClass' : 'addClass']('x-grid-group-collapsed');
+ }
+ },
+
+
+ toggleAllGroups : function(expanded){
+ var groups = this.getGroups();
+ for(var i = 0, len = groups.length; i < len; i++){
+ this.toggleGroup(groups[i], expanded);
+ }
+ },
+
+
+ expandAllGroups : function(){
+ this.toggleAllGroups(true);
+ },
+
+
+ collapseAllGroups : function(){
+ this.toggleAllGroups(false);
+ },
+
+
+ getGroup : function(v, r, groupRenderer, rowIndex, colIndex, ds){
+ var column = this.cm.config[colIndex],
+ g = groupRenderer ? groupRenderer.call(column.scope, v, {}, r, rowIndex, colIndex, ds) : String(v);
+ if(g === '' || g === '&#160;'){
+ g = column.emptyGroupText || this.emptyGroupText;
+ }
+ return g;
+ },
+
+
+ getGroupField : function(){
+ return this.grid.store.getGroupState();
+ },
+
+
+ afterRender : function(){
+ if(!this.ds || !this.cm){
+ return;
+ }
+ Ext.grid.GroupingView.superclass.afterRender.call(this);
+ if(this.grid.deferRowRender){
+ this.updateGroupWidths();
+ }
+ },
+
+ afterRenderUI: function () {
+ Ext.grid.GroupingView.superclass.afterRenderUI.call(this);
+
+ if (this.enableGroupingMenu && this.hmenu) {
+ this.hmenu.add('-',{
+ itemId:'groupBy',
+ text: this.groupByText,
+ handler: this.onGroupByClick,
+ scope: this,
+ iconCls:'x-group-by-icon'
+ });
+
+ if (this.enableNoGroups) {
+ this.hmenu.add({
+ itemId:'showGroups',
+ text: this.showGroupsText,
+ checked: true,
+ checkHandler: this.onShowGroupsClick,
+ scope: this
+ });
+ }
+
+ this.hmenu.on('beforeshow', this.beforeMenuShow, this);
+ }
+ },
+
+
+ renderRows : function(){
+ var groupField = this.getGroupField();
+ var eg = !!groupField;
+
+ if(this.hideGroupedColumn) {
+ var colIndex = this.cm.findColumnIndex(groupField),
+ hasLastGroupField = Ext.isDefined(this.lastGroupField);
+ if(!eg && hasLastGroupField){
+ this.mainBody.update('');
+ this.cm.setHidden(this.cm.findColumnIndex(this.lastGroupField), false);
+ delete this.lastGroupField;
+ }else if (eg && !hasLastGroupField){
+ this.lastGroupField = groupField;
+ this.cm.setHidden(colIndex, true);
+ }else if (eg && hasLastGroupField && groupField !== this.lastGroupField) {
+ this.mainBody.update('');
+ var oldIndex = this.cm.findColumnIndex(this.lastGroupField);
+ this.cm.setHidden(oldIndex, false);
+ this.lastGroupField = groupField;
+ this.cm.setHidden(colIndex, true);
+ }
+ }
+ return Ext.grid.GroupingView.superclass.renderRows.apply(
+ this, arguments);
+ },
+
+
+ doRender : function(cs, rs, ds, startRow, colCount, stripe){
+ if(rs.length < 1){
+ return '';
+ }
+
+ if(!this.canGroup() || this.isUpdating){
+ return Ext.grid.GroupingView.superclass.doRender.apply(this, arguments);
+ }
+
+ var groupField = this.getGroupField(),
+ colIndex = this.cm.findColumnIndex(groupField),
+ g,
+ gstyle = 'width:' + this.getTotalWidth() + ';',
+ cfg = this.cm.config[colIndex],
+ groupRenderer = cfg.groupRenderer || cfg.renderer,
+ prefix = this.showGroupName ? (cfg.groupName || cfg.header)+': ' : '',
+ groups = [],
+ curGroup, i, len, gid;
+
+ for(i = 0, len = rs.length; i < len; i++){
+ var rowIndex = startRow + i,
+ r = rs[i],
+ gvalue = r.data[groupField];
+
+ g = this.getGroup(gvalue, r, groupRenderer, rowIndex, colIndex, ds);
+ if(!curGroup || curGroup.group != g){
+ gid = this.constructId(gvalue, groupField, colIndex);
+
+
+ this.state[gid] = !(Ext.isDefined(this.state[gid]) ? !this.state[gid] : this.startCollapsed);
+ curGroup = {
+ group: g,
+ gvalue: gvalue,
+ text: prefix + g,
+ groupId: gid,
+ startRow: rowIndex,
+ rs: [r],
+ cls: this.state[gid] ? '' : 'x-grid-group-collapsed',
+ style: gstyle
+ };
+ groups.push(curGroup);
+ }else{
+ curGroup.rs.push(r);
+ }
+ r._groupId = gid;
+ }
+
+ var buf = [];
+ for(i = 0, len = groups.length; i < len; i++){
+ g = groups[i];
+ this.doGroupStart(buf, g, cs, ds, colCount);
+ buf[buf.length] = Ext.grid.GroupingView.superclass.doRender.call(
+ this, cs, g.rs, ds, g.startRow, colCount, stripe);
+
+ this.doGroupEnd(buf, g, cs, ds, colCount);
+ }
+ return buf.join('');
+ },
+
+
+ getGroupId : function(value){
+ var field = this.getGroupField();
+ return this.constructId(value, field, this.cm.findColumnIndex(field));
+ },
+
+
+ constructId : function(value, field, idx){
+ var cfg = this.cm.config[idx],
+ groupRenderer = cfg.groupRenderer || cfg.renderer,
+ val = (this.groupMode == 'value') ? value : this.getGroup(value, {data:{}}, groupRenderer, 0, idx, this.ds);
+
+ return this.getPrefix(field) + Ext.util.Format.htmlEncode(val);
+ },
+
+
+ canGroup : function(){
+ return this.enableGrouping && !!this.getGroupField();
+ },
+
+
+ getPrefix: function(field){
+ return this.grid.getGridEl().id + '-gp-' + field + '-';
+ },
+
+
+ doGroupStart : function(buf, g, cs, ds, colCount){
+ buf[buf.length] = this.startGroup.apply(g);
+ },
+
+
+ doGroupEnd : function(buf, g, cs, ds, colCount){
+ buf[buf.length] = this.endGroup;
+ },
+
+
+ getRows : function(){
+ if(!this.canGroup()){
+ return Ext.grid.GroupingView.superclass.getRows.call(this);
+ }
+ var r = [],
+ gs = this.getGroups(),
+ g,
+ i = 0,
+ len = gs.length,
+ j,
+ jlen;
+ for(; i < len; ++i){
+ g = gs[i].childNodes[1];
+ if(g){
+ g = g.childNodes;
+ for(j = 0, jlen = g.length; j < jlen; ++j){
+ r[r.length] = g[j];
+ }
+ }
+ }
+ return r;
+ },
+
+
+ updateGroupWidths : function(){
+ if(!this.canGroup() || !this.hasRows()){
+ return;
+ }
+ var tw = Math.max(this.cm.getTotalWidth(), this.el.dom.offsetWidth-this.getScrollOffset()) +'px';
+ var gs = this.getGroups();
+ for(var i = 0, len = gs.length; i < len; i++){
+ gs[i].firstChild.style.width = tw;
+ }
+ },
+
+
+ onColumnWidthUpdated : function(col, w, tw){
+ Ext.grid.GroupingView.superclass.onColumnWidthUpdated.call(this, col, w, tw);
+ this.updateGroupWidths();
+ },
+
+
+ onAllColumnWidthsUpdated : function(ws, tw){
+ Ext.grid.GroupingView.superclass.onAllColumnWidthsUpdated.call(this, ws, tw);
+ this.updateGroupWidths();
+ },
+
+
+ onColumnHiddenUpdated : function(col, hidden, tw){
+ Ext.grid.GroupingView.superclass.onColumnHiddenUpdated.call(this, col, hidden, tw);
+ this.updateGroupWidths();
+ },
+
+
+ onLayout : function(){
+ this.updateGroupWidths();
+ },
+
+
+ onBeforeRowSelect : function(sm, rowIndex){
+ this.toggleRowIndex(rowIndex, true);
+ }
+});
+
+Ext.grid.GroupingView.GROUP_ID = 1000;
diff --git a/deluge/ui/web/js/extjs/ext-all.js b/deluge/ui/web/js/extjs/ext-all.js
new file mode 100644
index 0000000..5f3e5aa
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-all.js
@@ -0,0 +1,21 @@
+/*
+This file is part of Ext JS 3.4
+
+Copyright (c) 2011-2013 Sencha Inc
+
+Contact: http://www.sencha.com/contact
+
+GNU General Public License Usage
+This file may be used under the terms of the GNU General Public License version 3.0 as
+published by the Free Software Foundation and appearing in the file LICENSE included in the
+packaging of this file.
+
+Please review the following information to ensure the GNU General Public License version 3.0
+requirements will be met: http://www.gnu.org/copyleft/gpl.html.
+
+If you are unsure which license is appropriate for your use, please contact the sales department
+at http://www.sencha.com/contact.
+
+Build date: 2013-04-03 15:07:25
+*/
+(function(){var h=Ext.util,j=Ext.each,g=true,i=false;h.Observable=function(){var k=this,l=k.events;if(k.listeners){k.on(k.listeners);delete k.listeners}k.events=l||{}};h.Observable.prototype={filterOptRe:/^(?:scope|delay|buffer|single)$/,fireEvent:function(){var k=Array.prototype.slice.call(arguments,0),m=k[0].toLowerCase(),n=this,l=g,p=n.events[m],s,o,r;if(n.eventsSuspended===g){if(o=n.eventQueue){o.push(k)}}else{if(typeof p=="object"){if(p.bubble){if(p.fire.apply(p,k.slice(1))===i){return i}r=n.getBubbleTarget&&n.getBubbleTarget();if(r&&r.enableBubble){s=r.events[m];if(!s||typeof s!="object"||!s.bubble){r.enableBubble(m)}return r.fireEvent.apply(r,k)}}else{k.shift();l=p.fire.apply(p,k)}}}return l},addListener:function(k,m,l,r){var n=this,q,s,p;if(typeof k=="object"){r=k;for(q in r){s=r[q];if(!n.filterOptRe.test(q)){n.addListener(q,s.fn||s,s.scope||r.scope,s.fn?s:r)}}}else{k=k.toLowerCase();p=n.events[k]||g;if(typeof p=="boolean"){n.events[k]=p=new h.Event(n,k)}p.addListener(m,l,typeof r=="object"?r:{})}},removeListener:function(k,m,l){var n=this.events[k.toLowerCase()];if(typeof n=="object"){n.removeListener(m,l)}},purgeListeners:function(){var m=this.events,k,l;for(l in m){k=m[l];if(typeof k=="object"){k.clearListeners()}}},addEvents:function(n){var m=this;m.events=m.events||{};if(typeof n=="string"){var k=arguments,l=k.length;while(l--){m.events[k[l]]=m.events[k[l]]||g}}else{Ext.applyIf(m.events,n)}},hasListener:function(k){var l=this.events[k.toLowerCase()];return typeof l=="object"&&l.listeners.length>0},suspendEvents:function(k){this.eventsSuspended=g;if(k&&!this.eventQueue){this.eventQueue=[]}},resumeEvents:function(){var k=this,l=k.eventQueue||[];k.eventsSuspended=i;delete k.eventQueue;j(l,function(m){k.fireEvent.apply(k,m)})}};var d=h.Observable.prototype;d.on=d.addListener;d.un=d.removeListener;h.Observable.releaseCapture=function(k){k.fireEvent=d.fireEvent};function e(l,m,k){return function(){if(m.target==arguments[0]){l.apply(k,Array.prototype.slice.call(arguments,0))}}}function b(n,p,k,m){k.task=new h.DelayedTask();return function(){k.task.delay(p.buffer,n,m,Array.prototype.slice.call(arguments,0))}}function c(m,n,l,k){return function(){n.removeListener(l,k);return m.apply(k,arguments)}}function a(n,p,k,m){return function(){var l=new h.DelayedTask(),o=Array.prototype.slice.call(arguments,0);if(!k.tasks){k.tasks=[]}k.tasks.push(l);l.delay(p.delay||10,function(){k.tasks.remove(l);n.apply(m,o)},m)}}h.Event=function(l,k){this.name=k;this.obj=l;this.listeners=[]};h.Event.prototype={addListener:function(o,n,m){var p=this,k;n=n||p.obj;if(!p.isListening(o,n)){k=p.createListener(o,n,m);if(p.firing){p.listeners=p.listeners.slice(0)}p.listeners.push(k)}},createListener:function(p,n,q){q=q||{};n=n||this.obj;var k={fn:p,scope:n,options:q},m=p;if(q.target){m=e(m,q,n)}if(q.delay){m=a(m,q,k,n)}if(q.single){m=c(m,this,p,n)}if(q.buffer){m=b(m,q,k,n)}k.fireFn=m;return k},findListener:function(o,n){var p=this.listeners,m=p.length,k;n=n||this.obj;while(m--){k=p[m];if(k){if(k.fn==o&&k.scope==n){return m}}}return -1},isListening:function(l,k){return this.findListener(l,k)!=-1},removeListener:function(r,q){var p,m,n,s=this,o=i;if((p=s.findListener(r,q))!=-1){if(s.firing){s.listeners=s.listeners.slice(0)}m=s.listeners[p];if(m.task){m.task.cancel();delete m.task}n=m.tasks&&m.tasks.length;if(n){while(n--){m.tasks[n].cancel()}delete m.tasks}s.listeners.splice(p,1);o=g}return o},clearListeners:function(){var n=this,k=n.listeners,m=k.length;while(m--){n.removeListener(k[m].fn,k[m].scope)}},fire:function(){var q=this,p=q.listeners,k=p.length,o=0,m;if(k>0){q.firing=g;var n=Array.prototype.slice.call(arguments,0);for(;o<k;o++){m=p[o];if(m&&m.fireFn.apply(m.scope||q.obj||window,n)===i){return(q.firing=i)}}}q.firing=i;return g}}})();Ext.DomHelper=function(){var x=null,k=/^(?:br|frame|hr|img|input|link|meta|range|spacer|wbr|area|param|col)$/i,m=/^table|tbody|tr|td$/i,d=/tag|children|cn|html$/i,t=/td|tr|tbody/i,o=/([a-z0-9-]+)\s*:\s*([^;\s]+(?:\s*[^;\s]+)*);?/gi,v=/end/i,r,n="afterbegin",p="afterend",c="beforebegin",q="beforeend",a="<table>",i="</table>",b=a+"<tbody>",j="</tbody>"+i,l=b+"<tr>",w="</tr>"+j;function h(B,D,C,E,A,y){var z=r.insertHtml(E,Ext.getDom(B),u(D));return C?Ext.get(z,true):z}function u(D){var z="",y,C,B,E;if(typeof D=="string"){z=D}else{if(Ext.isArray(D)){for(var A=0;A<D.length;A++){if(D[A]){z+=u(D[A])}}}else{z+="<"+(D.tag=D.tag||"div");for(y in D){C=D[y];if(!d.test(y)){if(typeof C=="object"){z+=" "+y+'="';for(B in C){z+=B+":"+C[B]+";"}z+='"'}else{z+=" "+({cls:"class",htmlFor:"for"}[y]||y)+'="'+C+'"'}}}if(k.test(D.tag)){z+="/>"}else{z+=">";if((E=D.children||D.cn)){z+=u(E)}else{if(D.html){z+=D.html}}z+="</"+D.tag+">"}}}return z}function g(F,C,B,D){x.innerHTML=[C,B,D].join("");var y=-1,A=x,z;while(++y<F){A=A.firstChild}if(z=A.nextSibling){var E=document.createDocumentFragment();while(A){z=A.nextSibling;E.appendChild(A);A=z}A=E}return A}function e(y,z,B,A){var C,D;x=x||document.createElement("div");if(y=="td"&&(z==n||z==q)||!t.test(y)&&(z==c||z==p)){return}D=z==c?B:z==p?B.nextSibling:z==n?B.firstChild:null;if(z==c||z==p){B=B.parentNode}if(y=="td"||(y=="tr"&&(z==q||z==n))){C=g(4,l,A,w)}else{if((y=="tbody"&&(z==q||z==n))||(y=="tr"&&(z==c||z==p))){C=g(3,b,A,j)}else{C=g(2,a,A,i)}}B.insertBefore(C,D);return C}function s(A){var D=document.createElement("div"),y=document.createDocumentFragment(),z=0,B,C;D.innerHTML=A;C=D.childNodes;B=C.length;for(;z<B;z++){y.appendChild(C[z].cloneNode(true))}return y}r={markup:function(y){return u(y)},applyStyles:function(y,z){if(z){var A;y=Ext.fly(y);if(typeof z=="function"){z=z.call()}if(typeof z=="string"){o.lastIndex=0;while((A=o.exec(z))){y.setStyle(A[1],A[2])}}else{if(typeof z=="object"){y.setStyle(z)}}}},insertHtml:function(D,y,E){var B={},A,F,C,G,H,z;D=D.toLowerCase();B[c]=["BeforeBegin","previousSibling"];B[p]=["AfterEnd","nextSibling"];if(y.insertAdjacentHTML){if(m.test(y.tagName)&&(z=e(y.tagName.toLowerCase(),D,y,E))){return z}B[n]=["AfterBegin","firstChild"];B[q]=["BeforeEnd","lastChild"];if((A=B[D])){y.insertAdjacentHTML(A[0],E);return y[A[1]]}}else{F=y.ownerDocument.createRange();G="setStart"+(v.test(D)?"After":"Before");if(B[D]){F[G](y);if(!F.createContextualFragment){H=s(E)}else{H=F.createContextualFragment(E)}y.parentNode.insertBefore(H,D==c?y:y.nextSibling);return y[(D==c?"previous":"next")+"Sibling"]}else{C=(D==n?"first":"last")+"Child";if(y.firstChild){F[G](y[C]);if(!F.createContextualFragment){H=s(E)}else{H=F.createContextualFragment(E)}if(D==n){y.insertBefore(H,y.firstChild)}else{y.appendChild(H)}}else{y.innerHTML=E}return y[C]}}throw'Illegal insertion point -> "'+D+'"'},insertBefore:function(y,A,z){return h(y,A,z,c)},insertAfter:function(y,A,z){return h(y,A,z,p,"nextSibling")},insertFirst:function(y,A,z){return h(y,A,z,n,"firstChild")},append:function(y,A,z){return h(y,A,z,q,"",true)},overwrite:function(y,A,z){y=Ext.getDom(y);y.innerHTML=u(A);return z?Ext.get(y.firstChild):y.firstChild},createHtml:u};return r}();Ext.Template=function(h){var j=this,c=arguments,e=[],d;if(Ext.isArray(h)){h=h.join("")}else{if(c.length>1){for(var g=0,b=c.length;g<b;g++){d=c[g];if(typeof d=="object"){Ext.apply(j,d)}else{e.push(d)}}h=e.join("")}}j.html=h;if(j.compiled){j.compile()}};Ext.Template.prototype={re:/\{([\w\-]+)\}/g,applyTemplate:function(a){var b=this;return b.compiled?b.compiled(a):b.html.replace(b.re,function(c,d){return a[d]!==undefined?a[d]:""})},set:function(a,c){var b=this;b.html=a;b.compiled=null;return c?b.compile():b},compile:function(){var me=this,sep=Ext.isGecko?"+":",";function fn(m,name){name="values['"+name+"']";return"'"+sep+"("+name+" == undefined ? '' : "+name+")"+sep+"'"}eval("this.compiled = function(values){ return "+(Ext.isGecko?"'":"['")+me.html.replace(/\\/g,"\\\\").replace(/(\r\n|\n)/g,"\\n").replace(/'/g,"\\'").replace(this.re,fn)+(Ext.isGecko?"';};":"'].join('');};"));return me},insertFirst:function(b,a,c){return this.doInsert("afterBegin",b,a,c)},insertBefore:function(b,a,c){return this.doInsert("beforeBegin",b,a,c)},insertAfter:function(b,a,c){return this.doInsert("afterEnd",b,a,c)},append:function(b,a,c){return this.doInsert("beforeEnd",b,a,c)},doInsert:function(c,e,b,a){e=Ext.getDom(e);var d=Ext.DomHelper.insertHtml(c,e,this.applyTemplate(b));return a?Ext.get(d,true):d},overwrite:function(b,a,c){b=Ext.getDom(b);b.innerHTML=this.applyTemplate(a);return c?Ext.get(b.firstChild,true):b.firstChild}};Ext.Template.prototype.apply=Ext.Template.prototype.applyTemplate;Ext.Template.from=function(b,a){b=Ext.getDom(b);return new Ext.Template(b.value||b.innerHTML,a||"")};Ext.DomQuery=function(){var cache={},simpleCache={},valueCache={},nonSpace=/\S/,trimRe=/^\s+|\s+$/g,tplRe=/\{(\d+)\}/g,modeRe=/^(\s?[\/>+~]\s?|\s|$)/,tagTokenRe=/^(#)?([\w\-\*]+)/,nthRe=/(\d*)n\+?(\d*)/,nthRe2=/\D/,isIE=window.ActiveXObject?true:false,key=30803;eval("var batch = 30803;");function child(parent,index){var i=0,n=parent.firstChild;while(n){if(n.nodeType==1){if(++i==index){return n}}n=n.nextSibling}return null}function next(n){while((n=n.nextSibling)&&n.nodeType!=1){}return n}function prev(n){while((n=n.previousSibling)&&n.nodeType!=1){}return n}function children(parent){var n=parent.firstChild,nodeIndex=-1,nextNode;while(n){nextNode=n.nextSibling;if(n.nodeType==3&&!nonSpace.test(n.nodeValue)){parent.removeChild(n)}else{n.nodeIndex=++nodeIndex}n=nextNode}return this}function byClassName(nodeSet,cls){if(!cls){return nodeSet}var result=[],ri=-1;for(var i=0,ci;ci=nodeSet[i];i++){if((" "+ci.className+" ").indexOf(cls)!=-1){result[++ri]=ci}}return result}function attrValue(n,attr){if(!n.tagName&&typeof n.length!="undefined"){n=n[0]}if(!n){return null}if(attr=="for"){return n.htmlFor}if(attr=="class"||attr=="className"){return n.className}return n.getAttribute(attr)||n[attr]}function getNodes(ns,mode,tagName){var result=[],ri=-1,cs;if(!ns){return result}tagName=tagName||"*";if(typeof ns.getElementsByTagName!="undefined"){ns=[ns]}if(!mode){for(var i=0,ni;ni=ns[i];i++){cs=ni.getElementsByTagName(tagName);for(var j=0,ci;ci=cs[j];j++){result[++ri]=ci}}}else{if(mode=="/"||mode==">"){var utag=tagName.toUpperCase();for(var i=0,ni,cn;ni=ns[i];i++){cn=ni.childNodes;for(var j=0,cj;cj=cn[j];j++){if(cj.nodeName==utag||cj.nodeName==tagName||tagName=="*"){result[++ri]=cj}}}}else{if(mode=="+"){var utag=tagName.toUpperCase();for(var i=0,n;n=ns[i];i++){while((n=n.nextSibling)&&n.nodeType!=1){}if(n&&(n.nodeName==utag||n.nodeName==tagName||tagName=="*")){result[++ri]=n}}}else{if(mode=="~"){var utag=tagName.toUpperCase();for(var i=0,n;n=ns[i];i++){while((n=n.nextSibling)){if(n.nodeName==utag||n.nodeName==tagName||tagName=="*"){result[++ri]=n}}}}}}}return result}function concat(a,b){if(b.slice){return a.concat(b)}for(var i=0,l=b.length;i<l;i++){a[a.length]=b[i]}return a}function byTag(cs,tagName){if(cs.tagName||cs==document){cs=[cs]}if(!tagName){return cs}var result=[],ri=-1;tagName=tagName.toLowerCase();for(var i=0,ci;ci=cs[i];i++){if(ci.nodeType==1&&ci.tagName.toLowerCase()==tagName){result[++ri]=ci}}return result}function byId(cs,id){if(cs.tagName||cs==document){cs=[cs]}if(!id){return cs}var result=[],ri=-1;for(var i=0,ci;ci=cs[i];i++){if(ci&&ci.id==id){result[++ri]=ci;return result}}return result}function byAttribute(cs,attr,value,op,custom){var result=[],ri=-1,useGetStyle=custom=="{",fn=Ext.DomQuery.operators[op],a,xml,hasXml;for(var i=0,ci;ci=cs[i];i++){if(ci.nodeType!=1){continue}if(!hasXml){xml=Ext.DomQuery.isXml(ci);hasXml=true}if(!xml){if(useGetStyle){a=Ext.DomQuery.getStyle(ci,attr)}else{if(attr=="class"||attr=="className"){a=ci.className}else{if(attr=="for"){a=ci.htmlFor}else{if(attr=="href"){a=ci.getAttribute("href",2)}else{a=ci.getAttribute(attr)}}}}}else{a=ci.getAttribute(attr)}if((fn&&fn(a,value))||(!fn&&a)){result[++ri]=ci}}return result}function byPseudo(cs,name,value){return Ext.DomQuery.pseudos[name](cs,value)}function nodupIEXml(cs){var d=++key,r;cs[0].setAttribute("_nodup",d);r=[cs[0]];for(var i=1,len=cs.length;i<len;i++){var c=cs[i];if(!c.getAttribute("_nodup")!=d){c.setAttribute("_nodup",d);r[r.length]=c}}for(var i=0,len=cs.length;i<len;i++){cs[i].removeAttribute("_nodup")}return r}function nodup(cs){if(!cs){return[]}var len=cs.length,c,i,r=cs,cj,ri=-1;if(!len||typeof cs.nodeType!="undefined"||len==1){return cs}if(isIE&&typeof cs[0].selectSingleNode!="undefined"){return nodupIEXml(cs)}var d=++key;cs[0]._nodup=d;for(i=1;c=cs[i];i++){if(c._nodup!=d){c._nodup=d}else{r=[];for(var j=0;j<i;j++){r[++ri]=cs[j]}for(j=i+1;cj=cs[j];j++){if(cj._nodup!=d){cj._nodup=d;r[++ri]=cj}}return r}}return r}function quickDiffIEXml(c1,c2){var d=++key,r=[];for(var i=0,len=c1.length;i<len;i++){c1[i].setAttribute("_qdiff",d)}for(var i=0,len=c2.length;i<len;i++){if(c2[i].getAttribute("_qdiff")!=d){r[r.length]=c2[i]}}for(var i=0,len=c1.length;i<len;i++){c1[i].removeAttribute("_qdiff")}return r}function quickDiff(c1,c2){var len1=c1.length,d=++key,r=[];if(!len1){return c2}if(isIE&&typeof c1[0].selectSingleNode!="undefined"){return quickDiffIEXml(c1,c2)}for(var i=0;i<len1;i++){c1[i]._qdiff=d}for(var i=0,len=c2.length;i<len;i++){if(c2[i]._qdiff!=d){r[r.length]=c2[i]}}return r}function quickId(ns,mode,root,id){if(ns==root){var d=root.ownerDocument||root;return d.getElementById(id)}ns=getNodes(ns,mode,"*");return byId(ns,id)}return{getStyle:function(el,name){return Ext.fly(el).getStyle(name)},compile:function(path,type){type=type||"select";var fn=["var f = function(root){\n var mode; ++batch; var n = root || document;\n"],mode,lastPath,matchers=Ext.DomQuery.matchers,matchersLn=matchers.length,modeMatch,lmode=path.match(modeRe);if(lmode&&lmode[1]){fn[fn.length]='mode="'+lmode[1].replace(trimRe,"")+'";';path=path.replace(lmode[1],"")}while(path.substr(0,1)=="/"){path=path.substr(1)}while(path&&lastPath!=path){lastPath=path;var tokenMatch=path.match(tagTokenRe);if(type=="select"){if(tokenMatch){if(tokenMatch[1]=="#"){fn[fn.length]='n = quickId(n, mode, root, "'+tokenMatch[2]+'");'}else{fn[fn.length]='n = getNodes(n, mode, "'+tokenMatch[2]+'");'}path=path.replace(tokenMatch[0],"")}else{if(path.substr(0,1)!="@"){fn[fn.length]='n = getNodes(n, mode, "*");'}}}else{if(tokenMatch){if(tokenMatch[1]=="#"){fn[fn.length]='n = byId(n, "'+tokenMatch[2]+'");'}else{fn[fn.length]='n = byTag(n, "'+tokenMatch[2]+'");'}path=path.replace(tokenMatch[0],"")}}while(!(modeMatch=path.match(modeRe))){var matched=false;for(var j=0;j<matchersLn;j++){var t=matchers[j];var m=path.match(t.re);if(m){fn[fn.length]=t.select.replace(tplRe,function(x,i){return m[i]});path=path.replace(m[0],"");matched=true;break}}if(!matched){throw'Error parsing selector, parsing failed at "'+path+'"'}}if(modeMatch[1]){fn[fn.length]='mode="'+modeMatch[1].replace(trimRe,"")+'";';path=path.replace(modeMatch[1],"")}}fn[fn.length]="return nodup(n);\n}";eval(fn.join(""));return f},jsSelect:function(path,root,type){root=root||document;if(typeof root=="string"){root=document.getElementById(root)}var paths=path.split(","),results=[];for(var i=0,len=paths.length;i<len;i++){var subPath=paths[i].replace(trimRe,"");if(!cache[subPath]){cache[subPath]=Ext.DomQuery.compile(subPath);if(!cache[subPath]){throw subPath+" is not a valid selector"}}var result=cache[subPath](root);if(result&&result!=document){results=results.concat(result)}}if(paths.length>1){return nodup(results)}return results},isXml:function(el){var docEl=(el?el.ownerDocument||el:0).documentElement;return docEl?docEl.nodeName!=="HTML":false},select:document.querySelectorAll?function(path,root,type){root=root||document;if(!Ext.DomQuery.isXml(root)){try{var cs=root.querySelectorAll(path);return Ext.toArray(cs)}catch(ex){}}return Ext.DomQuery.jsSelect.call(this,path,root,type)}:function(path,root,type){return Ext.DomQuery.jsSelect.call(this,path,root,type)},selectNode:function(path,root){return Ext.DomQuery.select(path,root)[0]},selectValue:function(path,root,defaultValue){path=path.replace(trimRe,"");if(!valueCache[path]){valueCache[path]=Ext.DomQuery.compile(path,"select")}var n=valueCache[path](root),v;n=n[0]?n[0]:n;if(typeof n.normalize=="function"){n.normalize()}v=(n&&n.firstChild?n.firstChild.nodeValue:null);return((v===null||v===undefined||v==="")?defaultValue:v)},selectNumber:function(path,root,defaultValue){var v=Ext.DomQuery.selectValue(path,root,defaultValue||0);return parseFloat(v)},is:function(el,ss){if(typeof el=="string"){el=document.getElementById(el)}var isArray=Ext.isArray(el),result=Ext.DomQuery.filter(isArray?el:[el],ss);return isArray?(result.length==el.length):(result.length>0)},filter:function(els,ss,nonMatches){ss=ss.replace(trimRe,"");if(!simpleCache[ss]){simpleCache[ss]=Ext.DomQuery.compile(ss,"simple")}var result=simpleCache[ss](els);return nonMatches?quickDiff(result,els):result},matchers:[{re:/^\.([\w\-]+)/,select:'n = byClassName(n, " {1} ");'},{re:/^\:([\w\-]+)(?:\(((?:[^\s>\/]*|.*?))\))?/,select:'n = byPseudo(n, "{1}", "{2}");'},{re:/^(?:([\[\{])(?:@)?([\w\-]+)\s?(?:(=|.=)\s?(["']?)(.*?)\4)?[\]\}])/,select:'n = byAttribute(n, "{2}", "{5}", "{3}", "{1}");'},{re:/^#([\w\-]+)/,select:'n = byId(n, "{1}");'},{re:/^@([\w\-]+)/,select:'return {firstChild:{nodeValue:attrValue(n, "{1}")}};'}],operators:{"=":function(a,v){return a==v},"!=":function(a,v){return a!=v},"^=":function(a,v){return a&&a.substr(0,v.length)==v},"$=":function(a,v){return a&&a.substr(a.length-v.length)==v},"*=":function(a,v){return a&&a.indexOf(v)!==-1},"%=":function(a,v){return(a%v)==0},"|=":function(a,v){return a&&(a==v||a.substr(0,v.length+1)==v+"-")},"~=":function(a,v){return a&&(" "+a+" ").indexOf(" "+v+" ")!=-1}},pseudos:{"first-child":function(c){var r=[],ri=-1,n;for(var i=0,ci;ci=n=c[i];i++){while((n=n.previousSibling)&&n.nodeType!=1){}if(!n){r[++ri]=ci}}return r},"last-child":function(c){var r=[],ri=-1,n;for(var i=0,ci;ci=n=c[i];i++){while((n=n.nextSibling)&&n.nodeType!=1){}if(!n){r[++ri]=ci}}return r},"nth-child":function(c,a){var r=[],ri=-1,m=nthRe.exec(a=="even"&&"2n"||a=="odd"&&"2n+1"||!nthRe2.test(a)&&"n+"+a||a),f=(m[1]||1)-0,l=m[2]-0;for(var i=0,n;n=c[i];i++){var pn=n.parentNode;if(batch!=pn._batch){var j=0;for(var cn=pn.firstChild;cn;cn=cn.nextSibling){if(cn.nodeType==1){cn.nodeIndex=++j}}pn._batch=batch}if(f==1){if(l==0||n.nodeIndex==l){r[++ri]=n}}else{if((n.nodeIndex+l)%f==0){r[++ri]=n}}}return r},"only-child":function(c){var r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){if(!prev(ci)&&!next(ci)){r[++ri]=ci}}return r},empty:function(c){var r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){var cns=ci.childNodes,j=0,cn,empty=true;while(cn=cns[j]){++j;if(cn.nodeType==1||cn.nodeType==3){empty=false;break}}if(empty){r[++ri]=ci}}return r},contains:function(c,v){var r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){if((ci.textContent||ci.innerText||"").indexOf(v)!=-1){r[++ri]=ci}}return r},nodeValue:function(c,v){var r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){if(ci.firstChild&&ci.firstChild.nodeValue==v){r[++ri]=ci}}return r},checked:function(c){var r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){if(ci.checked==true){r[++ri]=ci}}return r},not:function(c,ss){return Ext.DomQuery.filter(c,ss,true)},any:function(c,selectors){var ss=selectors.split("|"),r=[],ri=-1,s;for(var i=0,ci;ci=c[i];i++){for(var j=0;s=ss[j];j++){if(Ext.DomQuery.is(ci,s)){r[++ri]=ci;break}}}return r},odd:function(c){return this["nth-child"](c,"odd")},even:function(c){return this["nth-child"](c,"even")},nth:function(c,a){return c[a-1]||[]},first:function(c){return c[0]||[]},last:function(c){return c[c.length-1]||[]},has:function(c,ss){var s=Ext.DomQuery.select,r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){if(s(ss,ci).length>0){r[++ri]=ci}}return r},next:function(c,ss){var is=Ext.DomQuery.is,r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){var n=next(ci);if(n&&is(n,ss)){r[++ri]=ci}}return r},prev:function(c,ss){var is=Ext.DomQuery.is,r=[],ri=-1;for(var i=0,ci;ci=c[i];i++){var n=prev(ci);if(n&&is(n,ss)){r[++ri]=ci}}return r}}}}();Ext.query=Ext.DomQuery.select;Ext.util.DelayedTask=function(d,c,a){var e=this,g,b=function(){clearInterval(g);g=null;d.apply(c,a||[])};e.delay=function(i,k,j,h){e.cancel();d=k||d;c=j||c;a=h||a;g=setInterval(b,i)};e.cancel=function(){if(g){clearInterval(g);g=null}}};(function(){var h=document;Ext.Element=function(l,m){var n=typeof l=="string"?h.getElementById(l):l,o;if(!n){return null}o=n.id;if(!m&&o&&Ext.elCache[o]){return Ext.elCache[o].el}this.dom=n;this.id=o||Ext.id(n)};var d=Ext.DomHelper,e=Ext.Element,a=Ext.elCache;e.prototype={set:function(q,m){var n=this.dom,l,p,m=(m!==false)&&!!n.setAttribute;for(l in q){if(q.hasOwnProperty(l)){p=q[l];if(l=="style"){d.applyStyles(n,p)}else{if(l=="cls"){n.className=p}else{if(m){n.setAttribute(l,p)}else{n[l]=p}}}}}return this},defaultUnit:"px",is:function(l){return Ext.DomQuery.is(this.dom,l)},focus:function(o,n){var l=this,n=n||l.dom;try{if(Number(o)){l.focus.defer(o,null,[null,n])}else{n.focus()}}catch(m){}return l},blur:function(){try{this.dom.blur()}catch(l){}return this},getValue:function(l){var m=this.dom.value;return l?parseInt(m,10):m},addListener:function(l,o,n,m){Ext.EventManager.on(this.dom,l,o,n||this,m);return this},removeListener:function(l,n,m){Ext.EventManager.removeListener(this.dom,l,n,m||this);return this},removeAllListeners:function(){Ext.EventManager.removeAll(this.dom);return this},purgeAllListeners:function(){Ext.EventManager.purgeElement(this,true);return this},addUnits:function(l){if(l===""||l=="auto"||l===undefined){l=l||""}else{if(!isNaN(l)||!i.test(l)){l=l+(this.defaultUnit||"px")}}return l},load:function(m,n,l){Ext.Ajax.request(Ext.apply({params:n,url:m.url||m,callback:l,el:this.dom,indicatorText:m.indicatorText||""},Ext.isObject(m)?m:{}));return this},isBorderBox:function(){return Ext.isBorderBox||Ext.isForcedBorderBox||g[(this.dom.tagName||"").toLowerCase()]},remove:function(){var l=this,m=l.dom;if(m){delete l.dom;Ext.removeNode(m)}},hover:function(m,l,o,n){var p=this;p.on("mouseenter",m,o||p.dom,n);p.on("mouseleave",l,o||p.dom,n);return p},contains:function(l){return !l?false:Ext.lib.Dom.isAncestor(this.dom,l.dom?l.dom:l)},getAttributeNS:function(m,l){return this.getAttribute(l,m)},getAttribute:(function(){var p=document.createElement("table"),o=false,m="getAttribute" in p,l=/undefined|unknown/;if(m){try{p.getAttribute("ext:qtip")}catch(n){o=true}return function(q,s){var r=this.dom,t;if(r.getAttributeNS){t=r.getAttributeNS(s,q)||null}if(t==null){if(s){if(o&&r.tagName.toUpperCase()=="TABLE"){try{t=r.getAttribute(s+":"+q)}catch(u){t=""}}else{t=r.getAttribute(s+":"+q)}}else{t=r.getAttribute(q)||r[q]}}return t||""}}else{return function(q,s){var r=this.om,u,t;if(s){t=r[s+":"+q];u=l.test(typeof t)?undefined:t}else{u=r[q]}return u||""}}p=null})(),update:function(l){if(this.dom){this.dom.innerHTML=l}return this}};var k=e.prototype;e.addMethods=function(l){Ext.apply(k,l)};k.on=k.addListener;k.un=k.removeListener;k.autoBoxAdjust=true;var i=/\d+(px|em|%|en|ex|pt|in|cm|mm|pc)$/i,c;e.get=function(m){var l,p,o;if(!m){return null}if(typeof m=="string"){if(!(p=h.getElementById(m))){return null}if(a[m]&&a[m].el){l=a[m].el;l.dom=p}else{l=e.addToCache(new e(p))}return l}else{if(m.tagName){if(!(o=m.id)){o=Ext.id(m)}if(a[o]&&a[o].el){l=a[o].el;l.dom=m}else{l=e.addToCache(new e(m))}return l}else{if(m instanceof e){if(m!=c){if(Ext.isIE&&(m.id==undefined||m.id=="")){m.dom=m.dom}else{m.dom=h.getElementById(m.id)||m.dom}}return m}else{if(m.isComposite){return m}else{if(Ext.isArray(m)){return e.select(m)}else{if(m==h){if(!c){var n=function(){};n.prototype=e.prototype;c=new n();c.dom=h}return c}}}}}}return null};e.addToCache=function(l,m){m=m||l.id;a[m]={el:l,data:{},events:{}};return l};e.data=function(m,l,n){m=e.get(m);if(!m){return null}var o=a[m.id].data;if(arguments.length==2){return o[l]}else{return(o[l]=n)}};function j(){if(!Ext.enableGarbageCollector){clearInterval(e.collectorThreadId)}else{var l,n,q,p;for(l in a){p=a[l];if(p.skipGC){Ext.EventManager.removeFromSpecialCache(p.el);continue}n=p.el;q=n.dom;if(!q||!q.parentNode||(!q.offsetParent&&!h.getElementById(l))){if(Ext.enableListenerCollection){Ext.EventManager.removeAll(q)}delete a[l]}}if(Ext.isIE){var m={};for(l in a){m[l]=a[l]}a=Ext.elCache=m}}}e.collectorThreadId=setInterval(j,30000);var b=function(){};b.prototype=e.prototype;e.Flyweight=function(l){this.dom=l};e.Flyweight.prototype=new b();e.Flyweight.prototype.isFlyweight=true;e._flyweights={};e.fly=function(n,l){var m=null;l=l||"_global";if(n=Ext.getDom(n)){(e._flyweights[l]=e._flyweights[l]||new e.Flyweight()).dom=n;m=e._flyweights[l]}return m};Ext.get=e.get;Ext.fly=e.fly;var g=Ext.isStrict?{select:1}:{input:1,select:1,textarea:1};if(Ext.isIE||Ext.isGecko){g.button=1}})();Ext.Element.addMethods(function(){var d="parentNode",b="nextSibling",c="previousSibling",e=Ext.DomQuery,a=Ext.get;return{findParent:function(m,l,h){var j=this.dom,g=document.body,k=0,i;if(Ext.isGecko&&Object.prototype.toString.call(j)=="[object XULElement]"){return null}l=l||50;if(isNaN(l)){i=Ext.getDom(l);l=Number.MAX_VALUE}while(j&&j.nodeType==1&&k<l&&j!=g&&j!=i){if(e.is(j,m)){return h?a(j):j}k++;j=j.parentNode}return null},findParentNode:function(j,i,g){var h=Ext.fly(this.dom.parentNode,"_internal");return h?h.findParent(j,i,g):null},up:function(h,g){return this.findParentNode(h,g,true)},select:function(g){return Ext.Element.select(g,this.dom)},query:function(g){return e.select(g,this.dom)},child:function(g,h){var i=e.selectNode(g,this.dom);return h?i:a(i)},down:function(g,h){var i=e.selectNode(" > "+g,this.dom);return h?i:a(i)},parent:function(g,h){return this.matchNode(d,d,g,h)},next:function(g,h){return this.matchNode(b,b,g,h)},prev:function(g,h){return this.matchNode(c,c,g,h)},first:function(g,h){return this.matchNode(b,"firstChild",g,h)},last:function(g,h){return this.matchNode(c,"lastChild",g,h)},matchNode:function(h,k,g,i){var j=this.dom[k];while(j){if(j.nodeType==1&&(!g||e.is(j,g))){return !i?a(j):j}j=j[h]}return null}}}());Ext.Element.addMethods(function(){var c=Ext.getDom,a=Ext.get,b=Ext.DomHelper;return{appendChild:function(d){return a(d).appendTo(this)},appendTo:function(d){c(d).appendChild(this.dom);return this},insertBefore:function(d){(d=c(d)).parentNode.insertBefore(this.dom,d);return this},insertAfter:function(d){(d=c(d)).parentNode.insertBefore(this.dom,d.nextSibling);return this},insertFirst:function(e,d){e=e||{};if(e.nodeType||e.dom||typeof e=="string"){e=c(e);this.dom.insertBefore(e,this.dom.firstChild);return !d?a(e):e}else{return this.createChild(e,this.dom.firstChild,d)}},replace:function(d){d=a(d);this.insertBefore(d);d.remove();return this},replaceWith:function(d){var e=this;if(d.nodeType||d.dom||typeof d=="string"){d=c(d);e.dom.parentNode.insertBefore(d,e.dom)}else{d=b.insertBefore(e.dom,d)}delete Ext.elCache[e.id];Ext.removeNode(e.dom);e.id=Ext.id(e.dom=d);Ext.Element.addToCache(e.isFlyweight?new Ext.Element(e.dom):e);return e},createChild:function(e,d,g){e=e||{tag:"div"};return d?b.insertBefore(d,e,g!==true):b[!this.dom.firstChild?"overwrite":"append"](this.dom,e,g!==true)},wrap:function(d,e){var g=b.insertBefore(this.dom,d||{tag:"div"},!e);g.dom?g.dom.appendChild(this.dom):g.appendChild(this.dom);return g},insertHtml:function(e,g,d){var h=b.insertHtml(e,this.dom,g);return d?Ext.get(h):h}}}());Ext.Element.addMethods(function(){var A=Ext.supports,h={},x=/(-[a-z])/gi,s=document.defaultView,D=/alpha\(opacity=(.*)\)/i,l=/^\s+|\s+$/g,B=Ext.Element,u=/\s+/,b=/\w/g,d="padding",c="margin",y="border",t="-left",q="-right",w="-top",o="-bottom",j="-width",r=Math,z="hidden",e="isClipped",k="overflow",n="overflow-x",m="overflow-y",C="originalClip",i={l:y+t+j,r:y+q+j,t:y+w+j,b:y+o+j},g={l:d+t,r:d+q,t:d+w,b:d+o},a={l:c+t,r:c+q,t:c+w,b:c+o},E=Ext.Element.data;function p(F,G){return G.charAt(1).toUpperCase()}function v(F){return h[F]||(h[F]=F=="float"?(A.cssFloat?"cssFloat":"styleFloat"):F.replace(x,p))}return{adjustWidth:function(F){var G=this;var H=(typeof F=="number");if(H&&G.autoBoxAdjust&&!G.isBorderBox()){F-=(G.getBorderWidth("lr")+G.getPadding("lr"))}return(H&&F<0)?0:F},adjustHeight:function(F){var G=this;var H=(typeof F=="number");if(H&&G.autoBoxAdjust&&!G.isBorderBox()){F-=(G.getBorderWidth("tb")+G.getPadding("tb"))}return(H&&F<0)?0:F},addClass:function(J){var K=this,I,F,H,G=[];if(!Ext.isArray(J)){if(typeof J=="string"&&!this.hasClass(J)){K.dom.className+=" "+J}}else{for(I=0,F=J.length;I<F;I++){H=J[I];if(typeof H=="string"&&(" "+K.dom.className+" ").indexOf(" "+H+" ")==-1){G.push(H)}}if(G.length){K.dom.className+=" "+G.join(" ")}}return K},removeClass:function(K){var L=this,J,G,F,I,H;if(!Ext.isArray(K)){K=[K]}if(L.dom&&L.dom.className){H=L.dom.className.replace(l,"").split(u);for(J=0,F=K.length;J<F;J++){I=K[J];if(typeof I=="string"){I=I.replace(l,"");G=H.indexOf(I);if(G!=-1){H.splice(G,1)}}}L.dom.className=H.join(" ")}return L},radioClass:function(I){var J=this.dom.parentNode.childNodes,G,H,F;I=Ext.isArray(I)?I:[I];for(H=0,F=J.length;H<F;H++){G=J[H];if(G&&G.nodeType==1){Ext.fly(G,"_internal").removeClass(I)}}return this.addClass(I)},toggleClass:function(F){return this.hasClass(F)?this.removeClass(F):this.addClass(F)},hasClass:function(F){return F&&(" "+this.dom.className+" ").indexOf(" "+F+" ")!=-1},replaceClass:function(G,F){return this.removeClass(G).addClass(F)},isStyle:function(F,G){return this.getStyle(F)==G},getStyle:function(){return s&&s.getComputedStyle?function(K){var I=this.dom,F,H,G,J;if(I==document){return null}K=v(K);G=(F=I.style[K])?F:(H=s.getComputedStyle(I,""))?H[K]:null;if(K=="marginRight"&&G!="0px"&&!A.correctRightMargin){J=I.style.display;I.style.display="inline-block";G=s.getComputedStyle(I,"").marginRight;I.style.display=J}if(K=="backgroundColor"&&G=="rgba(0, 0, 0, 0)"&&!A.correctTransparentColor){G="transparent"}return G}:function(J){var H=this.dom,F,G;if(H==document){return null}if(J=="opacity"){if(H.style.filter.match){if(F=H.style.filter.match(D)){var I=parseFloat(F[1]);if(!isNaN(I)){return I?I/100:0}}}return 1}J=v(J);return H.style[J]||((G=H.currentStyle)?G[J]:null)}}(),getColor:function(F,G,K){var I=this.getStyle(F),H=(typeof K!="undefined")?K:"#",J;if(!I||(/transparent|inherit/.test(I))){return G}if(/^r/.test(I)){Ext.each(I.slice(4,I.length-1).split(","),function(L){J=parseInt(L,10);H+=(J<16?"0":"")+J.toString(16)})}else{I=I.replace("#","");H+=I.length==3?I.replace(/^(\w)(\w)(\w)$/,"$1$1$2$2$3$3"):I}return(H.length>5?H.toLowerCase():G)},setStyle:function(I,H){var F,G;if(typeof I!="object"){F={};F[I]=H;I=F}for(G in I){H=I[G];G=="opacity"?this.setOpacity(H):this.dom.style[v(G)]=H}return this},setOpacity:function(G,F){var J=this,H=J.dom.style;if(!F||!J.anim){if(Ext.isIE9m){var I=G<1?"alpha(opacity="+G*100+")":"",K=H.filter.replace(D,"").replace(l,"");H.zoom=1;H.filter=K+(K.length>0?" ":"")+I}else{H.opacity=G}}else{J.anim({opacity:{to:G}},J.preanim(arguments,1),null,0.35,"easeIn")}return J},clearOpacity:function(){var F=this.dom.style;if(Ext.isIE9m){if(!Ext.isEmpty(F.filter)){F.filter=F.filter.replace(D,"").replace(l,"")}}else{F.opacity=F["-moz-opacity"]=F["-khtml-opacity"]=""}return this},getHeight:function(H){var G=this,J=G.dom,I=Ext.isIE9m&&G.isStyle("display","none"),F=r.max(J.offsetHeight,I?0:J.clientHeight)||0;F=!H?F:F-G.getBorderWidth("tb")-G.getPadding("tb");return F<0?0:F},getWidth:function(G){var H=this,J=H.dom,I=Ext.isIE9m&&H.isStyle("display","none"),F=r.max(J.offsetWidth,I?0:J.clientWidth)||0;F=!G?F:F-H.getBorderWidth("lr")-H.getPadding("lr");return F<0?0:F},setWidth:function(G,F){var H=this;G=H.adjustWidth(G);!F||!H.anim?H.dom.style.width=H.addUnits(G):H.anim({width:{to:G}},H.preanim(arguments,1));return H},setHeight:function(F,G){var H=this;F=H.adjustHeight(F);!G||!H.anim?H.dom.style.height=H.addUnits(F):H.anim({height:{to:F}},H.preanim(arguments,1));return H},getBorderWidth:function(F){return this.addStyles(F,i)},getPadding:function(F){return this.addStyles(F,g)},clip:function(){var F=this,G=F.dom;if(!E(G,e)){E(G,e,true);E(G,C,{o:F.getStyle(k),x:F.getStyle(n),y:F.getStyle(m)});F.setStyle(k,z);F.setStyle(n,z);F.setStyle(m,z)}return F},unclip:function(){var F=this,H=F.dom;if(E(H,e)){E(H,e,false);var G=E(H,C);if(G.o){F.setStyle(k,G.o)}if(G.x){F.setStyle(n,G.x)}if(G.y){F.setStyle(m,G.y)}}return F},addStyles:function(M,L){var J=0,K=M.match(b),I,H,G,F=K.length;for(G=0;G<F;G++){I=K[G];H=I&&parseInt(this.getStyle(L[I]),10);if(H){J+=r.abs(H)}}return J},margins:a}}());(function(){var a=Ext.lib.Dom,b="left",g="right",d="top",i="bottom",h="position",c="static",e="relative",j="auto",k="z-index";Ext.Element.addMethods({getX:function(){return a.getX(this.dom)},getY:function(){return a.getY(this.dom)},getXY:function(){return a.getXY(this.dom)},getOffsetsTo:function(l){var n=this.getXY(),m=Ext.fly(l,"_internal").getXY();return[n[0]-m[0],n[1]-m[1]]},setX:function(l,m){return this.setXY([l,this.getY()],this.animTest(arguments,m,1))},setY:function(m,l){return this.setXY([this.getX(),m],this.animTest(arguments,l,1))},setLeft:function(l){this.setStyle(b,this.addUnits(l));return this},setTop:function(l){this.setStyle(d,this.addUnits(l));return this},setRight:function(l){this.setStyle(g,this.addUnits(l));return this},setBottom:function(l){this.setStyle(i,this.addUnits(l));return this},setXY:function(n,l){var m=this;if(!l||!m.anim){a.setXY(m.dom,n)}else{m.anim({points:{to:n}},m.preanim(arguments,1),"motion")}return m},setLocation:function(l,n,m){return this.setXY([l,n],this.animTest(arguments,m,2))},moveTo:function(l,n,m){return this.setXY([l,n],this.animTest(arguments,m,2))},getLeft:function(l){return !l?this.getX():parseInt(this.getStyle(b),10)||0},getRight:function(l){var m=this;return !l?m.getX()+m.getWidth():(m.getLeft(true)+m.getWidth())||0},getTop:function(l){return !l?this.getY():parseInt(this.getStyle(d),10)||0},getBottom:function(l){var m=this;return !l?m.getY()+m.getHeight():(m.getTop(true)+m.getHeight())||0},position:function(p,o,l,n){var m=this;if(!p&&m.isStyle(h,c)){m.setStyle(h,e)}else{if(p){m.setStyle(h,p)}}if(o){m.setStyle(k,o)}if(l||n){m.setXY([l||false,n||false])}},clearPositioning:function(l){l=l||"";this.setStyle({left:l,right:l,top:l,bottom:l,"z-index":"",position:c});return this},getPositioning:function(){var m=this.getStyle(b);var n=this.getStyle(d);return{position:this.getStyle(h),left:m,right:m?"":this.getStyle(g),top:n,bottom:n?"":this.getStyle(i),"z-index":this.getStyle(k)}},setPositioning:function(l){var n=this,m=n.dom.style;n.setStyle(l);if(l.right==j){m.right=""}if(l.bottom==j){m.bottom=""}return n},translatePoints:function(m,u){u=isNaN(m[1])?u:m[1];m=isNaN(m[0])?m:m[0];var q=this,r=q.isStyle(h,e),s=q.getXY(),n=parseInt(q.getStyle(b),10),p=parseInt(q.getStyle(d),10);n=!isNaN(n)?n:(r?0:q.dom.offsetLeft);p=!isNaN(p)?p:(r?0:q.dom.offsetTop);return{left:(m-s[0]+n),top:(u-s[1]+p)}},animTest:function(m,l,n){return !!l&&this.preanim?this.preanim(m,n):false}})})();Ext.Element.addMethods({isScrollable:function(){var a=this.dom;return a.scrollHeight>a.clientHeight||a.scrollWidth>a.clientWidth},scrollTo:function(a,b){this.dom["scroll"+(/top/i.test(a)?"Top":"Left")]=b;return this},getScroll:function(){var i=this.dom,h=document,a=h.body,c=h.documentElement,b,g,e;if(i==h||i==a){if(Ext.isIE&&Ext.isStrict){b=c.scrollLeft;g=c.scrollTop}else{b=window.pageXOffset;g=window.pageYOffset}e={left:b||(a?a.scrollLeft:0),top:g||(a?a.scrollTop:0)}}else{e={left:i.scrollLeft,top:i.scrollTop}}return e}});Ext.Element.VISIBILITY=1;Ext.Element.DISPLAY=2;Ext.Element.OFFSETS=3;Ext.Element.ASCLASS=4;Ext.Element.visibilityCls="x-hide-nosize";Ext.Element.addMethods(function(){var e=Ext.Element,p="opacity",j="visibility",g="display",d="hidden",n="offsets",k="asclass",m="none",a="nosize",b="originalDisplay",c="visibilityMode",h="isVisible",i=e.data,l=function(r){var q=i(r,b);if(q===undefined){i(r,b,q="")}return q},o=function(r){var q=i(r,c);if(q===undefined){i(r,c,q=1)}return q};return{originalDisplay:"",visibilityMode:1,setVisibilityMode:function(q){i(this.dom,c,q);return this},animate:function(r,t,s,u,q){this.anim(r,{duration:t,callback:s,easing:u},q);return this},anim:function(t,u,r,w,s,q){r=r||"run";u=u||{};var v=this,x=Ext.lib.Anim[r](v.dom,t,(u.duration||w)||0.35,(u.easing||s)||"easeOut",function(){if(q){q.call(v)}if(u.callback){u.callback.call(u.scope||v,v,u)}},v);u.anim=x;return x},preanim:function(q,r){return !q[r]?false:(typeof q[r]=="object"?q[r]:{duration:q[r+1],callback:q[r+2],easing:q[r+3]})},isVisible:function(){var q=this,s=q.dom,r=i(s,h);if(typeof r=="boolean"){return r}r=!q.isStyle(j,d)&&!q.isStyle(g,m)&&!((o(s)==e.ASCLASS)&&q.hasClass(q.visibilityCls||e.visibilityCls));i(s,h,r);return r},setVisible:function(t,q){var w=this,r,y,x,v,u=w.dom,s=o(u);if(typeof q=="string"){switch(q){case g:s=e.DISPLAY;break;case j:s=e.VISIBILITY;break;case n:s=e.OFFSETS;break;case a:case k:s=e.ASCLASS;break}w.setVisibilityMode(s);q=false}if(!q||!w.anim){if(s==e.ASCLASS){w[t?"removeClass":"addClass"](w.visibilityCls||e.visibilityCls)}else{if(s==e.DISPLAY){return w.setDisplayed(t)}else{if(s==e.OFFSETS){if(!t){w.hideModeStyles={position:w.getStyle("position"),top:w.getStyle("top"),left:w.getStyle("left")};w.applyStyles({position:"absolute",top:"-10000px",left:"-10000px"})}else{w.applyStyles(w.hideModeStyles||{position:"",top:"",left:""});delete w.hideModeStyles}}else{w.fixDisplay();u.style.visibility=t?"visible":d}}}}else{if(t){w.setOpacity(0.01);w.setVisible(true)}w.anim({opacity:{to:(t?1:0)}},w.preanim(arguments,1),null,0.35,"easeIn",function(){t||w.setVisible(false).setOpacity(1)})}i(u,h,t);return w},hasMetrics:function(){var q=this.dom;return this.isVisible()||(o(q)==e.VISIBILITY)},toggle:function(q){var r=this;r.setVisible(!r.isVisible(),r.preanim(arguments,0));return r},setDisplayed:function(q){if(typeof q=="boolean"){q=q?l(this.dom):m}this.setStyle(g,q);return this},fixDisplay:function(){var q=this;if(q.isStyle(g,m)){q.setStyle(j,d);q.setStyle(g,l(this.dom));if(q.isStyle(g,m)){q.setStyle(g,"block")}}},hide:function(q){if(typeof q=="string"){this.setVisible(false,q);return this}this.setVisible(false,this.preanim(arguments,0));return this},show:function(q){if(typeof q=="string"){this.setVisible(true,q);return this}this.setVisible(true,this.preanim(arguments,0));return this}}}());(function(){var y=null,A=undefined,k=true,t=false,j="setX",h="setY",a="setXY",n="left",l="bottom",s="top",m="right",q="height",g="width",i="points",w="hidden",z="absolute",u="visible",e="motion",o="position",r="easeOut",d=new Ext.Element.Flyweight(),v={},x=function(B){return B||{}},p=function(B){d.dom=B;d.id=Ext.id(B);return d},c=function(B){if(!v[B]){v[B]=[]}return v[B]},b=function(C,B){v[C]=B};Ext.enableFx=k;Ext.Fx={switchStatements:function(C,D,B){return D.apply(this,B[C])},slideIn:function(H,E){E=x(E);var J=this,G=J.dom,M=G.style,O,B,L,D,C,M,I,N,K,F;H=H||"t";J.queueFx(E,function(){O=p(G).getXY();p(G).fixDisplay();B=p(G).getFxRestore();L={x:O[0],y:O[1],0:O[0],1:O[1],width:G.offsetWidth,height:G.offsetHeight};L.right=L.x+L.width;L.bottom=L.y+L.height;p(G).setWidth(L.width).setHeight(L.height);D=p(G).fxWrap(B.pos,E,w);M.visibility=u;M.position=z;function P(){p(G).fxUnwrap(D,B.pos,E);M.width=B.width;M.height=B.height;p(G).afterFx(E)}N={to:[L.x,L.y]};K={to:L.width};F={to:L.height};function Q(U,R,V,S,X,Z,ac,ab,aa,W,T){var Y={};p(U).setWidth(V).setHeight(S);if(p(U)[X]){p(U)[X](Z)}R[ac]=R[ab]="0";if(aa){Y.width=aa}if(W){Y.height=W}if(T){Y.points=T}return Y}I=p(G).switchStatements(H.toLowerCase(),Q,{t:[D,M,L.width,0,y,y,n,l,y,F,y],l:[D,M,0,L.height,y,y,m,s,K,y,y],r:[D,M,L.width,L.height,j,L.right,n,s,y,y,N],b:[D,M,L.width,L.height,h,L.bottom,n,s,y,F,N],tl:[D,M,0,0,y,y,m,l,K,F,N],bl:[D,M,0,0,h,L.y+L.height,m,s,K,F,N],br:[D,M,0,0,a,[L.right,L.bottom],n,s,K,F,N],tr:[D,M,0,0,j,L.x+L.width,n,l,K,F,N]});M.visibility=u;p(D).show();arguments.callee.anim=p(D).fxanim(I,E,e,0.5,r,P)});return J},slideOut:function(F,D){D=x(D);var H=this,E=H.dom,K=E.style,L=H.getXY(),C,B,I,J,G={to:0};F=F||"t";H.queueFx(D,function(){B=p(E).getFxRestore();I={x:L[0],y:L[1],0:L[0],1:L[1],width:E.offsetWidth,height:E.offsetHeight};I.right=I.x+I.width;I.bottom=I.y+I.height;p(E).setWidth(I.width).setHeight(I.height);C=p(E).fxWrap(B.pos,D,u);K.visibility=u;K.position=z;p(C).setWidth(I.width).setHeight(I.height);function M(){D.useDisplay?p(E).setDisplayed(t):p(E).hide();p(E).fxUnwrap(C,B.pos,D);K.width=B.width;K.height=B.height;p(E).afterFx(D)}function N(O,W,U,X,S,V,R,T,Q){var P={};O[W]=O[U]="0";P[X]=S;if(V){P[V]=R}if(T){P[T]=Q}return P}J=p(E).switchStatements(F.toLowerCase(),N,{t:[K,n,l,q,G],l:[K,m,s,g,G],r:[K,n,s,g,G,i,{to:[I.right,I.y]}],b:[K,n,s,q,G,i,{to:[I.x,I.bottom]}],tl:[K,m,l,g,G,q,G],bl:[K,m,s,g,G,q,G,i,{to:[I.x,I.bottom]}],br:[K,n,s,g,G,q,G,i,{to:[I.x+I.width,I.bottom]}],tr:[K,n,l,g,G,q,G,i,{to:[I.right,I.y]}]});arguments.callee.anim=p(C).fxanim(J,D,e,0.5,r,M)});return H},puff:function(H){H=x(H);var F=this,G=F.dom,C=G.style,D,B,E;F.queueFx(H,function(){D=p(G).getWidth();B=p(G).getHeight();p(G).clearOpacity();p(G).show();E=p(G).getFxRestore();function I(){H.useDisplay?p(G).setDisplayed(t):p(G).hide();p(G).clearOpacity();p(G).setPositioning(E.pos);C.width=E.width;C.height=E.height;C.fontSize="";p(G).afterFx(H)}arguments.callee.anim=p(G).fxanim({width:{to:p(G).adjustWidth(D*2)},height:{to:p(G).adjustHeight(B*2)},points:{by:[-D*0.5,-B*0.5]},opacity:{to:0},fontSize:{to:200,unit:"%"}},H,e,0.5,r,I)});return F},switchOff:function(F){F=x(F);var D=this,E=D.dom,B=E.style,C;D.queueFx(F,function(){p(E).clearOpacity();p(E).clip();C=p(E).getFxRestore();function G(){F.useDisplay?p(E).setDisplayed(t):p(E).hide();p(E).clearOpacity();p(E).setPositioning(C.pos);B.width=C.width;B.height=C.height;p(E).afterFx(F)}p(E).fxanim({opacity:{to:0.3}},y,y,0.1,y,function(){p(E).clearOpacity();(function(){p(E).fxanim({height:{to:1},points:{by:[0,p(E).getHeight()*0.5]}},F,e,0.3,"easeIn",G)}).defer(100)})});return D},highlight:function(D,H){H=x(H);var F=this,G=F.dom,B=H.attr||"backgroundColor",C={},E;F.queueFx(H,function(){p(G).clearOpacity();p(G).show();function I(){G.style[B]=E;p(G).afterFx(H)}E=G.style[B];C[B]={from:D||"ffff9c",to:H.endColor||p(G).getColor(B)||"ffffff"};arguments.callee.anim=p(G).fxanim(C,H,"color",1,"easeIn",I)});return F},frame:function(B,E,H){H=x(H);var D=this,G=D.dom,C,F;D.queueFx(H,function(){B=B||"#C3DAF9";if(B.length==6){B="#"+B}E=E||1;p(G).show();var L=p(G).getXY(),J={x:L[0],y:L[1],0:L[0],1:L[1],width:G.offsetWidth,height:G.offsetHeight},I=function(){C=p(document.body||document.documentElement).createChild({style:{position:z,"z-index":35000,border:"0px solid "+B}});return C.queueFx({},K)};arguments.callee.anim={isAnimated:true,stop:function(){E=0;C.stopFx()}};function K(){var M=Ext.isBorderBox?2:1;F=C.anim({top:{from:J.y,to:J.y-20},left:{from:J.x,to:J.x-20},borderWidth:{from:0,to:10},opacity:{from:1,to:0},height:{from:J.height,to:J.height+20*M},width:{from:J.width,to:J.width+20*M}},{duration:H.duration||1,callback:function(){C.remove();--E>0?I():p(G).afterFx(H)}});arguments.callee.anim={isAnimated:true,stop:function(){F.stop()}}}I()});return D},pause:function(D){var C=this.dom,B;this.queueFx({},function(){B=setTimeout(function(){p(C).afterFx({})},D*1000);arguments.callee.anim={isAnimated:true,stop:function(){clearTimeout(B);p(C).afterFx({})}}});return this},fadeIn:function(D){D=x(D);var B=this,C=B.dom,E=D.endOpacity||1;B.queueFx(D,function(){p(C).setOpacity(0);p(C).fixDisplay();C.style.visibility=u;arguments.callee.anim=p(C).fxanim({opacity:{to:E}},D,y,0.5,r,function(){if(E==1){p(C).clearOpacity()}p(C).afterFx(D)})});return B},fadeOut:function(E){E=x(E);var C=this,D=C.dom,B=D.style,F=E.endOpacity||0;C.queueFx(E,function(){arguments.callee.anim=p(D).fxanim({opacity:{to:F}},E,y,0.5,r,function(){if(F==0){Ext.Element.data(D,"visibilityMode")==Ext.Element.DISPLAY||E.useDisplay?B.display="none":B.visibility=w;p(D).clearOpacity()}p(D).afterFx(E)})});return C},scale:function(B,C,D){this.shift(Ext.apply({},D,{width:B,height:C}));return this},shift:function(D){D=x(D);var C=this.dom,B={};this.queueFx(D,function(){for(var E in D){if(D[E]!=A){B[E]={to:D[E]}}}B.width?B.width.to=p(C).adjustWidth(D.width):B;B.height?B.height.to=p(C).adjustWidth(D.height):B;if(B.x||B.y||B.xy){B.points=B.xy||{to:[B.x?B.x.to:p(C).getX(),B.y?B.y.to:p(C).getY()]}}arguments.callee.anim=p(C).fxanim(B,D,e,0.35,r,function(){p(C).afterFx(D)})});return this},ghost:function(E,C){C=x(C);var G=this,D=G.dom,J=D.style,H={opacity:{to:0},points:{}},K=H.points,B,I,F;E=E||"b";G.queueFx(C,function(){B=p(D).getFxRestore();I=p(D).getWidth();F=p(D).getHeight();function L(){C.useDisplay?p(D).setDisplayed(t):p(D).hide();p(D).clearOpacity();p(D).setPositioning(B.pos);J.width=B.width;J.height=B.height;p(D).afterFx(C)}K.by=p(D).switchStatements(E.toLowerCase(),function(N,M){return[N,M]},{t:[0,-F],l:[-I,0],r:[I,0],b:[0,F],tl:[-I,-F],bl:[-I,F],br:[I,F],tr:[I,-F]});arguments.callee.anim=p(D).fxanim(H,C,e,0.5,r,L)});return G},syncFx:function(){var B=this;B.fxDefaults=Ext.apply(B.fxDefaults||{},{block:t,concurrent:k,stopFx:t});return B},sequenceFx:function(){var B=this;B.fxDefaults=Ext.apply(B.fxDefaults||{},{block:t,concurrent:t,stopFx:t});return B},nextFx:function(){var B=c(this.dom.id)[0];if(B){B.call(this)}},hasActiveFx:function(){return c(this.dom.id)[0]},stopFx:function(B){var C=this,E=C.dom.id;if(C.hasActiveFx()){var D=c(E)[0];if(D&&D.anim){if(D.anim.isAnimated){b(E,[D]);D.anim.stop(B!==undefined?B:k)}else{b(E,[])}}}return C},beforeFx:function(B){if(this.hasActiveFx()&&!B.concurrent){if(B.stopFx){this.stopFx();return k}return t}return k},hasFxBlock:function(){var B=c(this.dom.id);return B&&B[0]&&B[0].block},queueFx:function(E,B){var C=p(this.dom);if(!C.hasFxBlock()){Ext.applyIf(E,C.fxDefaults);if(!E.concurrent){var D=C.beforeFx(E);B.block=E.block;c(C.dom.id).push(B);if(D){C.nextFx()}}else{B.call(C)}}return C},fxWrap:function(H,F,D){var E=this.dom,C,B;if(!F.wrap||!(C=Ext.getDom(F.wrap))){if(F.fixPosition){B=p(E).getXY()}var G=document.createElement("div");G.style.visibility=D;C=E.parentNode.insertBefore(G,E);p(C).setPositioning(H);if(p(C).isStyle(o,"static")){p(C).position("relative")}p(E).clearPositioning("auto");p(C).clip();C.appendChild(E);if(B){p(C).setXY(B)}}return C},fxUnwrap:function(C,F,E){var D=this.dom;p(D).clearPositioning();p(D).setPositioning(F);if(!E.wrap){var B=p(C).dom.parentNode;B.insertBefore(D,C);p(C).remove()}},getFxRestore:function(){var B=this.dom.style;return{pos:this.getPositioning(),width:B.width,height:B.height}},afterFx:function(C){var B=this.dom,D=B.id;if(C.afterStyle){p(B).setStyle(C.afterStyle)}if(C.afterCls){p(B).addClass(C.afterCls)}if(C.remove==k){p(B).remove()}if(C.callback){C.callback.call(C.scope,p(B))}if(!C.concurrent){c(D).shift();p(B).nextFx()}},fxanim:function(E,F,C,G,D,B){C=C||"run";F=F||{};var H=Ext.lib.Anim[C](this.dom,E,(F.duration||G)||0.35,(F.easing||D)||r,B,this);F.anim=H;return H}};Ext.Fx.resize=Ext.Fx.scale;Ext.Element.addMethods(Ext.Fx)})();Ext.CompositeElementLite=function(b,a){this.elements=[];this.add(b,a);this.el=new Ext.Element.Flyweight()};Ext.CompositeElementLite.prototype={isComposite:true,getElement:function(a){var b=this.el;b.dom=a;b.id=a.id;return b},transformElement:function(a){return Ext.getDom(a)},getCount:function(){return this.elements.length},add:function(d,b){var e=this,g=e.elements;if(!d){return this}if(typeof d=="string"){d=Ext.Element.selectorFunction(d,b)}else{if(d.isComposite){d=d.elements}else{if(!Ext.isIterable(d)){d=[d]}}}for(var c=0,a=d.length;c<a;++c){g.push(e.transformElement(d[c]))}return e},invoke:function(g,b){var h=this,d=h.elements,a=d.length,j,c;for(c=0;c<a;c++){j=d[c];if(j){Ext.Element.prototype[g].apply(h.getElement(j),b)}}return h},item:function(b){var d=this,c=d.elements[b],a=null;if(c){a=d.getElement(c)}return a},addListener:function(b,j,h,g){var d=this.elements,a=d.length,c,k;for(c=0;c<a;c++){k=d[c];if(k){Ext.EventManager.on(k,b,j,h||k,g)}}return this},each:function(g,d){var h=this,c=h.elements,a=c.length,b,j;for(b=0;b<a;b++){j=c[b];if(j){j=this.getElement(j);if(g.call(d||j,j,h,b)===false){break}}}return h},fill:function(a){var b=this;b.elements=[];b.add(a);return b},filter:function(a){var b=[],d=this,c=Ext.isFunction(a)?a:function(e){return e.is(a)};d.each(function(h,e,g){if(c(h,g)!==false){b[b.length]=d.transformElement(h)}});d.elements=b;return d},indexOf:function(a){return this.elements.indexOf(this.transformElement(a))},replaceElement:function(e,c,a){var b=!isNaN(e)?e:this.indexOf(e),g;if(b>-1){c=Ext.getDom(c);if(a){g=this.elements[b];g.parentNode.insertBefore(c,g);Ext.removeNode(g)}this.elements.splice(b,1,c)}return this},clear:function(){this.elements=[]}};Ext.CompositeElementLite.prototype.on=Ext.CompositeElementLite.prototype.addListener;Ext.CompositeElementLite.importElementMethods=function(){var c,b=Ext.Element.prototype,a=Ext.CompositeElementLite.prototype;for(c in b){if(typeof b[c]=="function"){(function(d){a[d]=a[d]||function(){return this.invoke(d,arguments)}}).call(a,c)}}};Ext.CompositeElementLite.importElementMethods();if(Ext.DomQuery){Ext.Element.selectorFunction=Ext.DomQuery.select}Ext.Element.select=function(a,b){var c;if(typeof a=="string"){c=Ext.Element.selectorFunction(a,b)}else{if(a.length!==undefined){c=a}else{throw"Invalid selector"}}return new Ext.CompositeElementLite(c)};Ext.select=Ext.Element.select;(function(){var b="beforerequest",e="requestcomplete",d="requestexception",h=undefined,c="load",i="POST",a="GET",g=window;Ext.data.Connection=function(j){Ext.apply(this,j);this.addEvents(b,e,d);Ext.data.Connection.superclass.constructor.call(this)};Ext.extend(Ext.data.Connection,Ext.util.Observable,{timeout:30000,autoAbort:false,disableCaching:true,disableCachingParam:"_dc",request:function(n){var s=this;if(s.fireEvent(b,s,n)){if(n.el){if(!Ext.isEmpty(n.indicatorText)){s.indicatorText='<div class="loading-indicator">'+n.indicatorText+"</div>"}if(s.indicatorText){Ext.getDom(n.el).innerHTML=s.indicatorText}n.success=(Ext.isFunction(n.success)?n.success:function(){}).createInterceptor(function(o){Ext.getDom(n.el).innerHTML=o.responseText})}var l=n.params,k=n.url||s.url,j,q={success:s.handleResponse,failure:s.handleFailure,scope:s,argument:{options:n},timeout:Ext.num(n.timeout,s.timeout)},m,t;if(Ext.isFunction(l)){l=l.call(n.scope||g,n)}l=Ext.urlEncode(s.extraParams,Ext.isObject(l)?Ext.urlEncode(l):l);if(Ext.isFunction(k)){k=k.call(n.scope||g,n)}if((m=Ext.getDom(n.form))){k=k||m.action;if(n.isUpload||(/multipart\/form-data/i.test(m.getAttribute("enctype")))){return s.doFormUpload.call(s,n,l,k)}t=Ext.lib.Ajax.serializeForm(m);l=l?(l+"&"+t):t}j=n.method||s.method||((l||n.xmlData||n.jsonData)?i:a);if(j===a&&(s.disableCaching&&n.disableCaching!==false)||n.disableCaching===true){var r=n.disableCachingParam||s.disableCachingParam;k=Ext.urlAppend(k,r+"="+(new Date().getTime()))}n.headers=Ext.applyIf(n.headers||{},s.defaultHeaders||{});if(n.autoAbort===true||s.autoAbort){s.abort()}if((j==a||n.xmlData||n.jsonData)&&l){k=Ext.urlAppend(k,l);l=""}return(s.transId=Ext.lib.Ajax.request(j,k,q,l,n))}else{return n.callback?n.callback.apply(n.scope,[n,h,h]):null}},isLoading:function(j){return j?Ext.lib.Ajax.isCallInProgress(j):!!this.transId},abort:function(j){if(j||this.isLoading()){Ext.lib.Ajax.abort(j||this.transId)}},handleResponse:function(j){this.transId=false;var k=j.argument.options;j.argument=k?k.argument:null;this.fireEvent(e,this,j,k);if(k.success){k.success.call(k.scope,j,k)}if(k.callback){k.callback.call(k.scope,k,true,j)}},handleFailure:function(j,l){this.transId=false;var k=j.argument.options;j.argument=k?k.argument:null;this.fireEvent(d,this,j,k,l);if(k.failure){k.failure.call(k.scope,j,k)}if(k.callback){k.callback.call(k.scope,k,false,j)}},doFormUpload:function(q,j,k){var l=Ext.id(),v=document,r=v.createElement("iframe"),m=Ext.getDom(q.form),u=[],t,p="multipart/form-data",n={target:m.target,method:m.method,encoding:m.encoding,enctype:m.enctype,action:m.action};Ext.fly(r).set({id:l,name:l,cls:"x-hidden",src:Ext.SSL_SECURE_URL});v.body.appendChild(r);if(Ext.isIE){document.frames[l].name=l}Ext.fly(m).set({target:l,method:i,enctype:p,encoding:p,action:k||n.action});Ext.iterate(Ext.urlDecode(j,false),function(w,o){t=v.createElement("input");Ext.fly(t).set({type:"hidden",value:o,name:w});m.appendChild(t);u.push(t)});function s(){var x=this,w={responseText:"",responseXML:null,argument:q.argument},A,z;try{A=r.contentWindow.document||r.contentDocument||g.frames[l].document;if(A){if(A.body){if(/textarea/i.test((z=A.body.firstChild||{}).tagName)){w.responseText=z.value}else{w.responseText=A.body.innerHTML}}w.responseXML=A.XMLDocument||A}}catch(y){}Ext.EventManager.removeListener(r,c,s,x);x.fireEvent(e,x,w,q);function o(D,C,B){if(Ext.isFunction(D)){D.apply(C,B)}}o(q.success,q.scope,[w,q]);o(q.callback,q.scope,[q,true,w]);if(!x.debugUploads){setTimeout(function(){Ext.removeNode(r)},100)}}Ext.EventManager.on(r,c,s,this);m.submit();Ext.fly(m).set(n);Ext.each(u,function(o){Ext.removeNode(o)})}})})();Ext.Ajax=new Ext.data.Connection({autoAbort:false,serializeForm:function(a){return Ext.lib.Ajax.serializeForm(a)}});Ext.util.JSON=new (function(){var useHasOwn=!!{}.hasOwnProperty,isNative=function(){var useNative=null;return function(){if(useNative===null){useNative=Ext.USE_NATIVE_JSON&&window.JSON&&JSON.toString()=="[object JSON]"}return useNative}}(),pad=function(n){return n<10?"0"+n:n},doDecode=function(json){return json?eval("("+json+")"):""},doEncode=function(o){if(!Ext.isDefined(o)||o===null){return"null"}else{if(Ext.isArray(o)){return encodeArray(o)}else{if(Ext.isDate(o)){return Ext.util.JSON.encodeDate(o)}else{if(Ext.isString(o)){return encodeString(o)}else{if(typeof o=="number"){return isFinite(o)?String(o):"null"}else{if(Ext.isBoolean(o)){return String(o)}else{var a=["{"],b,i,v;for(i in o){if(!o.getElementsByTagName){if(!useHasOwn||o.hasOwnProperty(i)){v=o[i];switch(typeof v){case"undefined":case"function":case"unknown":break;default:if(b){a.push(",")}a.push(doEncode(i),":",v===null?"null":doEncode(v));b=true}}}}a.push("}");return a.join("")}}}}}}},m={"\b":"\\b","\t":"\\t","\n":"\\n","\f":"\\f","\r":"\\r",'"':'\\"',"\\":"\\\\"},encodeString=function(s){if(/["\\\x00-\x1f]/.test(s)){return'"'+s.replace(/([\x00-\x1f\\"])/g,function(a,b){var c=m[b];if(c){return c}c=b.charCodeAt();return"\\u00"+Math.floor(c/16).toString(16)+(c%16).toString(16)})+'"'}return'"'+s+'"'},encodeArray=function(o){var a=["["],b,i,l=o.length,v;for(i=0;i<l;i+=1){v=o[i];switch(typeof v){case"undefined":case"function":case"unknown":break;default:if(b){a.push(",")}a.push(v===null?"null":Ext.util.JSON.encode(v));b=true}}a.push("]");return a.join("")};this.encodeDate=function(o){return'"'+o.getFullYear()+"-"+pad(o.getMonth()+1)+"-"+pad(o.getDate())+"T"+pad(o.getHours())+":"+pad(o.getMinutes())+":"+pad(o.getSeconds())+'"'};this.encode=function(){var ec;return function(o){if(!ec){ec=isNative()?JSON.stringify:doEncode}return ec(o)}}();this.decode=function(){var dc;return function(json){if(!dc){dc=isNative()?JSON.parse:doDecode}return dc(json)}}()})();Ext.encode=Ext.util.JSON.encode;Ext.decode=Ext.util.JSON.decode;Ext.EventManager=function(){var z,p,j=false,l=Ext.isGecko||Ext.isWebKit||Ext.isSafari||Ext.isIE10p,o=Ext.lib.Event,q=Ext.lib.Dom,c=document,A=window,r="DOMContentLoaded",t="complete",g=/^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,u=[];function n(E){var H=false,D=0,C=u.length,F=false,G;if(E){if(E.getElementById||E.navigator){for(;D<C;++D){G=u[D];if(G.el===E){H=G.id;break}}if(!H){H=Ext.id(E);u.push({id:H,el:E});F=true}}else{H=Ext.id(E)}if(!Ext.elCache[H]){Ext.Element.addToCache(new Ext.Element(E),H);if(F){Ext.elCache[H].skipGC=true}}}return H}function m(E,G,J,F,D,L){E=Ext.getDom(E);var C=n(E),K=Ext.elCache[C].events,H;H=o.on(E,G,D);K[G]=K[G]||[];K[G].push([J,D,L,H,F]);if(E.addEventListener&&G=="mousewheel"){var I=["DOMMouseScroll",D,false];E.addEventListener.apply(E,I);Ext.EventManager.addListener(A,"unload",function(){E.removeEventListener.apply(E,I)})}if(E==c&&G=="mousedown"){Ext.EventManager.stoppedMouseDownEvent.addListener(D)}}function d(){if(window!=top){return false}try{c.documentElement.doScroll("left")}catch(C){return false}b();return true}function B(C){if(Ext.isIE9m&&d()){return true}if(c.readyState==t){b();return true}j||(p=setTimeout(arguments.callee,2));return false}var k;function i(C){k||(k=Ext.query("style, link[rel=stylesheet]"));if(k.length==c.styleSheets.length){b();return true}j||(p=setTimeout(arguments.callee,2));return false}function y(C){c.removeEventListener(r,arguments.callee,false);i()}function b(C){if(!j){j=true;if(p){clearTimeout(p)}if(l){c.removeEventListener(r,b,false)}if(Ext.isIE9m&&B.bindIE){c.detachEvent("onreadystatechange",B)}o.un(A,"load",arguments.callee)}if(z&&!Ext.isReady){Ext.isReady=true;z.fire();z.listeners=[]}}function a(){z||(z=new Ext.util.Event());if(l){c.addEventListener(r,b,false)}if(Ext.isIE9m){if(!B()){B.bindIE=true;c.attachEvent("onreadystatechange",B)}}else{if(Ext.isOpera){(c.readyState==t&&i())||c.addEventListener(r,y,false)}else{if(Ext.isWebKit){B()}}}o.on(A,"load",b)}function x(C,D){return function(){var E=Ext.toArray(arguments);if(D.target==Ext.EventObject.setEvent(E[0]).target){C.apply(this,E)}}}function w(D,E,C){return function(F){C.delay(E.buffer,D,null,[new Ext.EventObjectImpl(F)])}}function s(G,F,C,E,D){return function(H){Ext.EventManager.removeListener(F,C,E,D);G(H)}}function e(D,E,C){return function(G){var F=new Ext.util.DelayedTask(D);if(!C.tasks){C.tasks=[]}C.tasks.push(F);F.delay(E.delay||10,D,null,[new Ext.EventObjectImpl(G)])}}function h(H,G,C,J,K){var D=(!C||typeof C=="boolean")?{}:C,E=Ext.getDom(H),F;J=J||D.fn;K=K||D.scope;if(!E){throw'Error listening for "'+G+'". Element "'+H+"\" doesn't exist."}function I(M){if(!Ext){return}M=Ext.EventObject.setEvent(M);var L;if(D.delegate){if(!(L=M.getTarget(D.delegate,E))){return}}else{L=M.target}if(D.stopEvent){M.stopEvent()}if(D.preventDefault){M.preventDefault()}if(D.stopPropagation){M.stopPropagation()}if(D.normalized===false){M=M.browserEvent}J.call(K||E,M,L,D)}if(D.target){I=x(I,D)}if(D.delay){I=e(I,D,J)}if(D.single){I=s(I,E,G,J,K)}if(D.buffer){F=new Ext.util.DelayedTask(I);I=w(I,D,F)}m(E,G,J,F,I,K);return I}var v={addListener:function(E,C,G,F,D){if(typeof C=="object"){var J=C,H,I;for(H in J){I=J[H];if(!g.test(H)){if(Ext.isFunction(I)){h(E,H,J,I,J.scope)}else{h(E,H,I)}}}}else{h(E,C,D,G,F)}},removeListener:function(E,I,M,N){E=Ext.getDom(E);var C=n(E),K=E&&(Ext.elCache[C].events)[I]||[],D,H,F,G,J,L;for(H=0,J=K.length;H<J;H++){if(Ext.isArray(L=K[H])&&L[0]==M&&(!N||L[2]==N)){if(L[4]){L[4].cancel()}G=M.tasks&&M.tasks.length;if(G){while(G--){M.tasks[G].cancel()}delete M.tasks}D=L[1];o.un(E,I,o.extAdapter?L[3]:D);if(D&&E.addEventListener&&I=="mousewheel"){E.removeEventListener("DOMMouseScroll",D,false)}if(D&&E==c&&I=="mousedown"){Ext.EventManager.stoppedMouseDownEvent.removeListener(D)}K.splice(H,1);if(K.length===0){delete Ext.elCache[C].events[I]}for(G in Ext.elCache[C].events){return false}Ext.elCache[C].events={};return false}}},removeAll:function(E){E=Ext.getDom(E);var D=n(E),J=Ext.elCache[D]||{},M=J.events||{},I,H,K,F,L,G,C;for(F in M){if(M.hasOwnProperty(F)){I=M[F];for(H=0,K=I.length;H<K;H++){L=I[H];if(L[4]){L[4].cancel()}if(L[0].tasks&&(G=L[0].tasks.length)){while(G--){L[0].tasks[G].cancel()}delete L.tasks}C=L[1];o.un(E,F,o.extAdapter?L[3]:C);if(E.addEventListener&&C&&F=="mousewheel"){E.removeEventListener("DOMMouseScroll",C,false)}if(C&&E==c&&F=="mousedown"){Ext.EventManager.stoppedMouseDownEvent.removeListener(C)}}}}if(Ext.elCache[D]){Ext.elCache[D].events={}}},getListeners:function(F,C){F=Ext.getDom(F);var H=n(F),D=Ext.elCache[H]||{},G=D.events||{},E=[];if(G&&G[C]){return G[C]}else{return null}},removeFromSpecialCache:function(E){var D=0,C=u.length;for(;D<C;++D){if(u[D].el==E){u.splice(D,1)}}},purgeElement:function(E,C,G){E=Ext.getDom(E);var D=n(E),J=Ext.elCache[D]||{},K=J.events||{},F,I,H;if(G){if(K&&K.hasOwnProperty(G)){I=K[G];for(F=0,H=I.length;F<H;F++){Ext.EventManager.removeListener(E,G,I[F][0])}}}else{Ext.EventManager.removeAll(E)}if(C&&E&&E.childNodes){for(F=0,H=E.childNodes.length;F<H;F++){Ext.EventManager.purgeElement(E.childNodes[F],C,G)}}},_unload:function(){var C;for(C in Ext.elCache){Ext.EventManager.removeAll(C)}delete Ext.elCache;delete Ext.Element._flyweights;var G,D,F,E=Ext.lib.Ajax;(typeof E.conn=="object")?D=E.conn:D={};for(F in D){G=D[F];if(G){E.abort({conn:G,tId:F})}}},onDocumentReady:function(E,D,C){if(Ext.isReady){z||(z=new Ext.util.Event());z.addListener(E,D,C);z.fire();z.listeners=[]}else{if(!z){a()}C=C||{};C.delay=C.delay||1;z.addListener(E,D,C)}},fireDocReady:b};v.on=v.addListener;v.un=v.removeListener;v.stoppedMouseDownEvent=new Ext.util.Event();return v}();Ext.onReady=Ext.EventManager.onDocumentReady;(function(){var a=function(){var c=document.body||document.getElementsByTagName("body")[0];if(!c){return false}var b=[];if(Ext.isIE){if(!Ext.isIE10p){b.push("ext-ie")}if(Ext.isIE6){b.push("ext-ie6")}else{if(Ext.isIE7){b.push("ext-ie7","ext-ie7m")}else{if(Ext.isIE8){b.push("ext-ie8","ext-ie8m")}else{if(Ext.isIE9){b.push("ext-ie9","ext-ie9m")}else{if(Ext.isIE10){b.push("ext-ie10")}}}}}}if(Ext.isGecko){if(Ext.isGecko2){b.push("ext-gecko2")}else{b.push("ext-gecko3")}}if(Ext.isOpera){b.push("ext-opera")}if(Ext.isWebKit){b.push("ext-webkit")}if(Ext.isSafari){b.push("ext-safari "+(Ext.isSafari2?"ext-safari2":(Ext.isSafari3?"ext-safari3":"ext-safari4")))}else{if(Ext.isChrome){b.push("ext-chrome")}}if(Ext.isMac){b.push("ext-mac")}if(Ext.isLinux){b.push("ext-linux")}if(Ext.isStrict||Ext.isBorderBox){var d=c.parentNode;if(d){if(!Ext.isStrict){Ext.fly(d,"_internal").addClass("x-quirks");if(Ext.isIE9m&&!Ext.isStrict){Ext.isIEQuirks=true}}Ext.fly(d,"_internal").addClass(((Ext.isStrict&&Ext.isIE)||(!Ext.enableForcedBoxModel&&!Ext.isIE))?" ext-strict":" ext-border-box")}}if(Ext.enableForcedBoxModel&&!Ext.isIE){Ext.isForcedBorderBox=true;b.push("ext-forced-border-box")}Ext.fly(c,"_internal").addClass(b);return true};if(!a()){Ext.onReady(a)}})();(function(){var b=Ext.apply(Ext.supports,{correctRightMargin:true,correctTransparentColor:true,cssFloat:true});var a=function(){var g=document.createElement("div"),e=document,c,d;g.innerHTML='<div style="height:30px;width:50px;"><div style="height:20px;width:20px;"></div></div><div style="float:left;background-color:transparent;">';e.body.appendChild(g);d=g.lastChild;if((c=e.defaultView)){if(c.getComputedStyle(g.firstChild.firstChild,null).marginRight!="0px"){b.correctRightMargin=false}if(c.getComputedStyle(d,null).backgroundColor!="transparent"){b.correctTransparentColor=false}}b.cssFloat=!!d.style.cssFloat;e.body.removeChild(g)};if(Ext.isReady){a()}else{Ext.onReady(a)}})();Ext.EventObject=function(){var b=Ext.lib.Event,c=/(dbl)?click/,a={3:13,63234:37,63235:39,63232:38,63233:40,63276:33,63277:34,63272:46,63273:36,63275:35},d=Ext.isIE?{1:0,4:1,2:2}:{0:0,1:1,2:2};Ext.EventObjectImpl=function(g){if(g){this.setEvent(g.browserEvent||g)}};Ext.EventObjectImpl.prototype={setEvent:function(h){var g=this;if(h==g||(h&&h.browserEvent)){return h}g.browserEvent=h;if(h){g.button=h.button?d[h.button]:(h.which?h.which-1:-1);if(c.test(h.type)&&g.button==-1){g.button=0}g.type=h.type;g.shiftKey=h.shiftKey;g.ctrlKey=h.ctrlKey||h.metaKey||false;g.altKey=h.altKey;g.keyCode=h.keyCode;g.charCode=h.charCode;g.target=b.getTarget(h);g.xy=b.getXY(h)}else{g.button=-1;g.shiftKey=false;g.ctrlKey=false;g.altKey=false;g.keyCode=0;g.charCode=0;g.target=null;g.xy=[0,0]}return g},stopEvent:function(){var e=this;if(e.browserEvent){if(e.browserEvent.type=="mousedown"){Ext.EventManager.stoppedMouseDownEvent.fire(e)}b.stopEvent(e.browserEvent)}},preventDefault:function(){if(this.browserEvent){b.preventDefault(this.browserEvent)}},stopPropagation:function(){var e=this;if(e.browserEvent){if(e.browserEvent.type=="mousedown"){Ext.EventManager.stoppedMouseDownEvent.fire(e)}b.stopPropagation(e.browserEvent)}},getCharCode:function(){return this.charCode||this.keyCode},getKey:function(){return this.normalizeKey(this.keyCode||this.charCode)},normalizeKey:function(e){return Ext.isSafari?(a[e]||e):e},getPageX:function(){return this.xy[0]},getPageY:function(){return this.xy[1]},getXY:function(){return this.xy},getTarget:function(g,h,e){return g?Ext.fly(this.target).findParent(g,h,e):(e?Ext.get(this.target):this.target)},getRelatedTarget:function(){return this.browserEvent?b.getRelatedTarget(this.browserEvent):null},getWheelDelta:function(){var g=this.browserEvent;var h=0;if(g.wheelDelta){h=g.wheelDelta/120}else{if(g.detail){h=-g.detail/3}}return h},within:function(h,i,e){if(h){var g=this[i?"getRelatedTarget":"getTarget"]();return g&&((e?(g==Ext.getDom(h)):false)||Ext.fly(h).contains(g))}return false}};return new Ext.EventObjectImpl()}();Ext.Loader=Ext.apply({},{load:function(j,i,k,c){var k=k||this,g=document.getElementsByTagName("head")[0],b=document.createDocumentFragment(),a=j.length,h=0,e=this;var l=function(m){g.appendChild(e.buildScriptTag(j[m],d))};var d=function(){h++;if(a==h&&typeof i=="function"){i.call(k)}else{if(c===true){l(h)}}};if(c===true){l.call(this,0)}else{Ext.each(j,function(n,m){b.appendChild(this.buildScriptTag(n,d))},this);g.appendChild(b)}},buildScriptTag:function(b,c){var a=document.createElement("script");a.type="text/javascript";a.src=b;if(a.readyState){a.onreadystatechange=function(){if(a.readyState=="loaded"||a.readyState=="complete"){a.onreadystatechange=null;c()}}}else{a.onload=c}return a}});Ext.ns("Ext.grid","Ext.list","Ext.dd","Ext.tree","Ext.form","Ext.menu","Ext.state","Ext.layout.boxOverflow","Ext.app","Ext.ux","Ext.chart","Ext.direct","Ext.slider");Ext.apply(Ext,function(){var c=Ext,a=0,b=null;return{emptyFn:function(){},BLANK_IMAGE_URL:Ext.isIE6||Ext.isIE7||Ext.isAir?"http://www.extjs.com/s.gif":"data:image/gif;base64,R0lGODlhAQABAID/AMDAwAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==",extendX:function(d,e){return Ext.extend(d,e(d.prototype))},getDoc:function(){return Ext.get(document)},num:function(e,d){e=Number(Ext.isEmpty(e)||Ext.isArray(e)||typeof e=="boolean"||(typeof e=="string"&&e.trim().length==0)?NaN:e);return isNaN(e)?d:e},value:function(g,d,e){return Ext.isEmpty(g,e)?d:g},escapeRe:function(d){return d.replace(/([-.*+?^${}()|[\]\/\\])/g,"\\$1")},sequence:function(h,d,g,e){h[d]=h[d].createSequence(g,e)},addBehaviors:function(i){if(!Ext.isReady){Ext.onReady(function(){Ext.addBehaviors(i)})}else{var e={},h,d,g;for(d in i){if((h=d.split("@"))[1]){g=h[0];if(!e[g]){e[g]=Ext.select(g)}e[g].on(h[1],i[d])}}e=null}},getScrollBarWidth:function(g){if(!Ext.isReady){return 0}if(g===true||b===null){var i=Ext.getBody().createChild('<div class="x-hide-offsets" style="width:100px;height:50px;overflow:hidden;"><div style="height:200px;"></div></div>'),h=i.child("div",true);var e=h.offsetWidth;i.setStyle("overflow",(Ext.isWebKit||Ext.isGecko)?"auto":"scroll");var d=h.offsetWidth;i.remove();b=e-d+2}return b},combine:function(){var g=arguments,e=g.length,j=[];for(var h=0;h<e;h++){var d=g[h];if(Ext.isArray(d)){j=j.concat(d)}else{if(d.length!==undefined&&!d.substr){j=j.concat(Array.prototype.slice.call(d,0))}else{j.push(d)}}}return j},copyTo:function(d,e,g){if(typeof g=="string"){g=g.split(/[,;\s]/)}Ext.each(g,function(h){if(e.hasOwnProperty(h)){d[h]=e[h]}},this);return d},destroy:function(){Ext.each(arguments,function(d){if(d){if(Ext.isArray(d)){this.destroy.apply(this,d)}else{if(typeof d.destroy=="function"){d.destroy()}else{if(d.dom){d.remove()}}}}},this)},destroyMembers:function(l,j,g,h){for(var k=1,e=arguments,d=e.length;k<d;k++){Ext.destroy(l[e[k]]);delete l[e[k]]}},clean:function(d){var e=[];Ext.each(d,function(g){if(!!g){e.push(g)}});return e},unique:function(d){var e=[],g={};Ext.each(d,function(h){if(!g[h]){e.push(h)}g[h]=true});return e},flatten:function(d){var g=[];function e(h){Ext.each(h,function(i){if(Ext.isArray(i)){e(i)}else{g.push(i)}});return g}return e(d)},min:function(d,e){var g=d[0];e=e||function(i,h){return i<h?-1:1};Ext.each(d,function(h){g=e(g,h)==-1?g:h});return g},max:function(d,e){var g=d[0];e=e||function(i,h){return i>h?1:-1};Ext.each(d,function(h){g=e(g,h)==1?g:h});return g},mean:function(d){return d.length>0?Ext.sum(d)/d.length:undefined},sum:function(d){var e=0;Ext.each(d,function(g){e+=g});return e},partition:function(d,e){var g=[[],[]];Ext.each(d,function(j,k,h){g[(e&&e(j,k,h))||(!e&&j)?0:1].push(j)});return g},invoke:function(d,e){var h=[],g=Array.prototype.slice.call(arguments,2);Ext.each(d,function(j,k){if(j&&typeof j[e]=="function"){h.push(j[e].apply(j,g))}else{h.push(undefined)}});return h},pluck:function(d,g){var e=[];Ext.each(d,function(h){e.push(h[g])});return e},zip:function(){var n=Ext.partition(arguments,function(i){return typeof i!="function"}),k=n[0],m=n[1][0],d=Ext.max(Ext.pluck(k,"length")),h=[];for(var l=0;l<d;l++){h[l]=[];if(m){h[l]=m.apply(m,Ext.pluck(k,l))}else{for(var g=0,e=k.length;g<e;g++){h[l].push(k[g][l])}}}return h},getCmp:function(d){return Ext.ComponentMgr.get(d)},useShims:c.isIE6||(c.isMac&&c.isGecko2),type:function(e){if(e===undefined||e===null){return false}if(e.htmlElement){return"element"}var d=typeof e;if(d=="object"&&e.nodeName){switch(e.nodeType){case 1:return"element";case 3:return(/\S/).test(e.nodeValue)?"textnode":"whitespace"}}if(d=="object"||d=="function"){switch(e.constructor){case Array:return"array";case RegExp:return"regexp";case Date:return"date"}if(typeof e.length=="number"&&typeof e.item=="function"){return"nodelist"}}return d},intercept:function(h,d,g,e){h[d]=h[d].createInterceptor(g,e)},callback:function(d,h,g,e){if(typeof d=="function"){if(e){d.defer(e,h,g||[])}else{d.apply(h,g||[])}}}}}());Ext.apply(Function.prototype,{createSequence:function(b,a){var c=this;return(typeof b!="function")?this:function(){var d=c.apply(this||window,arguments);b.apply(a||this||window,arguments);return d}}});Ext.applyIf(String,{escape:function(a){return a.replace(/('|\\)/g,"\\$1")},leftPad:function(d,b,c){var a=String(d);if(!c){c=" "}while(a.length<b){a=c+a}return a}});String.prototype.toggle=function(b,a){return this==b?a:b};String.prototype.trim=function(){var a=/^\s+|\s+$/g;return function(){return this.replace(a,"")}}();Date.prototype.getElapsed=function(a){return Math.abs((a||new Date()).getTime()-this.getTime())};Ext.applyIf(Number.prototype,{constrain:function(b,a){return Math.min(Math.max(this,b),a)}});Ext.lib.Dom.getRegion=function(a){return Ext.lib.Region.getRegion(a)};Ext.lib.Region=function(d,g,a,c){var e=this;e.top=d;e[1]=d;e.right=g;e.bottom=a;e.left=c;e[0]=c};Ext.lib.Region.prototype={contains:function(b){var a=this;return(b.left>=a.left&&b.right<=a.right&&b.top>=a.top&&b.bottom<=a.bottom)},getArea:function(){var a=this;return((a.bottom-a.top)*(a.right-a.left))},intersect:function(h){var g=this,d=Math.max(g.top,h.top),e=Math.min(g.right,h.right),a=Math.min(g.bottom,h.bottom),c=Math.max(g.left,h.left);if(a>=d&&e>=c){return new Ext.lib.Region(d,e,a,c)}},union:function(h){var g=this,d=Math.min(g.top,h.top),e=Math.max(g.right,h.right),a=Math.max(g.bottom,h.bottom),c=Math.min(g.left,h.left);return new Ext.lib.Region(d,e,a,c)},constrainTo:function(b){var a=this;a.top=a.top.constrain(b.top,b.bottom);a.bottom=a.bottom.constrain(b.top,b.bottom);a.left=a.left.constrain(b.left,b.right);a.right=a.right.constrain(b.left,b.right);return a},adjust:function(d,c,a,g){var e=this;e.top+=d;e.left+=c;e.right+=g;e.bottom+=a;return e}};Ext.lib.Region.getRegion=function(e){var h=Ext.lib.Dom.getXY(e),d=h[1],g=h[0]+e.offsetWidth,a=h[1]+e.offsetHeight,c=h[0];return new Ext.lib.Region(d,g,a,c)};Ext.lib.Point=function(a,c){if(Ext.isArray(a)){c=a[1];a=a[0]}var b=this;b.x=b.right=b.left=b[0]=a;b.y=b.top=b.bottom=b[1]=c};Ext.lib.Point.prototype=new Ext.lib.Region();Ext.apply(Ext.DomHelper,function(){var e,a="afterbegin",h="afterend",i="beforebegin",d="beforeend",b=/tag|children|cn|html$/i;function g(m,p,n,q,l,j){m=Ext.getDom(m);var k;if(e.useDom){k=c(p,null);if(j){m.appendChild(k)}else{(l=="firstChild"?m:m.parentNode).insertBefore(k,m[l]||m)}}else{k=Ext.DomHelper.insertHtml(q,m,Ext.DomHelper.createHtml(p))}return n?Ext.get(k,true):k}function c(j,r){var k,u=document,p,s,m,t;if(Ext.isArray(j)){k=u.createDocumentFragment();for(var q=0,n=j.length;q<n;q++){c(j[q],k)}}else{if(typeof j=="string"){k=u.createTextNode(j)}else{k=u.createElement(j.tag||"div");p=!!k.setAttribute;for(var s in j){if(!b.test(s)){m=j[s];if(s=="cls"){k.className=m}else{if(p){k.setAttribute(s,m)}else{k[s]=m}}}}Ext.DomHelper.applyStyles(k,j.style);if((t=j.children||j.cn)){c(t,k)}else{if(j.html){k.innerHTML=j.html}}}}if(r){r.appendChild(k)}return k}e={createTemplate:function(k){var j=Ext.DomHelper.createHtml(k);return new Ext.Template(j)},useDom:false,insertBefore:function(j,l,k){return g(j,l,k,i)},insertAfter:function(j,l,k){return g(j,l,k,h,"nextSibling")},insertFirst:function(j,l,k){return g(j,l,k,a,"firstChild")},append:function(j,l,k){return g(j,l,k,d,"",true)},createDom:c};return e}());Ext.apply(Ext.Template.prototype,{disableFormats:false,re:/\{([\w\-]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?\}/g,argsRe:/^\s*['"](.*)["']\s*$/,compileARe:/\\/g,compileBRe:/(\r\n|\n)/g,compileCRe:/'/g,applyTemplate:function(b){var g=this,a=g.disableFormats!==true,e=Ext.util.Format,c=g;if(g.compiled){return g.compiled(b)}function d(j,l,p,k){if(p&&a){if(p.substr(0,5)=="this."){return c.call(p.substr(5),b[l],b)}else{if(k){var o=g.argsRe;k=k.split(",");for(var n=0,h=k.length;n<h;n++){k[n]=k[n].replace(o,"$1")}k=[b[l]].concat(k)}else{k=[b[l]]}return e[p].apply(e,k)}}else{return b[l]!==undefined?b[l]:""}}return g.html.replace(g.re,d)},compile:function(){var me=this,fm=Ext.util.Format,useF=me.disableFormats!==true,sep=Ext.isGecko?"+":",",body;function fn(m,name,format,args){if(format&&useF){args=args?","+args:"";if(format.substr(0,5)!="this."){format="fm."+format+"("}else{format='this.call("'+format.substr(5)+'", ';args=", values"}}else{args="";format="(values['"+name+"'] == undefined ? '' : "}return"'"+sep+format+"values['"+name+"']"+args+")"+sep+"'"}if(Ext.isGecko){body="this.compiled = function(values){ return '"+me.html.replace(me.compileARe,"\\\\").replace(me.compileBRe,"\\n").replace(me.compileCRe,"\\'").replace(me.re,fn)+"';};"}else{body=["this.compiled = function(values){ return ['"];body.push(me.html.replace(me.compileARe,"\\\\").replace(me.compileBRe,"\\n").replace(me.compileCRe,"\\'").replace(me.re,fn));body.push("'].join('');};");body=body.join("")}eval(body);return me},call:function(c,b,a){return this[c](b,a)}});Ext.Template.prototype.apply=Ext.Template.prototype.applyTemplate;Ext.util.Functions={createInterceptor:function(c,b,a){var d=c;if(!Ext.isFunction(b)){return c}else{return function(){var g=this,e=arguments;b.target=g;b.method=c;return(b.apply(a||g||window,e)!==false)?c.apply(g||window,e):null}}},createDelegate:function(c,d,b,a){if(!Ext.isFunction(c)){return c}return function(){var g=b||arguments;if(a===true){g=Array.prototype.slice.call(arguments,0);g=g.concat(b)}else{if(Ext.isNumber(a)){g=Array.prototype.slice.call(arguments,0);var e=[a,0].concat(b);Array.prototype.splice.apply(g,e)}}return c.apply(d||window,g)}},defer:function(d,c,e,b,a){d=Ext.util.Functions.createDelegate(d,e,b,a);if(c>0){return setTimeout(d,c)}d();return 0},createSequence:function(c,b,a){if(!Ext.isFunction(b)){return c}else{return function(){var d=c.apply(this||window,arguments);b.apply(a||this||window,arguments);return d}}}};Ext.defer=Ext.util.Functions.defer;Ext.createInterceptor=Ext.util.Functions.createInterceptor;Ext.createSequence=Ext.util.Functions.createSequence;Ext.createDelegate=Ext.util.Functions.createDelegate;Ext.apply(Ext.util.Observable.prototype,function(){function a(j){var i=(this.methodEvents=this.methodEvents||{})[j],d,c,g,h=this;if(!i){this.methodEvents[j]=i={};i.originalFn=this[j];i.methodName=j;i.before=[];i.after=[];var b=function(l,k,e){if((c=l.apply(k||h,e))!==undefined){if(typeof c=="object"){if(c.returnValue!==undefined){d=c.returnValue}else{d=c}g=!!c.cancel}else{if(c===false){g=true}else{d=c}}}};this[j]=function(){var l=Array.prototype.slice.call(arguments,0),k;d=c=undefined;g=false;for(var m=0,e=i.before.length;m<e;m++){k=i.before[m];b(k.fn,k.scope,l);if(g){return d}}if((c=i.originalFn.apply(h,l))!==undefined){d=c}for(var m=0,e=i.after.length;m<e;m++){k=i.after[m];b(k.fn,k.scope,l);if(g){return d}}return d}}return i}return{beforeMethod:function(d,c,b){a.call(this,d).before.push({fn:c,scope:b})},afterMethod:function(d,c,b){a.call(this,d).after.push({fn:c,scope:b})},removeMethodListener:function(j,g,d){var h=this.getMethodEvent(j);for(var c=0,b=h.before.length;c<b;c++){if(h.before[c].fn==g&&h.before[c].scope==d){h.before.splice(c,1);return}}for(var c=0,b=h.after.length;c<b;c++){if(h.after[c].fn==g&&h.after[c].scope==d){h.after.splice(c,1);return}}},relayEvents:function(j,e){var h=this;function g(i){return function(){return h.fireEvent.apply(h,[i].concat(Array.prototype.slice.call(arguments,0)))}}for(var d=0,b=e.length;d<b;d++){var c=e[d];h.events[c]=h.events[c]||true;j.on(c,g(c),h)}},enableBubble:function(e){var g=this;if(!Ext.isEmpty(e)){e=Ext.isArray(e)?e:Array.prototype.slice.call(arguments,0);for(var d=0,b=e.length;d<b;d++){var c=e[d];c=c.toLowerCase();var h=g.events[c]||true;if(typeof h=="boolean"){h=new Ext.util.Event(g,c);g.events[c]=h}h.bubble=true}}}}}());Ext.util.Observable.capture=function(c,b,a){c.fireEvent=c.fireEvent.createInterceptor(b,a)};Ext.util.Observable.observeClass=function(b,a){if(b){if(!b.fireEvent){Ext.apply(b,new Ext.util.Observable());Ext.util.Observable.capture(b.prototype,b.fireEvent,b)}if(typeof a=="object"){b.on(a)}return b}};Ext.apply(Ext.EventManager,function(){var d,k,g,b,a=Ext.lib.Dom,j=/^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/,c=Ext.EventManager._unload,i=0,h=0,e=Ext.isWebKit?Ext.num(navigator.userAgent.match(/AppleWebKit\/(\d+)/)[1])>=525:!((Ext.isGecko&&!Ext.isWindows)||Ext.isOpera);return{_unload:function(){Ext.EventManager.un(window,"resize",this.fireWindowResize,this);c.call(Ext.EventManager)},doResizeEvent:function(){var m=a.getViewHeight(),l=a.getViewWidth();if(h!=m||i!=l){d.fire(i=l,h=m)}},onWindowResize:function(n,m,l){if(!d){d=new Ext.util.Event();k=new Ext.util.DelayedTask(this.doResizeEvent);Ext.EventManager.on(window,"resize",this.fireWindowResize,this)}d.addListener(n,m,l)},fireWindowResize:function(){if(d){k.delay(100)}},onTextResize:function(o,n,l){if(!g){g=new Ext.util.Event();var m=new Ext.Element(document.createElement("div"));m.dom.className="x-text-resize";m.dom.innerHTML="X";m.appendTo(document.body);b=m.dom.offsetHeight;setInterval(function(){if(m.dom.offsetHeight!=b){g.fire(b,b=m.dom.offsetHeight)}},this.textResizeInterval)}g.addListener(o,n,l)},removeResizeListener:function(m,l){if(d){d.removeListener(m,l)}},fireResize:function(){if(d){d.fire(a.getViewWidth(),a.getViewHeight())}},textResizeInterval:50,ieDeferSrc:false,getKeyEvent:function(){return e?"keydown":"keypress"},useKeydown:e}}());Ext.EventManager.on=Ext.EventManager.addListener;Ext.apply(Ext.EventObjectImpl.prototype,{BACKSPACE:8,TAB:9,NUM_CENTER:12,ENTER:13,RETURN:13,SHIFT:16,CTRL:17,CONTROL:17,ALT:18,PAUSE:19,CAPS_LOCK:20,ESC:27,SPACE:32,PAGE_UP:33,PAGEUP:33,PAGE_DOWN:34,PAGEDOWN:34,END:35,HOME:36,LEFT:37,UP:38,RIGHT:39,DOWN:40,PRINT_SCREEN:44,INSERT:45,DELETE:46,ZERO:48,ONE:49,TWO:50,THREE:51,FOUR:52,FIVE:53,SIX:54,SEVEN:55,EIGHT:56,NINE:57,A:65,B:66,C:67,D:68,E:69,F:70,G:71,H:72,I:73,J:74,K:75,L:76,M:77,N:78,O:79,P:80,Q:81,R:82,S:83,T:84,U:85,V:86,W:87,X:88,Y:89,Z:90,CONTEXT_MENU:93,NUM_ZERO:96,NUM_ONE:97,NUM_TWO:98,NUM_THREE:99,NUM_FOUR:100,NUM_FIVE:101,NUM_SIX:102,NUM_SEVEN:103,NUM_EIGHT:104,NUM_NINE:105,NUM_MULTIPLY:106,NUM_PLUS:107,NUM_MINUS:109,NUM_PERIOD:110,NUM_DIVISION:111,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,isNavKeyPress:function(){var b=this,a=this.normalizeKey(b.keyCode);return(a>=33&&a<=40)||a==b.RETURN||a==b.TAB||a==b.ESC},isSpecialKey:function(){var a=this.normalizeKey(this.keyCode);return(this.type=="keypress"&&this.ctrlKey)||this.isNavKeyPress()||(a==this.BACKSPACE)||(a>=16&&a<=20)||(a>=44&&a<=46)},getPoint:function(){return new Ext.lib.Point(this.xy[0],this.xy[1])},hasModifier:function(){return((this.ctrlKey||this.altKey)||this.shiftKey)}});Ext.Element.addMethods({swallowEvent:function(a,b){var d=this;function c(g){g.stopPropagation();if(b){g.preventDefault()}}if(Ext.isArray(a)){Ext.each(a,function(g){d.on(g,c)});return d}d.on(a,c);return d},relayEvent:function(a,b){this.on(a,function(c){b.fireEvent(a,c)})},clean:function(b){var d=this,e=d.dom,g=e.firstChild,c=-1;if(Ext.Element.data(e,"isCleaned")&&b!==true){return d}while(g){var a=g.nextSibling;if(g.nodeType==3&&!(/\S/.test(g.nodeValue))){e.removeChild(g)}else{g.nodeIndex=++c}g=a}Ext.Element.data(e,"isCleaned",true);return d},load:function(){var a=this.getUpdater();a.update.apply(a,arguments);return this},getUpdater:function(){return this.updateManager||(this.updateManager=new Ext.Updater(this))},update:function(html,loadScripts,callback){if(!this.dom){return this}html=html||"";if(loadScripts!==true){this.dom.innerHTML=html;if(typeof callback=="function"){callback()}return this}var id=Ext.id(),dom=this.dom;html+='<span id="'+id+'"></span>';Ext.lib.Event.onAvailable(id,function(){var DOC=document,hd=DOC.getElementsByTagName("head")[0],re=/(?:<script([^>]*)?>)((\n|\r|.)*?)(?:<\/script>)/ig,srcRe=/\ssrc=([\'\"])(.*?)\1/i,typeRe=/\stype=([\'\"])(.*?)\1/i,match,attrs,srcMatch,typeMatch,el,s;while((match=re.exec(html))){attrs=match[1];srcMatch=attrs?attrs.match(srcRe):false;if(srcMatch&&srcMatch[2]){s=DOC.createElement("script");s.src=srcMatch[2];typeMatch=attrs.match(typeRe);if(typeMatch&&typeMatch[2]){s.type=typeMatch[2]}hd.appendChild(s)}else{if(match[2]&&match[2].length>0){if(window.execScript){window.execScript(match[2])}else{window.eval(match[2])}}}}el=DOC.getElementById(id);if(el){Ext.removeNode(el)}if(typeof callback=="function"){callback()}});dom.innerHTML=html.replace(/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,"");return this},removeAllListeners:function(){this.removeAnchor();Ext.EventManager.removeAll(this.dom);return this},createProxy:function(a,e,d){a=(typeof a=="object")?a:{tag:"div",cls:a};var c=this,b=e?Ext.DomHelper.append(e,a,true):Ext.DomHelper.insertBefore(c.dom,a,true);if(d&&c.setBox&&c.getBox){b.setBox(c.getBox())}return b}});Ext.Element.prototype.getUpdateManager=Ext.Element.prototype.getUpdater;Ext.Element.addMethods({getAnchorXY:function(e,l,q){e=(e||"tl").toLowerCase();q=q||{};var k=this,b=k.dom==document.body||k.dom==document,n=q.width||b?Ext.lib.Dom.getViewWidth():k.getWidth(),i=q.height||b?Ext.lib.Dom.getViewHeight():k.getHeight(),p,a=Math.round,c=k.getXY(),m=k.getScroll(),j=b?m.left:!l?c[0]:0,g=b?m.top:!l?c[1]:0,d={c:[a(n*0.5),a(i*0.5)],t:[a(n*0.5),0],l:[0,a(i*0.5)],r:[n,a(i*0.5)],b:[a(n*0.5),i],tl:[0,0],bl:[0,i],br:[n,i],tr:[n,0]};p=d[e];return[p[0]+j,p[1]+g]},anchorTo:function(b,h,c,a,k,l){var i=this,e=i.dom,j=!Ext.isEmpty(k),d=function(){Ext.fly(e).alignTo(b,h,c,a);Ext.callback(l,Ext.fly(e))},g=this.getAnchor();this.removeAnchor();Ext.apply(g,{fn:d,scroll:j});Ext.EventManager.onWindowResize(d,null);if(j){Ext.EventManager.on(window,"scroll",d,null,{buffer:!isNaN(k)?k:50})}d.call(i);return i},removeAnchor:function(){var b=this,a=this.getAnchor();if(a&&a.fn){Ext.EventManager.removeResizeListener(a.fn);if(a.scroll){Ext.EventManager.un(window,"scroll",a.fn)}delete a.fn}return b},getAnchor:function(){var b=Ext.Element.data,c=this.dom;if(!c){return}var a=b(c,"_anchor");if(!a){a=b(c,"_anchor",{})}return a},getAlignToXY:function(g,A,B){g=Ext.get(g);if(!g||!g.dom){throw"Element.alignToXY with an element that doesn't exist"}B=B||[0,0];A=(!A||A=="?"?"tl-bl?":(!(/-/).test(A)&&A!==""?"tl-"+A:A||"tl-bl")).toLowerCase();var K=this,H=K.dom,M,L,n,l,s,F,v,t=Ext.lib.Dom.getViewWidth()-10,G=Ext.lib.Dom.getViewHeight()-10,b,i,j,k,u,z,N=document,J=N.documentElement,q=N.body,E=(J.scrollLeft||q.scrollLeft||0)+5,D=(J.scrollTop||q.scrollTop||0)+5,I=false,e="",a="",C=A.match(/^([a-z]+)-([a-z]+)(\?)?$/);if(!C){throw"Element.alignTo with an invalid alignment "+A}e=C[1];a=C[2];I=!!C[3];M=K.getAnchorXY(e,true);L=g.getAnchorXY(a,false);n=L[0]-M[0]+B[0];l=L[1]-M[1]+B[1];if(I){s=K.getWidth();F=K.getHeight();v=g.getRegion();b=e.charAt(0);i=e.charAt(e.length-1);j=a.charAt(0);k=a.charAt(a.length-1);u=((b=="t"&&j=="b")||(b=="b"&&j=="t"));z=((i=="r"&&k=="l")||(i=="l"&&k=="r"));if(n+s>t+E){n=z?v.left-s:t+E-s}if(n<E){n=z?v.right:E}if(l+F>G+D){l=u?v.top-F:G+D-F}if(l<D){l=u?v.bottom:D}}return[n,l]},alignTo:function(c,a,e,b){var d=this;return d.setXY(d.getAlignToXY(c,a,e),d.preanim&&!!b?d.preanim(arguments,3):false)},adjustForConstraints:function(c,a,b){return this.getConstrainToXY(a||document,false,b,c)||c},getConstrainToXY:function(b,a,c,e){var d={top:0,left:0,bottom:0,right:0};return function(i,A,l,n){i=Ext.get(i);l=l?Ext.applyIf(l,d):d;var z,D,v=0,u=0;if(i.dom==document.body||i.dom==document){z=Ext.lib.Dom.getViewWidth();D=Ext.lib.Dom.getViewHeight()}else{z=i.dom.clientWidth;D=i.dom.clientHeight;if(!A){var t=i.getXY();v=t[0];u=t[1]}}var r=i.getScroll();v+=l.left+r.left;u+=l.top+r.top;z-=l.right;D-=l.bottom;var B=v+z,g=u+D,j=n||(!A?this.getXY():[this.getLeft(true),this.getTop(true)]),p=j[0],o=j[1],k=this.getConstrainOffset(),q=this.dom.offsetWidth+k,C=this.dom.offsetHeight+k;var m=false;if((p+q)>B){p=B-q;m=true}if((o+C)>g){o=g-C;m=true}if(p<v){p=v;m=true}if(o<u){o=u;m=true}return m?[p,o]:false}}(),getConstrainOffset:function(){return 0},getCenterXY:function(){return this.getAlignToXY(document,"c-c")},center:function(a){return this.alignTo(a||document,"c-c")}});Ext.Element.addMethods({select:function(a,b){return Ext.Element.select(a,b,this.dom)}});Ext.apply(Ext.Element.prototype,function(){var c=Ext.getDom,a=Ext.get,b=Ext.DomHelper;return{insertSibling:function(i,g,h){var j=this,e,d=(g||"before").toLowerCase()=="after",k;if(Ext.isArray(i)){k=j;Ext.each(i,function(l){e=Ext.fly(k,"_internal").insertSibling(l,g,h);if(d){k=e}});return e}i=i||{};if(i.nodeType||i.dom){e=j.dom.parentNode.insertBefore(c(i),d?j.dom.nextSibling:j.dom);if(!h){e=a(e)}}else{if(d&&!j.dom.nextSibling){e=b.append(j.dom.parentNode,i,!h)}else{e=b[d?"insertAfter":"insertBefore"](j.dom,i,!h)}}return e}}}());Ext.Element.boxMarkup='<div class="{0}-tl"><div class="{0}-tr"><div class="{0}-tc"></div></div></div><div class="{0}-ml"><div class="{0}-mr"><div class="{0}-mc"></div></div></div><div class="{0}-bl"><div class="{0}-br"><div class="{0}-bc"></div></div></div>';Ext.Element.addMethods(function(){var a="_internal",b=/(\d+\.?\d+)px/;return{applyStyles:function(c){Ext.DomHelper.applyStyles(this.dom,c);return this},getStyles:function(){var c={};Ext.each(arguments,function(d){c[d]=this.getStyle(d)},this);return c},setOverflow:function(c){var d=this.dom;if(c=="auto"&&Ext.isMac&&Ext.isGecko2){d.style.overflow="hidden";(function(){d.style.overflow="auto"}).defer(1)}else{d.style.overflow=c}},boxWrap:function(c){c=c||"x-box";var d=Ext.get(this.insertHtml("beforeBegin","<div class='"+c+"'>"+String.format(Ext.Element.boxMarkup,c)+"</div>"));Ext.DomQuery.selectNode("."+c+"-mc",d.dom).appendChild(this.dom);return d},setSize:function(e,c,d){var g=this;if(typeof e=="object"){c=e.height;e=e.width}e=g.adjustWidth(e);c=g.adjustHeight(c);if(!d||!g.anim){g.dom.style.width=g.addUnits(e);g.dom.style.height=g.addUnits(c)}else{g.anim({width:{to:e},height:{to:c}},g.preanim(arguments,2))}return g},getComputedHeight:function(){var d=this,c=Math.max(d.dom.offsetHeight,d.dom.clientHeight);if(!c){c=parseFloat(d.getStyle("height"))||0;if(!d.isBorderBox()){c+=d.getFrameWidth("tb")}}return c},getComputedWidth:function(){var c=Math.max(this.dom.offsetWidth,this.dom.clientWidth);if(!c){c=parseFloat(this.getStyle("width"))||0;if(!this.isBorderBox()){c+=this.getFrameWidth("lr")}}return c},getFrameWidth:function(d,c){return c&&this.isBorderBox()?0:(this.getPadding(d)+this.getBorderWidth(d))},addClassOnOver:function(c){this.hover(function(){Ext.fly(this,a).addClass(c)},function(){Ext.fly(this,a).removeClass(c)});return this},addClassOnFocus:function(c){this.on("focus",function(){Ext.fly(this,a).addClass(c)},this.dom);this.on("blur",function(){Ext.fly(this,a).removeClass(c)},this.dom);return this},addClassOnClick:function(c){var d=this.dom;this.on("mousedown",function(){Ext.fly(d,a).addClass(c);var g=Ext.getDoc(),e=function(){Ext.fly(d,a).removeClass(c);g.removeListener("mouseup",e)};g.on("mouseup",e)});return this},getViewSize:function(){var g=document,h=this.dom,c=(h==g||h==g.body);if(c){var e=Ext.lib.Dom;return{width:e.getViewWidth(),height:e.getViewHeight()}}else{return{width:h.clientWidth,height:h.clientHeight}}},getStyleSize:function(){var j=this,c,i,l=document,m=this.dom,e=(m==l||m==l.body),g=m.style;if(e){var k=Ext.lib.Dom;return{width:k.getViewWidth(),height:k.getViewHeight()}}if(g.width&&g.width!="auto"){c=parseFloat(g.width);if(j.isBorderBox()){c-=j.getFrameWidth("lr")}}if(g.height&&g.height!="auto"){i=parseFloat(g.height);if(j.isBorderBox()){i-=j.getFrameWidth("tb")}}return{width:c||j.getWidth(true),height:i||j.getHeight(true)}},getSize:function(c){return{width:this.getWidth(c),height:this.getHeight(c)}},repaint:function(){var c=this.dom;this.addClass("x-repaint");setTimeout(function(){Ext.fly(c).removeClass("x-repaint")},1);return this},unselectable:function(){this.dom.unselectable="on";return this.swallowEvent("selectstart",true).addClass("x-unselectable")},getMargins:function(d){var e=this,c,g={t:"top",l:"left",r:"right",b:"bottom"},h={};if(!d){for(c in e.margins){h[g[c]]=parseFloat(e.getStyle(e.margins[c]))||0}return h}else{return e.addStyles.call(e,d,e.margins)}}}}());Ext.Element.addMethods({setBox:function(e,g,b){var d=this,a=e.width,c=e.height;if((g&&!d.autoBoxAdjust)&&!d.isBorderBox()){a-=(d.getBorderWidth("lr")+d.getPadding("lr"));c-=(d.getBorderWidth("tb")+d.getPadding("tb"))}d.setBounds(e.x,e.y,a,c,d.animTest.call(d,arguments,b,2));return d},getBox:function(j,p){var m=this,v,e,o,d=m.getBorderWidth,q=m.getPadding,g,a,u,n;if(!p){v=m.getXY()}else{e=parseInt(m.getStyle("left"),10)||0;o=parseInt(m.getStyle("top"),10)||0;v=[e,o]}var c=m.dom,s=c.offsetWidth,i=c.offsetHeight,k;if(!j){k={x:v[0],y:v[1],0:v[0],1:v[1],width:s,height:i}}else{g=d.call(m,"l")+q.call(m,"l");a=d.call(m,"r")+q.call(m,"r");u=d.call(m,"t")+q.call(m,"t");n=d.call(m,"b")+q.call(m,"b");k={x:v[0]+g,y:v[1]+u,0:v[0]+g,1:v[1]+u,width:s-(g+a),height:i-(u+n)}}k.right=k.x+k.width;k.bottom=k.y+k.height;return k},move:function(j,b,c){var g=this,m=g.getXY(),k=m[0],i=m[1],d=[k-b,i],l=[k+b,i],h=[k,i-b],a=[k,i+b],e={l:d,left:d,r:l,right:l,t:h,top:h,up:h,b:a,bottom:a,down:a};j=j.toLowerCase();g.moveTo(e[j][0],e[j][1],g.animTest.call(g,arguments,c,2))},setLeftTop:function(d,c){var b=this,a=b.dom.style;a.left=b.addUnits(d);a.top=b.addUnits(c);return b},getRegion:function(){return Ext.lib.Dom.getRegion(this.dom)},setBounds:function(b,g,d,a,c){var e=this;if(!c||!e.anim){e.setSize(d,a);e.setLocation(b,g)}else{e.anim({points:{to:[b,g]},width:{to:e.adjustWidth(d)},height:{to:e.adjustHeight(a)}},e.preanim(arguments,4),"motion")}return e},setRegion:function(b,a){return this.setBounds(b.left,b.top,b.right-b.left,b.bottom-b.top,this.animTest.call(this,arguments,a,1))}});Ext.Element.addMethods({scrollTo:function(b,d,a){var e=/top/i.test(b),c=this,g=c.dom,h;if(!a||!c.anim){h="scroll"+(e?"Top":"Left");g[h]=d}else{h="scroll"+(e?"Left":"Top");c.anim({scroll:{to:e?[g[h],d]:[d,g[h]]}},c.preanim(arguments,2),"scroll")}return c},scrollIntoView:function(e,i){var p=Ext.getDom(e)||Ext.getBody().dom,h=this.dom,g=this.getOffsetsTo(p),k=g[0]+p.scrollLeft,u=g[1]+p.scrollTop,q=u+h.offsetHeight,d=k+h.offsetWidth,a=p.clientHeight,m=parseInt(p.scrollTop,10),s=parseInt(p.scrollLeft,10),j=m+a,n=s+p.clientWidth;if(h.offsetHeight>a||u<m){p.scrollTop=u}else{if(q>j){p.scrollTop=q-a}}p.scrollTop=p.scrollTop;if(i!==false){if(h.offsetWidth>p.clientWidth||k<s){p.scrollLeft=k}else{if(d>n){p.scrollLeft=d-p.clientWidth}}p.scrollLeft=p.scrollLeft}return this},scrollChildIntoView:function(b,a){Ext.fly(b,"_scrollChildIntoView").scrollIntoView(this,a)},scroll:function(m,b,d){if(!this.isScrollable()){return false}var e=this.dom,g=e.scrollLeft,p=e.scrollTop,n=e.scrollWidth,k=e.scrollHeight,i=e.clientWidth,a=e.clientHeight,c=false,o,j={l:Math.min(g+b,n-i),r:o=Math.max(g-b,0),t:Math.max(p-b,0),b:Math.min(p+b,k-a)};j.d=j.b;j.u=j.t;m=m.substr(0,1);if((o=j[m])>-1){c=true;this.scrollTo(m=="l"||m=="r"?"left":"top",o,this.preanim(arguments,2))}return c}});Ext.Element.addMethods(function(){var d="visibility",b="display",a="hidden",h="none",c="x-masked",g="x-masked-relative",e=Ext.Element.data;return{isVisible:function(i){var j=!this.isStyle(d,a)&&!this.isStyle(b,h),k=this.dom.parentNode;if(i!==true||!j){return j}while(k&&!(/^body/i.test(k.tagName))){if(!Ext.fly(k,"_isVisible").isVisible()){return false}k=k.parentNode}return true},isDisplayed:function(){return !this.isStyle(b,h)},enableDisplayMode:function(i){this.setVisibilityMode(Ext.Element.DISPLAY);if(!Ext.isEmpty(i)){e(this.dom,"originalDisplay",i)}return this},mask:function(j,n){var p=this,l=p.dom,o=Ext.DomHelper,m="ext-el-mask-msg",i,q;if(!/^body/i.test(l.tagName)&&p.getStyle("position")=="static"){p.addClass(g)}if(i=e(l,"maskMsg")){i.remove()}if(i=e(l,"mask")){i.remove()}q=o.append(l,{cls:"ext-el-mask"},true);e(l,"mask",q);p.addClass(c);q.setDisplayed(true);if(typeof j=="string"){var k=o.append(l,{cls:m,cn:{tag:"div"}},true);e(l,"maskMsg",k);k.dom.className=n?m+" "+n:m;k.dom.firstChild.innerHTML=j;k.setDisplayed(true);k.center(p)}if(Ext.isIE&&!(Ext.isIE7&&Ext.isStrict)&&p.getStyle("height")=="auto"){q.setSize(undefined,p.getHeight())}return q},unmask:function(){var k=this,l=k.dom,i=e(l,"mask"),j=e(l,"maskMsg");if(i){if(j){j.remove();e(l,"maskMsg",undefined)}i.remove();e(l,"mask",undefined);k.removeClass([c,g])}},isMasked:function(){var i=e(this.dom,"mask");return i&&i.isVisible()},createShim:function(){var i=document.createElement("iframe"),j;i.frameBorder="0";i.className="ext-shim";i.src=Ext.SSL_SECURE_URL;j=Ext.get(this.dom.parentNode.insertBefore(i,this.dom));j.autoBoxAdjust=false;return j}}}());Ext.Element.addMethods({addKeyListener:function(b,d,c){var a;if(typeof b!="object"||Ext.isArray(b)){a={key:b,fn:d,scope:c}}else{a={key:b.key,shift:b.shift,ctrl:b.ctrl,alt:b.alt,fn:d,scope:c}}return new Ext.KeyMap(this,a)},addKeyMap:function(a){return new Ext.KeyMap(this,a)}});Ext.CompositeElementLite.importElementMethods();Ext.apply(Ext.CompositeElementLite.prototype,{addElements:function(c,a){if(!c){return this}if(typeof c=="string"){c=Ext.Element.selectorFunction(c,a)}var b=this.elements;Ext.each(c,function(d){b.push(Ext.get(d))});return this},first:function(){return this.item(0)},last:function(){return this.item(this.getCount()-1)},contains:function(a){return this.indexOf(a)!=-1},removeElement:function(d,e){var c=this,a=this.elements,b;Ext.each(d,function(g){if((b=(a[g]||a[g=c.indexOf(g)]))){if(e){if(b.dom){b.remove()}else{Ext.removeNode(b)}}a.splice(g,1)}});return this}});Ext.CompositeElement=Ext.extend(Ext.CompositeElementLite,{constructor:function(b,a){this.elements=[];this.add(b,a)},getElement:function(a){return a},transformElement:function(a){return Ext.get(a)}});Ext.Element.select=function(a,d,b){var c;if(typeof a=="string"){c=Ext.Element.selectorFunction(a,b)}else{if(a.length!==undefined){c=a}else{throw"Invalid selector"}}return(d===true)?new Ext.CompositeElement(c):new Ext.CompositeElementLite(c)};Ext.select=Ext.Element.select;Ext.UpdateManager=Ext.Updater=Ext.extend(Ext.util.Observable,function(){var b="beforeupdate",d="update",c="failure";function a(h){var i=this;i.transaction=null;if(h.argument.form&&h.argument.reset){try{h.argument.form.reset()}catch(j){}}if(i.loadScripts){i.renderer.render(i.el,h,i,g.createDelegate(i,[h]))}else{i.renderer.render(i.el,h,i);g.call(i,h)}}function g(h,i,j){this.fireEvent(i||d,this.el,h);if(Ext.isFunction(h.argument.callback)){h.argument.callback.call(h.argument.scope,this.el,Ext.isEmpty(j)?true:false,h,h.argument.options)}}function e(h){g.call(this,h,c,!!(this.transaction=null))}return{constructor:function(i,h){var j=this;i=Ext.get(i);if(!h&&i.updateManager){return i.updateManager}j.el=i;j.defaultUrl=null;j.addEvents(b,d,c);Ext.apply(j,Ext.Updater.defaults);j.transaction=null;j.refreshDelegate=j.refresh.createDelegate(j);j.updateDelegate=j.update.createDelegate(j);j.formUpdateDelegate=(j.formUpdate||function(){}).createDelegate(j);j.renderer=j.renderer||j.getDefaultRenderer();Ext.Updater.superclass.constructor.call(j)},setRenderer:function(h){this.renderer=h},getRenderer:function(){return this.renderer},getDefaultRenderer:function(){return new Ext.Updater.BasicRenderer()},setDefaultUrl:function(h){this.defaultUrl=h},getEl:function(){return this.el},update:function(i,n,p,l){var k=this,h,j;if(k.fireEvent(b,k.el,i,n)!==false){if(Ext.isObject(i)){h=i;i=h.url;n=n||h.params;p=p||h.callback;l=l||h.discardUrl;j=h.scope;if(!Ext.isEmpty(h.nocache)){k.disableCaching=h.nocache}if(!Ext.isEmpty(h.text)){k.indicatorText='<div class="loading-indicator">'+h.text+"</div>"}if(!Ext.isEmpty(h.scripts)){k.loadScripts=h.scripts}if(!Ext.isEmpty(h.timeout)){k.timeout=h.timeout}}k.showLoading();if(!l){k.defaultUrl=i}if(Ext.isFunction(i)){i=i.call(k)}var m=Ext.apply({},{url:i,params:(Ext.isFunction(n)&&j)?n.createDelegate(j):n,success:a,failure:e,scope:k,callback:undefined,timeout:(k.timeout*1000),disableCaching:k.disableCaching,argument:{options:h,url:i,form:null,callback:p,scope:j||window,params:n}},h);k.transaction=Ext.Ajax.request(m)}},formUpdate:function(k,h,j,l){var i=this;if(i.fireEvent(b,i.el,k,h)!==false){if(Ext.isFunction(h)){h=h.call(i)}k=Ext.getDom(k);i.transaction=Ext.Ajax.request({form:k,url:h,success:a,failure:e,scope:i,timeout:(i.timeout*1000),argument:{url:h,form:k,callback:l,reset:j}});i.showLoading.defer(1,i)}},startAutoRefresh:function(i,j,l,m,h){var k=this;if(h){k.update(j||k.defaultUrl,l,m,true)}if(k.autoRefreshProcId){clearInterval(k.autoRefreshProcId)}k.autoRefreshProcId=setInterval(k.update.createDelegate(k,[j||k.defaultUrl,l,m,true]),i*1000)},stopAutoRefresh:function(){if(this.autoRefreshProcId){clearInterval(this.autoRefreshProcId);delete this.autoRefreshProcId}},isAutoRefreshing:function(){return !!this.autoRefreshProcId},showLoading:function(){if(this.showLoadIndicator){this.el.dom.innerHTML=this.indicatorText}},abort:function(){if(this.transaction){Ext.Ajax.abort(this.transaction)}},isUpdating:function(){return this.transaction?Ext.Ajax.isLoading(this.transaction):false},refresh:function(h){if(this.defaultUrl){this.update(this.defaultUrl,null,h,true)}}}}());Ext.Updater.defaults={timeout:30,disableCaching:false,showLoadIndicator:true,indicatorText:'<div class="loading-indicator">Loading...</div>',loadScripts:false,sslBlankUrl:Ext.SSL_SECURE_URL};Ext.Updater.updateElement=function(d,c,e,b){var a=Ext.get(d).getUpdater();Ext.apply(a,b);a.update(c,e,b?b.callback:null)};Ext.Updater.BasicRenderer=function(){};Ext.Updater.BasicRenderer.prototype={render:function(c,a,b,d){c.update(a.responseText,b.loadScripts,d)}};(function(){Date.useStrict=false;function b(d){var c=Array.prototype.slice.call(arguments,1);return d.replace(/\{(\d+)\}/g,function(e,g){return c[g]})}Date.formatCodeToRegex=function(d,c){var e=Date.parseCodes[d];if(e){e=typeof e=="function"?e():e;Date.parseCodes[d]=e}return e?Ext.applyIf({c:e.c?b(e.c,c||"{0}"):e.c},e):{g:0,c:null,s:Ext.escapeRe(d)}};var a=Date.formatCodeToRegex;Ext.apply(Date,{parseFunctions:{"M$":function(d,c){var e=new RegExp("\\/Date\\(([-+])?(\\d+)(?:[+-]\\d{4})?\\)\\/");var g=(d||"").match(e);return g?new Date(((g[1]||"")+g[2])*1):null}},parseRegexes:[],formatFunctions:{"M$":function(){return"\\/Date("+this.getTime()+")\\/"}},y2kYear:50,MILLI:"ms",SECOND:"s",MINUTE:"mi",HOUR:"h",DAY:"d",MONTH:"mo",YEAR:"y",defaults:{},dayNames:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],monthNames:["January","February","March","April","May","June","July","August","September","October","November","December"],monthNumbers:{Jan:0,Feb:1,Mar:2,Apr:3,May:4,Jun:5,Jul:6,Aug:7,Sep:8,Oct:9,Nov:10,Dec:11},getShortMonthName:function(c){return Date.monthNames[c].substring(0,3)},getShortDayName:function(c){return Date.dayNames[c].substring(0,3)},getMonthNumber:function(c){return Date.monthNumbers[c.substring(0,1).toUpperCase()+c.substring(1,3).toLowerCase()]},formatContainsHourInfo:(function(){var d=/(\\.)/g,c=/([gGhHisucUOPZ]|M\$)/;return function(e){return c.test(e.replace(d,""))}})(),formatCodes:{d:"String.leftPad(this.getDate(), 2, '0')",D:"Date.getShortDayName(this.getDay())",j:"this.getDate()",l:"Date.dayNames[this.getDay()]",N:"(this.getDay() ? this.getDay() : 7)",S:"this.getSuffix()",w:"this.getDay()",z:"this.getDayOfYear()",W:"String.leftPad(this.getWeekOfYear(), 2, '0')",F:"Date.monthNames[this.getMonth()]",m:"String.leftPad(this.getMonth() + 1, 2, '0')",M:"Date.getShortMonthName(this.getMonth())",n:"(this.getMonth() + 1)",t:"this.getDaysInMonth()",L:"(this.isLeapYear() ? 1 : 0)",o:"(this.getFullYear() + (this.getWeekOfYear() == 1 && this.getMonth() > 0 ? +1 : (this.getWeekOfYear() >= 52 && this.getMonth() < 11 ? -1 : 0)))",Y:"String.leftPad(this.getFullYear(), 4, '0')",y:"('' + this.getFullYear()).substring(2, 4)",a:"(this.getHours() < 12 ? 'am' : 'pm')",A:"(this.getHours() < 12 ? 'AM' : 'PM')",g:"((this.getHours() % 12) ? this.getHours() % 12 : 12)",G:"this.getHours()",h:"String.leftPad((this.getHours() % 12) ? this.getHours() % 12 : 12, 2, '0')",H:"String.leftPad(this.getHours(), 2, '0')",i:"String.leftPad(this.getMinutes(), 2, '0')",s:"String.leftPad(this.getSeconds(), 2, '0')",u:"String.leftPad(this.getMilliseconds(), 3, '0')",O:"this.getGMTOffset()",P:"this.getGMTOffset(true)",T:"this.getTimezone()",Z:"(this.getTimezoneOffset() * -60)",c:function(){for(var k="Y-m-dTH:i:sP",h=[],g=0,d=k.length;g<d;++g){var j=k.charAt(g);h.push(j=="T"?"'T'":Date.getFormatCode(j))}return h.join(" + ")},U:"Math.round(this.getTime() / 1000)"},isValid:function(o,c,n,k,g,j,e){k=k||0;g=g||0;j=j||0;e=e||0;var l=new Date(o<100?100:o,c-1,n,k,g,j,e).add(Date.YEAR,o<100?o-100:0);return o==l.getFullYear()&&c==l.getMonth()+1&&n==l.getDate()&&k==l.getHours()&&g==l.getMinutes()&&j==l.getSeconds()&&e==l.getMilliseconds()},parseDate:function(d,g,c){var e=Date.parseFunctions;if(e[g]==null){Date.createParser(g)}return e[g](d,Ext.isDefined(c)?c:Date.useStrict)},getFormatCode:function(d){var c=Date.formatCodes[d];if(c){c=typeof c=="function"?c():c;Date.formatCodes[d]=c}return c||("'"+String.escape(d)+"'")},createFormat:function(h){var g=[],c=false,e="";for(var d=0;d<h.length;++d){e=h.charAt(d);if(!c&&e=="\\"){c=true}else{if(c){c=false;g.push("'"+String.escape(e)+"'")}else{g.push(Date.getFormatCode(e))}}}Date.formatFunctions[h]=new Function("return "+g.join("+"))},createParser:function(){var c=["var dt, y, m, d, h, i, s, ms, o, z, zz, u, v,","def = Date.defaults,","results = String(input).match(Date.parseRegexes[{0}]);","if(results){","{1}","if(u != null){","v = new Date(u * 1000);","}else{","dt = (new Date()).clearTime();","y = Ext.num(y, Ext.num(def.y, dt.getFullYear()));","m = Ext.num(m, Ext.num(def.m - 1, dt.getMonth()));","d = Ext.num(d, Ext.num(def.d, dt.getDate()));","h = Ext.num(h, Ext.num(def.h, dt.getHours()));","i = Ext.num(i, Ext.num(def.i, dt.getMinutes()));","s = Ext.num(s, Ext.num(def.s, dt.getSeconds()));","ms = Ext.num(ms, Ext.num(def.ms, dt.getMilliseconds()));","if(z >= 0 && y >= 0){","v = new Date(y < 100 ? 100 : y, 0, 1, h, i, s, ms).add(Date.YEAR, y < 100 ? y - 100 : 0);","v = !strict? v : (strict === true && (z <= 364 || (v.isLeapYear() && z <= 365))? v.add(Date.DAY, z) : null);","}else if(strict === true && !Date.isValid(y, m + 1, d, h, i, s, ms)){","v = null;","}else{","v = new Date(y < 100 ? 100 : y, m, d, h, i, s, ms).add(Date.YEAR, y < 100 ? y - 100 : 0);","}","}","}","if(v){","if(zz != null){","v = v.add(Date.SECOND, -v.getTimezoneOffset() * 60 - zz);","}else if(o){","v = v.add(Date.MINUTE, -v.getTimezoneOffset() + (sn == '+'? -1 : 1) * (hr * 60 + mn));","}","}","return v;"].join("\n");return function(m){var e=Date.parseRegexes.length,o=1,g=[],l=[],k=false,d="",j=0,h,n;for(;j<m.length;++j){d=m.charAt(j);if(!k&&d=="\\"){k=true}else{if(k){k=false;l.push(String.escape(d))}else{h=a(d,o);o+=h.g;l.push(h.s);if(h.g&&h.c){if(h.calcLast){n=h.c}else{g.push(h.c)}}}}}if(n){g.push(n)}Date.parseRegexes[e]=new RegExp("^"+l.join("")+"$","i");Date.parseFunctions[m]=new Function("input","strict",b(c,e,g.join("")))}}(),parseCodes:{d:{g:1,c:"d = parseInt(results[{0}], 10);\n",s:"(\\d{2})"},j:{g:1,c:"d = parseInt(results[{0}], 10);\n",s:"(\\d{1,2})"},D:function(){for(var c=[],d=0;d<7;c.push(Date.getShortDayName(d)),++d){}return{g:0,c:null,s:"(?:"+c.join("|")+")"}},l:function(){return{g:0,c:null,s:"(?:"+Date.dayNames.join("|")+")"}},N:{g:0,c:null,s:"[1-7]"},S:{g:0,c:null,s:"(?:st|nd|rd|th)"},w:{g:0,c:null,s:"[0-6]"},z:{g:1,c:"z = parseInt(results[{0}], 10);\n",s:"(\\d{1,3})"},W:{g:0,c:null,s:"(?:\\d{2})"},F:function(){return{g:1,c:"m = parseInt(Date.getMonthNumber(results[{0}]), 10);\n",s:"("+Date.monthNames.join("|")+")"}},M:function(){for(var c=[],d=0;d<12;c.push(Date.getShortMonthName(d)),++d){}return Ext.applyIf({s:"("+c.join("|")+")"},a("F"))},m:{g:1,c:"m = parseInt(results[{0}], 10) - 1;\n",s:"(\\d{2})"},n:{g:1,c:"m = parseInt(results[{0}], 10) - 1;\n",s:"(\\d{1,2})"},t:{g:0,c:null,s:"(?:\\d{2})"},L:{g:0,c:null,s:"(?:1|0)"},o:function(){return a("Y")},Y:{g:1,c:"y = parseInt(results[{0}], 10);\n",s:"(\\d{4})"},y:{g:1,c:"var ty = parseInt(results[{0}], 10);\ny = ty > Date.y2kYear ? 1900 + ty : 2000 + ty;\n",s:"(\\d{1,2})"},a:function(){return a("A")},A:{calcLast:true,g:1,c:"if (/(am)/i.test(results[{0}])) {\nif (!h || h == 12) { h = 0; }\n} else { if (!h || h < 12) { h = (h || 0) + 12; }}",s:"(AM|PM|am|pm)"},g:function(){return a("G")},G:{g:1,c:"h = parseInt(results[{0}], 10);\n",s:"(\\d{1,2})"},h:function(){return a("H")},H:{g:1,c:"h = parseInt(results[{0}], 10);\n",s:"(\\d{2})"},i:{g:1,c:"i = parseInt(results[{0}], 10);\n",s:"(\\d{2})"},s:{g:1,c:"s = parseInt(results[{0}], 10);\n",s:"(\\d{2})"},u:{g:1,c:"ms = results[{0}]; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n",s:"(\\d+)"},O:{g:1,c:["o = results[{0}];","var sn = o.substring(0,1),","hr = o.substring(1,3)*1 + Math.floor(o.substring(3,5) / 60),","mn = o.substring(3,5) % 60;","o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + String.leftPad(hr, 2, '0') + String.leftPad(mn, 2, '0')) : null;\n"].join("\n"),s:"([+-]\\d{4})"},P:{g:1,c:["o = results[{0}];","var sn = o.substring(0,1),","hr = o.substring(1,3)*1 + Math.floor(o.substring(4,6) / 60),","mn = o.substring(4,6) % 60;","o = ((-12 <= (hr*60 + mn)/60) && ((hr*60 + mn)/60 <= 14))? (sn + String.leftPad(hr, 2, '0') + String.leftPad(mn, 2, '0')) : null;\n"].join("\n"),s:"([+-]\\d{2}:\\d{2})"},T:{g:0,c:null,s:"[A-Z]{1,4}"},Z:{g:1,c:"zz = results[{0}] * 1;\nzz = (-43200 <= zz && zz <= 50400)? zz : null;\n",s:"([+-]?\\d{1,5})"},c:function(){var e=[],c=[a("Y",1),a("m",2),a("d",3),a("h",4),a("i",5),a("s",6),{c:"ms = results[7] || '0'; ms = parseInt(ms, 10)/Math.pow(10, ms.length - 3);\n"},{c:["if(results[8]) {","if(results[8] == 'Z'){","zz = 0;","}else if (results[8].indexOf(':') > -1){",a("P",8).c,"}else{",a("O",8).c,"}","}"].join("\n")}];for(var g=0,d=c.length;g<d;++g){e.push(c[g].c)}return{g:1,c:e.join(""),s:[c[0].s,"(?:","-",c[1].s,"(?:","-",c[2].s,"(?:","(?:T| )?",c[3].s,":",c[4].s,"(?::",c[5].s,")?","(?:(?:\\.|,)(\\d+))?","(Z|(?:[-+]\\d{2}(?::)?\\d{2}))?",")?",")?",")?"].join("")}},U:{g:1,c:"u = parseInt(results[{0}], 10);\n",s:"(-?\\d+)"}}})}());Ext.apply(Date.prototype,{dateFormat:function(a){if(Date.formatFunctions[a]==null){Date.createFormat(a)}return Date.formatFunctions[a].call(this)},getTimezone:function(){return this.toString().replace(/^.* (?:\((.*)\)|([A-Z]{1,4})(?:[\-+][0-9]{4})?(?: -?\d+)?)$/,"$1$2").replace(/[^A-Z]/g,"")},getGMTOffset:function(a){return(this.getTimezoneOffset()>0?"-":"+")+String.leftPad(Math.floor(Math.abs(this.getTimezoneOffset())/60),2,"0")+(a?":":"")+String.leftPad(Math.abs(this.getTimezoneOffset()%60),2,"0")},getDayOfYear:function(){var b=0,e=this.clone(),a=this.getMonth(),c;for(c=0,e.setDate(1),e.setMonth(0);c<a;e.setMonth(++c)){b+=e.getDaysInMonth()}return b+this.getDate()-1},getWeekOfYear:function(){var a=86400000,b=7*a;return function(){var d=Date.UTC(this.getFullYear(),this.getMonth(),this.getDate()+3)/a,c=Math.floor(d/7),e=new Date(c*b).getUTCFullYear();return c-Math.floor(Date.UTC(e,0,7)/b)+1}}(),isLeapYear:function(){var a=this.getFullYear();return !!((a&3)==0&&(a%100||(a%400==0&&a)))},getFirstDayOfMonth:function(){var a=(this.getDay()-(this.getDate()-1))%7;return(a<0)?(a+7):a},getLastDayOfMonth:function(){return this.getLastDateOfMonth().getDay()},getFirstDateOfMonth:function(){return new Date(this.getFullYear(),this.getMonth(),1)},getLastDateOfMonth:function(){return new Date(this.getFullYear(),this.getMonth(),this.getDaysInMonth())},getDaysInMonth:function(){var a=[31,28,31,30,31,30,31,31,30,31,30,31];return function(){var b=this.getMonth();return b==1&&this.isLeapYear()?29:a[b]}}(),getSuffix:function(){switch(this.getDate()){case 1:case 21:case 31:return"st";case 2:case 22:return"nd";case 3:case 23:return"rd";default:return"th"}},clone:function(){return new Date(this.getTime())},isDST:function(){return new Date(this.getFullYear(),0,1).getTimezoneOffset()!=this.getTimezoneOffset()},clearTime:function(g){if(g){return this.clone().clearTime()}var b=this.getDate();this.setHours(0);this.setMinutes(0);this.setSeconds(0);this.setMilliseconds(0);if(this.getDate()!=b){for(var a=1,e=this.add(Date.HOUR,a);e.getDate()!=b;a++,e=this.add(Date.HOUR,a)){}this.setDate(b);this.setHours(e.getHours())}return this},add:function(b,c){var e=this.clone();if(!b||c===0){return e}switch(b.toLowerCase()){case Date.MILLI:e.setMilliseconds(this.getMilliseconds()+c);break;case Date.SECOND:e.setSeconds(this.getSeconds()+c);break;case Date.MINUTE:e.setMinutes(this.getMinutes()+c);break;case Date.HOUR:e.setHours(this.getHours()+c);break;case Date.DAY:e.setDate(this.getDate()+c);break;case Date.MONTH:var a=this.getDate();if(a>28){a=Math.min(a,this.getFirstDateOfMonth().add("mo",c).getLastDateOfMonth().getDate())}e.setDate(a);e.setMonth(this.getMonth()+c);break;case Date.YEAR:e.setFullYear(this.getFullYear()+c);break}return e},between:function(c,a){var b=this.getTime();return c.getTime()<=b&&b<=a.getTime()}});Date.prototype.format=Date.prototype.dateFormat;if(Ext.isSafari&&(navigator.userAgent.match(/WebKit\/(\d+)/)[1]||NaN)<420){Ext.apply(Date.prototype,{_xMonth:Date.prototype.setMonth,_xDate:Date.prototype.setDate,setMonth:function(a){if(a<=-1){var d=Math.ceil(-a),c=Math.ceil(d/12),b=(d%12)?12-d%12:0;this.setFullYear(this.getFullYear()-c);return this._xMonth(b)}else{return this._xMonth(a)}},setDate:function(a){return this.setTime(this.getTime()-(this.getDate()-a)*86400000)}})}Ext.util.MixedCollection=function(b,a){this.items=[];this.map={};this.keys=[];this.length=0;this.addEvents("clear","add","replace","remove","sort");this.allowFunctions=b===true;if(a){this.getKey=a}Ext.util.MixedCollection.superclass.constructor.call(this)};Ext.extend(Ext.util.MixedCollection,Ext.util.Observable,{allowFunctions:false,add:function(b,c){if(arguments.length==1){c=arguments[0];b=this.getKey(c)}if(typeof b!="undefined"&&b!==null){var a=this.map[b];if(typeof a!="undefined"){return this.replace(b,c)}this.map[b]=c}this.length++;this.items.push(c);this.keys.push(b);this.fireEvent("add",this.length-1,c,b);return c},getKey:function(a){return a.id},replace:function(c,d){if(arguments.length==1){d=arguments[0];c=this.getKey(d)}var a=this.map[c];if(typeof c=="undefined"||c===null||typeof a=="undefined"){return this.add(c,d)}var b=this.indexOfKey(c);this.items[b]=d;this.map[c]=d;this.fireEvent("replace",c,a,d);return d},addAll:function(e){if(arguments.length>1||Ext.isArray(e)){var b=arguments.length>1?arguments:e;for(var d=0,a=b.length;d<a;d++){this.add(b[d])}}else{for(var c in e){if(this.allowFunctions||typeof e[c]!="function"){this.add(c,e[c])}}}},each:function(e,d){var b=[].concat(this.items);for(var c=0,a=b.length;c<a;c++){if(e.call(d||b[c],b[c],c,a)===false){break}}},eachKey:function(d,c){for(var b=0,a=this.keys.length;b<a;b++){d.call(c||window,this.keys[b],this.items[b],b,a)}},find:function(d,c){for(var b=0,a=this.items.length;b<a;b++){if(d.call(c||window,this.items[b],this.keys[b])){return this.items[b]}}return null},insert:function(a,b,c){if(arguments.length==2){c=arguments[1];b=this.getKey(c)}if(this.containsKey(b)){this.suspendEvents();this.removeKey(b);this.resumeEvents()}if(a>=this.length){return this.add(b,c)}this.length++;this.items.splice(a,0,c);if(typeof b!="undefined"&&b!==null){this.map[b]=c}this.keys.splice(a,0,b);this.fireEvent("add",a,c,b);return c},remove:function(a){return this.removeAt(this.indexOf(a))},removeAt:function(a){if(a<this.length&&a>=0){this.length--;var c=this.items[a];this.items.splice(a,1);var b=this.keys[a];if(typeof b!="undefined"){delete this.map[b]}this.keys.splice(a,1);this.fireEvent("remove",c,b);return c}return false},removeKey:function(a){return this.removeAt(this.indexOfKey(a))},getCount:function(){return this.length},indexOf:function(a){return this.items.indexOf(a)},indexOfKey:function(a){return this.keys.indexOf(a)},item:function(b){var a=this.map[b],c=a!==undefined?a:(typeof b=="number")?this.items[b]:undefined;return typeof c!="function"||this.allowFunctions?c:null},itemAt:function(a){return this.items[a]},key:function(a){return this.map[a]},contains:function(a){return this.indexOf(a)!=-1},containsKey:function(a){return typeof this.map[a]!="undefined"},clear:function(){this.length=0;this.items=[];this.keys=[];this.map={};this.fireEvent("clear")},first:function(){return this.items[0]},last:function(){return this.items[this.length-1]},_sort:function(k,a,j){var d,e,b=String(a).toUpperCase()=="DESC"?-1:1,h=[],l=this.keys,g=this.items;j=j||function(i,c){return i-c};for(d=0,e=g.length;d<e;d++){h[h.length]={key:l[d],value:g[d],index:d}}h.sort(function(i,c){var m=j(i[k],c[k])*b;if(m===0){m=(i.index<c.index?-1:1)}return m});for(d=0,e=h.length;d<e;d++){g[d]=h[d].value;l[d]=h[d].key}this.fireEvent("sort",this)},sort:function(a,b){this._sort("value",a,b)},reorder:function(d){this.suspendEvents();var b=this.items,c=0,g=b.length,a=[],e=[],h;for(h in d){a[d[h]]=b[h]}for(c=0;c<g;c++){if(d[c]==undefined){e.push(b[c])}}for(c=0;c<g;c++){if(a[c]==undefined){a[c]=e.shift()}}this.clear();this.addAll(a);this.resumeEvents();this.fireEvent("sort",this)},keySort:function(a,b){this._sort("key",a,b||function(d,c){var g=String(d).toUpperCase(),e=String(c).toUpperCase();return g>e?1:(g<e?-1:0)})},getRange:function(e,a){var b=this.items;if(b.length<1){return[]}e=e||0;a=Math.min(typeof a=="undefined"?this.length-1:a,this.length-1);var c,d=[];if(e<=a){for(c=e;c<=a;c++){d[d.length]=b[c]}}else{for(c=e;c>=a;c--){d[d.length]=b[c]}}return d},filter:function(c,b,d,a){if(Ext.isEmpty(b,false)){return this.clone()}b=this.createValueMatcher(b,d,a);return this.filterBy(function(e){return e&&b.test(e[c])})},filterBy:function(g,e){var h=new Ext.util.MixedCollection();h.getKey=this.getKey;var b=this.keys,d=this.items;for(var c=0,a=d.length;c<a;c++){if(g.call(e||this,d[c],b[c])){h.add(b[c],d[c])}}return h},findIndex:function(c,b,e,d,a){if(Ext.isEmpty(b,false)){return -1}b=this.createValueMatcher(b,d,a);return this.findIndexBy(function(g){return g&&b.test(g[c])},null,e)},findIndexBy:function(g,e,h){var b=this.keys,d=this.items;for(var c=(h||0),a=d.length;c<a;c++){if(g.call(e||this,d[c],b[c])){return c}}return -1},createValueMatcher:function(c,e,a,b){if(!c.exec){var d=Ext.escapeRe;c=String(c);if(e===true){c=d(c)}else{c="^"+d(c);if(b===true){c+="$"}}c=new RegExp(c,a?"":"i")}return c},clone:function(){var e=new Ext.util.MixedCollection();var b=this.keys,d=this.items;for(var c=0,a=d.length;c<a;c++){e.add(b[c],d[c])}e.getKey=this.getKey;return e}});Ext.util.MixedCollection.prototype.get=Ext.util.MixedCollection.prototype.item;Ext.AbstractManager=Ext.extend(Object,{typeName:"type",constructor:function(a){Ext.apply(this,a||{});this.all=new Ext.util.MixedCollection();this.types={}},get:function(a){return this.all.get(a)},register:function(a){this.all.add(a)},unregister:function(a){this.all.remove(a)},registerType:function(b,a){this.types[b]=a;a[this.typeName]=b},isRegistered:function(a){return this.types[a]!==undefined},create:function(a,d){var b=a[this.typeName]||a.type||d,c=this.types[b];if(c==undefined){throw new Error(String.format("The '{0}' type has not been registered with this manager",b))}return new c(a)},onAvailable:function(d,c,b){var a=this.all;a.on("add",function(e,g){if(g.id==d){c.call(b||g,g);a.un("add",c,b)}})}});Ext.util.Format=function(){var trimRe=/^\s+|\s+$/g,stripTagsRE=/<\/?[^>]+>/gi,stripScriptsRe=/(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)/ig,nl2brRe=/\r?\n/g;return{ellipsis:function(value,len,word){if(value&&value.length>len){if(word){var vs=value.substr(0,len-2),index=Math.max(vs.lastIndexOf(" "),vs.lastIndexOf("."),vs.lastIndexOf("!"),vs.lastIndexOf("?"));if(index==-1||index<(len-15)){return value.substr(0,len-3)+"..."}else{return vs.substr(0,index)+"..."}}else{return value.substr(0,len-3)+"..."}}return value},undef:function(value){return value!==undefined?value:""},defaultValue:function(value,defaultValue){if(!defaultValue&&defaultValue!==0){defaultValue=""}return value!==undefined&&value!==""?value:defaultValue},htmlEncode:function(value){return !value?value:String(value).replace(/&/g,"&amp;").replace(/>/g,"&gt;").replace(/</g,"&lt;").replace(/"/g,"&quot;")},htmlDecode:function(value){return !value?value:String(value).replace(/&gt;/g,">").replace(/&lt;/g,"<").replace(/&quot;/g,'"').replace(/&amp;/g,"&")},trim:function(value){return String(value).replace(trimRe,"")},substr:function(value,start,length){return String(value).substr(start,length)},lowercase:function(value){return String(value).toLowerCase()},uppercase:function(value){return String(value).toUpperCase()},capitalize:function(value){return !value?value:value.charAt(0).toUpperCase()+value.substr(1).toLowerCase()},call:function(value,fn){if(arguments.length>2){var args=Array.prototype.slice.call(arguments,2);args.unshift(value);return eval(fn).apply(window,args)}else{return eval(fn).call(window,value)}},usMoney:function(v){v=(Math.round((v-0)*100))/100;v=(v==Math.floor(v))?v+".00":((v*10==Math.floor(v*10))?v+"0":v);v=String(v);var ps=v.split("."),whole=ps[0],sub=ps[1]?"."+ps[1]:".00",r=/(\d+)(\d{3})/;while(r.test(whole)){whole=whole.replace(r,"$1,$2")}v=whole+sub;if(v.charAt(0)=="-"){return"-$"+v.substr(1)}return"$"+v},date:function(v,format){if(!v){return""}if(!Ext.isDate(v)){v=new Date(Date.parse(v))}return v.dateFormat(format||"m/d/Y")},dateRenderer:function(format){return function(v){return Ext.util.Format.date(v,format)}},stripTags:function(v){return !v?v:String(v).replace(stripTagsRE,"")},stripScripts:function(v){return !v?v:String(v).replace(stripScriptsRe,"")},fileSize:function(size){if(size<1024){return size+" bytes"}else{if(size<1048576){return(Math.round(((size*10)/1024))/10)+" KB"}else{return(Math.round(((size*10)/1048576))/10)+" MB"}}},math:function(){var fns={};return function(v,a){if(!fns[a]){fns[a]=new Function("v","return v "+a+";")}return fns[a](v)}}(),round:function(value,precision){var result=Number(value);if(typeof precision=="number"){precision=Math.pow(10,precision);result=Math.round(value*precision)/precision}return result},number:function(v,format){if(!format){return v}v=Ext.num(v,NaN);if(isNaN(v)){return""}var comma=",",dec=".",i18n=false,neg=v<0;v=Math.abs(v);if(format.substr(format.length-2)=="/i"){format=format.substr(0,format.length-2);i18n=true;comma=".";dec=","}var hasComma=format.indexOf(comma)!=-1,psplit=(i18n?format.replace(/[^\d\,]/g,""):format.replace(/[^\d\.]/g,"")).split(dec);if(1<psplit.length){v=v.toFixed(psplit[1].length)}else{if(2<psplit.length){throw ("NumberFormatException: invalid format, formats should have no more than 1 period: "+format)}else{v=v.toFixed(0)}}var fnum=v.toString();psplit=fnum.split(".");if(hasComma){var cnum=psplit[0],parr=[],j=cnum.length,m=Math.floor(j/3),n=cnum.length%3||3,i;for(i=0;i<j;i+=n){if(i!=0){n=3}parr[parr.length]=cnum.substr(i,n);m-=1}fnum=parr.join(comma);if(psplit[1]){fnum+=dec+psplit[1]}}else{if(psplit[1]){fnum=psplit[0]+dec+psplit[1]}}return(neg?"-":"")+format.replace(/[\d,?\.?]+/,fnum)},numberRenderer:function(format){return function(v){return Ext.util.Format.number(v,format)}},plural:function(v,s,p){return v+" "+(v==1?s:(p?p:s+"s"))},nl2br:function(v){return Ext.isEmpty(v)?"":v.replace(nl2brRe,"<br/>")}}}();Ext.XTemplate=function(){Ext.XTemplate.superclass.constructor.apply(this,arguments);var y=this,j=y.html,q=/<tpl\b[^>]*>((?:(?=([^<]+))\2|<(?!tpl\b[^>]*>))*?)<\/tpl>/,d=/^<tpl\b[^>]*?for="(.*?)"/,v=/^<tpl\b[^>]*?if="(.*?)"/,x=/^<tpl\b[^>]*?exec="(.*?)"/,r,p=0,k=[],o="values",w="parent",l="xindex",n="xcount",e="return ",c="with(values){ ";j=["<tpl>",j,"</tpl>"].join("");while((r=j.match(q))){var b=r[0].match(d),a=r[0].match(v),A=r[0].match(x),g=null,h=null,t=null,z=b&&b[1]?b[1]:"";if(a){g=a&&a[1]?a[1]:null;if(g){h=new Function(o,w,l,n,c+e+(Ext.util.Format.htmlDecode(g))+"; }")}}if(A){g=A&&A[1]?A[1]:null;if(g){t=new Function(o,w,l,n,c+(Ext.util.Format.htmlDecode(g))+"; }")}}if(z){switch(z){case".":z=new Function(o,w,c+e+o+"; }");break;case"..":z=new Function(o,w,c+e+w+"; }");break;default:z=new Function(o,w,c+e+z+"; }")}}k.push({id:p,target:z,exec:t,test:h,body:r[1]||""});j=j.replace(r[0],"{xtpl"+p+"}");++p}for(var u=k.length-1;u>=0;--u){y.compileTpl(k[u])}y.master=k[k.length-1];y.tpls=k};Ext.extend(Ext.XTemplate,Ext.Template,{re:/\{([\w\-\.\#]+)(?:\:([\w\.]*)(?:\((.*?)?\))?)?(\s?[\+\-\*\\]\s?[\d\.\+\-\*\\\(\)]+)?\}/g,codeRe:/\{\[((?:\\\]|.|\n)*?)\]\}/g,applySubTemplate:function(a,k,j,d,c){var h=this,g,m=h.tpls[a],l,b=[];if((m.test&&!m.test.call(h,k,j,d,c))||(m.exec&&m.exec.call(h,k,j,d,c))){return""}l=m.target?m.target.call(h,k,j):k;g=l.length;j=m.target?k:j;if(m.target&&Ext.isArray(l)){for(var e=0,g=l.length;e<g;e++){b[b.length]=m.compiled.call(h,l[e],j,e+1,g)}return b.join("")}return m.compiled.call(h,l,j,d,c)},compileTpl:function(tpl){var fm=Ext.util.Format,useF=this.disableFormats!==true,sep=Ext.isGecko?"+":",",body;function fn(m,name,format,args,math){if(name.substr(0,4)=="xtpl"){return"'"+sep+"this.applySubTemplate("+name.substr(4)+", values, parent, xindex, xcount)"+sep+"'"}var v;if(name==="."){v="values"}else{if(name==="#"){v="xindex"}else{if(name.indexOf(".")!=-1){v=name}else{v="values['"+name+"']"}}}if(math){v="("+v+math+")"}if(format&&useF){args=args?","+args:"";if(format.substr(0,5)!="this."){format="fm."+format+"("}else{format='this.call("'+format.substr(5)+'", ';args=", values"}}else{args="";format="("+v+" === undefined ? '' : "}return"'"+sep+format+v+args+")"+sep+"'"}function codeFn(m,code){return"'"+sep+"("+code.replace(/\\'/g,"'")+")"+sep+"'"}if(Ext.isGecko){body="tpl.compiled = function(values, parent, xindex, xcount){ return '"+tpl.body.replace(/(\r\n|\n)/g,"\\n").replace(/'/g,"\\'").replace(this.re,fn).replace(this.codeRe,codeFn)+"';};"}else{body=["tpl.compiled = function(values, parent, xindex, xcount){ return ['"];body.push(tpl.body.replace(/(\r\n|\n)/g,"\\n").replace(/'/g,"\\'").replace(this.re,fn).replace(this.codeRe,codeFn));body.push("'].join('');};");body=body.join("")}eval(body);return this},applyTemplate:function(a){return this.master.compiled.call(this,a,{},1,1)},compile:function(){return this}});Ext.XTemplate.prototype.apply=Ext.XTemplate.prototype.applyTemplate;Ext.XTemplate.from=function(a){a=Ext.getDom(a);return new Ext.XTemplate(a.value||a.innerHTML)};Ext.util.CSS=function(){var d=null;var c=document;var b=/(-[a-z])/gi;var a=function(e,g){return g.charAt(1).toUpperCase()};return{createStyleSheet:function(i,l){var h;var g=c.getElementsByTagName("head")[0];var k=c.createElement("style");k.setAttribute("type","text/css");if(l){k.setAttribute("id",l)}if(Ext.isIE){g.appendChild(k);h=k.styleSheet;h.cssText=i}else{try{k.appendChild(c.createTextNode(i))}catch(j){k.cssText=i}g.appendChild(k);h=k.styleSheet?k.styleSheet:(k.sheet||c.styleSheets[c.styleSheets.length-1])}this.cacheStyleSheet(h);return h},removeStyleSheet:function(g){var e=c.getElementById(g);if(e){e.parentNode.removeChild(e)}},swapStyleSheet:function(h,e){this.removeStyleSheet(h);var g=c.createElement("link");g.setAttribute("rel","stylesheet");g.setAttribute("type","text/css");g.setAttribute("id",h);g.setAttribute("href",e);c.getElementsByTagName("head")[0].appendChild(g)},refreshCache:function(){return this.getRules(true)},cacheStyleSheet:function(h){if(!d){d={}}try{var k=h.cssRules||h.rules;for(var g=k.length-1;g>=0;--g){d[k[g].selectorText.toLowerCase()]=k[g]}}catch(i){}},getRules:function(h){if(d===null||h){d={};var k=c.styleSheets;for(var j=0,g=k.length;j<g;j++){try{this.cacheStyleSheet(k[j])}catch(l){}}}return d},getRule:function(e,h){var g=this.getRules(h);if(!Ext.isArray(e)){return g[e.toLowerCase()]}for(var j=0;j<e.length;j++){if(g[e[j]]){return g[e[j].toLowerCase()]}}return null},updateRule:function(e,j,h){if(!Ext.isArray(e)){var k=this.getRule(e);if(k){k.style[j.replace(b,a)]=h;return true}}else{for(var g=0;g<e.length;g++){if(this.updateRule(e[g],j,h)){return true}}}return false}}}();Ext.util.ClickRepeater=Ext.extend(Ext.util.Observable,{constructor:function(b,a){this.el=Ext.get(b);this.el.unselectable();Ext.apply(this,a);this.addEvents("mousedown","click","mouseup");if(!this.disabled){this.disabled=true;this.enable()}if(this.handler){this.on("click",this.handler,this.scope||this)}Ext.util.ClickRepeater.superclass.constructor.call(this)},interval:20,delay:250,preventDefault:true,stopDefault:false,timer:0,enable:function(){if(this.disabled){this.el.on("mousedown",this.handleMouseDown,this);if(Ext.isIE){this.el.on("dblclick",this.handleDblClick,this)}if(this.preventDefault||this.stopDefault){this.el.on("click",this.eventOptions,this)}}this.disabled=false},disable:function(a){if(a||!this.disabled){clearTimeout(this.timer);if(this.pressClass){this.el.removeClass(this.pressClass)}Ext.getDoc().un("mouseup",this.handleMouseUp,this);this.el.removeAllListeners()}this.disabled=true},setDisabled:function(a){this[a?"disable":"enable"]()},eventOptions:function(a){if(this.preventDefault){a.preventDefault()}if(this.stopDefault){a.stopEvent()}},destroy:function(){this.disable(true);Ext.destroy(this.el);this.purgeListeners()},handleDblClick:function(a){clearTimeout(this.timer);this.el.blur();this.fireEvent("mousedown",this,a);this.fireEvent("click",this,a)},handleMouseDown:function(a){clearTimeout(this.timer);this.el.blur();if(this.pressClass){this.el.addClass(this.pressClass)}this.mousedownTime=new Date();Ext.getDoc().on("mouseup",this.handleMouseUp,this);this.el.on("mouseout",this.handleMouseOut,this);this.fireEvent("mousedown",this,a);this.fireEvent("click",this,a);if(this.accelerate){this.delay=400}this.timer=this.click.defer(this.delay||this.interval,this,[a])},click:function(a){this.fireEvent("click",this,a);this.timer=this.click.defer(this.accelerate?this.easeOutExpo(this.mousedownTime.getElapsed(),400,-390,12000):this.interval,this,[a])},easeOutExpo:function(e,a,h,g){return(e==g)?a+h:h*(-Math.pow(2,-10*e/g)+1)+a},handleMouseOut:function(){clearTimeout(this.timer);if(this.pressClass){this.el.removeClass(this.pressClass)}this.el.on("mouseover",this.handleMouseReturn,this)},handleMouseReturn:function(){this.el.un("mouseover",this.handleMouseReturn,this);if(this.pressClass){this.el.addClass(this.pressClass)}this.click()},handleMouseUp:function(a){clearTimeout(this.timer);this.el.un("mouseover",this.handleMouseReturn,this);this.el.un("mouseout",this.handleMouseOut,this);Ext.getDoc().un("mouseup",this.handleMouseUp,this);this.el.removeClass(this.pressClass);this.fireEvent("mouseup",this,a)}});Ext.KeyNav=function(b,a){this.el=Ext.get(b);Ext.apply(this,a);if(!this.disabled){this.disabled=true;this.enable()}};Ext.KeyNav.prototype={disabled:false,defaultEventAction:"stopEvent",forceKeyDown:false,relay:function(c){var a=c.getKey(),b=this.keyToHandler[a];if(b&&this[b]){if(this.doRelay(c,this[b],b)!==true){c[this.defaultEventAction]()}}},doRelay:function(c,b,a){return b.call(this.scope||this,c,a)},enter:false,left:false,right:false,up:false,down:false,tab:false,esc:false,pageUp:false,pageDown:false,del:false,home:false,end:false,space:false,keyToHandler:{37:"left",39:"right",38:"up",40:"down",33:"pageUp",34:"pageDown",46:"del",36:"home",35:"end",13:"enter",27:"esc",9:"tab",32:"space"},stopKeyUp:function(b){var a=b.getKey();if(a>=37&&a<=40){b.stopEvent()}},destroy:function(){this.disable()},enable:function(){if(this.disabled){if(Ext.isSafari2){this.el.on("keyup",this.stopKeyUp,this)}this.el.on(this.isKeydown()?"keydown":"keypress",this.relay,this);this.disabled=false}},disable:function(){if(!this.disabled){if(Ext.isSafari2){this.el.un("keyup",this.stopKeyUp,this)}this.el.un(this.isKeydown()?"keydown":"keypress",this.relay,this);this.disabled=true}},setDisabled:function(a){this[a?"disable":"enable"]()},isKeydown:function(){return this.forceKeyDown||Ext.EventManager.useKeydown}};Ext.KeyMap=function(c,b,a){this.el=Ext.get(c);this.eventName=a||"keydown";this.bindings=[];if(b){this.addBinding(b)}this.enable()};Ext.KeyMap.prototype={stopEvent:false,addBinding:function(b){if(Ext.isArray(b)){Ext.each(b,function(j){this.addBinding(j)},this);return}var k=b.key,g=b.fn||b.handler,l=b.scope;if(b.stopEvent){this.stopEvent=b.stopEvent}if(typeof k=="string"){var h=[];var e=k.toUpperCase();for(var c=0,d=e.length;c<d;c++){h.push(e.charCodeAt(c))}k=h}var a=Ext.isArray(k);var i=function(o){if(this.checkModifiers(b,o)){var m=o.getKey();if(a){for(var n=0,j=k.length;n<j;n++){if(k[n]==m){if(this.stopEvent){o.stopEvent()}g.call(l||window,m,o);return}}}else{if(m==k){if(this.stopEvent){o.stopEvent()}g.call(l||window,m,o)}}}};this.bindings.push(i)},checkModifiers:function(b,h){var j,d,g=["shift","ctrl","alt"];for(var c=0,a=g.length;c<a;++c){d=g[c];j=b[d];if(!(j===undefined||(j===h[d+"Key"]))){return false}}return true},on:function(b,d,c){var h,a,e,g;if(typeof b=="object"&&!Ext.isArray(b)){h=b.key;a=b.shift;e=b.ctrl;g=b.alt}else{h=b}this.addBinding({key:h,shift:a,ctrl:e,alt:g,fn:d,scope:c})},handleKeyDown:function(g){if(this.enabled){var c=this.bindings;for(var d=0,a=c.length;d<a;d++){c[d].call(this,g)}}},isEnabled:function(){return this.enabled},enable:function(){if(!this.enabled){this.el.on(this.eventName,this.handleKeyDown,this);this.enabled=true}},disable:function(){if(this.enabled){this.el.removeListener(this.eventName,this.handleKeyDown,this);this.enabled=false}},setDisabled:function(a){this[a?"disable":"enable"]()}};Ext.util.TextMetrics=function(){var a;return{measure:function(b,c,d){if(!a){a=Ext.util.TextMetrics.Instance(b,d)}a.bind(b);a.setFixedWidth(d||"auto");return a.getSize(c)},createInstance:function(b,c){return Ext.util.TextMetrics.Instance(b,c)}}}();Ext.util.TextMetrics.Instance=function(b,d){var c=new Ext.Element(document.createElement("div"));document.body.appendChild(c.dom);c.position("absolute");c.setLeftTop(-1000,-1000);c.hide();if(d){c.setWidth(d)}var a={getSize:function(g){c.update(g);var e=c.getSize();c.update("");return e},bind:function(e){c.setStyle(Ext.fly(e).getStyles("font-size","font-style","font-weight","font-family","line-height","text-transform","letter-spacing"))},setFixedWidth:function(e){c.setWidth(e)},getWidth:function(e){c.dom.style.width="auto";return this.getSize(e).width},getHeight:function(e){return this.getSize(e).height}};a.bind(b);return a};Ext.Element.addMethods({getTextWidth:function(c,b,a){return(Ext.util.TextMetrics.measure(this.dom,Ext.value(c,this.dom.innerHTML,true)).width).constrain(b||0,a||1000000)}});Ext.util.Cookies={set:function(c,e){var a=arguments;var i=arguments.length;var b=(i>2)?a[2]:null;var h=(i>3)?a[3]:"/";var d=(i>4)?a[4]:null;var g=(i>5)?a[5]:false;document.cookie=c+"="+escape(e)+((b===null)?"":("; expires="+b.toGMTString()))+((h===null)?"":("; path="+h))+((d===null)?"":("; domain="+d))+((g===true)?"; secure":"")},get:function(d){var b=d+"=";var g=b.length;var a=document.cookie.length;var e=0;var c=0;while(e<a){c=e+g;if(document.cookie.substring(e,c)==b){return Ext.util.Cookies.getCookieVal(c)}e=document.cookie.indexOf(" ",e)+1;if(e===0){break}}return null},clear:function(a){if(Ext.util.Cookies.get(a)){document.cookie=a+"=; expires=Thu, 01-Jan-70 00:00:01 GMT"}},getCookieVal:function(b){var a=document.cookie.indexOf(";",b);if(a==-1){a=document.cookie.length}return unescape(document.cookie.substring(b,a))}};Ext.handleError=function(a){throw a};Ext.Error=function(a){this.message=(this.lang[a])?this.lang[a]:a};Ext.Error.prototype=new Error();Ext.apply(Ext.Error.prototype,{lang:{},name:"Ext.Error",getName:function(){return this.name},getMessage:function(){return this.message},toJson:function(){return Ext.encode(this)}});Ext.ComponentMgr=function(){var c=new Ext.util.MixedCollection();var b={};var a={};return{register:function(d){c.add(d)},unregister:function(d){c.remove(d)},get:function(d){return c.get(d)},onAvailable:function(g,e,d){c.on("add",function(h,i){if(i.id==g){e.call(d||i,i);c.un("add",e,d)}})},all:c,types:b,ptypes:a,isRegistered:function(d){return b[d]!==undefined},isPluginRegistered:function(d){return a[d]!==undefined},registerType:function(e,d){b[e]=d;d.xtype=e},create:function(d,e){return d.render?d:new b[d.xtype||e](d)},registerPlugin:function(e,d){a[e]=d;d.ptype=e},createPlugin:function(e,g){var d=a[e.ptype||g];if(d.init){return d}else{return new d(e)}}}}();Ext.reg=Ext.ComponentMgr.registerType;Ext.preg=Ext.ComponentMgr.registerPlugin;Ext.create=Ext.ComponentMgr.create;Ext.Component=function(b){b=b||{};if(b.initialConfig){if(b.isAction){this.baseAction=b}b=b.initialConfig}else{if(b.tagName||b.dom||Ext.isString(b)){b={applyTo:b,id:b.id||b}}}this.initialConfig=b;Ext.apply(this,b);this.addEvents("added","disable","enable","beforeshow","show","beforehide","hide","removed","beforerender","render","afterrender","beforedestroy","destroy","beforestaterestore","staterestore","beforestatesave","statesave");this.getId();Ext.ComponentMgr.register(this);Ext.Component.superclass.constructor.call(this);if(this.baseAction){this.baseAction.addComponent(this)}this.initComponent();if(this.plugins){if(Ext.isArray(this.plugins)){for(var c=0,a=this.plugins.length;c<a;c++){this.plugins[c]=this.initPlugin(this.plugins[c])}}else{this.plugins=this.initPlugin(this.plugins)}}if(this.stateful!==false){this.initState()}if(this.applyTo){this.applyToMarkup(this.applyTo);delete this.applyTo}else{if(this.renderTo){this.render(this.renderTo);delete this.renderTo}}};Ext.Component.AUTO_ID=1000;Ext.extend(Ext.Component,Ext.util.Observable,{disabled:false,hidden:false,autoEl:"div",disabledClass:"x-item-disabled",allowDomMove:true,autoShow:false,hideMode:"display",hideParent:false,rendered:false,tplWriteMode:"overwrite",bubbleEvents:[],ctype:"Ext.Component",actionMode:"el",getActionEl:function(){return this[this.actionMode]},initPlugin:function(a){if(a.ptype&&!Ext.isFunction(a.init)){a=Ext.ComponentMgr.createPlugin(a)}else{if(Ext.isString(a)){a=Ext.ComponentMgr.createPlugin({ptype:a})}}a.init(this);return a},initComponent:function(){if(this.listeners){this.on(this.listeners);delete this.listeners}this.enableBubble(this.bubbleEvents)},render:function(b,a){if(!this.rendered&&this.fireEvent("beforerender",this)!==false){if(!b&&this.el){this.el=Ext.get(this.el);b=this.el.dom.parentNode;this.allowDomMove=false}this.container=Ext.get(b);if(this.ctCls){this.container.addClass(this.ctCls)}this.rendered=true;if(a!==undefined){if(Ext.isNumber(a)){a=this.container.dom.childNodes[a]}else{a=Ext.getDom(a)}}this.onRender(this.container,a||null);if(this.autoShow){this.el.removeClass(["x-hidden","x-hide-"+this.hideMode])}if(this.cls){this.el.addClass(this.cls);delete this.cls}if(this.style){this.el.applyStyles(this.style);delete this.style}if(this.overCls){this.el.addClassOnOver(this.overCls)}this.fireEvent("render",this);var c=this.getContentTarget();if(this.html){c.update(Ext.DomHelper.markup(this.html));delete this.html}if(this.contentEl){var d=Ext.getDom(this.contentEl);Ext.fly(d).removeClass(["x-hidden","x-hide-display"]);c.appendChild(d)}if(this.tpl){if(!this.tpl.compile){this.tpl=new Ext.XTemplate(this.tpl)}if(this.data){this.tpl[this.tplWriteMode](c,this.data);delete this.data}}this.afterRender(this.container);if(this.hidden){this.doHide()}if(this.disabled){this.disable(true)}if(this.stateful!==false){this.initStateEvents()}this.fireEvent("afterrender",this)}return this},update:function(b,d,a){var c=this.getContentTarget();if(this.tpl&&typeof b!=="string"){this.tpl[this.tplWriteMode](c,b||{})}else{var e=Ext.isObject(b)?Ext.DomHelper.markup(b):b;c.update(e,d,a)}},onAdded:function(a,b){this.ownerCt=a;this.initRef();this.fireEvent("added",this,a,b)},onRemoved:function(){this.removeRef();this.fireEvent("removed",this,this.ownerCt);delete this.ownerCt},initRef:function(){if(this.ref&&!this.refOwner){var d=this.ref.split("/"),c=d.length,b=0,a=this;while(a&&b<c){a=a.ownerCt;++b}if(a){a[this.refName=d[--b]]=this;this.refOwner=a}}},removeRef:function(){if(this.refOwner&&this.refName){delete this.refOwner[this.refName];delete this.refOwner}},initState:function(){if(Ext.state.Manager){var b=this.getStateId();if(b){var a=Ext.state.Manager.get(b);if(a){if(this.fireEvent("beforestaterestore",this,a)!==false){this.applyState(Ext.apply({},a));this.fireEvent("staterestore",this,a)}}}}},getStateId:function(){return this.stateId||((/^(ext-comp-|ext-gen)/).test(String(this.id))?null:this.id)},initStateEvents:function(){if(this.stateEvents){for(var a=0,b;b=this.stateEvents[a];a++){this.on(b,this.saveState,this,{delay:100})}}},applyState:function(a){if(a){Ext.apply(this,a)}},getState:function(){return null},saveState:function(){if(Ext.state.Manager&&this.stateful!==false){var b=this.getStateId();if(b){var a=this.getState();if(this.fireEvent("beforestatesave",this,a)!==false){Ext.state.Manager.set(b,a);this.fireEvent("statesave",this,a)}}}},applyToMarkup:function(a){this.allowDomMove=false;this.el=Ext.get(a);this.render(this.el.dom.parentNode)},addClass:function(a){if(this.el){this.el.addClass(a)}else{this.cls=this.cls?this.cls+" "+a:a}return this},removeClass:function(a){if(this.el){this.el.removeClass(a)}else{if(this.cls){this.cls=this.cls.split(" ").remove(a).join(" ")}}return this},onRender:function(b,a){if(!this.el&&this.autoEl){if(Ext.isString(this.autoEl)){this.el=document.createElement(this.autoEl)}else{var c=document.createElement("div");Ext.DomHelper.overwrite(c,this.autoEl);this.el=c.firstChild}if(!this.el.id){this.el.id=this.getId()}}if(this.el){this.el=Ext.get(this.el);if(this.allowDomMove!==false){b.dom.insertBefore(this.el.dom,a);if(c){Ext.removeNode(c);c=null}}}},getAutoCreate:function(){var a=Ext.isObject(this.autoCreate)?this.autoCreate:Ext.apply({},this.defaultAutoCreate);if(this.id&&!a.id){a.id=this.id}return a},afterRender:Ext.emptyFn,destroy:function(){if(!this.isDestroyed){if(this.fireEvent("beforedestroy",this)!==false){this.destroying=true;this.beforeDestroy();if(this.ownerCt&&this.ownerCt.remove){this.ownerCt.remove(this,false)}if(this.rendered){this.el.remove();if(this.actionMode=="container"||this.removeMode=="container"){this.container.remove()}}if(this.focusTask&&this.focusTask.cancel){this.focusTask.cancel()}this.onDestroy();Ext.ComponentMgr.unregister(this);this.fireEvent("destroy",this);this.purgeListeners();this.destroying=false;this.isDestroyed=true}}},deleteMembers:function(){var b=arguments;for(var c=0,a=b.length;c<a;++c){delete this[b[c]]}},beforeDestroy:Ext.emptyFn,onDestroy:Ext.emptyFn,getEl:function(){return this.el},getContentTarget:function(){return this.el},getId:function(){return this.id||(this.id="ext-comp-"+(++Ext.Component.AUTO_ID))},getItemId:function(){return this.itemId||this.getId()},focus:function(b,a){if(a){this.focusTask=new Ext.util.DelayedTask(this.focus,this,[b,false]);this.focusTask.delay(Ext.isNumber(a)?a:10);return this}if(this.rendered&&!this.isDestroyed){this.el.focus();if(b===true){this.el.dom.select()}}return this},blur:function(){if(this.rendered){this.el.blur()}return this},disable:function(a){if(this.rendered){this.onDisable()}this.disabled=true;if(a!==true){this.fireEvent("disable",this)}return this},onDisable:function(){this.getActionEl().addClass(this.disabledClass);this.el.dom.disabled=true},enable:function(){if(this.rendered){this.onEnable()}this.disabled=false;this.fireEvent("enable",this);return this},onEnable:function(){this.getActionEl().removeClass(this.disabledClass);this.el.dom.disabled=false},setDisabled:function(a){return this[a?"disable":"enable"]()},show:function(){if(this.fireEvent("beforeshow",this)!==false){this.hidden=false;if(this.autoRender){this.render(Ext.isBoolean(this.autoRender)?Ext.getBody():this.autoRender)}if(this.rendered){this.onShow()}this.fireEvent("show",this)}return this},onShow:function(){this.getVisibilityEl().removeClass("x-hide-"+this.hideMode)},hide:function(){if(this.fireEvent("beforehide",this)!==false){this.doHide();this.fireEvent("hide",this)}return this},doHide:function(){this.hidden=true;if(this.rendered){this.onHide()}},onHide:function(){this.getVisibilityEl().addClass("x-hide-"+this.hideMode)},getVisibilityEl:function(){return this.hideParent?this.container:this.getActionEl()},setVisible:function(a){return this[a?"show":"hide"]()},isVisible:function(){return this.rendered&&this.getVisibilityEl().isVisible()},cloneConfig:function(b){b=b||{};var c=b.id||Ext.id();var a=Ext.applyIf(b,this.initialConfig);a.id=c;return new this.constructor(a)},getXType:function(){return this.constructor.xtype},isXType:function(b,a){if(Ext.isFunction(b)){b=b.xtype}else{if(Ext.isObject(b)){b=b.constructor.xtype}}return !a?("/"+this.getXTypes()+"/").indexOf("/"+b+"/")!=-1:this.constructor.xtype==b},getXTypes:function(){var a=this.constructor;if(!a.xtypes){var d=[],b=this;while(b&&b.constructor.xtype){d.unshift(b.constructor.xtype);b=b.constructor.superclass}a.xtypeChain=d;a.xtypes=d.join("/")}return a.xtypes},findParentBy:function(a){for(var b=this.ownerCt;(b!=null)&&!a(b,this);b=b.ownerCt){}return b||null},findParentByType:function(b,a){return this.findParentBy(function(d){return d.isXType(b,a)})},bubble:function(c,b,a){var d=this;while(d){if(c.apply(b||d,a||[d])===false){break}d=d.ownerCt}return this},getPositionEl:function(){return this.positionEl||this.el},purgeListeners:function(){Ext.Component.superclass.purgeListeners.call(this);if(this.mons){this.on("beforedestroy",this.clearMons,this,{single:true})}},clearMons:function(){Ext.each(this.mons,function(a){a.item.un(a.ename,a.fn,a.scope)},this);this.mons=[]},createMons:function(){if(!this.mons){this.mons=[];this.on("beforedestroy",this.clearMons,this,{single:true})}},mon:function(g,b,d,c,a){this.createMons();if(Ext.isObject(b)){var j=/^(?:scope|delay|buffer|single|stopEvent|preventDefault|stopPropagation|normalized|args|delegate)$/;var i=b;for(var h in i){if(j.test(h)){continue}if(Ext.isFunction(i[h])){this.mons.push({item:g,ename:h,fn:i[h],scope:i.scope});g.on(h,i[h],i.scope,i)}else{this.mons.push({item:g,ename:h,fn:i[h],scope:i.scope});g.on(h,i[h])}}return}this.mons.push({item:g,ename:b,fn:d,scope:c});g.on(b,d,c,a)},mun:function(h,c,g,e){var j,d;this.createMons();for(var b=0,a=this.mons.length;b<a;++b){d=this.mons[b];if(h===d.item&&c==d.ename&&g===d.fn&&e===d.scope){this.mons.splice(b,1);h.un(c,g,e);j=true;break}}return j},nextSibling:function(){if(this.ownerCt){var a=this.ownerCt.items.indexOf(this);if(a!=-1&&a+1<this.ownerCt.items.getCount()){return this.ownerCt.items.itemAt(a+1)}}return null},previousSibling:function(){if(this.ownerCt){var a=this.ownerCt.items.indexOf(this);if(a>0){return this.ownerCt.items.itemAt(a-1)}}return null},getBubbleTarget:function(){return this.ownerCt}});Ext.reg("component",Ext.Component);Ext.Action=Ext.extend(Object,{constructor:function(a){this.initialConfig=a;this.itemId=a.itemId=(a.itemId||a.id||Ext.id());this.items=[]},isAction:true,setText:function(a){this.initialConfig.text=a;this.callEach("setText",[a])},getText:function(){return this.initialConfig.text},setIconClass:function(a){this.initialConfig.iconCls=a;this.callEach("setIconClass",[a])},getIconClass:function(){return this.initialConfig.iconCls},setDisabled:function(a){this.initialConfig.disabled=a;this.callEach("setDisabled",[a])},enable:function(){this.setDisabled(false)},disable:function(){this.setDisabled(true)},isDisabled:function(){return this.initialConfig.disabled},setHidden:function(a){this.initialConfig.hidden=a;this.callEach("setVisible",[!a])},show:function(){this.setHidden(false)},hide:function(){this.setHidden(true)},isHidden:function(){return this.initialConfig.hidden},setHandler:function(b,a){this.initialConfig.handler=b;this.initialConfig.scope=a;this.callEach("setHandler",[b,a])},each:function(b,a){Ext.each(this.items,b,a)},callEach:function(e,b){var d=this.items;for(var c=0,a=d.length;c<a;c++){d[c][e].apply(d[c],b)}},addComponent:function(a){this.items.push(a);a.on("destroy",this.removeComponent,this)},removeComponent:function(a){this.items.remove(a)},execute:function(){this.initialConfig.handler.apply(this.initialConfig.scope||window,arguments)}});(function(){Ext.Layer=function(d,c){d=d||{};var e=Ext.DomHelper,h=d.parentEl,g=h?Ext.getDom(h):document.body;if(c){this.dom=Ext.getDom(c)}if(!this.dom){var i=d.dh||{tag:"div",cls:"x-layer"};this.dom=e.append(g,i)}if(d.cls){this.addClass(d.cls)}this.constrain=d.constrain!==false;this.setVisibilityMode(Ext.Element.VISIBILITY);if(d.id){this.id=this.dom.id=d.id}else{this.id=Ext.id(this.dom)}this.zindex=d.zindex||this.getZIndex();this.position("absolute",this.zindex);if(d.shadow){this.shadowOffset=d.shadowOffset||4;this.shadow=new Ext.Shadow({offset:this.shadowOffset,mode:d.shadow})}else{this.shadowOffset=0}this.useShim=d.shim!==false&&Ext.useShims;this.useDisplay=d.useDisplay;this.hide()};var a=Ext.Element.prototype;var b=[];Ext.extend(Ext.Layer,Ext.Element,{getZIndex:function(){return this.zindex||parseInt((this.getShim()||this).getStyle("z-index"),10)||11000},getShim:function(){if(!this.useShim){return null}if(this.shim){return this.shim}var d=b.shift();if(!d){d=this.createShim();d.enableDisplayMode("block");d.dom.style.display="none";d.dom.style.visibility="visible"}var c=this.dom.parentNode;if(d.dom.parentNode!=c){c.insertBefore(d.dom,this.dom)}d.setStyle("z-index",this.getZIndex()-2);this.shim=d;return d},hideShim:function(){if(this.shim){this.shim.setDisplayed(false);b.push(this.shim);delete this.shim}},disableShadow:function(){if(this.shadow){this.shadowDisabled=true;this.shadow.hide();this.lastShadowOffset=this.shadowOffset;this.shadowOffset=0}},enableShadow:function(c){if(this.shadow){this.shadowDisabled=false;if(Ext.isDefined(this.lastShadowOffset)){this.shadowOffset=this.lastShadowOffset;delete this.lastShadowOffset}if(c){this.sync(true)}}},sync:function(d){var n=this.shadow;if(!this.updating&&this.isVisible()&&(n||this.useShim)){var i=this.getShim(),m=this.getWidth(),j=this.getHeight(),e=this.getLeft(true),o=this.getTop(true);if(n&&!this.shadowDisabled){if(d&&!n.isVisible()){n.show(this)}else{n.realign(e,o,m,j)}if(i){if(d){i.show()}var k=n.el.getXY(),g=i.dom.style,c=n.el.getSize();g.left=(k[0])+"px";g.top=(k[1])+"px";g.width=(c.width)+"px";g.height=(c.height)+"px"}}else{if(i){if(d){i.show()}i.setSize(m,j);i.setLeftTop(e,o)}}}},destroy:function(){this.hideShim();if(this.shadow){this.shadow.hide()}this.removeAllListeners();Ext.removeNode(this.dom);delete this.dom},remove:function(){this.destroy()},beginUpdate:function(){this.updating=true},endUpdate:function(){this.updating=false;this.sync(true)},hideUnders:function(c){if(this.shadow){this.shadow.hide()}this.hideShim()},constrainXY:function(){if(this.constrain){var j=Ext.lib.Dom.getViewWidth(),d=Ext.lib.Dom.getViewHeight();var o=Ext.getDoc().getScroll();var n=this.getXY();var k=n[0],i=n[1];var c=this.shadowOffset;var l=this.dom.offsetWidth+c,e=this.dom.offsetHeight+c;var g=false;if((k+l)>j+o.left){k=j-l-c;g=true}if((i+e)>d+o.top){i=d-e-c;g=true}if(k<o.left){k=o.left;g=true}if(i<o.top){i=o.top;g=true}if(g){if(this.avoidY){var m=this.avoidY;if(i<=m&&(i+e)>=m){i=m-e-5}}n=[k,i];this.storeXY(n);a.setXY.call(this,n);this.sync()}}return this},getConstrainOffset:function(){return this.shadowOffset},isVisible:function(){return this.visible},showAction:function(){this.visible=true;if(this.useDisplay===true){this.setDisplayed("")}else{if(this.lastXY){a.setXY.call(this,this.lastXY)}else{if(this.lastLT){a.setLeftTop.call(this,this.lastLT[0],this.lastLT[1])}}}},hideAction:function(){this.visible=false;if(this.useDisplay===true){this.setDisplayed(false)}else{this.setLeftTop(-10000,-10000)}},setVisible:function(i,h,k,l,j){if(i){this.showAction()}if(h&&i){var g=function(){this.sync(true);if(l){l()}}.createDelegate(this);a.setVisible.call(this,true,true,k,g,j)}else{if(!i){this.hideUnders(true)}var g=l;if(h){g=function(){this.hideAction();if(l){l()}}.createDelegate(this)}a.setVisible.call(this,i,h,k,g,j);if(i){this.sync(true)}else{if(!h){this.hideAction()}}}return this},storeXY:function(c){delete this.lastLT;this.lastXY=c},storeLeftTop:function(d,c){delete this.lastXY;this.lastLT=[d,c]},beforeFx:function(){this.beforeAction();return Ext.Layer.superclass.beforeFx.apply(this,arguments)},afterFx:function(){Ext.Layer.superclass.afterFx.apply(this,arguments);this.sync(this.isVisible())},beforeAction:function(){if(!this.updating&&this.shadow){this.shadow.hide()}},setLeft:function(c){this.storeLeftTop(c,this.getTop(true));a.setLeft.apply(this,arguments);this.sync();return this},setTop:function(c){this.storeLeftTop(this.getLeft(true),c);a.setTop.apply(this,arguments);this.sync();return this},setLeftTop:function(d,c){this.storeLeftTop(d,c);a.setLeftTop.apply(this,arguments);this.sync();return this},setXY:function(j,h,k,l,i){this.fixDisplay();this.beforeAction();this.storeXY(j);var g=this.createCB(l);a.setXY.call(this,j,h,k,g,i);if(!h){g()}return this},createCB:function(e){var d=this;return function(){d.constrainXY();d.sync(true);if(e){e()}}},setX:function(g,h,j,k,i){this.setXY([g,this.getY()],h,j,k,i);return this},setY:function(k,g,i,j,h){this.setXY([this.getX(),k],g,i,j,h);return this},setSize:function(j,k,i,m,n,l){this.beforeAction();var g=this.createCB(n);a.setSize.call(this,j,k,i,m,g,l);if(!i){g()}return this},setWidth:function(i,h,k,l,j){this.beforeAction();var g=this.createCB(l);a.setWidth.call(this,i,h,k,g,j);if(!h){g()}return this},setHeight:function(j,i,l,m,k){this.beforeAction();var g=this.createCB(m);a.setHeight.call(this,j,i,l,g,k);if(!i){g()}return this},setBounds:function(o,m,p,i,n,k,l,j){this.beforeAction();var g=this.createCB(l);if(!n){this.storeXY([o,m]);a.setXY.call(this,[o,m]);a.setSize.call(this,p,i,n,k,g,j);g()}else{a.setBounds.call(this,o,m,p,i,n,k,g,j)}return this},setZIndex:function(c){this.zindex=c;this.setStyle("z-index",c+2);if(this.shadow){this.shadow.setZIndex(c+1)}if(this.shim){this.shim.setStyle("z-index",c)}return this}})})();Ext.Shadow=function(d){Ext.apply(this,d);if(typeof this.mode!="string"){this.mode=this.defaultMode}var e=this.offset,c={h:0},b=Math.floor(this.offset/2);switch(this.mode.toLowerCase()){case"drop":c.w=0;c.l=c.t=e;c.t-=1;if(Ext.isIE9m){c.l-=this.offset+b;c.t-=this.offset+b;c.w-=b;c.h-=b;c.t+=1}break;case"sides":c.w=(e*2);c.l=-e;c.t=e-1;if(Ext.isIE9m){c.l-=(this.offset-b);c.t-=this.offset+b;c.l+=1;c.w-=(this.offset-b)*2;c.w-=b+1;c.h-=1}break;case"frame":c.w=c.h=(e*2);c.l=c.t=-e;c.t+=1;c.h-=2;if(Ext.isIE9m){c.l-=(this.offset-b);c.t-=(this.offset-b);c.l+=1;c.w-=(this.offset+b+1);c.h-=(this.offset+b);c.h+=1}break}this.adjusts=c};Ext.Shadow.prototype={offset:4,defaultMode:"drop",show:function(a){a=Ext.get(a);if(!this.el){this.el=Ext.Shadow.Pool.pull();if(this.el.dom.nextSibling!=a.dom){this.el.insertBefore(a)}}this.el.setStyle("z-index",this.zIndex||parseInt(a.getStyle("z-index"),10)-1);if(Ext.isIE9m){this.el.dom.style.filter="progid:DXImageTransform.Microsoft.alpha(opacity=50) progid:DXImageTransform.Microsoft.Blur(pixelradius="+(this.offset)+")"}this.realign(a.getLeft(true),a.getTop(true),a.getWidth(),a.getHeight());this.el.dom.style.display="block"},isVisible:function(){return this.el?true:false},realign:function(b,r,q,g){if(!this.el){return}var n=this.adjusts,k=this.el.dom,u=k.style,i=0,p=(q+n.w),e=(g+n.h),j=p+"px",o=e+"px",m,c;u.left=(b+n.l)+"px";u.top=(r+n.t)+"px";if(u.width!=j||u.height!=o){u.width=j;u.height=o;if(!Ext.isIE9m){m=k.childNodes;c=Math.max(0,(p-12))+"px";m[0].childNodes[1].style.width=c;m[1].childNodes[1].style.width=c;m[2].childNodes[1].style.width=c;m[1].style.height=Math.max(0,(e-12))+"px"}}},hide:function(){if(this.el){this.el.dom.style.display="none";Ext.Shadow.Pool.push(this.el);delete this.el}},setZIndex:function(a){this.zIndex=a;if(this.el){this.el.setStyle("z-index",a)}}};Ext.Shadow.Pool=function(){var b=[],a=Ext.isIE9m?'<div class="x-ie-shadow"></div>':'<div class="x-shadow"><div class="xst"><div class="xstl"></div><div class="xstc"></div><div class="xstr"></div></div><div class="xsc"><div class="xsml"></div><div class="xsmc"></div><div class="xsmr"></div></div><div class="xsb"><div class="xsbl"></div><div class="xsbc"></div><div class="xsbr"></div></div></div>';return{pull:function(){var c=b.shift();if(!c){c=Ext.get(Ext.DomHelper.insertHtml("beforeBegin",document.body.firstChild,a));c.autoBoxAdjust=false}return c},push:function(c){b.push(c)}}}();Ext.BoxComponent=Ext.extend(Ext.Component,{initComponent:function(){Ext.BoxComponent.superclass.initComponent.call(this);this.addEvents("resize","move")},boxReady:false,deferHeight:false,setSize:function(b,d){if(typeof b=="object"){d=b.height;b=b.width}if(Ext.isDefined(b)&&Ext.isDefined(this.boxMinWidth)&&(b<this.boxMinWidth)){b=this.boxMinWidth}if(Ext.isDefined(d)&&Ext.isDefined(this.boxMinHeight)&&(d<this.boxMinHeight)){d=this.boxMinHeight}if(Ext.isDefined(b)&&Ext.isDefined(this.boxMaxWidth)&&(b>this.boxMaxWidth)){b=this.boxMaxWidth}if(Ext.isDefined(d)&&Ext.isDefined(this.boxMaxHeight)&&(d>this.boxMaxHeight)){d=this.boxMaxHeight}if(!this.boxReady){this.width=b;this.height=d;return this}if(this.cacheSizes!==false&&this.lastSize&&this.lastSize.width==b&&this.lastSize.height==d){return this}this.lastSize={width:b,height:d};var c=this.adjustSize(b,d),g=c.width,a=c.height,e;if(g!==undefined||a!==undefined){e=this.getResizeEl();if(!this.deferHeight&&g!==undefined&&a!==undefined){e.setSize(g,a)}else{if(!this.deferHeight&&a!==undefined){e.setHeight(a)}else{if(g!==undefined){e.setWidth(g)}}}this.onResize(g,a,b,d);this.fireEvent("resize",this,g,a,b,d)}return this},setWidth:function(a){return this.setSize(a)},setHeight:function(a){return this.setSize(undefined,a)},getSize:function(){return this.getResizeEl().getSize()},getWidth:function(){return this.getResizeEl().getWidth()},getHeight:function(){return this.getResizeEl().getHeight()},getOuterSize:function(){var a=this.getResizeEl();return{width:a.getWidth()+a.getMargins("lr"),height:a.getHeight()+a.getMargins("tb")}},getPosition:function(a){var b=this.getPositionEl();if(a===true){return[b.getLeft(true),b.getTop(true)]}return this.xy||b.getXY()},getBox:function(a){var c=this.getPosition(a);var b=this.getSize();b.x=c[0];b.y=c[1];return b},updateBox:function(a){this.setSize(a.width,a.height);this.setPagePosition(a.x,a.y);return this},getResizeEl:function(){return this.resizeEl||this.el},setAutoScroll:function(a){if(this.rendered){this.getContentTarget().setOverflow(a?"auto":"")}this.autoScroll=a;return this},setPosition:function(a,g){if(a&&typeof a[1]=="number"){g=a[1];a=a[0]}this.x=a;this.y=g;if(!this.boxReady){return this}var b=this.adjustPosition(a,g);var e=b.x,d=b.y;var c=this.getPositionEl();if(e!==undefined||d!==undefined){if(e!==undefined&&d!==undefined){c.setLeftTop(e,d)}else{if(e!==undefined){c.setLeft(e)}else{if(d!==undefined){c.setTop(d)}}}this.onPosition(e,d);this.fireEvent("move",this,e,d)}return this},setPagePosition:function(a,c){if(a&&typeof a[1]=="number"){c=a[1];a=a[0]}this.pageX=a;this.pageY=c;if(!this.boxReady){return}if(a===undefined||c===undefined){return}var b=this.getPositionEl().translatePoints(a,c);this.setPosition(b.left,b.top);return this},afterRender:function(){Ext.BoxComponent.superclass.afterRender.call(this);if(this.resizeEl){this.resizeEl=Ext.get(this.resizeEl)}if(this.positionEl){this.positionEl=Ext.get(this.positionEl)}this.boxReady=true;Ext.isDefined(this.autoScroll)&&this.setAutoScroll(this.autoScroll);this.setSize(this.width,this.height);if(this.x||this.y){this.setPosition(this.x,this.y)}else{if(this.pageX||this.pageY){this.setPagePosition(this.pageX,this.pageY)}}},syncSize:function(){delete this.lastSize;this.setSize(this.autoWidth?undefined:this.getResizeEl().getWidth(),this.autoHeight?undefined:this.getResizeEl().getHeight());return this},onResize:function(d,b,a,c){},onPosition:function(a,b){},adjustSize:function(a,b){if(this.autoWidth){a="auto"}if(this.autoHeight){b="auto"}return{width:a,height:b}},adjustPosition:function(a,b){return{x:a,y:b}}});Ext.reg("box",Ext.BoxComponent);Ext.Spacer=Ext.extend(Ext.BoxComponent,{autoEl:"div"});Ext.reg("spacer",Ext.Spacer);Ext.SplitBar=function(c,e,b,d,a){this.el=Ext.get(c,true);this.el.unselectable();this.resizingEl=Ext.get(e,true);this.orientation=b||Ext.SplitBar.HORIZONTAL;this.minSize=0;this.maxSize=2000;this.animate=false;this.useShim=false;this.shim=null;if(!a){this.proxy=Ext.SplitBar.createProxy(this.orientation)}else{this.proxy=Ext.get(a).dom}this.dd=new Ext.dd.DDProxy(this.el.dom.id,"XSplitBars",{dragElId:this.proxy.id});this.dd.b4StartDrag=this.onStartProxyDrag.createDelegate(this);this.dd.endDrag=this.onEndProxyDrag.createDelegate(this);this.dragSpecs={};this.adapter=new Ext.SplitBar.BasicLayoutAdapter();this.adapter.init(this);if(this.orientation==Ext.SplitBar.HORIZONTAL){this.placement=d||(this.el.getX()>this.resizingEl.getX()?Ext.SplitBar.LEFT:Ext.SplitBar.RIGHT);this.el.addClass("x-splitbar-h")}else{this.placement=d||(this.el.getY()>this.resizingEl.getY()?Ext.SplitBar.TOP:Ext.SplitBar.BOTTOM);this.el.addClass("x-splitbar-v")}this.addEvents("resize","moved","beforeresize","beforeapply");Ext.SplitBar.superclass.constructor.call(this)};Ext.extend(Ext.SplitBar,Ext.util.Observable,{onStartProxyDrag:function(a,e){this.fireEvent("beforeresize",this);this.overlay=Ext.DomHelper.append(document.body,{cls:"x-drag-overlay",html:"&#160;"},true);this.overlay.unselectable();this.overlay.setSize(Ext.lib.Dom.getViewWidth(true),Ext.lib.Dom.getViewHeight(true));this.overlay.show();Ext.get(this.proxy).setDisplayed("block");var c=this.adapter.getElementSize(this);this.activeMinSize=this.getMinimumSize();this.activeMaxSize=this.getMaximumSize();var d=c-this.activeMinSize;var b=Math.max(this.activeMaxSize-c,0);if(this.orientation==Ext.SplitBar.HORIZONTAL){this.dd.resetConstraints();this.dd.setXConstraint(this.placement==Ext.SplitBar.LEFT?d:b,this.placement==Ext.SplitBar.LEFT?b:d,this.tickSize);this.dd.setYConstraint(0,0)}else{this.dd.resetConstraints();this.dd.setXConstraint(0,0);this.dd.setYConstraint(this.placement==Ext.SplitBar.TOP?d:b,this.placement==Ext.SplitBar.TOP?b:d,this.tickSize)}this.dragSpecs.startSize=c;this.dragSpecs.startPoint=[a,e];Ext.dd.DDProxy.prototype.b4StartDrag.call(this.dd,a,e)},onEndProxyDrag:function(c){Ext.get(this.proxy).setDisplayed(false);var b=Ext.lib.Event.getXY(c);if(this.overlay){Ext.destroy(this.overlay);delete this.overlay}var a;if(this.orientation==Ext.SplitBar.HORIZONTAL){a=this.dragSpecs.startSize+(this.placement==Ext.SplitBar.LEFT?b[0]-this.dragSpecs.startPoint[0]:this.dragSpecs.startPoint[0]-b[0])}else{a=this.dragSpecs.startSize+(this.placement==Ext.SplitBar.TOP?b[1]-this.dragSpecs.startPoint[1]:this.dragSpecs.startPoint[1]-b[1])}a=Math.min(Math.max(a,this.activeMinSize),this.activeMaxSize);if(a!=this.dragSpecs.startSize){if(this.fireEvent("beforeapply",this,a)!==false){this.adapter.setElementSize(this,a);this.fireEvent("moved",this,a);this.fireEvent("resize",this,a)}}},getAdapter:function(){return this.adapter},setAdapter:function(a){this.adapter=a;this.adapter.init(this)},getMinimumSize:function(){return this.minSize},setMinimumSize:function(a){this.minSize=a},getMaximumSize:function(){return this.maxSize},setMaximumSize:function(a){this.maxSize=a},setCurrentSize:function(b){var a=this.animate;this.animate=false;this.adapter.setElementSize(this,b);this.animate=a},destroy:function(a){Ext.destroy(this.shim,Ext.get(this.proxy));this.dd.unreg();if(a){this.el.remove()}this.purgeListeners()}});Ext.SplitBar.createProxy=function(b){var c=new Ext.Element(document.createElement("div"));document.body.appendChild(c.dom);c.unselectable();var a="x-splitbar-proxy";c.addClass(a+" "+(b==Ext.SplitBar.HORIZONTAL?a+"-h":a+"-v"));return c.dom};Ext.SplitBar.BasicLayoutAdapter=function(){};Ext.SplitBar.BasicLayoutAdapter.prototype={init:function(a){},getElementSize:function(a){if(a.orientation==Ext.SplitBar.HORIZONTAL){return a.resizingEl.getWidth()}else{return a.resizingEl.getHeight()}},setElementSize:function(b,a,c){if(b.orientation==Ext.SplitBar.HORIZONTAL){if(!b.animate){b.resizingEl.setWidth(a);if(c){c(b,a)}}else{b.resizingEl.setWidth(a,true,0.1,c,"easeOut")}}else{if(!b.animate){b.resizingEl.setHeight(a);if(c){c(b,a)}}else{b.resizingEl.setHeight(a,true,0.1,c,"easeOut")}}}};Ext.SplitBar.AbsoluteLayoutAdapter=function(a){this.basic=new Ext.SplitBar.BasicLayoutAdapter();this.container=Ext.get(a)};Ext.SplitBar.AbsoluteLayoutAdapter.prototype={init:function(a){this.basic.init(a)},getElementSize:function(a){return this.basic.getElementSize(a)},setElementSize:function(b,a,c){this.basic.setElementSize(b,a,this.moveSplitter.createDelegate(this,[b]))},moveSplitter:function(a){var b=Ext.SplitBar;switch(a.placement){case b.LEFT:a.el.setX(a.resizingEl.getRight());break;case b.RIGHT:a.el.setStyle("right",(this.container.getWidth()-a.resizingEl.getLeft())+"px");break;case b.TOP:a.el.setY(a.resizingEl.getBottom());break;case b.BOTTOM:a.el.setY(a.resizingEl.getTop()-a.el.getHeight());break}}};Ext.SplitBar.VERTICAL=1;Ext.SplitBar.HORIZONTAL=2;Ext.SplitBar.LEFT=1;Ext.SplitBar.RIGHT=2;Ext.SplitBar.TOP=3;Ext.SplitBar.BOTTOM=4;Ext.Container=Ext.extend(Ext.BoxComponent,{bufferResize:50,autoDestroy:true,forceLayout:false,defaultType:"panel",resizeEvent:"resize",bubbleEvents:["add","remove"],initComponent:function(){Ext.Container.superclass.initComponent.call(this);this.addEvents("afterlayout","beforeadd","beforeremove","add","remove");var a=this.items;if(a){delete this.items;this.add(a)}},initItems:function(){if(!this.items){this.items=new Ext.util.MixedCollection(false,this.getComponentId);this.getLayout()}},setLayout:function(a){if(this.layout&&this.layout!=a){this.layout.setContainer(null)}this.layout=a;this.initItems();a.setContainer(this)},afterRender:function(){Ext.Container.superclass.afterRender.call(this);if(!this.layout){this.layout="auto"}if(Ext.isObject(this.layout)&&!this.layout.layout){this.layoutConfig=this.layout;this.layout=this.layoutConfig.type}if(Ext.isString(this.layout)){this.layout=new Ext.Container.LAYOUTS[this.layout.toLowerCase()](this.layoutConfig)}this.setLayout(this.layout);if(this.activeItem!==undefined&&this.layout.setActiveItem){var a=this.activeItem;delete this.activeItem;this.layout.setActiveItem(a)}if(!this.ownerCt){this.doLayout(false,true)}if(this.monitorResize===true){Ext.EventManager.onWindowResize(this.doLayout,this,[false])}},getLayoutTarget:function(){return this.el},getComponentId:function(a){return a.getItemId()},add:function(b){this.initItems();var e=arguments.length>1;if(e||Ext.isArray(b)){var a=[];Ext.each(e?arguments:b,function(h){a.push(this.add(h))},this);return a}var g=this.lookupComponent(this.applyDefaults(b));var d=this.items.length;if(this.fireEvent("beforeadd",this,g,d)!==false&&this.onBeforeAdd(g)!==false){this.items.add(g);g.onAdded(this,d);this.onAdd(g);this.fireEvent("add",this,g,d)}return g},onAdd:function(a){},onAdded:function(a,b){this.ownerCt=a;this.initRef();this.cascade(function(d){d.initRef()});this.fireEvent("added",this,a,b)},insert:function(e,b){var d=arguments,h=d.length,a=[],g,j;this.initItems();if(h>2){for(g=h-1;g>=1;--g){a.push(this.insert(e,d[g]))}return a}j=this.lookupComponent(this.applyDefaults(b));e=Math.min(e,this.items.length);if(this.fireEvent("beforeadd",this,j,e)!==false&&this.onBeforeAdd(j)!==false){if(j.ownerCt==this){this.items.remove(j)}this.items.insert(e,j);j.onAdded(this,e);this.onAdd(j);this.fireEvent("add",this,j,e)}return j},applyDefaults:function(b){var a=this.defaults;if(a){if(Ext.isFunction(a)){a=a.call(this,b)}if(Ext.isString(b)){b=Ext.ComponentMgr.get(b);Ext.apply(b,a)}else{if(!b.events){Ext.applyIf(b.isAction?b.initialConfig:b,a)}else{Ext.apply(b,a)}}}return b},onBeforeAdd:function(a){if(a.ownerCt){a.ownerCt.remove(a,false)}if(this.hideBorders===true){a.border=(a.border===true)}},remove:function(a,b){this.initItems();var d=this.getComponent(a);if(d&&this.fireEvent("beforeremove",this,d)!==false){this.doRemove(d,b);this.fireEvent("remove",this,d)}return d},onRemove:function(a){},doRemove:function(e,d){var b=this.layout,a=b&&this.rendered;if(a){b.onRemove(e)}this.items.remove(e);e.onRemoved();this.onRemove(e);if(d===true||(d!==false&&this.autoDestroy)){e.destroy()}if(a){b.afterRemove(e)}},removeAll:function(c){this.initItems();var e,g=[],b=[];this.items.each(function(h){g.push(h)});for(var d=0,a=g.length;d<a;++d){e=g[d];this.remove(e,c);if(e.ownerCt!==this){b.push(e)}}return b},getComponent:function(a){if(Ext.isObject(a)){a=a.getItemId()}return this.items.get(a)},lookupComponent:function(a){if(Ext.isString(a)){return Ext.ComponentMgr.get(a)}else{if(!a.events){return this.createComponent(a)}}return a},createComponent:function(a,d){if(a.render){return a}var b=Ext.create(Ext.apply({ownerCt:this},a),d||this.defaultType);delete b.initialConfig.ownerCt;delete b.ownerCt;return b},canLayout:function(){var a=this.getVisibilityEl();return a&&a.dom&&!a.isStyle("display","none")},doLayout:function(g,e){var k=this.rendered,j=e||this.forceLayout;if(this.collapsed||!this.canLayout()){this.deferLayout=this.deferLayout||!g;if(!j){return}g=g&&!this.deferLayout}else{delete this.deferLayout}if(k&&this.layout){this.layout.layout()}if(g!==true&&this.items){var d=this.items.items;for(var b=0,a=d.length;b<a;b++){var h=d[b];if(h.doLayout){h.doLayout(false,j)}}}if(k){this.onLayout(g,j)}this.hasLayout=true;delete this.forceLayout},onLayout:Ext.emptyFn,shouldBufferLayout:function(){var a=this.hasLayout;if(this.ownerCt){return a?!this.hasLayoutPending():false}return a},hasLayoutPending:function(){var a=false;this.ownerCt.bubble(function(b){if(b.layoutPending){a=true;return false}});return a},onShow:function(){Ext.Container.superclass.onShow.call(this);if(Ext.isDefined(this.deferLayout)){delete this.deferLayout;this.doLayout(true)}},getLayout:function(){if(!this.layout){var a=new Ext.layout.AutoLayout(this.layoutConfig);this.setLayout(a)}return this.layout},beforeDestroy:function(){var a;if(this.items){while(a=this.items.first()){this.doRemove(a,true)}}if(this.monitorResize){Ext.EventManager.removeResizeListener(this.doLayout,this)}Ext.destroy(this.layout);Ext.Container.superclass.beforeDestroy.call(this)},cascade:function(g,e,b){if(g.apply(e||this,b||[this])!==false){if(this.items){var d=this.items.items;for(var c=0,a=d.length;c<a;c++){if(d[c].cascade){d[c].cascade(g,e,b)}else{g.apply(e||d[c],b||[d[c]])}}}}return this},findById:function(c){var a=null,b=this;this.cascade(function(d){if(b!=d&&d.id===c){a=d;return false}});return a},findByType:function(b,a){return this.findBy(function(d){return d.isXType(b,a)})},find:function(b,a){return this.findBy(function(d){return d[b]===a})},findBy:function(d,c){var a=[],b=this;this.cascade(function(e){if(b!=e&&d.call(c||e,e,b)===true){a.push(e)}});return a},get:function(a){return this.getComponent(a)}});Ext.Container.LAYOUTS={};Ext.reg("container",Ext.Container);Ext.layout.ContainerLayout=Ext.extend(Object,{monitorResize:false,activeItem:null,constructor:function(a){this.id=Ext.id(null,"ext-layout-");Ext.apply(this,a)},type:"container",IEMeasureHack:function(k,g){var a=k.dom.childNodes,b=a.length,n,m=[],l,h,j;for(h=0;h<b;h++){n=a[h];l=Ext.get(n);if(l){m[h]=l.getStyle("display");l.setStyle({display:"none"})}}j=k?k.getViewSize(g):{};for(h=0;h<b;h++){n=a[h];l=Ext.get(n);if(l){l.setStyle({display:m[h]})}}return j},getLayoutTargetSize:Ext.EmptyFn,layout:function(){var a=this.container,b=a.getLayoutTarget();if(!(this.hasLayout||Ext.isEmpty(this.targetCls))){b.addClass(this.targetCls)}this.onLayout(a,b);a.fireEvent("afterlayout",a,this)},onLayout:function(a,b){this.renderAll(a,b)},isValidParent:function(b,a){return a&&b.getPositionEl().dom.parentNode==(a.dom||a)},renderAll:function(e,g){var b=e.items.items,d,h,a=b.length;for(d=0;d<a;d++){h=b[d];if(h&&(!h.rendered||!this.isValidParent(h,g))){this.renderItem(h,d,g)}}},renderItem:function(d,a,b){if(d){if(!d.rendered){d.render(b,a);this.configureItem(d)}else{if(!this.isValidParent(d,b)){if(Ext.isNumber(a)){a=b.dom.childNodes[a]}b.dom.insertBefore(d.getPositionEl().dom,a||null);d.container=b;this.configureItem(d)}}}},getRenderedItems:function(g){var e=g.getLayoutTarget(),h=g.items.items,a=h.length,d,j,b=[];for(d=0;d<a;d++){if((j=h[d]).rendered&&this.isValidParent(j,e)&&j.shouldLayout!==false){b.push(j)}}return b},configureItem:function(b){if(this.extraCls){var a=b.getPositionEl?b.getPositionEl():b;a.addClass(this.extraCls)}if(b.doLayout&&this.forceLayout){b.doLayout()}if(this.renderHidden&&b!=this.activeItem){b.hide()}},onRemove:function(b){if(this.activeItem==b){delete this.activeItem}if(b.rendered&&this.extraCls){var a=b.getPositionEl?b.getPositionEl():b;a.removeClass(this.extraCls)}},afterRemove:function(a){if(a.removeRestore){a.removeMode="container";delete a.removeRestore}},onResize:function(){var c=this.container,a;if(c.collapsed){return}if(a=c.bufferResize&&c.shouldBufferLayout()){if(!this.resizeTask){this.resizeTask=new Ext.util.DelayedTask(this.runLayout,this);this.resizeBuffer=Ext.isNumber(a)?a:50}c.layoutPending=true;this.resizeTask.delay(this.resizeBuffer)}else{this.runLayout()}},runLayout:function(){var a=this.container;this.layout();a.onLayout();delete a.layoutPending},setContainer:function(b){if(this.monitorResize&&b!=this.container){var a=this.container;if(a){a.un(a.resizeEvent,this.onResize,this)}if(b){b.on(b.resizeEvent,this.onResize,this)}}this.container=b},parseMargins:function(b){if(Ext.isNumber(b)){b=b.toString()}var c=b.split(" "),a=c.length;if(a==1){c[1]=c[2]=c[3]=c[0]}else{if(a==2){c[2]=c[0];c[3]=c[1]}else{if(a==3){c[3]=c[1]}}}return{top:parseInt(c[0],10)||0,right:parseInt(c[1],10)||0,bottom:parseInt(c[2],10)||0,left:parseInt(c[3],10)||0}},fieldTpl:(function(){var a=new Ext.Template('<div class="x-form-item {itemCls}" tabIndex="-1">','<label for="{id}" style="{labelStyle}" class="x-form-item-label">{label}{labelSeparator}</label>','<div class="x-form-element" id="x-form-el-{id}" style="{elementStyle}">','</div><div class="{clearCls}"></div>',"</div>");a.disableFormats=true;return a.compile()})(),destroy:function(){if(this.resizeTask&&this.resizeTask.cancel){this.resizeTask.cancel()}if(this.container){this.container.un(this.container.resizeEvent,this.onResize,this)}if(!Ext.isEmpty(this.targetCls)){var a=this.container.getLayoutTarget();if(a){a.removeClass(this.targetCls)}}}});Ext.layout.AutoLayout=Ext.extend(Ext.layout.ContainerLayout,{type:"auto",monitorResize:true,onLayout:function(d,g){Ext.layout.AutoLayout.superclass.onLayout.call(this,d,g);var e=this.getRenderedItems(d),a=e.length,b,h;for(b=0;b<a;b++){h=e[b];if(h.doLayout){h.doLayout(true)}}}});Ext.Container.LAYOUTS.auto=Ext.layout.AutoLayout;Ext.layout.FitLayout=Ext.extend(Ext.layout.ContainerLayout,{monitorResize:true,type:"fit",getLayoutTargetSize:function(){var a=this.container.getLayoutTarget();if(!a){return{}}return a.getStyleSize()},onLayout:function(a,b){Ext.layout.FitLayout.superclass.onLayout.call(this,a,b);if(!a.collapsed){this.setItemSize(this.activeItem||a.items.itemAt(0),this.getLayoutTargetSize())}},setItemSize:function(b,a){if(b&&a.height>0){b.setSize(a)}}});Ext.Container.LAYOUTS.fit=Ext.layout.FitLayout;Ext.layout.CardLayout=Ext.extend(Ext.layout.FitLayout,{deferredRender:false,layoutOnCardChange:false,renderHidden:true,type:"card",setActiveItem:function(d){var a=this.activeItem,b=this.container;d=b.getComponent(d);if(d&&a!=d){if(a){a.hide();if(a.hidden!==true){return false}a.fireEvent("deactivate",a)}var c=d.doLayout&&(this.layoutOnCardChange||!d.rendered);this.activeItem=d;delete d.deferLayout;d.show();this.layout();if(c){d.doLayout()}d.fireEvent("activate",d)}},renderAll:function(a,b){if(this.deferredRender){this.renderItem(this.activeItem,undefined,b)}else{Ext.layout.CardLayout.superclass.renderAll.call(this,a,b)}}});Ext.Container.LAYOUTS.card=Ext.layout.CardLayout;Ext.layout.AnchorLayout=Ext.extend(Ext.layout.ContainerLayout,{monitorResize:true,type:"anchor",defaultAnchor:"100%",parseAnchorRE:/^(r|right|b|bottom)$/i,getLayoutTargetSize:function(){var b=this.container.getLayoutTarget(),a={};if(b){a=b.getViewSize();if(Ext.isIE9m&&Ext.isStrict&&a.width==0){a=b.getStyleSize()}a.width-=b.getPadding("lr");a.height-=b.getPadding("tb")}return a},onLayout:function(m,w){Ext.layout.AnchorLayout.superclass.onLayout.call(this,m,w);var p=this.getLayoutTargetSize(),k=p.width,o=p.height,q=w.getStyle("overflow"),n=this.getRenderedItems(m),t=n.length,g=[],j,a,v,l,h,c,e,d,u=0,s,b;if(k<20&&o<20){return}if(m.anchorSize){if(typeof m.anchorSize=="number"){a=m.anchorSize}else{a=m.anchorSize.width;v=m.anchorSize.height}}else{a=m.initialConfig.width;v=m.initialConfig.height}for(s=0;s<t;s++){l=n[s];b=l.getPositionEl();if(!l.anchor&&l.items&&!Ext.isNumber(l.width)&&!(Ext.isIE6&&Ext.isStrict)){l.anchor=this.defaultAnchor}if(l.anchor){h=l.anchorSpec;if(!h){d=l.anchor.split(" ");l.anchorSpec=h={right:this.parseAnchor(d[0],l.initialConfig.width,a),bottom:this.parseAnchor(d[1],l.initialConfig.height,v)}}c=h.right?this.adjustWidthAnchor(h.right(k)-b.getMargins("lr"),l):undefined;e=h.bottom?this.adjustHeightAnchor(h.bottom(o)-b.getMargins("tb"),l):undefined;if(c||e){g.push({component:l,width:c||undefined,height:e||undefined})}}}for(s=0,t=g.length;s<t;s++){j=g[s];j.component.setSize(j.width,j.height)}if(q&&q!="hidden"&&!this.adjustmentPass){var r=this.getLayoutTargetSize();if(r.width!=p.width||r.height!=p.height){this.adjustmentPass=true;this.onLayout(m,w)}}delete this.adjustmentPass},parseAnchor:function(c,h,b){if(c&&c!="none"){var e;if(this.parseAnchorRE.test(c)){var g=b-h;return function(a){if(a!==e){e=a;return a-g}}}else{if(c.indexOf("%")!=-1){var d=parseFloat(c.replace("%",""))*0.01;return function(a){if(a!==e){e=a;return Math.floor(a*d)}}}else{c=parseInt(c,10);if(!isNaN(c)){return function(a){if(a!==e){e=a;return a+c}}}}}}return false},adjustWidthAnchor:function(b,a){return b},adjustHeightAnchor:function(b,a){return b}});Ext.Container.LAYOUTS.anchor=Ext.layout.AnchorLayout;Ext.layout.ColumnLayout=Ext.extend(Ext.layout.ContainerLayout,{monitorResize:true,type:"column",extraCls:"x-column",scrollOffset:0,targetCls:"x-column-layout-ct",isValidParent:function(b,a){return this.innerCt&&b.getPositionEl().dom.parentNode==this.innerCt.dom},getLayoutTargetSize:function(){var b=this.container.getLayoutTarget(),a;if(b){a=b.getViewSize();if(Ext.isIE9m&&Ext.isStrict&&a.width==0){a=b.getStyleSize()}a.width-=b.getPadding("lr");a.height-=b.getPadding("tb")}return a},renderAll:function(a,b){if(!this.innerCt){this.innerCt=b.createChild({cls:"x-column-inner"});this.innerCt.createChild({cls:"x-clear"})}Ext.layout.ColumnLayout.superclass.renderAll.call(this,a,this.innerCt)},onLayout:function(e,k){var g=e.items.items,j=g.length,n,b,a,o=[];this.renderAll(e,k);var r=this.getLayoutTargetSize();if(Ext.isIE9m&&(r.width<1&&r.height<1)){return}var p=r.width-this.scrollOffset,d=r.height,q=p;this.innerCt.setWidth(p);for(b=0;b<j;b++){n=g[b];a=n.getPositionEl().getMargins("lr");o[b]=a;if(!n.columnWidth){q-=(n.getWidth()+a)}}q=q<0?0:q;for(b=0;b<j;b++){n=g[b];a=o[b];if(n.columnWidth){n.setSize(Math.floor(n.columnWidth*q)-a)}}if(Ext.isIE9m){if(b=k.getStyle("overflow")&&b!="hidden"&&!this.adjustmentPass){var l=this.getLayoutTargetSize();if(l.width!=r.width){this.adjustmentPass=true;this.onLayout(e,k)}}}delete this.adjustmentPass}});Ext.Container.LAYOUTS.column=Ext.layout.ColumnLayout;Ext.layout.BorderLayout=Ext.extend(Ext.layout.ContainerLayout,{monitorResize:true,rendered:false,type:"border",targetCls:"x-border-layout-ct",getLayoutTargetSize:function(){var a=this.container.getLayoutTarget();return a?a.getViewSize():{}},onLayout:function(g,I){var j,B,F,o,x=g.items.items,C=x.length;if(!this.rendered){j=[];for(B=0;B<C;B++){F=x[B];o=F.region;if(F.collapsed){j.push(F)}F.collapsed=false;if(!F.rendered){F.render(I,B);F.getPositionEl().addClass("x-border-panel")}this[o]=o!="center"&&F.split?new Ext.layout.BorderLayout.SplitRegion(this,F.initialConfig,o):new Ext.layout.BorderLayout.Region(this,F.initialConfig,o);this[o].render(I,F)}this.rendered=true}var v=this.getLayoutTargetSize();if(v.width<20||v.height<20){if(j){this.restoreCollapsed=j}return}else{if(this.restoreCollapsed){j=this.restoreCollapsed;delete this.restoreCollapsed}}var t=v.width,D=v.height,r=t,A=D,p=0,q=0,y=this.north,u=this.south,l=this.west,E=this.east,F=this.center,H,z,d,G;if(!F&&Ext.layout.BorderLayout.WARN!==false){throw"No center region defined in BorderLayout "+g.id}if(y&&y.isVisible()){H=y.getSize();z=y.getMargins();H.width=t-(z.left+z.right);H.x=z.left;H.y=z.top;p=H.height+H.y+z.bottom;A-=p;y.applyLayout(H)}if(u&&u.isVisible()){H=u.getSize();z=u.getMargins();H.width=t-(z.left+z.right);H.x=z.left;G=(H.height+z.top+z.bottom);H.y=D-G+z.top;A-=G;u.applyLayout(H)}if(l&&l.isVisible()){H=l.getSize();z=l.getMargins();H.height=A-(z.top+z.bottom);H.x=z.left;H.y=p+z.top;d=(H.width+z.left+z.right);q+=d;r-=d;l.applyLayout(H)}if(E&&E.isVisible()){H=E.getSize();z=E.getMargins();H.height=A-(z.top+z.bottom);d=(H.width+z.left+z.right);H.x=t-d+z.left;H.y=p+z.top;r-=d;E.applyLayout(H)}if(F){z=F.getMargins();var k={x:q+z.left,y:p+z.top,width:r-(z.left+z.right),height:A-(z.top+z.bottom)};F.applyLayout(k)}if(j){for(B=0,C=j.length;B<C;B++){j[B].collapse(false)}}if(Ext.isIE9m&&Ext.isStrict){I.repaint()}if(B=I.getStyle("overflow")&&B!="hidden"&&!this.adjustmentPass){var a=this.getLayoutTargetSize();if(a.width!=v.width||a.height!=v.height){this.adjustmentPass=true;this.onLayout(g,I)}}delete this.adjustmentPass},destroy:function(){var b=["north","south","east","west"],a,c;for(a=0;a<b.length;a++){c=this[b[a]];if(c){if(c.destroy){c.destroy()}else{if(c.split){c.split.destroy(true)}}}}Ext.layout.BorderLayout.superclass.destroy.call(this)}});Ext.layout.BorderLayout.Region=function(b,a,c){Ext.apply(this,a);this.layout=b;this.position=c;this.state={};if(typeof this.margins=="string"){this.margins=this.layout.parseMargins(this.margins)}this.margins=Ext.applyIf(this.margins||{},this.defaultMargins);if(this.collapsible){if(typeof this.cmargins=="string"){this.cmargins=this.layout.parseMargins(this.cmargins)}if(this.collapseMode=="mini"&&!this.cmargins){this.cmargins={left:0,top:0,right:0,bottom:0}}else{this.cmargins=Ext.applyIf(this.cmargins||{},c=="north"||c=="south"?this.defaultNSCMargins:this.defaultEWCMargins)}}};Ext.layout.BorderLayout.Region.prototype={collapsible:false,split:false,floatable:true,minWidth:50,minHeight:50,defaultMargins:{left:0,top:0,right:0,bottom:0},defaultNSCMargins:{left:5,top:5,right:5,bottom:5},defaultEWCMargins:{left:5,top:0,right:5,bottom:0},floatingZIndex:100,isCollapsed:false,render:function(b,c){this.panel=c;c.el.enableDisplayMode();this.targetEl=b;this.el=c.el;var a=c.getState,d=this.position;c.getState=function(){return Ext.apply(a.call(c)||{},this.state)}.createDelegate(this);if(d!="center"){c.allowQueuedExpand=false;c.on({beforecollapse:this.beforeCollapse,collapse:this.onCollapse,beforeexpand:this.beforeExpand,expand:this.onExpand,hide:this.onHide,show:this.onShow,scope:this});if(this.collapsible||this.floatable){c.collapseEl="el";c.slideAnchor=this.getSlideAnchor()}if(c.tools&&c.tools.toggle){c.tools.toggle.addClass("x-tool-collapse-"+d);c.tools.toggle.addClassOnOver("x-tool-collapse-"+d+"-over")}}},getCollapsedEl:function(){if(!this.collapsedEl){if(!this.toolTemplate){var b=new Ext.Template('<div class="x-tool x-tool-{id}">&#160;</div>');b.disableFormats=true;b.compile();Ext.layout.BorderLayout.Region.prototype.toolTemplate=b}this.collapsedEl=this.targetEl.createChild({cls:"x-layout-collapsed x-layout-collapsed-"+this.position,id:this.panel.id+"-xcollapsed"});this.collapsedEl.enableDisplayMode("block");if(this.collapseMode=="mini"){this.collapsedEl.addClass("x-layout-cmini-"+this.position);this.miniCollapsedEl=this.collapsedEl.createChild({cls:"x-layout-mini x-layout-mini-"+this.position,html:"&#160;"});this.miniCollapsedEl.addClassOnOver("x-layout-mini-over");this.collapsedEl.addClassOnOver("x-layout-collapsed-over");this.collapsedEl.on("click",this.onExpandClick,this,{stopEvent:true})}else{if(this.collapsible!==false&&!this.hideCollapseTool){var a=this.expandToolEl=this.toolTemplate.append(this.collapsedEl.dom,{id:"expand-"+this.position},true);a.addClassOnOver("x-tool-expand-"+this.position+"-over");a.on("click",this.onExpandClick,this,{stopEvent:true})}if(this.floatable!==false||this.titleCollapse){this.collapsedEl.addClassOnOver("x-layout-collapsed-over");this.collapsedEl.on("click",this[this.floatable?"collapseClick":"onExpandClick"],this)}}}return this.collapsedEl},onExpandClick:function(a){if(this.isSlid){this.panel.expand(false)}else{this.panel.expand()}},onCollapseClick:function(a){this.panel.collapse()},beforeCollapse:function(c,a){this.lastAnim=a;if(this.splitEl){this.splitEl.hide()}this.getCollapsedEl().show();var b=this.panel.getEl();this.originalZIndex=b.getStyle("z-index");b.setStyle("z-index",100);this.isCollapsed=true;this.layout.layout()},onCollapse:function(a){this.panel.el.setStyle("z-index",1);if(this.lastAnim===false||this.panel.animCollapse===false){this.getCollapsedEl().dom.style.visibility="visible"}else{this.getCollapsedEl().slideIn(this.panel.slideAnchor,{duration:0.2})}this.state.collapsed=true;this.panel.saveState()},beforeExpand:function(a){if(this.isSlid){this.afterSlideIn()}var b=this.getCollapsedEl();this.el.show();if(this.position=="east"||this.position=="west"){this.panel.setSize(undefined,b.getHeight())}else{this.panel.setSize(b.getWidth(),undefined)}b.hide();b.dom.style.visibility="hidden";this.panel.el.setStyle("z-index",this.floatingZIndex)},onExpand:function(){this.isCollapsed=false;if(this.splitEl){this.splitEl.show()}this.layout.layout();this.panel.el.setStyle("z-index",this.originalZIndex);this.state.collapsed=false;this.panel.saveState()},collapseClick:function(a){if(this.isSlid){a.stopPropagation();this.slideIn()}else{a.stopPropagation();this.slideOut()}},onHide:function(){if(this.isCollapsed){this.getCollapsedEl().hide()}else{if(this.splitEl){this.splitEl.hide()}}},onShow:function(){if(this.isCollapsed){this.getCollapsedEl().show()}else{if(this.splitEl){this.splitEl.show()}}},isVisible:function(){return !this.panel.hidden},getMargins:function(){return this.isCollapsed&&this.cmargins?this.cmargins:this.margins},getSize:function(){return this.isCollapsed?this.getCollapsedEl().getSize():this.panel.getSize()},setPanel:function(a){this.panel=a},getMinWidth:function(){return this.minWidth},getMinHeight:function(){return this.minHeight},applyLayoutCollapsed:function(a){var b=this.getCollapsedEl();b.setLeftTop(a.x,a.y);b.setSize(a.width,a.height)},applyLayout:function(a){if(this.isCollapsed){this.applyLayoutCollapsed(a)}else{this.panel.setPosition(a.x,a.y);this.panel.setSize(a.width,a.height)}},beforeSlide:function(){this.panel.beforeEffect()},afterSlide:function(){this.panel.afterEffect()},initAutoHide:function(){if(this.autoHide!==false){if(!this.autoHideHd){this.autoHideSlideTask=new Ext.util.DelayedTask(this.slideIn,this);this.autoHideHd={mouseout:function(a){if(!a.within(this.el,true)){this.autoHideSlideTask.delay(500)}},mouseover:function(a){this.autoHideSlideTask.cancel()},scope:this}}this.el.on(this.autoHideHd);this.collapsedEl.on(this.autoHideHd)}},clearAutoHide:function(){if(this.autoHide!==false){this.el.un("mouseout",this.autoHideHd.mouseout);this.el.un("mouseover",this.autoHideHd.mouseover);this.collapsedEl.un("mouseout",this.autoHideHd.mouseout);this.collapsedEl.un("mouseover",this.autoHideHd.mouseover)}},clearMonitor:function(){Ext.getDoc().un("click",this.slideInIf,this)},slideOut:function(){if(this.isSlid||this.el.hasActiveFx()){return}this.isSlid=true;var b=this.panel.tools,c,a;if(b&&b.toggle){b.toggle.hide()}this.el.show();a=this.panel.collapsed;this.panel.collapsed=false;if(this.position=="east"||this.position=="west"){c=this.panel.deferHeight;this.panel.deferHeight=false;this.panel.setSize(undefined,this.collapsedEl.getHeight());this.panel.deferHeight=c}else{this.panel.setSize(this.collapsedEl.getWidth(),undefined)}this.panel.collapsed=a;this.restoreLT=[this.el.dom.style.left,this.el.dom.style.top];this.el.alignTo(this.collapsedEl,this.getCollapseAnchor());this.el.setStyle("z-index",this.floatingZIndex+2);this.panel.el.replaceClass("x-panel-collapsed","x-panel-floating");if(this.animFloat!==false){this.beforeSlide();this.el.slideIn(this.getSlideAnchor(),{callback:function(){this.afterSlide();this.initAutoHide();Ext.getDoc().on("click",this.slideInIf,this)},scope:this,block:true})}else{this.initAutoHide();Ext.getDoc().on("click",this.slideInIf,this)}},afterSlideIn:function(){this.clearAutoHide();this.isSlid=false;this.clearMonitor();this.el.setStyle("z-index","");this.panel.el.replaceClass("x-panel-floating","x-panel-collapsed");this.el.dom.style.left=this.restoreLT[0];this.el.dom.style.top=this.restoreLT[1];var a=this.panel.tools;if(a&&a.toggle){a.toggle.show()}},slideIn:function(a){if(!this.isSlid||this.el.hasActiveFx()){Ext.callback(a);return}this.isSlid=false;if(this.animFloat!==false){this.beforeSlide();this.el.slideOut(this.getSlideAnchor(),{callback:function(){this.el.hide();this.afterSlide();this.afterSlideIn();Ext.callback(a)},scope:this,block:true})}else{this.el.hide();this.afterSlideIn()}},slideInIf:function(a){if(!a.within(this.el)){this.slideIn()}},anchors:{west:"left",east:"right",north:"top",south:"bottom"},sanchors:{west:"l",east:"r",north:"t",south:"b"},canchors:{west:"tl-tr",east:"tr-tl",north:"tl-bl",south:"bl-tl"},getAnchor:function(){return this.anchors[this.position]},getCollapseAnchor:function(){return this.canchors[this.position]},getSlideAnchor:function(){return this.sanchors[this.position]},getAlignAdj:function(){var a=this.cmargins;switch(this.position){case"west":return[0,0];break;case"east":return[0,0];break;case"north":return[0,0];break;case"south":return[0,0];break}},getExpandAdj:function(){var b=this.collapsedEl,a=this.cmargins;switch(this.position){case"west":return[-(a.right+b.getWidth()+a.left),0];break;case"east":return[a.right+b.getWidth()+a.left,0];break;case"north":return[0,-(a.top+a.bottom+b.getHeight())];break;case"south":return[0,a.top+a.bottom+b.getHeight()];break}},destroy:function(){if(this.autoHideSlideTask&&this.autoHideSlideTask.cancel){this.autoHideSlideTask.cancel()}Ext.destroyMembers(this,"miniCollapsedEl","collapsedEl","expandToolEl")}};Ext.layout.BorderLayout.SplitRegion=function(b,a,c){Ext.layout.BorderLayout.SplitRegion.superclass.constructor.call(this,b,a,c);this.applyLayout=this.applyFns[c]};Ext.extend(Ext.layout.BorderLayout.SplitRegion,Ext.layout.BorderLayout.Region,{splitTip:"Drag to resize.",collapsibleSplitTip:"Drag to resize. Double click to hide.",useSplitTips:false,splitSettings:{north:{orientation:Ext.SplitBar.VERTICAL,placement:Ext.SplitBar.TOP,maxFn:"getVMaxSize",minProp:"minHeight",maxProp:"maxHeight"},south:{orientation:Ext.SplitBar.VERTICAL,placement:Ext.SplitBar.BOTTOM,maxFn:"getVMaxSize",minProp:"minHeight",maxProp:"maxHeight"},east:{orientation:Ext.SplitBar.HORIZONTAL,placement:Ext.SplitBar.RIGHT,maxFn:"getHMaxSize",minProp:"minWidth",maxProp:"maxWidth"},west:{orientation:Ext.SplitBar.HORIZONTAL,placement:Ext.SplitBar.LEFT,maxFn:"getHMaxSize",minProp:"minWidth",maxProp:"maxWidth"}},applyFns:{west:function(c){if(this.isCollapsed){return this.applyLayoutCollapsed(c)}var d=this.splitEl.dom,b=d.style;this.panel.setPosition(c.x,c.y);var a=d.offsetWidth;b.left=(c.x+c.width-a)+"px";b.top=(c.y)+"px";b.height=Math.max(0,c.height)+"px";this.panel.setSize(c.width-a,c.height)},east:function(c){if(this.isCollapsed){return this.applyLayoutCollapsed(c)}var d=this.splitEl.dom,b=d.style;var a=d.offsetWidth;this.panel.setPosition(c.x+a,c.y);b.left=(c.x)+"px";b.top=(c.y)+"px";b.height=Math.max(0,c.height)+"px";this.panel.setSize(c.width-a,c.height)},north:function(c){if(this.isCollapsed){return this.applyLayoutCollapsed(c)}var d=this.splitEl.dom,b=d.style;var a=d.offsetHeight;this.panel.setPosition(c.x,c.y);b.left=(c.x)+"px";b.top=(c.y+c.height-a)+"px";b.width=Math.max(0,c.width)+"px";this.panel.setSize(c.width,c.height-a)},south:function(c){if(this.isCollapsed){return this.applyLayoutCollapsed(c)}var d=this.splitEl.dom,b=d.style;var a=d.offsetHeight;this.panel.setPosition(c.x,c.y+a);b.left=(c.x)+"px";b.top=(c.y)+"px";b.width=Math.max(0,c.width)+"px";this.panel.setSize(c.width,c.height-a)}},render:function(a,c){Ext.layout.BorderLayout.SplitRegion.superclass.render.call(this,a,c);var d=this.position;this.splitEl=a.createChild({cls:"x-layout-split x-layout-split-"+d,html:"&#160;",id:this.panel.id+"-xsplit"});if(this.collapseMode=="mini"){this.miniSplitEl=this.splitEl.createChild({cls:"x-layout-mini x-layout-mini-"+d,html:"&#160;"});this.miniSplitEl.addClassOnOver("x-layout-mini-over");this.miniSplitEl.on("click",this.onCollapseClick,this,{stopEvent:true})}var b=this.splitSettings[d];this.split=new Ext.SplitBar(this.splitEl.dom,c.el,b.orientation);this.split.tickSize=this.tickSize;this.split.placement=b.placement;this.split.getMaximumSize=this[b.maxFn].createDelegate(this);this.split.minSize=this.minSize||this[b.minProp];this.split.on("beforeapply",this.onSplitMove,this);this.split.useShim=this.useShim===true;this.maxSize=this.maxSize||this[b.maxProp];if(c.hidden){this.splitEl.hide()}if(this.useSplitTips){this.splitEl.dom.title=this.collapsible?this.collapsibleSplitTip:this.splitTip}if(this.collapsible){this.splitEl.on("dblclick",this.onCollapseClick,this)}},getSize:function(){if(this.isCollapsed){return this.collapsedEl.getSize()}var a=this.panel.getSize();if(this.position=="north"||this.position=="south"){a.height+=this.splitEl.dom.offsetHeight}else{a.width+=this.splitEl.dom.offsetWidth}return a},getHMaxSize:function(){var b=this.maxSize||10000;var a=this.layout.center;return Math.min(b,(this.el.getWidth()+a.el.getWidth())-a.getMinWidth())},getVMaxSize:function(){var b=this.maxSize||10000;var a=this.layout.center;return Math.min(b,(this.el.getHeight()+a.el.getHeight())-a.getMinHeight())},onSplitMove:function(b,a){var c=this.panel.getSize();this.lastSplitSize=a;if(this.position=="north"||this.position=="south"){this.panel.setSize(c.width,a);this.state.height=a}else{this.panel.setSize(a,c.height);this.state.width=a}this.layout.layout();this.panel.saveState();return false},getSplitBar:function(){return this.split},destroy:function(){Ext.destroy(this.miniSplitEl,this.split,this.splitEl);Ext.layout.BorderLayout.SplitRegion.superclass.destroy.call(this)}});Ext.Container.LAYOUTS.border=Ext.layout.BorderLayout;Ext.layout.FormLayout=Ext.extend(Ext.layout.AnchorLayout,{labelSeparator:":",trackLabels:true,type:"form",onRemove:function(d){Ext.layout.FormLayout.superclass.onRemove.call(this,d);if(this.trackLabels){d.un("show",this.onFieldShow,this);d.un("hide",this.onFieldHide,this)}var b=d.getPositionEl(),a=d.getItemCt&&d.getItemCt();if(d.rendered&&a){if(b&&b.dom){b.insertAfter(a)}Ext.destroy(a);Ext.destroyMembers(d,"label","itemCt");if(d.customItemCt){Ext.destroyMembers(d,"getItemCt","customItemCt")}}},setContainer:function(a){Ext.layout.FormLayout.superclass.setContainer.call(this,a);a.labelAlign=a.labelAlign||this.labelAlign;if(a.labelAlign){a.addClass("x-form-label-"+a.labelAlign)}if(a.hideLabels||this.hideLabels){Ext.apply(this,{labelStyle:"display:none",elementStyle:"padding-left:0;",labelAdjust:0})}else{this.labelSeparator=Ext.isDefined(a.labelSeparator)?a.labelSeparator:this.labelSeparator;a.labelWidth=a.labelWidth||this.labelWidth||100;if(Ext.isNumber(a.labelWidth)){var b=a.labelPad||this.labelPad;b=Ext.isNumber(b)?b:5;Ext.apply(this,{labelAdjust:a.labelWidth+b,labelStyle:"width:"+a.labelWidth+"px;",elementStyle:"padding-left:"+(a.labelWidth+b)+"px"})}if(a.labelAlign=="top"){Ext.apply(this,{labelStyle:"width:auto;",labelAdjust:0,elementStyle:"padding-left:0;"})}}},isHide:function(a){return a.hideLabel||this.container.hideLabels},onFieldShow:function(a){a.getItemCt().removeClass("x-hide-"+a.hideMode);if(a.isComposite){a.doLayout()}},onFieldHide:function(a){a.getItemCt().addClass("x-hide-"+a.hideMode)},getLabelStyle:function(e){var b="",c=[this.labelStyle,e];for(var d=0,a=c.length;d<a;++d){if(c[d]){b+=c[d];if(b.substr(-1,1)!=";"){b+=";"}}}return b},renderItem:function(e,a,d){if(e&&(e.isFormField||e.fieldLabel)&&e.inputType!="hidden"){var b=this.getTemplateArgs(e);if(Ext.isNumber(a)){a=d.dom.childNodes[a]||null}if(a){e.itemCt=this.fieldTpl.insertBefore(a,b,true)}else{e.itemCt=this.fieldTpl.append(d,b,true)}if(!e.getItemCt){Ext.apply(e,{getItemCt:function(){return e.itemCt},customItemCt:true})}e.label=e.getItemCt().child("label.x-form-item-label");if(!e.rendered){e.render("x-form-el-"+e.id)}else{if(!this.isValidParent(e,d)){Ext.fly("x-form-el-"+e.id).appendChild(e.getPositionEl())}}if(this.trackLabels){if(e.hidden){this.onFieldHide(e)}e.on({scope:this,show:this.onFieldShow,hide:this.onFieldHide})}this.configureItem(e)}else{Ext.layout.FormLayout.superclass.renderItem.apply(this,arguments)}},getTemplateArgs:function(c){var a=!c.fieldLabel||c.hideLabel,b=(c.itemCls||this.container.itemCls||"")+(c.hideLabel?" x-hide-label":"");if(Ext.isIE9&&Ext.isIEQuirks&&c instanceof Ext.form.TextField){b+=" x-input-wrapper"}return{id:c.id,label:c.fieldLabel,itemCls:b,clearCls:c.clearCls||"x-form-clear-left",labelStyle:this.getLabelStyle(c.labelStyle),elementStyle:this.elementStyle||"",labelSeparator:a?"":(Ext.isDefined(c.labelSeparator)?c.labelSeparator:this.labelSeparator)}},adjustWidthAnchor:function(a,d){if(d.label&&!this.isHide(d)&&(this.container.labelAlign!="top")){var b=Ext.isIE6||Ext.isIEQuirks;return a-this.labelAdjust+(b?-3:0)}return a},adjustHeightAnchor:function(a,b){if(b.label&&!this.isHide(b)&&(this.container.labelAlign=="top")){return a-b.label.getHeight()}return a},isValidParent:function(b,a){return a&&this.container.getEl().contains(b.getPositionEl())}});Ext.Container.LAYOUTS.form=Ext.layout.FormLayout;Ext.layout.AccordionLayout=Ext.extend(Ext.layout.FitLayout,{fill:true,autoWidth:true,titleCollapse:true,hideCollapseTool:false,collapseFirst:false,animate:false,sequence:false,activeOnTop:false,type:"accordion",renderItem:function(a){if(this.animate===false){a.animCollapse=false}a.collapsible=true;if(this.autoWidth){a.autoWidth=true}if(this.titleCollapse){a.titleCollapse=true}if(this.hideCollapseTool){a.hideCollapseTool=true}if(this.collapseFirst!==undefined){a.collapseFirst=this.collapseFirst}if(!this.activeItem&&!a.collapsed){this.setActiveItem(a,true)}else{if(this.activeItem&&this.activeItem!=a){a.collapsed=true}}Ext.layout.AccordionLayout.superclass.renderItem.apply(this,arguments);a.header.addClass("x-accordion-hd");a.on("beforeexpand",this.beforeExpand,this)},onRemove:function(a){Ext.layout.AccordionLayout.superclass.onRemove.call(this,a);if(a.rendered){a.header.removeClass("x-accordion-hd")}a.un("beforeexpand",this.beforeExpand,this)},beforeExpand:function(c,b){var a=this.activeItem;if(a){if(this.sequence){delete this.activeItem;if(!a.collapsed){a.collapse({callback:function(){c.expand(b||true)},scope:this});return false}}else{a.collapse(this.animate)}}this.setActive(c);if(this.activeOnTop){c.el.dom.parentNode.insertBefore(c.el.dom,c.el.dom.parentNode.firstChild)}this.layout()},setItemSize:function(g,e){if(this.fill&&g){var d=0,c,b=this.getRenderedItems(this.container),a=b.length,h;for(c=0;c<a;c++){if((h=b[c])!=g&&!h.hidden){d+=h.header.getHeight()}}e.height-=d;g.setSize(e)}},setActiveItem:function(a){this.setActive(a,true)},setActive:function(c,b){var a=this.activeItem;c=this.container.getComponent(c);if(a!=c){if(c.rendered&&c.collapsed&&b){c.expand()}else{if(a){a.fireEvent("deactivate",a)}this.activeItem=c;c.fireEvent("activate",c)}}}});Ext.Container.LAYOUTS.accordion=Ext.layout.AccordionLayout;Ext.layout.Accordion=Ext.layout.AccordionLayout;Ext.layout.TableLayout=Ext.extend(Ext.layout.ContainerLayout,{monitorResize:false,type:"table",targetCls:"x-table-layout-ct",tableAttrs:null,setContainer:function(a){Ext.layout.TableLayout.superclass.setContainer.call(this,a);this.currentRow=0;this.currentColumn=0;this.cells=[]},onLayout:function(d,g){var e=d.items.items,a=e.length,h,b;if(!this.table){g.addClass("x-table-layout-ct");this.table=g.createChild(Ext.apply({tag:"table",cls:"x-table-layout",cellspacing:0,cn:{tag:"tbody"}},this.tableAttrs),null,true)}this.renderAll(d,g)},getRow:function(a){var b=this.table.tBodies[0].childNodes[a];if(!b){b=document.createElement("tr");this.table.tBodies[0].appendChild(b)}return b},getNextCell:function(j){var a=this.getNextNonSpan(this.currentColumn,this.currentRow);var g=this.currentColumn=a[0],e=this.currentRow=a[1];for(var i=e;i<e+(j.rowspan||1);i++){if(!this.cells[i]){this.cells[i]=[]}for(var d=g;d<g+(j.colspan||1);d++){this.cells[i][d]=true}}var h=document.createElement("td");if(j.cellId){h.id=j.cellId}var b="x-table-layout-cell";if(j.cellCls){b+=" "+j.cellCls}h.className=b;if(j.colspan){h.colSpan=j.colspan}if(j.rowspan){h.rowSpan=j.rowspan}this.getRow(e).appendChild(h);return h},getNextNonSpan:function(a,c){var b=this.columns;while((b&&a>=b)||(this.cells[c]&&this.cells[c][a])){if(b&&a>=b){c++;a=0}else{a++}}return[a,c]},renderItem:function(e,a,d){if(!this.table){this.table=d.createChild(Ext.apply({tag:"table",cls:"x-table-layout",cellspacing:0,cn:{tag:"tbody"}},this.tableAttrs),null,true)}if(e&&!e.rendered){e.render(this.getNextCell(e));this.configureItem(e)}else{if(e&&!this.isValidParent(e,d)){var b=this.getNextCell(e);b.insertBefore(e.getPositionEl().dom,null);e.container=Ext.get(b);this.configureItem(e)}}},isValidParent:function(b,a){return b.getPositionEl().up("table",5).dom.parentNode===(a.dom||a)},destroy:function(){delete this.table;Ext.layout.TableLayout.superclass.destroy.call(this)}});Ext.Container.LAYOUTS.table=Ext.layout.TableLayout;Ext.layout.AbsoluteLayout=Ext.extend(Ext.layout.AnchorLayout,{extraCls:"x-abs-layout-item",type:"absolute",onLayout:function(a,b){b.position();this.paddingLeft=b.getPadding("l");this.paddingTop=b.getPadding("t");Ext.layout.AbsoluteLayout.superclass.onLayout.call(this,a,b)},adjustWidthAnchor:function(b,a){return b?b-a.getPosition(true)[0]+this.paddingLeft:b},adjustHeightAnchor:function(b,a){return b?b-a.getPosition(true)[1]+this.paddingTop:b}});Ext.Container.LAYOUTS.absolute=Ext.layout.AbsoluteLayout;Ext.layout.BoxLayout=Ext.extend(Ext.layout.ContainerLayout,{defaultMargins:{left:0,top:0,right:0,bottom:0},padding:"0",pack:"start",monitorResize:true,type:"box",scrollOffset:0,extraCls:"x-box-item",targetCls:"x-box-layout-ct",innerCls:"x-box-inner",constructor:function(a){Ext.layout.BoxLayout.superclass.constructor.call(this,a);if(Ext.isString(this.defaultMargins)){this.defaultMargins=this.parseMargins(this.defaultMargins)}var d=this.overflowHandler;if(typeof d=="string"){d={type:d}}var c="none";if(d&&d.type!=undefined){c=d.type}var b=Ext.layout.boxOverflow[c];if(b[this.type]){b=b[this.type]}this.overflowHandler=new b(this,d)},onLayout:function(b,h){Ext.layout.BoxLayout.superclass.onLayout.call(this,b,h);var d=this.getLayoutTargetSize(),i=this.getVisibleItems(b),c=this.calculateChildBoxes(i,d),g=c.boxes,j=c.meta;if(d.width>0){var k=this.overflowHandler,a=j.tooNarrow?"handleOverflow":"clearOverflow";var e=k[a](c,d);if(e){if(e.targetSize){d=e.targetSize}if(e.recalculate){i=this.getVisibleItems(b);c=this.calculateChildBoxes(i,d);g=c.boxes}}}this.layoutTargetLastSize=d;this.childBoxCache=c;this.updateInnerCtSize(d,c);this.updateChildBoxes(g);this.handleTargetOverflow(d,b,h)},updateChildBoxes:function(c){for(var b=0,e=c.length;b<e;b++){var d=c[b],a=d.component;if(d.dirtySize){a.setSize(d.width,d.height)}if(isNaN(d.left)||isNaN(d.top)){continue}a.setPosition(d.left,d.top)}},updateInnerCtSize:function(c,h){var i=this.align,g=this.padding,e=c.width,a=c.height;if(this.type=="hbox"){var b=e,d=h.meta.maxHeight+g.top+g.bottom;if(i=="stretch"){d=a}else{if(i=="middle"){d=Math.max(a,d)}}}else{var d=a,b=h.meta.maxWidth+g.left+g.right;if(i=="stretch"){b=e}else{if(i=="center"){b=Math.max(e,b)}}}this.innerCt.setSize(b||undefined,d||undefined)},handleTargetOverflow:function(d,a,c){var e=c.getStyle("overflow");if(e&&e!="hidden"&&!this.adjustmentPass){var b=this.getLayoutTargetSize();if(b.width!=d.width||b.height!=d.height){this.adjustmentPass=true;this.onLayout(a,c)}}delete this.adjustmentPass},isValidParent:function(b,a){return this.innerCt&&b.getPositionEl().dom.parentNode==this.innerCt.dom},getVisibleItems:function(g){var g=g||this.container,e=g.getLayoutTarget(),h=g.items.items,a=h.length,d,j,b=[];for(d=0;d<a;d++){if((j=h[d]).rendered&&this.isValidParent(j,e)&&j.hidden!==true&&j.collapsed!==true&&j.shouldLayout!==false){b.push(j)}}return b},renderAll:function(a,b){if(!this.innerCt){this.innerCt=b.createChild({cls:this.innerCls});this.padding=this.parseMargins(this.padding)}Ext.layout.BoxLayout.superclass.renderAll.call(this,a,this.innerCt)},getLayoutTargetSize:function(){var b=this.container.getLayoutTarget(),a;if(b){a=b.getViewSize();if(Ext.isIE9m&&Ext.isStrict&&a.width==0){a=b.getStyleSize()}a.width-=b.getPadding("lr");a.height-=b.getPadding("tb")}return a},renderItem:function(a){if(Ext.isString(a.margins)){a.margins=this.parseMargins(a.margins)}else{if(!a.margins){a.margins=this.defaultMargins}}Ext.layout.BoxLayout.superclass.renderItem.apply(this,arguments)},destroy:function(){Ext.destroy(this.overflowHandler);Ext.layout.BoxLayout.superclass.destroy.apply(this,arguments)}});Ext.layout.boxOverflow.None=Ext.extend(Object,{constructor:function(b,a){this.layout=b;Ext.apply(this,a||{})},handleOverflow:Ext.emptyFn,clearOverflow:Ext.emptyFn});Ext.layout.boxOverflow.none=Ext.layout.boxOverflow.None;Ext.layout.boxOverflow.Menu=Ext.extend(Ext.layout.boxOverflow.None,{afterCls:"x-strip-right",noItemsMenuText:'<div class="x-toolbar-no-items">(None)</div>',constructor:function(a){Ext.layout.boxOverflow.Menu.superclass.constructor.apply(this,arguments);this.menuItems=[]},createInnerElements:function(){if(!this.afterCt){this.afterCt=this.layout.innerCt.insertSibling({cls:this.afterCls},"before")}},clearOverflow:function(a,g){var e=g.width+(this.afterCt?this.afterCt.getWidth():0),b=this.menuItems;this.hideTrigger();for(var c=0,d=b.length;c<d;c++){b.pop().component.show()}return{targetSize:{height:g.height,width:e}}},showTrigger:function(){this.createMenu();this.menuTrigger.show()},hideTrigger:function(){if(this.menuTrigger!=undefined){this.menuTrigger.hide()}},beforeMenuShow:function(h){var b=this.menuItems,a=b.length,g,e;var c=function(j,i){return j.isXType("buttongroup")&&!(i instanceof Ext.Toolbar.Separator)};this.clearMenu();h.removeAll();for(var d=0;d<a;d++){g=b[d].component;if(e&&(c(g,e)||c(e,g))){h.add("-")}this.addComponentToMenu(h,g);e=g}if(h.items.length<1){h.add(this.noItemsMenuText)}},createMenuConfig:function(c,a){var b=Ext.apply({},c.initialConfig),d=c.toggleGroup;Ext.copyTo(b,c,["iconCls","icon","itemId","disabled","handler","scope","menu"]);Ext.apply(b,{text:c.overflowText||c.text,hideOnClick:a});if(d||c.enableToggle){Ext.apply(b,{group:d,checked:c.pressed,listeners:{checkchange:function(g,e){c.toggle(e)}}})}delete b.ownerCt;delete b.xtype;delete b.id;return b},addComponentToMenu:function(b,a){if(a instanceof Ext.Toolbar.Separator){b.add("-")}else{if(Ext.isFunction(a.isXType)){if(a.isXType("splitbutton")){b.add(this.createMenuConfig(a,true))}else{if(a.isXType("button")){b.add(this.createMenuConfig(a,!a.menu))}else{if(a.isXType("buttongroup")){a.items.each(function(c){this.addComponentToMenu(b,c)},this)}}}}}},clearMenu:function(){var a=this.moreMenu;if(a&&a.items){a.items.each(function(b){delete b.menu})}},createMenu:function(){if(!this.menuTrigger){this.createInnerElements();this.menu=new Ext.menu.Menu({ownerCt:this.layout.container,listeners:{scope:this,beforeshow:this.beforeMenuShow}});this.menuTrigger=new Ext.Button({iconCls:"x-toolbar-more-icon",cls:"x-toolbar-more",menu:this.menu,renderTo:this.afterCt})}},destroy:function(){Ext.destroy(this.menu,this.menuTrigger)}});Ext.layout.boxOverflow.menu=Ext.layout.boxOverflow.Menu;Ext.layout.boxOverflow.HorizontalMenu=Ext.extend(Ext.layout.boxOverflow.Menu,{constructor:function(){Ext.layout.boxOverflow.HorizontalMenu.superclass.constructor.apply(this,arguments);var c=this,b=c.layout,a=b.calculateChildBoxes;b.calculateChildBoxes=function(d,i){var l=a.apply(b,arguments),k=l.meta,e=c.menuItems;var j=0;for(var g=0,h=e.length;g<h;g++){j+=e[g].width}k.minimumWidth+=j;k.tooNarrow=k.minimumWidth>i.width;return l}},handleOverflow:function(d,h){this.showTrigger();var k=h.width-this.afterCt.getWidth(),l=d.boxes,e=0,r=false;for(var o=0,c=l.length;o<c;o++){e+=l[o].width}var a=k-e,g=0;for(var o=0,c=this.menuItems.length;o<c;o++){var n=this.menuItems[o],m=n.component,b=n.width;if(b<a){m.show();a-=b;g++;r=true}else{break}}if(r){this.menuItems=this.menuItems.slice(g)}else{for(var j=l.length-1;j>=0;j--){var q=l[j].component,p=l[j].left+l[j].width;if(p>=k){this.menuItems.unshift({component:q,width:l[j].width});q.hide()}else{break}}}if(this.menuItems.length==0){this.hideTrigger()}return{targetSize:{height:h.height,width:k},recalculate:r}}});Ext.layout.boxOverflow.menu.hbox=Ext.layout.boxOverflow.HorizontalMenu;Ext.layout.boxOverflow.Scroller=Ext.extend(Ext.layout.boxOverflow.None,{animateScroll:true,scrollIncrement:100,wheelIncrement:3,scrollRepeatInterval:400,scrollDuration:0.4,beforeCls:"x-strip-left",afterCls:"x-strip-right",scrollerCls:"x-strip-scroller",beforeScrollerCls:"x-strip-scroller-left",afterScrollerCls:"x-strip-scroller-right",createWheelListener:function(){this.layout.innerCt.on({scope:this,mousewheel:function(a){a.stopEvent();this.scrollBy(a.getWheelDelta()*this.wheelIncrement*-1,false)}})},handleOverflow:function(a,b){this.createInnerElements();this.showScrollers()},clearOverflow:function(){this.hideScrollers()},showScrollers:function(){this.createScrollers();this.beforeScroller.show();this.afterScroller.show();this.updateScrollButtons()},hideScrollers:function(){if(this.beforeScroller!=undefined){this.beforeScroller.hide();this.afterScroller.hide()}},createScrollers:function(){if(!this.beforeScroller&&!this.afterScroller){var a=this.beforeCt.createChild({cls:String.format("{0} {1} ",this.scrollerCls,this.beforeScrollerCls)});var b=this.afterCt.createChild({cls:String.format("{0} {1}",this.scrollerCls,this.afterScrollerCls)});a.addClassOnOver(this.beforeScrollerCls+"-hover");b.addClassOnOver(this.afterScrollerCls+"-hover");a.setVisibilityMode(Ext.Element.DISPLAY);b.setVisibilityMode(Ext.Element.DISPLAY);this.beforeRepeater=new Ext.util.ClickRepeater(a,{interval:this.scrollRepeatInterval,handler:this.scrollLeft,scope:this});this.afterRepeater=new Ext.util.ClickRepeater(b,{interval:this.scrollRepeatInterval,handler:this.scrollRight,scope:this});this.beforeScroller=a;this.afterScroller=b}},destroy:function(){Ext.destroy(this.beforeScroller,this.afterScroller,this.beforeRepeater,this.afterRepeater,this.beforeCt,this.afterCt)},scrollBy:function(b,a){this.scrollTo(this.getScrollPosition()+b,a)},getItem:function(a){if(Ext.isString(a)){a=Ext.getCmp(a)}else{if(Ext.isNumber(a)){a=this.items[a]}}return a},getScrollAnim:function(){return{duration:this.scrollDuration,callback:this.updateScrollButtons,scope:this}},updateScrollButtons:function(){if(this.beforeScroller==undefined||this.afterScroller==undefined){return}var d=this.atExtremeBefore()?"addClass":"removeClass",c=this.atExtremeAfter()?"addClass":"removeClass",a=this.beforeScrollerCls+"-disabled",b=this.afterScrollerCls+"-disabled";this.beforeScroller[d](a);this.afterScroller[c](b);this.scrolling=false},atExtremeBefore:function(){return this.getScrollPosition()===0},scrollLeft:function(a){this.scrollBy(-this.scrollIncrement,a)},scrollRight:function(a){this.scrollBy(this.scrollIncrement,a)},scrollToItem:function(d,b){d=this.getItem(d);if(d!=undefined){var a=this.getItemVisibility(d);if(!a.fullyVisible){var c=d.getBox(true,true),e=c.x;if(a.hiddenRight){e-=(this.layout.innerCt.getWidth()-c.width)}this.scrollTo(e,b)}}},getItemVisibility:function(e){var d=this.getItem(e).getBox(true,true),a=d.x,c=d.x+d.width,g=this.getScrollPosition(),b=this.layout.innerCt.getWidth()+g;return{hiddenLeft:a<g,hiddenRight:c>b,fullyVisible:a>g&&c<b}}});Ext.layout.boxOverflow.scroller=Ext.layout.boxOverflow.Scroller;Ext.layout.boxOverflow.VerticalScroller=Ext.extend(Ext.layout.boxOverflow.Scroller,{scrollIncrement:75,wheelIncrement:2,handleOverflow:function(a,b){Ext.layout.boxOverflow.VerticalScroller.superclass.handleOverflow.apply(this,arguments);return{targetSize:{height:b.height-(this.beforeCt.getHeight()+this.afterCt.getHeight()),width:b.width}}},createInnerElements:function(){var a=this.layout.innerCt;if(!this.beforeCt){this.beforeCt=a.insertSibling({cls:this.beforeCls},"before");this.afterCt=a.insertSibling({cls:this.afterCls},"after");this.createWheelListener()}},scrollTo:function(a,b){var d=this.getScrollPosition(),c=a.constrain(0,this.getMaxScrollBottom());if(c!=d&&!this.scrolling){if(b==undefined){b=this.animateScroll}this.layout.innerCt.scrollTo("top",c,b?this.getScrollAnim():false);if(b){this.scrolling=true}else{this.scrolling=false;this.updateScrollButtons()}}},getScrollPosition:function(){return parseInt(this.layout.innerCt.dom.scrollTop,10)||0},getMaxScrollBottom:function(){return this.layout.innerCt.dom.scrollHeight-this.layout.innerCt.getHeight()},atExtremeAfter:function(){return this.getScrollPosition()>=this.getMaxScrollBottom()}});Ext.layout.boxOverflow.scroller.vbox=Ext.layout.boxOverflow.VerticalScroller;Ext.layout.boxOverflow.HorizontalScroller=Ext.extend(Ext.layout.boxOverflow.Scroller,{handleOverflow:function(a,b){Ext.layout.boxOverflow.HorizontalScroller.superclass.handleOverflow.apply(this,arguments);return{targetSize:{height:b.height,width:b.width-(this.beforeCt.getWidth()+this.afterCt.getWidth())}}},createInnerElements:function(){var a=this.layout.innerCt;if(!this.beforeCt){this.afterCt=a.insertSibling({cls:this.afterCls},"before");this.beforeCt=a.insertSibling({cls:this.beforeCls},"before");this.createWheelListener()}},scrollTo:function(a,b){var d=this.getScrollPosition(),c=a.constrain(0,this.getMaxScrollRight());if(c!=d&&!this.scrolling){if(b==undefined){b=this.animateScroll}this.layout.innerCt.scrollTo("left",c,b?this.getScrollAnim():false);if(b){this.scrolling=true}else{this.scrolling=false;this.updateScrollButtons()}}},getScrollPosition:function(){return parseInt(this.layout.innerCt.dom.scrollLeft,10)||0},getMaxScrollRight:function(){return this.layout.innerCt.dom.scrollWidth-this.layout.innerCt.getWidth()},atExtremeAfter:function(){return this.getScrollPosition()>=this.getMaxScrollRight()}});Ext.layout.boxOverflow.scroller.hbox=Ext.layout.boxOverflow.HorizontalScroller;Ext.layout.HBoxLayout=Ext.extend(Ext.layout.BoxLayout,{align:"top",type:"hbox",calculateChildBoxes:function(r,b){var F=r.length,R=this.padding,D=R.top,U=R.left,y=D+R.bottom,O=U+R.right,a=b.width-this.scrollOffset,e=b.height,o=Math.max(0,e-y),P=this.pack=="start",W=this.pack=="center",A=this.pack=="end",L=0,Q=0,T=0,l=0,X=0,H=[],k,J,M,V,w,j,S,I,c,x,q,N;for(S=0;S<F;S++){k=r[S];M=k.height;J=k.width;j=!k.hasLayout&&typeof k.doLayout=="function";if(typeof J!="number"){if(k.flex&&!J){T+=k.flex}else{if(!J&&j){k.doLayout()}V=k.getSize();J=V.width;M=V.height}}w=k.margins;x=w.left+w.right;L+=x+(J||0);l+=x+(k.flex?k.minWidth||0:J);X+=x+(k.minWidth||J||0);if(typeof M!="number"){if(j){k.doLayout()}M=k.getHeight()}Q=Math.max(Q,M+w.top+w.bottom);H.push({component:k,height:M||undefined,width:J||undefined})}var K=l-a,p=X>a;var n=Math.max(0,a-L-O);if(p){for(S=0;S<F;S++){H[S].width=r[S].minWidth||r[S].width||H[S].width}}else{if(K>0){var C=[];for(var E=0,v=F;E<v;E++){var B=r[E],t=B.minWidth||0;if(B.flex){H[E].width=t}else{C.push({minWidth:t,available:H[E].width-t,index:E})}}C.sort(function(Y,i){return Y.available>i.available?1:-1});for(var S=0,v=C.length;S<v;S++){var G=C[S].index;if(G==undefined){continue}var B=r[G],m=H[G],u=m.width,t=B.minWidth,d=Math.max(t,u-Math.ceil(K/(v-S))),g=u-d;H[G].width=d;K-=g}}else{var h=n,s=T;for(S=0;S<F;S++){k=r[S];I=H[S];w=k.margins;q=w.top+w.bottom;if(P&&k.flex&&!k.width){c=Math.ceil((k.flex/s)*h);h-=c;s-=k.flex;I.width=c;I.dirtySize=true}}}}if(W){U+=n/2}else{if(A){U+=n}}for(S=0;S<F;S++){k=r[S];I=H[S];w=k.margins;U+=w.left;q=w.top+w.bottom;I.left=U;I.top=D+w.top;switch(this.align){case"stretch":N=o-q;I.height=N.constrain(k.minHeight||0,k.maxHeight||1000000);I.dirtySize=true;break;case"stretchmax":N=Q-q;I.height=N.constrain(k.minHeight||0,k.maxHeight||1000000);I.dirtySize=true;break;case"middle":var z=o-I.height-q;if(z>0){I.top=D+q+(z/2)}}U+=I.width+w.right}return{boxes:H,meta:{maxHeight:Q,nonFlexWidth:L,desiredWidth:l,minimumWidth:X,shortfall:l-a,tooNarrow:p}}}});Ext.Container.LAYOUTS.hbox=Ext.layout.HBoxLayout;Ext.layout.VBoxLayout=Ext.extend(Ext.layout.BoxLayout,{align:"left",type:"vbox",calculateChildBoxes:function(o,b){var E=o.length,R=this.padding,C=R.top,V=R.left,x=C+R.bottom,O=V+R.right,a=b.width-this.scrollOffset,c=b.height,K=Math.max(0,a-O),P=this.pack=="start",X=this.pack=="center",z=this.pack=="end",k=0,u=0,U=0,L=0,m=0,G=[],h,I,N,W,t,g,T,H,S,w,n,d,r;for(T=0;T<E;T++){h=o[T];N=h.height;I=h.width;g=!h.hasLayout&&typeof h.doLayout=="function";if(typeof N!="number"){if(h.flex&&!N){U+=h.flex}else{if(!N&&g){h.doLayout()}W=h.getSize();I=W.width;N=W.height}}t=h.margins;n=t.top+t.bottom;k+=n+(N||0);L+=n+(h.flex?h.minHeight||0:N);m+=n+(h.minHeight||N||0);if(typeof I!="number"){if(g){h.doLayout()}I=h.getWidth()}u=Math.max(u,I+t.left+t.right);G.push({component:h,height:N||undefined,width:I||undefined})}var M=L-c,l=m>c;var q=Math.max(0,(c-k-x));if(l){for(T=0,r=E;T<r;T++){G[T].height=o[T].minHeight||o[T].height||G[T].height}}else{if(M>0){var J=[];for(var D=0,r=E;D<r;D++){var A=o[D],s=A.minHeight||0;if(A.flex){G[D].height=s}else{J.push({minHeight:s,available:G[D].height-s,index:D})}}J.sort(function(Y,i){return Y.available>i.available?1:-1});for(var T=0,r=J.length;T<r;T++){var F=J[T].index;if(F==undefined){continue}var A=o[F],j=G[F],v=j.height,s=A.minHeight,B=Math.max(s,v-Math.ceil(M/(r-T))),e=v-B;G[F].height=B;M-=e}}else{var Q=q,p=U;for(T=0;T<E;T++){h=o[T];H=G[T];t=h.margins;w=t.left+t.right;if(P&&h.flex&&!h.height){S=Math.ceil((h.flex/p)*Q);Q-=S;p-=h.flex;H.height=S;H.dirtySize=true}}}}if(X){C+=q/2}else{if(z){C+=q}}for(T=0;T<E;T++){h=o[T];H=G[T];t=h.margins;C+=t.top;w=t.left+t.right;H.left=V+t.left;H.top=C;switch(this.align){case"stretch":d=K-w;H.width=d.constrain(h.minWidth||0,h.maxWidth||1000000);H.dirtySize=true;break;case"stretchmax":d=u-w;H.width=d.constrain(h.minWidth||0,h.maxWidth||1000000);H.dirtySize=true;break;case"center":var y=K-H.width-w;if(y>0){H.left=V+w+(y/2)}}C+=H.height+t.bottom}return{boxes:G,meta:{maxWidth:u,nonFlexHeight:k,desiredHeight:L,minimumHeight:m,shortfall:L-c,tooNarrow:l}}}});Ext.Container.LAYOUTS.vbox=Ext.layout.VBoxLayout;Ext.layout.ToolbarLayout=Ext.extend(Ext.layout.ContainerLayout,{monitorResize:true,type:"toolbar",triggerWidth:18,noItemsMenuText:'<div class="x-toolbar-no-items">(None)</div>',lastOverflow:false,tableHTML:['<table cellspacing="0" class="x-toolbar-ct">',"<tbody>","<tr>",'<td class="x-toolbar-left" align="{0}">','<table cellspacing="0">',"<tbody>",'<tr class="x-toolbar-left-row"></tr>',"</tbody>","</table>","</td>",'<td class="x-toolbar-right" align="right">','<table cellspacing="0" class="x-toolbar-right-ct">',"<tbody>","<tr>","<td>",'<table cellspacing="0">',"<tbody>",'<tr class="x-toolbar-right-row"></tr>',"</tbody>","</table>","</td>","<td>",'<table cellspacing="0">',"<tbody>",'<tr class="x-toolbar-extras-row"></tr>',"</tbody>","</table>","</td>","</tr>","</tbody>","</table>","</td>","</tr>","</tbody>","</table>"].join(""),onLayout:function(e,j){if(!this.leftTr){var h=e.buttonAlign=="center"?"center":"left";j.addClass("x-toolbar-layout-ct");j.insertHtml("beforeEnd",String.format(this.tableHTML,h));this.leftTr=j.child("tr.x-toolbar-left-row",true);this.rightTr=j.child("tr.x-toolbar-right-row",true);this.extrasTr=j.child("tr.x-toolbar-extras-row",true);if(this.hiddenItem==undefined){this.hiddenItems=[]}}var k=e.buttonAlign=="right"?this.rightTr:this.leftTr,l=e.items.items,d=0;for(var b=0,g=l.length,m;b<g;b++,d++){m=l[b];if(m.isFill){k=this.rightTr;d=-1}else{if(!m.rendered){m.render(this.insertCell(m,k,d));this.configureItem(m)}else{if(!m.xtbHidden&&!this.isValidParent(m,k.childNodes[d])){var a=this.insertCell(m,k,d);a.appendChild(m.getPositionEl().dom);m.container=Ext.get(a)}}}}this.cleanup(this.leftTr);this.cleanup(this.rightTr);this.cleanup(this.extrasTr);this.fitToSize(j)},cleanup:function(b){var e=b.childNodes,a,d;for(a=e.length-1;a>=0&&(d=e[a]);a--){if(!d.firstChild){b.removeChild(d)}}},insertCell:function(e,b,a){var d=document.createElement("td");d.className="x-toolbar-cell";b.insertBefore(d,b.childNodes[a]||null);return d},hideItem:function(a){this.hiddenItems.push(a);a.xtbHidden=true;a.xtbWidth=a.getPositionEl().dom.parentNode.offsetWidth;a.hide()},unhideItem:function(a){a.show();a.xtbHidden=false;this.hiddenItems.remove(a)},getItemWidth:function(a){return a.hidden?(a.xtbWidth||0):a.getPositionEl().dom.parentNode.offsetWidth},fitToSize:function(k){if(this.container.enableOverflow===false){return}var b=k.dom.clientWidth,j=k.dom.firstChild.offsetWidth,m=b-this.triggerWidth,a=this.lastWidth||0,c=this.hiddenItems,e=c.length!=0,n=b>=a;this.lastWidth=b;if(j>b||(e&&n)){var l=this.container.items.items,h=l.length,d=0,o;for(var g=0;g<h;g++){o=l[g];if(!o.isFill){d+=this.getItemWidth(o);if(d>m){if(!(o.hidden||o.xtbHidden)){this.hideItem(o)}}else{if(o.xtbHidden){this.unhideItem(o)}}}}}e=c.length!=0;if(e){this.initMore();if(!this.lastOverflow){this.container.fireEvent("overflowchange",this.container,true);this.lastOverflow=true}}else{if(this.more){this.clearMenu();this.more.destroy();delete this.more;if(this.lastOverflow){this.container.fireEvent("overflowchange",this.container,false);this.lastOverflow=false}}}},createMenuConfig:function(c,a){var b=Ext.apply({},c.initialConfig),d=c.toggleGroup;Ext.copyTo(b,c,["iconCls","icon","itemId","disabled","handler","scope","menu"]);Ext.apply(b,{text:c.overflowText||c.text,hideOnClick:a});if(d||c.enableToggle){Ext.apply(b,{group:d,checked:c.pressed,listeners:{checkchange:function(g,e){c.toggle(e)}}})}delete b.ownerCt;delete b.xtype;delete b.id;return b},addComponentToMenu:function(b,a){if(a instanceof Ext.Toolbar.Separator){b.add("-")}else{if(Ext.isFunction(a.isXType)){if(a.isXType("splitbutton")){b.add(this.createMenuConfig(a,true))}else{if(a.isXType("button")){b.add(this.createMenuConfig(a,!a.menu))}else{if(a.isXType("buttongroup")){a.items.each(function(c){this.addComponentToMenu(b,c)},this)}}}}}},clearMenu:function(){var a=this.moreMenu;if(a&&a.items){a.items.each(function(b){delete b.menu})}},beforeMoreShow:function(h){var b=this.container.items.items,a=b.length,g,e;var c=function(j,i){return j.isXType("buttongroup")&&!(i instanceof Ext.Toolbar.Separator)};this.clearMenu();h.removeAll();for(var d=0;d<a;d++){g=b[d];if(g.xtbHidden){if(e&&(c(g,e)||c(e,g))){h.add("-")}this.addComponentToMenu(h,g);e=g}}if(h.items.length<1){h.add(this.noItemsMenuText)}},initMore:function(){if(!this.more){this.moreMenu=new Ext.menu.Menu({ownerCt:this.container,listeners:{beforeshow:this.beforeMoreShow,scope:this}});this.more=new Ext.Button({iconCls:"x-toolbar-more-icon",cls:"x-toolbar-more",menu:this.moreMenu,ownerCt:this.container});var a=this.insertCell(this.more,this.extrasTr,100);this.more.render(a)}},destroy:function(){Ext.destroy(this.more,this.moreMenu);delete this.leftTr;delete this.rightTr;delete this.extrasTr;Ext.layout.ToolbarLayout.superclass.destroy.call(this)}});Ext.Container.LAYOUTS.toolbar=Ext.layout.ToolbarLayout;Ext.layout.MenuLayout=Ext.extend(Ext.layout.ContainerLayout,{monitorResize:true,type:"menu",setContainer:function(a){this.monitorResize=!a.floating;a.on("autosize",this.doAutoSize,this);Ext.layout.MenuLayout.superclass.setContainer.call(this,a)},renderItem:function(g,b,e){if(!this.itemTpl){this.itemTpl=Ext.layout.MenuLayout.prototype.itemTpl=new Ext.XTemplate('<li id="{itemId}" class="{itemCls}">','<tpl if="needsIcon">','<img alt="{altText}" src="{icon}" class="{iconCls}"/>',"</tpl>","</li>")}if(g&&!g.rendered){if(Ext.isNumber(b)){b=e.dom.childNodes[b]}var d=this.getItemArgs(g);g.render(g.positionEl=b?this.itemTpl.insertBefore(b,d,true):this.itemTpl.append(e,d,true));g.positionEl.menuItemId=g.getItemId();if(!d.isMenuItem&&d.needsIcon){g.positionEl.addClass("x-menu-list-item-indent")}this.configureItem(g)}else{if(g&&!this.isValidParent(g,e)){if(Ext.isNumber(b)){b=e.dom.childNodes[b]}e.dom.insertBefore(g.getActionEl().dom,b||null)}}},getItemArgs:function(d){var a=d instanceof Ext.menu.Item,b=!(a||d instanceof Ext.menu.Separator);return{isMenuItem:a,needsIcon:b&&(d.icon||d.iconCls),icon:d.icon||Ext.BLANK_IMAGE_URL,iconCls:"x-menu-item-icon "+(d.iconCls||""),itemId:"x-menu-el-"+d.id,itemCls:"x-menu-list-item ",altText:d.altText||""}},isValidParent:function(b,a){return b.el.up("li.x-menu-list-item",5).dom.parentNode===(a.dom||a)},onLayout:function(a,b){Ext.layout.MenuLayout.superclass.onLayout.call(this,a,b);this.doAutoSize()},doAutoSize:function(){var c=this.container,a=c.width;if(c.floating){if(a){c.setWidth(a)}else{if(Ext.isIE9m){c.setWidth(Ext.isStrict&&(Ext.isIE7||Ext.isIE8||Ext.isIE9)?"auto":c.minWidth);var d=c.getEl(),b=d.dom.offsetWidth;c.setWidth(c.getLayoutTarget().getWidth()+d.getFrameWidth("lr"))}}}}});Ext.Container.LAYOUTS.menu=Ext.layout.MenuLayout;Ext.Viewport=Ext.extend(Ext.Container,{initComponent:function(){Ext.Viewport.superclass.initComponent.call(this);document.getElementsByTagName("html")[0].className+=" x-viewport";this.el=Ext.getBody();this.el.setHeight=Ext.emptyFn;this.el.setWidth=Ext.emptyFn;this.el.setSize=Ext.emptyFn;this.el.dom.scroll="no";this.allowDomMove=false;this.autoWidth=true;this.autoHeight=true;Ext.EventManager.onWindowResize(this.fireResize,this);this.renderTo=this.el},fireResize:function(a,b){this.fireEvent("resize",this,a,b,a,b)}});Ext.reg("viewport",Ext.Viewport);Ext.Panel=Ext.extend(Ext.Container,{baseCls:"x-panel",collapsedCls:"x-panel-collapsed",maskDisabled:true,animCollapse:Ext.enableFx,headerAsText:true,buttonAlign:"right",collapsed:false,collapseFirst:true,minButtonWidth:75,elements:"body",preventBodyReset:false,padding:undefined,resizeEvent:"bodyresize",toolTarget:"header",collapseEl:"bwrap",slideAnchor:"t",disabledClass:"",deferHeight:true,expandDefaults:{duration:0.25},collapseDefaults:{duration:0.25},initComponent:function(){Ext.Panel.superclass.initComponent.call(this);this.addEvents("bodyresize","titlechange","iconchange","collapse","expand","beforecollapse","beforeexpand","beforeclose","close","activate","deactivate");if(this.unstyled){this.baseCls="x-plain"}this.toolbars=[];if(this.tbar){this.elements+=",tbar";this.topToolbar=this.createToolbar(this.tbar);this.tbar=null}if(this.bbar){this.elements+=",bbar";this.bottomToolbar=this.createToolbar(this.bbar);this.bbar=null}if(this.header===true){this.elements+=",header";this.header=null}else{if(this.headerCfg||(this.title&&this.header!==false)){this.elements+=",header"}}if(this.footerCfg||this.footer===true){this.elements+=",footer";this.footer=null}if(this.buttons){this.fbar=this.buttons;this.buttons=null}if(this.fbar){this.createFbar(this.fbar)}if(this.autoLoad){this.on("render",this.doAutoLoad,this,{delay:10})}},createFbar:function(b){var a=this.minButtonWidth;this.elements+=",footer";this.fbar=this.createToolbar(b,{buttonAlign:this.buttonAlign,toolbarCls:"x-panel-fbar",enableOverflow:false,defaults:function(d){return{minWidth:d.minWidth||a}}});this.fbar.items.each(function(d){d.minWidth=d.minWidth||this.minButtonWidth},this);this.buttons=this.fbar.items.items},createToolbar:function(b,c){var a;if(Ext.isArray(b)){b={items:b}}a=b.events?Ext.apply(b,c):this.createComponent(Ext.apply({},b,c),"toolbar");this.toolbars.push(a);return a},createElement:function(a,c){if(this[a]){c.appendChild(this[a].dom);return}if(a==="bwrap"||this.elements.indexOf(a)!=-1){if(this[a+"Cfg"]){this[a]=Ext.fly(c).createChild(this[a+"Cfg"])}else{var b=document.createElement("div");b.className=this[a+"Cls"];this[a]=Ext.get(c.appendChild(b))}if(this[a+"CssClass"]){this[a].addClass(this[a+"CssClass"])}if(this[a+"Style"]){this[a].applyStyles(this[a+"Style"])}}},onRender:function(g,e){Ext.Panel.superclass.onRender.call(this,g,e);this.createClasses();var a=this.el,h=a.dom,k,i;if(this.collapsible&&!this.hideCollapseTool){this.tools=this.tools?this.tools.slice(0):[];this.tools[this.collapseFirst?"unshift":"push"]({id:"toggle",handler:this.toggleCollapse,scope:this})}if(this.tools){i=this.tools;this.elements+=(this.header!==false)?",header":""}this.tools={};a.addClass(this.baseCls);if(h.firstChild){this.header=a.down("."+this.headerCls);this.bwrap=a.down("."+this.bwrapCls);var j=this.bwrap?this.bwrap:a;this.tbar=j.down("."+this.tbarCls);this.body=j.down("."+this.bodyCls);this.bbar=j.down("."+this.bbarCls);this.footer=j.down("."+this.footerCls);this.fromMarkup=true}if(this.preventBodyReset===true){a.addClass("x-panel-reset")}if(this.cls){a.addClass(this.cls)}if(this.buttons){this.elements+=",footer"}if(this.frame){a.insertHtml("afterBegin",String.format(Ext.Element.boxMarkup,this.baseCls));this.createElement("header",h.firstChild.firstChild.firstChild);this.createElement("bwrap",h);k=this.bwrap.dom;var c=h.childNodes[1],b=h.childNodes[2];k.appendChild(c);k.appendChild(b);var l=k.firstChild.firstChild.firstChild;this.createElement("tbar",l);this.createElement("body",l);this.createElement("bbar",l);this.createElement("footer",k.lastChild.firstChild.firstChild);if(!this.footer){this.bwrap.dom.lastChild.className+=" x-panel-nofooter"}this.ft=Ext.get(this.bwrap.dom.lastChild);this.mc=Ext.get(l)}else{this.createElement("header",h);this.createElement("bwrap",h);k=this.bwrap.dom;this.createElement("tbar",k);this.createElement("body",k);this.createElement("bbar",k);this.createElement("footer",k);if(!this.header){this.body.addClass(this.bodyCls+"-noheader");if(this.tbar){this.tbar.addClass(this.tbarCls+"-noheader")}}}if(Ext.isDefined(this.padding)){this.body.setStyle("padding",this.body.addUnits(this.padding))}if(this.border===false){this.el.addClass(this.baseCls+"-noborder");this.body.addClass(this.bodyCls+"-noborder");if(this.header){this.header.addClass(this.headerCls+"-noborder")}if(this.footer){this.footer.addClass(this.footerCls+"-noborder")}if(this.tbar){this.tbar.addClass(this.tbarCls+"-noborder")}if(this.bbar){this.bbar.addClass(this.bbarCls+"-noborder")}}if(this.bodyBorder===false){this.body.addClass(this.bodyCls+"-noborder")}this.bwrap.enableDisplayMode("block");if(this.header){this.header.unselectable();if(this.headerAsText){this.header.dom.innerHTML='<span class="'+this.headerTextCls+'">'+this.header.dom.innerHTML+"</span>";if(this.iconCls){this.setIconClass(this.iconCls)}}}if(this.floating){this.makeFloating(this.floating)}if(this.collapsible&&this.titleCollapse&&this.header){this.mon(this.header,"click",this.toggleCollapse,this);this.header.setStyle("cursor","pointer")}if(i){this.addTool.apply(this,i)}if(this.fbar){this.footer.addClass("x-panel-btns");this.fbar.ownerCt=this;this.fbar.render(this.footer);this.footer.createChild({cls:"x-clear"})}if(this.tbar&&this.topToolbar){this.topToolbar.ownerCt=this;this.topToolbar.render(this.tbar)}if(this.bbar&&this.bottomToolbar){this.bottomToolbar.ownerCt=this;this.bottomToolbar.render(this.bbar)}},setIconClass:function(b){var a=this.iconCls;this.iconCls=b;if(this.rendered&&this.header){if(this.frame){this.header.addClass("x-panel-icon");this.header.replaceClass(a,this.iconCls)}else{var e=this.header,c=e.child("img.x-panel-inline-icon");if(c){Ext.fly(c).replaceClass(a,this.iconCls)}else{var d=e.child("span."+this.headerTextCls);if(d){Ext.DomHelper.insertBefore(d.dom,{tag:"img",alt:"",src:Ext.BLANK_IMAGE_URL,cls:"x-panel-inline-icon "+this.iconCls})}}}}this.fireEvent("iconchange",this,b,a)},makeFloating:function(a){this.floating=true;this.el=new Ext.Layer(Ext.apply({},a,{shadow:Ext.isDefined(this.shadow)?this.shadow:"sides",shadowOffset:this.shadowOffset,constrain:false,shim:this.shim===false?false:undefined}),this.el)},getTopToolbar:function(){return this.topToolbar},getBottomToolbar:function(){return this.bottomToolbar},getFooterToolbar:function(){return this.fbar},addButton:function(a,c,b){if(!this.fbar){this.createFbar([])}if(c){if(Ext.isString(a)){a={text:a}}a=Ext.apply({handler:c,scope:b},a)}return this.fbar.add(a)},addTool:function(){if(!this.rendered){if(!this.tools){this.tools=[]}Ext.each(arguments,function(a){this.tools.push(a)},this);return}if(!this[this.toolTarget]){return}if(!this.toolTemplate){var h=new Ext.Template('<div class="x-tool x-tool-{id}">&#160;</div>');h.disableFormats=true;h.compile();Ext.Panel.prototype.toolTemplate=h}for(var g=0,d=arguments,c=d.length;g<c;g++){var b=d[g];if(!this.tools[b.id]){var j="x-tool-"+b.id+"-over";var e=this.toolTemplate.insertFirst(this[this.toolTarget],b,true);this.tools[b.id]=e;e.enableDisplayMode("block");this.mon(e,"click",this.createToolHandler(e,b,j,this));if(b.on){this.mon(e,b.on)}if(b.hidden){e.hide()}if(b.qtip){if(Ext.isObject(b.qtip)){Ext.QuickTips.register(Ext.apply({target:e.id},b.qtip))}else{e.dom.qtip=b.qtip}}e.addClassOnOver(j)}}},onLayout:function(b,a){Ext.Panel.superclass.onLayout.apply(this,arguments);if(this.hasLayout&&this.toolbars.length>0){Ext.each(this.toolbars,function(c){c.doLayout(undefined,a)});this.syncHeight()}},syncHeight:function(){var b=this.toolbarHeight,c=this.body,a=this.lastSize.height,d;if(this.autoHeight||!Ext.isDefined(a)||a=="auto"){return}if(b!=this.getToolbarHeight()){b=Math.max(0,a-this.getFrameHeight());c.setHeight(b);d=c.getSize();this.toolbarHeight=this.getToolbarHeight();this.onBodyResize(d.width,d.height)}},onShow:function(){if(this.floating){return this.el.show()}Ext.Panel.superclass.onShow.call(this)},onHide:function(){if(this.floating){return this.el.hide()}Ext.Panel.superclass.onHide.call(this)},createToolHandler:function(c,a,d,b){return function(g){c.removeClass(d);if(a.stopEvent!==false){g.stopEvent()}if(a.handler){a.handler.call(a.scope||c,g,c,b,a)}}},afterRender:function(){if(this.floating&&!this.hidden){this.el.show()}if(this.title){this.setTitle(this.title)}Ext.Panel.superclass.afterRender.call(this);if(this.collapsed){this.collapsed=false;this.collapse(false)}this.initEvents()},getKeyMap:function(){if(!this.keyMap){this.keyMap=new Ext.KeyMap(this.el,this.keys)}return this.keyMap},initEvents:function(){if(this.keys){this.getKeyMap()}if(this.draggable){this.initDraggable()}if(this.toolbars.length>0){Ext.each(this.toolbars,function(a){a.doLayout();a.on({scope:this,afterlayout:this.syncHeight,remove:this.syncHeight})},this);this.syncHeight()}},initDraggable:function(){this.dd=new Ext.Panel.DD(this,Ext.isBoolean(this.draggable)?null:this.draggable)},beforeEffect:function(a){if(this.floating){this.el.beforeAction()}if(a!==false){this.el.addClass("x-panel-animated")}},afterEffect:function(a){this.syncShadow();this.el.removeClass("x-panel-animated")},createEffect:function(c,b,d){var e={scope:d,block:true};if(c===true){e.callback=b;return e}else{if(!c.callback){e.callback=b}else{e.callback=function(){b.call(d);Ext.callback(c.callback,c.scope)}}}return Ext.applyIf(e,c)},collapse:function(b){if(this.collapsed||this.el.hasFxBlock()||this.fireEvent("beforecollapse",this,b)===false){return}var a=b===true||(b!==false&&this.animCollapse);this.beforeEffect(a);this.onCollapse(a,b);return this},onCollapse:function(a,b){if(a){this[this.collapseEl].slideOut(this.slideAnchor,Ext.apply(this.createEffect(b||true,this.afterCollapse,this),this.collapseDefaults))}else{this[this.collapseEl].hide(this.hideMode);this.afterCollapse(false)}},afterCollapse:function(a){this.collapsed=true;this.el.addClass(this.collapsedCls);if(a!==false){this[this.collapseEl].hide(this.hideMode)}this.afterEffect(a);this.cascade(function(b){if(b.lastSize){b.lastSize={width:undefined,height:undefined}}});this.fireEvent("collapse",this)},expand:function(b){if(!this.collapsed||this.el.hasFxBlock()||this.fireEvent("beforeexpand",this,b)===false){return}var a=b===true||(b!==false&&this.animCollapse);this.el.removeClass(this.collapsedCls);this.beforeEffect(a);this.onExpand(a,b);return this},onExpand:function(a,b){if(a){this[this.collapseEl].slideIn(this.slideAnchor,Ext.apply(this.createEffect(b||true,this.afterExpand,this),this.expandDefaults))}else{this[this.collapseEl].show(this.hideMode);this.afterExpand(false)}},afterExpand:function(a){this.collapsed=false;if(a!==false){this[this.collapseEl].show(this.hideMode)}this.afterEffect(a);if(this.deferLayout){delete this.deferLayout;this.doLayout(true)}this.fireEvent("expand",this)},toggleCollapse:function(a){this[this.collapsed?"expand":"collapse"](a);return this},onDisable:function(){if(this.rendered&&this.maskDisabled){this.el.mask()}Ext.Panel.superclass.onDisable.call(this)},onEnable:function(){if(this.rendered&&this.maskDisabled){this.el.unmask()}Ext.Panel.superclass.onEnable.call(this)},onResize:function(g,d,c,e){var a=g,b=d;if(Ext.isDefined(a)||Ext.isDefined(b)){if(!this.collapsed){if(Ext.isNumber(a)){this.body.setWidth(a=this.adjustBodyWidth(a-this.getFrameWidth()))}else{if(a=="auto"){a=this.body.setWidth("auto").dom.offsetWidth}else{a=this.body.dom.offsetWidth}}if(this.tbar){this.tbar.setWidth(a);if(this.topToolbar){this.topToolbar.setSize(a)}}if(this.bbar){this.bbar.setWidth(a);if(this.bottomToolbar){this.bottomToolbar.setSize(a);if(Ext.isIE9m){this.bbar.setStyle("position","static");this.bbar.setStyle("position","")}}}if(this.footer){this.footer.setWidth(a);if(this.fbar){this.fbar.setSize(Ext.isIE9m?(a-this.footer.getFrameWidth("lr")):"auto")}}if(Ext.isNumber(b)){b=Math.max(0,b-this.getFrameHeight());this.body.setHeight(b)}else{if(b=="auto"){this.body.setHeight(b)}}if(this.disabled&&this.el._mask){this.el._mask.setSize(this.el.dom.clientWidth,this.el.getHeight())}}else{this.queuedBodySize={width:a,height:b};if(!this.queuedExpand&&this.allowQueuedExpand!==false){this.queuedExpand=true;this.on("expand",function(){delete this.queuedExpand;this.onResize(this.queuedBodySize.width,this.queuedBodySize.height)},this,{single:true})}}this.onBodyResize(a,b)}this.syncShadow();Ext.Panel.superclass.onResize.call(this,g,d,c,e)},onBodyResize:function(a,b){this.fireEvent("bodyresize",this,a,b)},getToolbarHeight:function(){var a=0;if(this.rendered){Ext.each(this.toolbars,function(b){a+=b.getHeight()},this)}return a},adjustBodyHeight:function(a){return a},adjustBodyWidth:function(a){return a},onPosition:function(){this.syncShadow()},getFrameWidth:function(){var b=this.el.getFrameWidth("lr")+this.bwrap.getFrameWidth("lr");if(this.frame){var a=this.bwrap.dom.firstChild;b+=(Ext.fly(a).getFrameWidth("l")+Ext.fly(a.firstChild).getFrameWidth("r"));b+=this.mc.getFrameWidth("lr")}return b},getFrameHeight:function(){var a=this.el.getFrameWidth("tb")+this.bwrap.getFrameWidth("tb");a+=(this.tbar?this.tbar.getHeight():0)+(this.bbar?this.bbar.getHeight():0);if(this.frame){a+=this.el.dom.firstChild.offsetHeight+this.ft.dom.offsetHeight+this.mc.getFrameWidth("tb")}else{a+=(this.header?this.header.getHeight():0)+(this.footer?this.footer.getHeight():0)}return a},getInnerWidth:function(){return this.getSize().width-this.getFrameWidth()},getInnerHeight:function(){return this.body.getHeight()},syncShadow:function(){if(this.floating){this.el.sync(true)}},getLayoutTarget:function(){return this.body},getContentTarget:function(){return this.body},setTitle:function(b,a){this.title=b;if(this.header&&this.headerAsText){this.header.child("span").update(b)}if(a){this.setIconClass(a)}this.fireEvent("titlechange",this,b);return this},getUpdater:function(){return this.body.getUpdater()},load:function(){var a=this.body.getUpdater();a.update.apply(a,arguments);return this},beforeDestroy:function(){Ext.Panel.superclass.beforeDestroy.call(this);if(this.header){this.header.removeAllListeners()}if(this.tools){for(var a in this.tools){Ext.destroy(this.tools[a])}}if(this.toolbars.length>0){Ext.each(this.toolbars,function(b){b.un("afterlayout",this.syncHeight,this);b.un("remove",this.syncHeight,this)},this)}if(Ext.isArray(this.buttons)){while(this.buttons.length){Ext.destroy(this.buttons[0])}}if(this.rendered){Ext.destroy(this.ft,this.header,this.footer,this.tbar,this.bbar,this.body,this.mc,this.bwrap,this.dd);if(this.fbar){Ext.destroy(this.fbar,this.fbar.el)}}Ext.destroy(this.toolbars)},createClasses:function(){this.headerCls=this.baseCls+"-header";this.headerTextCls=this.baseCls+"-header-text";this.bwrapCls=this.baseCls+"-bwrap";this.tbarCls=this.baseCls+"-tbar";this.bodyCls=this.baseCls+"-body";this.bbarCls=this.baseCls+"-bbar";this.footerCls=this.baseCls+"-footer"},createGhost:function(a,e,b){var d=document.createElement("div");d.className="x-panel-ghost "+(a?a:"");if(this.header){d.appendChild(this.el.dom.firstChild.cloneNode(true))}Ext.fly(d.appendChild(document.createElement("ul"))).setHeight(this.bwrap.getHeight());d.style.width=this.el.dom.offsetWidth+"px";if(!b){this.container.dom.appendChild(d)}else{Ext.getDom(b).appendChild(d)}if(e!==false&&this.el.useShim!==false){var c=new Ext.Layer({shadow:false,useDisplay:true,constrain:false},d);c.show();return c}else{return new Ext.Element(d)}},doAutoLoad:function(){var a=this.body.getUpdater();if(this.renderer){a.setRenderer(this.renderer)}a.update(Ext.isObject(this.autoLoad)?this.autoLoad:{url:this.autoLoad})},getTool:function(a){return this.tools[a]}});Ext.reg("panel",Ext.Panel);Ext.Editor=function(b,a){if(b.field){this.field=Ext.create(b.field,"textfield");a=Ext.apply({},b);delete a.field}else{this.field=b}Ext.Editor.superclass.constructor.call(this,a)};Ext.extend(Ext.Editor,Ext.Component,{allowBlur:true,value:"",alignment:"c-c?",offsets:[0,0],shadow:"frame",constrain:false,swallowKeys:true,completeOnEnter:true,cancelOnEsc:true,updateEl:false,initComponent:function(){Ext.Editor.superclass.initComponent.call(this);this.addEvents("beforestartedit","startedit","beforecomplete","complete","canceledit","specialkey")},onRender:function(b,a){this.el=new Ext.Layer({shadow:this.shadow,cls:"x-editor",parentEl:b,shim:this.shim,shadowOffset:this.shadowOffset||4,id:this.id,constrain:this.constrain});if(this.zIndex){this.el.setZIndex(this.zIndex)}this.el.setStyle("overflow",Ext.isGecko?"auto":"hidden");if(this.field.msgTarget!="title"){this.field.msgTarget="qtip"}this.field.inEditor=true;this.mon(this.field,{scope:this,blur:this.onBlur,specialkey:this.onSpecialKey});if(this.field.grow){this.mon(this.field,"autosize",this.el.sync,this.el,{delay:1})}this.field.render(this.el).show();this.field.getEl().dom.name="";if(this.swallowKeys){this.field.el.swallowEvent(["keypress","keydown"])}},onSpecialKey:function(g,d){var b=d.getKey(),a=this.completeOnEnter&&b==d.ENTER,c=this.cancelOnEsc&&b==d.ESC;if(a||c){d.stopEvent();if(a){this.completeEdit()}else{this.cancelEdit()}if(g.triggerBlur){g.triggerBlur()}}this.fireEvent("specialkey",g,d)},startEdit:function(b,c){if(this.editing){this.completeEdit()}this.boundEl=Ext.get(b);var a=c!==undefined?c:this.boundEl.dom.innerHTML;if(!this.rendered){this.render(this.parentEl||document.body)}if(this.fireEvent("beforestartedit",this,this.boundEl,a)!==false){this.startValue=a;this.field.reset();this.field.setValue(a);this.realign(true);this.editing=true;this.show()}},doAutoSize:function(){if(this.autoSize){var b=this.boundEl.getSize(),a=this.field.getSize();switch(this.autoSize){case"width":this.setSize(b.width,a.height);break;case"height":this.setSize(a.width,b.height);break;case"none":this.setSize(a.width,a.height);break;default:this.setSize(b.width,b.height)}}},setSize:function(a,b){delete this.field.lastSize;this.field.setSize(a,b);if(this.el){if(Ext.isGecko2||Ext.isOpera||(Ext.isIE7&&Ext.isStrict)){this.el.setSize(a,b)}this.el.sync()}},realign:function(a){if(a===true){this.doAutoSize()}this.el.alignTo(this.boundEl,this.alignment,this.offsets)},completeEdit:function(a){if(!this.editing){return}if(this.field.assertValue){this.field.assertValue()}var b=this.getValue();if(!this.field.isValid()){if(this.revertInvalid!==false){this.cancelEdit(a)}return}if(String(b)===String(this.startValue)&&this.ignoreNoChange){this.hideEdit(a);return}if(this.fireEvent("beforecomplete",this,b,this.startValue)!==false){b=this.getValue();if(this.updateEl&&this.boundEl){this.boundEl.update(b)}this.hideEdit(a);this.fireEvent("complete",this,b,this.startValue)}},onShow:function(){this.el.show();if(this.hideEl!==false){this.boundEl.hide()}this.field.show().focus(false,true);this.fireEvent("startedit",this.boundEl,this.startValue)},cancelEdit:function(a){if(this.editing){var b=this.getValue();this.setValue(this.startValue);this.hideEdit(a);this.fireEvent("canceledit",this,b,this.startValue)}},hideEdit:function(a){if(a!==true){this.editing=false;this.hide()}},onBlur:function(){if(this.allowBlur===true&&this.editing&&this.selectSameEditor!==true){this.completeEdit()}},onHide:function(){if(this.editing){this.completeEdit();return}this.field.blur();if(this.field.collapse){this.field.collapse()}this.el.hide();if(this.hideEl!==false){this.boundEl.show()}},setValue:function(a){this.field.setValue(a)},getValue:function(){return this.field.getValue()},beforeDestroy:function(){Ext.destroyMembers(this,"field");delete this.parentEl;delete this.boundEl}});Ext.reg("editor",Ext.Editor);Ext.ColorPalette=Ext.extend(Ext.Component,{itemCls:"x-color-palette",value:null,clickEvent:"click",ctype:"Ext.ColorPalette",allowReselect:false,colors:["000000","993300","333300","003300","003366","000080","333399","333333","800000","FF6600","808000","008000","008080","0000FF","666699","808080","FF0000","FF9900","99CC00","339966","33CCCC","3366FF","800080","969696","FF00FF","FFCC00","FFFF00","00FF00","00FFFF","00CCFF","993366","C0C0C0","FF99CC","FFCC99","FFFF99","CCFFCC","CCFFFF","99CCFF","CC99FF","FFFFFF"],initComponent:function(){Ext.ColorPalette.superclass.initComponent.call(this);this.addEvents("select");if(this.handler){this.on("select",this.handler,this.scope,true)}},onRender:function(b,a){this.autoEl={tag:"div",cls:this.itemCls};Ext.ColorPalette.superclass.onRender.call(this,b,a);var c=this.tpl||new Ext.XTemplate('<tpl for="."><a href="#" class="color-{.}" hidefocus="on"><em><span style="background:#{.}" class="x-unselectable" unselectable="on">&#160;</span></em></a></tpl>');c.overwrite(this.el,this.colors);this.mon(this.el,this.clickEvent,this.handleClick,this,{delegate:"a"});if(this.clickEvent!="click"){this.mon(this.el,"click",Ext.emptyFn,this,{delegate:"a",preventDefault:true})}},afterRender:function(){Ext.ColorPalette.superclass.afterRender.call(this);if(this.value){var a=this.value;this.value=null;this.select(a,true)}},handleClick:function(b,a){b.preventDefault();if(!this.disabled){var d=a.className.match(/(?:^|\s)color-(.{6})(?:\s|$)/)[1];this.select(d.toUpperCase())}},select:function(b,a){b=b.replace("#","");if(b!=this.value||this.allowReselect){var c=this.el;if(this.value){c.child("a.color-"+this.value).removeClass("x-color-palette-sel")}c.child("a.color-"+b).addClass("x-color-palette-sel");this.value=b;if(a!==true){this.fireEvent("select",this,b)}}}});Ext.reg("colorpalette",Ext.ColorPalette);Ext.DatePicker=Ext.extend(Ext.BoxComponent,{todayText:"Today",okText:"&#160;OK&#160;",cancelText:"Cancel",todayTip:"{0} (Spacebar)",minText:"This date is before the minimum date",maxText:"This date is after the maximum date",format:"m/d/y",disabledDaysText:"Disabled",disabledDatesText:"Disabled",monthNames:Date.monthNames,dayNames:Date.dayNames,nextText:"Next Month (Control+Right)",prevText:"Previous Month (Control+Left)",monthYearText:"Choose a month (Control+Up/Down to move years)",startDay:0,showToday:true,focusOnSelect:true,initHour:12,initComponent:function(){Ext.DatePicker.superclass.initComponent.call(this);this.value=this.value?this.value.clearTime(true):new Date().clearTime();this.addEvents("select");if(this.handler){this.on("select",this.handler,this.scope||this)}this.initDisabledDays()},initDisabledDays:function(){if(!this.disabledDatesRE&&this.disabledDates){var b=this.disabledDates,a=b.length-1,c="(?:";Ext.each(b,function(g,e){c+=Ext.isDate(g)?"^"+Ext.escapeRe(g.dateFormat(this.format))+"$":b[e];if(e!=a){c+="|"}},this);this.disabledDatesRE=new RegExp(c+")")}},setDisabledDates:function(a){if(Ext.isArray(a)){this.disabledDates=a;this.disabledDatesRE=null}else{this.disabledDatesRE=a}this.initDisabledDays();this.update(this.value,true)},setDisabledDays:function(a){this.disabledDays=a;this.update(this.value,true)},setMinDate:function(a){this.minDate=a;this.update(this.value,true)},setMaxDate:function(a){this.maxDate=a;this.update(this.value,true)},setValue:function(a){this.value=a.clearTime(true);this.update(this.value)},getValue:function(){return this.value},focus:function(){this.update(this.activeDate)},onEnable:function(a){Ext.DatePicker.superclass.onEnable.call(this);this.doDisabled(false);this.update(a?this.value:this.activeDate);if(Ext.isIE9m){this.el.repaint()}},onDisable:function(){Ext.DatePicker.superclass.onDisable.call(this);this.doDisabled(true);if(Ext.isIE9m&&!Ext.isIE8){Ext.each([].concat(this.textNodes,this.el.query("th span")),function(a){Ext.fly(a).repaint()})}},doDisabled:function(a){this.keyNav.setDisabled(a);this.prevRepeater.setDisabled(a);this.nextRepeater.setDisabled(a);if(this.showToday){this.todayKeyListener.setDisabled(a);this.todayBtn.setDisabled(a)}},onRender:function(e,b){var a=['<table cellspacing="0">','<tr><td class="x-date-left"><a href="#" title="',this.prevText,'">&#160;</a></td><td class="x-date-middle" align="center"></td><td class="x-date-right"><a href="#" title="',this.nextText,'">&#160;</a></td></tr>','<tr><td colspan="3"><table class="x-date-inner" cellspacing="0"><thead><tr>'],c=this.dayNames,h;for(h=0;h<7;h++){var k=this.startDay+h;if(k>6){k=k-7}a.push("<th><span>",c[k].substr(0,1),"</span></th>")}a[a.length]="</tr></thead><tbody><tr>";for(h=0;h<42;h++){if(h%7===0&&h!==0){a[a.length]="</tr><tr>"}a[a.length]='<td><a href="#" hidefocus="on" class="x-date-date" tabIndex="1"><em><span></span></em></a></td>'}a.push("</tr></tbody></table></td></tr>",this.showToday?'<tr><td colspan="3" class="x-date-bottom" align="center"></td></tr>':"",'</table><div class="x-date-mp"></div>');var j=document.createElement("div");j.className="x-date-picker";j.innerHTML=a.join("");e.dom.insertBefore(j,b);this.el=Ext.get(j);this.eventEl=Ext.get(j.firstChild);this.prevRepeater=new Ext.util.ClickRepeater(this.el.child("td.x-date-left a"),{handler:this.showPrevMonth,scope:this,preventDefault:true,stopDefault:true});this.nextRepeater=new Ext.util.ClickRepeater(this.el.child("td.x-date-right a"),{handler:this.showNextMonth,scope:this,preventDefault:true,stopDefault:true});this.monthPicker=this.el.down("div.x-date-mp");this.monthPicker.enableDisplayMode("block");this.keyNav=new Ext.KeyNav(this.eventEl,{left:function(d){if(d.ctrlKey){this.showPrevMonth()}else{this.update(this.activeDate.add("d",-1))}},right:function(d){if(d.ctrlKey){this.showNextMonth()}else{this.update(this.activeDate.add("d",1))}},up:function(d){if(d.ctrlKey){this.showNextYear()}else{this.update(this.activeDate.add("d",-7))}},down:function(d){if(d.ctrlKey){this.showPrevYear()}else{this.update(this.activeDate.add("d",7))}},pageUp:function(d){this.showNextMonth()},pageDown:function(d){this.showPrevMonth()},enter:function(d){d.stopPropagation();return true},scope:this});this.el.unselectable();this.cells=this.el.select("table.x-date-inner tbody td");this.textNodes=this.el.query("table.x-date-inner tbody span");this.mbtn=new Ext.Button({text:"&#160;",tooltip:this.monthYearText,renderTo:this.el.child("td.x-date-middle",true)});this.mbtn.el.child("em").addClass("x-btn-arrow");if(this.showToday){this.todayKeyListener=this.eventEl.addKeyListener(Ext.EventObject.SPACE,this.selectToday,this);var g=(new Date()).dateFormat(this.format);this.todayBtn=new Ext.Button({renderTo:this.el.child("td.x-date-bottom",true),text:String.format(this.todayText,g),tooltip:String.format(this.todayTip,g),handler:this.selectToday,scope:this})}this.mon(this.eventEl,"mousewheel",this.handleMouseWheel,this);this.mon(this.eventEl,"click",this.handleDateClick,this,{delegate:"a.x-date-date"});this.mon(this.mbtn,"click",this.showMonthPicker,this);this.onEnable(true)},createMonthPicker:function(){if(!this.monthPicker.dom.firstChild){var a=['<table border="0" cellspacing="0">'];for(var b=0;b<6;b++){a.push('<tr><td class="x-date-mp-month"><a href="#">',Date.getShortMonthName(b),"</a></td>",'<td class="x-date-mp-month x-date-mp-sep"><a href="#">',Date.getShortMonthName(b+6),"</a></td>",b===0?'<td class="x-date-mp-ybtn" align="center"><a class="x-date-mp-prev"></a></td><td class="x-date-mp-ybtn" align="center"><a class="x-date-mp-next"></a></td></tr>':'<td class="x-date-mp-year"><a href="#"></a></td><td class="x-date-mp-year"><a href="#"></a></td></tr>')}a.push('<tr class="x-date-mp-btns"><td colspan="4"><button type="button" class="x-date-mp-ok">',this.okText,'</button><button type="button" class="x-date-mp-cancel">',this.cancelText,"</button></td></tr>","</table>");this.monthPicker.update(a.join(""));this.mon(this.monthPicker,"click",this.onMonthClick,this);this.mon(this.monthPicker,"dblclick",this.onMonthDblClick,this);this.mpMonths=this.monthPicker.select("td.x-date-mp-month");this.mpYears=this.monthPicker.select("td.x-date-mp-year");this.mpMonths.each(function(c,d,e){e+=1;if((e%2)===0){c.dom.xmonth=5+Math.round(e*0.5)}else{c.dom.xmonth=Math.round((e-1)*0.5)}})}},showMonthPicker:function(){if(!this.disabled){this.createMonthPicker();var a=this.el.getSize();this.monthPicker.setSize(a);this.monthPicker.child("table").setSize(a);this.mpSelMonth=(this.activeDate||this.value).getMonth();this.updateMPMonth(this.mpSelMonth);this.mpSelYear=(this.activeDate||this.value).getFullYear();this.updateMPYear(this.mpSelYear);this.monthPicker.slideIn("t",{duration:0.2})}},updateMPYear:function(e){this.mpyear=e;var c=this.mpYears.elements;for(var b=1;b<=10;b++){var d=c[b-1],a;if((b%2)===0){a=e+Math.round(b*0.5);d.firstChild.innerHTML=a;d.xyear=a}else{a=e-(5-Math.round(b*0.5));d.firstChild.innerHTML=a;d.xyear=a}this.mpYears.item(b-1)[a==this.mpSelYear?"addClass":"removeClass"]("x-date-mp-sel")}},updateMPMonth:function(a){this.mpMonths.each(function(b,c,d){b[b.dom.xmonth==a?"addClass":"removeClass"]("x-date-mp-sel")})},selectMPMonth:function(a){},onMonthClick:function(g,b){g.stopEvent();var c=new Ext.Element(b),a;if(c.is("button.x-date-mp-cancel")){this.hideMonthPicker()}else{if(c.is("button.x-date-mp-ok")){var h=new Date(this.mpSelYear,this.mpSelMonth,(this.activeDate||this.value).getDate());if(h.getMonth()!=this.mpSelMonth){h=new Date(this.mpSelYear,this.mpSelMonth,1).getLastDateOfMonth()}this.update(h);this.hideMonthPicker()}else{if((a=c.up("td.x-date-mp-month",2))){this.mpMonths.removeClass("x-date-mp-sel");a.addClass("x-date-mp-sel");this.mpSelMonth=a.dom.xmonth}else{if((a=c.up("td.x-date-mp-year",2))){this.mpYears.removeClass("x-date-mp-sel");a.addClass("x-date-mp-sel");this.mpSelYear=a.dom.xyear}else{if(c.is("a.x-date-mp-prev")){this.updateMPYear(this.mpyear-10)}else{if(c.is("a.x-date-mp-next")){this.updateMPYear(this.mpyear+10)}}}}}}},onMonthDblClick:function(d,b){d.stopEvent();var c=new Ext.Element(b),a;if((a=c.up("td.x-date-mp-month",2))){this.update(new Date(this.mpSelYear,a.dom.xmonth,(this.activeDate||this.value).getDate()));this.hideMonthPicker()}else{if((a=c.up("td.x-date-mp-year",2))){this.update(new Date(a.dom.xyear,this.mpSelMonth,(this.activeDate||this.value).getDate()));this.hideMonthPicker()}}},hideMonthPicker:function(a){if(this.monthPicker){if(a===true){this.monthPicker.hide()}else{this.monthPicker.slideOut("t",{duration:0.2})}}},showPrevMonth:function(a){this.update(this.activeDate.add("mo",-1))},showNextMonth:function(a){this.update(this.activeDate.add("mo",1))},showPrevYear:function(){this.update(this.activeDate.add("y",-1))},showNextYear:function(){this.update(this.activeDate.add("y",1))},handleMouseWheel:function(a){a.stopEvent();if(!this.disabled){var b=a.getWheelDelta();if(b>0){this.showPrevMonth()}else{if(b<0){this.showNextMonth()}}}},handleDateClick:function(b,a){b.stopEvent();if(!this.disabled&&a.dateValue&&!Ext.fly(a.parentNode).hasClass("x-date-disabled")){this.cancelFocus=this.focusOnSelect===false;this.setValue(new Date(a.dateValue));delete this.cancelFocus;this.fireEvent("select",this,this.value)}},selectToday:function(){if(this.todayBtn&&!this.todayBtn.disabled){this.setValue(new Date().clearTime());this.fireEvent("select",this,this.value)}},update:function(G,A){if(this.rendered){var a=this.activeDate,p=this.isVisible();this.activeDate=G;if(!A&&a&&this.el){var o=G.getTime();if(a.getMonth()==G.getMonth()&&a.getFullYear()==G.getFullYear()){this.cells.removeClass("x-date-selected");this.cells.each(function(d){if(d.dom.firstChild.dateValue==o){d.addClass("x-date-selected");if(p&&!this.cancelFocus){Ext.fly(d.dom.firstChild).focus(50)}return false}},this);return}}var k=G.getDaysInMonth(),q=G.getFirstDateOfMonth(),g=q.getDay()-this.startDay;if(g<0){g+=7}k+=g;var B=G.add("mo",-1),h=B.getDaysInMonth()-g,e=this.cells.elements,r=this.textNodes,D=(new Date(B.getFullYear(),B.getMonth(),h,this.initHour)),C=new Date().clearTime().getTime(),v=G.clearTime(true).getTime(),u=this.minDate?this.minDate.clearTime(true):Number.NEGATIVE_INFINITY,y=this.maxDate?this.maxDate.clearTime(true):Number.POSITIVE_INFINITY,F=this.disabledDatesRE,s=this.disabledDatesText,I=this.disabledDays?this.disabledDays.join(""):false,E=this.disabledDaysText,z=this.format;if(this.showToday){var m=new Date().clearTime(),c=(m<u||m>y||(F&&z&&F.test(m.dateFormat(z)))||(I&&I.indexOf(m.getDay())!=-1));if(!this.disabled){this.todayBtn.setDisabled(c);this.todayKeyListener[c?"disable":"enable"]()}}var l=function(J,d){d.title="";var i=D.clearTime(true).getTime();d.firstChild.dateValue=i;if(i==C){d.className+=" x-date-today";d.title=J.todayText}if(i==v){d.className+=" x-date-selected";if(p){Ext.fly(d.firstChild).focus(50)}}if(i<u){d.className=" x-date-disabled";d.title=J.minText;return}if(i>y){d.className=" x-date-disabled";d.title=J.maxText;return}if(I){if(I.indexOf(D.getDay())!=-1){d.title=E;d.className=" x-date-disabled"}}if(F&&z){var w=D.dateFormat(z);if(F.test(w)){d.title=s.replace("%0",w);d.className=" x-date-disabled"}}};var x=0;for(;x<g;x++){r[x].innerHTML=(++h);D.setDate(D.getDate()+1);e[x].className="x-date-prevday";l(this,e[x])}for(;x<k;x++){var b=x-g+1;r[x].innerHTML=(b);D.setDate(D.getDate()+1);e[x].className="x-date-active";l(this,e[x])}var H=0;for(;x<42;x++){r[x].innerHTML=(++H);D.setDate(D.getDate()+1);e[x].className="x-date-nextday";l(this,e[x])}this.mbtn.setText(this.monthNames[G.getMonth()]+" "+G.getFullYear());if(!this.internalRender){var j=this.el.dom.firstChild,n=j.offsetWidth;this.el.setWidth(n+this.el.getBorderWidth("lr"));Ext.fly(j).setWidth(n);this.internalRender=true;if(Ext.isOpera&&!this.secondPass){j.rows[0].cells[1].style.width=(n-(j.rows[0].cells[0].offsetWidth+j.rows[0].cells[2].offsetWidth))+"px";this.secondPass=true;this.update.defer(10,this,[G])}}}},beforeDestroy:function(){if(this.rendered){Ext.destroy(this.keyNav,this.monthPicker,this.eventEl,this.mbtn,this.nextRepeater,this.prevRepeater,this.cells.el,this.todayBtn);delete this.textNodes;delete this.cells.elements}}});Ext.reg("datepicker",Ext.DatePicker);Ext.LoadMask=function(c,b){this.el=Ext.get(c);Ext.apply(this,b);if(this.store){this.store.on({scope:this,beforeload:this.onBeforeLoad,load:this.onLoad,exception:this.onLoad});this.removeMask=Ext.value(this.removeMask,false)}else{var a=this.el.getUpdater();a.showLoadIndicator=false;a.on({scope:this,beforeupdate:this.onBeforeLoad,update:this.onLoad,failure:this.onLoad});this.removeMask=Ext.value(this.removeMask,true)}};Ext.LoadMask.prototype={msg:"Loading...",msgCls:"x-mask-loading",disabled:false,disable:function(){this.disabled=true},enable:function(){this.disabled=false},onLoad:function(){this.el.unmask(this.removeMask)},onBeforeLoad:function(){if(!this.disabled){this.el.mask(this.msg,this.msgCls)}},show:function(){this.onBeforeLoad()},hide:function(){this.onLoad()},destroy:function(){if(this.store){this.store.un("beforeload",this.onBeforeLoad,this);this.store.un("load",this.onLoad,this);this.store.un("exception",this.onLoad,this)}else{var a=this.el.getUpdater();a.un("beforeupdate",this.onBeforeLoad,this);a.un("update",this.onLoad,this);a.un("failure",this.onLoad,this)}}};Ext.slider.Thumb=Ext.extend(Object,{dragging:false,constructor:function(a){Ext.apply(this,a||{},{cls:"x-slider-thumb",constrain:false});Ext.slider.Thumb.superclass.constructor.call(this,a);if(this.slider.vertical){Ext.apply(this,Ext.slider.Thumb.Vertical)}},render:function(){this.el=this.slider.innerEl.insertFirst({cls:this.cls});this.initEvents()},enable:function(){this.disabled=false;this.el.removeClass(this.slider.disabledClass)},disable:function(){this.disabled=true;this.el.addClass(this.slider.disabledClass)},initEvents:function(){var a=this.el;a.addClassOnOver("x-slider-thumb-over");this.tracker=new Ext.dd.DragTracker({onBeforeStart:this.onBeforeDragStart.createDelegate(this),onStart:this.onDragStart.createDelegate(this),onDrag:this.onDrag.createDelegate(this),onEnd:this.onDragEnd.createDelegate(this),tolerance:3,autoStart:300});this.tracker.initEl(a)},onBeforeDragStart:function(a){if(this.disabled){return false}else{this.slider.promoteThumb(this);return true}},onDragStart:function(a){this.el.addClass("x-slider-thumb-drag");this.dragging=true;this.dragStartValue=this.value;this.slider.fireEvent("dragstart",this.slider,a,this)},onDrag:function(g){var c=this.slider,b=this.index,d=this.getNewValue();if(this.constrain){var a=c.thumbs[b+1],h=c.thumbs[b-1];if(h!=undefined&&d<=h.value){d=h.value}if(a!=undefined&&d>=a.value){d=a.value}}c.setValue(b,d,false);c.fireEvent("drag",c,g,this)},getNewValue:function(){var a=this.slider,b=a.innerEl.translatePoints(this.tracker.getXY());return Ext.util.Format.round(a.reverseValue(b.left),a.decimalPrecision)},onDragEnd:function(c){var a=this.slider,b=this.value;this.el.removeClass("x-slider-thumb-drag");this.dragging=false;a.fireEvent("dragend",a,c);if(this.dragStartValue!=b){a.fireEvent("changecomplete",a,b,this)}},destroy:function(){Ext.destroyMembers(this,"tracker","el")}});Ext.slider.MultiSlider=Ext.extend(Ext.BoxComponent,{vertical:false,minValue:0,maxValue:100,decimalPrecision:0,keyIncrement:1,increment:0,clickRange:[5,15],clickToChange:true,animate:true,constrainThumbs:true,topThumbZIndex:10000,initComponent:function(){if(!Ext.isDefined(this.value)){this.value=this.minValue}this.thumbs=[];Ext.slider.MultiSlider.superclass.initComponent.call(this);this.keyIncrement=Math.max(this.increment,this.keyIncrement);this.addEvents("beforechange","change","changecomplete","dragstart","drag","dragend");if(this.values==undefined||Ext.isEmpty(this.values)){this.values=[0]}var a=this.values;for(var b=0;b<a.length;b++){this.addThumb(a[b])}if(this.vertical){Ext.apply(this,Ext.slider.Vertical)}},addThumb:function(b){var a=new Ext.slider.Thumb({value:b,slider:this,index:this.thumbs.length,constrain:this.constrainThumbs});this.thumbs.push(a);if(this.rendered){a.render()}},promoteThumb:function(d){var a=this.thumbs,g,b;for(var e=0,c=a.length;e<c;e++){b=a[e];if(b==d){g=this.topThumbZIndex}else{g=""}b.el.setStyle("zIndex",g)}},onRender:function(){this.autoEl={cls:"x-slider "+(this.vertical?"x-slider-vert":"x-slider-horz"),cn:{cls:"x-slider-end",cn:{cls:"x-slider-inner",cn:[{tag:"a",cls:"x-slider-focus",href:"#",tabIndex:"-1",hidefocus:"on"}]}}};Ext.slider.MultiSlider.superclass.onRender.apply(this,arguments);this.endEl=this.el.first();this.innerEl=this.endEl.first();this.focusEl=this.innerEl.child(".x-slider-focus");for(var b=0;b<this.thumbs.length;b++){this.thumbs[b].render()}var a=this.innerEl.child(".x-slider-thumb");this.halfThumb=(this.vertical?a.getHeight():a.getWidth())/2;this.initEvents()},initEvents:function(){this.mon(this.el,{scope:this,mousedown:this.onMouseDown,keydown:this.onKeyDown});this.focusEl.swallowEvent("click",true)},onMouseDown:function(d){if(this.disabled){return}var c=false;for(var b=0;b<this.thumbs.length;b++){c=c||d.target==this.thumbs[b].el.dom}if(this.clickToChange&&!c){var a=this.innerEl.translatePoints(d.getXY());this.onClickChange(a)}this.focus()},onClickChange:function(c){if(c.top>this.clickRange[0]&&c.top<this.clickRange[1]){var a=this.getNearest(c,"left"),b=a.index;this.setValue(b,Ext.util.Format.round(this.reverseValue(c.left),this.decimalPrecision),undefined,true)}},getNearest:function(k,b){var m=b=="top"?this.innerEl.getHeight()-k[b]:k[b],g=this.reverseValue(m),j=(this.maxValue-this.minValue)+5,e=0,c=null;for(var d=0;d<this.thumbs.length;d++){var a=this.thumbs[d],l=a.value,h=Math.abs(l-g);if(Math.abs(h<=j)){c=a;e=d;j=h}}return c},onKeyDown:function(b){if(this.disabled||this.thumbs.length!==1){b.preventDefault();return}var a=b.getKey(),c;switch(a){case b.UP:case b.RIGHT:b.stopEvent();c=b.ctrlKey?this.maxValue:this.getValue(0)+this.keyIncrement;this.setValue(0,c,undefined,true);break;case b.DOWN:case b.LEFT:b.stopEvent();c=b.ctrlKey?this.minValue:this.getValue(0)-this.keyIncrement;this.setValue(0,c,undefined,true);break;default:b.preventDefault()}},doSnap:function(b){if(!(this.increment&&b)){return b}var d=b,c=this.increment,a=b%c;if(a!=0){d-=a;if(a*2>=c){d+=c}else{if(a*2<-c){d-=c}}}return d.constrain(this.minValue,this.maxValue)},afterRender:function(){Ext.slider.MultiSlider.superclass.afterRender.apply(this,arguments);for(var c=0;c<this.thumbs.length;c++){var b=this.thumbs[c];if(b.value!==undefined){var a=this.normalizeValue(b.value);if(a!==b.value){this.setValue(c,a,false)}else{this.moveThumb(c,this.translateValue(a),false)}}}},getRatio:function(){var a=this.innerEl.getWidth(),b=this.maxValue-this.minValue;return b==0?a:(a/b)},normalizeValue:function(a){a=this.doSnap(a);a=Ext.util.Format.round(a,this.decimalPrecision);a=a.constrain(this.minValue,this.maxValue);return a},setMinValue:function(e){this.minValue=e;var d=0,b=this.thumbs,a=b.length,c;for(;d<a;++d){c=b[d];c.value=c.value<e?e:c.value}this.syncThumb()},setMaxValue:function(e){this.maxValue=e;var d=0,b=this.thumbs,a=b.length,c;for(;d<a;++d){c=b[d];c.value=c.value>e?e:c.value}this.syncThumb()},setValue:function(d,c,b,g){var a=this.thumbs[d],e=a.el;c=this.normalizeValue(c);if(c!==a.value&&this.fireEvent("beforechange",this,c,a.value,a)!==false){a.value=c;if(this.rendered){this.moveThumb(d,this.translateValue(c),b!==false);this.fireEvent("change",this,c,a);if(g){this.fireEvent("changecomplete",this,c,a)}}}},translateValue:function(a){var b=this.getRatio();return(a*b)-(this.minValue*b)-this.halfThumb},reverseValue:function(b){var a=this.getRatio();return(b+(this.minValue*a))/a},moveThumb:function(d,c,b){var a=this.thumbs[d].el;if(!b||this.animate===false){a.setLeft(c)}else{a.shift({left:c,stopFx:true,duration:0.35})}},focus:function(){this.focusEl.focus(10)},onResize:function(c,e){var b=this.thumbs,a=b.length,d=0;for(;d<a;++d){b[d].el.stopFx()}if(Ext.isNumber(c)){this.innerEl.setWidth(c-(this.el.getPadding("l")+this.endEl.getPadding("r")))}this.syncThumb();Ext.slider.MultiSlider.superclass.onResize.apply(this,arguments)},onDisable:function(){Ext.slider.MultiSlider.superclass.onDisable.call(this);for(var b=0;b<this.thumbs.length;b++){var a=this.thumbs[b],c=a.el;a.disable();if(Ext.isIE){var d=c.getXY();c.hide();this.innerEl.addClass(this.disabledClass).dom.disabled=true;if(!this.thumbHolder){this.thumbHolder=this.endEl.createChild({cls:"x-slider-thumb "+this.disabledClass})}this.thumbHolder.show().setXY(d)}}},onEnable:function(){Ext.slider.MultiSlider.superclass.onEnable.call(this);for(var b=0;b<this.thumbs.length;b++){var a=this.thumbs[b],c=a.el;a.enable();if(Ext.isIE){this.innerEl.removeClass(this.disabledClass).dom.disabled=false;if(this.thumbHolder){this.thumbHolder.hide()}c.show();this.syncThumb()}}},syncThumb:function(){if(this.rendered){for(var a=0;a<this.thumbs.length;a++){this.moveThumb(a,this.translateValue(this.thumbs[a].value))}}},getValue:function(a){return this.thumbs[a].value},getValues:function(){var a=[];for(var b=0;b<this.thumbs.length;b++){a.push(this.thumbs[b].value)}return a},beforeDestroy:function(){var b=this.thumbs;for(var c=0,a=b.length;c<a;++c){b[c].destroy();b[c]=null}Ext.destroyMembers(this,"endEl","innerEl","focusEl","thumbHolder");Ext.slider.MultiSlider.superclass.beforeDestroy.call(this)}});Ext.reg("multislider",Ext.slider.MultiSlider);Ext.slider.SingleSlider=Ext.extend(Ext.slider.MultiSlider,{constructor:function(a){a=a||{};Ext.applyIf(a,{values:[a.value||0]});Ext.slider.SingleSlider.superclass.constructor.call(this,a)},getValue:function(){return Ext.slider.SingleSlider.superclass.getValue.call(this,0)},setValue:function(d,b){var c=Ext.toArray(arguments),a=c.length;if(a==1||(a<=3&&typeof arguments[1]!="number")){c.unshift(0)}return Ext.slider.SingleSlider.superclass.setValue.apply(this,c)},syncThumb:function(){return Ext.slider.SingleSlider.superclass.syncThumb.apply(this,[0].concat(arguments))},getNearest:function(){return this.thumbs[0]}});Ext.Slider=Ext.slider.SingleSlider;Ext.reg("slider",Ext.slider.SingleSlider);Ext.slider.Vertical={onResize:function(a,b){this.innerEl.setHeight(b-(this.el.getPadding("t")+this.endEl.getPadding("b")));this.syncThumb()},getRatio:function(){var b=this.innerEl.getHeight(),a=this.maxValue-this.minValue;return b/a},moveThumb:function(d,c,b){var a=this.thumbs[d],e=a.el;if(!b||this.animate===false){e.setBottom(c)}else{e.shift({bottom:c,stopFx:true,duration:0.35})}},onClickChange:function(c){if(c.left>this.clickRange[0]&&c.left<this.clickRange[1]){var a=this.getNearest(c,"top"),b=a.index,d=this.minValue+this.reverseValue(this.innerEl.getHeight()-c.top);this.setValue(b,Ext.util.Format.round(d,this.decimalPrecision),undefined,true)}}};Ext.slider.Thumb.Vertical={getNewValue:function(){var b=this.slider,c=b.innerEl,d=c.translatePoints(this.tracker.getXY()),a=c.getHeight()-d.top;return b.minValue+Ext.util.Format.round(a/b.getRatio(),b.decimalPrecision)}};Ext.ProgressBar=Ext.extend(Ext.BoxComponent,{baseCls:"x-progress",animate:false,waitTimer:null,initComponent:function(){Ext.ProgressBar.superclass.initComponent.call(this);this.addEvents("update")},onRender:function(d,a){var c=new Ext.Template('<div class="{cls}-wrap">','<div class="{cls}-inner">','<div class="{cls}-bar">','<div class="{cls}-text">',"<div>&#160;</div>","</div>","</div>",'<div class="{cls}-text {cls}-text-back">',"<div>&#160;</div>","</div>","</div>","</div>");this.el=a?c.insertBefore(a,{cls:this.baseCls},true):c.append(d,{cls:this.baseCls},true);if(this.id){this.el.dom.id=this.id}var b=this.el.dom.firstChild;this.progressBar=Ext.get(b.firstChild);if(this.textEl){this.textEl=Ext.get(this.textEl);delete this.textTopEl}else{this.textTopEl=Ext.get(this.progressBar.dom.firstChild);var e=Ext.get(b.childNodes[1]);this.textTopEl.setStyle("z-index",99).addClass("x-hidden");this.textEl=new Ext.CompositeElement([this.textTopEl.dom.firstChild,e.dom.firstChild]);this.textEl.setWidth(b.offsetWidth)}this.progressBar.setHeight(b.offsetHeight)},afterRender:function(){Ext.ProgressBar.superclass.afterRender.call(this);if(this.value){this.updateProgress(this.value,this.text)}else{this.updateText(this.text)}},updateProgress:function(c,d,b){this.value=c||0;if(d){this.updateText(d)}if(this.rendered&&!this.isDestroyed){var a=Math.floor(c*this.el.dom.firstChild.offsetWidth);this.progressBar.setWidth(a,b===true||(b!==false&&this.animate));if(this.textTopEl){this.textTopEl.removeClass("x-hidden").setWidth(a)}}this.fireEvent("update",this,c,d);return this},wait:function(b){if(!this.waitTimer){var a=this;b=b||{};this.updateText(b.text);this.waitTimer=Ext.TaskMgr.start({run:function(c){var d=b.increment||10;c-=1;this.updateProgress(((((c+d)%d)+1)*(100/d))*0.01,null,b.animate)},interval:b.interval||1000,duration:b.duration,onStop:function(){if(b.fn){b.fn.apply(b.scope||this)}this.reset()},scope:a})}return this},isWaiting:function(){return this.waitTimer!==null},updateText:function(a){this.text=a||"&#160;";if(this.rendered){this.textEl.update(this.text)}return this},syncProgressBar:function(){if(this.value){this.updateProgress(this.value,this.text)}return this},setSize:function(a,c){Ext.ProgressBar.superclass.setSize.call(this,a,c);if(this.textTopEl){var b=this.el.dom.firstChild;this.textEl.setSize(b.offsetWidth,b.offsetHeight)}this.syncProgressBar();return this},reset:function(a){this.updateProgress(0);if(this.textTopEl){this.textTopEl.addClass("x-hidden")}this.clearTimer();if(a===true){this.hide()}return this},clearTimer:function(){if(this.waitTimer){this.waitTimer.onStop=null;Ext.TaskMgr.stop(this.waitTimer);this.waitTimer=null}},onDestroy:function(){this.clearTimer();if(this.rendered){if(this.textEl.isComposite){this.textEl.clear()}Ext.destroyMembers(this,"textEl","progressBar","textTopEl")}Ext.ProgressBar.superclass.onDestroy.call(this)}});Ext.reg("progress",Ext.ProgressBar);(function(){var a=Ext.EventManager;var b=Ext.lib.Dom;Ext.dd.DragDrop=function(e,c,d){if(e){this.init(e,c,d)}};Ext.dd.DragDrop.prototype={id:null,config:null,dragElId:null,handleElId:null,invalidHandleTypes:null,invalidHandleIds:null,invalidHandleClasses:null,startPageX:0,startPageY:0,groups:null,locked:false,lock:function(){this.locked=true},moveOnly:false,unlock:function(){this.locked=false},isTarget:true,padding:null,_domRef:null,__ygDragDrop:true,constrainX:false,constrainY:false,minX:0,maxX:0,minY:0,maxY:0,maintainOffset:false,xTicks:null,yTicks:null,primaryButtonOnly:true,available:false,hasOuterHandles:false,b4StartDrag:function(c,d){},startDrag:function(c,d){},b4Drag:function(c){},onDrag:function(c){},onDragEnter:function(c,d){},b4DragOver:function(c){},onDragOver:function(c,d){},b4DragOut:function(c){},onDragOut:function(c,d){},b4DragDrop:function(c){},onDragDrop:function(c,d){},onInvalidDrop:function(c){},b4EndDrag:function(c){},endDrag:function(c){},b4MouseDown:function(c){},onMouseDown:function(c){},onMouseUp:function(c){},onAvailable:function(){},defaultPadding:{left:0,right:0,top:0,bottom:0},constrainTo:function(j,h,o){if(Ext.isNumber(h)){h={left:h,right:h,top:h,bottom:h}}h=h||this.defaultPadding;var l=Ext.get(this.getEl()).getBox(),d=Ext.get(j),n=d.getScroll(),k,e=d.dom;if(e==document.body){k={x:n.left,y:n.top,width:Ext.lib.Dom.getViewWidth(),height:Ext.lib.Dom.getViewHeight()}}else{var m=d.getXY();k={x:m[0],y:m[1],width:e.clientWidth,height:e.clientHeight}}var i=l.y-k.y,g=l.x-k.x;this.resetConstraints();this.setXConstraint(g-(h.left||0),k.width-g-l.width-(h.right||0),this.xTickSize);this.setYConstraint(i-(h.top||0),k.height-i-l.height-(h.bottom||0),this.yTickSize)},getEl:function(){if(!this._domRef){this._domRef=Ext.getDom(this.id)}return this._domRef},getDragEl:function(){return Ext.getDom(this.dragElId)},init:function(e,c,d){this.initTarget(e,c,d);a.on(this.id,"mousedown",this.handleMouseDown,this)},initTarget:function(e,c,d){this.config=d||{};this.DDM=Ext.dd.DDM;this.groups={};if(typeof e!=="string"){e=Ext.id(e)}this.id=e;this.addToGroup((c)?c:"default");this.handleElId=e;this.setDragElId(e);this.invalidHandleTypes={A:"A"};this.invalidHandleIds={};this.invalidHandleClasses=[];this.applyConfig();this.handleOnAvailable()},applyConfig:function(){this.padding=this.config.padding||[0,0,0,0];this.isTarget=(this.config.isTarget!==false);this.maintainOffset=(this.config.maintainOffset);this.primaryButtonOnly=(this.config.primaryButtonOnly!==false)},handleOnAvailable:function(){this.available=true;this.resetConstraints();this.onAvailable()},setPadding:function(e,c,g,d){if(!c&&0!==c){this.padding=[e,e,e,e]}else{if(!g&&0!==g){this.padding=[e,c,e,c]}else{this.padding=[e,c,g,d]}}},setInitPosition:function(g,e){var h=this.getEl();if(!this.DDM.verifyEl(h)){return}var d=g||0;var c=e||0;var i=b.getXY(h);this.initPageX=i[0]-d;this.initPageY=i[1]-c;this.lastPageX=i[0];this.lastPageY=i[1];this.setStartPosition(i)},setStartPosition:function(d){var c=d||b.getXY(this.getEl());this.deltaSetXY=null;this.startPageX=c[0];this.startPageY=c[1]},addToGroup:function(c){this.groups[c]=true;this.DDM.regDragDrop(this,c)},removeFromGroup:function(c){if(this.groups[c]){delete this.groups[c]}this.DDM.removeDDFromGroup(this,c)},setDragElId:function(c){this.dragElId=c},setHandleElId:function(c){if(typeof c!=="string"){c=Ext.id(c)}this.handleElId=c;this.DDM.regHandle(this.id,c)},setOuterHandleElId:function(c){if(typeof c!=="string"){c=Ext.id(c)}a.on(c,"mousedown",this.handleMouseDown,this);this.setHandleElId(c);this.hasOuterHandles=true},unreg:function(){a.un(this.id,"mousedown",this.handleMouseDown);this._domRef=null;this.DDM._remove(this)},destroy:function(){this.unreg()},isLocked:function(){return(this.DDM.isLocked()||this.locked)},handleMouseDown:function(g,d){if(this.primaryButtonOnly&&g.button!=0){return}if(this.isLocked()){return}this.DDM.refreshCache(this.groups);var c=new Ext.lib.Point(Ext.lib.Event.getPageX(g),Ext.lib.Event.getPageY(g));if(!this.hasOuterHandles&&!this.DDM.isOverTarget(c,this)){}else{if(this.clickValidator(g)){this.setStartPosition();this.b4MouseDown(g);this.onMouseDown(g);this.DDM.handleMouseDown(g,this);if(this.preventDefault||this.stopPropagation){if(this.preventDefault){g.preventDefault()}if(this.stopPropagation){g.stopPropagation()}}else{this.DDM.stopEvent(g)}}else{}}},clickValidator:function(d){var c=d.getTarget();return(this.isValidHandleChild(c)&&(this.id==this.handleElId||this.DDM.handleWasClicked(c,this.id)))},addInvalidHandleType:function(c){var d=c.toUpperCase();this.invalidHandleTypes[d]=d},addInvalidHandleId:function(c){if(typeof c!=="string"){c=Ext.id(c)}this.invalidHandleIds[c]=c},addInvalidHandleClass:function(c){this.invalidHandleClasses.push(c)},removeInvalidHandleType:function(c){var d=c.toUpperCase();delete this.invalidHandleTypes[d]},removeInvalidHandleId:function(c){if(typeof c!=="string"){c=Ext.id(c)}delete this.invalidHandleIds[c]},removeInvalidHandleClass:function(d){for(var e=0,c=this.invalidHandleClasses.length;e<c;++e){if(this.invalidHandleClasses[e]==d){delete this.invalidHandleClasses[e]}}},isValidHandleChild:function(h){var g=true;var k;try{k=h.nodeName.toUpperCase()}catch(j){k=h.nodeName}g=g&&!this.invalidHandleTypes[k];g=g&&!this.invalidHandleIds[h.id];for(var d=0,c=this.invalidHandleClasses.length;g&&d<c;++d){g=!Ext.fly(h).hasClass(this.invalidHandleClasses[d])}return g},setXTicks:function(g,c){this.xTicks=[];this.xTickSize=c;var e={};for(var d=this.initPageX;d>=this.minX;d=d-c){if(!e[d]){this.xTicks[this.xTicks.length]=d;e[d]=true}}for(d=this.initPageX;d<=this.maxX;d=d+c){if(!e[d]){this.xTicks[this.xTicks.length]=d;e[d]=true}}this.xTicks.sort(this.DDM.numericSort)},setYTicks:function(g,c){this.yTicks=[];this.yTickSize=c;var e={};for(var d=this.initPageY;d>=this.minY;d=d-c){if(!e[d]){this.yTicks[this.yTicks.length]=d;e[d]=true}}for(d=this.initPageY;d<=this.maxY;d=d+c){if(!e[d]){this.yTicks[this.yTicks.length]=d;e[d]=true}}this.yTicks.sort(this.DDM.numericSort)},setXConstraint:function(e,d,c){this.leftConstraint=e;this.rightConstraint=d;this.minX=this.initPageX-e;this.maxX=this.initPageX+d;if(c){this.setXTicks(this.initPageX,c)}this.constrainX=true},clearConstraints:function(){this.constrainX=false;this.constrainY=false;this.clearTicks()},clearTicks:function(){this.xTicks=null;this.yTicks=null;this.xTickSize=0;this.yTickSize=0},setYConstraint:function(c,e,d){this.topConstraint=c;this.bottomConstraint=e;this.minY=this.initPageY-c;this.maxY=this.initPageY+e;if(d){this.setYTicks(this.initPageY,d)}this.constrainY=true},resetConstraints:function(){if(this.initPageX||this.initPageX===0){var d=(this.maintainOffset)?this.lastPageX-this.initPageX:0;var c=(this.maintainOffset)?this.lastPageY-this.initPageY:0;this.setInitPosition(d,c)}else{this.setInitPosition()}if(this.constrainX){this.setXConstraint(this.leftConstraint,this.rightConstraint,this.xTickSize)}if(this.constrainY){this.setYConstraint(this.topConstraint,this.bottomConstraint,this.yTickSize)}},getTick:function(k,g){if(!g){return k}else{if(g[0]>=k){return g[0]}else{for(var d=0,c=g.length;d<c;++d){var e=d+1;if(g[e]&&g[e]>=k){var j=k-g[d];var h=g[e]-k;return(h>j)?g[d]:g[e]}}return g[g.length-1]}}},toString:function(){return("DragDrop "+this.id)}}})();if(!Ext.dd.DragDropMgr){Ext.dd.DragDropMgr=function(){var a=Ext.EventManager;return{ids:{},handleIds:{},dragCurrent:null,dragOvers:{},deltaX:0,deltaY:0,preventDefault:true,stopPropagation:true,initialized:false,locked:false,init:function(){this.initialized=true},POINT:0,INTERSECT:1,mode:0,notifyOccluded:false,_execOnAll:function(d,c){for(var e in this.ids){for(var b in this.ids[e]){var g=this.ids[e][b];if(!this.isTypeOfDD(g)){continue}g[d].apply(g,c)}}},_onLoad:function(){this.init();a.on(document,"mouseup",this.handleMouseUp,this,true);a.on(document,"mousemove",this.handleMouseMove,this,true);a.on(window,"unload",this._onUnload,this,true);a.on(window,"resize",this._onResize,this,true)},_onResize:function(b){this._execOnAll("resetConstraints",[])},lock:function(){this.locked=true},unlock:function(){this.locked=false},isLocked:function(){return this.locked},locationCache:{},useCache:true,clickPixelThresh:3,clickTimeThresh:350,dragThreshMet:false,clickTimeout:null,startX:0,startY:0,regDragDrop:function(c,b){if(!this.initialized){this.init()}if(!this.ids[b]){this.ids[b]={}}this.ids[b][c.id]=c},removeDDFromGroup:function(d,b){if(!this.ids[b]){this.ids[b]={}}var c=this.ids[b];if(c&&c[d.id]){delete c[d.id]}},_remove:function(c){for(var b in c.groups){if(b&&this.ids[b]&&this.ids[b][c.id]){delete this.ids[b][c.id]}}delete this.handleIds[c.id]},regHandle:function(c,b){if(!this.handleIds[c]){this.handleIds[c]={}}this.handleIds[c][b]=b},isDragDrop:function(b){return(this.getDDById(b))?true:false},getRelated:function(h,c){var g=[];for(var e in h.groups){for(var d in this.ids[e]){var b=this.ids[e][d];if(!this.isTypeOfDD(b)){continue}if(!c||b.isTarget){g[g.length]=b}}}return g},isLegalTarget:function(g,e){var c=this.getRelated(g,true);for(var d=0,b=c.length;d<b;++d){if(c[d].id==e.id){return true}}return false},isTypeOfDD:function(b){return(b&&b.__ygDragDrop)},isHandle:function(c,b){return(this.handleIds[c]&&this.handleIds[c][b])},getDDById:function(c){for(var b in this.ids){if(this.ids[b][c]){return this.ids[b][c]}}return null},handleMouseDown:function(d,c){if(Ext.QuickTips){Ext.QuickTips.ddDisable()}if(this.dragCurrent){this.handleMouseUp(d)}this.currentTarget=d.getTarget();this.dragCurrent=c;var b=c.getEl();this.startX=d.getPageX();this.startY=d.getPageY();this.deltaX=this.startX-b.offsetLeft;this.deltaY=this.startY-b.offsetTop;this.dragThreshMet=false;this.clickTimeout=setTimeout(function(){var e=Ext.dd.DDM;e.startDrag(e.startX,e.startY)},this.clickTimeThresh)},startDrag:function(b,c){clearTimeout(this.clickTimeout);if(this.dragCurrent){this.dragCurrent.b4StartDrag(b,c);this.dragCurrent.startDrag(b,c)}this.dragThreshMet=true},handleMouseUp:function(b){if(Ext.QuickTips){Ext.QuickTips.ddEnable()}if(!this.dragCurrent){return}clearTimeout(this.clickTimeout);if(this.dragThreshMet){this.fireEvents(b,true)}else{}this.stopDrag(b);this.stopEvent(b)},stopEvent:function(b){if(this.stopPropagation){b.stopPropagation()}if(this.preventDefault){b.preventDefault()}},stopDrag:function(b){if(this.dragCurrent){if(this.dragThreshMet){this.dragCurrent.b4EndDrag(b);this.dragCurrent.endDrag(b)}this.dragCurrent.onMouseUp(b)}this.dragCurrent=null;this.dragOvers={}},handleMouseMove:function(d){if(!this.dragCurrent){return true}if(Ext.isIE&&(d.button!==0&&d.button!==1&&d.button!==2)){this.stopEvent(d);return this.handleMouseUp(d)}if(!this.dragThreshMet){var c=Math.abs(this.startX-d.getPageX());var b=Math.abs(this.startY-d.getPageY());if(c>this.clickPixelThresh||b>this.clickPixelThresh){this.startDrag(this.startX,this.startY)}}if(this.dragThreshMet){this.dragCurrent.b4Drag(d);this.dragCurrent.onDrag(d);if(!this.dragCurrent.moveOnly){this.fireEvents(d,false)}}this.stopEvent(d);return true},fireEvents:function(o,r){var q=this,l=q.dragCurrent,s=o.getPoint(),c,u,g=[],b=[],h=[],m=[],k=[],d=[],p,j,n,t;if(!l||l.isLocked()){return}for(j in q.dragOvers){c=q.dragOvers[j];if(!q.isTypeOfDD(c)){continue}if(!this.isOverTarget(s,c,q.mode)){h.push(c)}b[j]=true;delete q.dragOvers[j]}for(t in l.groups){if("string"!=typeof t){continue}for(j in q.ids[t]){c=q.ids[t][j];if(q.isTypeOfDD(c)&&(u=c.getEl())&&(c.isTarget)&&(!c.isLocked())&&((c!=l)||(l.ignoreSelf===false))){if((c.zIndex=q.getZIndex(u))!==-1){p=true}g.push(c)}}}if(p){g.sort(q.byZIndex)}for(j=0,n=g.length;j<n;j++){c=g[j];if(q.isOverTarget(s,c,q.mode)){if(r){k.push(c)}else{if(!b[c.id]){d.push(c)}else{m.push(c)}q.dragOvers[c.id]=c}if(!q.notifyOccluded){break}}}if(q.mode){if(h.length){l.b4DragOut(o,h);l.onDragOut(o,h)}if(d.length){l.onDragEnter(o,d)}if(m.length){l.b4DragOver(o,m);l.onDragOver(o,m)}if(k.length){l.b4DragDrop(o,k);l.onDragDrop(o,k)}}else{for(j=0,n=h.length;j<n;++j){l.b4DragOut(o,h[j].id);l.onDragOut(o,h[j].id)}for(j=0,n=d.length;j<n;++j){l.onDragEnter(o,d[j].id)}for(j=0,n=m.length;j<n;++j){l.b4DragOver(o,m[j].id);l.onDragOver(o,m[j].id)}for(j=0,n=k.length;j<n;++j){l.b4DragDrop(o,k[j].id);l.onDragDrop(o,k[j].id)}}if(r&&!k.length){l.onInvalidDrop(o)}},getZIndex:function(c){var b=document.body,d,e=-1;c=Ext.getDom(c);while(c!==b){if(!isNaN(d=Number(Ext.fly(c).getStyle("zIndex")))){e=d}c=c.parentNode}return e},byZIndex:function(c,b){return c.zIndex<b.zIndex},getBestMatch:function(d){var g=null;var c=d.length;if(c==1){g=d[0]}else{for(var e=0;e<c;++e){var b=d[e];if(b.cursorIsOver){g=b;break}else{if(!g||g.overlap.getArea()<b.overlap.getArea()){g=b}}}}return g},refreshCache:function(c){for(var b in c){if("string"!=typeof b){continue}for(var d in this.ids[b]){var e=this.ids[b][d];if(this.isTypeOfDD(e)){var g=this.getLocation(e);if(g){this.locationCache[e.id]=g}else{delete this.locationCache[e.id]}}}}},verifyEl:function(c){if(c){var b;if(Ext.isIE){try{b=c.offsetParent}catch(d){}}else{b=c.offsetParent}if(b){return true}}return false},getLocation:function(j){if(!this.isTypeOfDD(j)){return null}var h=j.getEl(),o,g,d,q,p,s,c,n,i,m;try{o=Ext.lib.Dom.getXY(h)}catch(k){}if(!o){return null}g=o[0];d=g+h.offsetWidth;q=o[1];p=q+h.offsetHeight;s=q-j.padding[0];c=d+j.padding[1];n=p+j.padding[2];i=g-j.padding[3];return new Ext.lib.Region(s,c,n,i)},isOverTarget:function(k,b,d){var g=this.locationCache[b.id];if(!g||!this.useCache){g=this.getLocation(b);this.locationCache[b.id]=g}if(!g){return false}b.cursorIsOver=g.contains(k);var j=this.dragCurrent;if(!j||!j.getTargetCoord||(!d&&!j.constrainX&&!j.constrainY)){return b.cursorIsOver}b.overlap=null;var h=j.getTargetCoord(k.x,k.y);var c=j.getDragEl();var e=new Ext.lib.Region(h.y,h.x+c.offsetWidth,h.y+c.offsetHeight,h.x);var i=e.intersect(g);if(i){b.overlap=i;return(d)?true:b.cursorIsOver}else{return false}},_onUnload:function(c,b){a.removeListener(document,"mouseup",this.handleMouseUp,this);a.removeListener(document,"mousemove",this.handleMouseMove,this);a.removeListener(window,"resize",this._onResize,this);Ext.dd.DragDropMgr.unregAll()},unregAll:function(){if(this.dragCurrent){this.stopDrag();this.dragCurrent=null}this._execOnAll("unreg",[]);for(var b in this.elementCache){delete this.elementCache[b]}this.elementCache={};this.ids={}},elementCache:{},getElWrapper:function(c){var b=this.elementCache[c];if(!b||!b.el){b=this.elementCache[c]=new this.ElementWrapper(Ext.getDom(c))}return b},getElement:function(b){return Ext.getDom(b)},getCss:function(c){var b=Ext.getDom(c);return(b)?b.style:null},ElementWrapper:function(b){this.el=b||null;this.id=this.el&&b.id;this.css=this.el&&b.style},getPosX:function(b){return Ext.lib.Dom.getX(b)},getPosY:function(b){return Ext.lib.Dom.getY(b)},swapNode:function(d,b){if(d.swapNode){d.swapNode(b)}else{var e=b.parentNode;var c=b.nextSibling;if(c==d){e.insertBefore(d,b)}else{if(b==d.nextSibling){e.insertBefore(b,d)}else{d.parentNode.replaceChild(b,d);e.insertBefore(d,c)}}}},getScroll:function(){var d,b,e=document.documentElement,c=document.body;if(e&&(e.scrollTop||e.scrollLeft)){d=e.scrollTop;b=e.scrollLeft}else{if(c){d=c.scrollTop;b=c.scrollLeft}else{}}return{top:d,left:b}},getStyle:function(c,b){return Ext.fly(c).getStyle(b)},getScrollTop:function(){return this.getScroll().top},getScrollLeft:function(){return this.getScroll().left},moveToEl:function(b,d){var c=Ext.lib.Dom.getXY(d);Ext.lib.Dom.setXY(b,c)},numericSort:function(d,c){return(d-c)},_timeoutCount:0,_addListeners:function(){var b=Ext.dd.DDM;if(Ext.lib.Event&&document){b._onLoad()}else{if(b._timeoutCount>2000){}else{setTimeout(b._addListeners,10);if(document&&document.body){b._timeoutCount+=1}}}},handleWasClicked:function(b,d){if(this.isHandle(d,b.id)){return true}else{var c=b.parentNode;while(c){if(this.isHandle(d,c.id)){return true}else{c=c.parentNode}}}return false}}}();Ext.dd.DDM=Ext.dd.DragDropMgr;Ext.dd.DDM._addListeners()}Ext.dd.DD=function(c,a,b){if(c){this.init(c,a,b)}};Ext.extend(Ext.dd.DD,Ext.dd.DragDrop,{scroll:true,autoOffset:function(c,b){var a=c-this.startPageX;var d=b-this.startPageY;this.setDelta(a,d)},setDelta:function(b,a){this.deltaX=b;this.deltaY=a},setDragElPos:function(c,b){var a=this.getDragEl();this.alignElWithMouse(a,c,b)},alignElWithMouse:function(c,h,g){var e=this.getTargetCoord(h,g);var b=c.dom?c:Ext.fly(c,"_dd");if(!this.deltaSetXY){var i=[e.x,e.y];b.setXY(i);var d=b.getLeft(true);var a=b.getTop(true);this.deltaSetXY=[d-e.x,a-e.y]}else{b.setLeftTop(e.x+this.deltaSetXY[0],e.y+this.deltaSetXY[1])}this.cachePosition(e.x,e.y);this.autoScroll(e.x,e.y,c.offsetHeight,c.offsetWidth);return e},cachePosition:function(b,a){if(b){this.lastPageX=b;this.lastPageY=a}else{var c=Ext.lib.Dom.getXY(this.getEl());this.lastPageX=c[0];this.lastPageY=c[1]}},autoScroll:function(l,k,e,m){if(this.scroll){var n=Ext.lib.Dom.getViewHeight();var b=Ext.lib.Dom.getViewWidth();var p=this.DDM.getScrollTop();var d=this.DDM.getScrollLeft();var j=e+k;var o=m+l;var i=(n+p-k-this.deltaY);var g=(b+d-l-this.deltaX);var c=40;var a=(document.all)?80:30;if(j>n&&i<c){window.scrollTo(d,p+a)}if(k<p&&p>0&&k-p<c){window.scrollTo(d,p-a)}if(o>b&&g<c){window.scrollTo(d+a,p)}if(l<d&&d>0&&l-d<c){window.scrollTo(d-a,p)}}},getTargetCoord:function(c,b){var a=c-this.deltaX;var d=b-this.deltaY;if(this.constrainX){if(a<this.minX){a=this.minX}if(a>this.maxX){a=this.maxX}}if(this.constrainY){if(d<this.minY){d=this.minY}if(d>this.maxY){d=this.maxY}}a=this.getTick(a,this.xTicks);d=this.getTick(d,this.yTicks);return{x:a,y:d}},applyConfig:function(){Ext.dd.DD.superclass.applyConfig.call(this);this.scroll=(this.config.scroll!==false)},b4MouseDown:function(a){this.autoOffset(a.getPageX(),a.getPageY())},b4Drag:function(a){this.setDragElPos(a.getPageX(),a.getPageY())},toString:function(){return("DD "+this.id)}});Ext.dd.DDProxy=function(c,a,b){if(c){this.init(c,a,b);this.initFrame()}};Ext.dd.DDProxy.dragElId="ygddfdiv";Ext.extend(Ext.dd.DDProxy,Ext.dd.DD,{resizeFrame:true,centerFrame:false,createFrame:function(){var b=this;var a=document.body;if(!a||!a.firstChild){setTimeout(function(){b.createFrame()},50);return}var d=this.getDragEl();if(!d){d=document.createElement("div");d.id=this.dragElId;var c=d.style;c.position="absolute";c.visibility="hidden";c.cursor="move";c.border="2px solid #aaa";c.zIndex=999;a.insertBefore(d,a.firstChild)}},initFrame:function(){this.createFrame()},applyConfig:function(){Ext.dd.DDProxy.superclass.applyConfig.call(this);this.resizeFrame=(this.config.resizeFrame!==false);this.centerFrame=(this.config.centerFrame);this.setDragElId(this.config.dragElId||Ext.dd.DDProxy.dragElId)},showFrame:function(e,d){var c=this.getEl();var a=this.getDragEl();var b=a.style;this._resizeProxy();if(this.centerFrame){this.setDelta(Math.round(parseInt(b.width,10)/2),Math.round(parseInt(b.height,10)/2))}this.setDragElPos(e,d);Ext.fly(a).show()},_resizeProxy:function(){if(this.resizeFrame){var a=this.getEl();Ext.fly(this.getDragEl()).setSize(a.offsetWidth,a.offsetHeight)}},b4MouseDown:function(b){var a=b.getPageX();var c=b.getPageY();this.autoOffset(a,c);this.setDragElPos(a,c)},b4StartDrag:function(a,b){this.showFrame(a,b)},b4EndDrag:function(a){Ext.fly(this.getDragEl()).hide()},endDrag:function(c){var b=this.getEl();var a=this.getDragEl();a.style.visibility="";this.beforeMove();b.style.visibility="hidden";Ext.dd.DDM.moveToEl(b,a);a.style.visibility="hidden";b.style.visibility="";this.afterDrag()},beforeMove:function(){},afterDrag:function(){},toString:function(){return("DDProxy "+this.id)}});Ext.dd.DDTarget=function(c,a,b){if(c){this.initTarget(c,a,b)}};Ext.extend(Ext.dd.DDTarget,Ext.dd.DragDrop,{getDragEl:Ext.emptyFn,isValidHandleChild:Ext.emptyFn,startDrag:Ext.emptyFn,endDrag:Ext.emptyFn,onDrag:Ext.emptyFn,onDragDrop:Ext.emptyFn,onDragEnter:Ext.emptyFn,onDragOut:Ext.emptyFn,onDragOver:Ext.emptyFn,onInvalidDrop:Ext.emptyFn,onMouseDown:Ext.emptyFn,onMouseUp:Ext.emptyFn,setXConstraint:Ext.emptyFn,setYConstraint:Ext.emptyFn,resetConstraints:Ext.emptyFn,clearConstraints:Ext.emptyFn,clearTicks:Ext.emptyFn,setInitPosition:Ext.emptyFn,setDragElId:Ext.emptyFn,setHandleElId:Ext.emptyFn,setOuterHandleElId:Ext.emptyFn,addInvalidHandleClass:Ext.emptyFn,addInvalidHandleId:Ext.emptyFn,addInvalidHandleType:Ext.emptyFn,removeInvalidHandleClass:Ext.emptyFn,removeInvalidHandleId:Ext.emptyFn,removeInvalidHandleType:Ext.emptyFn,toString:function(){return("DDTarget "+this.id)}});Ext.dd.DragTracker=Ext.extend(Ext.util.Observable,{active:false,tolerance:5,autoStart:false,constructor:function(a){Ext.apply(this,a);this.addEvents("mousedown","mouseup","mousemove","dragstart","dragend","drag");this.dragRegion=new Ext.lib.Region(0,0,0,0);if(this.el){this.initEl(this.el)}Ext.dd.DragTracker.superclass.constructor.call(this,a)},initEl:function(a){this.el=Ext.get(a);a.on("mousedown",this.onMouseDown,this,this.delegate?{delegate:this.delegate}:undefined)},destroy:function(){this.el.un("mousedown",this.onMouseDown,this);delete this.el},onMouseDown:function(b,a){if(this.fireEvent("mousedown",this,b)!==false&&this.onBeforeStart(b)!==false){this.startXY=this.lastXY=b.getXY();this.dragTarget=this.delegate?a:this.el.dom;if(this.preventDefault!==false){b.preventDefault()}Ext.getDoc().on({scope:this,mouseup:this.onMouseUp,mousemove:this.onMouseMove,selectstart:this.stopSelect});if(this.autoStart){this.timer=this.triggerStart.defer(this.autoStart===true?1000:this.autoStart,this,[b])}}},onMouseMove:function(g,d){var b=Ext.isIE6||Ext.isIE7||Ext.isIE8;if(this.active&&b&&!g.browserEvent.button){g.preventDefault();this.onMouseUp(g);return}g.preventDefault();var c=g.getXY(),a=this.startXY;this.lastXY=c;if(!this.active){if(Math.abs(a[0]-c[0])>this.tolerance||Math.abs(a[1]-c[1])>this.tolerance){this.triggerStart(g)}else{return}}this.fireEvent("mousemove",this,g);this.onDrag(g);this.fireEvent("drag",this,g)},onMouseUp:function(c){var b=Ext.getDoc(),a=this.active;b.un("mousemove",this.onMouseMove,this);b.un("mouseup",this.onMouseUp,this);b.un("selectstart",this.stopSelect,this);c.preventDefault();this.clearStart();this.active=false;delete this.elRegion;this.fireEvent("mouseup",this,c);if(a){this.onEnd(c);this.fireEvent("dragend",this,c)}},triggerStart:function(a){this.clearStart();this.active=true;this.onStart(a);this.fireEvent("dragstart",this,a)},clearStart:function(){if(this.timer){clearTimeout(this.timer);delete this.timer}},stopSelect:function(a){a.stopEvent();return false},onBeforeStart:function(a){},onStart:function(a){},onDrag:function(a){},onEnd:function(a){},getDragTarget:function(){return this.dragTarget},getDragCt:function(){return this.el},getXY:function(a){return a?this.constrainModes[a].call(this,this.lastXY):this.lastXY},getOffset:function(c){var b=this.getXY(c),a=this.startXY;return[a[0]-b[0],a[1]-b[1]]},constrainModes:{point:function(b){if(!this.elRegion){this.elRegion=this.getDragCt().getRegion()}var a=this.dragRegion;a.left=b[0];a.top=b[1];a.right=b[0];a.bottom=b[1];a.constrainTo(this.elRegion);return[a.left,a.top]}}});Ext.dd.ScrollManager=function(){var c=Ext.dd.DragDropMgr;var e={};var b=null;var i={};var h=function(l){b=null;a()};var j=function(){if(c.dragCurrent){c.refreshCache(c.dragCurrent.groups)}};var d=function(){if(c.dragCurrent){var l=Ext.dd.ScrollManager;var m=i.el.ddScrollConfig?i.el.ddScrollConfig.increment:l.increment;if(!l.animate){if(i.el.scroll(i.dir,m)){j()}}else{i.el.scroll(i.dir,m,true,l.animDuration,j)}}};var a=function(){if(i.id){clearInterval(i.id)}i.id=0;i.el=null;i.dir=""};var g=function(m,l){a();i.el=m;i.dir=l;var o=m.ddScrollConfig?m.ddScrollConfig.ddGroup:undefined,n=(m.ddScrollConfig&&m.ddScrollConfig.frequency)?m.ddScrollConfig.frequency:Ext.dd.ScrollManager.frequency;if(o===undefined||c.dragCurrent.ddGroup==o){i.id=setInterval(d,n)}};var k=function(o,q){if(q||!c.dragCurrent){return}var s=Ext.dd.ScrollManager;if(!b||b!=c.dragCurrent){b=c.dragCurrent;s.refreshCache()}var t=Ext.lib.Event.getXY(o);var u=new Ext.lib.Point(t[0],t[1]);for(var m in e){var n=e[m],l=n._region;var p=n.ddScrollConfig?n.ddScrollConfig:s;if(l&&l.contains(u)&&n.isScrollable()){if(l.bottom-u.y<=p.vthresh){if(i.el!=n){g(n,"down")}return}else{if(l.right-u.x<=p.hthresh){if(i.el!=n){g(n,"left")}return}else{if(u.y-l.top<=p.vthresh){if(i.el!=n){g(n,"up")}return}else{if(u.x-l.left<=p.hthresh){if(i.el!=n){g(n,"right")}return}}}}}}a()};c.fireEvents=c.fireEvents.createSequence(k,c);c.stopDrag=c.stopDrag.createSequence(h,c);return{register:function(n){if(Ext.isArray(n)){for(var m=0,l=n.length;m<l;m++){this.register(n[m])}}else{n=Ext.get(n);e[n.id]=n}},unregister:function(n){if(Ext.isArray(n)){for(var m=0,l=n.length;m<l;m++){this.unregister(n[m])}}else{n=Ext.get(n);delete e[n.id]}},vthresh:25,hthresh:25,increment:100,frequency:500,animate:true,animDuration:0.4,ddGroup:undefined,refreshCache:function(){for(var l in e){if(typeof e[l]=="object"){e[l]._region=e[l].getRegion()}}}}}();Ext.dd.Registry=function(){var d={};var b={};var a=0;var c=function(g,e){if(typeof g=="string"){return g}var h=g.id;if(!h&&e!==false){h="extdd-"+(++a);g.id=h}return h};return{register:function(j,k){k=k||{};if(typeof j=="string"){j=document.getElementById(j)}k.ddel=j;d[c(j)]=k;if(k.isHandle!==false){b[k.ddel.id]=k}if(k.handles){var h=k.handles;for(var g=0,e=h.length;g<e;g++){b[c(h[g])]=k}}},unregister:function(j){var l=c(j,false);var k=d[l];if(k){delete d[l];if(k.handles){var h=k.handles;for(var g=0,e=h.length;g<e;g++){delete b[c(h[g],false)]}}}},getHandle:function(e){if(typeof e!="string"){e=e.id}return b[e]},getHandleFromEvent:function(h){var g=Ext.lib.Event.getTarget(h);return g?b[g.id]:null},getTarget:function(e){if(typeof e!="string"){e=e.id}return d[e]},getTargetFromEvent:function(h){var g=Ext.lib.Event.getTarget(h);return g?d[g.id]||b[g.id]:null}}}();Ext.dd.StatusProxy=function(a){Ext.apply(this,a);this.id=this.id||Ext.id();this.el=new Ext.Layer({dh:{id:this.id,tag:"div",cls:"x-dd-drag-proxy "+this.dropNotAllowed,children:[{tag:"div",cls:"x-dd-drop-icon"},{tag:"div",cls:"x-dd-drag-ghost"}]},shadow:!a||a.shadow!==false});this.ghost=Ext.get(this.el.dom.childNodes[1]);this.dropStatus=this.dropNotAllowed};Ext.dd.StatusProxy.prototype={dropAllowed:"x-dd-drop-ok",dropNotAllowed:"x-dd-drop-nodrop",setStatus:function(a){a=a||this.dropNotAllowed;if(this.dropStatus!=a){this.el.replaceClass(this.dropStatus,a);this.dropStatus=a}},reset:function(a){this.el.dom.className="x-dd-drag-proxy "+this.dropNotAllowed;this.dropStatus=this.dropNotAllowed;if(a){this.ghost.update("")}},update:function(a){if(typeof a=="string"){this.ghost.update(a)}else{this.ghost.update("");a.style.margin="0";this.ghost.dom.appendChild(a)}var b=this.ghost.dom.firstChild;if(b){Ext.fly(b).setStyle("float","none")}},getEl:function(){return this.el},getGhost:function(){return this.ghost},hide:function(a){this.el.hide();if(a){this.reset(true)}},stop:function(){if(this.anim&&this.anim.isAnimated&&this.anim.isAnimated()){this.anim.stop()}},show:function(){this.el.show()},sync:function(){this.el.sync()},repair:function(b,c,a){this.callback=c;this.scope=a;if(b&&this.animRepair!==false){this.el.addClass("x-dd-drag-repair");this.el.hideUnders(true);this.anim=this.el.shift({duration:this.repairDuration||0.5,easing:"easeOut",xy:b,stopFx:true,callback:this.afterRepair,scope:this})}else{this.afterRepair()}},afterRepair:function(){this.hide(true);if(typeof this.callback=="function"){this.callback.call(this.scope||this)}this.callback=null;this.scope=null},destroy:function(){Ext.destroy(this.ghost,this.el)}};Ext.dd.DragSource=function(b,a){this.el=Ext.get(b);if(!this.dragData){this.dragData={}}Ext.apply(this,a);if(!this.proxy){this.proxy=new Ext.dd.StatusProxy()}Ext.dd.DragSource.superclass.constructor.call(this,this.el.dom,this.ddGroup||this.group,{dragElId:this.proxy.id,resizeFrame:false,isTarget:false,scroll:this.scroll===true});this.dragging=false};Ext.extend(Ext.dd.DragSource,Ext.dd.DDProxy,{dropAllowed:"x-dd-drop-ok",dropNotAllowed:"x-dd-drop-nodrop",getDragData:function(a){return this.dragData},onDragEnter:function(c,d){var b=Ext.dd.DragDropMgr.getDDById(d);this.cachedTarget=b;if(this.beforeDragEnter(b,c,d)!==false){if(b.isNotifyTarget){var a=b.notifyEnter(this,c,this.dragData);this.proxy.setStatus(a)}else{this.proxy.setStatus(this.dropAllowed)}if(this.afterDragEnter){this.afterDragEnter(b,c,d)}}},beforeDragEnter:function(b,a,c){return true},alignElWithMouse:function(){Ext.dd.DragSource.superclass.alignElWithMouse.apply(this,arguments);this.proxy.sync()},onDragOver:function(c,d){var b=this.cachedTarget||Ext.dd.DragDropMgr.getDDById(d);if(this.beforeDragOver(b,c,d)!==false){if(b.isNotifyTarget){var a=b.notifyOver(this,c,this.dragData);this.proxy.setStatus(a)}if(this.afterDragOver){this.afterDragOver(b,c,d)}}},beforeDragOver:function(b,a,c){return true},onDragOut:function(b,c){var a=this.cachedTarget||Ext.dd.DragDropMgr.getDDById(c);if(this.beforeDragOut(a,b,c)!==false){if(a.isNotifyTarget){a.notifyOut(this,b,this.dragData)}this.proxy.reset();if(this.afterDragOut){this.afterDragOut(a,b,c)}}this.cachedTarget=null},beforeDragOut:function(b,a,c){return true},onDragDrop:function(b,c){var a=this.cachedTarget||Ext.dd.DragDropMgr.getDDById(c);if(this.beforeDragDrop(a,b,c)!==false){if(a.isNotifyTarget){if(a.notifyDrop(this,b,this.dragData)){this.onValidDrop(a,b,c)}else{this.onInvalidDrop(a,b,c)}}else{this.onValidDrop(a,b,c)}if(this.afterDragDrop){this.afterDragDrop(a,b,c)}}delete this.cachedTarget},beforeDragDrop:function(b,a,c){return true},onValidDrop:function(b,a,c){this.hideProxy();if(this.afterValidDrop){this.afterValidDrop(b,a,c)}},getRepairXY:function(b,a){return this.el.getXY()},onInvalidDrop:function(b,a,c){this.beforeInvalidDrop(b,a,c);if(this.cachedTarget){if(this.cachedTarget.isNotifyTarget){this.cachedTarget.notifyOut(this,a,this.dragData)}this.cacheTarget=null}this.proxy.repair(this.getRepairXY(a,this.dragData),this.afterRepair,this);if(this.afterInvalidDrop){this.afterInvalidDrop(a,c)}},afterRepair:function(){if(Ext.enableFx){this.el.highlight(this.hlColor||"c3daf9")}this.dragging=false},beforeInvalidDrop:function(b,a,c){return true},handleMouseDown:function(b){if(this.dragging){return}var a=this.getDragData(b);if(a&&this.onBeforeDrag(a,b)!==false){this.dragData=a;this.proxy.stop();Ext.dd.DragSource.superclass.handleMouseDown.apply(this,arguments)}},onBeforeDrag:function(a,b){return true},onStartDrag:Ext.emptyFn,startDrag:function(a,b){this.proxy.reset();this.dragging=true;this.proxy.update("");this.onInitDrag(a,b);this.proxy.show()},onInitDrag:function(a,c){var b=this.el.dom.cloneNode(true);b.id=Ext.id();this.proxy.update(b);this.onStartDrag(a,c);return true},getProxy:function(){return this.proxy},hideProxy:function(){this.proxy.hide();this.proxy.reset(true);this.dragging=false},triggerCacheRefresh:function(){Ext.dd.DDM.refreshCache(this.groups)},b4EndDrag:function(a){},endDrag:function(a){this.onEndDrag(this.dragData,a)},onEndDrag:function(a,b){},autoOffset:function(a,b){this.setDelta(-12,-20)},destroy:function(){Ext.dd.DragSource.superclass.destroy.call(this);Ext.destroy(this.proxy)}});Ext.dd.DropTarget=Ext.extend(Ext.dd.DDTarget,{constructor:function(b,a){this.el=Ext.get(b);Ext.apply(this,a);if(this.containerScroll){Ext.dd.ScrollManager.register(this.el)}Ext.dd.DropTarget.superclass.constructor.call(this,this.el.dom,this.ddGroup||this.group,{isTarget:true})},dropAllowed:"x-dd-drop-ok",dropNotAllowed:"x-dd-drop-nodrop",isTarget:true,isNotifyTarget:true,notifyEnter:function(a,c,b){if(this.overClass){this.el.addClass(this.overClass)}return this.dropAllowed},notifyOver:function(a,c,b){return this.dropAllowed},notifyOut:function(a,c,b){if(this.overClass){this.el.removeClass(this.overClass)}},notifyDrop:function(a,c,b){return false},destroy:function(){Ext.dd.DropTarget.superclass.destroy.call(this);if(this.containerScroll){Ext.dd.ScrollManager.unregister(this.el)}}});Ext.dd.DragZone=Ext.extend(Ext.dd.DragSource,{constructor:function(b,a){Ext.dd.DragZone.superclass.constructor.call(this,b,a);if(this.containerScroll){Ext.dd.ScrollManager.register(this.el)}},getDragData:function(a){return Ext.dd.Registry.getHandleFromEvent(a)},onInitDrag:function(a,b){this.proxy.update(this.dragData.ddel.cloneNode(true));this.onStartDrag(a,b);return true},afterRepair:function(){if(Ext.enableFx){Ext.Element.fly(this.dragData.ddel).highlight(this.hlColor||"c3daf9")}this.dragging=false},getRepairXY:function(a){return Ext.Element.fly(this.dragData.ddel).getXY()},destroy:function(){Ext.dd.DragZone.superclass.destroy.call(this);if(this.containerScroll){Ext.dd.ScrollManager.unregister(this.el)}}});Ext.dd.DropZone=function(b,a){Ext.dd.DropZone.superclass.constructor.call(this,b,a)};Ext.extend(Ext.dd.DropZone,Ext.dd.DropTarget,{getTargetFromEvent:function(a){return Ext.dd.Registry.getTargetFromEvent(a)},onNodeEnter:function(d,a,c,b){},onNodeOver:function(d,a,c,b){return this.dropAllowed},onNodeOut:function(d,a,c,b){},onNodeDrop:function(d,a,c,b){return false},onContainerOver:function(a,c,b){return this.dropNotAllowed},onContainerDrop:function(a,c,b){return false},notifyEnter:function(a,c,b){return this.dropNotAllowed},notifyOver:function(a,c,b){var d=this.getTargetFromEvent(c);if(!d){if(this.lastOverNode){this.onNodeOut(this.lastOverNode,a,c,b);this.lastOverNode=null}return this.onContainerOver(a,c,b)}if(this.lastOverNode!=d){if(this.lastOverNode){this.onNodeOut(this.lastOverNode,a,c,b)}this.onNodeEnter(d,a,c,b);this.lastOverNode=d}return this.onNodeOver(d,a,c,b)},notifyOut:function(a,c,b){if(this.lastOverNode){this.onNodeOut(this.lastOverNode,a,c,b);this.lastOverNode=null}},notifyDrop:function(a,c,b){if(this.lastOverNode){this.onNodeOut(this.lastOverNode,a,c,b);this.lastOverNode=null}var d=this.getTargetFromEvent(c);return d?this.onNodeDrop(d,a,c,b):this.onContainerDrop(a,c,b)},triggerCacheRefresh:function(){Ext.dd.DDM.refreshCache(this.groups)}});Ext.Element.addMethods({initDD:function(c,b,d){var a=new Ext.dd.DD(Ext.id(this.dom),c,b);return Ext.apply(a,d)},initDDProxy:function(c,b,d){var a=new Ext.dd.DDProxy(Ext.id(this.dom),c,b);return Ext.apply(a,d)},initDDTarget:function(c,b,d){var a=new Ext.dd.DDTarget(Ext.id(this.dom),c,b);return Ext.apply(a,d)}});Ext.data.Api=(function(){var a={};return{actions:{create:"create",read:"read",update:"update",destroy:"destroy"},restActions:{create:"POST",read:"GET",update:"PUT",destroy:"DELETE"},isAction:function(b){return(Ext.data.Api.actions[b])?true:false},getVerb:function(b){if(a[b]){return a[b]}for(var c in this.actions){if(this.actions[c]===b){a[b]=c;break}}return(a[b]!==undefined)?a[b]:null},isValid:function(b){var e=[];var d=this.actions;for(var c in b){if(!(c in d)){e.push(c)}}return(!e.length)?true:e},hasUniqueUrl:function(c,g){var b=(c.api[g])?c.api[g].url:null;var e=true;for(var d in c.api){if((e=(d===g)?true:(c.api[d].url!=b)?true:false)===false){break}}return e},prepare:function(b){if(!b.api){b.api={}}for(var d in this.actions){var c=this.actions[d];b.api[c]=b.api[c]||b.url||b.directFn;if(typeof(b.api[c])=="string"){b.api[c]={url:b.api[c],method:(b.restful===true)?Ext.data.Api.restActions[c]:undefined}}}},restify:function(b){b.restful=true;for(var c in this.restActions){b.api[this.actions[c]].method||(b.api[this.actions[c]].method=this.restActions[c])}b.onWrite=b.onWrite.createInterceptor(function(i,j,g,e){var d=j.reader;var h=new Ext.data.Response({action:i,raw:g});switch(g.status){case 200:return true;break;case 201:if(Ext.isEmpty(h.raw.responseText)){h.success=true}else{return true}break;case 204:h.success=true;h.data=null;break;default:return true;break}if(h.success===true){this.fireEvent("write",this,i,h.data,h,e,j.request.arg)}else{this.fireEvent("exception",this,"remote",i,j,h,e)}j.request.callback.call(j.request.scope,h.data,h,h.success);return false},b)}}})();Ext.data.Response=function(b,a){Ext.apply(this,b,{raw:a})};Ext.data.Response.prototype={message:null,success:false,status:null,root:null,raw:null,getMessage:function(){return this.message},getSuccess:function(){return this.success},getStatus:function(){return this.status},getRoot:function(){return this.root},getRawResponse:function(){return this.raw}};Ext.data.Api.Error=Ext.extend(Ext.Error,{constructor:function(b,a){this.arg=a;Ext.Error.call(this,b)},name:"Ext.data.Api"});Ext.apply(Ext.data.Api.Error.prototype,{lang:{"action-url-undefined":"No fallback url defined for this action. When defining a DataProxy api, please be sure to define an url for each CRUD action in Ext.data.Api.actions or define a default url in addition to your api-configuration.",invalid:"received an invalid API-configuration. Please ensure your proxy API-configuration contains only the actions defined in Ext.data.Api.actions","invalid-url":"Invalid url. Please review your proxy configuration.",execute:'Attempted to execute an unknown action. Valid API actions are defined in Ext.data.Api.actions"'}});Ext.data.SortTypes={none:function(a){return a},stripTagsRE:/<\/?[^>]+>/gi,asText:function(a){return String(a).replace(this.stripTagsRE,"")},asUCText:function(a){return String(a).toUpperCase().replace(this.stripTagsRE,"")},asUCString:function(a){return String(a).toUpperCase()},asDate:function(a){if(!a){return 0}if(Ext.isDate(a)){return a.getTime()}return Date.parse(String(a))},asFloat:function(a){var b=parseFloat(String(a).replace(/,/g,""));return isNaN(b)?0:b},asInt:function(a){var b=parseInt(String(a).replace(/,/g,""),10);return isNaN(b)?0:b}};Ext.data.Record=function(a,b){this.id=(b||b===0)?b:Ext.data.Record.id(this);this.data=a||{}};Ext.data.Record.create=function(e){var c=Ext.extend(Ext.data.Record,{});var d=c.prototype;d.fields=new Ext.util.MixedCollection(false,function(g){return g.name});for(var b=0,a=e.length;b<a;b++){d.fields.add(new Ext.data.Field(e[b]))}c.getField=function(g){return d.fields.get(g)};return c};Ext.data.Record.PREFIX="ext-record";Ext.data.Record.AUTO_ID=1;Ext.data.Record.EDIT="edit";Ext.data.Record.REJECT="reject";Ext.data.Record.COMMIT="commit";Ext.data.Record.id=function(a){a.phantom=true;return[Ext.data.Record.PREFIX,"-",Ext.data.Record.AUTO_ID++].join("")};Ext.data.Record.prototype={dirty:false,editing:false,error:null,modified:null,phantom:false,join:function(a){this.store=a},set:function(a,c){var b=Ext.isPrimitive(c)?String:Ext.encode;if(b(this.data[a])==b(c)){return}this.dirty=true;if(!this.modified){this.modified={}}if(this.modified[a]===undefined){this.modified[a]=this.data[a]}this.data[a]=c;if(!this.editing){this.afterEdit()}},afterEdit:function(){if(this.store!=undefined&&typeof this.store.afterEdit=="function"){this.store.afterEdit(this)}},afterReject:function(){if(this.store){this.store.afterReject(this)}},afterCommit:function(){if(this.store){this.store.afterCommit(this)}},get:function(a){return this.data[a]},beginEdit:function(){this.editing=true;this.modified=this.modified||{}},cancelEdit:function(){this.editing=false;delete this.modified},endEdit:function(){this.editing=false;if(this.dirty){this.afterEdit()}},reject:function(b){var a=this.modified;for(var c in a){if(typeof a[c]!="function"){this.data[c]=a[c]}}this.dirty=false;delete this.modified;this.editing=false;if(b!==true){this.afterReject()}},commit:function(a){this.dirty=false;delete this.modified;this.editing=false;if(a!==true){this.afterCommit()}},getChanges:function(){var a=this.modified,b={};for(var c in a){if(a.hasOwnProperty(c)){b[c]=this.data[c]}}return b},hasError:function(){return this.error!==null},clearError:function(){this.error=null},copy:function(a){return new this.constructor(Ext.apply({},this.data),a||this.id)},isModified:function(a){return !!(this.modified&&this.modified.hasOwnProperty(a))},isValid:function(){return this.fields.find(function(a){return(a.allowBlank===false&&Ext.isEmpty(this.data[a.name]))?true:false},this)?false:true},markDirty:function(){this.dirty=true;if(!this.modified){this.modified={}}this.fields.each(function(a){this.modified[a.name]=this.data[a.name]},this)}};Ext.StoreMgr=Ext.apply(new Ext.util.MixedCollection(),{register:function(){for(var a=0,b;(b=arguments[a]);a++){this.add(b)}},unregister:function(){for(var a=0,b;(b=arguments[a]);a++){this.remove(this.lookup(b))}},lookup:function(e){if(Ext.isArray(e)){var b=["field1"],d=!Ext.isArray(e[0]);if(!d){for(var c=2,a=e[0].length;c<=a;++c){b.push("field"+c)}}return new Ext.data.ArrayStore({fields:b,data:e,expandData:d,autoDestroy:true,autoCreated:true})}return Ext.isObject(e)?(e.events?e:Ext.create(e,"store")):this.get(e)},getKey:function(a){return a.storeId}});Ext.data.Store=Ext.extend(Ext.util.Observable,{writer:undefined,remoteSort:false,autoDestroy:false,pruneModifiedRecords:false,lastOptions:null,autoSave:true,batch:true,restful:false,paramNames:undefined,defaultParamNames:{start:"start",limit:"limit",sort:"sort",dir:"dir"},isDestroyed:false,hasMultiSort:false,batchKey:"_ext_batch_",constructor:function(a){this.data=new Ext.util.MixedCollection(false);this.data.getKey=function(b){return b.id};this.removed=[];if(a&&a.data){this.inlineData=a.data;delete a.data}Ext.apply(this,a);this.baseParams=Ext.isObject(this.baseParams)?this.baseParams:{};this.paramNames=Ext.applyIf(this.paramNames||{},this.defaultParamNames);if((this.url||this.api)&&!this.proxy){this.proxy=new Ext.data.HttpProxy({url:this.url,api:this.api})}if(this.restful===true&&this.proxy){this.batch=false;Ext.data.Api.restify(this.proxy)}if(this.reader){if(!this.recordType){this.recordType=this.reader.recordType}if(this.reader.onMetaChange){this.reader.onMetaChange=this.reader.onMetaChange.createSequence(this.onMetaChange,this)}if(this.writer){if(this.writer instanceof (Ext.data.DataWriter)===false){this.writer=this.buildWriter(this.writer)}this.writer.meta=this.reader.meta;this.pruneModifiedRecords=true}}if(this.recordType){this.fields=this.recordType.prototype.fields}this.modified=[];this.addEvents("datachanged","metachange","add","remove","update","clear","exception","beforeload","load","loadexception","beforewrite","write","beforesave","save");if(this.proxy){this.relayEvents(this.proxy,["loadexception","exception"])}if(this.writer){this.on({scope:this,add:this.createRecords,remove:this.destroyRecord,update:this.updateRecord,clear:this.onClear})}this.sortToggle={};if(this.sortField){this.setDefaultSort(this.sortField,this.sortDir)}else{if(this.sortInfo){this.setDefaultSort(this.sortInfo.field,this.sortInfo.direction)}}Ext.data.Store.superclass.constructor.call(this);if(this.id){this.storeId=this.id;delete this.id}if(this.storeId){Ext.StoreMgr.register(this)}if(this.inlineData){this.loadData(this.inlineData);delete this.inlineData}else{if(this.autoLoad){this.load.defer(10,this,[typeof this.autoLoad=="object"?this.autoLoad:undefined])}}this.batchCounter=0;this.batches={}},buildWriter:function(b){var a=undefined,c=(b.format||"json").toLowerCase();switch(c){case"json":a=Ext.data.JsonWriter;break;case"xml":a=Ext.data.XmlWriter;break;default:a=Ext.data.JsonWriter}return new a(b)},destroy:function(){if(!this.isDestroyed){if(this.storeId){Ext.StoreMgr.unregister(this)}this.clearData();this.data=null;Ext.destroy(this.proxy);this.reader=this.writer=null;this.purgeListeners();this.isDestroyed=true}},add:function(c){var e,a,b,d;c=[].concat(c);if(c.length<1){return}for(e=0,a=c.length;e<a;e++){b=c[e];b.join(this);if(b.dirty||b.phantom){this.modified.push(b)}}d=this.data.length;this.data.addAll(c);if(this.snapshot){this.snapshot.addAll(c)}this.fireEvent("add",this,c,d)},addSorted:function(a){var b=this.findInsertIndex(a);this.insert(b,a)},doUpdate:function(a){var b=a.id;this.getById(b).join(null);this.data.replace(b,a);if(this.snapshot){this.snapshot.replace(b,a)}a.join(this);this.fireEvent("update",this,a,Ext.data.Record.COMMIT)},remove:function(a){if(Ext.isArray(a)){Ext.each(a,function(c){this.remove(c)},this);return}var b=this.data.indexOf(a);if(b>-1){a.join(null);this.data.removeAt(b)}if(this.pruneModifiedRecords){this.modified.remove(a)}if(this.snapshot){this.snapshot.remove(a)}if(b>-1){this.fireEvent("remove",this,a,b)}},removeAt:function(a){this.remove(this.getAt(a))},removeAll:function(b){var a=[];this.each(function(c){a.push(c)});this.clearData();if(this.snapshot){this.snapshot.clear()}if(this.pruneModifiedRecords){this.modified=[]}if(b!==true){this.fireEvent("clear",this,a)}},onClear:function(b,a){Ext.each(a,function(d,c){this.destroyRecord(this,d,c)},this)},insert:function(d,c){var e,a,b;c=[].concat(c);for(e=0,a=c.length;e<a;e++){b=c[e];this.data.insert(d+e,b);b.join(this);if(b.dirty||b.phantom){this.modified.push(b)}}if(this.snapshot){this.snapshot.addAll(c)}this.fireEvent("add",this,c,d)},indexOf:function(a){return this.data.indexOf(a)},indexOfId:function(a){return this.data.indexOfKey(a)},getById:function(a){return(this.snapshot||this.data).key(a)},getAt:function(a){return this.data.itemAt(a)},getRange:function(b,a){return this.data.getRange(b,a)},storeOptions:function(a){a=Ext.apply({},a);delete a.callback;delete a.scope;this.lastOptions=a},clearData:function(){this.data.each(function(a){a.join(null)});this.data.clear()},load:function(b){b=Ext.apply({},b);this.storeOptions(b);if(this.sortInfo&&this.remoteSort){var a=this.paramNames;b.params=Ext.apply({},b.params);b.params[a.sort]=this.sortInfo.field;b.params[a.dir]=this.sortInfo.direction}try{return this.execute("read",null,b)}catch(c){this.handleException(c);return false}},updateRecord:function(b,a,c){if(c==Ext.data.Record.EDIT&&this.autoSave===true&&(!a.phantom||(a.phantom&&a.isValid()))){this.save()}},createRecords:function(c,b,e){var d=this.modified,h=b.length,a,g;for(g=0;g<h;g++){a=b[g];if(a.phantom&&a.isValid()){a.markDirty();if(d.indexOf(a)==-1){d.push(a)}}}if(this.autoSave===true){this.save()}},destroyRecord:function(b,a,c){if(this.modified.indexOf(a)!=-1){this.modified.remove(a)}if(!a.phantom){this.removed.push(a);a.lastIndex=c;if(this.autoSave===true){this.save()}}},execute:function(e,a,c,b){if(!Ext.data.Api.isAction(e)){throw new Ext.data.Api.Error("execute",e)}c=Ext.applyIf(c||{},{params:{}});if(b!==undefined){this.addToBatch(b)}var d=true;if(e==="read"){d=this.fireEvent("beforeload",this,c);Ext.applyIf(c.params,this.baseParams)}else{if(this.writer.listful===true&&this.restful!==true){a=(Ext.isArray(a))?a:[a]}else{if(Ext.isArray(a)&&a.length==1){a=a.shift()}}if((d=this.fireEvent("beforewrite",this,e,a,c))!==false){this.writer.apply(c.params,this.baseParams,e,a)}}if(d!==false){if(this.writer&&this.proxy.url&&!this.proxy.restful&&!Ext.data.Api.hasUniqueUrl(this.proxy,e)){c.params.xaction=e}this.proxy.request(Ext.data.Api.actions[e],a,c.params,this.reader,this.createCallback(e,a,b),this,c)}return d},save:function(){if(!this.writer){throw new Ext.data.Store.Error("writer-undefined")}var h=[],j,k,e,c={},d;if(this.removed.length){h.push(["destroy",this.removed])}var b=[].concat(this.getModifiedRecords());if(b.length){var g=[];for(d=b.length-1;d>=0;d--){if(b[d].phantom===true){var a=b.splice(d,1).shift();if(a.isValid()){g.push(a)}}else{if(!b[d].isValid()){b.splice(d,1)}}}if(g.length){h.push(["create",g])}if(b.length){h.push(["update",b])}}j=h.length;if(j){e=++this.batchCounter;for(d=0;d<j;++d){k=h[d];c[k[0]]=k[1]}if(this.fireEvent("beforesave",this,c)!==false){for(d=0;d<j;++d){k=h[d];this.doTransaction(k[0],k[1],e)}return e}}return -1},doTransaction:function(e,b,c){function g(h){try{this.execute(e,h,undefined,c)}catch(i){this.handleException(i)}}if(this.batch===false){for(var d=0,a=b.length;d<a;d++){g.call(this,b[d])}}else{g.call(this,b)}},addToBatch:function(c){var a=this.batches,d=this.batchKey+c,e=a[d];if(!e){a[d]=e={id:c,count:0,data:{}}}++e.count},removeFromBatch:function(d,h,g){var c=this.batches,e=this.batchKey+d,i=c[e],a;if(i){a=i.data[h]||[];i.data[h]=a.concat(g);if(i.count===1){g=i.data;delete c[e];this.fireEvent("save",this,d,g)}else{--i.count}}},createCallback:function(c,a,b){var d=Ext.data.Api.actions;return(c=="read")?this.loadRecords:function(g,e,h){this["on"+Ext.util.Format.capitalize(c)+"Records"](h,a,[].concat(g));if(h===true){this.fireEvent("write",this,c,g,e,a)}this.removeFromBatch(b,c,g)}},clearModified:function(a){if(Ext.isArray(a)){for(var b=a.length-1;b>=0;b--){this.modified.splice(this.modified.indexOf(a[b]),1)}}else{this.modified.splice(this.modified.indexOf(a),1)}},reMap:function(b){if(Ext.isArray(b)){for(var d=0,a=b.length;d<a;d++){this.reMap(b[d])}}else{delete this.data.map[b._phid];this.data.map[b.id]=b;var c=this.data.keys.indexOf(b._phid);this.data.keys.splice(c,1,b.id);delete b._phid}},onCreateRecords:function(d,a,b){if(d===true){try{this.reader.realize(a,b)}catch(c){this.handleException(c);if(Ext.isArray(a)){this.onCreateRecords(d,a,b)}}}},onUpdateRecords:function(d,a,b){if(d===true){try{this.reader.update(a,b)}catch(c){this.handleException(c);if(Ext.isArray(a)){this.onUpdateRecords(d,a,b)}}}},onDestroyRecords:function(e,b,d){b=(b instanceof Ext.data.Record)?[b]:[].concat(b);for(var c=0,a=b.length;c<a;c++){this.removed.splice(this.removed.indexOf(b[c]),1)}if(e===false){for(c=b.length-1;c>=0;c--){this.insert(b[c].lastIndex,b[c])}}},handleException:function(a){Ext.handleError(a)},reload:function(a){this.load(Ext.applyIf(a||{},this.lastOptions))},loadRecords:function(b,l,h){var e,g;if(this.isDestroyed===true){return}if(!b||h===false){if(h!==false){this.fireEvent("load",this,[],l)}if(l.callback){l.callback.call(l.scope||this,[],l,false,b)}return}var a=b.records,j=b.totalRecords||a.length;if(!l||l.add!==true){if(this.pruneModifiedRecords){this.modified=[]}for(e=0,g=a.length;e<g;e++){a[e].join(this)}if(this.snapshot){this.data=this.snapshot;delete this.snapshot}this.clearData();this.data.addAll(a);this.totalLength=j;this.applySort();this.fireEvent("datachanged",this)}else{var k=[],d,c=0;for(e=0,g=a.length;e<g;++e){d=a[e];if(this.indexOfId(d.id)>-1){this.doUpdate(d)}else{k.push(d);++c}}this.totalLength=Math.max(j,this.data.length+c);this.add(k)}this.fireEvent("load",this,a,l);if(l.callback){l.callback.call(l.scope||this,a,l,true)}},loadData:function(c,a){var b=this.reader.readRecords(c);this.loadRecords(b,{add:a},true)},getCount:function(){return this.data.length||0},getTotalCount:function(){return this.totalLength||0},getSortState:function(){return this.sortInfo},applySort:function(){if((this.sortInfo||this.multiSortInfo)&&!this.remoteSort){this.sortData()}},sortData:function(){var a=this.hasMultiSort?this.multiSortInfo:this.sortInfo,k=a.direction||"ASC",h=a.sorters,c=[];if(!this.hasMultiSort){h=[{direction:k,field:a.field}]}for(var d=0,b=h.length;d<b;d++){c.push(this.createSortFunction(h[d].field,h[d].direction))}if(c.length==0){return}var g=k.toUpperCase()=="DESC"?-1:1;var e=function(n,m){var l=c[0].call(this,n,m);if(c.length>1){for(var p=1,o=c.length;p<o;p++){l=l||c[p].call(this,n,m)}}return g*l};this.data.sort(k,e);if(this.snapshot&&this.snapshot!=this.data){this.snapshot.sort(k,e)}},createSortFunction:function(c,b){b=b||"ASC";var a=b.toUpperCase()=="DESC"?-1:1;var d=this.fields.get(c).sortType;return function(g,e){var i=d(g.data[c]),h=d(e.data[c]);return a*(i>h?1:(i<h?-1:0))}},setDefaultSort:function(b,a){a=a?a.toUpperCase():"ASC";this.sortInfo={field:b,direction:a};this.sortToggle[b]=a},sort:function(b,a){if(Ext.isArray(arguments[0])){return this.multiSort.call(this,b,a)}else{return this.singleSort(b,a)}},singleSort:function(g,c){var e=this.fields.get(g);if(!e){return false}var b=e.name,a=this.sortInfo||null,d=this.sortToggle?this.sortToggle[b]:null;if(!c){if(a&&a.field==b){c=(this.sortToggle[b]||"ASC").toggle("ASC","DESC")}else{c=e.sortDir}}this.sortToggle[b]=c;this.sortInfo={field:b,direction:c};this.hasMultiSort=false;if(this.remoteSort){if(!this.load(this.lastOptions)){if(d){this.sortToggle[b]=d}if(a){this.sortInfo=a}}}else{this.applySort();this.fireEvent("datachanged",this)}return true},multiSort:function(b,a){this.hasMultiSort=true;a=a||"ASC";if(this.multiSortInfo&&a==this.multiSortInfo.direction){a=a.toggle("ASC","DESC")}this.multiSortInfo={sorters:b,direction:a};if(this.remoteSort){this.singleSort(b[0].field,b[0].direction)}else{this.applySort();this.fireEvent("datachanged",this)}},each:function(b,a){this.data.each(b,a)},getModifiedRecords:function(){return this.modified},sum:function(e,g,a){var c=this.data.items,b=0;g=g||0;a=(a||a===0)?a:c.length-1;for(var d=g;d<=a;d++){b+=(c[d].data[e]||0)}return b},createFilterFn:function(d,c,e,a,b){if(Ext.isEmpty(c,false)){return false}c=this.data.createValueMatcher(c,e,a,b);return function(g){return c.test(g.data[d])}},createMultipleFilterFn:function(a){return function(b){var k=true;for(var d=0,c=a.length;d<c;d++){var h=a[d],g=h.fn,e=h.scope;k=k&&g.call(e,b)}return k}},filter:function(n,m,h,k,e){var l;if(Ext.isObject(n)){n=[n]}if(Ext.isArray(n)){var b=[];for(var g=0,d=n.length;g<d;g++){var a=n[g],c=a.fn,o=a.scope||this;if(!Ext.isFunction(c)){c=this.createFilterFn(a.property,a.value,a.anyMatch,a.caseSensitive,a.exactMatch)}b.push({fn:c,scope:o})}l=this.createMultipleFilterFn(b)}else{l=this.createFilterFn(n,m,h,k,e)}return l?this.filterBy(l):this.clearFilter()},filterBy:function(b,a){this.snapshot=this.snapshot||this.data;this.data=this.queryBy(b,a||this);this.fireEvent("datachanged",this)},clearFilter:function(a){if(this.isFiltered()){this.data=this.snapshot;delete this.snapshot;if(a!==true){this.fireEvent("datachanged",this)}}},isFiltered:function(){return !!this.snapshot&&this.snapshot!=this.data},query:function(d,c,e,a){var b=this.createFilterFn(d,c,e,a);return b?this.queryBy(b):this.data.clone()},queryBy:function(b,a){var c=this.snapshot||this.data;return c.filterBy(b,a||this)},find:function(d,c,g,e,a){var b=this.createFilterFn(d,c,e,a);return b?this.data.findIndexBy(b,null,g):-1},findExact:function(b,a,c){return this.data.findIndexBy(function(d){return d.get(b)===a},this,c)},findBy:function(b,a,c){return this.data.findIndexBy(b,a,c)},collect:function(j,k,b){var h=(b===true&&this.snapshot)?this.snapshot.items:this.data.items;var m,n,a=[],c={};for(var e=0,g=h.length;e<g;e++){m=h[e].data[j];n=String(m);if((k||!Ext.isEmpty(m))&&!c[n]){c[n]=true;a[a.length]=m}}return a},afterEdit:function(a){if(this.modified.indexOf(a)==-1){this.modified.push(a)}this.fireEvent("update",this,a,Ext.data.Record.EDIT)},afterReject:function(a){this.modified.remove(a);this.fireEvent("update",this,a,Ext.data.Record.REJECT)},afterCommit:function(a){this.modified.remove(a);this.fireEvent("update",this,a,Ext.data.Record.COMMIT)},commitChanges:function(){var a=this.modified.slice(0),c=a.length,b;for(b=0;b<c;b++){a[b].commit()}this.modified=[];this.removed=[]},rejectChanges:function(){var a=this.modified.slice(0),e=this.removed.slice(0).reverse(),c=a.length,d=e.length,b;for(b=0;b<c;b++){a[b].reject()}for(b=0;b<d;b++){this.insert(e[b].lastIndex||0,e[b]);e[b].reject()}this.modified=[];this.removed=[]},onMetaChange:function(a){this.recordType=this.reader.recordType;this.fields=this.recordType.prototype.fields;delete this.snapshot;if(this.reader.meta.sortInfo){this.sortInfo=this.reader.meta.sortInfo}else{if(this.sortInfo&&!this.fields.get(this.sortInfo.field)){delete this.sortInfo}}if(this.writer){this.writer.meta=this.reader.meta}this.modified=[];this.fireEvent("metachange",this,this.reader.meta)},findInsertIndex:function(a){this.suspendEvents();var c=this.data.clone();this.data.add(a);this.applySort();var b=this.data.indexOf(a);this.data=c;this.resumeEvents();return b},setBaseParam:function(a,b){this.baseParams=this.baseParams||{};this.baseParams[a]=b}});Ext.reg("store",Ext.data.Store);Ext.data.Store.Error=Ext.extend(Ext.Error,{name:"Ext.data.Store"});Ext.apply(Ext.data.Store.Error.prototype,{lang:{"writer-undefined":"Attempted to execute a write-action without a DataWriter installed."}});Ext.data.Field=Ext.extend(Object,{constructor:function(b){if(Ext.isString(b)){b={name:b}}Ext.apply(this,b);var d=Ext.data.Types,a=this.sortType,c;if(this.type){if(Ext.isString(this.type)){this.type=Ext.data.Types[this.type.toUpperCase()]||d.AUTO}}else{this.type=d.AUTO}if(Ext.isString(a)){this.sortType=Ext.data.SortTypes[a]}else{if(Ext.isEmpty(a)){this.sortType=this.type.sortType}}if(!this.convert){this.convert=this.type.convert}},dateFormat:null,useNull:false,defaultValue:"",mapping:null,sortType:null,sortDir:"ASC",allowBlank:true});Ext.data.DataReader=function(a,b){this.meta=a;this.recordType=Ext.isArray(b)?Ext.data.Record.create(b):b;if(this.recordType){this.buildExtractors()}};Ext.data.DataReader.prototype={getTotal:Ext.emptyFn,getRoot:Ext.emptyFn,getMessage:Ext.emptyFn,getSuccess:Ext.emptyFn,getId:Ext.emptyFn,buildExtractors:Ext.emptyFn,extractValues:Ext.emptyFn,realize:function(a,c){if(Ext.isArray(a)){for(var b=a.length-1;b>=0;b--){if(Ext.isArray(c)){this.realize(a.splice(b,1).shift(),c.splice(b,1).shift())}else{this.realize(a.splice(b,1).shift(),c)}}}else{if(Ext.isArray(c)&&c.length==1){c=c.shift()}if(!this.isData(c)){throw new Ext.data.DataReader.Error("realize",a)}a.phantom=false;a._phid=a.id;a.id=this.getId(c);a.data=c;a.commit();a.store.reMap(a)}},update:function(a,c){if(Ext.isArray(a)){for(var b=a.length-1;b>=0;b--){if(Ext.isArray(c)){this.update(a.splice(b,1).shift(),c.splice(b,1).shift())}else{this.update(a.splice(b,1).shift(),c)}}}else{if(Ext.isArray(c)&&c.length==1){c=c.shift()}if(this.isData(c)){a.data=Ext.apply(a.data,c)}a.commit()}},extractData:function(k,a){var j=(this instanceof Ext.data.JsonReader)?"json":"node";var c=[];if(this.isData(k)&&!(this instanceof Ext.data.XmlReader)){k=[k]}var h=this.recordType.prototype.fields,o=h.items,m=h.length,c=[];if(a===true){var l=this.recordType;for(var e=0;e<k.length;e++){var b=k[e];var g=new l(this.extractValues(b,o,m),this.getId(b));g[j]=b;c.push(g)}}else{for(var e=0;e<k.length;e++){var d=this.extractValues(k[e],o,m);d[this.meta.idProperty]=this.getId(k[e]);c.push(d)}}return c},isData:function(a){return(a&&Ext.isObject(a)&&!Ext.isEmpty(this.getId(a)))?true:false},onMetaChange:function(a){delete this.ef;this.meta=a;this.recordType=Ext.data.Record.create(a.fields);this.buildExtractors()}};Ext.data.DataReader.Error=Ext.extend(Ext.Error,{constructor:function(b,a){this.arg=a;Ext.Error.call(this,b)},name:"Ext.data.DataReader"});Ext.apply(Ext.data.DataReader.Error.prototype,{lang:{update:"#update received invalid data from server. Please see docs for DataReader#update and review your DataReader configuration.",realize:"#realize was called with invalid remote-data. Please see the docs for DataReader#realize and review your DataReader configuration.","invalid-response":"#readResponse received an invalid response from the server."}});Ext.data.DataWriter=function(a){Ext.apply(this,a)};Ext.data.DataWriter.prototype={writeAllFields:false,listful:false,apply:function(e,g,d,a){var c=[],b=d+"Record";if(Ext.isArray(a)){Ext.each(a,function(h){c.push(this[b](h))},this)}else{if(a instanceof Ext.data.Record){c=this[b](a)}}this.render(e,g,c)},render:Ext.emptyFn,updateRecord:Ext.emptyFn,createRecord:Ext.emptyFn,destroyRecord:Ext.emptyFn,toHash:function(g,c){var e=g.fields.map,d={},b=(this.writeAllFields===false&&g.phantom===false)?g.getChanges():g.data,a;Ext.iterate(b,function(i,h){if((a=e[i])){d[a.mapping?a.mapping:a.name]=h}});if(g.phantom){if(g.fields.containsKey(this.meta.idProperty)&&Ext.isEmpty(g.data[this.meta.idProperty])){delete d[this.meta.idProperty]}}else{d[this.meta.idProperty]=g.id}return d},toArray:function(b){var a=[];Ext.iterate(b,function(d,c){a.push({name:d,value:c})},this);return a}};Ext.data.DataProxy=function(a){a=a||{};this.api=a.api;this.url=a.url;this.restful=a.restful;this.listeners=a.listeners;this.prettyUrls=a.prettyUrls;this.addEvents("exception","beforeload","load","loadexception","beforewrite","write");Ext.data.DataProxy.superclass.constructor.call(this);try{Ext.data.Api.prepare(this)}catch(b){if(b instanceof Ext.data.Api.Error){b.toConsole()}}Ext.data.DataProxy.relayEvents(this,["beforewrite","write","exception"])};Ext.extend(Ext.data.DataProxy,Ext.util.Observable,{restful:false,setApi:function(){if(arguments.length==1){var a=Ext.data.Api.isValid(arguments[0]);if(a===true){this.api=arguments[0]}else{throw new Ext.data.Api.Error("invalid",a)}}else{if(arguments.length==2){if(!Ext.data.Api.isAction(arguments[0])){throw new Ext.data.Api.Error("invalid",arguments[0])}this.api[arguments[0]]=arguments[1]}}Ext.data.Api.prepare(this)},isApiAction:function(a){return(this.api[a])?true:false},request:function(e,b,g,a,h,d,c){if(!this.api[e]&&!this.load){throw new Ext.data.DataProxy.Error("action-undefined",e)}g=g||{};if((e===Ext.data.Api.actions.read)?this.fireEvent("beforeload",this,g):this.fireEvent("beforewrite",this,e,b,g)!==false){this.doRequest.apply(this,arguments)}else{h.call(d||this,null,c,false)}},load:null,doRequest:function(e,b,g,a,h,d,c){this.load(g,a,h,d,c)},onRead:Ext.emptyFn,onWrite:Ext.emptyFn,buildUrl:function(d,b){b=b||null;var c=(this.conn&&this.conn.url)?this.conn.url:(this.api[d])?this.api[d].url:this.url;if(!c){throw new Ext.data.Api.Error("invalid-url",d)}var e=null;var a=c.match(/(.*)(\.json|\.xml|\.html)$/);if(a){e=a[2];c=a[1]}if((this.restful===true||this.prettyUrls===true)&&b instanceof Ext.data.Record&&!b.phantom){c+="/"+b.id}return(e===null)?c:c+e},destroy:function(){this.purgeListeners()}});Ext.apply(Ext.data.DataProxy,Ext.util.Observable.prototype);Ext.util.Observable.call(Ext.data.DataProxy);Ext.data.DataProxy.Error=Ext.extend(Ext.Error,{constructor:function(b,a){this.arg=a;Ext.Error.call(this,b)},name:"Ext.data.DataProxy"});Ext.apply(Ext.data.DataProxy.Error.prototype,{lang:{"action-undefined":"DataProxy attempted to execute an API-action but found an undefined url / function. Please review your Proxy url/api-configuration.","api-invalid":"Recieved an invalid API-configuration. Please ensure your proxy API-configuration contains only the actions from Ext.data.Api.actions."}});Ext.data.Request=function(a){Ext.apply(this,a)};Ext.data.Request.prototype={action:undefined,rs:undefined,params:undefined,callback:Ext.emptyFn,scope:undefined,reader:undefined};Ext.data.Response=function(a){Ext.apply(this,a)};Ext.data.Response.prototype={action:undefined,success:undefined,message:undefined,data:undefined,raw:undefined,records:undefined};Ext.data.ScriptTagProxy=function(a){Ext.apply(this,a);Ext.data.ScriptTagProxy.superclass.constructor.call(this,a);this.head=document.getElementsByTagName("head")[0]};Ext.data.ScriptTagProxy.TRANS_ID=1000;Ext.extend(Ext.data.ScriptTagProxy,Ext.data.DataProxy,{timeout:30000,callbackParam:"callback",nocache:true,doRequest:function(e,g,d,h,j,k,l){var c=Ext.urlEncode(Ext.apply(d,this.extraParams));var b=this.buildUrl(e,g);if(!b){throw new Ext.data.Api.Error("invalid-url",b)}b=Ext.urlAppend(b,c);if(this.nocache){b=Ext.urlAppend(b,"_dc="+(new Date().getTime()))}var a=++Ext.data.ScriptTagProxy.TRANS_ID;var m={id:a,action:e,cb:"stcCallback"+a,scriptId:"stcScript"+a,params:d,arg:l,url:b,callback:j,scope:k,reader:h};window[m.cb]=this.createCallback(e,g,m);b+=String.format("&{0}={1}",this.callbackParam,m.cb);if(this.autoAbort!==false){this.abort()}m.timeoutId=this.handleFailure.defer(this.timeout,this,[m]);var i=document.createElement("script");i.setAttribute("src",b);i.setAttribute("type","text/javascript");i.setAttribute("id",m.scriptId);this.head.appendChild(i);this.trans=m},createCallback:function(d,b,c){var a=this;return function(e){a.trans=false;a.destroyTrans(c,true);if(d===Ext.data.Api.actions.read){a.onRead.call(a,d,c,e)}else{a.onWrite.call(a,d,c,e,b)}}},onRead:function(d,c,b){var a;try{a=c.reader.readRecords(b)}catch(g){this.fireEvent("loadexception",this,c,b,g);this.fireEvent("exception",this,"response",d,c,b,g);c.callback.call(c.scope||window,null,c.arg,false);return}if(a.success===false){this.fireEvent("loadexception",this,c,b);this.fireEvent("exception",this,"remote",d,c,b,null)}else{this.fireEvent("load",this,b,c.arg)}c.callback.call(c.scope||window,a,c.arg,a.success)},onWrite:function(h,g,c,b){var a=g.reader;try{var d=a.readResponse(h,c)}catch(i){this.fireEvent("exception",this,"response",h,g,d,i);g.callback.call(g.scope||window,null,d,false);return}if(!d.success===true){this.fireEvent("exception",this,"remote",h,g,d,b);g.callback.call(g.scope||window,null,d,false);return}this.fireEvent("write",this,h,d.data,d,b,g.arg);g.callback.call(g.scope||window,d.data,d,true)},isLoading:function(){return this.trans?true:false},abort:function(){if(this.isLoading()){this.destroyTrans(this.trans)}},destroyTrans:function(b,a){this.head.removeChild(document.getElementById(b.scriptId));clearTimeout(b.timeoutId);if(a){window[b.cb]=undefined;try{delete window[b.cb]}catch(c){}}else{window[b.cb]=function(){window[b.cb]=undefined;try{delete window[b.cb]}catch(d){}}}},handleFailure:function(a){this.trans=false;this.destroyTrans(a,false);if(a.action===Ext.data.Api.actions.read){this.fireEvent("loadexception",this,null,a.arg)}this.fireEvent("exception",this,"response",a.action,{response:null,options:a.arg});a.callback.call(a.scope||window,null,a.arg,false)},destroy:function(){this.abort();Ext.data.ScriptTagProxy.superclass.destroy.call(this)}});Ext.data.HttpProxy=function(a){Ext.data.HttpProxy.superclass.constructor.call(this,a);this.conn=a;this.conn.url=null;this.useAjax=!a||!a.events;var c=Ext.data.Api.actions;this.activeRequest={};for(var b in c){this.activeRequest[c[b]]=undefined}};Ext.extend(Ext.data.HttpProxy,Ext.data.DataProxy,{getConnection:function(){return this.useAjax?Ext.Ajax:this.conn},setUrl:function(a,b){this.conn.url=a;if(b===true){this.url=a;this.api=null;Ext.data.Api.prepare(this)}},doRequest:function(g,d,i,c,b,e,a){var h={method:(this.api[g])?this.api[g]["method"]:undefined,request:{callback:b,scope:e,arg:a},reader:c,callback:this.createCallback(g,d),scope:this};if(i.jsonData){h.jsonData=i.jsonData}else{if(i.xmlData){h.xmlData=i.xmlData}else{h.params=i||{}}}this.conn.url=this.buildUrl(g,d);if(this.useAjax){Ext.applyIf(h,this.conn);if(g==Ext.data.Api.actions.read&&this.activeRequest[g]){Ext.Ajax.abort(this.activeRequest[g])}this.activeRequest[g]=Ext.Ajax.request(h)}else{this.conn.request(h)}this.conn.url=null},createCallback:function(b,a){return function(e,d,c){this.activeRequest[b]=undefined;if(!d){if(b===Ext.data.Api.actions.read){this.fireEvent("loadexception",this,e,c)}this.fireEvent("exception",this,"response",b,e,c);e.request.callback.call(e.request.scope,null,e.request.arg,false);return}if(b===Ext.data.Api.actions.read){this.onRead(b,e,c)}else{this.onWrite(b,e,c,a)}}},onRead:function(d,h,b){var a;try{a=h.reader.read(b)}catch(g){this.fireEvent("loadexception",this,h,b,g);this.fireEvent("exception",this,"response",d,h,b,g);h.request.callback.call(h.request.scope,null,h.request.arg,false);return}if(a.success===false){this.fireEvent("loadexception",this,h,b);var c=h.reader.readResponse(d,b);this.fireEvent("exception",this,"remote",d,h,c,null)}else{this.fireEvent("load",this,h,h.request.arg)}h.request.callback.call(h.request.scope,a,h.request.arg,a.success)},onWrite:function(g,i,c,b){var a=i.reader;var d;try{d=a.readResponse(g,c)}catch(h){this.fireEvent("exception",this,"response",g,i,c,h);i.request.callback.call(i.request.scope,null,i.request.arg,false);return}if(d.success===true){this.fireEvent("write",this,g,d.data,d,b,i.request.arg)}else{this.fireEvent("exception",this,"remote",g,i,d,b)}i.request.callback.call(i.request.scope,d.data,d,d.success)},destroy:function(){if(!this.useAjax){this.conn.abort()}else{if(this.activeRequest){var b=Ext.data.Api.actions;for(var a in b){if(this.activeRequest[b[a]]){Ext.Ajax.abort(this.activeRequest[b[a]])}}}}Ext.data.HttpProxy.superclass.destroy.call(this)}});Ext.data.MemoryProxy=function(b){var a={};a[Ext.data.Api.actions.read]=true;Ext.data.MemoryProxy.superclass.constructor.call(this,{api:a});this.data=b};Ext.extend(Ext.data.MemoryProxy,Ext.data.DataProxy,{doRequest:function(b,c,a,d,h,i,j){a=a||{};var k;try{k=d.readRecords(this.data)}catch(g){this.fireEvent("loadexception",this,null,j,g);this.fireEvent("exception",this,"response",b,j,null,g);h.call(i,null,j,false);return}h.call(i,k,j,true)}});Ext.data.Types=new function(){var a=Ext.data.SortTypes;Ext.apply(this,{stripRe:/[\$,%]/g,AUTO:{convert:function(b){return b},sortType:a.none,type:"auto"},STRING:{convert:function(b){return(b===undefined||b===null)?"":String(b)},sortType:a.asUCString,type:"string"},INT:{convert:function(b){return b!==undefined&&b!==null&&b!==""?parseInt(String(b).replace(Ext.data.Types.stripRe,""),10):(this.useNull?null:0)},sortType:a.none,type:"int"},FLOAT:{convert:function(b){return b!==undefined&&b!==null&&b!==""?parseFloat(String(b).replace(Ext.data.Types.stripRe,""),10):(this.useNull?null:0)},sortType:a.none,type:"float"},BOOL:{convert:function(b){return b===true||b==="true"||b==1},sortType:a.none,type:"bool"},DATE:{convert:function(c){var d=this.dateFormat;if(!c){return null}if(Ext.isDate(c)){return c}if(d){if(d=="timestamp"){return new Date(c*1000)}if(d=="time"){return new Date(parseInt(c,10))}return Date.parseDate(c,d)}var b=Date.parse(c);return b?new Date(b):null},sortType:a.asDate,type:"date"}});Ext.apply(this,{BOOLEAN:this.BOOL,INTEGER:this.INT,NUMBER:this.FLOAT})};Ext.data.JsonWriter=Ext.extend(Ext.data.DataWriter,{encode:true,encodeDelete:false,constructor:function(a){Ext.data.JsonWriter.superclass.constructor.call(this,a)},render:function(c,d,b){if(this.encode===true){Ext.apply(c,d);c[this.meta.root]=Ext.encode(b)}else{var a=Ext.apply({},d);a[this.meta.root]=b;c.jsonData=a}},createRecord:function(a){return this.toHash(a)},updateRecord:function(a){return this.toHash(a)},destroyRecord:function(b){if(this.encodeDelete){var a={};a[this.meta.idProperty]=b.id;return a}else{return b.id}}});Ext.data.JsonReader=function(a,b){a=a||{};Ext.applyIf(a,{idProperty:"id",successProperty:"success",totalProperty:"total"});Ext.data.JsonReader.superclass.constructor.call(this,a,b||a.fields)};Ext.extend(Ext.data.JsonReader,Ext.data.DataReader,{read:function(a){var b=a.responseText;var c=Ext.decode(b);if(!c){throw {message:"JsonReader.read: Json object not found"}}return this.readRecords(c)},readResponse:function(e,b){var h=(b.responseText!==undefined)?Ext.decode(b.responseText):b;if(!h){throw new Ext.data.JsonReader.Error("response")}var a=this.getRoot(h),g=this.getSuccess(h);if(g&&e===Ext.data.Api.actions.create){var d=Ext.isDefined(a);if(d&&Ext.isEmpty(a)){throw new Ext.data.JsonReader.Error("root-empty",this.meta.root)}else{if(!d){throw new Ext.data.JsonReader.Error("root-undefined-response",this.meta.root)}}}var c=new Ext.data.Response({action:e,success:g,data:(a)?this.extractData(a,false):[],message:this.getMessage(h),raw:h});if(Ext.isEmpty(c.success)){throw new Ext.data.JsonReader.Error("successProperty-response",this.meta.successProperty)}return c},readRecords:function(a){this.jsonData=a;if(a.metaData){this.onMetaChange(a.metaData)}var m=this.meta,h=this.recordType,b=h.prototype.fields,l=b.items,i=b.length,j;var g=this.getRoot(a),e=g.length,d=e,k=true;if(m.totalProperty){j=parseInt(this.getTotal(a),10);if(!isNaN(j)){d=j}}if(m.successProperty){j=this.getSuccess(a);if(j===false||j==="false"){k=false}}return{success:k,records:this.extractData(g,true),totalRecords:d}},buildExtractors:function(){if(this.ef){return}var l=this.meta,h=this.recordType,e=h.prototype.fields,k=e.items,j=e.length;if(l.totalProperty){this.getTotal=this.createAccessor(l.totalProperty)}if(l.successProperty){this.getSuccess=this.createAccessor(l.successProperty)}if(l.messageProperty){this.getMessage=this.createAccessor(l.messageProperty)}this.getRoot=l.root?this.createAccessor(l.root):function(g){return g};if(l.id||l.idProperty){var d=this.createAccessor(l.id||l.idProperty);this.getId=function(i){var g=d(i);return(g===undefined||g==="")?null:g}}else{this.getId=function(){return null}}var c=[];for(var b=0;b<j;b++){e=k[b];var a=(e.mapping!==undefined&&e.mapping!==null)?e.mapping:e.name;c.push(this.createAccessor(a))}this.ef=c},simpleAccess:function(b,a){return b[a]},createAccessor:function(){var a=/[\[\.]/;return function(c){if(Ext.isEmpty(c)){return Ext.emptyFn}if(Ext.isFunction(c)){return c}var b=String(c).search(a);if(b>=0){return new Function("obj","return obj"+(b>0?".":"")+c)}return function(d){return d[c]}}}(),extractValues:function(h,d,a){var g,c={};for(var e=0;e<a;e++){g=d[e];var b=this.ef[e](h);c[g.name]=g.convert((b!==undefined)?b:g.defaultValue,h)}return c}});Ext.data.JsonReader.Error=Ext.extend(Ext.Error,{constructor:function(b,a){this.arg=a;Ext.Error.call(this,b)},name:"Ext.data.JsonReader"});Ext.apply(Ext.data.JsonReader.Error.prototype,{lang:{response:"An error occurred while json-decoding your server response","successProperty-response":'Could not locate your "successProperty" in your server response. Please review your JsonReader config to ensure the config-property "successProperty" matches the property in your server-response. See the JsonReader docs.',"root-undefined-config":'Your JsonReader was configured without a "root" property. Please review your JsonReader config and make sure to define the root property. See the JsonReader docs.',"idProperty-undefined":'Your JsonReader was configured without an "idProperty" Please review your JsonReader configuration and ensure the "idProperty" is set (e.g.: "id"). See the JsonReader docs.',"root-empty":'Data was expected to be returned by the server in the "root" property of the response. Please review your JsonReader configuration to ensure the "root" property matches that returned in the server-response. See JsonReader docs.'}});Ext.data.ArrayReader=Ext.extend(Ext.data.JsonReader,{readRecords:function(r){this.arrayData=r;var l=this.meta,d=l?Ext.num(l.idIndex,l.id):null,b=this.recordType,q=b.prototype.fields,z=[],e=true,g;var u=this.getRoot(r);for(var y=0,A=u.length;y<A;y++){var t=u[y],a={},p=((d||d===0)&&t[d]!==undefined&&t[d]!==""?t[d]:null);for(var x=0,m=q.length;x<m;x++){var B=q.items[x],w=B.mapping!==undefined&&B.mapping!==null?B.mapping:x;g=t[w]!==undefined?t[w]:B.defaultValue;g=B.convert(g,t);a[B.name]=g}var c=new b(a,p);c.json=t;z[z.length]=c}var h=z.length;if(l.totalProperty){g=parseInt(this.getTotal(r),10);if(!isNaN(g)){h=g}}if(l.successProperty){g=this.getSuccess(r);if(g===false||g==="false"){e=false}}return{success:e,records:z,totalRecords:h}}});Ext.data.ArrayStore=Ext.extend(Ext.data.Store,{constructor:function(a){Ext.data.ArrayStore.superclass.constructor.call(this,Ext.apply(a,{reader:new Ext.data.ArrayReader(a)}))},loadData:function(e,b){if(this.expandData===true){var d=[];for(var c=0,a=e.length;c<a;c++){d[d.length]=[e[c]]}e=d}Ext.data.ArrayStore.superclass.loadData.call(this,e,b)}});Ext.reg("arraystore",Ext.data.ArrayStore);Ext.data.SimpleStore=Ext.data.ArrayStore;Ext.reg("simplestore",Ext.data.SimpleStore);Ext.data.JsonStore=Ext.extend(Ext.data.Store,{constructor:function(a){Ext.data.JsonStore.superclass.constructor.call(this,Ext.apply(a,{reader:new Ext.data.JsonReader(a)}))}});Ext.reg("jsonstore",Ext.data.JsonStore);Ext.data.XmlWriter=function(a){Ext.data.XmlWriter.superclass.constructor.apply(this,arguments);this.tpl=(typeof(this.tpl)==="string")?new Ext.XTemplate(this.tpl).compile():this.tpl.compile()};Ext.extend(Ext.data.XmlWriter,Ext.data.DataWriter,{documentRoot:"xrequest",forceDocumentRoot:false,root:"records",xmlVersion:"1.0",xmlEncoding:"ISO-8859-15",tpl:'<tpl for="."><\u003fxml version="{version}" encoding="{encoding}"\u003f><tpl if="documentRoot"><{documentRoot}><tpl for="baseParams"><tpl for="."><{name}>{value}</{name}></tpl></tpl></tpl><tpl if="records.length&gt;1"><{root}></tpl><tpl for="records"><{parent.record}><tpl for="."><{name}>{value}</{name}></tpl></{parent.record}></tpl><tpl if="records.length&gt;1"></{root}></tpl><tpl if="documentRoot"></{documentRoot}></tpl></tpl>',render:function(b,c,a){c=this.toArray(c);b.xmlData=this.tpl.applyTemplate({version:this.xmlVersion,encoding:this.xmlEncoding,documentRoot:(c.length>0||this.forceDocumentRoot===true)?this.documentRoot:false,record:this.meta.record,root:this.root,baseParams:c,records:(Ext.isArray(a[0]))?a:[a]})},createRecord:function(a){return this.toArray(this.toHash(a))},updateRecord:function(a){return this.toArray(this.toHash(a))},destroyRecord:function(b){var a={};a[this.meta.idProperty]=b.id;return this.toArray(a)}});Ext.data.XmlReader=function(a,b){a=a||{};Ext.applyIf(a,{idProperty:a.idProperty||a.idPath||a.id,successProperty:a.successProperty||a.success});Ext.data.XmlReader.superclass.constructor.call(this,a,b||a.fields)};Ext.extend(Ext.data.XmlReader,Ext.data.DataReader,{read:function(a){var b=a.responseXML;if(!b){throw {message:"XmlReader.read: XML Document not available"}}return this.readRecords(b)},readRecords:function(d){this.xmlData=d;var a=d.documentElement||d,c=Ext.DomQuery,g=0,e=true;if(this.meta.totalProperty){g=this.getTotal(a,0)}if(this.meta.successProperty){e=this.getSuccess(a)}var b=this.extractData(c.select(this.meta.record,a),true);return{success:e,records:b,totalRecords:g||b.length}},readResponse:function(g,b){var e=Ext.DomQuery,h=b.responseXML,a=h.documentElement||h;var c=new Ext.data.Response({action:g,success:this.getSuccess(a),message:this.getMessage(a),data:this.extractData(e.select(this.meta.record,a)||e.select(this.meta.root,a),false),raw:h});if(Ext.isEmpty(c.success)){throw new Ext.data.DataReader.Error("successProperty-response",this.meta.successProperty)}if(g===Ext.data.Api.actions.create){var d=Ext.isDefined(c.data);if(d&&Ext.isEmpty(c.data)){throw new Ext.data.JsonReader.Error("root-empty",this.meta.root)}else{if(!d){throw new Ext.data.JsonReader.Error("root-undefined-response",this.meta.root)}}}return c},getSuccess:function(){return true},buildExtractors:function(){if(this.ef){return}var l=this.meta,h=this.recordType,e=h.prototype.fields,k=e.items,j=e.length;if(l.totalProperty){this.getTotal=this.createAccessor(l.totalProperty)}if(l.successProperty){this.getSuccess=this.createAccessor(l.successProperty)}if(l.messageProperty){this.getMessage=this.createAccessor(l.messageProperty)}this.getRoot=function(g){return(!Ext.isEmpty(g[this.meta.record]))?g[this.meta.record]:g[this.meta.root]};if(l.idPath||l.idProperty){var d=this.createAccessor(l.idPath||l.idProperty);this.getId=function(g){var i=d(g)||g.id;return(i===undefined||i==="")?null:i}}else{this.getId=function(){return null}}var c=[];for(var b=0;b<j;b++){e=k[b];var a=(e.mapping!==undefined&&e.mapping!==null)?e.mapping:e.name;c.push(this.createAccessor(a))}this.ef=c},createAccessor:function(){var a=Ext.DomQuery;return function(b){if(Ext.isFunction(b)){return b}switch(b){case this.meta.totalProperty:return function(c,d){return a.selectNumber(b,c,d)};break;case this.meta.successProperty:return function(d,e){var c=a.selectValue(b,d,true);var g=c!==false&&c!=="false";return g};break;default:return function(c,d){return a.selectValue(b,c,d)};break}}}(),extractValues:function(h,d,a){var g,c={};for(var e=0;e<a;e++){g=d[e];var b=this.ef[e](h);c[g.name]=g.convert((b!==undefined)?b:g.defaultValue,h)}return c}});Ext.data.XmlStore=Ext.extend(Ext.data.Store,{constructor:function(a){Ext.data.XmlStore.superclass.constructor.call(this,Ext.apply(a,{reader:new Ext.data.XmlReader(a)}))}});Ext.reg("xmlstore",Ext.data.XmlStore);Ext.data.GroupingStore=Ext.extend(Ext.data.Store,{constructor:function(d){d=d||{};this.hasMultiSort=true;this.multiSortInfo=this.multiSortInfo||{sorters:[]};var e=this.multiSortInfo.sorters,c=d.groupField||this.groupField,b=d.sortInfo||this.sortInfo,a=d.groupDir||this.groupDir;if(c){e.push({field:c,direction:a})}if(b){e.push(b)}Ext.data.GroupingStore.superclass.constructor.call(this,d);this.addEvents("groupchange");this.applyGroupField()},remoteGroup:false,groupOnSort:false,groupDir:"ASC",clearGrouping:function(){this.groupField=false;if(this.remoteGroup){if(this.baseParams){delete this.baseParams.groupBy;delete this.baseParams.groupDir}var a=this.lastOptions;if(a&&a.params){delete a.params.groupBy;delete a.params.groupDir}this.reload()}else{this.sort();this.fireEvent("datachanged",this)}},groupBy:function(e,a,d){d=d?(String(d).toUpperCase()=="DESC"?"DESC":"ASC"):this.groupDir;if(this.groupField==e&&this.groupDir==d&&!a){return}var c=this.multiSortInfo.sorters;if(c.length>0&&c[0].field==this.groupField){c.shift()}this.groupField=e;this.groupDir=d;this.applyGroupField();var b=function(){this.fireEvent("groupchange",this,this.getGroupState())};if(this.groupOnSort){this.sort(e,d);b.call(this);return}if(this.remoteGroup){this.on("load",b,this,{single:true});this.reload()}else{this.sort(c);b.call(this)}},sort:function(h,c){if(this.remoteSort){return Ext.data.GroupingStore.superclass.sort.call(this,h,c)}var g=[];if(Ext.isArray(arguments[0])){g=arguments[0]}else{if(h==undefined){g=this.sortInfo?[this.sortInfo]:[]}else{var e=this.fields.get(h);if(!e){return false}var b=e.name,a=this.sortInfo||null,d=this.sortToggle?this.sortToggle[b]:null;if(!c){if(a&&a.field==b){c=(this.sortToggle[b]||"ASC").toggle("ASC","DESC")}else{c=e.sortDir}}this.sortToggle[b]=c;this.sortInfo={field:b,direction:c};g=[this.sortInfo]}}if(this.groupField){g.unshift({direction:this.groupDir,field:this.groupField})}return this.multiSort.call(this,g,c)},applyGroupField:function(){if(this.remoteGroup){if(!this.baseParams){this.baseParams={}}Ext.apply(this.baseParams,{groupBy:this.groupField,groupDir:this.groupDir});var a=this.lastOptions;if(a&&a.params){a.params.groupDir=this.groupDir;delete a.params.groupBy}}},applyGrouping:function(a){if(this.groupField!==false){this.groupBy(this.groupField,true,this.groupDir);return true}else{if(a===true){this.fireEvent("datachanged",this)}return false}},getGroupState:function(){return this.groupOnSort&&this.groupField!==false?(this.sortInfo?this.sortInfo.field:undefined):this.groupField}});Ext.reg("groupingstore",Ext.data.GroupingStore);Ext.data.DirectProxy=function(a){Ext.apply(this,a);if(typeof this.paramOrder=="string"){this.paramOrder=this.paramOrder.split(/[\s,|]/)}Ext.data.DirectProxy.superclass.constructor.call(this,a)};Ext.extend(Ext.data.DirectProxy,Ext.data.DataProxy,{paramOrder:undefined,paramsAsHash:true,directFn:undefined,doRequest:function(b,c,a,e,k,l,n){var j=[],h=this.api[b]||this.directFn;switch(b){case Ext.data.Api.actions.create:j.push(a.jsonData);break;case Ext.data.Api.actions.read:if(h.directCfg.method.len>0){if(this.paramOrder){for(var d=0,g=this.paramOrder.length;d<g;d++){j.push(a[this.paramOrder[d]])}}else{if(this.paramsAsHash){j.push(a)}}}break;case Ext.data.Api.actions.update:j.push(a.jsonData);break;case Ext.data.Api.actions.destroy:j.push(a.jsonData);break}var m={params:a||{},request:{callback:k,scope:l,arg:n},reader:e};j.push(this.createCallback(b,c,m),this);h.apply(window,j)},createCallback:function(d,a,b){var c=this;return function(e,g){if(!g.status){if(d===Ext.data.Api.actions.read){c.fireEvent("loadexception",c,b,g,null)}c.fireEvent("exception",c,"remote",d,b,g,null);b.request.callback.call(b.request.scope,null,b.request.arg,false);return}if(d===Ext.data.Api.actions.read){c.onRead(d,b,e,g)}else{c.onWrite(d,b,e,g,a)}}},onRead:function(g,e,a,d){var b;try{b=e.reader.readRecords(a)}catch(c){this.fireEvent("loadexception",this,e,d,c);this.fireEvent("exception",this,"response",g,e,d,c);e.request.callback.call(e.request.scope,null,e.request.arg,false);return}this.fireEvent("load",this,d,e.request.arg);e.request.callback.call(e.request.scope,b,e.request.arg,true)},onWrite:function(g,d,a,c,b){var e=d.reader.extractData(d.reader.getRoot(a),false);var h=d.reader.getSuccess(a);h=(h!==false);if(h){this.fireEvent("write",this,g,e,c,b,d.request.arg)}else{this.fireEvent("exception",this,"remote",g,d,a,b)}d.request.callback.call(d.request.scope,e,c,h)}});Ext.data.DirectStore=Ext.extend(Ext.data.Store,{constructor:function(a){var b=Ext.apply({},{batchTransactions:false},a);Ext.data.DirectStore.superclass.constructor.call(this,Ext.apply(b,{proxy:Ext.isDefined(b.proxy)?b.proxy:new Ext.data.DirectProxy(Ext.copyTo({},b,"paramOrder,paramsAsHash,directFn,api")),reader:(!Ext.isDefined(b.reader)&&b.fields)?new Ext.data.JsonReader(Ext.copyTo({},b,"totalProperty,root,idProperty"),b.fields):b.reader}))}});Ext.reg("directstore",Ext.data.DirectStore);Ext.Direct=Ext.extend(Ext.util.Observable,{exceptions:{TRANSPORT:"xhr",PARSE:"parse",LOGIN:"login",SERVER:"exception"},constructor:function(){this.addEvents("event","exception");this.transactions={};this.providers={}},addProvider:function(e){var c=arguments;if(c.length>1){for(var d=0,b=c.length;d<b;d++){this.addProvider(c[d])}return}if(!e.events){e=new Ext.Direct.PROVIDERS[e.type](e)}e.id=e.id||Ext.id();this.providers[e.id]=e;e.on("data",this.onProviderData,this);e.on("exception",this.onProviderException,this);if(!e.isConnected()){e.connect()}return e},getProvider:function(a){return this.providers[a]},removeProvider:function(b){var a=b.id?b:this.providers[b];a.un("data",this.onProviderData,this);a.un("exception",this.onProviderException,this);delete this.providers[a.id];return a},addTransaction:function(a){this.transactions[a.tid]=a;return a},removeTransaction:function(a){delete this.transactions[a.tid||a];return a},getTransaction:function(a){return this.transactions[a.tid||a]},onProviderData:function(d,c){if(Ext.isArray(c)){for(var b=0,a=c.length;b<a;b++){this.onProviderData(d,c[b])}return}if(c.name&&c.name!="event"&&c.name!="exception"){this.fireEvent(c.name,c)}else{if(c.type=="exception"){this.fireEvent("exception",c)}}this.fireEvent("event",c,d)},createEvent:function(a,b){return new Ext.Direct.eventTypes[a.type](Ext.apply(a,b))}});Ext.Direct=new Ext.Direct();Ext.Direct.TID=1;Ext.Direct.PROVIDERS={};Ext.Direct.Transaction=function(a){Ext.apply(this,a);this.tid=++Ext.Direct.TID;this.retryCount=0};Ext.Direct.Transaction.prototype={send:function(){this.provider.queueTransaction(this)},retry:function(){this.retryCount++;this.send()},getProvider:function(){return this.provider}};Ext.Direct.Event=function(a){Ext.apply(this,a)};Ext.Direct.Event.prototype={status:true,getData:function(){return this.data}};Ext.Direct.RemotingEvent=Ext.extend(Ext.Direct.Event,{type:"rpc",getTransaction:function(){return this.transaction||Ext.Direct.getTransaction(this.tid)}});Ext.Direct.ExceptionEvent=Ext.extend(Ext.Direct.RemotingEvent,{status:false,type:"exception"});Ext.Direct.eventTypes={rpc:Ext.Direct.RemotingEvent,event:Ext.Direct.Event,exception:Ext.Direct.ExceptionEvent};Ext.direct.Provider=Ext.extend(Ext.util.Observable,{priority:1,constructor:function(a){Ext.apply(this,a);this.addEvents("connect","disconnect","data","exception");Ext.direct.Provider.superclass.constructor.call(this,a)},isConnected:function(){return false},connect:Ext.emptyFn,disconnect:Ext.emptyFn});Ext.direct.JsonProvider=Ext.extend(Ext.direct.Provider,{parseResponse:function(a){if(!Ext.isEmpty(a.responseText)){if(typeof a.responseText=="object"){return a.responseText}return Ext.decode(a.responseText)}return null},getEvents:function(j){var g=null;try{g=this.parseResponse(j)}catch(h){var d=new Ext.Direct.ExceptionEvent({data:h,xhr:j,code:Ext.Direct.exceptions.PARSE,message:"Error parsing json response: \n\n "+g});return[d]}var c=[];if(Ext.isArray(g)){for(var b=0,a=g.length;b<a;b++){c.push(Ext.Direct.createEvent(g[b]))}}else{c.push(Ext.Direct.createEvent(g))}return c}});Ext.direct.PollingProvider=Ext.extend(Ext.direct.JsonProvider,{priority:3,interval:3000,constructor:function(a){Ext.direct.PollingProvider.superclass.constructor.call(this,a);this.addEvents("beforepoll","poll")},isConnected:function(){return !!this.pollTask},connect:function(){if(this.url&&!this.pollTask){this.pollTask=Ext.TaskMgr.start({run:function(){if(this.fireEvent("beforepoll",this)!==false){if(typeof this.url=="function"){this.url(this.baseParams)}else{Ext.Ajax.request({url:this.url,callback:this.onData,scope:this,params:this.baseParams})}}},interval:this.interval,scope:this});this.fireEvent("connect",this)}else{if(!this.url){throw"Error initializing PollingProvider, no url configured."}}},disconnect:function(){if(this.pollTask){Ext.TaskMgr.stop(this.pollTask);delete this.pollTask;this.fireEvent("disconnect",this)}},onData:function(d,j,h){if(j){var c=this.getEvents(h);for(var b=0,a=c.length;b<a;b++){var g=c[b];this.fireEvent("data",this,g)}}else{var g=new Ext.Direct.ExceptionEvent({data:g,code:Ext.Direct.exceptions.TRANSPORT,message:"Unable to connect to the server.",xhr:h});this.fireEvent("data",this,g)}}});Ext.Direct.PROVIDERS.polling=Ext.direct.PollingProvider;Ext.direct.RemotingProvider=Ext.extend(Ext.direct.JsonProvider,{enableBuffer:10,maxRetries:1,timeout:undefined,constructor:function(a){Ext.direct.RemotingProvider.superclass.constructor.call(this,a);this.addEvents("beforecall","call");this.namespace=(Ext.isString(this.namespace))?Ext.ns(this.namespace):this.namespace||window;this.transactions={};this.callBuffer=[]},initAPI:function(){var h=this.actions;for(var j in h){var d=this.namespace[j]||(this.namespace[j]={}),e=h[j];for(var g=0,b=e.length;g<b;g++){var a=e[g];d[a.name]=this.createMethod(j,a)}}},isConnected:function(){return !!this.connected},connect:function(){if(this.url){this.initAPI();this.connected=true;this.fireEvent("connect",this)}else{if(!this.url){throw"Error initializing RemotingProvider, no url configured."}}},disconnect:function(){if(this.connected){this.connected=false;this.fireEvent("disconnect",this)}},onData:function(a,h,j){if(h){var k=this.getEvents(j);for(var b=0,c=k.length;b<c;b++){var d=k[b],l=this.getTransaction(d);this.fireEvent("data",this,d);if(l){this.doCallback(l,d,true);Ext.Direct.removeTransaction(l)}}}else{var g=[].concat(a.ts);for(var b=0,c=g.length;b<c;b++){var l=this.getTransaction(g[b]);if(l&&l.retryCount<this.maxRetries){l.retry()}else{var d=new Ext.Direct.ExceptionEvent({data:d,transaction:l,code:Ext.Direct.exceptions.TRANSPORT,message:"Unable to connect to the server.",xhr:j});this.fireEvent("data",this,d);if(l){this.doCallback(l,d,false);Ext.Direct.removeTransaction(l)}}}}},getCallData:function(a){return{action:a.action,method:a.method,data:a.data,type:"rpc",tid:a.tid}},doSend:function(d){var g={url:this.url,callback:this.onData,scope:this,ts:d,timeout:this.timeout},b;if(Ext.isArray(d)){b=[];for(var c=0,a=d.length;c<a;c++){b.push(this.getCallData(d[c]))}}else{b=this.getCallData(d)}if(this.enableUrlEncode){var e={};e[Ext.isString(this.enableUrlEncode)?this.enableUrlEncode:"data"]=Ext.encode(b);g.params=e}else{g.jsonData=b}Ext.Ajax.request(g)},combineAndSend:function(){var a=this.callBuffer.length;if(a>0){this.doSend(a==1?this.callBuffer[0]:this.callBuffer);this.callBuffer=[]}},queueTransaction:function(a){if(a.form){this.processForm(a);return}this.callBuffer.push(a);if(this.enableBuffer){if(!this.callTask){this.callTask=new Ext.util.DelayedTask(this.combineAndSend,this)}this.callTask.delay(Ext.isNumber(this.enableBuffer)?this.enableBuffer:10)}else{this.combineAndSend()}},doCall:function(i,a,b){var h=null,e=b[a.len],g=b[a.len+1];if(a.len!==0){h=b.slice(0,a.len)}var d=new Ext.Direct.Transaction({provider:this,args:b,action:i,method:a.name,data:h,cb:g&&Ext.isFunction(e)?e.createDelegate(g):e});if(this.fireEvent("beforecall",this,d,a)!==false){Ext.Direct.addTransaction(d);this.queueTransaction(d);this.fireEvent("call",this,d,a)}},doForm:function(j,b,g,i,e){var d=new Ext.Direct.Transaction({provider:this,action:j,method:b.name,args:[g,i,e],cb:e&&Ext.isFunction(i)?i.createDelegate(e):i,isForm:true});if(this.fireEvent("beforecall",this,d,b)!==false){Ext.Direct.addTransaction(d);var a=String(g.getAttribute("enctype")).toLowerCase()=="multipart/form-data",h={extTID:d.tid,extAction:j,extMethod:b.name,extType:"rpc",extUpload:String(a)};Ext.apply(d,{form:Ext.getDom(g),isUpload:a,params:i&&Ext.isObject(i.params)?Ext.apply(h,i.params):h});this.fireEvent("call",this,d,b);this.processForm(d)}},processForm:function(a){Ext.Ajax.request({url:this.url,params:a.params,callback:this.onData,scope:this,form:a.form,isUpload:a.isUpload,ts:a})},createMethod:function(d,a){var b;if(!a.formHandler){b=function(){this.doCall(d,a,Array.prototype.slice.call(arguments,0))}.createDelegate(this)}else{b=function(e,g,c){this.doForm(d,a,e,g,c)}.createDelegate(this)}b.directCfg={action:d,method:a};return b},getTransaction:function(a){return a&&a.tid?Ext.Direct.getTransaction(a.tid):null},doCallback:function(c,g){var d=g.status?"success":"failure";if(c&&c.cb){var b=c.cb,a=Ext.isDefined(g.result)?g.result:g.data;if(Ext.isFunction(b)){b(a,g)}else{Ext.callback(b[d],b.scope,[a,g]);Ext.callback(b.callback,b.scope,[a,g])}}}});Ext.Direct.PROVIDERS.remoting=Ext.direct.RemotingProvider;Ext.Resizable=Ext.extend(Ext.util.Observable,{constructor:function(d,e){this.el=Ext.get(d);if(e&&e.wrap){e.resizeChild=this.el;this.el=this.el.wrap(typeof e.wrap=="object"?e.wrap:{cls:"xresizable-wrap"});this.el.id=this.el.dom.id=e.resizeChild.id+"-rzwrap";this.el.setStyle("overflow","hidden");this.el.setPositioning(e.resizeChild.getPositioning());e.resizeChild.clearPositioning();if(!e.width||!e.height){var g=e.resizeChild.getSize();this.el.setSize(g.width,g.height)}if(e.pinned&&!e.adjustments){e.adjustments="auto"}}this.proxy=this.el.createProxy({tag:"div",cls:"x-resizable-proxy",id:this.el.id+"-rzproxy"},Ext.getBody());this.proxy.unselectable();this.proxy.enableDisplayMode("block");Ext.apply(this,e);if(this.pinned){this.disableTrackOver=true;this.el.addClass("x-resizable-pinned")}var k=this.el.getStyle("position");if(k!="absolute"&&k!="fixed"){this.el.setStyle("position","relative")}if(!this.handles){this.handles="s,e,se";if(this.multiDirectional){this.handles+=",n,w"}}if(this.handles=="all"){this.handles="n s e w ne nw se sw"}var o=this.handles.split(/\s*?[,;]\s*?| /);var c=Ext.Resizable.positions;for(var j=0,l=o.length;j<l;j++){if(o[j]&&c[o[j]]){var n=c[o[j]];this[n]=new Ext.Resizable.Handle(this,n,this.disableTrackOver,this.transparent,this.handleCls)}}this.corner=this.southeast;if(this.handles.indexOf("n")!=-1||this.handles.indexOf("w")!=-1){this.updateBox=true}this.activeHandle=null;if(this.resizeChild){if(typeof this.resizeChild=="boolean"){this.resizeChild=Ext.get(this.el.dom.firstChild,true)}else{this.resizeChild=Ext.get(this.resizeChild,true)}}if(this.adjustments=="auto"){var b=this.resizeChild;var m=this.west,h=this.east,a=this.north,o=this.south;if(b&&(m||a)){b.position("relative");b.setLeft(m?m.el.getWidth():0);b.setTop(a?a.el.getHeight():0)}this.adjustments=[(h?-h.el.getWidth():0)+(m?-m.el.getWidth():0),(a?-a.el.getHeight():0)+(o?-o.el.getHeight():0)-1]}if(this.draggable){this.dd=this.dynamic?this.el.initDD(null):this.el.initDDProxy(null,{dragElId:this.proxy.id});this.dd.setHandleElId(this.resizeChild?this.resizeChild.id:this.el.id);if(this.constrainTo){this.dd.constrainTo(this.constrainTo)}}this.addEvents("beforeresize","resize");if(this.width!==null&&this.height!==null){this.resizeTo(this.width,this.height)}else{this.updateChildSize()}if(Ext.isIE){this.el.dom.style.zoom=1}Ext.Resizable.superclass.constructor.call(this)},adjustments:[0,0],animate:false,disableTrackOver:false,draggable:false,duration:0.35,dynamic:false,easing:"easeOutStrong",enabled:true,handles:false,multiDirectional:false,height:null,width:null,heightIncrement:0,widthIncrement:0,minHeight:5,minWidth:5,maxHeight:10000,maxWidth:10000,minX:0,minY:0,pinned:false,preserveRatio:false,resizeChild:false,transparent:false,resizeTo:function(b,a){this.el.setSize(b,a);this.updateChildSize();this.fireEvent("resize",this,b,a,null)},startSizing:function(c,b){this.fireEvent("beforeresize",this,c);if(this.enabled){if(!this.overlay){this.overlay=this.el.createProxy({tag:"div",cls:"x-resizable-overlay",html:"&#160;"},Ext.getBody());this.overlay.unselectable();this.overlay.enableDisplayMode("block");this.overlay.on({scope:this,mousemove:this.onMouseMove,mouseup:this.onMouseUp})}this.overlay.setStyle("cursor",b.el.getStyle("cursor"));this.resizing=true;this.startBox=this.el.getBox();this.startPoint=c.getXY();this.offsets=[(this.startBox.x+this.startBox.width)-this.startPoint[0],(this.startBox.y+this.startBox.height)-this.startPoint[1]];this.overlay.setSize(Ext.lib.Dom.getViewWidth(true),Ext.lib.Dom.getViewHeight(true));this.overlay.show();if(this.constrainTo){var a=Ext.get(this.constrainTo);this.resizeRegion=a.getRegion().adjust(a.getFrameWidth("t"),a.getFrameWidth("l"),-a.getFrameWidth("b"),-a.getFrameWidth("r"))}this.proxy.setStyle("visibility","hidden");this.proxy.show();this.proxy.setBox(this.startBox);if(!this.dynamic){this.proxy.setStyle("visibility","visible")}}},onMouseDown:function(a,b){if(this.enabled){b.stopEvent();this.activeHandle=a;this.startSizing(b,a)}},onMouseUp:function(b){this.activeHandle=null;var a=this.resizeElement();this.resizing=false;this.handleOut();this.overlay.hide();this.proxy.hide();this.fireEvent("resize",this,a.width,a.height,b)},updateChildSize:function(){if(this.resizeChild){var d=this.el;var e=this.resizeChild;var c=this.adjustments;if(d.dom.offsetWidth){var a=d.getSize(true);e.setSize(a.width+c[0],a.height+c[1])}if(Ext.isIE9m){setTimeout(function(){if(d.dom.offsetWidth){var g=d.getSize(true);e.setSize(g.width+c[0],g.height+c[1])}},10)}}},snap:function(c,e,b){if(!e||!c){return c}var d=c;var a=c%e;if(a>0){if(a>(e/2)){d=c+(e-a)}else{d=c-a}}return Math.max(b,d)},resizeElement:function(){var a=this.proxy.getBox();if(this.updateBox){this.el.setBox(a,false,this.animate,this.duration,null,this.easing)}else{this.el.setSize(a.width,a.height,this.animate,this.duration,null,this.easing)}this.updateChildSize();if(!this.dynamic){this.proxy.hide()}if(this.draggable&&this.constrainTo){this.dd.resetConstraints();this.dd.constrainTo(this.constrainTo)}return a},constrain:function(b,c,a,d){if(b-c<a){c=b-a}else{if(b-c>d){c=b-d}}return c},onMouseMove:function(z){if(this.enabled&&this.activeHandle){try{if(this.resizeRegion&&!this.resizeRegion.contains(z.getPoint())){return}var t=this.curSize||this.startBox,l=this.startBox.x,k=this.startBox.y,c=l,b=k,m=t.width,u=t.height,d=m,o=u,n=this.minWidth,A=this.minHeight,s=this.maxWidth,D=this.maxHeight,i=this.widthIncrement,a=this.heightIncrement,B=z.getXY(),r=-(this.startPoint[0]-Math.max(this.minX,B[0])),p=-(this.startPoint[1]-Math.max(this.minY,B[1])),j=this.activeHandle.position,E,g;switch(j){case"east":m+=r;m=Math.min(Math.max(n,m),s);break;case"south":u+=p;u=Math.min(Math.max(A,u),D);break;case"southeast":m+=r;u+=p;m=Math.min(Math.max(n,m),s);u=Math.min(Math.max(A,u),D);break;case"north":p=this.constrain(u,p,A,D);k+=p;u-=p;break;case"west":r=this.constrain(m,r,n,s);l+=r;m-=r;break;case"northeast":m+=r;m=Math.min(Math.max(n,m),s);p=this.constrain(u,p,A,D);k+=p;u-=p;break;case"northwest":r=this.constrain(m,r,n,s);p=this.constrain(u,p,A,D);k+=p;u-=p;l+=r;m-=r;break;case"southwest":r=this.constrain(m,r,n,s);u+=p;u=Math.min(Math.max(A,u),D);l+=r;m-=r;break}var q=this.snap(m,i,n);var C=this.snap(u,a,A);if(q!=m||C!=u){switch(j){case"northeast":k-=C-u;break;case"north":k-=C-u;break;case"southwest":l-=q-m;break;case"west":l-=q-m;break;case"northwest":l-=q-m;k-=C-u;break}m=q;u=C}if(this.preserveRatio){switch(j){case"southeast":case"east":u=o*(m/d);u=Math.min(Math.max(A,u),D);m=d*(u/o);break;case"south":m=d*(u/o);m=Math.min(Math.max(n,m),s);u=o*(m/d);break;case"northeast":m=d*(u/o);m=Math.min(Math.max(n,m),s);u=o*(m/d);break;case"north":E=m;m=d*(u/o);m=Math.min(Math.max(n,m),s);u=o*(m/d);l+=(E-m)/2;break;case"southwest":u=o*(m/d);u=Math.min(Math.max(A,u),D);E=m;m=d*(u/o);l+=E-m;break;case"west":g=u;u=o*(m/d);u=Math.min(Math.max(A,u),D);k+=(g-u)/2;E=m;m=d*(u/o);l+=E-m;break;case"northwest":E=m;g=u;u=o*(m/d);u=Math.min(Math.max(A,u),D);m=d*(u/o);k+=g-u;l+=E-m;break}}this.proxy.setBounds(l,k,m,u);if(this.dynamic){this.resizeElement()}}catch(v){}}},handleOver:function(){if(this.enabled){this.el.addClass("x-resizable-over")}},handleOut:function(){if(!this.resizing){this.el.removeClass("x-resizable-over")}},getEl:function(){return this.el},getResizeChild:function(){return this.resizeChild},destroy:function(b){Ext.destroy(this.dd,this.overlay,this.proxy);this.overlay=null;this.proxy=null;var c=Ext.Resizable.positions;for(var a in c){if(typeof c[a]!="function"&&this[c[a]]){this[c[a]].destroy()}}if(b){this.el.update("");Ext.destroy(this.el);this.el=null}this.purgeListeners()},syncHandleHeight:function(){var a=this.el.getHeight(true);if(this.west){this.west.el.setHeight(a)}if(this.east){this.east.el.setHeight(a)}}});Ext.Resizable.positions={n:"north",s:"south",e:"east",w:"west",se:"southeast",sw:"southwest",nw:"northwest",ne:"northeast"};Ext.Resizable.Handle=Ext.extend(Object,{constructor:function(d,g,c,e,a){if(!this.tpl){var b=Ext.DomHelper.createTemplate({tag:"div",cls:"x-resizable-handle x-resizable-handle-{0}"});b.compile();Ext.Resizable.Handle.prototype.tpl=b}this.position=g;this.rz=d;this.el=this.tpl.append(d.el.dom,[this.position],true);this.el.unselectable();if(e){this.el.setOpacity(0)}if(!Ext.isEmpty(a)){this.el.addClass(a)}this.el.on("mousedown",this.onMouseDown,this);if(!c){this.el.on({scope:this,mouseover:this.onMouseOver,mouseout:this.onMouseOut})}},afterResize:function(a){},onMouseDown:function(a){this.rz.onMouseDown(this,a)},onMouseOver:function(a){this.rz.handleOver(this,a)},onMouseOut:function(a){this.rz.handleOut(this,a)},destroy:function(){Ext.destroy(this.el);this.el=null}});Ext.Window=Ext.extend(Ext.Panel,{baseCls:"x-window",resizable:true,draggable:true,closable:true,closeAction:"close",constrain:false,constrainHeader:false,plain:false,minimizable:false,maximizable:false,minHeight:100,minWidth:200,expandOnShow:true,showAnimDuration:0.25,hideAnimDuration:0.25,collapsible:false,initHidden:undefined,hidden:true,elements:"header,body",frame:true,floating:true,initComponent:function(){this.initTools();Ext.Window.superclass.initComponent.call(this);this.addEvents("resize","maximize","minimize","restore");if(Ext.isDefined(this.initHidden)){this.hidden=this.initHidden}if(this.hidden===false){this.hidden=true;this.show()}},getState:function(){return Ext.apply(Ext.Window.superclass.getState.call(this)||{},this.getBox(true))},onRender:function(b,a){Ext.Window.superclass.onRender.call(this,b,a);if(this.plain){this.el.addClass("x-window-plain")}this.focusEl=this.el.createChild({tag:"a",href:"#",cls:"x-dlg-focus",tabIndex:"-1",html:"&#160;"});this.focusEl.swallowEvent("click",true);this.proxy=this.el.createProxy("x-window-proxy");this.proxy.enableDisplayMode("block");if(this.modal){this.mask=this.container.createChild({cls:"ext-el-mask"},this.el.dom);this.mask.enableDisplayMode("block");this.mask.hide();this.mon(this.mask,"click",this.focus,this)}if(this.maximizable){this.mon(this.header,"dblclick",this.toggleMaximize,this)}},initEvents:function(){Ext.Window.superclass.initEvents.call(this);if(this.animateTarget){this.setAnimateTarget(this.animateTarget)}if(this.resizable){this.resizer=new Ext.Resizable(this.el,{minWidth:this.minWidth,minHeight:this.minHeight,handles:this.resizeHandles||"all",pinned:true,resizeElement:this.resizerAction,handleCls:"x-window-handle"});this.resizer.window=this;this.mon(this.resizer,"beforeresize",this.beforeResize,this)}if(this.draggable){this.header.addClass("x-window-draggable")}this.mon(this.el,"mousedown",this.toFront,this);this.manager=this.manager||Ext.WindowMgr;this.manager.register(this);if(this.maximized){this.maximized=false;this.maximize()}if(this.closable){var a=this.getKeyMap();a.on(27,this.onEsc,this);a.disable()}},initDraggable:function(){this.dd=new Ext.Window.DD(this)},onEsc:function(a,b){if(this.activeGhost){this.unghost()}b.stopEvent();this[this.closeAction]()},beforeDestroy:function(){if(this.rendered){this.hide();this.clearAnchor();Ext.destroy(this.focusEl,this.resizer,this.dd,this.proxy,this.mask)}Ext.Window.superclass.beforeDestroy.call(this)},onDestroy:function(){if(this.manager){this.manager.unregister(this)}Ext.Window.superclass.onDestroy.call(this)},initTools:function(){if(this.minimizable){this.addTool({id:"minimize",handler:this.minimize.createDelegate(this,[])})}if(this.maximizable){this.addTool({id:"maximize",handler:this.maximize.createDelegate(this,[])});this.addTool({id:"restore",handler:this.restore.createDelegate(this,[]),hidden:true})}if(this.closable){this.addTool({id:"close",handler:this[this.closeAction].createDelegate(this,[])})}},resizerAction:function(){var a=this.proxy.getBox();this.proxy.hide();this.window.handleResize(a);return a},beforeResize:function(){this.resizer.minHeight=Math.max(this.minHeight,this.getFrameHeight()+40);this.resizer.minWidth=Math.max(this.minWidth,this.getFrameWidth()+40);this.resizeBox=this.el.getBox()},updateHandles:function(){if(Ext.isIE9m&&this.resizer){this.resizer.syncHandleHeight();this.el.repaint()}},handleResize:function(b){var a=this.resizeBox;if(a.x!=b.x||a.y!=b.y){this.updateBox(b)}else{this.setSize(b);if(Ext.isIE6&&Ext.isStrict){this.doLayout()}}this.focus();this.updateHandles();this.saveState()},focus:function(){var e=this.focusEl,a=this.defaultButton,c=typeof a,d,b;if(Ext.isDefined(a)){if(Ext.isNumber(a)&&this.fbar){e=this.fbar.items.get(a)}else{if(Ext.isString(a)){e=Ext.getCmp(a)}else{e=a}}d=e.getEl();b=Ext.getDom(this.container);if(d&&b){if(b!=document.body&&!Ext.lib.Region.getRegion(b).contains(Ext.lib.Region.getRegion(d.dom))){return}}}e=e||this.focusEl;e.focus.defer(10,e)},setAnimateTarget:function(a){a=Ext.get(a);this.animateTarget=a},beforeShow:function(){delete this.el.lastXY;delete this.el.lastLT;if(this.x===undefined||this.y===undefined){var a=this.el.getAlignToXY(this.container,"c-c");var b=this.el.translatePoints(a[0],a[1]);this.x=this.x===undefined?b.left:this.x;this.y=this.y===undefined?b.top:this.y}this.el.setLeftTop(this.x,this.y);if(this.expandOnShow){this.expand(false)}if(this.modal){Ext.getBody().addClass("x-body-masked");this.mask.setSize(Ext.lib.Dom.getViewWidth(true),Ext.lib.Dom.getViewHeight(true));this.mask.show()}},show:function(c,a,b){if(!this.rendered){this.render(Ext.getBody())}if(this.hidden===false){this.toFront();return this}if(this.fireEvent("beforeshow",this)===false){return this}if(a){this.on("show",a,b,{single:true})}this.hidden=false;if(Ext.isDefined(c)){this.setAnimateTarget(c)}this.beforeShow();if(this.animateTarget){this.animShow()}else{this.afterShow()}return this},afterShow:function(b){if(this.isDestroyed){return false}this.proxy.hide();this.el.setStyle("display","block");this.el.show();if(this.maximized){this.fitContainer()}if(Ext.isMac&&Ext.isGecko2){this.cascade(this.setAutoScroll)}if(this.monitorResize||this.modal||this.constrain||this.constrainHeader){Ext.EventManager.onWindowResize(this.onWindowResize,this)}this.doConstrain();this.doLayout();if(this.keyMap){this.keyMap.enable()}this.toFront();this.updateHandles();if(b&&(Ext.isIE||Ext.isWebKit)){var a=this.getSize();this.onResize(a.width,a.height)}this.onShow();this.fireEvent("show",this)},animShow:function(){this.proxy.show();this.proxy.setBox(this.animateTarget.getBox());this.proxy.setOpacity(0);var a=this.getBox();this.el.setStyle("display","none");this.proxy.shift(Ext.apply(a,{callback:this.afterShow.createDelegate(this,[true],false),scope:this,easing:"easeNone",duration:this.showAnimDuration,opacity:0.5}))},hide:function(c,a,b){if(this.hidden||this.fireEvent("beforehide",this)===false){return this}if(a){this.on("hide",a,b,{single:true})}this.hidden=true;if(c!==undefined){this.setAnimateTarget(c)}if(this.modal){this.mask.hide();Ext.getBody().removeClass("x-body-masked")}if(this.animateTarget){this.animHide()}else{this.el.hide();this.afterHide()}return this},afterHide:function(){this.proxy.hide();if(this.monitorResize||this.modal||this.constrain||this.constrainHeader){Ext.EventManager.removeResizeListener(this.onWindowResize,this)}if(this.keyMap){this.keyMap.disable()}this.onHide();this.fireEvent("hide",this)},animHide:function(){this.proxy.setOpacity(0.5);this.proxy.show();var a=this.getBox(false);this.proxy.setBox(a);this.el.hide();this.proxy.shift(Ext.apply(this.animateTarget.getBox(),{callback:this.afterHide,scope:this,duration:this.hideAnimDuration,easing:"easeNone",opacity:0}))},onShow:Ext.emptyFn,onHide:Ext.emptyFn,onWindowResize:function(){if(this.maximized){this.fitContainer()}if(this.modal){this.mask.setSize("100%","100%");var a=this.mask.dom.offsetHeight;this.mask.setSize(Ext.lib.Dom.getViewWidth(true),Ext.lib.Dom.getViewHeight(true))}this.doConstrain()},doConstrain:function(){if(this.constrain||this.constrainHeader){var b;if(this.constrain){b={right:this.el.shadowOffset,left:this.el.shadowOffset,bottom:this.el.shadowOffset}}else{var a=this.getSize();b={right:-(a.width-100),bottom:-(a.height-25+this.el.getConstrainOffset())}}var c=this.el.getConstrainToXY(this.container,true,b);if(c){this.setPosition(c[0],c[1])}}},ghost:function(a){var c=this.createGhost(a);var b=this.getBox(true);c.setLeftTop(b.x,b.y);c.setWidth(b.width);this.el.hide();this.activeGhost=c;return c},unghost:function(b,a){if(!this.activeGhost){return}if(b!==false){this.el.show();this.focus.defer(10,this);if(Ext.isMac&&Ext.isGecko2){this.cascade(this.setAutoScroll)}}if(a!==false){this.setPosition(this.activeGhost.getLeft(true),this.activeGhost.getTop(true))}this.activeGhost.hide();this.activeGhost.remove();delete this.activeGhost},minimize:function(){this.fireEvent("minimize",this);return this},close:function(){if(this.fireEvent("beforeclose",this)!==false){if(this.hidden){this.doClose()}else{this.hide(null,this.doClose,this)}}},doClose:function(){this.fireEvent("close",this);this.destroy()},maximize:function(){if(!this.maximized){this.expand(false);this.restoreSize=this.getSize();this.restorePos=this.getPosition(true);if(this.maximizable){this.tools.maximize.hide();this.tools.restore.show()}this.maximized=true;this.el.disableShadow();if(this.dd){this.dd.lock()}if(this.collapsible){this.tools.toggle.hide()}this.el.addClass("x-window-maximized");this.container.addClass("x-window-maximized-ct");this.setPosition(0,0);this.fitContainer();this.fireEvent("maximize",this)}return this},restore:function(){if(this.maximized){var a=this.tools;this.el.removeClass("x-window-maximized");if(a.restore){a.restore.hide()}if(a.maximize){a.maximize.show()}this.setPosition(this.restorePos[0],this.restorePos[1]);this.setSize(this.restoreSize.width,this.restoreSize.height);delete this.restorePos;delete this.restoreSize;this.maximized=false;this.el.enableShadow(true);if(this.dd){this.dd.unlock()}if(this.collapsible&&a.toggle){a.toggle.show()}this.container.removeClass("x-window-maximized-ct");this.doConstrain();this.fireEvent("restore",this)}return this},toggleMaximize:function(){return this[this.maximized?"restore":"maximize"]()},fitContainer:function(){var a=this.container.getViewSize(false);this.setSize(a.width,a.height)},setZIndex:function(a){if(this.modal){this.mask.setStyle("z-index",a)}this.el.setZIndex(++a);a+=5;if(this.resizer){this.resizer.proxy.setStyle("z-index",++a)}this.lastZIndex=a},alignTo:function(b,a,c){var d=this.el.getAlignToXY(b,a,c);this.setPagePosition(d[0],d[1]);return this},anchorTo:function(c,e,d,b){this.clearAnchor();this.anchorTarget={el:c,alignment:e,offsets:d};Ext.EventManager.onWindowResize(this.doAnchor,this);var a=typeof b;if(a!="undefined"){Ext.EventManager.on(window,"scroll",this.doAnchor,this,{buffer:a=="number"?b:50})}return this.doAnchor()},doAnchor:function(){var a=this.anchorTarget;this.alignTo(a.el,a.alignment,a.offsets);return this},clearAnchor:function(){if(this.anchorTarget){Ext.EventManager.removeResizeListener(this.doAnchor,this);Ext.EventManager.un(window,"scroll",this.doAnchor,this);delete this.anchorTarget}return this},toFront:function(a){if(this.manager.bringToFront(this)){if(!a||!a.getTarget().focus){this.focus()}}return this},setActive:function(a){if(a){if(!this.maximized){this.el.enableShadow(true)}this.fireEvent("activate",this)}else{this.el.disableShadow();this.fireEvent("deactivate",this)}},toBack:function(){this.manager.sendToBack(this);return this},center:function(){var a=this.el.getAlignToXY(this.container,"c-c");this.setPagePosition(a[0],a[1]);return this}});Ext.reg("window",Ext.Window);Ext.Window.DD=Ext.extend(Ext.dd.DD,{constructor:function(a){this.win=a;Ext.Window.DD.superclass.constructor.call(this,a.el.id,"WindowDD-"+a.id);this.setHandleElId(a.header.id);this.scroll=false},moveOnly:true,headerOffsets:[100,25],startDrag:function(){var a=this.win;this.proxy=a.ghost(a.initialConfig.cls);if(a.constrain!==false){var c=a.el.shadowOffset;this.constrainTo(a.container,{right:c,left:c,bottom:c})}else{if(a.constrainHeader!==false){var b=this.proxy.getSize();this.constrainTo(a.container,{right:-(b.width-this.headerOffsets[0]),bottom:-(b.height-this.headerOffsets[1])})}}},b4Drag:Ext.emptyFn,onDrag:function(a){this.alignElWithMouse(this.proxy,a.getPageX(),a.getPageY())},endDrag:function(a){this.win.unghost();this.win.saveState()}});Ext.WindowGroup=function(){var g={};var d=[];var e=null;var c=function(j,i){return(!j._lastAccess||j._lastAccess<i._lastAccess)?-1:1};var h=function(){var l=d,j=l.length;if(j>0){l.sort(c);var k=l[0].manager.zseed;for(var m=0;m<j;m++){var n=l[m];if(n&&!n.hidden){n.setZIndex(k+(m*10))}}}a()};var b=function(i){if(i!=e){if(e){e.setActive(false)}e=i;if(i){i.setActive(true)}}};var a=function(){for(var j=d.length-1;j>=0;--j){if(!d[j].hidden){b(d[j]);return}}b(null)};return{zseed:9000,register:function(i){if(i.manager){i.manager.unregister(i)}i.manager=this;g[i.id]=i;d.push(i);i.on("hide",a)},unregister:function(i){delete i.manager;delete g[i.id];i.un("hide",a);d.remove(i)},get:function(i){return typeof i=="object"?i:g[i]},bringToFront:function(i){i=this.get(i);if(i!=e){i._lastAccess=new Date().getTime();h();return true}return false},sendToBack:function(i){i=this.get(i);i._lastAccess=-(new Date().getTime());h();return i},hideAll:function(){for(var i in g){if(g[i]&&typeof g[i]!="function"&&g[i].isVisible()){g[i].hide()}}},getActive:function(){return e},getBy:function(l,k){var m=[];for(var j=d.length-1;j>=0;--j){var n=d[j];if(l.call(k||n,n)!==false){m.push(n)}}return m},each:function(j,i){for(var k in g){if(g[k]&&typeof g[k]!="function"){if(j.call(i||g[k],g[k])===false){return}}}}}};Ext.WindowMgr=new Ext.WindowGroup();Ext.MessageBox=function(){var u,b,q,t,h,l,s,a,n,p,j,g,r,v,o,i="",d="",m=["ok","yes","no","cancel"];var c=function(x){r[x].blur();if(u.isVisible()){u.hide();w();Ext.callback(b.fn,b.scope||window,[x,v.dom.value,b],1)}};var w=function(){if(b&&b.cls){u.el.removeClass(b.cls)}n.reset()};var e=function(z,x,y){if(b&&b.closable!==false){u.hide();w()}if(y){y.stopEvent()}};var k=function(x){var z=0,y;if(!x){Ext.each(m,function(A){r[A].hide()});return z}u.footer.dom.style.display="";Ext.iterate(r,function(A,B){y=x[A];if(y){B.show();B.setText(Ext.isString(y)?y:Ext.MessageBox.buttonText[A]);z+=B.getEl().getWidth()+15}else{B.hide()}});return z};return{getDialog:function(x){if(!u){var z=[];r={};Ext.each(m,function(A){z.push(r[A]=new Ext.Button({text:this.buttonText[A],handler:c.createCallback(A),hideMode:"offsets"}))},this);u=new Ext.Window({autoCreate:true,title:x,resizable:false,constrain:true,constrainHeader:true,minimizable:false,maximizable:false,stateful:false,modal:true,shim:true,buttonAlign:"center",width:400,height:100,minHeight:80,plain:true,footer:true,closable:true,close:function(){if(b&&b.buttons&&b.buttons.no&&!b.buttons.cancel){c("no")}else{c("cancel")}},fbar:new Ext.Toolbar({items:z,enableOverflow:false})});u.render(document.body);u.getEl().addClass("x-window-dlg");q=u.mask;h=u.body.createChild({html:'<div class="ext-mb-icon"></div><div class="ext-mb-content"><span class="ext-mb-text"></span><br /><div class="ext-mb-fix-cursor"><input type="text" class="ext-mb-input" /><textarea class="ext-mb-textarea"></textarea></div></div>'});j=Ext.get(h.dom.firstChild);var y=h.dom.childNodes[1];l=Ext.get(y.firstChild);s=Ext.get(y.childNodes[2].firstChild);s.enableDisplayMode();s.addKeyListener([10,13],function(){if(u.isVisible()&&b&&b.buttons){if(b.buttons.ok){c("ok")}else{if(b.buttons.yes){c("yes")}}}});a=Ext.get(y.childNodes[2].childNodes[1]);a.enableDisplayMode();n=new Ext.ProgressBar({renderTo:h});h.createChild({cls:"x-clear"})}return u},updateText:function(A){if(!u.isVisible()&&!b.width){u.setSize(this.maxWidth,100)}l.update(A?A+" ":"&#160;");var y=d!=""?(j.getWidth()+j.getMargins("lr")):0,C=l.getWidth()+l.getMargins("lr"),z=u.getFrameWidth("lr"),B=u.body.getFrameWidth("lr"),x;x=Math.max(Math.min(b.width||y+C+z+B,b.maxWidth||this.maxWidth),Math.max(b.minWidth||this.minWidth,o||0));if(b.prompt===true){v.setWidth(x-y-z-B)}if(b.progress===true||b.wait===true){n.setSize(x-y-z-B)}if(Ext.isIE9m&&x==o){x+=4}l.update(A||"&#160;");u.setSize(x,"auto").center();return this},updateProgress:function(y,x,z){n.updateProgress(y,x);if(z){this.updateText(z)}return this},isVisible:function(){return u&&u.isVisible()},hide:function(){var x=u?u.activeGhost:null;if(this.isVisible()||x){u.hide();w();if(x){u.unghost(false,false)}}return this},show:function(A){if(this.isVisible()){this.hide()}b=A;var B=this.getDialog(b.title||"&#160;");B.setTitle(b.title||"&#160;");var x=(b.closable!==false&&b.progress!==true&&b.wait!==true);B.tools.close.setDisplayed(x);v=s;b.prompt=b.prompt||(b.multiline?true:false);if(b.prompt){if(b.multiline){s.hide();a.show();a.setHeight(Ext.isNumber(b.multiline)?b.multiline:this.defaultTextHeight);v=a}else{s.show();a.hide()}}else{s.hide();a.hide()}v.dom.value=b.value||"";if(b.prompt){B.focusEl=v}else{var z=b.buttons;var y=null;if(z&&z.ok){y=r.ok}else{if(z&&z.yes){y=r.yes}}if(y){B.focusEl=y}}if(Ext.isDefined(b.iconCls)){B.setIconClass(b.iconCls)}this.setIcon(Ext.isDefined(b.icon)?b.icon:i);o=k(b.buttons);n.setVisible(b.progress===true||b.wait===true);this.updateProgress(0,b.progressText);this.updateText(b.msg);if(b.cls){B.el.addClass(b.cls)}B.proxyDrag=b.proxyDrag===true;B.modal=b.modal!==false;B.mask=b.modal!==false?q:false;if(!B.isVisible()){document.body.appendChild(u.el.dom);B.setAnimateTarget(b.animEl);B.on("show",function(){if(x===true){B.keyMap.enable()}else{B.keyMap.disable()}},this,{single:true});B.show(b.animEl)}if(b.wait===true){n.wait(b.waitConfig)}return this},setIcon:function(x){if(!u){i=x;return}i=undefined;if(x&&x!=""){j.removeClass("x-hidden");j.replaceClass(d,x);h.addClass("x-dlg-icon");d=x}else{j.replaceClass(d,"x-hidden");h.removeClass("x-dlg-icon");d=""}return this},progress:function(z,y,x){this.show({title:z,msg:y,buttons:false,progress:true,closable:false,minWidth:this.minProgressWidth,progressText:x});return this},wait:function(z,y,x){this.show({title:y,msg:z,buttons:false,closable:false,wait:true,modal:true,minWidth:this.minProgressWidth,waitConfig:x});return this},alert:function(A,z,y,x){this.show({title:A,msg:z,buttons:this.OK,fn:y,scope:x,minWidth:this.minWidth});return this},confirm:function(A,z,y,x){this.show({title:A,msg:z,buttons:this.YESNO,fn:y,scope:x,icon:this.QUESTION,minWidth:this.minWidth});return this},prompt:function(C,B,z,y,x,A){this.show({title:C,msg:B,buttons:this.OKCANCEL,fn:z,minWidth:this.minPromptWidth,scope:y,prompt:true,multiline:x,value:A});return this},OK:{ok:true},CANCEL:{cancel:true},OKCANCEL:{ok:true,cancel:true},YESNO:{yes:true,no:true},YESNOCANCEL:{yes:true,no:true,cancel:true},INFO:"ext-mb-info",WARNING:"ext-mb-warning",QUESTION:"ext-mb-question",ERROR:"ext-mb-error",defaultTextHeight:75,maxWidth:600,minWidth:100,minProgressWidth:250,minPromptWidth:250,buttonText:{ok:"OK",cancel:"Cancel",yes:"Yes",no:"No"}}}();Ext.Msg=Ext.MessageBox;Ext.dd.PanelProxy=Ext.extend(Object,{constructor:function(a,b){this.panel=a;this.id=this.panel.id+"-ddproxy";Ext.apply(this,b)},insertProxy:true,setStatus:Ext.emptyFn,reset:Ext.emptyFn,update:Ext.emptyFn,stop:Ext.emptyFn,sync:Ext.emptyFn,getEl:function(){return this.ghost},getGhost:function(){return this.ghost},getProxy:function(){return this.proxy},hide:function(){if(this.ghost){if(this.proxy){this.proxy.remove();delete this.proxy}this.panel.el.dom.style.display="";this.ghost.remove();delete this.ghost}},show:function(){if(!this.ghost){this.ghost=this.panel.createGhost(this.panel.initialConfig.cls,undefined,Ext.getBody());this.ghost.setXY(this.panel.el.getXY());if(this.insertProxy){this.proxy=this.panel.el.insertSibling({cls:"x-panel-dd-spacer"});this.proxy.setSize(this.panel.getSize())}this.panel.el.dom.style.display="none"}},repair:function(b,c,a){this.hide();if(typeof c=="function"){c.call(a||this)}},moveProxy:function(a,b){if(this.proxy){a.insertBefore(this.proxy.dom,b)}}});Ext.Panel.DD=Ext.extend(Ext.dd.DragSource,{constructor:function(b,a){this.panel=b;this.dragData={panel:b};this.proxy=new Ext.dd.PanelProxy(b,a);Ext.Panel.DD.superclass.constructor.call(this,b.el,a);var d=b.header,c=b.body;if(d){this.setHandleElId(d.id);c=b.header}c.setStyle("cursor","move");this.scroll=false},showFrame:Ext.emptyFn,startDrag:Ext.emptyFn,b4StartDrag:function(a,b){this.proxy.show()},b4MouseDown:function(b){var a=b.getPageX(),c=b.getPageY();this.autoOffset(a,c)},onInitDrag:function(a,b){this.onStartDrag(a,b);return true},createFrame:Ext.emptyFn,getDragEl:function(a){return this.proxy.ghost.dom},endDrag:function(a){this.proxy.hide();this.panel.saveState()},autoOffset:function(a,b){a-=this.startPageX;b-=this.startPageY;this.setDelta(a,b)}});Ext.state.Provider=Ext.extend(Ext.util.Observable,{constructor:function(){this.addEvents("statechange");this.state={};Ext.state.Provider.superclass.constructor.call(this)},get:function(b,a){return typeof this.state[b]=="undefined"?a:this.state[b]},clear:function(a){delete this.state[a];this.fireEvent("statechange",this,a,null)},set:function(a,b){this.state[a]=b;this.fireEvent("statechange",this,a,b)},decodeValue:function(b){var e=/^(a|n|d|b|s|o|e)\:(.*)$/,h=e.exec(unescape(b)),d,c,a,g;if(!h||!h[1]){return}c=h[1];a=h[2];switch(c){case"e":return null;case"n":return parseFloat(a);case"d":return new Date(Date.parse(a));case"b":return(a=="1");case"a":d=[];if(a!=""){Ext.each(a.split("^"),function(i){d.push(this.decodeValue(i))},this)}return d;case"o":d={};if(a!=""){Ext.each(a.split("^"),function(i){g=i.split("=");d[g[0]]=this.decodeValue(g[1])},this)}return d;default:return a}},encodeValue:function(c){var b,g="",e=0,a,d;if(c==null){return"e:1"}else{if(typeof c=="number"){b="n:"+c}else{if(typeof c=="boolean"){b="b:"+(c?"1":"0")}else{if(Ext.isDate(c)){b="d:"+c.toGMTString()}else{if(Ext.isArray(c)){for(a=c.length;e<a;e++){g+=this.encodeValue(c[e]);if(e!=a-1){g+="^"}}b="a:"+g}else{if(typeof c=="object"){for(d in c){if(typeof c[d]!="function"&&c[d]!==undefined){g+=d+"="+this.encodeValue(c[d])+"^"}}b="o:"+g.substring(0,g.length-1)}else{b="s:"+c}}}}}}return escape(b)}});Ext.state.Manager=function(){var a=new Ext.state.Provider();return{setProvider:function(b){a=b},get:function(c,b){return a.get(c,b)},set:function(b,c){a.set(b,c)},clear:function(b){a.clear(b)},getProvider:function(){return a}}}();Ext.state.CookieProvider=Ext.extend(Ext.state.Provider,{constructor:function(a){Ext.state.CookieProvider.superclass.constructor.call(this);this.path="/";this.expires=new Date(new Date().getTime()+(1000*60*60*24*7));this.domain=null;this.secure=false;Ext.apply(this,a);this.state=this.readCookies()},set:function(a,b){if(typeof b=="undefined"||b===null){this.clear(a);return}this.setCookie(a,b);Ext.state.CookieProvider.superclass.set.call(this,a,b)},clear:function(a){this.clearCookie(a);Ext.state.CookieProvider.superclass.clear.call(this,a)},readCookies:function(){var d={},h=document.cookie+";",b=/\s?(.*?)=(.*?);/g,g,a,e;while((g=b.exec(h))!=null){a=g[1];e=g[2];if(a&&a.substring(0,3)=="ys-"){d[a.substr(3)]=this.decodeValue(e)}}return d},setCookie:function(a,b){document.cookie="ys-"+a+"="+this.encodeValue(b)+((this.expires==null)?"":("; expires="+this.expires.toGMTString()))+((this.path==null)?"":("; path="+this.path))+((this.domain==null)?"":("; domain="+this.domain))+((this.secure==true)?"; secure":"")},clearCookie:function(a){document.cookie="ys-"+a+"=null; expires=Thu, 01-Jan-70 00:00:01 GMT"+((this.path==null)?"":("; path="+this.path))+((this.domain==null)?"":("; domain="+this.domain))+((this.secure==true)?"; secure":"")}});Ext.DataView=Ext.extend(Ext.BoxComponent,{selectedClass:"x-view-selected",emptyText:"",deferEmptyText:true,trackOver:false,blockRefresh:false,last:false,initComponent:function(){Ext.DataView.superclass.initComponent.call(this);if(Ext.isString(this.tpl)||Ext.isArray(this.tpl)){this.tpl=new Ext.XTemplate(this.tpl)}this.addEvents("beforeclick","click","mouseenter","mouseleave","containerclick","dblclick","contextmenu","containercontextmenu","selectionchange","beforeselect");this.store=Ext.StoreMgr.lookup(this.store);this.all=new Ext.CompositeElementLite();this.selected=new Ext.CompositeElementLite()},afterRender:function(){Ext.DataView.superclass.afterRender.call(this);this.mon(this.getTemplateTarget(),{click:this.onClick,dblclick:this.onDblClick,contextmenu:this.onContextMenu,scope:this});if(this.overClass||this.trackOver){this.mon(this.getTemplateTarget(),{mouseover:this.onMouseOver,mouseout:this.onMouseOut,scope:this})}if(this.store){this.bindStore(this.store,true)}},refresh:function(){this.clearSelections(false,true);var b=this.getTemplateTarget(),a=this.store.getRange();b.update("");if(a.length<1){if(!this.deferEmptyText||this.hasSkippedEmptyText){b.update(this.emptyText)}this.all.clear()}else{this.tpl.overwrite(b,this.collectData(a,0));this.all.fill(Ext.query(this.itemSelector,b.dom));this.updateIndexes(0)}this.hasSkippedEmptyText=true},getTemplateTarget:function(){return this.el},prepareData:function(a){return a},collectData:function(b,e){var d=[],c=0,a=b.length;for(;c<a;c++){d[d.length]=this.prepareData(b[c].data,e+c,b[c])}return d},bufferRender:function(a,b){var c=document.createElement("div");this.tpl.overwrite(c,this.collectData(a,b));return Ext.query(this.itemSelector,c)},onUpdate:function(g,a){var b=this.store.indexOf(a);if(b>-1){var e=this.isSelected(b),c=this.all.elements[b],d=this.bufferRender([a],b)[0];this.all.replaceElement(b,d,true);if(e){this.selected.replaceElement(c,d);this.all.item(b).addClass(this.selectedClass)}this.updateIndexes(b,b)}},onAdd:function(g,d,e){if(this.all.getCount()===0){this.refresh();return}var c=this.bufferRender(d,e),h,b=this.all.elements;if(e<this.all.getCount()){h=this.all.item(e).insertSibling(c,"before",true);b.splice.apply(b,[e,0].concat(c))}else{h=this.all.last().insertSibling(c,"after",true);b.push.apply(b,c)}this.updateIndexes(e)},onRemove:function(c,a,b){this.deselect(b);this.all.removeElement(b,true);this.updateIndexes(b);if(this.store.getCount()===0){this.refresh()}},refreshNode:function(a){this.onUpdate(this.store,this.store.getAt(a))},updateIndexes:function(d,c){var b=this.all.elements;d=d||0;c=c||((c===0)?0:(b.length-1));for(var a=d;a<=c;a++){b[a].viewIndex=a}},getStore:function(){return this.store},bindStore:function(a,b){if(!b&&this.store){if(a!==this.store&&this.store.autoDestroy){this.store.destroy()}else{this.store.un("beforeload",this.onBeforeLoad,this);this.store.un("datachanged",this.onDataChanged,this);this.store.un("add",this.onAdd,this);this.store.un("remove",this.onRemove,this);this.store.un("update",this.onUpdate,this);this.store.un("clear",this.refresh,this)}if(!a){this.store=null}}if(a){a=Ext.StoreMgr.lookup(a);a.on({scope:this,beforeload:this.onBeforeLoad,datachanged:this.onDataChanged,add:this.onAdd,remove:this.onRemove,update:this.onUpdate,clear:this.refresh})}this.store=a;if(a){this.refresh()}},onDataChanged:function(){if(this.blockRefresh!==true){this.refresh.apply(this,arguments)}},findItemFromChild:function(a){return Ext.fly(a).findParent(this.itemSelector,this.getTemplateTarget())},onClick:function(c){var b=c.getTarget(this.itemSelector,this.getTemplateTarget()),a;if(b){a=this.indexOf(b);if(this.onItemClick(b,a,c)!==false){this.fireEvent("click",this,a,b,c)}}else{if(this.fireEvent("containerclick",this,c)!==false){this.onContainerClick(c)}}},onContainerClick:function(a){this.clearSelections()},onContextMenu:function(b){var a=b.getTarget(this.itemSelector,this.getTemplateTarget());if(a){this.fireEvent("contextmenu",this,this.indexOf(a),a,b)}else{this.fireEvent("containercontextmenu",this,b)}},onDblClick:function(b){var a=b.getTarget(this.itemSelector,this.getTemplateTarget());if(a){this.fireEvent("dblclick",this,this.indexOf(a),a,b)}},onMouseOver:function(b){var a=b.getTarget(this.itemSelector,this.getTemplateTarget());if(a&&a!==this.lastItem){this.lastItem=a;Ext.fly(a).addClass(this.overClass);this.fireEvent("mouseenter",this,this.indexOf(a),a,b)}},onMouseOut:function(a){if(this.lastItem){if(!a.within(this.lastItem,true,true)){Ext.fly(this.lastItem).removeClass(this.overClass);this.fireEvent("mouseleave",this,this.indexOf(this.lastItem),this.lastItem,a);delete this.lastItem}}},onItemClick:function(b,a,c){if(this.fireEvent("beforeclick",this,a,b,c)===false){return false}if(this.multiSelect){this.doMultiSelection(b,a,c);c.preventDefault()}else{if(this.singleSelect){this.doSingleSelection(b,a,c);c.preventDefault()}}return true},doSingleSelection:function(b,a,c){if(c.ctrlKey&&this.isSelected(a)){this.deselect(a)}else{this.select(a,false)}},doMultiSelection:function(c,a,d){if(d.shiftKey&&this.last!==false){var b=this.last;this.selectRange(b,a,d.ctrlKey);this.last=b}else{if((d.ctrlKey||this.simpleSelect)&&this.isSelected(a)){this.deselect(a)}else{this.select(a,d.ctrlKey||d.shiftKey||this.simpleSelect)}}},getSelectionCount:function(){return this.selected.getCount()},getSelectedNodes:function(){return this.selected.elements},getSelectedIndexes:function(){var b=[],d=this.selected.elements,c=0,a=d.length;for(;c<a;c++){b.push(d[c].viewIndex)}return b},getSelectedRecords:function(){return this.getRecords(this.selected.elements)},getRecords:function(c){var b=[],d=0,a=c.length;for(;d<a;d++){b[b.length]=this.store.getAt(c[d].viewIndex)}return b},getRecord:function(a){return this.store.getAt(a.viewIndex)},clearSelections:function(a,b){if((this.multiSelect||this.singleSelect)&&this.selected.getCount()>0){if(!b){this.selected.removeClass(this.selectedClass)}this.selected.clear();this.last=false;if(!a){this.fireEvent("selectionchange",this,this.selected.elements)}}},isSelected:function(a){return this.selected.contains(this.getNode(a))},deselect:function(a){if(this.isSelected(a)){a=this.getNode(a);this.selected.removeElement(a);if(this.last==a.viewIndex){this.last=false}Ext.fly(a).removeClass(this.selectedClass);this.fireEvent("selectionchange",this,this.selected.elements)}},select:function(d,g,b){if(Ext.isArray(d)){if(!g){this.clearSelections(true)}for(var c=0,a=d.length;c<a;c++){this.select(d[c],true,true)}if(!b){this.fireEvent("selectionchange",this,this.selected.elements)}}else{var e=this.getNode(d);if(!g){this.clearSelections(true)}if(e&&!this.isSelected(e)){if(this.fireEvent("beforeselect",this,e,this.selected.elements)!==false){Ext.fly(e).addClass(this.selectedClass);this.selected.add(e);this.last=e.viewIndex;if(!b){this.fireEvent("selectionchange",this,this.selected.elements)}}}}},selectRange:function(c,a,b){if(!b){this.clearSelections(true)}this.select(this.getNodes(c,a),true)},getNode:function(b){if(Ext.isString(b)){return document.getElementById(b)}else{if(Ext.isNumber(b)){return this.all.elements[b]}else{if(b instanceof Ext.data.Record){var a=this.store.indexOf(b);return this.all.elements[a]}}}return b},getNodes:function(e,a){var d=this.all.elements,b=[],c;e=e||0;a=!Ext.isDefined(a)?Math.max(d.length-1,0):a;if(e<=a){for(c=e;c<=a&&d[c];c++){b.push(d[c])}}else{for(c=e;c>=a&&d[c];c--){b.push(d[c])}}return b},indexOf:function(a){a=this.getNode(a);if(Ext.isNumber(a.viewIndex)){return a.viewIndex}return this.all.indexOf(a)},onBeforeLoad:function(){if(this.loadingText){this.clearSelections(false,true);this.getTemplateTarget().update('<div class="loading-indicator">'+this.loadingText+"</div>");this.all.clear()}},onDestroy:function(){this.all.clear();this.selected.clear();Ext.DataView.superclass.onDestroy.call(this);this.bindStore(null)}});Ext.DataView.prototype.setStore=Ext.DataView.prototype.bindStore;Ext.reg("dataview",Ext.DataView);Ext.list.ListView=Ext.extend(Ext.DataView,{itemSelector:"dl",selectedClass:"x-list-selected",overClass:"x-list-over",scrollOffset:undefined,columnResize:true,columnSort:true,maxColumnWidth:Ext.isIE9m?99:100,initComponent:function(){if(this.columnResize){this.colResizer=new Ext.list.ColumnResizer(this.colResizer);this.colResizer.init(this)}if(this.columnSort){this.colSorter=new Ext.list.Sorter(this.columnSort);this.colSorter.init(this)}if(!this.internalTpl){this.internalTpl=new Ext.XTemplate('<div class="x-list-header"><div class="x-list-header-inner">','<tpl for="columns">','<div style="width:{[values.width*100]}%;text-align:{align};"><em class="x-unselectable" unselectable="on" id="',this.id,'-xlhd-{#}">',"{header}","</em></div>","</tpl>",'<div class="x-clear"></div>',"</div></div>",'<div class="x-list-body"><div class="x-list-body-inner">',"</div></div>")}if(!this.tpl){this.tpl=new Ext.XTemplate('<tpl for="rows">',"<dl>",'<tpl for="parent.columns">','<dt style="width:{[values.width*100]}%;text-align:{align};">','<em unselectable="on"<tpl if="cls"> class="{cls}</tpl>">',"{[values.tpl.apply(parent)]}","</em></dt>","</tpl>",'<div class="x-clear"></div>',"</dl>","</tpl>")}var l=this.columns,h=0,k=0,m=l.length,b=[];for(var g=0;g<m;g++){var n=l[g];if(!n.isColumn){n.xtype=n.xtype?(/^lv/.test(n.xtype)?n.xtype:"lv"+n.xtype):"lvcolumn";n=Ext.create(n)}if(n.width){h+=n.width*100;if(h>this.maxColumnWidth){n.width-=(h-this.maxColumnWidth)/100}k++}b.push(n)}l=this.columns=b;if(k<m){var d=m-k;if(h<this.maxColumnWidth){var a=((this.maxColumnWidth-h)/d)/100;for(var e=0;e<m;e++){var n=l[e];if(!n.width){n.width=a}}}}Ext.list.ListView.superclass.initComponent.call(this)},onRender:function(){this.autoEl={cls:"x-list-wrap"};Ext.list.ListView.superclass.onRender.apply(this,arguments);this.internalTpl.overwrite(this.el,{columns:this.columns});this.innerBody=Ext.get(this.el.dom.childNodes[1].firstChild);this.innerHd=Ext.get(this.el.dom.firstChild.firstChild);if(this.hideHeaders){this.el.dom.firstChild.style.display="none"}},getTemplateTarget:function(){return this.innerBody},collectData:function(){var a=Ext.list.ListView.superclass.collectData.apply(this,arguments);return{columns:this.columns,rows:a}},verifyInternalSize:function(){if(this.lastSize){this.onResize(this.lastSize.width,this.lastSize.height)}},onResize:function(c,e){var b=this.innerBody.dom,g=this.innerHd.dom,d=c-Ext.num(this.scrollOffset,Ext.getScrollBarWidth())+"px",a;if(!b){return}a=b.parentNode;if(Ext.isNumber(c)){if(this.reserveScrollOffset||((a.offsetWidth-a.clientWidth)>10)){b.style.width=d;g.style.width=d}else{b.style.width=c+"px";g.style.width=c+"px";setTimeout(function(){if((a.offsetWidth-a.clientWidth)>10){b.style.width=d;g.style.width=d}},10)}}if(Ext.isNumber(e)){a.style.height=Math.max(0,e-g.parentNode.offsetHeight)+"px"}},updateIndexes:function(){Ext.list.ListView.superclass.updateIndexes.apply(this,arguments);this.verifyInternalSize()},findHeaderIndex:function(g){g=g.dom||g;var a=g.parentNode,d=a.parentNode.childNodes,b=0,e;for(;e=d[b];b++){if(e==a){return b}}return -1},setHdWidths:function(){var d=this.innerHd.dom.getElementsByTagName("div"),c=0,b=this.columns,a=b.length;for(;c<a;c++){d[c].style.width=(b[c].width*100)+"%"}}});Ext.reg("listview",Ext.list.ListView);Ext.ListView=Ext.list.ListView;Ext.list.Column=Ext.extend(Object,{isColumn:true,align:"left",header:"",width:null,cls:"",constructor:function(a){if(!a.tpl){a.tpl=new Ext.XTemplate("{"+a.dataIndex+"}")}else{if(Ext.isString(a.tpl)){a.tpl=new Ext.XTemplate(a.tpl)}}Ext.apply(this,a)}});Ext.reg("lvcolumn",Ext.list.Column);Ext.list.NumberColumn=Ext.extend(Ext.list.Column,{format:"0,000.00",constructor:function(a){a.tpl=a.tpl||new Ext.XTemplate("{"+a.dataIndex+':number("'+(a.format||this.format)+'")}');Ext.list.NumberColumn.superclass.constructor.call(this,a)}});Ext.reg("lvnumbercolumn",Ext.list.NumberColumn);Ext.list.DateColumn=Ext.extend(Ext.list.Column,{format:"m/d/Y",constructor:function(a){a.tpl=a.tpl||new Ext.XTemplate("{"+a.dataIndex+':date("'+(a.format||this.format)+'")}');Ext.list.DateColumn.superclass.constructor.call(this,a)}});Ext.reg("lvdatecolumn",Ext.list.DateColumn);Ext.list.BooleanColumn=Ext.extend(Ext.list.Column,{trueText:"true",falseText:"false",undefinedText:"&#160;",constructor:function(e){e.tpl=e.tpl||new Ext.XTemplate("{"+e.dataIndex+":this.format}");var b=this.trueText,d=this.falseText,a=this.undefinedText;e.tpl.format=function(c){if(c===undefined){return a}if(!c||c==="false"){return d}return b};Ext.list.DateColumn.superclass.constructor.call(this,e)}});Ext.reg("lvbooleancolumn",Ext.list.BooleanColumn);Ext.list.ColumnResizer=Ext.extend(Ext.util.Observable,{minPct:0.05,constructor:function(a){Ext.apply(this,a);Ext.list.ColumnResizer.superclass.constructor.call(this)},init:function(a){this.view=a;a.on("render",this.initEvents,this)},initEvents:function(a){a.mon(a.innerHd,"mousemove",this.handleHdMove,this);this.tracker=new Ext.dd.DragTracker({onBeforeStart:this.onBeforeStart.createDelegate(this),onStart:this.onStart.createDelegate(this),onDrag:this.onDrag.createDelegate(this),onEnd:this.onEnd.createDelegate(this),tolerance:3,autoStart:300});this.tracker.initEl(a.innerHd);a.on("beforedestroy",this.tracker.destroy,this.tracker)},handleHdMove:function(i,d){var c=5,b=i.getPageX(),j=i.getTarget("em",3,true);if(j){var h=j.getRegion(),g=j.dom.style,a=j.dom.parentNode;if(b-h.left<=c&&a!=a.parentNode.firstChild){this.activeHd=Ext.get(a.previousSibling.firstChild);g.cursor=Ext.isWebKit?"e-resize":"col-resize"}else{if(h.right-b<=c&&a!=a.parentNode.lastChild.previousSibling){this.activeHd=j;g.cursor=Ext.isWebKit?"w-resize":"col-resize"}else{delete this.activeHd;g.cursor=""}}}},onBeforeStart:function(a){this.dragHd=this.activeHd;return !!this.dragHd},onStart:function(g){var d=this,b=d.view,c=d.dragHd,a=d.tracker.getXY()[0];d.proxy=b.el.createChild({cls:"x-list-resizer"});d.dragX=c.getX();d.headerIndex=b.findHeaderIndex(c);d.headersDisabled=b.disableHeaders;b.disableHeaders=true;d.proxy.setHeight(b.el.getHeight());d.proxy.setX(d.dragX);d.proxy.setWidth(a-d.dragX);this.setBoundaries()},setBoundaries:function(j){var k=this.view,h=this.headerIndex,c=k.innerHd.getWidth(),j=k.innerHd.getX(),b=Math.ceil(c*this.minPct),l=c-b,e=k.columns.length,d=k.innerHd.select("em",true),g=b+j,a=l+j,i;if(e==2){this.minX=g;this.maxX=a}else{i=d.item(h+2);this.minX=d.item(h).getX()+b;this.maxX=i?i.getX()-b:a;if(h==0){this.minX=g}else{if(h==e-2){this.maxX=a}}}},onDrag:function(c){var b=this,a=b.tracker.getXY()[0].constrain(b.minX,b.maxX);b.proxy.setWidth(a-this.dragX)},onEnd:function(i){var g=this.proxy.getWidth(),h=this.headerIndex,l=this.view,c=l.columns,b=l.innerHd.getWidth(),k=Math.ceil(g*l.maxColumnWidth/b)/100,d=this.headersDisabled,m=c[h],j=c[h+1],a=m.width+j.width;this.proxy.remove();m.width=k;j.width=a-k;delete this.dragHd;l.setHdWidths();l.refresh();setTimeout(function(){l.disableHeaders=d},100)}});Ext.ListView.ColumnResizer=Ext.list.ColumnResizer;Ext.list.Sorter=Ext.extend(Ext.util.Observable,{sortClasses:["sort-asc","sort-desc"],constructor:function(a){Ext.apply(this,a);Ext.list.Sorter.superclass.constructor.call(this)},init:function(a){this.view=a;a.on("render",this.initEvents,this)},initEvents:function(a){a.mon(a.innerHd,"click",this.onHdClick,this);a.innerHd.setStyle("cursor","pointer");a.mon(a.store,"datachanged",this.updateSortState,this);this.updateSortState.defer(10,this,[a.store])},updateSortState:function(c){var g=c.getSortState();if(!g){return}this.sortState=g;var e=this.view.columns,h=-1;for(var d=0,a=e.length;d<a;d++){if(e[d].dataIndex==g.field){h=d;break}}if(h!=-1){var b=g.direction;this.updateSortIcon(h,b)}},updateSortIcon:function(b,a){var d=this.sortClasses;var c=this.view.innerHd.select("em").removeClass(d);c.item(b).addClass(d[a=="DESC"?1:0])},onHdClick:function(c){var b=c.getTarget("em",3);if(b&&!this.view.disableHeaders){var a=this.view.findHeaderIndex(b);this.view.store.sort(this.view.columns[a].dataIndex)}}});Ext.ListView.Sorter=Ext.list.Sorter;Ext.TabPanel=Ext.extend(Ext.Panel,{deferredRender:true,tabWidth:120,minTabWidth:30,resizeTabs:false,enableTabScroll:false,scrollIncrement:0,scrollRepeatInterval:400,scrollDuration:0.35,animScroll:true,tabPosition:"top",baseCls:"x-tab-panel",autoTabs:false,autoTabSelector:"div.x-tab",activeTab:undefined,tabMargin:2,plain:false,wheelIncrement:20,idDelimiter:"__",itemCls:"x-tab-item",elements:"body",headerAsText:false,frame:false,hideBorders:true,initComponent:function(){this.frame=false;Ext.TabPanel.superclass.initComponent.call(this);this.addEvents("beforetabchange","tabchange","contextmenu");this.setLayout(new Ext.layout.CardLayout(Ext.apply({layoutOnCardChange:this.layoutOnTabChange,deferredRender:this.deferredRender},this.layoutConfig)));if(this.tabPosition=="top"){this.elements+=",header";this.stripTarget="header"}else{this.elements+=",footer";this.stripTarget="footer"}if(!this.stack){this.stack=Ext.TabPanel.AccessStack()}this.initItems()},onRender:function(c,a){Ext.TabPanel.superclass.onRender.call(this,c,a);if(this.plain){var g=this.tabPosition=="top"?"header":"footer";this[g].addClass("x-tab-panel-"+g+"-plain")}var b=this[this.stripTarget];this.stripWrap=b.createChild({cls:"x-tab-strip-wrap",cn:{tag:"ul",cls:"x-tab-strip x-tab-strip-"+this.tabPosition}});var e=(this.tabPosition=="bottom"?this.stripWrap:null);b.createChild({cls:"x-tab-strip-spacer"},e);this.strip=new Ext.Element(this.stripWrap.dom.firstChild);this.edge=this.strip.createChild({tag:"li",cls:"x-tab-edge",cn:[{tag:"span",cls:"x-tab-strip-text",cn:"&#160;"}]});this.strip.createChild({cls:"x-clear"});this.body.addClass("x-tab-panel-body-"+this.tabPosition);if(!this.itemTpl){var d=new Ext.Template('<li class="{cls}" id="{id}"><a class="x-tab-strip-close"></a>','<a class="x-tab-right" href="#"><em class="x-tab-left">','<span class="x-tab-strip-inner"><span class="x-tab-strip-text {iconCls}">{text}</span></span>',"</em></a></li>");d.disableFormats=true;d.compile();Ext.TabPanel.prototype.itemTpl=d}this.items.each(this.initTab,this)},afterRender:function(){Ext.TabPanel.superclass.afterRender.call(this);if(this.autoTabs){this.readTabs(false)}if(this.activeTab!==undefined){var a=Ext.isObject(this.activeTab)?this.activeTab:this.items.get(this.activeTab);delete this.activeTab;this.setActiveTab(a)}},initEvents:function(){Ext.TabPanel.superclass.initEvents.call(this);this.mon(this.strip,{scope:this,mousedown:this.onStripMouseDown,contextmenu:this.onStripContextMenu});if(this.enableTabScroll){this.mon(this.strip,"mousewheel",this.onWheel,this)}},findTargets:function(c){var b=null,a=c.getTarget("li:not(.x-tab-edge)",this.strip);if(a){b=this.getComponent(a.id.split(this.idDelimiter)[1]);if(b.disabled){return{close:null,item:null,el:null}}}return{close:c.getTarget(".x-tab-strip-close",this.strip),item:b,el:a}},onStripMouseDown:function(b){if(b.button!==0){return}b.preventDefault();var a=this.findTargets(b);if(a.close){if(a.item.fireEvent("beforeclose",a.item)!==false){a.item.fireEvent("close",a.item);this.remove(a.item)}return}if(a.item&&a.item!=this.activeTab){this.setActiveTab(a.item)}},onStripContextMenu:function(b){b.preventDefault();var a=this.findTargets(b);if(a.item){this.fireEvent("contextmenu",this,a.item,b)}},readTabs:function(d){if(d===true){this.items.each(function(h){this.remove(h)},this)}var c=this.el.query(this.autoTabSelector);for(var b=0,a=c.length;b<a;b++){var e=c[b],g=e.getAttribute("title");e.removeAttribute("title");this.add({title:g,contentEl:e})}},initTab:function(d,b){var e=this.strip.dom.childNodes[b],g=this.getTemplateArgs(d),c=e?this.itemTpl.insertBefore(e,g):this.itemTpl.append(this.strip,g),a="x-tab-strip-over",h=Ext.get(c);h.hover(function(){if(!d.disabled){h.addClass(a)}},function(){h.removeClass(a)});if(d.tabTip){h.child("span.x-tab-strip-text",true).qtip=d.tabTip}d.tabEl=c;h.select("a").on("click",function(i){if(!i.getPageX()){this.onStripMouseDown(i)}},this,{preventDefault:true});d.on({scope:this,disable:this.onItemDisabled,enable:this.onItemEnabled,titlechange:this.onItemTitleChanged,iconchange:this.onItemIconChanged,beforeshow:this.onBeforeShowItem})},getTemplateArgs:function(b){var a=b.closable?"x-tab-strip-closable":"";if(b.disabled){a+=" x-item-disabled"}if(b.iconCls){a+=" x-tab-with-icon"}if(b.tabCls){a+=" "+b.tabCls}return{id:this.id+this.idDelimiter+b.getItemId(),text:b.title,cls:a,iconCls:b.iconCls||""}},onAdd:function(b){Ext.TabPanel.superclass.onAdd.call(this,b);if(this.rendered){var a=this.items;this.initTab(b,a.indexOf(b));this.delegateUpdates()}},onBeforeAdd:function(b){var a=b.events?(this.items.containsKey(b.getItemId())?b:null):this.items.get(b);if(a){this.setActiveTab(b);return false}Ext.TabPanel.superclass.onBeforeAdd.apply(this,arguments);var c=b.elements;b.elements=c?c.replace(",header",""):c;b.border=(b.border===true)},onRemove:function(d){var b=Ext.get(d.tabEl);if(b){b.select("a").removeAllListeners();Ext.destroy(b)}Ext.TabPanel.superclass.onRemove.call(this,d);this.stack.remove(d);delete d.tabEl;d.un("disable",this.onItemDisabled,this);d.un("enable",this.onItemEnabled,this);d.un("titlechange",this.onItemTitleChanged,this);d.un("iconchange",this.onItemIconChanged,this);d.un("beforeshow",this.onBeforeShowItem,this);if(d==this.activeTab){var a=this.stack.next();if(a){this.setActiveTab(a)}else{if(this.items.getCount()>0){this.setActiveTab(0)}else{this.setActiveTab(null)}}}if(!this.destroying){this.delegateUpdates()}},onBeforeShowItem:function(a){if(a!=this.activeTab){this.setActiveTab(a);return false}},onItemDisabled:function(b){var a=this.getTabEl(b);if(a){Ext.fly(a).addClass("x-item-disabled")}this.stack.remove(b)},onItemEnabled:function(b){var a=this.getTabEl(b);if(a){Ext.fly(a).removeClass("x-item-disabled")}},onItemTitleChanged:function(b){var a=this.getTabEl(b);if(a){Ext.fly(a).child("span.x-tab-strip-text",true).innerHTML=b.title;this.delegateUpdates()}},onItemIconChanged:function(d,a,c){var b=this.getTabEl(d);if(b){b=Ext.get(b);b.child("span.x-tab-strip-text").replaceClass(c,a);b[Ext.isEmpty(a)?"removeClass":"addClass"]("x-tab-with-icon");this.delegateUpdates()}},getTabEl:function(a){var b=this.getComponent(a);return b?b.tabEl:null},onResize:function(){Ext.TabPanel.superclass.onResize.apply(this,arguments);this.delegateUpdates()},beginUpdate:function(){this.suspendUpdates=true},endUpdate:function(){this.suspendUpdates=false;this.delegateUpdates()},hideTabStripItem:function(b){b=this.getComponent(b);var a=this.getTabEl(b);if(a){a.style.display="none";this.delegateUpdates()}this.stack.remove(b)},unhideTabStripItem:function(b){b=this.getComponent(b);var a=this.getTabEl(b);if(a){a.style.display="";this.delegateUpdates()}},delegateUpdates:function(){var a=this.rendered;if(this.suspendUpdates){return}if(this.resizeTabs&&a){this.autoSizeTabs()}if(this.enableTabScroll&&a){this.autoScrollTabs()}},autoSizeTabs:function(){var h=this.items.length,b=this.tabPosition!="bottom"?"header":"footer",c=this[b].dom.offsetWidth,a=this[b].dom.clientWidth;if(!this.resizeTabs||h<1||!a){return}var k=Math.max(Math.min(Math.floor((a-4)/h)-this.tabMargin,this.tabWidth),this.minTabWidth);this.lastTabWidth=k;var m=this.strip.query("li:not(.x-tab-edge)");for(var e=0,j=m.length;e<j;e++){var l=m[e],n=Ext.fly(l).child(".x-tab-strip-inner",true),g=l.offsetWidth,d=n.offsetWidth;n.style.width=(k-(g-d))+"px"}},adjustBodyWidth:function(a){if(this.header){this.header.setWidth(a)}if(this.footer){this.footer.setWidth(a)}return a},setActiveTab:function(c){c=this.getComponent(c);if(this.fireEvent("beforetabchange",this,c,this.activeTab)===false){return}if(!this.rendered){this.activeTab=c;return}if(this.activeTab!=c){if(this.activeTab){var a=this.getTabEl(this.activeTab);if(a){Ext.fly(a).removeClass("x-tab-strip-active")}}this.activeTab=c;if(c){var b=this.getTabEl(c);Ext.fly(b).addClass("x-tab-strip-active");this.stack.add(c);this.layout.setActiveItem(c);this.delegateUpdates();if(this.scrolling){this.scrollToTab(c,this.animScroll)}}this.fireEvent("tabchange",this,c)}},getActiveTab:function(){return this.activeTab||null},getItem:function(a){return this.getComponent(a)},autoScrollTabs:function(){this.pos=this.tabPosition=="bottom"?this.footer:this.header;var h=this.items.length,d=this.pos.dom.offsetWidth,c=this.pos.dom.clientWidth,g=this.stripWrap,e=g.dom,b=e.offsetWidth,i=this.getScrollPos(),a=this.edge.getOffsetsTo(this.stripWrap)[0]+i;if(!this.enableTabScroll||b<20){return}if(h==0||a<=c){e.scrollLeft=0;g.setWidth(c);if(this.scrolling){this.scrolling=false;this.pos.removeClass("x-tab-scrolling");this.scrollLeft.hide();this.scrollRight.hide();if(Ext.isAir||Ext.isWebKit){e.style.marginLeft="";e.style.marginRight=""}}}else{if(!this.scrolling){this.pos.addClass("x-tab-scrolling");if(Ext.isAir||Ext.isWebKit){e.style.marginLeft="18px";e.style.marginRight="18px"}}c-=g.getMargins("lr");g.setWidth(c>20?c:20);if(!this.scrolling){if(!this.scrollLeft){this.createScrollers()}else{this.scrollLeft.show();this.scrollRight.show()}}this.scrolling=true;if(i>(a-c)){e.scrollLeft=a-c}else{this.scrollToTab(this.activeTab,false)}this.updateScrollButtons()}},createScrollers:function(){this.pos.addClass("x-tab-scrolling-"+this.tabPosition);var c=this.stripWrap.dom.offsetHeight;var a=this.pos.insertFirst({cls:"x-tab-scroller-left"});a.setHeight(c);a.addClassOnOver("x-tab-scroller-left-over");this.leftRepeater=new Ext.util.ClickRepeater(a,{interval:this.scrollRepeatInterval,handler:this.onScrollLeft,scope:this});this.scrollLeft=a;var b=this.pos.insertFirst({cls:"x-tab-scroller-right"});b.setHeight(c);b.addClassOnOver("x-tab-scroller-right-over");this.rightRepeater=new Ext.util.ClickRepeater(b,{interval:this.scrollRepeatInterval,handler:this.onScrollRight,scope:this});this.scrollRight=b},getScrollWidth:function(){return this.edge.getOffsetsTo(this.stripWrap)[0]+this.getScrollPos()},getScrollPos:function(){return parseInt(this.stripWrap.dom.scrollLeft,10)||0},getScrollArea:function(){return parseInt(this.stripWrap.dom.clientWidth,10)||0},getScrollAnim:function(){return{duration:this.scrollDuration,callback:this.updateScrollButtons,scope:this}},getScrollIncrement:function(){return this.scrollIncrement||(this.resizeTabs?this.lastTabWidth+2:100)},scrollToTab:function(e,a){if(!e){return}var c=this.getTabEl(e),h=this.getScrollPos(),d=this.getScrollArea(),g=Ext.fly(c).getOffsetsTo(this.stripWrap)[0]+h,b=g+c.offsetWidth;if(g<h){this.scrollTo(g,a)}else{if(b>(h+d)){this.scrollTo(b-d,a)}}},scrollTo:function(b,a){this.stripWrap.scrollTo("left",b,a?this.getScrollAnim():false);if(!a){this.updateScrollButtons()}},onWheel:function(g){var h=g.getWheelDelta()*this.wheelIncrement*-1;g.stopEvent();var i=this.getScrollPos(),c=i+h,a=this.getScrollWidth()-this.getScrollArea();var b=Math.max(0,Math.min(a,c));if(b!=i){this.scrollTo(b,false)}},onScrollRight:function(){var a=this.getScrollWidth()-this.getScrollArea(),c=this.getScrollPos(),b=Math.min(a,c+this.getScrollIncrement());if(b!=c){this.scrollTo(b,this.animScroll)}},onScrollLeft:function(){var b=this.getScrollPos(),a=Math.max(0,b-this.getScrollIncrement());if(a!=b){this.scrollTo(a,this.animScroll)}},updateScrollButtons:function(){var a=this.getScrollPos();this.scrollLeft[a===0?"addClass":"removeClass"]("x-tab-scroller-left-disabled");this.scrollRight[a>=(this.getScrollWidth()-this.getScrollArea())?"addClass":"removeClass"]("x-tab-scroller-right-disabled")},beforeDestroy:function(){Ext.destroy(this.leftRepeater,this.rightRepeater);this.deleteMembers("strip","edge","scrollLeft","scrollRight","stripWrap");this.activeTab=null;Ext.TabPanel.superclass.beforeDestroy.apply(this)}});Ext.reg("tabpanel",Ext.TabPanel);Ext.TabPanel.prototype.activate=Ext.TabPanel.prototype.setActiveTab;Ext.TabPanel.AccessStack=function(){var a=[];return{add:function(b){a.push(b);if(a.length>10){a.shift()}},remove:function(e){var d=[];for(var c=0,b=a.length;c<b;c++){if(a[c]!=e){d.push(a[c])}}a=d},next:function(){return a.pop()}}};Ext.Button=Ext.extend(Ext.BoxComponent,{hidden:false,disabled:false,pressed:false,enableToggle:false,menuAlign:"tl-bl?",type:"button",menuClassTarget:"tr:nth(2)",clickEvent:"click",handleMouseEvents:true,tooltipType:"qtip",buttonSelector:"button:first-child",scale:"small",iconAlign:"left",arrowAlign:"right",initComponent:function(){if(this.menu){if(Ext.isArray(this.menu)){this.menu={items:this.menu}}if(Ext.isObject(this.menu)){this.menu.ownerCt=this}this.menu=Ext.menu.MenuMgr.get(this.menu);this.menu.ownerCt=undefined}Ext.Button.superclass.initComponent.call(this);this.addEvents("click","toggle","mouseover","mouseout","menushow","menuhide","menutriggerover","menutriggerout");if(Ext.isString(this.toggleGroup)){this.enableToggle=true}},getTemplateArgs:function(){return[this.type,"x-btn-"+this.scale+" x-btn-icon-"+this.scale+"-"+this.iconAlign,this.getMenuClass(),this.cls,this.id]},setButtonClass:function(){if(this.useSetClass){if(!Ext.isEmpty(this.oldCls)){this.el.removeClass([this.oldCls,"x-btn-pressed"])}this.oldCls=(this.iconCls||this.icon)?(this.text?"x-btn-text-icon":"x-btn-icon"):"x-btn-noicon";this.el.addClass([this.oldCls,this.pressed?"x-btn-pressed":null])}},getMenuClass:function(){return this.menu?(this.arrowAlign!="bottom"?"x-btn-arrow":"x-btn-arrow-bottom"):""},onRender:function(c,a){if(!this.template){if(!Ext.Button.buttonTemplate){Ext.Button.buttonTemplate=new Ext.Template('<table id="{4}" cellspacing="0" class="x-btn {3}"><tbody class="{1}">','<tr><td class="x-btn-tl"><i>&#160;</i></td><td class="x-btn-tc"></td><td class="x-btn-tr"><i>&#160;</i></td></tr>','<tr><td class="x-btn-ml"><i>&#160;</i></td><td class="x-btn-mc"><em class="{2} x-unselectable" unselectable="on"><button type="{0}"></button></em></td><td class="x-btn-mr"><i>&#160;</i></td></tr>','<tr><td class="x-btn-bl"><i>&#160;</i></td><td class="x-btn-bc"></td><td class="x-btn-br"><i>&#160;</i></td></tr>',"</tbody></table>");Ext.Button.buttonTemplate.compile()}this.template=Ext.Button.buttonTemplate}var b,d=this.getTemplateArgs();if(a){b=this.template.insertBefore(a,d,true)}else{b=this.template.append(c,d,true)}this.btnEl=b.child(this.buttonSelector);this.mon(this.btnEl,{scope:this,focus:this.onFocus,blur:this.onBlur});this.initButtonEl(b,this.btnEl);Ext.ButtonToggleMgr.register(this)},initButtonEl:function(b,c){this.el=b;this.setIcon(this.icon);this.setText(this.text);this.setIconClass(this.iconCls);if(Ext.isDefined(this.tabIndex)){c.dom.tabIndex=this.tabIndex}if(this.tooltip){this.setTooltip(this.tooltip,true)}if(this.handleMouseEvents){this.mon(b,{scope:this,mouseover:this.onMouseOver,mousedown:this.onMouseDown})}if(this.menu){this.mon(this.menu,{scope:this,show:this.onMenuShow,hide:this.onMenuHide})}if(this.repeat){var a=new Ext.util.ClickRepeater(b,Ext.isObject(this.repeat)?this.repeat:{});this.mon(a,"click",this.onRepeatClick,this)}else{this.mon(b,this.clickEvent,this.onClick,this)}},afterRender:function(){Ext.Button.superclass.afterRender.call(this);this.useSetClass=true;this.setButtonClass();this.doc=Ext.getDoc();this.doAutoWidth()},setIconClass:function(a){this.iconCls=a;if(this.el){this.btnEl.dom.className="";this.btnEl.addClass(["x-btn-text",a||""]);this.setButtonClass()}return this},setTooltip:function(b,a){if(this.rendered){if(!a){this.clearTip()}if(Ext.isObject(b)){Ext.QuickTips.register(Ext.apply({target:this.btnEl.id},b));this.tooltip=b}else{this.btnEl.dom[this.tooltipType]=b}}else{this.tooltip=b}return this},clearTip:function(){if(Ext.isObject(this.tooltip)){Ext.QuickTips.unregister(this.btnEl)}},beforeDestroy:function(){if(this.rendered){this.clearTip()}if(this.menu&&this.destroyMenu!==false){Ext.destroy(this.btnEl,this.menu)}Ext.destroy(this.repeater)},onDestroy:function(){if(this.rendered){this.doc.un("mouseover",this.monitorMouseOver,this);this.doc.un("mouseup",this.onMouseUp,this);delete this.doc;delete this.btnEl;Ext.ButtonToggleMgr.unregister(this)}Ext.Button.superclass.onDestroy.call(this)},doAutoWidth:function(){if(this.autoWidth!==false&&this.el&&this.text&&this.width===undefined){this.el.setWidth("auto");if(Ext.isIE7&&Ext.isStrict){var a=this.btnEl;if(a&&a.getWidth()>20){a.clip();a.setWidth(Ext.util.TextMetrics.measure(a,this.text).width+a.getFrameWidth("lr"))}}if(this.minWidth){if(this.el.getWidth()<this.minWidth){this.el.setWidth(this.minWidth)}}}},setHandler:function(b,a){this.handler=b;this.scope=a;return this},setText:function(a){this.text=a;if(this.el){this.btnEl.update(a||"&#160;");this.setButtonClass()}this.doAutoWidth();return this},setIcon:function(a){this.icon=a;if(this.el){this.btnEl.setStyle("background-image",a?"url("+a+")":"");this.setButtonClass()}return this},getText:function(){return this.text},toggle:function(b,a){b=b===undefined?!this.pressed:!!b;if(b!=this.pressed){if(this.rendered){this.el[b?"addClass":"removeClass"]("x-btn-pressed")}this.pressed=b;if(!a){this.fireEvent("toggle",this,b);if(this.toggleHandler){this.toggleHandler.call(this.scope||this,this,b)}}}return this},onDisable:function(){this.onDisableChange(true)},onEnable:function(){this.onDisableChange(false)},onDisableChange:function(a){if(this.el){if(!Ext.isIE6||!this.text){this.el[a?"addClass":"removeClass"](this.disabledClass)}this.el.dom.disabled=a}this.disabled=a},showMenu:function(){if(this.rendered&&this.menu){if(this.tooltip){Ext.QuickTips.getQuickTip().cancelShow(this.btnEl)}if(this.menu.isVisible()){this.menu.hide()}this.menu.ownerCt=this;this.menu.show(this.el,this.menuAlign)}return this},hideMenu:function(){if(this.hasVisibleMenu()){this.menu.hide()}return this},hasVisibleMenu:function(){return this.menu&&this.menu.ownerCt==this&&this.menu.isVisible()},onRepeatClick:function(a,b){this.onClick(b)},onClick:function(a){if(a){a.preventDefault()}if(a.button!==0){return}if(!this.disabled){this.doToggle();if(this.menu&&!this.hasVisibleMenu()&&!this.ignoreNextClick){this.showMenu()}this.fireEvent("click",this,a);if(this.handler){this.handler.call(this.scope||this,this,a)}}},doToggle:function(){if(this.enableToggle&&(this.allowDepress!==false||!this.pressed)){this.toggle()}},isMenuTriggerOver:function(b,a){return this.menu&&!a},isMenuTriggerOut:function(b,a){return this.menu&&!a},onMouseOver:function(b){if(!this.disabled){var a=b.within(this.el,true);if(!a){this.el.addClass("x-btn-over");if(!this.monitoringMouseOver){this.doc.on("mouseover",this.monitorMouseOver,this);this.monitoringMouseOver=true}this.fireEvent("mouseover",this,b)}if(this.isMenuTriggerOver(b,a)){this.fireEvent("menutriggerover",this,this.menu,b)}}},monitorMouseOver:function(a){if(a.target!=this.el.dom&&!a.within(this.el)){if(this.monitoringMouseOver){this.doc.un("mouseover",this.monitorMouseOver,this);this.monitoringMouseOver=false}this.onMouseOut(a)}},onMouseOut:function(b){var a=b.within(this.el)&&b.target!=this.el.dom;this.el.removeClass("x-btn-over");this.fireEvent("mouseout",this,b);if(this.isMenuTriggerOut(b,a)){this.fireEvent("menutriggerout",this,this.menu,b)}},focus:function(){this.btnEl.focus()},blur:function(){this.btnEl.blur()},onFocus:function(a){if(!this.disabled){this.el.addClass("x-btn-focus")}},onBlur:function(a){this.el.removeClass("x-btn-focus")},getClickEl:function(b,a){return this.el},onMouseDown:function(a){if(!this.disabled&&a.button===0){this.getClickEl(a).addClass("x-btn-click");this.doc.on("mouseup",this.onMouseUp,this)}},onMouseUp:function(a){if(a.button===0){this.getClickEl(a,true).removeClass("x-btn-click");this.doc.un("mouseup",this.onMouseUp,this)}},onMenuShow:function(a){if(this.menu.ownerCt==this){this.menu.ownerCt=this;this.ignoreNextClick=0;this.el.addClass("x-btn-menu-active");this.fireEvent("menushow",this,this.menu)}},onMenuHide:function(a){if(this.menu.ownerCt==this){this.el.removeClass("x-btn-menu-active");this.ignoreNextClick=this.restoreClick.defer(250,this);this.fireEvent("menuhide",this,this.menu);delete this.menu.ownerCt}},restoreClick:function(){this.ignoreNextClick=0}});Ext.reg("button",Ext.Button);Ext.ButtonToggleMgr=function(){var a={};function b(e,j){if(j){var h=a[e.toggleGroup];for(var d=0,c=h.length;d<c;d++){if(h[d]!=e){h[d].toggle(false)}}}}return{register:function(c){if(!c.toggleGroup){return}var d=a[c.toggleGroup];if(!d){d=a[c.toggleGroup]=[]}d.push(c);c.on("toggle",b)},unregister:function(c){if(!c.toggleGroup){return}var d=a[c.toggleGroup];if(d){d.remove(c);c.un("toggle",b)}},getPressed:function(h){var e=a[h];if(e){for(var d=0,c=e.length;d<c;d++){if(e[d].pressed===true){return e[d]}}}return null}}}();Ext.SplitButton=Ext.extend(Ext.Button,{arrowSelector:"em",split:true,initComponent:function(){Ext.SplitButton.superclass.initComponent.call(this);this.addEvents("arrowclick")},onRender:function(){Ext.SplitButton.superclass.onRender.apply(this,arguments);if(this.arrowTooltip){this.el.child(this.arrowSelector).dom[this.tooltipType]=this.arrowTooltip}},setArrowHandler:function(b,a){this.arrowHandler=b;this.scope=a},getMenuClass:function(){return"x-btn-split"+(this.arrowAlign=="bottom"?"-bottom":"")},isClickOnArrow:function(c){if(this.arrowAlign!="bottom"){var b=this.el.child("em.x-btn-split");var a=b.getRegion().right-b.getPadding("r");return c.getPageX()>a}else{return c.getPageY()>this.btnEl.getRegion().bottom}},onClick:function(b,a){b.preventDefault();if(!this.disabled){if(this.isClickOnArrow(b)){if(this.menu&&!this.menu.isVisible()&&!this.ignoreNextClick){this.showMenu()}this.fireEvent("arrowclick",this,b);if(this.arrowHandler){this.arrowHandler.call(this.scope||this,this,b)}}else{this.doToggle();this.fireEvent("click",this,b);if(this.handler){this.handler.call(this.scope||this,this,b)}}}},isMenuTriggerOver:function(a){return this.menu&&a.target.tagName==this.arrowSelector},isMenuTriggerOut:function(b,a){return this.menu&&b.target.tagName!=this.arrowSelector}});Ext.reg("splitbutton",Ext.SplitButton);Ext.CycleButton=Ext.extend(Ext.SplitButton,{getItemText:function(a){if(a&&this.showText===true){var b="";if(this.prependText){b+=this.prependText}b+=a.text;return b}return undefined},setActiveItem:function(c,a){if(!Ext.isObject(c)){c=this.menu.getComponent(c)}if(c){if(!this.rendered){this.text=this.getItemText(c);this.iconCls=c.iconCls}else{var b=this.getItemText(c);if(b){this.setText(b)}this.setIconClass(c.iconCls)}this.activeItem=c;if(!c.checked){c.setChecked(true,a)}if(this.forceIcon){this.setIconClass(this.forceIcon)}if(!a){this.fireEvent("change",this,c)}}},getActiveItem:function(){return this.activeItem},initComponent:function(){this.addEvents("change");if(this.changeHandler){this.on("change",this.changeHandler,this.scope||this);delete this.changeHandler}this.itemCount=this.items.length;this.menu={cls:"x-cycle-menu",items:[]};var a=0;Ext.each(this.items,function(c,b){Ext.apply(c,{group:c.group||this.id,itemIndex:b,checkHandler:this.checkHandler,scope:this,checked:c.checked||false});this.menu.items.push(c);if(c.checked){a=b}},this);Ext.CycleButton.superclass.initComponent.call(this);this.on("click",this.toggleSelected,this);this.setActiveItem(a,true)},checkHandler:function(a,b){if(b){this.setActiveItem(a)}},toggleSelected:function(){var a=this.menu;a.render();if(!a.hasLayout){a.doLayout()}var d,b;for(var c=1;c<this.itemCount;c++){d=(this.activeItem.itemIndex+c)%this.itemCount;b=a.items.itemAt(d);if(!b.disabled){b.setChecked(true);break}}}});Ext.reg("cycle",Ext.CycleButton);Ext.Toolbar=function(a){if(Ext.isArray(a)){a={items:a,layout:"toolbar"}}else{a=Ext.apply({layout:"toolbar"},a);if(a.buttons){a.items=a.buttons}}Ext.Toolbar.superclass.constructor.call(this,a)};(function(){var a=Ext.Toolbar;Ext.extend(a,Ext.Container,{defaultType:"button",enableOverflow:false,trackMenus:true,internalDefaults:{removeMode:"container",hideParent:true},toolbarCls:"x-toolbar",initComponent:function(){a.superclass.initComponent.call(this);this.addEvents("overflowchange")},onRender:function(c,b){if(!this.el){if(!this.autoCreate){this.autoCreate={cls:this.toolbarCls+" x-small-editor"}}this.el=c.createChild(Ext.apply({id:this.id},this.autoCreate),b);Ext.Toolbar.superclass.onRender.apply(this,arguments)}},lookupComponent:function(b){if(Ext.isString(b)){if(b=="-"){b=new a.Separator()}else{if(b==" "){b=new a.Spacer()}else{if(b=="->"){b=new a.Fill()}else{b=new a.TextItem(b)}}}this.applyDefaults(b)}else{if(b.isFormField||b.render){b=this.createComponent(b)}else{if(b.tag){b=new a.Item({autoEl:b})}else{if(b.tagName){b=new a.Item({el:b})}else{if(Ext.isObject(b)){b=b.xtype?this.createComponent(b):this.constructButton(b)}}}}}return b},applyDefaults:function(e){if(!Ext.isString(e)){e=Ext.Toolbar.superclass.applyDefaults.call(this,e);var b=this.internalDefaults;if(e.events){Ext.applyIf(e.initialConfig,b);Ext.apply(e,b)}else{Ext.applyIf(e,b)}}return e},addSeparator:function(){return this.add(new a.Separator())},addSpacer:function(){return this.add(new a.Spacer())},addFill:function(){this.add(new a.Fill())},addElement:function(b){return this.addItem(new a.Item({el:b}))},addItem:function(b){return this.add.apply(this,arguments)},addButton:function(c){if(Ext.isArray(c)){var e=[];for(var d=0,b=c.length;d<b;d++){e.push(this.addButton(c[d]))}return e}return this.add(this.constructButton(c))},addText:function(b){return this.addItem(new a.TextItem(b))},addDom:function(b){return this.add(new a.Item({autoEl:b}))},addField:function(b){return this.add(b)},insertButton:function(c,g){if(Ext.isArray(g)){var e=[];for(var d=0,b=g.length;d<b;d++){e.push(this.insertButton(c+d,g[d]))}return e}return Ext.Toolbar.superclass.insert.call(this,c,g)},trackMenu:function(c,b){if(this.trackMenus&&c.menu){var d=b?"mun":"mon";this[d](c,"menutriggerover",this.onButtonTriggerOver,this);this[d](c,"menushow",this.onButtonMenuShow,this);this[d](c,"menuhide",this.onButtonMenuHide,this)}},constructButton:function(d){var c=d.events?d:this.createComponent(d,d.split?"splitbutton":this.defaultType);return c},onAdd:function(b){Ext.Toolbar.superclass.onAdd.call(this);this.trackMenu(b);if(this.disabled){b.disable()}},onRemove:function(b){Ext.Toolbar.superclass.onRemove.call(this);if(b==this.activeMenuBtn){delete this.activeMenuBtn}this.trackMenu(b,true)},onDisable:function(){this.items.each(function(b){if(b.disable){b.disable()}})},onEnable:function(){this.items.each(function(b){if(b.enable){b.enable()}})},onButtonTriggerOver:function(b){if(this.activeMenuBtn&&this.activeMenuBtn!=b){this.activeMenuBtn.hideMenu();b.showMenu();this.activeMenuBtn=b}},onButtonMenuShow:function(b){this.activeMenuBtn=b},onButtonMenuHide:function(b){delete this.activeMenuBtn}});Ext.reg("toolbar",Ext.Toolbar);a.Item=Ext.extend(Ext.BoxComponent,{hideParent:true,enable:Ext.emptyFn,disable:Ext.emptyFn,focus:Ext.emptyFn});Ext.reg("tbitem",a.Item);a.Separator=Ext.extend(a.Item,{onRender:function(c,b){this.el=c.createChild({tag:"span",cls:"xtb-sep"},b)}});Ext.reg("tbseparator",a.Separator);a.Spacer=Ext.extend(a.Item,{onRender:function(c,b){this.el=c.createChild({tag:"div",cls:"xtb-spacer",style:this.width?"width:"+this.width+"px":""},b)}});Ext.reg("tbspacer",a.Spacer);a.Fill=Ext.extend(a.Item,{render:Ext.emptyFn,isFill:true});Ext.reg("tbfill",a.Fill);a.TextItem=Ext.extend(a.Item,{constructor:function(b){a.TextItem.superclass.constructor.call(this,Ext.isString(b)?{text:b}:b)},onRender:function(c,b){this.autoEl={cls:"xtb-text",html:this.text||""};a.TextItem.superclass.onRender.call(this,c,b)},setText:function(b){if(this.rendered){this.el.update(b)}else{this.text=b}}});Ext.reg("tbtext",a.TextItem);a.Button=Ext.extend(Ext.Button,{});a.SplitButton=Ext.extend(Ext.SplitButton,{});Ext.reg("tbbutton",a.Button);Ext.reg("tbsplit",a.SplitButton)})();Ext.ButtonGroup=Ext.extend(Ext.Panel,{baseCls:"x-btn-group",layout:"table",defaultType:"button",frame:true,internalDefaults:{removeMode:"container",hideParent:true},initComponent:function(){this.layoutConfig=this.layoutConfig||{};Ext.applyIf(this.layoutConfig,{columns:this.columns});if(!this.title){this.addClass("x-btn-group-notitle")}this.on("afterlayout",this.onAfterLayout,this);Ext.ButtonGroup.superclass.initComponent.call(this)},applyDefaults:function(b){b=Ext.ButtonGroup.superclass.applyDefaults.call(this,b);var a=this.internalDefaults;if(b.events){Ext.applyIf(b.initialConfig,a);Ext.apply(b,a)}else{Ext.applyIf(b,a)}return b},onAfterLayout:function(){var a=this.body.getFrameWidth("lr")+this.body.dom.firstChild.offsetWidth;this.body.setWidth(a);this.el.setWidth(a+this.getFrameWidth())}});Ext.reg("buttongroup",Ext.ButtonGroup);(function(){var a=Ext.Toolbar;Ext.PagingToolbar=Ext.extend(Ext.Toolbar,{pageSize:20,displayMsg:"Displaying {0} - {1} of {2}",emptyMsg:"No data to display",beforePageText:"Page",afterPageText:"of {0}",firstText:"First Page",prevText:"Previous Page",nextText:"Next Page",lastText:"Last Page",refreshText:"Refresh",initComponent:function(){var c=[this.first=new a.Button({tooltip:this.firstText,overflowText:this.firstText,iconCls:"x-tbar-page-first",disabled:true,handler:this.moveFirst,scope:this}),this.prev=new a.Button({tooltip:this.prevText,overflowText:this.prevText,iconCls:"x-tbar-page-prev",disabled:true,handler:this.movePrevious,scope:this}),"-",this.beforePageText,this.inputItem=new Ext.form.NumberField({cls:"x-tbar-page-number",allowDecimals:false,allowNegative:false,enableKeyEvents:true,selectOnFocus:true,submitValue:false,listeners:{scope:this,keydown:this.onPagingKeyDown,blur:this.onPagingBlur}}),this.afterTextItem=new a.TextItem({text:String.format(this.afterPageText,1)}),"-",this.next=new a.Button({tooltip:this.nextText,overflowText:this.nextText,iconCls:"x-tbar-page-next",disabled:true,handler:this.moveNext,scope:this}),this.last=new a.Button({tooltip:this.lastText,overflowText:this.lastText,iconCls:"x-tbar-page-last",disabled:true,handler:this.moveLast,scope:this}),"-",this.refresh=new a.Button({tooltip:this.refreshText,overflowText:this.refreshText,iconCls:"x-tbar-loading",handler:this.doRefresh,scope:this})];var b=this.items||this.buttons||[];if(this.prependButtons){this.items=b.concat(c)}else{this.items=c.concat(b)}delete this.buttons;if(this.displayInfo){this.items.push("->");this.items.push(this.displayItem=new a.TextItem({}))}Ext.PagingToolbar.superclass.initComponent.call(this);this.addEvents("change","beforechange");this.on("afterlayout",this.onFirstLayout,this,{single:true});this.cursor=0;this.bindStore(this.store,true)},onFirstLayout:function(){if(this.dsLoaded){this.onLoad.apply(this,this.dsLoaded)}},updateInfo:function(){if(this.displayItem){var b=this.store.getCount();var c=b==0?this.emptyMsg:String.format(this.displayMsg,this.cursor+1,this.cursor+b,this.store.getTotalCount());this.displayItem.setText(c)}},onLoad:function(b,e,j){if(!this.rendered){this.dsLoaded=[b,e,j];return}var g=this.getParams();this.cursor=(j.params&&j.params[g.start])?j.params[g.start]:0;var i=this.getPageData(),c=i.activePage,h=i.pages;this.afterTextItem.setText(String.format(this.afterPageText,i.pages));this.inputItem.setValue(c);this.first.setDisabled(c==1);this.prev.setDisabled(c==1);this.next.setDisabled(c==h);this.last.setDisabled(c==h);this.refresh.enable();this.updateInfo();this.fireEvent("change",this,i)},getPageData:function(){var b=this.store.getTotalCount();return{total:b,activePage:Math.ceil((this.cursor+this.pageSize)/this.pageSize),pages:b<this.pageSize?1:Math.ceil(b/this.pageSize)}},changePage:function(b){this.doLoad(((b-1)*this.pageSize).constrain(0,this.store.getTotalCount()))},onLoadError:function(){if(!this.rendered){return}this.refresh.enable()},readPage:function(e){var b=this.inputItem.getValue(),c;if(!b||isNaN(c=parseInt(b,10))){this.inputItem.setValue(e.activePage);return false}return c},onPagingFocus:function(){this.inputItem.select()},onPagingBlur:function(b){this.inputItem.setValue(this.getPageData().activePage)},onPagingKeyDown:function(i,h){var c=h.getKey(),j=this.getPageData(),g;if(c==h.RETURN){h.stopEvent();g=this.readPage(j);if(g!==false){g=Math.min(Math.max(1,g),j.pages)-1;this.doLoad(g*this.pageSize)}}else{if(c==h.HOME||c==h.END){h.stopEvent();g=c==h.HOME?1:j.pages;i.setValue(g)}else{if(c==h.UP||c==h.PAGEUP||c==h.DOWN||c==h.PAGEDOWN){h.stopEvent();if((g=this.readPage(j))){var b=h.shiftKey?10:1;if(c==h.DOWN||c==h.PAGEDOWN){b*=-1}g+=b;if(g>=1&g<=j.pages){i.setValue(g)}}}}}},getParams:function(){return this.paramNames||this.store.paramNames},beforeLoad:function(){if(this.rendered&&this.refresh){this.refresh.disable()}},doLoad:function(d){var c={},b=this.getParams();c[b.start]=d;c[b.limit]=this.pageSize;if(this.fireEvent("beforechange",this,c)!==false){this.store.load({params:c})}},moveFirst:function(){this.doLoad(0)},movePrevious:function(){this.doLoad(Math.max(0,this.cursor-this.pageSize))},moveNext:function(){this.doLoad(this.cursor+this.pageSize)},moveLast:function(){var c=this.store.getTotalCount(),b=c%this.pageSize;this.doLoad(b?(c-b):c-this.pageSize)},doRefresh:function(){this.doLoad(this.cursor)},bindStore:function(c,d){var b;if(!d&&this.store){if(c!==this.store&&this.store.autoDestroy){this.store.destroy()}else{this.store.un("beforeload",this.beforeLoad,this);this.store.un("load",this.onLoad,this);this.store.un("exception",this.onLoadError,this)}if(!c){this.store=null}}if(c){c=Ext.StoreMgr.lookup(c);c.on({scope:this,beforeload:this.beforeLoad,load:this.onLoad,exception:this.onLoadError});b=true}this.store=c;if(b){this.onLoad(c,null,{})}},unbind:function(b){this.bindStore(null)},bind:function(b){this.bindStore(b)},onDestroy:function(){this.bindStore(null);Ext.PagingToolbar.superclass.onDestroy.call(this)}})})();Ext.reg("paging",Ext.PagingToolbar);Ext.History=(function(){var e,c;var k=false;var d;function g(){var l=location.href,m=l.indexOf("#"),n=m>=0?l.substr(m+1):null;if(Ext.isGecko){n=decodeURIComponent(n)}return n}function a(){c.value=d}function h(l){d=l;Ext.History.fireEvent("change",l)}function i(m){var l=['<html><body><div id="state">',Ext.util.Format.htmlEncode(m),"</div></body></html>"].join("");try{var o=e.contentWindow.document;o.open();o.write(l);o.close();return true}catch(n){return false}}function b(){if(!e.contentWindow||!e.contentWindow.document){setTimeout(b,10);return}var o=e.contentWindow.document;var m=o.getElementById("state");var l=m?m.innerText:null;var n=g();setInterval(function(){o=e.contentWindow.document;m=o.getElementById("state");var q=m?m.innerText:null;var p=g();if(q!==l){l=q;h(l);location.hash=l;n=l;a()}else{if(p!==n){n=p;i(p)}}},50);k=true;Ext.History.fireEvent("ready",Ext.History)}function j(){d=c.value?c.value:g();if(Ext.isIE){b()}else{var l=g();setInterval(function(){var m=g();if(m!==l){l=m;h(l);a()}},50);k=true;Ext.History.fireEvent("ready",Ext.History)}}return{fieldId:"x-history-field",iframeId:"x-history-frame",events:{},init:function(m,l){if(k){Ext.callback(m,l,[this]);return}if(!Ext.isReady){Ext.onReady(function(){Ext.History.init(m,l)});return}c=Ext.getDom(Ext.History.fieldId);if(Ext.isIE){e=Ext.getDom(Ext.History.iframeId)}this.addEvents("ready","change");if(m){this.on("ready",m,l,{single:true})}j()},add:function(l,m){if(m!==false){if(this.getToken()==l){return true}}if(Ext.isIE){return i(l)}else{location.hash=l;return true}},back:function(){history.go(-1)},forward:function(){history.go(1)},getToken:function(){return k?d:g()}}})();Ext.apply(Ext.History,new Ext.util.Observable());Ext.Tip=Ext.extend(Ext.Panel,{minWidth:40,maxWidth:300,shadow:"sides",defaultAlign:"tl-bl?",autoRender:true,quickShowInterval:250,frame:true,hidden:true,baseCls:"x-tip",floating:{shadow:true,shim:true,useDisplay:true,constrain:false},autoHeight:true,closeAction:"hide",initComponent:function(){Ext.Tip.superclass.initComponent.call(this);if(this.closable&&!this.title){this.elements+=",header"}},afterRender:function(){Ext.Tip.superclass.afterRender.call(this);if(this.closable){this.addTool({id:"close",handler:this[this.closeAction],scope:this})}},showAt:function(a){Ext.Tip.superclass.show.call(this);if(this.measureWidth!==false&&(!this.initialConfig||typeof this.initialConfig.width!="number")){this.doAutoWidth()}if(this.constrainPosition){a=this.el.adjustForConstraints(a)}this.setPagePosition(a[0],a[1])},doAutoWidth:function(a){a=a||0;var b=this.body.getTextWidth();if(this.title){b=Math.max(b,this.header.child("span").getTextWidth(this.title))}b+=this.getFrameWidth()+(this.closable?20:0)+this.body.getPadding("lr")+a;this.setWidth(b.constrain(this.minWidth,this.maxWidth));if(Ext.isIE7&&!this.repainted){this.el.repaint();this.repainted=true}},showBy:function(a,b){if(!this.rendered){this.render(Ext.getBody())}this.showAt(this.el.getAlignToXY(a,b||this.defaultAlign))},initDraggable:function(){this.dd=new Ext.Tip.DD(this,typeof this.draggable=="boolean"?null:this.draggable);this.header.addClass("x-tip-draggable")}});Ext.reg("tip",Ext.Tip);Ext.Tip.DD=function(b,a){Ext.apply(this,a);this.tip=b;Ext.Tip.DD.superclass.constructor.call(this,b.el.id,"WindowDD-"+b.id);this.setHandleElId(b.header.id);this.scroll=false};Ext.extend(Ext.Tip.DD,Ext.dd.DD,{moveOnly:true,scroll:false,headerOffsets:[100,25],startDrag:function(){this.tip.el.disableShadow()},endDrag:function(a){this.tip.el.enableShadow(true)}});Ext.ToolTip=Ext.extend(Ext.Tip,{showDelay:500,hideDelay:200,dismissDelay:5000,trackMouse:false,anchorToTarget:true,anchorOffset:0,targetCounter:0,constrainPosition:false,initComponent:function(){Ext.ToolTip.superclass.initComponent.call(this);this.lastActive=new Date();this.initTarget(this.target);this.origAnchor=this.anchor},onRender:function(b,a){Ext.ToolTip.superclass.onRender.call(this,b,a);this.anchorCls="x-tip-anchor-"+this.getAnchorPosition();this.anchorEl=this.el.createChild({cls:"x-tip-anchor "+this.anchorCls})},afterRender:function(){Ext.ToolTip.superclass.afterRender.call(this);this.anchorEl.setStyle("z-index",this.el.getZIndex()+1).setVisibilityMode(Ext.Element.DISPLAY)},initTarget:function(c){var a;if((a=Ext.get(c))){if(this.target){var b=Ext.get(this.target);this.mun(b,"mouseover",this.onTargetOver,this);this.mun(b,"mouseout",this.onTargetOut,this);this.mun(b,"mousemove",this.onMouseMove,this)}this.mon(a,{mouseover:this.onTargetOver,mouseout:this.onTargetOut,mousemove:this.onMouseMove,scope:this});this.target=a}if(this.anchor){this.anchorTarget=this.target}},onMouseMove:function(b){var a=this.delegate?b.getTarget(this.delegate):this.triggerElement=true;if(a){this.targetXY=b.getXY();if(a===this.triggerElement){if(!this.hidden&&this.trackMouse){this.setPagePosition(this.getTargetXY())}}else{this.hide();this.lastActive=new Date(0);this.onTargetOver(b)}}else{if(!this.closable&&this.isVisible()){this.hide()}}},getTargetXY:function(){if(this.delegate){this.anchorTarget=this.triggerElement}if(this.anchor){this.targetCounter++;var c=this.getOffsets(),l=(this.anchorToTarget&&!this.trackMouse)?this.el.getAlignToXY(this.anchorTarget,this.getAnchorAlign()):this.targetXY,a=Ext.lib.Dom.getViewWidth()-5,h=Ext.lib.Dom.getViewHeight()-5,i=document.documentElement,e=document.body,k=(i.scrollLeft||e.scrollLeft||0)+5,j=(i.scrollTop||e.scrollTop||0)+5,b=[l[0]+c[0],l[1]+c[1]],g=this.getSize();this.anchorEl.removeClass(this.anchorCls);if(this.targetCounter<2){if(b[0]<k){if(this.anchorToTarget){this.defaultAlign="l-r";if(this.mouseOffset){this.mouseOffset[0]*=-1}}this.anchor="left";return this.getTargetXY()}if(b[0]+g.width>a){if(this.anchorToTarget){this.defaultAlign="r-l";if(this.mouseOffset){this.mouseOffset[0]*=-1}}this.anchor="right";return this.getTargetXY()}if(b[1]<j){if(this.anchorToTarget){this.defaultAlign="t-b";if(this.mouseOffset){this.mouseOffset[1]*=-1}}this.anchor="top";return this.getTargetXY()}if(b[1]+g.height>h){if(this.anchorToTarget){this.defaultAlign="b-t";if(this.mouseOffset){this.mouseOffset[1]*=-1}}this.anchor="bottom";return this.getTargetXY()}}this.anchorCls="x-tip-anchor-"+this.getAnchorPosition();this.anchorEl.addClass(this.anchorCls);this.targetCounter=0;return b}else{var d=this.getMouseOffset();return[this.targetXY[0]+d[0],this.targetXY[1]+d[1]]}},getMouseOffset:function(){var a=this.anchor?[0,0]:[15,18];if(this.mouseOffset){a[0]+=this.mouseOffset[0];a[1]+=this.mouseOffset[1]}return a},getAnchorPosition:function(){if(this.anchor){this.tipAnchor=this.anchor.charAt(0)}else{var a=this.defaultAlign.match(/^([a-z]+)-([a-z]+)(\?)?$/);if(!a){throw"AnchorTip.defaultAlign is invalid"}this.tipAnchor=a[1].charAt(0)}switch(this.tipAnchor){case"t":return"top";case"b":return"bottom";case"r":return"right"}return"left"},getAnchorAlign:function(){switch(this.anchor){case"top":return"tl-bl";case"left":return"tl-tr";case"right":return"tr-tl";default:return"bl-tl"}},getOffsets:function(){var b,a=this.getAnchorPosition().charAt(0);if(this.anchorToTarget&&!this.trackMouse){switch(a){case"t":b=[0,9];break;case"b":b=[0,-13];break;case"r":b=[-13,0];break;default:b=[9,0];break}}else{switch(a){case"t":b=[-15-this.anchorOffset,30];break;case"b":b=[-19-this.anchorOffset,-13-this.el.dom.offsetHeight];break;case"r":b=[-15-this.el.dom.offsetWidth,-13-this.anchorOffset];break;default:b=[25,-13-this.anchorOffset];break}}var c=this.getMouseOffset();b[0]+=c[0];b[1]+=c[1];return b},onTargetOver:function(b){if(this.disabled||b.within(this.target.dom,true)){return}var a=b.getTarget(this.delegate);if(a){this.triggerElement=a;this.clearTimer("hide");this.targetXY=b.getXY();this.delayShow()}},delayShow:function(){if(this.hidden&&!this.showTimer){if(this.lastActive.getElapsed()<this.quickShowInterval){this.show()}else{this.showTimer=this.show.defer(this.showDelay,this)}}else{if(!this.hidden&&this.autoHide!==false){this.show()}}},onTargetOut:function(a){if(this.disabled||a.within(this.target.dom,true)){return}this.clearTimer("show");if(this.autoHide!==false){this.delayHide()}},delayHide:function(){if(!this.hidden&&!this.hideTimer){this.hideTimer=this.hide.defer(this.hideDelay,this)}},hide:function(){this.clearTimer("dismiss");this.lastActive=new Date();if(this.anchorEl){this.anchorEl.hide()}Ext.ToolTip.superclass.hide.call(this);delete this.triggerElement},show:function(){if(this.anchor){this.showAt([-1000,-1000]);this.origConstrainPosition=this.constrainPosition;this.constrainPosition=false;this.anchor=this.origAnchor}this.showAt(this.getTargetXY());if(this.anchor){this.anchorEl.show();this.syncAnchor();this.constrainPosition=this.origConstrainPosition}else{this.anchorEl.hide()}},showAt:function(a){this.lastActive=new Date();this.clearTimers();Ext.ToolTip.superclass.showAt.call(this,a);if(this.dismissDelay&&this.autoHide!==false){this.dismissTimer=this.hide.defer(this.dismissDelay,this)}if(this.anchor&&!this.anchorEl.isVisible()){this.syncAnchor();this.anchorEl.show()}else{this.anchorEl.hide()}},syncAnchor:function(){var a,b,c;switch(this.tipAnchor.charAt(0)){case"t":a="b";b="tl";c=[20+this.anchorOffset,2];break;case"r":a="l";b="tr";c=[-2,11+this.anchorOffset];break;case"b":a="t";b="bl";c=[20+this.anchorOffset,-2];break;default:a="r";b="tl";c=[2,11+this.anchorOffset];break}this.anchorEl.alignTo(this.el,a+"-"+b,c)},setPagePosition:function(a,b){Ext.ToolTip.superclass.setPagePosition.call(this,a,b);if(this.anchor){this.syncAnchor()}},clearTimer:function(a){a=a+"Timer";clearTimeout(this[a]);delete this[a]},clearTimers:function(){this.clearTimer("show");this.clearTimer("dismiss");this.clearTimer("hide")},onShow:function(){Ext.ToolTip.superclass.onShow.call(this);Ext.getDoc().on("mousedown",this.onDocMouseDown,this)},onHide:function(){Ext.ToolTip.superclass.onHide.call(this);Ext.getDoc().un("mousedown",this.onDocMouseDown,this)},onDocMouseDown:function(a){if(this.autoHide!==true&&!this.closable&&!a.within(this.el.dom)){this.disable();this.doEnable.defer(100,this)}},doEnable:function(){if(!this.isDestroyed){this.enable()}},onDisable:function(){this.clearTimers();this.hide()},adjustPosition:function(a,d){if(this.constrainPosition){var c=this.targetXY[1],b=this.getSize().height;if(d<=c&&(d+b)>=c){d=c-b-5}}return{x:a,y:d}},beforeDestroy:function(){this.clearTimers();Ext.destroy(this.anchorEl);delete this.anchorEl;delete this.target;delete this.anchorTarget;delete this.triggerElement;Ext.ToolTip.superclass.beforeDestroy.call(this)},onDestroy:function(){Ext.getDoc().un("mousedown",this.onDocMouseDown,this);Ext.ToolTip.superclass.onDestroy.call(this)}});Ext.reg("tooltip",Ext.ToolTip);Ext.QuickTip=Ext.extend(Ext.ToolTip,{interceptTitles:false,tagConfig:{namespace:"ext",attribute:"qtip",width:"qwidth",target:"target",title:"qtitle",hide:"hide",cls:"qclass",align:"qalign",anchor:"anchor"},initComponent:function(){this.target=this.target||Ext.getDoc();this.targets=this.targets||{};Ext.QuickTip.superclass.initComponent.call(this)},register:function(e){var h=Ext.isArray(e)?e:arguments;for(var g=0,a=h.length;g<a;g++){var l=h[g];var k=l.target;if(k){if(Ext.isArray(k)){for(var d=0,b=k.length;d<b;d++){this.targets[Ext.id(k[d])]=l}}else{this.targets[Ext.id(k)]=l}}}},unregister:function(a){delete this.targets[Ext.id(a)]},cancelShow:function(b){var a=this.activeTarget;b=Ext.get(b).dom;if(this.isVisible()){if(a&&a.el==b){this.hide()}}else{if(a&&a.el==b){this.clearTimer("show")}}},getTipCfg:function(d){var b=d.getTarget(),c,a;if(this.interceptTitles&&b.title&&Ext.isString(b.title)){c=b.title;b.qtip=c;b.removeAttribute("title");d.preventDefault()}else{a=this.tagConfig;c=b.qtip||Ext.fly(b).getAttribute(a.attribute,a.namespace)}return c},onTargetOver:function(i){if(this.disabled){return}this.targetXY=i.getXY();var c=i.getTarget();if(!c||c.nodeType!==1||c==document||c==document.body){return}if(this.activeTarget&&((c==this.activeTarget.el)||Ext.fly(this.activeTarget.el).contains(c))){this.clearTimer("hide");this.show();return}if(c&&this.targets[c.id]){this.activeTarget=this.targets[c.id];this.activeTarget.el=c;this.anchor=this.activeTarget.anchor;if(this.anchor){this.anchorTarget=c}this.delayShow();return}var g,h=Ext.fly(c),b=this.tagConfig,d=b.namespace;if(g=this.getTipCfg(i)){var a=h.getAttribute(b.hide,d);this.activeTarget={el:c,text:g,width:h.getAttribute(b.width,d),autoHide:a!="user"&&a!=="false",title:h.getAttribute(b.title,d),cls:h.getAttribute(b.cls,d),align:h.getAttribute(b.align,d)};this.anchor=h.getAttribute(b.anchor,d);if(this.anchor){this.anchorTarget=c}this.delayShow()}},onTargetOut:function(a){if(this.activeTarget&&a.within(this.activeTarget.el)&&!this.getTipCfg(a)){return}this.clearTimer("show");if(this.autoHide!==false){this.delayHide()}},showAt:function(b){var a=this.activeTarget;if(a){if(!this.rendered){this.render(Ext.getBody());this.activeTarget=a}if(a.width){this.setWidth(a.width);this.body.setWidth(this.adjustBodyWidth(a.width-this.getFrameWidth()));this.measureWidth=false}else{this.measureWidth=true}this.setTitle(a.title||"");this.body.update(a.text);this.autoHide=a.autoHide;this.dismissDelay=a.dismissDelay||this.dismissDelay;if(this.lastCls){this.el.removeClass(this.lastCls);delete this.lastCls}if(a.cls){this.el.addClass(a.cls);this.lastCls=a.cls}if(this.anchor){this.constrainPosition=false}else{if(a.align){b=this.el.getAlignToXY(a.el,a.align);this.constrainPosition=false}else{this.constrainPosition=true}}}Ext.QuickTip.superclass.showAt.call(this,b)},hide:function(){delete this.activeTarget;Ext.QuickTip.superclass.hide.call(this)}});Ext.reg("quicktip",Ext.QuickTip);Ext.QuickTips=function(){var b,a=false;return{init:function(c){if(!b){if(!Ext.isReady){Ext.onReady(function(){Ext.QuickTips.init(c)});return}b=new Ext.QuickTip({elements:"header,body",disabled:a});if(c!==false){b.render(Ext.getBody())}}},ddDisable:function(){if(b&&!a){b.disable()}},ddEnable:function(){if(b&&!a){b.enable()}},enable:function(){if(b){b.enable()}a=false},disable:function(){if(b){b.disable()}a=true},isEnabled:function(){return b!==undefined&&!b.disabled},getQuickTip:function(){return b},register:function(){b.register.apply(b,arguments)},unregister:function(){b.unregister.apply(b,arguments)},tips:function(){b.register.apply(b,arguments)}}}();Ext.slider.Tip=Ext.extend(Ext.Tip,{minWidth:10,offsets:[0,-10],init:function(a){a.on({scope:this,dragstart:this.onSlide,drag:this.onSlide,dragend:this.hide,destroy:this.destroy})},onSlide:function(b,c,a){this.show();this.body.update(this.getText(a));this.doAutoWidth();this.el.alignTo(a.el,"b-t?",this.offsets)},getText:function(a){return String(a.value)}});Ext.ux.SliderTip=Ext.slider.Tip;Ext.tree.TreePanel=Ext.extend(Ext.Panel,{rootVisible:true,animate:Ext.enableFx,lines:true,enableDD:false,hlDrop:Ext.enableFx,pathSeparator:"/",bubbleEvents:[],initComponent:function(){Ext.tree.TreePanel.superclass.initComponent.call(this);if(!this.eventModel){this.eventModel=new Ext.tree.TreeEventModel(this)}var a=this.loader;if(!a){a=new Ext.tree.TreeLoader({dataUrl:this.dataUrl,requestMethod:this.requestMethod})}else{if(Ext.isObject(a)&&!a.load){a=new Ext.tree.TreeLoader(a)}}this.loader=a;this.nodeHash={};if(this.root){var b=this.root;delete this.root;this.setRootNode(b)}this.addEvents("append","remove","movenode","insert","beforeappend","beforeremove","beforemovenode","beforeinsert","beforeload","load","textchange","beforeexpandnode","beforecollapsenode","expandnode","disabledchange","collapsenode","beforeclick","click","containerclick","checkchange","beforedblclick","dblclick","containerdblclick","contextmenu","containercontextmenu","beforechildrenrendered","startdrag","enddrag","dragdrop","beforenodedrop","nodedrop","nodedragover");if(this.singleExpand){this.on("beforeexpandnode",this.restrictExpand,this)}},proxyNodeEvent:function(c,b,a,h,g,e,d){if(c=="collapse"||c=="expand"||c=="beforecollapse"||c=="beforeexpand"||c=="move"||c=="beforemove"){c=c+"node"}return this.fireEvent(c,b,a,h,g,e,d)},getRootNode:function(){return this.root},setRootNode:function(b){this.destroyRoot();if(!b.render){b=this.loader.createNode(b)}this.root=b;b.ownerTree=this;b.isRoot=true;this.registerNode(b);if(!this.rootVisible){var a=b.attributes.uiProvider;b.ui=a?new a(b):new Ext.tree.RootTreeNodeUI(b)}if(this.innerCt){this.clearInnerCt();this.renderRoot()}return b},clearInnerCt:function(){this.innerCt.update("")},renderRoot:function(){this.root.render();if(!this.rootVisible){this.root.renderChildren()}},getNodeById:function(a){return this.nodeHash[a]},registerNode:function(a){this.nodeHash[a.id]=a},unregisterNode:function(a){delete this.nodeHash[a.id]},toString:function(){return"[Tree"+(this.id?" "+this.id:"")+"]"},restrictExpand:function(a){var b=a.parentNode;if(b){if(b.expandedChild&&b.expandedChild.parentNode==b){b.expandedChild.collapse()}b.expandedChild=a}},getChecked:function(b,c){c=c||this.root;var d=[];var e=function(){if(this.attributes.checked){d.push(!b?this:(b=="id"?this.id:this.attributes[b]))}};c.cascade(e);return d},getLoader:function(){return this.loader},expandAll:function(){this.root.expand(true)},collapseAll:function(){this.root.collapse(true)},getSelectionModel:function(){if(!this.selModel){this.selModel=new Ext.tree.DefaultSelectionModel()}return this.selModel},expandPath:function(g,a,h){if(Ext.isEmpty(g)){if(h){h(false,undefined)}return}a=a||"id";var d=g.split(this.pathSeparator);var c=this.root;if(c.attributes[a]!=d[1]){if(h){h(false,null)}return}var b=1;var e=function(){if(++b==d.length){if(h){h(true,c)}return}var i=c.findChild(a,d[b]);if(!i){if(h){h(false,c)}return}c=i;i.expand(false,false,e)};c.expand(false,false,e)},selectPath:function(e,a,g){if(Ext.isEmpty(e)){if(g){g(false,undefined)}return}a=a||"id";var c=e.split(this.pathSeparator),b=c.pop();if(c.length>1){var d=function(i,h){if(i&&h){var j=h.findChild(a,b);if(j){j.select();if(g){g(true,j)}}else{if(g){g(false,j)}}}else{if(g){g(false,j)}}};this.expandPath(c.join(this.pathSeparator),a,d)}else{this.root.select();if(g){g(true,this.root)}}},getTreeEl:function(){return this.body},onRender:function(b,a){Ext.tree.TreePanel.superclass.onRender.call(this,b,a);this.el.addClass("x-tree");this.innerCt=this.body.createChild({tag:"ul",cls:"x-tree-root-ct "+(this.useArrows?"x-tree-arrows":this.lines?"x-tree-lines":"x-tree-no-lines")})},initEvents:function(){Ext.tree.TreePanel.superclass.initEvents.call(this);if(this.containerScroll){Ext.dd.ScrollManager.register(this.body)}if((this.enableDD||this.enableDrop)&&!this.dropZone){this.dropZone=new Ext.tree.TreeDropZone(this,this.dropConfig||{ddGroup:this.ddGroup||"TreeDD",appendOnly:this.ddAppendOnly===true})}if((this.enableDD||this.enableDrag)&&!this.dragZone){this.dragZone=new Ext.tree.TreeDragZone(this,this.dragConfig||{ddGroup:this.ddGroup||"TreeDD",scroll:this.ddScroll})}this.getSelectionModel().init(this)},afterRender:function(){Ext.tree.TreePanel.superclass.afterRender.call(this);this.renderRoot()},beforeDestroy:function(){if(this.rendered){Ext.dd.ScrollManager.unregister(this.body);Ext.destroy(this.dropZone,this.dragZone)}this.destroyRoot();Ext.destroy(this.loader);this.nodeHash=this.root=this.loader=null;Ext.tree.TreePanel.superclass.beforeDestroy.call(this)},destroyRoot:function(){if(this.root&&this.root.destroy){this.root.destroy(true)}}});Ext.tree.TreePanel.nodeTypes={};Ext.reg("treepanel",Ext.tree.TreePanel);Ext.tree.TreeEventModel=function(a){this.tree=a;this.tree.on("render",this.initEvents,this)};Ext.tree.TreeEventModel.prototype={initEvents:function(){var a=this.tree;if(a.trackMouseOver!==false){a.mon(a.innerCt,{scope:this,mouseover:this.delegateOver,mouseout:this.delegateOut})}a.mon(a.getTreeEl(),{scope:this,click:this.delegateClick,dblclick:this.delegateDblClick,contextmenu:this.delegateContextMenu})},getNode:function(b){var a;if(a=b.getTarget(".x-tree-node-el",10)){var c=Ext.fly(a,"_treeEvents").getAttribute("tree-node-id","ext");if(c){return this.tree.getNodeById(c)}}return null},getNodeTarget:function(b){var a=b.getTarget(".x-tree-node-icon",1);if(!a){a=b.getTarget(".x-tree-node-el",6)}return a},delegateOut:function(b,a){if(!this.beforeEvent(b)){return}if(b.getTarget(".x-tree-ec-icon",1)){var c=this.getNode(b);this.onIconOut(b,c);if(c==this.lastEcOver){delete this.lastEcOver}}if((a=this.getNodeTarget(b))&&!b.within(a,true)){this.onNodeOut(b,this.getNode(b))}},delegateOver:function(b,a){if(!this.beforeEvent(b)){return}if(Ext.isGecko&&!this.trackingDoc){Ext.getBody().on("mouseover",this.trackExit,this);this.trackingDoc=true}if(this.lastEcOver){this.onIconOut(b,this.lastEcOver);delete this.lastEcOver}if(b.getTarget(".x-tree-ec-icon",1)){this.lastEcOver=this.getNode(b);this.onIconOver(b,this.lastEcOver)}if(a=this.getNodeTarget(b)){this.onNodeOver(b,this.getNode(b))}},trackExit:function(a){if(this.lastOverNode){if(this.lastOverNode.ui&&!a.within(this.lastOverNode.ui.getEl())){this.onNodeOut(a,this.lastOverNode)}delete this.lastOverNode;Ext.getBody().un("mouseover",this.trackExit,this);this.trackingDoc=false}},delegateClick:function(b,a){if(this.beforeEvent(b)){if(b.getTarget("input[type=checkbox]",1)){this.onCheckboxClick(b,this.getNode(b))}else{if(b.getTarget(".x-tree-ec-icon",1)){this.onIconClick(b,this.getNode(b))}else{if(this.getNodeTarget(b)){this.onNodeClick(b,this.getNode(b))}}}}else{this.checkContainerEvent(b,"click")}},delegateDblClick:function(b,a){if(this.beforeEvent(b)){if(this.getNodeTarget(b)){this.onNodeDblClick(b,this.getNode(b))}}else{this.checkContainerEvent(b,"dblclick")}},delegateContextMenu:function(b,a){if(this.beforeEvent(b)){if(this.getNodeTarget(b)){this.onNodeContextMenu(b,this.getNode(b))}}else{this.checkContainerEvent(b,"contextmenu")}},checkContainerEvent:function(b,a){if(this.disabled){b.stopEvent();return false}this.onContainerEvent(b,a)},onContainerEvent:function(b,a){this.tree.fireEvent("container"+a,this.tree,b)},onNodeClick:function(b,a){a.ui.onClick(b)},onNodeOver:function(b,a){this.lastOverNode=a;a.ui.onOver(b)},onNodeOut:function(b,a){a.ui.onOut(b)},onIconOver:function(b,a){a.ui.addClass("x-tree-ec-over")},onIconOut:function(b,a){a.ui.removeClass("x-tree-ec-over")},onIconClick:function(b,a){a.ui.ecClick(b)},onCheckboxClick:function(b,a){a.ui.onCheckChange(b)},onNodeDblClick:function(b,a){a.ui.onDblClick(b)},onNodeContextMenu:function(b,a){a.ui.onContextMenu(b)},beforeEvent:function(b){var a=this.getNode(b);if(this.disabled||!a||!a.ui){b.stopEvent();return false}return true},disable:function(){this.disabled=true},enable:function(){this.disabled=false}};Ext.tree.DefaultSelectionModel=Ext.extend(Ext.util.Observable,{constructor:function(a){this.selNode=null;this.addEvents("selectionchange","beforeselect");Ext.apply(this,a);Ext.tree.DefaultSelectionModel.superclass.constructor.call(this)},init:function(a){this.tree=a;a.mon(a.getTreeEl(),"keydown",this.onKeyDown,this);a.on("click",this.onNodeClick,this)},onNodeClick:function(a,b){this.select(a)},select:function(c,a){if(!Ext.fly(c.ui.wrap).isVisible()&&a){return a.call(this,c)}var b=this.selNode;if(c==b){c.ui.onSelectedChange(true)}else{if(this.fireEvent("beforeselect",this,c,b)!==false){if(b&&b.ui){b.ui.onSelectedChange(false)}this.selNode=c;c.ui.onSelectedChange(true);this.fireEvent("selectionchange",this,c,b)}}return c},unselect:function(b,a){if(this.selNode==b){this.clearSelections(a)}},clearSelections:function(a){var b=this.selNode;if(b){b.ui.onSelectedChange(false);this.selNode=null;if(a!==true){this.fireEvent("selectionchange",this,null)}}return b},getSelectedNode:function(){return this.selNode},isSelected:function(a){return this.selNode==a},selectPrevious:function(a){if(!(a=a||this.selNode||this.lastSelNode)){return null}var c=a.previousSibling;if(c){if(!c.isExpanded()||c.childNodes.length<1){return this.select(c,this.selectPrevious)}else{var b=c.lastChild;while(b&&b.isExpanded()&&Ext.fly(b.ui.wrap).isVisible()&&b.childNodes.length>0){b=b.lastChild}return this.select(b,this.selectPrevious)}}else{if(a.parentNode&&(this.tree.rootVisible||!a.parentNode.isRoot)){return this.select(a.parentNode,this.selectPrevious)}}return null},selectNext:function(b){if(!(b=b||this.selNode||this.lastSelNode)){return null}if(b.firstChild&&b.isExpanded()&&Ext.fly(b.ui.wrap).isVisible()){return this.select(b.firstChild,this.selectNext)}else{if(b.nextSibling){return this.select(b.nextSibling,this.selectNext)}else{if(b.parentNode){var a=null;b.parentNode.bubble(function(){if(this.nextSibling){a=this.getOwnerTree().selModel.select(this.nextSibling,this.selectNext);return false}});return a}}}return null},onKeyDown:function(c){var b=this.selNode||this.lastSelNode;var d=this;if(!b){return}var a=c.getKey();switch(a){case c.DOWN:c.stopEvent();this.selectNext();break;case c.UP:c.stopEvent();this.selectPrevious();break;case c.RIGHT:c.preventDefault();if(b.hasChildNodes()){if(!b.isExpanded()){b.expand()}else{if(b.firstChild){this.select(b.firstChild,c)}}}break;case c.LEFT:c.preventDefault();if(b.hasChildNodes()&&b.isExpanded()){b.collapse()}else{if(b.parentNode&&(this.tree.rootVisible||b.parentNode!=this.tree.getRootNode())){this.select(b.parentNode,c)}}break}}});Ext.tree.MultiSelectionModel=Ext.extend(Ext.util.Observable,{constructor:function(a){this.selNodes=[];this.selMap={};this.addEvents("selectionchange");Ext.apply(this,a);Ext.tree.MultiSelectionModel.superclass.constructor.call(this)},init:function(a){this.tree=a;a.mon(a.getTreeEl(),"keydown",this.onKeyDown,this);a.on("click",this.onNodeClick,this)},onNodeClick:function(a,b){if(b.ctrlKey&&this.isSelected(a)){this.unselect(a)}else{this.select(a,b,b.ctrlKey)}},select:function(a,c,b){if(b!==true){this.clearSelections(true)}if(this.isSelected(a)){this.lastSelNode=a;return a}this.selNodes.push(a);this.selMap[a.id]=a;this.lastSelNode=a;a.ui.onSelectedChange(true);this.fireEvent("selectionchange",this,this.selNodes);return a},unselect:function(b){if(this.selMap[b.id]){b.ui.onSelectedChange(false);var c=this.selNodes;var a=c.indexOf(b);if(a!=-1){this.selNodes.splice(a,1)}delete this.selMap[b.id];this.fireEvent("selectionchange",this,this.selNodes)}},clearSelections:function(b){var d=this.selNodes;if(d.length>0){for(var c=0,a=d.length;c<a;c++){d[c].ui.onSelectedChange(false)}this.selNodes=[];this.selMap={};if(b!==true){this.fireEvent("selectionchange",this,this.selNodes)}}},isSelected:function(a){return this.selMap[a.id]?true:false},getSelectedNodes:function(){return this.selNodes.concat([])},onKeyDown:Ext.tree.DefaultSelectionModel.prototype.onKeyDown,selectNext:Ext.tree.DefaultSelectionModel.prototype.selectNext,selectPrevious:Ext.tree.DefaultSelectionModel.prototype.selectPrevious});Ext.data.Tree=Ext.extend(Ext.util.Observable,{constructor:function(a){this.nodeHash={};this.root=null;if(a){this.setRootNode(a)}this.addEvents("append","remove","move","insert","beforeappend","beforeremove","beforemove","beforeinsert");Ext.data.Tree.superclass.constructor.call(this)},pathSeparator:"/",proxyNodeEvent:function(){return this.fireEvent.apply(this,arguments)},getRootNode:function(){return this.root},setRootNode:function(a){this.root=a;a.ownerTree=this;a.isRoot=true;this.registerNode(a);return a},getNodeById:function(a){return this.nodeHash[a]},registerNode:function(a){this.nodeHash[a.id]=a},unregisterNode:function(a){delete this.nodeHash[a.id]},toString:function(){return"[Tree"+(this.id?" "+this.id:"")+"]"}});Ext.data.Node=Ext.extend(Ext.util.Observable,{constructor:function(a){this.attributes=a||{};this.leaf=this.attributes.leaf;this.id=this.attributes.id;if(!this.id){this.id=Ext.id(null,"xnode-");this.attributes.id=this.id}this.childNodes=[];this.parentNode=null;this.firstChild=null;this.lastChild=null;this.previousSibling=null;this.nextSibling=null;this.addEvents({append:true,remove:true,move:true,insert:true,beforeappend:true,beforeremove:true,beforemove:true,beforeinsert:true});this.listeners=this.attributes.listeners;Ext.data.Node.superclass.constructor.call(this)},fireEvent:function(b){if(Ext.data.Node.superclass.fireEvent.apply(this,arguments)===false){return false}var a=this.getOwnerTree();if(a){if(a.proxyNodeEvent.apply(a,arguments)===false){return false}}return true},isLeaf:function(){return this.leaf===true},setFirstChild:function(a){this.firstChild=a},setLastChild:function(a){this.lastChild=a},isLast:function(){return(!this.parentNode?true:this.parentNode.lastChild==this)},isFirst:function(){return(!this.parentNode?true:this.parentNode.firstChild==this)},hasChildNodes:function(){return !this.isLeaf()&&this.childNodes.length>0},isExpandable:function(){return this.attributes.expandable||this.hasChildNodes()},appendChild:function(e){var g=false;if(Ext.isArray(e)){g=e}else{if(arguments.length>1){g=arguments}}if(g){for(var d=0,a=g.length;d<a;d++){this.appendChild(g[d])}}else{if(this.fireEvent("beforeappend",this.ownerTree,this,e)===false){return false}var b=this.childNodes.length;var c=e.parentNode;if(c){if(e.fireEvent("beforemove",e.getOwnerTree(),e,c,this,b)===false){return false}c.removeChild(e)}b=this.childNodes.length;if(b===0){this.setFirstChild(e)}this.childNodes.push(e);e.parentNode=this;var h=this.childNodes[b-1];if(h){e.previousSibling=h;h.nextSibling=e}else{e.previousSibling=null}e.nextSibling=null;this.setLastChild(e);e.setOwnerTree(this.getOwnerTree());this.fireEvent("append",this.ownerTree,this,e,b);if(c){e.fireEvent("move",this.ownerTree,e,c,this,b)}return e}},removeChild:function(c,b){var a=this.childNodes.indexOf(c);if(a==-1){return false}if(this.fireEvent("beforeremove",this.ownerTree,this,c)===false){return false}this.childNodes.splice(a,1);if(c.previousSibling){c.previousSibling.nextSibling=c.nextSibling}if(c.nextSibling){c.nextSibling.previousSibling=c.previousSibling}if(this.firstChild==c){this.setFirstChild(c.nextSibling)}if(this.lastChild==c){this.setLastChild(c.previousSibling)}this.fireEvent("remove",this.ownerTree,this,c);if(b){c.destroy(true)}else{c.clear()}return c},clear:function(a){this.setOwnerTree(null,a);this.parentNode=this.previousSibling=this.nextSibling=null;if(a){this.firstChild=this.lastChild=null}},destroy:function(a){if(a===true){this.purgeListeners();this.clear(true);Ext.each(this.childNodes,function(b){b.destroy(true)});this.childNodes=null}else{this.remove(true)}},insertBefore:function(d,a){if(!a){return this.appendChild(d)}if(d==a){return false}if(this.fireEvent("beforeinsert",this.ownerTree,this,d,a)===false){return false}var b=this.childNodes.indexOf(a);var c=d.parentNode;var e=b;if(c==this&&this.childNodes.indexOf(d)<b){e--}if(c){if(d.fireEvent("beforemove",d.getOwnerTree(),d,c,this,b,a)===false){return false}c.removeChild(d)}if(e===0){this.setFirstChild(d)}this.childNodes.splice(e,0,d);d.parentNode=this;var g=this.childNodes[e-1];if(g){d.previousSibling=g;g.nextSibling=d}else{d.previousSibling=null}d.nextSibling=a;a.previousSibling=d;d.setOwnerTree(this.getOwnerTree());this.fireEvent("insert",this.ownerTree,this,d,a);if(c){d.fireEvent("move",this.ownerTree,d,c,this,e,a)}return d},remove:function(a){if(this.parentNode){this.parentNode.removeChild(this,a)}return this},removeAll:function(a){var c=this.childNodes,b;while((b=c[0])){this.removeChild(b,a)}return this},item:function(a){return this.childNodes[a]},replaceChild:function(a,c){var b=c?c.nextSibling:null;this.removeChild(c);this.insertBefore(a,b);return c},indexOf:function(a){return this.childNodes.indexOf(a)},getOwnerTree:function(){if(!this.ownerTree){var a=this;while(a){if(a.ownerTree){this.ownerTree=a.ownerTree;break}a=a.parentNode}}return this.ownerTree},getDepth:function(){var b=0;var a=this;while(a.parentNode){++b;a=a.parentNode}return b},setOwnerTree:function(a,b){if(a!=this.ownerTree){if(this.ownerTree){this.ownerTree.unregisterNode(this)}this.ownerTree=a;if(b!==true){Ext.each(this.childNodes,function(c){c.setOwnerTree(a)})}if(a){a.registerNode(this)}}},setId:function(b){if(b!==this.id){var a=this.ownerTree;if(a){a.unregisterNode(this)}this.id=this.attributes.id=b;if(a){a.registerNode(this)}this.onIdChange(b)}},onIdChange:Ext.emptyFn,getPath:function(c){c=c||"id";var e=this.parentNode;var a=[this.attributes[c]];while(e){a.unshift(e.attributes[c]);e=e.parentNode}var d=this.getOwnerTree().pathSeparator;return d+a.join(d)},bubble:function(c,b,a){var d=this;while(d){if(c.apply(b||d,a||[d])===false){break}d=d.parentNode}},cascade:function(g,e,b){if(g.apply(e||this,b||[this])!==false){var d=this.childNodes;for(var c=0,a=d.length;c<a;c++){d[c].cascade(g,e,b)}}},eachChild:function(g,e,b){var d=this.childNodes;for(var c=0,a=d.length;c<a;c++){if(g.apply(e||d[c],b||[d[c]])===false){break}}},findChild:function(b,c,a){return this.findChildBy(function(){return this.attributes[b]==c},null,a)},findChildBy:function(h,g,b){var e=this.childNodes,a=e.length,d=0,j,c;for(;d<a;d++){j=e[d];if(h.call(g||j,j)===true){return j}else{if(b){c=j.findChildBy(h,g,b);if(c!=null){return c}}}}return null},sort:function(e,d){var c=this.childNodes;var a=c.length;if(a>0){var g=d?function(){e.apply(d,arguments)}:e;c.sort(g);for(var b=0;b<a;b++){var h=c[b];h.previousSibling=c[b-1];h.nextSibling=c[b+1];if(b===0){this.setFirstChild(h)}if(b==a-1){this.setLastChild(h)}}}},contains:function(a){return a.isAncestor(this)},isAncestor:function(a){var b=this.parentNode;while(b){if(b==a){return true}b=b.parentNode}return false},toString:function(){return"[Node"+(this.id?" "+this.id:"")+"]"}});Ext.tree.TreeNode=Ext.extend(Ext.data.Node,{constructor:function(a){a=a||{};if(Ext.isString(a)){a={text:a}}this.childrenRendered=false;this.rendered=false;Ext.tree.TreeNode.superclass.constructor.call(this,a);this.expanded=a.expanded===true;this.isTarget=a.isTarget!==false;this.draggable=a.draggable!==false&&a.allowDrag!==false;this.allowChildren=a.allowChildren!==false&&a.allowDrop!==false;this.text=a.text;this.disabled=a.disabled===true;this.hidden=a.hidden===true;this.addEvents("textchange","beforeexpand","beforecollapse","expand","disabledchange","collapse","beforeclick","click","checkchange","beforedblclick","dblclick","contextmenu","beforechildrenrendered");var b=this.attributes.uiProvider||this.defaultUI||Ext.tree.TreeNodeUI;this.ui=new b(this)},preventHScroll:true,isExpanded:function(){return this.expanded},getUI:function(){return this.ui},getLoader:function(){var a;return this.loader||((a=this.getOwnerTree())&&a.loader?a.loader:(this.loader=new Ext.tree.TreeLoader()))},setFirstChild:function(a){var b=this.firstChild;Ext.tree.TreeNode.superclass.setFirstChild.call(this,a);if(this.childrenRendered&&b&&a!=b){b.renderIndent(true,true)}if(this.rendered){this.renderIndent(true,true)}},setLastChild:function(b){var a=this.lastChild;Ext.tree.TreeNode.superclass.setLastChild.call(this,b);if(this.childrenRendered&&a&&b!=a){a.renderIndent(true,true)}if(this.rendered){this.renderIndent(true,true)}},appendChild:function(b){if(!b.render&&!Ext.isArray(b)){b=this.getLoader().createNode(b)}var a=Ext.tree.TreeNode.superclass.appendChild.call(this,b);if(a&&this.childrenRendered){a.render()}this.ui.updateExpandIcon();return a},removeChild:function(b,a){this.ownerTree.getSelectionModel().unselect(b);Ext.tree.TreeNode.superclass.removeChild.apply(this,arguments);if(!a){var c=b.ui.rendered;if(c){b.ui.remove()}if(c&&this.childNodes.length<1){this.collapse(false,false)}else{this.ui.updateExpandIcon()}if(!this.firstChild&&!this.isHiddenRoot()){this.childrenRendered=false}}return b},insertBefore:function(c,a){if(!c.render){c=this.getLoader().createNode(c)}var b=Ext.tree.TreeNode.superclass.insertBefore.call(this,c,a);if(b&&a&&this.childrenRendered){c.render()}this.ui.updateExpandIcon();return b},setText:function(b){var a=this.text;this.text=this.attributes.text=b;if(this.rendered){this.ui.onTextChange(this,b,a)}this.fireEvent("textchange",this,b,a)},setIconCls:function(b){var a=this.attributes.iconCls;this.attributes.iconCls=b;if(this.rendered){this.ui.onIconClsChange(this,b,a)}},setTooltip:function(a,b){this.attributes.qtip=a;this.attributes.qtipTitle=b;if(this.rendered){this.ui.onTipChange(this,a,b)}},setIcon:function(a){this.attributes.icon=a;if(this.rendered){this.ui.onIconChange(this,a)}},setHref:function(a,b){this.attributes.href=a;this.attributes.hrefTarget=b;if(this.rendered){this.ui.onHrefChange(this,a,b)}},setCls:function(b){var a=this.attributes.cls;this.attributes.cls=b;if(this.rendered){this.ui.onClsChange(this,b,a)}},select:function(){var a=this.getOwnerTree();if(a){a.getSelectionModel().select(this)}},unselect:function(a){var b=this.getOwnerTree();if(b){b.getSelectionModel().unselect(this,a)}},isSelected:function(){var a=this.getOwnerTree();return a?a.getSelectionModel().isSelected(this):false},expand:function(a,c,d,b){if(!this.expanded){if(this.fireEvent("beforeexpand",this,a,c)===false){return}if(!this.childrenRendered){this.renderChildren()}this.expanded=true;if(!this.isHiddenRoot()&&(this.getOwnerTree().animate&&c!==false)||c){this.ui.animExpand(function(){this.fireEvent("expand",this);this.runCallback(d,b||this,[this]);if(a===true){this.expandChildNodes(true,true)}}.createDelegate(this));return}else{this.ui.expand();this.fireEvent("expand",this);this.runCallback(d,b||this,[this])}}else{this.runCallback(d,b||this,[this])}if(a===true){this.expandChildNodes(true)}},runCallback:function(a,c,b){if(Ext.isFunction(a)){a.apply(c,b)}},isHiddenRoot:function(){return this.isRoot&&!this.getOwnerTree().rootVisible},collapse:function(b,g,h,e){if(this.expanded&&!this.isHiddenRoot()){if(this.fireEvent("beforecollapse",this,b,g)===false){return}this.expanded=false;if((this.getOwnerTree().animate&&g!==false)||g){this.ui.animCollapse(function(){this.fireEvent("collapse",this);this.runCallback(h,e||this,[this]);if(b===true){this.collapseChildNodes(true)}}.createDelegate(this));return}else{this.ui.collapse();this.fireEvent("collapse",this);this.runCallback(h,e||this,[this])}}else{if(!this.expanded){this.runCallback(h,e||this,[this])}}if(b===true){var d=this.childNodes;for(var c=0,a=d.length;c<a;c++){d[c].collapse(true,false)}}},delayedExpand:function(a){if(!this.expandProcId){this.expandProcId=this.expand.defer(a,this)}},cancelExpand:function(){if(this.expandProcId){clearTimeout(this.expandProcId)}this.expandProcId=false},toggle:function(){if(this.expanded){this.collapse()}else{this.expand()}},ensureVisible:function(c,b){var a=this.getOwnerTree();a.expandPath(this.parentNode?this.parentNode.getPath():this.getPath(),false,function(){var d=a.getNodeById(this.id);a.getTreeEl().scrollChildIntoView(d.ui.anchor);this.runCallback(c,b||this,[this])}.createDelegate(this))},expandChildNodes:function(b,e){var d=this.childNodes,c,a=d.length;for(c=0;c<a;c++){d[c].expand(b,e)}},collapseChildNodes:function(b){var d=this.childNodes;for(var c=0,a=d.length;c<a;c++){d[c].collapse(b)}},disable:function(){this.disabled=true;this.unselect();if(this.rendered&&this.ui.onDisableChange){this.ui.onDisableChange(this,true)}this.fireEvent("disabledchange",this,true)},enable:function(){this.disabled=false;if(this.rendered&&this.ui.onDisableChange){this.ui.onDisableChange(this,false)}this.fireEvent("disabledchange",this,false)},renderChildren:function(b){if(b!==false){this.fireEvent("beforechildrenrendered",this)}var d=this.childNodes;for(var c=0,a=d.length;c<a;c++){d[c].render(true)}this.childrenRendered=true},sort:function(e,d){Ext.tree.TreeNode.superclass.sort.apply(this,arguments);if(this.childrenRendered){var c=this.childNodes;for(var b=0,a=c.length;b<a;b++){c[b].render(true)}}},render:function(a){this.ui.render(a);if(!this.rendered){this.getOwnerTree().registerNode(this);this.rendered=true;if(this.expanded){this.expanded=false;this.expand(false,false)}}},renderIndent:function(b,e){if(e){this.ui.childIndent=null}this.ui.renderIndent();if(b===true&&this.childrenRendered){var d=this.childNodes;for(var c=0,a=d.length;c<a;c++){d[c].renderIndent(true,e)}}},beginUpdate:function(){this.childrenRendered=false},endUpdate:function(){if(this.expanded&&this.rendered){this.renderChildren()}},destroy:function(a){if(a===true){this.unselect(true)}Ext.tree.TreeNode.superclass.destroy.call(this,a);Ext.destroy(this.ui,this.loader);this.ui=this.loader=null},onIdChange:function(a){this.ui.onIdChange(a)}});Ext.tree.TreePanel.nodeTypes.node=Ext.tree.TreeNode;Ext.tree.AsyncTreeNode=function(a){this.loaded=a&&a.loaded===true;this.loading=false;Ext.tree.AsyncTreeNode.superclass.constructor.apply(this,arguments);this.addEvents("beforeload","load")};Ext.extend(Ext.tree.AsyncTreeNode,Ext.tree.TreeNode,{expand:function(b,e,h,c){if(this.loading){var g;var d=function(){if(!this.loading){clearInterval(g);this.expand(b,e,h,c)}}.createDelegate(this);g=setInterval(d,200);return}if(!this.loaded){if(this.fireEvent("beforeload",this)===false){return}this.loading=true;this.ui.beforeLoad(this);var a=this.loader||this.attributes.loader||this.getOwnerTree().getLoader();if(a){a.load(this,this.loadComplete.createDelegate(this,[b,e,h,c]),this);return}}Ext.tree.AsyncTreeNode.superclass.expand.call(this,b,e,h,c)},isLoading:function(){return this.loading},loadComplete:function(a,c,d,b){this.loading=false;this.loaded=true;this.ui.afterLoad(this);this.fireEvent("load",this);this.expand(a,c,d,b)},isLoaded:function(){return this.loaded},hasChildNodes:function(){if(!this.isLeaf()&&!this.loaded){return true}else{return Ext.tree.AsyncTreeNode.superclass.hasChildNodes.call(this)}},reload:function(b,a){this.collapse(false,false);while(this.firstChild){this.removeChild(this.firstChild).destroy()}this.childrenRendered=false;this.loaded=false;if(this.isHiddenRoot()){this.expanded=false}this.expand(false,false,b,a)}});Ext.tree.TreePanel.nodeTypes.async=Ext.tree.AsyncTreeNode;Ext.tree.TreeNodeUI=Ext.extend(Object,{constructor:function(a){Ext.apply(this,{node:a,rendered:false,animating:false,wasLeaf:true,ecc:"x-tree-ec-icon x-tree-elbow",emptyIcon:Ext.BLANK_IMAGE_URL})},removeChild:function(a){if(this.rendered){this.ctNode.removeChild(a.ui.getEl())}},beforeLoad:function(){this.addClass("x-tree-node-loading")},afterLoad:function(){this.removeClass("x-tree-node-loading")},onTextChange:function(b,c,a){if(this.rendered){this.textNode.innerHTML=c}},onIconClsChange:function(c,a,b){if(this.rendered){Ext.fly(this.iconNode).replaceClass(b,a)}},onIconChange:function(b,a){if(this.rendered){var c=Ext.isEmpty(a);this.iconNode.src=c?this.emptyIcon:a;Ext.fly(this.iconNode)[c?"removeClass":"addClass"]("x-tree-node-inline-icon")}},onTipChange:function(b,c,d){if(this.rendered){var a=Ext.isDefined(d);if(this.textNode.setAttributeNS){this.textNode.setAttributeNS("ext","qtip",c);if(a){this.textNode.setAttributeNS("ext","qtitle",d)}}else{this.textNode.setAttribute("ext:qtip",c);if(a){this.textNode.setAttribute("ext:qtitle",d)}}}},onHrefChange:function(b,a,c){if(this.rendered){this.anchor.href=this.getHref(a);if(Ext.isDefined(c)){this.anchor.target=c}}},onClsChange:function(c,a,b){if(this.rendered){Ext.fly(this.elNode).replaceClass(b,a)}},onDisableChange:function(a,b){this.disabled=b;if(this.checkbox){this.checkbox.disabled=b}this[b?"addClass":"removeClass"]("x-tree-node-disabled")},onSelectedChange:function(a){if(a){this.focus();this.addClass("x-tree-selected")}else{this.removeClass("x-tree-selected")}},onMove:function(a,h,e,g,d,b){this.childIndent=null;if(this.rendered){var i=g.ui.getContainer();if(!i){this.holder=document.createElement("div");this.holder.appendChild(this.wrap);return}var c=b?b.ui.getEl():null;if(c){i.insertBefore(this.wrap,c)}else{i.appendChild(this.wrap)}this.node.renderIndent(true,e!=g)}},addClass:function(a){if(this.elNode){Ext.fly(this.elNode).addClass(a)}},removeClass:function(a){if(this.elNode){Ext.fly(this.elNode).removeClass(a)}},remove:function(){if(this.rendered){this.holder=document.createElement("div");this.holder.appendChild(this.wrap)}},fireEvent:function(){return this.node.fireEvent.apply(this.node,arguments)},initEvents:function(){this.node.on("move",this.onMove,this);if(this.node.disabled){this.onDisableChange(this.node,true)}if(this.node.hidden){this.hide()}var b=this.node.getOwnerTree();var a=b.enableDD||b.enableDrag||b.enableDrop;if(a&&(!this.node.isRoot||b.rootVisible)){Ext.dd.Registry.register(this.elNode,{node:this.node,handles:this.getDDHandles(),isHandle:false})}},getDDHandles:function(){return[this.iconNode,this.textNode,this.elNode]},hide:function(){this.node.hidden=true;if(this.wrap){this.wrap.style.display="none"}},show:function(){this.node.hidden=false;if(this.wrap){this.wrap.style.display=""}},onContextMenu:function(a){if(this.node.hasListener("contextmenu")||this.node.getOwnerTree().hasListener("contextmenu")){a.preventDefault();this.focus();this.fireEvent("contextmenu",this.node,a)}},onClick:function(c){if(this.dropping){c.stopEvent();return}if(this.fireEvent("beforeclick",this.node,c)!==false){var b=c.getTarget("a");if(!this.disabled&&this.node.attributes.href&&b){this.fireEvent("click",this.node,c);return}else{if(b&&c.ctrlKey){c.stopEvent()}}c.preventDefault();if(this.disabled){return}if(this.node.attributes.singleClickExpand&&!this.animating&&this.node.isExpandable()){this.node.toggle()}this.fireEvent("click",this.node,c)}else{c.stopEvent()}},onDblClick:function(a){a.preventDefault();if(this.disabled){return}if(this.fireEvent("beforedblclick",this.node,a)!==false){if(this.checkbox){this.toggleCheck()}if(!this.animating&&this.node.isExpandable()){this.node.toggle()}this.fireEvent("dblclick",this.node,a)}},onOver:function(a){this.addClass("x-tree-node-over")},onOut:function(a){this.removeClass("x-tree-node-over")},onCheckChange:function(){var a=this.checkbox.checked;this.checkbox.defaultChecked=a;this.node.attributes.checked=a;this.fireEvent("checkchange",this.node,a)},ecClick:function(a){if(!this.animating&&this.node.isExpandable()){this.node.toggle()}},startDrop:function(){this.dropping=true},endDrop:function(){setTimeout(function(){this.dropping=false}.createDelegate(this),50)},expand:function(){this.updateExpandIcon();this.ctNode.style.display=""},focus:function(){if(!this.node.preventHScroll){try{this.anchor.focus()}catch(c){}}else{try{var b=this.node.getOwnerTree().getTreeEl().dom;var a=b.scrollLeft;this.anchor.focus();b.scrollLeft=a}catch(c){}}},toggleCheck:function(b){var a=this.checkbox;if(a){a.checked=(b===undefined?!a.checked:b);this.onCheckChange()}},blur:function(){try{this.anchor.blur()}catch(a){}},animExpand:function(b){var a=Ext.get(this.ctNode);a.stopFx();if(!this.node.isExpandable()){this.updateExpandIcon();this.ctNode.style.display="";Ext.callback(b);return}this.animating=true;this.updateExpandIcon();a.slideIn("t",{callback:function(){this.animating=false;Ext.callback(b)},scope:this,duration:this.node.ownerTree.duration||0.25})},highlight:function(){var a=this.node.getOwnerTree();Ext.fly(this.wrap).highlight(a.hlColor||"C3DAF9",{endColor:a.hlBaseColor})},collapse:function(){this.updateExpandIcon();this.ctNode.style.display="none"},animCollapse:function(b){var a=Ext.get(this.ctNode);a.enableDisplayMode("block");a.stopFx();this.animating=true;this.updateExpandIcon();a.slideOut("t",{callback:function(){this.animating=false;Ext.callback(b)},scope:this,duration:this.node.ownerTree.duration||0.25})},getContainer:function(){return this.ctNode},getEl:function(){return this.wrap},appendDDGhost:function(a){a.appendChild(this.elNode.cloneNode(true))},getDDRepairXY:function(){return Ext.lib.Dom.getXY(this.iconNode)},onRender:function(){this.render()},render:function(c){var e=this.node,b=e.attributes;var d=e.parentNode?e.parentNode.ui.getContainer():e.ownerTree.innerCt.dom;if(!this.rendered){this.rendered=true;this.renderElements(e,b,d,c);if(b.qtip){this.onTipChange(e,b.qtip,b.qtipTitle)}else{if(b.qtipCfg){b.qtipCfg.target=Ext.id(this.textNode);Ext.QuickTips.register(b.qtipCfg)}}this.initEvents();if(!this.node.expanded){this.updateExpandIcon(true)}}else{if(c===true){d.appendChild(this.wrap)}}},renderElements:function(e,k,j,l){this.indentMarkup=e.parentNode?e.parentNode.ui.getChildIndent():"";var g=Ext.isBoolean(k.checked),b,c=this.getHref(k.href),d=['<li class="x-tree-node"><div ext:tree-node-id="',e.id,'" class="x-tree-node-el x-tree-node-leaf x-unselectable ',k.cls,'" unselectable="on">','<span class="x-tree-node-indent">',this.indentMarkup,"</span>",'<img alt="" src="',this.emptyIcon,'" class="x-tree-ec-icon x-tree-elbow" />','<img alt="" src="',k.icon||this.emptyIcon,'" class="x-tree-node-icon',(k.icon?" x-tree-node-inline-icon":""),(k.iconCls?" "+k.iconCls:""),'" unselectable="on" />',g?('<input class="x-tree-node-cb" type="checkbox" '+(k.checked?'checked="checked" />':"/>")):"",'<a hidefocus="on" class="x-tree-node-anchor" href="',c,'" tabIndex="1" ',k.hrefTarget?' target="'+k.hrefTarget+'"':"",'><span unselectable="on">',e.text,"</span></a></div>",'<ul class="x-tree-node-ct" style="display:none;"></ul>',"</li>"].join("");if(l!==true&&e.nextSibling&&(b=e.nextSibling.ui.getEl())){this.wrap=Ext.DomHelper.insertHtml("beforeBegin",b,d)}else{this.wrap=Ext.DomHelper.insertHtml("beforeEnd",j,d)}this.elNode=this.wrap.childNodes[0];this.ctNode=this.wrap.childNodes[1];var i=this.elNode.childNodes;this.indentNode=i[0];this.ecNode=i[1];this.iconNode=i[2];var h=3;if(g){this.checkbox=i[3];this.checkbox.defaultChecked=this.checkbox.checked;h++}this.anchor=i[h];this.textNode=i[h].firstChild},getHref:function(a){return Ext.isEmpty(a)?(Ext.isGecko?"":"#"):a},getAnchor:function(){return this.anchor},getTextEl:function(){return this.textNode},getIconEl:function(){return this.iconNode},isChecked:function(){return this.checkbox?this.checkbox.checked:false},updateExpandIcon:function(){if(this.rendered){var g=this.node,d,c,a=g.isLast()?"x-tree-elbow-end":"x-tree-elbow",e=g.hasChildNodes();if(e||g.attributes.expandable){if(g.expanded){a+="-minus";d="x-tree-node-collapsed";c="x-tree-node-expanded"}else{a+="-plus";d="x-tree-node-expanded";c="x-tree-node-collapsed"}if(this.wasLeaf){this.removeClass("x-tree-node-leaf");this.wasLeaf=false}if(this.c1!=d||this.c2!=c){Ext.fly(this.elNode).replaceClass(d,c);this.c1=d;this.c2=c}}else{if(!this.wasLeaf){Ext.fly(this.elNode).replaceClass("x-tree-node-expanded","x-tree-node-collapsed");delete this.c1;delete this.c2;this.wasLeaf=true}}var b="x-tree-ec-icon "+a;if(this.ecc!=b){this.ecNode.className=b;this.ecc=b}}},onIdChange:function(a){if(this.rendered){this.elNode.setAttribute("ext:tree-node-id",a)}},getChildIndent:function(){if(!this.childIndent){var a=[],b=this.node;while(b){if(!b.isRoot||(b.isRoot&&b.ownerTree.rootVisible)){if(!b.isLast()){a.unshift('<img alt="" src="'+this.emptyIcon+'" class="x-tree-elbow-line" />')}else{a.unshift('<img alt="" src="'+this.emptyIcon+'" class="x-tree-icon" />')}}b=b.parentNode}this.childIndent=a.join("")}return this.childIndent},renderIndent:function(){if(this.rendered){var a="",b=this.node.parentNode;if(b){a=b.ui.getChildIndent()}if(this.indentMarkup!=a){this.indentNode.innerHTML=a;this.indentMarkup=a}this.updateExpandIcon()}},destroy:function(){if(this.elNode){Ext.dd.Registry.unregister(this.elNode.id)}Ext.each(["textnode","anchor","checkbox","indentNode","ecNode","iconNode","elNode","ctNode","wrap","holder"],function(a){if(this[a]){Ext.fly(this[a]).remove();delete this[a]}},this);delete this.node}});Ext.tree.RootTreeNodeUI=Ext.extend(Ext.tree.TreeNodeUI,{render:function(){if(!this.rendered){var a=this.node.ownerTree.innerCt.dom;this.node.expanded=true;a.innerHTML='<div class="x-tree-root-node"></div>';this.wrap=this.ctNode=a.firstChild}},collapse:Ext.emptyFn,expand:Ext.emptyFn});Ext.tree.TreeLoader=function(a){this.baseParams={};Ext.apply(this,a);this.addEvents("beforeload","load","loadexception");Ext.tree.TreeLoader.superclass.constructor.call(this);if(Ext.isString(this.paramOrder)){this.paramOrder=this.paramOrder.split(/[\s,|]/)}};Ext.extend(Ext.tree.TreeLoader,Ext.util.Observable,{uiProviders:{},clearOnLoad:true,paramOrder:undefined,paramsAsHash:false,nodeParameter:"node",directFn:undefined,load:function(b,c,a){if(this.clearOnLoad){while(b.firstChild){b.removeChild(b.firstChild)}}if(this.doPreload(b)){this.runCallback(c,a||b,[b])}else{if(this.directFn||this.dataUrl||this.url){this.requestData(b,c,a||b)}}},doPreload:function(d){if(d.attributes.children){if(d.childNodes.length<1){var c=d.attributes.children;d.beginUpdate();for(var b=0,a=c.length;b<a;b++){var e=d.appendChild(this.createNode(c[b]));if(this.preloadChildren){this.doPreload(e)}}d.endUpdate()}return true}return false},getParams:function(g){var e=Ext.apply({},this.baseParams),h=this.nodeParameter,b=this.paramOrder;h&&(e[h]=g.id);if(this.directFn){var c=[g.id];if(b){if(h&&b.indexOf(h)>-1){c=[]}for(var d=0,a=b.length;d<a;d++){c.push(e[b[d]])}}else{if(this.paramsAsHash){c=[e]}}return c}else{return e}},requestData:function(c,d,b){if(this.fireEvent("beforeload",this,c,d)!==false){if(this.directFn){var a=this.getParams(c);a.push(this.processDirectResponse.createDelegate(this,[{callback:d,node:c,scope:b}],true));this.directFn.apply(window,a)}else{this.transId=Ext.Ajax.request({method:this.requestMethod,url:this.dataUrl||this.url,success:this.handleResponse,failure:this.handleFailure,scope:this,argument:{callback:d,node:c,scope:b},params:this.getParams(c)})}}else{this.runCallback(d,b||c,[])}},processDirectResponse:function(a,b,c){if(b.status){this.handleResponse({responseData:Ext.isArray(a)?a:null,responseText:a,argument:c})}else{this.handleFailure({argument:c})}},runCallback:function(a,c,b){if(Ext.isFunction(a)){a.apply(c,b)}},isLoading:function(){return !!this.transId},abort:function(){if(this.isLoading()){Ext.Ajax.abort(this.transId)}},createNode:function(attr){if(this.baseAttrs){Ext.applyIf(attr,this.baseAttrs)}if(this.applyLoader!==false&&!attr.loader){attr.loader=this}if(Ext.isString(attr.uiProvider)){attr.uiProvider=this.uiProviders[attr.uiProvider]||eval(attr.uiProvider)}if(attr.nodeType){return new Ext.tree.TreePanel.nodeTypes[attr.nodeType](attr)}else{return attr.leaf?new Ext.tree.TreeNode(attr):new Ext.tree.AsyncTreeNode(attr)}},processResponse:function(d,c,k,l){var m=d.responseText;try{var a=d.responseData||Ext.decode(m);c.beginUpdate();for(var g=0,h=a.length;g<h;g++){var b=this.createNode(a[g]);if(b){c.appendChild(b)}}c.endUpdate();this.runCallback(k,l||c,[c])}catch(j){this.handleFailure(d)}},handleResponse:function(c){this.transId=false;var b=c.argument;this.processResponse(c,b.node,b.callback,b.scope);this.fireEvent("load",this,b.node,c)},handleFailure:function(c){this.transId=false;var b=c.argument;this.fireEvent("loadexception",this,b.node,c);this.runCallback(b.callback,b.scope||b.node,[b.node])},destroy:function(){this.abort();this.purgeListeners()}});Ext.tree.TreeFilter=function(a,b){this.tree=a;this.filtered={};Ext.apply(this,b)};Ext.tree.TreeFilter.prototype={clearBlank:false,reverse:false,autoClear:false,remove:false,filter:function(d,a,b){a=a||"text";var c;if(typeof d=="string"){var e=d.length;if(e==0&&this.clearBlank){this.clear();return}d=d.toLowerCase();c=function(g){return g.attributes[a].substr(0,e).toLowerCase()==d}}else{if(d.exec){c=function(g){return d.test(g.attributes[a])}}else{throw"Illegal filter type, must be string or regex"}}this.filterBy(c,null,b)},filterBy:function(d,c,b){b=b||this.tree.root;if(this.autoClear){this.clear()}var a=this.filtered,i=this.reverse;var e=function(k){if(k==b){return true}if(a[k.id]){return false}var j=d.call(c||k,k);if(!j||i){a[k.id]=k;k.ui.hide();return false}return true};b.cascade(e);if(this.remove){for(var h in a){if(typeof h!="function"){var g=a[h];if(g&&g.parentNode){g.parentNode.removeChild(g)}}}}},clear:function(){var b=this.tree;var a=this.filtered;for(var d in a){if(typeof d!="function"){var c=a[d];if(c){c.ui.show()}}}this.filtered={}}};Ext.tree.TreeSorter=Ext.extend(Object,{constructor:function(a,c){Ext.apply(this,c);a.on({scope:this,beforechildrenrendered:this.doSort,append:this.updateSort,insert:this.updateSort,textchange:this.updateSortParent});var e=this.dir&&this.dir.toLowerCase()=="desc",i=this.property||"text",d=this.sortType,h=this.folderSort,b=this.caseSensitive===true,g=this.leafAttr||"leaf";if(Ext.isString(d)){d=Ext.data.SortTypes[d]}this.sortFn=function(o,m){var k=o.attributes,j=m.attributes;if(h){if(k[g]&&!j[g]){return 1}if(!k[g]&&j[g]){return -1}}var n=k[i],l=j[i],q=d?d(n,o):(b?n:n.toUpperCase()),p=d?d(l,m):(b?l:l.toUpperCase());if(q<p){return e?1:-1}else{if(q>p){return e?-1:1}}return 0}},doSort:function(a){a.sort(this.sortFn)},updateSort:function(a,b){if(b.childrenRendered){this.doSort.defer(1,this,[b])}},updateSortParent:function(a){var b=a.parentNode;if(b&&b.childrenRendered){this.doSort.defer(1,this,[b])}}});if(Ext.dd.DropZone){Ext.tree.TreeDropZone=function(a,b){this.allowParentInsert=b.allowParentInsert||false;this.allowContainerDrop=b.allowContainerDrop||false;this.appendOnly=b.appendOnly||false;Ext.tree.TreeDropZone.superclass.constructor.call(this,a.getTreeEl(),b);this.tree=a;this.dragOverData={};this.lastInsertClass="x-tree-no-status"};Ext.extend(Ext.tree.TreeDropZone,Ext.dd.DropZone,{ddGroup:"TreeDD",expandDelay:1000,expandNode:function(a){if(a.hasChildNodes()&&!a.isExpanded()){a.expand(false,null,this.triggerCacheRefresh.createDelegate(this))}},queueExpand:function(a){this.expandProcId=this.expandNode.defer(this.expandDelay,this,[a])},cancelExpand:function(){if(this.expandProcId){clearTimeout(this.expandProcId);this.expandProcId=false}},isValidDropPoint:function(a,k,i,d,c){if(!a||!c){return false}var g=a.node;var h=c.node;if(!(g&&g.isTarget&&k)){return false}if(k=="append"&&g.allowChildren===false){return false}if((k=="above"||k=="below")&&(g.parentNode&&g.parentNode.allowChildren===false)){return false}if(h&&(g==h||h.contains(g))){return false}var b=this.dragOverData;b.tree=this.tree;b.target=g;b.data=c;b.point=k;b.source=i;b.rawEvent=d;b.dropNode=h;b.cancel=false;var j=this.tree.fireEvent("nodedragover",b);return b.cancel===false&&j!==false},getDropPoint:function(h,g,l){var m=g.node;if(m.isRoot){return m.allowChildren!==false?"append":false}var c=g.ddel;var o=Ext.lib.Dom.getY(c),j=o+c.offsetHeight;var i=Ext.lib.Event.getPageY(h);var k=m.allowChildren===false||m.isLeaf();if(this.appendOnly||m.parentNode.allowChildren===false){return k?false:"append"}var d=false;if(!this.allowParentInsert){d=m.hasChildNodes()&&m.isExpanded()}var a=(j-o)/(k?2:3);if(i>=o&&i<(o+a)){return"above"}else{if(!d&&(k||i>=j-a&&i<=j)){return"below"}else{return"append"}}},onNodeEnter:function(d,a,c,b){this.cancelExpand()},onContainerOver:function(a,c,b){if(this.allowContainerDrop&&this.isValidDropPoint({ddel:this.tree.getRootNode().ui.elNode,node:this.tree.getRootNode()},"append",a,c,b)){return this.dropAllowed}return this.dropNotAllowed},onNodeOver:function(b,i,h,g){var k=this.getDropPoint(h,b,i);var c=b.node;if(!this.expandProcId&&k=="append"&&c.hasChildNodes()&&!b.node.isExpanded()){this.queueExpand(c)}else{if(k!="append"){this.cancelExpand()}}var d=this.dropNotAllowed;if(this.isValidDropPoint(b,k,i,h,g)){if(k){var a=b.ddel;var j;if(k=="above"){d=b.node.isFirst()?"x-tree-drop-ok-above":"x-tree-drop-ok-between";j="x-tree-drag-insert-above"}else{if(k=="below"){d=b.node.isLast()?"x-tree-drop-ok-below":"x-tree-drop-ok-between";j="x-tree-drag-insert-below"}else{d="x-tree-drop-ok-append";j="x-tree-drag-append"}}if(this.lastInsertClass!=j){Ext.fly(a).replaceClass(this.lastInsertClass,j);this.lastInsertClass=j}}}return d},onNodeOut:function(d,a,c,b){this.cancelExpand();this.removeDropIndicators(d)},onNodeDrop:function(i,b,h,d){var a=this.getDropPoint(h,i,b);var g=i.node;g.ui.startDrop();if(!this.isValidDropPoint(i,a,b,h,d)){g.ui.endDrop();return false}var c=d.node||(b.getTreeNode?b.getTreeNode(d,g,a,h):null);return this.processDrop(g,d,a,b,h,c)},onContainerDrop:function(a,g,c){if(this.allowContainerDrop&&this.isValidDropPoint({ddel:this.tree.getRootNode().ui.elNode,node:this.tree.getRootNode()},"append",a,g,c)){var d=this.tree.getRootNode();d.ui.startDrop();var b=c.node||(a.getTreeNode?a.getTreeNode(c,d,"append",g):null);return this.processDrop(d,c,"append",a,g,b)}return false},processDrop:function(j,h,b,a,i,d){var g={tree:this.tree,target:j,data:h,point:b,source:a,rawEvent:i,dropNode:d,cancel:!d,dropStatus:false};var c=this.tree.fireEvent("beforenodedrop",g);if(c===false||g.cancel===true||!g.dropNode){j.ui.endDrop();return g.dropStatus}j=g.target;if(b=="append"&&!j.isExpanded()){j.expand(false,null,function(){this.completeDrop(g)}.createDelegate(this))}else{this.completeDrop(g)}return true},completeDrop:function(h){var d=h.dropNode,e=h.point,c=h.target;if(!Ext.isArray(d)){d=[d]}var g;for(var b=0,a=d.length;b<a;b++){g=d[b];if(e=="above"){c.parentNode.insertBefore(g,c)}else{if(e=="below"){c.parentNode.insertBefore(g,c.nextSibling)}else{c.appendChild(g)}}}g.ui.focus();if(Ext.enableFx&&this.tree.hlDrop){g.ui.highlight()}c.ui.endDrop();this.tree.fireEvent("nodedrop",h)},afterNodeMoved:function(a,c,g,d,b){if(Ext.enableFx&&this.tree.hlDrop){b.ui.focus();b.ui.highlight()}this.tree.fireEvent("nodedrop",this.tree,d,c,a,g)},getTree:function(){return this.tree},removeDropIndicators:function(b){if(b&&b.ddel){var a=b.ddel;Ext.fly(a).removeClass(["x-tree-drag-insert-above","x-tree-drag-insert-below","x-tree-drag-append"]);this.lastInsertClass="_noclass"}},beforeDragDrop:function(b,a,c){this.cancelExpand();return true},afterRepair:function(a){if(a&&Ext.enableFx){a.node.ui.highlight()}this.hideProxy()}})}if(Ext.dd.DragZone){Ext.tree.TreeDragZone=function(a,b){Ext.tree.TreeDragZone.superclass.constructor.call(this,a.innerCt,b);this.tree=a};Ext.extend(Ext.tree.TreeDragZone,Ext.dd.DragZone,{ddGroup:"TreeDD",onBeforeDrag:function(a,b){var c=a.node;return c&&c.draggable&&!c.disabled},onInitDrag:function(b){var a=this.dragData;this.tree.getSelectionModel().select(a.node);this.tree.eventModel.disable();this.proxy.update("");a.node.ui.appendDDGhost(this.proxy.ghost.dom);this.tree.fireEvent("startdrag",this.tree,a.node,b)},getRepairXY:function(b,a){return a.node.ui.getDDRepairXY()},onEndDrag:function(a,b){this.tree.eventModel.enable.defer(100,this.tree.eventModel);this.tree.fireEvent("enddrag",this.tree,a.node,b)},onValidDrop:function(a,b,c){this.tree.fireEvent("dragdrop",this.tree,this.dragData.node,a,b);this.hideProxy()},beforeInvalidDrop:function(a,c){var b=this.tree.getSelectionModel();b.clearSelections();b.select(this.dragData.node)},afterRepair:function(){if(Ext.enableFx&&this.tree.hlDrop){Ext.Element.fly(this.dragData.ddel).highlight(this.hlColor||"c3daf9")}this.dragging=false}})}Ext.tree.TreeEditor=function(a,c,b){c=c||{};var d=c.events?c:new Ext.form.TextField(c);Ext.tree.TreeEditor.superclass.constructor.call(this,d,b);this.tree=a;if(!a.rendered){a.on("render",this.initEditor,this)}else{this.initEditor(a)}};Ext.extend(Ext.tree.TreeEditor,Ext.Editor,{alignment:"l-l",autoSize:false,hideEl:false,cls:"x-small-editor x-tree-editor",shim:false,shadow:"frame",maxWidth:250,editDelay:350,initEditor:function(a){a.on({scope:this,beforeclick:this.beforeNodeClick,dblclick:this.onNodeDblClick});this.on({scope:this,complete:this.updateNode,beforestartedit:this.fitToTree,specialkey:this.onSpecialKey});this.on("startedit",this.bindScroll,this,{delay:10})},fitToTree:function(b,c){var e=this.tree.getTreeEl().dom,d=c.dom;if(e.scrollLeft>d.offsetLeft){e.scrollLeft=d.offsetLeft}var a=Math.min(this.maxWidth,(e.clientWidth>20?e.clientWidth:e.offsetWidth)-Math.max(0,d.offsetLeft-e.scrollLeft)-5);this.setSize(a,"")},triggerEdit:function(a,c){this.completeEdit();if(a.attributes.editable!==false){this.editNode=a;if(this.tree.autoScroll){Ext.fly(a.ui.getEl()).scrollIntoView(this.tree.body)}var b=a.text||"";if(!Ext.isGecko&&Ext.isEmpty(a.text)){a.setText("&#160;")}this.autoEditTimer=this.startEdit.defer(this.editDelay,this,[a.ui.textNode,b]);return false}},bindScroll:function(){this.tree.getTreeEl().on("scroll",this.cancelEdit,this)},beforeNodeClick:function(a,b){clearTimeout(this.autoEditTimer);if(this.tree.getSelectionModel().isSelected(a)){b.stopEvent();return this.triggerEdit(a)}},onNodeDblClick:function(a,b){clearTimeout(this.autoEditTimer)},updateNode:function(a,b){this.tree.getTreeEl().un("scroll",this.cancelEdit,this);this.editNode.setText(b)},onHide:function(){Ext.tree.TreeEditor.superclass.onHide.call(this);if(this.editNode){this.editNode.ui.focus.defer(50,this.editNode.ui)}},onSpecialKey:function(c,b){var a=b.getKey();if(a==b.ESC){b.stopEvent();this.cancelEdit()}else{if(a==b.ENTER&&!b.hasModifier()){b.stopEvent();this.completeEdit()}}},onDestroy:function(){clearTimeout(this.autoEditTimer);Ext.tree.TreeEditor.superclass.onDestroy.call(this);var a=this.tree;a.un("beforeclick",this.beforeNodeClick,this);a.un("dblclick",this.onNodeDblClick,this)}});var swfobject=function(){var E="undefined",s="object",T="Shockwave Flash",X="ShockwaveFlash.ShockwaveFlash",r="application/x-shockwave-flash",S="SWFObjectExprInst",y="onreadystatechange",P=window,k=document,u=navigator,U=false,V=[i],p=[],O=[],J=[],m,R,F,C,K=false,a=false,o,H,n=true,N=function(){var ab=typeof k.getElementById!=E&&typeof k.getElementsByTagName!=E&&typeof k.createElement!=E,ai=u.userAgent.toLowerCase(),Z=u.platform.toLowerCase(),af=Z?(/win/).test(Z):/win/.test(ai),ad=Z?(/mac/).test(Z):/mac/.test(ai),ag=/webkit/.test(ai)?parseFloat(ai.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,Y=!+"\v1",ah=[0,0,0],ac=null;if(typeof u.plugins!=E&&typeof u.plugins[T]==s){ac=u.plugins[T].description;if(ac&&!(typeof u.mimeTypes!=E&&u.mimeTypes[r]&&!u.mimeTypes[r].enabledPlugin)){U=true;Y=false;ac=ac.replace(/^.*\s+(\S+\s+\S+$)/,"$1");ah[0]=parseInt(ac.replace(/^(.*)\..*$/,"$1"),10);ah[1]=parseInt(ac.replace(/^.*\.(.*)\s.*$/,"$1"),10);ah[2]=/[a-zA-Z]/.test(ac)?parseInt(ac.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof P.ActiveXObject!=E){try{var ae=new ActiveXObject(X);if(ae){ac=ae.GetVariable("$version");if(ac){Y=true;ac=ac.split(" ")[1].split(",");ah=[parseInt(ac[0],10),parseInt(ac[1],10),parseInt(ac[2],10)]}}}catch(aa){}}}return{w3:ab,pv:ah,wk:ag,ie:Y,win:af,mac:ad}}(),l=function(){if(!N.w3){return}if((typeof k.readyState!=E&&k.readyState=="complete")||(typeof k.readyState==E&&(k.getElementsByTagName("body")[0]||k.body))){g()}if(!K){if(typeof k.addEventListener!=E){k.addEventListener("DOMContentLoaded",g,false)}if(N.ie&&N.win){k.attachEvent(y,function(){if(k.readyState=="complete"){k.detachEvent(y,arguments.callee);g()}});if(P==top){(function(){if(K){return}try{k.documentElement.doScroll("left")}catch(Y){setTimeout(arguments.callee,0);return}g()})()}}if(N.wk){(function(){if(K){return}if(!(/loaded|complete/).test(k.readyState)){setTimeout(arguments.callee,0);return}g()})()}t(g)}}();function g(){if(K){return}try{var aa=k.getElementsByTagName("body")[0].appendChild(D("span"));aa.parentNode.removeChild(aa)}catch(ab){return}K=true;var Y=V.length;for(var Z=0;Z<Y;Z++){V[Z]()}}function L(Y){if(K){Y()}else{V[V.length]=Y}}function t(Z){if(typeof P.addEventListener!=E){P.addEventListener("load",Z,false)}else{if(typeof k.addEventListener!=E){k.addEventListener("load",Z,false)}else{if(typeof P.attachEvent!=E){j(P,"onload",Z)}else{if(typeof P.onload=="function"){var Y=P.onload;P.onload=function(){Y();Z()}}else{P.onload=Z}}}}}function i(){I()}function W(){var Y=k.getElementsByTagName("body")[0];var ab=D(s);ab.setAttribute("type",r);var aa=Y.appendChild(ab);if(aa){var Z=0;(function(){if(typeof aa.GetVariable!=E){var ac=aa.GetVariable("$version");if(ac){ac=ac.split(" ")[1].split(",");N.pv=[parseInt(ac[0],10),parseInt(ac[1],10),parseInt(ac[2],10)]}}else{if(Z<10){Z++;setTimeout(arguments.callee,10);return}}Y.removeChild(ab);aa=null;I()})()}else{I()}}function I(){var ah=p.length;if(ah>0){for(var ag=0;ag<ah;ag++){var Z=p[ag].id;var ac=p[ag].callbackFn;var ab={success:false,id:Z};if(N.pv[0]>0){var af=c(Z);if(af){if(G(p[ag].swfVersion)&&!(N.wk&&N.wk<312)){x(Z,true);if(ac){ab.success=true;ab.ref=A(Z);ac(ab)}}else{if(p[ag].expressInstall&&B()){var aj={};aj.data=p[ag].expressInstall;aj.width=af.getAttribute("width")||"0";aj.height=af.getAttribute("height")||"0";if(af.getAttribute("class")){aj.styleclass=af.getAttribute("class")}if(af.getAttribute("align")){aj.align=af.getAttribute("align")}var ai={};var Y=af.getElementsByTagName("param");var ad=Y.length;for(var ae=0;ae<ad;ae++){if(Y[ae].getAttribute("name").toLowerCase()!="movie"){ai[Y[ae].getAttribute("name")]=Y[ae].getAttribute("value")}}Q(aj,ai,Z,ac)}else{q(af);if(ac){ac(ab)}}}}}else{x(Z,true);if(ac){var aa=A(Z);if(aa&&typeof aa.SetVariable!=E){ab.success=true;ab.ref=aa}ac(ab)}}}}}function A(ab){var Y=null;var Z=c(ab);if(Z&&Z.nodeName=="OBJECT"){if(typeof Z.SetVariable!=E){Y=Z}else{var aa=Z.getElementsByTagName(s)[0];if(aa){Y=aa}}}return Y}function B(){return !a&&G("6.0.65")&&(N.win||N.mac)&&!(N.wk&&N.wk<312)}function Q(ab,ac,Y,aa){a=true;F=aa||null;C={success:false,id:Y};var af=c(Y);if(af){if(af.nodeName=="OBJECT"){m=h(af);R=null}else{m=af;R=Y}ab.id=S;if(typeof ab.width==E||(!(/%$/).test(ab.width)&&parseInt(ab.width,10)<310)){ab.width="310"}if(typeof ab.height==E||(!(/%$/).test(ab.height)&&parseInt(ab.height,10)<137)){ab.height="137"}k.title=k.title.slice(0,47)+" - Flash Player Installation";var ae=N.ie&&N.win?"ActiveX":"PlugIn",ad="MMredirectURL="+P.location.toString().replace(/&/g,"%26")+"&MMplayerType="+ae+"&MMdoctitle="+k.title;if(typeof ac.flashvars!=E){ac.flashvars+="&"+ad}else{ac.flashvars=ad}if(N.ie&&N.win&&af.readyState!=4){var Z=D("div");Y+="SWFObjectNew";Z.setAttribute("id",Y);af.parentNode.insertBefore(Z,af);af.style.display="none";(function(){if(af.readyState==4){af.parentNode.removeChild(af)}else{setTimeout(arguments.callee,10)}})()}v(ab,ac,Y)}}function q(Z){if(N.ie&&N.win&&Z.readyState!=4){var Y=D("div");Z.parentNode.insertBefore(Y,Z);Y.parentNode.replaceChild(h(Z),Y);Z.style.display="none";(function(){if(Z.readyState==4){Z.parentNode.removeChild(Z)}else{setTimeout(arguments.callee,10)}})()}else{Z.parentNode.replaceChild(h(Z),Z)}}function h(ad){var ab=D("div");if(N.win&&N.ie){ab.innerHTML=ad.innerHTML}else{var Z=ad.getElementsByTagName(s)[0];if(Z){var ae=Z.childNodes;if(ae){var Y=ae.length;for(var aa=0;aa<Y;aa++){if(!(ae[aa].nodeType==1&&ae[aa].nodeName=="PARAM")&&!(ae[aa].nodeType==8)){ab.appendChild(ae[aa].cloneNode(true))}}}}}return ab}function v(aj,ah,Z){var Y,ab=c(Z);if(N.wk&&N.wk<312){return Y}if(ab){if(typeof aj.id==E){aj.id=Z}if(N.ie&&N.win){var ai="";for(var af in aj){if(aj[af]!=Object.prototype[af]){if(af.toLowerCase()=="data"){ah.movie=aj[af]}else{if(af.toLowerCase()=="styleclass"){ai+=' class="'+aj[af]+'"'}else{if(af.toLowerCase()!="classid"){ai+=" "+af+'="'+aj[af]+'"'}}}}}var ag="";for(var ae in ah){if(ah[ae]!=Object.prototype[ae]){ag+='<param name="'+ae+'" value="'+ah[ae]+'" />'}}ab.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+ai+">"+ag+"</object>";O[O.length]=aj.id;Y=c(aj.id)}else{var aa=D(s);aa.setAttribute("type",r);for(var ad in aj){if(aj[ad]!=Object.prototype[ad]){if(ad.toLowerCase()=="styleclass"){aa.setAttribute("class",aj[ad])}else{if(ad.toLowerCase()!="classid"){aa.setAttribute(ad,aj[ad])}}}}for(var ac in ah){if(ah[ac]!=Object.prototype[ac]&&ac.toLowerCase()!="movie"){e(aa,ac,ah[ac])}}ab.parentNode.replaceChild(aa,ab);Y=aa}}return Y}function e(aa,Y,Z){var ab=D("param");ab.setAttribute("name",Y);ab.setAttribute("value",Z);aa.appendChild(ab)}function z(Z){var Y=c(Z);if(Y&&Y.nodeName=="OBJECT"){if(N.ie&&N.win){Y.style.display="none";(function(){if(Y.readyState==4){b(Z)}else{setTimeout(arguments.callee,10)}})()}else{Y.parentNode.removeChild(Y)}}}function b(aa){var Z=c(aa);if(Z){for(var Y in Z){if(typeof Z[Y]=="function"){Z[Y]=null}}Z.parentNode.removeChild(Z)}}function c(aa){var Y=null;try{Y=k.getElementById(aa)}catch(Z){}return Y}function D(Y){return k.createElement(Y)}function j(aa,Y,Z){aa.attachEvent(Y,Z);J[J.length]=[aa,Y,Z]}function G(aa){var Z=N.pv,Y=aa.split(".");Y[0]=parseInt(Y[0],10);Y[1]=parseInt(Y[1],10)||0;Y[2]=parseInt(Y[2],10)||0;return(Z[0]>Y[0]||(Z[0]==Y[0]&&Z[1]>Y[1])||(Z[0]==Y[0]&&Z[1]==Y[1]&&Z[2]>=Y[2]))?true:false}function w(ad,Z,ae,ac){if(N.ie&&N.mac){return}var ab=k.getElementsByTagName("head")[0];if(!ab){return}var Y=(ae&&typeof ae=="string")?ae:"screen";if(ac){o=null;H=null}if(!o||H!=Y){var aa=D("style");aa.setAttribute("type","text/css");aa.setAttribute("media",Y);o=ab.appendChild(aa);if(N.ie&&N.win&&typeof k.styleSheets!=E&&k.styleSheets.length>0){o=k.styleSheets[k.styleSheets.length-1]}H=Y}if(N.ie&&N.win){if(o&&typeof o.addRule==s){o.addRule(ad,Z)}}else{if(o&&typeof k.createTextNode!=E){o.appendChild(k.createTextNode(ad+" {"+Z+"}"))}}}function x(aa,Y){if(!n){return}var Z=Y?"visible":"hidden";if(K&&c(aa)){c(aa).style.visibility=Z}else{w("#"+aa,"visibility:"+Z)}}function M(Z){var aa=/[\\\"<>\.;]/;var Y=aa.exec(Z)!=null;return Y&&typeof encodeURIComponent!=E?encodeURIComponent(Z):Z}var d=function(){if(N.ie&&N.win){window.attachEvent("onunload",function(){var ad=J.length;for(var ac=0;ac<ad;ac++){J[ac][0].detachEvent(J[ac][1],J[ac][2])}var aa=O.length;for(var ab=0;ab<aa;ab++){z(O[ab])}for(var Z in N){N[Z]=null}N=null;for(var Y in swfobject){swfobject[Y]=null}swfobject=null;window.detachEvent("onunload",arguments.callee)})}}();return{registerObject:function(ac,Y,ab,aa){if(N.w3&&ac&&Y){var Z={};Z.id=ac;Z.swfVersion=Y;Z.expressInstall=ab;Z.callbackFn=aa;p[p.length]=Z;x(ac,false)}else{if(aa){aa({success:false,id:ac})}}},getObjectById:function(Y){if(N.w3){return A(Y)}},embedSWF:function(ac,ai,af,ah,Z,ab,aa,ae,ag,ad){var Y={success:false,id:ai};if(N.w3&&!(N.wk&&N.wk<312)&&ac&&ai&&af&&ah&&Z){x(ai,false);L(function(){af+="";ah+="";var ak={};if(ag&&typeof ag===s){for(var am in ag){ak[am]=ag[am]}}ak.data=ac;ak.width=af;ak.height=ah;var an={};if(ae&&typeof ae===s){for(var al in ae){an[al]=ae[al]}}if(aa&&typeof aa===s){for(var aj in aa){if(typeof an.flashvars!=E){an.flashvars+="&"+aj+"="+aa[aj]}else{an.flashvars=aj+"="+aa[aj]}}}if(G(Z)){var ao=v(ak,an,ai);if(ak.id==ai){x(ai,true)}Y.success=true;Y.ref=ao}else{if(ab&&B()){ak.data=ab;Q(ak,an,ai,ad);return}else{x(ai,true)}}if(ad){ad(Y)}})}else{if(ad){ad(Y)}}},switchOffAutoHideShow:function(){n=false},ua:N,getFlashPlayerVersion:function(){return{major:N.pv[0],minor:N.pv[1],release:N.pv[2]}},hasFlashPlayerVersion:G,createSWF:function(aa,Z,Y){if(N.w3){return v(aa,Z,Y)}else{return undefined}},showExpressInstall:function(aa,ab,Y,Z){if(N.w3&&B()){Q(aa,ab,Y,Z)}},removeSWF:function(Y){if(N.w3){z(Y)}},createCSS:function(ab,aa,Z,Y){if(N.w3){w(ab,aa,Z,Y)}},addDomLoadEvent:L,addLoadEvent:t,getQueryParamValue:function(ab){var aa=k.location.search||k.location.hash;if(aa){if(/\?/.test(aa)){aa=aa.split("?")[1]}if(ab==null){return M(aa)}var Z=aa.split("&");for(var Y=0;Y<Z.length;Y++){if(Z[Y].substring(0,Z[Y].indexOf("="))==ab){return M(Z[Y].substring((Z[Y].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(a){var Y=c(S);if(Y&&m){Y.parentNode.replaceChild(m,Y);if(R){x(R,true);if(N.ie&&N.win){m.style.display="block"}}if(F){F(C)}}a=false}}}}();Ext.FlashComponent=Ext.extend(Ext.BoxComponent,{flashVersion:"9.0.115",backgroundColor:"#ffffff",wmode:"opaque",flashVars:undefined,flashParams:undefined,url:undefined,swfId:undefined,swfWidth:"100%",swfHeight:"100%",expressInstall:false,initComponent:function(){Ext.FlashComponent.superclass.initComponent.call(this);this.addEvents("initialize")},onRender:function(){Ext.FlashComponent.superclass.onRender.apply(this,arguments);var b=Ext.apply({allowScriptAccess:"always",bgcolor:this.backgroundColor,wmode:this.wmode},this.flashParams),a=Ext.apply({allowedDomain:document.location.hostname,YUISwfId:this.getId(),YUIBridgeCallback:"Ext.FlashEventProxy.onEvent"},this.flashVars);new swfobject.embedSWF(this.url,this.id,this.swfWidth,this.swfHeight,this.flashVersion,this.expressInstall?Ext.FlashComponent.EXPRESS_INSTALL_URL:undefined,a,b);this.swf=Ext.getDom(this.id);this.el=Ext.get(this.swf)},getSwfId:function(){return this.swfId||(this.swfId="extswf"+(++Ext.Component.AUTO_ID))},getId:function(){return this.id||(this.id="extflashcmp"+(++Ext.Component.AUTO_ID))},onFlashEvent:function(a){switch(a.type){case"swfReady":this.initSwf();return;case"log":return}a.component=this;this.fireEvent(a.type.toLowerCase().replace(/event$/,""),a)},initSwf:function(){this.onSwfReady(!!this.isInitialized);this.isInitialized=true;this.fireEvent("initialize",this)},beforeDestroy:function(){if(this.rendered){swfobject.removeSWF(this.swf.id)}Ext.FlashComponent.superclass.beforeDestroy.call(this)},onSwfReady:Ext.emptyFn});Ext.FlashComponent.EXPRESS_INSTALL_URL="http://swfobject.googlecode.com/svn/trunk/swfobject/expressInstall.swf";Ext.reg("flash",Ext.FlashComponent);Ext.FlashEventProxy={onEvent:function(c,b){var a=Ext.getCmp(c);if(a){a.onFlashEvent(b)}else{arguments.callee.defer(10,this,[c,b])}}};Ext.chart.Chart=Ext.extend(Ext.FlashComponent,{refreshBuffer:100,chartStyle:{padding:10,animationEnabled:true,font:{name:"Tahoma",color:4473924,size:11},dataTip:{padding:5,border:{color:10075112,size:1},background:{color:14346230,alpha:0.9},font:{name:"Tahoma",color:1393291,size:10,bold:true}}},extraStyle:null,seriesStyles:null,disableCaching:Ext.isIE||Ext.isOpera,disableCacheParam:"_dc",initComponent:function(){Ext.chart.Chart.superclass.initComponent.call(this);if(!this.url){this.url=Ext.chart.Chart.CHART_URL}if(this.disableCaching){this.url=Ext.urlAppend(this.url,String.format("{0}={1}",this.disableCacheParam,new Date().getTime()))}this.addEvents("itemmouseover","itemmouseout","itemclick","itemdoubleclick","itemdragstart","itemdrag","itemdragend","beforerefresh","refresh");this.store=Ext.StoreMgr.lookup(this.store)},setStyle:function(a,b){this.swf.setStyle(a,Ext.encode(b))},setStyles:function(a){this.swf.setStyles(Ext.encode(a))},setSeriesStyles:function(b){this.seriesStyles=b;var a=[];Ext.each(b,function(c){a.push(Ext.encode(c))});this.swf.setSeriesStyles(a)},setCategoryNames:function(a){this.swf.setCategoryNames(a)},setLegendRenderer:function(c,b){var a=this;b=b||a;a.removeFnProxy(a.legendFnName);a.legendFnName=a.createFnProxy(function(d){return c.call(b,d)});a.swf.setLegendLabelFunction(a.legendFnName)},setTipRenderer:function(c,b){var a=this;b=b||a;a.removeFnProxy(a.tipFnName);a.tipFnName=a.createFnProxy(function(h,e,g){var d=a.store.getAt(e);return c.call(b,a,d,e,g)});a.swf.setDataTipFunction(a.tipFnName)},setSeries:function(a){this.series=a;this.refresh()},bindStore:function(a,b){if(!b&&this.store){if(a!==this.store&&this.store.autoDestroy){this.store.destroy()}else{this.store.un("datachanged",this.refresh,this);this.store.un("add",this.delayRefresh,this);this.store.un("remove",this.delayRefresh,this);this.store.un("update",this.delayRefresh,this);this.store.un("clear",this.refresh,this)}}if(a){a=Ext.StoreMgr.lookup(a);a.on({scope:this,datachanged:this.refresh,add:this.delayRefresh,remove:this.delayRefresh,update:this.delayRefresh,clear:this.refresh})}this.store=a;if(a&&!b){this.refresh()}},onSwfReady:function(b){Ext.chart.Chart.superclass.onSwfReady.call(this,b);var a;this.swf.setType(this.type);if(this.chartStyle){this.setStyles(Ext.apply({},this.extraStyle,this.chartStyle))}if(this.categoryNames){this.setCategoryNames(this.categoryNames)}if(this.tipRenderer){a=this.getFunctionRef(this.tipRenderer);this.setTipRenderer(a.fn,a.scope)}if(this.legendRenderer){a=this.getFunctionRef(this.legendRenderer);this.setLegendRenderer(a.fn,a.scope)}if(!b){this.bindStore(this.store,true)}this.refresh.defer(10,this)},delayRefresh:function(){if(!this.refreshTask){this.refreshTask=new Ext.util.DelayedTask(this.refresh,this)}this.refreshTask.delay(this.refreshBuffer)},refresh:function(){if(this.fireEvent("beforerefresh",this)!==false){var m=false;var k=[],c=this.store.data.items;for(var g=0,l=c.length;g<l;g++){k[g]=c[g].data}var e=[];var d=0;var n=null;var h=0;if(this.series){d=this.series.length;for(h=0;h<d;h++){n=this.series[h];var b={};for(var a in n){if(a=="style"&&n.style!==null){b.style=Ext.encode(n.style);m=true}else{b[a]=n[a]}}e.push(b)}}if(d>0){for(h=0;h<d;h++){n=e[h];if(!n.type){n.type=this.type}n.dataProvider=k}}else{e.push({type:this.type,dataProvider:k})}this.swf.setDataProvider(e);if(this.seriesStyles){this.setSeriesStyles(this.seriesStyles)}this.fireEvent("refresh",this)}},createFnProxy:function(a){var b="extFnProxy"+(++Ext.chart.Chart.PROXY_FN_ID);Ext.chart.Chart.proxyFunction[b]=a;return"Ext.chart.Chart.proxyFunction."+b},removeFnProxy:function(a){if(!Ext.isEmpty(a)){a=a.replace("Ext.chart.Chart.proxyFunction.","");delete Ext.chart.Chart.proxyFunction[a]}},getFunctionRef:function(a){if(Ext.isFunction(a)){return{fn:a,scope:this}}else{return{fn:a.fn,scope:a.scope||this}}},onDestroy:function(){if(this.refreshTask&&this.refreshTask.cancel){this.refreshTask.cancel()}Ext.chart.Chart.superclass.onDestroy.call(this);this.bindStore(null);this.removeFnProxy(this.tipFnName);this.removeFnProxy(this.legendFnName)}});Ext.reg("chart",Ext.chart.Chart);Ext.chart.Chart.PROXY_FN_ID=0;Ext.chart.Chart.proxyFunction={};Ext.chart.Chart.CHART_URL="http://yui.yahooapis.com/2.8.2/build/charts/assets/charts.swf";Ext.chart.PieChart=Ext.extend(Ext.chart.Chart,{type:"pie",onSwfReady:function(a){Ext.chart.PieChart.superclass.onSwfReady.call(this,a);this.setDataField(this.dataField);this.setCategoryField(this.categoryField)},setDataField:function(a){this.dataField=a;this.swf.setDataField(a)},setCategoryField:function(a){this.categoryField=a;this.swf.setCategoryField(a)}});Ext.reg("piechart",Ext.chart.PieChart);Ext.chart.CartesianChart=Ext.extend(Ext.chart.Chart,{onSwfReady:function(a){Ext.chart.CartesianChart.superclass.onSwfReady.call(this,a);this.labelFn=[];if(this.xField){this.setXField(this.xField)}if(this.yField){this.setYField(this.yField)}if(this.xAxis){this.setXAxis(this.xAxis)}if(this.xAxes){this.setXAxes(this.xAxes)}if(this.yAxis){this.setYAxis(this.yAxis)}if(this.yAxes){this.setYAxes(this.yAxes)}if(Ext.isDefined(this.constrainViewport)){this.swf.setConstrainViewport(this.constrainViewport)}},setXField:function(a){this.xField=a;this.swf.setHorizontalField(a)},setYField:function(a){this.yField=a;this.swf.setVerticalField(a)},setXAxis:function(a){this.xAxis=this.createAxis("xAxis",a);this.swf.setHorizontalAxis(this.xAxis)},setXAxes:function(c){var b;for(var a=0;a<c.length;a++){b=this.createAxis("xAxis"+a,c[a]);this.swf.setHorizontalAxis(b)}},setYAxis:function(a){this.yAxis=this.createAxis("yAxis",a);this.swf.setVerticalAxis(this.yAxis)},setYAxes:function(c){var b;for(var a=0;a<c.length;a++){b=this.createAxis("yAxis"+a,c[a]);this.swf.setVerticalAxis(b)}},createAxis:function(b,d){var e=Ext.apply({},d),c,a;if(this[b]){a=this[b].labelFunction;this.removeFnProxy(a);this.labelFn.remove(a)}if(e.labelRenderer){c=this.getFunctionRef(e.labelRenderer);e.labelFunction=this.createFnProxy(function(g){return c.fn.call(c.scope,g)});delete e.labelRenderer;this.labelFn.push(e.labelFunction)}if(b.indexOf("xAxis")>-1&&e.position=="left"){e.position="bottom"}return e},onDestroy:function(){Ext.chart.CartesianChart.superclass.onDestroy.call(this);Ext.each(this.labelFn,function(a){this.removeFnProxy(a)},this)}});Ext.reg("cartesianchart",Ext.chart.CartesianChart);Ext.chart.LineChart=Ext.extend(Ext.chart.CartesianChart,{type:"line"});Ext.reg("linechart",Ext.chart.LineChart);Ext.chart.ColumnChart=Ext.extend(Ext.chart.CartesianChart,{type:"column"});Ext.reg("columnchart",Ext.chart.ColumnChart);Ext.chart.StackedColumnChart=Ext.extend(Ext.chart.CartesianChart,{type:"stackcolumn"});Ext.reg("stackedcolumnchart",Ext.chart.StackedColumnChart);Ext.chart.BarChart=Ext.extend(Ext.chart.CartesianChart,{type:"bar"});Ext.reg("barchart",Ext.chart.BarChart);Ext.chart.StackedBarChart=Ext.extend(Ext.chart.CartesianChart,{type:"stackbar"});Ext.reg("stackedbarchart",Ext.chart.StackedBarChart);Ext.chart.Axis=function(a){Ext.apply(this,a)};Ext.chart.Axis.prototype={type:null,orientation:"horizontal",reverse:false,labelFunction:null,hideOverlappingLabels:true,labelSpacing:2};Ext.chart.NumericAxis=Ext.extend(Ext.chart.Axis,{type:"numeric",minimum:NaN,maximum:NaN,majorUnit:NaN,minorUnit:NaN,snapToUnits:true,alwaysShowZero:true,scale:"linear",roundMajorUnit:true,calculateByLabelSize:true,position:"left",adjustMaximumByMajorUnit:true,adjustMinimumByMajorUnit:true});Ext.chart.TimeAxis=Ext.extend(Ext.chart.Axis,{type:"time",minimum:null,maximum:null,majorUnit:NaN,majorTimeUnit:null,minorUnit:NaN,minorTimeUnit:null,snapToUnits:true,stackingEnabled:false,calculateByLabelSize:true});Ext.chart.CategoryAxis=Ext.extend(Ext.chart.Axis,{type:"category",categoryNames:null,calculateCategoryCount:false});Ext.chart.Series=function(a){Ext.apply(this,a)};Ext.chart.Series.prototype={type:null,displayName:null};Ext.chart.CartesianSeries=Ext.extend(Ext.chart.Series,{xField:null,yField:null,showInLegend:true,axis:"primary"});Ext.chart.ColumnSeries=Ext.extend(Ext.chart.CartesianSeries,{type:"column"});Ext.chart.LineSeries=Ext.extend(Ext.chart.CartesianSeries,{type:"line"});Ext.chart.BarSeries=Ext.extend(Ext.chart.CartesianSeries,{type:"bar"});Ext.chart.PieSeries=Ext.extend(Ext.chart.Series,{type:"pie",dataField:null,categoryField:null});Ext.menu.Menu=Ext.extend(Ext.Container,{minWidth:120,shadow:"sides",subMenuAlign:"tl-tr?",defaultAlign:"tl-bl?",allowOtherMenus:false,ignoreParentClicks:false,enableScrolling:true,maxHeight:null,scrollIncrement:24,showSeparator:true,defaultOffsets:[0,0],plain:false,floating:true,zIndex:15000,hidden:true,layout:"menu",hideMode:"offsets",scrollerHeight:8,autoLayout:true,defaultType:"menuitem",bufferResize:false,initComponent:function(){if(Ext.isArray(this.initialConfig)){Ext.apply(this,{items:this.initialConfig})}this.addEvents("click","mouseover","mouseout","itemclick");Ext.menu.MenuMgr.register(this);if(this.floating){Ext.EventManager.onWindowResize(this.hide,this)}else{if(this.initialConfig.hidden!==false){this.hidden=false}this.internalDefaults={hideOnClick:false}}Ext.menu.Menu.superclass.initComponent.call(this);if(this.autoLayout){var a=this.doLayout.createDelegate(this,[]);this.on({add:a,remove:a})}},getLayoutTarget:function(){return this.ul},onRender:function(b,a){if(!b){b=Ext.getBody()}var c={id:this.getId(),cls:"x-menu "+((this.floating)?"x-menu-floating x-layer ":"")+(this.cls||"")+(this.plain?" x-menu-plain":"")+(this.showSeparator?"":" x-menu-nosep"),style:this.style,cn:[{tag:"a",cls:"x-menu-focus",href:"#",onclick:"return false;",tabIndex:"-1"},{tag:"ul",cls:"x-menu-list"}]};if(this.floating){this.el=new Ext.Layer({shadow:this.shadow,dh:c,constrain:false,parentEl:b,zindex:this.zIndex})}else{this.el=b.createChild(c)}Ext.menu.Menu.superclass.onRender.call(this,b,a);if(!this.keyNav){this.keyNav=new Ext.menu.MenuNav(this)}this.focusEl=this.el.child("a.x-menu-focus");this.ul=this.el.child("ul.x-menu-list");this.mon(this.ul,{scope:this,click:this.onClick,mouseover:this.onMouseOver,mouseout:this.onMouseOut});if(this.enableScrolling){this.mon(this.el,{scope:this,delegate:".x-menu-scroller",click:this.onScroll,mouseover:this.deactivateActive})}},findTargetItem:function(b){var a=b.getTarget(".x-menu-list-item",this.ul,true);if(a&&a.menuItemId){return this.items.get(a.menuItemId)}},onClick:function(b){var a=this.findTargetItem(b);if(a){if(a.isFormField){this.setActiveItem(a)}else{if(a instanceof Ext.menu.BaseItem){if(a.menu&&this.ignoreParentClicks){a.expandMenu();b.preventDefault()}else{if(a.onClick){a.onClick(b);this.fireEvent("click",this,a,b)}}}}}},setActiveItem:function(a,b){if(a!=this.activeItem){this.deactivateActive();if((this.activeItem=a).isFormField){a.focus()}else{a.activate(b)}}else{if(b){a.expandMenu()}}},deactivateActive:function(){var b=this.activeItem;if(b){if(b.isFormField){if(b.collapse){b.collapse()}}else{b.deactivate()}delete this.activeItem}},tryActivate:function(g,e){var b=this.items;for(var c=g,a=b.length;c>=0&&c<a;c+=e){var d=b.get(c);if(d.isVisible()&&!d.disabled&&(d.canActivate||d.isFormField)){this.setActiveItem(d,false);return d}}return false},onMouseOver:function(b){var a=this.findTargetItem(b);if(a){if(a.canActivate&&!a.disabled){this.setActiveItem(a,true)}}this.over=true;this.fireEvent("mouseover",this,b,a)},onMouseOut:function(b){var a=this.findTargetItem(b);if(a){if(a==this.activeItem&&a.shouldDeactivate&&a.shouldDeactivate(b)){this.activeItem.deactivate();delete this.activeItem}}this.over=false;this.fireEvent("mouseout",this,b,a)},onScroll:function(d,b){if(d){d.stopEvent()}var a=this.ul.dom,c=Ext.fly(b).is(".x-menu-scroller-top");a.scrollTop+=this.scrollIncrement*(c?-1:1);if(c?a.scrollTop<=0:a.scrollTop+this.activeMax>=a.scrollHeight){this.onScrollerOut(null,b)}},onScrollerIn:function(d,b){var a=this.ul.dom,c=Ext.fly(b).is(".x-menu-scroller-top");if(c?a.scrollTop>0:a.scrollTop+this.activeMax<a.scrollHeight){Ext.fly(b).addClass(["x-menu-item-active","x-menu-scroller-active"])}},onScrollerOut:function(b,a){Ext.fly(a).removeClass(["x-menu-item-active","x-menu-scroller-active"])},show:function(b,c,a){if(this.floating){this.parentMenu=a;if(!this.el){this.render();this.doLayout(false,true)}this.showAt(this.el.getAlignToXY(b,c||this.defaultAlign,this.defaultOffsets),a)}else{Ext.menu.Menu.superclass.show.call(this)}},showAt:function(b,a){if(this.fireEvent("beforeshow",this)!==false){this.parentMenu=a;if(!this.el){this.render()}if(this.enableScrolling){this.el.setXY(b);b[1]=this.constrainScroll(b[1]);b=[this.el.adjustForConstraints(b)[0],b[1]]}else{b=this.el.adjustForConstraints(b)}this.el.setXY(b);this.el.show();Ext.menu.Menu.superclass.onShow.call(this);if(Ext.isIE9m){this.fireEvent("autosize",this);if(!Ext.isIE8){this.el.repaint()}}this.hidden=false;this.focus();this.fireEvent("show",this)}},constrainScroll:function(i){var b,d=this.ul.setHeight("auto").getHeight(),a=i,h,e,g,c;if(this.floating){e=Ext.fly(this.el.dom.parentNode);g=e.getScroll().top;c=e.getViewSize().height;h=i-g;b=this.maxHeight?this.maxHeight:c-h;if(d>c){b=c;a=i-h}else{if(b<d){a=i-(d-b);b=d}}}else{b=this.getHeight()}if(this.maxHeight){b=Math.min(this.maxHeight,b)}if(d>b&&b>0){this.activeMax=b-this.scrollerHeight*2-this.el.getFrameWidth("tb")-Ext.num(this.el.shadowOffset,0);this.ul.setHeight(this.activeMax);this.createScrollers();this.el.select(".x-menu-scroller").setDisplayed("")}else{this.ul.setHeight(d);this.el.select(".x-menu-scroller").setDisplayed("none")}this.ul.dom.scrollTop=0;return a},createScrollers:function(){if(!this.scroller){this.scroller={pos:0,top:this.el.insertFirst({tag:"div",cls:"x-menu-scroller x-menu-scroller-top",html:"&#160;"}),bottom:this.el.createChild({tag:"div",cls:"x-menu-scroller x-menu-scroller-bottom",html:"&#160;"})};this.scroller.top.hover(this.onScrollerIn,this.onScrollerOut,this);this.scroller.topRepeater=new Ext.util.ClickRepeater(this.scroller.top,{listeners:{click:this.onScroll.createDelegate(this,[null,this.scroller.top],false)}});this.scroller.bottom.hover(this.onScrollerIn,this.onScrollerOut,this);this.scroller.bottomRepeater=new Ext.util.ClickRepeater(this.scroller.bottom,{listeners:{click:this.onScroll.createDelegate(this,[null,this.scroller.bottom],false)}})}},onLayout:function(){if(this.isVisible()){if(this.enableScrolling){this.constrainScroll(this.el.getTop())}if(this.floating){this.el.sync()}}},focus:function(){if(!this.hidden){this.doFocus.defer(50,this)}},doFocus:function(){if(!this.hidden){this.focusEl.focus()}},hide:function(a){if(!this.isDestroyed){this.deepHide=a;Ext.menu.Menu.superclass.hide.call(this);delete this.deepHide}},onHide:function(){Ext.menu.Menu.superclass.onHide.call(this);this.deactivateActive();if(this.el&&this.floating){this.el.hide()}var a=this.parentMenu;if(this.deepHide===true&&a){if(a.floating){a.hide(true)}else{a.deactivateActive()}}},lookupComponent:function(a){if(Ext.isString(a)){a=(a=="separator"||a=="-")?new Ext.menu.Separator():new Ext.menu.TextItem(a);this.applyDefaults(a)}else{if(Ext.isObject(a)){a=this.getMenuItem(a)}else{if(a.tagName||a.el){a=new Ext.BoxComponent({el:a})}}}return a},applyDefaults:function(b){if(!Ext.isString(b)){b=Ext.menu.Menu.superclass.applyDefaults.call(this,b);var a=this.internalDefaults;if(a){if(b.events){Ext.applyIf(b.initialConfig,a);Ext.apply(b,a)}else{Ext.applyIf(b,a)}}}return b},getMenuItem:function(a){a.ownerCt=this;if(!a.isXType){if(!a.xtype&&Ext.isBoolean(a.checked)){return new Ext.menu.CheckItem(a)}return Ext.create(a,this.defaultType)}return a},addSeparator:function(){return this.add(new Ext.menu.Separator())},addElement:function(a){return this.add(new Ext.menu.BaseItem({el:a}))},addItem:function(a){return this.add(a)},addMenuItem:function(a){return this.add(this.getMenuItem(a))},addText:function(a){return this.add(new Ext.menu.TextItem(a))},onDestroy:function(){Ext.EventManager.removeResizeListener(this.hide,this);var a=this.parentMenu;if(a&&a.activeChild==this){delete a.activeChild}delete this.parentMenu;Ext.menu.Menu.superclass.onDestroy.call(this);Ext.menu.MenuMgr.unregister(this);if(this.keyNav){this.keyNav.disable()}var b=this.scroller;if(b){Ext.destroy(b.topRepeater,b.bottomRepeater,b.top,b.bottom)}Ext.destroy(this.el,this.focusEl,this.ul)}});Ext.reg("menu",Ext.menu.Menu);Ext.menu.MenuNav=Ext.extend(Ext.KeyNav,function(){function a(d,c){if(!c.tryActivate(c.items.indexOf(c.activeItem)-1,-1)){c.tryActivate(c.items.length-1,-1)}}function b(d,c){if(!c.tryActivate(c.items.indexOf(c.activeItem)+1,1)){c.tryActivate(0,1)}}return{constructor:function(c){Ext.menu.MenuNav.superclass.constructor.call(this,c.el);this.scope=this.menu=c},doRelay:function(g,d){var c=g.getKey();if(this.menu.activeItem&&this.menu.activeItem.isFormField&&c!=g.TAB){return false}if(!this.menu.activeItem&&g.isNavKeyPress()&&c!=g.SPACE&&c!=g.RETURN){this.menu.tryActivate(0,1);return false}return d.call(this.scope||this,g,this.menu)},tab:function(d,c){d.stopEvent();if(d.shiftKey){a(d,c)}else{b(d,c)}},up:a,down:b,right:function(d,c){if(c.activeItem){c.activeItem.expandMenu(true)}},left:function(d,c){c.hide();if(c.parentMenu&&c.parentMenu.activeItem){c.parentMenu.activeItem.activate()}},enter:function(d,c){if(c.activeItem){d.stopPropagation();c.activeItem.onClick(d);c.fireEvent("click",this,c.activeItem);return true}}}}());Ext.menu.MenuMgr=function(){var h,e,b,d={},a=false,l=new Date();function n(){h={};e=new Ext.util.MixedCollection();b=Ext.getDoc().addKeyListener(27,j);b.disable()}function j(){if(e&&e.length>0){var o=e.clone();o.each(function(p){p.hide()});return true}return false}function g(o){e.remove(o);if(e.length<1){b.disable();Ext.getDoc().un("mousedown",m);a=false}}function k(o){var p=e.last();l=new Date();e.add(o);if(!a){b.enable();Ext.getDoc().on("mousedown",m);a=true}if(o.parentMenu){o.getEl().setZIndex(parseInt(o.parentMenu.getEl().getStyle("z-index"),10)+3);o.parentMenu.activeChild=o}else{if(p&&!p.isDestroyed&&p.isVisible()){o.getEl().setZIndex(parseInt(p.getEl().getStyle("z-index"),10)+3)}}}function c(o){if(o.activeChild){o.activeChild.hide()}if(o.autoHideTimer){clearTimeout(o.autoHideTimer);delete o.autoHideTimer}}function i(o){var p=o.parentMenu;if(!p&&!o.allowOtherMenus){j()}else{if(p&&p.activeChild){p.activeChild.hide()}}}function m(o){if(l.getElapsed()>50&&e.length>0&&!o.getTarget(".x-menu")){j()}}return{hideAll:function(){return j()},register:function(o){if(!h){n()}h[o.id]=o;o.on({beforehide:c,hide:g,beforeshow:i,show:k})},get:function(o){if(typeof o=="string"){if(!h){return null}return h[o]}else{if(o.events){return o}else{if(typeof o.length=="number"){return new Ext.menu.Menu({items:o})}else{return Ext.create(o,"menu")}}}},unregister:function(o){delete h[o.id];o.un("beforehide",c);o.un("hide",g);o.un("beforeshow",i);o.un("show",k)},registerCheckable:function(o){var p=o.group;if(p){if(!d[p]){d[p]=[]}d[p].push(o)}},unregisterCheckable:function(o){var p=o.group;if(p){d[p].remove(o)}},onCheckChange:function(q,r){if(q.group&&r){var t=d[q.group],p=0,o=t.length,s;for(;p<o;p++){s=t[p];if(s!=q){s.setChecked(false)}}}},getCheckedItem:function(q){var r=d[q];if(r){for(var p=0,o=r.length;p<o;p++){if(r[p].checked){return r[p]}}}return null},setCheckedItem:function(q,s){var r=d[q];if(r){for(var p=0,o=r.length;p<o;p++){if(r[p].id==s){r[p].setChecked(true)}}}return null}}}();Ext.menu.BaseItem=Ext.extend(Ext.Component,{canActivate:false,activeClass:"x-menu-item-active",hideOnClick:true,clickHideDelay:1,ctype:"Ext.menu.BaseItem",actionMode:"container",initComponent:function(){Ext.menu.BaseItem.superclass.initComponent.call(this);this.addEvents("click","activate","deactivate");if(this.handler){this.on("click",this.handler,this.scope)}},onRender:function(b,a){Ext.menu.BaseItem.superclass.onRender.apply(this,arguments);if(this.ownerCt&&this.ownerCt instanceof Ext.menu.Menu){this.parentMenu=this.ownerCt}else{this.container.addClass("x-menu-list-item");this.mon(this.el,{scope:this,click:this.onClick,mouseenter:this.activate,mouseleave:this.deactivate})}},setHandler:function(b,a){if(this.handler){this.un("click",this.handler,this.scope)}this.on("click",this.handler=b,this.scope=a)},onClick:function(a){if(!this.disabled&&this.fireEvent("click",this,a)!==false&&(this.parentMenu&&this.parentMenu.fireEvent("itemclick",this,a)!==false)){this.handleClick(a)}else{a.stopEvent()}},activate:function(){if(this.disabled){return false}var a=this.container;a.addClass(this.activeClass);this.region=a.getRegion().adjust(2,2,-2,-2);this.fireEvent("activate",this);return true},deactivate:function(){this.container.removeClass(this.activeClass);this.fireEvent("deactivate",this)},shouldDeactivate:function(a){return !this.region||!this.region.contains(a.getPoint())},handleClick:function(b){var a=this.parentMenu;if(this.hideOnClick){if(a.floating){this.clickHideDelayTimer=a.hide.defer(this.clickHideDelay,a,[true])}else{a.deactivateActive()}}},beforeDestroy:function(){clearTimeout(this.clickHideDelayTimer);Ext.menu.BaseItem.superclass.beforeDestroy.call(this)},expandMenu:Ext.emptyFn,hideMenu:Ext.emptyFn});Ext.reg("menubaseitem",Ext.menu.BaseItem);Ext.menu.TextItem=Ext.extend(Ext.menu.BaseItem,{hideOnClick:false,itemCls:"x-menu-text",constructor:function(a){if(typeof a=="string"){a={text:a}}Ext.menu.TextItem.superclass.constructor.call(this,a)},onRender:function(){var a=document.createElement("span");a.className=this.itemCls;a.innerHTML=this.text;this.el=a;Ext.menu.TextItem.superclass.onRender.apply(this,arguments)}});Ext.reg("menutextitem",Ext.menu.TextItem);Ext.menu.Separator=Ext.extend(Ext.menu.BaseItem,{itemCls:"x-menu-sep",hideOnClick:false,activeClass:"",onRender:function(a){var b=document.createElement("span");b.className=this.itemCls;b.innerHTML="&#160;";this.el=b;a.addClass("x-menu-sep-li");Ext.menu.Separator.superclass.onRender.apply(this,arguments)}});Ext.reg("menuseparator",Ext.menu.Separator);Ext.menu.Item=Ext.extend(Ext.menu.BaseItem,{itemCls:"x-menu-item",canActivate:true,showDelay:200,altText:"",hideDelay:200,ctype:"Ext.menu.Item",initComponent:function(){Ext.menu.Item.superclass.initComponent.call(this);if(this.menu){if(Ext.isArray(this.menu)){this.menu={items:this.menu}}if(Ext.isObject(this.menu)){this.menu.ownerCt=this}this.menu=Ext.menu.MenuMgr.get(this.menu);this.menu.ownerCt=undefined}},onRender:function(d,b){if(!this.itemTpl){this.itemTpl=Ext.menu.Item.prototype.itemTpl=new Ext.XTemplate('<a id="{id}" class="{cls} x-unselectable" hidefocus="true" unselectable="on" href="{href}"','<tpl if="hrefTarget">',' target="{hrefTarget}"',"</tpl>",">",'<img alt="{altText}" src="{icon}" class="x-menu-item-icon {iconCls}"/>','<span class="x-menu-item-text">{text}</span>',"</a>")}var c=this.getTemplateArgs();this.el=b?this.itemTpl.insertBefore(b,c,true):this.itemTpl.append(d,c,true);this.iconEl=this.el.child("img.x-menu-item-icon");this.textEl=this.el.child(".x-menu-item-text");if(!this.href){this.mon(this.el,"click",Ext.emptyFn,null,{preventDefault:true})}Ext.menu.Item.superclass.onRender.call(this,d,b)},getTemplateArgs:function(){return{id:this.id,cls:this.itemCls+(this.menu?" x-menu-item-arrow":"")+(this.cls?" "+this.cls:""),href:this.href||"#",hrefTarget:this.hrefTarget,icon:this.icon||Ext.BLANK_IMAGE_URL,iconCls:this.iconCls||"",text:this.itemText||this.text||"&#160;",altText:this.altText||""}},setText:function(a){this.text=a||"&#160;";if(this.rendered){this.textEl.update(this.text);this.parentMenu.layout.doAutoSize()}},setIconClass:function(a){var b=this.iconCls;this.iconCls=a;if(this.rendered){this.iconEl.replaceClass(b,this.iconCls)}},beforeDestroy:function(){clearTimeout(this.showTimer);clearTimeout(this.hideTimer);if(this.menu){delete this.menu.ownerCt;this.menu.destroy()}Ext.menu.Item.superclass.beforeDestroy.call(this)},handleClick:function(a){if(!this.href){a.stopEvent()}Ext.menu.Item.superclass.handleClick.apply(this,arguments)},activate:function(a){if(Ext.menu.Item.superclass.activate.apply(this,arguments)){this.focus();if(a){this.expandMenu()}}return true},shouldDeactivate:function(a){if(Ext.menu.Item.superclass.shouldDeactivate.call(this,a)){if(this.menu&&this.menu.isVisible()){return !this.menu.getEl().getRegion().contains(a.getPoint())}return true}return false},deactivate:function(){Ext.menu.Item.superclass.deactivate.apply(this,arguments);this.hideMenu()},expandMenu:function(a){if(!this.disabled&&this.menu){clearTimeout(this.hideTimer);delete this.hideTimer;if(!this.menu.isVisible()&&!this.showTimer){this.showTimer=this.deferExpand.defer(this.showDelay,this,[a])}else{if(this.menu.isVisible()&&a){this.menu.tryActivate(0,1)}}}},deferExpand:function(a){delete this.showTimer;this.menu.show(this.container,this.parentMenu.subMenuAlign||"tl-tr?",this.parentMenu);if(a){this.menu.tryActivate(0,1)}},hideMenu:function(){clearTimeout(this.showTimer);delete this.showTimer;if(!this.hideTimer&&this.menu&&this.menu.isVisible()){this.hideTimer=this.deferHide.defer(this.hideDelay,this)}},deferHide:function(){delete this.hideTimer;if(this.menu.over){this.parentMenu.setActiveItem(this,false)}else{this.menu.hide()}}});Ext.reg("menuitem",Ext.menu.Item);Ext.menu.CheckItem=Ext.extend(Ext.menu.Item,{itemCls:"x-menu-item x-menu-check-item",groupClass:"x-menu-group-item",checked:false,ctype:"Ext.menu.CheckItem",initComponent:function(){Ext.menu.CheckItem.superclass.initComponent.call(this);this.addEvents("beforecheckchange","checkchange");if(this.checkHandler){this.on("checkchange",this.checkHandler,this.scope)}Ext.menu.MenuMgr.registerCheckable(this)},onRender:function(a){Ext.menu.CheckItem.superclass.onRender.apply(this,arguments);if(this.group){this.el.addClass(this.groupClass)}if(this.checked){this.checked=false;this.setChecked(true,true)}},destroy:function(){Ext.menu.MenuMgr.unregisterCheckable(this);Ext.menu.CheckItem.superclass.destroy.apply(this,arguments)},setChecked:function(b,a){var c=a===true;if(this.checked!=b&&(c||this.fireEvent("beforecheckchange",this,b)!==false)){Ext.menu.MenuMgr.onCheckChange(this,b);if(this.container){this.container[b?"addClass":"removeClass"]("x-menu-item-checked")}this.checked=b;if(!c){this.fireEvent("checkchange",this,b)}}},handleClick:function(a){if(!this.disabled&&!(this.checked&&this.group)){this.setChecked(!this.checked)}Ext.menu.CheckItem.superclass.handleClick.apply(this,arguments)}});Ext.reg("menucheckitem",Ext.menu.CheckItem);Ext.menu.DateMenu=Ext.extend(Ext.menu.Menu,{enableScrolling:false,hideOnClick:true,pickerId:null,cls:"x-date-menu",initComponent:function(){this.on("beforeshow",this.onBeforeShow,this);if(this.strict=(Ext.isIE7&&Ext.isStrict)){this.on("show",this.onShow,this,{single:true,delay:20})}Ext.apply(this,{plain:true,showSeparator:false,items:this.picker=new Ext.DatePicker(Ext.applyIf({internalRender:this.strict||!Ext.isIE9m,ctCls:"x-menu-date-item",id:this.pickerId},this.initialConfig))});this.picker.purgeListeners();Ext.menu.DateMenu.superclass.initComponent.call(this);this.relayEvents(this.picker,["select"]);this.on("show",this.picker.focus,this.picker);this.on("select",this.menuHide,this);if(this.handler){this.on("select",this.handler,this.scope||this)}},menuHide:function(){if(this.hideOnClick){this.hide(true)}},onBeforeShow:function(){if(this.picker){this.picker.hideMonthPicker(true)}},onShow:function(){var a=this.picker.getEl();a.setWidth(a.getWidth())}});Ext.reg("datemenu",Ext.menu.DateMenu);Ext.menu.ColorMenu=Ext.extend(Ext.menu.Menu,{enableScrolling:false,hideOnClick:true,cls:"x-color-menu",paletteId:null,initComponent:function(){Ext.apply(this,{plain:true,showSeparator:false,items:this.palette=new Ext.ColorPalette(Ext.applyIf({id:this.paletteId},this.initialConfig))});this.palette.purgeListeners();Ext.menu.ColorMenu.superclass.initComponent.call(this);this.relayEvents(this.palette,["select"]);this.on("select",this.menuHide,this);if(this.handler){this.on("select",this.handler,this.scope||this)}},menuHide:function(){if(this.hideOnClick){this.hide(true)}}});Ext.reg("colormenu",Ext.menu.ColorMenu);Ext.form.Field=Ext.extend(Ext.BoxComponent,{invalidClass:"x-form-invalid",invalidText:"The value in this field is invalid",focusClass:"x-form-focus",validationEvent:"keyup",validateOnBlur:true,validationDelay:250,defaultAutoCreate:{tag:"input",type:"text",size:"20",autocomplete:"off"},fieldClass:"x-form-field",msgTarget:"qtip",msgFx:"normal",readOnly:false,disabled:false,submitValue:true,isFormField:true,msgDisplay:"",hasFocus:false,initComponent:function(){Ext.form.Field.superclass.initComponent.call(this);this.addEvents("focus","blur","specialkey","change","invalid","valid")},getName:function(){return this.rendered&&this.el.dom.name?this.el.dom.name:this.name||this.id||""},onRender:function(c,a){if(!this.el){var b=this.getAutoCreate();if(!b.name){b.name=this.name||this.id}if(this.inputType){b.type=this.inputType}this.autoEl=b}Ext.form.Field.superclass.onRender.call(this,c,a);if(this.submitValue===false){this.el.dom.removeAttribute("name")}var d=this.el.dom.type;if(d){if(d=="password"){d="text"}this.el.addClass("x-form-"+d)}if(this.readOnly){this.setReadOnly(true)}if(this.tabIndex!==undefined){this.el.dom.setAttribute("tabIndex",this.tabIndex)}this.el.addClass([this.fieldClass,this.cls])},getItemCt:function(){return this.itemCt},initValue:function(){if(this.value!==undefined){this.setValue(this.value)}else{if(!Ext.isEmpty(this.el.dom.value)&&this.el.dom.value!=this.emptyText){this.setValue(this.el.dom.value)}}this.originalValue=this.getValue()},isDirty:function(){if(this.disabled||!this.rendered){return false}return String(this.getValue())!==String(this.originalValue)},setReadOnly:function(a){if(this.rendered){this.el.dom.readOnly=a}this.readOnly=a},afterRender:function(){Ext.form.Field.superclass.afterRender.call(this);this.initEvents();this.initValue()},fireKey:function(a){if(a.isSpecialKey()){this.fireEvent("specialkey",this,a)}},reset:function(){this.setValue(this.originalValue);this.clearInvalid()},initEvents:function(){this.mon(this.el,Ext.EventManager.getKeyEvent(),this.fireKey,this);this.mon(this.el,"focus",this.onFocus,this);this.mon(this.el,"blur",this.onBlur,this,this.inEditor?{buffer:10}:null)},preFocus:Ext.emptyFn,onFocus:function(){this.preFocus();if(this.focusClass){this.el.addClass(this.focusClass)}if(!this.hasFocus){this.hasFocus=true;this.startValue=this.getValue();this.fireEvent("focus",this)}},beforeBlur:Ext.emptyFn,onBlur:function(){this.beforeBlur();if(this.focusClass){this.el.removeClass(this.focusClass)}this.hasFocus=false;if(this.validationEvent!==false&&(this.validateOnBlur||this.validationEvent=="blur")){this.validate()}var a=this.getValue();if(String(a)!==String(this.startValue)){this.fireEvent("change",this,a,this.startValue)}this.fireEvent("blur",this);this.postBlur()},postBlur:Ext.emptyFn,isValid:function(a){if(this.disabled){return true}var c=this.preventMark;this.preventMark=a===true;var b=this.validateValue(this.processValue(this.getRawValue()),a);this.preventMark=c;return b},validate:function(){if(this.disabled||this.validateValue(this.processValue(this.getRawValue()))){this.clearInvalid();return true}return false},processValue:function(a){return a},validateValue:function(b){var a=this.getErrors(b)[0];if(a==undefined){return true}else{this.markInvalid(a);return false}},getErrors:function(){return[]},getActiveError:function(){return this.activeError||""},markInvalid:function(c){if(this.rendered&&!this.preventMark){c=c||this.invalidText;var a=this.getMessageHandler();if(a){a.mark(this,c)}else{if(this.msgTarget){this.el.addClass(this.invalidClass);var b=Ext.getDom(this.msgTarget);if(b){b.innerHTML=c;b.style.display=this.msgDisplay}}}}this.setActiveError(c)},clearInvalid:function(){if(this.rendered&&!this.preventMark){this.el.removeClass(this.invalidClass);var a=this.getMessageHandler();if(a){a.clear(this)}else{if(this.msgTarget){this.el.removeClass(this.invalidClass);var b=Ext.getDom(this.msgTarget);if(b){b.innerHTML="";b.style.display="none"}}}}this.unsetActiveError()},setActiveError:function(b,a){this.activeError=b;if(a!==true){this.fireEvent("invalid",this,b)}},unsetActiveError:function(a){delete this.activeError;if(a!==true){this.fireEvent("valid",this)}},getMessageHandler:function(){return Ext.form.MessageTargets[this.msgTarget]},getErrorCt:function(){return this.el.findParent(".x-form-element",5,true)||this.el.findParent(".x-form-field-wrap",5,true)},alignErrorEl:function(){this.errorEl.setWidth(this.getErrorCt().getWidth(true)-20)},alignErrorIcon:function(){this.errorIcon.alignTo(this.el,"tl-tr",[2,0])},getRawValue:function(){var a=this.rendered?this.el.getValue():Ext.value(this.value,"");if(a===this.emptyText){a=""}return a},getValue:function(){if(!this.rendered){return this.value}var a=this.el.getValue();if(a===this.emptyText||a===undefined){a=""}return a},setRawValue:function(a){return this.rendered?(this.el.dom.value=(Ext.isEmpty(a)?"":a)):""},setValue:function(a){this.value=a;if(this.rendered){this.el.dom.value=(Ext.isEmpty(a)?"":a);this.validate()}return this},append:function(a){this.setValue([this.getValue(),a].join(""))}});Ext.form.MessageTargets={qtip:{mark:function(a,b){a.el.addClass(a.invalidClass);a.el.dom.qtip=b;a.el.dom.qclass="x-form-invalid-tip";if(Ext.QuickTips){Ext.QuickTips.enable()}},clear:function(a){a.el.removeClass(a.invalidClass);a.el.dom.qtip=""}},title:{mark:function(a,b){a.el.addClass(a.invalidClass);a.el.dom.title=b},clear:function(a){a.el.dom.title=""}},under:{mark:function(b,c){b.el.addClass(b.invalidClass);if(!b.errorEl){var a=b.getErrorCt();if(!a){b.el.dom.title=c;return}b.errorEl=a.createChild({cls:"x-form-invalid-msg"});b.on("resize",b.alignErrorEl,b);b.on("destroy",function(){Ext.destroy(this.errorEl)},b)}b.alignErrorEl();b.errorEl.update(c);Ext.form.Field.msgFx[b.msgFx].show(b.errorEl,b)},clear:function(a){a.el.removeClass(a.invalidClass);if(a.errorEl){Ext.form.Field.msgFx[a.msgFx].hide(a.errorEl,a)}else{a.el.dom.title=""}}},side:{mark:function(b,c){b.el.addClass(b.invalidClass);if(!b.errorIcon){var a=b.getErrorCt();if(!a){b.el.dom.title=c;return}b.errorIcon=a.createChild({cls:"x-form-invalid-icon"});if(b.ownerCt){b.ownerCt.on("afterlayout",b.alignErrorIcon,b);b.ownerCt.on("expand",b.alignErrorIcon,b)}b.on("resize",b.alignErrorIcon,b);b.on("destroy",function(){Ext.destroy(this.errorIcon)},b)}b.alignErrorIcon();b.errorIcon.dom.qtip=c;b.errorIcon.dom.qclass="x-form-invalid-tip";b.errorIcon.show()},clear:function(a){a.el.removeClass(a.invalidClass);if(a.errorIcon){a.errorIcon.dom.qtip="";a.errorIcon.hide()}else{a.el.dom.title=""}}}};Ext.form.Field.msgFx={normal:{show:function(a,b){a.setDisplayed("block")},hide:function(a,b){a.setDisplayed(false).update("")}},slide:{show:function(a,b){a.slideIn("t",{stopFx:true})},hide:function(a,b){a.slideOut("t",{stopFx:true,useDisplay:true})}},slideRight:{show:function(a,b){a.fixDisplay();a.alignTo(b.el,"tl-tr");a.slideIn("l",{stopFx:true})},hide:function(a,b){a.slideOut("l",{stopFx:true,useDisplay:true})}}};Ext.reg("field",Ext.form.Field);Ext.form.TextField=Ext.extend(Ext.form.Field,{grow:false,growMin:30,growMax:800,vtype:null,maskRe:null,disableKeyFilter:false,allowBlank:true,minLength:0,maxLength:Number.MAX_VALUE,minLengthText:"The minimum length for this field is {0}",maxLengthText:"The maximum length for this field is {0}",selectOnFocus:false,blankText:"This field is required",validator:null,regex:null,regexText:"",emptyText:null,emptyClass:"x-form-empty-field",initComponent:function(){Ext.form.TextField.superclass.initComponent.call(this);this.addEvents("autosize","keydown","keyup","keypress")},initEvents:function(){Ext.form.TextField.superclass.initEvents.call(this);if(this.validationEvent=="keyup"){this.validationTask=new Ext.util.DelayedTask(this.validate,this);this.mon(this.el,"keyup",this.filterValidation,this)}else{if(this.validationEvent!==false&&this.validationEvent!="blur"){this.mon(this.el,this.validationEvent,this.validate,this,{buffer:this.validationDelay})}}if(this.selectOnFocus||this.emptyText){this.mon(this.el,"mousedown",this.onMouseDown,this);if(this.emptyText){this.applyEmptyText()}}if(this.maskRe||(this.vtype&&this.disableKeyFilter!==true&&(this.maskRe=Ext.form.VTypes[this.vtype+"Mask"]))){this.mon(this.el,"keypress",this.filterKeys,this)}if(this.grow){this.mon(this.el,"keyup",this.onKeyUpBuffered,this,{buffer:50});this.mon(this.el,"click",this.autoSize,this)}if(this.enableKeyEvents){this.mon(this.el,{scope:this,keyup:this.onKeyUp,keydown:this.onKeyDown,keypress:this.onKeyPress})}},onMouseDown:function(a){if(!this.hasFocus){this.mon(this.el,"mouseup",Ext.emptyFn,this,{single:true,preventDefault:true})}},processValue:function(a){if(this.stripCharsRe){var b=a.replace(this.stripCharsRe,"");if(b!==a){this.setRawValue(b);return b}}return a},filterValidation:function(a){if(!a.isNavKeyPress()){this.validationTask.delay(this.validationDelay)}},onDisable:function(){Ext.form.TextField.superclass.onDisable.call(this);if(Ext.isIE){this.el.dom.unselectable="on"}},onEnable:function(){Ext.form.TextField.superclass.onEnable.call(this);if(Ext.isIE){this.el.dom.unselectable=""}},onKeyUpBuffered:function(a){if(this.doAutoSize(a)){this.autoSize()}},doAutoSize:function(a){return !a.isNavKeyPress()},onKeyUp:function(a){this.fireEvent("keyup",this,a)},onKeyDown:function(a){this.fireEvent("keydown",this,a)},onKeyPress:function(a){this.fireEvent("keypress",this,a)},reset:function(){Ext.form.TextField.superclass.reset.call(this);this.applyEmptyText()},applyEmptyText:function(){if(this.rendered&&this.emptyText&&this.getRawValue().length<1&&!this.hasFocus){this.setRawValue(this.emptyText);this.el.addClass(this.emptyClass)}},preFocus:function(){var a=this.el,b;if(this.emptyText){if(a.dom.value==this.emptyText){this.setRawValue("");b=true}a.removeClass(this.emptyClass)}if(this.selectOnFocus||b){a.dom.select()}},postBlur:function(){this.applyEmptyText()},filterKeys:function(b){if(b.ctrlKey){return}var a=b.getKey();if(Ext.isGecko&&(b.isNavKeyPress()||a==b.BACKSPACE||(a==b.DELETE&&b.button==-1))){return}var c=String.fromCharCode(b.getCharCode());if(!Ext.isGecko&&b.isSpecialKey()&&!c){return}if(!this.maskRe.test(c)){b.stopEvent()}},setValue:function(a){if(this.emptyText&&this.el&&!Ext.isEmpty(a)){this.el.removeClass(this.emptyClass)}Ext.form.TextField.superclass.setValue.apply(this,arguments);this.applyEmptyText();this.autoSize();return this},getErrors:function(a){var d=Ext.form.TextField.superclass.getErrors.apply(this,arguments);a=Ext.isDefined(a)?a:this.processValue(this.getRawValue());if(Ext.isFunction(this.validator)){var c=this.validator(a);if(c!==true){d.push(c)}}if(a.length<1||a===this.emptyText){if(this.allowBlank){return d}else{d.push(this.blankText)}}if(!this.allowBlank&&(a.length<1||a===this.emptyText)){d.push(this.blankText)}if(a.length<this.minLength){d.push(String.format(this.minLengthText,this.minLength))}if(a.length>this.maxLength){d.push(String.format(this.maxLengthText,this.maxLength))}if(this.vtype){var b=Ext.form.VTypes;if(!b[this.vtype](a,this)){d.push(this.vtypeText||b[this.vtype+"Text"])}}if(this.regex&&!this.regex.test(a)){d.push(this.regexText)}return d},selectText:function(h,a){var c=this.getRawValue();var e=false;if(c.length>0){h=h===undefined?0:h;a=a===undefined?c.length:a;var g=this.el.dom;if(g.setSelectionRange){g.setSelectionRange(h,a)}else{if(g.createTextRange){var b=g.createTextRange();b.moveStart("character",h);b.moveEnd("character",a-c.length);b.select()}}e=Ext.isGecko||Ext.isOpera}else{e=true}if(e){this.focus()}},autoSize:function(){if(!this.grow||!this.rendered){return}if(!this.metrics){this.metrics=Ext.util.TextMetrics.createInstance(this.el)}var c=this.el;var b=c.dom.value;var e=document.createElement("div");e.appendChild(document.createTextNode(b));b=e.innerHTML;Ext.removeNode(e);e=null;b+="&#160;";var a=Math.min(this.growMax,Math.max(this.metrics.getWidth(b)+10,this.growMin));this.el.setWidth(a);this.fireEvent("autosize",this,a)},onDestroy:function(){if(this.validationTask){this.validationTask.cancel();this.validationTask=null}Ext.form.TextField.superclass.onDestroy.call(this)}});Ext.reg("textfield",Ext.form.TextField);Ext.form.TriggerField=Ext.extend(Ext.form.TextField,{defaultAutoCreate:{tag:"input",type:"text",size:"16",autocomplete:"off"},hideTrigger:false,editable:true,readOnly:false,wrapFocusClass:"x-trigger-wrap-focus",autoSize:Ext.emptyFn,monitorTab:true,deferHeight:true,mimicing:false,actionMode:"wrap",defaultTriggerWidth:17,onResize:function(a,c){Ext.form.TriggerField.superclass.onResize.call(this,a,c);var b=this.getTriggerWidth();if(Ext.isNumber(a)){this.el.setWidth(a-b)}this.wrap.setWidth(this.el.getWidth()+b)},getTriggerWidth:function(){var a=this.trigger.getWidth();if(!this.hideTrigger&&!this.readOnly&&a===0){a=this.defaultTriggerWidth}return a},alignErrorIcon:function(){if(this.wrap){this.errorIcon.alignTo(this.wrap,"tl-tr",[2,0])}},onRender:function(b,a){this.doc=Ext.isIE?Ext.getBody():Ext.getDoc();Ext.form.TriggerField.superclass.onRender.call(this,b,a);this.wrap=this.el.wrap({cls:"x-form-field-wrap x-form-field-trigger-wrap"});this.trigger=this.wrap.createChild(this.triggerConfig||{tag:"img",src:Ext.BLANK_IMAGE_URL,alt:"",cls:"x-form-trigger "+this.triggerClass});this.initTrigger();if(!this.width){this.wrap.setWidth(this.el.getWidth()+this.trigger.getWidth())}this.resizeEl=this.positionEl=this.wrap},getWidth:function(){return(this.el.getWidth()+this.trigger.getWidth())},updateEditState:function(){if(this.rendered){if(this.readOnly){this.el.dom.readOnly=true;this.el.addClass("x-trigger-noedit");this.mun(this.el,"click",this.onTriggerClick,this);this.trigger.setDisplayed(false)}else{if(!this.editable){this.el.dom.readOnly=true;this.el.addClass("x-trigger-noedit");this.mon(this.el,"click",this.onTriggerClick,this)}else{this.el.dom.readOnly=false;this.el.removeClass("x-trigger-noedit");this.mun(this.el,"click",this.onTriggerClick,this)}this.trigger.setDisplayed(!this.hideTrigger)}this.onResize(this.width||this.wrap.getWidth())}},setHideTrigger:function(a){if(a!=this.hideTrigger){this.hideTrigger=a;this.updateEditState()}},setEditable:function(a){if(a!=this.editable){this.editable=a;this.updateEditState()}},setReadOnly:function(a){if(a!=this.readOnly){this.readOnly=a;this.updateEditState()}},afterRender:function(){Ext.form.TriggerField.superclass.afterRender.call(this);this.updateEditState()},initTrigger:function(){this.mon(this.trigger,"click",this.onTriggerClick,this,{preventDefault:true});this.trigger.addClassOnOver("x-form-trigger-over");this.trigger.addClassOnClick("x-form-trigger-click")},onDestroy:function(){Ext.destroy(this.trigger,this.wrap);if(this.mimicing){this.doc.un("mousedown",this.mimicBlur,this)}delete this.doc;Ext.form.TriggerField.superclass.onDestroy.call(this)},onFocus:function(){Ext.form.TriggerField.superclass.onFocus.call(this);if(!this.mimicing){this.wrap.addClass(this.wrapFocusClass);this.mimicing=true;this.doc.on("mousedown",this.mimicBlur,this,{delay:10});if(this.monitorTab){this.on("specialkey",this.checkTab,this)}}},checkTab:function(a,b){if(b.getKey()==b.TAB){this.triggerBlur()}},onBlur:Ext.emptyFn,mimicBlur:function(a){if(!this.isDestroyed&&!this.wrap.contains(a.target)&&this.validateBlur(a)){this.triggerBlur()}},triggerBlur:function(){this.mimicing=false;this.doc.un("mousedown",this.mimicBlur,this);if(this.monitorTab&&this.el){this.un("specialkey",this.checkTab,this)}Ext.form.TriggerField.superclass.onBlur.call(this);if(this.wrap){this.wrap.removeClass(this.wrapFocusClass)}},beforeBlur:Ext.emptyFn,validateBlur:function(a){return true},onTriggerClick:Ext.emptyFn});Ext.form.TwinTriggerField=Ext.extend(Ext.form.TriggerField,{initComponent:function(){Ext.form.TwinTriggerField.superclass.initComponent.call(this);this.triggerConfig={tag:"span",cls:"x-form-twin-triggers",cn:[{tag:"img",src:Ext.BLANK_IMAGE_URL,alt:"",cls:"x-form-trigger "+this.trigger1Class},{tag:"img",src:Ext.BLANK_IMAGE_URL,alt:"",cls:"x-form-trigger "+this.trigger2Class}]}},getTrigger:function(a){return this.triggers[a]},afterRender:function(){Ext.form.TwinTriggerField.superclass.afterRender.call(this);var c=this.triggers,b=0,a=c.length;for(;b<a;++b){if(this["hideTrigger"+(b+1)]){c[b].hide()}}},initTrigger:function(){var a=this.trigger.select(".x-form-trigger",true),b=this;a.each(function(d,g,c){var e="Trigger"+(c+1);d.hide=function(){var h=b.wrap.getWidth();this.dom.style.display="none";b.el.setWidth(h-b.trigger.getWidth());b["hidden"+e]=true};d.show=function(){var h=b.wrap.getWidth();this.dom.style.display="";b.el.setWidth(h-b.trigger.getWidth());b["hidden"+e]=false};this.mon(d,"click",this["on"+e+"Click"],this,{preventDefault:true});d.addClassOnOver("x-form-trigger-over");d.addClassOnClick("x-form-trigger-click")},this);this.triggers=a.elements},getTriggerWidth:function(){var a=0;Ext.each(this.triggers,function(d,c){var e="Trigger"+(c+1),b=d.getWidth();if(b===0&&!this["hidden"+e]){a+=this.defaultTriggerWidth}else{a+=b}},this);return a},onDestroy:function(){Ext.destroy(this.triggers);Ext.form.TwinTriggerField.superclass.onDestroy.call(this)},onTrigger1Click:Ext.emptyFn,onTrigger2Click:Ext.emptyFn});Ext.reg("trigger",Ext.form.TriggerField);Ext.reg("twintrigger",Ext.form.TwinTriggerField);Ext.form.TextArea=Ext.extend(Ext.form.TextField,{growMin:60,growMax:1000,growAppend:"&#160;\n&#160;",enterIsSpecial:false,preventScrollbars:false,onRender:function(b,a){if(!this.el){this.defaultAutoCreate={tag:"textarea",style:"width:100px;height:60px;",autocomplete:"off"}}Ext.form.TextArea.superclass.onRender.call(this,b,a);if(this.grow){this.textSizeEl=Ext.DomHelper.append(document.body,{tag:"pre",cls:"x-form-grow-sizer"});if(this.preventScrollbars){this.el.setStyle("overflow","hidden")}this.el.setHeight(this.growMin)}},onDestroy:function(){Ext.removeNode(this.textSizeEl);Ext.form.TextArea.superclass.onDestroy.call(this)},fireKey:function(a){if(a.isSpecialKey()&&(this.enterIsSpecial||(a.getKey()!=a.ENTER||a.hasModifier()))){this.fireEvent("specialkey",this,a)}},doAutoSize:function(a){return !a.isNavKeyPress()||a.getKey()==a.ENTER},filterValidation:function(a){if(!a.isNavKeyPress()||(!this.enterIsSpecial&&a.keyCode==a.ENTER)){this.validationTask.delay(this.validationDelay)}},autoSize:function(){if(!this.grow||!this.textSizeEl){return}var c=this.el,a=Ext.util.Format.htmlEncode(c.dom.value),d=this.textSizeEl,b;Ext.fly(d).setWidth(this.el.getWidth());if(a.length<1){a="&#160;&#160;"}else{a+=this.growAppend;if(Ext.isIE){a=a.replace(/\n/g,"&#160;<br />")}}d.innerHTML=a;b=Math.min(this.growMax,Math.max(d.offsetHeight,this.growMin));if(b!=this.lastHeight){this.lastHeight=b;this.el.setHeight(b);this.fireEvent("autosize",this,b)}}});Ext.reg("textarea",Ext.form.TextArea);Ext.form.NumberField=Ext.extend(Ext.form.TextField,{fieldClass:"x-form-field x-form-num-field",allowDecimals:true,decimalSeparator:".",decimalPrecision:2,allowNegative:true,minValue:Number.NEGATIVE_INFINITY,maxValue:Number.MAX_VALUE,minText:"The minimum value for this field is {0}",maxText:"The maximum value for this field is {0}",nanText:"{0} is not a valid number",baseChars:"0123456789",autoStripChars:false,initEvents:function(){var a=this.baseChars+"";if(this.allowDecimals){a+=this.decimalSeparator}if(this.allowNegative){a+="-"}a=Ext.escapeRe(a);this.maskRe=new RegExp("["+a+"]");if(this.autoStripChars){this.stripCharsRe=new RegExp("[^"+a+"]","gi")}Ext.form.NumberField.superclass.initEvents.call(this)},getErrors:function(b){var c=Ext.form.NumberField.superclass.getErrors.apply(this,arguments);b=Ext.isDefined(b)?b:this.processValue(this.getRawValue());if(b.length<1){return c}b=String(b).replace(this.decimalSeparator,".");if(isNaN(b)){c.push(String.format(this.nanText,b))}var a=this.parseValue(b);if(a<this.minValue){c.push(String.format(this.minText,this.minValue))}if(a>this.maxValue){c.push(String.format(this.maxText,this.maxValue))}return c},getValue:function(){return this.fixPrecision(this.parseValue(Ext.form.NumberField.superclass.getValue.call(this)))},setValue:function(a){a=Ext.isNumber(a)?a:parseFloat(String(a).replace(this.decimalSeparator,"."));a=this.fixPrecision(a);a=isNaN(a)?"":String(a).replace(".",this.decimalSeparator);return Ext.form.NumberField.superclass.setValue.call(this,a)},setMinValue:function(a){this.minValue=Ext.num(a,Number.NEGATIVE_INFINITY)},setMaxValue:function(a){this.maxValue=Ext.num(a,Number.MAX_VALUE)},parseValue:function(a){a=parseFloat(String(a).replace(this.decimalSeparator,"."));return isNaN(a)?"":a},fixPrecision:function(b){var a=isNaN(b);if(!this.allowDecimals||this.decimalPrecision==-1||a||!b){return a?"":b}return parseFloat(parseFloat(b).toFixed(this.decimalPrecision))},beforeBlur:function(){var a=this.parseValue(this.getRawValue());if(!Ext.isEmpty(a)){this.setValue(a)}}});Ext.reg("numberfield",Ext.form.NumberField);Ext.form.DateField=Ext.extend(Ext.form.TriggerField,{format:"m/d/Y",altFormats:"m/d/Y|n/j/Y|n/j/y|m/j/y|n/d/y|m/j/Y|n/d/Y|m-d-y|m-d-Y|m/d|m-d|md|mdy|mdY|d|Y-m-d|n-j|n/j",disabledDaysText:"Disabled",disabledDatesText:"Disabled",minText:"The date in this field must be equal to or after {0}",maxText:"The date in this field must be equal to or before {0}",invalidText:"{0} is not a valid date - it must be in the format {1}",triggerClass:"x-form-date-trigger",showToday:true,startDay:0,defaultAutoCreate:{tag:"input",type:"text",size:"10",autocomplete:"off"},initTime:"12",initTimeFormat:"H",safeParse:function(b,c){if(Date.formatContainsHourInfo(c)){return Date.parseDate(b,c)}else{var a=Date.parseDate(b+" "+this.initTime,c+" "+this.initTimeFormat);if(a){return a.clearTime()}}},initComponent:function(){Ext.form.DateField.superclass.initComponent.call(this);this.addEvents("select");if(Ext.isString(this.minValue)){this.minValue=this.parseDate(this.minValue)}if(Ext.isString(this.maxValue)){this.maxValue=this.parseDate(this.maxValue)}this.disabledDatesRE=null;this.initDisabledDays()},initEvents:function(){Ext.form.DateField.superclass.initEvents.call(this);this.keyNav=new Ext.KeyNav(this.el,{down:function(a){this.onTriggerClick()},scope:this,forceKeyDown:true})},initDisabledDays:function(){if(this.disabledDates){var b=this.disabledDates,a=b.length-1,c="(?:";Ext.each(b,function(g,e){c+=Ext.isDate(g)?"^"+Ext.escapeRe(g.dateFormat(this.format))+"$":b[e];if(e!=a){c+="|"}},this);this.disabledDatesRE=new RegExp(c+")")}},setDisabledDates:function(a){this.disabledDates=a;this.initDisabledDays();if(this.menu){this.menu.picker.setDisabledDates(this.disabledDatesRE)}},setDisabledDays:function(a){this.disabledDays=a;if(this.menu){this.menu.picker.setDisabledDays(a)}},setMinValue:function(a){this.minValue=(Ext.isString(a)?this.parseDate(a):a);if(this.menu){this.menu.picker.setMinDate(this.minValue)}},setMaxValue:function(a){this.maxValue=(Ext.isString(a)?this.parseDate(a):a);if(this.menu){this.menu.picker.setMaxDate(this.maxValue)}},getErrors:function(e){var h=Ext.form.DateField.superclass.getErrors.apply(this,arguments);e=this.formatDate(e||this.processValue(this.getRawValue()));if(e.length<1){return h}var c=e;e=this.parseDate(e);if(!e){h.push(String.format(this.invalidText,c,this.format));return h}var g=e.getTime();if(this.minValue&&g<this.minValue.clearTime().getTime()){h.push(String.format(this.minText,this.formatDate(this.minValue)))}if(this.maxValue&&g>this.maxValue.clearTime().getTime()){h.push(String.format(this.maxText,this.formatDate(this.maxValue)))}if(this.disabledDays){var a=e.getDay();for(var b=0;b<this.disabledDays.length;b++){if(a===this.disabledDays[b]){h.push(this.disabledDaysText);break}}}var d=this.formatDate(e);if(this.disabledDatesRE&&this.disabledDatesRE.test(d)){h.push(String.format(this.disabledDatesText,d))}return h},validateBlur:function(){return !this.menu||!this.menu.isVisible()},getValue:function(){return this.parseDate(Ext.form.DateField.superclass.getValue.call(this))||""},setValue:function(a){return Ext.form.DateField.superclass.setValue.call(this,this.formatDate(this.parseDate(a)))},parseDate:function(g){if(!g||Ext.isDate(g)){return g}var b=this.safeParse(g,this.format),c=this.altFormats,e=this.altFormatsArray;if(!b&&c){e=e||c.split("|");for(var d=0,a=e.length;d<a&&!b;d++){b=this.safeParse(g,e[d])}}return b},onDestroy:function(){Ext.destroy(this.menu,this.keyNav);Ext.form.DateField.superclass.onDestroy.call(this)},formatDate:function(a){return Ext.isDate(a)?a.dateFormat(this.format):a},onTriggerClick:function(){if(this.disabled){return}if(this.menu==null){this.menu=new Ext.menu.DateMenu({hideOnClick:false,focusOnSelect:false})}this.onFocus();Ext.apply(this.menu.picker,{minDate:this.minValue,maxDate:this.maxValue,disabledDatesRE:this.disabledDatesRE,disabledDatesText:this.disabledDatesText,disabledDays:this.disabledDays,disabledDaysText:this.disabledDaysText,format:this.format,showToday:this.showToday,startDay:this.startDay,minText:String.format(this.minText,this.formatDate(this.minValue)),maxText:String.format(this.maxText,this.formatDate(this.maxValue))});this.menu.picker.setValue(this.getValue()||new Date());this.menu.show(this.el,"tl-bl?");this.menuEvents("on")},menuEvents:function(a){this.menu[a]("select",this.onSelect,this);this.menu[a]("hide",this.onMenuHide,this);this.menu[a]("show",this.onFocus,this)},onSelect:function(a,b){this.setValue(b);this.fireEvent("select",this,b);this.menu.hide()},onMenuHide:function(){this.focus(false,60);this.menuEvents("un")},beforeBlur:function(){var a=this.parseDate(this.getRawValue());if(a){this.setValue(a)}}});Ext.reg("datefield",Ext.form.DateField);Ext.form.DisplayField=Ext.extend(Ext.form.Field,{validationEvent:false,validateOnBlur:false,defaultAutoCreate:{tag:"div"},fieldClass:"x-form-display-field",htmlEncode:false,initEvents:Ext.emptyFn,isValid:function(){return true},validate:function(){return true},getRawValue:function(){var a=this.rendered?this.el.dom.innerHTML:Ext.value(this.value,"");if(a===this.emptyText){a=""}if(this.htmlEncode){a=Ext.util.Format.htmlDecode(a)}return a},getValue:function(){return this.getRawValue()},getName:function(){return this.name},setRawValue:function(a){if(this.htmlEncode){a=Ext.util.Format.htmlEncode(a)}return this.rendered?(this.el.dom.innerHTML=(Ext.isEmpty(a)?"":a)):(this.value=a)},setValue:function(a){this.setRawValue(a);return this}});Ext.reg("displayfield",Ext.form.DisplayField);Ext.form.ComboBox=Ext.extend(Ext.form.TriggerField,{defaultAutoCreate:{tag:"input",type:"text",size:"24",autocomplete:"off"},listClass:"",selectedClass:"x-combo-selected",listEmptyText:"",triggerClass:"x-form-arrow-trigger",shadow:"sides",listAlign:"tl-bl?",maxHeight:300,minHeight:90,triggerAction:"query",minChars:4,autoSelect:true,typeAhead:false,queryDelay:500,pageSize:0,selectOnFocus:false,queryParam:"query",loadingText:"Loading...",resizable:false,handleHeight:8,allQuery:"",mode:"remote",minListWidth:70,forceSelection:false,typeAheadDelay:250,lazyInit:true,clearFilterOnReset:true,submitValue:undefined,initComponent:function(){Ext.form.ComboBox.superclass.initComponent.call(this);this.addEvents("expand","collapse","beforeselect","select","beforequery");if(this.transform){var c=Ext.getDom(this.transform);if(!this.hiddenName){this.hiddenName=c.name}if(!this.store){this.mode="local";var j=[],e=c.options;for(var b=0,a=e.length;b<a;b++){var h=e[b],g=(h.hasAttribute?h.hasAttribute("value"):h.getAttributeNode("value").specified)?h.value:h.text;if(h.selected&&Ext.isEmpty(this.value,true)){this.value=g}j.push([g,h.text])}this.store=new Ext.data.ArrayStore({idIndex:0,fields:["value","text"],data:j,autoDestroy:true});this.valueField="value";this.displayField="text"}c.name=Ext.id();if(!this.lazyRender){this.target=true;this.el=Ext.DomHelper.insertBefore(c,this.autoCreate||this.defaultAutoCreate);this.render(this.el.parentNode,c)}Ext.removeNode(c)}else{if(this.store){this.store=Ext.StoreMgr.lookup(this.store);if(this.store.autoCreated){this.displayField=this.valueField="field1";if(!this.store.expandData){this.displayField="field2"}this.mode="local"}}}this.selectedIndex=-1;if(this.mode=="local"){if(!Ext.isDefined(this.initialConfig.queryDelay)){this.queryDelay=10}if(!Ext.isDefined(this.initialConfig.minChars)){this.minChars=0}}},onRender:function(b,a){if(this.hiddenName&&!Ext.isDefined(this.submitValue)){this.submitValue=false}Ext.form.ComboBox.superclass.onRender.call(this,b,a);if(this.hiddenName){this.hiddenField=this.el.insertSibling({tag:"input",type:"hidden",name:this.hiddenName,id:(this.hiddenId||Ext.id())},"before",true)}if(Ext.isGecko){this.el.dom.setAttribute("autocomplete","off")}if(!this.lazyInit){this.initList()}else{this.on("focus",this.initList,this,{single:true})}},initValue:function(){Ext.form.ComboBox.superclass.initValue.call(this);if(this.hiddenField){this.hiddenField.value=Ext.value(Ext.isDefined(this.hiddenValue)?this.hiddenValue:this.value,"")}},getParentZIndex:function(){var a;if(this.ownerCt){this.findParentBy(function(b){a=parseInt(b.getPositionEl().getStyle("z-index"),10);return !!a})}return a},getZIndex:function(b){b=b||Ext.getDom(this.getListParent()||Ext.getBody());var a=parseInt(Ext.fly(b).getStyle("z-index"),10);if(!a){a=this.getParentZIndex()}return(a||12000)+5},initList:function(){if(!this.list){var a="x-combo-list",c=Ext.getDom(this.getListParent()||Ext.getBody());this.list=new Ext.Layer({parentEl:c,shadow:this.shadow,cls:[a,this.listClass].join(" "),constrain:false,zindex:this.getZIndex(c)});var b=this.listWidth||Math.max(this.wrap.getWidth(),this.minListWidth);this.list.setSize(b,0);this.list.swallowEvent("mousewheel");this.assetHeight=0;if(this.syncFont!==false){this.list.setStyle("font-size",this.el.getStyle("font-size"))}if(this.title){this.header=this.list.createChild({cls:a+"-hd",html:this.title});this.assetHeight+=this.header.getHeight()}this.innerList=this.list.createChild({cls:a+"-inner"});this.mon(this.innerList,"mouseover",this.onViewOver,this);this.mon(this.innerList,"mousemove",this.onViewMove,this);this.innerList.setWidth(b-this.list.getFrameWidth("lr"));if(this.pageSize){this.footer=this.list.createChild({cls:a+"-ft"});this.pageTb=new Ext.PagingToolbar({store:this.store,pageSize:this.pageSize,renderTo:this.footer});this.assetHeight+=this.footer.getHeight()}if(!this.tpl){this.tpl='<tpl for="."><div class="'+a+'-item">{'+this.displayField+"}</div></tpl>"}this.view=new Ext.DataView({applyTo:this.innerList,tpl:this.tpl,singleSelect:true,selectedClass:this.selectedClass,itemSelector:this.itemSelector||"."+a+"-item",emptyText:this.listEmptyText,deferEmptyText:false});this.mon(this.view,{containerclick:this.onViewClick,click:this.onViewClick,scope:this});this.bindStore(this.store,true);if(this.resizable){this.resizer=new Ext.Resizable(this.list,{pinned:true,handles:"se"});this.mon(this.resizer,"resize",function(g,d,e){this.maxHeight=e-this.handleHeight-this.list.getFrameWidth("tb")-this.assetHeight;this.listWidth=d;this.innerList.setWidth(d-this.list.getFrameWidth("lr"));this.restrictHeight()},this);this[this.pageSize?"footer":"innerList"].setStyle("margin-bottom",this.handleHeight+"px")}}},getListParent:function(){return document.body},getStore:function(){return this.store},bindStore:function(a,b){if(this.store&&!b){if(this.store!==a&&this.store.autoDestroy){this.store.destroy()}else{this.store.un("beforeload",this.onBeforeLoad,this);this.store.un("load",this.onLoad,this);this.store.un("exception",this.collapse,this)}if(!a){this.store=null;if(this.view){this.view.bindStore(null)}if(this.pageTb){this.pageTb.bindStore(null)}}}if(a){if(!b){this.lastQuery=null;if(this.pageTb){this.pageTb.bindStore(a)}}this.store=Ext.StoreMgr.lookup(a);this.store.on({scope:this,beforeload:this.onBeforeLoad,load:this.onLoad,exception:this.collapse});if(this.view){this.view.bindStore(a)}}},reset:function(){if(this.clearFilterOnReset&&this.mode=="local"){this.store.clearFilter()}Ext.form.ComboBox.superclass.reset.call(this)},initEvents:function(){Ext.form.ComboBox.superclass.initEvents.call(this);this.keyNav=new Ext.KeyNav(this.el,{up:function(a){this.inKeyMode=true;this.selectPrev()},down:function(a){if(!this.isExpanded()){this.onTriggerClick()}else{this.inKeyMode=true;this.selectNext()}},enter:function(a){this.onViewClick()},esc:function(a){this.collapse()},tab:function(a){if(this.forceSelection===true){this.collapse()}else{this.onViewClick(false)}return true},scope:this,doRelay:function(c,b,a){if(a=="down"||this.scope.isExpanded()){var d=Ext.KeyNav.prototype.doRelay.apply(this,arguments);if((((Ext.isIE9&&Ext.isStrict)||Ext.isIE10p)||!Ext.isIE)&&Ext.EventManager.useKeydown){this.scope.fireKey(c)}return d}return true},forceKeyDown:true,defaultEventAction:"stopEvent"});this.queryDelay=Math.max(this.queryDelay||10,this.mode=="local"?10:250);this.dqTask=new Ext.util.DelayedTask(this.initQuery,this);if(this.typeAhead){this.taTask=new Ext.util.DelayedTask(this.onTypeAhead,this)}if(!this.enableKeyEvents){this.mon(this.el,"keyup",this.onKeyUp,this)}},onDestroy:function(){if(this.dqTask){this.dqTask.cancel();this.dqTask=null}this.bindStore(null);Ext.destroy(this.resizer,this.view,this.pageTb,this.list);Ext.destroyMembers(this,"hiddenField");Ext.form.ComboBox.superclass.onDestroy.call(this)},fireKey:function(a){if(!this.isExpanded()){Ext.form.ComboBox.superclass.fireKey.call(this,a)}},onResize:function(a,b){Ext.form.ComboBox.superclass.onResize.apply(this,arguments);if(!isNaN(a)&&this.isVisible()&&this.list){this.doResize(a)}else{this.bufferSize=a}},doResize:function(a){if(!Ext.isDefined(this.listWidth)){var b=Math.max(a,this.minListWidth);this.list.setWidth(b);this.innerList.setWidth(b-this.list.getFrameWidth("lr"))}},onEnable:function(){Ext.form.ComboBox.superclass.onEnable.apply(this,arguments);if(this.hiddenField){this.hiddenField.disabled=false}},onDisable:function(){Ext.form.ComboBox.superclass.onDisable.apply(this,arguments);if(this.hiddenField){this.hiddenField.disabled=true}},onBeforeLoad:function(){if(!this.hasFocus){return}this.innerList.update(this.loadingText?'<div class="loading-indicator">'+this.loadingText+"</div>":"");this.restrictHeight();this.selectedIndex=-1},onLoad:function(){if(!this.hasFocus){return}if(this.store.getCount()>0||this.listEmptyText){this.expand();this.restrictHeight();if(this.lastQuery==this.allQuery){if(this.editable){this.el.dom.select()}if(this.autoSelect!==false&&!this.selectByValue(this.value,true)){this.select(0,true)}}else{if(this.autoSelect!==false){this.selectNext()}if(this.typeAhead&&this.lastKey!=Ext.EventObject.BACKSPACE&&this.lastKey!=Ext.EventObject.DELETE){this.taTask.delay(this.typeAheadDelay)}}}else{this.collapse()}},onTypeAhead:function(){if(this.store.getCount()>0){var b=this.store.getAt(0);var c=b.data[this.displayField];var a=c.length;var d=this.getRawValue().length;if(d!=a){this.setRawValue(c);this.selectText(d,c.length)}}},assertValue:function(){var b=this.getRawValue(),a;if(this.valueField&&Ext.isDefined(this.value)){a=this.findRecord(this.valueField,this.value)}if(!a||a.get(this.displayField)!=b){a=this.findRecord(this.displayField,b)}if(!a&&this.forceSelection){if(b.length>0&&b!=this.emptyText){this.el.dom.value=Ext.value(this.lastSelectionText,"");this.applyEmptyText()}else{this.clearValue()}}else{if(a&&this.valueField){if(this.value==b){return}b=a.get(this.valueField||this.displayField)}this.setValue(b)}},onSelect:function(a,b){if(this.fireEvent("beforeselect",this,a,b)!==false){this.setValue(a.data[this.valueField||this.displayField]);this.collapse();this.fireEvent("select",this,a,b)}},getName:function(){var a=this.hiddenField;return a&&a.name?a.name:this.hiddenName||Ext.form.ComboBox.superclass.getName.call(this)},getValue:function(){if(this.valueField){return Ext.isDefined(this.value)?this.value:""}else{return Ext.form.ComboBox.superclass.getValue.call(this)}},clearValue:function(){if(this.hiddenField){this.hiddenField.value=""}this.setRawValue("");this.lastSelectionText="";this.applyEmptyText();this.value=""},setValue:function(a){var c=a;if(this.valueField){var b=this.findRecord(this.valueField,a);if(b){c=b.data[this.displayField]}else{if(Ext.isDefined(this.valueNotFoundText)){c=this.valueNotFoundText}}}this.lastSelectionText=c;if(this.hiddenField){this.hiddenField.value=Ext.value(a,"")}Ext.form.ComboBox.superclass.setValue.call(this,c);this.value=a;return this},findRecord:function(c,b){var a;if(this.store.getCount()>0){this.store.each(function(d){if(d.data[c]==b){a=d;return false}})}return a},onViewMove:function(b,a){this.inKeyMode=false},onViewOver:function(d,b){if(this.inKeyMode){return}var c=this.view.findItemFromChild(b);if(c){var a=this.view.indexOf(c);this.select(a,false)}},onViewClick:function(b){var a=this.view.getSelectedIndexes()[0],c=this.store,d=c.getAt(a);if(d){this.onSelect(d,a)}else{this.collapse()}if(b!==false){this.el.focus()}},restrictHeight:function(){this.innerList.dom.style.height="";var b=this.innerList.dom,e=this.list.getFrameWidth("tb")+(this.resizable?this.handleHeight:0)+this.assetHeight,c=Math.max(b.clientHeight,b.offsetHeight,b.scrollHeight),a=this.getPosition()[1]-Ext.getBody().getScroll().top,g=Ext.lib.Dom.getViewHeight()-a-this.getSize().height,d=Math.max(a,g,this.minHeight||0)-this.list.shadowOffset-e-5;c=Math.min(c,d,this.maxHeight);this.innerList.setHeight(c);this.list.beginUpdate();this.list.setHeight(c+e);this.list.alignTo.apply(this.list,[this.el].concat(this.listAlign));this.list.endUpdate()},isExpanded:function(){return this.list&&this.list.isVisible()},selectByValue:function(a,c){if(!Ext.isEmpty(a,true)){var b=this.findRecord(this.valueField||this.displayField,a);if(b){this.select(this.store.indexOf(b),c);return true}}return false},select:function(a,c){this.selectedIndex=a;this.view.select(a);if(c!==false){var b=this.view.getNode(a);if(b){this.innerList.scrollChildIntoView(b,false)}}},selectNext:function(){var a=this.store.getCount();if(a>0){if(this.selectedIndex==-1){this.select(0)}else{if(this.selectedIndex<a-1){this.select(this.selectedIndex+1)}}}},selectPrev:function(){var a=this.store.getCount();if(a>0){if(this.selectedIndex==-1){this.select(0)}else{if(this.selectedIndex!==0){this.select(this.selectedIndex-1)}}}},onKeyUp:function(b){var a=b.getKey();if(this.editable!==false&&this.readOnly!==true&&(a==b.BACKSPACE||!b.isSpecialKey())){this.lastKey=a;this.dqTask.delay(this.queryDelay)}Ext.form.ComboBox.superclass.onKeyUp.call(this,b)},validateBlur:function(){return !this.list||!this.list.isVisible()},initQuery:function(){this.doQuery(this.getRawValue())},beforeBlur:function(){this.assertValue()},postBlur:function(){Ext.form.ComboBox.superclass.postBlur.call(this);this.collapse();this.inKeyMode=false},doQuery:function(c,b){c=Ext.isEmpty(c)?"":c;var a={query:c,forceAll:b,combo:this,cancel:false};if(this.fireEvent("beforequery",a)===false||a.cancel){return false}c=a.query;b=a.forceAll;if(b===true||(c.length>=this.minChars)){if(this.lastQuery!==c){this.lastQuery=c;if(this.mode=="local"){this.selectedIndex=-1;if(b){this.store.clearFilter()}else{this.store.filter(this.displayField,c)}this.onLoad()}else{this.store.baseParams[this.queryParam]=c;this.store.load({params:this.getParams(c)});this.expand()}}else{this.selectedIndex=-1;this.onLoad()}}},getParams:function(a){var b={},c=this.store.paramNames;if(this.pageSize){b[c.start]=0;b[c.limit]=this.pageSize}return b},collapse:function(){if(!this.isExpanded()){return}this.list.hide();Ext.getDoc().un("mousewheel",this.collapseIf,this);Ext.getDoc().un("mousedown",this.collapseIf,this);this.fireEvent("collapse",this)},collapseIf:function(a){if(!this.isDestroyed&&!a.within(this.wrap)&&!a.within(this.list)){this.collapse()}},expand:function(){if(this.isExpanded()||!this.hasFocus){return}if(this.title||this.pageSize){this.assetHeight=0;if(this.title){this.assetHeight+=this.header.getHeight()}if(this.pageSize){this.assetHeight+=this.footer.getHeight()}}if(this.bufferSize){this.doResize(this.bufferSize);delete this.bufferSize}this.list.alignTo.apply(this.list,[this.el].concat(this.listAlign));this.list.setZIndex(this.getZIndex());this.list.show();if(Ext.isGecko2){this.innerList.setOverflow("auto")}this.mon(Ext.getDoc(),{scope:this,mousewheel:this.collapseIf,mousedown:this.collapseIf});this.fireEvent("expand",this)},onTriggerClick:function(){if(this.readOnly||this.disabled){return}if(this.isExpanded()){this.collapse();this.el.focus()}else{this.onFocus({});if(this.triggerAction=="all"){this.doQuery(this.allQuery,true)}else{this.doQuery(this.getRawValue())}this.el.focus()}}});Ext.reg("combo",Ext.form.ComboBox);Ext.form.Checkbox=Ext.extend(Ext.form.Field,{focusClass:undefined,fieldClass:"x-form-field",checked:false,boxLabel:"&#160;",defaultAutoCreate:{tag:"input",type:"checkbox",autocomplete:"off"},actionMode:"wrap",initComponent:function(){Ext.form.Checkbox.superclass.initComponent.call(this);this.addEvents("check")},onResize:function(){Ext.form.Checkbox.superclass.onResize.apply(this,arguments);if(!this.boxLabel&&!this.fieldLabel){this.el.alignTo(this.wrap,"c-c")}},initEvents:function(){Ext.form.Checkbox.superclass.initEvents.call(this);this.mon(this.el,{scope:this,click:this.onClick,change:this.onClick})},markInvalid:Ext.emptyFn,clearInvalid:Ext.emptyFn,onRender:function(b,a){Ext.form.Checkbox.superclass.onRender.call(this,b,a);if(this.inputValue!==undefined){this.el.dom.value=this.inputValue}this.wrap=this.el.wrap({cls:"x-form-check-wrap"});if(this.boxLabel){this.wrap.createChild({tag:"label",htmlFor:this.el.id,cls:"x-form-cb-label",html:this.boxLabel})}if(this.checked){this.setValue(true)}else{this.checked=this.el.dom.checked}if(Ext.isIEQuirks){this.wrap.repaint()}this.resizeEl=this.positionEl=this.wrap},onDestroy:function(){Ext.destroy(this.wrap);Ext.form.Checkbox.superclass.onDestroy.call(this)},initValue:function(){this.originalValue=this.getValue()},getValue:function(){if(this.rendered){return this.el.dom.checked}return this.checked},onClick:function(){if(this.el.dom.checked!=this.checked){this.setValue(this.el.dom.checked)}},setValue:function(a){var c=this.checked,b=this.inputValue;if(a===false){this.checked=false}else{this.checked=(a===true||a==="true"||a=="1"||(b?a==b:String(a).toLowerCase()=="on"))}if(this.rendered){this.el.dom.checked=this.checked;this.el.dom.defaultChecked=this.checked}if(c!=this.checked){this.fireEvent("check",this,this.checked);if(this.handler){this.handler.call(this.scope||this,this,this.checked)}}return this}});Ext.reg("checkbox",Ext.form.Checkbox);Ext.form.CheckboxGroup=Ext.extend(Ext.form.Field,{columns:"auto",vertical:false,allowBlank:true,blankText:"You must select at least one item in this group",defaultType:"checkbox",groupCls:"x-form-check-group",initComponent:function(){this.addEvents("change");this.on("change",this.validate,this);Ext.form.CheckboxGroup.superclass.initComponent.call(this)},onRender:function(j,g){if(!this.el){var p={autoEl:{id:this.id},cls:this.groupCls,layout:"column",renderTo:j,bufferResize:false};var a={xtype:"container",defaultType:this.defaultType,layout:"form",defaults:{hideLabel:true,anchor:"100%"}};if(this.items[0].items){Ext.apply(p,{layoutConfig:{columns:this.items.length},defaults:this.defaults,items:this.items});for(var e=0,m=this.items.length;e<m;e++){Ext.applyIf(this.items[e],a)}}else{var d,n=[];if(typeof this.columns=="string"){this.columns=this.items.length}if(!Ext.isArray(this.columns)){var k=[];for(var e=0;e<this.columns;e++){k.push((100/this.columns)*0.01)}this.columns=k}d=this.columns.length;for(var e=0;e<d;e++){var b=Ext.apply({items:[]},a);b[this.columns[e]<=1?"columnWidth":"width"]=this.columns[e];if(this.defaults){b.defaults=Ext.apply(b.defaults||{},this.defaults)}n.push(b)}if(this.vertical){var r=Math.ceil(this.items.length/d),o=0;for(var e=0,m=this.items.length;e<m;e++){if(e>0&&e%r==0){o++}if(this.items[e].fieldLabel){this.items[e].hideLabel=false}n[o].items.push(this.items[e])}}else{for(var e=0,m=this.items.length;e<m;e++){var q=e%d;if(this.items[e].fieldLabel){this.items[e].hideLabel=false}n[q].items.push(this.items[e])}}Ext.apply(p,{layoutConfig:{columns:d},items:n})}this.panel=new Ext.Container(p);this.panel.ownerCt=this;this.el=this.panel.getEl();if(this.forId&&this.itemCls){var c=this.el.up(this.itemCls).child("label",true);if(c){c.setAttribute("htmlFor",this.forId)}}var h=this.panel.findBy(function(i){return i.isFormField},this);this.items=new Ext.util.MixedCollection();this.items.addAll(h)}Ext.form.CheckboxGroup.superclass.onRender.call(this,j,g)},initValue:function(){if(this.value){this.setValue.apply(this,this.buffered?this.value:[this.value]);delete this.buffered;delete this.value}},afterRender:function(){Ext.form.CheckboxGroup.superclass.afterRender.call(this);this.eachItem(function(a){a.on("check",this.fireChecked,this);a.inGroup=true})},doLayout:function(){if(this.rendered){this.panel.forceLayout=this.ownerCt.forceLayout;this.panel.doLayout()}},fireChecked:function(){var a=[];this.eachItem(function(b){if(b.checked){a.push(b)}});this.fireEvent("change",this,a)},getErrors:function(){var b=Ext.form.CheckboxGroup.superclass.getErrors.apply(this,arguments);if(!this.allowBlank){var a=true;this.eachItem(function(c){if(c.checked){return(a=false)}});if(a){b.push(this.blankText)}}return b},isDirty:function(){if(this.disabled||!this.rendered){return false}var a=false;this.eachItem(function(b){if(b.isDirty()){a=true;return false}});return a},setReadOnly:function(a){if(this.rendered){this.eachItem(function(b){b.setReadOnly(a)})}this.readOnly=a},onDisable:function(){this.eachItem(function(a){a.disable()})},onEnable:function(){this.eachItem(function(a){a.enable()})},onResize:function(a,b){this.panel.setSize(a,b);this.panel.doLayout()},reset:function(){if(this.originalValue){this.eachItem(function(a){if(a.setValue){a.setValue(false);a.originalValue=a.getValue()}});this.resetOriginal=true;this.setValue(this.originalValue);delete this.resetOriginal}else{this.eachItem(function(a){if(a.reset){a.reset()}})}(function(){this.clearInvalid()}).defer(50,this)},setValue:function(){if(this.rendered){this.onSetValue.apply(this,arguments)}else{this.buffered=true;this.value=arguments}return this},onSetValue:function(d,c){if(arguments.length==1){if(Ext.isArray(d)){Ext.each(d,function(h,e){if(Ext.isObject(h)&&h.setValue){h.setValue(true);if(this.resetOriginal===true){h.originalValue=h.getValue()}}else{var g=this.items.itemAt(e);if(g){g.setValue(h)}}},this)}else{if(Ext.isObject(d)){for(var a in d){var b=this.getBox(a);if(b){b.setValue(d[a])}}}else{this.setValueForItem(d)}}}else{var b=this.getBox(d);if(b){b.setValue(c)}}},beforeDestroy:function(){Ext.destroy(this.panel);if(!this.rendered){Ext.destroy(this.items)}Ext.form.CheckboxGroup.superclass.beforeDestroy.call(this)},setValueForItem:function(a){a=String(a).split(",");this.eachItem(function(b){if(a.indexOf(b.inputValue)>-1){b.setValue(true)}})},getBox:function(b){var a=null;this.eachItem(function(c){if(b==c||c.dataIndex==b||c.id==b||c.getName()==b){a=c;return false}});return a},getValue:function(){var a=[];this.eachItem(function(b){if(b.checked){a.push(b)}});return a},eachItem:function(b,a){if(this.items&&this.items.each){this.items.each(b,a||this)}},getRawValue:Ext.emptyFn,setRawValue:Ext.emptyFn});Ext.reg("checkboxgroup",Ext.form.CheckboxGroup);Ext.form.CompositeField=Ext.extend(Ext.form.Field,{defaultMargins:"0 5 0 0",skipLastItemMargin:true,isComposite:true,combineErrors:true,labelConnector:", ",initComponent:function(){var g=[],b=this.items,e;for(var d=0,c=b.length;d<c;d++){e=b[d];if(!Ext.isEmpty(e.ref)){e.ref="../"+e.ref}g.push(e.fieldLabel);Ext.applyIf(e,this.defaults);if(!(d==c-1&&this.skipLastItemMargin)){Ext.applyIf(e,{margins:this.defaultMargins})}}this.fieldLabel=this.fieldLabel||this.buildLabel(g);this.fieldErrors=new Ext.util.MixedCollection(true,function(h){return h.field});this.fieldErrors.on({scope:this,add:this.updateInvalidMark,remove:this.updateInvalidMark,replace:this.updateInvalidMark});Ext.form.CompositeField.superclass.initComponent.apply(this,arguments);this.innerCt=new Ext.Container({layout:"hbox",items:this.items,cls:"x-form-composite",defaultMargins:"0 3 0 0",ownerCt:this});delete this.innerCt.ownerCt;var a=this.innerCt.findBy(function(h){return h.isFormField},this);this.items=new Ext.util.MixedCollection();this.items.addAll(a)},onRender:function(c,a){if(!this.el){var d=this.innerCt;d.render(c);this.innerCt.ownerCt=this;this.el=d.getEl();if(this.combineErrors){this.eachItem(function(e){Ext.apply(e,{markInvalid:this.onFieldMarkInvalid.createDelegate(this,[e],0),clearInvalid:this.onFieldClearInvalid.createDelegate(this,[e],0)})})}var b=this.el.parent().parent().child("label",true);if(b){b.setAttribute("for",this.items.items[0].id)}}Ext.form.CompositeField.superclass.onRender.apply(this,arguments)},onFieldMarkInvalid:function(d,c){var b=d.getName(),a={field:b,errorName:d.fieldLabel||b,error:c};this.fieldErrors.replace(b,a);if(!d.preventMark){d.el.addClass(d.invalidClass)}},onFieldClearInvalid:function(a){this.fieldErrors.removeKey(a.getName());a.el.removeClass(a.invalidClass)},updateInvalidMark:function(){var a=Ext.isIE6&&Ext.isStrict;if(this.fieldErrors.length==0){this.clearInvalid();if(a){this.clearInvalid.defer(50,this)}}else{var b=this.buildCombinedErrorMessage(this.fieldErrors.items);this.sortErrors();this.markInvalid(b);if(a){this.markInvalid(b)}}},validateValue:function(c,a){var b=true;this.eachItem(function(d){if(!d.isValid(a)){b=false}});return b},buildCombinedErrorMessage:function(e){var d=[],b;for(var c=0,a=e.length;c<a;c++){b=e[c];d.push(String.format("{0}: {1}",b.errorName,b.error))}return d.join("<br />")},sortErrors:function(){var a=this.items;this.fieldErrors.sort("ASC",function(g,d){var c=function(b){return function(i){return i.getName()==b}};var h=a.findIndexBy(c(g.field)),e=a.findIndexBy(c(d.field));return h<e?-1:1})},reset:function(){this.eachItem(function(a){a.reset()});(function(){this.clearInvalid()}).defer(50,this)},clearInvalidChildren:function(){this.eachItem(function(a){a.clearInvalid()})},buildLabel:function(a){return Ext.clean(a).join(this.labelConnector)},isDirty:function(){if(this.disabled||!this.rendered){return false}var a=false;this.eachItem(function(b){if(b.isDirty()){a=true;return false}});return a},eachItem:function(b,a){if(this.items&&this.items.each){this.items.each(b,a||this)}},onResize:function(e,c,a,d){var b=this.innerCt;if(this.rendered&&b.rendered){b.setSize(e,c)}Ext.form.CompositeField.superclass.onResize.apply(this,arguments)},doLayout:function(c,b){if(this.rendered){var a=this.innerCt;a.forceLayout=this.ownerCt.forceLayout;a.doLayout(c,b)}},beforeDestroy:function(){Ext.destroy(this.innerCt);Ext.form.CompositeField.superclass.beforeDestroy.call(this)},setReadOnly:function(a){if(a==undefined){a=true}a=!!a;if(this.rendered){this.eachItem(function(b){b.setReadOnly(a)})}this.readOnly=a},onShow:function(){Ext.form.CompositeField.superclass.onShow.call(this);this.doLayout()},onDisable:function(){this.eachItem(function(a){a.disable()})},onEnable:function(){this.eachItem(function(a){a.enable()})}});Ext.reg("compositefield",Ext.form.CompositeField);Ext.form.Radio=Ext.extend(Ext.form.Checkbox,{inputType:"radio",markInvalid:Ext.emptyFn,clearInvalid:Ext.emptyFn,getGroupValue:function(){var a=this.el.up("form")||Ext.getBody();var b=a.child('input[name="'+this.el.dom.name+'"]:checked',true);return b?b.value:null},setValue:function(b){var a,d,c;if(typeof b=="boolean"){Ext.form.Radio.superclass.setValue.call(this,b)}else{if(this.rendered){a=this.getCheckEl();c=a.child('input[name="'+this.el.dom.name+'"][value="'+b+'"]',true);if(c){Ext.getCmp(c.id).setValue(true)}}}if(this.rendered&&this.checked){a=a||this.getCheckEl();d=this.getCheckEl().select('input[name="'+this.el.dom.name+'"]');d.each(function(e){if(e.dom.id!=this.id){Ext.getCmp(e.dom.id).setValue(false)}},this)}return this},getCheckEl:function(){if(this.inGroup){return this.el.up(".x-form-radio-group")}return this.el.up("form")||Ext.getBody()}});Ext.reg("radio",Ext.form.Radio);Ext.form.RadioGroup=Ext.extend(Ext.form.CheckboxGroup,{allowBlank:true,blankText:"You must select one item in this group",defaultType:"radio",groupCls:"x-form-radio-group",getValue:function(){var a=null;this.eachItem(function(b){if(b.checked){a=b;return false}});return a},onSetValue:function(c,b){if(arguments.length>1){var a=this.getBox(c);if(a){a.setValue(b);if(a.checked){this.eachItem(function(d){if(d!==a){d.setValue(false)}})}}}else{this.setValueForItem(c)}},setValueForItem:function(a){a=String(a).split(",")[0];this.eachItem(function(b){b.setValue(a==b.inputValue)})},fireChecked:function(){if(!this.checkTask){this.checkTask=new Ext.util.DelayedTask(this.bufferChecked,this)}this.checkTask.delay(10)},bufferChecked:function(){var a=null;this.eachItem(function(b){if(b.checked){a=b;return false}});this.fireEvent("change",this,a)},onDestroy:function(){if(this.checkTask){this.checkTask.cancel();this.checkTask=null}Ext.form.RadioGroup.superclass.onDestroy.call(this)}});Ext.reg("radiogroup",Ext.form.RadioGroup);Ext.form.Hidden=Ext.extend(Ext.form.Field,{inputType:"hidden",shouldLayout:false,onRender:function(){Ext.form.Hidden.superclass.onRender.apply(this,arguments)},initEvents:function(){this.originalValue=this.getValue()},setSize:Ext.emptyFn,setWidth:Ext.emptyFn,setHeight:Ext.emptyFn,setPosition:Ext.emptyFn,setPagePosition:Ext.emptyFn,markInvalid:Ext.emptyFn,clearInvalid:Ext.emptyFn});Ext.reg("hidden",Ext.form.Hidden);Ext.form.BasicForm=Ext.extend(Ext.util.Observable,{constructor:function(b,a){Ext.apply(this,a);if(Ext.isString(this.paramOrder)){this.paramOrder=this.paramOrder.split(/[\s,|]/)}this.items=new Ext.util.MixedCollection(false,function(c){return c.getItemId()});this.addEvents("beforeaction","actionfailed","actioncomplete");if(b){this.initEl(b)}Ext.form.BasicForm.superclass.constructor.call(this)},timeout:30,paramOrder:undefined,paramsAsHash:false,waitTitle:"Please Wait...",activeAction:null,trackResetOnLoad:false,initEl:function(a){this.el=Ext.get(a);this.id=this.el.id||Ext.id();if(!this.standardSubmit){this.el.on("submit",this.onSubmit,this)}this.el.addClass("x-form")},getEl:function(){return this.el},onSubmit:function(a){a.stopEvent()},destroy:function(a){if(a!==true){this.items.each(function(b){Ext.destroy(b)});Ext.destroy(this.el)}this.items.clear();this.purgeListeners()},isValid:function(){var a=true;this.items.each(function(b){if(!b.validate()){a=false}});return a},isDirty:function(){var a=false;this.items.each(function(b){if(b.isDirty()){a=true;return false}});return a},doAction:function(b,a){if(Ext.isString(b)){b=new Ext.form.Action.ACTION_TYPES[b](this,a)}if(this.fireEvent("beforeaction",this,b)!==false){this.beforeAction(b);b.run.defer(100,b)}return this},submit:function(b){b=b||{};if(this.standardSubmit){var a=b.clientValidation===false||this.isValid();if(a){var c=this.el.dom;if(this.url&&Ext.isEmpty(c.action)){c.action=this.url}c.submit()}return a}var d=String.format("{0}submit",this.api?"direct":"");this.doAction(d,b);return this},load:function(a){var b=String.format("{0}load",this.api?"direct":"");this.doAction(b,a);return this},updateRecord:function(b){b.beginEdit();var a=b.fields,d,c;a.each(function(e){d=this.findField(e.name);if(d){c=d.getValue();if(Ext.type(c)!==false&&c.getGroupValue){c=c.getGroupValue()}else{if(d.eachItem){c=[];d.eachItem(function(g){c.push(g.getValue())})}}b.set(e.name,c)}},this);b.endEdit();return this},loadRecord:function(a){this.setValues(a.data);return this},beforeAction:function(a){this.items.each(function(c){if(c.isFormField&&c.syncValue){c.syncValue()}});var b=a.options;if(b.waitMsg){if(this.waitMsgTarget===true){this.el.mask(b.waitMsg,"x-mask-loading")}else{if(this.waitMsgTarget){this.waitMsgTarget=Ext.get(this.waitMsgTarget);this.waitMsgTarget.mask(b.waitMsg,"x-mask-loading")}else{Ext.MessageBox.wait(b.waitMsg,b.waitTitle||this.waitTitle)}}}},afterAction:function(a,c){this.activeAction=null;var b=a.options;if(b.waitMsg){if(this.waitMsgTarget===true){this.el.unmask()}else{if(this.waitMsgTarget){this.waitMsgTarget.unmask()}else{Ext.MessageBox.updateProgress(1);Ext.MessageBox.hide()}}}if(c){if(b.reset){this.reset()}Ext.callback(b.success,b.scope,[this,a]);this.fireEvent("actioncomplete",this,a)}else{Ext.callback(b.failure,b.scope,[this,a]);this.fireEvent("actionfailed",this,a)}},findField:function(c){var b=this.items.get(c);if(!Ext.isObject(b)){var a=function(d){if(d.isFormField){if(d.dataIndex==c||d.id==c||d.getName()==c){b=d;return false}else{if(d.isComposite){return d.items.each(a)}else{if(d instanceof Ext.form.CheckboxGroup&&d.rendered){return d.eachItem(a)}}}}};this.items.each(a)}return b||null},markInvalid:function(h){if(Ext.isArray(h)){for(var c=0,a=h.length;c<a;c++){var b=h[c];var d=this.findField(b.id);if(d){d.markInvalid(b.msg)}}}else{var e,g;for(g in h){if(!Ext.isFunction(h[g])&&(e=this.findField(g))){e.markInvalid(h[g])}}}return this},setValues:function(c){if(Ext.isArray(c)){for(var d=0,a=c.length;d<a;d++){var b=c[d];var e=this.findField(b.id);if(e){e.setValue(b.value);if(this.trackResetOnLoad){e.originalValue=e.getValue()}}}}else{var g,h;for(h in c){if(!Ext.isFunction(c[h])&&(g=this.findField(h))){g.setValue(c[h]);if(this.trackResetOnLoad){g.originalValue=g.getValue()}}}}return this},getValues:function(b){var a=Ext.lib.Ajax.serializeForm(this.el.dom);if(b===true){return a}return Ext.urlDecode(a)},getFieldValues:function(a){var d={},e,b,c;this.items.each(function(g){if(!g.disabled&&(a!==true||g.isDirty())){e=g.getName();b=d[e];c=g.getValue();if(Ext.isDefined(b)){if(Ext.isArray(b)){d[e].push(c)}else{d[e]=[b,c]}}else{d[e]=c}}});return d},clearInvalid:function(){this.items.each(function(a){a.clearInvalid()});return this},reset:function(){this.items.each(function(a){a.reset()});return this},add:function(){this.items.addAll(Array.prototype.slice.call(arguments,0));return this},remove:function(a){this.items.remove(a);return this},cleanDestroyed:function(){this.items.filterBy(function(a){return !!a.isDestroyed}).each(this.remove,this)},render:function(){this.items.each(function(a){if(a.isFormField&&!a.rendered&&document.getElementById(a.id)){a.applyToMarkup(a.id)}});return this},applyToFields:function(a){this.items.each(function(b){Ext.apply(b,a)});return this},applyIfToFields:function(a){this.items.each(function(b){Ext.applyIf(b,a)});return this},callFieldMethod:function(b,a){a=a||[];this.items.each(function(c){if(Ext.isFunction(c[b])){c[b].apply(c,a)}});return this}});Ext.BasicForm=Ext.form.BasicForm;Ext.FormPanel=Ext.extend(Ext.Panel,{minButtonWidth:75,labelAlign:"left",monitorValid:false,monitorPoll:200,layout:"form",initComponent:function(){this.form=this.createForm();Ext.FormPanel.superclass.initComponent.call(this);this.bodyCfg={tag:"form",cls:this.baseCls+"-body",method:this.method||"POST",id:this.formId||Ext.id()};if(this.fileUpload){this.bodyCfg.enctype="multipart/form-data"}this.initItems();this.addEvents("clientvalidation");this.relayEvents(this.form,["beforeaction","actionfailed","actioncomplete"])},createForm:function(){var a=Ext.applyIf({listeners:{}},this.initialConfig);return new Ext.form.BasicForm(null,a)},initFields:function(){var c=this.form;var a=this;var b=function(d){if(a.isField(d)){c.add(d)}else{if(d.findBy&&d!=a){a.applySettings(d);if(d.items&&d.items.each){d.items.each(b,this)}}}};this.items.each(b,this)},applySettings:function(b){var a=b.ownerCt;Ext.applyIf(b,{labelAlign:a.labelAlign,labelWidth:a.labelWidth,itemCls:a.itemCls})},getLayoutTarget:function(){return this.form.el},getForm:function(){return this.form},onRender:function(b,a){this.initFields();Ext.FormPanel.superclass.onRender.call(this,b,a);this.form.initEl(this.body)},beforeDestroy:function(){this.stopMonitoring();this.form.destroy(true);Ext.FormPanel.superclass.beforeDestroy.call(this)},isField:function(a){return !!a.setValue&&!!a.getValue&&!!a.markInvalid&&!!a.clearInvalid},initEvents:function(){Ext.FormPanel.superclass.initEvents.call(this);this.on({scope:this,add:this.onAddEvent,remove:this.onRemoveEvent});if(this.monitorValid){this.startMonitoring()}},onAdd:function(a){Ext.FormPanel.superclass.onAdd.call(this,a);this.processAdd(a)},onAddEvent:function(a,b){if(a!==this){this.processAdd(b)}},processAdd:function(a){if(this.isField(a)){this.form.add(a)}else{if(a.findBy){this.applySettings(a);this.form.add.apply(this.form,a.findBy(this.isField))}}},onRemove:function(a){Ext.FormPanel.superclass.onRemove.call(this,a);this.processRemove(a)},onRemoveEvent:function(a,b){if(a!==this){this.processRemove(b)}},processRemove:function(a){if(!this.destroying){if(this.isField(a)){this.form.remove(a)}else{if(a.findBy){Ext.each(a.findBy(this.isField),this.form.remove,this.form);this.form.cleanDestroyed()}}}},startMonitoring:function(){if(!this.validTask){this.validTask=new Ext.util.TaskRunner();this.validTask.start({run:this.bindHandler,interval:this.monitorPoll||200,scope:this})}},stopMonitoring:function(){if(this.validTask){this.validTask.stopAll();this.validTask=null}},load:function(){this.form.load.apply(this.form,arguments)},onDisable:function(){Ext.FormPanel.superclass.onDisable.call(this);if(this.form){this.form.items.each(function(){this.disable()})}},onEnable:function(){Ext.FormPanel.superclass.onEnable.call(this);if(this.form){this.form.items.each(function(){this.enable()})}},bindHandler:function(){var e=true;this.form.items.each(function(g){if(!g.isValid(true)){e=false;return false}});if(this.fbar){var b=this.fbar.items.items;for(var d=0,a=b.length;d<a;d++){var c=b[d];if(c.formBind===true&&c.disabled===e){c.setDisabled(!e)}}}this.fireEvent("clientvalidation",this,e)}});Ext.reg("form",Ext.FormPanel);Ext.form.FormPanel=Ext.FormPanel;Ext.form.FieldSet=Ext.extend(Ext.Panel,{baseCls:"x-fieldset",layout:"form",animCollapse:false,onRender:function(b,a){if(!this.el){this.el=document.createElement("fieldset");this.el.id=this.id;if(this.title||this.header||this.checkboxToggle){this.el.appendChild(document.createElement("legend")).className=this.baseCls+"-header"}}Ext.form.FieldSet.superclass.onRender.call(this,b,a);if(this.checkboxToggle){var c=typeof this.checkboxToggle=="object"?this.checkboxToggle:{tag:"input",type:"checkbox",name:this.checkboxName||this.id+"-checkbox"};this.checkbox=this.header.insertFirst(c);this.checkbox.dom.checked=!this.collapsed;this.mon(this.checkbox,"click",this.onCheckClick,this)}},onCollapse:function(a,b){if(this.checkbox){this.checkbox.dom.checked=false}Ext.form.FieldSet.superclass.onCollapse.call(this,a,b)},onExpand:function(a,b){if(this.checkbox){this.checkbox.dom.checked=true}Ext.form.FieldSet.superclass.onExpand.call(this,a,b)},onCheckClick:function(){this[this.checkbox.dom.checked?"expand":"collapse"]()}});Ext.reg("fieldset",Ext.form.FieldSet);Ext.form.HtmlEditor=Ext.extend(Ext.form.Field,{enableFormat:true,enableFontSize:true,enableColors:true,enableAlignments:true,enableLists:true,enableSourceEdit:true,enableLinks:true,enableFont:true,createLinkText:"Please enter the URL for the link:",defaultLinkValue:"http://",fontFamilies:["Arial","Courier New","Tahoma","Times New Roman","Verdana"],defaultFont:"tahoma",defaultValue:(Ext.isOpera||Ext.isIE6)?"&#160;":"&#8203;",actionMode:"wrap",validationEvent:false,deferHeight:true,initialized:false,activated:false,sourceEditMode:false,onFocus:Ext.emptyFn,iframePad:3,hideMode:"offsets",defaultAutoCreate:{tag:"textarea",style:"width:500px;height:300px;",autocomplete:"off"},initComponent:function(){this.addEvents("initialize","activate","beforesync","beforepush","sync","push","editmodechange");Ext.form.HtmlEditor.superclass.initComponent.call(this)},createFontOptions:function(){var d=[],b=this.fontFamilies,c,g;for(var e=0,a=b.length;e<a;e++){c=b[e];g=c.toLowerCase();d.push('<option value="',g,'" style="font-family:',c,';"',(this.defaultFont==g?' selected="true">':">"),c,"</option>")}return d.join("")},createToolbar:function(e){var c=[];var a=Ext.QuickTips&&Ext.QuickTips.isEnabled();function d(j,h,i){return{itemId:j,cls:"x-btn-icon",iconCls:"x-edit-"+j,enableToggle:h!==false,scope:e,handler:i||e.relayBtnCmd,clickEvent:"mousedown",tooltip:a?e.buttonTips[j]||undefined:undefined,overflowText:e.buttonTips[j].title||undefined,tabIndex:-1}}if(this.enableFont&&!Ext.isSafari2){var g=new Ext.Toolbar.Item({autoEl:{tag:"select",cls:"x-font-select",html:this.createFontOptions()}});c.push(g,"-")}if(this.enableFormat){c.push(d("bold"),d("italic"),d("underline"))}if(this.enableFontSize){c.push("-",d("increasefontsize",false,this.adjustFont),d("decreasefontsize",false,this.adjustFont))}if(this.enableColors){c.push("-",{itemId:"forecolor",cls:"x-btn-icon",iconCls:"x-edit-forecolor",clickEvent:"mousedown",tooltip:a?e.buttonTips.forecolor||undefined:undefined,tabIndex:-1,menu:new Ext.menu.ColorMenu({allowReselect:true,focus:Ext.emptyFn,value:"000000",plain:true,listeners:{scope:this,select:function(i,h){this.execCmd("forecolor",Ext.isWebKit||Ext.isIE?"#"+h:h);this.deferFocus()}},clickEvent:"mousedown"})},{itemId:"backcolor",cls:"x-btn-icon",iconCls:"x-edit-backcolor",clickEvent:"mousedown",tooltip:a?e.buttonTips.backcolor||undefined:undefined,tabIndex:-1,menu:new Ext.menu.ColorMenu({focus:Ext.emptyFn,value:"FFFFFF",plain:true,allowReselect:true,listeners:{scope:this,select:function(i,h){if(Ext.isGecko){this.execCmd("useCSS",false);this.execCmd("hilitecolor",h);this.execCmd("useCSS",true);this.deferFocus()}else{this.execCmd(Ext.isOpera?"hilitecolor":"backcolor",Ext.isWebKit||Ext.isIE?"#"+h:h);this.deferFocus()}}},clickEvent:"mousedown"})})}if(this.enableAlignments){c.push("-",d("justifyleft"),d("justifycenter"),d("justifyright"))}if(!Ext.isSafari2){if(this.enableLinks){c.push("-",d("createlink",false,this.createLink))}if(this.enableLists){c.push("-",d("insertorderedlist"),d("insertunorderedlist"))}if(this.enableSourceEdit){c.push("-",d("sourceedit",true,function(h){this.toggleSourceEdit(!this.sourceEditMode)}))}}var b=new Ext.Toolbar({renderTo:this.wrap.dom.firstChild,items:c});if(g){this.fontSelect=g.el;this.mon(this.fontSelect,"change",function(){var h=this.fontSelect.dom.value;this.relayCmd("fontname",h);this.deferFocus()},this)}this.mon(b.el,"click",function(h){h.preventDefault()});this.tb=b;this.tb.doLayout()},onDisable:function(){this.wrap.mask();Ext.form.HtmlEditor.superclass.onDisable.call(this)},onEnable:function(){this.wrap.unmask();Ext.form.HtmlEditor.superclass.onEnable.call(this)},setReadOnly:function(b){Ext.form.HtmlEditor.superclass.setReadOnly.call(this,b);if(this.initialized){if(Ext.isIE){this.getEditorBody().contentEditable=!b}else{this.setDesignMode(!b)}var a=this.getEditorBody();if(a){a.style.cursor=this.readOnly?"default":"text"}this.disableItems(b)}},getDocMarkup:function(){var a=Ext.fly(this.iframe).getHeight()-this.iframePad*2;return String.format('<html><head><style type="text/css">body{border: 0; margin: 0; padding: {0}px; height: {1}px; cursor: text}</style></head><body></body></html>',this.iframePad,a)},getEditorBody:function(){var a=this.getDoc();return a.body||a.documentElement},getDoc:function(){return Ext.isIE?this.getWin().document:(this.iframe.contentDocument||this.getWin().document)},getWin:function(){return Ext.isIE?this.iframe.contentWindow:window.frames[this.iframe.name]},onRender:function(b,a){Ext.form.HtmlEditor.superclass.onRender.call(this,b,a);this.el.dom.style.border="0 none";this.el.dom.setAttribute("tabIndex",-1);this.el.addClass("x-hidden");if(Ext.isIE){this.el.applyStyles("margin-top:-1px;margin-bottom:-1px;")}this.wrap=this.el.wrap({cls:"x-html-editor-wrap",cn:{cls:"x-html-editor-tb"}});this.createToolbar(this);this.disableItems(true);this.tb.doLayout();this.createIFrame();if(!this.width){var c=this.el.getSize();this.setSize(c.width,this.height||c.height)}this.resizeEl=this.positionEl=this.wrap},createIFrame:function(){var a=document.createElement("iframe");a.name=Ext.id();a.frameBorder="0";a.style.overflow="auto";a.src=Ext.SSL_SECURE_URL;this.wrap.dom.appendChild(a);this.iframe=a;this.monitorTask=Ext.TaskMgr.start({run:this.checkDesignMode,scope:this,interval:100})},initFrame:function(){Ext.TaskMgr.stop(this.monitorTask);var a=this.getDoc();this.win=this.getWin();a.open();a.write(this.getDocMarkup());a.close();this.readyTask={run:function(){var b=this.getDoc();if(b.body||b.readyState=="complete"){Ext.TaskMgr.stop(this.readyTask);this.setDesignMode(true);this.initEditor.defer(10,this)}},interval:10,duration:10000,scope:this};Ext.TaskMgr.start(this.readyTask)},checkDesignMode:function(){if(this.wrap&&this.wrap.dom.offsetWidth){var a=this.getDoc();if(!a){return}if(!a.editorInitialized||this.getDesignMode()!="on"){this.initFrame()}}},setDesignMode:function(b){var a=this.getDoc();if(a){if(this.readOnly){b=false}a.designMode=(/on|true/i).test(String(b).toLowerCase())?"on":"off"}},getDesignMode:function(){var a=this.getDoc();if(!a){return""}return String(a.designMode).toLowerCase()},disableItems:function(a){if(this.fontSelect){this.fontSelect.dom.disabled=a}this.tb.items.each(function(b){if(b.getItemId()!="sourceedit"){b.setDisabled(a)}})},onResize:function(b,c){Ext.form.HtmlEditor.superclass.onResize.apply(this,arguments);if(this.el&&this.iframe){if(Ext.isNumber(b)){var e=b-this.wrap.getFrameWidth("lr");this.el.setWidth(e);this.tb.setWidth(e);this.iframe.style.width=Math.max(e,0)+"px"}if(Ext.isNumber(c)){var a=c-this.wrap.getFrameWidth("tb")-this.tb.el.getHeight();this.el.setHeight(a);this.iframe.style.height=Math.max(a,0)+"px";var d=this.getEditorBody();if(d){d.style.height=Math.max((a-(this.iframePad*2)),0)+"px"}}}},toggleSourceEdit:function(b){var d,a;if(b===undefined){b=!this.sourceEditMode}this.sourceEditMode=b===true;var c=this.tb.getComponent("sourceedit");if(c.pressed!==this.sourceEditMode){c.toggle(this.sourceEditMode);if(!c.xtbHidden){return}}if(this.sourceEditMode){this.previousSize=this.getSize();d=Ext.get(this.iframe).getHeight();this.disableItems(true);this.syncValue();this.iframe.className="x-hidden";this.el.removeClass("x-hidden");this.el.dom.removeAttribute("tabIndex");this.el.focus();this.el.dom.style.height=d+"px"}else{a=parseInt(this.el.dom.style.height,10);if(this.initialized){this.disableItems(this.readOnly)}this.pushValue();this.iframe.className="";this.el.addClass("x-hidden");this.el.dom.setAttribute("tabIndex",-1);this.deferFocus();this.setSize(this.previousSize);delete this.previousSize;this.iframe.style.height=a+"px"}this.fireEvent("editmodechange",this,this.sourceEditMode)},createLink:function(){var a=prompt(this.createLinkText,this.defaultLinkValue);if(a&&a!="http://"){this.relayCmd("createlink",a)}},initEvents:function(){this.originalValue=this.getValue()},markInvalid:Ext.emptyFn,clearInvalid:Ext.emptyFn,setValue:function(a){Ext.form.HtmlEditor.superclass.setValue.call(this,a);this.pushValue();return this},cleanHtml:function(a){a=String(a);if(Ext.isWebKit){a=a.replace(/\sclass="(?:Apple-style-span|khtml-block-placeholder)"/gi,"")}if(a.charCodeAt(0)==this.defaultValue.replace(/\D/g,"")){a=a.substring(1)}return a},syncValue:function(){if(this.initialized){var d=this.getEditorBody();var c=d.innerHTML;if(Ext.isWebKit){var b=d.getAttribute("style");var a=b.match(/text-align:(.*?);/i);if(a&&a[1]){c='<div style="'+a[0]+'">'+c+"</div>"}}c=this.cleanHtml(c);if(this.fireEvent("beforesync",this,c)!==false){this.el.dom.value=c;this.fireEvent("sync",this,c)}}},getValue:function(){this[this.sourceEditMode?"pushValue":"syncValue"]();return Ext.form.HtmlEditor.superclass.getValue.call(this)},pushValue:function(){if(this.initialized){var a=this.el.dom.value;if(!this.activated&&a.length<1){a=this.defaultValue}if(this.fireEvent("beforepush",this,a)!==false){this.getEditorBody().innerHTML=a;if(Ext.isGecko){this.setDesignMode(false);this.setDesignMode(true)}this.fireEvent("push",this,a)}}},deferFocus:function(){this.focus.defer(10,this)},focus:function(){if(this.win&&!this.sourceEditMode){this.win.focus()}else{this.el.focus()}},initEditor:function(){try{var c=this.getEditorBody(),a=this.el.getStyles("font-size","font-family","background-image","background-repeat","background-color","color"),g,b;a["background-attachment"]="fixed";c.bgProperties="fixed";Ext.DomHelper.applyStyles(c,a);g=this.getDoc();if(g){try{Ext.EventManager.removeAll(g)}catch(d){}}b=this.onEditorEvent.createDelegate(this);Ext.EventManager.on(g,{mousedown:b,dblclick:b,click:b,keyup:b,buffer:100});if(Ext.isGecko){Ext.EventManager.on(g,"keypress",this.applyCommand,this)}if(Ext.isIE||Ext.isWebKit||Ext.isOpera){Ext.EventManager.on(g,"keydown",this.fixKeys,this)}g.editorInitialized=true;this.initialized=true;this.pushValue();this.setReadOnly(this.readOnly);this.fireEvent("initialize",this)}catch(d){}},beforeDestroy:function(){if(this.monitorTask){Ext.TaskMgr.stop(this.monitorTask)}if(this.readyTask){Ext.TaskMgr.stop(this.readyTask)}if(this.rendered){Ext.destroy(this.tb);var b=this.getDoc();Ext.EventManager.removeFromSpecialCache(b);if(b){try{Ext.EventManager.removeAll(b);for(var c in b){delete b[c]}}catch(a){}}if(this.wrap){this.wrap.dom.innerHTML="";this.wrap.remove()}}Ext.form.HtmlEditor.superclass.beforeDestroy.call(this)},onFirstFocus:function(){this.activated=true;this.disableItems(this.readOnly);if(Ext.isGecko){this.win.focus();var a=this.win.getSelection();if(!a.focusNode||a.focusNode.nodeType!=3){var b=a.getRangeAt(0);b.selectNodeContents(this.getEditorBody());b.collapse(true);this.deferFocus()}try{this.execCmd("useCSS",true);this.execCmd("styleWithCSS",false)}catch(c){}}this.fireEvent("activate",this)},adjustFont:function(b){var d=b.getItemId()=="increasefontsize"?1:-1,c=this.getDoc(),a=parseInt(c.queryCommandValue("FontSize")||2,10);if((Ext.isSafari&&!Ext.isSafari2)||Ext.isChrome||Ext.isAir){if(a<=10){a=1+d}else{if(a<=13){a=2+d}else{if(a<=16){a=3+d}else{if(a<=18){a=4+d}else{if(a<=24){a=5+d}else{a=6+d}}}}}a=a.constrain(1,6)}else{if(Ext.isSafari){d*=2}a=Math.max(1,a+d)+(Ext.isSafari?"px":0)}this.execCmd("FontSize",a)},onEditorEvent:function(a){this.updateToolbar()},updateToolbar:function(){if(this.readOnly){return}if(!this.activated){this.onFirstFocus();return}var b=this.tb.items.map,c=this.getDoc();if(this.enableFont&&!Ext.isSafari2){var a=(c.queryCommandValue("FontName")||this.defaultFont).toLowerCase();if(a!=this.fontSelect.dom.value){this.fontSelect.dom.value=a}}if(this.enableFormat){b.bold.toggle(c.queryCommandState("bold"));b.italic.toggle(c.queryCommandState("italic"));b.underline.toggle(c.queryCommandState("underline"))}if(this.enableAlignments){b.justifyleft.toggle(c.queryCommandState("justifyleft"));b.justifycenter.toggle(c.queryCommandState("justifycenter"));b.justifyright.toggle(c.queryCommandState("justifyright"))}if(!Ext.isSafari2&&this.enableLists){b.insertorderedlist.toggle(c.queryCommandState("insertorderedlist"));b.insertunorderedlist.toggle(c.queryCommandState("insertunorderedlist"))}Ext.menu.MenuMgr.hideAll();this.syncValue()},relayBtnCmd:function(a){this.relayCmd(a.getItemId())},relayCmd:function(b,a){(function(){this.focus();this.execCmd(b,a);this.updateToolbar()}).defer(10,this)},execCmd:function(b,a){var c=this.getDoc();c.execCommand(b,false,a===undefined?null:a);this.syncValue()},applyCommand:function(b){if(b.ctrlKey){var d=b.getCharCode(),a;if(d>0){d=String.fromCharCode(d);switch(d){case"b":a="bold";break;case"i":a="italic";break;case"u":a="underline";break}if(a){this.win.focus();this.execCmd(a);this.deferFocus();b.preventDefault()}}}},insertAtCursor:function(c){if(!this.activated){return}if(Ext.isIE){this.win.focus();var b=this.getDoc(),a=b.selection.createRange();if(a){a.pasteHTML(c);this.syncValue();this.deferFocus()}}else{this.win.focus();this.execCmd("InsertHTML",c);this.deferFocus()}},fixKeys:function(){if(Ext.isIE){return function(g){var a=g.getKey(),d=this.getDoc(),b;if(a==g.TAB){g.stopEvent();b=d.selection.createRange();if(b){b.collapse(true);b.pasteHTML("&nbsp;&nbsp;&nbsp;&nbsp;");this.deferFocus()}}else{if(a==g.ENTER){b=d.selection.createRange();if(b){var c=b.parentElement();if(!c||c.tagName.toLowerCase()!="li"){g.stopEvent();b.pasteHTML("<br />");b.collapse(false);b.select()}}}}}}else{if(Ext.isOpera){return function(b){var a=b.getKey();if(a==b.TAB){b.stopEvent();this.win.focus();this.execCmd("InsertHTML","&nbsp;&nbsp;&nbsp;&nbsp;");this.deferFocus()}}}else{if(Ext.isWebKit){return function(b){var a=b.getKey();if(a==b.TAB){b.stopEvent();this.execCmd("InsertText","\t");this.deferFocus()}else{if(a==b.ENTER){b.stopEvent();this.execCmd("InsertHtml","<br /><br />");this.deferFocus()}}}}}}}(),getToolbar:function(){return this.tb},buttonTips:{bold:{title:"Bold (Ctrl+B)",text:"Make the selected text bold.",cls:"x-html-editor-tip"},italic:{title:"Italic (Ctrl+I)",text:"Make the selected text italic.",cls:"x-html-editor-tip"},underline:{title:"Underline (Ctrl+U)",text:"Underline the selected text.",cls:"x-html-editor-tip"},increasefontsize:{title:"Grow Text",text:"Increase the font size.",cls:"x-html-editor-tip"},decreasefontsize:{title:"Shrink Text",text:"Decrease the font size.",cls:"x-html-editor-tip"},backcolor:{title:"Text Highlight Color",text:"Change the background color of the selected text.",cls:"x-html-editor-tip"},forecolor:{title:"Font Color",text:"Change the color of the selected text.",cls:"x-html-editor-tip"},justifyleft:{title:"Align Text Left",text:"Align text to the left.",cls:"x-html-editor-tip"},justifycenter:{title:"Center Text",text:"Center text in the editor.",cls:"x-html-editor-tip"},justifyright:{title:"Align Text Right",text:"Align text to the right.",cls:"x-html-editor-tip"},insertunorderedlist:{title:"Bullet List",text:"Start a bulleted list.",cls:"x-html-editor-tip"},insertorderedlist:{title:"Numbered List",text:"Start a numbered list.",cls:"x-html-editor-tip"},createlink:{title:"Hyperlink",text:"Make the selected text a hyperlink.",cls:"x-html-editor-tip"},sourceedit:{title:"Source Edit",text:"Switch to source editing mode.",cls:"x-html-editor-tip"}}});Ext.reg("htmleditor",Ext.form.HtmlEditor);Ext.form.TimeField=Ext.extend(Ext.form.ComboBox,{minValue:undefined,maxValue:undefined,minText:"The time in this field must be equal to or after {0}",maxText:"The time in this field must be equal to or before {0}",invalidText:"{0} is not a valid time",format:"g:i A",altFormats:"g:ia|g:iA|g:i a|g:i A|h:i|g:i|H:i|ga|ha|gA|h a|g a|g A|gi|hi|gia|hia|g|H|gi a|hi a|giA|hiA|gi A|hi A",increment:15,mode:"local",triggerAction:"all",typeAhead:false,initDate:"1/1/2008",initDateFormat:"j/n/Y",initComponent:function(){if(Ext.isDefined(this.minValue)){this.setMinValue(this.minValue,true)}if(Ext.isDefined(this.maxValue)){this.setMaxValue(this.maxValue,true)}if(!this.store){this.generateStore(true)}Ext.form.TimeField.superclass.initComponent.call(this)},setMinValue:function(b,a){this.setLimit(b,true,a);return this},setMaxValue:function(b,a){this.setLimit(b,false,a);return this},generateStore:function(b){var c=this.minValue||new Date(this.initDate).clearTime(),a=this.maxValue||new Date(this.initDate).clearTime().add("mi",(24*60)-1),d=[];while(c<=a){d.push(c.dateFormat(this.format));c=c.add("mi",this.increment)}this.bindStore(d,b)},setLimit:function(b,g,a){var e;if(Ext.isString(b)){e=this.parseDate(b)}else{if(Ext.isDate(b)){e=b}}if(e){var c=new Date(this.initDate).clearTime();c.setHours(e.getHours(),e.getMinutes(),e.getSeconds(),e.getMilliseconds());this[g?"minValue":"maxValue"]=c;if(!a){this.generateStore()}}},getValue:function(){var a=Ext.form.TimeField.superclass.getValue.call(this);return this.formatDate(this.parseDate(a))||""},setValue:function(a){return Ext.form.TimeField.superclass.setValue.call(this,this.formatDate(this.parseDate(a)))},validateValue:Ext.form.DateField.prototype.validateValue,formatDate:Ext.form.DateField.prototype.formatDate,parseDate:function(h){if(!h||Ext.isDate(h)){return h}var j=this.initDate+" ",g=this.initDateFormat+" ",b=Date.parseDate(j+h,g+this.format),c=this.altFormats;if(!b&&c){if(!this.altFormatsArray){this.altFormatsArray=c.split("|")}for(var e=0,d=this.altFormatsArray,a=d.length;e<a&&!b;e++){b=Date.parseDate(j+h,g+d[e])}}return b}});Ext.reg("timefield",Ext.form.TimeField);Ext.form.SliderField=Ext.extend(Ext.form.Field,{useTips:true,tipText:null,actionMode:"wrap",initComponent:function(){var b=Ext.copyTo({id:this.id+"-slider"},this.initialConfig,["vertical","minValue","maxValue","decimalPrecision","keyIncrement","increment","clickToChange","animate"]);if(this.useTips){var a=this.tipText?{getText:this.tipText}:{};b.plugins=[new Ext.slider.Tip(a)]}this.slider=new Ext.Slider(b);Ext.form.SliderField.superclass.initComponent.call(this)},onRender:function(b,a){this.autoCreate={id:this.id,name:this.name,type:"hidden",tag:"input"};Ext.form.SliderField.superclass.onRender.call(this,b,a);this.wrap=this.el.wrap({cls:"x-form-field-wrap"});this.resizeEl=this.positionEl=this.wrap;this.slider.render(this.wrap)},onResize:function(b,c,d,a){Ext.form.SliderField.superclass.onResize.call(this,b,c,d,a);this.slider.setSize(b,c)},initEvents:function(){Ext.form.SliderField.superclass.initEvents.call(this);this.slider.on("change",this.onChange,this)},onChange:function(b,a){this.setValue(a,undefined,true)},onEnable:function(){Ext.form.SliderField.superclass.onEnable.call(this);this.slider.enable()},onDisable:function(){Ext.form.SliderField.superclass.onDisable.call(this);this.slider.disable()},beforeDestroy:function(){Ext.destroy(this.slider);Ext.form.SliderField.superclass.beforeDestroy.call(this)},alignErrorIcon:function(){this.errorIcon.alignTo(this.slider.el,"tl-tr",[2,0])},setMinValue:function(a){this.slider.setMinValue(a);return this},setMaxValue:function(a){this.slider.setMaxValue(a);return this},setValue:function(c,b,a){if(!a){this.slider.setValue(c,b)}return Ext.form.SliderField.superclass.setValue.call(this,this.slider.getValue())},getValue:function(){return this.slider.getValue()}});Ext.reg("sliderfield",Ext.form.SliderField);Ext.form.Label=Ext.extend(Ext.BoxComponent,{onRender:function(b,a){if(!this.el){this.el=document.createElement("label");this.el.id=this.getId();this.el.innerHTML=this.text?Ext.util.Format.htmlEncode(this.text):(this.html||"");if(this.forId){this.el.setAttribute("for",this.forId)}}Ext.form.Label.superclass.onRender.call(this,b,a)},setText:function(a,b){var c=b===false;this[!c?"text":"html"]=a;delete this[c?"text":"html"];if(this.rendered){this.el.dom.innerHTML=b!==false?Ext.util.Format.htmlEncode(a):a}return this}});Ext.reg("label",Ext.form.Label);Ext.form.Action=function(b,a){this.form=b;this.options=a||{}};Ext.form.Action.CLIENT_INVALID="client";Ext.form.Action.SERVER_INVALID="server";Ext.form.Action.CONNECT_FAILURE="connect";Ext.form.Action.LOAD_FAILURE="load";Ext.form.Action.prototype={type:"default",run:function(a){},success:function(a){},handleResponse:function(a){},failure:function(a){this.response=a;this.failureType=Ext.form.Action.CONNECT_FAILURE;this.form.afterAction(this,false)},processResponse:function(a){this.response=a;if(!a.responseText&&!a.responseXML){return true}this.result=this.handleResponse(a);return this.result},decodeResponse:function(a){try{return Ext.decode(a.responseText)}catch(b){return false}},getUrl:function(c){var a=this.options.url||this.form.url||this.form.el.dom.action;if(c){var b=this.getParams();if(b){a=Ext.urlAppend(a,b)}}return a},getMethod:function(){return(this.options.method||this.form.method||this.form.el.dom.method||"POST").toUpperCase()},getParams:function(){var a=this.form.baseParams;var b=this.options.params;if(b){if(typeof b=="object"){b=Ext.urlEncode(Ext.applyIf(b,a))}else{if(typeof b=="string"&&a){b+="&"+Ext.urlEncode(a)}}}else{if(a){b=Ext.urlEncode(a)}}return b},createCallback:function(a){var a=a||{};return{success:this.success,failure:this.failure,scope:this,timeout:(a.timeout*1000)||(this.form.timeout*1000),upload:this.form.fileUpload?this.success:undefined}}};Ext.form.Action.Submit=function(b,a){Ext.form.Action.Submit.superclass.constructor.call(this,b,a)};Ext.extend(Ext.form.Action.Submit,Ext.form.Action,{type:"submit",run:function(){var e=this.options,g=this.getMethod(),d=g=="GET";if(e.clientValidation===false||this.form.isValid()){if(e.submitEmptyText===false){var a=this.form.items,c=[],b=function(h){if(h.el.getValue()==h.emptyText){c.push(h);h.el.dom.value=""}if(h.isComposite&&h.rendered){h.items.each(b)}};a.each(b)}Ext.Ajax.request(Ext.apply(this.createCallback(e),{form:this.form.el.dom,url:this.getUrl(d),method:g,headers:e.headers,params:!d?this.getParams():null,isUpload:this.form.fileUpload}));if(e.submitEmptyText===false){Ext.each(c,function(h){if(h.applyEmptyText){h.applyEmptyText()}})}}else{if(e.clientValidation!==false){this.failureType=Ext.form.Action.CLIENT_INVALID;this.form.afterAction(this,false)}}},success:function(b){var a=this.processResponse(b);if(a===true||a.success){this.form.afterAction(this,true);return}if(a.errors){this.form.markInvalid(a.errors)}this.failureType=Ext.form.Action.SERVER_INVALID;this.form.afterAction(this,false)},handleResponse:function(c){if(this.form.errorReader){var b=this.form.errorReader.read(c);var g=[];if(b.records){for(var d=0,a=b.records.length;d<a;d++){var e=b.records[d];g[d]=e.data}}if(g.length<1){g=null}return{success:b.success,errors:g}}return this.decodeResponse(c)}});Ext.form.Action.Load=function(b,a){Ext.form.Action.Load.superclass.constructor.call(this,b,a);this.reader=this.form.reader};Ext.extend(Ext.form.Action.Load,Ext.form.Action,{type:"load",run:function(){Ext.Ajax.request(Ext.apply(this.createCallback(this.options),{method:this.getMethod(),url:this.getUrl(false),headers:this.options.headers,params:this.getParams()}))},success:function(b){var a=this.processResponse(b);if(a===true||!a.success||!a.data){this.failureType=Ext.form.Action.LOAD_FAILURE;this.form.afterAction(this,false);return}this.form.clearInvalid();this.form.setValues(a.data);this.form.afterAction(this,true)},handleResponse:function(b){if(this.form.reader){var a=this.form.reader.read(b);var c=a.records&&a.records[0]?a.records[0].data:null;return{success:a.success,data:c}}return this.decodeResponse(b)}});Ext.form.Action.DirectLoad=Ext.extend(Ext.form.Action.Load,{constructor:function(b,a){Ext.form.Action.DirectLoad.superclass.constructor.call(this,b,a)},type:"directload",run:function(){var a=this.getParams();a.push(this.success,this);this.form.api.load.apply(window,a)},getParams:function(){var c=[],h={};var e=this.form.baseParams;var g=this.options.params;Ext.apply(h,g,e);var b=this.form.paramOrder;if(b){for(var d=0,a=b.length;d<a;d++){c.push(h[b[d]])}}else{if(this.form.paramsAsHash){c.push(h)}}return c},processResponse:function(a){this.result=a;return a},success:function(a,b){if(b.type==Ext.Direct.exceptions.SERVER){a={}}Ext.form.Action.DirectLoad.superclass.success.call(this,a)}});Ext.form.Action.DirectSubmit=Ext.extend(Ext.form.Action.Submit,{constructor:function(b,a){Ext.form.Action.DirectSubmit.superclass.constructor.call(this,b,a)},type:"directsubmit",run:function(){var a=this.options;if(a.clientValidation===false||this.form.isValid()){this.success.params=this.getParams();this.form.api.submit(this.form.el.dom,this.success,this)}else{if(a.clientValidation!==false){this.failureType=Ext.form.Action.CLIENT_INVALID;this.form.afterAction(this,false)}}},getParams:function(){var c={};var a=this.form.baseParams;var b=this.options.params;Ext.apply(c,b,a);return c},processResponse:function(a){this.result=a;return a},success:function(a,b){if(b.type==Ext.Direct.exceptions.SERVER){a={}}Ext.form.Action.DirectSubmit.superclass.success.call(this,a)}});Ext.form.Action.ACTION_TYPES={load:Ext.form.Action.Load,submit:Ext.form.Action.Submit,directload:Ext.form.Action.DirectLoad,directsubmit:Ext.form.Action.DirectSubmit};Ext.form.VTypes=function(){var c=/^[a-zA-Z_]+$/,d=/^[a-zA-Z0-9_]+$/,b=/^(\w+)([\-+.\'][\w]+)*@(\w[\-\w]*\.){1,5}([A-Za-z]){2,6}$/,a=/(((^https?)|(^ftp)):\/\/([\-\w]+\.)+\w{2,3}(\/[%\-\w]+(\.\w{2,})?)*(([\w\-\.\?\\\/+@&#;`~=%!]*)(\.\w{2,})?)*\/?)/i;return{email:function(e){return b.test(e)},emailText:'This field should be an e-mail address in the format "user@example.com"',emailMask:/[a-z0-9_\.\-\+\'@]/i,url:function(e){return a.test(e)},urlText:'This field should be a URL in the format "http://www.example.com"',alpha:function(e){return c.test(e)},alphaText:"This field should only contain letters and _",alphaMask:/[a-z_]/i,alphanum:function(e){return d.test(e)},alphanumText:"This field should only contain letters, numbers and _",alphanumMask:/[a-z0-9_]/i}}();Ext.grid.GridPanel=Ext.extend(Ext.Panel,{autoExpandColumn:false,autoExpandMax:1000,autoExpandMin:50,columnLines:false,ddText:"{0} selected row{1}",deferRowRender:true,enableColumnHide:true,enableColumnMove:true,enableDragDrop:false,enableHdMenu:true,loadMask:false,minColumnWidth:25,stripeRows:false,trackMouseOver:true,stateEvents:["columnmove","columnresize","sortchange","groupchange"],view:null,bubbleEvents:[],rendered:false,viewReady:false,initComponent:function(){Ext.grid.GridPanel.superclass.initComponent.call(this);if(this.columnLines){this.cls=(this.cls||"")+" x-grid-with-col-lines"}this.autoScroll=false;this.autoWidth=false;if(Ext.isArray(this.columns)){this.colModel=new Ext.grid.ColumnModel(this.columns);delete this.columns}if(this.ds){this.store=this.ds;delete this.ds}if(this.cm){this.colModel=this.cm;delete this.cm}if(this.sm){this.selModel=this.sm;delete this.sm}this.store=Ext.StoreMgr.lookup(this.store);this.addEvents("click","dblclick","contextmenu","mousedown","mouseup","mouseover","mouseout","keypress","keydown","cellmousedown","rowmousedown","headermousedown","groupmousedown","rowbodymousedown","containermousedown","cellclick","celldblclick","rowclick","rowdblclick","headerclick","headerdblclick","groupclick","groupdblclick","containerclick","containerdblclick","rowbodyclick","rowbodydblclick","rowcontextmenu","cellcontextmenu","headercontextmenu","groupcontextmenu","containercontextmenu","rowbodycontextmenu","bodyscroll","columnresize","columnmove","sortchange","groupchange","reconfigure","viewready")},onRender:function(d,a){Ext.grid.GridPanel.superclass.onRender.apply(this,arguments);var e=this.getGridEl();this.el.addClass("x-grid-panel");this.mon(e,{scope:this,mousedown:this.onMouseDown,click:this.onClick,dblclick:this.onDblClick,contextmenu:this.onContextMenu});this.relayEvents(e,["mousedown","mouseup","mouseover","mouseout","keypress","keydown"]);var b=this.getView();b.init(this);b.render();this.getSelectionModel().init(this)},initEvents:function(){Ext.grid.GridPanel.superclass.initEvents.call(this);if(this.loadMask){this.loadMask=new Ext.LoadMask(this.bwrap,Ext.apply({store:this.store},this.loadMask))}},initStateEvents:function(){Ext.grid.GridPanel.superclass.initStateEvents.call(this);this.mon(this.colModel,"hiddenchange",this.saveState,this,{delay:100})},applyState:function(a){var k=this.colModel,g=a.columns,j=this.store,m,h,l;if(g){for(var d=0,e=g.length;d<e;d++){m=g[d];h=k.getColumnById(m.id);if(h){l=k.getIndexById(m.id);k.setState(l,{hidden:m.hidden,width:m.width,sortable:h.sortable,editable:h.editable});if(l!=d){k.moveColumn(l,d)}}}}if(j){m=a.sort;if(m){j[j.remoteSort?"setDefaultSort":"sort"](m.field,m.direction)}m=a.group;if(j.groupBy){if(m){j.groupBy(m)}else{j.clearGrouping()}}}var b=Ext.apply({},a);delete b.columns;delete b.sort;Ext.grid.GridPanel.superclass.applyState.call(this,b)},getState:function(){var g={columns:[]},b=this.store,e,a;for(var d=0,h;(h=this.colModel.config[d]);d++){g.columns[d]={id:h.id,width:h.width};if(h.hidden){g.columns[d].hidden=true}}if(b){e=b.getSortState();if(e){g.sort=e}if(b.getGroupState){a=b.getGroupState();if(a){g.group=a}}}return g},afterRender:function(){Ext.grid.GridPanel.superclass.afterRender.call(this);var a=this.view;this.on("bodyresize",a.layout,a);a.layout(true);if(this.deferRowRender){if(!this.deferRowRenderTask){this.deferRowRenderTask=new Ext.util.DelayedTask(a.afterRender,this.view)}this.deferRowRenderTask.delay(10)}else{a.afterRender()}this.viewReady=true},reconfigure:function(a,b){var c=this.rendered;if(c){if(this.loadMask){this.loadMask.destroy();this.loadMask=new Ext.LoadMask(this.bwrap,Ext.apply({},{store:a},this.initialConfig.loadMask))}}if(this.view){this.view.initData(a,b)}this.store=a;this.colModel=b;if(c){this.view.refresh(true)}this.fireEvent("reconfigure",this,a,b)},onDestroy:function(){if(this.deferRowRenderTask&&this.deferRowRenderTask.cancel){this.deferRowRenderTask.cancel()}if(this.rendered){Ext.destroy(this.view,this.loadMask)}else{if(this.store&&this.store.autoDestroy){this.store.destroy()}}Ext.destroy(this.colModel,this.selModel);this.store=this.selModel=this.colModel=this.view=this.loadMask=null;Ext.grid.GridPanel.superclass.onDestroy.call(this)},processEvent:function(a,b){this.view.processEvent(a,b)},onClick:function(a){this.processEvent("click",a)},onMouseDown:function(a){this.processEvent("mousedown",a)},onContextMenu:function(b,a){this.processEvent("contextmenu",b)},onDblClick:function(a){this.processEvent("dblclick",a)},walkCells:function(k,c,b,e,j){var i=this.colModel,g=i.getColumnCount(),a=this.store,h=a.getCount(),d=true;if(b<0){if(c<0){k--;d=false}while(k>=0){if(!d){c=g-1}d=false;while(c>=0){if(e.call(j||this,k,c,i)===true){return[k,c]}c--}k--}}else{if(c>=g){k++;d=false}while(k<h){if(!d){c=0}d=false;while(c<g){if(e.call(j||this,k,c,i)===true){return[k,c]}c++}k++}}return null},getGridEl:function(){return this.body},stopEditing:Ext.emptyFn,getSelectionModel:function(){if(!this.selModel){this.selModel=new Ext.grid.RowSelectionModel(this.disableSelection?{selectRow:Ext.emptyFn}:null)}return this.selModel},getStore:function(){return this.store},getColumnModel:function(){return this.colModel},getView:function(){if(!this.view){this.view=new Ext.grid.GridView(this.viewConfig)}return this.view},getDragDropText:function(){var a=this.selModel.getCount?this.selModel.getCount():1;return String.format(this.ddText,a,a==1?"":"s")}});Ext.reg("grid",Ext.grid.GridPanel);Ext.grid.PivotGrid=Ext.extend(Ext.grid.GridPanel,{aggregator:"sum",renderer:undefined,initComponent:function(){Ext.grid.PivotGrid.superclass.initComponent.apply(this,arguments);this.initAxes();this.enableColumnResize=false;this.viewConfig=Ext.apply(this.viewConfig||{},{forceFit:true});this.colModel=new Ext.grid.ColumnModel({})},getAggregator:function(){if(typeof this.aggregator=="string"){return Ext.grid.PivotAggregatorMgr.types[this.aggregator]}else{return this.aggregator}},setAggregator:function(a){this.aggregator=a},setMeasure:function(a){this.measure=a},setLeftAxis:function(b,a){this.leftAxis=b;if(a){this.view.refresh()}},setTopAxis:function(b,a){this.topAxis=b;if(a){this.view.refresh()}},initAxes:function(){var a=Ext.grid.PivotAxis;if(!(this.leftAxis instanceof a)){this.setLeftAxis(new a({orientation:"vertical",dimensions:this.leftAxis||[],store:this.store}))}if(!(this.topAxis instanceof a)){this.setTopAxis(new a({orientation:"horizontal",dimensions:this.topAxis||[],store:this.store}))}},extractData:function(){var c=this.store.data.items,s=c.length,q=[],h,g,e,d;if(s==0){return[]}var l=this.leftAxis.getTuples(),o=l.length,m=this.topAxis.getTuples(),a=m.length,b=this.getAggregator();for(g=0;g<s;g++){h=c[g];for(e=0;e<o;e++){q[e]=q[e]||[];if(l[e].matcher(h)===true){for(d=0;d<a;d++){q[e][d]=q[e][d]||[];if(m[d].matcher(h)){q[e][d].push(h)}}}}}var n=q.length,p,r;for(g=0;g<n;g++){r=q[g];p=r.length;for(e=0;e<p;e++){q[g][e]=b(q[g][e],this.measure)}}return q},getView:function(){if(!this.view){this.view=new Ext.grid.PivotGridView(this.viewConfig)}return this.view}});Ext.reg("pivotgrid",Ext.grid.PivotGrid);Ext.grid.PivotAggregatorMgr=new Ext.AbstractManager();Ext.grid.PivotAggregatorMgr.registerType("sum",function(a,c){var e=a.length,d=0,b;for(b=0;b<e;b++){d+=a[b].get(c)}return d});Ext.grid.PivotAggregatorMgr.registerType("avg",function(a,c){var e=a.length,d=0,b;for(b=0;b<e;b++){d+=a[b].get(c)}return(d/e)||"n/a"});Ext.grid.PivotAggregatorMgr.registerType("min",function(a,c){var e=[],d=a.length,b;for(b=0;b<d;b++){e.push(a[b].get(c))}return Math.min.apply(this,e)||"n/a"});Ext.grid.PivotAggregatorMgr.registerType("max",function(a,c){var e=[],d=a.length,b;for(b=0;b<d;b++){e.push(a[b].get(c))}return Math.max.apply(this,e)||"n/a"});Ext.grid.PivotAggregatorMgr.registerType("count",function(a,b){return a.length});Ext.grid.GridView=Ext.extend(Ext.util.Observable,{deferEmptyText:true,scrollOffset:undefined,autoFill:false,forceFit:false,sortClasses:["sort-asc","sort-desc"],sortAscText:"Sort Ascending",sortDescText:"Sort Descending",hideSortIcons:false,columnsText:"Columns",selectedRowClass:"x-grid3-row-selected",borderWidth:2,tdClass:"x-grid3-cell",hdCls:"x-grid3-hd",markDirty:true,cellSelectorDepth:4,rowSelectorDepth:10,rowBodySelectorDepth:10,cellSelector:"td.x-grid3-cell",rowSelector:"div.x-grid3-row",rowBodySelector:"div.x-grid3-row-body",firstRowCls:"x-grid3-row-first",lastRowCls:"x-grid3-row-last",rowClsRe:/(?:^|\s+)x-grid3-row-(first|last|alt)(?:\s+|$)/g,headerMenuOpenCls:"x-grid3-hd-menu-open",rowOverCls:"x-grid3-row-over",constructor:function(a){Ext.apply(this,a);this.addEvents("beforerowremoved","beforerowsinserted","beforerefresh","rowremoved","rowsinserted","rowupdated","refresh");Ext.grid.GridView.superclass.constructor.call(this)},masterTpl:new Ext.Template('<div class="x-grid3" hidefocus="true">','<div class="x-grid3-viewport">','<div class="x-grid3-header">','<div class="x-grid3-header-inner">','<div class="x-grid3-header-offset" style="{ostyle}">{header}</div>',"</div>",'<div class="x-clear"></div>',"</div>",'<div class="x-grid3-scroller">','<div class="x-grid3-body" style="{bstyle}">{body}</div>','<a href="#" class="x-grid3-focus" tabIndex="-1"></a>',"</div>","</div>",'<div class="x-grid3-resize-marker">&#160;</div>','<div class="x-grid3-resize-proxy">&#160;</div>',"</div>"),headerTpl:new Ext.Template('<table border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',"<thead>",'<tr class="x-grid3-hd-row">{cells}</tr>',"</thead>","</table>"),bodyTpl:new Ext.Template("{rows}"),cellTpl:new Ext.Template('<td class="x-grid3-col x-grid3-cell x-grid3-td-{id} {css}" style="{style}" tabIndex="0" {cellAttr}>','<div class="x-grid3-cell-inner x-grid3-col-{id} x-unselectable" unselectable="on" {attr}>{value}</div>',"</td>"),initTemplates:function(){var c=this.templates||{},d,b,g=new Ext.Template('<td class="x-grid3-hd x-grid3-cell x-grid3-td-{id} {css}" style="{style}">','<div {tooltip} {attr} class="x-grid3-hd-inner x-grid3-hd-{id}" unselectable="on" style="{istyle}">',this.grid.enableHdMenu?'<a class="x-grid3-hd-btn" href="#"></a>':"","{value}",'<img alt="" class="x-grid3-sort-icon" src="',Ext.BLANK_IMAGE_URL,'" />',"</div>","</td>"),a=['<tr class="x-grid3-row-body-tr" style="{bodyStyle}">','<td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on">','<div class="x-grid3-row-body">{body}</div>',"</td>","</tr>"].join(""),e=['<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',"<tbody>","<tr>{cells}</tr>",this.enableRowBody?a:"","</tbody>","</table>"].join("");Ext.applyIf(c,{hcell:g,cell:this.cellTpl,body:this.bodyTpl,header:this.headerTpl,master:this.masterTpl,row:new Ext.Template('<div class="x-grid3-row {alt}" style="{tstyle}">'+e+"</div>"),rowInner:new Ext.Template(e)});for(b in c){d=c[b];if(d&&Ext.isFunction(d.compile)&&!d.compiled){d.disableFormats=true;d.compile()}}this.templates=c;this.colRe=new RegExp("x-grid3-td-([^\\s]+)","")},fly:function(a){if(!this._flyweight){this._flyweight=new Ext.Element.Flyweight(document.body)}this._flyweight.dom=a;return this._flyweight},getEditorParent:function(){return this.scroller.dom},initElements:function(){var b=Ext.Element,d=Ext.get(this.grid.getGridEl().dom.firstChild),e=new b(d.child("div.x-grid3-viewport")),c=new b(e.child("div.x-grid3-header")),a=new b(e.child("div.x-grid3-scroller"));if(this.grid.hideHeaders){c.setDisplayed(false)}if(this.forceFit){a.setStyle("overflow-x","hidden")}Ext.apply(this,{el:d,mainWrap:e,scroller:a,mainHd:c,innerHd:c.child("div.x-grid3-header-inner").dom,mainBody:new b(b.fly(a).child("div.x-grid3-body")),focusEl:new b(b.fly(a).child("a")),resizeMarker:new b(d.child("div.x-grid3-resize-marker")),resizeProxy:new b(d.child("div.x-grid3-resize-proxy"))});this.focusEl.swallowEvent("click",true)},getRows:function(){return this.hasRows()?this.mainBody.dom.childNodes:[]},findCell:function(a){if(!a){return false}return this.fly(a).findParent(this.cellSelector,this.cellSelectorDepth)},findCellIndex:function(d,c){var b=this.findCell(d),a;if(b){a=this.fly(b).hasClass(c);if(!c||a){return this.getCellIndex(b)}}return false},getCellIndex:function(b){if(b){var a=b.className.match(this.colRe);if(a&&a[1]){return this.cm.getIndexById(a[1])}}return false},findHeaderCell:function(b){var a=this.findCell(b);return a&&this.fly(a).hasClass(this.hdCls)?a:null},findHeaderIndex:function(a){return this.findCellIndex(a,this.hdCls)},findRow:function(a){if(!a){return false}return this.fly(a).findParent(this.rowSelector,this.rowSelectorDepth)},findRowIndex:function(a){var b=this.findRow(a);return b?b.rowIndex:false},findRowBody:function(a){if(!a){return false}return this.fly(a).findParent(this.rowBodySelector,this.rowBodySelectorDepth)},getRow:function(a){return this.getRows()[a]},getCell:function(b,a){return Ext.fly(this.getRow(b)).query(this.cellSelector)[a]},getHeaderCell:function(a){return this.mainHd.dom.getElementsByTagName("td")[a]},addRowClass:function(b,a){var c=this.getRow(b);if(c){this.fly(c).addClass(a)}},removeRowClass:function(c,a){var b=this.getRow(c);if(b){this.fly(b).removeClass(a)}},removeRow:function(a){Ext.removeNode(this.getRow(a));this.syncFocusEl(a)},removeRows:function(c,a){var b=this.mainBody.dom,d;for(d=c;d<=a;d++){Ext.removeNode(b.childNodes[c])}this.syncFocusEl(c)},getScrollState:function(){var a=this.scroller.dom;return{left:a.scrollLeft,top:a.scrollTop}},restoreScroll:function(a){var b=this.scroller.dom;b.scrollLeft=a.left;b.scrollTop=a.top},scrollToTop:function(){var a=this.scroller.dom;a.scrollTop=0;a.scrollLeft=0},syncScroll:function(){this.syncHeaderScroll();var a=this.scroller.dom;this.grid.fireEvent("bodyscroll",a.scrollLeft,a.scrollTop)},syncHeaderScroll:function(){var a=this.innerHd,b=this.scroller.dom.scrollLeft;a.scrollLeft=b;a.scrollLeft=b},updateSortIcon:function(d,c){var a=this.sortClasses,b=a[c=="DESC"?1:0],e=this.mainHd.select("td").removeClass(a);e.item(d).addClass(b)},updateAllColumnWidths:function(){var e=this.getTotalWidth(),k=this.cm.getColumnCount(),m=this.getRows(),g=m.length,b=[],l,a,h,d,c;for(d=0;d<k;d++){b[d]=this.getColumnWidth(d);this.getHeaderCell(d).style.width=b[d]}this.updateHeaderWidth();for(d=0;d<g;d++){l=m[d];l.style.width=e;a=l.firstChild;if(a){a.style.width=e;h=a.rows[0];for(c=0;c<k;c++){h.childNodes[c].style.width=b[c]}}}this.onAllColumnWidthsUpdated(b,e)},updateColumnWidth:function(d,b){var c=this.getColumnWidth(d),j=this.getTotalWidth(),h=this.getHeaderCell(d),a=this.getRows(),e=a.length,l,g,k;this.updateHeaderWidth();h.style.width=c;for(g=0;g<e;g++){l=a[g];k=l.firstChild;l.style.width=j;if(k){k.style.width=j;k.rows[0].childNodes[d].style.width=c}}this.onColumnWidthUpdated(d,c,j)},updateColumnHidden:function(b,j){var h=this.getTotalWidth(),k=j?"none":"",g=this.getHeaderCell(b),a=this.getRows(),d=a.length,l,c,e;this.updateHeaderWidth();g.style.display=k;for(e=0;e<d;e++){l=a[e];l.style.width=h;c=l.firstChild;if(c){c.style.width=h;c.rows[0].childNodes[b].style.display=k}}this.onColumnHiddenUpdated(b,j,h);delete this.lastViewWidth;this.layout()},doRender:function(d,v,m,a,r,t){var h=this.templates,c=h.cell,y=h.row,o=r-1,b="width:"+this.getTotalWidth()+";",k=[],l=[],n={tstyle:b},q={},w=v.length,x,g,e,u,s,p;for(s=0;s<w;s++){e=v[s];l=[];p=s+a;for(u=0;u<r;u++){g=d[u];q.id=g.id;q.css=u===0?"x-grid3-cell-first ":(u==o?"x-grid3-cell-last ":"");q.attr=q.cellAttr="";q.style=g.style;q.value=g.renderer.call(g.scope,e.data[g.name],q,e,p,u,m);if(Ext.isEmpty(q.value)){q.value="&#160;"}if(this.markDirty&&e.dirty&&typeof e.modified[g.name]!="undefined"){q.css+=" x-grid3-dirty-cell"}l[l.length]=c.apply(q)}x=[];if(t&&((p+1)%2===0)){x[0]="x-grid3-row-alt"}if(e.dirty){x[1]=" x-grid3-dirty-row"}n.cols=r;if(this.getRowClass){x[2]=this.getRowClass(e,p,n,m)}n.alt=x.join(" ");n.cells=l.join("");k[k.length]=y.apply(n)}return k.join("")},processRows:function(a,g){if(!this.ds||this.ds.getCount()<1){return}var d=this.getRows(),c=d.length,e,b;g=g||!this.grid.stripeRows;a=a||0;for(b=0;b<c;b++){e=d[b];if(e){e.rowIndex=b;if(!g){e.className=e.className.replace(this.rowClsRe," ");if((b+1)%2===0){e.className+=" x-grid3-row-alt"}}}}if(a===0){Ext.fly(d[0]).addClass(this.firstRowCls)}Ext.fly(d[c-1]).addClass(this.lastRowCls)},afterRender:function(){if(!this.ds||!this.cm){return}this.mainBody.dom.innerHTML=this.renderBody()||"&#160;";this.processRows(0,true);if(this.deferEmptyText!==true){this.applyEmptyText()}this.grid.fireEvent("viewready",this.grid)},afterRenderUI:function(){var a=this.grid;this.initElements();Ext.fly(this.innerHd).on("click",this.handleHdDown,this);this.mainHd.on({scope:this,mouseover:this.handleHdOver,mouseout:this.handleHdOut,mousemove:this.handleHdMove});this.scroller.on("scroll",this.syncScroll,this);if(a.enableColumnResize!==false){this.splitZone=new Ext.grid.GridView.SplitDragZone(a,this.mainHd.dom)}if(a.enableColumnMove){this.columnDrag=new Ext.grid.GridView.ColumnDragZone(a,this.innerHd);this.columnDrop=new Ext.grid.HeaderDropZone(a,this.mainHd.dom)}if(a.enableHdMenu!==false){this.hmenu=new Ext.menu.Menu({id:a.id+"-hctx"});this.hmenu.add({itemId:"asc",text:this.sortAscText,cls:"xg-hmenu-sort-asc"},{itemId:"desc",text:this.sortDescText,cls:"xg-hmenu-sort-desc"});if(a.enableColumnHide!==false){this.colMenu=new Ext.menu.Menu({id:a.id+"-hcols-menu"});this.colMenu.on({scope:this,beforeshow:this.beforeColMenuShow,itemclick:this.handleHdMenuClick});this.hmenu.add({itemId:"sortSep",xtype:"menuseparator"},{itemId:"columns",hideOnClick:false,text:this.columnsText,menu:this.colMenu,iconCls:"x-cols-icon"})}this.hmenu.on("itemclick",this.handleHdMenuClick,this)}if(a.trackMouseOver){this.mainBody.on({scope:this,mouseover:this.onRowOver,mouseout:this.onRowOut})}if(a.enableDragDrop||a.enableDrag){this.dragZone=new Ext.grid.GridDragZone(a,{ddGroup:a.ddGroup||"GridDD"})}this.updateHeaderSortState()},renderUI:function(){var a=this.templates;return a.master.apply({body:a.body.apply({rows:"&#160;"}),header:this.renderHeaders(),ostyle:"width:"+this.getOffsetWidth()+";",bstyle:"width:"+this.getTotalWidth()+";"})},processEvent:function(b,h){var i=h.getTarget(),a=this.grid,d=this.findHeaderIndex(i),k,j,c,g;a.fireEvent(b,h);if(d!==false){a.fireEvent("header"+b,a,d,h)}else{k=this.findRowIndex(i);if(k!==false){j=this.findCellIndex(i);if(j!==false){c=a.colModel.getColumnAt(j);if(a.fireEvent("cell"+b,a,k,j,h)!==false){if(!c||(c.processEvent&&(c.processEvent(b,h,a,k,j)!==false))){a.fireEvent("row"+b,a,k,h)}}}else{if(a.fireEvent("row"+b,a,k,h)!==false){(g=this.findRowBody(i))&&a.fireEvent("rowbody"+b,a,k,h)}}}else{a.fireEvent("container"+b,a,h)}}},layout:function(j){if(!this.mainBody){return}var a=this.grid,d=a.getGridEl(),c=d.getSize(true),i=c.width,b=c.height,h=this.scroller,g,e,k;if(i<20||b<20){return}if(a.autoHeight){g=h.dom.style;g.overflow="visible";if(Ext.isWebKit){g.position="static"}}else{this.el.setSize(i,b);e=this.mainHd.getHeight();k=b-e;h.setSize(i,k);if(this.innerHd){this.innerHd.style.width=(i)+"px"}}if(this.forceFit||(j===true&&this.autoFill)){if(this.lastViewWidth!=i){this.fitColumns(false,false);this.lastViewWidth=i}}else{this.autoExpand();this.syncHeaderScroll()}this.onLayout(i,k)},onLayout:function(a,b){},onColumnWidthUpdated:function(c,a,b){},onAllColumnWidthsUpdated:function(a,b){},onColumnHiddenUpdated:function(b,c,a){},updateColumnText:function(a,b){},afterMove:function(a){},init:function(a){this.grid=a;this.initTemplates();this.initData(a.store,a.colModel);this.initUI(a)},getColumnId:function(a){return this.cm.getColumnId(a)},getOffsetWidth:function(){return(this.cm.getTotalWidth()+this.getScrollOffset())+"px"},getScrollOffset:function(){return Ext.num(this.scrollOffset,Ext.getScrollBarWidth())},renderHeaders:function(){var e=this.cm,g=this.templates,a=g.hcell,d={},h=e.getColumnCount(),j=h-1,k=[],c,b;for(c=0;c<h;c++){if(c==0){b="x-grid3-cell-first "}else{b=c==j?"x-grid3-cell-last ":""}d={id:e.getColumnId(c),value:e.getColumnHeader(c)||"",style:this.getColumnStyle(c,true),css:b,tooltip:this.getColumnTooltip(c)};if(e.config[c].align=="right"){d.istyle="padding-right: 16px;"}else{delete d.istyle}k[c]=a.apply(d)}return g.header.apply({cells:k.join(""),tstyle:String.format("width: {0};",this.getTotalWidth())})},getColumnTooltip:function(a){var b=this.cm.getColumnTooltip(a);if(b){if(Ext.QuickTips.isEnabled()){return'ext:qtip="'+b+'"'}else{return'title="'+b+'"'}}return""},beforeUpdate:function(){this.grid.stopEditing(true)},updateHeaders:function(){this.innerHd.firstChild.innerHTML=this.renderHeaders();this.updateHeaderWidth(false)},updateHeaderWidth:function(c){var b=this.innerHd.firstChild,a=this.getTotalWidth();b.style.width=this.getOffsetWidth();b.firstChild.style.width=a;if(c!==false){this.mainBody.dom.style.width=a}},focusRow:function(a){this.focusCell(a,0,false)},focusCell:function(d,b,c){this.syncFocusEl(this.ensureVisible(d,b,c));var a=this.focusEl;if(Ext.isGecko){a.focus()}else{a.focus.defer(1,a)}},resolveCell:function(h,d,g){if(!Ext.isNumber(h)){h=h.rowIndex}if(!this.ds){return null}if(h<0||h>=this.ds.getCount()){return null}d=(d!==undefined?d:0);var c=this.getRow(h),b=this.cm,e=b.getColumnCount(),a;if(!(g===false&&d===0)){while(d<e&&b.isHidden(d)){d++}a=this.getCell(h,d)}return{row:c,cell:a}},getResolvedXY:function(b){if(!b){return null}var a=b.cell,c=b.row;if(a){return Ext.fly(a).getXY()}else{return[this.el.getX(),Ext.fly(c).getY()]}},syncFocusEl:function(d,a,c){var b=d;if(!Ext.isArray(b)){d=Math.min(d,Math.max(0,this.getRows().length-1));if(isNaN(d)){return}b=this.getResolvedXY(this.resolveCell(d,a,c))}this.focusEl.setXY(b||this.scroller.getXY())},ensureVisible:function(t,g,e){var r=this.resolveCell(t,g,e);if(!r||!r.row){return null}var k=r.row,h=r.cell,n=this.scroller.dom,d=k,s=0,o=this.el.dom;while(d&&d!=o){s+=d.offsetTop;d=d.offsetParent}s-=this.mainHd.dom.offsetHeight;o=parseInt(n.scrollTop,10);var q=s+k.offsetHeight,a=n.clientHeight,m=o+a;if(s<o){n.scrollTop=s}else{if(q>m){n.scrollTop=q-a}}if(e!==false){var l=parseInt(h.offsetLeft,10),j=l+h.offsetWidth,i=parseInt(n.scrollLeft,10),b=i+n.clientWidth;if(l<i){n.scrollLeft=l}else{if(j>b){n.scrollLeft=j-n.clientWidth}}}return this.getResolvedXY(r)},insertRows:function(a,i,e,h){var d=a.getCount()-1;if(!h&&i===0&&e>=d){this.fireEvent("beforerowsinserted",this,i,e);this.refresh();this.fireEvent("rowsinserted",this,i,e)}else{if(!h){this.fireEvent("beforerowsinserted",this,i,e)}var b=this.renderRows(i,e),g=this.getRow(i);if(g){if(i===0){Ext.fly(this.getRow(0)).removeClass(this.firstRowCls)}Ext.DomHelper.insertHtml("beforeBegin",g,b)}else{var c=this.getRow(d-1);if(c){Ext.fly(c).removeClass(this.lastRowCls)}Ext.DomHelper.insertHtml("beforeEnd",this.mainBody.dom,b)}if(!h){this.processRows(i);this.fireEvent("rowsinserted",this,i,e)}else{if(i===0||i>=d){Ext.fly(this.getRow(i)).addClass(i===0?this.firstRowCls:this.lastRowCls)}}}this.syncFocusEl(i)},deleteRows:function(a,c,b){if(a.getRowCount()<1){this.refresh()}else{this.fireEvent("beforerowsdeleted",this,c,b);this.removeRows(c,b);this.processRows(c);this.fireEvent("rowsdeleted",this,c,b)}},getColumnStyle:function(b,d){var a=this.cm,g=a.config,c=d?"":g[b].css||"",e=g[b].align;c+=String.format("width: {0};",this.getColumnWidth(b));if(a.isHidden(b)){c+="display: none; "}if(e){c+=String.format("text-align: {0};",e)}return c},getColumnWidth:function(b){var c=this.cm.getColumnWidth(b),a=this.borderWidth;if(Ext.isNumber(c)){if(Ext.isBorderBox){return c+"px"}else{return Math.max(c-a,0)+"px"}}else{return c}},getTotalWidth:function(){return this.cm.getTotalWidth()+"px"},fitColumns:function(g,j,h){var a=this.grid,l=this.cm,s=l.getTotalWidth(false),q=this.getGridInnerWidth(),r=q-s,c=[],o=0,n=0,u,d,p;if(q<20||r===0){return false}var e=l.getColumnCount(true),m=l.getColumnCount(false),b=e-(Ext.isNumber(h)?1:0);if(b===0){b=1;h=undefined}for(p=0;p<m;p++){if(!l.isFixed(p)&&p!==h){u=l.getColumnWidth(p);c.push(p,u);if(!l.isHidden(p)){o=p;n+=u}}}d=(q-l.getTotalWidth())/n;while(c.length){u=c.pop();p=c.pop();l.setColumnWidth(p,Math.max(a.minColumnWidth,Math.floor(u+u*d)),true)}s=l.getTotalWidth(false);if(s>q){var t=(b==e)?o:h,k=Math.max(1,l.getColumnWidth(t)-(s-q));l.setColumnWidth(t,k,true)}if(g!==true){this.updateAllColumnWidths()}return true},autoExpand:function(k){var a=this.grid,i=this.cm,e=this.getGridInnerWidth(),c=i.getTotalWidth(false),g=a.autoExpandColumn;if(!this.userResized&&g){if(e!=c){var j=i.getIndexById(g),b=i.getColumnWidth(j),h=e-c+b,d=Math.min(Math.max(h,a.autoExpandMin),a.autoExpandMax);if(b!=d){i.setColumnWidth(j,d,true);if(k!==true){this.updateColumnWidth(j,d)}}}}},getGridInnerWidth:function(){return this.grid.getGridEl().getWidth(true)-this.getScrollOffset()},getColumnData:function(){var e=[],c=this.cm,g=c.getColumnCount(),a=this.ds.fields,d,b;for(d=0;d<g;d++){b=c.getDataIndex(d);e[d]={name:Ext.isDefined(b)?b:(a.get(d)?a.get(d).name:undefined),renderer:c.getRenderer(d),scope:c.getRendererScope(d),id:c.getColumnId(d),style:this.getColumnStyle(d)}}return e},renderRows:function(i,c){var a=this.grid,g=a.store,j=a.stripeRows,e=a.colModel,h=e.getColumnCount(),d=g.getCount(),b;if(d<1){return""}i=i||0;c=Ext.isDefined(c)?c:d-1;b=g.getRange(i,c);return this.doRender(this.getColumnData(),b,g,i,h,j)},renderBody:function(){var a=this.renderRows()||"&#160;";return this.templates.body.apply({rows:a})},refreshRow:function(g){var l=this.ds,m=this.cm.getColumnCount(),c=this.getColumnData(),n=m-1,p=["x-grid3-row"],e={tstyle:String.format("width: {0};",this.getTotalWidth())},a=[],k=this.templates.cell,j,q,b,o,h,d;if(Ext.isNumber(g)){j=g;g=l.getAt(j)}else{j=l.indexOf(g)}if(!g||j<0){return}for(d=0;d<m;d++){b=c[d];if(d==0){h="x-grid3-cell-first"}else{h=(d==n)?"x-grid3-cell-last ":""}o={id:b.id,style:b.style,css:h,attr:"",cellAttr:""};o.value=b.renderer.call(b.scope,g.data[b.name],o,g,j,d,l);if(Ext.isEmpty(o.value)){o.value="&#160;"}if(this.markDirty&&g.dirty&&typeof g.modified[b.name]!="undefined"){o.css+=" x-grid3-dirty-cell"}a[d]=k.apply(o)}q=this.getRow(j);q.className="";if(this.grid.stripeRows&&((j+1)%2===0)){p.push("x-grid3-row-alt")}if(this.getRowClass){e.cols=m;p.push(this.getRowClass(g,j,e,l))}this.fly(q).addClass(p).setStyle(e.tstyle);e.cells=a.join("");q.innerHTML=this.templates.rowInner.apply(e);this.fireEvent("rowupdated",this,j,g)},refresh:function(b){this.fireEvent("beforerefresh",this);this.grid.stopEditing(true);var a=this.renderBody();this.mainBody.update(a).setWidth(this.getTotalWidth());if(b===true){this.updateHeaders();this.updateHeaderSortState()}this.processRows(0,true);this.layout();this.applyEmptyText();this.fireEvent("refresh",this)},applyEmptyText:function(){if(this.emptyText&&!this.hasRows()){this.mainBody.update('<div class="x-grid-empty">'+this.emptyText+"</div>")}},updateHeaderSortState:function(){var b=this.ds.getSortState();if(!b){return}if(!this.sortState||(this.sortState.field!=b.field||this.sortState.direction!=b.direction)){this.grid.fireEvent("sortchange",this.grid,b)}this.sortState=b;var c=this.cm.findColumnIndex(b.field);if(c!=-1){var a=b.direction;this.updateSortIcon(c,a)}},clearHeaderSortState:function(){if(!this.sortState){return}this.grid.fireEvent("sortchange",this.grid,null);this.mainHd.select("td").removeClass(this.sortClasses);delete this.sortState},destroy:function(){var j=this,a=j.grid,d=a.getGridEl(),i=j.dragZone,g=j.splitZone,h=j.columnDrag,e=j.columnDrop,k=j.scrollToTopTask,c,b;if(k&&k.cancel){k.cancel()}Ext.destroyMembers(j,"colMenu","hmenu");j.initData(null,null);j.purgeListeners();Ext.fly(j.innerHd).un("click",j.handleHdDown,j);if(a.enableColumnMove){c=h.dragData;b=h.proxy;Ext.destroy(h.el,b.ghost,b.el,e.el,e.proxyTop,e.proxyBottom,c.ddel,c.header);if(b.anim){Ext.destroy(b.anim)}delete b.ghost;delete c.ddel;delete c.header;h.destroy();delete Ext.dd.DDM.locationCache[h.id];delete h._domRef;delete e.proxyTop;delete e.proxyBottom;e.destroy();delete Ext.dd.DDM.locationCache["gridHeader"+d.id];delete e._domRef;delete Ext.dd.DDM.ids[e.ddGroup]}if(g){g.destroy();delete g._domRef;delete Ext.dd.DDM.ids["gridSplitters"+d.id]}Ext.fly(j.innerHd).removeAllListeners();Ext.removeNode(j.innerHd);delete j.innerHd;Ext.destroy(j.el,j.mainWrap,j.mainHd,j.scroller,j.mainBody,j.focusEl,j.resizeMarker,j.resizeProxy,j.activeHdBtn,j._flyweight,i,g);delete a.container;if(i){i.destroy()}Ext.dd.DDM.currentTarget=null;delete Ext.dd.DDM.locationCache[d.id];Ext.EventManager.removeResizeListener(j.onWindowResize,j)},onDenyColumnHide:function(){},render:function(){if(this.autoFill){var a=this.grid.ownerCt;if(a&&a.getLayout()){a.on("afterlayout",function(){this.fitColumns(true,true);this.updateHeaders();this.updateHeaderSortState()},this,{single:true})}}else{if(this.forceFit){this.fitColumns(true,false)}else{if(this.grid.autoExpandColumn){this.autoExpand(true)}}}this.grid.getGridEl().dom.innerHTML=this.renderUI();this.afterRenderUI()},initData:function(a,e){var b=this;if(b.ds){var d=b.ds;d.un("add",b.onAdd,b);d.un("load",b.onLoad,b);d.un("clear",b.onClear,b);d.un("remove",b.onRemove,b);d.un("update",b.onUpdate,b);d.un("datachanged",b.onDataChange,b);if(d!==a&&d.autoDestroy){d.destroy()}}if(a){a.on({scope:b,load:b.onLoad,add:b.onAdd,remove:b.onRemove,update:b.onUpdate,clear:b.onClear,datachanged:b.onDataChange})}if(b.cm){var c=b.cm;c.un("configchange",b.onColConfigChange,b);c.un("widthchange",b.onColWidthChange,b);c.un("headerchange",b.onHeaderChange,b);c.un("hiddenchange",b.onHiddenChange,b);c.un("columnmoved",b.onColumnMove,b)}if(e){delete b.lastViewWidth;e.on({scope:b,configchange:b.onColConfigChange,widthchange:b.onColWidthChange,headerchange:b.onHeaderChange,hiddenchange:b.onHiddenChange,columnmoved:b.onColumnMove})}b.ds=a;b.cm=e},onDataChange:function(){this.refresh(true);this.updateHeaderSortState();this.syncFocusEl(0)},onClear:function(){this.refresh();this.syncFocusEl(0)},onUpdate:function(b,a){this.refreshRow(a)},onAdd:function(b,a,c){this.insertRows(b,c,c+(a.length-1))},onRemove:function(b,a,c,d){if(d!==true){this.fireEvent("beforerowremoved",this,c,a)}this.removeRow(c);if(d!==true){this.processRows(c);this.applyEmptyText();this.fireEvent("rowremoved",this,c,a)}},onLoad:function(){if(Ext.isGecko){if(!this.scrollToTopTask){this.scrollToTopTask=new Ext.util.DelayedTask(this.scrollToTop,this)}this.scrollToTopTask.delay(1)}else{this.scrollToTop()}},onColWidthChange:function(a,b,c){this.updateColumnWidth(b,c)},onHeaderChange:function(a,b,c){this.updateHeaders()},onHiddenChange:function(a,b,c){this.updateColumnHidden(b,c)},onColumnMove:function(a,c,b){this.indexMap=null;this.refresh(true);this.restoreScroll(this.getScrollState());this.afterMove(b);this.grid.fireEvent("columnmove",c,b)},onColConfigChange:function(){delete this.lastViewWidth;this.indexMap=null;this.refresh(true)},initUI:function(a){a.on("headerclick",this.onHeaderClick,this)},initEvents:Ext.emptyFn,onHeaderClick:function(b,a){if(this.headersDisabled||!this.cm.isSortable(a)){return}b.stopEditing(true);b.store.sort(this.cm.getDataIndex(a))},onRowOver:function(b,a){var c=this.findRowIndex(a);if(c!==false){this.addRowClass(c,this.rowOverCls)}},onRowOut:function(b,a){var c=this.findRowIndex(a);if(c!==false&&!b.within(this.getRow(c),true)){this.removeRowClass(c,this.rowOverCls)}},onRowSelect:function(a){this.addRowClass(a,this.selectedRowClass)},onRowDeselect:function(a){this.removeRowClass(a,this.selectedRowClass)},onCellSelect:function(c,b){var a=this.getCell(c,b);if(a){this.fly(a).addClass("x-grid3-cell-selected")}},onCellDeselect:function(c,b){var a=this.getCell(c,b);if(a){this.fly(a).removeClass("x-grid3-cell-selected")}},handleWheel:function(a){a.stopPropagation()},onColumnSplitterMoved:function(a,b){this.userResized=true;this.grid.colModel.setColumnWidth(a,b,true);if(this.forceFit){this.fitColumns(true,false,a);this.updateAllColumnWidths()}else{this.updateColumnWidth(a,b);this.syncHeaderScroll()}this.grid.fireEvent("columnresize",a,b)},beforeColMenuShow:function(){var b=this.cm,d=b.getColumnCount(),a=this.colMenu,c;a.removeAll();for(c=0;c<d;c++){if(b.config[c].hideable!==false){a.add(new Ext.menu.CheckItem({text:b.getColumnHeader(c),itemId:"col-"+b.getColumnId(c),checked:!b.isHidden(c),disabled:b.config[c].hideable===false,hideOnClick:false}))}}},handleHdMenuClick:function(c){var a=this.ds,b=this.cm.getDataIndex(this.hdCtxIndex);switch(c.getItemId()){case"asc":a.sort(b,"ASC");break;case"desc":a.sort(b,"DESC");break;default:this.handleHdMenuClickDefault(c)}return true},handleHdMenuClickDefault:function(c){var b=this.cm,d=c.getItemId(),a=b.getIndexById(d.substr(4));if(a!=-1){if(c.checked&&b.getColumnsBy(this.isHideableColumn,this).length<=1){this.onDenyColumnHide();return}b.setHidden(a,c.checked)}},handleHdDown:function(i,j){if(Ext.fly(j).hasClass("x-grid3-hd-btn")){i.stopEvent();var k=this.cm,g=this.findHeaderCell(j),h=this.getCellIndex(g),d=k.isSortable(h),c=this.hmenu,b=c.items,a=this.headerMenuOpenCls,l;this.hdCtxIndex=h;Ext.fly(g).addClass(a);if(this.hideSortIcons){b.get("asc").setVisible(d);b.get("desc").setVisible(d);l=b.get("sortSep");if(l){l.setVisible(d)}}else{b.get("asc").setDisabled(!d);b.get("desc").setDisabled(!d)}c.on("hide",function(){Ext.fly(g).removeClass(a)},this,{single:true});c.show(j,"tl-bl?")}},handleHdMove:function(k){var i=this.findHeaderCell(this.activeHdRef);if(i&&!this.headersDisabled){var l=this.splitHandleWidth||5,j=this.activeHdRegion,p=i.style,m=this.cm,o="",g=k.getPageX();if(this.grid.enableColumnResize!==false){var a=this.activeHdIndex,b=this.getPreviousVisible(a),n=m.isResizable(a),c=b&&m.isResizable(b),d=g-j.left<=l,h=j.right-g<=(!this.activeHdBtn?l:2);if(d&&c){o=Ext.isAir?"move":Ext.isWebKit?"e-resize":"col-resize"}else{if(h&&n){o=Ext.isAir?"move":Ext.isWebKit?"w-resize":"col-resize"}}}p.cursor=o}},getPreviousVisible:function(a){while(a>0){if(!this.cm.isHidden(a-1)){return a}a--}return undefined},handleHdOver:function(c,b){var d=this.findHeaderCell(b);if(d&&!this.headersDisabled){var a=this.fly(d);this.activeHdRef=b;this.activeHdIndex=this.getCellIndex(d);this.activeHdRegion=a.getRegion();if(!this.isMenuDisabled(this.activeHdIndex,a)){a.addClass("x-grid3-hd-over");this.activeHdBtn=a.child(".x-grid3-hd-btn");if(this.activeHdBtn){this.activeHdBtn.dom.style.height=(d.firstChild.offsetHeight-1)+"px"}}}},handleHdOut:function(b,a){var c=this.findHeaderCell(a);if(c&&(!Ext.isIE9m||!b.within(c,true))){this.activeHdRef=null;this.fly(c).removeClass("x-grid3-hd-over");c.style.cursor=""}},isMenuDisabled:function(a,b){return this.cm.isMenuDisabled(a)},hasRows:function(){var a=this.mainBody.dom.firstChild;return a&&a.nodeType==1&&a.className!="x-grid-empty"},isHideableColumn:function(a){return !a.hidden},bind:function(a,b){this.initData(a,b)}});Ext.grid.GridView.SplitDragZone=Ext.extend(Ext.dd.DDProxy,{constructor:function(a,b){this.grid=a;this.view=a.getView();this.marker=this.view.resizeMarker;this.proxy=this.view.resizeProxy;Ext.grid.GridView.SplitDragZone.superclass.constructor.call(this,b,"gridSplitters"+this.grid.getGridEl().id,{dragElId:Ext.id(this.proxy.dom),resizeFrame:false});this.scroll=false;this.hw=this.view.splitHandleWidth||5},b4StartDrag:function(a,e){this.dragHeadersDisabled=this.view.headersDisabled;this.view.headersDisabled=true;var d=this.view.mainWrap.getHeight();this.marker.setHeight(d);this.marker.show();this.marker.alignTo(this.view.getHeaderCell(this.cellIndex),"tl-tl",[-2,0]);this.proxy.setHeight(d);var b=this.cm.getColumnWidth(this.cellIndex),c=Math.max(b-this.grid.minColumnWidth,0);this.resetConstraints();this.setXConstraint(c,1000);this.setYConstraint(0,0);this.minX=a-c;this.maxX=a+1000;this.startPos=a;Ext.dd.DDProxy.prototype.b4StartDrag.call(this,a,e)},allowHeaderDrag:function(a){return true},handleMouseDown:function(a){var h=this.view.findHeaderCell(a.getTarget());if(h&&this.allowHeaderDrag(a)){var k=this.view.fly(h).getXY(),c=k[0],i=a.getXY(),b=i[0],g=h.offsetWidth,d=false;if((b-c)<=this.hw){d=-1}else{if((c+g)-b<=this.hw){d=0}}if(d!==false){this.cm=this.grid.colModel;var j=this.view.getCellIndex(h);if(d==-1){if(j+d<0){return}while(this.cm.isHidden(j+d)){--d;if(j+d<0){return}}}this.cellIndex=j+d;this.split=h.dom;if(this.cm.isResizable(this.cellIndex)&&!this.cm.isFixed(this.cellIndex)){Ext.grid.GridView.SplitDragZone.superclass.handleMouseDown.apply(this,arguments)}}else{if(this.view.columnDrag){this.view.columnDrag.callHandleMouseDown(a)}}}},endDrag:function(g){this.marker.hide();var a=this.view,c=Math.max(this.minX,g.getPageX()),d=c-this.startPos,b=this.dragHeadersDisabled;a.onColumnSplitterMoved(this.cellIndex,this.cm.getColumnWidth(this.cellIndex)+d);setTimeout(function(){a.headersDisabled=b},50)},autoOffset:function(){this.setDelta(0,0)}});Ext.grid.PivotGridView=Ext.extend(Ext.grid.GridView,{colHeaderCellCls:"grid-hd-group-cell",title:"",getColumnHeaders:function(){return this.grid.topAxis.buildHeaders()},getRowHeaders:function(){return this.grid.leftAxis.buildHeaders()},renderRows:function(a,t){var b=this.grid,o=b.extractData(),p=o.length,g=this.templates,s=b.renderer,h=typeof s=="function",w=this.getCellCls,n=typeof w=="function",d=g.cell,x=g.row,k=[],q={},c="width:"+this.getGridInnerWidth()+"px;",l,r,e,v,m;a=a||0;t=Ext.isDefined(t)?t:p-1;for(v=0;v<p;v++){m=o[v];r=m.length;l=[];for(var u=0;u<r;u++){q.id=v+"-"+u;q.css=u===0?"x-grid3-cell-first ":(u==(r-1)?"x-grid3-cell-last ":"");q.attr=q.cellAttr="";q.value=m[u];if(Ext.isEmpty(q.value)){q.value="&#160;"}if(h){q.value=s(q.value)}if(n){q.css+=w(q.value)+" "}l[l.length]=d.apply(q)}k[k.length]=x.apply({tstyle:c,cols:r,cells:l.join(""),alt:""})}return k.join("")},masterTpl:new Ext.Template('<div class="x-grid3 x-pivotgrid" hidefocus="true">','<div class="x-grid3-viewport">','<div class="x-grid3-header">','<div class="x-grid3-header-title"><span>{title}</span></div>','<div class="x-grid3-header-inner">','<div class="x-grid3-header-offset" style="{ostyle}"></div>',"</div>",'<div class="x-clear"></div>',"</div>",'<div class="x-grid3-scroller">','<div class="x-grid3-row-headers"></div>','<div class="x-grid3-body" style="{bstyle}">{body}</div>','<a href="#" class="x-grid3-focus" tabIndex="-1"></a>',"</div>","</div>",'<div class="x-grid3-resize-marker">&#160;</div>','<div class="x-grid3-resize-proxy">&#160;</div>',"</div>"),initTemplates:function(){Ext.grid.PivotGridView.superclass.initTemplates.apply(this,arguments);var a=this.templates||{};if(!a.gcell){a.gcell=new Ext.XTemplate('<td class="x-grid3-hd x-grid3-gcell x-grid3-td-{id} ux-grid-hd-group-row-{row} '+this.colHeaderCellCls+'" style="{style}">','<div {tooltip} class="x-grid3-hd-inner x-grid3-hd-{id} x-unselectable" unselectable="on" style="{istyle}">',this.grid.enableHdMenu?'<a class="x-grid3-hd-btn" href="#"></a>':"","{value}","</div>","</td>")}this.templates=a;this.hrowRe=new RegExp("ux-grid-hd-group-row-(\\d+)","")},initElements:function(){Ext.grid.PivotGridView.superclass.initElements.apply(this,arguments);this.rowHeadersEl=new Ext.Element(this.scroller.child("div.x-grid3-row-headers"));this.headerTitleEl=new Ext.Element(this.mainHd.child("div.x-grid3-header-title"))},getGridInnerWidth:function(){var a=Ext.grid.PivotGridView.superclass.getGridInnerWidth.apply(this,arguments);return a-this.getTotalRowHeaderWidth()},getTotalRowHeaderWidth:function(){var d=this.getRowHeaders(),c=d.length,b=0,a;for(a=0;a<c;a++){b+=d[a].width}return b},getTotalColumnHeaderHeight:function(){return this.getColumnHeaders().length*21},getCellIndex:function(b){if(b){var a=b.className.match(this.colRe),c;if(a&&(c=a[1])){return parseInt(c.split("-")[1],10)}}return false},renderUI:function(){var b=this.templates,a=this.getGridInnerWidth();return b.master.apply({body:b.body.apply({rows:"&#160;"}),ostyle:"width:"+a+"px",bstyle:"width:"+a+"px"})},onLayout:function(b,a){Ext.grid.PivotGridView.superclass.onLayout.apply(this,arguments);var b=this.getGridInnerWidth();this.resizeColumnHeaders(b);this.resizeAllRows(b)},refresh:function(b){this.fireEvent("beforerefresh",this);this.grid.stopEditing(true);var a=this.renderBody();this.mainBody.update(a).setWidth(this.getGridInnerWidth());if(b===true){this.updateHeaders();this.updateHeaderSortState()}this.processRows(0,true);this.layout();this.applyEmptyText();this.fireEvent("refresh",this)},renderHeaders:Ext.emptyFn,fitColumns:Ext.emptyFn,resizeColumnHeaders:function(b){var a=this.grid.topAxis;if(a.rendered){a.el.setWidth(b)}},resizeRowHeaders:function(){var a=this.getTotalRowHeaderWidth(),b=String.format("margin-left: {0}px;",a);this.rowHeadersEl.setWidth(a);this.mainBody.applyStyles(b);Ext.fly(this.innerHd).applyStyles(b);this.headerTitleEl.setWidth(a);this.headerTitleEl.setHeight(this.getTotalColumnHeaderHeight())},resizeAllRows:function(b){var d=this.getRows(),c=d.length,a;for(a=0;a<c;a++){Ext.fly(d[a]).setWidth(b);Ext.fly(d[a]).child("table").setWidth(b)}},updateHeaders:function(){this.renderGroupRowHeaders();this.renderGroupColumnHeaders()},renderGroupRowHeaders:function(){var a=this.grid.leftAxis;this.resizeRowHeaders();a.rendered=false;a.render(this.rowHeadersEl);this.setTitle(this.title)},setTitle:function(a){this.headerTitleEl.child("span").dom.innerHTML=a},renderGroupColumnHeaders:function(){var a=this.grid.topAxis;a.rendered=false;a.render(this.innerHd.firstChild)},isMenuDisabled:function(a,b){return true}});Ext.grid.PivotAxis=Ext.extend(Ext.Component,{orientation:"horizontal",defaultHeaderWidth:80,paddingWidth:7,setDimensions:function(a){this.dimensions=a},onRender:function(b,a){var c=this.orientation=="horizontal"?this.renderHorizontalRows():this.renderVerticalRows();this.el=Ext.DomHelper.overwrite(b.dom,{tag:"table",cn:c},true)},renderHorizontalRows:function(){var k=this.buildHeaders(),a=k.length,g=[],c,h,e,d,b;for(d=0;d<a;d++){c=[];h=k[d].items;e=h.length;for(b=0;b<e;b++){c.push({tag:"td",html:h[b].header,colspan:h[b].span})}g[d]={tag:"tr",cn:c}}return g},renderVerticalRows:function(){var b=this.buildHeaders(),k=b.length,a=[],m=[],h,c,l,g,e,d;for(e=0;e<k;e++){c=b[e];g=c.width||80;h=c.items.length;for(d=0;d<h;d++){l=c.items[d];a[l.start]=a[l.start]||[];a[l.start].push({tag:"td",html:l.header,rowspan:l.span,width:Ext.isBorderBox?g:g-this.paddingWidth})}}h=a.length;for(e=0;e<h;e++){m[e]={tag:"tr",cn:a[e]}}return m},getTuples:function(){var b=new Ext.data.Store({});b.data=this.store.data.clone();b.fields=this.store.fields;var l=[],a=this.dimensions,c=a.length,j;for(j=0;j<c;j++){l.push({field:a[j].dataIndex,direction:a[j].direction||"ASC"})}b.sort(l);var e=b.data.items,n=[],k=[],o,h,d,g,m;c=e.length;for(j=0;j<c;j++){d=this.getRecordInfo(e[j]);g=d.data;h="";for(m in g){h+=g[m]+"---"}if(n.indexOf(h)==-1){n.push(h);k.push(d)}}b.destroy();return k},getRecordInfo:function(a){var e=this.dimensions,d=e.length,h={},j,c,b;for(b=0;b<d;b++){j=e[b];c=j.dataIndex;h[c]=a.get(c)}var g=function(i){return function(k){for(var l in i){if(k.get(l)!=i[l]){return false}}return true}};return{data:h,matcher:g(h)}},buildHeaders:function(){var l=this.getTuples(),m=l.length,a=this.dimensions,e,r=a.length,c=[],o,s,n,q,p,b,k,h,g,d;for(g=0;g<r;g++){e=a[g];s=[];p=0;b=0;for(d=0;d<m;d++){o=l[d];k=d==(m-1);n=o.data[e.dataIndex];h=q!=undefined&&q!=n;if(g>0&&d>0){h=h||o.data[a[g-1].dataIndex]!=l[d-1].data[a[g-1].dataIndex]}if(h){s.push({header:q,span:p,start:b});b+=p;p=0}if(k){s.push({header:n,span:p+1,start:b});b+=p;p=0}q=n;p++}c.push({items:s,width:e.width||this.defaultHeaderWidth});q=undefined}return c}});Ext.grid.HeaderDragZone=Ext.extend(Ext.dd.DragZone,{maxDragWidth:120,constructor:function(a,c,b){this.grid=a;this.view=a.getView();this.ddGroup="gridHeader"+this.grid.getGridEl().id;Ext.grid.HeaderDragZone.superclass.constructor.call(this,c);if(b){this.setHandleElId(Ext.id(c));this.setOuterHandleElId(Ext.id(b))}this.scroll=false},getDragData:function(c){var a=Ext.lib.Event.getTarget(c),b=this.view.findHeaderCell(a);if(b){return{ddel:b.firstChild,header:b}}return false},onInitDrag:function(a){this.dragHeadersDisabled=this.view.headersDisabled;this.view.headersDisabled=true;var b=this.dragData.ddel.cloneNode(true);b.id=Ext.id();b.style.width=Math.min(this.dragData.header.offsetWidth,this.maxDragWidth)+"px";this.proxy.update(b);return true},afterValidDrop:function(){this.completeDrop()},afterInvalidDrop:function(){this.completeDrop()},completeDrop:function(){var a=this.view,b=this.dragHeadersDisabled;setTimeout(function(){a.headersDisabled=b},50)}});Ext.grid.HeaderDropZone=Ext.extend(Ext.dd.DropZone,{proxyOffsets:[-4,-9],fly:Ext.Element.fly,constructor:function(a,c,b){this.grid=a;this.view=a.getView();this.proxyTop=Ext.DomHelper.append(document.body,{cls:"col-move-top",html:"&#160;"},true);this.proxyBottom=Ext.DomHelper.append(document.body,{cls:"col-move-bottom",html:"&#160;"},true);this.proxyTop.hide=this.proxyBottom.hide=function(){this.setLeftTop(-100,-100);this.setStyle("visibility","hidden")};this.ddGroup="gridHeader"+this.grid.getGridEl().id;Ext.grid.HeaderDropZone.superclass.constructor.call(this,a.getGridEl().dom)},getTargetFromEvent:function(c){var a=Ext.lib.Event.getTarget(c),b=this.view.findCellIndex(a);if(b!==false){return this.view.getHeaderCell(b)}},nextVisible:function(c){var b=this.view,a=this.grid.colModel;c=c.nextSibling;while(c){if(!a.isHidden(b.getCellIndex(c))){return c}c=c.nextSibling}return null},prevVisible:function(c){var b=this.view,a=this.grid.colModel;c=c.prevSibling;while(c){if(!a.isHidden(b.getCellIndex(c))){return c}c=c.prevSibling}return null},positionIndicator:function(d,k,j){var a=Ext.lib.Event.getPageX(j),g=Ext.lib.Dom.getRegion(k.firstChild),c,i,b=g.top+this.proxyOffsets[1];if((g.right-a)<=(g.right-g.left)/2){c=g.right+this.view.borderWidth;i="after"}else{c=g.left;i="before"}if(this.grid.colModel.isFixed(this.view.getCellIndex(k))){return false}c+=this.proxyOffsets[0];this.proxyTop.setLeftTop(c,b);this.proxyTop.show();if(!this.bottomOffset){this.bottomOffset=this.view.mainHd.getHeight()}this.proxyBottom.setLeftTop(c,b+this.proxyTop.dom.offsetHeight+this.bottomOffset);this.proxyBottom.show();return i},onNodeEnter:function(d,a,c,b){if(b.header!=d){this.positionIndicator(b.header,d,c)}},onNodeOver:function(g,b,d,c){var a=false;if(c.header!=g){a=this.positionIndicator(c.header,g,d)}if(!a){this.proxyTop.hide();this.proxyBottom.hide()}return a?this.dropAllowed:this.dropNotAllowed},onNodeOut:function(d,a,c,b){this.proxyTop.hide();this.proxyBottom.hide()},onNodeDrop:function(b,m,g,c){var d=c.header;if(d!=b){var k=this.grid.colModel,j=Ext.lib.Event.getPageX(g),a=Ext.lib.Dom.getRegion(b.firstChild),o=(a.right-j)<=((a.right-a.left)/2)?"after":"before",i=this.view.getCellIndex(d),l=this.view.getCellIndex(b);if(o=="after"){l++}if(i<l){l--}k.moveColumn(i,l);return true}return false}});Ext.grid.GridView.ColumnDragZone=Ext.extend(Ext.grid.HeaderDragZone,{constructor:function(a,b){Ext.grid.GridView.ColumnDragZone.superclass.constructor.call(this,a,b,null);this.proxy.el.addClass("x-grid3-col-dd")},handleMouseDown:function(a){},callHandleMouseDown:function(a){Ext.grid.GridView.ColumnDragZone.superclass.handleMouseDown.call(this,a)}});Ext.grid.SplitDragZone=Ext.extend(Ext.dd.DDProxy,{fly:Ext.Element.fly,constructor:function(a,c,b){this.grid=a;this.view=a.getView();this.proxy=this.view.resizeProxy;Ext.grid.SplitDragZone.superclass.constructor.call(this,c,"gridSplitters"+this.grid.getGridEl().id,{dragElId:Ext.id(this.proxy.dom),resizeFrame:false});this.setHandleElId(Ext.id(c));this.setOuterHandleElId(Ext.id(b));this.scroll=false},b4StartDrag:function(a,d){this.view.headersDisabled=true;this.proxy.setHeight(this.view.mainWrap.getHeight());var b=this.cm.getColumnWidth(this.cellIndex);var c=Math.max(b-this.grid.minColumnWidth,0);this.resetConstraints();this.setXConstraint(c,1000);this.setYConstraint(0,0);this.minX=a-c;this.maxX=a+1000;this.startPos=a;Ext.dd.DDProxy.prototype.b4StartDrag.call(this,a,d)},handleMouseDown:function(c){var b=Ext.EventObject.setEvent(c);var a=this.fly(b.getTarget());if(a.hasClass("x-grid-split")){this.cellIndex=this.view.getCellIndex(a.dom);this.split=a.dom;this.cm=this.grid.colModel;if(this.cm.isResizable(this.cellIndex)&&!this.cm.isFixed(this.cellIndex)){Ext.grid.SplitDragZone.superclass.handleMouseDown.apply(this,arguments)}}},endDrag:function(c){this.view.headersDisabled=false;var a=Math.max(this.minX,Ext.lib.Event.getPageX(c));var b=a-this.startPos;this.view.onColumnSplitterMoved(this.cellIndex,this.cm.getColumnWidth(this.cellIndex)+b)},autoOffset:function(){this.setDelta(0,0)}});Ext.grid.GridDragZone=function(b,a){this.view=b.getView();Ext.grid.GridDragZone.superclass.constructor.call(this,this.view.mainBody.dom,a);this.scroll=false;this.grid=b;this.ddel=document.createElement("div");this.ddel.className="x-grid-dd-wrap";this.preventDefault=true};Ext.extend(Ext.grid.GridDragZone,Ext.dd.DragZone,{ddGroup:"GridDD",getDragData:function(g){var c=Ext.lib.Event.getTarget(g),i,h=this.view.findRowIndex(c),b,a,d;if(h!==false){i=this.grid.selModel;if(i.getSelectedCell){b=this.view.findCellIndex(c);a=i.getSelectedCell();if(!a||a[0]!==h||a[1]!==b){i.handleMouseDown(this.grid,h,b,g)}if(this.grid.dragCell){d=i.getSelectedCell();if(!this.grid.hasOwnProperty("ddText")){this.grid.ddText="{0} selected cell{1}"}}else{d=[this.grid.store.getAt(h)]}}else{if(!i.isSelected(h)||g.hasModifier()){i.handleMouseDown(this.grid,h,g)}d=i.getSelections()}return{grid:this.grid,ddel:this.ddel,rowIndex:h,selections:d}}return false},onInitDrag:function(b){var a=this.dragData;this.ddel.innerHTML=this.grid.getDragDropText();this.proxy.update(this.ddel)},afterRepair:function(){this.dragging=false},getRepairXY:function(b,a){return false},onEndDrag:function(a,b){},onValidDrop:function(a,b,c){this.hideProxy()},beforeInvalidDrop:function(a,b){}});Ext.grid.ColumnModel=Ext.extend(Ext.util.Observable,{defaultWidth:100,defaultSortable:false,constructor:function(a){if(a.columns){Ext.apply(this,a);this.setConfig(a.columns,true)}else{this.setConfig(a,true)}this.addEvents("widthchange","headerchange","hiddenchange","columnmoved","configchange");Ext.grid.ColumnModel.superclass.constructor.call(this)},getColumnId:function(a){return this.config[a].id},getColumnAt:function(a){return this.config[a]},setConfig:function(d,b){var e,h,a;if(!b){delete this.totalWidth;for(e=0,a=this.config.length;e<a;e++){h=this.config[e];if(h.setEditor){h.setEditor(null)}}}this.defaults=Ext.apply({width:this.defaultWidth,sortable:this.defaultSortable},this.defaults);this.config=d;this.lookup={};for(e=0,a=d.length;e<a;e++){h=Ext.applyIf(d[e],this.defaults);if(Ext.isEmpty(h.id)){h.id=e}if(!h.isColumn){var g=Ext.grid.Column.types[h.xtype||"gridcolumn"];h=new g(h);d[e]=h}this.lookup[h.id]=h}if(!b){this.fireEvent("configchange",this)}},getColumnById:function(a){return this.lookup[a]},getIndexById:function(c){for(var b=0,a=this.config.length;b<a;b++){if(this.config[b].id==c){return b}}return -1},moveColumn:function(e,b){var a=this.config,d=a[e];a.splice(e,1);a.splice(b,0,d);this.dataMap=null;this.fireEvent("columnmoved",this,e,b)},getColumnCount:function(b){var d=this.config.length,e=0,a;if(b===true){for(a=0;a<d;a++){if(!this.isHidden(a)){e++}}return e}return d},getColumnsBy:function(g,e){var b=this.config,h=b.length,a=[],d,j;for(d=0;d<h;d++){j=b[d];if(g.call(e||this,j,d)===true){a[a.length]=j}}return a},isSortable:function(a){return !!this.config[a].sortable},isMenuDisabled:function(a){return !!this.config[a].menuDisabled},getRenderer:function(a){return this.config[a].renderer||Ext.grid.ColumnModel.defaultRenderer},getRendererScope:function(a){return this.config[a].scope},setRenderer:function(a,b){this.config[a].renderer=b},getColumnWidth:function(a){var b=this.config[a].width;if(typeof b!="number"){b=this.defaultWidth}return b},setColumnWidth:function(b,c,a){this.config[b].width=c;this.totalWidth=null;if(!a){this.fireEvent("widthchange",this,b,c)}},getTotalWidth:function(b){if(!this.totalWidth){this.totalWidth=0;for(var c=0,a=this.config.length;c<a;c++){if(b||!this.isHidden(c)){this.totalWidth+=this.getColumnWidth(c)}}}return this.totalWidth},getColumnHeader:function(a){return this.config[a].header},setColumnHeader:function(a,b){this.config[a].header=b;this.fireEvent("headerchange",this,a,b)},getColumnTooltip:function(a){return this.config[a].tooltip},setColumnTooltip:function(a,b){this.config[a].tooltip=b},getDataIndex:function(a){return this.config[a].dataIndex},setDataIndex:function(a,b){this.config[a].dataIndex=b},findColumnIndex:function(d){var e=this.config;for(var b=0,a=e.length;b<a;b++){if(e[b].dataIndex==d){return b}}return -1},isCellEditable:function(b,e){var d=this.config[b],a=d.editable;return !!(a||(!Ext.isDefined(a)&&d.editor))},getCellEditor:function(a,b){return this.config[a].getCellEditor(b)},setEditable:function(a,b){this.config[a].editable=b},isHidden:function(a){return !!this.config[a].hidden},isFixed:function(a){return !!this.config[a].fixed},isResizable:function(a){return a>=0&&this.config[a].resizable!==false&&this.config[a].fixed!==true},setHidden:function(a,b){var d=this.config[a];if(d.hidden!==b){d.hidden=b;this.totalWidth=null;this.fireEvent("hiddenchange",this,a,b)}},setEditor:function(a,b){this.config[a].setEditor(b)},destroy:function(){var b=this.config.length,a=0;for(;a<b;a++){this.config[a].destroy()}delete this.config;delete this.lookup;this.purgeListeners()},setState:function(a,b){b=Ext.applyIf(b,this.defaults);Ext.apply(this.config[a],b)}});Ext.grid.ColumnModel.defaultRenderer=function(a){if(typeof a=="string"&&a.length<1){return"&#160;"}return a};Ext.grid.AbstractSelectionModel=Ext.extend(Ext.util.Observable,{constructor:function(){this.locked=false;Ext.grid.AbstractSelectionModel.superclass.constructor.call(this)},init:function(a){this.grid=a;if(this.lockOnInit){delete this.lockOnInit;this.locked=false;this.lock()}this.initEvents()},lock:function(){if(!this.locked){this.locked=true;var a=this.grid;if(a){a.getView().on({scope:this,beforerefresh:this.sortUnLock,refresh:this.sortLock})}else{this.lockOnInit=true}}},sortLock:function(){this.locked=true},sortUnLock:function(){this.locked=false},unlock:function(){if(this.locked){this.locked=false;var a=this.grid,b;if(a){b=a.getView();b.un("beforerefresh",this.sortUnLock,this);b.un("refresh",this.sortLock,this)}else{delete this.lockOnInit}}},isLocked:function(){return this.locked},destroy:function(){this.unlock();this.purgeListeners()}});Ext.grid.RowSelectionModel=Ext.extend(Ext.grid.AbstractSelectionModel,{singleSelect:false,constructor:function(a){Ext.apply(this,a);this.selections=new Ext.util.MixedCollection(false,function(b){return b.id});this.last=false;this.lastActive=false;this.addEvents("selectionchange","beforerowselect","rowselect","rowdeselect");Ext.grid.RowSelectionModel.superclass.constructor.call(this)},initEvents:function(){if(!this.grid.enableDragDrop&&!this.grid.enableDrag){this.grid.on("rowmousedown",this.handleMouseDown,this)}this.rowNav=new Ext.KeyNav(this.grid.getGridEl(),{up:this.onKeyPress,down:this.onKeyPress,scope:this});this.grid.getView().on({scope:this,refresh:this.onRefresh,rowupdated:this.onRowUpdated,rowremoved:this.onRemove})},onKeyPress:function(g,b){var a=b=="up",h=a?"selectPrevious":"selectNext",d=a?-1:1,c;if(!g.shiftKey||this.singleSelect){this[h](false)}else{if(this.last!==false&&this.lastActive!==false){c=this.last;this.selectRange(this.last,this.lastActive+d);this.grid.getView().focusRow(this.lastActive);if(c!==false){this.last=c}}else{this.selectFirstRow()}}},onRefresh:function(){var g=this.grid.store,d=this.getSelections(),c=0,a=d.length,b,e;this.silent=true;this.clearSelections(true);for(;c<a;c++){e=d[c];if((b=g.indexOfId(e.id))!=-1){this.selectRow(b,true)}}if(d.length!=this.selections.getCount()){this.fireEvent("selectionchange",this)}this.silent=false},onRemove:function(a,b,c){if(this.selections.remove(c)!==false){this.fireEvent("selectionchange",this)}},onRowUpdated:function(a,b,c){if(this.isSelected(c)){a.onRowSelect(b)}},selectRecords:function(b,e){if(!e){this.clearSelections()}var d=this.grid.store,c=0,a=b.length;for(;c<a;c++){this.selectRow(d.indexOf(b[c]),true)}},getCount:function(){return this.selections.length},selectFirstRow:function(){this.selectRow(0)},selectLastRow:function(a){this.selectRow(this.grid.store.getCount()-1,a)},selectNext:function(a){if(this.hasNext()){this.selectRow(this.last+1,a);this.grid.getView().focusRow(this.last);return true}return false},selectPrevious:function(a){if(this.hasPrevious()){this.selectRow(this.last-1,a);this.grid.getView().focusRow(this.last);return true}return false},hasNext:function(){return this.last!==false&&(this.last+1)<this.grid.store.getCount()},hasPrevious:function(){return !!this.last},getSelections:function(){return[].concat(this.selections.items)},getSelected:function(){return this.selections.itemAt(0)},each:function(e,d){var c=this.getSelections(),b=0,a=c.length;for(;b<a;b++){if(e.call(d||this,c[b],b)===false){return false}}return true},clearSelections:function(a){if(this.isLocked()){return}if(a!==true){var c=this.grid.store,b=this.selections;b.each(function(d){this.deselectRow(c.indexOfId(d.id))},this);b.clear()}else{this.selections.clear()}this.last=false},selectAll:function(){if(this.isLocked()){return}this.selections.clear();for(var b=0,a=this.grid.store.getCount();b<a;b++){this.selectRow(b,true)}},hasSelection:function(){return this.selections.length>0},isSelected:function(a){var b=Ext.isNumber(a)?this.grid.store.getAt(a):a;return(b&&this.selections.key(b.id)?true:false)},isIdSelected:function(a){return(this.selections.key(a)?true:false)},handleMouseDown:function(d,i,h){if(h.button!==0||this.isLocked()){return}var a=this.grid.getView();if(h.shiftKey&&!this.singleSelect&&this.last!==false){var c=this.last;this.selectRange(c,i,h.ctrlKey);this.last=c;a.focusRow(i)}else{var b=this.isSelected(i);if(h.ctrlKey&&b){this.deselectRow(i)}else{if(!b||this.getCount()>1){this.selectRow(i,h.ctrlKey||h.shiftKey);a.focusRow(i)}}}},selectRows:function(c,d){if(!d){this.clearSelections()}for(var b=0,a=c.length;b<a;b++){this.selectRow(c[b],true)}},selectRange:function(b,a,d){var c;if(this.isLocked()){return}if(!d){this.clearSelections()}if(b<=a){for(c=b;c<=a;c++){this.selectRow(c,true)}}else{for(c=b;c>=a;c--){this.selectRow(c,true)}}},deselectRange:function(c,b,a){if(this.isLocked()){return}for(var d=c;d<=b;d++){this.deselectRow(d,a)}},selectRow:function(b,d,a){if(this.isLocked()||(b<0||b>=this.grid.store.getCount())||(d&&this.isSelected(b))){return}var c=this.grid.store.getAt(b);if(c&&this.fireEvent("beforerowselect",this,b,d,c)!==false){if(!d||this.singleSelect){this.clearSelections()}this.selections.add(c);this.last=this.lastActive=b;if(!a){this.grid.getView().onRowSelect(b)}if(!this.silent){this.fireEvent("rowselect",this,b,c);this.fireEvent("selectionchange",this)}}},deselectRow:function(b,a){if(this.isLocked()){return}if(this.last==b){this.last=false}if(this.lastActive==b){this.lastActive=false}var c=this.grid.store.getAt(b);if(c){this.selections.remove(c);if(!a){this.grid.getView().onRowDeselect(b)}this.fireEvent("rowdeselect",this,b,c);this.fireEvent("selectionchange",this)}},acceptsNav:function(c,b,a){return !a.isHidden(b)&&a.isCellEditable(b,c)},onEditorKey:function(n,l){var d=l.getKey(),h,i=this.grid,p=i.lastEdit,j=i.activeEditor,b=l.shiftKey,o,p,a,m;if(d==l.TAB){l.stopEvent();j.completeEdit();if(b){h=i.walkCells(j.row,j.col-1,-1,this.acceptsNav,this)}else{h=i.walkCells(j.row,j.col+1,1,this.acceptsNav,this)}}else{if(d==l.ENTER){if(this.moveEditorOnEnter!==false){if(b){h=i.walkCells(p.row-1,p.col,-1,this.acceptsNav,this)}else{h=i.walkCells(p.row+1,p.col,1,this.acceptsNav,this)}}}}if(h){a=h[0];m=h[1];this.onEditorSelect(a,p.row);if(i.isEditor&&i.editing){o=i.activeEditor;if(o&&o.field.triggerBlur){o.field.triggerBlur()}}i.startEditing(a,m)}},onEditorSelect:function(b,a){if(a!=b){this.selectRow(b)}},destroy:function(){Ext.destroy(this.rowNav);this.rowNav=null;Ext.grid.RowSelectionModel.superclass.destroy.call(this)}});Ext.grid.Column=Ext.extend(Ext.util.Observable,{isColumn:true,constructor:function(b){Ext.apply(this,b);if(Ext.isString(this.renderer)){this.renderer=Ext.util.Format[this.renderer]}else{if(Ext.isObject(this.renderer)){this.scope=this.renderer.scope;this.renderer=this.renderer.fn}}if(!this.scope){this.scope=this}var a=this.editor;delete this.editor;this.setEditor(a);this.addEvents("click","contextmenu","dblclick","mousedown");Ext.grid.Column.superclass.constructor.call(this)},processEvent:function(b,d,c,g,a){return this.fireEvent(b,this,c,g,d)},destroy:function(){if(this.setEditor){this.setEditor(null)}this.purgeListeners()},renderer:function(a){return a},getEditor:function(a){return this.editable!==false?this.editor:null},setEditor:function(b){var a=this.editor;if(a){if(a.gridEditor){a.gridEditor.destroy();delete a.gridEditor}else{a.destroy()}}this.editor=null;if(b){if(!b.isXType){b=Ext.create(b,"textfield")}this.editor=b}},getCellEditor:function(b){var a=this.getEditor(b);if(a){if(!a.startEdit){if(!a.gridEditor){a.gridEditor=new Ext.grid.GridEditor(a)}a=a.gridEditor}}return a}});Ext.grid.BooleanColumn=Ext.extend(Ext.grid.Column,{trueText:"true",falseText:"false",undefinedText:"&#160;",constructor:function(a){Ext.grid.BooleanColumn.superclass.constructor.call(this,a);var c=this.trueText,d=this.falseText,b=this.undefinedText;this.renderer=function(e){if(e===undefined){return b}if(!e||e==="false"){return d}return c}}});Ext.grid.NumberColumn=Ext.extend(Ext.grid.Column,{format:"0,000.00",constructor:function(a){Ext.grid.NumberColumn.superclass.constructor.call(this,a);this.renderer=Ext.util.Format.numberRenderer(this.format)}});Ext.grid.DateColumn=Ext.extend(Ext.grid.Column,{format:"m/d/Y",constructor:function(a){Ext.grid.DateColumn.superclass.constructor.call(this,a);this.renderer=Ext.util.Format.dateRenderer(this.format)}});Ext.grid.TemplateColumn=Ext.extend(Ext.grid.Column,{constructor:function(a){Ext.grid.TemplateColumn.superclass.constructor.call(this,a);var b=(!Ext.isPrimitive(this.tpl)&&this.tpl.compile)?this.tpl:new Ext.XTemplate(this.tpl);this.renderer=function(d,e,c){return b.apply(c.data)};this.tpl=b}});Ext.grid.ActionColumn=Ext.extend(Ext.grid.Column,{header:"&#160;",actionIdRe:/x-action-col-(\d+)/,altText:"",constructor:function(b){var g=this,c=b.items||(g.items=[g]),a=c.length,d,e;Ext.grid.ActionColumn.superclass.constructor.call(g,b);g.renderer=function(h,i){h=Ext.isFunction(b.renderer)?b.renderer.apply(this,arguments)||"":"";i.css+=" x-action-col-cell";for(d=0;d<a;d++){e=c[d];h+='<img alt="'+(e.altText||g.altText)+'" src="'+(e.icon||Ext.BLANK_IMAGE_URL)+'" class="x-action-col-icon x-action-col-'+String(d)+" "+(e.iconCls||"")+" "+(Ext.isFunction(e.getClass)?e.getClass.apply(e.scope||this.scope||this,arguments):"")+'"'+((e.tooltip)?' ext:qtip="'+e.tooltip+'"':"")+" />"}return h}},destroy:function(){delete this.items;delete this.renderer;return Ext.grid.ActionColumn.superclass.destroy.apply(this,arguments)},processEvent:function(c,i,d,j,b){var a=i.getTarget().className.match(this.actionIdRe),h,g;if(a&&(h=this.items[parseInt(a[1],10)])){if(c=="click"){(g=h.handler||this.handler)&&g.call(h.scope||this.scope||this,d,j,b,h,i)}else{if((c=="mousedown")&&(h.stopSelection!==false)){return false}}}return Ext.grid.ActionColumn.superclass.processEvent.apply(this,arguments)}});Ext.grid.Column.types={gridcolumn:Ext.grid.Column,booleancolumn:Ext.grid.BooleanColumn,numbercolumn:Ext.grid.NumberColumn,datecolumn:Ext.grid.DateColumn,templatecolumn:Ext.grid.TemplateColumn,actioncolumn:Ext.grid.ActionColumn};Ext.grid.RowNumberer=Ext.extend(Object,{header:"",width:23,sortable:false,constructor:function(a){Ext.apply(this,a);if(this.rowspan){this.renderer=this.renderer.createDelegate(this)}},fixed:true,hideable:false,menuDisabled:true,dataIndex:"",id:"numberer",rowspan:undefined,renderer:function(b,c,a,d){if(this.rowspan){c.cellAttr='rowspan="'+this.rowspan+'"'}return d+1}});Ext.grid.CheckboxSelectionModel=Ext.extend(Ext.grid.RowSelectionModel,{header:'<div class="x-grid3-hd-checker">&#160;</div>',width:20,sortable:false,menuDisabled:true,fixed:true,hideable:false,dataIndex:"",id:"checker",isColumn:true,constructor:function(){Ext.grid.CheckboxSelectionModel.superclass.constructor.apply(this,arguments);if(this.checkOnly){this.handleMouseDown=Ext.emptyFn}},initEvents:function(){Ext.grid.CheckboxSelectionModel.superclass.initEvents.call(this);this.grid.on("render",function(){Ext.fly(this.grid.getView().innerHd).on("mousedown",this.onHdMouseDown,this)},this)},processEvent:function(b,d,c,g,a){if(b=="mousedown"){this.onMouseDown(d,d.getTarget());return false}else{return Ext.grid.Column.prototype.processEvent.apply(this,arguments)}},onMouseDown:function(c,b){if(c.button===0&&b.className=="x-grid3-row-checker"){c.stopEvent();var d=c.getTarget(".x-grid3-row");if(d){var a=d.rowIndex;if(this.isSelected(a)){this.deselectRow(a)}else{this.selectRow(a,true);this.grid.getView().focusRow(a)}}}},onHdMouseDown:function(c,a){if(a.className=="x-grid3-hd-checker"){c.stopEvent();var b=Ext.fly(a.parentNode);var d=b.hasClass("x-grid3-hd-checker-on");if(d){b.removeClass("x-grid3-hd-checker-on");this.clearSelections()}else{b.addClass("x-grid3-hd-checker-on");this.selectAll()}}},renderer:function(b,c,a){return'<div class="x-grid3-row-checker">&#160;</div>'},onEditorSelect:function(b,a){if(a!=b&&!this.checkOnly){this.selectRow(b)}}});Ext.grid.CellSelectionModel=Ext.extend(Ext.grid.AbstractSelectionModel,{constructor:function(a){Ext.apply(this,a);this.selection=null;this.addEvents("beforecellselect","cellselect","selectionchange");Ext.grid.CellSelectionModel.superclass.constructor.call(this)},initEvents:function(){this.grid.on("cellmousedown",this.handleMouseDown,this);this.grid.on(Ext.EventManager.getKeyEvent(),this.handleKeyDown,this);this.grid.getView().on({scope:this,refresh:this.onViewChange,rowupdated:this.onRowUpdated,beforerowremoved:this.clearSelections,beforerowsinserted:this.clearSelections});if(this.grid.isEditor){this.grid.on("beforeedit",this.beforeEdit,this)}},beforeEdit:function(a){this.select(a.row,a.column,false,true,a.record)},onRowUpdated:function(a,b,c){if(this.selection&&this.selection.record==c){a.onCellSelect(b,this.selection.cell[1])}},onViewChange:function(){this.clearSelections(true)},getSelectedCell:function(){return this.selection?this.selection.cell:null},clearSelections:function(b){var a=this.selection;if(a){if(b!==true){this.grid.view.onCellDeselect(a.cell[0],a.cell[1])}this.selection=null;this.fireEvent("selectionchange",this,null)}},hasSelection:function(){return this.selection?true:false},handleMouseDown:function(b,d,a,c){if(c.button!==0||this.isLocked()){return}this.select(d,a)},select:function(g,c,b,e,d){if(this.fireEvent("beforecellselect",this,g,c)!==false){this.clearSelections();d=d||this.grid.store.getAt(g);this.selection={record:d,cell:[g,c]};if(!b){var a=this.grid.getView();a.onCellSelect(g,c);if(e!==true){a.focusCell(g,c)}}this.fireEvent("cellselect",this,g,c);this.fireEvent("selectionchange",this,this.selection)}},isSelectable:function(c,b,a){return !a.isHidden(b)},onEditorKey:function(b,a){if(a.getKey()==a.TAB){this.handleKeyDown(a)}},handleKeyDown:function(j){if(!j.isNavKeyPress()){return}var d=j.getKey(),i=this.grid,p=this.selection,b=this,m=function(g,c,e){return i.walkCells(g,c,e,i.isEditor&&i.editing?b.acceptsNav:b.isSelectable,b)},o,h,a,l,n;switch(d){case j.ESC:case j.PAGE_UP:case j.PAGE_DOWN:break;default:j.stopEvent();break}if(!p){o=m(0,0,1);if(o){this.select(o[0],o[1])}return}o=p.cell;a=o[0];l=o[1];switch(d){case j.TAB:if(j.shiftKey){h=m(a,l-1,-1)}else{h=m(a,l+1,1)}break;case j.DOWN:h=m(a+1,l,1);break;case j.UP:h=m(a-1,l,-1);break;case j.RIGHT:h=m(a,l+1,1);break;case j.LEFT:h=m(a,l-1,-1);break;case j.ENTER:if(i.isEditor&&!i.editing){i.startEditing(a,l);return}break}if(h){a=h[0];l=h[1];this.select(a,l);if(i.isEditor&&i.editing){n=i.activeEditor;if(n&&n.field.triggerBlur){n.field.triggerBlur()}i.startEditing(a,l)}}},acceptsNav:function(c,b,a){return !a.isHidden(b)&&a.isCellEditable(b,c)}});Ext.grid.EditorGridPanel=Ext.extend(Ext.grid.GridPanel,{clicksToEdit:2,forceValidation:false,isEditor:true,detectEdit:false,autoEncode:false,trackMouseOver:false,initComponent:function(){Ext.grid.EditorGridPanel.superclass.initComponent.call(this);if(!this.selModel){this.selModel=new Ext.grid.CellSelectionModel()}this.activeEditor=null;this.addEvents("beforeedit","afteredit","validateedit")},initEvents:function(){Ext.grid.EditorGridPanel.superclass.initEvents.call(this);this.getGridEl().on("mousewheel",this.stopEditing.createDelegate(this,[true]),this);this.on("columnresize",this.stopEditing,this,[true]);if(this.clicksToEdit==1){this.on("cellclick",this.onCellDblClick,this)}else{var a=this.getView();if(this.clicksToEdit=="auto"&&a.mainBody){a.mainBody.on("mousedown",this.onAutoEditClick,this)}this.on("celldblclick",this.onCellDblClick,this)}},onResize:function(){Ext.grid.EditorGridPanel.superclass.onResize.apply(this,arguments);var a=this.activeEditor;if(this.editing&&a){a.realign(true)}},onCellDblClick:function(b,c,a){this.startEditing(c,a)},onAutoEditClick:function(c,b){if(c.button!==0){return}var g=this.view.findRowIndex(b),a=this.view.findCellIndex(b);if(g!==false&&a!==false){this.stopEditing();if(this.selModel.getSelectedCell){var d=this.selModel.getSelectedCell();if(d&&d[0]===g&&d[1]===a){this.startEditing(g,a)}}else{if(this.selModel.isSelected(g)){this.startEditing(g,a)}}}},onEditComplete:function(b,d,a){this.editing=false;this.lastActiveEditor=this.activeEditor;this.activeEditor=null;var c=b.record,h=this.colModel.getDataIndex(b.col);d=this.postEditValue(d,a,c,h);if(this.forceValidation===true||String(d)!==String(a)){var g={grid:this,record:c,field:h,originalValue:a,value:d,row:b.row,column:b.col,cancel:false};if(this.fireEvent("validateedit",g)!==false&&!g.cancel&&String(d)!==String(a)){c.set(h,g.value);delete g.cancel;this.fireEvent("afteredit",g)}}this.view.focusCell(b.row,b.col)},startEditing:function(i,c){this.stopEditing();if(this.colModel.isCellEditable(c,i)){this.view.ensureVisible(i,c,true);var d=this.store.getAt(i),h=this.colModel.getDataIndex(c),g={grid:this,record:d,field:h,value:d.data[h],row:i,column:c,cancel:false};if(this.fireEvent("beforeedit",g)!==false&&!g.cancel){this.editing=true;var b=this.colModel.getCellEditor(c,i);if(!b){return}if(!b.rendered){b.parentEl=this.view.getEditorParent(b);b.on({scope:this,render:{fn:function(e){e.field.focus(false,true)},single:true,scope:this},specialkey:function(k,j){this.getSelectionModel().onEditorKey(k,j)},complete:this.onEditComplete,canceledit:this.stopEditing.createDelegate(this,[true])})}Ext.apply(b,{row:i,col:c,record:d});this.lastEdit={row:i,col:c};this.activeEditor=b;if(b.field.isXType("checkbox")){b.allowBlur=false;this.setupCheckbox(b.field)}b.selectSameEditor=(this.activeEditor==this.lastActiveEditor);var a=this.preEditValue(d,h);b.startEdit(this.view.getCell(i,c).firstChild,Ext.isDefined(a)?a:"");(function(){delete b.selectSameEditor}).defer(50)}}},setupCheckbox:function(c){var b=this,a=function(){c.el.on("click",b.onCheckClick,b,{single:true})};if(c.rendered){a()}else{c.on("render",a,null,{single:true})}},onCheckClick:function(){var a=this.activeEditor;a.allowBlur=true;a.field.focus(false,10)},preEditValue:function(a,c){var b=a.data[c];return this.autoEncode&&Ext.isString(b)?Ext.util.Format.htmlDecode(b):b},postEditValue:function(c,a,b,d){return this.autoEncode&&Ext.isString(c)?Ext.util.Format.htmlEncode(c):c},stopEditing:function(b){if(this.editing){var a=this.lastActiveEditor=this.activeEditor;if(a){a[b===true?"cancelEdit":"completeEdit"]();this.view.focusCell(a.row,a.col)}this.activeEditor=null}this.editing=false}});Ext.reg("editorgrid",Ext.grid.EditorGridPanel);Ext.grid.GridEditor=function(b,a){Ext.grid.GridEditor.superclass.constructor.call(this,b,a);b.monitorTab=false};Ext.extend(Ext.grid.GridEditor,Ext.Editor,{alignment:"tl-tl",autoSize:"width",hideEl:false,cls:"x-small-editor x-grid-editor",shim:false,shadow:false});Ext.grid.PropertyRecord=Ext.data.Record.create([{name:"name",type:"string"},"value"]);Ext.grid.PropertyStore=Ext.extend(Ext.util.Observable,{constructor:function(a,b){this.grid=a;this.store=new Ext.data.Store({recordType:Ext.grid.PropertyRecord});this.store.on("update",this.onUpdate,this);if(b){this.setSource(b)}Ext.grid.PropertyStore.superclass.constructor.call(this)},setSource:function(c){this.source=c;this.store.removeAll();var b=[];for(var a in c){if(this.isEditableValue(c[a])){b.push(new Ext.grid.PropertyRecord({name:a,value:c[a]},a))}}this.store.loadRecords({records:b},{},true)},onUpdate:function(e,a,d){if(d==Ext.data.Record.EDIT){var b=a.data.value;var c=a.modified.value;if(this.grid.fireEvent("beforepropertychange",this.source,a.id,b,c)!==false){this.source[a.id]=b;a.commit();this.grid.fireEvent("propertychange",this.source,a.id,b,c)}else{a.reject()}}},getProperty:function(a){return this.store.getAt(a)},isEditableValue:function(a){return Ext.isPrimitive(a)||Ext.isDate(a)},setValue:function(d,c,a){var b=this.getRec(d);if(b){b.set("value",c);this.source[d]=c}else{if(a){this.source[d]=c;b=new Ext.grid.PropertyRecord({name:d,value:c},d);this.store.add(b)}}},remove:function(b){var a=this.getRec(b);if(a){this.store.remove(a);delete this.source[b]}},getRec:function(a){return this.store.getById(a)},getSource:function(){return this.source}});Ext.grid.PropertyColumnModel=Ext.extend(Ext.grid.ColumnModel,{nameText:"Name",valueText:"Value",dateFormat:"m/j/Y",trueText:"true",falseText:"false",constructor:function(c,b){var d=Ext.grid,e=Ext.form;this.grid=c;d.PropertyColumnModel.superclass.constructor.call(this,[{header:this.nameText,width:50,sortable:true,dataIndex:"name",id:"name",menuDisabled:true},{header:this.valueText,width:50,resizable:false,dataIndex:"value",id:"value",menuDisabled:true}]);this.store=b;var a=new e.Field({autoCreate:{tag:"select",children:[{tag:"option",value:"true",html:this.trueText},{tag:"option",value:"false",html:this.falseText}]},getValue:function(){return this.el.dom.value=="true"}});this.editors={date:new d.GridEditor(new e.DateField({selectOnFocus:true})),string:new d.GridEditor(new e.TextField({selectOnFocus:true})),number:new d.GridEditor(new e.NumberField({selectOnFocus:true,style:"text-align:left;"})),"boolean":new d.GridEditor(a,{autoSize:"both"})};this.renderCellDelegate=this.renderCell.createDelegate(this);this.renderPropDelegate=this.renderProp.createDelegate(this)},renderDate:function(a){return a.dateFormat(this.dateFormat)},renderBool:function(a){return this[a?"trueText":"falseText"]},isCellEditable:function(a,b){return a==1},getRenderer:function(a){return a==1?this.renderCellDelegate:this.renderPropDelegate},renderProp:function(a){return this.getPropertyName(a)},renderCell:function(d,b,c){var a=this.grid.customRenderers[c.get("name")];if(a){return a.apply(this,arguments)}var e=d;if(Ext.isDate(d)){e=this.renderDate(d)}else{if(typeof d=="boolean"){e=this.renderBool(d)}}return Ext.util.Format.htmlEncode(e)},getPropertyName:function(b){var a=this.grid.propertyNames;return a&&a[b]?a[b]:b},getCellEditor:function(a,e){var b=this.store.getProperty(e),d=b.data.name,c=b.data.value;if(this.grid.customEditors[d]){return this.grid.customEditors[d]}if(Ext.isDate(c)){return this.editors.date}else{if(typeof c=="number"){return this.editors.number}else{if(typeof c=="boolean"){return this.editors["boolean"]}else{return this.editors.string}}}},destroy:function(){Ext.grid.PropertyColumnModel.superclass.destroy.call(this);this.destroyEditors(this.editors);this.destroyEditors(this.grid.customEditors)},destroyEditors:function(b){for(var a in b){Ext.destroy(b[a])}}});Ext.grid.PropertyGrid=Ext.extend(Ext.grid.EditorGridPanel,{enableColumnMove:false,stripeRows:false,trackMouseOver:false,clicksToEdit:1,enableHdMenu:false,viewConfig:{forceFit:true},initComponent:function(){this.customRenderers=this.customRenderers||{};this.customEditors=this.customEditors||{};this.lastEditRow=null;var b=new Ext.grid.PropertyStore(this);this.propStore=b;var a=new Ext.grid.PropertyColumnModel(this,b);b.store.sort("name","ASC");this.addEvents("beforepropertychange","propertychange");this.cm=a;this.ds=b.store;Ext.grid.PropertyGrid.superclass.initComponent.call(this);this.mon(this.selModel,"beforecellselect",function(e,d,c){if(c===0){this.startEditing.defer(200,this,[d,1]);return false}},this)},onRender:function(){Ext.grid.PropertyGrid.superclass.onRender.apply(this,arguments);this.getGridEl().addClass("x-props-grid")},afterRender:function(){Ext.grid.PropertyGrid.superclass.afterRender.apply(this,arguments);if(this.source){this.setSource(this.source)}},setSource:function(a){this.propStore.setSource(a)},getSource:function(){return this.propStore.getSource()},setProperty:function(c,b,a){this.propStore.setValue(c,b,a)},removeProperty:function(a){this.propStore.remove(a)}});Ext.reg("propertygrid",Ext.grid.PropertyGrid);Ext.grid.GroupingView=Ext.extend(Ext.grid.GridView,{groupByText:"Group By This Field",showGroupsText:"Show in Groups",hideGroupedColumn:false,showGroupName:true,startCollapsed:false,enableGrouping:true,enableGroupingMenu:true,enableNoGroups:true,emptyGroupText:"(None)",ignoreAdd:false,groupTextTpl:"{text}",groupMode:"value",cancelEditOnToggle:true,initTemplates:function(){Ext.grid.GroupingView.superclass.initTemplates.call(this);this.state={};var a=this.grid.getSelectionModel();a.on(a.selectRow?"beforerowselect":"beforecellselect",this.onBeforeRowSelect,this);if(!this.startGroup){this.startGroup=new Ext.XTemplate('<div id="{groupId}" class="x-grid-group {cls}">','<div id="{groupId}-hd" class="x-grid-group-hd" style="{style}"><div class="x-grid-group-title">',this.groupTextTpl,"</div></div>",'<div id="{groupId}-bd" class="x-grid-group-body">')}this.startGroup.compile();if(!this.endGroup){this.endGroup="</div></div>"}},findGroup:function(a){return Ext.fly(a).up(".x-grid-group",this.mainBody.dom)},getGroups:function(){return this.hasRows()?this.mainBody.dom.childNodes:[]},onAdd:function(d,a,b){if(this.canGroup()&&!this.ignoreAdd){var c=this.getScrollState();this.fireEvent("beforerowsinserted",d,b,b+(a.length-1));this.refresh();this.restoreScroll(c);this.fireEvent("rowsinserted",d,b,b+(a.length-1))}else{if(!this.canGroup()){Ext.grid.GroupingView.superclass.onAdd.apply(this,arguments)}}},onRemove:function(e,a,b,d){Ext.grid.GroupingView.superclass.onRemove.apply(this,arguments);var c=document.getElementById(a._groupId);if(c&&c.childNodes[1].childNodes.length<1){Ext.removeNode(c)}this.applyEmptyText()},refreshRow:function(a){if(this.ds.getCount()==1){this.refresh()}else{this.isUpdating=true;Ext.grid.GroupingView.superclass.refreshRow.apply(this,arguments);this.isUpdating=false}},beforeMenuShow:function(){var c,a=this.hmenu.items,b=this.cm.config[this.hdCtxIndex].groupable===false;if((c=a.get("groupBy"))){c.setDisabled(b)}if((c=a.get("showGroups"))){c.setDisabled(b);c.setChecked(this.canGroup(),true)}},renderUI:function(){var a=Ext.grid.GroupingView.superclass.renderUI.call(this);if(this.enableGroupingMenu&&this.hmenu){this.hmenu.add("-",{itemId:"groupBy",text:this.groupByText,handler:this.onGroupByClick,scope:this,iconCls:"x-group-by-icon"});if(this.enableNoGroups){this.hmenu.add({itemId:"showGroups",text:this.showGroupsText,checked:true,checkHandler:this.onShowGroupsClick,scope:this})}this.hmenu.on("beforeshow",this.beforeMenuShow,this)}return a},processEvent:function(b,i){Ext.grid.GroupingView.superclass.processEvent.call(this,b,i);var h=i.getTarget(".x-grid-group-hd",this.mainBody);if(h){var g=this.getGroupField(),d=this.getPrefix(g),a=h.id.substring(d.length),c=new RegExp("gp-"+Ext.escapeRe(g)+"--hd");a=a.substr(0,a.length-3);if(a||c.test(h.id)){this.grid.fireEvent("group"+b,this.grid,g,a,i)}if(b=="mousedown"&&i.button==0){this.toggleGroup(h.parentNode)}}},onGroupByClick:function(){var a=this.grid;this.enableGrouping=true;a.store.groupBy(this.cm.getDataIndex(this.hdCtxIndex));a.fireEvent("groupchange",a,a.store.getGroupState());this.beforeMenuShow();this.refresh()},onShowGroupsClick:function(a,b){this.enableGrouping=b;if(b){this.onGroupByClick()}else{this.grid.store.clearGrouping();this.grid.fireEvent("groupchange",this,null)}},toggleRowIndex:function(c,a){if(!this.canGroup()){return}var b=this.getRow(c);if(b){this.toggleGroup(this.findGroup(b),a)}},toggleGroup:function(c,b){var a=Ext.get(c),d=Ext.util.Format.htmlEncode(a.id);b=Ext.isDefined(b)?b:a.hasClass("x-grid-group-collapsed");if(this.state[d]!==b){if(this.cancelEditOnToggle!==false){this.grid.stopEditing(true)}this.state[d]=b;a[b?"removeClass":"addClass"]("x-grid-group-collapsed")}},toggleAllGroups:function(c){var b=this.getGroups();for(var d=0,a=b.length;d<a;d++){this.toggleGroup(b[d],c)}},expandAllGroups:function(){this.toggleAllGroups(true)},collapseAllGroups:function(){this.toggleAllGroups(false)},getGroup:function(a,e,i,j,b,h){var c=this.cm.config[b],d=i?i.call(c.scope,a,{},e,j,b,h):String(a);if(d===""||d==="&#160;"){d=c.emptyGroupText||this.emptyGroupText}return d},getGroupField:function(){return this.grid.store.getGroupState()},afterRender:function(){if(!this.ds||!this.cm){return}Ext.grid.GroupingView.superclass.afterRender.call(this);if(this.grid.deferRowRender){this.updateGroupWidths()}},afterRenderUI:function(){Ext.grid.GroupingView.superclass.afterRenderUI.call(this);if(this.enableGroupingMenu&&this.hmenu){this.hmenu.add("-",{itemId:"groupBy",text:this.groupByText,handler:this.onGroupByClick,scope:this,iconCls:"x-group-by-icon"});if(this.enableNoGroups){this.hmenu.add({itemId:"showGroups",text:this.showGroupsText,checked:true,checkHandler:this.onShowGroupsClick,scope:this})}this.hmenu.on("beforeshow",this.beforeMenuShow,this)}},renderRows:function(){var a=this.getGroupField();var e=!!a;if(this.hideGroupedColumn){var b=this.cm.findColumnIndex(a),d=Ext.isDefined(this.lastGroupField);if(!e&&d){this.mainBody.update("");this.cm.setHidden(this.cm.findColumnIndex(this.lastGroupField),false);delete this.lastGroupField}else{if(e&&!d){this.lastGroupField=a;this.cm.setHidden(b,true)}else{if(e&&d&&a!==this.lastGroupField){this.mainBody.update("");var c=this.cm.findColumnIndex(this.lastGroupField);this.cm.setHidden(c,false);this.lastGroupField=a;this.cm.setHidden(b,true)}}}}return Ext.grid.GroupingView.superclass.renderRows.apply(this,arguments)},doRender:function(c,h,q,a,p,s){if(h.length<1){return""}if(!this.canGroup()||this.isUpdating){return Ext.grid.GroupingView.superclass.doRender.apply(this,arguments)}var z=this.getGroupField(),o=this.cm.findColumnIndex(z),w,j="width:"+this.getTotalWidth()+";",e=this.cm.config[o],b=e.groupRenderer||e.renderer,t=this.showGroupName?(e.groupName||e.header)+": ":"",y=[],l,u,v,n;for(u=0,v=h.length;u<v;u++){var k=a+u,m=h[u],d=m.data[z];w=this.getGroup(d,m,b,k,o,q);if(!l||l.group!=w){n=this.constructId(d,z,o);this.state[n]=!(Ext.isDefined(this.state[n])?!this.state[n]:this.startCollapsed);l={group:w,gvalue:d,text:t+w,groupId:n,startRow:k,rs:[m],cls:this.state[n]?"":"x-grid-group-collapsed",style:j};y.push(l)}else{l.rs.push(m)}m._groupId=n}var x=[];for(u=0,v=y.length;u<v;u++){w=y[u];this.doGroupStart(x,w,c,q,p);x[x.length]=Ext.grid.GroupingView.superclass.doRender.call(this,c,w.rs,q,w.startRow,p,s);this.doGroupEnd(x,w,c,q,p)}return x.join("")},getGroupId:function(a){var b=this.getGroupField();return this.constructId(a,b,this.cm.findColumnIndex(b))},constructId:function(c,e,a){var b=this.cm.config[a],d=b.groupRenderer||b.renderer,g=(this.groupMode=="value")?c:this.getGroup(c,{data:{}},d,0,a,this.ds);return this.getPrefix(e)+Ext.util.Format.htmlEncode(g)},canGroup:function(){return this.enableGrouping&&!!this.getGroupField()},getPrefix:function(a){return this.grid.getGridEl().id+"-gp-"+a+"-"},doGroupStart:function(a,d,b,e,c){a[a.length]=this.startGroup.apply(d)},doGroupEnd:function(a,d,b,e,c){a[a.length]=this.endGroup},getRows:function(){if(!this.canGroup()){return Ext.grid.GroupingView.superclass.getRows.call(this)}var k=[],c=this.getGroups(),h,e=0,a=c.length,d,b;for(;e<a;++e){h=c[e].childNodes[1];if(h){h=h.childNodes;for(d=0,b=h.length;d<b;++d){k[k.length]=h[d]}}}return k},updateGroupWidths:function(){if(!this.canGroup()||!this.hasRows()){return}var c=Math.max(this.cm.getTotalWidth(),this.el.dom.offsetWidth-this.getScrollOffset())+"px";var b=this.getGroups();for(var d=0,a=b.length;d<a;d++){b[d].firstChild.style.width=c}},onColumnWidthUpdated:function(c,a,b){Ext.grid.GroupingView.superclass.onColumnWidthUpdated.call(this,c,a,b);this.updateGroupWidths()},onAllColumnWidthsUpdated:function(a,b){Ext.grid.GroupingView.superclass.onAllColumnWidthsUpdated.call(this,a,b);this.updateGroupWidths()},onColumnHiddenUpdated:function(b,c,a){Ext.grid.GroupingView.superclass.onColumnHiddenUpdated.call(this,b,c,a);this.updateGroupWidths()},onLayout:function(){this.updateGroupWidths()},onBeforeRowSelect:function(b,a){this.toggleRowIndex(a,true)}});Ext.grid.GroupingView.GROUP_ID=1000; \ No newline at end of file
diff --git a/deluge/ui/web/js/extjs/ext-base-debug.js b/deluge/ui/web/js/extjs/ext-base-debug.js
new file mode 100644
index 0000000..7b20934
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-base-debug.js
@@ -0,0 +1,3352 @@
+/*
+This file is part of Ext JS 3.4
+
+Copyright (c) 2011-2013 Sencha Inc
+
+Contact: http://www.sencha.com/contact
+
+GNU General Public License Usage
+This file may be used under the terms of the GNU General Public License version 3.0 as
+published by the Free Software Foundation and appearing in the file LICENSE included in the
+packaging of this file.
+
+Please review the following information to ensure the GNU General Public License version 3.0
+requirements will be met: http://www.gnu.org/copyleft/gpl.html.
+
+If you are unsure which license is appropriate for your use, please contact the sales department
+at http://www.sencha.com/contact.
+
+Build date: 2013-04-03 15:07:25
+*/
+// for old browsers
+window.undefined = window.undefined;
+
+/**
+ * @class Ext
+ * Ext core utilities and functions.
+ * @singleton
+ */
+
+Ext = {
+ /**
+ * The version of the framework
+ * @type String
+ */
+ version : '3.4.1.1',
+ versionDetail : {
+ major : 3,
+ minor : 4,
+ patch : 1.1
+ }
+};
+
+/**
+ * Copies all the properties of config to obj.
+ * @param {Object} obj The receiver of the properties
+ * @param {Object} config The source of the properties
+ * @param {Object} defaults A different object that will also be applied for default values
+ * @return {Object} returns obj
+ * @member Ext apply
+ */
+Ext.apply = function(o, c, defaults){
+ // no "this" reference for friendly out of scope calls
+ if(defaults){
+ Ext.apply(o, defaults);
+ }
+ if(o && c && typeof c == 'object'){
+ for(var p in c){
+ o[p] = c[p];
+ }
+ }
+ return o;
+};
+
+(function(){
+ var idSeed = 0,
+ toString = Object.prototype.toString,
+ ua = navigator.userAgent.toLowerCase(),
+ check = function(r){
+ return r.test(ua);
+ },
+ DOC = document,
+ docMode = DOC.documentMode,
+ isStrict = DOC.compatMode == "CSS1Compat",
+ isOpera = check(/opera/),
+ isChrome = check(/\bchrome\b/),
+ isWebKit = check(/webkit/),
+ isSafari = !isChrome && check(/safari/),
+ isSafari2 = isSafari && check(/applewebkit\/4/), // unique to Safari 2
+ isSafari3 = isSafari && check(/version\/3/),
+ isSafari4 = isSafari && check(/version\/4/),
+ isIE = !isOpera && check(/msie/),
+ isIE7 = isIE && ((check(/msie 7/) && docMode != 8 && docMode != 9 && docMode != 10) || docMode == 7),
+ isIE8 = isIE && ((check(/msie 8/) && docMode != 7 && docMode != 9 && docMode != 10) || docMode == 8),
+ isIE9 = isIE && ((check(/msie 9/) && docMode != 7 && docMode != 8 && docMode != 10) || docMode == 9),
+ isIE10 = isIE && ((check(/msie 10/) && docMode != 7 && docMode != 8 && docMode != 9) || docMode == 10),
+ isIE6 = isIE && check(/msie 6/),
+ isIE9m = isIE && (isIE6 || isIE7 || isIE8 || isIE9),
+ isGecko = !isWebKit && check(/gecko/),
+ isGecko2 = isGecko && check(/rv:1\.8/),
+ isGecko3 = isGecko && check(/rv:1\.9/),
+ isBorderBox = isIE9m && !isStrict,
+ isWindows = check(/windows|win32/),
+ isMac = check(/macintosh|mac os x/),
+ isAir = check(/adobeair/),
+ isLinux = check(/linux/),
+ isSecure = /^https/i.test(window.location.protocol),
+ noArgs = [],
+ nonEnumerables = [],
+ emptyFn = Ext.emptyFn,
+ t = Ext.apply({}, {
+ constructor: emptyFn,
+ toString: emptyFn,
+ valueOf: emptyFn
+ }),
+ callOverrideParent = function () {
+ var method = callOverrideParent.caller.caller; // skip callParent (our caller)
+ return method.$owner.prototype[method.$name].apply(this, arguments);
+ };
+
+ if (t.constructor !== emptyFn) {
+ nonEnumerables.push('constructor');
+ }
+ if (t.toString !== emptyFn) {
+ nonEnumerables.push('toString');
+ }
+ if (t.valueOf !== emptyFn) {
+ nonEnumerables.push('valueOf');
+ }
+ if (!nonEnumerables.length) {
+ nonEnumerables = null;
+ }
+
+ // Create the abstract Base class to provide an empty constructor and callParent implementations
+ function Base () {
+ //
+ }
+
+ Ext.apply(Base, {
+ $isClass: true,
+
+ callParent: function (args) {
+ var method;
+
+ // This code is intentionally inlined for the least number of debugger stepping
+ return (method = this.callParent.caller) && (method.$previous ||
+ ((method = method.$owner ? method : method.caller) &&
+ method.$owner.superclass.self[method.$name])).apply(this, args || noArgs);
+ }
+ });
+
+ Base.prototype = {
+ constructor: function() {
+ },
+ callParent: function(args) {
+ // NOTE: this code is deliberately as few expressions (and no function calls)
+ // as possible so that a debugger can skip over this noise with the minimum number
+ // of steps. Basically, just hit Step Into until you are where you really wanted
+ // to be.
+ var method,
+ superMethod = (method = this.callParent.caller) && (method.$previous ||
+ ((method = method.$owner ? method : method.caller) &&
+ method.$owner.superclass[method.$name]));
+
+ return superMethod.apply(this, args || noArgs);
+ }
+ };
+
+ // remove css image flicker
+ if(isIE6){
+ try{
+ DOC.execCommand("BackgroundImageCache", false, true);
+ }catch(e){}
+ }
+
+ Ext.apply(Ext, {
+ /**
+ * URL to a blank file used by Ext when in secure mode for iframe src and onReady src to prevent
+ * the IE insecure content warning (<tt>'about:blank'</tt>, except for IE in secure mode, which is <tt>'javascript:""'</tt>).
+ * @type String
+ */
+ SSL_SECURE_URL : isSecure && isIE ? 'javascript:""' : 'about:blank',
+ /**
+ * True if the browser is in strict (standards-compliant) mode, as opposed to quirks mode
+ * @type Boolean
+ */
+ isStrict : isStrict,
+ /**
+ * True if the page is running over SSL
+ * @type Boolean
+ */
+ isSecure : isSecure,
+ /**
+ * True when the document is fully initialized and ready for action
+ * @type Boolean
+ */
+ isReady : false,
+
+ /**
+ * True if the {@link Ext.Fx} Class is available
+ * @type Boolean
+ * @property enableFx
+ */
+
+ /**
+ * HIGHLY EXPERIMENTAL
+ * True to force css based border-box model override and turning off javascript based adjustments. This is a
+ * runtime configuration and must be set before onReady.
+ * @type Boolean
+ */
+ enableForcedBoxModel : false,
+
+ /**
+ * True to automatically uncache orphaned Ext.Elements periodically (defaults to true)
+ * @type Boolean
+ */
+ enableGarbageCollector : true,
+
+ /**
+ * True to automatically purge event listeners during garbageCollection (defaults to false).
+ * @type Boolean
+ */
+ enableListenerCollection : false,
+
+ /**
+ * EXPERIMENTAL - True to cascade listener removal to child elements when an element is removed.
+ * Currently not optimized for performance.
+ * @type Boolean
+ */
+ enableNestedListenerRemoval : false,
+
+ /**
+ * Indicates whether to use native browser parsing for JSON methods.
+ * This option is ignored if the browser does not support native JSON methods.
+ * <b>Note: Native JSON methods will not work with objects that have functions.
+ * Also, property names must be quoted, otherwise the data will not parse.</b> (Defaults to false)
+ * @type Boolean
+ */
+ USE_NATIVE_JSON : false,
+
+ /**
+ * Copies all the properties of config to obj if they don't already exist.
+ * @param {Object} obj The receiver of the properties
+ * @param {Object} config The source of the properties
+ * @return {Object} returns obj
+ */
+ applyIf : function(o, c){
+ if(o){
+ for(var p in c){
+ if(!Ext.isDefined(o[p])){
+ o[p] = c[p];
+ }
+ }
+ }
+ return o;
+ },
+
+ /**
+ * Generates unique ids. If the element already has an id, it is unchanged
+ * @param {Mixed} el (optional) The element to generate an id for
+ * @param {String} prefix (optional) Id prefix (defaults "ext-gen")
+ * @return {String} The generated Id.
+ */
+ id : function(el, prefix){
+ el = Ext.getDom(el, true) || {};
+ if (!el.id) {
+ el.id = (prefix || "ext-gen") + (++idSeed);
+ }
+ return el.id;
+ },
+
+ /**
+ * <p>Extends one class to create a subclass and optionally overrides members with the passed literal. This method
+ * also adds the function "override()" to the subclass that can be used to override members of the class.</p>
+ * For example, to create a subclass of Ext GridPanel:
+ * <pre><code>
+MyGridPanel = Ext.extend(Ext.grid.GridPanel, {
+ constructor: function(config) {
+
+// Create configuration for this Grid.
+ var store = new Ext.data.Store({...});
+ var colModel = new Ext.grid.ColumnModel({...});
+
+// Create a new config object containing our computed properties
+// *plus* whatever was in the config parameter.
+ config = Ext.apply({
+ store: store,
+ colModel: colModel
+ }, config);
+
+ MyGridPanel.superclass.constructor.call(this, config);
+
+// Your postprocessing here
+ },
+
+ yourMethod: function() {
+ // etc.
+ }
+});
+</code></pre>
+ *
+ * <p>This function also supports a 3-argument call in which the subclass's constructor is
+ * passed as an argument. In this form, the parameters are as follows:</p>
+ * <div class="mdetail-params"><ul>
+ * <li><code>subclass</code> : Function <div class="sub-desc">The subclass constructor.</div></li>
+ * <li><code>superclass</code> : Function <div class="sub-desc">The constructor of class being extended</div></li>
+ * <li><code>overrides</code> : Object <div class="sub-desc">A literal with members which are copied into the subclass's
+ * prototype, and are therefore shared among all instances of the new class.</div></li>
+ * </ul></div>
+ *
+ * @param {Function} superclass The constructor of class being extended.
+ * @param {Object} overrides <p>A literal with members which are copied into the subclass's
+ * prototype, and are therefore shared between all instances of the new class.</p>
+ * <p>This may contain a special member named <tt><b>constructor</b></tt>. This is used
+ * to define the constructor of the new class, and is returned. If this property is
+ * <i>not</i> specified, a constructor is generated and returned which just calls the
+ * superclass's constructor passing on its parameters.</p>
+ * <p><b>It is essential that you call the superclass constructor in any provided constructor. See example code.</b></p>
+ * @return {Function} The subclass constructor from the <code>overrides</code> parameter, or a generated one if not provided.
+ */
+ extend : function(){
+ // inline overrides
+ var io = function(o){
+ for(var m in o){
+ this[m] = o[m];
+ }
+ };
+ var oc = Object.prototype.constructor;
+
+ return function(sb, sp, overrides){
+ if(typeof sp == 'object'){
+ overrides = sp;
+ sp = sb;
+ sb = overrides.constructor != oc ? overrides.constructor : function(){sp.apply(this, arguments);};
+ }
+ var F = function(){},
+ sbp,
+ spp = sp.prototype;
+
+ F.prototype = spp;
+ sbp = sb.prototype = new F();
+ sbp.constructor=sb;
+ sb.superclass=spp;
+ if(spp.constructor == oc){
+ spp.constructor=sp;
+ }
+ sb.override = function(o){
+ Ext.override(sb, o);
+ };
+ sbp.superclass = sbp.supr = (function(){
+ return spp;
+ });
+ sbp.override = io;
+ Ext.override(sb, overrides);
+ sb.extend = function(o){return Ext.extend(sb, o);};
+ return sb;
+ };
+ }(),
+
+ global: (function () {
+ return this;
+ })(),
+
+ Base: Base,
+
+ namespaceCache: {},
+
+ createNamespace: function (namespaceOrClass, isClass) {
+ var cache = Ext.namespaceCache,
+ namespace = isClass ? namespaceOrClass.substring(0, namespaceOrClass.lastIndexOf('.'))
+ : namespaceOrClass,
+ ns = cache[namespace],
+ i, n, part, parts, partials;
+
+ if (!ns) {
+ ns = Ext.global;
+ if (namespace) {
+ partials = [];
+ parts = namespace.split('.');
+
+ for (i = 0, n = parts.length; i < n; ++i) {
+ part = parts[i];
+
+ ns = ns[part] || (ns[part] = {});
+ partials.push(part);
+
+ cache[partials.join('.')] = ns; // build up prefixes as we go
+ }
+ }
+ }
+
+ return ns;
+ },
+
+ getClassByName: function (className) {
+ var parts = className.split('.'),
+ cls = Ext.global,
+ n = parts.length,
+ i;
+
+ for (i = 0; cls && i < n; ++i) {
+ cls = cls[parts[i]];
+ }
+
+ return cls || null;
+ },
+
+ addMembers: function (cls, target, members, handleNonEnumerables) {
+ var i, name, member;
+
+ for (name in members) {
+ if (members.hasOwnProperty(name)) {
+ member = members[name];
+ if (typeof member == 'function') {
+ member.$owner = cls;
+ member.$name = name;
+ }
+
+ target[name] = member;
+ }
+ }
+
+ if (handleNonEnumerables && nonEnumerables) {
+ for (i = nonEnumerables.length; i-- > 0; ) {
+ name = nonEnumerables[i];
+ if (members.hasOwnProperty(name)) {
+ member = members[name];
+ if (typeof member == 'function') {
+ member.$owner = cls;
+ member.$name = name;
+ }
+
+ target[name] = member;
+ }
+ }
+ }
+ },
+
+ /**
+ * @method
+ * Defines a class or override. A basic class is defined like this:
+ *
+ * Ext.define('My.awesome.Class', {
+ * someProperty: 'something',
+ *
+ * someMethod: function(s) {
+ * alert(s + this.someProperty);
+ * }
+ *
+ * ...
+ * });
+ *
+ * var obj = new My.awesome.Class();
+ *
+ * obj.someMethod('Say '); // alerts 'Say something'
+ *
+ * To create an anonymous class, pass `null` for the `className`:
+ *
+ * Ext.define(null, {
+ * constructor: function () {
+ * // ...
+ * }
+ * });
+ *
+ * In some cases, it is helpful to create a nested scope to contain some private
+ * properties. The best way to do this is to pass a function instead of an object
+ * as the second parameter. This function will be called to produce the class
+ * body:
+ *
+ * Ext.define('MyApp.foo.Bar', function () {
+ * var id = 0;
+ *
+ * return {
+ * nextId: function () {
+ * return ++id;
+ * }
+ * };
+ * });
+ *
+ * When using this form of `Ext.define`, the function is passed a reference to its
+ * class. This can be used as an efficient way to access any static properties you
+ * may have:
+ *
+ * Ext.define('MyApp.foo.Bar', function (Bar) {
+ * return {
+ * statics: {
+ * staticMethod: function () {
+ * // ...
+ * }
+ * },
+ *
+ * method: function () {
+ * return Bar.staticMethod();
+ * }
+ * };
+ * });
+ *
+ * To define an override, include the `override` property. The content of an
+ * override is aggregated with the specified class in order to extend or modify
+ * that class. This can be as simple as setting default property values or it can
+ * extend and/or replace methods. This can also extend the statics of the class.
+ *
+ * One use for an override is to break a large class into manageable pieces.
+ *
+ * // File: /src/app/Panel.js
+ *
+ * Ext.define('My.app.Panel', {
+ * extend: 'Ext.panel.Panel',
+ *
+ * constructor: function (config) {
+ * this.callParent(arguments); // calls Ext.panel.Panel's constructor
+ * //...
+ * },
+ *
+ * statics: {
+ * method: function () {
+ * return 'abc';
+ * }
+ * }
+ * });
+ *
+ * // File: /src/app/PanelPart2.js
+ * Ext.define('My.app.PanelPart2', {
+ * override: 'My.app.Panel',
+ *
+ * constructor: function (config) {
+ * this.callParent(arguments); // calls My.app.Panel's constructor
+ * //...
+ * }
+ * });
+ *
+ * Another use of overrides is to provide optional parts of classes that can be
+ * independently required. In this case, the class may even be unaware of the
+ * override altogether.
+ *
+ * Ext.define('My.ux.CoolTip', {
+ * override: 'Ext.tip.ToolTip',
+ *
+ * constructor: function (config) {
+ * this.callParent(arguments); // calls Ext.tip.ToolTip's constructor
+ * //...
+ * }
+ * });
+ *
+ * Overrides can also contain statics:
+ *
+ * Ext.define('My.app.BarMod', {
+ * override: 'Ext.foo.Bar',
+ *
+ * statics: {
+ * method: function (x) {
+ * return this.callParent([x * 2]); // call Ext.foo.Bar.method
+ * }
+ * }
+ * });
+ *
+ * @param {String} className The class name to create in string dot-namespaced format, for example:
+ * 'My.very.awesome.Class', 'FeedViewer.plugin.CoolPager'
+ * It is highly recommended to follow this simple convention:
+ * - The root and the class name are 'CamelCased'
+ * - Everything else is lower-cased
+ * Pass `null` to create an anonymous class.
+ * @param {Object} data The key - value pairs of properties to apply to this class. Property names can be of any valid
+ * strings, except those in the reserved listed below:
+ * - `mixins`
+ * - `statics`
+ * - `config`
+ * - `alias`
+ * - `self`
+ * - `singleton`
+ * - `alternateClassName`
+ * - `override`
+ *
+ * @param {Function} createdFn Optional callback to execute after the class is created, the execution scope of which
+ * (`this`) will be the newly created class itself.
+ * @return {Ext.Base}
+ * @markdown
+ * @member Ext
+ * @method define
+ */
+ define: function (className, body, createdFn) {
+ var override = body.override,
+ cls, extend, name, namespace;
+
+ if (override) {
+ delete body.override;
+ cls = Ext.getClassByName(override);
+ Ext.override(cls, body);
+ } else {
+ if (className) {
+ namespace = Ext.createNamespace(className, true);
+ name = className.substring(className.lastIndexOf('.')+1);
+ }
+
+ cls = function ctor () {
+ this.constructor.apply(this, arguments);
+ }
+
+ if (className) {
+ cls.displayName = className;
+ }
+ cls.$isClass = true;
+ cls.callParent = Ext.Base.callParent;
+
+ if (typeof body == 'function') {
+ body = body(cls);
+ }
+
+ extend = body.extend;
+ if (extend) {
+ delete body.extend;
+ if (typeof extend == 'string') {
+ extend = Ext.getClassByName(extend);
+ }
+ } else {
+ extend = Base;
+ }
+
+ Ext.extend(cls, extend, body);
+ if (cls.prototype.constructor === cls) {
+ delete cls.prototype.constructor;
+ }
+
+ // Not extending a class which derives from Base...
+ if (!cls.prototype.$isClass) {
+ Ext.applyIf(cls.prototype, Base.prototype);
+ }
+ cls.prototype.self = cls;
+
+ if (body.xtype) {
+ Ext.reg(body.xtype, cls);
+ }
+ cls = body.singleton ? new cls() : cls;
+ if (className) {
+ namespace[name] = cls;
+ }
+ }
+
+ if (createdFn) {
+ createdFn.call(cls);
+ }
+
+ return cls;
+ },
+
+ /**
+ * Overrides members of the specified `target` with the given values.
+ *
+ * If the `target` is a function, it is assumed to be a constructor and the contents
+ * of `overrides` are applied to its `prototype` using {@link Ext#apply Ext.apply}.
+ *
+ * If the `target` is an instance of a class created using {@link #define},
+ * the `overrides` are applied to only that instance. In this case, methods are
+ * specially processed to allow them to use {@link Ext.Base#callParent}.
+ *
+ * var panel = new Ext.Panel({ ... });
+ *
+ * Ext.override(panel, {
+ * initComponent: function () {
+ * // extra processing...
+ *
+ * this.callParent();
+ * }
+ * });
+ *
+ * If the `target` is none of these, the `overrides` are applied to the `target`
+ * using {@link Ext#apply Ext.apply}.
+ *
+ * Please refer to {@link Ext#define Ext.define} for further details.
+ *
+ * @param {Object} target The target to override.
+ * @param {Object} overrides The properties to add or replace on `target`.
+ * @method override
+ */
+ override: function (target, overrides) {
+ var proto, statics;
+
+ if (overrides) {
+ if (target.$isClass) {
+ statics = overrides.statics;
+ if (statics) {
+ delete overrides.statics;
+ }
+
+ Ext.addMembers(target, target.prototype, overrides, true);
+ if (statics) {
+ Ext.addMembers(target, target, statics);
+ }
+ } else if (typeof target == 'function') {
+ proto = target.prototype;
+ Ext.apply(proto, overrides);
+ if(Ext.isIE && overrides.hasOwnProperty('toString')){
+ proto.toString = overrides.toString;
+ }
+ } else {
+ var owner = target.self,
+ name, value;
+
+ if (owner && owner.$isClass) {
+ for (name in overrides) {
+ if (overrides.hasOwnProperty(name)) {
+ value = overrides[name];
+
+ if (typeof value == 'function') {
+ //<debug>
+ if (owner.$className) {
+ value.displayName = owner.$className + '#' + name;
+ }
+ //</debug>
+
+ value.$name = name;
+ value.$owner = owner;
+ value.$previous = target.hasOwnProperty(name)
+ ? target[name] // already hooked, so call previous hook
+ : callOverrideParent; // calls by name on prototype
+ }
+
+ target[name] = value;
+ }
+ }
+ } else {
+ Ext.apply(target, overrides);
+
+ if (!target.constructor.$isClass) {
+ target.constructor.prototype.callParent = Base.prototype.callParent;
+ target.constructor.callParent = Base.callParent;
+ }
+ }
+ }
+ }
+ },
+
+ /**
+ * Creates namespaces to be used for scoping variables and classes so that they are not global.
+ * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
+ * <pre><code>
+Ext.namespace('Company', 'Company.data');
+Ext.namespace('Company.data'); // equivalent and preferable to above syntax
+Company.Widget = function() { ... }
+Company.data.CustomStore = function(config) { ... }
+</code></pre>
+ * @param {String} namespace1
+ * @param {String} namespace2
+ * @param {String} etc
+ * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created)
+ * @method namespace
+ */
+ namespace : function(){
+ var len1 = arguments.length,
+ i = 0,
+ len2,
+ j,
+ main,
+ ns,
+ sub,
+ current;
+
+ for(; i < len1; ++i) {
+ main = arguments[i];
+ ns = arguments[i].split('.');
+ current = window[ns[0]];
+ if (current === undefined) {
+ current = window[ns[0]] = {};
+ }
+ sub = ns.slice(1);
+ len2 = sub.length;
+ for(j = 0; j < len2; ++j) {
+ current = current[sub[j]] = current[sub[j]] || {};
+ }
+ }
+ return current;
+ },
+
+ /**
+ * Takes an object and converts it to an encoded URL. e.g. Ext.urlEncode({foo: 1, bar: 2}); would return "foo=1&bar=2". Optionally, property values can be arrays, instead of keys and the resulting string that's returned will contain a name/value pair for each array value.
+ * @param {Object} o
+ * @param {String} pre (optional) A prefix to add to the url encoded string
+ * @return {String}
+ */
+ urlEncode : function(o, pre){
+ var empty,
+ buf = [],
+ e = encodeURIComponent;
+
+ Ext.iterate(o, function(key, item){
+ empty = Ext.isEmpty(item);
+ Ext.each(empty ? key : item, function(val){
+ buf.push('&', e(key), '=', (!Ext.isEmpty(val) && (val != key || !empty)) ? (Ext.isDate(val) ? Ext.encode(val).replace(/"/g, '') : e(val)) : '');
+ });
+ });
+ if(!pre){
+ buf.shift();
+ pre = '';
+ }
+ return pre + buf.join('');
+ },
+
+ /**
+ * Takes an encoded URL and and converts it to an object. Example: <pre><code>
+Ext.urlDecode("foo=1&bar=2"); // returns {foo: "1", bar: "2"}
+Ext.urlDecode("foo=1&bar=2&bar=3&bar=4", false); // returns {foo: "1", bar: ["2", "3", "4"]}
+</code></pre>
+ * @param {String} string
+ * @param {Boolean} overwrite (optional) Items of the same name will overwrite previous values instead of creating an an array (Defaults to false).
+ * @return {Object} A literal with members
+ */
+ urlDecode : function(string, overwrite){
+ if(Ext.isEmpty(string)){
+ return {};
+ }
+ var obj = {},
+ pairs = string.split('&'),
+ d = decodeURIComponent,
+ name,
+ value;
+ Ext.each(pairs, function(pair) {
+ pair = pair.split('=');
+ name = d(pair[0]);
+ value = d(pair[1]);
+ obj[name] = overwrite || !obj[name] ? value :
+ [].concat(obj[name]).concat(value);
+ });
+ return obj;
+ },
+
+ /**
+ * Appends content to the query string of a URL, handling logic for whether to place
+ * a question mark or ampersand.
+ * @param {String} url The URL to append to.
+ * @param {String} s The content to append to the URL.
+ * @return (String) The resulting URL
+ */
+ urlAppend : function(url, s){
+ if(!Ext.isEmpty(s)){
+ return url + (url.indexOf('?') === -1 ? '?' : '&') + s;
+ }
+ return url;
+ },
+
+ /**
+ * Converts any iterable (numeric indices and a length property) into a true array
+ * Don't use this on strings. IE doesn't support "abc"[0] which this implementation depends on.
+ * For strings, use this instead: "abc".match(/./g) => [a,b,c];
+ * @param {Iterable} the iterable object to be turned into a true Array.
+ * @return (Array) array
+ */
+ toArray : function(){
+ return isIE ?
+ function(a, i, j, res){
+ res = [];
+ for(var x = 0, len = a.length; x < len; x++) {
+ res.push(a[x]);
+ }
+ return res.slice(i || 0, j || res.length);
+ } :
+ function(a, i, j){
+ return Array.prototype.slice.call(a, i || 0, j || a.length);
+ };
+ }(),
+
+ isIterable : function(v){
+ //check for array or arguments
+ if(Ext.isArray(v) || v.callee){
+ return true;
+ }
+ //check for node list type
+ if(/NodeList|HTMLCollection/.test(toString.call(v))){
+ return true;
+ }
+ //NodeList has an item and length property
+ //IXMLDOMNodeList has nextNode method, needs to be checked first.
+ return ((typeof v.nextNode != 'undefined' || v.item) && Ext.isNumber(v.length));
+ },
+
+ /**
+ * Iterates an array calling the supplied function.
+ * @param {Array/NodeList/Mixed} array The array to be iterated. If this
+ * argument is not really an array, the supplied function is called once.
+ * @param {Function} fn The function to be called with each item. If the
+ * supplied function returns false, iteration stops and this method returns
+ * the current <code>index</code>. This function is called with
+ * the following arguments:
+ * <div class="mdetail-params"><ul>
+ * <li><code>item</code> : <i>Mixed</i>
+ * <div class="sub-desc">The item at the current <code>index</code>
+ * in the passed <code>array</code></div></li>
+ * <li><code>index</code> : <i>Number</i>
+ * <div class="sub-desc">The current index within the array</div></li>
+ * <li><code>allItems</code> : <i>Array</i>
+ * <div class="sub-desc">The <code>array</code> passed as the first
+ * argument to <code>Ext.each</code>.</div></li>
+ * </ul></div>
+ * @param {Object} scope The scope (<code>this</code> reference) in which the specified function is executed.
+ * Defaults to the <code>item</code> at the current <code>index</code>
+ * within the passed <code>array</code>.
+ * @return See description for the fn parameter.
+ */
+ each : function(array, fn, scope){
+ if(Ext.isEmpty(array, true)){
+ return;
+ }
+ if(!Ext.isIterable(array) || Ext.isPrimitive(array)){
+ array = [array];
+ }
+ for(var i = 0, len = array.length; i < len; i++){
+ if(fn.call(scope || array[i], array[i], i, array) === false){
+ return i;
+ };
+ }
+ },
+
+ /**
+ * Iterates either the elements in an array, or each of the properties in an object.
+ * <b>Note</b>: If you are only iterating arrays, it is better to call {@link #each}.
+ * @param {Object/Array} object The object or array to be iterated
+ * @param {Function} fn The function to be called for each iteration.
+ * The iteration will stop if the supplied function returns false, or
+ * all array elements / object properties have been covered. The signature
+ * varies depending on the type of object being interated:
+ * <div class="mdetail-params"><ul>
+ * <li>Arrays : <tt>(Object item, Number index, Array allItems)</tt>
+ * <div class="sub-desc">
+ * When iterating an array, the supplied function is called with each item.</div></li>
+ * <li>Objects : <tt>(String key, Object value, Object)</tt>
+ * <div class="sub-desc">
+ * When iterating an object, the supplied function is called with each key-value pair in
+ * the object, and the iterated object</div></li>
+ * </ul></div>
+ * @param {Object} scope The scope (<code>this</code> reference) in which the specified function is executed. Defaults to
+ * the <code>object</code> being iterated.
+ */
+ iterate : function(obj, fn, scope){
+ if(Ext.isEmpty(obj)){
+ return;
+ }
+ if(Ext.isIterable(obj)){
+ Ext.each(obj, fn, scope);
+ return;
+ }else if(typeof obj == 'object'){
+ for(var prop in obj){
+ if(obj.hasOwnProperty(prop)){
+ if(fn.call(scope || obj, prop, obj[prop], obj) === false){
+ return;
+ };
+ }
+ }
+ }
+ },
+
+ /**
+ * Return the dom node for the passed String (id), dom node, or Ext.Element.
+ * Optional 'strict' flag is needed for IE since it can return 'name' and
+ * 'id' elements by using getElementById.
+ * Here are some examples:
+ * <pre><code>
+// gets dom node based on id
+var elDom = Ext.getDom('elId');
+// gets dom node based on the dom node
+var elDom1 = Ext.getDom(elDom);
+
+// If we don&#39;t know if we are working with an
+// Ext.Element or a dom node use Ext.getDom
+function(el){
+ var dom = Ext.getDom(el);
+ // do something with the dom node
+}
+ * </code></pre>
+ * <b>Note</b>: the dom node to be found actually needs to exist (be rendered, etc)
+ * when this method is called to be successful.
+ * @param {Mixed} el
+ * @return HTMLElement
+ */
+ getDom : function(el, strict){
+ if(!el || !DOC){
+ return null;
+ }
+ if (el.dom){
+ return el.dom;
+ } else {
+ if (typeof el == 'string') {
+ var e = DOC.getElementById(el);
+ // IE returns elements with the 'name' and 'id' attribute.
+ // we do a strict check to return the element with only the id attribute
+ if (e && isIE && strict) {
+ if (el == e.getAttribute('id')) {
+ return e;
+ } else {
+ return null;
+ }
+ }
+ return e;
+ } else {
+ return el;
+ }
+ }
+ },
+
+ /**
+ * Returns the current document body as an {@link Ext.Element}.
+ * @return Ext.Element The document body
+ */
+ getBody : function(){
+ return Ext.get(DOC.body || DOC.documentElement);
+ },
+
+ /**
+ * Returns the current document body as an {@link Ext.Element}.
+ * @return Ext.Element The document body
+ * @method
+ */
+ getHead : function() {
+ var head;
+
+ return function() {
+ if (head == undefined) {
+ head = Ext.get(DOC.getElementsByTagName("head")[0]);
+ }
+
+ return head;
+ };
+ }(),
+
+ /**
+ * <p>Removes this element from the document, removes all DOM event listeners, and deletes the cache reference.
+ * All DOM event listeners are removed from this element. If {@link Ext#enableNestedListenerRemoval} is
+ * <code>true</code>, then DOM event listeners are also removed from all child nodes. The body node
+ * will be ignored if passed in.</p>
+ * @param {HTMLElement} node The node to remove
+ * @method
+ */
+ removeNode : isIE && !isIE8 ? function(){
+ var d;
+ return function(n){
+ if(n && n.tagName != 'BODY'){
+ (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n, true) : Ext.EventManager.removeAll(n);
+ d = d || DOC.createElement('div');
+ d.appendChild(n);
+ d.innerHTML = '';
+ delete Ext.elCache[n.id];
+ }
+ };
+ }() : function(n){
+ if(n && n.parentNode && n.tagName != 'BODY'){
+ (Ext.enableNestedListenerRemoval) ? Ext.EventManager.purgeElement(n, true) : Ext.EventManager.removeAll(n);
+ n.parentNode.removeChild(n);
+ delete Ext.elCache[n.id];
+ }
+ },
+
+ /**
+ * <p>Returns true if the passed value is empty.</p>
+ * <p>The value is deemed to be empty if it is<div class="mdetail-params"><ul>
+ * <li>null</li>
+ * <li>undefined</li>
+ * <li>an empty array</li>
+ * <li>a zero length string (Unless the <tt>allowBlank</tt> parameter is <tt>true</tt>)</li>
+ * </ul></div>
+ * @param {Mixed} value The value to test
+ * @param {Boolean} allowBlank (optional) true to allow empty strings (defaults to false)
+ * @return {Boolean}
+ */
+ isEmpty : function(v, allowBlank){
+ return v === null || v === undefined || ((Ext.isArray(v) && !v.length)) || (!allowBlank ? v === '' : false);
+ },
+
+ /**
+ * Returns true if the passed value is a JavaScript array, otherwise false.
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isArray : function(v){
+ return toString.apply(v) === '[object Array]';
+ },
+
+ /**
+ * Returns true if the passed object is a JavaScript date object, otherwise false.
+ * @param {Object} object The object to test
+ * @return {Boolean}
+ */
+ isDate : function(v){
+ return toString.apply(v) === '[object Date]';
+ },
+
+ /**
+ * Returns true if the passed value is a JavaScript Object, otherwise false.
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isObject : function(v){
+ return !!v && Object.prototype.toString.call(v) === '[object Object]';
+ },
+
+ /**
+ * Returns true if the passed value is a JavaScript 'primitive', a string, number or boolean.
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isPrimitive : function(v){
+ return Ext.isString(v) || Ext.isNumber(v) || Ext.isBoolean(v);
+ },
+
+ /**
+ * Returns true if the passed value is a JavaScript Function, otherwise false.
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isFunction : function(v){
+ return toString.apply(v) === '[object Function]';
+ },
+
+ /**
+ * Returns true if the passed value is a number. Returns false for non-finite numbers.
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isNumber : function(v){
+ return typeof v === 'number' && isFinite(v);
+ },
+
+ /**
+ * Returns true if the passed value is a string.
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isString : function(v){
+ return typeof v === 'string';
+ },
+
+ /**
+ * Returns true if the passed value is a boolean.
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isBoolean : function(v){
+ return typeof v === 'boolean';
+ },
+
+ /**
+ * Returns true if the passed value is an HTMLElement
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isElement : function(v) {
+ return v ? !!v.tagName : false;
+ },
+
+ /**
+ * Returns true if the passed value is not undefined.
+ * @param {Mixed} value The value to test
+ * @return {Boolean}
+ */
+ isDefined : function(v){
+ return typeof v !== 'undefined';
+ },
+
+ /**
+ * True if the detected browser is Opera.
+ * @type Boolean
+ */
+ isOpera : isOpera,
+ /**
+ * True if the detected browser uses WebKit.
+ * @type Boolean
+ */
+ isWebKit : isWebKit,
+ /**
+ * True if the detected browser is Chrome.
+ * @type Boolean
+ */
+ isChrome : isChrome,
+ /**
+ * True if the detected browser is Safari.
+ * @type Boolean
+ */
+ isSafari : isSafari,
+ /**
+ * True if the detected browser is Safari 3.x.
+ * @type Boolean
+ */
+ isSafari3 : isSafari3,
+ /**
+ * True if the detected browser is Safari 4.x.
+ * @type Boolean
+ */
+ isSafari4 : isSafari4,
+ /**
+ * True if the detected browser is Safari 2.x.
+ * @type Boolean
+ */
+ isSafari2 : isSafari2,
+ /**
+ * True if the detected browser is Internet Explorer.
+ * @type Boolean
+ */
+ isIE : isIE,
+ /**
+ * True if the detected browser is Internet Explorer 6.x.
+ * @type Boolean
+ */
+ isIE6 : isIE6,
+ /**
+ * True if the detected browser is Internet Explorer 7.x.
+ * @type Boolean
+ */
+ isIE7 : isIE7,
+ /**
+ * True if the detected browser is Internet Explorer 8.x.
+ * @type Boolean
+ */
+ isIE8 : isIE8,
+ /**
+ * True if the detected browser is Internet Explorer 9.x.
+ * @type Boolean
+ */
+ isIE9 : isIE9,
+
+ /**
+ * True if the detected browser is Internet Explorer 10.x
+ * @type Boolean
+ */
+ isIE10 : isIE10,
+
+ /**
+ * True if the detected browser is Internet Explorer 9.x or lower
+ * @type Boolean
+ */
+ isIE9m : isIE9m,
+
+ /**
+ * True if the detected browser is Internet Explorer 10.x or higher
+ * @type Boolean
+ */
+ isIE10p : isIE && !(isIE6 || isIE7 || isIE8 || isIE9),
+
+ // IE10 quirks behaves like Gecko/WebKit quirks, so don't include it here
+ // Used internally
+ isIEQuirks: isIE && (!isStrict && (isIE6 || isIE7 || isIE8 || isIE9)),
+
+ /**
+ * True if the detected browser uses the Gecko layout engine (e.g. Mozilla, Firefox).
+ * @type Boolean
+ */
+ isGecko : isGecko,
+ /**
+ * True if the detected browser uses a pre-Gecko 1.9 layout engine (e.g. Firefox 2.x).
+ * @type Boolean
+ */
+ isGecko2 : isGecko2,
+ /**
+ * True if the detected browser uses a Gecko 1.9+ layout engine (e.g. Firefox 3.x).
+ * @type Boolean
+ */
+ isGecko3 : isGecko3,
+ /**
+ * True if the detected browser is Internet Explorer running in non-strict mode.
+ * @type Boolean
+ */
+ isBorderBox : isBorderBox,
+ /**
+ * True if the detected platform is Linux.
+ * @type Boolean
+ */
+ isLinux : isLinux,
+ /**
+ * True if the detected platform is Windows.
+ * @type Boolean
+ */
+ isWindows : isWindows,
+ /**
+ * True if the detected platform is Mac OS.
+ * @type Boolean
+ */
+ isMac : isMac,
+ /**
+ * True if the detected platform is Adobe Air.
+ * @type Boolean
+ */
+ isAir : isAir
+ });
+
+ /**
+ * Creates namespaces to be used for scoping variables and classes so that they are not global.
+ * Specifying the last node of a namespace implicitly creates all other nodes. Usage:
+ * <pre><code>
+Ext.namespace('Company', 'Company.data');
+Ext.namespace('Company.data'); // equivalent and preferable to above syntax
+Company.Widget = function() { ... }
+Company.data.CustomStore = function(config) { ... }
+</code></pre>
+ * @param {String} namespace1
+ * @param {String} namespace2
+ * @param {String} etc
+ * @return {Object} The namespace object. (If multiple arguments are passed, this will be the last namespace created)
+ * @method ns
+ */
+ Ext.ns = Ext.namespace;
+})();
+
+Ext.ns('Ext.util', 'Ext.lib', 'Ext.data', 'Ext.supports');
+
+Ext.elCache = {};
+
+/**
+ * @class Function
+ * These functions are available on every Function object (any JavaScript function).
+ */
+Ext.apply(Function.prototype, {
+ /**
+ * Creates an interceptor function. The passed function is called before the original one. If it returns false,
+ * the original one is not called. The resulting function returns the results of the original function.
+ * The passed function is called with the parameters of the original function. Example usage:
+ * <pre><code>
+var sayHi = function(name){
+ alert('Hi, ' + name);
+}
+
+sayHi('Fred'); // alerts "Hi, Fred"
+
+// create a new function that validates input without
+// directly modifying the original function:
+var sayHiToFriend = sayHi.createInterceptor(function(name){
+ return name == 'Brian';
+});
+
+sayHiToFriend('Fred'); // no alert
+sayHiToFriend('Brian'); // alerts "Hi, Brian"
+</code></pre>
+ * @param {Function} fcn The function to call before the original
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the passed function is executed.
+ * <b>If omitted, defaults to the scope in which the original function is called or the browser window.</b>
+ * @return {Function} The new function
+ */
+ createInterceptor : function(fcn, scope){
+ var method = this;
+ return !Ext.isFunction(fcn) ?
+ this :
+ function() {
+ var me = this,
+ args = arguments;
+ fcn.target = me;
+ fcn.method = method;
+ return (fcn.apply(scope || me || window, args) !== false) ?
+ method.apply(me || window, args) :
+ null;
+ };
+ },
+
+ /**
+ * Creates a callback that passes arguments[0], arguments[1], arguments[2], ...
+ * Call directly on any function. Example: <code>myFunction.createCallback(arg1, arg2)</code>
+ * Will create a function that is bound to those 2 args. <b>If a specific scope is required in the
+ * callback, use {@link #createDelegate} instead.</b> The function returned by createCallback always
+ * executes in the window scope.
+ * <p>This method is required when you want to pass arguments to a callback function. If no arguments
+ * are needed, you can simply pass a reference to the function as a callback (e.g., callback: myFn).
+ * However, if you tried to pass a function with arguments (e.g., callback: myFn(arg1, arg2)) the function
+ * would simply execute immediately when the code is parsed. Example usage:
+ * <pre><code>
+var sayHi = function(name){
+ alert('Hi, ' + name);
+}
+
+// clicking the button alerts "Hi, Fred"
+new Ext.Button({
+ text: 'Say Hi',
+ renderTo: Ext.getBody(),
+ handler: sayHi.createCallback('Fred')
+});
+</code></pre>
+ * @return {Function} The new function
+ */
+ createCallback : function(/*args...*/){
+ // make args available, in function below
+ var args = arguments,
+ method = this;
+ return function() {
+ return method.apply(window, args);
+ };
+ },
+
+ /**
+ * Creates a delegate (callback) that sets the scope to obj.
+ * Call directly on any function. Example: <code>this.myFunction.createDelegate(this, [arg1, arg2])</code>
+ * Will create a function that is automatically scoped to obj so that the <tt>this</tt> variable inside the
+ * callback points to obj. Example usage:
+ * <pre><code>
+var sayHi = function(name){
+ // Note this use of "this.text" here. This function expects to
+ // execute within a scope that contains a text property. In this
+ // example, the "this" variable is pointing to the btn object that
+ // was passed in createDelegate below.
+ alert('Hi, ' + name + '. You clicked the "' + this.text + '" button.');
+}
+
+var btn = new Ext.Button({
+ text: 'Say Hi',
+ renderTo: Ext.getBody()
+});
+
+// This callback will execute in the scope of the
+// button instance. Clicking the button alerts
+// "Hi, Fred. You clicked the "Say Hi" button."
+btn.on('click', sayHi.createDelegate(btn, ['Fred']));
+</code></pre>
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
+ * <b>If omitted, defaults to the browser window.</b>
+ * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
+ * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
+ * if a number the args are inserted at the specified position
+ * @return {Function} The new function
+ */
+ createDelegate : function(obj, args, appendArgs){
+ var method = this;
+ return function() {
+ var callArgs = args || arguments;
+ if (appendArgs === true){
+ callArgs = Array.prototype.slice.call(arguments, 0);
+ callArgs = callArgs.concat(args);
+ }else if (Ext.isNumber(appendArgs)){
+ callArgs = Array.prototype.slice.call(arguments, 0); // copy arguments first
+ var applyArgs = [appendArgs, 0].concat(args); // create method call params
+ Array.prototype.splice.apply(callArgs, applyArgs); // splice them in
+ }
+ return method.apply(obj || window, callArgs);
+ };
+ },
+
+ /**
+ * Calls this function after the number of millseconds specified, optionally in a specific scope. Example usage:
+ * <pre><code>
+var sayHi = function(name){
+ alert('Hi, ' + name);
+}
+
+// executes immediately:
+sayHi('Fred');
+
+// executes after 2 seconds:
+sayHi.defer(2000, this, ['Fred']);
+
+// this syntax is sometimes useful for deferring
+// execution of an anonymous function:
+(function(){
+ alert('Anonymous');
+}).defer(100);
+</code></pre>
+ * @param {Number} millis The number of milliseconds for the setTimeout call (if less than or equal to 0 the function is executed immediately)
+ * @param {Object} scope (optional) The scope (<code><b>this</b></code> reference) in which the function is executed.
+ * <b>If omitted, defaults to the browser window.</b>
+ * @param {Array} args (optional) Overrides arguments for the call. (Defaults to the arguments passed by the caller)
+ * @param {Boolean/Number} appendArgs (optional) if True args are appended to call args instead of overriding,
+ * if a number the args are inserted at the specified position
+ * @return {Number} The timeout id that can be used with clearTimeout
+ */
+ defer : function(millis, obj, args, appendArgs){
+ var fn = this.createDelegate(obj, args, appendArgs);
+ if(millis > 0){
+ return setTimeout(fn, millis);
+ }
+ fn();
+ return 0;
+ }
+});
+
+/**
+ * @class String
+ * These functions are available on every String object.
+ */
+Ext.applyIf(String, {
+ /**
+ * Allows you to define a tokenized string and pass an arbitrary number of arguments to replace the tokens. Each
+ * token must be unique, and must increment in the format {0}, {1}, etc. Example usage:
+ * <pre><code>
+var cls = 'my-class', text = 'Some text';
+var s = String.format('&lt;div class="{0}">{1}&lt;/div>', cls, text);
+// s now contains the string: '&lt;div class="my-class">Some text&lt;/div>'
+ * </code></pre>
+ * @param {String} string The tokenized string to be formatted
+ * @param {String} value1 The value to replace token {0}
+ * @param {String} value2 Etc...
+ * @return {String} The formatted string
+ * @static
+ */
+ format : function(format){
+ var args = Ext.toArray(arguments, 1);
+ return format.replace(/\{(\d+)\}/g, function(m, i){
+ return args[i];
+ });
+ }
+});
+
+/**
+ * @class Array
+ */
+Ext.applyIf(Array.prototype, {
+ /**
+ * Checks whether or not the specified object exists in the array.
+ * @param {Object} o The object to check for
+ * @param {Number} from (Optional) The index at which to begin the search
+ * @return {Number} The index of o in the array (or -1 if it is not found)
+ */
+ indexOf : function(o, from){
+ var len = this.length;
+ from = from || 0;
+ from += (from < 0) ? len : 0;
+ for (; from < len; ++from){
+ if(this[from] === o){
+ return from;
+ }
+ }
+ return -1;
+ },
+
+ /**
+ * Removes the specified object from the array. If the object is not found nothing happens.
+ * @param {Object} o The object to remove
+ * @return {Array} this array
+ */
+ remove : function(o){
+ var index = this.indexOf(o);
+ if(index != -1){
+ this.splice(index, 1);
+ }
+ return this;
+ }
+});
+/**
+ * @class Ext.util.TaskRunner
+ * Provides the ability to execute one or more arbitrary tasks in a multithreaded
+ * manner. Generally, you can use the singleton {@link Ext.TaskMgr} instead, but
+ * if needed, you can create separate instances of TaskRunner. Any number of
+ * separate tasks can be started at any time and will run independently of each
+ * other. Example usage:
+ * <pre><code>
+// Start a simple clock task that updates a div once per second
+var updateClock = function(){
+ Ext.fly('clock').update(new Date().format('g:i:s A'));
+}
+var task = {
+ run: updateClock,
+ interval: 1000 //1 second
+}
+var runner = new Ext.util.TaskRunner();
+runner.start(task);
+
+// equivalent using TaskMgr
+Ext.TaskMgr.start({
+ run: updateClock,
+ interval: 1000
+});
+
+ * </code></pre>
+ * <p>See the {@link #start} method for details about how to configure a task object.</p>
+ * Also see {@link Ext.util.DelayedTask}.
+ *
+ * @constructor
+ * @param {Number} interval (optional) The minimum precision in milliseconds supported by this TaskRunner instance
+ * (defaults to 10)
+ */
+Ext.util.TaskRunner = function(interval){
+ interval = interval || 10;
+ var tasks = [],
+ removeQueue = [],
+ id = 0,
+ running = false,
+
+ // private
+ stopThread = function(){
+ running = false;
+ clearInterval(id);
+ id = 0;
+ },
+
+ // private
+ startThread = function(){
+ if(!running){
+ running = true;
+ id = setInterval(runTasks, interval);
+ }
+ },
+
+ // private
+ removeTask = function(t){
+ removeQueue.push(t);
+ if(t.onStop){
+ t.onStop.apply(t.scope || t);
+ }
+ },
+
+ // private
+ runTasks = function(){
+ var rqLen = removeQueue.length,
+ now = new Date().getTime();
+
+ if(rqLen > 0){
+ for(var i = 0; i < rqLen; i++){
+ tasks.remove(removeQueue[i]);
+ }
+ removeQueue = [];
+ if(tasks.length < 1){
+ stopThread();
+ return;
+ }
+ }
+ for(var i = 0, t, itime, rt, len = tasks.length; i < len; ++i){
+ t = tasks[i];
+ itime = now - t.taskRunTime;
+ if(t.interval <= itime){
+ rt = t.run.apply(t.scope || t, t.args || [++t.taskRunCount]);
+ t.taskRunTime = now;
+ if(rt === false || t.taskRunCount === t.repeat){
+ removeTask(t);
+ return;
+ }
+ }
+ if(t.duration && t.duration <= (now - t.taskStartTime)){
+ removeTask(t);
+ }
+ }
+ };
+
+ /**
+ * Starts a new task.
+ * @method start
+ * @param {Object} task <p>A config object that supports the following properties:<ul>
+ * <li><code>run</code> : Function<div class="sub-desc"><p>The function to execute each time the task is invoked. The
+ * function will be called at each interval and passed the <code>args</code> argument if specified, and the
+ * current invocation count if not.</p>
+ * <p>If a particular scope (<code>this</code> reference) is required, be sure to specify it using the <code>scope</code> argument.</p>
+ * <p>Return <code>false</code> from this function to terminate the task.</p></div></li>
+ * <li><code>interval</code> : Number<div class="sub-desc">The frequency in milliseconds with which the task
+ * should be invoked.</div></li>
+ * <li><code>args</code> : Array<div class="sub-desc">(optional) An array of arguments to be passed to the function
+ * specified by <code>run</code>. If not specified, the current invocation count is passed.</div></li>
+ * <li><code>scope</code> : Object<div class="sub-desc">(optional) The scope (<tt>this</tt> reference) in which to execute the
+ * <code>run</code> function. Defaults to the task config object.</div></li>
+ * <li><code>duration</code> : Number<div class="sub-desc">(optional) The length of time in milliseconds to invoke
+ * the task before stopping automatically (defaults to indefinite).</div></li>
+ * <li><code>repeat</code> : Number<div class="sub-desc">(optional) The number of times to invoke the task before
+ * stopping automatically (defaults to indefinite).</div></li>
+ * </ul></p>
+ * <p>Before each invocation, Ext injects the property <code>taskRunCount</code> into the task object so
+ * that calculations based on the repeat count can be performed.</p>
+ * @return {Object} The task
+ */
+ this.start = function(task){
+ tasks.push(task);
+ task.taskStartTime = new Date().getTime();
+ task.taskRunTime = 0;
+ task.taskRunCount = 0;
+ startThread();
+ return task;
+ };
+
+ /**
+ * Stops an existing running task.
+ * @method stop
+ * @param {Object} task The task to stop
+ * @return {Object} The task
+ */
+ this.stop = function(task){
+ removeTask(task);
+ return task;
+ };
+
+ /**
+ * Stops all tasks that are currently running.
+ * @method stopAll
+ */
+ this.stopAll = function(){
+ stopThread();
+ for(var i = 0, len = tasks.length; i < len; i++){
+ if(tasks[i].onStop){
+ tasks[i].onStop();
+ }
+ }
+ tasks = [];
+ removeQueue = [];
+ };
+};
+
+/**
+ * @class Ext.TaskMgr
+ * @extends Ext.util.TaskRunner
+ * A static {@link Ext.util.TaskRunner} instance that can be used to start and stop arbitrary tasks. See
+ * {@link Ext.util.TaskRunner} for supported methods and task config properties.
+ * <pre><code>
+// Start a simple clock task that updates a div once per second
+var task = {
+ run: function(){
+ Ext.fly('clock').update(new Date().format('g:i:s A'));
+ },
+ interval: 1000 //1 second
+}
+Ext.TaskMgr.start(task);
+</code></pre>
+ * <p>See the {@link #start} method for details about how to configure a task object.</p>
+ * @singleton
+ */
+Ext.TaskMgr = new Ext.util.TaskRunner();(function(){
+ var libFlyweight;
+
+ function fly(el) {
+ if (!libFlyweight) {
+ libFlyweight = new Ext.Element.Flyweight();
+ }
+ libFlyweight.dom = el;
+ return libFlyweight;
+ }
+
+ (function(){
+ var doc = document,
+ isCSS1 = doc.compatMode == "CSS1Compat",
+ MAX = Math.max,
+ ROUND = Math.round,
+ PARSEINT = parseInt;
+
+ Ext.lib.Dom = {
+ isAncestor : function(p, c) {
+ var ret = false;
+
+ p = Ext.getDom(p);
+ c = Ext.getDom(c);
+ if (p && c) {
+ if (p.contains) {
+ return p.contains(c);
+ } else if (p.compareDocumentPosition) {
+ return !!(p.compareDocumentPosition(c) & 16);
+ } else {
+ while (c = c.parentNode) {
+ ret = c == p || ret;
+ }
+ }
+ }
+ return ret;
+ },
+
+ getViewWidth : function(full) {
+ return full ? this.getDocumentWidth() : this.getViewportWidth();
+ },
+
+ getViewHeight : function(full) {
+ return full ? this.getDocumentHeight() : this.getViewportHeight();
+ },
+
+ getDocumentHeight: function() {
+ return MAX(!isCSS1 ? doc.body.scrollHeight : doc.documentElement.scrollHeight, this.getViewportHeight());
+ },
+
+ getDocumentWidth: function() {
+ return MAX(!isCSS1 ? doc.body.scrollWidth : doc.documentElement.scrollWidth, this.getViewportWidth());
+ },
+
+ getViewportHeight: function(){
+ return Ext.isIE9m ?
+ (Ext.isStrict ? doc.documentElement.clientHeight : doc.body.clientHeight) :
+ self.innerHeight;
+ },
+
+ getViewportWidth : function() {
+ return !Ext.isStrict && !Ext.isOpera ? doc.body.clientWidth :
+ Ext.isIE9m ? doc.documentElement.clientWidth : self.innerWidth;
+ },
+
+ getY : function(el) {
+ return this.getXY(el)[1];
+ },
+
+ getX : function(el) {
+ return this.getXY(el)[0];
+ },
+
+ getXY : function(el) {
+ var p,
+ pe,
+ b,
+ bt,
+ bl,
+ dbd,
+ x = 0,
+ y = 0,
+ scroll,
+ hasAbsolute,
+ bd = (doc.body || doc.documentElement),
+ ret = [0,0];
+
+ el = Ext.getDom(el);
+
+ if(el != bd){
+ if (el.getBoundingClientRect) {
+ b = el.getBoundingClientRect();
+ scroll = fly(document).getScroll();
+ ret = [ROUND(b.left + scroll.left), ROUND(b.top + scroll.top)];
+ } else {
+ p = el;
+ hasAbsolute = fly(el).isStyle("position", "absolute");
+
+ while (p) {
+ pe = fly(p);
+ x += p.offsetLeft;
+ y += p.offsetTop;
+
+ hasAbsolute = hasAbsolute || pe.isStyle("position", "absolute");
+
+ if (Ext.isGecko) {
+ y += bt = PARSEINT(pe.getStyle("borderTopWidth"), 10) || 0;
+ x += bl = PARSEINT(pe.getStyle("borderLeftWidth"), 10) || 0;
+
+ if (p != el && !pe.isStyle('overflow','visible')) {
+ x += bl;
+ y += bt;
+ }
+ }
+ p = p.offsetParent;
+ }
+
+ if (Ext.isSafari && hasAbsolute) {
+ x -= bd.offsetLeft;
+ y -= bd.offsetTop;
+ }
+
+ if (Ext.isGecko && !hasAbsolute) {
+ dbd = fly(bd);
+ x += PARSEINT(dbd.getStyle("borderLeftWidth"), 10) || 0;
+ y += PARSEINT(dbd.getStyle("borderTopWidth"), 10) || 0;
+ }
+
+ p = el.parentNode;
+ while (p && p != bd) {
+ if (!Ext.isOpera || (p.tagName != 'TR' && !fly(p).isStyle("display", "inline"))) {
+ x -= p.scrollLeft;
+ y -= p.scrollTop;
+ }
+ p = p.parentNode;
+ }
+ ret = [x,y];
+ }
+ }
+ return ret;
+ },
+
+ setXY : function(el, xy) {
+ (el = Ext.fly(el, '_setXY')).position();
+
+ var pts = el.translatePoints(xy),
+ style = el.dom.style,
+ pos;
+
+ for (pos in pts) {
+ if (!isNaN(pts[pos])) {
+ style[pos] = pts[pos] + "px";
+ }
+ }
+ },
+
+ setX : function(el, x) {
+ this.setXY(el, [x, false]);
+ },
+
+ setY : function(el, y) {
+ this.setXY(el, [false, y]);
+ }
+ };
+})();Ext.lib.Event = function() {
+ var loadComplete = false,
+ unloadListeners = {},
+ retryCount = 0,
+ onAvailStack = [],
+ _interval,
+ locked = false,
+ win = window,
+ doc = document,
+
+ // constants
+ POLL_RETRYS = 200,
+ POLL_INTERVAL = 20,
+ TYPE = 0,
+ FN = 1,
+ OBJ = 2,
+ ADJ_SCOPE = 3,
+ SCROLLLEFT = 'scrollLeft',
+ SCROLLTOP = 'scrollTop',
+ UNLOAD = 'unload',
+ MOUSEOVER = 'mouseover',
+ MOUSEOUT = 'mouseout',
+ // private
+ doAdd = function() {
+ var ret;
+ if (win.addEventListener) {
+ ret = function(el, eventName, fn, capture) {
+ if (eventName == 'mouseenter') {
+ fn = fn.createInterceptor(checkRelatedTarget);
+ el.addEventListener(MOUSEOVER, fn, (capture));
+ } else if (eventName == 'mouseleave') {
+ fn = fn.createInterceptor(checkRelatedTarget);
+ el.addEventListener(MOUSEOUT, fn, (capture));
+ } else {
+ el.addEventListener(eventName, fn, (capture));
+ }
+ return fn;
+ };
+ } else if (win.attachEvent) {
+ ret = function(el, eventName, fn, capture) {
+ el.attachEvent("on" + eventName, fn);
+ return fn;
+ };
+ } else {
+ ret = function(){};
+ }
+ return ret;
+ }(),
+ // private
+ doRemove = function(){
+ var ret;
+ if (win.removeEventListener) {
+ ret = function (el, eventName, fn, capture) {
+ if (eventName == 'mouseenter') {
+ eventName = MOUSEOVER;
+ } else if (eventName == 'mouseleave') {
+ eventName = MOUSEOUT;
+ }
+ el.removeEventListener(eventName, fn, (capture));
+ };
+ } else if (win.detachEvent) {
+ ret = function (el, eventName, fn) {
+ el.detachEvent("on" + eventName, fn);
+ };
+ } else {
+ ret = function(){};
+ }
+ return ret;
+ }();
+
+ function checkRelatedTarget(e) {
+ return !elContains(e.currentTarget, pub.getRelatedTarget(e));
+ }
+
+ function elContains(parent, child) {
+ if(parent && parent.firstChild){
+ while(child) {
+ if(child === parent) {
+ return true;
+ }
+ child = child.parentNode;
+ if(child && (child.nodeType != 1)) {
+ child = null;
+ }
+ }
+ }
+ return false;
+ }
+
+ // private
+ function _tryPreloadAttach() {
+ var ret = false,
+ notAvail = [],
+ element, i, v, override,
+ tryAgain = !loadComplete || (retryCount > 0);
+
+ if(!locked){
+ locked = true;
+
+ for(i = 0; i < onAvailStack.length; ++i){
+ v = onAvailStack[i];
+ if(v && (element = doc.getElementById(v.id))){
+ if(!v.checkReady || loadComplete || element.nextSibling || (doc && doc.body)) {
+ override = v.override;
+ element = override ? (override === true ? v.obj : override) : element;
+ v.fn.call(element, v.obj);
+ onAvailStack.remove(v);
+ --i;
+ }else{
+ notAvail.push(v);
+ }
+ }
+ }
+
+ retryCount = (notAvail.length === 0) ? 0 : retryCount - 1;
+
+ if (tryAgain) {
+ startInterval();
+ } else {
+ clearInterval(_interval);
+ _interval = null;
+ }
+ ret = !(locked = false);
+ }
+ return ret;
+ }
+
+ // private
+ function startInterval() {
+ if(!_interval){
+ var callback = function() {
+ _tryPreloadAttach();
+ };
+ _interval = setInterval(callback, POLL_INTERVAL);
+ }
+ }
+
+ // private
+ function getScroll() {
+ var dd = doc.documentElement,
+ db = doc.body;
+ if(dd && (dd[SCROLLTOP] || dd[SCROLLLEFT])){
+ return [dd[SCROLLLEFT], dd[SCROLLTOP]];
+ }else if(db){
+ return [db[SCROLLLEFT], db[SCROLLTOP]];
+ }else{
+ return [0, 0];
+ }
+ }
+
+ // private
+ function getPageCoord (ev, xy) {
+ ev = ev.browserEvent || ev;
+ var coord = ev['page' + xy];
+ if (!coord && coord !== 0) {
+ coord = ev['client' + xy] || 0;
+
+ if (Ext.isIE) {
+ coord += getScroll()[xy == "X" ? 0 : 1];
+ }
+ }
+
+ return coord;
+ }
+
+ var pub = {
+ extAdapter: true,
+ onAvailable : function(p_id, p_fn, p_obj, p_override) {
+ onAvailStack.push({
+ id: p_id,
+ fn: p_fn,
+ obj: p_obj,
+ override: p_override,
+ checkReady: false });
+
+ retryCount = POLL_RETRYS;
+ startInterval();
+ },
+
+ // This function should ALWAYS be called from Ext.EventManager
+ addListener: function(el, eventName, fn) {
+ el = Ext.getDom(el);
+ if (el && fn) {
+ if (eventName == UNLOAD) {
+ if (unloadListeners[el.id] === undefined) {
+ unloadListeners[el.id] = [];
+ }
+ unloadListeners[el.id].push([eventName, fn]);
+ return fn;
+ }
+ return doAdd(el, eventName, fn, false);
+ }
+ return false;
+ },
+
+ // This function should ALWAYS be called from Ext.EventManager
+ removeListener: function(el, eventName, fn) {
+ el = Ext.getDom(el);
+ var i, len, li, lis;
+ if (el && fn) {
+ if(eventName == UNLOAD){
+ if((lis = unloadListeners[el.id]) !== undefined){
+ for(i = 0, len = lis.length; i < len; i++){
+ if((li = lis[i]) && li[TYPE] == eventName && li[FN] == fn){
+ unloadListeners[el.id].splice(i, 1);
+ }
+ }
+ }
+ return;
+ }
+ doRemove(el, eventName, fn, false);
+ }
+ },
+
+ getTarget : function(ev) {
+ ev = ev.browserEvent || ev;
+ return this.resolveTextNode(ev.target || ev.srcElement);
+ },
+
+ resolveTextNode : Ext.isGecko ? function(node){
+ if(!node){
+ return;
+ }
+ // work around firefox bug, https://bugzilla.mozilla.org/show_bug.cgi?id=101197
+ var s = HTMLElement.prototype.toString.call(node);
+ if(s == '[xpconnect wrapped native prototype]' || s == '[object XULElement]'){
+ return;
+ }
+ return node.nodeType == 3 ? node.parentNode : node;
+ } : function(node){
+ return node && node.nodeType == 3 ? node.parentNode : node;
+ },
+
+ getRelatedTarget : function(ev) {
+ ev = ev.browserEvent || ev;
+ return this.resolveTextNode(ev.relatedTarget ||
+ (/(mouseout|mouseleave)/.test(ev.type) ? ev.toElement :
+ /(mouseover|mouseenter)/.test(ev.type) ? ev.fromElement : null));
+ },
+
+ getPageX : function(ev) {
+ return getPageCoord(ev, "X");
+ },
+
+ getPageY : function(ev) {
+ return getPageCoord(ev, "Y");
+ },
+
+
+ getXY : function(ev) {
+ return [this.getPageX(ev), this.getPageY(ev)];
+ },
+
+ stopEvent : function(ev) {
+ this.stopPropagation(ev);
+ this.preventDefault(ev);
+ },
+
+ stopPropagation : function(ev) {
+ ev = ev.browserEvent || ev;
+ if (ev.stopPropagation) {
+ ev.stopPropagation();
+ } else {
+ ev.cancelBubble = true;
+ }
+ },
+
+ preventDefault : function(ev) {
+ ev = ev.browserEvent || ev;
+ if (ev.preventDefault) {
+ ev.preventDefault();
+ } else {
+ if (ev.keyCode) {
+ ev.keyCode = 0;
+ }
+ ev.returnValue = false;
+ }
+ },
+
+ getEvent : function(e) {
+ e = e || win.event;
+ if (!e) {
+ var c = this.getEvent.caller;
+ while (c) {
+ e = c.arguments[0];
+ if (e && Event == e.constructor) {
+ break;
+ }
+ c = c.caller;
+ }
+ }
+ return e;
+ },
+
+ getCharCode : function(ev) {
+ ev = ev.browserEvent || ev;
+ return ev.charCode || ev.keyCode || 0;
+ },
+
+ //clearCache: function() {},
+ // deprecated, call from EventManager
+ getListeners : function(el, eventName) {
+ Ext.EventManager.getListeners(el, eventName);
+ },
+
+ // deprecated, call from EventManager
+ purgeElement : function(el, recurse, eventName) {
+ Ext.EventManager.purgeElement(el, recurse, eventName);
+ },
+
+ _load : function(e) {
+ loadComplete = true;
+
+ if (Ext.isIE9m && e !== true) {
+ // IE8 complains that _load is null or not an object
+ // so lets remove self via arguments.callee
+ doRemove(win, "load", arguments.callee);
+ }
+ },
+
+ _unload : function(e) {
+ var EU = Ext.lib.Event,
+ i, v, ul, id, len, scope;
+
+ for (id in unloadListeners) {
+ ul = unloadListeners[id];
+ for (i = 0, len = ul.length; i < len; i++) {
+ v = ul[i];
+ if (v) {
+ try{
+ scope = v[ADJ_SCOPE] ? (v[ADJ_SCOPE] === true ? v[OBJ] : v[ADJ_SCOPE]) : win;
+ v[FN].call(scope, EU.getEvent(e), v[OBJ]);
+ }catch(ex){}
+ }
+ }
+ };
+
+ Ext.EventManager._unload();
+
+ doRemove(win, UNLOAD, EU._unload);
+ }
+ };
+
+ // Initialize stuff.
+ pub.on = pub.addListener;
+ pub.un = pub.removeListener;
+ if (doc && doc.body) {
+ pub._load(true);
+ } else {
+ doAdd(win, "load", pub._load);
+ }
+ doAdd(win, UNLOAD, pub._unload);
+ _tryPreloadAttach();
+
+ return pub;
+}();
+/*
+* Portions of this file are based on pieces of Yahoo User Interface Library
+* Copyright (c) 2007, Yahoo! Inc. All rights reserved.
+* YUI licensed under the BSD License:
+* http://developer.yahoo.net/yui/license.txt
+*/
+Ext.lib.Ajax = function() {
+ var activeX = ['Msxml2.XMLHTTP.3.0',
+ 'Msxml2.XMLHTTP'],
+ CONTENTTYPE = 'Content-Type';
+
+ // private
+ function setHeader(o) {
+ var conn = o.conn,
+ prop,
+ headers = {};
+
+ function setTheHeaders(conn, headers){
+ for (prop in headers) {
+ if (headers.hasOwnProperty(prop)) {
+ conn.setRequestHeader(prop, headers[prop]);
+ }
+ }
+ }
+
+ Ext.apply(headers, pub.headers, pub.defaultHeaders);
+ setTheHeaders(conn, headers);
+ delete pub.headers;
+ }
+
+ // private
+ function createExceptionObject(tId, callbackArg, isAbort, isTimeout) {
+ return {
+ tId : tId,
+ status : isAbort ? -1 : 0,
+ statusText : isAbort ? 'transaction aborted' : 'communication failure',
+ isAbort: isAbort,
+ isTimeout: isTimeout,
+ argument : callbackArg
+ };
+ }
+
+ // private
+ function initHeader(label, value) {
+ (pub.headers = pub.headers || {})[label] = value;
+ }
+
+ // private
+ function createResponseObject(o, callbackArg) {
+ var headerObj = {},
+ headerStr,
+ conn = o.conn,
+ t,
+ s,
+ // see: https://prototype.lighthouseapp.com/projects/8886/tickets/129-ie-mangles-http-response-status-code-204-to-1223
+ isBrokenStatus = conn.status == 1223;
+
+ try {
+ headerStr = o.conn.getAllResponseHeaders();
+ Ext.each(headerStr.replace(/\r\n/g, '\n').split('\n'), function(v){
+ t = v.indexOf(':');
+ if(t >= 0){
+ s = v.substr(0, t).toLowerCase();
+ if(v.charAt(t + 1) == ' '){
+ ++t;
+ }
+ headerObj[s] = v.substr(t + 1);
+ }
+ });
+ } catch(e) {}
+
+ return {
+ tId : o.tId,
+ // Normalize the status and statusText when IE returns 1223, see the above link.
+ status : isBrokenStatus ? 204 : conn.status,
+ statusText : isBrokenStatus ? 'No Content' : conn.statusText,
+ getResponseHeader : function(header){return headerObj[header.toLowerCase()];},
+ getAllResponseHeaders : function(){return headerStr;},
+ responseText : conn.responseText,
+ responseXML : conn.responseXML,
+ argument : callbackArg
+ };
+ }
+
+ // private
+ function releaseObject(o) {
+ if (o.tId) {
+ pub.conn[o.tId] = null;
+ }
+ o.conn = null;
+ o = null;
+ }
+
+ // private
+ function handleTransactionResponse(o, callback, isAbort, isTimeout) {
+ if (!callback) {
+ releaseObject(o);
+ return;
+ }
+
+ var httpStatus, responseObject;
+
+ try {
+ if (o.conn.status !== undefined && o.conn.status != 0) {
+ httpStatus = o.conn.status;
+ }
+ else {
+ httpStatus = 13030;
+ }
+ }
+ catch(e) {
+ httpStatus = 13030;
+ }
+
+ if ((httpStatus >= 200 && httpStatus < 300) || (Ext.isIE && httpStatus == 1223)) {
+ responseObject = createResponseObject(o, callback.argument);
+ if (callback.success) {
+ if (!callback.scope) {
+ callback.success(responseObject);
+ }
+ else {
+ callback.success.apply(callback.scope, [responseObject]);
+ }
+ }
+ }
+ else {
+ switch (httpStatus) {
+ case 12002:
+ case 12029:
+ case 12030:
+ case 12031:
+ case 12152:
+ case 13030:
+ responseObject = createExceptionObject(o.tId, callback.argument, (isAbort ? isAbort : false), isTimeout);
+ if (callback.failure) {
+ if (!callback.scope) {
+ callback.failure(responseObject);
+ }
+ else {
+ callback.failure.apply(callback.scope, [responseObject]);
+ }
+ }
+ break;
+ default:
+ responseObject = createResponseObject(o, callback.argument);
+ if (callback.failure) {
+ if (!callback.scope) {
+ callback.failure(responseObject);
+ }
+ else {
+ callback.failure.apply(callback.scope, [responseObject]);
+ }
+ }
+ }
+ }
+
+ releaseObject(o);
+ responseObject = null;
+ }
+
+ function checkResponse(o, callback, conn, tId, poll, cbTimeout){
+ if (conn && conn.readyState == 4) {
+ clearInterval(poll[tId]);
+ poll[tId] = null;
+
+ if (cbTimeout) {
+ clearTimeout(pub.timeout[tId]);
+ pub.timeout[tId] = null;
+ }
+ handleTransactionResponse(o, callback);
+ }
+ }
+
+ function checkTimeout(o, callback){
+ pub.abort(o, callback, true);
+ }
+
+
+ // private
+ function handleReadyState(o, callback){
+ callback = callback || {};
+ var conn = o.conn,
+ tId = o.tId,
+ poll = pub.poll,
+ cbTimeout = callback.timeout || null;
+
+ if (cbTimeout) {
+ pub.conn[tId] = conn;
+ pub.timeout[tId] = setTimeout(checkTimeout.createCallback(o, callback), cbTimeout);
+ }
+ poll[tId] = setInterval(checkResponse.createCallback(o, callback, conn, tId, poll, cbTimeout), pub.pollInterval);
+ }
+
+ // private
+ function asyncRequest(method, uri, callback, postData) {
+ var o = getConnectionObject() || null;
+
+ if (o) {
+ o.conn.open(method, uri, true);
+
+ if (pub.useDefaultXhrHeader) {
+ initHeader('X-Requested-With', pub.defaultXhrHeader);
+ }
+
+ if(postData && pub.useDefaultHeader && (!pub.headers || !pub.headers[CONTENTTYPE])){
+ initHeader(CONTENTTYPE, pub.defaultPostHeader);
+ }
+
+ if (pub.defaultHeaders || pub.headers) {
+ setHeader(o);
+ }
+
+ handleReadyState(o, callback);
+ o.conn.send(postData || null);
+ }
+ return o;
+ }
+
+ // private
+ function getConnectionObject() {
+ var o;
+
+ try {
+ if (o = createXhrObject(pub.transactionId)) {
+ pub.transactionId++;
+ }
+ } catch(e) {
+ } finally {
+ return o;
+ }
+ }
+
+ // private
+ function createXhrObject(transactionId) {
+ var http;
+
+ try {
+ http = new XMLHttpRequest();
+ } catch(e) {
+ for (var i = Ext.isIE6 ? 1 : 0; i < activeX.length; ++i) {
+ try {
+ http = new ActiveXObject(activeX[i]);
+ break;
+ } catch(e) {}
+ }
+ } finally {
+ return {conn : http, tId : transactionId};
+ }
+ }
+
+ var pub = {
+ request : function(method, uri, cb, data, options) {
+ if(options){
+ var me = this,
+ xmlData = options.xmlData,
+ jsonData = options.jsonData,
+ hs;
+
+ Ext.applyIf(me, options);
+
+ if(xmlData || jsonData){
+ hs = me.headers;
+ if(!hs || !hs[CONTENTTYPE]){
+ initHeader(CONTENTTYPE, xmlData ? 'text/xml' : 'application/json');
+ }
+ data = xmlData || (!Ext.isPrimitive(jsonData) ? Ext.encode(jsonData) : jsonData);
+ }
+ }
+ return asyncRequest(method || options.method || "POST", uri, cb, data);
+ },
+
+ serializeForm : function(form) {
+ var fElements = form.elements || (document.forms[form] || Ext.getDom(form)).elements,
+ hasSubmit = false,
+ encoder = encodeURIComponent,
+ name,
+ data = '',
+ type,
+ hasValue;
+
+ Ext.each(fElements, function(element){
+ name = element.name;
+ type = element.type;
+
+ if (!element.disabled && name) {
+ if (/select-(one|multiple)/i.test(type)) {
+ Ext.each(element.options, function(opt){
+ if (opt.selected) {
+ hasValue = opt.hasAttribute ? opt.hasAttribute('value') : opt.getAttributeNode('value').specified;
+ data += String.format("{0}={1}&", encoder(name), encoder(hasValue ? opt.value : opt.text));
+ }
+ });
+ } else if (!(/file|undefined|reset|button/i.test(type))) {
+ if (!(/radio|checkbox/i.test(type) && !element.checked) && !(type == 'submit' && hasSubmit)) {
+ data += encoder(name) + '=' + encoder(element.value) + '&';
+ hasSubmit = /submit/i.test(type);
+ }
+ }
+ }
+ });
+ return data.substr(0, data.length - 1);
+ },
+
+ useDefaultHeader : true,
+ defaultPostHeader : 'application/x-www-form-urlencoded; charset=UTF-8',
+ useDefaultXhrHeader : true,
+ defaultXhrHeader : 'XMLHttpRequest',
+ poll : {},
+ timeout : {},
+ conn: {},
+ pollInterval : 50,
+ transactionId : 0,
+
+// This is never called - Is it worth exposing this?
+// setProgId : function(id) {
+// activeX.unshift(id);
+// },
+
+// This is never called - Is it worth exposing this?
+// setDefaultPostHeader : function(b) {
+// this.useDefaultHeader = b;
+// },
+
+// This is never called - Is it worth exposing this?
+// setDefaultXhrHeader : function(b) {
+// this.useDefaultXhrHeader = b;
+// },
+
+// This is never called - Is it worth exposing this?
+// setPollingInterval : function(i) {
+// if (typeof i == 'number' && isFinite(i)) {
+// this.pollInterval = i;
+// }
+// },
+
+// This is never called - Is it worth exposing this?
+// resetDefaultHeaders : function() {
+// this.defaultHeaders = null;
+// },
+
+ abort : function(o, callback, isTimeout) {
+ var me = this,
+ tId = o.tId,
+ isAbort = false;
+
+ if (me.isCallInProgress(o)) {
+ o.conn.abort();
+ clearInterval(me.poll[tId]);
+ me.poll[tId] = null;
+ clearTimeout(pub.timeout[tId]);
+ me.timeout[tId] = null;
+
+ handleTransactionResponse(o, callback, (isAbort = true), isTimeout);
+ }
+ return isAbort;
+ },
+
+ isCallInProgress : function(o) {
+ // if there is a connection and readyState is not 0 or 4
+ return o.conn && !{0:true,4:true}[o.conn.readyState];
+ }
+ };
+ return pub;
+}();(function(){
+ var EXTLIB = Ext.lib,
+ noNegatives = /width|height|opacity|padding/i,
+ offsetAttribute = /^((width|height)|(top|left))$/,
+ defaultUnit = /width|height|top$|bottom$|left$|right$/i,
+ offsetUnit = /\d+(em|%|en|ex|pt|in|cm|mm|pc)$/i,
+ isset = function(v){
+ return typeof v !== 'undefined';
+ },
+ now = function(){
+ return new Date();
+ };
+
+ EXTLIB.Anim = {
+ motion : function(el, args, duration, easing, cb, scope) {
+ return this.run(el, args, duration, easing, cb, scope, Ext.lib.Motion);
+ },
+
+ run : function(el, args, duration, easing, cb, scope, type) {
+ type = type || Ext.lib.AnimBase;
+ if (typeof easing == "string") {
+ easing = Ext.lib.Easing[easing];
+ }
+ var anim = new type(el, args, duration, easing);
+ anim.animateX(function() {
+ if(Ext.isFunction(cb)){
+ cb.call(scope);
+ }
+ });
+ return anim;
+ }
+ };
+
+ EXTLIB.AnimBase = function(el, attributes, duration, method) {
+ if (el) {
+ this.init(el, attributes, duration, method);
+ }
+ };
+
+ EXTLIB.AnimBase.prototype = {
+ doMethod: function(attr, start, end) {
+ var me = this;
+ return me.method(me.curFrame, start, end - start, me.totalFrames);
+ },
+
+
+ setAttr: function(attr, val, unit) {
+ if (noNegatives.test(attr) && val < 0) {
+ val = 0;
+ }
+ Ext.fly(this.el, '_anim').setStyle(attr, val + unit);
+ },
+
+
+ getAttr: function(attr) {
+ var el = Ext.fly(this.el),
+ val = el.getStyle(attr),
+ a = offsetAttribute.exec(attr) || [];
+
+ if (val !== 'auto' && !offsetUnit.test(val)) {
+ return parseFloat(val);
+ }
+
+ return (!!(a[2]) || (el.getStyle('position') == 'absolute' && !!(a[3]))) ? el.dom['offset' + a[0].charAt(0).toUpperCase() + a[0].substr(1)] : 0;
+ },
+
+
+ getDefaultUnit: function(attr) {
+ return defaultUnit.test(attr) ? 'px' : '';
+ },
+
+ animateX : function(callback, scope) {
+ var me = this,
+ f = function() {
+ me.onComplete.removeListener(f);
+ if (Ext.isFunction(callback)) {
+ callback.call(scope || me, me);
+ }
+ };
+ me.onComplete.addListener(f, me);
+ me.animate();
+ },
+
+
+ setRunAttr: function(attr) {
+ var me = this,
+ a = this.attributes[attr],
+ to = a.to,
+ by = a.by,
+ from = a.from,
+ unit = a.unit,
+ ra = (this.runAttrs[attr] = {}),
+ end;
+
+ if (!isset(to) && !isset(by)){
+ return false;
+ }
+
+ var start = isset(from) ? from : me.getAttr(attr);
+ if (isset(to)) {
+ end = to;
+ }else if(isset(by)) {
+ if (Ext.isArray(start)){
+ end = [];
+ for(var i=0,len=start.length; i<len; i++) {
+ end[i] = start[i] + by[i];
+ }
+ }else{
+ end = start + by;
+ }
+ }
+
+ Ext.apply(ra, {
+ start: start,
+ end: end,
+ unit: isset(unit) ? unit : me.getDefaultUnit(attr)
+ });
+ },
+
+
+ init: function(el, attributes, duration, method) {
+ var me = this,
+ actualFrames = 0,
+ mgr = EXTLIB.AnimMgr;
+
+ Ext.apply(me, {
+ isAnimated: false,
+ startTime: null,
+ el: Ext.getDom(el),
+ attributes: attributes || {},
+ duration: duration || 1,
+ method: method || EXTLIB.Easing.easeNone,
+ useSec: true,
+ curFrame: 0,
+ totalFrames: mgr.fps,
+ runAttrs: {},
+ animate: function(){
+ var me = this,
+ d = me.duration;
+
+ if(me.isAnimated){
+ return false;
+ }
+
+ me.curFrame = 0;
+ me.totalFrames = me.useSec ? Math.ceil(mgr.fps * d) : d;
+ mgr.registerElement(me);
+ },
+
+ stop: function(finish){
+ var me = this;
+
+ if(finish){
+ me.curFrame = me.totalFrames;
+ me._onTween.fire();
+ }
+ mgr.stop(me);
+ }
+ });
+
+ var onStart = function(){
+ var me = this,
+ attr;
+
+ me.onStart.fire();
+ me.runAttrs = {};
+ for(attr in this.attributes){
+ this.setRunAttr(attr);
+ }
+
+ me.isAnimated = true;
+ me.startTime = now();
+ actualFrames = 0;
+ };
+
+
+ var onTween = function(){
+ var me = this;
+
+ me.onTween.fire({
+ duration: now() - me.startTime,
+ curFrame: me.curFrame
+ });
+
+ var ra = me.runAttrs;
+ for (var attr in ra) {
+ this.setAttr(attr, me.doMethod(attr, ra[attr].start, ra[attr].end), ra[attr].unit);
+ }
+
+ ++actualFrames;
+ };
+
+ var onComplete = function() {
+ var me = this,
+ actual = (now() - me.startTime) / 1000,
+ data = {
+ duration: actual,
+ frames: actualFrames,
+ fps: actualFrames / actual
+ };
+
+ me.isAnimated = false;
+ actualFrames = 0;
+ me.onComplete.fire(data);
+ };
+
+ me.onStart = new Ext.util.Event(me);
+ me.onTween = new Ext.util.Event(me);
+ me.onComplete = new Ext.util.Event(me);
+ (me._onStart = new Ext.util.Event(me)).addListener(onStart);
+ (me._onTween = new Ext.util.Event(me)).addListener(onTween);
+ (me._onComplete = new Ext.util.Event(me)).addListener(onComplete);
+ }
+ };
+
+
+ Ext.lib.AnimMgr = new function() {
+ var me = this,
+ thread = null,
+ queue = [],
+ tweenCount = 0;
+
+
+ Ext.apply(me, {
+ fps: 1000,
+ delay: 1,
+ registerElement: function(tween){
+ queue.push(tween);
+ ++tweenCount;
+ tween._onStart.fire();
+ me.start();
+ },
+
+ unRegister: function(tween, index){
+ tween._onComplete.fire();
+ index = index || getIndex(tween);
+ if (index != -1) {
+ queue.splice(index, 1);
+ }
+
+ if (--tweenCount <= 0) {
+ me.stop();
+ }
+ },
+
+ start: function(){
+ if(thread === null){
+ thread = setInterval(me.run, me.delay);
+ }
+ },
+
+ stop: function(tween){
+ if(!tween){
+ clearInterval(thread);
+ for(var i = 0, len = queue.length; i < len; ++i){
+ if(queue[0].isAnimated){
+ me.unRegister(queue[0], 0);
+ }
+ }
+
+ queue = [];
+ thread = null;
+ tweenCount = 0;
+ }else{
+ me.unRegister(tween);
+ }
+ },
+
+ run: function(){
+ var tf, i, len, tween;
+ for(i = 0, len = queue.length; i<len; i++) {
+ tween = queue[i];
+ if(tween && tween.isAnimated){
+ tf = tween.totalFrames;
+ if(tween.curFrame < tf || tf === null){
+ ++tween.curFrame;
+ if(tween.useSec){
+ correctFrame(tween);
+ }
+ tween._onTween.fire();
+ }else{
+ me.stop(tween);
+ }
+ }
+ }
+ }
+ });
+
+ var getIndex = function(anim) {
+ var i, len;
+ for(i = 0, len = queue.length; i<len; i++) {
+ if(queue[i] === anim) {
+ return i;
+ }
+ }
+ return -1;
+ };
+
+ var correctFrame = function(tween) {
+ var frames = tween.totalFrames,
+ frame = tween.curFrame,
+ duration = tween.duration,
+ expected = (frame * duration * 1000 / frames),
+ elapsed = (now() - tween.startTime),
+ tweak = 0;
+
+ if(elapsed < duration * 1000){
+ tweak = Math.round((elapsed / expected - 1) * frame);
+ }else{
+ tweak = frames - (frame + 1);
+ }
+ if(tweak > 0 && isFinite(tweak)){
+ if(tween.curFrame + tweak >= frames){
+ tweak = frames - (frame + 1);
+ }
+ tween.curFrame += tweak;
+ }
+ };
+ };
+
+ EXTLIB.Bezier = new function() {
+
+ this.getPosition = function(points, t) {
+ var n = points.length,
+ tmp = [],
+ c = 1 - t,
+ i,
+ j;
+
+ for (i = 0; i < n; ++i) {
+ tmp[i] = [points[i][0], points[i][1]];
+ }
+
+ for (j = 1; j < n; ++j) {
+ for (i = 0; i < n - j; ++i) {
+ tmp[i][0] = c * tmp[i][0] + t * tmp[parseInt(i + 1, 10)][0];
+ tmp[i][1] = c * tmp[i][1] + t * tmp[parseInt(i + 1, 10)][1];
+ }
+ }
+
+ return [ tmp[0][0], tmp[0][1] ];
+
+ };
+ };
+
+
+ EXTLIB.Easing = {
+ easeNone: function (t, b, c, d) {
+ return c * t / d + b;
+ },
+
+
+ easeIn: function (t, b, c, d) {
+ return c * (t /= d) * t + b;
+ },
+
+
+ easeOut: function (t, b, c, d) {
+ return -c * (t /= d) * (t - 2) + b;
+ }
+ };
+
+ (function() {
+ EXTLIB.Motion = function(el, attributes, duration, method) {
+ if (el) {
+ EXTLIB.Motion.superclass.constructor.call(this, el, attributes, duration, method);
+ }
+ };
+
+ Ext.extend(EXTLIB.Motion, Ext.lib.AnimBase);
+
+ var superclass = EXTLIB.Motion.superclass,
+ pointsRe = /^points$/i;
+
+ Ext.apply(EXTLIB.Motion.prototype, {
+ setAttr: function(attr, val, unit){
+ var me = this,
+ setAttr = superclass.setAttr;
+
+ if (pointsRe.test(attr)) {
+ unit = unit || 'px';
+ setAttr.call(me, 'left', val[0], unit);
+ setAttr.call(me, 'top', val[1], unit);
+ } else {
+ setAttr.call(me, attr, val, unit);
+ }
+ },
+
+ getAttr: function(attr){
+ var me = this,
+ getAttr = superclass.getAttr;
+
+ return pointsRe.test(attr) ? [getAttr.call(me, 'left'), getAttr.call(me, 'top')] : getAttr.call(me, attr);
+ },
+
+ doMethod: function(attr, start, end){
+ var me = this;
+
+ return pointsRe.test(attr)
+ ? EXTLIB.Bezier.getPosition(me.runAttrs[attr], me.method(me.curFrame, 0, 100, me.totalFrames) / 100)
+ : superclass.doMethod.call(me, attr, start, end);
+ },
+
+ setRunAttr: function(attr){
+ if(pointsRe.test(attr)){
+
+ var me = this,
+ el = this.el,
+ points = this.attributes.points,
+ control = points.control || [],
+ from = points.from,
+ to = points.to,
+ by = points.by,
+ DOM = EXTLIB.Dom,
+ start,
+ i,
+ end,
+ len,
+ ra;
+
+
+ if(control.length > 0 && !Ext.isArray(control[0])){
+ control = [control];
+ }else{
+ /*
+ var tmp = [];
+ for (i = 0,len = control.length; i < len; ++i) {
+ tmp[i] = control[i];
+ }
+ control = tmp;
+ */
+ }
+
+ Ext.fly(el, '_anim').position();
+ DOM.setXY(el, isset(from) ? from : DOM.getXY(el));
+ start = me.getAttr('points');
+
+
+ if(isset(to)){
+ end = translateValues.call(me, to, start);
+ for (i = 0,len = control.length; i < len; ++i) {
+ control[i] = translateValues.call(me, control[i], start);
+ }
+ } else if (isset(by)) {
+ end = [start[0] + by[0], start[1] + by[1]];
+
+ for (i = 0,len = control.length; i < len; ++i) {
+ control[i] = [ start[0] + control[i][0], start[1] + control[i][1] ];
+ }
+ }
+
+ ra = this.runAttrs[attr] = [start];
+ if (control.length > 0) {
+ ra = ra.concat(control);
+ }
+
+ ra[ra.length] = end;
+ }else{
+ superclass.setRunAttr.call(this, attr);
+ }
+ }
+ });
+
+ var translateValues = function(val, start) {
+ var pageXY = EXTLIB.Dom.getXY(this.el);
+ return [val[0] - pageXY[0] + start[0], val[1] - pageXY[1] + start[1]];
+ };
+ })();
+})();// Easing functions
+(function(){
+ // shortcuts to aid compression
+ var abs = Math.abs,
+ pi = Math.PI,
+ asin = Math.asin,
+ pow = Math.pow,
+ sin = Math.sin,
+ EXTLIB = Ext.lib;
+
+ Ext.apply(EXTLIB.Easing, {
+
+ easeBoth: function (t, b, c, d) {
+ return ((t /= d / 2) < 1) ? c / 2 * t * t + b : -c / 2 * ((--t) * (t - 2) - 1) + b;
+ },
+
+ easeInStrong: function (t, b, c, d) {
+ return c * (t /= d) * t * t * t + b;
+ },
+
+ easeOutStrong: function (t, b, c, d) {
+ return -c * ((t = t / d - 1) * t * t * t - 1) + b;
+ },
+
+ easeBothStrong: function (t, b, c, d) {
+ return ((t /= d / 2) < 1) ? c / 2 * t * t * t * t + b : -c / 2 * ((t -= 2) * t * t * t - 2) + b;
+ },
+
+ elasticIn: function (t, b, c, d, a, p) {
+ if (t == 0 || (t /= d) == 1) {
+ return t == 0 ? b : b + c;
+ }
+ p = p || (d * .3);
+
+ var s;
+ if (a >= abs(c)) {
+ s = p / (2 * pi) * asin(c / a);
+ } else {
+ a = c;
+ s = p / 4;
+ }
+
+ return -(a * pow(2, 10 * (t -= 1)) * sin((t * d - s) * (2 * pi) / p)) + b;
+
+ },
+
+ elasticOut: function (t, b, c, d, a, p) {
+ if (t == 0 || (t /= d) == 1) {
+ return t == 0 ? b : b + c;
+ }
+ p = p || (d * .3);
+
+ var s;
+ if (a >= abs(c)) {
+ s = p / (2 * pi) * asin(c / a);
+ } else {
+ a = c;
+ s = p / 4;
+ }
+
+ return a * pow(2, -10 * t) * sin((t * d - s) * (2 * pi) / p) + c + b;
+ },
+
+ elasticBoth: function (t, b, c, d, a, p) {
+ if (t == 0 || (t /= d / 2) == 2) {
+ return t == 0 ? b : b + c;
+ }
+
+ p = p || (d * (.3 * 1.5));
+
+ var s;
+ if (a >= abs(c)) {
+ s = p / (2 * pi) * asin(c / a);
+ } else {
+ a = c;
+ s = p / 4;
+ }
+
+ return t < 1 ?
+ -.5 * (a * pow(2, 10 * (t -= 1)) * sin((t * d - s) * (2 * pi) / p)) + b :
+ a * pow(2, -10 * (t -= 1)) * sin((t * d - s) * (2 * pi) / p) * .5 + c + b;
+ },
+
+ backIn: function (t, b, c, d, s) {
+ s = s || 1.70158;
+ return c * (t /= d) * t * ((s + 1) * t - s) + b;
+ },
+
+
+ backOut: function (t, b, c, d, s) {
+ if (!s) {
+ s = 1.70158;
+ }
+ return c * ((t = t / d - 1) * t * ((s + 1) * t + s) + 1) + b;
+ },
+
+
+ backBoth: function (t, b, c, d, s) {
+ s = s || 1.70158;
+
+ return ((t /= d / 2 ) < 1) ?
+ c / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)) + b :
+ c / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2) + b;
+ },
+
+
+ bounceIn: function (t, b, c, d) {
+ return c - EXTLIB.Easing.bounceOut(d - t, 0, c, d) + b;
+ },
+
+
+ bounceOut: function (t, b, c, d) {
+ if ((t /= d) < (1 / 2.75)) {
+ return c * (7.5625 * t * t) + b;
+ } else if (t < (2 / 2.75)) {
+ return c * (7.5625 * (t -= (1.5 / 2.75)) * t + .75) + b;
+ } else if (t < (2.5 / 2.75)) {
+ return c * (7.5625 * (t -= (2.25 / 2.75)) * t + .9375) + b;
+ }
+ return c * (7.5625 * (t -= (2.625 / 2.75)) * t + .984375) + b;
+ },
+
+
+ bounceBoth: function (t, b, c, d) {
+ return (t < d / 2) ?
+ EXTLIB.Easing.bounceIn(t * 2, 0, c, d) * .5 + b :
+ EXTLIB.Easing.bounceOut(t * 2 - d, 0, c, d) * .5 + c * .5 + b;
+ }
+ });
+})();
+
+(function() {
+ var EXTLIB = Ext.lib;
+ // Color Animation
+ EXTLIB.Anim.color = function(el, args, duration, easing, cb, scope) {
+ return EXTLIB.Anim.run(el, args, duration, easing, cb, scope, EXTLIB.ColorAnim);
+ };
+
+ EXTLIB.ColorAnim = function(el, attributes, duration, method) {
+ EXTLIB.ColorAnim.superclass.constructor.call(this, el, attributes, duration, method);
+ };
+
+ Ext.extend(EXTLIB.ColorAnim, EXTLIB.AnimBase);
+
+ var superclass = EXTLIB.ColorAnim.superclass,
+ colorRE = /color$/i,
+ transparentRE = /^transparent|rgba\(0, 0, 0, 0\)$/,
+ rgbRE = /^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,
+ hexRE= /^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,
+ hex3RE = /^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i,
+ isset = function(v){
+ return typeof v !== 'undefined';
+ };
+
+ // private
+ function parseColor(s) {
+ var pi = parseInt,
+ base,
+ out = null,
+ c;
+
+ if (s.length == 3) {
+ return s;
+ }
+
+ Ext.each([hexRE, rgbRE, hex3RE], function(re, idx){
+ base = (idx % 2 == 0) ? 16 : 10;
+ c = re.exec(s);
+ if(c && c.length == 4){
+ out = [pi(c[1], base), pi(c[2], base), pi(c[3], base)];
+ return false;
+ }
+ });
+ return out;
+ }
+
+ Ext.apply(EXTLIB.ColorAnim.prototype, {
+ getAttr : function(attr) {
+ var me = this,
+ el = me.el,
+ val;
+ if(colorRE.test(attr)){
+ while(el && transparentRE.test(val = Ext.fly(el).getStyle(attr))){
+ el = el.parentNode;
+ val = "fff";
+ }
+ }else{
+ val = superclass.getAttr.call(me, attr);
+ }
+ return val;
+ },
+
+ doMethod : function(attr, start, end) {
+ var me = this,
+ val,
+ floor = Math.floor,
+ i,
+ len,
+ v;
+
+ if(colorRE.test(attr)){
+ val = [];
+ end = end || [];
+
+ for(i = 0, len = start.length; i < len; i++) {
+ v = start[i];
+ val[i] = superclass.doMethod.call(me, attr, v, end[i]);
+ }
+ val = 'rgb(' + floor(val[0]) + ',' + floor(val[1]) + ',' + floor(val[2]) + ')';
+ }else{
+ val = superclass.doMethod.call(me, attr, start, end);
+ }
+ return val;
+ },
+
+ setRunAttr : function(attr) {
+ var me = this,
+ a = me.attributes[attr],
+ to = a.to,
+ by = a.by,
+ ra;
+
+ superclass.setRunAttr.call(me, attr);
+ ra = me.runAttrs[attr];
+ if(colorRE.test(attr)){
+ var start = parseColor(ra.start),
+ end = parseColor(ra.end);
+
+ if(!isset(to) && isset(by)){
+ end = parseColor(by);
+ for(var i=0,len=start.length; i<len; i++) {
+ end[i] = start[i] + end[i];
+ }
+ }
+ ra.start = start;
+ ra.end = end;
+ }
+ }
+ });
+})();
+
+
+(function() {
+ // Scroll Animation
+ var EXTLIB = Ext.lib;
+ EXTLIB.Anim.scroll = function(el, args, duration, easing, cb, scope) {
+ return EXTLIB.Anim.run(el, args, duration, easing, cb, scope, EXTLIB.Scroll);
+ };
+
+ EXTLIB.Scroll = function(el, attributes, duration, method) {
+ if(el){
+ EXTLIB.Scroll.superclass.constructor.call(this, el, attributes, duration, method);
+ }
+ };
+
+ Ext.extend(EXTLIB.Scroll, EXTLIB.ColorAnim);
+
+ var superclass = EXTLIB.Scroll.superclass,
+ SCROLL = 'scroll';
+
+ Ext.apply(EXTLIB.Scroll.prototype, {
+
+ doMethod : function(attr, start, end) {
+ var val,
+ me = this,
+ curFrame = me.curFrame,
+ totalFrames = me.totalFrames;
+
+ if(attr == SCROLL){
+ val = [me.method(curFrame, start[0], end[0] - start[0], totalFrames),
+ me.method(curFrame, start[1], end[1] - start[1], totalFrames)];
+ }else{
+ val = superclass.doMethod.call(me, attr, start, end);
+ }
+ return val;
+ },
+
+ getAttr : function(attr) {
+ var me = this;
+
+ if (attr == SCROLL) {
+ return [me.el.scrollLeft, me.el.scrollTop];
+ }else{
+ return superclass.getAttr.call(me, attr);
+ }
+ },
+
+ setAttr : function(attr, val, unit) {
+ var me = this;
+
+ if(attr == SCROLL){
+ me.el.scrollLeft = val[0];
+ me.el.scrollTop = val[1];
+ }else{
+ superclass.setAttr.call(me, attr, val, unit);
+ }
+ }
+ });
+})();
+ if (Ext.isIE9m) {
+ function fnCleanUp() {
+ var p = Function.prototype;
+ delete p.createSequence;
+ delete p.defer;
+ delete p.createDelegate;
+ delete p.createCallback;
+ delete p.createInterceptor;
+
+ window.detachEvent("onunload", fnCleanUp);
+ }
+ window.attachEvent("onunload", fnCleanUp);
+ }
+})();
diff --git a/deluge/ui/web/js/extjs/ext-base.js b/deluge/ui/web/js/extjs/ext-base.js
new file mode 100644
index 0000000..29047a7
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-base.js
@@ -0,0 +1,21 @@
+/*
+This file is part of Ext JS 3.4
+
+Copyright (c) 2011-2013 Sencha Inc
+
+Contact: http://www.sencha.com/contact
+
+GNU General Public License Usage
+This file may be used under the terms of the GNU General Public License version 3.0 as
+published by the Free Software Foundation and appearing in the file LICENSE included in the
+packaging of this file.
+
+Please review the following information to ensure the GNU General Public License version 3.0
+requirements will be met: http://www.gnu.org/copyleft/gpl.html.
+
+If you are unsure which license is appropriate for your use, please contact the sales department
+at http://www.sencha.com/contact.
+
+Build date: 2013-04-03 15:07:25
+*/
+window.undefined=window.undefined;Ext={version:"3.4.1.1",versionDetail:{major:3,minor:4,patch:1.1}};Ext.apply=function(d,e,b){if(b){Ext.apply(d,b)}if(d&&e&&typeof e=="object"){for(var a in e){d[a]=e[a]}}return d};(function(){var g=0,f=Object.prototype.toString,y=navigator.userAgent.toLowerCase(),n=function(e){return e.test(y)},s=document,q=s.documentMode,u=s.compatMode=="CSS1Compat",a=n(/opera/),H=n(/\bchrome\b/),z=n(/webkit/),d=!H&&n(/safari/),F=d&&n(/applewebkit\/4/),D=d&&n(/version\/3/),B=d&&n(/version\/4/),j=!a&&n(/msie/),G=j&&((n(/msie 7/)&&q!=8&&q!=9&&q!=10)||q==7),E=j&&((n(/msie 8/)&&q!=7&&q!=9&&q!=10)||q==8),C=j&&((n(/msie 9/)&&q!=7&&q!=8&&q!=10)||q==9),i=j&&((n(/msie 10/)&&q!=7&&q!=8&&q!=9)||q==10),J=j&&n(/msie 6/),K=j&&(J||G||E||C),c=!z&&n(/gecko/),M=c&&n(/rv:1\.8/),L=c&&n(/rv:1\.9/),m=K&&!u,h=n(/windows|win32/),A=n(/macintosh|mac os x/),p=n(/adobeair/),v=n(/linux/),r=/^https/i.test(window.location.protocol),b=[],w=[],o=Ext.emptyFn,x=Ext.apply({},{constructor:o,toString:o,valueOf:o}),l=function(){var e=l.caller.caller;return e.$owner.prototype[e.$name].apply(this,arguments)};if(x.constructor!==o){w.push("constructor")}if(x.toString!==o){w.push("toString")}if(x.valueOf!==o){w.push("valueOf")}if(!w.length){w=null}function k(){}Ext.apply(k,{$isClass:true,callParent:function(e){var t;return(t=this.callParent.caller)&&(t.$previous||((t=t.$owner?t:t.caller)&&t.$owner.superclass.self[t.$name])).apply(this,e||b)}});k.prototype={constructor:function(){},callParent:function(t){var N,e=(N=this.callParent.caller)&&(N.$previous||((N=N.$owner?N:N.caller)&&N.$owner.superclass[N.$name]));return e.apply(this,t||b)}};if(J){try{s.execCommand("BackgroundImageCache",false,true)}catch(I){}}Ext.apply(Ext,{SSL_SECURE_URL:r&&j?'javascript:""':"about:blank",isStrict:u,isSecure:r,isReady:false,enableForcedBoxModel:false,enableGarbageCollector:true,enableListenerCollection:false,enableNestedListenerRemoval:false,USE_NATIVE_JSON:false,applyIf:function(t,N){if(t){for(var e in N){if(!Ext.isDefined(t[e])){t[e]=N[e]}}}return t},id:function(e,t){e=Ext.getDom(e,true)||{};if(!e.id){e.id=(t||"ext-gen")+(++g)}return e.id},extend:function(){var t=function(O){for(var N in O){this[N]=O[N]}};var e=Object.prototype.constructor;return function(S,P,R){if(typeof P=="object"){R=P;P=S;S=R.constructor!=e?R.constructor:function(){P.apply(this,arguments)}}var O=function(){},Q,N=P.prototype;O.prototype=N;Q=S.prototype=new O();Q.constructor=S;S.superclass=N;if(N.constructor==e){N.constructor=P}S.override=function(T){Ext.override(S,T)};Q.superclass=Q.supr=(function(){return N});Q.override=t;Ext.override(S,R);S.extend=function(T){return Ext.extend(S,T)};return S}}(),global:(function(){return this})(),Base:k,namespaceCache:{},createNamespace:function(R,O){var e=Ext.namespaceCache,P=O?R.substring(0,R.lastIndexOf(".")):R,U=e[P],S,N,t,Q,T;if(!U){U=Ext.global;if(P){T=[];Q=P.split(".");for(S=0,N=Q.length;S<N;++S){t=Q[S];U=U[t]||(U[t]={});T.push(t);e[T.join(".")]=U}}}return U},getClassByName:function(N){var O=N.split("."),e=Ext.global,P=O.length,t;for(t=0;e&&t<P;++t){e=e[O[t]]}return e||null},addMembers:function(t,Q,N,e){var P,O,R;for(O in N){if(N.hasOwnProperty(O)){R=N[O];if(typeof R=="function"){R.$owner=t;R.$name=O}Q[O]=R}}if(e&&w){for(P=w.length;P-->0;){O=w[P];if(N.hasOwnProperty(O)){R=N[O];if(typeof R=="function"){R.$owner=t;R.$name=O}Q[O]=R}}}},define:function(R,P,N){var t=P.override,T,Q,e,O;if(t){delete P.override;T=Ext.getClassByName(t);Ext.override(T,P)}else{if(R){O=Ext.createNamespace(R,true);e=R.substring(R.lastIndexOf(".")+1)}T=function S(){this.constructor.apply(this,arguments)};if(R){T.displayName=R}T.$isClass=true;T.callParent=Ext.Base.callParent;if(typeof P=="function"){P=P(T)}Q=P.extend;if(Q){delete P.extend;if(typeof Q=="string"){Q=Ext.getClassByName(Q)}}else{Q=k}Ext.extend(T,Q,P);if(T.prototype.constructor===T){delete T.prototype.constructor}if(!T.prototype.$isClass){Ext.applyIf(T.prototype,k.prototype)}T.prototype.self=T;if(P.xtype){Ext.reg(P.xtype,T)}T=P.singleton?new T():T;if(R){O[e]=T}}if(N){N.call(T)}return T},override:function(P,R){var N,Q;if(R){if(P.$isClass){Q=R.statics;if(Q){delete R.statics}Ext.addMembers(P,P.prototype,R,true);if(Q){Ext.addMembers(P,P,Q)}}else{if(typeof P=="function"){N=P.prototype;Ext.apply(N,R);if(Ext.isIE&&R.hasOwnProperty("toString")){N.toString=R.toString}}else{var e=P.self,t,O;if(e&&e.$isClass){for(t in R){if(R.hasOwnProperty(t)){O=R[t];if(typeof O=="function"){if(e.$className){O.displayName=e.$className+"#"+t}O.$name=t;O.$owner=e;O.$previous=P.hasOwnProperty(t)?P[t]:l}P[t]=O}}}else{Ext.apply(P,R);if(!P.constructor.$isClass){P.constructor.prototype.callParent=k.prototype.callParent;P.constructor.callParent=k.callParent}}}}}},namespace:function(){var O=arguments.length,P=0,t,N,e,R,Q,S;for(;P<O;++P){e=arguments[P];R=arguments[P].split(".");S=window[R[0]];if(S===undefined){S=window[R[0]]={}}Q=R.slice(1);t=Q.length;for(N=0;N<t;++N){S=S[Q[N]]=S[Q[N]]||{}}}return S},urlEncode:function(Q,P){var N,t=[],O=encodeURIComponent;Ext.iterate(Q,function(e,R){N=Ext.isEmpty(R);Ext.each(N?e:R,function(S){t.push("&",O(e),"=",(!Ext.isEmpty(S)&&(S!=e||!N))?(Ext.isDate(S)?Ext.encode(S).replace(/"/g,""):O(S)):"")})});if(!P){t.shift();P=""}return P+t.join("")},urlDecode:function(N,t){if(Ext.isEmpty(N)){return{}}var Q={},P=N.split("&"),R=decodeURIComponent,e,O;Ext.each(P,function(S){S=S.split("=");e=R(S[0]);O=R(S[1]);Q[e]=t||!Q[e]?O:[].concat(Q[e]).concat(O)});return Q},urlAppend:function(e,t){if(!Ext.isEmpty(t)){return e+(e.indexOf("?")===-1?"?":"&")+t}return e},toArray:function(){return j?function(N,Q,O,P){P=[];for(var t=0,e=N.length;t<e;t++){P.push(N[t])}return P.slice(Q||0,O||P.length)}:function(e,N,t){return Array.prototype.slice.call(e,N||0,t||e.length)}}(),isIterable:function(e){if(Ext.isArray(e)||e.callee){return true}if(/NodeList|HTMLCollection/.test(f.call(e))){return true}return((typeof e.nextNode!="undefined"||e.item)&&Ext.isNumber(e.length))},each:function(P,O,N){if(Ext.isEmpty(P,true)){return}if(!Ext.isIterable(P)||Ext.isPrimitive(P)){P=[P]}for(var t=0,e=P.length;t<e;t++){if(O.call(N||P[t],P[t],t,P)===false){return t}}},iterate:function(N,t,e){if(Ext.isEmpty(N)){return}if(Ext.isIterable(N)){Ext.each(N,t,e);return}else{if(typeof N=="object"){for(var O in N){if(N.hasOwnProperty(O)){if(t.call(e||N,O,N[O],N)===false){return}}}}}},getDom:function(N,t){if(!N||!s){return null}if(N.dom){return N.dom}else{if(typeof N=="string"){var O=s.getElementById(N);if(O&&j&&t){if(N==O.getAttribute("id")){return O}else{return null}}return O}else{return N}}},getBody:function(){return Ext.get(s.body||s.documentElement)},getHead:function(){var e;return function(){if(e==undefined){e=Ext.get(s.getElementsByTagName("head")[0])}return e}}(),removeNode:j&&!E?function(){var e;return function(t){if(t&&t.tagName!="BODY"){(Ext.enableNestedListenerRemoval)?Ext.EventManager.purgeElement(t,true):Ext.EventManager.removeAll(t);e=e||s.createElement("div");e.appendChild(t);e.innerHTML="";delete Ext.elCache[t.id]}}}():function(e){if(e&&e.parentNode&&e.tagName!="BODY"){(Ext.enableNestedListenerRemoval)?Ext.EventManager.purgeElement(e,true):Ext.EventManager.removeAll(e);e.parentNode.removeChild(e);delete Ext.elCache[e.id]}},isEmpty:function(t,e){return t===null||t===undefined||((Ext.isArray(t)&&!t.length))||(!e?t==="":false)},isArray:function(e){return f.apply(e)==="[object Array]"},isDate:function(e){return f.apply(e)==="[object Date]"},isObject:function(e){return !!e&&Object.prototype.toString.call(e)==="[object Object]"},isPrimitive:function(e){return Ext.isString(e)||Ext.isNumber(e)||Ext.isBoolean(e)},isFunction:function(e){return f.apply(e)==="[object Function]"},isNumber:function(e){return typeof e==="number"&&isFinite(e)},isString:function(e){return typeof e==="string"},isBoolean:function(e){return typeof e==="boolean"},isElement:function(e){return e?!!e.tagName:false},isDefined:function(e){return typeof e!=="undefined"},isOpera:a,isWebKit:z,isChrome:H,isSafari:d,isSafari3:D,isSafari4:B,isSafari2:F,isIE:j,isIE6:J,isIE7:G,isIE8:E,isIE9:C,isIE10:i,isIE9m:K,isIE10p:j&&!(J||G||E||C),isIEQuirks:j&&(!u&&(J||G||E||C)),isGecko:c,isGecko2:M,isGecko3:L,isBorderBox:m,isLinux:v,isWindows:h,isMac:A,isAir:p});Ext.ns=Ext.namespace})();Ext.ns("Ext.util","Ext.lib","Ext.data","Ext.supports");Ext.elCache={};Ext.apply(Function.prototype,{createInterceptor:function(b,a){var c=this;return !Ext.isFunction(b)?this:function(){var e=this,d=arguments;b.target=e;b.method=c;return(b.apply(a||e||window,d)!==false)?c.apply(e||window,d):null}},createCallback:function(){var a=arguments,b=this;return function(){return b.apply(window,a)}},createDelegate:function(c,b,a){var d=this;return function(){var f=b||arguments;if(a===true){f=Array.prototype.slice.call(arguments,0);f=f.concat(b)}else{if(Ext.isNumber(a)){f=Array.prototype.slice.call(arguments,0);var e=[a,0].concat(b);Array.prototype.splice.apply(f,e)}}return d.apply(c||window,f)}},defer:function(c,e,b,a){var d=this.createDelegate(e,b,a);if(c>0){return setTimeout(d,c)}d();return 0}});Ext.applyIf(String,{format:function(b){var a=Ext.toArray(arguments,1);return b.replace(/\{(\d+)\}/g,function(c,d){return a[d]})}});Ext.applyIf(Array.prototype,{indexOf:function(b,c){var a=this.length;c=c||0;c+=(c<0)?a:0;for(;c<a;++c){if(this[c]===b){return c}}return -1},remove:function(b){var a=this.indexOf(b);if(a!=-1){this.splice(a,1)}return this}});Ext.util.TaskRunner=function(e){e=e||10;var f=[],a=[],b=0,g=false,d=function(){g=false;clearInterval(b);b=0},h=function(){if(!g){g=true;b=setInterval(i,e)}},c=function(j){a.push(j);if(j.onStop){j.onStop.apply(j.scope||j)}},i=function(){var l=a.length,n=new Date().getTime();if(l>0){for(var p=0;p<l;p++){f.remove(a[p])}a=[];if(f.length<1){d();return}}for(var p=0,o,k,m,j=f.length;p<j;++p){o=f[p];k=n-o.taskRunTime;if(o.interval<=k){m=o.run.apply(o.scope||o,o.args||[++o.taskRunCount]);o.taskRunTime=n;if(m===false||o.taskRunCount===o.repeat){c(o);return}}if(o.duration&&o.duration<=(n-o.taskStartTime)){c(o)}}};this.start=function(j){f.push(j);j.taskStartTime=new Date().getTime();j.taskRunTime=0;j.taskRunCount=0;h();return j};this.stop=function(j){c(j);return j};this.stopAll=function(){d();for(var k=0,j=f.length;k<j;k++){if(f[k].onStop){f[k].onStop()}}f=[];a=[]}};Ext.TaskMgr=new Ext.util.TaskRunner();(function(){var b;function c(d){if(!b){b=new Ext.Element.Flyweight()}b.dom=d;return b}(function(){var g=document,e=g.compatMode=="CSS1Compat",f=Math.max,d=Math.round,h=parseInt;Ext.lib.Dom={isAncestor:function(j,k){var i=false;j=Ext.getDom(j);k=Ext.getDom(k);if(j&&k){if(j.contains){return j.contains(k)}else{if(j.compareDocumentPosition){return !!(j.compareDocumentPosition(k)&16)}else{while(k=k.parentNode){i=k==j||i}}}}return i},getViewWidth:function(i){return i?this.getDocumentWidth():this.getViewportWidth()},getViewHeight:function(i){return i?this.getDocumentHeight():this.getViewportHeight()},getDocumentHeight:function(){return f(!e?g.body.scrollHeight:g.documentElement.scrollHeight,this.getViewportHeight())},getDocumentWidth:function(){return f(!e?g.body.scrollWidth:g.documentElement.scrollWidth,this.getViewportWidth())},getViewportHeight:function(){return Ext.isIE9m?(Ext.isStrict?g.documentElement.clientHeight:g.body.clientHeight):self.innerHeight},getViewportWidth:function(){return !Ext.isStrict&&!Ext.isOpera?g.body.clientWidth:Ext.isIE9m?g.documentElement.clientWidth:self.innerWidth},getY:function(i){return this.getXY(i)[1]},getX:function(i){return this.getXY(i)[0]},getXY:function(k){var j,q,s,v,l,m,u=0,r=0,t,i,n=(g.body||g.documentElement),o=[0,0];k=Ext.getDom(k);if(k!=n){if(k.getBoundingClientRect){s=k.getBoundingClientRect();t=c(document).getScroll();o=[d(s.left+t.left),d(s.top+t.top)]}else{j=k;i=c(k).isStyle("position","absolute");while(j){q=c(j);u+=j.offsetLeft;r+=j.offsetTop;i=i||q.isStyle("position","absolute");if(Ext.isGecko){r+=v=h(q.getStyle("borderTopWidth"),10)||0;u+=l=h(q.getStyle("borderLeftWidth"),10)||0;if(j!=k&&!q.isStyle("overflow","visible")){u+=l;r+=v}}j=j.offsetParent}if(Ext.isSafari&&i){u-=n.offsetLeft;r-=n.offsetTop}if(Ext.isGecko&&!i){m=c(n);u+=h(m.getStyle("borderLeftWidth"),10)||0;r+=h(m.getStyle("borderTopWidth"),10)||0}j=k.parentNode;while(j&&j!=n){if(!Ext.isOpera||(j.tagName!="TR"&&!c(j).isStyle("display","inline"))){u-=j.scrollLeft;r-=j.scrollTop}j=j.parentNode}o=[u,r]}}return o},setXY:function(j,k){(j=Ext.fly(j,"_setXY")).position();var l=j.translatePoints(k),i=j.dom.style,m;for(m in l){if(!isNaN(l[m])){i[m]=l[m]+"px"}}},setX:function(j,i){this.setXY(j,[i,false])},setY:function(i,j){this.setXY(i,[false,j])}}})();Ext.lib.Event=function(){var v=false,f={},z=0,o=[],d,A=false,k=window,E=document,l=200,r=20,p=0,i=1,s=2,w=3,t="scrollLeft",q="scrollTop",g="unload",y="mouseover",D="mouseout",e=function(){var F;if(k.addEventListener){F=function(J,H,I,G){if(H=="mouseenter"){I=I.createInterceptor(n);J.addEventListener(y,I,(G))}else{if(H=="mouseleave"){I=I.createInterceptor(n);J.addEventListener(D,I,(G))}else{J.addEventListener(H,I,(G))}}return I}}else{if(k.attachEvent){F=function(J,H,I,G){J.attachEvent("on"+H,I);return I}}else{F=function(){}}}return F}(),h=function(){var F;if(k.removeEventListener){F=function(J,H,I,G){if(H=="mouseenter"){H=y}else{if(H=="mouseleave"){H=D}}J.removeEventListener(H,I,(G))}}else{if(k.detachEvent){F=function(I,G,H){I.detachEvent("on"+G,H)}}else{F=function(){}}}return F}();function n(F){return !u(F.currentTarget,x.getRelatedTarget(F))}function u(F,G){if(F&&F.firstChild){while(G){if(G===F){return true}G=G.parentNode;if(G&&(G.nodeType!=1)){G=null}}}return false}function B(){var G=false,L=[],J,I,F,H,K=!v||(z>0);if(!A){A=true;for(I=0;I<o.length;++I){F=o[I];if(F&&(J=E.getElementById(F.id))){if(!F.checkReady||v||J.nextSibling||(E&&E.body)){H=F.override;J=H?(H===true?F.obj:H):J;F.fn.call(J,F.obj);o.remove(F);--I}else{L.push(F)}}}z=(L.length===0)?0:z-1;if(K){m()}else{clearInterval(d);d=null}G=!(A=false)}return G}function m(){if(!d){var F=function(){B()};d=setInterval(F,r)}}function C(){var F=E.documentElement,G=E.body;if(F&&(F[q]||F[t])){return[F[t],F[q]]}else{if(G){return[G[t],G[q]]}else{return[0,0]}}}function j(F,G){F=F.browserEvent||F;var H=F["page"+G];if(!H&&H!==0){H=F["client"+G]||0;if(Ext.isIE){H+=C()[G=="X"?0:1]}}return H}var x={extAdapter:true,onAvailable:function(H,F,I,G){o.push({id:H,fn:F,obj:I,override:G,checkReady:false});z=l;m()},addListener:function(H,F,G){H=Ext.getDom(H);if(H&&G){if(F==g){if(f[H.id]===undefined){f[H.id]=[]}f[H.id].push([F,G]);return G}return e(H,F,G,false)}return false},removeListener:function(L,H,K){L=Ext.getDom(L);var J,G,F,I;if(L&&K){if(H==g){if((I=f[L.id])!==undefined){for(J=0,G=I.length;J<G;J++){if((F=I[J])&&F[p]==H&&F[i]==K){f[L.id].splice(J,1)}}}return}h(L,H,K,false)}},getTarget:function(F){F=F.browserEvent||F;return this.resolveTextNode(F.target||F.srcElement)},resolveTextNode:Ext.isGecko?function(G){if(!G){return}var F=HTMLElement.prototype.toString.call(G);if(F=="[xpconnect wrapped native prototype]"||F=="[object XULElement]"){return}return G.nodeType==3?G.parentNode:G}:function(F){return F&&F.nodeType==3?F.parentNode:F},getRelatedTarget:function(F){F=F.browserEvent||F;return this.resolveTextNode(F.relatedTarget||(/(mouseout|mouseleave)/.test(F.type)?F.toElement:/(mouseover|mouseenter)/.test(F.type)?F.fromElement:null))},getPageX:function(F){return j(F,"X")},getPageY:function(F){return j(F,"Y")},getXY:function(F){return[this.getPageX(F),this.getPageY(F)]},stopEvent:function(F){this.stopPropagation(F);this.preventDefault(F)},stopPropagation:function(F){F=F.browserEvent||F;if(F.stopPropagation){F.stopPropagation()}else{F.cancelBubble=true}},preventDefault:function(F){F=F.browserEvent||F;if(F.preventDefault){F.preventDefault()}else{if(F.keyCode){F.keyCode=0}F.returnValue=false}},getEvent:function(F){F=F||k.event;if(!F){var G=this.getEvent.caller;while(G){F=G.arguments[0];if(F&&Event==F.constructor){break}G=G.caller}}return F},getCharCode:function(F){F=F.browserEvent||F;return F.charCode||F.keyCode||0},getListeners:function(G,F){Ext.EventManager.getListeners(G,F)},purgeElement:function(G,H,F){Ext.EventManager.purgeElement(G,H,F)},_load:function(F){v=true;if(Ext.isIE9m&&F!==true){h(k,"load",arguments.callee)}},_unload:function(J){var G=Ext.lib.Event,H,M,K,F,I,N;for(F in f){K=f[F];for(H=0,I=K.length;H<I;H++){M=K[H];if(M){try{N=M[w]?(M[w]===true?M[s]:M[w]):k;M[i].call(N,G.getEvent(J),M[s])}catch(L){}}}}Ext.EventManager._unload();h(k,g,G._unload)}};x.on=x.addListener;x.un=x.removeListener;if(E&&E.body){x._load(true)}else{e(k,"load",x._load)}e(k,g,x._unload);B();return x}();Ext.lib.Ajax=function(){var g=["Msxml2.XMLHTTP.3.0","Msxml2.XMLHTTP"],d="Content-Type";function h(v){var t=v.conn,w,u={};function s(x,y){for(w in y){if(y.hasOwnProperty(w)){x.setRequestHeader(w,y[w])}}}Ext.apply(u,k.headers,k.defaultHeaders);s(t,u);delete k.headers}function e(v,u,t,s){return{tId:v,status:t?-1:0,statusText:t?"transaction aborted":"communication failure",isAbort:t,isTimeout:s,argument:u}}function j(s,t){(k.headers=k.headers||{})[s]=t}function p(u,y){var C={},x,w=u.conn,A,B,v=w.status==1223;try{x=u.conn.getAllResponseHeaders();Ext.each(x.replace(/\r\n/g,"\n").split("\n"),function(s){A=s.indexOf(":");if(A>=0){B=s.substr(0,A).toLowerCase();if(s.charAt(A+1)==" "){++A}C[B]=s.substr(A+1)}})}catch(z){}return{tId:u.tId,status:v?204:w.status,statusText:v?"No Content":w.statusText,getResponseHeader:function(s){return C[s.toLowerCase()]},getAllResponseHeaders:function(){return x},responseText:w.responseText,responseXML:w.responseXML,argument:y}}function o(s){if(s.tId){k.conn[s.tId]=null}s.conn=null;s=null}function f(x,y,t,s){if(!y){o(x);return}var v,u;try{if(x.conn.status!==undefined&&x.conn.status!=0){v=x.conn.status}else{v=13030}}catch(w){v=13030}if((v>=200&&v<300)||(Ext.isIE&&v==1223)){u=p(x,y.argument);if(y.success){if(!y.scope){y.success(u)}else{y.success.apply(y.scope,[u])}}}else{switch(v){case 12002:case 12029:case 12030:case 12031:case 12152:case 13030:u=e(x.tId,y.argument,(t?t:false),s);if(y.failure){if(!y.scope){y.failure(u)}else{y.failure.apply(y.scope,[u])}}break;default:u=p(x,y.argument);if(y.failure){if(!y.scope){y.failure(u)}else{y.failure.apply(y.scope,[u])}}}}o(x);u=null}function m(u,x,s,w,t,v){if(s&&s.readyState==4){clearInterval(t[w]);t[w]=null;if(v){clearTimeout(k.timeout[w]);k.timeout[w]=null}f(u,x)}}function r(s,t){k.abort(s,t,true)}function n(u,x){x=x||{};var s=u.conn,w=u.tId,t=k.poll,v=x.timeout||null;if(v){k.conn[w]=s;k.timeout[w]=setTimeout(r.createCallback(u,x),v)}t[w]=setInterval(m.createCallback(u,x,s,w,t,v),k.pollInterval)}function i(w,t,v,s){var u=l()||null;if(u){u.conn.open(w,t,true);if(k.useDefaultXhrHeader){j("X-Requested-With",k.defaultXhrHeader)}if(s&&k.useDefaultHeader&&(!k.headers||!k.headers[d])){j(d,k.defaultPostHeader)}if(k.defaultHeaders||k.headers){h(u)}n(u,v);u.conn.send(s||null)}return u}function l(){var t;try{if(t=q(k.transactionId)){k.transactionId++}}catch(s){}finally{return t}}function q(v){var s;try{s=new XMLHttpRequest()}catch(u){for(var t=Ext.isIE6?1:0;t<g.length;++t){try{s=new ActiveXObject(g[t]);break}catch(u){}}}finally{return{conn:s,tId:v}}}var k={request:function(s,u,v,w,A){if(A){var x=this,t=A.xmlData,y=A.jsonData,z;Ext.applyIf(x,A);if(t||y){z=x.headers;if(!z||!z[d]){j(d,t?"text/xml":"application/json")}w=t||(!Ext.isPrimitive(y)?Ext.encode(y):y)}}return i(s||A.method||"POST",u,v,w)},serializeForm:function(y){var x=y.elements||(document.forms[y]||Ext.getDom(y)).elements,s=false,w=encodeURIComponent,t,z="",v,u;Ext.each(x,function(A){t=A.name;v=A.type;if(!A.disabled&&t){if(/select-(one|multiple)/i.test(v)){Ext.each(A.options,function(B){if(B.selected){u=B.hasAttribute?B.hasAttribute("value"):B.getAttributeNode("value").specified;z+=String.format("{0}={1}&",w(t),w(u?B.value:B.text))}})}else{if(!(/file|undefined|reset|button/i.test(v))){if(!(/radio|checkbox/i.test(v)&&!A.checked)&&!(v=="submit"&&s)){z+=w(t)+"="+w(A.value)+"&";s=/submit/i.test(v)}}}}});return z.substr(0,z.length-1)},useDefaultHeader:true,defaultPostHeader:"application/x-www-form-urlencoded; charset=UTF-8",useDefaultXhrHeader:true,defaultXhrHeader:"XMLHttpRequest",poll:{},timeout:{},conn:{},pollInterval:50,transactionId:0,abort:function(v,x,s){var u=this,w=v.tId,t=false;if(u.isCallInProgress(v)){v.conn.abort();clearInterval(u.poll[w]);u.poll[w]=null;clearTimeout(k.timeout[w]);u.timeout[w]=null;f(v,x,(t=true),s)}return t},isCallInProgress:function(s){return s.conn&&!{0:true,4:true}[s.conn.readyState]}};return k}();(function(){var g=Ext.lib,i=/width|height|opacity|padding/i,f=/^((width|height)|(top|left))$/,d=/width|height|top$|bottom$|left$|right$/i,h=/\d+(em|%|en|ex|pt|in|cm|mm|pc)$/i,j=function(k){return typeof k!=="undefined"},e=function(){return new Date()};g.Anim={motion:function(n,l,o,p,k,m){return this.run(n,l,o,p,k,m,Ext.lib.Motion)},run:function(o,l,q,r,k,n,m){m=m||Ext.lib.AnimBase;if(typeof r=="string"){r=Ext.lib.Easing[r]}var p=new m(o,l,q,r);p.animateX(function(){if(Ext.isFunction(k)){k.call(n)}});return p}};g.AnimBase=function(l,k,m,n){if(l){this.init(l,k,m,n)}};g.AnimBase.prototype={doMethod:function(k,n,l){var m=this;return m.method(m.curFrame,n,l-n,m.totalFrames)},setAttr:function(k,m,l){if(i.test(k)&&m<0){m=0}Ext.fly(this.el,"_anim").setStyle(k,m+l)},getAttr:function(k){var m=Ext.fly(this.el),n=m.getStyle(k),l=f.exec(k)||[];if(n!=="auto"&&!h.test(n)){return parseFloat(n)}return(!!(l[2])||(m.getStyle("position")=="absolute"&&!!(l[3])))?m.dom["offset"+l[0].charAt(0).toUpperCase()+l[0].substr(1)]:0},getDefaultUnit:function(k){return d.test(k)?"px":""},animateX:function(n,k){var l=this,m=function(){l.onComplete.removeListener(m);if(Ext.isFunction(n)){n.call(k||l,l)}};l.onComplete.addListener(m,l);l.animate()},setRunAttr:function(p){var r=this,s=this.attributes[p],t=s.to,q=s.by,u=s.from,v=s.unit,l=(this.runAttrs[p]={}),m;if(!j(t)&&!j(q)){return false}var k=j(u)?u:r.getAttr(p);if(j(t)){m=t}else{if(j(q)){if(Ext.isArray(k)){m=[];for(var n=0,o=k.length;n<o;n++){m[n]=k[n]+q[n]}}else{m=k+q}}}Ext.apply(l,{start:k,end:m,unit:j(v)?v:r.getDefaultUnit(p)})},init:function(l,p,o,k){var r=this,n=0,s=g.AnimMgr;Ext.apply(r,{isAnimated:false,startTime:null,el:Ext.getDom(l),attributes:p||{},duration:o||1,method:k||g.Easing.easeNone,useSec:true,curFrame:0,totalFrames:s.fps,runAttrs:{},animate:function(){var u=this,v=u.duration;if(u.isAnimated){return false}u.curFrame=0;u.totalFrames=u.useSec?Math.ceil(s.fps*v):v;s.registerElement(u)},stop:function(u){var v=this;if(u){v.curFrame=v.totalFrames;v._onTween.fire()}s.stop(v)}});var t=function(){var v=this,u;v.onStart.fire();v.runAttrs={};for(u in this.attributes){this.setRunAttr(u)}v.isAnimated=true;v.startTime=e();n=0};var q=function(){var v=this;v.onTween.fire({duration:e()-v.startTime,curFrame:v.curFrame});var w=v.runAttrs;for(var u in w){this.setAttr(u,v.doMethod(u,w[u].start,w[u].end),w[u].unit)}++n};var m=function(){var u=this,w=(e()-u.startTime)/1000,v={duration:w,frames:n,fps:n/w};u.isAnimated=false;n=0;u.onComplete.fire(v)};r.onStart=new Ext.util.Event(r);r.onTween=new Ext.util.Event(r);r.onComplete=new Ext.util.Event(r);(r._onStart=new Ext.util.Event(r)).addListener(t);(r._onTween=new Ext.util.Event(r)).addListener(q);(r._onComplete=new Ext.util.Event(r)).addListener(m)}};Ext.lib.AnimMgr=new function(){var o=this,m=null,l=[],k=0;Ext.apply(o,{fps:1000,delay:1,registerElement:function(q){l.push(q);++k;q._onStart.fire();o.start()},unRegister:function(r,q){r._onComplete.fire();q=q||p(r);if(q!=-1){l.splice(q,1)}if(--k<=0){o.stop()}},start:function(){if(m===null){m=setInterval(o.run,o.delay)}},stop:function(s){if(!s){clearInterval(m);for(var r=0,q=l.length;r<q;++r){if(l[0].isAnimated){o.unRegister(l[0],0)}}l=[];m=null;k=0}else{o.unRegister(s)}},run:function(){var t,s,q,r;for(s=0,q=l.length;s<q;s++){r=l[s];if(r&&r.isAnimated){t=r.totalFrames;if(r.curFrame<t||t===null){++r.curFrame;if(r.useSec){n(r)}r._onTween.fire()}else{o.stop(r)}}}}});var p=function(s){var r,q;for(r=0,q=l.length;r<q;r++){if(l[r]===s){return r}}return -1};var n=function(r){var v=r.totalFrames,u=r.curFrame,t=r.duration,s=(u*t*1000/v),q=(e()-r.startTime),w=0;if(q<t*1000){w=Math.round((q/s-1)*u)}else{w=v-(u+1)}if(w>0&&isFinite(w)){if(r.curFrame+w>=v){w=v-(u+1)}r.curFrame+=w}}};g.Bezier=new function(){this.getPosition=function(p,o){var r=p.length,m=[],q=1-o,l,k;for(l=0;l<r;++l){m[l]=[p[l][0],p[l][1]]}for(k=1;k<r;++k){for(l=0;l<r-k;++l){m[l][0]=q*m[l][0]+o*m[parseInt(l+1,10)][0];m[l][1]=q*m[l][1]+o*m[parseInt(l+1,10)][1]}}return[m[0][0],m[0][1]]}};g.Easing={easeNone:function(l,k,n,m){return n*l/m+k},easeIn:function(l,k,n,m){return n*(l/=m)*l+k},easeOut:function(l,k,n,m){return -n*(l/=m)*(l-2)+k}};(function(){g.Motion=function(o,n,p,q){if(o){g.Motion.superclass.constructor.call(this,o,n,p,q)}};Ext.extend(g.Motion,Ext.lib.AnimBase);var m=g.Motion.superclass,l=/^points$/i;Ext.apply(g.Motion.prototype,{setAttr:function(n,r,q){var p=this,o=m.setAttr;if(l.test(n)){q=q||"px";o.call(p,"left",r[0],q);o.call(p,"top",r[1],q)}else{o.call(p,n,r,q)}},getAttr:function(n){var p=this,o=m.getAttr;return l.test(n)?[o.call(p,"left"),o.call(p,"top")]:o.call(p,n)},doMethod:function(n,q,o){var p=this;return l.test(n)?g.Bezier.getPosition(p.runAttrs[n],p.method(p.curFrame,0,100,p.totalFrames)/100):m.doMethod.call(p,n,q,o)},setRunAttr:function(u){if(l.test(u)){var w=this,p=this.el,z=this.attributes.points,s=z.control||[],x=z.from,y=z.to,v=z.by,A=g.Dom,o,r,q,t,n;if(s.length>0&&!Ext.isArray(s[0])){s=[s]}else{}Ext.fly(p,"_anim").position();A.setXY(p,j(x)?x:A.getXY(p));o=w.getAttr("points");if(j(y)){q=k.call(w,y,o);for(r=0,t=s.length;r<t;++r){s[r]=k.call(w,s[r],o)}}else{if(j(v)){q=[o[0]+v[0],o[1]+v[1]];for(r=0,t=s.length;r<t;++r){s[r]=[o[0]+s[r][0],o[1]+s[r][1]]}}}n=this.runAttrs[u]=[o];if(s.length>0){n=n.concat(s)}n[n.length]=q}else{m.setRunAttr.call(this,u)}}});var k=function(n,p){var o=g.Dom.getXY(this.el);return[n[0]-o[0]+p[0],n[1]-o[1]+p[1]]}})()})();(function(){var d=Math.abs,i=Math.PI,h=Math.asin,g=Math.pow,e=Math.sin,f=Ext.lib;Ext.apply(f.Easing,{easeBoth:function(k,j,m,l){return((k/=l/2)<1)?m/2*k*k+j:-m/2*((--k)*(k-2)-1)+j},easeInStrong:function(k,j,m,l){return m*(k/=l)*k*k*k+j},easeOutStrong:function(k,j,m,l){return -m*((k=k/l-1)*k*k*k-1)+j},easeBothStrong:function(k,j,m,l){return((k/=l/2)<1)?m/2*k*k*k*k+j:-m/2*((k-=2)*k*k*k-2)+j},elasticIn:function(l,j,q,o,k,n){if(l==0||(l/=o)==1){return l==0?j:j+q}n=n||(o*0.3);var m;if(k>=d(q)){m=n/(2*i)*h(q/k)}else{k=q;m=n/4}return -(k*g(2,10*(l-=1))*e((l*o-m)*(2*i)/n))+j},elasticOut:function(l,j,q,o,k,n){if(l==0||(l/=o)==1){return l==0?j:j+q}n=n||(o*0.3);var m;if(k>=d(q)){m=n/(2*i)*h(q/k)}else{k=q;m=n/4}return k*g(2,-10*l)*e((l*o-m)*(2*i)/n)+q+j},elasticBoth:function(l,j,q,o,k,n){if(l==0||(l/=o/2)==2){return l==0?j:j+q}n=n||(o*(0.3*1.5));var m;if(k>=d(q)){m=n/(2*i)*h(q/k)}else{k=q;m=n/4}return l<1?-0.5*(k*g(2,10*(l-=1))*e((l*o-m)*(2*i)/n))+j:k*g(2,-10*(l-=1))*e((l*o-m)*(2*i)/n)*0.5+q+j},backIn:function(k,j,n,m,l){l=l||1.70158;return n*(k/=m)*k*((l+1)*k-l)+j},backOut:function(k,j,n,m,l){if(!l){l=1.70158}return n*((k=k/m-1)*k*((l+1)*k+l)+1)+j},backBoth:function(k,j,n,m,l){l=l||1.70158;return((k/=m/2)<1)?n/2*(k*k*(((l*=(1.525))+1)*k-l))+j:n/2*((k-=2)*k*(((l*=(1.525))+1)*k+l)+2)+j},bounceIn:function(k,j,m,l){return m-f.Easing.bounceOut(l-k,0,m,l)+j},bounceOut:function(k,j,m,l){if((k/=l)<(1/2.75)){return m*(7.5625*k*k)+j}else{if(k<(2/2.75)){return m*(7.5625*(k-=(1.5/2.75))*k+0.75)+j}else{if(k<(2.5/2.75)){return m*(7.5625*(k-=(2.25/2.75))*k+0.9375)+j}}}return m*(7.5625*(k-=(2.625/2.75))*k+0.984375)+j},bounceBoth:function(k,j,m,l){return(k<l/2)?f.Easing.bounceIn(k*2,0,m,l)*0.5+j:f.Easing.bounceOut(k*2-l,0,m,l)*0.5+m*0.5+j}})})();(function(){var h=Ext.lib;h.Anim.color=function(p,n,q,r,m,o){return h.Anim.run(p,n,q,r,m,o,h.ColorAnim)};h.ColorAnim=function(n,m,o,p){h.ColorAnim.superclass.constructor.call(this,n,m,o,p)};Ext.extend(h.ColorAnim,h.AnimBase);var j=h.ColorAnim.superclass,i=/color$/i,f=/^transparent|rgba\(0, 0, 0, 0\)$/,l=/^rgb\(([0-9]+)\s*,\s*([0-9]+)\s*,\s*([0-9]+)\)$/i,d=/^#?([0-9A-F]{2})([0-9A-F]{2})([0-9A-F]{2})$/i,e=/^#?([0-9A-F]{1})([0-9A-F]{1})([0-9A-F]{1})$/i,g=function(m){return typeof m!=="undefined"};function k(n){var p=parseInt,o,m=null,q;if(n.length==3){return n}Ext.each([d,l,e],function(s,r){o=(r%2==0)?16:10;q=s.exec(n);if(q&&q.length==4){m=[p(q[1],o),p(q[2],o),p(q[3],o)];return false}});return m}Ext.apply(h.ColorAnim.prototype,{getAttr:function(m){var o=this,n=o.el,p;if(i.test(m)){while(n&&f.test(p=Ext.fly(n).getStyle(m))){n=n.parentNode;p="fff"}}else{p=j.getAttr.call(o,m)}return p},doMethod:function(s,m,o){var t=this,n,q=Math.floor,p,r,u;if(i.test(s)){n=[];o=o||[];for(p=0,r=m.length;p<r;p++){u=m[p];n[p]=j.doMethod.call(t,s,u,o[p])}n="rgb("+q(n[0])+","+q(n[1])+","+q(n[2])+")"}else{n=j.doMethod.call(t,s,m,o)}return n},setRunAttr:function(r){var t=this,u=t.attributes[r],v=u.to,s=u.by,n;j.setRunAttr.call(t,r);n=t.runAttrs[r];if(i.test(r)){var m=k(n.start),o=k(n.end);if(!g(v)&&g(s)){o=k(s);for(var p=0,q=m.length;p<q;p++){o[p]=m[p]+o[p]}}n.start=m;n.end=o}}})})();(function(){var d=Ext.lib;d.Anim.scroll=function(j,h,k,l,g,i){return d.Anim.run(j,h,k,l,g,i,d.Scroll)};d.Scroll=function(h,g,i,j){if(h){d.Scroll.superclass.constructor.call(this,h,g,i,j)}};Ext.extend(d.Scroll,d.ColorAnim);var f=d.Scroll.superclass,e="scroll";Ext.apply(d.Scroll.prototype,{doMethod:function(g,m,h){var k,j=this,l=j.curFrame,i=j.totalFrames;if(g==e){k=[j.method(l,m[0],h[0]-m[0],i),j.method(l,m[1],h[1]-m[1],i)]}else{k=f.doMethod.call(j,g,m,h)}return k},getAttr:function(g){var h=this;if(g==e){return[h.el.scrollLeft,h.el.scrollTop]}else{return f.getAttr.call(h,g)}},setAttr:function(g,j,i){var h=this;if(g==e){h.el.scrollLeft=j[0];h.el.scrollTop=j[1]}else{f.setAttr.call(h,g,j,i)}}})})();if(Ext.isIE9m){function a(){var d=Function.prototype;delete d.createSequence;delete d.defer;delete d.createDelegate;delete d.createCallback;delete d.createInterceptor;window.detachEvent("onunload",a)}window.attachEvent("onunload",a)}})(); \ No newline at end of file
diff --git a/deluge/ui/web/js/extjs/ext-extensions-debug.js b/deluge/ui/web/js/extjs/ext-extensions-debug.js
new file mode 100644
index 0000000..a5b4a60
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions-debug.js
@@ -0,0 +1,2931 @@
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+Ext.ns('Ext.ux.form');
+
+/**
+ * @class Ext.ux.form.FileUploadField
+ * @extends Ext.form.TextField
+ * Creates a file upload field.
+ * @xtype fileuploadfield
+ */
+Ext.ux.form.FileUploadField = Ext.extend(Ext.form.TextField, {
+ /**
+ * @cfg {String} buttonText The button text to display on the upload button (defaults to
+ * 'Browse...'). Note that if you supply a value for {@link #buttonCfg}, the buttonCfg.text
+ * value will be used instead if available.
+ */
+ buttonText: 'Browse...',
+ /**
+ * @cfg {Boolean} buttonOnly True to display the file upload field as a button with no visible
+ * text field (defaults to false). If true, all inherited TextField members will still be available.
+ */
+ buttonOnly: false,
+ /**
+ * @cfg {Number} buttonOffset The number of pixels of space reserved between the button and the text field
+ * (defaults to 3). Note that this only applies if {@link #buttonOnly} = false.
+ */
+ buttonOffset: 3,
+
+ /**
+ * @cfg {Boolean} multiple True to select more than one file. (defaults to false).
+ * Note that this only applies if the HTML doc is using HTML5.
+ */
+ multiple: false,
+
+ /**
+ * @cfg {Object} buttonCfg A standard {@link Ext.Button} config object.
+ */
+
+ // private
+ readOnly: true,
+
+ /**
+ * @hide
+ * @method autoSize
+ */
+ autoSize: Ext.emptyFn,
+
+ // private
+ initComponent: function() {
+ Ext.ux.form.FileUploadField.superclass.initComponent.call(this);
+
+ this.addEvents(
+ /**
+ * @event fileselected
+ * Fires when the underlying file input field's value has changed from the user
+ * selecting a new file from the system file selection dialog.
+ * @param {Ext.ux.form.FileUploadField} this
+ * @param {String} value The file value returned by the underlying file input field
+ */
+ 'fileselected'
+ );
+ },
+
+ // private
+ onRender: function(ct, position) {
+ Ext.ux.form.FileUploadField.superclass.onRender.call(
+ this,
+ ct,
+ position
+ );
+
+ this.wrap = this.el.wrap({ cls: 'x-form-field-wrap x-form-file-wrap' });
+ this.el.addClass('x-form-file-text');
+ this.el.dom.removeAttribute('name');
+ this.createFileInput();
+
+ var btnCfg = Ext.applyIf(this.buttonCfg || {}, {
+ text: this.buttonText,
+ });
+ this.button = new Ext.Button(
+ Ext.apply(btnCfg, {
+ renderTo: this.wrap,
+ cls: 'x-form-file-btn' + (btnCfg.iconCls ? ' x-btn-icon' : ''),
+ })
+ );
+
+ if (this.buttonOnly) {
+ this.el.hide();
+ this.wrap.setWidth(this.button.getEl().getWidth());
+ }
+
+ this.bindListeners();
+ this.resizeEl = this.positionEl = this.wrap;
+ },
+
+ bindListeners: function() {
+ this.fileInput.on({
+ scope: this,
+ mouseenter: function() {
+ this.button.addClass(['x-btn-over', 'x-btn-focus']);
+ },
+ mouseleave: function() {
+ this.button.removeClass([
+ 'x-btn-over',
+ 'x-btn-focus',
+ 'x-btn-click',
+ ]);
+ },
+ mousedown: function() {
+ this.button.addClass('x-btn-click');
+ },
+ mouseup: function() {
+ this.button.removeClass([
+ 'x-btn-over',
+ 'x-btn-focus',
+ 'x-btn-click',
+ ]);
+ },
+ change: function() {
+ var value = this.fileInput.dom.files;
+ // Fallback to value.
+ if (!value) value = this.fileInput.dom.value;
+ this.setValue(value);
+ this.fireEvent('fileselected', this, value);
+ },
+ });
+ },
+
+ createFileInput: function() {
+ this.fileInput = this.wrap.createChild({
+ id: this.getFileInputId(),
+ name: this.name || this.getId(),
+ cls: 'x-form-file',
+ tag: 'input',
+ type: 'file',
+ size: 1,
+ });
+ this.fileInput.dom.multiple = this.multiple;
+ },
+
+ reset: function() {
+ if (this.rendered) {
+ this.fileInput.remove();
+ this.createFileInput();
+ this.bindListeners();
+ }
+ Ext.ux.form.FileUploadField.superclass.reset.call(this);
+ },
+
+ // private
+ getFileInputId: function() {
+ return this.id + '-file';
+ },
+
+ // private
+ onResize: function(w, h) {
+ Ext.ux.form.FileUploadField.superclass.onResize.call(this, w, h);
+
+ this.wrap.setWidth(w);
+
+ if (!this.buttonOnly) {
+ var w =
+ this.wrap.getWidth() -
+ this.button.getEl().getWidth() -
+ this.buttonOffset;
+ this.el.setWidth(w);
+ }
+ },
+
+ // private
+ onDestroy: function() {
+ Ext.ux.form.FileUploadField.superclass.onDestroy.call(this);
+ Ext.destroy(this.fileInput, this.button, this.wrap);
+ },
+
+ onDisable: function() {
+ Ext.ux.form.FileUploadField.superclass.onDisable.call(this);
+ this.doDisable(true);
+ },
+
+ onEnable: function() {
+ Ext.ux.form.FileUploadField.superclass.onEnable.call(this);
+ this.doDisable(false);
+ },
+
+ // private
+ doDisable: function(disabled) {
+ this.fileInput.dom.disabled = disabled;
+ this.button.setDisabled(disabled);
+ },
+
+ // private
+ preFocus: Ext.emptyFn,
+
+ // private
+ alignErrorIcon: function() {
+ this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
+ },
+});
+
+Ext.reg('fileuploadfield', Ext.ux.form.FileUploadField);
+
+// backwards compat
+Ext.form.FileUploadField = Ext.ux.form.FileUploadField;
+/**
+ * Ext.ux.form.RadioGroup.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+// Allow radiogroups to be treated as a single form element.
+Ext.override(Ext.form.RadioGroup, {
+ afterRender: function() {
+ this.items.each(function(i) {
+ this.relayEvents(i, ['check']);
+ }, this);
+ if (this.lazyValue) {
+ this.setValue(this.value);
+ delete this.value;
+ delete this.lazyValue;
+ }
+ Ext.form.RadioGroup.superclass.afterRender.call(this);
+ },
+
+ getName: function() {
+ return this.items.first().getName();
+ },
+
+ getValue: function() {
+ return this.items.first().getGroupValue();
+ },
+
+ setValue: function(v) {
+ if (!this.items.each) {
+ this.value = v;
+ this.lazyValue = true;
+ return;
+ }
+ this.items.each(function(item) {
+ if (item.rendered) {
+ var checked = item.el.getValue() == String(v);
+ item.el.dom.checked = checked;
+ item.el.dom.defaultChecked = checked;
+ item.wrap[checked ? 'addClass' : 'removeClass'](
+ item.checkedCls
+ );
+ }
+ });
+ },
+});
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+Ext.ns('Ext.ux.form');
+
+/**
+ * @class Ext.ux.form.SpinnerField
+ * @extends Ext.form.NumberField
+ * Creates a field utilizing Ext.ux.Spinner
+ * @xtype spinnerfield
+ */
+Ext.ux.form.SpinnerField = Ext.extend(Ext.form.NumberField, {
+ actionMode: 'wrap',
+ deferHeight: true,
+ autoSize: Ext.emptyFn,
+ onBlur: Ext.emptyFn,
+ adjustSize: Ext.BoxComponent.prototype.adjustSize,
+
+ constructor: function(config) {
+ var spinnerConfig = Ext.copyTo(
+ {},
+ config,
+ 'incrementValue,alternateIncrementValue,accelerate,defaultValue,triggerClass,splitterClass'
+ );
+
+ var spl = (this.spinner = new Ext.ux.Spinner(spinnerConfig));
+
+ var plugins = config.plugins
+ ? Ext.isArray(config.plugins)
+ ? config.plugins.push(spl)
+ : [config.plugins, spl]
+ : spl;
+
+ Ext.ux.form.SpinnerField.superclass.constructor.call(
+ this,
+ Ext.apply(config, { plugins: plugins })
+ );
+ },
+
+ // private
+ getResizeEl: function() {
+ return this.wrap;
+ },
+
+ // private
+ getPositionEl: function() {
+ return this.wrap;
+ },
+
+ // private
+ alignErrorIcon: function() {
+ if (this.wrap) {
+ this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
+ }
+ },
+
+ validateBlur: function() {
+ return true;
+ },
+});
+
+Ext.reg('spinnerfield', Ext.ux.form.SpinnerField);
+
+//backwards compat
+Ext.form.SpinnerField = Ext.ux.form.SpinnerField;
+/**
+ * Ext.ux.form.SpinnerField.js
+ *
+ * Copyright (c) Damien Churchill 2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Ext.override(Ext.ux.form.SpinnerField, {
+ onBlur: Ext.form.Field.prototype.onBlur,
+});
+/**
+ * Ext.ux.form.SpinnerGroup.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Ext.ux.form');
+
+/**
+ *
+ */
+Ext.ux.form.SpinnerGroup = Ext.extend(Ext.form.CheckboxGroup, {
+ // private
+ defaultType: 'spinnerfield',
+ anchor: '98%',
+
+ // private
+ groupCls: 'x-form-spinner-group',
+
+ colCfg: {},
+
+ // private
+ onRender: function(ct, position) {
+ if (!this.el) {
+ var panelCfg = {
+ cls: this.groupCls,
+ layout: 'column',
+ border: false,
+ renderTo: ct,
+ };
+ var colCfg = Ext.apply(
+ {
+ defaultType: this.defaultType,
+ layout: 'form',
+ border: false,
+ labelWidth: 60,
+ defaults: {
+ hideLabel: true,
+ anchor: '60%',
+ },
+ },
+ this.colCfg
+ );
+
+ if (this.items[0].items) {
+ // The container has standard ColumnLayout configs, so pass them in directly
+
+ Ext.apply(panelCfg, {
+ layoutConfig: { columns: this.items.length },
+ defaults: this.defaults,
+ items: this.items,
+ });
+ for (var i = 0, len = this.items.length; i < len; i++) {
+ Ext.applyIf(this.items[i], colCfg);
+ }
+ } else {
+ // The container has field item configs, so we have to generate the column
+ // panels first then move the items into the columns as needed.
+
+ var numCols,
+ cols = [];
+
+ if (typeof this.columns == 'string') {
+ // 'auto' so create a col per item
+ this.columns = this.items.length;
+ }
+ if (!Ext.isArray(this.columns)) {
+ var cs = [];
+ for (var i = 0; i < this.columns; i++) {
+ cs.push((100 / this.columns) * 0.01); // distribute by even %
+ }
+ this.columns = cs;
+ }
+
+ numCols = this.columns.length;
+
+ // Generate the column configs with the correct width setting
+ for (var i = 0; i < numCols; i++) {
+ var cc = Ext.apply({ items: [] }, colCfg);
+ cc[
+ this.columns[i] <= 1 ? 'columnWidth' : 'width'
+ ] = this.columns[i];
+ if (this.defaults) {
+ cc.defaults = Ext.apply(
+ cc.defaults || {},
+ this.defaults
+ );
+ }
+ cols.push(cc);
+ }
+
+ // Distribute the original items into the columns
+ if (this.vertical) {
+ var rows = Math.ceil(this.items.length / numCols),
+ ri = 0;
+ for (var i = 0, len = this.items.length; i < len; i++) {
+ if (i > 0 && i % rows == 0) {
+ ri++;
+ }
+ if (this.items[i].fieldLabel) {
+ this.items[i].hideLabel = false;
+ }
+ cols[ri].items.push(this.items[i]);
+ }
+ } else {
+ for (var i = 0, len = this.items.length; i < len; i++) {
+ var ci = i % numCols;
+ if (this.items[i].fieldLabel) {
+ this.items[i].hideLabel = false;
+ }
+ cols[ci].items.push(this.items[i]);
+ }
+ }
+
+ Ext.apply(panelCfg, {
+ layoutConfig: { columns: numCols },
+ items: cols,
+ });
+ }
+
+ this.panel = new Ext.Panel(panelCfg);
+ this.el = this.panel.getEl();
+
+ if (this.forId && this.itemCls) {
+ var l = this.el.up(this.itemCls).child('label', true);
+ if (l) {
+ l.setAttribute('htmlFor', this.forId);
+ }
+ }
+
+ var fields = this.panel.findBy(function(c) {
+ return c.isFormField;
+ }, this);
+
+ this.items = new Ext.util.MixedCollection();
+ this.items.addAll(fields);
+
+ this.items.each(function(field) {
+ field.on('spin', this.onFieldChange, this);
+ field.on('change', this.onFieldChange, this);
+ }, this);
+
+ if (this.lazyValueSet) {
+ this.setValue(this.value);
+ delete this.value;
+ delete this.lazyValueSet;
+ }
+
+ if (this.lazyRawValueSet) {
+ this.setRawValue(this.rawValue);
+ delete this.rawValue;
+ delete this.lazyRawValueSet;
+ }
+ }
+
+ Ext.ux.form.SpinnerGroup.superclass.onRender.call(this, ct, position);
+ },
+
+ onFieldChange: function(spinner) {
+ this.fireEvent('change', this, this.getValue());
+ },
+
+ initValue: Ext.emptyFn,
+
+ getValue: function() {
+ var value = [this.items.getCount()];
+ this.items.each(function(item, i) {
+ value[i] = Number(item.getValue());
+ });
+ return value;
+ },
+
+ getRawValue: function() {
+ var value = [this.items.getCount()];
+ this.items.each(function(item, i) {
+ value[i] = Number(item.getRawValue());
+ });
+ return value;
+ },
+
+ setValue: function(value) {
+ if (!this.rendered) {
+ this.value = value;
+ this.lazyValueSet = true;
+ } else {
+ this.items.each(function(item, i) {
+ item.setValue(value[i]);
+ });
+ }
+ },
+
+ setRawValue: function(value) {
+ if (!this.rendered) {
+ this.rawValue = value;
+ this.lazyRawValueSet = true;
+ } else {
+ this.items.each(function(item, i) {
+ item.setRawValue(value[i]);
+ });
+ }
+ },
+});
+Ext.reg('spinnergroup', Ext.ux.form.SpinnerGroup);
+/**
+ * Ext.ux.form.ToggleField.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Ext.ux.form');
+
+/**
+ * Ext.ux.form.ToggleField class
+ *
+ * @author Damien Churchill
+ * @version v0.1
+ *
+ * @class Ext.ux.form.ToggleField
+ * @extends Ext.form.TriggerField
+ */
+Ext.ux.form.ToggleField = Ext.extend(Ext.form.Field, {
+ cls: 'x-toggle-field',
+
+ initComponent: function() {
+ Ext.ux.form.ToggleField.superclass.initComponent.call(this);
+
+ this.toggle = new Ext.form.Checkbox();
+ this.toggle.on('check', this.onToggleCheck, this);
+
+ this.input = new Ext.form.TextField({
+ disabled: true,
+ });
+ },
+
+ onRender: function(ct, position) {
+ if (!this.el) {
+ this.panel = new Ext.Panel({
+ cls: this.groupCls,
+ layout: 'table',
+ layoutConfig: {
+ columns: 2,
+ },
+ border: false,
+ renderTo: ct,
+ });
+ this.panel.ownerCt = this;
+ this.el = this.panel.getEl();
+
+ this.panel.add(this.toggle);
+ this.panel.add(this.input);
+ this.panel.doLayout();
+
+ this.toggle
+ .getEl()
+ .parent()
+ .setStyle('padding-right', '10px');
+ }
+ Ext.ux.form.ToggleField.superclass.onRender.call(this, ct, position);
+ },
+
+ // private
+ onResize: function(w, h) {
+ this.panel.setSize(w, h);
+ this.panel.doLayout();
+
+ // we substract 10 for the padding :-)
+ var inputWidth = w - this.toggle.getSize().width - 25;
+ this.input.setSize(inputWidth, h);
+ },
+
+ onToggleCheck: function(toggle, checked) {
+ this.input.setDisabled(!checked);
+ },
+});
+Ext.reg('togglefield', Ext.ux.form.ToggleField);
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+Ext.ns('Ext.ux.grid');
+
+/**
+ * @class Ext.ux.grid.BufferView
+ * @extends Ext.grid.GridView
+ * A custom GridView which renders rows on an as-needed basis.
+ */
+Ext.ux.grid.BufferView = Ext.extend(Ext.grid.GridView, {
+ /**
+ * @cfg {Number} rowHeight
+ * The height of a row in the grid.
+ */
+ rowHeight: 19,
+
+ /**
+ * @cfg {Number} borderHeight
+ * The combined height of border-top and border-bottom of a row.
+ */
+ borderHeight: 2,
+
+ /**
+ * @cfg {Boolean/Number} scrollDelay
+ * The number of milliseconds before rendering rows out of the visible
+ * viewing area. Defaults to 100. Rows will render immediately with a config
+ * of false.
+ */
+ scrollDelay: 100,
+
+ /**
+ * @cfg {Number} cacheSize
+ * The number of rows to look forward and backwards from the currently viewable
+ * area. The cache applies only to rows that have been rendered already.
+ */
+ cacheSize: 20,
+
+ /**
+ * @cfg {Number} cleanDelay
+ * The number of milliseconds to buffer cleaning of extra rows not in the
+ * cache.
+ */
+ cleanDelay: 500,
+
+ initTemplates: function() {
+ Ext.ux.grid.BufferView.superclass.initTemplates.call(this);
+ var ts = this.templates;
+ // empty div to act as a place holder for a row
+ ts.rowHolder = new Ext.Template(
+ '<div class="x-grid3-row {alt}" style="{tstyle}"></div>'
+ );
+ ts.rowHolder.disableFormats = true;
+ ts.rowHolder.compile();
+
+ ts.rowBody = new Ext.Template(
+ '<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
+ '<tbody><tr>{cells}</tr>',
+ this.enableRowBody
+ ? '<tr class="x-grid3-row-body-tr" style="{bodyStyle}"><td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on"><div class="x-grid3-row-body">{body}</div></td></tr>'
+ : '',
+ '</tbody></table>'
+ );
+ ts.rowBody.disableFormats = true;
+ ts.rowBody.compile();
+ },
+
+ getStyleRowHeight: function() {
+ return Ext.isBorderBox
+ ? this.rowHeight + this.borderHeight
+ : this.rowHeight;
+ },
+
+ getCalculatedRowHeight: function() {
+ return this.rowHeight + this.borderHeight;
+ },
+
+ getVisibleRowCount: function() {
+ var rh = this.getCalculatedRowHeight(),
+ visibleHeight = this.scroller.dom.clientHeight;
+ return visibleHeight < 1 ? 0 : Math.ceil(visibleHeight / rh);
+ },
+
+ getVisibleRows: function() {
+ var count = this.getVisibleRowCount(),
+ sc = this.scroller.dom.scrollTop,
+ start =
+ sc === 0
+ ? 0
+ : Math.floor(sc / this.getCalculatedRowHeight()) - 1;
+ return {
+ first: Math.max(start, 0),
+ last: Math.min(start + count + 2, this.ds.getCount() - 1),
+ };
+ },
+
+ doRender: function(cs, rs, ds, startRow, colCount, stripe, onlyBody) {
+ var ts = this.templates,
+ ct = ts.cell,
+ rt = ts.row,
+ rb = ts.rowBody,
+ last = colCount - 1,
+ rh = this.getStyleRowHeight(),
+ vr = this.getVisibleRows(),
+ tstyle = 'width:' + this.getTotalWidth() + ';height:' + rh + 'px;',
+ // buffers
+ buf = [],
+ cb,
+ c,
+ p = {},
+ rp = { tstyle: tstyle },
+ r;
+ for (var j = 0, len = rs.length; j < len; j++) {
+ r = rs[j];
+ cb = [];
+ var rowIndex = j + startRow,
+ visible = rowIndex >= vr.first && rowIndex <= vr.last;
+ if (visible) {
+ for (var i = 0; i < colCount; i++) {
+ c = cs[i];
+ p.id = c.id;
+ p.css =
+ i === 0
+ ? 'x-grid3-cell-first '
+ : i == last
+ ? 'x-grid3-cell-last '
+ : '';
+ p.attr = p.cellAttr = '';
+ p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
+ p.style = c.style;
+ if (p.value === undefined || p.value === '') {
+ p.value = '&#160;';
+ }
+ if (r.dirty && typeof r.modified[c.name] !== 'undefined') {
+ p.css += ' x-grid3-dirty-cell';
+ }
+ cb[cb.length] = ct.apply(p);
+ }
+ }
+ var alt = [];
+ if (stripe && (rowIndex + 1) % 2 === 0) {
+ alt[0] = 'x-grid3-row-alt';
+ }
+ if (r.dirty) {
+ alt[1] = ' x-grid3-dirty-row';
+ }
+ rp.cols = colCount;
+ if (this.getRowClass) {
+ alt[2] = this.getRowClass(r, rowIndex, rp, ds);
+ }
+ rp.alt = alt.join(' ');
+ rp.cells = cb.join('');
+ buf[buf.length] = !visible
+ ? ts.rowHolder.apply(rp)
+ : onlyBody
+ ? rb.apply(rp)
+ : rt.apply(rp);
+ }
+ return buf.join('');
+ },
+
+ isRowRendered: function(index) {
+ var row = this.getRow(index);
+ return row && row.childNodes.length > 0;
+ },
+
+ syncScroll: function() {
+ Ext.ux.grid.BufferView.superclass.syncScroll.apply(this, arguments);
+ this.update();
+ },
+
+ // a (optionally) buffered method to update contents of gridview
+ update: function() {
+ if (this.scrollDelay) {
+ if (!this.renderTask) {
+ this.renderTask = new Ext.util.DelayedTask(this.doUpdate, this);
+ }
+ this.renderTask.delay(this.scrollDelay);
+ } else {
+ this.doUpdate();
+ }
+ },
+
+ onRemove: function(ds, record, index, isUpdate) {
+ Ext.ux.grid.BufferView.superclass.onRemove.apply(this, arguments);
+ if (isUpdate !== true) {
+ this.update();
+ }
+ },
+
+ doUpdate: function() {
+ if (this.getVisibleRowCount() > 0) {
+ var g = this.grid,
+ cm = g.colModel,
+ ds = g.store,
+ cs = this.getColumnData(),
+ vr = this.getVisibleRows(),
+ row;
+ for (var i = vr.first; i <= vr.last; i++) {
+ // if row is NOT rendered and is visible, render it
+ if (!this.isRowRendered(i) && (row = this.getRow(i))) {
+ var html = this.doRender(
+ cs,
+ [ds.getAt(i)],
+ ds,
+ i,
+ cm.getColumnCount(),
+ g.stripeRows,
+ true
+ );
+ row.innerHTML = html;
+ }
+ }
+ this.clean();
+ }
+ },
+
+ // a buffered method to clean rows
+ clean: function() {
+ if (!this.cleanTask) {
+ this.cleanTask = new Ext.util.DelayedTask(this.doClean, this);
+ }
+ this.cleanTask.delay(this.cleanDelay);
+ },
+
+ doClean: function() {
+ if (this.getVisibleRowCount() > 0) {
+ var vr = this.getVisibleRows();
+ vr.first -= this.cacheSize;
+ vr.last += this.cacheSize;
+
+ var i = 0,
+ rows = this.getRows();
+ // if first is less than 0, all rows have been rendered
+ // so lets clean the end...
+ if (vr.first <= 0) {
+ i = vr.last + 1;
+ }
+ for (var len = this.ds.getCount(); i < len; i++) {
+ // if current row is outside of first and last and
+ // has content, update the innerHTML to nothing
+ if ((i < vr.first || i > vr.last) && rows[i].innerHTML) {
+ rows[i].innerHTML = '';
+ }
+ }
+ }
+ },
+
+ removeTask: function(name) {
+ var task = this[name];
+ if (task && task.cancel) {
+ task.cancel();
+ this[name] = null;
+ }
+ },
+
+ destroy: function() {
+ this.removeTask('cleanTask');
+ this.removeTask('renderTask');
+ Ext.ux.grid.BufferView.superclass.destroy.call(this);
+ },
+
+ layout: function() {
+ Ext.ux.grid.BufferView.superclass.layout.call(this);
+ this.update();
+ },
+});
+/**
+ * Ext.ux.layout.FormLayoutFix.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+// Taken from http://extjs.com/forum/showthread.php?t=75273
+// remove spaces for hidden elements and make show(), hide(), enable() and disable() act on
+// the label. don't use hideLabel with this.
+Ext.override(Ext.layout.FormLayout, {
+ renderItem: function(c, position, target) {
+ if (
+ c &&
+ !c.rendered &&
+ (c.isFormField || c.fieldLabel) &&
+ c.inputType != 'hidden'
+ ) {
+ var args = this.getTemplateArgs(c);
+ if (typeof position == 'number') {
+ position = target.dom.childNodes[position] || null;
+ }
+ if (position) {
+ c.formItem = this.fieldTpl.insertBefore(position, args, true);
+ } else {
+ c.formItem = this.fieldTpl.append(target, args, true);
+ }
+ c.actionMode = 'formItem';
+ c.render('x-form-el-' + c.id);
+ c.container = c.formItem;
+ c.actionMode = 'container';
+ } else {
+ Ext.layout.FormLayout.superclass.renderItem.apply(this, arguments);
+ }
+ },
+});
+/**
+ * Ext.ux.tree.MultiSelectionModelFix.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+/**
+ * This enhances the MSM to allow for shift selecting in tree grids etc.
+ * @author Damien Churchill <damoxc@gmail.com>
+ */
+Ext.override(Ext.tree.MultiSelectionModel, {
+ onNodeClick: function(node, e) {
+ if (e.ctrlKey && this.isSelected(node)) {
+ this.unselect(node);
+ } else if (e.shiftKey && !this.isSelected(node)) {
+ var parentNode = node.parentNode;
+ // We can only shift select files in the same node
+ if (this.lastSelNode.parentNode.id != parentNode.id) return;
+
+ // Get the node indexes
+ var fi = parentNode.indexOf(node),
+ li = parentNode.indexOf(this.lastSelNode);
+
+ // Select the last clicked node and wipe old selections
+ this.select(this.lastSelNode, e, false, true);
+
+ // Swap the values if required
+ if (fi > li) {
+ (fi = fi + li), (li = fi - li), (fi = fi - li);
+ }
+
+ // Select all the nodes
+ parentNode.eachChild(function(n) {
+ var i = parentNode.indexOf(n);
+ if (fi < i && i < li) {
+ this.select(n, e, true, true);
+ }
+ }, this);
+
+ // Select the clicked node
+ this.select(node, e, true);
+ } else {
+ this.select(node, e, e.ctrlKey);
+ }
+ },
+
+ select: function(node, e, keepExisting, suppressEvent) {
+ if (keepExisting !== true) {
+ this.clearSelections(true);
+ }
+ if (this.isSelected(node)) {
+ this.lastSelNode = node;
+ return node;
+ }
+ this.selNodes.push(node);
+ this.selMap[node.id] = node;
+ this.lastSelNode = node;
+ node.ui.onSelectedChange(true);
+ if (suppressEvent !== true) {
+ this.fireEvent('selectionchange', this, this.selNodes);
+ }
+ return node;
+ },
+});
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+Ext.ns('Ext.ux.tree');
+
+/**
+ * @class Ext.ux.tree.TreeGrid
+ * @extends Ext.tree.TreePanel
+ *
+ * @xtype treegrid
+ */
+Ext.ux.tree.TreeGrid = Ext.extend(Ext.tree.TreePanel, {
+ rootVisible: false,
+ useArrows: true,
+ lines: false,
+ borderWidth: Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell
+ cls: 'x-treegrid',
+
+ columnResize: true,
+ enableSort: true,
+ reserveScrollOffset: true,
+ enableHdMenu: true,
+
+ columnsText: 'Columns',
+
+ initComponent: function() {
+ if (!this.root) {
+ this.root = new Ext.tree.AsyncTreeNode({ text: 'Root' });
+ }
+
+ // initialize the loader
+ var l = this.loader;
+ if (!l) {
+ l = new Ext.ux.tree.TreeGridLoader({
+ dataUrl: this.dataUrl,
+ requestMethod: this.requestMethod,
+ store: this.store,
+ });
+ } else if (Ext.isObject(l) && !l.load) {
+ l = new Ext.ux.tree.TreeGridLoader(l);
+ }
+ this.loader = l;
+
+ Ext.ux.tree.TreeGrid.superclass.initComponent.call(this);
+
+ this.initColumns();
+
+ if (this.enableSort) {
+ this.treeGridSorter = new Ext.ux.tree.TreeGridSorter(
+ this,
+ this.enableSort
+ );
+ }
+
+ if (this.columnResize) {
+ this.colResizer = new Ext.tree.ColumnResizer(this.columnResize);
+ this.colResizer.init(this);
+ }
+
+ var c = this.columns;
+ if (!this.internalTpl) {
+ this.internalTpl = new Ext.XTemplate(
+ '<div class="x-grid3-header">',
+ '<div class="x-treegrid-header-inner">',
+ '<div class="x-grid3-header-offset">',
+ '<table style="table-layout: fixed;" cellspacing="0" cellpadding="0" border="0"><colgroup><tpl for="columns"><col /></tpl></colgroup>',
+ '<thead><tr class="x-grid3-hd-row">',
+ '<tpl for="columns">',
+ '<td class="x-grid3-hd x-grid3-cell x-treegrid-hd" style="text-align: {align};" id="',
+ this.id,
+ '-xlhd-{#}">',
+ '<div class="x-grid3-hd-inner x-treegrid-hd-inner" unselectable="on">',
+ this.enableHdMenu
+ ? '<a class="x-grid3-hd-btn" href="#"></a>'
+ : '',
+ '{header}<img class="x-grid3-sort-icon" src="',
+ Ext.BLANK_IMAGE_URL,
+ '" />',
+ '</div>',
+ '</td></tpl>',
+ '</tr></thead>',
+ '</table>',
+ '</div></div>',
+ '</div>',
+ '<div class="x-treegrid-root-node">',
+ '<table class="x-treegrid-root-table" cellpadding="0" cellspacing="0" style="table-layout: fixed;"></table>',
+ '</div>'
+ );
+ }
+
+ if (!this.colgroupTpl) {
+ this.colgroupTpl = new Ext.XTemplate(
+ '<colgroup><tpl for="columns"><col style="width: {width}px"/></tpl></colgroup>'
+ );
+ }
+ },
+
+ initColumns: function() {
+ var cs = this.columns,
+ len = cs.length,
+ columns = [],
+ i,
+ c;
+
+ for (i = 0; i < len; i++) {
+ c = cs[i];
+ if (!c.isColumn) {
+ c.xtype = c.xtype
+ ? /^tg/.test(c.xtype)
+ ? c.xtype
+ : 'tg' + c.xtype
+ : 'tgcolumn';
+ c = Ext.create(c);
+ }
+ c.init(this);
+ columns.push(c);
+
+ if (this.enableSort !== false && c.sortable !== false) {
+ c.sortable = true;
+ this.enableSort = true;
+ }
+ }
+
+ this.columns = columns;
+ },
+
+ onRender: function() {
+ Ext.tree.TreePanel.superclass.onRender.apply(this, arguments);
+
+ this.el.addClass('x-treegrid');
+
+ this.outerCt = this.body.createChild({
+ cls:
+ 'x-tree-root-ct x-treegrid-ct ' +
+ (this.useArrows
+ ? 'x-tree-arrows'
+ : this.lines
+ ? 'x-tree-lines'
+ : 'x-tree-no-lines'),
+ });
+
+ this.internalTpl.overwrite(this.outerCt, { columns: this.columns });
+
+ this.mainHd = Ext.get(this.outerCt.dom.firstChild);
+ this.innerHd = Ext.get(this.mainHd.dom.firstChild);
+ this.innerBody = Ext.get(this.outerCt.dom.lastChild);
+ this.innerCt = Ext.get(this.innerBody.dom.firstChild);
+
+ this.colgroupTpl.insertFirst(this.innerCt, { columns: this.columns });
+
+ if (this.hideHeaders) {
+ this.el.child('.x-grid3-header').setDisplayed('none');
+ } else if (this.enableHdMenu !== false) {
+ this.hmenu = new Ext.menu.Menu({ id: this.id + '-hctx' });
+ if (this.enableColumnHide !== false) {
+ this.colMenu = new Ext.menu.Menu({
+ id: this.id + '-hcols-menu',
+ });
+ this.colMenu.on({
+ scope: this,
+ beforeshow: this.beforeColMenuShow,
+ itemclick: this.handleHdMenuClick,
+ });
+ this.hmenu.add({
+ itemId: 'columns',
+ hideOnClick: false,
+ text: this.columnsText,
+ menu: this.colMenu,
+ iconCls: 'x-cols-icon',
+ });
+ }
+ this.hmenu.on('itemclick', this.handleHdMenuClick, this);
+ }
+ },
+
+ setRootNode: function(node) {
+ node.attributes.uiProvider = Ext.ux.tree.TreeGridRootNodeUI;
+ node = Ext.ux.tree.TreeGrid.superclass.setRootNode.call(this, node);
+ if (this.innerCt) {
+ this.colgroupTpl.insertFirst(this.innerCt, {
+ columns: this.columns,
+ });
+ }
+ return node;
+ },
+
+ clearInnerCt: function() {
+ if (Ext.isIE) {
+ var dom = this.innerCt.dom;
+ while (dom.firstChild) {
+ dom.removeChild(dom.firstChild);
+ }
+ } else {
+ Ext.ux.tree.TreeGrid.superclass.clearInnerCt.call(this);
+ }
+ },
+
+ initEvents: function() {
+ Ext.ux.tree.TreeGrid.superclass.initEvents.apply(this, arguments);
+
+ this.mon(this.innerBody, 'scroll', this.syncScroll, this);
+ this.mon(this.innerHd, 'click', this.handleHdDown, this);
+ this.mon(this.mainHd, {
+ scope: this,
+ mouseover: this.handleHdOver,
+ mouseout: this.handleHdOut,
+ });
+ },
+
+ onResize: function(w, h) {
+ Ext.ux.tree.TreeGrid.superclass.onResize.apply(this, arguments);
+
+ var bd = this.innerBody.dom;
+ var hd = this.innerHd.dom;
+
+ if (!bd) {
+ return;
+ }
+
+ if (Ext.isNumber(h)) {
+ bd.style.height =
+ this.body.getHeight(true) - hd.offsetHeight + 'px';
+ }
+
+ if (Ext.isNumber(w)) {
+ var sw = Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
+ if (
+ this.reserveScrollOffset ||
+ bd.offsetWidth - bd.clientWidth > 10
+ ) {
+ this.setScrollOffset(sw);
+ } else {
+ var me = this;
+ setTimeout(function() {
+ me.setScrollOffset(
+ bd.offsetWidth - bd.clientWidth > 10 ? sw : 0
+ );
+ }, 10);
+ }
+ }
+ },
+
+ updateColumnWidths: function() {
+ var cols = this.columns,
+ colCount = cols.length,
+ groups = this.outerCt.query('colgroup'),
+ groupCount = groups.length,
+ c,
+ g,
+ i,
+ j;
+
+ for (i = 0; i < colCount; i++) {
+ c = cols[i];
+ for (j = 0; j < groupCount; j++) {
+ g = groups[j];
+ g.childNodes[i].style.width = (c.hidden ? 0 : c.width) + 'px';
+ }
+ }
+
+ for (
+ i = 0, groups = this.innerHd.query('td'), len = groups.length;
+ i < len;
+ i++
+ ) {
+ c = Ext.fly(groups[i]);
+ if (cols[i] && cols[i].hidden) {
+ c.addClass('x-treegrid-hd-hidden');
+ } else {
+ c.removeClass('x-treegrid-hd-hidden');
+ }
+ }
+
+ var tcw = this.getTotalColumnWidth();
+ Ext.fly(this.innerHd.dom.firstChild).setWidth(
+ tcw + (this.scrollOffset || 0)
+ );
+ this.outerCt.select('table').setWidth(tcw);
+ this.syncHeaderScroll();
+ },
+
+ getVisibleColumns: function() {
+ var columns = [],
+ cs = this.columns,
+ len = cs.length,
+ i;
+
+ for (i = 0; i < len; i++) {
+ if (!cs[i].hidden) {
+ columns.push(cs[i]);
+ }
+ }
+ return columns;
+ },
+
+ getTotalColumnWidth: function() {
+ var total = 0;
+ for (
+ var i = 0, cs = this.getVisibleColumns(), len = cs.length;
+ i < len;
+ i++
+ ) {
+ total += cs[i].width;
+ }
+ return total;
+ },
+
+ setScrollOffset: function(scrollOffset) {
+ this.scrollOffset = scrollOffset;
+ this.updateColumnWidths();
+ },
+
+ // private
+ handleHdDown: function(e, t) {
+ var hd = e.getTarget('.x-treegrid-hd');
+
+ if (hd && Ext.fly(t).hasClass('x-grid3-hd-btn')) {
+ var ms = this.hmenu.items,
+ cs = this.columns,
+ index = this.findHeaderIndex(hd),
+ c = cs[index],
+ sort = c.sortable;
+
+ e.stopEvent();
+ Ext.fly(hd).addClass('x-grid3-hd-menu-open');
+ this.hdCtxIndex = index;
+
+ this.fireEvent('headerbuttonclick', ms, c, hd, index);
+
+ this.hmenu.on(
+ 'hide',
+ function() {
+ Ext.fly(hd).removeClass('x-grid3-hd-menu-open');
+ },
+ this,
+ { single: true }
+ );
+
+ this.hmenu.show(t, 'tl-bl?');
+ } else if (hd) {
+ var index = this.findHeaderIndex(hd);
+ this.fireEvent('headerclick', this.columns[index], hd, index);
+ }
+ },
+
+ // private
+ handleHdOver: function(e, t) {
+ var hd = e.getTarget('.x-treegrid-hd');
+ if (hd && !this.headersDisabled) {
+ index = this.findHeaderIndex(hd);
+ this.activeHdRef = t;
+ this.activeHdIndex = index;
+ var el = Ext.get(hd);
+ this.activeHdRegion = el.getRegion();
+ el.addClass('x-grid3-hd-over');
+ this.activeHdBtn = el.child('.x-grid3-hd-btn');
+ if (this.activeHdBtn) {
+ this.activeHdBtn.dom.style.height =
+ hd.firstChild.offsetHeight - 1 + 'px';
+ }
+ }
+ },
+
+ // private
+ handleHdOut: function(e, t) {
+ var hd = e.getTarget('.x-treegrid-hd');
+ if (hd && (!Ext.isIE || !e.within(hd, true))) {
+ this.activeHdRef = null;
+ Ext.fly(hd).removeClass('x-grid3-hd-over');
+ hd.style.cursor = '';
+ }
+ },
+
+ findHeaderIndex: function(hd) {
+ hd = hd.dom || hd;
+ var cs = hd.parentNode.childNodes;
+ for (var i = 0, c; (c = cs[i]); i++) {
+ if (c == hd) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ // private
+ beforeColMenuShow: function() {
+ var cols = this.columns,
+ colCount = cols.length,
+ i,
+ c;
+ this.colMenu.removeAll();
+ for (i = 1; i < colCount; i++) {
+ c = cols[i];
+ if (c.hideable !== false) {
+ this.colMenu.add(
+ new Ext.menu.CheckItem({
+ itemId: 'col-' + i,
+ text: c.header,
+ checked: !c.hidden,
+ hideOnClick: false,
+ disabled: c.hideable === false,
+ })
+ );
+ }
+ }
+ },
+
+ // private
+ handleHdMenuClick: function(item) {
+ var index = this.hdCtxIndex,
+ id = item.getItemId();
+
+ if (
+ this.fireEvent(
+ 'headermenuclick',
+ this.columns[index],
+ id,
+ index
+ ) !== false
+ ) {
+ index = id.substr(4);
+ if (index > 0 && this.columns[index]) {
+ this.setColumnVisible(index, !item.checked);
+ }
+ }
+
+ return true;
+ },
+
+ setColumnVisible: function(index, visible) {
+ this.columns[index].hidden = !visible;
+ this.updateColumnWidths();
+ },
+
+ /**
+ * Scrolls the grid to the top
+ */
+ scrollToTop: function() {
+ this.innerBody.dom.scrollTop = 0;
+ this.innerBody.dom.scrollLeft = 0;
+ },
+
+ // private
+ syncScroll: function() {
+ this.syncHeaderScroll();
+ var mb = this.innerBody.dom;
+ this.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop);
+ },
+
+ // private
+ syncHeaderScroll: function() {
+ var mb = this.innerBody.dom;
+ this.innerHd.dom.scrollLeft = mb.scrollLeft;
+ this.innerHd.dom.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
+ },
+
+ registerNode: function(n) {
+ Ext.ux.tree.TreeGrid.superclass.registerNode.call(this, n);
+ if (!n.uiProvider && !n.isRoot && !n.ui.isTreeGridNodeUI) {
+ n.ui = new Ext.ux.tree.TreeGridNodeUI(n);
+ }
+ },
+});
+
+Ext.reg('treegrid', Ext.ux.tree.TreeGrid);
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+/**
+ * @class Ext.tree.ColumnResizer
+ * @extends Ext.util.Observable
+ */
+Ext.tree.ColumnResizer = Ext.extend(Ext.util.Observable, {
+ /**
+ * @cfg {Number} minWidth The minimum width the column can be dragged to.
+ * Defaults to <tt>14</tt>.
+ */
+ minWidth: 14,
+
+ constructor: function(config) {
+ Ext.apply(this, config);
+ Ext.tree.ColumnResizer.superclass.constructor.call(this);
+ },
+
+ init: function(tree) {
+ this.tree = tree;
+ tree.on('render', this.initEvents, this);
+ },
+
+ initEvents: function(tree) {
+ tree.mon(tree.innerHd, 'mousemove', this.handleHdMove, this);
+ this.tracker = new Ext.dd.DragTracker({
+ onBeforeStart: this.onBeforeStart.createDelegate(this),
+ onStart: this.onStart.createDelegate(this),
+ onDrag: this.onDrag.createDelegate(this),
+ onEnd: this.onEnd.createDelegate(this),
+ tolerance: 3,
+ autoStart: 300,
+ });
+ this.tracker.initEl(tree.innerHd);
+ tree.on('beforedestroy', this.tracker.destroy, this.tracker);
+ },
+
+ handleHdMove: function(e, t) {
+ var hw = 5,
+ x = e.getPageX(),
+ hd = e.getTarget('.x-treegrid-hd', 3, true);
+
+ if (hd) {
+ var r = hd.getRegion(),
+ ss = hd.dom.style,
+ pn = hd.dom.parentNode;
+
+ if (x - r.left <= hw && hd.dom !== pn.firstChild) {
+ var ps = hd.dom.previousSibling;
+ while (ps && Ext.fly(ps).hasClass('x-treegrid-hd-hidden')) {
+ ps = ps.previousSibling;
+ }
+ if (ps) {
+ this.activeHd = Ext.get(ps);
+ ss.cursor = Ext.isWebKit ? 'e-resize' : 'col-resize';
+ }
+ } else if (r.right - x <= hw) {
+ var ns = hd.dom;
+ while (ns && Ext.fly(ns).hasClass('x-treegrid-hd-hidden')) {
+ ns = ns.previousSibling;
+ }
+ if (ns) {
+ this.activeHd = Ext.get(ns);
+ ss.cursor = Ext.isWebKit ? 'w-resize' : 'col-resize';
+ }
+ } else {
+ delete this.activeHd;
+ ss.cursor = '';
+ }
+ }
+ },
+
+ onBeforeStart: function(e) {
+ this.dragHd = this.activeHd;
+ return !!this.dragHd;
+ },
+
+ onStart: function(e) {
+ this.dragHeadersDisabled = this.tree.headersDisabled;
+ this.tree.headersDisabled = true;
+ this.proxy = this.tree.body.createChild({ cls: 'x-treegrid-resizer' });
+ this.proxy.setHeight(this.tree.body.getHeight());
+
+ var x = this.tracker.getXY()[0];
+
+ this.hdX = this.dragHd.getX();
+ this.hdIndex = this.tree.findHeaderIndex(this.dragHd);
+
+ this.proxy.setX(this.hdX);
+ this.proxy.setWidth(x - this.hdX);
+
+ this.maxWidth =
+ this.tree.outerCt.getWidth() -
+ this.tree.innerBody.translatePoints(this.hdX).left;
+ },
+
+ onDrag: function(e) {
+ var cursorX = this.tracker.getXY()[0];
+ this.proxy.setWidth(
+ (cursorX - this.hdX).constrain(this.minWidth, this.maxWidth)
+ );
+ },
+
+ onEnd: function(e) {
+ var nw = this.proxy.getWidth(),
+ tree = this.tree,
+ disabled = this.dragHeadersDisabled;
+
+ this.proxy.remove();
+ delete this.dragHd;
+
+ tree.columns[this.hdIndex].width = nw;
+ tree.updateColumnWidths();
+
+ setTimeout(function() {
+ tree.headersDisabled = disabled;
+ }, 100);
+ },
+});
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+(function() {
+ Ext.override(Ext.list.Column, {
+ init: function() {
+ var types = Ext.data.Types,
+ st = this.sortType;
+
+ if (this.type) {
+ if (Ext.isString(this.type)) {
+ this.type =
+ Ext.data.Types[this.type.toUpperCase()] || types.AUTO;
+ }
+ } else {
+ this.type = types.AUTO;
+ }
+
+ // named sortTypes are supported, here we look them up
+ if (Ext.isString(st)) {
+ this.sortType = Ext.data.SortTypes[st];
+ } else if (Ext.isEmpty(st)) {
+ this.sortType = this.type.sortType;
+ }
+ },
+ });
+
+ Ext.tree.Column = Ext.extend(Ext.list.Column, {});
+ Ext.tree.NumberColumn = Ext.extend(Ext.list.NumberColumn, {});
+ Ext.tree.DateColumn = Ext.extend(Ext.list.DateColumn, {});
+ Ext.tree.BooleanColumn = Ext.extend(Ext.list.BooleanColumn, {});
+
+ Ext.reg('tgcolumn', Ext.tree.Column);
+ Ext.reg('tgnumbercolumn', Ext.tree.NumberColumn);
+ Ext.reg('tgdatecolumn', Ext.tree.DateColumn);
+ Ext.reg('tgbooleancolumn', Ext.tree.BooleanColumn);
+})();
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+/**
+ * @class Ext.ux.tree.TreeGridLoader
+ * @extends Ext.tree.TreeLoader
+ */
+Ext.ux.tree.TreeGridLoader = Ext.extend(Ext.tree.TreeLoader, {
+ createNode: function(attr) {
+ if (!attr.uiProvider) {
+ attr.uiProvider = Ext.ux.tree.TreeGridNodeUI;
+ }
+ return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
+ },
+});
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+/**
+ * @class Ext.ux.tree.TreeGridNodeUI
+ * @extends Ext.tree.TreeNodeUI
+ */
+Ext.ux.tree.TreeGridNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
+ isTreeGridNodeUI: true,
+
+ renderElements: function(n, a, targetNode, bulkRender) {
+ var t = n.getOwnerTree(),
+ cols = t.columns,
+ c = cols[0],
+ i,
+ buf,
+ len;
+
+ this.indentMarkup = n.parentNode
+ ? n.parentNode.ui.getChildIndent()
+ : '';
+
+ buf = [
+ '<tbody class="x-tree-node">',
+ '<tr ext:tree-node-id="',
+ n.id,
+ '" class="x-tree-node-el x-tree-node-leaf ',
+ a.cls,
+ '">',
+ '<td class="x-treegrid-col">',
+ '<span class="x-tree-node-indent">',
+ this.indentMarkup,
+ '</span>',
+ '<img src="',
+ this.emptyIcon,
+ '" class="x-tree-ec-icon x-tree-elbow" />',
+ '<img src="',
+ a.icon || this.emptyIcon,
+ '" class="x-tree-node-icon',
+ a.icon ? ' x-tree-node-inline-icon' : '',
+ a.iconCls ? ' ' + a.iconCls : '',
+ '" unselectable="on" />',
+ '<a hidefocus="on" class="x-tree-node-anchor" href="',
+ a.href ? a.href : '#',
+ '" tabIndex="1" ',
+ a.hrefTarget ? ' target="' + a.hrefTarget + '"' : '',
+ '>',
+ '<span unselectable="on">',
+ c.tpl ? c.tpl.apply(a) : a[c.dataIndex] || c.text,
+ '</span></a>',
+ '</td>',
+ ];
+
+ for (i = 1, len = cols.length; i < len; i++) {
+ c = cols[i];
+ buf.push(
+ '<td class="x-treegrid-col ',
+ c.cls ? c.cls : '',
+ '">',
+ '<div unselectable="on" class="x-treegrid-text"',
+ c.align ? ' style="text-align: ' + c.align + ';"' : '',
+ '>',
+ c.tpl ? c.tpl.apply(a) : a[c.dataIndex],
+ '</div>',
+ '</td>'
+ );
+ }
+
+ buf.push(
+ '</tr><tr class="x-tree-node-ct"><td colspan="',
+ cols.length,
+ '">',
+ '<table class="x-treegrid-node-ct-table" cellpadding="0" cellspacing="0" style="table-layout: fixed; display: none; width: ',
+ t.innerCt.getWidth(),
+ 'px;"><colgroup>'
+ );
+ for (i = 0, len = cols.length; i < len; i++) {
+ buf.push(
+ '<col style="width: ',
+ cols[i].hidden ? 0 : cols[i].width,
+ 'px;" />'
+ );
+ }
+ buf.push('</colgroup></table></td></tr></tbody>');
+
+ if (bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()) {
+ this.wrap = Ext.DomHelper.insertHtml(
+ 'beforeBegin',
+ n.nextSibling.ui.getEl(),
+ buf.join('')
+ );
+ } else {
+ this.wrap = Ext.DomHelper.insertHtml(
+ 'beforeEnd',
+ targetNode,
+ buf.join('')
+ );
+ }
+
+ this.elNode = this.wrap.childNodes[0];
+ this.ctNode = this.wrap.childNodes[1].firstChild.firstChild;
+ var cs = this.elNode.firstChild.childNodes;
+ this.indentNode = cs[0];
+ this.ecNode = cs[1];
+ this.iconNode = cs[2];
+ this.anchor = cs[3];
+ this.textNode = cs[3].firstChild;
+ },
+
+ // private
+ animExpand: function(cb) {
+ this.ctNode.style.display = '';
+ Ext.ux.tree.TreeGridNodeUI.superclass.animExpand.call(this, cb);
+ },
+});
+
+Ext.ux.tree.TreeGridRootNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
+ isTreeGridNodeUI: true,
+
+ // private
+ render: function() {
+ if (!this.rendered) {
+ this.wrap = this.ctNode = this.node.ownerTree.innerCt.dom;
+ this.node.expanded = true;
+ }
+
+ if (Ext.isWebKit) {
+ // weird table-layout: fixed issue in webkit
+ var ct = this.ctNode;
+ ct.style.tableLayout = null;
+ (function() {
+ ct.style.tableLayout = 'fixed';
+ }.defer(1));
+ }
+ },
+
+ destroy: function() {
+ if (this.elNode) {
+ Ext.dd.Registry.unregister(this.elNode.id);
+ }
+ delete this.node;
+ },
+
+ collapse: Ext.emptyFn,
+ expand: Ext.emptyFn,
+});
+/**
+ * Ext.ux.tree.TreeGridNodeUIFix.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Ext.override(Ext.ux.tree.TreeGridNodeUI, {
+ updateColumns: function() {
+ if (!this.rendered) return;
+
+ var a = this.node.attributes,
+ t = this.node.getOwnerTree(),
+ cols = t.columns,
+ c = cols[0];
+
+ // Update the first column
+ this.anchor.firstChild.innerHTML = c.tpl
+ ? c.tpl.apply(a)
+ : a[c.dataIndex] || c.text;
+
+ // Update the remaining columns
+ for (i = 1, len = cols.length; i < len; i++) {
+ c = cols[i];
+ this.elNode.childNodes[i].firstChild.innerHTML = c.tpl
+ ? c.tpl.apply(a)
+ : a[c.dataIndex] || c.text;
+ }
+ },
+});
+Ext.tree.RenderColumn = Ext.extend(Ext.tree.Column, {
+ constructor: function(c) {
+ c.tpl = c.tpl || new Ext.XTemplate('{' + c.dataIndex + ':this.format}');
+ c.tpl.format = c.renderer;
+ c.tpl.col = this;
+ Ext.tree.RenderColumn.superclass.constructor.call(this, c);
+ },
+});
+Ext.reg('tgrendercolumn', Ext.tree.RenderColumn);
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+Ext.ns('Ext.ux.tree');
+
+/**
+ * @class Ext.ux.tree.TreeGridSorter
+ * @extends Ext.tree.TreeSorter
+ * Provides sorting of nodes in a {@link Ext.ux.tree.TreeGrid}. The TreeGridSorter automatically monitors events on the
+ * associated TreeGrid that might affect the tree's sort order (beforechildrenrendered, append, insert and textchange).
+ * Example usage:<br />
+ * <pre><code>
+ new Ext.ux.tree.TreeGridSorter(myTreeGrid, {
+ folderSort: true,
+ dir: "desc",
+ sortType: function(node) {
+ // sort by a custom, typed attribute:
+ return parseInt(node.id, 10);
+ }
+ });
+ </code></pre>
+ * @constructor
+ * @param {TreeGrid} tree
+ * @param {Object} config
+ */
+Ext.ux.tree.TreeGridSorter = Ext.extend(Ext.tree.TreeSorter, {
+ /**
+ * @cfg {Array} sortClasses The CSS classes applied to a header when it is sorted. (defaults to <tt>['sort-asc', 'sort-desc']</tt>)
+ */
+ sortClasses: ['sort-asc', 'sort-desc'],
+ /**
+ * @cfg {String} sortAscText The text displayed in the 'Sort Ascending' menu item (defaults to <tt>'Sort Ascending'</tt>)
+ */
+ sortAscText: 'Sort Ascending',
+ /**
+ * @cfg {String} sortDescText The text displayed in the 'Sort Descending' menu item (defaults to <tt>'Sort Descending'</tt>)
+ */
+ sortDescText: 'Sort Descending',
+
+ constructor: function(tree, config) {
+ if (!Ext.isObject(config)) {
+ config = {
+ property: tree.columns[0].dataIndex || 'text',
+ folderSort: true,
+ };
+ }
+
+ Ext.ux.tree.TreeGridSorter.superclass.constructor.apply(
+ this,
+ arguments
+ );
+
+ this.tree = tree;
+ tree.on('headerclick', this.onHeaderClick, this);
+ tree.ddAppendOnly = true;
+
+ var me = this;
+ this.defaultSortFn = function(n1, n2) {
+ var desc = me.dir && me.dir.toLowerCase() == 'desc',
+ prop = me.property || 'text',
+ sortType = me.sortType,
+ caseSensitive = me.caseSensitive === true,
+ leafAttr = me.leafAttr || 'leaf',
+ attr1 = n1.attributes,
+ attr2 = n2.attributes;
+
+ if (me.folderSort) {
+ if (attr1[leafAttr] && !attr2[leafAttr]) {
+ return 1;
+ }
+ if (!attr1[leafAttr] && attr2[leafAttr]) {
+ return -1;
+ }
+ }
+ var prop1 = attr1[prop],
+ prop2 = attr2[prop],
+ v1 = sortType
+ ? sortType(prop1)
+ : caseSensitive
+ ? prop1
+ : prop1.toUpperCase();
+ v2 = sortType
+ ? sortType(prop2)
+ : caseSensitive
+ ? prop2
+ : prop2.toUpperCase();
+
+ if (v1 < v2) {
+ return desc ? +1 : -1;
+ } else if (v1 > v2) {
+ return desc ? -1 : +1;
+ } else {
+ return 0;
+ }
+ };
+
+ tree.on('afterrender', this.onAfterTreeRender, this, { single: true });
+ tree.on('headermenuclick', this.onHeaderMenuClick, this);
+ },
+
+ onAfterTreeRender: function() {
+ if (this.tree.hmenu) {
+ this.tree.hmenu.insert(
+ 0,
+ {
+ itemId: 'asc',
+ text: this.sortAscText,
+ cls: 'xg-hmenu-sort-asc',
+ },
+ {
+ itemId: 'desc',
+ text: this.sortDescText,
+ cls: 'xg-hmenu-sort-desc',
+ }
+ );
+ }
+ this.updateSortIcon(0, 'asc');
+ },
+
+ onHeaderMenuClick: function(c, id, index) {
+ if (id === 'asc' || id === 'desc') {
+ this.onHeaderClick(c, null, index);
+ return false;
+ }
+ },
+
+ onHeaderClick: function(c, el, i) {
+ if (c && !this.tree.headersDisabled) {
+ var me = this;
+
+ me.property = c.dataIndex;
+ me.dir = c.dir = c.dir === 'desc' ? 'asc' : 'desc';
+ me.sortType = c.sortType;
+ me.caseSensitive === Ext.isBoolean(c.caseSensitive)
+ ? c.caseSensitive
+ : this.caseSensitive;
+ me.sortFn = c.sortFn || this.defaultSortFn;
+
+ this.tree.root.cascade(function(n) {
+ if (!n.isLeaf()) {
+ me.updateSort(me.tree, n);
+ }
+ });
+
+ this.updateSortIcon(i, c.dir);
+ }
+ },
+
+ // private
+ updateSortIcon: function(col, dir) {
+ var sc = this.sortClasses,
+ hds = this.tree.innerHd.select('td').removeClass(sc);
+ hds.item(col).addClass(sc[dir == 'desc' ? 1 : 0]);
+ },
+});
+Ext.ux.JSLoader = function(options) {
+ Ext.ux.JSLoader.scripts[++Ext.ux.JSLoader.index] = {
+ url: options.url,
+ success: true,
+ jsLoadObj: null,
+ options: options,
+ onLoad: options.onLoad || Ext.emptyFn,
+ onError: options.onError || Ext.ux.JSLoader.stdError,
+ scope: options.scope || this,
+ };
+
+ Ext.Ajax.request({
+ url: options.url,
+ scriptIndex: Ext.ux.JSLoader.index,
+ success: function(response, options) {
+ var script = Ext.ux.JSLoader.scripts[options.scriptIndex];
+ try {
+ eval(response.responseText);
+ } catch (e) {
+ script.success = false;
+ script.onError(script.options, e);
+ }
+ if (script.success) {
+ script.onLoad.call(script.scope, script.options);
+ }
+ },
+ failure: function(response, options) {
+ var script = Ext.ux.JSLoader.scripts[options.scriptIndex];
+ script.success = false;
+ script.onError(script.options, response.status);
+ },
+ });
+};
+Ext.ux.JSLoader.index = 0;
+Ext.ux.JSLoader.scripts = [];
+Ext.ux.JSLoader.stdError = function(options, e) {
+ window.alert(
+ 'Error loading script:\n\n' + options.url + '\n\nstatus: ' + e
+ );
+};
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+/**
+ * @class Ext.ux.Spinner
+ * @extends Ext.util.Observable
+ * Creates a Spinner control utilized by Ext.ux.form.SpinnerField
+ */
+Ext.ux.Spinner = Ext.extend(Ext.util.Observable, {
+ incrementValue: 1,
+ alternateIncrementValue: 5,
+ triggerClass: 'x-form-spinner-trigger',
+ splitterClass: 'x-form-spinner-splitter',
+ alternateKey: Ext.EventObject.shiftKey,
+ defaultValue: 0,
+ accelerate: false,
+
+ constructor: function(config) {
+ Ext.ux.Spinner.superclass.constructor.call(this, config);
+ Ext.apply(this, config);
+ this.mimicing = false;
+ },
+
+ init: function(field) {
+ this.field = field;
+
+ field.afterMethod('onRender', this.doRender, this);
+ field.afterMethod('onEnable', this.doEnable, this);
+ field.afterMethod('onDisable', this.doDisable, this);
+ field.afterMethod('afterRender', this.doAfterRender, this);
+ field.afterMethod('onResize', this.doResize, this);
+ field.afterMethod('onFocus', this.doFocus, this);
+ field.beforeMethod('onDestroy', this.doDestroy, this);
+ },
+
+ doRender: function(ct, position) {
+ var el = (this.el = this.field.getEl());
+ var f = this.field;
+
+ if (!f.wrap) {
+ f.wrap = this.wrap = el.wrap({
+ cls: 'x-form-field-wrap',
+ });
+ } else {
+ this.wrap = f.wrap.addClass('x-form-field-wrap');
+ }
+
+ this.trigger = this.wrap.createChild({
+ tag: 'img',
+ src: Ext.BLANK_IMAGE_URL,
+ cls: 'x-form-trigger ' + this.triggerClass,
+ });
+
+ if (!f.width) {
+ this.wrap.setWidth(el.getWidth() + this.trigger.getWidth());
+ }
+
+ this.splitter = this.wrap.createChild({
+ tag: 'div',
+ cls: this.splitterClass,
+ style: 'width:13px; height:2px;',
+ });
+ this.splitter
+ .setRight(Ext.isIE ? 1 : 2)
+ .setTop(10)
+ .show();
+
+ this.proxy = this.trigger.createProxy('', this.splitter, true);
+ this.proxy.addClass('x-form-spinner-proxy');
+ this.proxy.setStyle('left', '0px');
+ this.proxy.setSize(14, 1);
+ this.proxy.hide();
+ this.dd = new Ext.dd.DDProxy(this.splitter.dom.id, 'SpinnerDrag', {
+ dragElId: this.proxy.id,
+ });
+
+ this.initTrigger();
+ this.initSpinner();
+ },
+
+ doAfterRender: function() {
+ var y;
+ if (Ext.isIE && this.el.getY() != (y = this.trigger.getY())) {
+ this.el.position();
+ this.el.setY(y);
+ }
+ },
+
+ doEnable: function() {
+ if (this.wrap) {
+ this.disabled = false;
+ this.wrap.removeClass(this.field.disabledClass);
+ }
+ },
+
+ doDisable: function() {
+ if (this.wrap) {
+ this.disabled = true;
+ this.wrap.addClass(this.field.disabledClass);
+ this.el.removeClass(this.field.disabledClass);
+ }
+ },
+
+ doResize: function(w, h) {
+ if (typeof w == 'number') {
+ this.el.setWidth(w - this.trigger.getWidth());
+ }
+ this.wrap.setWidth(this.el.getWidth() + this.trigger.getWidth());
+ },
+
+ doFocus: function() {
+ if (!this.mimicing) {
+ this.wrap.addClass('x-trigger-wrap-focus');
+ this.mimicing = true;
+ Ext.get(Ext.isIE ? document.body : document).on(
+ 'mousedown',
+ this.mimicBlur,
+ this,
+ {
+ delay: 10,
+ }
+ );
+ this.el.on('keydown', this.checkTab, this);
+ }
+ },
+
+ // private
+ checkTab: function(e) {
+ if (e.getKey() == e.TAB) {
+ this.triggerBlur();
+ }
+ },
+
+ // private
+ mimicBlur: function(e) {
+ if (!this.wrap.contains(e.target) && this.field.validateBlur(e)) {
+ this.triggerBlur();
+ }
+ },
+
+ // private
+ triggerBlur: function() {
+ this.mimicing = false;
+ Ext.get(Ext.isIE ? document.body : document).un(
+ 'mousedown',
+ this.mimicBlur,
+ this
+ );
+ this.el.un('keydown', this.checkTab, this);
+ this.field.beforeBlur();
+ this.wrap.removeClass('x-trigger-wrap-focus');
+ this.field.onBlur.call(this.field);
+ },
+
+ initTrigger: function() {
+ this.trigger.addClassOnOver('x-form-trigger-over');
+ this.trigger.addClassOnClick('x-form-trigger-click');
+ },
+
+ initSpinner: function() {
+ this.field.addEvents({
+ spin: true,
+ spinup: true,
+ spindown: true,
+ });
+
+ this.keyNav = new Ext.KeyNav(this.el, {
+ up: function(e) {
+ e.preventDefault();
+ this.onSpinUp();
+ },
+
+ down: function(e) {
+ e.preventDefault();
+ this.onSpinDown();
+ },
+
+ pageUp: function(e) {
+ e.preventDefault();
+ this.onSpinUpAlternate();
+ },
+
+ pageDown: function(e) {
+ e.preventDefault();
+ this.onSpinDownAlternate();
+ },
+
+ scope: this,
+ });
+
+ this.repeater = new Ext.util.ClickRepeater(this.trigger, {
+ accelerate: this.accelerate,
+ });
+ this.field.mon(this.repeater, 'click', this.onTriggerClick, this, {
+ preventDefault: true,
+ });
+
+ this.field.mon(this.trigger, {
+ mouseover: this.onMouseOver,
+ mouseout: this.onMouseOut,
+ mousemove: this.onMouseMove,
+ mousedown: this.onMouseDown,
+ mouseup: this.onMouseUp,
+ scope: this,
+ preventDefault: true,
+ });
+
+ this.field.mon(this.wrap, 'mousewheel', this.handleMouseWheel, this);
+
+ this.dd.setXConstraint(0, 0, 10);
+ this.dd.setYConstraint(1500, 1500, 10);
+ this.dd.endDrag = this.endDrag.createDelegate(this);
+ this.dd.startDrag = this.startDrag.createDelegate(this);
+ this.dd.onDrag = this.onDrag.createDelegate(this);
+ },
+
+ onMouseOver: function() {
+ if (this.disabled) {
+ return;
+ }
+ var middle = this.getMiddle();
+ this.tmpHoverClass =
+ Ext.EventObject.getPageY() < middle
+ ? 'x-form-spinner-overup'
+ : 'x-form-spinner-overdown';
+ this.trigger.addClass(this.tmpHoverClass);
+ },
+
+ //private
+ onMouseOut: function() {
+ this.trigger.removeClass(this.tmpHoverClass);
+ },
+
+ //private
+ onMouseMove: function() {
+ if (this.disabled) {
+ return;
+ }
+ var middle = this.getMiddle();
+ if (
+ (Ext.EventObject.getPageY() > middle &&
+ this.tmpHoverClass == 'x-form-spinner-overup') ||
+ (Ext.EventObject.getPageY() < middle &&
+ this.tmpHoverClass == 'x-form-spinner-overdown')
+ ) {
+ }
+ },
+
+ //private
+ onMouseDown: function() {
+ if (this.disabled) {
+ return;
+ }
+ var middle = this.getMiddle();
+ this.tmpClickClass =
+ Ext.EventObject.getPageY() < middle
+ ? 'x-form-spinner-clickup'
+ : 'x-form-spinner-clickdown';
+ this.trigger.addClass(this.tmpClickClass);
+ },
+
+ //private
+ onMouseUp: function() {
+ this.trigger.removeClass(this.tmpClickClass);
+ },
+
+ //private
+ onTriggerClick: function() {
+ if (this.disabled || this.el.dom.readOnly) {
+ return;
+ }
+ var middle = this.getMiddle();
+ var ud = Ext.EventObject.getPageY() < middle ? 'Up' : 'Down';
+ this['onSpin' + ud]();
+ },
+
+ //private
+ getMiddle: function() {
+ var t = this.trigger.getTop();
+ var h = this.trigger.getHeight();
+ var middle = t + h / 2;
+ return middle;
+ },
+
+ //private
+ //checks if control is allowed to spin
+ isSpinnable: function() {
+ if (this.disabled || this.el.dom.readOnly) {
+ Ext.EventObject.preventDefault(); //prevent scrolling when disabled/readonly
+ return false;
+ }
+ return true;
+ },
+
+ handleMouseWheel: function(e) {
+ //disable scrolling when not focused
+ if (this.wrap.hasClass('x-trigger-wrap-focus') == false) {
+ return;
+ }
+
+ var delta = e.getWheelDelta();
+ if (delta > 0) {
+ this.onSpinUp();
+ e.stopEvent();
+ } else if (delta < 0) {
+ this.onSpinDown();
+ e.stopEvent();
+ }
+ },
+
+ //private
+ startDrag: function() {
+ this.proxy.show();
+ this._previousY = Ext.fly(this.dd.getDragEl()).getTop();
+ },
+
+ //private
+ endDrag: function() {
+ this.proxy.hide();
+ },
+
+ //private
+ onDrag: function() {
+ if (this.disabled) {
+ return;
+ }
+ var y = Ext.fly(this.dd.getDragEl()).getTop();
+ var ud = '';
+
+ if (this._previousY > y) {
+ ud = 'Up';
+ } //up
+ if (this._previousY < y) {
+ ud = 'Down';
+ } //down
+ if (ud != '') {
+ this['onSpin' + ud]();
+ }
+
+ this._previousY = y;
+ },
+
+ //private
+ onSpinUp: function() {
+ if (this.isSpinnable() == false) {
+ return;
+ }
+ if (Ext.EventObject.shiftKey == true) {
+ this.onSpinUpAlternate();
+ return;
+ } else {
+ this.spin(false, false);
+ }
+ this.field.fireEvent('spin', this);
+ this.field.fireEvent('spinup', this);
+ },
+
+ //private
+ onSpinDown: function() {
+ if (this.isSpinnable() == false) {
+ return;
+ }
+ if (Ext.EventObject.shiftKey == true) {
+ this.onSpinDownAlternate();
+ return;
+ } else {
+ this.spin(true, false);
+ }
+ this.field.fireEvent('spin', this);
+ this.field.fireEvent('spindown', this);
+ },
+
+ //private
+ onSpinUpAlternate: function() {
+ if (this.isSpinnable() == false) {
+ return;
+ }
+ this.spin(false, true);
+ this.field.fireEvent('spin', this);
+ this.field.fireEvent('spinup', this);
+ },
+
+ //private
+ onSpinDownAlternate: function() {
+ if (this.isSpinnable() == false) {
+ return;
+ }
+ this.spin(true, true);
+ this.field.fireEvent('spin', this);
+ this.field.fireEvent('spindown', this);
+ },
+
+ spin: function(down, alternate) {
+ var v = parseFloat(this.field.getValue());
+ var incr =
+ alternate == true
+ ? this.alternateIncrementValue
+ : this.incrementValue;
+ down == true ? (v -= incr) : (v += incr);
+
+ v = isNaN(v) ? this.defaultValue : v;
+ v = this.fixBoundries(v);
+ this.field.setRawValue(v);
+ },
+
+ fixBoundries: function(value) {
+ var v = value;
+
+ if (this.field.minValue != undefined && v < this.field.minValue) {
+ v = this.field.minValue;
+ }
+ if (this.field.maxValue != undefined && v > this.field.maxValue) {
+ v = this.field.maxValue;
+ }
+
+ return this.fixPrecision(v);
+ },
+
+ // private
+ fixPrecision: function(value) {
+ var nan = isNaN(value);
+ if (
+ !this.field.allowDecimals ||
+ this.field.decimalPrecision == -1 ||
+ nan ||
+ !value
+ ) {
+ return nan ? '' : value;
+ }
+ return parseFloat(
+ parseFloat(value).toFixed(this.field.decimalPrecision)
+ );
+ },
+
+ doDestroy: function() {
+ if (this.trigger) {
+ this.trigger.remove();
+ }
+ if (this.wrap) {
+ this.wrap.remove();
+ delete this.field.wrap;
+ }
+
+ if (this.splitter) {
+ this.splitter.remove();
+ }
+
+ if (this.dd) {
+ this.dd.unreg();
+ this.dd = null;
+ }
+
+ if (this.proxy) {
+ this.proxy.remove();
+ }
+
+ if (this.repeater) {
+ this.repeater.purgeListeners();
+ }
+ if (this.mimicing) {
+ Ext.get(Ext.isIE ? document.body : document).un(
+ 'mousedown',
+ this.mimicBlur,
+ this
+ );
+ }
+ },
+});
+
+//backwards compat
+Ext.form.Spinner = Ext.ux.Spinner;
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+/**
+ * @class Ext.ux.StatusBar
+ * <p>Basic status bar component that can be used as the bottom toolbar of any {@link Ext.Panel}. In addition to
+ * supporting the standard {@link Ext.Toolbar} interface for adding buttons, menus and other items, the StatusBar
+ * provides a greedy status element that can be aligned to either side and has convenient methods for setting the
+ * status text and icon. You can also indicate that something is processing using the {@link #showBusy} method.</p>
+ * <pre><code>
+new Ext.Panel({
+ title: 'StatusBar',
+ // etc.
+ bbar: new Ext.ux.StatusBar({
+ id: 'my-status',
+
+ // defaults to use when the status is cleared:
+ defaultText: 'Default status text',
+ defaultIconCls: 'default-icon',
+
+ // values to set initially:
+ text: 'Ready',
+ iconCls: 'ready-icon',
+
+ // any standard Toolbar items:
+ items: [{
+ text: 'A Button'
+ }, '-', 'Plain Text']
+ })
+});
+
+// Update the status bar later in code:
+var sb = Ext.getCmp('my-status');
+sb.setStatus({
+ text: 'OK',
+ iconCls: 'ok-icon',
+ clear: true // auto-clear after a set interval
+});
+
+// Set the status bar to show that something is processing:
+sb.showBusy();
+
+// processing....
+
+sb.clearStatus(); // once completeed
+</code></pre>
+ * @extends Ext.Toolbar
+ * @constructor
+ * Creates a new StatusBar
+ * @param {Object/Array} config A config object
+ */
+Ext.ux.StatusBar = Ext.extend(Ext.Toolbar, {
+ /**
+ * @cfg {String} statusAlign
+ * The alignment of the status element within the overall StatusBar layout. When the StatusBar is rendered,
+ * it creates an internal div containing the status text and icon. Any additional Toolbar items added in the
+ * StatusBar's {@link #items} config, or added via {@link #add} or any of the supported add* methods, will be
+ * rendered, in added order, to the opposite side. The status element is greedy, so it will automatically
+ * expand to take up all sapce left over by any other items. Example usage:
+ * <pre><code>
+// Create a left-aligned status bar containing a button,
+// separator and text item that will be right-aligned (default):
+new Ext.Panel({
+ title: 'StatusBar',
+ // etc.
+ bbar: new Ext.ux.StatusBar({
+ defaultText: 'Default status text',
+ id: 'status-id',
+ items: [{
+ text: 'A Button'
+ }, '-', 'Plain Text']
+ })
+});
+
+// By adding the statusAlign config, this will create the
+// exact same toolbar, except the status and toolbar item
+// layout will be reversed from the previous example:
+new Ext.Panel({
+ title: 'StatusBar',
+ // etc.
+ bbar: new Ext.ux.StatusBar({
+ defaultText: 'Default status text',
+ id: 'status-id',
+ statusAlign: 'right',
+ items: [{
+ text: 'A Button'
+ }, '-', 'Plain Text']
+ })
+});
+</code></pre>
+ */
+ /**
+ * @cfg {String} defaultText
+ * The default {@link #text} value. This will be used anytime the status bar is cleared with the
+ * <tt>useDefaults:true</tt> option (defaults to '').
+ */
+ /**
+ * @cfg {String} defaultIconCls
+ * The default {@link #iconCls} value (see the iconCls docs for additional details about customizing the icon).
+ * This will be used anytime the status bar is cleared with the <tt>useDefaults:true</tt> option (defaults to '').
+ */
+ /**
+ * @cfg {String} text
+ * A string that will be <b>initially</b> set as the status message. This string
+ * will be set as innerHTML (html tags are accepted) for the toolbar item.
+ * If not specified, the value set for <code>{@link #defaultText}</code>
+ * will be used.
+ */
+ /**
+ * @cfg {String} iconCls
+ * A CSS class that will be <b>initially</b> set as the status bar icon and is
+ * expected to provide a background image (defaults to '').
+ * Example usage:<pre><code>
+// Example CSS rule:
+.x-statusbar .x-status-custom {
+ padding-left: 25px;
+ background: transparent url(images/custom-icon.gif) no-repeat 3px 2px;
+}
+
+// Setting a default icon:
+var sb = new Ext.ux.StatusBar({
+ defaultIconCls: 'x-status-custom'
+});
+
+// Changing the icon:
+sb.setStatus({
+ text: 'New status',
+ iconCls: 'x-status-custom'
+});
+</code></pre>
+ */
+
+ /**
+ * @cfg {String} cls
+ * The base class applied to the containing element for this component on render (defaults to 'x-statusbar')
+ */
+ cls: 'x-statusbar',
+ /**
+ * @cfg {String} busyIconCls
+ * The default <code>{@link #iconCls}</code> applied when calling
+ * <code>{@link #showBusy}</code> (defaults to <tt>'x-status-busy'</tt>).
+ * It can be overridden at any time by passing the <code>iconCls</code>
+ * argument into <code>{@link #showBusy}</code>.
+ */
+ busyIconCls: 'x-status-busy',
+ /**
+ * @cfg {String} busyText
+ * The default <code>{@link #text}</code> applied when calling
+ * <code>{@link #showBusy}</code> (defaults to <tt>'Loading...'</tt>).
+ * It can be overridden at any time by passing the <code>text</code>
+ * argument into <code>{@link #showBusy}</code>.
+ */
+ busyText: 'Loading...',
+ /**
+ * @cfg {Number} autoClear
+ * The number of milliseconds to wait after setting the status via
+ * <code>{@link #setStatus}</code> before automatically clearing the status
+ * text and icon (defaults to <tt>5000</tt>). Note that this only applies
+ * when passing the <tt>clear</tt> argument to <code>{@link #setStatus}</code>
+ * since that is the only way to defer clearing the status. This can
+ * be overridden by specifying a different <tt>wait</tt> value in
+ * <code>{@link #setStatus}</code>. Calls to <code>{@link #clearStatus}</code>
+ * always clear the status bar immediately and ignore this value.
+ */
+ autoClear: 5000,
+
+ /**
+ * @cfg {String} emptyText
+ * The text string to use if no text has been set. Defaults to
+ * <tt>'&nbsp;'</tt>). If there are no other items in the toolbar using
+ * an empty string (<tt>''</tt>) for this value would end up in the toolbar
+ * height collapsing since the empty string will not maintain the toolbar
+ * height. Use <tt>''</tt> if the toolbar should collapse in height
+ * vertically when no text is specified and there are no other items in
+ * the toolbar.
+ */
+ emptyText: '&nbsp;',
+
+ // private
+ activeThreadId: 0,
+
+ // private
+ initComponent: function() {
+ if (this.statusAlign == 'right') {
+ this.cls += ' x-status-right';
+ }
+ Ext.ux.StatusBar.superclass.initComponent.call(this);
+ },
+
+ // private
+ afterRender: function() {
+ Ext.ux.StatusBar.superclass.afterRender.call(this);
+
+ var right = this.statusAlign == 'right';
+ this.currIconCls = this.iconCls || this.defaultIconCls;
+ this.statusEl = new Ext.Toolbar.TextItem({
+ cls: 'x-status-text ' + (this.currIconCls || ''),
+ text: this.text || this.defaultText || '',
+ });
+
+ if (right) {
+ this.add('->');
+ this.add(this.statusEl);
+ } else {
+ this.insert(0, this.statusEl);
+ this.insert(1, '->');
+ }
+ this.doLayout();
+ },
+
+ /**
+ * Sets the status {@link #text} and/or {@link #iconCls}. Also supports automatically clearing the
+ * status that was set after a specified interval.
+ * @param {Object/String} config A config object specifying what status to set, or a string assumed
+ * to be the status text (and all other options are defaulted as explained below). A config
+ * object containing any or all of the following properties can be passed:<ul>
+ * <li><tt>text</tt> {String} : (optional) The status text to display. If not specified, any current
+ * status text will remain unchanged.</li>
+ * <li><tt>iconCls</tt> {String} : (optional) The CSS class used to customize the status icon (see
+ * {@link #iconCls} for details). If not specified, any current iconCls will remain unchanged.</li>
+ * <li><tt>clear</tt> {Boolean/Number/Object} : (optional) Allows you to set an internal callback that will
+ * automatically clear the status text and iconCls after a specified amount of time has passed. If clear is not
+ * specified, the new status will not be auto-cleared and will stay until updated again or cleared using
+ * {@link #clearStatus}. If <tt>true</tt> is passed, the status will be cleared using {@link #autoClear},
+ * {@link #defaultText} and {@link #defaultIconCls} via a fade out animation. If a numeric value is passed,
+ * it will be used as the callback interval (in milliseconds), overriding the {@link #autoClear} value.
+ * All other options will be defaulted as with the boolean option. To customize any other options,
+ * you can pass an object in the format:<ul>
+ * <li><tt>wait</tt> {Number} : (optional) The number of milliseconds to wait before clearing
+ * (defaults to {@link #autoClear}).</li>
+ * <li><tt>anim</tt> {Number} : (optional) False to clear the status immediately once the callback
+ * executes (defaults to true which fades the status out).</li>
+ * <li><tt>useDefaults</tt> {Number} : (optional) False to completely clear the status text and iconCls
+ * (defaults to true which uses {@link #defaultText} and {@link #defaultIconCls}).</li>
+ * </ul></li></ul>
+ * Example usage:<pre><code>
+// Simple call to update the text
+statusBar.setStatus('New status');
+
+// Set the status and icon, auto-clearing with default options:
+statusBar.setStatus({
+ text: 'New status',
+ iconCls: 'x-status-custom',
+ clear: true
+});
+
+// Auto-clear with custom options:
+statusBar.setStatus({
+ text: 'New status',
+ iconCls: 'x-status-custom',
+ clear: {
+ wait: 8000,
+ anim: false,
+ useDefaults: false
+ }
+});
+</code></pre>
+ * @return {Ext.ux.StatusBar} this
+ */
+ setStatus: function(o) {
+ o = o || {};
+
+ if (typeof o == 'string') {
+ o = { text: o };
+ }
+ if (o.text !== undefined) {
+ this.setText(o.text);
+ }
+ if (o.iconCls !== undefined) {
+ this.setIcon(o.iconCls);
+ }
+
+ if (o.clear) {
+ var c = o.clear,
+ wait = this.autoClear,
+ defaults = { useDefaults: true, anim: true };
+
+ if (typeof c == 'object') {
+ c = Ext.applyIf(c, defaults);
+ if (c.wait) {
+ wait = c.wait;
+ }
+ } else if (typeof c == 'number') {
+ wait = c;
+ c = defaults;
+ } else if (typeof c == 'boolean') {
+ c = defaults;
+ }
+
+ c.threadId = this.activeThreadId;
+ this.clearStatus.defer(wait, this, [c]);
+ }
+ return this;
+ },
+
+ /**
+ * Clears the status {@link #text} and {@link #iconCls}. Also supports clearing via an optional fade out animation.
+ * @param {Object} config (optional) A config object containing any or all of the following properties. If this
+ * object is not specified the status will be cleared using the defaults below:<ul>
+ * <li><tt>anim</tt> {Boolean} : (optional) True to clear the status by fading out the status element (defaults
+ * to false which clears immediately).</li>
+ * <li><tt>useDefaults</tt> {Boolean} : (optional) True to reset the text and icon using {@link #defaultText} and
+ * {@link #defaultIconCls} (defaults to false which sets the text to '' and removes any existing icon class).</li>
+ * </ul>
+ * @return {Ext.ux.StatusBar} this
+ */
+ clearStatus: function(o) {
+ o = o || {};
+
+ if (o.threadId && o.threadId !== this.activeThreadId) {
+ // this means the current call was made internally, but a newer
+ // thread has set a message since this call was deferred. Since
+ // we don't want to overwrite a newer message just ignore.
+ return this;
+ }
+
+ var text = o.useDefaults ? this.defaultText : this.emptyText,
+ iconCls = o.useDefaults
+ ? this.defaultIconCls
+ ? this.defaultIconCls
+ : ''
+ : '';
+
+ if (o.anim) {
+ // animate the statusEl Ext.Element
+ this.statusEl.el.fadeOut({
+ remove: false,
+ useDisplay: true,
+ scope: this,
+ callback: function() {
+ this.setStatus({
+ text: text,
+ iconCls: iconCls,
+ });
+
+ this.statusEl.el.show();
+ },
+ });
+ } else {
+ // hide/show the el to avoid jumpy text or icon
+ this.statusEl.hide();
+ this.setStatus({
+ text: text,
+ iconCls: iconCls,
+ });
+ this.statusEl.show();
+ }
+ return this;
+ },
+
+ /**
+ * Convenience method for setting the status text directly. For more flexible options see {@link #setStatus}.
+ * @param {String} text (optional) The text to set (defaults to '')
+ * @return {Ext.ux.StatusBar} this
+ */
+ setText: function(text) {
+ this.activeThreadId++;
+ this.text = text || '';
+ if (this.rendered) {
+ this.statusEl.setText(this.text);
+ }
+ return this;
+ },
+
+ /**
+ * Returns the current status text.
+ * @return {String} The status text
+ */
+ getText: function() {
+ return this.text;
+ },
+
+ /**
+ * Convenience method for setting the status icon directly. For more flexible options see {@link #setStatus}.
+ * See {@link #iconCls} for complete details about customizing the icon.
+ * @param {String} iconCls (optional) The icon class to set (defaults to '', and any current icon class is removed)
+ * @return {Ext.ux.StatusBar} this
+ */
+ setIcon: function(cls) {
+ this.activeThreadId++;
+ cls = cls || '';
+
+ if (this.rendered) {
+ if (this.currIconCls) {
+ this.statusEl.removeClass(this.currIconCls);
+ this.currIconCls = null;
+ }
+ if (cls.length > 0) {
+ this.statusEl.addClass(cls);
+ this.currIconCls = cls;
+ }
+ } else {
+ this.currIconCls = cls;
+ }
+ return this;
+ },
+
+ /**
+ * Convenience method for setting the status text and icon to special values that are pre-configured to indicate
+ * a "busy" state, usually for loading or processing activities.
+ * @param {Object/String} config (optional) A config object in the same format supported by {@link #setStatus}, or a
+ * string to use as the status text (in which case all other options for setStatus will be defaulted). Use the
+ * <tt>text</tt> and/or <tt>iconCls</tt> properties on the config to override the default {@link #busyText}
+ * and {@link #busyIconCls} settings. If the config argument is not specified, {@link #busyText} and
+ * {@link #busyIconCls} will be used in conjunction with all of the default options for {@link #setStatus}.
+ * @return {Ext.ux.StatusBar} this
+ */
+ showBusy: function(o) {
+ if (typeof o == 'string') {
+ o = { text: o };
+ }
+ o = Ext.applyIf(o || {}, {
+ text: this.busyText,
+ iconCls: this.busyIconCls,
+ });
+ return this.setStatus(o);
+ },
+});
+Ext.reg('statusbar', Ext.ux.StatusBar);
diff --git a/deluge/ui/web/js/extjs/ext-extensions/JSLoader.js b/deluge/ui/web/js/extjs/ext-extensions/JSLoader.js
new file mode 100644
index 0000000..9631fd8
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/JSLoader.js
@@ -0,0 +1,40 @@
+Ext.ux.JSLoader = function(options) {
+ Ext.ux.JSLoader.scripts[++Ext.ux.JSLoader.index] = {
+ url: options.url,
+ success: true,
+ jsLoadObj: null,
+ options: options,
+ onLoad: options.onLoad || Ext.emptyFn,
+ onError: options.onError || Ext.ux.JSLoader.stdError,
+ scope: options.scope || this,
+ };
+
+ Ext.Ajax.request({
+ url: options.url,
+ scriptIndex: Ext.ux.JSLoader.index,
+ success: function(response, options) {
+ var script = Ext.ux.JSLoader.scripts[options.scriptIndex];
+ try {
+ eval(response.responseText);
+ } catch (e) {
+ script.success = false;
+ script.onError(script.options, e);
+ }
+ if (script.success) {
+ script.onLoad.call(script.scope, script.options);
+ }
+ },
+ failure: function(response, options) {
+ var script = Ext.ux.JSLoader.scripts[options.scriptIndex];
+ script.success = false;
+ script.onError(script.options, response.status);
+ },
+ });
+};
+Ext.ux.JSLoader.index = 0;
+Ext.ux.JSLoader.scripts = [];
+Ext.ux.JSLoader.stdError = function(options, e) {
+ window.alert(
+ 'Error loading script:\n\n' + options.url + '\n\nstatus: ' + e
+ );
+};
diff --git a/deluge/ui/web/js/extjs/ext-extensions/Spinner.js b/deluge/ui/web/js/extjs/ext-extensions/Spinner.js
new file mode 100644
index 0000000..ff272d2
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/Spinner.js
@@ -0,0 +1,474 @@
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+/**
+ * @class Ext.ux.Spinner
+ * @extends Ext.util.Observable
+ * Creates a Spinner control utilized by Ext.ux.form.SpinnerField
+ */
+Ext.ux.Spinner = Ext.extend(Ext.util.Observable, {
+ incrementValue: 1,
+ alternateIncrementValue: 5,
+ triggerClass: 'x-form-spinner-trigger',
+ splitterClass: 'x-form-spinner-splitter',
+ alternateKey: Ext.EventObject.shiftKey,
+ defaultValue: 0,
+ accelerate: false,
+
+ constructor: function(config) {
+ Ext.ux.Spinner.superclass.constructor.call(this, config);
+ Ext.apply(this, config);
+ this.mimicing = false;
+ },
+
+ init: function(field) {
+ this.field = field;
+
+ field.afterMethod('onRender', this.doRender, this);
+ field.afterMethod('onEnable', this.doEnable, this);
+ field.afterMethod('onDisable', this.doDisable, this);
+ field.afterMethod('afterRender', this.doAfterRender, this);
+ field.afterMethod('onResize', this.doResize, this);
+ field.afterMethod('onFocus', this.doFocus, this);
+ field.beforeMethod('onDestroy', this.doDestroy, this);
+ },
+
+ doRender: function(ct, position) {
+ var el = (this.el = this.field.getEl());
+ var f = this.field;
+
+ if (!f.wrap) {
+ f.wrap = this.wrap = el.wrap({
+ cls: 'x-form-field-wrap',
+ });
+ } else {
+ this.wrap = f.wrap.addClass('x-form-field-wrap');
+ }
+
+ this.trigger = this.wrap.createChild({
+ tag: 'img',
+ src: Ext.BLANK_IMAGE_URL,
+ cls: 'x-form-trigger ' + this.triggerClass,
+ });
+
+ if (!f.width) {
+ this.wrap.setWidth(el.getWidth() + this.trigger.getWidth());
+ }
+
+ this.splitter = this.wrap.createChild({
+ tag: 'div',
+ cls: this.splitterClass,
+ style: 'width:13px; height:2px;',
+ });
+ this.splitter
+ .setRight(Ext.isIE ? 1 : 2)
+ .setTop(10)
+ .show();
+
+ this.proxy = this.trigger.createProxy('', this.splitter, true);
+ this.proxy.addClass('x-form-spinner-proxy');
+ this.proxy.setStyle('left', '0px');
+ this.proxy.setSize(14, 1);
+ this.proxy.hide();
+ this.dd = new Ext.dd.DDProxy(this.splitter.dom.id, 'SpinnerDrag', {
+ dragElId: this.proxy.id,
+ });
+
+ this.initTrigger();
+ this.initSpinner();
+ },
+
+ doAfterRender: function() {
+ var y;
+ if (Ext.isIE && this.el.getY() != (y = this.trigger.getY())) {
+ this.el.position();
+ this.el.setY(y);
+ }
+ },
+
+ doEnable: function() {
+ if (this.wrap) {
+ this.disabled = false;
+ this.wrap.removeClass(this.field.disabledClass);
+ }
+ },
+
+ doDisable: function() {
+ if (this.wrap) {
+ this.disabled = true;
+ this.wrap.addClass(this.field.disabledClass);
+ this.el.removeClass(this.field.disabledClass);
+ }
+ },
+
+ doResize: function(w, h) {
+ if (typeof w == 'number') {
+ this.el.setWidth(w - this.trigger.getWidth());
+ }
+ this.wrap.setWidth(this.el.getWidth() + this.trigger.getWidth());
+ },
+
+ doFocus: function() {
+ if (!this.mimicing) {
+ this.wrap.addClass('x-trigger-wrap-focus');
+ this.mimicing = true;
+ Ext.get(Ext.isIE ? document.body : document).on(
+ 'mousedown',
+ this.mimicBlur,
+ this,
+ {
+ delay: 10,
+ }
+ );
+ this.el.on('keydown', this.checkTab, this);
+ }
+ },
+
+ // private
+ checkTab: function(e) {
+ if (e.getKey() == e.TAB) {
+ this.triggerBlur();
+ }
+ },
+
+ // private
+ mimicBlur: function(e) {
+ if (!this.wrap.contains(e.target) && this.field.validateBlur(e)) {
+ this.triggerBlur();
+ }
+ },
+
+ // private
+ triggerBlur: function() {
+ this.mimicing = false;
+ Ext.get(Ext.isIE ? document.body : document).un(
+ 'mousedown',
+ this.mimicBlur,
+ this
+ );
+ this.el.un('keydown', this.checkTab, this);
+ this.field.beforeBlur();
+ this.wrap.removeClass('x-trigger-wrap-focus');
+ this.field.onBlur.call(this.field);
+ },
+
+ initTrigger: function() {
+ this.trigger.addClassOnOver('x-form-trigger-over');
+ this.trigger.addClassOnClick('x-form-trigger-click');
+ },
+
+ initSpinner: function() {
+ this.field.addEvents({
+ spin: true,
+ spinup: true,
+ spindown: true,
+ });
+
+ this.keyNav = new Ext.KeyNav(this.el, {
+ up: function(e) {
+ e.preventDefault();
+ this.onSpinUp();
+ },
+
+ down: function(e) {
+ e.preventDefault();
+ this.onSpinDown();
+ },
+
+ pageUp: function(e) {
+ e.preventDefault();
+ this.onSpinUpAlternate();
+ },
+
+ pageDown: function(e) {
+ e.preventDefault();
+ this.onSpinDownAlternate();
+ },
+
+ scope: this,
+ });
+
+ this.repeater = new Ext.util.ClickRepeater(this.trigger, {
+ accelerate: this.accelerate,
+ });
+ this.field.mon(this.repeater, 'click', this.onTriggerClick, this, {
+ preventDefault: true,
+ });
+
+ this.field.mon(this.trigger, {
+ mouseover: this.onMouseOver,
+ mouseout: this.onMouseOut,
+ mousemove: this.onMouseMove,
+ mousedown: this.onMouseDown,
+ mouseup: this.onMouseUp,
+ scope: this,
+ preventDefault: true,
+ });
+
+ this.field.mon(this.wrap, 'mousewheel', this.handleMouseWheel, this);
+
+ this.dd.setXConstraint(0, 0, 10);
+ this.dd.setYConstraint(1500, 1500, 10);
+ this.dd.endDrag = this.endDrag.createDelegate(this);
+ this.dd.startDrag = this.startDrag.createDelegate(this);
+ this.dd.onDrag = this.onDrag.createDelegate(this);
+ },
+
+ onMouseOver: function() {
+ if (this.disabled) {
+ return;
+ }
+ var middle = this.getMiddle();
+ this.tmpHoverClass =
+ Ext.EventObject.getPageY() < middle
+ ? 'x-form-spinner-overup'
+ : 'x-form-spinner-overdown';
+ this.trigger.addClass(this.tmpHoverClass);
+ },
+
+ //private
+ onMouseOut: function() {
+ this.trigger.removeClass(this.tmpHoverClass);
+ },
+
+ //private
+ onMouseMove: function() {
+ if (this.disabled) {
+ return;
+ }
+ var middle = this.getMiddle();
+ if (
+ (Ext.EventObject.getPageY() > middle &&
+ this.tmpHoverClass == 'x-form-spinner-overup') ||
+ (Ext.EventObject.getPageY() < middle &&
+ this.tmpHoverClass == 'x-form-spinner-overdown')
+ ) {
+ }
+ },
+
+ //private
+ onMouseDown: function() {
+ if (this.disabled) {
+ return;
+ }
+ var middle = this.getMiddle();
+ this.tmpClickClass =
+ Ext.EventObject.getPageY() < middle
+ ? 'x-form-spinner-clickup'
+ : 'x-form-spinner-clickdown';
+ this.trigger.addClass(this.tmpClickClass);
+ },
+
+ //private
+ onMouseUp: function() {
+ this.trigger.removeClass(this.tmpClickClass);
+ },
+
+ //private
+ onTriggerClick: function() {
+ if (this.disabled || this.el.dom.readOnly) {
+ return;
+ }
+ var middle = this.getMiddle();
+ var ud = Ext.EventObject.getPageY() < middle ? 'Up' : 'Down';
+ this['onSpin' + ud]();
+ },
+
+ //private
+ getMiddle: function() {
+ var t = this.trigger.getTop();
+ var h = this.trigger.getHeight();
+ var middle = t + h / 2;
+ return middle;
+ },
+
+ //private
+ //checks if control is allowed to spin
+ isSpinnable: function() {
+ if (this.disabled || this.el.dom.readOnly) {
+ Ext.EventObject.preventDefault(); //prevent scrolling when disabled/readonly
+ return false;
+ }
+ return true;
+ },
+
+ handleMouseWheel: function(e) {
+ //disable scrolling when not focused
+ if (this.wrap.hasClass('x-trigger-wrap-focus') == false) {
+ return;
+ }
+
+ var delta = e.getWheelDelta();
+ if (delta > 0) {
+ this.onSpinUp();
+ e.stopEvent();
+ } else if (delta < 0) {
+ this.onSpinDown();
+ e.stopEvent();
+ }
+ },
+
+ //private
+ startDrag: function() {
+ this.proxy.show();
+ this._previousY = Ext.fly(this.dd.getDragEl()).getTop();
+ },
+
+ //private
+ endDrag: function() {
+ this.proxy.hide();
+ },
+
+ //private
+ onDrag: function() {
+ if (this.disabled) {
+ return;
+ }
+ var y = Ext.fly(this.dd.getDragEl()).getTop();
+ var ud = '';
+
+ if (this._previousY > y) {
+ ud = 'Up';
+ } //up
+ if (this._previousY < y) {
+ ud = 'Down';
+ } //down
+ if (ud != '') {
+ this['onSpin' + ud]();
+ }
+
+ this._previousY = y;
+ },
+
+ //private
+ onSpinUp: function() {
+ if (this.isSpinnable() == false) {
+ return;
+ }
+ if (Ext.EventObject.shiftKey == true) {
+ this.onSpinUpAlternate();
+ return;
+ } else {
+ this.spin(false, false);
+ }
+ this.field.fireEvent('spin', this);
+ this.field.fireEvent('spinup', this);
+ },
+
+ //private
+ onSpinDown: function() {
+ if (this.isSpinnable() == false) {
+ return;
+ }
+ if (Ext.EventObject.shiftKey == true) {
+ this.onSpinDownAlternate();
+ return;
+ } else {
+ this.spin(true, false);
+ }
+ this.field.fireEvent('spin', this);
+ this.field.fireEvent('spindown', this);
+ },
+
+ //private
+ onSpinUpAlternate: function() {
+ if (this.isSpinnable() == false) {
+ return;
+ }
+ this.spin(false, true);
+ this.field.fireEvent('spin', this);
+ this.field.fireEvent('spinup', this);
+ },
+
+ //private
+ onSpinDownAlternate: function() {
+ if (this.isSpinnable() == false) {
+ return;
+ }
+ this.spin(true, true);
+ this.field.fireEvent('spin', this);
+ this.field.fireEvent('spindown', this);
+ },
+
+ spin: function(down, alternate) {
+ var v = parseFloat(this.field.getValue());
+ var incr =
+ alternate == true
+ ? this.alternateIncrementValue
+ : this.incrementValue;
+ down == true ? (v -= incr) : (v += incr);
+
+ v = isNaN(v) ? this.defaultValue : v;
+ v = this.fixBoundries(v);
+ this.field.setRawValue(v);
+ },
+
+ fixBoundries: function(value) {
+ var v = value;
+
+ if (this.field.minValue != undefined && v < this.field.minValue) {
+ v = this.field.minValue;
+ }
+ if (this.field.maxValue != undefined && v > this.field.maxValue) {
+ v = this.field.maxValue;
+ }
+
+ return this.fixPrecision(v);
+ },
+
+ // private
+ fixPrecision: function(value) {
+ var nan = isNaN(value);
+ if (
+ !this.field.allowDecimals ||
+ this.field.decimalPrecision == -1 ||
+ nan ||
+ !value
+ ) {
+ return nan ? '' : value;
+ }
+ return parseFloat(
+ parseFloat(value).toFixed(this.field.decimalPrecision)
+ );
+ },
+
+ doDestroy: function() {
+ if (this.trigger) {
+ this.trigger.remove();
+ }
+ if (this.wrap) {
+ this.wrap.remove();
+ delete this.field.wrap;
+ }
+
+ if (this.splitter) {
+ this.splitter.remove();
+ }
+
+ if (this.dd) {
+ this.dd.unreg();
+ this.dd = null;
+ }
+
+ if (this.proxy) {
+ this.proxy.remove();
+ }
+
+ if (this.repeater) {
+ this.repeater.purgeListeners();
+ }
+ if (this.mimicing) {
+ Ext.get(Ext.isIE ? document.body : document).un(
+ 'mousedown',
+ this.mimicBlur,
+ this
+ );
+ }
+ },
+});
+
+//backwards compat
+Ext.form.Spinner = Ext.ux.Spinner;
diff --git a/deluge/ui/web/js/extjs/ext-extensions/StatusBar.js b/deluge/ui/web/js/extjs/ext-extensions/StatusBar.js
new file mode 100644
index 0000000..a12b8f9
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/StatusBar.js
@@ -0,0 +1,422 @@
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+/**
+ * @class Ext.ux.StatusBar
+ * <p>Basic status bar component that can be used as the bottom toolbar of any {@link Ext.Panel}. In addition to
+ * supporting the standard {@link Ext.Toolbar} interface for adding buttons, menus and other items, the StatusBar
+ * provides a greedy status element that can be aligned to either side and has convenient methods for setting the
+ * status text and icon. You can also indicate that something is processing using the {@link #showBusy} method.</p>
+ * <pre><code>
+new Ext.Panel({
+ title: 'StatusBar',
+ // etc.
+ bbar: new Ext.ux.StatusBar({
+ id: 'my-status',
+
+ // defaults to use when the status is cleared:
+ defaultText: 'Default status text',
+ defaultIconCls: 'default-icon',
+
+ // values to set initially:
+ text: 'Ready',
+ iconCls: 'ready-icon',
+
+ // any standard Toolbar items:
+ items: [{
+ text: 'A Button'
+ }, '-', 'Plain Text']
+ })
+});
+
+// Update the status bar later in code:
+var sb = Ext.getCmp('my-status');
+sb.setStatus({
+ text: 'OK',
+ iconCls: 'ok-icon',
+ clear: true // auto-clear after a set interval
+});
+
+// Set the status bar to show that something is processing:
+sb.showBusy();
+
+// processing....
+
+sb.clearStatus(); // once completeed
+</code></pre>
+ * @extends Ext.Toolbar
+ * @constructor
+ * Creates a new StatusBar
+ * @param {Object/Array} config A config object
+ */
+Ext.ux.StatusBar = Ext.extend(Ext.Toolbar, {
+ /**
+ * @cfg {String} statusAlign
+ * The alignment of the status element within the overall StatusBar layout. When the StatusBar is rendered,
+ * it creates an internal div containing the status text and icon. Any additional Toolbar items added in the
+ * StatusBar's {@link #items} config, or added via {@link #add} or any of the supported add* methods, will be
+ * rendered, in added order, to the opposite side. The status element is greedy, so it will automatically
+ * expand to take up all sapce left over by any other items. Example usage:
+ * <pre><code>
+// Create a left-aligned status bar containing a button,
+// separator and text item that will be right-aligned (default):
+new Ext.Panel({
+ title: 'StatusBar',
+ // etc.
+ bbar: new Ext.ux.StatusBar({
+ defaultText: 'Default status text',
+ id: 'status-id',
+ items: [{
+ text: 'A Button'
+ }, '-', 'Plain Text']
+ })
+});
+
+// By adding the statusAlign config, this will create the
+// exact same toolbar, except the status and toolbar item
+// layout will be reversed from the previous example:
+new Ext.Panel({
+ title: 'StatusBar',
+ // etc.
+ bbar: new Ext.ux.StatusBar({
+ defaultText: 'Default status text',
+ id: 'status-id',
+ statusAlign: 'right',
+ items: [{
+ text: 'A Button'
+ }, '-', 'Plain Text']
+ })
+});
+</code></pre>
+ */
+ /**
+ * @cfg {String} defaultText
+ * The default {@link #text} value. This will be used anytime the status bar is cleared with the
+ * <tt>useDefaults:true</tt> option (defaults to '').
+ */
+ /**
+ * @cfg {String} defaultIconCls
+ * The default {@link #iconCls} value (see the iconCls docs for additional details about customizing the icon).
+ * This will be used anytime the status bar is cleared with the <tt>useDefaults:true</tt> option (defaults to '').
+ */
+ /**
+ * @cfg {String} text
+ * A string that will be <b>initially</b> set as the status message. This string
+ * will be set as innerHTML (html tags are accepted) for the toolbar item.
+ * If not specified, the value set for <code>{@link #defaultText}</code>
+ * will be used.
+ */
+ /**
+ * @cfg {String} iconCls
+ * A CSS class that will be <b>initially</b> set as the status bar icon and is
+ * expected to provide a background image (defaults to '').
+ * Example usage:<pre><code>
+// Example CSS rule:
+.x-statusbar .x-status-custom {
+ padding-left: 25px;
+ background: transparent url(images/custom-icon.gif) no-repeat 3px 2px;
+}
+
+// Setting a default icon:
+var sb = new Ext.ux.StatusBar({
+ defaultIconCls: 'x-status-custom'
+});
+
+// Changing the icon:
+sb.setStatus({
+ text: 'New status',
+ iconCls: 'x-status-custom'
+});
+</code></pre>
+ */
+
+ /**
+ * @cfg {String} cls
+ * The base class applied to the containing element for this component on render (defaults to 'x-statusbar')
+ */
+ cls: 'x-statusbar',
+ /**
+ * @cfg {String} busyIconCls
+ * The default <code>{@link #iconCls}</code> applied when calling
+ * <code>{@link #showBusy}</code> (defaults to <tt>'x-status-busy'</tt>).
+ * It can be overridden at any time by passing the <code>iconCls</code>
+ * argument into <code>{@link #showBusy}</code>.
+ */
+ busyIconCls: 'x-status-busy',
+ /**
+ * @cfg {String} busyText
+ * The default <code>{@link #text}</code> applied when calling
+ * <code>{@link #showBusy}</code> (defaults to <tt>'Loading...'</tt>).
+ * It can be overridden at any time by passing the <code>text</code>
+ * argument into <code>{@link #showBusy}</code>.
+ */
+ busyText: 'Loading...',
+ /**
+ * @cfg {Number} autoClear
+ * The number of milliseconds to wait after setting the status via
+ * <code>{@link #setStatus}</code> before automatically clearing the status
+ * text and icon (defaults to <tt>5000</tt>). Note that this only applies
+ * when passing the <tt>clear</tt> argument to <code>{@link #setStatus}</code>
+ * since that is the only way to defer clearing the status. This can
+ * be overridden by specifying a different <tt>wait</tt> value in
+ * <code>{@link #setStatus}</code>. Calls to <code>{@link #clearStatus}</code>
+ * always clear the status bar immediately and ignore this value.
+ */
+ autoClear: 5000,
+
+ /**
+ * @cfg {String} emptyText
+ * The text string to use if no text has been set. Defaults to
+ * <tt>'&nbsp;'</tt>). If there are no other items in the toolbar using
+ * an empty string (<tt>''</tt>) for this value would end up in the toolbar
+ * height collapsing since the empty string will not maintain the toolbar
+ * height. Use <tt>''</tt> if the toolbar should collapse in height
+ * vertically when no text is specified and there are no other items in
+ * the toolbar.
+ */
+ emptyText: '&nbsp;',
+
+ // private
+ activeThreadId: 0,
+
+ // private
+ initComponent: function() {
+ if (this.statusAlign == 'right') {
+ this.cls += ' x-status-right';
+ }
+ Ext.ux.StatusBar.superclass.initComponent.call(this);
+ },
+
+ // private
+ afterRender: function() {
+ Ext.ux.StatusBar.superclass.afterRender.call(this);
+
+ var right = this.statusAlign == 'right';
+ this.currIconCls = this.iconCls || this.defaultIconCls;
+ this.statusEl = new Ext.Toolbar.TextItem({
+ cls: 'x-status-text ' + (this.currIconCls || ''),
+ text: this.text || this.defaultText || '',
+ });
+
+ if (right) {
+ this.add('->');
+ this.add(this.statusEl);
+ } else {
+ this.insert(0, this.statusEl);
+ this.insert(1, '->');
+ }
+ this.doLayout();
+ },
+
+ /**
+ * Sets the status {@link #text} and/or {@link #iconCls}. Also supports automatically clearing the
+ * status that was set after a specified interval.
+ * @param {Object/String} config A config object specifying what status to set, or a string assumed
+ * to be the status text (and all other options are defaulted as explained below). A config
+ * object containing any or all of the following properties can be passed:<ul>
+ * <li><tt>text</tt> {String} : (optional) The status text to display. If not specified, any current
+ * status text will remain unchanged.</li>
+ * <li><tt>iconCls</tt> {String} : (optional) The CSS class used to customize the status icon (see
+ * {@link #iconCls} for details). If not specified, any current iconCls will remain unchanged.</li>
+ * <li><tt>clear</tt> {Boolean/Number/Object} : (optional) Allows you to set an internal callback that will
+ * automatically clear the status text and iconCls after a specified amount of time has passed. If clear is not
+ * specified, the new status will not be auto-cleared and will stay until updated again or cleared using
+ * {@link #clearStatus}. If <tt>true</tt> is passed, the status will be cleared using {@link #autoClear},
+ * {@link #defaultText} and {@link #defaultIconCls} via a fade out animation. If a numeric value is passed,
+ * it will be used as the callback interval (in milliseconds), overriding the {@link #autoClear} value.
+ * All other options will be defaulted as with the boolean option. To customize any other options,
+ * you can pass an object in the format:<ul>
+ * <li><tt>wait</tt> {Number} : (optional) The number of milliseconds to wait before clearing
+ * (defaults to {@link #autoClear}).</li>
+ * <li><tt>anim</tt> {Number} : (optional) False to clear the status immediately once the callback
+ * executes (defaults to true which fades the status out).</li>
+ * <li><tt>useDefaults</tt> {Number} : (optional) False to completely clear the status text and iconCls
+ * (defaults to true which uses {@link #defaultText} and {@link #defaultIconCls}).</li>
+ * </ul></li></ul>
+ * Example usage:<pre><code>
+// Simple call to update the text
+statusBar.setStatus('New status');
+
+// Set the status and icon, auto-clearing with default options:
+statusBar.setStatus({
+ text: 'New status',
+ iconCls: 'x-status-custom',
+ clear: true
+});
+
+// Auto-clear with custom options:
+statusBar.setStatus({
+ text: 'New status',
+ iconCls: 'x-status-custom',
+ clear: {
+ wait: 8000,
+ anim: false,
+ useDefaults: false
+ }
+});
+</code></pre>
+ * @return {Ext.ux.StatusBar} this
+ */
+ setStatus: function(o) {
+ o = o || {};
+
+ if (typeof o == 'string') {
+ o = { text: o };
+ }
+ if (o.text !== undefined) {
+ this.setText(o.text);
+ }
+ if (o.iconCls !== undefined) {
+ this.setIcon(o.iconCls);
+ }
+
+ if (o.clear) {
+ var c = o.clear,
+ wait = this.autoClear,
+ defaults = { useDefaults: true, anim: true };
+
+ if (typeof c == 'object') {
+ c = Ext.applyIf(c, defaults);
+ if (c.wait) {
+ wait = c.wait;
+ }
+ } else if (typeof c == 'number') {
+ wait = c;
+ c = defaults;
+ } else if (typeof c == 'boolean') {
+ c = defaults;
+ }
+
+ c.threadId = this.activeThreadId;
+ this.clearStatus.defer(wait, this, [c]);
+ }
+ return this;
+ },
+
+ /**
+ * Clears the status {@link #text} and {@link #iconCls}. Also supports clearing via an optional fade out animation.
+ * @param {Object} config (optional) A config object containing any or all of the following properties. If this
+ * object is not specified the status will be cleared using the defaults below:<ul>
+ * <li><tt>anim</tt> {Boolean} : (optional) True to clear the status by fading out the status element (defaults
+ * to false which clears immediately).</li>
+ * <li><tt>useDefaults</tt> {Boolean} : (optional) True to reset the text and icon using {@link #defaultText} and
+ * {@link #defaultIconCls} (defaults to false which sets the text to '' and removes any existing icon class).</li>
+ * </ul>
+ * @return {Ext.ux.StatusBar} this
+ */
+ clearStatus: function(o) {
+ o = o || {};
+
+ if (o.threadId && o.threadId !== this.activeThreadId) {
+ // this means the current call was made internally, but a newer
+ // thread has set a message since this call was deferred. Since
+ // we don't want to overwrite a newer message just ignore.
+ return this;
+ }
+
+ var text = o.useDefaults ? this.defaultText : this.emptyText,
+ iconCls = o.useDefaults
+ ? this.defaultIconCls
+ ? this.defaultIconCls
+ : ''
+ : '';
+
+ if (o.anim) {
+ // animate the statusEl Ext.Element
+ this.statusEl.el.fadeOut({
+ remove: false,
+ useDisplay: true,
+ scope: this,
+ callback: function() {
+ this.setStatus({
+ text: text,
+ iconCls: iconCls,
+ });
+
+ this.statusEl.el.show();
+ },
+ });
+ } else {
+ // hide/show the el to avoid jumpy text or icon
+ this.statusEl.hide();
+ this.setStatus({
+ text: text,
+ iconCls: iconCls,
+ });
+ this.statusEl.show();
+ }
+ return this;
+ },
+
+ /**
+ * Convenience method for setting the status text directly. For more flexible options see {@link #setStatus}.
+ * @param {String} text (optional) The text to set (defaults to '')
+ * @return {Ext.ux.StatusBar} this
+ */
+ setText: function(text) {
+ this.activeThreadId++;
+ this.text = text || '';
+ if (this.rendered) {
+ this.statusEl.setText(this.text);
+ }
+ return this;
+ },
+
+ /**
+ * Returns the current status text.
+ * @return {String} The status text
+ */
+ getText: function() {
+ return this.text;
+ },
+
+ /**
+ * Convenience method for setting the status icon directly. For more flexible options see {@link #setStatus}.
+ * See {@link #iconCls} for complete details about customizing the icon.
+ * @param {String} iconCls (optional) The icon class to set (defaults to '', and any current icon class is removed)
+ * @return {Ext.ux.StatusBar} this
+ */
+ setIcon: function(cls) {
+ this.activeThreadId++;
+ cls = cls || '';
+
+ if (this.rendered) {
+ if (this.currIconCls) {
+ this.statusEl.removeClass(this.currIconCls);
+ this.currIconCls = null;
+ }
+ if (cls.length > 0) {
+ this.statusEl.addClass(cls);
+ this.currIconCls = cls;
+ }
+ } else {
+ this.currIconCls = cls;
+ }
+ return this;
+ },
+
+ /**
+ * Convenience method for setting the status text and icon to special values that are pre-configured to indicate
+ * a "busy" state, usually for loading or processing activities.
+ * @param {Object/String} config (optional) A config object in the same format supported by {@link #setStatus}, or a
+ * string to use as the status text (in which case all other options for setStatus will be defaulted). Use the
+ * <tt>text</tt> and/or <tt>iconCls</tt> properties on the config to override the default {@link #busyText}
+ * and {@link #busyIconCls} settings. If the config argument is not specified, {@link #busyText} and
+ * {@link #busyIconCls} will be used in conjunction with all of the default options for {@link #setStatus}.
+ * @return {Ext.ux.StatusBar} this
+ */
+ showBusy: function(o) {
+ if (typeof o == 'string') {
+ o = { text: o };
+ }
+ o = Ext.applyIf(o || {}, {
+ text: this.busyText,
+ iconCls: this.busyIconCls,
+ });
+ return this.setStatus(o);
+ },
+});
+Ext.reg('statusbar', Ext.ux.StatusBar);
diff --git a/deluge/ui/web/js/extjs/ext-extensions/form/FileUploadField.js b/deluge/ui/web/js/extjs/ext-extensions/form/FileUploadField.js
new file mode 100644
index 0000000..ca15073
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/form/FileUploadField.js
@@ -0,0 +1,208 @@
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+Ext.ns('Ext.ux.form');
+
+/**
+ * @class Ext.ux.form.FileUploadField
+ * @extends Ext.form.TextField
+ * Creates a file upload field.
+ * @xtype fileuploadfield
+ */
+Ext.ux.form.FileUploadField = Ext.extend(Ext.form.TextField, {
+ /**
+ * @cfg {String} buttonText The button text to display on the upload button (defaults to
+ * 'Browse...'). Note that if you supply a value for {@link #buttonCfg}, the buttonCfg.text
+ * value will be used instead if available.
+ */
+ buttonText: 'Browse...',
+ /**
+ * @cfg {Boolean} buttonOnly True to display the file upload field as a button with no visible
+ * text field (defaults to false). If true, all inherited TextField members will still be available.
+ */
+ buttonOnly: false,
+ /**
+ * @cfg {Number} buttonOffset The number of pixels of space reserved between the button and the text field
+ * (defaults to 3). Note that this only applies if {@link #buttonOnly} = false.
+ */
+ buttonOffset: 3,
+
+ /**
+ * @cfg {Boolean} multiple True to select more than one file. (defaults to false).
+ * Note that this only applies if the HTML doc is using HTML5.
+ */
+ multiple: false,
+
+ /**
+ * @cfg {Object} buttonCfg A standard {@link Ext.Button} config object.
+ */
+
+ // private
+ readOnly: true,
+
+ /**
+ * @hide
+ * @method autoSize
+ */
+ autoSize: Ext.emptyFn,
+
+ // private
+ initComponent: function() {
+ Ext.ux.form.FileUploadField.superclass.initComponent.call(this);
+
+ this.addEvents(
+ /**
+ * @event fileselected
+ * Fires when the underlying file input field's value has changed from the user
+ * selecting a new file from the system file selection dialog.
+ * @param {Ext.ux.form.FileUploadField} this
+ * @param {String} value The file value returned by the underlying file input field
+ */
+ 'fileselected'
+ );
+ },
+
+ // private
+ onRender: function(ct, position) {
+ Ext.ux.form.FileUploadField.superclass.onRender.call(
+ this,
+ ct,
+ position
+ );
+
+ this.wrap = this.el.wrap({ cls: 'x-form-field-wrap x-form-file-wrap' });
+ this.el.addClass('x-form-file-text');
+ this.el.dom.removeAttribute('name');
+ this.createFileInput();
+
+ var btnCfg = Ext.applyIf(this.buttonCfg || {}, {
+ text: this.buttonText,
+ });
+ this.button = new Ext.Button(
+ Ext.apply(btnCfg, {
+ renderTo: this.wrap,
+ cls: 'x-form-file-btn' + (btnCfg.iconCls ? ' x-btn-icon' : ''),
+ })
+ );
+
+ if (this.buttonOnly) {
+ this.el.hide();
+ this.wrap.setWidth(this.button.getEl().getWidth());
+ }
+
+ this.bindListeners();
+ this.resizeEl = this.positionEl = this.wrap;
+ },
+
+ bindListeners: function() {
+ this.fileInput.on({
+ scope: this,
+ mouseenter: function() {
+ this.button.addClass(['x-btn-over', 'x-btn-focus']);
+ },
+ mouseleave: function() {
+ this.button.removeClass([
+ 'x-btn-over',
+ 'x-btn-focus',
+ 'x-btn-click',
+ ]);
+ },
+ mousedown: function() {
+ this.button.addClass('x-btn-click');
+ },
+ mouseup: function() {
+ this.button.removeClass([
+ 'x-btn-over',
+ 'x-btn-focus',
+ 'x-btn-click',
+ ]);
+ },
+ change: function() {
+ var value = this.fileInput.dom.files;
+ // Fallback to value.
+ if (!value) value = this.fileInput.dom.value;
+ this.setValue(value);
+ this.fireEvent('fileselected', this, value);
+ },
+ });
+ },
+
+ createFileInput: function() {
+ this.fileInput = this.wrap.createChild({
+ id: this.getFileInputId(),
+ name: this.name || this.getId(),
+ cls: 'x-form-file',
+ tag: 'input',
+ type: 'file',
+ size: 1,
+ });
+ this.fileInput.dom.multiple = this.multiple;
+ },
+
+ reset: function() {
+ if (this.rendered) {
+ this.fileInput.remove();
+ this.createFileInput();
+ this.bindListeners();
+ }
+ Ext.ux.form.FileUploadField.superclass.reset.call(this);
+ },
+
+ // private
+ getFileInputId: function() {
+ return this.id + '-file';
+ },
+
+ // private
+ onResize: function(w, h) {
+ Ext.ux.form.FileUploadField.superclass.onResize.call(this, w, h);
+
+ this.wrap.setWidth(w);
+
+ if (!this.buttonOnly) {
+ var w =
+ this.wrap.getWidth() -
+ this.button.getEl().getWidth() -
+ this.buttonOffset;
+ this.el.setWidth(w);
+ }
+ },
+
+ // private
+ onDestroy: function() {
+ Ext.ux.form.FileUploadField.superclass.onDestroy.call(this);
+ Ext.destroy(this.fileInput, this.button, this.wrap);
+ },
+
+ onDisable: function() {
+ Ext.ux.form.FileUploadField.superclass.onDisable.call(this);
+ this.doDisable(true);
+ },
+
+ onEnable: function() {
+ Ext.ux.form.FileUploadField.superclass.onEnable.call(this);
+ this.doDisable(false);
+ },
+
+ // private
+ doDisable: function(disabled) {
+ this.fileInput.dom.disabled = disabled;
+ this.button.setDisabled(disabled);
+ },
+
+ // private
+ preFocus: Ext.emptyFn,
+
+ // private
+ alignErrorIcon: function() {
+ this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
+ },
+});
+
+Ext.reg('fileuploadfield', Ext.ux.form.FileUploadField);
+
+// backwards compat
+Ext.form.FileUploadField = Ext.ux.form.FileUploadField;
diff --git a/deluge/ui/web/js/extjs/ext-extensions/form/RadioGroupFix.js b/deluge/ui/web/js/extjs/ext-extensions/form/RadioGroupFix.js
new file mode 100644
index 0000000..134e7a1
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/form/RadioGroupFix.js
@@ -0,0 +1,50 @@
+/**
+ * Ext.ux.form.RadioGroup.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+// Allow radiogroups to be treated as a single form element.
+Ext.override(Ext.form.RadioGroup, {
+ afterRender: function() {
+ this.items.each(function(i) {
+ this.relayEvents(i, ['check']);
+ }, this);
+ if (this.lazyValue) {
+ this.setValue(this.value);
+ delete this.value;
+ delete this.lazyValue;
+ }
+ Ext.form.RadioGroup.superclass.afterRender.call(this);
+ },
+
+ getName: function() {
+ return this.items.first().getName();
+ },
+
+ getValue: function() {
+ return this.items.first().getGroupValue();
+ },
+
+ setValue: function(v) {
+ if (!this.items.each) {
+ this.value = v;
+ this.lazyValue = true;
+ return;
+ }
+ this.items.each(function(item) {
+ if (item.rendered) {
+ var checked = item.el.getValue() == String(v);
+ item.el.dom.checked = checked;
+ item.el.dom.defaultChecked = checked;
+ item.wrap[checked ? 'addClass' : 'removeClass'](
+ item.checkedCls
+ );
+ }
+ });
+ },
+});
diff --git a/deluge/ui/web/js/extjs/ext-extensions/form/SpinnerField.js b/deluge/ui/web/js/extjs/ext-extensions/form/SpinnerField.js
new file mode 100644
index 0000000..d14f320
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/form/SpinnerField.js
@@ -0,0 +1,68 @@
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+Ext.ns('Ext.ux.form');
+
+/**
+ * @class Ext.ux.form.SpinnerField
+ * @extends Ext.form.NumberField
+ * Creates a field utilizing Ext.ux.Spinner
+ * @xtype spinnerfield
+ */
+Ext.ux.form.SpinnerField = Ext.extend(Ext.form.NumberField, {
+ actionMode: 'wrap',
+ deferHeight: true,
+ autoSize: Ext.emptyFn,
+ onBlur: Ext.emptyFn,
+ adjustSize: Ext.BoxComponent.prototype.adjustSize,
+
+ constructor: function(config) {
+ var spinnerConfig = Ext.copyTo(
+ {},
+ config,
+ 'incrementValue,alternateIncrementValue,accelerate,defaultValue,triggerClass,splitterClass'
+ );
+
+ var spl = (this.spinner = new Ext.ux.Spinner(spinnerConfig));
+
+ var plugins = config.plugins
+ ? Ext.isArray(config.plugins)
+ ? config.plugins.push(spl)
+ : [config.plugins, spl]
+ : spl;
+
+ Ext.ux.form.SpinnerField.superclass.constructor.call(
+ this,
+ Ext.apply(config, { plugins: plugins })
+ );
+ },
+
+ // private
+ getResizeEl: function() {
+ return this.wrap;
+ },
+
+ // private
+ getPositionEl: function() {
+ return this.wrap;
+ },
+
+ // private
+ alignErrorIcon: function() {
+ if (this.wrap) {
+ this.errorIcon.alignTo(this.wrap, 'tl-tr', [2, 0]);
+ }
+ },
+
+ validateBlur: function() {
+ return true;
+ },
+});
+
+Ext.reg('spinnerfield', Ext.ux.form.SpinnerField);
+
+//backwards compat
+Ext.form.SpinnerField = Ext.ux.form.SpinnerField;
diff --git a/deluge/ui/web/js/extjs/ext-extensions/form/SpinnerFieldFix.js b/deluge/ui/web/js/extjs/ext-extensions/form/SpinnerFieldFix.js
new file mode 100644
index 0000000..6784ae0
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/form/SpinnerFieldFix.js
@@ -0,0 +1,13 @@
+/**
+ * Ext.ux.form.SpinnerField.js
+ *
+ * Copyright (c) Damien Churchill 2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Ext.override(Ext.ux.form.SpinnerField, {
+ onBlur: Ext.form.Field.prototype.onBlur,
+});
diff --git a/deluge/ui/web/js/extjs/ext-extensions/form/SpinnerGroup.js b/deluge/ui/web/js/extjs/ext-extensions/form/SpinnerGroup.js
new file mode 100644
index 0000000..eafc4e1
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/form/SpinnerGroup.js
@@ -0,0 +1,206 @@
+/**
+ * Ext.ux.form.SpinnerGroup.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.ns('Ext.ux.form');
+
+/**
+ *
+ */
+Ext.ux.form.SpinnerGroup = Ext.extend(Ext.form.CheckboxGroup, {
+ // private
+ defaultType: 'spinnerfield',
+ anchor: '98%',
+
+ // private
+ groupCls: 'x-form-spinner-group',
+
+ colCfg: {},
+
+ // private
+ onRender: function(ct, position) {
+ if (!this.el) {
+ var panelCfg = {
+ cls: this.groupCls,
+ layout: 'column',
+ border: false,
+ renderTo: ct,
+ };
+ var colCfg = Ext.apply(
+ {
+ defaultType: this.defaultType,
+ layout: 'form',
+ border: false,
+ labelWidth: 60,
+ defaults: {
+ hideLabel: true,
+ anchor: '60%',
+ },
+ },
+ this.colCfg
+ );
+
+ if (this.items[0].items) {
+ // The container has standard ColumnLayout configs, so pass them in directly
+
+ Ext.apply(panelCfg, {
+ layoutConfig: { columns: this.items.length },
+ defaults: this.defaults,
+ items: this.items,
+ });
+ for (var i = 0, len = this.items.length; i < len; i++) {
+ Ext.applyIf(this.items[i], colCfg);
+ }
+ } else {
+ // The container has field item configs, so we have to generate the column
+ // panels first then move the items into the columns as needed.
+
+ var numCols,
+ cols = [];
+
+ if (typeof this.columns == 'string') {
+ // 'auto' so create a col per item
+ this.columns = this.items.length;
+ }
+ if (!Ext.isArray(this.columns)) {
+ var cs = [];
+ for (var i = 0; i < this.columns; i++) {
+ cs.push((100 / this.columns) * 0.01); // distribute by even %
+ }
+ this.columns = cs;
+ }
+
+ numCols = this.columns.length;
+
+ // Generate the column configs with the correct width setting
+ for (var i = 0; i < numCols; i++) {
+ var cc = Ext.apply({ items: [] }, colCfg);
+ cc[
+ this.columns[i] <= 1 ? 'columnWidth' : 'width'
+ ] = this.columns[i];
+ if (this.defaults) {
+ cc.defaults = Ext.apply(
+ cc.defaults || {},
+ this.defaults
+ );
+ }
+ cols.push(cc);
+ }
+
+ // Distribute the original items into the columns
+ if (this.vertical) {
+ var rows = Math.ceil(this.items.length / numCols),
+ ri = 0;
+ for (var i = 0, len = this.items.length; i < len; i++) {
+ if (i > 0 && i % rows == 0) {
+ ri++;
+ }
+ if (this.items[i].fieldLabel) {
+ this.items[i].hideLabel = false;
+ }
+ cols[ri].items.push(this.items[i]);
+ }
+ } else {
+ for (var i = 0, len = this.items.length; i < len; i++) {
+ var ci = i % numCols;
+ if (this.items[i].fieldLabel) {
+ this.items[i].hideLabel = false;
+ }
+ cols[ci].items.push(this.items[i]);
+ }
+ }
+
+ Ext.apply(panelCfg, {
+ layoutConfig: { columns: numCols },
+ items: cols,
+ });
+ }
+
+ this.panel = new Ext.Panel(panelCfg);
+ this.el = this.panel.getEl();
+
+ if (this.forId && this.itemCls) {
+ var l = this.el.up(this.itemCls).child('label', true);
+ if (l) {
+ l.setAttribute('htmlFor', this.forId);
+ }
+ }
+
+ var fields = this.panel.findBy(function(c) {
+ return c.isFormField;
+ }, this);
+
+ this.items = new Ext.util.MixedCollection();
+ this.items.addAll(fields);
+
+ this.items.each(function(field) {
+ field.on('spin', this.onFieldChange, this);
+ field.on('change', this.onFieldChange, this);
+ }, this);
+
+ if (this.lazyValueSet) {
+ this.setValue(this.value);
+ delete this.value;
+ delete this.lazyValueSet;
+ }
+
+ if (this.lazyRawValueSet) {
+ this.setRawValue(this.rawValue);
+ delete this.rawValue;
+ delete this.lazyRawValueSet;
+ }
+ }
+
+ Ext.ux.form.SpinnerGroup.superclass.onRender.call(this, ct, position);
+ },
+
+ onFieldChange: function(spinner) {
+ this.fireEvent('change', this, this.getValue());
+ },
+
+ initValue: Ext.emptyFn,
+
+ getValue: function() {
+ var value = [this.items.getCount()];
+ this.items.each(function(item, i) {
+ value[i] = Number(item.getValue());
+ });
+ return value;
+ },
+
+ getRawValue: function() {
+ var value = [this.items.getCount()];
+ this.items.each(function(item, i) {
+ value[i] = Number(item.getRawValue());
+ });
+ return value;
+ },
+
+ setValue: function(value) {
+ if (!this.rendered) {
+ this.value = value;
+ this.lazyValueSet = true;
+ } else {
+ this.items.each(function(item, i) {
+ item.setValue(value[i]);
+ });
+ }
+ },
+
+ setRawValue: function(value) {
+ if (!this.rendered) {
+ this.rawValue = value;
+ this.lazyRawValueSet = true;
+ } else {
+ this.items.each(function(item, i) {
+ item.setRawValue(value[i]);
+ });
+ }
+ },
+});
+Ext.reg('spinnergroup', Ext.ux.form.SpinnerGroup);
diff --git a/deluge/ui/web/js/extjs/ext-extensions/form/ToggleField.js b/deluge/ui/web/js/extjs/ext-extensions/form/ToggleField.js
new file mode 100644
index 0000000..27eebf3
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/form/ToggleField.js
@@ -0,0 +1,75 @@
+/**
+ * Ext.ux.form.ToggleField.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+Ext.namespace('Ext.ux.form');
+
+/**
+ * Ext.ux.form.ToggleField class
+ *
+ * @author Damien Churchill
+ * @version v0.1
+ *
+ * @class Ext.ux.form.ToggleField
+ * @extends Ext.form.TriggerField
+ */
+Ext.ux.form.ToggleField = Ext.extend(Ext.form.Field, {
+ cls: 'x-toggle-field',
+
+ initComponent: function() {
+ Ext.ux.form.ToggleField.superclass.initComponent.call(this);
+
+ this.toggle = new Ext.form.Checkbox();
+ this.toggle.on('check', this.onToggleCheck, this);
+
+ this.input = new Ext.form.TextField({
+ disabled: true,
+ });
+ },
+
+ onRender: function(ct, position) {
+ if (!this.el) {
+ this.panel = new Ext.Panel({
+ cls: this.groupCls,
+ layout: 'table',
+ layoutConfig: {
+ columns: 2,
+ },
+ border: false,
+ renderTo: ct,
+ });
+ this.panel.ownerCt = this;
+ this.el = this.panel.getEl();
+
+ this.panel.add(this.toggle);
+ this.panel.add(this.input);
+ this.panel.doLayout();
+
+ this.toggle
+ .getEl()
+ .parent()
+ .setStyle('padding-right', '10px');
+ }
+ Ext.ux.form.ToggleField.superclass.onRender.call(this, ct, position);
+ },
+
+ // private
+ onResize: function(w, h) {
+ this.panel.setSize(w, h);
+ this.panel.doLayout();
+
+ // we substract 10 for the padding :-)
+ var inputWidth = w - this.toggle.getSize().width - 25;
+ this.input.setSize(inputWidth, h);
+ },
+
+ onToggleCheck: function(toggle, checked) {
+ this.input.setDisabled(!checked);
+ },
+});
+Ext.reg('togglefield', Ext.ux.form.ToggleField);
diff --git a/deluge/ui/web/js/extjs/ext-extensions/grid/BufferView.js b/deluge/ui/web/js/extjs/ext-extensions/grid/BufferView.js
new file mode 100644
index 0000000..e9f0e0c
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/grid/BufferView.js
@@ -0,0 +1,270 @@
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+Ext.ns('Ext.ux.grid');
+
+/**
+ * @class Ext.ux.grid.BufferView
+ * @extends Ext.grid.GridView
+ * A custom GridView which renders rows on an as-needed basis.
+ */
+Ext.ux.grid.BufferView = Ext.extend(Ext.grid.GridView, {
+ /**
+ * @cfg {Number} rowHeight
+ * The height of a row in the grid.
+ */
+ rowHeight: 19,
+
+ /**
+ * @cfg {Number} borderHeight
+ * The combined height of border-top and border-bottom of a row.
+ */
+ borderHeight: 2,
+
+ /**
+ * @cfg {Boolean/Number} scrollDelay
+ * The number of milliseconds before rendering rows out of the visible
+ * viewing area. Defaults to 100. Rows will render immediately with a config
+ * of false.
+ */
+ scrollDelay: 100,
+
+ /**
+ * @cfg {Number} cacheSize
+ * The number of rows to look forward and backwards from the currently viewable
+ * area. The cache applies only to rows that have been rendered already.
+ */
+ cacheSize: 20,
+
+ /**
+ * @cfg {Number} cleanDelay
+ * The number of milliseconds to buffer cleaning of extra rows not in the
+ * cache.
+ */
+ cleanDelay: 500,
+
+ initTemplates: function() {
+ Ext.ux.grid.BufferView.superclass.initTemplates.call(this);
+ var ts = this.templates;
+ // empty div to act as a place holder for a row
+ ts.rowHolder = new Ext.Template(
+ '<div class="x-grid3-row {alt}" style="{tstyle}"></div>'
+ );
+ ts.rowHolder.disableFormats = true;
+ ts.rowHolder.compile();
+
+ ts.rowBody = new Ext.Template(
+ '<table class="x-grid3-row-table" border="0" cellspacing="0" cellpadding="0" style="{tstyle}">',
+ '<tbody><tr>{cells}</tr>',
+ this.enableRowBody
+ ? '<tr class="x-grid3-row-body-tr" style="{bodyStyle}"><td colspan="{cols}" class="x-grid3-body-cell" tabIndex="0" hidefocus="on"><div class="x-grid3-row-body">{body}</div></td></tr>'
+ : '',
+ '</tbody></table>'
+ );
+ ts.rowBody.disableFormats = true;
+ ts.rowBody.compile();
+ },
+
+ getStyleRowHeight: function() {
+ return Ext.isBorderBox
+ ? this.rowHeight + this.borderHeight
+ : this.rowHeight;
+ },
+
+ getCalculatedRowHeight: function() {
+ return this.rowHeight + this.borderHeight;
+ },
+
+ getVisibleRowCount: function() {
+ var rh = this.getCalculatedRowHeight(),
+ visibleHeight = this.scroller.dom.clientHeight;
+ return visibleHeight < 1 ? 0 : Math.ceil(visibleHeight / rh);
+ },
+
+ getVisibleRows: function() {
+ var count = this.getVisibleRowCount(),
+ sc = this.scroller.dom.scrollTop,
+ start =
+ sc === 0
+ ? 0
+ : Math.floor(sc / this.getCalculatedRowHeight()) - 1;
+ return {
+ first: Math.max(start, 0),
+ last: Math.min(start + count + 2, this.ds.getCount() - 1),
+ };
+ },
+
+ doRender: function(cs, rs, ds, startRow, colCount, stripe, onlyBody) {
+ var ts = this.templates,
+ ct = ts.cell,
+ rt = ts.row,
+ rb = ts.rowBody,
+ last = colCount - 1,
+ rh = this.getStyleRowHeight(),
+ vr = this.getVisibleRows(),
+ tstyle = 'width:' + this.getTotalWidth() + ';height:' + rh + 'px;',
+ // buffers
+ buf = [],
+ cb,
+ c,
+ p = {},
+ rp = { tstyle: tstyle },
+ r;
+ for (var j = 0, len = rs.length; j < len; j++) {
+ r = rs[j];
+ cb = [];
+ var rowIndex = j + startRow,
+ visible = rowIndex >= vr.first && rowIndex <= vr.last;
+ if (visible) {
+ for (var i = 0; i < colCount; i++) {
+ c = cs[i];
+ p.id = c.id;
+ p.css =
+ i === 0
+ ? 'x-grid3-cell-first '
+ : i == last
+ ? 'x-grid3-cell-last '
+ : '';
+ p.attr = p.cellAttr = '';
+ p.value = c.renderer(r.data[c.name], p, r, rowIndex, i, ds);
+ p.style = c.style;
+ if (p.value === undefined || p.value === '') {
+ p.value = '&#160;';
+ }
+ if (r.dirty && typeof r.modified[c.name] !== 'undefined') {
+ p.css += ' x-grid3-dirty-cell';
+ }
+ cb[cb.length] = ct.apply(p);
+ }
+ }
+ var alt = [];
+ if (stripe && (rowIndex + 1) % 2 === 0) {
+ alt[0] = 'x-grid3-row-alt';
+ }
+ if (r.dirty) {
+ alt[1] = ' x-grid3-dirty-row';
+ }
+ rp.cols = colCount;
+ if (this.getRowClass) {
+ alt[2] = this.getRowClass(r, rowIndex, rp, ds);
+ }
+ rp.alt = alt.join(' ');
+ rp.cells = cb.join('');
+ buf[buf.length] = !visible
+ ? ts.rowHolder.apply(rp)
+ : onlyBody
+ ? rb.apply(rp)
+ : rt.apply(rp);
+ }
+ return buf.join('');
+ },
+
+ isRowRendered: function(index) {
+ var row = this.getRow(index);
+ return row && row.childNodes.length > 0;
+ },
+
+ syncScroll: function() {
+ Ext.ux.grid.BufferView.superclass.syncScroll.apply(this, arguments);
+ this.update();
+ },
+
+ // a (optionally) buffered method to update contents of gridview
+ update: function() {
+ if (this.scrollDelay) {
+ if (!this.renderTask) {
+ this.renderTask = new Ext.util.DelayedTask(this.doUpdate, this);
+ }
+ this.renderTask.delay(this.scrollDelay);
+ } else {
+ this.doUpdate();
+ }
+ },
+
+ onRemove: function(ds, record, index, isUpdate) {
+ Ext.ux.grid.BufferView.superclass.onRemove.apply(this, arguments);
+ if (isUpdate !== true) {
+ this.update();
+ }
+ },
+
+ doUpdate: function() {
+ if (this.getVisibleRowCount() > 0) {
+ var g = this.grid,
+ cm = g.colModel,
+ ds = g.store,
+ cs = this.getColumnData(),
+ vr = this.getVisibleRows(),
+ row;
+ for (var i = vr.first; i <= vr.last; i++) {
+ // if row is NOT rendered and is visible, render it
+ if (!this.isRowRendered(i) && (row = this.getRow(i))) {
+ var html = this.doRender(
+ cs,
+ [ds.getAt(i)],
+ ds,
+ i,
+ cm.getColumnCount(),
+ g.stripeRows,
+ true
+ );
+ row.innerHTML = html;
+ }
+ }
+ this.clean();
+ }
+ },
+
+ // a buffered method to clean rows
+ clean: function() {
+ if (!this.cleanTask) {
+ this.cleanTask = new Ext.util.DelayedTask(this.doClean, this);
+ }
+ this.cleanTask.delay(this.cleanDelay);
+ },
+
+ doClean: function() {
+ if (this.getVisibleRowCount() > 0) {
+ var vr = this.getVisibleRows();
+ vr.first -= this.cacheSize;
+ vr.last += this.cacheSize;
+
+ var i = 0,
+ rows = this.getRows();
+ // if first is less than 0, all rows have been rendered
+ // so lets clean the end...
+ if (vr.first <= 0) {
+ i = vr.last + 1;
+ }
+ for (var len = this.ds.getCount(); i < len; i++) {
+ // if current row is outside of first and last and
+ // has content, update the innerHTML to nothing
+ if ((i < vr.first || i > vr.last) && rows[i].innerHTML) {
+ rows[i].innerHTML = '';
+ }
+ }
+ }
+ },
+
+ removeTask: function(name) {
+ var task = this[name];
+ if (task && task.cancel) {
+ task.cancel();
+ this[name] = null;
+ }
+ },
+
+ destroy: function() {
+ this.removeTask('cleanTask');
+ this.removeTask('renderTask');
+ Ext.ux.grid.BufferView.superclass.destroy.call(this);
+ },
+
+ layout: function() {
+ Ext.ux.grid.BufferView.superclass.layout.call(this);
+ this.update();
+ },
+});
diff --git a/deluge/ui/web/js/extjs/ext-extensions/layout/FormLayoutFix.js b/deluge/ui/web/js/extjs/ext-extensions/layout/FormLayoutFix.js
new file mode 100644
index 0000000..14ac55a
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/layout/FormLayoutFix.js
@@ -0,0 +1,39 @@
+/**
+ * Ext.ux.layout.FormLayoutFix.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+// Taken from http://extjs.com/forum/showthread.php?t=75273
+// remove spaces for hidden elements and make show(), hide(), enable() and disable() act on
+// the label. don't use hideLabel with this.
+Ext.override(Ext.layout.FormLayout, {
+ renderItem: function(c, position, target) {
+ if (
+ c &&
+ !c.rendered &&
+ (c.isFormField || c.fieldLabel) &&
+ c.inputType != 'hidden'
+ ) {
+ var args = this.getTemplateArgs(c);
+ if (typeof position == 'number') {
+ position = target.dom.childNodes[position] || null;
+ }
+ if (position) {
+ c.formItem = this.fieldTpl.insertBefore(position, args, true);
+ } else {
+ c.formItem = this.fieldTpl.append(target, args, true);
+ }
+ c.actionMode = 'formItem';
+ c.render('x-form-el-' + c.id);
+ c.container = c.formItem;
+ c.actionMode = 'container';
+ } else {
+ Ext.layout.FormLayout.superclass.renderItem.apply(this, arguments);
+ }
+ },
+});
diff --git a/deluge/ui/web/js/extjs/ext-extensions/tree/MultiSelectionModelFix.js b/deluge/ui/web/js/extjs/ext-extensions/tree/MultiSelectionModelFix.js
new file mode 100644
index 0000000..979bd2c
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/tree/MultiSelectionModelFix.js
@@ -0,0 +1,68 @@
+/**
+ * Ext.ux.tree.MultiSelectionModelFix.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+/**
+ * This enhances the MSM to allow for shift selecting in tree grids etc.
+ * @author Damien Churchill <damoxc@gmail.com>
+ */
+Ext.override(Ext.tree.MultiSelectionModel, {
+ onNodeClick: function(node, e) {
+ if (e.ctrlKey && this.isSelected(node)) {
+ this.unselect(node);
+ } else if (e.shiftKey && !this.isSelected(node)) {
+ var parentNode = node.parentNode;
+ // We can only shift select files in the same node
+ if (this.lastSelNode.parentNode.id != parentNode.id) return;
+
+ // Get the node indexes
+ var fi = parentNode.indexOf(node),
+ li = parentNode.indexOf(this.lastSelNode);
+
+ // Select the last clicked node and wipe old selections
+ this.select(this.lastSelNode, e, false, true);
+
+ // Swap the values if required
+ if (fi > li) {
+ (fi = fi + li), (li = fi - li), (fi = fi - li);
+ }
+
+ // Select all the nodes
+ parentNode.eachChild(function(n) {
+ var i = parentNode.indexOf(n);
+ if (fi < i && i < li) {
+ this.select(n, e, true, true);
+ }
+ }, this);
+
+ // Select the clicked node
+ this.select(node, e, true);
+ } else {
+ this.select(node, e, e.ctrlKey);
+ }
+ },
+
+ select: function(node, e, keepExisting, suppressEvent) {
+ if (keepExisting !== true) {
+ this.clearSelections(true);
+ }
+ if (this.isSelected(node)) {
+ this.lastSelNode = node;
+ return node;
+ }
+ this.selNodes.push(node);
+ this.selMap[node.id] = node;
+ this.lastSelNode = node;
+ node.ui.onSelectedChange(true);
+ if (suppressEvent !== true) {
+ this.fireEvent('selectionchange', this, this.selNodes);
+ }
+ return node;
+ },
+});
diff --git a/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGrid.js b/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGrid.js
new file mode 100644
index 0000000..d3d5fc3
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGrid.js
@@ -0,0 +1,468 @@
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+Ext.ns('Ext.ux.tree');
+
+/**
+ * @class Ext.ux.tree.TreeGrid
+ * @extends Ext.tree.TreePanel
+ *
+ * @xtype treegrid
+ */
+Ext.ux.tree.TreeGrid = Ext.extend(Ext.tree.TreePanel, {
+ rootVisible: false,
+ useArrows: true,
+ lines: false,
+ borderWidth: Ext.isBorderBox ? 0 : 2, // the combined left/right border for each cell
+ cls: 'x-treegrid',
+
+ columnResize: true,
+ enableSort: true,
+ reserveScrollOffset: true,
+ enableHdMenu: true,
+
+ columnsText: 'Columns',
+
+ initComponent: function() {
+ if (!this.root) {
+ this.root = new Ext.tree.AsyncTreeNode({ text: 'Root' });
+ }
+
+ // initialize the loader
+ var l = this.loader;
+ if (!l) {
+ l = new Ext.ux.tree.TreeGridLoader({
+ dataUrl: this.dataUrl,
+ requestMethod: this.requestMethod,
+ store: this.store,
+ });
+ } else if (Ext.isObject(l) && !l.load) {
+ l = new Ext.ux.tree.TreeGridLoader(l);
+ }
+ this.loader = l;
+
+ Ext.ux.tree.TreeGrid.superclass.initComponent.call(this);
+
+ this.initColumns();
+
+ if (this.enableSort) {
+ this.treeGridSorter = new Ext.ux.tree.TreeGridSorter(
+ this,
+ this.enableSort
+ );
+ }
+
+ if (this.columnResize) {
+ this.colResizer = new Ext.tree.ColumnResizer(this.columnResize);
+ this.colResizer.init(this);
+ }
+
+ var c = this.columns;
+ if (!this.internalTpl) {
+ this.internalTpl = new Ext.XTemplate(
+ '<div class="x-grid3-header">',
+ '<div class="x-treegrid-header-inner">',
+ '<div class="x-grid3-header-offset">',
+ '<table style="table-layout: fixed;" cellspacing="0" cellpadding="0" border="0"><colgroup><tpl for="columns"><col /></tpl></colgroup>',
+ '<thead><tr class="x-grid3-hd-row">',
+ '<tpl for="columns">',
+ '<td class="x-grid3-hd x-grid3-cell x-treegrid-hd" style="text-align: {align};" id="',
+ this.id,
+ '-xlhd-{#}">',
+ '<div class="x-grid3-hd-inner x-treegrid-hd-inner" unselectable="on">',
+ this.enableHdMenu
+ ? '<a class="x-grid3-hd-btn" href="#"></a>'
+ : '',
+ '{header}<img class="x-grid3-sort-icon" src="',
+ Ext.BLANK_IMAGE_URL,
+ '" />',
+ '</div>',
+ '</td></tpl>',
+ '</tr></thead>',
+ '</table>',
+ '</div></div>',
+ '</div>',
+ '<div class="x-treegrid-root-node">',
+ '<table class="x-treegrid-root-table" cellpadding="0" cellspacing="0" style="table-layout: fixed;"></table>',
+ '</div>'
+ );
+ }
+
+ if (!this.colgroupTpl) {
+ this.colgroupTpl = new Ext.XTemplate(
+ '<colgroup><tpl for="columns"><col style="width: {width}px"/></tpl></colgroup>'
+ );
+ }
+ },
+
+ initColumns: function() {
+ var cs = this.columns,
+ len = cs.length,
+ columns = [],
+ i,
+ c;
+
+ for (i = 0; i < len; i++) {
+ c = cs[i];
+ if (!c.isColumn) {
+ c.xtype = c.xtype
+ ? /^tg/.test(c.xtype)
+ ? c.xtype
+ : 'tg' + c.xtype
+ : 'tgcolumn';
+ c = Ext.create(c);
+ }
+ c.init(this);
+ columns.push(c);
+
+ if (this.enableSort !== false && c.sortable !== false) {
+ c.sortable = true;
+ this.enableSort = true;
+ }
+ }
+
+ this.columns = columns;
+ },
+
+ onRender: function() {
+ Ext.tree.TreePanel.superclass.onRender.apply(this, arguments);
+
+ this.el.addClass('x-treegrid');
+
+ this.outerCt = this.body.createChild({
+ cls:
+ 'x-tree-root-ct x-treegrid-ct ' +
+ (this.useArrows
+ ? 'x-tree-arrows'
+ : this.lines
+ ? 'x-tree-lines'
+ : 'x-tree-no-lines'),
+ });
+
+ this.internalTpl.overwrite(this.outerCt, { columns: this.columns });
+
+ this.mainHd = Ext.get(this.outerCt.dom.firstChild);
+ this.innerHd = Ext.get(this.mainHd.dom.firstChild);
+ this.innerBody = Ext.get(this.outerCt.dom.lastChild);
+ this.innerCt = Ext.get(this.innerBody.dom.firstChild);
+
+ this.colgroupTpl.insertFirst(this.innerCt, { columns: this.columns });
+
+ if (this.hideHeaders) {
+ this.el.child('.x-grid3-header').setDisplayed('none');
+ } else if (this.enableHdMenu !== false) {
+ this.hmenu = new Ext.menu.Menu({ id: this.id + '-hctx' });
+ if (this.enableColumnHide !== false) {
+ this.colMenu = new Ext.menu.Menu({
+ id: this.id + '-hcols-menu',
+ });
+ this.colMenu.on({
+ scope: this,
+ beforeshow: this.beforeColMenuShow,
+ itemclick: this.handleHdMenuClick,
+ });
+ this.hmenu.add({
+ itemId: 'columns',
+ hideOnClick: false,
+ text: this.columnsText,
+ menu: this.colMenu,
+ iconCls: 'x-cols-icon',
+ });
+ }
+ this.hmenu.on('itemclick', this.handleHdMenuClick, this);
+ }
+ },
+
+ setRootNode: function(node) {
+ node.attributes.uiProvider = Ext.ux.tree.TreeGridRootNodeUI;
+ node = Ext.ux.tree.TreeGrid.superclass.setRootNode.call(this, node);
+ if (this.innerCt) {
+ this.colgroupTpl.insertFirst(this.innerCt, {
+ columns: this.columns,
+ });
+ }
+ return node;
+ },
+
+ clearInnerCt: function() {
+ if (Ext.isIE) {
+ var dom = this.innerCt.dom;
+ while (dom.firstChild) {
+ dom.removeChild(dom.firstChild);
+ }
+ } else {
+ Ext.ux.tree.TreeGrid.superclass.clearInnerCt.call(this);
+ }
+ },
+
+ initEvents: function() {
+ Ext.ux.tree.TreeGrid.superclass.initEvents.apply(this, arguments);
+
+ this.mon(this.innerBody, 'scroll', this.syncScroll, this);
+ this.mon(this.innerHd, 'click', this.handleHdDown, this);
+ this.mon(this.mainHd, {
+ scope: this,
+ mouseover: this.handleHdOver,
+ mouseout: this.handleHdOut,
+ });
+ },
+
+ onResize: function(w, h) {
+ Ext.ux.tree.TreeGrid.superclass.onResize.apply(this, arguments);
+
+ var bd = this.innerBody.dom;
+ var hd = this.innerHd.dom;
+
+ if (!bd) {
+ return;
+ }
+
+ if (Ext.isNumber(h)) {
+ bd.style.height =
+ this.body.getHeight(true) - hd.offsetHeight + 'px';
+ }
+
+ if (Ext.isNumber(w)) {
+ var sw = Ext.num(this.scrollOffset, Ext.getScrollBarWidth());
+ if (
+ this.reserveScrollOffset ||
+ bd.offsetWidth - bd.clientWidth > 10
+ ) {
+ this.setScrollOffset(sw);
+ } else {
+ var me = this;
+ setTimeout(function() {
+ me.setScrollOffset(
+ bd.offsetWidth - bd.clientWidth > 10 ? sw : 0
+ );
+ }, 10);
+ }
+ }
+ },
+
+ updateColumnWidths: function() {
+ var cols = this.columns,
+ colCount = cols.length,
+ groups = this.outerCt.query('colgroup'),
+ groupCount = groups.length,
+ c,
+ g,
+ i,
+ j;
+
+ for (i = 0; i < colCount; i++) {
+ c = cols[i];
+ for (j = 0; j < groupCount; j++) {
+ g = groups[j];
+ g.childNodes[i].style.width = (c.hidden ? 0 : c.width) + 'px';
+ }
+ }
+
+ for (
+ i = 0, groups = this.innerHd.query('td'), len = groups.length;
+ i < len;
+ i++
+ ) {
+ c = Ext.fly(groups[i]);
+ if (cols[i] && cols[i].hidden) {
+ c.addClass('x-treegrid-hd-hidden');
+ } else {
+ c.removeClass('x-treegrid-hd-hidden');
+ }
+ }
+
+ var tcw = this.getTotalColumnWidth();
+ Ext.fly(this.innerHd.dom.firstChild).setWidth(
+ tcw + (this.scrollOffset || 0)
+ );
+ this.outerCt.select('table').setWidth(tcw);
+ this.syncHeaderScroll();
+ },
+
+ getVisibleColumns: function() {
+ var columns = [],
+ cs = this.columns,
+ len = cs.length,
+ i;
+
+ for (i = 0; i < len; i++) {
+ if (!cs[i].hidden) {
+ columns.push(cs[i]);
+ }
+ }
+ return columns;
+ },
+
+ getTotalColumnWidth: function() {
+ var total = 0;
+ for (
+ var i = 0, cs = this.getVisibleColumns(), len = cs.length;
+ i < len;
+ i++
+ ) {
+ total += cs[i].width;
+ }
+ return total;
+ },
+
+ setScrollOffset: function(scrollOffset) {
+ this.scrollOffset = scrollOffset;
+ this.updateColumnWidths();
+ },
+
+ // private
+ handleHdDown: function(e, t) {
+ var hd = e.getTarget('.x-treegrid-hd');
+
+ if (hd && Ext.fly(t).hasClass('x-grid3-hd-btn')) {
+ var ms = this.hmenu.items,
+ cs = this.columns,
+ index = this.findHeaderIndex(hd),
+ c = cs[index],
+ sort = c.sortable;
+
+ e.stopEvent();
+ Ext.fly(hd).addClass('x-grid3-hd-menu-open');
+ this.hdCtxIndex = index;
+
+ this.fireEvent('headerbuttonclick', ms, c, hd, index);
+
+ this.hmenu.on(
+ 'hide',
+ function() {
+ Ext.fly(hd).removeClass('x-grid3-hd-menu-open');
+ },
+ this,
+ { single: true }
+ );
+
+ this.hmenu.show(t, 'tl-bl?');
+ } else if (hd) {
+ var index = this.findHeaderIndex(hd);
+ this.fireEvent('headerclick', this.columns[index], hd, index);
+ }
+ },
+
+ // private
+ handleHdOver: function(e, t) {
+ var hd = e.getTarget('.x-treegrid-hd');
+ if (hd && !this.headersDisabled) {
+ index = this.findHeaderIndex(hd);
+ this.activeHdRef = t;
+ this.activeHdIndex = index;
+ var el = Ext.get(hd);
+ this.activeHdRegion = el.getRegion();
+ el.addClass('x-grid3-hd-over');
+ this.activeHdBtn = el.child('.x-grid3-hd-btn');
+ if (this.activeHdBtn) {
+ this.activeHdBtn.dom.style.height =
+ hd.firstChild.offsetHeight - 1 + 'px';
+ }
+ }
+ },
+
+ // private
+ handleHdOut: function(e, t) {
+ var hd = e.getTarget('.x-treegrid-hd');
+ if (hd && (!Ext.isIE || !e.within(hd, true))) {
+ this.activeHdRef = null;
+ Ext.fly(hd).removeClass('x-grid3-hd-over');
+ hd.style.cursor = '';
+ }
+ },
+
+ findHeaderIndex: function(hd) {
+ hd = hd.dom || hd;
+ var cs = hd.parentNode.childNodes;
+ for (var i = 0, c; (c = cs[i]); i++) {
+ if (c == hd) {
+ return i;
+ }
+ }
+ return -1;
+ },
+
+ // private
+ beforeColMenuShow: function() {
+ var cols = this.columns,
+ colCount = cols.length,
+ i,
+ c;
+ this.colMenu.removeAll();
+ for (i = 1; i < colCount; i++) {
+ c = cols[i];
+ if (c.hideable !== false) {
+ this.colMenu.add(
+ new Ext.menu.CheckItem({
+ itemId: 'col-' + i,
+ text: c.header,
+ checked: !c.hidden,
+ hideOnClick: false,
+ disabled: c.hideable === false,
+ })
+ );
+ }
+ }
+ },
+
+ // private
+ handleHdMenuClick: function(item) {
+ var index = this.hdCtxIndex,
+ id = item.getItemId();
+
+ if (
+ this.fireEvent(
+ 'headermenuclick',
+ this.columns[index],
+ id,
+ index
+ ) !== false
+ ) {
+ index = id.substr(4);
+ if (index > 0 && this.columns[index]) {
+ this.setColumnVisible(index, !item.checked);
+ }
+ }
+
+ return true;
+ },
+
+ setColumnVisible: function(index, visible) {
+ this.columns[index].hidden = !visible;
+ this.updateColumnWidths();
+ },
+
+ /**
+ * Scrolls the grid to the top
+ */
+ scrollToTop: function() {
+ this.innerBody.dom.scrollTop = 0;
+ this.innerBody.dom.scrollLeft = 0;
+ },
+
+ // private
+ syncScroll: function() {
+ this.syncHeaderScroll();
+ var mb = this.innerBody.dom;
+ this.fireEvent('bodyscroll', mb.scrollLeft, mb.scrollTop);
+ },
+
+ // private
+ syncHeaderScroll: function() {
+ var mb = this.innerBody.dom;
+ this.innerHd.dom.scrollLeft = mb.scrollLeft;
+ this.innerHd.dom.scrollLeft = mb.scrollLeft; // second time for IE (1/2 time first fails, other browsers ignore)
+ },
+
+ registerNode: function(n) {
+ Ext.ux.tree.TreeGrid.superclass.registerNode.call(this, n);
+ if (!n.uiProvider && !n.isRoot && !n.ui.isTreeGridNodeUI) {
+ n.ui = new Ext.ux.tree.TreeGridNodeUI(n);
+ }
+ },
+});
+
+Ext.reg('treegrid', Ext.ux.tree.TreeGrid);
diff --git a/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridColumnResizer.js b/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridColumnResizer.js
new file mode 100644
index 0000000..870172e
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridColumnResizer.js
@@ -0,0 +1,123 @@
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+/**
+ * @class Ext.tree.ColumnResizer
+ * @extends Ext.util.Observable
+ */
+Ext.tree.ColumnResizer = Ext.extend(Ext.util.Observable, {
+ /**
+ * @cfg {Number} minWidth The minimum width the column can be dragged to.
+ * Defaults to <tt>14</tt>.
+ */
+ minWidth: 14,
+
+ constructor: function(config) {
+ Ext.apply(this, config);
+ Ext.tree.ColumnResizer.superclass.constructor.call(this);
+ },
+
+ init: function(tree) {
+ this.tree = tree;
+ tree.on('render', this.initEvents, this);
+ },
+
+ initEvents: function(tree) {
+ tree.mon(tree.innerHd, 'mousemove', this.handleHdMove, this);
+ this.tracker = new Ext.dd.DragTracker({
+ onBeforeStart: this.onBeforeStart.createDelegate(this),
+ onStart: this.onStart.createDelegate(this),
+ onDrag: this.onDrag.createDelegate(this),
+ onEnd: this.onEnd.createDelegate(this),
+ tolerance: 3,
+ autoStart: 300,
+ });
+ this.tracker.initEl(tree.innerHd);
+ tree.on('beforedestroy', this.tracker.destroy, this.tracker);
+ },
+
+ handleHdMove: function(e, t) {
+ var hw = 5,
+ x = e.getPageX(),
+ hd = e.getTarget('.x-treegrid-hd', 3, true);
+
+ if (hd) {
+ var r = hd.getRegion(),
+ ss = hd.dom.style,
+ pn = hd.dom.parentNode;
+
+ if (x - r.left <= hw && hd.dom !== pn.firstChild) {
+ var ps = hd.dom.previousSibling;
+ while (ps && Ext.fly(ps).hasClass('x-treegrid-hd-hidden')) {
+ ps = ps.previousSibling;
+ }
+ if (ps) {
+ this.activeHd = Ext.get(ps);
+ ss.cursor = Ext.isWebKit ? 'e-resize' : 'col-resize';
+ }
+ } else if (r.right - x <= hw) {
+ var ns = hd.dom;
+ while (ns && Ext.fly(ns).hasClass('x-treegrid-hd-hidden')) {
+ ns = ns.previousSibling;
+ }
+ if (ns) {
+ this.activeHd = Ext.get(ns);
+ ss.cursor = Ext.isWebKit ? 'w-resize' : 'col-resize';
+ }
+ } else {
+ delete this.activeHd;
+ ss.cursor = '';
+ }
+ }
+ },
+
+ onBeforeStart: function(e) {
+ this.dragHd = this.activeHd;
+ return !!this.dragHd;
+ },
+
+ onStart: function(e) {
+ this.dragHeadersDisabled = this.tree.headersDisabled;
+ this.tree.headersDisabled = true;
+ this.proxy = this.tree.body.createChild({ cls: 'x-treegrid-resizer' });
+ this.proxy.setHeight(this.tree.body.getHeight());
+
+ var x = this.tracker.getXY()[0];
+
+ this.hdX = this.dragHd.getX();
+ this.hdIndex = this.tree.findHeaderIndex(this.dragHd);
+
+ this.proxy.setX(this.hdX);
+ this.proxy.setWidth(x - this.hdX);
+
+ this.maxWidth =
+ this.tree.outerCt.getWidth() -
+ this.tree.innerBody.translatePoints(this.hdX).left;
+ },
+
+ onDrag: function(e) {
+ var cursorX = this.tracker.getXY()[0];
+ this.proxy.setWidth(
+ (cursorX - this.hdX).constrain(this.minWidth, this.maxWidth)
+ );
+ },
+
+ onEnd: function(e) {
+ var nw = this.proxy.getWidth(),
+ tree = this.tree,
+ disabled = this.dragHeadersDisabled;
+
+ this.proxy.remove();
+ delete this.dragHd;
+
+ tree.columns[this.hdIndex].width = nw;
+ tree.updateColumnWidths();
+
+ setTimeout(function() {
+ tree.headersDisabled = disabled;
+ }, 100);
+ },
+});
diff --git a/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridColumns.js b/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridColumns.js
new file mode 100644
index 0000000..312bf21
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridColumns.js
@@ -0,0 +1,40 @@
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+(function() {
+ Ext.override(Ext.list.Column, {
+ init: function() {
+ var types = Ext.data.Types,
+ st = this.sortType;
+
+ if (this.type) {
+ if (Ext.isString(this.type)) {
+ this.type =
+ Ext.data.Types[this.type.toUpperCase()] || types.AUTO;
+ }
+ } else {
+ this.type = types.AUTO;
+ }
+
+ // named sortTypes are supported, here we look them up
+ if (Ext.isString(st)) {
+ this.sortType = Ext.data.SortTypes[st];
+ } else if (Ext.isEmpty(st)) {
+ this.sortType = this.type.sortType;
+ }
+ },
+ });
+
+ Ext.tree.Column = Ext.extend(Ext.list.Column, {});
+ Ext.tree.NumberColumn = Ext.extend(Ext.list.NumberColumn, {});
+ Ext.tree.DateColumn = Ext.extend(Ext.list.DateColumn, {});
+ Ext.tree.BooleanColumn = Ext.extend(Ext.list.BooleanColumn, {});
+
+ Ext.reg('tgcolumn', Ext.tree.Column);
+ Ext.reg('tgnumbercolumn', Ext.tree.NumberColumn);
+ Ext.reg('tgdatecolumn', Ext.tree.DateColumn);
+ Ext.reg('tgbooleancolumn', Ext.tree.BooleanColumn);
+})();
diff --git a/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridLoader.js b/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridLoader.js
new file mode 100644
index 0000000..eb5156a
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridLoader.js
@@ -0,0 +1,18 @@
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+/**
+ * @class Ext.ux.tree.TreeGridLoader
+ * @extends Ext.tree.TreeLoader
+ */
+Ext.ux.tree.TreeGridLoader = Ext.extend(Ext.tree.TreeLoader, {
+ createNode: function(attr) {
+ if (!attr.uiProvider) {
+ attr.uiProvider = Ext.ux.tree.TreeGridNodeUI;
+ }
+ return Ext.tree.TreeLoader.prototype.createNode.call(this, attr);
+ },
+});
diff --git a/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridNodeUI.js b/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridNodeUI.js
new file mode 100644
index 0000000..e58a801
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridNodeUI.js
@@ -0,0 +1,149 @@
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+/**
+ * @class Ext.ux.tree.TreeGridNodeUI
+ * @extends Ext.tree.TreeNodeUI
+ */
+Ext.ux.tree.TreeGridNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
+ isTreeGridNodeUI: true,
+
+ renderElements: function(n, a, targetNode, bulkRender) {
+ var t = n.getOwnerTree(),
+ cols = t.columns,
+ c = cols[0],
+ i,
+ buf,
+ len;
+
+ this.indentMarkup = n.parentNode
+ ? n.parentNode.ui.getChildIndent()
+ : '';
+
+ buf = [
+ '<tbody class="x-tree-node">',
+ '<tr ext:tree-node-id="',
+ n.id,
+ '" class="x-tree-node-el x-tree-node-leaf ',
+ a.cls,
+ '">',
+ '<td class="x-treegrid-col">',
+ '<span class="x-tree-node-indent">',
+ this.indentMarkup,
+ '</span>',
+ '<img src="',
+ this.emptyIcon,
+ '" class="x-tree-ec-icon x-tree-elbow" />',
+ '<img src="',
+ a.icon || this.emptyIcon,
+ '" class="x-tree-node-icon',
+ a.icon ? ' x-tree-node-inline-icon' : '',
+ a.iconCls ? ' ' + a.iconCls : '',
+ '" unselectable="on" />',
+ '<a hidefocus="on" class="x-tree-node-anchor" href="',
+ a.href ? a.href : '#',
+ '" tabIndex="1" ',
+ a.hrefTarget ? ' target="' + a.hrefTarget + '"' : '',
+ '>',
+ '<span unselectable="on">',
+ c.tpl ? c.tpl.apply(a) : a[c.dataIndex] || c.text,
+ '</span></a>',
+ '</td>',
+ ];
+
+ for (i = 1, len = cols.length; i < len; i++) {
+ c = cols[i];
+ buf.push(
+ '<td class="x-treegrid-col ',
+ c.cls ? c.cls : '',
+ '">',
+ '<div unselectable="on" class="x-treegrid-text"',
+ c.align ? ' style="text-align: ' + c.align + ';"' : '',
+ '>',
+ c.tpl ? c.tpl.apply(a) : a[c.dataIndex],
+ '</div>',
+ '</td>'
+ );
+ }
+
+ buf.push(
+ '</tr><tr class="x-tree-node-ct"><td colspan="',
+ cols.length,
+ '">',
+ '<table class="x-treegrid-node-ct-table" cellpadding="0" cellspacing="0" style="table-layout: fixed; display: none; width: ',
+ t.innerCt.getWidth(),
+ 'px;"><colgroup>'
+ );
+ for (i = 0, len = cols.length; i < len; i++) {
+ buf.push(
+ '<col style="width: ',
+ cols[i].hidden ? 0 : cols[i].width,
+ 'px;" />'
+ );
+ }
+ buf.push('</colgroup></table></td></tr></tbody>');
+
+ if (bulkRender !== true && n.nextSibling && n.nextSibling.ui.getEl()) {
+ this.wrap = Ext.DomHelper.insertHtml(
+ 'beforeBegin',
+ n.nextSibling.ui.getEl(),
+ buf.join('')
+ );
+ } else {
+ this.wrap = Ext.DomHelper.insertHtml(
+ 'beforeEnd',
+ targetNode,
+ buf.join('')
+ );
+ }
+
+ this.elNode = this.wrap.childNodes[0];
+ this.ctNode = this.wrap.childNodes[1].firstChild.firstChild;
+ var cs = this.elNode.firstChild.childNodes;
+ this.indentNode = cs[0];
+ this.ecNode = cs[1];
+ this.iconNode = cs[2];
+ this.anchor = cs[3];
+ this.textNode = cs[3].firstChild;
+ },
+
+ // private
+ animExpand: function(cb) {
+ this.ctNode.style.display = '';
+ Ext.ux.tree.TreeGridNodeUI.superclass.animExpand.call(this, cb);
+ },
+});
+
+Ext.ux.tree.TreeGridRootNodeUI = Ext.extend(Ext.tree.TreeNodeUI, {
+ isTreeGridNodeUI: true,
+
+ // private
+ render: function() {
+ if (!this.rendered) {
+ this.wrap = this.ctNode = this.node.ownerTree.innerCt.dom;
+ this.node.expanded = true;
+ }
+
+ if (Ext.isWebKit) {
+ // weird table-layout: fixed issue in webkit
+ var ct = this.ctNode;
+ ct.style.tableLayout = null;
+ (function() {
+ ct.style.tableLayout = 'fixed';
+ }.defer(1));
+ }
+ },
+
+ destroy: function() {
+ if (this.elNode) {
+ Ext.dd.Registry.unregister(this.elNode.id);
+ }
+ delete this.node;
+ },
+
+ collapse: Ext.emptyFn,
+ expand: Ext.emptyFn,
+});
diff --git a/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridNodeUIFix.js b/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridNodeUIFix.js
new file mode 100644
index 0000000..4c21bc3
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridNodeUIFix.js
@@ -0,0 +1,33 @@
+/**
+ * Ext.ux.tree.TreeGridNodeUIFix.js
+ *
+ * Copyright (c) Damien Churchill 2009-2010 <damoxc@gmail.com>
+ *
+ * This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+ * the additional special exception to link portions of this program with the OpenSSL library.
+ * See LICENSE for more details.
+ */
+
+Ext.override(Ext.ux.tree.TreeGridNodeUI, {
+ updateColumns: function() {
+ if (!this.rendered) return;
+
+ var a = this.node.attributes,
+ t = this.node.getOwnerTree(),
+ cols = t.columns,
+ c = cols[0];
+
+ // Update the first column
+ this.anchor.firstChild.innerHTML = c.tpl
+ ? c.tpl.apply(a)
+ : a[c.dataIndex] || c.text;
+
+ // Update the remaining columns
+ for (i = 1, len = cols.length; i < len; i++) {
+ c = cols[i];
+ this.elNode.childNodes[i].firstChild.innerHTML = c.tpl
+ ? c.tpl.apply(a)
+ : a[c.dataIndex] || c.text;
+ }
+ },
+});
diff --git a/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridRenderColumn.js b/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridRenderColumn.js
new file mode 100644
index 0000000..20bde8a
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridRenderColumn.js
@@ -0,0 +1,9 @@
+Ext.tree.RenderColumn = Ext.extend(Ext.tree.Column, {
+ constructor: function(c) {
+ c.tpl = c.tpl || new Ext.XTemplate('{' + c.dataIndex + ':this.format}');
+ c.tpl.format = c.renderer;
+ c.tpl.col = this;
+ Ext.tree.RenderColumn.superclass.constructor.call(this, c);
+ },
+});
+Ext.reg('tgrendercolumn', Ext.tree.RenderColumn);
diff --git a/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridSorter.js b/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridSorter.js
new file mode 100644
index 0000000..376f414
--- /dev/null
+++ b/deluge/ui/web/js/extjs/ext-extensions/tree/TreeGridSorter.js
@@ -0,0 +1,158 @@
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+Ext.ns('Ext.ux.tree');
+
+/**
+ * @class Ext.ux.tree.TreeGridSorter
+ * @extends Ext.tree.TreeSorter
+ * Provides sorting of nodes in a {@link Ext.ux.tree.TreeGrid}. The TreeGridSorter automatically monitors events on the
+ * associated TreeGrid that might affect the tree's sort order (beforechildrenrendered, append, insert and textchange).
+ * Example usage:<br />
+ * <pre><code>
+ new Ext.ux.tree.TreeGridSorter(myTreeGrid, {
+ folderSort: true,
+ dir: "desc",
+ sortType: function(node) {
+ // sort by a custom, typed attribute:
+ return parseInt(node.id, 10);
+ }
+ });
+ </code></pre>
+ * @constructor
+ * @param {TreeGrid} tree
+ * @param {Object} config
+ */
+Ext.ux.tree.TreeGridSorter = Ext.extend(Ext.tree.TreeSorter, {
+ /**
+ * @cfg {Array} sortClasses The CSS classes applied to a header when it is sorted. (defaults to <tt>['sort-asc', 'sort-desc']</tt>)
+ */
+ sortClasses: ['sort-asc', 'sort-desc'],
+ /**
+ * @cfg {String} sortAscText The text displayed in the 'Sort Ascending' menu item (defaults to <tt>'Sort Ascending'</tt>)
+ */
+ sortAscText: 'Sort Ascending',
+ /**
+ * @cfg {String} sortDescText The text displayed in the 'Sort Descending' menu item (defaults to <tt>'Sort Descending'</tt>)
+ */
+ sortDescText: 'Sort Descending',
+
+ constructor: function(tree, config) {
+ if (!Ext.isObject(config)) {
+ config = {
+ property: tree.columns[0].dataIndex || 'text',
+ folderSort: true,
+ };
+ }
+
+ Ext.ux.tree.TreeGridSorter.superclass.constructor.apply(
+ this,
+ arguments
+ );
+
+ this.tree = tree;
+ tree.on('headerclick', this.onHeaderClick, this);
+ tree.ddAppendOnly = true;
+
+ var me = this;
+ this.defaultSortFn = function(n1, n2) {
+ var desc = me.dir && me.dir.toLowerCase() == 'desc',
+ prop = me.property || 'text',
+ sortType = me.sortType,
+ caseSensitive = me.caseSensitive === true,
+ leafAttr = me.leafAttr || 'leaf',
+ attr1 = n1.attributes,
+ attr2 = n2.attributes;
+
+ if (me.folderSort) {
+ if (attr1[leafAttr] && !attr2[leafAttr]) {
+ return 1;
+ }
+ if (!attr1[leafAttr] && attr2[leafAttr]) {
+ return -1;
+ }
+ }
+ var prop1 = attr1[prop],
+ prop2 = attr2[prop],
+ v1 = sortType
+ ? sortType(prop1)
+ : caseSensitive
+ ? prop1
+ : prop1.toUpperCase();
+ v2 = sortType
+ ? sortType(prop2)
+ : caseSensitive
+ ? prop2
+ : prop2.toUpperCase();
+
+ if (v1 < v2) {
+ return desc ? +1 : -1;
+ } else if (v1 > v2) {
+ return desc ? -1 : +1;
+ } else {
+ return 0;
+ }
+ };
+
+ tree.on('afterrender', this.onAfterTreeRender, this, { single: true });
+ tree.on('headermenuclick', this.onHeaderMenuClick, this);
+ },
+
+ onAfterTreeRender: function() {
+ if (this.tree.hmenu) {
+ this.tree.hmenu.insert(
+ 0,
+ {
+ itemId: 'asc',
+ text: this.sortAscText,
+ cls: 'xg-hmenu-sort-asc',
+ },
+ {
+ itemId: 'desc',
+ text: this.sortDescText,
+ cls: 'xg-hmenu-sort-desc',
+ }
+ );
+ }
+ this.updateSortIcon(0, 'asc');
+ },
+
+ onHeaderMenuClick: function(c, id, index) {
+ if (id === 'asc' || id === 'desc') {
+ this.onHeaderClick(c, null, index);
+ return false;
+ }
+ },
+
+ onHeaderClick: function(c, el, i) {
+ if (c && !this.tree.headersDisabled) {
+ var me = this;
+
+ me.property = c.dataIndex;
+ me.dir = c.dir = c.dir === 'desc' ? 'asc' : 'desc';
+ me.sortType = c.sortType;
+ me.caseSensitive === Ext.isBoolean(c.caseSensitive)
+ ? c.caseSensitive
+ : this.caseSensitive;
+ me.sortFn = c.sortFn || this.defaultSortFn;
+
+ this.tree.root.cascade(function(n) {
+ if (!n.isLeaf()) {
+ me.updateSort(me.tree, n);
+ }
+ });
+
+ this.updateSortIcon(i, c.dir);
+ }
+ },
+
+ // private
+ updateSortIcon: function(col, dir) {
+ var sc = this.sortClasses,
+ hds = this.tree.innerHd.select('td').removeClass(sc);
+ hds.item(col).addClass(sc[dir == 'desc' ? 1 : 0]);
+ },
+});
diff --git a/deluge/ui/web/js/gettext.js b/deluge/ui/web/js/gettext.js
new file mode 100644
index 0000000..9cc1c4f
--- /dev/null
+++ b/deluge/ui/web/js/gettext.js
@@ -0,0 +1,322 @@
+GetText={maps:{},add:function(string,translation){this.maps[string]=translation},get:function(string){if (this.maps[string]){string=this.maps[string]} return string}};function _(string){return GetText.get(string)}GetText.add('10 KiB/s','${escape(_("10 KiB/s"))}')
+GetText.add('30 KiB/s','${escape(_("30 KiB/s"))}')
+GetText.add('300 KiB/s','${escape(_("300 KiB/s"))}')
+GetText.add('5 KiB/s','${escape(_("5 KiB/s"))}')
+GetText.add('80 KiB/s','${escape(_("80 KiB/s"))}')
+GetText.add('<b>IP</b> {0}','${escape(_("<b>IP</b> {0}"))}')
+GetText.add('About Deluge','${escape(_("About Deluge"))}')
+GetText.add('Active','${escape(_("Active"))}')
+GetText.add('Active Torrents','${escape(_("Active Torrents"))}')
+GetText.add('Add','${escape(_("Add"))}')
+GetText.add('Add Connection','${escape(_("Add Connection"))}')
+GetText.add('Add In Paused State','${escape(_("Add In Paused State"))}')
+GetText.add('Add Torrents','${escape(_("Add Torrents"))}')
+GetText.add('Add Tracker','${escape(_("Add Tracker"))}')
+GetText.add('Add from Url','${escape(_("Add from Url"))}')
+GetText.add('Add torrents in Paused state','${escape(_("Add torrents in Paused state"))}')
+GetText.add('Added','${escape(_("Added"))}')
+GetText.add('Address','${escape(_("Address"))}')
+GetText.add('All','${escape(_("All"))}')
+GetText.add('Allocating','${escape(_("Allocating"))}')
+GetText.add('Allow Remote Connections','${escape(_("Allow Remote Connections"))}')
+GetText.add('Allow the use of multiple filters at once','${escape(_("Allow the use of multiple filters at once"))}')
+GetText.add('Announce OK','${escape(_("Announce OK"))}')
+GetText.add('Announce Sent','${escape(_("Announce Sent"))}')
+GetText.add('Apply','${escape(_("Apply"))}')
+GetText.add('Author Email:','${escape(_("Author Email:"))}')
+GetText.add('Author:','${escape(_("Author:"))}')
+GetText.add('Auto Managed','${escape(_("Auto Managed"))}')
+GetText.add('Avail','${escape(_("Avail"))}')
+GetText.add('Back','${escape(_("Back"))}')
+GetText.add('Bandwidth','${escape(_("Bandwidth"))}')
+GetText.add('Be alerted about new releases','${escape(_("Be alerted about new releases"))}')
+GetText.add('Bottom','${escape(_("Bottom"))}')
+GetText.add('Browse','${escape(_("Browse"))}')
+GetText.add('Browse...','${escape(_("Browse..."))}')
+GetText.add('Cache','${escape(_("Cache"))}')
+GetText.add('Cache Expiry (seconds):','${escape(_("Cache Expiry (seconds):"))}')
+GetText.add('Cache Size (16 KiB Blocks):','${escape(_("Cache Size (16 KiB Blocks):"))}')
+GetText.add('Cancel','${escape(_("Cancel"))}')
+GetText.add('Certificate:','${escape(_("Certificate:"))}')
+GetText.add('Change Default Password','${escape(_("Change Default Password"))}')
+GetText.add('Change Successful','${escape(_("Change Successful"))}')
+GetText.add('Checking','${escape(_("Checking"))}')
+GetText.add('Client','${escape(_("Client"))}')
+GetText.add('Client:','${escape(_("Client:"))}')
+GetText.add('Close','${escape(_("Close"))}')
+GetText.add('Comment:','${escape(_("Comment:"))}')
+GetText.add('Complete Seen','${escape(_("Complete Seen"))}')
+GetText.add('Completed','${escape(_("Completed"))}')
+GetText.add('Confirm:','${escape(_("Confirm:"))}')
+GetText.add('Connect','${escape(_("Connect"))}')
+GetText.add('Connected','${escape(_("Connected"))}')
+GetText.add('Connection Limit','${escape(_("Connection Limit"))}')
+GetText.add('Connection Manager','${escape(_("Connection Manager"))}')
+GetText.add('Connection restored','${escape(_("Connection restored"))}')
+GetText.add('Connections','${escape(_("Connections"))}')
+GetText.add('Cookies','${escape(_("Cookies"))}')
+GetText.add('Copy of .torrent files to:','${escape(_("Copy of .torrent files to:"))}')
+GetText.add('Copyright 2007-2018 Deluge Team','${escape(_("Copyright 2007-2018 Deluge Team"))}')
+GetText.add('Create','${escape(_("Create"))}')
+GetText.add('Created By:','${escape(_("Created By:"))}')
+GetText.add('D/L Speed Limit','${escape(_("D/L Speed Limit"))}')
+GetText.add('DHT','${escape(_("DHT"))}')
+GetText.add('DHT Nodes','${escape(_("DHT Nodes"))}')
+GetText.add('Daemon','${escape(_("Daemon"))}')
+GetText.add('Daemon port:','${escape(_("Daemon port:"))}')
+GetText.add('Deluge','${escape(_("Deluge"))}')
+GetText.add('Details','${escape(_("Details"))}')
+GetText.add('Details:','${escape(_("Details:"))}')
+GetText.add('Disabled','${escape(_("Disabled"))}')
+GetText.add('Disconnect','${escape(_("Disconnect"))}')
+GetText.add('Down','${escape(_("Down"))}')
+GetText.add('Down Limit','${escape(_("Down Limit"))}')
+GetText.add('Down Speed','${escape(_("Down Speed"))}')
+GetText.add('Download','${escape(_("Download"))}')
+GetText.add('Download Folder','${escape(_("Download Folder"))}')
+GetText.add('Download Folder:','${escape(_("Download Folder:"))}')
+GetText.add('Download Speed','${escape(_("Download Speed"))}')
+GetText.add('Download to:','${escape(_("Download to:"))}')
+GetText.add('Downloaded','${escape(_("Downloaded"))}')
+GetText.add('Downloading','${escape(_("Downloading"))}')
+GetText.add('Downloading:','${escape(_("Downloading:"))}')
+GetText.add('Downloads','${escape(_("Downloads"))}')
+GetText.add('ETA','${escape(_("ETA"))}')
+GetText.add('Edit','${escape(_("Edit"))}')
+GetText.add('Edit Connection','${escape(_("Edit Connection"))}')
+GetText.add('Edit Tracker','${escape(_("Edit Tracker"))}')
+GetText.add('Edit Trackers','${escape(_("Edit Trackers"))}')
+GetText.add('Either','${escape(_("Either"))}')
+GetText.add('Enabled','${escape(_("Enabled"))}')
+GetText.add('Encryption','${escape(_("Encryption"))}')
+GetText.add('Error','${escape(_("Error"))}')
+GetText.add('Expand All','${escape(_("Expand All"))}')
+GetText.add('External IP Address','${escape(_("External IP Address"))}')
+GetText.add('File','${escape(_("File"))}')
+GetText.add('File Browser','${escape(_("File Browser"))}')
+GetText.add('Filename','${escape(_("Filename"))}')
+GetText.add('Files','${escape(_("Files"))}')
+GetText.add('Filters','${escape(_("Filters"))}')
+GetText.add('Find More','${escape(_("Find More"))}')
+GetText.add('Folders','${escape(_("Folders"))}')
+GetText.add('Force Proxy','${escape(_("Force Proxy"))}')
+GetText.add('Force Recheck','${escape(_("Force Recheck"))}')
+GetText.add('Force Use of Proxy','${escape(_("Force Use of Proxy"))}')
+GetText.add('Forced','${escape(_("Forced"))}')
+GetText.add('Forward','${escape(_("Forward"))}')
+GetText.add('Freespace in download folder','${escape(_("Freespace in download folder"))}')
+GetText.add('From:','${escape(_("From:"))}')
+GetText.add('Full Stream','${escape(_("Full Stream"))}')
+GetText.add('General','${escape(_("General"))}')
+GetText.add('GeoIP Database','${escape(_("GeoIP Database"))}')
+GetText.add('Global Bandwidth Usage','${escape(_("Global Bandwidth Usage"))}')
+GetText.add('HTTP','${escape(_("HTTP"))}')
+GetText.add('HTTP Auth','${escape(_("HTTP Auth"))}')
+GetText.add('Handshake','${escape(_("Handshake"))}')
+GetText.add('Hash:','${escape(_("Hash:"))}')
+GetText.add('Help','${escape(_("Help"))}')
+GetText.add('Hide Client Identity','${escape(_("Hide Client Identity"))}')
+GetText.add('High','${escape(_("High"))}')
+GetText.add('Home','${escape(_("Home"))}')
+GetText.add('Homepage:','${escape(_("Homepage:"))}')
+GetText.add('Host','${escape(_("Host"))}')
+GetText.add('Host:','${escape(_("Host:"))}')
+GetText.add('I2P','${escape(_("I2P"))}')
+GetText.add('Ignore limits on local network','${escape(_("Ignore limits on local network"))}')
+GetText.add('Ignore slow torrents','${escape(_("Ignore slow torrents"))}')
+GetText.add('Incoming Address','${escape(_("Incoming Address"))}')
+GetText.add('Incoming Port','${escape(_("Incoming Port"))}')
+GetText.add('Incoming:','${escape(_("Incoming:"))}')
+GetText.add('Infohash','${escape(_("Infohash"))}')
+GetText.add('Install','${escape(_("Install"))}')
+GetText.add('Install Plugin','${escape(_("Install Plugin"))}')
+GetText.add('Interface','${escape(_("Interface"))}')
+GetText.add('Invalid Password','${escape(_("Invalid Password"))}')
+GetText.add('KiB/s','${escape(_("KiB/s"))}')
+GetText.add('LSD','${escape(_("LSD"))}')
+GetText.add('Labels','${escape(_("Labels"))}')
+GetText.add('Language','${escape(_("Language"))}')
+GetText.add('Last Transfer','${escape(_("Last Transfer"))}')
+GetText.add('Level:','${escape(_("Level:"))}')
+GetText.add('Loading','${escape(_("Loading"))}')
+GetText.add('Login','${escape(_("Login"))}')
+GetText.add('Login Failed','${escape(_("Login Failed"))}')
+GetText.add('Logout','${escape(_("Logout"))}')
+GetText.add('Lost Connection','${escape(_("Lost Connection"))}')
+GetText.add('Lost connection to webserver','${escape(_("Lost connection to webserver"))}')
+GetText.add('Low','${escape(_("Low"))}')
+GetText.add('Max Connections','${escape(_("Max Connections"))}')
+GetText.add('Max Connections:','${escape(_("Max Connections:"))}')
+GetText.add('Max Down Speed','${escape(_("Max Down Speed"))}')
+GetText.add('Max Download Speed:','${escape(_("Max Download Speed:"))}')
+GetText.add('Max Up Speed','${escape(_("Max Up Speed"))}')
+GetText.add('Max Upload Slots','${escape(_("Max Upload Slots"))}')
+GetText.add('Max Upload Slots:','${escape(_("Max Upload Slots:"))}')
+GetText.add('Max Upload Speed:','${escape(_("Max Upload Speed:"))}')
+GetText.add('Maximum Connection Attempts per Second:','${escape(_("Maximum Connection Attempts per Second:"))}')
+GetText.add('Maximum Connections:','${escape(_("Maximum Connections:"))}')
+GetText.add('Maximum Download Speed (KiB/s):','${escape(_("Maximum Download Speed (KiB/s):"))}')
+GetText.add('Maximum Half-Open Connections:','${escape(_("Maximum Half-Open Connections:"))}')
+GetText.add('Maximum Upload Slots','${escape(_("Maximum Upload Slots"))}')
+GetText.add('Maximum Upload Slots:','${escape(_("Maximum Upload Slots:"))}')
+GetText.add('Maximum Upload Speed (KiB/s):','${escape(_("Maximum Upload Speed (KiB/s):"))}')
+GetText.add('Mixed','${escape(_("Mixed"))}')
+GetText.add('Move','${escape(_("Move"))}')
+GetText.add('Move Completed Folder','${escape(_("Move Completed Folder"))}')
+GetText.add('Move Completed:','${escape(_("Move Completed:"))}')
+GetText.add('Move Download Folder','${escape(_("Move Download Folder"))}')
+GetText.add('Move completed to:','${escape(_("Move completed to:"))}')
+GetText.add('NAT-PMP','${escape(_("NAT-PMP"))}')
+GetText.add('Name','${escape(_("Name"))}')
+GetText.add('Name:','${escape(_("Name:"))}')
+GetText.add('Network','${escape(_("Network"))}')
+GetText.add('Network Extras','${escape(_("Network Extras"))}')
+GetText.add('Never','${escape(_("Never"))}')
+GetText.add('New Torrents','${escape(_("New Torrents"))}')
+GetText.add('New:','${escape(_("New:"))}')
+GetText.add('None','${escape(_("None"))}')
+GetText.add('Normal','${escape(_("Normal"))}')
+GetText.add('Not Connected','${escape(_("Not Connected"))}')
+GetText.add('Not a valid torrent','${escape(_("Not a valid torrent"))}')
+GetText.add('OK','${escape(_("OK"))}')
+GetText.add('Off','${escape(_("Off"))}')
+GetText.add('Offline','${escape(_("Offline"))}')
+GetText.add('Old:','${escape(_("Old:"))}')
+GetText.add('On','${escape(_("On"))}')
+GetText.add('Online','${escape(_("Online"))}')
+GetText.add('Options','${escape(_("Options"))}')
+GetText.add('Other','${escape(_("Other"))}')
+GetText.add('Outgoing Interface','${escape(_("Outgoing Interface"))}')
+GetText.add('Outgoing Ports','${escape(_("Outgoing Ports"))}')
+GetText.add('Outgoing:','${escape(_("Outgoing:"))}')
+GetText.add('Owner','${escape(_("Owner"))}')
+GetText.add('Password','${escape(_("Password"))}')
+GetText.add('Password:','${escape(_("Password:"))}')
+GetText.add('Path:','${escape(_("Path:"))}')
+GetText.add('Pause','${escape(_("Pause"))}')
+GetText.add('Pause torrent','${escape(_("Pause torrent"))}')
+GetText.add('Paused','${escape(_("Paused"))}')
+GetText.add('Peer Exchange','${escape(_("Peer Exchange"))}')
+GetText.add('Peer TOS Byte:','${escape(_("Peer TOS Byte:"))}')
+GetText.add('Peers','${escape(_("Peers"))}')
+GetText.add('Per Torrent Bandwidth Usage','${escape(_("Per Torrent Bandwidth Usage"))}')
+GetText.add('Periodically check the website for new releases','${escape(_("Periodically check the website for new releases"))}')
+GetText.add('Plugin','${escape(_("Plugin"))}')
+GetText.add('Plugin Egg','${escape(_("Plugin Egg"))}')
+GetText.add('Plugins','${escape(_("Plugins"))}')
+GetText.add('Port','${escape(_("Port"))}')
+GetText.add('Port:','${escape(_("Port:"))}')
+GetText.add('Pre-allocate disk space','${escape(_("Pre-allocate disk space"))}')
+GetText.add('Preallocate Disk Space','${escape(_("Preallocate Disk Space"))}')
+GetText.add('Prefer seeding torrents','${escape(_("Prefer seeding torrents"))}')
+GetText.add('Preferences','${escape(_("Preferences"))}')
+GetText.add('Prioritize First/Last','${escape(_("Prioritize First/Last"))}')
+GetText.add('Prioritize First/Last Pieces','${escape(_("Prioritize First/Last Pieces"))}')
+GetText.add('Prioritize first and last pieces of torrent','${escape(_("Prioritize first and last pieces of torrent"))}')
+GetText.add('Priority','${escape(_("Priority"))}')
+GetText.add('Private','${escape(_("Private"))}')
+GetText.add('Private Key:','${escape(_("Private Key:"))}')
+GetText.add('Progress','${escape(_("Progress"))}')
+GetText.add('Protocol Traffic Download/Upload','${escape(_("Protocol Traffic Download/Upload"))}')
+GetText.add('Proxy','${escape(_("Proxy"))}')
+GetText.add('Proxy Hostnames','${escape(_("Proxy Hostnames"))}')
+GetText.add('Proxy Peers','${escape(_("Proxy Peers"))}')
+GetText.add('Proxy Trackers','${escape(_("Proxy Trackers"))}')
+GetText.add('Public','${escape(_("Public"))}')
+GetText.add('Queue','${escape(_("Queue"))}')
+GetText.add('Queue to top','${escape(_("Queue to top"))}')
+GetText.add('Queued','${escape(_("Queued"))}')
+GetText.add('Rate limit IP overhead','${escape(_("Rate limit IP overhead"))}')
+GetText.add('Ratio','${escape(_("Ratio"))}')
+GetText.add('Refresh','${escape(_("Refresh"))}')
+GetText.add('Remaining','${escape(_("Remaining"))}')
+GetText.add('Remove','${escape(_("Remove"))}')
+GetText.add('Remove Torrent','${escape(_("Remove Torrent"))}')
+GetText.add('Remove With Data','${escape(_("Remove With Data"))}')
+GetText.add('Remove at ratio','${escape(_("Remove at ratio"))}')
+GetText.add('Remove torrent','${escape(_("Remove torrent"))}')
+GetText.add('Resume','${escape(_("Resume"))}')
+GetText.add('Save','${escape(_("Save"))}')
+GetText.add('Seeding','${escape(_("Seeding"))}')
+GetText.add('Seeding Rotation','${escape(_("Seeding Rotation"))}')
+GetText.add('Seeding:','${escape(_("Seeding:"))}')
+GetText.add('Seeds','${escape(_("Seeds"))}')
+GetText.add('Seeds:Peers','${escape(_("Seeds:Peers"))}')
+GetText.add('Select an egg','${escape(_("Select an egg"))}')
+GetText.add('Sequential Download','${escape(_("Sequential Download"))}')
+GetText.add('Sequential download','${escape(_("Sequential download"))}')
+GetText.add('Server','${escape(_("Server"))}')
+GetText.add('Server:','${escape(_("Server:"))}')
+GetText.add('Session Timeout:','${escape(_("Session Timeout:"))}')
+GetText.add('Set Maximum Connections','${escape(_("Set Maximum Connections"))}')
+GetText.add('Set Maximum Download Speed','${escape(_("Set Maximum Download Speed"))}')
+GetText.add('Set Maximum Upload Speed','${escape(_("Set Maximum Upload Speed"))}')
+GetText.add('Settings','${escape(_("Settings"))}')
+GetText.add('Share Ratio Reached','${escape(_("Share Ratio Reached"))}')
+GetText.add('Share Ratio:','${escape(_("Share Ratio:"))}')
+GetText.add('Shared','${escape(_("Shared"))}')
+GetText.add('Show filters with zero torrents','${escape(_("Show filters with zero torrents"))}')
+GetText.add('Show session speed in titlebar','${escape(_("Show session speed in titlebar"))}')
+GetText.add('Size','${escape(_("Size"))}')
+GetText.add('Skip','${escape(_("Skip"))}')
+GetText.add('Skip File Hash Check','${escape(_("Skip File Hash Check"))}')
+GetText.add('Socks4','${escape(_("Socks4"))}')
+GetText.add('Socks5','${escape(_("Socks5"))}')
+GetText.add('Socks5 Auth','${escape(_("Socks5 Auth"))}')
+GetText.add('Start Daemon','${escape(_("Start Daemon"))}')
+GetText.add('State','${escape(_("State"))}')
+GetText.add('States','${escape(_("States"))}')
+GetText.add('Status','${escape(_("Status"))}')
+GetText.add('Status:','${escape(_("Status:"))}')
+GetText.add('Stop Daemon','${escape(_("Stop Daemon"))}')
+GetText.add('Stop seed at ratio:','${escape(_("Stop seed at ratio:"))}')
+GetText.add('Super Seed','${escape(_("Super Seed"))}')
+GetText.add('Super Seeding','${escape(_("Super Seeding"))}')
+GetText.add('System Default','${escape(_("System Default"))}')
+GetText.add('System Information','${escape(_("System Information"))}')
+GetText.add('The connection to the webserver has been lost!','${escape(_("The connection to the webserver has been lost!"))}')
+GetText.add('Tier','${escape(_("Tier"))}')
+GetText.add('Time (m):','${escape(_("Time (m):"))}')
+GetText.add('Time Ratio:','${escape(_("Time Ratio:"))}')
+GetText.add('To:','${escape(_("To:"))}')
+GetText.add('Top','${escape(_("Top"))}')
+GetText.add('Total Files:','${escape(_("Total Files:"))}')
+GetText.add('Total Size:','${escape(_("Total Size:"))}')
+GetText.add('Total:','${escape(_("Total:"))}')
+GetText.add('Tracker','${escape(_("Tracker"))}')
+GetText.add('Tracker Host','${escape(_("Tracker Host"))}')
+GetText.add('Tracker:','${escape(_("Tracker:"))}')
+GetText.add('Trackers','${escape(_("Trackers"))}')
+GetText.add('Trackers:','${escape(_("Trackers:"))}')
+GetText.add('Type Of Service','${escape(_("Type Of Service"))}')
+GetText.add('Type:','${escape(_("Type:"))}')
+GetText.add('U/L Speed Limit','${escape(_("U/L Speed Limit"))}')
+GetText.add('UPnP','${escape(_("UPnP"))}')
+GetText.add('Unable to add host: {0}','${escape(_("Unable to add host: {0}"))}')
+GetText.add('Unable to edit host','${escape(_("Unable to edit host"))}')
+GetText.add('Unlimited','${escape(_("Unlimited"))}')
+GetText.add('Up','${escape(_("Up"))}')
+GetText.add('Up Limit','${escape(_("Up Limit"))}')
+GetText.add('Up Speed','${escape(_("Up Speed"))}')
+GetText.add('Update Tracker','${escape(_("Update Tracker"))}')
+GetText.add('Updates','${escape(_("Updates"))}')
+GetText.add('Upload Slot Limit','${escape(_("Upload Slot Limit"))}')
+GetText.add('Upload Speed','${escape(_("Upload Speed"))}')
+GetText.add('Uploaded','${escape(_("Uploaded"))}')
+GetText.add('Uploading your plugin...','${escape(_("Uploading your plugin..."))}')
+GetText.add('Uploading your torrent...','${escape(_("Uploading your torrent..."))}')
+GetText.add('Url','${escape(_("Url"))}')
+GetText.add('Use Random Port','${escape(_("Use Random Port"))}')
+GetText.add('Use Random Ports','${escape(_("Use Random Ports"))}')
+GetText.add('Username:','${escape(_("Username:"))}')
+GetText.add('Version','${escape(_("Version"))}')
+GetText.add('Version:','${escape(_("Version:"))}')
+GetText.add('Warning','${escape(_("Warning"))}')
+GetText.add('WebUI Language Changed','${escape(_("WebUI Language Changed"))}')
+GetText.add('WebUI Password','${escape(_("WebUI Password"))}')
+GetText.add('Yes, please send anonymous statistics','${escape(_("Yes, please send anonymous statistics"))}')
+GetText.add('You entered an incorrect password','${escape(_("You entered an incorrect password"))}')
+GetText.add('Your old password was incorrect!','${escape(_("Your old password was incorrect!"))}')
+GetText.add('Your password was successfully changed!','${escape(_("Your password was successfully changed!"))}')
+GetText.add('libtorrent:','${escape(_("libtorrent:"))}')
+GetText.add('n/a','${escape(_("n/a"))}')
diff --git a/deluge/ui/web/json_api.py b/deluge/ui/web/json_api.py
new file mode 100644
index 0000000..bfacb58
--- /dev/null
+++ b/deluge/ui/web/json_api.py
@@ -0,0 +1,1019 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009-2010 Damien Churchill <damoxc@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import division, unicode_literals
+
+import json
+import logging
+import os
+import shutil
+import tempfile
+from base64 import b64encode
+from types import FunctionType
+from xml.sax.saxutils import escape as xml_escape
+
+from twisted.internet import defer, reactor
+from twisted.internet.defer import Deferred, DeferredList
+from twisted.web import http, resource, server
+
+from deluge import component, httpdownloader
+from deluge.common import AUTH_LEVEL_DEFAULT, get_magnet_info, is_magnet
+from deluge.configmanager import get_config_dir
+from deluge.error import NotAuthorizedError
+from deluge.i18n import get_languages
+from deluge.ui.client import Client, client
+from deluge.ui.common import FileTree2, TorrentInfo
+from deluge.ui.coreconfig import CoreConfig
+from deluge.ui.hostlist import HostList
+from deluge.ui.sessionproxy import SessionProxy
+from deluge.ui.web.common import _
+
+log = logging.getLogger(__name__)
+
+
+class JSONComponent(component.Component):
+ def __init__(self, name, interval=1, depend=None):
+ super(JSONComponent, self).__init__(name, interval, depend)
+ self._json = component.get('JSON')
+ self._json.register_object(self, name)
+
+
+def export(auth_level=AUTH_LEVEL_DEFAULT):
+ """
+ Decorator function to register an object's method as a RPC. The object
+ will need to be registered with a `:class:JSON` to be effective.
+
+ :param func: the function to export
+ :type func: function
+ :param auth_level: the auth level required to call this method
+ :type auth_level: int
+
+ """
+
+ def wrap(func, *args, **kwargs):
+ func._json_export = True
+ func._json_auth_level = auth_level
+ return func
+
+ if isinstance(auth_level, FunctionType):
+ func = auth_level
+ auth_level = AUTH_LEVEL_DEFAULT
+ return wrap(func)
+ else:
+ return wrap
+
+
+class JSONException(Exception):
+ def __init__(self, inner_exception):
+ self.inner_exception = inner_exception
+ Exception.__init__(self, str(inner_exception))
+
+
+class JSON(resource.Resource, component.Component):
+ """
+ A Twisted Web resource that exposes a JSON-RPC interface for web clients \
+ to use.
+ """
+
+ def __init__(self):
+ resource.Resource.__init__(self)
+ component.Component.__init__(self, 'JSON')
+ self._remote_methods = []
+ self._local_methods = {}
+ if client.is_standalone():
+ self.get_remote_methods()
+
+ def get_remote_methods(self, result=None):
+ """
+ Updates remote methods from the daemon.
+
+ Returns:
+ t.i.d.Deferred: A deferred returning the available remote methods
+ """
+
+ def on_get_methods(methods):
+ self._remote_methods = methods
+ return methods
+
+ return client.daemon.get_method_list().addCallback(on_get_methods)
+
+ def _exec_local(self, method, params, request):
+ """
+ Handles executing all local methods.
+ """
+ if method == 'system.listMethods':
+ d = Deferred()
+ methods = list(self._remote_methods)
+ methods.extend(self._local_methods)
+ d.callback(methods)
+ return d
+ elif method in self._local_methods:
+ # This will eventually process methods that the server adds
+ # and any plugins.
+ meth = self._local_methods[method]
+ meth.__globals__['__request__'] = request
+ component.get('Auth').check_request(request, meth)
+ return meth(*params)
+ raise JSONException('Unknown system method')
+
+ def _exec_remote(self, method, params, request):
+ """
+ Executes methods using the Deluge client.
+ """
+ component.get('Auth').check_request(request, level=AUTH_LEVEL_DEFAULT)
+ core_component, method = method.split('.')
+ return getattr(getattr(client, core_component), method)(*params)
+
+ def _handle_request(self, request):
+ """
+ Takes some json data as a string and attempts to decode it, and process
+ the rpc object that should be contained, returning a deferred for all
+ procedure calls and the request id.
+ """
+ try:
+ request_data = json.loads(request.json.decode())
+ except (ValueError, TypeError):
+ raise JSONException('JSON not decodable')
+
+ try:
+ method = request_data['method']
+ params = request_data['params']
+ request_id = request_data['id']
+ except KeyError as ex:
+ message = 'Invalid JSON request, missing param %s in %s' % (
+ ex,
+ request_data,
+ )
+ raise JSONException(message)
+
+ result = None
+ error = None
+
+ try:
+ if method.startswith('system.') or method in self._local_methods:
+ result = self._exec_local(method, params, request)
+ elif method in self._remote_methods:
+ result = self._exec_remote(method, params, request)
+ else:
+ error = {'message': 'Unknown method', 'code': 2}
+ except NotAuthorizedError:
+ error = {'message': 'Not authenticated', 'code': 1}
+ except Exception as ex:
+ log.error('Error calling method `%s`: %s', method, ex)
+ log.exception(ex)
+ error = {'message': '%s: %s' % (ex.__class__.__name__, str(ex)), 'code': 3}
+
+ return request_id, result, error
+
+ def _on_rpc_request_finished(self, result, response, request):
+ """
+ Sends the response of any rpc calls back to the json-rpc client.
+ """
+ response['result'] = result
+ return self._send_response(request, response)
+
+ def _on_rpc_request_failed(self, reason, response, request):
+ """
+ Handles any failures that occurred while making an rpc call.
+ """
+ log.error(reason)
+ response['error'] = {
+ 'message': '%s: %s' % (reason.__class__.__name__, str(reason)),
+ 'code': 4,
+ }
+ return self._send_response(request, response)
+
+ def _on_json_request(self, request):
+ """
+ Handler to take the json data as a string and pass it on to the
+ _handle_request method for further processing.
+ """
+ content_type = request.getHeader(b'content-type').decode()
+ if content_type != 'application/json':
+ message = 'Invalid JSON request content-type: %s' % content_type
+ raise JSONException(message)
+
+ log.debug('json-request: %s', request.json)
+ response = {'result': None, 'error': None, 'id': None}
+ response['id'], d, response['error'] = self._handle_request(request)
+
+ if isinstance(d, Deferred):
+ d.addCallback(self._on_rpc_request_finished, response, request)
+ d.addErrback(self._on_rpc_request_failed, response, request)
+ return d
+ else:
+ response['result'] = d
+ return self._send_response(request, response)
+
+ def _on_json_request_failed(self, reason, request):
+ """
+ Returns the error in json response.
+ """
+ log.error(reason)
+ response = {
+ 'result': None,
+ 'id': None,
+ 'error': {
+ 'code': 5,
+ 'message': '%s: %s' % (reason.__class__.__name__, str(reason)),
+ },
+ }
+ return self._send_response(request, response)
+
+ def _send_response(self, request, response):
+ if request._disconnected:
+ return ''
+ response = json.dumps(response)
+ request.setHeader(b'content-type', b'application/json')
+ request.write(response.encode())
+ request.finish()
+ return server.NOT_DONE_YET
+
+ def render(self, request):
+ """
+ Handles all the POST requests made to the /json controller.
+ """
+ if request.method != b'POST':
+ request.setResponseCode(http.NOT_ALLOWED)
+ request.finish()
+ return server.NOT_DONE_YET
+
+ try:
+ request.content.seek(0)
+ request.json = request.content.read()
+ self._on_json_request(request)
+ return server.NOT_DONE_YET
+ except Exception as ex:
+ return self._on_json_request_failed(ex, request)
+
+ def register_object(self, obj, name=None):
+ """Registers an object to export it's rpc methods.
+
+ These methods should be exported with the export decorator prior
+ to registering the object.
+
+ Args:
+ obj (object): The object that we want to export.
+ name (str): The name to use. If None, uses the object class name.
+
+ """
+ name = name or obj.__class__.__name__
+ name = name.lower()
+
+ for d in dir(obj):
+ if d[0] == '_':
+ continue
+ if getattr(getattr(obj, d), '_json_export', False):
+ log.debug('Registering method: %s', name + '.' + d)
+ self._local_methods[name + '.' + d] = getattr(obj, d)
+
+ def deregister_object(self, obj):
+ """Deregisters an objects exported rpc methods.
+
+ Args:
+ obj (object): The object that was previously registered.
+
+ """
+ for key, value in self._local_methods.items():
+ if value.__self__ == obj:
+ del self._local_methods[key]
+
+
+FILES_KEYS = ['files', 'file_progress', 'file_priorities']
+
+
+class EventQueue(object):
+ """
+ This class subscribes to events from the core and stores them until all
+ the subscribed listeners have received the events.
+ """
+
+ def __init__(self):
+ self.__events = {}
+ self.__handlers = {}
+ self.__queue = {}
+ self.__requests = {}
+
+ def add_listener(self, listener_id, event):
+ """
+ Add a listener to the event queue.
+
+ :param listener_id: A unique id for the listener
+ :type listener_id: string
+ :param event: The event name
+ :type event: string
+ """
+ if event not in self.__events:
+
+ def on_event(*args):
+ for listener in self.__events[event]:
+ if listener not in self.__queue:
+ self.__queue[listener] = []
+ self.__queue[listener].append((event, args))
+
+ client.register_event_handler(event, on_event)
+ self.__handlers[event] = on_event
+ self.__events[event] = [listener_id]
+ elif listener_id not in self.__events[event]:
+ self.__events[event].append(listener_id)
+
+ def get_events(self, listener_id):
+ """
+ Retrieve the pending events for the listener.
+
+ :param listener_id: A unique id for the listener
+ :type listener_id: string
+ """
+
+ # Check to see if we have anything to return immediately
+ if listener_id in self.__queue:
+ queue = self.__queue[listener_id]
+ del self.__queue[listener_id]
+ return queue
+
+ # Create a deferred to and check again in 100ms
+ d = Deferred()
+ reactor.callLater(0.1, self._get_events, listener_id, 0, d)
+ return d
+
+ def _get_events(self, listener_id, count, d):
+ if listener_id in self.__queue:
+ queue = self.__queue[listener_id]
+ del self.__queue[listener_id]
+ d.callback(queue)
+ else:
+ # Prevent this loop going on indefinitely incase a client leaves
+ # the page or disconnects uncleanly.
+ if count >= 50:
+ d.callback(None)
+ else:
+ reactor.callLater(0.1, self._get_events, listener_id, count + 1, d)
+
+ def remove_listener(self, listener_id, event):
+ """
+ Remove a listener from the event queue.
+
+ :param listener_id: The unique id for the listener
+ :type listener_id: string
+ :param event: The event name
+ :type event: string
+ """
+ self.__events[event].remove(listener_id)
+ if not self.__events[event]:
+ client.deregister_event_handler(event, self.__handlers[event])
+ del self.__events[event]
+ del self.__handlers[event]
+
+
+class WebApi(JSONComponent):
+ """
+ The component that implements all the methods required for managing
+ the web interface. The complete web json interface also exposes all the
+ methods available from the core RPC.
+ """
+
+ XSS_VULN_KEYS = ['name', 'message', 'comment', 'tracker_status', 'peers']
+
+ def __init__(self):
+ super(WebApi, self).__init__('Web', depend=['SessionProxy'])
+ self.hostlist = HostList()
+ self.core_config = CoreConfig()
+ self.event_queue = EventQueue()
+ try:
+ self.sessionproxy = component.get('SessionProxy')
+ except KeyError:
+ self.sessionproxy = SessionProxy()
+
+ def disable(self):
+ client.deregister_event_handler(
+ 'PluginEnabledEvent', self._json.get_remote_methods
+ )
+ client.deregister_event_handler(
+ 'PluginDisabledEvent', self._json.get_remote_methods
+ )
+
+ if client.is_standalone():
+ component.get('Web.PluginManager').stop()
+ else:
+ client.disconnect()
+ client.set_disconnect_callback(None)
+
+ def enable(self):
+ client.register_event_handler(
+ 'PluginEnabledEvent', self._json.get_remote_methods
+ )
+ client.register_event_handler(
+ 'PluginDisabledEvent', self._json.get_remote_methods
+ )
+
+ if client.is_standalone():
+ component.get('Web.PluginManager').start()
+ else:
+ client.set_disconnect_callback(self._on_client_disconnect)
+ default_host_id = component.get('DelugeWeb').config['default_daemon']
+ if default_host_id:
+ return self.connect(default_host_id)
+
+ return defer.succeed(True)
+
+ def _on_client_connect(self, *args):
+ """Handles client successfully connecting to the daemon.
+
+ Invokes retrieving the method names and starts webapi and plugins.
+
+ """
+ d_methods = self._json.get_remote_methods()
+ component.get('Web.PluginManager').start()
+ self.start()
+ return d_methods
+
+ def _on_client_connect_fail(self, result, host_id):
+ log.error(
+ 'Unable to connect to daemon, check host_id "%s" is correct.', host_id
+ )
+
+ def _on_client_disconnect(self, *args):
+ component.get('Web.PluginManager').stop()
+ return self.stop()
+
+ def start(self):
+ self.core_config.start()
+ return self.sessionproxy.start()
+
+ def stop(self):
+ self.core_config.stop()
+ self.sessionproxy.stop()
+ return defer.succeed(True)
+
+ @export
+ def connect(self, host_id):
+ """Connect the web client to a daemon.
+
+ Args:
+ host_id (str): The id of the daemon in the host list.
+
+ Returns:
+ Deferred: List of methods the daemon supports.
+ """
+ d = self.hostlist.connect_host(host_id)
+ d.addCallback(self._on_client_connect)
+ d.addErrback(self._on_client_connect_fail, host_id)
+ return d
+
+ @export
+ def connected(self):
+ """
+ The current connection state.
+
+ :returns: True if the client is connected
+ :rtype: booleon
+ """
+ return client.connected()
+
+ @export
+ def disconnect(self):
+ """
+ Disconnect the web interface from the connected daemon.
+ """
+ d = client.disconnect()
+
+ def on_disconnect(reason):
+ return str(reason)
+
+ d.addCallback(on_disconnect)
+ return d
+
+ @export
+ def update_ui(self, keys, filter_dict):
+ """
+ Gather the information required for updating the web interface.
+
+ :param keys: the information about the torrents to gather
+ :type keys: list
+ :param filter_dict: the filters to apply when selecting torrents.
+ :type filter_dict: dictionary
+ :returns: The torrent and ui information.
+ :rtype: dictionary
+ """
+ d = Deferred()
+ ui_info = {
+ 'connected': client.connected(),
+ 'torrents': None,
+ 'filters': None,
+ 'stats': {
+ 'max_download': self.core_config.get('max_download_speed'),
+ 'max_upload': self.core_config.get('max_upload_speed'),
+ 'max_num_connections': self.core_config.get('max_connections_global'),
+ },
+ }
+
+ if not client.connected():
+ d.callback(ui_info)
+ return d
+
+ def got_stats(stats):
+ ui_info['stats']['num_connections'] = stats['num_peers']
+ ui_info['stats']['upload_rate'] = stats['payload_upload_rate']
+ ui_info['stats']['download_rate'] = stats['payload_download_rate']
+ ui_info['stats']['download_protocol_rate'] = (
+ stats['download_rate'] - stats['payload_download_rate']
+ )
+ ui_info['stats']['upload_protocol_rate'] = (
+ stats['upload_rate'] - stats['payload_upload_rate']
+ )
+ ui_info['stats']['dht_nodes'] = stats['dht_nodes']
+ ui_info['stats']['has_incoming_connections'] = stats[
+ 'has_incoming_connections'
+ ]
+
+ def got_filters(filters):
+ ui_info['filters'] = filters
+
+ def got_free_space(free_space):
+ ui_info['stats']['free_space'] = free_space
+
+ def got_external_ip(external_ip):
+ ui_info['stats']['external_ip'] = external_ip
+
+ def got_torrents(torrents):
+ ui_info['torrents'] = torrents
+
+ def on_complete(result):
+ d.callback(ui_info)
+
+ d1 = component.get('SessionProxy').get_torrents_status(filter_dict, keys)
+ d1.addCallback(got_torrents)
+
+ d2 = client.core.get_filter_tree()
+ d2.addCallback(got_filters)
+
+ d3 = client.core.get_session_status(
+ [
+ 'num_peers',
+ 'payload_download_rate',
+ 'payload_upload_rate',
+ 'download_rate',
+ 'upload_rate',
+ 'dht_nodes',
+ 'has_incoming_connections',
+ ]
+ )
+ d3.addCallback(got_stats)
+
+ d4 = client.core.get_free_space(self.core_config.get('download_location'))
+ d4.addCallback(got_free_space)
+
+ d5 = client.core.get_external_ip()
+ d5.addCallback(got_external_ip)
+
+ dl = DeferredList([d1, d2, d3, d4, d5], consumeErrors=True)
+ dl.addCallback(on_complete)
+ return d
+
+ def _on_got_files(self, torrent, d):
+ files = torrent.get('files')
+ file_progress = torrent.get('file_progress')
+ file_priorities = torrent.get('file_priorities')
+
+ paths = []
+ info = {}
+ for index, torrent_file in enumerate(files):
+ path = xml_escape(torrent_file['path'])
+ paths.append(path)
+ torrent_file['progress'] = file_progress[index]
+ torrent_file['priority'] = file_priorities[index]
+ torrent_file['index'] = index
+ torrent_file['path'] = path
+ info[path] = torrent_file
+
+ # update the directory info
+ dirname = os.path.dirname(path)
+ while dirname:
+ dirinfo = info.setdefault(dirname, {})
+ dirinfo['size'] = dirinfo.get('size', 0) + torrent_file['size']
+ if 'priority' not in dirinfo:
+ dirinfo['priority'] = torrent_file['priority']
+ else:
+ if dirinfo['priority'] != torrent_file['priority']:
+ dirinfo['priority'] = 9
+
+ progresses = dirinfo.setdefault('progresses', [])
+ progresses.append(torrent_file['size'] * torrent_file['progress'] / 100)
+ dirinfo['progress'] = sum(progresses) / dirinfo['size'] * 100
+ dirinfo['path'] = dirname
+ dirname = os.path.dirname(dirname)
+
+ def walk(path, item):
+ if item['type'] == 'dir':
+ item.update(info[path])
+ return item
+ else:
+ item.update(info[path])
+ return item
+
+ file_tree = FileTree2(paths)
+ file_tree.walk(walk)
+ d.callback(file_tree.get_tree())
+
+ def _on_torrent_status(self, torrent, d):
+ for key in self.XSS_VULN_KEYS:
+ try:
+ if key == 'peers':
+ for peer in torrent[key]:
+ peer['client'] = xml_escape(peer['client'])
+ else:
+ torrent[key] = xml_escape(torrent[key])
+ except KeyError:
+ pass
+ d.callback(torrent)
+
+ @export
+ def get_torrent_status(self, torrent_id, keys):
+ """Get the status for a torrent, filtered by status keys."""
+ main_deferred = Deferred()
+ d = component.get('SessionProxy').get_torrent_status(torrent_id, keys)
+ d.addCallback(self._on_torrent_status, main_deferred)
+ return main_deferred
+
+ @export
+ def get_torrent_files(self, torrent_id):
+ """
+ Gets the files for a torrent in tree format
+
+ :param torrent_id: the id of the torrent to retrieve.
+ :type torrent_id: string
+ :returns: The torrents files in a tree
+ :rtype: dictionary
+ """
+ main_deferred = Deferred()
+ d = component.get('SessionProxy').get_torrent_status(torrent_id, FILES_KEYS)
+ d.addCallback(self._on_got_files, main_deferred)
+ return main_deferred
+
+ @export
+ def download_torrent_from_url(self, url, cookie=None):
+ """
+ Download a torrent file from a url to a temporary directory.
+
+ :param url: the url of the torrent
+ :type url: string
+ :returns: the temporary file name of the torrent file
+ :rtype: string
+ """
+
+ def on_download_success(result):
+ log.debug('Successfully downloaded %s to %s', url, result)
+ return result
+
+ def on_download_fail(result):
+ log.error('Failed to add torrent from url %s', url)
+ return result
+
+ tempdir = tempfile.mkdtemp(prefix='delugeweb-')
+ tmp_file = os.path.join(tempdir, url.split('/')[-1])
+ log.debug('filename: %s', tmp_file)
+ headers = {}
+ if cookie:
+ headers['Cookie'] = cookie
+ log.debug('cookie: %s', cookie)
+ d = httpdownloader.download_file(url, tmp_file, headers=headers)
+ d.addCallbacks(on_download_success, on_download_fail)
+ return d
+
+ @export
+ def get_torrent_info(self, filename):
+ """
+ Return information about a torrent on the filesystem.
+
+ :param filename: the path to the torrent
+ :type filename: string
+
+ :returns: information about the torrent:
+
+ ::
+
+ {
+ "name": the torrent name,
+ "files_tree": the files the torrent contains,
+ "info_hash" the torrents info_hash
+ }
+
+ :rtype: dictionary
+ """
+ try:
+ torrent_info = TorrentInfo(filename.strip(), 2)
+ return torrent_info.as_dict('name', 'info_hash', 'files_tree')
+ except Exception as ex:
+ log.error(ex)
+ return False
+
+ @export
+ def get_magnet_info(self, uri):
+ """Parse a magnet URI for hash and name."""
+ return get_magnet_info(uri)
+
+ @export
+ def add_torrents(self, torrents):
+ """
+ Add torrents by file
+
+ :param torrents: A list of dictionaries containing the torrent \
+ path and torrent options to add with.
+ :type torrents: list
+
+ ::
+
+ json_api.web.add_torrents([{
+ "path": "/tmp/deluge-web/some-torrent-file.torrent",
+ "options": {"download_location": "/home/deluge/"}
+ }])
+
+ """
+ deferreds = []
+
+ for torrent in torrents:
+ if is_magnet(torrent['path']):
+ log.info(
+ 'Adding torrent from magnet uri `%s` with options `%r`',
+ torrent['path'],
+ torrent['options'],
+ )
+ d = client.core.add_torrent_magnet(torrent['path'], torrent['options'])
+ deferreds.append(d)
+ else:
+ filename = os.path.basename(torrent['path'])
+ with open(torrent['path'], 'rb') as _file:
+ fdump = b64encode(_file.read())
+ log.info(
+ 'Adding torrent from file `%s` with options `%r`',
+ filename,
+ torrent['options'],
+ )
+ d = client.core.add_torrent_file_async(
+ filename, fdump, torrent['options']
+ )
+ deferreds.append(d)
+ return DeferredList(deferreds, consumeErrors=False)
+
+ def _get_host(self, host_id):
+ """Information about a host from supplied host id.
+
+ Args:
+ host_id (str): The id of the host.
+
+ Returns:
+ list: The host information, empty list if not found.
+
+ """
+ return list(self.hostlist.get_host_info(host_id))
+
+ @export
+ def get_hosts(self):
+ """
+ Return the hosts in the hostlist.
+ """
+ log.debug('get_hosts called')
+ return self.hostlist.get_hosts_info()
+
+ @export
+ def get_host_status(self, host_id):
+ """
+ Returns the current status for the specified host.
+
+ :param host_id: the hash id of the host
+ :type host_id: string
+
+ """
+
+ def response(result):
+ return result
+
+ return self.hostlist.get_host_status(host_id).addCallback(response)
+
+ @export
+ def add_host(self, host, port, username='', password=''):
+ """Adds a host to the list.
+
+ Args:
+ host (str): The IP or hostname of the deluge daemon.
+ port (int): The port of the deluge daemon.
+ username (str): The username to login to the daemon with.
+ password (str): The password to login to the daemon with.
+
+ Returns:
+ tuple: A tuple of (bool, str). If True will contain the host_id, otherwise
+ if False will contain the error message.
+ """
+ try:
+ host_id = self.hostlist.add_host(host, port, username, password)
+ except ValueError as ex:
+ return False, str(ex)
+ else:
+ return True, host_id
+
+ @export
+ def edit_host(self, host_id, host, port, username='', password=''):
+ """Edit host details in the hostlist.
+
+ Args:
+ host_id (str): The host identifying hash.
+ host (str): The IP or hostname of the deluge daemon.
+ port (int): The port of the deluge daemon.
+ username (str): The username to login to the daemon with.
+ password (str): The password to login to the daemon with.
+
+ Returns:
+ bool: True if succesful, False otherwise.
+
+ """
+ return self.hostlist.update_host(host_id, host, port, username, password)
+
+ @export
+ def remove_host(self, host_id):
+ """Removes a host from the hostlist.
+
+ Args:
+ host_id (str): The host identifying hash.
+
+ Returns:
+ bool: True if succesful, False otherwise.
+
+ """
+ return self.hostlist.remove_host(host_id)
+
+ @export
+ def start_daemon(self, port):
+ """
+ Starts a local daemon.
+ """
+ client.start_daemon(port, get_config_dir())
+
+ @export
+ def stop_daemon(self, host_id):
+ """
+ Stops a running daemon.
+
+ :param host_id: the hash id of the host
+ :type host_id: string
+ """
+ main_deferred = Deferred()
+ host = self._get_host(host_id)
+ if not host:
+ main_deferred.callback((False, _('Daemon does not exist')))
+ return main_deferred
+
+ try:
+
+ def on_connect(connected, c):
+ if not connected:
+ main_deferred.callback((False, _('Daemon not running')))
+ return
+ c.daemon.shutdown()
+ main_deferred.callback((True,))
+
+ def on_connect_failed(reason):
+ main_deferred.callback((False, reason))
+
+ host, port, user, password = host[1:5]
+ c = Client()
+ d = c.connect(host, port, user, password)
+ d.addCallback(on_connect, c)
+ d.addErrback(on_connect_failed)
+ except Exception:
+ main_deferred.callback((False, 'An error occurred'))
+ return main_deferred
+
+ @export
+ def get_config(self):
+ """
+ Get the configuration dictionary for the web interface.
+
+ :rtype: dictionary
+ :returns: the configuration
+ """
+ config = component.get('DelugeWeb').config.config.copy()
+ del config['sessions']
+ del config['pwd_salt']
+ del config['pwd_sha1']
+ return config
+
+ @export
+ def set_config(self, config):
+ """
+ Sets the configuration dictionary for the web interface.
+
+ :param config: The configuration options to update
+ :type config: dictionary
+ """
+ web_config = component.get('DelugeWeb').config
+ for key in config:
+ if key in ['sessions', 'pwd_salt', 'pwd_sha1']:
+ log.warning('Ignored attempt to overwrite web config key: %s', key)
+ continue
+ web_config[key] = config[key]
+
+ @export
+ def get_plugins(self):
+ """All available and enabled plugins within WebUI.
+
+ Note:
+ This does not represent all plugins from deluge.client.core.
+
+ Returns:
+ dict: A dict containing 'available_plugins' and 'enabled_plugins' lists.
+
+ """
+
+ return {
+ 'enabled_plugins': list(component.get('Web.PluginManager').plugins),
+ 'available_plugins': component.get('Web.PluginManager').available_plugins,
+ }
+
+ @export
+ def get_plugin_info(self, name):
+ """Get the details for a plugin."""
+ return component.get('Web.PluginManager').get_plugin_info(name)
+
+ @export
+ def get_plugin_resources(self, name):
+ """Get the resource data files for a plugin."""
+ return component.get('Web.PluginManager').get_plugin_resources(name)
+
+ @export
+ def upload_plugin(self, filename, path):
+ """Upload a plugin to config."""
+ main_deferred = Deferred()
+
+ shutil.copyfile(path, os.path.join(get_config_dir(), 'plugins', filename))
+ component.get('Web.PluginManager').scan_for_plugins()
+
+ if client.is_localhost():
+ client.core.rescan_plugins()
+ return True
+ with open(path, 'rb') as _file:
+ plugin_data = b64encode(_file.read())
+
+ def on_upload_complete(*args):
+ client.core.rescan_plugins()
+ component.get('Web.PluginManager').scan_for_plugins()
+ main_deferred.callback(True)
+
+ def on_upload_error(*args):
+ main_deferred.callback(False)
+
+ d = client.core.upload_plugin(filename, plugin_data)
+ d.addCallback(on_upload_complete)
+ d.addErrback(on_upload_error)
+ return main_deferred
+
+ @export
+ def register_event_listener(self, event):
+ """
+ Add a listener to the event queue.
+
+ :param event: The event name
+ :type event: string
+ """
+ self.event_queue.add_listener(__request__.session_id, event)
+
+ @export
+ def deregister_event_listener(self, event):
+ """
+ Remove an event listener from the event queue.
+
+ :param event: The event name
+ :type event: string
+ """
+ self.event_queue.remove_listener(__request__.session_id, event)
+
+ @export
+ def get_events(self):
+ """
+ Retrieve the pending events for the session.
+ """
+ return self.event_queue.get_events(__request__.session_id)
+
+
+class WebUtils(JSONComponent):
+ """
+ Utility functions for the webui that do not fit in the WebApi.
+ """
+
+ def __init__(self):
+ super(WebUtils, self).__init__('WebUtils')
+
+ @export
+ def get_languages(self):
+ """
+ Get the available translated languages
+
+ Returns:
+ list: of tuples [(lang-id, language-name), ...]
+ """
+ return get_languages()
diff --git a/deluge/ui/web/pluginmanager.py b/deluge/ui/web/pluginmanager.py
new file mode 100644
index 0000000..24f20ce
--- /dev/null
+++ b/deluge/ui/web/pluginmanager.py
@@ -0,0 +1,161 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import logging
+import os
+
+from deluge import component
+from deluge.configmanager import ConfigManager
+from deluge.pluginmanagerbase import PluginManagerBase
+from deluge.ui.client import client
+
+log = logging.getLogger(__name__)
+
+
+def gather_info(plugin):
+ # Get the scripts for the plugin
+ scripts = getattr(plugin, 'scripts', ())
+ debug_scripts = getattr(plugin, 'debug_scripts') or scripts
+
+ directories = []
+ for script in scripts + debug_scripts:
+ if os.path.dirname(script) not in directories:
+ directories.append(os.path.dirname(script))
+
+ return {
+ 'scripts': scripts,
+ 'debug_scripts': debug_scripts,
+ 'script_directories': directories,
+ }
+
+
+class PluginManager(PluginManagerBase, component.Component):
+ def __init__(self):
+ component.Component.__init__(self, 'Web.PluginManager')
+ self.config = ConfigManager('web.conf')
+ PluginManagerBase.__init__(self, 'web.conf', 'deluge.plugin.web')
+
+ client.register_event_handler(
+ 'PluginEnabledEvent', self._on_plugin_enabled_event
+ )
+ client.register_event_handler(
+ 'PluginDisabledEvent', self._on_plugin_disabled_event
+ )
+
+ def _on_get_enabled_plugins(self, plugins):
+ for plugin in plugins:
+ self.enable_plugin(plugin)
+
+ def _on_plugin_enabled_event(self, name):
+ self.enable_plugin(name)
+
+ def _on_plugin_disabled_event(self, name):
+ self.disable_plugin(name)
+
+ def disable_plugin(self, name):
+ # Get the plugin instance
+ try:
+ plugin = component.get('WebPlugin.' + name)
+ except KeyError:
+ log.debug(
+ '%s plugin contains no WebUI code, ignoring WebUI disable call.', name
+ )
+ return
+
+ info = gather_info(plugin)
+
+ scripts = component.get('Scripts')
+ for script in info['scripts']:
+ scripts.remove_script(
+ '%s/%s' % (name.lower(), os.path.basename(script).lower())
+ )
+
+ for script in info['debug_scripts']:
+ scripts.remove_script(
+ '%s/%s' % (name.lower(), os.path.basename(script).lower()), 'debug'
+ )
+ scripts.remove_script(
+ '%s/%s' % (name.lower(), os.path.basename(script).lower()), 'dev'
+ )
+
+ super(PluginManager, self).disable_plugin(name)
+
+ def enable_plugin(self, name):
+ super(PluginManager, self).enable_plugin(name)
+
+ # Get the plugin instance
+ try:
+ plugin = component.get('WebPlugin.' + name)
+ except KeyError:
+ log.info(
+ '%s plugin contains no WebUI code, ignoring WebUI enable call.', name
+ )
+ return
+
+ info = gather_info(plugin)
+
+ scripts = component.get('Scripts')
+ for script in info['scripts']:
+ log.debug('adding script %s for %s', name, os.path.basename(script))
+ scripts.add_script(
+ '%s/%s' % (name.lower(), os.path.basename(script)), script
+ )
+
+ for script in info['debug_scripts']:
+ log.debug('adding debug script %s for %s', name, os.path.basename(script))
+ scripts.add_script(
+ '%s/%s' % (name.lower(), os.path.basename(script)), script, 'debug'
+ )
+ scripts.add_script(
+ '%s/%s' % (name.lower(), os.path.basename(script)), script, 'dev'
+ )
+
+ def start(self):
+ """
+ Start up the plugin manager
+ """
+ # Update the enabled plugins from the core
+ d = client.core.get_enabled_plugins()
+ d.addCallback(self._on_get_enabled_plugins)
+
+ def stop(self):
+ """
+ Stop the plugin manager
+ """
+ self.disable_plugins()
+ client.deregister_event_handler(
+ 'PluginEnabledEvent', self._on_plugin_enabled_event
+ )
+ client.deregister_event_handler(
+ 'PluginDisabledEvent', self._on_plugin_disabled_event
+ )
+
+ def update(self):
+ pass
+
+ def get_plugin_resources(self, name):
+ # Get the plugin instance
+ try:
+ plugin = component.get('WebPlugin.' + name)
+ except KeyError:
+ log.info('Plugin has no web ui')
+ return
+ info = gather_info(plugin)
+ info['name'] = name
+ info['scripts'] = [
+ 'js/%s/%s' % (name.lower(), os.path.basename(s)) for s in info['scripts']
+ ]
+ info['debug_scripts'] = [
+ 'js/%s/%s' % (name.lower(), os.path.basename(s))
+ for s in info['debug_scripts']
+ ]
+ del info['script_directories']
+ return info
diff --git a/deluge/ui/web/render/404.html b/deluge/ui/web/render/404.html
new file mode 100644
index 0000000..8624de4
--- /dev/null
+++ b/deluge/ui/web/render/404.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml">
+ <head>
+ <title>Deluge: Web UI ${version}</title>
+ </head>
+ <body>
+ <h1>Error 404 - Page Not Found</h1>
+ <b>The requested template file was not found.</b>
+ </body>
+</html>
diff --git a/deluge/ui/web/render/tab_status.html b/deluge/ui/web/render/tab_status.html
new file mode 100644
index 0000000..deed7d6
--- /dev/null
+++ b/deluge/ui/web/render/tab_status.html
@@ -0,0 +1,29 @@
+<dl>
+ <dt class="downloaded">${_("Downloaded:")}</dt><dd class="downloaded"/>
+ <dt class="uploaded">${_("Uploaded:")}</dt><dd class="uploaded"/>
+ <dt class="share">${_("Share Ratio:")}</dt><dd class="share"/>
+ <dt class="announce">${_("Next Announce:")}</dt><dd class="announce"/>
+ <dt class="tracker">${_("Tracker Status:")}</dt><dd class="tracker_status"/>
+</dl>
+<dl>
+ <dt class="downspeed">${_("Down Speed:")}</dt><dd class="downspeed"/>
+ <dt class="upspeed">${_("Up Speed:")}</dt><dd class="upspeed"/>
+ <dt class="eta">${_("ETA:")}</dt><dd class="eta"/>
+ <dt class="pieces">${_("Pieces:")}</dt><dd class="pieces"/>
+ <dt class="time_since_transfer">${_("Last Transfer:")}</dt><dd class="time_since_transfer"/>
+</dl>
+<dl>
+ <dt class="seeds">${_("Seeds:")}</dt><dd class="seeds"/>
+ <dt class="peers">${_("Peers:")}</dt><dd class="peers"/>
+ <dt class="avail">${_("Availability:")}</dt><dd class="avail"/>
+ <dt class="auto_managed">${_("Auto Managed:")}</dt><dd class="auto_managed"/>
+ <dt class="last_seen_complete">${_("Complete Seen:")}</dt><dd class="last_seen_complete"/>
+</dl>
+<dl>
+ <dt class="active_time">${_("Active Time:")}</dt><dd class="active_time"/>
+ <dt class="seeding_time">${_("Seeding Time:")}</dt><dd class="seeding_time"/>
+ <dt class="seed_rank">${_("Seed Rank:")}</dt><dd class="seed_rank"/>
+ <dt class="time_rank">${_("Date Added:")}</dt><dd class="time_added"/>
+ <dt class="completed_time">${_("Completed:")}</dt><dd class="completed_time"/>
+</dl>
+<br style="clear: both;" />
diff --git a/deluge/ui/web/server.py b/deluge/ui/web/server.py
new file mode 100644
index 0000000..192c2b7
--- /dev/null
+++ b/deluge/ui/web/server.py
@@ -0,0 +1,806 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009-2010 Damien Churchill <damoxc@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import unicode_literals
+
+import fnmatch
+import json
+import logging
+import mimetypes
+import os
+import tempfile
+
+from twisted.application import internet, service
+from twisted.internet import defer, reactor
+from twisted.web import http, resource, server, static
+from twisted.web.resource import EncodingResourceWrapper
+
+from deluge import common, component, configmanager
+from deluge.common import is_ipv6
+from deluge.core.rpcserver import check_ssl_keys
+from deluge.crypto_utils import get_context_factory
+from deluge.i18n import set_language, setup_translation
+from deluge.ui.tracker_icons import TrackerIcons
+from deluge.ui.web.auth import Auth
+from deluge.ui.web.common import Template
+from deluge.ui.web.json_api import JSON, WebApi, WebUtils
+from deluge.ui.web.pluginmanager import PluginManager
+
+log = logging.getLogger(__name__)
+
+CONFIG_DEFAULTS = {
+ # Misc Settings
+ 'enabled_plugins': [],
+ 'default_daemon': '',
+ # Auth Settings
+ 'pwd_salt': 'c26ab3bbd8b137f99cd83c2c1c0963bcc1a35cad',
+ 'pwd_sha1': '2ce1a410bcdcc53064129b6d950f2e9fee4edc1e',
+ 'session_timeout': 3600,
+ 'sessions': {},
+ # UI Settings
+ 'sidebar_show_zero': False,
+ 'sidebar_multiple_filters': True,
+ 'show_session_speed': False,
+ 'show_sidebar': True,
+ 'theme': 'gray',
+ 'first_login': True,
+ 'language': '',
+ # Server Settings
+ 'base': '/',
+ 'interface': '0.0.0.0',
+ 'port': 8112,
+ 'https': False,
+ 'pkey': 'ssl/daemon.pkey',
+ 'cert': 'ssl/daemon.cert',
+}
+
+UI_CONFIG_KEYS = (
+ 'theme',
+ 'sidebar_show_zero',
+ 'sidebar_multiple_filters',
+ 'show_session_speed',
+ 'base',
+ 'first_login',
+)
+
+
+def rpath(*paths):
+ """Convert a relative path into an absolute path relative to the location
+ of this script.
+ """
+ return common.resource_filename('deluge.ui.web', os.path.join(*paths))
+
+
+class GetText(resource.Resource):
+ def render(self, request):
+ request.setHeader(b'content-type', b'text/javascript; encoding=utf-8')
+ template = Template(filename=rpath('js', 'gettext.js'))
+ return template.render()
+
+
+class MockGetText(resource.Resource):
+ """GetText Mocking class
+
+ This class will mock the file `gettext.js` in case it does not exists.
+ It will be used to define the `_` (underscore) function for translations,
+ and will return the string to translate, as is.
+ """
+
+ def render(self, request):
+ request.setHeader(b'content-type', b'text/javascript; encoding=utf-8')
+ return b'function _(string) { return string; }'
+
+
+class Upload(resource.Resource):
+ """
+ Twisted Web resource to handle file uploads
+ """
+
+ def render(self, request):
+ """
+ Saves all uploaded files to the disk and returns a list of filenames,
+ each on a new line.
+ """
+
+ # Block all other HTTP methods.
+ if request.method != b'POST':
+ request.setResponseCode(http.NOT_ALLOWED)
+ request.finish()
+ return server.NOT_DONE_YET
+
+ files = request.args.get(b'file', [])
+ filenames = []
+
+ if files:
+ tempdir = tempfile.mkdtemp(prefix='delugeweb-')
+ log.debug('uploading files to %s', tempdir)
+
+ for upload in files:
+ fd, fn = tempfile.mkstemp('.torrent', dir=tempdir)
+ os.write(fd, upload)
+ os.close(fd)
+ filenames.append(fn)
+
+ log.debug('uploaded %d file(s)', len(filenames))
+
+ request.setHeader(b'content-type', b'text/html')
+ request.setResponseCode(http.OK)
+ return json.dumps({'success': bool(filenames), 'files': filenames}).encode(
+ 'utf8'
+ )
+
+
+class Render(resource.Resource):
+ def __init__(self):
+ resource.Resource.__init__(self)
+ # Make a list of all the template files to check requests against.
+ self.template_files = fnmatch.filter(os.listdir(rpath('render')), '*.html')
+
+ def getChild(self, path, request): # NOQA: N802
+ request.render_file = path
+ return EncodingResourceWrapper(self, [server.GzipEncoderFactory()])
+
+ def render(self, request):
+ log.debug('Render template file: %s', request.render_file)
+ if not hasattr(request, 'render_file'):
+ request.setResponseCode(http.INTERNAL_SERVER_ERROR)
+ return ''
+
+ request.setHeader(b'content-type', b'text/html')
+
+ tpl_file = request.render_file.decode()
+ if tpl_file in self.template_files:
+ request.setResponseCode(http.OK)
+ else:
+ request.setResponseCode(http.NOT_FOUND)
+ tpl_file = '404.html'
+
+ template = Template(filename=rpath(os.path.join('render', tpl_file)))
+ return template.render()
+
+
+class Tracker(resource.Resource):
+ def __init__(self):
+ resource.Resource.__init__(self)
+ try:
+ self.tracker_icons = component.get('TrackerIcons')
+ except KeyError:
+ self.tracker_icons = TrackerIcons()
+
+ def getChild(self, path, request): # NOQA: N802
+ request.tracker_name = path
+ return self
+
+ def on_got_icon(self, icon, request):
+ if icon:
+ request.setHeader(
+ b'cache-control', b'public, must-revalidate, max-age=86400'
+ )
+ request.setHeader(b'content-type', icon.get_mimetype().encode('utf8'))
+ request.setResponseCode(http.OK)
+ request.write(icon.get_data())
+ request.finish()
+ else:
+ request.setResponseCode(http.NOT_FOUND)
+ request.finish()
+
+ def render(self, request):
+ d = self.tracker_icons.fetch(request.tracker_name)
+ d.addCallback(self.on_got_icon, request)
+ return server.NOT_DONE_YET
+
+
+class Flag(resource.Resource):
+ def getChild(self, path, request): # NOQA: N802
+ request.country = path
+ return self
+
+ def render(self, request):
+ flag = request.country.decode('utf-8').lower() + '.png'
+ path = ('ui', 'data', 'pixmaps', 'flags', flag)
+ filename = common.resource_filename('deluge', os.path.join(*path))
+ if os.path.exists(filename):
+ request.setHeader(
+ b'cache-control', b'public, must-revalidate, max-age=86400'
+ )
+ request.setHeader(b'content-type', b'image/png')
+ with open(filename, 'rb') as _file:
+ data = _file.read()
+ request.setResponseCode(http.OK)
+ return data
+ else:
+ request.setResponseCode(http.NOT_FOUND)
+ return ''
+
+
+class LookupResource(resource.Resource, component.Component):
+ def __init__(self, name, *directories):
+ resource.Resource.__init__(self)
+ component.Component.__init__(self, name)
+
+ self.__paths = {}
+ for directory in directories:
+ self.add_directory(directory)
+
+ def add_directory(self, directory, path=''):
+ log.debug('Adding directory `%s` with path `%s`', directory, path)
+ paths = self.__paths.setdefault(path, [])
+ paths.append(directory)
+
+ def remove_directory(self, directory, path=''):
+ log.debug('Removing directory `%s`', directory)
+ self.__paths[path].remove(directory)
+
+ def getChild(self, path, request): # NOQA: N802
+ if hasattr(request, 'lookup_path'):
+ request.lookup_path = os.path.join(request.lookup_path, path)
+ else:
+ request.lookup_path = path
+
+ if request.uri.endswith(b'css'):
+ return EncodingResourceWrapper(self, [server.GzipEncoderFactory()])
+ else:
+ return self
+
+ def render(self, request):
+ log.debug('Requested path: %s', request.lookup_path)
+ path = os.path.dirname(request.lookup_path).decode()
+
+ if path in self.__paths:
+ filename = os.path.basename(request.path).decode()
+ for directory in self.__paths[path]:
+ path = os.path.join(directory, filename)
+ if os.path.isfile(path):
+ log.debug('Serving path: %s', path)
+ mime_type = mimetypes.guess_type(path)
+ request.setHeader(b'content-type', mime_type[0].encode())
+ with open(path, 'rb') as _file:
+ data = _file.read()
+ return data
+
+ request.setResponseCode(http.NOT_FOUND)
+ request.setHeader(b'content-type', b'text/html')
+ template = Template(filename=rpath(os.path.join('render', '404.html')))
+ return template.render()
+
+
+class ScriptResource(resource.Resource, component.Component):
+ def __init__(self):
+ resource.Resource.__init__(self)
+ component.Component.__init__(self, 'Scripts')
+ self.__scripts = {}
+ for script_type in ['normal', 'debug', 'dev']:
+ self.__scripts[script_type] = {
+ 'scripts': {},
+ 'order': [],
+ 'files_exist': True,
+ }
+
+ def has_script_type_files(self, script_type):
+ """Returns whether all the script files exist for this script type.
+
+ Args:
+ script_type (str): The script type to check (normal, debug, dev).
+
+ Returns:
+ bool: True if the files for this script type exist, otherwise False.
+
+ """
+ return self.__scripts[script_type]['files_exist']
+
+ def add_script(self, path, filepath, script_type=None):
+ """
+ Adds a script or scripts to the script resource.
+
+ :param path: The path of the script (this supports globbing)
+ :type path: string
+ :param filepath: The physical location of the script
+ :type filepath: string
+ :param script_type: The type of script to add (normal, debug, dev)
+ :param script_type: string
+ """
+ if script_type not in ('dev', 'debug', 'normal'):
+ script_type = 'normal'
+
+ self.__scripts[script_type]['scripts'][path] = filepath
+ self.__scripts[script_type]['order'].append(path)
+ if not os.path.isfile(filepath):
+ self.__scripts[script_type]['files_exist'] = False
+
+ def add_script_folder(self, path, filepath, script_type=None, recurse=True):
+ """
+ Adds a folder of scripts to the script resource.
+
+ :param path: The path of the folder
+ :type path: string
+ :param filepath: The physical location of the script
+ :type filepath: string
+ :param script_type: The type of script to add (normal, debug, dev)
+ :param script_type: string
+ :param recurse: Whether or not to recurse into other folders
+ :param recurse: bool
+ """
+ if script_type not in ('dev', 'debug', 'normal'):
+ script_type = 'normal'
+
+ self.__scripts[script_type]['scripts'][path] = (filepath, recurse)
+ self.__scripts[script_type]['order'].append(path)
+ if not os.path.isdir(filepath):
+ self.__scripts[script_type]['files_exist'] = False
+
+ def remove_script(self, path, script_type=None):
+ """
+ Removes a script or folder of scripts from the script resource.
+
+ :param path: The path of the folder
+ :type path: string
+ :param script_type: The type of script to add (normal, debug, dev)
+ :param script_type: string
+ """
+ if script_type not in ('dev', 'debug', 'normal'):
+ script_type = 'normal'
+
+ del self.__scripts[script_type]['scripts'][path]
+ self.__scripts[script_type]['order'].remove(path)
+
+ def get_scripts(self, script_type=None):
+ """
+ Returns a list of the scripts that can be used for producing
+ script tags.
+
+ :param script_type: The type of scripts to get (normal, debug, dev)
+ :param script_type: string
+ """
+ if script_type not in ('dev', 'debug', 'normal'):
+ script_type = 'normal'
+
+ _scripts = self.__scripts[script_type]['scripts']
+ _order = self.__scripts[script_type]['order']
+
+ scripts = []
+ for path in _order:
+ # Index for grouping the scripts when inserting.
+ script_idx = len(scripts)
+ # A folder resource is enclosed in a tuple.
+ if isinstance(_scripts[path], tuple):
+ filepath, recurse = _scripts[path]
+ for root, dirnames, filenames in os.walk(filepath):
+ dirnames.sort(reverse=True)
+ files = sorted(fnmatch.filter(filenames, '*.js'))
+
+ order_file = os.path.join(root, '.order')
+ if os.path.isfile(order_file):
+ with open(order_file, 'r') as _file:
+ for line in _file:
+ if line.startswith('+ '):
+ order_filename = line.split()[1]
+ files.pop(files.index(order_filename))
+ files.insert(0, order_filename)
+
+ # Ensure sub-directory scripts are top of list with root directory scripts bottom.
+ if dirnames:
+ scripts.extend(
+ ['js/' + os.path.basename(root) + '/' + f for f in files]
+ )
+ else:
+ dirpath = (
+ os.path.basename(os.path.dirname(root))
+ + '/'
+ + os.path.basename(root)
+ )
+ for filename in reversed(files):
+ scripts.insert(script_idx, 'js/' + dirpath + '/' + filename)
+
+ if not recurse:
+ break
+ else:
+ scripts.append('js/' + path)
+ return scripts
+
+ def getChild(self, path, request): # NOQA: N802
+ if hasattr(request, 'lookup_path'):
+ request.lookup_path += b'/' + path
+ else:
+ request.lookup_path = path
+ return EncodingResourceWrapper(self, [server.GzipEncoderFactory()])
+
+ def render(self, request):
+ log.debug('Requested path: %s', request.lookup_path)
+ lookup_path = request.lookup_path.decode()
+ for script_type in ('dev', 'debug', 'normal'):
+ scripts = self.__scripts[script_type]['scripts']
+ for pattern in scripts:
+ if not lookup_path.startswith(pattern):
+ continue
+
+ filepath = scripts[pattern]
+ if isinstance(filepath, tuple):
+ filepath = filepath[0]
+
+ path = filepath + lookup_path[len(pattern) :]
+
+ if not os.path.isfile(path):
+ continue
+
+ log.debug('Serving path: %s', path)
+ mime_type = mimetypes.guess_type(path)
+ request.setHeader(b'content-type', mime_type[0].encode())
+ with open(path, 'rb') as _file:
+ data = _file.read()
+ return data
+
+ request.setResponseCode(http.NOT_FOUND)
+ request.setHeader(b'content-type', b'text/html')
+ template = Template(filename=rpath(os.path.join('render', '404.html')))
+ return template.render()
+
+
+class Themes(static.File):
+ def getChild(self, path, request): # NOQA: N802
+ child = static.File.getChild(self, path, request)
+ if request.uri.endswith(b'css'):
+ return EncodingResourceWrapper(child, [server.GzipEncoderFactory()])
+ else:
+ return child
+
+
+class TopLevel(resource.Resource):
+
+ __stylesheets = [
+ 'css/ext-all-notheme.css',
+ 'css/ext-extensions.css',
+ 'css/deluge.css',
+ ]
+
+ def __init__(self):
+ resource.Resource.__init__(self)
+
+ self.putChild(b'css', LookupResource('Css', rpath('css')))
+ if os.path.isfile(rpath('js', 'gettext.js')):
+ self.putChild(
+ b'gettext.js',
+ EncodingResourceWrapper(GetText(), [server.GzipEncoderFactory()]),
+ )
+ else:
+ log.warning(
+ 'Cannot find "gettext.js" translation file!'
+ ' Text will only be available in English.'
+ )
+ self.putChild(b'gettext.js', MockGetText())
+ self.putChild(b'flag', Flag())
+ self.putChild(b'icons', LookupResource('Icons', rpath('icons')))
+ self.putChild(b'images', LookupResource('Images', rpath('images')))
+ self.putChild(
+ b'ui_images',
+ LookupResource(
+ 'UI_Images', common.resource_filename('deluge.ui.data', 'pixmaps')
+ ),
+ )
+
+ js = ScriptResource()
+
+ # configure the dev scripts
+ js.add_script(
+ 'ext-base-debug.js', rpath('js', 'extjs', 'ext-base-debug.js'), 'dev'
+ )
+ js.add_script(
+ 'ext-all-debug.js', rpath('js', 'extjs', 'ext-all-debug.js'), 'dev'
+ )
+ js.add_script_folder(
+ 'ext-extensions', rpath('js', 'extjs', 'ext-extensions'), 'dev'
+ )
+ js.add_script_folder('deluge-all', rpath('js', 'deluge-all'), 'dev')
+
+ # configure the debug scripts
+ js.add_script(
+ 'ext-base-debug.js', rpath('js', 'extjs', 'ext-base-debug.js'), 'debug'
+ )
+ js.add_script(
+ 'ext-all-debug.js', rpath('js', 'extjs', 'ext-all-debug.js'), 'debug'
+ )
+ js.add_script(
+ 'ext-extensions-debug.js',
+ rpath('js', 'extjs', 'ext-extensions-debug.js'),
+ 'debug',
+ )
+ js.add_script(
+ 'deluge-all-debug.js', rpath('js', 'deluge-all-debug.js'), 'debug'
+ )
+
+ # configure the normal scripts
+ js.add_script('ext-base.js', rpath('js', 'extjs', 'ext-base.js'))
+ js.add_script('ext-all.js', rpath('js', 'extjs', 'ext-all.js'))
+ js.add_script('ext-extensions.js', rpath('js', 'extjs', 'ext-extensions.js'))
+ js.add_script('deluge-all.js', rpath('js', 'deluge-all.js'))
+
+ self.js = js
+ self.putChild(b'js', js)
+ self.putChild(
+ b'json', EncodingResourceWrapper(JSON(), [server.GzipEncoderFactory()])
+ )
+ self.putChild(
+ b'upload', EncodingResourceWrapper(Upload(), [server.GzipEncoderFactory()])
+ )
+ self.putChild(b'render', Render())
+ self.putChild(b'themes', Themes(rpath('themes')))
+ self.putChild(b'tracker', Tracker())
+
+ theme = component.get('DelugeWeb').config['theme']
+ if not os.path.isfile(rpath('themes', 'css', 'xtheme-%s.css' % theme)):
+ theme = CONFIG_DEFAULTS.get('theme')
+ self.__stylesheets.insert(1, 'themes/css/xtheme-%s.css' % theme)
+
+ @property
+ def stylesheets(self):
+ return self.__stylesheets
+
+ def add_script(self, script):
+ """
+ Adds a script to the server so it is included in the <head> element
+ of the index page.
+
+ :param script: The path to the script
+ :type script: string
+ """
+
+ self.__scripts.append(script)
+ self.__debug_scripts.append(script)
+
+ def remove_script(self, script):
+ """
+ Removes a script from the server.
+
+ :param script: The path to the script
+ :type script: string
+ """
+ self.__scripts.remove(script)
+ self.__debug_scripts.remove(script)
+
+ def getChild(self, path, request): # NOQA: N802
+ if not path:
+ return self
+ else:
+ return resource.Resource.getChild(self, path, request)
+
+ def getChildWithDefault(self, path, request): # NOQA: N802
+ # Calculate the request base
+ header = request.getHeader(b'x-deluge-base')
+ base = header.decode('utf-8') if header else component.get('DelugeWeb').base
+
+ # validate the base parameter
+ if not base:
+ base = '/'
+
+ if base[0] != '/':
+ base = '/' + base
+
+ if base[-1] != '/':
+ base += '/'
+
+ request.base = base.encode('utf-8')
+
+ return resource.Resource.getChildWithDefault(self, path, request)
+
+ def render(self, request):
+ uri_true = ('true', 'yes', 'on', '1')
+ uri_false = ('false', 'no', 'off', '0')
+
+ debug_arg = None
+ req_dbg_arg = request.args.get('debug', [b''])[-1].decode().lower()
+ if req_dbg_arg in uri_true:
+ debug_arg = True
+ elif req_dbg_arg in uri_false:
+ debug_arg = False
+
+ dev_arg = request.args.get('dev', [b''])[-1].decode().lower() in uri_true
+ dev_ver = 'dev' in common.get_version()
+
+ script_type = 'normal'
+ if debug_arg is not None:
+ # Use debug arg to force switching to normal script type.
+ script_type = 'debug' if debug_arg else 'normal'
+ elif dev_arg or dev_ver:
+ # Also use dev files if development version.
+ script_type = 'dev'
+
+ if not self.js.has_script_type_files(script_type):
+ if not dev_ver:
+ log.warning(
+ 'Failed to enable WebUI "%s" mode, script files are missing!',
+ script_type,
+ )
+ # Fallback to checking other types in order and selecting first with
+ # files available. Ordered to start with dev files lookup.
+ for alt_script_type in [
+ x for x in ['dev', 'debug', 'normal'] if x != script_type
+ ]:
+ if self.js.has_script_type_files(alt_script_type):
+ script_type = alt_script_type
+ if not dev_ver:
+ log.warning('WebUI falling back to "%s" mode.', script_type)
+ break
+
+ scripts = component.get('Scripts').get_scripts(script_type)
+ scripts.insert(0, 'gettext.js')
+
+ template = Template(filename=rpath('index.html'))
+ request.setHeader(b'content-type', b'text/html; charset=utf-8')
+
+ web_config = component.get('Web').get_config()
+ web_config['base'] = request.base.decode()
+ config = {key: web_config[key] for key in UI_CONFIG_KEYS}
+ js_config = json.dumps(config)
+ # Insert the values into 'index.html' and return.
+ return template.render(
+ scripts=scripts,
+ stylesheets=self.stylesheets,
+ debug=str(bool(debug_arg)).lower(),
+ base=web_config['base'],
+ js_config=js_config,
+ )
+
+
+class DelugeWeb(component.Component):
+ def __init__(self, options=None, daemon=True):
+ """
+ Setup the DelugeWeb server.
+
+ Args:
+ options (argparse.Namespace): The web server options.
+ daemon (bool): If True run web server as a seperate daemon process (starts a twisted
+ reactor). If False shares the process and twisted reactor from WebUI plugin or tests.
+
+ """
+ component.Component.__init__(self, 'DelugeWeb', depend=['Web'])
+ self.config = configmanager.ConfigManager(
+ 'web.conf', defaults=CONFIG_DEFAULTS, file_version=2
+ )
+ self.config.run_converter((0, 1), 2, self._migrate_config_1_to_2)
+ self.config.register_set_function('language', self._on_language_changed)
+ self.socket = None
+ self.top_level = TopLevel()
+
+ self.interface = self.config['interface']
+ self.port = self.config['port']
+ self.https = self.config['https']
+ self.pkey = self.config['pkey']
+ self.cert = self.config['cert']
+ self.base = self.config['base']
+
+ if options:
+ self.interface = (
+ options.interface if options.interface is not None else self.interface
+ )
+ self.port = options.port if options.port else self.port
+ self.base = options.base if options.base else self.base
+ if options.ssl:
+ self.https = True
+ elif options.no_ssl:
+ self.https = False
+
+ if self.base != '/':
+ # Strip away slashes and serve on the base path as well as root path
+ self.top_level.putChild(self.base.strip('/'), self.top_level)
+
+ setup_translation()
+
+ # Remove twisted version number from 'server' http-header for security reasons
+ server.version = 'TwistedWeb'
+ self.site = server.Site(self.top_level)
+ self.web_api = WebApi()
+ self.web_utils = WebUtils()
+
+ self.auth = Auth(self.config)
+ self.daemon = daemon
+ # Initalize the plugins
+ self.plugins = PluginManager()
+
+ def _on_language_changed(self, key, value):
+ log.debug('Setting UI language %s', value)
+ set_language(value)
+
+ def install_signal_handlers(self):
+ # Since twisted assigns itself all the signals may as well make
+ # use of it.
+ reactor.addSystemEventTrigger('after', 'shutdown', self.shutdown)
+
+ # Twisted doesn't handle windows specific signals so we still
+ # need to attach to those to handle the close correctly.
+ if common.windows_check():
+ from win32api import SetConsoleCtrlHandler
+ from win32con import CTRL_CLOSE_EVENT, CTRL_SHUTDOWN_EVENT
+
+ def win_handler(ctrl_type):
+ log.debug('ctrl type: %s', ctrl_type)
+ if ctrl_type == CTRL_CLOSE_EVENT or ctrl_type == CTRL_SHUTDOWN_EVENT:
+ self.shutdown()
+ return 1
+
+ SetConsoleCtrlHandler(win_handler)
+
+ def start(self):
+ """
+ Start the DelugeWeb server
+ """
+ if self.socket:
+ log.warning('DelugeWeb is already running and cannot be started')
+ return
+
+ log.info('Starting webui server at PID %s', os.getpid())
+ if self.https:
+ self.start_ssl()
+ else:
+ self.start_normal()
+
+ component.get('Web').enable()
+
+ if self.daemon:
+ reactor.run()
+
+ def start_normal(self):
+ self.socket = reactor.listenTCP(self.port, self.site, interface=self.interface)
+ ip = self.socket.getHost().host
+ ip = '[%s]' % ip if is_ipv6(ip) else ip
+ log.info('Serving at http://%s:%s%s', ip, self.port, self.base)
+
+ def start_ssl(self):
+ check_ssl_keys()
+ log.debug('Enabling SSL with PKey: %s, Cert: %s', self.pkey, self.cert)
+
+ cert = configmanager.get_config_dir(self.cert)
+ pkey = configmanager.get_config_dir(self.pkey)
+
+ self.socket = reactor.listenSSL(
+ self.port,
+ self.site,
+ get_context_factory(cert, pkey),
+ interface=self.interface,
+ )
+ ip = self.socket.getHost().host
+ ip = '[%s]' % ip if is_ipv6(ip) else ip
+ log.info('Serving at https://%s:%s%s', ip, self.port, self.base)
+
+ def stop(self):
+ log.info('Shutting down webserver')
+ try:
+ component.get('Web').disable()
+ except KeyError:
+ pass
+
+ self.plugins.disable_plugins()
+ log.debug('Saving configuration file')
+ self.config.save()
+
+ if self.socket:
+ d = self.socket.stopListening()
+ self.socket = None
+ else:
+ d = defer.Deferred()
+ d.callback(False)
+ return d
+
+ def shutdown(self, *args):
+ self.stop()
+ if self.daemon and reactor.running:
+ reactor.stop()
+
+ def _migrate_config_1_to_2(self, config):
+ config['language'] = CONFIG_DEFAULTS['language']
+ return config
+
+
+if __name__ == '__builtin__':
+ deluge_web = DelugeWeb()
+ application = service.Application('DelugeWeb')
+ sc = service.IServiceCollection(application)
+ i = internet.TCPServer(deluge_web.port, deluge_web.site)
+ i.setServiceParent(sc)
+elif __name__ == '__main__':
+ deluge_web = DelugeWeb()
+ deluge_web.start()
diff --git a/deluge/ui/web/themes/css/xtheme-access.css b/deluge/ui/web/themes/css/xtheme-access.css
new file mode 100644
index 0000000..4ade3d9
--- /dev/null
+++ b/deluge/ui/web/themes/css/xtheme-access.css
@@ -0,0 +1,1933 @@
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+body {
+ background-color: #16181a;
+ color: #fcfcfc;
+}
+
+.ext-el-mask {
+ background-color: #ccc;
+}
+
+.ext-el-mask-msg {
+ border-color: #223;
+ background-color: #3f4757;
+ background-image: url(../images/access/box/tb-blue.gif);
+}
+.ext-el-mask-msg div {
+ background-color: #232d38;
+ border-color: #556;
+ color: #fff;
+ font: normal 14px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-mask-loading div {
+ background-color: #232d38;
+ background-image: url(../images/access/grid/loading.gif);
+}
+
+.x-item-disabled {
+ color: #ddd;
+}
+
+.x-item-disabled * {
+ color: #ddd !important;
+}
+
+.x-splitbar-proxy {
+ background-color: #aaa;
+}
+
+.x-color-palette a {
+ border-color: #fff;
+}
+
+.x-color-palette a:hover,
+.x-color-palette a.x-color-palette-sel {
+ border-color: #8bb8f3;
+ background-color: #deecfd;
+}
+
+.x-color-palette em {
+ border-color: #aca899;
+}
+
+.x-ie-shadow {
+ background-color: #777;
+}
+
+.x-shadow .xsmc {
+ background-image: url(../images/default/shadow-c.png);
+}
+
+.x-shadow .xsml,
+.x-shadow .xsmr {
+ background-image: url(../images/default/shadow-lr.png);
+}
+
+.x-shadow .xstl,
+.x-shadow .xstc,
+.x-shadow .xstr,
+.x-shadow .xsbl,
+.x-shadow .xsbc,
+.x-shadow .xsbr {
+ background-image: url(../images/default/shadow.png);
+}
+
+.loading-indicator {
+ font-size: 14px;
+ background-image: url(../images/access/grid/loading.gif);
+}
+
+.x-spotlight {
+ background-color: #ccc;
+}
+.x-tab-panel-header,
+.x-tab-panel-footer {
+ background-color: #e18325;
+ border-color: #8db2e3;
+ overflow: hidden;
+ zoom: 1;
+}
+
+.x-tab-panel-header,
+.x-tab-panel-footer {
+ border-color: #222;
+}
+
+ul.x-tab-strip-top {
+ background-color: #343843;
+ background-image: url(../images/access/tabs/tab-strip-bg.gif);
+ border-bottom-color: #343d4e;
+}
+
+ul.x-tab-strip-bottom {
+ background-color: #343843;
+ background-image: url(../images/access/tabs/tab-strip-btm-bg.gif);
+ border-top-color: #343843;
+}
+
+.x-tab-panel-header-plain .x-tab-strip-spacer,
+.x-tab-panel-footer-plain .x-tab-strip-spacer {
+ border-color: #222;
+ background-color: #e18325;
+}
+
+.x-tab-strip span.x-tab-strip-text {
+ font: normal 14px tahoma, arial, helvetica;
+ color: #fff;
+}
+
+.x-tab-strip-over span.x-tab-strip-text {
+ color: #fff;
+}
+
+.x-tab-strip-active span.x-tab-strip-text {
+ color: #fff;
+ font-weight: bold;
+}
+
+.x-tab-strip-disabled .x-tabs-text {
+ color: #aaaaaa;
+}
+
+.x-tab-strip-top .x-tab-right,
+.x-tab-strip-top .x-tab-left,
+.x-tab-strip-top .x-tab-strip-inner {
+ background-image: url(../images/access/tabs/tabs-sprite.gif);
+}
+
+.x-tab-strip-bottom .x-tab-right {
+ background-image: url(../images/access/tabs/tab-btm-inactive-right-bg.gif);
+}
+
+.x-tab-strip-bottom .x-tab-left {
+ background-image: url(../images/access/tabs/tab-btm-inactive-left-bg.gif);
+}
+
+.x-tab-strip-bottom .x-tab-strip-active .x-tab-right {
+ background-image: url(../images/access/tabs/tab-btm-right-bg.gif);
+}
+
+.x-tab-strip-bottom .x-tab-strip-active .x-tab-left {
+ background-image: url(../images/access/tabs/tab-btm-left-bg.gif);
+}
+
+.x-tab-strip .x-tab-strip-closable a.x-tab-strip-close {
+ background-image: url(../images/access/tabs/tab-close.gif);
+}
+
+.x-tab-strip .x-tab-strip-closable a.x-tab-strip-close:hover {
+ background-image: url(../images/access/tabs/tab-close.gif);
+}
+
+.x-tab-panel-body {
+ border-color: #18181a;
+ background-color: #fff;
+}
+
+.x-tab-panel-body-top {
+ border-top: 0 none;
+}
+
+.x-tab-panel-body-bottom {
+ border-bottom: 0 none;
+}
+
+.x-tab-scroller-left {
+ background-image: url(../images/access/tabs/scroll-left.gif);
+ border-bottom-color: #8db2e3;
+}
+
+.x-tab-scroller-left-over {
+ background-position: 0 0;
+}
+
+.x-tab-scroller-left-disabled {
+ background-position: -18px 0;
+ opacity: 0.5;
+ -moz-opacity: 0.5;
+ filter: alpha(opacity=50);
+ cursor: default;
+}
+
+.x-tab-scroller-right {
+ background-image: url(../images/access/tabs/scroll-right.gif);
+ border-bottom-color: #8db2e3;
+}
+
+.x-tab-panel-bbar .x-toolbar,
+.x-tab-panel-tbar .x-toolbar {
+ border-color: #99bbe8;
+}
+.x-form-field {
+ font: normal 15px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-form-text,
+textarea.x-form-field {
+ color: #ffffff;
+ background-color: #33373d;
+ background-image: url(../images/access/form/text-bg.gif);
+ border-color: #737b8c;
+ border-width: 2px;
+}
+
+.ext-webkit .x-form-text,
+.ext-webkit textarea.x-form-field {
+ border-width: 2px;
+}
+
+.x-form-text,
+.ext-ie .x-form-file {
+ height: 26px;
+}
+
+.ext-strict .x-form-text {
+ height: 20px;
+}
+
+.x-form-select-one {
+ background-color: #fff;
+ border-color: #b5b8c8;
+}
+
+.x-form-check-group-label {
+ border-bottom: 1px solid #99bbe8;
+ color: #fff;
+}
+
+.x-editor .x-form-check-wrap {
+ background-color: #fff;
+}
+
+.x-form-field-wrap .x-form-trigger {
+ background-image: url(../images/access/form/trigger.gif);
+ border-bottom-color: #737b8c;
+ border-bottom-width: 2px;
+ height: 24px;
+ width: 20px;
+}
+
+.x-form-field-wrap .x-form-trigger.x-form-trigger-over {
+ border-bottom-color: #d97e27;
+}
+
+.x-form-field-wrap .x-form-trigger.x-form-trigger-click {
+ border-bottom-color: #c86e19;
+}
+
+.x-small-editor .x-form-field-wrap .x-form-trigger {
+ height: 24px;
+}
+
+.x-form-field-wrap .x-form-trigger-over {
+ background-position: -20px 0;
+}
+
+.x-form-field-wrap .x-form-trigger-click {
+ background-position: -40px 0;
+}
+
+.x-trigger-wrap-focus .x-form-trigger {
+ background-position: -60px 0;
+}
+
+.x-trigger-wrap-focus .x-form-trigger-over {
+ background-position: -80px 0;
+}
+
+.x-trigger-wrap-focus .x-form-trigger-click {
+ background-position: -100px 0;
+}
+
+.x-form-field-wrap .x-form-date-trigger {
+ background-image: url(../images/access/form/date-trigger.gif);
+}
+
+.x-form-field-wrap .x-form-clear-trigger {
+ background-image: url(../images/access/form/clear-trigger.gif);
+}
+
+.x-form-field-wrap .x-form-search-trigger {
+ background-image: url(../images/access/form/search-trigger.gif);
+}
+
+.x-trigger-wrap-focus .x-form-trigger {
+ border-bottom-color: #737b8c;
+}
+
+.x-item-disabled .x-form-trigger-over {
+ border-bottom-color: #b5b8c8;
+}
+
+.x-item-disabled .x-form-trigger-click {
+ border-bottom-color: #b5b8c8;
+}
+
+.x-form-focus,
+textarea.x-form-focus {
+ border-color: #ff9c33;
+}
+
+.x-form-invalid,
+textarea.x-form-invalid,
+.ext-webkit .x-form-invalid,
+.ext-webkit textarea.x-form-invalid {
+ background-color: #15171a;
+ background-image: url(../images/access/grid/invalid_line.gif);
+ border-color: #c30;
+}
+
+/*
+.ext-safari .x-form-invalid{
+ background-color:#fee;
+ border-color:#ff7870;
+}
+*/
+
+.x-form-inner-invalid,
+textarea.x-form-inner-invalid {
+ background-color: #fff;
+ background-image: url(../images/access/grid/invalid_line.gif);
+}
+
+.x-form-grow-sizer {
+ font: normal 15px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-form-item {
+ font: normal 15px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-form-invalid-msg {
+ color: #c0272b;
+ font: normal 14px tahoma, arial, helvetica, sans-serif;
+ background-image: url(../images/default/shared/warning.gif);
+}
+
+.x-form-empty-field {
+ color: #dadadd;
+}
+
+.x-small-editor .x-form-text {
+ height: 26px;
+}
+
+.x-small-editor .x-form-field {
+ font: normal 14px arial, tahoma, helvetica, sans-serif;
+}
+
+.ext-safari .x-small-editor .x-form-field {
+ font: normal 15px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-form-invalid-icon {
+ background-image: url(../images/access/form/exclamation.gif);
+ height: 25px;
+ width: 19px;
+ background-position: center right;
+}
+
+.x-fieldset {
+ border-color: #737b8c;
+}
+
+.x-fieldset legend {
+ font: bold 14px tahoma, arial, helvetica, sans-serif;
+ color: #fff;
+}
+.x-btn {
+ font: normal 14px tahoma, verdana, helvetica;
+}
+
+.x-btn button {
+ font: normal 14px arial, tahoma, verdana, helvetica;
+ color: #fffffa;
+ padding-left: 6px !important;
+ padding-right: 6px !important;
+}
+
+.x-btn-over .x-btn button {
+ color: #fff;
+}
+
+.x-btn-noicon .x-btn-small .x-btn-text,
+.x-btn-text-icon .x-btn-icon-small-left .x-btn-text,
+.x-btn-icon .x-btn-small .x-btn-text,
+.x-btn-text-icon .x-btn-icon-small-right .x-btn-text {
+ height: 18px;
+}
+
+.x-btn-icon .x-btn-small .x-btn-text {
+ width: 18px;
+}
+
+.x-btn-text-icon .x-btn-icon-small-left .x-btn-text {
+ padding-left: 21px !important;
+}
+
+.x-btn-text-icon .x-btn-icon-small-right .x-btn-text {
+ padding-right: 21px !important;
+}
+
+.x-btn-text-icon .x-btn-icon-medium-left .x-btn-text {
+ padding-left: 29px !important;
+}
+
+.x-btn-text-icon .x-btn-icon-medium-right .x-btn-text {
+ padding-right: 29px !important;
+}
+
+.x-btn-text-icon .x-btn-icon-large-left .x-btn-text {
+ padding-left: 37px !important;
+}
+
+.x-btn-text-icon .x-btn-icon-large-right .x-btn-text {
+ padding-right: 37px !important;
+}
+
+.x-btn em {
+ font-style: normal;
+ font-weight: normal;
+}
+
+.x-btn-tl,
+.x-btn-tr,
+.x-btn-tc,
+.x-btn-ml,
+.x-btn-mr,
+.x-btn-mc,
+.x-btn-bl,
+.x-btn-br,
+.x-btn-bc {
+ background-image: url(../images/access/button/btn.gif);
+}
+
+.x-btn-click .x-btn-text,
+.x-btn-menu-active .x-btn-text,
+.x-btn-pressed .x-btn-text {
+ color: #fff;
+}
+
+.x-btn-disabled * {
+ color: #eee !important;
+}
+
+.x-btn-mc em.x-btn-arrow {
+ background-image: url(../images/access/button/arrow.gif);
+ padding-right: 13px;
+}
+
+.x-btn-mc em.x-btn-split {
+ background-image: url(../images/access/button/s-arrow.gif);
+ padding-right: 20px;
+}
+
+.x-btn-over .x-btn-mc em.x-btn-split,
+.x-btn-click .x-btn-mc em.x-btn-split,
+.x-btn-menu-active .x-btn-mc em.x-btn-split,
+.x-btn-pressed .x-btn-mc em.x-btn-split {
+ background-image: url(../images/access/button/s-arrow-o.gif);
+}
+
+.x-btn-mc em.x-btn-arrow-bottom {
+ background-image: url(../images/access/button/s-arrow-b-noline.gif);
+}
+
+.x-btn-mc em.x-btn-split-bottom {
+ background-image: url(../images/access/button/s-arrow-b.gif);
+}
+
+.x-btn-over .x-btn-mc em.x-btn-split-bottom,
+.x-btn-click .x-btn-mc em.x-btn-split-bottom,
+.x-btn-menu-active .x-btn-mc em.x-btn-split-bottom,
+.x-btn-pressed .x-btn-mc em.x-btn-split-bottom {
+ background-image: url(../images/access/button/s-arrow-bo.gif);
+}
+
+.x-btn-group-header {
+ color: #d2d2d2;
+}
+
+.x-btn-group-tc {
+ background-image: url(../images/access/button/group-tb.gif);
+}
+
+.x-btn-group-tl {
+ background-image: url(../images/access/button/group-cs.gif);
+}
+
+.x-btn-group-tr {
+ background-image: url(../images/access/button/group-cs.gif);
+}
+
+.x-btn-group-bc {
+ background-image: url(../images/access/button/group-tb.gif);
+}
+
+.x-btn-group-bl {
+ background-image: url(../images/access/button/group-cs.gif);
+}
+
+.x-btn-group-br {
+ background-image: url(../images/access/button/group-cs.gif);
+}
+
+.x-btn-group-ml {
+ background-image: url(../images/access/button/group-lr.gif);
+}
+
+.x-btn-group-mr {
+ background-image: url(../images/access/button/group-lr.gif);
+}
+
+.x-btn-group-notitle .x-btn-group-tc {
+ background-image: url(../images/access/button/group-tb.gif);
+}
+.x-toolbar {
+ border-color: #18181a;
+ background-color: #393d4e;
+ background-image: url(../images/access/toolbar/bg.gif);
+}
+
+.x-toolbar td,
+.x-toolbar span,
+.x-toolbar input,
+.x-toolbar div,
+.x-toolbar select,
+.x-toolbar label {
+ font: normal 14px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-toolbar .x-item-disabled {
+ color: gray;
+}
+
+.x-toolbar .x-item-disabled * {
+ color: gray;
+}
+
+.x-toolbar .x-btn-mc em.x-btn-split {
+ background-image: url(../images/access/button/s-arrow-noline.gif);
+}
+
+.x-toolbar .x-btn-over .x-btn-mc em.x-btn-split,
+.x-toolbar .x-btn-click .x-btn-mc em.x-btn-split,
+.x-toolbar .x-btn-menu-active .x-btn-mc em.x-btn-split,
+.x-toolbar .x-btn-pressed .x-btn-mc em.x-btn-split {
+ background-image: url(../images/access/button/s-arrow-o.gif);
+}
+
+.x-toolbar .x-btn-mc em.x-btn-split-bottom {
+ background-image: url(../images/access/button/s-arrow-b-noline.gif);
+}
+
+.x-toolbar .x-btn-over .x-btn-mc em.x-btn-split-bottom,
+.x-toolbar .x-btn-click .x-btn-mc em.x-btn-split-bottom,
+.x-toolbar .x-btn-menu-active .x-btn-mc em.x-btn-split-bottom,
+.x-toolbar .x-btn-pressed .x-btn-mc em.x-btn-split-bottom {
+ background-image: url(../images/access/button/s-arrow-bo.gif);
+}
+
+.x-toolbar .xtb-sep {
+ background-image: url(../images/access/grid/grid-blue-split.gif);
+}
+
+.x-toolbar .x-btn {
+ padding-left: 3px;
+ padding-right: 3px;
+}
+
+.x-toolbar .x-btn-mc em.x-btn-arrow {
+ padding-right: 10px;
+}
+
+.x-toolbar .x-btn-text-icon .x-btn-icon-small-left .x-btn-text {
+ padding-left: 18px !important;
+}
+
+.x-toolbar .x-btn-mc em.x-btn-split {
+ padding-right: 14px;
+}
+
+.x-tbar-page-first {
+ background-image: url(../images/access/grid/page-first.gif) !important;
+}
+
+.x-tbar-loading {
+ background-image: url(../images/access/grid/refresh.gif) !important;
+}
+
+.x-tbar-page-last {
+ background-image: url(../images/access/grid/page-last.gif) !important;
+}
+
+.x-tbar-page-next {
+ background-image: url(../images/access/grid/page-next.gif) !important;
+}
+
+.x-tbar-page-prev {
+ background-image: url(../images/access/grid/page-prev.gif) !important;
+}
+
+.x-item-disabled .x-tbar-loading {
+ background-image: url(../images/access/grid/loading.gif) !important;
+}
+
+.x-item-disabled .x-tbar-page-first {
+ background-image: url(../images/access/grid/page-first-disabled.gif) !important;
+}
+
+.x-item-disabled .x-tbar-page-last {
+ background-image: url(../images/access/grid/page-last-disabled.gif) !important;
+}
+
+.x-item-disabled .x-tbar-page-next {
+ background-image: url(../images/access/grid/page-next-disabled.gif) !important;
+}
+
+.x-item-disabled .x-tbar-page-prev {
+ background-image: url(../images/access/grid/page-prev-disabled.gif) !important;
+}
+
+.x-paging-info {
+ color: #444;
+}
+
+.x-toolbar-more-icon {
+ background-image: url(../images/access/toolbar/more.gif) !important;
+}
+
+.x-statusbar .x-status-busy {
+ background-image: url(../images/access/grid/loading.gif);
+}
+
+.x-statusbar .x-status-text-panel {
+ border-color: #99bbe8 #fff #fff #99bbe8;
+}
+.x-resizable-handle {
+ background-color: #fff;
+ color: #000;
+}
+
+.x-resizable-over .x-resizable-handle-east,
+.x-resizable-pinned .x-resizable-handle-east,
+.x-resizable-over .x-resizable-handle-west,
+.x-resizable-pinned .x-resizable-handle-west {
+ background-image: url(../images/access/sizer/e-handle.gif);
+}
+
+.x-resizable-over .x-resizable-handle-south,
+.x-resizable-pinned .x-resizable-handle-south,
+.x-resizable-over .x-resizable-handle-north,
+.x-resizable-pinned .x-resizable-handle-north {
+ background-image: url(../images/access/sizer/s-handle.gif);
+}
+
+.x-resizable-over .x-resizable-handle-north,
+.x-resizable-pinned .x-resizable-handle-north {
+ background-image: url(../images/access/sizer/s-handle.gif);
+}
+.x-resizable-over .x-resizable-handle-southeast,
+.x-resizable-pinned .x-resizable-handle-southeast {
+ background-image: url(../images/access/sizer/se-handle.gif);
+}
+.x-resizable-over .x-resizable-handle-northwest,
+.x-resizable-pinned .x-resizable-handle-northwest {
+ background-image: url(../images/access/sizer/nw-handle.gif);
+}
+.x-resizable-over .x-resizable-handle-northeast,
+.x-resizable-pinned .x-resizable-handle-northeast {
+ background-image: url(../images/access/sizer/ne-handle.gif);
+}
+.x-resizable-over .x-resizable-handle-southwest,
+.x-resizable-pinned .x-resizable-handle-southwest {
+ background-image: url(../images/access/sizer/sw-handle.gif);
+}
+.x-resizable-proxy {
+ border-color: #3b5a82;
+}
+.x-resizable-overlay {
+ background-color: #fff;
+}
+.x-grid3 {
+ background-color: #1f2933;
+}
+
+.x-grid-panel .x-panel-mc .x-panel-body {
+ border-color: #223;
+}
+
+.x-grid3-hd-row td,
+.x-grid3-row td,
+.x-grid3-summary-row td {
+ font: normal 14px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-grid3-hd-row td {
+ border-left-color: #556;
+ border-right-color: #223;
+}
+
+.x-grid-row-loading {
+ background-color: #fff;
+ background-image: url(../images/default/shared/loading-balls.gif);
+}
+
+.x-grid3-row {
+ border: 0 none;
+ border-bottom: 1px solid #111;
+ border-right: 1px solid #1a1a1c;
+}
+
+.x-grid3-row-alt {
+ background-color: #1b232b;
+}
+
+.x-grid3-row-over {
+ background-color: #7e5530;
+}
+
+.x-grid3-resize-proxy {
+ background-color: #777;
+}
+
+.x-grid3-resize-marker {
+ background-color: #777;
+}
+
+.x-grid3-header {
+ background-color: #3b3f50;
+ background-image: url(../images/access/grid/grid3-hrow.gif);
+}
+
+.x-grid3-header-pop {
+ border-left-color: #d0d0d0;
+}
+
+.x-grid3-header-pop-inner {
+ border-left-color: #eee;
+ background-image: url(../images/default/grid/hd-pop.gif);
+}
+
+td.x-grid3-hd-over,
+td.sort-desc,
+td.sort-asc,
+td.x-grid3-hd-menu-open {
+ border-left-color: #889;
+ border-right-color: #445;
+}
+
+td.x-grid3-hd-over .x-grid3-hd-inner,
+td.sort-desc .x-grid3-hd-inner,
+td.sort-asc .x-grid3-hd-inner,
+td.x-grid3-hd-menu-open .x-grid3-hd-inner {
+ background-color: #4e628a;
+ background-image: url(../images/access/grid/grid3-hrow-over.gif);
+}
+
+.x-grid3-cell-inner,
+.x-grid3-hd-inner {
+ color: #fff;
+}
+
+.sort-asc .x-grid3-sort-icon {
+ background-image: url(../images/access/grid/sort_asc.gif);
+ width: 15px;
+ height: 9px;
+ margin-left: 5px;
+}
+
+.sort-desc .x-grid3-sort-icon {
+ background-image: url(../images/access/grid/sort_desc.gif);
+ width: 15px;
+ height: 9px;
+ margin-left: 5px;
+}
+
+.x-grid3-cell-text,
+.x-grid3-hd-text {
+ color: #fff;
+}
+
+.x-grid3-split {
+ background-image: url(../images/default/grid/grid-split.gif);
+}
+
+.x-grid3-hd-text {
+ color: #fff;
+}
+
+.x-dd-drag-proxy .x-grid3-hd-inner {
+ background-color: #ebf3fd;
+ background-image: url(../images/access/grid/grid3-hrow-over.gif);
+ border-color: #aaccf6;
+}
+
+.col-move-top {
+ background-image: url(../images/default/grid/col-move-top.gif);
+}
+
+.col-move-bottom {
+ background-image: url(../images/default/grid/col-move-bottom.gif);
+}
+
+.x-grid3-row-selected {
+ background-color: #e5872c !important;
+ background-image: none;
+ border-style: solid;
+}
+
+.x-grid3-row-selected .x-grid3-cell {
+ color: #fff;
+}
+
+.x-grid3-cell-selected {
+ background-color: #ffa340 !important;
+ color: #fff;
+}
+
+.x-grid3-cell-selected span {
+ color: #fff !important;
+}
+
+.x-grid3-cell-selected .x-grid3-cell-text {
+ color: #fff;
+}
+
+.x-grid3-locked td.x-grid3-row-marker,
+.x-grid3-locked .x-grid3-row-selected td.x-grid3-row-marker {
+ background-color: #ebeadb !important;
+ background-image: url(../images/default/grid/grid-hrow.gif) !important;
+ color: #fff;
+ border-top-color: #fff;
+ border-right-color: #6fa0df !important;
+}
+
+.x-grid3-locked td.x-grid3-row-marker div,
+.x-grid3-locked .x-grid3-row-selected td.x-grid3-row-marker div {
+ color: #fff !important;
+}
+
+.x-grid3-dirty-cell {
+ background-image: url(../images/access/grid/dirty.gif);
+}
+
+.x-grid3-topbar,
+.x-grid3-bottombar {
+ font: normal 14px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-grid3-bottombar .x-toolbar {
+ border-top-color: #a9bfd3;
+}
+
+.x-props-grid .x-grid3-td-name .x-grid3-cell-inner {
+ background-image: url(../images/access/grid/grid3-special-col-bg.gif) !important;
+ color: #fff !important;
+}
+.x-props-grid .x-grid3-td-value {
+ color: #fff !important;
+}
+
+.x-props-grid .x-grid3-body .x-grid3-td-name {
+ background-color: #263240 !important;
+ border-right-color: #223;
+}
+
+.xg-hmenu-sort-asc .x-menu-item-icon {
+ background-image: url(../images/access/grid/hmenu-asc.gif);
+}
+
+.xg-hmenu-sort-desc .x-menu-item-icon {
+ background-image: url(../images/access/grid/hmenu-desc.gif);
+}
+
+.xg-hmenu-lock .x-menu-item-icon {
+ background-image: url(../images/access/grid/hmenu-lock.gif);
+}
+
+.xg-hmenu-unlock .x-menu-item-icon {
+ background-image: url(../images/access/grid/hmenu-unlock.gif);
+}
+
+.x-grid3-hd-btn {
+ background-color: #c2c9d0;
+ background-image: url(../images/access/grid/grid3-hd-btn.gif);
+}
+
+.x-grid3-body .x-grid3-td-expander {
+ background-image: url(../images/access/grid/grid3-special-col-bg.gif);
+}
+
+.x-grid3-row-expander {
+ background-image: url(../images/access/grid/row-expand-sprite.gif);
+}
+
+.x-grid3-body .x-grid3-td-checker {
+ background-image: url(../images/access/grid/grid3-special-col-bg.gif);
+}
+
+.x-grid3-row-checker,
+.x-grid3-hd-checker {
+ background-image: url(../images/default/grid/row-check-sprite.gif);
+}
+
+.x-grid3-body .x-grid3-td-numberer {
+ background-image: url(../images/access/grid/grid3-special-col-bg.gif);
+}
+
+.x-grid3-body .x-grid3-td-numberer .x-grid3-cell-inner {
+ color: #fff;
+}
+
+.x-grid3-body .x-grid3-td-row-icon {
+ background-image: url(../images/access/grid/grid3-special-col-bg.gif);
+}
+
+.x-grid3-body .x-grid3-row-selected .x-grid3-td-numberer,
+.x-grid3-body .x-grid3-row-selected .x-grid3-td-checker,
+.x-grid3-body .x-grid3-row-selected .x-grid3-td-expander {
+ background-image: url(../images/access/grid/grid3-special-col-sel-bg.gif);
+}
+
+.x-grid3-check-col {
+ background-image: url(../images/default/menu/unchecked.gif);
+}
+
+.x-grid3-check-col-on {
+ background-image: url(../images/default/menu/checked.gif);
+}
+
+.x-grid-group,
+.x-grid-group-body,
+.x-grid-group-hd {
+ zoom: 1;
+}
+
+.x-grid-group-hd {
+ border-bottom-color: #4e628a;
+}
+
+.x-grid-group-hd div.x-grid-group-title {
+ background-image: url(../images/access/grid/group-collapse.gif);
+ background-position: 3px 6px;
+ color: #ffd;
+ font: bold 14px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-grid-group-collapsed .x-grid-group-hd div.x-grid-group-title {
+ background-image: url(../images/access/grid/group-expand.gif);
+}
+
+.x-group-by-icon {
+ background-image: url(../images/default/grid/group-by.gif);
+}
+
+.x-cols-icon {
+ background-image: url(../images/default/grid/columns.gif);
+}
+
+.x-show-groups-icon {
+ background-image: url(../images/default/grid/group-by.gif);
+}
+
+.x-grid-empty {
+ color: gray;
+ font: normal 14px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-grid-with-col-lines .x-grid3-row td.x-grid3-cell {
+ border-right-color: #ededed;
+}
+
+.x-grid-with-col-lines .x-grid3-row {
+ border-top-color: #ededed;
+}
+
+.x-grid-with-col-lines .x-grid3-row-selected {
+ border-top-color: #a3bae9;
+}
+.x-dd-drag-ghost {
+ color: #000;
+ font: normal 14px arial, helvetica, sans-serif;
+ border-color: #ddd #bbb #bbb #ddd;
+ background-color: #fff;
+}
+
+.x-dd-drop-nodrop .x-dd-drop-icon {
+ background-image: url(../images/default/dd/drop-no.gif);
+}
+
+.x-dd-drop-ok .x-dd-drop-icon {
+ background-image: url(../images/default/dd/drop-yes.gif);
+}
+
+.x-dd-drop-ok-add .x-dd-drop-icon {
+ background-image: url(../images/default/dd/drop-add.gif);
+}
+
+.x-view-selector {
+ background-color: #c3daf9;
+ border-color: #3399bb;
+}
+.x-tree-node-expanded .x-tree-node-icon {
+ background-image: url(../images/access/tree/folder-open.gif);
+}
+
+.x-tree-node-leaf .x-tree-node-icon {
+ background-image: url(../images/default/tree/leaf.gif);
+}
+
+.x-tree-node-collapsed .x-tree-node-icon {
+ background-image: url(../images/access/tree/folder.gif);
+}
+
+.x-tree-node-loading .x-tree-node-icon {
+ background-image: url(../images/default/tree/loading.gif) !important;
+}
+
+.x-tree-node .x-tree-node-inline-icon {
+ background-image: none;
+}
+
+.x-tree-node-loading a span {
+ font-style: italic;
+ color: #444444;
+}
+
+.ext-ie .x-tree-node-el input {
+ width: 14px;
+ height: 14px;
+}
+
+.x-tree-lines .x-tree-elbow {
+ background-image: url(../images/access/tree/elbow.gif);
+}
+
+.x-tree-lines .x-tree-elbow-plus {
+ background-image: url(../images/access/tree/elbow-plus.gif);
+}
+
+.x-tree-lines .x-tree-elbow-minus {
+ background-image: url(../images/access/tree/elbow-minus.gif);
+}
+
+.x-tree-lines .x-tree-elbow-end {
+ background-image: url(../images/access/tree/elbow-end.gif);
+}
+
+.x-tree-lines .x-tree-elbow-end-plus {
+ background-image: url(../images/access/tree/elbow-end-plus.gif);
+}
+
+.x-tree-lines .x-tree-elbow-end-minus {
+ background-image: url(../images/access/tree/elbow-end-minus.gif);
+}
+
+.x-tree-lines .x-tree-elbow-line {
+ background-image: url(../images/access/tree/elbow-line.gif);
+}
+
+.x-tree-no-lines .x-tree-elbow-plus {
+ background-image: url(../images/access/tree/elbow-plus-nl.gif);
+}
+
+.x-tree-no-lines .x-tree-elbow-minus {
+ background-image: url(../images/access/tree/elbow-minus-nl.gif);
+}
+
+.x-tree-no-lines .x-tree-elbow-end-plus {
+ background-image: url(../images/access/tree/elbow-end-plus-nl.gif);
+}
+
+.x-tree-no-lines .x-tree-elbow-end-minus {
+ background-image: url(../images/access/tree/elbow-end-minus-nl.gif);
+}
+
+.x-tree-arrows .x-tree-elbow-plus {
+ background-image: url(../images/access/tree/arrows.gif);
+}
+
+.x-tree-arrows .x-tree-elbow-minus {
+ background-image: url(../images/access/tree/arrows.gif);
+}
+
+.x-tree-arrows .x-tree-elbow-end-plus {
+ background-image: url(../images/access/tree/arrows.gif);
+}
+
+.x-tree-arrows .x-tree-elbow-end-minus {
+ background-image: url(../images/access/tree/arrows.gif);
+}
+
+.x-tree-node {
+ color: #000;
+ font: normal 14px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-tree-node a,
+.x-dd-drag-ghost a {
+ color: #fff;
+}
+
+.x-tree-node a span,
+.x-dd-drag-ghost a span {
+ color: #fff;
+}
+
+.x-tree-node .x-tree-selected a,
+.x-dd-drag-ghost a {
+ color: #fff;
+}
+
+.x-tree-node .x-tree-selected a span,
+.x-dd-drag-ghost a span {
+ color: #fff;
+}
+
+.x-tree-node .x-tree-node-disabled a span {
+ color: gray !important;
+}
+
+.x-tree-node div.x-tree-drag-insert-below {
+ border-bottom-color: #36c;
+}
+
+.x-tree-node div.x-tree-drag-insert-above {
+ border-top-color: #36c;
+}
+
+.x-tree-dd-underline .x-tree-node div.x-tree-drag-insert-below a {
+ border-bottom-color: #36c;
+}
+
+.x-tree-dd-underline .x-tree-node div.x-tree-drag-insert-above a {
+ border-top-color: #36c;
+}
+
+.x-tree-node .x-tree-drag-append a span {
+ background-color: #ddd;
+ border-color: gray;
+}
+
+.x-tree-node .x-tree-node-over {
+ background-color: #7e5530;
+}
+
+.x-tree-node .x-tree-selected {
+ background-color: #e5872c;
+}
+
+.x-tree-drop-ok-append .x-dd-drop-icon {
+ background-image: url(../images/default/tree/drop-add.gif);
+}
+
+.x-tree-drop-ok-above .x-dd-drop-icon {
+ background-image: url(../images/default/tree/drop-over.gif);
+}
+
+.x-tree-drop-ok-below .x-dd-drop-icon {
+ background-image: url(../images/default/tree/drop-under.gif);
+}
+
+.x-tree-drop-ok-between .x-dd-drop-icon {
+ background-image: url(../images/default/tree/drop-between.gif);
+}
+.x-date-picker {
+ border-color: #737b8c;
+ background-color: #21252e;
+}
+
+.x-date-middle,
+.x-date-left,
+.x-date-right {
+ background-image: url(../images/access/shared/hd-sprite.gif);
+ color: #fff;
+ font: bold 14px 'sans serif', tahoma, verdana, helvetica;
+}
+
+.x-date-middle .x-btn .x-btn-text {
+ color: #fff;
+}
+
+.x-date-middle .x-btn-mc em.x-btn-arrow {
+ background-image: url(../images/access/toolbar/btn-arrow-light.gif);
+}
+
+.x-date-right a {
+ background-image: url(../images/access/shared/right-btn.gif);
+}
+
+.x-date-left a {
+ background-image: url(../images/access/shared/left-btn.gif);
+}
+
+.x-date-inner th {
+ background-color: #363d4a;
+ background-image: url(../images/access/toolbar/bg.gif);
+ border-bottom-color: #535b5c;
+ font: normal 13px arial, helvetica, tahoma, sans-serif;
+ color: #fff;
+}
+
+.x-date-inner td {
+ border-color: #112;
+}
+
+.x-date-inner a {
+ font: normal 14px arial, helvetica, tahoma, sans-serif;
+ color: #fff;
+ padding: 2px 7px 1px 3px; /* Structure to account for larger, bolder fonts in Access theme. */
+}
+
+.x-date-inner .x-date-active {
+ color: #000;
+}
+
+.x-date-inner .x-date-selected a {
+ background-color: #e5872c;
+ background-image: none;
+ border-color: #864900;
+ padding: 1px 6px 1px 2px; /* Structure to account for larger, bolder fonts in Access theme. */
+}
+
+.x-date-inner .x-date-today a {
+ border-color: #99a;
+}
+
+.x-date-inner .x-date-selected span {
+ font-weight: bold;
+}
+
+.x-date-inner .x-date-prevday a,
+.x-date-inner .x-date-nextday a {
+ color: #aaa;
+}
+
+.x-date-bottom {
+ border-top-color: #737b8c;
+ background-color: #464d5a;
+ background-image: url(../images/access/shared/glass-bg.gif);
+}
+
+.x-date-inner a:hover,
+.x-date-inner .x-date-disabled a:hover {
+ color: #fff;
+ background-color: #7e5530;
+}
+
+.x-date-inner .x-date-disabled a {
+ background-color: #eee;
+ color: #bbb;
+}
+
+.x-date-mmenu {
+ background-color: #eee !important;
+}
+
+.x-date-mmenu .x-menu-item {
+ font-size: 13px;
+ color: #000;
+}
+
+.x-date-mp {
+ background-color: #21252e;
+}
+
+.x-date-mp td {
+ font: normal 14px arial, helvetica, tahoma, sans-serif;
+}
+
+.x-date-mp-btns button {
+ background-color: #083772;
+ color: #fff;
+ border-color: #3366cc #000055 #000055 #3366cc;
+ font: normal 14px arial, helvetica, tahoma, sans-serif;
+}
+
+.x-date-mp-btns {
+ background-color: #dfecfb;
+ background-image: url(../images/access/shared/glass-bg.gif);
+}
+
+.x-date-mp-btns td {
+ border-top-color: #c5d2df;
+}
+
+td.x-date-mp-month a,
+td.x-date-mp-year a {
+ color: #fff;
+}
+
+td.x-date-mp-month a:hover,
+td.x-date-mp-year a:hover {
+ color: #fff;
+ background-color: #7e5530;
+}
+
+td.x-date-mp-sel a {
+ background-color: #e5872c;
+ background-image: none;
+ border-color: #864900;
+}
+
+.x-date-mp-ybtn a {
+ background-image: url(../images/access/panel/tool-sprites.gif);
+}
+
+td.x-date-mp-sep {
+ border-right-color: #c5d2df;
+}
+.x-tip .x-tip-close {
+ background-image: url(../images/access/qtip/close.gif);
+}
+
+.x-tip .x-tip-tc,
+.x-tip .x-tip-tl,
+.x-tip .x-tip-tr,
+.x-tip .x-tip-bc,
+.x-tip .x-tip-bl,
+.x-tip .x-tip-br,
+.x-tip .x-tip-ml,
+.x-tip .x-tip-mr {
+ background-image: url(../images/access/qtip/tip-sprite.gif);
+}
+
+.x-tip .x-tip-mc {
+ font: normal 14px tahoma, arial, helvetica, sans-serif;
+}
+.x-tip .x-tip-ml {
+ background-color: #fff;
+}
+
+.x-tip .x-tip-header-text {
+ font: bold 14px tahoma, arial, helvetica, sans-serif;
+ color: #ffd;
+}
+
+.x-tip .x-tip-body {
+ font: normal 14px tahoma, arial, helvetica, sans-serif;
+ color: #000;
+}
+
+.x-form-invalid-tip .x-tip-tc,
+.x-form-invalid-tip .x-tip-tl,
+.x-form-invalid-tip .x-tip-tr,
+.x-form-invalid-tip .x-tip-bc,
+.x-form-invalid-tip .x-tip-bl,
+.x-form-invalid-tip .x-tip-br,
+.x-form-invalid-tip .x-tip-ml,
+.x-form-invalid-tip .x-tip-mr {
+ background-image: url(../images/default/form/error-tip-corners.gif);
+}
+
+.x-form-invalid-tip .x-tip-body {
+ background-image: url(../images/access/form/exclamation.gif);
+}
+
+.x-tip-anchor {
+ background-image: url(../images/access/qtip/tip-anchor-sprite.gif);
+}
+.x-menu {
+ border-color: #222;
+ background-color: #414551;
+ background-image: url(../images/access/menu/menu.gif);
+}
+
+.x-menu-nosep {
+ background-image: none;
+}
+
+.x-menu-list-item {
+ font: normal 14px tahoma, arial, sans-serif;
+}
+
+.x-menu-item-arrow {
+ background-image: url(../images/access/menu/menu-parent.gif);
+}
+
+.x-menu-sep {
+ background-color: #223;
+ border-bottom-color: #666;
+}
+
+a.x-menu-item {
+ color: #fffff6;
+}
+
+.x-menu-item-active {
+ background-color: #f09134;
+ background-image: none;
+ border-color: #b36427;
+}
+
+.x-menu-item-active a.x-menu-item {
+ border-color: #b36427;
+}
+
+.x-menu-check-item .x-menu-item-icon {
+ background-image: url(../images/default/menu/unchecked.gif);
+}
+
+.x-menu-item-checked .x-menu-item-icon {
+ background-image: url(../images/default/menu/checked.gif);
+}
+
+.x-menu-item-checked .x-menu-group-item .x-menu-item-icon {
+ background-image: url(../images/access/menu/group-checked.gif);
+}
+
+.x-menu-group-item .x-menu-item-icon {
+ background-image: none;
+}
+
+.x-menu-plain {
+ background-color: #fff !important;
+}
+
+.x-menu .x-date-picker {
+ border-color: #a3bad9;
+}
+
+.x-cycle-menu .x-menu-item-checked {
+ border-color: #a3bae9 !important;
+ background-color: #def8f6;
+}
+
+.x-menu-scroller-top {
+ background-image: url(../images/default/layout/mini-top.gif);
+}
+
+.x-menu-scroller-bottom {
+ background-image: url(../images/default/layout/mini-bottom.gif);
+}
+.x-box-tl {
+ background-image: url(../images/default/box/corners.gif);
+}
+
+.x-box-tc {
+ background-image: url(../images/default/box/tb.gif);
+}
+
+.x-box-tr {
+ background-image: url(../images/default/box/corners.gif);
+}
+
+.x-box-ml {
+ background-image: url(../images/default/box/l.gif);
+}
+
+.x-box-mc {
+ background-color: #eee;
+ background-image: url(../images/default/box/tb.gif);
+ font-family: 'Myriad Pro', 'Myriad Web', 'Tahoma', 'Helvetica', 'Arial',
+ sans-serif;
+ color: #393939;
+ font-size: 15px;
+}
+
+.x-box-mc h3 {
+ font-size: 18px;
+ font-weight: bold;
+}
+
+.x-box-mr {
+ background-image: url(../images/default/box/r.gif);
+}
+
+.x-box-bl {
+ background-image: url(../images/default/box/corners.gif);
+}
+
+.x-box-bc {
+ background-image: url(../images/default/box/tb.gif);
+}
+
+.x-box-br {
+ background-image: url(../images/default/box/corners.gif);
+}
+
+.x-box-blue .x-box-bl,
+.x-box-blue .x-box-br,
+.x-box-blue .x-box-tl,
+.x-box-blue .x-box-tr {
+ background-image: url(../images/default/box/corners-blue.gif);
+}
+
+.x-box-blue .x-box-bc,
+.x-box-blue .x-box-mc,
+.x-box-blue .x-box-tc {
+ background-image: url(../images/default/box/tb-blue.gif);
+}
+
+.x-box-blue .x-box-mc {
+ background-color: #c3daf9;
+}
+
+.x-box-blue .x-box-mc h3 {
+ color: #17385b;
+}
+
+.x-box-blue .x-box-ml {
+ background-image: url(../images/default/box/l-blue.gif);
+}
+
+.x-box-blue .x-box-mr {
+ background-image: url(../images/default/box/r-blue.gif);
+}
+.x-combo-list {
+ border: 2px solid #232732;
+ background-color: #555566;
+ font: normal 15px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-combo-list-inner {
+ background-color: #414551;
+}
+
+.x-combo-list-hd {
+ font: bold 14px tahoma, arial, helvetica, sans-serif;
+ color: #fff;
+ background-image: url(../images/default/layout/panel-title-light-bg.gif);
+ border-bottom-color: #98c0f4;
+}
+
+.x-resizable-pinned .x-combo-list-inner {
+ border-bottom-color: #98c0f4;
+}
+
+.x-combo-list-item {
+ border-color: #556;
+}
+
+.x-combo-list .x-combo-selected {
+ border-color: #e5872c !important;
+ background-color: #e5872c;
+}
+
+.x-combo-list .x-toolbar {
+ border-top-color: #98c0f4;
+}
+
+.x-combo-list-small {
+ font: normal 14px tahoma, arial, helvetica, sans-serif;
+}
+.x-panel {
+ border-color: #18181a;
+ font-size: 14px;
+}
+
+.x-panel-header {
+ color: #fff;
+ font-weight: bold;
+ font-size: 14px;
+ font-family: tahoma, arial, verdana, sans-serif;
+ border-color: #18181a;
+ background-image: url(../images/access/panel/white-top-bottom.gif);
+}
+
+.x-panel-body {
+ color: #fffff6;
+ border-color: #18181a;
+ background-color: #232d38;
+}
+
+.x-tab-panel .x-panel-body {
+ color: #fffff6;
+ border-color: #18181a;
+ background-color: #1f2730;
+}
+
+.x-panel-bbar .x-toolbar,
+.x-panel-tbar .x-toolbar {
+ border-color: #223;
+}
+
+.x-panel-tbar-noheader .x-toolbar,
+.x-panel-mc .x-panel-tbar .x-toolbar {
+ border-top-color: #223;
+}
+
+.x-panel-body-noheader,
+.x-panel-mc .x-panel-body {
+ border-top-color: #223;
+}
+
+.x-panel-tl .x-panel-header {
+ color: #fff;
+ font: bold 14px tahoma, arial, verdana, sans-serif;
+}
+
+.x-panel-tc {
+ background-image: url(../images/access/panel/top-bottom.gif);
+}
+
+.x-panel-tl,
+.x-panel-tr,
+.x-panel-bl,
+.x-panel-br {
+ background-image: url(../images/access/panel/corners-sprite.gif);
+ border-bottom-color: #222224;
+}
+
+.x-panel-bc {
+ background-image: url(../images/access/panel/top-bottom.gif);
+}
+
+.x-panel-mc {
+ font: normal 14px tahoma, arial, helvetica, sans-serif;
+ background-color: #3f4757;
+}
+
+.x-panel-ml {
+ background-image: url(../images/access/panel/left-right.gif);
+}
+
+.x-panel-mr {
+ background-image: url(../images/access/panel/left-right.gif);
+}
+
+.x-tool {
+ background-image: url(../images/access/panel/tool-sprites.gif);
+}
+
+.x-panel-ghost {
+ background-color: #3f4757;
+}
+
+.x-panel-ghost ul {
+ border-color: #18181a;
+}
+
+.x-panel-dd-spacer {
+ border-color: #18181a;
+}
+
+.x-panel-fbar td,
+.x-panel-fbar span,
+.x-panel-fbar input,
+.x-panel-fbar div,
+.x-panel-fbar select,
+.x-panel-fbar label {
+ font: normal 14px arial, tahoma, helvetica, sans-serif;
+}
+.x-window-proxy {
+ background-color: #1f2833;
+ border-color: #18181a;
+}
+
+.x-window-tl .x-window-header {
+ color: #fff;
+ font: bold 14px tahoma, arial, verdana, sans-serif;
+}
+
+.x-window-tc {
+ background-image: url(../images/access/window/top-bottom.png);
+}
+
+.x-window-tl {
+ background-image: url(../images/access/window/left-corners.png);
+}
+
+.x-window-tr {
+ background-image: url(../images/access/window/right-corners.png);
+}
+
+.x-window-bc {
+ background-image: url(../images/access/window/top-bottom.png);
+}
+
+.x-window-bl {
+ background-image: url(../images/access/window/left-corners.png);
+}
+
+.x-window-br {
+ background-image: url(../images/access/window/right-corners.png);
+}
+
+.x-window-mc {
+ border-color: #18181a;
+ font: normal 14px tahoma, arial, helvetica, sans-serif;
+ background-color: #1f2833;
+}
+
+.x-window-ml {
+ background-image: url(../images/access/window/left-right.png);
+}
+
+.x-window-mr {
+ background-image: url(../images/access/window/left-right.png);
+}
+
+.x-window-maximized .x-window-tc {
+ background-color: #fff;
+}
+
+.x-window-bbar .x-toolbar {
+ border-top-color: #323945;
+}
+
+.x-panel-ghost .x-window-tl {
+ border-bottom-color: #323945;
+}
+
+.x-panel-collapsed .x-window-tl {
+ border-bottom-color: #323945;
+}
+
+.x-dlg-mask {
+ background-color: #ccc;
+}
+
+.x-window-plain .x-window-mc {
+ background-color: #464f61;
+ border-color: #636778;
+}
+
+.x-window-plain .x-window-body {
+ color: #fffff6;
+ border-color: #464f61;
+}
+
+body.x-body-masked .x-window-plain .x-window-mc {
+ background-color: #464f61;
+}
+.x-html-editor-wrap {
+ border-color: #737b8c;
+ background-color: #fff;
+}
+.x-html-editor-wrap iframe {
+ background-color: #fff;
+}
+.x-html-editor-tb .x-btn-text {
+ background-image: url(../images/access/editor/tb-sprite.gif);
+}
+.x-panel-noborder .x-panel-header-noborder {
+ border-bottom-color: #343d4e;
+}
+
+.x-panel-noborder .x-panel-tbar-noborder .x-toolbar {
+ border-bottom-color: #343d4e;
+}
+
+.x-panel-noborder .x-panel-bbar-noborder .x-toolbar {
+ border-top-color: #343d4e;
+}
+
+.x-tab-panel-bbar-noborder .x-toolbar {
+ border-top-color: #343d4e;
+}
+
+.x-tab-panel-tbar-noborder .x-toolbar {
+ border-bottom-color: #343d4e;
+}
+.x-border-layout-ct {
+ background-color: #3f4757;
+}
+
+.x-accordion-hd {
+ color: #fff;
+ font-weight: normal;
+ background-image: url(../images/access/panel/light-hd.gif);
+}
+
+.x-layout-collapsed {
+ background-color: #323845;
+ border-color: #1a1a1c;
+}
+
+.x-layout-collapsed-over {
+ background-color: #2d3440;
+}
+
+.x-layout-split-west .x-layout-mini {
+ background-image: url(../images/default/layout/mini-left.gif);
+}
+
+.x-layout-split-east .x-layout-mini {
+ background-image: url(../images/default/layout/mini-right.gif);
+}
+
+.x-layout-split-north .x-layout-mini {
+ background-image: url(../images/default/layout/mini-top.gif);
+}
+
+.x-layout-split-south .x-layout-mini {
+ background-image: url(../images/default/layout/mini-bottom.gif);
+}
+
+.x-layout-cmini-west .x-layout-mini {
+ background-image: url(../images/default/layout/mini-right.gif);
+}
+
+.x-layout-cmini-east .x-layout-mini {
+ background-image: url(../images/default/layout/mini-left.gif);
+}
+
+.x-layout-cmini-north .x-layout-mini {
+ background-image: url(../images/default/layout/mini-bottom.gif);
+}
+
+.x-layout-cmini-south .x-layout-mini {
+ background-image: url(../images/default/layout/mini-top.gif);
+}
+.x-progress-wrap {
+ border-color: #18181a;
+}
+
+.x-progress-inner {
+ background-color: #232d38;
+ background-image: none;
+}
+
+.x-progress-bar {
+ background-color: #f39a00;
+ background-image: url(../images/access/progress/progress-bg.gif);
+ border-top-color: #a66900;
+ border-bottom-color: #a66900;
+ border-right-color: #ffb941;
+ height: 20px !important; /* structural override for Accessibility Theme */
+}
+
+.x-progress-text {
+ font-size: 14px;
+ font-weight: bold;
+ color: #fff;
+ padding: 0 5px !important; /* structural override for Accessibility Theme */
+}
+
+.x-progress-text-back {
+ color: #aaa;
+ line-height: 19px;
+}
+.x-list-header {
+ background-color: #393d4e;
+ background-image: url(../images/access/toolbar/bg.gif);
+ background-position: 0 top;
+}
+
+.x-list-header-inner div em {
+ border-left-color: #667;
+ font: normal 14px arial, tahoma, helvetica, sans-serif;
+ line-height: 14px;
+}
+
+.x-list-body-inner {
+ background-color: #1b232b;
+}
+
+.x-list-body dt em {
+ font: normal 14px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-list-over {
+ background-color: #7e5530;
+}
+
+.x-list-selected {
+ background-color: #e5872c;
+}
+
+.x-list-resizer {
+ border-left-color: #555;
+ border-right-color: #555;
+}
+
+.x-list-header-inner em.sort-asc,
+.x-list-header-inner em.sort-desc {
+ background-image: url(../images/access/grid/sort-hd.gif);
+ border-color: #3e4e6c;
+}
+.x-slider-horz,
+.x-slider-horz .x-slider-end,
+.x-slider-horz .x-slider-inner {
+ background-image: url(../images/access/slider/slider-bg.png);
+}
+
+.x-slider-horz .x-slider-thumb {
+ background-image: url(../images/access/slider/slider-thumb.png);
+}
+
+.x-slider-vert,
+.x-slider-vert .x-slider-end,
+.x-slider-vert .x-slider-inner {
+ background-image: url(../images/access/slider/slider-v-bg.png);
+}
+
+.x-slider-vert .x-slider-thumb {
+ background-image: url(../images/access/slider/slider-v-thumb.png);
+}
+.x-window-dlg .ext-mb-text,
+.x-window-dlg .x-window-header-text {
+ font-size: 15px;
+}
+
+.x-window-dlg .ext-mb-textarea {
+ font: normal 15px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-window-dlg .x-msg-box-wait {
+ background-image: url(../images/access/grid/loading.gif);
+}
+
+.x-window-dlg .ext-mb-info {
+ background-image: url(../images/access/window/icon-info.gif);
+}
+
+.x-window-dlg .ext-mb-warning {
+ background-image: url(../images/access/window/icon-warning.gif);
+}
+
+.x-window-dlg .ext-mb-question {
+ background-image: url(../images/access/window/icon-question.gif);
+}
+
+.x-window-dlg .ext-mb-error {
+ background-image: url(../images/access/window/icon-error.gif);
+}
diff --git a/deluge/ui/web/themes/css/xtheme-blue.css b/deluge/ui/web/themes/css/xtheme-blue.css
new file mode 100644
index 0000000..c2131cf
--- /dev/null
+++ b/deluge/ui/web/themes/css/xtheme-blue.css
@@ -0,0 +1,1793 @@
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+.ext-el-mask {
+ background-color: #ccc;
+}
+
+.ext-el-mask-msg {
+ border-color: #6593cf;
+ background-color: #c3daf9;
+ background-image: url(../images/default/box/tb-blue.gif);
+}
+.ext-el-mask-msg div {
+ background-color: #eee;
+ border-color: #a3bad9;
+ color: #222;
+ font: normal 11px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-mask-loading div {
+ background-color: #fbfbfb;
+ background-image: url(../images/default/grid/loading.gif);
+}
+
+.x-item-disabled {
+ color: gray;
+}
+
+.x-item-disabled * {
+ color: gray !important;
+}
+
+.x-splitbar-proxy {
+ background-color: #aaa;
+}
+
+.x-color-palette a {
+ border-color: #fff;
+}
+
+.x-color-palette a:hover,
+.x-color-palette a.x-color-palette-sel {
+ border-color: #8bb8f3;
+ background-color: #deecfd;
+}
+
+/*
+.x-color-palette em:hover, .x-color-palette span:hover{
+ background-color: #deecfd;
+}
+*/
+
+.x-color-palette em {
+ border-color: #aca899;
+}
+
+.x-ie-shadow {
+ background-color: #777;
+}
+
+.x-shadow .xsmc {
+ background-image: url(../images/default/shadow-c.png);
+}
+
+.x-shadow .xsml,
+.x-shadow .xsmr {
+ background-image: url(../images/default/shadow-lr.png);
+}
+
+.x-shadow .xstl,
+.x-shadow .xstc,
+.x-shadow .xstr,
+.x-shadow .xsbl,
+.x-shadow .xsbc,
+.x-shadow .xsbr {
+ background-image: url(../images/default/shadow.png);
+}
+
+.loading-indicator {
+ font-size: 11px;
+ background-image: url(../images/default/grid/loading.gif);
+}
+
+.x-spotlight {
+ background-color: #ccc;
+}
+.x-tab-panel-header,
+.x-tab-panel-footer {
+ background-color: #deecfd;
+ border-color: #8db2e3;
+ overflow: hidden;
+ zoom: 1;
+}
+
+.x-tab-panel-header,
+.x-tab-panel-footer {
+ border-color: #8db2e3;
+}
+
+ul.x-tab-strip-top {
+ background-color: #cedff5;
+ background-image: url(../images/default/tabs/tab-strip-bg.gif);
+ border-bottom-color: #8db2e3;
+}
+
+ul.x-tab-strip-bottom {
+ background-color: #cedff5;
+ background-image: url(../images/default/tabs/tab-strip-btm-bg.gif);
+ border-top-color: #8db2e3;
+}
+
+.x-tab-panel-header-plain .x-tab-strip-spacer,
+.x-tab-panel-footer-plain .x-tab-strip-spacer {
+ border-color: #8db2e3;
+ background-color: #deecfd;
+}
+
+.x-tab-strip span.x-tab-strip-text {
+ font: normal 11px tahoma, arial, helvetica;
+ color: #416aa3;
+}
+
+.x-tab-strip-over span.x-tab-strip-text {
+ color: #15428b;
+}
+
+.x-tab-strip-active span.x-tab-strip-text {
+ color: #15428b;
+ font-weight: bold;
+}
+
+.x-tab-strip-disabled .x-tabs-text {
+ color: #aaaaaa;
+}
+
+.x-tab-strip-top .x-tab-right,
+.x-tab-strip-top .x-tab-left,
+.x-tab-strip-top .x-tab-strip-inner {
+ background-image: url(../images/default/tabs/tabs-sprite.gif);
+}
+
+.x-tab-strip-bottom .x-tab-right {
+ background-image: url(../images/default/tabs/tab-btm-inactive-right-bg.gif);
+}
+
+.x-tab-strip-bottom .x-tab-left {
+ background-image: url(../images/default/tabs/tab-btm-inactive-left-bg.gif);
+}
+
+.x-tab-strip-bottom .x-tab-strip-over .x-tab-right {
+ background-image: url(../images/default/tabs/tab-btm-over-right-bg.gif);
+}
+
+.x-tab-strip-bottom .x-tab-strip-over .x-tab-left {
+ background-image: url(../images/default/tabs/tab-btm-over-left-bg.gif);
+}
+
+.x-tab-strip-bottom .x-tab-strip-active .x-tab-right {
+ background-image: url(../images/default/tabs/tab-btm-right-bg.gif);
+}
+
+.x-tab-strip-bottom .x-tab-strip-active .x-tab-left {
+ background-image: url(../images/default/tabs/tab-btm-left-bg.gif);
+}
+
+.x-tab-strip .x-tab-strip-closable a.x-tab-strip-close {
+ background-image: url(../images/default/tabs/tab-close.gif);
+}
+
+.x-tab-strip .x-tab-strip-closable a.x-tab-strip-close:hover {
+ background-image: url(../images/default/tabs/tab-close.gif);
+}
+
+.x-tab-panel-body {
+ border-color: #8db2e3;
+ background-color: #fff;
+}
+
+.x-tab-panel-body-top {
+ border-top: 0 none;
+}
+
+.x-tab-panel-body-bottom {
+ border-bottom: 0 none;
+}
+
+.x-tab-scroller-left {
+ background-image: url(../images/default/tabs/scroll-left.gif);
+ border-bottom-color: #8db2e3;
+}
+
+.x-tab-scroller-left-over {
+ background-position: 0 0;
+}
+
+.x-tab-scroller-left-disabled {
+ background-position: -18px 0;
+ opacity: 0.5;
+ -moz-opacity: 0.5;
+ filter: alpha(opacity=50);
+ cursor: default;
+}
+
+.x-tab-scroller-right {
+ background-image: url(../images/default/tabs/scroll-right.gif);
+ border-bottom-color: #8db2e3;
+}
+
+.x-tab-panel-bbar .x-toolbar,
+.x-tab-panel-tbar .x-toolbar {
+ border-color: #99bbe8;
+}
+.x-form-field {
+ font: normal 12px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-form-text,
+textarea.x-form-field {
+ background-color: #fff;
+ background-image: url(../images/default/form/text-bg.gif);
+ border-color: #b5b8c8;
+}
+
+.x-form-select-one {
+ background-color: #fff;
+ border-color: #b5b8c8;
+}
+
+.x-form-check-group-label {
+ border-bottom: 1px solid #99bbe8;
+ color: #15428b;
+}
+
+.x-editor .x-form-check-wrap {
+ background-color: #fff;
+}
+
+.x-form-field-wrap .x-form-trigger {
+ background-image: url(../images/default/form/trigger.gif);
+ border-bottom-color: #b5b8c8;
+}
+
+.x-form-field-wrap .x-form-date-trigger {
+ background-image: url(../images/default/form/date-trigger.gif);
+}
+
+.x-form-field-wrap .x-form-clear-trigger {
+ background-image: url(../images/default/form/clear-trigger.gif);
+}
+
+.x-form-field-wrap .x-form-search-trigger {
+ background-image: url(../images/default/form/search-trigger.gif);
+}
+
+.x-trigger-wrap-focus .x-form-trigger {
+ border-bottom-color: #7eadd9;
+}
+
+.x-item-disabled .x-form-trigger-over {
+ border-bottom-color: #b5b8c8;
+}
+
+.x-item-disabled .x-form-trigger-click {
+ border-bottom-color: #b5b8c8;
+}
+
+.x-form-focus,
+textarea.x-form-focus {
+ border-color: #7eadd9;
+}
+
+.x-form-invalid,
+textarea.x-form-invalid {
+ background-color: #fff;
+ background-image: url(../images/default/grid/invalid_line.gif);
+ border-color: #c30;
+}
+
+.x-form-invalid.x-form-composite {
+ border: none;
+ background-image: none;
+}
+
+.x-form-invalid.x-form-composite .x-form-invalid {
+ background-color: #fff;
+ background-image: url(../images/default/grid/invalid_line.gif);
+ border-color: #c30;
+}
+
+.x-form-inner-invalid,
+textarea.x-form-inner-invalid {
+ background-color: #fff;
+ background-image: url(../images/default/grid/invalid_line.gif);
+}
+
+.x-form-grow-sizer {
+ font: normal 12px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-form-item {
+ font: normal 12px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-form-invalid-msg {
+ color: #c0272b;
+ font: normal 11px tahoma, arial, helvetica, sans-serif;
+ background-image: url(../images/default/shared/warning.gif);
+}
+
+.x-form-empty-field {
+ color: gray;
+}
+
+.x-small-editor .x-form-field {
+ font: normal 11px arial, tahoma, helvetica, sans-serif;
+}
+
+.ext-webkit .x-small-editor .x-form-field {
+ font: normal 11px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-form-invalid-icon {
+ background-image: url(../images/default/form/exclamation.gif);
+}
+
+.x-fieldset {
+ border-color: #b5b8c8;
+}
+
+.x-fieldset legend {
+ font: bold 11px tahoma, arial, helvetica, sans-serif;
+ color: #15428b;
+}
+.x-btn {
+ font: normal 11px tahoma, verdana, helvetica;
+}
+
+.x-btn button {
+ font: normal 11px arial, tahoma, verdana, helvetica;
+ color: #333;
+}
+
+.x-btn em {
+ font-style: normal;
+ font-weight: normal;
+}
+
+.x-btn-tl,
+.x-btn-tr,
+.x-btn-tc,
+.x-btn-ml,
+.x-btn-mr,
+.x-btn-mc,
+.x-btn-bl,
+.x-btn-br,
+.x-btn-bc {
+ background-image: url(../images/default/button/btn.gif);
+}
+
+.x-btn-click .x-btn-text,
+.x-btn-menu-active .x-btn-text,
+.x-btn-pressed .x-btn-text {
+ color: #000;
+}
+
+.x-btn-disabled * {
+ color: gray !important;
+}
+
+.x-btn-mc em.x-btn-arrow {
+ background-image: url(../images/default/button/arrow.gif);
+}
+
+.x-btn-mc em.x-btn-split {
+ background-image: url(../images/default/button/s-arrow.gif);
+}
+
+.x-btn-over .x-btn-mc em.x-btn-split,
+.x-btn-click .x-btn-mc em.x-btn-split,
+.x-btn-menu-active .x-btn-mc em.x-btn-split,
+.x-btn-pressed .x-btn-mc em.x-btn-split {
+ background-image: url(../images/default/button/s-arrow-o.gif);
+}
+
+.x-btn-mc em.x-btn-arrow-bottom {
+ background-image: url(../images/default/button/s-arrow-b-noline.gif);
+}
+
+.x-btn-mc em.x-btn-split-bottom {
+ background-image: url(../images/default/button/s-arrow-b.gif);
+}
+
+.x-btn-over .x-btn-mc em.x-btn-split-bottom,
+.x-btn-click .x-btn-mc em.x-btn-split-bottom,
+.x-btn-menu-active .x-btn-mc em.x-btn-split-bottom,
+.x-btn-pressed .x-btn-mc em.x-btn-split-bottom {
+ background-image: url(../images/default/button/s-arrow-bo.gif);
+}
+
+.x-btn-group-header {
+ color: #3e6aaa;
+}
+
+.x-btn-group-tc {
+ background-image: url(../images/default/button/group-tb.gif);
+}
+
+.x-btn-group-tl {
+ background-image: url(../images/default/button/group-cs.gif);
+}
+
+.x-btn-group-tr {
+ background-image: url(../images/default/button/group-cs.gif);
+}
+
+.x-btn-group-bc {
+ background-image: url(../images/default/button/group-tb.gif);
+}
+
+.x-btn-group-bl {
+ background-image: url(../images/default/button/group-cs.gif);
+}
+
+.x-btn-group-br {
+ background-image: url(../images/default/button/group-cs.gif);
+}
+
+.x-btn-group-ml {
+ background-image: url(../images/default/button/group-lr.gif);
+}
+.x-btn-group-mr {
+ background-image: url(../images/default/button/group-lr.gif);
+}
+
+.x-btn-group-notitle .x-btn-group-tc {
+ background-image: url(../images/default/button/group-tb.gif);
+}
+.x-toolbar {
+ border-color: #a9bfd3;
+ background-color: #d0def0;
+ background-image: url(../images/default/toolbar/bg.gif);
+}
+
+.x-toolbar td,
+.x-toolbar span,
+.x-toolbar input,
+.x-toolbar div,
+.x-toolbar select,
+.x-toolbar label {
+ font: normal 11px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-toolbar .x-item-disabled {
+ color: gray;
+}
+
+.x-toolbar .x-item-disabled * {
+ color: gray;
+}
+
+.x-toolbar .x-btn-mc em.x-btn-split {
+ background-image: url(../images/default/button/s-arrow-noline.gif);
+}
+
+.x-toolbar .x-btn-over .x-btn-mc em.x-btn-split,
+.x-toolbar .x-btn-click .x-btn-mc em.x-btn-split,
+.x-toolbar .x-btn-menu-active .x-btn-mc em.x-btn-split,
+.x-toolbar .x-btn-pressed .x-btn-mc em.x-btn-split {
+ background-image: url(../images/default/button/s-arrow-o.gif);
+}
+
+.x-toolbar .x-btn-mc em.x-btn-split-bottom {
+ background-image: url(../images/default/button/s-arrow-b-noline.gif);
+}
+
+.x-toolbar .x-btn-over .x-btn-mc em.x-btn-split-bottom,
+.x-toolbar .x-btn-click .x-btn-mc em.x-btn-split-bottom,
+.x-toolbar .x-btn-menu-active .x-btn-mc em.x-btn-split-bottom,
+.x-toolbar .x-btn-pressed .x-btn-mc em.x-btn-split-bottom {
+ background-image: url(../images/default/button/s-arrow-bo.gif);
+}
+
+.x-toolbar .xtb-sep {
+ background-image: url(../images/default/grid/grid-blue-split.gif);
+}
+
+.x-tbar-page-first {
+ background-image: url(../images/default/grid/page-first.gif) !important;
+}
+
+.x-tbar-loading {
+ background-image: url(../images/default/grid/refresh.gif) !important;
+}
+
+.x-tbar-page-last {
+ background-image: url(../images/default/grid/page-last.gif) !important;
+}
+
+.x-tbar-page-next {
+ background-image: url(../images/default/grid/page-next.gif) !important;
+}
+
+.x-tbar-page-prev {
+ background-image: url(../images/default/grid/page-prev.gif) !important;
+}
+
+.x-item-disabled .x-tbar-loading {
+ background-image: url(../images/default/grid/refresh-disabled.gif) !important;
+}
+
+.x-item-disabled .x-tbar-page-first {
+ background-image: url(../images/default/grid/page-first-disabled.gif) !important;
+}
+
+.x-item-disabled .x-tbar-page-last {
+ background-image: url(../images/default/grid/page-last-disabled.gif) !important;
+}
+
+.x-item-disabled .x-tbar-page-next {
+ background-image: url(../images/default/grid/page-next-disabled.gif) !important;
+}
+
+.x-item-disabled .x-tbar-page-prev {
+ background-image: url(../images/default/grid/page-prev-disabled.gif) !important;
+}
+
+.x-paging-info {
+ color: #444;
+}
+
+.x-toolbar-more-icon {
+ background-image: url(../images/default/toolbar/more.gif) !important;
+}
+.x-resizable-handle {
+ background-color: #fff;
+}
+
+.x-resizable-over .x-resizable-handle-east,
+.x-resizable-pinned .x-resizable-handle-east,
+.x-resizable-over .x-resizable-handle-west,
+.x-resizable-pinned .x-resizable-handle-west {
+ background-image: url(../images/default/sizer/e-handle.gif);
+}
+
+.x-resizable-over .x-resizable-handle-south,
+.x-resizable-pinned .x-resizable-handle-south,
+.x-resizable-over .x-resizable-handle-north,
+.x-resizable-pinned .x-resizable-handle-north {
+ background-image: url(../images/default/sizer/s-handle.gif);
+}
+
+.x-resizable-over .x-resizable-handle-north,
+.x-resizable-pinned .x-resizable-handle-north {
+ background-image: url(../images/default/sizer/s-handle.gif);
+}
+.x-resizable-over .x-resizable-handle-southeast,
+.x-resizable-pinned .x-resizable-handle-southeast {
+ background-image: url(../images/default/sizer/se-handle.gif);
+}
+.x-resizable-over .x-resizable-handle-northwest,
+.x-resizable-pinned .x-resizable-handle-northwest {
+ background-image: url(../images/default/sizer/nw-handle.gif);
+}
+.x-resizable-over .x-resizable-handle-northeast,
+.x-resizable-pinned .x-resizable-handle-northeast {
+ background-image: url(../images/default/sizer/ne-handle.gif);
+}
+.x-resizable-over .x-resizable-handle-southwest,
+.x-resizable-pinned .x-resizable-handle-southwest {
+ background-image: url(../images/default/sizer/sw-handle.gif);
+}
+.x-resizable-proxy {
+ border-color: #3b5a82;
+}
+.x-resizable-overlay {
+ background-color: #fff;
+}
+.x-grid3 {
+ background-color: #fff;
+}
+
+.x-grid-panel .x-panel-mc .x-panel-body {
+ border-color: #99bbe8;
+}
+
+.x-grid3-row td,
+.x-grid3-summary-row td {
+ font: normal 11px/13px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-grid3-hd-row td {
+ font: normal 11px/15px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-grid3-hd-row td {
+ border-left-color: #eee;
+ border-right-color: #d0d0d0;
+}
+
+.x-grid-row-loading {
+ background-color: #fff;
+ background-image: url(../images/default/shared/loading-balls.gif);
+}
+
+.x-grid3-row {
+ border-color: #ededed;
+ border-top-color: #fff;
+}
+
+.x-grid3-row-alt {
+ background-color: #fafafa;
+}
+
+.x-grid3-row-over {
+ border-color: #ddd;
+ background-color: #efefef;
+ background-image: url(../images/default/grid/row-over.gif);
+}
+
+.x-grid3-resize-proxy {
+ background-color: #777;
+}
+
+.x-grid3-resize-marker {
+ background-color: #777;
+}
+
+.x-grid3-header {
+ background-color: #f9f9f9;
+ background-image: url(../images/default/grid/grid3-hrow.gif);
+}
+
+.x-grid3-header-pop {
+ border-left-color: #d0d0d0;
+}
+
+.x-grid3-header-pop-inner {
+ border-left-color: #eee;
+ background-image: url(../images/default/grid/hd-pop.gif);
+}
+
+td.x-grid3-hd-over,
+td.sort-desc,
+td.sort-asc,
+td.x-grid3-hd-menu-open {
+ border-left-color: #aaccf6;
+ border-right-color: #aaccf6;
+}
+
+td.x-grid3-hd-over .x-grid3-hd-inner,
+td.sort-desc .x-grid3-hd-inner,
+td.sort-asc .x-grid3-hd-inner,
+td.x-grid3-hd-menu-open .x-grid3-hd-inner {
+ background-color: #ebf3fd;
+ background-image: url(../images/default/grid/grid3-hrow-over.gif);
+}
+
+.sort-asc .x-grid3-sort-icon {
+ background-image: url(../images/default/grid/sort_asc.gif);
+}
+
+.sort-desc .x-grid3-sort-icon {
+ background-image: url(../images/default/grid/sort_desc.gif);
+}
+
+.x-grid3-cell-text,
+.x-grid3-hd-text {
+ color: #000;
+}
+
+.x-grid3-split {
+ background-image: url(../images/default/grid/grid-split.gif);
+}
+
+.x-grid3-hd-text {
+ color: #15428b;
+}
+
+.x-dd-drag-proxy .x-grid3-hd-inner {
+ background-color: #ebf3fd;
+ background-image: url(../images/default/grid/grid3-hrow-over.gif);
+ border-color: #aaccf6;
+}
+
+.col-move-top {
+ background-image: url(../images/default/grid/col-move-top.gif);
+}
+
+.col-move-bottom {
+ background-image: url(../images/default/grid/col-move-bottom.gif);
+}
+
+td.grid-hd-group-cell {
+ background: url(../images/default/grid/grid3-hrow.gif) repeat-x bottom;
+}
+
+.x-grid3-row-selected {
+ background-color: #dfe8f6 !important;
+ background-image: none;
+ border-color: #a3bae9;
+}
+
+.x-grid3-cell-selected {
+ background-color: #b8cfee !important;
+ color: #000;
+}
+
+.x-grid3-cell-selected span {
+ color: #000 !important;
+}
+
+.x-grid3-cell-selected .x-grid3-cell-text {
+ color: #000;
+}
+
+.x-grid3-locked td.x-grid3-row-marker,
+.x-grid3-locked .x-grid3-row-selected td.x-grid3-row-marker {
+ background-color: #ebeadb !important;
+ background-image: url(../images/default/grid/grid-hrow.gif) !important;
+ color: #000;
+ border-top-color: #fff;
+ border-right-color: #6fa0df !important;
+}
+
+.x-grid3-locked td.x-grid3-row-marker div,
+.x-grid3-locked .x-grid3-row-selected td.x-grid3-row-marker div {
+ color: #15428b !important;
+}
+
+.x-grid3-dirty-cell {
+ background-image: url(../images/default/grid/dirty.gif);
+}
+
+.x-grid3-topbar,
+.x-grid3-bottombar {
+ font: normal 11px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-grid3-bottombar .x-toolbar {
+ border-top-color: #a9bfd3;
+}
+
+.x-props-grid .x-grid3-td-name .x-grid3-cell-inner {
+ background-image: url(../images/default/grid/grid3-special-col-bg.gif) !important;
+ color: #000 !important;
+}
+
+.x-props-grid .x-grid3-body .x-grid3-td-name {
+ background-color: #fff !important;
+ border-right-color: #eee;
+}
+
+.xg-hmenu-sort-asc .x-menu-item-icon {
+ background-image: url(../images/default/grid/hmenu-asc.gif);
+}
+
+.xg-hmenu-sort-desc .x-menu-item-icon {
+ background-image: url(../images/default/grid/hmenu-desc.gif);
+}
+
+.xg-hmenu-lock .x-menu-item-icon {
+ background-image: url(../images/default/grid/hmenu-lock.gif);
+}
+
+.xg-hmenu-unlock .x-menu-item-icon {
+ background-image: url(../images/default/grid/hmenu-unlock.gif);
+}
+
+.x-grid3-hd-btn {
+ background-color: #c3daf9;
+ background-image: url(../images/default/grid/grid3-hd-btn.gif);
+}
+
+.x-grid3-body .x-grid3-td-expander {
+ background-image: url(../images/default/grid/grid3-special-col-bg.gif);
+}
+
+.x-grid3-row-expander {
+ background-image: url(../images/default/grid/row-expand-sprite.gif);
+}
+
+.x-grid3-body .x-grid3-td-checker {
+ background-image: url(../images/default/grid/grid3-special-col-bg.gif);
+}
+
+.x-grid3-row-checker,
+.x-grid3-hd-checker {
+ background-image: url(../images/default/grid/row-check-sprite.gif);
+}
+
+.x-grid3-body .x-grid3-td-numberer {
+ background-image: url(../images/default/grid/grid3-special-col-bg.gif);
+}
+
+.x-grid3-body .x-grid3-td-numberer .x-grid3-cell-inner {
+ color: #444;
+}
+
+.x-grid3-body .x-grid3-td-row-icon {
+ background-image: url(../images/default/grid/grid3-special-col-bg.gif);
+}
+
+.x-grid3-body .x-grid3-row-selected .x-grid3-td-numberer,
+.x-grid3-body .x-grid3-row-selected .x-grid3-td-checker,
+.x-grid3-body .x-grid3-row-selected .x-grid3-td-expander {
+ background-image: url(../images/default/grid/grid3-special-col-sel-bg.gif);
+}
+
+.x-grid3-check-col {
+ background-image: url(../images/default/menu/unchecked.gif);
+}
+
+.x-grid3-check-col-on {
+ background-image: url(../images/default/menu/checked.gif);
+}
+
+.x-grid-group,
+.x-grid-group-body,
+.x-grid-group-hd {
+ zoom: 1;
+}
+
+.x-grid-group-hd {
+ border-bottom-color: #99bbe8;
+}
+
+.x-grid-group-hd div.x-grid-group-title {
+ background-image: url(../images/default/grid/group-collapse.gif);
+ color: #3764a0;
+ font: bold 11px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-grid-group-collapsed .x-grid-group-hd div.x-grid-group-title {
+ background-image: url(../images/default/grid/group-expand.gif);
+}
+
+.x-group-by-icon {
+ background-image: url(../images/default/grid/group-by.gif);
+}
+
+.x-cols-icon {
+ background-image: url(../images/default/grid/columns.gif);
+}
+
+.x-show-groups-icon {
+ background-image: url(../images/default/grid/group-by.gif);
+}
+
+.x-grid-empty {
+ color: gray;
+ font: normal 11px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-grid-with-col-lines .x-grid3-row td.x-grid3-cell {
+ border-right-color: #ededed;
+}
+
+.x-grid-with-col-lines .x-grid3-row-selected {
+ border-top-color: #a3bae9;
+}
+.x-pivotgrid .x-grid3-header-offset table td {
+ background: url(../images/default/grid/grid3-hrow.gif) repeat-x 50% 100%;
+ border-left: 1px solid;
+ border-right: 1px solid;
+ border-left-color: #eee;
+ border-right-color: #d0d0d0;
+}
+
+.x-pivotgrid .x-grid3-row-headers {
+ background-color: #f9f9f9;
+}
+
+.x-pivotgrid .x-grid3-row-headers table td {
+ background: #eee url(../images/default/grid/grid3-rowheader.gif) repeat-x
+ left top;
+ border-left: 1px solid;
+ border-right: 1px solid;
+ border-left-color: #eee;
+ border-right-color: #d0d0d0;
+ border-bottom: 1px solid;
+ border-bottom-color: #d0d0d0;
+ height: 18px;
+}
+.x-dd-drag-ghost {
+ color: #000;
+ font: normal 11px arial, helvetica, sans-serif;
+ border-color: #ddd #bbb #bbb #ddd;
+ background-color: #fff;
+}
+
+.x-dd-drop-nodrop .x-dd-drop-icon {
+ background-image: url(../images/default/dd/drop-no.gif);
+}
+
+.x-dd-drop-ok .x-dd-drop-icon {
+ background-image: url(../images/default/dd/drop-yes.gif);
+}
+
+.x-dd-drop-ok-add .x-dd-drop-icon {
+ background-image: url(../images/default/dd/drop-add.gif);
+}
+
+.x-view-selector {
+ background-color: #c3daf9;
+ border-color: #3399bb;
+}
+.x-tree-node-expanded .x-tree-node-icon {
+ background-image: url(../images/default/tree/folder-open.gif);
+}
+
+.x-tree-node-leaf .x-tree-node-icon {
+ background-image: url(../images/default/tree/leaf.gif);
+}
+
+.x-tree-node-collapsed .x-tree-node-icon {
+ background-image: url(../images/default/tree/folder.gif);
+}
+
+.x-tree-node-loading .x-tree-node-icon {
+ background-image: url(../images/default/tree/loading.gif) !important;
+}
+
+.x-tree-node .x-tree-node-inline-icon {
+ background-image: none;
+}
+
+.x-tree-node-loading a span {
+ font-style: italic;
+ color: #444444;
+}
+
+.x-tree-lines .x-tree-elbow {
+ background-image: url(../images/default/tree/elbow.gif);
+}
+
+.x-tree-lines .x-tree-elbow-plus {
+ background-image: url(../images/default/tree/elbow-plus.gif);
+}
+
+.x-tree-lines .x-tree-elbow-minus {
+ background-image: url(../images/default/tree/elbow-minus.gif);
+}
+
+.x-tree-lines .x-tree-elbow-end {
+ background-image: url(../images/default/tree/elbow-end.gif);
+}
+
+.x-tree-lines .x-tree-elbow-end-plus {
+ background-image: url(../images/default/tree/elbow-end-plus.gif);
+}
+
+.x-tree-lines .x-tree-elbow-end-minus {
+ background-image: url(../images/default/tree/elbow-end-minus.gif);
+}
+
+.x-tree-lines .x-tree-elbow-line {
+ background-image: url(../images/default/tree/elbow-line.gif);
+}
+
+.x-tree-no-lines .x-tree-elbow-plus {
+ background-image: url(../images/default/tree/elbow-plus-nl.gif);
+}
+
+.x-tree-no-lines .x-tree-elbow-minus {
+ background-image: url(../images/default/tree/elbow-minus-nl.gif);
+}
+
+.x-tree-no-lines .x-tree-elbow-end-plus {
+ background-image: url(../images/default/tree/elbow-end-plus-nl.gif);
+}
+
+.x-tree-no-lines .x-tree-elbow-end-minus {
+ background-image: url(../images/default/tree/elbow-end-minus-nl.gif);
+}
+
+.x-tree-arrows .x-tree-elbow-plus {
+ background-image: url(../images/default/tree/arrows.gif);
+}
+
+.x-tree-arrows .x-tree-elbow-minus {
+ background-image: url(../images/default/tree/arrows.gif);
+}
+
+.x-tree-arrows .x-tree-elbow-end-plus {
+ background-image: url(../images/default/tree/arrows.gif);
+}
+
+.x-tree-arrows .x-tree-elbow-end-minus {
+ background-image: url(../images/default/tree/arrows.gif);
+}
+
+.x-tree-node {
+ color: #000;
+ font: normal 11px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-tree-node a,
+.x-dd-drag-ghost a {
+ color: #000;
+}
+
+.x-tree-node a span,
+.x-dd-drag-ghost a span {
+ color: #000;
+}
+
+.x-tree-node .x-tree-node-disabled a span {
+ color: gray !important;
+}
+
+.x-tree-node div.x-tree-drag-insert-below {
+ border-bottom-color: #36c;
+}
+
+.x-tree-node div.x-tree-drag-insert-above {
+ border-top-color: #36c;
+}
+
+.x-tree-dd-underline .x-tree-node div.x-tree-drag-insert-below a {
+ border-bottom-color: #36c;
+}
+
+.x-tree-dd-underline .x-tree-node div.x-tree-drag-insert-above a {
+ border-top-color: #36c;
+}
+
+.x-tree-node .x-tree-drag-append a span {
+ background-color: #ddd;
+ border-color: gray;
+}
+
+.x-tree-node .x-tree-node-over {
+ background-color: #eee;
+}
+
+.x-tree-node .x-tree-selected {
+ background-color: #d9e8fb;
+}
+
+.x-tree-drop-ok-append .x-dd-drop-icon {
+ background-image: url(../images/default/tree/drop-add.gif);
+}
+
+.x-tree-drop-ok-above .x-dd-drop-icon {
+ background-image: url(../images/default/tree/drop-over.gif);
+}
+
+.x-tree-drop-ok-below .x-dd-drop-icon {
+ background-image: url(../images/default/tree/drop-under.gif);
+}
+
+.x-tree-drop-ok-between .x-dd-drop-icon {
+ background-image: url(../images/default/tree/drop-between.gif);
+}
+.x-date-picker {
+ border-color: #1b376c;
+ background-color: #fff;
+}
+
+.x-date-middle,
+.x-date-left,
+.x-date-right {
+ background-image: url(../images/default/shared/hd-sprite.gif);
+ color: #fff;
+ font: bold 11px 'sans serif', tahoma, verdana, helvetica;
+}
+
+.x-date-middle .x-btn .x-btn-text {
+ color: #fff;
+}
+
+.x-date-middle .x-btn-mc em.x-btn-arrow {
+ background-image: url(../images/default/toolbar/btn-arrow-light.gif);
+}
+
+.x-date-right a {
+ background-image: url(../images/default/shared/right-btn.gif);
+}
+
+.x-date-left a {
+ background-image: url(../images/default/shared/left-btn.gif);
+}
+
+.x-date-inner th {
+ background-color: #dfecfb;
+ background-image: url(../images/default/shared/glass-bg.gif);
+ border-bottom-color: #a3bad9;
+ font: normal 10px arial, helvetica, tahoma, sans-serif;
+ color: #233d6d;
+}
+
+.x-date-inner td {
+ border-color: #fff;
+}
+
+.x-date-inner a {
+ font: normal 11px arial, helvetica, tahoma, sans-serif;
+ color: #000;
+}
+
+.x-date-inner .x-date-active {
+ color: #000;
+}
+
+.x-date-inner .x-date-selected a {
+ background-color: #dfecfb;
+ background-image: url(../images/default/shared/glass-bg.gif);
+ border-color: #8db2e3;
+}
+
+.x-date-inner .x-date-today a {
+ border-color: darkred;
+}
+
+.x-date-inner .x-date-selected span {
+ font-weight: bold;
+}
+
+.x-date-inner .x-date-prevday a,
+.x-date-inner .x-date-nextday a {
+ color: #aaa;
+}
+
+.x-date-bottom {
+ border-top-color: #a3bad9;
+ background-color: #dfecfb;
+ background-image: url(../images/default/shared/glass-bg.gif);
+}
+
+.x-date-inner a:hover,
+.x-date-inner .x-date-disabled a:hover {
+ color: #000;
+ background-color: #ddecfe;
+}
+
+.x-date-inner .x-date-disabled a {
+ background-color: #eee;
+ color: #bbb;
+}
+
+.x-date-mmenu {
+ background-color: #eee !important;
+}
+
+.x-date-mmenu .x-menu-item {
+ font-size: 10px;
+ color: #000;
+}
+
+.x-date-mp {
+ background-color: #fff;
+}
+
+.x-date-mp td {
+ font: normal 11px arial, helvetica, tahoma, sans-serif;
+}
+
+.x-date-mp-btns button {
+ background-color: #083772;
+ color: #fff;
+ border-color: #3366cc #000055 #000055 #3366cc;
+ font: normal 11px arial, helvetica, tahoma, sans-serif;
+}
+
+.x-date-mp-btns {
+ background-color: #dfecfb;
+ background-image: url(../images/default/shared/glass-bg.gif);
+}
+
+.x-date-mp-btns td {
+ border-top-color: #c5d2df;
+}
+
+td.x-date-mp-month a,
+td.x-date-mp-year a {
+ color: #15428b;
+}
+
+td.x-date-mp-month a:hover,
+td.x-date-mp-year a:hover {
+ color: #15428b;
+ background-color: #ddecfe;
+}
+
+td.x-date-mp-sel a {
+ background-color: #dfecfb;
+ background-image: url(../images/default/shared/glass-bg.gif);
+ border-color: #8db2e3;
+}
+
+.x-date-mp-ybtn a {
+ background-image: url(../images/default/panel/tool-sprites.gif);
+}
+
+td.x-date-mp-sep {
+ border-right-color: #c5d2df;
+}
+.x-tip .x-tip-close {
+ background-image: url(../images/default/qtip/close.gif);
+}
+
+.x-tip .x-tip-tc,
+.x-tip .x-tip-tl,
+.x-tip .x-tip-tr,
+.x-tip .x-tip-bc,
+.x-tip .x-tip-bl,
+.x-tip .x-tip-br,
+.x-tip .x-tip-ml,
+.x-tip .x-tip-mr {
+ background-image: url(../images/default/qtip/tip-sprite.gif);
+}
+
+.x-tip .x-tip-mc {
+ font: normal 11px tahoma, arial, helvetica, sans-serif;
+}
+.x-tip .x-tip-ml {
+ background-color: #fff;
+}
+
+.x-tip .x-tip-header-text {
+ font: bold 11px tahoma, arial, helvetica, sans-serif;
+ color: #444;
+}
+
+.x-tip .x-tip-body {
+ font: normal 11px tahoma, arial, helvetica, sans-serif;
+ color: #444;
+}
+
+.x-form-invalid-tip .x-tip-tc,
+.x-form-invalid-tip .x-tip-tl,
+.x-form-invalid-tip .x-tip-tr,
+.x-form-invalid-tip .x-tip-bc,
+.x-form-invalid-tip .x-tip-bl,
+.x-form-invalid-tip .x-tip-br,
+.x-form-invalid-tip .x-tip-ml,
+.x-form-invalid-tip .x-tip-mr {
+ background-image: url(../images/default/form/error-tip-corners.gif);
+}
+
+.x-form-invalid-tip .x-tip-body {
+ background-image: url(../images/default/form/exclamation.gif);
+}
+
+.x-tip-anchor {
+ background-image: url(../images/default/qtip/tip-anchor-sprite.gif);
+}
+.x-menu {
+ background-color: #f0f0f0;
+ background-image: url(../images/default/menu/menu.gif);
+}
+
+.x-menu-floating {
+ border-color: #718bb7;
+}
+
+.x-menu-nosep {
+ background-image: none;
+}
+
+.x-menu-list-item {
+ font: normal 11px arial, tahoma, sans-serif;
+}
+
+.x-menu-item-arrow {
+ background-image: url(../images/default/menu/menu-parent.gif);
+}
+
+.x-menu-sep {
+ background-color: #e0e0e0;
+ border-bottom-color: #fff;
+}
+
+a.x-menu-item {
+ color: #222;
+}
+
+.x-menu-item-active {
+ background-image: url(../images/default/menu/item-over.gif);
+ background-color: #dbecf4;
+ border-color: #aaccf6;
+}
+
+.x-menu-item-active a.x-menu-item {
+ border-color: #aaccf6;
+}
+
+.x-menu-check-item .x-menu-item-icon {
+ background-image: url(../images/default/menu/unchecked.gif);
+}
+
+.x-menu-item-checked .x-menu-item-icon {
+ background-image: url(../images/default/menu/checked.gif);
+}
+
+.x-menu-item-checked .x-menu-group-item .x-menu-item-icon {
+ background-image: url(../images/default/menu/group-checked.gif);
+}
+
+.x-menu-group-item .x-menu-item-icon {
+ background-image: none;
+}
+
+.x-menu-plain {
+ background-color: #f0f0f0 !important;
+ background-image: none;
+}
+
+.x-date-menu,
+.x-color-menu {
+ background-color: #fff !important;
+}
+
+.x-menu .x-date-picker {
+ border-color: #a3bad9;
+}
+
+.x-cycle-menu .x-menu-item-checked {
+ border-color: #a3bae9 !important;
+ background-color: #def8f6;
+}
+
+.x-menu-scroller-top {
+ background-image: url(../images/default/layout/mini-top.gif);
+}
+
+.x-menu-scroller-bottom {
+ background-image: url(../images/default/layout/mini-bottom.gif);
+}
+.x-box-tl {
+ background-image: url(../images/default/box/corners.gif);
+}
+
+.x-box-tc {
+ background-image: url(../images/default/box/tb.gif);
+}
+
+.x-box-tr {
+ background-image: url(../images/default/box/corners.gif);
+}
+
+.x-box-ml {
+ background-image: url(../images/default/box/l.gif);
+}
+
+.x-box-mc {
+ background-color: #eee;
+ background-image: url(../images/default/box/tb.gif);
+ font-family: 'Myriad Pro', 'Myriad Web', 'Tahoma', 'Helvetica', 'Arial',
+ sans-serif;
+ color: #393939;
+ font-size: 12px;
+}
+
+.x-box-mc h3 {
+ font-size: 14px;
+ font-weight: bold;
+}
+
+.x-box-mr {
+ background-image: url(../images/default/box/r.gif);
+}
+
+.x-box-bl {
+ background-image: url(../images/default/box/corners.gif);
+}
+
+.x-box-bc {
+ background-image: url(../images/default/box/tb.gif);
+}
+
+.x-box-br {
+ background-image: url(../images/default/box/corners.gif);
+}
+
+.x-box-blue .x-box-bl,
+.x-box-blue .x-box-br,
+.x-box-blue .x-box-tl,
+.x-box-blue .x-box-tr {
+ background-image: url(../images/default/box/corners-blue.gif);
+}
+
+.x-box-blue .x-box-bc,
+.x-box-blue .x-box-mc,
+.x-box-blue .x-box-tc {
+ background-image: url(../images/default/box/tb-blue.gif);
+}
+
+.x-box-blue .x-box-mc {
+ background-color: #c3daf9;
+}
+
+.x-box-blue .x-box-mc h3 {
+ color: #17385b;
+}
+
+.x-box-blue .x-box-ml {
+ background-image: url(../images/default/box/l-blue.gif);
+}
+
+.x-box-blue .x-box-mr {
+ background-image: url(../images/default/box/r-blue.gif);
+}
+.x-combo-list {
+ border-color: #98c0f4;
+ background-color: #ddecfe;
+ font: normal 12px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-combo-list-inner {
+ background-color: #fff;
+}
+
+.x-combo-list-hd {
+ font: bold 11px tahoma, arial, helvetica, sans-serif;
+ color: #15428b;
+ background-image: url(../images/default/layout/panel-title-light-bg.gif);
+ border-bottom-color: #98c0f4;
+}
+
+.x-resizable-pinned .x-combo-list-inner {
+ border-bottom-color: #98c0f4;
+}
+
+.x-combo-list-item {
+ border-color: #fff;
+}
+
+.x-combo-list .x-combo-selected {
+ border-color: #a3bae9 !important;
+ background-color: #dfe8f6;
+}
+
+.x-combo-list .x-toolbar {
+ border-top-color: #98c0f4;
+}
+
+.x-combo-list-small {
+ font: normal 11px tahoma, arial, helvetica, sans-serif;
+}
+.x-panel {
+ border-color: #99bbe8;
+}
+
+.x-panel-header {
+ color: #15428b;
+ font-weight: bold;
+ font-size: 11px;
+ font-family: tahoma, arial, verdana, sans-serif;
+ border-color: #99bbe8;
+ background-image: url(../images/default/panel/white-top-bottom.gif);
+}
+
+.x-panel-body {
+ border-color: #99bbe8;
+ background-color: #fff;
+}
+
+.x-panel-bbar .x-toolbar,
+.x-panel-tbar .x-toolbar {
+ border-color: #99bbe8;
+}
+
+.x-panel-tbar-noheader .x-toolbar,
+.x-panel-mc .x-panel-tbar .x-toolbar {
+ border-top-color: #99bbe8;
+}
+
+.x-panel-body-noheader,
+.x-panel-mc .x-panel-body {
+ border-top-color: #99bbe8;
+}
+
+.x-panel-tl .x-panel-header {
+ color: #15428b;
+ font: bold 11px tahoma, arial, verdana, sans-serif;
+}
+
+.x-panel-tc {
+ background-image: url(../images/default/panel/top-bottom.gif);
+}
+
+.x-panel-tl,
+.x-panel-tr,
+.x-panel-bl,
+.x-panel-br {
+ background-image: url(../images/default/panel/corners-sprite.gif);
+ border-bottom-color: #99bbe8;
+}
+
+.x-panel-bc {
+ background-image: url(../images/default/panel/top-bottom.gif);
+}
+
+.x-panel-mc {
+ font: normal 11px tahoma, arial, helvetica, sans-serif;
+ background-color: #dfe8f6;
+}
+
+.x-panel-ml {
+ background-color: #fff;
+ background-image: url(../images/default/panel/left-right.gif);
+}
+
+.x-panel-mr {
+ background-image: url(../images/default/panel/left-right.gif);
+}
+
+.x-tool {
+ background-image: url(../images/default/panel/tool-sprites.gif);
+}
+
+.x-panel-ghost {
+ background-color: #cbddf3;
+}
+
+.x-panel-ghost ul {
+ border-color: #99bbe8;
+}
+
+.x-panel-dd-spacer {
+ border-color: #99bbe8;
+}
+
+.x-panel-fbar td,
+.x-panel-fbar span,
+.x-panel-fbar input,
+.x-panel-fbar div,
+.x-panel-fbar select,
+.x-panel-fbar label {
+ font: normal 11px arial, tahoma, helvetica, sans-serif;
+}
+.x-window-proxy {
+ background-color: #c7dffc;
+ border-color: #99bbe8;
+}
+
+.x-window-tl .x-window-header {
+ color: #15428b;
+ font: bold 11px tahoma, arial, verdana, sans-serif;
+}
+
+.x-window-tc {
+ background-image: url(../images/default/window/top-bottom.png);
+}
+
+.x-window-tl {
+ background-image: url(../images/default/window/left-corners.png);
+}
+
+.x-window-tr {
+ background-image: url(../images/default/window/right-corners.png);
+}
+
+.x-window-bc {
+ background-image: url(../images/default/window/top-bottom.png);
+}
+
+.x-window-bl {
+ background-image: url(../images/default/window/left-corners.png);
+}
+
+.x-window-br {
+ background-image: url(../images/default/window/right-corners.png);
+}
+
+.x-window-mc {
+ border-color: #99bbe8;
+ font: normal 11px tahoma, arial, helvetica, sans-serif;
+ background-color: #dfe8f6;
+}
+
+.x-window-ml {
+ background-image: url(../images/default/window/left-right.png);
+}
+
+.x-window-mr {
+ background-image: url(../images/default/window/left-right.png);
+}
+
+.x-window-maximized .x-window-tc {
+ background-color: #fff;
+}
+
+.x-window-bbar .x-toolbar {
+ border-top-color: #99bbe8;
+}
+
+.x-panel-ghost .x-window-tl {
+ border-bottom-color: #99bbe8;
+}
+
+.x-panel-collapsed .x-window-tl {
+ border-bottom-color: #84a0c4;
+}
+
+.x-dlg-mask {
+ background-color: #ccc;
+}
+
+.x-window-plain .x-window-mc {
+ background-color: #ccd9e8;
+ border-color: #a3bae9 #dfe8f6 #dfe8f6 #a3bae9;
+}
+
+.x-window-plain .x-window-body {
+ border-color: #dfe8f6 #a3bae9 #a3bae9 #dfe8f6;
+}
+
+body.x-body-masked .x-window-plain .x-window-mc {
+ background-color: #ccd9e8;
+}
+.x-html-editor-wrap {
+ border-color: #a9bfd3;
+ background-color: #fff;
+}
+.x-html-editor-tb .x-btn-text {
+ background-image: url(../images/default/editor/tb-sprite.gif);
+}
+.x-panel-noborder .x-panel-header-noborder {
+ border-bottom-color: #99bbe8;
+}
+
+.x-panel-noborder .x-panel-tbar-noborder .x-toolbar {
+ border-bottom-color: #99bbe8;
+}
+
+.x-panel-noborder .x-panel-bbar-noborder .x-toolbar {
+ border-top-color: #99bbe8;
+}
+
+.x-tab-panel-bbar-noborder .x-toolbar {
+ border-top-color: #99bbe8;
+}
+
+.x-tab-panel-tbar-noborder .x-toolbar {
+ border-bottom-color: #99bbe8;
+}
+.x-border-layout-ct {
+ background-color: #dfe8f6;
+}
+
+.x-accordion-hd {
+ color: #222;
+ font-weight: normal;
+ background-image: url(../images/default/panel/light-hd.gif);
+}
+
+.x-layout-collapsed {
+ background-color: #d2e0f2;
+ border-color: #98c0f4;
+}
+
+.x-layout-collapsed-over {
+ background-color: #d9e8fb;
+}
+
+.x-layout-split-west .x-layout-mini {
+ background-image: url(../images/default/layout/mini-left.gif);
+}
+.x-layout-split-east .x-layout-mini {
+ background-image: url(../images/default/layout/mini-right.gif);
+}
+.x-layout-split-north .x-layout-mini {
+ background-image: url(../images/default/layout/mini-top.gif);
+}
+.x-layout-split-south .x-layout-mini {
+ background-image: url(../images/default/layout/mini-bottom.gif);
+}
+
+.x-layout-cmini-west .x-layout-mini {
+ background-image: url(../images/default/layout/mini-right.gif);
+}
+
+.x-layout-cmini-east .x-layout-mini {
+ background-image: url(../images/default/layout/mini-left.gif);
+}
+
+.x-layout-cmini-north .x-layout-mini {
+ background-image: url(../images/default/layout/mini-bottom.gif);
+}
+
+.x-layout-cmini-south .x-layout-mini {
+ background-image: url(../images/default/layout/mini-top.gif);
+}
+.x-progress-wrap {
+ border-color: #6593cf;
+}
+
+.x-progress-inner {
+ background-color: #e0e8f3;
+ background-image: url(../images/default/qtip/bg.gif);
+}
+
+.x-progress-bar {
+ background-color: #9cbfee;
+ background-image: url(../images/default/progress/progress-bg.gif);
+ border-top-color: #d1e4fd;
+ border-bottom-color: #7fa9e4;
+ border-right-color: #7fa9e4;
+}
+
+.x-progress-text {
+ font-size: 11px;
+ font-weight: bold;
+ color: #fff;
+}
+
+.x-progress-text-back {
+ color: #396095;
+}
+.x-list-header {
+ background-color: #f9f9f9;
+ background-image: url(../images/default/grid/grid3-hrow.gif);
+}
+
+.x-list-header-inner div em {
+ border-left-color: #ddd;
+ font: normal 11px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-list-body dt em {
+ font: normal 11px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-list-over {
+ background-color: #eee;
+}
+
+.x-list-selected {
+ background-color: #dfe8f6;
+}
+
+.x-list-resizer {
+ border-left-color: #555;
+ border-right-color: #555;
+}
+
+.x-list-header-inner em.sort-asc,
+.x-list-header-inner em.sort-desc {
+ background-image: url(../images/default/grid/sort-hd.gif);
+ border-color: #99bbe8;
+}
+.x-slider-horz,
+.x-slider-horz .x-slider-end,
+.x-slider-horz .x-slider-inner {
+ background-image: url(../images/default/slider/slider-bg.png);
+}
+
+.x-slider-horz .x-slider-thumb {
+ background-image: url(../images/default/slider/slider-thumb.png);
+}
+
+.x-slider-vert,
+.x-slider-vert .x-slider-end,
+.x-slider-vert .x-slider-inner {
+ background-image: url(../images/default/slider/slider-v-bg.png);
+}
+
+.x-slider-vert .x-slider-thumb {
+ background-image: url(../images/default/slider/slider-v-thumb.png);
+}
+.x-window-dlg .ext-mb-text,
+.x-window-dlg .x-window-header-text {
+ font-size: 12px;
+}
+
+.x-window-dlg .ext-mb-textarea {
+ font: normal 12px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-window-dlg .x-msg-box-wait {
+ background-image: url(../images/default/grid/loading.gif);
+}
+
+.x-window-dlg .ext-mb-info {
+ background-image: url(../images/default/window/icon-info.gif);
+}
+
+.x-window-dlg .ext-mb-warning {
+ background-image: url(../images/default/window/icon-warning.gif);
+}
+
+.x-window-dlg .ext-mb-question {
+ background-image: url(../images/default/window/icon-question.gif);
+}
+
+.x-window-dlg .ext-mb-error {
+ background-image: url(../images/default/window/icon-error.gif);
+}
diff --git a/deluge/ui/web/themes/css/xtheme-gray.css b/deluge/ui/web/themes/css/xtheme-gray.css
new file mode 100644
index 0000000..25bae0a
--- /dev/null
+++ b/deluge/ui/web/themes/css/xtheme-gray.css
@@ -0,0 +1,1791 @@
+/**
+ * Ext JS Library 3.4.0
+ * Copyright(c) 2006-2011 Sencha Inc.
+ * licensing@sencha.com
+ * http://www.sencha.com/license
+ */
+.ext-el-mask {
+ background-color: #ccc;
+}
+
+.ext-el-mask-msg {
+ border-color: #999;
+ background-color: #ddd;
+ background-image: url(../images/gray/panel/white-top-bottom.gif);
+ background-position: 0 -1px;
+}
+.ext-el-mask-msg div {
+ background-color: #eee;
+ border-color: #d0d0d0;
+ color: #222;
+ font: normal 11px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-mask-loading div {
+ background-color: #fbfbfb;
+ background-image: url(../images/default/grid/loading.gif);
+}
+
+.x-item-disabled {
+ color: gray;
+}
+
+.x-item-disabled * {
+ color: gray !important;
+}
+
+.x-splitbar-proxy {
+ background-color: #aaa;
+}
+
+.x-color-palette a {
+ border-color: #fff;
+}
+
+.x-color-palette a:hover,
+.x-color-palette a.x-color-palette-sel {
+ border-color: #cfcfcf;
+ background-color: #eaeaea;
+}
+
+/*
+.x-color-palette em:hover, .x-color-palette span:hover{
+ background-color: #eaeaea;
+}
+*/
+
+.x-color-palette em {
+ border-color: #aca899;
+}
+
+.x-ie-shadow {
+ background-color: #777;
+}
+
+.x-shadow .xsmc {
+ background-image: url(../images/default/shadow-c.png);
+}
+
+.x-shadow .xsml,
+.x-shadow .xsmr {
+ background-image: url(../images/default/shadow-lr.png);
+}
+
+.x-shadow .xstl,
+.x-shadow .xstc,
+.x-shadow .xstr,
+.x-shadow .xsbl,
+.x-shadow .xsbc,
+.x-shadow .xsbr {
+ background-image: url(../images/default/shadow.png);
+}
+
+.loading-indicator {
+ font-size: 11px;
+ background-image: url(../images/default/grid/loading.gif);
+}
+
+.x-spotlight {
+ background-color: #ccc;
+}
+.x-tab-panel-header,
+.x-tab-panel-footer {
+ background-color: #eaeaea;
+ border-color: #d0d0d0;
+ overflow: hidden;
+ zoom: 1;
+}
+
+.x-tab-panel-header,
+.x-tab-panel-footer {
+ border-color: #d0d0d0;
+}
+
+ul.x-tab-strip-top {
+ background-color: #dbdbdb;
+ background-image: url(../images/gray/tabs/tab-strip-bg.gif);
+ border-bottom-color: #d0d0d0;
+}
+
+ul.x-tab-strip-bottom {
+ background-color: #dbdbdb;
+ background-image: url(../images/gray/tabs/tab-strip-btm-bg.gif);
+ border-top-color: #d0d0d0;
+}
+
+.x-tab-panel-header-plain .x-tab-strip-spacer,
+.x-tab-panel-footer-plain .x-tab-strip-spacer {
+ border-color: #d0d0d0;
+ background-color: #eaeaea;
+}
+
+.x-tab-strip span.x-tab-strip-text {
+ font: normal 11px tahoma, arial, helvetica;
+ color: #333;
+}
+
+.x-tab-strip-over span.x-tab-strip-text {
+ color: #111;
+}
+
+.x-tab-strip-active span.x-tab-strip-text {
+ color: #333;
+ font-weight: bold;
+}
+
+.x-tab-strip-disabled .x-tabs-text {
+ color: #aaaaaa;
+}
+
+.x-tab-strip-top .x-tab-right,
+.x-tab-strip-top .x-tab-left,
+.x-tab-strip-top .x-tab-strip-inner {
+ background-image: url(../images/gray/tabs/tabs-sprite.gif);
+}
+
+.x-tab-strip-bottom .x-tab-right {
+ background-image: url(../images/gray/tabs/tab-btm-inactive-right-bg.gif);
+}
+
+.x-tab-strip-bottom .x-tab-left {
+ background-image: url(../images/gray/tabs/tab-btm-inactive-left-bg.gif);
+}
+
+.x-tab-strip-bottom .x-tab-strip-over .x-tab-left {
+ background-image: url(../images/gray/tabs/tab-btm-over-left-bg.gif);
+}
+
+.x-tab-strip-bottom .x-tab-strip-over .x-tab-right {
+ background-image: url(../images/gray/tabs/tab-btm-over-right-bg.gif);
+}
+
+.x-tab-strip-bottom .x-tab-strip-active .x-tab-right {
+ background-image: url(../images/gray/tabs/tab-btm-right-bg.gif);
+}
+
+.x-tab-strip-bottom .x-tab-strip-active .x-tab-left {
+ background-image: url(../images/gray/tabs/tab-btm-left-bg.gif);
+}
+
+.x-tab-strip .x-tab-strip-closable a.x-tab-strip-close {
+ background-image: url(../images/gray/tabs/tab-close.gif);
+}
+
+.x-tab-strip .x-tab-strip-closable a.x-tab-strip-close:hover {
+ background-image: url(../images/gray/tabs/tab-close.gif);
+}
+
+.x-tab-panel-body {
+ border-color: #d0d0d0;
+ background-color: #fff;
+}
+
+.x-tab-panel-body-top {
+ border-top: 0 none;
+}
+
+.x-tab-panel-body-bottom {
+ border-bottom: 0 none;
+}
+
+.x-tab-scroller-left {
+ background-image: url(../images/gray/tabs/scroll-left.gif);
+ border-bottom-color: #d0d0d0;
+}
+
+.x-tab-scroller-left-over {
+ background-position: 0 0;
+}
+
+.x-tab-scroller-left-disabled {
+ background-position: -18px 0;
+ opacity: 0.5;
+ -moz-opacity: 0.5;
+ filter: alpha(opacity=50);
+ cursor: default;
+}
+
+.x-tab-scroller-right {
+ background-image: url(../images/gray/tabs/scroll-right.gif);
+ border-bottom-color: #d0d0d0;
+}
+
+.x-tab-panel-bbar .x-toolbar,
+.x-tab-panel-tbar .x-toolbar {
+ border-color: #d0d0d0;
+}
+.x-form-field {
+ font: normal 12px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-form-text,
+textarea.x-form-field {
+ background-color: #fff;
+ background-image: url(../images/default/form/text-bg.gif);
+ border-color: #c1c1c1;
+}
+
+.x-form-select-one {
+ background-color: #fff;
+ border-color: #c1c1c1;
+}
+
+.x-form-check-group-label {
+ border-bottom: 1px solid #d0d0d0;
+ color: #333;
+}
+
+.x-editor .x-form-check-wrap {
+ background-color: #fff;
+}
+
+.x-form-field-wrap .x-form-trigger {
+ background-image: url(../images/gray/form/trigger.gif);
+ border-bottom-color: #b5b8c8;
+}
+
+.x-form-field-wrap .x-form-date-trigger {
+ background-image: url(../images/gray/form/date-trigger.gif);
+}
+
+.x-form-field-wrap .x-form-clear-trigger {
+ background-image: url(../images/gray/form/clear-trigger.gif);
+}
+
+.x-form-field-wrap .x-form-search-trigger {
+ background-image: url(../images/gray/form/search-trigger.gif);
+}
+
+.x-trigger-wrap-focus .x-form-trigger {
+ border-bottom-color: #777777;
+}
+
+.x-item-disabled .x-form-trigger-over {
+ border-bottom-color: #b5b8c8;
+}
+
+.x-item-disabled .x-form-trigger-click {
+ border-bottom-color: #b5b8c8;
+}
+
+.x-form-focus,
+textarea.x-form-focus {
+ border-color: #777777;
+}
+
+.x-form-invalid,
+textarea.x-form-invalid {
+ background-color: #fff;
+ background-image: url(../images/default/grid/invalid_line.gif);
+ border-color: #c30;
+}
+
+.ext-webkit .x-form-invalid {
+ background-color: #fee;
+ border-color: #ff7870;
+}
+
+.x-form-inner-invalid,
+textarea.x-form-inner-invalid {
+ background-color: #fff;
+ background-image: url(../images/default/grid/invalid_line.gif);
+}
+
+.x-form-grow-sizer {
+ font: normal 12px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-form-item {
+ font: normal 12px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-form-invalid-msg {
+ color: #c0272b;
+ font: normal 11px tahoma, arial, helvetica, sans-serif;
+ background-image: url(../images/default/shared/warning.gif);
+}
+
+.x-form-empty-field {
+ color: gray;
+}
+
+.x-small-editor .x-form-field {
+ font: normal 11px arial, tahoma, helvetica, sans-serif;
+}
+
+.ext-webkit .x-small-editor .x-form-field {
+ font: normal 12px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-form-invalid-icon {
+ background-image: url(../images/default/form/exclamation.gif);
+}
+
+.x-fieldset {
+ border-color: #cccccc;
+}
+
+.x-fieldset legend {
+ font: bold 11px tahoma, arial, helvetica, sans-serif;
+ color: #777777;
+}
+.x-btn {
+ font: normal 11px tahoma, verdana, helvetica;
+}
+
+.x-btn button {
+ font: normal 11px arial, tahoma, verdana, helvetica;
+ color: #333;
+}
+
+.x-btn em {
+ font-style: normal;
+ font-weight: normal;
+}
+
+.x-btn-tl,
+.x-btn-tr,
+.x-btn-tc,
+.x-btn-ml,
+.x-btn-mr,
+.x-btn-mc,
+.x-btn-bl,
+.x-btn-br,
+.x-btn-bc {
+ background-image: url(../images/gray/button/btn.gif);
+}
+
+.x-btn-click .x-btn-text,
+.x-btn-menu-active .x-btn-text,
+.x-btn-pressed .x-btn-text {
+ color: #000;
+}
+
+.x-btn-disabled * {
+ color: gray !important;
+}
+
+.x-btn-mc em.x-btn-arrow {
+ background-image: url(../images/default/button/arrow.gif);
+}
+
+.x-btn-mc em.x-btn-split {
+ background-image: url(../images/default/button/s-arrow.gif);
+}
+
+.x-btn-over .x-btn-mc em.x-btn-split,
+.x-btn-click .x-btn-mc em.x-btn-split,
+.x-btn-menu-active .x-btn-mc em.x-btn-split,
+.x-btn-pressed .x-btn-mc em.x-btn-split {
+ background-image: url(../images/gray/button/s-arrow-o.gif);
+}
+
+.x-btn-mc em.x-btn-arrow-bottom {
+ background-image: url(../images/default/button/s-arrow-b-noline.gif);
+}
+
+.x-btn-mc em.x-btn-split-bottom {
+ background-image: url(../images/default/button/s-arrow-b.gif);
+}
+
+.x-btn-over .x-btn-mc em.x-btn-split-bottom,
+.x-btn-click .x-btn-mc em.x-btn-split-bottom,
+.x-btn-menu-active .x-btn-mc em.x-btn-split-bottom,
+.x-btn-pressed .x-btn-mc em.x-btn-split-bottom {
+ background-image: url(../images/gray/button/s-arrow-bo.gif);
+}
+
+.x-btn-group-header {
+ color: #666;
+}
+
+.x-btn-group-tc {
+ background-image: url(../images/gray/button/group-tb.gif);
+}
+
+.x-btn-group-tl {
+ background-image: url(../images/gray/button/group-cs.gif);
+}
+
+.x-btn-group-tr {
+ background-image: url(../images/gray/button/group-cs.gif);
+}
+
+.x-btn-group-bc {
+ background-image: url(../images/gray/button/group-tb.gif);
+}
+
+.x-btn-group-bl {
+ background-image: url(../images/gray/button/group-cs.gif);
+}
+
+.x-btn-group-br {
+ background-image: url(../images/gray/button/group-cs.gif);
+}
+
+.x-btn-group-ml {
+ background-image: url(../images/gray/button/group-lr.gif);
+}
+.x-btn-group-mr {
+ background-image: url(../images/gray/button/group-lr.gif);
+}
+
+.x-btn-group-notitle .x-btn-group-tc {
+ background-image: url(../images/gray/button/group-tb.gif);
+}
+.x-toolbar {
+ border-color: #d0d0d0;
+ background-color: #f0f0f0;
+ background-image: url(../images/gray/toolbar/bg.gif);
+}
+
+.x-toolbar td,
+.x-toolbar span,
+.x-toolbar input,
+.x-toolbar div,
+.x-toolbar select,
+.x-toolbar label {
+ font: normal 11px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-toolbar .x-item-disabled {
+ color: gray;
+}
+
+.x-toolbar .x-item-disabled * {
+ color: gray;
+}
+
+.x-toolbar .x-btn-mc em.x-btn-split {
+ background-image: url(../images/default/button/s-arrow-noline.gif);
+}
+
+.x-toolbar .x-btn-over .x-btn-mc em.x-btn-split,
+.x-toolbar .x-btn-click .x-btn-mc em.x-btn-split,
+.x-toolbar .x-btn-menu-active .x-btn-mc em.x-btn-split,
+.x-toolbar .x-btn-pressed .x-btn-mc em.x-btn-split {
+ background-image: url(../images/gray/button/s-arrow-o.gif);
+}
+
+.x-toolbar .x-btn-mc em.x-btn-split-bottom {
+ background-image: url(../images/default/button/s-arrow-b-noline.gif);
+}
+
+.x-toolbar .x-btn-over .x-btn-mc em.x-btn-split-bottom,
+.x-toolbar .x-btn-click .x-btn-mc em.x-btn-split-bottom,
+.x-toolbar .x-btn-menu-active .x-btn-mc em.x-btn-split-bottom,
+.x-toolbar .x-btn-pressed .x-btn-mc em.x-btn-split-bottom {
+ background-image: url(../images/gray/button/s-arrow-bo.gif);
+}
+
+.x-toolbar .xtb-sep {
+ background-image: url(../images/default/grid/grid-split.gif);
+}
+
+.x-tbar-page-first {
+ background-image: url(../images/gray/grid/page-first.gif) !important;
+}
+
+.x-tbar-loading {
+ background-image: url(../images/gray/grid/refresh.gif) !important;
+}
+
+.x-tbar-page-last {
+ background-image: url(../images/gray/grid/page-last.gif) !important;
+}
+
+.x-tbar-page-next {
+ background-image: url(../images/gray/grid/page-next.gif) !important;
+}
+
+.x-tbar-page-prev {
+ background-image: url(../images/gray/grid/page-prev.gif) !important;
+}
+
+.x-item-disabled .x-tbar-loading {
+ background-image: url(../images/default/grid/loading.gif) !important;
+}
+
+.x-item-disabled .x-tbar-page-first {
+ background-image: url(../images/default/grid/page-first-disabled.gif) !important;
+}
+
+.x-item-disabled .x-tbar-page-last {
+ background-image: url(../images/default/grid/page-last-disabled.gif) !important;
+}
+
+.x-item-disabled .x-tbar-page-next {
+ background-image: url(../images/default/grid/page-next-disabled.gif) !important;
+}
+
+.x-item-disabled .x-tbar-page-prev {
+ background-image: url(../images/default/grid/page-prev-disabled.gif) !important;
+}
+
+.x-paging-info {
+ color: #444;
+}
+
+.x-toolbar-more-icon {
+ background-image: url(../images/gray/toolbar/more.gif) !important;
+}
+.x-resizable-handle {
+ background-color: #fff;
+}
+
+.x-resizable-over .x-resizable-handle-east,
+.x-resizable-pinned .x-resizable-handle-east,
+.x-resizable-over .x-resizable-handle-west,
+.x-resizable-pinned .x-resizable-handle-west {
+ background-image: url(../images/gray/sizer/e-handle.gif);
+}
+
+.x-resizable-over .x-resizable-handle-south,
+.x-resizable-pinned .x-resizable-handle-south,
+.x-resizable-over .x-resizable-handle-north,
+.x-resizable-pinned .x-resizable-handle-north {
+ background-image: url(../images/gray/sizer/s-handle.gif);
+}
+
+.x-resizable-over .x-resizable-handle-north,
+.x-resizable-pinned .x-resizable-handle-north {
+ background-image: url(../images/gray/sizer/s-handle.gif);
+}
+.x-resizable-over .x-resizable-handle-southeast,
+.x-resizable-pinned .x-resizable-handle-southeast {
+ background-image: url(../images/gray/sizer/se-handle.gif);
+}
+.x-resizable-over .x-resizable-handle-northwest,
+.x-resizable-pinned .x-resizable-handle-northwest {
+ background-image: url(../images/gray/sizer/nw-handle.gif);
+}
+.x-resizable-over .x-resizable-handle-northeast,
+.x-resizable-pinned .x-resizable-handle-northeast {
+ background-image: url(../images/gray/sizer/ne-handle.gif);
+}
+.x-resizable-over .x-resizable-handle-southwest,
+.x-resizable-pinned .x-resizable-handle-southwest {
+ background-image: url(../images/gray/sizer/sw-handle.gif);
+}
+.x-resizable-proxy {
+ border-color: #565656;
+}
+.x-resizable-overlay {
+ background-color: #fff;
+}
+.x-grid3 {
+ background-color: #fff;
+}
+
+.x-grid-panel .x-panel-mc .x-panel-body {
+ border-color: #d0d0d0;
+}
+
+.x-grid3-row td,
+.x-grid3-summary-row td {
+ font: normal 11px/13px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-grid3-hd-row td {
+ font: normal 11px/15px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-grid3-hd-row td {
+ border-left-color: #eee;
+ border-right-color: #d0d0d0;
+}
+
+.x-grid-row-loading {
+ background-color: #fff;
+ background-image: url(../images/default/shared/loading-balls.gif);
+}
+
+.x-grid3-row {
+ border-color: #ededed;
+ border-top-color: #fff;
+}
+
+.x-grid3-row-alt {
+ background-color: #fafafa;
+}
+
+.x-grid3-row-over {
+ border-color: #ddd;
+ background-color: #efefef;
+ background-image: url(../images/default/grid/row-over.gif);
+}
+
+.x-grid3-resize-proxy {
+ background-color: #777;
+}
+
+.x-grid3-resize-marker {
+ background-color: #777;
+}
+
+.x-grid3-header {
+ background-color: #f9f9f9;
+ background-image: url(../images/gray/grid/grid3-hrow2.gif);
+}
+
+.x-grid3-header-pop {
+ border-left-color: #d0d0d0;
+}
+
+.x-grid3-header-pop-inner {
+ border-left-color: #eee;
+ background-image: url(../images/default/grid/hd-pop.gif);
+}
+
+td.x-grid3-hd-over,
+td.sort-desc,
+td.sort-asc,
+td.x-grid3-hd-menu-open {
+ border-left-color: #acacac;
+ border-right-color: #acacac;
+}
+
+td.x-grid3-hd-over .x-grid3-hd-inner,
+td.sort-desc .x-grid3-hd-inner,
+td.sort-asc .x-grid3-hd-inner,
+td.x-grid3-hd-menu-open .x-grid3-hd-inner {
+ background-color: #f9f9f9;
+ background-image: url(../images/gray/grid/grid3-hrow-over2.gif);
+}
+
+.sort-asc .x-grid3-sort-icon {
+ background-image: url(../images/gray/grid/sort_asc.gif);
+}
+
+.sort-desc .x-grid3-sort-icon {
+ background-image: url(../images/gray/grid/sort_desc.gif);
+}
+
+.x-grid3-cell-text,
+.x-grid3-hd-text {
+ color: #000;
+}
+
+.x-grid3-split {
+ background-image: url(../images/default/grid/grid-split.gif);
+}
+
+.x-grid3-hd-text {
+ color: #333;
+}
+
+.x-dd-drag-proxy .x-grid3-hd-inner {
+ background-color: #f9f9f9;
+ background-image: url(../images/gray/grid/grid3-hrow-over2.gif);
+ border-color: #acacac;
+}
+
+.col-move-top {
+ background-image: url(../images/gray/grid/col-move-top.gif);
+}
+
+.col-move-bottom {
+ background-image: url(../images/gray/grid/col-move-bottom.gif);
+}
+
+.x-grid3-row-selected {
+ background-color: #cccccc !important;
+ background-image: none;
+ border-color: #acacac;
+}
+
+.x-grid3-cell-selected {
+ background-color: #cbcbcb !important;
+ color: #000;
+}
+
+.x-grid3-cell-selected span {
+ color: #000 !important;
+}
+
+.x-grid3-cell-selected .x-grid3-cell-text {
+ color: #000;
+}
+
+.x-grid3-locked td.x-grid3-row-marker,
+.x-grid3-locked .x-grid3-row-selected td.x-grid3-row-marker {
+ background-color: #ebeadb !important;
+ background-image: url(../images/default/grid/grid-hrow.gif) !important;
+ color: #000;
+ border-top-color: #fff;
+ border-right-color: #6fa0df !important;
+}
+
+.x-grid3-locked td.x-grid3-row-marker div,
+.x-grid3-locked .x-grid3-row-selected td.x-grid3-row-marker div {
+ color: #333 !important;
+}
+
+.x-grid3-dirty-cell {
+ background-image: url(../images/default/grid/dirty.gif);
+}
+
+.x-grid3-topbar,
+.x-grid3-bottombar {
+ font: normal 11px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-grid3-bottombar .x-toolbar {
+ border-top-color: #a9bfd3;
+}
+
+.x-props-grid .x-grid3-td-name .x-grid3-cell-inner {
+ background-image: url(../images/default/grid/grid3-special-col-bg.gif) !important;
+ color: #000 !important;
+}
+
+.x-props-grid .x-grid3-body .x-grid3-td-name {
+ background-color: #fff !important;
+ border-right-color: #eee;
+}
+
+.xg-hmenu-sort-asc .x-menu-item-icon {
+ background-image: url(../images/default/grid/hmenu-asc.gif);
+}
+
+.xg-hmenu-sort-desc .x-menu-item-icon {
+ background-image: url(../images/default/grid/hmenu-desc.gif);
+}
+
+.xg-hmenu-lock .x-menu-item-icon {
+ background-image: url(../images/default/grid/hmenu-lock.gif);
+}
+
+.xg-hmenu-unlock .x-menu-item-icon {
+ background-image: url(../images/default/grid/hmenu-unlock.gif);
+}
+
+.x-grid3-hd-btn {
+ background-color: #f9f9f9;
+ background-image: url(../images/gray/grid/grid3-hd-btn.gif);
+}
+
+.x-grid3-body .x-grid3-td-expander {
+ background-image: url(../images/default/grid/grid3-special-col-bg.gif);
+}
+
+.x-grid3-row-expander {
+ background-image: url(../images/gray/grid/row-expand-sprite.gif);
+}
+
+.x-grid3-body .x-grid3-td-checker {
+ background-image: url(../images/default/grid/grid3-special-col-bg.gif);
+}
+
+.x-grid3-row-checker,
+.x-grid3-hd-checker {
+ background-image: url(../images/default/grid/row-check-sprite.gif);
+}
+
+.x-grid3-body .x-grid3-td-numberer {
+ background-image: url(../images/default/grid/grid3-special-col-bg.gif);
+}
+
+.x-grid3-body .x-grid3-td-numberer .x-grid3-cell-inner {
+ color: #444;
+}
+
+.x-grid3-body .x-grid3-td-row-icon {
+ background-image: url(../images/default/grid/grid3-special-col-bg.gif);
+}
+
+.x-grid3-body .x-grid3-row-selected .x-grid3-td-numberer,
+.x-grid3-body .x-grid3-row-selected .x-grid3-td-checker,
+.x-grid3-body .x-grid3-row-selected .x-grid3-td-expander {
+ background-image: url(../images/gray/grid/grid3-special-col-sel-bg.gif);
+}
+
+.x-grid3-check-col {
+ background-image: url(../images/default/menu/unchecked.gif);
+}
+
+.x-grid3-check-col-on {
+ background-image: url(../images/default/menu/checked.gif);
+}
+
+.x-grid-group,
+.x-grid-group-body,
+.x-grid-group-hd {
+ zoom: 1;
+}
+
+.x-grid-group-hd {
+ border-bottom-color: #d0d0d0;
+}
+
+.x-grid-group-hd div.x-grid-group-title {
+ background-image: url(../images/gray/grid/group-collapse.gif);
+ color: #5f5f5f;
+ font: bold 11px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-grid-group-collapsed .x-grid-group-hd div.x-grid-group-title {
+ background-image: url(../images/gray/grid/group-expand.gif);
+}
+
+.x-group-by-icon {
+ background-image: url(../images/default/grid/group-by.gif);
+}
+
+.x-cols-icon {
+ background-image: url(../images/default/grid/columns.gif);
+}
+
+.x-show-groups-icon {
+ background-image: url(../images/default/grid/group-by.gif);
+}
+
+.x-grid-empty {
+ color: gray;
+ font: normal 11px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-grid-with-col-lines .x-grid3-row td.x-grid3-cell {
+ border-right-color: #ededed;
+}
+
+.x-grid-with-col-lines .x-grid3-row {
+ border-top-color: #ededed;
+}
+
+.x-grid-with-col-lines .x-grid3-row-selected {
+ border-top-color: #b9b9b9;
+}
+.x-pivotgrid .x-grid3-header-offset table td {
+ background: url(../images/gray/grid/grid3-hrow2.gif) repeat-x 50% 100%;
+ border-left: 1px solid;
+ border-right: 1px solid;
+ border-left-color: #d0d0d0;
+ border-right-color: #d0d0d0;
+}
+
+.x-pivotgrid .x-grid3-row-headers {
+ background-color: #f9f9f9;
+}
+
+.x-pivotgrid .x-grid3-row-headers table td {
+ background: #eee url(../images/default/grid/grid3-rowheader.gif) repeat-x
+ left top;
+ border-left: 1px solid;
+ border-right: 1px solid;
+ border-left-color: #eee;
+ border-right-color: #d0d0d0;
+ border-bottom: 1px solid;
+ border-bottom-color: #d0d0d0;
+ height: 18px;
+}
+.x-dd-drag-ghost {
+ color: #000;
+ font: normal 11px arial, helvetica, sans-serif;
+ border-color: #ddd #bbb #bbb #ddd;
+ background-color: #fff;
+}
+
+.x-dd-drop-nodrop .x-dd-drop-icon {
+ background-image: url(../images/default/dd/drop-no.gif);
+}
+
+.x-dd-drop-ok .x-dd-drop-icon {
+ background-image: url(../images/default/dd/drop-yes.gif);
+}
+
+.x-dd-drop-ok-add .x-dd-drop-icon {
+ background-image: url(../images/default/dd/drop-add.gif);
+}
+
+.x-view-selector {
+ background-color: #d6d6d6;
+ border-color: #888888;
+}
+.x-tree-node-expanded .x-tree-node-icon {
+ background-image: url(../images/default/tree/folder-open.gif);
+}
+
+.x-tree-node-leaf .x-tree-node-icon {
+ background-image: url(../images/default/tree/leaf.gif);
+}
+
+.x-tree-node-collapsed .x-tree-node-icon {
+ background-image: url(../images/default/tree/folder.gif);
+}
+
+.x-tree-node-loading .x-tree-node-icon {
+ background-image: url(../images/default/tree/loading.gif) !important;
+}
+
+.x-tree-node .x-tree-node-inline-icon {
+ background-image: none;
+}
+
+.x-tree-node-loading a span {
+ font-style: italic;
+ color: #444444;
+}
+
+.ext-ie .x-tree-node-el input {
+ width: 15px;
+ height: 15px;
+}
+
+.x-tree-lines .x-tree-elbow {
+ background-image: url(../images/default/tree/elbow.gif);
+}
+
+.x-tree-lines .x-tree-elbow-plus {
+ background-image: url(../images/default/tree/elbow-plus.gif);
+}
+
+.x-tree-lines .x-tree-elbow-minus {
+ background-image: url(../images/default/tree/elbow-minus.gif);
+}
+
+.x-tree-lines .x-tree-elbow-end {
+ background-image: url(../images/default/tree/elbow-end.gif);
+}
+
+.x-tree-lines .x-tree-elbow-end-plus {
+ background-image: url(../images/gray/tree/elbow-end-plus.gif);
+}
+
+.x-tree-lines .x-tree-elbow-end-minus {
+ background-image: url(../images/gray/tree/elbow-end-minus.gif);
+}
+
+.x-tree-lines .x-tree-elbow-line {
+ background-image: url(../images/default/tree/elbow-line.gif);
+}
+
+.x-tree-no-lines .x-tree-elbow-plus {
+ background-image: url(../images/default/tree/elbow-plus-nl.gif);
+}
+
+.x-tree-no-lines .x-tree-elbow-minus {
+ background-image: url(../images/default/tree/elbow-minus-nl.gif);
+}
+
+.x-tree-no-lines .x-tree-elbow-end-plus {
+ background-image: url(../images/gray/tree/elbow-end-plus-nl.gif);
+}
+
+.x-tree-no-lines .x-tree-elbow-end-minus {
+ background-image: url(../images/gray/tree/elbow-end-minus-nl.gif);
+}
+
+.x-tree-arrows .x-tree-elbow-plus {
+ background-image: url(../images/gray/tree/arrows.gif);
+}
+
+.x-tree-arrows .x-tree-elbow-minus {
+ background-image: url(../images/gray/tree/arrows.gif);
+}
+
+.x-tree-arrows .x-tree-elbow-end-plus {
+ background-image: url(../images/gray/tree/arrows.gif);
+}
+
+.x-tree-arrows .x-tree-elbow-end-minus {
+ background-image: url(../images/gray/tree/arrows.gif);
+}
+
+.x-tree-node {
+ color: #000;
+ font: normal 11px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-tree-node a,
+.x-dd-drag-ghost a {
+ color: #000;
+}
+
+.x-tree-node a span,
+.x-dd-drag-ghost a span {
+ color: #000;
+}
+
+.x-tree-node .x-tree-node-disabled a span {
+ color: gray !important;
+}
+
+.x-tree-node div.x-tree-drag-insert-below {
+ border-bottom-color: #36c;
+}
+
+.x-tree-node div.x-tree-drag-insert-above {
+ border-top-color: #36c;
+}
+
+.x-tree-dd-underline .x-tree-node div.x-tree-drag-insert-below a {
+ border-bottom-color: #36c;
+}
+
+.x-tree-dd-underline .x-tree-node div.x-tree-drag-insert-above a {
+ border-top-color: #36c;
+}
+
+.x-tree-node .x-tree-drag-append a span {
+ background-color: #ddd;
+ border-color: gray;
+}
+
+.x-tree-node .x-tree-node-over {
+ background-color: #eee;
+}
+
+.x-tree-node .x-tree-selected {
+ background-color: #ddd;
+}
+
+.x-tree-drop-ok-append .x-dd-drop-icon {
+ background-image: url(../images/default/tree/drop-add.gif);
+}
+
+.x-tree-drop-ok-above .x-dd-drop-icon {
+ background-image: url(../images/default/tree/drop-over.gif);
+}
+
+.x-tree-drop-ok-below .x-dd-drop-icon {
+ background-image: url(../images/default/tree/drop-under.gif);
+}
+
+.x-tree-drop-ok-between .x-dd-drop-icon {
+ background-image: url(../images/default/tree/drop-between.gif);
+}
+.x-date-picker {
+ border-color: #585858;
+ background-color: #fff;
+}
+
+.x-date-middle,
+.x-date-left,
+.x-date-right {
+ background-image: url(../images/gray/shared/hd-sprite.gif);
+ color: #fff;
+ font: bold 11px 'sans serif', tahoma, verdana, helvetica;
+}
+
+.x-date-middle .x-btn .x-btn-text {
+ color: #fff;
+}
+
+.x-date-middle .x-btn-mc em.x-btn-arrow {
+ background-image: url(../images/gray/toolbar/btn-arrow-light.gif);
+}
+
+.x-date-right a {
+ background-image: url(../images/gray/shared/right-btn.gif);
+}
+
+.x-date-left a {
+ background-image: url(../images/gray/shared/left-btn.gif);
+}
+
+.x-date-inner th {
+ background-color: #d8d8d8;
+ background-image: url(../images/gray/panel/white-top-bottom.gif);
+ border-bottom-color: #afafaf;
+ font: normal 10px arial, helvetica, tahoma, sans-serif;
+ color: #595959;
+}
+
+.x-date-inner td {
+ border-color: #fff;
+}
+
+.x-date-inner a {
+ font: normal 11px arial, helvetica, tahoma, sans-serif;
+ color: #000;
+}
+
+.x-date-inner .x-date-active {
+ color: #000;
+}
+
+.x-date-inner .x-date-selected a {
+ background-image: none;
+ background-color: #d8d8d8;
+ border-color: #dcdcdc;
+}
+
+.x-date-inner .x-date-today a {
+ border-color: darkred;
+}
+
+.x-date-inner .x-date-selected span {
+ font-weight: bold;
+}
+
+.x-date-inner .x-date-prevday a,
+.x-date-inner .x-date-nextday a {
+ color: #aaa;
+}
+
+.x-date-bottom {
+ border-top-color: #afafaf;
+ background-color: #d8d8d8;
+ background: #d8d8d8 url(../images/gray/panel/white-top-bottom.gif) 0 -2px;
+}
+
+.x-date-inner a:hover,
+.x-date-inner .x-date-disabled a:hover {
+ color: #000;
+ background-color: #d8d8d8;
+}
+
+.x-date-inner .x-date-disabled a {
+ background-color: #eee;
+ color: #bbb;
+}
+
+.x-date-mmenu {
+ background-color: #eee !important;
+}
+
+.x-date-mmenu .x-menu-item {
+ font-size: 10px;
+ color: #000;
+}
+
+.x-date-mp {
+ background-color: #fff;
+}
+
+.x-date-mp td {
+ font: normal 11px arial, helvetica, tahoma, sans-serif;
+}
+
+.x-date-mp-btns button {
+ background-color: #4e565f;
+ color: #fff;
+ border-color: #c0c0c0 #434343 #434343 #c0c0c0;
+ font: normal 11px arial, helvetica, tahoma, sans-serif;
+}
+
+.x-date-mp-btns {
+ background-color: #d8d8d8;
+ background: #d8d8d8 url(../images/gray/panel/white-top-bottom.gif) 0 -2px;
+}
+
+.x-date-mp-btns td {
+ border-top-color: #afafaf;
+}
+
+td.x-date-mp-month a,
+td.x-date-mp-year a {
+ color: #333;
+}
+
+td.x-date-mp-month a:hover,
+td.x-date-mp-year a:hover {
+ color: #333;
+ background-color: #fdfdfd;
+}
+
+td.x-date-mp-sel a {
+ background-color: #d8d8d8;
+ background: #d8d8d8 url(../images/gray/panel/white-top-bottom.gif) 0 -2px;
+ border-color: #dcdcdc;
+}
+
+.x-date-mp-ybtn a {
+ background-image: url(../images/gray/panel/tool-sprites.gif);
+}
+
+td.x-date-mp-sep {
+ border-right-color: #d7d7d7;
+}
+.x-tip .x-tip-close {
+ background-image: url(../images/gray/qtip/close.gif);
+}
+
+.x-tip .x-tip-tc,
+.x-tip .x-tip-tl,
+.x-tip .x-tip-tr,
+.x-tip .x-tip-bc,
+.x-tip .x-tip-bl,
+.x-tip .x-tip-br,
+.x-tip .x-tip-ml,
+.x-tip .x-tip-mr {
+ background-image: url(../images/gray/qtip/tip-sprite.gif);
+}
+
+.x-tip .x-tip-mc {
+ font: normal 11px tahoma, arial, helvetica, sans-serif;
+}
+.x-tip .x-tip-ml {
+ background-color: #fff;
+}
+
+.x-tip .x-tip-header-text {
+ font: bold 11px tahoma, arial, helvetica, sans-serif;
+ color: #444;
+}
+
+.x-tip .x-tip-body {
+ font: normal 11px tahoma, arial, helvetica, sans-serif;
+ color: #444;
+}
+
+.x-form-invalid-tip .x-tip-tc,
+.x-form-invalid-tip .x-tip-tl,
+.x-form-invalid-tip .x-tip-tr,
+.x-form-invalid-tip .x-tip-bc,
+.x-form-invalid-tip .x-tip-bl,
+.x-form-invalid-tip .x-tip-br,
+.x-form-invalid-tip .x-tip-ml,
+.x-form-invalid-tip .x-tip-mr {
+ background-image: url(../images/default/form/error-tip-corners.gif);
+}
+
+.x-form-invalid-tip .x-tip-body {
+ background-image: url(../images/default/form/exclamation.gif);
+}
+
+.x-tip-anchor {
+ background-image: url(../images/gray/qtip/tip-anchor-sprite.gif);
+}
+.x-menu {
+ background-color: #f0f0f0;
+ background-image: url(../images/default/menu/menu.gif);
+}
+
+.x-menu-floating {
+ border-color: #7d7d7d;
+}
+
+.x-menu-nosep {
+ background-image: none;
+}
+
+.x-menu-list-item {
+ font: normal 11px arial, tahoma, sans-serif;
+}
+
+.x-menu-item-arrow {
+ background-image: url(../images/gray/menu/menu-parent.gif);
+}
+
+.x-menu-sep {
+ background-color: #e0e0e0;
+ border-bottom-color: #fff;
+}
+
+a.x-menu-item {
+ color: #222;
+}
+
+.x-menu-item-active {
+ background-image: url(../images/gray/menu/item-over.gif);
+ background-color: #f1f1f1;
+ border-color: #acacac;
+}
+
+.x-menu-item-active a.x-menu-item {
+ border-color: #acacac;
+}
+
+.x-menu-check-item .x-menu-item-icon {
+ background-image: url(../images/default/menu/unchecked.gif);
+}
+
+.x-menu-item-checked .x-menu-item-icon {
+ background-image: url(../images/default/menu/checked.gif);
+}
+
+.x-menu-item-checked .x-menu-group-item .x-menu-item-icon {
+ background-image: url(../images/gray/menu/group-checked.gif);
+}
+
+.x-menu-group-item .x-menu-item-icon {
+ background-image: none;
+}
+
+.x-menu-plain {
+ background-color: #fff !important;
+}
+
+.x-menu .x-date-picker {
+ border-color: #afafaf;
+}
+
+.x-cycle-menu .x-menu-item-checked {
+ border-color: #b9b9b9 !important;
+ background-color: #f1f1f1;
+}
+
+.x-menu-scroller-top {
+ background-image: url(../images/default/layout/mini-top.gif);
+}
+
+.x-menu-scroller-bottom {
+ background-image: url(../images/default/layout/mini-bottom.gif);
+}
+.x-box-tl {
+ background-image: url(../images/default/box/corners.gif);
+}
+
+.x-box-tc {
+ background-image: url(../images/default/box/tb.gif);
+}
+
+.x-box-tr {
+ background-image: url(../images/default/box/corners.gif);
+}
+
+.x-box-ml {
+ background-image: url(../images/default/box/l.gif);
+}
+
+.x-box-mc {
+ background-color: #eee;
+ background-image: url(../images/default/box/tb.gif);
+ font-family: 'Myriad Pro', 'Myriad Web', 'Tahoma', 'Helvetica', 'Arial',
+ sans-serif;
+ color: #393939;
+ font-size: 12px;
+}
+
+.x-box-mc h3 {
+ font-size: 14px;
+ font-weight: bold;
+}
+
+.x-box-mr {
+ background-image: url(../images/default/box/r.gif);
+}
+
+.x-box-bl {
+ background-image: url(../images/default/box/corners.gif);
+}
+
+.x-box-bc {
+ background-image: url(../images/default/box/tb.gif);
+}
+
+.x-box-br {
+ background-image: url(../images/default/box/corners.gif);
+}
+
+.x-box-blue .x-box-bl,
+.x-box-blue .x-box-br,
+.x-box-blue .x-box-tl,
+.x-box-blue .x-box-tr {
+ background-image: url(../images/default/box/corners-blue.gif);
+}
+
+.x-box-blue .x-box-bc,
+.x-box-blue .x-box-mc,
+.x-box-blue .x-box-tc {
+ background-image: url(../images/default/box/tb-blue.gif);
+}
+
+.x-box-blue .x-box-mc {
+ background-color: #c3daf9;
+}
+
+.x-box-blue .x-box-mc h3 {
+ color: #17385b;
+}
+
+.x-box-blue .x-box-ml {
+ background-image: url(../images/default/box/l-blue.gif);
+}
+
+.x-box-blue .x-box-mr {
+ background-image: url(../images/default/box/r-blue.gif);
+}
+.x-combo-list {
+ border-color: #ccc;
+ background-color: #ddd;
+ font: normal 12px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-combo-list-inner {
+ background-color: #fff;
+}
+
+.x-combo-list-hd {
+ font: bold 11px tahoma, arial, helvetica, sans-serif;
+ color: #333;
+ background-image: url(../images/default/layout/panel-title-light-bg.gif);
+ border-bottom-color: #bcbcbc;
+}
+
+.x-resizable-pinned .x-combo-list-inner {
+ border-bottom-color: #bebebe;
+}
+
+.x-combo-list-item {
+ border-color: #fff;
+}
+
+.x-combo-list .x-combo-selected {
+ border-color: #777 !important;
+ background-color: #f0f0f0;
+}
+
+.x-combo-list .x-toolbar {
+ border-top-color: #bcbcbc;
+}
+
+.x-combo-list-small {
+ font: normal 11px tahoma, arial, helvetica, sans-serif;
+}
+.x-panel {
+ border-color: #d0d0d0;
+}
+
+.x-panel-header {
+ color: #333;
+ font-weight: bold;
+ font-size: 11px;
+ font-family: tahoma, arial, verdana, sans-serif;
+ border-color: #d0d0d0;
+ background-image: url(../images/gray/panel/white-top-bottom.gif);
+}
+
+.x-panel-body {
+ border-color: #d0d0d0;
+ background-color: #fff;
+}
+
+.x-panel-bbar .x-toolbar,
+.x-panel-tbar .x-toolbar {
+ border-color: #d0d0d0;
+}
+
+.x-panel-tbar-noheader .x-toolbar,
+.x-panel-mc .x-panel-tbar .x-toolbar {
+ border-top-color: #d0d0d0;
+}
+
+.x-panel-body-noheader,
+.x-panel-mc .x-panel-body {
+ border-top-color: #d0d0d0;
+}
+
+.x-panel-tl .x-panel-header {
+ color: #333;
+ font: bold 11px tahoma, arial, verdana, sans-serif;
+}
+
+.x-panel-tc {
+ background-image: url(../images/gray/panel/top-bottom.gif);
+}
+
+.x-panel-tl,
+.x-panel-tr,
+.x-panel-bl,
+.x-panel-br {
+ background-image: url(../images/gray/panel/corners-sprite.gif);
+ border-bottom-color: #d0d0d0;
+}
+
+.x-panel-bc {
+ background-image: url(../images/gray/panel/top-bottom.gif);
+}
+
+.x-panel-mc {
+ font: normal 11px tahoma, arial, helvetica, sans-serif;
+ background-color: #f1f1f1;
+}
+
+.x-panel-ml {
+ background-color: #fff;
+ background-image: url(../images/gray/panel/left-right.gif);
+}
+
+.x-panel-mr {
+ background-image: url(../images/gray/panel/left-right.gif);
+}
+
+.x-tool {
+ background-image: url(../images/gray/panel/tool-sprites.gif);
+}
+
+.x-panel-ghost {
+ background-color: #f2f2f2;
+}
+
+.x-panel-ghost ul {
+ border-color: #d0d0d0;
+}
+
+.x-panel-dd-spacer {
+ border-color: #d0d0d0;
+}
+
+.x-panel-fbar td,
+.x-panel-fbar span,
+.x-panel-fbar input,
+.x-panel-fbar div,
+.x-panel-fbar select,
+.x-panel-fbar label {
+ font: normal 11px arial, tahoma, helvetica, sans-serif;
+}
+.x-window-proxy {
+ background-color: #fcfcfc;
+ border-color: #d0d0d0;
+}
+
+.x-window-tl .x-window-header {
+ color: #555;
+ font: bold 11px tahoma, arial, verdana, sans-serif;
+}
+
+.x-window-tc {
+ background-image: url(../images/gray/window/top-bottom.png);
+}
+
+.x-window-tl {
+ background-image: url(../images/gray/window/left-corners.png);
+}
+
+.x-window-tr {
+ background-image: url(../images/gray/window/right-corners.png);
+}
+
+.x-window-bc {
+ background-image: url(../images/gray/window/top-bottom.png);
+}
+
+.x-window-bl {
+ background-image: url(../images/gray/window/left-corners.png);
+}
+
+.x-window-br {
+ background-image: url(../images/gray/window/right-corners.png);
+}
+
+.x-window-mc {
+ border-color: #d0d0d0;
+ font: normal 11px tahoma, arial, helvetica, sans-serif;
+ background-color: #e8e8e8;
+}
+
+.x-window-ml {
+ background-image: url(../images/gray/window/left-right.png);
+}
+
+.x-window-mr {
+ background-image: url(../images/gray/window/left-right.png);
+}
+
+.x-window-maximized .x-window-tc {
+ background-color: #fff;
+}
+
+.x-window-bbar .x-toolbar {
+ border-top-color: #d0d0d0;
+}
+
+.x-panel-ghost .x-window-tl {
+ border-bottom-color: #d0d0d0;
+}
+
+.x-panel-collapsed .x-window-tl {
+ border-bottom-color: #d0d0d0;
+}
+
+.x-dlg-mask {
+ background-color: #ccc;
+}
+
+.x-window-plain .x-window-mc {
+ background-color: #e8e8e8;
+ border-color: #d0d0d0 #eeeeee #eeeeee #d0d0d0;
+}
+
+.x-window-plain .x-window-body {
+ border-color: #eeeeee #d0d0d0 #d0d0d0 #eeeeee;
+}
+
+body.x-body-masked .x-window-plain .x-window-mc {
+ background-color: #e4e4e4;
+}
+.x-html-editor-wrap {
+ border-color: #bcbcbc;
+ background-color: #fff;
+}
+.x-html-editor-tb .x-btn-text {
+ background-image: url(../images/default/editor/tb-sprite.gif);
+}
+.x-panel-noborder .x-panel-header-noborder {
+ border-bottom-color: #d0d0d0;
+}
+
+.x-panel-noborder .x-panel-tbar-noborder .x-toolbar {
+ border-bottom-color: #d0d0d0;
+}
+
+.x-panel-noborder .x-panel-bbar-noborder .x-toolbar {
+ border-top-color: #d0d0d0;
+}
+
+.x-tab-panel-bbar-noborder .x-toolbar {
+ border-top-color: #d0d0d0;
+}
+
+.x-tab-panel-tbar-noborder .x-toolbar {
+ border-bottom-color: #d0d0d0;
+}
+
+.x-border-layout-ct {
+ background-color: #f0f0f0;
+}
+.x-border-layout-ct {
+ background-color: #f0f0f0;
+}
+
+.x-accordion-hd {
+ color: #222;
+ font-weight: normal;
+ background-image: url(../images/gray/panel/light-hd.gif);
+}
+
+.x-layout-collapsed {
+ background-color: #dfdfdf;
+ border-color: #d0d0d0;
+}
+
+.x-layout-collapsed-over {
+ background-color: #e7e7e7;
+}
+
+.x-layout-split-west .x-layout-mini {
+ background-image: url(../images/default/layout/mini-left.gif);
+}
+.x-layout-split-east .x-layout-mini {
+ background-image: url(../images/default/layout/mini-right.gif);
+}
+.x-layout-split-north .x-layout-mini {
+ background-image: url(../images/default/layout/mini-top.gif);
+}
+.x-layout-split-south .x-layout-mini {
+ background-image: url(../images/default/layout/mini-bottom.gif);
+}
+
+.x-layout-cmini-west .x-layout-mini {
+ background-image: url(../images/default/layout/mini-right.gif);
+}
+
+.x-layout-cmini-east .x-layout-mini {
+ background-image: url(../images/default/layout/mini-left.gif);
+}
+
+.x-layout-cmini-north .x-layout-mini {
+ background-image: url(../images/default/layout/mini-bottom.gif);
+}
+
+.x-layout-cmini-south .x-layout-mini {
+ background-image: url(../images/default/layout/mini-top.gif);
+}
+.x-progress-wrap {
+ border-color: #8e8e8e;
+}
+
+.x-progress-inner {
+ background-color: #e7e7e7;
+ background-image: url(../images/gray/qtip/bg.gif);
+}
+
+.x-progress-bar {
+ background-color: #bcbcbc;
+ background-image: url(../images/gray/progress/progress-bg.gif);
+ border-top-color: #e2e2e2;
+ border-bottom-color: #a4a4a4;
+ border-right-color: #a4a4a4;
+}
+
+.x-progress-text {
+ font-size: 11px;
+ font-weight: bold;
+ color: #fff;
+}
+
+.x-progress-text-back {
+ color: #5f5f5f;
+}
+.x-list-header {
+ background-color: #f9f9f9;
+ background-image: url(../images/gray/grid/grid3-hrow2.gif);
+}
+
+.x-list-header-inner div em {
+ border-left-color: #ddd;
+ font: normal 11px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-list-body dt em {
+ font: normal 11px arial, tahoma, helvetica, sans-serif;
+}
+
+.x-list-over {
+ background-color: #eee;
+}
+
+.x-list-selected {
+ background-color: #f0f0f0;
+}
+
+.x-list-resizer {
+ border-left-color: #555;
+ border-right-color: #555;
+}
+
+.x-list-header-inner em.sort-asc,
+.x-list-header-inner em.sort-desc {
+ background-image: url(../images/gray/grid/sort-hd.gif);
+ border-color: #d0d0d0;
+}
+.x-slider-horz,
+.x-slider-horz .x-slider-end,
+.x-slider-horz .x-slider-inner {
+ background-image: url(../images/default/slider/slider-bg.png);
+}
+
+.x-slider-horz .x-slider-thumb {
+ background-image: url(../images/gray/slider/slider-thumb.png);
+}
+
+.x-slider-vert,
+.x-slider-vert .x-slider-end,
+.x-slider-vert .x-slider-inner {
+ background-image: url(../images/default/slider/slider-v-bg.png);
+}
+
+.x-slider-vert .x-slider-thumb {
+ background-image: url(../images/gray/slider/slider-v-thumb.png);
+}
+.x-window-dlg .ext-mb-text,
+.x-window-dlg .x-window-header-text {
+ font-size: 12px;
+}
+
+.x-window-dlg .ext-mb-textarea {
+ font: normal 12px tahoma, arial, helvetica, sans-serif;
+}
+
+.x-window-dlg .x-msg-box-wait {
+ background-image: url(../images/default/grid/loading.gif);
+}
+
+.x-window-dlg .ext-mb-info {
+ background-image: url(../images/gray/window/icon-info.gif);
+}
+
+.x-window-dlg .ext-mb-warning {
+ background-image: url(../images/gray/window/icon-warning.gif);
+}
+
+.x-window-dlg .ext-mb-question {
+ background-image: url(../images/gray/window/icon-question.gif);
+}
+
+.x-window-dlg .ext-mb-error {
+ background-image: url(../images/gray/window/icon-error.gif);
+}
diff --git a/deluge/ui/web/themes/images/access/box/corners-blue.gif b/deluge/ui/web/themes/images/access/box/corners-blue.gif
new file mode 100644
index 0000000..fa419b5
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/box/corners-blue.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/box/corners.gif b/deluge/ui/web/themes/images/access/box/corners.gif
new file mode 100644
index 0000000..8aa8cae
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/box/corners.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/box/l-blue.gif b/deluge/ui/web/themes/images/access/box/l-blue.gif
new file mode 100644
index 0000000..5ed7f00
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/box/l-blue.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/box/l.gif b/deluge/ui/web/themes/images/access/box/l.gif
new file mode 100644
index 0000000..0160f97
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/box/l.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/box/r-blue.gif b/deluge/ui/web/themes/images/access/box/r-blue.gif
new file mode 100644
index 0000000..3ea5cae
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/box/r-blue.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/box/r.gif b/deluge/ui/web/themes/images/access/box/r.gif
new file mode 100644
index 0000000..34237f6
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/box/r.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/box/tb-blue.gif b/deluge/ui/web/themes/images/access/box/tb-blue.gif
new file mode 100644
index 0000000..4b1382c
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/box/tb-blue.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/box/tb.gif b/deluge/ui/web/themes/images/access/box/tb.gif
new file mode 100644
index 0000000..435889b
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/box/tb.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/button/arrow.gif b/deluge/ui/web/themes/images/access/button/arrow.gif
new file mode 100644
index 0000000..087b450
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/button/arrow.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/button/btn.gif b/deluge/ui/web/themes/images/access/button/btn.gif
new file mode 100644
index 0000000..3e705ba
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/button/btn.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/button/group-cs.gif b/deluge/ui/web/themes/images/access/button/group-cs.gif
new file mode 100644
index 0000000..aaf0d46
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/button/group-cs.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/button/group-lr.gif b/deluge/ui/web/themes/images/access/button/group-lr.gif
new file mode 100644
index 0000000..374ea75
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/button/group-lr.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/button/group-tb.gif b/deluge/ui/web/themes/images/access/button/group-tb.gif
new file mode 100644
index 0000000..50a9972
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/button/group-tb.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/button/s-arrow-b-noline.gif b/deluge/ui/web/themes/images/access/button/s-arrow-b-noline.gif
new file mode 100644
index 0000000..644e9f3
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/button/s-arrow-b-noline.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/button/s-arrow-b.gif b/deluge/ui/web/themes/images/access/button/s-arrow-b.gif
new file mode 100644
index 0000000..ba55d0a
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/button/s-arrow-b.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/button/s-arrow-bo.gif b/deluge/ui/web/themes/images/access/button/s-arrow-bo.gif
new file mode 100644
index 0000000..c672b60
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/button/s-arrow-bo.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/button/s-arrow-noline.gif b/deluge/ui/web/themes/images/access/button/s-arrow-noline.gif
new file mode 100644
index 0000000..f3cd351
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/button/s-arrow-noline.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/button/s-arrow-o.gif b/deluge/ui/web/themes/images/access/button/s-arrow-o.gif
new file mode 100644
index 0000000..4bdafd0
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/button/s-arrow-o.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/button/s-arrow.gif b/deluge/ui/web/themes/images/access/button/s-arrow.gif
new file mode 100644
index 0000000..a77be7f
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/button/s-arrow.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/editor/tb-sprite.gif b/deluge/ui/web/themes/images/access/editor/tb-sprite.gif
new file mode 100644
index 0000000..bd4011d
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/editor/tb-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/form/checkbox.gif b/deluge/ui/web/themes/images/access/form/checkbox.gif
new file mode 100644
index 0000000..835b346
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/form/checkbox.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/form/clear-trigger.gif b/deluge/ui/web/themes/images/access/form/clear-trigger.gif
new file mode 100644
index 0000000..9bfd184
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/form/clear-trigger.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/form/clear-trigger.psd b/deluge/ui/web/themes/images/access/form/clear-trigger.psd
new file mode 100644
index 0000000..fcd7944
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/form/clear-trigger.psd
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/form/date-trigger.gif b/deluge/ui/web/themes/images/access/form/date-trigger.gif
new file mode 100644
index 0000000..048506d
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/form/date-trigger.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/form/date-trigger.psd b/deluge/ui/web/themes/images/access/form/date-trigger.psd
new file mode 100644
index 0000000..d9f9be1
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/form/date-trigger.psd
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/form/error-tip-corners.gif b/deluge/ui/web/themes/images/access/form/error-tip-corners.gif
new file mode 100644
index 0000000..6ea4c38
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/form/error-tip-corners.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/form/exclamation.gif b/deluge/ui/web/themes/images/access/form/exclamation.gif
new file mode 100644
index 0000000..daa88b8
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/form/exclamation.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/form/radio.gif b/deluge/ui/web/themes/images/access/form/radio.gif
new file mode 100644
index 0000000..36bb91d
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/form/radio.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/form/search-trigger.gif b/deluge/ui/web/themes/images/access/form/search-trigger.gif
new file mode 100644
index 0000000..ab8b3b4
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/form/search-trigger.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/form/search-trigger.psd b/deluge/ui/web/themes/images/access/form/search-trigger.psd
new file mode 100644
index 0000000..4f92b72
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/form/search-trigger.psd
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/form/text-bg.gif b/deluge/ui/web/themes/images/access/form/text-bg.gif
new file mode 100644
index 0000000..4ce90bb
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/form/text-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/form/trigger-tpl.gif b/deluge/ui/web/themes/images/access/form/trigger-tpl.gif
new file mode 100644
index 0000000..2574ead
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/form/trigger-tpl.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/form/trigger.gif b/deluge/ui/web/themes/images/access/form/trigger.gif
new file mode 100644
index 0000000..bd25572
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/form/trigger.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/form/trigger.psd b/deluge/ui/web/themes/images/access/form/trigger.psd
new file mode 100644
index 0000000..c078133
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/form/trigger.psd
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/arrow-left-white.gif b/deluge/ui/web/themes/images/access/grid/arrow-left-white.gif
new file mode 100644
index 0000000..63088f5
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/arrow-left-white.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/arrow-right-white.gif b/deluge/ui/web/themes/images/access/grid/arrow-right-white.gif
new file mode 100644
index 0000000..e9e0678
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/arrow-right-white.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/col-move-bottom.gif b/deluge/ui/web/themes/images/access/grid/col-move-bottom.gif
new file mode 100644
index 0000000..cc1e473
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/col-move-bottom.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/col-move-top.gif b/deluge/ui/web/themes/images/access/grid/col-move-top.gif
new file mode 100644
index 0000000..58ff32c
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/col-move-top.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/columns.gif b/deluge/ui/web/themes/images/access/grid/columns.gif
new file mode 100644
index 0000000..2d3a823
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/columns.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/dirty.gif b/deluge/ui/web/themes/images/access/grid/dirty.gif
new file mode 100644
index 0000000..d524ee5
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/dirty.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/done.gif b/deluge/ui/web/themes/images/access/grid/done.gif
new file mode 100644
index 0000000..a937cb2
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/done.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/drop-no.gif b/deluge/ui/web/themes/images/access/grid/drop-no.gif
new file mode 100644
index 0000000..31a332b
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/drop-no.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/drop-yes.gif b/deluge/ui/web/themes/images/access/grid/drop-yes.gif
new file mode 100644
index 0000000..926010e
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/drop-yes.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/footer-bg.gif b/deluge/ui/web/themes/images/access/grid/footer-bg.gif
new file mode 100644
index 0000000..126120f
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/footer-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/grid-blue-hd.gif b/deluge/ui/web/themes/images/access/grid/grid-blue-hd.gif
new file mode 100644
index 0000000..862094e
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/grid-blue-hd.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/grid-blue-split.gif b/deluge/ui/web/themes/images/access/grid/grid-blue-split.gif
new file mode 100644
index 0000000..1b0bae3
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/grid-blue-split.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/grid-hrow.gif b/deluge/ui/web/themes/images/access/grid/grid-hrow.gif
new file mode 100644
index 0000000..6374104
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/grid-hrow.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/grid-loading.gif b/deluge/ui/web/themes/images/access/grid/grid-loading.gif
new file mode 100644
index 0000000..d112c54
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/grid-loading.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/grid-split.gif b/deluge/ui/web/themes/images/access/grid/grid-split.gif
new file mode 100644
index 0000000..c76a16e
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/grid-split.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/grid-vista-hd.gif b/deluge/ui/web/themes/images/access/grid/grid-vista-hd.gif
new file mode 100644
index 0000000..d097263
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/grid-vista-hd.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/grid3-hd-btn.gif b/deluge/ui/web/themes/images/access/grid/grid3-hd-btn.gif
new file mode 100644
index 0000000..9ecd650
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/grid3-hd-btn.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/grid3-hrow-over.gif b/deluge/ui/web/themes/images/access/grid/grid3-hrow-over.gif
new file mode 100644
index 0000000..0405f6c
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/grid3-hrow-over.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/grid3-hrow.gif b/deluge/ui/web/themes/images/access/grid/grid3-hrow.gif
new file mode 100644
index 0000000..509737a
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/grid3-hrow.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/grid3-special-col-bg.gif b/deluge/ui/web/themes/images/access/grid/grid3-special-col-bg.gif
new file mode 100644
index 0000000..8ec57f5
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/grid3-special-col-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/grid3-special-col-sel-bg.gif b/deluge/ui/web/themes/images/access/grid/grid3-special-col-sel-bg.gif
new file mode 100644
index 0000000..93a9ca6
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/grid3-special-col-sel-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/group-by.gif b/deluge/ui/web/themes/images/access/grid/group-by.gif
new file mode 100644
index 0000000..d6075bb
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/group-by.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/group-collapse.gif b/deluge/ui/web/themes/images/access/grid/group-collapse.gif
new file mode 100644
index 0000000..9bd255e
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/group-collapse.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/group-expand-sprite.gif b/deluge/ui/web/themes/images/access/grid/group-expand-sprite.gif
new file mode 100644
index 0000000..f230489
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/group-expand-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/group-expand.gif b/deluge/ui/web/themes/images/access/grid/group-expand.gif
new file mode 100644
index 0000000..fd22e6b
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/group-expand.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/hd-pop.gif b/deluge/ui/web/themes/images/access/grid/hd-pop.gif
new file mode 100644
index 0000000..eb8ba79
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/hd-pop.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/hmenu-asc.gif b/deluge/ui/web/themes/images/access/grid/hmenu-asc.gif
new file mode 100644
index 0000000..8917e0e
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/hmenu-asc.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/hmenu-desc.gif b/deluge/ui/web/themes/images/access/grid/hmenu-desc.gif
new file mode 100644
index 0000000..f26b7c2
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/hmenu-desc.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/hmenu-lock.gif b/deluge/ui/web/themes/images/access/grid/hmenu-lock.gif
new file mode 100644
index 0000000..1596126
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/hmenu-lock.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/hmenu-lock.png b/deluge/ui/web/themes/images/access/grid/hmenu-lock.png
new file mode 100644
index 0000000..5d33c09
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/hmenu-lock.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/hmenu-unlock.gif b/deluge/ui/web/themes/images/access/grid/hmenu-unlock.gif
new file mode 100644
index 0000000..af59cf9
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/hmenu-unlock.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/hmenu-unlock.png b/deluge/ui/web/themes/images/access/grid/hmenu-unlock.png
new file mode 100644
index 0000000..0371ca4
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/hmenu-unlock.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/invalid_line.gif b/deluge/ui/web/themes/images/access/grid/invalid_line.gif
new file mode 100644
index 0000000..025cffc
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/invalid_line.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/loading.gif b/deluge/ui/web/themes/images/access/grid/loading.gif
new file mode 100644
index 0000000..e846e1d
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/loading.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/mso-hd.gif b/deluge/ui/web/themes/images/access/grid/mso-hd.gif
new file mode 100644
index 0000000..669f3cf
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/mso-hd.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/nowait.gif b/deluge/ui/web/themes/images/access/grid/nowait.gif
new file mode 100644
index 0000000..4c5862c
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/nowait.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/page-first-disabled.gif b/deluge/ui/web/themes/images/access/grid/page-first-disabled.gif
new file mode 100644
index 0000000..e4df7a7
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/page-first-disabled.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/page-first.gif b/deluge/ui/web/themes/images/access/grid/page-first.gif
new file mode 100644
index 0000000..aa0a822
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/page-first.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/page-last-disabled.gif b/deluge/ui/web/themes/images/access/grid/page-last-disabled.gif
new file mode 100644
index 0000000..67fee75
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/page-last-disabled.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/page-last.gif b/deluge/ui/web/themes/images/access/grid/page-last.gif
new file mode 100644
index 0000000..e0cf111
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/page-last.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/page-next-disabled.gif b/deluge/ui/web/themes/images/access/grid/page-next-disabled.gif
new file mode 100644
index 0000000..e3e8e87
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/page-next-disabled.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/page-next.gif b/deluge/ui/web/themes/images/access/grid/page-next.gif
new file mode 100644
index 0000000..69899c0
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/page-next.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/page-prev-disabled.gif b/deluge/ui/web/themes/images/access/grid/page-prev-disabled.gif
new file mode 100644
index 0000000..0f94bf7
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/page-prev-disabled.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/page-prev.gif b/deluge/ui/web/themes/images/access/grid/page-prev.gif
new file mode 100644
index 0000000..289b126
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/page-prev.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/pick-button.gif b/deluge/ui/web/themes/images/access/grid/pick-button.gif
new file mode 100644
index 0000000..6957924
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/pick-button.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/refresh.gif b/deluge/ui/web/themes/images/access/grid/refresh.gif
new file mode 100644
index 0000000..8435d1e
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/refresh.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/row-check-sprite.gif b/deluge/ui/web/themes/images/access/grid/row-check-sprite.gif
new file mode 100644
index 0000000..6101164
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/row-check-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/row-expand-sprite.gif b/deluge/ui/web/themes/images/access/grid/row-expand-sprite.gif
new file mode 100644
index 0000000..6f4d874
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/row-expand-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/row-over.gif b/deluge/ui/web/themes/images/access/grid/row-over.gif
new file mode 100644
index 0000000..b288e38
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/row-over.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/row-sel.gif b/deluge/ui/web/themes/images/access/grid/row-sel.gif
new file mode 100644
index 0000000..98209e6
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/row-sel.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/sort-hd.gif b/deluge/ui/web/themes/images/access/grid/sort-hd.gif
new file mode 100644
index 0000000..681628f
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/sort-hd.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/sort_asc.gif b/deluge/ui/web/themes/images/access/grid/sort_asc.gif
new file mode 100644
index 0000000..371f5e4
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/sort_asc.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/sort_desc.gif b/deluge/ui/web/themes/images/access/grid/sort_desc.gif
new file mode 100644
index 0000000..000e363
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/sort_desc.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/grid/wait.gif b/deluge/ui/web/themes/images/access/grid/wait.gif
new file mode 100644
index 0000000..471c1a4
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/grid/wait.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/menu/checked.gif b/deluge/ui/web/themes/images/access/menu/checked.gif
new file mode 100644
index 0000000..fad5893
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/menu/checked.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/menu/group-checked.gif b/deluge/ui/web/themes/images/access/menu/group-checked.gif
new file mode 100644
index 0000000..d8b08f5
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/menu/group-checked.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/menu/item-over.gif b/deluge/ui/web/themes/images/access/menu/item-over.gif
new file mode 100644
index 0000000..0167839
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/menu/item-over.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/menu/menu-parent.gif b/deluge/ui/web/themes/images/access/menu/menu-parent.gif
new file mode 100644
index 0000000..49286cd
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/menu/menu-parent.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/menu/menu.gif b/deluge/ui/web/themes/images/access/menu/menu.gif
new file mode 100644
index 0000000..9bb3960
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/menu/menu.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/menu/unchecked.gif b/deluge/ui/web/themes/images/access/menu/unchecked.gif
new file mode 100644
index 0000000..43823e5
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/menu/unchecked.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/panel/corners-sprite.gif b/deluge/ui/web/themes/images/access/panel/corners-sprite.gif
new file mode 100644
index 0000000..43e2862
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/panel/corners-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/panel/left-right.gif b/deluge/ui/web/themes/images/access/panel/left-right.gif
new file mode 100644
index 0000000..51850b7
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/panel/left-right.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/panel/light-hd.gif b/deluge/ui/web/themes/images/access/panel/light-hd.gif
new file mode 100644
index 0000000..660bedb
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/panel/light-hd.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/panel/tool-sprite-tpl.gif b/deluge/ui/web/themes/images/access/panel/tool-sprite-tpl.gif
new file mode 100644
index 0000000..e647867
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/panel/tool-sprite-tpl.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/panel/tool-sprites.gif b/deluge/ui/web/themes/images/access/panel/tool-sprites.gif
new file mode 100644
index 0000000..a3ffe58
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/panel/tool-sprites.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/panel/tools-sprites-trans.gif b/deluge/ui/web/themes/images/access/panel/tools-sprites-trans.gif
new file mode 100644
index 0000000..ead931e
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/panel/tools-sprites-trans.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/panel/top-bottom.gif b/deluge/ui/web/themes/images/access/panel/top-bottom.gif
new file mode 100644
index 0000000..6b2649d
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/panel/top-bottom.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/panel/white-corners-sprite.gif b/deluge/ui/web/themes/images/access/panel/white-corners-sprite.gif
new file mode 100644
index 0000000..22d4bba
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/panel/white-corners-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/panel/white-left-right.gif b/deluge/ui/web/themes/images/access/panel/white-left-right.gif
new file mode 100644
index 0000000..51850b7
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/panel/white-left-right.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/panel/white-top-bottom.gif b/deluge/ui/web/themes/images/access/panel/white-top-bottom.gif
new file mode 100644
index 0000000..08f8fae
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/panel/white-top-bottom.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/progress/progress-bg.gif b/deluge/ui/web/themes/images/access/progress/progress-bg.gif
new file mode 100644
index 0000000..55629b1
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/progress/progress-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/qtip/close.gif b/deluge/ui/web/themes/images/access/qtip/close.gif
new file mode 100644
index 0000000..69ab915
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/qtip/close.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/qtip/tip-anchor-sprite.gif b/deluge/ui/web/themes/images/access/qtip/tip-anchor-sprite.gif
new file mode 100644
index 0000000..f46d31d
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/qtip/tip-anchor-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/qtip/tip-sprite.gif b/deluge/ui/web/themes/images/access/qtip/tip-sprite.gif
new file mode 100644
index 0000000..9f6a629
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/qtip/tip-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/shared/glass-bg.gif b/deluge/ui/web/themes/images/access/shared/glass-bg.gif
new file mode 100644
index 0000000..ed3c886
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/shared/glass-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/shared/hd-sprite.gif b/deluge/ui/web/themes/images/access/shared/hd-sprite.gif
new file mode 100644
index 0000000..446be92
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/shared/hd-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/shared/left-btn.gif b/deluge/ui/web/themes/images/access/shared/left-btn.gif
new file mode 100644
index 0000000..0622439
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/shared/left-btn.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/shared/right-btn.gif b/deluge/ui/web/themes/images/access/shared/right-btn.gif
new file mode 100644
index 0000000..5e3215d
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/shared/right-btn.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/sizer/e-handle-dark.gif b/deluge/ui/web/themes/images/access/sizer/e-handle-dark.gif
new file mode 100644
index 0000000..70aad3f
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/sizer/e-handle-dark.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/sizer/e-handle.gif b/deluge/ui/web/themes/images/access/sizer/e-handle.gif
new file mode 100644
index 0000000..52c045e
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/sizer/e-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/sizer/ne-handle-dark.gif b/deluge/ui/web/themes/images/access/sizer/ne-handle-dark.gif
new file mode 100644
index 0000000..3a30ca2
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/sizer/ne-handle-dark.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/sizer/ne-handle.gif b/deluge/ui/web/themes/images/access/sizer/ne-handle.gif
new file mode 100644
index 0000000..e48f9f9
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/sizer/ne-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/sizer/nw-handle-dark.gif b/deluge/ui/web/themes/images/access/sizer/nw-handle-dark.gif
new file mode 100644
index 0000000..5ea8b51
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/sizer/nw-handle-dark.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/sizer/nw-handle.gif b/deluge/ui/web/themes/images/access/sizer/nw-handle.gif
new file mode 100644
index 0000000..65d5cc2
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/sizer/nw-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/sizer/s-handle-dark.gif b/deluge/ui/web/themes/images/access/sizer/s-handle-dark.gif
new file mode 100644
index 0000000..421b534
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/sizer/s-handle-dark.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/sizer/s-handle.gif b/deluge/ui/web/themes/images/access/sizer/s-handle.gif
new file mode 100644
index 0000000..2b635de
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/sizer/s-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/sizer/se-handle-dark.gif b/deluge/ui/web/themes/images/access/sizer/se-handle-dark.gif
new file mode 100644
index 0000000..881a5c4
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/sizer/se-handle-dark.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/sizer/se-handle.gif b/deluge/ui/web/themes/images/access/sizer/se-handle.gif
new file mode 100644
index 0000000..5f1e3b8
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/sizer/se-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/sizer/square.gif b/deluge/ui/web/themes/images/access/sizer/square.gif
new file mode 100644
index 0000000..4dc5a2d
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/sizer/square.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/sizer/sw-handle-dark.gif b/deluge/ui/web/themes/images/access/sizer/sw-handle-dark.gif
new file mode 100644
index 0000000..030d8f8
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/sizer/sw-handle-dark.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/sizer/sw-handle.gif b/deluge/ui/web/themes/images/access/sizer/sw-handle.gif
new file mode 100644
index 0000000..79bcb84
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/sizer/sw-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/slider/slider-bg.png b/deluge/ui/web/themes/images/access/slider/slider-bg.png
new file mode 100644
index 0000000..1ba12ad
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/slider/slider-bg.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/slider/slider-thumb.png b/deluge/ui/web/themes/images/access/slider/slider-thumb.png
new file mode 100644
index 0000000..1712edf
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/slider/slider-thumb.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/slider/slider-v-bg.png b/deluge/ui/web/themes/images/access/slider/slider-v-bg.png
new file mode 100644
index 0000000..6944f9e
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/slider/slider-v-bg.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/slider/slider-v-thumb.png b/deluge/ui/web/themes/images/access/slider/slider-v-thumb.png
new file mode 100644
index 0000000..df9cfc9
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/slider/slider-v-thumb.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tabs/scroll-left.gif b/deluge/ui/web/themes/images/access/tabs/scroll-left.gif
new file mode 100644
index 0000000..71a2e88
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tabs/scroll-left.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tabs/scroll-right.gif b/deluge/ui/web/themes/images/access/tabs/scroll-right.gif
new file mode 100644
index 0000000..8f3d659
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tabs/scroll-right.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tabs/tab-btm-inactive-left-bg.gif b/deluge/ui/web/themes/images/access/tabs/tab-btm-inactive-left-bg.gif
new file mode 100644
index 0000000..687af2b
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tabs/tab-btm-inactive-left-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tabs/tab-btm-inactive-right-bg.gif b/deluge/ui/web/themes/images/access/tabs/tab-btm-inactive-right-bg.gif
new file mode 100644
index 0000000..3c1b3eb
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tabs/tab-btm-inactive-right-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tabs/tab-btm-left-bg.gif b/deluge/ui/web/themes/images/access/tabs/tab-btm-left-bg.gif
new file mode 100644
index 0000000..e5f827a
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tabs/tab-btm-left-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tabs/tab-btm-right-bg.gif b/deluge/ui/web/themes/images/access/tabs/tab-btm-right-bg.gif
new file mode 100644
index 0000000..2551f4c
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tabs/tab-btm-right-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tabs/tab-close.gif b/deluge/ui/web/themes/images/access/tabs/tab-close.gif
new file mode 100644
index 0000000..ef9a7c2
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tabs/tab-close.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tabs/tab-strip-bg.gif b/deluge/ui/web/themes/images/access/tabs/tab-strip-bg.gif
new file mode 100644
index 0000000..fc1fdcd
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tabs/tab-strip-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tabs/tab-strip-btm-bg.gif b/deluge/ui/web/themes/images/access/tabs/tab-strip-btm-bg.gif
new file mode 100644
index 0000000..a151553
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tabs/tab-strip-btm-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tabs/tabs-sprite.gif b/deluge/ui/web/themes/images/access/tabs/tabs-sprite.gif
new file mode 100644
index 0000000..8194001
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tabs/tabs-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/toolbar/bg.gif b/deluge/ui/web/themes/images/access/toolbar/bg.gif
new file mode 100644
index 0000000..b67a54e
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/toolbar/bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/toolbar/btn-arrow-light.gif b/deluge/ui/web/themes/images/access/toolbar/btn-arrow-light.gif
new file mode 100644
index 0000000..b0e24b5
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/toolbar/btn-arrow-light.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/toolbar/btn-arrow.gif b/deluge/ui/web/themes/images/access/toolbar/btn-arrow.gif
new file mode 100644
index 0000000..8acb460
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/toolbar/btn-arrow.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/toolbar/btn-over-bg.gif b/deluge/ui/web/themes/images/access/toolbar/btn-over-bg.gif
new file mode 100644
index 0000000..ee2dd98
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/toolbar/btn-over-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/toolbar/gray-bg.gif b/deluge/ui/web/themes/images/access/toolbar/gray-bg.gif
new file mode 100644
index 0000000..bd49438
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/toolbar/gray-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/toolbar/more.gif b/deluge/ui/web/themes/images/access/toolbar/more.gif
new file mode 100644
index 0000000..4f01020
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/toolbar/more.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/toolbar/s-arrow-bo.gif b/deluge/ui/web/themes/images/access/toolbar/s-arrow-bo.gif
new file mode 100644
index 0000000..1505edd
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/toolbar/s-arrow-bo.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/toolbar/tb-btn-sprite.gif b/deluge/ui/web/themes/images/access/toolbar/tb-btn-sprite.gif
new file mode 100644
index 0000000..19bbef3
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/toolbar/tb-btn-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/toolbar/tb-xl-btn-sprite.gif b/deluge/ui/web/themes/images/access/toolbar/tb-xl-btn-sprite.gif
new file mode 100644
index 0000000..1bc0420
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/toolbar/tb-xl-btn-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/toolbar/tb-xl-sep.gif b/deluge/ui/web/themes/images/access/toolbar/tb-xl-sep.gif
new file mode 100644
index 0000000..30555ee
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/toolbar/tb-xl-sep.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/arrows.gif b/deluge/ui/web/themes/images/access/tree/arrows.gif
new file mode 100644
index 0000000..2e635eb
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/arrows.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/drop-add.gif b/deluge/ui/web/themes/images/access/tree/drop-add.gif
new file mode 100644
index 0000000..b22cd14
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/drop-add.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/drop-between.gif b/deluge/ui/web/themes/images/access/tree/drop-between.gif
new file mode 100644
index 0000000..5c6c09d
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/drop-between.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/drop-no.gif b/deluge/ui/web/themes/images/access/tree/drop-no.gif
new file mode 100644
index 0000000..9d9c6a9
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/drop-no.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/drop-over.gif b/deluge/ui/web/themes/images/access/tree/drop-over.gif
new file mode 100644
index 0000000..30d1ca7
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/drop-over.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/drop-under.gif b/deluge/ui/web/themes/images/access/tree/drop-under.gif
new file mode 100644
index 0000000..85f66b1
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/drop-under.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/drop-yes.gif b/deluge/ui/web/themes/images/access/tree/drop-yes.gif
new file mode 100644
index 0000000..8aacb30
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/drop-yes.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/elbow-end-minus-nl.gif b/deluge/ui/web/themes/images/access/tree/elbow-end-minus-nl.gif
new file mode 100644
index 0000000..b4ae595
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/elbow-end-minus-nl.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/elbow-end-minus.gif b/deluge/ui/web/themes/images/access/tree/elbow-end-minus.gif
new file mode 100644
index 0000000..514cf3e
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/elbow-end-minus.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/elbow-end-plus-nl.gif b/deluge/ui/web/themes/images/access/tree/elbow-end-plus-nl.gif
new file mode 100644
index 0000000..6af2e29
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/elbow-end-plus-nl.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/elbow-end-plus.gif b/deluge/ui/web/themes/images/access/tree/elbow-end-plus.gif
new file mode 100644
index 0000000..96df679
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/elbow-end-plus.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/elbow-end.gif b/deluge/ui/web/themes/images/access/tree/elbow-end.gif
new file mode 100644
index 0000000..f24ddee
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/elbow-end.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/elbow-line.gif b/deluge/ui/web/themes/images/access/tree/elbow-line.gif
new file mode 100644
index 0000000..75e6da4
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/elbow-line.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/elbow-minus-nl.gif b/deluge/ui/web/themes/images/access/tree/elbow-minus-nl.gif
new file mode 100644
index 0000000..b4ae595
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/elbow-minus-nl.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/elbow-minus.gif b/deluge/ui/web/themes/images/access/tree/elbow-minus.gif
new file mode 100644
index 0000000..68ba298
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/elbow-minus.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/elbow-plus-nl.gif b/deluge/ui/web/themes/images/access/tree/elbow-plus-nl.gif
new file mode 100644
index 0000000..6af2e29
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/elbow-plus-nl.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/elbow-plus.gif b/deluge/ui/web/themes/images/access/tree/elbow-plus.gif
new file mode 100644
index 0000000..58ba9e4
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/elbow-plus.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/elbow.gif b/deluge/ui/web/themes/images/access/tree/elbow.gif
new file mode 100644
index 0000000..b8f4208
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/elbow.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/folder-open.gif b/deluge/ui/web/themes/images/access/tree/folder-open.gif
new file mode 100644
index 0000000..7c52965
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/folder-open.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/folder.gif b/deluge/ui/web/themes/images/access/tree/folder.gif
new file mode 100644
index 0000000..501e75c
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/folder.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/leaf.gif b/deluge/ui/web/themes/images/access/tree/leaf.gif
new file mode 100644
index 0000000..445769d
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/leaf.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/loading.gif b/deluge/ui/web/themes/images/access/tree/loading.gif
new file mode 100644
index 0000000..e846e1d
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/loading.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/tree/s.gif b/deluge/ui/web/themes/images/access/tree/s.gif
new file mode 100644
index 0000000..1d11fa9
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/tree/s.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/window/icon-error.gif b/deluge/ui/web/themes/images/access/window/icon-error.gif
new file mode 100644
index 0000000..05c713c
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/window/icon-error.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/window/icon-info.gif b/deluge/ui/web/themes/images/access/window/icon-info.gif
new file mode 100644
index 0000000..adc0613
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/window/icon-info.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/window/icon-question.gif b/deluge/ui/web/themes/images/access/window/icon-question.gif
new file mode 100644
index 0000000..9b31a94
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/window/icon-question.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/window/icon-warning.gif b/deluge/ui/web/themes/images/access/window/icon-warning.gif
new file mode 100644
index 0000000..0d89077
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/window/icon-warning.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/window/left-corners.png b/deluge/ui/web/themes/images/access/window/left-corners.png
new file mode 100644
index 0000000..f9a6463
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/window/left-corners.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/window/left-right.png b/deluge/ui/web/themes/images/access/window/left-right.png
new file mode 100644
index 0000000..7721193
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/window/left-right.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/window/right-corners.png b/deluge/ui/web/themes/images/access/window/right-corners.png
new file mode 100644
index 0000000..1111bb4
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/window/right-corners.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/access/window/top-bottom.png b/deluge/ui/web/themes/images/access/window/top-bottom.png
new file mode 100644
index 0000000..179747b
--- /dev/null
+++ b/deluge/ui/web/themes/images/access/window/top-bottom.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/box/corners-blue.gif b/deluge/ui/web/themes/images/default/box/corners-blue.gif
new file mode 100644
index 0000000..fa419b5
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/box/corners-blue.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/box/corners.gif b/deluge/ui/web/themes/images/default/box/corners.gif
new file mode 100644
index 0000000..8aa8cae
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/box/corners.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/box/l-blue.gif b/deluge/ui/web/themes/images/default/box/l-blue.gif
new file mode 100644
index 0000000..5ed7f00
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/box/l-blue.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/box/l.gif b/deluge/ui/web/themes/images/default/box/l.gif
new file mode 100644
index 0000000..0160f97
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/box/l.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/box/r-blue.gif b/deluge/ui/web/themes/images/default/box/r-blue.gif
new file mode 100644
index 0000000..3ea5cae
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/box/r-blue.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/box/r.gif b/deluge/ui/web/themes/images/default/box/r.gif
new file mode 100644
index 0000000..34237f6
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/box/r.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/box/tb-blue.gif b/deluge/ui/web/themes/images/default/box/tb-blue.gif
new file mode 100644
index 0000000..562fecc
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/box/tb-blue.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/box/tb.gif b/deluge/ui/web/themes/images/default/box/tb.gif
new file mode 100644
index 0000000..435889b
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/box/tb.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/button/arrow.gif b/deluge/ui/web/themes/images/default/button/arrow.gif
new file mode 100644
index 0000000..3ab4f71
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/button/arrow.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/button/btn.gif b/deluge/ui/web/themes/images/default/button/btn.gif
new file mode 100644
index 0000000..06b404d
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/button/btn.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/button/group-cs.gif b/deluge/ui/web/themes/images/default/button/group-cs.gif
new file mode 100644
index 0000000..3d1dca8
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/button/group-cs.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/button/group-lr.gif b/deluge/ui/web/themes/images/default/button/group-lr.gif
new file mode 100644
index 0000000..7c549f9
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/button/group-lr.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/button/group-tb.gif b/deluge/ui/web/themes/images/default/button/group-tb.gif
new file mode 100644
index 0000000..adeb0a4
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/button/group-tb.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/button/s-arrow-b-noline.gif b/deluge/ui/web/themes/images/default/button/s-arrow-b-noline.gif
new file mode 100644
index 0000000..a4220ee
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/button/s-arrow-b-noline.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/button/s-arrow-b.gif b/deluge/ui/web/themes/images/default/button/s-arrow-b.gif
new file mode 100644
index 0000000..84b6470
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/button/s-arrow-b.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/button/s-arrow-bo.gif b/deluge/ui/web/themes/images/default/button/s-arrow-bo.gif
new file mode 100644
index 0000000..548700b
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/button/s-arrow-bo.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/button/s-arrow-noline.gif b/deluge/ui/web/themes/images/default/button/s-arrow-noline.gif
new file mode 100644
index 0000000..0953eab
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/button/s-arrow-noline.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/button/s-arrow-o.gif b/deluge/ui/web/themes/images/default/button/s-arrow-o.gif
new file mode 100644
index 0000000..89c70f3
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/button/s-arrow-o.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/button/s-arrow.gif b/deluge/ui/web/themes/images/default/button/s-arrow.gif
new file mode 100644
index 0000000..8940774
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/button/s-arrow.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/dd/drop-add.gif b/deluge/ui/web/themes/images/default/dd/drop-add.gif
new file mode 100644
index 0000000..b22cd14
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/dd/drop-add.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/dd/drop-no.gif b/deluge/ui/web/themes/images/default/dd/drop-no.gif
new file mode 100644
index 0000000..08d0833
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/dd/drop-no.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/dd/drop-yes.gif b/deluge/ui/web/themes/images/default/dd/drop-yes.gif
new file mode 100644
index 0000000..8aacb30
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/dd/drop-yes.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/editor/tb-sprite.gif b/deluge/ui/web/themes/images/default/editor/tb-sprite.gif
new file mode 100644
index 0000000..fb70577
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/editor/tb-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/form/checkbox.gif b/deluge/ui/web/themes/images/default/form/checkbox.gif
new file mode 100644
index 0000000..835b346
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/form/checkbox.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/form/clear-trigger.gif b/deluge/ui/web/themes/images/default/form/clear-trigger.gif
new file mode 100644
index 0000000..da78d45
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/form/clear-trigger.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/form/clear-trigger.psd b/deluge/ui/web/themes/images/default/form/clear-trigger.psd
new file mode 100644
index 0000000..f637fa5
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/form/clear-trigger.psd
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/form/date-trigger.gif b/deluge/ui/web/themes/images/default/form/date-trigger.gif
new file mode 100644
index 0000000..25ef7b3
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/form/date-trigger.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/form/date-trigger.psd b/deluge/ui/web/themes/images/default/form/date-trigger.psd
new file mode 100644
index 0000000..74883b2
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/form/date-trigger.psd
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/form/error-tip-corners.gif b/deluge/ui/web/themes/images/default/form/error-tip-corners.gif
new file mode 100644
index 0000000..6ea4c38
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/form/error-tip-corners.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/form/exclamation.gif b/deluge/ui/web/themes/images/default/form/exclamation.gif
new file mode 100644
index 0000000..ea31a30
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/form/exclamation.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/form/radio.gif b/deluge/ui/web/themes/images/default/form/radio.gif
new file mode 100644
index 0000000..36bb91d
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/form/radio.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/form/search-trigger.gif b/deluge/ui/web/themes/images/default/form/search-trigger.gif
new file mode 100644
index 0000000..db8802b
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/form/search-trigger.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/form/search-trigger.psd b/deluge/ui/web/themes/images/default/form/search-trigger.psd
new file mode 100644
index 0000000..b11f273
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/form/search-trigger.psd
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/form/text-bg.gif b/deluge/ui/web/themes/images/default/form/text-bg.gif
new file mode 100644
index 0000000..4179607
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/form/text-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/form/trigger-square.gif b/deluge/ui/web/themes/images/default/form/trigger-square.gif
new file mode 100644
index 0000000..3004ec5
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/form/trigger-square.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/form/trigger-square.psd b/deluge/ui/web/themes/images/default/form/trigger-square.psd
new file mode 100644
index 0000000..e922ee6
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/form/trigger-square.psd
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/form/trigger-tpl.gif b/deluge/ui/web/themes/images/default/form/trigger-tpl.gif
new file mode 100644
index 0000000..e3701a3
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/form/trigger-tpl.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/form/trigger.gif b/deluge/ui/web/themes/images/default/form/trigger.gif
new file mode 100644
index 0000000..f6cba37
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/form/trigger.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/form/trigger.psd b/deluge/ui/web/themes/images/default/form/trigger.psd
new file mode 100644
index 0000000..344c768
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/form/trigger.psd
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/gradient-bg.gif b/deluge/ui/web/themes/images/default/gradient-bg.gif
new file mode 100644
index 0000000..8134e49
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/gradient-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/arrow-left-white.gif b/deluge/ui/web/themes/images/default/grid/arrow-left-white.gif
new file mode 100644
index 0000000..63088f5
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/arrow-left-white.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/arrow-right-white.gif b/deluge/ui/web/themes/images/default/grid/arrow-right-white.gif
new file mode 100644
index 0000000..e9e0678
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/arrow-right-white.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/col-move-bottom.gif b/deluge/ui/web/themes/images/default/grid/col-move-bottom.gif
new file mode 100644
index 0000000..cc1e473
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/col-move-bottom.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/col-move-top.gif b/deluge/ui/web/themes/images/default/grid/col-move-top.gif
new file mode 100644
index 0000000..58ff32c
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/col-move-top.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/columns.gif b/deluge/ui/web/themes/images/default/grid/columns.gif
new file mode 100644
index 0000000..2d3a823
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/columns.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/dirty.gif b/deluge/ui/web/themes/images/default/grid/dirty.gif
new file mode 100644
index 0000000..4f217a4
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/dirty.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/done.gif b/deluge/ui/web/themes/images/default/grid/done.gif
new file mode 100644
index 0000000..a937cb2
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/done.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/drop-no.gif b/deluge/ui/web/themes/images/default/grid/drop-no.gif
new file mode 100644
index 0000000..31a332b
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/drop-no.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/drop-yes.gif b/deluge/ui/web/themes/images/default/grid/drop-yes.gif
new file mode 100644
index 0000000..926010e
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/drop-yes.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/footer-bg.gif b/deluge/ui/web/themes/images/default/grid/footer-bg.gif
new file mode 100644
index 0000000..126120f
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/footer-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/grid-blue-hd.gif b/deluge/ui/web/themes/images/default/grid/grid-blue-hd.gif
new file mode 100644
index 0000000..862094e
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/grid-blue-hd.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/grid-blue-split.gif b/deluge/ui/web/themes/images/default/grid/grid-blue-split.gif
new file mode 100644
index 0000000..5286f58
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/grid-blue-split.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/grid-hrow.gif b/deluge/ui/web/themes/images/default/grid/grid-hrow.gif
new file mode 100644
index 0000000..6374104
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/grid-hrow.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/grid-loading.gif b/deluge/ui/web/themes/images/default/grid/grid-loading.gif
new file mode 100644
index 0000000..d112c54
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/grid-loading.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/grid-split.gif b/deluge/ui/web/themes/images/default/grid/grid-split.gif
new file mode 100644
index 0000000..c76a16e
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/grid-split.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/grid-vista-hd.gif b/deluge/ui/web/themes/images/default/grid/grid-vista-hd.gif
new file mode 100644
index 0000000..d097263
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/grid-vista-hd.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/grid3-hd-btn.gif b/deluge/ui/web/themes/images/default/grid/grid3-hd-btn.gif
new file mode 100644
index 0000000..2112607
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/grid3-hd-btn.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/grid3-hrow-over.gif b/deluge/ui/web/themes/images/default/grid/grid3-hrow-over.gif
new file mode 100644
index 0000000..f9c07af
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/grid3-hrow-over.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/grid3-hrow.gif b/deluge/ui/web/themes/images/default/grid/grid3-hrow.gif
new file mode 100644
index 0000000..8d459a3
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/grid3-hrow.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/grid3-rowheader.gif b/deluge/ui/web/themes/images/default/grid/grid3-rowheader.gif
new file mode 100644
index 0000000..2799b45
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/grid3-rowheader.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/grid3-special-col-bg.gif b/deluge/ui/web/themes/images/default/grid/grid3-special-col-bg.gif
new file mode 100644
index 0000000..0b4d6ca
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/grid3-special-col-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/grid3-special-col-sel-bg.gif b/deluge/ui/web/themes/images/default/grid/grid3-special-col-sel-bg.gif
new file mode 100644
index 0000000..1dfe9a6
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/grid3-special-col-sel-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/group-by.gif b/deluge/ui/web/themes/images/default/grid/group-by.gif
new file mode 100644
index 0000000..d6075bb
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/group-by.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/group-collapse.gif b/deluge/ui/web/themes/images/default/grid/group-collapse.gif
new file mode 100644
index 0000000..495bb05
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/group-collapse.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/group-expand-sprite.gif b/deluge/ui/web/themes/images/default/grid/group-expand-sprite.gif
new file mode 100644
index 0000000..9c1653b
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/group-expand-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/group-expand.gif b/deluge/ui/web/themes/images/default/grid/group-expand.gif
new file mode 100644
index 0000000..a33ac30
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/group-expand.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/hd-pop.gif b/deluge/ui/web/themes/images/default/grid/hd-pop.gif
new file mode 100644
index 0000000..eb8ba79
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/hd-pop.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/hmenu-asc.gif b/deluge/ui/web/themes/images/default/grid/hmenu-asc.gif
new file mode 100644
index 0000000..8917e0e
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/hmenu-asc.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/hmenu-desc.gif b/deluge/ui/web/themes/images/default/grid/hmenu-desc.gif
new file mode 100644
index 0000000..f26b7c2
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/hmenu-desc.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/hmenu-lock.gif b/deluge/ui/web/themes/images/default/grid/hmenu-lock.gif
new file mode 100644
index 0000000..1596126
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/hmenu-lock.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/hmenu-lock.png b/deluge/ui/web/themes/images/default/grid/hmenu-lock.png
new file mode 100644
index 0000000..5d33c09
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/hmenu-lock.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/hmenu-unlock.gif b/deluge/ui/web/themes/images/default/grid/hmenu-unlock.gif
new file mode 100644
index 0000000..af59cf9
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/hmenu-unlock.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/hmenu-unlock.png b/deluge/ui/web/themes/images/default/grid/hmenu-unlock.png
new file mode 100644
index 0000000..0371ca4
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/hmenu-unlock.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/invalid_line.gif b/deluge/ui/web/themes/images/default/grid/invalid_line.gif
new file mode 100644
index 0000000..fb7e0f3
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/invalid_line.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/loading.gif b/deluge/ui/web/themes/images/default/grid/loading.gif
new file mode 100644
index 0000000..e846e1d
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/loading.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/mso-hd.gif b/deluge/ui/web/themes/images/default/grid/mso-hd.gif
new file mode 100644
index 0000000..669f3cf
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/mso-hd.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/nowait.gif b/deluge/ui/web/themes/images/default/grid/nowait.gif
new file mode 100644
index 0000000..4c5862c
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/nowait.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/page-first-disabled.gif b/deluge/ui/web/themes/images/default/grid/page-first-disabled.gif
new file mode 100644
index 0000000..1e02c41
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/page-first-disabled.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/page-first.gif b/deluge/ui/web/themes/images/default/grid/page-first.gif
new file mode 100644
index 0000000..d84f41a
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/page-first.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/page-last-disabled.gif b/deluge/ui/web/themes/images/default/grid/page-last-disabled.gif
new file mode 100644
index 0000000..8697067
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/page-last-disabled.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/page-last.gif b/deluge/ui/web/themes/images/default/grid/page-last.gif
new file mode 100644
index 0000000..3df5c2b
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/page-last.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/page-next-disabled.gif b/deluge/ui/web/themes/images/default/grid/page-next-disabled.gif
new file mode 100644
index 0000000..90a7756
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/page-next-disabled.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/page-next.gif b/deluge/ui/web/themes/images/default/grid/page-next.gif
new file mode 100644
index 0000000..9601635
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/page-next.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/page-prev-disabled.gif b/deluge/ui/web/themes/images/default/grid/page-prev-disabled.gif
new file mode 100644
index 0000000..37154d6
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/page-prev-disabled.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/page-prev.gif b/deluge/ui/web/themes/images/default/grid/page-prev.gif
new file mode 100644
index 0000000..eb70cf8
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/page-prev.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/pick-button.gif b/deluge/ui/web/themes/images/default/grid/pick-button.gif
new file mode 100644
index 0000000..6957924
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/pick-button.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/refresh-disabled.gif b/deluge/ui/web/themes/images/default/grid/refresh-disabled.gif
new file mode 100644
index 0000000..607800b
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/refresh-disabled.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/refresh.gif b/deluge/ui/web/themes/images/default/grid/refresh.gif
new file mode 100644
index 0000000..110f684
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/refresh.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/row-check-sprite.gif b/deluge/ui/web/themes/images/default/grid/row-check-sprite.gif
new file mode 100644
index 0000000..6101164
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/row-check-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/row-expand-sprite.gif b/deluge/ui/web/themes/images/default/grid/row-expand-sprite.gif
new file mode 100644
index 0000000..6f4d874
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/row-expand-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/row-over.gif b/deluge/ui/web/themes/images/default/grid/row-over.gif
new file mode 100644
index 0000000..b288e38
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/row-over.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/row-sel.gif b/deluge/ui/web/themes/images/default/grid/row-sel.gif
new file mode 100644
index 0000000..98209e6
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/row-sel.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/sort-hd.gif b/deluge/ui/web/themes/images/default/grid/sort-hd.gif
new file mode 100644
index 0000000..45e545f
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/sort-hd.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/sort_asc.gif b/deluge/ui/web/themes/images/default/grid/sort_asc.gif
new file mode 100644
index 0000000..67a2a4c
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/sort_asc.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/sort_desc.gif b/deluge/ui/web/themes/images/default/grid/sort_desc.gif
new file mode 100644
index 0000000..34db47c
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/sort_desc.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/grid/wait.gif b/deluge/ui/web/themes/images/default/grid/wait.gif
new file mode 100644
index 0000000..471c1a4
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/grid/wait.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/layout/collapse.gif b/deluge/ui/web/themes/images/default/layout/collapse.gif
new file mode 100644
index 0000000..d87b0a9
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/layout/collapse.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/layout/expand.gif b/deluge/ui/web/themes/images/default/layout/expand.gif
new file mode 100644
index 0000000..7b6e1c1
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/layout/expand.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/layout/gradient-bg.gif b/deluge/ui/web/themes/images/default/layout/gradient-bg.gif
new file mode 100644
index 0000000..8134e49
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/layout/gradient-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/layout/mini-bottom.gif b/deluge/ui/web/themes/images/default/layout/mini-bottom.gif
new file mode 100644
index 0000000..c18f9e3
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/layout/mini-bottom.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/layout/mini-left.gif b/deluge/ui/web/themes/images/default/layout/mini-left.gif
new file mode 100644
index 0000000..99f7993
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/layout/mini-left.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/layout/mini-right.gif b/deluge/ui/web/themes/images/default/layout/mini-right.gif
new file mode 100644
index 0000000..5b13c5a
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/layout/mini-right.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/layout/mini-top.gif b/deluge/ui/web/themes/images/default/layout/mini-top.gif
new file mode 100644
index 0000000..a4ca2bb
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/layout/mini-top.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/layout/ns-collapse.gif b/deluge/ui/web/themes/images/default/layout/ns-collapse.gif
new file mode 100644
index 0000000..df2a77e
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/layout/ns-collapse.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/layout/ns-expand.gif b/deluge/ui/web/themes/images/default/layout/ns-expand.gif
new file mode 100644
index 0000000..77ab9da
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/layout/ns-expand.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/layout/panel-close.gif b/deluge/ui/web/themes/images/default/layout/panel-close.gif
new file mode 100644
index 0000000..2bdd623
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/layout/panel-close.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/layout/panel-title-bg.gif b/deluge/ui/web/themes/images/default/layout/panel-title-bg.gif
new file mode 100644
index 0000000..d1daef5
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/layout/panel-title-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/layout/panel-title-light-bg.gif b/deluge/ui/web/themes/images/default/layout/panel-title-light-bg.gif
new file mode 100644
index 0000000..8c2c83d
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/layout/panel-title-light-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/layout/stick.gif b/deluge/ui/web/themes/images/default/layout/stick.gif
new file mode 100644
index 0000000..5a1e8ba
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/layout/stick.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/layout/stuck.gif b/deluge/ui/web/themes/images/default/layout/stuck.gif
new file mode 100644
index 0000000..0a8de4d
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/layout/stuck.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/layout/tab-close-on.gif b/deluge/ui/web/themes/images/default/layout/tab-close-on.gif
new file mode 100644
index 0000000..eacea39
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/layout/tab-close-on.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/layout/tab-close.gif b/deluge/ui/web/themes/images/default/layout/tab-close.gif
new file mode 100644
index 0000000..45db61e
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/layout/tab-close.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/menu/checked.gif b/deluge/ui/web/themes/images/default/menu/checked.gif
new file mode 100644
index 0000000..fad5893
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/menu/checked.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/menu/group-checked.gif b/deluge/ui/web/themes/images/default/menu/group-checked.gif
new file mode 100644
index 0000000..d30b3e5
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/menu/group-checked.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/menu/item-over.gif b/deluge/ui/web/themes/images/default/menu/item-over.gif
new file mode 100644
index 0000000..0167839
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/menu/item-over.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/menu/menu-parent.gif b/deluge/ui/web/themes/images/default/menu/menu-parent.gif
new file mode 100644
index 0000000..1e37562
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/menu/menu-parent.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/menu/menu.gif b/deluge/ui/web/themes/images/default/menu/menu.gif
new file mode 100644
index 0000000..30a2c4b
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/menu/menu.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/menu/unchecked.gif b/deluge/ui/web/themes/images/default/menu/unchecked.gif
new file mode 100644
index 0000000..43823e5
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/menu/unchecked.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/panel/corners-sprite.gif b/deluge/ui/web/themes/images/default/panel/corners-sprite.gif
new file mode 100644
index 0000000..aa0d0ed
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/panel/corners-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/panel/left-right.gif b/deluge/ui/web/themes/images/default/panel/left-right.gif
new file mode 100644
index 0000000..9fae2d5
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/panel/left-right.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/panel/light-hd.gif b/deluge/ui/web/themes/images/default/panel/light-hd.gif
new file mode 100644
index 0000000..58d6747
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/panel/light-hd.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/panel/tool-sprite-tpl.gif b/deluge/ui/web/themes/images/default/panel/tool-sprite-tpl.gif
new file mode 100644
index 0000000..e647867
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/panel/tool-sprite-tpl.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/panel/tool-sprites.gif b/deluge/ui/web/themes/images/default/panel/tool-sprites.gif
new file mode 100644
index 0000000..2b6b809
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/panel/tool-sprites.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/panel/tools-sprites-trans.gif b/deluge/ui/web/themes/images/default/panel/tools-sprites-trans.gif
new file mode 100644
index 0000000..ead931e
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/panel/tools-sprites-trans.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/panel/top-bottom.gif b/deluge/ui/web/themes/images/default/panel/top-bottom.gif
new file mode 100644
index 0000000..be6c50e
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/panel/top-bottom.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/panel/top-bottom.png b/deluge/ui/web/themes/images/default/panel/top-bottom.png
new file mode 100644
index 0000000..6df5f68
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/panel/top-bottom.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/panel/white-corners-sprite.gif b/deluge/ui/web/themes/images/default/panel/white-corners-sprite.gif
new file mode 100644
index 0000000..22d4bba
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/panel/white-corners-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/panel/white-left-right.gif b/deluge/ui/web/themes/images/default/panel/white-left-right.gif
new file mode 100644
index 0000000..d82c337
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/panel/white-left-right.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/panel/white-top-bottom.gif b/deluge/ui/web/themes/images/default/panel/white-top-bottom.gif
new file mode 100644
index 0000000..fe7dd1c
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/panel/white-top-bottom.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/progress/progress-bg.gif b/deluge/ui/web/themes/images/default/progress/progress-bg.gif
new file mode 100644
index 0000000..1c1abeb
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/progress/progress-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/qtip/bg.gif b/deluge/ui/web/themes/images/default/qtip/bg.gif
new file mode 100644
index 0000000..43488af
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/qtip/bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/qtip/close.gif b/deluge/ui/web/themes/images/default/qtip/close.gif
new file mode 100644
index 0000000..69ab915
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/qtip/close.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/qtip/tip-anchor-sprite.gif b/deluge/ui/web/themes/images/default/qtip/tip-anchor-sprite.gif
new file mode 100644
index 0000000..9cf4850
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/qtip/tip-anchor-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/qtip/tip-sprite.gif b/deluge/ui/web/themes/images/default/qtip/tip-sprite.gif
new file mode 100644
index 0000000..9810aca
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/qtip/tip-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/s.gif b/deluge/ui/web/themes/images/default/s.gif
new file mode 100644
index 0000000..1d11fa9
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/s.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/shadow-c.png b/deluge/ui/web/themes/images/default/shadow-c.png
new file mode 100644
index 0000000..35891d3
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/shadow-c.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/shadow-lr.png b/deluge/ui/web/themes/images/default/shadow-lr.png
new file mode 100644
index 0000000..e59a9d7
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/shadow-lr.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/shadow.png b/deluge/ui/web/themes/images/default/shadow.png
new file mode 100644
index 0000000..87dcc6e
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/shadow.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/shared/blue-loading.gif b/deluge/ui/web/themes/images/default/shared/blue-loading.gif
new file mode 100644
index 0000000..3bbf639
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/shared/blue-loading.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/shared/calendar.gif b/deluge/ui/web/themes/images/default/shared/calendar.gif
new file mode 100644
index 0000000..133cf23
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/shared/calendar.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/shared/glass-bg.gif b/deluge/ui/web/themes/images/default/shared/glass-bg.gif
new file mode 100644
index 0000000..26fbbae
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/shared/glass-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/shared/hd-sprite.gif b/deluge/ui/web/themes/images/default/shared/hd-sprite.gif
new file mode 100644
index 0000000..42da1ea
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/shared/hd-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/shared/large-loading.gif b/deluge/ui/web/themes/images/default/shared/large-loading.gif
new file mode 100644
index 0000000..b36b555
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/shared/large-loading.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/shared/left-btn.gif b/deluge/ui/web/themes/images/default/shared/left-btn.gif
new file mode 100644
index 0000000..a0ddd9e
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/shared/left-btn.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/shared/loading-balls.gif b/deluge/ui/web/themes/images/default/shared/loading-balls.gif
new file mode 100644
index 0000000..9ce214b
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/shared/loading-balls.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/shared/right-btn.gif b/deluge/ui/web/themes/images/default/shared/right-btn.gif
new file mode 100644
index 0000000..dee63e2
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/shared/right-btn.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/shared/warning.gif b/deluge/ui/web/themes/images/default/shared/warning.gif
new file mode 100644
index 0000000..806d4bc
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/shared/warning.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/sizer/e-handle-dark.gif b/deluge/ui/web/themes/images/default/sizer/e-handle-dark.gif
new file mode 100644
index 0000000..b5486c1
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/sizer/e-handle-dark.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/sizer/e-handle.gif b/deluge/ui/web/themes/images/default/sizer/e-handle.gif
new file mode 100644
index 0000000..00ba835
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/sizer/e-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/sizer/ne-handle-dark.gif b/deluge/ui/web/themes/images/default/sizer/ne-handle-dark.gif
new file mode 100644
index 0000000..04e5ecf
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/sizer/ne-handle-dark.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/sizer/ne-handle.gif b/deluge/ui/web/themes/images/default/sizer/ne-handle.gif
new file mode 100644
index 0000000..09405c7
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/sizer/ne-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/sizer/nw-handle-dark.gif b/deluge/ui/web/themes/images/default/sizer/nw-handle-dark.gif
new file mode 100644
index 0000000..6e49d69
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/sizer/nw-handle-dark.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/sizer/nw-handle.gif b/deluge/ui/web/themes/images/default/sizer/nw-handle.gif
new file mode 100644
index 0000000..2fcea8a
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/sizer/nw-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/sizer/s-handle-dark.gif b/deluge/ui/web/themes/images/default/sizer/s-handle-dark.gif
new file mode 100644
index 0000000..4eb5f0f
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/sizer/s-handle-dark.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/sizer/s-handle.gif b/deluge/ui/web/themes/images/default/sizer/s-handle.gif
new file mode 100644
index 0000000..bf069c2
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/sizer/s-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/sizer/se-handle-dark.gif b/deluge/ui/web/themes/images/default/sizer/se-handle-dark.gif
new file mode 100644
index 0000000..c4c1087
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/sizer/se-handle-dark.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/sizer/se-handle.gif b/deluge/ui/web/themes/images/default/sizer/se-handle.gif
new file mode 100644
index 0000000..972055e
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/sizer/se-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/sizer/square.gif b/deluge/ui/web/themes/images/default/sizer/square.gif
new file mode 100644
index 0000000..14ce6f7
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/sizer/square.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/sizer/sw-handle-dark.gif b/deluge/ui/web/themes/images/default/sizer/sw-handle-dark.gif
new file mode 100644
index 0000000..77224b0
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/sizer/sw-handle-dark.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/sizer/sw-handle.gif b/deluge/ui/web/themes/images/default/sizer/sw-handle.gif
new file mode 100644
index 0000000..3ca0ed9
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/sizer/sw-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/slider/slider-bg.png b/deluge/ui/web/themes/images/default/slider/slider-bg.png
new file mode 100644
index 0000000..6825d73
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/slider/slider-bg.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/slider/slider-thumb.png b/deluge/ui/web/themes/images/default/slider/slider-thumb.png
new file mode 100644
index 0000000..d16ed52
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/slider/slider-thumb.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/slider/slider-v-bg.png b/deluge/ui/web/themes/images/default/slider/slider-v-bg.png
new file mode 100644
index 0000000..1ebe8ef
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/slider/slider-v-bg.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/slider/slider-v-thumb.png b/deluge/ui/web/themes/images/default/slider/slider-v-thumb.png
new file mode 100644
index 0000000..cc86cb7
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/slider/slider-v-thumb.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tabs/scroll-left.gif b/deluge/ui/web/themes/images/default/tabs/scroll-left.gif
new file mode 100644
index 0000000..9f2f6d1
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tabs/scroll-left.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tabs/scroll-right.gif b/deluge/ui/web/themes/images/default/tabs/scroll-right.gif
new file mode 100644
index 0000000..4c5e7e3
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tabs/scroll-right.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tabs/scroller-bg.gif b/deluge/ui/web/themes/images/default/tabs/scroller-bg.gif
new file mode 100644
index 0000000..099b90d
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tabs/scroller-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tabs/tab-btm-inactive-left-bg.gif b/deluge/ui/web/themes/images/default/tabs/tab-btm-inactive-left-bg.gif
new file mode 100644
index 0000000..188bf94
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tabs/tab-btm-inactive-left-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tabs/tab-btm-inactive-right-bg.gif b/deluge/ui/web/themes/images/default/tabs/tab-btm-inactive-right-bg.gif
new file mode 100644
index 0000000..e1f5e3c
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tabs/tab-btm-inactive-right-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tabs/tab-btm-left-bg.gif b/deluge/ui/web/themes/images/default/tabs/tab-btm-left-bg.gif
new file mode 100644
index 0000000..dde7968
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tabs/tab-btm-left-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tabs/tab-btm-over-left-bg.gif b/deluge/ui/web/themes/images/default/tabs/tab-btm-over-left-bg.gif
new file mode 100644
index 0000000..da49c10
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tabs/tab-btm-over-left-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tabs/tab-btm-over-right-bg.gif b/deluge/ui/web/themes/images/default/tabs/tab-btm-over-right-bg.gif
new file mode 100644
index 0000000..45346ab
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tabs/tab-btm-over-right-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tabs/tab-btm-right-bg.gif b/deluge/ui/web/themes/images/default/tabs/tab-btm-right-bg.gif
new file mode 100644
index 0000000..e695186
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tabs/tab-btm-right-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tabs/tab-close.gif b/deluge/ui/web/themes/images/default/tabs/tab-close.gif
new file mode 100644
index 0000000..e699878
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tabs/tab-close.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tabs/tab-strip-bg.gif b/deluge/ui/web/themes/images/default/tabs/tab-strip-bg.gif
new file mode 100644
index 0000000..34f1333
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tabs/tab-strip-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tabs/tab-strip-bg.png b/deluge/ui/web/themes/images/default/tabs/tab-strip-bg.png
new file mode 100644
index 0000000..fed48eb
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tabs/tab-strip-bg.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tabs/tab-strip-btm-bg.gif b/deluge/ui/web/themes/images/default/tabs/tab-strip-btm-bg.gif
new file mode 100644
index 0000000..5eaba1e
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tabs/tab-strip-btm-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tabs/tabs-sprite.gif b/deluge/ui/web/themes/images/default/tabs/tabs-sprite.gif
new file mode 100644
index 0000000..e969fb0
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tabs/tabs-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/toolbar/bg.gif b/deluge/ui/web/themes/images/default/toolbar/bg.gif
new file mode 100644
index 0000000..0b085bf
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/toolbar/bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/toolbar/btn-arrow-light.gif b/deluge/ui/web/themes/images/default/toolbar/btn-arrow-light.gif
new file mode 100644
index 0000000..b0e24b5
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/toolbar/btn-arrow-light.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/toolbar/btn-arrow.gif b/deluge/ui/web/themes/images/default/toolbar/btn-arrow.gif
new file mode 100644
index 0000000..8acb460
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/toolbar/btn-arrow.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/toolbar/btn-over-bg.gif b/deluge/ui/web/themes/images/default/toolbar/btn-over-bg.gif
new file mode 100644
index 0000000..ee2dd98
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/toolbar/btn-over-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/toolbar/gray-bg.gif b/deluge/ui/web/themes/images/default/toolbar/gray-bg.gif
new file mode 100644
index 0000000..bd49438
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/toolbar/gray-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/toolbar/more.gif b/deluge/ui/web/themes/images/default/toolbar/more.gif
new file mode 100644
index 0000000..02c2509
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/toolbar/more.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/toolbar/tb-bg.gif b/deluge/ui/web/themes/images/default/toolbar/tb-bg.gif
new file mode 100644
index 0000000..4969e4e
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/toolbar/tb-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/toolbar/tb-btn-sprite.gif b/deluge/ui/web/themes/images/default/toolbar/tb-btn-sprite.gif
new file mode 100644
index 0000000..19bbef3
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/toolbar/tb-btn-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/toolbar/tb-xl-btn-sprite.gif b/deluge/ui/web/themes/images/default/toolbar/tb-xl-btn-sprite.gif
new file mode 100644
index 0000000..1bc0420
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/toolbar/tb-xl-btn-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/toolbar/tb-xl-sep.gif b/deluge/ui/web/themes/images/default/toolbar/tb-xl-sep.gif
new file mode 100644
index 0000000..30555ee
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/toolbar/tb-xl-sep.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/arrows.gif b/deluge/ui/web/themes/images/default/tree/arrows.gif
new file mode 100644
index 0000000..2683463
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/arrows.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/drop-add.gif b/deluge/ui/web/themes/images/default/tree/drop-add.gif
new file mode 100644
index 0000000..b22cd14
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/drop-add.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/drop-between.gif b/deluge/ui/web/themes/images/default/tree/drop-between.gif
new file mode 100644
index 0000000..5c6c09d
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/drop-between.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/drop-no.gif b/deluge/ui/web/themes/images/default/tree/drop-no.gif
new file mode 100644
index 0000000..9d9c6a9
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/drop-no.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/drop-over.gif b/deluge/ui/web/themes/images/default/tree/drop-over.gif
new file mode 100644
index 0000000..30d1ca7
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/drop-over.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/drop-under.gif b/deluge/ui/web/themes/images/default/tree/drop-under.gif
new file mode 100644
index 0000000..85f66b1
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/drop-under.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/drop-yes.gif b/deluge/ui/web/themes/images/default/tree/drop-yes.gif
new file mode 100644
index 0000000..8aacb30
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/drop-yes.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/elbow-end-minus-nl.gif b/deluge/ui/web/themes/images/default/tree/elbow-end-minus-nl.gif
new file mode 100644
index 0000000..928779e
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/elbow-end-minus-nl.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/elbow-end-minus.gif b/deluge/ui/web/themes/images/default/tree/elbow-end-minus.gif
new file mode 100644
index 0000000..9a8d727
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/elbow-end-minus.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/elbow-end-plus-nl.gif b/deluge/ui/web/themes/images/default/tree/elbow-end-plus-nl.gif
new file mode 100644
index 0000000..9f7f698
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/elbow-end-plus-nl.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/elbow-end-plus.gif b/deluge/ui/web/themes/images/default/tree/elbow-end-plus.gif
new file mode 100644
index 0000000..5943a01
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/elbow-end-plus.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/elbow-end.gif b/deluge/ui/web/themes/images/default/tree/elbow-end.gif
new file mode 100644
index 0000000..f24ddee
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/elbow-end.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/elbow-line.gif b/deluge/ui/web/themes/images/default/tree/elbow-line.gif
new file mode 100644
index 0000000..75e6da4
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/elbow-line.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/elbow-minus-nl.gif b/deluge/ui/web/themes/images/default/tree/elbow-minus-nl.gif
new file mode 100644
index 0000000..928779e
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/elbow-minus-nl.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/elbow-minus.gif b/deluge/ui/web/themes/images/default/tree/elbow-minus.gif
new file mode 100644
index 0000000..97dcc71
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/elbow-minus.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/elbow-plus-nl.gif b/deluge/ui/web/themes/images/default/tree/elbow-plus-nl.gif
new file mode 100644
index 0000000..9f7f698
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/elbow-plus-nl.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/elbow-plus.gif b/deluge/ui/web/themes/images/default/tree/elbow-plus.gif
new file mode 100644
index 0000000..698de47
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/elbow-plus.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/elbow.gif b/deluge/ui/web/themes/images/default/tree/elbow.gif
new file mode 100644
index 0000000..b8f4208
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/elbow.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/folder-open.gif b/deluge/ui/web/themes/images/default/tree/folder-open.gif
new file mode 100644
index 0000000..56ba737
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/folder-open.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/folder.gif b/deluge/ui/web/themes/images/default/tree/folder.gif
new file mode 100644
index 0000000..20412f7
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/folder.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/leaf.gif b/deluge/ui/web/themes/images/default/tree/leaf.gif
new file mode 100644
index 0000000..445769d
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/leaf.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/loading.gif b/deluge/ui/web/themes/images/default/tree/loading.gif
new file mode 100644
index 0000000..e846e1d
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/loading.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/tree/s.gif b/deluge/ui/web/themes/images/default/tree/s.gif
new file mode 100644
index 0000000..1d11fa9
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/tree/s.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/window/icon-error.gif b/deluge/ui/web/themes/images/default/window/icon-error.gif
new file mode 100644
index 0000000..397b655
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/window/icon-error.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/window/icon-info.gif b/deluge/ui/web/themes/images/default/window/icon-info.gif
new file mode 100644
index 0000000..58281c3
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/window/icon-info.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/window/icon-question.gif b/deluge/ui/web/themes/images/default/window/icon-question.gif
new file mode 100644
index 0000000..08abd82
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/window/icon-question.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/window/icon-warning.gif b/deluge/ui/web/themes/images/default/window/icon-warning.gif
new file mode 100644
index 0000000..27ff98b
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/window/icon-warning.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/window/left-corners.png b/deluge/ui/web/themes/images/default/window/left-corners.png
new file mode 100644
index 0000000..a2250fd
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/window/left-corners.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/window/left-corners.psd b/deluge/ui/web/themes/images/default/window/left-corners.psd
new file mode 100644
index 0000000..3d7f062
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/window/left-corners.psd
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/window/left-right.png b/deluge/ui/web/themes/images/default/window/left-right.png
new file mode 100644
index 0000000..acd72c2
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/window/left-right.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/window/left-right.psd b/deluge/ui/web/themes/images/default/window/left-right.psd
new file mode 100644
index 0000000..59a3960
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/window/left-right.psd
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/window/right-corners.png b/deluge/ui/web/themes/images/default/window/right-corners.png
new file mode 100644
index 0000000..7c40007
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/window/right-corners.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/window/right-corners.psd b/deluge/ui/web/themes/images/default/window/right-corners.psd
new file mode 100644
index 0000000..86d5095
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/window/right-corners.psd
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/window/top-bottom.png b/deluge/ui/web/themes/images/default/window/top-bottom.png
new file mode 100644
index 0000000..3b5f86b
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/window/top-bottom.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/default/window/top-bottom.psd b/deluge/ui/web/themes/images/default/window/top-bottom.psd
new file mode 100644
index 0000000..48c5395
--- /dev/null
+++ b/deluge/ui/web/themes/images/default/window/top-bottom.psd
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/button/btn-arrow.gif b/deluge/ui/web/themes/images/gray/button/btn-arrow.gif
new file mode 100644
index 0000000..f90d5df
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/button/btn-arrow.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/button/btn-sprite.gif b/deluge/ui/web/themes/images/gray/button/btn-sprite.gif
new file mode 100644
index 0000000..834ff97
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/button/btn-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/button/btn.gif b/deluge/ui/web/themes/images/gray/button/btn.gif
new file mode 100644
index 0000000..96ea61a
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/button/btn.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/button/group-cs.gif b/deluge/ui/web/themes/images/gray/button/group-cs.gif
new file mode 100644
index 0000000..7059e2b
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/button/group-cs.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/button/group-lr.gif b/deluge/ui/web/themes/images/gray/button/group-lr.gif
new file mode 100644
index 0000000..3f41fbd
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/button/group-lr.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/button/group-tb.gif b/deluge/ui/web/themes/images/gray/button/group-tb.gif
new file mode 100644
index 0000000..c5ea8ca
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/button/group-tb.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/button/s-arrow-bo.gif b/deluge/ui/web/themes/images/gray/button/s-arrow-bo.gif
new file mode 100644
index 0000000..fa5b2f4
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/button/s-arrow-bo.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/button/s-arrow-o.gif b/deluge/ui/web/themes/images/gray/button/s-arrow-o.gif
new file mode 100644
index 0000000..52a5141
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/button/s-arrow-o.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/form/clear-trigger.gif b/deluge/ui/web/themes/images/gray/form/clear-trigger.gif
new file mode 100644
index 0000000..be3ff58
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/form/clear-trigger.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/form/date-trigger.gif b/deluge/ui/web/themes/images/gray/form/date-trigger.gif
new file mode 100644
index 0000000..e0537cb
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/form/date-trigger.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/form/search-trigger.gif b/deluge/ui/web/themes/images/gray/form/search-trigger.gif
new file mode 100644
index 0000000..0cc4f59
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/form/search-trigger.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/form/trigger-square.gif b/deluge/ui/web/themes/images/gray/form/trigger-square.gif
new file mode 100644
index 0000000..7a0f585
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/form/trigger-square.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/form/trigger.gif b/deluge/ui/web/themes/images/gray/form/trigger.gif
new file mode 100644
index 0000000..b563474
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/form/trigger.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/gradient-bg.gif b/deluge/ui/web/themes/images/gray/gradient-bg.gif
new file mode 100644
index 0000000..8134e49
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/gradient-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/col-move-bottom.gif b/deluge/ui/web/themes/images/gray/grid/col-move-bottom.gif
new file mode 100644
index 0000000..c525f7e
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/col-move-bottom.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/col-move-top.gif b/deluge/ui/web/themes/images/gray/grid/col-move-top.gif
new file mode 100644
index 0000000..ccc92b6
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/col-move-top.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/grid3-hd-btn.gif b/deluge/ui/web/themes/images/gray/grid/grid3-hd-btn.gif
new file mode 100644
index 0000000..daf1ef2
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/grid3-hd-btn.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/grid3-hrow-over.gif b/deluge/ui/web/themes/images/gray/grid/grid3-hrow-over.gif
new file mode 100644
index 0000000..d37252f
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/grid3-hrow-over.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/grid3-hrow-over2.gif b/deluge/ui/web/themes/images/gray/grid/grid3-hrow-over2.gif
new file mode 100644
index 0000000..353d906
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/grid3-hrow-over2.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/grid3-hrow.gif b/deluge/ui/web/themes/images/gray/grid/grid3-hrow.gif
new file mode 100644
index 0000000..8d459a3
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/grid3-hrow.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/grid3-hrow2.gif b/deluge/ui/web/themes/images/gray/grid/grid3-hrow2.gif
new file mode 100644
index 0000000..423b507
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/grid3-hrow2.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/grid3-special-col-bg.gif b/deluge/ui/web/themes/images/gray/grid/grid3-special-col-bg.gif
new file mode 100644
index 0000000..12d64d7
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/grid3-special-col-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/grid3-special-col-bg2.gif b/deluge/ui/web/themes/images/gray/grid/grid3-special-col-bg2.gif
new file mode 100644
index 0000000..f10e6ad
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/grid3-special-col-bg2.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/grid3-special-col-sel-bg.gif b/deluge/ui/web/themes/images/gray/grid/grid3-special-col-sel-bg.gif
new file mode 100644
index 0000000..4fa6e10
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/grid3-special-col-sel-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/group-collapse.gif b/deluge/ui/web/themes/images/gray/grid/group-collapse.gif
new file mode 100644
index 0000000..c9ad30d
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/group-collapse.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/group-expand-sprite.gif b/deluge/ui/web/themes/images/gray/grid/group-expand-sprite.gif
new file mode 100644
index 0000000..d24891d
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/group-expand-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/group-expand.gif b/deluge/ui/web/themes/images/gray/grid/group-expand.gif
new file mode 100644
index 0000000..663b5c8
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/group-expand.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/page-first.gif b/deluge/ui/web/themes/images/gray/grid/page-first.gif
new file mode 100644
index 0000000..60be4bc
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/page-first.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/page-last.gif b/deluge/ui/web/themes/images/gray/grid/page-last.gif
new file mode 100644
index 0000000..beb4a83
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/page-last.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/page-next.gif b/deluge/ui/web/themes/images/gray/grid/page-next.gif
new file mode 100644
index 0000000..97db1c2
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/page-next.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/page-prev.gif b/deluge/ui/web/themes/images/gray/grid/page-prev.gif
new file mode 100644
index 0000000..d07e61c
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/page-prev.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/refresh.gif b/deluge/ui/web/themes/images/gray/grid/refresh.gif
new file mode 100644
index 0000000..868b2dc
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/refresh.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/row-expand-sprite.gif b/deluge/ui/web/themes/images/gray/grid/row-expand-sprite.gif
new file mode 100644
index 0000000..09c00a6
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/row-expand-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/sort-hd.gif b/deluge/ui/web/themes/images/gray/grid/sort-hd.gif
new file mode 100644
index 0000000..4cf483d
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/sort-hd.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/sort_asc.gif b/deluge/ui/web/themes/images/gray/grid/sort_asc.gif
new file mode 100644
index 0000000..7e562e2
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/sort_asc.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/grid/sort_desc.gif b/deluge/ui/web/themes/images/gray/grid/sort_desc.gif
new file mode 100644
index 0000000..9b7a871
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/grid/sort_desc.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/menu/group-checked.gif b/deluge/ui/web/themes/images/gray/menu/group-checked.gif
new file mode 100644
index 0000000..c882488
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/menu/group-checked.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/menu/item-over-disabled.gif b/deluge/ui/web/themes/images/gray/menu/item-over-disabled.gif
new file mode 100644
index 0000000..97d5ffa
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/menu/item-over-disabled.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/menu/item-over.gif b/deluge/ui/web/themes/images/gray/menu/item-over.gif
new file mode 100644
index 0000000..e0dc5f7
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/menu/item-over.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/menu/menu-parent.gif b/deluge/ui/web/themes/images/gray/menu/menu-parent.gif
new file mode 100644
index 0000000..5461a8b
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/menu/menu-parent.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/panel/corners-sprite.gif b/deluge/ui/web/themes/images/gray/panel/corners-sprite.gif
new file mode 100644
index 0000000..fad0e6d
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/panel/corners-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/panel/left-right.gif b/deluge/ui/web/themes/images/gray/panel/left-right.gif
new file mode 100644
index 0000000..c5f3dca
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/panel/left-right.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/panel/light-hd.gif b/deluge/ui/web/themes/images/gray/panel/light-hd.gif
new file mode 100644
index 0000000..6eb28ba
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/panel/light-hd.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/panel/tool-sprite-tpl.gif b/deluge/ui/web/themes/images/gray/panel/tool-sprite-tpl.gif
new file mode 100644
index 0000000..18277a3
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/panel/tool-sprite-tpl.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/panel/tool-sprites.gif b/deluge/ui/web/themes/images/gray/panel/tool-sprites.gif
new file mode 100644
index 0000000..36b6b67
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/panel/tool-sprites.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/panel/tools-sprites-trans.gif b/deluge/ui/web/themes/images/gray/panel/tools-sprites-trans.gif
new file mode 100644
index 0000000..b6d7ba3
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/panel/tools-sprites-trans.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/panel/top-bottom.gif b/deluge/ui/web/themes/images/gray/panel/top-bottom.gif
new file mode 100644
index 0000000..24ceb30
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/panel/top-bottom.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/panel/top-bottom.png b/deluge/ui/web/themes/images/gray/panel/top-bottom.png
new file mode 100644
index 0000000..6df5f68
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/panel/top-bottom.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/panel/white-corners-sprite.gif b/deluge/ui/web/themes/images/gray/panel/white-corners-sprite.gif
new file mode 100644
index 0000000..d5b8adf
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/panel/white-corners-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/panel/white-left-right.gif b/deluge/ui/web/themes/images/gray/panel/white-left-right.gif
new file mode 100644
index 0000000..2c9e142
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/panel/white-left-right.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/panel/white-top-bottom.gif b/deluge/ui/web/themes/images/gray/panel/white-top-bottom.gif
new file mode 100644
index 0000000..025fbd5
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/panel/white-top-bottom.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/progress/progress-bg.gif b/deluge/ui/web/themes/images/gray/progress/progress-bg.gif
new file mode 100644
index 0000000..5585d80
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/progress/progress-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/qtip/bg.gif b/deluge/ui/web/themes/images/gray/qtip/bg.gif
new file mode 100644
index 0000000..a9055a5
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/qtip/bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/qtip/close.gif b/deluge/ui/web/themes/images/gray/qtip/close.gif
new file mode 100644
index 0000000..69ab915
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/qtip/close.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/qtip/tip-anchor-sprite.gif b/deluge/ui/web/themes/images/gray/qtip/tip-anchor-sprite.gif
new file mode 100644
index 0000000..0671586
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/qtip/tip-anchor-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/qtip/tip-sprite.gif b/deluge/ui/web/themes/images/gray/qtip/tip-sprite.gif
new file mode 100644
index 0000000..4ade664
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/qtip/tip-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/s.gif b/deluge/ui/web/themes/images/gray/s.gif
new file mode 100644
index 0000000..1d11fa9
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/s.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/shared/hd-sprite.gif b/deluge/ui/web/themes/images/gray/shared/hd-sprite.gif
new file mode 100644
index 0000000..d943833
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/shared/hd-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/shared/left-btn.gif b/deluge/ui/web/themes/images/gray/shared/left-btn.gif
new file mode 100644
index 0000000..3301054
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/shared/left-btn.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/shared/right-btn.gif b/deluge/ui/web/themes/images/gray/shared/right-btn.gif
new file mode 100644
index 0000000..c529110
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/shared/right-btn.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/sizer/e-handle.gif b/deluge/ui/web/themes/images/gray/sizer/e-handle.gif
new file mode 100644
index 0000000..a8ed0ed
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/sizer/e-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/sizer/ne-handle.gif b/deluge/ui/web/themes/images/gray/sizer/ne-handle.gif
new file mode 100644
index 0000000..6f7b0c2
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/sizer/ne-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/sizer/nw-handle.gif b/deluge/ui/web/themes/images/gray/sizer/nw-handle.gif
new file mode 100644
index 0000000..92ad82c
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/sizer/nw-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/sizer/s-handle.gif b/deluge/ui/web/themes/images/gray/sizer/s-handle.gif
new file mode 100644
index 0000000..d7eeae2
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/sizer/s-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/sizer/se-handle.gif b/deluge/ui/web/themes/images/gray/sizer/se-handle.gif
new file mode 100644
index 0000000..f011a3b
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/sizer/se-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/sizer/square.gif b/deluge/ui/web/themes/images/gray/sizer/square.gif
new file mode 100644
index 0000000..7751d5e
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/sizer/square.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/sizer/sw-handle.gif b/deluge/ui/web/themes/images/gray/sizer/sw-handle.gif
new file mode 100644
index 0000000..aa903dd
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/sizer/sw-handle.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/slider/slider-thumb.png b/deluge/ui/web/themes/images/gray/slider/slider-thumb.png
new file mode 100644
index 0000000..6ec1667
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/slider/slider-thumb.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/slider/slider-v-thumb.png b/deluge/ui/web/themes/images/gray/slider/slider-v-thumb.png
new file mode 100644
index 0000000..1945672
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/slider/slider-v-thumb.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tabs/scroll-left.gif b/deluge/ui/web/themes/images/gray/tabs/scroll-left.gif
new file mode 100644
index 0000000..bbb3e3d
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tabs/scroll-left.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tabs/scroll-right.gif b/deluge/ui/web/themes/images/gray/tabs/scroll-right.gif
new file mode 100644
index 0000000..feb6a76
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tabs/scroll-right.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tabs/scroller-bg.gif b/deluge/ui/web/themes/images/gray/tabs/scroller-bg.gif
new file mode 100644
index 0000000..f089c0a
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tabs/scroller-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tabs/tab-btm-inactive-left-bg.gif b/deluge/ui/web/themes/images/gray/tabs/tab-btm-inactive-left-bg.gif
new file mode 100644
index 0000000..d718173
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tabs/tab-btm-inactive-left-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tabs/tab-btm-inactive-right-bg.gif b/deluge/ui/web/themes/images/gray/tabs/tab-btm-inactive-right-bg.gif
new file mode 100644
index 0000000..bf35493
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tabs/tab-btm-inactive-right-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tabs/tab-btm-left-bg.gif b/deluge/ui/web/themes/images/gray/tabs/tab-btm-left-bg.gif
new file mode 100644
index 0000000..96d2e5e
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tabs/tab-btm-left-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tabs/tab-btm-over-left-bg.gif b/deluge/ui/web/themes/images/gray/tabs/tab-btm-over-left-bg.gif
new file mode 100644
index 0000000..164d101
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tabs/tab-btm-over-left-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tabs/tab-btm-over-right-bg.gif b/deluge/ui/web/themes/images/gray/tabs/tab-btm-over-right-bg.gif
new file mode 100644
index 0000000..f6f0495
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tabs/tab-btm-over-right-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tabs/tab-btm-right-bg.gif b/deluge/ui/web/themes/images/gray/tabs/tab-btm-right-bg.gif
new file mode 100644
index 0000000..c41cada
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tabs/tab-btm-right-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tabs/tab-close.gif b/deluge/ui/web/themes/images/gray/tabs/tab-close.gif
new file mode 100644
index 0000000..98d5da9
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tabs/tab-close.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tabs/tab-strip-bg.gif b/deluge/ui/web/themes/images/gray/tabs/tab-strip-bg.gif
new file mode 100644
index 0000000..040b677
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tabs/tab-strip-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tabs/tab-strip-bg.png b/deluge/ui/web/themes/images/gray/tabs/tab-strip-bg.png
new file mode 100644
index 0000000..fed48eb
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tabs/tab-strip-bg.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tabs/tab-strip-btm-bg.gif b/deluge/ui/web/themes/images/gray/tabs/tab-strip-btm-bg.gif
new file mode 100644
index 0000000..f35087f
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tabs/tab-strip-btm-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tabs/tabs-sprite.gif b/deluge/ui/web/themes/images/gray/tabs/tabs-sprite.gif
new file mode 100644
index 0000000..1901b23
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tabs/tabs-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/toolbar/bg.gif b/deluge/ui/web/themes/images/gray/toolbar/bg.gif
new file mode 100644
index 0000000..9ab78a2
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/toolbar/bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/toolbar/btn-arrow-light.gif b/deluge/ui/web/themes/images/gray/toolbar/btn-arrow-light.gif
new file mode 100644
index 0000000..b0e24b5
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/toolbar/btn-arrow-light.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/toolbar/btn-arrow.gif b/deluge/ui/web/themes/images/gray/toolbar/btn-arrow.gif
new file mode 100644
index 0000000..8acb460
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/toolbar/btn-arrow.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/toolbar/btn-over-bg.gif b/deluge/ui/web/themes/images/gray/toolbar/btn-over-bg.gif
new file mode 100644
index 0000000..ee2dd98
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/toolbar/btn-over-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/toolbar/gray-bg.gif b/deluge/ui/web/themes/images/gray/toolbar/gray-bg.gif
new file mode 100644
index 0000000..5464e21
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/toolbar/gray-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/toolbar/more.gif b/deluge/ui/web/themes/images/gray/toolbar/more.gif
new file mode 100644
index 0000000..77f4f23
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/toolbar/more.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/toolbar/tb-bg.gif b/deluge/ui/web/themes/images/gray/toolbar/tb-bg.gif
new file mode 100644
index 0000000..4969e4e
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/toolbar/tb-bg.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/toolbar/tb-btn-sprite.gif b/deluge/ui/web/themes/images/gray/toolbar/tb-btn-sprite.gif
new file mode 100644
index 0000000..894fef2
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/toolbar/tb-btn-sprite.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tree/arrows.gif b/deluge/ui/web/themes/images/gray/tree/arrows.gif
new file mode 100644
index 0000000..a51a8e4
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tree/arrows.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tree/elbow-end-minus-nl.gif b/deluge/ui/web/themes/images/gray/tree/elbow-end-minus-nl.gif
new file mode 100644
index 0000000..8c5bc5d
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tree/elbow-end-minus-nl.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tree/elbow-end-minus.gif b/deluge/ui/web/themes/images/gray/tree/elbow-end-minus.gif
new file mode 100644
index 0000000..5850513
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tree/elbow-end-minus.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tree/elbow-end-plus-nl.gif b/deluge/ui/web/themes/images/gray/tree/elbow-end-plus-nl.gif
new file mode 100644
index 0000000..752b42a
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tree/elbow-end-plus-nl.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/tree/elbow-end-plus.gif b/deluge/ui/web/themes/images/gray/tree/elbow-end-plus.gif
new file mode 100644
index 0000000..ff12635
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/tree/elbow-end-plus.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/window/icon-error.gif b/deluge/ui/web/themes/images/gray/window/icon-error.gif
new file mode 100644
index 0000000..397b655
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/window/icon-error.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/window/icon-info.gif b/deluge/ui/web/themes/images/gray/window/icon-info.gif
new file mode 100644
index 0000000..58281c3
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/window/icon-info.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/window/icon-question.gif b/deluge/ui/web/themes/images/gray/window/icon-question.gif
new file mode 100644
index 0000000..08abd82
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/window/icon-question.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/window/icon-warning.gif b/deluge/ui/web/themes/images/gray/window/icon-warning.gif
new file mode 100644
index 0000000..27ff98b
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/window/icon-warning.gif
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/window/left-corners.png b/deluge/ui/web/themes/images/gray/window/left-corners.png
new file mode 100644
index 0000000..f9f44ad
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/window/left-corners.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/window/left-right.png b/deluge/ui/web/themes/images/gray/window/left-right.png
new file mode 100644
index 0000000..0272b5d
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/window/left-right.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/window/right-corners.png b/deluge/ui/web/themes/images/gray/window/right-corners.png
new file mode 100644
index 0000000..e0a58d3
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/window/right-corners.png
Binary files differ
diff --git a/deluge/ui/web/themes/images/gray/window/top-bottom.png b/deluge/ui/web/themes/images/gray/window/top-bottom.png
new file mode 100644
index 0000000..798cb9b
--- /dev/null
+++ b/deluge/ui/web/themes/images/gray/window/top-bottom.png
Binary files differ
diff --git a/deluge/ui/web/web.py b/deluge/ui/web/web.py
new file mode 100644
index 0000000..4d06247
--- /dev/null
+++ b/deluge/ui/web/web.py
@@ -0,0 +1,91 @@
+# -*- coding: utf-8 -*-
+#
+# Copyright (C) 2009 Damien Churchill <damoxc@gmail.com>
+#
+# This file is part of Deluge and is licensed under GNU General Public License 3.0, or later, with
+# the additional special exception to link portions of this program with the OpenSSL library.
+# See LICENSE for more details.
+#
+
+from __future__ import print_function, unicode_literals
+
+import logging
+
+from twisted.internet.error import CannotListenError
+
+from deluge.common import run_profiled
+from deluge.ui.ui import UI
+
+log = logging.getLogger(__name__)
+
+
+class Web(UI):
+
+ cmd_description = """Web-based user interface (http://localhost:8112)"""
+
+ def __init__(self, *args, **kwargs):
+ super(Web, self).__init__(
+ 'web', *args, description='Starts the Deluge Web interface', **kwargs
+ )
+ self.__server = None
+
+ group = self.parser.add_argument_group(_('Web Server Options'))
+ group.add_argument(
+ '-i',
+ '--interface',
+ metavar='<ip_address>',
+ action='store',
+ help=_('IP address for web server to listen on'),
+ )
+ group.add_argument(
+ '-p',
+ '--port',
+ metavar='<port>',
+ type=int,
+ action='store',
+ help=_('Port for web server to listen on'),
+ )
+ group.add_argument(
+ '-b',
+ '--base',
+ metavar='<path>',
+ action='store',
+ help=_('Set the base path that the ui is running on'),
+ )
+ group.add_argument(
+ '--ssl', action='store_true', help=_('Force the web server to use SSL')
+ )
+ group.add_argument(
+ '--no-ssl',
+ action='store_true',
+ help=_('Force the web server to disable SSL'),
+ )
+ self.parser.add_process_arg_group()
+
+ @property
+ def server(self):
+ return self.__server
+
+ def start(self):
+ super(Web, self).start()
+
+ from deluge.ui.web import server
+
+ self.__server = server.DelugeWeb(options=self.options)
+
+ def run():
+ try:
+ self.server.install_signal_handlers()
+ self.server.start()
+ except CannotListenError as ex:
+ log.error(
+ '%s \nCheck that deluge-web or webui plugin is not already running.',
+ ex,
+ )
+ except Exception as ex:
+ log.exception(ex)
+ raise
+
+ run_profiled(
+ run, output_file=self.options.profile, do_profile=self.options.profile
+ )