commit 7dc479789395662e3b2a4fabca2885676206b89c Author: netquick Date: Sat Jun 1 17:34:27 2024 +0200 test diff --git a/Qt5Core.dll b/Qt5Core.dll new file mode 100644 index 0000000..7a7e820 Binary files /dev/null and b/Qt5Core.dll differ diff --git a/Qt5Gui.dll b/Qt5Gui.dll new file mode 100644 index 0000000..70d7df0 Binary files /dev/null and b/Qt5Gui.dll differ diff --git a/Qt5Widgets.dll b/Qt5Widgets.dll new file mode 100644 index 0000000..2d05350 Binary files /dev/null and b/Qt5Widgets.dll differ diff --git a/RADIANT_MAJOR b/RADIANT_MAJOR new file mode 100644 index 0000000..1e8b314 --- /dev/null +++ b/RADIANT_MAJOR @@ -0,0 +1 @@ +6 diff --git a/RADIANT_MINOR b/RADIANT_MINOR new file mode 100644 index 0000000..573541a --- /dev/null +++ b/RADIANT_MINOR @@ -0,0 +1 @@ +0 diff --git a/bitmaps/MyriadPro-Regular.ttf b/bitmaps/MyriadPro-Regular.ttf new file mode 100644 index 0000000..3d98ec1 Binary files /dev/null and b/bitmaps/MyriadPro-Regular.ttf differ diff --git a/bitmaps/brush_flip_hor.png b/bitmaps/brush_flip_hor.png new file mode 100644 index 0000000..091cd04 Binary files /dev/null and b/bitmaps/brush_flip_hor.png differ diff --git a/bitmaps/brush_flip_vert.png b/bitmaps/brush_flip_vert.png new file mode 100644 index 0000000..9d6de7e Binary files /dev/null and b/bitmaps/brush_flip_vert.png differ diff --git a/bitmaps/brush_rotate_anti.png b/bitmaps/brush_rotate_anti.png new file mode 100644 index 0000000..4897620 Binary files /dev/null and b/bitmaps/brush_rotate_anti.png differ diff --git a/bitmaps/brush_rotate_clock.png b/bitmaps/brush_rotate_clock.png new file mode 100644 index 0000000..7c6b0ba Binary files /dev/null and b/bitmaps/brush_rotate_clock.png differ diff --git a/bitmaps/cap_bevel.png b/bitmaps/cap_bevel.png new file mode 100644 index 0000000..92408cc Binary files /dev/null and b/bitmaps/cap_bevel.png differ diff --git a/bitmaps/cap_cylinder.png b/bitmaps/cap_cylinder.png new file mode 100644 index 0000000..9119be8 Binary files /dev/null and b/bitmaps/cap_cylinder.png differ diff --git a/bitmaps/cap_endcap.png b/bitmaps/cap_endcap.png new file mode 100644 index 0000000..5e841cc Binary files /dev/null and b/bitmaps/cap_endcap.png differ diff --git a/bitmaps/cap_ibevel.png b/bitmaps/cap_ibevel.png new file mode 100644 index 0000000..27a633e Binary files /dev/null and b/bitmaps/cap_ibevel.png differ diff --git a/bitmaps/cap_iendcap.png b/bitmaps/cap_iendcap.png new file mode 100644 index 0000000..283ea60 Binary files /dev/null and b/bitmaps/cap_iendcap.png differ diff --git a/bitmaps/console.png b/bitmaps/console.png new file mode 100644 index 0000000..4004739 Binary files /dev/null and b/bitmaps/console.png differ diff --git a/bitmaps/csgtool_diagonal.png b/bitmaps/csgtool_diagonal.png new file mode 100644 index 0000000..6216152 Binary files /dev/null and b/bitmaps/csgtool_diagonal.png differ diff --git a/bitmaps/csgtool_expand.png b/bitmaps/csgtool_expand.png new file mode 100644 index 0000000..ee29803 Binary files /dev/null and b/bitmaps/csgtool_expand.png differ diff --git a/bitmaps/csgtool_extrude.png b/bitmaps/csgtool_extrude.png new file mode 100644 index 0000000..680338d Binary files /dev/null and b/bitmaps/csgtool_extrude.png differ diff --git a/bitmaps/csgtool_pull.png b/bitmaps/csgtool_pull.png new file mode 100644 index 0000000..cabdd67 Binary files /dev/null and b/bitmaps/csgtool_pull.png differ diff --git a/bitmaps/csgtool_removeinner.png b/bitmaps/csgtool_removeinner.png new file mode 100644 index 0000000..5161b47 Binary files /dev/null and b/bitmaps/csgtool_removeinner.png differ diff --git a/bitmaps/csgtool_shrink.png b/bitmaps/csgtool_shrink.png new file mode 100644 index 0000000..2cb2458 Binary files /dev/null and b/bitmaps/csgtool_shrink.png differ diff --git a/bitmaps/csgtool_wrap.png b/bitmaps/csgtool_wrap.png new file mode 100644 index 0000000..569b8bc Binary files /dev/null and b/bitmaps/csgtool_wrap.png differ diff --git a/bitmaps/curve_cap.png b/bitmaps/curve_cap.png new file mode 100644 index 0000000..a46d1dd Binary files /dev/null and b/bitmaps/curve_cap.png differ diff --git a/bitmaps/ellipsis.png b/bitmaps/ellipsis.png new file mode 100644 index 0000000..270f7b1 Binary files /dev/null and b/bitmaps/ellipsis.png differ diff --git a/bitmaps/entities.png b/bitmaps/entities.png new file mode 100644 index 0000000..e6fa2d1 Binary files /dev/null and b/bitmaps/entities.png differ diff --git a/bitmaps/f-caulk.png b/bitmaps/f-caulk.png new file mode 100644 index 0000000..3b6b899 Binary files /dev/null and b/bitmaps/f-caulk.png differ diff --git a/bitmaps/f-clip.png b/bitmaps/f-clip.png new file mode 100644 index 0000000..e69ae95 Binary files /dev/null and b/bitmaps/f-clip.png differ diff --git a/bitmaps/f-details.png b/bitmaps/f-details.png new file mode 100644 index 0000000..7748ba2 Binary files /dev/null and b/bitmaps/f-details.png differ diff --git a/bitmaps/f-entities.png b/bitmaps/f-entities.png new file mode 100644 index 0000000..7e90d43 Binary files /dev/null and b/bitmaps/f-entities.png differ diff --git a/bitmaps/f-funcgroups.png b/bitmaps/f-funcgroups.png new file mode 100644 index 0000000..aef9613 Binary files /dev/null and b/bitmaps/f-funcgroups.png differ diff --git a/bitmaps/f-hide.png b/bitmaps/f-hide.png new file mode 100644 index 0000000..f283417 Binary files /dev/null and b/bitmaps/f-hide.png differ diff --git a/bitmaps/f-hint.png b/bitmaps/f-hint.png new file mode 100644 index 0000000..0358c22 Binary files /dev/null and b/bitmaps/f-hint.png differ diff --git a/bitmaps/f-lights.png b/bitmaps/f-lights.png new file mode 100644 index 0000000..0c75e2e Binary files /dev/null and b/bitmaps/f-lights.png differ diff --git a/bitmaps/f-liquids.png b/bitmaps/f-liquids.png new file mode 100644 index 0000000..1ecdaae Binary files /dev/null and b/bitmaps/f-liquids.png differ diff --git a/bitmaps/f-models.png b/bitmaps/f-models.png new file mode 100644 index 0000000..42dc3aa Binary files /dev/null and b/bitmaps/f-models.png differ diff --git a/bitmaps/f-region.png b/bitmaps/f-region.png new file mode 100644 index 0000000..a16c7ac Binary files /dev/null and b/bitmaps/f-region.png differ diff --git a/bitmaps/f-reset.png b/bitmaps/f-reset.png new file mode 100644 index 0000000..fa3b37a Binary files /dev/null and b/bitmaps/f-reset.png differ diff --git a/bitmaps/f-sky.png b/bitmaps/f-sky.png new file mode 100644 index 0000000..fe6a997 Binary files /dev/null and b/bitmaps/f-sky.png differ diff --git a/bitmaps/f-structural.png b/bitmaps/f-structural.png new file mode 100644 index 0000000..493cb10 Binary files /dev/null and b/bitmaps/f-structural.png differ diff --git a/bitmaps/f-translucent.png b/bitmaps/f-translucent.png new file mode 100644 index 0000000..90d3351 Binary files /dev/null and b/bitmaps/f-translucent.png differ diff --git a/bitmaps/f-triggers.png b/bitmaps/f-triggers.png new file mode 100644 index 0000000..4b2a8f9 Binary files /dev/null and b/bitmaps/f-triggers.png differ diff --git a/bitmaps/f-world.png b/bitmaps/f-world.png new file mode 100644 index 0000000..e96dd55 Binary files /dev/null and b/bitmaps/f-world.png differ diff --git a/bitmaps/file_open.png b/bitmaps/file_open.png new file mode 100644 index 0000000..bbfe7cc Binary files /dev/null and b/bitmaps/file_open.png differ diff --git a/bitmaps/file_save.png b/bitmaps/file_save.png new file mode 100644 index 0000000..2d5a09f Binary files /dev/null and b/bitmaps/file_save.png differ diff --git a/bitmaps/icon.png b/bitmaps/icon.png new file mode 100644 index 0000000..b0219e8 Binary files /dev/null and b/bitmaps/icon.png differ diff --git a/bitmaps/logo.png b/bitmaps/logo.png new file mode 100644 index 0000000..533933e Binary files /dev/null and b/bitmaps/logo.png differ diff --git a/bitmaps/modify_edges.png b/bitmaps/modify_edges.png new file mode 100644 index 0000000..0bb8714 Binary files /dev/null and b/bitmaps/modify_edges.png differ diff --git a/bitmaps/modify_faces.png b/bitmaps/modify_faces.png new file mode 100644 index 0000000..7615c5b Binary files /dev/null and b/bitmaps/modify_faces.png differ diff --git a/bitmaps/modify_vertices.png b/bitmaps/modify_vertices.png new file mode 100644 index 0000000..70aa38a Binary files /dev/null and b/bitmaps/modify_vertices.png differ diff --git a/bitmaps/notex.png b/bitmaps/notex.png new file mode 100644 index 0000000..e09df39 Binary files /dev/null and b/bitmaps/notex.png differ diff --git a/bitmaps/patch_wireframe.png b/bitmaps/patch_wireframe.png new file mode 100644 index 0000000..78bb81e Binary files /dev/null and b/bitmaps/patch_wireframe.png differ diff --git a/bitmaps/radiant.ico b/bitmaps/radiant.ico new file mode 100644 index 0000000..63de151 Binary files /dev/null and b/bitmaps/radiant.ico differ diff --git a/bitmaps/redo.png b/bitmaps/redo.png new file mode 100644 index 0000000..9483d42 Binary files /dev/null and b/bitmaps/redo.png differ diff --git a/bitmaps/refresh_models.png b/bitmaps/refresh_models.png new file mode 100644 index 0000000..4e53334 Binary files /dev/null and b/bitmaps/refresh_models.png differ diff --git a/bitmaps/select_clipper.png b/bitmaps/select_clipper.png new file mode 100644 index 0000000..f56698f Binary files /dev/null and b/bitmaps/select_clipper.png differ diff --git a/bitmaps/select_mouseresize.png b/bitmaps/select_mouseresize.png new file mode 100644 index 0000000..0e8a097 Binary files /dev/null and b/bitmaps/select_mouseresize.png differ diff --git a/bitmaps/select_mouserotate.png b/bitmaps/select_mouserotate.png new file mode 100644 index 0000000..c07ee34 Binary files /dev/null and b/bitmaps/select_mouserotate.png differ diff --git a/bitmaps/select_mousescale.png b/bitmaps/select_mousescale.png new file mode 100644 index 0000000..e55d26b Binary files /dev/null and b/bitmaps/select_mousescale.png differ diff --git a/bitmaps/select_mousetransform.png b/bitmaps/select_mousetransform.png new file mode 100644 index 0000000..8a69786 Binary files /dev/null and b/bitmaps/select_mousetransform.png differ diff --git a/bitmaps/select_mousetranslate.png b/bitmaps/select_mousetranslate.png new file mode 100644 index 0000000..e0b6e7e Binary files /dev/null and b/bitmaps/select_mousetranslate.png differ diff --git a/bitmaps/select_mouseuv.png b/bitmaps/select_mouseuv.png new file mode 100644 index 0000000..b44891b Binary files /dev/null and b/bitmaps/select_mouseuv.png differ diff --git a/bitmaps/selection_csgmerge.png b/bitmaps/selection_csgmerge.png new file mode 100644 index 0000000..eabaecf Binary files /dev/null and b/bitmaps/selection_csgmerge.png differ diff --git a/bitmaps/selection_csgsubtract.png b/bitmaps/selection_csgsubtract.png new file mode 100644 index 0000000..71d9263 Binary files /dev/null and b/bitmaps/selection_csgsubtract.png differ diff --git a/bitmaps/selection_makeroom.png b/bitmaps/selection_makeroom.png new file mode 100644 index 0000000..8b44a8c Binary files /dev/null and b/bitmaps/selection_makeroom.png differ diff --git a/bitmaps/selection_selectinside.png b/bitmaps/selection_selectinside.png new file mode 100644 index 0000000..457805a Binary files /dev/null and b/bitmaps/selection_selectinside.png differ diff --git a/bitmaps/selection_selecttouching.png b/bitmaps/selection_selecttouching.png new file mode 100644 index 0000000..f4cf1c0 Binary files /dev/null and b/bitmaps/selection_selecttouching.png differ diff --git a/bitmaps/shadernotex.png b/bitmaps/shadernotex.png new file mode 100644 index 0000000..0eab827 Binary files /dev/null and b/bitmaps/shadernotex.png differ diff --git a/bitmaps/splash.png b/bitmaps/splash.png new file mode 100644 index 0000000..3e92376 Binary files /dev/null and b/bitmaps/splash.png differ diff --git a/bitmaps/status_brush.png b/bitmaps/status_brush.png new file mode 100644 index 0000000..b3b84c6 Binary files /dev/null and b/bitmaps/status_brush.png differ diff --git a/bitmaps/status_entity.png b/bitmaps/status_entity.png new file mode 100644 index 0000000..2f084a4 Binary files /dev/null and b/bitmaps/status_entity.png differ diff --git a/bitmaps/texbro_gtk-find-and-replace.png b/bitmaps/texbro_gtk-find-and-replace.png new file mode 100644 index 0000000..fca34f5 Binary files /dev/null and b/bitmaps/texbro_gtk-find-and-replace.png differ diff --git a/bitmaps/texbro_refresh.png b/bitmaps/texbro_refresh.png new file mode 100644 index 0000000..ed2419a Binary files /dev/null and b/bitmaps/texbro_refresh.png differ diff --git a/bitmaps/texbro_view.png b/bitmaps/texbro_view.png new file mode 100644 index 0000000..cf2f4ac Binary files /dev/null and b/bitmaps/texbro_view.png differ diff --git a/bitmaps/texture_browser.png b/bitmaps/texture_browser.png new file mode 100644 index 0000000..3c145c5 Binary files /dev/null and b/bitmaps/texture_browser.png differ diff --git a/bitmaps/texture_lock.png b/bitmaps/texture_lock.png new file mode 100644 index 0000000..2a6c271 Binary files /dev/null and b/bitmaps/texture_lock.png differ diff --git a/bitmaps/texture_vertexlock.png b/bitmaps/texture_vertexlock.png new file mode 100644 index 0000000..5a8d329 Binary files /dev/null and b/bitmaps/texture_vertexlock.png differ diff --git a/bitmaps/undo.png b/bitmaps/undo.png new file mode 100644 index 0000000..a9f9c04 Binary files /dev/null and b/bitmaps/undo.png differ diff --git a/bitmaps/view_change.png b/bitmaps/view_change.png new file mode 100644 index 0000000..e72238f Binary files /dev/null and b/bitmaps/view_change.png differ diff --git a/bitmaps/view_cubicclipping.png b/bitmaps/view_cubicclipping.png new file mode 100644 index 0000000..f48edac Binary files /dev/null and b/bitmaps/view_cubicclipping.png differ diff --git a/bitmaps/view_entity.png b/bitmaps/view_entity.png new file mode 100644 index 0000000..4a29d90 Binary files /dev/null and b/bitmaps/view_entity.png differ diff --git a/bitmaps/window1.png b/bitmaps/window1.png new file mode 100644 index 0000000..8163ee7 Binary files /dev/null and b/bitmaps/window1.png differ diff --git a/bitmaps/window2.png b/bitmaps/window2.png new file mode 100644 index 0000000..67da22a Binary files /dev/null and b/bitmaps/window2.png differ diff --git a/bitmaps/window3.png b/bitmaps/window3.png new file mode 100644 index 0000000..8596ca8 Binary files /dev/null and b/bitmaps/window3.png differ diff --git a/bitmaps/window4.png b/bitmaps/window4.png new file mode 100644 index 0000000..ac6df1c Binary files /dev/null and b/bitmaps/window4.png differ diff --git a/docs/Additional_map_compiler_features.htm b/docs/Additional_map_compiler_features.htm new file mode 100644 index 0000000..c3e5c94 --- /dev/null +++ b/docs/Additional_map_compiler_features.htm @@ -0,0 +1,310 @@ + + + + +NetRadiant - Additional map compiler features - Alientrap Development + + + + + + + + + + + + + + + + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +История +
+ + + + + +
+

Additional map compiler features

+ + +

The following features for use by mappers have been added in NetRadiant and thus are not documented elsewhere:

+ +

Floodlighting

+ + +

+ + +

A very quick and dirty way to light a map is adding the following key to worldspawn:

+ + +
+"_floodlight" "240 240 255 1024 128 1"
+

The parameters work as follow: the first three numbers select the color of the lighting. The fourth number sets a distance to trace, and the fifth number sets the intensity of the floodlight. + +

This lighting is somewhat similar to -dirty, combined with use of the ambient key in worldspawn.

+ + +

This feature was contributed by the Urban Terror team.

+ + +

Lightmap exposure

+ + +

+ + +

The q3map2 light compile parameter -exposure changes the handling of overbright pixels to look more realistic. Try values like -exposure 200! The higher the value, the darker the result gets.

+ + +

It is most interesting if you intentionally put colored lights of a far too high light value in your map.

+ + +

This feature was contributed by the Urban Terror team.

+ + +

dotProductScale

+ + + + + + + + + + + + + + + +
dotProduct dotProduct2 dotProductScale dotProduct2scale
+ + + + +

The following shader parameters are added for use with terrain blending:

+ + +
+q3map_alphagen dotProduct2scale ( X Y Z MIN MAX )
+
+ +

As with dotProduct2, X Y Z denote the normal on the plane to use for dotProduct terrain blending. The values MIN and MAX specify a range of squared cosine values, so that MIN is mapped to alpha value 0, MAX is mapped to alpha value 1, and everything in between is mapped linearily.

+ + +

If you prefer working with angles, you can set MIN to the squared cosine of the most steep angle of the blending, and MAX to the squared cosine of the most flat angle of the blending. Example:

+ + +
+q3map_alphagen dotProduct2scale ( 0 0 1 0.250 0.933 )
+
+ +

will set alpha value 0 for anything steeper than 60 degrees, and alpha value 1 for anything more flat than 15 degrees.

+ + +

The same extension to dotProduct is called dotProductScale. Note that the MIN and MAX values are not squared there. You get a different curve with the same min/max angles by writing:

+ + +
+q3map_alphagen dotProductScale ( 0 0 1 0.500 0.966 )
+
+ +

Minimum sample size

+ + +

The compiler option -minsamplesize in the BSP stage enforces a given minimum
sample size, even if shaders or func_groups set a lightmap scale. This allows
higher quality compiles of existing maps that use these features without having
to create extraordinarily large amounts of lightmaps, especially when using
external lightmaps.

+ + +

Converting to ASE prefabs

+ + +

Use -convert -format ase -shadersasbitmap for this purpose. That way, the
created ASE files work as prefabs, and thus contain shader names, instead of
texture file names. In a model editor, however, these ASE files will not show
up with textures, as modelling software does not support Q3 shaders.

+ + +

Zero-effort cel shading

+ + +

If you already have a cel shader (in Nexuiz, cel/black_ink), you can compile
the map using it easily by adding the option -celshader cel/black_ink to the
BSP stage.

+ + +

MiniMap generator

+ + +

+ + +This version of the map compiler can generate minimaps as used by Nexuiz.
The specs of the minimap's texture mapping are: +
    +
  • Let M be the rectangle of the world's mins/maxs coordinates.
  • +
  • If keepaspect is set: let S be the smallest square completely covering M whose center matches the one of M. Otherwise, let S be M.
  • +
  • Let S' be S scaled by factor 1/(1-2*border) around the center of S.
  • +
  • The "mins" corner of S' corresponds to the top left corner of the image.
  • +
  • The "maxs" corner of S' corresponds to the bottom right corner of the image.
  • +
+
+ + +
+ +

floodlight.jpg + (7 КБ) + + + divVerent, Вт, 05 мая 2009, 13:26:00 -0400 + +

+ +

exposure.jpg + (27.8 КБ) + + + divVerent, Вт, 05 мая 2009, 13:26:31 -0400 + +

+ +

dotproduct-small.jpg + (25.9 КБ) + + + divVerent, Вт, 05 мая 2009, 13:29:00 -0400 + +

+ +

dotproduct2-small.jpg + (24.2 КБ) + + + divVerent, Вт, 05 мая 2009, 13:29:36 -0400 + +

+ +

dotproductscale-small.jpg + (23.3 КБ) + + + divVerent, Вт, 05 мая 2009, 13:29:54 -0400 + +

+ +

dotproduct2scale-small.jpg + (22.8 КБ) + + + divVerent, Вт, 05 мая 2009, 13:30:08 -0400 + +

+ +

minimap.jpg + (6.4 КБ) + + + divVerent, Вт, 05 мая 2009, 13:30:30 -0400 + +

+ +
+ + + + +

Экспортировать в + HTML + TXT +

+ + + + + + + + +
+
+
+ + + + +
+ + + diff --git a/docs/Additional_map_editor_features.htm b/docs/Additional_map_editor_features.htm new file mode 100644 index 0000000..beeea1f --- /dev/null +++ b/docs/Additional_map_editor_features.htm @@ -0,0 +1,236 @@ + + + + +NetRadiant - Additional map editor features - Alientrap Development + + + + + + + + + + + + + + + + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +История +
+ + + + + +
+

Additional map editor features

+ + +

The following features were added to the map editor:

+ + +

OBJ model format

+ + +

OBJ files can be used as models, and texture information is taken from their associated MTL file.

+ + +

In Blender, make sure "use material groups" is enabled when exporting.

+ + +

Expand selection to whole entities

+ + +

Ctrl-Alt-E now selects the parent entity, too. Useful for duplicating the entity, as opposed to just its brushes.

+ + +

Targeting lines

+ + +

Targeting lines are also shown for target2, target3, target4 and killtarget keys to match usage in Nexuiz.

+ + +

Non-modal Rotate and Scale dialogs

+ + +

Rotate and Scale dialogs are no longer modal, so you can keep the dialog open and e.g. change the selection and then apply another rotation.

+ + +

Four-pane view improvement

+ + +

In the four-pane view, Ctrl-Tab now centers all three 2D views to the currently selected entity, so you do not need to hold Shift too.

+ + +

Strafe mode

+ + +

The option "Strafe mode" can be changed to select the strafing behaviour: default mode "Both" matches GtkRadiant 1.5: Ctrl enables sideways strafing, Shift-Ctrl enables forward moving. The other modes allow to switch between one of these two behaviours permanently, so Shift-Ctrl can be used to select faces while moving around in the 3D view.

+ + +

Regrouping entities

+ + +

When having a brush of an entity selected, this command removes that brush from the entity and puts it in worldspawn. When having a brush and an entity selected, it puts the brush into that entity (note: do this in the entity list or using the Ctrl-Alt-E bind "Expand selection to whole entities" to make sure that the entity, and not just one of its brushes, is selected). That way, you can insert/remove brushes from entities without having to retype all entity keys or redoing the brushwork.

+ + +

Clone selection change

+ + +

"Clone selection" (space) no longer changes targetname/target of entities, so you can clone to create many entities to target one. If you want to change targetname/target when cloning like Radiant did before, use Shift+Space.

+ + +

The clip line

+ + +

+ + +

The clip tool shows an extra line pointing in the direction of what's getting erased when you hit Enter. That way, the mistake of accidentally deleting the wrong half when clipping can be avoided.

+ + +

Automatic game configuration

+ + +

NetRadiant detects being inside a Q2World or Nexuiz install, and automatically configures its engine path to it. Other games can be easily added to the list.

+ + +

Editable keyboard shortcuts

+ + +

The Help/Keyboard shortcuts dialog now allows changing the shortcuts, so you can e.g. enable previously unbound features.

+ + +

Portable NetRadiant

+ + +

If you create a subdirectory called settings in your Radiant install, the install becomes "portable", that means it'll write its settings there, and not in a user-specific directory. Useful to take Radiant together with its settings with you on an USB stick.

+ + +

Makes especially much sense in conjunction with automatic game configuration.

+
+ + +
+ +

clipline-small.png + (1.8 КБ) + + + divVerent, Вт, 05 мая 2009, 13:13:56 -0400 + +

+ +

clipline.png + (400.6 КБ) + + + divVerent, Вт, 05 мая 2009, 13:14:57 -0400 + +

+ +
+ + + + +

Экспортировать в + HTML + TXT +

+ + + + + + + + +
+
+
+ + + + +
+ + + diff --git a/docs/Blendmodes_cheatsheet.jpg b/docs/Blendmodes_cheatsheet.jpg new file mode 100644 index 0000000..b2e4c39 Binary files /dev/null and b/docs/Blendmodes_cheatsheet.jpg differ diff --git a/docs/Complete_list_of_command_line_parameters.htm b/docs/Complete_list_of_command_line_parameters.htm new file mode 100644 index 0000000..5fbaeb9 --- /dev/null +++ b/docs/Complete_list_of_command_line_parameters.htm @@ -0,0 +1,470 @@ + + + + +NetRadiant - Complete list of command line parameters - Alientrap Development + + + + + + + + + + + + + + + + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +История +
+ + + + + +
+

Complete list of command line parameters

+ + +

Common options

+
    +
  • -connect address: Talk to a NetRadiant instance using a specific XML based protocol
  • +
  • -force: Allow reading some broken/unsupported BSP files e.g. when decompiling, may crash. Also enables decompilation of model autoclip brushes.
  • +
  • -fs_basepath path: Sets the given path as main directory of the game (can be used more than once to look in multiple paths)
  • +
  • -fs_forbiddenpath pattern: Pattern to ignore directories, pk3, and pk3dir; example pak?.pk3 (can be used more than once to look for multiple patterns)
  • +
  • -fs_game gamename: Sets extra game directory name to additionally load mod's resources from at higher priority (by default for Q3A 'baseq3' is loaded, -fs_game cpma will also load 'cpma'; can be used more than once)
  • +
  • -fs_basegame gamename: Overrides default game directory name (e.g. Q3A uses 'baseq3', OpenArena 'baseoa', so -game quake3 -fs_basegame baseoa for OA )
  • +
  • -fs_home dir: Specifies where the user home directory is on Linux
  • +
  • -fs_homebase dir: Specifies game home directory relative to user home directory on Linux (default for Q3A: .q3a)
  • +
  • -fs_homepath path: Sets the given path as the game home directory name (fs_home + fs_homebase)
  • +
  • -fs_pakpath path: Specify a package directory (can be used more than once to look in multiple paths)
  • +
  • -game gamename: Load settings for the given game (default: quake3), -help -game lists available games
  • +
  • -maxmapdrawsurfs N: Sets max amount of mapDrawSurfs, used during .map compilation (-bsp, -convert), default = 131072
  • +
  • -maxshaderinfo N: Sets max amount of shaderInfo, default = 8192
  • +
  • -subdivisions F: multiplier for patch subdivisions quality
  • +
  • -threads N: number of threads to use
  • +
  • -v: Verbose mode
  • +
+ + +

BSP stage

+
    +
  • -bsp ... filename.map: Switch that enters this stage
  • +
  • -altsplit: Alternate BSP tree splitting weights (should give more fps)
  • +
  • -autocaulk: Only output special .caulk file for use by radiant
  • +
  • -celshader shadername: Sets a global cel shader name
  • +
  • -clipdepth F: Model autoclip brushes thickness, default = 2
  • +
  • -custinfoparms: Read scripts/custinfoparms.txt
  • +
  • -debugclip: Make model autoclip brushes visible, using shaders debugclip, debugclip2
  • +
  • -debuginset: Push all triangle vertexes towards the triangle center
  • +
  • -debugportals: Make BSP portals visible in the map
  • +
  • -debugsurfaces: Color the vertexes according to the index of the surface
  • +
  • -deep: Use detail brushes in the BSP tree, but at lowest priority (should give more fps)
  • +
  • -de F: Distance epsilon for plane snapping etc.
  • +
  • -fakemap: Write fakemap.map containing all world brushes
  • +
  • -flares: Turn on support for flares
  • +
  • -flat: Enable flat shading (good for combining with -celshader)
  • +
  • -fulldetail: Treat detail brushes as structural ones
  • +
  • -keeplights: Keep light entities in the BSP file after compile
  • +
  • -keepmodels: Keep misc_model entities in the BSP file after compile
  • +
  • -leaktest: Abort if a leak was found
  • +
  • -maxarea: Use Max Area face surface generation
  • +
  • -meta: Combine adjacent triangles of the same texture to surfaces (ALWAYS USE THIS)
  • +
  • -metaadequatescore N: Adequate score for adding triangles to meta surfaces
  • +
  • -metagoodscore N: Good score for adding triangles to meta surfaces
  • +
  • -minsamplesize N: Sets minimum lightmap resolution in luxels/qu
  • +
  • -mi N: Sets the maximum number of indexes per surface
  • +
  • -mv N: Sets the maximum number of vertices of a lightmapped surface
  • +
  • -ne F: Normal epsilon for plane snapping etc.
  • +
  • -nocurves: Turn off support for patches
  • +
  • -nodetail: Leave out detail brushes
  • +
  • -noflares: Turn off support for flares
  • +
  • -nofog: Turn off support for fog volumes
  • +
  • -nohint: Turn off support for hint brushes
  • +
  • -noob: Assign surfaceparm noob to all map surfaces (Q3A:Defrag mod no-overbounces flag)
  • +
  • -nosRGB: Treat colors and textures as linear colorspace
  • +
  • -nosRGBcolor: Treat shader and light entity colors as linear colorspace
  • +
  • -nosRGBtex: Treat textures as linear colorspace
  • +
  • -nosubdivide: Turn off support for q3map_tessSize (breaks water vertex deforms)
  • +
  • -notjunc: Do not fix T-junctions (causes cracks between triangles, do not use)
  • +
  • -nowater: Turn off support for water, slime or lava (Stef, this is for you)
  • +
  • -np A: Force all surfaces to be nonplanar with a given shade angle
  • +
  • -onlyents: Only update entities in the BSP
  • +
  • -patchmeta: Turn patches into triangle meshes for display
  • +
  • -rename: Append "\_bsp" suffix to misc\_model shaders (needed for SoF2)
  • +
  • -samplesize N: Sets default lightmap resolution in luxels/qu
  • +
  • -skyfix: Turn sky box into six surfaces to work around ATI problems
  • +
  • -snap N: Snap brush bevel planes to the given number of units
  • +
  • -sRGBcolor: Treat shader and light entity colors as sRGB colorspace
  • +
  • -sRGBtex: Treat textures as sRGB colorspace
  • +
  • -tempname filename.map: Read the MAP file from the given file name
  • +
  • -verboseentities: Enable -v only for map entities, not for the world
  • +
+ + +

VIS stage

+
    +
  • -vis ... filename.map: Switch that enters this stage
  • +
  • -fast: Very fast and crude vis calculation
  • +
  • -hint: Merge all but hint portals
  • +
  • -mergeportals: The less crude half of -merge, makes vis sometimes much faster but doesn't hurt fps usually
  • +
  • -merge: Faster but still okay vis calculation
  • +
  • -nopassage: Just use PortalFlow vis (usually less fps)
  • +
  • -nosort: Do not sort the portals before calculating vis (usually slower)
  • +
  • -passageOnly: Just use PassageFlow vis (usually less fps)
  • +
  • -saveprt: Keep the PRT file after running vis (so you can run vis again)
  • +
  • -v -v: Extra verbose mode for cluster debug
  • +
+ + +

LIGHT stage

+
    +
  • -light ... filename.map: Switch that enters this stage
  • +
  • -approx N: Vertex light approximation tolerance (never use in conjunction with deluxemapping)
  • +
  • -areascale F, -area F: Scaling factor for area lights (surfacelight)
  • +
  • -backsplash Fscale Fdistance: scale area lights backsplash fraction + set distance globally; (distance < -900 to omit distance setting); default = 1 23; real area lights have no backsplash (scale = 0); q3map_backsplash shader keyword overrides this setting
  • +
  • -border: Add a red border to lightmaps for debugging
  • +
  • -bouncecolorratio F: 0..1 ratio of colorizing light sample by texture
  • +
  • -bouncegrid: Also compute radiosity on the light grid
  • +
  • -bounceonly: Only compute radiosity
  • +
  • -bouncescale F: Scaling factor for radiosity
  • +
  • -bounce N: Maximal number of bounces for radiosity
  • +
  • -brightness F: Scaling factor for resulting lightmaps brightness
  • +
  • -cheapgrid: Use -cheap style lighting for radiosity
  • +
  • -cheap: Abort vertex light calculations when white is reached
  • +
  • -compensate F: Lightmap compensate (darkening factor applied after everything else)
  • +
  • -contrast F: -255 .. 255 lighting contrast, default = 0
  • +
  • -cpma: CPMA vertex lighting mode
  • +
  • -custinfoparms: Read scripts/custinfoparms.txt
  • +
  • -dark: Darken lightmap seams
  • +
  • -debugaxis: Color the lightmaps according to the lightmap axis
  • +
  • -debugcluster: Color the lightmaps according to the index of the cluster
  • +
  • -debugdeluxe: Show deluxemaps on the lightmap
  • +
  • -debugnormals: Color the lightmaps according to the direction of the surface normal
  • +
  • -debugorigin: Color the lightmaps according to the origin of the luxels
  • +
  • -debugsamplesize: display all of 'surface too large for desired samplesize+lightmapsize' warnings
  • +
  • -debugsurfaces, -debugsurface: Color the lightmaps according to the index of the surface
  • +
  • -debug: Mark the lightmaps according to the cluster: unmapped clusters get yellow, occluded ones get pink, flooded ones get blue overlay color, otherwise red
  • +
  • -deluxemode 0: Use modelspace deluxemaps (DarkPlaces)
  • +
  • -deluxemode 1: Use tangentspace deluxemaps
  • +
  • -deluxe, -deluxemap: Enable deluxemapping (light direction maps)
  • +
  • -dirtdebug, -debugdirt: Store the dirtmaps as lightmaps for debugging
  • +
  • -dirtdepth: Dirtmapping depth
  • +
  • -dirtgain: Dirtmapping exponent
  • +
  • -dirtmode 0: Ordered direction dirtmapping
  • +
  • -dirtmode 1: Randomized direction dirtmapping
  • +
  • -dirtscale: Dirtmapping scaling factor
  • +
  • -dirty: Enable dirtmapping
  • +
  • -dump: Dump radiosity from -bounce into numbered MAP file prefabs
  • +
  • -export: Export lightmaps when compile finished (like -export mode)
  • +
  • -exposure F: Lightmap exposure to better support overbright spots
  • +
  • -external: Force external lightmaps even if at size of internal lightmaps
  • +
  • -extlmhacksize N or N N: External lightmaps hack size: similar to -lightmapsize N: Size of lightmaps to generate (must be a power of two), but instead of native external lightmaps enables hack to reference them in autogenerated shader (for vanilla Q3 etc)
  • +
  • -extradist F: Extra distance for lights in map units
  • +
  • -extravisnudge: Broken feature to nudge the luxel origin to a better vis cluster
  • +
  • -fastbounce: Use -fast style lighting for radiosity
  • +
  • -faster: Use a faster falloff curve for lighting; also implies -fast
  • +
  • -fastgrid: Use -fast style lighting for the light grid
  • +
  • -fast: Ignore tiny light contributions
  • +
  • -fill: Fill lightmap colors from surrounding pixels to improve JPEG compression
  • +
  • -fillpink: Fill unoccupied lightmap pixels with pink colour
  • +
  • -filter: Lightmap filtering
  • +
  • -floodlight: Enable floodlight (zero-effort somewhat decent lighting)
  • +
  • -gamma F: Lightmap gamma
  • +
  • -gridambientdirectionality F: Ambient directional lighting received (default: 0.0)
  • +
  • -gridambientscale F: Scaling factor for the light grid ambient components only
  • +
  • -griddirectionality F: Directional lighting received (default: 1.0)
  • +
  • -gridscale F: Scaling factor for the light grid only
  • +
  • -lightanglehl 0: Disable half lambert light angle attenuation
  • +
  • -lightanglehl 1: Enable half lambert light angle attenuation
  • +
  • -lightmapdir directory: Directory to store external lightmaps (default: same as map name without extension)
  • +
  • -lightmapsearchblocksize N: Restricted lightmap searching - block size
  • +
  • -lightmapsearchpower N: Restricted lightmap searching - merge power
  • +
  • -lightmapsize N: Size of lightmaps to generate (must be a power of two)
  • +
  • -lomem: Low memory but slower lighting mode
  • +
  • -lowquality: Low quality floodlight (appears to currently break floodlight)
  • +
  • -minsamplesize N: Sets minimum lightmap resolution in luxels/qu
  • +
  • -nobouncestore: Do not store BSP, lightmap and shader files between bounces
  • +
  • -nocollapse: Do not collapse identical lightmaps
  • +
  • -nodeluxe, -nodeluxemap: Disable deluxemapping
  • +
  • -nofastpoint: Disable fast point light calculation
  • +
  • -nogrid: Disable grid light calculation (makes all entities fullbright)
  • +
  • -nolightmapsearch: Do not optimize lightmap packing for GPU memory usage (as doing so costs fps)
  • +
  • -nolm: Skip lightmaps calculation
  • +
  • -normalmap: Color the lightmaps according to the direction of the surface normal (TODO is this identical to -debugnormals?)
  • +
  • -nosRGB: Treat colors, textures, and lightmaps as linear colorspace
  • +
  • -nosRGBcolor: Treat shader and light entity colors as linear colorspace
  • +
  • -nosRGBlight: Write lightmaps as linear colorspace
  • +
  • -nosRGBtex: Treat textures as linear colorspace
  • +
  • -nostyle, -nostyles: Disable support for light styles
  • +
  • -nosurf: Disable tracing against surfaces (only uses BSP nodes then)
  • +
  • -notrace: Disable shadow occlusion
  • +
  • -novertex: Disable vertex lighting; optional (0..1) value sets constant vertex light globally
  • +
  • -patchshadows: Cast shadows from patches
  • +
  • -pointscale F, -point F: Scaling factor for spherical and spot point lights (light entities)
  • +
  • -q3: Use nonlinear falloff curve by default (like Q3A)
  • +
  • -randomsamples: Use random sampling for lightmaps
  • +
  • -rawlightmapsizelimit N: Sets maximum lightmap resolution in luxels/qu (only affects patches if used -patchmeta in BSP stage)
  • +
  • -samplescale F: Scales all lightmap resolutions
  • +
  • -samplesize N: Sets default lightmap resolution in luxels/qu
  • +
  • -samplessearchboxsize N: Search box size (1 to 4) for lightmap adaptive supersampling
  • +
  • -samples N: Adaptive supersampling quality
  • +
  • -saturation F: Lighting saturation: default = 1, > 1 = saturate, 0 = grayscale, < 0 = complementary colors
  • +
  • -scale F: Scaling factor for all light types
  • +
  • -shadeangle A: Angle for phong shading
  • +
  • -shade: Enable phong shading at default shade angle
  • +
  • -skyscale F, -sky F: Scaling factor for sky and sun light
  • +
  • -slowallocate: Use old (a bit more careful, but much slower) lightmaps packing algorithm
  • +
  • -sphericalscale F, -spherical F: Scaling factor for spherical point light entities
  • +
  • -spotscale F, -spot F: Scaling factor for spot point light entities
  • +
  • -sRGB: Treat colors, textures, and lightmaps as sRGB colorspace
  • +
  • -sRGBcolor: Treat shader and light entity colors as sRGB colorspace
  • +
  • -sRGBlight: Write lightmaps as sRGB colorspace
  • +
  • -sRGBtex: Treat textures as sRGB colorspace
  • +
  • -style, -styles: Enable support for light styles
  • +
  • -sunonly: Only compute sun light
  • +
  • -super N, -supersample N: Ordered grid supersampling quality
  • +
  • -thresh F: Triangle subdivision threshold
  • +
  • -trianglecheck: Broken check that should ensure luxels apply to the right triangle
  • +
  • -trisoup: Convert brush faces to triangle soup
  • +
  • -vertexscale F: Scaling factor for resulting vertex light values
  • +
  • -wolf: Use linear falloff curve by default (like W:ET)
  • +
