1 /********************************************************************\
2  * table-allgui.c -- 2D grid table object, embeds cells for i/o     *
3  *                                                                  *
4  * This program is free software; you can redistribute it and/or    *
5  * modify it under the terms of the GNU General Public License as   *
6  * published by the Free Software Foundation; either version 2 of   *
7  * the License, or (at your option) any later version.              *
8  *                                                                  *
9  * This program is distributed in the hope that it will be useful,  *
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of   *
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the    *
12  * GNU General Public License for more details.                     *
13  *                                                                  *
14  * You should have received a copy of the GNU General Public License*
15  * along with this program; if not, contact:                        *
16  *                                                                  *
17  * Free Software Foundation           Voice:  +1-617-542-5942       *
18  * 51 Franklin Street, Fifth Floor    Fax:    +1-617-542-2652       *
19  * Boston, MA  02110-1301,  USA       gnu@gnu.org                   *
20  *                                                                  *
21 \********************************************************************/
22 
23 /*
24  * FILE:
25  * table-allgui.c
26  *
27  * FUNCTION:
28  * Implements the gui-independent parts of the table infrastructure.
29  *
30  * HISTORY:
31  * Copyright (c) 1998,1999,2000 Linas Vepstas
32  * Copyright (c) 2000 Dave Peticolas
33  */
34 
35 #include <config.h>
36 
37 #include <stdio.h>
38 #include <stdlib.h>
39 #include <string.h>
40 
41 #include <glib.h>
42 
43 #include "table-allgui.h"
44 #include "cellblock.h"
45 #include "gnc-engine.h"
46 
47 
48 /** Static Globals *****************************************************/
49 
50 static TableGUIHandlers default_gui_handlers;
51 
52 /* This static indicates the debugging module that this .o belongs to. */
53 static QofLogModule log_module = GNC_MOD_REGISTER;
54 
55 
56 /** Prototypes *********************************************************/
57 static void gnc_table_init (Table * table);
58 static void gnc_table_free_data (Table * table);
59 static void gnc_virtual_cell_construct (gpointer vcell, gpointer user_data);
60 static void gnc_virtual_cell_destroy (gpointer vcell, gpointer user_data);
61 static void gnc_table_resize (Table * table, int virt_rows, int virt_cols);
62 
63 
64 /** Implementation *****************************************************/
65 
66 void
gnc_table_set_default_gui_handlers(TableGUIHandlers * gui_handlers)67 gnc_table_set_default_gui_handlers (TableGUIHandlers *gui_handlers)
68 {
69     if (!gui_handlers)
70         memset (&default_gui_handlers, 0, sizeof (default_gui_handlers));
71     else
72         default_gui_handlers = *gui_handlers;
73 }
74 
75 Table *
gnc_table_new(TableLayout * layout,TableModel * model,TableControl * control)76 gnc_table_new (TableLayout *layout, TableModel *model, TableControl *control)
77 {
78     Table *table;
79 
80     g_return_val_if_fail (layout != NULL, NULL);
81     g_return_val_if_fail (model != NULL, NULL);
82     g_return_val_if_fail (control != NULL, NULL);
83 
84     table = g_new0 (Table, 1);
85 
86     table->layout = layout;
87     table->model = model;
88     table->control = control;
89 
90     table->gui_handlers = default_gui_handlers;
91 
92     gnc_table_init (table);
93 
94     table->virt_cells = g_table_new (sizeof (VirtualCell),
95                                      gnc_virtual_cell_construct,
96                                      gnc_virtual_cell_destroy, table);
97 
98     return table;
99 }
100 
101 static void
gnc_table_init(Table * table)102 gnc_table_init (Table * table)
103 {
104     table->num_virt_rows = -1;
105     table->num_virt_cols = -1;
106 
107     table->current_cursor = NULL;
108 
109     gnc_virtual_location_init (&table->current_cursor_loc);
110 
111     /* initialize private data */
112 
113     table->virt_cells = NULL;
114     table->ui_data = NULL;
115 }
116 
117 void
gnc_table_destroy(Table * table)118 gnc_table_destroy (Table * table)
119 {
120     /* invoke destroy callback */
121     if (table->gui_handlers.destroy)
122         table->gui_handlers.destroy (table);
123 
124     /* free the dynamic structures */
125     gnc_table_free_data (table);
126 
127     /* free the cell tables */
128     g_table_destroy (table->virt_cells);
129 
130     gnc_table_layout_destroy (table->layout);
131     table->layout = NULL;
132 
133     gnc_table_control_destroy (table->control);
134     table->control = NULL;
135 
136     gnc_table_model_destroy (table->model);
137     table->model = NULL;
138 
139     /* initialize vars to null value so that any access is voided. */
140     gnc_table_init (table);
141 
142     g_free (table);
143 }
144 
145 int
gnc_table_current_cursor_changed(Table * table,gboolean include_conditional)146 gnc_table_current_cursor_changed (Table *table,
147                                   gboolean include_conditional)
148 {
149     if (!table)
150         return FALSE;
151 
152     return gnc_cellblock_changed (table->current_cursor, include_conditional);
153 }
154 
155 void
gnc_table_clear_current_cursor_changes(Table * table)156 gnc_table_clear_current_cursor_changes (Table *table)
157 {
158     if (!table)
159         return;
160 
161     gnc_cellblock_clear_changes (table->current_cursor);
162 }
163 
164 void
gnc_table_save_current_cursor(Table * table,CursorBuffer * buffer)165 gnc_table_save_current_cursor (Table *table, CursorBuffer *buffer)
166 {
167     if (!table || !buffer)
168         return;
169 
170     gnc_table_layout_save_cursor (table->layout, table->current_cursor, buffer);
171 }
172 
173 void
gnc_table_restore_current_cursor(Table * table,CursorBuffer * buffer)174 gnc_table_restore_current_cursor (Table *table,
175                                   CursorBuffer *buffer)
176 {
177     if (!table || !buffer)
178         return;
179 
180     gnc_table_layout_restore_cursor (table->layout,
181                                      table->current_cursor, buffer);
182 }
183 
184 const char *
gnc_table_get_current_cell_name(Table * table)185 gnc_table_get_current_cell_name (Table *table)
186 {
187     if (table == NULL)
188         return NULL;
189 
190     return gnc_table_get_cell_name (table, table->current_cursor_loc);
191 }
192 
193 gboolean
gnc_table_get_current_cell_location(Table * table,const char * cell_name,VirtualLocation * virt_loc)194 gnc_table_get_current_cell_location (Table *table,
195                                      const char *cell_name,
196                                      VirtualLocation *virt_loc)
197 {
198     if (table == NULL)
199         return FALSE;
200 
201     return gnc_table_get_cell_location (table, cell_name,
202                                         table->current_cursor_loc.vcell_loc,
203                                         virt_loc);
204 }
205 
206 gboolean
gnc_table_virtual_cell_out_of_bounds(Table * table,VirtualCellLocation vcell_loc)207 gnc_table_virtual_cell_out_of_bounds (Table *table,
208                                       VirtualCellLocation vcell_loc)
209 {
210     if (!table)
211         return TRUE;
212 
213     return ((vcell_loc.virt_row < 0) ||
214             (vcell_loc.virt_row >= table->num_virt_rows) ||
215             (vcell_loc.virt_col < 0) ||
216             (vcell_loc.virt_col >= table->num_virt_cols));
217 }
218 
219 gboolean
gnc_table_virtual_location_in_header(Table * table,VirtualLocation virt_loc)220 gnc_table_virtual_location_in_header (Table *table,
221                                       VirtualLocation virt_loc)
222 {
223     return (virt_loc.vcell_loc.virt_row == 0);
224 }
225 
226 VirtualCell *
gnc_table_get_virtual_cell(Table * table,VirtualCellLocation vcell_loc)227 gnc_table_get_virtual_cell (Table *table, VirtualCellLocation vcell_loc)
228 {
229     if (table == NULL)
230         return NULL;
231 
232     return g_table_index (table->virt_cells,
233                           vcell_loc.virt_row, vcell_loc.virt_col);
234 }
235 
236 VirtualCell *
gnc_table_get_header_cell(Table * table)237 gnc_table_get_header_cell (Table *table)
238 {
239     VirtualCellLocation vcell_loc = { 0, 0 };
240 
241     return gnc_table_get_virtual_cell (table, vcell_loc);
242 }
243 
244 static const char *
gnc_table_get_entry_internal(Table * table,VirtualLocation virt_loc,gboolean * conditionally_changed)245 gnc_table_get_entry_internal (Table *table, VirtualLocation virt_loc,
246                               gboolean *conditionally_changed)
247 {
248     TableGetEntryHandler entry_handler;
249     const char *cell_name;
250     const char *entry;
251 
252     cell_name = gnc_table_get_cell_name (table, virt_loc);
253 
254     entry_handler = gnc_table_model_get_entry_handler (table->model, cell_name);
255     if (!entry_handler) return "";
256 
257     entry = entry_handler (virt_loc, FALSE,
258                            conditionally_changed,
259                            table->model->handler_user_data);
260     if (!entry)
261         entry = "";
262 
263     return entry;
264 }
265 
266 const char *
gnc_table_get_entry(Table * table,VirtualLocation virt_loc)267 gnc_table_get_entry (Table *table, VirtualLocation virt_loc)
268 {
269     TableGetEntryHandler entry_handler;
270     const char *entry;
271     BasicCell *cell;
272 
273     cell = gnc_table_get_cell (table, virt_loc);
274     if (!cell || !cell->cell_name)
275         return "";
276 
277     if (virt_cell_loc_equal (table->current_cursor_loc.vcell_loc,
278                              virt_loc.vcell_loc))
279     {
280         CellIOFlags io_flags;
281 
282         io_flags = gnc_table_get_io_flags (table, virt_loc);
283 
284         if (io_flags & XACC_CELL_ALLOW_INPUT)
285             return cell->value;
286     }
287 
288     entry_handler = gnc_table_model_get_entry_handler (table->model,
289                     cell->cell_name);
290     if (!entry_handler) return "";
291 
292     entry = entry_handler (virt_loc, TRUE, NULL,
293                            table->model->handler_user_data);
294     if (!entry)
295         entry = "";
296 
297     return entry;
298 }
299 
300 char *
gnc_table_get_tooltip(Table * table,VirtualLocation virt_loc)301 gnc_table_get_tooltip (Table *table, VirtualLocation virt_loc)
302 {
303     TableGetTooltipHandler tooltip_handler;
304     BasicCell *cell;
305 
306     cell = gnc_table_get_cell (table, virt_loc);
307     if (!cell || !cell->cell_name)
308         return NULL;
309 
310     tooltip_handler = gnc_table_model_get_tooltip_handler (table->model,
311                          cell->cell_name);
312 
313     if (!tooltip_handler)
314         return NULL;
315 
316     return  tooltip_handler (virt_loc, table->model->handler_user_data);
317 }
318 
319 CellIOFlags
gnc_table_get_io_flags(Table * table,VirtualLocation virt_loc)320 gnc_table_get_io_flags (Table *table, VirtualLocation virt_loc)
321 {
322     TableGetCellIOFlagsHandler io_flags_handler;
323     const char *cell_name;
324     CellIOFlags flags;
325 
326     if (!table || !table->model)
327         return XACC_CELL_ALLOW_NONE;
328 
329     cell_name = gnc_table_get_cell_name (table, virt_loc);
330 
331     io_flags_handler = gnc_table_model_get_io_flags_handler (table->model,
332                        cell_name);
333     if (!io_flags_handler)
334         return XACC_CELL_ALLOW_NONE;
335 
336     flags = io_flags_handler (virt_loc, table->model->handler_user_data);
337 
338     if (gnc_table_model_read_only (table->model))
339         flags &= XACC_CELL_ALLOW_SHADOW;
340 
341     return flags;
342 }
343 
344 const char *
gnc_table_get_label(Table * table,VirtualLocation virt_loc)345 gnc_table_get_label (Table *table, VirtualLocation virt_loc)
346 {
347     TableGetLabelHandler label_handler;
348     const char *cell_name;
349     const char *label;
350 
351     if (!table || !table->model)
352         return "";
353 
354     cell_name = gnc_table_get_cell_name (table, virt_loc);
355 
356     label_handler = gnc_table_model_get_label_handler (table->model, cell_name);
357     if (!label_handler)
358         return "";
359 
360     label = label_handler (virt_loc, table->model->handler_user_data);
361     if (!label)
362         return "";
363 
364     return label;
365 }
366 
367 guint32
gnc_table_get_color(Table * table,VirtualLocation virt_loc,gboolean * hatching)368 gnc_table_get_color (Table *table, VirtualLocation virt_loc,
369                                  gboolean *hatching)
370 {
371     TableGetCellColorHandler color_handler;
372     const char *handler_name;
373 
374     if (hatching)
375         *hatching = FALSE;
376 
377     if (!table || !table->model)
378         return COLOR_UNDEFINED;
379 
380     handler_name = gnc_table_get_cell_name (table, virt_loc);
381 
382     color_handler = gnc_table_model_get_cell_color_handler (table->model,
383                                                             handler_name);
384 
385     if (!color_handler)
386         return COLOR_UNDEFINED;
387 
388     return color_handler (virt_loc, hatching,
389                           table->model->handler_user_data);
390 }
391 
392 void
gnc_table_get_borders(Table * table,VirtualLocation virt_loc,PhysicalCellBorders * borders)393 gnc_table_get_borders (Table *table, VirtualLocation virt_loc,
394                        PhysicalCellBorders *borders)
395 {
396     TableGetCellBorderHandler cell_border_handler;
397     const char *cell_name;
398 
399     if (!table || !table->model)
400         return;
401 
402     cell_name = gnc_table_get_cell_name (table, virt_loc);
403 
404     cell_border_handler = gnc_table_model_get_cell_border_handler (table->model,
405                           cell_name);
406     if (!cell_border_handler)
407         return;
408 
409     cell_border_handler (virt_loc, borders, table->model->handler_user_data);
410 }
411 
412 CellAlignment
gnc_table_get_align(Table * table,VirtualLocation virt_loc)413 gnc_table_get_align (Table *table, VirtualLocation virt_loc)
414 {
415     BasicCell *cell;
416 
417     cell = gnc_table_get_cell (table, virt_loc);
418     if (!cell)
419         return CELL_ALIGN_RIGHT;
420 
421     return cell->alignment;
422 }
423 
424 gboolean
gnc_table_is_popup(Table * table,VirtualLocation virt_loc)425 gnc_table_is_popup (Table *table, VirtualLocation virt_loc)
426 {
427     BasicCell *cell;
428 
429     cell = gnc_table_get_cell (table, virt_loc);
430     if (!cell)
431         return FALSE;
432 
433     return cell->is_popup;
434 }
435 
436 char *
gnc_table_get_help(Table * table)437 gnc_table_get_help (Table *table)
438 {
439     TableGetHelpHandler help_handler;
440     VirtualLocation virt_loc;
441     const char * cell_name;
442 
443     if (!table)
444         return NULL;
445 
446     virt_loc = table->current_cursor_loc;
447 
448     cell_name = gnc_table_get_cell_name (table, virt_loc);
449 
450     help_handler = gnc_table_model_get_help_handler (table->model, cell_name);
451     if (!help_handler)
452         return NULL;
453 
454     return help_handler (virt_loc, table->model->handler_user_data);
455 }
456 
457 BasicCell *
gnc_table_get_cell(Table * table,VirtualLocation virt_loc)458 gnc_table_get_cell (Table *table, VirtualLocation virt_loc)
459 {
460     VirtualCell *vcell;
461 
462     if (!table)
463         return NULL;
464 
465     vcell = gnc_table_get_virtual_cell (table, virt_loc.vcell_loc);
466     if (!vcell)
467         return NULL;
468 
469     return gnc_cellblock_get_cell (vcell->cellblock,
470                                    virt_loc.phys_row_offset,
471                                    virt_loc.phys_col_offset);
472 }
473 
474 const char *
gnc_table_get_cell_name(Table * table,VirtualLocation virt_loc)475 gnc_table_get_cell_name (Table *table, VirtualLocation virt_loc)
476 {
477     BasicCell *cell;
478 
479     cell = gnc_table_get_cell (table, virt_loc);
480     if (cell == NULL)
481         return NULL;
482 
483     return cell->cell_name;
484 }
485 
486 const gchar *
gnc_table_get_cell_type_name(Table * table,VirtualLocation virt_loc)487 gnc_table_get_cell_type_name (Table *table, VirtualLocation virt_loc)
488 {
489     BasicCell *cell;
490 
491     cell = gnc_table_get_cell (table, virt_loc);
492     if (cell == NULL)
493         return NULL;
494 
495     return cell->cell_type_name;
496 }
497 
498 
499 gboolean
gnc_table_get_cell_location(Table * table,const char * cell_name,VirtualCellLocation vcell_loc,VirtualLocation * virt_loc)500 gnc_table_get_cell_location (Table *table,
501                              const char *cell_name,
502                              VirtualCellLocation vcell_loc,
503                              VirtualLocation *virt_loc)
504 {
505     VirtualCell *vcell;
506     CellBlock *cellblock;
507     int cell_row, cell_col;
508 
509     if (table == NULL)
510         return FALSE;
511 
512     vcell = gnc_table_get_virtual_cell (table, vcell_loc);
513     if (vcell == NULL)
514         return FALSE;
515 
516     cellblock = vcell->cellblock;
517 
518     for (cell_row = 0; cell_row < cellblock->num_rows; cell_row++)
519         for (cell_col = 0; cell_col < cellblock->num_cols; cell_col++)
520         {
521             BasicCell *cell;
522 
523             cell = gnc_cellblock_get_cell (cellblock, cell_row, cell_col);
524             if (!cell)
525                 continue;
526 
527             if (gnc_basic_cell_has_name (cell, cell_name))
528             {
529                 if (virt_loc != NULL)
530                 {
531                     virt_loc->vcell_loc = vcell_loc;
532 
533                     virt_loc->phys_row_offset = cell_row;
534                     virt_loc->phys_col_offset = cell_col;
535                 }
536 
537                 return TRUE;
538             }
539         }
540 
541     return FALSE;
542 }
543 
544 void
gnc_table_save_cells(Table * table,gpointer save_data)545 gnc_table_save_cells (Table *table, gpointer save_data)
546 {
547     TableSaveHandler save_handler;
548     GList * cells;
549     GList * node;
550 
551     g_return_if_fail (table);
552 
553     /* ignore any changes to read-only tables */
554     if (gnc_table_model_read_only (table->model))
555         return;
556 
557     // gnc_table_leave_update (table, table->current_cursor_loc);
558 
559     save_handler = gnc_table_model_get_pre_save_handler (table->model);
560     if (save_handler)
561         save_handler (save_data, table->model->handler_user_data);
562 
563     cells = gnc_table_layout_get_cells (table->layout);
564     for (node = cells; node; node = node->next)
565     {
566         BasicCell * cell = node->data;
567         TableSaveCellHandler save_cell_handler;
568 
569         if (!cell) continue;
570 
571         if (!gnc_table_layout_get_cell_changed (table->layout,
572                                                 cell->cell_name, TRUE))
573             continue;
574 
575         save_cell_handler = gnc_table_model_get_save_handler (table->model,
576                             cell->cell_name);
577         if (save_cell_handler)
578             save_cell_handler (cell, save_data, table->model->handler_user_data);
579     }
580 
581     save_handler = gnc_table_model_get_post_save_handler (table->model);
582     if (save_handler)
583         save_handler (save_data, table->model->handler_user_data);
584 }
585 
586 void
gnc_table_set_size(Table * table,int virt_rows,int virt_cols)587 gnc_table_set_size (Table * table, int virt_rows, int virt_cols)
588 {
589     /* Invalidate the current cursor position, if the array is
590      * shrinking. This must be done since the table is probably
591      * shrinking because some rows were deleted, and the cursor
592      * could be on the deleted rows. */
593     if ((virt_rows < table->num_virt_rows) ||
594             (virt_cols < table->num_virt_cols))
595     {
596         gnc_virtual_location_init (&table->current_cursor_loc);
597         table->current_cursor = NULL;
598     }
599 
600     gnc_table_resize (table, virt_rows, virt_cols);
601 }
602 
603 static void
gnc_table_free_data(Table * table)604 gnc_table_free_data (Table * table)
605 {
606     if (table == NULL)
607         return;
608 
609     g_table_resize (table->virt_cells, 0, 0);
610 }
611 
612 void
gnc_virtual_location_init(VirtualLocation * vloc)613 gnc_virtual_location_init (VirtualLocation *vloc)
614 {
615     if (vloc == NULL)
616         return;
617 
618     vloc->phys_row_offset = -1;
619     vloc->phys_col_offset = -1;
620     vloc->vcell_loc.virt_row = -1;
621     vloc->vcell_loc.virt_col = -1;
622 }
623 
624 static void
gnc_virtual_cell_construct(gpointer _vcell,gpointer user_data)625 gnc_virtual_cell_construct (gpointer _vcell, gpointer user_data)
626 {
627     VirtualCell *vcell = _vcell;
628     Table *table = user_data;
629 
630     vcell->cellblock = NULL;
631 
632     if (table && table->model->cell_data_allocator)
633         vcell->vcell_data = table->model->cell_data_allocator ();
634     else
635         vcell->vcell_data = NULL;
636 
637     vcell->visible = 1;
638 }
639 
640 static void
gnc_virtual_cell_destroy(gpointer _vcell,gpointer user_data)641 gnc_virtual_cell_destroy (gpointer _vcell, gpointer user_data)
642 {
643     VirtualCell *vcell = _vcell;
644     Table *table = user_data;
645 
646     if (vcell->vcell_data && table && table->model->cell_data_deallocator)
647         table->model->cell_data_deallocator (vcell->vcell_data);
648 
649     vcell->vcell_data = NULL;
650 }
651 
652 static void
gnc_table_resize(Table * table,int new_virt_rows,int new_virt_cols)653 gnc_table_resize (Table * table, int new_virt_rows, int new_virt_cols)
654 {
655     if (!table) return;
656 
657     g_table_resize (table->virt_cells, new_virt_rows, new_virt_cols);
658 
659     table->num_virt_rows = new_virt_rows;
660     table->num_virt_cols = new_virt_cols;
661 }
662 
663 void
gnc_table_set_vcell(Table * table,CellBlock * cursor,gconstpointer vcell_data,gboolean visible,gboolean start_primary_color,VirtualCellLocation vcell_loc)664 gnc_table_set_vcell (Table *table,
665                      CellBlock *cursor,
666                      gconstpointer vcell_data,
667                      gboolean visible,
668                      gboolean start_primary_color,
669                      VirtualCellLocation vcell_loc)
670 {
671     VirtualCell *vcell;
672 
673     if ((table == NULL) || (cursor == NULL))
674         return;
675 
676     if ((vcell_loc.virt_row >= table->num_virt_rows) ||
677             (vcell_loc.virt_col >= table->num_virt_cols))
678         gnc_table_resize (table,
679                           MAX (table->num_virt_rows, vcell_loc.virt_row + 1),
680                           MAX (table->num_virt_cols, vcell_loc.virt_col + 1));
681 
682     vcell = gnc_table_get_virtual_cell (table, vcell_loc);
683     if (vcell == NULL)
684         return;
685 
686     /* this cursor is the handler for this block */
687     vcell->cellblock = cursor;
688 
689     /* copy the vcell user data */
690     if (table->model->cell_data_copy)
691         table->model->cell_data_copy (vcell->vcell_data, vcell_data);
692     else
693         vcell->vcell_data = (gpointer) vcell_data;
694 
695     vcell->visible = visible ? 1 : 0;
696     vcell->start_primary_color = start_primary_color ? 1 : 0;
697 }
698 
699 void
gnc_table_set_virt_cell_data(Table * table,VirtualCellLocation vcell_loc,gconstpointer vcell_data)700 gnc_table_set_virt_cell_data (Table *table,
701                               VirtualCellLocation vcell_loc,
702                               gconstpointer vcell_data)
703 {
704     VirtualCell *vcell;
705 
706     if (table == NULL)
707         return;
708 
709     vcell = gnc_table_get_virtual_cell (table, vcell_loc);
710     if (vcell == NULL)
711         return;
712 
713     if (table->model->cell_data_copy)
714         table->model->cell_data_copy (vcell->vcell_data, vcell_data);
715     else
716         vcell->vcell_data = (gpointer) vcell_data;
717 }
718 
719 void
gnc_table_set_virt_cell_visible(Table * table,VirtualCellLocation vcell_loc,gboolean visible)720 gnc_table_set_virt_cell_visible (Table *table,
721                                  VirtualCellLocation vcell_loc,
722                                  gboolean visible)
723 {
724     VirtualCell *vcell;
725 
726     if (table == NULL)
727         return;
728 
729     vcell = gnc_table_get_virtual_cell (table, vcell_loc);
730     if (vcell == NULL)
731         return;
732 
733     vcell->visible = visible ? 1 : 0;
734 }
735 
736 void
gnc_table_set_virt_cell_cursor(Table * table,VirtualCellLocation vcell_loc,CellBlock * cursor)737 gnc_table_set_virt_cell_cursor (Table *table,
738                                 VirtualCellLocation vcell_loc,
739                                 CellBlock *cursor)
740 {
741     VirtualCell *vcell;
742 
743     if (table == NULL)
744         return;
745 
746     vcell = gnc_table_get_virtual_cell (table, vcell_loc);
747     if (vcell == NULL)
748         return;
749 
750     vcell->cellblock = cursor;
751 }
752 
753 static void
gnc_table_move_cursor_internal(Table * table,VirtualLocation new_virt_loc,gboolean do_move_gui)754 gnc_table_move_cursor_internal (Table *table,
755                                 VirtualLocation new_virt_loc,
756                                 gboolean do_move_gui)
757 {
758     int cell_row, cell_col;
759     VirtualLocation virt_loc;
760     VirtualCell *vcell;
761     CellBlock *curs;
762 
763     ENTER("new_virt=(%d %d) do_move_gui=%d\n",
764           new_virt_loc.vcell_loc.virt_row,
765           new_virt_loc.vcell_loc.virt_col, do_move_gui);
766 
767     /* call the callback, allowing the app to commit any changes
768      * associated with the current location of the cursor. Note that
769      * this callback may recursively call this routine. */
770     if (table->control->move_cursor && table->control->allow_move)
771     {
772         table->control->move_cursor (&new_virt_loc, table->control->user_data);
773 
774         /* The above callback can cause this routine to be called
775          * recursively. As a result of this recursion, the cursor may
776          * have gotten repositioned. We need to make sure we make
777          * passive again. */
778         if (do_move_gui)
779             gnc_table_refresh_current_cursor_gui (table, FALSE);
780     }
781 
782     /* invalidate the cursor for now; we'll fix it back up below */
783     gnc_virtual_location_init (&table->current_cursor_loc);
784 
785     curs = table->current_cursor;
786     table->current_cursor = NULL;
787 
788     /* check for out-of-bounds conditions (which may be deliberate) */
789     if ((new_virt_loc.vcell_loc.virt_row < 0) ||
790             (new_virt_loc.vcell_loc.virt_col < 0))
791     {
792         /* if the location is invalid, then we should take this
793          * as a command to unmap the cursor gui. */
794         if (do_move_gui && curs)
795         {
796             for (cell_row = 0; cell_row < curs->num_rows; cell_row++)
797                 for (cell_col = 0; cell_col < curs->num_cols; cell_col++)
798                 {
799                     BasicCell *cell;
800 
801                     cell = gnc_cellblock_get_cell (curs, cell_row, cell_col);
802                     if (cell)
803                     {
804                         cell->changed = FALSE;
805                         cell->conditionally_changed = FALSE;
806 
807                         if (cell->gui_move)
808                             cell->gui_move (cell);
809                     }
810                 }
811         }
812 
813         LEAVE("out of bounds\n");
814         return;
815     }
816 
817     if (!gnc_table_virtual_loc_valid (table, new_virt_loc, TRUE))
818     {
819         PWARN("bad table location");
820         LEAVE("");
821         return;
822     }
823 
824     /* ok, we now have a valid position. Find the new cursor to use,
825      * and initialize its cells */
826     vcell = gnc_table_get_virtual_cell (table, new_virt_loc.vcell_loc);
827     curs = vcell->cellblock;
828     table->current_cursor = curs;
829 
830     /* record the new position */
831     table->current_cursor_loc = new_virt_loc;
832 
833     virt_loc.vcell_loc = new_virt_loc.vcell_loc;
834 
835     /* update the cell values to reflect the new position */
836     for (cell_row = 0; cell_row < curs->num_rows; cell_row++)
837         for (cell_col = 0; cell_col < curs->num_cols; cell_col++)
838         {
839             BasicCell *cell;
840             CellIOFlags io_flags;
841 
842             virt_loc.phys_row_offset = cell_row;
843             virt_loc.phys_col_offset = cell_col;
844 
845             cell = gnc_cellblock_get_cell(curs, cell_row, cell_col);
846             if (cell)
847             {
848                 /* if a cell has a GUI, move that first, before setting
849                  * the cell value.  Otherwise, we'll end up putting the
850                  * new values in the old cell locations, and that would
851                  * lead to confusion of all sorts. */
852                 if (do_move_gui && cell->gui_move)
853                     cell->gui_move (cell);
854 
855                 /* OK, now copy the string value from the table at large
856                  * into the cell handler. */
857                 io_flags = gnc_table_get_io_flags (table, virt_loc);
858                 if (io_flags & XACC_CELL_ALLOW_SHADOW)
859                 {
860                     const char *entry;
861                     gboolean conditionally_changed = FALSE;
862 
863                     entry = gnc_table_get_entry_internal (table, virt_loc,
864                                                           &conditionally_changed);
865 
866                     gnc_basic_cell_set_value (cell, entry);
867 
868                     cell->changed = FALSE;
869                     cell->conditionally_changed = conditionally_changed;
870                 }
871             }
872         }
873 
874     LEAVE("did move\n");
875 }
876 
877 void
gnc_table_move_cursor(Table * table,VirtualLocation new_virt_loc)878 gnc_table_move_cursor (Table *table, VirtualLocation new_virt_loc)
879 {
880     if (!table) return;
881 
882     gnc_table_move_cursor_internal (table, new_virt_loc, FALSE);
883 }
884 
885 /* same as above, but be sure to deal with GUI elements as well */
886 void
gnc_table_move_cursor_gui(Table * table,VirtualLocation new_virt_loc)887 gnc_table_move_cursor_gui (Table *table, VirtualLocation new_virt_loc)
888 {
889     if (!table) return;
890 
891     gnc_table_move_cursor_internal (table, new_virt_loc, TRUE);
892 }
893 
894 /* gnc_table_verify_cursor_position checks the location of the cursor
895  * with respect to a virtual location, and repositions the cursor
896  * if necessary. Returns true if the cell cursor was repositioned. */
897 gboolean
gnc_table_verify_cursor_position(Table * table,VirtualLocation virt_loc)898 gnc_table_verify_cursor_position (Table *table, VirtualLocation virt_loc)
899 {
900     gboolean do_move = FALSE;
901     gboolean moved_cursor = FALSE;
902 
903     if (!table) return FALSE;
904 
905     /* Someone may be trying to intentionally invalidate the cursor, in
906      * which case the physical addresses could be out of bounds. For
907      * example, in order to unmap it in preparation for a reconfig.
908      * So, if the specified location is out of bounds, then the cursor
909      * MUST be moved. */
910     if (gnc_table_virtual_cell_out_of_bounds (table, virt_loc.vcell_loc))
911         do_move = TRUE;
912 
913     if (!virt_cell_loc_equal (virt_loc.vcell_loc,
914                               table->current_cursor_loc.vcell_loc))
915         do_move = TRUE;
916 
917     if (do_move)
918     {
919         gnc_table_move_cursor_gui (table, virt_loc);
920         moved_cursor = TRUE;
921     }
922     else if (!virt_loc_equal (virt_loc, table->current_cursor_loc))
923     {
924         table->current_cursor_loc = virt_loc;
925         moved_cursor = TRUE;
926     }
927 
928     return moved_cursor;
929 }
930 
931 gpointer
gnc_table_get_vcell_data(Table * table,VirtualCellLocation vcell_loc)932 gnc_table_get_vcell_data (Table *table, VirtualCellLocation vcell_loc)
933 {
934     VirtualCell *vcell;
935 
936     if (!table) return NULL;
937 
938     vcell = gnc_table_get_virtual_cell (table, vcell_loc);
939     if (vcell == NULL)
940         return NULL;
941 
942     return vcell->vcell_data;
943 }
944 
945 /* If any of the cells have GUI specific components that need
946  * initialization, initialize them now. The realize() callback
947  * on the cursor cell is how we inform the cell handler that
948  * now is the time to initialize its GUI.  */
949 void
gnc_table_realize_gui(Table * table)950 gnc_table_realize_gui (Table * table)
951 {
952     GList *cells;
953     GList *node;
954 
955     if (!table) return;
956     if (!table->ui_data) return;
957 
958     cells = gnc_table_layout_get_cells (table->layout);
959 
960     for (node = cells; node; node = node->next)
961     {
962         BasicCell *cell = node->data;
963 
964         if (cell->gui_realize)
965             cell->gui_realize (cell, table->ui_data);
966     }
967 }
968 
969 void
gnc_table_wrap_verify_cursor_position(Table * table,VirtualLocation virt_loc)970 gnc_table_wrap_verify_cursor_position (Table *table, VirtualLocation virt_loc)
971 {
972     VirtualLocation save_loc;
973     gboolean moved_cursor;
974 
975     if (!table) return;
976 
977     ENTER("(%d %d)", virt_loc.vcell_loc.virt_row, virt_loc.vcell_loc.virt_col);
978 
979     save_loc = table->current_cursor_loc;
980 
981     /* VerifyCursor will do all sorts of gui-independent machinations */
982     moved_cursor = gnc_table_verify_cursor_position (table, virt_loc);
983 
984     if (moved_cursor)
985     {
986         /* make sure *both* the old and the new cursor rows get redrawn */
987         gnc_table_refresh_current_cursor_gui (table, TRUE);
988         gnc_table_refresh_cursor_gui (table, save_loc.vcell_loc, FALSE);
989     }
990 
991     LEAVE ("");
992 }
993 
994 void
gnc_table_refresh_current_cursor_gui(Table * table,gboolean do_scroll)995 gnc_table_refresh_current_cursor_gui (Table * table, gboolean do_scroll)
996 {
997     if (!table) return;
998 
999     gnc_table_refresh_cursor_gui (table, table->current_cursor_loc.vcell_loc,
1000                                   do_scroll);
1001 }
1002 
1003 gboolean
gnc_table_virtual_loc_valid(Table * table,VirtualLocation virt_loc,gboolean exact_pointer)1004 gnc_table_virtual_loc_valid(Table *table,
1005                             VirtualLocation virt_loc,
1006                             gboolean exact_pointer)
1007 {
1008     VirtualCell *vcell;
1009     CellIOFlags io_flags;
1010 
1011     if (!table) return FALSE;
1012 
1013     /* header rows cannot be modified */
1014     if (virt_loc.vcell_loc.virt_row == 0)
1015         return FALSE;
1016 
1017     vcell = gnc_table_get_virtual_cell(table, virt_loc.vcell_loc);
1018     if (vcell == NULL)
1019         return FALSE;
1020 
1021     if (!vcell->visible)
1022         return FALSE;
1023 
1024     /* verify that offsets are valid. This may occur if the app that is
1025      * using the table has a partially initialized cursor. (probably due
1026      * to a programming error, but maybe they meant to do this). */
1027     if ((0 > virt_loc.phys_row_offset) || (0 > virt_loc.phys_col_offset))
1028         return FALSE;
1029 
1030     /* check for a cell handler, but only if cell address is valid */
1031     if (vcell->cellblock == NULL) return FALSE;
1032 
1033     /* if table is read-only, any cell is ok :) */
1034     if (gnc_table_model_read_only (table->model)) return TRUE;
1035 
1036     io_flags = gnc_table_get_io_flags (table, virt_loc);
1037 
1038     /* if the cell allows ENTER, then it is ok */
1039     if (io_flags & XACC_CELL_ALLOW_ENTER) return TRUE;
1040 
1041     /* if cell is marked as output-only, you can't enter */
1042     if (0 == (XACC_CELL_ALLOW_INPUT & io_flags)) return FALSE;
1043 
1044     /* if cell is pointer only and this is not an exact pointer test,
1045      * it cannot be entered. */
1046     if (!exact_pointer && ((XACC_CELL_ALLOW_EXACT_ONLY & io_flags) != 0))
1047         return FALSE;
1048 
1049     return TRUE;
1050 }
1051 
1052 /* Handle the non gui-specific parts of a cell enter callback */
1053 gboolean
gnc_table_enter_update(Table * table,VirtualLocation virt_loc,int * cursor_position,int * start_selection,int * end_selection)1054 gnc_table_enter_update (Table *table,
1055                         VirtualLocation virt_loc,
1056                         int *cursor_position,
1057                         int *start_selection,
1058                         int *end_selection)
1059 {
1060     gboolean can_edit = TRUE;
1061     CellEnterFunc enter;
1062     BasicCell *cell;
1063     CellBlock *cb;
1064     int cell_row;
1065     int cell_col;
1066     CellIOFlags io_flags;
1067 
1068     if (table == NULL)
1069         return FALSE;
1070 
1071     cb = table->current_cursor;
1072 
1073     cell_row = virt_loc.phys_row_offset;
1074     cell_col = virt_loc.phys_col_offset;
1075 
1076     ENTER("enter %d %d (relrow=%d relcol=%d)",
1077           virt_loc.vcell_loc.virt_row,
1078           virt_loc.vcell_loc.virt_col,
1079           cell_row, cell_col);
1080 
1081     /* OK, if there is a callback for this cell, call it */
1082     cell = gnc_cellblock_get_cell (cb, cell_row, cell_col);
1083     if (!cell)
1084     {
1085         LEAVE("no cell");
1086         return FALSE;
1087     }
1088 
1089     io_flags = gnc_table_get_io_flags (table, virt_loc);
1090     if (io_flags == XACC_CELL_ALLOW_READ_ONLY)
1091     {
1092         LEAVE("read only cell");
1093         return FALSE;
1094     }
1095 
1096     enter = cell->enter_cell;
1097 
1098     if (enter)
1099     {
1100         char * old_value;
1101 
1102         DEBUG("gnc_table_enter_update(): %d %d has enter handler\n",
1103               cell_row, cell_col);
1104 
1105         old_value = g_strdup (cell->value);
1106 
1107         can_edit = enter (cell, cursor_position, start_selection, end_selection);
1108 
1109         if (g_strcmp0 (old_value, cell->value) != 0)
1110         {
1111             if (gnc_table_model_read_only (table->model))
1112             {
1113                 PWARN ("enter update changed read-only table");
1114             }
1115 
1116             cell->changed = TRUE;
1117         }
1118 
1119         g_free (old_value);
1120     }
1121 
1122     if (table->gui_handlers.redraw_help)
1123         table->gui_handlers.redraw_help (table);
1124 
1125     LEAVE("return %d\n", can_edit);
1126     return can_edit;
1127 }
1128 
1129 void
gnc_table_leave_update(Table * table,VirtualLocation virt_loc)1130 gnc_table_leave_update (Table *table, VirtualLocation virt_loc)
1131 {
1132     CellLeaveFunc leave;
1133     BasicCell *cell;
1134     CellBlock *cb;
1135     int cell_row;
1136     int cell_col;
1137 
1138     if (table == NULL)
1139         return;
1140 
1141     cb = table->current_cursor;
1142 
1143     cell_row = virt_loc.phys_row_offset;
1144     cell_col = virt_loc.phys_col_offset;
1145 
1146     ENTER("proposed (%d %d) rel(%d %d)\n",
1147           virt_loc.vcell_loc.virt_row,
1148           virt_loc.vcell_loc.virt_col,
1149           cell_row, cell_col);
1150 
1151     /* OK, if there is a callback for this cell, call it */
1152     cell = gnc_cellblock_get_cell (cb, cell_row, cell_col);
1153     if (!cell)
1154     {
1155         LEAVE("no cell");
1156         return;
1157     }
1158 
1159     leave = cell->leave_cell;
1160 
1161     if (leave)
1162     {
1163         char * old_value;
1164 
1165         old_value = g_strdup (cell->value);
1166 
1167         leave (cell);
1168 
1169         if (g_strcmp0 (old_value, cell->value) != 0)
1170         {
1171             if (gnc_table_model_read_only (table->model))
1172             {
1173                 PWARN ("leave update changed read-only table");
1174             }
1175 
1176             cell->changed = TRUE;
1177         }
1178 
1179         g_free (old_value);
1180     }
1181     LEAVE("");
1182 }
1183 
1184 gboolean
gnc_table_confirm_change(Table * table,VirtualLocation virt_loc)1185 gnc_table_confirm_change (Table *table, VirtualLocation virt_loc)
1186 {
1187     TableConfirmHandler confirm_handler;
1188     const char *cell_name;
1189 
1190     if (!table || !table->model)
1191         return TRUE;
1192 
1193     cell_name = gnc_table_get_cell_name (table, virt_loc);
1194 
1195     confirm_handler = gnc_table_model_get_confirm_handler (table->model,
1196                       cell_name);
1197     if (!confirm_handler)
1198         return TRUE;
1199 
1200     return confirm_handler (virt_loc, table->model->handler_user_data);
1201 }
1202 
1203 /* Returned result should not be touched by the caller.
1204  * NULL return value means the edit was rejected. */
1205 const char *
gnc_table_modify_update(Table * table,VirtualLocation virt_loc,const char * change,int change_len,const char * newval,int newval_len,int * cursor_position,int * start_selection,int * end_selection,gboolean * cancelled)1206 gnc_table_modify_update (Table *table,
1207                          VirtualLocation virt_loc,
1208                          const char *change,
1209                          int change_len,
1210                          const char *newval,
1211                          int newval_len,
1212                          int *cursor_position,
1213                          int *start_selection,
1214                          int *end_selection,
1215                          gboolean *cancelled)
1216 {
1217     gboolean changed = FALSE;
1218     CellModifyVerifyFunc mv;
1219     BasicCell *cell;
1220     CellBlock *cb;
1221     int cell_row;
1222     int cell_col;
1223     char * old_value;
1224 
1225     g_return_val_if_fail (table, NULL);
1226     g_return_val_if_fail (table->model, NULL);
1227 
1228     if (gnc_table_model_read_only (table->model))
1229     {
1230         PWARN ("change to read-only table");
1231         return NULL;
1232     }
1233 
1234     cb = table->current_cursor;
1235 
1236     cell_row = virt_loc.phys_row_offset;
1237     cell_col = virt_loc.phys_col_offset;
1238 
1239     ENTER ("");
1240 
1241     if (!gnc_table_confirm_change (table, virt_loc))
1242     {
1243         if (cancelled)
1244             *cancelled = TRUE;
1245 
1246         LEAVE("change cancelled");
1247         return NULL;
1248     }
1249 
1250     if (cancelled)
1251         *cancelled = FALSE;
1252 
1253     /* OK, if there is a callback for this cell, call it */
1254     cell = gnc_cellblock_get_cell (cb, cell_row, cell_col);
1255     if (!cell)
1256     {
1257         LEAVE("no cell");
1258         return NULL;
1259     }
1260 
1261     mv = cell->modify_verify;
1262 
1263     old_value = g_strdup (cell->value);
1264 
1265     if (mv)
1266     {
1267         mv (cell, change, change_len, newval, newval_len,
1268             cursor_position, start_selection, end_selection);
1269     }
1270     else
1271     {
1272         gnc_basic_cell_set_value (cell, newval);
1273     }
1274 
1275     if (g_strcmp0 (old_value, cell->value) != 0)
1276     {
1277         changed = TRUE;
1278         cell->changed = TRUE;
1279     }
1280 
1281     g_free (old_value);
1282 
1283     if (table->gui_handlers.redraw_help)
1284         table->gui_handlers.redraw_help (table);
1285 
1286     LEAVE ("change %d %d (relrow=%d relcol=%d) val=%s\n",
1287            virt_loc.vcell_loc.virt_row,
1288            virt_loc.vcell_loc.virt_col,
1289            cell_row, cell_col,
1290            cell->value ? cell->value : "(null)");
1291 
1292     if (changed)
1293         return cell->value;
1294     else
1295         return NULL;
1296 }
1297 
1298 gboolean
gnc_table_direct_update(Table * table,VirtualLocation virt_loc,char ** newval_ptr,int * cursor_position,int * start_selection,int * end_selection,gpointer gui_data)1299 gnc_table_direct_update (Table *table,
1300                          VirtualLocation virt_loc,
1301                          char **newval_ptr,
1302                          int *cursor_position,
1303                          int *start_selection,
1304                          int *end_selection,
1305                          gpointer gui_data)
1306 {
1307     gboolean result;
1308     BasicCell *cell;
1309     CellBlock *cb;
1310     int cell_row;
1311     int cell_col;
1312     char * old_value;
1313 
1314     g_return_val_if_fail (table, FALSE);
1315     g_return_val_if_fail (table->model, FALSE);
1316 
1317     if (gnc_table_model_read_only (table->model))
1318     {
1319         PWARN ("input to read-only table");
1320         return FALSE;
1321     }
1322 
1323     cb = table->current_cursor;
1324 
1325     cell_row = virt_loc.phys_row_offset;
1326     cell_col = virt_loc.phys_col_offset;
1327 
1328     cell = gnc_cellblock_get_cell (cb, cell_row, cell_col);
1329     if (!cell)
1330         return FALSE;
1331 
1332     ENTER ("");
1333 
1334     if (cell->direct_update == NULL)
1335     {
1336         LEAVE("no direct update");
1337         return FALSE;
1338     }
1339 
1340     old_value = g_strdup (cell->value);
1341 
1342     result = cell->direct_update (cell, cursor_position, start_selection,
1343                                   end_selection, gui_data);
1344 
1345     if (g_strcmp0 (old_value, cell->value) != 0)
1346     {
1347         if (!gnc_table_confirm_change (table, virt_loc))
1348         {
1349             gnc_basic_cell_set_value (cell, old_value);
1350             *newval_ptr = NULL;
1351             result = TRUE;
1352         }
1353         else
1354         {
1355             cell->changed = TRUE;
1356             *newval_ptr = cell->value;
1357         }
1358     }
1359     else
1360         *newval_ptr = NULL;
1361 
1362     g_free (old_value);
1363 
1364     if (table->gui_handlers.redraw_help)
1365         table->gui_handlers.redraw_help (table);
1366 
1367     LEAVE("");
1368     return result;
1369 }
1370 
1371 static gboolean gnc_table_find_valid_cell_horiz (Table *table,
1372         VirtualLocation *virt_loc,
1373         gboolean exact_cell);
1374 
1375 static gboolean
gnc_table_find_valid_row_vert(Table * table,VirtualLocation * virt_loc)1376 gnc_table_find_valid_row_vert (Table *table, VirtualLocation *virt_loc)
1377 {
1378     VirtualLocation vloc;
1379     VirtualCell *vcell = NULL;
1380     int top;
1381     int bottom;
1382 
1383     if (table == NULL)
1384         return FALSE;
1385 
1386     if (virt_loc == NULL)
1387         return FALSE;
1388 
1389     vloc = *virt_loc;
1390 
1391     if (vloc.vcell_loc.virt_row < 1)
1392         vloc.vcell_loc.virt_row = 1;
1393     if (vloc.vcell_loc.virt_row >= table->num_virt_rows)
1394         vloc.vcell_loc.virt_row = table->num_virt_rows - 1;
1395 
1396     top  = vloc.vcell_loc.virt_row;
1397     bottom = vloc.vcell_loc.virt_row + 1;
1398 
1399     while (top >= 1 || bottom < table->num_virt_rows)
1400     {
1401         vloc.vcell_loc.virt_row = top;
1402         vcell = gnc_table_get_virtual_cell (table, vloc.vcell_loc);
1403         if (vcell && vcell->cellblock && vcell->visible)
1404         {
1405             vloc.phys_row_offset = 0;
1406             vloc.phys_col_offset = 0;
1407 
1408             if (gnc_table_find_valid_cell_horiz (table, &vloc, FALSE))
1409                 break;
1410         }
1411 
1412         vloc.vcell_loc.virt_row = bottom;
1413         vcell = gnc_table_get_virtual_cell (table, vloc.vcell_loc);
1414         if (vcell && vcell->cellblock && vcell->visible)
1415         {
1416             vloc.phys_row_offset = 0;
1417             vloc.phys_col_offset = 0;
1418 
1419             if (gnc_table_find_valid_cell_horiz (table, &vloc, FALSE))
1420                 break;
1421         }
1422 
1423         top--;
1424         bottom++;
1425     }
1426 
1427     if (!vcell || !vcell->cellblock || !vcell->visible)
1428         return FALSE;
1429 
1430     if (vloc.phys_row_offset < 0)
1431         vloc.phys_row_offset = 0;
1432     if (vloc.phys_row_offset >= vcell->cellblock->num_rows)
1433         vloc.phys_row_offset = vcell->cellblock->num_rows - 1;
1434 
1435     virt_loc->vcell_loc = vloc.vcell_loc;
1436 
1437     return TRUE;
1438 }
1439 
1440 static gboolean
gnc_table_find_valid_cell_horiz(Table * table,VirtualLocation * virt_loc,gboolean exact_cell)1441 gnc_table_find_valid_cell_horiz (Table *table,
1442                                  VirtualLocation *virt_loc,
1443                                  gboolean exact_cell)
1444 {
1445     VirtualLocation vloc;
1446     VirtualCell *vcell;
1447     int left;
1448     int right;
1449 
1450     if (table == NULL)
1451         return FALSE;
1452 
1453     if (virt_loc == NULL)
1454         return FALSE;
1455 
1456     if (gnc_table_virtual_cell_out_of_bounds (table, virt_loc->vcell_loc))
1457         return FALSE;
1458 
1459     if (gnc_table_virtual_loc_valid (table, *virt_loc, exact_cell))
1460         return TRUE;
1461 
1462     vloc = *virt_loc;
1463 
1464     vcell = gnc_table_get_virtual_cell (table, vloc.vcell_loc);
1465     if (vcell == NULL)
1466         return FALSE;
1467     if (vcell->cellblock == NULL)
1468         return FALSE;
1469 
1470     if (vloc.phys_col_offset < 0)
1471         vloc.phys_col_offset = 0;
1472     if (vloc.phys_col_offset >= vcell->cellblock->num_cols)
1473         vloc.phys_col_offset = vcell->cellblock->num_cols - 1;
1474 
1475     left  = vloc.phys_col_offset - 1;
1476     right = vloc.phys_col_offset + 1;
1477 
1478     while (left >= 0 || right < vcell->cellblock->num_cols)
1479     {
1480         vloc.phys_col_offset = right;
1481         if (gnc_table_virtual_loc_valid(table, vloc, FALSE))
1482         {
1483             *virt_loc = vloc;
1484             return TRUE;
1485         }
1486 
1487         vloc.phys_col_offset = left;
1488         if (gnc_table_virtual_loc_valid(table, vloc, FALSE))
1489         {
1490             *virt_loc = vloc;
1491             return TRUE;
1492         }
1493 
1494         left--;
1495         right++;
1496     }
1497 
1498     return FALSE;
1499 }
1500 
1501 gboolean
gnc_table_find_close_valid_cell(Table * table,VirtualLocation * virt_loc,gboolean exact_pointer)1502 gnc_table_find_close_valid_cell (Table *table, VirtualLocation *virt_loc,
1503                                  gboolean exact_pointer)
1504 {
1505     if (!gnc_table_find_valid_row_vert (table, virt_loc))
1506         return FALSE;
1507 
1508     return gnc_table_find_valid_cell_horiz (table, virt_loc, exact_pointer);
1509 }
1510 
1511 void
gnc_table_refresh_cursor_gui(Table * table,VirtualCellLocation vcell_loc,gboolean do_scroll)1512 gnc_table_refresh_cursor_gui (Table * table,
1513                               VirtualCellLocation vcell_loc,
1514                               gboolean do_scroll)
1515 {
1516     g_return_if_fail (table != NULL);
1517     g_return_if_fail (table->gui_handlers.cursor_refresh != NULL);
1518 
1519     table->gui_handlers.cursor_refresh (table, vcell_loc, do_scroll);
1520 }
1521 
1522 gboolean
gnc_table_move_tab(Table * table,VirtualLocation * virt_loc,gboolean move_right)1523 gnc_table_move_tab (Table *table,
1524                     VirtualLocation *virt_loc,
1525                     gboolean move_right)
1526 {
1527     VirtualCell *vcell;
1528     VirtualLocation vloc;
1529     BasicCell *cell;
1530 
1531     if ((table == NULL) || (virt_loc == NULL))
1532         return FALSE;
1533 
1534     vloc = *virt_loc;
1535 
1536     vcell = gnc_table_get_virtual_cell (table, vloc.vcell_loc);
1537     if ((vcell == NULL) || (vcell->cellblock == NULL) || !vcell->visible)
1538         return FALSE;
1539 
1540     while (1)
1541     {
1542         CellIOFlags io_flags;
1543 
1544         if (move_right)
1545         {
1546             vloc.phys_col_offset++;
1547 
1548             if (vloc.phys_col_offset >= vcell->cellblock->num_cols)
1549             {
1550                 if (!gnc_table_move_vertical_position (table, &vloc, 1))
1551                     return FALSE;
1552 
1553                 vloc.phys_col_offset = 0;
1554             }
1555         }
1556         else
1557         {
1558             vloc.phys_col_offset--;
1559 
1560             if (vloc.phys_col_offset < 0)
1561             {
1562                 if (!gnc_table_move_vertical_position (table, &vloc, -1))
1563                     return FALSE;
1564 
1565                 vloc.phys_col_offset = vcell->cellblock->num_cols - 1;
1566             }
1567         }
1568 
1569         vcell = gnc_table_get_virtual_cell (table, vloc.vcell_loc);
1570         if ((vcell == NULL) || (vcell->cellblock == NULL) || !vcell->visible)
1571             return FALSE;
1572 
1573         cell = gnc_cellblock_get_cell (vcell->cellblock,
1574                                        vloc.phys_row_offset,
1575                                        vloc.phys_col_offset);
1576         if (!cell)
1577             continue;
1578 
1579         io_flags = gnc_table_get_io_flags (table, vloc);
1580 
1581         if (!(io_flags & XACC_CELL_ALLOW_INPUT))
1582             continue;
1583 
1584         if (io_flags & XACC_CELL_ALLOW_EXACT_ONLY)
1585             continue;
1586 
1587         break;
1588     }
1589 
1590     {
1591         gboolean changed = !virt_loc_equal (vloc, *virt_loc);
1592 
1593         *virt_loc = vloc;
1594 
1595         return changed;
1596     }
1597 }
1598 
1599 gboolean
gnc_table_move_vertical_position(Table * table,VirtualLocation * virt_loc,int phys_row_offset)1600 gnc_table_move_vertical_position (Table *table,
1601                                   VirtualLocation *virt_loc,
1602                                   int phys_row_offset)
1603 {
1604     VirtualLocation vloc;
1605     VirtualCell *vcell;
1606     gint last_visible_row;
1607 
1608     if ((table == NULL) || (virt_loc == NULL))
1609         return FALSE;
1610 
1611     vloc = *virt_loc;
1612     last_visible_row = vloc.vcell_loc.virt_row;
1613 
1614     vcell = gnc_table_get_virtual_cell (table, vloc.vcell_loc);
1615     if ((vcell == NULL) || (vcell->cellblock == NULL))
1616         return FALSE;
1617 
1618     while (phys_row_offset != 0)
1619     {
1620         /* going up */
1621         if (phys_row_offset < 0)
1622         {
1623             phys_row_offset++;
1624 
1625             /* room left in the current cursor */
1626             if (vloc.phys_row_offset > 0)
1627             {
1628                 vloc.phys_row_offset--;
1629                 continue;
1630             }
1631 
1632             /* end of the line */
1633             if (vloc.vcell_loc.virt_row == 1)
1634                 break;
1635 
1636             do
1637             {
1638                 vloc.vcell_loc.virt_row--;
1639 
1640                 vcell = gnc_table_get_virtual_cell (table, vloc.vcell_loc);
1641             }
1642             while (vcell && vcell->cellblock && !vcell->visible);
1643 
1644             if (!vcell || !vcell->cellblock)
1645                 break;
1646 
1647             last_visible_row = vloc.vcell_loc.virt_row;
1648             vloc.phys_row_offset = vcell->cellblock->num_rows - 1;
1649         }
1650         /* going down */
1651         else
1652         {
1653             phys_row_offset--;
1654 
1655             /* room left in the current cursor */
1656             if (vloc.phys_row_offset < (vcell->cellblock->num_rows - 1))
1657             {
1658                 vloc.phys_row_offset++;
1659                 continue;
1660             }
1661 
1662             /* end of the line */
1663             if (vloc.vcell_loc.virt_row == (table->num_virt_rows - 1))
1664                 break;
1665 
1666             do
1667             {
1668                 vloc.vcell_loc.virt_row++;
1669 
1670                 vcell = gnc_table_get_virtual_cell (table, vloc.vcell_loc);
1671             }
1672             while (vcell && vcell->cellblock && !vcell->visible);
1673 
1674             if (!vcell || !vcell->cellblock)
1675                 break;
1676 
1677             last_visible_row = vloc.vcell_loc.virt_row;
1678             vloc.phys_row_offset = 0;
1679         }
1680     }
1681 
1682     vloc.vcell_loc.virt_row = last_visible_row;
1683 
1684     {
1685         gboolean changed = !virt_loc_equal (vloc, *virt_loc);
1686 
1687         *virt_loc = vloc;
1688 
1689         return changed;
1690     }
1691 }
1692 
1693 gboolean
gnc_table_traverse_update(Table * table,VirtualLocation virt_loc,gncTableTraversalDir dir,VirtualLocation * dest_loc)1694 gnc_table_traverse_update(Table *table,
1695                           VirtualLocation virt_loc,
1696                           gncTableTraversalDir dir,
1697                           VirtualLocation *dest_loc)
1698 {
1699     gboolean abort_move;
1700 
1701     if ((table == NULL) || (dest_loc == NULL))
1702         return FALSE;
1703 
1704     ENTER("proposed (%d %d) -> (%d %d)\n",
1705           virt_loc.vcell_loc.virt_row, virt_loc.vcell_loc.virt_row,
1706           dest_loc->vcell_loc.virt_row, dest_loc->vcell_loc.virt_col);
1707 
1708     /* first, make sure our destination cell is valid. If it is out
1709      * of bounds report an error. I don't think this ever happens. */
1710     if (gnc_table_virtual_cell_out_of_bounds (table, dest_loc->vcell_loc))
1711     {
1712         PERR("destination (%d, %d) out of bounds (%d, %d)\n",
1713              dest_loc->vcell_loc.virt_row, dest_loc->vcell_loc.virt_col,
1714              table->num_virt_rows, table->num_virt_cols);
1715         LEAVE("");
1716         return TRUE;
1717     }
1718 
1719     /* next, check the current row and column.  If they are out of bounds
1720      * we can recover by treating the traversal as a mouse point. This can
1721      * occur whenever the register widget is resized smaller, maybe?. */
1722     if (!gnc_table_virtual_loc_valid (table, virt_loc, TRUE))
1723     {
1724         PINFO("source (%d, %d) out of bounds (%d, %d)\n",
1725               virt_loc.vcell_loc.virt_row, virt_loc.vcell_loc.virt_col,
1726               table->num_virt_rows, table->num_virt_cols);
1727 
1728         dir = GNC_TABLE_TRAVERSE_POINTER;
1729     }
1730 
1731     /* process forward-moving traversals */
1732     switch (dir)
1733     {
1734     case GNC_TABLE_TRAVERSE_RIGHT:
1735     case GNC_TABLE_TRAVERSE_LEFT:
1736         gnc_table_find_valid_cell_horiz(table, dest_loc, FALSE);
1737 
1738         break;
1739 
1740     case GNC_TABLE_TRAVERSE_UP:
1741     case GNC_TABLE_TRAVERSE_DOWN:
1742     {
1743         VirtualLocation new_loc = *dest_loc;
1744         int increment;
1745         int col_offset = 0;
1746         gboolean second_traversal = FALSE;
1747 
1748         /* Keep going in the specified direction until we find a valid
1749          * row to land on, or we hit the end of the table. At the end,
1750          * turn around and go back until we find a valid row or we get
1751          * to where we started. If we still can't find anything, try
1752          * going left and right. */
1753         increment = (dir == GNC_TABLE_TRAVERSE_DOWN) ? 1 : -1;
1754 
1755         while (!gnc_table_virtual_loc_valid(table, new_loc, FALSE))
1756         {
1757             if (virt_loc_equal (new_loc, virt_loc))
1758             {
1759                 new_loc = *dest_loc;
1760                 gnc_table_find_valid_cell_horiz(table, &new_loc, FALSE);
1761                 break;
1762             }
1763 
1764             if (!gnc_table_move_vertical_position (table, &new_loc, increment))
1765             {
1766                 /* Special case: if there is no valid cell at all in the column
1767                  * we are scanning, (both up and down directions didn't work)
1768                  * attempt to do the same in the next column.
1769                  * Hack alert: there is no check to see if there really is a
1770                  * valid next column. However this situation so far only happens
1771                  * after a pagedown/pageup key event in the SX transaction editor
1772                  * which always tests the first column to start (which has no
1773                  * editable cells) and in that situation there is a valid next column.
1774                  */
1775                 if (!second_traversal)
1776                     second_traversal = TRUE;
1777                 else
1778                 {
1779                     second_traversal = FALSE;
1780                     col_offset++;
1781                 }
1782                 increment *= -1;
1783                 new_loc = *dest_loc;
1784                 new_loc.phys_col_offset = new_loc.phys_col_offset + col_offset;
1785             }
1786         }
1787 
1788         *dest_loc = new_loc;
1789     }
1790 
1791     if (!gnc_table_virtual_loc_valid(table, *dest_loc, FALSE))
1792     {
1793         LEAVE("");
1794         return TRUE;
1795     }
1796 
1797     break;
1798 
1799     case GNC_TABLE_TRAVERSE_POINTER:
1800         if (!gnc_table_find_valid_cell_horiz(table, dest_loc, TRUE))
1801         {
1802             LEAVE("");
1803             return TRUE;
1804         }
1805 
1806         break;
1807 
1808     default:
1809         g_return_val_if_fail (FALSE, TRUE);
1810         break;
1811     }
1812 
1813     /* Call the table traverse callback for any modifications. */
1814     if (table->control->traverse)
1815         abort_move = table->control->traverse (dest_loc, dir,
1816                                                table->control->user_data);
1817     else
1818         abort_move = FALSE;
1819 
1820     LEAVE("dest_row = %d, dest_col = %d\n",
1821           dest_loc->vcell_loc.virt_row, dest_loc->vcell_loc.virt_col);
1822 
1823     return abort_move;
1824 }
1825