# gList

Definition: | include xpGUI.e gdx id = gList(rtn get_data, [selected=NULL,] string attributes="", dword_seq args={})
Description: | Creates a simple interface element that displays a non-sortable, header-less list of text-only items, with automatic scrollbars. NB: scrollbars now almost done, but selection not even started [DEV] get_data: (non-optional) a fetch function, see notes and DATA below. selected: (optional) a procedure which is invoked when the selection changes. For more information on the attributes and args parameters see gSetAttributes(). This is a paranormalised function. (see technicalia) Returns: the identifier of the created element.
pwa/p2js: | Almost, no scrollbars as yet, for one thing. [DEV]
See Also: | gCanvas, gTable
Example: |
    -- demo\xpGUI\gList.exw
    include xpGUI.e

    sequence data = {"one","too","three"}
    integer ldx = largest(apply(data,length),true)

    function get_data(integer idx)
        if idx=0 then return length(data) end if
        if idx=-1 then return ldx end if -- note 1
        sequence res = data[idx]
        if res="too" then -- spelling mistake?
            -- see note 2 below
            res = {{"too",XPG_RED,XPG_ITALIC}}
        end if
        return res
    end function

    procedure selected(integer idx)
        ?{"selected",idx,data[idx]}
    end procedure

    gdx list = gList(get_data,selected,`FONT="Consolas, 12"`),
         dlg = gDialog(list,"gList",`SIZE=240x80`)
    gSetAttribute(list,"BGCLR",XPG_LIGHT_PARCHMENT)
    gShow(dlg)
    gMainLoop()
Notes: | A gList is technically a read-only control, however there is nothing to prevent an application from handling keyboard/mouse input and modifying the data itself, and then presumably invoking gRedraw(). First-class performance is a significant concern in the design of this (deliberately simple) interface element. 1 Specifically when gList() asks data(-1), aka "what is the longest line?", it is entirely the application’s responsibility to figure out a clever way to answer that efficiently, and not the fault of gList() should an inefficient method be used, for instance apply(data,length) is fine when done once but not ideal should it repeat >99.99% of the exact same work on every single keystroke. For that reason, 0 is allowed to say "don’t care", with the obvious catch of no horizontal scrollbar to view the rest of any over-long lines. Text is always left aligned, with everything in the same font face/pointsize, but optionally scrollable and each line can be specified as a list of coloured and/or normal/bold/italic text fragments. Line numbers (for instance) would be the responsibility of the get_data() function. Should this control not meet your needs then perhaps you might consider a gTable() instead, which can handle multiple sortable columns, with headers, numeric entries, icons[??], etc. Also, a gList is just one half of the original IupList functionality which has been completely redesigned and split, so you may actually be looking for a gDropDown() instead. Should you be looking for a "dropped down with edit area" then what you probably want is actually a gVbox() with a pair of {gText,gList}) children instead, and performing the whole "add entry" thing explicitly yourself, along with setting the gText whenever selection changes in the gList. (Not really a fan of that kind of silly nonsense, be I, but at least whenever xpGUI starts to show signs of getting a bit highly opinionated, it should be relatively straightforward to change.) Each element of data/returns from the DATA fetch function can be a plain string or a sequence of string or {string,colour[,style]} fragments. Of course you could pre-analyse (/syntax-colour) the whole of data in one go up-front, or perform that per-line/on demand as shown above, and the maxlen index might also change over time. 2 Note the double braces on the "too" above: a `{"too",XPG_RED}` would display "too" (in the current foreground colour), but then complain that XPG_RED is neither a string nor {string,colour[,style]}, where colour and style are as per gCanvasText() (including the -1 for "as is"). Obviously "two" would also be valid [albeit misleading/autocorrect], as would `{"t",{"o",XPG_RED},"o"}`.
Attributes: |
?? MULTIPLE | (creation only) Allows selecting several items simultaneously (multiple list). Default: "NO"/0/false. To select multiple items, the Ctrl or Shift key must be held down. Note this changes the required signature of any SELECTED handler. Also note this is whole lines, inappropriate for and distinct from block selection in (say) an editor, which is much more of a syntax-colouring thing (see DATA). The latter can however co-exist quite happily with single line selection.
? ROWS | (creation only) The number of lines to be displayed at any given time, in cases where no explict SIZE has been set. Of course using ROWS rather than SIZE helps to prevent it showing partial/vertically clipped lines, however that cannot be assured when it is resized or micro-scrolled. If (both are) undefined the length of data is/may be consulted and clamped to 1..50, that is unless constrained by some outer container size or suchlike, say perhaps being normalised to some other sibling control. Typically much smaller than whatever get_data(0) returns, but can be a bit larger.
?? SELECTED | (read only) When MULTIPLE is false, returns the index of the currently selected line, or 0 if none. When MULTIPLE is true, returns a sequence of such, or {} if none. This can be used as an independant (and on-demand) alternative to a SELECTED handler.
? SHOWITEM | (write-only) Ensure the specified line (1..length(data)) is visible, in other words set the vertical scrollbar as needed. Can also be {line,fromcol[,tocol[,toline]]} to set the horizontal scrollbar as well, to show as much of that as possible.
? TOPITEM | Position the specified line (1..length(DATA)) at the top of the list.
also | ?? CARET, CARETPOS, ACTIVE, CANFOCUS, EXPAND (see note), FONT, MINSIZE, MAXSIZE, SIZE, TIP, VISIBLE. Note that SCROLLSIZE and VIEWPORT are automatically handled on a gList, and you should not attempt to tamper with them.
Handlers: |
DATA | A fetch function for retrieving the gList entries/lines/rows. function fetch_data([gdx id,] integer item) id: (optional) identifier of the element that activated the event. item: Index of the desired line, 0 to get the total length, -1 to get the index of the longest. Naturally, specify a gdx id should you have some use for it, or just omit it otherwise. When item is 0, as shown above, should simply return the outer/vertical length(). When item is -1, returning 0, or in fact the index of any line that fully fits, simply disables the horizontal scrollbar. Aside: idx:=fetch_data(-1) is immediately followed by line:=fetch_data(idx) for all idx>0. Technically speaking, for perfect horizontal scrolling, fetch_data(-1) should effectively invoke gGetTextExtent() on every element of data, but in practice length() is usually good enough, and in fact when a fixed-width font is in use that perfectly identifies the correct line anyway. Obviously your application may well perform much better by cacheing the results from such [getsize] calls, and you could also of course blatently lie, perform a full scan in the background/idle handler, then gRefresh() and tell the honest truth, iyswim. Obviously, you are perfectly at liberty to break up each data[i] on every fetch, rather than store it in pieces. Should some data[i] contain multiple fragments, it would be down to you to either sum the gGetTextExtent/length or apply said to a contatenation of them (gList does the former on the line:=fetch_data(idx) mentioned). Edita keeps tables of linelengths to minimise the effort required in that regard when things change, and therefore so will the new xpEditer, which will actually use a gList(). The fetch_data function can simulate a cuebanner by returning 1/1/"Enter xxx" when the internal application data is {}, of course with appropriate colour/style on the fake fragment if desired. [DEV: untested] The function should return the integer length|idx, a text string, or a sequence of {text|{text,colour[,style]}} fragments. Only those lines which are actually being displayed are ever requested, plus, optionally, the longest.
?SELECTED | Action generated when an item in the list is selected. Note there is no "unselect" equivalent (as yet). procedure selected([gdx id,] integer item) -- when MULTIPLE=NO -- or -- procedure selected([gdx id,] sequence items) -- when MULTIPLE=YES id: (optional) identifier of the element that activated the event. item: Number of the changed item starting at 1. items: Sequence of selected items, or {} if none. Naturally, specify a gdx id should you have some use for it, and just omit it otherwise. The MULTIPLE attribute determines whether the second parameter is an integer or a sequence, said can also be declared as object and tested within.
also | CLICK, IDROP, KEY, MOUSEMOVE: All common handlers are supported.
Technicalia | As this is a paranormalised function, and with no expectation that you should attempt to memorise this lot, except maybe "dsaa", you can assume that all of the following are potentially perfectly valid, with the fairly obvious restrictions that any parameters must be in strict dsaa-order, and no args without attributes:
                            gList(data)
                            gList(data, selected)
                            gList(data, attributes)
                            gList(data, attributes, args)
                            gList(data, selected, attributes, args)  -- (the full set)
