Roxen.git/server/modules/graphics/gbutton.pike:1:
// Button module. Generates graphical buttons for use in Roxen config
// interface, Roxen SiteBuilder and other places.
//
- // Copyright © 1999-2000 Roxen IS. Author: Jonas Walldén, <jonasw@roxen.com>
+ // Copyright © 1999 - 2009, Roxen IS. Author: Jonas Walldén, <jonasw@roxen.com>
// Usage:
//
// <gbutton
// bgcolor -- background color inside/outside button
// textcolor -- button text color
// href -- button URL
-
+ // target -- target frame
// alt -- alternative button alt text
-
+ // title -- button tooltip
// border -- image border
// state -- enabled|disabled button state
// textstyle -- normal|consensed text
// icon-src -- icon reference
// icon-data -- inline icon data
// align -- left|center|right text alignment
// align-icon -- left|center-before|center-after|right icon alignment
// valign-icon -- above|middle|below icon vertical alignment
// >Button text</gbutton>
//
// Alignment restriction: when text alignment is either left or right, icons
// must also be aligned left or right.
- constant cvs_version = "$Id: gbutton.pike,v 1.83 2001/04/03 07:29:40 per Exp $";
+ constant cvs_version = "$Id$";
constant thread_safe = 1;
#include <module.h>
inherit "module";
roxen.ImageCache button_cache;
int do_ext;
constant module_type = MODULE_TAG;
constant module_name = "Graphics: GButton";
Roxen.git/server/modules/graphics/gbutton.pike:47:
"Append the image format (.gif, .png, "
".jpg, etc) to the generated images. "
"This is not necessary, but might seem "
"nicer, especially to people who try "
"to mirror your site."));
}
mapping tagdocumentation() {
Stdio.File file=Stdio.File();
if(!file->open(__FILE__,"r")) return 0;
- string doc=compile_string("#define manual\n"+file->read())->gbuttonattr;
+ string doc=compile_string("#define manual\n"+file->read(), __FILE__)->gbuttonattr;
string imagecache=button_cache->documentation();
return ([
- "gbutton":#"<desc cont='cont'><p><short>
+ "gbutton":#"<desc type='cont'><p><short>
Creates graphical buttons.</short></p>
</desc>"
+doc
+imagecache,
- "gbutton-url":#"<desc cont='cont'><p><short>
+ "gbutton-url":#"<desc type='cont'><p><short>
Generates an URI to the button.</short> <tag>gbutton-url</tag> takes
the same attributes as <xref href='gbutton.tag' /> including the
image cache attributes.</p>
</desc>"
+doc
+imagecache,
]);
}
Roxen.git/server/modules/graphics/gbutton.pike:83: Inside #if defined(manual)
</attr>
<attr name='bgcolor' value='color'><p>
Background color inside and outside button.</p>
<ex>
<gbutton bgcolor='lightblue'>Background</gbutton>
</ex>
</attr>
- <attr name='textcolor' value='color'><p>
- Button text color.</p>
- <ex>
- <gbutton textcolor='#ff6600'>Text</gbutton>
- </ex>
+ <attr name='textcolor' value='color'>
+ <p>Button text color.</p>
+ <ex><gbutton textcolor='#ff6600'>Text</gbutton></ex>
</attr>
<attr name='frame-image' value='path'><p>
Use this XCF-image as a frame for the button. The image is required
- to have at least the following layers: background, mask and frame.
-
- <?comment
- Non working example.
-
- More information on how to create frame images can be found in the
- Roxen documentation; Web Site Creator/Graphical tags section.</p>
- <ex>
- <gbutton frame-image='internal-roxen-tabframe'>foo</gbutton>
- </ex>
- ?>
-
+ to have at least the following layers: background, mask and frame.</p>
+ "+/* <ex><gbutton frame-image='internal-roxen-tabframe'>foo</gbutton></ex> */#"
</attr>
<attr name='alt' value='string'><p>
Alternative button and alt text.</p>
</attr>
-
+ <attr name='title' value='string'><p>
+ Button tooltip.</p>
+ </attr>
+
<attr name='href' value='uri'><p>
Button URI.</p>
</attr>
-
+ <attr name='target' value='string'><p>
+ Button target frame.</p>
+ </attr>
+
<attr name='textstyle' value='normal|condensed'><p>
- Set to <att>normal</att> or <att>condensed</att> to alter text style.</p>
+ Set to <i>normal</i> or <i>condensed</i> to alter text style.</p>
</attr>
<attr name='width' value=''><p>
Minimum button width.</p>
</attr>
<attr name='align' value='left|center|right'><p>
Set text alignment. There are some alignment restrictions: when text
- alignment is either <att>left</att> or <att>right</att>, icons must
- also be aligned <att>left</att> or <att>right</att>.</p>
+ alignment is either <i>left</i> or <i>right</i>, icons must
+ also be aligned <i>left</i> or <i>right</i>.</p>
</attr>
-
+ <attr name='img-align' value=''><p>
+ Alignment passed on to the resulting <tag>img</tag>.</p>
+ </attr>
+
<attr name='state' value='enabled|disabled'><p>
- Set to <att>enabled</att> or <att>disabled</att> to select button state.</p>
+ Set to <i>enabled</i> or <i>disabled</i> to select button state.</p>
</attr>
<attr name='icon-src' value='URI'><p>
Fetch the icon from this URI.</p>
</attr>
<attr name='icon-data' value=''><p>
Inline icon data.</p>
</attr>
<attr name='align-icon' value='left|center-before|center-after|right'><p>
Set icon alignment.</p>
<xtable>
<row><c><p>left</p></c><c><p>Place icon on the left side of the text.</p></c></row>
- <row><c><p>center-before</p></c><c><p>Center the icon before the text. Requires the <att>align='center'</att> attribute.</p></c></row>
- <row><c><p>center-after</p></c><c><p>Center the icon after the text. Requires the <att>align='center'</att> attribute.</p></c></row>
+ <row><c><p>center-before</p></c><c><p>Center the icon before the text. Requires the <i>align='center'</i> attribute.</p></c></row>
+ <row><c><p>center-after</p></c><c><p>Center the icon after the text. Requires the <i>align='center'</i> attribute.</p></c></row>
<row><c><p>right</p></c><c><p>Place icon on the right side of the text.</p></c></row>
</xtable>
<ex>
<gbutton width='150' align-icon='center-before' icon-src='internal-roxen-help'>Roxen 2.0</gbutton>
</ex>
<ex>
<gbutton width='150' align='center' align-icon='center-after'
- icon-src='internal-roxen-help'>Roxen 2.0</gbutton>
+ icon-src='/internal-roxen-help'>Roxen 2.0</gbutton>
</ex>
</attr>
<attr name='valign-icon' value='above|middle|below'><p>
Set icon vertical alignment. Requires three horizontal guidelines in the
- frame image. If set to <att>above</att> the icon is placed between the first
+ frame image. If set to <i>above</i> the icon is placed between the first
and second guidelines and the text between the second and third ones. If
- set to <att>below</att> the placement is reversed. Default value is
- <att>middle</att>.</p>
+ set to <i>below</i> the placement is reversed. Default value is
+ <i>middle</i>.</p>
</attr>
- <attr name='font' value='fontname'><p></p>
+ <attr name='font' value='fontname'><p></p></attr>
-
+ <h1>Timeout</h1>
+
+ <p>The generated image will by default never expire, but
+ in some circumstances it may be pertinent to limit the
+ time the image and its associated data is kept. Its
+ possible to set an (advisory) timeout on the image data
+ using the following attributes.</p>
+
+ <attr name='unix-time' value='number'><p>
+ Set the base expiry time to this absolute time.</p><p>
+ If left out, the other attributes are relative to current time.</p>
+ </attr>
+
+ <attr name='years' value='number'><p>
+ Add this number of years to the time this entry is valid.</p>
+ </attr>
+
+ <attr name='months' value='number'><p>
+ Add this number of months to the time this entry is valid.</p>
+ </attr>
+
+ <attr name='weeks' value='number'><p>
+ Add this number of weeks to the time this entry is valid.</p>
+ </attr>
+
+ <attr name='days' value='number'><p>
+ Add this number of days to the time this entry is valid.</p>
+ </attr>
+
+ <attr name='hours' value='number'><p>
+ Add this number of hours to the time this entry is valid.</p>
+ </attr>
+
+ <attr name='beats' value='number'><p>
+ Add this number of beats to the time this entry is valid.</p>
+ </attr>
+
+ <attr name='minutes' value='number'><p>
+ Add this number of minutes to the time this entry is valid.</p>
+ </attr>
+
+ <attr name='seconds' value='number'><p>
+ Add this number of seconds to the time this entry is valid.</p>
</attr>";
#endif
-
+ // Cached copy of conf->query("compat_level"). This setting is defined
+ // to require a module reload to take effect so we only query it when
+ // the module instance is created.
+ float compat_level = (float) my_configuration()->query("compat_level");
+
function TIMER( function f )
{
#if 0
return lambda(mixed ... args) {
int h = gethrtime();
mixed res;
werror("Drawing ... ");
res = f( @args );
werror(" %.1fms\n", (gethrtime()-h)/1000000.0 );
return res;
};
#endif
return f;
}
void start()
{
button_cache = roxen.ImageCache("gbutton", TIMER(draw_button));
do_ext = query("ext");
}
-
+ void stop()
+ {
+ destruct(button_cache);
+ }
+
string status() {
array s=button_cache->status();
return sprintf("<b>Images in cache:</b> %d images<br />\n"
"<b>Cache size:</b> %s",
s[0], Roxen.sizetostring(s[1]));
}
mapping(string:function) query_action_buttons() {
- return ([ "Clear cache":flush_cache ]);
+ return ([ "Clear Cache":flush_cache ]);
}
void flush_cache() {
button_cache->flush();
-
+
+ // It's possible that user code contains a number of stale URLs in
+ // e.g. <cache> blocks so we can just as well flush the RAM cache to
+ // reduce the risk of broken images.
+ cache.flush_memory_cache();
}
Image.Layer layer_slice( Image.Layer l, int from, int to )
{
return Image.Layer( ([
"image":l->image()->copy( from,0, to-1, l->ysize()-1 ),
"alpha":l->alpha()->copy( from,0, to-1, l->ysize()-1 ),
]) );
}
Roxen.git/server/modules/graphics/gbutton.pike:237:
l->set_offset( 0,0 );
m->set_offset( x1,0 );
r->set_offset( w-r->xsize(),0 );
o = Image.lay( ({ l, m, r }) );
o->set_mode( oo->mode() );
o->set_alpha_value( oo->alpha_value() );
return o;
}
- array(Image.Layer) draw_button(mapping args, string text, object id)
+ array(Image.Layer)|mapping draw_button(mapping args, string text, object id)
{
Image.Image text_img;
mapping icon;
Image.Layer background;
Image.Layer frame;
Image.Layer mask;
int left, right, top, middle, bottom; /* offsets */
int req_width, noframe;
mapping ll = ([]);
-
+ // Photoshop layers: don't let individual layers expand the image
+ // beyond the bounds of the overall image.
+ mapping opts = ([ "crop_to_bounds" : 1 ]);
+
void set_image( array layers )
{
foreach( layers||({}), object l )
{
if(!l->get_misc_value( "name" ) ) // Hm. Probably PSD
continue;
ll[lower_case(l->get_misc_value( "name" ))] = l;
switch( lower_case(l->get_misc_value( "name" )) )
{
case "background": background = l; break;
case "frame": frame = l; break;
case "mask": mask = l; break;
}
}
};
if( args->border_image )
- set_image( roxen.load_layers(args->border_image, id) );
+ {
+ array(Image.Layer)|mapping tmp;
-
+ #if constant(Sitebuilder) && constant(Sitebuilder.sb_start_use_imagecache)
+ // Let SiteBuilder get a chance to decode its argument data
+ if (Sitebuilder.sb_start_use_imagecache) {
+ Sitebuilder.sb_start_use_imagecache(args, id);
+ tmp = roxen.load_layers(args->border_image, id, opts);
+ Sitebuilder.sb_end_use_imagecache(args, id);
+ } else
+ #endif
+ {
+ tmp = roxen.load_layers(args->border_image, id, opts);
+ }
-
+ if (mappingp(tmp)) {
+ if (tmp->error != 401)
+ RXML.parse_error("Failed to load frame image: %O (error: %O)\n",
+ args->border_image, tmp->error);
+ return tmp;
+ }
+ set_image( tmp );
+ }
+
+
// otherwise load default images
if ( !frame && !background && !mask )
{
string data = Stdio.read_file("roxen-images/gbutton.xcf");
if (!data)
error ("Failed to load default frame image "
"(roxen-images/gbutton.xcf): " + strerror (errno()));
mixed err = catch {
set_image(Image.XCF.decode_layers(data));
};
Roxen.git/server/modules/graphics/gbutton.pike:351:
case "below":
text_height = middle - top;
break;
default:
case "middle":
text_height = bottom - top;
break;
}
// Get icon
- if (args->icn)
- icon = roxen.low_load_image(args->icn, id);
- else if (args->icd)
+ if (args->icn) {
+ // Pass error mapping to find out possible errors when loading icon
+ mapping err = ([ ]);
+ icon = roxen.low_load_image(args->icn, id, err);
+
+ // If icon loading fails due to missing authentication we reject the
+ // gbutton request so that the browser can re-request it with proper
+ // authentication headers.
+ if (!icon && err->error == 401)
+ return err;
+ } else if (args->icd)
icon = roxen.low_decode_image(args->icd);
int i_width = icon && icon->img->xsize();
int i_height = icon && icon->img->ysize();
int i_spc = i_width && sizeof(text) && 5;
// Generate text
if (sizeof(text))
{
- int os, dir;
- Font button_font;
- int th = text_height;
- do
- {
- button_font = resolve_font( args->font+" "+th );
+ int min_font_size = 0;
+ int max_font_size = text_height * 2;
+ do {
+ // Use binary search to find an appropriate font size. Since we prefer
+ // font sizes which err on the small side (so we never extend outside
+ // the given boundaries) we must round up when computing the next size
+ // or we risk missing a good size.
+ int try_font_size = (max_font_size + min_font_size + 1) / 2;
+ Font button_font = resolve_font(args->font + " " + try_font_size);
text_img = button_font->write(text);
- os = text_img->ysize();
- if( !dir )
- if( os < text_height )
- dir = 1;
- else if( os > text_height )
- dir =-1;
- if( dir > 0 && os > text_height ) break;
- else if( dir < 0 && os < text_height ) dir = 1;
- else if( os == text_height ) break;
- th += dir;
- } while( (text_img->ysize() - text_height)
- && (th>0 && th<text_height*2));
+ int real_height = text_img->ysize();
-
+ // Early bail for fixed-point fonts which are too large
+ if (real_height > try_font_size * 2)
+ break;
+
+ // Go up or down in size?
+ if (real_height == text_height)
+ break;
+ if (real_height > text_height)
+ max_font_size = try_font_size - 1;
+ else {
+ if (min_font_size == max_font_size)
+ break;
+ min_font_size = try_font_size;
+ }
+ } while (max_font_size - min_font_size >= 0);
+
// fonts that can not be scaled.
if( abs(text_img->ysize() - text_height)>2 )
text_img = text_img->scale(0, text_height );
else
{
int o = text_img->ysize() - text_height;
top -= o;
middle -= o/2;
}
if (args->cnd)
text_img = text_img->scale((int) round(text_img->xsize() * 0.8),
text_img->ysize());
- }
+ } else
+ text_height = 0;
int t_width = text_img && text_img->xsize();
// Compute text and icon placement. Only incorporate icon width/spacing if
// it's placed inline with the text.
req_width = t_width + left + right;
if ((args->icva || "middle") == "middle")
req_width += i_width + i_spc;
if (args->wi && (req_width < args->wi))
req_width = args->wi;
Roxen.git/server/modules/graphics/gbutton.pike:417:
int icn_x, icn_y, txt_x, txt_y;
// Are text and icon lined up or on separate lines?
switch (args->icva) {
case "above":
case "below":
// Note: This requires _three_ guidelines! Icon and text can only be
// horizontally centered
icn_x = left + (req_width - right - left - i_width) / 2;
txt_x = left + (req_width - right - left - t_width) / 2;
- if (args->icva == "above") {
+ if (args->icva == "above" || !text_height) {
txt_y = middle;
- icn_y = top + (middle - top - i_height) / 2;
+ icn_y = top + ((text_height ? middle : bottom) - top - i_height) / 2;
} else {
txt_y = top;
icn_y = middle + (bottom - middle - i_height) / 2;
}
break;
default:
case "middle":
// Center icon vertically on same line as text
icn_y = icon && (frame->ysize() - icon->img->ysize()) / 2;
Roxen.git/server/modules/graphics/gbutton.pike:476:
icn_x = (req_width - i_width - i_spc - t_width) / 2;
txt_x = icn_x + i_width + i_spc;
break;
case "center_after":
case "center-after":
txt_x = (req_width - i_width - i_spc - t_width) / 2;
icn_x = txt_x + t_width + i_spc;
break;
case "right":
icn_x = req_width - right - i_width;
- txt_x = left + (icn_x - i_spc - t_width) / 2;
+ txt_x = left + (icn_x - i_spc - t_width - left) / 2;
break;
}
break;
case "right":
// Allow icon alignment: left, right
switch (args->ica)
{
default:
case "left":
Roxen.git/server/modules/graphics/gbutton.pike:701:
// 1. Only do this check when the ext flag is set, old URLs might
// live in caches
//
// 2. Check str[-4] for '.', consider .jpeg .tiff etc.
//
// However, . is not a valid character in the ID, so just cutting at
// the first one works.
return button_cache->http_file_answer( (f/".")[0], id );
}
- mapping __stat_cache = ([ ]);
+
int get_file_stat( string f, RequestID id )
{
- if( __stat_cache[ f ] )
- return __stat_cache[ f ];
+ int res;
+ mapping stat_cache;
- call_out( m_delete, 10, __stat_cache, f );
- return __stat_cache[ f ] = (id->conf->stat_file( f,id )
- || file_stat( f )
- || ({ 0,0,0,0 }))[ST_MTIME];
+ // -1 is used to cache negative results. When SiteBuilder crawler runs
+ // we must let the stat_file() run unconditionally to register
+ // dependencies properly.
+ if (stat_cache = id->misc->gbutton_statcache) {
+ if (!id->misc->persistent_cache_crawler)
+ if (res = stat_cache[f])
+ return (res > 0) && res;
+ } else
+ stat_cache = id->misc->gbutton_statcache = ([ ]);
+
+ int was_internal = id->misc->internal_get;
+ id->misc->internal_get = 1;
+ res = stat_cache[ f ] = (id->conf->stat_file( f,id ) ||
+ ({ 0,0,0,0 }) )[ST_MTIME] || -1;
+ if (!was_internal)
+ m_delete(id->misc, "internal_get");
+ return (res > 0) && res;
}
class ButtonFrame {
inherit RXML.Frame;
array mk_url(RequestID id)
{
// int t = gethrtime();
string fi = (args["frame-image"] ||
id->misc->defines["gbutton-frame-image"]);
- if( fi )
+ if( fi ) {
+ // Reject empty file paths for sufficiently high compat_level
+ if (fi == "" && compat_level >= 5.2)
+ RXML.parse_error("Empty frame-image attribute not allowed.");
+
fi = Roxen.fix_relative( fi, id );
-
+ }
+ m_delete(args, "frame-image");
// Harmonize some attribute names to RXML standards...
args->icon_src = args["icon-src"] || args->icon_src;
args->icon_data = args["icon-data"] || args->icon_data;
args->align_icon = args["align-icon"] || args->align_icon;
args->valign_icon = args["valign-icon"] || args->valign_icon;
m_delete(args, "icon-src");
m_delete(args, "icon-data");
m_delete(args, "align-icon");
-
+ if (args->icon_src == "" && compat_level >= 5.2)
+ RXML.parse_error("Empty icon-src attribute not allowed.");
+
mapping new_args =
([
"pagebg" :parse_color(args->pagebgcolor ||
id->misc->defines->theme_bgcolor ||
id->misc->defines->bgcolor ||
args->bgcolor ||
"#eeeeee"), // _page_ bg color
"bg" : parse_color(args->bgcolor ||
id->misc->defines->theme_bgcolor ||
id->misc->defines->bgcolor ||
Roxen.git/server/modules/graphics/gbutton.pike:763:
"al" : args->align || "left", // Text alignment
"dim" : (args->dim || // Button dimming
(< "dim", "disabled" >)[lower_case(args->state || "")]),
"icn" : args->icon_src &&
Roxen.fix_relative(args->icon_src, id), // Icon URL
"icd" : args->icon_data, // Inline icon data
"ica" : lower_case(args->align_icon || "left"), // Icon alignment
"icva": lower_case(args->valign_icon || "middle"),// Vertical align
"font": (args->font||id->misc->defines->font||
roxen->query("default_font")),
+ "fontkey": roxen->fonts->verify_font(args->font||id->misc->defines->font),
"border_image":fi,
"extra_layers":args["extra-layers"],
"extra_left_layers":args["extra-left-layers"],
"extra_right_layers":args["extra-right-layers"],
"extra_background_layers":args["extra-background-layers"],
"extra_mask_layers":args["extra-mask-layers"],
"extra_frame_layers":args["extra-frame-layers"],
"scale":args["scale"],
"format":args["format"],
"gamma":args["gamma"],
"crop":args["crop"],
]);
- if( fi )
+
+ // Remove extra layer attributes to avoid *-* copying below
+ m_delete(args, "extra-layers");
+ m_delete(args, "extra-left-layers");
+ m_delete(args, "extra-right-layers");
+ m_delete(args, "extra-background-layers");
+ m_delete(args, "extra-mask-layers");
+ m_delete(args, "extra-frame-layers");
+
+ int timeout = Roxen.timeout_dequantifier(args);
+
+ if( fi ) {
new_args->stat = get_file_stat( fi, id );
-
+ #if constant(Sitebuilder) && constant(Sitebuilder.sb_prepare_imagecache)
+ // The file we called get_file_stat() on above may be a SiteBuilder
+ // file. If so we need to extend the argument data with e.g.
+ // current language fork.
+ if (Sitebuilder.sb_prepare_imagecache)
+ new_args = Sitebuilder.sb_prepare_imagecache(new_args, fi, id);
+ #endif
+ }
-
+ if (string icn_path = new_args->icn) {
+ new_args->stat_icn = get_file_stat(icn_path, id);
+ #if constant(Sitebuilder) && constant(Sitebuilder.sb_prepare_imagecache)
+ if (Sitebuilder.sb_prepare_imagecache)
+ new_args = Sitebuilder.sb_prepare_imagecache(new_args, icn_path, id);
+ #endif
+ }
+
new_args->quant = args->quant || 128;
foreach(glob("*-*", indices(args)), string n)
new_args[n] = args[n];
-
+ //string fn;
// if( new_args->stat && (fn = id->conf->real_file( fi, id ) ) )
// Roxen.add_cache_stat_callback( id, fn, new_args->stat );
- string fn;
+
// werror("mkurl took %dµs\n", gethrtime()-t );
// t = gethrtime();
string img_src =
query_absolute_internal_location(id) +
- button_cache->store( ({ new_args, content }), id);
+ button_cache->store( ({ new_args, (string)content }), id, timeout);
if(do_ext)
img_src += "." + (new_args->format || "gif");
// werror("argcache->store took %dµs\n", gethrtime()-t );
- return ({ img_src, new_args });
+ return ({ img_src, new_args, timeout });
}
}
class TagGButtonURL {
inherit RXML.Tag;
constant name = "gbutton-url";
constant flags = RXML.FLAG_DONT_REPORT_ERRORS;
RXML.Type content_type = RXML.t_text(RXML.PXml);
class Frame {
Roxen.git/server/modules/graphics/gbutton.pike:828:
constant name = "gbutton";
constant flags = RXML.FLAG_DONT_REPORT_ERRORS;
RXML.Type content_type = RXML.t_text(RXML.PXml);
class Frame {
inherit ButtonFrame;
array do_return(RequestID id) {
// Peek at img-align and remove it so it won't be copied by "*-*" glob
// in mk_url().
string img_align = args["img-align"];
+ string title = args->title;
m_delete(args, "img-align");
- [string img_src, mapping new_args]=mk_url(id);
+ [string img_src, mapping new_args, int timeout]=mk_url(id);
mapping img_attrs = ([ "src" : img_src,
- "alt" : args->alt || content,
+ "alt" : args->alt || (string)content,
"border" : args->border,
"hspace" : args->hspace,
"vspace" : args->vspace ]);
if (img_align)
img_attrs->align = img_align;
-
+ if (title)
+ img_attrs->title = title;
- if (mapping size = button_cache->metadata( ({ new_args, content }),
- id, 1)) {
- // Image in cache (1 above prevents generation on-the-fly, i.e.
+ int no_draw = !id->misc->generate_images;
+ if (mapping size = button_cache->metadata( ({ new_args, (string)content }),
+ id, no_draw, timeout)) {
+ // Image in cache (no_draw above prevents generation on-the-fly, i.e.
// first image will lack sizes).
img_attrs->width = size->xsize;
img_attrs->height = size->ysize;
}
result = Roxen.make_tag("img", img_attrs, !args->noxml);
// Make button clickable if not dimmed
if(args->href && !new_args->dim)
{
- mapping a_attrs = ([ "href" : args->href ]);
+ mapping a_attrs = ([ "href" : args->href ]);
foreach(indices(args), string arg)
- if(has_value("target/onmousedown/onmouseup/onclick/ondblclick/"
+ if(has_value("/target/onmousedown/onmouseup/onclick/ondblclick/"
"onmouseout/onmouseover/onkeypress/onkeyup/"
- "onkeydown" / "/", lower_case(arg)))
+ "onkeydown/style/class/id/accesskey/",
+ "/" + lower_case(arg) + "/"))
a_attrs[arg] = args[arg];
result = Roxen.make_container("a", a_attrs, result);
}
return 0;
}
}
}