/** * 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; }
int * gnm_sort_contents (GnmSortData *data, GOCmdContext *cc) { ColRowInfo const *cra; SortDataPerm *perm; int length, real_length, i, cur, *iperm, *real; int const first = data->top ? data->range->start.row : data->range->start.col; length = gnm_sort_data_length (data); real_length = 0; /* Discern the rows/cols to be actually sorted */ real = g_new (int, length); for (i = 0; i < length; i++) { cra = data->top ? sheet_row_get (data->sheet, first + i) : sheet_col_get (data->sheet, first + i); if (cra && !cra->visible) { real[i] = -1; } else { real[i] = i; real_length++; } } cur = 0; perm = g_new (SortDataPerm, real_length); for (i = 0; i < length; i++) { if (real[i] != -1) { perm[cur].index = i; perm[cur].data = data; cur++; } } if (real_length > 1) { if (data->locale) { char *old_locale = g_strdup (go_setlocale (LC_ALL, NULL)); go_setlocale (LC_ALL, data->locale); qsort (perm, real_length, sizeof (SortDataPerm), g_str_has_prefix (old_locale, data->locale) ? sort_qsort_compare : sort_qsort_compare_in_locale); go_setlocale (LC_ALL, old_locale); g_free (old_locale); } else qsort (perm, real_length, sizeof (SortDataPerm), sort_qsort_compare); } cur = 0; iperm = g_new (int, length); for (i = 0; i < length; i++) { if (real[i] != -1) { iperm[i] = perm[cur].index; cur++; } else { iperm[i] = i; } } g_free (perm); g_free (real); sort_permute (data, iperm, length, cc); /* Make up for the PASTE_NO_RECALC. */ sheet_region_queue_recalc (data->sheet, data->range); sheet_flag_status_update_range (data->sheet, data->range); sheet_range_calc_spans (data->sheet, data->range, data->retain_formats ? GNM_SPANCALC_RENDER : GNM_SPANCALC_RE_RENDER); sheet_redraw_all (data->sheet, FALSE); return iperm; }
/** * wbcg_edit_finish: * @wbcg: #WBCGtk * @result: what should we do with the content * @showed_dialog: If non-NULL will indicate if a dialog was displayed. * * Return: TRUE if editing completed successfully, or we were no editing. **/ gboolean wbcg_edit_finish (WBCGtk *wbcg, WBCEditResult result, gboolean *showed_dialog) { Sheet *sheet; SheetView *sv; WorkbookControl *wbc; WorkbookView *wbv; g_return_val_if_fail (GNM_IS_WBC_GTK (wbcg), FALSE); wbc = GNM_WBC (wbcg); wbv = wb_control_view (wbc); wbcg_focus_cur_scg (wbcg); gnm_expr_entry_close_tips (wbcg_get_entry_logical (wbcg)); if (showed_dialog != NULL) *showed_dialog = FALSE; /* Remove the range selection cursor if it exists */ if (NULL != wbcg->rangesel) scg_rangesel_stop (wbcg->rangesel, result == WBC_EDIT_REJECT); if (!wbcg_is_editing (wbcg)) { /* We may have a guru up even if we are not editing. remove it. * Do NOT remove until later it if we are editing, it is possible * that we may want to continue editing. */ if (wbcg->edit_line.guru != NULL) { GtkWidget *w = wbcg->edit_line.guru; wbc_gtk_detach_guru (wbcg); gtk_widget_destroy (w); } return TRUE; } g_return_val_if_fail (IS_SHEET (wbcg->editing_sheet), TRUE); sheet = wbcg->editing_sheet; sv = sheet_get_view (sheet, wbv); /* Save the results before changing focus */ if (result != WBC_EDIT_REJECT) { ValidationStatus valid = GNM_VALIDATION_STATUS_VALID; char *free_txt = NULL; char const *txt; GnmStyle const *mstyle; char const *expr_txt = NULL; GOFormat const *fmt; GnmValue *value; GOUndo *u = NULL; GSList *selection = selection_get_ranges (sv, FALSE); GnmParsePos pp; GnmExprTop const *texpr = NULL; parse_pos_init_editpos (&pp, sv); /* Array only works on single range. */ if (result == WBC_EDIT_ACCEPT_ARRAY && (selection == NULL || selection->next != NULL)) result = WBC_EDIT_ACCEPT_RANGE; /******* Check whether we would split a range ********/ switch (result) { case (WBC_EDIT_ACCEPT_RANGE): case (WBC_EDIT_ACCEPT_ARRAY): { if (sheet_ranges_split_region (sheet, selection, GO_CMD_CONTEXT (wbc), _("Set Text"))) { range_fragment_free (selection); if (showed_dialog != NULL) *showed_dialog = TRUE; return FALSE; } if (result == WBC_EDIT_ACCEPT_ARRAY && sheet_range_contains_merges_or_arrays (sheet, selection->data, GO_CMD_CONTEXT (wbc), _("Set Text"), TRUE, FALSE)) { range_fragment_free (selection); if (showed_dialog != NULL) *showed_dialog = TRUE; return FALSE; } break; } case (WBC_EDIT_ACCEPT_WO_AC): case (WBC_EDIT_ACCEPT): { GnmCell const *cell = sheet_cell_get (sheet, sv->edit_pos.col, sv->edit_pos.row); if (gnm_cell_is_nonsingleton_array (cell)) { gnm_cmd_context_error_splits_array (GO_CMD_CONTEXT (wbc), _("Set Text"), NULL); if (showed_dialog != NULL) *showed_dialog = TRUE; range_fragment_free (selection); return FALSE; } break; } case (WBC_EDIT_REJECT): default: /* We should not be able to get here! */ break; } /******* Check whether the range is locked ********/ switch (result) { case (WBC_EDIT_ACCEPT_RANGE): case (WBC_EDIT_ACCEPT_ARRAY): { if (cmd_selection_is_locked_effective (sheet, selection, wbc, _("Set Text"))) { range_fragment_free (selection); if (showed_dialog != NULL) *showed_dialog = TRUE; return FALSE; } break; } case (WBC_EDIT_ACCEPT_WO_AC): case (WBC_EDIT_ACCEPT): { GnmRange r; r.end = r.start = pp.eval; if (cmd_cell_range_is_locked_effective (sheet, &r, wbc, _("Set Text"))) { range_fragment_free (selection); if (showed_dialog != NULL) *showed_dialog = TRUE; return FALSE; } break; } case (WBC_EDIT_REJECT): default: /* We should not be able to get here! */ break; } /*****************************************************/ txt = wbcg_edit_get_display_text (wbcg); mstyle = sheet_style_get (sheet, sv->edit_pos.col, sv->edit_pos.row); fmt = gnm_cell_get_format (sheet_cell_fetch (sheet, sv->edit_pos.col, sv->edit_pos.row)); value = format_match (txt, fmt, workbook_date_conv (sheet->workbook)); if (value == NULL) expr_txt = gnm_expr_char_start_p (txt); else value_release (value); /* NOTE : do not modify gnm_expr_char_start_p to exclude "-" * it _can_ start an expression, which is required for rangesel * it just isn't an expression. */ if (expr_txt != NULL && *expr_txt != '\0' && strcmp (expr_txt, "-")) { GnmExprTop const *texpr_test = NULL; GnmParseError perr; parse_error_init (&perr); texpr_test = gnm_expr_parse_str (expr_txt, &pp, GNM_EXPR_PARSE_DEFAULT, NULL, &perr); /* Try adding a single extra closing paren to see if it helps */ if (texpr_test == NULL && perr.err != NULL && perr.err->code == PERR_MISSING_PAREN_CLOSE) { GnmParseError tmp_err; char *tmp = g_strconcat (txt, ")", NULL); parse_error_init (&tmp_err); texpr_test = gnm_expr_parse_str (gnm_expr_char_start_p (tmp), &pp, GNM_EXPR_PARSE_DEFAULT, NULL, &tmp_err); parse_error_free (&tmp_err); if (texpr_test != NULL) { txt = free_txt = tmp; expr_txt = gnm_expr_char_start_p (txt); } else g_free (tmp); } if (texpr_test == NULL && perr.err != NULL) { ValidationStatus reedit; /* set focus _before_ selection. gtk2 seems to * screw with selection in gtk_entry_grab_focus * (no longer required now that we clear * gtk-entry-select-on-focus) */ gtk_window_set_focus (wbcg_toplevel (wbcg), (GtkWidget *) wbcg_get_entry (wbcg)); if (perr.begin_char != 0 || perr.end_char != 0) { int offset = expr_txt - txt; gtk_editable_select_region (GTK_EDITABLE (wbcg_get_entry (wbcg)), offset + perr.begin_char, offset + perr.end_char); } else gtk_editable_set_position ( GTK_EDITABLE (wbcg_get_entry (wbcg)), -1); reedit = wb_control_validation_msg (GNM_WBC (wbcg), GNM_VALIDATION_STYLE_PARSE_ERROR, NULL, perr.err->message); if (showed_dialog != NULL) *showed_dialog = TRUE; parse_error_free (&perr); if (reedit == GNM_VALIDATION_STATUS_INVALID_EDIT) { range_fragment_free (selection); return FALSE; } /* restore focus to sheet , or we'll leave edit * mode only to jump right back in the new * cell because it looks like someone just * focused on the edit line (eg hit F2) */ wbcg_focus_cur_scg (wbcg); } if (texpr_test != NULL) gnm_expr_top_unref (texpr_test); } /* We only enter an array formula if the text is a formula */ if (result == WBC_EDIT_ACCEPT_ARRAY && !expr_txt) result = WBC_EDIT_ACCEPT_RANGE; if (result == WBC_EDIT_ACCEPT_ARRAY) { GnmParsePos pp_array; GnmRange *r = selection->data; parse_pos_init (&pp_array, sheet->workbook, sheet, r->start.col, r->start.row); if ((texpr = gnm_expr_parse_str (expr_txt, &pp_array, GNM_EXPR_PARSE_DEFAULT, sheet_get_conventions (sheet), NULL)) == NULL) result = WBC_EDIT_ACCEPT_RANGE; } /* We need to save the information that we will temporarily overwrite */ /* We then assign the information. No need to worry about formatting */ /* Finally we can check the validation! */ switch (result) { case (WBC_EDIT_ACCEPT_RANGE): { GSList *l; for (l = selection; l != NULL; l = l->next) { GnmRange *r = l->data; u = go_undo_combine (u, clipboard_copy_range_undo (sheet, r)); } for (l = selection; l != NULL; l = l->next) { GnmRange *r = l->data; /* We do this separately since there may be overlap between ranges */ sheet_range_set_text (&pp, r, txt); valid = gnm_validation_eval_range (wbc, sheet, &sv->edit_pos, r, showed_dialog); if (valid != GNM_VALIDATION_STATUS_VALID) break; } break; } case (WBC_EDIT_ACCEPT_ARRAY): { GnmRange *r = selection->data; u = go_undo_combine (u, clipboard_copy_range_undo (sheet, r)); if (texpr) { gnm_expr_top_ref (texpr); gnm_cell_set_array_formula (sheet, r->start.col, r->start.row, r->end.col, r->end.row, texpr); sheet_region_queue_recalc (sheet, r); } valid = gnm_validation_eval_range (wbc, sheet, &sv->edit_pos, r, showed_dialog); break; } case (WBC_EDIT_ACCEPT_WO_AC): case (WBC_EDIT_ACCEPT): { GnmRange r; GnmCell *cell; range_init_cellpos (&r, &sv->edit_pos); u = clipboard_copy_range_undo (sheet, &r); cell = sheet_cell_fetch (sheet, sv->edit_pos.col, sv->edit_pos.row); sheet_cell_set_text (cell, txt, wbcg->edit_line.markup); valid = gnm_validation_eval (wbc, mstyle, sheet, &sv->edit_pos, showed_dialog); break; } case (WBC_EDIT_REJECT): default: /* We should not be able to get here! */ break; } range_fragment_free (selection); /* We need to rebuild the original info first. */ go_undo_undo (u); g_object_unref (u); /* Now we can respond to our validation information */ if (valid != GNM_VALIDATION_STATUS_VALID) { result = WBC_EDIT_REJECT; if (valid == GNM_VALIDATION_STATUS_INVALID_EDIT) { gtk_window_set_focus (wbcg_toplevel (wbcg), (GtkWidget *) wbcg_get_entry (wbcg)); g_free (free_txt); if (texpr != NULL) gnm_expr_top_unref (texpr); return FALSE; } } else { if (result == WBC_EDIT_ACCEPT_ARRAY) { cmd_area_set_array_expr (wbc, sv, texpr); } else { PangoAttrList *res_markup = wbcg->edit_line.markup ? pango_attr_list_copy (wbcg->edit_line.markup) : NULL; if (result == WBC_EDIT_ACCEPT) cmd_set_text (wbc, sheet, &sv->edit_pos, txt, res_markup, TRUE); else if (result == WBC_EDIT_ACCEPT_WO_AC) cmd_set_text (wbc, sheet, &sv->edit_pos, txt, res_markup, FALSE); else cmd_area_set_text (wbc, sv, txt, res_markup); if (res_markup) pango_attr_list_unref (res_markup); } } if (texpr != NULL) gnm_expr_top_unref (texpr); g_free (free_txt); } else { if (sv == wb_control_cur_sheet_view (wbc)) { /* Redraw the cell contents in case there was a span */ GnmRange tmp; tmp.start = tmp.end = sv->edit_pos; sheet_range_bounding_box (sv->sheet, &tmp); sv_redraw_range (wb_control_cur_sheet_view (wbc), &tmp); } /* Reload the entry widget with the original contents */ wb_view_edit_line_set (wbv, wbc); } /* Stop editing */ wbcg->editing = FALSE; wbcg->editing_sheet = NULL; wbcg->editing_cell = NULL; if (wbcg->edit_line.guru != NULL) { GtkWidget *w = wbcg->edit_line.guru; wbc_gtk_detach_guru (wbcg); gtk_widget_destroy (w); } if (wbcg->edit_line.signal_insert) { g_signal_handler_disconnect (wbcg_get_entry (wbcg), wbcg->edit_line.signal_insert); wbcg->edit_line.signal_insert = 0; } if (wbcg->edit_line.signal_delete) { g_signal_handler_disconnect (wbcg_get_entry (wbcg), wbcg->edit_line.signal_delete); wbcg->edit_line.signal_delete = 0; } if (wbcg->edit_line.signal_cursor_pos) { g_signal_handler_disconnect (wbcg_get_entry (wbcg), wbcg->edit_line.signal_cursor_pos); wbcg->edit_line.signal_cursor_pos = 0; } if (wbcg->edit_line.signal_selection_bound) { g_signal_handler_disconnect (wbcg_get_entry (wbcg), wbcg->edit_line.signal_selection_bound); wbcg->edit_line.signal_selection_bound = 0; } if (wbcg->edit_line.cell_attrs != NULL) { pango_attr_list_unref (wbcg->edit_line.cell_attrs); wbcg->edit_line.cell_attrs = NULL; } if (wbcg->edit_line.markup) { pango_attr_list_unref (wbcg->edit_line.markup); wbcg->edit_line.markup = NULL; } if (wbcg->edit_line.full_content != NULL) { pango_attr_list_unref (wbcg->edit_line.full_content); wbcg->edit_line.full_content = NULL; } if (wbcg->edit_line.cur_fmt) { pango_attr_list_unref (wbcg->edit_line.cur_fmt); wbcg->edit_line.cur_fmt = NULL; } /* set pos to 0, to ensure that if we start editing by clicking on the * editline at the last position, we'll get the right style feedback */ gtk_editable_set_position ((GtkEditable *) wbcg_get_entry (wbcg), 0); wb_control_update_action_sensitivity (wbc); if (!sheet->workbook->during_destruction) { /* restore focus to original sheet in case things were being selected * on a different page. Do no go through the view, rangesel is * specific to the control. */ wb_control_sheet_focus (wbc, sheet); /* Only the edit sheet has an edit cursor */ scg_edit_stop (wbcg_cur_scg (wbcg)); } wbcg_auto_complete_destroy (wbcg); wb_control_style_feedback (wbc, NULL); /* in case markup messed with things */ return TRUE; }