1 /* dirbookmarkui.cc
2  * This file belongs to Worker, a file manager for UN*X/X11.
3  * Copyright (C) 2007-2021 Ralf Hoffmann.
4  * You can contact me at: ralf@boomerangsworld.de
5  *   or http://www.boomerangsworld.de/worker
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21 
22 #include "dirbookmarkui.hh"
23 #include "aguix/aguix.h"
24 #include "aguix/awindow.h"
25 #include "aguix/text.h"
26 #include "aguix/fieldlistview.h"
27 #include "aguix/button.h"
28 #include "aguix/solidbutton.h"
29 #include "worker_locale.h"
30 #include "bookmarkdbentry.hh"
31 #include "bookmarkdbfilter.hh"
32 #include "dirbookmarkaddui.hh"
33 #include "dirbookmarkeditui.hh"
34 #include "bookmarkdbproxy.hh"
35 #include "nwc_path.hh"
36 #include <algorithm>
37 #include "prefixdb.hh"
38 #include "worker.h"
39 #include "stringmatcher_flexibleregex.hh"
40 #include "async_job_limiter.hh"
41 #include "wconfig.h"
42 #include <iostream>
43 
DirBookmarkUI(Worker & worker,AGUIX & aguix,BookmarkDBProxy & data)44 DirBookmarkUI::DirBookmarkUI( Worker &worker,
45                               AGUIX &aguix,
46                               BookmarkDBProxy &data  ) : m_worker( worker ),
47                                                          m_aguix( aguix ),
48                                                          m_data( data )
49 {
50     m_filtered_data = std::unique_ptr<BookmarkDBFilter>( new BookmarkDBFilter( data ) );
51 
52     m_win = std::unique_ptr<AWindow>( new AWindow( &m_aguix,
53                                                    0, 0,
54                                                    400, 400,
55                                                    catalog.getLocale( 1292 ) ) );
56     m_win->create();
57 
58     m_co1 = m_win->setContainer( new AContainer( m_win.get(), 1, 5 ), true );
59     m_co1->setMaxSpace( 5 );
60 
61     AContainer *co1_2 = m_co1->add( new AContainer( m_win.get(), 2, 1 ), 0, 0 );
62     co1_2->setMaxSpace( 5 );
63     co1_2->setBorderWidth( 0 );
64     co1_2->add( new Text( &m_aguix, 0, 0, catalog.getLocale( 758 ) ),
65                 0, 0, AContainer::CO_FIX );
66     m_infixtext = static_cast<Text*>( co1_2->add( new Text( &m_aguix, 0, 0, "" ),
67                                                   1, 0, AContainer::CO_INCW ) );
68 
69     AContainer *co1_5 = m_co1->add( new AContainer( m_win.get(), 3, 1 ), 0, 1 );
70     co1_5->setMaxSpace( 5 );
71     co1_5->setBorderWidth( 0 );
72     co1_5->add( new Text( &m_aguix, 0, 0, catalog.getLocale( 822 ) ),
73                 0, 0, AContainer::CO_FIX );
74 
75     AContainer *co1_5_1 = co1_5->add( new AContainer( m_win.get(), 3, 1 ), 1, 0 );
76     co1_5_1->setMaxSpace( 0 );
77     co1_5_1->setMinSpace( 0 );
78     co1_5_1->setBorderWidth( 0 );
79     m_prev_cat_b = (Button*)co1_5_1->add( new Button( &m_aguix, 0, 0,
80                                                       "<", 0 ), 0, 0, AContainer::CO_FIX );
81     m_cat_sb = dynamic_cast<SolidButton*>( co1_5_1->add( new SolidButton( &m_aguix, 0, 0,
82                                                                           "", true ), 1, 0, AContainer::CO_INCW ) );
83     m_next_cat_b = (Button*)co1_5_1->add( new Button( &m_aguix, 0, 0,
84                                                       ">", 0 ), 2, 0, AContainer::CO_FIX );
85 
86     m_lv = static_cast<FieldListView*>( m_co1->add( new FieldListView( &m_aguix,
87                                                                      0, 0,
88                                                                      400, 300, 0 ),
89                                                   0, 2, AContainer::CO_MIN ) );
90     m_lv->setNrOfFields( 4 );
91     m_lv->setGlobalFieldSpace( 5 );
92     m_lv->setShowHeader( true );
93     m_lv->setFieldText( 0, catalog.getLocale( 809 ) );
94     m_lv->setFieldSpace( 0, 0 );
95     m_lv->setFieldSpace( 1, 0 );
96     m_lv->setFieldTextMerged( 0, true );
97     m_lv->setFieldWidth( 0, m_aguix.getTextWidth( "?" ) );
98     m_lv->setFieldText( 2, catalog.getLocale( 810 ) );
99     m_lv->setFieldText( 3, catalog.getLocale( 823 ) );
100     m_lv->setHBarState( 2 );
101     m_lv->setVBarState( 2 );
102     m_lv->setAcceptFocus( true );
103     m_lv->setDisplayFocus( true );
104     m_lv->setOnDemandCallback( [this]( FieldListView *lv,
105                                        int row,
106                                        int field,
107                                        struct FieldListView::on_demand_data &data )
108                                {
109                                    updateLVData( row, field, data );
110                                } );
111 
112 
113     AContainer *co1_4 = m_co1->add( new AContainer( m_win.get(), 3, 1 ), 0, 3 );
114     co1_4->setMinSpace( 5 );
115     co1_4->setMaxSpace( -1 );
116     co1_4->setBorderWidth( 0 );
117 
118     m_addb = (Button*)co1_4->add( new Button( &m_aguix,
119                                               0,
120                                               0,
121                                               catalog.getLocale( 759 ),
122                                               0 ), 0, 0, AContainer::CO_FIX );
123 
124     m_editb = (Button*)co1_4->add( new Button( &m_aguix,
125                                                0,
126                                                0,
127                                                catalog.getLocale( 805 ),
128                                                0 ), 1, 0, AContainer::CO_FIX );
129 
130     m_delb = (Button*)co1_4->add( new Button( &m_aguix,
131                                               0,
132                                               0,
133                                               catalog.getLocale( 760 ),
134                                               0 ), 2, 0, AContainer::CO_FIX );
135 
136     AContainer *co1_3 = m_co1->add( new AContainer( m_win.get(), 2, 1 ), 0, 4 );
137     co1_3->setMinSpace( 5 );
138     co1_3->setMaxSpace( -1 );
139     co1_3->setBorderWidth( 0 );
140     m_okb = (Button*)co1_3->add( new Button( &m_aguix,
141                                              0,
142                                              0,
143                                              catalog.getLocale( 761 ),
144                                              0 ), 0, 0, AContainer::CO_FIX );
145     m_cancelb = (Button*)co1_3->add( new Button( &m_aguix,
146                                                  0,
147                                                  0,
148                                                  catalog.getLocale( 633 ),
149                                                  0 ), 1, 0, AContainer::CO_FIX );
150 
151     m_win->contMaximize( true );
152     m_win->setDoTabCycling( true );
153 }
154 
~DirBookmarkUI()155 DirBookmarkUI::~DirBookmarkUI()
156 {
157 }
158 
mainLoop()159 int DirBookmarkUI::mainLoop()
160 {
161     showData( "" );
162 
163     maximizeWin();
164     m_win->show();
165 
166     m_lv->takeFocus();
167 
168     std::string current_cat = "";
169     AGMessage *msg;
170     int endmode = 0;
171 
172     std::string cfgfile = Worker::getWorkerConfigDir();
173 #ifdef USEOWNCONFIGFILES
174     cfgfile = NWC::Path::join( cfgfile, "prefix.db2" );
175 #else
176     cfgfile = NWC::Path::join( cfgfile, "prefix.db" );
177 #endif
178 
179     PrefixDB pdb( cfgfile );
180 
181     updateCategoryText( current_cat );
182 
183     {
184         std::string start_entry = NWC::Path::join( m_dirname,
185                                                    m_basename );
186         bool found = false;
187 
188         for ( int row = 0;; row++ ) {
189             if ( m_lv->isValidRow( row ) == false ) break;
190             if ( m_lv->getText( row, 1 ) == start_entry ) {
191                 m_lv->setActiveRow( row );
192                 m_lv->showActive();
193                 found = true;
194                 break;
195             }
196         }
197 
198         if ( ! found ) {
199             /* first try to find an entry for which the start_entry
200                is a prefix */
201 
202             int best_hit = -1;
203             std::string::size_type best_hit_length = 0;
204 
205             for ( int row = 0;; row++ ) {
206                 if ( m_lv->isValidRow( row ) == false ) break;
207 
208                 const std::string &entry_text = m_lv->getText( row, 1 );
209 
210                 if ( entry_text.length() > start_entry.length() ) {
211                     if ( entry_text.compare( 0,
212                                              start_entry.length(),
213                                              start_entry ) == 0 ) {
214                         if ( best_hit < 0 ||
215                              entry_text.length() < best_hit_length ) {
216                             best_hit = row;
217                             best_hit_length = entry_text.length();
218                         }
219                     }
220                 }
221             }
222 
223             if ( best_hit >= 0 ) {
224                 m_lv->setActiveRow( best_hit );
225                 m_lv->showActive();
226                 found = true;
227             }
228         }
229 
230         if ( ! found ) {
231             /* now try to find the largest prefix */
232 
233             int best_hit = -1;
234             std::string::size_type best_hit_length = 0;
235 
236             for ( int row = 0;; row++ ) {
237                 if ( m_lv->isValidRow( row ) == false ) break;
238 
239                 const std::string &entry_text = m_lv->getText( row, 1 );
240 
241                 if ( entry_text.length() < start_entry.length() ) {
242                     if ( start_entry.compare( 0,
243                                               entry_text.length(),
244                                               entry_text ) == 0 ) {
245                         if ( best_hit < 0 ||
246                              entry_text.length() > best_hit_length ) {
247                             best_hit = row;
248                             best_hit_length = entry_text.length();
249                         }
250                     }
251                 }
252             }
253 
254             if ( best_hit >= 0 ) {
255                 m_lv->setActiveRow( best_hit );
256                 m_lv->showActive();
257                 found = true;
258             }
259         }
260     }
261 
262     for ( ; endmode == 0; ) {
263         if ( m_existence_tests.empty() ) {
264             msg = m_aguix.WaitMessage( NULL );
265         } else {
266             msg = m_aguix.GetMessage( NULL );
267         }
268 
269         checkExistResults();
270 
271         if ( msg != NULL ) {
272             switch ( msg->type ) {
273               case AG_CLOSEWINDOW:
274                   endmode = -1;
275                   break;
276               case AG_BUTTONCLICKED:
277                   if ( msg->button.button == m_okb ) {
278                       endmode = 1;
279                   } else if ( msg->button.button == m_cancelb ) {
280                       endmode = -1;
281                   } else if ( msg->button.button == m_addb ) {
282                       DirBookmarkAddUI addui( m_aguix, m_data, m_dirname, m_basename );
283 
284                       if ( addui.mainLoop() > 0 ) {
285                           m_data.write();
286                           showData( current_cat );
287                       }
288                   } else if ( msg->button.button == m_editb ) {
289                       BookmarkDBEntry active_entry = getActiveEntry( current_cat );
290                       if ( active_entry.getName().length() > 0 ) {
291                           DirBookmarkEditUI editui( m_aguix, m_data, active_entry );
292 
293                           if ( editui.mainLoop() > 0 ) {
294                               m_data.write();
295                               showData( current_cat );
296                           }
297                       }
298                   } else if ( msg->button.button == m_delb ) {
299                       BookmarkDBEntry active_entry = getActiveEntry( current_cat );
300                       if ( active_entry.getName().length() > 0 ) {
301                           std::string buttontext = catalog.getLocale( 11 );
302                           buttontext += "|";
303                           buttontext += catalog.getLocale( 8 );
304 
305                           int erg = m_win->request( catalog.getLocale( 123 ),
306                                                     catalog.getLocale( 762 ),
307                                                     buttontext.c_str() );
308                           if ( erg == 0 ) {
309                               m_data.delEntry( active_entry );
310                               m_data.write();
311                               showData( current_cat );
312                           }
313                       }
314                   } else if ( msg->button.button == m_prev_cat_b ) {
315                       current_cat = switchCat( current_cat, -1 );
316                       showData( current_cat );
317                       updateCategoryText( current_cat );
318                   } else if ( msg->button.button == m_next_cat_b ) {
319                       current_cat = switchCat( current_cat, 1 );
320                       showData( current_cat );
321                       updateCategoryText( current_cat );
322                   }
323                   break;
324               case AG_KEYPRESSED:
325                   if ( msg->key.key == XK_BackSpace ) {
326                       if ( KEYSTATEMASK( msg->key.keystate ) == ShiftMask ) {
327                           m_filtered_data->setInfixFilter( "" );
328                           showData( current_cat );
329 			  highlightBestHit( pdb );
330                       } else {
331                           std::string cur_infix = m_filtered_data->getInfixFilter();
332 
333                           if ( cur_infix.length() > 0 ) {
334                               cur_infix.resize( cur_infix.length() - 1 );
335                               m_filtered_data->setInfixFilter( cur_infix );
336                               showData( current_cat );
337 			      highlightBestHit( pdb );
338                           }
339                       }
340                   } else if ( msg->key.key == XK_Return ) {
341                       endmode = 1;
342                   } else if ( msg->key.key == XK_Escape ) {
343                       endmode = -1;
344                   } else if ( msg->key.key == XK_Left ) {
345                       current_cat = switchCat( current_cat, -1 );
346                       showData( current_cat );
347 		      highlightBestHit( pdb );
348                       updateCategoryText( current_cat );
349                   } else if ( msg->key.key == XK_Right ) {
350                       current_cat = switchCat( current_cat, 1 );
351                       showData( current_cat );
352 		      highlightBestHit( pdb );
353                       updateCategoryText( current_cat );
354                   } else if ( msg->key.key == XK_Up ) {
355                   } else if ( msg->key.key == XK_Down ) {
356                   } else if ( msg->key.key == XK_Next ) {
357                   } else if ( msg->key.key == XK_Prior ) {
358                   } else if ( msg->key.key == XK_Home ) {
359                   } else if ( msg->key.key == XK_End ) {
360                   } else if ( IsModifierKey( msg->key.key ) ||
361                               IsCursorKey( msg->key.key ) ||
362                               IsPFKey( msg->key.key ) ||
363                               IsFunctionKey( msg->key.key ) ||
364                               IsMiscFunctionKey( msg->key.key ) ||
365                               ( ( msg->key.key >= XK_BackSpace ) && ( msg->key.key <= XK_Escape ) ) ) {
366                   } else if ( strlen( msg->key.keybuf ) > 0 ) {
367                       std::string cur_infix = m_filtered_data->getInfixFilter(), old_infix;
368                       old_infix = cur_infix;
369                       cur_infix += msg->key.keybuf;
370                       m_filtered_data->setInfixFilter( cur_infix );
371                       if ( m_filtered_data->getEntries( current_cat ).empty() == true ) {
372                           m_filtered_data->setInfixFilter( old_infix );
373                       }
374                       showData( current_cat );
375 		      highlightBestHit( pdb );
376                   }
377                   break;
378               case AG_FIELDLV_DOUBLECLICK:
379                   if ( msg->fieldlv.lv == m_lv ) {
380                       // double click in lv, actual element is unimportant here
381                       endmode = 1;
382                   }
383                   break;
384                 case AG_FIELDLV_HEADERCLICKED:
385                     if ( ( msg->fieldlv.lv == m_lv ) &&
386                          ( msg->fieldlv.button == Button1 ) ) {
387                         // store current active entry
388                         BookmarkDBEntry active_entry = getActiveEntry( current_cat );
389 
390                         // switch sort mode
391                         switch ( msg->fieldlv.row ) {
392                             case 0:
393                                 m_filtered_data->toggleSortMode( BookmarkDBFilter::SORT_BY_NAME );
394                                 showData( current_cat );
395                                 break;
396                             case 2:
397                                 m_filtered_data->toggleSortMode( BookmarkDBFilter::SORT_BY_ALIAS );
398                                 showData( current_cat );
399                                 break;
400                             case 4:
401                                 m_filtered_data->toggleSortMode( BookmarkDBFilter::SORT_BY_CATEGORY );
402                                 showData( current_cat );
403                                 break;
404                             default:
405                                 break;
406                         }
407 
408                         // find previously stored entry and activate it
409                         std::list<BookmarkDBEntry> e = m_filtered_data->getEntries( current_cat );
410                         int row = 0;
411                         for ( std::list<BookmarkDBEntry>::iterator it1 = e.begin();
412                               it1 != e.end();
413                               it1++, row++ ) {
414                             if ( *it1 == active_entry ) {
415                                 m_lv->setActiveRow( row );
416                                 break;
417                             }
418                         }
419                     }
420                     break;
421             }
422             m_aguix.ReplyMessage( msg );
423 
424             if ( endmode == 1 ) {
425                 int row = m_lv->getActiveRow();
426                 if ( m_lv->isValidRow( row ) ) {
427                     std::string path = m_lv->getText( row, 1 );
428 
429                     if ( ! NWC::FSEntry( path ).entryExists() ) {
430                         endmode = 0;
431                     }
432                 }
433             }
434         }
435     }
436 
437     m_selected_entry.reset();
438 
439     if ( endmode == 1 ) {
440         int row = m_lv->getActiveRow();
441         if ( m_lv->isValidRow( row ) == true ) {
442             std::list<BookmarkDBEntry> e = m_filtered_data->getEntries( current_cat );
443             for ( std::list<BookmarkDBEntry>::iterator it1 = e.begin();
444                   it1 != e.end();
445                   it1++, row-- ) {
446                 if ( row == 0 ) {
447                     m_selected_entry = std::unique_ptr<BookmarkDBEntry>( new BookmarkDBEntry( *it1 ) );
448                     pdb.pushAccess( m_filtered_data->getInfixFilter(), it1->getName(), time( NULL ) );
449                 }
450             }
451         }
452     }
453 
454     m_win->hide();
455 
456     moveTests();
457 
458     return endmode;
459 }
460 
showData(const std::string & cat)461 void DirBookmarkUI::showData( const std::string &cat )
462 {
463     StringMatcherFlexibleRegEx matcher;
464 
465     matcher.setMatchFlexible( false );
466 
467     matcher.setMatchString( m_filtered_data->getInfixFilter() );
468 
469     m_infixtext->setText( m_filtered_data->getInfixFilter().c_str() );
470 
471     int act_row = m_lv->getActiveRow();
472 
473     m_lv->setSize( 0 );
474 
475     // invalidate running futures
476     for ( auto &e : m_existence_tests ) {
477         std::get<2>( e ) = false;
478     }
479 
480     std::list<BookmarkDBEntry> e = m_filtered_data->getEntries( cat );
481     for ( std::list<BookmarkDBEntry>::iterator it1 = e.begin();
482           it1 != e.end();
483           it1++ ) {
484         int row = m_lv->addRow();
485         m_lv->setText( row, 1, it1->getName() );
486         m_lv->setText( row, 2, it1->getAlias() );
487         m_lv->setText( row, 3, it1->getCategory() );
488         m_lv->setPreColors( row, FieldListView::PRECOLOR_ONLYACTIVE );
489 
490         if ( row == act_row )
491             m_lv->setActiveRow( row );
492 
493         if ( ! m_filtered_data->getInfixFilter().empty() ) {
494             std::vector< size_t > segments = matcher.getMatchSegments( it1->getName() );
495 
496             if ( segments.size() > 1 ) {
497                 m_lv->setHighlightSegments( row, 1, segments );
498             }
499         }
500     }
501 
502     if ( m_lv->isValidRow( m_lv->getActiveRow() ) == false ) {
503         if ( act_row < 0 ) {
504             m_lv->setActiveRow( 0 );
505         } else {
506             m_lv->setActiveRow( e.size() - 1 );
507         }
508     }
509 
510     m_lv->redraw();
511 }
512 
getSelectedEntry()513 BookmarkDBEntry DirBookmarkUI::getSelectedEntry()
514 {
515     if ( m_selected_entry.get() != NULL ) {
516         return *m_selected_entry;
517     }
518     throw 0;
519 }
520 
getFilterString()521 std::string DirBookmarkUI::getFilterString()
522 {
523     return m_filtered_data->getInfixFilter();
524 }
525 
setCurrentDirname(const std::string & dirname)526 void DirBookmarkUI::setCurrentDirname( const std::string &dirname )
527 {
528     m_dirname = dirname;
529 }
530 
setCurrentBasename(const std::string & basename)531 void DirBookmarkUI::setCurrentBasename( const std::string &basename )
532 {
533     m_basename = basename;
534 }
535 
getActiveEntry(const std::string & cat)536 BookmarkDBEntry DirBookmarkUI::getActiveEntry( const std::string &cat )
537 {
538     int row = m_lv->getActiveRow();
539     BookmarkDBEntry dbe( "", "" );
540 
541     if ( m_lv->isValidRow( row ) == true ) {
542         std::list<BookmarkDBEntry> e = m_filtered_data->getEntries( cat );
543         for ( std::list<BookmarkDBEntry>::iterator it1 = e.begin();
544               it1 != e.end();
545               it1++, row-- ) {
546             if ( row == 0 ) {
547                 dbe = *it1;
548             }
549         }
550     }
551     return dbe;
552 }
553 
switchCat(const std::string & current_cat,int dir)554 std::string DirBookmarkUI::switchCat( const std::string &current_cat, int dir )
555 {
556     std::list<std::string> cats = m_data.getCats();
557     std::string new_cat = current_cat;
558 
559     if ( dir == 0 || cats.empty() == true ) return "";
560 
561     if ( current_cat == "" ) {
562         // "all"
563         if ( dir > 0 ) {
564             new_cat = cats.front();
565         } else {
566             new_cat = cats.back();
567         }
568     } else {
569         std::list<std::string>::iterator it1 = std::find( cats.begin(),
570                                                           cats.end(),
571                                                           current_cat );
572 
573         if ( it1 != cats.end() ) {
574             if ( dir < 0 ) {
575                 // to the left
576                 if ( it1 == cats.begin() )
577                     new_cat = "";
578                 else {
579                     it1--;
580                     new_cat = *it1;;
581                 }
582             } else {
583                 it1++;
584                 if ( it1 == cats.end() )
585                     new_cat = "";
586                 else
587                     new_cat = *it1;
588             }
589         } else {
590             new_cat = "";
591         }
592     }
593     return new_cat;
594 }
595 
updateCategoryText(const std::string & current_cat)596 void DirBookmarkUI::updateCategoryText( const std::string &current_cat )
597 {
598     if ( current_cat == "" ) {
599         m_cat_sb->setText( catalog.getLocale( 824 ) );
600     } else {
601         m_cat_sb->setText( current_cat.c_str() );
602     }
603 }
604 
maximizeWin()605 void DirBookmarkUI::maximizeWin()
606 {
607     m_lv->maximizeX();
608     m_lv->maximizeY();
609     int my_w = m_lv->getWidth() + 10;
610     int my_h = m_lv->getHeight() + 10;
611 
612     if ( my_w < 400 ) my_w = 400;
613     if ( my_h < 300 ) my_h = 300;
614 
615     int rx, ry, rw, rh;
616 
617     m_aguix.getLargestDimensionOfCurrentScreen( &rx, &ry,
618                                                 &rw, &rh );
619 
620     int mw = rw * 80 / 100;
621     int mh = rh * 80 / 100;
622     m_co1->resize( mw, mh );
623     m_co1->rearrange();
624 
625     if ( my_w < m_lv->getWidth() ) {
626         m_co1->setMinWidth( my_w, 0, 2 );
627     } else {
628         m_co1->setMinWidth( m_lv->getWidth(), 0, 2 );
629     }
630     if ( my_h < m_lv->getHeight() ) {
631         m_co1->setMinHeight( my_h, 0, 2 );
632     } else {
633         m_co1->setMinHeight( m_lv->getHeight(), 0, 2 );
634     }
635     m_win->contMaximize( true );
636 }
637 
highlightBestHit(class PrefixDB & pdb)638 void DirBookmarkUI::highlightBestHit( class PrefixDB &pdb )
639 {
640     if ( m_filtered_data->getInfixFilter().empty() ) return;
641 
642     time_t t1;
643     std::string best_hit = pdb.getBestHit( m_filtered_data->getInfixFilter(), t1 );
644 
645     if ( best_hit.empty() ) return;
646 
647     // search for best_hit in visible entries
648 
649     for ( int row = 0;; row++ ) {
650 	if ( m_lv->isValidRow( row ) == false ) break;
651 	if ( m_lv->getText( row, 1 ) == best_hit ) {
652 	    m_lv->setActiveRow( row );
653 	    m_lv->showActive();
654 	    break;
655 	}
656     }
657 }
658 
updateLVData(int row,int field,struct FieldListView::on_demand_data & data)659 void DirBookmarkUI::updateLVData( int row, int field,
660                                   struct FieldListView::on_demand_data &data )
661 {
662     if ( field == 0 ) {
663         std::string path = m_lv->getText( row, 1 );
664 
665         if ( m_existence_results.count( path ) == 0 ) {
666             bool async_started = false;
667             bool do_check = true;
668 
669             if ( ! wconfig->getDisableBGCheckPrefix().empty() &&
670                  AGUIXUtils::starts_with( path, wconfig->getDisableBGCheckPrefix() ) ) {
671                 do_check = false;
672             }
673 
674             if ( do_check && ! AsyncJobLimiter::async_job_limit_reached() ) {
675                 try {
676                     auto f = std::async( std::launch::async,
677                                          [path] {
678                                              AsyncJobLimiter::inc_async_job();
679                                              auto res = ExistenceTest::checkPathExistence( path );
680                                              AsyncJobLimiter::dec_async_job();
681 
682                                              return res;
683                                          } );
684                     m_existence_tests.push_back( std::make_tuple( std::move( f ), row, true ) );
685 
686                     data.text = "?";
687                     data.text_set = true;
688 
689                     async_started = true;
690                 } catch ( std::system_error &e ) {
691                     std::cout << e.what() << std::endl;
692                 }
693             }
694 
695             if ( ! async_started ) {
696                 std::promise< ExistenceTest::existence_state_t > promise;
697                 auto f = promise.get_future();
698 
699                 m_existence_tests.push_back( std::make_tuple( std::move( f ), row, true ) );
700 
701                 if ( do_check ) {
702                     promise.set_value( ExistenceTest::checkPathExistence( path ) );
703                 } else {
704                     promise.set_value( { .exists = ExistenceTest::EXISTS_YES,
705                             .path_length = path.size() } );
706                 }
707 
708                 data.text = "?";
709                 data.text_set = true;
710             }
711         } else {
712             auto &res = m_existence_results.at( path );
713 
714             if ( res.exists == ExistenceTest::EXISTS_YES ) {
715                 data.text = "";
716                 data.text_set = true;
717             } else if ( res.exists == ExistenceTest::EXISTS_NO ) {
718                 data.text = "";
719                 data.text_set = true;
720             }
721         }
722     } else if ( field == 1 ) {
723         std::string path = m_lv->getText( row, 1 );
724 
725         if ( m_existence_results.count( path ) > 0 ) {
726             auto &res = m_existence_results.at( path );
727 
728             if ( res.exists == ExistenceTest::EXISTS_NO ) {
729                 data.strike_out = true;
730                 data.strike_out_set = true;
731                 data.strike_out_start = res.path_length + 1;
732                 data.strike_out_start_set = true;
733             }
734 
735             data.done = true;
736         }
737     }
738 }
739 
moveTests()740 void DirBookmarkUI::moveTests()
741 {
742     for ( auto it = m_existence_tests.begin();
743           it != m_existence_tests.end();
744           ) {
745         std::future_status status = std::get<0>( *it ).wait_for( std::chrono::duration<int>::zero() );
746 
747         auto next_it = it;
748         next_it++;
749 
750         if ( status != std::future_status::ready ) {
751             auto f = std::move( std::get<0>( *it ) );
752 
753             m_worker.enqueueFuture( std::move( f ) );
754         }
755 
756         m_existence_tests.erase( it );
757         it = next_it;
758     }
759 }
760 
checkExistResults()761 void DirBookmarkUI::checkExistResults()
762 {
763     if ( m_existence_tests.empty() ) {
764         return;
765     }
766 
767     bool update_lv = false;
768 
769     for ( auto it = m_existence_tests.begin();
770           it != m_existence_tests.end();
771           ) {
772         std::future_status status = std::get<0>( *it ).wait_for(std::chrono::duration<int>::zero());
773 
774         if ( status == std::future_status::ready ) {
775             ExistenceTest::existence_state_t res = std::get<0>( *it ).get();
776 
777             if ( std::get<2>( *it ) == true ) {
778                 int row = std::get<1>( *it );
779 
780                 if ( m_lv->isValidRow( row ) ) {
781                     std::string path = m_lv->getText( row, 1 );
782 
783                     m_existence_results[ path ] = res;
784 
785                     m_lv->clearOnDemandDataAvailableFlag( row );
786                     update_lv = true;
787                 }
788             }
789 
790             auto next_it = it;
791             next_it++;
792 
793             m_existence_tests.erase( it );
794 
795             it = next_it;
796         } else {
797             it++;
798         }
799     }
800 
801     if ( update_lv ) {
802         m_lv->redraw();
803     }
804 }
805