Line data Source code
1 : /****************************************************************************/
2 : // Eclipse SUMO, Simulation of Urban MObility; see https://eclipse.dev/sumo
3 : // Copyright (C) 2006-2025 German Aerospace Center (DLR) and others.
4 : // This program and the accompanying materials are made available under the
5 : // terms of the Eclipse Public License 2.0 which is available at
6 : // https://www.eclipse.org/legal/epl-2.0/
7 : // This Source Code may also be made available under the following Secondary
8 : // Licenses when the conditions for such availability set forth in the Eclipse
9 : // Public License 2.0 are satisfied: GNU General Public License, version 2
10 : // or later which is available at
11 : // https://www.gnu.org/licenses/old-licenses/gpl-2.0-standalone.html
12 : // SPDX-License-Identifier: EPL-2.0 OR GPL-2.0-or-later
13 : /****************************************************************************/
14 : /// @file MFXListIcon.cpp
15 : /// @author Pablo Alvarez Lopez
16 : /// @date Feb 2023
17 : ///
18 : //
19 : /****************************************************************************/
20 :
21 : #include <utils/common/UtilExceptions.h>
22 : #include <fxkeys.h>
23 :
24 : #include "MFXListIconItem.h"
25 : #include "MFXListIcon.h"
26 :
27 : // ===========================================================================
28 : // Macross
29 : // ===========================================================================
30 :
31 : #define LINE_SPACING 4 // Line spacing between items
32 : #define ICON_SIZE 16
33 :
34 : #define LIST_MASK (SELECT_MASK | LIST_AUTOSELECT)
35 :
36 : // ===========================================================================
37 : // FOX callback mapping
38 : // ===========================================================================
39 :
40 : // Map
41 : FXDEFMAP(MFXListIcon) MFXListIconMap[] = {
42 : FXMAPFUNC(SEL_PAINT, 0, MFXListIcon::onPaint),
43 : FXMAPFUNC(SEL_ENTER, 0, MFXListIcon::onEnter),
44 : FXMAPFUNC(SEL_LEAVE, 0, MFXListIcon::onLeave),
45 : FXMAPFUNC(SEL_MOTION, 0, MFXListIcon::onMotion),
46 : FXMAPFUNC(SEL_TIMEOUT, FXWindow::ID_AUTOSCROLL, MFXListIcon::onAutoScroll),
47 : FXMAPFUNC(SEL_TIMEOUT, MFXListIcon::ID_TIPTIMER, MFXListIcon::onTipTimer),
48 : FXMAPFUNC(SEL_TIMEOUT, MFXListIcon::ID_LOOKUPTIMER, MFXListIcon::onLookupTimer),
49 : FXMAPFUNC(SEL_UNGRABBED, 0, MFXListIcon::onUngrabbed),
50 : FXMAPFUNC(SEL_LEFTBUTTONPRESS, 0, MFXListIcon::onLeftBtnPress),
51 : FXMAPFUNC(SEL_LEFTBUTTONRELEASE, 0, MFXListIcon::onLeftBtnRelease),
52 : FXMAPFUNC(SEL_RIGHTBUTTONPRESS, 0, MFXListIcon::onRightBtnPress),
53 : FXMAPFUNC(SEL_RIGHTBUTTONRELEASE, 0, MFXListIcon::onRightBtnRelease),
54 : FXMAPFUNC(SEL_KEYPRESS, 0, MFXListIcon::onKeyPress),
55 : FXMAPFUNC(SEL_KEYRELEASE, 0, MFXListIcon::onKeyRelease),
56 : FXMAPFUNC(SEL_FOCUSIN, 0, MFXListIcon::onFocusIn),
57 : FXMAPFUNC(SEL_FOCUSOUT, 0, MFXListIcon::onFocusOut),
58 : FXMAPFUNC(SEL_CLICKED, 0, MFXListIcon::onClicked),
59 : FXMAPFUNC(SEL_DOUBLECLICKED, 0, MFXListIcon::onDoubleClicked),
60 : FXMAPFUNC(SEL_TRIPLECLICKED, 0, MFXListIcon::onTripleClicked),
61 : FXMAPFUNC(SEL_COMMAND, 0, MFXListIcon::onCommand),
62 : FXMAPFUNC(SEL_QUERY_TIP, 0, MFXListIcon::onQueryTip),
63 : FXMAPFUNC(SEL_QUERY_HELP, 0, MFXListIcon::onQueryHelp),
64 : };
65 :
66 :
67 : // Object implementation
68 1334550 : FXIMPLEMENT(MFXListIcon, FXScrollArea, MFXListIconMap, ARRAYNUMBER(MFXListIconMap))
69 :
70 : // ===========================================================================
71 : // member method definitions
72 : // ===========================================================================
73 :
74 7582 : MFXListIcon::MFXListIcon(FXComposite* p, FXObject* tgt, FXSelector sel, FXuint opts, FXint x, FXint y, FXint w, FXint h):
75 7582 : FXScrollArea(p, opts, x, y, w, h) {
76 7582 : flags |= FLAG_ENABLED;
77 7582 : target = tgt;
78 7582 : message = sel;
79 7582 : font = getApp()->getNormalFont();
80 7582 : textColor = getApp()->getForeColor();
81 7582 : selbackColor = getApp()->getSelbackColor();
82 7582 : seltextColor = getApp()->getSelforeColor();
83 7582 : }
84 :
85 :
86 15112 : MFXListIcon::~MFXListIcon() {
87 7556 : getApp()->removeTimeout(this, ID_TIPTIMER);
88 7556 : getApp()->removeTimeout(this, ID_LOOKUPTIMER);
89 7556 : clearItems(FALSE);
90 7556 : font = (FXFont*) - 1L;
91 15112 : }
92 :
93 :
94 : void
95 30326 : MFXListIcon::create() {
96 30326 : FXScrollArea::create();
97 182098 : for (const auto& item : items) {
98 151772 : item->create();
99 : }
100 30326 : font->create();
101 30326 : }
102 :
103 :
104 : void
105 0 : MFXListIcon::detach() {
106 0 : FXScrollArea::detach();
107 0 : for (const auto& item : items) {
108 0 : item->detach();
109 : }
110 0 : font->detach();
111 0 : }
112 :
113 :
114 : bool
115 0 : MFXListIcon::canFocus() const {
116 0 : return true;
117 : }
118 :
119 :
120 : void
121 0 : MFXListIcon::setFocus() {
122 0 : FXScrollArea::setFocus();
123 0 : setDefault(TRUE);
124 0 : }
125 :
126 :
127 : void
128 0 : MFXListIcon::killFocus() {
129 0 : FXScrollArea::killFocus();
130 0 : setDefault(MAYBE);
131 0 : }
132 :
133 :
134 : FXint
135 45036 : MFXListIcon::getDefaultWidth() {
136 45036 : return FXScrollArea::getDefaultWidth();
137 : }
138 :
139 :
140 : FXint
141 60188 : MFXListIcon::getDefaultHeight() {
142 60188 : if (visible > (int)itemFiltered.size()) {
143 60188 : return (int)itemFiltered.size() * (LINE_SPACING + FXMAX(font->getFontHeight(), ICON_SIZE));
144 : } else {
145 0 : return visible * (LINE_SPACING + FXMAX(font->getFontHeight(), ICON_SIZE));
146 : }
147 : }
148 :
149 :
150 : void
151 60666 : MFXListIcon::recalc() {
152 60666 : FXScrollArea::recalc();
153 60666 : flags |= FLAG_RECALC;
154 60666 : cursor = nullptr;
155 60666 : }
156 :
157 :
158 : void
159 7582 : MFXListIcon::setNumVisible(FXint nvis) {
160 7582 : if (nvis < 0) {
161 : nvis = 0;
162 : }
163 7582 : if (visible != nvis) {
164 7582 : visible = nvis;
165 7582 : recalc();
166 : }
167 7582 : }
168 :
169 :
170 : FXint
171 52618 : MFXListIcon::getContentWidth() {
172 52618 : if (flags & FLAG_RECALC) {
173 7582 : recompute();
174 : }
175 52618 : return listWidth;
176 : }
177 :
178 :
179 : FXint
180 7582 : MFXListIcon::getContentHeight() {
181 7582 : if (flags & FLAG_RECALC) {
182 0 : recompute();
183 : }
184 7582 : return listHeight;
185 : }
186 :
187 :
188 : void
189 7582 : MFXListIcon::layout() {
190 : // Calculate contents
191 7582 : FXScrollArea::layout();
192 : // Determine line size for scroll bars
193 7582 : if (0 < (int)itemFiltered.size()) {
194 7582 : vertical->setLine(itemFiltered[0]->getHeight(this));
195 7582 : horizontal->setLine(itemFiltered[0]->getWidth(this) / 10);
196 : }
197 7582 : update();
198 : // We were supposed to make this item viewable
199 7582 : if (viewable) {
200 7582 : makeItemVisible(viewable);
201 : }
202 : // No more dirty
203 7582 : flags &= ~(FXuint)FLAG_DIRTY;
204 7582 : }
205 :
206 :
207 : FXbool
208 37946 : MFXListIcon::isItemCurrent(FXint index) const {
209 37946 : for (int i = 0; i < (int)items.size(); i++) {
210 37946 : if (items[i] == currentItem) {
211 37946 : return i == index;
212 : }
213 : }
214 : return false;
215 : }
216 :
217 :
218 : FXbool
219 0 : MFXListIcon::isItemVisible(MFXListIconItem* item) const {
220 0 : return (0 < (pos_y + item->y + item->getHeight(this))) && ((pos_y + item->y) < viewport_h);
221 : }
222 :
223 :
224 : void
225 15201 : MFXListIcon::makeItemVisible(MFXListIconItem* item) {
226 : FXint y, h;
227 : // Remember for later
228 15201 : viewable = item;
229 : // Was realized
230 15201 : if (xid) {
231 : // Force layout if dirty
232 7619 : if (flags & FLAG_RECALC) {
233 0 : layout();
234 : }
235 7619 : y = pos_y;
236 7619 : h = item->getHeight(this);
237 7619 : if (viewport_h <= y + item->y + h) {
238 0 : y = viewport_h - item->y - h;
239 : }
240 7619 : if (y + item->y <= 0) {
241 7582 : y = -item->y;
242 : }
243 : // Scroll into view
244 7619 : setPosition(pos_x, y);
245 : // Done it
246 7619 : viewable = nullptr;
247 : }
248 15201 : }
249 :
250 :
251 : void
252 7619 : MFXListIcon::makeItemVisible(FXint index) {
253 7619 : makeItemVisible(items[index]);
254 7619 : }
255 :
256 :
257 : FXint
258 0 : MFXListIcon::getItemWidth(FXint index) const {
259 0 : if ((index < 0) || ((int)itemFiltered.size() <= index)) {
260 0 : fxerror("%s::isItemSelected: index out of range.\n", getClassName());
261 : }
262 0 : return itemFiltered[index]->getWidth(this);
263 : }
264 :
265 :
266 : FXint
267 0 : MFXListIcon::getItemHeight(FXint index) const {
268 0 : if ((index < 0) || ((int)itemFiltered.size() <= index)) {
269 0 : fxerror("%s::isItemSelected: index out of range.\n", getClassName());
270 : }
271 0 : return itemFiltered[index]->getHeight(this);
272 : }
273 :
274 :
275 : MFXListIconItem*
276 0 : MFXListIcon::getItemAt(FXint y) const {
277 0 : y -= pos_y;
278 : // continue depending if we're filtering
279 0 : if (filter.empty()) {
280 0 : for (int i = 0; i < (int)items.size(); i++) {
281 0 : if (items[i]->y <= y && y < items[i]->y + items[i]->getHeight(this)) {
282 0 : return items[i];
283 : }
284 : }
285 : } else {
286 0 : for (int i = 0; i < (int)itemFiltered.size(); i++) {
287 0 : if ((itemFiltered[i]->y <= y) && (y < itemFiltered[i]->y + itemFiltered[i]->getHeight(this))) {
288 0 : return itemFiltered[i];
289 : }
290 : }
291 : }
292 : return nullptr;
293 : }
294 :
295 :
296 : int
297 40 : MFXListIcon::findItem(const FXString& text) const {
298 232 : for (int i = 0; i < (int)items.size(); i++) {
299 229 : if (items[i]->getText().text() == text) {
300 37 : return i;
301 : }
302 : }
303 : return -1;
304 : }
305 :
306 :
307 : FXint
308 0 : MFXListIcon::hitItem(MFXListIconItem* item, FXint x, FXint y) const {
309 : FXint ix, iy, hit = 0;
310 0 : if (item) {
311 0 : x -= pos_x;
312 0 : y -= pos_y;
313 0 : ix = item->x;
314 0 : iy = item->y;
315 0 : hit = item->hitItem(this, x - ix, y - iy);
316 : }
317 0 : return hit;
318 : }
319 :
320 :
321 : void
322 15238 : MFXListIcon::updateItem(MFXListIconItem* item) const {
323 15238 : update(0, pos_y + item->y, viewport_w, item->getHeight(this));
324 15238 : }
325 :
326 :
327 : FXbool
328 0 : MFXListIcon::selectItem(MFXListIconItem* item, FXbool notify) {
329 0 : if (!item->isSelected()) {
330 0 : killSelection(notify);
331 0 : item->setSelected(TRUE);
332 0 : updateItem(item);
333 0 : if (notify && target) {
334 0 : target->tryHandle(this, FXSEL(SEL_SELECTED, message), nullptr);
335 : }
336 0 : return TRUE;
337 : } else {
338 : return FALSE;
339 : }
340 : }
341 :
342 :
343 : FXbool
344 0 : MFXListIcon::deselectItem(MFXListIconItem* item, FXbool notify) {
345 0 : if (item->isSelected()) {
346 0 : item->setSelected(FALSE);
347 0 : updateItem(item);
348 0 : if (notify && target) {
349 0 : target->tryHandle(this, FXSEL(SEL_DESELECTED, message), nullptr);
350 : }
351 0 : return TRUE;
352 : } else {
353 : return FALSE;
354 : }
355 : }
356 :
357 :
358 : FXbool
359 0 : MFXListIcon::toggleItem(MFXListIconItem* item, FXbool notify) {
360 0 : if (!item->isSelected()) {
361 0 : killSelection(notify);
362 0 : item->setSelected(TRUE);
363 0 : updateItem(item);
364 0 : if (notify && target) {
365 0 : target->tryHandle(this, FXSEL(SEL_SELECTED, message), nullptr);
366 : }
367 : } else {
368 0 : item->setSelected(FALSE);
369 0 : updateItem(item);
370 0 : if (notify && target) {
371 0 : target->tryHandle(this, FXSEL(SEL_DESELECTED, message), nullptr);
372 : }
373 : }
374 0 : return TRUE;
375 : }
376 :
377 :
378 : FXbool
379 0 : MFXListIcon::killSelection(FXbool notify) {
380 : FXbool changes = FALSE;
381 : FXint i;
382 0 : for (i = 0; i < (int)items.size(); i++) {
383 0 : if (items[i]->isSelected()) {
384 0 : items[i]->setSelected(FALSE);
385 0 : updateItem(items[i]);
386 : changes = TRUE;
387 0 : if (notify && target) {
388 0 : target->tryHandle(this, FXSEL(SEL_DESELECTED, message), (void*)(FXival)i);
389 : }
390 : }
391 : }
392 0 : return changes;
393 : }
394 :
395 :
396 : long
397 0 : MFXListIcon::onEnter(FXObject* sender, FXSelector sel, void* ptr) {
398 0 : FXScrollArea::onEnter(sender, sel, ptr);
399 0 : getApp()->addTimeout(this, ID_TIPTIMER, getApp()->getMenuPause());
400 0 : cursor = nullptr;
401 0 : return 1;
402 : }
403 :
404 :
405 : long
406 0 : MFXListIcon::onLeave(FXObject* sender, FXSelector sel, void* ptr) {
407 0 : FXScrollArea::onLeave(sender, sel, ptr);
408 0 : getApp()->removeTimeout(this, ID_TIPTIMER);
409 0 : cursor = nullptr;
410 0 : return 1;
411 : }
412 :
413 :
414 : long
415 0 : MFXListIcon::onFocusIn(FXObject* sender, FXSelector sel, void* ptr) {
416 0 : FXScrollArea::onFocusIn(sender, sel, ptr);
417 0 : if (currentItem) {
418 0 : currentItem->setFocus(TRUE);
419 0 : updateItem(currentItem);
420 : }
421 0 : return 1;
422 : }
423 :
424 :
425 : long
426 0 : MFXListIcon::onTipTimer(FXObject*, FXSelector, void*) {
427 0 : flags |= FLAG_TIP;
428 0 : return 1;
429 : }
430 :
431 :
432 : long
433 0 : MFXListIcon::onQueryTip(FXObject* sender, FXSelector sel, void* ptr) {
434 0 : if (FXWindow::onQueryTip(sender, sel, ptr)) {
435 : return 1;
436 : }
437 0 : if (cursor && (flags & FLAG_TIP) && !(options & LIST_AUTOSELECT)) { // No tip when autoselect!
438 0 : FXString string = cursor->getText();
439 0 : sender->handle(this, FXSEL(SEL_COMMAND, ID_SETSTRINGVALUE), (void*) & string);
440 : return 1;
441 0 : }
442 : return 0;
443 : }
444 :
445 :
446 : long
447 0 : MFXListIcon::onQueryHelp(FXObject* sender, FXSelector sel, void* ptr) {
448 0 : if (FXWindow::onQueryHelp(sender, sel, ptr)) {
449 : return 1;
450 : }
451 0 : if ((flags & FLAG_HELP) && !help.empty()) {
452 0 : sender->handle(this, FXSEL(SEL_COMMAND, ID_SETSTRINGVALUE), (void*) & help);
453 0 : return 1;
454 : }
455 : return 0;
456 : }
457 :
458 :
459 : long
460 0 : MFXListIcon::onFocusOut(FXObject* sender, FXSelector sel, void* ptr) {
461 0 : FXScrollArea::onFocusOut(sender, sel, ptr);
462 0 : if (currentItem) {
463 0 : currentItem->setFocus(FALSE);
464 0 : updateItem(currentItem);
465 : }
466 0 : return 1;
467 : }
468 :
469 :
470 : long
471 7541 : MFXListIcon::onPaint(FXObject*, FXSelector, void* ptr) {
472 : FXEvent* event = (FXEvent*)ptr;
473 7541 : FXDCWindow dc(this, event);
474 : FXint y, h;
475 : // Paint items
476 7541 : y = pos_y;
477 45281 : for (int i = 0; i < (int)itemFiltered.size(); i++) {
478 37740 : h = itemFiltered[i]->getHeight(this);
479 37740 : if (event->rect.y <= (y + h) && y < (event->rect.y + event->rect.h)) {
480 37740 : itemFiltered[i]->draw(this, dc, pos_x, y, FXMAX(listWidth, viewport_w), h);
481 : }
482 : y += h;
483 : }
484 : // Paint blank area below items
485 7541 : if (y < (event->rect.y + event->rect.h)) {
486 7541 : dc.setForeground(backColor);
487 7541 : dc.fillRectangle(event->rect.x, y, event->rect.w, event->rect.y + event->rect.h - y);
488 : }
489 7541 : return 1;
490 7541 : }
491 :
492 :
493 : long
494 0 : MFXListIcon::onLookupTimer(FXObject*, FXSelector, void*) {
495 0 : lookup = FXString::null;
496 0 : return 1;
497 : }
498 :
499 :
500 : long
501 0 : MFXListIcon::onKeyPress(FXObject*, FXSelector, void* ptr) {
502 : FXEvent* event = (FXEvent*)ptr;
503 0 : FXint index = getCurrentItemIndex();
504 0 : flags &= ~FLAG_TIP;
505 0 : if (!isEnabled()) {
506 : return 0;
507 : }
508 0 : if (target && target->tryHandle(this, FXSEL(SEL_KEYPRESS, message), ptr)) {
509 : return 1;
510 : }
511 0 : switch (event->code) {
512 0 : case KEY_Control_L:
513 : case KEY_Control_R:
514 : case KEY_Shift_L:
515 : case KEY_Shift_R:
516 : case KEY_Alt_L:
517 : case KEY_Alt_R:
518 0 : if (flags & FLAG_DODRAG) {
519 0 : handle(this, FXSEL(SEL_DRAGGED, 0), ptr);
520 : }
521 : return 1;
522 0 : case KEY_Page_Up:
523 : case KEY_KP_Page_Up:
524 0 : lookup = FXString::null;
525 0 : setPosition(pos_x, pos_y + verticalScrollBar()->getPage());
526 0 : return 1;
527 0 : case KEY_Page_Down:
528 : case KEY_KP_Page_Down:
529 0 : lookup = FXString::null;
530 0 : setPosition(pos_x, pos_y - verticalScrollBar()->getPage());
531 0 : return 1;
532 0 : case KEY_Up:
533 : case KEY_KP_Up:
534 0 : index -= 1;
535 0 : goto hop;
536 0 : case KEY_Down:
537 : case KEY_KP_Down:
538 0 : index += 1;
539 0 : goto hop;
540 0 : case KEY_Home:
541 : case KEY_KP_Home:
542 : index = 0;
543 0 : goto hop;
544 0 : case KEY_End:
545 : case KEY_KP_End:
546 0 : index = (int)itemFiltered.size() - 1;
547 0 : hop:
548 0 : lookup = FXString::null;
549 : // continue depending of filter
550 0 : if (filter.empty()) {
551 0 : if (0 <= index && index < (int)items.size()) {
552 0 : setCurrentItem(items[index], TRUE);
553 0 : makeItemVisible(items[index]);
554 : }
555 : } else {
556 0 : if ((0 <= index) && (index < (int)itemFiltered.size())) {
557 0 : setCurrentItem(itemFiltered[index], TRUE);
558 0 : makeItemVisible(itemFiltered[index]);
559 : }
560 : }
561 0 : handle(this, FXSEL(SEL_CLICKED, 0), (void*)currentItem);
562 0 : if (currentItem && currentItem->isEnabled()) {
563 0 : handle(this, FXSEL(SEL_COMMAND, 0), (void*)currentItem);
564 : }
565 : return 1;
566 0 : case KEY_space:
567 : case KEY_KP_Space:
568 0 : lookup = FXString::null;
569 0 : if (currentItem && currentItem->isEnabled()) {
570 0 : toggleItem(currentItem, TRUE);
571 0 : setAnchorItem(currentItem);
572 : }
573 0 : handle(this, FXSEL(SEL_CLICKED, 0), (void*)currentItem);
574 0 : if (currentItem && currentItem->isEnabled()) {
575 0 : handle(this, FXSEL(SEL_COMMAND, 0), (void*)currentItem);
576 : }
577 : return 1;
578 0 : case KEY_Return:
579 : case KEY_KP_Enter:
580 0 : lookup = FXString::null;
581 0 : handle(this, FXSEL(SEL_DOUBLECLICKED, 0), (void*)currentItem);
582 0 : if (currentItem && currentItem->isEnabled()) {
583 0 : handle(this, FXSEL(SEL_COMMAND, 0), (void*)currentItem);
584 : }
585 : return 1;
586 : default:
587 : return 1;
588 : }
589 : }
590 :
591 :
592 : long
593 0 : MFXListIcon::onKeyRelease(FXObject*, FXSelector, void* ptr) {
594 : FXEvent* event = (FXEvent*)ptr;
595 0 : if (!isEnabled()) {
596 : return 0;
597 : }
598 0 : if (target && target->tryHandle(this, FXSEL(SEL_KEYRELEASE, message), ptr)) {
599 : return 1;
600 : }
601 0 : switch (event->code) {
602 0 : case KEY_Shift_L:
603 : case KEY_Shift_R:
604 : case KEY_Control_L:
605 : case KEY_Control_R:
606 : case KEY_Alt_L:
607 : case KEY_Alt_R:
608 0 : if (flags & FLAG_DODRAG) {
609 0 : handle(this, FXSEL(SEL_DRAGGED, 0), ptr);
610 : }
611 : return 1;
612 : }
613 : return 0;
614 : }
615 :
616 :
617 : long
618 0 : MFXListIcon::onAutoScroll(FXObject*, FXSelector, void*) {
619 0 : return 1;
620 : }
621 :
622 :
623 : long
624 0 : MFXListIcon::onMotion(FXObject*, FXSelector, void* ptr) {
625 : FXEvent* event = (FXEvent*)ptr;
626 0 : MFXListIconItem* oldcursor = cursor;
627 0 : FXuint flg = flags;
628 :
629 : // Kill the tip
630 0 : flags &= ~FLAG_TIP;
631 :
632 : // Kill the tip timer
633 0 : getApp()->removeTimeout(this, ID_TIPTIMER);
634 :
635 : // Right mouse scrolling
636 0 : if (flags & FLAG_SCROLLING) {
637 0 : setPosition(event->win_x - grabx, event->win_y - graby);
638 0 : return 1;
639 : }
640 :
641 : // Drag and drop mode
642 0 : if (flags & FLAG_DODRAG) {
643 0 : if (startAutoScroll(event, TRUE)) {
644 : return 1;
645 : }
646 0 : handle(this, FXSEL(SEL_DRAGGED, 0), ptr);
647 0 : return 1;
648 : }
649 :
650 : // Tentative drag and drop
651 0 : if ((flags & FLAG_TRYDRAG) && event->moved) {
652 0 : flags &= ~FLAG_TRYDRAG;
653 0 : if (handle(this, FXSEL(SEL_BEGINDRAG, 0), ptr)) {
654 0 : flags |= FLAG_DODRAG;
655 : }
656 0 : return 1;
657 : }
658 :
659 : // Normal operation
660 0 : if ((flags & FLAG_PRESSED) || (options & LIST_AUTOSELECT)) {
661 : // Start auto scrolling?
662 0 : if (startAutoScroll(event, FALSE)) {
663 : return 1;
664 : }
665 : // Find item
666 0 : auto element = getItemAt(event->win_y);
667 : // Got an item different from before
668 0 : if (element) {
669 : // Make it the current item
670 0 : setCurrentItem(element, TRUE);
671 0 : return 1;
672 : }
673 : }
674 :
675 : // Reset tip timer if nothing's going on
676 0 : getApp()->addTimeout(this, ID_TIPTIMER, getApp()->getMenuPause());
677 :
678 : // Get item we're over
679 0 : cursor = getItemAt(event->win_y);
680 :
681 : // Force GUI update only when needed
682 0 : return (cursor != oldcursor) || (flg & FLAG_TIP);
683 : }
684 :
685 :
686 : long
687 0 : MFXListIcon::onLeftBtnPress(FXObject*, FXSelector, void* ptr) {
688 : FXEvent* event = (FXEvent*)ptr;
689 : FXint code;
690 0 : flags &= ~FLAG_TIP;
691 0 : handle(this, FXSEL(SEL_FOCUS_SELF, 0), ptr);
692 0 : if (isEnabled()) {
693 0 : grab();
694 0 : flags &= ~FLAG_UPDATE;
695 : // First change callback
696 0 : if (target && target->tryHandle(this, FXSEL(SEL_LEFTBUTTONPRESS, message), ptr)) {
697 : return 1;
698 : }
699 : // Autoselect mode
700 0 : if (options & LIST_AUTOSELECT) {
701 : return 1;
702 : }
703 : // Locate item
704 0 : auto item = getItemAt(event->win_y);
705 : // No item
706 0 : if (item == nullptr) {
707 : return 1;
708 : }
709 : // Find out where hit
710 0 : code = hitItem(item, event->win_x, event->win_y);
711 : // Change current item
712 0 : setCurrentItem(item, TRUE);
713 : // Change item selection
714 0 : state = item->isSelected();
715 0 : if (item->isEnabled() && !state) {
716 0 : selectItem(item, TRUE);
717 : }
718 : // Start drag if actually pressed text or icon only
719 0 : if (code && item->isSelected() && item->isDraggable()) {
720 0 : flags |= FLAG_TRYDRAG;
721 : }
722 0 : flags |= FLAG_PRESSED;
723 0 : return 1;
724 : }
725 : return 0;
726 : }
727 :
728 :
729 : long
730 0 : MFXListIcon::onLeftBtnRelease(FXObject*, FXSelector, void* ptr) {
731 : FXEvent* event = (FXEvent*)ptr;
732 0 : FXuint flg = flags;
733 0 : if (isEnabled()) {
734 0 : ungrab();
735 0 : stopAutoScroll();
736 0 : flags |= FLAG_UPDATE;
737 0 : flags &= ~(FLAG_PRESSED | FLAG_TRYDRAG | FLAG_DODRAG);
738 : // First chance callback
739 0 : if (target && target->tryHandle(this, FXSEL(SEL_LEFTBUTTONRELEASE, message), ptr)) {
740 : return 1;
741 : }
742 : // No activity
743 0 : if (!(flg & FLAG_PRESSED) && !(options & LIST_AUTOSELECT)) {
744 : return 1;
745 : }
746 : // Was dragging
747 0 : if (flg & FLAG_DODRAG) {
748 0 : handle(this, FXSEL(SEL_ENDDRAG, 0), ptr);
749 0 : return 1;
750 : }
751 0 : if (currentItem && currentItem->isEnabled()) {
752 0 : if (state) {
753 0 : deselectItem(currentItem, TRUE);
754 : }
755 : }
756 : // Scroll to make item visibke
757 0 : makeItemVisible(currentItem);
758 : // Update anchor
759 0 : setAnchorItem(currentItem);
760 : // Generate clicked callbacks
761 0 : if (event->click_count == 1) {
762 0 : handle(this, FXSEL(SEL_CLICKED, 0), (void*)currentItem);
763 0 : } else if (event->click_count == 2) {
764 0 : handle(this, FXSEL(SEL_DOUBLECLICKED, 0), (void*)currentItem);
765 0 : } else if (event->click_count == 3) {
766 0 : handle(this, FXSEL(SEL_TRIPLECLICKED, 0), (void*)currentItem);
767 : }
768 : // Command callback only when clicked on item
769 0 : if (currentItem && currentItem->isEnabled()) {
770 0 : handle(this, FXSEL(SEL_COMMAND, 0), (void*)currentItem);
771 : }
772 0 : return 1;
773 : }
774 : return 0;
775 : }
776 :
777 :
778 : long
779 0 : MFXListIcon::onRightBtnPress(FXObject*, FXSelector, void* ptr) {
780 : FXEvent* event = (FXEvent*)ptr;
781 0 : flags &= ~FLAG_TIP;
782 0 : handle(this, FXSEL(SEL_FOCUS_SELF, 0), ptr);
783 0 : if (isEnabled()) {
784 0 : grab();
785 0 : flags &= ~FLAG_UPDATE;
786 0 : if (target && target->tryHandle(this, FXSEL(SEL_RIGHTBUTTONPRESS, message), ptr)) {
787 : return 1;
788 : }
789 0 : flags |= FLAG_SCROLLING;
790 0 : grabx = event->win_x - pos_x;
791 0 : graby = event->win_y - pos_y;
792 0 : return 1;
793 : }
794 : return 0;
795 : }
796 :
797 :
798 : long
799 0 : MFXListIcon::onRightBtnRelease(FXObject*, FXSelector, void* ptr) {
800 0 : if (isEnabled()) {
801 0 : ungrab();
802 0 : flags &= ~FLAG_SCROLLING;
803 0 : flags |= FLAG_UPDATE;
804 0 : if (target && target->tryHandle(this, FXSEL(SEL_RIGHTBUTTONRELEASE, message), ptr)) {
805 : return 1;
806 : }
807 : return 1;
808 : }
809 : return 0;
810 : }
811 :
812 :
813 : long
814 0 : MFXListIcon::onUngrabbed(FXObject* sender, FXSelector sel, void* ptr) {
815 0 : FXScrollArea::onUngrabbed(sender, sel, ptr);
816 0 : flags &= ~(FLAG_DODRAG | FLAG_TRYDRAG | FLAG_PRESSED | FLAG_CHANGED | FLAG_SCROLLING);
817 0 : flags |= FLAG_UPDATE;
818 0 : stopAutoScroll();
819 0 : return 1;
820 : }
821 :
822 :
823 : long
824 0 : MFXListIcon::onCommand(FXObject*, FXSelector, void* ptr) {
825 0 : return target ? target->tryHandle(this, FXSEL(SEL_COMMAND, message), ptr) : 0;
826 : }
827 :
828 :
829 : long
830 0 : MFXListIcon::onClicked(FXObject*, FXSelector, void* ptr) {
831 0 : return target ? target->tryHandle(this, FXSEL(SEL_CLICKED, message), ptr) : 0;
832 : }
833 :
834 :
835 : long
836 0 : MFXListIcon::onDoubleClicked(FXObject*, FXSelector, void* ptr) {
837 0 : return target ? target->tryHandle(this, FXSEL(SEL_DOUBLECLICKED, message), ptr) : 0;
838 : }
839 :
840 :
841 : long
842 0 : MFXListIcon::onTripleClicked(FXObject*, FXSelector, void* ptr) {
843 0 : return target ? target->tryHandle(this, FXSEL(SEL_TRIPLECLICKED, message), ptr) : 0;
844 : }
845 :
846 :
847 : void
848 7619 : MFXListIcon::setCurrentItem(MFXListIconItem* item, FXbool notify) {
849 7619 : if (item) {
850 : // Deactivate old item
851 7619 : if (currentItem) {
852 7619 : currentItem->setFocus(FALSE);
853 7619 : updateItem(currentItem);
854 : }
855 7619 : currentItem = item;
856 : // Activate new item
857 : if (currentItem) {
858 7619 : currentItem->setFocus(TRUE);
859 7619 : updateItem(currentItem);
860 : }
861 : // Notify item change
862 7619 : if (notify && target) {
863 0 : target->tryHandle(this, FXSEL(SEL_CHANGED, message), (void*)currentItem);
864 : }
865 : }
866 7619 : }
867 :
868 :
869 : FXint
870 37946 : MFXListIcon::getCurrentItemIndex() const {
871 45528 : for (int i = 0; i < (int)items.size(); i++) {
872 37946 : if (items[i] == currentItem) {
873 30364 : return i;
874 : }
875 : }
876 : return -1;
877 : }
878 :
879 :
880 : FXint
881 37946 : MFXListIcon::getViewableItem() const {
882 : // continue depending if we're filtering
883 37946 : if (filter.empty()) {
884 45528 : for (int i = 0; i < (int)items.size(); i++) {
885 37946 : if (items[i] == viewable) {
886 30364 : return i;
887 : }
888 : }
889 : } else {
890 0 : for (int i = 0; i < (int)itemFiltered.size(); i++) {
891 0 : if (itemFiltered[i] == viewable) {
892 0 : return i;
893 : }
894 : }
895 : }
896 : return -1;
897 : }
898 :
899 :
900 : void
901 0 : MFXListIcon::setAnchorItem(MFXListIconItem* item) {
902 : int index = 0;
903 : // continue depending if we're filtering
904 0 : if (filter.empty()) {
905 0 : for (int i = 0; i < (int)items.size(); i++) {
906 0 : if (items[i] == item) {
907 : index = i;
908 : }
909 : }
910 : } else {
911 0 : for (int i = 0; i < (int)itemFiltered.size(); i++) {
912 0 : if (itemFiltered[i] == item) {
913 : index = i;
914 : }
915 : }
916 : }
917 0 : anchor = index;
918 0 : extent = index;
919 0 : }
920 :
921 :
922 : FXint
923 0 : MFXListIcon::getAnchorItem() const {
924 0 : return anchor;
925 : }
926 :
927 :
928 : MFXListIconItem*
929 0 : MFXListIcon::getCursorItem() const {
930 0 : return cursor;
931 : }
932 :
933 :
934 : MFXListIconItem*
935 7619 : MFXListIcon::getItem(FXint index) const {
936 7619 : if (index < 0 || (int)items.size() <= index) {
937 0 : fxerror("%s::getItem: index out of range.\n", getClassName());
938 : }
939 7619 : return items[index];
940 : }
941 :
942 :
943 : FXint
944 0 : MFXListIcon::setItem(FXint index, MFXListIconItem* item, FXbool notify) {
945 : // Must have item
946 0 : if (!item) {
947 0 : fxerror("%s::setItem: item is NULL.\n", getClassName());
948 : }
949 : // Must be in range
950 0 : if (index < 0 || (int)items.size() <= index) {
951 0 : fxerror("%s::setItem: index out of range.\n", getClassName());
952 : }
953 : // Notify item will be replaced
954 0 : if (notify && target) {
955 0 : target->tryHandle(this, FXSEL(SEL_REPLACED, message), (void*)(FXival)index);
956 : }
957 : // Copy the state over
958 0 : item->state = items[index]->state;
959 : // Delete old
960 0 : delete items[index];
961 : // Add new
962 0 : items[index] = item;
963 : // apply filter
964 0 : setFilter(filter, nullptr);
965 0 : return index;
966 : }
967 :
968 :
969 : FXint
970 0 : MFXListIcon::editItem(FXint index, const FXString& text, FXIcon* icon, void* ptr, FXbool notify) {
971 0 : return setItem(index, createItem(text, icon, ptr), notify);
972 : }
973 :
974 :
975 : FXint
976 37946 : MFXListIcon::insertItem(FXint index, MFXListIconItem* item, FXbool notify) {
977 37946 : MFXListIconItem* old = currentItem;
978 : // Must have item
979 37946 : if (!item) {
980 0 : fxerror("%s::insertItem: item is NULL.\n", getClassName());
981 : }
982 : // Must be in range
983 37946 : if (index < 0 || (int)items.size() < index) {
984 0 : fxerror("%s::insertItem: index out of range.\n", getClassName());
985 : }
986 : // Add item to list
987 37946 : items.insert(items.begin() + index, item);
988 : // Adjust indices
989 37946 : if (anchor >= index) {
990 0 : anchor++;
991 : }
992 37946 : if (extent >= index) {
993 0 : extent++;
994 : }
995 37946 : if (getCurrentItemIndex() >= index) {
996 0 : currentItem = items[index];
997 : }
998 37946 : if (getViewableItem() >= index) {
999 0 : viewable = items[index];
1000 : }
1001 37946 : if ((currentItem == nullptr) && ((int)items.size() == 1)) {
1002 7582 : currentItem = items[0];
1003 : }
1004 : // Notify item has been inserted
1005 37946 : if (notify && target) {
1006 0 : target->tryHandle(this, FXSEL(SEL_INSERTED, message), (void*)(FXival)index);
1007 : }
1008 : // Current item may have changed
1009 37946 : if (old != currentItem) {
1010 7582 : if (notify && target) {
1011 0 : target->tryHandle(this, FXSEL(SEL_CHANGED, message), (void*)currentItem);
1012 : }
1013 : }
1014 : // Was new item
1015 37946 : if (currentItem && currentItem == items[index]) {
1016 7582 : if (hasFocus()) {
1017 0 : currentItem->setFocus(TRUE);
1018 : }
1019 : }
1020 : // apply filter
1021 37946 : setFilter(filter, nullptr);
1022 37946 : return index;
1023 : }
1024 :
1025 :
1026 : FXint
1027 0 : MFXListIcon::insertItem(FXint index, const FXString& text, FXIcon* icon, void* ptr, FXbool notify) {
1028 0 : return insertItem(index, createItem(text, icon, ptr), notify);
1029 : }
1030 :
1031 :
1032 : FXint
1033 37946 : MFXListIcon::appendItem(MFXListIconItem* item, FXbool notify) {
1034 37946 : return insertItem((int)items.size(), item, notify);
1035 : }
1036 :
1037 :
1038 : FXint
1039 0 : MFXListIcon::appendItem(const FXString& text, FXIcon* icon, void* ptr, FXbool notify) {
1040 0 : return insertItem((int)items.size(), createItem(text, icon, ptr), notify);
1041 : }
1042 :
1043 :
1044 : void
1045 0 : MFXListIcon::removeItem(FXint index, FXbool notify) {
1046 0 : MFXListIconItem* old = currentItem;
1047 : // Must be in range
1048 0 : if ((index < 0) || ((int)items.size() <= index)) {
1049 0 : fxerror("%s::removeItem: index out of range.\n", getClassName());
1050 : }
1051 : // Notify item will be deleted
1052 0 : if (notify && target) {
1053 0 : target->tryHandle(this, FXSEL(SEL_DELETED, message), (void*)(FXival)index);
1054 : }
1055 : // Delete item
1056 0 : delete items[index];
1057 : // Remove item from list
1058 0 : items.erase(items.begin() + index);
1059 : // Adjust indices
1060 0 : if (anchor >= index) {
1061 0 : anchor++;
1062 : }
1063 0 : if (extent >= index) {
1064 0 : extent++;
1065 : }
1066 0 : if (getCurrentItemIndex() >= index) {
1067 0 : currentItem = items[index];
1068 : }
1069 0 : if (getViewableItem() >= index) {
1070 0 : viewable = items[index];
1071 : }
1072 0 : if ((currentItem == nullptr) && ((int)items.size() == 1)) {
1073 0 : currentItem = items[0];
1074 : }
1075 : // Notify item has been inserted
1076 0 : if (notify && target) {
1077 0 : target->tryHandle(this, FXSEL(SEL_INSERTED, message), (void*)(FXival)index);
1078 : }
1079 : // Current item may have changed
1080 0 : if (old != currentItem) {
1081 0 : if (notify && target) {
1082 0 : target->tryHandle(this, FXSEL(SEL_CHANGED, message), (void*)currentItem);
1083 : }
1084 : }
1085 : // Was new item
1086 0 : if (currentItem && currentItem == items[index]) {
1087 0 : if (hasFocus()) {
1088 0 : currentItem->setFocus(TRUE);
1089 : }
1090 : }
1091 : // apply filter
1092 0 : setFilter(filter, nullptr);
1093 0 : }
1094 :
1095 :
1096 : void
1097 7556 : MFXListIcon::clearItems(FXbool notify) {
1098 : // Delete items
1099 45370 : for (FXint index = (int)items.size() - 1; 0 <= index; index--) {
1100 37814 : if (notify && target) {
1101 0 : target->tryHandle(this, FXSEL(SEL_DELETED, message), (void*)(FXival)index);
1102 : }
1103 37814 : delete items[index];
1104 : }
1105 : // Free array
1106 : items.clear();
1107 : // Adjust indices
1108 7556 : anchor = -1;
1109 7556 : extent = -1;
1110 : // Current item has changed
1111 7556 : if (currentItem) {
1112 7556 : if (notify && target) {
1113 0 : target->tryHandle(this, FXSEL(SEL_CHANGED, message), (void*)(FXival) - 1);
1114 : }
1115 7556 : currentItem = nullptr;
1116 : }
1117 7556 : viewable = nullptr;
1118 : // apply filter
1119 7556 : setFilter(filter, nullptr);
1120 7556 : }
1121 :
1122 :
1123 : void
1124 45502 : MFXListIcon::setFilter(const FXString& value, FXLabel* label) {
1125 45502 : filter = value;
1126 : // update item filtered
1127 : itemFiltered.clear();
1128 159449 : for (int i = 0; i < (int)items.size(); i++) {
1129 227894 : items[i]->show = showItem(items[i]->getText());
1130 113947 : if (items[i]->show) {
1131 113947 : itemFiltered.push_back(items[i]);
1132 : }
1133 : }
1134 : // check if show label
1135 45502 : if (label) {
1136 0 : if (!value.empty() && ((int)itemFiltered.size() == 0)) {
1137 0 : label->show();
1138 : } else {
1139 0 : label->hide();
1140 : }
1141 : }
1142 : // recompute and recalc
1143 45502 : recompute();
1144 45502 : recalc();
1145 45502 : }
1146 :
1147 :
1148 : void
1149 0 : MFXListIcon::setTextColor(FXColor clr) {
1150 0 : if (textColor != clr) {
1151 0 : textColor = clr;
1152 0 : update();
1153 : }
1154 0 : }
1155 :
1156 :
1157 : void
1158 0 : MFXListIcon::setHelpText(const FXString& text) {
1159 0 : help = text;
1160 0 : }
1161 :
1162 :
1163 : FXString
1164 0 : MFXListIcon::tolowerString(const FXString& str) const {
1165 0 : FXString result;
1166 0 : for (int i = 0; i < str.count(); i++) {
1167 0 : result.append((char)::tolower(str[i]));
1168 : }
1169 0 : return result;
1170 0 : }
1171 :
1172 :
1173 0 : MFXListIcon::MFXListIcon() {
1174 0 : flags |= FLAG_ENABLED;
1175 0 : font = (FXFont*) - 1L;
1176 0 : }
1177 :
1178 :
1179 : void
1180 53084 : MFXListIcon::recompute() {
1181 : FXint x, y, w, h;
1182 : x = 0;
1183 : y = 0;
1184 53084 : listWidth = 0;
1185 53084 : listHeight = 0;
1186 204977 : for (auto& item : itemFiltered) {
1187 : // set position and size
1188 151893 : item->x = x;
1189 151893 : item->y = y;
1190 151893 : w = item->getWidth(this);
1191 151893 : h = item->getHeight(this);
1192 151893 : if (w > listWidth) {
1193 83474 : listWidth = w;
1194 : }
1195 151893 : y += h;
1196 : }
1197 53084 : listHeight = y;
1198 53084 : flags &= ~(FXuint)FLAG_RECALC;
1199 53084 : }
1200 :
1201 :
1202 : MFXListIconItem*
1203 0 : MFXListIcon::createItem(const FXString& text, FXIcon* icon, void* ptr) {
1204 0 : return new MFXListIconItem(text, icon, 0, ptr);
1205 : }
1206 :
1207 :
1208 : bool
1209 113947 : MFXListIcon::showItem(const FXString& itemName) const {
1210 113947 : if (filter.empty()) {
1211 : return true;
1212 : } else {
1213 0 : return tolowerString(itemName).find(tolowerString(filter)) != -1;
1214 : }
1215 : }
|