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