Code update for Window Maker version 0.50.0
[wmaker-crm.git] / WINGs / wlist.c
blobc592519826337d51ffc3aaf2833ddf56c0d3330b
5 #include "WINGsP.h"
7 char *WMListDidScrollNotification = "WMListDidScrollNotification";
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 resizeList();
71 W_ViewProcedureTable _ListViewProcedures = {
72 NULL,
73 resizeList,
74 NULL
79 WMList*
80 WMCreateList(WMWidget *parent)
82 List *lPtr;
83 W_Screen *scrPtr = W_VIEW(parent)->screen;
85 lPtr = wmalloc(sizeof(List));
86 memset(lPtr, 0, sizeof(List));
88 lPtr->widgetClass = WC_List;
90 lPtr->view = W_CreateView(W_VIEW(parent));
91 if (!lPtr->view) {
92 free(lPtr);
93 return NULL;
95 lPtr->view->self = lPtr;
97 WMCreateEventHandler(lPtr->view, ExposureMask|StructureNotifyMask
98 |ClientMessageMask, handleEvents, lPtr);
100 WMCreateEventHandler(lPtr->view, ButtonPressMask|ButtonReleaseMask
101 |EnterWindowMask|LeaveWindowMask|ButtonMotionMask,
102 handleActionEvents, lPtr);
104 lPtr->itemHeight = WMFontHeight(scrPtr->normalFont) + 1;
106 /* create the vertical scroller */
107 lPtr->vScroller = WMCreateScroller(lPtr);
108 WMMoveWidget(lPtr->vScroller, 1, 1);
109 WMSetScrollerArrowsPosition(lPtr->vScroller, WSAMaxEnd);
111 WMSetScrollerAction(lPtr->vScroller, vScrollCallBack, lPtr);
113 /* make the scroller map itself when it's realized */
114 WMMapWidget(lPtr->vScroller);
116 resizeList(lPtr, DEFAULT_WIDTH, DEFAULT_HEIGHT);
118 lPtr->selectedItem = -1;
120 return lPtr;
125 WMListItem*
126 WMAddSortedListItem(WMList *lPtr, char *text)
128 WMListItem *item;
129 WMListItem *tmp;
130 int index;
132 item = wmalloc(sizeof(WMListItem));
133 memset(item, 0, sizeof(WMListItem));
134 item->text = wstrdup(text);
135 if (!lPtr->items) {
136 lPtr->items = item;
137 index = 0;
138 } else if (strcmp(lPtr->items->text, text) > 0) {
139 item->nextPtr = lPtr->items;
140 lPtr->items = item;
141 index = 1;
142 } else {
143 int added = 0;
145 index = 0;
146 tmp = lPtr->items;
147 while (tmp->nextPtr) {
148 if (strcmp(tmp->nextPtr->text, text) >= 0) {
149 item->nextPtr = tmp->nextPtr;
150 tmp->nextPtr = item;
151 added = 1;
152 break;
154 index++;
155 tmp = tmp->nextPtr;
157 if (!added) {
158 tmp->nextPtr = item;
162 lPtr->itemCount++;
164 if (lPtr->selectedItem >= index)
165 lPtr->selectedItem++;
167 /* update the scroller when idle, so that we don't waste time
168 * updating it when another item is going to be added later */
169 if (!lPtr->idleID) {
170 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
173 return item;
178 WMListItem*
179 WMInsertListItem(WMList *lPtr, int row, char *text)
181 WMListItem *item;
182 WMListItem *tmp = lPtr->items;
184 CHECK_CLASS(lPtr, WC_List);
186 item = wmalloc(sizeof(WMListItem));
187 memset(item, 0, sizeof(WMListItem));
188 item->text = wstrdup(text);
190 if (lPtr->selectedItem >= row && lPtr->selectedItem >= 0)
191 lPtr->selectedItem++;
193 if (lPtr->items==NULL) {
194 lPtr->items = item;
195 } else if (row == 0) {
196 item->nextPtr = lPtr->items;
197 lPtr->items = item;
198 } else if (row < 0) {
199 while (tmp->nextPtr)
200 tmp = tmp->nextPtr;
202 tmp->nextPtr = item;
203 row = lPtr->itemCount;
204 } else {
205 while (--row > 0)
206 tmp = tmp->nextPtr;
208 item->nextPtr = tmp->nextPtr;
209 tmp->nextPtr = item;
212 lPtr->itemCount++;
214 /* update the scroller when idle, so that we don't waste time
215 * updating it when another item is going to be added later */
216 if (!lPtr->idleID) {
217 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
220 return item;
224 void
225 WMRemoveListItem(WMList *lPtr, int row)
227 WMListItem *llist;
228 WMListItem *tmp;
229 int topItem = lPtr->topItem;
231 CHECK_CLASS(lPtr, WC_List);
233 if (row < 0 || row >= lPtr->itemCount)
234 return;
236 if (lPtr->selectedItem == row)
237 lPtr->selectedItem = -1;
238 else if (lPtr->selectedItem > row)
239 lPtr->selectedItem--;
241 if (row <= lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll)
242 lPtr->topItem--;
243 if (lPtr->topItem < 0)
244 lPtr->topItem = 0;
246 if (row == 0) {
247 if (lPtr->items->text)
248 free(lPtr->items->text);
250 tmp = lPtr->items->nextPtr;
251 free(lPtr->items);
253 lPtr->items = tmp;
254 } else {
255 llist = lPtr->items;
256 while (--row > 0)
257 llist = llist->nextPtr;
258 tmp = llist->nextPtr;
259 llist->nextPtr = llist->nextPtr->nextPtr;
261 if (tmp->text)
262 free(tmp->text);
264 free(tmp);
267 lPtr->itemCount--;
269 if (!lPtr->idleID) {
270 lPtr->idleID = WMAddIdleHandler((WMCallback*)updateScroller, lPtr);
272 if (lPtr->topItem != topItem)
273 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
278 WMListItem*
279 WMGetListItem(WMList *lPtr, int row)
281 WMListItem *listPtr;
283 listPtr = lPtr->items;
285 while (row-- > 0)
286 listPtr = listPtr->nextPtr;
288 return listPtr;
293 void
294 WMSetListUserDrawProc(WMList *lPtr, WMListDrawProc *proc)
296 lPtr->flags.userDrawn = 1;
297 lPtr->draw = proc;
301 void
302 WMSetListUserDrawItemHeight(WMList *lPtr, unsigned short height)
304 assert(height > 0);
306 lPtr->flags.userItemHeight = 1;
307 lPtr->itemHeight = height;
311 void
312 WMClearList(WMList *lPtr)
314 WMListItem *item, *tmp;
316 item = lPtr->items;
317 while (item) {
318 free(item->text);
319 tmp = item->nextPtr;
320 free(item);
321 item = tmp;
323 lPtr->items = NULL;
324 lPtr->itemCount = 0;
325 lPtr->topItem = 0;
326 lPtr->selectedItem = -1;
328 if (!lPtr->idleID) {
329 WMDeleteIdleHandler(lPtr->idleID);
330 lPtr->idleID = NULL;
332 if (lPtr->view->flags.realized) {
333 updateScroller(lPtr);
338 void
339 WMSetListAction(WMList *lPtr, WMAction *action, void *clientData)
341 lPtr->action = action;
342 lPtr->clientData = clientData;
346 void
347 WMSetListDoubleAction(WMList *lPtr, WMAction *action, void *clientData)
349 lPtr->doubleAction = action;
350 lPtr->doubleClientData = clientData;
354 WMListItem*
355 WMGetListSelectedItem(WMList *lPtr)
357 int i = lPtr->selectedItem;
358 WMListItem *item;
360 if (lPtr->selectedItem < 0)
361 return NULL;
363 item = lPtr->items;
364 while (i-- > 0) {
365 item = item->nextPtr;
367 return item;
372 WMGetListSelectedItemRow(WMList *lPtr)
374 return lPtr->selectedItem;
379 WMGetListItemHeight(WMList *lPtr)
381 return lPtr->itemHeight;
385 void
386 WMSetListPosition(WMList *lPtr, int row)
388 lPtr->topItem = row;
389 if (lPtr->topItem + lPtr->fullFitLines > lPtr->itemCount)
390 lPtr->topItem = lPtr->itemCount - lPtr->fullFitLines;
392 if (lPtr->topItem < 0)
393 lPtr->topItem = 0;
395 if (lPtr->view->flags.realized)
396 updateScroller(lPtr);
400 void
401 WMSetListBottomPosition(WMList *lPtr, int row)
403 if (lPtr->itemCount > lPtr->fullFitLines) {
404 lPtr->topItem = row - lPtr->fullFitLines;
405 if (lPtr->topItem < 0)
406 lPtr->topItem = 0;
407 if (lPtr->view->flags.realized)
408 updateScroller(lPtr);
414 WMGetListNumberOfRows(WMList *lPtr)
416 return lPtr->itemCount;
420 WMGetListPosition(WMList *lPtr)
422 return lPtr->topItem;
426 static void
427 vScrollCallBack(WMWidget *scroller, void *self)
429 WMList *lPtr = (WMList*)self;
430 WMScroller *sPtr = (WMScroller*)scroller;
431 int height;
432 int topItem = lPtr->topItem;
434 height = lPtr->view->size.height - 4;
436 switch (WMGetScrollerHitPart(sPtr)) {
437 case WSDecrementLine:
438 if (lPtr->topItem > 0) {
439 lPtr->topItem--;
441 updateScroller(lPtr);
443 break;
445 case WSDecrementPage:
446 if (lPtr->topItem > 0) {
447 lPtr->topItem -= lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
448 if (lPtr->topItem < 0)
449 lPtr->topItem = 0;
451 updateScroller(lPtr);
453 break;
456 case WSIncrementLine:
457 if (lPtr->topItem + lPtr->fullFitLines < lPtr->itemCount) {
458 lPtr->topItem++;
460 updateScroller(lPtr);
462 break;
464 case WSIncrementPage:
465 if (lPtr->topItem + lPtr->fullFitLines < lPtr->itemCount) {
466 lPtr->topItem += lPtr->fullFitLines-(1-lPtr->flags.dontFitAll)-1;
468 if (lPtr->topItem + lPtr->fullFitLines > lPtr->itemCount)
469 lPtr->topItem = lPtr->itemCount - lPtr->fullFitLines;
471 updateScroller(lPtr);
473 break;
475 case WSKnob:
477 int oldTopItem = lPtr->topItem;
479 lPtr->topItem = WMGetScrollerValue(lPtr->vScroller) *
480 (float)(lPtr->itemCount - lPtr->fullFitLines);
482 if (oldTopItem != lPtr->topItem)
483 paintList(lPtr);
485 break;
487 case WSKnobSlot:
488 case WSNoPart:
489 /* do nothing */
490 break;
493 if (lPtr->topItem != topItem)
494 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
498 static void
499 paintItem(List *lPtr, int index)
501 WMView *view = lPtr->view;
502 W_Screen *scr = view->screen;
503 int width, height, x, y;
504 WMListItem *itemPtr;
505 int i;
507 i = index;
508 itemPtr = lPtr->items;
509 while (i-- > 0)
510 itemPtr = itemPtr->nextPtr;
512 width = lPtr->view->size.width - 2 - 19;
513 height = lPtr->itemHeight;
514 x = 19;
515 y = 2 + (index-lPtr->topItem) * lPtr->itemHeight + 1;
517 if (lPtr->flags.userDrawn) {
518 WMRect rect;
519 int flags;
521 rect.size.width = width;
522 rect.size.height = height;
523 rect.pos.x = x;
524 rect.pos.y = y;
526 flags = itemPtr->uflags;
527 if (itemPtr->disabled)
528 flags |= WLDSDisabled;
529 if (itemPtr->selected)
530 flags |= WLDSSelected;
531 if (itemPtr->isBranch)
532 flags |= WLDSIsBranch;
534 if (lPtr->draw)
535 (*lPtr->draw)(lPtr, index, view->window, itemPtr->text, flags,
536 &rect);
537 } else {
538 if (itemPtr->selected)
539 XFillRectangle(scr->display, view->window, W_GC(scr->white), x, y,
540 width, height);
541 else
542 XClearArea(scr->display, view->window, x, y, width, height, False);
544 W_PaintText(view, view->window, scr->normalFont, x+4, y, width,
545 WALeft, W_GC(scr->black), False,
546 itemPtr->text, strlen(itemPtr->text));
552 static void
553 paintList(List *lPtr)
555 W_Screen *scrPtr = lPtr->view->screen;
556 int i, lim;
558 if (!lPtr->view->flags.mapped)
559 return;
561 if (lPtr->itemCount>0) {
562 if (lPtr->topItem+lPtr->fullFitLines+lPtr->flags.dontFitAll > lPtr->itemCount) {
563 lim = lPtr->itemCount - lPtr->topItem;
564 XClearArea(scrPtr->display, lPtr->view->window, 19,
565 2+lim*lPtr->itemHeight, lPtr->view->size.width-21,
566 lPtr->view->size.height-lim*lPtr->itemHeight-3, False);
567 } else {
568 lim = lPtr->fullFitLines + lPtr->flags.dontFitAll;
570 for (i = lPtr->topItem; i < lPtr->topItem + lim; i++) {
571 paintItem(lPtr, i);
573 } else {
574 XClearWindow(scrPtr->display, lPtr->view->window);
576 W_DrawRelief(scrPtr, lPtr->view->window, 0, 0, lPtr->view->size.width,
577 lPtr->view->size.height, WRSunken);
580 #if 0
581 static void
582 scrollTo(List *lPtr, int newTop)
586 #endif
588 static void
589 updateScroller(List *lPtr)
591 float knobProportion, floatValue, tmp;
593 if (lPtr->idleID)
594 WMDeleteIdleHandler(lPtr->idleID);
595 lPtr->idleID = NULL;
597 paintList(lPtr);
599 if (lPtr->itemCount == 0 || lPtr->itemCount <= lPtr->fullFitLines)
600 WMSetScrollerParameters(lPtr->vScroller, 0, 1);
601 else {
602 tmp = lPtr->fullFitLines;
603 knobProportion = tmp/(float)lPtr->itemCount;
605 floatValue = (float)lPtr->topItem/(float)(lPtr->itemCount - lPtr->fullFitLines);
607 WMSetScrollerParameters(lPtr->vScroller, floatValue, knobProportion);
612 static void
613 handleEvents(XEvent *event, void *data)
615 List *lPtr = (List*)data;
617 CHECK_CLASS(data, WC_List);
620 switch (event->type) {
621 case Expose:
622 if (event->xexpose.count!=0)
623 break;
624 paintList(lPtr);
625 break;
627 case DestroyNotify:
628 destroyList(lPtr);
629 break;
636 WMFindRowOfListItemWithTitle(WMList *lPtr, char *title)
638 WMListItem *item;
639 int i;
640 int ok = 0;
642 for (i=0, item=lPtr->items; item!=NULL; item=item->nextPtr, i++) {
643 if (strcmp(item->text, title)==0) {
644 ok = 1;
645 break;
649 return ok ? i : -1;
653 void
654 WMSelectListItem(WMList *lPtr, int row)
656 WMListItem *itemPtr;
657 int i;
659 if (row >= lPtr->itemCount)
660 return;
662 if (!lPtr->flags.allowMultipleSelection) {
663 /* unselect previous selected item */
664 if (lPtr->selectedItem >= 0) {
665 itemPtr = lPtr->items;
666 for (i=0; i<lPtr->selectedItem; i++)
667 itemPtr = itemPtr->nextPtr;
669 if (itemPtr->selected) {
670 itemPtr->selected = 0;
671 if (lPtr->view->flags.mapped && i>=lPtr->topItem
672 && i<=lPtr->topItem+lPtr->fullFitLines)
673 paintItem(lPtr, i);
678 if (row < 0) {
679 if (!lPtr->flags.allowMultipleSelection)
680 lPtr->selectedItem = -1;
681 return;
684 /* select item */
685 itemPtr = lPtr->items;
686 for (i=0; i<row; i++)
687 itemPtr = itemPtr->nextPtr;
688 if (lPtr->flags.allowMultipleSelection)
689 itemPtr->selected = !itemPtr->selected;
690 else
691 itemPtr->selected = 1;
693 if (lPtr->view->flags.mapped) {
694 paintItem(lPtr, row);
696 if ((row-lPtr->topItem+lPtr->fullFitLines)*lPtr->itemHeight
697 > lPtr->view->size.height-2)
698 W_DrawRelief(lPtr->view->screen, lPtr->view->window, 0, 0,
699 lPtr->view->size.width, lPtr->view->size.height,
700 WRSunken);
702 lPtr->selectedItem = row;
706 static int
707 getItemIndexAt(List *lPtr, int clickY)
709 int index;
711 index = (clickY - 2) / lPtr->itemHeight + lPtr->topItem;
713 if (index < 0 || index >= lPtr->itemCount)
714 return -1;
716 return index;
720 static void
721 handleActionEvents(XEvent *event, void *data)
723 List *lPtr = (List*)data;
724 int tmp;
725 int topItem = lPtr->topItem;
727 CHECK_CLASS(data, WC_List);
729 switch (event->type) {
730 case ButtonRelease:
731 lPtr->flags.buttonPressed = 0;
732 tmp = getItemIndexAt(lPtr, event->xbutton.y);
734 if (tmp == lPtr->selectedItem && tmp >= 0) {
736 if (lPtr->action)
737 (*lPtr->action)(lPtr, lPtr->clientData);
739 break;
741 case EnterNotify:
742 lPtr->flags.buttonPressed = lPtr->flags.buttonWasPressed;
743 lPtr->flags.buttonWasPressed = 0;
744 break;
746 case LeaveNotify:
747 lPtr->flags.buttonWasPressed = lPtr->flags.buttonPressed;
748 lPtr->flags.buttonPressed = 0;
749 break;
751 case ButtonPress:
752 if (event->xbutton.x > WMWidgetWidth(lPtr->vScroller)) {
753 tmp = getItemIndexAt(lPtr, event->xbutton.y);
755 if (tmp>=0) {
756 WMSelectListItem(lPtr, tmp);
757 lPtr->selectedItem = tmp;
759 lPtr->flags.buttonPressed = 1;
761 if (WMIsDoubleClick(event)) {
762 if (lPtr->doubleAction)
763 (*lPtr->doubleAction)(lPtr, lPtr->doubleClientData);
766 break;
768 case MotionNotify:
769 if (lPtr->flags.buttonPressed) {
770 tmp = getItemIndexAt(lPtr, event->xmotion.y);
771 if (tmp>=0 && tmp != lPtr->selectedItem) {
772 WMSelectListItem(lPtr, tmp);
773 lPtr->selectedItem = tmp;
776 break;
778 if (lPtr->topItem != topItem)
779 WMPostNotificationName(WMListDidScrollNotification, lPtr, NULL);
783 static void
784 resizeList(WMList *lPtr, unsigned int width, unsigned int height)
786 W_ResizeView(lPtr->view, width, height);
788 WMResizeWidget(lPtr->vScroller, 1, height-2);
790 lPtr->fullFitLines = (height - 4) / lPtr->itemHeight;
791 if (lPtr->fullFitLines * lPtr->itemHeight < height-4) {
792 lPtr->flags.dontFitAll = 1;
793 } else {
794 lPtr->flags.dontFitAll = 0;
799 static void
800 destroyList(List *lPtr)
802 WMListItem *itemPtr;
804 if (lPtr->idleID)
805 WMDeleteIdleHandler(lPtr->idleID);
806 lPtr->idleID = NULL;
808 while (lPtr->items!=NULL) {
809 itemPtr = lPtr->items;
810 if (itemPtr->text)
811 free(itemPtr->text);
813 lPtr->items = itemPtr->nextPtr;
814 free(itemPtr);
817 free(lPtr);