push
This commit is contained in:
630
window.c
Normal file
630
window.c
Normal file
@@ -0,0 +1,630 @@
|
||||
/*
|
||||
* Copyright 2008-2013 Various Authors
|
||||
* Copyright 2004-2005 Timo Hirvonen
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License as
|
||||
* published by the Free Software Foundation; either version 2 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but
|
||||
* WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
* General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program; if not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "window.h"
|
||||
#include "options.h"
|
||||
#include "xmalloc.h"
|
||||
#include "debug.h"
|
||||
#include "utils.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
|
||||
static void sel_changed(struct window *win)
|
||||
{
|
||||
if (win->sel_changed)
|
||||
win->sel_changed();
|
||||
win->changed = 1;
|
||||
}
|
||||
|
||||
static int selectable(struct window *win, struct iter *iter)
|
||||
{
|
||||
if (win->selectable)
|
||||
return win->selectable(iter);
|
||||
return 1;
|
||||
}
|
||||
|
||||
struct window *window_new(int (*get_prev)(struct iter *), int (*get_next)(struct iter *))
|
||||
{
|
||||
struct window *win;
|
||||
|
||||
win = xnew(struct window, 1);
|
||||
win->get_next = get_next;
|
||||
win->get_prev = get_prev;
|
||||
win->selectable = NULL;
|
||||
win->sel_changed = NULL;
|
||||
win->nr_rows = 1;
|
||||
win->changed = 1;
|
||||
iter_init(&win->head);
|
||||
iter_init(&win->top);
|
||||
iter_init(&win->sel);
|
||||
return win;
|
||||
}
|
||||
|
||||
void window_free(struct window *win)
|
||||
{
|
||||
free(win);
|
||||
}
|
||||
|
||||
void window_set_empty(struct window *win)
|
||||
{
|
||||
iter_init(&win->head);
|
||||
iter_init(&win->top);
|
||||
iter_init(&win->sel);
|
||||
sel_changed(win);
|
||||
}
|
||||
|
||||
void window_set_contents(struct window *win, void *head)
|
||||
{
|
||||
struct iter first;
|
||||
|
||||
win->head.data0 = head;
|
||||
win->head.data1 = NULL;
|
||||
win->head.data2 = NULL;
|
||||
first = win->head;
|
||||
win->get_next(&first);
|
||||
win->top = first;
|
||||
win->sel = first;
|
||||
while (!selectable(win, &win->sel))
|
||||
win->get_next(&win->sel);
|
||||
sel_changed(win);
|
||||
}
|
||||
|
||||
void window_set_nr_rows(struct window *win, int nr_rows)
|
||||
{
|
||||
if (nr_rows < 1)
|
||||
return;
|
||||
win->nr_rows = nr_rows;
|
||||
window_changed(win);
|
||||
win->changed = 1;
|
||||
}
|
||||
|
||||
void window_up(struct window *win, int rows)
|
||||
{
|
||||
struct iter iter;
|
||||
int upper_bound = min_i(scroll_offset, win->nr_rows/2);
|
||||
int buffer = 0; /* rows between `old sel` and `old top` */
|
||||
int sel_up = 0; /* selectable rows between `old sel` and `new sel` */
|
||||
int skipped = 0; /* unselectable rows between `old sel` and `new sel` */
|
||||
int actual_offset = 0; /* rows between `new sel` and `new top` */
|
||||
int top_up = 0; /* rows between `old top` and `new top` */
|
||||
|
||||
iter = win->top;
|
||||
while (!iters_equal(&iter, &win->sel)) {
|
||||
win->get_next(&iter);
|
||||
buffer++;
|
||||
}
|
||||
|
||||
iter = win->sel;
|
||||
while (sel_up < rows) {
|
||||
if (!win->get_prev(&iter)) {
|
||||
break;
|
||||
}
|
||||
if (selectable(win, &iter)) {
|
||||
sel_up++;
|
||||
win->sel = iter;
|
||||
} else {
|
||||
skipped++;
|
||||
}
|
||||
}
|
||||
/* if there is no selectable row above the current, we move win->top instead
|
||||
* this is necessary when scroll_offset=0 to make the first album header visible */
|
||||
if (sel_up == 0) {
|
||||
skipped = 0;
|
||||
upper_bound = min_i(buffer+rows, win->nr_rows/2);
|
||||
}
|
||||
|
||||
iter = win->sel;
|
||||
while (actual_offset < upper_bound) {
|
||||
if (!win->get_prev(&iter)) {
|
||||
break;
|
||||
}
|
||||
actual_offset++;
|
||||
}
|
||||
|
||||
top_up = actual_offset + sel_up + skipped - buffer;
|
||||
while (top_up > 0) {
|
||||
win->get_prev(&win->top);
|
||||
top_up--;
|
||||
}
|
||||
|
||||
if (sel_up > 0 || actual_offset > 0)
|
||||
sel_changed(win);
|
||||
}
|
||||
|
||||
void window_down(struct window *win, int rows)
|
||||
{
|
||||
struct iter iter;
|
||||
int upper_bound = min_i(scroll_offset, (win->nr_rows-1)/2);
|
||||
int buffer = 0; /* rows between `old sel` and `old bottom` */
|
||||
int sel_down = 0; /* selectable rows between `old sel` and `new sel` */
|
||||
int skipped = 0; /* unselectable rows between `old sel` and `new sel` */
|
||||
int actual_offset = 0; /* rows between `new sel` and `new bottom` */
|
||||
int top_down = 0; /* rows between `old top` and `new top` */
|
||||
|
||||
buffer = win->nr_rows - 1;
|
||||
iter = win->top;
|
||||
while (!iters_equal(&iter, &win->sel)) {
|
||||
win->get_next(&iter);
|
||||
buffer--;
|
||||
}
|
||||
|
||||
iter = win->sel;
|
||||
while (sel_down < rows) {
|
||||
if (!win->get_next(&iter)) {
|
||||
break;
|
||||
}
|
||||
if (selectable(win, &iter)) {
|
||||
sel_down++;
|
||||
win->sel = iter;
|
||||
} else {
|
||||
skipped++;
|
||||
}
|
||||
}
|
||||
if (sel_down == 0) {
|
||||
skipped = 0;
|
||||
upper_bound = min_i(buffer+rows, (win->nr_rows-1)/2);
|
||||
}
|
||||
|
||||
iter = win->sel;
|
||||
while (actual_offset < upper_bound) {
|
||||
if (!win->get_next(&iter))
|
||||
break;
|
||||
actual_offset++;
|
||||
}
|
||||
|
||||
top_down = actual_offset + sel_down + skipped - buffer;
|
||||
while (top_down > 0) {
|
||||
win->get_next(&win->top);
|
||||
top_down--;
|
||||
}
|
||||
|
||||
if (sel_down > 0 || actual_offset > 0)
|
||||
sel_changed(win);
|
||||
}
|
||||
|
||||
/*
|
||||
* minimize number of empty lines visible
|
||||
* make sure selection is visible
|
||||
*/
|
||||
void window_changed(struct window *win)
|
||||
{
|
||||
struct iter iter;
|
||||
int delta, rows, blank_lines;
|
||||
int upper_bound = min_i(scroll_offset, win->nr_rows/2);
|
||||
|
||||
if (iter_is_null(&win->head)) {
|
||||
BUG_ON(!iter_is_null(&win->top));
|
||||
BUG_ON(!iter_is_null(&win->sel));
|
||||
return;
|
||||
}
|
||||
BUG_ON(iter_is_null(&win->top));
|
||||
BUG_ON(iter_is_null(&win->sel));
|
||||
|
||||
/* make sure top and sel point to real row if possible */
|
||||
if (iter_is_head(&win->top)) {
|
||||
win->get_next(&win->top);
|
||||
win->sel = win->top;
|
||||
sel_changed(win);
|
||||
return;
|
||||
}
|
||||
|
||||
/* make sure the selected row is visible */
|
||||
|
||||
/* get distance between top and sel */
|
||||
delta = 0;
|
||||
iter = win->top;
|
||||
while (!iters_equal(&iter, &win->sel)) {
|
||||
if (!win->get_next(&iter)) {
|
||||
/* sel < top, scroll up until top == sel */
|
||||
while (!iters_equal(&win->top, &win->sel))
|
||||
win->get_prev(&win->top);
|
||||
delta = 0;
|
||||
break;
|
||||
}
|
||||
delta++;
|
||||
}
|
||||
|
||||
/* scroll down if needed to make sel visible at bottom, and a bit
|
||||
* further for the scroll_offset */
|
||||
while (delta >= win->nr_rows - upper_bound) {
|
||||
win->get_next(&win->top);
|
||||
delta--;
|
||||
}
|
||||
|
||||
/* scroll up if sel is less than scroll_offset from top */
|
||||
while (delta < upper_bound) {
|
||||
iter = win->top;
|
||||
if (!win->get_prev(&iter))
|
||||
break;
|
||||
win->top = iter;
|
||||
delta++;
|
||||
}
|
||||
|
||||
/* scroll up to fill in wasted blank space */
|
||||
iter = win->top;
|
||||
rows = 1;
|
||||
while (rows < win->nr_rows) {
|
||||
if (!win->get_next(&iter))
|
||||
break;
|
||||
rows++;
|
||||
}
|
||||
blank_lines = win->nr_rows - rows;
|
||||
while (blank_lines) {
|
||||
iter = win->top;
|
||||
if (!win->get_prev(&iter))
|
||||
break;
|
||||
win->top = iter;
|
||||
blank_lines--;
|
||||
}
|
||||
win->changed = 1;
|
||||
}
|
||||
|
||||
void window_row_vanishes(struct window *win, struct iter *iter)
|
||||
{
|
||||
struct iter new = *iter;
|
||||
if (!win->get_next(&new) && !win->get_prev(&new)) {
|
||||
window_set_empty(win);
|
||||
}
|
||||
|
||||
BUG_ON(iter->data0 != win->head.data0);
|
||||
if (iters_equal(&win->top, iter)) {
|
||||
new = *iter;
|
||||
if (win->get_next(&new)) {
|
||||
win->top = new;
|
||||
} else {
|
||||
new = *iter;
|
||||
win->get_prev(&new);
|
||||
win->top = new;
|
||||
}
|
||||
}
|
||||
if (iters_equal(&win->sel, iter)) {
|
||||
/* calculate minimal distance to next selectable */
|
||||
int down = 0;
|
||||
int up = 0;
|
||||
new = *iter;
|
||||
do {
|
||||
if (!win->get_next(&new)) {
|
||||
down = 0;
|
||||
break;
|
||||
}
|
||||
down++;
|
||||
} while (!selectable(win, &new));
|
||||
new = *iter;
|
||||
do {
|
||||
if (!win->get_prev(&new)) {
|
||||
up = 0;
|
||||
break;
|
||||
}
|
||||
up++;
|
||||
} while (!selectable(win, &new));
|
||||
new = *iter;
|
||||
if (down > 0 && (up == 0 || down <= up)) {
|
||||
do {
|
||||
win->get_next(&new);
|
||||
} while (!selectable(win, &new));
|
||||
} else if (up > 0) {
|
||||
do {
|
||||
win->get_prev(&new);
|
||||
} while (!selectable(win, &new));
|
||||
} else {
|
||||
/* no selectable item left but window not empty */
|
||||
new.data1 = new.data2 = NULL;
|
||||
}
|
||||
win->sel = new;
|
||||
sel_changed(win);
|
||||
}
|
||||
|
||||
win->changed = 1;
|
||||
}
|
||||
|
||||
int window_get_top(struct window *win, struct iter *iter)
|
||||
{
|
||||
*iter = win->top;
|
||||
return !iter_is_empty(iter);
|
||||
}
|
||||
|
||||
int window_get_sel(struct window *win, struct iter *iter)
|
||||
{
|
||||
*iter = win->sel;
|
||||
return !iter_is_empty(iter);
|
||||
}
|
||||
|
||||
int window_get_prev(struct window *win, struct iter *iter)
|
||||
{
|
||||
return win->get_prev(iter);
|
||||
}
|
||||
|
||||
int window_get_next(struct window *win, struct iter *iter)
|
||||
{
|
||||
return win->get_next(iter);
|
||||
}
|
||||
|
||||
void window_set_sel(struct window *win, struct iter *iter)
|
||||
{
|
||||
int sel_nr, top_nr, bottom_nr;
|
||||
int upper_bound;
|
||||
struct iter tmp;
|
||||
|
||||
BUG_ON(iter_is_empty(&win->top));
|
||||
BUG_ON(iter_is_empty(iter));
|
||||
BUG_ON(iter->data0 != win->head.data0);
|
||||
|
||||
if (iters_equal(&win->sel, iter))
|
||||
return;
|
||||
win->sel = *iter;
|
||||
|
||||
tmp = win->head;
|
||||
win->get_next(&tmp);
|
||||
top_nr = 0;
|
||||
while (!iters_equal(&tmp, &win->top)) {
|
||||
win->get_next(&tmp);
|
||||
top_nr++;
|
||||
}
|
||||
|
||||
tmp = win->head;
|
||||
win->get_next(&tmp);
|
||||
sel_nr = 0;
|
||||
while (!iters_equal(&tmp, &win->sel)) {
|
||||
BUG_ON(!win->get_next(&tmp));
|
||||
sel_nr++;
|
||||
}
|
||||
|
||||
upper_bound = win->nr_rows / 2;
|
||||
if (scroll_offset < upper_bound)
|
||||
upper_bound = scroll_offset;
|
||||
|
||||
if (sel_nr < top_nr + upper_bound) { /* scroll up */
|
||||
tmp = win->head;
|
||||
win->get_next(&tmp);
|
||||
if (sel_nr < upper_bound) { /* no space above */
|
||||
win->top = tmp;
|
||||
} else {
|
||||
win->top = win->sel;
|
||||
while (upper_bound > 0) {
|
||||
win->get_prev(&win->top);
|
||||
upper_bound--;
|
||||
}
|
||||
}
|
||||
} else { /* scroll down */
|
||||
upper_bound = (win->nr_rows - 1) / 2;
|
||||
if (scroll_offset < upper_bound)
|
||||
upper_bound = scroll_offset;
|
||||
|
||||
tmp = win->sel;
|
||||
bottom_nr = sel_nr;
|
||||
if (sel_nr >= top_nr + win->nr_rows) { /* selected element not visible */
|
||||
while (sel_nr >= top_nr + win->nr_rows) {
|
||||
win->get_next(&win->top);
|
||||
top_nr++;
|
||||
}
|
||||
} else { /* selected element visible */
|
||||
while (bottom_nr + 1 < top_nr + win->nr_rows) {
|
||||
if (!win->get_next(&tmp)) { /* no space below */
|
||||
bottom_nr = sel_nr + upper_bound;
|
||||
break;
|
||||
}
|
||||
bottom_nr++;
|
||||
}
|
||||
}
|
||||
|
||||
while (bottom_nr < sel_nr + upper_bound) {
|
||||
if (!win->get_next(&tmp))
|
||||
break;
|
||||
bottom_nr++;
|
||||
win->get_next(&win->top);
|
||||
}
|
||||
}
|
||||
sel_changed(win);
|
||||
}
|
||||
|
||||
void window_goto_top(struct window *win)
|
||||
{
|
||||
struct iter old_sel;
|
||||
|
||||
old_sel = win->sel;
|
||||
win->sel = win->head;
|
||||
win->get_next(&win->sel);
|
||||
win->top = win->sel;
|
||||
while (!selectable(win, &win->sel))
|
||||
win->get_next(&win->sel);
|
||||
if (!iters_equal(&old_sel, &win->sel))
|
||||
sel_changed(win);
|
||||
}
|
||||
|
||||
void window_goto_bottom(struct window *win)
|
||||
{
|
||||
struct iter old_sel;
|
||||
int count;
|
||||
|
||||
old_sel = win->sel;
|
||||
win->sel = win->head;
|
||||
win->get_prev(&win->sel);
|
||||
win->top = win->sel;
|
||||
count = win->nr_rows - 1;
|
||||
while (count) {
|
||||
struct iter iter = win->top;
|
||||
|
||||
if (!win->get_prev(&iter))
|
||||
break;
|
||||
win->top = iter;
|
||||
count--;
|
||||
}
|
||||
while (!selectable(win, &win->sel))
|
||||
win->get_prev(&win->sel);
|
||||
if (!iters_equal(&old_sel, &win->sel))
|
||||
sel_changed(win);
|
||||
}
|
||||
|
||||
void window_page_up(struct window *win)
|
||||
{
|
||||
struct iter sel = win->sel;
|
||||
struct iter top = win->top;
|
||||
int up;
|
||||
|
||||
for (up = 0; up < win->nr_rows - 1; up++) {
|
||||
if (!win->get_prev(&sel) || !win->get_prev(&top))
|
||||
break;
|
||||
if (selectable(win, &sel)) {
|
||||
win->sel = sel;
|
||||
win->top = top;
|
||||
}
|
||||
}
|
||||
|
||||
sel_changed(win);
|
||||
}
|
||||
|
||||
void window_half_page_up(struct window *win)
|
||||
{
|
||||
struct iter sel = win->sel;
|
||||
struct iter top = win->top;
|
||||
int up;
|
||||
|
||||
for (up = 0; up < (win->nr_rows - 1) / 2; up++) {
|
||||
if (!win->get_prev(&sel) || !win->get_prev(&top))
|
||||
break;
|
||||
if (selectable(win, &sel)) {
|
||||
win->sel = sel;
|
||||
win->top = top;
|
||||
}
|
||||
}
|
||||
|
||||
sel_changed(win);
|
||||
}
|
||||
|
||||
static struct iter window_bottom(struct window *win)
|
||||
{
|
||||
struct iter bottom = win->top;
|
||||
struct iter iter = win->top;
|
||||
int down;
|
||||
|
||||
for (down = 0; down < win->nr_rows - 1; down++) {
|
||||
if (!win->get_next(&iter))
|
||||
break;
|
||||
bottom = iter;
|
||||
}
|
||||
|
||||
return bottom;
|
||||
}
|
||||
|
||||
void window_page_down(struct window *win)
|
||||
{
|
||||
struct iter sel = win->sel;
|
||||
struct iter bot = window_bottom(win);
|
||||
struct iter top = win->top;
|
||||
int down;
|
||||
|
||||
for (down = 0; down < win->nr_rows - 1; down++) {
|
||||
if (!win->get_next(&sel) || !win->get_next(&bot))
|
||||
break;
|
||||
win->get_next(&top);
|
||||
if (selectable(win, &sel)) {
|
||||
win->sel = sel;
|
||||
win->top = top;
|
||||
}
|
||||
}
|
||||
|
||||
sel_changed(win);
|
||||
}
|
||||
|
||||
void window_half_page_down(struct window *win)
|
||||
{
|
||||
struct iter sel = win->sel;
|
||||
struct iter bot = window_bottom(win);
|
||||
struct iter top = win->top;
|
||||
int down;
|
||||
|
||||
for (down = 0; down < (win-> nr_rows - 1) / 2; down++) {
|
||||
if (!win->get_next(&sel) || !win->get_next(&bot))
|
||||
break;
|
||||
win->get_next(&top);
|
||||
if (selectable(win, &sel)) {
|
||||
win->sel = sel;
|
||||
win->top = top;
|
||||
}
|
||||
}
|
||||
|
||||
sel_changed(win);
|
||||
}
|
||||
|
||||
|
||||
void window_scroll_down(struct window *win)
|
||||
{
|
||||
struct iter bot = window_bottom(win);
|
||||
struct iter top = win->top;
|
||||
if (!win->get_next(&bot)) return;
|
||||
if (!win->get_next(&top)) return;
|
||||
if (iters_equal(&win->top, &win->sel))
|
||||
win->get_next(&win->sel);
|
||||
win->top = top;
|
||||
while (!selectable(win, &win->sel))
|
||||
win->get_next(&win->sel);
|
||||
sel_changed(win);
|
||||
}
|
||||
|
||||
void window_scroll_up(struct window *win)
|
||||
{
|
||||
struct iter top = win->top;
|
||||
if (!win->get_prev(&top)) return;
|
||||
struct iter bot = window_bottom(win);
|
||||
/* keep selected row on screen: */
|
||||
if (iters_equal(&bot, &win->sel))
|
||||
win->get_prev(&win->sel);
|
||||
win->top = top;
|
||||
while (!selectable(win, &win->sel))
|
||||
win->get_prev(&win->sel);
|
||||
sel_changed(win);
|
||||
}
|
||||
|
||||
static void window_goto_pos(struct window *win, int pos)
|
||||
{
|
||||
struct iter old_sel;
|
||||
int i;
|
||||
|
||||
old_sel = win->sel;
|
||||
win->sel = win->top;
|
||||
for (i = 0; i < pos; i++)
|
||||
win->get_next(&win->sel);
|
||||
if (!iters_equal(&old_sel, &win->sel))
|
||||
sel_changed(win);
|
||||
}
|
||||
|
||||
void window_page_top(struct window *win)
|
||||
{
|
||||
window_goto_pos(win, 0);
|
||||
while (!selectable(win, &win->sel))
|
||||
win->get_next(&win->sel);
|
||||
}
|
||||
|
||||
void window_page_bottom(struct window *win)
|
||||
{
|
||||
window_goto_pos(win, win->nr_rows - 1);
|
||||
while (!selectable(win, &win->sel))
|
||||
win->get_prev(&win->sel);
|
||||
}
|
||||
|
||||
void window_page_middle(struct window *win)
|
||||
{
|
||||
window_goto_pos(win, win->nr_rows / 2);
|
||||
while (!selectable(win, &win->sel))
|
||||
win->get_next(&win->sel);
|
||||
}
|
||||
|
||||
int window_get_nr_rows(struct window *win)
|
||||
{
|
||||
return win->nr_rows;
|
||||
}
|
||||
Reference in New Issue
Block a user