+ + +

Analyzing BSP-like file structure

+
    +
  • -analyze ... filename.bsp: Switch that enters this mode
  • +
  • -lumpswap: Swap byte order in the lumps
  • +
+ + +

Converting & Decompiling

+
    +
  • -convert ... filename.bsp: Switch that enters this mode
  • +
  • -deluxemapsastexcoord: Save deluxemap names and texcoords instead of textures (only when writing ase and obj)
  • +
  • -de F: Distance epsilon for the conversion (only when reading map)
  • +
  • -fast: fast bsp to map conversion mode (without texture alignments)
  • +
  • -format converter: Select the converter, default ase (available: map, map_bp, ase, obj, or game names)
  • +
  • -lightmapsastexcoord: Save lightmap names and texcoords instead of textures (only when writing ase and obj)
  • +
  • -meta: Combine adjacent triangles of the same texture to surfaces (only when reading map)
  • +
  • -ne F: Normal epsilon for the conversion (only when reading map)
  • +
  • -patchmeta: Turn patches into triangle meshes for display (only when reading map)
  • +
  • -readbsp: Force converting bsp to selected format
  • +
  • -readmap: Force converting map to selected format
  • +
  • -shadersasbitmap: Save shader names as bitmap names in the model so it works as a prefab (only when writing ase and obj)
  • +
+ + +

Exporting lightmaps

+
    +
  • -export filename.bsp: Copies lightmaps from the BSP to filename/lightmap_NNNN.tga
  • +
+ + +

Importing lightmaps

+
    +
  • -import filename.bsp: Copies lightmaps from filename/lightmap_NNNN.tga into the BSP
  • +
+ + +

Exporting entities

+
    +
  • -exportents filename.bsp: Exports the entities to a text file (.ent)
  • +
+ + +

Fixing AAS checksum

+
    +
  • -fixaas filename.bsp: Writes BSP checksum to AAS file, so that it's accepted as valid by engine
  • +
+ + +

Get info about BSP file

+
    +
  • -info filename.bsp .. filenameN.bsp: Switch that enters this mode
  • +
+ + +

MiniMap

