1#############################################################################
2##
3#W  brgrids.g             GAP 4 package `browse'                Thomas Breuer
4##
5#Y  Copyright (C)  2007,  Lehrstuhl D für Mathematik,  RWTH Aachen,  Germany
6##
7##  This file contains some <C>SpecialGrid</C> functions.
8##
9
10#T TB, May 2007:
11#T extended version of `NCurses.Grid',
12#T supports ``indicating continuation'' and attributes for lines;
13#T the optional components `tend', `bend', `lend', `rend' are `true'
14#T if the shown grid is not continued at the top, bottom, left, and right,
15#T respectively (this is also the default), and `false' otherwise;
16#T the argument `attrs', if given, must be an integer
17#T describing the attributes for the lines.
18
19NCurses.GridExt:= function( win, args, attrs )
20  local size, trow, brow, lcol, rcol, rowinds, colinds,
21        tend, bend, lend, rend,
22        tvis, bvis, ht, lvis, rvis, wdth, ld, lmr, i, j;
23
24  size := NCurses.getmaxyx(win);
25  if size = false then return false; fi;
26
27  trow:= args.trow;
28  brow:= args.brow;
29  lcol:= args.lcol;
30  rcol:= args.rcol;
31  rowinds:= args.rowinds;
32  colinds:= args.colinds;
33  tend:= not IsBound( args.tend ) or args.tend = true;
34  bend:= not IsBound( args.bend ) or args.bend = true;
35  lend:= not IsBound( args.lend ) or args.lend = true;
36  rend:= not IsBound( args.rend ) or args.rend = true;
37
38  if not ForAll([trow, brow, lcol, rcol], IsInt) then return false; fi;
39  if not ForAll(rowinds, IsInt) then return false; fi;
40  if not ForAll(colinds, IsInt) then return false; fi;
41  # find viewable rows and cols
42  rowinds := Filtered(rowinds, i-> i >= 0 and i >= trow and
43                                   i <= size[1]-1 and i <= brow);
44  colinds := Filtered(colinds, i-> i >= 0 and i >= lcol and
45                                   i <= size[2]-1 and i <= rcol);
46  if IsEmpty( rowinds ) and IsEmpty( colinds ) then
47    return false;
48  fi;
49  tvis := Maximum(trow, 0);
50  bvis := Minimum(brow, size[1]);
51  ht := bvis - tvis + 1;
52  lvis := Maximum(lcol, 0);
53  rvis := Minimum(rcol, size[2]);
54  wdth := rvis - lvis + 1;
55  # Set attributes for the lines.
56  NCurses.wattrset( win, attrs );
57  # draw vlines
58  ld := NCurses.lineDraw;
59  for i in colinds do
60    NCurses.wmove(win, tvis, i);
61    NCurses.wvline(win, ld.VLINE, ht);
62  od;
63  # draw hlines and handle crossings
64  for i in rowinds do
65    NCurses.wmove(win, i, lvis);
66    NCurses.whline(win, ld.HLINE, wdth);
67    if i = trow and tend then
68      lmr := [ld.ULCORNER, ld.TTEE, ld.URCORNER];
69    elif i = brow and bend then
70      lmr := [ld.LLCORNER, ld.BTEE, ld.LRCORNER];
71    else
72      lmr := [ld.LTEE, ld.PLUS, ld.RTEE];
73    fi;
74    for j in colinds do
75      NCurses.wmove(win, i, j);
76      if j = lcol and lend then
77        NCurses.waddch(win, lmr[1]);
78      elif j = rcol and rend then
79        NCurses.waddch(win, lmr[3]);
80      else
81        NCurses.waddch(win, lmr[2]);
82      fi;
83    od;
84  od;
85  # Reset the attributes.
86  NCurses.wattrset( win, NCurses.attrs.NORMAL );
87  return true;
88end;
89
90
91#############################################################################
92##
93#F  BrowseData.SpecialGridLineDraw( <t>, <data> )
94##
95##  When this special grid is used in a browse table,
96##  nonempty row separators are overwritten with horizontal rows
97##  that consist of the special character <C>NCurses.lineDraw.HLINE</C>,
98##  non-blank characters in column separators are overwritten with vertical
99##  rows that consist of the special character <C>NCurses.lineDraw.VLINE</C>,
100##  and <Q>crossings</Q> of horizontal and vertical lines are handled as in
101##  <Ref Func="NCurses.Grid"/>.
102##  <P/>
103##  In categorized browse tables, each set of table rows shown under
104##  a category row gets a grid of its own,
105##  and the separators below category rows are regarded as the top rows of
106##  these grids.
107##  So this special grid requires nonempty category separators if nonempty
108##  row separators occur.
109##  (Note that below a row separator, first the category rows and their
110##  separators appear, and then the data rows and their separators.
111##  If there is a nonempty row separator above the first data row
112##  then it is overwritten by whitespace.)
113##
114BrowseData.SpecialGridLineDraw:= function( t, data )
115    local win, entry, top, i;
116
117    win:= t.dynamic.window;
118
119    # Clear category separators, since they may exceed the last column.
120    for entry in data.catSeparators do
121      NCurses.wmove( win, entry[1], entry[2] );
122      NCurses.waddstr( win, ListWithIdenticalEntries( entry[3], ' ' ) );
123    od;
124
125    # Replace the row and column separators in all four tables by the lines.
126    for entry in data.gridsInfo do
127      NCurses.GridExt( win, entry, NCurses.attrs.NORMAL );
128    od;
129
130    # Overwrite a row separator above the first data row
131    # if the table has a category for the first data row.
132    top:= t.dynamic.topleft[1];
133    if IsOddInt( top )
134       and t.dynamic.topleft[3] = 1
135       and BrowseData.LengthCell( t, top, "vert" ) <> 0 then
136      for i in [ top+1, top+3 .. Length( t.dynamic.indexRow ) - 1 ] do
137        if BrowseData.LengthCell( t, i, "vert" ) <> 0 then
138          if i in t.dynamic.categories[1] then
139            NCurses.wmove( win, data.topmargin + data.headerLength
140                                + BrowseData.HeightLabelsColTable( t ),
141                           data.leftmargin );
142            NCurses.waddstr( win, ListWithIdenticalEntries(
143                NCurses.getmaxyx( win )[2] - data.leftmargin, ' ' ) );
144          fi;
145          break;
146        fi;
147      od;
148    fi;
149
150    # Print those category separators that do not overwrite grid top lines.
151#T force printing of category separators involving attributes!
152    for entry in data.catSeparators do
153      if ForAll( data.gridsInfo, x -> entry[1] <> x.trow ) then
154        NCurses.wmove( win, entry[1], entry[2] );
155        NCurses.whline( win, NCurses.lineDraw.HLINE, entry[3] );
156      fi;
157    od;
158end;
159
160
161#############################################################################
162##
163#F  BrowseData.SpecialGridLineDrawPlus( <t>, <data> )
164##
165##  This function is used for example in the `Browse' method for matrices.
166##
167##  It draws the crossing of the two lines that separate the row and column
168##  labels from the main table.
169##  Note that the grids drawn by `BrowseData.SpecialGridLineDraw' belong to
170##  one of the four subtables of a browse table, and it is not supported
171##  that lines separate these tables.
172##
173BrowseData.SpecialGridLineDrawPlus:= function( t, data )
174    local win;
175
176    BrowseData.SpecialGridLineDraw( t, data );
177    win:= t.dynamic.window;
178    NCurses.wmove( win, data.gridsInfo[1].rowinds[1],
179                        data.gridsInfo[1].colinds[1] );
180    NCurses.waddch( win, NCurses.lineDraw.PLUS );
181end;
182
183
184#############################################################################
185##
186#F  BrowseData.SpecialGridTreeStyle( <t>, <data> )
187##
188##  *NOTE*:
189##  This function does not yet support category separators
190##  and cells of height larger than 1.
191##  (And there is the general problem that when one expands all categories at
192##  the end of the table, the programmatically hidden rows are taken into
193##  account and then lead to unmotivated empty rows.)
194##  (Scrolling by cells seems to be not appropriate for this application,
195##  scrolling by characters would be better.)
196##
197BrowseData.SpecialGridTreeStyle:= function( t, data )
198    local categories, catpos, win, i, fromrow, maxdepth, previousrow, scr,
199          ymin, ymax, y, prevdatarow, pos, entry, level, currlevel, pos2,
200          kmin, j, k, lastcategorypos, height, l;
201
202    categories:= t.dynamic.categories;
203    catpos:= categories[1];
204    if IsEmpty( catpos ) or 2 < catpos[1] then
205      # `BrowseData.SpecialGridTreeStyle' expects category rows,
206      # starting in the first row.
207      return;
208    fi;
209    win:= t.dynamic.window;
210    i:= t.dynamic.topleft[1];
211    fromrow:= t.dynamic.topleft[3];
212    maxdepth:= Length( t.work.sepCol[1] );
213
214    # The `i'-th entry in `previousrow' is the position of the previous
215    # level `i' category if there is one on the screen, and zero otherwise.
216    previousrow:= 0 * [ 0 .. maxdepth ];
217
218    # Process the current line.
219    scr:= BrowseData.HeightWidthWindow( t );
220    ymin:= data.topmargin + BrowseData.HeightLabelsColTable( t )
221                          + data.headerLength + 1;
222    ymax:= scr[1] - data.bottommargin - data.footerLength;
223    y:= ymin;
224    prevdatarow:= fail;
225    while y <= ymax+1 and IsBound( t.dynamic.indexRow[i] ) do
226      # We consider row `ymax+1' but we do not print there.
227      if i mod 2 = 0 then
228
229        # Deal with categories, omitting the first `fromrow - 1' rows.
230        pos:= PositionSorted( catpos, i );
231#T Why is there no variant with a <from> argument?
232        while pos <= Length( catpos ) and catpos[ pos ] = i do
233          entry:= categories[2][ pos ];
234          if entry.isUnderCollapsedCategory or entry.isRejectedCategory then
235            # This category and all of higher level are hidden.
236            break;
237          fi;
238          level:= entry.level;
239
240          # Deal with the category line.
241          if 1 < fromrow then
242            # The beginning of the cell is above the screen.
243            fromrow:= fromrow - 1;
244          else
245            # (We print no path to level 1 rows.)
246            if 1 < level then
247              if not IsBound( currlevel ) then
248                # The current level is that of the previous category row.
249                pos2:= pos;
250                while 1 < pos2 and catpos[ pos2 ] = i do
251                  pos2:= pos2 - 1;
252                od;
253                currlevel:= categories[2][ pos2 ].level;
254              fi;
255
256              # Print the grid part for this level above the current line.
257              if   currlevel < level then
258                # Fill the level `currlevel' column above the current line.
259                if ymin < y and previousrow[ currlevel ] + 1 < y then
260                  NCurses.wmove( win, y-2, 0 );
261                  NCurses.waddstr( win, ListWithIdenticalEntries(
262                                            2*currlevel-2, ' ' ) );
263                  NCurses.wmove( win, y-2, 2*currlevel-2 );
264                  NCurses.waddch( win, NCurses.lineDraw.LTEE );
265                fi;
266              elif currlevel = level then
267                # Fill the level `currlevel' column above the current line.
268                if previousrow[ currlevel ] = 0 then
269                  kmin:= ymin;
270                  NCurses.wmove( win, kmin-1, 0 );
271                  NCurses.waddstr( win, ListWithIdenticalEntries(
272                                            2*currlevel-4, ' ' ) );
273                  NCurses.wmove( win, kmin - 1, 2*currlevel-4 );
274                  NCurses.waddch( win, NCurses.lineDraw.VLINE );
275                else
276                  kmin:= previousrow[ currlevel ];
277                  NCurses.wmove( win, kmin-1, 0 );
278                  NCurses.waddstr( win, ListWithIdenticalEntries(
279                                            2*currlevel-4, ' ' ) );
280                  NCurses.wmove( win, kmin - 1, 2*currlevel-4 );
281                  NCurses.waddch( win, NCurses.lineDraw.LTEE );
282                fi;
283                NCurses.wmove( win, kmin, 2 * ( currlevel-2 ) );
284                NCurses.wvline( win, NCurses.lineDraw.VLINE, y - kmin - 1 );
285              else
286                # Fill the column for level `level' above the current line.
287                if previousrow[ level ] = 0 then
288                  kmin:= ymin;
289                else
290                  kmin:= previousrow[ level ];
291                fi;
292                NCurses.wmove( win, kmin-1, 0 );
293                NCurses.waddstr( win, ListWithIdenticalEntries(
294                                          2*level-4, ' ' ) );
295                NCurses.wmove( win, kmin - 1, 2*level-4 );
296                if previousrow[ level ] = 0 then
297                  NCurses.waddch( win, NCurses.lineDraw.VLINE );
298                else
299                  NCurses.waddch( win, NCurses.lineDraw.LTEE );
300                fi;
301                NCurses.wmove( win, kmin, 2 * ( level-2 ) );
302                NCurses.wvline( win, NCurses.lineDraw.VLINE, y - kmin - 1 );
303              fi;
304              if y <= ymax then
305                NCurses.wmove( win, y-1, 0 );
306                NCurses.waddstr( win, ListWithIdenticalEntries(
307                                          2*level-4, ' ' ) );
308                NCurses.wmove( win, y-1, 2*level-4 );
309                NCurses.waddch( win, NCurses.lineDraw.LLCORNER );
310              fi;
311            fi;
312            previousrow[ level ]:= y;
313            currlevel:= level;
314            lastcategorypos:= pos;
315
316            y:= y + 1;
317          fi;
318
319          pos:= pos + 1;
320        od;
321      fi;
322
323      # Deal with the data rows.
324#T what about fromrow?
325      height:= BrowseData.HeightRow( t, i );
326      if 0 < height then
327        if i mod 2 = 0 then
328          if not IsBound( currlevel ) then
329            # The first shown row is a data row.
330            # Determine the level of the category row above the first line.
331            pos:= PositionSorted( catpos, i );
332            if IsBound( catpos[ pos ] ) and catpos[ pos ] = i then
333              if BrowseData.HeightCategories( t, i ) < fromrow then
334                # Take the last category row for `i'.
335                while IsBound( catpos[ pos ] ) and catpos[ pos ] = i do
336                  pos:= pos + 1;
337                od;
338              else
339                # Take the last category row for `i' above the first line,
340                # together with its category separator below.
341                while IsBound( catpos[ pos ] )
342                      and BrowseData.HeightCategories( t, i,
343                              categories[2][ pos ].level ) < fromrow do
344                  pos:= pos + 1;
345                od;
346              fi;
347            fi;
348            if 1 < pos then
349              pos:= pos-1;
350            fi;
351            currlevel:= categories[2][ pos ].level;
352            lastcategorypos:= pos;
353          fi;
354
355          # Fill the column for level `currlevel' above the current line.
356          if prevdatarow = y-1 then
357            NCurses.wmove( win, y-2, 0 );
358            NCurses.waddstr( win, ListWithIdenticalEntries(
359                                      2*currlevel-2, ' ' ) );
360            NCurses.wmove( win, y-2, 2*currlevel-2 );
361            NCurses.waddch( win, NCurses.lineDraw.LTEE );
362          fi;
363          if y <= ymax then
364            NCurses.wmove( win, y-1, 0 );
365            NCurses.waddstr( win, ListWithIdenticalEntries(
366                                      2*currlevel-2, ' ' ) );
367            NCurses.wmove( win, y-1, 2*currlevel-2 );
368            NCurses.waddch( win, NCurses.lineDraw.LLCORNER );
369          fi;
370          prevdatarow:= y;
371#T fill with LTEE only in the first line of a cell, otherwise with VLINE
372          # Draw a horizontal line in row `y'.
373          if y <= ymax then
374            NCurses.wmove( win, y-1, 2 * currlevel - 1 );
375            NCurses.whline( win, NCurses.lineDraw.HLINE,
376                            2 * ( maxdepth - currlevel ) );
377          fi;
378        fi;
379        y:= y + height - fromrow + 1;
380      fi;
381      i:= i + 1;
382      fromrow:= 1;
383    od;
384
385    # Draw the missing vertical lines from and to outside categories:
386    # If there is an unhidden category of level $k$ below and above
387    # the next category of level less than $k$
388    # then either replace the `LLCORNER' in column $k-1$ by an `LTEE' and
389    # draw a `VLINE' below until the end of the window,
390    # or in the case that there is no `LLCORNER' in column $k-1$
391    # draw a `VLINE' through the whole window.
392    for k in [ 2 .. Length( previousrow ) ] do
393      if previousrow[k] >= previousrow[ k-1 ] then
394        for pos2 in [ lastcategorypos + 1 .. Length( catpos ) ] do
395          if   categories[2][ pos2 ].level = k
396               and not categories[2][ pos2 ].isRejectedCategory then
397            if previousrow[k] = 0 then
398              NCurses.wmove( win, ymin - 1, 2 * ( k-2 ) );
399              NCurses.wvline( win, NCurses.lineDraw.VLINE,
400                              ymax - ymin + 1 );
401            elif previousrow[k] <= ymax then
402              NCurses.wmove( win, previousrow[k]-1, 2 * ( k-2 ) );
403              NCurses.waddch( win, NCurses.lineDraw.LTEE );
404              NCurses.wmove( win, previousrow[k], 2 * ( k-2 ) );
405              NCurses.wvline( win, NCurses.lineDraw.VLINE,
406                              ymax - previousrow[k] );
407            fi;
408            break;
409          elif categories[2][ pos2 ].level < k
410               and not categories[2][ pos2 ].isRejectedCategory then
411            break;
412          fi;
413        od;
414      fi;
415    od;
416end;
417
418
419#############################################################################
420##
421#E
422
423