1 /*******************************************************************************
2 * Copyright (c) 2013-2021, Andrés Martinelli <andmarti@gmail.com> *
3 * All rights reserved. *
4 * *
5 * This file is a part of SC-IM *
6 * *
7 * SC-IM is a spreadsheet program that is based on SC. The original authors *
8 * of SC are James Gosling and Mark Weiser, and mods were later added by *
9 * Chuck Martin. *
10 * *
11 * Redistribution and use in source and binary forms, with or without *
12 * modification, are permitted provided that the following conditions are met: *
13 * 1. Redistributions of source code must retain the above copyright *
14 * notice, this list of conditions and the following disclaimer. *
15 * 2. Redistributions in binary form must reproduce the above copyright *
16 * notice, this list of conditions and the following disclaimer in the *
17 * documentation and/or other materials provided with the distribution. *
18 * 3. All advertising materials mentioning features or use of this software *
19 * must display the following acknowledgement: *
20 * This product includes software developed by Andrés Martinelli *
21 * <andmarti@gmail.com>. *
22 * 4. Neither the name of the Andrés Martinelli nor the *
23 * names of other contributors may be used to endorse or promote products *
24 * derived from this software without specific prior written permission. *
25 * *
26 * THIS SOFTWARE IS PROVIDED BY ANDRES MARTINELLI ''AS IS'' AND ANY *
27 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED *
28 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE *
29 * DISCLAIMED. IN NO EVENT SHALL ANDRES MARTINELLI BE LIABLE FOR ANY *
30 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES *
31 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;*
32 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND *
33 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT *
34 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE *
35 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. *
36 *******************************************************************************/
37
38 /**
39 * \file cmds_visuals.c
40 * \author Andrés Martinelli <andmarti@gmail.com>
41 * \date 2017-07-18
42 * \brief TODO Write a tbrief file description.
43 */
44
45 #include <stdlib.h>
46
47 #include "utils/string.h"
48 #include "tui.h"
49 #include "buffer.h"
50 #include "marks.h"
51 #include "macros.h"
52 #include "cmds.h"
53 #include "conf.h"
54 #include "hide_show.h"
55 #include "shift.h"
56 #include "freeze.h"
57 #include "yank.h"
58 #include "history.h"
59 #include "interp.h"
60 #ifdef UNDO
61 #include "undo.h"
62 #endif
63
64 extern int offscr_sc_rows, offscr_sc_cols;
65 extern unsigned int curmode;
66 extern int cmd_multiplier;
67 extern struct history * commandline_history;
68
69 char visual_submode = '0';
70 srange * r; // SELECTED RANGE!
71 int moving = FALSE;
72
73 /**
74 * \brief TODO Document start_visualmode()
75 *
76 * \param[in] tlrow
77 * \param[in] tlcol
78 * \param[in] brrow
79 * \param[in] brcol
80 *
81 * \return none
82 */
83
start_visualmode(int tlrow,int tlcol,int brrow,int brcol)84 void start_visualmode(int tlrow, int tlcol, int brrow, int brcol) {
85 unselect_ranges();
86
87 struct srange * sr = get_range_by_marks('\t', '\t'); // visual mode selected range
88 if (sr != NULL) del_ranges_by_mark('\t');
89
90 r = (srange *) malloc (sizeof(srange));
91 r->tlrow = tlrow;
92 r->tlcol = tlcol;
93 r->brrow = brrow;
94 r->brcol = brcol;
95 r->orig_row = currow; // original row before starting selection
96 r->orig_col = curcol; // original col before starting selection
97 r->startup_row = currow; // original row position before entering visual mode
98 r->startup_col = curcol; // original col position before entering visual mode
99 r->marks[0] = '\t';
100 r->marks[1] = '\t';
101 r->selected = 1;
102 r->pnext = NULL;
103
104 // add visual selected range at start of list
105 if (ranges == NULL) ranges = r;
106 else {
107 r->pnext = ranges;
108 ranges = r;
109 }
110
111 if (visual_submode == '0') { // Started visual mode with 'v' command
112 ui_update(TRUE);
113 moving = FALSE;
114 } else { // Started visual mode with 'C-v' command
115 ui_update(TRUE);
116 moving = TRUE;
117 }
118 return;
119 }
120
121 /**
122 * \brief TODO Document exit_visualmode()
123 *
124 * \return none
125 */
126
exit_visualmode()127 void exit_visualmode() {
128 moving = FALSE;
129 visual_submode = '0';
130 r->selected = 0;
131 currow = r->startup_row;
132 curcol = r->startup_col;
133 del_ranges_by_mark('\t');
134 return;
135 }
136
137 /**
138 * \brief TODO Document do_visualmode()
139 *
140 * \param[in] buf
141 *
142 * \return none
143 */
144
do_visualmode(struct block * buf)145 void do_visualmode(struct block * buf) {
146 // we are moving (previous to a 'C-o' keypress)
147 if (moving == TRUE) {
148 switch (buf->value) {
149 case L'j':
150 case OKEY_DOWN:
151 currow = forw_row(1)->row;
152 break;
153
154 case L'k':
155 case OKEY_UP:
156 currow = back_row(1)->row;
157 break;
158
159 case L'h':
160 case OKEY_LEFT:
161 curcol = back_col(1)->col;
162 break;
163
164 case L'l':
165 case OKEY_RIGHT:
166 curcol = forw_col(1)->col;
167 break;
168
169 case ctl('o'):
170 moving = FALSE;
171 r->orig_row = currow;
172 r->orig_col = curcol;
173 break;
174
175 case OKEY_ENTER:
176 sc_info("Press <C-o> to begin selection or <Esc> key to exit VISUAL MODE");
177 return;
178
179 }
180 r->tlrow = currow;
181 r->tlcol = curcol;
182 r->brrow = currow;
183 r->brcol = curcol;
184
185 ui_update(FALSE);
186 return;
187 }
188
189 // started visual mode with 'C-v'
190 // ENTER or 'C-k' : Confirm selection
191 // 'C-k' only works if started visualmode with 'C-v'
192 if ((buf->value == OKEY_ENTER || buf->value == ctl('k')) && visual_submode != '0') {
193 wchar_t cline [BUFFERSIZE];
194 swprintf(cline, BUFFERSIZE, L"%ls%d", coltoa(r->tlcol), r->tlrow);
195 if (r->tlrow != r->brrow || r->tlcol != r->brcol)
196 swprintf(cline + wcslen(cline), BUFFERSIZE, L":%ls%d", coltoa(r->brcol), r->brrow);
197 swprintf(inputline + wcslen(inputline), BUFFERSIZE, L"%ls", cline);
198
199 real_inputline_pos += wcslen(cline);
200 inputline_pos = wcswidth(inputline, real_inputline_pos);
201
202 char c = visual_submode;
203 exit_visualmode();
204 chg_mode(c);
205
206 ui_show_header();
207 return;
208
209 // moving to TRUE
210 //} else if (buf->value == ctl('m')) {
211 // moving = TRUE;
212
213 // MOVEMENT COMMANDS
214 // UP - ctl(b)
215 } else if (buf->value == OKEY_UP || buf->value == L'k' || buf->value == ctl('b') ) {
216 int n, i;
217 if (buf->value == ctl('b')) {
218 n = LINES - RESROW - 1;
219 if (get_conf_value("half_page_scroll")) n = n / 2;
220 } else n = 1;
221
222 for (i=0; i < n; i++)
223 if (r->orig_row < r->brrow && r->tlrow < r->brrow) {
224 while (row_hidden[-- r->brrow]);
225 currow = r->brrow;
226 } else if (r->tlrow <= r->brrow && r->tlrow-1 >= 0) {
227 int newrow = r->tlrow;
228 while (newrow > 0 && row_hidden[-- newrow]);
229 if (!row_hidden[newrow]) {
230 currow = r->tlrow = newrow;
231 }
232 }
233
234 // DOWN - ctl('f')
235 } else if (buf->value == OKEY_DOWN || buf->value == L'j' || buf->value == ctl('f')) {
236 int n, i;
237 if (buf->value == ctl('f')) {
238 n = LINES - RESROW - 1;
239 if (get_conf_value("half_page_scroll")) n = n / 2;
240 } else n = 1;
241
242 for (i=0; i < n; i++)
243 if (r->orig_row <= r->tlrow && r->tlrow <= r->brrow) {
244 while (r->brrow+1 < maxrows && row_hidden[++ r->brrow]);
245 currow = r->brrow;
246 } else if (r->tlrow < r->brrow) {
247 while (row_hidden[++ r->tlrow]);
248 currow = r->tlrow;
249 }
250
251 // LEFT
252 } else if (buf->value == OKEY_LEFT || buf->value == L'h') {
253 if (r->orig_col < r->brcol && r->tlcol < r->brcol) {
254 while (col_hidden[-- r->brcol]);
255 curcol = r->brcol;
256 } else if (r->tlcol <= r->brcol && r->tlcol-1 >= 0) {
257 while (col_hidden[-- r->tlcol]);
258 curcol = r->tlcol;
259 }
260
261 // RIGHT
262 } else if (buf->value == OKEY_RIGHT || buf->value == L'l') {
263 if (r->orig_col <= r->tlcol && r->tlcol <= r->brcol && r->brcol+2 < maxcols) {
264 while (col_hidden[++ r->brcol]);
265 curcol = r->brcol;
266 } else if (r->tlcol <= r->brcol) {
267 while (col_hidden[++ r->tlcol]);
268 curcol = r->tlcol;
269 }
270
271 // 0
272 } else if (buf->value == L'0') {
273 r->brcol = r->tlcol;
274 r->tlcol = left_limit()->col;
275 curcol = r->tlcol;
276
277 // $
278 } else if (buf->value == L'$') {
279 int s = right_limit(currow)->col;
280 r->tlcol = r->brcol;
281 r->brcol = r->brcol > s ? r->brcol : s;
282 curcol = r->brcol;
283
284 // ^
285 } else if (buf->value == L'^') {
286 r->brrow = r->tlrow;
287 r->tlrow = goto_top()->row;
288 currow = r->tlrow;
289
290 // #
291 } else if (buf->value == L'#') {
292 int s = goto_bottom()->row;
293 if (s == r->brrow) s = go_end()->row;
294 //r->tlrow = r->brrow;
295 r->brrow = r->brrow > s ? r->brrow : s;
296 //r->brrow = s;
297 currow = r->brrow;
298
299 // ctl(a)
300 } else if (buf->value == ctl('a')) {
301 if (r->tlrow == 0 && r->tlcol == 0) return;
302 struct ent * e = go_home();
303 r->tlrow = e->row;
304 r->tlcol = e->col;
305 r->brrow = r->orig_row;
306 r->brcol = r->orig_col;
307 currow = r->tlrow;
308 curcol = r->tlcol;
309
310 // G
311 } else if (buf->value == L'G') {
312 struct ent * e = go_end();
313 r->tlrow = r->orig_row;
314 r->tlcol = r->orig_col;
315 r->brrow = e->row;
316 r->brcol = e->col;
317 currow = r->tlrow;
318 curcol = r->tlcol;
319
320 // '
321 } else if (buf->value == L'\'') {
322 // if we receive a mark of a range, just return.
323 if (get_mark(buf->pnext->value)->row == -1) return;
324
325 struct ent * e = tick(buf->pnext->value);
326 if (row_hidden[e->row]) {
327 sc_error("Cell row is hidden");
328 return;
329 } else if (col_hidden[e->col]) {
330 sc_error("Cell column is hidden");
331 return;
332 }
333 r->tlrow = r->tlrow < e->row ? r->tlrow : e->row;
334 r->tlcol = r->tlcol < e->col ? r->tlcol : e->col;
335 r->brrow = r->brrow > e->row ? r->brrow : e->row;
336 r->brcol = r->brcol > e->col ? r->brcol : e->col;
337
338 // w
339 } else if (buf->value == L'w') {
340 struct ent * e = go_forward();
341 if (e->col > r->orig_col) {
342 r->brcol = e->col;
343 r->tlcol = r->orig_col;
344 } else {
345 r->tlcol = e->col;
346 r->brcol = r->orig_col;
347 }
348 r->brrow = e->row;
349 r->tlrow = r->orig_row;
350 curcol = e->col;
351 currow = e->row;
352
353 // b
354 } else if (buf->value == L'b') {
355 struct ent * e = go_backward();
356 if (e->col <= r->orig_col) {
357 r->tlcol = e->col;
358 r->brcol = r->orig_col;
359 } else {
360 r->brcol = e->col;
361 r->tlcol = r->orig_col;
362 }
363 r->tlrow = e->row;
364 r->brrow = r->orig_row;
365 curcol = e->col;
366 currow = e->row;
367
368 // H
369 } else if (buf->value == L'H') {
370 r->brrow = r->tlrow;
371 r->tlrow = vert_top()->row;
372 currow = r->tlrow;
373
374 // M
375 } else if (buf->value == L'M') {
376 r->tlrow = r->orig_row;
377 int rm = vert_middle()->row;
378 if (r->orig_row < rm) r->brrow = rm;
379 else r->tlrow = rm;
380 currow = r->tlrow;
381
382 // L
383 } else if (buf->value == L'L') {
384 r->tlrow = r->orig_row;
385 r->brrow = vert_bottom()->row;
386 currow = r->brrow;
387
388 // mark a range
389 } else if (buf->value == L'm' && get_bufsize(buf) == 2) {
390 del_ranges_by_mark(buf->pnext->value);
391 srange * rn = create_range('\0', '\0', lookat(r->tlrow, r->tlcol), lookat(r->brrow, r->brcol));
392 set_range_mark(buf->pnext->value, rn);
393 exit_visualmode();
394 chg_mode('.');
395 ui_show_header();
396
397 // auto_justify
398 } else if (buf->value == ctl('j')) {
399 auto_justify(r->tlcol, r->brcol, DEFWIDTH); // auto justify columns
400 exit_visualmode();
401 chg_mode('.');
402 ui_show_header();
403
404 // datefmt with locale D_FMT format
405 } else if (buf->value == ctl('d')) {
406 #ifdef USELOCALE
407 #include <locale.h>
408 #include <langinfo.h>
409 char * loc = NULL;
410 char * f = NULL;
411 loc = setlocale(LC_TIME, "");
412 if (loc != NULL) {
413 f = nl_langinfo(D_FMT);
414 } else {
415 sc_error("No locale set. Nothing changed");
416 }
417 if (any_locked_cells(r->tlrow, r->tlcol, r->brrow, r->brcol)) {
418 sc_error("Locked cells encountered. Nothing changed");
419 return;
420 }
421 dateformat(lookat(r->tlrow, r->tlcol), lookat(r->brrow, r->brcol), f);
422 exit_visualmode();
423 chg_mode('.');
424 ui_show_header();
425 #else
426 sc_info("Build made without USELOCALE enabled");
427 #endif
428
429 // EDITION COMMANDS
430 // yank
431 } else if (buf->value == 'y') {
432 yank_area(r->tlrow, r->tlcol, r->brrow, r->brcol, 'a', 1);
433
434 exit_visualmode();
435 chg_mode('.');
436 ui_show_header();
437
438 // 'p' normal paste
439 // 'P' Works like 'p' except that all cell references are adjusted.
440 } else if (buf->value == 'P' || buf->value == 'p') {
441 struct ent * yl = get_yanklist();
442 int type_paste = (buf->value == 'P') ? 'c' : 'a' ;
443 int row, col;
444 if( yl != NULL) {
445 int colsize = -(yl->col); //calculate colsize for correct repeating if paste area is bigger than yank area
446 int rowsize = -(yl->row); //calculate rowsize
447 while (yl->next != NULL) { yl = yl->next; } //get the last one to calculated size of yank_area
448 colsize += (yl->col +1); //calculate size
449 rowsize += (yl->row +1); //calculate size
450 #ifdef DEBUG
451 char str[20];
452 sprintf(str, "RowSize:%d ColSize:%d Type Paste:%d", rowsize, colsize, type_paste);
453 #endif
454 for (row = r->tlrow; row <= r->brrow; row += rowsize) {
455 for (col = r->tlcol; col <= r->brcol; col += colsize) {
456 currow = row;
457 curcol = col;
458 paste_yanked_ents(0,type_paste);
459 }
460 }
461 exit_visualmode();
462 chg_mode('.');
463 ui_show_header();
464 #ifdef DEBUG
465 sc_info(str);
466 #endif
467 #ifndef DEBUG
468 sc_info("Nice Pasting :-)");
469 #endif
470 }
471 else{
472 exit_visualmode();
473 chg_mode('.');
474 ui_show_header();
475 sc_info("Nothing to Paste");
476 }
477
478 // left / right / center align
479 } else if (buf->value == L'{' || buf->value == L'}' || buf->value == L'|') {
480 if (any_locked_cells(r->tlrow, r->tlcol, r->brrow, r->brcol)) {
481 sc_error("Locked cells encountered. Nothing changed");
482 return;
483 }
484 extern wchar_t interp_line[BUFFERSIZE];
485 if (buf->value == L'{') swprintf(interp_line, BUFFERSIZE, L"leftjustify %s", v_name(r->tlrow, r->tlcol));
486 else if (buf->value == L'}') swprintf(interp_line, BUFFERSIZE, L"rightjustify %s", v_name(r->tlrow, r->tlcol));
487 else if (buf->value == L'|') swprintf(interp_line, BUFFERSIZE, L"center %s", v_name(r->tlrow, r->tlcol));
488 swprintf(interp_line + wcslen(interp_line), BUFFERSIZE, L":%s", v_name(r->brrow, r->brcol));
489 #ifdef UNDO
490 create_undo_action();
491 copy_to_undostruct(r->tlrow, r->tlcol, r->brrow, r->brcol, UNDO_DEL, IGNORE_DEPS, NULL);
492 #endif
493 send_to_interp(interp_line);
494 #ifdef UNDO
495 copy_to_undostruct(r->tlrow, r->tlcol, r->brrow, r->brcol, UNDO_ADD, IGNORE_DEPS, NULL);
496 end_undo_action();
497 #endif
498 cmd_multiplier = 0;
499
500 exit_visualmode();
501 chg_mode('.');
502 ui_show_header();
503
504 // freeze a range
505 } else if (buf->value == L'f') {
506 handle_freeze(lookat(r->tlrow, r->tlcol), lookat(r->brrow, r->brcol), 1, 'r');
507 handle_freeze(lookat(r->tlrow, r->tlcol), lookat(r->brrow, r->brcol), 1, 'c');
508 cmd_multiplier = 0;
509
510 exit_visualmode();
511 chg_mode('.');
512 ui_show_header();
513 sc_info("Area frozen");
514
515 // range lock / unlock // valueize
516 } else if ( buf->value == L'r' && (buf->pnext->value == L'l' || buf->pnext->value == L'u' ||
517 buf->pnext->value == L'v' )) {
518 if (buf->pnext->value == L'l') {
519 lock_cells(lookat(r->tlrow, r->tlcol), lookat(r->brrow, r->brcol));
520 } else if (buf->pnext->value == L'u') {
521 unlock_cells(lookat(r->tlrow, r->tlcol), lookat(r->brrow, r->brcol));
522 } else if (buf->pnext->value == L'v') {
523 valueize_area(r->tlrow, r->tlcol, r->brrow, r->brcol);
524 }
525 cmd_multiplier = 0;
526
527 exit_visualmode();
528 chg_mode('.');
529 ui_show_header();
530
531 // Zr Zc - Zap col or row
532 } else if ( (buf->value == L'Z' || buf->value == L'S') && (buf->pnext->value == L'c' || buf->pnext->value == L'r')) {
533 int arg = buf->pnext->value == L'r' ? r->brrow - r->tlrow + 1 : r->brcol - r->tlcol + 1;
534 if (buf->value == L'Z' && buf->pnext->value == L'r') {
535 hide_row(r->tlrow, arg);
536 } else if (buf->value == L'Z' && buf->pnext->value == L'c') {
537 hide_col(r->tlcol, arg);
538 } else if (buf->value == L'S' && buf->pnext->value == L'r') {
539 show_row(r->tlrow, arg);
540 } else if (buf->value == L'S' && buf->pnext->value == L'c') {
541 show_col(r->tlcol, arg);
542 }
543 cmd_multiplier = 0;
544
545 exit_visualmode();
546 chg_mode('.');
547 ui_show_header();
548
549 // delete selected range
550 } else if (buf->value == L'x' || (buf->value == L'd' && buf->pnext->value == L'd') ) {
551 del_selected_cells();
552 exit_visualmode();
553 chg_mode('.');
554 ui_show_header();
555
556 // shift range
557 } else if (buf->value == L's') {
558 shift(r->tlrow, r->tlcol, r->brrow, r->brcol, buf->pnext->value);
559 exit_visualmode();
560 chg_mode('.');
561 ui_show_header();
562
563 } else if (buf->value == L':') {
564 chg_mode(':');
565 ui_show_header();
566 #ifdef HISTORY_FILE
567 add(commandline_history, L"");
568 #endif
569 ui_handle_cursor();
570 inputline_pos = 0;
571 real_inputline_pos = 0;
572 return;
573 }
574
575 if (visual_submode == '0')
576 ui_update(TRUE);
577 else {
578 ui_update(FALSE);
579 }
580 }
581