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