+
    +
  • -minimap ... filename.bsp: Creates a minimap of the BSP, by default writes to ../gfx/filename_mini.tga
  • +
  • -autolevel: Automatically level brightness and contrast
  • +
  • -black: Write the minimap as a black-on-transparency RGBA32 image
  • +
  • -boost F: Sets the contrast boost value (higher values make a brighter image); contrast boost is somewhat similar to gamma, but continuous even at zero
  • +
  • -border F: Sets the amount of border pixels relative to the total image size
  • +
  • -brightness F: Sets brightness value to add to minimap values
  • +
  • -contrast F: Sets contrast value to scale minimap values (doesn't affect brightness)
  • +
  • -gray: Write the minimap as a white-on-black GRAY8 image
  • +
  • -keepaspect: Ensure the aspect ratio is kept (the minimap is then letterboxed to keep aspect)
  • +
  • -minmax xmin ymin zmin xmax ymax zmax: Forces specific map dimensions (note: the minimap actually uses these dimensions, scaled to the target size while keeping aspect with centering, and 1/64 of border appended to all sides)
  • +
  • -noautolevel: Do not automatically level brightness and contrast
  • +
  • -nokeepaspect: Do not ensure the aspect ratio is kept (makes it easier to use the image in your code, but looks bad together with sharpening)
  • +
  • -o filename.tga: Sets the output file name
  • +
  • -random N: Sets the randomized supersampling count (cannot be combined with -samples)
  • +
  • -samples N: Sets the ordered supersampling count (cannot be combined with -random)
  • +
  • -sharpen F: Sets the sharpening coefficient
  • +
  • -size N: Sets the width and height of the output image
  • +
  • -white: Write the minimap as a white-on-transparency RGBA32 image
  • +
+ + +

BSP Scaling

+
    +
  • -scale [options] S filename.bsp: Scale uniformly
  • +
  • -scale [options] SX SY SZ filename.bsp: Scale non-uniformly
  • +
  • -tex: Option to scale without texture lock
  • +
  • -spawn_ref F: Option to vertically offset info_player_* entities (adds spawn_ref, scales, subtracts spawn_ref)
  • +
+ + +

BSP Shift

+
    +
  • -shift S filename.bsp: Shift uniformly
  • +
  • -shift SX SY SZ filename.bsp: Shift non-uniformly
  • +
+ + +

PK3 creation

+
    +
  • -pk3 ... filename.bsp .. filenameN.bsp: Creates a pk3 for the BSP(s) (complete Q3 support). Using file 'gamename.exclude' to exclude vanilla game resources.
  • +
  • -complevel N: Set compression level (-1 .. 10); 0 = uncompressed, -1 = 6, 10 = ultra zlib incompatible preset
  • +
  • -dbg: Print wall of debug text, useful for .exclude file creation
  • +
  • -png: include png textures, at highest priority; taking tga, jpg by default
  • +
+ + +

Maps repack creation

+
    +
  • -repack ... filename.bsp .. filenameN.bsp or filenames.txt: Creates repack of BSP(s) (complete Q3 support). Rips off only used shaders to new shader file. Using file 'gamename.exclude' to exclude vanilla game resources and 'repack.exclude' to exclude resources of existing repack.
  • +
  • -analyze: Only print bsp resource references and exit
  • +
  • -complevel N: Set compression level (-1 .. 10); 0 = uncompressed, -1 = 6, 10 = ultra zlib incompatible preset
  • +
  • -dbg: Print wall of debug text
  • +
  • -png: include png textures, at highest priority; taking tga, jpg by default
  • +
+ + +

BSP json export/import

+
    +
  • -json ... filename.bsp: Export/import BSP to/from json text files for debugging and editing purposes
  • +
  • -unpack: Unpack BSP to json
  • +
  • -pack: Pack json to BSP
  • +
  • -useflagnames: While packing, deduce surface/content flag values from their names in shaders.json (useful for conversion to a game with different flag values)
  • +
  • -skipflags: While -useflagnames, skip unknown flag names
  • +
+
+ + +

BSP merge

+
    +
  • -mergebsp ... mainBsp.bsp bspToinject.bsp: Inject latter BSP to former. Tree and vis data of the main one are preserved.
  • +
  • -fixnames: Make incoming BSP target/targetname names unique to not collide with existing names
  • +
  • -world: Also merge worldspawn model (brushes as if they were detail, no BSP tree is affected) (only merges entities by default)
  • +
+
+ + + + + + +

Экспортировать в + HTML + TXT +

+ + + + + + + + +
+
+
+ + + + + + + + diff --git a/docs/Complete_list_of_entity_keys.htm b/docs/Complete_list_of_entity_keys.htm new file mode 100644 index 0000000..224528f --- /dev/null +++ b/docs/Complete_list_of_entity_keys.htm @@ -0,0 +1,266 @@ + + + + +NetRadiant - Complete list of entity keys - Alientrap Development + + + + + + + + + + + + + + + + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +История +
+ + + + + +
+

Complete list of entity keys

+ + +
    +
  • On all entities +
      +
    • angle: shorthand for just setting the yaw angle
    • +
    • angles: angles vector
    • +
    • origin: origin vector
    • +
    • targetname: name of the entity so it can be referenced by target keys.
    • +
    +
  • +
  • On brush entities, classname func_group and classname misc_model: +
      +
    • _castShadows, _cs: sets whether the entity casts shadows
    • +
    • _celshader: CelShader to use
    • +
    • _lightmapsamplesize, _samplesize, _ss: sample size to use for surfaces of this entity
    • +
    • _receiveShadows, _rs: sets whether the entity receives shadows
    • +
    • _shadeangle, _smoothnormals, _sn, _sa, _smooth: largest angle between faces to allow to treat them part of the same nonplanar surface
    • +
    • lightmapscale, _lightmapscale, _ls: scaling factor for the lightmap resolution
    • +
    +
  • +
  • On brush entities and on classname func_group: +
      +
    • _indexmap, alphamap: file name of index map image for terrain blending
    • +
    • _layers, layers: number of layers of the index map image (encoded as brightness levels)
    • +
    • _offsets, offsets: space separated list of z offsets for index map
    • +
    • _shader, shader: shader name prefix of the index map (when this is set to X, the shader names that are generated will be like textures/X_42 and textures/X_23to42
    • +
    +
  • +
  • On brush entities +
      +
    • _clone, _ins, _instance: _clonename field value of a brush entity to clone brushes from (can be used to use the same brush model multiple times)
    • +
    • _clonename: see _clone
    • +
    • _patchMeta, patchMeta: generate a triangle soup from patches in this entity
    • +
    • _patchQuality, patchQuality: quality multiplier for patches on this surface when _patchMeta is used
    • +
    • _patchSubdivide, patchSubdivide: absolute quality setting for patches on this surface when _patchMeta is used
    • +
    • max: override the maxs vector of this brush entity
    • +
    • min: override the mins vector of this brush entity
    • +
    +
  • +
  • On classname worldspawn +
      +
    • _ambient, ambient: amount of ambient light
    • +
    • _blocksize, blocksize, chopsize: block size for unconditional BSP subdivisions
    • +
    • _color: color to use for _ambient, _minlight, _minvertexlight, _mingridlight
    • +
    • _farplanedist, fogclip, distancecull: far plane distance for vis culling -- this must be the shortest distance at which nothing can be seen any more due to fog; o/r/e value suffixes enable Origin2Origin/Radius+Radius/Exact distance measuring modes respectively
    • +
    • _floodlight: a sextet of values "r g b dist intensity directional_power" to set global floodlight parameters (good defaults are 240 240 255 1024 128 1)
    • +
    • _fog, fog: if set, the whole map is fogged using the given shader name; only for engines, supporting this, e.g. ET
    • +
    • _foghull: must be set to a sky shader without textures/ prefix when _farplanedist is used to avoid HoM effect
    • +
    • _ignoreleaks, ignoreleaks: when set, no leak test is performed
    • +
    • _keepLights: if set, light entities are not stripped from the BSP file when compiling
    • +
    • _keepModels: if set, misc_model entities are not stripped from the BSP file when compiling
    • +
    • _maxlight: amount of maximum light
    • +
    • _mingridlight: amount of minimum grid light
    • +
    • _minlight: amount of minimum light
    • +
    • _minvertexlight: amount of minimum vertex light
    • +
    • _noshadersun: if set, sun light from shaders is suppressed
    • +
    • _q3map_cmdline: written by q3map2; contains the command line the map was compiled with
    • +
    • _q3map_version: written by q3map2; contains the version of q3map2 the map was compiled with
    • +
    • _style42alphagen: |alphaGen|-like shader definition string for light style 42 (works the same way for all style numbers)
    • +
    • _style42rgbgen: |rgbGen|-like shader definition string for light style 42 (works the same way for all style numbers)
    • +
    • gridsize: N N N resolution of the light grid
    • +
    +
  • +
  • On classname light and classname lightJunior +
      +
    • Note: lightJunior entities only affect the light grid.
    • +
    • _anglescale: scales angle attenuation
    • +
    • _color: color of the light
    • +
    • _deviance, _deviation, _jitter: position deviance of the samples of a regular light (distributes the light samples in a cube of side length 2*_deviance around the origin), or angle deviance of the sun light samples in radians
    • +
    • _extradist: "extra dimension" distance of the light, to kill hot spots
    • +
    • _filterradius, _filteradius, _filter: filter radius for this light, similar to -light -filter
    • +
    • _light, light: intensity factor (default: 300)
    • +
    • _samples: number of samples to use to get soft shadows from a light
    • +
    • _sun: if 1, this light is an infinite sun light, must have target
    • +
    • fade: Fade factor of light attenuation of linear lights. Linear lights completely vanish at distance light/(fade * 8000), so if you want the light to vanish at distance X, specify light/(8000*X) here.
    • +
    • radius: radius of a spotlight at the target point (default: 64)
    • +
    • scale: intensity multiplier
    • +
    • spawnflags: 1 = linear attenuation (inverted in -wolf lighting mode)
    • +
    • spawnflags: 2 = no angle attenuation (inverted in -wolf lighting mode)
    • +
    • spawnflags: 32 = the light color is not normalized
    • +
    • spawnflags: 64 = force distance attenuation (why did vortex add this, this is always set...?)
    • +
    • target: target of a spotlight
    • + +
    • targetname: when set, the light can be toggled in game by some engine provided way
    • +
    +
  • +
  • On classname light +
      +
    • _flare: when set, this light is a flare without a specified shader
    • +
    • _flareshader: shader for a flare surface generated by this light
    • +
    • _style, style: light style number
    • +
    • spawnflags: 16 = light does not affect the grid
    • +
    +
  • +
  • On classname advertisement (QuakeLive only) +
      +
    • Brushes/Patches: must be a single rectangular patch surface where the ad will be placed
    • +
    • cellId: identifier of the ad, must be used only once (??)
    • +
    +
  • +
  • On classname _decal +
      +
    • Brushes/Patches: must be a single rectangular patch surface
    • +
    • target: positional target to set the projection direction of the decal
    • +
    +
  • +
  • On classname misc_model +
      + +
    • _clipdepth: thickness of autoclip brushes
    • +
    • _frame, frame: frame of model to load (doesn't work)
    • +
    • _remapXXX: XXX can be any string to allow multiple keys for this; contains a string of the form from;to, and any shader from in the model will be replaced by to; the special value * in from matches all shader names
    • +
    • _skin, skin: skin number (DarkPlaces convention) or name (Quake3 convention) to use
    • + +
    • model: path name of model to load
    • +
    • modelscale: scaling factor for the model to include
    • +
    • modelscale_vec: non-uniform scaling vector for the model to include
    • +
    • spawnflags: 1 = in SoF2, append a _RMG_BSP suffix to shader names instead of _BSP
    • +
    • spawnflags: 2 = generate clipping brushes from the model
    • +
    • spawnflags: 4 = force this model through meta surface merging
    • +
    • spawnflags: 8 = when generating clipping planes, perform extrusion using the original normals from the model instead of per-triangle best axial normals
    • +
    • spawnflags: 16 = when generating clipping planes, perform extrusion using only up or down pointing normals (ideal for terrain)
    • +
    • spawnflags: 32 = turn vertex color from the model into alpha (for terrain blending)
    • +
    • target, _target: points to brush entity with any of matching targetname/_targetname keys to bake the model into
    • +
    +
  • +
  • On classname _skybox +
      +
    • To be placed inside a small otherwise hidden room; surfaces in it are scaled up, and visible through skybox surfaces
    • +
    • _scale: scaling factor or vector for the portal sky (default: 64)
    • +
  • +
+
+ + + + + + +

Экспортировать в + HTML + TXT +

+ + + + + + + + +
+
+
+ + + + +
+ + + diff --git a/docs/Complete_list_of_shader_keywords.htm b/docs/Complete_list_of_shader_keywords.htm new file mode 100644 index 0000000..7b666e8 --- /dev/null +++ b/docs/Complete_list_of_shader_keywords.htm @@ -0,0 +1,259 @@ + + + + +NetRadiant - Complete list of shader keywords - Alientrap Development + + + + + + + + + + + + + + + + + +
+ + + + +
+ + +
+ +
+ + + + + + + + + +История +
+ + + + + +
+

Complete list of shader keywords

+ + +

Note: You can append a :q3map suffix to a shader name to make q3map2 use the shader but the engine ignore it.

+ + +

Note: This list does not contain all possible shader keywords, but just those that q3map2 uses for anything. More keywords are likely to be supported by your engine. Check the documentation or the source code of your engine on this.

+ + +

Inside shader stages

+ + +
    +
  • map texture: loads a texture and displays it repeating
  • +
  • clampMap texture: loads a texture and displays it non-repeating
  • +
  • animMap fps texture texture...: loads an animation and displays it repeating
  • +
  • clampAnimMap fps texture texture...: loads an animation and displays it non-repeating
  • +
  • mapComp texture: ???
  • +
  • mapNoComp texture: ???
  • +
+ + +

In the shader preamble:

+ + +
    +
  • cull none, cull disable, cull twosided: treat the surface as two sided for lighting
  • +
  • damageShader shadername: sets the given shader as damage shader (for SoF2 mods)
  • +
  • fogparms ...: marks the brush as a fog volume; otherwise handled by the engine
  • +
  • implicitBlend: ???
  • +
  • implicitMap: ???
  • +
  • implicitMask: ???
  • +
  • light N: sets the default game flare shader as flare shader
  • +
  • polygonoffset: enable polygon offset
  • +
  • q3map_alphaGen, q3map_alphaMod: <set F|const F|scale F|dotProduct ( X Y Z )|dotProduct2 ( X Y Z )|dotProductScale ( X Y Z MIN MAX )|dotProduct2scale ( X Y Z MIN MAX )|volume>
  • +
  • q3map_backShader shadername: sets the given shader as shader for back faces
  • +
  • q3map_backsplash percent distance: configures light backsplash (self-surfacelight)
  • +
  • q3map_baseShader shadername: inherit parameters from a shader
  • +
  • q3map_bounce F, q3map_bounceScale F: scales the intensity of radiosity
  • +
  • q3map_clipmodel: ???
  • +
  • q3map_cloneShader shadername: ???
  • +
  • q3map_colorGen, q3map_colorMod, q3map_rgbGen, q3map_rgbMod: <set ( R G B )|const ( R G B )|scale ( F F F )|dotProduct ( X Y Z )|dotProduct2 ( X Y Z )|dotProductScale ( X Y Z MIN MAX )|dotProduct2scale ( X Y Z MIN MAX )|volume>
  • +
  • q3map_deprecateShader shadername: ???
  • +
  • q3map_flare shadername, q3map_flareShader shadername: sets the given shader as flare shader
  • +
  • q3map_floodLight r g b dist intensity power: overrides the global floodlight parameters
  • +
  • q3map_fogDir ( x y z ): sets the direction a fog shader fades from transparent to opaque
  • +
  • q3map_foliage path scale density odds invertalpha: ???
  • +
  • q3map_forceMeta: forces brush faces and/or triangle models to go through the metasurface pipeline
  • +
  • q3map_forceSunlight: ???
  • +
  • q3map_fur layers offset fade: ???
  • +
  • q3map_globaltexture: ???
  • +
  • q3map_indexed: ???
  • +
  • q3map_invert: inverts the direction from which the face is visible
  • +
  • q3map_lightImage texture: sets the image to take the light color from
  • +
  • q3map_lightRGB r g b: overrides the light color of the texture
  • +
  • q3map_lightStyle N: sets the light style (SoF2, JK2)
  • +
  • q3map_lightSubdivide N: subdivision interval for q3map_surfacelight
  • +
  • q3map_lightmapAxis axis: sets the lightmap axis to one of x, y, z (useful for terrain)
  • +
  • q3map_lightmapBrightness F, q3map_lightmapGamma F: overrides lightmap brightness
  • +
  • q3map_lightmapFilterRadius self other: ???
  • +
  • q3map_lightmapMergable: allows merging the lightmap with other surfaces on another plane
  • +
  • q3map_lightmapSampleOffset F: light sampling point offset off the surface
  • +
  • q3map_lightmapSampleSize N: overrides samplesize
  • +
  • q3map_lightmapSize N: overrides the lightmap size (forces an external lightmap for this surface)
  • +
  • q3map_material materialname: ???
  • +
  • q3map_noDirty: don't apply -dirty on this surface
  • +
  • q3map_noFast: disable -fast style lighting for this surface
  • +
  • q3map_noVertexLight: turns off vertex lighting for this surface
  • +
  • q3map_noVertexShadows: ???
  • +
  • q3map_noclip: do not clip the surface by the BSP tree
  • +
  • q3map_nofog: ???
  • +
  • q3map_nonplanar: marks the surface as nonplanar for meta surface merging
  • +
  • q3map_normalImage texture: sets the normal map for bump mapping
  • +
  • q3map_notjunc: do not do t-junction elimination
  • +
  • q3map_offset F: ???
  • +
  • q3map_onlyVertexLighting: same as using surfaceparm pointlight
  • +
  • q3map_patchShadows: force shadowing from patches using this shader
  • +
  • q3map_remapShader shadername: ???
  • +
  • q3map_shadeAngle F: sets the shading angle for nonplanar surfaces
  • +
  • q3map_skyLight value iterations: sets the amount of sky light from this surface
  • +
  • q3map_splotchfix: ???
  • +
  • q3map_styleMarker2: ???
  • +
  • q3map_styleMarker: ???
  • +
  • q3map_sunext r g b intensity degrees elevation deviance samples: sets an unsharp sun for the map
  • +
  • q3map_sun r g b intensity degrees elevation, sun r g b intensity degrees elevation: sets a sharp sun for the map
  • +
  • q3map_surfacelight F: sets the amount of surface light from this surface
  • +
  • q3map_surfacemodel path density minscale maxscale minangle maxangle oriented: randomly place models on the surface
  • +
  • q3map_tcGen ivector ( sx sy sz ) ( tx ty tz ): same as q3map_tcGen vector but with inverted values
  • +
  • q3map_tcGen vector ( sx sy sz ) ( tx ty tz ): overrides texcoords based on world coordinates (for terrain)
  • +
  • q3map_tcMod rotate a: rotates the texture
  • +
  • q3map_tcMod scale s t: multiplies texcoords by factors
  • +
  • q3map_tcMod translate s t: translates texcoords by a vector
  • +
  • q3map_terrain: ???
  • +
  • q3map_textureSize width height: overrides the texture size for texcoords
  • +
  • q3map_vertexScale F: scales vertex lighting amount by a factor
  • +
  • q3map_vertexShadows: ???
  • +
  • qer_editorImage texture: sets the texture to show for radiant
  • +
  • skyparms outerimage cloudheight innerimage: loads a skybox
  • +
  • surfaceparm alphashadow: use the alpha channel of the shader image as shadow mask
  • +
  • surfaceparm antiportal: creates opaque autoexpanding portal
  • +
  • surfaceparm areaportal: ???
  • +
  • surfaceparm botclip: ???
  • +
  • surfaceparm clusterportal: ???
  • +
  • surfaceparm detail: ignore this surface for vis
  • +
  • surfaceparm donotenter: ???
  • +
  • surfaceparm fog: ???
  • +
  • surfaceparm hint: use this surface as a hint to generate BSP splits
  • +
  • surfaceparm lava: Stef hates this brush
  • +
  • surfaceparm lightfilter: use the color channel of the shader image as shadow mask
  • +
  • surfaceparm lightgrid: limit map lightgrid to bounds of brushes with this shader
  • +
  • surfaceparm monsterclip: monsters can't go through this brush, but shots can
  • +
  • surfaceparm nodraw: do not generate draw surfaces
  • +
  • surfaceparm nodrop: items can't be dropped on this brush
  • +
  • surfaceparm nolightmap, surfaceparm pointlight: do not lightmap this surface
  • +
  • surfaceparm nomarks: this surface is stain free
  • +
  • surfaceparm nonsolid: do not make this surface solid
  • +
  • surfaceparm origin: the center of this brush shall be the origin of this brush model
  • +
  • surfaceparm playerclip: players can't go through this brush, but shots can
  • +
  • surfaceparm skip: for unused faces of hint, antiportal brushes
  • +
  • surfaceparm sky: this surface shows the skybox
  • +
  • surfaceparm slime: this brush contains more poisonous stuff than dihydrogene monoxide
  • +
  • surfaceparm structural: use this surface for vis
  • +
  • surfaceparm trans: cast no shadows
  • +
  • surfaceparm trigger: this is a trigger brush (translucent and solid)
  • +
  • surfaceparm water: this brush contains dihydrogene monoxide
  • +
  • tessSize F, q3map_tessSize F: subdivides the polygons to ensure no parts are larger than the given size
  • +
+
+ + + + + + +

Экспортировать в + HTML + TXT +

+ + + + + + + + +
+
+
+ + + + +
+ + + diff --git a/docs/application.css b/docs/application.css new file mode 100644 index 0000000..0e359f8 --- /dev/null +++ b/docs/application.css @@ -0,0 +1,881 @@ +body { font-family: Verdana, sans-serif; font-size: 12px; color:#484848; margin: 0; padding: 0; min-width: 900px; } + +h1, h2, h3, h4 { font-family: "Trebuchet MS", Verdana, sans-serif;} +h1 {margin:0; padding:0; font-size: 24px;} +h2, .wiki h1 {font-size: 20px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;} +h3, .wiki h2 {font-size: 16px;padding: 2px 10px 1px 0px;margin: 0 0 10px 0; border-bottom: 1px solid #bbbbbb; color: #444;} +h4, .wiki h3 {font-size: 13px;padding: 2px 10px 1px 0px;margin-bottom: 5px; border-bottom: 1px dotted #bbbbbb; color: #444;} + +/***** Layout *****/ +#wrapper {background: white;} + +#top-menu {background: #55646D; color: #fff; height:2em; font-size: 0.8em; padding: 4px 2px 0px 6px;border-bottom:2px solid #333} +#top-menu ul {margin: 0; padding: 0;} +#top-menu li { + float:left; + list-style-type:none; + margin: 0; + padding: 0; + white-space:nowrap; +} +#top-menu a {color: #fff; margin-right: 8px; font-weight: bold;} +#top-menu #loggedas { float: right; margin-right: 0.5em; color: #fff; margin-top:2px; } + +#top-menu ul a { padding:2px 0 3px 20px; display:block; height:16px; } +#top-menu a.home { background:transparent url('house.png') 0 0 no-repeat; } +#top-menu a.my-page { background:transparent url('../images/my-page.png') 0 0 no-repeat; } +#top-menu a.projects { background:transparent url('projects.png') 0 0 no-repeat; } +#top-menu a.stuff-to-do { background:transparent url('../images/stuff_to_do.png') 0 0 no-repeat; } +#top-menu a.timesheet { background:transparent url('../images/timesheet.png') 0 0 no-repeat; } +#top-menu a.administration { background:transparent url('../images/administration.png') 0 0 no-repeat; } +#top-menu a.help { background:transparent url('help-top.png') 0 0 no-repeat; } +#top-menu a.logout { background:transparent url('../images/sign-out.png') 0 0 no-repeat; } +#top-menu a.my-account { background:transparent url('../images/my-account.png') 0 0 no-repeat; } + +#account {float:right;} + +#header {height:5.3em;margin:0;background:#547938 url('header_gradient.png') 0 0 repeat-x;color:#f8f8f8; padding: 4px 8px 0px 6px; position:relative;} +#header a {color:#f8f8f8;} +#header h1 { float:left; margin-top:5px; } +#header h1 a.ancestor { font-size: 80%; } +#quick-search {float:right;} + +#main-menu {position: absolute; bottom: 0px; left:6px; margin-right: -500px; } +#main-menu ul {margin: 0; padding: 0;} +#main-menu li { + float:left; + list-style-type:none; + margin: 0px 2px 0px 0px; + padding: 0px 0px 0px 0px; + white-space:nowrap; +} +#main-menu li a { + display: block; + color: #fff; + text-decoration: none; + font-weight: bold; + margin: 0; + padding: 4px 10px 4px 26px; + background-position:6px center; + background-repeat:no-repeat; +} +#main-menu li a:hover { + background:#8ACB58; + color:#fff; + background-position:6px center; + background-repeat:no-repeat; +} +#main-menu li a.selected, #main-menu li a.selected:hover { + background-color:#fff; + color:#555; + background-position:6px center; + background-repeat:no-repeat; +} + +/* Redmine core project-menu links */ +#main-menu li a.overview { background-image: url(document-text-image.png); } +#main-menu li a.activity { background-image: url(lightning.png); } +#main-menu li a.roadmap { background-image: url(../images/fugue/map-pin.png); } +#main-menu li a.issues { background-image: url(ticket.png); } +#main-menu li a.new-issue { background-image: url(../images/fugue/ticket--plus.png); } +#main-menu li a.news { background-image: url(newspaper.png); } +#main-menu li a.documents { background-image: url(documents-text.png); } +#main-menu li a.wiki { background-image: url(document-horizontal-text.png); } +#main-menu li a.boards { background-image: url(balloons.png); } +#main-menu li a.files { background-image: url(document-zipper.png); } +#main-menu li a.repository { background-image: url(safe.png); } +#main-menu li a.customers { background-image: url(../images/fugue/user-business.png); } +#main-menu li a.ezfaq { background-image: url(help-top.png); } +#main-menu li a.settings { background-image: url(../images/fugue/equalizer.png); } + +#main {background-color:#EEEEEE;} + +#sidebar{ float: right; width: 17%; position: relative; z-index: 9; min-height: 600px; padding: 0; margin: 0;} +* html #sidebar{ width: 17%; } +#sidebar h3{ font-size: 14px; margin-top:14px; color: #666; } +#sidebar hr{ width: 100%; margin: 0 auto; height: 1px; background: #ccc; border: 0; } +* html #sidebar hr{ width: 95%; position: relative; left: -6px; color: #ccc; } + +#content { width: 80%; background-color: #fff; margin: 0px; border-right: 1px solid #ddd; padding: 6px 10px 10px 10px; z-index: 10; } +* html #content{ width: 80%; padding-left: 0; margin-top: 0px; padding: 6px 10px 10px 10px;} +html>body #content { min-height: 600px; } +* html body #content { height: 600px; } /* IE */ + +#main.nosidebar #sidebar{ display: none; } +#main.nosidebar #content{ width: auto; border-right: 0; } + +#footer {clear: both; border-top: 1px solid #bbb; font-size: 0.9em; color: #aaa; padding: 5px; text-align:center; background:#fff;} + +#login-form table {margin-top:5em; padding:1em; margin-left: auto; margin-right: auto; border: 2px solid #FDBF3B; background-color:#FFEBC1; } +#login-form table td {padding: 6px;} +#login-form label {font-weight: bold;} +#login-form input#username, #login-form input#password { width: 300px; } + +input#openid_url { background: url(../images/openid-bg.gif) no-repeat; background-color: #fff; background-position: 0 50%; padding-left: 18px; } + +.clear:after{ content: "."; display: block; height: 0; clear: both; visibility: hidden; } + +/***** Links *****/ +a, a:link, a:visited{ color: #3F794B; text-decoration: none; } +a:hover, a:active{ color: #3A4F5B; text-decoration: underline;} +a img{ border: 0; } + +a.issue.closed, a.issue.closed:link, a.issue.closed:visited { text-decoration: line-through; } + +/***** Tables *****/ +table.list { border: 1px solid #49702A; border-collapse: collapse; width: 100%; margin-bottom: 4px; } +table.list th { background:#547938 url('header_gradient.png') 0 0 repeat-x; color:#D6E4CB; padding: 4px; white-space:nowrap; } +table.list th a { color:#fff; } +table.list th a:hover { color:#E0FFC9; } +table.list td { vertical-align: top; } +table.list td.id { width: 2%; text-align: center;} +table.list td.checkbox { width: 15px; padding: 0px;} +table.list.issues .status-1 .id { background:transparent url('../images/new.png') center 18px no-repeat; } +table.list.issues .status-2 .id { background:transparent url('../images/assigned.png') center 18px no-repeat; } +table.list.issues .status-3 .id { background:transparent url('../images/resolved.png') center 18px no-repeat; } +table.list.issues .status-4 .id { background:transparent url('../images/feedback.png') center 18px no-repeat; } +table.list.issues .status-5 .id { background:transparent url('../images/closed.png') center 18px no-repeat; } +table.list.issues .status-6 .id { background:transparent url('../images/rejected.png') center 18px no-repeat; } +table.list.issues .status-8 .id { background:transparent url('../images/waiting.png') center 18px no-repeat; } +table.list.issues .status-7 .id { background:transparent url('../images/wont_fix.png') center 18px no-repeat; } + +tr.issue { text-align: center; white-space: nowrap; } +tr.issue td.subject, tr.issue td.category, td.assigned_to { white-space: normal; } +tr.issue td.subject { text-align: left; } +tr.issue td.done_ratio { width:80px } +tr.issue td.done_ratio table.progress { margin-left:auto; margin-right: auto;} + +tr.priority-1.even { background-color:#F8FFAA; } +tr.priority-1.odd { background-color:#FAFFB8; } +tr.priority-2.even { background-color:#FFEF9A; } +tr.priority-2.odd { background-color:#FFF1A6; } +tr.priority-3.even { background-color:#FFD29A; } +tr.priority-3.odd { background-color:#FFD8A6; } +tr.priority-4.even { background-color:#FFB19A; } +tr.priority-4.odd { background-color:#FFBAA6; } +tr.priority-5.even { background-color:#FF9A9F; } +tr.priority-5.odd { background-color:#FFA6AA; } + +.status { text-align:left; padding-left:22px; } +tr.status-1 .status { background:transparent url('../images/new.png') 0 0 no-repeat; } +tr.status-2 .status { background:transparent url('../images/assigned.png') 0 0 no-repeat; } +tr.status-3 .status { background:transparent url('../images/resolved.png') 0 0 no-repeat; } +tr.status-4 .status { background:transparent url('../images/feedback.png') 0 0 no-repeat; } +tr.status-5 .status { background:transparent url('../images/closed.png') 0 0 no-repeat; } +tr.status-6 .status { background:transparent url('../images/rejected.png') 0 0 no-repeat; } +tr.status-8 .status { background:transparent url('../images/waiting.png') 0 0 no-repeat; } +tr.status-7 .status { background:transparent url('../images/wont_fix.png') 0 0 no-repeat; } + +tr.entry { border: 1px solid #f8f8f8; } +tr.entry td { white-space: nowrap; } +tr.entry td.filename { width: 30%; } +tr.entry td.size { text-align: right; font-size: 90%; } +tr.entry td.revision, tr.entry td.author { text-align: center; } +tr.entry td.age { text-align: right; } + +tr.entry span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;} +tr.entry.open span.expander {background-image: url(../images/bullet_toggle_minus.png);} +tr.entry.file td.filename a { margin-left: 16px; } +tr span.expander {background-image: url(../images/bullet_toggle_plus.png); padding-left: 8px; margin-left: 0; cursor: pointer;} +tr.open span.expander {background-image: url(../images/bullet_toggle_minus.png);} + +tr.changeset td.author { text-align: center; width: 15%; } +tr.changeset td.committed_on { text-align: center; width: 15%; } + +table.files tr.file td { text-align: center; } +table.files tr.file td.filename { text-align: left; padding-left: 24px; } +table.files tr.file td.digest { font-size: 80%; } + +table.members td.roles, table.memberships td.roles { width: 45%; } + +tr.message { height: 2.6em; } +tr.message td.last_message { font-size: 80%; } +tr.message.locked td.subject a { background-image: url(../images/locked.png); } +tr.message.sticky td.subject a { background-image: url(../images/sticky.png); font-weight: bold; } + +tr.user td { width:13%; } +tr.user td.email { width:18%; } +tr.user td { white-space: nowrap; } +tr.user.locked, tr.user.registered { color: #aaa; } +tr.user.locked a, tr.user.registered a { color: #aaa; } + +tr.time-entry { text-align: center; white-space: nowrap; } +tr.time-entry td.subject, tr.time-entry td.comments { text-align: left; white-space: normal; } +td.hours { text-align: right; font-weight: bold; padding-right: 0.5em; } +td.hours .hours-dec { font-size: 0.9em; } + +table.plugins td { vertical-align: middle; } +table.plugins td.configure { text-align: right; padding-right: 1em; } +table.plugins span.name { font-weight: bold; display: block; margin-bottom: 6px; } +table.plugins span.description { display: block; font-size: 0.9em; } +table.plugins span.url { display: block; font-size: 0.9em; } + +table.list tbody tr.group td { padding: 0.8em 0 0.5em 0.3em; font-weight: bold; border-bottom: 1px solid #ccc; } +table.list tbody tr.group span.count { color: #aaa; font-size: 80%; } + +table.list tbody tr:hover { background-color:#ffffdd; } +table.list tbody tr.group:hover { background-color:inherit; } +table td {padding:2px;} +table p {margin:0;} +.odd {background-color:#f6f7f8;} +.even {background-color: #fff;} +a.sort { padding-right: 16px; background-position: 100% 50%; background-repeat: no-repeat; } +a.sort.asc { background-image: url(../images/sort_asc.png); } +a.sort.desc { background-image: url(../images/sort_desc.png); } + +table.attributes { width: 100% } +table.attributes th { vertical-align: top; text-align: left; } +table.attributes td { vertical-align: top; } + +td.center {text-align:center;} + +.highlight { background-color: #FCFD8D;} +.highlight.token-1 { background-color: #faa;} +.highlight.token-2 { background-color: #afa;} +.highlight.token-3 { background-color: #aaf;} + +.box{ +padding:6px; +margin-bottom: 10px; +background-color:#f6f6f6; +color:#505050; +line-height:1.5em; +border: 1px solid #e4e4e4; +} + +div.square { + border: 1px solid #999; + float: left; + margin: .3em .4em 0 .4em; + overflow: hidden; + width: .6em; height: .6em; +} +.contextual {float:right; white-space: nowrap; line-height:1.4em;margin-top:5px; padding-left: 10px; font-size:0.9em;} +.contextual input, .contextual select {font-size:0.9em;} +.message .contextual { margin-top: 0; } + +.splitcontentleft{float:left; width:49%;} +.splitcontentright{float:right; width:49%;} +form {display: inline;} +input, select {vertical-align: middle; margin-top: 1px; margin-bottom: 1px;} +fieldset {border: 1px solid #e4e4e4; margin:0;} +legend {color: #484848;} +hr { width: 100%; height: 1px; background: #ccc; border: 0;} +blockquote { font-style: italic; border-left: 3px solid #e0e0e0; padding-left: 0.6em; margin-left: 2.4em;} +blockquote blockquote { margin-left: 0;} +textarea.wiki-edit { width: 99%; } +li p {margin-top: 0;} +div.issue {background:#ffffdd; padding:6px; margin-bottom:6px;border: 1px solid #d7d7d7;} +p.breadcrumb { font-size: 0.9em; margin: 4px 0 4px 0;} +p.subtitle { font-size: 0.9em; margin: -6px 0 12px 0; font-style: italic; } +p.footnote { font-size: 0.9em; margin-top: 0px; margin-bottom: 0px; } + +fieldset.collapsible { border-width: 1px 0 0 0; font-size: 0.9em; } +fieldset.collapsible legend { padding-left: 16px; background: url(../images/arrow_expanded.png) no-repeat 0% 40%; cursor:pointer; } +fieldset.collapsible.collapsed legend { background-image: url(../images/arrow_collapsed.png); } + +fieldset#date-range p { margin: 2px 0 2px 0; } +fieldset#filters table { border-collapse: collapse; } +fieldset#filters table td { padding: 0; vertical-align: middle; } +fieldset#filters tr.filter { height: 2em; } +fieldset#filters td.add-filter { text-align: right; vertical-align: top; } +.buttons { font-size: 0.9em; margin-bottom: 1.4em; margin-top: 1em; } + +div#issue-changesets {float:right; width:45%; margin-left: 1em; margin-bottom: 1em; background: #fff; padding-left: 1em; font-size: 90%;} +div#issue-changesets .changeset { padding: 4px;} +div#issue-changesets .changeset { border-bottom: 1px solid #ddd; } +div#issue-changesets p { margin-top: 0; margin-bottom: 1em;} + +div#activity dl, #search-results { margin-left: 2em; } +div#activity dd, #search-results dd { margin-bottom: 1em; padding:4px; font-size: 0.9em; background-color:#eee; border:1px solid #ddd; border-top:0; } +div#activity dt, #search-results dt { margin-bottom: 0px; padding-left: 20px; line-height: 18px; background-position: 0 50%; background-repeat: no-repeat; } +div#activity dt.me .time { border-bottom: 1px solid #999; } +div#activity dt .time { color: #777; font-size: 80%; } +div#activity dd .description, #search-results dd .description { font-style: italic; } +div#activity span.project:after, #search-results span.project:after { content: " -"; } +div#activity dd span.description, #search-results dd span.description { display:block; } + +#search-results dd { margin-bottom: 1em; padding-left: 20px; margin-left:0px; } +div#search-results-counts {float:right;} +div#search-results-counts ul { margin-top: 0.5em; } +div#search-results-counts li { list-style-type:none; float: left; margin-left: 1em; } + +dt.me a { background:transparent url(../images/fav.png) right 0 no-repeat; padding-right:18px; } +dt.issue { background-image: url(../images/ticket.png); background-color:#fed; border:1px solid #edc; } +dt.issue-edit { background-image: url(../images/ticket_edit.png); background-color:#fdf; border:1px solid #ece; } +dt.issue-closed { background-image: url(../images/ticket_checked.png); background-color:#ddf; border:1px solid #cce; } +dt.issue-note { background-image: url(../images/ticket_note.png); background-color:#ffd; border:1px solid #eec; } +dt.changeset { background-image: url(../images/changeset.png); background-color:#dfd; border:1px solid #cec; } +dt.news { background-image: url(../images/news.png); } +dt.message { background-image: url(../images/message.png); } +dt.reply { background-image: url(../images/comments.png); } +dt.wiki-page { background-image: url(../images/wiki_edit.png); } +dt.attachment { background-image: url(attachment.png); } +dt.document { background-image: url(../images/document.png); } +dt.project { background-image: url(projects.png); } +dt.time-entry { background-image: url(../images/time.png); } + +#search-results dt.issue.closed { background-image: url(../images/ticket_checked.png); } + +div#roadmap fieldset.related-issues { margin-bottom: 1em; } +div#roadmap fieldset.related-issues ul { margin-top: 0.3em; margin-bottom: 0.3em; } +div#roadmap .wiki h1:first-child { display: none; } +div#roadmap .wiki h1 { font-size: 120%; } +div#roadmap .wiki h2 { font-size: 110%; } + +div#version-summary { float:right; width:380px; margin-left: 16px; margin-bottom: 16px; background-color: #fff; } +div#version-summary fieldset { margin-bottom: 1em; } +div#version-summary .total-hours { text-align: right; } + +table#time-report td.hours, table#time-report th.period, table#time-report th.total { text-align: right; padding-right: 0.5em; } +table#time-report tbody tr { font-style: italic; color: #777; } +table#time-report tbody tr.last-level { font-style: normal; color: #555; } +table#time-report tbody tr.total { font-style: normal; font-weight: bold; color: #555; background-color:#EEEEEE; } +table#time-report .hours-dec { font-size: 0.9em; } + +form#issue-form .attributes { margin-bottom: 8px; } +form#issue-form .attributes p { padding-top: 1px; padding-bottom: 2px; } +form#issue-form .attributes select { min-width: 30%; } + +ul.projects { margin: 0; padding-left: 1em; } +ul.projects.root { margin: 0; padding: 0; } +ul.projects ul { border-left: 3px solid #e0e0e0; } +ul.projects li { list-style-type:none; } +ul.projects li.root { margin-bottom: 1em; } +ul.projects li.child { margin-top: 1em;} +ul.projects div.root a.project { font-family: "Trebuchet MS", Verdana, sans-serif; font-weight: bold; font-size: 16px; margin: 0 0 10px 0; } +.my-project { padding-left: 18px; background: url(../images/fav.png) no-repeat 0 50%; } + +#tracker_project_ids ul { margin: 0; padding-left: 1em; } +#tracker_project_ids li { list-style-type:none; } + +ul.properties {padding:0; font-size: 0.9em; color: #777;} +ul.properties li {list-style-type:none;} +ul.properties li span {font-style:italic;} + +.total-hours { font-size: 110%; font-weight: bold; } +.total-hours span.hours-int { font-size: 120%; } + +.autoscroll {overflow-x: auto; padding:1px; margin-bottom: 1.2em;} +#user_firstname, #user_lastname, #user_mail, #my_account_form select { width: 90%; } + +.pagination {font-size: 90%} +p.pagination {margin-top:8px;} + +/***** Tabular forms ******/ +.tabular p{ +margin: 0; +padding: 5px 0 8px 0; +padding-left: 180px; /*width of left column containing the label elements*/ +height: 1%; +clear:left; +} + +html>body .tabular p {overflow:hidden;} + +.tabular label{ +font-weight: bold; +float: left; +text-align: right; +margin-left: -180px; /*width of left column*/ +width: 175px; /*width of labels. Should be smaller than left column to create some right +margin*/ +} + +.tabular label.floating{ +font-weight: normal; +margin-left: 0px; +text-align: left; +width: 270px; +} + +.tabular label.block{ +font-weight: normal; +margin-left: 0px; +text-align: left; +float: none; +display: block; +width: auto; +} + +input#time_entry_comments { width: 90%;} + +#preview fieldset {margin-top: 1em; background: url(../images/draft.png)} + +.tabular.settings p{ padding-left: 300px; } +.tabular.settings label{ margin-left: -300px; width: 295px; } + +.required {color: #bb0000;} +.summary {font-style: italic;} + +#attachments_fields input[type=text] {margin-left: 8px; } + +div.attachments { margin-top: 12px; } +div.attachments p { margin:4px 0 2px 0; } +div.attachments img { vertical-align: middle; } +div.attachments span.author { font-size: 0.9em; color: #888; } + +p.other-formats { text-align: right; font-size:0.9em; color: #666; } +.other-formats span + span:before { content: "| "; } + +a.atom { background: url(../images/feed.png) no-repeat 1px 50%; padding: 2px 0px 3px 16px; } + +/* Project members tab */ +div#tab-content-members .splitcontentleft, div#tab-content-memberships .splitcontentleft, div#tab-content-users .splitcontentleft { width: 64% } +div#tab-content-members .splitcontentright, div#tab-content-memberships .splitcontentright, div#tab-content-users .splitcontentright { width: 34% } +div#tab-content-members fieldset, div#tab-content-memberships fieldset, div#tab-content-users fieldset { padding:1em; margin-bottom: 1em; } +div#tab-content-members fieldset legend, div#tab-content-memberships fieldset legend, div#tab-content-users fieldset legend { font-weight: bold; } +div#tab-content-members fieldset label, div#tab-content-memberships fieldset label, div#tab-content-users fieldset label { display: block; } +div#tab-content-members fieldset div, div#tab-content-users fieldset div { max-height: 400px; overflow:auto; } + +table.members td.group { padding-left: 20px; background: url(../images/users.png) no-repeat 0% 0%; } + +* html div#tab-content-members fieldset div { height: 450px; } + +/***** Flash & error messages ****/ +#errorExplanation, div.flash, .nodata, .warning { + padding: 4px 4px 4px 30px; + margin-bottom: 12px; + font-size: 1.1em; + border: 2px solid; +} + +div.flash {margin-top: 8px;} + +div.flash.error, #errorExplanation { + background: url(../images/false.png) 8px 5px no-repeat; + background-color: #ffe3e3; + border-color: #dd0000; + color: #550000; +} + +div.flash.notice { + background: url(../images/true.png) 8px 5px no-repeat; + background-color: #dfffdf; + border-color: #9fcf9f; + color: #005f00; +} + +div.flash.warning { + background: url(../images/warning.png) 8px 5px no-repeat; + background-color: #FFEBC1; + border-color: #FDBF3B; + color: #A6750C; + text-align: left; +} + +.nodata, .warning { + text-align: center; + background-color: #FFEBC1; + border-color: #FDBF3B; + color: #A6750C; +} + +#errorExplanation ul { font-size: 0.9em;} +#errorExplanation h2, #errorExplanation p { display: none; } + +/***** Ajax indicator ******/ +#ajax-indicator { +position: absolute; /* fixed not supported by IE */ +background-color:#eee; +border: 1px solid #bbb; +top:35%; +left:40%; +width:20%; +font-weight:bold; +text-align:center; +padding:0.6em; +z-index:100; +filter:alpha(opacity=50); +opacity: 0.5; +} + +html>body #ajax-indicator { position: fixed; } + +#ajax-indicator span { +background-position: 0% 40%; +background-repeat: no-repeat; +background-image: url(../images/loading.gif); +padding-left: 26px; +vertical-align: bottom; +} + +/***** Calendar *****/ +table.cal {border-collapse: collapse; width: 100%; margin: 0px 0 6px 0;border: 1px solid #d7d7d7;} +table.cal thead th {width: 14%;} +table.cal tbody tr {height: 100px;} +table.cal th { background-color:#EEEEEE; padding: 4px; } +table.cal td {border: 1px solid #d7d7d7; vertical-align: top; font-size: 0.9em;} +table.cal td p.day-num {font-size: 1.1em; text-align:right;} +table.cal td.odd p.day-num {color: #bbb;} +table.cal td.today {background:#ffffdd;} +table.cal td.today p.day-num {font-weight: bold;} + +/***** Tooltips ******/ +.tooltip{position:relative;z-index:24;} +.tooltip:hover{z-index:25;color:#000;} +.tooltip span.tip{display: none; text-align:left;} + +div.tooltip:hover span.tip{ +display:block; +position:absolute; +top:12px; left:24px; width:270px; +border:1px solid #555; +background-color:#fff; +padding: 4px; +font-size: 0.8em; +color:#505050; +} + +/***** Progress bar *****/ +table.progress { + border: 1px solid #D7D7D7; + border-collapse: collapse; + border-spacing: 0pt; + empty-cells: show; + text-align: center; + float:left; + margin: 1px 6px 1px 0px; +} + +table.progress td { height: 0.9em; } +table.progress td.todo { border:1px solid #aaa } +table.progress td.closed { background: #648749 none repeat scroll 0%; } +table.progress td.done { background: #DEF0DE none repeat scroll 0%; } +table.progress td.open { background: #FFF none repeat scroll 0%; } +p.pourcent {font-size: 80%;} +p.progress-info {clear: left; font-style: italic; font-size: 80%;} + +/***** Tabs *****/ +#content .tabs {height: 2.6em; border-bottom: 1px solid #bbbbbb; margin-bottom:1.2em; position:relative;} +#content .tabs ul {margin:0; position:absolute; bottom:-2px; padding-left:1em;} +#content .tabs>ul { bottom:-1px; } /* others */ +#content .tabs ul li { +float:left; +list-style-type:none; +white-space:nowrap; +margin-right:8px; +background:#fff; +} +#content .tabs ul li a{ +display:block; +font-size: 0.9em; +text-decoration:none; +line-height:1.3em; +padding:4px 6px 4px 6px; +border: 1px solid #ccc; +border-bottom: 1px solid #bbbbbb; +background-color: #eeeeee; +color:#777; +font-weight:bold; +} + +#content .tabs ul li a:hover { +background-color: #ffffdd; +text-decoration:none; +} + +#content .tabs ul li a.selected { +background-color: #fff; +border: 1px solid #bbbbbb; +border-bottom: 1px solid #fff; +} + +#content .tabs ul li a.selected:hover { +background-color: #fff; +} + +/***** Auto-complete *****/ +div.autocomplete { + position:absolute; + width:250px; + background-color:white; + margin:0; + padding:0; +} +div.autocomplete ul { + list-style-type:none; + margin:0; + padding:0; +} +div.autocomplete ul li.selected { background-color: #ffb;} +div.autocomplete ul li { + list-style-type:none; + display:block; + margin:0; + padding:2px; + cursor:pointer; + font-size: 90%; + border-bottom: 1px solid #ccc; + border-left: 1px solid #ccc; + border-right: 1px solid #ccc; +} +div.autocomplete ul li span.informal { + font-size: 80%; + color: #aaa; +} + +/***** Diff *****/ +.diff_out { background: #fcc; } +.diff_in { background: #cfc; } + +/***** Wiki *****/ +div.wiki table { + border: 1px solid #505050; + border-collapse: collapse; + margin-bottom: 1em; +} + +div.wiki table, div.wiki td, div.wiki th { + border: 1px solid #bbb; + padding: 4px; +} + +div.wiki .external { + background-position: 0% 60%; + background-repeat: no-repeat; + padding-left: 12px; + background-image: url(external.png); +} + +div.wiki a.new { + color: #b73535; +} + +div.wiki pre { + margin: 1em 1em 1em 1.6em; + padding: 2px; + background-color: #fafafa; + border: 1px solid #dadada; + width:95%; + overflow-x: auto; +} + +div.wiki ul.toc { + background-color: #ffffdd; + border: 1px solid #e4e4e4; + padding: 4px; + line-height: 1.2em; + margin-bottom: 12px; + margin-right: 12px; + margin-left: 0; + display: table +} +* html div.wiki ul.toc { width: 50%; } /* IE6 doesn't autosize div */ + +div.wiki ul.toc.right { float: right; margin-left: 12px; margin-right: 0; width: auto; } +div.wiki ul.toc.left { float: left; margin-right: 12px; margin-left: 0; width: auto; } +div.wiki ul.toc li { list-style-type:none;} +div.wiki ul.toc li.heading2 { margin-left: 6px; } +div.wiki ul.toc li.heading3 { margin-left: 12px; font-size: 0.8em; } + +div.wiki ul.toc a { + font-size: 0.9em; + font-weight: normal; + text-decoration: none; + color: #606060; +} +div.wiki ul.toc a:hover { color: #c61a1a; text-decoration: underline;} + +a.wiki-anchor { display: none; margin-left: 6px; text-decoration: none; } +a.wiki-anchor:hover { color: #aaa !important; text-decoration: none; } +h1:hover a.wiki-anchor, h2:hover a.wiki-anchor, h3:hover a.wiki-anchor { display: inline; color: #ddd; } + +/***** My page layout *****/ +.block-receiver { +border:1px dashed #c0c0c0; +margin-bottom: 20px; +padding: 15px 0 15px 0; +} + +.mypage-box { +margin:0 0 20px 0; +color:#505050; +line-height:1.5em; +} + +.handle { +cursor: move; +} + +a.close-icon { +display:block; +margin-top:3px; +overflow:hidden; +width:12px; +height:12px; +background-repeat: no-repeat; +cursor:pointer; +background-image:url('../images/close.png'); +} + +a.close-icon:hover { +background-image:url('../images/close_hl.png'); +} + +/***** Gantt chart *****/ +.gantt_hdr { + position:absolute; + top:0; + height:16px; + border-top: 1px solid #c0c0c0; + border-bottom: 1px solid #c0c0c0; + border-right: 1px solid #c0c0c0; + text-align: center; + overflow: hidden; +} + +.task { + position: absolute; + height:8px; + font-size:0.8em; + color:#888; + padding:0; + margin:0; + line-height:0.8em; +} + +.task_late { background:#f66 url(../images/task_late.png); border: 1px solid #f66; } +.task_done { background:#66f url(../images/task_done.png); border: 1px solid #66f; } +.task_todo { background:#aaa url(../images/task_todo.png); border: 1px solid #aaa; } +.milestone { background-image:url(../images/milestone.png); background-repeat: no-repeat; border: 0; } + +/***** Icons *****/ +.icon { +background-position: 0% 40%; +background-repeat: no-repeat; +padding-left: 20px; +padding-top: 2px; +padding-bottom: 3px; +} + +.icon22 { +background-position: 0% 40%; +background-repeat: no-repeat; +padding-left: 26px; +line-height: 22px; +vertical-align: middle; +} + +.icon-add { background-image: url(../images/add.png); } +.icon-edit { background-image: url(../images/edit.png); } +.icon-copy { background-image: url(../images/copy.png); } +.icon-del { background-image: url(../images/delete.png); } +.icon-move { background-image: url(../images/move.png); } +.icon-save { background-image: url(../images/save.png); } +.icon-cancel { background-image: url(../images/cancel.png); } +.icon-file { background-image: url(../images/file.png); } +.icon-folder { background-image: url(../images/folder.png); } +.open .icon-folder { background-image: url(../images/folder_open.png); } +.icon-package { background-image: url(../images/package.png); } +.icon-home { background-image: url(../images/home.png); } +.icon-user { background-image: url(../images/user.png); } +.icon-mypage { background-image: url(../images/user_page.png); } +.icon-admin { background-image: url(../images/admin.png); } +.icon-projects { background-image: url(projects.png); } +.icon-help { background-image: url(../images/help.png); } +.icon-attachment { background-image: url(attachment.png); } +.icon-index { background-image: url(../images/index.png); } +.icon-history { background-image: url(history.png); } +.icon-time { background-image: url(../images/time.png); } +.icon-time-add { background-image: url(../images/time_add.png); } +.icon-stats { background-image: url(../images/stats.png); } +.icon-warning { background-image: url(../images/warning.png); } +.icon-fav { background-image: url(../images/fav.png); } +.icon-fav-off { background-image: url(../images/fav_off.png); } +.icon-reload { background-image: url(../images/reload.png); } +.icon-lock { background-image: url(../images/locked.png); } +.icon-unlock { background-image: url(../images/unlock.png); } +.icon-checked { background-image: url(../images/true.png); } +.icon-details { background-image: url(../images/zoom_in.png); } +.icon-report { background-image: url(../images/report.png); } +.icon-comment { background-image: url(../images/comment.png); } + +.icon-file { background-image: url(../images/files/default.png); } +.icon-file.text-plain { background-image: url(../images/files/text.png); } +.icon-file.text-x-c { background-image: url(../images/files/c.png); } +.icon-file.text-x-csharp { background-image: url(../images/files/csharp.png); } +.icon-file.text-x-php { background-image: url(../images/files/php.png); } +.icon-file.text-x-ruby { background-image: url(../images/files/ruby.png); } +.icon-file.text-xml { background-image: url(../images/files/xml.png); } +.icon-file.image-gif { background-image: url(../images/files/image.png); } +.icon-file.image-jpeg { background-image: url(../images/files/image.png); } +.icon-file.image-png { background-image: url(../images/files/image.png); } +.icon-file.image-tiff { background-image: url(../images/files/image.png); } +.icon-file.application-pdf { background-image: url(../images/files/pdf.png); } +.icon-file.application-zip { background-image: url(../images/files/zip.png); } +.icon-file.application-x-gzip { background-image: url(../images/files/zip.png); } + +.icon22-projects { background-image: url(../images/22x22/projects.png); } +.icon22-users { background-image: url(../images/22x22/users.png); } +.icon22-groups { background-image: url(../images/22x22/groups.png); } +.icon22-tracker { background-image: url(../images/22x22/tracker.png); } +.icon22-role { background-image: url(../images/22x22/role.png); } +.icon22-workflow { background-image: url(../images/22x22/workflow.png); } +.icon22-options { background-image: url(../images/22x22/options.png); } +.icon22-notifications { background-image: url(../images/22x22/notifications.png); } +.icon22-system_notification { background-image: url(../images/22x22/system_notification.png); } +.icon22-authent { background-image: url(../images/22x22/authent.png); } +.icon22-info { background-image: url(../images/22x22/info.png); } +.icon22-comment { background-image: url(../images/22x22/comment.png); } +.icon22-package { background-image: url(../images/22x22/package.png); } +.icon22-settings { background-image: url(../images/22x22/settings.png); } +.icon22-plugin { background-image: url(../images/22x22/plugin.png); } + +img.gravatar { + padding: 2px; + border: solid 1px #d5d5d5; + background: #fff; +} + +div.issue img.gravatar { + float: right; + margin: 0 0 0 1em; + padding: 5px; +} + +div.issue table img.gravatar { + height: 14px; + width: 14px; + padding: 2px; + float: left; + margin: 0 0.5em 0 0; +} + +#history img.gravatar { + padding: 3px; + margin: 0 1.5em 1em 0; + float: left; +} + +td.username img.gravatar { + float: left; + margin: 0 1em 0 0; +} + +#activity dt img.gravatar { + float: left; + margin: 0 1em 1em 0; +} + +#activity dt, +.journal { + clear: left; +} + +.gravatar-margin { + margin-left: 40px; +} + +h2 img { vertical-align:middle; } + + +/***** Media print specific styles *****/ +@media print { + #top-menu, #header, #main-menu, #sidebar, #footer, .contextual, .other-formats { display:none; } + #main { background: #fff; } + #content { width: 99%; margin: 0; padding: 0; border: 0; background: #fff; overflow: visible !important;} + #wiki_add_attachment { display:none; } +} + +@import "ui-lightness/jquery-ui-1.7.2.custom.css"; diff --git a/docs/application.js b/docs/application.js new file mode 100644 index 0000000..57419d0 --- /dev/null +++ b/docs/application.js @@ -0,0 +1,209 @@ +/* redMine - project management software + Copyright (C) 2006-2008 Jean-Philippe Lang */ + +function checkAll (id, checked) { + var els = Element.descendants(id); + for (var i = 0; i < els.length; i++) { + if (els[i].disabled==false) { + els[i].checked = checked; + } + } +} + +function toggleCheckboxesBySelector(selector) { + boxes = $$(selector); + var all_checked = true; + for (i = 0; i < boxes.length; i++) { if (boxes[i].checked == false) { all_checked = false; } } + for (i = 0; i < boxes.length; i++) { boxes[i].checked = !all_checked; } +} + +function showAndScrollTo(id, focus) { + Element.show(id); + if (focus!=null) { Form.Element.focus(focus); } + Element.scrollTo(id); +} + +function toggleRowGroup(el) { + var tr = Element.up(el, 'tr'); + var n = Element.next(tr); + tr.toggleClassName('open'); + while (n != undefined && !n.hasClassName('group')) { + Element.toggle(n); + n = Element.next(n); + } +} + +function toggleFieldset(el) { + var fieldset = Element.up(el, 'fieldset'); + fieldset.toggleClassName('collapsed'); + Effect.toggle(fieldset.down('div'), 'slide', {duration:0.2}); +} + +var fileFieldCount = 1; + +function addFileField() { + if (fileFieldCount >= 10) return false + fileFieldCount++; + var f = document.createElement("input"); + f.type = "file"; + f.name = "attachments[" + fileFieldCount + "][file]"; + f.size = 30; + var d = document.createElement("input"); + d.type = "text"; + d.name = "attachments[" + fileFieldCount + "][description]"; + d.size = 60; + + p = document.getElementById("attachments_fields"); + p.appendChild(document.createElement("br")); + p.appendChild(f); + p.appendChild(d); +} + +function showTab(name) { + var f = $$('div#content .tab-content'); + for(var i=0; i0) { + lis[i-1].show(); + } +} + +function displayTabsButtons() { + var lis; + var tabsWidth = 0; + var i; + $$('div.tabs').each(function(el) { + lis = el.down('ul').childElements(); + for (i=0; i 0) { + Element.show('ajax-indicator'); + } + }, + onComplete: function(){ + if ($('ajax-indicator') && Ajax.activeRequestCount == 0) { + Element.hide('ajax-indicator'); + } + } +}); diff --git a/docs/attachment.png b/docs/attachment.png new file mode 100644 index 0000000..b7ce3c4 Binary files /dev/null and b/docs/attachment.png differ diff --git a/docs/balloons.png b/docs/balloons.png new file mode 100644 index 0000000..89efe42 Binary files /dev/null and b/docs/balloons.png differ diff --git a/docs/clipline-small.png b/docs/clipline-small.png new file mode 100644 index 0000000..02d0734 Binary files /dev/null and b/docs/clipline-small.png differ diff --git a/docs/controls.js b/docs/controls.js new file mode 100644 index 0000000..617d0d3 --- /dev/null +++ b/docs/controls.js @@ -0,0 +1,963 @@ +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2008 Ivan Krstic (http://blogs.law.harvard.edu/ivan) +// (c) 2005-2008 Jon Tirsen (http://www.tirsen.com) +// Contributors: +// Richard Livsey +// Rahul Bhargava +// Rob Wills +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// Autocompleter.Base handles all the autocompletion functionality +// that's independent of the data source for autocompletion. This +// includes drawing the autocompletion menu, observing keyboard +// and mouse events, and similar. +// +// Specific autocompleters need to provide, at the very least, +// a getUpdatedChoices function that will be invoked every time +// the text inside the monitored textbox changes. This method +// should get the text for which to provide autocompletion by +// invoking this.getToken(), NOT by directly accessing +// this.element.value. This is to allow incremental tokenized +// autocompletion. Specific auto-completion logic (AJAX, etc) +// belongs in getUpdatedChoices. +// +// Tokenized incremental autocompletion is enabled automatically +// when an autocompleter is instantiated with the 'tokens' option +// in the options parameter, e.g.: +// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' }); +// will incrementally autocomplete with a comma as the token. +// Additionally, ',' in the above example can be replaced with +// a token array, e.g. { tokens: [',', '\n'] } which +// enables autocompletion on multiple tokens. This is most +// useful when one of the tokens is \n (a newline), as it +// allows smart autocompletion after linebreaks. + +if(typeof Effect == 'undefined') + throw("controls.js requires including script.aculo.us' effects.js library"); + +var Autocompleter = { }; +Autocompleter.Base = Class.create({ + baseInitialize: function(element, update, options) { + element = $(element); + this.element = element; + this.update = $(update); + this.hasFocus = false; + this.changed = false; + this.active = false; + this.index = 0; + this.entryCount = 0; + this.oldElementValue = this.element.value; + + if(this.setOptions) + this.setOptions(options); + else + this.options = options || { }; + + this.options.paramName = this.options.paramName || this.element.name; + this.options.tokens = this.options.tokens || []; + this.options.frequency = this.options.frequency || 0.4; + this.options.minChars = this.options.minChars || 1; + this.options.onShow = this.options.onShow || + function(element, update){ + if(!update.style.position || update.style.position=='absolute') { + update.style.position = 'absolute'; + Position.clone(element, update, { + setHeight: false, + offsetTop: element.offsetHeight + }); + } + Effect.Appear(update,{duration:0.15}); + }; + this.options.onHide = this.options.onHide || + function(element, update){ new Effect.Fade(update,{duration:0.15}) }; + + if(typeof(this.options.tokens) == 'string') + this.options.tokens = new Array(this.options.tokens); + // Force carriage returns as token delimiters anyway + if (!this.options.tokens.include('\n')) + this.options.tokens.push('\n'); + + this.observer = null; + + this.element.setAttribute('autocomplete','off'); + + Element.hide(this.update); + + Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this)); + Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this)); + }, + + show: function() { + if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update); + if(!this.iefix && + (Prototype.Browser.IE) && + (Element.getStyle(this.update, 'position')=='absolute')) { + new Insertion.After(this.update, + ''); + this.iefix = $(this.update.id+'_iefix'); + } + if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50); + }, + + fixIEOverlapping: function() { + Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)}); + this.iefix.style.zIndex = 1; + this.update.style.zIndex = 2; + Element.show(this.iefix); + }, + + hide: function() { + this.stopIndicator(); + if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update); + if(this.iefix) Element.hide(this.iefix); + }, + + startIndicator: function() { + if(this.options.indicator) Element.show(this.options.indicator); + }, + + stopIndicator: function() { + if(this.options.indicator) Element.hide(this.options.indicator); + }, + + onKeyPress: function(event) { + if(this.active) + switch(event.keyCode) { + case Event.KEY_TAB: + case Event.KEY_RETURN: + this.selectEntry(); + Event.stop(event); + case Event.KEY_ESC: + this.hide(); + this.active = false; + Event.stop(event); + return; + case Event.KEY_LEFT: + case Event.KEY_RIGHT: + return; + case Event.KEY_UP: + this.markPrevious(); + this.render(); + Event.stop(event); + return; + case Event.KEY_DOWN: + this.markNext(); + this.render(); + Event.stop(event); + return; + } + else + if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN || + (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return; + + this.changed = true; + this.hasFocus = true; + + if(this.observer) clearTimeout(this.observer); + this.observer = + setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000); + }, + + activate: function() { + this.changed = false; + this.hasFocus = true; + this.getUpdatedChoices(); + }, + + onHover: function(event) { + var element = Event.findElement(event, 'LI'); + if(this.index != element.autocompleteIndex) + { + this.index = element.autocompleteIndex; + this.render(); + } + Event.stop(event); + }, + + onClick: function(event) { + var element = Event.findElement(event, 'LI'); + this.index = element.autocompleteIndex; + this.selectEntry(); + this.hide(); + }, + + onBlur: function(event) { + // needed to make click events working + setTimeout(this.hide.bind(this), 250); + this.hasFocus = false; + this.active = false; + }, + + render: function() { + if(this.entryCount > 0) { + for (var i = 0; i < this.entryCount; i++) + this.index==i ? + Element.addClassName(this.getEntry(i),"selected") : + Element.removeClassName(this.getEntry(i),"selected"); + if(this.hasFocus) { + this.show(); + this.active = true; + } + } else { + this.active = false; + this.hide(); + } + }, + + markPrevious: function() { + if(this.index > 0) this.index--; + else this.index = this.entryCount-1; + this.getEntry(this.index).scrollIntoView(true); + }, + + markNext: function() { + if(this.index < this.entryCount-1) this.index++; + else this.index = 0; + this.getEntry(this.index).scrollIntoView(false); + }, + + getEntry: function(index) { + return this.update.firstChild.childNodes[index]; + }, + + getCurrentEntry: function() { + return this.getEntry(this.index); + }, + + selectEntry: function() { + this.active = false; + this.updateElement(this.getCurrentEntry()); + }, + + updateElement: function(selectedElement) { + if (this.options.updateElement) { + this.options.updateElement(selectedElement); + return; + } + var value = ''; + if (this.options.select) { + var nodes = $(selectedElement).select('.' + this.options.select) || []; + if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select); + } else + value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal'); + + var bounds = this.getTokenBounds(); + if (bounds[0] != -1) { + var newValue = this.element.value.substr(0, bounds[0]); + var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/); + if (whitespace) + newValue += whitespace[0]; + this.element.value = newValue + value + this.element.value.substr(bounds[1]); + } else { + this.element.value = value; + } + this.oldElementValue = this.element.value; + this.element.focus(); + + if (this.options.afterUpdateElement) + this.options.afterUpdateElement(this.element, selectedElement); + }, + + updateChoices: function(choices) { + if(!this.changed && this.hasFocus) { + this.update.innerHTML = choices; + Element.cleanWhitespace(this.update); + Element.cleanWhitespace(this.update.down()); + + if(this.update.firstChild && this.update.down().childNodes) { + this.entryCount = + this.update.down().childNodes.length; + for (var i = 0; i < this.entryCount; i++) { + var entry = this.getEntry(i); + entry.autocompleteIndex = i; + this.addObservers(entry); + } + } else { + this.entryCount = 0; + } + + this.stopIndicator(); + this.index = 0; + + if(this.entryCount==1 && this.options.autoSelect) { + this.selectEntry(); + this.hide(); + } else { + this.render(); + } + } + }, + + addObservers: function(element) { + Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this)); + Event.observe(element, "click", this.onClick.bindAsEventListener(this)); + }, + + onObserverEvent: function() { + this.changed = false; + this.tokenBounds = null; + if(this.getToken().length>=this.options.minChars) { + this.getUpdatedChoices(); + } else { + this.active = false; + this.hide(); + } + this.oldElementValue = this.element.value; + }, + + getToken: function() { + var bounds = this.getTokenBounds(); + return this.element.value.substring(bounds[0], bounds[1]).strip(); + }, + + getTokenBounds: function() { + if (null != this.tokenBounds) return this.tokenBounds; + var value = this.element.value; + if (value.strip().empty()) return [-1, 0]; + var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue); + var offset = (diff == this.oldElementValue.length ? 1 : 0); + var prevTokenPos = -1, nextTokenPos = value.length; + var tp; + for (var index = 0, l = this.options.tokens.length; index < l; ++index) { + tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1); + if (tp > prevTokenPos) prevTokenPos = tp; + tp = value.indexOf(this.options.tokens[index], diff + offset); + if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp; + } + return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]); + } +}); + +Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) { + var boundary = Math.min(newS.length, oldS.length); + for (var index = 0; index < boundary; ++index) + if (newS[index] != oldS[index]) + return index; + return boundary; +}; + +Ajax.Autocompleter = Class.create(Autocompleter.Base, { + initialize: function(element, update, url, options) { + this.baseInitialize(element, update, options); + this.options.asynchronous = true; + this.options.onComplete = this.onComplete.bind(this); + this.options.defaultParams = this.options.parameters || null; + this.url = url; + }, + + getUpdatedChoices: function() { + this.startIndicator(); + + var entry = encodeURIComponent(this.options.paramName) + '=' + + encodeURIComponent(this.getToken()); + + this.options.parameters = this.options.callback ? + this.options.callback(this.element, entry) : entry; + + if(this.options.defaultParams) + this.options.parameters += '&' + this.options.defaultParams; + + new Ajax.Request(this.url, this.options); + }, + + onComplete: function(request) { + this.updateChoices(request.responseText); + } +}); + +// The local array autocompleter. Used when you'd prefer to +// inject an array of autocompletion options into the page, rather +// than sending out Ajax queries, which can be quite slow sometimes. +// +// The constructor takes four parameters. The first two are, as usual, +// the id of the monitored textbox, and id of the autocompletion menu. +// The third is the array you want to autocomplete from, and the fourth +// is the options block. +// +// Extra local autocompletion options: +// - choices - How many autocompletion choices to offer +// +// - partialSearch - If false, the autocompleter will match entered +// text only at the beginning of strings in the +// autocomplete array. Defaults to true, which will +// match text at the beginning of any *word* in the +// strings in the autocomplete array. If you want to +// search anywhere in the string, additionally set +// the option fullSearch to true (default: off). +// +// - fullSsearch - Search anywhere in autocomplete array strings. +// +// - partialChars - How many characters to enter before triggering +// a partial match (unlike minChars, which defines +// how many characters are required to do any match +// at all). Defaults to 2. +// +// - ignoreCase - Whether to ignore case when autocompleting. +// Defaults to true. +// +// It's possible to pass in a custom function as the 'selector' +// option, if you prefer to write your own autocompletion logic. +// In that case, the other options above will not apply unless +// you support them. + +Autocompleter.Local = Class.create(Autocompleter.Base, { + initialize: function(element, update, array, options) { + this.baseInitialize(element, update, options); + this.options.array = array; + }, + + getUpdatedChoices: function() { + this.updateChoices(this.options.selector(this)); + }, + + setOptions: function(options) { + this.options = Object.extend({ + choices: 10, + partialSearch: true, + partialChars: 2, + ignoreCase: true, + fullSearch: false, + selector: function(instance) { + var ret = []; // Beginning matches + var partial = []; // Inside matches + var entry = instance.getToken(); + var count = 0; + + for (var i = 0; i < instance.options.array.length && + ret.length < instance.options.choices ; i++) { + + var elem = instance.options.array[i]; + var foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase()) : + elem.indexOf(entry); + + while (foundPos != -1) { + if (foundPos == 0 && elem.length != entry.length) { + ret.push("
  • " + elem.substr(0, entry.length) + "" + + elem.substr(entry.length) + "
  • "); + break; + } else if (entry.length >= instance.options.partialChars && + instance.options.partialSearch && foundPos != -1) { + if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) { + partial.push("
  • " + elem.substr(0, foundPos) + "" + + elem.substr(foundPos, entry.length) + "" + elem.substr( + foundPos + entry.length) + "
  • "); + break; + } + } + + foundPos = instance.options.ignoreCase ? + elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) : + elem.indexOf(entry, foundPos + 1); + + } + } + if (partial.length) + ret = ret.concat(partial.slice(0, instance.options.choices - ret.length)); + return "
      " + ret.join('') + "
    "; + } + }, options || { }); + } +}); + +// AJAX in-place editor and collection editor +// Full rewrite by Christophe Porteneuve (April 2007). + +// Use this if you notice weird scrolling problems on some browsers, +// the DOM might be a bit confused when this gets called so do this +// waits 1 ms (with setTimeout) until it does the activation +Field.scrollFreeActivate = function(field) { + setTimeout(function() { + Field.activate(field); + }, 1); +}; + +Ajax.InPlaceEditor = Class.create({ + initialize: function(element, url, options) { + this.url = url; + this.element = element = $(element); + this.prepareOptions(); + this._controls = { }; + arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!! + Object.extend(this.options, options || { }); + if (!this.options.formId && this.element.id) { + this.options.formId = this.element.id + '-inplaceeditor'; + if ($(this.options.formId)) + this.options.formId = ''; + } + if (this.options.externalControl) + this.options.externalControl = $(this.options.externalControl); + if (!this.options.externalControl) + this.options.externalControlOnly = false; + this._originalBackground = this.element.getStyle('background-color') || 'transparent'; + this.element.title = this.options.clickToEditText; + this._boundCancelHandler = this.handleFormCancellation.bind(this); + this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this); + this._boundFailureHandler = this.handleAJAXFailure.bind(this); + this._boundSubmitHandler = this.handleFormSubmission.bind(this); + this._boundWrapperHandler = this.wrapUp.bind(this); + this.registerListeners(); + }, + checkForEscapeOrReturn: function(e) { + if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return; + if (Event.KEY_ESC == e.keyCode) + this.handleFormCancellation(e); + else if (Event.KEY_RETURN == e.keyCode) + this.handleFormSubmission(e); + }, + createControl: function(mode, handler, extraClasses) { + var control = this.options[mode + 'Control']; + var text = this.options[mode + 'Text']; + if ('button' == control) { + var btn = document.createElement('input'); + btn.type = 'submit'; + btn.value = text; + btn.className = 'editor_' + mode + '_button'; + if ('cancel' == mode) + btn.onclick = this._boundCancelHandler; + this._form.appendChild(btn); + this._controls[mode] = btn; + } else if ('link' == control) { + var link = document.createElement('a'); + link.href = '#'; + link.appendChild(document.createTextNode(text)); + link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler; + link.className = 'editor_' + mode + '_link'; + if (extraClasses) + link.className += ' ' + extraClasses; + this._form.appendChild(link); + this._controls[mode] = link; + } + }, + createEditField: function() { + var text = (this.options.loadTextURL ? this.options.loadingText : this.getText()); + var fld; + if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) { + fld = document.createElement('input'); + fld.type = 'text'; + var size = this.options.size || this.options.cols || 0; + if (0 < size) fld.size = size; + } else { + fld = document.createElement('textarea'); + fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows); + fld.cols = this.options.cols || 40; + } + fld.name = this.options.paramName; + fld.value = text; // No HTML breaks conversion anymore + fld.className = 'editor_field'; + if (this.options.submitOnBlur) + fld.onblur = this._boundSubmitHandler; + this._controls.editor = fld; + if (this.options.loadTextURL) + this.loadExternalText(); + this._form.appendChild(this._controls.editor); + }, + createForm: function() { + var ipe = this; + function addText(mode, condition) { + var text = ipe.options['text' + mode + 'Controls']; + if (!text || condition === false) return; + ipe._form.appendChild(document.createTextNode(text)); + }; + this._form = $(document.createElement('form')); + this._form.id = this.options.formId; + this._form.addClassName(this.options.formClassName); + this._form.onsubmit = this._boundSubmitHandler; + this.createEditField(); + if ('textarea' == this._controls.editor.tagName.toLowerCase()) + this._form.appendChild(document.createElement('br')); + if (this.options.onFormCustomization) + this.options.onFormCustomization(this, this._form); + addText('Before', this.options.okControl || this.options.cancelControl); + this.createControl('ok', this._boundSubmitHandler); + addText('Between', this.options.okControl && this.options.cancelControl); + this.createControl('cancel', this._boundCancelHandler, 'editor_cancel'); + addText('After', this.options.okControl || this.options.cancelControl); + }, + destroy: function() { + if (this._oldInnerHTML) + this.element.innerHTML = this._oldInnerHTML; + this.leaveEditMode(); + this.unregisterListeners(); + }, + enterEditMode: function(e) { + if (this._saving || this._editing) return; + this._editing = true; + this.triggerCallback('onEnterEditMode'); + if (this.options.externalControl) + this.options.externalControl.hide(); + this.element.hide(); + this.createForm(); + this.element.parentNode.insertBefore(this._form, this.element); + if (!this.options.loadTextURL) + this.postProcessEditField(); + if (e) Event.stop(e); + }, + enterHover: function(e) { + if (this.options.hoverClassName) + this.element.addClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onEnterHover'); + }, + getText: function() { + return this.element.innerHTML.unescapeHTML(); + }, + handleAJAXFailure: function(transport) { + this.triggerCallback('onFailure', transport); + if (this._oldInnerHTML) { + this.element.innerHTML = this._oldInnerHTML; + this._oldInnerHTML = null; + } + }, + handleFormCancellation: function(e) { + this.wrapUp(); + if (e) Event.stop(e); + }, + handleFormSubmission: function(e) { + var form = this._form; + var value = $F(this._controls.editor); + this.prepareSubmission(); + var params = this.options.callback(form, value) || ''; + if (Object.isString(params)) + params = params.toQueryParams(); + params.editorId = this.element.id; + if (this.options.htmlResponse) { + var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Updater({ success: this.element }, this.url, options); + } else { + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: params, + onComplete: this._boundWrapperHandler, + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.url, options); + } + if (e) Event.stop(e); + }, + leaveEditMode: function() { + this.element.removeClassName(this.options.savingClassName); + this.removeForm(); + this.leaveHover(); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + if (this.options.externalControl) + this.options.externalControl.show(); + this._saving = false; + this._editing = false; + this._oldInnerHTML = null; + this.triggerCallback('onLeaveEditMode'); + }, + leaveHover: function(e) { + if (this.options.hoverClassName) + this.element.removeClassName(this.options.hoverClassName); + if (this._saving) return; + this.triggerCallback('onLeaveHover'); + }, + loadExternalText: function() { + this._form.addClassName(this.options.loadingClassName); + this._controls.editor.disabled = true; + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._form.removeClassName(this.options.loadingClassName); + var text = transport.responseText; + if (this.options.stripLoadedTextTags) + text = text.stripTags(); + this._controls.editor.value = text; + this._controls.editor.disabled = false; + this.postProcessEditField(); + }.bind(this), + onFailure: this._boundFailureHandler + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + postProcessEditField: function() { + var fpc = this.options.fieldPostCreation; + if (fpc) + $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate'](); + }, + prepareOptions: function() { + this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions); + Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks); + [this._extraDefaultOptions].flatten().compact().each(function(defs) { + Object.extend(this.options, defs); + }.bind(this)); + }, + prepareSubmission: function() { + this._saving = true; + this.removeForm(); + this.leaveHover(); + this.showSaving(); + }, + registerListeners: function() { + this._listeners = { }; + var listener; + $H(Ajax.InPlaceEditor.Listeners).each(function(pair) { + listener = this[pair.value].bind(this); + this._listeners[pair.key] = listener; + if (!this.options.externalControlOnly) + this.element.observe(pair.key, listener); + if (this.options.externalControl) + this.options.externalControl.observe(pair.key, listener); + }.bind(this)); + }, + removeForm: function() { + if (!this._form) return; + this._form.remove(); + this._form = null; + this._controls = { }; + }, + showSaving: function() { + this._oldInnerHTML = this.element.innerHTML; + this.element.innerHTML = this.options.savingText; + this.element.addClassName(this.options.savingClassName); + this.element.style.backgroundColor = this._originalBackground; + this.element.show(); + }, + triggerCallback: function(cbName, arg) { + if ('function' == typeof this.options[cbName]) { + this.options[cbName](this, arg); + } + }, + unregisterListeners: function() { + $H(this._listeners).each(function(pair) { + if (!this.options.externalControlOnly) + this.element.stopObserving(pair.key, pair.value); + if (this.options.externalControl) + this.options.externalControl.stopObserving(pair.key, pair.value); + }.bind(this)); + }, + wrapUp: function(transport) { + this.leaveEditMode(); + // Can't use triggerCallback due to backward compatibility: requires + // binding + direct element + this._boundComplete(transport, this.element); + } +}); + +Object.extend(Ajax.InPlaceEditor.prototype, { + dispose: Ajax.InPlaceEditor.prototype.destroy +}); + +Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, { + initialize: function($super, element, url, options) { + this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions; + $super(element, url, options); + }, + + createEditField: function() { + var list = document.createElement('select'); + list.name = this.options.paramName; + list.size = 1; + this._controls.editor = list; + this._collection = this.options.collection || []; + if (this.options.loadCollectionURL) + this.loadCollection(); + else + this.checkForExternalText(); + this._form.appendChild(this._controls.editor); + }, + + loadCollection: function() { + this._form.addClassName(this.options.loadingClassName); + this.showLoadingText(this.options.loadingCollectionText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + var js = transport.responseText.strip(); + if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check + throw('Server returned an invalid collection representation.'); + this._collection = eval(js); + this.checkForExternalText(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadCollectionURL, options); + }, + + showLoadingText: function(text) { + this._controls.editor.disabled = true; + var tempOption = this._controls.editor.firstChild; + if (!tempOption) { + tempOption = document.createElement('option'); + tempOption.value = ''; + this._controls.editor.appendChild(tempOption); + tempOption.selected = true; + } + tempOption.update((text || '').stripScripts().stripTags()); + }, + + checkForExternalText: function() { + this._text = this.getText(); + if (this.options.loadTextURL) + this.loadExternalText(); + else + this.buildOptionList(); + }, + + loadExternalText: function() { + this.showLoadingText(this.options.loadingText); + var options = Object.extend({ method: 'get' }, this.options.ajaxOptions); + Object.extend(options, { + parameters: 'editorId=' + encodeURIComponent(this.element.id), + onComplete: Prototype.emptyFunction, + onSuccess: function(transport) { + this._text = transport.responseText.strip(); + this.buildOptionList(); + }.bind(this), + onFailure: this.onFailure + }); + new Ajax.Request(this.options.loadTextURL, options); + }, + + buildOptionList: function() { + this._form.removeClassName(this.options.loadingClassName); + this._collection = this._collection.map(function(entry) { + return 2 === entry.length ? entry : [entry, entry].flatten(); + }); + var marker = ('value' in this.options) ? this.options.value : this._text; + var textFound = this._collection.any(function(entry) { + return entry[0] == marker; + }.bind(this)); + this._controls.editor.update(''); + var option; + this._collection.each(function(entry, index) { + option = document.createElement('option'); + option.value = entry[0]; + option.selected = textFound ? entry[0] == marker : 0 == index; + option.appendChild(document.createTextNode(entry[1])); + this._controls.editor.appendChild(option); + }.bind(this)); + this._controls.editor.disabled = false; + Field.scrollFreeActivate(this._controls.editor); + } +}); + +//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! **** +//**** This only exists for a while, in order to let **** +//**** users adapt to the new API. Read up on the new **** +//**** API and convert your code to it ASAP! **** + +Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) { + if (!options) return; + function fallback(name, expr) { + if (name in options || expr === undefined) return; + options[name] = expr; + }; + fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' : + options.cancelLink == options.cancelButton == false ? false : undefined))); + fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' : + options.okLink == options.okButton == false ? false : undefined))); + fallback('highlightColor', options.highlightcolor); + fallback('highlightEndColor', options.highlightendcolor); +}; + +Object.extend(Ajax.InPlaceEditor, { + DefaultOptions: { + ajaxOptions: { }, + autoRows: 3, // Use when multi-line w/ rows == 1 + cancelControl: 'link', // 'link'|'button'|false + cancelText: 'cancel', + clickToEditText: 'Click to edit', + externalControl: null, // id|elt + externalControlOnly: false, + fieldPostCreation: 'activate', // 'activate'|'focus'|false + formClassName: 'inplaceeditor-form', + formId: null, // id|elt + highlightColor: '#ffff99', + highlightEndColor: '#ffffff', + hoverClassName: '', + htmlResponse: true, + loadingClassName: 'inplaceeditor-loading', + loadingText: 'Loading...', + okControl: 'button', // 'link'|'button'|false + okText: 'ok', + paramName: 'value', + rows: 1, // If 1 and multi-line, uses autoRows + savingClassName: 'inplaceeditor-saving', + savingText: 'Saving...', + size: 0, + stripLoadedTextTags: false, + submitOnBlur: false, + textAfterControls: '', + textBeforeControls: '', + textBetweenControls: '' + }, + DefaultCallbacks: { + callback: function(form) { + return Form.serialize(form); + }, + onComplete: function(transport, element) { + // For backward compatibility, this one is bound to the IPE, and passes + // the element directly. It was too often customized, so we don't break it. + new Effect.Highlight(element, { + startcolor: this.options.highlightColor, keepBackgroundImage: true }); + }, + onEnterEditMode: null, + onEnterHover: function(ipe) { + ipe.element.style.backgroundColor = ipe.options.highlightColor; + if (ipe._effect) + ipe._effect.cancel(); + }, + onFailure: function(transport, ipe) { + alert('Error communication with the server: ' + transport.responseText.stripTags()); + }, + onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls. + onLeaveEditMode: null, + onLeaveHover: function(ipe) { + ipe._effect = new Effect.Highlight(ipe.element, { + startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor, + restorecolor: ipe._originalBackground, keepBackgroundImage: true + }); + } + }, + Listeners: { + click: 'enterEditMode', + keydown: 'checkForEscapeOrReturn', + mouseover: 'enterHover', + mouseout: 'leaveHover' + } +}); + +Ajax.InPlaceCollectionEditor.DefaultOptions = { + loadingCollectionText: 'Loading options...' +}; + +// Delayed observer, like Form.Element.Observer, +// but waits for delay after last key input +// Ideal for live-search fields + +Form.Element.DelayedObserver = Class.create({ + initialize: function(element, delay, callback) { + this.delay = delay || 0.5; + this.element = $(element); + this.callback = callback; + this.timer = null; + this.lastValue = $F(this.element); + Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this)); + }, + delayedListener: function(event) { + if(this.lastValue == $F(this.element)) return; + if(this.timer) clearTimeout(this.timer); + this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000); + this.lastValue = $F(this.element); + }, + onTimerEvent: function() { + this.timer = null; + this.callback(this.element, $F(this.element)); + } +}); diff --git a/docs/document-horizontal-text.png b/docs/document-horizontal-text.png new file mode 100644 index 0000000..f1a100a Binary files /dev/null and b/docs/document-horizontal-text.png differ diff --git a/docs/document-text-image.png b/docs/document-text-image.png new file mode 100644 index 0000000..56c4f0b Binary files /dev/null and b/docs/document-text-image.png differ diff --git a/docs/document-zipper.png b/docs/document-zipper.png new file mode 100644 index 0000000..6d6333a Binary files /dev/null and b/docs/document-zipper.png differ diff --git a/docs/documents-text.png b/docs/documents-text.png new file mode 100644 index 0000000..7537994 Binary files /dev/null and b/docs/documents-text.png differ diff --git a/docs/dotproduct-small.jpg b/docs/dotproduct-small.jpg new file mode 100644 index 0000000..815c019 Binary files /dev/null and b/docs/dotproduct-small.jpg differ diff --git a/docs/dragdrop.js b/docs/dragdrop.js new file mode 100644 index 0000000..2b06e89 --- /dev/null +++ b/docs/dragdrop.js @@ -0,0 +1,973 @@ +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// (c) 2005-2008 Sammi Williams (http://www.oriontransfer.co.nz, sammi@oriontransfer.co.nz) +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +if(Object.isUndefined(Effect)) + throw("dragdrop.js requires including script.aculo.us' effects.js library"); + +var Droppables = { + drops: [], + + remove: function(element) { + this.drops = this.drops.reject(function(d) { return d.element==$(element) }); + }, + + add: function(element) { + element = $(element); + var options = Object.extend({ + greedy: true, + hoverclass: null, + tree: false + }, arguments[1] || { }); + + // cache containers + if(options.containment) { + options._containers = []; + var containment = options.containment; + if(Object.isArray(containment)) { + containment.each( function(c) { options._containers.push($(c)) }); + } else { + options._containers.push($(containment)); + } + } + + if(options.accept) options.accept = [options.accept].flatten(); + + Element.makePositioned(element); // fix IE + options.element = element; + + this.drops.push(options); + }, + + findDeepestChild: function(drops) { + deepest = drops[0]; + + for (i = 1; i < drops.length; ++i) + if (Element.isParent(drops[i].element, deepest.element)) + deepest = drops[i]; + + return deepest; + }, + + isContained: function(element, drop) { + var containmentNode; + if(drop.tree) { + containmentNode = element.treeNode; + } else { + containmentNode = element.parentNode; + } + return drop._containers.detect(function(c) { return containmentNode == c }); + }, + + isAffected: function(point, element, drop) { + return ( + (drop.element!=element) && + ((!drop._containers) || + this.isContained(element, drop)) && + ((!drop.accept) || + (Element.classNames(element).detect( + function(v) { return drop.accept.include(v) } ) )) && + Position.within(drop.element, point[0], point[1]) ); + }, + + deactivate: function(drop) { + if(drop.hoverclass) + Element.removeClassName(drop.element, drop.hoverclass); + this.last_active = null; + }, + + activate: function(drop) { + if(drop.hoverclass) + Element.addClassName(drop.element, drop.hoverclass); + this.last_active = drop; + }, + + show: function(point, element) { + if(!this.drops.length) return; + var drop, affected = []; + + this.drops.each( function(drop) { + if(Droppables.isAffected(point, element, drop)) + affected.push(drop); + }); + + if(affected.length>0) + drop = Droppables.findDeepestChild(affected); + + if(this.last_active && this.last_active != drop) this.deactivate(this.last_active); + if (drop) { + Position.within(drop.element, point[0], point[1]); + if(drop.onHover) + drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element)); + + if (drop != this.last_active) Droppables.activate(drop); + } + }, + + fire: function(event, element) { + if(!this.last_active) return; + Position.prepare(); + + if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active)) + if (this.last_active.onDrop) { + this.last_active.onDrop(element, this.last_active.element, event); + return true; + } + }, + + reset: function() { + if(this.last_active) + this.deactivate(this.last_active); + } +}; + +var Draggables = { + drags: [], + observers: [], + + register: function(draggable) { + if(this.drags.length == 0) { + this.eventMouseUp = this.endDrag.bindAsEventListener(this); + this.eventMouseMove = this.updateDrag.bindAsEventListener(this); + this.eventKeypress = this.keyPress.bindAsEventListener(this); + + Event.observe(document, "mouseup", this.eventMouseUp); + Event.observe(document, "mousemove", this.eventMouseMove); + Event.observe(document, "keypress", this.eventKeypress); + } + this.drags.push(draggable); + }, + + unregister: function(draggable) { + this.drags = this.drags.reject(function(d) { return d==draggable }); + if(this.drags.length == 0) { + Event.stopObserving(document, "mouseup", this.eventMouseUp); + Event.stopObserving(document, "mousemove", this.eventMouseMove); + Event.stopObserving(document, "keypress", this.eventKeypress); + } + }, + + activate: function(draggable) { + if(draggable.options.delay) { + this._timeout = setTimeout(function() { + Draggables._timeout = null; + window.focus(); + Draggables.activeDraggable = draggable; + }.bind(this), draggable.options.delay); + } else { + window.focus(); // allows keypress events if window isn't currently focused, fails for Safari + this.activeDraggable = draggable; + } + }, + + deactivate: function() { + this.activeDraggable = null; + }, + + updateDrag: function(event) { + if(!this.activeDraggable) return; + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + // Mozilla-based browsers fire successive mousemove events with + // the same coordinates, prevent needless redrawing (moz bug?) + if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return; + this._lastPointer = pointer; + + this.activeDraggable.updateDrag(event, pointer); + }, + + endDrag: function(event) { + if(this._timeout) { + clearTimeout(this._timeout); + this._timeout = null; + } + if(!this.activeDraggable) return; + this._lastPointer = null; + this.activeDraggable.endDrag(event); + this.activeDraggable = null; + }, + + keyPress: function(event) { + if(this.activeDraggable) + this.activeDraggable.keyPress(event); + }, + + addObserver: function(observer) { + this.observers.push(observer); + this._cacheObserverCallbacks(); + }, + + removeObserver: function(element) { // element instead of observer fixes mem leaks + this.observers = this.observers.reject( function(o) { return o.element==element }); + this._cacheObserverCallbacks(); + }, + + notify: function(eventName, draggable, event) { // 'onStart', 'onEnd', 'onDrag' + if(this[eventName+'Count'] > 0) + this.observers.each( function(o) { + if(o[eventName]) o[eventName](eventName, draggable, event); + }); + if(draggable.options[eventName]) draggable.options[eventName](draggable, event); + }, + + _cacheObserverCallbacks: function() { + ['onStart','onEnd','onDrag'].each( function(eventName) { + Draggables[eventName+'Count'] = Draggables.observers.select( + function(o) { return o[eventName]; } + ).length; + }); + } +}; + +/*--------------------------------------------------------------------------*/ + +var Draggable = Class.create({ + initialize: function(element) { + var defaults = { + handle: false, + reverteffect: function(element, top_offset, left_offset) { + var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02; + new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur, + queue: {scope:'_draggable', position:'end'} + }); + }, + endeffect: function(element) { + var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0; + new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity, + queue: {scope:'_draggable', position:'end'}, + afterFinish: function(){ + Draggable._dragging[element] = false + } + }); + }, + zindex: 1000, + revert: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + snap: false, // false, or xy or [x,y] or function(x,y){ return [x,y] } + delay: 0 + }; + + if(!arguments[1] || Object.isUndefined(arguments[1].endeffect)) + Object.extend(defaults, { + starteffect: function(element) { + element._opacity = Element.getOpacity(element); + Draggable._dragging[element] = true; + new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7}); + } + }); + + var options = Object.extend(defaults, arguments[1] || { }); + + this.element = $(element); + + if(options.handle && Object.isString(options.handle)) + this.handle = this.element.down('.'+options.handle, 0); + + if(!this.handle) this.handle = $(options.handle); + if(!this.handle) this.handle = this.element; + + if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) { + options.scroll = $(options.scroll); + this._isScrollChild = Element.childOf(this.element, options.scroll); + } + + Element.makePositioned(this.element); // fix IE + + this.options = options; + this.dragging = false; + + this.eventMouseDown = this.initDrag.bindAsEventListener(this); + Event.observe(this.handle, "mousedown", this.eventMouseDown); + + Draggables.register(this); + }, + + destroy: function() { + Event.stopObserving(this.handle, "mousedown", this.eventMouseDown); + Draggables.unregister(this); + }, + + currentDelta: function() { + return([ + parseInt(Element.getStyle(this.element,'left') || '0'), + parseInt(Element.getStyle(this.element,'top') || '0')]); + }, + + initDrag: function(event) { + if(!Object.isUndefined(Draggable._dragging[this.element]) && + Draggable._dragging[this.element]) return; + if(Event.isLeftClick(event)) { + // abort on form elements, fixes a Firefox issue + var src = Event.element(event); + if((tag_name = src.tagName.toUpperCase()) && ( + tag_name=='INPUT' || + tag_name=='SELECT' || + tag_name=='OPTION' || + tag_name=='BUTTON' || + tag_name=='TEXTAREA')) return; + + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + var pos = Position.cumulativeOffset(this.element); + this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) }); + + Draggables.activate(this); + Event.stop(event); + } + }, + + startDrag: function(event) { + this.dragging = true; + if(!this.delta) + this.delta = this.currentDelta(); + + if(this.options.zindex) { + this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0); + this.element.style.zIndex = this.options.zindex; + } + + if(this.options.ghosting) { + this._clone = this.element.cloneNode(true); + this._originallyAbsolute = (this.element.getStyle('position') == 'absolute'); + if (!this._originallyAbsolute) + Position.absolutize(this.element); + this.element.parentNode.insertBefore(this._clone, this.element); + } + + if(this.options.scroll) { + if (this.options.scroll == window) { + var where = this._getWindowScroll(this.options.scroll); + this.originalScrollLeft = where.left; + this.originalScrollTop = where.top; + } else { + this.originalScrollLeft = this.options.scroll.scrollLeft; + this.originalScrollTop = this.options.scroll.scrollTop; + } + } + + Draggables.notify('onStart', this, event); + + if(this.options.starteffect) this.options.starteffect(this.element); + }, + + updateDrag: function(event, pointer) { + if(!this.dragging) this.startDrag(event); + + if(!this.options.quiet){ + Position.prepare(); + Droppables.show(pointer, this.element); + } + + Draggables.notify('onDrag', this, event); + + this.draw(pointer); + if(this.options.change) this.options.change(this); + + if(this.options.scroll) { + this.stopScrolling(); + + var p; + if (this.options.scroll == window) { + with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; } + } else { + p = Position.page(this.options.scroll); + p[0] += this.options.scroll.scrollLeft + Position.deltaX; + p[1] += this.options.scroll.scrollTop + Position.deltaY; + p.push(p[0]+this.options.scroll.offsetWidth); + p.push(p[1]+this.options.scroll.offsetHeight); + } + var speed = [0,0]; + if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity); + if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity); + if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity); + if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity); + this.startScrolling(speed); + } + + // fix AppleWebKit rendering + if(Prototype.Browser.WebKit) window.scrollBy(0,0); + + Event.stop(event); + }, + + finishDrag: function(event, success) { + this.dragging = false; + + if(this.options.quiet){ + Position.prepare(); + var pointer = [Event.pointerX(event), Event.pointerY(event)]; + Droppables.show(pointer, this.element); + } + + if(this.options.ghosting) { + if (!this._originallyAbsolute) + Position.relativize(this.element); + delete this._originallyAbsolute; + Element.remove(this._clone); + this._clone = null; + } + + var dropped = false; + if(success) { + dropped = Droppables.fire(event, this.element); + if (!dropped) dropped = false; + } + if(dropped && this.options.onDropped) this.options.onDropped(this.element); + Draggables.notify('onEnd', this, event); + + var revert = this.options.revert; + if(revert && Object.isFunction(revert)) revert = revert(this.element); + + var d = this.currentDelta(); + if(revert && this.options.reverteffect) { + if (dropped == 0 || revert != 'failure') + this.options.reverteffect(this.element, + d[1]-this.delta[1], d[0]-this.delta[0]); + } else { + this.delta = d; + } + + if(this.options.zindex) + this.element.style.zIndex = this.originalZ; + + if(this.options.endeffect) + this.options.endeffect(this.element); + + Draggables.deactivate(this); + Droppables.reset(); + }, + + keyPress: function(event) { + if(event.keyCode!=Event.KEY_ESC) return; + this.finishDrag(event, false); + Event.stop(event); + }, + + endDrag: function(event) { + if(!this.dragging) return; + this.stopScrolling(); + this.finishDrag(event, true); + Event.stop(event); + }, + + draw: function(point) { + var pos = Position.cumulativeOffset(this.element); + if(this.options.ghosting) { + var r = Position.realOffset(this.element); + pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY; + } + + var d = this.currentDelta(); + pos[0] -= d[0]; pos[1] -= d[1]; + + if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) { + pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft; + pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop; + } + + var p = [0,1].map(function(i){ + return (point[i]-pos[i]-this.offset[i]) + }.bind(this)); + + if(this.options.snap) { + if(Object.isFunction(this.options.snap)) { + p = this.options.snap(p[0],p[1],this); + } else { + if(Object.isArray(this.options.snap)) { + p = p.map( function(v, i) { + return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this)); + } else { + p = p.map( function(v) { + return (v/this.options.snap).round()*this.options.snap }.bind(this)); + } + }} + + var style = this.element.style; + if((!this.options.constraint) || (this.options.constraint=='horizontal')) + style.left = p[0] + "px"; + if((!this.options.constraint) || (this.options.constraint=='vertical')) + style.top = p[1] + "px"; + + if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering + }, + + stopScrolling: function() { + if(this.scrollInterval) { + clearInterval(this.scrollInterval); + this.scrollInterval = null; + Draggables._lastScrollPointer = null; + } + }, + + startScrolling: function(speed) { + if(!(speed[0] || speed[1])) return; + this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed]; + this.lastScrolled = new Date(); + this.scrollInterval = setInterval(this.scroll.bind(this), 10); + }, + + scroll: function() { + var current = new Date(); + var delta = current - this.lastScrolled; + this.lastScrolled = current; + if(this.options.scroll == window) { + with (this._getWindowScroll(this.options.scroll)) { + if (this.scrollSpeed[0] || this.scrollSpeed[1]) { + var d = delta / 1000; + this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] ); + } + } + } else { + this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000; + this.options.scroll.scrollTop += this.scrollSpeed[1] * delta / 1000; + } + + Position.prepare(); + Droppables.show(Draggables._lastPointer, this.element); + Draggables.notify('onDrag', this); + if (this._isScrollChild) { + Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer); + Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000; + Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000; + if (Draggables._lastScrollPointer[0] < 0) + Draggables._lastScrollPointer[0] = 0; + if (Draggables._lastScrollPointer[1] < 0) + Draggables._lastScrollPointer[1] = 0; + this.draw(Draggables._lastScrollPointer); + } + + if(this.options.change) this.options.change(this); + }, + + _getWindowScroll: function(w) { + var T, L, W, H; + with (w.document) { + if (w.document.documentElement && documentElement.scrollTop) { + T = documentElement.scrollTop; + L = documentElement.scrollLeft; + } else if (w.document.body) { + T = body.scrollTop; + L = body.scrollLeft; + } + if (w.innerWidth) { + W = w.innerWidth; + H = w.innerHeight; + } else if (w.document.documentElement && documentElement.clientWidth) { + W = documentElement.clientWidth; + H = documentElement.clientHeight; + } else { + W = body.offsetWidth; + H = body.offsetHeight; + } + } + return { top: T, left: L, width: W, height: H }; + } +}); + +Draggable._dragging = { }; + +/*--------------------------------------------------------------------------*/ + +var SortableObserver = Class.create({ + initialize: function(element, observer) { + this.element = $(element); + this.observer = observer; + this.lastValue = Sortable.serialize(this.element); + }, + + onStart: function() { + this.lastValue = Sortable.serialize(this.element); + }, + + onEnd: function() { + Sortable.unmark(); + if(this.lastValue != Sortable.serialize(this.element)) + this.observer(this.element) + } +}); + +var Sortable = { + SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/, + + sortables: { }, + + _findRootElement: function(element) { + while (element.tagName.toUpperCase() != "BODY") { + if(element.id && Sortable.sortables[element.id]) return element; + element = element.parentNode; + } + }, + + options: function(element) { + element = Sortable._findRootElement($(element)); + if(!element) return; + return Sortable.sortables[element.id]; + }, + + destroy: function(element){ + element = $(element); + var s = Sortable.sortables[element.id]; + + if(s) { + Draggables.removeObserver(s.element); + s.droppables.each(function(d){ Droppables.remove(d) }); + s.draggables.invoke('destroy'); + + delete Sortable.sortables[s.element.id]; + } + }, + + create: function(element) { + element = $(element); + var options = Object.extend({ + element: element, + tag: 'li', // assumes li children, override with tag: 'tagname' + dropOnEmpty: false, + tree: false, + treeTag: 'ul', + overlap: 'vertical', // one of 'vertical', 'horizontal' + constraint: 'vertical', // one of 'vertical', 'horizontal', false + containment: element, // also takes array of elements (or id's); or false + handle: false, // or a CSS class + only: false, + delay: 0, + hoverclass: null, + ghosting: false, + quiet: false, + scroll: false, + scrollSensitivity: 20, + scrollSpeed: 15, + format: this.SERIALIZE_RULE, + + // these take arrays of elements or ids and can be + // used for better initialization performance + elements: false, + handles: false, + + onChange: Prototype.emptyFunction, + onUpdate: Prototype.emptyFunction + }, arguments[1] || { }); + + // clear any old sortable with same element + this.destroy(element); + + // build options for the draggables + var options_for_draggable = { + revert: true, + quiet: options.quiet, + scroll: options.scroll, + scrollSpeed: options.scrollSpeed, + scrollSensitivity: options.scrollSensitivity, + delay: options.delay, + ghosting: options.ghosting, + constraint: options.constraint, + handle: options.handle }; + + if(options.starteffect) + options_for_draggable.starteffect = options.starteffect; + + if(options.reverteffect) + options_for_draggable.reverteffect = options.reverteffect; + else + if(options.ghosting) options_for_draggable.reverteffect = function(element) { + element.style.top = 0; + element.style.left = 0; + }; + + if(options.endeffect) + options_for_draggable.endeffect = options.endeffect; + + if(options.zindex) + options_for_draggable.zindex = options.zindex; + + // build options for the droppables + var options_for_droppable = { + overlap: options.overlap, + containment: options.containment, + tree: options.tree, + hoverclass: options.hoverclass, + onHover: Sortable.onHover + }; + + var options_for_tree = { + onHover: Sortable.onEmptyHover, + overlap: options.overlap, + containment: options.containment, + hoverclass: options.hoverclass + }; + + // fix for gecko engine + Element.cleanWhitespace(element); + + options.draggables = []; + options.droppables = []; + + // drop on empty handling + if(options.dropOnEmpty || options.tree) { + Droppables.add(element, options_for_tree); + options.droppables.push(element); + } + + (options.elements || this.findElements(element, options) || []).each( function(e,i) { + var handle = options.handles ? $(options.handles[i]) : + (options.handle ? $(e).select('.' + options.handle)[0] : e); + options.draggables.push( + new Draggable(e, Object.extend(options_for_draggable, { handle: handle }))); + Droppables.add(e, options_for_droppable); + if(options.tree) e.treeNode = element; + options.droppables.push(e); + }); + + if(options.tree) { + (Sortable.findTreeElements(element, options) || []).each( function(e) { + Droppables.add(e, options_for_tree); + e.treeNode = element; + options.droppables.push(e); + }); + } + + // keep reference + this.sortables[element.id] = options; + + // for onupdate + Draggables.addObserver(new SortableObserver(element, options.onUpdate)); + + }, + + // return all suitable-for-sortable elements in a guaranteed order + findElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.tag); + }, + + findTreeElements: function(element, options) { + return Element.findChildren( + element, options.only, options.tree ? true : false, options.treeTag); + }, + + onHover: function(element, dropon, overlap) { + if(Element.isParent(dropon, element)) return; + + if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) { + return; + } else if(overlap>0.5) { + Sortable.mark(dropon, 'before'); + if(dropon.previousSibling != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, dropon); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } else { + Sortable.mark(dropon, 'after'); + var nextElement = dropon.nextSibling || null; + if(nextElement != element) { + var oldParentNode = element.parentNode; + element.style.visibility = "hidden"; // fix gecko rendering + dropon.parentNode.insertBefore(element, nextElement); + if(dropon.parentNode!=oldParentNode) + Sortable.options(oldParentNode).onChange(element); + Sortable.options(dropon.parentNode).onChange(element); + } + } + }, + + onEmptyHover: function(element, dropon, overlap) { + var oldParentNode = element.parentNode; + var droponOptions = Sortable.options(dropon); + + if(!Element.isParent(dropon, element)) { + var index; + + var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only}); + var child = null; + + if(children) { + var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap); + + for (index = 0; index < children.length; index += 1) { + if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) { + offset -= Element.offsetSize (children[index], droponOptions.overlap); + } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) { + child = index + 1 < children.length ? children[index + 1] : null; + break; + } else { + child = children[index]; + break; + } + } + } + + dropon.insertBefore(element, child); + + Sortable.options(oldParentNode).onChange(element); + droponOptions.onChange(element); + } + }, + + unmark: function() { + if(Sortable._marker) Sortable._marker.hide(); + }, + + mark: function(dropon, position) { + // mark on ghosting only + var sortable = Sortable.options(dropon.parentNode); + if(sortable && !sortable.ghosting) return; + + if(!Sortable._marker) { + Sortable._marker = + ($('dropmarker') || Element.extend(document.createElement('DIV'))). + hide().addClassName('dropmarker').setStyle({position:'absolute'}); + document.getElementsByTagName("body").item(0).appendChild(Sortable._marker); + } + var offsets = Position.cumulativeOffset(dropon); + Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'}); + + if(position=='after') + if(sortable.overlap == 'horizontal') + Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'}); + else + Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'}); + + Sortable._marker.show(); + }, + + _tree: function(element, options, parent) { + var children = Sortable.findElements(element, options) || []; + + for (var i = 0; i < children.length; ++i) { + var match = children[i].id.match(options.format); + + if (!match) continue; + + var child = { + id: encodeURIComponent(match ? match[1] : null), + element: element, + parent: parent, + children: [], + position: parent.children.length, + container: $(children[i]).down(options.treeTag) + }; + + /* Get the element containing the children and recurse over it */ + if (child.container) + this._tree(child.container, options, child); + + parent.children.push (child); + } + + return parent; + }, + + tree: function(element) { + element = $(element); + var sortableOptions = this.options(element); + var options = Object.extend({ + tag: sortableOptions.tag, + treeTag: sortableOptions.treeTag, + only: sortableOptions.only, + name: element.id, + format: sortableOptions.format + }, arguments[1] || { }); + + var root = { + id: null, + parent: null, + children: [], + container: element, + position: 0 + }; + + return Sortable._tree(element, options, root); + }, + + /* Construct a [i] index for a particular node */ + _constructIndex: function(node) { + var index = ''; + do { + if (node.id) index = '[' + node.position + ']' + index; + } while ((node = node.parent) != null); + return index; + }, + + sequence: function(element) { + element = $(element); + var options = Object.extend(this.options(element), arguments[1] || { }); + + return $(this.findElements(element, options) || []).map( function(item) { + return item.id.match(options.format) ? item.id.match(options.format)[1] : ''; + }); + }, + + setSequence: function(element, new_sequence) { + element = $(element); + var options = Object.extend(this.options(element), arguments[2] || { }); + + var nodeMap = { }; + this.findElements(element, options).each( function(n) { + if (n.id.match(options.format)) + nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode]; + n.parentNode.removeChild(n); + }); + + new_sequence.each(function(ident) { + var n = nodeMap[ident]; + if (n) { + n[1].appendChild(n[0]); + delete nodeMap[ident]; + } + }); + }, + + serialize: function(element) { + element = $(element); + var options = Object.extend(Sortable.options(element), arguments[1] || { }); + var name = encodeURIComponent( + (arguments[1] && arguments[1].name) ? arguments[1].name : element.id); + + if (options.tree) { + return Sortable.tree(element, arguments[1]).children.map( function (item) { + return [name + Sortable._constructIndex(item) + "[id]=" + + encodeURIComponent(item.id)].concat(item.children.map(arguments.callee)); + }).flatten().join('&'); + } else { + return Sortable.sequence(element, arguments[1]).map( function(item) { + return name + "[]=" + encodeURIComponent(item); + }).join('&'); + } + } +}; + +// Returns true if child is contained within element +Element.isParent = function(child, element) { + if (!child.parentNode || child == element) return false; + if (child.parentNode == element) return true; + return Element.isParent(child.parentNode, element); +}; + +Element.findChildren = function(element, only, recursive, tagName) { + if(!element.hasChildNodes()) return null; + tagName = tagName.toUpperCase(); + if(only) only = [only].flatten(); + var elements = []; + $A(element.childNodes).each( function(e) { + if(e.tagName && e.tagName.toUpperCase()==tagName && + (!only || (Element.classNames(e).detect(function(v) { return only.include(v) })))) + elements.push(e); + if(recursive) { + var grandchildren = Element.findChildren(e, only, recursive, tagName); + if(grandchildren) elements.push(grandchildren); + } + }); + + return (elements.length>0 ? elements.flatten() : []); +}; + +Element.offsetSize = function (element, type) { + return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')]; +}; diff --git a/docs/effects.js b/docs/effects.js new file mode 100644 index 0000000..2d57404 --- /dev/null +++ b/docs/effects.js @@ -0,0 +1,1128 @@ +// Copyright (c) 2005-2008 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us) +// Contributors: +// Justin Palmer (http://encytemedia.com/) +// Mark Pilgrim (http://diveintomark.org/) +// Martin Bialasinki +// +// script.aculo.us is freely distributable under the terms of an MIT-style license. +// For details, see the script.aculo.us web site: http://script.aculo.us/ + +// converts rgb() and #xxx to #xxxxxx format, +// returns self (or first argument) if not convertable +String.prototype.parseColor = function() { + var color = '#'; + if (this.slice(0,4) == 'rgb(') { + var cols = this.slice(4,this.length-1).split(','); + var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3); + } else { + if (this.slice(0,1) == '#') { + if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase(); + if (this.length==7) color = this.toLowerCase(); + } + } + return (color.length==7 ? color : (arguments[0] || this)); +}; + +/*--------------------------------------------------------------------------*/ + +Element.collectTextNodes = function(element) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + (node.hasChildNodes() ? Element.collectTextNodes(node) : '')); + }).flatten().join(''); +}; + +Element.collectTextNodesIgnoreClass = function(element, className) { + return $A($(element).childNodes).collect( function(node) { + return (node.nodeType==3 ? node.nodeValue : + ((node.hasChildNodes() && !Element.hasClassName(node,className)) ? + Element.collectTextNodesIgnoreClass(node, className) : '')); + }).flatten().join(''); +}; + +Element.setContentZoom = function(element, percent) { + element = $(element); + element.setStyle({fontSize: (percent/100) + 'em'}); + if (Prototype.Browser.WebKit) window.scrollBy(0,0); + return element; +}; + +Element.getInlineOpacity = function(element){ + return $(element).style.opacity || ''; +}; + +Element.forceRerendering = function(element) { + try { + element = $(element); + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch(e) { } +}; + +/*--------------------------------------------------------------------------*/ + +var Effect = { + _elementDoesNotExistError: { + name: 'ElementDoesNotExistError', + message: 'The specified DOM element does not exist, but is required for this effect to operate' + }, + Transitions: { + linear: Prototype.K, + sinoidal: function(pos) { + return (-Math.cos(pos*Math.PI)/2) + .5; + }, + reverse: function(pos) { + return 1-pos; + }, + flicker: function(pos) { + var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4; + return pos > 1 ? 1 : pos; + }, + wobble: function(pos) { + return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5; + }, + pulse: function(pos, pulses) { + return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5; + }, + spring: function(pos) { + return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6)); + }, + none: function(pos) { + return 0; + }, + full: function(pos) { + return 1; + } + }, + DefaultOptions: { + duration: 1.0, // seconds + fps: 100, // 100= assume 66fps max. + sync: false, // true for combining + from: 0.0, + to: 1.0, + delay: 0.0, + queue: 'parallel' + }, + tagifyText: function(element) { + var tagifyStyle = 'position:relative'; + if (Prototype.Browser.IE) tagifyStyle += ';zoom:1'; + + element = $(element); + $A(element.childNodes).each( function(child) { + if (child.nodeType==3) { + child.nodeValue.toArray().each( function(character) { + element.insertBefore( + new Element('span', {style: tagifyStyle}).update( + character == ' ' ? String.fromCharCode(160) : character), + child); + }); + Element.remove(child); + } + }); + }, + multiple: function(element, effect) { + var elements; + if (((typeof element == 'object') || + Object.isFunction(element)) && + (element.length)) + elements = element; + else + elements = $(element).childNodes; + + var options = Object.extend({ + speed: 0.1, + delay: 0.0 + }, arguments[2] || { }); + var masterDelay = options.delay; + + $A(elements).each( function(element, index) { + new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay })); + }); + }, + PAIRS: { + 'slide': ['SlideDown','SlideUp'], + 'blind': ['BlindDown','BlindUp'], + 'appear': ['Appear','Fade'] + }, + toggle: function(element, effect) { + element = $(element); + effect = (effect || 'appear').toLowerCase(); + var options = Object.extend({ + queue: { position:'end', scope:(element.id || 'global'), limit: 1 } + }, arguments[2] || { }); + Effect[element.visible() ? + Effect.PAIRS[effect][1] : Effect.PAIRS[effect][0]](element, options); + } +}; + +Effect.DefaultOptions.transition = Effect.Transitions.sinoidal; + +/* ------------- core effects ------------- */ + +Effect.ScopedQueue = Class.create(Enumerable, { + initialize: function() { + this.effects = []; + this.interval = null; + }, + _each: function(iterator) { + this.effects._each(iterator); + }, + add: function(effect) { + var timestamp = new Date().getTime(); + + var position = Object.isString(effect.options.queue) ? + effect.options.queue : effect.options.queue.position; + + switch(position) { + case 'front': + // move unstarted effects after this effect + this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) { + e.startOn += effect.finishOn; + e.finishOn += effect.finishOn; + }); + break; + case 'with-last': + timestamp = this.effects.pluck('startOn').max() || timestamp; + break; + case 'end': + // start effect after last queued effect has finished + timestamp = this.effects.pluck('finishOn').max() || timestamp; + break; + } + + effect.startOn += timestamp; + effect.finishOn += timestamp; + + if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit)) + this.effects.push(effect); + + if (!this.interval) + this.interval = setInterval(this.loop.bind(this), 15); + }, + remove: function(effect) { + this.effects = this.effects.reject(function(e) { return e==effect }); + if (this.effects.length == 0) { + clearInterval(this.interval); + this.interval = null; + } + }, + loop: function() { + var timePos = new Date().getTime(); + for(var i=0, len=this.effects.length;i= this.startOn) { + if (timePos >= this.finishOn) { + this.render(1.0); + this.cancel(); + this.event('beforeFinish'); + if (this.finish) this.finish(); + this.event('afterFinish'); + return; + } + var pos = (timePos - this.startOn) / this.totalTime, + frame = (pos * this.totalFrames).round(); + if (frame > this.currentFrame) { + this.render(pos); + this.currentFrame = frame; + } + } + }, + cancel: function() { + if (!this.options.sync) + Effect.Queues.get(Object.isString(this.options.queue) ? + 'global' : this.options.queue.scope).remove(this); + this.state = 'finished'; + }, + event: function(eventName) { + if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this); + if (this.options[eventName]) this.options[eventName](this); + }, + inspect: function() { + var data = $H(); + for(property in this) + if (!Object.isFunction(this[property])) data.set(property, this[property]); + return '#'; + } +}); + +Effect.Parallel = Class.create(Effect.Base, { + initialize: function(effects) { + this.effects = effects || []; + this.start(arguments[1]); + }, + update: function(position) { + this.effects.invoke('render', position); + }, + finish: function(position) { + this.effects.each( function(effect) { + effect.render(1.0); + effect.cancel(); + effect.event('beforeFinish'); + if (effect.finish) effect.finish(position); + effect.event('afterFinish'); + }); + } +}); + +Effect.Tween = Class.create(Effect.Base, { + initialize: function(object, from, to) { + object = Object.isString(object) ? $(object) : object; + var args = $A(arguments), method = args.last(), + options = args.length == 5 ? args[3] : null; + this.method = Object.isFunction(method) ? method.bind(object) : + Object.isFunction(object[method]) ? object[method].bind(object) : + function(value) { object[method] = value }; + this.start(Object.extend({ from: from, to: to }, options || { })); + }, + update: function(position) { + this.method(position); + } +}); + +Effect.Event = Class.create(Effect.Base, { + initialize: function() { + this.start(Object.extend({ duration: 0 }, arguments[0] || { })); + }, + update: Prototype.emptyFunction +}); + +Effect.Opacity = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + // make this work on IE on elements without 'layout' + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + var options = Object.extend({ + from: this.element.getOpacity() || 0.0, + to: 1.0 + }, arguments[1] || { }); + this.start(options); + }, + update: function(position) { + this.element.setOpacity(position); + } +}); + +Effect.Move = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + x: 0, + y: 0, + mode: 'relative' + }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + this.element.makePositioned(); + this.originalLeft = parseFloat(this.element.getStyle('left') || '0'); + this.originalTop = parseFloat(this.element.getStyle('top') || '0'); + if (this.options.mode == 'absolute') { + this.options.x = this.options.x - this.originalLeft; + this.options.y = this.options.y - this.originalTop; + } + }, + update: function(position) { + this.element.setStyle({ + left: (this.options.x * position + this.originalLeft).round() + 'px', + top: (this.options.y * position + this.originalTop).round() + 'px' + }); + } +}); + +// for backwards compatibility +Effect.MoveBy = function(element, toTop, toLeft) { + return new Effect.Move(element, + Object.extend({ x: toLeft, y: toTop }, arguments[3] || { })); +}; + +Effect.Scale = Class.create(Effect.Base, { + initialize: function(element, percent) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + scaleX: true, + scaleY: true, + scaleContent: true, + scaleFromCenter: false, + scaleMode: 'box', // 'box' or 'contents' or { } with provided values + scaleFrom: 100.0, + scaleTo: percent + }, arguments[2] || { }); + this.start(options); + }, + setup: function() { + this.restoreAfterFinish = this.options.restoreAfterFinish || false; + this.elementPositioning = this.element.getStyle('position'); + + this.originalStyle = { }; + ['top','left','width','height','fontSize'].each( function(k) { + this.originalStyle[k] = this.element.style[k]; + }.bind(this)); + + this.originalTop = this.element.offsetTop; + this.originalLeft = this.element.offsetLeft; + + var fontSize = this.element.getStyle('font-size') || '100%'; + ['em','px','%','pt'].each( function(fontSizeType) { + if (fontSize.indexOf(fontSizeType)>0) { + this.fontSize = parseFloat(fontSize); + this.fontSizeType = fontSizeType; + } + }.bind(this)); + + this.factor = (this.options.scaleTo - this.options.scaleFrom)/100; + + this.dims = null; + if (this.options.scaleMode=='box') + this.dims = [this.element.offsetHeight, this.element.offsetWidth]; + if (/^content/.test(this.options.scaleMode)) + this.dims = [this.element.scrollHeight, this.element.scrollWidth]; + if (!this.dims) + this.dims = [this.options.scaleMode.originalHeight, + this.options.scaleMode.originalWidth]; + }, + update: function(position) { + var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position); + if (this.options.scaleContent && this.fontSize) + this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType }); + this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale); + }, + finish: function(position) { + if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle); + }, + setDimensions: function(height, width) { + var d = { }; + if (this.options.scaleX) d.width = width.round() + 'px'; + if (this.options.scaleY) d.height = height.round() + 'px'; + if (this.options.scaleFromCenter) { + var topd = (height - this.dims[0])/2; + var leftd = (width - this.dims[1])/2; + if (this.elementPositioning == 'absolute') { + if (this.options.scaleY) d.top = this.originalTop-topd + 'px'; + if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px'; + } else { + if (this.options.scaleY) d.top = -topd + 'px'; + if (this.options.scaleX) d.left = -leftd + 'px'; + } + } + this.element.setStyle(d); + } +}); + +Effect.Highlight = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { }); + this.start(options); + }, + setup: function() { + // Prevent executing on elements not in the layout flow + if (this.element.getStyle('display')=='none') { this.cancel(); return; } + // Disable background image during the effect + this.oldStyle = { }; + if (!this.options.keepBackgroundImage) { + this.oldStyle.backgroundImage = this.element.getStyle('background-image'); + this.element.setStyle({backgroundImage: 'none'}); + } + if (!this.options.endcolor) + this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff'); + if (!this.options.restorecolor) + this.options.restorecolor = this.element.getStyle('background-color'); + // init color calculations + this._base = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this)); + this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this)); + }, + update: function(position) { + this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){ + return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) }); + }, + finish: function() { + this.element.setStyle(Object.extend(this.oldStyle, { + backgroundColor: this.options.restorecolor + })); + } +}); + +Effect.ScrollTo = function(element) { + var options = arguments[1] || { }, + scrollOffsets = document.viewport.getScrollOffsets(), + elementOffsets = $(element).cumulativeOffset(); + + if (options.offset) elementOffsets[1] += options.offset; + + return new Effect.Tween(null, + scrollOffsets.top, + elementOffsets[1], + options, + function(p){ scrollTo(scrollOffsets.left, p.round()); } + ); +}; + +/* ------------- combination effects ------------- */ + +Effect.Fade = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + var options = Object.extend({ + from: element.getOpacity() || 1.0, + to: 0.0, + afterFinishInternal: function(effect) { + if (effect.options.to!=0) return; + effect.element.hide().setStyle({opacity: oldOpacity}); + } + }, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Appear = function(element) { + element = $(element); + var options = Object.extend({ + from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0), + to: 1.0, + // force Safari to render floated elements properly + afterFinishInternal: function(effect) { + effect.element.forceRerendering(); + }, + beforeSetup: function(effect) { + effect.element.setOpacity(effect.options.from).show(); + }}, arguments[1] || { }); + return new Effect.Opacity(element,options); +}; + +Effect.Puff = function(element) { + element = $(element); + var oldStyle = { + opacity: element.getInlineOpacity(), + position: element.getStyle('position'), + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height + }; + return new Effect.Parallel( + [ new Effect.Scale(element, 200, + { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 } ) ], + Object.extend({ duration: 1.0, + beforeSetupInternal: function(effect) { + Position.absolutize(effect.effects[0].element); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().setStyle(oldStyle); } + }, arguments[1] || { }) + ); +}; + +Effect.BlindUp = function(element) { + element = $(element); + element.makeClipping(); + return new Effect.Scale(element, 0, + Object.extend({ scaleContent: false, + scaleX: false, + restoreAfterFinish: true, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }, arguments[1] || { }) + ); +}; + +Effect.BlindDown = function(element) { + element = $(element); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: 0, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping(); + } + }, arguments[1] || { })); +}; + +Effect.SwitchOff = function(element) { + element = $(element); + var oldOpacity = element.getInlineOpacity(); + return new Effect.Appear(element, Object.extend({ + duration: 0.4, + from: 0, + transition: Effect.Transitions.flicker, + afterFinishInternal: function(effect) { + new Effect.Scale(effect.element, 1, { + duration: 0.3, scaleFromCenter: true, + scaleX: false, scaleContent: false, restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity}); + } + }); + } + }, arguments[1] || { })); +}; + +Effect.DropOut = function(element) { + element = $(element); + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left'), + opacity: element.getInlineOpacity() }; + return new Effect.Parallel( + [ new Effect.Move(element, {x: 0, y: 100, sync: true }), + new Effect.Opacity(element, { sync: true, to: 0.0 }) ], + Object.extend( + { duration: 0.5, + beforeSetup: function(effect) { + effect.effects[0].element.makePositioned(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle); + } + }, arguments[1] || { })); +}; + +Effect.Shake = function(element) { + element = $(element); + var options = Object.extend({ + distance: 20, + duration: 0.5 + }, arguments[1] || {}); + var distance = parseFloat(options.distance); + var split = parseFloat(options.duration) / 10.0; + var oldStyle = { + top: element.getStyle('top'), + left: element.getStyle('left') }; + return new Effect.Move(element, + { x: distance, y: 0, duration: split, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: distance*2, y: 0, duration: split*2, afterFinishInternal: function(effect) { + new Effect.Move(effect.element, + { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) { + effect.element.undoPositioned().setStyle(oldStyle); + }}); }}); }}); }}); }}); }}); +}; + +Effect.SlideDown = function(element) { + element = $(element).cleanWhitespace(); + // SlideDown need to have the content of the element wrapped in a container element with fixed height! + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, 100, Object.extend({ + scaleContent: false, + scaleX: false, + scaleFrom: window.opera ? 0 : 1, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().setStyle({height: '0px'}).show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); } + }, arguments[1] || { }) + ); +}; + +Effect.SlideUp = function(element) { + element = $(element).cleanWhitespace(); + var oldInnerBottom = element.down().getStyle('bottom'); + var elementDimensions = element.getDimensions(); + return new Effect.Scale(element, window.opera ? 0 : 1, + Object.extend({ scaleContent: false, + scaleX: false, + scaleMode: 'box', + scaleFrom: 100, + scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width}, + restoreAfterFinish: true, + afterSetup: function(effect) { + effect.element.makePositioned(); + effect.element.down().makePositioned(); + if (window.opera) effect.element.setStyle({top: ''}); + effect.element.makeClipping().show(); + }, + afterUpdateInternal: function(effect) { + effect.element.down().setStyle({bottom: + (effect.dims[0] - effect.element.clientHeight) + 'px' }); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().undoPositioned(); + effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); + } + }, arguments[1] || { }) + ); +}; + +// Bug in opera makes the TD containing this element expand for a instance after finish +Effect.Squish = function(element) { + return new Effect.Scale(element, window.opera ? 1 : 0, { + restoreAfterFinish: true, + beforeSetup: function(effect) { + effect.element.makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping(); + } + }); +}; + +Effect.Grow = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.full + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var initialMoveX, initialMoveY; + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + initialMoveX = initialMoveY = moveX = moveY = 0; + break; + case 'top-right': + initialMoveX = dims.width; + initialMoveY = moveY = 0; + moveX = -dims.width; + break; + case 'bottom-left': + initialMoveX = moveX = 0; + initialMoveY = dims.height; + moveY = -dims.height; + break; + case 'bottom-right': + initialMoveX = dims.width; + initialMoveY = dims.height; + moveX = -dims.width; + moveY = -dims.height; + break; + case 'center': + initialMoveX = dims.width / 2; + initialMoveY = dims.height / 2; + moveX = -dims.width / 2; + moveY = -dims.height / 2; + break; + } + + return new Effect.Move(element, { + x: initialMoveX, + y: initialMoveY, + duration: 0.01, + beforeSetup: function(effect) { + effect.element.hide().makeClipping().makePositioned(); + }, + afterFinishInternal: function(effect) { + new Effect.Parallel( + [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }), + new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }), + new Effect.Scale(effect.element, 100, { + scaleMode: { originalHeight: dims.height, originalWidth: dims.width }, + sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true}) + ], Object.extend({ + beforeSetup: function(effect) { + effect.effects[0].element.setStyle({height: '0px'}).show(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle); + } + }, options) + ); + } + }); +}; + +Effect.Shrink = function(element) { + element = $(element); + var options = Object.extend({ + direction: 'center', + moveTransition: Effect.Transitions.sinoidal, + scaleTransition: Effect.Transitions.sinoidal, + opacityTransition: Effect.Transitions.none + }, arguments[1] || { }); + var oldStyle = { + top: element.style.top, + left: element.style.left, + height: element.style.height, + width: element.style.width, + opacity: element.getInlineOpacity() }; + + var dims = element.getDimensions(); + var moveX, moveY; + + switch (options.direction) { + case 'top-left': + moveX = moveY = 0; + break; + case 'top-right': + moveX = dims.width; + moveY = 0; + break; + case 'bottom-left': + moveX = 0; + moveY = dims.height; + break; + case 'bottom-right': + moveX = dims.width; + moveY = dims.height; + break; + case 'center': + moveX = dims.width / 2; + moveY = dims.height / 2; + break; + } + + return new Effect.Parallel( + [ new Effect.Opacity(element, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }), + new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}), + new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }) + ], Object.extend({ + beforeStartInternal: function(effect) { + effect.effects[0].element.makePositioned().makeClipping(); + }, + afterFinishInternal: function(effect) { + effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); } + }, options) + ); +}; + +Effect.Pulsate = function(element) { + element = $(element); + var options = arguments[1] || { }, + oldOpacity = element.getInlineOpacity(), + transition = options.transition || Effect.Transitions.linear, + reverser = function(pos){ + return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5); + }; + + return new Effect.Opacity(element, + Object.extend(Object.extend({ duration: 2.0, from: 0, + afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); } + }, options), {transition: reverser})); +}; + +Effect.Fold = function(element) { + element = $(element); + var oldStyle = { + top: element.style.top, + left: element.style.left, + width: element.style.width, + height: element.style.height }; + element.makeClipping(); + return new Effect.Scale(element, 5, Object.extend({ + scaleContent: false, + scaleX: false, + afterFinishInternal: function(effect) { + new Effect.Scale(element, 1, { + scaleContent: false, + scaleY: false, + afterFinishInternal: function(effect) { + effect.element.hide().undoClipping().setStyle(oldStyle); + } }); + }}, arguments[1] || { })); +}; + +Effect.Morph = Class.create(Effect.Base, { + initialize: function(element) { + this.element = $(element); + if (!this.element) throw(Effect._elementDoesNotExistError); + var options = Object.extend({ + style: { } + }, arguments[1] || { }); + + if (!Object.isString(options.style)) this.style = $H(options.style); + else { + if (options.style.include(':')) + this.style = options.style.parseStyle(); + else { + this.element.addClassName(options.style); + this.style = $H(this.element.getStyles()); + this.element.removeClassName(options.style); + var css = this.element.getStyles(); + this.style = this.style.reject(function(style) { + return style.value == css[style.key]; + }); + options.afterFinishInternal = function(effect) { + effect.element.addClassName(effect.options.style); + effect.transforms.each(function(transform) { + effect.element.style[transform.style] = ''; + }); + }; + } + } + this.start(options); + }, + + setup: function(){ + function parseColor(color){ + if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff'; + color = color.parseColor(); + return $R(0,2).map(function(i){ + return parseInt( color.slice(i*2+1,i*2+3), 16 ); + }); + } + this.transforms = this.style.map(function(pair){ + var property = pair[0], value = pair[1], unit = null; + + if (value.parseColor('#zzzzzz') != '#zzzzzz') { + value = value.parseColor(); + unit = 'color'; + } else if (property == 'opacity') { + value = parseFloat(value); + if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout)) + this.element.setStyle({zoom: 1}); + } else if (Element.CSS_LENGTH.test(value)) { + var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/); + value = parseFloat(components[1]); + unit = (components.length == 3) ? components[2] : null; + } + + var originalValue = this.element.getStyle(property); + return { + style: property.camelize(), + originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0), + targetValue: unit=='color' ? parseColor(value) : value, + unit: unit + }; + }.bind(this)).reject(function(transform){ + return ( + (transform.originalValue == transform.targetValue) || + ( + transform.unit != 'color' && + (isNaN(transform.originalValue) || isNaN(transform.targetValue)) + ) + ); + }); + }, + update: function(position) { + var style = { }, transform, i = this.transforms.length; + while(i--) + style[(transform = this.transforms[i]).style] = + transform.unit=='color' ? '#'+ + (Math.round(transform.originalValue[0]+ + (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() + + (Math.round(transform.originalValue[1]+ + (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() + + (Math.round(transform.originalValue[2]+ + (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() : + (transform.originalValue + + (transform.targetValue - transform.originalValue) * position).toFixed(3) + + (transform.unit === null ? '' : transform.unit); + this.element.setStyle(style, true); + } +}); + +Effect.Transform = Class.create({ + initialize: function(tracks){ + this.tracks = []; + this.options = arguments[1] || { }; + this.addTracks(tracks); + }, + addTracks: function(tracks){ + tracks.each(function(track){ + track = $H(track); + var data = track.values().first(); + this.tracks.push($H({ + ids: track.keys().first(), + effect: Effect.Morph, + options: { style: data } + })); + }.bind(this)); + return this; + }, + play: function(){ + return new Effect.Parallel( + this.tracks.map(function(track){ + var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options'); + var elements = [$(ids) || $$(ids)].flatten(); + return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) }); + }).flatten(), + this.options + ); + } +}); + +Element.CSS_PROPERTIES = $w( + 'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' + + 'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' + + 'borderRightColor borderRightStyle borderRightWidth borderSpacing ' + + 'borderTopColor borderTopStyle borderTopWidth bottom clip color ' + + 'fontSize fontWeight height left letterSpacing lineHeight ' + + 'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+ + 'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' + + 'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' + + 'right textIndent top width wordSpacing zIndex'); + +Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/; + +String.__parseStyleElement = document.createElement('div'); +String.prototype.parseStyle = function(){ + var style, styleRules = $H(); + if (Prototype.Browser.WebKit) + style = new Element('div',{style:this}).style; + else { + String.__parseStyleElement.innerHTML = '
    '; + style = String.__parseStyleElement.childNodes[0].style; + } + + Element.CSS_PROPERTIES.each(function(property){ + if (style[property]) styleRules.set(property, style[property]); + }); + + if (Prototype.Browser.IE && this.include('opacity')) + styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]); + + return styleRules; +}; + +if (document.defaultView && document.defaultView.getComputedStyle) { + Element.getStyles = function(element) { + var css = document.defaultView.getComputedStyle($(element), null); + return Element.CSS_PROPERTIES.inject({ }, function(styles, property) { + styles[property] = css[property]; + return styles; + }); + }; +} else { + Element.getStyles = function(element) { + element = $(element); + var css = element.currentStyle, styles; + styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) { + results[property] = css[property]; + return results; + }); + if (!styles.opacity) styles.opacity = element.getOpacity(); + return styles; + }; +} + +Effect.Methods = { + morph: function(element, style) { + element = $(element); + new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { })); + return element; + }, + visualEffect: function(element, effect, options) { + element = $(element); + var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1); + new Effect[klass](element, options); + return element; + }, + highlight: function(element, options) { + element = $(element); + new Effect.Highlight(element, options); + return element; + } +}; + +$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+ + 'pulsate shake puff squish switchOff dropOut').each( + function(effect) { + Effect.Methods[effect] = function(element, options){ + element = $(element); + Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options); + return element; + }; + } +); + +$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each( + function(f) { Effect.Methods[f] = Element[f]; } +); + +Element.addMethods(Effect.Methods); diff --git a/docs/exposure.jpg b/docs/exposure.jpg new file mode 100644 index 0000000..9f9e6a7 Binary files /dev/null and b/docs/exposure.jpg differ diff --git a/docs/external.png b/docs/external.png new file mode 100644 index 0000000..45df640 Binary files /dev/null and b/docs/external.png differ diff --git a/docs/floodlight.jpg b/docs/floodlight.jpg new file mode 100644 index 0000000..4497899 Binary files /dev/null and b/docs/floodlight.jpg differ diff --git a/docs/fsr_readme.txt b/docs/fsr_readme.txt new file mode 100644 index 0000000..5b5ecbf --- /dev/null +++ b/docs/fsr_readme.txt @@ -0,0 +1,111 @@ +//////////////////////////////////////// +// Q3Map2 - FS20 - R5 +// programming: Pavel P. [VorteX] Timofeyev +// e-mail: paul.vortex@gmail.com +////////// + +======================================== + ABOUT +========== + + The FS-R releases are modifications of Q3map2 FS releases (see fs_readme.txt) by TwentySeven. + + This software is licensed under the GPL. + +======================================== + VERSION HISTORY +======================================== + +-------------------- +---------------------------------------- +- VERSION R5 +-------------------- +---------- + +- added "-gridscale X" and "-gridambientscale X" to scale grid lightning, note that ambient grid receive +both "-gridscale" and "-gridambientscale". For -darkplaces, -dq and -prophecy game mode added +default game-specific values: -gridscale 0.3 -gridambientscale 0.4 + +- modified game-specific options prints at the begin of light stage + +-------------------- +---------------------------------------- +- VERSION R4 +-------------------- +---------- + +- added spawnflag 32 "unnormalized" - it means that light color will not be normalized on parse + +- added spawnflag 64 "distance_falloff" to gain access to light distance attenuation used for sun and area lights + +- to make things easy, behavior of _deviance/_samples on lights is changed - now when "_deviance" detected, "_samples" get same start value. So in most cases we need to set 1 key instead of 2. + +-------------------- +---------------------------------------- +- VERSION R3 +-------------------- +---------- + +- fixed tangentspace deluxemaps with radiocity (was broken) + +- fixed bug with overbrights on shadow edges in deluxemapping. This bug was here because shadowed luxels does not receive light directions leading to very contrast light direction vectors in light/shadow zones, then texture filtering introduces a bug when interpolating this values. Now deluxels calculated with no shadows (shadowing work is done in lightmap already). + +- added "-keeplights" switch into light phase, this works like "_keeplights 1" world key. World key has greater priority (so if you set -keeplights and _keeplights 0 light entites won't be keeped). Also Per-game "keepLights" setting added, it defaults to True in "dq", "darkplaces" and "prophecy" games. + +- Global floodlight code reverted back to have no effect on deluxemap (it don't add anything and deluxemaps looks blurry). q3map_floodlight shader keyword is changed to have "" parameter in the end unless "low_quality", so now it is q3map_floodlight . Use 0 in to have no effect on deluxemap, 1 to have standart effect like all lights do and 200 and more to make floodlight override deluxemap on this surface (it would be useful for floodlighted water since if some lightsource will be too close it will make dark zone on deluxemap). + +- added printing "entity has vertex normal smoothing with..." when _smoothnormals/_sn/_smooth keys is found, just like _lightmapscale did + +-------------------- +---------------------------------------- +- VERSION R2 +-------------------- +---------- + +- added "_smoothnormals" or "_sn" or "_smooth" to set nonplanar normal smoothing on entities. This works exactly as -shadeangle and overrides shader-set normal smoothing (q3map_shadeangle). + +- reorganized floodlighting code to be more clean + +- fixed bug with floodlight not adding a light direction to deluxemaps + +- removed "q3map_minlight" test code that was added in previous version + +- added "-deluxemode 1" switch to generate tangentspace deluxemaps instead of modelspace. +Actually deluxemaps being converted to tangentspace in StoreLigtmaps phase. "-deluxemode 0" will switch back to modelspace deluxemaps. + +- added game-specific setting of deluxemode. "darkplaces", "dq" and "prophecy" games still have 0 because darkplaces engine can't detect tangentspace deluxemaps on Q3BSP yet (probably detection can be done by setting of custom world field?). + +-------------------- +---------------------------------------- +- VERSION R1 +-------------------- +---------- + +- added shader deprecation keyword "q3map_deprecateShader ", a global variant of q3map_baseShader/q3map_remapShader. Replacing is done in early load stage, so all q3map2 keyworlds are supported (instead of q3map_remapShader which only remaps rendering part of shader so surfaceparms won't work). Maximum of 16 chained deprecations are allowed. In other words if you deprecate the shader by this keyword, it will be showed in map with another name. + + Example deprecated shader: + + // this shader will appear as "textures/#water0" in map + textures/test/test_deprecate_water + { + q3map_deprecateShader textures/#water0 + } + +- added "_patchMeta 1" (or "patchMeta") entity keyword to force entity patch surfaces to be converted to planar brush at compile. This works exactly as "-patchmeta" bsp switch, but only for user customized entities. Additional 2 new keys: "_patchQuality" ("patchQuality") and "_patchSubdivide" ("patchSubdivide") are added. _PatchQuality divides the default patch subdivisions by it's value (2 means 2x more detailed patch). _patchSubdivide overrides patch subdivisions for that entity (use 4 to match Quake3) and ignores _patchQuality. Note: using "_patchMeta" on world makes all world patches to be triangulated, but other entities will remain same. + +- MAX_TW_VERTS increased from 12 to 24 for ability co compile some insane maps with large curve count. + +- added "EmitMetaStats" printing in the end of BSP stage to show full meta statistics, not only for world. So all "_patchMeta 1" surfaces will be in it. + +- added gametype-controlled structure fields for "-deluxe", "-subdivisions", "-nostyles", "-patchshadows". New "dq" (deluxequake), "prophecy" and "darkplaces" games uses them. Additionally added "-nodeluxe", "-nopatchshadows", "-styles" to negate positive defaults. + +- Floodligting code is changed to handle custom surfaces. And "q3map_floodlight " shader keyword was added. Per-surfaces floodlight code does not intersect with global floodlight done by -floodlight of _floodlight worldspawn key, their effects get summarized. This is good way to light up surfacelight surfaces, such as water. + +- added printing of game-specific options (default light gamma, exposure and such) to light phase + +- "func_wall" entities now have _castShadows default to 1. This works only for "dq" and "prophecy" games. + +- added "-samplesize" switch to light phase, this scales samplesizes for all lightmaps, useful to compile map with different lightmap quality. + +- added "_ls" key which duplicates "_lightmapscale" but have short name (order of checking is lightmapscale->_lightmapscale->_ls) + diff --git a/docs/header_gradient.png b/docs/header_gradient.png new file mode 100644 index 0000000..ab9c016 Binary files /dev/null and b/docs/header_gradient.png differ diff --git a/docs/help-top.png b/docs/help-top.png new file mode 100644 index 0000000..5c87017 Binary files /dev/null and b/docs/help-top.png differ diff --git a/docs/history.png b/docs/history.png new file mode 100644 index 0000000..c6a9607 Binary files /dev/null and b/docs/history.png differ diff --git a/docs/house.png b/docs/house.png new file mode 100644 index 0000000..fed6221 Binary files /dev/null and b/docs/house.png differ diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..60d0ed4 --- /dev/null +++ b/docs/index.html @@ -0,0 +1,16 @@ + + + + + + + + + + + +

    Xonotic Mapping Wiki

    + + diff --git a/docs/jstoolbar.css b/docs/jstoolbar.css new file mode 100644 index 0000000..9514e3e --- /dev/null +++ b/docs/jstoolbar.css @@ -0,0 +1,95 @@ +.jstEditor { + padding-left: 0px; +} +.jstEditor textarea, .jstEditor iframe { + margin: 0; +} + +.jstHandle { + height: 10px; + font-size: 0.1em; + cursor: s-resize; + /*background: transparent url(img/resizer.png) no-repeat 45% 50%;*/ +} + +.jstElements { + padding: 3px 3px; +} + +.jstElements button { + margin-right : 6px; + width : 24px; + height: 24px; + padding: 4px; + border-style: solid; + border-width: 1px; + border-color: #ddd; + background-color : #f7f7f7; + background-position : 50% 50%; + background-repeat: no-repeat; +} +.jstElements button:hover { + border-color : #000; +} +.jstElements button span { + display : none; +} +.jstElements span { + display : inline; +} + +.jstSpacer { + width : 0px; + font-size: 1px; + margin-right: 4px; +} + +.jstElements .help { float: right; margin-right: 1em; padding-top: 8px; font-size: 0.9em; } + +/* Buttons +-------------------------------------------------------- */ +.jstb_strong { + background-image: url(../images/jstoolbar/bt_strong.png); +} +.jstb_em { + background-image: url(../images/jstoolbar/bt_em.png); +} +.jstb_ins { + background-image: url(../images/jstoolbar/bt_ins.png); +} +.jstb_del { + background-image: url(../images/jstoolbar/bt_del.png); +} +.jstb_code { + background-image: url(../images/jstoolbar/bt_code.png); +} +.jstb_h1 { + background-image: url(../images/jstoolbar/bt_h1.png); +} +.jstb_h2 { + background-image: url(../images/jstoolbar/bt_h2.png); +} +.jstb_h3 { + background-image: url(../images/jstoolbar/bt_h3.png); +} +.jstb_ul { + background-image: url(../images/jstoolbar/bt_ul.png); +} +.jstb_ol { + background-image: url(../images/jstoolbar/bt_ol.png); +} +.jstb_bq { + background-image: url(../images/jstoolbar/bt_bq.png); +} +.jstb_unbq { + background-image: url(../images/jstoolbar/bt_bq_remove.png); +} +.jstb_pre { + background-image: url(../images/jstoolbar/bt_pre.png); +} +.jstb_link { + background-image: url(../images/jstoolbar/bt_link.png); +} +.jstb_img { + background-image: url(../images/jstoolbar/bt_img.png); +} diff --git a/docs/lightning.png b/docs/lightning.png new file mode 100644 index 0000000..1800099 Binary files /dev/null and b/docs/lightning.png differ diff --git a/docs/mbspc.html b/docs/mbspc.html new file mode 100644 index 0000000..3c9b5f9 --- /dev/null +++ b/docs/mbspc.html @@ -0,0 +1,248 @@ + + + + + + + + + + + + +
    +

    MBSPC -- BSP converter and editing utility

    +

    Copyright (C) 2004-2012 Laszlo Menczel (laszlo.menczel@gmail.com)

    +
    + +

    +This program is distributed under the terms of the GNU General Public +License version 2 as published by the Free Software Foundation. A copy of +it is in the file GPL.TXT. This program comes with ABSOLUTELY NO WARRANTY. + +

    Introduction

    + +

    +This program is based on version 2.1 of the BSPC utility (part of the +toolkit created for the Quake series of games). Originally it used to be +a MAP to BSP converter. For Quake 3 it is mainly used for the creation of AAS +file from BSP files. An AAS file is a file containing area information +used by the Quake III Arena bots to navigate and understand a map. + +

    +MBSPC is a modified (enhanced) version of this utility. I have (re)added +some functions that were either not present originally or were removed +from this version for some reason. So you can also do the following tricks +with MBSPC: + +

      +
    • + convert BSP files to editable MAP format +
    • +
    • + extract the entity list from a BSP file to a text file (new) +
    • +
    • + create a list of the textures used in a BSP file (new, only for Q3A at present) +
    • +
    + +

    +When MBSPC is used for converting BSP files to MAP files, the correct +texture name is written for every brush side. However, correct texture alignment +info (shift, scale, rotation) is not written to the MAP file, when converting Quake 3 maps. +As for Quake 1/2, SiN and Half-Life, alignment info is half right for regular map format (-bsp2map) +and is correct, when using Valve 220 one (-bsp2map220). + +

    +

    Usage

    + +
    +
    +  bspc -option [ -option ... ] input-file
    +
    +
    + +

    +At least one option must be given to specify the desired action. The +rest of the option(s) within the brackets are optional. Examples: + +

    +
    +  mbspc -bsp2aas d:\quake3\baseq3\maps\mymap?.bsp
    +  mbspc -bsp2aas d:\quake3\baseq3\pak0.pk3\maps/q3dm*.bsp
    +  mbspc -bsp2map -output d:\tmp d:\quake3\baseq3\maps\q3dm1.bsp
    +
    +
    + +

    Options

    + +The first section in the table contains obligatory options, i.e. one of +them must be specified so that MBSPC knows what to do. The second section +contains optional parameters that modify the way the action is done. The +third section contains options that are useful only if you want to use +MBSPC for compiling a BSP from a MAP. These are kind of obsolete because for +Quake 1/2/3 there are now better compilers (e.g. q3map2), so MBSPC should +not be used for this purpose. + +

    +List of options recognized by MBSPC: + +

    +
    +   option           argument        explanation                          output
    +   ---------------------------------------------------------------------------------------
    +   bsp2map          file.bsp        create MAP from BSP                  file.map
    +   bsp2map220       file.bsp        create Valve 220 MAP from BSP        file.map
    +   bsp2aas          file.bsp        create AAS from BSP                  file.aas
    +   reach            file.bsp        compute reachability and clusters    file.aas
    +   cluster          file.aas        compute clusters                     file.aas
    +   aasopt           file.aas        optimize aas file                    file.aas
    +   aasinfo          file.aas        show AAS file info
    +   entlist          file.bsp        extract entity list                  file.ent
    +   texinfo          file.bsp        extract texture list                 file.txi
    +   ---------------------------------------------------------------------------------------
    +   output           output-path     set output path
    +   threads          N               set number of threads to N
    +   cfg              file            use configuration data in 'file'
    +   noverbose                        disable verbose output
    +   ---------------------------------------------------------------------------------------
    +   optimize                         enable optimization
    +   breadthfirst                     breadth first bsp building
    +   capsule                          use spherical collision model
    +   nobrushmerge                     don't merge brushes
    +   freetree                         free the bsp tree
    +   nocsg                            disables brush chopping
    +   noliquids                        don't write liquids to map
    +   forcesidesvisible                force all sides to be visible
    +   grapplereach                     calculate grapple reachabilities
    +   lessbrushes                      less brushes when decompiling Q1, HL maps at the expense of texturing
    +
    +
    + +

    +Several metacharacter may be used in the paths of the input file: + +

    +
    +  *          match any string of zero or more characters
    +  ?          match any single character
    +  [abc...]   match any of the enclosed characters; a hyphen can
    +             be used to specify a range (e.g. a-z, A-Z, 0-9)
    +
    +
    + +

    +.pk3 files are accessed as if they are normal folders. For instance +use "d:\quake3\baseq3\pak0.pk3\maps/q3dm1.bsp" to access the +map q3dm1.bsp from the pak0.pk3 file. + +

    +Multiple files may be listed after the switches bsp2map, bsp2aas, reach, +cluster and aasopt. + +

    +If a BSP file is being converted to an AAS file and no output path +is entered on the command-line then the AAS file will automatically +be stored in the same folder as the BSP file. However if the BSP file +was stored in a .pk3 file then the AAS file will be stored in a folder +with the name 'maps' outside the .pk3 file. + +

    How updating the entity lump affects the AAS file?

    + +

    +If an AAS file is already available for a BSP file and you ONLY change +the entities inside this BSP file then you only have to recalculate the reachabilities. +This way you can move items, platforms etc. around without the need to +recalculate the whole AAS file which can save quite some compile time. +You can recalculate the reachabilities as follows: + +

    +
    +  bspc -reach file.bsp
    +
    +
    + +

    +where 'file.bsp' is the updated BSP file. 'file.aas' must exist and must +be in the same folder as 'file.bsp' or in the output folder specified with +the '-output' option. + +

    +Note: the option '-reach' does not work on optimized '.AAS' files. + +

    +Keep in mind that as soon as ANY geometry in the map changes the whole +AAS file HAS to be recalculated in order to play with bots. + +

    Testing AAS files

    + +

    +One of the easiest ways to test the AAS file is to load the map in +Quake3 in teamplay mode (type /set g_gametype 3 on the console before +loading the map). Enter a team and add a bot to your team. Use the +team order menu (by default bound to the key F3) to command the bot +to follow you. Walk around the map and see if the bot is able to +follow you everywhere. + +

    +Map bugs can sometimes cause certain places in the map to show up +'solid' in the AAS file. The bots cannot travel through these 'solid' +areas. To test for these 'solid' places set the cvar bot_testsolid +to 1 on the console. (type /set bot_testsolid 1) The map has to be +started with devmap instead of map before the cvar bot_testsolid can +be set. When the cvar is set to 1 then either "empty area" or +"SOLID area" will be printed on the screen while traveling through a map. + +

    +Several map bugs can cause these 'solid' places in the AAS file. + +

    +
    +- Sometimes microscopic brushes are left over after a brush CSG. Search
    +  for such brushes in the problem area and delete them.
    +
    +- Tiny brush faces (not curves) can also cause problems. Due to vertex
    +  snapping in the q3map tool those tiny brush faces can be snapped out
    +  of existence. Such faces will not show up in Quake3 and you'll see
    +  tiny peek holes or slits where you can view through the geometry.
    +  Align vertexes of and edges of adjacent brushes to remove and avoid
    +  such tiny faces. Placing a clip brush in front of the face that is
    +  snapped out of existence will also remove the 'solid' area but ofcourse
    +  it's much better to remove the peek holes and slits.
    +
    +- Another cause could be a brush with a collapsed side. Check how many
    +  sides a brush has and how many sides actually have a surface. Rebuild
    +  brushes with collapsed sides.
    +
    +- All faces contained within liquid brushes using a shader without
    +  "surfaceparm trans" set will be removed. Those contained surfaces will
    +  not be visible and can cause the lava to appear "solid" in the aas file.
    +
    +
    + +

    +Clusters can be tested with the cvar bot_testclusters. +(type "/set bot_testclusters 1" on the console) + +

    +Jumppads can also be tested. Type the following on the Quake3 console +before loading your map: + +

    +
    +/set bot_maxdebugpolys 1024
    +/set bot_visualizejumppads 1
    +/set bot_forcereachability 1
    +
    +
    + +

    +Now load the map. A counter will be shown and goes from 0% to 100%. +When the counter has reached 100% type /set r_debugSurface 2 on the +console. For every jumppad the default arch of travel (without using +air control) will be visualized. + + + + diff --git a/docs/mouse shortcuts.txt b/docs/mouse shortcuts.txt new file mode 100644 index 0000000..b6a0fb9 --- /dev/null +++ b/docs/mouse shortcuts.txt @@ -0,0 +1,71 @@ + Mouse bindings list + + ** 2d+3d ** + m1 tunnel selector (cycles through matching) + m1 drag create brush, resize, move + shift + m1 select objects + shift + m1 drag paint objects selection + ctrl + m1 select brush face + ctrl + m1 drag paint faces selection + shift + m2 tunnel selector + shift + m2 drag rectangular selector (select/deselect/toggle, complete depth, partial match) + ctrl + m2 tunnel face selector + ctrl + m2 drag rectangular selector of brush faces + alt + m1 select objects in component modes + ctrl + alt + m1 extrude pointed/selected brush faces + drag m1 + shift snapped modes of manipulators, clipper; new brush is quadratic + drag m1 + ctrl snap bbox, while using move and scale manipulators; new brush is cube + + + ** 2d ** + m2 drag move pan + m2 entities creation menu + alt + m2 drag quick zoom in/out + alt + m1 quick face/vertex shear in QE tool mode + ctrl + m1 quick clipper mode + m1 x2 on clipper point = do clip + m3 change 3d camera sight direction + ctrl + m3 move 3d camera location + shift + m3 set transform origin in pivoted mode + + + ** 3d ** + alt + m1 alternative objects resizing + m1 + alt adjust height of brush, being created; move stuff vertically + m2 enter/quit 3d mouselook mode, sideways + up/down strafes + alt + m2 orbit around clicked point + m2 x2 entities creation menu + m3 copy texture name, alignment, color, light power, color + focus on texture in texture browser, fill find/replace dialog entries + alt + m3/drag paste texture name (to pointed and selected stuff) + shift + m3/drag paste texture name, alignment, light power + ctrl + m3/drag paste texture seamlessly between brush faces, patches, light color + ctrl + shift + m3/drag project texture from copied brush face, paste light power, color + alt + ctrl/shift/ + /ctrl+shift + m3/drag respective texture alignment paste w/o texture name + ctrl + mouse sideways + up/down strafes + ctrl + shift + mouse sideways + forward/back strafes + + + **texture browser** + m1 select texture + apply to selection + shift + m1 open shader in internal editor + ctrl + shift + m1 open shader in external editor + m2 show tags frame + m2 drag scroll pan + drag m2 + shift scroll pan 4x faster + m1 x2 load directory, selected texture belongs to + m2 x2 load common/ directory + m3 select texture, don't apply to selection + + + **entities creation menu** + m1 create an entity + worldspawn = ungroup selected primitives + m2 convert selected entities + worldspawn = ungroup selected entities + ctrl + m1/2 do things, keep menu opened + + + + scroll: 2d: zoom; 3d: move; tex bro: scroll diff --git a/docs/newspaper.png b/docs/newspaper.png new file mode 100644 index 0000000..70e7e5a Binary files /dev/null and b/docs/newspaper.png differ diff --git a/docs/projects.png b/docs/projects.png new file mode 100644 index 0000000..073c721 Binary files /dev/null and b/docs/projects.png differ diff --git a/docs/prototype.js b/docs/prototype.js new file mode 100644 index 0000000..b535908 --- /dev/null +++ b/docs/prototype.js @@ -0,0 +1,4320 @@ +/* Prototype JavaScript framework, version 1.6.0.3 + * (c) 2005-2008 Sam Stephenson + * + * Prototype is freely distributable under the terms of an MIT-style license. + * For details, see the Prototype web site: http://www.prototypejs.org/ + * + *--------------------------------------------------------------------------*/ + +var Prototype = { + Version: '1.6.0.3', + + Browser: { + IE: !!(window.attachEvent && + navigator.userAgent.indexOf('Opera') === -1), + Opera: navigator.userAgent.indexOf('Opera') > -1, + WebKit: navigator.userAgent.indexOf('AppleWebKit/') > -1, + Gecko: navigator.userAgent.indexOf('Gecko') > -1 && + navigator.userAgent.indexOf('KHTML') === -1, + MobileSafari: !!navigator.userAgent.match(/Apple.*Mobile.*Safari/) + }, + + BrowserFeatures: { + XPath: !!document.evaluate, + SelectorsAPI: !!document.querySelector, + ElementExtensions: !!window.HTMLElement, + SpecificElementExtensions: + document.createElement('div')['__proto__'] && + document.createElement('div')['__proto__'] !== + document.createElement('form')['__proto__'] + }, + + ScriptFragment: ']*>([\\S\\s]*?)<\/script>', + JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/, + + emptyFunction: function() { }, + K: function(x) { return x } +}; + +if (Prototype.Browser.MobileSafari) + Prototype.BrowserFeatures.SpecificElementExtensions = false; + + +/* Based on Alex Arnell's inheritance implementation. */ +var Class = { + create: function() { + var parent = null, properties = $A(arguments); + if (Object.isFunction(properties[0])) + parent = properties.shift(); + + function klass() { + this.initialize.apply(this, arguments); + } + + Object.extend(klass, Class.Methods); + klass.superclass = parent; + klass.subclasses = []; + + if (parent) { + var subclass = function() { }; + subclass.prototype = parent.prototype; + klass.prototype = new subclass; + parent.subclasses.push(klass); + } + + for (var i = 0; i < properties.length; i++) + klass.addMethods(properties[i]); + + if (!klass.prototype.initialize) + klass.prototype.initialize = Prototype.emptyFunction; + + klass.prototype.constructor = klass; + + return klass; + } +}; + +Class.Methods = { + addMethods: function(source) { + var ancestor = this.superclass && this.superclass.prototype; + var properties = Object.keys(source); + + if (!Object.keys({ toString: true }).length) + properties.push("toString", "valueOf"); + + for (var i = 0, length = properties.length; i < length; i++) { + var property = properties[i], value = source[property]; + if (ancestor && Object.isFunction(value) && + value.argumentNames().first() == "$super") { + var method = value; + value = (function(m) { + return function() { return ancestor[m].apply(this, arguments) }; + })(property).wrap(method); + + value.valueOf = method.valueOf.bind(method); + value.toString = method.toString.bind(method); + } + this.prototype[property] = value; + } + + return this; + } +}; + +var Abstract = { }; + +Object.extend = function(destination, source) { + for (var property in source) + destination[property] = source[property]; + return destination; +}; + +Object.extend(Object, { + inspect: function(object) { + try { + if (Object.isUndefined(object)) return 'undefined'; + if (object === null) return 'null'; + return object.inspect ? object.inspect() : String(object); + } catch (e) { + if (e instanceof RangeError) return '...'; + throw e; + } + }, + + toJSON: function(object) { + var type = typeof object; + switch (type) { + case 'undefined': + case 'function': + case 'unknown': return; + case 'boolean': return object.toString(); + } + + if (object === null) return 'null'; + if (object.toJSON) return object.toJSON(); + if (Object.isElement(object)) return; + + var results = []; + for (var property in object) { + var value = Object.toJSON(object[property]); + if (!Object.isUndefined(value)) + results.push(property.toJSON() + ': ' + value); + } + + return '{' + results.join(', ') + '}'; + }, + + toQueryString: function(object) { + return $H(object).toQueryString(); + }, + + toHTML: function(object) { + return object && object.toHTML ? object.toHTML() : String.interpret(object); + }, + + keys: function(object) { + var keys = []; + for (var property in object) + keys.push(property); + return keys; + }, + + values: function(object) { + var values = []; + for (var property in object) + values.push(object[property]); + return values; + }, + + clone: function(object) { + return Object.extend({ }, object); + }, + + isElement: function(object) { + return !!(object && object.nodeType == 1); + }, + + isArray: function(object) { + return object != null && typeof object == "object" && + 'splice' in object && 'join' in object; + }, + + isHash: function(object) { + return object instanceof Hash; + }, + + isFunction: function(object) { + return typeof object == "function"; + }, + + isString: function(object) { + return typeof object == "string"; + }, + + isNumber: function(object) { + return typeof object == "number"; + }, + + isUndefined: function(object) { + return typeof object == "undefined"; + } +}); + +Object.extend(Function.prototype, { + argumentNames: function() { + var names = this.toString().match(/^[\s\(]*function[^(]*\(([^\)]*)\)/)[1] + .replace(/\s+/g, '').split(','); + return names.length == 1 && !names[0] ? [] : names; + }, + + bind: function() { + if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this; + var __method = this, args = $A(arguments), object = args.shift(); + return function() { + return __method.apply(object, args.concat($A(arguments))); + } + }, + + bindAsEventListener: function() { + var __method = this, args = $A(arguments), object = args.shift(); + return function(event) { + return __method.apply(object, [event || window.event].concat(args)); + } + }, + + curry: function() { + if (!arguments.length) return this; + var __method = this, args = $A(arguments); + return function() { + return __method.apply(this, args.concat($A(arguments))); + } + }, + + delay: function() { + var __method = this, args = $A(arguments), timeout = args.shift() * 1000; + return window.setTimeout(function() { + return __method.apply(__method, args); + }, timeout); + }, + + defer: function() { + var args = [0.01].concat($A(arguments)); + return this.delay.apply(this, args); + }, + + wrap: function(wrapper) { + var __method = this; + return function() { + return wrapper.apply(this, [__method.bind(this)].concat($A(arguments))); + } + }, + + methodize: function() { + if (this._methodized) return this._methodized; + var __method = this; + return this._methodized = function() { + return __method.apply(null, [this].concat($A(arguments))); + }; + } +}); + +Date.prototype.toJSON = function() { + return '"' + this.getUTCFullYear() + '-' + + (this.getUTCMonth() + 1).toPaddedString(2) + '-' + + this.getUTCDate().toPaddedString(2) + 'T' + + this.getUTCHours().toPaddedString(2) + ':' + + this.getUTCMinutes().toPaddedString(2) + ':' + + this.getUTCSeconds().toPaddedString(2) + 'Z"'; +}; + +var Try = { + these: function() { + var returnValue; + + for (var i = 0, length = arguments.length; i < length; i++) { + var lambda = arguments[i]; + try { + returnValue = lambda(); + break; + } catch (e) { } + } + + return returnValue; + } +}; + +RegExp.prototype.match = RegExp.prototype.test; + +RegExp.escape = function(str) { + return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1'); +}; + +/*--------------------------------------------------------------------------*/ + +var PeriodicalExecuter = Class.create({ + initialize: function(callback, frequency) { + this.callback = callback; + this.frequency = frequency; + this.currentlyExecuting = false; + + this.registerCallback(); + }, + + registerCallback: function() { + this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000); + }, + + execute: function() { + this.callback(this); + }, + + stop: function() { + if (!this.timer) return; + clearInterval(this.timer); + this.timer = null; + }, + + onTimerEvent: function() { + if (!this.currentlyExecuting) { + try { + this.currentlyExecuting = true; + this.execute(); + } finally { + this.currentlyExecuting = false; + } + } + } +}); +Object.extend(String, { + interpret: function(value) { + return value == null ? '' : String(value); + }, + specialChar: { + '\b': '\\b', + '\t': '\\t', + '\n': '\\n', + '\f': '\\f', + '\r': '\\r', + '\\': '\\\\' + } +}); + +Object.extend(String.prototype, { + gsub: function(pattern, replacement) { + var result = '', source = this, match; + replacement = arguments.callee.prepareReplacement(replacement); + + while (source.length > 0) { + if (match = source.match(pattern)) { + result += source.slice(0, match.index); + result += String.interpret(replacement(match)); + source = source.slice(match.index + match[0].length); + } else { + result += source, source = ''; + } + } + return result; + }, + + sub: function(pattern, replacement, count) { + replacement = this.gsub.prepareReplacement(replacement); + count = Object.isUndefined(count) ? 1 : count; + + return this.gsub(pattern, function(match) { + if (--count < 0) return match[0]; + return replacement(match); + }); + }, + + scan: function(pattern, iterator) { + this.gsub(pattern, iterator); + return String(this); + }, + + truncate: function(length, truncation) { + length = length || 30; + truncation = Object.isUndefined(truncation) ? '...' : truncation; + return this.length > length ? + this.slice(0, length - truncation.length) + truncation : String(this); + }, + + strip: function() { + return this.replace(/^\s+/, '').replace(/\s+$/, ''); + }, + + stripTags: function() { + return this.replace(/<\/?[^>]+>/gi, ''); + }, + + stripScripts: function() { + return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), ''); + }, + + extractScripts: function() { + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); + var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + return (this.match(matchAll) || []).map(function(scriptTag) { + return (scriptTag.match(matchOne) || ['', ''])[1]; + }); + }, + + evalScripts: function() { + return this.extractScripts().map(function(script) { return eval(script) }); + }, + + escapeHTML: function() { + var self = arguments.callee; + self.text.data = this; + return self.div.innerHTML; + }, + + unescapeHTML: function() { + var div = new Element('div'); + div.innerHTML = this.stripTags(); + return div.childNodes[0] ? (div.childNodes.length > 1 ? + $A(div.childNodes).inject('', function(memo, node) { return memo+node.nodeValue }) : + div.childNodes[0].nodeValue) : ''; + }, + + toQueryParams: function(separator) { + var match = this.strip().match(/([^?#]*)(#.*)?$/); + if (!match) return { }; + + return match[1].split(separator || '&').inject({ }, function(hash, pair) { + if ((pair = pair.split('='))[0]) { + var key = decodeURIComponent(pair.shift()); + var value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); + + if (key in hash) { + if (!Object.isArray(hash[key])) hash[key] = [hash[key]]; + hash[key].push(value); + } + else hash[key] = value; + } + return hash; + }); + }, + + toArray: function() { + return this.split(''); + }, + + succ: function() { + return this.slice(0, this.length - 1) + + String.fromCharCode(this.charCodeAt(this.length - 1) + 1); + }, + + times: function(count) { + return count < 1 ? '' : new Array(count + 1).join(this); + }, + + camelize: function() { + var parts = this.split('-'), len = parts.length; + if (len == 1) return parts[0]; + + var camelized = this.charAt(0) == '-' + ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1) + : parts[0]; + + for (var i = 1; i < len; i++) + camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1); + + return camelized; + }, + + capitalize: function() { + return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); + }, + + underscore: function() { + return this.gsub(/::/, '/').gsub(/([A-Z]+)([A-Z][a-z])/,'#{1}_#{2}').gsub(/([a-z\d])([A-Z])/,'#{1}_#{2}').gsub(/-/,'_').toLowerCase(); + }, + + dasherize: function() { + return this.gsub(/_/,'-'); + }, + + inspect: function(useDoubleQuotes) { + var escapedString = this.gsub(/[\x00-\x1f\\]/, function(match) { + var character = String.specialChar[match[0]]; + return character ? character : '\\u00' + match[0].charCodeAt().toPaddedString(2, 16); + }); + if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"'; + return "'" + escapedString.replace(/'/g, '\\\'') + "'"; + }, + + toJSON: function() { + return this.inspect(true); + }, + + unfilterJSON: function(filter) { + return this.sub(filter || Prototype.JSONFilter, '#{1}'); + }, + + isJSON: function() { + var str = this; + if (str.blank()) return false; + str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''); + return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str); + }, + + evalJSON: function(sanitize) { + var json = this.unfilterJSON(); + try { + if (!sanitize || json.isJSON()) return eval('(' + json + ')'); + } catch (e) { } + throw new SyntaxError('Badly formed JSON string: ' + this.inspect()); + }, + + include: function(pattern) { + return this.indexOf(pattern) > -1; + }, + + startsWith: function(pattern) { + return this.indexOf(pattern) === 0; + }, + + endsWith: function(pattern) { + var d = this.length - pattern.length; + return d >= 0 && this.lastIndexOf(pattern) === d; + }, + + empty: function() { + return this == ''; + }, + + blank: function() { + return /^\s*$/.test(this); + }, + + interpolate: function(object, pattern) { + return new Template(this, pattern).evaluate(object); + } +}); + +if (Prototype.Browser.WebKit || Prototype.Browser.IE) Object.extend(String.prototype, { + escapeHTML: function() { + return this.replace(/&/g,'&').replace(//g,'>'); + }, + unescapeHTML: function() { + return this.stripTags().replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>'); + } +}); + +String.prototype.gsub.prepareReplacement = function(replacement) { + if (Object.isFunction(replacement)) return replacement; + var template = new Template(replacement); + return function(match) { return template.evaluate(match) }; +}; + +String.prototype.parseQuery = String.prototype.toQueryParams; + +Object.extend(String.prototype.escapeHTML, { + div: document.createElement('div'), + text: document.createTextNode('') +}); + +String.prototype.escapeHTML.div.appendChild(String.prototype.escapeHTML.text); + +var Template = Class.create({ + initialize: function(template, pattern) { + this.template = template.toString(); + this.pattern = pattern || Template.Pattern; + }, + + evaluate: function(object) { + if (Object.isFunction(object.toTemplateReplacements)) + object = object.toTemplateReplacements(); + + return this.template.gsub(this.pattern, function(match) { + if (object == null) return ''; + + var before = match[1] || ''; + if (before == '\\') return match[2]; + + var ctx = object, expr = match[3]; + var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); + if (match == null) return before; + + while (match != null) { + var comp = match[1].startsWith('[') ? match[2].gsub('\\\\]', ']') : match[1]; + ctx = ctx[comp]; + if (null == ctx || '' == match[3]) break; + expr = expr.substring('[' == match[3] ? match[1].length : match[0].length); + match = pattern.exec(expr); + } + + return before + String.interpret(ctx); + }); + } +}); +Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/; + +var $break = { }; + +var Enumerable = { + each: function(iterator, context) { + var index = 0; + try { + this._each(function(value) { + iterator.call(context, value, index++); + }); + } catch (e) { + if (e != $break) throw e; + } + return this; + }, + + eachSlice: function(number, iterator, context) { + var index = -number, slices = [], array = this.toArray(); + if (number < 1) return array; + while ((index += number) < array.length) + slices.push(array.slice(index, index+number)); + return slices.collect(iterator, context); + }, + + all: function(iterator, context) { + iterator = iterator || Prototype.K; + var result = true; + this.each(function(value, index) { + result = result && !!iterator.call(context, value, index); + if (!result) throw $break; + }); + return result; + }, + + any: function(iterator, context) { + iterator = iterator || Prototype.K; + var result = false; + this.each(function(value, index) { + if (result = !!iterator.call(context, value, index)) + throw $break; + }); + return result; + }, + + collect: function(iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + this.each(function(value, index) { + results.push(iterator.call(context, value, index)); + }); + return results; + }, + + detect: function(iterator, context) { + var result; + this.each(function(value, index) { + if (iterator.call(context, value, index)) { + result = value; + throw $break; + } + }); + return result; + }, + + findAll: function(iterator, context) { + var results = []; + this.each(function(value, index) { + if (iterator.call(context, value, index)) + results.push(value); + }); + return results; + }, + + grep: function(filter, iterator, context) { + iterator = iterator || Prototype.K; + var results = []; + + if (Object.isString(filter)) + filter = new RegExp(filter); + + this.each(function(value, index) { + if (filter.match(value)) + results.push(iterator.call(context, value, index)); + }); + return results; + }, + + include: function(object) { + if (Object.isFunction(this.indexOf)) + if (this.indexOf(object) != -1) return true; + + var found = false; + this.each(function(value) { + if (value == object) { + found = true; + throw $break; + } + }); + return found; + }, + + inGroupsOf: function(number, fillWith) { + fillWith = Object.isUndefined(fillWith) ? null : fillWith; + return this.eachSlice(number, function(slice) { + while(slice.length < number) slice.push(fillWith); + return slice; + }); + }, + + inject: function(memo, iterator, context) { + this.each(function(value, index) { + memo = iterator.call(context, memo, value, index); + }); + return memo; + }, + + invoke: function(method) { + var args = $A(arguments).slice(1); + return this.map(function(value) { + return value[method].apply(value, args); + }); + }, + + max: function(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value >= result) + result = value; + }); + return result; + }, + + min: function(iterator, context) { + iterator = iterator || Prototype.K; + var result; + this.each(function(value, index) { + value = iterator.call(context, value, index); + if (result == null || value < result) + result = value; + }); + return result; + }, + + partition: function(iterator, context) { + iterator = iterator || Prototype.K; + var trues = [], falses = []; + this.each(function(value, index) { + (iterator.call(context, value, index) ? + trues : falses).push(value); + }); + return [trues, falses]; + }, + + pluck: function(property) { + var results = []; + this.each(function(value) { + results.push(value[property]); + }); + return results; + }, + + reject: function(iterator, context) { + var results = []; + this.each(function(value, index) { + if (!iterator.call(context, value, index)) + results.push(value); + }); + return results; + }, + + sortBy: function(iterator, context) { + return this.map(function(value, index) { + return { + value: value, + criteria: iterator.call(context, value, index) + }; + }).sort(function(left, right) { + var a = left.criteria, b = right.criteria; + return a < b ? -1 : a > b ? 1 : 0; + }).pluck('value'); + }, + + toArray: function() { + return this.map(); + }, + + zip: function() { + var iterator = Prototype.K, args = $A(arguments); + if (Object.isFunction(args.last())) + iterator = args.pop(); + + var collections = [this].concat(args).map($A); + return this.map(function(value, index) { + return iterator(collections.pluck(index)); + }); + }, + + size: function() { + return this.toArray().length; + }, + + inspect: function() { + return '#'; + } +}; + +Object.extend(Enumerable, { + map: Enumerable.collect, + find: Enumerable.detect, + select: Enumerable.findAll, + filter: Enumerable.findAll, + member: Enumerable.include, + entries: Enumerable.toArray, + every: Enumerable.all, + some: Enumerable.any +}); +function $A(iterable) { + if (!iterable) return []; + if (iterable.toArray) return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; +} + +if (Prototype.Browser.WebKit) { + $A = function(iterable) { + if (!iterable) return []; + // In Safari, only use the `toArray` method if it's not a NodeList. + // A NodeList is a function, has an function `item` property, and a numeric + // `length` property. Adapted from Google Doctype. + if (!(typeof iterable === 'function' && typeof iterable.length === + 'number' && typeof iterable.item === 'function') && iterable.toArray) + return iterable.toArray(); + var length = iterable.length || 0, results = new Array(length); + while (length--) results[length] = iterable[length]; + return results; + }; +} + +Array.from = $A; + +Object.extend(Array.prototype, Enumerable); + +if (!Array.prototype._reverse) Array.prototype._reverse = Array.prototype.reverse; + +Object.extend(Array.prototype, { + _each: function(iterator) { + for (var i = 0, length = this.length; i < length; i++) + iterator(this[i]); + }, + + clear: function() { + this.length = 0; + return this; + }, + + first: function() { + return this[0]; + }, + + last: function() { + return this[this.length - 1]; + }, + + compact: function() { + return this.select(function(value) { + return value != null; + }); + }, + + flatten: function() { + return this.inject([], function(array, value) { + return array.concat(Object.isArray(value) ? + value.flatten() : [value]); + }); + }, + + without: function() { + var values = $A(arguments); + return this.select(function(value) { + return !values.include(value); + }); + }, + + reverse: function(inline) { + return (inline !== false ? this : this.toArray())._reverse(); + }, + + reduce: function() { + return this.length > 1 ? this : this[0]; + }, + + uniq: function(sorted) { + return this.inject([], function(array, value, index) { + if (0 == index || (sorted ? array.last() != value : !array.include(value))) + array.push(value); + return array; + }); + }, + + intersect: function(array) { + return this.uniq().findAll(function(item) { + return array.detect(function(value) { return item === value }); + }); + }, + + clone: function() { + return [].concat(this); + }, + + size: function() { + return this.length; + }, + + inspect: function() { + return '[' + this.map(Object.inspect).join(', ') + ']'; + }, + + toJSON: function() { + var results = []; + this.each(function(object) { + var value = Object.toJSON(object); + if (!Object.isUndefined(value)) results.push(value); + }); + return '[' + results.join(', ') + ']'; + } +}); + +// use native browser JS 1.6 implementation if available +if (Object.isFunction(Array.prototype.forEach)) + Array.prototype._each = Array.prototype.forEach; + +if (!Array.prototype.indexOf) Array.prototype.indexOf = function(item, i) { + i || (i = 0); + var length = this.length; + if (i < 0) i = length + i; + for (; i < length; i++) + if (this[i] === item) return i; + return -1; +}; + +if (!Array.prototype.lastIndexOf) Array.prototype.lastIndexOf = function(item, i) { + i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1; + var n = this.slice(0, i).reverse().indexOf(item); + return (n < 0) ? n : i - n - 1; +}; + +Array.prototype.toArray = Array.prototype.clone; + +function $w(string) { + if (!Object.isString(string)) return []; + string = string.strip(); + return string ? string.split(/\s+/) : []; +} + +if (Prototype.Browser.Opera){ + Array.prototype.concat = function() { + var array = []; + for (var i = 0, length = this.length; i < length; i++) array.push(this[i]); + for (var i = 0, length = arguments.length; i < length; i++) { + if (Object.isArray(arguments[i])) { + for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++) + array.push(arguments[i][j]); + } else { + array.push(arguments[i]); + } + } + return array; + }; +} +Object.extend(Number.prototype, { + toColorPart: function() { + return this.toPaddedString(2, 16); + }, + + succ: function() { + return this + 1; + }, + + times: function(iterator, context) { + $R(0, this, true).each(iterator, context); + return this; + }, + + toPaddedString: function(length, radix) { + var string = this.toString(radix || 10); + return '0'.times(length - string.length) + string; + }, + + toJSON: function() { + return isFinite(this) ? this.toString() : 'null'; + } +}); + +$w('abs round ceil floor').each(function(method){ + Number.prototype[method] = Math[method].methodize(); +}); +function $H(object) { + return new Hash(object); +}; + +var Hash = Class.create(Enumerable, (function() { + + function toQueryPair(key, value) { + if (Object.isUndefined(value)) return key; + return key + '=' + encodeURIComponent(String.interpret(value)); + } + + return { + initialize: function(object) { + this._object = Object.isHash(object) ? object.toObject() : Object.clone(object); + }, + + _each: function(iterator) { + for (var key in this._object) { + var value = this._object[key], pair = [key, value]; + pair.key = key; + pair.value = value; + iterator(pair); + } + }, + + set: function(key, value) { + return this._object[key] = value; + }, + + get: function(key) { + // simulating poorly supported hasOwnProperty + if (this._object[key] !== Object.prototype[key]) + return this._object[key]; + }, + + unset: function(key) { + var value = this._object[key]; + delete this._object[key]; + return value; + }, + + toObject: function() { + return Object.clone(this._object); + }, + + keys: function() { + return this.pluck('key'); + }, + + values: function() { + return this.pluck('value'); + }, + + index: function(value) { + var match = this.detect(function(pair) { + return pair.value === value; + }); + return match && match.key; + }, + + merge: function(object) { + return this.clone().update(object); + }, + + update: function(object) { + return new Hash(object).inject(this, function(result, pair) { + result.set(pair.key, pair.value); + return result; + }); + }, + + toQueryString: function() { + return this.inject([], function(results, pair) { + var key = encodeURIComponent(pair.key), values = pair.value; + + if (values && typeof values == 'object') { + if (Object.isArray(values)) + return results.concat(values.map(toQueryPair.curry(key))); + } else results.push(toQueryPair(key, values)); + return results; + }).join('&'); + }, + + inspect: function() { + return '#'; + }, + + toJSON: function() { + return Object.toJSON(this.toObject()); + }, + + clone: function() { + return new Hash(this); + } + } +})()); + +Hash.prototype.toTemplateReplacements = Hash.prototype.toObject; +Hash.from = $H; +var ObjectRange = Class.create(Enumerable, { + initialize: function(start, end, exclusive) { + this.start = start; + this.end = end; + this.exclusive = exclusive; + }, + + _each: function(iterator) { + var value = this.start; + while (this.include(value)) { + iterator(value); + value = value.succ(); + } + }, + + include: function(value) { + if (value < this.start) + return false; + if (this.exclusive) + return value < this.end; + return value <= this.end; + } +}); + +var $R = function(start, end, exclusive) { + return new ObjectRange(start, end, exclusive); +}; + +var Ajax = { + getTransport: function() { + return Try.these( + function() {return new XMLHttpRequest()}, + function() {return new ActiveXObject('Msxml2.XMLHTTP')}, + function() {return new ActiveXObject('Microsoft.XMLHTTP')} + ) || false; + }, + + activeRequestCount: 0 +}; + +Ajax.Responders = { + responders: [], + + _each: function(iterator) { + this.responders._each(iterator); + }, + + register: function(responder) { + if (!this.include(responder)) + this.responders.push(responder); + }, + + unregister: function(responder) { + this.responders = this.responders.without(responder); + }, + + dispatch: function(callback, request, transport, json) { + this.each(function(responder) { + if (Object.isFunction(responder[callback])) { + try { + responder[callback].apply(responder, [request, transport, json]); + } catch (e) { } + } + }); + } +}; + +Object.extend(Ajax.Responders, Enumerable); + +Ajax.Responders.register({ + onCreate: function() { Ajax.activeRequestCount++ }, + onComplete: function() { Ajax.activeRequestCount-- } +}); + +Ajax.Base = Class.create({ + initialize: function(options) { + this.options = { + method: 'post', + asynchronous: true, + contentType: 'application/x-www-form-urlencoded', + encoding: 'UTF-8', + parameters: '', + evalJSON: true, + evalJS: true + }; + Object.extend(this.options, options || { }); + + this.options.method = this.options.method.toLowerCase(); + + if (Object.isString(this.options.parameters)) + this.options.parameters = this.options.parameters.toQueryParams(); + else if (Object.isHash(this.options.parameters)) + this.options.parameters = this.options.parameters.toObject(); + } +}); + +Ajax.Request = Class.create(Ajax.Base, { + _complete: false, + + initialize: function($super, url, options) { + $super(options); + this.transport = Ajax.getTransport(); + this.request(url); + }, + + request: function(url) { + this.url = url; + this.method = this.options.method; + var params = Object.clone(this.options.parameters); + + if (!['get', 'post'].include(this.method)) { + // simulate other verbs over post + params['_method'] = this.method; + this.method = 'post'; + } + + this.parameters = params; + + if (params = Object.toQueryString(params)) { + // when GET, append parameters to URL + if (this.method == 'get') + this.url += (this.url.include('?') ? '&' : '?') + params; + else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent)) + params += '&_='; + } + + try { + var response = new Ajax.Response(this); + if (this.options.onCreate) this.options.onCreate(response); + Ajax.Responders.dispatch('onCreate', this, response); + + this.transport.open(this.method.toUpperCase(), this.url, + this.options.asynchronous); + + if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1); + + this.transport.onreadystatechange = this.onStateChange.bind(this); + this.setRequestHeaders(); + + this.body = this.method == 'post' ? (this.options.postBody || params) : null; + this.transport.send(this.body); + + /* Force Firefox to handle ready state 4 for synchronous requests */ + if (!this.options.asynchronous && this.transport.overrideMimeType) + this.onStateChange(); + + } + catch (e) { + this.dispatchException(e); + } + }, + + onStateChange: function() { + var readyState = this.transport.readyState; + if (readyState > 1 && !((readyState == 4) && this._complete)) + this.respondToReadyState(this.transport.readyState); + }, + + setRequestHeaders: function() { + var headers = { + 'X-Requested-With': 'XMLHttpRequest', + 'X-Prototype-Version': Prototype.Version, + 'Accept': 'text/javascript, text/html, application/xml, text/xml, */*' + }; + + if (this.method == 'post') { + headers['Content-type'] = this.options.contentType + + (this.options.encoding ? '; charset=' + this.options.encoding : ''); + + /* Force "Connection: close" for older Mozilla browsers to work + * around a bug where XMLHttpRequest sends an incorrect + * Content-length header. See Mozilla Bugzilla #246651. + */ + if (this.transport.overrideMimeType && + (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005) + headers['Connection'] = 'close'; + } + + // user-defined headers + if (typeof this.options.requestHeaders == 'object') { + var extras = this.options.requestHeaders; + + if (Object.isFunction(extras.push)) + for (var i = 0, length = extras.length; i < length; i += 2) + headers[extras[i]] = extras[i+1]; + else + $H(extras).each(function(pair) { headers[pair.key] = pair.value }); + } + + for (var name in headers) + this.transport.setRequestHeader(name, headers[name]); + }, + + success: function() { + var status = this.getStatus(); + return !status || (status >= 200 && status < 300); + }, + + getStatus: function() { + try { + return this.transport.status || 0; + } catch (e) { return 0 } + }, + + respondToReadyState: function(readyState) { + var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this); + + if (state == 'Complete') { + try { + this._complete = true; + (this.options['on' + response.status] + || this.options['on' + (this.success() ? 'Success' : 'Failure')] + || Prototype.emptyFunction)(response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + var contentType = response.getHeader('Content-type'); + if (this.options.evalJS == 'force' + || (this.options.evalJS && this.isSameOrigin() && contentType + && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i))) + this.evalResponse(); + } + + try { + (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON); + Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON); + } catch (e) { + this.dispatchException(e); + } + + if (state == 'Complete') { + // avoid memory leak in MSIE: clean up + this.transport.onreadystatechange = Prototype.emptyFunction; + } + }, + + isSameOrigin: function() { + var m = this.url.match(/^\s*https?:\/\/[^\/]*/); + return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({ + protocol: location.protocol, + domain: document.domain, + port: location.port ? ':' + location.port : '' + })); + }, + + getHeader: function(name) { + try { + return this.transport.getResponseHeader(name) || null; + } catch (e) { return null } + }, + + evalResponse: function() { + try { + return eval((this.transport.responseText || '').unfilterJSON()); + } catch (e) { + this.dispatchException(e); + } + }, + + dispatchException: function(exception) { + (this.options.onException || Prototype.emptyFunction)(this, exception); + Ajax.Responders.dispatch('onException', this, exception); + } +}); + +Ajax.Request.Events = + ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete']; + +Ajax.Response = Class.create({ + initialize: function(request){ + this.request = request; + var transport = this.transport = request.transport, + readyState = this.readyState = transport.readyState; + + if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + this.status = this.getStatus(); + this.statusText = this.getStatusText(); + this.responseText = String.interpret(transport.responseText); + this.headerJSON = this._getHeaderJSON(); + } + + if(readyState == 4) { + var xml = transport.responseXML; + this.responseXML = Object.isUndefined(xml) ? null : xml; + this.responseJSON = this._getResponseJSON(); + } + }, + + status: 0, + statusText: '', + + getStatus: Ajax.Request.prototype.getStatus, + + getStatusText: function() { + try { + return this.transport.statusText || ''; + } catch (e) { return '' } + }, + + getHeader: Ajax.Request.prototype.getHeader, + + getAllHeaders: function() { + try { + return this.getAllResponseHeaders(); + } catch (e) { return null } + }, + + getResponseHeader: function(name) { + return this.transport.getResponseHeader(name); + }, + + getAllResponseHeaders: function() { + return this.transport.getAllResponseHeaders(); + }, + + _getHeaderJSON: function() { + var json = this.getHeader('X-JSON'); + if (!json) return null; + json = decodeURIComponent(escape(json)); + try { + return json.evalJSON(this.request.options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + }, + + _getResponseJSON: function() { + var options = this.request.options; + if (!options.evalJSON || (options.evalJSON != 'force' && + !(this.getHeader('Content-type') || '').include('application/json')) || + this.responseText.blank()) + return null; + try { + return this.responseText.evalJSON(options.sanitizeJSON || + !this.request.isSameOrigin()); + } catch (e) { + this.request.dispatchException(e); + } + } +}); + +Ajax.Updater = Class.create(Ajax.Request, { + initialize: function($super, container, url, options) { + this.container = { + success: (container.success || container), + failure: (container.failure || (container.success ? null : container)) + }; + + options = Object.clone(options); + var onComplete = options.onComplete; + options.onComplete = (function(response, json) { + this.updateContent(response.responseText); + if (Object.isFunction(onComplete)) onComplete(response, json); + }).bind(this); + + $super(url, options); + }, + + updateContent: function(responseText) { + var receiver = this.container[this.success() ? 'success' : 'failure'], + options = this.options; + + if (!options.evalScripts) responseText = responseText.stripScripts(); + + if (receiver = $(receiver)) { + if (options.insertion) { + if (Object.isString(options.insertion)) { + var insertion = { }; insertion[options.insertion] = responseText; + receiver.insert(insertion); + } + else options.insertion(receiver, responseText); + } + else receiver.update(responseText); + } + } +}); + +Ajax.PeriodicalUpdater = Class.create(Ajax.Base, { + initialize: function($super, container, url, options) { + $super(options); + this.onComplete = this.options.onComplete; + + this.frequency = (this.options.frequency || 2); + this.decay = (this.options.decay || 1); + + this.updater = { }; + this.container = container; + this.url = url; + + this.start(); + }, + + start: function() { + this.options.onComplete = this.updateComplete.bind(this); + this.onTimerEvent(); + }, + + stop: function() { + this.updater.options.onComplete = undefined; + clearTimeout(this.timer); + (this.onComplete || Prototype.emptyFunction).apply(this, arguments); + }, + + updateComplete: function(response) { + if (this.options.decay) { + this.decay = (response.responseText == this.lastText ? + this.decay * this.options.decay : 1); + + this.lastText = response.responseText; + } + this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency); + }, + + onTimerEvent: function() { + this.updater = new Ajax.Updater(this.container, this.url, this.options); + } +}); +function $(element) { + if (arguments.length > 1) { + for (var i = 0, elements = [], length = arguments.length; i < length; i++) + elements.push($(arguments[i])); + return elements; + } + if (Object.isString(element)) + element = document.getElementById(element); + return Element.extend(element); +} + +if (Prototype.BrowserFeatures.XPath) { + document._getElementsByXPath = function(expression, parentElement) { + var results = []; + var query = document.evaluate(expression, $(parentElement) || document, + null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + for (var i = 0, length = query.snapshotLength; i < length; i++) + results.push(Element.extend(query.snapshotItem(i))); + return results; + }; +} + +/*--------------------------------------------------------------------------*/ + +if (!window.Node) var Node = { }; + +if (!Node.ELEMENT_NODE) { + // DOM level 2 ECMAScript Language Binding + Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 + }); +} + +(function() { + var element = this.Element; + this.Element = function(tagName, attributes) { + attributes = attributes || { }; + tagName = tagName.toLowerCase(); + var cache = Element.cache; + if (Prototype.Browser.IE && attributes.name) { + tagName = '<' + tagName + ' name="' + attributes.name + '">'; + delete attributes.name; + return Element.writeAttribute(document.createElement(tagName), attributes); + } + if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); + return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); + }; + Object.extend(this.Element, element || { }); + if (element) this.Element.prototype = element.prototype; +}).call(window); + +Element.cache = { }; + +Element.Methods = { + visible: function(element) { + return $(element).style.display != 'none'; + }, + + toggle: function(element) { + element = $(element); + Element[Element.visible(element) ? 'hide' : 'show'](element); + return element; + }, + + hide: function(element) { + element = $(element); + element.style.display = 'none'; + return element; + }, + + show: function(element) { + element = $(element); + element.style.display = ''; + return element; + }, + + remove: function(element) { + element = $(element); + element.parentNode.removeChild(element); + return element; + }, + + update: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) return element.update().insert(content); + content = Object.toHTML(content); + element.innerHTML = content.stripScripts(); + content.evalScripts.bind(content).defer(); + return element; + }, + + replace: function(element, content) { + element = $(element); + if (content && content.toElement) content = content.toElement(); + else if (!Object.isElement(content)) { + content = Object.toHTML(content); + var range = element.ownerDocument.createRange(); + range.selectNode(element); + content.evalScripts.bind(content).defer(); + content = range.createContextualFragment(content.stripScripts()); + } + element.parentNode.replaceChild(content, element); + return element; + }, + + insert: function(element, insertions) { + element = $(element); + + if (Object.isString(insertions) || Object.isNumber(insertions) || + Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML))) + insertions = {bottom:insertions}; + + var content, insert, tagName, childNodes; + + for (var position in insertions) { + content = insertions[position]; + position = position.toLowerCase(); + insert = Element._insertionTranslations[position]; + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + insert(element, content); + continue; + } + + content = Object.toHTML(content); + + tagName = ((position == 'before' || position == 'after') + ? element.parentNode : element).tagName.toUpperCase(); + + childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + + if (position == 'top' || position == 'after') childNodes.reverse(); + childNodes.each(insert.curry(element)); + + content.evalScripts.bind(content).defer(); + } + + return element; + }, + + wrap: function(element, wrapper, attributes) { + element = $(element); + if (Object.isElement(wrapper)) + $(wrapper).writeAttribute(attributes || { }); + else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes); + else wrapper = new Element('div', wrapper); + if (element.parentNode) + element.parentNode.replaceChild(wrapper, element); + wrapper.appendChild(element); + return wrapper; + }, + + inspect: function(element) { + element = $(element); + var result = '<' + element.tagName.toLowerCase(); + $H({'id': 'id', 'className': 'class'}).each(function(pair) { + var property = pair.first(), attribute = pair.last(); + var value = (element[property] || '').toString(); + if (value) result += ' ' + attribute + '=' + value.inspect(true); + }); + return result + '>'; + }, + + recursivelyCollect: function(element, property) { + element = $(element); + var elements = []; + while (element = element[property]) + if (element.nodeType == 1) + elements.push(Element.extend(element)); + return elements; + }, + + ancestors: function(element) { + return $(element).recursivelyCollect('parentNode'); + }, + + descendants: function(element) { + return $(element).select("*"); + }, + + firstDescendant: function(element) { + element = $(element).firstChild; + while (element && element.nodeType != 1) element = element.nextSibling; + return $(element); + }, + + immediateDescendants: function(element) { + if (!(element = $(element).firstChild)) return []; + while (element && element.nodeType != 1) element = element.nextSibling; + if (element) return [element].concat($(element).nextSiblings()); + return []; + }, + + previousSiblings: function(element) { + return $(element).recursivelyCollect('previousSibling'); + }, + + nextSiblings: function(element) { + return $(element).recursivelyCollect('nextSibling'); + }, + + siblings: function(element) { + element = $(element); + return element.previousSiblings().reverse().concat(element.nextSiblings()); + }, + + match: function(element, selector) { + if (Object.isString(selector)) + selector = new Selector(selector); + return selector.match($(element)); + }, + + up: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(element.parentNode); + var ancestors = element.ancestors(); + return Object.isNumber(expression) ? ancestors[expression] : + Selector.findElement(ancestors, expression, index); + }, + + down: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return element.firstDescendant(); + return Object.isNumber(expression) ? element.descendants()[expression] : + Element.select(element, expression)[index || 0]; + }, + + previous: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); + var previousSiblings = element.previousSiblings(); + return Object.isNumber(expression) ? previousSiblings[expression] : + Selector.findElement(previousSiblings, expression, index); + }, + + next: function(element, expression, index) { + element = $(element); + if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); + var nextSiblings = element.nextSiblings(); + return Object.isNumber(expression) ? nextSiblings[expression] : + Selector.findElement(nextSiblings, expression, index); + }, + + select: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element, args); + }, + + adjacent: function() { + var args = $A(arguments), element = $(args.shift()); + return Selector.findChildElements(element.parentNode, args).without(element); + }, + + identify: function(element) { + element = $(element); + var id = element.readAttribute('id'), self = arguments.callee; + if (id) return id; + do { id = 'anonymous_element_' + self.counter++ } while ($(id)); + element.writeAttribute('id', id); + return id; + }, + + readAttribute: function(element, name) { + element = $(element); + if (Prototype.Browser.IE) { + var t = Element._attributeTranslations.read; + if (t.values[name]) return t.values[name](element, name); + if (t.names[name]) name = t.names[name]; + if (name.include(':')) { + return (!element.attributes || !element.attributes[name]) ? null : + element.attributes[name].value; + } + } + return element.getAttribute(name); + }, + + writeAttribute: function(element, name, value) { + element = $(element); + var attributes = { }, t = Element._attributeTranslations.write; + + if (typeof name == 'object') attributes = name; + else attributes[name] = Object.isUndefined(value) ? true : value; + + for (var attr in attributes) { + name = t.names[attr] || attr; + value = attributes[attr]; + if (t.values[attr]) name = t.values[attr](element, value); + if (value === false || value === null) + element.removeAttribute(name); + else if (value === true) + element.setAttribute(name, name); + else element.setAttribute(name, value); + } + return element; + }, + + getHeight: function(element) { + return $(element).getDimensions().height; + }, + + getWidth: function(element) { + return $(element).getDimensions().width; + }, + + classNames: function(element) { + return new Element.ClassNames(element); + }, + + hasClassName: function(element, className) { + if (!(element = $(element))) return; + var elementClassName = element.className; + return (elementClassName.length > 0 && (elementClassName == className || + new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName))); + }, + + addClassName: function(element, className) { + if (!(element = $(element))) return; + if (!element.hasClassName(className)) + element.className += (element.className ? ' ' : '') + className; + return element; + }, + + removeClassName: function(element, className) { + if (!(element = $(element))) return; + element.className = element.className.replace( + new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip(); + return element; + }, + + toggleClassName: function(element, className) { + if (!(element = $(element))) return; + return element[element.hasClassName(className) ? + 'removeClassName' : 'addClassName'](className); + }, + + // removes whitespace-only text node children + cleanWhitespace: function(element) { + element = $(element); + var node = element.firstChild; + while (node) { + var nextNode = node.nextSibling; + if (node.nodeType == 3 && !/\S/.test(node.nodeValue)) + element.removeChild(node); + node = nextNode; + } + return element; + }, + + empty: function(element) { + return $(element).innerHTML.blank(); + }, + + descendantOf: function(element, ancestor) { + element = $(element), ancestor = $(ancestor); + + if (element.compareDocumentPosition) + return (element.compareDocumentPosition(ancestor) & 8) === 8; + + if (ancestor.contains) + return ancestor.contains(element) && ancestor !== element; + + while (element = element.parentNode) + if (element == ancestor) return true; + + return false; + }, + + scrollTo: function(element) { + element = $(element); + var pos = element.cumulativeOffset(); + window.scrollTo(pos[0], pos[1]); + return element; + }, + + getStyle: function(element, style) { + element = $(element); + style = style == 'float' ? 'cssFloat' : style.camelize(); + var value = element.style[style]; + if (!value || value == 'auto') { + var css = document.defaultView.getComputedStyle(element, null); + value = css ? css[style] : null; + } + if (style == 'opacity') return value ? parseFloat(value) : 1.0; + return value == 'auto' ? null : value; + }, + + getOpacity: function(element) { + return $(element).getStyle('opacity'); + }, + + setStyle: function(element, styles) { + element = $(element); + var elementStyle = element.style, match; + if (Object.isString(styles)) { + element.style.cssText += ';' + styles; + return styles.include('opacity') ? + element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element; + } + for (var property in styles) + if (property == 'opacity') element.setOpacity(styles[property]); + else + elementStyle[(property == 'float' || property == 'cssFloat') ? + (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') : + property] = styles[property]; + + return element; + }, + + setOpacity: function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + return element; + }, + + getDimensions: function(element) { + element = $(element); + var display = element.getStyle('display'); + if (display != 'none' && display != null) // Safari bug + return {width: element.offsetWidth, height: element.offsetHeight}; + + // All *Width and *Height properties give 0 on elements with display none, + // so enable the element temporarily + var els = element.style; + var originalVisibility = els.visibility; + var originalPosition = els.position; + var originalDisplay = els.display; + els.visibility = 'hidden'; + els.position = 'absolute'; + els.display = 'block'; + var originalWidth = element.clientWidth; + var originalHeight = element.clientHeight; + els.display = originalDisplay; + els.position = originalPosition; + els.visibility = originalVisibility; + return {width: originalWidth, height: originalHeight}; + }, + + makePositioned: function(element) { + element = $(element); + var pos = Element.getStyle(element, 'position'); + if (pos == 'static' || !pos) { + element._madePositioned = true; + element.style.position = 'relative'; + // Opera returns the offset relative to the positioning context, when an + // element is position relative but top and left have not been defined + if (Prototype.Browser.Opera) { + element.style.top = 0; + element.style.left = 0; + } + } + return element; + }, + + undoPositioned: function(element) { + element = $(element); + if (element._madePositioned) { + element._madePositioned = undefined; + element.style.position = + element.style.top = + element.style.left = + element.style.bottom = + element.style.right = ''; + } + return element; + }, + + makeClipping: function(element) { + element = $(element); + if (element._overflow) return element; + element._overflow = Element.getStyle(element, 'overflow') || 'auto'; + if (element._overflow !== 'hidden') + element.style.overflow = 'hidden'; + return element; + }, + + undoClipping: function(element) { + element = $(element); + if (!element._overflow) return element; + element.style.overflow = element._overflow == 'auto' ? '' : element._overflow; + element._overflow = null; + return element; + }, + + cumulativeOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + positionedOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + element = element.offsetParent; + if (element) { + if (element.tagName.toUpperCase() == 'BODY') break; + var p = Element.getStyle(element, 'position'); + if (p !== 'static') break; + } + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + absolutize: function(element) { + element = $(element); + if (element.getStyle('position') == 'absolute') return element; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + var offsets = element.positionedOffset(); + var top = offsets[1]; + var left = offsets[0]; + var width = element.clientWidth; + var height = element.clientHeight; + + element._originalLeft = left - parseFloat(element.style.left || 0); + element._originalTop = top - parseFloat(element.style.top || 0); + element._originalWidth = element.style.width; + element._originalHeight = element.style.height; + + element.style.position = 'absolute'; + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.width = width + 'px'; + element.style.height = height + 'px'; + return element; + }, + + relativize: function(element) { + element = $(element); + if (element.getStyle('position') == 'relative') return element; + // Position.prepare(); // To be done manually by Scripty when it needs it. + + element.style.position = 'relative'; + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); + var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + + element.style.top = top + 'px'; + element.style.left = left + 'px'; + element.style.height = element._originalHeight; + element.style.width = element._originalWidth; + return element; + }, + + cumulativeScrollOffset: function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.scrollTop || 0; + valueL += element.scrollLeft || 0; + element = element.parentNode; + } while (element); + return Element._returnOffset(valueL, valueT); + }, + + getOffsetParent: function(element) { + if (element.offsetParent) return $(element.offsetParent); + if (element == document.body) return $(element); + + while ((element = element.parentNode) && element != document.body) + if (Element.getStyle(element, 'position') != 'static') + return $(element); + + return $(document.body); + }, + + viewportOffset: function(forElement) { + var valueT = 0, valueL = 0; + + var element = forElement; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + + // Safari fix + if (element.offsetParent == document.body && + Element.getStyle(element, 'position') == 'absolute') break; + + } while (element = element.offsetParent); + + element = forElement; + do { + if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) { + valueT -= element.scrollTop || 0; + valueL -= element.scrollLeft || 0; + } + } while (element = element.parentNode); + + return Element._returnOffset(valueL, valueT); + }, + + clonePosition: function(element, source) { + var options = Object.extend({ + setLeft: true, + setTop: true, + setWidth: true, + setHeight: true, + offsetTop: 0, + offsetLeft: 0 + }, arguments[2] || { }); + + // find page position of source + source = $(source); + var p = source.viewportOffset(); + + // find coordinate system to use + element = $(element); + var delta = [0, 0]; + var parent = null; + // delta [0,0] will do fine with position: fixed elements, + // position:absolute needs offsetParent deltas + if (Element.getStyle(element, 'position') == 'absolute') { + parent = element.getOffsetParent(); + delta = parent.viewportOffset(); + } + + // correct by body offsets (fixes Safari) + if (parent == document.body) { + delta[0] -= document.body.offsetLeft; + delta[1] -= document.body.offsetTop; + } + + // set position + if (options.setLeft) element.style.left = (p[0] - delta[0] + options.offsetLeft) + 'px'; + if (options.setTop) element.style.top = (p[1] - delta[1] + options.offsetTop) + 'px'; + if (options.setWidth) element.style.width = source.offsetWidth + 'px'; + if (options.setHeight) element.style.height = source.offsetHeight + 'px'; + return element; + } +}; + +Element.Methods.identify.counter = 1; + +Object.extend(Element.Methods, { + getElementsBySelector: Element.Methods.select, + childElements: Element.Methods.immediateDescendants +}); + +Element._attributeTranslations = { + write: { + names: { + className: 'class', + htmlFor: 'for' + }, + values: { } + } +}; + +if (Prototype.Browser.Opera) { + Element.Methods.getStyle = Element.Methods.getStyle.wrap( + function(proceed, element, style) { + switch (style) { + case 'left': case 'top': case 'right': case 'bottom': + if (proceed(element, 'position') === 'static') return null; + case 'height': case 'width': + // returns '0px' for hidden elements; we want it to return null + if (!Element.visible(element)) return null; + + // returns the border-box dimensions rather than the content-box + // dimensions, so we subtract padding and borders from the value + var dim = parseInt(proceed(element, style), 10); + + if (dim !== element['offset' + style.capitalize()]) + return dim + 'px'; + + var properties; + if (style === 'height') { + properties = ['border-top-width', 'padding-top', + 'padding-bottom', 'border-bottom-width']; + } + else { + properties = ['border-left-width', 'padding-left', + 'padding-right', 'border-right-width']; + } + return properties.inject(dim, function(memo, property) { + var val = proceed(element, property); + return val === null ? memo : memo - parseInt(val, 10); + }) + 'px'; + default: return proceed(element, style); + } + } + ); + + Element.Methods.readAttribute = Element.Methods.readAttribute.wrap( + function(proceed, element, attribute) { + if (attribute === 'title') return element.title; + return proceed(element, attribute); + } + ); +} + +else if (Prototype.Browser.IE) { + // IE doesn't report offsets correctly for static elements, so we change them + // to "relative" to get the values, then change them back. + Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap( + function(proceed, element) { + element = $(element); + // IE throws an error if element is not in document + try { element.offsetParent } + catch(e) { return $(document.body) } + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + + $w('positionedOffset viewportOffset').each(function(method) { + Element.Methods[method] = Element.Methods[method].wrap( + function(proceed, element) { + element = $(element); + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + var position = element.getStyle('position'); + if (position !== 'static') return proceed(element); + // Trigger hasLayout on the offset parent so that IE6 reports + // accurate offsetTop and offsetLeft values for position: fixed. + var offsetParent = element.getOffsetParent(); + if (offsetParent && offsetParent.getStyle('position') === 'fixed') + offsetParent.setStyle({ zoom: 1 }); + element.setStyle({ position: 'relative' }); + var value = proceed(element); + element.setStyle({ position: position }); + return value; + } + ); + }); + + Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap( + function(proceed, element) { + try { element.offsetParent } + catch(e) { return Element._returnOffset(0,0) } + return proceed(element); + } + ); + + Element.Methods.getStyle = function(element, style) { + element = $(element); + style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize(); + var value = element.style[style]; + if (!value && element.currentStyle) value = element.currentStyle[style]; + + if (style == 'opacity') { + if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/)) + if (value[1]) return parseFloat(value[1]) / 100; + return 1.0; + } + + if (value == 'auto') { + if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none')) + return element['offset' + style.capitalize()] + 'px'; + return null; + } + return value; + }; + + Element.Methods.setOpacity = function(element, value) { + function stripAlpha(filter){ + return filter.replace(/alpha\([^\)]*\)/gi,''); + } + element = $(element); + var currentStyle = element.currentStyle; + if ((currentStyle && !currentStyle.hasLayout) || + (!currentStyle && element.style.zoom == 'normal')) + element.style.zoom = 1; + + var filter = element.getStyle('filter'), style = element.style; + if (value == 1 || value === '') { + (filter = stripAlpha(filter)) ? + style.filter = filter : style.removeAttribute('filter'); + return element; + } else if (value < 0.00001) value = 0; + style.filter = stripAlpha(filter) + + 'alpha(opacity=' + (value * 100) + ')'; + return element; + }; + + Element._attributeTranslations = { + read: { + names: { + 'class': 'className', + 'for': 'htmlFor' + }, + values: { + _getAttr: function(element, attribute) { + return element.getAttribute(attribute, 2); + }, + _getAttrNode: function(element, attribute) { + var node = element.getAttributeNode(attribute); + return node ? node.value : ""; + }, + _getEv: function(element, attribute) { + attribute = element.getAttribute(attribute); + return attribute ? attribute.toString().slice(23, -2) : null; + }, + _flag: function(element, attribute) { + return $(element).hasAttribute(attribute) ? attribute : null; + }, + style: function(element) { + return element.style.cssText.toLowerCase(); + }, + title: function(element) { + return element.title; + } + } + } + }; + + Element._attributeTranslations.write = { + names: Object.extend({ + cellpadding: 'cellPadding', + cellspacing: 'cellSpacing' + }, Element._attributeTranslations.read.names), + values: { + checked: function(element, value) { + element.checked = !!value; + }, + + style: function(element, value) { + element.style.cssText = value ? value : ''; + } + } + }; + + Element._attributeTranslations.has = {}; + + $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' + + 'encType maxLength readOnly longDesc frameBorder').each(function(attr) { + Element._attributeTranslations.write.names[attr.toLowerCase()] = attr; + Element._attributeTranslations.has[attr.toLowerCase()] = attr; + }); + + (function(v) { + Object.extend(v, { + href: v._getAttr, + src: v._getAttr, + type: v._getAttr, + action: v._getAttrNode, + disabled: v._flag, + checked: v._flag, + readonly: v._flag, + multiple: v._flag, + onload: v._getEv, + onunload: v._getEv, + onclick: v._getEv, + ondblclick: v._getEv, + onmousedown: v._getEv, + onmouseup: v._getEv, + onmouseover: v._getEv, + onmousemove: v._getEv, + onmouseout: v._getEv, + onfocus: v._getEv, + onblur: v._getEv, + onkeypress: v._getEv, + onkeydown: v._getEv, + onkeyup: v._getEv, + onsubmit: v._getEv, + onreset: v._getEv, + onselect: v._getEv, + onchange: v._getEv + }); + })(Element._attributeTranslations.read.values); +} + +else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1) ? 0.999999 : + (value === '') ? '' : (value < 0.00001) ? 0 : value; + return element; + }; +} + +else if (Prototype.Browser.WebKit) { + Element.Methods.setOpacity = function(element, value) { + element = $(element); + element.style.opacity = (value == 1 || value === '') ? '' : + (value < 0.00001) ? 0 : value; + + if (value == 1) + if(element.tagName.toUpperCase() == 'IMG' && element.width) { + element.width++; element.width--; + } else try { + var n = document.createTextNode(' '); + element.appendChild(n); + element.removeChild(n); + } catch (e) { } + + return element; + }; + + // Safari returns margins on body which is incorrect if the child is absolutely + // positioned. For performance reasons, redefine Element#cumulativeOffset for + // KHTML/WebKit only. + Element.Methods.cumulativeOffset = function(element) { + var valueT = 0, valueL = 0; + do { + valueT += element.offsetTop || 0; + valueL += element.offsetLeft || 0; + if (element.offsetParent == document.body) + if (Element.getStyle(element, 'position') == 'absolute') break; + + element = element.offsetParent; + } while (element); + + return Element._returnOffset(valueL, valueT); + }; +} + +if (Prototype.Browser.IE || Prototype.Browser.Opera) { + // IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements + Element.Methods.update = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) return element.update().insert(content); + + content = Object.toHTML(content); + var tagName = element.tagName.toUpperCase(); + + if (tagName in Element._insertionTranslations.tags) { + $A(element.childNodes).each(function(node) { element.removeChild(node) }); + Element._getContentFromAnonymousElement(tagName, content.stripScripts()) + .each(function(node) { element.appendChild(node) }); + } + else element.innerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +if ('outerHTML' in document.createElement('div')) { + Element.Methods.replace = function(element, content) { + element = $(element); + + if (content && content.toElement) content = content.toElement(); + if (Object.isElement(content)) { + element.parentNode.replaceChild(content, element); + return element; + } + + content = Object.toHTML(content); + var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); + + if (Element._insertionTranslations.tags[tagName]) { + var nextSibling = element.next(); + var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + parent.removeChild(element); + if (nextSibling) + fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); + else + fragments.each(function(node) { parent.appendChild(node) }); + } + else element.outerHTML = content.stripScripts(); + + content.evalScripts.bind(content).defer(); + return element; + }; +} + +Element._returnOffset = function(l, t) { + var result = [l, t]; + result.left = l; + result.top = t; + return result; +}; + +Element._getContentFromAnonymousElement = function(tagName, html) { + var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; + if (t) { + div.innerHTML = t[0] + html + t[1]; + t[2].times(function() { div = div.firstChild }); + } else div.innerHTML = html; + return $A(div.childNodes); +}; + +Element._insertionTranslations = { + before: function(element, node) { + element.parentNode.insertBefore(node, element); + }, + top: function(element, node) { + element.insertBefore(node, element.firstChild); + }, + bottom: function(element, node) { + element.appendChild(node); + }, + after: function(element, node) { + element.parentNode.insertBefore(node, element.nextSibling); + }, + tags: { + TABLE: ['', '
    ', 1], + TBODY: ['', '
    ', 2], + TR: ['', '
    ', 3], + TD: ['
    ', '
    ', 4], + SELECT: ['', 1] + } +}; + +(function() { + Object.extend(this.tags, { + THEAD: this.tags.TBODY, + TFOOT: this.tags.TBODY, + TH: this.tags.TD + }); +}).call(Element._insertionTranslations); + +Element.Methods.Simulated = { + hasAttribute: function(element, attribute) { + attribute = Element._attributeTranslations.has[attribute] || attribute; + var node = $(element).getAttributeNode(attribute); + return !!(node && node.specified); + } +}; + +Element.Methods.ByTag = { }; + +Object.extend(Element, Element.Methods); + +if (!Prototype.BrowserFeatures.ElementExtensions && + document.createElement('div')['__proto__']) { + window.HTMLElement = { }; + window.HTMLElement.prototype = document.createElement('div')['__proto__']; + Prototype.BrowserFeatures.ElementExtensions = true; +} + +Element.extend = (function() { + if (Prototype.BrowserFeatures.SpecificElementExtensions) + return Prototype.K; + + var Methods = { }, ByTag = Element.Methods.ByTag; + + var extend = Object.extend(function(element) { + if (!element || element._extendedByPrototype || + element.nodeType != 1 || element == window) return element; + + var methods = Object.clone(Methods), + tagName = element.tagName.toUpperCase(), property, value; + + // extend methods for specific tags + if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]); + + for (property in methods) { + value = methods[property]; + if (Object.isFunction(value) && !(property in element)) + element[property] = value.methodize(); + } + + element._extendedByPrototype = Prototype.emptyFunction; + return element; + + }, { + refresh: function() { + // extend methods for all tags (Safari doesn't need this) + if (!Prototype.BrowserFeatures.ElementExtensions) { + Object.extend(Methods, Element.Methods); + Object.extend(Methods, Element.Methods.Simulated); + } + } + }); + + extend.refresh(); + return extend; +})(); + +Element.hasAttribute = function(element, attribute) { + if (element.hasAttribute) return element.hasAttribute(attribute); + return Element.Methods.Simulated.hasAttribute(element, attribute); +}; + +Element.addMethods = function(methods) { + var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag; + + if (!methods) { + Object.extend(Form, Form.Methods); + Object.extend(Form.Element, Form.Element.Methods); + Object.extend(Element.Methods.ByTag, { + "FORM": Object.clone(Form.Methods), + "INPUT": Object.clone(Form.Element.Methods), + "SELECT": Object.clone(Form.Element.Methods), + "TEXTAREA": Object.clone(Form.Element.Methods) + }); + } + + if (arguments.length == 2) { + var tagName = methods; + methods = arguments[1]; + } + + if (!tagName) Object.extend(Element.Methods, methods || { }); + else { + if (Object.isArray(tagName)) tagName.each(extend); + else extend(tagName); + } + + function extend(tagName) { + tagName = tagName.toUpperCase(); + if (!Element.Methods.ByTag[tagName]) + Element.Methods.ByTag[tagName] = { }; + Object.extend(Element.Methods.ByTag[tagName], methods); + } + + function copy(methods, destination, onlyIfAbsent) { + onlyIfAbsent = onlyIfAbsent || false; + for (var property in methods) { + var value = methods[property]; + if (!Object.isFunction(value)) continue; + if (!onlyIfAbsent || !(property in destination)) + destination[property] = value.methodize(); + } + } + + function findDOMClass(tagName) { + var klass; + var trans = { + "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph", + "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList", + "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading", + "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote", + "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION": + "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD": + "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR": + "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET": + "FrameSet", "IFRAME": "IFrame" + }; + if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName + 'Element'; + if (window[klass]) return window[klass]; + klass = 'HTML' + tagName.capitalize() + 'Element'; + if (window[klass]) return window[klass]; + + window[klass] = { }; + window[klass].prototype = document.createElement(tagName)['__proto__']; + return window[klass]; + } + + if (F.ElementExtensions) { + copy(Element.Methods, HTMLElement.prototype); + copy(Element.Methods.Simulated, HTMLElement.prototype, true); + } + + if (F.SpecificElementExtensions) { + for (var tag in Element.Methods.ByTag) { + var klass = findDOMClass(tag); + if (Object.isUndefined(klass)) continue; + copy(T[tag], klass.prototype); + } + } + + Object.extend(Element, Element.Methods); + delete Element.ByTag; + + if (Element.extend.refresh) Element.extend.refresh(); + Element.cache = { }; +}; + +document.viewport = { + getDimensions: function() { + var dimensions = { }, B = Prototype.Browser; + $w('width height').each(function(d) { + var D = d.capitalize(); + if (B.WebKit && !document.evaluate) { + // Safari <3.0 needs self.innerWidth/Height + dimensions[d] = self['inner' + D]; + } else if (B.Opera && parseFloat(window.opera.version()) < 9.5) { + // Opera <9.5 needs document.body.clientWidth/Height + dimensions[d] = document.body['client' + D] + } else { + dimensions[d] = document.documentElement['client' + D]; + } + }); + return dimensions; + }, + + getWidth: function() { + return this.getDimensions().width; + }, + + getHeight: function() { + return this.getDimensions().height; + }, + + getScrollOffsets: function() { + return Element._returnOffset( + window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft, + window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop); + } +}; +/* Portions of the Selector class are derived from Jack Slocum's DomQuery, + * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style + * license. Please see http://www.yui-ext.com/ for more information. */ + +var Selector = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + + if (this.shouldUseSelectorsAPI()) { + this.mode = 'selectorsAPI'; + } else if (this.shouldUseXPath()) { + this.mode = 'xpath'; + this.compileXPathMatcher(); + } else { + this.mode = "normal"; + this.compileMatcher(); + } + + }, + + shouldUseXPath: function() { + if (!Prototype.BrowserFeatures.XPath) return false; + + var e = this.expression; + + // Safari 3 chokes on :*-of-type and :empty + if (Prototype.Browser.WebKit && + (e.include("-of-type") || e.include(":empty"))) + return false; + + // XPath can't do namespaced attributes, nor can it read + // the "checked" property from DOM nodes + if ((/(\[[\w-]*?:|:checked)/).test(e)) + return false; + + return true; + }, + + shouldUseSelectorsAPI: function() { + if (!Prototype.BrowserFeatures.SelectorsAPI) return false; + + if (!Selector._div) Selector._div = new Element('div'); + + // Make sure the browser treats the selector as valid. Test on an + // isolated element to minimize cost of this check. + try { + Selector._div.querySelector(this.expression); + } catch(e) { + return false; + } + + return true; + }, + + compileMatcher: function() { + var e = this.expression, ps = Selector.patterns, h = Selector.handlers, + c = Selector.criteria, le, p, m; + + if (Selector._cache[e]) { + this.matcher = Selector._cache[e]; + return; + } + + this.matcher = ["this.matcher = function(root) {", + "var r = root, h = Selector.handlers, c = false, n;"]; + + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + this.matcher.push(Object.isFunction(c[i]) ? c[i](m) : + new Template(c[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.matcher.push("return h.unique(n);\n}"); + eval(this.matcher.join('\n')); + Selector._cache[this.expression] = this.matcher; + }, + + compileXPathMatcher: function() { + var e = this.expression, ps = Selector.patterns, + x = Selector.xpath, le, m; + + if (Selector._cache[e]) { + this.xpath = Selector._cache[e]; return; + } + + this.matcher = ['.//*']; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + if (m = e.match(ps[i])) { + this.matcher.push(Object.isFunction(x[i]) ? x[i](m) : + new Template(x[i]).evaluate(m)); + e = e.replace(m[0], ''); + break; + } + } + } + + this.xpath = this.matcher.join(''); + Selector._cache[this.expression] = this.xpath; + }, + + findElements: function(root) { + root = root || document; + var e = this.expression, results; + + switch (this.mode) { + case 'selectorsAPI': + // querySelectorAll queries document-wide, then filters to descendants + // of the context element. That's not what we want. + // Add an explicit context to the selector if necessary. + if (root !== document) { + var oldId = root.id, id = $(root).identify(); + e = "#" + id + " " + e; + } + + results = $A(root.querySelectorAll(e)).map(Element.extend); + root.id = oldId; + + return results; + case 'xpath': + return document._getElementsByXPath(this.xpath, root); + default: + return this.matcher(root); + } + }, + + match: function(element) { + this.tokens = []; + + var e = this.expression, ps = Selector.patterns, as = Selector.assertions; + var le, p, m; + + while (e && le !== e && (/\S/).test(e)) { + le = e; + for (var i in ps) { + p = ps[i]; + if (m = e.match(p)) { + // use the Selector.assertions methods unless the selector + // is too complex. + if (as[i]) { + this.tokens.push([i, Object.clone(m)]); + e = e.replace(m[0], ''); + } else { + // reluctantly do a document-wide search + // and look for a match in the array + return this.findElements(document).include(element); + } + } + } + } + + var match = true, name, matches; + for (var i = 0, token; token = this.tokens[i]; i++) { + name = token[0], matches = token[1]; + if (!Selector.assertions[name](element, matches)) { + match = false; break; + } + } + + return match; + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } +}); + +Object.extend(Selector, { + _cache: { }, + + xpath: { + descendant: "//*", + child: "/*", + adjacent: "/following-sibling::*[1]", + laterSibling: '/following-sibling::*', + tagName: function(m) { + if (m[1] == '*') return ''; + return "[local-name()='" + m[1].toLowerCase() + + "' or local-name()='" + m[1].toUpperCase() + "']"; + }, + className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", + id: "[@id='#{1}']", + attrPresence: function(m) { + m[1] = m[1].toLowerCase(); + return new Template("[@#{1}]").evaluate(m); + }, + attr: function(m) { + m[1] = m[1].toLowerCase(); + m[3] = m[5] || m[6]; + return new Template(Selector.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Selector.xpath.pseudos[m[1]]; + if (!h) return ''; + if (Object.isFunction(h)) return h(m); + return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); + }, + operators: { + '=': "[@#{1}='#{3}']", + '!=': "[@#{1}!='#{3}']", + '^=': "[starts-with(@#{1}, '#{3}')]", + '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", + '*=': "[contains(@#{1}, '#{3}')]", + '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", + '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + }, + pseudos: { + 'first-child': '[not(preceding-sibling::*)]', + 'last-child': '[not(following-sibling::*)]', + 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', + 'empty': "[count(*) = 0 and (count(text()) = 0)]", + 'checked': "[@checked]", + 'disabled': "[(@disabled) and (@type!='hidden')]", + 'enabled': "[not(@disabled) and (@type!='hidden')]", + 'not': function(m) { + var e = m[6], p = Selector.patterns, + x = Selector.xpath, le, v; + + var exclusion = []; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i in p) { + if (m = e.match(p[i])) { + v = Object.isFunction(x[i]) ? x[i](m) : new Template(x[i]).evaluate(m); + exclusion.push("(" + v.substring(1, v.length - 1) + ")"); + e = e.replace(m[0], ''); + break; + } + } + } + return "[not(" + exclusion.join(" and ") + ")]"; + }, + 'nth-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + }, + 'nth-last-child': function(m) { + return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + }, + 'nth-of-type': function(m) { + return Selector.xpath.pseudos.nth("position() ", m); + }, + 'nth-last-of-type': function(m) { + return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); + }, + 'first-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); + }, + 'last-of-type': function(m) { + m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); + }, + 'only-of-type': function(m) { + var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + }, + nth: function(fragment, m) { + var mm, formula = m[6], predicate; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + if (mm = formula.match(/^(\d+)$/)) // digit only + return '[' + fragment + "= " + mm[1] + ']'; + if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (mm[1] == "-") mm[1] = -1; + var a = mm[1] ? Number(mm[1]) : 1; + var b = mm[2] ? Number(mm[2]) : 0; + predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " + + "((#{fragment} - #{b}) div #{a} >= 0)]"; + return new Template(predicate).evaluate({ + fragment: fragment, a: a, b: b }); + } + } + } + }, + + criteria: { + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', + attr: function(m) { + m[3] = (m[5] || m[6]); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); + }, + pseudo: function(m) { + if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); + return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); + }, + descendant: 'c = "descendant";', + child: 'c = "child";', + adjacent: 'c = "adjacent";', + laterSibling: 'c = "laterSibling";' + }, + + patterns: { + // combinators must be listed first + // (and descendant needs to be last combinator) + laterSibling: /^\s*~\s*/, + child: /^\s*>\s*/, + adjacent: /^\s*\+\s*/, + descendant: /^\s/, + + // selectors follow + tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, + id: /^#([\w\-\*]+)(\b|$)/, + className: /^\.([\w\-\*]+)(\b|$)/, + pseudo: +/^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/, + attrPresence: /^\[((?:[\w]+:)?[\w]+)\]/, + attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ + }, + + // for Selector.match and Element#match + assertions: { + tagName: function(element, matches) { + return matches[1].toUpperCase() == element.tagName.toUpperCase(); + }, + + className: function(element, matches) { + return Element.hasClassName(element, matches[1]); + }, + + id: function(element, matches) { + return element.id === matches[1]; + }, + + attrPresence: function(element, matches) { + return Element.hasAttribute(element, matches[1]); + }, + + attr: function(element, matches) { + var nodeValue = Element.readAttribute(element, matches[1]); + return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); + } + }, + + handlers: { + // UTILITY FUNCTIONS + // joins two collections + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + // marks an array of nodes for counting + mark: function(nodes) { + var _true = Prototype.emptyFunction; + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = _true; + return nodes; + }, + + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = undefined; + return nodes; + }, + + // mark each child node with its position (for nth calls) + // "ofType" flag indicates whether we're indexing for nth-of-type + // rather than nth-child + index: function(parentNode, reverse, ofType) { + parentNode._countedByPrototype = Prototype.emptyFunction; + if (reverse) { + for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { + var node = nodes[i]; + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + } else { + for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + }, + + // filters out duplicates and extends all nodes + unique: function(nodes) { + if (nodes.length == 0) return nodes; + var results = [], n; + for (var i = 0, l = nodes.length; i < l; i++) + if (!(n = nodes[i])._countedByPrototype) { + n._countedByPrototype = Prototype.emptyFunction; + results.push(Element.extend(n)); + } + return Selector.handlers.unmark(results); + }, + + // COMBINATOR FUNCTIONS + descendant: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName('*')); + return results; + }, + + child: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) { + for (var j = 0, child; child = node.childNodes[j]; j++) + if (child.nodeType == 1 && child.tagName != '!') results.push(child); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + laterSibling: function(nodes) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, Element.nextSiblings(node)); + return results; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + // TOKEN FUNCTIONS + tagName: function(nodes, root, tagName, combinator) { + var uTagName = tagName.toUpperCase(); + var results = [], h = Selector.handlers; + if (nodes) { + if (combinator) { + // fastlane for ordinary descendant combinators + if (combinator == "descendant") { + for (var i = 0, node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName(tagName)); + return results; + } else nodes = this[combinator](nodes); + if (tagName == "*") return nodes; + } + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName.toUpperCase() === uTagName) results.push(node); + return results; + } else return root.getElementsByTagName(tagName); + }, + + id: function(nodes, root, id, combinator) { + var targetNode = $(id), h = Selector.handlers; + if (!targetNode) return []; + if (!nodes && root == document) return [targetNode]; + if (nodes) { + if (combinator) { + if (combinator == 'child') { + for (var i = 0, node; node = nodes[i]; i++) + if (targetNode.parentNode == node) return [targetNode]; + } else if (combinator == 'descendant') { + for (var i = 0, node; node = nodes[i]; i++) + if (Element.descendantOf(targetNode, node)) return [targetNode]; + } else if (combinator == 'adjacent') { + for (var i = 0, node; node = nodes[i]; i++) + if (Selector.handlers.previousElementSibling(targetNode) == node) + return [targetNode]; + } else nodes = h[combinator](nodes); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Selector.handlers.byClassName(nodes, root, className); + }, + + byClassName: function(nodes, root, className) { + if (!nodes) nodes = Selector.handlers.descendant([root]); + var needle = ' ' + className + ' '; + for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { + nodeClassName = node.className; + if (nodeClassName.length == 0) continue; + if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) + results.push(node); + } + return results; + }, + + attrPresence: function(nodes, root, attr, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (Element.hasAttribute(node, attr)) results.push(node); + return results; + }, + + attr: function(nodes, root, attr, value, operator, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var handler = Selector.operators[operator], results = []; + for (var i = 0, node; node = nodes[i]; i++) { + var nodeValue = Element.readAttribute(node, attr); + if (nodeValue === null) continue; + if (handler(nodeValue, value)) results.push(node); + } + return results; + }, + + pseudo: function(nodes, name, value, root, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + if (!nodes) nodes = root.getElementsByTagName("*"); + return Selector.pseudos[name](nodes, value, root); + } + }, + + pseudos: { + 'first-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.previousElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'last-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Selector.handlers.nextElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'only-child': function(nodes, value, root) { + var h = Selector.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) + results.push(node); + return results; + }, + 'nth-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root); + }, + 'nth-last-child': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true); + }, + 'nth-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, false, true); + }, + 'nth-last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, formula, root, true, true); + }, + 'first-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, false, true); + }, + 'last-of-type': function(nodes, formula, root) { + return Selector.pseudos.nth(nodes, "1", root, true, true); + }, + 'only-of-type': function(nodes, formula, root) { + var p = Selector.pseudos; + return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); + }, + + // handles the an+b logic + getIndices: function(a, b, total) { + if (a == 0) return b > 0 ? [b] : []; + return $R(1, total).inject([], function(memo, i) { + if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); + return memo; + }); + }, + + // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type + nth: function(nodes, formula, root, reverse, ofType) { + if (nodes.length == 0) return []; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + var h = Selector.handlers, results = [], indexed = [], m; + h.mark(nodes); + for (var i = 0, node; node = nodes[i]; i++) { + if (!node.parentNode._countedByPrototype) { + h.index(node.parentNode, reverse, ofType); + indexed.push(node.parentNode); + } + } + if (formula.match(/^\d+$/)) { // just a number + formula = Number(formula); + for (var i = 0, node; node = nodes[i]; i++) + if (node.nodeIndex == formula) results.push(node); + } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (m[1] == "-") m[1] = -1; + var a = m[1] ? Number(m[1]) : 1; + var b = m[2] ? Number(m[2]) : 0; + var indices = Selector.pseudos.getIndices(a, b, nodes.length); + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { + for (var j = 0; j < l; j++) + if (node.nodeIndex == indices[j]) results.push(node); + } + } + h.unmark(nodes); + h.unmark(indexed); + return results; + }, + + 'empty': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + // IE treats comments as element nodes + if (node.tagName == '!' || node.firstChild) continue; + results.push(node); + } + return results; + }, + + 'not': function(nodes, selector, root) { + var h = Selector.handlers, selectorType, m; + var exclusions = new Selector(selector).findElements(root); + h.mark(exclusions); + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node._countedByPrototype) results.push(node); + h.unmark(exclusions); + return results; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled && (!node.type || node.type !== 'hidden')) + results.push(node); + return results; + }, + + 'disabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.disabled) results.push(node); + return results; + }, + + 'checked': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.checked) results.push(node); + return results; + } + }, + + operators: { + '=': function(nv, v) { return nv == v; }, + '!=': function(nv, v) { return nv != v; }, + '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, + '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, + '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, + '$=': function(nv, v) { return nv.endsWith(v); }, + '*=': function(nv, v) { return nv.include(v); }, + '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, + '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + + '-').include('-' + (v || "").toUpperCase() + '-'); } + }, + + split: function(expression) { + var expressions = []; + expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + return expressions; + }, + + matchElements: function(elements, expression) { + var matches = $$(expression), h = Selector.handlers; + h.mark(matches); + for (var i = 0, results = [], element; element = elements[i]; i++) + if (element._countedByPrototype) results.push(element); + h.unmark(matches); + return results; + }, + + findElement: function(elements, expression, index) { + if (Object.isNumber(expression)) { + index = expression; expression = false; + } + return Selector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + expressions = Selector.split(expressions.join(',')); + var results = [], h = Selector.handlers; + for (var i = 0, l = expressions.length, selector; i < l; i++) { + selector = new Selector(expressions[i].strip()); + h.concat(results, selector.findElements(element)); + } + return (l > 1) ? h.unique(results) : results; + } +}); + +if (Prototype.Browser.IE) { + Object.extend(Selector.handlers, { + // IE returns comment nodes on getElementsByTagName("*"). + // Filter them out. + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + if (node.tagName !== "!") a.push(node); + return a; + }, + + // IE improperly serializes _countedByPrototype in (inner|outer)HTML. + unmark: function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node.removeAttribute('_countedByPrototype'); + return nodes; + } + }); +} + +function $$() { + return Selector.findChildElements(document, $A(arguments)); +} +var Form = { + reset: function(form) { + $(form).reset(); + return form; + }, + + serializeElements: function(elements, options) { + if (typeof options != 'object') options = { hash: !!options }; + else if (Object.isUndefined(options.hash)) options.hash = true; + var key, value, submitted = false, submit = options.submit; + + var data = elements.inject({ }, function(result, element) { + if (!element.disabled && element.name) { + key = element.name; value = $(element).getValue(); + if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted && + submit !== false && (!submit || key == submit) && (submitted = true)))) { + if (key in result) { + // a key is already present; construct an array of values + if (!Object.isArray(result[key])) result[key] = [result[key]]; + result[key].push(value); + } + else result[key] = value; + } + } + return result; + }); + + return options.hash ? data : Object.toQueryString(data); + } +}; + +Form.Methods = { + serialize: function(form, options) { + return Form.serializeElements(Form.getElements(form), options); + }, + + getElements: function(form) { + return $A($(form).getElementsByTagName('*')).inject([], + function(elements, child) { + if (Form.Element.Serializers[child.tagName.toLowerCase()]) + elements.push(Element.extend(child)); + return elements; + } + ); + }, + + getInputs: function(form, typeName, name) { + form = $(form); + var inputs = form.getElementsByTagName('input'); + + if (!typeName && !name) return $A(inputs).map(Element.extend); + + for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) { + var input = inputs[i]; + if ((typeName && input.type != typeName) || (name && input.name != name)) + continue; + matchingInputs.push(Element.extend(input)); + } + + return matchingInputs; + }, + + disable: function(form) { + form = $(form); + Form.getElements(form).invoke('disable'); + return form; + }, + + enable: function(form) { + form = $(form); + Form.getElements(form).invoke('enable'); + return form; + }, + + findFirstElement: function(form) { + var elements = $(form).getElements().findAll(function(element) { + return 'hidden' != element.type && !element.disabled; + }); + var firstByIndex = elements.findAll(function(element) { + return element.hasAttribute('tabIndex') && element.tabIndex >= 0; + }).sortBy(function(element) { return element.tabIndex }).first(); + + return firstByIndex ? firstByIndex : elements.find(function(element) { + return ['input', 'select', 'textarea'].include(element.tagName.toLowerCase()); + }); + }, + + focusFirstElement: function(form) { + form = $(form); + form.findFirstElement().activate(); + return form; + }, + + request: function(form, options) { + form = $(form), options = Object.clone(options || { }); + + var params = options.parameters, action = form.readAttribute('action') || ''; + if (action.blank()) action = window.location.href; + options.parameters = form.serialize(true); + + if (params) { + if (Object.isString(params)) params = params.toQueryParams(); + Object.extend(options.parameters, params); + } + + if (form.hasAttribute('method') && !options.method) + options.method = form.method; + + return new Ajax.Request(action, options); + } +}; + +/*--------------------------------------------------------------------------*/ + +Form.Element = { + focus: function(element) { + $(element).focus(); + return element; + }, + + select: function(element) { + $(element).select(); + return element; + } +}; + +Form.Element.Methods = { + serialize: function(element) { + element = $(element); + if (!element.disabled && element.name) { + var value = element.getValue(); + if (value != undefined) { + var pair = { }; + pair[element.name] = value; + return Object.toQueryString(pair); + } + } + return ''; + }, + + getValue: function(element) { + element = $(element); + var method = element.tagName.toLowerCase(); + return Form.Element.Serializers[method](element); + }, + + setValue: function(element, value) { + element = $(element); + var method = element.tagName.toLowerCase(); + Form.Element.Serializers[method](element, value); + return element; + }, + + clear: function(element) { + $(element).value = ''; + return element; + }, + + present: function(element) { + return $(element).value != ''; + }, + + activate: function(element) { + element = $(element); + try { + element.focus(); + if (element.select && (element.tagName.toLowerCase() != 'input' || + !['button', 'reset', 'submit'].include(element.type))) + element.select(); + } catch (e) { } + return element; + }, + + disable: function(element) { + element = $(element); + element.disabled = true; + return element; + }, + + enable: function(element) { + element = $(element); + element.disabled = false; + return element; + } +}; + +/*--------------------------------------------------------------------------*/ + +var Field = Form.Element; +var $F = Form.Element.Methods.getValue; + +/*--------------------------------------------------------------------------*/ + +Form.Element.Serializers = { + input: function(element, value) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + return Form.Element.Serializers.inputSelector(element, value); + default: + return Form.Element.Serializers.textarea(element, value); + } + }, + + inputSelector: function(element, value) { + if (Object.isUndefined(value)) return element.checked ? element.value : null; + else element.checked = !!value; + }, + + textarea: function(element, value) { + if (Object.isUndefined(value)) return element.value; + else element.value = value; + }, + + select: function(element, value) { + if (Object.isUndefined(value)) + return this[element.type == 'select-one' ? + 'selectOne' : 'selectMany'](element); + else { + var opt, currentValue, single = !Object.isArray(value); + for (var i = 0, length = element.length; i < length; i++) { + opt = element.options[i]; + currentValue = this.optionValue(opt); + if (single) { + if (currentValue == value) { + opt.selected = true; + return; + } + } + else opt.selected = value.include(currentValue); + } + } + }, + + selectOne: function(element) { + var index = element.selectedIndex; + return index >= 0 ? this.optionValue(element.options[index]) : null; + }, + + selectMany: function(element) { + var values, length = element.length; + if (!length) return null; + + for (var i = 0, values = []; i < length; i++) { + var opt = element.options[i]; + if (opt.selected) values.push(this.optionValue(opt)); + } + return values; + }, + + optionValue: function(opt) { + // extend element because hasAttribute may not be native + return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text; + } +}; + +/*--------------------------------------------------------------------------*/ + +Abstract.TimedObserver = Class.create(PeriodicalExecuter, { + initialize: function($super, element, frequency, callback) { + $super(callback, frequency); + this.element = $(element); + this.lastValue = this.getValue(); + }, + + execute: function() { + var value = this.getValue(); + if (Object.isString(this.lastValue) && Object.isString(value) ? + this.lastValue != value : String(this.lastValue) != String(value)) { + this.callback(this.element, value); + this.lastValue = value; + } + } +}); + +Form.Element.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.Observer = Class.create(Abstract.TimedObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); + +/*--------------------------------------------------------------------------*/ + +Abstract.EventObserver = Class.create({ + initialize: function(element, callback) { + this.element = $(element); + this.callback = callback; + + this.lastValue = this.getValue(); + if (this.element.tagName.toLowerCase() == 'form') + this.registerFormCallbacks(); + else + this.registerCallback(this.element); + }, + + onElementEvent: function() { + var value = this.getValue(); + if (this.lastValue != value) { + this.callback(this.element, value); + this.lastValue = value; + } + }, + + registerFormCallbacks: function() { + Form.getElements(this.element).each(this.registerCallback, this); + }, + + registerCallback: function(element) { + if (element.type) { + switch (element.type.toLowerCase()) { + case 'checkbox': + case 'radio': + Event.observe(element, 'click', this.onElementEvent.bind(this)); + break; + default: + Event.observe(element, 'change', this.onElementEvent.bind(this)); + break; + } + } + } +}); + +Form.Element.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.Element.getValue(this.element); + } +}); + +Form.EventObserver = Class.create(Abstract.EventObserver, { + getValue: function() { + return Form.serialize(this.element); + } +}); +if (!window.Event) var Event = { }; + +Object.extend(Event, { + KEY_BACKSPACE: 8, + KEY_TAB: 9, + KEY_RETURN: 13, + KEY_ESC: 27, + KEY_LEFT: 37, + KEY_UP: 38, + KEY_RIGHT: 39, + KEY_DOWN: 40, + KEY_DELETE: 46, + KEY_HOME: 36, + KEY_END: 35, + KEY_PAGEUP: 33, + KEY_PAGEDOWN: 34, + KEY_INSERT: 45, + + cache: { }, + + relatedTarget: function(event) { + var element; + switch(event.type) { + case 'mouseover': element = event.fromElement; break; + case 'mouseout': element = event.toElement; break; + default: return null; + } + return Element.extend(element); + } +}); + +Event.Methods = (function() { + var isButton; + + if (Prototype.Browser.IE) { + var buttonMap = { 0: 1, 1: 4, 2: 2 }; + isButton = function(event, code) { + return event.button == buttonMap[code]; + }; + + } else if (Prototype.Browser.WebKit) { + isButton = function(event, code) { + switch (code) { + case 0: return event.which == 1 && !event.metaKey; + case 1: return event.which == 1 && event.metaKey; + default: return false; + } + }; + + } else { + isButton = function(event, code) { + return event.which ? (event.which === code + 1) : (event.button === code); + }; + } + + return { + isLeftClick: function(event) { return isButton(event, 0) }, + isMiddleClick: function(event) { return isButton(event, 1) }, + isRightClick: function(event) { return isButton(event, 2) }, + + element: function(event) { + event = Event.extend(event); + + var node = event.target, + type = event.type, + currentTarget = event.currentTarget; + + if (currentTarget && currentTarget.tagName) { + // Firefox screws up the "click" event when moving between radio buttons + // via arrow keys. It also screws up the "load" and "error" events on images, + // reporting the document as the target instead of the original image. + if (type === 'load' || type === 'error' || + (type === 'click' && currentTarget.tagName.toLowerCase() === 'input' + && currentTarget.type === 'radio')) + node = currentTarget; + } + if (node.nodeType == Node.TEXT_NODE) node = node.parentNode; + return Element.extend(node); + }, + + findElement: function(event, expression) { + var element = Event.element(event); + if (!expression) return element; + var elements = [element].concat(element.ancestors()); + return Selector.findElement(elements, expression, 0); + }, + + pointer: function(event) { + var docElement = document.documentElement, + body = document.body || { scrollLeft: 0, scrollTop: 0 }; + return { + x: event.pageX || (event.clientX + + (docElement.scrollLeft || body.scrollLeft) - + (docElement.clientLeft || 0)), + y: event.pageY || (event.clientY + + (docElement.scrollTop || body.scrollTop) - + (docElement.clientTop || 0)) + }; + }, + + pointerX: function(event) { return Event.pointer(event).x }, + pointerY: function(event) { return Event.pointer(event).y }, + + stop: function(event) { + Event.extend(event); + event.preventDefault(); + event.stopPropagation(); + event.stopped = true; + } + }; +})(); + +Event.extend = (function() { + var methods = Object.keys(Event.Methods).inject({ }, function(m, name) { + m[name] = Event.Methods[name].methodize(); + return m; + }); + + if (Prototype.Browser.IE) { + Object.extend(methods, { + stopPropagation: function() { this.cancelBubble = true }, + preventDefault: function() { this.returnValue = false }, + inspect: function() { return "[object Event]" } + }); + + return function(event) { + if (!event) return false; + if (event._extendedByPrototype) return event; + + event._extendedByPrototype = Prototype.emptyFunction; + var pointer = Event.pointer(event); + Object.extend(event, { + target: event.srcElement, + relatedTarget: Event.relatedTarget(event), + pageX: pointer.x, + pageY: pointer.y + }); + return Object.extend(event, methods); + }; + + } else { + Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__']; + Object.extend(Event.prototype, methods); + return Prototype.K; + } +})(); + +Object.extend(Event, (function() { + var cache = Event.cache; + + function getEventID(element) { + if (element._prototypeEventID) return element._prototypeEventID[0]; + arguments.callee.id = arguments.callee.id || 1; + return element._prototypeEventID = [++arguments.callee.id]; + } + + function getDOMEventName(eventName) { + if (eventName && eventName.include(':')) return "dataavailable"; + return eventName; + } + + function getCacheForID(id) { + return cache[id] = cache[id] || { }; + } + + function getWrappersForEventName(id, eventName) { + var c = getCacheForID(id); + return c[eventName] = c[eventName] || []; + } + + function createWrapper(element, eventName, handler) { + var id = getEventID(element); + var c = getWrappersForEventName(id, eventName); + if (c.pluck("handler").include(handler)) return false; + + var wrapper = function(event) { + if (!Event || !Event.extend || + (event.eventName && event.eventName != eventName)) + return false; + + Event.extend(event); + handler.call(element, event); + }; + + wrapper.handler = handler; + c.push(wrapper); + return wrapper; + } + + function findWrapper(id, eventName, handler) { + var c = getWrappersForEventName(id, eventName); + return c.find(function(wrapper) { return wrapper.handler == handler }); + } + + function destroyWrapper(id, eventName, handler) { + var c = getCacheForID(id); + if (!c[eventName]) return false; + c[eventName] = c[eventName].without(findWrapper(id, eventName, handler)); + } + + function destroyCache() { + for (var id in cache) + for (var eventName in cache[id]) + cache[id][eventName] = null; + } + + + // Internet Explorer needs to remove event handlers on page unload + // in order to avoid memory leaks. + if (window.attachEvent) { + window.attachEvent("onunload", destroyCache); + } + + // Safari has a dummy event handler on page unload so that it won't + // use its bfcache. Safari <= 3.1 has an issue with restoring the "document" + // object when page is returned to via the back button using its bfcache. + if (Prototype.Browser.WebKit) { + window.addEventListener('unload', Prototype.emptyFunction, false); + } + + return { + observe: function(element, eventName, handler) { + element = $(element); + var name = getDOMEventName(eventName); + + var wrapper = createWrapper(element, eventName, handler); + if (!wrapper) return element; + + if (element.addEventListener) { + element.addEventListener(name, wrapper, false); + } else { + element.attachEvent("on" + name, wrapper); + } + + return element; + }, + + stopObserving: function(element, eventName, handler) { + element = $(element); + var id = getEventID(element), name = getDOMEventName(eventName); + + if (!handler && eventName) { + getWrappersForEventName(id, eventName).each(function(wrapper) { + element.stopObserving(eventName, wrapper.handler); + }); + return element; + + } else if (!eventName) { + Object.keys(getCacheForID(id)).each(function(eventName) { + element.stopObserving(eventName); + }); + return element; + } + + var wrapper = findWrapper(id, eventName, handler); + if (!wrapper) return element; + + if (element.removeEventListener) { + element.removeEventListener(name, wrapper, false); + } else { + element.detachEvent("on" + name, wrapper); + } + + destroyWrapper(id, eventName, handler); + + return element; + }, + + fire: function(element, eventName, memo) { + element = $(element); + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + var event; + if (document.createEvent) { + event = document.createEvent("HTMLEvents"); + event.initEvent("dataavailable", true, true); + } else { + event = document.createEventObject(); + event.eventType = "ondataavailable"; + } + + event.eventName = eventName; + event.memo = memo || { }; + + if (document.createEvent) { + element.dispatchEvent(event); + } else { + element.fireEvent(event.eventType, event); + } + + return Event.extend(event); + } + }; +})()); + +Object.extend(Event, Event.Methods); + +Element.addMethods({ + fire: Event.fire, + observe: Event.observe, + stopObserving: Event.stopObserving +}); + +Object.extend(document, { + fire: Element.Methods.fire.methodize(), + observe: Element.Methods.observe.methodize(), + stopObserving: Element.Methods.stopObserving.methodize(), + loaded: false +}); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards and John Resig. */ + + var timer; + + function fireContentLoadedEvent() { + if (document.loaded) return; + if (timer) window.clearInterval(timer); + document.fire("dom:loaded"); + document.loaded = true; + } + + if (document.addEventListener) { + if (Prototype.Browser.WebKit) { + timer = window.setInterval(function() { + if (/loaded|complete/.test(document.readyState)) + fireContentLoadedEvent(); + }, 0); + + Event.observe(window, "load", fireContentLoadedEvent); + + } else { + document.addEventListener("DOMContentLoaded", + fireContentLoadedEvent, false); + } + + } else { + document.write("