This repository has been archived on 2023-10-09. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
blender-archive/source/blender/editors/space_text/text_ops.c
Bastien Montagne 69ba3b98e4 Fix new Text ID usercount handling in add/load cases.
Text datablocks should always have a 'single user' flag set, and they
usually do not have any user (since neither text editor itself, nor
Freestyle usage are text users - the second is odd btw...), the only one
am aware of is the script node (e.g. for OSL).

Add text case was simply not doing anything, so added.

Load text case was doing things in inversed logic (setting user count to
zero in BKE, then setting 'real user' flag in ED code). Made it the
other way around (BKE ID creation code should not care about usercount
usually, this is up to higher-level code to decide what to do
(operators, RNA...).

Note: tried to check all cases, but there might very well be some more
hidden bugs here...
2019-07-19 13:58:26 +02:00

3607 lines
85 KiB
C

/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* The Original Code is Copyright (C) 2001-2002 by NaN Holding BV.
* All rights reserved.
*/
/** \file
* \ingroup sptext
*/
#include <string.h>
#include <errno.h>
#include "MEM_guardedalloc.h"
#include "DNA_text_types.h"
#include "BLI_blenlib.h"
#include "BLT_translation.h"
#include "PIL_time.h"
#include "BKE_context.h"
#include "BKE_library.h"
#include "BKE_main.h"
#include "BKE_report.h"
#include "BKE_text.h"
#include "WM_api.h"
#include "WM_types.h"
#include "ED_text.h"
#include "ED_curve.h"
#include "ED_screen.h"
#include "UI_interface.h"
#include "UI_resources.h"
#include "RNA_access.h"
#include "RNA_define.h"
#ifdef WITH_PYTHON
# include "BPY_extern.h"
#endif
#include "text_intern.h"
#include "text_format.h"
static void txt_screen_clamp(SpaceText *st, ARegion *ar);
/************************ util ***************************/
/**
* Tests if the given character represents a start of a new line or the
* indentation part of a line.
* \param c: The current character.
* \param r_last_state: A pointer to a flag representing the last state. The
* flag may be modified.
*/
static void test_line_start(char c, bool *r_last_state)
{
if (c == '\n') {
*r_last_state = true;
}
else if (!ELEM(c, '\t', ' ')) {
*r_last_state = false;
}
}
/**
* This function converts the indentation tabs from a buffer to spaces.
* \param buf: A pointer to a cstring.
* \param tab_size: The size, in spaces, of the tab character.
*/
static char *buf_tabs_to_spaces(const char *in_buf, const int tab_size)
{
/* Get the number of tab characters in buffer. */
bool line_start = true;
int num_tabs = 0;
for (int in_offset = 0; in_buf[in_offset]; in_offset++) {
/* Verify if is an indentation whitespace character. */
test_line_start(in_buf[in_offset], &line_start);
if (in_buf[in_offset] == '\t' && line_start) {
num_tabs++;
}
}
/* Allocate output before with extra space for expanded tabs. */
const int out_size = strlen(in_buf) + num_tabs * (tab_size - 1) + 1;
char *out_buf = MEM_mallocN(out_size * sizeof(char), __func__);
/* Fill output buffer. */
int spaces_until_tab = 0;
int out_offset = 0;
line_start = true;
for (int in_offset = 0; in_buf[in_offset]; in_offset++) {
/* Verify if is an indentation whitespace character. */
test_line_start(in_buf[in_offset], &line_start);
if (in_buf[in_offset] == '\t' && line_start) {
/* Calculate tab size so it fills until next indentation. */
int num_spaces = tab_size - (spaces_until_tab % tab_size);
spaces_until_tab = 0;
/* Write to buffer. */
memset(&out_buf[out_offset], ' ', num_spaces);
out_offset += num_spaces;
}
else {
if (in_buf[in_offset] == ' ') {
spaces_until_tab++;
}
else if (in_buf[in_offset] == '\n') {
spaces_until_tab = 0;
}
out_buf[out_offset++] = in_buf[in_offset];
}
}
out_buf[out_offset] = '\0';
return out_buf;
}
/************************ poll ***************************/
BLI_INLINE int text_pixel_x_to_column(SpaceText *st, const int x)
{
/* add half the char width so mouse cursor selection is inbetween letters */
return (x + (st->cwidth / 2)) / st->cwidth;
}
static bool text_new_poll(bContext *UNUSED(C))
{
return 1;
}
static bool text_edit_poll(bContext *C)
{
Text *text = CTX_data_edit_text(C);
if (!text) {
return 0;
}
if (ID_IS_LINKED(text)) {
// BKE_report(op->reports, RPT_ERROR, "Cannot edit external library data");
return 0;
}
return 1;
}
bool text_space_edit_poll(bContext *C)
{
SpaceText *st = CTX_wm_space_text(C);
Text *text = CTX_data_edit_text(C);
if (!st || !text) {
return 0;
}
if (ID_IS_LINKED(text)) {
// BKE_report(op->reports, RPT_ERROR, "Cannot edit external library data");
return 0;
}
return 1;
}
static bool text_region_edit_poll(bContext *C)
{
SpaceText *st = CTX_wm_space_text(C);
Text *text = CTX_data_edit_text(C);
ARegion *ar = CTX_wm_region(C);
if (!st || !text) {
return 0;
}
if (!ar || ar->regiontype != RGN_TYPE_WINDOW) {
return 0;
}
if (ID_IS_LINKED(text)) {
// BKE_report(op->reports, RPT_ERROR, "Cannot edit external library data");
return 0;
}
return 1;
}
/********************** updates *********************/
void text_update_line_edited(TextLine *line)
{
if (!line) {
return;
}
/* we just free format here, and let it rebuild during draw */
if (line->format) {
MEM_freeN(line->format);
line->format = NULL;
}
}
void text_update_edited(Text *text)
{
TextLine *line;
for (line = text->lines.first; line; line = line->next) {
text_update_line_edited(line);
}
}
/******************* new operator *********************/
static int text_new_exec(bContext *C, wmOperator *UNUSED(op))
{
SpaceText *st = CTX_wm_space_text(C);
Main *bmain = CTX_data_main(C);
Text *text;
PointerRNA ptr, idptr;
PropertyRNA *prop;
text = BKE_text_add(bmain, "Text");
/* Texts have no user by default... Only the 'real' user flag. */
id_us_min(&text->id);
/* hook into UI */
UI_context_active_but_prop_get_templateID(C, &ptr, &prop);
if (prop) {
RNA_id_pointer_create(&text->id, &idptr);
RNA_property_pointer_set(&ptr, prop, idptr, NULL);
RNA_property_update(C, &ptr, prop);
}
else if (st) {
st->text = text;
st->left = 0;
st->top = 0;
st->scroll_accum[0] = 0.0f;
st->scroll_accum[1] = 0.0f;
text_drawcache_tag_update(st, 1);
}
WM_event_add_notifier(C, NC_TEXT | NA_ADDED, text);
return OPERATOR_FINISHED;
}
void TEXT_OT_new(wmOperatorType *ot)
{
/* identifiers */
ot->name = "New Text";
ot->idname = "TEXT_OT_new";
ot->description = "Create a new text data-block";
/* api callbacks */
ot->exec = text_new_exec;
ot->poll = text_new_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
}
/******************* open operator *********************/
static void text_open_init(bContext *C, wmOperator *op)
{
PropertyPointerRNA *pprop;
op->customdata = pprop = MEM_callocN(sizeof(PropertyPointerRNA), "OpenPropertyPointerRNA");
UI_context_active_but_prop_get_templateID(C, &pprop->ptr, &pprop->prop);
}
static void text_open_cancel(bContext *UNUSED(C), wmOperator *op)
{
MEM_freeN(op->customdata);
}
static int text_open_exec(bContext *C, wmOperator *op)
{
SpaceText *st = CTX_wm_space_text(C);
Main *bmain = CTX_data_main(C);
Text *text;
PropertyPointerRNA *pprop;
PointerRNA idptr;
char str[FILE_MAX];
const bool internal = RNA_boolean_get(op->ptr, "internal");
RNA_string_get(op->ptr, "filepath", str);
text = BKE_text_load_ex(bmain, str, BKE_main_blendfile_path(bmain), internal);
/* Texts have no user by default... Only the 'real' user flag. */
id_us_min(&text->id);
if (!text) {
if (op->customdata) {
MEM_freeN(op->customdata);
}
return OPERATOR_CANCELLED;
}
if (!op->customdata) {
text_open_init(C, op);
}
/* hook into UI */
pprop = op->customdata;
if (pprop->prop) {
RNA_id_pointer_create(&text->id, &idptr);
RNA_property_pointer_set(&pprop->ptr, pprop->prop, idptr, NULL);
RNA_property_update(C, &pprop->ptr, pprop->prop);
}
else if (st) {
st->text = text;
st->left = 0;
st->top = 0;
st->scroll_accum[0] = 0.0f;
st->scroll_accum[1] = 0.0f;
}
text_drawcache_tag_update(st, 1);
WM_event_add_notifier(C, NC_TEXT | NA_ADDED, text);
MEM_freeN(op->customdata);
return OPERATOR_FINISHED;
}
static int text_open_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
Main *bmain = CTX_data_main(C);
Text *text = CTX_data_edit_text(C);
const char *path = (text && text->name) ? text->name : BKE_main_blendfile_path(bmain);
if (RNA_struct_property_is_set(op->ptr, "filepath")) {
return text_open_exec(C, op);
}
text_open_init(C, op);
RNA_string_set(op->ptr, "filepath", path);
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
void TEXT_OT_open(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Open Text";
ot->idname = "TEXT_OT_open";
ot->description = "Open a new text data-block";
/* api callbacks */
ot->exec = text_open_exec;
ot->invoke = text_open_invoke;
ot->cancel = text_open_cancel;
ot->poll = text_new_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
/* properties */
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER | FILE_TYPE_TEXT | FILE_TYPE_PYSCRIPT,
FILE_SPECIAL,
FILE_OPENFILE,
WM_FILESEL_FILEPATH,
FILE_DEFAULTDISPLAY,
FILE_SORT_ALPHA); // XXX TODO, relative_path
RNA_def_boolean(
ot->srna, "internal", 0, "Make internal", "Make text file internal after loading");
}
/******************* reload operator *********************/
static int text_reload_exec(bContext *C, wmOperator *op)
{
SpaceText *st = CTX_wm_space_text(C);
Text *text = CTX_data_edit_text(C);
ARegion *ar = CTX_wm_region(C);
/* store view & cursor state */
const int orig_top = st->top;
const int orig_curl = BLI_findindex(&text->lines, text->curl);
const int orig_curc = text->curc;
if (!BKE_text_reload(text)) {
BKE_report(op->reports, RPT_ERROR, "Could not reopen file");
return OPERATOR_CANCELLED;
}
#ifdef WITH_PYTHON
if (text->compiled) {
BPY_text_free_code(text);
}
#endif
text_update_edited(text);
text_update_cursor_moved(C);
text_drawcache_tag_update(CTX_wm_space_text(C), 1);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
/* return to scroll position */
st->top = orig_top;
txt_screen_clamp(st, ar);
/* return cursor */
txt_move_to(text, orig_curl, orig_curc, false);
return OPERATOR_FINISHED;
}
void TEXT_OT_reload(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Reload";
ot->idname = "TEXT_OT_reload";
ot->description = "Reload active text data-block from its file";
/* api callbacks */
ot->exec = text_reload_exec;
ot->invoke = WM_operator_confirm;
ot->poll = text_edit_poll;
}
/******************* delete operator *********************/
static bool text_unlink_poll(bContext *C)
{
/* it should be possible to unlink texts if they're lib-linked in... */
return CTX_data_edit_text(C) != NULL;
}
static int text_unlink_exec(bContext *C, wmOperator *UNUSED(op))
{
Main *bmain = CTX_data_main(C);
SpaceText *st = CTX_wm_space_text(C);
Text *text = CTX_data_edit_text(C);
/* make the previous text active, if its not there make the next text active */
if (st) {
if (text->id.prev) {
st->text = text->id.prev;
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
}
else if (text->id.next) {
st->text = text->id.next;
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
}
}
BKE_id_delete(bmain, text);
text_drawcache_tag_update(st, 1);
WM_event_add_notifier(C, NC_TEXT | NA_REMOVED, NULL);
return OPERATOR_FINISHED;
}
void TEXT_OT_unlink(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Unlink";
ot->idname = "TEXT_OT_unlink";
ot->description = "Unlink active text data-block";
/* api callbacks */
ot->exec = text_unlink_exec;
ot->invoke = WM_operator_confirm;
ot->poll = text_unlink_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
}
/******************* make internal operator *********************/
static int text_make_internal_exec(bContext *C, wmOperator *UNUSED(op))
{
Text *text = CTX_data_edit_text(C);
text->flags |= TXT_ISMEM | TXT_ISDIRTY;
if (text->name) {
MEM_freeN(text->name);
text->name = NULL;
}
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
return OPERATOR_FINISHED;
}
void TEXT_OT_make_internal(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Make Internal";
ot->idname = "TEXT_OT_make_internal";
ot->description = "Make active text file internal";
/* api callbacks */
ot->exec = text_make_internal_exec;
ot->poll = text_edit_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
}
/******************* save operator *********************/
static bool text_save_poll(bContext *C)
{
Text *text = CTX_data_edit_text(C);
if (!text_edit_poll(C)) {
return 0;
}
return (text->name != NULL && !(text->flags & TXT_ISMEM));
}
static void txt_write_file(Main *bmain, Text *text, ReportList *reports)
{
FILE *fp;
TextLine *tmp;
BLI_stat_t st;
char filepath[FILE_MAX];
BLI_strncpy(filepath, text->name, FILE_MAX);
BLI_path_abs(filepath, BKE_main_blendfile_path(bmain));
fp = BLI_fopen(filepath, "w");
if (fp == NULL) {
BKE_reportf(reports,
RPT_ERROR,
"Unable to save '%s': %s",
filepath,
errno ? strerror(errno) : TIP_("unknown error writing file"));
return;
}
for (tmp = text->lines.first; tmp; tmp = tmp->next) {
fputs(tmp->line, fp);
if (tmp->next) {
fputc('\n', fp);
}
}
fclose(fp);
if (BLI_stat(filepath, &st) == 0) {
text->mtime = st.st_mtime;
/* report since this can be called from key-shortcuts */
BKE_reportf(reports, RPT_INFO, "Saved Text '%s'", filepath);
}
else {
text->mtime = 0;
BKE_reportf(reports,
RPT_WARNING,
"Unable to stat '%s': %s",
filepath,
errno ? strerror(errno) : TIP_("unknown error stating file"));
}
text->flags &= ~TXT_ISDIRTY;
}
static int text_save_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Text *text = CTX_data_edit_text(C);
txt_write_file(bmain, text, op->reports);
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
return OPERATOR_FINISHED;
}
void TEXT_OT_save(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Save";
ot->idname = "TEXT_OT_save";
ot->description = "Save active text data-block";
/* api callbacks */
ot->exec = text_save_exec;
ot->poll = text_save_poll;
}
/******************* save as operator *********************/
static int text_save_as_exec(bContext *C, wmOperator *op)
{
Main *bmain = CTX_data_main(C);
Text *text = CTX_data_edit_text(C);
char str[FILE_MAX];
if (!text) {
return OPERATOR_CANCELLED;
}
RNA_string_get(op->ptr, "filepath", str);
if (text->name) {
MEM_freeN(text->name);
}
text->name = BLI_strdup(str);
text->flags &= ~TXT_ISMEM;
txt_write_file(bmain, text, op->reports);
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
return OPERATOR_FINISHED;
}
static int text_save_as_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
Main *bmain = CTX_data_main(C);
Text *text = CTX_data_edit_text(C);
const char *str;
if (RNA_struct_property_is_set(op->ptr, "filepath")) {
return text_save_as_exec(C, op);
}
if (text->name) {
str = text->name;
}
else if (text->flags & TXT_ISMEM) {
str = text->id.name + 2;
}
else {
str = BKE_main_blendfile_path(bmain);
}
RNA_string_set(op->ptr, "filepath", str);
WM_event_add_fileselect(C, op);
return OPERATOR_RUNNING_MODAL;
}
void TEXT_OT_save_as(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Save As";
ot->idname = "TEXT_OT_save_as";
ot->description = "Save active text file with options";
/* api callbacks */
ot->exec = text_save_as_exec;
ot->invoke = text_save_as_invoke;
ot->poll = text_edit_poll;
/* properties */
WM_operator_properties_filesel(ot,
FILE_TYPE_FOLDER | FILE_TYPE_TEXT | FILE_TYPE_PYSCRIPT,
FILE_SPECIAL,
FILE_SAVE,
WM_FILESEL_FILEPATH,
FILE_DEFAULTDISPLAY,
FILE_SORT_ALPHA); // XXX TODO, relative_path
}
/******************* run script operator *********************/
static bool text_run_script_poll(bContext *C)
{
return (CTX_data_edit_text(C) != NULL);
}
static int text_run_script(bContext *C, ReportList *reports)
{
#ifdef WITH_PYTHON
Text *text = CTX_data_edit_text(C);
const bool is_live = (reports == NULL);
/* only for comparison */
void *curl_prev = text->curl;
int curc_prev = text->curc;
if (BPY_execute_text(C, text, reports, !is_live)) {
if (is_live) {
/* for nice live updates */
WM_event_add_notifier(C, NC_WINDOW | NA_EDITED, NULL);
}
return OPERATOR_FINISHED;
}
/* Don't report error messages while live editing */
if (!is_live) {
/* text may have freed its self */
if (CTX_data_edit_text(C) == text) {
if (text->curl != curl_prev || curc_prev != text->curc) {
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
}
}
BKE_report(
reports, RPT_ERROR, "Python script failed, check the message in the system console");
return OPERATOR_FINISHED;
}
#else
(void)C;
(void)reports;
#endif /* !WITH_PYTHON */
return OPERATOR_CANCELLED;
}
static int text_run_script_exec(bContext *C, wmOperator *op)
{
#ifndef WITH_PYTHON
(void)C; /* unused */
BKE_report(op->reports, RPT_ERROR, "Python disabled in this build");
return OPERATOR_CANCELLED;
#else
return text_run_script(C, op->reports);
#endif
}
void TEXT_OT_run_script(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Run Script";
ot->idname = "TEXT_OT_run_script";
ot->description = "Run active script";
/* api callbacks */
ot->poll = text_run_script_poll;
ot->exec = text_run_script_exec;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
}
/******************* refresh pyconstraints operator *********************/
static int text_refresh_pyconstraints_exec(bContext *UNUSED(C), wmOperator *UNUSED(op))
{
#ifdef WITH_PYTHON
# if 0
Text *text = CTX_data_edit_text(C);
Object *ob;
bConstraint *con;
short update;
/* check all pyconstraints */
for (ob = CTX_data_main(C)->objects.first; ob; ob = ob->id.next) {
update = 0;
if (ob->type == OB_ARMATURE && ob->pose) {
bPoseChannel *pchan;
for (pchan = ob->pose->chanbase.first; pchan; pchan = pchan->next) {
for (con = pchan->constraints.first; con; con = con->next) {
if (con->type == CONSTRAINT_TYPE_PYTHON) {
bPythonConstraint *data = con->data;
if (data->text == text) {
BPY_pyconstraint_update(ob, con);
}
update = 1;
}
}
}
}
for (con = ob->constraints.first; con; con = con->next) {
if (con->type == CONSTRAINT_TYPE_PYTHON) {
bPythonConstraint *data = con->data;
if (data->text == text) {
BPY_pyconstraint_update(ob, con);
}
update = 1;
}
}
if (update) {
DEG_id_tag_update(&ob->id, ID_RECALC_GEOMETRY);
}
}
# endif
#endif
return OPERATOR_FINISHED;
}
void TEXT_OT_refresh_pyconstraints(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Refresh PyConstraints";
ot->idname = "TEXT_OT_refresh_pyconstraints";
ot->description = "Refresh all pyconstraints";
/* api callbacks */
ot->exec = text_refresh_pyconstraints_exec;
ot->poll = text_edit_poll;
}
/******************* paste operator *********************/
static int text_paste_exec(bContext *C, wmOperator *op)
{
const bool selection = RNA_boolean_get(op->ptr, "selection");
Text *text = CTX_data_edit_text(C);
char *buf;
int buf_len;
buf = WM_clipboard_text_get(selection, &buf_len);
if (!buf) {
return OPERATOR_CANCELLED;
}
text_drawcache_tag_update(CTX_wm_space_text(C), 0);
ED_text_undo_push_init(C);
/* Convert clipboard content indentation to spaces if specified */
if (text->flags & TXT_TABSTOSPACES) {
char *new_buf = buf_tabs_to_spaces(buf, TXT_TABSIZE);
MEM_freeN(buf);
buf = new_buf;
}
txt_insert_buf(text, buf);
text_update_edited(text);
MEM_freeN(buf);
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
/* run the script while editing, evil but useful */
if (CTX_wm_space_text(C)->live_edit) {
text_run_script(C, NULL);
}
return OPERATOR_FINISHED;
}
void TEXT_OT_paste(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Paste";
ot->idname = "TEXT_OT_paste";
ot->description = "Paste text from clipboard";
/* api callbacks */
ot->exec = text_paste_exec;
ot->poll = text_edit_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
/* properties */
RNA_def_boolean(ot->srna,
"selection",
0,
"Selection",
"Paste text selected elsewhere rather than copied (X11 only)");
}
/**************** duplicate operator *******************/
static int text_duplicate_line_exec(bContext *C, wmOperator *UNUSED(op))
{
Text *text = CTX_data_edit_text(C);
ED_text_undo_push_init(C);
txt_duplicate_line(text);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
/* run the script while editing, evil but useful */
if (CTX_wm_space_text(C)->live_edit) {
text_run_script(C, NULL);
}
return OPERATOR_FINISHED;
}
void TEXT_OT_duplicate_line(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Duplicate Line";
ot->idname = "TEXT_OT_duplicate_line";
ot->description = "Duplicate the current line";
/* api callbacks */
ot->exec = text_duplicate_line_exec;
ot->poll = text_edit_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
}
/******************* copy operator *********************/
static void txt_copy_clipboard(Text *text)
{
char *buf;
if (!txt_has_sel(text)) {
return;
}
buf = txt_sel_to_buf(text, NULL);
if (buf) {
WM_clipboard_text_set(buf, 0);
MEM_freeN(buf);
}
}
static int text_copy_exec(bContext *C, wmOperator *UNUSED(op))
{
Text *text = CTX_data_edit_text(C);
txt_copy_clipboard(text);
return OPERATOR_FINISHED;
}
void TEXT_OT_copy(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Copy";
ot->idname = "TEXT_OT_copy";
ot->description = "Copy selected text to clipboard";
/* api callbacks */
ot->exec = text_copy_exec;
ot->poll = text_edit_poll;
}
/******************* cut operator *********************/
static int text_cut_exec(bContext *C, wmOperator *UNUSED(op))
{
Text *text = CTX_data_edit_text(C);
text_drawcache_tag_update(CTX_wm_space_text(C), 0);
txt_copy_clipboard(text);
ED_text_undo_push_init(C);
txt_delete_selected(text);
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
/* run the script while editing, evil but useful */
if (CTX_wm_space_text(C)->live_edit) {
text_run_script(C, NULL);
}
return OPERATOR_FINISHED;
}
void TEXT_OT_cut(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Cut";
ot->idname = "TEXT_OT_cut";
ot->description = "Cut selected text to clipboard";
/* api callbacks */
ot->exec = text_cut_exec;
ot->poll = text_edit_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
}
/******************* indent operator *********************/
static int text_indent_exec(bContext *C, wmOperator *UNUSED(op))
{
Text *text = CTX_data_edit_text(C);
text_drawcache_tag_update(CTX_wm_space_text(C), 0);
ED_text_undo_push_init(C);
if (txt_has_sel(text)) {
txt_order_cursors(text, false);
txt_indent(text);
}
else {
txt_add_char(text, '\t');
}
text_update_edited(text);
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
return OPERATOR_FINISHED;
}
void TEXT_OT_indent(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Indent";
ot->idname = "TEXT_OT_indent";
ot->description = "Indent selected text";
/* api callbacks */
ot->exec = text_indent_exec;
ot->poll = text_edit_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
}
/******************* unindent operator *********************/
static int text_unindent_exec(bContext *C, wmOperator *UNUSED(op))
{
Text *text = CTX_data_edit_text(C);
text_drawcache_tag_update(CTX_wm_space_text(C), 0);
ED_text_undo_push_init(C);
txt_order_cursors(text, false);
txt_unindent(text);
text_update_edited(text);
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
return OPERATOR_FINISHED;
}
void TEXT_OT_unindent(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Unindent";
ot->idname = "TEXT_OT_unindent";
ot->description = "Unindent selected text";
/* api callbacks */
ot->exec = text_unindent_exec;
ot->poll = text_edit_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
}
/******************* line break operator *********************/
static int text_line_break_exec(bContext *C, wmOperator *UNUSED(op))
{
SpaceText *st = CTX_wm_space_text(C);
Text *text = CTX_data_edit_text(C);
int a, curts;
int space = (text->flags & TXT_TABSTOSPACES) ? st->tabnumber : 1;
text_drawcache_tag_update(st, 0);
// double check tabs/spaces before splitting the line
curts = txt_setcurr_tab_spaces(text, space);
ED_text_undo_push_init(C);
txt_split_curline(text);
for (a = 0; a < curts; a++) {
if (text->flags & TXT_TABSTOSPACES) {
txt_add_char(text, ' ');
}
else {
txt_add_char(text, '\t');
}
}
if (text->curl) {
if (text->curl->prev) {
text_update_line_edited(text->curl->prev);
}
text_update_line_edited(text->curl);
}
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
return OPERATOR_FINISHED;
}
void TEXT_OT_line_break(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Line Break";
ot->idname = "TEXT_OT_line_break";
ot->description = "Insert line break at cursor position";
/* api callbacks */
ot->exec = text_line_break_exec;
ot->poll = text_edit_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
}
/******************* comment operator *********************/
static int text_comment_exec(bContext *C, wmOperator *UNUSED(op))
{
Text *text = CTX_data_edit_text(C);
if (txt_has_sel(text)) {
text_drawcache_tag_update(CTX_wm_space_text(C), 0);
ED_text_undo_push_init(C);
txt_order_cursors(text, false);
txt_comment(text);
text_update_edited(text);
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
}
void TEXT_OT_comment(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Comment";
ot->idname = "TEXT_OT_comment";
ot->description = "Convert selected text to comment";
/* api callbacks */
ot->exec = text_comment_exec;
ot->poll = text_edit_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
}
/******************* uncomment operator *********************/
static int text_uncomment_exec(bContext *C, wmOperator *UNUSED(op))
{
Text *text = CTX_data_edit_text(C);
if (txt_has_sel(text)) {
text_drawcache_tag_update(CTX_wm_space_text(C), 0);
ED_text_undo_push_init(C);
txt_order_cursors(text, false);
txt_uncomment(text);
text_update_edited(text);
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
}
void TEXT_OT_uncomment(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Uncomment";
ot->idname = "TEXT_OT_uncomment";
ot->description = "Convert selected comment to text";
/* api callbacks */
ot->exec = text_uncomment_exec;
ot->poll = text_edit_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
}
/******************* convert whitespace operator *********************/
enum { TO_SPACES, TO_TABS };
static const EnumPropertyItem whitespace_type_items[] = {
{TO_SPACES, "SPACES", 0, "To Spaces", NULL},
{TO_TABS, "TABS", 0, "To Tabs", NULL},
{0, NULL, 0, NULL, NULL},
};
static int text_convert_whitespace_exec(bContext *C, wmOperator *op)
{
SpaceText *st = CTX_wm_space_text(C);
Text *text = CTX_data_edit_text(C);
TextLine *tmp;
FlattenString fs;
size_t a, j, max_len = 0;
int type = RNA_enum_get(op->ptr, "type");
/* first convert to all space, this make it a lot easier to convert to tabs
* because there is no mixtures of ' ' && '\t' */
for (tmp = text->lines.first; tmp; tmp = tmp->next) {
char *new_line;
BLI_assert(tmp->line);
flatten_string(st, &fs, tmp->line);
new_line = BLI_strdup(fs.buf);
flatten_string_free(&fs);
MEM_freeN(tmp->line);
if (tmp->format) {
MEM_freeN(tmp->format);
}
/* Put new_line in the tmp->line spot still need to try and set the curc correctly. */
tmp->line = new_line;
tmp->len = strlen(new_line);
tmp->format = NULL;
if (tmp->len > max_len) {
max_len = tmp->len;
}
}
if (type == TO_TABS) {
char *tmp_line = MEM_mallocN(sizeof(*tmp_line) * (max_len + 1), __func__);
for (tmp = text->lines.first; tmp; tmp = tmp->next) {
const char *text_check_line = tmp->line;
const int text_check_line_len = tmp->len;
char *tmp_line_cur = tmp_line;
const size_t tab_len = st->tabnumber;
BLI_assert(text_check_line);
for (a = 0; a < text_check_line_len;) {
/* A tab can only start at a position multiple of tab_len... */
if (!(a % tab_len) && (text_check_line[a] == ' ')) {
/* a + 0 we already know to be ' ' char... */
for (j = 1;
(j < tab_len) && (a + j < text_check_line_len) && (text_check_line[a + j] == ' ');
j++) {
/* pass */
}
if (j == tab_len) {
/* We found a set of spaces that can be replaced by a tab... */
if ((tmp_line_cur == tmp_line) && a != 0) {
/* Copy all 'valid' string already 'parsed'... */
memcpy(tmp_line_cur, text_check_line, a);
tmp_line_cur += a;
}
*tmp_line_cur = '\t';
tmp_line_cur++;
a += j;
}
else {
if (tmp_line_cur != tmp_line) {
memcpy(tmp_line_cur, &text_check_line[a], j);
tmp_line_cur += j;
}
a += j;
}
}
else {
size_t len = BLI_str_utf8_size_safe(&text_check_line[a]);
if (tmp_line_cur != tmp_line) {
memcpy(tmp_line_cur, &text_check_line[a], len);
tmp_line_cur += len;
}
a += len;
}
}
if (tmp_line_cur != tmp_line) {
*tmp_line_cur = '\0';
#ifndef NDEBUG
BLI_assert(tmp_line_cur - tmp_line <= max_len);
flatten_string(st, &fs, tmp_line);
BLI_assert(STREQ(fs.buf, tmp->line));
flatten_string_free(&fs);
#endif
MEM_freeN(tmp->line);
if (tmp->format) {
MEM_freeN(tmp->format);
}
/* Put new_line in the tmp->line spot
* still need to try and set the curc correctly. */
tmp->line = BLI_strdup(tmp_line);
tmp->len = strlen(tmp_line);
tmp->format = NULL;
}
}
MEM_freeN(tmp_line);
}
text_update_edited(text);
text_update_cursor_moved(C);
text_drawcache_tag_update(st, 1);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
return OPERATOR_FINISHED;
}
void TEXT_OT_convert_whitespace(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Convert Whitespace";
ot->idname = "TEXT_OT_convert_whitespace";
ot->description = "Convert whitespaces by type";
/* api callbacks */
ot->exec = text_convert_whitespace_exec;
ot->poll = text_edit_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
/* properties */
RNA_def_enum(ot->srna,
"type",
whitespace_type_items,
TO_SPACES,
"Type",
"Type of whitespace to convert to");
}
/******************* select all operator *********************/
static int text_select_all_exec(bContext *C, wmOperator *UNUSED(op))
{
Text *text = CTX_data_edit_text(C);
txt_sel_all(text);
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
return OPERATOR_FINISHED;
}
void TEXT_OT_select_all(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select All";
ot->idname = "TEXT_OT_select_all";
ot->description = "Select all text";
/* api callbacks */
ot->exec = text_select_all_exec;
ot->poll = text_edit_poll;
}
/******************* select line operator *********************/
static int text_select_line_exec(bContext *C, wmOperator *UNUSED(op))
{
Text *text = CTX_data_edit_text(C);
txt_sel_line(text);
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
return OPERATOR_FINISHED;
}
void TEXT_OT_select_line(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Line";
ot->idname = "TEXT_OT_select_line";
ot->description = "Select text by line";
/* api callbacks */
ot->exec = text_select_line_exec;
ot->poll = text_edit_poll;
}
/******************* select word operator *********************/
static int text_select_word_exec(bContext *C, wmOperator *UNUSED(op))
{
Text *text = CTX_data_edit_text(C);
/* don't advance cursor before stepping */
const bool use_init_step = false;
txt_jump_left(text, false, use_init_step);
txt_jump_right(text, true, use_init_step);
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
return OPERATOR_FINISHED;
}
void TEXT_OT_select_word(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Select Word";
ot->idname = "TEXT_OT_select_word";
ot->description = "Select word under cursor";
/* api callbacks */
ot->exec = text_select_word_exec;
ot->poll = text_edit_poll;
}
/********************* move lines operators ***********************/
static int move_lines_exec(bContext *C, wmOperator *op)
{
Text *text = CTX_data_edit_text(C);
const int direction = RNA_enum_get(op->ptr, "direction");
ED_text_undo_push_init(C);
txt_move_lines(text, direction);
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
/* run the script while editing, evil but useful */
if (CTX_wm_space_text(C)->live_edit) {
text_run_script(C, NULL);
}
return OPERATOR_FINISHED;
}
void TEXT_OT_move_lines(wmOperatorType *ot)
{
static const EnumPropertyItem direction_items[] = {
{TXT_MOVE_LINE_UP, "UP", 0, "Up", ""},
{TXT_MOVE_LINE_DOWN, "DOWN", 0, "Down", ""},
{0, NULL, 0, NULL, NULL},
};
/* identifiers */
ot->name = "Move Lines";
ot->idname = "TEXT_OT_move_lines";
ot->description = "Move the currently selected line(s) up/down";
/* api callbacks */
ot->exec = move_lines_exec;
ot->poll = text_edit_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
/* properties */
RNA_def_enum(ot->srna, "direction", direction_items, 1, "Direction", "");
}
/************************ move operator ************************/
static const EnumPropertyItem move_type_items[] = {
{LINE_BEGIN, "LINE_BEGIN", 0, "Line Begin", ""},
{LINE_END, "LINE_END", 0, "Line End", ""},
{FILE_TOP, "FILE_TOP", 0, "File Top", ""},
{FILE_BOTTOM, "FILE_BOTTOM", 0, "File Bottom", ""},
{PREV_CHAR, "PREVIOUS_CHARACTER", 0, "Previous Character", ""},
{NEXT_CHAR, "NEXT_CHARACTER", 0, "Next Character", ""},
{PREV_WORD, "PREVIOUS_WORD", 0, "Previous Word", ""},
{NEXT_WORD, "NEXT_WORD", 0, "Next Word", ""},
{PREV_LINE, "PREVIOUS_LINE", 0, "Previous Line", ""},
{NEXT_LINE, "NEXT_LINE", 0, "Next Line", ""},
{PREV_PAGE, "PREVIOUS_PAGE", 0, "Previous Page", ""},
{NEXT_PAGE, "NEXT_PAGE", 0, "Next Page", ""},
{0, NULL, 0, NULL, NULL},
};
/* get cursor position in line by relative wrapped line and column positions */
static int text_get_cursor_rel(SpaceText *st, ARegion *ar, TextLine *linein, int rell, int relc)
{
int i, j, start, end, max, chop, curs, loop, endj, found, selc;
char ch;
max = wrap_width(st, ar);
selc = start = endj = curs = found = 0;
end = max;
chop = loop = 1;
for (i = 0, j = 0; loop; j += BLI_str_utf8_size_safe(linein->line + j)) {
int chars;
int columns = BLI_str_utf8_char_width_safe(linein->line + j); /* = 1 for tab */
/* Mimic replacement of tabs */
ch = linein->line[j];
if (ch == '\t') {
chars = st->tabnumber - i % st->tabnumber;
ch = ' ';
}
else {
chars = 1;
}
while (chars--) {
if (rell == 0 && i - start <= relc && i + columns - start > relc) {
/* current position could be wrapped to next line */
/* this should be checked when end of current line would be reached */
selc = j;
found = 1;
}
else if (i - end <= relc && i + columns - end > relc) {
curs = j;
}
if (i + columns - start > max) {
end = MIN2(end, i);
if (found) {
/* exact cursor position was found, check if it's */
/* still on needed line (hasn't been wrapped) */
if (selc > endj && !chop) {
selc = endj;
}
loop = 0;
break;
}
if (chop) {
endj = j;
}
start = end;
end += max;
chop = 1;
rell--;
if (rell == 0 && i + columns - start > relc) {
selc = curs;
loop = 0;
break;
}
}
else if (ch == '\0') {
if (!found) {
selc = linein->len;
}
loop = 0;
break;
}
else if (ch == ' ' || ch == '-') {
if (found) {
loop = 0;
break;
}
if (rell == 0 && i + columns - start > relc) {
selc = curs;
loop = 0;
break;
}
end = i + 1;
endj = j;
chop = 0;
}
i += columns;
}
}
return selc;
}
static int cursor_skip_find_line(
SpaceText *st, ARegion *ar, int lines, TextLine **linep, int *charp, int *rell, int *relc)
{
int offl, offc, visible_lines;
wrap_offset_in_line(st, ar, *linep, *charp, &offl, &offc);
*relc = text_get_char_pos(st, (*linep)->line, *charp) + offc;
*rell = lines;
/* handle current line */
if (lines > 0) {
visible_lines = text_get_visible_lines(st, ar, (*linep)->line);
if (*rell - visible_lines + offl >= 0) {
if (!(*linep)->next) {
if (offl < visible_lines - 1) {
*rell = visible_lines - 1;
return 1;
}
*charp = (*linep)->len;
return 0;
}
*rell -= visible_lines - offl;
*linep = (*linep)->next;
}
else {
*rell += offl;
return 1;
}
}
else {
if (*rell + offl <= 0) {
if (!(*linep)->prev) {
if (offl) {
*rell = 0;
return 1;
}
*charp = 0;
return 0;
}
*rell += offl;
*linep = (*linep)->prev;
}
else {
*rell += offl;
return 1;
}
}
/* skip lines and find destination line and offsets */
while (*linep) {
visible_lines = text_get_visible_lines(st, ar, (*linep)->line);
if (lines < 0) { /* moving top */
if (*rell + visible_lines >= 0) {
*rell += visible_lines;
break;
}
if (!(*linep)->prev) {
*rell = 0;
break;
}
*rell += visible_lines;
*linep = (*linep)->prev;
}
else { /* moving bottom */
if (*rell - visible_lines < 0) {
break;
}
if (!(*linep)->next) {
*rell = visible_lines - 1;
break;
}
*rell -= visible_lines;
*linep = (*linep)->next;
}
}
return 1;
}
static void txt_wrap_move_bol(SpaceText *st, ARegion *ar, const bool sel)
{
Text *text = st->text;
TextLine **linep;
int *charp;
int oldc, i, j, max, start, end, endj, chop, loop;
char ch;
text_update_character_width(st);
if (sel) {
linep = &text->sell;
charp = &text->selc;
}
else {
linep = &text->curl;
charp = &text->curc;
}
oldc = *charp;
max = wrap_width(st, ar);
start = endj = 0;
end = max;
chop = loop = 1;
*charp = 0;
for (i = 0, j = 0; loop; j += BLI_str_utf8_size_safe((*linep)->line + j)) {
int chars;
int columns = BLI_str_utf8_char_width_safe((*linep)->line + j); /* = 1 for tab */
/* Mimic replacement of tabs */
ch = (*linep)->line[j];
if (ch == '\t') {
chars = st->tabnumber - i % st->tabnumber;
ch = ' ';
}
else {
chars = 1;
}
while (chars--) {
if (i + columns - start > max) {
end = MIN2(end, i);
*charp = endj;
if (j >= oldc) {
if (ch == '\0') {
*charp = txt_utf8_column_to_offset((*linep)->line, start);
}
loop = 0;
break;
}
if (chop) {
endj = j;
}
start = end;
end += max;
chop = 1;
}
else if (ch == ' ' || ch == '-' || ch == '\0') {
if (j >= oldc) {
*charp = txt_utf8_column_to_offset((*linep)->line, start);
loop = 0;
break;
}
end = i + 1;
endj = j + 1;
chop = 0;
}
i += columns;
}
}
if (!sel) {
txt_pop_sel(text);
}
}
static void txt_wrap_move_eol(SpaceText *st, ARegion *ar, const bool sel)
{
Text *text = st->text;
TextLine **linep;
int *charp;
int oldc, i, j, max, start, end, endj, chop, loop;
char ch;
text_update_character_width(st);
if (sel) {
linep = &text->sell;
charp = &text->selc;
}
else {
linep = &text->curl;
charp = &text->curc;
}
oldc = *charp;
max = wrap_width(st, ar);
start = endj = 0;
end = max;
chop = loop = 1;
*charp = 0;
for (i = 0, j = 0; loop; j += BLI_str_utf8_size_safe((*linep)->line + j)) {
int chars;
int columns = BLI_str_utf8_char_width_safe((*linep)->line + j); /* = 1 for tab */
/* Mimic replacement of tabs */
ch = (*linep)->line[j];
if (ch == '\t') {
chars = st->tabnumber - i % st->tabnumber;
ch = ' ';
}
else {
chars = 1;
}
while (chars--) {
if (i + columns - start > max) {
end = MIN2(end, i);
if (chop) {
endj = BLI_str_prev_char_utf8((*linep)->line + j) - (*linep)->line;
}
if (endj >= oldc) {
if (ch == '\0') {
*charp = (*linep)->len;
}
else {
*charp = endj;
}
loop = 0;
break;
}
start = end;
end += max;
chop = 1;
}
else if (ch == '\0') {
*charp = (*linep)->len;
loop = 0;
break;
}
else if (ch == ' ' || ch == '-') {
end = i + 1;
endj = j;
chop = 0;
}
i += columns;
}
}
if (!sel) {
txt_pop_sel(text);
}
}
static void txt_wrap_move_up(SpaceText *st, ARegion *ar, const bool sel)
{
Text *text = st->text;
TextLine **linep;
int *charp;
int offl, offc, col;
text_update_character_width(st);
if (sel) {
linep = &text->sell;
charp = &text->selc;
}
else {
linep = &text->curl;
charp = &text->curc;
}
wrap_offset_in_line(st, ar, *linep, *charp, &offl, &offc);
col = text_get_char_pos(st, (*linep)->line, *charp) + offc;
if (offl) {
*charp = text_get_cursor_rel(st, ar, *linep, offl - 1, col);
}
else {
if ((*linep)->prev) {
int visible_lines;
*linep = (*linep)->prev;
visible_lines = text_get_visible_lines(st, ar, (*linep)->line);
*charp = text_get_cursor_rel(st, ar, *linep, visible_lines - 1, col);
}
else {
*charp = 0;
}
}
if (!sel) {
txt_pop_sel(text);
}
}
static void txt_wrap_move_down(SpaceText *st, ARegion *ar, const bool sel)
{
Text *text = st->text;
TextLine **linep;
int *charp;
int offl, offc, col, visible_lines;
text_update_character_width(st);
if (sel) {
linep = &text->sell;
charp = &text->selc;
}
else {
linep = &text->curl;
charp = &text->curc;
}
wrap_offset_in_line(st, ar, *linep, *charp, &offl, &offc);
col = text_get_char_pos(st, (*linep)->line, *charp) + offc;
visible_lines = text_get_visible_lines(st, ar, (*linep)->line);
if (offl < visible_lines - 1) {
*charp = text_get_cursor_rel(st, ar, *linep, offl + 1, col);
}
else {
if ((*linep)->next) {
*linep = (*linep)->next;
*charp = text_get_cursor_rel(st, ar, *linep, 0, col);
}
else {
*charp = (*linep)->len;
}
}
if (!sel) {
txt_pop_sel(text);
}
}
/* Moves the cursor vertically by the specified number of lines.
* If the destination line is shorter than the current cursor position, the
* cursor will be positioned at the end of this line.
*
* This is to replace screen_skip for PageUp/Down operations.
*/
static void cursor_skip(SpaceText *st, ARegion *ar, Text *text, int lines, const bool sel)
{
TextLine **linep;
int *charp;
if (sel) {
linep = &text->sell;
charp = &text->selc;
}
else {
linep = &text->curl;
charp = &text->curc;
}
if (st && ar && st->wordwrap) {
int rell, relc;
/* find line and offsets inside it needed to set cursor position */
if (cursor_skip_find_line(st, ar, lines, linep, charp, &rell, &relc)) {
*charp = text_get_cursor_rel(st, ar, *linep, rell, relc);
}
}
else {
while (lines > 0 && (*linep)->next) {
*linep = (*linep)->next;
lines--;
}
while (lines < 0 && (*linep)->prev) {
*linep = (*linep)->prev;
lines++;
}
}
if (*charp > (*linep)->len) {
*charp = (*linep)->len;
}
if (!sel) {
txt_pop_sel(text);
}
}
static int text_move_cursor(bContext *C, int type, bool select)
{
SpaceText *st = CTX_wm_space_text(C);
Text *text = CTX_data_edit_text(C);
ARegion *ar = CTX_wm_region(C);
/* ensure we have the right region, it's optional */
if (ar && ar->regiontype != RGN_TYPE_WINDOW) {
ar = NULL;
}
switch (type) {
case LINE_BEGIN:
if (!select) {
txt_sel_clear(text);
}
if (st && st->wordwrap && ar) {
txt_wrap_move_bol(st, ar, select);
}
else {
txt_move_bol(text, select);
}
break;
case LINE_END:
if (!select) {
txt_sel_clear(text);
}
if (st && st->wordwrap && ar) {
txt_wrap_move_eol(st, ar, select);
}
else {
txt_move_eol(text, select);
}
break;
case FILE_TOP:
txt_move_bof(text, select);
break;
case FILE_BOTTOM:
txt_move_eof(text, select);
break;
case PREV_WORD:
if (txt_cursor_is_line_start(text)) {
txt_move_left(text, select);
}
txt_jump_left(text, select, true);
break;
case NEXT_WORD:
if (txt_cursor_is_line_end(text)) {
txt_move_right(text, select);
}
txt_jump_right(text, select, true);
break;
case PREV_CHAR:
if (txt_has_sel(text) && !select) {
txt_order_cursors(text, false);
txt_pop_sel(text);
}
else {
txt_move_left(text, select);
}
break;
case NEXT_CHAR:
if (txt_has_sel(text) && !select) {
txt_order_cursors(text, true);
txt_pop_sel(text);
}
else {
txt_move_right(text, select);
}
break;
case PREV_LINE:
if (st && st->wordwrap && ar) {
txt_wrap_move_up(st, ar, select);
}
else {
txt_move_up(text, select);
}
break;
case NEXT_LINE:
if (st && st->wordwrap && ar) {
txt_wrap_move_down(st, ar, select);
}
else {
txt_move_down(text, select);
}
break;
case PREV_PAGE:
if (st) {
cursor_skip(st, ar, st->text, -st->viewlines, select);
}
else {
cursor_skip(NULL, NULL, text, -10, select);
}
break;
case NEXT_PAGE:
if (st) {
cursor_skip(st, ar, st->text, st->viewlines, select);
}
else {
cursor_skip(NULL, NULL, text, 10, select);
}
break;
}
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text);
return OPERATOR_FINISHED;
}
static int text_move_exec(bContext *C, wmOperator *op)
{
int type = RNA_enum_get(op->ptr, "type");
return text_move_cursor(C, type, 0);
}
void TEXT_OT_move(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Move Cursor";
ot->idname = "TEXT_OT_move";
ot->description = "Move cursor to position type";
/* api callbacks */
ot->exec = text_move_exec;
ot->poll = text_edit_poll;
/* properties */
RNA_def_enum(ot->srna, "type", move_type_items, LINE_BEGIN, "Type", "Where to move cursor to");
}
/******************* move select operator ********************/
static int text_move_select_exec(bContext *C, wmOperator *op)
{
int type = RNA_enum_get(op->ptr, "type");
return text_move_cursor(C, type, 1);
}
void TEXT_OT_move_select(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Move Select";
ot->idname = "TEXT_OT_move_select";
ot->description = "Move the cursor while selecting";
/* api callbacks */
ot->exec = text_move_select_exec;
ot->poll = text_space_edit_poll;
/* properties */
RNA_def_enum(ot->srna,
"type",
move_type_items,
LINE_BEGIN,
"Type",
"Where to move cursor to, to make a selection");
}
/******************* jump operator *********************/
static int text_jump_exec(bContext *C, wmOperator *op)
{
Text *text = CTX_data_edit_text(C);
int line = RNA_int_get(op->ptr, "line");
short nlines = txt_get_span(text->lines.first, text->lines.last) + 1;
if (line < 1) {
txt_move_toline(text, 1, 0);
}
else if (line > nlines) {
txt_move_toline(text, nlines - 1, 0);
}
else {
txt_move_toline(text, line - 1, 0);
}
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text);
return OPERATOR_FINISHED;
}
static int text_jump_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
return WM_operator_props_dialog_popup(C, op, 200, 100);
}
void TEXT_OT_jump(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Jump";
ot->idname = "TEXT_OT_jump";
ot->description = "Jump cursor to line";
/* api callbacks */
ot->invoke = text_jump_invoke;
ot->exec = text_jump_exec;
ot->poll = text_edit_poll;
/* properties */
prop = RNA_def_int(ot->srna, "line", 1, 1, INT_MAX, "Line", "Line number to jump to", 1, 10000);
RNA_def_property_translation_context(prop, BLT_I18NCONTEXT_ID_TEXT);
}
/******************* delete operator **********************/
static const EnumPropertyItem delete_type_items[] = {
{DEL_NEXT_CHAR, "NEXT_CHARACTER", 0, "Next Character", ""},
{DEL_PREV_CHAR, "PREVIOUS_CHARACTER", 0, "Previous Character", ""},
{DEL_NEXT_WORD, "NEXT_WORD", 0, "Next Word", ""},
{DEL_PREV_WORD, "PREVIOUS_WORD", 0, "Previous Word", ""},
{0, NULL, 0, NULL, NULL},
};
static int text_delete_exec(bContext *C, wmOperator *op)
{
SpaceText *st = CTX_wm_space_text(C);
Text *text = CTX_data_edit_text(C);
int type = RNA_enum_get(op->ptr, "type");
text_drawcache_tag_update(st, 0);
/* behavior could be changed here,
* but for now just don't jump words when we have a selection */
if (txt_has_sel(text)) {
if (type == DEL_PREV_WORD) {
type = DEL_PREV_CHAR;
}
else if (type == DEL_NEXT_WORD) {
type = DEL_NEXT_CHAR;
}
}
ED_text_undo_push_init(C);
if (type == DEL_PREV_WORD) {
if (txt_cursor_is_line_start(text)) {
txt_backspace_char(text);
}
txt_backspace_word(text);
}
else if (type == DEL_PREV_CHAR) {
if (text->flags & TXT_TABSTOSPACES) {
if (!txt_has_sel(text) && !txt_cursor_is_line_start(text)) {
int tabsize = 0;
tabsize = txt_calc_tab_left(text->curl, text->curc);
if (tabsize) {
text->sell = text->curl;
text->selc = text->curc - tabsize;
txt_order_cursors(text, false);
}
}
}
txt_backspace_char(text);
}
else if (type == DEL_NEXT_WORD) {
if (txt_cursor_is_line_end(text)) {
txt_delete_char(text);
}
txt_delete_word(text);
}
else if (type == DEL_NEXT_CHAR) {
if (text->flags & TXT_TABSTOSPACES) {
if (!txt_has_sel(text) && !txt_cursor_is_line_end(text)) {
int tabsize = 0;
tabsize = txt_calc_tab_right(text->curl, text->curc);
if (tabsize) {
text->sell = text->curl;
text->selc = text->curc + tabsize;
txt_order_cursors(text, true);
}
}
}
txt_delete_char(text);
}
text_update_line_edited(text->curl);
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
/* run the script while editing, evil but useful */
if (st->live_edit) {
text_run_script(C, NULL);
}
return OPERATOR_FINISHED;
}
void TEXT_OT_delete(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Delete";
ot->idname = "TEXT_OT_delete";
ot->description = "Delete text by cursor position";
/* api callbacks */
ot->exec = text_delete_exec;
ot->poll = text_edit_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
/* properties */
PropertyRNA *prop;
prop = RNA_def_enum(ot->srna,
"type",
delete_type_items,
DEL_NEXT_CHAR,
"Type",
"Which part of the text to delete");
RNA_def_property_flag(prop, PROP_HIDDEN | PROP_SKIP_SAVE);
}
/******************* toggle overwrite operator **********************/
static int text_toggle_overwrite_exec(bContext *C, wmOperator *UNUSED(op))
{
SpaceText *st = CTX_wm_space_text(C);
st->overwrite = !st->overwrite;
WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
return OPERATOR_FINISHED;
}
void TEXT_OT_overwrite_toggle(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Toggle Overwrite";
ot->idname = "TEXT_OT_overwrite_toggle";
ot->description = "Toggle overwrite while typing";
/* api callbacks */
ot->exec = text_toggle_overwrite_exec;
ot->poll = text_space_edit_poll;
}
/******************* scroll operator **********************/
static void txt_screen_clamp(SpaceText *st, ARegion *ar)
{
if (st->top <= 0) {
st->top = 0;
}
else {
int last;
last = text_get_total_lines(st, ar);
last = last - (st->viewlines / 2);
if (last > 0 && st->top > last) {
st->top = last;
}
}
}
/* Moves the view vertically by the specified number of lines */
static void txt_screen_skip(SpaceText *st, ARegion *ar, int lines)
{
st->top += lines;
txt_screen_clamp(st, ar);
}
/* quick enum for tsc->zone (scroller handles) */
enum {
SCROLLHANDLE_BAR,
SCROLLHANDLE_MIN_OUTSIDE,
SCROLLHANDLE_MAX_OUTSIDE,
};
typedef struct TextScroll {
int old[2];
int delta[2];
int first;
int scrollbar;
int zone;
} TextScroll;
static bool text_scroll_poll(bContext *C)
{
/* it should be possible to still scroll linked texts to read them,
* even if they can't be edited... */
return CTX_data_edit_text(C) != NULL;
}
static int text_scroll_exec(bContext *C, wmOperator *op)
{
SpaceText *st = CTX_wm_space_text(C);
ARegion *ar = CTX_wm_region(C);
int lines = RNA_int_get(op->ptr, "lines");
if (lines == 0) {
return OPERATOR_CANCELLED;
}
txt_screen_skip(st, ar, lines * U.wheellinescroll);
ED_area_tag_redraw(CTX_wm_area(C));
return OPERATOR_FINISHED;
}
static void text_scroll_apply(bContext *C, wmOperator *op, const wmEvent *event)
{
SpaceText *st = CTX_wm_space_text(C);
ARegion *ar = CTX_wm_region(C);
TextScroll *tsc = op->customdata;
int mval[2] = {event->x, event->y};
int scroll_steps[2] = {0, 0};
text_update_character_width(st);
/* compute mouse move distance */
if (tsc->first) {
tsc->old[0] = mval[0];
tsc->old[1] = mval[1];
tsc->first = 0;
}
if (event->type != MOUSEPAN) {
tsc->delta[0] = mval[0] - tsc->old[0];
tsc->delta[1] = mval[1] - tsc->old[1];
}
/* accumulate scroll, in float values for events that give less than one
* line offset but taken together should still scroll */
if (!tsc->scrollbar) {
st->scroll_accum[0] += -tsc->delta[0] / (float)st->cwidth;
st->scroll_accum[1] += tsc->delta[1] / (float)(st->lheight_dpi + TXT_LINE_SPACING);
}
else {
st->scroll_accum[1] += -tsc->delta[1] * st->pix_per_line;
}
/* round to number of lines to scroll */
scroll_steps[0] = (int)st->scroll_accum[0];
scroll_steps[1] = (int)st->scroll_accum[1];
st->scroll_accum[0] -= scroll_steps[0];
st->scroll_accum[1] -= scroll_steps[1];
/* perform vertical and/or horizontal scroll */
if (scroll_steps[0] || scroll_steps[1]) {
txt_screen_skip(st, ar, scroll_steps[1]);
if (st->wordwrap) {
st->left = 0;
}
else {
st->left += scroll_steps[0];
if (st->left < 0) {
st->left = 0;
}
}
ED_area_tag_redraw(CTX_wm_area(C));
}
tsc->old[0] = mval[0];
tsc->old[1] = mval[1];
}
static void scroll_exit(bContext *C, wmOperator *op)
{
SpaceText *st = CTX_wm_space_text(C);
st->flags &= ~ST_SCROLL_SELECT;
MEM_freeN(op->customdata);
}
static int text_scroll_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
TextScroll *tsc = op->customdata;
SpaceText *st = CTX_wm_space_text(C);
ARegion *ar = CTX_wm_region(C);
switch (event->type) {
case MOUSEMOVE:
if (tsc->zone == SCROLLHANDLE_BAR) {
text_scroll_apply(C, op, event);
}
break;
case LEFTMOUSE:
case RIGHTMOUSE:
case MIDDLEMOUSE:
if (event->val == KM_RELEASE) {
if (ELEM(tsc->zone, SCROLLHANDLE_MIN_OUTSIDE, SCROLLHANDLE_MAX_OUTSIDE)) {
txt_screen_skip(
st, ar, st->viewlines * (tsc->zone == SCROLLHANDLE_MIN_OUTSIDE ? 1 : -1));
ED_area_tag_redraw(CTX_wm_area(C));
}
scroll_exit(C, op);
return OPERATOR_FINISHED;
}
}
return OPERATOR_RUNNING_MODAL;
}
static void text_scroll_cancel(bContext *C, wmOperator *op)
{
scroll_exit(C, op);
}
static int text_scroll_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
SpaceText *st = CTX_wm_space_text(C);
TextScroll *tsc;
if (RNA_struct_property_is_set(op->ptr, "lines")) {
return text_scroll_exec(C, op);
}
tsc = MEM_callocN(sizeof(TextScroll), "TextScroll");
tsc->first = 1;
tsc->zone = SCROLLHANDLE_BAR;
op->customdata = tsc;
st->flags |= ST_SCROLL_SELECT;
if (event->type == MOUSEPAN) {
text_update_character_width(st);
tsc->old[0] = event->x;
tsc->old[1] = event->y;
/* Sensitivity of scroll set to 4pix per line/char */
tsc->delta[0] = (event->x - event->prevx) * st->cwidth / 4;
tsc->delta[1] = (event->y - event->prevy) * st->lheight_dpi / 4;
tsc->first = 0;
tsc->scrollbar = 0;
text_scroll_apply(C, op, event);
scroll_exit(C, op);
return OPERATOR_FINISHED;
}
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
void TEXT_OT_scroll(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Scroll";
/* don't really see the difference between this and
* scroll_bar. Both do basically the same thing (aside
* from keymaps).*/
ot->idname = "TEXT_OT_scroll";
/* api callbacks */
ot->exec = text_scroll_exec;
ot->invoke = text_scroll_invoke;
ot->modal = text_scroll_modal;
ot->cancel = text_scroll_cancel;
ot->poll = text_scroll_poll;
/* flags */
ot->flag = OPTYPE_BLOCKING | OPTYPE_GRAB_CURSOR_XY | OPTYPE_INTERNAL;
/* properties */
RNA_def_int(
ot->srna, "lines", 1, INT_MIN, INT_MAX, "Lines", "Number of lines to scroll", -100, 100);
}
/******************** scroll bar operator *******************/
static bool text_region_scroll_poll(bContext *C)
{
/* same as text_region_edit_poll except it works on libdata too */
SpaceText *st = CTX_wm_space_text(C);
Text *text = CTX_data_edit_text(C);
ARegion *ar = CTX_wm_region(C);
if (!st || !text) {
return 0;
}
if (!ar || ar->regiontype != RGN_TYPE_WINDOW) {
return 0;
}
return 1;
}
static int text_scroll_bar_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
SpaceText *st = CTX_wm_space_text(C);
ARegion *ar = CTX_wm_region(C);
TextScroll *tsc;
const int *mval = event->mval;
int zone = -1;
if (RNA_struct_property_is_set(op->ptr, "lines")) {
return text_scroll_exec(C, op);
}
/* verify we are in the right zone */
if (mval[0] > st->txtbar.xmin && mval[0] < st->txtbar.xmax) {
if (mval[1] >= st->txtbar.ymin && mval[1] <= st->txtbar.ymax) {
/* mouse inside scroll handle */
zone = SCROLLHANDLE_BAR;
}
else if (mval[1] > TXT_SCROLL_SPACE && mval[1] < ar->winy - TXT_SCROLL_SPACE) {
if (mval[1] < st->txtbar.ymin) {
zone = SCROLLHANDLE_MIN_OUTSIDE;
}
else {
zone = SCROLLHANDLE_MAX_OUTSIDE;
}
}
}
if (zone == -1) {
/* we are outside slider - nothing to do */
return OPERATOR_PASS_THROUGH;
}
tsc = MEM_callocN(sizeof(TextScroll), "TextScroll");
tsc->first = 1;
tsc->scrollbar = 1;
tsc->zone = zone;
op->customdata = tsc;
st->flags |= ST_SCROLL_SELECT;
/* jump scroll, works in v2d but needs to be added here too :S */
if (event->type == MIDDLEMOUSE) {
tsc->old[0] = ar->winrct.xmin + BLI_rcti_cent_x(&st->txtbar);
tsc->old[1] = ar->winrct.ymin + BLI_rcti_cent_y(&st->txtbar);
tsc->first = 0;
tsc->zone = SCROLLHANDLE_BAR;
text_scroll_apply(C, op, event);
}
WM_event_add_modal_handler(C, op);
return OPERATOR_RUNNING_MODAL;
}
void TEXT_OT_scroll_bar(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Scrollbar";
/* don't really see the difference between this and
* scroll. Both do basically the same thing (aside
* from keymaps).*/
ot->idname = "TEXT_OT_scroll_bar";
/* api callbacks */
ot->invoke = text_scroll_bar_invoke;
ot->modal = text_scroll_modal;
ot->cancel = text_scroll_cancel;
ot->poll = text_region_scroll_poll;
/* flags */
ot->flag = OPTYPE_BLOCKING | OPTYPE_INTERNAL;
/* properties */
RNA_def_int(
ot->srna, "lines", 1, INT_MIN, INT_MAX, "Lines", "Number of lines to scroll", -100, 100);
}
/******************* set selection operator **********************/
typedef struct SetSelection {
int selecting;
int selc, sell;
short old[2];
wmTimer *timer; /* needed for scrolling when mouse at region bounds */
} SetSelection;
static int flatten_width(SpaceText *st, const char *str)
{
int i, total = 0;
for (i = 0; str[i]; i += BLI_str_utf8_size_safe(str + i)) {
if (str[i] == '\t') {
total += st->tabnumber - total % st->tabnumber;
}
else {
total += BLI_str_utf8_char_width_safe(str + i);
}
}
return total;
}
static int flatten_column_to_offset(SpaceText *st, const char *str, int index)
{
int i = 0, j = 0, col;
while (*(str + j)) {
if (str[j] == '\t') {
col = st->tabnumber - i % st->tabnumber;
}
else {
col = BLI_str_utf8_char_width_safe(str + j);
}
if (i + col > index) {
break;
}
i += col;
j += BLI_str_utf8_size_safe(str + j);
}
return j;
}
static TextLine *get_line_pos_wrapped(SpaceText *st, ARegion *ar, int *y)
{
TextLine *linep = st->text->lines.first;
int i, lines;
if (*y < -st->top) {
return NULL; /* We are beyond the first line... */
}
for (i = -st->top; i <= *y && linep; linep = linep->next, i += lines) {
lines = text_get_visible_lines(st, ar, linep->line);
if (i + lines > *y) {
/* We found the line matching given vertical 'coordinate',
* now set y relative to this line's start. */
*y -= i;
break;
}
}
return linep;
}
static void text_cursor_set_to_pos_wrapped(
SpaceText *st, ARegion *ar, int x, int y, const bool sel)
{
Text *text = st->text;
int max = wrap_width(st, ar); /* column */
int charp = -1; /* mem */
bool found = false; /* flags */
/* Point to line matching given y position, if any. */
TextLine *linep = get_line_pos_wrapped(st, ar, &y);
if (linep) {
int i = 0, start = 0, end = max; /* column */
int j, curs = 0, endj = 0; /* mem */
bool chop = true; /* flags */
char ch;
for (j = 0; !found && ((ch = linep->line[j]) != '\0');
j += BLI_str_utf8_size_safe(linep->line + j)) {
int chars;
int columns = BLI_str_utf8_char_width_safe(linep->line + j); /* = 1 for tab */
/* Mimic replacement of tabs */
if (ch == '\t') {
chars = st->tabnumber - i % st->tabnumber;
ch = ' ';
}
else {
chars = 1;
}
while (chars--) {
/* Gone too far, go back to last wrap point */
if (y < 0) {
charp = endj;
y = 0;
found = true;
break;
/* Exactly at the cursor */
}
else if (y == 0 && i - start <= x && i + columns - start > x) {
/* current position could be wrapped to next line */
/* this should be checked when end of current line would be reached */
charp = curs = j;
found = true;
/* Prepare curs for next wrap */
}
else if (i - end <= x && i + columns - end > x) {
curs = j;
}
if (i + columns - start > max) {
end = MIN2(end, i);
if (found) {
/* exact cursor position was found, check if it's still on needed line
* (hasn't been wrapped) */
if (charp > endj && !chop && ch != '\0') {
charp = endj;
}
break;
}
if (chop) {
endj = j;
}
start = end;
end += max;
if (j < linep->len) {
y--;
}
chop = true;
if (y == 0 && i + columns - start > x) {
charp = curs;
found = true;
break;
}
}
else if (ch == ' ' || ch == '-' || ch == '\0') {
if (found) {
break;
}
if (y == 0 && i + columns - start > x) {
charp = curs;
found = true;
break;
}
end = i + 1;
endj = j;
chop = false;
}
i += columns;
}
}
BLI_assert(y == 0);
if (!found) {
/* On correct line but didn't meet cursor, must be at end */
charp = linep->len;
}
}
else if (y < 0) { /* Before start of text. */
linep = st->text->lines.first;
charp = 0;
}
else { /* Beyond end of text */
linep = st->text->lines.last;
charp = linep->len;
}
BLI_assert(linep && charp != -1);
if (sel) {
text->sell = linep;
text->selc = charp;
}
else {
text->curl = linep;
text->curc = charp;
}
}
static void text_cursor_set_to_pos(SpaceText *st, ARegion *ar, int x, int y, const bool sel)
{
Text *text = st->text;
text_update_character_width(st);
y = (ar->winy - 2 - y) / (st->lheight_dpi + TXT_LINE_SPACING);
if (st->showlinenrs) {
x -= TXT_OFFSET + TEXTXLOC;
}
else {
x -= TXT_OFFSET;
}
if (x < 0) {
x = 0;
}
x = text_pixel_x_to_column(st, x) + st->left;
if (st->wordwrap) {
text_cursor_set_to_pos_wrapped(st, ar, x, y, sel);
}
else {
TextLine **linep;
int *charp;
int w;
if (sel) {
linep = &text->sell;
charp = &text->selc;
}
else {
linep = &text->curl;
charp = &text->curc;
}
y -= txt_get_span(text->lines.first, *linep) - st->top;
if (y > 0) {
while (y-- != 0) {
if ((*linep)->next) {
*linep = (*linep)->next;
}
}
}
else if (y < 0) {
while (y++ != 0) {
if ((*linep)->prev) {
*linep = (*linep)->prev;
}
}
}
w = flatten_width(st, (*linep)->line);
if (x < w) {
*charp = flatten_column_to_offset(st, (*linep)->line, x);
}
else {
*charp = (*linep)->len;
}
}
if (!sel) {
txt_pop_sel(text);
}
}
static void text_cursor_timer_ensure(bContext *C, SetSelection *ssel)
{
if (ssel->timer == NULL) {
wmWindowManager *wm = CTX_wm_manager(C);
wmWindow *win = CTX_wm_window(C);
ssel->timer = WM_event_add_timer(wm, win, TIMER, 0.02f);
}
}
static void text_cursor_timer_remove(bContext *C, SetSelection *ssel)
{
if (ssel->timer) {
wmWindowManager *wm = CTX_wm_manager(C);
wmWindow *win = CTX_wm_window(C);
WM_event_remove_timer(wm, win, ssel->timer);
}
ssel->timer = NULL;
}
static void text_cursor_set_apply(bContext *C, wmOperator *op, const wmEvent *event)
{
SpaceText *st = CTX_wm_space_text(C);
ARegion *ar = CTX_wm_region(C);
SetSelection *ssel = op->customdata;
if (event->mval[1] < 0 || event->mval[1] > ar->winy) {
text_cursor_timer_ensure(C, ssel);
if (event->type == TIMER) {
text_cursor_set_to_pos(st, ar, event->mval[0], event->mval[1], 1);
text_scroll_to_cursor(st, ar, false);
WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
}
}
else if (!st->wordwrap && (event->mval[0] < 0 || event->mval[0] > ar->winx)) {
text_cursor_timer_ensure(C, ssel);
if (event->type == TIMER) {
text_cursor_set_to_pos(st, ar, CLAMPIS(event->mval[0], 0, ar->winx), event->mval[1], 1);
text_scroll_to_cursor(st, ar, false);
WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
}
}
else {
text_cursor_timer_remove(C, ssel);
if (event->type != TIMER) {
text_cursor_set_to_pos(st, ar, event->mval[0], event->mval[1], 1);
text_scroll_to_cursor(st, ar, false);
WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
ssel->old[0] = event->mval[0];
ssel->old[1] = event->mval[1];
}
}
}
static void text_cursor_set_exit(bContext *C, wmOperator *op)
{
SpaceText *st = CTX_wm_space_text(C);
Text *text = st->text;
SetSelection *ssel = op->customdata;
char *buffer;
if (txt_has_sel(text)) {
buffer = txt_sel_to_buf(text, NULL);
WM_clipboard_text_set(buffer, 1);
MEM_freeN(buffer);
}
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
text_cursor_timer_remove(C, ssel);
MEM_freeN(ssel);
}
static int text_set_selection_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
SpaceText *st = CTX_wm_space_text(C);
SetSelection *ssel;
if (event->mval[0] >= st->txtbar.xmin) {
return OPERATOR_PASS_THROUGH;
}
op->customdata = MEM_callocN(sizeof(SetSelection), "SetCursor");
ssel = op->customdata;
ssel->selecting = RNA_boolean_get(op->ptr, "select");
ssel->old[0] = event->mval[0];
ssel->old[1] = event->mval[1];
ssel->sell = txt_get_span(st->text->lines.first, st->text->sell);
ssel->selc = st->text->selc;
WM_event_add_modal_handler(C, op);
text_cursor_set_apply(C, op, event);
return OPERATOR_RUNNING_MODAL;
}
static int text_set_selection_modal(bContext *C, wmOperator *op, const wmEvent *event)
{
switch (event->type) {
case LEFTMOUSE:
case MIDDLEMOUSE:
case RIGHTMOUSE:
text_cursor_set_exit(C, op);
return OPERATOR_FINISHED;
case TIMER:
case MOUSEMOVE:
text_cursor_set_apply(C, op, event);
break;
}
return OPERATOR_RUNNING_MODAL;
}
static void text_set_selection_cancel(bContext *C, wmOperator *op)
{
text_cursor_set_exit(C, op);
}
void TEXT_OT_selection_set(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Set Selection";
ot->idname = "TEXT_OT_selection_set";
ot->description = "Set cursor selection";
/* api callbacks */
ot->invoke = text_set_selection_invoke;
ot->modal = text_set_selection_modal;
ot->cancel = text_set_selection_cancel;
ot->poll = text_region_edit_poll;
/* properties */
RNA_def_boolean(ot->srna, "select", 0, "Select", "Set selection end rather than cursor");
}
/******************* set cursor operator **********************/
static int text_cursor_set_exec(bContext *C, wmOperator *op)
{
SpaceText *st = CTX_wm_space_text(C);
ARegion *ar = CTX_wm_region(C);
int x = RNA_int_get(op->ptr, "x");
int y = RNA_int_get(op->ptr, "y");
text_cursor_set_to_pos(st, ar, x, y, 0);
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, st->text);
return OPERATOR_PASS_THROUGH;
}
static int text_cursor_set_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
SpaceText *st = CTX_wm_space_text(C);
if (event->mval[0] >= st->txtbar.xmin) {
return OPERATOR_PASS_THROUGH;
}
RNA_int_set(op->ptr, "x", event->mval[0]);
RNA_int_set(op->ptr, "y", event->mval[1]);
return text_cursor_set_exec(C, op);
}
void TEXT_OT_cursor_set(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Set Cursor";
ot->idname = "TEXT_OT_cursor_set";
ot->description = "Set cursor position";
/* api callbacks */
ot->invoke = text_cursor_set_invoke;
ot->exec = text_cursor_set_exec;
ot->poll = text_region_edit_poll;
/* properties */
RNA_def_int(ot->srna, "x", 0, INT_MIN, INT_MAX, "X", "", INT_MIN, INT_MAX);
RNA_def_int(ot->srna, "y", 0, INT_MIN, INT_MAX, "Y", "", INT_MIN, INT_MAX);
}
/******************* line number operator **********************/
static int text_line_number_invoke(bContext *C, wmOperator *UNUSED(op), const wmEvent *event)
{
SpaceText *st = CTX_wm_space_text(C);
Text *text = CTX_data_edit_text(C);
ARegion *ar = CTX_wm_region(C);
const int *mval = event->mval;
double time;
static int jump_to = 0;
static double last_jump = 0;
text_update_character_width(st);
if (!st->showlinenrs) {
return OPERATOR_PASS_THROUGH;
}
if (!(mval[0] > 2 && mval[0] < (TXT_OFFSET + TEXTXLOC) && mval[1] > 2 &&
mval[1] < ar->winy - 2)) {
return OPERATOR_PASS_THROUGH;
}
if (!(event->ascii >= '0' && event->ascii <= '9')) {
return OPERATOR_PASS_THROUGH;
}
time = PIL_check_seconds_timer();
if (last_jump < time - 1) {
jump_to = 0;
}
jump_to *= 10;
jump_to += (int)(event->ascii - '0');
txt_move_toline(text, jump_to - 1, 0);
last_jump = time;
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text);
return OPERATOR_FINISHED;
}
void TEXT_OT_line_number(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Line Number";
ot->idname = "TEXT_OT_line_number";
ot->description = "The current line number";
/* api callbacks */
ot->invoke = text_line_number_invoke;
ot->poll = text_region_edit_poll;
}
/******************* insert operator **********************/
static int text_insert_exec(bContext *C, wmOperator *op)
{
SpaceText *st = CTX_wm_space_text(C);
Text *text = CTX_data_edit_text(C);
char *str;
bool done = false;
size_t i = 0;
unsigned int code;
text_drawcache_tag_update(st, 0);
str = RNA_string_get_alloc(op->ptr, "text", NULL, 0);
ED_text_undo_push_init(C);
if (st && st->overwrite) {
while (str[i]) {
code = BLI_str_utf8_as_unicode_step(str, &i);
done |= txt_replace_char(text, code);
}
}
else {
while (str[i]) {
code = BLI_str_utf8_as_unicode_step(str, &i);
done |= txt_add_char(text, code);
}
}
MEM_freeN(str);
if (!done) {
return OPERATOR_CANCELLED;
}
text_update_line_edited(text->curl);
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
return OPERATOR_FINISHED;
}
static int text_insert_invoke(bContext *C, wmOperator *op, const wmEvent *event)
{
int ret;
// if (!RNA_struct_property_is_set(op->ptr, "text")) { /* always set from keymap XXX */
if (!RNA_string_length(op->ptr, "text")) {
/* if alt/ctrl/super are pressed pass through except for utf8 character event
* (when input method are used for utf8 inputs, the user may assign key event
* including alt/ctrl/super like ctrl+m to commit utf8 string. in such case,
* the modifiers in the utf8 character event make no sense.) */
if ((event->ctrl || event->oskey) && !event->utf8_buf[0]) {
return OPERATOR_PASS_THROUGH;
}
else {
char str[BLI_UTF8_MAX + 1];
size_t len;
if (event->utf8_buf[0]) {
len = BLI_str_utf8_size_safe(event->utf8_buf);
memcpy(str, event->utf8_buf, len);
}
else {
/* in theory, ghost can set value to extended ascii here */
len = BLI_str_utf8_from_unicode(event->ascii, str);
}
str[len] = '\0';
RNA_string_set(op->ptr, "text", str);
}
}
ret = text_insert_exec(C, op);
/* run the script while editing, evil but useful */
if (ret == OPERATOR_FINISHED && CTX_wm_space_text(C)->live_edit) {
text_run_script(C, NULL);
}
return ret;
}
void TEXT_OT_insert(wmOperatorType *ot)
{
PropertyRNA *prop;
/* identifiers */
ot->name = "Insert";
ot->idname = "TEXT_OT_insert";
ot->description = "Insert text at cursor position";
/* api callbacks */
ot->exec = text_insert_exec;
ot->invoke = text_insert_invoke;
ot->poll = text_edit_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
/* properties */
prop = RNA_def_string(
ot->srna, "text", NULL, 0, "Text", "Text to insert at the cursor position");
RNA_def_property_flag(prop, PROP_SKIP_SAVE);
}
/******************* find operator *********************/
/* mode */
#define TEXT_FIND 0
#define TEXT_REPLACE 1
static int text_find_and_replace(bContext *C, wmOperator *op, short mode)
{
Main *bmain = CTX_data_main(C);
SpaceText *st = CTX_wm_space_text(C);
Text *text = st->text;
int flags;
int found = 0;
char *tmp;
if (!st->findstr[0]) {
return OPERATOR_CANCELLED;
}
flags = st->flags;
if (flags & ST_FIND_ALL) {
flags &= ~ST_FIND_WRAP;
}
/* Replace current */
if (mode != TEXT_FIND && txt_has_sel(text)) {
tmp = txt_sel_to_buf(text, NULL);
if (flags & ST_MATCH_CASE) {
found = STREQ(st->findstr, tmp);
}
else {
found = BLI_strcasecmp(st->findstr, tmp) == 0;
}
if (found) {
if (mode == TEXT_REPLACE) {
ED_text_undo_push_init(C);
txt_insert_buf(text, st->replacestr);
if (text->curl && text->curl->format) {
MEM_freeN(text->curl->format);
text->curl->format = NULL;
}
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | NA_EDITED, text);
text_drawcache_tag_update(CTX_wm_space_text(C), 1);
}
}
MEM_freeN(tmp);
tmp = NULL;
}
/* Find next */
if (txt_find_string(text, st->findstr, flags & ST_FIND_WRAP, flags & ST_MATCH_CASE)) {
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text);
}
else if (flags & ST_FIND_ALL) {
if (text->id.next) {
text = st->text = text->id.next;
}
else {
text = st->text = bmain->texts.first;
}
txt_move_toline(text, 0, 0);
text_update_cursor_moved(C);
WM_event_add_notifier(C, NC_TEXT | ND_CURSOR, text);
}
else {
if (!found) {
BKE_reportf(op->reports, RPT_ERROR, "Text not found: %s", st->findstr);
}
}
return OPERATOR_FINISHED;
}
static int text_find_exec(bContext *C, wmOperator *op)
{
return text_find_and_replace(C, op, TEXT_FIND);
}
void TEXT_OT_find(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Find Next";
ot->idname = "TEXT_OT_find";
ot->description = "Find specified text";
/* api callbacks */
ot->exec = text_find_exec;
ot->poll = text_space_edit_poll;
}
/******************* replace operator *********************/
static int text_replace_exec(bContext *C, wmOperator *op)
{
return text_find_and_replace(C, op, TEXT_REPLACE);
}
void TEXT_OT_replace(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Replace";
ot->idname = "TEXT_OT_replace";
ot->description = "Replace text with the specified text";
/* api callbacks */
ot->exec = text_replace_exec;
ot->poll = text_space_edit_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
}
/******************* find set selected *********************/
static int text_find_set_selected_exec(bContext *C, wmOperator *op)
{
SpaceText *st = CTX_wm_space_text(C);
Text *text = CTX_data_edit_text(C);
char *tmp;
tmp = txt_sel_to_buf(text, NULL);
BLI_strncpy(st->findstr, tmp, ST_MAX_FIND_STR);
MEM_freeN(tmp);
if (!st->findstr[0]) {
return OPERATOR_FINISHED;
}
return text_find_and_replace(C, op, TEXT_FIND);
}
void TEXT_OT_find_set_selected(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Find Set Selected";
ot->idname = "TEXT_OT_find_set_selected";
ot->description = "Find specified text and set as selected";
/* api callbacks */
ot->exec = text_find_set_selected_exec;
ot->poll = text_space_edit_poll;
}
/******************* replace set selected *********************/
static int text_replace_set_selected_exec(bContext *C, wmOperator *UNUSED(op))
{
SpaceText *st = CTX_wm_space_text(C);
Text *text = CTX_data_edit_text(C);
char *tmp;
tmp = txt_sel_to_buf(text, NULL);
BLI_strncpy(st->replacestr, tmp, ST_MAX_FIND_STR);
MEM_freeN(tmp);
return OPERATOR_FINISHED;
}
void TEXT_OT_replace_set_selected(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Replace Set Selected";
ot->idname = "TEXT_OT_replace_set_selected";
ot->description = "Replace text with specified text and set as selected";
/* api callbacks */
ot->exec = text_replace_set_selected_exec;
ot->poll = text_space_edit_poll;
/* flags */
ot->flag = OPTYPE_UNDO;
}
/****************** resolve conflict operator ******************/
enum { RESOLVE_IGNORE, RESOLVE_RELOAD, RESOLVE_SAVE, RESOLVE_MAKE_INTERNAL };
static const EnumPropertyItem resolution_items[] = {
{RESOLVE_IGNORE, "IGNORE", 0, "Ignore", ""},
{RESOLVE_RELOAD, "RELOAD", 0, "Reload", ""},
{RESOLVE_SAVE, "SAVE", 0, "Save", ""},
{RESOLVE_MAKE_INTERNAL, "MAKE_INTERNAL", 0, "Make Internal", ""},
{0, NULL, 0, NULL, NULL},
};
static int text_resolve_conflict_exec(bContext *C, wmOperator *op)
{
Text *text = CTX_data_edit_text(C);
int resolution = RNA_enum_get(op->ptr, "resolution");
switch (resolution) {
case RESOLVE_RELOAD:
return text_reload_exec(C, op);
case RESOLVE_SAVE:
return text_save_exec(C, op);
case RESOLVE_MAKE_INTERNAL:
return text_make_internal_exec(C, op);
case RESOLVE_IGNORE:
BKE_text_file_modified_ignore(text);
return OPERATOR_FINISHED;
}
return OPERATOR_CANCELLED;
}
static int text_resolve_conflict_invoke(bContext *C, wmOperator *op, const wmEvent *UNUSED(event))
{
Text *text = CTX_data_edit_text(C);
uiPopupMenu *pup;
uiLayout *layout;
switch (BKE_text_file_modified_check(text)) {
case 1:
if (text->flags & TXT_ISDIRTY) {
/* modified locally and externally, ahhh. offer more possibilities. */
pup = UI_popup_menu_begin(
C, IFACE_("File Modified Outside and Inside Blender"), ICON_NONE);
layout = UI_popup_menu_layout(pup);
uiItemEnumO_ptr(layout,
op->type,
IFACE_("Reload from disk (ignore local changes)"),
0,
"resolution",
RESOLVE_RELOAD);
uiItemEnumO_ptr(layout,
op->type,
IFACE_("Save to disk (ignore outside changes)"),
0,
"resolution",
RESOLVE_SAVE);
uiItemEnumO_ptr(layout,
op->type,
IFACE_("Make text internal (separate copy)"),
0,
"resolution",
RESOLVE_MAKE_INTERNAL);
UI_popup_menu_end(C, pup);
}
else {
pup = UI_popup_menu_begin(C, IFACE_("File Modified Outside Blender"), ICON_NONE);
layout = UI_popup_menu_layout(pup);
uiItemEnumO_ptr(
layout, op->type, IFACE_("Reload from disk"), 0, "resolution", RESOLVE_RELOAD);
uiItemEnumO_ptr(layout,
op->type,
IFACE_("Make text internal (separate copy)"),
0,
"resolution",
RESOLVE_MAKE_INTERNAL);
uiItemEnumO_ptr(layout, op->type, IFACE_("Ignore"), 0, "resolution", RESOLVE_IGNORE);
UI_popup_menu_end(C, pup);
}
break;
case 2:
pup = UI_popup_menu_begin(C, IFACE_("File Deleted Outside Blender"), ICON_NONE);
layout = UI_popup_menu_layout(pup);
uiItemEnumO_ptr(
layout, op->type, IFACE_("Make text internal"), 0, "resolution", RESOLVE_MAKE_INTERNAL);
uiItemEnumO_ptr(layout, op->type, IFACE_("Recreate file"), 0, "resolution", RESOLVE_SAVE);
UI_popup_menu_end(C, pup);
break;
}
return OPERATOR_INTERFACE;
}
void TEXT_OT_resolve_conflict(wmOperatorType *ot)
{
/* identifiers */
ot->name = "Resolve Conflict";
ot->idname = "TEXT_OT_resolve_conflict";
ot->description = "When external text is out of sync, resolve the conflict";
/* api callbacks */
ot->exec = text_resolve_conflict_exec;
ot->invoke = text_resolve_conflict_invoke;
ot->poll = text_save_poll;
/* properties */
RNA_def_enum(ot->srna,
"resolution",
resolution_items,
RESOLVE_IGNORE,
"Resolution",
"How to solve conflict due to differences in internal and external text");
}
/********************** to 3d object operator *****************/
static int text_to_3d_object_exec(bContext *C, wmOperator *op)
{
Text *text = CTX_data_edit_text(C);
const bool split_lines = RNA_boolean_get(op->ptr, "split_lines");
ED_text_to_object(C, text, split_lines);
return OPERATOR_FINISHED;
}
void TEXT_OT_to_3d_object(wmOperatorType *ot)
{
/* identifiers */
ot->name = "To 3D Object";
ot->idname = "TEXT_OT_to_3d_object";
ot->description = "Create 3D text object from active text data-block";
/* api callbacks */
ot->exec = text_to_3d_object_exec;
ot->poll = text_edit_poll;
/* flags */
ot->flag = OPTYPE_REGISTER | OPTYPE_UNDO;
/* properties */
RNA_def_boolean(
ot->srna, "split_lines", 0, "Split Lines", "Create one object per line in the text");
}