Update for 0.52.0. This is a test version, which brings the Appearance
[wmaker-crm.git] / WINGs / wlist.c
blob85bda3050549153a46ae816c2a419eaf45ce57c6
5 #include "WINGsP.h"
7 char *WMListDidScrollNotification = "WMListDidScrollNotification";
8 char *WMListSelectionDidChangeNotification = "WMListSelectionDidChangeNotification";
10 typedef struct W_List {
11 W_Class widgetClass;
12 W_View *view;
14 WMListItem *items; /* array of items */
15 short itemCount;
17 short selectedItem;
19 short itemHeight;
21 short topItem; /* index of first visible item */
23 short fullFitLines; /* no of lines that fit entirely */
25 void *clientData;
26 WMAction *action;
27 void *doubleClientData;
28 WMAction *doubleAction;
30 WMListDrawProc *draw;
32 WMHandlerID *idleID; /* for updating the scroller after
33 * adding elements */
35 WMScroller *vScroller;
37 WMScroller *hScroller;
40 struct {
41 unsigned int allowMultipleSelection:1;
42 unsigned int userDrawn:1;
43 unsigned int userItemHeight:1;
44 /* */
45 unsigned int dontFitAll:1; /* 1 = last item won't be fully visible */
46 unsigned int redrawPending:1;
47 unsigned int buttonPressed:1;
48 unsigned int buttonWasPressed:1;
49 } flags;
50 } List;
54 #define DEFAULT_WIDTH 150
55 #define DEFAULT_HEIGHT 150
58 static void destroyList(List *lPtr);
59 static void paintList(List *lPtr);
62 static void handleEvents(XEvent *event, void *data);
63 static void handleActionEvents(XEvent *event, void *data);
64 static void updateScroller(List *lPtr);
66 static void vScrollCallBack(WMWidget *scroller, void *self);
68 static void updateGeometry(WMList *lPtr);
69 static void resizeList();
72 W_ViewProcedureTable _ListViewProcedures = {
73 NULL,
74 resizeList,
75 NULL
80 WMList*
81 WMCreateList(WMWidget *parent)
83 List *lPtr;
84 W_Screen *scrPtr = W_VIEW(parent)->screen;
86 lPtr = wmalloc(sizeof(List));
87 memset(lPtr, 0, sizeof(List));
89 lPtr->widgetClass = WC_List;
91 lPtr->view = W_CreateView(W_VIEW(parent));
92 if (!lPtr->view) {
93 free(lPtr);
94 return NULL;
96 lPtr->view->self = lPtr;
98 WMCreateEventHandler(lPtr->view, ExposureMask|StructureNotifyMask
99 |ClientMessageMask, handleEvents, lPtr);
101 WMCreateEventHandler(lPtr->view, ButtonPressMask|ButtonReleaseMask
102 |EnterWindowMask|LeaveWindowMask|ButtonMotionMask,
103 handleActionEvents, lPtr);
105 lPtr->itemHeight = WMFontHeight(scrPtr->normalFont) + 1;
107 /* create the vertical scroller */
108 lPtr->vScroller = WMCreateScroller(lPtr);
109 WMMoveWidget(lPtr->vScroller, 1, 1);
110 WMSetScrollerArrowsPosition(lPtr->vScroller, WSAMaxEnd);
112 WMSetScrollerAction(lPtr->vScroller, vScrollCallBack, lPtr);
114 /* make the scroller map itself when it's realized */
115 WMMapWidget(lPtr->vScroller);
117 resizeList(lPtr, DEFAULT_WIDTH, DEFAULT_HEIGHT);
119 lPtr->selectedItem = -1;
121 return lPtr;
126 WMListItem*
127 WMAddSortedListItem(WMList *lPtr, char *text)
129 WMListItem *item;
130 WMListItem *tmp;
131 int index;
133 item = wmalloc(sizeof(WMListItem));
134 memset(item, 0, sizeof(WMListItem));
135 item->text = wstrdup(text);
136 if (!lPtr->items) {
137 lPtr->items = item;
138 index = 0;
139 } else if (strcmp(lPtr->items->text, text) > 0) {
140 item->nextPtr = lPtr->items;
141 lPtr->items = item;
142 index = 1;
143 } else {
144 int added = 0;
146 index = 0;
147 tmp = lPtr->items;
148 while (tmp->nextPtr) {
149 if (strcmp(tmp->nextPtr->text, text) >= 0) {
150 item->nextPtr = tmp->nextPtr;
151 tmp->nextPtr = item;
152 added = 1;
153 break;
155 index++;
156 tmp = tmp->nextPtr;
158 if (!added) {
159 tmp->nextPtr = item;
163 lPtr->itemCount++;
165 if (lPtr->selectedItem >= index)
166 lPtr->selectedItem++;
168 /* update the scroller when idle, so that we don't waste time
169 * updating it when another item is going to be added later */
170 if (!lPtr->idleID) {
171 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
174 return item;
179 WMListItem*
180 WMInsertListItem(WMList *lPtr, int row, char *text)
182 WMListItem *item;
183 WMListItem *tmp = lPtr->items;
185 CHECK_CLASS(lPtr, WC_List);
187 item = wmalloc(sizeof(WMListItem));
188 memset(item, 0, sizeof(WMListItem));
189 item->text = wstrdup(text);
192 if (lPtr->selectedItem >= row && lPtr->selectedItem >= 0
193 && row >= 0)
194 lPtr->selectedItem++;
196 if (lPtr->items==NULL) {
197 lPtr->items = item;
198 } else if (row == 0) {
199 item->nextPtr = lPtr->items;
200 lPtr->items = item;
201 } else if (row < 0) {
202 while (tmp->nextPtr)
203 tmp = tmp->nextPtr;
205 tmp->nextPtr = item;
206 row = lPtr->itemCount;
207 } else {
208 while (--row > 0)
209 tmp = tmp->nextPtr;
211 item->nextPtr = tmp->nextPtr;
212 tmp->nextPtr = item;
215 lPtr->itemCount++;
217 /* update the scroller when idle, so that we don't waste time
218 * updating it when another item is going to be added later */
219 if (!lPtr->idleID) {
220 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
223 return item;
227 void
228 WMRemoveListItem(WMList *lPtr, int row)
230 WMListItem *llist;
231 WMListItem *tmp;
232 int topItem = lPtr->topItem;
233 int selNotify = 0;
235 CHECK_CLASS(lPtr, WC_List);
237 if (row < 0 || row >= lPtr->itemCount)
238 return;
240 if (lPtr->selectedItem == row) {
241 lPtr->selectedItem = -1;
242 selNotify = 1;
243 } else if (lPtr->selectedItem > row) {
244 lPtr->selectedItem--;
247 if (row <= lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll)
248 lPtr->topItem--;
249 if (lPtr->topItem < 0)
250 lPtr->topItem = 0;
252 if (row == 0) {
253 if (lPtr->items->text)
254 free(lPtr->items->text);
256 tmp = lPtr->items->nextPtr;
257 free(lPtr->items);
259 lPtr->items = tmp;
260 } else {
261 llist = lPtr->items;
262 while (--row > 0)
263 llist = llist->nextPtr;
264 tmp = llist->nextPtr;
265 llist->nextPtr = llist->nextPtr->nextPtr;
267 if (tmp->text)
268 free(tmp->text);
270 free(tmp);
273 lPtr->itemCount--;
275 if (!lPtr->idleID) {
276 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
278 if (lPtr->topItem != topItem)
279 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
280 if (selNotify)
281 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
282 (void*)((int)lPtr->selectedItem));
287 WMListItem*
288 WMGetListItem(WMList *lPtr, int row)
290 WMListItem *listPtr;
292 listPtr = lPtr->items;
294 while (row-- > 0)
295 listPtr = listPtr->nextPtr;
297 return listPtr;
302 void
303 WMSetListUserDrawProc(WMList *lPtr, WMListDrawProc *proc)
305 lPtr->flags.userDrawn = 1;
306 lPtr->draw = proc;
310 void
311 WMSetListUserDrawItemHeight(WMList *lPtr, unsigned short height)
313 assert(height > 0);
315 lPtr->flags.userItemHeight = 1;
316 lPtr->itemHeight = height;
318 updateGeometry(lPtr);
322 void
323 WMClearList(WMList *lPtr)
325 WMListItem *item, *tmp;
326 int oldSelected = lPtr->selectedItem;
328 item = lPtr->items;
329 while (item) {
330 free(item->text);
331 tmp = item->nextPtr;
332 free(item);
333 item = tmp;
335 lPtr->items = NULL;
336 lPtr->itemCount = 0;
337 lPtr->topItem = 0;
338 lPtr->selectedItem = -1;
340 if (!lPtr->idleID) {
341 WMDeleteIdleHandler(lPtr->idleID);
342 lPtr->idleID = NULL;
344 if (lPtr->view->flags.realized) {
345 updateScroller(lPtr);
347 if (oldSelected != -1)
348 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
349 (void*)-1);
353 void
354 WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
356 lPtr->action = action;
357 lPtr->clientData = clientData;
361 void
362 WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
364 lPtr->doubleAction = action;
365 lPtr->doubleClientData = clientData;
369 WMListItem*
370 WMGetListSelectedItem(WMList *lPtr)
372 int i = lPtr->selectedItem;
373 WMListItem *item;
375 if (lPtr->selectedItem < 0)
376 return NULL;
378 item = lPtr->items;
379 while (i-- > 0) {
380 item = item->nextPtr;
382 return item;
387 WMGetListSelectedItemRow(WMList *lPtr)
389 return lPtr->selectedItem;
394 WMGetListItemHeight(WMList *lPtr)
396 return lPtr->itemHeight;
400 void
401 WMSetListPosition(WMList *lPtr, int row)
403 lPtr->topItem = row;
404 if (lPtr->topItem + lPtr->fullFitLines > lPtr->itemCount)
405 lPtr->topItem = lPtr->itemCount - lPtr->fullFitLines;
407 if (lPtr->topItem < 0)
408 lPtr->topItem = 0;
410 if (lPtr->view->flags.realized)
411 updateScroller(lPtr);
415 void
416 WMSetListBottomPosition(WMList *lPtr, int row)
418 if (lPtr->itemCount > lPtr->fullFitLines) {
419 lPtr->topItem = row - lPtr->fullFitLines;
420 if (lPtr->topItem < 0)
421 lPtr->topItem = 0;
422 if (lPtr->view->flags.realized)
423 updateScroller(lPtr);
429 WMGetListNumberOfRows(WMList *lPtr)
431 return lPtr->itemCount;
435 WMGetListPosition(WMList *lPtr)
437 return lPtr->topItem;
441 static void
442 vScrollCallBack(WMWidget *scroller, void *self)
444 WMList *lPtr = (WMList*)self;
445 WMScroller *sPtr = (WMScroller*)scroller;
446 int height;
447 int topItem = lPtr->topItem;
449 height = lPtr->view->size.height - 4;
451 switch (WMGetScrollerHitPart(sPtr)) {
452 case WSDecrementLine:
453 if (lPtr->topItem > 0) {
454 lPtr->topItem--;
456 updateScroller(lPtr);
458 break;
460 case WSDecrementPage:
461 if (lPtr->topItem > 0) {
462 lPtr->topItem -= lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
463 if (lPtr->topItem < 0)
464 lPtr->topItem = 0;
466 updateScroller(lPtr);
468 break;
471 case WSIncrementLine:
472 if (lPtr->topItem + lPtr->fullFitLines < lPtr->itemCount) {
473 lPtr->topItem++;
475 updateScroller(lPtr);
477 break;
479 case WSIncrementPage:
480 if (lPtr->topItem + lPtr->fullFitLines < lPtr->itemCount) {
481 lPtr->topItem += lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
483 if (lPtr->topItem + lPtr->fullFitLines > lPtr->itemCount)
484 lPtr->topItem = lPtr->itemCount - lPtr->fullFitLines;
486 updateScroller(lPtr);
488 break;
490 case WSKnob:
492 int oldTopItem = lPtr->topItem;
494 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
495 (float)(lPtr->itemCount - lPtr->fullFitLines);
497 if (oldTopItem != lPtr->topItem)
498 paintList(lPtr);
500 break;
502 case WSKnobSlot:
503 case WSNoPart:
504 /* do nothing */
505 break;
508 if (lPtr->topItem != topItem)
509 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
513 static void
514 paintItem(List *lPtr, int index)
516 WMView *view = lPtr->view;
517 W_Screen *scr = view->screen;
518 int width, height, x, y;
519 WMListItem *itemPtr;
520 int i;
522 i = index;
523 itemPtr = lPtr->items;
524 while (i-- > 0)
525 itemPtr = itemPtr->nextPtr;
527 width = lPtr->view->size.width - 2 - 19;
528 height = lPtr->itemHeight;
529 x = 19;
530 y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
532 if (lPtr->flags.userDrawn) {
533 WMRect rect;
534 int flags;
536 rect.size.width = width;
537 rect.size.height = height;
538 rect.pos.x = x;
539 rect.pos.y = y;
541 flags = itemPtr->uflags;
542 if (itemPtr->disabled)
543 flags |= WLDSDisabled;
544 if (itemPtr->selected)
545 flags |= WLDSSelected;
546 if (itemPtr->isBranch)
547 flags |= WLDSIsBranch;
549 if (lPtr->draw)
550 (*lPtr->draw)(lPtr, index, view->window, itemPtr->text, flags,
551 &rect);
552 } else {
553 if (itemPtr->selected)
554 XFillRectangle(scr->display, view->window, WMColorGC(scr->white), x, y,
555 width, height);
556 else
557 XClearArea(scr->display, view->window, x, y, width, height, False);
559 W_PaintText(view, view->window, scr->normalFont, x+4, y, width,
560 WALeft, WMColorGC(scr->black), False,
561 itemPtr->text, strlen(itemPtr->text));
567 static void
568 paintList(List *lPtr)
570 W_Screen *scrPtr = lPtr->view->screen;
571 int i, lim;
573 if (!lPtr->view->flags.mapped)
574 return;
576 if (lPtr->itemCount>0) {
577 if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll > lPtr->itemCount) {
578 lim = lPtr->itemCount - lPtr->topItem;
579 XClearArea(scrPtr->display, lPtr->view->window, 19,
580 2+lim*lPtr->itemHeight, lPtr->view->size.width-21,
581 lPtr->view->size.height-lim*lPtr->itemHeight-3, False);
582 } else {
583 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
585 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
586 paintItem(lPtr, i);
588 } else {
589 XClearWindow(scrPtr->display, lPtr->view->window);
591 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width,
592 lPtr->view->size.height, WRSunken);
595 #if 0
596 static void
597 scrollTo(List *lPtr, int newTop)
601 #endif
603 static void
604 updateScroller(List *lPtr)
606 float knobProportion, floatValue, tmp;
608 if (lPtr->idleID)
609 WMDeleteIdleHandler(lPtr->idleID);
610 lPtr->idleID = NULL;
612 paintList(lPtr);
614 if (lPtr->itemCount == 0 || lPtr->itemCount <= lPtr->fullFitLines)
615 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
616 else {
617 tmp = lPtr->fullFitLines;
618 knobProportion = tmp/(float)lPtr->itemCount;
620 floatValue = (float)lPtr->topItem/(float)(lPtr->itemCount - lPtr->fullFitLines);
622 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
627 static void
628 handleEvents(XEvent *event, void *data)
630 List *lPtr = (List*)data;
632 CHECK_CLASS(data, WC_List);
635 switch (event->type) {
636 case Expose:
637 if (event->xexpose.count!=0)
638 break;
639 paintList(lPtr);
640 break;
642 case DestroyNotify:
643 destroyList(lPtr);
644 break;
651 WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
653 WMListItem *item;
654 int i;
655 int ok = 0;
657 for (i=0, item=lPtr->items; item!=NULL; item=item->nextPtr, i++) {
658 if (strcmp(item->text, title)==0) {
659 ok = 1;
660 break;
664 return ok ? i : -1;
668 void
669 WMSelectListItem(WMList *lPtr, int row)
671 WMListItem *itemPtr;
672 int i, notify = 0;
674 if (row >= lPtr->itemCount)
675 return;
677 /* the check below must be changed when the multiple selection is
678 * implemented. -Dan
680 if (!lPtr->flags.allowMultipleSelection && row == lPtr->selectedItem)
681 notify = 0;
682 else
683 notify = 1;
685 if (!lPtr->flags.allowMultipleSelection) {
686 /* unselect previous selected item */
687 if (lPtr->selectedItem >= 0) {
688 itemPtr = lPtr->items;
689 for (i=0; i<lPtr->selectedItem; i++)
690 itemPtr = itemPtr->nextPtr;
692 if (itemPtr->selected) {
693 itemPtr->selected = 0;
694 if (lPtr->view->flags.mapped && i>=lPtr->topItem
695 && i<=lPtr->topItem+lPtr->fullFitLines)
696 paintItem(lPtr, i);
701 if (row < 0) {
702 if (!lPtr->flags.allowMultipleSelection) {
703 lPtr->selectedItem = -1;
704 if (notify)
705 WMPostNotificationName(WMListSelectionDidChangeNotification,
706 lPtr, (void*)((int)lPtr->selectedItem));
708 return;
711 /* select item */
712 itemPtr = lPtr->items;
713 for (i=0; i<row; i++)
714 itemPtr = itemPtr->nextPtr;
715 if (lPtr->flags.allowMultipleSelection)
716 itemPtr->selected = !itemPtr->selected;
717 else
718 itemPtr->selected = 1;
720 if (lPtr->view->flags.mapped) {
721 paintItem(lPtr, row);
723 if ((row-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight
724 > lPtr->view->size.height-2)
725 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
726 lPtr->view->size.width, lPtr->view->size.height,
727 WRSunken);
729 lPtr->selectedItem = row;
730 if (notify)
731 WMPostNotificationName(WMListSelectionDidChangeNotification, lPtr,
732 (void*)((int)lPtr->selectedItem));
736 static int
737 getItemIndexAt(List *lPtr, int clickY)
739 int index;
741 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
743 if (index < 0 || index >= lPtr->itemCount)
744 return -1;
746 return index;
750 static void
751 handleActionEvents(XEvent *event, void *data)
753 List *lPtr = (List*)data;
754 int tmp;
755 int topItem = lPtr->topItem;
757 CHECK_CLASS(data, WC_List);
759 switch (event->type) {
760 case ButtonRelease:
761 lPtr->flags.buttonPressed = 0;
762 tmp = getItemIndexAt(lPtr, event->xbutton.y);
764 if (tmp == lPtr->selectedItem && tmp >= 0) {
765 if (lPtr->action)
766 (*lPtr->action)(lPtr, lPtr->clientData);
768 break;
770 case EnterNotify:
771 lPtr->flags.buttonPressed = lPtr->flags.buttonWasPressed;
772 lPtr->flags.buttonWasPressed = 0;
773 break;
775 case LeaveNotify:
776 lPtr->flags.buttonWasPressed = lPtr->flags.buttonPressed;
777 lPtr->flags.buttonPressed = 0;
778 break;
780 case ButtonPress:
781 if (event->xbutton.x > WMWidgetWidth(lPtr->vScroller)) {
782 tmp = getItemIndexAt(lPtr, event->xbutton.y);
783 lPtr->flags.buttonPressed = 1;
785 if (tmp >= 0) {
786 if (tmp == lPtr->selectedItem && WMIsDoubleClick(event)) {
787 WMSelectListItem(lPtr, tmp);
788 if (lPtr->doubleAction)
789 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
790 } else {
791 WMSelectListItem(lPtr, tmp);
795 break;
797 case MotionNotify:
798 if (lPtr->flags.buttonPressed) {
799 tmp = getItemIndexAt(lPtr, event->xmotion.y);
800 if (tmp>=0 && tmp != lPtr->selectedItem) {
801 WMSelectListItem(lPtr, tmp);
804 break;
806 if (lPtr->topItem != topItem)
807 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
811 static void
812 updateGeometry(WMList *lPtr)
814 lPtr->fullFitLines = (lPtr->view->size.height - 4) / lPtr->itemHeight;
815 if (lPtr->fullFitLines * lPtr->itemHeight < lPtr->view->size.height - 4) {
816 lPtr->flags.dontFitAll = 1;
817 } else {
818 lPtr->flags.dontFitAll = 0;
821 if (lPtr->itemCount - lPtr->topItem <= lPtr->fullFitLines) {
822 lPtr->topItem = lPtr->itemCount - lPtr->fullFitLines;
823 if (lPtr->topItem < 0)
824 lPtr->topItem = 0;
827 updateScroller(lPtr);
831 static void
832 resizeList(WMList *lPtr, unsigned int width, unsigned int height)
834 W_ResizeView(lPtr->view, width, height);
836 WMResizeWidget(lPtr->vScroller, 1, height-2);
838 updateGeometry(lPtr);
842 static void
843 destroyList(List *lPtr)
845 WMListItem *itemPtr;
847 if (lPtr->idleID)
848 WMDeleteIdleHandler(lPtr->idleID);
849 lPtr->idleID = NULL;
851 while (lPtr->items!=NULL) {
852 itemPtr = lPtr->items;
853 if (itemPtr->text)
854 free(itemPtr->text);
856 lPtr->items = itemPtr->nextPtr;
857 free(itemPtr);
860 free(lPtr);