/* * cell.c: Cell management of the Gnumeric spreadsheet. * * Author: * Miguel de Icaza 1998 (miguel@kernel.org) */ #include #include #include #include "gnumeric.h" #include "gnumeric-sheet.h" #include "eval.h" #include "format.h" #include "color.h" #include "cursors.h" static int redraws_frozen = 0; static GHashTable *cell_hash_queue; void cell_formula_changed (Cell *cell) { g_return_if_fail (cell != NULL); sheet_cell_formula_link (cell); cell_queue_recalc (cell); } static inline void cell_modified (Cell *cell) { Sheet *sheet = cell->sheet; /* Cells from the clipboard do not have a sheet attached */ if (sheet) sheet->modified = TRUE; } void cell_set_formula (Cell *cell, char *text) { char *error_msg = NULL; char *desired_format = NULL; g_return_if_fail (cell != NULL); g_return_if_fail (text != NULL); cell_modified (cell); cell->parsed_node = expr_parse_string (&text [1], cell->sheet, cell->col->pos, cell->row->pos, &desired_format, &error_msg); if (cell->parsed_node == NULL){ cell->flags |= CELL_ERROR; cell_set_rendered_text (cell, error_msg); if (cell->value) value_release (cell->value); cell->value = NULL; return; } else { if (cell->flags & CELL_ERROR) cell->flags &= ~CELL_ERROR; } if (desired_format && strcmp (cell->style->format->format, "General") == 0){ style_format_unref (cell->style->format); cell->style->format = style_format_new (desired_format); } cell_formula_changed (cell); } /* * cell_set_alignment: * * @cell: the cell to change the alignment of * @halign: the horizontal alignemnt * @valign: the vertical alignemnt * @orient: the text orientation * * This routine changes the alignment of a cell to those specified. */ void cell_set_alignment (Cell *cell, int halign, int valign, int orient, int auto_return) { g_return_if_fail (cell != NULL); g_return_if_fail (cell->style != NULL); if ((cell->style->halign == halign) && (cell->style->valign == valign) && (cell->style->fit_in_cell == auto_return) && (cell->style->orientation == orient)) return; cell_modified (cell); cell_queue_redraw (cell); cell->style->halign = halign; cell->style->valign = valign; cell->style->orientation = orient; cell->style->fit_in_cell = auto_return; cell_calc_dimensions (cell); cell_queue_redraw (cell); } void cell_set_halign (Cell *cell, StyleHAlignFlags halign) { g_return_if_fail (cell != NULL); if (((unsigned int)cell->style->halign) == ((unsigned int) halign)) return; cell_modified (cell); cell_queue_redraw (cell); cell->style->halign = halign; cell_calc_dimensions (cell); cell_queue_redraw (cell); } void cell_set_font_from_style (Cell *cell, StyleFont *style_font) { g_return_if_fail (cell != NULL); g_return_if_fail (style_font != NULL); cell_modified (cell); cell_queue_redraw (cell); style_font_unref (cell->style->font); style_font_ref (style_font); cell->style->font = style_font; cell_calc_dimensions (cell); cell_queue_redraw (cell); } void cell_set_font (Cell *cell, char *font_name) { StyleFont *style_font; g_return_if_fail (cell != NULL); g_return_if_fail (font_name != NULL); style_font = style_font_new (font_name, 1); if (style_font) cell_set_font_from_style (cell, style_font); } void cell_set_style (Cell *cell, Style *reference_style) { g_return_if_fail (cell != NULL); g_return_if_fail (reference_style != NULL); cell_modified (cell); cell_queue_redraw (cell); style_destroy (cell->style); cell->style = style_duplicate (reference_style); if (cell->value) cell_render_value (cell); cell_calc_dimensions (cell); cell_queue_redraw (cell); } void cell_comment_destroy (Cell *cell) { CellComment *comment; GList *l; g_return_if_fail (cell != NULL); comment = cell->comment; if (!comment) return; cell->comment = NULL; /* Free resources */ string_unref (comment->comment); if (comment->timer_tag != -1) gtk_timeout_remove (comment->timer_tag); if (comment->window) gtk_object_destroy (GTK_OBJECT (comment->window)); for (l = comment->realized_list; l; l = l->next) gtk_object_destroy (l->data); g_free (comment); } static void cell_comment_cancel_timer (Cell *cell) { if (cell->comment->timer_tag != -1){ gtk_timeout_remove (cell->comment->timer_tag); cell->comment->timer_tag = -1; } } static void cell_display_comment (Cell *cell) { GtkWidget *window, *label; int x, y; g_return_if_fail (cell != NULL); cell_comment_cancel_timer (cell); window = gtk_window_new (GTK_WINDOW_POPUP); label = gtk_label_new (cell->comment->comment->str); gtk_container_add (GTK_CONTAINER (window), label); gdk_window_get_pointer (NULL, &x, &y, NULL); gtk_widget_set_uposition (window, x+10, y+10); gtk_widget_show_all (window); cell->comment->window = window; } static gint cell_popup_comment (gpointer data) { Cell *cell = data; cell->comment->timer_tag = -1; cell_display_comment (cell); return FALSE; } static int cell_comment_clicked (GnomeCanvasItem *item, GdkEvent *event, Cell *cell) { GnomeCanvas *canvas = item->canvas; switch (event->type){ case GDK_BUTTON_RELEASE: if (event->button.button != 1) return FALSE; if (cell->comment->window) return FALSE; cell_display_comment (cell); break; case GDK_BUTTON_PRESS: if (event->button.button != 1) return FALSE; break; case GDK_ENTER_NOTIFY: cell->comment->timer_tag = gtk_timeout_add (1000, cell_popup_comment, cell); cursor_set_widget (canvas, GNUMERIC_CURSOR_ARROW); break; case GDK_LEAVE_NOTIFY: cell_comment_cancel_timer (cell); if (cell->comment->window){ gtk_object_destroy (GTK_OBJECT (cell->comment->window)); cell->comment->window = NULL; } break; default: return FALSE; } return TRUE; } static void cell_comment_realize (Cell *cell) { GList *l; g_return_if_fail (cell->comment != NULL); sheet_cell_comment_link (cell); for (l = ((Sheet *)cell->sheet)->sheet_views; l; l = l->next){ SheetView *sheet_view = SHEET_VIEW (l->data); GnomeCanvasItem *o; o = sheet_view_comment_create_marker ( sheet_view, cell->col->pos, cell->row->pos); cell->comment->realized_list = g_list_prepend ( cell->comment->realized_list, o); gtk_signal_connect (GTK_OBJECT (o), "event", GTK_SIGNAL_FUNC (cell_comment_clicked), cell); } } static void cell_comment_unrealize (Cell *cell) { GList *l; g_return_if_fail (cell->comment != NULL); sheet_cell_comment_unlink (cell); for (l = cell->comment->realized_list; l; l = l->next){ GnomeCanvasItem *o = l->data; gtk_object_destroy (GTK_OBJECT (o)); } g_list_free (cell->comment->realized_list); cell->comment->realized_list = NULL; } void cell_realize (Cell *cell) { g_return_if_fail (cell != NULL); if (cell->comment) cell_comment_realize (cell); } void cell_unrealize (Cell *cell) { g_return_if_fail (cell != NULL); if (cell->comment) cell_comment_unrealize (cell); } void cell_set_comment (Cell *cell, char *str) { int had_comments = FALSE; g_return_if_fail (cell != NULL); g_return_if_fail (str != NULL); cell_modified (cell); cell_comment_destroy (cell); cell->comment = g_new (CellComment, 1); cell->comment->realized_list = NULL; cell->comment->timer_tag = -1; cell->comment->window = NULL; cell->comment->comment = string_get (str); if (had_comments) cell_queue_redraw (cell); if (cell->sheet) cell_comment_realize (cell); } void cell_set_foreground (Cell *cell, gushort red, gushort green, gushort blue) { g_return_if_fail (cell != NULL); cell_modified (cell); if (cell->style->valid_flags & STYLE_FORE_COLOR) style_color_unref (cell->style->fore_color); cell->style->valid_flags |= STYLE_FORE_COLOR; cell->style->fore_color = style_color_new (red, green, blue); cell_queue_redraw (cell); } void cell_set_background (Cell *cell, gushort red, gushort green, gushort blue) { g_return_if_fail (cell != NULL); cell_modified (cell); if (cell->style->valid_flags & STYLE_BACK_COLOR) style_color_unref (cell->style->back_color); cell->style->valid_flags |= STYLE_BACK_COLOR; cell->style->back_color = style_color_new (red, green, blue); cell_queue_redraw (cell); } void cell_set_pattern (Cell *cell, int pattern) { g_return_if_fail (cell != NULL); cell_modified (cell); cell->style->valid_flags |= STYLE_PATTERN; cell->style->pattern = pattern; cell_queue_redraw (cell); } /** * cell_set_border: * @cell: the cell * @border_type: an array containing the borders for the cell * @border_color: an array of StyleColors with the * NB. don't unref the StyleColor *s you pass. */ void cell_set_border (Cell *cell, StyleBorderType border_type[4], StyleColor *border_color[4]) { g_return_if_fail (cell != NULL); cell_modified (cell); if (cell->style->valid_flags & STYLE_BORDER) style_border_unref (cell->style->border); cell->style->valid_flags |= STYLE_BORDER; cell->style->border = style_border_new (border_type, border_color); cell_queue_redraw (cell); } /* * cell_set_rendered_text * @cell: the cell we will modify * @rendered_text: the text we will display * * This routine sets the rendered text field of the cell * it recomputes the bounding box for the cell as well */ void cell_set_rendered_text (Cell *cell, char *rendered_text) { g_return_if_fail (cell != NULL); g_return_if_fail (rendered_text != NULL); cell_modified (cell); if (cell->text) string_unref (cell->text); cell->text = string_get (rendered_text); cell_calc_dimensions (cell); } /* * cell_render_value * @cell: The cell whose value needs to be rendered * * The value of the cell is formated according to the format style */ void cell_render_value (Cell *cell) { StyleColor *color; char *str; g_return_if_fail (cell != NULL); g_return_if_fail (cell->value != NULL); if (cell->render_color){ style_color_unref (cell->render_color); cell->render_color = NULL; } str = format_value (cell->style->format, cell->value, &color); cell->render_color = color; cell_set_rendered_text (cell, str); g_free (str); } /* * Sets the text for a cell: * * This is kind of an internal function and should be only called by * routines that know what they are doing. These are the important * differences from cell_set_text: * * - It does not queue redraws (so you have to queue the redraw yourself * or queue a full redraw). * * - It does not queue any recomputations. You have to queue the recompute * yourself. */ void cell_set_text_simple (Cell *cell, char *text) { struct lconv *lconv; g_return_if_fail (cell != NULL); g_return_if_fail (text != NULL); cell_modified (cell); if (cell->entered_text) string_unref (cell->entered_text); cell->entered_text = string_get (text); if (cell->value){ value_release (cell->value); cell->value = NULL; } if (cell->parsed_node){ sheet_cell_formula_unlink (cell); expr_tree_unref (cell->parsed_node); cell->parsed_node = NULL; } if (text [0] == '=' && text [1] != 0){ cell_set_formula (cell, text); } else { Value *v = g_new (Value, 1); int is_text, is_float, maybe_float, has_digits; int seen_exp; char *p; lconv = localeconv (); is_text = is_float = maybe_float = has_digits = FALSE; seen_exp = FALSE; for (p = text; *p && !is_text; p++){ switch (*p){ case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': has_digits = TRUE; break; case '-': if (p == text) break; if (seen_exp) is_text = TRUE; /* falldown */ case 'E': case 'e': case '+': case ':': case '.': case ',': if (*p == 'e' || *p == 'E') seen_exp = TRUE; if (*p == ',' || *p == '.') if (*lconv->decimal_point != *p){ is_text = TRUE; break; } maybe_float = TRUE; break; default: is_text = TRUE; } } if (has_digits && maybe_float) is_float = TRUE; if (has_digits && !is_text){ if (is_float){ v->type = VALUE_FLOAT; float_get_from_range (text, text+strlen(text), &v->v.v_float); } else { v->type = VALUE_INTEGER; int_get_from_range (text, text+strlen (text), &v->v.v_int); } } else { v->type = VALUE_STRING; v->v.str = string_get (text); } cell->value = v; cell_render_value (cell); } } /* * cell_content_changed: * * Call this routine if you modify the contents of a cell * to trigger a recompute on any dependencies */ void cell_content_changed (Cell *cell) { GList *deps; g_return_if_fail (cell != NULL); /* Queue all of the dependencies for this cell */ deps = cell_get_dependencies (cell->sheet, cell->col->pos, cell->row->pos); if (deps) cell_queue_recalc_list (deps); } /* * cell_set_text * * Changes the content of a cell */ void cell_set_text (Cell *cell, char *text) { g_return_if_fail (cell != NULL); g_return_if_fail (text != NULL); cell_queue_redraw (cell); cell_set_text_simple (cell, text); cell_content_changed (cell); cell_queue_redraw (cell); } /** * cell_set_formula_tree_simple: * @cell: the cell to set the formula to * @formula: an expression tree with the formula * * This is an internal function. It should be only called by routines that * know what they are doing. These are the important differences from * cell_set_formula: * * - It does not queue redraws (so you have to queue the redraw yourself * or queue a full redraw). * * - It does not queue any recomputations. YOu have to queue the * recompute yourself. */ void cell_set_formula_tree_simple (Cell *cell, ExprTree *formula) { g_return_if_fail (cell != NULL); g_return_if_fail (formula != NULL); cell_modified (cell); if (cell->parsed_node) expr_tree_unref (cell->parsed_node); cell->parsed_node = formula; expr_tree_ref (formula); cell_formula_changed (cell); } void cell_set_formula_tree (Cell *cell, ExprTree *formula) { g_return_if_fail (cell != NULL); cell_queue_redraw (cell); cell_set_formula_tree_simple (cell, formula); cell_content_changed (cell); cell_queue_redraw (cell); } /** * cell_copy: * @cell: existing cell to duplicate * * Makes a copy of a Cell. * * Returns a copy of the cell. Note that the col, row and sheet * fields are set to NULL. */ Cell * cell_copy (Cell *cell) { Cell *new_cell; g_return_val_if_fail (cell != NULL, NULL); new_cell = g_new (Cell, 1); /* bitmap copy first */ *new_cell = *cell; new_cell->col = NULL; new_cell->row = NULL; new_cell->sheet = NULL; /* now copy propertly the rest */ if (new_cell->parsed_node) expr_tree_ref (new_cell->parsed_node); string_ref (new_cell->text); new_cell->style = style_duplicate (new_cell->style); /* * The cell->value can be NULL if the cell contains * an error */ if (new_cell->value) new_cell->value = value_duplicate (new_cell->value); new_cell->comment = NULL; if (cell->comment) cell_set_comment (new_cell, cell->comment->comment->str); return new_cell; } void cell_destroy (Cell *cell) { g_return_if_fail (cell != NULL); cell_modified (cell); if (cell->parsed_node){ expr_tree_unref (cell->parsed_node); } if (cell->render_color) style_color_unref (cell->render_color); cell_comment_destroy (cell); if (cell->text) string_unref (cell->text); style_destroy (cell->style); value_release (cell->value); g_free (cell); } void cell_freeze_redraws (void) { redraws_frozen++; if (redraws_frozen == 1) cell_hash_queue = g_hash_table_new (g_direct_hash, g_direct_equal); } static void call_cell_queue_redraw (gpointer key, gpointer value, gpointer user_data) { cell_queue_redraw (value); } void cell_thaw_redraws (void) { redraws_frozen--; if (redraws_frozen < 0){ g_warning ("unbalanced freeze/thaw\n"); return; } if (redraws_frozen == 0){ g_hash_table_foreach (cell_hash_queue, call_cell_queue_redraw, NULL); g_hash_table_destroy (cell_hash_queue); cell_hash_queue = NULL; } } static void queue_cell (Cell *cell) { if (g_hash_table_lookup (cell_hash_queue, cell)) return; g_hash_table_insert (cell_hash_queue, cell, cell); } void cell_queue_redraw (Cell *cell) { g_return_if_fail (cell != NULL); if (redraws_frozen){ queue_cell (cell); return; } sheet_redraw_cell_region (cell->sheet, cell->col->pos, cell->row->pos, cell->col->pos, cell->row->pos); } /* * cell_set_format_simple: * * This routine is similar to cell_set_format, but it does not queue * any redraws, nor expects the cell to have a value. * * Make sure you queue a draw in the future for this cell. */ void cell_set_format_simple (Cell *cell, char *format) { g_return_if_fail (cell != NULL); g_return_if_fail (format != NULL); if (strcmp (format, cell->style->format->format) == 0) return; /* Change the format */ cell_modified (cell); style_format_unref (cell->style->format); cell->style->format = style_format_new (format); cell->flags |= CELL_FORMAT_SET; } /* * cell_set_format: * * Changes the format for CELL to be FORMAT. FORMAT should be * a number display format as specified on the manual */ void cell_set_format (Cell *cell, char *format) { g_return_if_fail (cell != NULL); g_return_if_fail (format != NULL); g_return_if_fail (cell->value); if (strcmp (format, cell->style->format->format) == 0) return; cell_modified (cell); cell_queue_redraw (cell); /* Change the format */ style_format_unref (cell->style->format); cell->style->format = style_format_new (format); cell->flags |= CELL_FORMAT_SET; /* re-render the cell text */ cell_render_value (cell); cell_queue_redraw (cell); } void cell_set_format_from_style (Cell *cell, StyleFormat *style_format) { g_return_if_fail (cell != NULL) ; g_return_if_fail (cell->value) ; g_return_if_fail (style_format != NULL) ; cell_modified (cell); cell_queue_redraw (cell); /* Change the format */ style_format_unref (cell->style->format); style_format_ref (style_format) ; cell->style->format = style_format ; cell->flags |= CELL_FORMAT_SET; /* re-render the cell text */ cell_render_value (cell); cell_queue_redraw (cell); } void cell_comment_reposition (Cell *cell) { GList *l; g_return_if_fail (cell != NULL); g_return_if_fail (cell->comment != NULL); for (l = cell->comment->realized_list; l; l = l->next){ GnomeCanvasItem *o = l->data; SheetView *sheet_view = GNUMERIC_SHEET (o->canvas)->sheet_view; sheet_view_comment_relocate (sheet_view, cell->col->pos, cell->row->pos, o); } } /* * cell_relocate: * @cell: The cell that is changing position * @col_diff: The column delta. * @row_diff: The row delta . * * This routine is used to move a cell to a different location: * * Auxiliary items canvas items attached to the cell are moved. */ void cell_relocate (Cell *cell, int col_diff, int row_diff) { g_return_if_fail (cell != NULL); /* 1. Tag the cell as modified */ cell_modified (cell); /* 2. If the cell contains a formula, relocate the formula */ if (cell->parsed_node){ if (col_diff != 0 || row_diff != 0){ ExprTree *new_tree; char *text, *formula; new_tree = expr_tree_relocate (cell->parsed_node, col_diff, row_diff); expr_tree_unref (cell->parsed_node); cell->parsed_node = new_tree; } cell_formula_changed (cell); } /* 3. Move any auxiliary canvas items */ if (cell->comment) cell_comment_reposition (cell); } /* * This routine drops the formula and just keeps the value */ void cell_make_value (Cell *cell) { g_return_if_fail (cell != NULL); g_return_if_fail (cell->parsed_node != NULL); cell_modified (cell); } int cell_get_horizontal_align (Cell *cell) { g_return_val_if_fail (cell != NULL, HALIGN_LEFT); if (cell->style->halign == HALIGN_GENERAL) if (cell->value){ if (cell->value->type== VALUE_FLOAT || cell->value->type == VALUE_INTEGER) return HALIGN_RIGHT; else return HALIGN_LEFT; } else return HALIGN_RIGHT; else return cell->style->halign; } static inline int cell_is_number (Cell *cell) { if (cell->value) if (cell->value->type == VALUE_FLOAT || cell->value->type == VALUE_INTEGER) return TRUE; return FALSE; } static inline int cell_contents_fit_inside_column (Cell *cell) { if (cell->width < COL_INTERNAL_WIDTH (cell->col)) return TRUE; else return FALSE; } /* * cell_get_span: * @cell: The cell we will examine * @col1: return value: the first column used by this cell * @col2: return value: the last column used by this cell * * This routine returns the column interval used by a Cell. */ void cell_get_span (Cell *cell, int *col1, int *col2) { Sheet *sheet; int align, left; int row, pos, margin; g_return_if_fail (cell != NULL); /* * If the cell is a number, or the text fits inside the column, or the * alignment modes are set to "justify", then we report only one * column is used. */ if (cell_is_number (cell) || cell->style->fit_in_cell || cell->style->valign == VALIGN_JUSTIFY || cell->style->halign == HALIGN_JUSTIFY || cell->style->halign == HALIGN_FILL || cell_contents_fit_inside_column (cell)){ *col1 = *col2 = cell->col->pos; return; } sheet = cell->sheet; align = cell_get_horizontal_align (cell); row = cell->row->pos; switch (align){ case HALIGN_LEFT: *col1 = *col2 = cell->col->pos; pos = cell->col->pos + 1; left = cell->width - COL_INTERNAL_WIDTH (cell->col); margin = cell->col->margin_b; for (; left > 0 && pos < SHEET_MAX_COLS-1; pos++){ ColRowInfo *ci; Cell *sibling; sibling = sheet_cell_get (sheet, pos, row); if (sibling) return; ci = sheet_col_get_info (sheet, pos); /* The space consumed is: * - The margin_b from the last column * - The width of the cell */ left -= COL_INTERNAL_WIDTH (ci) + margin + ci->margin_a; margin = ci->margin_b; (*col2)++; } return; case HALIGN_RIGHT: *col1 = *col2 = cell->col->pos; pos = cell->col->pos - 1; left = cell->width - COL_INTERNAL_WIDTH (cell->col); margin = cell->col->margin_a; for (; left > 0 && pos >= 0; pos--){ ColRowInfo *ci; Cell *sibling; sibling = sheet_cell_get (sheet, pos, row); if (sibling) return; ci = sheet_col_get_info (sheet, pos); /* The space consumed is: * - The margin_a from the last column * - The widht of this cell */ left -= COL_INTERNAL_WIDTH (ci) + margin + ci->margin_b; margin = ci->margin_a; (*col1)--; } return; case HALIGN_CENTER: { int left_left, left_right; int margin_a, margin_b; *col1 = *col2 = cell->col->pos; left = cell->width - COL_INTERNAL_WIDTH (cell->col); left_left = left / 2 + (left % 2); left_right = left / 2; margin_a = cell->col->margin_a; margin_b = cell->col->margin_b; for (; left_left > 0 || left_right > 0;){ ColRowInfo *ci; Cell *left_sibling, *right_sibling; if (*col1 - 1 >= 0){ left_sibling = sheet_cell_get (sheet, *col1 - 1, row); if (left_sibling) left_left = 0; else { ci = sheet_col_get_info (sheet, *col1 - 1); left_left -= COL_INTERNAL_WIDTH (ci) + margin_a + ci->margin_b; margin_a = ci->margin_a; (*col1)--; } } else left_left = 0; if (*col2 + 1 < SHEET_MAX_COLS-1){ right_sibling = sheet_cell_get (sheet, *col2 + 1, row); if (right_sibling) left_right = 0; else { ci = sheet_col_get_info (sheet, *col2 + 1); left_right -= COL_INTERNAL_WIDTH (ci) + margin_b + ci->margin_a; margin_b = ci->margin_b; (*col2)++; } } else left_right = 0; } /* for */ break; default: g_warning ("Unknown horizontal alignment type\n"); *col1 = *col2 = cell->col->pos; } /* case HALIGN_CENTER */ } /* switch */ } /* * calc_text_dimensions * @is_number: whether we are computing the size for a number. * @style: the style formatting constraints (font, alignments) * @text: the string contents. * @cell_w: the cell width * @cell_h: the cell height * @h: return value: the height used * @w: return value: the width used. * * Computes the width and height used by the cell based on alignments * constraints in the style using the font specified on the style. */ void calc_text_dimensions (int is_number, Style *style, char *text, int cell_w, int cell_h, int *h, int *w) { GdkFont *font = style->font->font; int text_width, font_height; text_width = gdk_string_width (font, text); font_height = font->ascent + font->descent; if (text_width < cell_w || is_number){ *w = text_width; *h = font_height; return; } if (style->halign == HALIGN_JUSTIFY || style->valign == VALIGN_JUSTIFY || style->fit_in_cell){ char *ideal_cut_spot = NULL; int used, last_was_cut_point; char *p = text; *w = cell_w; *h = font_height; used = 0; last_was_cut_point = FALSE; for (; *p; p++){ int len; if (last_was_cut_point && *p != ' ') ideal_cut_spot = p; len = gdk_text_width (font, p, 1); /* If we have overflowed the cell, wrap */ if (used + len > cell_w){ if (ideal_cut_spot){ int n = p - ideal_cut_spot; used = gdk_text_width (font, ideal_cut_spot, n); } else { used = len; } *h += font_height; ideal_cut_spot = NULL; } else used += len; if (*p == ' ') last_was_cut_point = TRUE; else last_was_cut_point = FALSE; } } else { *w = text_width; *h = font_height; return; } } /* * cell_calc_dimensions * @cell: The cell * * This routine updates the dimensions of the the rendered text of a cell */ void cell_calc_dimensions (Cell *cell) { char *rendered_text; int left, right; g_return_if_fail (cell != NULL); cell_unregister_span (cell); if (cell->text){ Style *style = cell->style; int h, w; rendered_text = cell->text->str; calc_text_dimensions (cell_is_number (cell), style, rendered_text, COL_INTERNAL_WIDTH (cell->col), ROW_INTERNAL_HEIGHT (cell->row), &h, &w); cell->width = cell->col->margin_a + cell->col->margin_b + w; cell->height = cell->row->margin_a + cell->row->margin_b + h; if (cell->height > cell->row->pixels && !cell->row->hard_size) sheet_row_set_internal_height (cell->sheet, cell->row, h); } else cell->width = cell->col->margin_a + cell->col->margin_b; /* Register the span */ cell_get_span (cell, &left, &right); if (left != right) cell_register_span (cell, left, right); } static void draw_overflow (GdkDrawable *drawable, GdkGC *gc, GdkFont *font, int x1, int y1, int text_base, int width, int height) { GdkRectangle rect; int len = gdk_string_width (font, "#"); int total, offset; rect.x = x1; rect.y = y1; rect.width = width; rect.height = height; gdk_gc_set_clip_rectangle (gc, &rect); offset = x1 + width - len; for (total = len; offset > len; total += len){ gdk_draw_text (drawable, font, gc, x1 + offset, text_base, "#", 1); offset -= len; } } static GList * cell_split_text (GdkFont *font, char *text, int width) { GList *list; char *p, *line, *line_begin, *ideal_cut_spot = NULL; int line_len, used, last_was_cut_point; list = NULL; used = 0; last_was_cut_point = FALSE; for (line_begin = p = text; *p; p++){ int len; if (last_was_cut_point && *p != ' ') ideal_cut_spot = p; len = gdk_text_width (font, p, 1); /* If we have overflowed, do the wrap */ if (used + len > width){ char *begin = line_begin; if (ideal_cut_spot){ int n = p - ideal_cut_spot + 1; line_len = ideal_cut_spot - line_begin; used = gdk_text_width (font, ideal_cut_spot, n); line_begin = ideal_cut_spot; } else { used = len; line_len = p - line_begin; line_begin = p; } line = g_malloc (line_len + 1); memcpy (line, begin, line_len); line [line_len] = 0; list = g_list_append (list, line); ideal_cut_spot = NULL; } else used += len; if (*p == ' ') last_was_cut_point = TRUE; else last_was_cut_point = FALSE; } if (*line_begin){ line_len = p - line_begin; line = g_malloc (line_len+1); memcpy (line, line_begin, line_len); line [line_len] = 0; list = g_list_append (list, line); } return list; } /* * str_trim_spaces: * s: the string to modify * * This routine trims the leading and trailing spaces of the * string. The string is possibly modified and the returned * value lies inside the original string. * * No duplication takes place */ static char * str_trim_spaces (char *s) { char *p; while (*s && *s == ' ') s++; p = s + strlen (s); while (p >= s){ if (*p == ' ') *p = 0; else break; p--; } return s; } /* * Returns the number of columns used for the draw */ int cell_draw (Cell *cell, void *sv, GdkGC *gc, GdkDrawable *drawable, int x1, int y1) { Style *style = cell->style; GdkFont *font = style->font->font; SheetView *sheet_view = sv; GdkGC *white_gc = GTK_WIDGET (sheet_view->sheet_view)->style->white_gc; GdkRectangle rect; int start_col, end_col; int width, height; int text_base = y1 + cell->row->pixels - cell->row->margin_b - font->descent; int font_height; int halign; int do_multi_line; char *text; cell_get_span (cell, &start_col, &end_col); width = COL_INTERNAL_WIDTH (cell->col); height = ROW_INTERNAL_HEIGHT (cell->row); font_height = font->ascent + font->descent; halign = cell_get_horizontal_align (cell); /* if a number overflows, do special drawing */ if (width < cell->width && cell_is_number (cell)){ draw_overflow (drawable, gc, font, x1 + cell->col->margin_a, y1, text_base, width, height); return 1; } if (halign == HALIGN_JUSTIFY || style->valign == VALIGN_JUSTIFY || style->fit_in_cell) do_multi_line = TRUE; else do_multi_line = FALSE; text = cell->text->str; if (do_multi_line){ GList *lines, *l; int cell_pixel_height = ROW_INTERNAL_HEIGHT (cell->row); int line_count, x_offset, y_offset, inter_space; lines = cell_split_text (font, text, width); line_count = g_list_length (lines); rect.x = x1; rect.y = y1; rect.height = cell->row->pixels + 1; rect.width = cell->col->pixels + 1; gdk_gc_set_clip_rectangle (gc, &rect); switch (style->valign){ case VALIGN_TOP: y_offset = 0; inter_space = font_height; break; case VALIGN_CENTER: y_offset = (cell_pixel_height - (line_count * font_height))/2; inter_space = font_height; break; case VALIGN_JUSTIFY: if (line_count > 1){ y_offset = 0; inter_space = font_height + (cell_pixel_height - (line_count * font_height)) / (line_count-1); break; } /* Else, we become a VALIGN_BOTTOM line */ case VALIGN_BOTTOM: y_offset = cell_pixel_height - (line_count * font_height); inter_space = font_height; break; default: g_warning ("Unhandled cell vertical alignment\n"); y_offset = 0; inter_space = font_height; } y_offset += font_height - 1; for (l = lines; l; l = l->next){ char *str = l->data; str = str_trim_spaces (str); switch (halign){ case HALIGN_LEFT: case HALIGN_JUSTIFY: x_offset = cell->col->margin_a; break; case HALIGN_RIGHT: x_offset = cell->col->pixels - cell->col->margin_b - gdk_string_width (font, str); break; case HALIGN_CENTER: x_offset = (cell->col->pixels - gdk_string_width (font, str)) / 2; break; default: g_warning ("Multi-line justification style not supported\n"); x_offset = cell->col->margin_a; } /* Advance one pixel for the border */ x_offset++; gc = GTK_WIDGET (sheet_view->sheet_view)->style->black_gc; gdk_draw_text (drawable, font, gc, x1 + x_offset, y1 + y_offset, str, strlen (str)); y_offset += inter_space; g_free (l->data); } g_list_free (lines); } else { int x, diff, total, len; /* * x1, y1 are relative to this cell origin, but the cell might be using * columns to the left (if it is set to right justify or center justify) * compute the pixel difference */ if (start_col != cell->col->pos) diff = -sheet_col_get_distance (cell->sheet, start_col, cell->col->pos); else diff = 0; /* This rectangle has the whole area used by this cell */ rect.x = x1 + 1 + diff; rect.y = y1 + 1; rect.width = sheet_col_get_distance (cell->sheet, start_col, end_col+1) - 1; rect.height = cell->row->pixels - 1; /* Set the clip rectangle */ gdk_gc_set_clip_rectangle (gc, &rect); /* * In order to draw the background of the cell, we need to change the * gc's drawing color before calling the gdk_draw_rectangle function */ gdk_gc_set_foreground (gc, &(style->back_color->color)); gdk_draw_rectangle (drawable, gc, TRUE, rect.x, rect.y, rect.width, rect.height); /* * And now reset the previous foreground color */ if (cell->render_color) gdk_gc_set_foreground (gc, &cell->render_color->color); else gdk_gc_set_foreground (gc, &(style->fore_color->color)); len = 0; switch (halign){ case HALIGN_FILL: printf ("FILL!\n"); len = gdk_string_width (font, text); /* fall down */ case HALIGN_LEFT: x = cell->col->margin_a; break; case HALIGN_RIGHT: x = cell->col->pixels - cell->col->margin_b - cell->width; break; case HALIGN_CENTER: x = (cell->col->pixels - cell->width)/2; break; default: g_warning ("Single-line justification style not supported\n"); x = cell->col->margin_a; break; } total = 0; do { gdk_draw_text (drawable, font, gc, 1 + x1 + x, text_base, text, strlen (text)); x1 += len; total += len; } while (halign == HALIGN_FILL && total < rect.width); } return end_col - start_col + 1; } /** * cell_get_text: * @cell: the cell to fetch the text from. * * Returns a g_malloced() version of the contents of the cell. It will * return a formula if it is a formula, or a string value rendered with the * current format. * * This should not be used by routines that need the actual content * of the cell value in a reliable way */ char * cell_get_text (Cell *cell) { char *str; g_return_val_if_fail (cell != NULL, NULL); if (cell->parsed_node){ char *func, *ret; func = expr_decode_tree (cell->parsed_node, cell->sheet, cell->col->pos, cell->row->pos); ret = g_strconcat ("=", func, NULL); g_free (func); return ret; } /* * If a value is set, return that text formatted */ if (cell->value) str = format_value (cell->style->format, cell->value, NULL); else str = g_strdup (cell->entered_text->str); return str; } /** * cell_get_content: * @cell: the cell from which we want to pull the content from * * This returns a g_malloc()ed region of memory with a text representation * of the cell contents. * * This will return a text expression if the cell contains a formula, or * a string representation of the value. */ char * cell_get_content (Cell *cell) { char *str; g_return_val_if_fail (cell != NULL, NULL); if (cell->parsed_node){ char *func, *ret; func = expr_decode_tree (cell->parsed_node, cell->sheet, cell->col->pos, cell->row->pos); ret = g_strconcat ("=", func, NULL); g_free (func); return ret; } /* * If a value is set, return that text formatted */ if (cell->value) str = value_string (cell->value); else str = g_strdup (cell->entered_text->str); return str; }