Only toggle MUIA_Listview_SelectChange if selections are (probably) changing.
[AROS.git] / workbench / libs / muimaster / classes / listview.c
blob34f1e88a7b09d39e217755f74daae3ef31056847
1 /*
2 Copyright © 2002-2015, The AROS Development Team. All rights reserved.
3 $Id$
4 */
6 #include <aros/debug.h>
8 #include <graphics/gfx.h>
9 #include <graphics/view.h>
10 #include <devices/rawkeycodes.h>
11 #include <clib/alib_protos.h>
12 #include <proto/exec.h>
13 #include <proto/graphics.h>
14 #include <proto/utility.h>
15 #include <proto/intuition.h>
16 #include <proto/muimaster.h>
18 #include "mui.h"
19 #include "muimaster_intern.h"
20 #include "support.h"
21 #include "window.h"
22 #include "list.h"
23 #include "prefs.h"
25 extern struct Library *MUIMasterBase;
27 struct MUI_ListviewData
29 Object *list, *group, *vert;
30 struct Hook hook;
31 struct Hook selfnotify_hook;
32 BOOL noforward;
33 BOOL read_only;
34 BOOL select_change;
35 IPTR multiselect;
36 IPTR scroller_pos;
37 struct MUI_EventHandlerNode ehn;
39 int mouse_click; /* see below if mouse is held down */
41 /* double click */
42 ULONG last_secs;
43 ULONG last_mics;
44 ULONG last_active;
45 BOOL doubleclick;
47 /* clicked column */
48 LONG click_column;
49 LONG def_click_column;
51 /* user prefs */
52 ListviewMulti prefs_multi;
55 #define MOUSE_CLICK_ENTRY 1 /* on entry clicked */
56 #define MOUSE_CLICK_TITLE 2 /* on title clicked */
58 static void DoWheelMove(struct IClass *cl, Object *obj, LONG wheely);
60 #define PROP_VERT_FIRST 1
61 #define LIST_VERT_FIRST 4
62 #define LIST_VERT_VISIBLE 5
63 #define LIST_VERT_ENTRIES 6
65 ULONG Listview_Function(struct Hook *hook, APTR dummyobj, void **msg)
67 struct MUI_ListviewData *data = (struct MUI_ListviewData *)hook->h_Data;
68 SIPTR type = (SIPTR) msg[0];
69 SIPTR val = (SIPTR) msg[1];
71 D(bug("[ListView] List 0x%p, Event %d, value %ld\n", data->list, type,
72 val));
74 switch (type)
76 case PROP_VERT_FIRST:
77 get(data->vert, MUIA_Prop_First, &val);
78 nnset(data->list, MUIA_List_VertProp_First, val);
79 break;
81 case LIST_VERT_FIRST:
82 nnset(data->vert, MUIA_Prop_First, val);
83 break;
84 case LIST_VERT_VISIBLE:
85 nnset(data->vert, MUIA_Prop_Visible, val);
86 break;
87 case LIST_VERT_ENTRIES:
88 nnset(data->vert, MUIA_Prop_Entries, val);
89 break;
91 return 0;
94 ULONG SelfNotify_Function(struct Hook *hook, APTR obj, void **msg)
96 struct MUI_ListviewData *data = (struct MUI_ListviewData *)hook->h_Data;
97 SIPTR attribute = (SIPTR) msg[0];
98 SIPTR value = (SIPTR) msg[1];
100 /* This allows avoiding notify loops */
101 data->noforward = TRUE;
102 SetAttrs(obj, MUIA_Group_Forward, FALSE, attribute, value, TAG_DONE);
103 data->noforward = FALSE;
105 return 0;
108 /**************************************************************************
109 OM_NEW
110 **************************************************************************/
111 IPTR Listview__OM_NEW(struct IClass *cl, Object *obj, struct opSet *msg)
113 struct MUI_ListviewData *data;
114 struct TagItem *tag, *tags;
115 Object *group, *vert;
116 Object *list =
117 (Object *) GetTagData(MUIA_Listview_List, (IPTR) NULL,
118 msg->ops_AttrList);
119 IPTR cyclechain;
120 LONG entries = 0, first = 0, visible = 0;
122 if (!list)
123 return (IPTR) NULL;
125 cyclechain =
126 (IPTR) GetTagData(MUIA_CycleChain, (IPTR) 0, msg->ops_AttrList);
128 vert = ScrollbarObject, MUIA_Group_Horiz, FALSE, End;
130 obj = (Object *) DoSuperNewTags(cl, obj, NULL,
131 MUIA_Group_Horiz, FALSE,
132 MUIA_CycleChain, cyclechain,
133 MUIA_InnerLeft, 0,
134 MUIA_InnerRight, 0,
135 Child, (IPTR) (group = HGroup,
136 MUIA_InnerLeft, 0,
137 MUIA_InnerRight, 0,
138 MUIA_Group_Spacing, 0,
139 End),
140 TAG_DONE);
142 if (!obj)
143 return (IPTR) NULL;
145 data = INST_DATA(cl, obj);
146 data->list = list;
147 data->vert = vert;
148 data->group = group;
150 data->hook.h_Entry = HookEntry;
151 data->hook.h_SubEntry = (HOOKFUNC) Listview_Function;
152 data->hook.h_Data = data;
154 data->selfnotify_hook.h_Entry = HookEntry;
155 data->selfnotify_hook.h_SubEntry = (HOOKFUNC) SelfNotify_Function;
156 data->selfnotify_hook.h_Data = data;
157 data->noforward = FALSE;
159 data->last_active = -1;
161 data->ehn.ehn_Events = IDCMP_MOUSEBUTTONS | IDCMP_RAWKEY;
162 data->ehn.ehn_Priority = 0;
163 data->ehn.ehn_Flags = 0;
164 data->ehn.ehn_Object = obj;
165 data->ehn.ehn_Class = cl;
167 /* parse initial taglist */
168 for (tags = msg->ops_AttrList; (tag = NextTagItem(&tags));)
170 switch (tag->ti_Tag)
172 case MUIA_Listview_DoubleClick:
173 data->doubleclick = tag->ti_Data != 0;
174 break;
175 case MUIA_Listview_Input:
176 data->read_only = !tag->ti_Data;
177 break;
178 case MUIA_Listview_MultiSelect:
179 data->multiselect = tag->ti_Data;
180 break;
181 case MUIA_Listview_ScrollerPos:
182 data->scroller_pos = tag->ti_Data;
183 break;
187 /* Add list and/or scroller */
188 switch (data->scroller_pos)
190 case MUIV_Listview_ScrollerPos_None:
191 DoMethod(group, OM_ADDMEMBER, list);
192 break;
193 case MUIV_Listview_ScrollerPos_Left:
194 DoMethod(group, OM_ADDMEMBER, vert);
195 DoMethod(group, OM_ADDMEMBER, list);
196 break;
197 default:
198 DoMethod(group, OM_ADDMEMBER, list);
199 DoMethod(group, OM_ADDMEMBER, vert);
200 break;
203 get(list, MUIA_List_VertProp_First, &first);
204 get(list, MUIA_List_VertProp_Visible, &visible);
205 get(list, MUIA_List_VertProp_Entries, &entries);
207 D(bug
208 ("[ListView 0x%p] List 0x%p, First %ld, Visible %ld, Entries %ld\n",
209 obj, list, first, visible, entries));
211 SetAttrs(data->vert,
212 MUIA_Prop_First, first,
213 MUIA_Prop_Visible, visible, MUIA_Prop_Entries, entries, TAG_DONE);
215 DoMethod(vert, MUIM_Notify, MUIA_Prop_First, MUIV_EveryTime, (IPTR) obj,
216 4, MUIM_CallHook, (IPTR) &data->hook, PROP_VERT_FIRST,
217 MUIV_TriggerValue);
218 DoMethod(list, MUIM_Notify, MUIA_List_VertProp_First, MUIV_EveryTime,
219 (IPTR) obj, 4, MUIM_CallHook, (IPTR) &data->hook, LIST_VERT_FIRST,
220 MUIV_TriggerValue);
221 DoMethod(list, MUIM_Notify, MUIA_List_VertProp_Visible, MUIV_EveryTime,
222 (IPTR) obj, 4, MUIM_CallHook, (IPTR) &data->hook,
223 LIST_VERT_VISIBLE, MUIV_TriggerValue);
224 DoMethod(list, MUIM_Notify, MUIA_List_VertProp_Entries, MUIV_EveryTime,
225 (IPTR) obj, 4, MUIM_CallHook, (IPTR) &data->hook,
226 LIST_VERT_ENTRIES, MUIV_TriggerValue);
227 DoMethod(list, MUIM_Notify, MUIA_List_Active, MUIV_EveryTime,
228 (IPTR) obj, 4, MUIM_CallHook, (IPTR) &data->selfnotify_hook,
229 MUIA_List_Active, MUIV_TriggerValue);
231 return (IPTR) obj;
234 /**************************************************************************
235 OM_DISPOSE
236 **************************************************************************/
237 IPTR Listview__OM_DISPOSE(struct IClass *cl, Object *obj, Msg msg)
239 return DoSuperMethodA(cl, obj, msg);
242 /**************************************************************************
243 OM_SET
244 **************************************************************************/
245 IPTR Listview__OM_SET(struct IClass *cl, Object *obj, struct opSet *msg)
247 struct TagItem *tag, *tags;
248 IPTR no_notify = GetTagData(MUIA_NoNotify, FALSE, msg->ops_AttrList);
249 struct MUI_ListviewData *data = INST_DATA(cl, obj);
251 if (data->noforward)
253 return DoSuperMethodA(cl, obj, (Msg) msg);
256 for (tags = msg->ops_AttrList; (tag = NextTagItem(&tags));)
258 switch (tag->ti_Tag)
260 case MUIA_List_CompareHook:
261 case MUIA_List_ConstructHook:
262 case MUIA_List_DestructHook:
263 case MUIA_List_DisplayHook:
264 case MUIA_List_VertProp_First:
265 case MUIA_List_Format:
266 case MUIA_List_VertProp_Entries:
267 case MUIA_List_VertProp_Visible:
268 case MUIA_List_Active:
269 case MUIA_List_First:
270 case MUIA_List_Visible:
271 case MUIA_List_Entries:
272 case MUIA_List_Quiet:
273 SetAttrs(data->list, MUIA_NoNotify, no_notify, tag->ti_Tag,
274 tag->ti_Data, TAG_DONE);
275 break;
276 case MUIA_Listview_DoubleClick:
277 data->doubleclick = tag->ti_Data != 0;
278 break;
279 case MUIA_Listview_SelectChange:
280 data->select_change = tag->ti_Data != 0;
281 break;
285 return DoSuperMethodA(cl, obj, (Msg) msg);
288 /**************************************************************************
289 OM_GET
290 **************************************************************************/
291 IPTR Listview__OM_GET(struct IClass *cl, Object *obj, struct opGet *msg)
293 /* small macro to simplify return value storage */
294 #define STORE *(msg->opg_Storage)
295 struct MUI_ListviewData *data = INST_DATA(cl, obj);
297 switch (msg->opg_AttrID)
299 case MUIA_List_CompareHook:
300 case MUIA_List_ConstructHook:
301 case MUIA_List_DestructHook:
302 case MUIA_List_DisplayHook:
303 case MUIA_List_VertProp_First:
304 case MUIA_List_Format:
305 case MUIA_List_VertProp_Entries:
306 case MUIA_List_VertProp_Visible:
307 case MUIA_List_Active:
308 case MUIA_List_First:
309 case MUIA_List_Visible:
310 case MUIA_List_Entries:
311 case MUIA_List_Quiet:
312 return GetAttr(msg->opg_AttrID, data->list, msg->opg_Storage);
313 case MUIA_Listview_DoubleClick:
314 STORE = data->doubleclick;
315 return 1;
316 case MUIA_Listview_ClickColumn:
317 STORE = data->click_column;
318 return 1;
319 case MUIA_Listview_List:
320 STORE = (IPTR) data->list;
321 return 1;
322 case MUIA_Listview_SelectChange:
323 STORE = data->select_change;
324 return 1;
327 return DoSuperMethodA(cl, obj, (Msg) msg);
328 #undef STORE
331 /**************************************************************************
332 MUIM_Setup
333 **************************************************************************/
334 IPTR Listview__MUIM_Setup(struct IClass *cl, Object *obj,
335 struct MUIP_Setup *msg)
337 struct MUI_ListviewData *data = INST_DATA(cl, obj);
339 if (!DoSuperMethodA(cl, obj, (Msg) msg))
340 return 0;
342 data->prefs_multi = muiGlobalInfo(obj)->mgi_Prefs->list_multi;
343 if (data->multiselect == MUIV_Listview_MultiSelect_Default)
345 if (data->prefs_multi == LISTVIEW_MULTI_SHIFTED)
346 data->multiselect = MUIV_Listview_MultiSelect_Shifted;
347 else
348 data->multiselect = MUIV_Listview_MultiSelect_Always;
351 DoMethod(_win(obj), MUIM_Window_AddEventHandler, (IPTR) &data->ehn);
353 return 1;
356 /**************************************************************************
357 MUIM_Cleanup
358 **************************************************************************/
359 IPTR Listview__MUIM_Cleanup(struct IClass *cl, Object *obj,
360 struct MUIP_Cleanup *msg)
362 struct MUI_ListviewData *data = INST_DATA(cl, obj);
364 DoMethod(_win(obj), MUIM_Window_RemEventHandler, (IPTR) &data->ehn);
365 data->mouse_click = 0;
367 return DoSuperMethodA(cl, obj, (Msg) msg);
370 /**************************************************************************
371 MUIM_HandleEvent
372 **************************************************************************/
373 IPTR Listview__MUIM_HandleEvent(struct IClass *cl, Object *obj,
374 struct MUIP_HandleEvent *msg)
376 struct MUI_ListviewData *data = INST_DATA(cl, obj);
377 Object *list = data->list;
378 struct MUI_List_TestPos_Result pos;
379 LONG seltype, old_active, new_active, visible, first, last, i;
380 IPTR result = 0;
381 BOOL select = FALSE, clear = FALSE, range_select = FALSE, changing;
382 WORD delta;
383 typeof(msg->muikey) muikey = msg->muikey;
385 new_active = old_active = XGET(list, MUIA_List_Active);
386 visible = XGET(list, MUIA_List_Visible);
388 if (muikey != MUIKEY_NONE)
390 result = MUI_EventHandlerRC_Eat;
392 /* Make keys behave differently in read-only mode */
393 if (data->read_only)
395 switch (muikey)
397 case MUIKEY_TOP:
398 muikey = MUIKEY_LINESTART;
399 break;
401 case MUIKEY_BOTTOM:
402 muikey = MUIKEY_LINEEND;
403 break;
405 case MUIKEY_UP:
406 muikey = MUIKEY_LEFT;
407 break;
409 case MUIKEY_DOWN:
410 case MUIKEY_PRESS:
411 muikey = MUIKEY_RIGHT;
412 break;
416 switch (muikey)
418 case MUIKEY_TOGGLE:
419 if (data->multiselect != MUIV_Listview_MultiSelect_None
420 && !data->read_only)
422 select = TRUE;
423 data->click_column = data->def_click_column;
424 new_active = MUIV_List_Active_Down;
426 else
428 DoMethod(list, MUIM_List_Jump, 0);
429 muikey = MUIKEY_NONE;
431 break;
433 case MUIKEY_TOP:
434 new_active = MUIV_List_Active_Top;
435 break;
437 case MUIKEY_BOTTOM:
438 new_active = MUIV_List_Active_Bottom;
439 break;
441 case MUIKEY_LEFT:
442 case MUIKEY_WORDLEFT:
443 DoMethod(list, MUIM_List_Jump, MUIV_List_Jump_Up);
444 break;
446 case MUIKEY_RIGHT:
447 case MUIKEY_WORDRIGHT:
448 DoMethod(list, MUIM_List_Jump, MUIV_List_Jump_Down);
449 break;
451 case MUIKEY_LINESTART:
452 DoMethod(list, MUIM_List_Jump, MUIV_List_Jump_Top);
453 break;
455 case MUIKEY_LINEEND:
456 DoMethod(list, MUIM_List_Jump, MUIV_List_Jump_Bottom);
457 break;
459 case MUIKEY_UP:
460 new_active = MUIV_List_Active_Up;
461 break;
463 case MUIKEY_DOWN:
464 new_active = MUIV_List_Active_Down;
465 break;
467 case MUIKEY_PAGEUP:
468 if (data->read_only)
469 DoWheelMove(cl, obj, -visible);
470 else
471 new_active = MUIV_List_Active_PageUp;
472 break;
474 case MUIKEY_PAGEDOWN:
475 if (data->read_only)
476 DoWheelMove(cl, obj, visible);
477 else
478 new_active = MUIV_List_Active_PageDown;
479 break;
481 default:
482 result = 0;
485 else if (msg->imsg)
487 DoMethod(list, MUIM_List_TestPos, msg->imsg->MouseX,
488 msg->imsg->MouseY, (IPTR) &pos);
490 switch (msg->imsg->Class)
492 case IDCMP_MOUSEBUTTONS:
493 if (msg->imsg->Code == SELECTDOWN)
495 if (_isinobject(list, msg->imsg->MouseX, msg->imsg->MouseY))
497 data->mouse_click = MOUSE_CLICK_ENTRY;
499 if (!data->read_only && pos.entry != -1)
501 new_active = pos.entry;
503 clear = (data->multiselect
504 == MUIV_Listview_MultiSelect_Shifted
505 && (msg->imsg->Qualifier
506 & (IEQUALIFIER_LSHIFT | IEQUALIFIER_RSHIFT)) == 0);
507 seltype = clear ?
508 MUIV_List_Select_On: MUIV_List_Select_Toggle;
509 select = data->multiselect
510 != MUIV_Listview_MultiSelect_None;
512 /* Handle MUIA_Listview_ClickColumn */
513 data->click_column = pos.column;
515 /* Handle double clicking */
516 if (data->last_active == pos.entry
517 && DoubleClick(data->last_secs, data->last_mics,
518 msg->imsg->Seconds, msg->imsg->Micros))
520 set(obj, MUIA_Listview_DoubleClick, TRUE);
521 data->last_active = -1;
522 data->last_secs = data->last_mics = 0;
524 else
526 data->last_active = pos.entry;
527 data->last_secs = msg->imsg->Seconds;
528 data->last_mics = msg->imsg->Micros;
531 /* Look out for mouse movement and timer events while
532 mouse button is down */
533 DoMethod(_win(list), MUIM_Window_RemEventHandler,
534 (IPTR) &data->ehn);
535 data->ehn.ehn_Events |=
536 (IDCMP_MOUSEMOVE | IDCMP_INTUITICKS);
537 DoMethod(_win(list), MUIM_Window_AddEventHandler,
538 (IPTR) &data->ehn);
542 else
544 /* Activate object */
545 if (msg->imsg->Code == SELECTUP && data->mouse_click)
547 set(_win(obj), MUIA_Window_ActiveObject, (IPTR)obj);
548 data->mouse_click = 0;
551 /* Restore normal event mask */
552 DoMethod(_win(list), MUIM_Window_RemEventHandler,
553 (IPTR) &data->ehn);
554 data->ehn.ehn_Events &=
555 ~(IDCMP_MOUSEMOVE | IDCMP_INTUITICKS);
556 DoMethod(_win(list), MUIM_Window_AddEventHandler,
557 (IPTR) &data->ehn);
559 break;
561 case IDCMP_MOUSEMOVE:
562 case IDCMP_INTUITICKS:
563 if (pos.flags & MUI_LPR_ABOVE)
564 new_active = MUIV_List_Active_Up;
565 else if (pos.flags & MUI_LPR_BELOW)
566 new_active = MUIV_List_Active_Down;
567 else
568 new_active = pos.entry;
570 select = new_active != old_active
571 && data->multiselect != MUIV_Listview_MultiSelect_None;
572 if (select)
574 DoMethod(list, MUIM_List_Select, MUIV_List_Select_Active,
575 MUIV_List_Select_Ask, &seltype);
576 range_select = new_active >= 0;
579 break;
581 case IDCMP_RAWKEY:
582 /* Scroll wheel */
583 if (_isinobject(data->vert, msg->imsg->MouseX, msg->imsg->MouseY))
584 delta = 1;
585 else if (_isinobject(list, msg->imsg->MouseX, msg->imsg->MouseY))
586 delta = 4;
587 else
588 delta = 0;
590 if (delta != 0)
592 switch (msg->imsg->Code)
594 case RAWKEY_NM_WHEEL_UP:
595 DoWheelMove(cl, obj, -delta);
596 break;
598 case RAWKEY_NM_WHEEL_DOWN:
599 DoWheelMove(cl, obj, delta);
600 break;
602 result = MUI_EventHandlerRC_Eat;
604 break;
608 /* Decide in advance if any selections may change */
609 changing = clear || muikey == MUIKEY_TOGGLE || select;
611 /* Change selected and active entries */
612 if (changing)
613 set(obj, MUIA_Listview_SelectChange, TRUE);
615 if (clear)
617 DoMethod(list, MUIM_List_Select, MUIV_List_Select_All,
618 MUIV_List_Select_Off, NULL);
621 if (muikey == MUIKEY_TOGGLE)
623 DoMethod(list, MUIM_List_Select,
624 MUIV_List_Select_Active,
625 MUIV_List_Select_Toggle, NULL);
626 select = FALSE;
629 if (new_active != old_active)
630 set(list, MUIA_List_Active, new_active);
632 if (select)
634 if (range_select)
636 if (old_active < new_active)
637 first = old_active + 1, last = new_active;
638 else
639 first = new_active, last = old_active - 1;
640 for (i = first; i <= last; i++)
641 DoMethod(list, MUIM_List_Select, i, seltype, NULL);
643 else
644 DoMethod(list, MUIM_List_Select, MUIV_List_Select_Active, seltype,
645 NULL);
648 if (changing)
649 set(obj, MUIA_Listview_SelectChange, FALSE);
651 return result;
654 static void DoWheelMove(struct IClass *cl, Object *obj, LONG wheely)
656 struct MUI_ListviewData *data = INST_DATA(cl, obj);
657 LONG new, first, entries, visible;
659 new = first = XGET(data->list, MUIA_List_First);
660 entries = XGET(data->list, MUIA_List_Entries);
661 visible = XGET(data->list, MUIA_List_Visible);
663 new += wheely;
665 if (new > entries - visible)
667 new = entries - visible;
670 if (new < 0)
672 new = 0;
675 if (new != first)
677 set(data->list, MUIA_List_First, new);
681 BOOPSI_DISPATCHER(IPTR, Listview_Dispatcher, cl, obj, msg)
683 switch (msg->MethodID)
685 case OM_SET:
686 return Listview__OM_SET(cl, obj, (struct opSet *)msg);
687 case OM_GET:
688 return Listview__OM_GET(cl, obj, (struct opGet *)msg);
689 case OM_NEW:
690 return Listview__OM_NEW(cl, obj, (struct opSet *)msg);
691 case OM_DISPOSE:
692 return Listview__OM_DISPOSE(cl, obj, msg);
693 case MUIM_Setup:
694 return Listview__MUIM_Setup(cl, obj, (struct MUIP_Setup *)msg);
695 case MUIM_Cleanup:
696 return Listview__MUIM_Cleanup(cl, obj, (struct MUIP_Cleanup *)msg);
697 case MUIM_HandleEvent:
698 return Listview__MUIM_HandleEvent(cl, obj,
699 (struct MUIP_HandleEvent *)msg);
700 case MUIM_List_Clear:
701 case MUIM_List_CreateImage:
702 case MUIM_List_DeleteImage:
703 case MUIM_List_Exchange:
704 case MUIM_List_GetEntry:
705 case MUIM_List_Insert:
706 case MUIM_List_InsertSingle:
707 case MUIM_List_Jump:
708 case MUIM_List_NextSelected:
709 case MUIM_List_Redraw:
710 case MUIM_List_Remove:
711 case MUIM_List_Select:
712 case MUIM_List_Sort:
713 case MUIM_List_TestPos:
715 struct MUI_ListviewData *data = INST_DATA(cl, obj);
717 return DoMethodA(data->list, msg);
722 return DoSuperMethodA(cl, obj, msg);
724 BOOPSI_DISPATCHER_END
727 * Class descriptor.
729 const struct __MUIBuiltinClass _MUI_Listview_desc =
731 MUIC_Listview,
732 MUIC_Group,
733 sizeof(struct MUI_ListviewData),
734 (void *) Listview_Dispatcher