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 }