2 ** $Id: ListViewClass.c,v 1.4 2000/01/12 21:18:06 bernie Exp $
4 ** Copyright (C) 1996,97,99 Bernardo Innocenti <bernie@cosmos.it>
5 ** All rights reserved.
7 ** Use 4 chars wide TABs to read this file
9 ** GadTools-like `boopsi' ListView gadget class
12 /* Definitions for system headers */
13 #define USE_BUILTIN_MATH
14 #define INTUI_V36_NAMES_ONLY
15 #define INTUITION_IOBSOLETE_H
17 #define CLIB_ALIB_PROTOS_H /* Avoid dupe defines of boopsi funcs */
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>
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>
35 #include <CompilerSpecific.h>
36 #include <DebugMacros.h>
37 #include <DiagnosticMacros.h>
38 #include <BoopsiStubs.h>
39 #include <BoopsiLib.h>
41 #define LV_GADTOOLS_STUFF
42 #include <gadgets/ListViewClass.h>
50 /* ListView private instance data */
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));
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 */
72 /* Old values used to track scrolling amount in GM_RENDER */
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;
91 /* User or internal hooks */
95 LVHook *DrawBeginFunc;
97 LVDrawHook *DrawItemFunc;
98 struct Hook *CallBack; /* Callback hook provided by user */
100 struct TextFont *Font; /* Font used to render text labels */
101 struct Region *ClipRegion; /* Used in LVA_Clipped mode */
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.
110 struct Rectangle GRect;
112 struct IBox FrameBox; /* The size of our surrounding frame */
117 /* Local function prototypes */
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);
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);
138 /* Definitions for the builtin List hooks */
142 LVDrawHook ListStringDrawItem;
143 LVDrawHook ListImageDrawItem;
145 /* Definitions for the builtin Array hooks */
147 LVDrawHook StringDrawItem;
148 LVDrawHook ImageDrawItem;
152 #if (CLASS_FLAVOUR & FLAVOUR_CLASSLIB)
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));
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";
164 /* Workaround a bug in StormC header file <proto/utility.h> */
166 #define UTILITYBASETYPE struct Library
168 #define UTILITYBASETYPE struct UtilityBase
172 struct ExecBase *SysBase = NULL;
173 struct IntuitionBase *IntuitionBase = NULL;
174 UTILITYBASETYPE *UtilityBase = NULL;
175 struct GfxBase *GfxBase = NULL;
176 struct Library *LayersBase = NULL;
181 /* ListView class dispatcher - Handles all supported methods
183 static ULONG HOOKCALL LVDispatcher (
185 REG(a2, struct Gadget *g),
190 ASSERT_VALID_PTR(msg)
192 switch (msg->MethodID)
195 LV_GMRender(cl, g, (struct gpRender *)msg);
199 return LV_GMGoActive(cl, g, (struct gpInput *)msg);
202 return LV_GMHandleInput(cl, g, (struct gpInput *)msg);
205 LV_GMGoInactive(cl, g, (struct gpGoInactive *)msg);
209 /* This method is only supported on V39 and above */
210 LV_GMLayout(cl, g, (struct gpLayout *)msg);
215 return LV_OMSet(cl, g, (struct opUpdate *)msg);
218 return LV_OMGet(cl, g, (struct opGet *)msg);
221 return LV_OMNew(cl, g, (struct opSet *)msg);
224 LV_OMDispose(cl, g, msg);
228 /* Unsupported method: let our superclass's
229 * dispatcher take a look at it.
231 return DoSuperMethodA(cl, (Object *)g, msg);
237 /* Stub for LV_GETITEM hook method
239 INLINE APTR GetItem(struct LVData *lv, LONG num)
241 struct lvGetItem lvgi;
245 ASSERT_VALID_PTR(lv->Items)
246 ASSERT_VALID_PTR(lv->GetItemFunc)
248 ASSERT(num < lv->Total)
251 lvgi.lvgi_MethodID = LV_GETITEM;
252 lvgi.lvgi_Number = num;
253 lvgi.lvgi_Items = lv->Items;
255 return (lv->GetItemFunc(lv->CallBack, NULL, &lvgi));
260 /* Stub for LV_GETNEXT hook method
262 INLINE APTR GetNext(struct LVData *lv, APTR item, LONG num)
264 struct lvGetItem lvgi;
268 ASSERT_VALID_PTR(lv->GetNextFunc)
269 ASSERT_VALID_PTR_OR_NULL(item)
271 ASSERT(num < lv->Total)
274 lvgi.lvgi_MethodID = LV_GETNEXT;
275 lvgi.lvgi_Number = num;
276 lvgi.lvgi_Items = lv->Items;
278 return (lv->GetNextFunc (lv->CallBack, item, &lvgi));
283 /* Stub for LV_GETPREV hook method
285 INLINE APTR GetPrev(struct LVData *lv, APTR item, LONG num)
287 struct lvGetItem lvgi;
291 ASSERT_VALID_PTR(lv->GetPrevFunc)
292 ASSERT_VALID_PTR_OR_NULL(item)
294 ASSERT(num < lv->Total)
297 lvgi.lvgi_MethodID = LV_GETPREV;
298 lvgi.lvgi_Number = num;
299 lvgi.lvgi_Items = lv->Items;
301 return (lv->GetPrevFunc (lv->CallBack, item, &lvgi));
306 INLINE void GetItemBounds(struct LVData *lv, struct Rectangle *rect, LONG item)
308 /* Compute the bounding box to render the given item and store it in the passed
309 * Rectangle structure.
313 ASSERT_VALID_PTR(rect)
314 ASSERT(item < lv->Total)
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;
327 /* Checks if the given item is selected
329 INLINE ULONG IsItemSelected(struct LVData *lv, APTR item, LONG num)
332 ASSERT_VALID_PTR_OR_NULL(item)
334 ASSERT(num < lv->Total)
337 if (lv->Flags & LVF_DOMULTISELECT)
341 ASSERT(num < lv->Total)
343 return lv->SelectArray[num];
345 else if (lv->Flags & LVF_LIST)
348 item = GetItem(lv, num);
350 ASSERT_VALID_PTR(item)
352 return item ? (ULONG)(((struct Node *)item)->ln_Type) : 0;
358 return ((ULONG)(num == lv->Selected));
363 /* Return the number of nodes in a list
365 INLINE ULONG CountNodes(struct List *list)
372 ASSERT_VALID_PTR(list)
374 for (node = list->lh_Head; (node = node->ln_Succ); count++)
375 ASSERT_VALID_PTR_OR_NULL(node);
383 /* Count the number of selections in a multiselect listview
385 static ULONG CountSelections(struct LVData *lv)
392 if (lv->Flags & LVF_DOMULTISELECT)
398 ASSERT_VALID_PTR(lv->SelectArray)
400 for (i = 0; i < lv->Total; i++)
401 if (lv->SelectArray[i])
404 else if ((lv->Flags & LVF_LIST) && lv->Items)
408 ASSERT_VALID_PTR(lv->Items)
410 for (node = ((struct List *)lv->Items)->lh_Head; (node = node->ln_Succ); count++)
411 ASSERT_VALID_PTR_OR_NULL(node);
420 static void RedrawItems(struct LVData *lv, struct gpRender *msg, LONG first, LONG last, APTR item)
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.
426 struct lvDrawItem lvdi;
431 ASSERT_VALID_PTR(msg)
432 ASSERT(first <= last)
433 ASSERT(last < lv->Total)
435 DB2( DBPRINTF (" RedrawItems (first = %ld, last = %ld)\n", first, last);)
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;
444 GetItemBounds (lv, &lvdi.lvdi_Bounds, first);
448 lvdi.lvdi_MethodID = LV_GETITEM;
449 item = lv->GetItemFunc (lv->CallBack, NULL, (struct lvGetItem *)&lvdi);
452 if (lv->DrawBeginFunc)
454 lvdi.lvdi_MethodID = LV_DRAWBEGIN;
455 lv->DrawBeginFunc (lv->CallBack, item, (struct lvDrawBegin *)&lvdi);
460 if (lv->Flags & LVF_DOMULTISELECT)
463 /* Array selection */
464 selected = lv->SelectArray[lvdi.lvdi_Current];
466 if (lv->Flags & LVF_LIST)
468 selected = (((struct Node *)item)->ln_Type);
473 /* Single selection */
474 selected = (lvdi.lvdi_Current == lv->Selected);
476 lvdi.lvdi_State = selected ? LVR_SELECTED : LVR_NORMAL;
478 lvdi.lvdi_MethodID = LV_DRAW;
479 lv->DrawItemFunc (lv->CallBack, item, &lvdi);
481 if (++lvdi.lvdi_Current > last)
484 lvdi.lvdi_MethodID = LV_GETNEXT;
485 item = lv->GetNextFunc (lv->CallBack, item, (struct lvGetNext *)&lvdi);
487 lvdi.lvdi_Bounds.MinY += lv->ItemHeight + lv->Spacing;
488 lvdi.lvdi_Bounds.MaxY += lv->ItemHeight + lv->Spacing;
493 lvdi.lvdi_MethodID = LV_DRAWEND;
494 lv->DrawEndFunc (lv->CallBack, item, (struct lvDrawEnd *)&lvdi);
500 static void LV_GMRender(Class *cl, struct Gadget *g, struct gpRender *msg)
502 struct LVData *lv = INST_DATA (cl, g);
503 struct RastPort *rp = msg->gpr_RPort;
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 ***")) );)
515 if (lv->Flags & LVF_DONTDRAW)
518 if (lv->Items && lv->Visible)
520 struct TextFont *oldfont = NULL;
521 struct Region *oldregion;
522 BOOL layerupdating = FALSE;
524 if (rp->Font != lv->Font)
527 SetFont (rp, lv->Font);
532 ASSERT_VALID_PTR(lv->ClipRegion)
533 ASSERT_VALID_PTR(msg->gpr_RPort->Layer)
534 DB2(DBPRINTF ("Calling InstallClipRegion()...\n");)
536 /* Workaround for installing clip regions in
537 * updating layers within a boopsi gadget dispatcher,
538 * as suggested by Jochen Bechen.
540 // if (msg->gpr_RPort->Layer->Flags & LAYERUPDATING)
542 // layerupdating = TRUE;
543 // EndUpdate(msg->gpr_RPort->Layer, FALSE);
545 oldregion = InstallClipRegion(rp->Layer, lv->ClipRegion);
548 switch (msg->gpr_Redraw)
550 case GREDRAW_TOGGLE: /* Toggle selected item */
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);
555 if (drawold || drawnew)
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;
564 if (lv->DrawBeginFunc)
566 lvdi.lvdi_MethodID = LV_DRAWBEGIN;
567 lv->DrawBeginFunc(lv->CallBack, NULL, (struct lvDrawBegin *)&lvdi);
570 lvdi.lvdi_MethodID = LV_DRAW;
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;
579 lv->DrawItemFunc (lv->CallBack, lv->SelectedPtr, &lvdi);
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;
589 lv->DrawItemFunc (lv->CallBack, lv->OldSelectedPtr, &lvdi);
594 lvdi.lvdi_MethodID = LV_DRAWEND;
595 lv->DrawEndFunc (lv->CallBack, NULL, (struct lvDrawEnd *)&lvdi);
599 lv->OldSelected = lv->Selected;
600 lv->OldSelectedPtr = lv->SelectedPtr;
605 case GREDRAW_REDRAW: /* Redraw everything */
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 */
613 /* Now clear the spacing between the items */
614 if (lv->Spacing && lv->Items && lv->Visible)
618 ycoord = lv->GRect.MinY + lv->ItemHeight;
619 lastitem = min (lv->Visible, lv->Total - lv->Top) - 1;
621 for (i = 0 ; i < lastitem; i++)
623 RectFill (rp, lv->GRect.MinX, ycoord,
624 lv->GRect.MaxX, ycoord + lv->Spacing - 1);
626 ycoord += lv->ItemHeight + lv->Spacing;
630 ycoord = lv->GRect.MinY + min (lv->Visible, lv->Total - lv->Top)
633 /* Now let's clear bottom part of gadget */
634 RectFill (rp, lv->GRect.MinX, ycoord,
635 lv->GRect.MaxX, lv->GRect.MaxY);
637 /* Finally, draw the items */
638 RedrawItems (lv, msg, lv->Top,
639 min (lv->Top + lv->Visible, lv->Total) - 1, lv->TopPtr);
644 case GREDRAW_UPDATE: /* Scroll ListView */
646 LONG scroll_dy, scroll_height;
650 /* Calculate scrolling amount in pixels */
651 if (!(scroll_dy = lv->PixelTop - lv->OldPixelTop))
652 /* Do nothing if called improperly */
655 /* Scroll everything */
656 scroll_height = lv->GBox.Height;
660 if (!(lv->Top - lv->OldTop))
661 /* Do nothing if called improperly */
664 /* Calculate scrolling amount in pixels */
665 scroll_dy = (lv->Top - lv->OldTop) * (lv->ItemHeight + lv->Spacing);
667 /* Only scroll upto last visible item */
668 scroll_height = lv->Visible * (lv->ItemHeight + lv->Spacing) - lv->Spacing;
671 if (abs(scroll_dy) > lv->MaxScroll)
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);
679 /* Optimize scrolling on planar displays if possible */
680 SetMaxPen (rp, lv->MaxPen);
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.
689 if (scroll_dy > 0) /* Scroll Down */
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,
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
703 (lv->OldPixelTop + lv->GBox.Height) / (lv->ItemHeight + lv->Spacing),
704 (lv->PixelTop + lv->GBox.Height - 1) / (lv->ItemHeight + lv->Spacing),
709 lv->Visible + lv->OldTop,
710 lv->Visible + lv->Top - 1,
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,
723 lv->PixelTop / (lv->ItemHeight + lv->Spacing),
724 lv->OldPixelTop / (lv->ItemHeight + lv->Spacing),
733 /* This will scroll the layer damage regions without actually
734 * scrolling the display, but only if our layer really needs it.
736 if (NeedZeroScrollRaster(rp->Layer))
738 UBYTE oldmask = rp->Mask; /* Would GetRPAttr() be better? */
740 DB2 (DBPRINTF (" Calling ScrollRaster()\n");)
742 ScrollRaster(rp, 0, scroll_dy,
743 lv->GRect.MinX, lv->GRect.MinY,
747 SetWriteMask(rp, oldmask);
750 /* Restore MaxPen in our RastPort */
754 /* Update OldTop to the current Top item and
755 * OldPixelTop to the current PixelTop position.
757 lv->OldTop = lv->Top;
758 lv->OldPixelTop = lv->PixelTop;
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);
774 BeginUpdate(msg->gpr_RPort->Layer);
778 SetFont (rp, oldfont);
780 else if (msg->gpr_Redraw == GREDRAW_REDRAW)
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);
788 /* Last but not least, draw the frame if we have one associated */
790 if ((msg->gpr_Redraw == GREDRAW_REDRAW) && GadgetHasFrame(g))
792 DB2 (DBPRINTF("ListViewClass: Rendering my own frame\n");)
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 */
805 static ULONG LV_GMGoActive(Class *cl, struct Gadget *g, struct gpInput *msg)
807 struct LVData *lv = INST_DATA (cl, g);
810 DB2 (DBPRINTF ("GM_GOACTIVE: gpi_IEvent = $%lx\n", msg->gpi_IEvent);)
816 g->Flags |= GFLG_SELECTED;
818 /* Do not process InputEvent when the gadget has been
819 * activated by ActivateGadget().
821 if (!msg->gpi_IEvent)
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.
828 return LV_GMHandleInput (cl, g, msg);
833 INLINE LONG ItemHit (struct LVData *lv, UNUSED(WORD x), WORD y)
835 /* Determine which item has been hit with gadget relative
836 * coordinates x and y.
839 return ((y + lv->PixelTop) / (lv->ItemHeight + lv->Spacing));
844 static ULONG LV_GMHandleInput (Class *cl, struct Gadget *g, struct gpInput *msg)
846 struct LVData *lv = INST_DATA (cl, g);
847 struct InputEvent *ie = msg->gpi_IEvent;
848 ULONG result = GMR_MEACTIVE;
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);)
855 switch (ie->ie_Class)
865 if ((lv->Flags & LVF_READONLY) || (ie->ie_Qualifier & IEQUALIFIER_CONTROL))
867 if (ie->ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
868 pos = lv->Top - lv->Visible / 2;
872 if (pos < 0) pos = 0;
880 if (ie->ie_Qualifier & (IEQUALIFIER_LALT | IEQUALIFIER_RALT))
882 else if (ie->ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
883 pos = lv->Selected - lv->Visible + 1;
885 pos = lv->Selected - 1;
887 if (pos < 0) pos = 0;
889 tags[0] = LVA_Selected;
891 tags[2] = LVA_MakeVisible;
898 if ((lv->Flags & LVF_READONLY) || (ie->ie_Qualifier & IEQUALIFIER_CONTROL))
900 if (ie->ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
901 pos = lv->Top + lv->Visible / 2;
911 if (ie->ie_Qualifier & (IEQUALIFIER_LALT | IEQUALIFIER_RALT))
913 else if (ie->ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
914 pos = lv->Selected + lv->Visible - 1;
916 pos = lv->Selected + 1;
918 tags[0] = LVA_Selected;
920 tags[2] = LVA_MakeVisible;
929 } /* End switch (ie->ie_Code) */
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);
938 case IECLASS_RAWMOUSE:
946 /* Check for click outside gadget box */
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))
957 /* Start dragging mode */
958 lv->Flags |= LVF_DRAGGING;
960 if (lv->Flags & LVF_READONLY)
964 selected = ItemHit (lv, msg->gpi_Mouse.X, msg->gpi_Mouse.Y);
966 /* No action when selecting over blank space in the bottom */
967 if ((selected < 0) || (selected >= lv->Total))
970 /* Backup current selection for RMB undo */
971 lv->BackupSelected = lv->Selected;
972 lv->BackupPixelTop = lv->PixelTop;
974 if (selected == lv->Selected)
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,
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)
990 /* Setup for single item toggle */
991 lv->DragSelect = LVA_Selected;
992 if (selected == lv->Selected)
995 else /* Single selection */
996 /* Setup for single item drag selection */
997 lv->DragSelect = LVA_Selected;
999 UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
1000 lv->DragSelect, selected,
1003 /* Save double click info */
1004 lv->DoubleClickSecs = ie->ie_TimeStamp.tv_secs;
1005 lv->DoubleClickMicros = ie->ie_TimeStamp.tv_micro;
1009 /* Undo selection & position when RMB is pressed */
1010 if (lv->Flags & (LVF_DRAGGING | LVF_SCROLLING))
1012 /* Stop dragging and scrolling modes */
1013 lv->Flags &= ~(LVF_DRAGGING | LVF_SCROLLING);
1015 if ((lv->BackupSelected != lv->Selected) ||
1016 (lv->BackupPixelTop != lv->PixelTop))
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,
1026 /* Deactivate gadget on menu button press */
1032 /* Argh, input.device never sends this event in V40! */
1033 DB1 (DBPRINTF ("MIDDLEDOWN received\n");)
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;
1044 /* Stop dragging mode */
1045 lv->Flags &= ~LVF_DRAGGING;
1047 if (g->Activation & GACT_RELVERIFY)
1049 /* Send IDCMP_GADGETUP message to our parent window */
1050 msg->gpi_Termination = &lv->Selected;
1051 result = GMR_NOREUSE | GMR_VERIFY;
1056 /* Argh, input.device never sends this event in V40! */
1057 DB1 (DBPRINTF ("scrolling off\n");)
1059 /* Stop MMB scrolling */
1060 lv->Flags &= ~LVF_SCROLLING;
1063 default: /* Mouse moved */
1066 if (lv->Flags & LVF_DRAGGING)
1068 /* Select an item */
1069 selected = ItemHit (lv, msg->gpi_Mouse.X, msg->gpi_Mouse.Y);
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))
1075 /* Single selection */
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.
1081 UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
1082 lv->DragSelect, selected,
1088 if (lv->Flags & LVF_SCROLLING)
1090 DB1 (DBPRINTF (" scrolling\n");)
1091 selected = (msg->gpi_Mouse.Y - lv->MiddleMouseY)
1092 + lv->BackupPixelTop;
1094 UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
1095 LVA_PixelTop, selected < 0 ? 0 : selected,
1099 } /* End switch (ie->ie_Code) */
1107 if (lv->Flags & LVF_DRAGGING)
1109 /* Mouse above the upper item? */
1110 if ((msg->gpi_Mouse.Y < 0) && lv->Top)
1113 UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
1115 (lv->Flags & LVF_READONLY) ? TAG_IGNORE : LVA_Selected, lv->Top - 1,
1118 /* Mouse below the bottom item? */
1119 else if (msg->gpi_Mouse.Y / (lv->ItemHeight + lv->Spacing) >= lv->Visible)
1122 UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
1124 (lv->Flags & LVF_READONLY) ? TAG_IGNORE : LVA_Selected, lv->Top + lv->Visible,
1133 } /* End switch (ie->ie_Class) */
1140 static void LV_GMGoInactive (Class *cl, struct Gadget *g, UNUSED(struct gpGoInactive *msg))
1142 struct LVData *lv = INST_DATA (cl, g);
1143 ASSERT_VALID_PTR(lv)
1145 DB1 (DBPRINTF ("ListViewClass: GM_GOINACTIVE\n");)
1147 /* Stop dragging and scrolling modes */
1148 lv->Flags &= ~(LVF_DRAGGING | LVF_SCROLLING);
1150 /* Mark gadget inactive */
1151 g->Flags &= ~GFLG_SELECTED;
1156 static void LV_GMLayout(Class *cl, struct Gadget *g, struct gpLayout *msg)
1158 struct LVData *lv = INST_DATA (cl, g);
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)
1168 /* We shouldn't draw inside the GM_LAYOUT method: the
1169 * GM_REDRAW method will be called by Intuition shortly after.
1171 lv->Flags |= LVF_DONTDRAW;
1173 /* Collect new gadget size */
1174 GetGadgetBox(msg->gpl_GInfo, (struct ExtGadget *)g, &lv->GBox);
1175 IBoxToRect(&lv->GBox, &lv->GRect);
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);
1182 /* Calculate clipping region for gadget LVA_Clipped mode */
1185 /* Remove previous clipping rectangle, if any */
1186 ClearRegion (lv->ClipRegion);
1188 /* Install a clipping rectangle around the gadget box.
1189 * We don't check for failure because we couldn't do
1190 * anything otherwise.
1192 OrRectRegion (lv->ClipRegion, &lv->GRect);
1195 /* Setup Font if not yet done */
1198 lv->Font = msg->gpl_GInfo->gi_DrInfo->dri_Font;
1199 if (!lv->ItemHeight)
1200 lv->ItemHeight = lv->Font->tf_YSize;
1206 /* Allow displaying an incomplete item at the bottom of the listview,
1207 * plus one incomplete item at the top.
1209 visible = (lv->GBox.Height + lv->ItemHeight + lv->Spacing - 1) /
1210 (lv->ItemHeight + lv->Spacing);
1212 /* get maximum number of items fitting in the listview height.
1213 * Ignore spacing for the last visible item.
1215 visible = (lv->GBox.Height + lv->Spacing) / (lv->ItemHeight + lv->Spacing);
1220 lv->MaxScroll = lv->GBox.Height / lv->ScrollRatio;
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.
1228 * The private class attribute LVA_Visible will handle everything for us.
1230 UpdateAttrs ((Object *)g, msg->gpl_GInfo, 0,
1231 LVA_Visible, visible,
1234 /* Re-enable drawing */
1235 lv->Flags &= ~LVF_DONTDRAW;
1240 static ULONG LV_OMSet(Class *cl, struct Gadget *g, struct opUpdate *msg)
1242 struct LVData *lv = INST_DATA (cl, g);
1244 *tstate = msg->opu_AttrList;
1246 UWORD action = 0; /* See flag definitions below */
1248 ASSERT_VALID_PTR(lv)
1249 ASSERT_VALID_PTR_OR_NULL(tstate)
1251 DB2 (DBPRINTF ((msg->MethodID == OM_SET) ?
1252 "ListViewClass: OM_SET\n" :
1253 "ListViewClass: OM_UPDATE:\n");)
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.
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)
1269 while ((ti = NextTagItem (&tstate)))
1273 if (msg->MethodID == OM_SET)
1275 DB2 (DBPRINTF (" GA_ID, %ld\n", ti->ti_Data);)
1277 /* Avoid forwarding all taglists to superclass because of GA_ID */
1278 g->GadgetID = ti->ti_Data;
1283 DB2 (DBPRINTF (" LVA_Selected, %ld\n", ti->ti_Data);)
1287 LONG newselected = ti->ti_Data;
1289 if (newselected != ~0)
1290 newselected = (newselected >= lv->Total) ?
1291 (lv->Total - 1) : newselected;
1293 if (lv->Selected != newselected)
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;
1301 lv->Selected = newselected;
1303 if (newselected == ~0)
1304 lv->SelectedPtr = NULL;
1306 lv->SelectedPtr = GetItem(lv, newselected);
1308 action |= LVF_NOTIFY;
1314 DB2 (DBPRINTF (" LVA_Top, %ld\n", ti->ti_Data);)
1316 if ((lv->Top != (LONG)ti->ti_Data) && lv->Items)
1318 /* This will scroll the listview contents when needed */
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);
1325 /* TODO: optimize for some special cases:
1326 * Top == oldtop + 1 and Top == oldtop - 1
1328 lv->TopPtr = GetItem(lv, lv->Top);
1329 action |= LVF_SCROLL | LVF_NOTIFY;
1334 DB2 (DBPRINTF (" LVA_Total, %ld\n", ti->ti_Data);)
1336 /* We don't hhandle LVA_Total except when setting a new
1337 * list or array of items.
1341 case LVA_SelectItem:
1342 DB2 (DBPRINTF (" LVA_SelectItem, %ld\n", ti->ti_Data);)
1344 /* Check LVA_MaxSelect */
1345 if (lv->SelectCount >= lv->MaxSelect)
1346 DisplayBeep (msg->opu_GInfo ? msg->opu_GInfo->gi_Screen : NULL);
1349 LONG newselected = ((LONG)ti->ti_Data >= lv->Total) ?
1350 (lv->Total - 1) : (LONG)ti->ti_Data;
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;
1358 lv->Selected = newselected;
1359 lv->SelectedPtr = GetItem(lv, newselected);
1361 if (!IsItemSelected(lv, lv->SelectedPtr, newselected))
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;
1370 action |= LVF_NOTIFY;
1374 case LVA_DeselectItem:
1375 DB2 (DBPRINTF (" LVA_DeselectItem, %ld\n", ti->ti_Data);)
1379 LONG newselected = ((LONG)ti->ti_Data >= lv->Total) ?
1380 (lv->Total - 1) : (LONG)ti->ti_Data;
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;
1388 lv->Selected = newselected;
1389 lv->SelectedPtr = GetItem(lv, newselected);
1391 if (IsItemSelected(lv, lv->SelectedPtr, newselected))
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;
1400 action |= LVF_NOTIFY;
1405 case LVA_ToggleItem:
1406 DB2 (DBPRINTF (" LVA_ToggleItem, %ld\n", ti->ti_Data);)
1410 LONG newselected = ((LONG)ti->ti_Data >= lv->Total) ?
1411 (lv->Total - 1) : (LONG)ti->ti_Data;
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;
1419 lv->Selected = newselected;
1420 lv->SelectedPtr = GetItem(lv, newselected);
1422 if (IsItemSelected(lv, lv->SelectedPtr, lv->Selected))
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;
1434 /* Check LVA_MaxSelect */
1435 if (lv->SelectCount >= lv->MaxSelect)
1436 DisplayBeep (msg->opu_GInfo ? msg->opu_GInfo->gi_Screen : NULL);
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;
1449 action |= LVF_NOTIFY;
1453 case LVA_ClearSelected:
1454 DB2 (DBPRINTF (" LVA_ClearSelected, %ld\n", ti->ti_Data);)
1458 LONG newselected = (LONG)ti->ti_Data;
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;
1468 lv->SelectedPtr = NULL;
1469 lv->SelectCount = 0;
1472 /* Clear the selections */
1474 if (lv->SelectArray)
1475 for (i = 0; i < lv->Total; i++)
1476 lv->SelectArray[i] = 0;
1477 else if (lv->Flags & LVF_LIST)
1481 for (node = ((struct List *)lv->Items)->lh_Head;
1483 node = node->ln_Succ)
1486 ASSERT_VALID_PTR_OR_NULL(node);
1490 /* TODO: check if total redraw is really needed */
1491 action |= LVF_REDRAW | LVF_NOTIFY;
1495 case LVA_MakeVisible:
1497 LONG itemnum = (LONG)ti->ti_Data;
1499 DB2 (DBPRINTF (" LVA_MakeVisible, %ld\n", ti->ti_Data);)
1504 if (itemnum >= lv->Total)
1505 itemnum = lv->Total - 1;
1507 if (itemnum < lv->Top)
1512 lv->TopPtr = GetItem(lv, lv->Top);
1513 action |= LVF_SCROLL | LVF_NOTIFY;
1515 else if (itemnum >= lv->Top + lv->Visible)
1519 lv->Top = itemnum - lv->Visible + 1;
1520 lv->TopPtr = GetItem(lv, lv->Top);
1521 action |= LVF_SCROLL | LVF_NOTIFY;
1527 DB2 (DBPRINTF (" LVA_MoveUp, %ld\n", ti->ti_Data);)
1529 if ((lv->Top > 0) && lv->Items)
1532 lv->TopPtr = GetPrev (lv, lv->TopPtr, lv->Top);
1533 action |= LVF_SCROLL | LVF_NOTIFY;
1538 DB2 (DBPRINTF (" LVA_MoveDown, %ld\n", ti->ti_Data);)
1540 if ((lv->Top + lv->Visible < lv->Total) && lv->Items)
1543 lv->TopPtr = GetNext (lv, lv->TopPtr, lv->Top);
1544 action |= LVF_SCROLL | LVF_NOTIFY;
1549 DB2 (DBPRINTF (" Unimplemented attr: LVA_MoveLeft\n");)
1553 DB2 (DBPRINTF (" Unimplemented attr: LVA_MoveRight\n");)
1556 case LVA_StringList:
1557 DB2 (DBPRINTF (" LVA_StringList, $%lx\n", ti->ti_Data);)
1559 if (ti->ti_Data == (ULONG)~0L)
1563 ASSERT_VALID_PTR_OR_NULL(ti->ti_Data)
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;
1572 lv->Total = GetTagData (LVA_Total, ~0, msg->opu_AttrList);
1573 if (lv->Total == ~0)
1574 lv->Total = CountNodes(lv->Items);
1576 lv->SelectCount = CountSelections (lv);
1578 action |= LVF_REDRAW | LVF_NOTIFYALL;
1582 case LVA_StringArray:
1583 DB2 (DBPRINTF (" LVA_StringArray, $%lx\n", ti->ti_Data);)
1585 if (ti->ti_Data == (ULONG)~0L)
1589 ASSERT_VALID_PTR_OR_NULL(ti->ti_Data)
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;
1598 lv->Total = GetTagData (LVA_Total, ~0, msg->opu_AttrList);
1599 if ((lv->Total == ~0) && lv->Items)
1603 while (((APTR *)lv->Items)[i]) i++;
1607 lv->SelectCount = CountSelections(lv);
1609 action |= LVF_REDRAW | LVF_NOTIFYALL;
1614 DB2 (DBPRINTF (" LVA_ImageList, $%lx\n", ti->ti_Data);)
1616 if (ti->ti_Data == (ULONG)~0L)
1620 ASSERT_VALID_PTR_OR_NULL(ti->ti_Data)
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;
1629 lv->Total = GetTagData (LVA_Total, ~0, msg->opu_AttrList);
1630 if (lv->Total == ~0)
1631 lv->Total = CountNodes(lv->Items);
1633 lv->SelectCount = CountSelections(lv);
1635 action |= LVF_REDRAW | LVF_NOTIFYALL;
1639 case LVA_ImageArray:
1640 DB2 (DBPRINTF (" LVA_ImageArray, $%lx\n", ti->ti_Data);)
1642 if (ti->ti_Data == (ULONG)~0L)
1646 ASSERT_VALID_PTR_OR_NULL(ti->ti_Data)
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;
1655 lv->Total = GetTagData (LVA_Total, ~0, msg->opu_AttrList);
1656 if ((lv->Total == ~0) && lv->Items)
1660 while (((APTR *)lv->Items)[i]) i++;
1664 action |= LVF_REDRAW | LVF_NOTIFYALL;
1668 case LVA_CustomList:
1669 DB2 (DBPRINTF (" LVA_CustomList, $%lx\n", ti->ti_Data);)
1671 if (ti->ti_Data == (ULONG)~0L)
1675 ASSERT_VALID_PTR_OR_NULL(ti->ti_Data)
1677 lv->Items = (void *) ti->ti_Data;
1678 lv->SelectCount = CountSelections (lv);
1680 action |= LVF_REDRAW | LVF_NOTIFYALL;
1685 DB2 (DBPRINTF (" LVA_Visible, %ld\n", ti->ti_Data);)
1687 /* This attribute can only be set internally, and will
1688 * trigger a full slider notification.
1690 lv->Visible = (LONG)ti->ti_Data;
1691 action |= LVF_NOTIFYALL;
1694 /* Also scroll the ListView if needed. */
1697 LONG height = lv->Total * (lv->ItemHeight + lv->Spacing);
1700 if (lv->PixelTop + lv->GBox.Height >= height)
1702 lv->PixelTop = height - lv->GBox.Height;
1703 if (lv->PixelTop < 0)
1706 newtop = lv->PixelTop / (lv->ItemHeight + lv->Spacing);
1707 if (newtop != lv->Top)
1710 lv->TopPtr = GetItem(lv, newtop);
1712 action |= LVF_SCROLL;
1715 else if (lv->Top + lv->Visible >= lv->Total)
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;
1724 case LVA_SelectArray:
1725 DB2 (DBPRINTF (" LVA_SelectArray, $%lx\n", ti->ti_Data);)
1726 ASSERT_VALID_PTR_OR_NULL(ti->ti_Data)
1728 lv->SelectArray = (ULONG *)ti->ti_Data;
1729 lv->SelectCount = CountSelections (lv);
1730 action |= LVF_REDRAW;
1734 DB2(DBPRINTF(" LVA_MaxSelect, %ld\n", ti->ti_Data);)
1736 lv->MaxSelect = ti->ti_Data;
1737 /* NOTE: We are not checking lv->SelectCount */
1740 case LVA_PixelTop: /* Handle pixel-wise scrolling */
1741 DB2(DBPRINTF(" LVA_PixelTop, %ld\n", ti->ti_Data);)
1743 if (((LONG)ti->ti_Data != lv->PixelTop) && lv->Items && lv->ItemHeight)
1747 lv->PixelTop = (LONG)ti->ti_Data;
1748 action |= LVF_SCROLL;
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))
1755 if (newtop != lv->Top)
1757 /* TODO: optimize GetItem() for some special cases:
1758 * Top = oldtop + 1 and Top = oldtop - 1
1761 lv->TopPtr = GetItem(lv, newtop);
1762 action |= LVF_NOTIFY | LVF_SCROLL;
1767 case LVA_ScrollRatio:
1768 DB2 (DBPRINTF (" LVA_ScrollRatio, %ld\n", ti->ti_Data);)
1769 ASSERT(ti->ti_Data != 0)
1771 lv->ScrollRatio = (LONG)ti->ti_Data;
1772 lv->MaxScroll = lv->GBox.Height / lv->ScrollRatio;
1776 DB2 (DBPRINTF (" Passing unknown tag to superclass: $%lx, %ld\n",
1777 ti->ti_Tag, ti->ti_Data);)
1779 /* This little optimization avoids forwarding the
1780 * OM_SET method to our superclass then there are
1783 action |= LVF_DO_SUPER_METHOD;
1787 DB2 (DBPRINTF (" TAG_DONE\n");)
1789 /* Forward method to our superclass dispatcher, only if needed */
1791 if (action & LVF_DO_SUPER_METHOD)
1792 result = (DoSuperMethodA (cl, (Object *)g, (Msg) msg));
1797 /* Update gadget imagery, only when needed */
1799 if ((action & (LVF_REDRAW | LVF_SCROLL | LVF_TOGGLESELECT))
1800 && msg->opu_GInfo && !(lv->Flags & LVF_DONTDRAW))
1802 struct RastPort *rp;
1804 if ((rp = ObtainGIRPort (msg->opu_GInfo)))
1806 /* Just redraw everything */
1807 if (action & LVF_REDRAW)
1808 DoMethod ((Object *)g, GM_RENDER, msg->opu_GInfo, rp, GREDRAW_REDRAW);
1811 /* Both these may happen at the same time */
1813 if (action & LVF_SCROLL)
1814 DoMethod ((Object *)g, GM_RENDER, msg->opu_GInfo, rp,
1817 if (action & LVF_TOGGLESELECT)
1818 DoMethod ((Object *)g, GM_RENDER, msg->opu_GInfo, rp,
1822 ReleaseGIRPort (rp);
1824 DB1(else DBPRINTF ("*** ObtainGIRPort() failed!\n");)
1828 /* Notify our targets about changed attributes */
1830 if (action & LVF_NOTIFYALL)
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");)
1842 NotifyAttrs((Object *)g, msg->opu_GInfo,
1843 (msg->MethodID == OM_UPDATE) ? msg->opu_Flags : 0,
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 ?
1852 lv->Visible * (lv->ItemHeight + lv->Spacing),
1856 else if (action & LVF_NOTIFY)
1861 if (action & LVF_SCROLL)
1863 tags[0] = LVA_Top; tags[1] = lv->Top;
1864 tags[2] = LVA_PixelTop; tags[3] = lv->Top * (lv->ItemHeight + lv->Spacing);
1868 if (action & LVF_TOGGLESELECT)
1870 tags[cnt++] = LVA_Selected; tags[cnt++] = lv->Selected;
1873 tags[cnt++] = GA_ID; tags[cnt++] = g->GadgetID;
1874 tags[cnt] = TAG_DONE;
1876 DoMethod((Object *)g, OM_NOTIFY, tags, msg->opu_GInfo,
1877 (msg->MethodID == OM_UPDATE) ? msg->opu_Flags : 0);
1885 static ULONG LV_OMGet(Class *cl, struct Gadget *g, struct opGet *msg)
1887 struct LVData *lv = INST_DATA (cl, g);
1889 ASSERT_VALID_PTR(lv)
1890 ASSERT_VALID_PTR(msg->opg_Storage)
1892 DB2 (DBPRINTF("ListViewClass: OM_GET\n");)
1895 switch (msg->opg_AttrID)
1898 *msg->opg_Storage = (ULONG) lv->Selected;
1902 *msg->opg_Storage = (ULONG) lv->Top;
1906 *msg->opg_Storage = (ULONG) lv->Total;
1909 case LVA_StringList:
1910 case LVA_StringArray:
1912 case LVA_ImageArray:
1913 case LVA_CustomList:
1914 *msg->opg_Storage = (ULONG) lv->Items;
1918 *msg->opg_Storage = (ULONG) lv->Visible;
1921 case LVA_SelectedPtr:
1922 *msg->opg_Storage = (ULONG) lv->SelectedPtr;
1925 case LVA_SelectArray:
1926 *msg->opg_Storage = (ULONG) lv->SelectArray;
1930 return DoSuperMethodA (cl, (Object *)g, (Msg) msg);
1936 static ULONG LV_OMNew(Class *cl, struct Gadget *g, struct opSet *msg)
1939 struct TagItem *tag;
1940 struct DrawInfo *drawinfo;
1943 DB2(DBPRINTF("ListViewClass: OM_NEW\n");)
1945 if ((g = (struct Gadget *)DoSuperMethodA(cl, (Object *)g, (Msg)msg)))
1947 /* Set the GMORE_SCROLLRASTER flag */
1948 if (g->Flags & GFLG_EXTENDED)
1950 DB(DBPRINTF(" Setting GMORE_SCROLLRASTER\n");)
1951 ((struct ExtGadget *)g)->MoreFlags |= GMORE_SCROLLRASTER;
1954 lv = (struct LVData *) INST_DATA (cl, (Object *)g);
1955 ASSERT_VALID_PTR(lv)
1957 /* Handle creation-time attributes */
1959 /* Map boolean attributes */
1961 static IPTR boolMap[] =
1963 GA_ReadOnly, LVF_READONLY,
1964 LVA_Clipped, LVF_CLIPPED,
1965 LVA_ShowSelected, LVF_SHOWSELECTED,
1966 LVA_DoMultiSelect, LVF_DOMULTISELECT,
1970 lv->Flags = PackBoolTags (
1973 (struct TagItem *)boolMap);
1977 /* Select font to use when drawing the Listview labels */
1979 /* First, try to get the font from our DrawInfo... */
1981 if ((drawinfo = (struct DrawInfo *)
1982 GetTagData (GA_DrawInfo, NULL, msg->ops_AttrList)))
1984 ASSERT_VALID_PTR(drawinfo)
1985 lv->Font = drawinfo->dri_Font;
1991 /* ...then override it with LVA_TextFont */
1993 if ((tag = FindTagItem (LVA_TextFont, msg->ops_AttrList)))
1997 lv->Font = (struct TextFont *)tag->ti_Data;
1998 ASSERT_VALID_PTR_OR_NULL(lv->Font)
2001 else /* Otherwise, try GA_TextAttr */
2003 struct TextAttr *attr;
2004 struct TextFont *font;
2006 if ((attr = (struct TextAttr *)GetTagData (GA_TextAttr,
2007 NULL, msg->ops_AttrList)))
2009 if ((font = OpenFont(attr)))
2011 /* Must remember to close this font later */
2012 lv->Flags |= LVF_CLOSEFONT;
2018 /* Calculate ItemHeight */
2021 /* Get height from font Y size */
2022 lv->ItemHeight = lv->Font->tf_YSize;
2026 lv->ItemHeight = GetTagData(LVA_ItemHeight, lv->ItemHeight, msg->ops_AttrList);
2027 lv->Spacing = GetTagData(LAYOUTA_Spacing, 0, msg->ops_AttrList);
2029 if ((tag = FindTagItem(LVA_MaxPen, msg->ops_AttrList)))
2030 lv->MaxPen = tag->ti_Data;
2035 max (drawinfo->dri_Pens[BACKGROUNDPEN],
2036 drawinfo->dri_Pens[TEXTPEN]),
2037 max (drawinfo->dri_Pens[FILLPEN],
2038 drawinfo->dri_Pens[FILLTEXTPEN]));
2040 lv->MaxPen = (ULONG)-1;
2044 lv->Total = GetTagData (LVA_Total, ~0, msg->ops_AttrList);
2046 if ((lv->Items = (APTR) GetTagData (LVA_StringList, NULL, msg->ops_AttrList)))
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;
2055 if (lv->Total == ~0)
2056 lv->Total = CountNodes(lv->Items);
2058 else if ((lv->Items = (APTR) GetTagData (LVA_StringArray, NULL, msg->ops_AttrList)))
2060 ASSERT_VALID_PTR_OR_NULL(lv->Items)
2061 lv->GetItemFunc = ArrayGetItem;
2062 lv->GetNextFunc = ArrayGetItem;
2063 lv->GetPrevFunc = ArrayGetItem;
2064 lv->DrawItemFunc = StringDrawItem;
2066 if (lv->Total == ~0)
2070 while (((APTR *)lv->Items)[i]) i++;
2074 else if ((lv->Items = (APTR)GetTagData(LVA_ImageList, NULL, msg->ops_AttrList)))
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;
2083 if (lv->Total == ~0)
2084 lv->Total = CountNodes(lv->Items);
2086 else if ((lv->Items = (APTR) GetTagData (LVA_ImageArray, NULL, msg->ops_AttrList)))
2088 ASSERT_VALID_PTR_OR_NULL(lv->Items)
2089 lv->GetItemFunc = ArrayGetItem;
2090 lv->GetNextFunc = ArrayGetItem;
2091 lv->GetPrevFunc = ArrayGetItem;
2092 lv->DrawItemFunc = ImageDrawItem;
2094 if (lv->Total == ~0)
2098 while (((APTR *)lv->Items)[i]) i++;
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);
2107 if ((lv->Visible = GetTagData(LVA_Visible, 0, msg->ops_AttrList)))
2110 GA_Height, lv->Visible * (lv->ItemHeight + lv->Spacing),
2114 /* Initialize Top and all related values */
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);
2121 lv->TopPtr = GetItem(lv, lv->Top);
2123 lv->ScrollRatio = GetTagData(LVA_ScrollRatio, 2, msg->ops_AttrList);
2124 ASSERT(lv->ScrollRatio != 0)
2126 if ((lv->OldSelected =
2127 lv->Selected = GetTagData(LVA_Selected, ~0, msg->ops_AttrList)) != ~0)
2128 lv->SelectedPtr = GetItem(lv, lv->Selected);
2130 if ((lv->CallBack = (struct Hook *)GetTagData (LVA_CallBack, NULL,
2131 msg->ops_AttrList)))
2133 ASSERT_VALID_PTR_OR_NULL(lv->CallBack->h_Entry)
2134 lv->DrawItemFunc = (LVDrawHook *) lv->CallBack->h_Entry;
2137 if (lv->Flags & LVF_CLIPPED)
2138 lv->ClipRegion = NewRegion ();
2145 static void LV_OMDispose(Class *cl, struct Gadget *g, Msg msg)
2149 lv = (struct LVData *) INST_DATA (cl, (Object *)g);
2151 DB2 (DBPRINTF ("ListViewClass: OM_DISPOSE\n");)
2152 ASSERT_VALID_PTR(lv)
2155 DisposeRegion (lv->ClipRegion);
2157 if (lv->Flags & LVF_CLOSEFONT)
2158 CloseFont(lv->Font);
2160 /* Our superclass will cleanup everything else now */
2161 DoSuperMethodA (cl, (Object *)g, (Msg) msg);
2163 /* From now on, our instance data is no longer available */
2168 /* Class support functions */
2171 Class *MakeListViewClass(void)
2175 if ((LVClass = MakeClass(NULL, GADGETCLASS, NULL, sizeof (struct LVData), 0)))
2176 LVClass->cl_Dispatcher.h_Entry = (ULONG (*)()) LVDispatcher;
2183 BOOL FreeListViewClass(Class *LVClass)
2185 ASSERT_VALID_PTR_OR_NULL(LVClass)
2186 return FreeClass(LVClass);
2190 #if (CLASS_FLAVOUR & FLAVOUR_CLASSLIB)
2192 * Class library support functions
2195 struct ClassLibrary * HOOKCALL _UserLibInit(REG(a6, struct ClassLibrary *mybase))
2197 /* Initialize SysBase */
2198 SysBase = *((struct ExecBase **)4UL);
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))))
2205 _UserLibCleanup(mybase);
2209 if (!(mybase->cl_Class = MakeListViewClass()))
2211 _UserLibCleanup(mybase);
2215 AddClass(mybase->cl_Class);
2219 struct ClassLibrary * HOOKCALL _UserLibCleanup(REG(a6, struct ClassLibrary *mybase))
2221 if (mybase->cl_Class)
2222 if (!FreeListViewClass(mybase->cl_Class))
2225 CloseLibrary((struct Library *)LayersBase);
2226 CloseLibrary((struct Library *)GfxBase);
2227 CloseLibrary((struct Library *)IntuitionBase);
2228 CloseLibrary((struct Library *)UtilityBase);
2233 Class * HOOKCALL _GetEngine(REG(a6, struct ClassLibrary *mybase))
2235 return mybase->cl_Class;
2238 #endif /* (CLASS_FLAVOUR & FLAVOUR_CLASSLIB) */