/* * Select the given range and make the it visible. */ static gboolean wb_control_jump (WorkbookControl *wbc, Sheet *sheet, const GnmRangeRef *r) { SheetView *sv; GnmCellPos tmp; if (r->a.sheet) sheet = r->a.sheet; if (!sheet_is_visible (sheet)) { go_cmd_context_error_invalid (GO_CMD_CONTEXT (wbc), _("Cannot jump to an invisible sheet"), sheet->name_unquoted); return FALSE; } sv = sheet_get_view (sheet, wb_control_view (wbc)); tmp.col = r->a.col; tmp.row = r->a.row; sv_selection_set (sv, &tmp, r->a.col, r->a.row, r->b.col, r->b.row); sv_make_cell_visible (sv, r->b.col, r->b.row, FALSE); sv_make_cell_visible (sv, r->a.col, r->a.row, FALSE); sv_update (sv); if (wb_control_cur_sheet (wbc) != sheet) wb_view_sheet_focus (wbc->wb_view, sheet); return TRUE; }
/** * cmd_paste : * @sheet: The destination sheet * @range : The range to paste to within the destination sheet. * @flags: Any paste special flags. * * Pastes the current cut buffer, copy buffer, or X selection to * the destination sheet range. * * When pasting a cut the destination MUST be the same size as the src. * * When pasting a copy the destination can be a singleton, or an integer * multiple of the size of the source. This is not tested here. * Full undo support. **/ void cmd_paste (WorkbookControl *wbc, GnmPasteTarget const *pt) { GnmCellRegion *content; GnmRange const *src_range; g_return_if_fail (pt != NULL); g_return_if_fail (IS_SHEET (pt->sheet)); src_range = gnm_app_clipboard_area_get (); content = gnm_app_clipboard_contents_get (); if (content == NULL && src_range != NULL) { /* Pasting a Cut */ GnmExprRelocateInfo rinfo; Sheet *src_sheet = gnm_app_clipboard_sheet_get (); /* Validate the size & shape of the target here. */ int const cols = (src_range->end.col - src_range->start.col); int const rows = (src_range->end.row - src_range->start.row); GnmRange dst = pt->range; if (range_is_singleton (&dst)) { dst.end.col = dst.start.col + cols; dst.end.row = dst.start.row + rows; } else if ((dst.end.col - dst.start.col) != cols || (dst.end.row - dst.start.row) != rows) { char *msg = g_strdup_printf ( _("destination has a different shape (%dRx%dC) than the original (%dRx%dC)\n\n" "Try selecting a single cell or an area of the same shape and size."), (dst.end.row - dst.start.row)+1, (dst.end.col - dst.start.col)+1, rows+1, cols+1); go_cmd_context_error_invalid (GO_CMD_CONTEXT (wbc), _("Unable to paste into selection"), msg); g_free (msg); return; } rinfo.reloc_type = GNM_EXPR_RELOCATE_MOVE_RANGE; rinfo.origin = *src_range; rinfo.col_offset = dst.start.col - rinfo.origin.start.col; rinfo.row_offset = dst.start.row - rinfo.origin.start.row; rinfo.origin_sheet = src_sheet; rinfo.target_sheet = pt->sheet; if (!cmd_paste_cut (wbc, &rinfo, TRUE, NULL)) gnm_app_clipboard_clear (TRUE); /* If this application has marked a selection use it */ } else if (content != NULL) cmd_paste_copy (wbc, pt, content); /* See if the control has access to information to paste */ else wb_control_paste_from_selection (wbc, pt); }
/* * This is called when something is entered in the location entry. * We either go there (if the text refers to a cell by address or * name), or we try to define a name for the selection. */ gboolean wb_control_parse_and_jump (WorkbookControl *wbc, char const *text) { Sheet *sheet = wb_control_cur_sheet (wbc); GnmParsePos pp; GnmEvalPos ep; GnmValue *target; GnmRangeRef range; SheetView *sv; if (text == NULL || *text == '\0') return FALSE; sv = wb_control_cur_sheet_view (wbc); parse_pos_init_editpos (&pp, sv); target = value_new_cellrange_parsepos_str (&pp, text, GNM_EXPR_PARSE_DEFAULT); if (target == NULL) { /* Not an address; is it a name? */ GnmParsePos pp; GnmNamedExpr *nexpr = expr_name_lookup ( parse_pos_init_sheet (&pp, sheet), text); /* If no name, or just a placeholder exists create a name */ if (nexpr == NULL || expr_name_is_placeholder (nexpr)) { wb_create_name (wbc, text, &pp); return FALSE; } else { target = gnm_expr_top_get_range (nexpr->texpr); if (target == NULL) { go_cmd_context_error_invalid (GO_CMD_CONTEXT (wbc), _("Address"), text); return FALSE; } } } eval_pos_init_editpos (&ep, sv); gnm_cellref_make_abs (&range.a, &target->v_range.cell.a, &ep); gnm_cellref_make_abs (&range.b, &target->v_range.cell.b, &ep); value_release (target); return wb_control_jump (wbc, sheet, &range); }
/** * clipboard_paste_region: * @cr: The GnmCellRegion to paste. * @pt: Where to paste the values. * @cc: The context for error handling. * * Pastes the supplied GnmCellRegion (@cr) into the supplied * GnmPasteTarget (@pt). This operation is not undoable. It does not auto grow * the destination if the target is a singleton. This is a simple interface to * paste a region. * * returns : TRUE if there was a problem. **/ gboolean clipboard_paste_region (GnmCellRegion const *cr, GnmPasteTarget const *pt, GOCmdContext *cc) { int repeat_horizontal, repeat_vertical, clearFlags; int dst_cols, dst_rows, src_cols, src_rows; int i, j; GSList *ptr; GnmRange const *r; gboolean has_contents, adjust_merges = TRUE; struct paste_cell_data dat; GnmRange const *merge_src; g_return_val_if_fail (pt != NULL, TRUE); g_return_val_if_fail (cr != NULL, TRUE); /* we do not need any of this fancy stuff when pasting a simple object */ if (cr->cell_content == NULL && cr->styles == NULL && cr->merged == NULL && cr->objects != NULL) { if (pt->paste_flags & (PASTE_COMMENTS | PASTE_OBJECTS)) for (ptr = cr->objects; ptr; ptr = ptr->next) paste_object (pt, ptr->data, pt->range.start.col, pt->range.start.row); return FALSE; } r = &pt->range; dst_cols = range_width (r); dst_rows = range_height (r); src_cols = cr->cols; src_rows = cr->rows; /* If the source is a single cell or a single merge */ /* Treat a target of a single merge specially, don't split the merge */ if ((src_cols == 1 && src_rows == 1) || (g_slist_length (cr->merged) == 1 && (NULL != (merge_src = cr->merged->data)) && range_height (merge_src) == cr->rows && range_width (merge_src) == cr->cols)) { GnmRange const *merge = gnm_sheet_merge_is_corner (pt->sheet, &r->start); if (merge != NULL && range_equal (r, merge)) { dst_cols = dst_rows = 1; adjust_merges = FALSE; src_cols = 1; src_rows = 1; } /* Apparently links do not supercede merges */ } else if (pt->paste_flags & PASTE_LINK) adjust_merges = FALSE; has_contents = pt->paste_flags & (PASTE_CONTENTS|PASTE_AS_VALUES|PASTE_LINK); if (pt->paste_flags & PASTE_TRANSPOSE) { int tmp = src_cols; src_cols = src_rows; src_rows = tmp; } if (cr->not_as_contents && (pt->paste_flags & PASTE_CONTENTS)) { go_cmd_context_error_invalid (cc, _("Unable to paste"), _("Contents can only be pasted by value or by link.")); return TRUE; } /* calculate the tiling */ repeat_horizontal = dst_cols/src_cols; if (repeat_horizontal * src_cols != dst_cols) { char *msg = g_strdup_printf ( _("destination does not have an even multiple of source columns (%d vs %d)\n\n" "Try selecting a single cell or an area of the same shape and size."), dst_cols, src_cols); go_cmd_context_error_invalid (cc, _("Unable to paste"), msg); g_free (msg); return TRUE; } repeat_vertical = dst_rows/src_rows; if (repeat_vertical * src_rows != dst_rows) { char *msg = g_strdup_printf ( _("destination does not have an even multiple of source rows (%d vs %d)\n\n" "Try selecting a single cell or an area of the same shape and size."), dst_rows, src_rows); go_cmd_context_error_invalid (cc, _("Unable to paste"), msg); g_free (msg); return TRUE; } if ((pt->range.start.col + dst_cols) > gnm_sheet_get_max_cols (pt->sheet) || (pt->range.start.row + dst_rows) > gnm_sheet_get_max_rows (pt->sheet)) { go_cmd_context_error_invalid (cc, _("Unable to paste"), _("result passes the sheet boundary")); return TRUE; } clearFlags = 0; /* clear the region where we will paste */ if (has_contents) clearFlags = CLEAR_VALUES | CLEAR_NORESPAN; if (pt->paste_flags & PASTE_COMMENTS) clearFlags |= CLEAR_COMMENTS; /* No need to clear the formats. We will paste over top of these. */ /* if (pt->paste_flags & PASTE_FORMATS) clearFlags |= CLEAR_FORMATS; */ if (pt->paste_flags & (PASTE_OPER_MASK | PASTE_SKIP_BLANKS)) clearFlags = 0; /* remove merged regions even for operations, or blanks */ if (has_contents && adjust_merges) clearFlags |= CLEAR_MERGES; if (clearFlags != 0) { int const dst_col = pt->range.start.col; int const dst_row = pt->range.start.row; sheet_clear_region (pt->sheet, dst_col, dst_row, dst_col + dst_cols - 1, dst_row + dst_rows - 1, clearFlags, cc); } dat.translate_dates = cr->date_conv && !go_date_conv_equal (cr->date_conv, workbook_date_conv (pt->sheet->workbook)); for (i = 0; i < repeat_horizontal ; i++) for (j = 0; j < repeat_vertical ; j++) { int const left = i * src_cols + pt->range.start.col; int const top = j * src_rows + pt->range.start.row; dat.top_left.col = left; dat.top_left.row = top; dat.rinfo.reloc_type = GNM_EXPR_RELOCATE_MOVE_RANGE; dat.rinfo.origin_sheet = dat.rinfo.target_sheet = pt->sheet; if (pt->paste_flags & PASTE_EXPR_LOCAL_RELOCATE) { dat.rinfo.origin.start = cr->base; dat.rinfo.origin.end.col = cr->base.col + cr->cols - 1; dat.rinfo.origin.end.row = cr->base.row + cr->rows - 1; dat.rinfo.col_offset = left - cr->base.col; dat.rinfo.row_offset = top - cr->base.row; } else { dat.rinfo.origin = pt->range; dat.rinfo.col_offset = 0; dat.rinfo.row_offset = 0; } /* Move the styles on here so we get correct formats before recalc */ if (pt->paste_flags & PASTE_FORMATS) { if (pt->paste_flags & PASTE_TRANSPOSE) sheet_style_set_list (pt->sheet, &dat.top_left, cr->styles, (sheet_style_set_list_cb_t) range_transpose, &dat.top_left); else if (pt->paste_flags & PASTE_FLIP_H) { int data = 2 * left + src_cols - 1; sheet_style_set_list (pt->sheet, &dat.top_left, cr->styles, (sheet_style_set_list_cb_t) range_flip_h, &data); } else if (pt->paste_flags & PASTE_FLIP_V) { int data = 2 * top + src_rows - 1; sheet_style_set_list (pt->sheet, &dat.top_left, cr->styles, (sheet_style_set_list_cb_t) range_flip_v, &data); } else sheet_style_set_list (pt->sheet, &dat.top_left, cr->styles, NULL, NULL); } if (has_contents && !(pt->paste_flags & PASTE_DONT_MERGE)) { for (ptr = cr->merged; ptr != NULL ; ptr = ptr->next) { GnmRange tmp = *((GnmRange const *)ptr->data); if (pt->paste_flags & PASTE_TRANSPOSE) { int x; x = tmp.start.col; tmp.start.col = tmp.start.row; tmp.start.row = x; x = tmp.end.col; tmp.end.col = tmp.end.row; tmp.end.row = x; } if (!range_translate (&tmp, pt->sheet, left, top)) gnm_sheet_merge_add (pt->sheet, &tmp, TRUE, cc); } } if (has_contents && (pt->paste_flags & PASTE_LINK)) { paste_link (pt, top, left, cr); continue; } if (has_contents && NULL != cr->cell_content) { dat.pt = pt; dat.cr = cr; g_hash_table_foreach (cr->cell_content, (GHFunc)cb_paste_cell, &dat); } if (pt->paste_flags & (PASTE_COMMENTS | PASTE_OBJECTS)) for (ptr = cr->objects; ptr; ptr = ptr->next) paste_object (pt, ptr->data, left, top); } if (!(pt->paste_flags & PASTE_NO_RECALC)) { if (has_contents) { sheet_region_queue_recalc (pt->sheet, r); sheet_flag_status_update_range (pt->sheet, r); } else sheet_flag_style_update_range (pt->sheet, r); sheet_range_calc_spans (pt->sheet, r, (pt->paste_flags & PASTE_FORMATS) ? GNM_SPANCALC_RE_RENDER : GNM_SPANCALC_RENDER); if (pt->paste_flags & PASTE_UPDATE_ROW_HEIGHT) rows_height_update (pt->sheet, &pt->range, FALSE); sheet_redraw_all (pt->sheet, FALSE); } return FALSE; }
/** * wbcg_edit_start: * @wbcg: The workbook to be edited. * @blankp: If true, erase current cell contents first. If false, leave the * contents alone. * @cursorp: If true, create an editing cursor in the current sheet. (If * false, the text will be editing in the edit box above the sheet, * but this is not handled by this function.) * * Initiate editing of a cell in the sheet. Note that we have two modes of * editing: * 1) in-cell editing when you just start typing, and * 2) above sheet editing when you hit F2. * * Returns TRUE if we did indeed start editing. Returns FALSE if the * cell-to-be-edited was locked. */ gboolean wbcg_edit_start (WBCGtk *wbcg, gboolean blankp, gboolean cursorp) { /* We could save this, but the situation is rare, if confusing. */ static gboolean warn_on_text_format = TRUE; SheetView *sv; SheetControlGUI *scg; GnmCell *cell; char *text = NULL; int col, row; WorkbookView *wbv; int cursor_pos = -1; g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE); if (wbcg_is_editing (wbcg)) return TRUE; /* Avoid recursion, and do not begin editing if a guru is up */ if (wbcg->inside_editing || wbc_gtk_get_guru (wbcg) != NULL) return TRUE; wbcg->inside_editing = TRUE; wbv = wb_control_view (GNM_WBC (wbcg)); sv = wb_control_cur_sheet_view (GNM_WBC (wbcg)); scg = wbcg_cur_scg (wbcg); col = sv->edit_pos.col; row = sv->edit_pos.row; /* don't edit a locked cell */ /* TODO : extend this to disable edits that cannot succeed * like editing a single cell of an array. I think we have enough * information if we look at the selection. */ if (wb_view_is_protected (wbv, TRUE) && gnm_style_get_contents_locked (sheet_style_get (sv->sheet, col, row))) { char *pos = g_strdup_printf ( _("%s!%s is locked"), sv->sheet->name_quoted, cell_coord_name (col, row)); go_cmd_context_error_invalid (GO_CMD_CONTEXT (wbcg), pos, wb_view_is_protected (wbv, FALSE) ? _("Unprotect the workbook to enable editing.") : _("Unprotect the sheet to enable editing.")); wbcg->inside_editing = FALSE; g_free (pos); return FALSE; } cell = sheet_cell_get (sv->sheet, col, row); if (cell && warn_on_text_format && go_format_is_text (gnm_cell_get_format (cell)) && (gnm_cell_has_expr (cell) || !VALUE_IS_STRING (cell->value))) { gint res; /* Using GtkResponseType would yield a warning on the switch */ GtkWidget *check; GtkWidget *align; GtkWidget *d = gnm_message_dialog_create (wbcg_toplevel (wbcg), GTK_DIALOG_DESTROY_WITH_PARENT, GTK_MESSAGE_WARNING, _("You are about to edit a cell with \"text\" format."), _("The cell does not currently contain text, though, so if " "you go on editing then the contents will be turned into " "text.")); gtk_dialog_add_button (GTK_DIALOG (d), GTK_STOCK_EDIT, GTK_RESPONSE_OK); go_gtk_dialog_add_button (GTK_DIALOG (d), _("Remove format"), GTK_STOCK_REMOVE, GNM_RESPONSE_REMOVE); gtk_dialog_add_button (GTK_DIALOG (d), GNM_STOCK_CANCEL, GTK_RESPONSE_CANCEL); gtk_dialog_set_default_response (GTK_DIALOG (d), GTK_RESPONSE_CANCEL); check = gtk_check_button_new_with_label (_("Show this dialog next time.")); g_signal_connect (check, "toggled", G_CALLBACK (cb_warn_toggled), &warn_on_text_format); gtk_toggle_button_set_active (GTK_TOGGLE_BUTTON (check), TRUE); align = gtk_alignment_new (0.5, 0.5, 0, 0); gtk_container_add (GTK_CONTAINER (align), check); gtk_widget_show_all (align); gtk_box_pack_end (GTK_BOX (gtk_dialog_get_content_area (GTK_DIALOG (d))), align, TRUE, TRUE, 0); res = go_gtk_dialog_run (GTK_DIALOG (d), wbcg_toplevel (wbcg)); switch (res) { case GNM_RESPONSE_REMOVE: { GnmStyle *style = gnm_style_new (); gnm_style_set_format (style, go_format_general ()); if (!cmd_selection_format (GNM_WBC (wbcg), style, NULL, NULL)) break; /* Fall through. */ } default: case GTK_RESPONSE_CANCEL: wbcg->inside_editing = FALSE; return FALSE; case GTK_RESPONSE_OK: break; } } gnm_app_clipboard_unant (); if (blankp) gtk_entry_set_text (wbcg_get_entry (wbcg), ""); else if (cell != NULL) { gboolean quoted = FALSE; text = gnm_cell_get_text_for_editing (cell, sv->sheet, "ed, &cursor_pos); if (text) gtk_entry_set_text (wbcg_get_entry (wbcg), text); if (cell->value != NULL) { GOFormat const *fmt = VALUE_FMT (cell->value); if (fmt != NULL && go_format_is_markup (fmt)) { PangoAttrList *markup = pango_attr_list_copy ((PangoAttrList *)go_format_get_markup (fmt)); if (quoted) go_pango_attr_list_open_hole (markup, 0, 1); wbcg_edit_init_markup (wbcg, markup); } } } gnm_expr_entry_set_scg (wbcg->edit_line.entry, scg); gnm_expr_entry_set_flags (wbcg->edit_line.entry, GNM_EE_SHEET_OPTIONAL | GNM_EE_FORMULA_ONLY, GNM_EE_SINGLE_RANGE | GNM_EE_SHEET_OPTIONAL | GNM_EE_FORMULA_ONLY | GNM_EE_FORCE_REL_REF | GNM_EE_FORCE_ABS_REF); scg_edit_start (scg); /* Redraw the cell contents in case there was a span */ sheet_redraw_region (sv->sheet, col, row, col, row); if (cursorp && /* autocompletion code will not work in the edit line */ wbv->do_auto_completion && (text == NULL || g_unichar_isalpha (g_utf8_get_char (text)))) { wbcg->auto_complete = gnm_complete_sheet_new ( sv->sheet, col, row, workbook_edit_complete_notify, wbcg); wbcg->auto_completing = TRUE; wbcg->auto_max_size = 0; } else wbcg->auto_complete = NULL; /* Give the focus to the edit line */ if (!cursorp) gtk_window_set_focus (wbcg_toplevel (wbcg), (GtkWidget *) wbcg_get_entry (wbcg)); wbcg->editing = TRUE; wbcg->editing_sheet = sv->sheet; wbcg->editing_cell = cell; /* If this assert fails, it means editing was not shut down * properly before */ g_return_val_if_fail (wbcg->edit_line.signal_changed == 0, TRUE); wbcg->edit_line.signal_changed = g_signal_connect ( G_OBJECT (wbcg_get_entry (wbcg)), "changed", G_CALLBACK (cb_entry_changed), wbcg); wbcg->edit_line.signal_insert = g_signal_connect ( G_OBJECT (wbcg_get_entry (wbcg)), "insert-text", G_CALLBACK (cb_entry_insert_text), wbcg); wbcg->edit_line.signal_delete = g_signal_connect ( G_OBJECT (wbcg_get_entry (wbcg)), "delete-text", G_CALLBACK (cb_entry_delete_text), wbcg); wbcg->edit_line.signal_cursor_pos = g_signal_connect_swapped ( G_OBJECT (wbcg_get_entry (wbcg)), "notify::cursor-position", G_CALLBACK (cb_entry_cursor_pos), wbcg); wbcg->edit_line.signal_selection_bound = g_signal_connect_swapped ( G_OBJECT (wbcg_get_entry (wbcg)), "notify::selection-bound", G_CALLBACK (cb_entry_cursor_pos), wbcg); g_free (text); wb_control_update_action_sensitivity (GNM_WBC (wbcg)); wbcg->inside_editing = FALSE; gtk_editable_set_position (GTK_EDITABLE (wbcg_get_entry (wbcg)), cursor_pos); return TRUE; }