001    /*
002     * Copyright (C) 2012 eXo Platform SAS.
003     *
004     * This is free software; you can redistribute it and/or modify it
005     * under the terms of the GNU Lesser General Public License as
006     * published by the Free Software Foundation; either version 2.1 of
007     * the License, or (at your option) any later version.
008     *
009     * This software is distributed in the hope that it will be useful,
010     * but WITHOUT ANY WARRANTY; without even the implied warranty of
011     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012     * Lesser General Public License for more details.
013     *
014     * You should have received a copy of the GNU Lesser General Public
015     * License along with this software; if not, write to the Free
016     * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
017     * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
018     */
019    
020    package org.crsh.text.ui;
021    
022    /**
023     * The columns layout computes the width of the columns in a table.
024     */
025    public abstract class ColumnLayout {
026    
027      public static ColumnLayout rightToLeft() {
028        return RTL;
029      }
030    
031      public static ColumnLayout weighted(int... weights) throws NullPointerException, IllegalArgumentException {
032        return new Weighted(weights);
033      }
034    
035      abstract int[] compute(Border border, int width, int[] widths, int[] minWidths);
036    
037      public static class Weighted extends ColumnLayout {
038    
039        /** The weights. */
040        private final int[] weights;
041    
042        /**
043         * Create a new weighted layout.
044         *
045         * @param weights the weights
046         * @throws NullPointerException if the weights argument is null
047         * @throws IllegalArgumentException if any weight is negative
048         */
049        private Weighted(int... weights) throws NullPointerException, IllegalArgumentException {
050          if (weights == null) {
051            throw new NullPointerException("No null weights accepted");
052          }
053          for (int weight : weights) {
054            if (weight < 0) {
055              throw new IllegalArgumentException("No negative weight accepted");
056            }
057          }
058          this.weights = weights.clone();
059        }
060    
061        public int[] getWeights() {
062          return weights.clone();
063        }
064    
065        @Override
066        int[] compute(Border border, int width, int[] widths, int[] minWidths) {
067    
068          //
069          int len = Math.min(widths.length, weights.length);
070    
071          //
072          for (int i = len;i > 0;i--) {
073    
074            //
075            int totalWeight = 0;
076            int width2 = width;
077            for (int j = 0;j < i;j++) {
078              totalWeight += weights[j];
079              if (border != null) {
080                if (j == 0) {
081                  width2 -= 2;
082                } else {
083                  width2 -= 1;
084                }
085              }
086            }
087    
088            //
089    
090            // Compute the width of each column
091            int[] ret = new int[widths.length];
092            for (int j = 0;j < i;j++) {
093              int w = width2 * weights[j];
094              if (w < minWidths[j] * totalWeight) {
095                ret = null;
096                break;
097              } else {
098                ret[j] = w;
099              }
100            }
101    
102            //
103            if (ret != null) {
104              // Error based scaling inspired from Brensenham algorithm:
105              // => sum of the weights == width
106              // => minimize error
107              // for instance with "foo","bar" scaled to 11 chars
108              // using floor + division gives "foobar_____"
109              // this methods gives           "foo_bar____"
110              int err = 0;
111              for (int j = 0;j < ret.length;j++) {
112    
113                // Compute base value
114                int value = ret[j] / totalWeight;
115    
116                // Lower value
117                int lower = value * totalWeight;
118                int errLower = err + ret[j] - lower;
119    
120                // Upper value
121                int upper = lower + totalWeight;
122                int errUpper = err + ret[j] - upper;
123    
124                // We choose between lower/upper according to the accumulated error
125                // and we propagate the error
126                if (Math.abs(errLower) < Math.abs(errUpper)) {
127                  ret[j] = value;
128                  err = errLower;
129                } else {
130                  ret[j] = value + 1;
131                  err = errUpper;
132                }
133              }
134              return ret;
135            }
136          }
137    
138          //
139          return null;
140        }
141      }
142    
143      private static final ColumnLayout RTL = new ColumnLayout() {
144    
145        @Override
146        int[] compute(Border border, int viewWidth, int[] colWidths, int[] colMinWidths) {
147    
148          //
149          int[] ret = colWidths.clone();
150    
151          //
152          int total = 0;
153          for (int colWidth : colWidths) {
154            if (border != null) {
155              total += 1;
156            }
157            total += colWidth;
158          }
159          if (border != null) {
160            total += 1;
161          }
162    
163          //
164          int index = colMinWidths.length - 1;
165          while (total > viewWidth && index >= 0) {
166            int delta = total - viewWidth;
167            int bar = colWidths[index] - colMinWidths[index];
168            if (delta <= bar) {
169              int sub = Math.min(bar, delta);
170              total -= sub;
171              ret[index] -= sub;
172            } else {
173              int foo = colWidths[index];
174              if (border != null) {
175                foo++;
176                if (index == 0) {
177                  foo++;
178                }
179              }
180              total -= foo;
181              ret[index] = 0;
182            }
183            index--;
184          }
185    
186          //
187          return total > 0 ? ret : null;
188        }
189      };
190    /*
191    
192      public static final ColumnLayout DISTRIBUTED = new ColumnLayout() {
193        @Override
194        public int[] compute(Border border, int width, int[] widths, int[] minWidths) {
195          int index = 0;
196          while (true) {
197    
198            // Compute now the number of chars
199            boolean done = false;
200            int total = 0;
201            for (int i = 0;i < widths.length;i++) {
202              if (widths[i] >= minWidths[i]) {
203                total += widths[i];
204                if (border != null) {
205                  if (done) {
206                    total++;
207                  }
208                  else {
209                    total += 2;
210                    done = true;
211                  }
212                }
213              }
214            }
215    
216            // It's not valid
217            if (total == 0) {
218              return null;
219            }
220    
221            //
222            int delta = width - total;
223    
224            //
225            if (delta == 0) {
226              break;
227            } else if (delta > 0) {
228              switch (widths[index]) {
229                case 0:
230                  break;
231                default:
232                  widths[index]++;
233                  break;
234              }
235              index = (index + 1) % widths.length;
236            } else {
237    
238              // First try to remove from a column above min size
239              int found = -1;
240              for (int i = widths.length - 1;i >= 0;i--) {
241                int p = (index + i) % widths.length;
242                if (widths[p] > minWidths[p]) {
243                  found = p;
244                  if (--index < 0) {
245                    index += widths.length;
246                  }
247                  break;
248                }
249              }
250    
251              // If we haven't found a victim then we consider removing a column
252              if (found == -1) {
253                for (int i = widths.length - 1;i >= 0;i--) {
254                  if (widths[i] > 0) {
255                    found = i;
256                    break;
257                  }
258                }
259              }
260    
261              // We couldn't find any solution we give up
262              if (found == -1) {
263                break;
264              } else {
265                widths[found]--;
266              }
267            }
268          }
269    
270          //
271          return widths;
272        }
273      };
274    */
275    }