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