Initial commit
[amiga/BoopsiListView.git] / ListViewClass.c
1 /*      ListViewClass.c
2 **
3 **      Copyright (C) 1996,97 Bernardo Innocenti
4 **
5 **      Use 4 chars wide TABs to read this file
6 **
7 **      GadTools-like `boopsi' ListView gadget class
8 */
9
10 #define USE_BUILTIN_MATH
11 #define INTUI_V36_NAMES_ONLY
12 #define __USE_SYSBASE
13 #define  CLIB_ALIB_PROTOS_H             /* Avoid dupe defs of boopsi funcs */
14
15 #include <exec/types.h>
16 #include <exec/libraries.h>
17 #include <intuition/intuition.h>
18 #include <intuition/intuitionbase.h>
19 #include <intuition/classes.h>
20 #include <intuition/gadgetclass.h>
21 #include <graphics/gfxbase.h>
22 #include <graphics/gfxmacros.h>
23
24 #include <proto/intuition.h>
25 #include <proto/graphics.h>
26 #include <proto/layers.h>
27 #include <proto/utility.h>
28
29 #ifdef __STORM__
30         #pragma header
31 #endif
32
33 #include "CompilerSpecific.h"
34 #include "Debug.h"
35 #include "BoopsiStubs.h"
36
37 #define LV_GADTOOLS_STUFF
38 #include "ListViewClass.h"
39
40
41
42 /* ListView private instance data */
43
44
45 /* Type of a listview hook function */
46 typedef ASMCALL APTR    LVHook(
47         REG(a0, struct Hook     *hook), REG(a1, APTR item), REG(a2, struct lvGetItem *lvg));
48 typedef ASMCALL APTR    LVDrawHook(
49         REG(a0, struct Hook     *hook), REG(a1, APTR item), REG(a2, struct lvDrawItem *lvdi));
50
51 struct LVData
52 {
53         APTR                     Items;                         /* The list/array of items                              */
54         LONG                     Top;                           /* Ordinal nr. of the top visible item  */
55         APTR                     TopPtr;                        /* Pointer to the top visible item              */
56         LONG                     Total;                         /* Total nr. of items in the list               */
57         LONG                     Visible;                       /* Number of items visible in the list  */
58         LONG                     PixelTop;                      /* Pixel-wise offset from the top               */
59         LONG                     Selected;                      /* Ordinal nr. of the selected item             */
60         APTR                     SelectedPtr;           /* Pointer to the selected item                 */
61         ULONG                    SelectCount;           /* Number of items currently selected   */
62         ULONG                    MaxSelect;                     /* Maximum nr. of selections to allow   */
63
64         /* Old values used to track scrolling amount in GM_RENDER */
65         LONG                     OldTop;
66         LONG                     OldPixelTop;
67         LONG                     OldSelected;
68         APTR                     OldSelectedPtr;
69
70         ULONG                    DragSelect;            /* Status of drag selection                             */
71         LONG                     ItemHeight;            /* Height of one item in pixels                 */
72         LONG                     Spacing;                       /* Spacing between items in pixels              */
73         LONG                     MaxScroll;                     /* Redraw all when scrolling too much   */
74         LONG                     ScrollRatio;           /* max visible/scrolled ratio                   */
75         ULONG                   *SelectArray;           /* Array of selected items. May be NULL */
76         LONG                     BackupSelected;        /* Used by RMB undo                                     */
77         LONG                     BackupPixelTop;        /* Used by RMB undo                                             */
78         WORD                     MiddleMouseY;          /* Initial Y position for MMB scrolling */
79         ULONG                    Flags;                         /* See <listviewclass.h>                                */
80         ULONG                    MaxPen;                        /* Highest pen number used                              */
81         ULONG                    DoubleClickSecs, DoubleClickMicros;
82
83         /* User or internal hooks */
84         LVHook                  *GetItemFunc;
85         LVHook                  *GetNextFunc;
86         LVHook                  *GetPrevFunc;
87         LVHook                  *DrawBeginFunc;
88         LVHook                  *DrawEndFunc;
89         LVDrawHook              *DrawItemFunc;
90         struct Hook             *CallBack;                      /* Callback hook provided by user       */
91
92         struct TextFont *Font;                          /* Font used to render text labels      */
93         struct Region   *ClipRegion;            /* Used in LVA_Clipped mode                     */
94
95         /* These two have the same meaning, but we keep both updated
96          * because the Rectangle structure (MinX, MinY, MaxX, MaxY)
97          * is more handy in some cases, while the IBox structure
98          * (Left/Top/Width/Height) is best for other cases.
99          */
100         struct IBox              GBox;
101         struct Rectangle GRect;
102 };
103
104
105
106 /* Local function prototypes */
107
108 static void             LV_GMRender             (Class *cl, struct Gadget *g, struct gpRender *msg);
109 static ULONG    LV_GMGoActive   (Class *cl, struct Gadget *g, struct gpInput *msg);
110 static ULONG    LV_GMHandleInput(Class *cl, struct Gadget *g, struct gpInput *msg);
111 static void             LV_GMGoInactive (Class *cl, struct Gadget *g, struct gpGoInactive *msg);
112 static void             LV_GMLayout             (Class *cl, struct Gadget *g, struct gpLayout *msg);
113 static ULONG    LV_OMSet                (Class *cl, struct Gadget *g, struct opUpdate *msg);
114 static ULONG    LV_OMGet                (Class *cl, struct Gadget *g, struct opGet *msg);
115 static ULONG    LV_OMNew                (Class *cl, struct Gadget *g, struct opSet *msg);
116 static void             LV_OMDispose    (Class *cl, struct Gadget *g, Msg msg);
117
118 static void             RedrawItems             (struct LVData *lv, struct gpRender *msg, ULONG first, ULONG last, APTR item);
119 INLINE LONG             ItemHit                 (struct LVData *lv, WORD x, WORD y);
120 INLINE void             GetGadgetBox    (struct GadgetInfo *ginfo, struct ExtGadget *g, struct IBox *box, struct Rectangle *rect);
121 INLINE  APTR    GetItem                 (struct LVData *lv, ULONG num);
122 INLINE  APTR    GetNext                 (struct LVData *lv, APTR item, ULONG num);
123 INLINE  APTR    GetPrev                 (struct LVData *lv, APTR item, ULONG num);
124 INLINE ULONG    CountNodes              (struct List *list);
125 static ULONG    CountSelections (struct LVData *lv);
126 INLINE ULONG    IsItemSelected  (struct LVData *lv, APTR item, ULONG num);
127
128 /* Definitions for the builtin List hooks */
129 LVHook          ListGetItem;
130 LVHook          ListGetNext;
131 LVHook          ListGetPrev;
132 LVDrawHook      ListStringDrawItem;
133 LVDrawHook      ListImageDrawItem;
134
135 /* Definitions for the builtin Array hooks */
136 LVHook          ArrayGetItem;
137 LVDrawHook      StringDrawItem;
138 LVDrawHook      ImageDrawItem;
139
140
141
142 static ULONG HOOKCALL LVDispatcher (
143         REG(a0, Class *cl),
144         REG(a2, struct Gadget *g),
145         REG(a1, Msg msg))
146
147 /* ListView class dispatcher - Handles all supported methods */
148 {
149         ASSERT_VALIDNO0(cl)
150         ASSERT_VALIDNO0(g)
151         ASSERT_VALIDNO0(msg)
152
153         switch (msg->MethodID)
154         {
155                 case GM_RENDER:
156                         LV_GMRender (cl, g, (struct gpRender *)msg);
157                         return TRUE;
158
159                 case GM_GOACTIVE:
160                         return LV_GMGoActive (cl, g, (struct gpInput *)msg);
161
162                 case GM_HANDLEINPUT:
163                         return LV_GMHandleInput (cl, g, (struct gpInput *)msg);
164
165                 case GM_GOINACTIVE:
166                         LV_GMGoInactive (cl, g, (struct gpGoInactive *)msg);
167                         return TRUE;
168
169                 case GM_LAYOUT:
170                         /* This method is only supported on V39 and above */
171                         LV_GMLayout (cl, g, (struct gpLayout *)msg);
172                         return TRUE;
173
174                 case OM_SET:
175                 case OM_UPDATE:
176                         return LV_OMSet (cl, g, (struct opUpdate *)msg);
177
178                 case OM_GET:
179                         return LV_OMGet (cl, g, (struct opGet *)msg);
180
181                 case OM_NEW:
182                         return LV_OMNew (cl, g, (struct opSet *)msg);
183
184                 case OM_DISPOSE:
185                         LV_OMDispose (cl, g, msg);
186                         return TRUE;
187
188                 default:
189                         /* Unsupported method: let our superclass's
190                          * dispatcher take a look at it.
191                          */
192                         return DoSuperMethodA (cl, (Object *)g, msg);
193         }
194 }
195
196
197
198 INLINE void GetItemBounds (struct LVData *lv, struct Rectangle *rect, LONG item)
199
200 /* Compute the bounding box to render the given item and store it in the passed
201  * Rectangle structure.
202  */
203 {
204         ASSERT_VALIDNO0(lv)
205         ASSERT_VALIDNO0(rect)
206         ASSERT(item < lv->Total)
207         ASSERT(item >= 0)
208
209         rect->MinX = lv->GRect.MinX;
210         rect->MaxX = lv->GRect.MaxX;
211         rect->MinY = lv->ClipRegion ?
212                 (lv->GRect.MinY + item * (lv->ItemHeight + lv->Spacing) - lv->PixelTop) :
213                 (lv->GRect.MinY + (item - lv->Top) * (lv->ItemHeight + lv->Spacing));
214         rect->MaxY = rect->MinY + lv->ItemHeight - 1;
215 }
216
217
218
219 static void RedrawItems (struct LVData *lv, struct gpRender *msg, ULONG first, ULONG last, APTR item)
220
221 /* Redraw items from <min> to <max>.  No sanity checks are performed
222  * to ensure that all items between <min> and <max> are really visible.
223  */
224 {
225         struct lvDrawItem lvdi;
226         LONG selected;
227
228
229         ASSERT_VALIDNO0(lv)
230         ASSERT_VALIDNO0(msg)
231         ASSERT(first <= last)
232         ASSERT(last < lv->Total)
233
234         DB (kprintf ("  RedrawItems (first = %ld, last = %ld)\n", first, last);)
235
236
237         lvdi.lvdi_Current       = first;
238         lvdi.lvdi_Items         = lv->Items;
239         lvdi.lvdi_RastPort      = msg->gpr_RPort;
240         lvdi.lvdi_DrawInfo      = msg->gpr_GInfo->gi_DrInfo;
241         lvdi.lvdi_Flags         = lv->Flags;
242
243         GetItemBounds (lv, &lvdi.lvdi_Bounds, first);
244
245         if (!item)
246         {
247                 lvdi.lvdi_MethodID = LV_GETITEM;
248                 item = lv->GetItemFunc (lv->CallBack, NULL, (struct lvGetItem *)&lvdi);
249         }
250
251         if (lv->DrawBeginFunc)
252         {
253                 lvdi.lvdi_MethodID      = LV_DRAWBEGIN;
254                 lv->DrawBeginFunc (lv->CallBack, item, (struct lvDrawBegin *)&lvdi);
255         }
256
257         for (;;)
258         {
259                 if (lv->Flags & LVF_DOMULTISELECT)
260                 {
261                         if (lv->SelectArray)
262                                 /* Array selection */
263                                 selected = lv->SelectArray[lvdi.lvdi_Current];
264                         else
265                                 if (lv->Flags & LVF_LIST)
266                                         /* Node selection */
267                                         selected = (((struct Node *)item)->ln_Type);
268                                 else
269                                         selected = 0;
270                 }
271                 else
272                         /* Single selection */
273                         selected = (lvdi.lvdi_Current == lv->Selected);
274
275                 lvdi.lvdi_State = selected ? LVR_SELECTED : LVR_NORMAL;
276
277                 lvdi.lvdi_MethodID      = LV_DRAW;
278                 lv->DrawItemFunc (lv->CallBack, item, &lvdi);
279
280                 if (++lvdi.lvdi_Current > last)
281                         break;
282
283                 lvdi.lvdi_MethodID      = LV_GETNEXT;
284                 item = lv->GetNextFunc (lv->CallBack, item, (struct lvGetNext *)&lvdi);
285
286                 lvdi.lvdi_Bounds.MinY += lv->ItemHeight + lv->Spacing;
287                 lvdi.lvdi_Bounds.MaxY += lv->ItemHeight + lv->Spacing;
288         }
289
290         if (lv->DrawEndFunc)
291         {
292                 lvdi.lvdi_MethodID      = LV_DRAWEND;
293                 lv->DrawEndFunc (lv->CallBack, item, (struct lvDrawEnd *)&lvdi);
294         }
295 }
296
297
298
299 static void LV_GMRender (Class *cl, struct Gadget *g, struct gpRender *msg)
300 {
301         struct LVData           *lv = INST_DATA (cl, g);
302         struct RastPort         *rp = msg->gpr_RPort;
303
304         ASSERT_VALIDNO0(lv)
305         ASSERT_VALIDNO0(rp)
306
307         DB (kprintf ("GM_RENDER: msg->gpr_Redraw = %s\n",
308                 (msg->gpr_Redraw == GREDRAW_TOGGLE) ? "GREDRAW_TOGGLE" :
309                 ((msg->gpr_Redraw == GREDRAW_REDRAW) ? "GREDRAW_REDRAW" :
310                 ((msg->gpr_Redraw == GREDRAW_UPDATE) ? "GREDRAW_UPDATE" :
311                 "*** Unknown ***")) );)
312
313
314 #ifndef OS30_ONLY
315         /* Pre-V39 Intuition won't call our GM_LAYOUT method, so we must
316          * always call it before redrawing the gadget.
317          */
318         if ((IntuitionBase->LibNode.lib_Version < 39) &&
319                 (msg->gpr_Redraw == GREDRAW_REDRAW))
320                 LV_GMLayout (cl, g, (struct gpLayout *)msg);
321 #endif /* !OS30_ONLY */
322
323         if (lv->Flags & LVF_DONTDRAW)
324                 return;
325
326         if (lv->Items && lv->Visible)
327         {
328                 struct TextFont *oldfont = NULL;
329                 struct Region *oldregion = NULL;
330
331                 if (rp->Font != lv->Font)
332                 {
333                         oldfont = rp->Font;
334                         SetFont (rp, lv->Font);
335                 }
336
337                 if (lv->ClipRegion)
338                 {
339                         ASSERT_VALIDNO0(lv->ClipRegion)
340                         oldregion = InstallClipRegion (rp->Layer, lv->ClipRegion);
341                 }
342
343                 switch (msg->gpr_Redraw)
344                 {
345                         case GREDRAW_TOGGLE:    /* Toggle selected item */
346                         {
347                                 BOOL    drawnew = (lv->Selected >= lv->Top) && (lv->Selected < lv->Top + lv->Visible),
348                                                 drawold = (lv->OldSelected >= lv->Top) && (lv->OldSelected < lv->Top + lv->Visible);
349
350                                 if (drawold || drawnew)
351                                 {
352                                         struct lvDrawItem        lvdi;
353                                         lvdi.lvdi_Items         = lv->Items;
354                                         lvdi.lvdi_RastPort      = rp;
355                                         lvdi.lvdi_DrawInfo      = msg->gpr_GInfo->gi_DrInfo;
356                                         lvdi.lvdi_Flags         = lv->Flags;
357
358
359                                         if (lv->DrawBeginFunc)
360                                         {
361                                                 lvdi.lvdi_MethodID      = LV_DRAWBEGIN;
362                                                 lv->DrawBeginFunc (lv->CallBack, NULL, (struct lvDrawBegin *)&lvdi);
363                                         }
364
365                                         lvdi.lvdi_MethodID      = LV_DRAW;
366
367                                         if (drawnew)
368                                         {
369                                                 GetItemBounds (lv, &lvdi.lvdi_Bounds, lv->Selected);
370                                                 lvdi.lvdi_State = IsItemSelected (lv, lv->SelectedPtr, lv->Selected) ?
371                                                         LVR_SELECTED : LVR_NORMAL;
372                                                 lvdi.lvdi_Current = lv->Selected;
373
374                                                 lv->DrawItemFunc (lv->CallBack, lv->SelectedPtr, &lvdi);
375                                         }
376
377                                         if (drawold)
378                                         {
379                                                 GetItemBounds (lv, &lvdi.lvdi_Bounds, lv->OldSelected);
380                                                 lvdi.lvdi_State = IsItemSelected (lv, lv->OldSelectedPtr, lv->OldSelected) ?
381                                                         LVR_SELECTED : LVR_NORMAL;
382                                                 lvdi.lvdi_Current = lv->OldSelected;
383
384                                                 lv->DrawItemFunc (lv->CallBack, lv->OldSelectedPtr, &lvdi);
385                                         }
386
387                                         if (lv->DrawEndFunc)
388                                         {
389                                                 lvdi.lvdi_MethodID      = LV_DRAWEND;
390                                                 lv->DrawEndFunc (lv->CallBack, NULL, (struct lvDrawEnd *)&lvdi);
391                                         }
392                                 }
393
394                                 lv->OldSelected = lv->Selected;
395                                 lv->OldSelectedPtr = lv->SelectedPtr;
396
397                                 break;
398                         }
399
400                         case GREDRAW_REDRAW:    /* Redraw everything */
401                         {
402                                 LONG    ycoord;
403
404                                 /* Set the background pen */
405                                 SetAPen (rp, msg->gpr_GInfo->gi_DrInfo->dri_Pens[BACKGROUNDPEN]);
406                                 /* SetAPen (rp, -1); Used to debug clearing code */
407
408                                 /* Now clear the spacing between the items */
409                                 if (lv->Spacing && lv->Items && lv->Visible)
410                                 {
411                                         LONG i, lastitem;
412
413                                         ycoord = lv->GRect.MinY + lv->ItemHeight;
414                                         lastitem = min (lv->Visible, lv->Total - lv->Top) - 1;
415
416                                         for (i = 0 ; i < lastitem; i++)
417                                         {
418                                                 RectFill (rp, lv->GRect.MinX, ycoord,
419                                                         lv->GRect.MaxX, ycoord + lv->Spacing - 1);
420
421                                                 ycoord += lv->ItemHeight + lv->Spacing;
422                                         }
423                                 }
424                                 else
425                                         ycoord = lv->GRect.MinY + min (lv->Visible, lv->Total - lv->Top)
426                                                 * lv->ItemHeight;
427
428                                 /* Now let's clear bottom part of gadget */
429                                 RectFill (rp, lv->GRect.MinX, ycoord,
430                                         lv->GRect.MaxX, lv->GRect.MaxY);
431
432                                 /* Finally, draw the items */
433                                 RedrawItems (lv, msg, lv->Top,
434                                         min (lv->Top + lv->Visible, lv->Total) - 1, lv->TopPtr);
435
436                                 break;
437                         }
438
439                         case GREDRAW_UPDATE:    /* Scroll ListView */
440                         {
441                                 LONG scroll_dy, scroll_height;
442
443                                 if (lv->ClipRegion)
444                                 {
445                                         /* Calculate scrolling amount in pixels */
446                                         if (!(scroll_dy = lv->PixelTop - lv->OldPixelTop))
447                                                 /* Do nothing if called improperly */
448                                                 break;
449
450                                         /* Scroll everything */
451                                         scroll_height = lv->GBox.Height;
452                                 }
453                                 else
454                                 {
455                                         if (!(lv->Top - lv->OldTop))
456                                                 /* Do nothing if called improperly */
457                                                 break;
458
459                                         /* Calculate scrolling amount in pixels */
460                                         scroll_dy = (lv->Top - lv->OldTop) * (lv->ItemHeight + lv->Spacing);
461
462                                         /* Only scroll upto last visible item */
463                                         scroll_height = lv->Visible * (lv->ItemHeight + lv->Spacing) - lv->Spacing;
464                                 }
465
466                                 if (abs(scroll_dy) > lv->MaxScroll)
467                                 {
468                                         /* Redraw everything when listview has been scrolled too much */
469                                         RedrawItems (lv, msg, lv->Top,
470                                                 min (lv->Top + lv->Visible, lv->Total) - 1, lv->TopPtr);
471                                 }
472                                 else
473                                 {
474 #ifndef OS30_ONLY
475                                         if (GfxBase->LibNode.lib_Version >= 39)
476 #endif /* OS30_ONLY */
477                                                 /* Optimize scrolling on planar displays if possible */
478                                                 SetMaxPen (rp, lv->MaxPen);
479
480                                         /* We use ClipBlit() to scroll the listview because it doesn't clear
481                                          * the scrolled region like ScrollRaster() would do.  Unfortunately,
482                                          * ClipBlit() does not scroll along the damage regions, so we also
483                                          * call ScrollRaster() with the mask set to 0, which will scroll the
484                                          * layer damage regions without actually modifying the display.
485                                          */
486
487                                         if (scroll_dy > 0)      /* Scroll Down */
488                                         {
489                                                 ClipBlit (rp, lv->GBox.Left, lv->GBox.Top + scroll_dy,
490                                                         rp, lv->GBox.Left, lv->GBox.Top,
491                                                         lv->GBox.Width, scroll_height - scroll_dy,
492                                                         0x0C0);
493
494                                                         if (lv->ClipRegion)
495                                                         {
496                                                                 /* NOTE: We subtract 1 pixel to avoid an exact division which would
497                                                                  *       render one item beyond the end when the slider is dragged
498                                                                  *       all the way down.
499                                                                  */
500                                                                 RedrawItems (lv, msg,
501                                                                         (lv->OldPixelTop + lv->GBox.Height) / (lv->ItemHeight + lv->Spacing),
502                                                                         (lv->PixelTop + lv->GBox.Height - 1) / (lv->ItemHeight + lv->Spacing),
503                                                                         NULL);
504                                                         }
505                                                         else
506                                                                 RedrawItems (lv, msg,
507                                                                         lv->Visible + lv->OldTop,
508                                                                         lv->Visible + lv->Top - 1,
509                                                                         NULL);
510                                         }
511                                         else                            /* Scroll Up */
512                                         {
513                                                 ClipBlit (rp, lv->GBox.Left, lv->GBox.Top,
514                                                         rp, lv->GBox.Left, lv->GBox.Top - scroll_dy,
515                                                         lv->GBox.Width, scroll_height + scroll_dy,
516                                                         0x0C0);
517
518
519                                                         if (lv->ClipRegion)
520                                                                 RedrawItems (lv, msg,
521                                                                         lv->PixelTop / (lv->ItemHeight + lv->Spacing),
522                                                                         lv->OldPixelTop / (lv->ItemHeight + lv->Spacing),
523                                                                         NULL);
524                                                         else
525                                                                 RedrawItems (lv, msg,
526                                                                         lv->Top,
527                                                                         lv->OldTop - 1,
528                                                                         lv->TopPtr);
529                                         }
530
531
532                                         /* Some layers magic adapded from "MUI.undoc",
533                                          * by Alessandro Zummo <azummo@ita.flashnet.it>
534                                          */
535                                         #define LayerCovered(l) \
536                                                 ((!(l)->ClipRect) || memcmp (&(l)->ClipRect->bounds, \
537                                                 &(l)->bounds, sizeof (struct Rectangle)))
538                                         #define LayerDamaged(l) \
539                                                 ((l)->DamageList && (l)->DamageList->RegionRectangle)
540                                         #define NeedZeroScrollRaster(l) (LayerCovered(l) || LayerDamaged(l))
541
542
543                                         /* This will scroll the layer damage regions without actually
544                                          * scrolling the display, but only if our layer really needs it.
545                                          */
546                                         if ((rp->Layer->Flags & LAYERSIMPLE) && NeedZeroScrollRaster (rp->Layer))
547                                         {
548                                                 UBYTE oldmask = rp->Mask; /* Would GetRPAttr() be better? */
549
550                                                 DB (kprintf ("  Calling ScrollRaster()\n");)
551 #ifdef OS30_ONLY
552                                                 SetWriteMask (rp, 0);
553 #else
554                                                 SafeSetWriteMask (rp, 0);
555 #endif  /* OS30_ONLY */
556                                                 ScrollRaster (rp, 0, scroll_dy,
557                                                         lv->GRect.MinX, lv->GRect.MinY,
558                                                         lv->GRect.MaxX,
559                                                         lv->GRect.MaxY);
560
561 #ifdef OS30_ONLY
562                                                 SetWriteMask (rp, oldmask);
563 #else
564                                                 SafeSetWriteMask (rp, oldmask);
565 #endif  /* OS30_ONLY */
566                                         }
567
568 #ifndef OS30_ONLY
569                                         if (GfxBase->LibNode.lib_Version >= 39)
570 #endif /* OS30_ONLY */
571                                                 /* Restore MaxPen in our RastPort */
572                                                 SetMaxPen (rp, -1);
573                                 }
574
575                                 /* Update OldTop to the current Top item and
576                                  * OldPixelTop to the current PixelTop position.
577                                  */
578                                 lv->OldTop = lv->Top;
579                                 lv->OldPixelTop = lv->PixelTop;
580
581                                 break;
582                         }
583
584                         default:
585                                 break;
586                 }
587
588                 if (lv->ClipRegion)
589                 {
590                         ASSERT_VALIDNO0(oldregion)
591                         /* Restore old clipping region in our layer */
592                         InstallClipRegion (rp->Layer, oldregion);
593                 }
594
595                 if (oldfont)
596                         SetFont (rp, oldfont);
597         }
598         else if (msg->gpr_Redraw == GREDRAW_REDRAW)
599         {
600                 /* Clear all gadget contents */
601                 SetAPen (rp, msg->gpr_GInfo->gi_DrInfo->dri_Pens[BACKGROUNDPEN]);
602                 RectFill (rp, lv->GRect.MinX, lv->GRect.MinY, lv->GRect.MaxX, lv->GRect.MaxY);
603         }
604 }
605
606
607
608 static ULONG LV_GMGoActive (Class *cl, struct Gadget *g, struct gpInput *msg)
609 {
610         struct LVData           *lv = INST_DATA (cl, g);
611
612         ASSERT_VALIDNO0(lv)
613         DB (kprintf ("GM_GOACTIVE: gpi_IEvent = $%lx\n", msg->gpi_IEvent);)
614
615
616         if (!lv->Items)
617                 return GMR_NOREUSE;
618
619         g->Flags |= GFLG_SELECTED;
620
621         /* Do not process InputEvent when the gadget has been
622          * activated by ActivateGadget().
623          */
624         if (!msg->gpi_IEvent)
625                 return GMR_MEACTIVE;
626
627         /* Note: The input event that triggered the gadget
628          * activation (usually a mouse click) should be passed
629          * to the GM_HANDLEINPUT method, so we fall down to it.
630          */
631         return LV_GMHandleInput (cl, g, msg);
632 }
633
634
635
636 INLINE LONG ItemHit (struct LVData *lv, WORD x, WORD y)
637
638 /* Determine which item has been hit with gadget relative
639  * coordinates x and y.
640  */
641 {
642         return ((y + lv->PixelTop) / (lv->ItemHeight + lv->Spacing));
643 }
644
645
646
647 static ULONG LV_GMHandleInput (Class *cl, struct Gadget *g, struct gpInput *msg)
648 {
649         struct LVData           *lv = INST_DATA (cl, g);
650         struct InputEvent       *ie = msg->gpi_IEvent;
651         ULONG                            result = GMR_MEACTIVE;
652
653         ASSERT_VALIDNO0(lv)
654 /*      DB (kprintf ("GM_HANDLEINPUT: ie_Class = $%lx, ie->ie_Code = $%lx, "
655                 "gpi_Mouse.X = %ld, gpi_Mouse.Y = %ld\n",
656                 ie->ie_Class, ie->ie_Code, msg->gpi_Mouse.X, msg->gpi_Mouse.Y);)
657 */
658         switch (ie->ie_Class)
659         {
660                 case IECLASS_RAWKEY:
661                 {
662                         LONG tags[5];
663                         LONG pos;
664
665                         switch (ie->ie_Code)
666                         {
667                                 case CURSORUP:
668                                         if ((lv->Flags & LVF_READONLY) || (ie->ie_Qualifier & IEQUALIFIER_CONTROL))
669                                         {
670                                                 if (ie->ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
671                                                         pos = lv->Top - lv->Visible / 2;
672                                                 else
673                                                         pos = lv->Top - 1;
674
675                                                 if (pos < 0) pos = 0;
676
677                                                 tags[0] = LVA_Top;
678                                                 tags[1] = pos;
679                                                 tags[2] = TAG_DONE;
680                                         }
681                                         else
682                                         {
683                                                 if (ie->ie_Qualifier & (IEQUALIFIER_LALT | IEQUALIFIER_RALT))
684                                                         pos = 0;
685                                                 else if (ie->ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
686                                                         pos = lv->Selected - lv->Visible + 1;
687                                                 else
688                                                         pos = lv->Selected - 1;
689
690                                                 if (pos < 0) pos = 0;
691
692                                                 tags[0] = LVA_Selected;
693                                                 tags[1] = pos;
694                                                 tags[2] = LVA_MakeVisible;
695                                                 tags[3] = pos;
696                                                 tags[4] = TAG_DONE;
697                                         }
698                                         break;
699
700                                 case CURSORDOWN:
701                                         if ((lv->Flags & LVF_READONLY) || (ie->ie_Qualifier & IEQUALIFIER_CONTROL))
702                                         {
703                                                 if (ie->ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
704                                                         pos = lv->Top + lv->Visible / 2;
705                                                 else
706                                                         pos = lv->Top + 1;
707
708                                                 tags[0] = LVA_Top;
709                                                 tags[1] = pos;
710                                                 tags[2] = TAG_DONE;
711                                         }
712                                         else
713                                         {
714                                                 if (ie->ie_Qualifier & (IEQUALIFIER_LALT | IEQUALIFIER_RALT))
715                                                         pos = lv->Total - 1;
716                                                 else if (ie->ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
717                                                         pos = lv->Selected + lv->Visible - 1;
718                                                 else
719                                                         pos = lv->Selected + 1;
720
721                                                 tags[0] = LVA_Selected;
722                                                 tags[1] = pos;
723                                                 tags[2] = LVA_MakeVisible;
724                                                 tags[3] = pos;
725                                                 tags[4] = TAG_DONE;
726                                         }
727                                         break;
728
729                                 default:
730                                         tags[0] = TAG_DONE;
731
732                         } /* End switch (ie->ie_Code) */
733
734                         if (tags[0] != TAG_DONE)
735                                 DoMethod ((Object *)g, OM_UPDATE, tags, msg->gpi_GInfo,
736                                         (ie->ie_Qualifier & IEQUALIFIERB_REPEAT) ? OPUF_INTERIM : 0);
737
738                         break;
739                 }
740
741                 case IECLASS_RAWMOUSE:
742                 {
743                         LONG selected;
744
745                         switch (ie->ie_Code)
746                         {
747                                 case SELECTDOWN:
748
749                                         /* Check for click outside gadget box */
750
751                                         if ((msg->gpi_Mouse.X < 0) ||
752                                                 (msg->gpi_Mouse.X >= lv->GBox.Width) ||
753                                                 (msg->gpi_Mouse.Y < 0) ||
754                                                 (msg->gpi_Mouse.Y >= lv->GBox.Height))
755                                         {
756                                                 result = GMR_REUSE;
757                                                 break;
758                                         }
759
760                                         /* Start dragging mode */
761                                         lv->Flags |= LVF_DRAGGING;
762
763                                         if (lv->Flags & LVF_READONLY)
764                                                 break;
765
766                                         /* Select an item */
767                                         selected = ItemHit (lv, msg->gpi_Mouse.X, msg->gpi_Mouse.Y);
768
769                                         /* No action when selecting over blank space in the bottom */
770                                         if ((selected < 0) || (selected >= lv->Total))
771                                                 break;
772
773                                         /* Backup current selection for RMB undo */
774                                         lv->BackupSelected = lv->Selected;
775                                         lv->BackupPixelTop = lv->PixelTop;
776
777                                         if (selected == lv->Selected)
778                                         {
779                                                 /* Check for double click */
780                                                 if (DoubleClick (lv->DoubleClickSecs, lv->DoubleClickMicros,
781                                                         ie->ie_TimeStamp.tv_secs, ie->ie_TimeStamp.tv_micro))
782                                                         UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
783                                                                 LVA_DoubleClick, selected,
784                                                                 TAG_DONE);
785                                         }
786
787                                         if (lv->Flags & LVF_DOMULTISELECT)
788                                                 /* Setup for multiple items drag selection */
789                                                 lv->DragSelect = IsItemSelected (lv, NULL, selected) ?
790                                                         LVA_DeselectItem : LVA_SelectItem;
791                                         else if (g->Activation & GACT_TOGGLESELECT)
792                                         {
793                                                 /* Setup for single item toggle */
794                                                 lv->DragSelect = LVA_Selected;
795                                                 if (selected == lv->Selected)
796                                                         selected = ~0;
797                                         }
798                                         else /* Single selection */
799                                                 /* Setup for single item drag selection */
800                                                 lv->DragSelect = LVA_Selected;
801
802                                         UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
803                                                 lv->DragSelect, selected,
804                                                 TAG_DONE);
805
806                                         /* Save double click info */
807                                         lv->DoubleClickSecs = ie->ie_TimeStamp.tv_secs;
808                                         lv->DoubleClickMicros = ie->ie_TimeStamp.tv_micro;
809                                         break;
810
811                                 case MENUDOWN:
812                                         /* Undo selection & position when RMB is pressed */
813                                         if (lv->Flags & (LVF_DRAGGING | LVF_SCROLLING))
814                                         {
815                                                 /* Stop dragging and scrolling modes */
816                                                 lv->Flags &= ~(LVF_DRAGGING | LVF_SCROLLING);
817
818                                                 if ((lv->BackupSelected != lv->Selected) ||
819                                                         (lv->BackupPixelTop != lv->PixelTop))
820                                                 {
821                                                         UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
822                                                                 (lv->Flags & LVF_READONLY) ?
823                                                                         TAG_IGNORE : LVA_Selected, lv->BackupSelected,
824                                                                 LVA_PixelTop, lv->BackupPixelTop,
825                                                                 TAG_DONE);
826                                                 }
827                                         }
828                                         else
829                                                 /* Deactivate gadget on menu button press */
830                                                 result = GMR_REUSE;
831
832                                         break;
833
834                                 case MIDDLEDOWN:
835                                         /* Argh, input.device never sends this event in V40! */
836                                         DB (kprintf ("scrolling on\n");)
837
838                                         /* Start MMB scrolling */
839                                         lv->BackupPixelTop = lv->PixelTop;
840                                         lv->BackupSelected = lv->Selected;
841                                         lv->MiddleMouseY = msg->gpi_Mouse.Y;
842                                         lv->Flags |= LVF_DRAGGING;
843                                         break;
844
845                                 case SELECTUP:
846
847                                         /* Stop dragging mode */
848                                         lv->Flags &= ~LVF_DRAGGING;
849
850                                         if (g->Activation & GACT_RELVERIFY)
851                                         {
852                                                 /* Send IDCMP_GADGETUP message to our parent window */
853                                                 msg->gpi_Termination = &lv->Selected;
854                                                 result = GMR_NOREUSE | GMR_VERIFY;
855                                         }
856                                         break;
857
858                                 case MIDDLEUP:
859                                         /* Argh, input.device never sends this event in V40! */
860                                         DB (kprintf ("scrolling off\n");)
861
862                                         /* Stop MMB scrolling */
863                                         lv->Flags &= ~LVF_SCROLLING;
864                                         break;
865
866                                 default: /* Mouse moved */
867
868                                         /* Holding LMB? */
869                                         if (lv->Flags & LVF_DRAGGING)
870                                         {
871                                                 /* Select an item */
872                                                 selected = ItemHit (lv, msg->gpi_Mouse.X, msg->gpi_Mouse.Y);
873
874                                                 /* Moved over another item inside the currently displayed list? */
875                                                 if ((selected != lv->Selected) && !(lv->Flags & LVF_READONLY)
876                                                         && (selected >= lv->Top) && (selected < lv->Top + lv->Visible))
877                                                 {
878                                                         /* Single selection */
879
880                                                         /* Call our OM_UPDATE method to change the attributes.
881                                                          * This will also send notification to targets and
882                                                          * update the contents of the gadget.
883                                                          */
884                                                         UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
885                                                                 lv->DragSelect, selected,
886                                                                 TAG_DONE);
887                                                 }
888                                         }
889
890                                         /* Holding MMB? */
891                                         if (lv->Flags & LVF_SCROLLING)
892                                         {
893                                                 DB (kprintf ("  scrolling\n");)
894                                                 selected = (msg->gpi_Mouse.Y - lv->MiddleMouseY)
895                                                         + lv->BackupPixelTop;
896
897                                                 UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
898                                                         LVA_PixelTop, selected < 0 ? 0 : selected,
899                                                         TAG_DONE);
900                                         }
901
902                         } /* End switch (ie->ie_Code) */
903
904                         break;
905                 }
906
907                 case IECLASS_TIMER:
908
909                         /* Holding LMB? */
910                         if (lv->Flags & LVF_DRAGGING)
911                         {
912                                 /* Mouse above the upper item? */
913                                 if ((msg->gpi_Mouse.Y < 0) && lv->Top)
914                                 {
915                                         /* Scroll up */
916                                         UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
917                                                 LVA_MoveUp,     1,
918                                                 (lv->Flags & LVF_READONLY) ? TAG_IGNORE : LVA_Selected, lv->Top - 1,
919                                                 TAG_DONE);
920                                 }
921                                 /* Mouse below the bottom item? */
922                                 else if (msg->gpi_Mouse.Y / (lv->ItemHeight + lv->Spacing) >= lv->Visible)
923                                 {
924                                         /* Scroll down */
925                                         UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
926                                                 LVA_MoveDown,           1,
927                                                 (lv->Flags & LVF_READONLY) ? TAG_IGNORE : LVA_Selected, lv->Top + lv->Visible,
928                                                 TAG_DONE);
929                                 }
930                         }
931                         break;
932
933                 default:
934                         ;
935
936         } /* End switch (ie->ie_Class) */
937
938         return result;
939 }
940
941
942
943 static void LV_GMGoInactive (Class *cl, struct Gadget *g, struct gpGoInactive *msg)
944 {
945         struct LVData           *lv = INST_DATA (cl, g);
946         ASSERT_VALIDNO0(lv)
947
948         DB (kprintf ("GM_GOINACTIVE\n");)
949
950         /* Stop dragging and scrolling modes */
951         lv->Flags &= ~(LVF_DRAGGING | LVF_SCROLLING);
952
953         /* Mark gadget inactive */
954         g->Flags &= ~GFLG_SELECTED;
955 }
956
957
958
959 INLINE void GetGadgetBox (struct GadgetInfo *ginfo, struct ExtGadget *g, struct IBox *box, struct Rectangle *rect)
960
961 /* Gets the actual IBox where a gadget exists in a window.
962  * The special cases it handles are all the REL#? (relative positioning flags).
963  *
964  * This function returns the gadget size in both the provided IBox and
965  * Rectangle structures, computing the values from the coordinates of the
966  * gadget and the window where it lives.
967  */
968 {
969         ASSERT_VALIDNO0(g)
970         ASSERT_VALIDNO0(ginfo)
971         ASSERT_VALIDNO0(box)
972         ASSERT_VALIDNO0(rect)
973
974         DB (if ((g->Flags & GFLG_EXTENDED) && (g->MoreFlags & GMORE_BOUNDS))
975                 kprintf ("  Gadget has valid bounds\n");)
976
977         box->Left = g->LeftEdge;
978         if (g->Flags & GFLG_RELRIGHT)
979                 box->Left += ginfo->gi_Domain.Width - 1;
980
981         box->Top = g->TopEdge;
982         if (g->Flags & GFLG_RELBOTTOM)
983                 box->Top += ginfo->gi_Domain.Height - 1;
984
985         box->Width = g->Width;
986         if (g->Flags & GFLG_RELWIDTH)
987                 box->Width += ginfo->gi_Domain.Width;
988
989         box->Height = g->Height;
990         if (g->Flags & GFLG_RELHEIGHT)
991                 box->Height += ginfo->gi_Domain.Height;
992
993         /* Convert IBox to Rectangle coordinates system */
994         rect->MinX = box->Left;
995         rect->MinY = box->Top;
996         rect->MaxX = box->Left + box->Width - 1;
997         rect->MaxY = box->Top + box->Height - 1;
998 }
999
1000
1001
1002 static void LV_GMLayout (Class *cl, struct Gadget *g, struct gpLayout *msg)
1003 {
1004         struct LVData *lv = INST_DATA (cl, g);
1005         LONG visible;
1006
1007         DB (kprintf ("GM_LAYOUT\n");)
1008         ASSERT_VALIDNO0(lv)
1009         ASSERT_VALIDNO0(msg->gpl_GInfo)
1010         ASSERT_VALIDNO0(msg->gpl_GInfo->gi_DrInfo)
1011         ASSERT_VALIDNO0(msg->gpl_GInfo->gi_DrInfo->dri_Font)
1012
1013
1014         /* We shouldn't draw inside the GM_LAYOUT method: the
1015          * GM_REDRAW method will be called by Intuition shortly after.
1016          */
1017         lv->Flags |= LVF_DONTDRAW;
1018
1019         /* Collect new gadget size */
1020         GetGadgetBox (msg->gpl_GInfo, (struct ExtGadget *)g, &lv->GBox, &lv->GRect);
1021
1022         /* Calculate clipping region for gadget LVA_Clipped mode */
1023         if (lv->ClipRegion)
1024         {
1025                 /* Remove previous clipping rectangle, if any */
1026                 ClearRegion (lv->ClipRegion);
1027
1028                 /* Install a clipping rectangle around the gadget box.
1029                  * We don't check for failure because we couldn't do
1030                  * anything otherwise.
1031                  */
1032                 OrRectRegion (lv->ClipRegion, &lv->GRect);
1033         }
1034
1035         /* Setup Font if not yet done */
1036         if (!lv->Font)
1037         {
1038                 lv->Font = msg->gpl_GInfo->gi_DrInfo->dri_Font;
1039                 if (!lv->ItemHeight)
1040                         lv->ItemHeight = lv->Font->tf_YSize;
1041         }
1042
1043         if (lv->ItemHeight)
1044         {
1045                 if (lv->ClipRegion)
1046                         /* Allow displaying an incomplete item at the bottom of the listview,
1047                          * plus one incomplete item at the top.
1048                          */
1049                         visible = (lv->GBox.Height + lv->ItemHeight + lv->Spacing - 1) /
1050                                 (lv->ItemHeight + lv->Spacing);
1051                 else
1052                         /* get maximum number of items fitting in the listview height.
1053                          * Ignore spacing for the last visible item.
1054                          */
1055                         visible = (lv->GBox.Height + lv->Spacing) / (lv->ItemHeight + lv->Spacing);
1056         }
1057         else
1058                 visible = 0;
1059
1060         lv->MaxScroll = lv->GBox.Height / lv->ScrollRatio;
1061
1062
1063         /* Send initial notification to our sliders, or update them to
1064          * the new values. The slieders will get the correct size also
1065          * in the special case where the list is attached at creation
1066          * time and the sliders are attached later using a model object.
1067          *
1068          * The private class attribute LVA_Visible will handle everything for us.
1069          */
1070         UpdateAttrs ((Object *)g, msg->gpl_GInfo, 0,
1071                 LVA_Visible,    visible,
1072                 TAG_DONE);
1073
1074         /* Re-enable drawing */
1075         lv->Flags &= ~LVF_DONTDRAW;
1076 }
1077
1078
1079
1080 static ULONG LV_OMSet (Class *cl, struct Gadget *g, struct opUpdate *msg)
1081 {
1082         struct LVData   *lv = INST_DATA (cl, g);
1083         struct TagItem  *ti,
1084                                         *tstate = msg->opu_AttrList;
1085         ULONG   result;
1086         UWORD   action = 0;     /* See flag definitions above */
1087
1088         ASSERT_VALIDNO0(lv)
1089         ASSERT_VALID(tstate)
1090
1091         DB (kprintf ((msg->MethodID == OM_SET) ? "OM_SET:\n" : "OM_UPDATE:\n");)
1092
1093
1094         /* Definitions for the ations to be taken right after
1095          * scanning the attributes list in OM_SET/OM_UPDATE.
1096          * For speed reasons we pack them together in a single variable,
1097          * so we can set and test multiple flags in once.
1098          */
1099         #define LVF_DO_SUPER_METHOD     (1<<0)
1100         #define LVF_REDRAW                      (1<<1)
1101         #define LVF_SCROLL                      (1<<2)
1102         #define LVF_TOGGLESELECT        (1<<3)
1103         #define LVF_NOTIFY                      (1<<4)
1104         #define LVF_NOTIFYALL           (1<<5)
1105
1106
1107         while (ti = NextTagItem (&tstate))
1108                 switch (ti->ti_Tag)
1109                 {
1110                         case GA_ID:
1111                                 DB (kprintf ("  GA_ID, %ld\n", ti->ti_Data);)
1112
1113                                 /* Avoid sending all taglists to our superclass because of GA_ID */
1114                                 g->GadgetID = ti->ti_Data;
1115                                 break;
1116
1117                         case LVA_Selected:
1118                                 DB (kprintf ("  LVA_Selected, %ld\n", ti->ti_Data);)
1119
1120                                 if (lv->Items)
1121                                 {
1122                                         LONG newselected = ti->ti_Data;
1123
1124                                         if (newselected != ~0)
1125                                                 newselected = (newselected >= lv->Total) ?
1126                                                         (lv->Total - 1) : newselected;
1127
1128                                         if (lv->Selected != newselected)
1129                                         {
1130                                                 if (((lv->Selected >= lv->Top) &&
1131                                                         (lv->Selected < lv->Top + lv->Visible)) ||
1132                                                         ((newselected >= lv->Top) &&
1133                                                         (newselected < lv->Top + lv->Visible)))
1134                                                         action |= LVF_TOGGLESELECT;
1135
1136                                                 lv->Selected = newselected;
1137
1138                                                 if (newselected == ~0)
1139                                                         lv->SelectedPtr = NULL;
1140                                                 else
1141                                                         lv->SelectedPtr = GetItem (lv, newselected);
1142
1143                                                 action |= LVF_NOTIFY;
1144                                         }
1145                                 }
1146                                 break;
1147
1148                         case LVA_Top:
1149                                 DB (kprintf ("  LVA_Top, %ld\n", ti->ti_Data);)
1150
1151                                 if ((lv->Top != ti->ti_Data) && lv->Items)
1152                                 {
1153                                         /* This will scroll the listview contents when needed */
1154
1155                                         lv->Top = ((ti->ti_Data + lv->Visible) >= lv->Total) ?
1156                                                 ((lv->Total <= lv->Visible) ? 0 : (lv->Total - lv->Visible))
1157                                                 : ti->ti_Data;
1158                                         lv->PixelTop = lv->Top * (lv->ItemHeight + lv->Spacing);
1159
1160                                         /* TODO: optimize for some special cases:
1161                                          * Top == oldtop + 1 and Top == oldtop - 1
1162                                          */
1163                                         lv->TopPtr = GetItem (lv, lv->Top);
1164                                         action |= LVF_SCROLL | LVF_NOTIFY;
1165                                 }
1166                                 break;
1167
1168                         case LVA_Total:
1169                                 DB (kprintf ("  LVA_Total, %ld\n", ti->ti_Data);)
1170
1171                                 /* We don't hhandle LVA_Total except when setting a new
1172                                  * list or array of items.
1173                                  */
1174                                 break;
1175
1176                         case LVA_SelectItem:
1177                                 DB (kprintf ("  LVA_SelectItem, %ld\n", ti->ti_Data);)
1178
1179                                 /* Check LVA_MaxSelect */
1180                                 if (lv->SelectCount >= lv->MaxSelect)
1181                                         DisplayBeep (msg->opu_GInfo ? msg->opu_GInfo->gi_Screen : NULL);
1182                                 else if (lv->Items)
1183                                 {
1184                                         LONG newselected = (ti->ti_Data >= lv->Total) ?
1185                                                 (lv->Total - 1) : ti->ti_Data;
1186
1187                                         if (((lv->Selected >= lv->Top) &&
1188                                                 (lv->Selected < lv->Top + lv->Visible)) ||
1189                                                 ((newselected >= lv->Top) &&
1190                                                 (newselected < lv->Top + lv->Visible)))
1191                                                 action |= LVF_TOGGLESELECT;
1192
1193                                         lv->Selected = newselected;
1194                                         lv->SelectedPtr = GetItem (lv, newselected);
1195
1196                                         if (!IsItemSelected (lv, lv->SelectedPtr, newselected))
1197                                         {
1198                                                 lv->SelectCount++;
1199
1200                                                 if (lv->SelectArray)
1201                                                         lv->SelectArray[newselected] = lv->SelectCount;
1202                                                 else if (lv->Flags & LVF_LIST)
1203                                                         ((struct Node *)lv->SelectedPtr)->ln_Type = lv->SelectCount;
1204                                         }
1205                                         action |= LVF_NOTIFY;
1206                                 }
1207                                 break;
1208
1209                         case LVA_DeselectItem:
1210                                 DB (kprintf ("  LVA_DeselectItem, %ld\n", ti->ti_Data);)
1211
1212                                 if (lv->Items)
1213                                 {
1214                                         LONG newselected = (ti->ti_Data >= lv->Total) ?
1215                                                 (lv->Total - 1) : ti->ti_Data;
1216
1217                                         if (((lv->Selected >= lv->Top) &&
1218                                                 (lv->Selected < lv->Top + lv->Visible)) ||
1219                                                 ((newselected >= lv->Top) &&
1220                                                 (newselected < lv->Top + lv->Visible)))
1221                                                 action |= LVF_TOGGLESELECT;
1222
1223                                         lv->Selected = newselected;
1224                                         lv->SelectedPtr = GetItem (lv, newselected);
1225
1226                                         if (IsItemSelected (lv, lv->SelectedPtr, newselected))
1227                                         {
1228                                                 lv->SelectCount--;
1229
1230                                                 if (lv->SelectArray)
1231                                                         lv->SelectArray[lv->Selected] = 0;
1232                                                 else if (lv->Flags & LVF_LIST)
1233                                                         ((struct Node *)lv->SelectedPtr)->ln_Type = 0;
1234
1235                                                 action |= LVF_NOTIFY;
1236                                         }
1237                                 }
1238                                 break;
1239
1240                         case LVA_ToggleItem:
1241                                 DB (kprintf ("  LVA_ToggleItem, %ld\n", ti->ti_Data);)
1242
1243                                 if (lv->Items)
1244                                 {
1245                                         LONG newselected = newselected = (ti->ti_Data >= lv->Total) ?
1246                                                 (lv->Total - 1) : ti->ti_Data;
1247
1248                                         if (((lv->Selected >= lv->Top) &&
1249                                                 (lv->Selected < lv->Top + lv->Visible)) ||
1250                                                 ((newselected >= lv->Top) &&
1251                                                 (newselected < lv->Top + lv->Visible)))
1252                                                 action |= LVF_TOGGLESELECT;
1253
1254                                         lv->Selected = newselected;
1255                                         lv->SelectedPtr = GetItem (lv, newselected);
1256
1257                                         if (IsItemSelected (lv, lv->SelectedPtr, lv->Selected))
1258                                         {
1259                                                 /* Deselect */
1260                                                 lv->SelectCount--;
1261
1262                                                 if (lv->SelectArray)
1263                                                         lv->SelectArray[lv->Selected] = 0;
1264                                                 else if (lv->Flags & LVF_LIST)
1265                                                         ((struct Node *)lv->SelectedPtr)->ln_Type = 0;
1266                                         }
1267                                         else
1268                                         {
1269                                                 /* Check LVA_MaxSelect */
1270                                                 if (lv->SelectCount >= lv->MaxSelect)
1271                                                         DisplayBeep (msg->opu_GInfo ? msg->opu_GInfo->gi_Screen : NULL);
1272                                                 else
1273                                                 {
1274                                                         /* Select */
1275                                                         lv->SelectCount++;
1276
1277                                                         if (lv->SelectArray)
1278                                                                 lv->SelectArray[lv->Selected] = lv->SelectCount;
1279                                                         else if (lv->Flags & LVF_LIST)
1280                                                                 ((struct Node *)lv->SelectedPtr)->ln_Type = lv->SelectCount;
1281                                                 }
1282                                         }
1283
1284                                         action |= LVF_NOTIFY;
1285                                 }
1286                                 break;
1287
1288                         case LVA_ClearSelected:
1289                                 DB (kprintf ("  LVA_ClearSelected, %ld\n", ti->ti_Data);)
1290
1291                                 if (lv->Items)
1292                                 {
1293                                         LONG newselected = ti->ti_Data;
1294                                         LONG i;
1295
1296                                         if (((lv->Selected >= lv->Top) &&
1297                                                 (lv->Selected < lv->Top + lv->Visible)) ||
1298                                                 ((newselected >= lv->Top) &&
1299                                                 (newselected < lv->Top + lv->Visible)))
1300                                                 action |= LVF_TOGGLESELECT;
1301
1302                                         lv->Selected = ~0;
1303                                         lv->SelectedPtr = NULL;
1304                                         lv->SelectCount = 0;
1305
1306
1307                                         /* Clear the selections */
1308
1309                                         if (lv->SelectArray)
1310                                                 for (i = 0; i < lv->Total; i++)
1311                                                         lv->SelectArray[i] = 0;
1312                                         else if (lv->Flags & LVF_LIST)
1313                                         {
1314                                                 struct Node *node;
1315
1316                                                 for (node = ((struct List *)lv->Items)->lh_Head;
1317                                                         node = node->ln_Succ;
1318                                                         node->ln_Type = 0)
1319                                                         ASSERT_VALID(node);
1320                                         }
1321
1322                                         /* TODO: check if total redraw is really needed */
1323                                         action |= LVF_REDRAW | LVF_NOTIFY;
1324                                 }
1325                                 break;
1326
1327                         case LVA_MakeVisible:
1328                         {
1329                                 LONG itemnum = ti->ti_Data;
1330
1331                                 DB (kprintf ("  LVA_MakeVisible, %ld\n", ti->ti_Data);)
1332
1333                                 if (itemnum < 0)
1334                                         itemnum = 0;
1335
1336                                 if (itemnum >= lv->Total)
1337                                         itemnum = lv->Total - 1;
1338
1339                                 if (itemnum < lv->Top)
1340                                 {
1341                                         /* Scroll up */
1342
1343                                         lv->Top = itemnum;
1344                                         lv->TopPtr = GetItem (lv, lv->Top);
1345                                         action |= LVF_SCROLL | LVF_NOTIFY;
1346                                 }
1347                                 else if (itemnum >= lv->Top + lv->Visible)
1348                                 {
1349                                         /* Scroll down */
1350
1351                                         lv->Top = itemnum - lv->Visible + 1;
1352                                         lv->TopPtr = GetItem (lv, lv->Top);
1353                                         action |= LVF_SCROLL | LVF_NOTIFY;
1354                                 }
1355                                 break;
1356                         }
1357
1358                         case LVA_MoveUp:
1359                                 DB (kprintf ("  LVA_MoveUp, %ld\n", ti->ti_Data);)
1360
1361                                 if ((lv->Top > 0) && lv->Items)
1362                                 {
1363                                         lv->Top--;
1364                                         lv->TopPtr = GetPrev (lv, lv->TopPtr, lv->Top);
1365                                         action |= LVF_SCROLL | LVF_NOTIFY;
1366                                 }
1367                                 break;
1368
1369                         case LVA_MoveDown:
1370                                 DB (kprintf ("  LVA_MoveDown, %ld\n", ti->ti_Data);)
1371
1372                                 if ((lv->Top + lv->Visible < lv->Total) && lv->Items)
1373                                 {
1374                                         lv->Top++;
1375                                         lv->TopPtr = GetNext (lv, lv->TopPtr, lv->Top);
1376                                         action |= LVF_SCROLL | LVF_NOTIFY;
1377                                 }
1378                                 break;
1379
1380                         case LVA_MoveLeft:
1381                                 DB (kprintf ("  Unimplemented attr: LVA_MoveLeft\n");)
1382                                 break;
1383
1384                         case LVA_MoveRight:
1385                                 DB (kprintf ("  Unimplemented attr: LVA_MoveRight\n");)
1386                                 break;
1387
1388                         case LVA_StringList:
1389                                 DB (kprintf ("  LVA_StringList, $%lx\n", ti->ti_Data);)
1390
1391                                 if (ti->ti_Data == ~0)
1392                                         lv->Items = NULL;
1393                                 else
1394                                 {
1395                                         ASSERT_VALID(ti->ti_Data)
1396
1397                                         lv->Items = (void *) ti->ti_Data;
1398                                         lv->GetItemFunc         = ListGetItem;
1399                                         lv->GetNextFunc         = ListGetNext;
1400                                         lv->GetPrevFunc         = ListGetPrev;
1401                                         lv->DrawItemFunc        = ListStringDrawItem;
1402                                         lv->Flags |= LVF_LIST;
1403
1404                                         lv->Total = GetTagData (LVA_Total, ~0, msg->opu_AttrList);
1405                                         if (lv->Total == ~0)
1406                                                 lv->Total = CountNodes (lv->Items);
1407
1408                                         lv->SelectCount = CountSelections (lv);
1409
1410                                         action |= LVF_REDRAW | LVF_NOTIFYALL;
1411                                 }
1412                                 break;
1413
1414                         case LVA_StringArray:
1415                                 DB (kprintf ("  LVA_StringArray, $%lx\n", ti->ti_Data);)
1416
1417                                 if (ti->ti_Data == ~0)
1418                                         lv->Items = NULL;
1419                                 else
1420                                 {
1421                                         ASSERT_VALID(ti->ti_Data)
1422
1423                                         lv->Items = (void *) ti->ti_Data;
1424                                         lv->GetItemFunc         = ArrayGetItem;
1425                                         lv->GetNextFunc         = ArrayGetItem;
1426                                         lv->GetPrevFunc         = ArrayGetItem;
1427                                         lv->DrawItemFunc        = StringDrawItem;
1428                                         lv->Flags &= ~LVF_LIST;
1429
1430                                         lv->Total = GetTagData (LVA_Total, ~0, msg->opu_AttrList);
1431                                         if ((lv->Total == ~0) && lv->Items)
1432                                         {
1433                                                 /* Count items */
1434                                                 ULONG i = 0;
1435                                                 while (((APTR *)lv->Items)[i]) i++;
1436                                                 lv->Total = i;
1437                                         }
1438
1439                                         lv->SelectCount = CountSelections(lv);
1440
1441                                         action |= LVF_REDRAW | LVF_NOTIFYALL;
1442                                 }
1443                                 break;
1444
1445                         case LVA_ImageList:
1446                                 DB (kprintf ("  LVA_ImageList, $%lx\n", ti->ti_Data);)
1447
1448                                 if (ti->ti_Data == ~0)
1449                                         lv->Items = NULL;
1450                                 else
1451                                 {
1452                                         ASSERT_VALID(ti->ti_Data)
1453
1454                                         lv->Items = (void *) ti->ti_Data;
1455                                         lv->GetItemFunc         = ListGetItem;
1456                                         lv->GetNextFunc         = ListGetNext;
1457                                         lv->GetPrevFunc         = ListGetPrev;
1458                                         lv->DrawItemFunc        = ListImageDrawItem;
1459                                         lv->Flags |= LVF_LIST;
1460
1461                                         lv->Total = GetTagData (LVA_Total, ~0, msg->opu_AttrList);
1462                                         if (lv->Total == ~0)
1463                                                 lv->Total = CountNodes (lv->Items);
1464
1465                                         lv->SelectCount = CountSelections(lv);
1466
1467                                         action |= LVF_REDRAW | LVF_NOTIFYALL;
1468                                 }
1469                                 break;
1470
1471                         case LVA_ImageArray:
1472                                 DB (kprintf ("  LVA_ImageArray, $%lx\n", ti->ti_Data);)
1473
1474                                 if (ti->ti_Data == ~0)
1475                                         lv->Items = NULL;
1476                                 else
1477                                 {
1478                                         ASSERT_VALID(ti->ti_Data)
1479
1480                                         lv->Items = (void *) ti->ti_Data;
1481                                         lv->GetItemFunc         = ArrayGetItem;
1482                                         lv->GetNextFunc         = ArrayGetItem;
1483                                         lv->GetPrevFunc         = ArrayGetItem;
1484                                         lv->DrawItemFunc        = ImageDrawItem;
1485                                         lv->Flags &= ~LVF_LIST;
1486
1487                                         lv->Total = GetTagData (LVA_Total, ~0, msg->opu_AttrList);
1488                                         if ((lv->Total == ~0) && lv->Items)
1489                                         {
1490                                                 /* Count items */
1491                                                 ULONG i = 0;
1492                                                 while (((APTR *)lv->Items)[i]) i++;
1493                                                 lv->Total = i;
1494                                         }
1495
1496                                         action |= LVF_REDRAW | LVF_NOTIFYALL;
1497                                 }
1498                                 break;
1499
1500                         case LVA_CustomList:
1501                                 DB (kprintf ("  LVA_CustomList, $%lx\n", ti->ti_Data);)
1502
1503                                 if (ti->ti_Data == ~0)
1504                                         lv->Items = NULL;
1505                                 else
1506                                 {
1507                                         ASSERT_VALID(ti->ti_Data)
1508
1509                                         lv->Items = (void *) ti->ti_Data;
1510                                         lv->SelectCount = CountSelections (lv);
1511
1512                                         action |= LVF_REDRAW | LVF_NOTIFYALL;
1513                                 }
1514                                 break;
1515
1516                         case LVA_Visible:
1517                                 DB (kprintf ("  LVA_Visible, %ld\n", ti->ti_Data);)
1518
1519                                 /* This attribute can only be set internally, and will
1520                                  * trigger a full slider notification.
1521                                  */
1522                                 lv->Visible = ti->ti_Data;
1523                                 action |= LVF_NOTIFYALL;
1524
1525
1526                                 /* Also scroll the ListView if needed. */
1527                                 if (lv->ClipRegion)
1528                                 {
1529                                         LONG height = lv->Total * (lv->ItemHeight + lv->Spacing);
1530                                         LONG newtop;
1531
1532                                         if (lv->PixelTop + lv->GBox.Height >= height)
1533                                         {
1534                                                 lv->PixelTop = height - lv->GBox.Height;
1535                                                 if (lv->PixelTop < 0)
1536                                                         lv->PixelTop = 0;
1537
1538                                                 newtop = lv->PixelTop / (lv->ItemHeight + lv->Spacing);
1539                                                 if (newtop != lv->Top)
1540                                                 {
1541                                                         lv->Top = newtop;
1542                                                         lv->TopPtr = GetItem (lv, newtop);
1543                                                 }
1544                                                 action |= LVF_SCROLL;
1545                                         }
1546                                 }
1547                                 else if (lv->Top + lv->Visible >= lv->Total)
1548                                 {
1549                                         lv->Top = (lv->Total <= lv->Visible) ? 0 : (lv->Total - lv->Visible);
1550                                         lv->TopPtr = GetItem (lv, lv->Top);
1551                                         lv->PixelTop = lv->Top * (lv->ItemHeight + lv->Spacing);
1552                                         action |= LVF_SCROLL;
1553                                 }
1554                                 break;
1555
1556                         case LVA_SelectArray:
1557                                 DB (kprintf ("  LVA_SelectArray, $%lx\n", ti->ti_Data);)
1558                                 ASSERT_VALID(ti->ti_Data)
1559
1560                                 lv->SelectArray = (ULONG *) ti->ti_Data;
1561                                 lv->SelectCount = CountSelections (lv);
1562                                 action |= LVF_REDRAW;
1563                                 break;
1564
1565                         case LVA_MaxSelect:
1566                                 DB (kprintf ("  LVA_MaxSelect, %ld\n", ti->ti_Data);)
1567
1568                                 lv->MaxSelect = ti->ti_Data;
1569                                 /* NOTE: We are not checking lv->SelectCount */
1570                                 break;
1571
1572                         case LVA_PixelTop:      /* Handle pixel-wise scrolling */
1573                                 DB (kprintf ("  LVA_PixelTop, %ld\n", ti->ti_Data);)
1574
1575                                 if (ti->ti_Data != lv->PixelTop && lv->Items && lv->ItemHeight)
1576                                 {
1577                                         LONG newtop;
1578
1579                                         lv->PixelTop = ti->ti_Data;
1580                                         action |= LVF_SCROLL;
1581
1582                                         newtop = lv->PixelTop / (lv->ItemHeight + lv->Spacing);
1583                                         newtop = ((newtop + lv->Visible) >= lv->Total) ?
1584                                                 ((lv->Total <= lv->Visible) ? 0 : (lv->Total - lv->Visible))
1585                                                 : newtop;
1586
1587                                         if (newtop != lv->Top)
1588                                         {
1589                                                 /* TODO: optimize GetItem for some special cases:
1590                                                  * Top = oldtop + 1 and Top = oldtop - 1
1591                                                  */
1592                                                 lv->Top = newtop;
1593                                                 lv->TopPtr = GetItem (lv, newtop);
1594                                                 action |= LVF_NOTIFY | LVF_SCROLL;
1595                                         }
1596                                 }
1597                                 break;
1598
1599                         case LVA_ScrollRatio:
1600                                 DB (kprintf ("  LVA_ScrollRatio, %ld\n", ti->ti_Data);)
1601                                 ASSERT(ti->ti_Data != 0)
1602
1603                                 lv->ScrollRatio = ti->ti_Data;
1604                                 lv->MaxScroll = lv->GBox.Height / lv->ScrollRatio;
1605                                 break;
1606
1607                         default:
1608                                 DB (kprintf ("  Passing unknown tag to superclass: $%lx, %ld\n",
1609                                         ti->ti_Tag, ti->ti_Data);)
1610
1611                                 /* This little optimization avoids forwarding the
1612                                  * OM_SET method to our superclass then there are
1613                                  * no unknown tags.
1614                                  */
1615                                 action |= LVF_DO_SUPER_METHOD;
1616                                 break;
1617                 }
1618
1619         DB(kprintf ("  TAG_DONE\n");)
1620
1621         /* Forward method to our superclass dispatcher, only if needed */
1622
1623         if (action & LVF_DO_SUPER_METHOD)
1624                 result = (DoSuperMethodA (cl, (Object *)g, (Msg) msg));
1625         else
1626                 result = TRUE;
1627
1628
1629         /* Update gadget imagery, only when needed */
1630
1631         if ((action & (LVF_REDRAW | LVF_SCROLL | LVF_TOGGLESELECT))
1632                 && msg->opu_GInfo && !(lv->Flags & LVF_DONTDRAW))
1633         {
1634                 struct RastPort *rp;
1635
1636                 if (rp = ObtainGIRPort (msg->opu_GInfo))
1637                 {
1638                         /* Just redraw everything */
1639                         if (action & LVF_REDRAW)
1640                                 DoMethod ((Object *)g, GM_RENDER, msg->opu_GInfo, rp, GREDRAW_REDRAW);
1641                         else
1642                         {
1643                                 /* Both these may happen at the same time */
1644
1645                                 if (action & LVF_SCROLL)
1646                                         DoMethod ((Object *)g, GM_RENDER, msg->opu_GInfo, rp,
1647                                                 GREDRAW_UPDATE);
1648
1649                                 if (action & LVF_TOGGLESELECT)
1650                                         DoMethod ((Object *)g, GM_RENDER, msg->opu_GInfo, rp,
1651                                                 GREDRAW_TOGGLE);
1652                         }
1653
1654                         ReleaseGIRPort (rp);
1655                 }
1656                 DB(else kprintf ("*** ObtainGIRPort() failed!\n");)
1657         }
1658
1659
1660         /* Notify our targets about changed attributes */
1661
1662         if (action & LVF_NOTIFYALL)
1663         {
1664                 DB(kprintf("OM_NOTIFY: ALL\n");)
1665                 DB(kprintf("  LVA_Top,           %ld\n", lv->Top);)
1666                 DB(kprintf("  LVA_Total,         %ld\n", lv->Total);)
1667                 DB(kprintf("  LVA_Visible,       %ld\n", lv->Visible);)
1668                 DB(kprintf("  LVA_Selected,      %ld\n", lv->Selected);)
1669                 DB(kprintf("  LVA_PixelTop,      %ld\n", lv->PixelTop);)
1670                 DB(kprintf("  LVA_PixelHeight,   %ld\n", lv->Total * (lv->ItemHeight + lv->Spacing));)
1671                 DB(kprintf("  LVA_PixelVVisible, %ld\n", lv->GBox.Height);)
1672                 DB(kprintf("  TAG_DONE\n");)
1673
1674                 NotifyAttrs ((Object *)g, msg->opu_GInfo,
1675                         (msg->MethodID == OM_UPDATE) ? msg->opu_Flags : 0,
1676                         LVA_Top,                        lv->Top,
1677                         LVA_Total,                      lv->Total,
1678                         LVA_Visible,            lv->Visible,
1679                         LVA_Selected,           lv->Selected,
1680                         LVA_PixelTop,           lv->PixelTop,
1681                         LVA_PixelHeight,        lv->Total * (lv->ItemHeight + lv->Spacing),
1682                         LVA_PixelVVisible,      lv->ClipRegion ?
1683                                                                         lv->GBox.Height :
1684                                                                         lv->Visible * (lv->ItemHeight + lv->Spacing),
1685                         GA_ID,                          g->GadgetID,
1686                         TAG_DONE);
1687         }
1688         else if (action & LVF_NOTIFY)
1689         {
1690                 IPTR tags[9];
1691                 int cnt = 0;
1692
1693                 if (action & LVF_SCROLL)
1694                 {
1695                         tags[0] = LVA_Top;                      tags[1] = lv->Top;
1696                         tags[2] = LVA_PixelTop;         tags[3] = lv->Top * (lv->ItemHeight + lv->Spacing);
1697                         cnt = 4;
1698                 }
1699
1700                 if (action & LVF_TOGGLESELECT)
1701                 {
1702                         tags[cnt++] = LVA_Selected;     tags[cnt++] = lv->Selected;
1703                 }
1704
1705                 tags[cnt++]     = GA_ID;
1706                 tags[cnt++]     = g->GadgetID;
1707                 tags[cnt]       = TAG_DONE;
1708
1709                 DoMethod ((Object *)g, OM_NOTIFY, tags, msg->opu_GInfo,
1710                         (msg->MethodID == OM_UPDATE) ? msg->opu_Flags : 0);
1711         }
1712
1713         return result;
1714 }
1715
1716
1717
1718 static ULONG LV_OMGet (Class *cl, struct Gadget *g, struct opGet *msg)
1719 {
1720         struct LVData *lv = INST_DATA (cl, g);
1721
1722         ASSERT_VALIDNO0(lv)
1723         ASSERT_VALIDNO0(msg->opg_Storage)
1724
1725         DB (kprintf ("OM_GET\n");)
1726
1727
1728         switch (msg->opg_AttrID)
1729         {
1730                 case LVA_Selected:
1731                         *msg->opg_Storage = (ULONG) lv->Selected;
1732                         return TRUE;
1733
1734                 case LVA_Top:
1735                         *msg->opg_Storage = (ULONG) lv->Top;
1736                         return TRUE;
1737
1738                 case LVA_Total:
1739                         *msg->opg_Storage = (ULONG) lv->Total;
1740                         return TRUE;
1741
1742                 case LVA_StringList:
1743                 case LVA_StringArray:
1744                 case LVA_ImageList:
1745                 case LVA_ImageArray:
1746                 case LVA_CustomList:
1747                         *msg->opg_Storage = (ULONG) lv->Items;
1748                         return TRUE;
1749
1750                 case LVA_Visible:
1751                         *msg->opg_Storage = (ULONG) lv->Visible;
1752                         return TRUE;
1753
1754                 case LVA_SelectedPtr:
1755                         *msg->opg_Storage = (ULONG) lv->SelectedPtr;
1756                         return TRUE;
1757
1758                 case LVA_SelectArray:
1759                         *msg->opg_Storage = (ULONG) lv->SelectArray;
1760                         return TRUE;
1761
1762                 default:
1763                         return DoSuperMethodA (cl, (Object *)g, (Msg) msg);
1764         }
1765 }
1766
1767
1768
1769 static ULONG LV_OMNew (Class *cl, struct Gadget *g, struct opSet *msg)
1770 {
1771         struct LVData   *lv;
1772         struct TagItem  *tag;
1773         struct DrawInfo *drawinfo;
1774
1775
1776         DB (kprintf ("OM_NEW\n");)
1777
1778         if (g = (struct Gadget *)DoSuperMethodA (cl, (Object *)g, (Msg)msg))
1779         {
1780                 /* Set the GMORE_SCROLLRASTER flag */
1781                 if (g->Flags & GFLG_EXTENDED)
1782                 {
1783                         DB (kprintf ("  Setting GMORE_SCROLLRASTER\n");)
1784                         ((struct ExtGadget *)g)->MoreFlags |= GMORE_SCROLLRASTER;
1785                 }
1786
1787                 lv = (struct LVData *) INST_DATA (cl, (Object *)g);
1788                 ASSERT_VALIDNO0(lv)
1789
1790                 /* Handle creation-time attributes */
1791
1792                 /* Map boolean attributes */
1793                 {
1794                         static IPTR boolMap[] =
1795                         {
1796                                 GA_ReadOnly,            LVF_READONLY,
1797                                 LVA_Clipped,            LVF_CLIPPED,
1798                                 LVA_ShowSelected,       LVF_SHOWSELECTED,
1799                                 LVA_DoMultiSelect,      LVF_DOMULTISELECT,
1800                                 TAG_DONE
1801                         };
1802
1803                         lv->Flags = PackBoolTags (
1804                                 LVF_SHOWSELECTED,
1805                                 msg->ops_AttrList,
1806                                 (struct TagItem *)boolMap);
1807                 }
1808
1809
1810                 /* Select font to use when drawing the Listview labels */
1811
1812                 /* First, try to get the font from our DrawInfo... */
1813
1814                 if (drawinfo = (struct DrawInfo *)
1815                         GetTagData (GA_DrawInfo, NULL, msg->ops_AttrList))
1816                 {
1817                         ASSERT_VALID(drawinfo)
1818                         lv->Font = drawinfo->dri_Font;
1819                 }
1820                 else
1821                         lv->Font = NULL;
1822
1823
1824                 /* ...then override it with LVA_TextFont */
1825
1826                 if (tag = FindTagItem (LVA_TextFont, msg->ops_AttrList))
1827                 {
1828                         if (tag->ti_Data)
1829                         {
1830                                 lv->Font = (struct TextFont *)tag->ti_Data;
1831                                 ASSERT_VALID(lv->Font)
1832                         }
1833                 }
1834                 else    /* Otherwise, try GA_TextAttr */
1835                 {
1836                         struct TextAttr *attr;
1837                         struct TextFont *font;
1838
1839                         if (attr = (struct TextAttr *)GetTagData (GA_TextAttr,
1840                                 NULL, msg->ops_AttrList))
1841                         {
1842                                 if (font = OpenFont (attr))
1843                                 {
1844                                         /* Must remember to close this font later */
1845                                         lv->Flags |= LVF_CLOSEFONT;
1846                                         lv->Font = font;
1847                                 }
1848                         }
1849                 }
1850
1851                 /* Calculate ItemHeight */
1852
1853                 if (lv->Font)
1854                         /* Get height from font Y size */
1855                         lv->ItemHeight = lv->Font->tf_YSize;
1856                 else
1857                         lv->ItemHeight = 0;
1858
1859                 lv->ItemHeight = GetTagData (LVA_ItemHeight, lv->ItemHeight, msg->ops_AttrList);
1860                 lv->Spacing = GetTagData (LAYOUTA_Spacing, 0, msg->ops_AttrList);
1861
1862                 if (tag = FindTagItem (LVA_MaxPen, msg->ops_AttrList))
1863                         lv->MaxPen = tag->ti_Data;
1864                 else
1865                 {
1866                         if (drawinfo)
1867                                 lv->MaxPen = max (
1868                                         max (drawinfo->dri_Pens[BACKGROUNDPEN],
1869                                                 drawinfo->dri_Pens[TEXTPEN]),
1870                                         max (drawinfo->dri_Pens[FILLPEN],
1871                                                 drawinfo->dri_Pens[FILLTEXTPEN]));
1872                         else
1873                                 lv->MaxPen = (ULONG)-1;
1874                 }
1875
1876
1877                 lv->Total = GetTagData (LVA_Total, ~0, msg->ops_AttrList);
1878
1879                 if (lv->Items = (APTR) GetTagData (LVA_StringList, NULL, msg->ops_AttrList))
1880                 {
1881                         ASSERT_VALID(lv->Items)
1882                         lv->GetItemFunc = ListGetItem;
1883                         lv->GetNextFunc = ListGetNext;
1884                         lv->GetPrevFunc = ListGetPrev;
1885                         lv->DrawItemFunc = ListStringDrawItem;
1886                         lv->Flags |= LVF_LIST;
1887
1888                         if (lv->Total == ~0)
1889                                 lv->Total = CountNodes (lv->Items);
1890                 }
1891                 else if (lv->Items = (APTR) GetTagData (LVA_StringArray, NULL, msg->ops_AttrList))
1892                 {
1893                         ASSERT_VALID(lv->Items)
1894                         lv->GetItemFunc = ArrayGetItem;
1895                         lv->GetNextFunc = ArrayGetItem;
1896                         lv->GetPrevFunc = ArrayGetItem;
1897                         lv->DrawItemFunc = StringDrawItem;
1898
1899                         if (lv->Total == ~0)
1900                         {
1901                                 /* Count items */
1902                                 ULONG i = 0;
1903                                 while (((APTR *)lv->Items)[i]) i++;
1904                                 lv->Total = i;
1905                         }
1906                 }
1907                 else if (lv->Items = (APTR) GetTagData (LVA_ImageList, NULL, msg->ops_AttrList))
1908                 {
1909                         ASSERT_VALID(lv->Items)
1910                         lv->GetItemFunc = ListGetItem;
1911                         lv->GetNextFunc = ListGetNext;
1912                         lv->GetPrevFunc = ListGetPrev;
1913                         lv->DrawItemFunc = ListImageDrawItem;
1914                         lv->Flags |= LVF_LIST;
1915
1916                         if (lv->Total == ~0)
1917                                 lv->Total = CountNodes (lv->Items);
1918                 }
1919                 else if (lv->Items = (APTR) GetTagData (LVA_ImageArray, NULL, msg->ops_AttrList))
1920                 {
1921                         ASSERT_VALID(lv->Items)
1922                         lv->GetItemFunc = ArrayGetItem;
1923                         lv->GetNextFunc = ArrayGetItem;
1924                         lv->GetPrevFunc = ArrayGetItem;
1925                         lv->DrawItemFunc = ImageDrawItem;
1926
1927                         if (lv->Total == ~0)
1928                         {
1929                                 /* Count items */
1930                                 ULONG i = 0;
1931                                 while (((APTR *)lv->Items)[i]) i++;
1932                                 lv->Total = i;
1933                         }
1934                 }
1935
1936                 lv->SelectArray = (ULONG *)GetTagData (LVA_SelectArray, NULL, msg->ops_AttrList);
1937                 lv->MaxSelect = GetTagData (LVA_MaxSelect, -1, msg->ops_AttrList);
1938                 lv->SelectCount = CountSelections(lv);
1939
1940                 if (lv->Visible = GetTagData (LVA_Visible, 0, msg->ops_AttrList))
1941                 {
1942                         SetAttrs (g,
1943                                 GA_Height, lv->Visible * (lv->ItemHeight + lv->Spacing),
1944                                 TAG_DONE);
1945                 }
1946
1947                 /* Initialize Top and all related values */
1948
1949                 lv->OldTop = lv->Top = GetTagData (LVA_MakeVisible,
1950                         GetTagData (LVA_Top, 0, msg->ops_AttrList), msg->ops_AttrList);
1951                 lv->OldPixelTop = lv->PixelTop = lv->Top * (lv->ItemHeight + lv->Spacing);
1952
1953                 if (lv->Items)
1954                         lv->TopPtr = GetItem (lv, lv->Top);
1955
1956                 lv->ScrollRatio = GetTagData (LVA_ScrollRatio, 2, msg->ops_AttrList);
1957                 ASSERT(lv->ScrollRatio != 0)
1958
1959                 if ((lv->OldSelected =
1960                         lv->Selected = GetTagData (LVA_Selected, ~0, msg->ops_AttrList)) != ~0)
1961                         lv->SelectedPtr = GetItem (lv, lv->Selected);
1962
1963                 if (lv->CallBack = (struct Hook *)GetTagData (LVA_CallBack, NULL,
1964                         msg->ops_AttrList))
1965                 {
1966                         ASSERT_VALID(lv->CallBack->h_Entry)
1967                         lv->DrawItemFunc = (LVDrawHook *) lv->CallBack->h_Entry;
1968                 }
1969
1970                 if (lv->Flags & LVF_CLIPPED)
1971                         lv->ClipRegion = NewRegion ();
1972         }
1973         return (ULONG)g;
1974 }
1975
1976
1977
1978 static void LV_OMDispose (Class *cl, struct Gadget *g, Msg msg)
1979 {
1980         struct LVData   *lv;
1981
1982         lv = (struct LVData *) INST_DATA (cl, (Object *)g);
1983
1984         ASSERT_VALIDNO0(lv)
1985         DB (kprintf ("OM_DISPOSE\n");)
1986
1987         if (lv->ClipRegion)
1988                 DisposeRegion (lv->ClipRegion);
1989
1990         if (lv->Flags & LVF_CLOSEFONT)
1991                 CloseFont (lv->Font);
1992
1993         /* Our superclass will cleanup everything else now */
1994         DoSuperMethodA (cl, (Object *)g, (Msg) msg);
1995
1996         /* From now on, our instance data is no longer available */
1997 }
1998
1999
2000
2001 /* Misc support functions */
2002
2003 INLINE ULONG CountNodes (struct List *list)
2004
2005 /* Return the number of nodes in a list */
2006 {
2007         struct Node *node;
2008         ULONG count = 0;
2009
2010         if (list)
2011         {
2012                 ASSERT_VALID(list)
2013
2014                 for (node = list->lh_Head; node = node->ln_Succ; count++)
2015                         ASSERT_VALID(node);
2016         }
2017
2018         return count;
2019 }
2020
2021
2022
2023 static ULONG CountSelections (struct LVData *lv)
2024
2025 /* Count the number of selections in a multiselect listview */
2026 {
2027         ULONG count = 0;
2028
2029         ASSERT_VALIDNO0(lv)
2030
2031
2032         if (lv->Flags & LVF_DOMULTISELECT)
2033         {
2034                 if (lv->SelectArray)
2035                 {
2036                         int i;
2037
2038                         ASSERT_VALID(lv->SelectArray)
2039
2040                         for (i = 0; i < lv->Total; i++)
2041                                 if (lv->SelectArray[i])
2042                                         count++;
2043                 }
2044                 else if ((lv->Flags & LVF_LIST) && lv->Items)
2045                 {
2046                         struct Node *node;
2047
2048                         ASSERT_VALID(lv->Items)
2049
2050                         for (node = ((struct List *)lv->Items)->lh_Head; node = node->ln_Succ; count++)
2051                                 ASSERT_VALID(node);
2052                 }
2053         }
2054
2055         return count;
2056 }
2057
2058
2059
2060 INLINE ULONG IsItemSelected (struct LVData *lv, APTR item, ULONG num)
2061
2062 /* Checks if the given item is selected */
2063 {
2064         ASSERT_VALIDNO0(lv)
2065         ASSERT_VALID(item)
2066         ASSERT(num >= 0)
2067         ASSERT(num < lv->Total)
2068
2069
2070         if (lv->Flags & LVF_DOMULTISELECT)
2071         {
2072                 if (lv->SelectArray)
2073                 {
2074                         ASSERT(num < lv->Total)
2075
2076                         return lv->SelectArray[num];
2077                 }
2078                 else if (lv->Flags & LVF_LIST)
2079                 {
2080                         if (!item)
2081                                 item = GetItem (lv, num);
2082
2083                         ASSERT_VALIDNO0(item)
2084
2085                         return item ? (ULONG)(((struct Node *)item)->ln_Type) : 0;
2086                 }
2087
2088                 return 0;
2089         }
2090         else
2091                 return ((ULONG)(num == lv->Selected));
2092 }
2093
2094
2095
2096 INLINE APTR GetItem (struct LVData *lv, ULONG num)
2097
2098 /* Stub for LV_GETITEM hook method */
2099 {
2100         struct lvGetItem lvgi;
2101
2102
2103         ASSERT_VALIDNO0(lv)
2104         ASSERT_VALIDNO0(lv->Items)
2105         ASSERT_VALIDNO0(lv->GetItemFunc)
2106         ASSERT(num >= 0)
2107         ASSERT(num < lv->Total)
2108
2109
2110         lvgi.lvgi_MethodID      = LV_GETITEM;
2111         lvgi.lvgi_Number        = num;
2112         lvgi.lvgi_Items         = lv->Items;
2113
2114         return (lv->GetItemFunc (lv->CallBack, NULL, &lvgi));
2115 }
2116
2117
2118
2119 INLINE APTR GetNext (struct LVData *lv, APTR item, ULONG num)
2120
2121 /* Stub for LV_GETNEXT hook method */
2122 {
2123         struct lvGetItem lvgi;
2124
2125
2126         ASSERT_VALIDNO0(lv)
2127         ASSERT_VALIDNO0(lv->GetNextFunc)
2128         ASSERT_VALID(item)
2129         ASSERT(num >= 0)
2130         ASSERT(num < lv->Total)
2131
2132
2133         lvgi.lvgi_MethodID      = LV_GETNEXT;
2134         lvgi.lvgi_Number        = num;
2135         lvgi.lvgi_Items         = lv->Items;
2136
2137         return (lv->GetNextFunc (lv->CallBack, item, &lvgi));
2138 }
2139
2140
2141
2142 INLINE APTR GetPrev (struct LVData *lv, APTR item, ULONG num)
2143
2144 /* Stub for LV_GETPREV hook method */
2145 {
2146         struct lvGetItem lvgi;
2147
2148
2149         ASSERT_VALIDNO0(lv)
2150         ASSERT_VALIDNO0(lv->GetPrevFunc)
2151         ASSERT_VALID(item)
2152         ASSERT(num >= 0)
2153         ASSERT(num < lv->Total)
2154
2155
2156         lvgi.lvgi_MethodID      = LV_GETPREV;
2157         lvgi.lvgi_Number        = num;
2158         lvgi.lvgi_Items         = lv->Items;
2159
2160         return (lv->GetPrevFunc (lv->CallBack, item, &lvgi));
2161 }
2162
2163
2164
2165 Class *MakeListViewClass (void)
2166 {
2167         Class *LVClass;
2168
2169         if (LVClass = MakeClass (NULL, GADGETCLASS, NULL, sizeof (struct LVData), 0))
2170                 LVClass->cl_Dispatcher.h_Entry = (ULONG (*)()) LVDispatcher;
2171
2172         return LVClass;
2173 }
2174
2175
2176
2177 void FreeListViewClass (Class *LVClass)
2178 {
2179         ASSERT_VALID(LVClass)
2180         FreeClass (LVClass);
2181 }