1/*
2 * Copyright (c) 2008 Apple Computer, Inc.  All rights reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
6 * The contents of this file constitute Original Code as defined in and
7 * are subject to the Apple Public Source License Version 1.1 (the
8 * "License").  You may not use this file except in compliance with the
9 * License.  Please obtain a copy of the License at
10 * http://www.apple.com/publicsource and read it before using this file.
11 *
12 * This Original Code and all software distributed under the License are
13 * distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, EITHER
14 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
15 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
16 * FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT.  Please see the
17 * License for the specific language governing rights and limitations
18 * under the License.
19 *
20 * @APPLE_LICENSE_HEADER_END@
21 */
22
23#include <stdlib.h>
24#include <assert.h>
25#include <math.h>
26#include "layout.h"
27#include "top.h"
28#include "log.h"
29
30enum { COLUMN_PADDING = 1 };
31
32struct enlarge_data {
33    int add;
34    int maxy;
35    int count;
36};
37
38static bool enlarge_iterator(struct statistic *s, void *ptr) {
39    struct enlarge_data *data = ptr;
40    int size;
41
42    if(s->request_size.width > s->minimum_size.width) {
43        size = s->request_size.width;
44    } else {
45        size = s->minimum_size.width;
46    }
47
48    if(data->count > 0)
49	size += COLUMN_PADDING;
50
51    s->actual_size.width = size;
52
53    s->actual_size.width += data->add;
54    s->actual_size.height = data->maxy;
55
56    data->count += 1;
57
58    /* Return true for more iterations (if possible). */
59    return true;
60}
61
62/* This assumes that toadd is >= 1.0, so the caller must verify that. */
63static void enlarge(struct statistics_controller *c,
64					int maxy, int toadd) {
65    struct enlarge_data data;
66
67    data.add = toadd;
68    data.maxy = maxy;
69    data.count = 0;
70
71    c->iterate(c, enlarge_iterator, &data);
72}
73
74
75struct do_layout_data {
76    int x, y;
77    bool error;
78};
79
80static bool do_layout_iterator(struct statistic *s, void *ptr) {
81    struct do_layout_data *data = ptr;
82    struct statistic_size firstsize = {.width = 1, .height = 1};
83
84    if(s->callbacks.resize_cells(s, &firstsize)) {
85	data->error = true;
86	return false;
87    }
88
89    if(s->callbacks.move_cells(s, data->x, data->y)) {
90	top_log("error: moving cells for %s!\n", s->header);
91	top_log("error info: data->x %d data->y %d\n",
92		data->x, data->y);
93
94	data->error = true;
95	/*stop iterating*/
96	return false;
97     }
98
99    if(s->callbacks.resize_cells(s, &s->actual_size)) {
100	top_log("error: resizing cells for %s!\n", s->header);
101	top_log("error info: data->x %d s->actual_size.width %d "
102		"s->actual_size.height %d\n", data->x, s->actual_size.width,
103		s->actual_size.height);
104
105	data->error = true;
106	/*stop iterating*/
107	return false;
108    }
109
110    data->x += s->actual_size.width;
111
112    /*continue iterating*/
113    return true;
114}
115
116static bool do_layout(struct statistics_controller *c, int y) {
117    struct do_layout_data data;
118
119    data.x = 0;
120    data.y = y;
121    data.error = false;
122
123    c->iterate(c, do_layout_iterator, &data);
124
125    return data.error;
126}
127
128struct get_size_data {
129    struct statistic_size *reqsize;
130    struct statistic_size *minsize;
131    int count;
132};
133
134/* Return true if this iterator wants more data. */
135static bool get_size_iterator(struct statistic *s, void *ptr) {
136    struct get_size_data *data = ptr;
137    int minwidth, reqwidth;
138
139    s->callbacks.get_request_size(s);
140    s->callbacks.get_minimum_size(s);
141
142    minwidth = s->minimum_size.width;
143    reqwidth = s->request_size.width;
144
145    /* Skip padding the first column's left side. */
146    if(data->count > 0) {
147	minwidth += COLUMN_PADDING;
148	reqwidth += COLUMN_PADDING;
149    }
150
151    data->minsize->width += minwidth;
152
153    if(s->minimum_size.height > data->minsize->height)
154	data->minsize->height = s->minimum_size.height;
155
156    if(reqwidth > minwidth) {
157        data->reqsize->width += reqwidth;
158    } else {
159        data->reqsize->width += minwidth;
160    }
161
162    if(s->request_size.height > data->reqsize->height)
163	data->reqsize->height = s->request_size.height;
164
165    data->count += 1;
166
167    return true;
168}
169
170static void get_size(struct statistics_controller *c,
171		     struct statistic_size *reqsize,
172		     struct statistic_size *minsize) {
173    struct get_size_data data;
174
175    reqsize->width = 0;
176    reqsize->height = 0;
177    minsize->width = 0;
178    minsize->height = 0;
179
180    data.reqsize = reqsize;
181    data.minsize = minsize;
182    data.count = 0;
183
184    c->iterate(c, get_size_iterator, &data);
185}
186
187struct minsize_fit_data {
188    int maxy;
189    int count;
190};
191
192static bool minsize_fit_iterator(struct statistic *s, void *ptr) {
193    struct minsize_fit_data *data = ptr;
194
195    s->actual_size.width = s->minimum_size.width;
196
197    /* Don't pad the first column's left side. */
198    if(data->count > 0)
199	s->actual_size.width += COLUMN_PADDING;
200
201    s->actual_size.height = data->maxy;
202
203    data->count += 1;
204
205    /*more iterations*/
206    return true;
207}
208
209static void minsize_fit(struct statistics_controller *c, int maxy) {
210    struct minsize_fit_data data;
211
212    data.maxy = maxy;
213    data.count = 0;
214
215    c->iterate(c, minsize_fit_iterator, &data);
216}
217
218/*
219 * The layout algorithm:
220 * We first attempt to fit as many statistics as possible using the
221 * minimum size of each.  If we are at a point where the width of the
222 * terminal is such that the total statistics is equal to the total
223 * possible statistics, then we attempt to use the request size.
224 * If the request size exceeds the terminal size, then we fall back
225 * to the minimum size.  If the request size fits, then we try to
226 * enlarge proportionately every column, as long as the enlargement
227 * is > 1 for each column.
228 */
229
230/* Return true if an error occurred. */
231/* The caller will probably want to schedule another layout after a failure. */
232bool layout_statistics(struct statistics_controller *c, int maxx, int maxy,
233		       int y) {
234    struct statistic_size reqsize, minsize;
235    int total_stats = 0;
236    int inserts = 0, removes = 0;
237
238    if(maxy <= 0)
239	return false;
240
241    while(1) {
242	get_size(c, &reqsize, &minsize);
243	total_stats = c->get_total_active(c);
244
245	if(minsize.width == maxx) {
246	    minsize_fit(c, maxy);
247	    break;
248	} else if(minsize.width > maxx) {
249	    /* The minsize is greater than the total window width. */
250
251	    if(total_stats > 1) {
252		/* Mark that we removed to prevent an endless loop. */
253		++removes;
254		//werase(c->parent);
255		c->remove_tail(c);
256		//werase(c->parent);
257		continue;
258	    }
259
260	    minsize_fit(c, maxy);
261	    break;
262	} else {
263	    if(0 == removes && total_stats < c->get_total_possible(c)) {
264		++inserts;
265		c->insert_tail(c);
266		continue;
267	    }
268
269	    minsize_fit(c, maxy);
270	    break;
271	}
272    }
273
274    //werase(c->parent);
275
276    total_stats = c->get_total_active(c);
277    if(total_stats >= c->get_total_possible(c)) {
278	/*
279	 * We have all of the possible statistics.
280	 * Now we attempt to enlarge to the request size or beyond.
281	 */
282	get_size(c, &reqsize, &minsize);
283
284	if(reqsize.width == maxx) {
285	    /* The request size fit exactly. */
286	    enlarge(c, maxy, /*add*/ 0);
287	} else if(reqsize.width > maxx) {
288	    /* Fallback to the minsize. */
289	    minsize_fit(c, maxy);
290	} else {
291	    /* reqsize.width < maxx */
292	    /*
293	     * We can probably enlarge some if not all of the stats.
294	     */
295	    int xdelta = maxx - reqsize.width;
296	    int enlargement = xdelta / c->get_total_possible(c);
297
298	    if(enlargement > 0) {
299		enlarge(c, maxy, enlargement);
300	    } else {
301		enlarge(c, maxy, 0);
302	    }
303	}
304    }
305
306    top_log("c->get_total_active(c) is %d\n", c->get_total_active(c));
307
308    if(do_layout(c, y))
309	return true;
310
311    return false;
312}
313