Note that in practice selected is actually defined as object, and attributes as sequence rather than string, but are thoroughly verified to be of the documented types, after being repositioned via xpGUI.e/paranormalise_raa() [the non-optional data fetch routine never being moved]. The new xpEditer program uses (or rather is planned to use) a gList to implement the syntax-coloured editable text area, though it is the application code which interprets all keystrokes (bar basic navigation) and modifies the text, not the gList() itself. In that particular case the data will contain the plain text lines, with get_data() splitting requested lines into the appropriate syntax-colouring fragments on demand, in a similar fashion to what Edita has always done, with bracket/comment carryFwds[]s, and a linelengths[], since recalculating 50,000 lengths gets a bit costly when you do it on every single keystroke, etc. Since it may be opening hundreds of files and some of those could be rather long, it initially returns 0 for the longest line, which (temporarily) disables the horizontal scrollbar, then uses a background process and redraws with a proper scrollbar when done. Likewise it might (semi-deliberately) get the initial bracket/comment carry forwards wrong, and correct the current tab if needed, which is only goiung to matter when it is re-opening a file from the previous session, with the viewport not at the very top. During the implementation of all that, I will of course move as much reusable code into xpGUI.e as I can, for potential use on a gTable(), which I already know will be needed for demo\rosetta\7guis\Cells.exw and possibly several other things. Another example: it might be wise to keep the last 1,000 log entries in a circular list, rather than keep all and gradually get slower and slower before inevitably running out of memory and crashing and finally creating a ridiculously humongous ex.err. It is the responsibility of the gList() to deal with all that quickly and efficiently, whatever you decide to do.