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