source: mainline/uspace/app/nav/panel.c@ 3e6c51f

Last change on this file since 3e6c51f was 3e6c51f, checked in by Jiri Svoboda <jiri@…>, 4 years ago

Page up and page down

  • Property mode set to 100644
File size: 15.5 KB
Line 
1/*
2 * Copyright (c) 2021 Jiri Svoboda
3 * All rights reserved.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * - Redistributions of source code must retain the above copyright
10 * notice, this list of conditions and the following disclaimer.
11 * - Redistributions in binary form must reproduce the above copyright
12 * notice, this list of conditions and the following disclaimer in the
13 * documentation and/or other materials provided with the distribution.
14 * - The name of the author may not be used to endorse or promote products
15 * derived from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29/** @addtogroup nav
30 * @{
31 */
32/** @file Navigator panel.
33 *
34 * Displays a file listing.
35 */
36
37#include <dirent.h>
38#include <errno.h>
39#include <gfx/render.h>
40#include <gfx/text.h>
41#include <stdlib.h>
42#include <str.h>
43#include <ui/control.h>
44#include <ui/paint.h>
45#include <ui/resource.h>
46#include "panel.h"
47#include "nav.h"
48
49static void panel_ctl_destroy(void *);
50static errno_t panel_ctl_paint(void *);
51static ui_evclaim_t panel_ctl_kbd_event(void *, kbd_event_t *);
52static ui_evclaim_t panel_ctl_pos_event(void *, pos_event_t *);
53
54/** Panel control ops */
55ui_control_ops_t panel_ctl_ops = {
56 .destroy = panel_ctl_destroy,
57 .paint = panel_ctl_paint,
58 .kbd_event = panel_ctl_kbd_event,
59 .pos_event = panel_ctl_pos_event
60};
61
62/** Create panel.
63 *
64 * @param window Containing window
65 * @param rpanel Place to store pointer to new panel
66 * @return EOK on success or an error code
67 */
68errno_t panel_create(ui_window_t *window, panel_t **rpanel)
69{
70 panel_t *panel;
71 errno_t rc;
72
73 panel = calloc(1, sizeof(panel_t));
74 if (panel == NULL)
75 return ENOMEM;
76
77 rc = ui_control_new(&panel_ctl_ops, (void *)panel,
78 &panel->control);
79 if (rc != EOK) {
80 free(panel);
81 return rc;
82 }
83
84 rc = gfx_color_new_ega(0x07, &panel->color);
85 if (rc != EOK)
86 goto error;
87
88 rc = gfx_color_new_ega(0x30, &panel->curs_color);
89 if (rc != EOK)
90 goto error;
91
92 panel->window = window;
93 list_initialize(&panel->entries);
94 panel->entries_cnt = 0;
95 *rpanel = panel;
96 return EOK;
97error:
98 if (panel->color != NULL)
99 gfx_color_delete(panel->color);
100 if (panel->curs_color != NULL)
101 gfx_color_delete(panel->curs_color);
102 ui_control_delete(panel->control);
103 free(panel);
104 return rc;
105}
106
107/** Destroy panel.
108 *
109 * @param panel Panel
110 */
111void panel_destroy(panel_t *panel)
112{
113 gfx_color_delete(panel->color);
114 gfx_color_delete(panel->curs_color);
115 panel_clear_entries(panel);
116 ui_control_delete(panel->control);
117 free(panel);
118}
119
120/** Paint panel entry.
121 *
122 * @param entry Panel entry
123 * @param entry_idx Entry index (within list of entries)
124 * @return EOK on success or an error code
125 */
126errno_t panel_entry_paint(panel_entry_t *entry, size_t entry_idx)
127{
128 panel_t *panel = entry->panel;
129 gfx_context_t *gc = ui_window_get_gc(panel->window);
130 ui_resource_t *res = ui_window_get_res(panel->window);
131 gfx_font_t *font = ui_resource_get_font(res);
132 gfx_text_fmt_t fmt;
133 gfx_coord2_t pos;
134 gfx_rect_t rect;
135 size_t rows;
136 errno_t rc;
137
138 gfx_text_fmt_init(&fmt);
139 rows = panel_page_size(panel);
140
141 /* Do not display entry outside of current page */
142 if (entry_idx < panel->page_idx ||
143 entry_idx >= panel->page_idx + rows)
144 return EOK;
145
146 pos.x = panel->rect.p0.x + 1;
147 pos.y = panel->rect.p0.y + 1 + entry_idx - panel->page_idx;
148
149 if (entry == panel->cursor)
150 fmt.color = panel->curs_color;
151 else
152 fmt.color = panel->color;
153
154 /* Draw entry background */
155 rect.p0 = pos;
156 rect.p1.x = panel->rect.p1.x - 1;
157 rect.p1.y = rect.p0.y + 1;
158
159 rc = gfx_set_color(gc, fmt.color);
160 if (rc != EOK)
161 return rc;
162
163 rc = gfx_fill_rect(gc, &rect);
164 if (rc != EOK)
165 return rc;
166
167 rc = gfx_puttext(font, &pos, &fmt, entry->name);
168 if (rc != EOK)
169 return rc;
170
171 return EOK;
172}
173
174/** Paint panel.
175 *
176 * @param panel Panel
177 */
178errno_t panel_paint(panel_t *panel)
179{
180 gfx_context_t *gc = ui_window_get_gc(panel->window);
181 ui_resource_t *res = ui_window_get_res(panel->window);
182 gfx_text_fmt_t fmt;
183 panel_entry_t *entry;
184 int i, lines;
185 errno_t rc;
186
187 gfx_text_fmt_init(&fmt);
188
189 rc = gfx_set_color(gc, panel->color);
190 if (rc != EOK)
191 return rc;
192
193 rc = gfx_fill_rect(gc, &panel->rect);
194 if (rc != EOK)
195 return rc;
196
197 rc = ui_paint_text_box(res, &panel->rect, ui_box_single,
198 panel->color);
199 if (rc != EOK)
200 return rc;
201
202 lines = panel_page_size(panel);
203 i = 0;
204
205 entry = panel->page;
206 while (entry != NULL && i < lines) {
207 rc = panel_entry_paint(entry, panel->page_idx + i);
208 if (rc != EOK)
209 return rc;
210
211 ++i;
212 entry = panel_next(entry);
213 }
214
215 rc = gfx_update(gc);
216 if (rc != EOK)
217 return rc;
218
219 return EOK;
220}
221
222/** Handle panel keyboard event.
223 *
224 * @param panel Panel
225 * @param event Keyboard event
226 * @return ui_claimed iff event was claimed
227 */
228ui_evclaim_t panel_kbd_event(panel_t *panel, kbd_event_t *event)
229{
230 if (event->type == KEY_PRESS) {
231 if ((event->mods & (KM_CTRL | KM_ALT | KM_SHIFT)) == 0) {
232 switch (event->key) {
233 case KC_UP:
234 panel_cursor_up(panel);
235 break;
236 case KC_DOWN:
237 panel_cursor_down(panel);
238 break;
239 case KC_HOME:
240 panel_cursor_top(panel);
241 break;
242 case KC_END:
243 panel_cursor_bottom(panel);
244 break;
245 case KC_PAGE_UP:
246 panel_page_up(panel);
247 break;
248 case KC_PAGE_DOWN:
249 panel_page_down(panel);
250 break;
251 default:
252 break;
253 }
254 }
255 }
256
257 return ui_unclaimed;
258}
259
260/** Handle panel position event.
261 *
262 * @param panel Panel
263 * @param event Position event
264 * @return ui_claimed iff event was claimed
265 */
266ui_evclaim_t panel_pos_event(panel_t *panel, pos_event_t *event)
267{
268 return ui_unclaimed;
269}
270
271/** Get base control for panel.
272 *
273 * @param panel Panel
274 * @return Base UI control
275 */
276ui_control_t *panel_ctl(panel_t *panel)
277{
278 return panel->control;
279}
280
281/** Set panel rectangle.
282 *
283 * @param panel Panel
284 * @param rect Rectangle
285 */
286void panel_set_rect(panel_t *panel, gfx_rect_t *rect)
287{
288 panel->rect = *rect;
289}
290
291/** Get panel page size.
292 *
293 * @param panel Panel
294 * @return Number of entries that fit in panel at the same time.
295 */
296unsigned panel_page_size(panel_t *panel)
297{
298 return panel->rect.p1.y - panel->rect.p0.y - 2;
299}
300
301/** Destroy panel control.
302 *
303 * @param arg Argument (panel_t *)
304 */
305void panel_ctl_destroy(void *arg)
306{
307 panel_t *panel = (panel_t *) arg;
308
309 panel_destroy(panel);
310}
311
312/** Paint panel control.
313 *
314 * @param arg Argument (panel_t *)
315 * @return EOK on success or an error code
316 */
317errno_t panel_ctl_paint(void *arg)
318{
319 panel_t *panel = (panel_t *) arg;
320
321 return panel_paint(panel);
322}
323
324/** Handle panel control keyboard event.
325 *
326 * @param arg Argument (panel_t *)
327 * @param kbd_event Keyboard event
328 * @return @c ui_claimed iff the event is claimed
329 */
330ui_evclaim_t panel_ctl_kbd_event(void *arg, kbd_event_t *event)
331{
332 panel_t *panel = (panel_t *) arg;
333
334 return panel_kbd_event(panel, event);
335}
336
337/** Handle panel control position event.
338 *
339 * @param arg Argument (panel_t *)
340 * @param pos_event Position event
341 * @return @c ui_claimed iff the event is claimed
342 */
343ui_evclaim_t panel_ctl_pos_event(void *arg, pos_event_t *event)
344{
345 panel_t *panel = (panel_t *) arg;
346
347 return panel_pos_event(panel, event);
348}
349
350/** Append new panel entry.
351 *
352 * @param panel Panel
353 * @param name File name
354 * @param size File size;
355 * @return EOK on success or an error code
356 */
357errno_t panel_entry_append(panel_t *panel, const char *name, uint64_t size)
358{
359 panel_entry_t *entry;
360
361 entry = calloc(1, sizeof(panel_entry_t));
362 if (entry == NULL)
363 return ENOMEM;
364
365 entry->panel = panel;
366 entry->name = str_dup(name);
367 if (entry->name == NULL) {
368 free(entry);
369 return ENOMEM;
370 }
371
372 entry->size = size;
373 link_initialize(&entry->lentries);
374 list_append(&entry->lentries, &panel->entries);
375 ++panel->entries_cnt;
376 return EOK;
377}
378
379/** Delete panel entry.
380 *
381 * @param entry Panel entry
382 */
383void panel_entry_delete(panel_entry_t *entry)
384{
385 if (entry->panel->cursor == entry)
386 entry->panel->cursor = NULL;
387 if (entry->panel->page == entry)
388 entry->panel->page = NULL;
389
390 list_remove(&entry->lentries);
391 --entry->panel->entries_cnt;
392 free(entry->name);
393 free(entry);
394}
395
396/** Clear panel entry list.
397 *
398 * @param panel Panel
399 */
400void panel_clear_entries(panel_t *panel)
401{
402 panel_entry_t *entry;
403
404 entry = panel_first(panel);
405 while (entry != NULL) {
406 panel_entry_delete(entry);
407 entry = panel_first(panel);
408 }
409}
410
411/** Read directory into panel entry list.
412 *
413 * @param panel Panel
414 * @param dirname Directory path
415 * @return EOK on success or an error code
416 */
417errno_t panel_read_dir(panel_t *panel, const char *dirname)
418{
419 DIR *dir;
420 struct dirent *dirent;
421 errno_t rc;
422
423 dir = opendir(dirname);
424 if (dir == NULL)
425 return errno;
426
427 dirent = readdir(dir);
428 while (dirent != NULL) {
429 rc = panel_entry_append(panel, dirent->d_name, 1);
430 if (rc != EOK)
431 goto error;
432 dirent = readdir(dir);
433 }
434
435 closedir(dir);
436
437 panel->cursor = panel_first(panel);
438 panel->cursor_idx = 0;
439 panel->page = panel_first(panel);
440 panel->page_idx = 0;
441 return EOK;
442error:
443 closedir(dir);
444 return rc;
445}
446
447/** Return first panel entry.
448 *
449 * @panel Panel
450 * @return First panel entry or @c NULL if there are no entries
451 */
452panel_entry_t *panel_first(panel_t *panel)
453{
454 link_t *link;
455
456 link = list_first(&panel->entries);
457 if (link == NULL)
458 return NULL;
459
460 return list_get_instance(link, panel_entry_t, lentries);
461}
462
463/** Return last panel entry.
464 *
465 * @panel Panel
466 * @return Last panel entry or @c NULL if there are no entries
467 */
468panel_entry_t *panel_last(panel_t *panel)
469{
470 link_t *link;
471
472 link = list_last(&panel->entries);
473 if (link == NULL)
474 return NULL;
475
476 return list_get_instance(link, panel_entry_t, lentries);
477}
478
479/** Return next panel entry.
480 *
481 * @param cur Current entry
482 * @return Next entry or @c NULL if @a cur is the last entry
483 */
484panel_entry_t *panel_next(panel_entry_t *cur)
485{
486 link_t *link;
487
488 link = list_next(&cur->lentries, &cur->panel->entries);
489 if (link == NULL)
490 return NULL;
491
492 return list_get_instance(link, panel_entry_t, lentries);
493}
494
495/** Return previous panel entry.
496 *
497 * @param cur Current entry
498 * @return Previous entry or @c NULL if @a cur is the first entry
499 */
500panel_entry_t *panel_prev(panel_entry_t *cur)
501{
502 link_t *link;
503
504 link = list_prev(&cur->lentries, &cur->panel->entries);
505 if (link == NULL)
506 return NULL;
507
508 return list_get_instance(link, panel_entry_t, lentries);
509}
510
511/** Move cursor to a new position, possibly scrolling.
512 *
513 * @param panel Panel
514 * @param entry New entry under cursor
515 * @param entry_idx Index of new entry under cursor
516 */
517void panel_cursor_move(panel_t *panel, panel_entry_t *entry, size_t entry_idx)
518{
519 gfx_context_t *gc = ui_window_get_gc(panel->window);
520 panel_entry_t *old_cursor;
521 size_t old_idx;
522 size_t rows;
523 panel_entry_t *e;
524 size_t i;
525
526 rows = panel_page_size(panel);
527
528 old_cursor = panel->cursor;
529 old_idx = panel->cursor_idx;
530
531 panel->cursor = entry;
532 panel->cursor_idx = entry_idx;
533
534 if (entry_idx >= panel->page_idx &&
535 entry_idx < panel->page_idx + rows) {
536 /*
537 * If cursor is still on the current page, we're not
538 * scrolling. Just unpaint old cursor and paint new
539 * cursor.
540 */
541 panel_entry_paint(old_cursor, old_idx);
542 panel_entry_paint(panel->cursor, panel->cursor_idx);
543
544 (void) gfx_update(gc);
545 } else {
546 /*
547 * Need to scroll and update all rows.
548 */
549
550 /* Scrolling up */
551 if (entry_idx < panel->page_idx) {
552 panel->page = entry;
553 panel->page_idx = entry_idx;
554 }
555
556 /* Scrolling down */
557 if (entry_idx >= panel->page_idx + rows) {
558 if (entry_idx >= rows) {
559 panel->page_idx = entry_idx - rows + 1;
560 /* Find first page entry (go back rows - 1) */
561 e = entry;
562 for (i = 0; i < rows - 1; i++) {
563 e = panel_prev(e);
564 }
565
566 /* Should be valid */
567 assert(e != NULL);
568 panel->page = e;
569 } else {
570 panel->page = panel_first(panel);
571 panel->page_idx = 0;
572 }
573 }
574
575 (void) panel_paint(panel);
576 }
577}
578
579/** Move cursor one entry up.
580 *
581 * @param panel Panel
582 */
583void panel_cursor_up(panel_t *panel)
584{
585 panel_entry_t *prev;
586 size_t prev_idx;
587
588 prev = panel_prev(panel->cursor);
589 prev_idx = panel->cursor_idx - 1;
590 if (prev != NULL)
591 panel_cursor_move(panel, prev, prev_idx);
592}
593
594/** Move cursor one entry down.
595 *
596 * @param panel Panel
597 */
598void panel_cursor_down(panel_t *panel)
599{
600 panel_entry_t *next;
601 size_t next_idx;
602
603 next = panel_next(panel->cursor);
604 next_idx = panel->cursor_idx + 1;
605 if (next != NULL)
606 panel_cursor_move(panel, next, next_idx);
607}
608
609/** Move cursor to top.
610 *
611 * @param panel Panel
612 */
613void panel_cursor_top(panel_t *panel)
614{
615 panel_cursor_move(panel, panel_first(panel), 0);
616}
617
618/** Move cursor to bottom.
619 *
620 * @param panel Panel
621 */
622void panel_cursor_bottom(panel_t *panel)
623{
624 panel_cursor_move(panel, panel_last(panel), panel->entries_cnt - 1);
625}
626
627/** Move one page up.
628 *
629 * @param panel Panel
630 */
631void panel_page_up(panel_t *panel)
632{
633 gfx_context_t *gc = ui_window_get_gc(panel->window);
634 panel_entry_t *old_page;
635 panel_entry_t *old_cursor;
636 size_t old_idx;
637 size_t rows;
638 panel_entry_t *entry;
639 size_t i;
640
641 rows = panel_page_size(panel);
642
643 old_page = panel->page;
644 old_cursor = panel->cursor;
645 old_idx = panel->cursor_idx;
646
647 /* Move page by rows entries up (if possible) */
648 for (i = 0; i < rows; i++) {
649 entry = panel_prev(panel->page);
650 if (entry != NULL) {
651 panel->page = entry;
652 --panel->page_idx;
653 }
654 }
655
656 /* Move cursor by rows entries up (if possible) */
657
658 for (i = 0; i < rows; i++) {
659 entry = panel_prev(panel->cursor);
660 if (entry != NULL) {
661 panel->cursor = entry;
662 --panel->cursor_idx;
663 }
664 }
665
666 if (panel->page != old_page) {
667 /* We have scrolled. Need to repaint all entries */
668 (void) panel_paint(panel);
669 } else if (panel->cursor != old_cursor) {
670 /* No scrolling, but cursor has moved */
671 panel_entry_paint(old_cursor, old_idx);
672 panel_entry_paint(panel->cursor, panel->cursor_idx);
673
674 (void) gfx_update(gc);
675 }
676}
677
678/** Move one page down.
679 *
680 * @param panel Panel
681 */
682void panel_page_down(panel_t *panel)
683{
684 gfx_context_t *gc = ui_window_get_gc(panel->window);
685 panel_entry_t *old_page;
686 panel_entry_t *old_cursor;
687 size_t old_idx;
688 size_t max_idx;
689 size_t rows;
690 panel_entry_t *entry;
691 size_t i;
692
693 rows = panel_page_size(panel);
694
695 old_page = panel->page;
696 old_cursor = panel->cursor;
697 old_idx = panel->cursor_idx;
698 max_idx = panel->entries_cnt - rows;
699
700 /* Move page by rows entries down (if possible) */
701 for (i = 0; i < rows; i++) {
702 entry = panel_next(panel->page);
703 /* Do not scroll that results in a short page */
704 if (entry != NULL && panel->page_idx < max_idx) {
705 panel->page = entry;
706 ++panel->page_idx;
707 }
708 }
709
710 /* Move cursor by rows entries down (if possible) */
711
712 for (i = 0; i < rows; i++) {
713 entry = panel_next(panel->cursor);
714 if (entry != NULL) {
715 panel->cursor = entry;
716 ++panel->cursor_idx;
717 }
718 }
719
720 if (panel->page != old_page) {
721 /* We have scrolled. Need to repaint all entries */
722 (void) panel_paint(panel);
723 } else if (panel->cursor != old_cursor) {
724 /* No scrolling, but cursor has moved */
725 panel_entry_paint(old_cursor, old_idx);
726 panel_entry_paint(panel->cursor, panel->cursor_idx);
727
728 (void) gfx_update(gc);
729 }
730}
731
732/** @}
733 */
Note: See TracBrowser for help on using the repository browser.