3 ** Copyright (C) 1996,97 Bernardo Innocenti
5 ** Use 4 chars wide TABs to read this file
7 ** GadTools-like `boopsi' ListView gadget class
10 #define USE_BUILTIN_MATH
11 #define INTUI_V36_NAMES_ONLY
13 #define CLIB_ALIB_PROTOS_H /* Avoid dupe defs of boopsi funcs */
15 #include <exec/types.h>
16 #include <exec/libraries.h>
17 #include <intuition/intuition.h>
18 #include <intuition/intuitionbase.h>
19 #include <intuition/classes.h>
20 #include <intuition/gadgetclass.h>
21 #include <graphics/gfxbase.h>
22 #include <graphics/gfxmacros.h>
24 #include <proto/intuition.h>
25 #include <proto/graphics.h>
26 #include <proto/layers.h>
27 #include <proto/utility.h>
33 #include "CompilerSpecific.h"
35 #include "BoopsiStubs.h"
37 #define LV_GADTOOLS_STUFF
38 #include "ListViewClass.h"
42 /* ListView private instance data */
45 /* Type of a listview hook function */
46 typedef ASMCALL APTR LVHook(
47 REG(a0, struct Hook *hook), REG(a1, APTR item), REG(a2, struct lvGetItem *lvg));
48 typedef ASMCALL APTR LVDrawHook(
49 REG(a0, struct Hook *hook), REG(a1, APTR item), REG(a2, struct lvDrawItem *lvdi));
53 APTR Items; /* The list/array of items */
54 LONG Top; /* Ordinal nr. of the top visible item */
55 APTR TopPtr; /* Pointer to the top visible item */
56 LONG Total; /* Total nr. of items in the list */
57 LONG Visible; /* Number of items visible in the list */
58 LONG PixelTop; /* Pixel-wise offset from the top */
59 LONG Selected; /* Ordinal nr. of the selected item */
60 APTR SelectedPtr; /* Pointer to the selected item */
61 ULONG SelectCount; /* Number of items currently selected */
62 ULONG MaxSelect; /* Maximum nr. of selections to allow */
64 /* Old values used to track scrolling amount in GM_RENDER */
70 ULONG DragSelect; /* Status of drag selection */
71 LONG ItemHeight; /* Height of one item in pixels */
72 LONG Spacing; /* Spacing between items in pixels */
73 LONG MaxScroll; /* Redraw all when scrolling too much */
74 LONG ScrollRatio; /* max visible/scrolled ratio */
75 ULONG *SelectArray; /* Array of selected items. May be NULL */
76 LONG BackupSelected; /* Used by RMB undo */
77 LONG BackupPixelTop; /* Used by RMB undo */
78 WORD MiddleMouseY; /* Initial Y position for MMB scrolling */
79 ULONG Flags; /* See <listviewclass.h> */
80 ULONG MaxPen; /* Highest pen number used */
81 ULONG DoubleClickSecs, DoubleClickMicros;
83 /* User or internal hooks */
87 LVHook *DrawBeginFunc;
89 LVDrawHook *DrawItemFunc;
90 struct Hook *CallBack; /* Callback hook provided by user */
92 struct TextFont *Font; /* Font used to render text labels */
93 struct Region *ClipRegion; /* Used in LVA_Clipped mode */
95 /* These two have the same meaning, but we keep both updated
96 * because the Rectangle structure (MinX, MinY, MaxX, MaxY)
97 * is more handy in some cases, while the IBox structure
98 * (Left/Top/Width/Height) is best for other cases.
101 struct Rectangle GRect;
106 /* Local function prototypes */
108 static void LV_GMRender (Class *cl, struct Gadget *g, struct gpRender *msg);
109 static ULONG LV_GMGoActive (Class *cl, struct Gadget *g, struct gpInput *msg);
110 static ULONG LV_GMHandleInput(Class *cl, struct Gadget *g, struct gpInput *msg);
111 static void LV_GMGoInactive (Class *cl, struct Gadget *g, struct gpGoInactive *msg);
112 static void LV_GMLayout (Class *cl, struct Gadget *g, struct gpLayout *msg);
113 static ULONG LV_OMSet (Class *cl, struct Gadget *g, struct opUpdate *msg);
114 static ULONG LV_OMGet (Class *cl, struct Gadget *g, struct opGet *msg);
115 static ULONG LV_OMNew (Class *cl, struct Gadget *g, struct opSet *msg);
116 static void LV_OMDispose (Class *cl, struct Gadget *g, Msg msg);
118 static void RedrawItems (struct LVData *lv, struct gpRender *msg, ULONG first, ULONG last, APTR item);
119 INLINE LONG ItemHit (struct LVData *lv, WORD x, WORD y);
120 INLINE void GetGadgetBox (struct GadgetInfo *ginfo, struct ExtGadget *g, struct IBox *box, struct Rectangle *rect);
121 INLINE APTR GetItem (struct LVData *lv, ULONG num);
122 INLINE APTR GetNext (struct LVData *lv, APTR item, ULONG num);
123 INLINE APTR GetPrev (struct LVData *lv, APTR item, ULONG num);
124 INLINE ULONG CountNodes (struct List *list);
125 static ULONG CountSelections (struct LVData *lv);
126 INLINE ULONG IsItemSelected (struct LVData *lv, APTR item, ULONG num);
128 /* Definitions for the builtin List hooks */
132 LVDrawHook ListStringDrawItem;
133 LVDrawHook ListImageDrawItem;
135 /* Definitions for the builtin Array hooks */
137 LVDrawHook StringDrawItem;
138 LVDrawHook ImageDrawItem;
142 static ULONG HOOKCALL LVDispatcher (
144 REG(a2, struct Gadget *g),
147 /* ListView class dispatcher - Handles all supported methods */
153 switch (msg->MethodID)
156 LV_GMRender (cl, g, (struct gpRender *)msg);
160 return LV_GMGoActive (cl, g, (struct gpInput *)msg);
163 return LV_GMHandleInput (cl, g, (struct gpInput *)msg);
166 LV_GMGoInactive (cl, g, (struct gpGoInactive *)msg);
170 /* This method is only supported on V39 and above */
171 LV_GMLayout (cl, g, (struct gpLayout *)msg);
176 return LV_OMSet (cl, g, (struct opUpdate *)msg);
179 return LV_OMGet (cl, g, (struct opGet *)msg);
182 return LV_OMNew (cl, g, (struct opSet *)msg);
185 LV_OMDispose (cl, g, msg);
189 /* Unsupported method: let our superclass's
190 * dispatcher take a look at it.
192 return DoSuperMethodA (cl, (Object *)g, msg);
198 INLINE void GetItemBounds (struct LVData *lv, struct Rectangle *rect, LONG item)
200 /* Compute the bounding box to render the given item and store it in the passed
201 * Rectangle structure.
205 ASSERT_VALIDNO0(rect)
206 ASSERT(item < lv->Total)
209 rect->MinX = lv->GRect.MinX;
210 rect->MaxX = lv->GRect.MaxX;
211 rect->MinY = lv->ClipRegion ?
212 (lv->GRect.MinY + item * (lv->ItemHeight + lv->Spacing) - lv->PixelTop) :
213 (lv->GRect.MinY + (item - lv->Top) * (lv->ItemHeight + lv->Spacing));
214 rect->MaxY = rect->MinY + lv->ItemHeight - 1;
219 static void RedrawItems (struct LVData *lv, struct gpRender *msg, ULONG first, ULONG last, APTR item)
221 /* Redraw items from <min> to <max>. No sanity checks are performed
222 * to ensure that all items between <min> and <max> are really visible.
225 struct lvDrawItem lvdi;
231 ASSERT(first <= last)
232 ASSERT(last < lv->Total)
234 DB (kprintf (" RedrawItems (first = %ld, last = %ld)\n", first, last);)
237 lvdi.lvdi_Current = first;
238 lvdi.lvdi_Items = lv->Items;
239 lvdi.lvdi_RastPort = msg->gpr_RPort;
240 lvdi.lvdi_DrawInfo = msg->gpr_GInfo->gi_DrInfo;
241 lvdi.lvdi_Flags = lv->Flags;
243 GetItemBounds (lv, &lvdi.lvdi_Bounds, first);
247 lvdi.lvdi_MethodID = LV_GETITEM;
248 item = lv->GetItemFunc (lv->CallBack, NULL, (struct lvGetItem *)&lvdi);
251 if (lv->DrawBeginFunc)
253 lvdi.lvdi_MethodID = LV_DRAWBEGIN;
254 lv->DrawBeginFunc (lv->CallBack, item, (struct lvDrawBegin *)&lvdi);
259 if (lv->Flags & LVF_DOMULTISELECT)
262 /* Array selection */
263 selected = lv->SelectArray[lvdi.lvdi_Current];
265 if (lv->Flags & LVF_LIST)
267 selected = (((struct Node *)item)->ln_Type);
272 /* Single selection */
273 selected = (lvdi.lvdi_Current == lv->Selected);
275 lvdi.lvdi_State = selected ? LVR_SELECTED : LVR_NORMAL;
277 lvdi.lvdi_MethodID = LV_DRAW;
278 lv->DrawItemFunc (lv->CallBack, item, &lvdi);
280 if (++lvdi.lvdi_Current > last)
283 lvdi.lvdi_MethodID = LV_GETNEXT;
284 item = lv->GetNextFunc (lv->CallBack, item, (struct lvGetNext *)&lvdi);
286 lvdi.lvdi_Bounds.MinY += lv->ItemHeight + lv->Spacing;
287 lvdi.lvdi_Bounds.MaxY += lv->ItemHeight + lv->Spacing;
292 lvdi.lvdi_MethodID = LV_DRAWEND;
293 lv->DrawEndFunc (lv->CallBack, item, (struct lvDrawEnd *)&lvdi);
299 static void LV_GMRender (Class *cl, struct Gadget *g, struct gpRender *msg)
301 struct LVData *lv = INST_DATA (cl, g);
302 struct RastPort *rp = msg->gpr_RPort;
307 DB (kprintf ("GM_RENDER: msg->gpr_Redraw = %s\n",
308 (msg->gpr_Redraw == GREDRAW_TOGGLE) ? "GREDRAW_TOGGLE" :
309 ((msg->gpr_Redraw == GREDRAW_REDRAW) ? "GREDRAW_REDRAW" :
310 ((msg->gpr_Redraw == GREDRAW_UPDATE) ? "GREDRAW_UPDATE" :
311 "*** Unknown ***")) );)
315 /* Pre-V39 Intuition won't call our GM_LAYOUT method, so we must
316 * always call it before redrawing the gadget.
318 if ((IntuitionBase->LibNode.lib_Version < 39) &&
319 (msg->gpr_Redraw == GREDRAW_REDRAW))
320 LV_GMLayout (cl, g, (struct gpLayout *)msg);
321 #endif /* !OS30_ONLY */
323 if (lv->Flags & LVF_DONTDRAW)
326 if (lv->Items && lv->Visible)
328 struct TextFont *oldfont = NULL;
329 struct Region *oldregion = NULL;
331 if (rp->Font != lv->Font)
334 SetFont (rp, lv->Font);
339 ASSERT_VALIDNO0(lv->ClipRegion)
340 oldregion = InstallClipRegion (rp->Layer, lv->ClipRegion);
343 switch (msg->gpr_Redraw)
345 case GREDRAW_TOGGLE: /* Toggle selected item */
347 BOOL drawnew = (lv->Selected >= lv->Top) && (lv->Selected < lv->Top + lv->Visible),
348 drawold = (lv->OldSelected >= lv->Top) && (lv->OldSelected < lv->Top + lv->Visible);
350 if (drawold || drawnew)
352 struct lvDrawItem lvdi;
353 lvdi.lvdi_Items = lv->Items;
354 lvdi.lvdi_RastPort = rp;
355 lvdi.lvdi_DrawInfo = msg->gpr_GInfo->gi_DrInfo;
356 lvdi.lvdi_Flags = lv->Flags;
359 if (lv->DrawBeginFunc)
361 lvdi.lvdi_MethodID = LV_DRAWBEGIN;
362 lv->DrawBeginFunc (lv->CallBack, NULL, (struct lvDrawBegin *)&lvdi);
365 lvdi.lvdi_MethodID = LV_DRAW;
369 GetItemBounds (lv, &lvdi.lvdi_Bounds, lv->Selected);
370 lvdi.lvdi_State = IsItemSelected (lv, lv->SelectedPtr, lv->Selected) ?
371 LVR_SELECTED : LVR_NORMAL;
372 lvdi.lvdi_Current = lv->Selected;
374 lv->DrawItemFunc (lv->CallBack, lv->SelectedPtr, &lvdi);
379 GetItemBounds (lv, &lvdi.lvdi_Bounds, lv->OldSelected);
380 lvdi.lvdi_State = IsItemSelected (lv, lv->OldSelectedPtr, lv->OldSelected) ?
381 LVR_SELECTED : LVR_NORMAL;
382 lvdi.lvdi_Current = lv->OldSelected;
384 lv->DrawItemFunc (lv->CallBack, lv->OldSelectedPtr, &lvdi);
389 lvdi.lvdi_MethodID = LV_DRAWEND;
390 lv->DrawEndFunc (lv->CallBack, NULL, (struct lvDrawEnd *)&lvdi);
394 lv->OldSelected = lv->Selected;
395 lv->OldSelectedPtr = lv->SelectedPtr;
400 case GREDRAW_REDRAW: /* Redraw everything */
404 /* Set the background pen */
405 SetAPen (rp, msg->gpr_GInfo->gi_DrInfo->dri_Pens[BACKGROUNDPEN]);
406 /* SetAPen (rp, -1); Used to debug clearing code */
408 /* Now clear the spacing between the items */
409 if (lv->Spacing && lv->Items && lv->Visible)
413 ycoord = lv->GRect.MinY + lv->ItemHeight;
414 lastitem = min (lv->Visible, lv->Total - lv->Top) - 1;
416 for (i = 0 ; i < lastitem; i++)
418 RectFill (rp, lv->GRect.MinX, ycoord,
419 lv->GRect.MaxX, ycoord + lv->Spacing - 1);
421 ycoord += lv->ItemHeight + lv->Spacing;
425 ycoord = lv->GRect.MinY + min (lv->Visible, lv->Total - lv->Top)
428 /* Now let's clear bottom part of gadget */
429 RectFill (rp, lv->GRect.MinX, ycoord,
430 lv->GRect.MaxX, lv->GRect.MaxY);
432 /* Finally, draw the items */
433 RedrawItems (lv, msg, lv->Top,
434 min (lv->Top + lv->Visible, lv->Total) - 1, lv->TopPtr);
439 case GREDRAW_UPDATE: /* Scroll ListView */
441 LONG scroll_dy, scroll_height;
445 /* Calculate scrolling amount in pixels */
446 if (!(scroll_dy = lv->PixelTop - lv->OldPixelTop))
447 /* Do nothing if called improperly */
450 /* Scroll everything */
451 scroll_height = lv->GBox.Height;
455 if (!(lv->Top - lv->OldTop))
456 /* Do nothing if called improperly */
459 /* Calculate scrolling amount in pixels */
460 scroll_dy = (lv->Top - lv->OldTop) * (lv->ItemHeight + lv->Spacing);
462 /* Only scroll upto last visible item */
463 scroll_height = lv->Visible * (lv->ItemHeight + lv->Spacing) - lv->Spacing;
466 if (abs(scroll_dy) > lv->MaxScroll)
468 /* Redraw everything when listview has been scrolled too much */
469 RedrawItems (lv, msg, lv->Top,
470 min (lv->Top + lv->Visible, lv->Total) - 1, lv->TopPtr);
475 if (GfxBase->LibNode.lib_Version >= 39)
476 #endif /* OS30_ONLY */
477 /* Optimize scrolling on planar displays if possible */
478 SetMaxPen (rp, lv->MaxPen);
480 /* We use ClipBlit() to scroll the listview because it doesn't clear
481 * the scrolled region like ScrollRaster() would do. Unfortunately,
482 * ClipBlit() does not scroll along the damage regions, so we also
483 * call ScrollRaster() with the mask set to 0, which will scroll the
484 * layer damage regions without actually modifying the display.
487 if (scroll_dy > 0) /* Scroll Down */
489 ClipBlit (rp, lv->GBox.Left, lv->GBox.Top + scroll_dy,
490 rp, lv->GBox.Left, lv->GBox.Top,
491 lv->GBox.Width, scroll_height - scroll_dy,
496 /* NOTE: We subtract 1 pixel to avoid an exact division which would
497 * render one item beyond the end when the slider is dragged
500 RedrawItems (lv, msg,
501 (lv->OldPixelTop + lv->GBox.Height) / (lv->ItemHeight + lv->Spacing),
502 (lv->PixelTop + lv->GBox.Height - 1) / (lv->ItemHeight + lv->Spacing),
506 RedrawItems (lv, msg,
507 lv->Visible + lv->OldTop,
508 lv->Visible + lv->Top - 1,
513 ClipBlit (rp, lv->GBox.Left, lv->GBox.Top,
514 rp, lv->GBox.Left, lv->GBox.Top - scroll_dy,
515 lv->GBox.Width, scroll_height + scroll_dy,
520 RedrawItems (lv, msg,
521 lv->PixelTop / (lv->ItemHeight + lv->Spacing),
522 lv->OldPixelTop / (lv->ItemHeight + lv->Spacing),
525 RedrawItems (lv, msg,
532 /* Some layers magic adapded from "MUI.undoc",
533 * by Alessandro Zummo <azummo@ita.flashnet.it>
535 #define LayerCovered(l) \
536 ((!(l)->ClipRect) || memcmp (&(l)->ClipRect->bounds, \
537 &(l)->bounds, sizeof (struct Rectangle)))
538 #define LayerDamaged(l) \
539 ((l)->DamageList && (l)->DamageList->RegionRectangle)
540 #define NeedZeroScrollRaster(l) (LayerCovered(l) || LayerDamaged(l))
543 /* This will scroll the layer damage regions without actually
544 * scrolling the display, but only if our layer really needs it.
546 if ((rp->Layer->Flags & LAYERSIMPLE) && NeedZeroScrollRaster (rp->Layer))
548 UBYTE oldmask = rp->Mask; /* Would GetRPAttr() be better? */
550 DB (kprintf (" Calling ScrollRaster()\n");)
552 SetWriteMask (rp, 0);
554 SafeSetWriteMask (rp, 0);
555 #endif /* OS30_ONLY */
556 ScrollRaster (rp, 0, scroll_dy,
557 lv->GRect.MinX, lv->GRect.MinY,
562 SetWriteMask (rp, oldmask);
564 SafeSetWriteMask (rp, oldmask);
565 #endif /* OS30_ONLY */
569 if (GfxBase->LibNode.lib_Version >= 39)
570 #endif /* OS30_ONLY */
571 /* Restore MaxPen in our RastPort */
575 /* Update OldTop to the current Top item and
576 * OldPixelTop to the current PixelTop position.
578 lv->OldTop = lv->Top;
579 lv->OldPixelTop = lv->PixelTop;
590 ASSERT_VALIDNO0(oldregion)
591 /* Restore old clipping region in our layer */
592 InstallClipRegion (rp->Layer, oldregion);
596 SetFont (rp, oldfont);
598 else if (msg->gpr_Redraw == GREDRAW_REDRAW)
600 /* Clear all gadget contents */
601 SetAPen (rp, msg->gpr_GInfo->gi_DrInfo->dri_Pens[BACKGROUNDPEN]);
602 RectFill (rp, lv->GRect.MinX, lv->GRect.MinY, lv->GRect.MaxX, lv->GRect.MaxY);
608 static ULONG LV_GMGoActive (Class *cl, struct Gadget *g, struct gpInput *msg)
610 struct LVData *lv = INST_DATA (cl, g);
613 DB (kprintf ("GM_GOACTIVE: gpi_IEvent = $%lx\n", msg->gpi_IEvent);)
619 g->Flags |= GFLG_SELECTED;
621 /* Do not process InputEvent when the gadget has been
622 * activated by ActivateGadget().
624 if (!msg->gpi_IEvent)
627 /* Note: The input event that triggered the gadget
628 * activation (usually a mouse click) should be passed
629 * to the GM_HANDLEINPUT method, so we fall down to it.
631 return LV_GMHandleInput (cl, g, msg);
636 INLINE LONG ItemHit (struct LVData *lv, WORD x, WORD y)
638 /* Determine which item has been hit with gadget relative
639 * coordinates x and y.
642 return ((y + lv->PixelTop) / (lv->ItemHeight + lv->Spacing));
647 static ULONG LV_GMHandleInput (Class *cl, struct Gadget *g, struct gpInput *msg)
649 struct LVData *lv = INST_DATA (cl, g);
650 struct InputEvent *ie = msg->gpi_IEvent;
651 ULONG result = GMR_MEACTIVE;
654 /* DB (kprintf ("GM_HANDLEINPUT: ie_Class = $%lx, ie->ie_Code = $%lx, "
655 "gpi_Mouse.X = %ld, gpi_Mouse.Y = %ld\n",
656 ie->ie_Class, ie->ie_Code, msg->gpi_Mouse.X, msg->gpi_Mouse.Y);)
658 switch (ie->ie_Class)
668 if ((lv->Flags & LVF_READONLY) || (ie->ie_Qualifier & IEQUALIFIER_CONTROL))
670 if (ie->ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
671 pos = lv->Top - lv->Visible / 2;
675 if (pos < 0) pos = 0;
683 if (ie->ie_Qualifier & (IEQUALIFIER_LALT | IEQUALIFIER_RALT))
685 else if (ie->ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
686 pos = lv->Selected - lv->Visible + 1;
688 pos = lv->Selected - 1;
690 if (pos < 0) pos = 0;
692 tags[0] = LVA_Selected;
694 tags[2] = LVA_MakeVisible;
701 if ((lv->Flags & LVF_READONLY) || (ie->ie_Qualifier & IEQUALIFIER_CONTROL))
703 if (ie->ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
704 pos = lv->Top + lv->Visible / 2;
714 if (ie->ie_Qualifier & (IEQUALIFIER_LALT | IEQUALIFIER_RALT))
716 else if (ie->ie_Qualifier & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT))
717 pos = lv->Selected + lv->Visible - 1;
719 pos = lv->Selected + 1;
721 tags[0] = LVA_Selected;
723 tags[2] = LVA_MakeVisible;
732 } /* End switch (ie->ie_Code) */
734 if (tags[0] != TAG_DONE)
735 DoMethod ((Object *)g, OM_UPDATE, tags, msg->gpi_GInfo,
736 (ie->ie_Qualifier & IEQUALIFIERB_REPEAT) ? OPUF_INTERIM : 0);
741 case IECLASS_RAWMOUSE:
749 /* Check for click outside gadget box */
751 if ((msg->gpi_Mouse.X < 0) ||
752 (msg->gpi_Mouse.X >= lv->GBox.Width) ||
753 (msg->gpi_Mouse.Y < 0) ||
754 (msg->gpi_Mouse.Y >= lv->GBox.Height))
760 /* Start dragging mode */
761 lv->Flags |= LVF_DRAGGING;
763 if (lv->Flags & LVF_READONLY)
767 selected = ItemHit (lv, msg->gpi_Mouse.X, msg->gpi_Mouse.Y);
769 /* No action when selecting over blank space in the bottom */
770 if ((selected < 0) || (selected >= lv->Total))
773 /* Backup current selection for RMB undo */
774 lv->BackupSelected = lv->Selected;
775 lv->BackupPixelTop = lv->PixelTop;
777 if (selected == lv->Selected)
779 /* Check for double click */
780 if (DoubleClick (lv->DoubleClickSecs, lv->DoubleClickMicros,
781 ie->ie_TimeStamp.tv_secs, ie->ie_TimeStamp.tv_micro))
782 UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
783 LVA_DoubleClick, selected,
787 if (lv->Flags & LVF_DOMULTISELECT)
788 /* Setup for multiple items drag selection */
789 lv->DragSelect = IsItemSelected (lv, NULL, selected) ?
790 LVA_DeselectItem : LVA_SelectItem;
791 else if (g->Activation & GACT_TOGGLESELECT)
793 /* Setup for single item toggle */
794 lv->DragSelect = LVA_Selected;
795 if (selected == lv->Selected)
798 else /* Single selection */
799 /* Setup for single item drag selection */
800 lv->DragSelect = LVA_Selected;
802 UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
803 lv->DragSelect, selected,
806 /* Save double click info */
807 lv->DoubleClickSecs = ie->ie_TimeStamp.tv_secs;
808 lv->DoubleClickMicros = ie->ie_TimeStamp.tv_micro;
812 /* Undo selection & position when RMB is pressed */
813 if (lv->Flags & (LVF_DRAGGING | LVF_SCROLLING))
815 /* Stop dragging and scrolling modes */
816 lv->Flags &= ~(LVF_DRAGGING | LVF_SCROLLING);
818 if ((lv->BackupSelected != lv->Selected) ||
819 (lv->BackupPixelTop != lv->PixelTop))
821 UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
822 (lv->Flags & LVF_READONLY) ?
823 TAG_IGNORE : LVA_Selected, lv->BackupSelected,
824 LVA_PixelTop, lv->BackupPixelTop,
829 /* Deactivate gadget on menu button press */
835 /* Argh, input.device never sends this event in V40! */
836 DB (kprintf ("scrolling on\n");)
838 /* Start MMB scrolling */
839 lv->BackupPixelTop = lv->PixelTop;
840 lv->BackupSelected = lv->Selected;
841 lv->MiddleMouseY = msg->gpi_Mouse.Y;
842 lv->Flags |= LVF_DRAGGING;
847 /* Stop dragging mode */
848 lv->Flags &= ~LVF_DRAGGING;
850 if (g->Activation & GACT_RELVERIFY)
852 /* Send IDCMP_GADGETUP message to our parent window */
853 msg->gpi_Termination = &lv->Selected;
854 result = GMR_NOREUSE | GMR_VERIFY;
859 /* Argh, input.device never sends this event in V40! */
860 DB (kprintf ("scrolling off\n");)
862 /* Stop MMB scrolling */
863 lv->Flags &= ~LVF_SCROLLING;
866 default: /* Mouse moved */
869 if (lv->Flags & LVF_DRAGGING)
872 selected = ItemHit (lv, msg->gpi_Mouse.X, msg->gpi_Mouse.Y);
874 /* Moved over another item inside the currently displayed list? */
875 if ((selected != lv->Selected) && !(lv->Flags & LVF_READONLY)
876 && (selected >= lv->Top) && (selected < lv->Top + lv->Visible))
878 /* Single selection */
880 /* Call our OM_UPDATE method to change the attributes.
881 * This will also send notification to targets and
882 * update the contents of the gadget.
884 UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
885 lv->DragSelect, selected,
891 if (lv->Flags & LVF_SCROLLING)
893 DB (kprintf (" scrolling\n");)
894 selected = (msg->gpi_Mouse.Y - lv->MiddleMouseY)
895 + lv->BackupPixelTop;
897 UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
898 LVA_PixelTop, selected < 0 ? 0 : selected,
902 } /* End switch (ie->ie_Code) */
910 if (lv->Flags & LVF_DRAGGING)
912 /* Mouse above the upper item? */
913 if ((msg->gpi_Mouse.Y < 0) && lv->Top)
916 UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
918 (lv->Flags & LVF_READONLY) ? TAG_IGNORE : LVA_Selected, lv->Top - 1,
921 /* Mouse below the bottom item? */
922 else if (msg->gpi_Mouse.Y / (lv->ItemHeight + lv->Spacing) >= lv->Visible)
925 UpdateAttrs ((Object *)g, msg->gpi_GInfo, 0,
927 (lv->Flags & LVF_READONLY) ? TAG_IGNORE : LVA_Selected, lv->Top + lv->Visible,
936 } /* End switch (ie->ie_Class) */
943 static void LV_GMGoInactive (Class *cl, struct Gadget *g, struct gpGoInactive *msg)
945 struct LVData *lv = INST_DATA (cl, g);
948 DB (kprintf ("GM_GOINACTIVE\n");)
950 /* Stop dragging and scrolling modes */
951 lv->Flags &= ~(LVF_DRAGGING | LVF_SCROLLING);
953 /* Mark gadget inactive */
954 g->Flags &= ~GFLG_SELECTED;
959 INLINE void GetGadgetBox (struct GadgetInfo *ginfo, struct ExtGadget *g, struct IBox *box, struct Rectangle *rect)
961 /* Gets the actual IBox where a gadget exists in a window.
962 * The special cases it handles are all the REL#? (relative positioning flags).
964 * This function returns the gadget size in both the provided IBox and
965 * Rectangle structures, computing the values from the coordinates of the
966 * gadget and the window where it lives.
970 ASSERT_VALIDNO0(ginfo)
972 ASSERT_VALIDNO0(rect)
974 DB (if ((g->Flags & GFLG_EXTENDED) && (g->MoreFlags & GMORE_BOUNDS))
975 kprintf (" Gadget has valid bounds\n");)
977 box->Left = g->LeftEdge;
978 if (g->Flags & GFLG_RELRIGHT)
979 box->Left += ginfo->gi_Domain.Width - 1;
981 box->Top = g->TopEdge;
982 if (g->Flags & GFLG_RELBOTTOM)
983 box->Top += ginfo->gi_Domain.Height - 1;
985 box->Width = g->Width;
986 if (g->Flags & GFLG_RELWIDTH)
987 box->Width += ginfo->gi_Domain.Width;
989 box->Height = g->Height;
990 if (g->Flags & GFLG_RELHEIGHT)
991 box->Height += ginfo->gi_Domain.Height;
993 /* Convert IBox to Rectangle coordinates system */
994 rect->MinX = box->Left;
995 rect->MinY = box->Top;
996 rect->MaxX = box->Left + box->Width - 1;
997 rect->MaxY = box->Top + box->Height - 1;
1002 static void LV_GMLayout (Class *cl, struct Gadget *g, struct gpLayout *msg)
1004 struct LVData *lv = INST_DATA (cl, g);
1007 DB (kprintf ("GM_LAYOUT\n");)
1009 ASSERT_VALIDNO0(msg->gpl_GInfo)
1010 ASSERT_VALIDNO0(msg->gpl_GInfo->gi_DrInfo)
1011 ASSERT_VALIDNO0(msg->gpl_GInfo->gi_DrInfo->dri_Font)
1014 /* We shouldn't draw inside the GM_LAYOUT method: the
1015 * GM_REDRAW method will be called by Intuition shortly after.
1017 lv->Flags |= LVF_DONTDRAW;
1019 /* Collect new gadget size */
1020 GetGadgetBox (msg->gpl_GInfo, (struct ExtGadget *)g, &lv->GBox, &lv->GRect);
1022 /* Calculate clipping region for gadget LVA_Clipped mode */
1025 /* Remove previous clipping rectangle, if any */
1026 ClearRegion (lv->ClipRegion);
1028 /* Install a clipping rectangle around the gadget box.
1029 * We don't check for failure because we couldn't do
1030 * anything otherwise.
1032 OrRectRegion (lv->ClipRegion, &lv->GRect);
1035 /* Setup Font if not yet done */
1038 lv->Font = msg->gpl_GInfo->gi_DrInfo->dri_Font;
1039 if (!lv->ItemHeight)
1040 lv->ItemHeight = lv->Font->tf_YSize;
1046 /* Allow displaying an incomplete item at the bottom of the listview,
1047 * plus one incomplete item at the top.
1049 visible = (lv->GBox.Height + lv->ItemHeight + lv->Spacing - 1) /
1050 (lv->ItemHeight + lv->Spacing);
1052 /* get maximum number of items fitting in the listview height.
1053 * Ignore spacing for the last visible item.
1055 visible = (lv->GBox.Height + lv->Spacing) / (lv->ItemHeight + lv->Spacing);
1060 lv->MaxScroll = lv->GBox.Height / lv->ScrollRatio;
1063 /* Send initial notification to our sliders, or update them to
1064 * the new values. The slieders will get the correct size also
1065 * in the special case where the list is attached at creation
1066 * time and the sliders are attached later using a model object.
1068 * The private class attribute LVA_Visible will handle everything for us.
1070 UpdateAttrs ((Object *)g, msg->gpl_GInfo, 0,
1071 LVA_Visible, visible,
1074 /* Re-enable drawing */
1075 lv->Flags &= ~LVF_DONTDRAW;
1080 static ULONG LV_OMSet (Class *cl, struct Gadget *g, struct opUpdate *msg)
1082 struct LVData *lv = INST_DATA (cl, g);
1084 *tstate = msg->opu_AttrList;
1086 UWORD action = 0; /* See flag definitions above */
1089 ASSERT_VALID(tstate)
1091 DB (kprintf ((msg->MethodID == OM_SET) ? "OM_SET:\n" : "OM_UPDATE:\n");)
1094 /* Definitions for the ations to be taken right after
1095 * scanning the attributes list in OM_SET/OM_UPDATE.
1096 * For speed reasons we pack them together in a single variable,
1097 * so we can set and test multiple flags in once.
1099 #define LVF_DO_SUPER_METHOD (1<<0)
1100 #define LVF_REDRAW (1<<1)
1101 #define LVF_SCROLL (1<<2)
1102 #define LVF_TOGGLESELECT (1<<3)
1103 #define LVF_NOTIFY (1<<4)
1104 #define LVF_NOTIFYALL (1<<5)
1107 while (ti = NextTagItem (&tstate))
1111 DB (kprintf (" GA_ID, %ld\n", ti->ti_Data);)
1113 /* Avoid sending all taglists to our superclass because of GA_ID */
1114 g->GadgetID = ti->ti_Data;
1118 DB (kprintf (" LVA_Selected, %ld\n", ti->ti_Data);)
1122 LONG newselected = ti->ti_Data;
1124 if (newselected != ~0)
1125 newselected = (newselected >= lv->Total) ?
1126 (lv->Total - 1) : newselected;
1128 if (lv->Selected != newselected)
1130 if (((lv->Selected >= lv->Top) &&
1131 (lv->Selected < lv->Top + lv->Visible)) ||
1132 ((newselected >= lv->Top) &&
1133 (newselected < lv->Top + lv->Visible)))
1134 action |= LVF_TOGGLESELECT;
1136 lv->Selected = newselected;
1138 if (newselected == ~0)
1139 lv->SelectedPtr = NULL;
1141 lv->SelectedPtr = GetItem (lv, newselected);
1143 action |= LVF_NOTIFY;
1149 DB (kprintf (" LVA_Top, %ld\n", ti->ti_Data);)
1151 if ((lv->Top != ti->ti_Data) && lv->Items)
1153 /* This will scroll the listview contents when needed */
1155 lv->Top = ((ti->ti_Data + lv->Visible) >= lv->Total) ?
1156 ((lv->Total <= lv->Visible) ? 0 : (lv->Total - lv->Visible))
1158 lv->PixelTop = lv->Top * (lv->ItemHeight + lv->Spacing);
1160 /* TODO: optimize for some special cases:
1161 * Top == oldtop + 1 and Top == oldtop - 1
1163 lv->TopPtr = GetItem (lv, lv->Top);
1164 action |= LVF_SCROLL | LVF_NOTIFY;
1169 DB (kprintf (" LVA_Total, %ld\n", ti->ti_Data);)
1171 /* We don't hhandle LVA_Total except when setting a new
1172 * list or array of items.
1176 case LVA_SelectItem:
1177 DB (kprintf (" LVA_SelectItem, %ld\n", ti->ti_Data);)
1179 /* Check LVA_MaxSelect */
1180 if (lv->SelectCount >= lv->MaxSelect)
1181 DisplayBeep (msg->opu_GInfo ? msg->opu_GInfo->gi_Screen : NULL);
1184 LONG newselected = (ti->ti_Data >= lv->Total) ?
1185 (lv->Total - 1) : ti->ti_Data;
1187 if (((lv->Selected >= lv->Top) &&
1188 (lv->Selected < lv->Top + lv->Visible)) ||
1189 ((newselected >= lv->Top) &&
1190 (newselected < lv->Top + lv->Visible)))
1191 action |= LVF_TOGGLESELECT;
1193 lv->Selected = newselected;
1194 lv->SelectedPtr = GetItem (lv, newselected);
1196 if (!IsItemSelected (lv, lv->SelectedPtr, newselected))
1200 if (lv->SelectArray)
1201 lv->SelectArray[newselected] = lv->SelectCount;
1202 else if (lv->Flags & LVF_LIST)
1203 ((struct Node *)lv->SelectedPtr)->ln_Type = lv->SelectCount;
1205 action |= LVF_NOTIFY;
1209 case LVA_DeselectItem:
1210 DB (kprintf (" LVA_DeselectItem, %ld\n", ti->ti_Data);)
1214 LONG newselected = (ti->ti_Data >= lv->Total) ?
1215 (lv->Total - 1) : ti->ti_Data;
1217 if (((lv->Selected >= lv->Top) &&
1218 (lv->Selected < lv->Top + lv->Visible)) ||
1219 ((newselected >= lv->Top) &&
1220 (newselected < lv->Top + lv->Visible)))
1221 action |= LVF_TOGGLESELECT;
1223 lv->Selected = newselected;
1224 lv->SelectedPtr = GetItem (lv, newselected);
1226 if (IsItemSelected (lv, lv->SelectedPtr, newselected))
1230 if (lv->SelectArray)
1231 lv->SelectArray[lv->Selected] = 0;
1232 else if (lv->Flags & LVF_LIST)
1233 ((struct Node *)lv->SelectedPtr)->ln_Type = 0;
1235 action |= LVF_NOTIFY;
1240 case LVA_ToggleItem:
1241 DB (kprintf (" LVA_ToggleItem, %ld\n", ti->ti_Data);)
1245 LONG newselected = newselected = (ti->ti_Data >= lv->Total) ?
1246 (lv->Total - 1) : ti->ti_Data;
1248 if (((lv->Selected >= lv->Top) &&
1249 (lv->Selected < lv->Top + lv->Visible)) ||
1250 ((newselected >= lv->Top) &&
1251 (newselected < lv->Top + lv->Visible)))
1252 action |= LVF_TOGGLESELECT;
1254 lv->Selected = newselected;
1255 lv->SelectedPtr = GetItem (lv, newselected);
1257 if (IsItemSelected (lv, lv->SelectedPtr, lv->Selected))
1262 if (lv->SelectArray)
1263 lv->SelectArray[lv->Selected] = 0;
1264 else if (lv->Flags & LVF_LIST)
1265 ((struct Node *)lv->SelectedPtr)->ln_Type = 0;
1269 /* Check LVA_MaxSelect */
1270 if (lv->SelectCount >= lv->MaxSelect)
1271 DisplayBeep (msg->opu_GInfo ? msg->opu_GInfo->gi_Screen : NULL);
1277 if (lv->SelectArray)
1278 lv->SelectArray[lv->Selected] = lv->SelectCount;
1279 else if (lv->Flags & LVF_LIST)
1280 ((struct Node *)lv->SelectedPtr)->ln_Type = lv->SelectCount;
1284 action |= LVF_NOTIFY;
1288 case LVA_ClearSelected:
1289 DB (kprintf (" LVA_ClearSelected, %ld\n", ti->ti_Data);)
1293 LONG newselected = ti->ti_Data;
1296 if (((lv->Selected >= lv->Top) &&
1297 (lv->Selected < lv->Top + lv->Visible)) ||
1298 ((newselected >= lv->Top) &&
1299 (newselected < lv->Top + lv->Visible)))
1300 action |= LVF_TOGGLESELECT;
1303 lv->SelectedPtr = NULL;
1304 lv->SelectCount = 0;
1307 /* Clear the selections */
1309 if (lv->SelectArray)
1310 for (i = 0; i < lv->Total; i++)
1311 lv->SelectArray[i] = 0;
1312 else if (lv->Flags & LVF_LIST)
1316 for (node = ((struct List *)lv->Items)->lh_Head;
1317 node = node->ln_Succ;
1322 /* TODO: check if total redraw is really needed */
1323 action |= LVF_REDRAW | LVF_NOTIFY;
1327 case LVA_MakeVisible:
1329 LONG itemnum = ti->ti_Data;
1331 DB (kprintf (" LVA_MakeVisible, %ld\n", ti->ti_Data);)
1336 if (itemnum >= lv->Total)
1337 itemnum = lv->Total - 1;
1339 if (itemnum < lv->Top)
1344 lv->TopPtr = GetItem (lv, lv->Top);
1345 action |= LVF_SCROLL | LVF_NOTIFY;
1347 else if (itemnum >= lv->Top + lv->Visible)
1351 lv->Top = itemnum - lv->Visible + 1;
1352 lv->TopPtr = GetItem (lv, lv->Top);
1353 action |= LVF_SCROLL | LVF_NOTIFY;
1359 DB (kprintf (" LVA_MoveUp, %ld\n", ti->ti_Data);)
1361 if ((lv->Top > 0) && lv->Items)
1364 lv->TopPtr = GetPrev (lv, lv->TopPtr, lv->Top);
1365 action |= LVF_SCROLL | LVF_NOTIFY;
1370 DB (kprintf (" LVA_MoveDown, %ld\n", ti->ti_Data);)
1372 if ((lv->Top + lv->Visible < lv->Total) && lv->Items)
1375 lv->TopPtr = GetNext (lv, lv->TopPtr, lv->Top);
1376 action |= LVF_SCROLL | LVF_NOTIFY;
1381 DB (kprintf (" Unimplemented attr: LVA_MoveLeft\n");)
1385 DB (kprintf (" Unimplemented attr: LVA_MoveRight\n");)
1388 case LVA_StringList:
1389 DB (kprintf (" LVA_StringList, $%lx\n", ti->ti_Data);)
1391 if (ti->ti_Data == ~0)
1395 ASSERT_VALID(ti->ti_Data)
1397 lv->Items = (void *) ti->ti_Data;
1398 lv->GetItemFunc = ListGetItem;
1399 lv->GetNextFunc = ListGetNext;
1400 lv->GetPrevFunc = ListGetPrev;
1401 lv->DrawItemFunc = ListStringDrawItem;
1402 lv->Flags |= LVF_LIST;
1404 lv->Total = GetTagData (LVA_Total, ~0, msg->opu_AttrList);
1405 if (lv->Total == ~0)
1406 lv->Total = CountNodes (lv->Items);
1408 lv->SelectCount = CountSelections (lv);
1410 action |= LVF_REDRAW | LVF_NOTIFYALL;
1414 case LVA_StringArray:
1415 DB (kprintf (" LVA_StringArray, $%lx\n", ti->ti_Data);)
1417 if (ti->ti_Data == ~0)
1421 ASSERT_VALID(ti->ti_Data)
1423 lv->Items = (void *) ti->ti_Data;
1424 lv->GetItemFunc = ArrayGetItem;
1425 lv->GetNextFunc = ArrayGetItem;
1426 lv->GetPrevFunc = ArrayGetItem;
1427 lv->DrawItemFunc = StringDrawItem;
1428 lv->Flags &= ~LVF_LIST;
1430 lv->Total = GetTagData (LVA_Total, ~0, msg->opu_AttrList);
1431 if ((lv->Total == ~0) && lv->Items)
1435 while (((APTR *)lv->Items)[i]) i++;
1439 lv->SelectCount = CountSelections(lv);
1441 action |= LVF_REDRAW | LVF_NOTIFYALL;
1446 DB (kprintf (" LVA_ImageList, $%lx\n", ti->ti_Data);)
1448 if (ti->ti_Data == ~0)
1452 ASSERT_VALID(ti->ti_Data)
1454 lv->Items = (void *) ti->ti_Data;
1455 lv->GetItemFunc = ListGetItem;
1456 lv->GetNextFunc = ListGetNext;
1457 lv->GetPrevFunc = ListGetPrev;
1458 lv->DrawItemFunc = ListImageDrawItem;
1459 lv->Flags |= LVF_LIST;
1461 lv->Total = GetTagData (LVA_Total, ~0, msg->opu_AttrList);
1462 if (lv->Total == ~0)
1463 lv->Total = CountNodes (lv->Items);
1465 lv->SelectCount = CountSelections(lv);
1467 action |= LVF_REDRAW | LVF_NOTIFYALL;
1471 case LVA_ImageArray:
1472 DB (kprintf (" LVA_ImageArray, $%lx\n", ti->ti_Data);)
1474 if (ti->ti_Data == ~0)
1478 ASSERT_VALID(ti->ti_Data)
1480 lv->Items = (void *) ti->ti_Data;
1481 lv->GetItemFunc = ArrayGetItem;
1482 lv->GetNextFunc = ArrayGetItem;
1483 lv->GetPrevFunc = ArrayGetItem;
1484 lv->DrawItemFunc = ImageDrawItem;
1485 lv->Flags &= ~LVF_LIST;
1487 lv->Total = GetTagData (LVA_Total, ~0, msg->opu_AttrList);
1488 if ((lv->Total == ~0) && lv->Items)
1492 while (((APTR *)lv->Items)[i]) i++;
1496 action |= LVF_REDRAW | LVF_NOTIFYALL;
1500 case LVA_CustomList:
1501 DB (kprintf (" LVA_CustomList, $%lx\n", ti->ti_Data);)
1503 if (ti->ti_Data == ~0)
1507 ASSERT_VALID(ti->ti_Data)
1509 lv->Items = (void *) ti->ti_Data;
1510 lv->SelectCount = CountSelections (lv);
1512 action |= LVF_REDRAW | LVF_NOTIFYALL;
1517 DB (kprintf (" LVA_Visible, %ld\n", ti->ti_Data);)
1519 /* This attribute can only be set internally, and will
1520 * trigger a full slider notification.
1522 lv->Visible = ti->ti_Data;
1523 action |= LVF_NOTIFYALL;
1526 /* Also scroll the ListView if needed. */
1529 LONG height = lv->Total * (lv->ItemHeight + lv->Spacing);
1532 if (lv->PixelTop + lv->GBox.Height >= height)
1534 lv->PixelTop = height - lv->GBox.Height;
1535 if (lv->PixelTop < 0)
1538 newtop = lv->PixelTop / (lv->ItemHeight + lv->Spacing);
1539 if (newtop != lv->Top)
1542 lv->TopPtr = GetItem (lv, newtop);
1544 action |= LVF_SCROLL;
1547 else if (lv->Top + lv->Visible >= lv->Total)
1549 lv->Top = (lv->Total <= lv->Visible) ? 0 : (lv->Total - lv->Visible);
1550 lv->TopPtr = GetItem (lv, lv->Top);
1551 lv->PixelTop = lv->Top * (lv->ItemHeight + lv->Spacing);
1552 action |= LVF_SCROLL;
1556 case LVA_SelectArray:
1557 DB (kprintf (" LVA_SelectArray, $%lx\n", ti->ti_Data);)
1558 ASSERT_VALID(ti->ti_Data)
1560 lv->SelectArray = (ULONG *) ti->ti_Data;
1561 lv->SelectCount = CountSelections (lv);
1562 action |= LVF_REDRAW;
1566 DB (kprintf (" LVA_MaxSelect, %ld\n", ti->ti_Data);)
1568 lv->MaxSelect = ti->ti_Data;
1569 /* NOTE: We are not checking lv->SelectCount */
1572 case LVA_PixelTop: /* Handle pixel-wise scrolling */
1573 DB (kprintf (" LVA_PixelTop, %ld\n", ti->ti_Data);)
1575 if (ti->ti_Data != lv->PixelTop && lv->Items && lv->ItemHeight)
1579 lv->PixelTop = ti->ti_Data;
1580 action |= LVF_SCROLL;
1582 newtop = lv->PixelTop / (lv->ItemHeight + lv->Spacing);
1583 newtop = ((newtop + lv->Visible) >= lv->Total) ?
1584 ((lv->Total <= lv->Visible) ? 0 : (lv->Total - lv->Visible))
1587 if (newtop != lv->Top)
1589 /* TODO: optimize GetItem for some special cases:
1590 * Top = oldtop + 1 and Top = oldtop - 1
1593 lv->TopPtr = GetItem (lv, newtop);
1594 action |= LVF_NOTIFY | LVF_SCROLL;
1599 case LVA_ScrollRatio:
1600 DB (kprintf (" LVA_ScrollRatio, %ld\n", ti->ti_Data);)
1601 ASSERT(ti->ti_Data != 0)
1603 lv->ScrollRatio = ti->ti_Data;
1604 lv->MaxScroll = lv->GBox.Height / lv->ScrollRatio;
1608 DB (kprintf (" Passing unknown tag to superclass: $%lx, %ld\n",
1609 ti->ti_Tag, ti->ti_Data);)
1611 /* This little optimization avoids forwarding the
1612 * OM_SET method to our superclass then there are
1615 action |= LVF_DO_SUPER_METHOD;
1619 DB(kprintf (" TAG_DONE\n");)
1621 /* Forward method to our superclass dispatcher, only if needed */
1623 if (action & LVF_DO_SUPER_METHOD)
1624 result = (DoSuperMethodA (cl, (Object *)g, (Msg) msg));
1629 /* Update gadget imagery, only when needed */
1631 if ((action & (LVF_REDRAW | LVF_SCROLL | LVF_TOGGLESELECT))
1632 && msg->opu_GInfo && !(lv->Flags & LVF_DONTDRAW))
1634 struct RastPort *rp;
1636 if (rp = ObtainGIRPort (msg->opu_GInfo))
1638 /* Just redraw everything */
1639 if (action & LVF_REDRAW)
1640 DoMethod ((Object *)g, GM_RENDER, msg->opu_GInfo, rp, GREDRAW_REDRAW);
1643 /* Both these may happen at the same time */
1645 if (action & LVF_SCROLL)
1646 DoMethod ((Object *)g, GM_RENDER, msg->opu_GInfo, rp,
1649 if (action & LVF_TOGGLESELECT)
1650 DoMethod ((Object *)g, GM_RENDER, msg->opu_GInfo, rp,
1654 ReleaseGIRPort (rp);
1656 DB(else kprintf ("*** ObtainGIRPort() failed!\n");)
1660 /* Notify our targets about changed attributes */
1662 if (action & LVF_NOTIFYALL)
1664 DB(kprintf("OM_NOTIFY: ALL\n");)
1665 DB(kprintf(" LVA_Top, %ld\n", lv->Top);)
1666 DB(kprintf(" LVA_Total, %ld\n", lv->Total);)
1667 DB(kprintf(" LVA_Visible, %ld\n", lv->Visible);)
1668 DB(kprintf(" LVA_Selected, %ld\n", lv->Selected);)
1669 DB(kprintf(" LVA_PixelTop, %ld\n", lv->PixelTop);)
1670 DB(kprintf(" LVA_PixelHeight, %ld\n", lv->Total * (lv->ItemHeight + lv->Spacing));)
1671 DB(kprintf(" LVA_PixelVVisible, %ld\n", lv->GBox.Height);)
1672 DB(kprintf(" TAG_DONE\n");)
1674 NotifyAttrs ((Object *)g, msg->opu_GInfo,
1675 (msg->MethodID == OM_UPDATE) ? msg->opu_Flags : 0,
1677 LVA_Total, lv->Total,
1678 LVA_Visible, lv->Visible,
1679 LVA_Selected, lv->Selected,
1680 LVA_PixelTop, lv->PixelTop,
1681 LVA_PixelHeight, lv->Total * (lv->ItemHeight + lv->Spacing),
1682 LVA_PixelVVisible, lv->ClipRegion ?
1684 lv->Visible * (lv->ItemHeight + lv->Spacing),
1688 else if (action & LVF_NOTIFY)
1693 if (action & LVF_SCROLL)
1695 tags[0] = LVA_Top; tags[1] = lv->Top;
1696 tags[2] = LVA_PixelTop; tags[3] = lv->Top * (lv->ItemHeight + lv->Spacing);
1700 if (action & LVF_TOGGLESELECT)
1702 tags[cnt++] = LVA_Selected; tags[cnt++] = lv->Selected;
1705 tags[cnt++] = GA_ID;
1706 tags[cnt++] = g->GadgetID;
1707 tags[cnt] = TAG_DONE;
1709 DoMethod ((Object *)g, OM_NOTIFY, tags, msg->opu_GInfo,
1710 (msg->MethodID == OM_UPDATE) ? msg->opu_Flags : 0);
1718 static ULONG LV_OMGet (Class *cl, struct Gadget *g, struct opGet *msg)
1720 struct LVData *lv = INST_DATA (cl, g);
1723 ASSERT_VALIDNO0(msg->opg_Storage)
1725 DB (kprintf ("OM_GET\n");)
1728 switch (msg->opg_AttrID)
1731 *msg->opg_Storage = (ULONG) lv->Selected;
1735 *msg->opg_Storage = (ULONG) lv->Top;
1739 *msg->opg_Storage = (ULONG) lv->Total;
1742 case LVA_StringList:
1743 case LVA_StringArray:
1745 case LVA_ImageArray:
1746 case LVA_CustomList:
1747 *msg->opg_Storage = (ULONG) lv->Items;
1751 *msg->opg_Storage = (ULONG) lv->Visible;
1754 case LVA_SelectedPtr:
1755 *msg->opg_Storage = (ULONG) lv->SelectedPtr;
1758 case LVA_SelectArray:
1759 *msg->opg_Storage = (ULONG) lv->SelectArray;
1763 return DoSuperMethodA (cl, (Object *)g, (Msg) msg);
1769 static ULONG LV_OMNew (Class *cl, struct Gadget *g, struct opSet *msg)
1772 struct TagItem *tag;
1773 struct DrawInfo *drawinfo;
1776 DB (kprintf ("OM_NEW\n");)
1778 if (g = (struct Gadget *)DoSuperMethodA (cl, (Object *)g, (Msg)msg))
1780 /* Set the GMORE_SCROLLRASTER flag */
1781 if (g->Flags & GFLG_EXTENDED)
1783 DB (kprintf (" Setting GMORE_SCROLLRASTER\n");)
1784 ((struct ExtGadget *)g)->MoreFlags |= GMORE_SCROLLRASTER;
1787 lv = (struct LVData *) INST_DATA (cl, (Object *)g);
1790 /* Handle creation-time attributes */
1792 /* Map boolean attributes */
1794 static IPTR boolMap[] =
1796 GA_ReadOnly, LVF_READONLY,
1797 LVA_Clipped, LVF_CLIPPED,
1798 LVA_ShowSelected, LVF_SHOWSELECTED,
1799 LVA_DoMultiSelect, LVF_DOMULTISELECT,
1803 lv->Flags = PackBoolTags (
1806 (struct TagItem *)boolMap);
1810 /* Select font to use when drawing the Listview labels */
1812 /* First, try to get the font from our DrawInfo... */
1814 if (drawinfo = (struct DrawInfo *)
1815 GetTagData (GA_DrawInfo, NULL, msg->ops_AttrList))
1817 ASSERT_VALID(drawinfo)
1818 lv->Font = drawinfo->dri_Font;
1824 /* ...then override it with LVA_TextFont */
1826 if (tag = FindTagItem (LVA_TextFont, msg->ops_AttrList))
1830 lv->Font = (struct TextFont *)tag->ti_Data;
1831 ASSERT_VALID(lv->Font)
1834 else /* Otherwise, try GA_TextAttr */
1836 struct TextAttr *attr;
1837 struct TextFont *font;
1839 if (attr = (struct TextAttr *)GetTagData (GA_TextAttr,
1840 NULL, msg->ops_AttrList))
1842 if (font = OpenFont (attr))
1844 /* Must remember to close this font later */
1845 lv->Flags |= LVF_CLOSEFONT;
1851 /* Calculate ItemHeight */
1854 /* Get height from font Y size */
1855 lv->ItemHeight = lv->Font->tf_YSize;
1859 lv->ItemHeight = GetTagData (LVA_ItemHeight, lv->ItemHeight, msg->ops_AttrList);
1860 lv->Spacing = GetTagData (LAYOUTA_Spacing, 0, msg->ops_AttrList);
1862 if (tag = FindTagItem (LVA_MaxPen, msg->ops_AttrList))
1863 lv->MaxPen = tag->ti_Data;
1868 max (drawinfo->dri_Pens[BACKGROUNDPEN],
1869 drawinfo->dri_Pens[TEXTPEN]),
1870 max (drawinfo->dri_Pens[FILLPEN],
1871 drawinfo->dri_Pens[FILLTEXTPEN]));
1873 lv->MaxPen = (ULONG)-1;
1877 lv->Total = GetTagData (LVA_Total, ~0, msg->ops_AttrList);
1879 if (lv->Items = (APTR) GetTagData (LVA_StringList, NULL, msg->ops_AttrList))
1881 ASSERT_VALID(lv->Items)
1882 lv->GetItemFunc = ListGetItem;
1883 lv->GetNextFunc = ListGetNext;
1884 lv->GetPrevFunc = ListGetPrev;
1885 lv->DrawItemFunc = ListStringDrawItem;
1886 lv->Flags |= LVF_LIST;
1888 if (lv->Total == ~0)
1889 lv->Total = CountNodes (lv->Items);
1891 else if (lv->Items = (APTR) GetTagData (LVA_StringArray, NULL, msg->ops_AttrList))
1893 ASSERT_VALID(lv->Items)
1894 lv->GetItemFunc = ArrayGetItem;
1895 lv->GetNextFunc = ArrayGetItem;
1896 lv->GetPrevFunc = ArrayGetItem;
1897 lv->DrawItemFunc = StringDrawItem;
1899 if (lv->Total == ~0)
1903 while (((APTR *)lv->Items)[i]) i++;
1907 else if (lv->Items = (APTR) GetTagData (LVA_ImageList, NULL, msg->ops_AttrList))
1909 ASSERT_VALID(lv->Items)
1910 lv->GetItemFunc = ListGetItem;
1911 lv->GetNextFunc = ListGetNext;
1912 lv->GetPrevFunc = ListGetPrev;
1913 lv->DrawItemFunc = ListImageDrawItem;
1914 lv->Flags |= LVF_LIST;
1916 if (lv->Total == ~0)
1917 lv->Total = CountNodes (lv->Items);
1919 else if (lv->Items = (APTR) GetTagData (LVA_ImageArray, NULL, msg->ops_AttrList))
1921 ASSERT_VALID(lv->Items)
1922 lv->GetItemFunc = ArrayGetItem;
1923 lv->GetNextFunc = ArrayGetItem;
1924 lv->GetPrevFunc = ArrayGetItem;
1925 lv->DrawItemFunc = ImageDrawItem;
1927 if (lv->Total == ~0)
1931 while (((APTR *)lv->Items)[i]) i++;
1936 lv->SelectArray = (ULONG *)GetTagData (LVA_SelectArray, NULL, msg->ops_AttrList);
1937 lv->MaxSelect = GetTagData (LVA_MaxSelect, -1, msg->ops_AttrList);
1938 lv->SelectCount = CountSelections(lv);
1940 if (lv->Visible = GetTagData (LVA_Visible, 0, msg->ops_AttrList))
1943 GA_Height, lv->Visible * (lv->ItemHeight + lv->Spacing),
1947 /* Initialize Top and all related values */
1949 lv->OldTop = lv->Top = GetTagData (LVA_MakeVisible,
1950 GetTagData (LVA_Top, 0, msg->ops_AttrList), msg->ops_AttrList);
1951 lv->OldPixelTop = lv->PixelTop = lv->Top * (lv->ItemHeight + lv->Spacing);
1954 lv->TopPtr = GetItem (lv, lv->Top);
1956 lv->ScrollRatio = GetTagData (LVA_ScrollRatio, 2, msg->ops_AttrList);
1957 ASSERT(lv->ScrollRatio != 0)
1959 if ((lv->OldSelected =
1960 lv->Selected = GetTagData (LVA_Selected, ~0, msg->ops_AttrList)) != ~0)
1961 lv->SelectedPtr = GetItem (lv, lv->Selected);
1963 if (lv->CallBack = (struct Hook *)GetTagData (LVA_CallBack, NULL,
1966 ASSERT_VALID(lv->CallBack->h_Entry)
1967 lv->DrawItemFunc = (LVDrawHook *) lv->CallBack->h_Entry;
1970 if (lv->Flags & LVF_CLIPPED)
1971 lv->ClipRegion = NewRegion ();
1978 static void LV_OMDispose (Class *cl, struct Gadget *g, Msg msg)
1982 lv = (struct LVData *) INST_DATA (cl, (Object *)g);
1985 DB (kprintf ("OM_DISPOSE\n");)
1988 DisposeRegion (lv->ClipRegion);
1990 if (lv->Flags & LVF_CLOSEFONT)
1991 CloseFont (lv->Font);
1993 /* Our superclass will cleanup everything else now */
1994 DoSuperMethodA (cl, (Object *)g, (Msg) msg);
1996 /* From now on, our instance data is no longer available */
2001 /* Misc support functions */
2003 INLINE ULONG CountNodes (struct List *list)
2005 /* Return the number of nodes in a list */
2014 for (node = list->lh_Head; node = node->ln_Succ; count++)
2023 static ULONG CountSelections (struct LVData *lv)
2025 /* Count the number of selections in a multiselect listview */
2032 if (lv->Flags & LVF_DOMULTISELECT)
2034 if (lv->SelectArray)
2038 ASSERT_VALID(lv->SelectArray)
2040 for (i = 0; i < lv->Total; i++)
2041 if (lv->SelectArray[i])
2044 else if ((lv->Flags & LVF_LIST) && lv->Items)
2048 ASSERT_VALID(lv->Items)
2050 for (node = ((struct List *)lv->Items)->lh_Head; node = node->ln_Succ; count++)
2060 INLINE ULONG IsItemSelected (struct LVData *lv, APTR item, ULONG num)
2062 /* Checks if the given item is selected */
2067 ASSERT(num < lv->Total)
2070 if (lv->Flags & LVF_DOMULTISELECT)
2072 if (lv->SelectArray)
2074 ASSERT(num < lv->Total)
2076 return lv->SelectArray[num];
2078 else if (lv->Flags & LVF_LIST)
2081 item = GetItem (lv, num);
2083 ASSERT_VALIDNO0(item)
2085 return item ? (ULONG)(((struct Node *)item)->ln_Type) : 0;
2091 return ((ULONG)(num == lv->Selected));
2096 INLINE APTR GetItem (struct LVData *lv, ULONG num)
2098 /* Stub for LV_GETITEM hook method */
2100 struct lvGetItem lvgi;
2104 ASSERT_VALIDNO0(lv->Items)
2105 ASSERT_VALIDNO0(lv->GetItemFunc)
2107 ASSERT(num < lv->Total)
2110 lvgi.lvgi_MethodID = LV_GETITEM;
2111 lvgi.lvgi_Number = num;
2112 lvgi.lvgi_Items = lv->Items;
2114 return (lv->GetItemFunc (lv->CallBack, NULL, &lvgi));
2119 INLINE APTR GetNext (struct LVData *lv, APTR item, ULONG num)
2121 /* Stub for LV_GETNEXT hook method */
2123 struct lvGetItem lvgi;
2127 ASSERT_VALIDNO0(lv->GetNextFunc)
2130 ASSERT(num < lv->Total)
2133 lvgi.lvgi_MethodID = LV_GETNEXT;
2134 lvgi.lvgi_Number = num;
2135 lvgi.lvgi_Items = lv->Items;
2137 return (lv->GetNextFunc (lv->CallBack, item, &lvgi));
2142 INLINE APTR GetPrev (struct LVData *lv, APTR item, ULONG num)
2144 /* Stub for LV_GETPREV hook method */
2146 struct lvGetItem lvgi;
2150 ASSERT_VALIDNO0(lv->GetPrevFunc)
2153 ASSERT(num < lv->Total)
2156 lvgi.lvgi_MethodID = LV_GETPREV;
2157 lvgi.lvgi_Number = num;
2158 lvgi.lvgi_Items = lv->Items;
2160 return (lv->GetPrevFunc (lv->CallBack, item, &lvgi));
2165 Class *MakeListViewClass (void)
2169 if (LVClass = MakeClass (NULL, GADGETCLASS, NULL, sizeof (struct LVData), 0))
2170 LVClass->cl_Dispatcher.h_Entry = (ULONG (*)()) LVDispatcher;
2177 void FreeListViewClass (Class *LVClass)
2179 ASSERT_VALID(LVClass)
2180 FreeClass (LVClass);