diff --git a/.indent.pro b/.indent.pro index b8c50265..f473baeb 100644 --- a/.indent.pro +++ b/.indent.pro @@ -47,6 +47,7 @@ -T lxw_author_id -T lxw_autofilter -T lxw_border +-T lxw_button_options -T lxw_cell -T lxw_chart -T lxw_chart_axis diff --git a/include/xlsxwriter/worksheet.h b/include/xlsxwriter/worksheet.h index 3f40081c..8a86e167 100644 --- a/include/xlsxwriter/worksheet.h +++ b/include/xlsxwriter/worksheet.h @@ -1538,7 +1538,6 @@ typedef struct lxw_table_options { */ uint8_t last_column; - /** * The `style_type` parameter can be used to set the style of the table, * in conjunction with the `style_type_number` parameter: @@ -1892,6 +1891,50 @@ typedef struct lxw_comment_options { } lxw_comment_options; +/** + * @brief Options for inserted buttons. + * + * Options for modifying buttons inserted via `worksheet_insert_button()`. + * + */ +typedef struct lxw_button_options { + + /** Sets the caption on the button. The default is "Button n" where n is + * the current number of buttons in the worksheet, including this + * button. */ + char *caption; + + /** Name of the macro to run when the button is pressed. The macro must be + * included with workbook_add_vba_project(). */ + char *macro; + + /** Optional description or "Alt text" for the button. This field can be + * used to provide a text description of the button to help + * accessibility. Set to NULL to ignore the description field. */ + char *description; + + /** This option is used to set the width of the cell button box + * explicitly in pixels. The default width is 64 pixels. */ + uint16_t width; + + /** This option is used to set the height of the cell button box + * explicitly in pixels. The default height is 20 pixels. */ + uint16_t height; + + /** X scale of the button as a decimal. */ + double x_scale; + + /** Y scale of the button as a decimal. */ + double y_scale; + + /** Offset from the left of the cell in pixels. */ + int32_t x_offset; + + /** Offset from the top of the cell in pixels. */ + int32_t y_offset; + +} lxw_button_options; + /* Internal structure for VML object options. */ typedef struct lxw_vml_obj { @@ -1920,6 +1963,7 @@ typedef struct lxw_vml_obj { char *text; char *image_position; char *name; + char *macro; STAILQ_ENTRY (lxw_vml_obj) list_pointers; } lxw_vml_obj; @@ -2078,6 +2122,7 @@ typedef struct lxw_worksheet { struct lxw_vml_drawing_rel_ids *vml_drawing_rel_ids; struct lxw_comment_objs *comment_objs; struct lxw_comment_objs *header_image_objs; + struct lxw_comment_objs *button_objs; struct lxw_table_objs *table_objs; uint16_t table_count; @@ -2146,6 +2191,7 @@ typedef struct lxw_worksheet { uint8_t num_validations; uint8_t has_dynamic_arrays; char *vba_codename; + uint16_t num_buttons; lxw_color_t tab_color; @@ -2200,6 +2246,7 @@ typedef struct lxw_worksheet { uint8_t has_comments; uint8_t has_header_vml; uint8_t has_background_image; + uint8_t has_buttons; lxw_rel_tuple *external_vml_comment_link; lxw_rel_tuple *external_comment_link; lxw_rel_tuple *external_vml_header_link; @@ -4259,6 +4306,11 @@ lxw_error worksheet_conditional_format_range(lxw_worksheet *worksheet, lxw_col_t last_col, lxw_conditional_format *conditional_format); + +lxw_error worksheet_insert_button(lxw_worksheet *worksheet, lxw_row_t row_num, + lxw_col_t col_num, + lxw_button_options *options); + /** * @brief Add an Excel table to a worksheet. * diff --git a/src/packager.c b/src/packager.c index f8f635c5..0b4a9fc2 100644 --- a/src/packager.c +++ b/src/packager.c @@ -564,6 +564,7 @@ _write_vml_files(lxw_packager *self) } vml->comment_objs = worksheet->comment_objs; + vml->button_objs = worksheet->button_objs; vml->vml_shape_id = worksheet->vml_shape_id; vml->comment_display_default = worksheet->comment_display_default; diff --git a/src/vml.c b/src/vml.c index 8ab696c8..7959a9c1 100644 --- a/src/vml.c +++ b/src/vml.c @@ -127,10 +127,9 @@ _vml_write_text_valign(lxw_vml *self) * Write the element. */ STATIC void -_vml_write_fmla_macro(lxw_vml *self) +_vml_write_fmla_macro(lxw_vml *self, lxw_vml_obj *vml_obj) { - lxw_xml_data_element(self->file, "x:FmlaMacro", "[0]!Button1_Click", - NULL); + lxw_xml_data_element(self->file, "x:FmlaMacro", vml_obj->macro, NULL); } /* @@ -182,11 +181,11 @@ _vml_write_rotation_lock(lxw_vml *self) * Write the element. */ STATIC void -_vml_write_column(lxw_vml *self, lxw_vml_obj *comment_obj) +_vml_write_column(lxw_vml *self, lxw_vml_obj *vml_obj) { char data[LXW_ATTR_32]; - lxw_snprintf(data, LXW_ATTR_32, "%d", comment_obj->col); + lxw_snprintf(data, LXW_ATTR_32, "%d", vml_obj->col); lxw_xml_data_element(self->file, "x:Column", data, NULL); } @@ -195,11 +194,11 @@ _vml_write_column(lxw_vml *self, lxw_vml_obj *comment_obj) * Write the element. */ STATIC void -_vml_write_row(lxw_vml *self, lxw_vml_obj *comment_obj) +_vml_write_row(lxw_vml *self, lxw_vml_obj *vml_obj) { char data[LXW_ATTR_32]; - lxw_snprintf(data, LXW_ATTR_32, "%d", comment_obj->row); + lxw_snprintf(data, LXW_ATTR_32, "%d", vml_obj->row); lxw_xml_data_element(self->file, "x:Row", data, NULL); } @@ -217,20 +216,20 @@ _vml_write_auto_fill(lxw_vml *self) * Write the element. */ STATIC void -_vml_write_anchor(lxw_vml *self, lxw_vml_obj *comment_obj) +_vml_write_anchor(lxw_vml *self, lxw_vml_obj *vml_obj) { char anchor_data[LXW_MAX_ATTRIBUTE_LENGTH]; lxw_snprintf(anchor_data, LXW_MAX_ATTRIBUTE_LENGTH, "%d, %d, %d, %d, %d, %d, %d, %d", - comment_obj->from.col, - (uint32_t) comment_obj->from.col_offset, - comment_obj->from.row, - (uint32_t) comment_obj->from.row_offset, - comment_obj->to.col, - (uint32_t) comment_obj->to.col_offset, - comment_obj->to.row, (uint32_t) comment_obj->to.row_offset); + vml_obj->from.col, + (uint32_t) vml_obj->from.col_offset, + vml_obj->from.row, + (uint32_t) vml_obj->from.row_offset, + vml_obj->to.col, + (uint32_t) vml_obj->to.col_offset, + vml_obj->to.row, (uint32_t) vml_obj->to.row_offset); lxw_xml_data_element(self->file, "x:Anchor", anchor_data, NULL); } @@ -311,7 +310,7 @@ _vml_write_shapetype_lock(lxw_vml *self) * Write the element. */ STATIC void -_vml_write_font(lxw_vml *self) +_vml_write_font(lxw_vml *self, lxw_vml_obj *vml_obj) { struct xml_attribute_list attributes; struct xml_attribute *attribute; @@ -321,7 +320,7 @@ _vml_write_font(lxw_vml *self) LXW_PUSH_ATTRIBUTES_STR("size", "220"); LXW_PUSH_ATTRIBUTES_STR("color", "#000000"); - lxw_xml_data_element(self->file, "font", "Button 1", &attributes); + lxw_xml_data_element(self->file, "font", vml_obj->name, &attributes); LXW_FREE_ATTRIBUTES(); } @@ -471,7 +470,7 @@ _vml_write_image_shapetype(lxw_vml *self) * Write the element. */ STATIC void -_vml_write_button_client_data(lxw_vml *self) +_vml_write_button_client_data(lxw_vml *self, lxw_vml_obj *vml_obj) { struct xml_attribute_list attributes; struct xml_attribute *attribute; @@ -482,7 +481,7 @@ _vml_write_button_client_data(lxw_vml *self) lxw_xml_start_tag(self->file, "x:ClientData", &attributes); /* Write the element. */ - _vml_write_anchor(self, NULL); + _vml_write_anchor(self, vml_obj); /* Write the x:PrintObject element. */ _vml_write_print_object(self); @@ -491,7 +490,7 @@ _vml_write_button_client_data(lxw_vml *self) _vml_write_auto_fill(self); /* Write the x:FmlaMacro element. */ - _vml_write_fmla_macro(self); + _vml_write_fmla_macro(self, vml_obj); /* Write the x:TextHAlign element. */ _vml_write_text_halign(self); @@ -508,7 +507,7 @@ _vml_write_button_client_data(lxw_vml *self) * Write the
element. */ STATIC void -_vml_write_button_div(lxw_vml *self) +_vml_write_button_div(lxw_vml *self, lxw_vml_obj *vml_obj) { struct xml_attribute_list attributes; struct xml_attribute *attribute; @@ -519,7 +518,7 @@ _vml_write_button_div(lxw_vml *self) lxw_xml_start_tag(self->file, "div", &attributes); /* Write the font element. */ - _vml_write_font(self); + _vml_write_font(self, vml_obj); lxw_xml_end_tag(self->file, "div"); @@ -530,7 +529,7 @@ _vml_write_button_div(lxw_vml *self) * Write the element. */ STATIC void -_vml_write_button_textbox(lxw_vml *self) +_vml_write_button_textbox(lxw_vml *self, lxw_vml_obj *vml_obj) { struct xml_attribute_list attributes; struct xml_attribute *attribute; @@ -542,7 +541,7 @@ _vml_write_button_textbox(lxw_vml *self) lxw_xml_start_tag(self->file, "v:textbox", &attributes); /* Write the div element. */ - _vml_write_button_div(self); + _vml_write_button_div(self, vml_obj); lxw_xml_end_tag(self->file, "v:textbox"); @@ -592,22 +591,49 @@ _vml_write_button_path(lxw_vml *self) * Write the element for buttons. */ STATIC void -_vml_write_button_shape(lxw_vml *self) +_vml_write_button_shape(lxw_vml *self, uint32_t vml_shape_id, + uint32_t z_index, lxw_vml_obj *vml_obj) { struct xml_attribute_list attributes; struct xml_attribute *attribute; - char id[] = "_x0000_s1025"; char type[] = "#_x0000_t201"; - char style[] = "position:absolute;margin-left:96pt;margin-top:15pt;" - "width:48pt;height:15pt;z-index:1;mso-wrap-style:tight"; char o_button[] = "t"; char fillcolor[] = "buttonFace [67]"; char strokecolor[] = "windowText [64]"; char o_insetmode[] = "auto"; + char id[LXW_ATTR_32]; + char margin_left[LXW_ATTR_32]; + char margin_top[LXW_ATTR_32]; + char width[LXW_ATTR_32]; + char height[LXW_ATTR_32]; + char style[LXW_MAX_ATTRIBUTE_LENGTH]; + + lxw_sprintf_dbl(margin_left, vml_obj->col_absolute * 0.75); + lxw_sprintf_dbl(margin_top, vml_obj->row_absolute * 0.75); + lxw_sprintf_dbl(width, vml_obj->width * 0.75); + lxw_sprintf_dbl(height, vml_obj->height * 0.75); + + lxw_snprintf(id, LXW_ATTR_32, "_x0000_s%d", vml_shape_id); + + lxw_snprintf(style, + LXW_MAX_ATTRIBUTE_LENGTH, + "position:absolute;" + "margin-left:%spt;" + "margin-top:%spt;" + "width:%spt;" + "height:%spt;" + "z-index:%d;" + "mso-wrap-style:tight", + margin_left, margin_top, width, height, z_index); + LXW_INIT_ATTRIBUTES(); LXW_PUSH_ATTRIBUTES_STR("id", id); LXW_PUSH_ATTRIBUTES_STR("type", type); + + if (vml_obj->text) + LXW_PUSH_ATTRIBUTES_STR("alt", vml_obj->text); + LXW_PUSH_ATTRIBUTES_STR("style", style); LXW_PUSH_ATTRIBUTES_STR("o:button", o_button); LXW_PUSH_ATTRIBUTES_STR("fillcolor", fillcolor); @@ -623,10 +649,10 @@ _vml_write_button_shape(lxw_vml *self) _vml_write_rotation_lock(self); /* Write the v:textbox element. */ - _vml_write_button_textbox(self); + _vml_write_button_textbox(self, vml_obj); /* Write the x:ClientData element. */ - _vml_write_button_client_data(self); + _vml_write_button_client_data(self, vml_obj); lxw_xml_end_tag(self->file, "v:shape"); @@ -672,7 +698,7 @@ _vml_write_button_shapetype(lxw_vml *self) * Write the element. */ STATIC void -_vml_write_comment_client_data(lxw_vml *self, lxw_vml_obj *comment_obj) +_vml_write_comment_client_data(lxw_vml *self, lxw_vml_obj *vml_obj) { struct xml_attribute_list attributes; struct xml_attribute *attribute; @@ -689,19 +715,19 @@ _vml_write_comment_client_data(lxw_vml *self, lxw_vml_obj *comment_obj) _vml_write_size_with_cells(self); /* Write the element. */ - _vml_write_anchor(self, comment_obj); + _vml_write_anchor(self, vml_obj); /* Write the element. */ _vml_write_auto_fill(self); /* Write the element. */ - _vml_write_row(self, comment_obj); + _vml_write_row(self, vml_obj); /* Write the element. */ - _vml_write_column(self, comment_obj); + _vml_write_column(self, vml_obj); /* Write the x:Visible element. */ - if (comment_obj->visible == LXW_COMMENT_DISPLAY_VISIBLE) + if (vml_obj->visible == LXW_COMMENT_DISPLAY_VISIBLE) _vml_write_visible(self); lxw_xml_end_tag(self->file, "x:ClientData"); @@ -793,7 +819,7 @@ _vml_write_comment_path(lxw_vml *self, uint8_t has_gradient, char *type) */ STATIC void _vml_write_comment_shape(lxw_vml *self, uint32_t vml_shape_id, - uint32_t z_index, lxw_vml_obj *comment_obj) + uint32_t z_index, lxw_vml_obj *vml_obj) { struct xml_attribute_list attributes; struct xml_attribute *attribute; @@ -808,24 +834,24 @@ _vml_write_comment_shape(lxw_vml *self, uint32_t vml_shape_id, char type[] = "#_x0000_t202"; char o_insetmode[] = "auto"; - lxw_sprintf_dbl(margin_left, comment_obj->col_absolute * 0.75); - lxw_sprintf_dbl(margin_top, comment_obj->row_absolute * 0.75); - lxw_sprintf_dbl(width, comment_obj->width * 0.75); - lxw_sprintf_dbl(height, comment_obj->height * 0.75); + lxw_sprintf_dbl(margin_left, vml_obj->col_absolute * 0.75); + lxw_sprintf_dbl(margin_top, vml_obj->row_absolute * 0.75); + lxw_sprintf_dbl(width, vml_obj->width * 0.75); + lxw_sprintf_dbl(height, vml_obj->height * 0.75); lxw_snprintf(id, LXW_ATTR_32, "_x0000_s%d", vml_shape_id); - if (comment_obj->visible == LXW_COMMENT_DISPLAY_DEFAULT) - comment_obj->visible = self->comment_display_default; + if (vml_obj->visible == LXW_COMMENT_DISPLAY_DEFAULT) + vml_obj->visible = self->comment_display_default; - if (comment_obj->visible == LXW_COMMENT_DISPLAY_VISIBLE) + if (vml_obj->visible == LXW_COMMENT_DISPLAY_VISIBLE) lxw_snprintf(visible, LXW_ATTR_32, "visible"); else lxw_snprintf(visible, LXW_ATTR_32, "hidden"); - if (comment_obj->color) + if (vml_obj->color) lxw_snprintf(fillcolor, LXW_ATTR_32, "#%06x", - comment_obj->color & LXW_COLOR_MASK); + vml_obj->color & LXW_COLOR_MASK); else lxw_snprintf(fillcolor, LXW_ATTR_32, "#%06x", 0xffffe1); @@ -862,7 +888,7 @@ _vml_write_comment_shape(lxw_vml *self, uint32_t vml_shape_id, _vml_write_comment_textbox(self); /* Write the x:ClientData element. */ - _vml_write_comment_client_data(self, comment_obj); + _vml_write_comment_client_data(self, vml_obj); lxw_xml_end_tag(self->file, "v:shape"); @@ -981,7 +1007,22 @@ lxw_vml_assemble_xml_file(lxw_vml *self) /* Write the o:shapelayout element. */ _vml_write_shapelayout(self); - if (self->comment_objs) { + if (self->button_objs && !STAILQ_EMPTY(self->button_objs)) { + /* Write the element. */ + _vml_write_button_shapetype(self); + + STAILQ_FOREACH(button_obj, self->button_objs, list_pointers) { + self->vml_shape_id++; + + /* Write the element. */ + _vml_write_button_shape(self, self->vml_shape_id, z_index, + button_obj); + + z_index++; + } + } + + if (self->comment_objs && !STAILQ_EMPTY(self->comment_objs)) { /* Write the element. */ _vml_write_comment_shapetype(self); @@ -996,18 +1037,7 @@ lxw_vml_assemble_xml_file(lxw_vml *self) } } - if (self->button_objs) { - /* Write the element. */ - _vml_write_button_shapetype(self); - - STAILQ_FOREACH(button_obj, self->button_objs, list_pointers) { - /* Write the element. */ - _vml_write_button_shape(self); - - } - } - - if (self->image_objs) { + if (self->image_objs && !STAILQ_EMPTY(self->image_objs)) { /* Write the element. */ _vml_write_image_shapetype(self); diff --git a/src/worksheet.c b/src/worksheet.c index ae5c8920..f513b2d1 100644 --- a/src/worksheet.c +++ b/src/worksheet.c @@ -153,6 +153,10 @@ lxw_worksheet_new(lxw_worksheet_init_data *init_data) GOTO_LABEL_ON_MEM_ERROR(worksheet->header_image_objs, mem_error); STAILQ_INIT(worksheet->header_image_objs); + worksheet->button_objs = calloc(1, sizeof(struct lxw_comment_objs)); + GOTO_LABEL_ON_MEM_ERROR(worksheet->button_objs, mem_error); + STAILQ_INIT(worksheet->button_objs); + worksheet->selections = calloc(1, sizeof(struct lxw_selections)); GOTO_LABEL_ON_MEM_ERROR(worksheet->selections, mem_error); STAILQ_INIT(worksheet->selections); @@ -303,6 +307,7 @@ _free_vml_object(lxw_vml_obj *vml_obj) free(vml_obj->text); free(vml_obj->image_position); free(vml_obj->name); + free(vml_obj->macro); free(vml_obj); } @@ -515,7 +520,7 @@ lxw_worksheet_free(lxw_worksheet *worksheet) lxw_col_t col; lxw_merged_range *merged_range; lxw_object_properties *object_props; - lxw_vml_obj *header_image_vml; + lxw_vml_obj *vml_obj; lxw_selection *selection; lxw_data_val_obj *data_validation; lxw_rel_tuple *relationship; @@ -611,14 +616,24 @@ lxw_worksheet_free(lxw_worksheet *worksheet) if (worksheet->header_image_objs) { while (!STAILQ_EMPTY(worksheet->header_image_objs)) { - header_image_vml = STAILQ_FIRST(worksheet->header_image_objs); + vml_obj = STAILQ_FIRST(worksheet->header_image_objs); STAILQ_REMOVE_HEAD(worksheet->header_image_objs, list_pointers); - _free_vml_object(header_image_vml); + _free_vml_object(vml_obj); } free(worksheet->header_image_objs); } + if (worksheet->button_objs) { + while (!STAILQ_EMPTY(worksheet->button_objs)) { + vml_obj = STAILQ_FIRST(worksheet->button_objs); + STAILQ_REMOVE_HEAD(worksheet->button_objs, list_pointers); + _free_vml_object(vml_obj); + } + + free(worksheet->button_objs); + } + if (worksheet->selections) { while (!STAILQ_EMPTY(worksheet->selections)) { selection = STAILQ_FIRST(worksheet->selections); @@ -3107,35 +3122,131 @@ _get_comment_params(lxw_vml_obj *comment, lxw_comment_options *options) } /* - * Calculate the comment object position and vertices. + * This function handles the additional optional parameters to + * worksheet_insert_button() as well as calculating the button object + * position and vertices. + */ +lxw_error +_get_button_params(lxw_vml_obj *button, uint16_t button_number, + lxw_button_options *options) +{ + + int32_t x_offset = 0; + int32_t y_offset = 0; + uint32_t height = LXW_DEF_ROW_HEIGHT_PIXELS; + uint32_t width = LXW_DEF_COL_WIDTH_PIXELS; + double x_scale = 1.0; + double y_scale = 1.0; + lxw_row_t row = button->row; + lxw_col_t col = button->col; + char buffer[LXW_ATTR_32]; + uint8_t has_caption = LXW_FALSE; + uint8_t has_macro = LXW_FALSE; + size_t len; + + /* Set any user defined options. */ + if (options) { + + if (options->width > 0.0) + width = options->width; + + if (options->height > 0.0) + height = options->height; + + if (options->x_scale > 0.0) + x_scale = options->x_scale; + + if (options->y_scale > 0.0) + y_scale = options->y_scale; + + if (options->x_offset != 0) + x_offset = options->x_offset; + + if (options->y_offset != 0) + y_offset = options->y_offset; + + if (options->caption) { + button->name = lxw_strdup(options->caption); + RETURN_ON_MEM_ERROR(button->name, LXW_ERROR_MEMORY_MALLOC_FAILED); + has_caption = LXW_TRUE; + } + + if (options->macro) { + len = sizeof("[0]!") + strlen(options->macro); + button->macro = calloc(1, len); + RETURN_ON_MEM_ERROR(button->macro, + LXW_ERROR_MEMORY_MALLOC_FAILED); + + if (button->macro) + lxw_snprintf(button->macro, len, "[0]!%s", options->macro); + + has_macro = LXW_TRUE; + } + + if (options->description) { + button->text = lxw_strdup(options->description); + RETURN_ON_MEM_ERROR(button->text, LXW_ERROR_MEMORY_MALLOC_FAILED); + } + } + + if (!has_caption) { + lxw_snprintf(buffer, LXW_ATTR_32, "Button %d", button_number); + button->name = lxw_strdup(buffer); + RETURN_ON_MEM_ERROR(button->name, LXW_ERROR_MEMORY_MALLOC_FAILED); + } + + if (!has_macro) { + lxw_snprintf(buffer, LXW_ATTR_32, "[0]!Button%d_Click", + button_number); + button->macro = lxw_strdup(buffer); + RETURN_ON_MEM_ERROR(button->macro, LXW_ERROR_MEMORY_MALLOC_FAILED); + } + + /* Scale the width/height to the default/user scale and round to the + * nearest pixel. */ + width = (uint32_t) (0.5 + x_scale * width); + height = (uint32_t) (0.5 + y_scale * height); + + button->width = width; + button->height = height; + button->start_col = col; + button->start_row = row; + button->x_offset = x_offset; + button->y_offset = y_offset; + + return LXW_NO_ERROR; +} + +/* + * Calculate the vml_obj object position and vertices. */ void -_worksheet_position_vml_object(lxw_worksheet *self, lxw_vml_obj *comment) +_worksheet_position_vml_object(lxw_worksheet *self, lxw_vml_obj *vml_obj) { lxw_object_properties object_props; lxw_drawing_object drawing_object; - object_props.col = comment->start_col; - object_props.row = comment->start_row; - object_props.x_offset = comment->x_offset; - object_props.y_offset = comment->y_offset; - object_props.width = comment->width; - object_props.height = comment->height; + object_props.col = vml_obj->start_col; + object_props.row = vml_obj->start_row; + object_props.x_offset = vml_obj->x_offset; + object_props.y_offset = vml_obj->y_offset; + object_props.width = vml_obj->width; + object_props.height = vml_obj->height; drawing_object.anchor = LXW_OBJECT_DONT_MOVE_DONT_SIZE; _worksheet_position_object_pixels(self, &object_props, &drawing_object); - comment->from.col = drawing_object.from.col; - comment->from.row = drawing_object.from.row; - comment->from.col_offset = drawing_object.from.col_offset; - comment->from.row_offset = drawing_object.from.row_offset; - comment->to.col = drawing_object.to.col; - comment->to.row = drawing_object.to.row; - comment->to.col_offset = drawing_object.to.col_offset; - comment->to.row_offset = drawing_object.to.row_offset; - comment->col_absolute = drawing_object.col_absolute; - comment->row_absolute = drawing_object.row_absolute; + vml_obj->from.col = drawing_object.from.col; + vml_obj->from.row = drawing_object.from.row; + vml_obj->from.col_offset = drawing_object.from.col_offset; + vml_obj->from.row_offset = drawing_object.from.row_offset; + vml_obj->to.col = drawing_object.to.col; + vml_obj->to.row = drawing_object.to.row; + vml_obj->to.col_offset = drawing_object.to.col_offset; + vml_obj->to.row_offset = drawing_object.to.row_offset; + vml_obj->col_absolute = drawing_object.col_absolute; + vml_obj->row_absolute = drawing_object.row_absolute; } /* @@ -9129,10 +9240,10 @@ worksheet_add_table(lxw_worksheet *self, lxw_row_t first_row, lxw_table_column **columns; if (self->optimize) { - LXW_WARN_FORMAT("worksheet_add_table(): " - "worksheet tables aren't supported in " - "'constant_memory' mode"); - return LXW_ERROR_FEATURE_NOT_SUPPORTED; + LXW_WARN_FORMAT("worksheet_add_table(): " + "worksheet tables aren't supported in " + "'constant_memory' mode"); + return LXW_ERROR_FEATURE_NOT_SUPPORTED; } /* Swap last row/col with first row/col as necessary */ @@ -10991,6 +11102,49 @@ worksheet_conditional_format_cell(lxw_worksheet *self, row, col, options); } +/* + * Insert a button object into the worksheet. + */ +lxw_error +worksheet_insert_button(lxw_worksheet *self, lxw_row_t row_num, + lxw_col_t col_num, lxw_button_options *options) +{ + lxw_error err; + lxw_vml_obj *button; + + err = _check_dimensions(self, row_num, col_num, LXW_TRUE, LXW_TRUE); + if (err) + return err; + + button = calloc(1, sizeof(lxw_vml_obj)); + GOTO_LABEL_ON_MEM_ERROR(button, mem_error); + + button->row = row_num; + button->col = col_num; + + /* Set user and default parameters for the button. */ + err = _get_button_params(button, 1 + self->num_buttons, options); + if (err) + goto mem_error; + + /* Calculate the worksheet position of the button. */ + _worksheet_position_vml_object(self, button); + + self->has_vml = LXW_TRUE; + self->has_buttons = LXW_TRUE; + self->num_buttons++; + + STAILQ_INSERT_TAIL(self->button_objs, button, list_pointers); + + return LXW_NO_ERROR; + +mem_error: + if (button) + _free_vml_object(button); + + return LXW_ERROR_MEMORY_MALLOC_FAILED; +} + /* * Set the VBA name for the worksheet. */ diff --git a/test/functional/src/images/vbaProject02.bin b/test/functional/src/images/vbaProject02.bin new file mode 100644 index 00000000..61b760d6 Binary files /dev/null and b/test/functional/src/images/vbaProject02.bin differ diff --git a/test/functional/src/test_button01.c b/test/functional/src/test_button01.c new file mode 100644 index 00000000..5d9febf0 --- /dev/null +++ b/test/functional/src/test_button01.c @@ -0,0 +1,20 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_button01.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + worksheet_insert_button(worksheet, CELL("C2"), NULL); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_button02.c b/test/functional/src/test_button02.c new file mode 100644 index 00000000..2713b72f --- /dev/null +++ b/test/functional/src/test_button02.c @@ -0,0 +1,22 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_button02.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + lxw_button_options options = {.x_offset = 4, .y_offset = 3, .caption = "my text"}; + + worksheet_insert_button(worksheet, CELL("B4"), &options); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_button03.c b/test/functional/src/test_button03.c new file mode 100644 index 00000000..8ea75817 --- /dev/null +++ b/test/functional/src/test_button03.c @@ -0,0 +1,21 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_button03.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + worksheet_insert_button(worksheet, CELL("C2"), NULL); + worksheet_insert_button(worksheet, CELL("E5"), NULL); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_button04.c b/test/functional/src/test_button04.c new file mode 100644 index 00000000..0d36820f --- /dev/null +++ b/test/functional/src/test_button04.c @@ -0,0 +1,22 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_button04.xlsx"); + lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, NULL); + + worksheet_insert_button(worksheet1, CELL("C2"), NULL); + worksheet_insert_button(worksheet2, CELL("E5"), NULL); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_button05.c b/test/functional/src/test_button05.c new file mode 100644 index 00000000..a6bab4a4 --- /dev/null +++ b/test/functional/src/test_button05.c @@ -0,0 +1,22 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_button05.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + lxw_button_options options = {.x_scale = 2, .y_scale = 1.5, .macro = "my_macro"}; + + worksheet_insert_button(worksheet, CELL("C2"), &options); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_button06.c b/test/functional/src/test_button06.c new file mode 100644 index 00000000..b93f252c --- /dev/null +++ b/test/functional/src/test_button06.c @@ -0,0 +1,22 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_button06.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + lxw_button_options options = {.width = 128, .height = 30, .macro = "my_macro"}; + + worksheet_insert_button(worksheet, CELL("C2"), &options); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_button07.c b/test/functional/src/test_button07.c new file mode 100644 index 00000000..b09a70e5 --- /dev/null +++ b/test/functional/src/test_button07.c @@ -0,0 +1,24 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_button07.xlsm"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + lxw_button_options options = {.caption = "Hello", .macro = "say_hello"}; + + worksheet_insert_button(worksheet, CELL("C2"), &options); + + workbook_add_vba_project(workbook, "images/vbaProject02.bin"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_button08.c b/test/functional/src/test_button08.c new file mode 100644 index 00000000..78bbbd62 --- /dev/null +++ b/test/functional/src/test_button08.c @@ -0,0 +1,25 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_button08.xlsx"); + lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, NULL); + + worksheet_insert_button(worksheet1, CELL("C2"), NULL); + + worksheet_write_comment(worksheet2, CELL("A1"), "Foo"); + + worksheet_set_comments_author(worksheet2, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_button09.c b/test/functional/src/test_button09.c new file mode 100644 index 00000000..79301fac --- /dev/null +++ b/test/functional/src/test_button09.c @@ -0,0 +1,25 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_button09.xlsx"); + lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, NULL); + + worksheet_insert_button(worksheet2, CELL("C2"), NULL); + + worksheet_write_comment(worksheet1, CELL("A1"), "Foo"); + + worksheet_set_comments_author(worksheet1, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_button10.c b/test/functional/src/test_button10.c new file mode 100644 index 00000000..473b78f5 --- /dev/null +++ b/test/functional/src/test_button10.c @@ -0,0 +1,29 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_button10.xlsx"); + lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet3 = workbook_add_worksheet(workbook, NULL); + + worksheet_write_comment(worksheet1, CELL("A1"), "Some text"); + + worksheet_insert_button(worksheet2, CELL("B2"), NULL); + + worksheet_write_comment(worksheet3, CELL("C2"), "More text"); + + worksheet_set_comments_author(worksheet1, "John"); + worksheet_set_comments_author(worksheet3, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_button11.c b/test/functional/src/test_button11.c new file mode 100644 index 00000000..23952eaa --- /dev/null +++ b/test/functional/src/test_button11.c @@ -0,0 +1,29 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_button11.xlsx"); + lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet2 = workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet3 = workbook_add_worksheet(workbook, NULL); + + worksheet_insert_button(worksheet1, CELL("C2"), NULL); + + worksheet_write_comment(worksheet2, CELL("B2"), "Some text"); + + worksheet_write_comment(worksheet3, CELL("C3"), "More text"); + + worksheet_set_comments_author(worksheet2, "John"); + worksheet_set_comments_author(worksheet3, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_button12.c b/test/functional/src/test_button12.c new file mode 100644 index 00000000..0cef6047 --- /dev/null +++ b/test/functional/src/test_button12.c @@ -0,0 +1,28 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_button12.xlsx"); + lxw_worksheet *worksheet1 = workbook_add_worksheet(workbook, NULL); + workbook_add_worksheet(workbook, NULL); + lxw_worksheet *worksheet3 = workbook_add_worksheet(workbook, NULL); + + worksheet_write_comment(worksheet1, CELL("A1"), "Some text"); + worksheet_insert_button(worksheet1, CELL("C2"), NULL); + + worksheet_write_comment(worksheet3, CELL("C3"), "More text"); + + worksheet_set_comments_author(worksheet1, "John"); + worksheet_set_comments_author(worksheet3, "John"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_button13.c b/test/functional/src/test_button13.c new file mode 100644 index 00000000..d097e96f --- /dev/null +++ b/test/functional/src/test_button13.c @@ -0,0 +1,27 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_button13.xlsm"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + lxw_button_options options = {.caption = "Hello", .macro = "say_hello"}; + + workbook_set_vba_name(workbook, "ThisWorkbook"); + worksheet_set_vba_name(worksheet, "Sheet1"); + + worksheet_insert_button(worksheet, CELL("C2"), &options); + + workbook_add_vba_project(workbook, "images/vbaProject02.bin"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_button14.c b/test/functional/src/test_button14.c new file mode 100644 index 00000000..d5a6de11 --- /dev/null +++ b/test/functional/src/test_button14.c @@ -0,0 +1,28 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_button14.xlsm"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + lxw_button_options options = {.caption = "Hello", .macro = "say_hello"}; + + /* Implicit vba names. + workbook_set_vba_name(workbook, "ThisWorkbook"); + worksheet_set_vba_name(worksheet, "Sheet1"); + */ + worksheet_insert_button(worksheet, CELL("C2"), &options); + + workbook_add_vba_project(workbook, "images/vbaProject02.bin"); + + return workbook_close(workbook); +} diff --git a/test/functional/src/test_button15.c b/test/functional/src/test_button15.c new file mode 100644 index 00000000..20a28107 --- /dev/null +++ b/test/functional/src/test_button15.c @@ -0,0 +1,22 @@ +/***************************************************************************** + * Test cases for libxlsxwriter. + * + * Test to compare output against Excel files. + * + * Copyright 2014-2021, John McNamara, jmcnamara@cpan.org + * + */ + +#include "xlsxwriter.h" + +int main() { + + lxw_workbook *workbook = workbook_new("test_button15.xlsx"); + lxw_worksheet *worksheet = workbook_add_worksheet(workbook, NULL); + + lxw_button_options options = {.description = "Some alternative text"}; + + worksheet_insert_button(worksheet, CELL("C2"), &options); + + return workbook_close(workbook); +} diff --git a/test/functional/test_button.py b/test/functional/test_button.py new file mode 100644 index 00000000..8300e602 --- /dev/null +++ b/test/functional/test_button.py @@ -0,0 +1,59 @@ +############################################################################### +# +# Tests for libxlsxwriter. +# +# Copyright 2014-2021, John McNamara, jmcnamara@cpan.org +# + +import base_test_class + +class TestCompareXLSXFiles(base_test_class.XLSXBaseTest): + """ + Test file created with libxlsxwriter against a file created by Excel. + + """ + + def test_button01(self): + self.run_exe_test('test_button01') + + def test_button02(self): + self.run_exe_test('test_button02') + + def test_button03(self): + self.run_exe_test('test_button03') + + def test_button04(self): + self.run_exe_test('test_button04') + + def test_button05(self): + self.run_exe_test('test_button05') + + def test_button06(self): + self.run_exe_test('test_button06', 'button05.xlsx') + + def test_button07(self): + self.run_exe_test('test_button07', 'button07.xlsm') + + def test_button08(self): + self.run_exe_test('test_button08') + + def test_button09(self): + self.run_exe_test('test_button09') + + def test_button10(self): + self.run_exe_test('test_button10') + + def test_button11(self): + self.run_exe_test('test_button11') + + def test_button12(self): + self.run_exe_test('test_button12') + + def test_button13(self): + self.run_exe_test('test_button13', 'button07.xlsm') + + def test_button14(self): + self.run_exe_test('test_button14', 'button07.xlsm') + + def test_button15(self): + self.run_exe_test('test_button15') diff --git a/test/functional/xlsx_files/button01.xlsx b/test/functional/xlsx_files/button01.xlsx new file mode 100644 index 00000000..1712d53c Binary files /dev/null and b/test/functional/xlsx_files/button01.xlsx differ diff --git a/test/functional/xlsx_files/button02.xlsx b/test/functional/xlsx_files/button02.xlsx new file mode 100644 index 00000000..311f6897 Binary files /dev/null and b/test/functional/xlsx_files/button02.xlsx differ diff --git a/test/functional/xlsx_files/button03.xlsx b/test/functional/xlsx_files/button03.xlsx new file mode 100644 index 00000000..a26303a5 Binary files /dev/null and b/test/functional/xlsx_files/button03.xlsx differ diff --git a/test/functional/xlsx_files/button04.xlsx b/test/functional/xlsx_files/button04.xlsx new file mode 100644 index 00000000..8a6f458d Binary files /dev/null and b/test/functional/xlsx_files/button04.xlsx differ diff --git a/test/functional/xlsx_files/button05.xlsx b/test/functional/xlsx_files/button05.xlsx new file mode 100644 index 00000000..852d6ed8 Binary files /dev/null and b/test/functional/xlsx_files/button05.xlsx differ diff --git a/test/functional/xlsx_files/button07.xlsm b/test/functional/xlsx_files/button07.xlsm new file mode 100644 index 00000000..835fe455 Binary files /dev/null and b/test/functional/xlsx_files/button07.xlsm differ diff --git a/test/functional/xlsx_files/button08.xlsx b/test/functional/xlsx_files/button08.xlsx new file mode 100644 index 00000000..a8751074 Binary files /dev/null and b/test/functional/xlsx_files/button08.xlsx differ diff --git a/test/functional/xlsx_files/button09.xlsx b/test/functional/xlsx_files/button09.xlsx new file mode 100644 index 00000000..83ae73cb Binary files /dev/null and b/test/functional/xlsx_files/button09.xlsx differ diff --git a/test/functional/xlsx_files/button10.xlsx b/test/functional/xlsx_files/button10.xlsx new file mode 100644 index 00000000..dca6dcdf Binary files /dev/null and b/test/functional/xlsx_files/button10.xlsx differ diff --git a/test/functional/xlsx_files/button11.xlsx b/test/functional/xlsx_files/button11.xlsx new file mode 100644 index 00000000..4f5296c8 Binary files /dev/null and b/test/functional/xlsx_files/button11.xlsx differ diff --git a/test/functional/xlsx_files/button12.xlsx b/test/functional/xlsx_files/button12.xlsx new file mode 100644 index 00000000..6b9084a6 Binary files /dev/null and b/test/functional/xlsx_files/button12.xlsx differ diff --git a/test/functional/xlsx_files/button15.xlsx b/test/functional/xlsx_files/button15.xlsx new file mode 100644 index 00000000..d14ecec1 Binary files /dev/null and b/test/functional/xlsx_files/button15.xlsx differ