root/trunk/txlib/txselist.c

Revision 11, 34.4 kB (checked in by jvw, 3 years ago)

DFSee 8.01 level; fix crash on huge PATH; History-popup improved (4OS2 compatible)

Line 
1//
2//                     TxWin, Textmode Windowing Library
3//
4//   Original code Copyright (c) 1995-2005 Fsys Software and Jan van Wijk
5//
6// ==========================================================================
7//
8// This file contains Original Code and/or Modifications of Original Code as
9// defined in and that are subject to the GNU Lesser General Public License.
10// You may not use this file except in compliance with the License.
11// BY USING THIS FILE YOU AGREE TO ALL TERMS AND CONDITIONS OF THE LICENSE.
12// A copy of the License is provided with the Original Code and Modifications,
13// and is also available at http://www.dfsee.com/txwin/lgpl.htm
14//
15// This library is free software; you can redistribute it and/or modify
16// it under the terms of the GNU Lesser General Public License as published
17// by the Free Software Foundation; either version 2.1 of the License,
18// or (at your option) any later version.
19//
20// This library is distributed in the hope that it will be useful,
21// but WITHOUT ANY WARRANTY; without even the implied warranty of
22// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
23// See the GNU Lesser General Public License for more details.
24//
25// You should have received a copy of the GNU Lesser General Public License
26// along with this library; (lgpl.htm) if not, write to the Free Software
27// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
28//
29// Questions on TxWin licensing can be directed to: txwin@fsys.nl
30//
31// ==========================================================================
32//
33// TX library selection-list manager
34//
35// Author: J. van Wijk
36//
37// JvW  26-06-2003 Initial version
38
39#include <txlib.h>                              // public interface
40
41/*****************************************************************************/
42// Get length of longest string in a list
43/*****************************************************************************/
44ULONG TxSelGetMaxLength                         // RET   longest string length
45(
46   TXSELIST           *list                     // IN    selection list
47)
48{
49   ULONG               rc = 0;                  // function return
50   TXS_ITEM           *item;
51   int                 l;
52
53   ENTER();
54
55   if (list != NULL)
56   {
57      for (l = 0; l < list->count; l++)
58      {
59         if ((item = list->items[l]) != NULL)
60         {
61            if (item->text != NULL)             // might be NULL (separator)
62            {
63               if (strlen( item->text) > rc)
64               {
65                  rc = strlen( item->text);
66               }
67               TRACES(("Item %2u len:%2u '%s'\n", l, strlen(item->text), item->text));
68            }
69            else
70            {
71               TRACES(("Item %2u len: 0  Probably a separator\n", l));
72            }
73         }
74      }
75   }
76   RETURN (rc);
77}                                               // end 'TxSelGetMaxLength'
78/*---------------------------------------------------------------------------*/
79
80
81/*****************************************************************************/
82// Scroll visible list up by one line (towards lower index)
83/*****************************************************************************/
84ULONG TxSelScrollUp                             // RET   scrolling succeeded
85(
86   TXSELIST           *list                     // INOUT selection list
87)
88{
89   ULONG               rc = NO_ERROR;              // function return
90
91   ENTER();
92
93   if ((list->vsize > 1) && (list->top != 0))   // scroll-up possible ?
94   {
95      if (list->renderNewItem)                  // render-new available ?
96      {
97         ULONG     index = list->vsize -1;      // index of discarded item
98         TXS_ITEM *temp  = list->items[index];  // keep item allocation
99
100         txSelItemDestroy( list->astatus & TXS_AS_DSTRINGS, &temp);
101
102         memmove( &(list->items[1]),            // destination of move
103                  &(list->items[0]),            // source of move
104                   (list->vsize -1) * sizeof(TXS_ITEM *));
105
106         list->items[0] = temp;                 // re-use same item struct
107         rc = (list->renderNewItem)( list->items[0],
108                                     NULL, list->top -1);
109      }
110      list->top--;                              // update visible top position
111   }
112   else
113   {
114      rc = TX_INVALID_DATA;
115   }
116   RETURN (rc);
117}                                               // end 'TxSelScrollUp'
118/*---------------------------------------------------------------------------*/
119
120
121/*****************************************************************************/
122// Scroll visible list down by one line (towards higher index)
123/*****************************************************************************/
124ULONG TxSelScrollDown                           // RET   scrolling succeeded
125(
126   TXSELIST           *list                     // INOUT selection list
127)
128{
129   ULONG               rc = NO_ERROR;           // function return
130
131   ENTER();
132
133   if (             (list->vsize  > 1) &&
134       ((list->top + list->vsize) < list->count)) // scroll-down possible ?
135   {
136      if (list->renderNewItem)                  // render-new available ?
137      {
138         ULONG         index = list->vsize -1;  // index of new item
139         TXS_ITEM     *temp  = list->items[0];  // keep item allocation
140
141         txSelItemDestroy( list->astatus & TXS_AS_DSTRINGS, &temp);
142
143         memmove( &(list->items[0]),            // destination of move
144                  &(list->items[1]),            // source of move
145                   (list->vsize -1) * sizeof(TXS_ITEM *));
146
147         list->items[index] = temp;             // re-use same item struct
148         rc = (list->renderNewItem)( list->items[index],
149                                     NULL, list->top + index +1);
150      }
151      list->top++;                              // update visible top position
152   }
153   else
154   {
155      rc = TX_INVALID_DATA;
156   }
157   RETURN (rc);
158}                                               // end 'TxSelScrollDown'
159/*---------------------------------------------------------------------------*/
160
161
162/*****************************************************************************/
163// Set visible list to start at specified external index; Discard old items
164/*****************************************************************************/
165ULONG TxSelSetPosition                          // RET   resulting top index
166(
167   TXSELIST           *list,                    // INOUT selection list
168   ULONG               index                    // IN    top external index
169)
170{
171   ULONG               newtop = index;          // function return
172
173   ENTER();
174
175   if ((newtop + list->vsize) > list->count)    // set top beyond end list ?
176   {
177      if (list->count > list->vsize)
178      {
179         newtop = list->count - list->vsize;    // set to end
180      }
181      else
182      {
183         newtop = 0;                            // end equals beginning ...
184      }
185   }
186   if (list->renderNewItem)                     // render-new available ?
187   {
188      ULONG         i;
189
190      for (i = 0; i < list->vsize; i++)
191      {
192         TXS_ITEM     *item  = list->items[i];  // keep item allocation
193
194         if (item == NULL)                      // not allocated yet
195         {
196            list->astatus |= TXS_AS_DYNITEMS;
197            item = TxAlloc( 1, sizeof( TXS_ITEM));
198         }
199         else                                   // discard string values
200         {
201            txSelItemDestroy( list->astatus & TXS_AS_DSTRINGS, &item);
202         }
203         if (item != NULL)
204         {
205            (list->renderNewItem)( item, NULL, list->top + i);
206         }
207         list->items[i] = item;
208      }
209   }
210   list->top = newtop;                          // update visible top position
211   RETURN (newtop);
212}                                               // end 'TxSelSetPosition'
213/*---------------------------------------------------------------------------*/
214
215
216/*****************************************************************************/
217// Set selection for specified index, and/or bits if multiple select possible
218/*****************************************************************************/
219ULONG TxSelSetSelected                          // RET   resultcode
220(
221   TXSELIST           *list,                    // INOUT selection list
222   ULONG               index,                   // IN    external index
223   TXSETRESET          action,                  // IN    set, reset or toggle
224   BYTE                mask                     // IN    bits to set/reset
225)
226{
227   ULONG               rc = NO_ERROR;           // function return
228
229   ENTER();
230
231   if (index < list->tsize)
232   {
233      if (list->selarray != NULL)               // multiple select OK ?
234      {
235         BYTE  old = list->selarray[index];
236         BYTE  new;
237
238         switch (action)
239         {
240            case TX_SET:   new = old |  mask; break;
241            case TX_RESET: new = old & ~mask; break;
242            default:       new = old ^  mask; break;
243         }
244         if ((old == 0) && (new != 0) && (list->selected < list->tsize))
245         {
246            list->selected++;
247         }
248         if ((old != 0) && (new == 0) && (list->selected != 0))
249         {
250            list->selected--;
251         }
252         list->selarray[index] = new;
253      }
254      else
255      {
256         list->selected = (action == TX_RESET) ? 0 : index;
257      }
258   }
259   else
260   {
261      rc = TX_INVALID_DATA;
262   }
263   RETURN (rc);
264}                                               // end 'TxSelSetSelected'
265/*---------------------------------------------------------------------------*/
266
267
268/*****************************************************************************/
269// Itterate over selected list items, calback all with specified mask set
270/*****************************************************************************/
271ULONG TxSelItterateSelected
272(
273   TXSELIST           *list,                    // INOUT selection list
274   TXS_ITEMHANDLER     selected,                // IN    callback selected
275   BYTE                mask                     // IN    selection mask
276)
277{
278   ULONG               rc = NO_ERROR;           // function return
279   TXS_ITEM            item;
280
281   ENTER();
282
283   if (list->selarray != NULL)                  // multiple select OK ?
284   {
285      if (list->selected != 0)
286      {
287         ULONG            i;
288
289         for (i = 0; i < list->tsize; i++)      // visit all list entries
290         {
291            if ((list->selarray[i] & mask) == mask) // matches selection ?
292            {
293               if (list->vsize == list->tsize)  // all info available
294               {
295                  (selected)(list->items[i], &(list->selarray[i]), i);
296               }
297               else if (list->renderNewItem)
298               {
299                  (list->renderNewItem)(&item, &(list->selarray[i]), i);
300                  (selected)(&item, NULL, i);
301               }
302               else
303               {
304                  (selected)( NULL, NULL, list->selected);
305               }
306            }
307         }
308      }
309      else                                      // nothing selected
310      {
311         rc = TX_INVALID_DATA;
312      }
313   }
314   else                                         // callback for selected
315   {
316      if (list->vsize == list->tsize)           // all info available
317      {
318         (selected)(list->items[list->selected], NULL, list->selected);
319      }
320      else if (list->renderNewItem)
321      {
322         (list->renderNewItem)(&item, NULL, list->selected);
323         (selected)(&item, NULL, list->selected);
324      }
325      else
326      {
327         (selected)( NULL, NULL, list->selected);
328      }
329   }
330   RETURN (rc);
331}                                               // end 'TxSelItterateSelected'
332/*---------------------------------------------------------------------------*/
333
334
335/*****************************************************************************/
336// Set 'selected' to item with specified selection char (at text[index-1])
337/*****************************************************************************/
338ULONG TxSelCharSelect                           // RET   index now selected
339(
340   TXSELIST           *list,                    // INOUT selection list
341   char                select                   // IN    selection character
342)
343{
344   ULONG               rc = list->selected;
345   TXS_ITEM           *item;
346
347   ENTER();
348   TRACES(("list:%8.8lx select: '%c'\n", list, select));
349
350   if (list->selarray == NULL)                  // single-select only
351   {
352      ULONG            i;
353
354      for (i = 0; i < list->count; i++)         // visit list entries
355      {
356         if ((item = list->items[i]) != NULL)
357         {
358            if (item->index != 0)               // selection char there ?
359            {
360               if (list->items[i]->text != NULL) // has text string ?
361               {
362                  if (item->text[ item->index -1] == select)
363                  {
364                     list->selected = i;        // set new selection
365                     rc = i;
366                     break;                     // end the search
367                  }
368               }
369            }
370         }
371      }
372   }
373   RETURN (rc);
374}                                               // end 'TxSelCharSelect'
375/*---------------------------------------------------------------------------*/
376
377
378/*****************************************************************************/
379// Set 'selected' to item where text starts with 'string' (case-insensitive)
380/*****************************************************************************/
381ULONG TxSelStringSelect                         // RET   index now selected
382(
383   TXSELIST           *list,                    // INOUT selection list
384   char               *select,                  // IN    selection string
385   int                 length                   // IN    significant length
386)
387{
388   ULONG               rc = 0;
389   TXS_ITEM           *item;
390
391   ENTER();
392   TRACES(( "list:%8.8lx  select:'%s'  length:%u\n", list, select, length));
393
394   if (list != NULL)
395   {
396      rc = list->selected;
397      TRACES(("list:%8.8lx select: '%s'\n", list, select));
398
399      if (list->selarray == NULL)               // single-select only
400      {
401         ULONG            i;
402
403         for (i = 0; i < list->count; i++)      // visit list entries
404         {
405            if ((item = list->items[i]) != NULL) // existing item ?
406            {
407               if (list->items[i]->text != NULL) // has text string ?
408               {
409                  if (strnicmp( list->items[i]->text, select, length) == 0)
410                  {
411                     list->selected = i;        // set new selection
412                     rc = i;
413                     break;                     // end the search
414                  }
415               }
416            }
417         }
418      }
419   }
420   RETURN (rc);
421}                                               // end 'TxSelStringSelect'
422/*---------------------------------------------------------------------------*/
423
424
425/*****************************************************************************/
426// Get unique quick-select character for specified list index
427/*****************************************************************************/
428USHORT TxSelGetCharSelect                       // RET   index for select-char
429(                                               //       or 0 if not possible
430   TXSELIST           *list,                    // IN    selection list
431   ULONG               nr,                      // IN    item to get char for
432   int                 offset                   // IN    minimum position 0..n
433)
434{
435   USHORT              rc = 0;                  // function return
436   TXS_ITEM           *item;
437   TXLN                used;                    // used characters
438   int                 l;
439   char                c;
440
441   ENTER();
442
443   memset( used, 0, TXMAXLN);
444
445   if (list != NULL)
446   {
447      //- build list of used characters
448
449      for (l = 0; l < list->count; l++)
450      {
451         if (l != nr)                           // can replace by same value!
452         {
453            if ((item = list->items[l]) != NULL)
454            {
455               if ((item->text != NULL) && (item->index != 0))
456               {
457                  used[ strlen(used)] = item->text[item->index -1];
458               }
459            }
460         }
461      }
462      strupr( used);                            // convert to upper-case!
463      TRACES(("item-nr %lu, Selchars used: '%s'\n", nr, used));
464
465      if ((item = list->items[nr]) != NULL)     // item to test
466      {
467         for (l = offset; l < strlen( item->text); l++)
468         {
469            c = toupper( item->text[l]);
470            if (isalnum(c))                     // accept alpha-numeric only
471            {
472               if (strchr( used, c) == NULL)    // not used yet
473               {
474                  rc = l +1;
475                  TRACES(("SelChar: '%c' at index %hu\n", item->text[l], rc));
476                  break;
477               }
478            }
479         }
480      }
481   }
482   RETURN (rc);
483}                                               // end 'TxSelGetCharSelect'
484/*---------------------------------------------------------------------------*/
485
486
487/*****************************************************************************/
488// Set/reset specified flag-bit(s) in menu-item by searching menu-bar structure
489/*****************************************************************************/
490ULONG TxSetMenuItemFlag                         // RET   #items found and set
491(
492   TXS_MENUBAR        *mbar,                    // IN    menubar information
493   ULONG               mid,                     // IN    value, menu item id
494   BYTE                mask,                    // IN    bits to set/reset
495   BOOL                set                      // IN    set specified bit(s)
496)
497{
498   ULONG               rc = 0;                  // function return
499   TXS_MENU           *menu;
500   int                 m;
501
502   ENTER();
503   TRACES(("%sSET item: %lu, using mask %2.2hx\n", (set) ? "" : "RE", mid, mask));
504
505   for (m = 0; m < mbar->count; m++)
506   {
507      if ((menu = mbar->menu[m]) != NULL)
508      {
509         TRACES(("Menu %d='%s' list:%8.8lx\n", m +1, menu->text, menu->list));
510
511         rc += TxSetListItemFlag( 1, menu->list, mid, mask, set);
512      }
513   }
514   RETURN (rc);
515}                                               // end 'TxSetMenuItemFlag'
516/*---------------------------------------------------------------------------*/
517
518
519/*****************************************************************************/
520// Set/reset specified flag-bit(s) in menu-item by searching list-structure
521/*****************************************************************************/
522ULONG TxSetListItemFlag                         // RET   #items found and set
523(
524   ULONG               recurse,                 // IN    recursion level (1)
525   TXSELIST           *list,                    // IN    list information
526   ULONG               mid,                     // IN    value, menu item id
527   BYTE                mask,                    // IN    bits to set/reset
528   BOOL                set                      // IN    set specified bit(s)
529)
530{
531   ULONG               rc = 0;                  // function return
532   TXS_ITEM           *item;
533   int                 i;
534
535   TRACES(("%sSET list item: %lu, using mask %2.2hx on list at %8.8lx\n",
536          (set) ? "" : "RE", mid, mask, list));
537
538   if (list != NULL)
539   {
540      for (i = 0; i < list->count; i++)
541      {
542         // TRACES(("Check item: %u\n", i));
543         if ((item = list->items[i]) != NULL)   // item to test
544         {
545            if (item->value == mid)             // matching item id ?
546            {
547               if (set)
548               {
549                  item->flags |=  mask;
550               }
551               else
552               {
553                  item->flags &= ~mask;
554               }
555               rc++;                            // count item as set
556
557               /*
558               TRACES(("List %8.8lx item %d='%s' %sSET %s %s\n",
559                        list, i +1, item->text, (set) ? "" : "RE",
560                        (mask & TXSF_DISABLED) ? "DISABLED" : "",
561                        (mask & TXSF_MARKED  ) ? "MARKED"   : ""));
562               */
563            }
564            if ((item->flags & TXSF_P_LISTBOX) && // this is a popup/submenu
565                (item->user != 0))              // and a list is attached.
566            {
567               // TRACES(( "Recurse level %lu list: %8.8lx\n", recurse +1, item->user));
568
569               if (recurse < 99)                // sanity check
570               {
571                  rc += TxSetListItemFlag( recurse +1,
572                                          (TXSELIST *) item->user,
573                                           mid, mask, set);
574               }
575               else
576               {
577                  TRACES(("Aborting on insane recursion level ...\n"));
578               }
579            }
580         }
581      }
582   }
583   return (rc);
584}                                               // end 'TxSetListItemFlag'
585/*---------------------------------------------------------------------------*/
586
587
588/*****************************************************************************/
589// Create a dynamic selection list with specified size
590/*****************************************************************************/
591ULONG TxSelCreate
592(
593   ULONG               vsize,                   // IN    visible size
594   ULONG               tsize,                   // IN    total size of list
595   ULONG               asize,                   // IN    size for item array
596   ULONG               astat,                   // IN    alloc status for items
597   BOOL                multi,                   // IN    create multi-select info
598   TXS_ITEMHANDLER     render,                  // IN    callback render new
599   TXSELIST          **list                     // OUT   selection list
600)
601{
602   ULONG               rc = NO_ERROR;           // function return
603   TXSELIST           *selist;
604
605   ENTER();
606   TRACES(("vsize:%hu  tsize:%hu  asize:%hu  astat:%8.8lx\n",
607            vsize, tsize, asize, astat));
608
609   if ((selist = TxAlloc( 1, sizeof(TXSELIST))) != NULL)
610   {
611      selist->astatus       = astat | TXS_AS_LISTRUCT;
612      selist->renderNewItem = render;
613      selist->asize         = asize;
614      selist->vsize         = vsize;
615      selist->tsize         = tsize;
616      selist->count         = 0;                // no items yet
617
618      if (asize != NULL)
619      {
620         TRACES(( "Allocate item-ptr array for %lu items\n", tsize));
621         if ((selist->items  = TxAlloc( asize, sizeof(TXS_ITEM *))) != NULL)
622         {
623            selist->astatus |= TXS_AS_PTRARRAY;
624         }
625         else
626         {
627            rc = TX_ALLOC_ERROR;
628         }
629      }
630      if (multi && (tsize != 0))
631      {
632         TRACES(( "Allocate selection array for %lu items\n", tsize));
633         if ((selist->selarray = TxAlloc( tsize, sizeof(BYTE))) != NULL)
634         {
635            selist->astatus   |= TXS_AS_SELARRAY;
636         }
637         else
638         {
639            rc = TX_ALLOC_ERROR;
640         }
641      }
642      *list = selist;                           // return created list
643      if (rc != NO_ERROR)
644      {
645         txSelDestroy( list);                   // free partial list memory
646      }
647   }
648   else
649   {
650      rc = TX_ALLOC_ERROR;
651   }
652   TRACES(("list:%8.8lx\n", *list));
653   RETURN (rc);
654}                                               // end 'TxSelCreate'
655/*---------------------------------------------------------------------------*/
656
657
658/*****************************************************************************/
659// Destroy dynamic parts of the selection list, freeing memory
660/*****************************************************************************/
661ULONG txSelDestroy
662(
663   TXSELIST          **list                     // IN    selection list
664)
665{
666   ULONG               rc = NO_ERROR;           // function return
667
668   ENTER();
669
670   if ((list != NULL) && ((*list) != NULL))
671   {
672      ULONG            as = (*list)->astatus;   // allocation status
673      ULONG            ic = (*list)->count;     // actual number of items
674      ULONG            i;
675
676      if ((as & TXS_AS_DYNITEMS) != 0)          // dynamic alloc in items ?
677      {
678         for (i = 0; i < ic; i++)
679         {
680            txSelItemDestroy( as, &((*list)->items[i]));
681         }
682      }
683      if (as & TXS_AS_PTRARRAY)
684      {
685         TRACES(("Destroying item ptr-array at:%8.8lx\n", (*list)->items));
686         TxFreeMem( (*list)->items);            // free item array
687      }
688      if (as & TXS_AS_SELARRAY)
689      {
690         TRACES(("Destroying multi selarray at:%8.8lx\n", (*list)->selarray));
691         TxFreeMem( (*list)->selarray);         // free selection array
692      }
693      if (as & TXS_AS_LISTRUCT)
694      {
695         TRACES(("Destroying list at:%8.8lx\n", *list));
696         TxFreeMem( (*list));                   // free list structure
697      }
698   }
699   else                                         // invalid parameters
700   {
701      rc = TX_INVALID_DATA;
702   }
703   RETURN (rc);
704}                                               // end 'txSelDestroy'
705/*---------------------------------------------------------------------------*/
706
707
708/*****************************************************************************/
709// Destroy dynamic parts of a selection list-item, freeing memory
710/*****************************************************************************/
711ULONG txSelItemDestroy
712(
713   ULONG               as,                      // IN    allocaton status
714   TXS_ITEM          **itemref                  // IN    selection list item
715)
716{
717   ULONG               rc = NO_ERROR;           // function return
718
719   ENTER();
720
721   if ((itemref != NULL) && ((*itemref) != NULL))
722   {
723      TXS_ITEM  *item = *itemref;
724
725      if (as & TXS_AS_SEL_DESC)
726      {
727         TxFreeMem( item->desc);
728      }
729      if (as & TXS_AS_SEL_TEXT)
730      {
731         TxFreeMem( item->text);
732      }
733      if (as & TXS_AS_SELITEMS)
734      {
735         TxFreeMem( (*itemref));
736      }
737   }
738   else                                         // invalid parameters
739   {
740      rc = TX_INVALID_DATA;
741   }
742   RETURN (rc);
743}                                               // end 'txSelItemDestroy'
744/*---------------------------------------------------------------------------*/
745
746
747/*****************************************************************************/
748// Delete current item from the list (like Ctrl-D on a history :-)
749/*****************************************************************************/
750ULONG TxSelDeleteCurrent                        // RET   resulting selected
751(
752   TXSELIST           *list                     // INOUT selection list
753)
754{
755   ENTER();
756
757   if ((list != NULL) && (list->count != 0) && (list->selarray == NULL))
758   {
759      ULONG            i;
760
761      txSelItemDestroy( list->astatus, &(list->items[ list->selected]));
762
763      list->count--;                            // update real list-size
764      for (i = list->selected; i < list->count; i++)
765      {
766         list