1 /*
2 * Copyright 2012 - 2013 Michael Drake <tlsa@netsurf-browser.org>
3 *
4 * This file is part of NetSurf, http://www.netsurf-browser.org/
5 *
6 * NetSurf is free software; you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation; version 2 of the License.
9 *
10 * NetSurf is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 /**
20 * \file
21 *
22 * Treeview handling implementation.
23 */
24
25 #include "utils/config.h"
26
27 #include <string.h>
28
29 #include "utils/utils.h"
30 #include "utils/log.h"
31 #include "utils/nsurl.h"
32 #include "utils/nscolour.h"
33 #include "utils/nsoption.h"
34 #include "netsurf/bitmap.h"
35 #include "netsurf/content.h"
36 #include "netsurf/plotters.h"
37 #include "netsurf/clipboard.h"
38 #include "netsurf/layout.h"
39 #include "netsurf/keypress.h"
40 #include "netsurf/core_window.h"
41 #include "content/hlcache.h"
42 #include "css/utils.h"
43
44 #include "desktop/knockout.h"
45 #include "desktop/textarea.h"
46 #include "desktop/treeview.h"
47 #include "desktop/cw_helper.h"
48 #include "desktop/gui_internal.h"
49 #include "desktop/system_colour.h"
50
51 /**
52 * The maximum horizontal size a treeview can possibly be.
53 *
54 * \todo get rid of REDRAW_MAX -- need to be able to know window size
55 */
56 #define REDRAW_MAX 8000
57
58
59 /**
60 * Treeview handling global context
61 */
62 struct treeview_globals {
63 unsigned initialised;
64 int line_height;
65 int furniture_width;
66 int step_width;
67 int window_padding;
68 int icon_size;
69 int icon_step;
70 int move_offset;
71 } tree_g;
72
73
74 /**
75 * Section type of a treeview at a point
76 */
77 enum treeview_node_part {
78 TV_NODE_PART_TOGGLE, /**< Expansion toggle */
79 TV_NODE_PART_ON_NODE, /**< Node content (text, icon) */
80 TV_NODE_PART_NONE /**< Empty area */
81 };
82
83
84 /**
85 * Text within a treeview field or node
86 */
87 struct treeview_text {
88 const char *data; /**< Text string */
89 uint32_t len; /**< Length of string in bytes */
90 int width; /**< Width of text in px */
91 };
92
93
94 /**
95 * a treeview field
96 */
97 struct treeview_field {
98 /** flags controlling how field is interpreted */
99 enum treeview_field_flags flags;
100
101 lwc_string *field; /**< field contents */
102 struct treeview_text value; /**< field text */
103 };
104
105
106 /**
107 * flags indicating render state of node.
108 */
109 enum treeview_node_flags {
110 TV_NFLAGS_NONE = 0, /**< No node flags set */
111 TV_NFLAGS_EXPANDED = (1 << 0), /**< Whether node is expanded */
112 TV_NFLAGS_SELECTED = (1 << 1), /**< Whether node is selected */
113 TV_NFLAGS_SPECIAL = (1 << 2), /**< Render as special node */
114 TV_NFLAGS_MATCHED = (1 << 3), /**< Whether node matches search */
115 };
116
117
118 /**
119 * Treeview target position
120 */
121 enum treeview_target_pos {
122 TV_TARGET_ABOVE,
123 TV_TARGET_INSIDE,
124 TV_TARGET_BELOW,
125 TV_TARGET_NONE
126 };
127
128
129 /**
130 * Treeview node
131 */
132 struct treeview_node {
133 enum treeview_node_flags flags; /**< Node flags */
134 enum treeview_node_type type; /**< Node type */
135
136 int height; /**< Includes height of any descendants (pixels) */
137 int inset; /**< Node's inset depending on tree depth (pixels) */
138
139 treeview_node *parent; /**< parent node */
140 treeview_node *prev_sib; /**< previous sibling node */
141 treeview_node *next_sib; /**< next sibling node */
142 treeview_node *children; /**< first child node */
143
144 void *client_data; /**< Passed to client on node event msg callback */
145
146 struct treeview_text text; /** Text to show for node (default field) */
147 };
148
149
150 /**
151 * Node entry
152 *
153 * node entry contains a base node at the beginning allowing for
154 * trivial containerof by cast and some number of fields.
155 */
156 struct treeview_node_entry {
157 treeview_node base; /**< Entry class inherits node base class */
158 struct treeview_field fields[FLEX_ARRAY_LEN_DECL];
159 };
160
161
162 /**
163 * A mouse position wrt treeview
164 */
165 struct treeview_pos {
166 int x; /**< Mouse X coordinate */
167 int y; /**< Mouse Y coordinate */
168 int node_y; /**< Top of node at y */
169 int node_h; /**< Height of node at y */
170 };
171
172
173 /**
174 * Treeview drag state
175 */
176 struct treeview_drag {
177 enum {
178 TV_DRAG_NONE,
179 TV_DRAG_SELECTION,
180 TV_DRAG_MOVE,
181 TV_DRAG_TEXTAREA,
182 TV_DRAG_SEARCH,
183 } type; /**< Drag type */
184 treeview_node *start_node; /**< Start node */
185 bool selected; /**< Start node is selected */
186 enum treeview_node_part part; /**< Node part at start */
187 struct treeview_pos start; /**< Start pos */
188 struct treeview_pos prev; /**< Previous pos */
189 };
190
191
192 /**
193 * Treeview node move details
194 */
195 struct treeview_move {
196 treeview_node *root; /**< Head of yanked node list */
197 treeview_node *target; /**< Move target */
198 struct rect target_area; /**< Pos/size of target indicator */
199 enum treeview_target_pos target_pos; /**< Pos wrt render node */
200 };
201
202
203 /**
204 * Treeview node edit details
205 */
206 struct treeview_edit {
207 treeview_node *node; /**< Node being edited, or NULL */
208 struct textarea *textarea; /**< Textarea for edit, or NULL */
209 lwc_string *field; /**< The field being edited, or NULL */
210 int x; /**< Textarea x position */
211 int y; /**< Textarea y position */
212 int w; /**< Textarea width */
213 int h; /**< Textarea height */
214 };
215
216
217 /**
218 * Treeview search box details
219 */
220 struct treeview_search {
221 struct textarea *textarea; /**< Search box. */
222 bool active; /**< Whether the search box has focus. */
223 bool search; /**< Whether we have a search term. */
224 int height; /**< Current search display height. */
225 };
226
227
228 /**
229 * The treeview context
230 */
231 struct treeview {
232 uint32_t view_width; /**< Viewport horizontal size */
233
234 treeview_flags flags; /**< Treeview behaviour settings */
235
236 treeview_node *root; /**< Root node */
237
238 struct treeview_field *fields; /**< Array of fields */
239 int n_fields; /**< fields[n_fields] is folder, lower are entry fields */
240 int field_width; /**< Max width of shown field names */
241
242 struct treeview_drag drag; /**< Drag state */
243 struct treeview_move move; /**< Move drag details */
244 struct treeview_edit edit; /**< Edit details */
245
246 struct treeview_search search; /**< Treeview search box */
247
248 const struct treeview_callback_table *callbacks; /**< For node events */
249
250 const struct core_window_callback_table *cw_t; /**< Window cb table */
251 struct core_window *cw_h; /**< Core window handle */
252 };
253
254
255 /**
256 * Treeview furniture states.
257 */
258 enum treeview_furniture_id {
259 TREE_FURN_EXPAND = 0,
260 TREE_FURN_CONTRACT,
261 TREE_FURN_LAST
262 };
263
264
265 /**
266 * style for a node
267 */
268 struct treeview_node_style {
269 plot_style_t bg; /**< Background */
270 plot_font_style_t text; /**< Text */
271 plot_font_style_t itext; /**< Entry field text */
272
273 plot_style_t sbg; /**< Selected background */
274 plot_font_style_t stext; /**< Selected text */
275 plot_font_style_t sitext; /**< Selected entry field text */
276
277 struct {
278 int size;
279 struct bitmap *bmp;
280 struct bitmap *sel;
281 } furn[TREE_FURN_LAST];
282 };
283
284
285 /**
286 * Plot style for odd rows
287 */
288 struct treeview_node_style plot_style_odd;
289
290
291 /**
292 * Plot style for even rows
293 */
294 struct treeview_node_style plot_style_even;
295
296
297 /**
298 * Treeview content resource data
299 */
300 struct treeview_resource {
301 const char *url;
302 struct hlcache_handle *c;
303 int height;
304 bool ready;
305 };
306
307
308 /**
309 * treeview resource indexes
310 */
311 enum treeview_resource_id {
312 TREE_RES_ARROW = 0,
313 TREE_RES_CONTENT,
314 TREE_RES_FOLDER,
315 TREE_RES_FOLDER_SPECIAL,
316 TREE_RES_SEARCH,
317 TREE_RES_LAST
318 };
319
320
321 /**
322 * Treeview content resources
323 */
324 static struct treeview_resource treeview_res[TREE_RES_LAST] = {
325 { "resource:icons/arrow-l.png", NULL, 0, false },
326 { "resource:icons/content.png", NULL, 0, false },
327 { "resource:icons/directory.png", NULL, 0, false },
328 { "resource:icons/directory2.png", NULL, 0, false },
329 { "resource:icons/search.png", NULL, 0, false }
330 };
331
332
333 /**
334 * Get the display height of the treeview data component of the display.
335 *
336 * \param[in] tree Treeview to get the height of.
337 * \return the display height in pixels.
338 */
treeview__get_display_height(const treeview * tree)339 static inline int treeview__get_display_height(const treeview *tree)
340 {
341 return (tree->search.search == false) ?
342 tree->root->height :
343 tree->search.height;
344 }
345
346
347 /**
348 * Corewindow callback wrapper: Request a redraw of the window
349 *
350 * \param[in] tree The treeview to request redraw on.
351 * \param[in] r rectangle to redraw
352 */
treeview__cw_invalidate_area(const struct treeview * tree,const struct rect * r)353 static inline void treeview__cw_invalidate_area(
354 const struct treeview *tree,
355 const struct rect *r)
356 {
357 if (tree->cw_t != NULL) {
358 tree->cw_t->invalidate(tree->cw_h, r);
359 }
360 }
361
362
363 /**
364 * Corewindow callback wrapper: Request a full redraw of the window
365 *
366 * \param[in] tree The treeview to request redraw on.
367 */
treeview__cw_full_redraw(const struct treeview * tree)368 static inline void treeview__cw_full_redraw(
369 const struct treeview *tree)
370 {
371 if (tree->cw_t != NULL) {
372 static const struct rect r = {
373 .x0 = 0,
374 .y0 = 0,
375 .x1 = REDRAW_MAX,
376 .y1 = REDRAW_MAX,
377 };
378 tree->cw_t->invalidate(tree->cw_h, &r);
379 }
380 }
381
382
383 /**
384 * Get height used by treeview's search bar (or 0 if not present).
385 *
386 * \param tree Treeview object to check.
387 * \return height used by search bar in pixels.
388 */
treeview__get_search_height(const treeview * tree)389 static inline unsigned treeview__get_search_height(
390 const treeview *tree)
391 {
392 return (tree->flags & TREEVIEW_SEARCHABLE) ?
393 tree_g.line_height : 0;
394 }
395
396
397 /**
398 * Corewindow callback wrapper: Update the limits of the window
399 *
400 * \param[in] tree The treeview to update size for.
401 * \param[in] width the width in px, or negative if don't care
402 * \param[in] height the height in px, or negative if don't care
403 */
treeview__cw_update_size(const struct treeview * tree,int width,int height)404 static inline void treeview__cw_update_size(
405 const struct treeview *tree,
406 int width, int height)
407 {
408 if (tree->cw_t != NULL) {
409 tree->cw_t->update_size(tree->cw_h, width,
410 height + treeview__get_search_height(tree));
411 }
412 }
413
414
415 /**
416 * Corewindow callback_wrapper: Scroll to top of window.
417 *
418 * \param[in] tree The treeview to scroll.
419 */
treeview__cw_scroll_top(const struct treeview * tree)420 static inline void treeview__cw_scroll_top(
421 const struct treeview *tree)
422 {
423 struct rect r = {
424 .x0 = 0,
425 .y0 = 0,
426 .x1 = tree_g.window_padding,
427 .y1 = tree_g.line_height,
428 };
429
430 cw_helper_scroll_visible(tree->cw_t, tree->cw_h, &r);
431 }
432
433
434 /**
435 * Corewindow callback wrapper: Get window viewport dimensions
436 *
437 * \param[in] tree The treeview to get dimensions for.
438 * \param[out] width to be set to viewport width in px
439 * \param[out] height to be set to viewport height in px
440 */
treeview__cw_get_window_dimensions(const struct treeview * tree,int * width,int * height)441 static inline void treeview__cw_get_window_dimensions(
442 const struct treeview *tree,
443 int *width, int *height)
444 {
445 if (tree->cw_t != NULL) {
446 tree->cw_t->get_window_dimensions(tree->cw_h, width, height);
447 }
448 }
449
450
451 /**
452 * Corewindow callback wrapper: Inform corewindow owner of drag status
453 *
454 * \param[in] tree The treeview to report status on.
455 * \param[in] ds the current drag status
456 */
treeview__cw_drag_status(const struct treeview * tree,core_window_drag_status ds)457 static inline void treeview__cw_drag_status(
458 const struct treeview *tree,
459 core_window_drag_status ds)
460 {
461 if (tree->cw_t != NULL) {
462 tree->cw_t->drag_status(tree->cw_h, ds);
463 }
464 }
465
466
467 /**
468 * Helper function to access the given field of a node
469 *
470 * \param tree Treeview that node belongs to
471 * \param n Node to get field from
472 * \param i Index of field of interest
473 * \return text entry for field or NULL.
474 */
475 static inline struct treeview_text *
treeview_get_text_for_field(treeview * tree,treeview_node * n,int i)476 treeview_get_text_for_field(treeview *tree, treeview_node *n, int i)
477 {
478 if (i == 0) {
479 return &n->text;
480
481 } else if (i < tree->n_fields && n->type == TREE_NODE_ENTRY) {
482 struct treeview_node_entry *e = (struct treeview_node_entry *)n;
483 return &e->fields[i - 1].value;
484 }
485
486 assert(0 && "Bad field index for node");
487 return NULL;
488 }
489
490
491 /**
492 * Find the next node in depth first tree order
493 *
494 * \param node Start node
495 * \param full Iff true, visit children of collapsed nodes
496 * \return next node, or NULL if \a node is last node
497 */
treeview_node_next(treeview_node * node,bool full)498 static inline treeview_node * treeview_node_next(treeview_node *node, bool full)
499 {
500 assert(node != NULL);
501
502 if ((full || (node->flags & TV_NFLAGS_EXPANDED)) &&
503 node->children != NULL) {
504 /* Next node is child */
505 node = node->children;
506 } else {
507 /* No children. As long as we're not at the root,
508 * go to next sibling if present, or nearest ancestor
509 * with a next sibling. */
510
511 while (node->parent != NULL && node->next_sib == NULL) {
512 node = node->parent;
513 }
514
515 if (node->type == TREE_NODE_ROOT) {
516 node = NULL;
517
518 } else {
519 node = node->next_sib;
520 }
521 }
522
523 return node;
524 }
525
526
527 /**
528 * Find node at given y-position
529 *
530 * \param tree Treeview object to delete node from
531 * \param target_y Target y-position
532 * \return node at y_target
533 */
treeview_y_node(treeview * tree,int target_y)534 static treeview_node * treeview_y_node(treeview *tree, int target_y)
535 {
536 int y = treeview__get_search_height(tree);
537 treeview_node *n;
538
539 assert(tree != NULL);
540 assert(tree->root != NULL);
541
542 n = treeview_node_next(tree->root, false);
543
544 while (n != NULL) {
545 int h = (n->type == TREE_NODE_ENTRY) ?
546 n->height : tree_g.line_height;
547 if (target_y >= y && target_y < y + h)
548 return n;
549 y += h;
550
551 n = treeview_node_next(n, false);
552 }
553
554 return NULL;
555 }
556
557
558 /**
559 * Find y position of the top of a node
560 *
561 * \param tree Treeview object to delete node from
562 * \param node Node to get position of
563 * \return node's y position
564 */
treeview_node_y(const treeview * tree,const treeview_node * node)565 static int treeview_node_y(
566 const treeview *tree,
567 const treeview_node *node)
568 {
569 treeview_node *n;
570 int y = treeview__get_search_height(tree);
571
572 assert(tree != NULL);
573 assert(tree->root != NULL);
574
575 n = treeview_node_next(tree->root, false);
576
577 while (n != NULL && n != node) {
578 y += (n->type == TREE_NODE_ENTRY) ?
579 n->height : tree_g.line_height;
580
581 n = treeview_node_next(n, false);
582 }
583
584 return y;
585 }
586
587
588 /**
589 * Corewindow callback_wrapper: Scroll to make node visible
590 *
591 * \param[in] tree The treeview to scroll.
592 * \param[in] node The treeview node to scroll to visibility.
593 */
treeview__cw_scroll_to_node(const struct treeview * tree,const struct treeview_node * node)594 static inline void treeview__cw_scroll_to_node(
595 const struct treeview *tree,
596 const struct treeview_node *node)
597 {
598 struct rect r = {
599 .x0 = 0,
600 .y0 = treeview_node_y(tree, node),
601 .x1 = 1,
602 .y1 = ((node->type == TREE_NODE_ENTRY) ?
603 node->height : tree_g.line_height),
604 };
605
606 r.y1 += r.y0; /* Apply the Y offset to the second Y coordinate */
607
608 cw_helper_scroll_visible(tree->cw_t, tree->cw_h, &r);
609 }
610
611
612 /**
613 * Redraw tree from given node to the bottom.
614 *
615 * \param[in] tree Tree to redraw from node in.
616 * \param[in] node Node to redraw from.
617 */
treeview__redraw_from_node(const treeview * tree,const treeview_node * node)618 static void treeview__redraw_from_node(
619 const treeview *tree,
620 const treeview_node *node)
621 {
622 struct rect r = {
623 .x0 = 0,
624 .y0 = treeview_node_y(tree, node),
625 .x1 = REDRAW_MAX,
626 .y1 = treeview__get_display_height(tree) +
627 treeview__get_search_height(tree),
628 };
629
630 assert(tree != NULL);
631
632 treeview__cw_invalidate_area(tree, &r);
633 }
634
635
636 /**
637 * The treeview walk mode. Controls which nodes are visited in a walk.
638 */
639 enum treeview_walk_mode {
640 /**
641 * Walk to all nodes in the (sub)tree.
642 */
643 TREEVIEW_WALK_MODE_LOGICAL_COMPLETE,
644
645 /**
646 * Walk to expanded nodes in the (sub)tree only. Children of
647 * collapsed nodes are not visited.
648 */
649 TREEVIEW_WALK_MODE_LOGICAL_EXPANDED,
650
651 /**
652 * Walk displayed nodes. This differs from the
653 * `TREEVIEW_WALK_MODE_LOGICAL_EXPANDED` mode when there is
654 * an active search filter display.
655 */
656 TREEVIEW_WALK_MODE_DISPLAY,
657 };
658
659
660 /**
661 * Walk a treeview subtree, calling a callback at each node (depth first)
662 *
663 * \param tree Treeview being walked.
664 * \param root Root to walk tree from (doesn't get a callback call)
665 * \param mode The treeview walk mode to use.
666 * \param callback_bwd Function to call on each node in backwards order
667 * \param callback_fwd Function to call on each node in forwards order
668 * \param ctx Context to pass to callback
669 * \return NSERROR_OK on success, or appropriate error otherwise
670 *
671 * \note Any node deletion must happen in callback_bwd.
672 */
treeview_walk_internal(treeview * tree,treeview_node * root,enum treeview_walk_mode mode,nserror (* callback_bwd)(treeview_node * n,void * ctx,bool * end),nserror (* callback_fwd)(treeview_node * n,void * ctx,bool * skip_children,bool * end),void * ctx)673 static nserror treeview_walk_internal(
674 treeview *tree,
675 treeview_node *root,
676 enum treeview_walk_mode mode,
677 nserror (*callback_bwd)(
678 treeview_node *n,
679 void *ctx,
680 bool *end),
681 nserror (*callback_fwd)(
682 treeview_node *n,
683 void *ctx,
684 bool *skip_children,
685 bool *end),
686 void *ctx)
687 {
688 treeview_node *node, *child, *parent, *next_sibling;
689 bool walking_search = (mode == TREEVIEW_WALK_MODE_DISPLAY &&
690 tree->search.search == true);
691 bool skip_children = false;
692 bool abort = false;
693 bool full = false;
694 nserror err;
695 bool entry;
696
697 assert(root != NULL);
698
699 if (mode == TREEVIEW_WALK_MODE_LOGICAL_COMPLETE || walking_search) {
700 /* We need to visit children of collapsed folders. */
701 full = true;
702 }
703
704 node = root;
705 parent = node->parent;
706 next_sibling = node->next_sib;
707 child = (full || (node->flags & TV_NFLAGS_EXPANDED)) ?
708 node->children : NULL;
709
710 while (node != NULL) {
711
712 if (child != NULL && !skip_children) {
713 /* Down to children */
714 node = child;
715 } else {
716 /* No children. As long as we're not at the root,
717 * go to next sibling if present, or nearest ancestor
718 * with a next sibling. */
719
720 while (node != root && next_sibling == NULL) {
721 entry = (node->type == TREE_NODE_ENTRY);
722 if (callback_bwd != NULL &&
723 (entry || !walking_search)) {
724 /* Backwards callback */
725 err = callback_bwd(node, ctx, &abort);
726
727 if (err != NSERROR_OK) {
728 return err;
729
730 } else if (abort) {
731 /* callback requested early
732 * termination */
733 return NSERROR_OK;
734 }
735 }
736 node = parent;
737 parent = node->parent;
738 next_sibling = node->next_sib;
739 }
740
741 if (node == root)
742 break;
743
744 if (callback_bwd != NULL) {
745 /* Backwards callback */
746 err = callback_bwd(node, ctx, &abort);
747
748 if (err != NSERROR_OK) {
749 return err;
750
751 } else if (abort) {
752 /* callback requested early
753 * termination */
754 return NSERROR_OK;
755 }
756 }
757 node = next_sibling;
758 }
759
760 assert(node != NULL);
761 assert(node != root);
762
763 entry = (node->type == TREE_NODE_ENTRY);
764
765 parent = node->parent;
766 next_sibling = node->next_sib;
767 child = (full || (node->flags & TV_NFLAGS_EXPANDED)) ?
768 node->children : NULL;
769
770 if (walking_search && (!entry ||
771 !(node->flags & TV_NFLAGS_MATCHED))) {
772 continue;
773 }
774
775 if (callback_fwd != NULL) {
776 /* Forwards callback */
777 err = callback_fwd(node, ctx, &skip_children, &abort);
778
779 if (err != NSERROR_OK) {
780 return err;
781
782 } else if (abort) {
783 /* callback requested early termination */
784 return NSERROR_OK;
785 }
786 }
787 }
788 return NSERROR_OK;
789 }
790
791
792 /**
793 * Data used when doing a treeview walk for search.
794 */
795 struct treeview_search_walk_data {
796 treeview *tree; /**< The treeview to search. */
797 const char *text; /**< The string being searched for. */
798 const unsigned int len; /**< Length of string being searched for. */
799 int window_height; /**< Accumulate height for matching entries. */
800 };
801
802
803 /**
804 * Treewalk node callback for handling search.
805 *
806 * \param[in] n Current node.
807 * \param[in] ctx Treeview search context.
808 * \param[in,out] skip_children Flag to allow children to be skipped.
809 * \param[in,out] end Flag to allow iteration to be finished early.
810 * \return NSERROR_OK on success else error code.
811 */
treeview__search_walk_cb(treeview_node * n,void * ctx,bool * skip_children,bool * end)812 static nserror treeview__search_walk_cb(
813 treeview_node *n,
814 void *ctx,
815 bool *skip_children,
816 bool *end)
817 {
818 struct treeview_search_walk_data *sw = ctx;
819
820 if (n->type != TREE_NODE_ENTRY) {
821 return NSERROR_OK;
822 }
823
824 if (sw->len == 0) {
825 n->flags &= ~TV_NFLAGS_MATCHED;
826 } else {
827 struct treeview_node_entry *entry =
828 (struct treeview_node_entry *)n;
829 bool matched = false;
830
831 for (int i = 0; i < sw->tree->n_fields; i++) {
832 struct treeview_field *ef = &(sw->tree->fields[i + 1]);
833 if (ef->flags & TREE_FLAG_SEARCHABLE) {
834 if (strcasestr(entry->fields[i].value.data,
835 sw->text) != NULL) {
836 matched = true;
837 break;
838 }
839 }
840 }
841
842 if (!matched && strcasestr(n->text.data, sw->text) != NULL) {
843 matched = true;
844 }
845
846 if (matched) {
847 n->flags |= TV_NFLAGS_MATCHED;
848 sw->window_height += n->height;
849 } else {
850 n->flags &= ~TV_NFLAGS_MATCHED;
851 }
852 }
853
854 return NSERROR_OK;
855 }
856
857
858 /**
859 * Search treeview for text.
860 *
861 * \param[in] tree Treeview to search.
862 * \param[in] text UTF-8 string to search for. (NULL-terminated.)
863 * \param[in] len Byte length of UTF-8 string.
864 * \return NSERROR_OK on success, appropriate error otherwise.
865 */
treeview__search(treeview * tree,const char * text,unsigned int len)866 static nserror treeview__search(
867 treeview *tree,
868 const char *text,
869 unsigned int len)
870 {
871 nserror err;
872 uint32_t height;
873 uint32_t prev_height = treeview__get_display_height(tree);
874 int search_height = treeview__get_search_height(tree);
875 struct treeview_search_walk_data sw = {
876 .len = len,
877 .text = text,
878 .tree = tree,
879 .window_height = 0,
880 };
881 struct rect r = {
882 .x0 = 0,
883 .y0 = search_height,
884 .x1 = REDRAW_MAX,
885 };
886
887 assert(text[len] == '\0');
888
889 if (tree->root == NULL) {
890 return NSERROR_OK;
891 }
892
893 err = treeview_walk_internal(tree, tree->root,
894 TREEVIEW_WALK_MODE_LOGICAL_COMPLETE, NULL,
895 treeview__search_walk_cb, &sw);
896 if (err != NSERROR_OK) {
897 return err;
898 }
899
900 if (len > 0) {
901 tree->search.height = sw.window_height;
902 tree->search.search = true;
903 height = sw.window_height;
904 } else {
905 tree->search.search = false;
906 height = tree->root->height;
907 }
908
909 r.y1 = ((height > prev_height) ? height : prev_height) + search_height;
910 treeview__cw_invalidate_area(tree, &r);
911 treeview__cw_update_size(tree, -1, height);
912 treeview__cw_scroll_top(tree);
913
914 return NSERROR_OK;
915 }
916
917
918 /**
919 * Cancel a treeview search, optionally droping focus from search widget.
920 *
921 * \param[in] tree Treeview to cancel search in.
922 * \param[in] drop_focus Iff true, drop input focus from search widget.
923 */
treeview__search_cancel(treeview * tree,bool drop_focus)924 static void treeview__search_cancel(treeview *tree, bool drop_focus)
925 {
926 struct rect r = {
927 .x0 = tree_g.window_padding + tree_g.icon_size,
928 .x1 = 600,
929 .y0 = 0,
930 .y1 = tree_g.line_height,
931 };
932
933 tree->search.search = false;
934 if (tree->search.active == false) {
935 return;
936 }
937
938 if (textarea_get_text(tree->search.textarea, NULL, 0) == 1) {
939 // If there's no text in the search box, we drop focus on a
940 // cancel. Note '1' because it includes the trailing \0
941 drop_focus = true;
942 }
943
944 if (drop_focus) {
945 tree->search.active = false;
946 textarea_set_caret(tree->search.textarea, -1);
947 } else {
948 textarea_set_caret(tree->search.textarea, 0);
949 }
950
951 textarea_set_text(tree->search.textarea, "");
952 treeview__cw_invalidate_area(tree, &r);
953 }
954
955
956 /**
957 * Callback for textarea_create, in desktop/treeview.h
958 *
959 * \param data treeview context
960 * \param msg textarea message
961 */
treeview_textarea_search_callback(void * data,struct textarea_msg * msg)962 static void treeview_textarea_search_callback(void *data,
963 struct textarea_msg *msg)
964 {
965 treeview *tree = data;
966 struct rect *r;
967
968 if (tree->search.active == false || tree->root == NULL) {
969 return;
970 }
971
972 switch (msg->type) {
973 case TEXTAREA_MSG_DRAG_REPORT:
974 if (msg->data.drag == TEXTAREA_DRAG_NONE) {
975 /* Textarea drag finished */
976 tree->drag.type = TV_DRAG_NONE;
977 } else {
978 /* Textarea drag started */
979 tree->drag.type = TV_DRAG_SEARCH;
980 }
981 treeview__cw_drag_status(tree, tree->drag.type);
982 break;
983
984 case TEXTAREA_MSG_REDRAW_REQUEST:
985 r = &msg->data.redraw;
986 r->x0 += tree_g.window_padding + tree_g.icon_size;
987 r->y0 += 0;
988 r->x1 += 600;
989 r->y1 += tree_g.line_height;
990
991 /* Redraw the textarea */
992 treeview__cw_invalidate_area(tree, r);
993 break;
994
995 case TEXTAREA_MSG_TEXT_MODIFIED:
996 /* Textarea length includes trailing NULL, so subtract it. */
997 treeview__search(tree,
998 msg->data.modified.text,
999 msg->data.modified.len - 1);
1000 break;
1001
1002 default:
1003 break;
1004 }
1005 }
1006
1007
1008 /**
1009 * Update the layout for any active search.
1010 *
1011 * \param[in] tree The tree to update.
1012 */
treeview__search_update_display(treeview * tree)1013 static void treeview__search_update_display(
1014 treeview *tree)
1015 {
1016 const char *string;
1017 unsigned int len;
1018
1019 if (tree->search.search == false) {
1020 /* No active search to update view for. */
1021 return;
1022 }
1023
1024 string = textarea_data(tree->search.textarea, &len);
1025 if (string == NULL || len == 0) {
1026 return;
1027 }
1028
1029 treeview__search(tree, string, len - 1);
1030 }
1031
1032
1033 /**
1034 * Create treeview's root node
1035 *
1036 * \param[out] root Returns root node
1037 * \return NSERROR_OK on success, appropriate error otherwise
1038 */
treeview_create_node_root(treeview_node ** root)1039 static nserror treeview_create_node_root(treeview_node **root)
1040 {
1041 treeview_node *n;
1042
1043 n = malloc(sizeof(struct treeview_node));
1044 if (n == NULL) {
1045 return NSERROR_NOMEM;
1046 }
1047
1048 n->flags = TV_NFLAGS_EXPANDED;
1049 n->type = TREE_NODE_ROOT;
1050
1051 n->height = 0;
1052 n->inset = tree_g.window_padding - tree_g.step_width;
1053
1054 n->text.data = NULL;
1055 n->text.len = 0;
1056 n->text.width = 0;
1057
1058 n->parent = NULL;
1059 n->next_sib = NULL;
1060 n->prev_sib = NULL;
1061 n->children = NULL;
1062
1063 n->client_data = NULL;
1064
1065 *root = n;
1066
1067 return NSERROR_OK;
1068 }
1069
1070
1071 /**
1072 * Set a node's inset from its parent
1073 *
1074 * This may be used as treeview walk callback
1075 *
1076 * \param[in] n node to set inset on
1077 * \param[in] ctx context unused
1078 * \param[out] skip_children set to false so child nodes are not skipped.
1079 * \param[out] end unused flag so treewalk in not terminated early.
1080 */
1081 static nserror
treeview_set_inset_from_parent(treeview_node * n,void * ctx,bool * skip_children,bool * end)1082 treeview_set_inset_from_parent(treeview_node *n,
1083 void *ctx,
1084 bool *skip_children,
1085 bool *end)
1086 {
1087 if (n->parent != NULL)
1088 n->inset = n->parent->inset + tree_g.step_width;
1089
1090 *skip_children = false;
1091 return NSERROR_OK;
1092 }
1093
1094
1095 /**
1096 * Insert a treeview node into a treeview
1097 *
1098 * \param tree the treeview to insert node into.
1099 * \param a parentless node to insert
1100 * \param b tree node to insert a as a relation of
1101 * \param rel The relationship between \a a and \a b
1102 */
1103 static inline void
treeview_insert_node(treeview * tree,treeview_node * a,treeview_node * b,enum treeview_relationship rel)1104 treeview_insert_node(
1105 treeview *tree,
1106 treeview_node *a,
1107 treeview_node *b,
1108 enum treeview_relationship rel)
1109 {
1110 assert(a != NULL);
1111 assert(a->parent == NULL);
1112 assert(b != NULL);
1113
1114 switch (rel) {
1115 case TREE_REL_FIRST_CHILD:
1116 assert(b->type != TREE_NODE_ENTRY);
1117 a->parent = b;
1118 a->next_sib = b->children;
1119 if (a->next_sib)
1120 a->next_sib->prev_sib = a;
1121 b->children = a;
1122 break;
1123
1124 case TREE_REL_NEXT_SIBLING:
1125 assert(b->type != TREE_NODE_ROOT);
1126 a->prev_sib = b;
1127 a->next_sib = b->next_sib;
1128 a->parent = b->parent;
1129 b->next_sib = a;
1130 if (a->next_sib)
1131 a->next_sib->prev_sib = a;
1132 break;
1133
1134 default:
1135 assert(0);
1136 break;
1137 }
1138
1139 assert(a->parent != NULL);
1140
1141 a->inset = a->parent->inset + tree_g.step_width;
1142 if (a->children != NULL) {
1143 treeview_walk_internal(tree, a,
1144 TREEVIEW_WALK_MODE_LOGICAL_COMPLETE, NULL,
1145 treeview_set_inset_from_parent, NULL);
1146 }
1147
1148 if (a->parent->flags & TV_NFLAGS_EXPANDED) {
1149 int height = a->height;
1150 /* Parent is expanded, so inserted node will be visible and
1151 * affect layout */
1152 if (a->text.width == 0) {
1153 guit->layout->width(&plot_style_odd.text,
1154 a->text.data,
1155 a->text.len,
1156 &(a->text.width));
1157 }
1158
1159 do {
1160 a->parent->height += height;
1161 a = a->parent;
1162 } while (a->parent != NULL);
1163 }
1164 }
1165
1166
1167 /* Exported interface, documented in treeview.h */
1168 nserror
treeview_create_node_folder(treeview * tree,treeview_node ** folder,treeview_node * relation,enum treeview_relationship rel,const struct treeview_field_data * field,void * data,treeview_node_options_flags flags)1169 treeview_create_node_folder(treeview *tree,
1170 treeview_node **folder,
1171 treeview_node *relation,
1172 enum treeview_relationship rel,
1173 const struct treeview_field_data *field,
1174 void *data,
1175 treeview_node_options_flags flags)
1176 {
1177 treeview_node *n;
1178
1179 assert(data != NULL);
1180 assert(tree != NULL);
1181 assert(tree->root != NULL);
1182
1183 if (relation == NULL) {
1184 relation = tree->root;
1185 rel = TREE_REL_FIRST_CHILD;
1186 }
1187
1188 n = malloc(sizeof(struct treeview_node));
1189 if (n == NULL) {
1190 return NSERROR_NOMEM;
1191 }
1192
1193 n->flags = (flags & TREE_OPTION_SPECIAL_DIR) ?
1194 TV_NFLAGS_SPECIAL : TV_NFLAGS_NONE;
1195 n->type = TREE_NODE_FOLDER;
1196
1197 n->height = tree_g.line_height;
1198
1199 n->text.data = field->value;
1200 n->text.len = field->value_len;
1201 n->text.width = 0;
1202
1203 n->parent = NULL;
1204 n->next_sib = NULL;
1205 n->prev_sib = NULL;
1206 n->children = NULL;
1207
1208 n->client_data = data;
1209
1210 treeview_insert_node(tree, n, relation, rel);
1211
1212 if (n->parent->flags & TV_NFLAGS_EXPANDED) {
1213 /* Inform front end of change in dimensions */
1214 if (!(flags & TREE_OPTION_SUPPRESS_RESIZE))
1215 treeview__cw_update_size(tree, -1,
1216 tree->root->height);
1217
1218 /* Redraw */
1219 if (!(flags & TREE_OPTION_SUPPRESS_REDRAW)) {
1220 struct rect r;
1221 r.x0 = 0;
1222 r.y0 = treeview_node_y(tree, n);
1223 r.x1 = REDRAW_MAX;
1224 r.y1 = tree->root->height;
1225 treeview__cw_invalidate_area(tree, &r);
1226 }
1227 }
1228
1229 *folder = n;
1230
1231 return NSERROR_OK;
1232 }
1233
1234
1235 /* Exported interface, documented in treeview.h */
1236 nserror
treeview_update_node_folder(treeview * tree,treeview_node * folder,const struct treeview_field_data * field,void * data)1237 treeview_update_node_folder(treeview *tree,
1238 treeview_node *folder,
1239 const struct treeview_field_data *field,
1240 void *data)
1241 {
1242 bool match;
1243
1244 assert(data != NULL);
1245 assert(tree != NULL);
1246 assert(folder != NULL);
1247 assert(data == folder->client_data);
1248 assert(folder->parent != NULL);
1249
1250 assert(field != NULL);
1251 assert(lwc_string_isequal(tree->fields[tree->n_fields].field,
1252 field->field, &match) == lwc_error_ok &&
1253 match == true);
1254 folder->text.data = field->value;
1255 folder->text.len = field->value_len;
1256 folder->text.width = 0;
1257
1258 if (folder->parent->flags & TV_NFLAGS_EXPANDED) {
1259 /* Text will be seen, get its width */
1260 guit->layout->width(&plot_style_odd.text,
1261 folder->text.data,
1262 folder->text.len,
1263 &(folder->text.width));
1264 } else {
1265 /* Just invalidate the width, since it's not needed now */
1266 folder->text.width = 0;
1267 }
1268
1269 /* Redraw */
1270 if (folder->parent->flags & TV_NFLAGS_EXPANDED) {
1271 struct rect r;
1272 r.x0 = 0;
1273 r.y0 = treeview_node_y(tree, folder);
1274 r.x1 = REDRAW_MAX;
1275 r.y1 = r.y0 + tree_g.line_height;
1276 treeview__cw_invalidate_area(tree, &r);
1277 }
1278
1279 return NSERROR_OK;
1280 }
1281
1282
1283 /* Exported interface, documented in treeview.h */
1284 nserror
treeview_update_node_entry(treeview * tree,treeview_node * entry,const struct treeview_field_data fields[],void * data)1285 treeview_update_node_entry(treeview *tree,
1286 treeview_node *entry,
1287 const struct treeview_field_data fields[],
1288 void *data)
1289 {
1290 bool match;
1291 struct treeview_node_entry *e = (struct treeview_node_entry *)entry;
1292 int i;
1293
1294 assert(data != NULL);
1295 assert(tree != NULL);
1296 assert(entry != NULL);
1297 assert(data == entry->client_data);
1298 assert(entry->parent != NULL);
1299
1300 assert(fields != NULL);
1301 assert(fields[0].field != NULL);
1302 assert(lwc_string_isequal(tree->fields[0].field,
1303 fields[0].field, &match) == lwc_error_ok &&
1304 match == true);
1305 entry->text.data = fields[0].value;
1306 entry->text.len = fields[0].value_len;
1307 entry->text.width = 0;
1308
1309 if (entry->parent->flags & TV_NFLAGS_EXPANDED) {
1310 /* Text will be seen, get its width */
1311 guit->layout->width(&plot_style_odd.text,
1312 entry->text.data,
1313 entry->text.len,
1314 &(entry->text.width));
1315 } else {
1316 /* Just invalidate the width, since it's not needed now */
1317 entry->text.width = 0;
1318 }
1319
1320 for (i = 1; i < tree->n_fields; i++) {
1321 assert(fields[i].field != NULL);
1322 assert(lwc_string_isequal(tree->fields[i].field,
1323 fields[i].field, &match) == lwc_error_ok &&
1324 match == true);
1325
1326 e->fields[i - 1].value.data = fields[i].value;
1327 e->fields[i - 1].value.len = fields[i].value_len;
1328
1329 if (entry->flags & TV_NFLAGS_EXPANDED) {
1330 /* Text will be seen, get its width */
1331 guit->layout->width(&plot_style_odd.text,
1332 e->fields[i - 1].value.data,
1333 e->fields[i - 1].value.len,
1334 &(e->fields[i - 1].value.width));
1335 } else {
1336 /* Invalidate the width, since it's not needed yet */
1337 e->fields[i - 1].value.width = 0;
1338 }
1339 }
1340
1341 treeview__search_update_display(tree);
1342
1343 /* Redraw */
1344 if (entry->parent->flags & TV_NFLAGS_EXPANDED) {
1345 struct rect r;
1346 r.x0 = 0;
1347 r.y0 = treeview_node_y(tree, entry);
1348 r.x1 = REDRAW_MAX;
1349 r.y1 = r.y0 + entry->height;
1350 treeview__cw_invalidate_area(tree, &r);
1351 }
1352
1353 return NSERROR_OK;
1354 }
1355
1356
1357 /* Exported interface, documented in treeview.h */
1358 nserror
treeview_create_node_entry(treeview * tree,treeview_node ** entry,treeview_node * relation,enum treeview_relationship rel,const struct treeview_field_data fields[],void * data,treeview_node_options_flags flags)1359 treeview_create_node_entry(treeview *tree,
1360 treeview_node **entry,
1361 treeview_node *relation,
1362 enum treeview_relationship rel,
1363 const struct treeview_field_data fields[],
1364 void *data,
1365 treeview_node_options_flags flags)
1366 {
1367 bool match;
1368 struct treeview_node_entry *e;
1369 treeview_node *n;
1370 int i;
1371
1372 assert(data != NULL);
1373 assert(tree != NULL);
1374 assert(tree->root != NULL);
1375
1376 if (relation == NULL) {
1377 relation = tree->root;
1378 rel = TREE_REL_FIRST_CHILD;
1379 }
1380
1381 e = malloc(sizeof(struct treeview_node_entry) +
1382 (tree->n_fields - 1) * sizeof(struct treeview_field));
1383 if (e == NULL) {
1384 return NSERROR_NOMEM;
1385 }
1386
1387
1388 n = (treeview_node *) e;
1389
1390 n->flags = TV_NFLAGS_NONE;
1391 n->type = TREE_NODE_ENTRY;
1392
1393 n->height = tree_g.line_height;
1394
1395 assert(fields != NULL);
1396 assert(fields[0].field != NULL);
1397 assert(lwc_string_isequal(tree->fields[0].field,
1398 fields[0].field, &match) == lwc_error_ok &&
1399 match == true);
1400 n->text.data = fields[0].value;
1401 n->text.len = fields[0].value_len;
1402 n->text.width = 0;
1403
1404 n->parent = NULL;
1405 n->next_sib = NULL;
1406 n->prev_sib = NULL;
1407 n->children = NULL;
1408
1409 n->client_data = data;
1410
1411 for (i = 1; i < tree->n_fields; i++) {
1412 assert(fields[i].field != NULL);
1413 assert(lwc_string_isequal(tree->fields[i].field,
1414 fields[i].field, &match) == lwc_error_ok &&
1415 match == true);
1416
1417 e->fields[i - 1].value.data = fields[i].value;
1418 e->fields[i - 1].value.len = fields[i].value_len;
1419 e->fields[i - 1].value.width = 0;
1420 }
1421
1422 treeview_insert_node(tree, n, relation, rel);
1423
1424 if (n->parent->flags & TV_NFLAGS_EXPANDED) {
1425 /* Inform front end of change in dimensions */
1426 if (!(flags & TREE_OPTION_SUPPRESS_RESIZE))
1427 treeview__cw_update_size(tree, -1,
1428 tree->root->height);
1429
1430 /* Redraw */
1431 if (!(flags & TREE_OPTION_SUPPRESS_REDRAW)) {
1432 struct rect r;
1433 r.x0 = 0;
1434 r.y0 = treeview_node_y(tree, n);
1435 r.x1 = REDRAW_MAX;
1436 r.y1 = tree->root->height;
1437 treeview__cw_invalidate_area(tree, &r);
1438 }
1439 }
1440
1441 treeview__search_update_display(tree);
1442
1443 *entry = n;
1444
1445 return NSERROR_OK;
1446 }
1447
1448
1449 /**
1450 * Treewalk iterator context
1451 */
1452 struct treeview_walk_ctx {
1453 treeview_walk_cb enter_cb;
1454 treeview_walk_cb leave_cb;
1455 void *ctx;
1456 enum treeview_node_type type;
1457 };
1458
1459
1460 /**
1461 * Treewalk node enter callback.
1462 *
1463 * \param n current node
1464 * \param ctx treewalk context
1465 * \param skip_children set if child nodes should be skipped
1466 * \param end set if iteration should end early
1467 */
1468 static nserror
treeview_walk_fwd_cb(treeview_node * n,void * ctx,bool * skip_children,bool * end)1469 treeview_walk_fwd_cb(treeview_node *n,
1470 void *ctx,
1471 bool *skip_children,
1472 bool *end)
1473 {
1474 struct treeview_walk_ctx *tw = ctx;
1475
1476 if (n->type & tw->type) {
1477 return tw->enter_cb(tw->ctx, n->client_data, n->type, end);
1478 }
1479
1480 return NSERROR_OK;
1481 }
1482
1483
1484 /**
1485 * Treewalk node leave callback.
1486 *
1487 * \param n current node
1488 * \param ctx treewalk context
1489 * \param end set if iteration should end early
1490 */
treeview_walk_bwd_cb(treeview_node * n,void * ctx,bool * end)1491 static nserror treeview_walk_bwd_cb(treeview_node *n, void *ctx, bool *end)
1492 {
1493 struct treeview_walk_ctx *tw = ctx;
1494
1495 if (n->type & tw->type) {
1496 return tw->leave_cb(tw->ctx, n->client_data, n->type, end);
1497 }
1498
1499 return NSERROR_OK;
1500 }
1501
1502
1503 /* Exported interface, documented in treeview.h */
1504 nserror
treeview_walk(treeview * tree,treeview_node * root,treeview_walk_cb enter_cb,treeview_walk_cb leave_cb,void * ctx,enum treeview_node_type type)1505 treeview_walk(treeview *tree,
1506 treeview_node *root,
1507 treeview_walk_cb enter_cb,
1508 treeview_walk_cb leave_cb,
1509 void *ctx,
1510 enum treeview_node_type type)
1511 {
1512 struct treeview_walk_ctx tw = {
1513 .enter_cb = enter_cb,
1514 .leave_cb = leave_cb,
1515 .ctx = ctx,
1516 .type = type
1517 };
1518
1519 assert(tree != NULL);
1520 assert(tree->root != NULL);
1521
1522 if (root == NULL)
1523 root = tree->root;
1524
1525 return treeview_walk_internal(tree, root,
1526 TREEVIEW_WALK_MODE_LOGICAL_COMPLETE,
1527 (leave_cb != NULL) ? treeview_walk_bwd_cb : NULL,
1528 (enter_cb != NULL) ? treeview_walk_fwd_cb : NULL,
1529 &tw);
1530 }
1531
1532
1533 /**
1534 * Unlink a treeview node
1535 *
1536 * \param n Node to unlink
1537 * \return true iff ancestor heights need to be reduced
1538 */
treeview_unlink_node(treeview_node * n)1539 static inline bool treeview_unlink_node(treeview_node *n)
1540 {
1541 /* Unlink node from tree */
1542 if (n->parent != NULL && n->parent->children == n) {
1543 /* Node is a first child */
1544 n->parent->children = n->next_sib;
1545
1546 } else if (n->prev_sib != NULL) {
1547 /* Node is not first child */
1548 n->prev_sib->next_sib = n->next_sib;
1549 }
1550
1551 if (n->next_sib != NULL) {
1552 /* Always need to do this */
1553 n->next_sib->prev_sib = n->prev_sib;
1554 }
1555
1556 /* Reduce ancestor heights */
1557 if ((n->parent != NULL) &&
1558 (n->parent->flags & TV_NFLAGS_EXPANDED)) {
1559 return true;
1560 }
1561
1562 return false;
1563 }
1564
1565
1566 /**
1567 * Cancel the editing of a treeview node
1568 *
1569 * \param tree Treeview object to cancel node editing in
1570 * \param redraw Set true iff redraw of removed textarea area required
1571 */
treeview_edit_cancel(treeview * tree,bool redraw)1572 static void treeview_edit_cancel(treeview *tree, bool redraw)
1573 {
1574 struct rect r;
1575
1576 if (tree->edit.textarea == NULL)
1577 return;
1578
1579 textarea_destroy(tree->edit.textarea);
1580
1581 tree->edit.textarea = NULL;
1582 tree->edit.node = NULL;
1583
1584 if (tree->drag.type == TV_DRAG_TEXTAREA)
1585 tree->drag.type = TV_DRAG_NONE;
1586
1587 if (redraw) {
1588 r.x0 = tree->edit.x;
1589 r.y0 = tree->edit.y;
1590 r.x1 = tree->edit.x + tree->edit.w;
1591 r.y1 = tree->edit.y + tree->edit.h;
1592 treeview__cw_invalidate_area(tree, &r);
1593 }
1594 }
1595
1596
1597 /**
1598 * Complete a treeview edit
1599 *
1600 * Complete edit by informing the client with a change request msg
1601 *
1602 * \param tree Treeview object to complete edit in
1603 */
treeview_edit_done(treeview * tree)1604 static void treeview_edit_done(treeview *tree)
1605 {
1606 int len, error;
1607 char* new_text;
1608 treeview_node *n = tree->edit.node;
1609 struct treeview_node_msg msg;
1610 msg.msg = TREE_MSG_NODE_EDIT;
1611
1612 if (tree->edit.textarea == NULL) {
1613 return;
1614 }
1615
1616 assert(n != NULL);
1617
1618 /* Get new text length */
1619 len = textarea_get_text(tree->edit.textarea, NULL, 0);
1620
1621 new_text = malloc(len);
1622 if (new_text == NULL) {
1623 /* TODO: don't just silently ignore */
1624 return;
1625 }
1626
1627 /* Get the new text from textarea */
1628 error = textarea_get_text(tree->edit.textarea, new_text, len);
1629 if (error == -1) {
1630 /* TODO: don't just silently ignore */
1631 free(new_text);
1632 return;
1633 }
1634
1635 /* Inform the treeview client with change request message */
1636 msg.data.node_edit.field = tree->edit.field;
1637 msg.data.node_edit.text = new_text;
1638
1639 switch (n->type) {
1640 case TREE_NODE_ENTRY:
1641 tree->callbacks->entry(msg, n->client_data);
1642 break;
1643 case TREE_NODE_FOLDER:
1644 tree->callbacks->folder(msg, n->client_data);
1645 break;
1646 case TREE_NODE_ROOT:
1647 break;
1648 default:
1649 break;
1650 }
1651
1652 /* Finished with the new text */
1653 free(new_text);
1654
1655 /* Finally, destroy the treeview, and redraw */
1656 treeview_edit_cancel(tree, true);
1657 }
1658
1659
1660 /**
1661 * context for treeview node deletion iterator
1662 */
1663 struct treeview_node_delete {
1664 treeview *tree;
1665 int h_reduction;
1666 bool user_interaction;
1667 };
1668
1669
1670 /**
1671 * Treewalk node callback deleting nodes.
1672 */
1673 static nserror
treeview_delete_node_walk_cb(treeview_node * n,void * ctx,bool * end)1674 treeview_delete_node_walk_cb(treeview_node *n, void *ctx, bool *end)
1675 {
1676 struct treeview_node_delete *nd = (struct treeview_node_delete *)ctx;
1677 struct treeview_node_msg msg;
1678
1679 msg.msg = TREE_MSG_NODE_DELETE;
1680 msg.data.delete.user = nd->user_interaction;
1681
1682 assert(n->children == NULL);
1683
1684 if (treeview_unlink_node(n))
1685 nd->h_reduction += (n->type == TREE_NODE_ENTRY) ?
1686 n->height : tree_g.line_height;
1687
1688 /* Handle any special treatment */
1689 switch (n->type) {
1690 case TREE_NODE_ENTRY:
1691 nd->tree->callbacks->entry(msg, n->client_data);
1692 break;
1693
1694 case TREE_NODE_FOLDER:
1695 nd->tree->callbacks->folder(msg, n->client_data);
1696 break;
1697
1698 case TREE_NODE_ROOT:
1699 break;
1700
1701 default:
1702 return NSERROR_BAD_PARAMETER;
1703 }
1704
1705 /* Cancel any edit of this node */
1706 if (nd->tree->edit.textarea != NULL &&
1707 nd->tree->edit.node == n) {
1708 treeview_edit_cancel(nd->tree, false);
1709 }
1710
1711 /* Free the node */
1712 free(n);
1713
1714 return NSERROR_OK;
1715 }
1716
1717
1718 /**
1719 * Delete a treeview node
1720 *
1721 * Will emit folder or entry deletion msg callback.
1722 *
1723 * \note this can be called from inside a treeview_walk fwd callback.
1724 * For example walking the tree and calling this for any node that's selected.
1725 *
1726 * This function does not delete empty nodes, so if TREEVIEW_DEL_EMPTY_DIRS is
1727 * set, caller must also call treeview_delete_empty.
1728 *
1729 * \param tree Treeview object to delete node from
1730 * \param n Node to delete
1731 * \param interaction Delete is result of user interaction with treeview
1732 * \param flags Treeview node options flags
1733 * \return NSERROR_OK on success, appropriate error otherwise
1734 */
1735 static nserror
treeview_delete_node_internal(treeview * tree,treeview_node * n,bool interaction,treeview_node_options_flags flags)1736 treeview_delete_node_internal(treeview *tree,
1737 treeview_node *n,
1738 bool interaction,
1739 treeview_node_options_flags flags)
1740 {
1741 nserror err;
1742 treeview_node *p = n->parent;
1743 struct treeview_node_delete nd = {
1744 .tree = tree,
1745 .h_reduction = 0,
1746 .user_interaction = interaction
1747 };
1748
1749 if (interaction && (tree->flags & TREEVIEW_NO_DELETES)) {
1750 return NSERROR_OK;
1751 }
1752
1753 /* Delete any children first */
1754 err = treeview_walk_internal(tree, n,
1755 TREEVIEW_WALK_MODE_LOGICAL_COMPLETE,
1756 treeview_delete_node_walk_cb, NULL, &nd);
1757 if (err != NSERROR_OK) {
1758 return err;
1759 }
1760
1761 /* Now delete node */
1762 if (n == tree->root)
1763 tree->root = NULL;
1764 err = treeview_delete_node_walk_cb(n, &nd, false);
1765 if (err != NSERROR_OK) {
1766 return err;
1767 }
1768
1769 n = p;
1770 /* Reduce ancestor heights */
1771 while (n != NULL && n->flags & TV_NFLAGS_EXPANDED) {
1772 n->height -= nd.h_reduction;
1773 n = n->parent;
1774 }
1775
1776 /* Inform front end of change in dimensions */
1777 if (tree->root != NULL && p != NULL && p->flags & TV_NFLAGS_EXPANDED &&
1778 nd.h_reduction > 0 &&
1779 !(flags & TREE_OPTION_SUPPRESS_RESIZE)) {
1780 treeview__cw_update_size(tree, -1,
1781 tree->root->height);
1782 }
1783
1784 treeview__search_update_display(tree);
1785
1786 return NSERROR_OK;
1787 }
1788
1789
1790 /**
1791 * Delete any empty treeview folder nodes
1792 *
1793 * \param tree Treeview object to delete empty nodes from
1794 * \param interaction Delete is result of user interaction with treeview
1795 * \return NSERROR_OK on success, appropriate error otherwise
1796 *
1797 * Note this must not be called within a treeview_walk. It may delete the
1798 * walker's 'current' node, making it impossible to move on without invalid
1799 * reads.
1800 */
treeview_delete_empty_nodes(treeview * tree,bool interaction)1801 static nserror treeview_delete_empty_nodes(treeview *tree, bool interaction)
1802 {
1803 treeview_node *node, *child, *parent, *next_sibling, *p;
1804 bool abort = false;
1805 nserror err;
1806 struct treeview_node_delete nd = {
1807 .tree = tree,
1808 .h_reduction = 0,
1809 .user_interaction = interaction
1810 };
1811
1812 assert(tree != NULL);
1813 assert(tree->root != NULL);
1814
1815 node = tree->root;
1816 parent = node->parent;
1817 next_sibling = node->next_sib;
1818 child = (node->flags & TV_NFLAGS_EXPANDED) ? node->children : NULL;
1819
1820 while (node != NULL) {
1821
1822 if (child != NULL) {
1823 /* Down to children */
1824 node = child;
1825 } else {
1826 /* No children. As long as we're not at the root,
1827 * go to next sibling if present, or nearest ancestor
1828 * with a next sibling. */
1829
1830 while (node->parent != NULL &&
1831 next_sibling == NULL) {
1832 if (node->type == TREE_NODE_FOLDER &&
1833 node->children == NULL) {
1834 /* Delete node */
1835 p = node->parent;
1836 err = treeview_delete_node_walk_cb(
1837 node, &nd, &abort);
1838 if (err != NSERROR_OK) {
1839 return err;
1840 }
1841
1842 /* Reduce ancestor heights */
1843 while (p != NULL &&
1844 p->flags &
1845 TV_NFLAGS_EXPANDED) {
1846 p->height -= nd.h_reduction;
1847 p = p->parent;
1848 }
1849 nd.h_reduction = 0;
1850 }
1851 node = parent;
1852 parent = node->parent;
1853 next_sibling = node->next_sib;
1854 }
1855
1856 if (node->parent == NULL)
1857 break;
1858
1859 if (node->type == TREE_NODE_FOLDER &&
1860 node->children == NULL) {
1861 /* Delete node */
1862 p = node->parent;
1863 err = treeview_delete_node_walk_cb(
1864 node, &nd, &abort);
1865 if (err != NSERROR_OK) {
1866 return err;
1867 }
1868
1869 /* Reduce ancestor heights */
1870 while (p != NULL &&
1871 p->flags & TV_NFLAGS_EXPANDED) {
1872 p->height -= nd.h_reduction;
1873 p = p->parent;
1874 }
1875 nd.h_reduction = 0;
1876 }
1877 node = next_sibling;
1878 }
1879
1880 assert(node != NULL);
1881 assert(node->parent != NULL);
1882
1883 parent = node->parent;
1884 next_sibling = node->next_sib;
1885 child = (node->flags & TV_NFLAGS_EXPANDED) ?
1886 node->children : NULL;
1887 }
1888
1889 return NSERROR_OK;
1890 }
1891
1892
1893 /* Exported interface, documented in treeview.h */
1894 nserror
treeview_delete_node(treeview * tree,treeview_node * n,treeview_node_options_flags flags)1895 treeview_delete_node(treeview *tree,
1896 treeview_node *n,
1897 treeview_node_options_flags flags)
1898 {
1899 nserror err;
1900 struct rect r;
1901 bool visible;
1902
1903 assert(tree != NULL);
1904 assert(n != NULL);
1905 assert(n->parent != NULL);
1906
1907 visible = n->parent->flags & TV_NFLAGS_EXPANDED;
1908
1909 r.y0 = treeview_node_y(tree, n);
1910 r.y1 = tree->root->height;
1911
1912 err = treeview_delete_node_internal(tree, n, false, flags);
1913 if (err != NSERROR_OK)
1914 return err;
1915
1916 if (tree->flags & TREEVIEW_DEL_EMPTY_DIRS) {
1917 int h = tree->root->height;
1918 /* Delete any empty nodes */
1919 err = treeview_delete_empty_nodes(tree, false);
1920 if (err != NSERROR_OK)
1921 return err;
1922
1923 /* Inform front end of change in dimensions */
1924 if (tree->root->height != h) {
1925 r.y0 = 0;
1926 if (!(flags & TREE_OPTION_SUPPRESS_RESIZE)) {
1927 treeview__cw_update_size(tree, -1,
1928 tree->root->height);
1929 }
1930 }
1931 }
1932
1933 /* Redraw */
1934 if (visible && !(flags & TREE_OPTION_SUPPRESS_REDRAW)) {
1935 r.x0 = 0;
1936 r.x1 = REDRAW_MAX;
1937 treeview__cw_invalidate_area(tree, &r);
1938 }
1939
1940 return NSERROR_OK;
1941 }
1942
1943
1944 /**
1945 * Helper to create a textarea.
1946 *
1947 * \param[in] tree The treeview we're creating the textarea for.
1948 * \param[in] width The width of the textarea.
1949 * \param[in] height The height of the textarea.
1950 * \param[in] border The border colour to use.
1951 * \param[in] background The background colour to use.
1952 * \param[in] foreground The foreground colour to use.
1953 * \param[in] text The text style to use for the text area.
1954 * \param[in] ta_callback The textarea callback function to give the textarea.
1955 * \return the textarea pointer on success, or NULL on failure.
1956 */
treeview__create_textarea(treeview * tree,int width,int height,colour border,colour background,colour foreground,plot_font_style_t text,textarea_client_callback ta_callback)1957 static struct textarea *treeview__create_textarea(
1958 treeview *tree,
1959 int width,
1960 int height,
1961 colour border,
1962 colour background,
1963 colour foreground,
1964 plot_font_style_t text,
1965 textarea_client_callback ta_callback)
1966 {
1967 /* Configure the textarea */
1968 textarea_flags ta_flags = TEXTAREA_INTERNAL_CARET;
1969 textarea_setup ta_setup = {
1970 .text = text,
1971 .width = width,
1972 .height = height,
1973 .pad_top = 0,
1974 .pad_left = 2,
1975 .pad_right = 2,
1976 .pad_bottom = 0,
1977 .border_width = 1,
1978 .border_col = border,
1979 .selected_bg = foreground,
1980 .selected_text = background,
1981 };
1982
1983 ta_setup.text.foreground = foreground;
1984 ta_setup.text.background = background;
1985
1986 /* Create text area */
1987 return textarea_create(ta_flags, &ta_setup, ta_callback, tree);
1988 }
1989
1990
1991 /* Exported interface, documented in treeview.h */
1992 nserror
treeview_create(treeview ** tree,const struct treeview_callback_table * callbacks,int n_fields,struct treeview_field_desc fields[],const struct core_window_callback_table * cw_t,struct core_window * cw,treeview_flags flags)1993 treeview_create(treeview **tree,
1994 const struct treeview_callback_table *callbacks,
1995 int n_fields,
1996 struct treeview_field_desc fields[],
1997 const struct core_window_callback_table *cw_t,
1998 struct core_window *cw,
1999 treeview_flags flags)
2000 {
2001 nserror error;
2002 int i;
2003
2004 assert((cw_t == NULL && cw == NULL) || (cw_t != NULL && cw != NULL));
2005 assert(callbacks != NULL);
2006
2007 assert(fields != NULL);
2008 assert(fields[0].flags & TREE_FLAG_DEFAULT);
2009 assert(fields[n_fields - 1].flags & TREE_FLAG_DEFAULT);
2010 assert(n_fields >= 2);
2011
2012 *tree = malloc(sizeof(struct treeview));
2013 if (*tree == NULL) {
2014 return NSERROR_NOMEM;
2015 }
2016
2017 (*tree)->fields = malloc(sizeof(struct treeview_field) * n_fields);
2018 if ((*tree)->fields == NULL) {
2019 free(*tree);
2020 return NSERROR_NOMEM;
2021 }
2022
2023 error = treeview_create_node_root(&((*tree)->root));
2024 if (error != NSERROR_OK) {
2025 free((*tree)->fields);
2026 free(*tree);
2027 return error;
2028 }
2029
2030 (*tree)->field_width = 0;
2031 for (i = 0; i < n_fields; i++) {
2032 struct treeview_field *f = &((*tree)->fields[i]);
2033
2034 f->flags = fields[i].flags;
2035 f->field = lwc_string_ref(fields[i].field);
2036 f->value.data = lwc_string_data(fields[i].field);
2037 f->value.len = lwc_string_length(fields[i].field);
2038
2039 guit->layout->width(&plot_style_odd.text, f->value.data,
2040 f->value.len, &(f->value.width));
2041
2042 if (f->flags & TREE_FLAG_SHOW_NAME)
2043 if ((*tree)->field_width < f->value.width)
2044 (*tree)->field_width = f->value.width;
2045 }
2046
2047 (*tree)->field_width += tree_g.step_width;
2048
2049 (*tree)->callbacks = callbacks;
2050 (*tree)->n_fields = n_fields - 1;
2051
2052 (*tree)->drag.type = TV_DRAG_NONE;
2053 (*tree)->drag.start_node = NULL;
2054 (*tree)->drag.start.x = 0;
2055 (*tree)->drag.start.y = 0;
2056 (*tree)->drag.start.node_y = 0;
2057 (*tree)->drag.start.node_h = 0;
2058 (*tree)->drag.prev.x = 0;
2059 (*tree)->drag.prev.y = 0;
2060 (*tree)->drag.prev.node_y = 0;
2061 (*tree)->drag.prev.node_h = 0;
2062
2063 (*tree)->move.root = NULL;
2064 (*tree)->move.target = NULL;
2065 (*tree)->move.target_pos = TV_TARGET_NONE;
2066
2067 (*tree)->edit.textarea = NULL;
2068 (*tree)->edit.node = NULL;
2069
2070 if (flags & TREEVIEW_SEARCHABLE) {
2071 (*tree)->search.textarea = treeview__create_textarea(
2072 *tree, 600, tree_g.line_height,
2073 nscolours[NSCOLOUR_TEXT_INPUT_BG],
2074 nscolours[NSCOLOUR_TEXT_INPUT_BG],
2075 nscolours[NSCOLOUR_TEXT_INPUT_FG],
2076 plot_style_odd.text,
2077 treeview_textarea_search_callback);
2078 if ((*tree)->search.textarea == NULL) {
2079 treeview_destroy(*tree);
2080 return NSERROR_NOMEM;
2081 }
2082 } else {
2083 (*tree)->search.textarea = NULL;
2084 }
2085 (*tree)->search.active = false;
2086 (*tree)->search.search = false;
2087
2088 (*tree)->flags = flags;
2089
2090 (*tree)->cw_t = cw_t;
2091 (*tree)->cw_h = cw;
2092
2093 return NSERROR_OK;
2094 }
2095
2096
2097 /* Exported interface, documented in treeview.h */
2098 nserror
treeview_cw_attach(treeview * tree,const struct core_window_callback_table * cw_t,struct core_window * cw)2099 treeview_cw_attach(treeview *tree,
2100 const struct core_window_callback_table *cw_t,
2101 struct core_window *cw)
2102 {
2103 assert(cw_t != NULL);
2104 assert(cw != NULL);
2105
2106 if (tree->cw_t != NULL || tree->cw_h != NULL) {
2107 NSLOG(netsurf, INFO, "Treeview already attached.");
2108 return NSERROR_UNKNOWN;
2109 }
2110 tree->cw_t = cw_t;
2111 tree->cw_h = cw;
2112
2113 return NSERROR_OK;
2114 }
2115
2116
2117 /* Exported interface, documented in treeview.h */
treeview_cw_detach(treeview * tree)2118 nserror treeview_cw_detach(treeview *tree)
2119 {
2120 tree->cw_t = NULL;
2121 tree->cw_h = NULL;
2122
2123 treeview__search_cancel(tree, true);
2124
2125 return NSERROR_OK;
2126 }
2127
2128
2129 /* Exported interface, documented in treeview.h */
treeview_destroy(treeview * tree)2130 nserror treeview_destroy(treeview *tree)
2131 {
2132 int f;
2133
2134 assert(tree != NULL);
2135
2136 if (tree->search.textarea != NULL) {
2137 tree->search.active = false;
2138 tree->search.search = false;
2139 textarea_destroy(tree->search.textarea);
2140 }
2141
2142 /* Destroy nodes */
2143 treeview_delete_node_internal(tree, tree->root, false,
2144 TREE_OPTION_SUPPRESS_RESIZE |
2145 TREE_OPTION_SUPPRESS_REDRAW);
2146
2147 /* Destroy feilds */
2148 for (f = 0; f <= tree->n_fields; f++) {
2149 lwc_string_unref(tree->fields[f].field);
2150 }
2151 free(tree->fields);
2152
2153 /* Free treeview */
2154 free(tree);
2155
2156 return NSERROR_OK;
2157 }
2158
2159
2160 /**
2161 * Expand a treeview's nodes
2162 *
2163 * \param tree Treeview object to expand nodes in
2164 * \param node The node to expand.
2165 * \return NSERROR_OK on success, appropriate error otherwise.
2166 */
2167 static nserror
treeview_node_expand_internal(treeview * tree,treeview_node * node)2168 treeview_node_expand_internal(treeview *tree, treeview_node *node)
2169 {
2170 treeview_node *child;
2171 struct treeview_node_entry *e;
2172 int additional_height_folders = 0;
2173 int additional_height_entries = 0;
2174 int i;
2175
2176 assert(tree != NULL);
2177 assert(node != NULL);
2178
2179 if (node->flags & TV_NFLAGS_EXPANDED) {
2180 /* What madness is this? */
2181 NSLOG(netsurf, INFO, "Tried to expand an expanded node.");
2182 return NSERROR_OK;
2183 }
2184
2185 switch (node->type) {
2186 case TREE_NODE_FOLDER:
2187 child = node->children;
2188 if (child == NULL) {
2189 /* Allow expansion of empty folders */
2190 break;
2191 }
2192
2193 do {
2194 if (child->text.width == 0) {
2195 guit->layout->width(&plot_style_odd.text,
2196 child->text.data,
2197 child->text.len,
2198 &(child->text.width));
2199 }
2200
2201 additional_height_folders += child->height;
2202
2203 child = child->next_sib;
2204 } while (child != NULL);
2205
2206 break;
2207
2208 case TREE_NODE_ENTRY:
2209 assert(node->children == NULL);
2210
2211 e = (struct treeview_node_entry *)node;
2212
2213 for (i = 0; i < tree->n_fields - 1; i++) {
2214
2215 if (e->fields[i].value.width == 0) {
2216 guit->layout->width(&plot_style_odd.text,
2217 e->fields[i].value.data,
2218 e->fields[i].value.len,
2219 &(e->fields[i].value.width));
2220 }
2221
2222 /* Add height for field */
2223 additional_height_entries += tree_g.line_height;
2224 }
2225
2226 break;
2227
2228 case TREE_NODE_ROOT:
2229 case TREE_NODE_NONE:
2230 assert(node->type != TREE_NODE_ROOT);
2231 assert(node->type != TREE_NODE_NONE);
2232 break;
2233 }
2234
2235 /* Update the node */
2236 node->flags |= TV_NFLAGS_EXPANDED;
2237
2238 /* And node heights */
2239 for (struct treeview_node *n = node;
2240 (n != NULL) && (n->flags & TV_NFLAGS_EXPANDED);
2241 n = n->parent) {
2242 n->height += additional_height_entries +
2243 additional_height_folders;
2244 }
2245
2246 if (tree->search.search &&
2247 node->type == TREE_NODE_ENTRY &&
2248 node->flags & TV_NFLAGS_MATCHED) {
2249 tree->search.height += additional_height_entries;
2250 }
2251
2252 /* Inform front end of change in dimensions */
2253 if (additional_height_entries + additional_height_folders != 0) {
2254 treeview__cw_update_size(tree, -1,
2255 treeview__get_display_height(tree));
2256 }
2257
2258 return NSERROR_OK;
2259 }
2260
2261
2262 /* Exported interface, documented in treeview.h */
treeview_node_expand(treeview * tree,treeview_node * node)2263 nserror treeview_node_expand(treeview *tree, treeview_node *node)
2264 {
2265 nserror res;
2266
2267 res = treeview_node_expand_internal(tree, node);
2268 NSLOG(netsurf, INFO, "Expanding!");
2269 if (res == NSERROR_OK) {
2270 /* expansion was successful, attempt redraw */
2271 treeview__redraw_from_node(tree, node);
2272 NSLOG(netsurf, INFO, "Expanded!");
2273 }
2274
2275 return res;
2276 }
2277
2278
2279 /**
2280 * context for treeview contraction callback
2281 */
2282 struct treeview_contract_data {
2283 treeview *tree;
2284 bool only_entries;
2285 };
2286
2287
2288 /**
2289 * Treewalk node callback for handling node contraction.
2290 *
2291 * \param n node
2292 * \param ctx contract iterator context
2293 * \param end flag to end iteration now
2294 * \return NSERROR_OK on success else appropriate error code
2295 */
treeview_node_contract_cb(treeview_node * n,void * ctx,bool * end)2296 static nserror treeview_node_contract_cb(treeview_node *n, void *ctx, bool *end)
2297 {
2298 struct treeview_contract_data *data = ctx;
2299 int h_reduction_folder = 0;
2300 int h_reduction_entry = 0;
2301
2302 assert(n != NULL);
2303 assert(n->type != TREE_NODE_ROOT);
2304
2305 n->flags &= ~TV_NFLAGS_SELECTED;
2306
2307 if ((n->flags & TV_NFLAGS_EXPANDED) == false ||
2308 (n->type == TREE_NODE_FOLDER && data->only_entries)) {
2309 /* Nothing to do. */
2310 return NSERROR_OK;
2311 }
2312
2313
2314 switch (n->type) {
2315 case TREE_NODE_FOLDER:
2316 h_reduction_folder = n->height - tree_g.line_height;
2317 break;
2318
2319 case TREE_NODE_ENTRY:
2320 h_reduction_entry = n->height - tree_g.line_height;
2321 break;
2322
2323 default:
2324 break;
2325 }
2326
2327
2328 assert(h_reduction_folder + h_reduction_entry >= 0);
2329 for (struct treeview_node *node = n;
2330 (node != NULL) && (node->flags & TV_NFLAGS_EXPANDED);
2331 node = node->parent) {
2332 node->height -= h_reduction_folder + h_reduction_entry;
2333 }
2334
2335 if (data->tree->search.search) {
2336 data->tree->search.height -= h_reduction_entry;
2337 }
2338
2339 n->flags ^= TV_NFLAGS_EXPANDED;
2340
2341 return NSERROR_OK;
2342 }
2343
2344
2345 /**
2346 * Contract a treeview node
2347 *
2348 * \param tree Treeview object to contract node in
2349 * \param node Node to contract
2350 * \return NSERROR_OK on success, appropriate error otherwise
2351 */
2352 static nserror
treeview_node_contract_internal(treeview * tree,treeview_node * node)2353 treeview_node_contract_internal(treeview *tree, treeview_node *node)
2354 {
2355 struct treeview_contract_data data;
2356 bool selected;
2357 assert(node != NULL);
2358
2359 if ((node->flags & TV_NFLAGS_EXPANDED) == false) {
2360 /* What madness is this? */
2361 NSLOG(netsurf, INFO, "Tried to contract a contracted node.");
2362 return NSERROR_OK;
2363 }
2364
2365 data.tree = tree;
2366 data.only_entries = false;
2367 selected = node->flags & TV_NFLAGS_SELECTED;
2368
2369 /* Contract children. */
2370 treeview_walk_internal(tree, node, TREEVIEW_WALK_MODE_LOGICAL_EXPANDED,
2371 treeview_node_contract_cb, NULL, &data);
2372
2373 /* Contract node */
2374 treeview_node_contract_cb(node, &data, false);
2375
2376 if (selected)
2377 node->flags |= TV_NFLAGS_SELECTED;
2378
2379 /* Inform front end of change in dimensions */
2380 treeview__cw_update_size(tree, -1, treeview__get_display_height(tree));
2381
2382 return NSERROR_OK;
2383 }
2384
2385
2386 /* Exported interface, documented in treeview.h */
treeview_node_contract(treeview * tree,treeview_node * node)2387 nserror treeview_node_contract(treeview *tree, treeview_node *node)
2388 {
2389 nserror res;
2390
2391 assert(tree != NULL);
2392
2393 res = treeview_node_contract_internal(tree, node);
2394 NSLOG(netsurf, INFO, "Contracting!");
2395 if (res == NSERROR_OK) {
2396 /* successful contraction, request redraw */
2397 treeview__redraw_from_node(tree, node);
2398 NSLOG(netsurf, INFO, "Contracted!");
2399 }
2400
2401 return res;
2402 }
2403
2404
2405 /* Exported interface, documented in treeview.h */
treeview_contract(treeview * tree,bool all)2406 nserror treeview_contract(treeview *tree, bool all)
2407 {
2408 int search_height = treeview__get_search_height(tree);
2409 struct treeview_contract_data data;
2410 bool selected;
2411 treeview_node *n;
2412 struct rect r;
2413
2414 assert(tree != NULL);
2415 assert(tree->root != NULL);
2416
2417 r.x0 = 0;
2418 r.y0 = 0;
2419 r.x1 = REDRAW_MAX;
2420 r.y1 = tree->root->height + search_height;
2421
2422 data.tree = tree;
2423 data.only_entries = !all;
2424
2425 for (n = tree->root->children; n != NULL; n = n->next_sib) {
2426 if ((n->flags & TV_NFLAGS_EXPANDED) == false) {
2427 continue;
2428 }
2429
2430 selected = n->flags & TV_NFLAGS_SELECTED;
2431
2432 /* Contract children. */
2433 treeview_walk_internal(tree, n,
2434 TREEVIEW_WALK_MODE_LOGICAL_EXPANDED,
2435 treeview_node_contract_cb, NULL, &data);
2436
2437 /* Contract node */
2438 treeview_node_contract_cb(n, &data, false);
2439
2440 if (selected)
2441 n->flags |= TV_NFLAGS_SELECTED;
2442 }
2443
2444 /* Inform front end of change in dimensions */
2445 treeview__cw_update_size(tree, -1, tree->root->height);
2446
2447 /* Redraw */
2448 treeview__cw_invalidate_area(tree, &r);
2449
2450 return NSERROR_OK;
2451 }
2452
2453
2454 /**
2455 * context data for treeview expansion
2456 */
2457 struct treeview_expand_data {
2458 treeview *tree;
2459 bool only_folders;
2460 };
2461
2462
2463 /**
2464 * Treewalk node callback for handling recursive node expansion.
2465 *
2466 * \param n current node
2467 * \param ctx node expansion context
2468 * \param skip_children flag to allow children to be skipped
2469 * \param end flag to allow iteration to be finished early.
2470 * \return NSERROR_OK on success else error code.
2471 */
2472 static nserror
treeview_expand_cb(treeview_node * n,void * ctx,bool * skip_children,bool * end)2473 treeview_expand_cb(treeview_node *n,
2474 void *ctx,
2475 bool *skip_children,
2476 bool *end)
2477 {
2478 struct treeview_expand_data *data = ctx;
2479 nserror err;
2480
2481 assert(n != NULL);
2482 assert(n->type != TREE_NODE_ROOT);
2483
2484 if (n->flags & TV_NFLAGS_EXPANDED ||
2485 (data->only_folders && n->type != TREE_NODE_FOLDER)) {
2486 /* Nothing to do. */
2487 return NSERROR_OK;
2488 }
2489
2490 err = treeview_node_expand_internal(data->tree, n);
2491
2492 return err;
2493 }
2494
2495
2496 /* Exported interface, documented in treeview.h */
treeview_expand(treeview * tree,bool only_folders)2497 nserror treeview_expand(treeview *tree, bool only_folders)
2498 {
2499 struct treeview_expand_data data;
2500 nserror res;
2501 struct rect r;
2502
2503 assert(tree != NULL);
2504 assert(tree->root != NULL);
2505
2506 data.tree = tree;
2507 data.only_folders = only_folders;
2508
2509 res = treeview_walk_internal(tree, tree->root,
2510 TREEVIEW_WALK_MODE_LOGICAL_COMPLETE,
2511 NULL, treeview_expand_cb, &data);
2512 if (res == NSERROR_OK) {
2513 /* expansion succeeded, schedule redraw */
2514
2515 r.x0 = 0;
2516 r.y0 = 0;
2517 r.x1 = REDRAW_MAX;
2518 r.y1 = tree->root->height;
2519
2520 treeview__cw_invalidate_area(tree, &r);
2521 }
2522 return res;
2523 }
2524
2525
2526 /**
2527 * Draw a treeview normally, in tree mode.
2528 *
2529 * \param[in] tree The treeview we're rendering.
2530 * \param[in] x X coordinate we're rendering the treeview at.
2531 * \param[in] y Y coordinate we're rendering the treeview at.
2532 * \param[in,out] render_y Current vertical position in tree, updated on exit.
2533 * \param[in] r Clip rectangle.
2534 * \param[in] data Redraw data for rendering contents.
2535 * \param[in] ctx Current render context.
2536 */
treeview_redraw_tree(treeview * tree,const int x,const int y,int * render_y_in_out,const struct rect * r,struct content_redraw_data * data,const struct redraw_context * ctx)2537 static void treeview_redraw_tree(
2538 treeview *tree,
2539 const int x,
2540 const int y,
2541 int *render_y_in_out,
2542 const struct rect *r,
2543 struct content_redraw_data *data,
2544 const struct redraw_context *ctx)
2545 {
2546 struct treeview_node_style *style = &plot_style_odd;
2547 enum treeview_resource_id res = TREE_RES_CONTENT;
2548 int baseline = (tree_g.line_height * 3 + 2) / 4;
2549 plot_font_style_t *infotext_style;
2550 treeview_node *root = tree->root;
2551 treeview_node *node = tree->root;
2552 int render_y = *render_y_in_out;
2553 plot_font_style_t *text_style;
2554 plot_style_t *bg_style;
2555 int sel_min, sel_max;
2556 uint32_t count = 0;
2557 struct rect rect;
2558 int inset;
2559 int x0;
2560
2561 if (tree->drag.start.y > tree->drag.prev.y) {
2562 sel_min = tree->drag.prev.y;
2563 sel_max = tree->drag.start.y;
2564 } else {
2565 sel_min = tree->drag.start.y;
2566 sel_max = tree->drag.prev.y;
2567 }
2568
2569 while (node != NULL) {
2570 struct treeview_node_entry *entry;
2571 struct bitmap *furniture;
2572 bool invert_selection;
2573 treeview_node *next;
2574 int height;
2575 int i;
2576
2577 next = (node->flags & TV_NFLAGS_EXPANDED) ?
2578 node->children : NULL;
2579
2580 if (next != NULL) {
2581 /* down to children */
2582 node = next;
2583 } else {
2584 /* No children. As long as we're not at the root,
2585 * go to next sibling if present, or nearest ancestor
2586 * with a next sibling. */
2587
2588 while (node != root &&
2589 node->next_sib == NULL) {
2590 node = node->parent;
2591 }
2592
2593 if (node == root)
2594 break;
2595
2596 node = node->next_sib;
2597 }
2598
2599 assert(node != NULL);
2600 assert(node != root);
2601 assert(node->type == TREE_NODE_FOLDER ||
2602 node->type == TREE_NODE_ENTRY);
2603
2604 count++;
2605 inset = x + node->inset;
2606 height = (node->type == TREE_NODE_ENTRY) ? node->height :
2607 tree_g.line_height;
2608
2609 if ((render_y + height) < r->y0) {
2610 /* This node's line is above clip region */
2611 render_y += height;
2612 continue;
2613 }
2614
2615 style = (count & 0x1) ? &plot_style_odd : &plot_style_even;
2616 if (tree->drag.type == TV_DRAG_SELECTION &&
2617 (render_y + height >= sel_min &&
2618 render_y < sel_max)) {
2619 invert_selection = true;
2620 } else {
2621 invert_selection = false;
2622 }
2623 if ((node->flags & TV_NFLAGS_SELECTED && !invert_selection) ||
2624 (!(node->flags & TV_NFLAGS_SELECTED) &&
2625 invert_selection)) {
2626 bg_style = &style->sbg;
2627 text_style = &style->stext;
2628 infotext_style = &style->sitext;
2629 furniture = (node->flags & TV_NFLAGS_EXPANDED) ?
2630 style->furn[TREE_FURN_CONTRACT].sel :
2631 style->furn[TREE_FURN_EXPAND].sel;
2632 } else {
2633 bg_style = &style->bg;
2634 text_style = &style->text;
2635 infotext_style = &style->itext;
2636 furniture = (node->flags & TV_NFLAGS_EXPANDED) ?
2637 style->furn[TREE_FURN_CONTRACT].bmp :
2638 style->furn[TREE_FURN_EXPAND].bmp;
2639 }
2640
2641 /* Render background */
2642 rect.x0 = r->x0;
2643 rect.y0 = render_y;
2644 rect.x1 = r->x1;
2645 rect.y1 = render_y + height;
2646 ctx->plot->rectangle(ctx, bg_style, &rect);
2647
2648 /* Render toggle */
2649 ctx->plot->bitmap(ctx,
2650 furniture,
2651 inset,
2652 render_y + tree_g.line_height / 4,
2653 style->furn[TREE_FURN_EXPAND].size,
2654 style->furn[TREE_FURN_EXPAND].size,
2655 bg_style->fill_colour,
2656 BITMAPF_NONE);
2657
2658 /* Render icon */
2659 if (node->type == TREE_NODE_ENTRY) {
2660 res = TREE_RES_CONTENT;
2661 } else if (node->flags & TV_NFLAGS_SPECIAL) {
2662 res = TREE_RES_FOLDER_SPECIAL;
2663 } else {
2664 res = TREE_RES_FOLDER;
2665 }
2666
2667 if (treeview_res[res].ready) {
2668 /* Icon resource is available */
2669 data->x = inset + tree_g.step_width;
2670 data->y = render_y + ((tree_g.line_height -
2671 treeview_res[res].height + 1) / 2);
2672 data->background_colour = bg_style->fill_colour;
2673
2674 content_redraw(treeview_res[res].c, data, r, ctx);
2675 }
2676
2677 /* Render text */
2678 x0 = inset + tree_g.step_width + tree_g.icon_step;
2679 ctx->plot->text(ctx,
2680 text_style,
2681 x0, render_y + baseline,
2682 node->text.data,
2683 node->text.len);
2684
2685 /* Rendered the node */
2686 render_y += tree_g.line_height;
2687 if (render_y > r->y1) {
2688 /* Passed the bottom of what's in the clip region.
2689 * Done. */
2690 break;
2691 }
2692
2693
2694 if (node->type != TREE_NODE_ENTRY ||
2695 !(node->flags & TV_NFLAGS_EXPANDED))
2696 /* Done everything for this node */
2697 continue;
2698
2699 /* Render expanded entry fields */
2700 entry = (struct treeview_node_entry *)node;
2701 for (i = 0; i < tree->n_fields - 1; i++) {
2702 struct treeview_field *ef = &(tree->fields[i + 1]);
2703
2704 if (ef->flags & TREE_FLAG_SHOW_NAME) {
2705 int max_width = tree->field_width;
2706
2707 ctx->plot->text(ctx,
2708 infotext_style,
2709 x0 + max_width - ef->value.width - tree_g.step_width,
2710 render_y + baseline,
2711 ef->value.data,
2712 ef->value.len);
2713
2714 ctx->plot->text(ctx,
2715 infotext_style,
2716 x0 + max_width,
2717 render_y + baseline,
2718 entry->fields[i].value.data,
2719 entry->fields[i].value.len);
2720 } else {
2721 ctx->plot->text(ctx,
2722 infotext_style,
2723 x0, render_y + baseline,
2724 entry->fields[i].value.data,
2725 entry->fields[i].value.len);
2726 }
2727
2728 /* Rendered the expanded entry field */
2729 render_y += tree_g.line_height;
2730 }
2731
2732 /* Finished rendering expanded entry */
2733
2734 if (render_y > r->y1) {
2735 /* Passed the bottom of what's in the clip region.
2736 * Done. */
2737 break;
2738 }
2739 }
2740
2741 *render_y_in_out = render_y;
2742 }
2743
2744
2745 /**
2746 * Draw a treeview normally, in tree mode.
2747 *
2748 * \param[in] tree The treeview we're rendering.
2749 * \param[in] x X coordinate we're rendering the treeview at.
2750 * \param[in] y Y coordinate we're rendering the treeview at.
2751 * \param[in,out] render_y Current vertical position in tree, updated on exit.
2752 * \param[in] r Clip rectangle.
2753 * \param[in] data Redraw data for rendering contents.
2754 * \param[in] ctx Current render context.
2755 */
treeview_redraw_search(treeview * tree,const int x,const int y,int * render_y_in_out,const struct rect * r,struct content_redraw_data * data,const struct redraw_context * ctx)2756 static void treeview_redraw_search(
2757 treeview *tree,
2758 const int x,
2759 const int y,
2760 int *render_y_in_out,
2761 const struct rect *r,
2762 struct content_redraw_data *data,
2763 const struct redraw_context *ctx)
2764 {
2765 struct treeview_node_style *style = &plot_style_odd;
2766 enum treeview_resource_id res = TREE_RES_CONTENT;
2767 int baseline = (tree_g.line_height * 3 + 2) / 4;
2768 plot_font_style_t *infotext_style;
2769 treeview_node *root = tree->root;
2770 treeview_node *node = tree->root;
2771 int render_y = *render_y_in_out;
2772 plot_font_style_t *text_style;
2773 plot_style_t *bg_style;
2774 int sel_min, sel_max;
2775 uint32_t count = 0;
2776 struct rect rect;
2777 int inset;
2778 int x0;
2779
2780 if (tree->drag.start.y > tree->drag.prev.y) {
2781 sel_min = tree->drag.prev.y;
2782 sel_max = tree->drag.start.y;
2783 } else {
2784 sel_min = tree->drag.start.y;
2785 sel_max = tree->drag.prev.y;
2786 }
2787
2788 while (node != NULL) {
2789 struct treeview_node_entry *entry;
2790 struct bitmap *furniture;
2791 bool invert_selection;
2792 treeview_node *next;
2793 int height;
2794 int i;
2795
2796 next = node->children;
2797
2798 if (next != NULL) {
2799 /* down to children */
2800 node = next;
2801 } else {
2802 /* No children. As long as we're not at the root,
2803 * go to next sibling if present, or nearest ancestor
2804 * with a next sibling. */
2805
2806 while (node != root &&
2807 node->next_sib == NULL) {
2808 node = node->parent;
2809 }
2810
2811 if (node == root)
2812 break;
2813
2814 node = node->next_sib;
2815 }
2816
2817 assert(node != NULL);
2818 assert(node != root);
2819 assert(node->type == TREE_NODE_FOLDER ||
2820 node->type == TREE_NODE_ENTRY);
2821
2822 if (node->type == TREE_NODE_FOLDER ||
2823 !(node->flags & TV_NFLAGS_MATCHED)) {
2824 continue;
2825 }
2826
2827 count++;
2828 inset = x + tree_g.window_padding;
2829 height = node->height;
2830
2831 if ((render_y + height) < r->y0) {
2832 /* This node's line is above clip region */
2833 render_y += height;
2834 continue;
2835 }
2836
2837 style = (count & 0x1) ? &plot_style_odd : &plot_style_even;
2838 if (tree->drag.type == TV_DRAG_SELECTION &&
2839 (render_y + height >= sel_min &&
2840 render_y < sel_max)) {
2841 invert_selection = true;
2842 } else {
2843 invert_selection = false;
2844 }
2845 if ((node->flags & TV_NFLAGS_SELECTED && !invert_selection) ||
2846 (!(node->flags & TV_NFLAGS_SELECTED) &&
2847 invert_selection)) {
2848 bg_style = &style->sbg;
2849 text_style = &style->stext;
2850 infotext_style = &style->sitext;
2851 furniture = (node->flags & TV_NFLAGS_EXPANDED) ?
2852 style->furn[TREE_FURN_CONTRACT].sel :
2853 style->furn[TREE_FURN_EXPAND].sel;
2854 } else {
2855 bg_style = &style->bg;
2856 text_style = &style->text;
2857 infotext_style = &style->itext;
2858 furniture = (node->flags & TV_NFLAGS_EXPANDED) ?
2859 style->furn[TREE_FURN_CONTRACT].bmp :
2860 style->furn[TREE_FURN_EXPAND].bmp;
2861 }
2862
2863 /* Render background */
2864 rect.x0 = r->x0;
2865 rect.y0 = render_y;
2866 rect.x1 = r->x1;
2867 rect.y1 = render_y + height;
2868 ctx->plot->rectangle(ctx, bg_style, &rect);
2869
2870 /* Render toggle */
2871 ctx->plot->bitmap(ctx,
2872 furniture,
2873 inset,
2874 render_y + tree_g.line_height / 4,
2875 style->furn[TREE_FURN_EXPAND].size,
2876 style->furn[TREE_FURN_EXPAND].size,
2877 bg_style->fill_colour,
2878 BITMAPF_NONE);
2879
2880 /* Render icon */
2881 if (node->type == TREE_NODE_ENTRY) {
2882 res = TREE_RES_CONTENT;
2883 } else if (node->flags & TV_NFLAGS_SPECIAL) {
2884 res = TREE_RES_FOLDER_SPECIAL;
2885 } else {
2886 res = TREE_RES_FOLDER;
2887 }
2888
2889 if (treeview_res[res].ready) {
2890 /* Icon resource is available */
2891 data->x = inset + tree_g.step_width;
2892 data->y = render_y + ((tree_g.line_height -
2893 treeview_res[res].height + 1) / 2);
2894 data->background_colour = bg_style->fill_colour;
2895
2896 content_redraw(treeview_res[res].c, data, r, ctx);
2897 }
2898
2899 /* Render text */
2900 x0 = inset + tree_g.step_width + tree_g.icon_step;
2901 ctx->plot->text(ctx,
2902 text_style,
2903 x0, render_y + baseline,
2904 node->text.data,
2905 node->text.len);
2906
2907 /* Rendered the node */
2908 render_y += tree_g.line_height;
2909 if (render_y > r->y1) {
2910 /* Passed the bottom of what's in the clip region.
2911 * Done. */
2912 break;
2913 }
2914
2915
2916 if (node->type != TREE_NODE_ENTRY ||
2917 !(node->flags & TV_NFLAGS_EXPANDED))
2918 /* Done everything for this node */
2919 continue;
2920
2921 /* Render expanded entry fields */
2922 entry = (struct treeview_node_entry *)node;
2923 for (i = 0; i < tree->n_fields - 1; i++) {
2924 struct treeview_field *ef = &(tree->fields[i + 1]);
2925
2926 if (ef->flags & TREE_FLAG_SHOW_NAME) {
2927 int max_width = tree->field_width;
2928
2929 ctx->plot->text(ctx,
2930 infotext_style,
2931 x0 + max_width - ef->value.width - tree_g.step_width,
2932 render_y + baseline,
2933 ef->value.data,
2934 ef->value.len);
2935
2936 ctx->plot->text(ctx,
2937 infotext_style,
2938 x0 + max_width,
2939 render_y + baseline,
2940 entry->fields[i].value.data,
2941 entry->fields[i].value.len);
2942 } else {
2943 ctx->plot->text(ctx,
2944 infotext_style,
2945 x0, render_y + baseline,
2946 entry->fields[i].value.data,
2947 entry->fields[i].value.len);
2948 }
2949
2950 /* Rendered the expanded entry field */
2951 render_y += tree_g.line_height;
2952 }
2953
2954 /* Finished rendering expanded entry */
2955
2956 if (render_y > r->y1) {
2957 /* Passed the bottom of what's in the clip region.
2958 * Done. */
2959 break;
2960 }
2961 }
2962
2963 *render_y_in_out = render_y;
2964 }
2965
2966
2967 /* Exported interface, documented in treeview.h */
2968 void
treeview_redraw(treeview * tree,const int x,const int y,struct rect * clip,const struct redraw_context * ctx)2969 treeview_redraw(treeview *tree,
2970 const int x,
2971 const int y,
2972 struct rect *clip,
2973 const struct redraw_context *ctx)
2974 {
2975 struct redraw_context new_ctx = *ctx;
2976 struct content_redraw_data data;
2977 struct rect r;
2978 struct rect rect;
2979 int render_y = y;
2980
2981 assert(tree != NULL);
2982 assert(tree->root != NULL);
2983 assert(tree->root->flags & TV_NFLAGS_EXPANDED);
2984
2985 /* Start knockout rendering if it's available for this plotter */
2986 if (ctx->plot->option_knockout) {
2987 knockout_plot_start(ctx, &new_ctx);
2988 }
2989
2990 /* Set up clip rectangle */
2991 r.x0 = clip->x0 + x;
2992 r.y0 = clip->y0 + y;
2993 r.x1 = clip->x1 + x;
2994 r.y1 = clip->y1 + y;
2995 new_ctx.plot->clip(&new_ctx, &r);
2996
2997 /* Setup common content redraw data */
2998 data.width = tree_g.icon_size;
2999 data.height = tree_g.icon_size;
3000 data.scale = 1;
3001 data.repeat_x = false;
3002 data.repeat_y = false;
3003
3004 if (tree->flags & TREEVIEW_SEARCHABLE) {
3005 if (render_y < r.y1) {
3006 enum treeview_resource_id icon = TREE_RES_SEARCH;
3007
3008 /* Fill the blank area at the bottom */
3009 rect.x0 = r.x0;
3010 rect.y0 = render_y;
3011 rect.x1 = r.x1;
3012 rect.y1 = render_y + tree_g.line_height;
3013 new_ctx.plot->rectangle(&new_ctx, &plot_style_even.bg,
3014 &rect);
3015
3016 if (treeview_res[icon].ready) {
3017 /* Icon resource is available */
3018 data.x = tree_g.window_padding;
3019 data.y = render_y + ((tree_g.line_height -
3020 treeview_res[icon].height + 1) /
3021 2);
3022 data.background_colour = plot_style_even.bg.
3023 fill_colour;
3024
3025 content_redraw(treeview_res[icon].c,
3026 &data, &r, &new_ctx);
3027 }
3028
3029 textarea_redraw(tree->search.textarea,
3030 x + tree_g.window_padding +
3031 tree_g.icon_step, y,
3032 plot_style_even.bg.fill_colour, 1.0,
3033 &r, &new_ctx);
3034 }
3035 render_y += tree_g.line_height;
3036 }
3037
3038 /* Render the treeview data */
3039 if (tree->search.search == true) {
3040 treeview_redraw_search(tree, x, y,
3041 &render_y, &r, &data, &new_ctx);
3042 } else {
3043 treeview_redraw_tree(tree, x, y,
3044 &render_y, &r, &data, &new_ctx);
3045 }
3046
3047 if (render_y < r.y1) {
3048 /* Fill the blank area at the bottom */
3049 rect.x0 = r.x0;
3050 rect.y0 = render_y;
3051 rect.x1 = r.x1;
3052 rect.y1 = r.y1;
3053 new_ctx.plot->rectangle(&new_ctx, &plot_style_even.bg, &rect);
3054 }
3055
3056 /* All normal treeview rendering is done; render any overlays */
3057 if ((tree->move.target_pos != TV_TARGET_NONE) &&
3058 (treeview_res[TREE_RES_ARROW].ready)) {
3059 /* Got a MOVE drag; render move indicator arrow */
3060 data.x = tree->move.target_area.x0 + x;
3061 data.y = tree->move.target_area.y0 + y;
3062 data.background_colour = plot_style_even.bg.fill_colour;
3063
3064 content_redraw(treeview_res[TREE_RES_ARROW].c, &data, &r, &new_ctx);
3065
3066 } else if (tree->edit.textarea != NULL) {
3067 /* Edit in progress; render textarea */
3068 textarea_redraw(tree->edit.textarea,
3069 tree->edit.x + x, tree->edit.y + y,
3070 plot_style_even.bg.fill_colour, 1.0,
3071 &r, &new_ctx);
3072 }
3073
3074 /* Rendering complete */
3075 if (ctx->plot->option_knockout) {
3076 knockout_plot_end(ctx);
3077 }
3078 }
3079
3080
3081 /**
3082 * context for treeview selection
3083 */
3084 struct treeview_selection_walk_data {
3085 enum {
3086 TREEVIEW_WALK_HAS_SELECTION,
3087 TREEVIEW_WALK_GET_FIRST_SELECTED,
3088 TREEVIEW_WALK_CLEAR_SELECTION,
3089 TREEVIEW_WALK_SELECT_ALL,
3090 TREEVIEW_WALK_COMMIT_SELECT_DRAG,
3091 TREEVIEW_WALK_DELETE_SELECTION,
3092 TREEVIEW_WALK_PROPAGATE_SELECTION,
3093 TREEVIEW_WALK_YANK_SELECTION,
3094 TREEVIEW_WALK_COPY_SELECTION
3095 } purpose;
3096 union {
3097 bool has_selection;
3098 struct {
3099 bool required;
3100 struct rect *rect;
3101 } redraw;
3102 struct {
3103 int sel_min;
3104 int sel_max;
3105 } drag;
3106 struct {
3107 treeview_node *prev;
3108 treeview_node *fixed;
3109 } yank;
3110 struct {
3111 treeview_node *n;
3112 } first;
3113 struct {
3114 char *text;
3115 uint32_t len;
3116 } copy;
3117 } data;
3118 int current_y;
3119 treeview *tree;
3120 };
3121
3122
3123 /**
3124 * Treewalk node callback for handling selection related actions.
3125 *
3126 * \param n current node
3127 * \param ctx node selection context
3128 * \param skip_children flag to allow children to be skipped
3129 * \param end flag to allow iteration to be finished early.
3130 * \return NSERROR_OK on success else error code.
3131 */
3132 static nserror
treeview_node_selection_walk_cb(treeview_node * n,void * ctx,bool * skip_children,bool * end)3133 treeview_node_selection_walk_cb(treeview_node *n,
3134 void *ctx,
3135 bool *skip_children,
3136 bool *end)
3137 {
3138 struct treeview_selection_walk_data *sw = ctx;
3139 int height;
3140 bool changed = false;
3141 nserror err;
3142
3143 height = (n->type == TREE_NODE_ENTRY) ? n->height : tree_g.line_height;
3144 sw->current_y += height;
3145
3146 switch (sw->purpose) {
3147 case TREEVIEW_WALK_HAS_SELECTION:
3148 if (n->flags & TV_NFLAGS_SELECTED) {
3149 sw->data.has_selection = true;
3150 *end = true; /* Can abort tree walk */
3151 return NSERROR_OK;
3152 }
3153 break;
3154
3155 case TREEVIEW_WALK_GET_FIRST_SELECTED:
3156 if (n->flags & TV_NFLAGS_SELECTED) {
3157 sw->data.first.n = n;
3158 *end = true; /* Can abort tree walk */
3159 return NSERROR_OK;
3160 }
3161 break;
3162
3163 case TREEVIEW_WALK_DELETE_SELECTION:
3164 if (n->flags & TV_NFLAGS_SELECTED) {
3165 err = treeview_delete_node_internal(sw->tree, n, true,
3166 TREE_OPTION_NONE);
3167 if (err != NSERROR_OK) {
3168 return err;
3169 }
3170 *skip_children = true;
3171 changed = true;
3172 }
3173 break;
3174
3175 case TREEVIEW_WALK_PROPAGATE_SELECTION:
3176 if (n->parent != NULL &&
3177 n->parent->flags & TV_NFLAGS_SELECTED &&
3178 !(n->flags & TV_NFLAGS_SELECTED)) {
3179 n->flags ^= TV_NFLAGS_SELECTED;
3180 changed = true;
3181 }
3182 break;
3183
3184 case TREEVIEW_WALK_CLEAR_SELECTION:
3185 if (n->flags & TV_NFLAGS_SELECTED) {
3186 n->flags ^= TV_NFLAGS_SELECTED;
3187 changed = true;
3188 }
3189 break;
3190
3191 case TREEVIEW_WALK_SELECT_ALL:
3192 if (!(n->flags & TV_NFLAGS_SELECTED)) {
3193 n->flags ^= TV_NFLAGS_SELECTED;
3194 changed = true;
3195 }
3196 break;
3197
3198 case TREEVIEW_WALK_COMMIT_SELECT_DRAG:
3199 if (sw->current_y >= sw->data.drag.sel_min &&
3200 sw->current_y - height <
3201 sw->data.drag.sel_max) {
3202 n->flags ^= TV_NFLAGS_SELECTED;
3203 }
3204 return NSERROR_OK;
3205
3206 case TREEVIEW_WALK_YANK_SELECTION:
3207 if (n->flags & TV_NFLAGS_SELECTED) {
3208 treeview_node *p = n->parent;
3209 int h = 0;
3210
3211 if (n == sw->data.yank.fixed) {
3212 break;
3213 }
3214
3215 if (treeview_unlink_node(n))
3216 h = n->height;
3217
3218 /* Reduce ancestor heights */
3219 while (p != NULL && p->flags & TV_NFLAGS_EXPANDED) {
3220 p->height -= h;
3221 p = p->parent;
3222 }
3223 if (sw->data.yank.prev == NULL) {
3224 sw->tree->move.root = n;
3225 n->parent = NULL;
3226 n->prev_sib = NULL;
3227 n->next_sib = NULL;
3228 } else {
3229 n->parent = NULL;
3230 n->prev_sib = sw->data.yank.prev;
3231 n->next_sib = NULL;
3232 sw->data.yank.prev->next_sib = n;
3233 }
3234 sw->data.yank.prev = n;
3235
3236 *skip_children = true;
3237 }
3238 break;
3239
3240 case TREEVIEW_WALK_COPY_SELECTION:
3241 if (n->flags & TV_NFLAGS_SELECTED &&
3242 n->type == TREE_NODE_ENTRY) {
3243 int i;
3244 char *temp;
3245 uint32_t len;
3246 const char *text;
3247 struct treeview_field *ef;
3248 struct treeview_text *val;
3249
3250 for (i = 0; i < sw->tree->n_fields; i++) {
3251 ef = &(sw->tree->fields[i]);
3252
3253 if (!(ef->flags & TREE_FLAG_COPY_TEXT)) {
3254 continue;
3255 }
3256 val = treeview_get_text_for_field(sw->tree,
3257 n, i);
3258 text = val->data;
3259 len = val->len;
3260
3261 temp = realloc(sw->data.copy.text,
3262 sw->data.copy.len + len + 1);
3263 if (temp == NULL) {
3264 free(sw->data.copy.text);
3265 sw->data.copy.text = NULL;
3266 sw->data.copy.len = 0;
3267 return NSERROR_NOMEM;
3268 }
3269
3270 if (sw->data.copy.len != 0) {
3271 temp[sw->data.copy.len - 1] = '\n';
3272 }
3273 memcpy(temp + sw->data.copy.len, text, len);
3274 temp[sw->data.copy.len + len] = '\0';
3275 sw->data.copy.len += len + 1;
3276 sw->data.copy.text = temp;
3277 }
3278 }
3279 break;
3280 }
3281
3282 if (changed) {
3283 if (sw->data.redraw.required == false) {
3284 sw->data.redraw.required = true;
3285 sw->data.redraw.rect->y0 = sw->current_y - height;
3286 }
3287
3288 if (sw->current_y > sw->data.redraw.rect->y1) {
3289 sw->data.redraw.rect->y1 = sw->current_y;
3290 }
3291 }
3292
3293 return NSERROR_OK;
3294 }
3295
3296
3297 /* Exported interface, documented in treeview.h */
treeview_has_selection(treeview * tree)3298 bool treeview_has_selection(treeview *tree)
3299 {
3300 struct treeview_selection_walk_data sw;
3301
3302 sw.purpose = TREEVIEW_WALK_HAS_SELECTION;
3303 sw.data.has_selection = false;
3304
3305 treeview_walk_internal(tree, tree->root,
3306 TREEVIEW_WALK_MODE_DISPLAY, NULL,
3307 treeview_node_selection_walk_cb, &sw);
3308
3309 return sw.data.has_selection;
3310 }
3311
3312
3313 /**
3314 * Get first selected node (in any)
3315 *
3316 * \param tree Treeview object in which to create folder
3317 * \return the first selected treeview node, or NULL
3318 */
treeview_get_first_selected(treeview * tree)3319 static treeview_node * treeview_get_first_selected(treeview *tree)
3320 {
3321 struct treeview_selection_walk_data sw;
3322
3323 sw.purpose = TREEVIEW_WALK_GET_FIRST_SELECTED;
3324 sw.data.first.n = NULL;
3325
3326 treeview_walk_internal(tree, tree->root,
3327 TREEVIEW_WALK_MODE_DISPLAY, NULL,
3328 treeview_node_selection_walk_cb, &sw);
3329
3330 return sw.data.first.n;
3331 }
3332
3333
3334 /* Exported interface, documented in treeview.h */
treeview_get_selection(treeview * tree,void ** node_data)3335 enum treeview_node_type treeview_get_selection(treeview *tree,
3336 void **node_data)
3337 {
3338 treeview_node *n;
3339
3340 assert(tree != NULL);
3341
3342 n = treeview_get_first_selected(tree);
3343
3344 if (n != NULL && n->type & (TREE_NODE_ENTRY | TREE_NODE_FOLDER)) {
3345 *node_data = n->client_data;
3346 return n->type;
3347 }
3348
3349 *node_data = NULL;
3350 return TREE_NODE_NONE;
3351 }
3352
3353
3354 /**
3355 * Clear any selection in a treeview
3356 *
3357 * \param tree Treeview object to clear selection in
3358 * \param rect Redraw rectangle (if redraw required)
3359 * \return true iff redraw required
3360 */
treeview_clear_selection(treeview * tree,struct rect * rect)3361 static bool treeview_clear_selection(treeview *tree, struct rect *rect)
3362 {
3363 struct treeview_selection_walk_data sw;
3364
3365 rect->x0 = 0;
3366 rect->y0 = 0;
3367 rect->x1 = REDRAW_MAX;
3368 rect->y1 = 0;
3369
3370 sw.purpose = TREEVIEW_WALK_CLEAR_SELECTION;
3371 sw.data.redraw.required = false;
3372 sw.data.redraw.rect = rect;
3373 sw.current_y = treeview__get_search_height(tree);
3374
3375 treeview_walk_internal(tree, tree->root,
3376 TREEVIEW_WALK_MODE_DISPLAY, NULL,
3377 treeview_node_selection_walk_cb, &sw);
3378
3379 return sw.data.redraw.required;
3380 }
3381
3382
3383 /**
3384 * Select all in a treeview
3385 *
3386 * \param tree Treeview object to select all in
3387 * \param rect Redraw rectangle (if redraw required)
3388 * \return true iff redraw required
3389 */
treeview_select_all(treeview * tree,struct rect * rect)3390 static bool treeview_select_all(treeview *tree, struct rect *rect)
3391 {
3392 struct treeview_selection_walk_data sw;
3393
3394 rect->x0 = 0;
3395 rect->y0 = 0;
3396 rect->x1 = REDRAW_MAX;
3397 rect->y1 = 0;
3398
3399 sw.purpose = TREEVIEW_WALK_SELECT_ALL;
3400 sw.data.redraw.required = false;
3401 sw.data.redraw.rect = rect;
3402 sw.current_y = treeview__get_search_height(tree);
3403
3404 treeview_walk_internal(tree, tree->root,
3405 TREEVIEW_WALK_MODE_DISPLAY, NULL,
3406 treeview_node_selection_walk_cb, &sw);
3407
3408 return sw.data.redraw.required;
3409 }
3410
3411
3412 /**
3413 * Commit a current selection drag, modifying the node's selection state.
3414 *
3415 * \param tree Treeview object to commit drag selection in
3416 */
treeview_commit_selection_drag(treeview * tree)3417 static void treeview_commit_selection_drag(treeview *tree)
3418 {
3419 struct treeview_selection_walk_data sw;
3420
3421 sw.purpose = TREEVIEW_WALK_COMMIT_SELECT_DRAG;
3422 sw.current_y = treeview__get_search_height(tree);
3423
3424 if (tree->drag.start.y > tree->drag.prev.y) {
3425 sw.data.drag.sel_min = tree->drag.prev.y;
3426 sw.data.drag.sel_max = tree->drag.start.y;
3427 } else {
3428 sw.data.drag.sel_min = tree->drag.start.y;
3429 sw.data.drag.sel_max = tree->drag.prev.y;
3430 }
3431
3432 treeview_walk_internal(tree, tree->root,
3433 TREEVIEW_WALK_MODE_DISPLAY, NULL,
3434 treeview_node_selection_walk_cb, &sw);
3435 }
3436
3437
3438 /**
3439 * Yank a selection to the node move list.
3440 *
3441 * \param tree Treeview object to yank selection from
3442 * \param fixed Treeview node that should not be yanked
3443 */
treeview_move_yank_selection(treeview * tree,treeview_node * fixed)3444 static void treeview_move_yank_selection(treeview *tree, treeview_node *fixed)
3445 {
3446 struct treeview_selection_walk_data sw;
3447
3448 sw.purpose = TREEVIEW_WALK_YANK_SELECTION;
3449 sw.data.yank.fixed = fixed;
3450 sw.data.yank.prev = NULL;
3451 sw.tree = tree;
3452
3453 treeview_walk_internal(tree, tree->root,
3454 TREEVIEW_WALK_MODE_DISPLAY, NULL,
3455 treeview_node_selection_walk_cb, &sw);
3456 }
3457
3458
3459 /**
3460 * Copy a selection to the clipboard.
3461 *
3462 * \param tree Treeview object to yank selection from
3463 */
treeview_copy_selection(treeview * tree)3464 static void treeview_copy_selection(treeview *tree)
3465 {
3466 struct treeview_selection_walk_data sw;
3467 nserror err;
3468
3469 sw.purpose = TREEVIEW_WALK_COPY_SELECTION;
3470 sw.data.copy.text = NULL;
3471 sw.data.copy.len = 0;
3472 sw.tree = tree;
3473
3474 err = treeview_walk_internal(tree, tree->root,
3475 TREEVIEW_WALK_MODE_DISPLAY, NULL,
3476 treeview_node_selection_walk_cb, &sw);
3477 if (err != NSERROR_OK) {
3478 return;
3479 }
3480
3481 if (sw.data.copy.text != NULL) {
3482 guit->clipboard->set(sw.data.copy.text,
3483 sw.data.copy.len - 1, NULL, 0);
3484 free(sw.data.copy.text);
3485 }
3486 }
3487
3488
3489 /**
3490 * Delete a selection.
3491 *
3492 * \param tree Treeview object to delete selected nodes from
3493 * \param rect Updated to redraw rectangle
3494 * \return true iff redraw required.
3495 */
treeview_delete_selection(treeview * tree,struct rect * rect)3496 static bool treeview_delete_selection(treeview *tree, struct rect *rect)
3497 {
3498 struct treeview_selection_walk_data sw;
3499
3500 assert(tree != NULL);
3501 assert(tree->root != NULL);
3502
3503 rect->x0 = 0;
3504 rect->y0 = 0;
3505 rect->x1 = REDRAW_MAX;
3506 rect->y1 = treeview__get_display_height(tree);
3507
3508 sw.purpose = TREEVIEW_WALK_DELETE_SELECTION;
3509 sw.data.redraw.required = false;
3510 sw.data.redraw.rect = rect;
3511 sw.current_y = treeview__get_search_height(tree);
3512 sw.tree = tree;
3513
3514 treeview_walk_internal(tree, tree->root,
3515 TREEVIEW_WALK_MODE_DISPLAY, NULL,
3516 treeview_node_selection_walk_cb, &sw);
3517
3518 return sw.data.redraw.required;
3519 }
3520
3521
3522 /**
3523 * Propagate selection to visible descendants of selected nodes.
3524 *
3525 * \param tree Treeview object to propagate selection in
3526 * \param rect Redraw rectangle (if redraw required)
3527 * \return true iff redraw required
3528 */
treeview_propagate_selection(treeview * tree,struct rect * rect)3529 static bool treeview_propagate_selection(treeview *tree, struct rect *rect)
3530 {
3531 struct treeview_selection_walk_data sw;
3532
3533 assert(tree != NULL);
3534 assert(tree->root != NULL);
3535
3536 rect->x0 = 0;
3537 rect->y0 = 0;
3538 rect->x1 = REDRAW_MAX;
3539 rect->y1 = 0;
3540
3541 sw.purpose = TREEVIEW_WALK_PROPAGATE_SELECTION;
3542 sw.data.redraw.required = false;
3543 sw.data.redraw.rect = rect;
3544 sw.current_y = treeview__get_search_height(tree);
3545 sw.tree = tree;
3546
3547 treeview_walk_internal(tree, tree->root,
3548 TREEVIEW_WALK_MODE_DISPLAY, NULL,
3549 treeview_node_selection_walk_cb, &sw);
3550
3551 return sw.data.redraw.required;
3552 }
3553
3554
3555 /**
3556 * Move a selection according to the current move drag.
3557 *
3558 * \param tree Treeview object to move selected nodes in
3559 * \param rect Redraw rectangle
3560 * \return NSERROR_OK on success else appropriate error code
3561 */
treeview_move_selection(treeview * tree,struct rect * rect)3562 static nserror treeview_move_selection(treeview *tree, struct rect *rect)
3563 {
3564 treeview_node *node, *next, *parent;
3565 treeview_node *relation;
3566 enum treeview_relationship relationship;
3567 int height;
3568
3569 assert(tree != NULL);
3570 assert(tree->root != NULL);
3571 assert(tree->root->children != NULL);
3572 assert(tree->move.target_pos != TV_TARGET_NONE);
3573
3574 height = tree->root->height;
3575
3576 /* Identify target location */
3577 switch (tree->move.target_pos) {
3578 case TV_TARGET_ABOVE:
3579 if (tree->move.target == NULL) {
3580 /* Target: After last child of root */
3581 relation = tree->root->children;
3582 while (relation->next_sib != NULL) {
3583 relation = relation->next_sib;
3584 }
3585 relationship = TREE_REL_NEXT_SIBLING;
3586
3587 } else if (tree->move.target->prev_sib != NULL) {
3588 /* Target: After previous sibling */
3589 relation = tree->move.target->prev_sib;
3590 relationship = TREE_REL_NEXT_SIBLING;
3591
3592 } else {
3593 /* Target: Target: First child of parent */
3594 assert(tree->move.target->parent != NULL);
3595 relation = tree->move.target->parent;
3596 relationship = TREE_REL_FIRST_CHILD;
3597 }
3598 break;
3599
3600 case TV_TARGET_INSIDE:
3601 assert(tree->move.target != NULL);
3602 relation = tree->move.target;
3603 relationship = TREE_REL_FIRST_CHILD;
3604 break;
3605
3606 case TV_TARGET_BELOW:
3607 assert(tree->move.target != NULL);
3608 relation = tree->move.target;
3609 relationship = TREE_REL_NEXT_SIBLING;
3610 break;
3611
3612 default:
3613 NSLOG(netsurf, INFO, "Bad drop target for move.");
3614 return NSERROR_BAD_PARAMETER;
3615 }
3616
3617 if (relationship == TREE_REL_FIRST_CHILD) {
3618 parent = relation;
3619 } else {
3620 parent = relation->parent;
3621 }
3622
3623 /* Move all selected nodes from treeview to tree->move.root */
3624 treeview_move_yank_selection(tree, relation);
3625
3626 /* Move all nodes on tree->move.root to target location */
3627 for (node = tree->move.root; node != NULL; node = next) {
3628 next = node->next_sib;
3629
3630 if (node == relation) {
3631 continue;
3632 }
3633
3634 if (!(parent->flags & TV_NFLAGS_EXPANDED)) {
3635 if (node->flags & TV_NFLAGS_EXPANDED)
3636 treeview_node_contract_internal(tree, node);
3637 node->flags &= ~TV_NFLAGS_SELECTED;
3638 }
3639
3640 treeview_insert_node(tree, node, relation, relationship);
3641
3642 relation = node;
3643 relationship = TREE_REL_NEXT_SIBLING;
3644 }
3645 tree->move.root = NULL;
3646
3647 /* Tell window, if height has changed */
3648 if (height != tree->root->height)
3649 treeview__cw_update_size(tree, -1, tree->root->height);
3650
3651 /* TODO: Deal with redraw area properly */
3652 rect->x0 = 0;
3653 rect->y0 = 0;
3654 rect->x1 = REDRAW_MAX;
3655 rect->y1 = REDRAW_MAX;
3656
3657 return NSERROR_OK;
3658 }
3659
3660
3661 /**
3662 * context for treeview launch action
3663 */
3664 struct treeview_launch_walk_data {
3665 int selected_depth;
3666 treeview *tree;
3667 };
3668
3669
3670 /**
3671 * Treewalk node walk backward callback for tracking folder selection.
3672 */
3673 static nserror
treeview_node_launch_walk_bwd_cb(treeview_node * n,void * ctx,bool * end)3674 treeview_node_launch_walk_bwd_cb(treeview_node *n, void *ctx, bool *end)
3675 {
3676 struct treeview_launch_walk_data *lw = ctx;
3677
3678 if (n->type == TREE_NODE_FOLDER && n->flags == TV_NFLAGS_SELECTED) {
3679 lw->selected_depth--;
3680 }
3681
3682 return NSERROR_OK;
3683 }
3684
3685
3686 /**
3687 * Treewalk node walk forward callback for launching nodes.
3688 *
3689 * \param n current node
3690 * \param ctx node launch context
3691 * \param skip_children flag to allow children to be skipped
3692 * \param end flag to allow iteration to be finished early.
3693 * \return NSERROR_OK on success else error code.
3694 */
3695 static nserror
treeview_node_launch_walk_fwd_cb(treeview_node * n,void * ctx,bool * skip_children,bool * end)3696 treeview_node_launch_walk_fwd_cb(treeview_node *n,
3697 void *ctx,
3698 bool *skip_children,
3699 bool *end)
3700 {
3701 struct treeview_launch_walk_data *lw = ctx;
3702 nserror ret = NSERROR_OK;
3703
3704 if (n->type == TREE_NODE_FOLDER && n->flags & TV_NFLAGS_SELECTED) {
3705 lw->selected_depth++;
3706
3707 } else if (n->type == TREE_NODE_ENTRY &&
3708 (n->flags & TV_NFLAGS_SELECTED ||
3709 lw->selected_depth > 0)) {
3710 struct treeview_node_msg msg;
3711 msg.msg = TREE_MSG_NODE_LAUNCH;
3712 msg.data.node_launch.mouse = BROWSER_MOUSE_HOVER;
3713 ret = lw->tree->callbacks->entry(msg, n->client_data);
3714 }
3715
3716 return ret;
3717 }
3718
3719
3720 /**
3721 * Launch a selection.
3722 *
3723 * \note Selected entries are launched. Entries that are descendants
3724 * of selected folders are also launched.
3725 *
3726 * \param tree Treeview object to launch selected nodes in
3727 * \return NSERROR_OK on success, appropriate error otherwise
3728 */
treeview_launch_selection(treeview * tree)3729 static nserror treeview_launch_selection(treeview *tree)
3730 {
3731 struct treeview_launch_walk_data lw;
3732
3733 assert(tree != NULL);
3734 assert(tree->root != NULL);
3735
3736 lw.selected_depth = 0;
3737 lw.tree = tree;
3738
3739 return treeview_walk_internal(tree, tree->root,
3740 TREEVIEW_WALK_MODE_LOGICAL_COMPLETE,
3741 treeview_node_launch_walk_bwd_cb,
3742 treeview_node_launch_walk_fwd_cb, &lw);
3743 }
3744
3745
3746 /* Exported interface, documented in treeview.h */
3747 nserror
treeview_get_relation(treeview * tree,treeview_node ** relation,enum treeview_relationship * rel,bool at_y,int y)3748 treeview_get_relation(treeview *tree,
3749 treeview_node **relation,
3750 enum treeview_relationship *rel,
3751 bool at_y,
3752 int y)
3753 {
3754 treeview_node *n;
3755
3756 assert(tree != NULL);
3757
3758 if (at_y) {
3759 n = treeview_y_node(tree, y);
3760
3761 } else {
3762 n = treeview_get_first_selected(tree);
3763 }
3764
3765 if (n != NULL && n->parent != NULL) {
3766 if (n == n->parent->children) {
3767 /* First child */
3768 *relation = n->parent;
3769 *rel = TREE_REL_FIRST_CHILD;
3770 } else {
3771 /* Not first child */
3772 *relation = n->prev_sib;
3773 *rel = TREE_REL_NEXT_SIBLING;
3774 }
3775 } else {
3776 if (tree->root->children == NULL) {
3777 /* First child of root */
3778 *relation = tree->root;
3779 *rel = TREE_REL_FIRST_CHILD;
3780 } else {
3781 /* Last child of root */
3782 n = tree->root->children;
3783 while (n->next_sib != NULL)
3784 n = n->next_sib;
3785 *relation = n;
3786 *rel = TREE_REL_NEXT_SIBLING;
3787 }
3788 }
3789
3790 return NSERROR_OK;
3791 }
3792
3793
3794 /**
3795 * context for treeview keyboard action
3796 */
3797 struct treeview_nav_state {
3798 treeview *tree;
3799 treeview_node *prev;
3800 treeview_node *curr;
3801 treeview_node *next;
3802 treeview_node *last;
3803 int n_selected;
3804 int prev_n_selected;
3805 };
3806
3807
3808 /**
3809 * Treewalk node callback for handling mouse action.
3810 *
3811 * \param node current node
3812 * \param ctx node context
3813 * \param skip_children flag to allow children to be skipped
3814 * \param end flag to allow iteration to be finished early.
3815 * \return NSERROR_OK on success else error code.
3816 */
3817 static nserror
treeview_node_nav_cb(treeview_node * node,void * ctx,bool * skip_children,bool * end)3818 treeview_node_nav_cb(treeview_node *node,
3819 void *ctx,
3820 bool *skip_children,
3821 bool *end)
3822 {
3823 struct treeview_nav_state *ns = ctx;
3824
3825 if (node == ns->tree->root)
3826 return NSERROR_OK;
3827
3828 if (node->flags & TV_NFLAGS_SELECTED) {
3829 ns->n_selected++;
3830 if (ns->curr == NULL) {
3831 ns->curr = node;
3832 }
3833
3834 } else {
3835 if (ns->n_selected == 0) {
3836 ns->prev = node;
3837
3838 } else if (ns->prev_n_selected < ns->n_selected) {
3839 ns->next = node;
3840 ns->prev_n_selected = ns->n_selected;
3841 }
3842 }
3843 ns->last = node;
3844
3845 return NSERROR_OK;
3846 }
3847
3848
3849 /**
3850 * Handle keyboard navigation.
3851 *
3852 * \note Selected entries are launched.
3853 * Entries that are descendants of selected folders are also launched.
3854 *
3855 * \param tree Treeview object to launch selected nodes in
3856 * \param key The ucs4 character codepoint
3857 * \param rect Updated to redraw rectangle
3858 * \return true if treeview needs redraw, false otherwise
3859 */
3860 static bool
treeview_keyboard_navigation(treeview * tree,uint32_t key,struct rect * rect)3861 treeview_keyboard_navigation(treeview *tree, uint32_t key, struct rect *rect)
3862 {
3863 struct treeview_nav_state ns = {
3864 .tree = tree,
3865 .prev = NULL,
3866 .curr = NULL,
3867 .next = NULL,
3868 .last = NULL,
3869 .n_selected = 0,
3870 .prev_n_selected = 0
3871 };
3872 int search_height = treeview__get_search_height(tree);
3873 int h = treeview__get_display_height(tree) + search_height;
3874 bool redraw = false;
3875 struct treeview_node *scroll_to_node = NULL;
3876
3877 /* Fill out the nav. state struct, by examining the current selection
3878 * state */
3879 treeview_walk_internal(tree, tree->root,
3880 TREEVIEW_WALK_MODE_DISPLAY, NULL,
3881 treeview_node_nav_cb, &ns);
3882
3883 scroll_to_node = ns.curr;
3884
3885 if (tree->search.search == false) {
3886 if (ns.next == NULL)
3887 ns.next = tree->root->children;
3888 if (ns.prev == NULL)
3889 ns.prev = ns.last;
3890 }
3891
3892 /* Clear any existing selection */
3893 redraw = treeview_clear_selection(tree, rect);
3894
3895 switch (key) {
3896 case NS_KEY_LEFT:
3897 if (tree->search.search == true) {
3898 break;
3899 }
3900 if (ns.curr != NULL &&
3901 ns.curr->parent != NULL &&
3902 ns.curr->parent->type != TREE_NODE_ROOT) {
3903 /* Step to parent */
3904 ns.curr->parent->flags |= TV_NFLAGS_SELECTED;
3905 scroll_to_node = ns.curr->parent;
3906
3907 } else if (ns.curr != NULL && tree->root->children != NULL) {
3908 /* Select first node in tree */
3909 tree->root->children->flags |= TV_NFLAGS_SELECTED;
3910 scroll_to_node = tree->root->children;
3911 }
3912 break;
3913
3914 case NS_KEY_RIGHT:
3915 if (ns.curr != NULL) {
3916 if (!(ns.curr->flags & TV_NFLAGS_EXPANDED)) {
3917 /* Toggle node to expanded */
3918 treeview_node_expand_internal(tree, ns.curr);
3919 if (ns.curr->children != NULL) {
3920 /* Step to first child */
3921 ns.curr->children->flags |=
3922 TV_NFLAGS_SELECTED;
3923 scroll_to_node = ns.curr->children;
3924 } else {
3925 /* Retain current node selection */
3926 ns.curr->flags |= TV_NFLAGS_SELECTED;
3927 }
3928 } else {
3929 /* Toggle node to contracted */
3930 treeview_node_contract_internal(tree, ns.curr);
3931 /* Retain current node selection */
3932 ns.curr->flags |= TV_NFLAGS_SELECTED;
3933 }
3934
3935 } else if (ns.curr != NULL) {
3936 /* Retain current node selection */
3937 ns.curr->flags |= TV_NFLAGS_SELECTED;
3938 }
3939 break;
3940
3941 case NS_KEY_UP:
3942 if (ns.prev != NULL) {
3943 /* Step to previous node */
3944 ns.prev->flags |= TV_NFLAGS_SELECTED;
3945 scroll_to_node = ns.prev;
3946 }
3947 break;
3948
3949 case NS_KEY_DOWN:
3950 if (ns.next != NULL) {
3951 /* Step to next node */
3952 ns.next->flags |= TV_NFLAGS_SELECTED;
3953 scroll_to_node = ns.next;
3954 }
3955 break;
3956
3957 default:
3958 break;
3959 }
3960
3961 treeview__cw_scroll_to_node(tree, scroll_to_node);
3962
3963 /* TODO: Deal with redraw area properly */
3964 rect->x0 = 0;
3965 rect->y0 = 0;
3966 rect->x1 = REDRAW_MAX;
3967 if (treeview__get_display_height(tree) + search_height > h)
3968 rect->y1 = treeview__get_display_height(tree) + search_height;
3969 else
3970 rect->y1 = h;
3971 redraw = true;
3972
3973 return redraw;
3974 }
3975
3976
3977 /* Exported interface, documented in treeview.h */
treeview_keypress(treeview * tree,uint32_t key)3978 bool treeview_keypress(treeview *tree, uint32_t key)
3979 {
3980 struct rect r; /**< Redraw rectangle */
3981 bool redraw = false;
3982
3983 assert(tree != NULL);
3984
3985 /* Pass to any textarea, if editing in progress */
3986 if (tree->edit.textarea != NULL) {
3987 switch (key) {
3988 case NS_KEY_ESCAPE:
3989 treeview_edit_cancel(tree, true);
3990 return true;
3991 case NS_KEY_NL:
3992 case NS_KEY_CR:
3993 treeview_edit_done(tree);
3994 return true;
3995 default:
3996 return textarea_keypress(tree->edit.textarea, key);
3997 }
3998 } else if (tree->search.active == true) {
3999 switch (key) {
4000 case NS_KEY_ESCAPE:
4001 treeview__search_cancel(tree, false);
4002 return true;
4003 case NS_KEY_NL:
4004 case NS_KEY_CR:
4005 return true;
4006 default:
4007 return textarea_keypress(tree->search.textarea, key);
4008 }
4009 }
4010
4011 /* Keypress to be handled by treeview */
4012 switch (key) {
4013 case NS_KEY_SELECT_ALL:
4014 redraw = treeview_select_all(tree, &r);
4015 break;
4016 case NS_KEY_COPY_SELECTION:
4017 treeview_copy_selection(tree);
4018 break;
4019 case NS_KEY_DELETE_LEFT:
4020 case NS_KEY_DELETE_RIGHT:
4021 redraw = treeview_delete_selection(tree, &r);
4022
4023 if (tree->flags & TREEVIEW_DEL_EMPTY_DIRS) {
4024 int h = tree->root->height;
4025 /* Delete any empty nodes */
4026 treeview_delete_empty_nodes(tree, false);
4027
4028 /* Inform front end of change in dimensions */
4029 if (tree->root->height != h) {
4030 r.y0 = 0;
4031 treeview__cw_update_size(tree, -1,
4032 tree->root->height);
4033 }
4034 }
4035 break;
4036 case NS_KEY_CR:
4037 case NS_KEY_NL:
4038 treeview_launch_selection(tree);
4039 break;
4040 case NS_KEY_ESCAPE:
4041 case NS_KEY_CLEAR_SELECTION:
4042 redraw = treeview_clear_selection(tree, &r);
4043 break;
4044 case NS_KEY_LEFT:
4045 case NS_KEY_RIGHT:
4046 case NS_KEY_UP:
4047 case NS_KEY_DOWN:
4048 redraw = treeview_keyboard_navigation(tree, key, &r);
4049 break;
4050 default:
4051 return false;
4052 }
4053
4054 if (redraw) {
4055 treeview__cw_invalidate_area(tree, &r);
4056 }
4057
4058 return true;
4059 }
4060
4061
4062 /**
4063 * Set the drag&drop drop indicator
4064 *
4065 * \param tree Treeview object to set node indicator in
4066 * \param need_redraw True iff we already have a redraw region
4067 * \param target The treeview node with mouse pointer over it
4068 * \param node_height The height of node
4069 * \param node_y The Y coord of the top of target node
4070 * \param mouse_y Y coord of mouse position
4071 * \param rect Redraw rectangle (if redraw required)
4072 * \return true iff redraw required
4073 */
4074 static bool
treeview_set_move_indicator(treeview * tree,bool need_redraw,treeview_node * target,int node_height,int node_y,int mouse_y,struct rect * rect)4075 treeview_set_move_indicator(treeview *tree,
4076 bool need_redraw,
4077 treeview_node *target,
4078 int node_height,
4079 int node_y,
4080 int mouse_y,
4081 struct rect *rect)
4082 {
4083 treeview_node *orig = target;
4084 enum treeview_target_pos target_pos;
4085 int mouse_pos = mouse_y - node_y;
4086 int x;
4087
4088 assert(tree != NULL);
4089 assert(tree->root != NULL);
4090 assert(tree->root->children != NULL);
4091 assert(target != NULL);
4092
4093 if (target->flags & TV_NFLAGS_SELECTED) {
4094 /* Find top selected ancestor */
4095 while (target->parent &&
4096 target->parent->flags & TV_NFLAGS_SELECTED) {
4097 target = target->parent;
4098 }
4099
4100 /* Find top adjacent selected sibling */
4101 while (target->prev_sib &&
4102 target->prev_sib->flags & TV_NFLAGS_SELECTED) {
4103 target = target->prev_sib;
4104 }
4105 target_pos = TV_TARGET_ABOVE;
4106
4107 } else switch (target->type) {
4108 case TREE_NODE_FOLDER:
4109 if (mouse_pos <= node_height / 4) {
4110 target_pos = TV_TARGET_ABOVE;
4111 } else if (mouse_pos <= (3 * node_height) / 4 ||
4112 target->flags & TV_NFLAGS_EXPANDED) {
4113 target_pos = TV_TARGET_INSIDE;
4114 } else {
4115 target_pos = TV_TARGET_BELOW;
4116 }
4117 break;
4118
4119 case TREE_NODE_ENTRY:
4120 if (mouse_pos <= node_height / 2) {
4121 target_pos = TV_TARGET_ABOVE;
4122 } else {
4123 target_pos = TV_TARGET_BELOW;
4124 }
4125 break;
4126
4127 default:
4128 assert(target->type != TREE_NODE_ROOT);
4129 return false;
4130 }
4131
4132 if (target_pos == tree->move.target_pos &&
4133 target == tree->move.target) {
4134 /* No change */
4135 return need_redraw;
4136 }
4137
4138 if (tree->move.target_pos != TV_TARGET_NONE) {
4139 /* Need to clear old indicator position */
4140 if (need_redraw) {
4141 if (rect->x0 > tree->move.target_area.x0)
4142 rect->x0 = tree->move.target_area.x0;
4143 if (tree->move.target_area.x1 > rect->x1)
4144 rect->x1 = tree->move.target_area.x1;
4145 if (rect->y0 > tree->move.target_area.y0)
4146 rect->y0 = tree->move.target_area.y0;
4147 if (tree->move.target_area.y1 > rect->y1)
4148 rect->y1 = tree->move.target_area.y1;
4149 } else {
4150 *rect = tree->move.target_area;
4151 need_redraw = true;
4152 }
4153 }
4154
4155 /* Offset for ABOVE / BELOW */
4156 if (target_pos == TV_TARGET_ABOVE) {
4157 if (target != orig) {
4158 node_y = treeview_node_y(tree, target);
4159 }
4160 node_y -= (tree_g.line_height + 1) / 2;
4161 } else if (target_pos == TV_TARGET_BELOW) {
4162 node_y += node_height - (tree_g.line_height + 1) / 2;
4163 }
4164
4165 /* Oftsets are all relative to centred (INSIDE) */
4166 node_y += (tree_g.line_height -
4167 treeview_res[TREE_RES_ARROW].height + 1) / 2;
4168
4169 x = target->inset + tree_g.move_offset;
4170
4171 /* Update target details */
4172 tree->move.target = target;
4173 tree->move.target_pos = target_pos;
4174 tree->move.target_area.x0 = x;
4175 tree->move.target_area.y0 = node_y;
4176 tree->move.target_area.x1 = tree_g.icon_size + x;
4177 tree->move.target_area.y1 = tree_g.icon_size + node_y;
4178
4179 if (target_pos != TV_TARGET_NONE) {
4180 /* Need to draw new indicator position */
4181 if (need_redraw) {
4182 if (rect->x0 > tree->move.target_area.x0)
4183 rect->x0 = tree->move.target_area.x0;
4184 if (tree->move.target_area.x1 > rect->x1)
4185 rect->x1 = tree->move.target_area.x1;
4186 if (rect->y0 > tree->move.target_area.y0)
4187 rect->y0 = tree->move.target_area.y0;
4188 if (tree->move.target_area.y1 > rect->y1)
4189 rect->y1 = tree->move.target_area.y1;
4190 } else {
4191 *rect = tree->move.target_area;
4192 need_redraw = true;
4193 }
4194 }
4195
4196 return need_redraw;
4197 }
4198
4199
4200 /**
4201 * Callback for textarea_create, in desktop/treeview.h
4202 *
4203 * \param data treeview context
4204 * \param msg textarea message
4205 */
treeview_textarea_callback(void * data,struct textarea_msg * msg)4206 static void treeview_textarea_callback(void *data, struct textarea_msg *msg)
4207 {
4208 treeview *tree = data;
4209 struct rect *r;
4210
4211 switch (msg->type) {
4212 case TEXTAREA_MSG_DRAG_REPORT:
4213 if (msg->data.drag == TEXTAREA_DRAG_NONE) {
4214 /* Textarea drag finished */
4215 tree->drag.type = TV_DRAG_NONE;
4216 } else {
4217 /* Textarea drag started */
4218 tree->drag.type = TV_DRAG_TEXTAREA;
4219 }
4220 treeview__cw_drag_status(tree, tree->drag.type);
4221 break;
4222
4223 case TEXTAREA_MSG_REDRAW_REQUEST:
4224 r = &msg->data.redraw;
4225 r->x0 += tree->edit.x;
4226 r->y0 += tree->edit.y;
4227 r->x1 += tree->edit.x;
4228 r->y1 += tree->edit.y;
4229
4230 /* Redraw the textarea */
4231 treeview__cw_invalidate_area(tree, r);
4232 break;
4233
4234 default:
4235 break;
4236 }
4237 }
4238
4239
4240 /**
4241 * Start edit of node field, at given y-coord, if editable
4242 *
4243 * \param tree Treeview object to consider editing in
4244 * \param n The treeview node to try editing
4245 * \param node_y The Y coord of the top of n
4246 * \param mouse_x X coord of mouse position
4247 * \param mouse_y Y coord of mouse position
4248 * \param rect Redraw rectangle (if redraw required)
4249 * \return true iff redraw required
4250 */
4251 static bool
treeview_edit_node_at_point(treeview * tree,treeview_node * n,int node_y,int mouse_x,int mouse_y,struct rect * rect)4252 treeview_edit_node_at_point(treeview *tree,
4253 treeview_node *n,
4254 int node_y,
4255 int mouse_x,
4256 int mouse_y,
4257 struct rect *rect)
4258 {
4259 struct treeview_text *field_data = NULL;
4260 struct treeview_field *ef, *field_desc = NULL;
4261 int pos = node_y + tree_g.line_height;
4262 int field_y = node_y;
4263 int field_x;
4264 int width, height;
4265 bool success;
4266
4267 /* If the main field is editable, make field_data point to it */
4268 if (n->type == TREE_NODE_ENTRY)
4269 ef = &(tree->fields[0]);
4270 else
4271 ef = &(tree->fields[tree->n_fields]);
4272 if (ef->flags & TREE_FLAG_ALLOW_EDIT) {
4273 field_data = &n->text;
4274 field_desc = ef;
4275 field_y = node_y;
4276 }
4277
4278 /* Check for editable entry fields */
4279 if (n->type == TREE_NODE_ENTRY && n->height != tree_g.line_height) {
4280 struct treeview_node_entry *e = (struct treeview_node_entry *)n;
4281 int i;
4282
4283 for (i = 0; i < tree->n_fields - 1; i++) {
4284 if (mouse_y <= pos)
4285 continue;
4286
4287 ef = &(tree->fields[i + 1]);
4288 pos += tree_g.line_height;
4289 if (mouse_y <= pos && (ef->flags &
4290 TREE_FLAG_ALLOW_EDIT)) {
4291 field_data = &e->fields[i].value;
4292 field_desc = ef;
4293 field_y = pos - tree_g.line_height;
4294 }
4295 }
4296 }
4297
4298 if (field_data == NULL || field_desc == NULL) {
4299 /* No editable field */
4300 return false;
4301 }
4302
4303 /* Get window width/height */
4304 treeview__cw_get_window_dimensions(tree, &width, &height);
4305
4306 /* Calculate textarea width/height */
4307 field_x = n->inset + tree_g.step_width + tree_g.icon_step - 3;
4308 width -= field_x;
4309 height = tree_g.line_height;
4310
4311 /* Create text area */
4312 tree->edit.textarea = treeview__create_textarea(tree, width, height,
4313 0x000000, 0xffffff, 0x000000, plot_style_odd.text,
4314 treeview_textarea_callback);
4315 if (tree->edit.textarea == NULL) {
4316 return false;
4317 }
4318
4319 success = textarea_set_text(tree->edit.textarea, field_data->data);
4320 if (!success) {
4321 textarea_destroy(tree->edit.textarea);
4322 return false;
4323 }
4324
4325 tree->edit.node = n;
4326 tree->edit.field = field_desc->field;
4327
4328 /* Position the caret */
4329 mouse_x -= field_x;
4330 if (mouse_x < 0)
4331 mouse_x = 0;
4332 else if (mouse_x >= width)
4333 mouse_x = width - 1;
4334
4335 textarea_mouse_action(tree->edit.textarea,
4336 BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_CLICK_1,
4337 mouse_x, tree_g.line_height / 2);
4338
4339 /* Position the textarea */
4340 tree->edit.x = field_x;
4341 tree->edit.y = field_y;
4342 tree->edit.w = width;
4343 tree->edit.h = height;
4344
4345 /* Setup redraw rectangle */
4346 if (rect->x0 > field_x)
4347 rect->x0 = field_x;
4348 if (rect->y0 > field_y)
4349 rect->y0 = field_y;
4350 if (rect->x1 < field_x + width)
4351 rect->x1 = field_x + width;
4352 if (rect->y1 < field_y + height)
4353 rect->y1 = field_y + height;
4354
4355 return true;
4356 }
4357
4358
4359 /* Exported interface, documented in treeview.h */
treeview_edit_selection(treeview * tree)4360 void treeview_edit_selection(treeview *tree)
4361 {
4362 struct rect rect;
4363 treeview_node *n;
4364 bool redraw;
4365 int y;
4366
4367 assert(tree != NULL);
4368 assert(tree->root != NULL);
4369
4370 /* Get first selected node */
4371 n = treeview_get_first_selected(tree);
4372
4373 if (n == NULL)
4374 return;
4375
4376 /* Get node's y-position */
4377 y = treeview_node_y(tree, n);
4378
4379 /* Edit node at y */
4380 redraw = treeview_edit_node_at_point(tree, n, y,
4381 0, y + tree_g.line_height / 2, &rect);
4382
4383 if (redraw == false)
4384 return;
4385
4386 /* Redraw */
4387 rect.x0 = 0;
4388 rect.y0 = y;
4389 rect.x1 = REDRAW_MAX;
4390 rect.y1 = y + tree_g.line_height;
4391 treeview__cw_invalidate_area(tree, &rect);
4392 }
4393
4394
4395 /**
4396 * context for treeview mouse handling
4397 */
4398 struct treeview_mouse_action {
4399 treeview *tree;
4400 browser_mouse_state mouse;
4401 int x;
4402 int y;
4403 int current_y; /* Y coordinate value of top of current node */
4404 int search_height;
4405 };
4406
4407
4408 /**
4409 * Treewalk node callback for handling mouse action.
4410 *
4411 * \param node current node
4412 * \param ctx node context
4413 * \param skip_children flag to allow children to be skipped
4414 * \param end flag to allow iteration to be finished early.
4415 * \return NSERROR_OK on success else error code.
4416 */
4417 static nserror
treeview_node_mouse_action_cb(treeview_node * node,void * ctx,bool * skip_children,bool * end)4418 treeview_node_mouse_action_cb(treeview_node *node,
4419 void *ctx,
4420 bool *skip_children,
4421 bool *end)
4422 {
4423 struct treeview_mouse_action *ma = ctx;
4424 struct rect r;
4425 bool redraw = false;
4426 bool click;
4427 int height;
4428 enum {
4429 TV_NODE_ACTION_NONE = 0,
4430 TV_NODE_ACTION_SELECTION = (1 << 0)
4431 } action = TV_NODE_ACTION_NONE;
4432 enum treeview_node_part part = TV_NODE_PART_NONE;
4433 nserror err;
4434
4435 r.x0 = 0;
4436 r.x1 = REDRAW_MAX;
4437
4438 height = (node->type == TREE_NODE_ENTRY) ? node->height :
4439 tree_g.line_height;
4440
4441 /* Skip line if we've not reached mouse y */
4442 if (ma->y > ma->current_y + height) {
4443 ma->current_y += height;
4444 return NSERROR_OK; /* Don't want to abort tree walk */
4445 }
4446
4447 /* Find where the mouse is */
4448 if (ma->y <= ma->current_y + tree_g.line_height) {
4449 int inset = node->inset;
4450 if (ma->tree->search.search == true) {
4451 inset = tree_g.window_padding;
4452 }
4453 if (ma->x >= inset - 1 &&
4454 ma->x < inset + tree_g.step_width) {
4455 /* Over expansion toggle */
4456 part = TV_NODE_PART_TOGGLE;
4457
4458 } else if (ma->x >= inset + tree_g.step_width &&
4459 ma->x < inset + tree_g.step_width +
4460 tree_g.icon_step + node->text.width) {
4461 /* On node */
4462 part = TV_NODE_PART_ON_NODE;
4463 }
4464 } else if (node->type == TREE_NODE_ENTRY &&
4465 height > tree_g.line_height) {
4466 /* Expanded entries */
4467 int x = node->inset + tree_g.step_width + tree_g.icon_step;
4468 int y = ma->current_y + tree_g.line_height;
4469 int i;
4470 struct treeview_node_entry *entry =
4471 (struct treeview_node_entry *)node;
4472 for (i = 0; i < ma->tree->n_fields - 1; i++) {
4473 struct treeview_field *ef = &(ma->tree->fields[i + 1]);
4474
4475 if (ma->y > y + tree_g.line_height) {
4476 y += tree_g.line_height;
4477 continue;
4478 }
4479
4480 if (ef->flags & TREE_FLAG_SHOW_NAME) {
4481 int max_width = ma->tree->field_width;
4482
4483 if (ma->x >= x + max_width - ef->value.width -
4484 tree_g.step_width &&
4485 ma->x < x + max_width -
4486 tree_g.step_width) {
4487 /* On a field name */
4488 part = TV_NODE_PART_ON_NODE;
4489
4490 } else if (ma->x >= x + max_width &&
4491 ma->x < x + max_width +
4492 entry->fields[i].value.width) {
4493 /* On a field value */
4494 part = TV_NODE_PART_ON_NODE;
4495 }
4496 } else {
4497 if (ma->x >= x && ma->x < x +
4498 entry->fields[i].value.width) {
4499 /* On a field value */
4500 part = TV_NODE_PART_ON_NODE;
4501 }
4502 }
4503
4504 break;
4505 }
4506 }
4507
4508 /* Record what position / part a drag started on */
4509 if (ma->mouse & (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_PRESS_2) &&
4510 ma->tree->drag.type == TV_DRAG_NONE) {
4511 ma->tree->drag.selected = node->flags & TV_NFLAGS_SELECTED;
4512 ma->tree->drag.start_node = node;
4513 ma->tree->drag.part = part;
4514 ma->tree->drag.start.x = ma->x;
4515 ma->tree->drag.start.y = ma->y;
4516 ma->tree->drag.start.node_y = ma->current_y;
4517 ma->tree->drag.start.node_h = height;
4518
4519 ma->tree->drag.prev.x = ma->x;
4520 ma->tree->drag.prev.y = ma->y;
4521 ma->tree->drag.prev.node_y = ma->current_y;
4522 ma->tree->drag.prev.node_h = height;
4523 }
4524
4525 /* Handle drag start */
4526 if (ma->tree->drag.type == TV_DRAG_NONE) {
4527 if (ma->mouse & BROWSER_MOUSE_DRAG_1 &&
4528 ma->tree->drag.selected == false &&
4529 ma->tree->drag.part == TV_NODE_PART_NONE) {
4530 ma->tree->drag.type = TV_DRAG_SELECTION;
4531 treeview__cw_drag_status(ma->tree,
4532 CORE_WINDOW_DRAG_SELECTION);
4533
4534 } else if (ma->tree->search.search == false &&
4535 !(ma->tree->flags & TREEVIEW_NO_MOVES) &&
4536 ma->mouse & BROWSER_MOUSE_DRAG_1 &&
4537 (ma->tree->drag.selected == true ||
4538 ma->tree->drag.part == TV_NODE_PART_ON_NODE)) {
4539 ma->tree->drag.type = TV_DRAG_MOVE;
4540 treeview__cw_drag_status(ma->tree,
4541 CORE_WINDOW_DRAG_MOVE);
4542 redraw |= treeview_propagate_selection(ma->tree, &r);
4543
4544 } else if (ma->mouse & BROWSER_MOUSE_DRAG_2) {
4545 ma->tree->drag.type = TV_DRAG_SELECTION;
4546 treeview__cw_drag_status(ma->tree,
4547 CORE_WINDOW_DRAG_SELECTION);
4548 }
4549
4550 if (ma->tree->drag.start_node != NULL &&
4551 ma->tree->drag.type == TV_DRAG_SELECTION) {
4552 ma->tree->drag.start_node->flags ^= TV_NFLAGS_SELECTED;
4553 }
4554 }
4555
4556 /* Handle active drags */
4557 switch (ma->tree->drag.type) {
4558 case TV_DRAG_SELECTION:
4559 {
4560 int curr_y1 = ma->current_y + height;
4561 int prev_y1 = ma->tree->drag.prev.node_y +
4562 ma->tree->drag.prev.node_h;
4563
4564 r.y0 = (ma->current_y < ma->tree->drag.prev.node_y) ?
4565 ma->current_y : ma->tree->drag.prev.node_y;
4566 r.y1 = (curr_y1 > prev_y1) ? curr_y1 : prev_y1;
4567
4568 redraw = true;
4569
4570 ma->tree->drag.prev.x = ma->x;
4571 ma->tree->drag.prev.y = ma->y;
4572 ma->tree->drag.prev.node_y = ma->current_y;
4573 ma->tree->drag.prev.node_h = height;
4574 }
4575 break;
4576
4577 case TV_DRAG_MOVE:
4578 redraw |= treeview_set_move_indicator(ma->tree, redraw,
4579 node, height,
4580 ma->current_y, ma->y, &r);
4581 break;
4582
4583 default:
4584 break;
4585 }
4586
4587 click = ma->mouse & (BROWSER_MOUSE_CLICK_1 | BROWSER_MOUSE_CLICK_2);
4588
4589 if (((node->type == TREE_NODE_FOLDER) &&
4590 (ma->mouse & BROWSER_MOUSE_DOUBLE_CLICK) && click) ||
4591 (part == TV_NODE_PART_TOGGLE && click)) {
4592 int h = treeview__get_display_height(ma->tree) +
4593 ma->search_height;
4594
4595 /* Clear any existing selection */
4596 redraw |= treeview_clear_selection(ma->tree, &r);
4597
4598 /* Toggle node expansion */
4599 if (node->flags & TV_NFLAGS_EXPANDED) {
4600 err = treeview_node_contract_internal(ma->tree, node);
4601 } else {
4602 err = treeview_node_expand_internal(ma->tree, node);
4603 }
4604 if (err != NSERROR_OK) {
4605 return err;
4606 }
4607
4608 /* Set up redraw */
4609 if (!redraw || r.y0 > ma->current_y)
4610 r.y0 = ma->current_y;
4611 if (h > treeview__get_display_height(ma->tree) +
4612 ma->search_height) {
4613 r.y1 = h;
4614 } else {
4615 r.y1 = treeview__get_display_height(ma->tree) +
4616 ma->search_height;
4617 }
4618 redraw = true;
4619
4620 } else if ((node->type == TREE_NODE_ENTRY) &&
4621 (ma->mouse & BROWSER_MOUSE_DOUBLE_CLICK) && click) {
4622 struct treeview_node_msg msg;
4623 msg.msg = TREE_MSG_NODE_LAUNCH;
4624 msg.data.node_launch.mouse = ma->mouse;
4625
4626 /* Clear any existing selection */
4627 redraw |= treeview_clear_selection(ma->tree, &r);
4628
4629 /* Tell client an entry was launched */
4630 ma->tree->callbacks->entry(msg, node->client_data);
4631
4632 } else if (ma->mouse & BROWSER_MOUSE_PRESS_2 ||
4633 (ma->mouse & BROWSER_MOUSE_PRESS_1 &&
4634 ma->mouse & BROWSER_MOUSE_MOD_2)) {
4635 /* Toggle selection of node */
4636 action |= TV_NODE_ACTION_SELECTION;
4637
4638 } else if (ma->mouse & BROWSER_MOUSE_CLICK_1 &&
4639 ma->mouse &
4640 (BROWSER_MOUSE_MOD_1 | BROWSER_MOUSE_MOD_3) &&
4641 part != TV_NODE_PART_TOGGLE) {
4642
4643 /* Clear any existing selection */
4644 redraw |= treeview_clear_selection(ma->tree, &r);
4645
4646 /* Edit node */
4647 redraw |= treeview_edit_node_at_point(ma->tree, node,
4648 ma->current_y, ma->x,
4649 ma->y, &r);
4650
4651 } else if (ma->mouse & BROWSER_MOUSE_PRESS_1 &&
4652 !(ma->mouse &
4653 (BROWSER_MOUSE_MOD_1 | BROWSER_MOUSE_MOD_3)) &&
4654 !(node->flags & TV_NFLAGS_SELECTED) &&
4655 part != TV_NODE_PART_TOGGLE) {
4656 /* Clear any existing selection */
4657 redraw |= treeview_clear_selection(ma->tree, &r);
4658
4659 /* Select node */
4660 action |= TV_NODE_ACTION_SELECTION;
4661
4662 }
4663
4664 if (action & TV_NODE_ACTION_SELECTION) {
4665 /* Handle change in selection */
4666 node->flags ^= TV_NFLAGS_SELECTED;
4667
4668 /* Redraw */
4669 if (!redraw) {
4670 r.y0 = ma->current_y;
4671 r.y1 = ma->current_y + height;
4672 redraw = true;
4673 } else {
4674 if (r.y0 > ma->current_y) {
4675 r.y0 = ma->current_y;
4676 }
4677 if (r.y1 < ma->current_y + height) {
4678 r.y1 = ma->current_y + height;
4679 }
4680 }
4681 }
4682
4683 if (redraw) {
4684 treeview__cw_invalidate_area(ma->tree, &r);
4685 }
4686
4687 *end = true; /* Reached line with click; stop walking tree */
4688 return NSERROR_OK;
4689 }
4690
4691
4692 /* Exported interface, documented in treeview.h */
4693 void
treeview_mouse_action(treeview * tree,browser_mouse_state mouse,int x,int y)4694 treeview_mouse_action(treeview *tree, browser_mouse_state mouse, int x, int y)
4695 {
4696 struct rect r;
4697 bool redraw = false;
4698 int search_height = treeview__get_search_height(tree);
4699
4700 assert(tree != NULL);
4701 assert(tree->root != NULL);
4702
4703 /* Not interested in whether mouse leaves window. */
4704 if (mouse == BROWSER_MOUSE_LEAVE) {
4705 return;
4706 }
4707
4708 /* Handle mouse drag captured by textarea */
4709 if (tree->drag.type == TV_DRAG_TEXTAREA) {
4710 textarea_mouse_action(tree->edit.textarea, mouse,
4711 x - tree->edit.x, y - tree->edit.y);
4712 return;
4713 } else if (tree->drag.type == TV_DRAG_SEARCH) {
4714 if (tree->search.active == false) {
4715 tree->search.active = true;
4716 if (treeview_clear_selection(tree, &r)) {
4717 treeview__cw_invalidate_area(tree, &r);
4718 }
4719 }
4720 textarea_mouse_action(tree->search.textarea, mouse,
4721 x - tree_g.window_padding - tree_g.icon_size,
4722 y);
4723 return;
4724 } else if (mouse & (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_PRESS_2) &&
4725 y < search_height && tree->search.active == false) {
4726 tree->search.active = true;
4727 if (treeview_clear_selection(tree, &r)) {
4728 treeview__cw_invalidate_area(tree, &r);
4729 }
4730 textarea_mouse_action(tree->search.textarea, mouse,
4731 x - tree_g.window_padding - tree_g.icon_size,
4732 y);
4733 return;
4734 } else if (mouse & (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_PRESS_2) &&
4735 tree->search.active == true) {
4736
4737 tree->search.active = false;
4738 textarea_set_caret(tree->search.textarea, -1);
4739 r.x0 = 0;
4740 r.y0 = 0;
4741 r.x1 = REDRAW_MAX;
4742 r.y1 = tree_g.line_height;
4743 treeview__cw_invalidate_area(tree, &r);
4744 }
4745
4746 /* Handle textarea related mouse action */
4747 if (tree->edit.textarea != NULL) {
4748 int ta_x = x - tree->edit.x;
4749 int ta_y = y - tree->edit.y;
4750
4751 if (ta_x > 0 && ta_x < tree->edit.w &&
4752 ta_y > 0 && ta_y < tree->edit.h) {
4753 /* Inside textarea */
4754 textarea_mouse_action(tree->edit.textarea, mouse,
4755 ta_x, ta_y);
4756 return;
4757
4758 } else if (mouse != BROWSER_MOUSE_HOVER) {
4759 /* Action outside textarea */
4760 treeview_edit_cancel(tree, true);
4761 }
4762 }
4763
4764 /* Handle drag ends */
4765 if (mouse == BROWSER_MOUSE_HOVER) {
4766 switch (tree->drag.type) {
4767 case TV_DRAG_SELECTION:
4768 treeview_commit_selection_drag(tree);
4769 tree->drag.type = TV_DRAG_NONE;
4770 tree->drag.start_node = NULL;
4771
4772 treeview__cw_drag_status(tree, CORE_WINDOW_DRAG_NONE);
4773 return;
4774 case TV_DRAG_MOVE:
4775 treeview_move_selection(tree, &r);
4776 tree->drag.type = TV_DRAG_NONE;
4777 tree->drag.start_node = NULL;
4778
4779 tree->move.target = NULL;
4780 tree->move.target_pos = TV_TARGET_NONE;
4781
4782 treeview__cw_drag_status(tree, CORE_WINDOW_DRAG_NONE);
4783 treeview__cw_invalidate_area(tree, &r);
4784 return;
4785 default:
4786 /* No drag to end */
4787 break;
4788 }
4789 }
4790
4791 if (y > treeview__get_display_height(tree) + search_height) {
4792 /* Below tree */
4793
4794 r.x0 = 0;
4795 r.x1 = REDRAW_MAX;
4796
4797 /* Record what position / part a drag started on */
4798 if (mouse & (BROWSER_MOUSE_PRESS_1 | BROWSER_MOUSE_PRESS_2) &&
4799 tree->drag.type == TV_DRAG_NONE) {
4800 tree->drag.selected = false;
4801 tree->drag.start_node = NULL;
4802 tree->drag.part = TV_NODE_PART_NONE;
4803 tree->drag.start.x = x;
4804 tree->drag.start.y = y;
4805 tree->drag.start.node_y = tree->root->height;
4806 tree->drag.start.node_h = 0;
4807
4808 tree->drag.prev.x = x;
4809 tree->drag.prev.y = y;
4810 tree->drag.prev.node_y = tree->root->height;
4811 tree->drag.prev.node_h = 0;
4812 }
4813
4814 /* Handle drag start */
4815 if (tree->drag.type == TV_DRAG_NONE) {
4816 if (mouse & BROWSER_MOUSE_DRAG_1 &&
4817 tree->drag.selected == false &&
4818 tree->drag.part == TV_NODE_PART_NONE) {
4819 tree->drag.type = TV_DRAG_SELECTION;
4820 treeview__cw_drag_status(tree,
4821 CORE_WINDOW_DRAG_SELECTION);
4822 } else if (mouse & BROWSER_MOUSE_DRAG_2) {
4823 tree->drag.type = TV_DRAG_SELECTION;
4824 treeview__cw_drag_status(tree,
4825 CORE_WINDOW_DRAG_SELECTION);
4826 }
4827
4828 if (tree->drag.start_node != NULL &&
4829 tree->drag.type == TV_DRAG_SELECTION) {
4830 tree->drag.start_node->flags ^=
4831 TV_NFLAGS_SELECTED;
4832 }
4833 }
4834
4835 /* Handle selection drags */
4836 if (tree->drag.type == TV_DRAG_SELECTION) {
4837 int curr_y1 = tree->root->height;
4838 int prev_y1 = tree->drag.prev.node_y +
4839 tree->drag.prev.node_h;
4840
4841 r.y0 = tree->drag.prev.node_y;
4842 r.y1 = (curr_y1 > prev_y1) ? curr_y1 : prev_y1;
4843
4844 redraw = true;
4845
4846 tree->drag.prev.x = x;
4847 tree->drag.prev.y = y;
4848 tree->drag.prev.node_y = curr_y1;
4849 tree->drag.prev.node_h = 0;
4850 }
4851
4852 if (mouse & BROWSER_MOUSE_PRESS_1) {
4853 /* Clear any existing selection */
4854 redraw |= treeview_clear_selection(tree, &r);
4855 }
4856
4857 if (redraw) {
4858 treeview__cw_invalidate_area(tree, &r);
4859 }
4860
4861 } else {
4862 /* On tree */
4863 struct treeview_mouse_action ma = {
4864 .tree = tree,
4865 .mouse = mouse,
4866 .x = x,
4867 .y = y,
4868 .current_y = search_height,
4869 .search_height = search_height,
4870 };
4871
4872 treeview_walk_internal(tree, tree->root,
4873 TREEVIEW_WALK_MODE_DISPLAY, NULL,
4874 treeview_node_mouse_action_cb, &ma);
4875 }
4876 }
4877
4878 /* Exported interface, documented in treeview.h */
treeview_get_height(treeview * tree)4879 int treeview_get_height(treeview *tree)
4880 {
4881 int search_height = treeview__get_search_height(tree);
4882 int height = treeview__get_display_height(tree);
4883
4884 assert(tree != NULL);
4885 assert(tree->root != NULL);
4886
4887 treeview__cw_update_size(tree, -1, height);
4888
4889 return height + search_height;
4890 }
4891
4892 /* Exported interface, documented in treeview.h */
treeview_set_search_string(treeview * tree,const char * string)4893 nserror treeview_set_search_string(
4894 treeview *tree,
4895 const char *string)
4896 {
4897 if (!(tree->flags & TREEVIEW_SEARCHABLE)) {
4898 return NSERROR_BAD_PARAMETER;
4899 }
4900
4901 if (string == NULL || strlen(string) == 0) {
4902 tree->search.active = false;
4903 tree->search.search = false;
4904 return treeview__search(tree, "", 0);
4905 }
4906
4907 tree->search.active = true;
4908 tree->search.search = true;
4909 if (!textarea_set_text(tree->search.textarea, string)) {
4910 return NSERROR_UNKNOWN;
4911 }
4912
4913 treeview__cw_full_redraw(tree);
4914
4915 return NSERROR_OK;
4916 }
4917
4918 /**
4919 * Initialise the plot styles from CSS system colour values.
4920 *
4921 * \param font_pt_size font size to use
4922 * \return NSERROR_OK on success else appropriate error code
4923 */
treeview_init_plot_styles(int font_pt_size)4924 static nserror treeview_init_plot_styles(int font_pt_size)
4925 {
4926 /* Background colour */
4927 plot_style_even.bg.stroke_type = PLOT_OP_TYPE_NONE;
4928 plot_style_even.bg.stroke_width = 0;
4929 plot_style_even.bg.stroke_colour = 0;
4930 plot_style_even.bg.fill_type = PLOT_OP_TYPE_SOLID;
4931 plot_style_even.bg.fill_colour = nscolours[NSCOLOUR_WIN_EVEN_BG];
4932
4933 /* Text colour */
4934 plot_style_even.text.family = PLOT_FONT_FAMILY_SANS_SERIF;
4935 plot_style_even.text.size = font_pt_size;
4936 plot_style_even.text.weight = 400;
4937 plot_style_even.text.flags = FONTF_NONE;
4938 plot_style_even.text.foreground = nscolours[NSCOLOUR_WIN_EVEN_FG];
4939 plot_style_even.text.background = nscolours[NSCOLOUR_WIN_EVEN_BG];
4940
4941 /* Entry field text colour */
4942 plot_style_even.itext = plot_style_even.text;
4943 plot_style_even.itext.foreground = nscolours[NSCOLOUR_WIN_EVEN_FG_FADED];
4944
4945 /* Selected background colour */
4946 plot_style_even.sbg = plot_style_even.bg;
4947 plot_style_even.sbg.fill_colour = nscolours[NSCOLOUR_SEL_BG];
4948
4949 /* Selected text colour */
4950 plot_style_even.stext = plot_style_even.text;
4951 plot_style_even.stext.foreground = nscolours[NSCOLOUR_SEL_FG];
4952 plot_style_even.stext.background = nscolours[NSCOLOUR_SEL_BG];
4953
4954 /* Selected entry field text colour */
4955 plot_style_even.sitext = plot_style_even.stext;
4956 plot_style_even.sitext.foreground = nscolours[NSCOLOUR_SEL_FG_SUBTLE];
4957
4958 /* Odd numbered node styles */
4959 plot_style_odd.bg = plot_style_even.bg;
4960 plot_style_odd.bg.fill_colour = nscolours[NSCOLOUR_WIN_ODD_BG];
4961 plot_style_odd.text = plot_style_even.text;
4962 plot_style_odd.text.background = plot_style_odd.bg.fill_colour;
4963 plot_style_odd.itext = plot_style_odd.text;
4964 plot_style_odd.itext.foreground = nscolours[NSCOLOUR_WIN_EVEN_FG_FADED];
4965
4966 plot_style_odd.sbg = plot_style_even.sbg;
4967 plot_style_odd.stext = plot_style_even.stext;
4968 plot_style_odd.sitext = plot_style_even.sitext;
4969
4970 return NSERROR_OK;
4971 }
4972
4973
4974 /**
4975 * Callback for hlcache retrieving resources.
4976 *
4977 * \param handle content hlcache handle
4978 * \param event The event that occurred on the content
4979 * \param pw treeview resource context
4980 */
4981 static nserror
treeview_res_cb(struct hlcache_handle * handle,const hlcache_event * event,void * pw)4982 treeview_res_cb(struct hlcache_handle *handle,
4983 const hlcache_event *event,
4984 void *pw)
4985 {
4986 struct treeview_resource *r = pw;
4987
4988 switch (event->type) {
4989 case CONTENT_MSG_READY:
4990 case CONTENT_MSG_DONE:
4991 r->ready = true;
4992 r->height = content_get_height(handle);
4993 break;
4994
4995 default:
4996 break;
4997 }
4998
4999 return NSERROR_OK;
5000 }
5001
5002
5003 /**
5004 * Fetch content resources used by treeview.
5005 */
treeview_init_resources(void)5006 static void treeview_init_resources(void)
5007 {
5008 int i;
5009
5010 for (i = 0; i < TREE_RES_LAST; i++) {
5011 nsurl *url;
5012 treeview_res[i].ready = false;
5013 treeview_res[i].height = 0;
5014 if (nsurl_create(treeview_res[i].url, &url) == NSERROR_OK) {
5015 hlcache_handle_retrieve(url, 0, NULL, NULL,
5016 treeview_res_cb,
5017 &(treeview_res[i]), NULL,
5018 CONTENT_IMAGE,
5019 &(treeview_res[i].c));
5020 nsurl_unref(url);
5021 }
5022 }
5023 }
5024
5025
5026 /**
5027 * Create a right-pointing anti-aliased triangle bitmap
5028 *
5029 * \param bg background colour
5030 * \param fg foreground colour
5031 * \param size required bitmap size
5032 */
5033 static struct bitmap *
treeview_generate_triangle_bitmap(colour bg,colour fg,int size)5034 treeview_generate_triangle_bitmap(colour bg, colour fg, int size)
5035 {
5036 struct bitmap *b = NULL;
5037 int x, y;
5038 unsigned char *rpos;
5039 unsigned char *pos;
5040 size_t stride;
5041
5042 /* Set up required colour graduations. Ignores screen gamma. */
5043 colour colour0 = bg;
5044 colour colour1 = mix_colour(bg, fg, 255 * 3 / 4);
5045 colour colour2 = blend_colour(bg, fg);
5046 colour colour3 = mix_colour(bg, fg, 255 * 1 / 4);
5047 colour colour4 = fg;
5048
5049 /* Create the bitmap */
5050 b = guit->bitmap->create(size, size, BITMAP_NEW | BITMAP_OPAQUE);
5051 if (b == NULL)
5052 return NULL;
5053
5054 rpos = guit->bitmap->get_buffer(b);
5055 stride = guit->bitmap->get_rowstride(b);
5056
5057 /* Draw the triangle */
5058 for (y = 0; y < size; y++) {
5059 pos = rpos;
5060
5061 if (y < size / 2) {
5062 /* Top half */
5063 for (x = 0; x < y * 2; x++) {
5064 *(pos++) = red_from_colour(colour4);
5065 *(pos++) = green_from_colour(colour4);
5066 *(pos++) = blue_from_colour(colour4);
5067 *(pos++) = 0xff;
5068 }
5069 *(pos++) = red_from_colour(colour3);
5070 *(pos++) = green_from_colour(colour3);
5071 *(pos++) = blue_from_colour(colour3);
5072 *(pos++) = 0xff;
5073 *(pos++) = red_from_colour(colour1);
5074 *(pos++) = green_from_colour(colour1);
5075 *(pos++) = blue_from_colour(colour1);
5076 *(pos++) = 0xff;
5077 for (x = y * 2 + 2; x < size ; x++) {
5078 *(pos++) = red_from_colour(colour0);
5079 *(pos++) = green_from_colour(colour0);
5080 *(pos++) = blue_from_colour(colour0);
5081 *(pos++) = 0xff;
5082 }
5083 } else if ((y == size / 2) && (size & 0x1)) {
5084 /* Middle row */
5085 for (x = 0; x < size - 1; x++) {
5086 *(pos++) = red_from_colour(colour4);
5087 *(pos++) = green_from_colour(colour4);
5088 *(pos++) = blue_from_colour(colour4);
5089 *(pos++) = 0xff;
5090 }
5091 *(pos++) = red_from_colour(colour2);
5092 *(pos++) = green_from_colour(colour2);
5093 *(pos++) = blue_from_colour(colour2);
5094 *(pos++) = 0xff;
5095 } else {
5096 /* Bottom half */
5097 for (x = 0; x < (size - y - 1) * 2; x++) {
5098 *(pos++) = red_from_colour(colour4);
5099 *(pos++) = green_from_colour(colour4);
5100 *(pos++) = blue_from_colour(colour4);
5101 *(pos++) = 0xff;
5102 }
5103 *(pos++) = red_from_colour(colour3);
5104 *(pos++) = green_from_colour(colour3);
5105 *(pos++) = blue_from_colour(colour3);
5106 *(pos++) = 0xff;
5107 *(pos++) = red_from_colour(colour1);
5108 *(pos++) = green_from_colour(colour1);
5109 *(pos++) = blue_from_colour(colour1);
5110 *(pos++) = 0xff;
5111 for (x = (size - y) * 2; x < size ; x++) {
5112 *(pos++) = red_from_colour(colour0);
5113 *(pos++) = green_from_colour(colour0);
5114 *(pos++) = blue_from_colour(colour0);
5115 *(pos++) = 0xff;
5116 }
5117 }
5118
5119 rpos += stride;
5120 }
5121
5122 guit->bitmap->modified(b);
5123
5124 return b;
5125 }
5126
5127
5128 /**
5129 * Create bitmap copy of another bitmap
5130 *
5131 * \param orig bitmap to copy
5132 * \param size required bitmap size
5133 */
5134 static struct bitmap *
treeview_generate_copy_bitmap(struct bitmap * orig,int size)5135 treeview_generate_copy_bitmap(struct bitmap *orig, int size)
5136 {
5137 struct bitmap *b = NULL;
5138 unsigned char *data;
5139 unsigned char *orig_data;
5140 size_t stride;
5141
5142 if (orig == NULL)
5143 return NULL;
5144
5145 assert(size == guit->bitmap->get_width(orig));
5146 assert(size == guit->bitmap->get_height(orig));
5147
5148 /* Create the bitmap */
5149 b = guit->bitmap->create(size, size, BITMAP_NEW | BITMAP_OPAQUE);
5150 if (b == NULL)
5151 return NULL;
5152
5153 stride = guit->bitmap->get_rowstride(b);
5154 assert(stride == guit->bitmap->get_rowstride(orig));
5155
5156 data = guit->bitmap->get_buffer(b);
5157 orig_data = guit->bitmap->get_buffer(orig);
5158
5159 /* Copy the bitmap */
5160 memcpy(data, orig_data, stride * size);
5161
5162 guit->bitmap->modified(b);
5163
5164 /* We've not modified the original image, but we called
5165 * bitmap_get_buffer(), so we need to pair that with a
5166 * bitmap_modified() call to appease certain front ends. */
5167 guit->bitmap->modified(orig);
5168
5169 return b;
5170 }
5171
5172
5173 /**
5174 * Create bitmap from rotation of another bitmap
5175 *
5176 * \param orig bitmap to create rotation of
5177 * \param size required bitmap size
5178 */
5179 static struct bitmap *
treeview_generate_rotate_bitmap(struct bitmap * orig,int size)5180 treeview_generate_rotate_bitmap(struct bitmap *orig, int size)
5181 {
5182 struct bitmap *b = NULL;
5183 int x, y;
5184 unsigned char *rpos;
5185 unsigned char *pos;
5186 unsigned char *orig_data;
5187 unsigned char *orig_pos;
5188 size_t stride;
5189
5190 if (orig == NULL)
5191 return NULL;
5192
5193 assert(size == guit->bitmap->get_width(orig));
5194 assert(size == guit->bitmap->get_height(orig));
5195
5196 /* Create the bitmap */
5197 b = guit->bitmap->create(size, size, BITMAP_NEW | BITMAP_OPAQUE);
5198 if (b == NULL)
5199 return NULL;
5200
5201 stride = guit->bitmap->get_rowstride(b);
5202 assert(stride == guit->bitmap->get_rowstride(orig));
5203
5204 rpos = guit->bitmap->get_buffer(b);
5205 orig_data = guit->bitmap->get_buffer(orig);
5206
5207 /* Copy the rotated bitmap */
5208 for (y = 0; y < size; y++) {
5209 pos = rpos;
5210
5211 for (x = 0; x < size; x++) {
5212 orig_pos = orig_data + x * stride + y * 4;
5213 *(pos++) = *(orig_pos++);
5214 *(pos++) = *(orig_pos++);
5215 *(pos++) = *(orig_pos);
5216 *(pos++) = 0xff;
5217
5218 }
5219
5220 rpos += stride;
5221 }
5222
5223 guit->bitmap->modified(b);
5224
5225 /* We've not modified the original image, but we called
5226 * bitmap_get_buffer(), so we need to pair that with a
5227 * bitmap_modified() call to appease certain front ends.
5228 */
5229 guit->bitmap->modified(orig);
5230
5231 return b;
5232 }
5233
5234
5235 /**
5236 * Measures width of characters used to represent treeview furniture.
5237 *
5238 * \return NSERROR_OK on success else error code
5239 */
treeview_init_furniture(void)5240 static nserror treeview_init_furniture(void)
5241 {
5242 int size = tree_g.line_height / 2;
5243
5244 plot_style_odd.furn[TREE_FURN_EXPAND].size = size;
5245 plot_style_odd.furn[TREE_FURN_EXPAND].bmp =
5246 treeview_generate_triangle_bitmap(
5247 plot_style_odd.bg.fill_colour,
5248 plot_style_odd.itext.foreground, size);
5249 plot_style_odd.furn[TREE_FURN_EXPAND].sel =
5250 treeview_generate_triangle_bitmap(
5251 plot_style_odd.sbg.fill_colour,
5252 plot_style_odd.sitext.foreground, size);
5253
5254 plot_style_even.furn[TREE_FURN_EXPAND].size = size;
5255 plot_style_even.furn[TREE_FURN_EXPAND].bmp =
5256 treeview_generate_triangle_bitmap(
5257 plot_style_even.bg.fill_colour,
5258 plot_style_even.itext.foreground, size);
5259 plot_style_even.furn[TREE_FURN_EXPAND].sel =
5260 treeview_generate_copy_bitmap(
5261 plot_style_odd.furn[TREE_FURN_EXPAND].sel, size);
5262
5263 plot_style_odd.furn[TREE_FURN_CONTRACT].size = size;
5264 plot_style_odd.furn[TREE_FURN_CONTRACT].bmp =
5265 treeview_generate_rotate_bitmap(
5266 plot_style_odd.furn[TREE_FURN_EXPAND].bmp, size);
5267 plot_style_odd.furn[TREE_FURN_CONTRACT].sel =
5268 treeview_generate_rotate_bitmap(
5269 plot_style_odd.furn[TREE_FURN_EXPAND].sel, size);
5270
5271 plot_style_even.furn[TREE_FURN_CONTRACT].size = size;
5272 plot_style_even.furn[TREE_FURN_CONTRACT].bmp =
5273 treeview_generate_rotate_bitmap(
5274 plot_style_even.furn[TREE_FURN_EXPAND].bmp, size);
5275 plot_style_even.furn[TREE_FURN_CONTRACT].sel =
5276 treeview_generate_rotate_bitmap(
5277 plot_style_even.furn[TREE_FURN_EXPAND].sel, size);
5278
5279 if (plot_style_odd.furn[TREE_FURN_EXPAND].bmp == NULL ||
5280 plot_style_odd.furn[TREE_FURN_EXPAND].sel == NULL ||
5281 plot_style_even.furn[TREE_FURN_EXPAND].bmp == NULL ||
5282 plot_style_even.furn[TREE_FURN_EXPAND].sel == NULL ||
5283 plot_style_odd.furn[TREE_FURN_CONTRACT].bmp == NULL ||
5284 plot_style_odd.furn[TREE_FURN_CONTRACT].sel == NULL ||
5285 plot_style_even.furn[TREE_FURN_CONTRACT].bmp == NULL ||
5286 plot_style_even.furn[TREE_FURN_CONTRACT].sel == NULL)
5287 return NSERROR_NOMEM;
5288
5289 tree_g.furniture_width = size + tree_g.line_height / 4;
5290
5291 return NSERROR_OK;
5292 }
5293
5294
5295 /* Exported interface, documented in treeview.h */
treeview_init(void)5296 nserror treeview_init(void)
5297 {
5298 long long font_px_size;
5299 long long font_pt_size;
5300 nserror res;
5301
5302 if (tree_g.initialised > 0) {
5303 tree_g.initialised++;
5304 return NSERROR_OK;
5305 }
5306
5307 NSLOG(netsurf, INFO, "Initialising treeview module");
5308
5309 font_pt_size = nsoption_int(treeview_font_size);
5310 if (font_pt_size <= 0) {
5311 font_pt_size = 11 * 10;
5312 }
5313
5314 font_px_size = (font_pt_size * FIXTOINT(nscss_screen_dpi) /
5315 10 + 36) / 72;
5316 tree_g.line_height = (font_px_size * 8 + 3) / 6;
5317
5318 res = treeview_init_plot_styles(font_pt_size * PLOT_STYLE_SCALE / 10);
5319 if (res != NSERROR_OK) {
5320 return res;
5321 }
5322
5323 treeview_init_resources();
5324
5325 res = treeview_init_furniture();
5326 if (res != NSERROR_OK) {
5327 return res;
5328 }
5329
5330 tree_g.step_width = tree_g.furniture_width;
5331 tree_g.window_padding = 6;
5332 tree_g.icon_size = 17;
5333 tree_g.icon_step = 23;
5334 tree_g.move_offset = 18;
5335
5336 tree_g.initialised++;
5337
5338 NSLOG(netsurf, INFO, "Initialised treeview module");
5339
5340 return NSERROR_OK;
5341 }
5342
5343
5344 /* Exported interface, documented in treeview.h */
treeview_fini(void)5345 nserror treeview_fini(void)
5346 {
5347 int i;
5348
5349 if (tree_g.initialised > 1) {
5350 tree_g.initialised--;
5351 return NSERROR_OK;
5352
5353 } else if (tree_g.initialised == 0) {
5354 NSLOG(netsurf, INFO,
5355 "Warning: tried to finalise uninitialised treeview module");
5356 return NSERROR_OK;
5357 }
5358
5359 NSLOG(netsurf, INFO, "Finalising treeview module");
5360
5361 for (i = 0; i < TREE_RES_LAST; i++) {
5362 hlcache_handle_release(treeview_res[i].c);
5363 }
5364
5365 guit->bitmap->destroy(plot_style_odd.furn[TREE_FURN_EXPAND].bmp);
5366 guit->bitmap->destroy(plot_style_odd.furn[TREE_FURN_EXPAND].sel);
5367 guit->bitmap->destroy(plot_style_even.furn[TREE_FURN_EXPAND].bmp);
5368 guit->bitmap->destroy(plot_style_even.furn[TREE_FURN_EXPAND].sel);
5369 guit->bitmap->destroy(plot_style_odd.furn[TREE_FURN_CONTRACT].bmp);
5370 guit->bitmap->destroy(plot_style_odd.furn[TREE_FURN_CONTRACT].sel);
5371 guit->bitmap->destroy(plot_style_even.furn[TREE_FURN_CONTRACT].bmp);
5372 guit->bitmap->destroy(plot_style_even.furn[TREE_FURN_CONTRACT].sel);
5373
5374 tree_g.initialised--;
5375
5376 NSLOG(netsurf, INFO, "Finalised treeview module");
5377
5378 return NSERROR_OK;
5379 }
5380