Change to the linux kernel coding style
[wmaker-crm.git] / WINGs / wlist.c
1
2 #include "WINGsP.h"
3
4 char *WMListDidScrollNotification = "WMListDidScrollNotification";
5 char *WMListSelectionDidChangeNotification = "WMListSelectionDidChangeNotification";
6
7 typedef struct W_List {
8         W_Class widgetClass;
9         W_View *view;
10
11         WMArray *items;         /* list of WMListItem */
12         WMArray *selectedItems; /* list of selected WMListItems */
13
14         short itemHeight;
15
16         int topItem;            /* index of first visible item */
17
18         short fullFitLines;     /* no of lines that fit entirely */
19
20         void *clientData;
21         WMAction *action;
22         void *doubleClientData;
23         WMAction *doubleAction;
24
25         WMListDrawProc *draw;
26
27         WMHandlerID *idleID;    /* for updating the scroller after adding elements */
28
29         WMHandlerID *selectID;  /* for selecting items in list while scrolling */
30
31         WMScroller *vScroller;
32
33         Pixmap doubleBuffer;
34
35         struct {
36                 unsigned int allowMultipleSelection:1;
37                 unsigned int allowEmptySelection:1;
38                 unsigned int userDrawn:1;
39                 unsigned int userItemHeight:1;
40                 unsigned int dontFitAll:1;      /* 1 = last item won't be fully visible */
41                 unsigned int redrawPending:1;
42                 unsigned int buttonPressed:1;
43                 unsigned int buttonWasPressed:1;
44         } flags;
45 } List;
46
47 #define DEFAULT_WIDTH   150
48 #define DEFAULT_HEIGHT  150
49
50 #define SCROLL_DELAY    100
51
52 static void destroyList(List * lPtr);
53 static void paintList(List * lPtr);
54
55 static void handleEvents(XEvent * event, void *data);
56 static void handleActionEvents(XEvent * event, void *data);
57
58 static void updateScroller(void *data);
59 static void scrollForwardSelecting(void *data);
60 static void scrollBackwardSelecting(void *data);
61
62 static void vScrollCallBack(WMWidget * scroller, void *self);
63
64 static void toggleItemSelection(WMList * lPtr, int index);
65
66 static void updateGeometry(WMList * lPtr);
67 static void didResizeList();
68
69 static void unselectAllListItems(WMList * lPtr, WMListItem * exceptThis);
70
71 W_ViewDelegate _ListViewDelegate = {
72         NULL,
73         NULL,
74         didResizeList,
75         NULL,
76         NULL
77 };
78
79 static void updateDoubleBufferPixmap(WMList * lPtr)
80 {
81         WMView *view = lPtr->view;
82         WMScreen *scr = view->screen;
83
84         if (!view->flags.realized)
85                 return;
86
87         if (lPtr->doubleBuffer)
88                 XFreePixmap(scr->display, lPtr->doubleBuffer);
89         lPtr->doubleBuffer =
90             XCreatePixmap(scr->display, view->window, view->size.width, lPtr->itemHeight, scr->depth);
91 }
92
93 static void realizeObserver(void *self, WMNotification * not)
94 {
95         updateDoubleBufferPixmap(self);
96 }
97
98 static void releaseItem(void *data)
99 {
100         WMListItem *item = (WMListItem *) data;
101
102         if (item->text)
103                 wfree(item->text);
104         wfree(item);
105 }
106
107 WMList *WMCreateList(WMWidget * parent)
108 {
109         List *lPtr;
110         W_Screen *scrPtr = W_VIEW(parent)->screen;
111
112         lPtr = wmalloc(sizeof(List));
113         memset(lPtr, 0, sizeof(List));
114
115         lPtr->widgetClass = WC_List;
116
117         lPtr->view = W_CreateView(W_VIEW(parent));
118         if (!lPtr->view) {
119                 wfree(lPtr);
120                 return NULL;
121         }
122         lPtr->view->self = lPtr;
123
124         lPtr->view->delegate = &_ListViewDelegate;
125
126         WMCreateEventHandler(lPtr->view, ExposureMask | StructureNotifyMask
127                              | ClientMessageMask, handleEvents, lPtr);
128
129         WMCreateEventHandler(lPtr->view, ButtonPressMask | ButtonReleaseMask
130                              | EnterWindowMask | LeaveWindowMask | ButtonMotionMask, handleActionEvents, lPtr);
131
132         lPtr->itemHeight = WMFontHeight(scrPtr->normalFont) + 1;
133
134         lPtr->items = WMCreateArrayWithDestructor(4, releaseItem);
135         lPtr->selectedItems = WMCreateArray(4);
136
137         /* create the vertical scroller */
138         lPtr->vScroller = WMCreateScroller(lPtr);
139         WMMoveWidget(lPtr->vScroller, 1, 1);
140         WMSetScrollerArrowsPosition(lPtr->vScroller, WSAMaxEnd);
141
142         WMSetScrollerAction(lPtr->vScroller, vScrollCallBack, lPtr);
143
144         /* make the scroller map itself when it's realized */
145         WMMapWidget(lPtr->vScroller);
146
147         W_ResizeView(lPtr->view, DEFAULT_WIDTH, DEFAULT_HEIGHT);
148
149         WMAddNotificationObserver(realizeObserver, lPtr, WMViewRealizedNotification, lPtr->view);
150
151         return lPtr;
152 }
153
154 void WMSetListAllowMultipleSelection(WMList * lPtr, Bool flag)
155 {
156         lPtr->flags.allowMultipleSelection = ((flag == 0) ? 0 : 1);
157 }
158
159 void WMSetListAllowEmptySelection(WMList * lPtr, Bool flag)
160 {
161         lPtr->flags.allowEmptySelection = ((flag == 0) ? 0 : 1);
162 }
163
164 static int comparator(const void *a, const void *b)
165 {
166         return (strcmp((*(WMListItem **) a)->text, (*(WMListItem **) b)->text));
167 }
168
169 void WMSortListItems(WMList * lPtr)
170 {
171         WMSortArray(lPtr->items, comparator);
172
173         paintList(lPtr);
174 }
175
176 void WMSortListItemsWithComparer(WMList * lPtr, WMCompareDataProc * func)
177 {
178         WMSortArray(lPtr->items, func);
179
180         paintList(lPtr);
181 }
182
183 WMListItem *WMInsertListItem(WMList * lPtr, int row, char *text)
184 {
185         WMListItem *item;
186
187         CHECK_CLASS(lPtr, WC_List);
188
189         item = wmalloc(sizeof(WMListItem));
190         memset(item, 0, sizeof(WMListItem));
191         item->text = wstrdup(text);
192
193         row = WMIN(row, WMGetArrayItemCount(lPtr->items));
194
195         if (row < 0)
196                 WMAddToArray(lPtr->items, item);
197         else
198                 WMInsertInArray(lPtr->items, row, item);
199
200         /* update the scroller when idle, so that we don't waste time
201          * updating it when another item is going to be added later */
202         if (!lPtr->idleID) {
203                 lPtr->idleID = WMAddIdleHandler((WMCallback *) updateScroller, lPtr);
204         }
205
206         return item;
207 }
208
209 void WMRemoveListItem(WMList * lPtr, int row)
210 {
211         WMListItem *item;
212         int topItem = lPtr->topItem;
213         int selNotify = 0;
214
215         CHECK_CLASS(lPtr, WC_List);
216
217         /*wassertr(row>=0 && row<WMGetArrayItemCount(lPtr->items)); */
218         if (row < 0 || row >= WMGetArrayItemCount(lPtr->items))
219                 return;
220
221         item = WMGetFromArray(lPtr->items, row);
222         if (item->selected) {
223                 WMRemoveFromArray(lPtr->selectedItems, item);
224                 selNotify = 1;
225         }
226
227         if (row <= lPtr->topItem + lPtr->fullFitLines + lPtr->flags.dontFitAll)
228                 lPtr->topItem--;
229         if (lPtr->topItem < 0)
230                 lPtr->topItem = 0;
231
232         WMDeleteFromArray(lPtr->items, row);
233
234         if (!lPtr->idleID) {
235                 lPtr->idleID = WMAddIdleHandler((WMCallback *) updateScroller, lPtr);
236         }
237         if (lPtr->topItem != topItem) {
238                 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
239         }
240         if (selNotify) {
241                 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
242         }
243 }
244
245 WMListItem *WMGetListItem(WMList * lPtr, int row)
246 {
247         return WMGetFromArray(lPtr->items, row);
248 }
249
250 WMArray *WMGetListItems(WMList * lPtr)
251 {
252         return lPtr->items;
253 }
254
255 void WMSetListUserDrawProc(WMList * lPtr, WMListDrawProc * proc)
256 {
257         lPtr->flags.userDrawn = 1;
258         lPtr->draw = proc;
259 }
260
261 void WMSetListUserDrawItemHeight(WMList * lPtr, unsigned short height)
262 {
263         assert(height > 0);
264
265         lPtr->flags.userItemHeight = 1;
266         lPtr->itemHeight = height;
267
268         updateDoubleBufferPixmap(lPtr);
269
270         updateGeometry(lPtr);
271 }
272
273 void WMClearList(WMList * lPtr)
274 {
275         int selNo = WMGetArrayItemCount(lPtr->selectedItems);
276
277         WMEmptyArray(lPtr->selectedItems);
278         WMEmptyArray(lPtr->items);
279
280         lPtr->topItem = 0;
281
282         if (!lPtr->idleID) {
283                 WMDeleteIdleHandler(lPtr->idleID);
284                 lPtr->idleID = NULL;
285         }
286         if (lPtr->selectID) {
287                 WMDeleteTimerHandler(lPtr->selectID);
288                 lPtr->selectID = NULL;
289         }
290         if (lPtr->view->flags.realized) {
291                 updateScroller(lPtr);
292         }
293         if (selNo > 0) {
294                 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
295         }
296 }
297
298 void WMSetListAction(WMList * lPtr, WMAction * action, void *clientData)
299 {
300         lPtr->action = action;
301         lPtr->clientData = clientData;
302 }
303
304 void WMSetListDoubleAction(WMList * lPtr, WMAction * action, void *clientData)
305 {
306         lPtr->doubleAction = action;
307         lPtr->doubleClientData = clientData;
308 }
309
310 WMArray *WMGetListSelectedItems(WMList * lPtr)
311 {
312         return lPtr->selectedItems;
313 }
314
315 WMListItem *WMGetListSelectedItem(WMList * lPtr)
316 {
317         return WMGetFromArray(lPtr->selectedItems, 0);
318 }
319
320 int WMGetListSelectedItemRow(WMList * lPtr)
321 {
322         WMListItem *item = WMGetFromArray(lPtr->selectedItems, 0);
323
324         return (item != NULL ? WMGetFirstInArray(lPtr->items, item) : WLNotFound);
325 }
326
327 int WMGetListItemHeight(WMList * lPtr)
328 {
329         return lPtr->itemHeight;
330 }
331
332 void WMSetListPosition(WMList * lPtr, int row)
333 {
334         lPtr->topItem = row;
335         if (lPtr->topItem + lPtr->fullFitLines > WMGetArrayItemCount(lPtr->items))
336                 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
337
338         if (lPtr->topItem < 0)
339                 lPtr->topItem = 0;
340
341         if (lPtr->view->flags.realized)
342                 updateScroller(lPtr);
343 }
344
345 void WMSetListBottomPosition(WMList * lPtr, int row)
346 {
347         if (WMGetArrayItemCount(lPtr->items) > lPtr->fullFitLines) {
348                 lPtr->topItem = row - lPtr->fullFitLines;
349                 if (lPtr->topItem < 0)
350                         lPtr->topItem = 0;
351                 if (lPtr->view->flags.realized)
352                         updateScroller(lPtr);
353         }
354 }
355
356 int WMGetListNumberOfRows(WMList * lPtr)
357 {
358         return WMGetArrayItemCount(lPtr->items);
359 }
360
361 int WMGetListPosition(WMList * lPtr)
362 {
363         return lPtr->topItem;
364 }
365
366 Bool WMListAllowsMultipleSelection(WMList * lPtr)
367 {
368         return lPtr->flags.allowMultipleSelection;
369 }
370
371 Bool WMListAllowsEmptySelection(WMList * lPtr)
372 {
373         return lPtr->flags.allowEmptySelection;
374 }
375
376 static void scrollByAmount(WMList * lPtr, int amount)
377 {
378         int itemCount = WMGetArrayItemCount(lPtr->items);
379
380         if ((amount < 0 && lPtr->topItem > 0) || (amount > 0 && (lPtr->topItem + lPtr->fullFitLines < itemCount))) {
381
382                 lPtr->topItem += amount;
383                 if (lPtr->topItem < 0)
384                         lPtr->topItem = 0;
385                 if (lPtr->topItem + lPtr->fullFitLines > itemCount)
386                         lPtr->topItem = itemCount - lPtr->fullFitLines;
387
388                 updateScroller(lPtr);
389         }
390 }
391
392 static void vScrollCallBack(WMWidget * scroller, void *self)
393 {
394         WMList *lPtr = (WMList *) self;
395         int height;
396         int oldTopItem = lPtr->topItem;
397         int itemCount = WMGetArrayItemCount(lPtr->items);
398
399         height = lPtr->view->size.height - 4;
400
401         switch (WMGetScrollerHitPart((WMScroller *) scroller)) {
402         case WSDecrementLine:
403                 scrollByAmount(lPtr, -1);
404                 break;
405
406         case WSIncrementLine:
407                 scrollByAmount(lPtr, 1);
408                 break;
409
410         case WSDecrementPage:
411                 scrollByAmount(lPtr, -lPtr->fullFitLines + (1 - lPtr->flags.dontFitAll) + 1);
412                 break;
413
414         case WSIncrementPage:
415                 scrollByAmount(lPtr, lPtr->fullFitLines - (1 - lPtr->flags.dontFitAll) - 1);
416                 break;
417
418         case WSDecrementWheel:
419                 scrollByAmount(lPtr, -lPtr->fullFitLines / 3);
420                 break;
421
422         case WSIncrementWheel:
423                 scrollByAmount(lPtr, lPtr->fullFitLines / 3);
424                 break;
425
426         case WSKnob:
427                 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) * (float)(itemCount - lPtr->fullFitLines);
428
429                 if (oldTopItem != lPtr->topItem)
430                         paintList(lPtr);        /* use updateScroller(lPtr) here? -Dan */
431                 break;
432
433         case WSKnobSlot:
434         case WSNoPart:
435         default:
436                 /* do nothing */
437                 break;
438         }
439
440         if (lPtr->topItem != oldTopItem)
441                 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
442 }
443
444 static void paintItem(List * lPtr, int index)
445 {
446         WMView *view = lPtr->view;
447         W_Screen *scr = view->screen;
448         Display *display = scr->display;
449         int width, height, x, y, tlen;
450         WMListItem *itemPtr;
451         Drawable d = lPtr->doubleBuffer;
452
453         itemPtr = WMGetFromArray(lPtr->items, index);
454
455         width = lPtr->view->size.width - 2 - 19;
456         height = lPtr->itemHeight;
457         x = 19;
458         y = 2 + (index - lPtr->topItem) * lPtr->itemHeight + 1;
459         tlen = strlen(itemPtr->text);
460
461         if (lPtr->flags.userDrawn) {
462                 WMRect rect;
463                 int flags;
464
465                 rect.size.width = width;
466                 rect.size.height = height;
467                 rect.pos.x = 0;
468                 rect.pos.y = 0;
469
470                 flags = itemPtr->uflags;
471                 if (itemPtr->disabled)
472                         flags |= WLDSDisabled;
473                 if (itemPtr->selected)
474                         flags |= WLDSSelected;
475                 if (itemPtr->isBranch)
476                         flags |= WLDSIsBranch;
477
478                 if (lPtr->draw)
479                         (*lPtr->draw) (lPtr, index, d, itemPtr->text, flags, &rect);
480
481                 XCopyArea(display, d, view->window, scr->copyGC, 0, 0, width, height, x, y);
482         } else {
483                 WMColor *back = (itemPtr->selected ? scr->white : view->backColor);
484
485                 XFillRectangle(display, d, WMColorGC(back), 0, 0, width, height);
486
487                 W_PaintText(view, d, scr->normalFont, 4, 0, width, WALeft, scr->black, False, itemPtr->text, tlen);
488                 XCopyArea(display, d, view->window, scr->copyGC, 0, 0, width, height, x, y);
489         }
490
491         if ((index - lPtr->topItem + lPtr->fullFitLines) * lPtr->itemHeight > lPtr->view->size.height - 2) {
492                 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
493                              lPtr->view->size.width, lPtr->view->size.height, WRSunken);
494         }
495 }
496
497 static void paintList(List * lPtr)
498 {
499         W_Screen *scrPtr = lPtr->view->screen;
500         int i, lim;
501
502         if (!lPtr->view->flags.mapped)
503                 return;
504
505         if (WMGetArrayItemCount(lPtr->items) > 0) {
506                 if (lPtr->topItem + lPtr->fullFitLines + lPtr->flags.dontFitAll > WMGetArrayItemCount(lPtr->items)) {
507
508                         lim = WMGetArrayItemCount(lPtr->items) - lPtr->topItem;
509                         XClearArea(scrPtr->display, lPtr->view->window, 19,
510                                    2 + lim * lPtr->itemHeight, lPtr->view->size.width - 21,
511                                    lPtr->view->size.height - lim * lPtr->itemHeight - 3, False);
512                 } else {
513                         lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
514                 }
515                 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
516                         paintItem(lPtr, i);
517                 }
518         } else {
519                 XClearWindow(scrPtr->display, lPtr->view->window);
520         }
521         W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width, lPtr->view->size.height, WRSunken);
522 }
523
524 #if 0
525 static void scrollTo(List * lPtr, int newTop)
526 {
527
528 }
529 #endif
530
531 static void updateScroller(void *data)
532 {
533         List *lPtr = (List *) data;
534
535         float knobProportion, floatValue, tmp;
536         int count = WMGetArrayItemCount(lPtr->items);
537
538         if (lPtr->idleID)
539                 WMDeleteIdleHandler(lPtr->idleID);
540         lPtr->idleID = NULL;
541
542         paintList(lPtr);
543
544         if (count == 0 || count <= lPtr->fullFitLines)
545                 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
546         else {
547                 tmp = lPtr->fullFitLines;
548                 knobProportion = tmp / (float)count;
549
550                 floatValue = (float)lPtr->topItem / (float)(count - lPtr->fullFitLines);
551
552                 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
553         }
554 }
555
556 static void scrollForwardSelecting(void *data)
557 {
558         List *lPtr = (List *) data;
559         int lastSelected;
560
561         lastSelected = lPtr->topItem + lPtr->fullFitLines + lPtr->flags.dontFitAll - 1;
562
563         if (lastSelected >= WMGetArrayItemCount(lPtr->items) - 1) {
564                 lPtr->selectID = NULL;
565                 if (lPtr->flags.dontFitAll)
566                         scrollByAmount(lPtr, 1);
567                 return;
568         }
569
570         /* selecting NEEDS to be done before scrolling to avoid flickering */
571         if (lPtr->flags.allowMultipleSelection) {
572                 WMListItem *item;
573                 WMRange range;
574
575                 item = WMGetFromArray(lPtr->selectedItems, 0);
576                 range.position = WMGetFirstInArray(lPtr->items, item);
577                 if (lastSelected + 1 >= range.position) {
578                         range.count = lastSelected - range.position + 2;
579                 } else {
580                         range.count = lastSelected - range.position;
581                 }
582                 WMSetListSelectionToRange(lPtr, range);
583         } else {
584                 WMSelectListItem(lPtr, lastSelected + 1);
585         }
586         scrollByAmount(lPtr, 1);
587
588         lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting, lPtr);
589 }
590
591 static void scrollBackwardSelecting(void *data)
592 {
593         List *lPtr = (List *) data;
594
595         if (lPtr->topItem < 1) {
596                 lPtr->selectID = NULL;
597                 return;
598         }
599
600         /* selecting NEEDS to be done before scrolling to avoid flickering */
601         if (lPtr->flags.allowMultipleSelection) {
602                 WMListItem *item;
603                 WMRange range;
604
605                 item = WMGetFromArray(lPtr->selectedItems, 0);
606                 range.position = WMGetFirstInArray(lPtr->items, item);
607                 if (lPtr->topItem - 1 >= range.position) {
608                         range.count = lPtr->topItem - range.position;
609                 } else {
610                         range.count = lPtr->topItem - range.position - 2;
611                 }
612                 WMSetListSelectionToRange(lPtr, range);
613         } else {
614                 WMSelectListItem(lPtr, lPtr->topItem - 1);
615         }
616         scrollByAmount(lPtr, -1);
617
618         lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting, lPtr);
619 }
620
621 static void handleEvents(XEvent * event, void *data)
622 {
623         List *lPtr = (List *) data;
624
625         CHECK_CLASS(data, WC_List);
626
627         switch (event->type) {
628         case Expose:
629                 if (event->xexpose.count != 0)
630                         break;
631                 paintList(lPtr);
632                 break;
633
634         case DestroyNotify:
635                 destroyList(lPtr);
636                 break;
637
638         }
639 }
640
641 static int matchTitle(void *item, void *title)
642 {
643         return (strcmp(((WMListItem *) item)->text, (char *)title) == 0 ? 1 : 0);
644 }
645
646 int WMFindRowOfListItemWithTitle(WMList * lPtr, char *title)
647 {
648         return WMFindInArray(lPtr->items, matchTitle, title);
649 }
650
651 void WMSelectListItem(WMList * lPtr, int row)
652 {
653         WMListItem *item;
654
655         if (row >= WMGetArrayItemCount(lPtr->items))
656                 return;
657
658         if (row < 0) {
659                 /* row = -1 will deselects all for backward compatibility.
660                  * will be removed later. -Dan */
661                 WMUnselectAllListItems(lPtr);
662                 return;
663         }
664
665         item = WMGetFromArray(lPtr->items, row);
666         if (item->selected)
667                 return;         /* Return if already selected */
668
669         if (!lPtr->flags.allowMultipleSelection) {
670                 /* unselect previous selected items */
671                 unselectAllListItems(lPtr, NULL);
672         }
673
674         /* select item */
675         item->selected = 1;
676         WMAddToArray(lPtr->selectedItems, item);
677
678         if (lPtr->view->flags.mapped && row >= lPtr->topItem && row <= lPtr->topItem + lPtr->fullFitLines) {
679                 paintItem(lPtr, row);
680         }
681
682         WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
683 }
684
685 void WMUnselectListItem(WMList * lPtr, int row)
686 {
687         WMListItem *item = WMGetFromArray(lPtr->items, row);
688
689         if (!item || !item->selected)
690                 return;
691
692         if (!lPtr->flags.allowEmptySelection && WMGetArrayItemCount(lPtr->selectedItems) <= 1) {
693                 return;
694         }
695
696         item->selected = 0;
697         WMRemoveFromArray(lPtr->selectedItems, item);
698
699         if (lPtr->view->flags.mapped && row >= lPtr->topItem && row <= lPtr->topItem + lPtr->fullFitLines) {
700                 paintItem(lPtr, row);
701         }
702
703         WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
704 }
705
706 void WMSelectListItemsInRange(WMList * lPtr, WMRange range)
707 {
708         WMListItem *item;
709         int position = range.position, k = 1, notify = 0;
710         int total = WMGetArrayItemCount(lPtr->items);
711
712         if (!lPtr->flags.allowMultipleSelection)
713                 return;
714         if (range.count == 0)
715                 return;         /* Nothing to select */
716
717         if (range.count < 0) {
718                 range.count = -range.count;
719                 k = -1;
720         }
721
722         for (; range.count > 0 && position >= 0 && position < total; range.count--) {
723                 item = WMGetFromArray(lPtr->items, position);
724                 if (!item->selected) {
725                         item->selected = 1;
726                         WMAddToArray(lPtr->selectedItems, item);
727                         if (lPtr->view->flags.mapped && position >= lPtr->topItem
728                             && position <= lPtr->topItem + lPtr->fullFitLines) {
729                                 paintItem(lPtr, position);
730                         }
731                         notify = 1;
732                 }
733                 position += k;
734         }
735
736         if (notify) {
737                 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
738         }
739 }
740
741 void WMSetListSelectionToRange(WMList * lPtr, WMRange range)
742 {
743         WMListItem *item;
744         int mark1, mark2, i, k;
745         int position = range.position, notify = 0;
746         int total = WMGetArrayItemCount(lPtr->items);
747
748         if (!lPtr->flags.allowMultipleSelection)
749                 return;
750
751         if (range.count == 0) {
752                 WMUnselectAllListItems(lPtr);
753                 return;
754         }
755
756         if (range.count < 0) {
757                 mark1 = range.position + range.count + 1;
758                 mark2 = range.position + 1;
759                 range.count = -range.count;
760                 k = -1;
761         } else {
762                 mark1 = range.position;
763                 mark2 = range.position + range.count;
764                 k = 1;
765         }
766         if (mark1 > total)
767                 mark1 = total;
768         if (mark2 < 0)
769                 mark2 = 0;
770
771         WMEmptyArray(lPtr->selectedItems);
772
773         for (i = 0; i < mark1; i++) {
774                 item = WMGetFromArray(lPtr->items, i);
775                 if (item->selected) {
776                         item->selected = 0;
777                         if (lPtr->view->flags.mapped && i >= lPtr->topItem
778                             && i <= lPtr->topItem + lPtr->fullFitLines) {
779                                 paintItem(lPtr, i);
780                         }
781                         notify = 1;
782                 }
783         }
784         for (; range.count > 0 && position >= 0 && position < total; range.count--) {
785                 item = WMGetFromArray(lPtr->items, position);
786                 if (!item->selected) {
787                         item->selected = 1;
788                         if (lPtr->view->flags.mapped && position >= lPtr->topItem
789                             && position <= lPtr->topItem + lPtr->fullFitLines) {
790                                 paintItem(lPtr, position);
791                         }
792                         notify = 1;
793                 }
794                 WMAddToArray(lPtr->selectedItems, item);
795                 position += k;
796         }
797         for (i = mark2; i < total; i++) {
798                 item = WMGetFromArray(lPtr->items, i);
799                 if (item->selected) {
800                         item->selected = 0;
801                         if (lPtr->view->flags.mapped && i >= lPtr->topItem
802                             && i <= lPtr->topItem + lPtr->fullFitLines) {
803                                 paintItem(lPtr, i);
804                         }
805                         notify = 1;
806                 }
807         }
808
809         if (notify) {
810                 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
811         }
812 }
813
814 void WMSelectAllListItems(WMList * lPtr)
815 {
816         int i;
817         WMListItem *item;
818
819         if (!lPtr->flags.allowMultipleSelection)
820                 return;
821
822         if (WMGetArrayItemCount(lPtr->items) == WMGetArrayItemCount(lPtr->selectedItems)) {
823                 return;         /* All items are selected already */
824         }
825
826         WMFreeArray(lPtr->selectedItems);
827         lPtr->selectedItems = WMCreateArrayWithArray(lPtr->items);
828
829         for (i = 0; i < WMGetArrayItemCount(lPtr->items); i++) {
830                 item = WMGetFromArray(lPtr->items, i);
831                 if (!item->selected) {
832                         item->selected = 1;
833                         if (lPtr->view->flags.mapped && i >= lPtr->topItem
834                             && i <= lPtr->topItem + lPtr->fullFitLines) {
835                                 paintItem(lPtr, i);
836                         }
837                 }
838         }
839
840         WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
841 }
842
843 /*
844  * Be careful from where you call this function! It doesn't honor the
845  * allowEmptySelection flag and doesn't send a notification about selection
846  * change! You need to manage these in the functions from where you call it.
847  *
848  * This will unselect all items if exceptThis is NULL, else will keep
849  * exceptThis selected.
850  * Make sure that exceptThis is one of the already selected items if not NULL!
851  *
852  */
853 static void unselectAllListItems(WMList * lPtr, WMListItem * exceptThis)
854 {
855         int i;
856         WMListItem *item;
857
858         for (i = 0; i < WMGetArrayItemCount(lPtr->items); i++) {
859                 item = WMGetFromArray(lPtr->items, i);
860                 if (item != exceptThis && item->selected) {
861                         item->selected = 0;
862                         if (lPtr->view->flags.mapped && i >= lPtr->topItem
863                             && i <= lPtr->topItem + lPtr->fullFitLines) {
864                                 paintItem(lPtr, i);
865                         }
866                 }
867         }
868
869         WMEmptyArray(lPtr->selectedItems);
870         if (exceptThis != NULL) {
871                 exceptThis->selected = 1;
872                 WMAddToArray(lPtr->selectedItems, exceptThis);
873         }
874 }
875
876 void WMUnselectAllListItems(WMList * lPtr)
877 {
878         int keep;
879         WMListItem *keepItem;
880
881         keep = lPtr->flags.allowEmptySelection ? 0 : 1;
882
883         if (WMGetArrayItemCount(lPtr->selectedItems) == keep)
884                 return;
885
886         keepItem = (keep == 1 ? WMGetFromArray(lPtr->selectedItems, 0) : NULL);
887
888         unselectAllListItems(lPtr, keepItem);
889
890         WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr, NULL);
891 }
892
893 static int getItemIndexAt(List * lPtr, int clickY)
894 {
895         int index;
896
897         index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
898
899         if (index < 0 || index >= WMGetArrayItemCount(lPtr->items))
900                 return -1;
901
902         return index;
903 }
904
905 static void toggleItemSelection(WMList * lPtr, int index)
906 {
907         WMListItem *item = WMGetFromArray(lPtr->items, index);
908
909         if (item && item->selected) {
910                 WMUnselectListItem(lPtr, index);
911         } else {
912                 WMSelectListItem(lPtr, index);
913         }
914 }
915
916 static void handleActionEvents(XEvent * event, void *data)
917 {
918         List *lPtr = (List *) data;
919         int tmp, height;
920         int topItem = lPtr->topItem;
921         static int lastClicked = -1, prevItem = -1;
922
923         CHECK_CLASS(data, WC_List);
924
925         switch (event->type) {
926         case ButtonRelease:
927                 /* Ignore mouse wheel events, they're not "real" button events */
928                 if (event->xbutton.button == WINGsConfiguration.mouseWheelUp ||
929                     event->xbutton.button == WINGsConfiguration.mouseWheelDown) {
930                         break;
931                 }
932
933                 lPtr->flags.buttonPressed = 0;
934                 if (lPtr->selectID) {
935                         WMDeleteTimerHandler(lPtr->selectID);
936                         lPtr->selectID = NULL;
937                 }
938                 tmp = getItemIndexAt(lPtr, event->xbutton.y);
939
940                 if (tmp >= 0) {
941                         if (lPtr->action)
942                                 (*lPtr->action) (lPtr, lPtr->clientData);
943                 }
944
945                 if (!(event->xbutton.state & ShiftMask))
946                         lastClicked = prevItem = tmp;
947
948                 break;
949
950         case EnterNotify:
951                 if (lPtr->selectID) {
952                         WMDeleteTimerHandler(lPtr->selectID);
953                         lPtr->selectID = NULL;
954                 }
955                 break;
956
957         case LeaveNotify:
958                 height = WMWidgetHeight(lPtr);
959                 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
960                         if (event->xcrossing.y >= height) {
961                                 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting, lPtr);
962                         } else if (event->xcrossing.y <= 0) {
963                                 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting, lPtr);
964                         }
965                 }
966                 break;
967
968         case ButtonPress:
969                 if (event->xbutton.x <= WMWidgetWidth(lPtr->vScroller))
970                         break;
971                 if (event->xbutton.button == WINGsConfiguration.mouseWheelDown ||
972                     event->xbutton.button == WINGsConfiguration.mouseWheelUp) {
973                         int amount = 0;
974
975                         if (event->xbutton.state & ControlMask) {
976                                 amount = lPtr->fullFitLines - (1 - lPtr->flags.dontFitAll) - 1;
977                         } else if (event->xbutton.state & ShiftMask) {
978                                 amount = 1;
979                         } else {
980                                 amount = lPtr->fullFitLines / 3;
981                                 if (amount == 0)
982                                         amount++;
983                         }
984                         if (event->xbutton.button == WINGsConfiguration.mouseWheelUp)
985                                 amount = -amount;
986
987                         scrollByAmount(lPtr, amount);
988                         break;
989                 }
990
991                 tmp = getItemIndexAt(lPtr, event->xbutton.y);
992                 lPtr->flags.buttonPressed = 1;
993
994                 if (tmp >= 0) {
995                         if (tmp == lastClicked && WMIsDoubleClick(event)) {
996                                 WMSelectListItem(lPtr, tmp);
997                                 if (lPtr->doubleAction)
998                                         (*lPtr->doubleAction) (lPtr, lPtr->doubleClientData);
999                         } else {
1000                                 if (!lPtr->flags.allowMultipleSelection) {
1001                                         if (event->xbutton.state & ControlMask) {
1002                                                 toggleItemSelection(lPtr, tmp);
1003                                         } else {
1004                                                 WMSelectListItem(lPtr, tmp);
1005                                         }
1006                                 } else {
1007                                         WMRange range;
1008                                         WMListItem *lastSel;
1009
1010                                         if (event->xbutton.state & ControlMask) {
1011                                                 toggleItemSelection(lPtr, tmp);
1012                                         } else if (event->xbutton.state & ShiftMask) {
1013                                                 if (WMGetArrayItemCount(lPtr->selectedItems) == 0) {
1014                                                         WMSelectListItem(lPtr, tmp);
1015                                                 } else {
1016                                                         lastSel = WMGetFromArray(lPtr->items, lastClicked);
1017                                                         range.position = WMGetFirstInArray(lPtr->items, lastSel);
1018                                                         if (tmp >= range.position)
1019                                                                 range.count = tmp - range.position + 1;
1020                                                         else
1021                                                                 range.count = tmp - range.position - 1;
1022
1023                                                         WMSetListSelectionToRange(lPtr, range);
1024                                                 }
1025                                         } else {
1026                                                 range.position = tmp;
1027                                                 range.count = 1;
1028                                                 WMSetListSelectionToRange(lPtr, range);
1029                                         }
1030                                 }
1031                         }
1032                 }
1033
1034                 if (!(event->xbutton.state & ShiftMask))
1035                         lastClicked = prevItem = tmp;
1036
1037                 break;
1038
1039         case MotionNotify:
1040                 height = WMWidgetHeight(lPtr);
1041                 if (lPtr->selectID && event->xmotion.y > 0 && event->xmotion.y < height) {
1042                         WMDeleteTimerHandler(lPtr->selectID);
1043                         lPtr->selectID = NULL;
1044                 }
1045                 if (lPtr->flags.buttonPressed && !lPtr->selectID) {
1046                         if (event->xmotion.y <= 0) {
1047                                 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollBackwardSelecting, lPtr);
1048                                 break;
1049                         } else if (event->xmotion.y >= height) {
1050                                 lPtr->selectID = WMAddTimerHandler(SCROLL_DELAY, scrollForwardSelecting, lPtr);
1051                                 break;
1052                         }
1053
1054                         tmp = getItemIndexAt(lPtr, event->xmotion.y);
1055                         if (tmp >= 0 && tmp != prevItem) {
1056                                 if (lPtr->flags.allowMultipleSelection) {
1057                                         WMRange range;
1058
1059                                         range.position = lastClicked;
1060                                         if (tmp >= lastClicked)
1061                                                 range.count = tmp - lastClicked + 1;
1062                                         else
1063                                                 range.count = tmp - lastClicked - 1;
1064                                         WMSetListSelectionToRange(lPtr, range);
1065                                 } else {
1066                                         WMSelectListItem(lPtr, tmp);
1067                                 }
1068                         }
1069                         prevItem = tmp;
1070                 }
1071                 break;
1072         }
1073         if (lPtr->topItem != topItem)
1074                 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
1075 }
1076
1077 static void updateGeometry(WMList * lPtr)
1078 {
1079         lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
1080         if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
1081                 lPtr->flags.dontFitAll = 1;
1082         } else {
1083                 lPtr->flags.dontFitAll = 0;
1084         }
1085
1086         if (WMGetArrayItemCount(lPtr->items) - lPtr->topItem <= lPtr->fullFitLines) {
1087                 lPtr->topItem = WMGetArrayItemCount(lPtr->items) - lPtr->fullFitLines;
1088                 if (lPtr->topItem < 0)
1089                         lPtr->topItem = 0;
1090         }
1091
1092         updateScroller(lPtr);
1093 }
1094
1095 static void didResizeList(W_ViewDelegate * self, WMView * view)
1096 {
1097         WMList *lPtr = (WMList *) view->self;
1098
1099         WMResizeWidget(lPtr->vScroller, 1, view->size.height - 2);
1100
1101         updateDoubleBufferPixmap(lPtr);
1102
1103         updateGeometry(lPtr);
1104 }
1105
1106 static void destroyList(List * lPtr)
1107 {
1108         if (lPtr->idleID)
1109                 WMDeleteIdleHandler(lPtr->idleID);
1110         lPtr->idleID = NULL;
1111
1112         if (lPtr->selectID)
1113                 WMDeleteTimerHandler(lPtr->selectID);
1114         lPtr->selectID = NULL;
1115
1116         if (lPtr->selectedItems)
1117                 WMFreeArray(lPtr->selectedItems);
1118
1119         if (lPtr->items)
1120                 WMFreeArray(lPtr->items);
1121
1122         if (lPtr->doubleBuffer)
1123                 XFreePixmap(lPtr->view->screen->display, lPtr->doubleBuffer);
1124
1125         WMRemoveNotificationObserver(lPtr);
1126
1127         wfree(lPtr);
1128 }