67bff71999-11-15Jonas Wallden // Button module. Generates graphical buttons for use in Roxen config // interface, Roxen SiteBuilder and other places. //
f41b982009-05-07Martin Stjernholm // Copyright © 1999 - 2009, Roxen IS. Author: Jonas Walldén, <jonasw@roxen.com>
67bff71999-11-15Jonas Wallden  // Usage: // // <gbutton // bgcolor -- background color inside/outside button // textcolor -- button text color // href -- button URL
f94ec52003-12-29Henrik Grubbström (Grubba) // target -- target frame
67bff71999-11-15Jonas Wallden // alt -- alternative button alt text
8ac5122003-12-15Jonas Wallden // title -- button tooltip
67bff71999-11-15Jonas Wallden // border -- image border
0b91941999-11-16Jonas Wallden // state -- enabled|disabled button state // textstyle -- normal|consensed text
dc73752000-05-25Jonas Wallden // icon-src -- icon reference // icon-data -- inline icon data
67bff71999-11-15Jonas Wallden // align -- left|center|right text alignment
dc73752000-05-25Jonas Wallden // align-icon -- left|center-before|center-after|right icon alignment
6cf7e52000-08-07Jonas Wallden // valign-icon -- above|middle|below icon vertical alignment
67bff71999-11-15Jonas Wallden // >Button text</gbutton> // // Alignment restriction: when text alignment is either left or right, icons // must also be aligned left or right.
0917d32013-03-04Anders Johansson constant cvs_version = "$Id$";
67bff71999-11-15Jonas Wallden constant thread_safe = 1; #include <module.h> inherit "module"; roxen.ImageCache button_cache;
4afee22001-04-03Per Hedbor int do_ext;
67bff71999-11-15Jonas Wallden 
b3281f2000-09-10Martin Nilsson constant module_type = MODULE_TAG;
bc0fa02001-03-08Per Hedbor constant module_name = "Graphics: GButton";
76fd092000-04-06Mattias Wingstedt constant module_doc = "Provides the <tt>&lt;gbutton&gt;</tt> tag that is used to draw graphical " "buttons.";
2e0a351999-12-09Martin Nilsson 
9d86b52001-03-30Johan Sundström void create() { defvar("ext", Variable.Flag(0, VAR_MORE, "Append format to generated images", "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.")); }
7390582000-06-01Martin Nilsson mapping tagdocumentation() { Stdio.File file=Stdio.File(); if(!file->open(__FILE__,"r")) return 0;
b996582004-06-21Martin Stjernholm  string doc=compile_string("#define manual\n"+file->read(), __FILE__)->gbuttonattr;
7390582000-06-01Martin Nilsson  string imagecache=button_cache->documentation();
67bff71999-11-15Jonas Wallden 
9b03652001-03-07Kenneth Johansson  return ([
ce8fb02001-09-21Johan Sundström "gbutton":#"<desc type='cont'><p><short>
9b03652001-03-07Kenneth Johansson  Creates graphical buttons.</short></p> </desc>"
67bff71999-11-15Jonas Wallden 
7390582000-06-01Martin Nilsson  +doc +imagecache,
67bff71999-11-15Jonas Wallden 
ce8fb02001-09-21Johan Sundström "gbutton-url":#"<desc type='cont'><p><short>
9b03652001-03-07Kenneth Johansson  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>"
67bff71999-11-15Jonas Wallden 
7390582000-06-01Martin Nilsson  +doc +imagecache, ]); }
67bff71999-11-15Jonas Wallden 
7390582000-06-01Martin Nilsson #ifdef manual constant gbuttonattr=#"
9b03652001-03-07Kenneth Johansson <attr name='pagebgcolor' value='color'><p></p>
7390582000-06-01Martin Nilsson  </attr>
9b03652001-03-07Kenneth Johansson <attr name='bgcolor' value='color'><p> Background color inside and outside button.</p>
7390582000-06-01Martin Nilsson <ex> <gbutton bgcolor='lightblue'>Background</gbutton> </ex> </attr>
67bff71999-11-15Jonas Wallden 
27758e2001-07-20Johan Sundström <attr name='textcolor' value='color'> <p>Button text color.</p> <ex><gbutton textcolor='#ff6600'>Text</gbutton></ex>
7390582000-06-01Martin Nilsson </attr>
67bff71999-11-15Jonas Wallden 
9b03652001-03-07Kenneth Johansson <attr name='frame-image' value='path'><p>
7390582000-06-01Martin Nilsson  Use this XCF-image as a frame for the button. The image is required
27758e2001-07-20Johan Sundström  to have at least the following layers: background, mask and frame.</p>
7dfc6a2001-07-20Johan Sundström "+/* <ex><gbutton frame-image='internal-roxen-tabframe'>foo</gbutton></ex> */#"
7390582000-06-01Martin Nilsson </attr>
67bff71999-11-15Jonas Wallden 
9b03652001-03-07Kenneth Johansson <attr name='alt' value='string'><p> Alternative button and alt text.</p>
7390582000-06-01Martin Nilsson </attr>
8ac5122003-12-15Jonas Wallden <attr name='title' value='string'><p> Button tooltip.</p> </attr>
9b03652001-03-07Kenneth Johansson <attr name='href' value='uri'><p> Button URI.</p>
7390582000-06-01Martin Nilsson </attr>
f94ec52003-12-29Henrik Grubbström (Grubba) <attr name='target' value='string'><p> Button target frame.</p> </attr>
9b03652001-03-07Kenneth Johansson <attr name='textstyle' value='normal|condensed'><p>
7dfc6a2001-07-20Johan Sundström  Set to <i>normal</i> or <i>condensed</i> to alter text style.</p>
7390582000-06-01Martin Nilsson </attr>
9b03652001-03-07Kenneth Johansson <attr name='width' value=''><p> Minimum button width.</p>
7390582000-06-01Martin Nilsson </attr>
9b03652001-03-07Kenneth Johansson <attr name='align' value='left|center|right'><p>
7390582000-06-01Martin Nilsson  Set text alignment. There are some alignment restrictions: when text
7dfc6a2001-07-20Johan Sundström  alignment is either <i>left</i> or <i>right</i>, icons must also be aligned <i>left</i> or <i>right</i>.</p>
7390582000-06-01Martin Nilsson </attr>
ee3b802003-10-30Anders Johansson <attr name='img-align' value=''><p> Alignment passed on to the resulting <tag>img</tag>.</p> </attr>
9b03652001-03-07Kenneth Johansson <attr name='state' value='enabled|disabled'><p>
7dfc6a2001-07-20Johan Sundström  Set to <i>enabled</i> or <i>disabled</i> to select button state.</p>
7390582000-06-01Martin Nilsson </attr>
9b03652001-03-07Kenneth Johansson <attr name='icon-src' value='URI'><p> Fetch the icon from this URI.</p>
7390582000-06-01Martin Nilsson </attr>
9b03652001-03-07Kenneth Johansson <attr name='icon-data' value=''><p> Inline icon data.</p>
7390582000-06-01Martin Nilsson </attr>
9b03652001-03-07Kenneth Johansson <attr name='align-icon' value='left|center-before|center-after|right'><p> Set icon alignment.</p>
7390582000-06-01Martin Nilsson 
9b03652001-03-07Kenneth Johansson <xtable>
0186dc2001-03-28Kenneth Johansson <row><c><p>left</p></c><c><p>Place icon on the left side of the text.</p></c></row>
7dfc6a2001-07-20Johan Sundström <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>
0186dc2001-03-28Kenneth Johansson <row><c><p>right</p></c><c><p>Place icon on the right side of the text.</p></c></row>
9b03652001-03-07Kenneth Johansson </xtable>
7390582000-06-01Martin Nilsson  <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'
7dfc6a2001-07-20Johan Sundström  icon-src='/internal-roxen-help'>Roxen 2.0</gbutton>
7390582000-06-01Martin Nilsson </ex> </attr>
9b03652001-03-07Kenneth Johansson <attr name='valign-icon' value='above|middle|below'><p>
6363fa2000-08-15Jonas Wallden  Set icon vertical alignment. Requires three horizontal guidelines in the
7dfc6a2001-07-20Johan Sundström  frame image. If set to <i>above</i> the icon is placed between the first
6363fa2000-08-15Jonas Wallden  and second guidelines and the text between the second and third ones. If
7dfc6a2001-07-20Johan Sundström  set to <i>below</i> the placement is reversed. Default value is <i>middle</i>.</p>
7390582000-06-01Martin Nilsson </attr>
5adc582009-11-30Henrik Grubbström (Grubba) <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>";
2e0a351999-12-09Martin Nilsson #endif
67bff71999-11-15Jonas Wallden 
ed9f2a2011-01-21Jonas Wallden // 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");
8a98622000-04-04Per Hedbor 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; }
67bff71999-11-15Jonas Wallden void start() {
8a98622000-04-04Per Hedbor  button_cache = roxen.ImageCache("gbutton", TIMER(draw_button));
4afee22001-04-03Per Hedbor  do_ext = query("ext");
67bff71999-11-15Jonas Wallden }
c7ba992010-04-27Henrik Grubbström (Grubba) void stop() { destruct(button_cache); }
f049812000-07-24Martin Nilsson string status() { array s=button_cache->status();
482ba72000-12-30Per Hedbor  return sprintf("<b>Images in cache:</b> %d images<br />\n" "<b>Cache size:</b> %s", s[0], Roxen.sizetostring(s[1]));
f049812000-07-24Martin Nilsson } mapping(string:function) query_action_buttons() {
da43042005-12-16Jonas Wallden  return ([ "Clear Cache":flush_cache ]);
f049812000-07-24Martin Nilsson } void flush_cache() { button_cache->flush();
3cca1c2004-05-21Jonas Wallden  // 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();
f049812000-07-24Martin Nilsson }
fde9082000-02-08Per Hedbor 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 ), ]) ); } Image.Layer stretch_layer( Image.Layer o, int x1, int x2, int w ) { Image.Layer l, m, r; int leftovers = w - (x1 + (o->xsize()-x2) ); object oo = o; l = layer_slice( o, 0, x1 );
3ef6452000-02-08Jonas Wallden  m = layer_slice( o, x1+1, x2-1 );
fde9082000-02-08Per Hedbor  r = layer_slice( o, x2, o->xsize() ); m->set_image( m->image()->scale( leftovers, l->ysize() ), m->alpha()->scale( leftovers, l->ysize() )); l->set_offset( 0,0 ); m->set_offset( x1,0 ); r->set_offset( w-r->xsize(),0 ); o = Image.lay( ({ l, m, r }) );
e0fda72000-04-04Marcus Comstedt  o->set_mode( oo->mode() ); o->set_alpha_value( oo->alpha_value() );
fde9082000-02-08Per Hedbor  return o; }
67bff71999-11-15Jonas Wallden 
6a613a2002-06-17Anders Johansson array(Image.Layer)|mapping draw_button(mapping args, string text, object id)
67bff71999-11-15Jonas Wallden {
fde9082000-02-08Per Hedbor  Image.Image text_img;
67bff71999-11-15Jonas Wallden  mapping icon;
fde9082000-02-08Per Hedbor  Image.Layer background; Image.Layer frame; Image.Layer mask;
6cf7e52000-08-07Jonas Wallden  int left, right, top, middle, bottom; /* offsets */
2bf4842000-12-21Per Hedbor  int req_width, noframe;
7169752000-02-02Per Hedbor 
3d3a472000-02-08Per Hedbor  mapping ll = ([]);
13b35c2003-07-04Jonas Wallden  // Photoshop layers: don't let individual layers expand the image // beyond the bounds of the overall image. mapping opts = ([ "crop_to_bounds" : 1 ]);
fff48a2000-02-21Per Hedbor  void set_image( array layers )
e76d2c2000-02-03Per Hedbor  {
fff48a2000-02-21Per Hedbor  foreach( layers||({}), object l )
fde9082000-02-08Per Hedbor  {
e2a3de2000-10-17Per Hedbor  if(!l->get_misc_value( "name" ) ) // Hm. Probably PSD
fff48a2000-02-21Per Hedbor  continue;
e2a3de2000-10-17Per Hedbor 
fff48a2000-02-21Per Hedbor  ll[lower_case(l->get_misc_value( "name" ))] = l; switch( lower_case(l->get_misc_value( "name" )) )
fde9082000-02-08Per Hedbor  { case "background": background = l; break; case "frame": frame = l; break; case "mask": mask = l; break; } }
fff48a2000-02-21Per Hedbor  };
5dac1c2000-01-10Martin Nilsson 
fff48a2000-02-21Per Hedbor  if( args->border_image )
6a613a2002-06-17Anders Johansson  {
233ab52003-06-30Jonas Wallden  array(Image.Layer)|mapping tmp;
1171622013-11-26Anders Johansson #if constant(Sitebuilder) && constant(Sitebuilder.sb_start_use_imagecache)
233ab52003-06-30Jonas Wallden  // Let SiteBuilder get a chance to decode its argument data if (Sitebuilder.sb_start_use_imagecache) { Sitebuilder.sb_start_use_imagecache(args, id);
13b35c2003-07-04Jonas Wallden  tmp = roxen.load_layers(args->border_image, id, opts);
233ab52003-06-30Jonas Wallden  Sitebuilder.sb_end_use_imagecache(args, id); } else #endif {
13b35c2003-07-04Jonas Wallden  tmp = roxen.load_layers(args->border_image, id, opts);
233ab52003-06-30Jonas Wallden  }
62b0912006-01-25Jonas Wallden  if (mappingp(tmp)) { if (tmp->error != 401)
78b9122012-06-21Martin Stjernholm  RXML.parse_error("Failed to load frame image: %O (error: %O)\n", args->border_image, tmp->error);
62b0912006-01-25Jonas Wallden  return tmp; }
6a613a2002-06-17Anders Johansson  set_image( tmp ); }
fff48a2000-02-21Per Hedbor 
2bf4842000-12-21Per Hedbor 
fff48a2000-02-21Per Hedbor  // otherwise load default images
2bf4842000-12-21Per Hedbor  if ( !frame && !background && !mask )
e2a3de2000-10-17Per Hedbor  {
76dcb72000-10-18Martin Stjernholm  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));
e2a3de2000-10-17Per Hedbor  }; if( !frame )
76dcb72000-10-18Martin Stjernholm  if (err) { catch (err[0] = "Failed to decode default frame image " "(roxen-images/gbutton.xcf): " + err[0]); throw (err); } else error("Failed to decode default frame image " "(roxen-images/gbutton.xcf).\n");
e2a3de2000-10-17Per Hedbor  }
fff48a2000-02-21Per Hedbor 
2bf4842000-12-21Per Hedbor  if( !frame ) { noframe = 1; frame = background || mask; // for sizes offsets et.al. }
fff48a2000-02-21Per Hedbor  // Translate frame image to 0,0 (left layers are most likely to the // left of the frame image) int x0 = frame->xoffset(); int y0 = frame->yoffset(); if( x0 || y0 ) foreach( values( ll ), object l ) { int x = l->xoffset(); int y = l->yoffset(); l->set_offset( x-x0, y-y0 ); }
7169752000-02-02Per Hedbor 
fde9082000-02-08Per Hedbor  if( !mask ) mask = frame;
e76d2c2000-02-03Per Hedbor 
fde9082000-02-08Per Hedbor  array x = ({}); array y = ({});
2bf4842000-12-21Per Hedbor 
fde9082000-02-08Per Hedbor  foreach( frame->get_misc_value( "image_guides" ), object g )
2bf4842000-12-21Per Hedbor  if( g->vertical ) x += ({ g->pos - x0 }); else y += ({ g->pos - y0 });
fff48a2000-02-21Per Hedbor  sort( y ); sort( x );
e76d2c2000-02-03Per Hedbor 
fde9082000-02-08Per Hedbor  if(sizeof( x ) < 2) x = ({ 5, frame->xsize()-5 });
fff48a2000-02-21Per Hedbor 
fde9082000-02-08Per Hedbor  if(sizeof( y ) < 2) y = ({ 2, frame->ysize()-2 });
e76d2c2000-02-03Per Hedbor 
6cf7e52000-08-07Jonas Wallden  left = x[0]; right = x[-1]; top = y[0]; middle = y[1]; bottom = y[-1];
fde9082000-02-08Per Hedbor  right = frame->xsize()-right;
6cf7e52000-08-07Jonas Wallden  // Text height depends on which guides we should align to int text_height; switch (args->icva) { case "above": text_height = bottom - middle; break; case "below": text_height = middle - top; break; default: case "middle": text_height = bottom - top; break; }
7169752000-02-02Per Hedbor 
67bff71999-11-15Jonas Wallden  // Get icon
2576ea2004-02-16Jonas Wallden  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)
67bff71999-11-15Jonas Wallden  icon = roxen.low_decode_image(args->icd);
2576ea2004-02-16Jonas Wallden 
fde9082000-02-08Per Hedbor  int i_width = icon && icon->img->xsize();
6cf7e52000-08-07Jonas Wallden  int i_height = icon && icon->img->ysize();
4e63f12000-05-24Jonas Wallden  int i_spc = i_width && sizeof(text) && 5;
7169752000-02-02Per Hedbor 
67bff71999-11-15Jonas Wallden  // Generate text
fde9082000-02-08Per Hedbor  if (sizeof(text)) {
c19bfd2003-12-18Jonas Wallden  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);
300d982000-12-11Per Hedbor  text_img = button_font->write(text);
c19bfd2003-12-18Jonas Wallden  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);
300d982000-12-11Per Hedbor  // fonts that can not be scaled.
e59f912000-12-11Per Hedbor  if( abs(text_img->ysize() - text_height)>2 )
1597b42000-12-11Martin Nilsson  text_img = text_img->scale(0, text_height );
300d982000-12-11Per Hedbor  else { int o = text_img->ysize() - text_height; top -= o; middle -= o/2; }
3c24b62000-01-19Fredrik Noring  if (args->cnd) text_img = text_img->scale((int) round(text_img->xsize() * 0.8), text_img->ysize());
f537202002-04-15Jonas Wallden  } else text_height = 0;
7169752000-02-02Per Hedbor 
fde9082000-02-08Per Hedbor  int t_width = text_img && text_img->xsize();
6cf7e52000-08-07Jonas Wallden  // 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;
67bff71999-11-15Jonas Wallden  if (args->wi && (req_width < args->wi)) req_width = args->wi;
fde9082000-02-08Per Hedbor 
6cf7e52000-08-07Jonas Wallden  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;
f537202002-04-15Jonas Wallden  if (args->icva == "above" || !text_height) {
6cf7e52000-08-07Jonas Wallden  txt_y = middle;
f537202002-04-15Jonas Wallden  icn_y = top + ((text_height ? middle : bottom) - top - i_height) / 2;
6cf7e52000-08-07Jonas Wallden  } else { txt_y = top; icn_y = middle + (bottom - middle - i_height) / 2;
67bff71999-11-15Jonas Wallden  } break; default: case "middle":
6cf7e52000-08-07Jonas Wallden  // Center icon vertically on same line as text icn_y = icon && (frame->ysize() - icon->img->ysize()) / 2; txt_y = top; switch (args->al)
fde9082000-02-08Per Hedbor  {
6cf7e52000-08-07Jonas Wallden  case "left": // Allow icon alignment: left, right switch (args->ica) { case "left": icn_x = left; txt_x = icn_x + i_width + i_spc; break; default: case "right": txt_x = left; icn_x = req_width - right - i_width; break; }
67bff71999-11-15Jonas Wallden  break;
6cf7e52000-08-07Jonas Wallden  default: case "center": case "middle": // Allow icon alignment: // left, center, center-before, center-after, right switch (args->ica) { case "left": icn_x = left; txt_x = (req_width - right - left - i_width - i_spc - t_width) / 2; txt_x += icn_x + i_width + i_spc; break; default: case "center": case "center_before": case "center-before": 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;
6f02072002-09-05Jonas Wallden  txt_x = left + (icn_x - i_spc - t_width - left) / 2;
6cf7e52000-08-07Jonas Wallden  break; } break; case "right": // Allow icon alignment: left, right switch (args->ica) { default: case "left": icn_x = left; txt_x = req_width - right - t_width; break; case "right": icn_x = req_width - right - i_width; txt_x = icn_x - i_spc - t_width; break; } break;
67bff71999-11-15Jonas Wallden  } break; }
f6ea612000-02-07Per Hedbor 
5321c02000-02-08Per Hedbor  if( args->extra_frame_layers ) {
8a98622000-04-04Per Hedbor  array l = ({ });
5321c02000-02-08Per Hedbor  foreach( args->extra_frame_layers/",", string q ) l += ({ ll[q] }); l-=({ 0 });
8a98622000-04-04Per Hedbor  if( sizeof( l ) )
2bf4842000-12-21Per Hedbor  frame = Image.lay( l+(noframe?({}):({frame})) );
5321c02000-02-08Per Hedbor  }
f6ea612000-02-07Per Hedbor 
5321c02000-02-08Per Hedbor  if( args->extra_mask_layers ) { array l = ({ }); foreach( args->extra_mask_layers/",", string q ) l += ({ ll[q] }); l-=({ 0 }); if( sizeof( l ) )
fff48a2000-02-21Per Hedbor  { if( mask ) l = ({ mask })+l;
5321c02000-02-08Per Hedbor  mask = Image.lay( l );
fff48a2000-02-21Per Hedbor  }
5321c02000-02-08Per Hedbor  } right = frame->xsize()-right; if (mask != frame)
e95d462000-12-16Per Hedbor  { Image.Image i = mask->image(); Image.Image m = mask->alpha(); int x0 = -mask->xoffset(); int y0 = -mask->yoffset(); int x1 = frame->xsize()-1+x0; int y1 = frame->ysize()-1+y0; i = i->copy(x0,y0, x1,y1); if( m ) m = m->copy(x0,y0, x1,y1); mask->set_image( i, m );
5321c02000-02-08Per Hedbor  mask = stretch_layer( mask, left, right, req_width );
e95d462000-12-16Per Hedbor  }
2bf4842000-12-21Per Hedbor  if( frame != background ) frame = stretch_layer( frame, left, right, req_width );
e0fda72000-04-04Marcus Comstedt  array(Image.Layer) button_layers = ({ Image.Layer( Image.Image(req_width, frame->ysize(), args->bg),
e95d462000-12-16Per Hedbor  mask->alpha()->copy(0,0,req_width-1,frame->ysize()-1)),
e0fda72000-04-04Marcus Comstedt  });
8a98622000-04-04Per Hedbor  if( args->extra_background_layers || background)
fde9082000-02-08Per Hedbor  {
8a98622000-04-04Per Hedbor  array l = ({ background }); foreach( (args->extra_background_layers||"")/","-({""}), string q ) l += ({ ll[q] }); l-=({ 0 }); foreach( l, object ll ) { if( args->dim ) ll->set_alpha_value( 0.3 ); button_layers += ({ stretch_layer( ll, left, right, req_width ) }); }
fde9082000-02-08Per Hedbor  }
12493c2000-02-22Per Hedbor 
2bf4842000-12-21Per Hedbor  if( !noframe ) { button_layers += ({ frame }); frame->set_mode( "value" ); }
12493c2000-02-22Per Hedbor  if( args->dim ) {
1ac32d2000-02-10Jonas Wallden  // Adjust dimmed border intensity to the background int bg_value = Image.Color(@args->bg)->hsv()[2]; int dim_high, dim_low; if (bg_value < 128) { dim_low = max(bg_value - 64, 0); dim_high = dim_low + 128; } else { dim_high = min(bg_value + 64, 255); dim_low = dim_high - 128; }
12493c2000-02-22Per Hedbor  frame->set_image(frame->image()-> modify_by_intensity( 1, 1, 1, ({ dim_low, dim_low, dim_low }), ({ dim_high, dim_high, dim_high })), frame->alpha());
1ac32d2000-02-10Jonas Wallden  }
1a54422000-02-21Per Hedbor 
fde9082000-02-08Per Hedbor  // Draw icon. if (icon)
1a54422000-02-21Per Hedbor  button_layers += ({ Image.Layer( ([
12493c2000-02-22Per Hedbor  "alpha_value":(args->dim ? 0.3 : 1.0),
1a54422000-02-21Per Hedbor  "image":icon->img,
12493c2000-02-22Per Hedbor  "alpha":icon->alpha,
1a54422000-02-21Per Hedbor  "xoffset":icn_x,
6cf7e52000-08-07Jonas Wallden  "yoffset":icn_y
1a54422000-02-21Per Hedbor  ]) )});
f6ea612000-02-07Per Hedbor 
67bff71999-11-15Jonas Wallden  // Draw text
3c24b62000-01-19Fredrik Noring  if(text_img)
2bf4842000-12-21Per Hedbor  { float ta = args->txtalpha?args->txtalpha:1.0; button_layers += ({ Image.Layer(([ "mode":args->txtmode, "image":text_img->color(0,0,0)->invert()->color(@args->txt), "alpha":(text_img*(args->dim?0.5*ta:ta)), "xoffset":txt_x, "yoffset":txt_y, ])) }); }
7169752000-02-02Per Hedbor 
fff48a2000-02-21Per Hedbor  // 'plain' extra layers are added on top of everything else
5321c02000-02-08Per Hedbor  if( args->extra_layers ) {
1a54422000-02-21Per Hedbor  array q = map(args->extra_layers/",", lambda(string q) { return ll[q]; } )-({0});
8a98622000-04-04Per Hedbor  foreach( q, object ll ) { if( args->dim ) ll->set_alpha_value( 0.3 ); button_layers += ({stretch_layer(ll,left,right,req_width)});
e95d462000-12-16Per Hedbor  button_layers[-1]->set_offset( 0, button_layers[0]->ysize()- button_layers[-1]->ysize() );
8a98622000-04-04Per Hedbor  }
5321c02000-02-08Per Hedbor  }
1a54422000-02-21Per Hedbor  button_layers -= ({ 0 });
fff48a2000-02-21Per Hedbor  // left layers are added to the left of the image, and the mask is // extended using their mask. There is no corresponding 'mask' layers // for these, but that is not a problem most of the time. if( args->extra_left_layers ) { array l = ({ }); foreach( args->extra_left_layers/",", string q ) l += ({ ll[q] }); l-=({ 0 });
e95d462000-12-16Per Hedbor  l->set_offset( 0, 0 );
fff48a2000-02-21Per Hedbor  if( sizeof( l ) ) { object q = Image.lay( l );
1a54422000-02-21Per Hedbor  foreach( button_layers, object b ) { int x = b->xoffset(); int y = b->yoffset(); b->set_offset( x+q->xsize(), y ); }
e95d462000-12-16Per Hedbor  q->set_offset( 0, button_layers[0]->ysize()-q->ysize() );
1a54422000-02-21Per Hedbor  button_layers += ({ q });
fff48a2000-02-21Per Hedbor  } } // right layers are added to the right of the image, and the mask is // extended using their mask. There is no corresponding 'mask' layers // for these, but that is not a problem most of the time. if( args->extra_right_layers ) { array l = ({ }); foreach( args->extra_right_layers/",", string q ) l += ({ ll[q] }); l-=({ 0 });
e95d462000-12-16Per Hedbor  l->set_offset( 0, 0 );
fff48a2000-02-21Per Hedbor  if( sizeof( l ) ) { object q = Image.lay( l );
a76b0a2000-02-22Per Hedbor  q->set_offset( button_layers[0]->xsize()+
e95d462000-12-16Per Hedbor  button_layers[0]->xoffset(), button_layers[0]->ysize()-q->ysize());
1a54422000-02-21Per Hedbor  button_layers += ({ q });
fff48a2000-02-21Per Hedbor  } }
5321c02000-02-08Per Hedbor 
fcb9512000-09-19Per Hedbor // if( !equal( args->pagebg, args->bg ) ) // { // FIXME: fix transparency (somewhat) // this version totally destroys the alpha channel of the image, // but that's sort of the intention. The reason is that // the png images are generated without alpha.
d123332000-11-17Anders Johansson  if (args->format == "png") return ({ Image.Layer(([ "fill":args->pagebg, ])) }) + button_layers; else return button_layers;
fcb9512000-09-19Per Hedbor // }
67bff71999-11-15Jonas Wallden } mapping find_internal(string f, RequestID id) {
4afee22001-04-03Per Hedbor  // It's not enough to // 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 );
67bff71999-11-15Jonas Wallden }
350dfa2001-02-02Per Hedbor int get_file_stat( string f, RequestID id ) {
4805d92001-08-03Jonas Wallden  int res;
b3b0952003-09-22Jonas Wallden  mapping stat_cache;
500e392001-08-23Jonas Wallden  // -1 is used to cache negative results. When SiteBuilder crawler runs // we must let the stat_file() run unconditionally to register // dependencies properly.
b3b0952003-09-22Jonas Wallden  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 = ([ ]);
500e392001-08-23Jonas Wallden 
2483e52002-08-28Anders Johansson  int was_internal = id->misc->internal_get; id->misc->internal_get = 1;
b3b0952003-09-22Jonas Wallden  res = stat_cache[ f ] = (id->conf->stat_file( f,id ) || ({ 0,0,0,0 }) )[ST_MTIME] || -1;
2483e52002-08-28Anders Johansson  if (!was_internal) m_delete(id->misc, "internal_get");
4805d92001-08-03Jonas Wallden  return (res > 0) && res;
350dfa2001-02-02Per Hedbor }
67bff71999-11-15Jonas Wallden 
c421c62000-03-14Martin Nilsson class ButtonFrame { inherit RXML.Frame;
f29b142000-03-24Per Hedbor  array mk_url(RequestID id) {
350dfa2001-02-02Per Hedbor // int t = gethrtime();
6cf7e52000-08-07Jonas Wallden  string fi = (args["frame-image"] || id->misc->defines["gbutton-frame-image"]);
ed9f2a2011-01-21Jonas Wallden  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.");
bf956c2000-05-01Martin Nilsson  fi = Roxen.fix_relative( fi, id );
ed9f2a2011-01-21Jonas Wallden  }
ca42992004-05-20Jonas Wallden  m_delete(args, "frame-image");
dc73752000-05-25Jonas Wallden  // Harmonize some attribute names to RXML standards...
350dfa2001-02-02Per Hedbor  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;
6cf7e52000-08-07Jonas Wallden  args->valign_icon = args["valign-icon"] || args->valign_icon;
dc73752000-05-25Jonas Wallden  m_delete(args, "icon-src"); m_delete(args, "icon-data"); m_delete(args, "align-icon");
ed9f2a2011-01-21Jonas Wallden  if (args->icon_src == "" && compat_level >= 5.2) RXML.parse_error("Empty icon-src attribute not allowed.");
dc73752000-05-25Jonas Wallden 
350dfa2001-02-02Per Hedbor  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 ||
c421c62000-03-14Martin Nilsson  id->misc->defines->theme_bgcolor || id->misc->defines->bgcolor ||
350dfa2001-02-02Per Hedbor  "#eeeeee"), // Background color "txt" : parse_color(args->textcolor || id->misc->defines->theme_bgcolor || id->misc->defines->fgcolor || "#000000"), // Text color "txtalpha": (args->textalpha?(float)args->textalpha:1.0), "txtmode": (args->textmode||"normal"), "cnd" : (args->condensed || // Condensed text (lower_case(args->textstyle || "") == "condensed")), "wi" : (int) args->width, // Min button width "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")),
f850ff2001-07-09Martin Nilsson  "fontkey": roxen->fonts->verify_font(args->font||id->misc->defines->font),
350dfa2001-02-02Per Hedbor  "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"], ]);
ca42992004-05-20Jonas Wallden  // 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");
ce4deb2009-11-26Henrik Grubbström (Grubba)  int timeout = Roxen.timeout_dequantifier(args);
9363fa2009-11-24Henrik Grubbström (Grubba) 
233ab52003-06-30Jonas Wallden  if( fi ) {
350dfa2001-02-02Per Hedbor  new_args->stat = get_file_stat( fi, id );
1171622013-11-26Anders Johansson #if constant(Sitebuilder) && constant(Sitebuilder.sb_prepare_imagecache)
233ab52003-06-30Jonas Wallden  // 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 }
e4b4b72000-11-21Per Hedbor 
ef3b2b2009-04-29Jonas Wallden  if (string icn_path = new_args->icn) { new_args->stat_icn = get_file_stat(icn_path, id);
1171622013-11-26Anders Johansson #if constant(Sitebuilder) && constant(Sitebuilder.sb_prepare_imagecache)
ef3b2b2009-04-29Jonas Wallden  if (Sitebuilder.sb_prepare_imagecache) new_args = Sitebuilder.sb_prepare_imagecache(new_args, icn_path, id); #endif }
c421c62000-03-14Martin Nilsson  new_args->quant = args->quant || 128; foreach(glob("*-*", indices(args)), string n) new_args[n] = args[n];
10494b2008-05-22Martin Stjernholm  //string fn;
350dfa2001-02-02Per Hedbor  // if( new_args->stat && (fn = id->conf->real_file( fi, id ) ) ) // Roxen.add_cache_stat_callback( id, fn, new_args->stat ); // werror("mkurl took %dµs\n", gethrtime()-t ); // t = gethrtime();
c421c62000-03-14Martin Nilsson  string img_src =
db053e2000-12-05Martin Nilsson  query_absolute_internal_location(id) +
9363fa2009-11-24Henrik Grubbström (Grubba)  button_cache->store( ({ new_args, (string)content }), id, timeout);
9d86b52001-03-30Johan Sundström 
4afee22001-04-03Per Hedbor  if(do_ext)
9d86b52001-03-30Johan Sundström  img_src += "." + (new_args->format || "gif");
350dfa2001-02-02Per Hedbor // werror("argcache->store took %dµs\n", gethrtime()-t );
91d0172009-12-01Henrik Grubbström (Grubba)  return ({ img_src, new_args, timeout });
c421c62000-03-14Martin Nilsson  } } class TagGButtonURL { inherit RXML.Tag; constant name = "gbutton-url";
1b2d742001-10-08Anders Johansson  constant flags = RXML.FLAG_DONT_REPORT_ERRORS;
c421c62000-03-14Martin Nilsson  RXML.Type content_type = RXML.t_text(RXML.PXml); class Frame { inherit ButtonFrame; array do_return(RequestID id) { result=mk_url(id)[0]; return 0; }
67bff71999-11-15Jonas Wallden  }
c421c62000-03-14Martin Nilsson }
5dac1c2000-01-10Martin Nilsson 
25d1762000-08-12Martin Stjernholm class TagGButton {
c421c62000-03-14Martin Nilsson  inherit RXML.Tag; constant name = "gbutton";
1b2d742001-10-08Anders Johansson  constant flags = RXML.FLAG_DONT_REPORT_ERRORS;
c421c62000-03-14Martin Nilsson  RXML.Type content_type = RXML.t_text(RXML.PXml); class Frame { inherit ButtonFrame; array do_return(RequestID id) {
dc73752000-05-25Jonas Wallden  // Peek at img-align and remove it so it won't be copied by "*-*" glob // in mk_url(). string img_align = args["img-align"];
8ac5122003-12-15Jonas Wallden  string title = args->title;
dc73752000-05-25Jonas Wallden  m_delete(args, "img-align");
91d0172009-12-01Henrik Grubbström (Grubba)  [string img_src, mapping new_args, int timeout]=mk_url(id);
c421c62000-03-14Martin Nilsson  mapping img_attrs = ([ "src" : img_src,
5c323e2001-10-09Martin Nilsson  "alt" : args->alt || (string)content,
c421c62000-03-14Martin Nilsson  "border" : args->border, "hspace" : args->hspace, "vspace" : args->vspace ]);
dc73752000-05-25Jonas Wallden  if (img_align) img_attrs->align = img_align;
8ac5122003-12-15Jonas Wallden  if (title) img_attrs->title = title;
cf5b112003-10-16Anders Johansson  int no_draw = !id->misc->generate_images;
5c323e2001-10-09Martin Nilsson  if (mapping size = button_cache->metadata( ({ new_args, (string)content }),
91d0172009-12-01Henrik Grubbström (Grubba)  id, no_draw, timeout)) {
cf5b112003-10-16Anders Johansson  // Image in cache (no_draw above prevents generation on-the-fly, i.e.
c421c62000-03-14Martin Nilsson  // first image will lack sizes). img_attrs->width = size->xsize; img_attrs->height = size->ysize; }
0023a62000-08-22Martin Nilsson  result = Roxen.make_tag("img", img_attrs, !args->noxml);
dbc2722000-03-24Johan Sundström 
c421c62000-03-14Martin Nilsson  // Make button clickable if not dimmed
dbc2722000-03-24Johan Sundström  if(args->href && !new_args->dim) {
2c20012012-02-14Anders Johansson  mapping a_attrs = ([ "href" : args->href ]);
dbc2722000-03-24Johan Sundström  foreach(indices(args), string arg)
dbd1842005-10-01Jonas Wallden  if(has_value("/target/onmousedown/onmouseup/onclick/ondblclick/"
6cf7e52000-08-07Jonas Wallden  "onmouseout/onmouseover/onkeypress/onkeyup/"
6dcfc72005-10-24Anders Johansson  "onkeydown/style/class/id/accesskey/",
dbd1842005-10-01Jonas Wallden  "/" + lower_case(arg) + "/"))
dbc2722000-03-24Johan Sundström  a_attrs[arg] = args[arg];
bf956c2000-05-01Martin Nilsson  result = Roxen.make_container("a", a_attrs, result);
dbc2722000-03-24Johan Sundström  }
c421c62000-03-14Martin Nilsson  return 0; } }
67bff71999-11-15Jonas Wallden }