doxygen comments
[aoi.git] / src / aoi.cxx
blob9da7cc62892bf7e0a7d81d5ad95e99f3f62b6ef3
1 /*
2 Copyright 2013 Karel Matas
4 This program is free software: you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation, either version 3 of the License, or
7 (at your option) any later version.
9 This program is distributed in the hope that it will be useful,
10 but WITHOUT ANY WARRANTY; without even the implied warranty of
11 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 GNU General Public License for more details.
14 You should have received a copy of the GNU General Public License
15 along with this program. If not, see <http://www.gnu.org/licenses/>.
17 #include <cmath>
18 #include <set>
19 #include <FL/fl_ask.H>
20 #include "aoi.hxx"
21 #include "utils.hxx"
23 namespace aoi {
25 using std::set;
26 using aoi_ui::TextStyle;
27 using utils::to_string;
29 const char *LOG_FILE = "aoi.log";
30 const char SENSE_SEARCH_CHAR = ':';
32 App* App::instance_ = nullptr;
34 App::App ()
36 remove(LOG_FILE);
37 logger_.filename(LOG_FILE);
38 logger_.loglevel(Logger::MSG_DEBUG);
40 cfg_ = new aoi_config::Config();
41 ui_ = new aoi_ui::GUI(this);
42 rmn_ = new Romanization();
44 ui_->progress(0,"Opening database...");
46 try {
47 db_ = new SQLite3::SQLite3( get_config("db/file_main").c_str() );
49 catch (SQLite3::CantOpenDatabase &e){
50 log_e( "Aoi: Can't open database '" + string(e.what()) + "'.");
53 std::stringstream ss;
54 ss << "ATTACH DATABASE '" << get_config("db/file_user") << "'as user;";
55 query(ss.str().c_str());
57 ui_->progress( 30, "Checking tables..." );
58 check_tables();
59 ui_->progress( 60, "Checking indexes..." );
60 check_indexes();
62 load_config();
63 init_dicview();
65 #ifdef DEBUG
66 // ui_->progress(90, "DEBUG query..." );
67 // parse_dic_input("ana*");
68 // cb_kanji_search();
69 // ui_->cb_toggle_group(nullptr);
70 #endif
72 ui_->progress( 98, "Initializing fonts..." );
73 ui_->fontname_kanji( get_config("font/kanji") );
74 ui_->help_file( get_config("sources/help_index") );
76 ui_->progress_hide();
79 auto q = query("select val from aoi where key='jmdict_version'");
80 string jmdict_version = (q.empty()) ? "NONE":q[0];
81 q = query("select val from aoi where key='kanjidic_version'");
82 string kanjidic_version = q.empty() ? "NONE":q[0];
83 if ( jmdict_version == "NONE" || kanjidic_version == "NONE" )
84 cb_manage_db();
87 #ifdef DEBUG
88 logger_.loglevel(Logger::MSG_DEBUG);
89 #endif
91 instance_ = this;
95 App::~App ()
97 delete db_;
98 delete rmn_;
99 delete ui_;
100 delete cfg_;
104 void App::init_dicview ()
106 // initialize textstyles
107 TextStyle style_default(FL_HELVETICA,1.2);
108 TextStyle style_reading(aoi_ui::FONT_KANJI,1.5);
109 TextStyle style_reading_freq = style_reading;
110 style_reading_freq.color = get_color("frequent");
111 TextStyle style_kanji(aoi_ui::FONT_KANJI,1.7);
112 TextStyle style_kanji_freq = style_kanji;
113 style_kanji_freq.color = get_color("frequent");
114 TextStyle style_inf(FL_HELVETICA_ITALIC,0.8,get_color("pos"));
115 style_inf.offset_y = 3;
116 TextStyle style_pos = style_inf;
117 TextStyle style_misc = style_pos;
118 style_misc.color = get_color("misc");
119 TextStyle style_field = style_pos;
120 style_field.color = get_color("field");
121 TextStyle style_dial = style_pos;
123 // register TextStyles
124 ui_->register_tag_dicview( "default", style_default );
125 ui_->register_tag_dicview( "reading", style_reading );
126 ui_->register_tag_dicview( "reading_freq", style_reading_freq );
127 ui_->register_tag_dicview( "kanji", style_kanji );
128 ui_->register_tag_dicview( "kanji_freq", style_kanji_freq );
129 ui_->register_tag_dicview( "kinf", style_inf );
130 ui_->register_tag_dicview( "rinf", style_inf );
131 ui_->register_tag_dicview( "pos", style_pos );
132 ui_->register_tag_dicview( "misc", style_misc );
133 ui_->register_tag_dicview( "field", style_field );
134 ui_->register_tag_dicview( "dial", style_dial );
138 vector<string> App::query ( const char *q, bool log_query, bool replace_separator )
140 if ( !db_ ){
141 log_e("App::query(): Database does not exist.");
142 return {};
144 ui_->cursor_wait();
145 vector<string> result;
146 try {
147 if ( log_query )
148 log_d(string(q));
149 result = db_->query(q);
151 catch (SQLite3::DatabaseError &e){
152 log_e("App: DatabaseError:: " + string(e.what()) + string("\nQuery: ")
153 + string(e.query()) );
154 ui_->cursor_default();
156 string msg = std::to_string(db_->result_rows()) + " results";
157 log( "App::query(): " + msg);
158 if ( replace_separator )
159 for ( string &s: result )
160 utils::replace_all(s, SEPARATOR_SQL, ", ");
161 ui_->cursor_default();
162 return result;
166 void App::cb_set_components ()
168 if ( curr_components_.empty() )
169 return;
171 using aoi_ui::ComponentView;
173 // prepare cells
174 vector<string> included = ui_->components_include();
175 vector<string> excluded = ui_->components_exclude();
177 vector<ComponentView::Cell> v;
178 for ( string &s: included )
179 v.push_back( ComponentView::Cell(s,ComponentView::CELL_SELECTED_1) );
180 for ( string &s: excluded )
181 v.push_back( ComponentView::Cell(s,ComponentView::CELL_SELECTED_2) );
183 // sort by occurences
184 if ( !ui_->sort_components_by_strokes() ){
185 for ( auto mi = curr_components_.rbegin(); mi!=curr_components_.rend(); ++mi ){
186 if ( !utils::is_in( included, mi->second )
187 && !utils::is_in( excluded, mi->second ) )
188 v.push_back( ComponentView::Cell(mi->second) );
191 // sort by strokes
192 else {
193 vector<string> comps_by_strokes;
194 for ( auto mi: curr_components_ ){
195 if ( mi.first < get_config<int>("knj/min_compo_count") )
196 continue;
197 if ( !utils::is_in( included, mi.second )
198 && !utils::is_in( excluded, mi.second ) )
199 comps_by_strokes.push_back(mi.second);
202 struct SortByStrokes {
203 map<string,int> comps;
204 SortByStrokes( const map<string,int> &c ): comps(c){};
205 bool operator() (const string &c1, const string &c2 ){
206 return ( this->comps[c1] < this->comps[c2] );
208 } sbc(components_);
210 std::sort( comps_by_strokes.begin(), comps_by_strokes.end(), sbc );
212 int prev_strokes=0;
213 for ( string &s: comps_by_strokes ){
214 int curr_strokes = components_[s];
215 if ( prev_strokes != curr_strokes )
216 v.push_back( ComponentView::Cell( std::to_string(curr_strokes),
217 ComponentView::CELL_LABEL) );
218 v.push_back( ComponentView::Cell(s) );
219 prev_strokes = curr_strokes;
223 ui_->set_components( v );
227 void App::cb_kanji_search ()
230 if ( components_.empty() ){
231 log("Loading components...");
232 vector<string> res = query("select component, strokes from components");
233 int skipped = 0;
234 for ( size_t i=0; i<res.size(); i=i+2 ){
235 int strokes = 0;
236 if ( res[i+1].empty() )
237 skipped++;
238 else
239 strokes = std::stoi(res[i+1]);
240 components_[res[i]] = strokes;
242 if ( skipped>0 )
243 log_d(std::to_string(skipped)+" components without strokes data.");
246 // strokes
247 std::pair<int,int> strokes = utils::parse_range( utils::strip( ui_->strokes() ) );
248 // jlpt
249 std::pair<int,int> jlpt = utils::parse_range( utils::strip( ui_->jlpt() ) );
250 // grade
251 std::pair<int,int> grade = utils::parse_range( utils::strip( ui_->grade() ) );
253 // skip
254 vector<string> skip = utils::split_string( ui_->skip() );
255 std::stringstream sskip;
256 if ( skip.size() > 0 ){
257 string skip1 = utils::strip(skip[0].c_str());
258 sskip << " S.skip1=" << skip1;
260 if ( skip.size() > 1 ){
261 std::pair<int,int> skip2 = utils::parse_range( utils::strip(skip[1].c_str()) );
262 sskip << " and S.skip2>=" << skip2.first << " and S.skip2<=" << skip2.second;
264 if ( skip.size() > 2 ){
265 std::pair<int,int> skip3 = utils::parse_range( utils::strip(skip[2].c_str()) );
266 sskip << " and S.skip3>=" << skip3.first << " and S.skip3<=" << skip3.second;
268 if ( !sskip.str().empty() )
269 sskip << " and ";
271 // ordering
272 string order_by;
273 switch ( ui_->sort_mode() ){
274 case SORT_FREQ_ASC:
275 order_by = "(case freq when 0 then 9999 else freq end) asc";
276 break;
277 case SORT_FREQ_DESC:
278 order_by = "(case freq when 0 then 9999 else freq end) desc";
279 break;
280 case SORT_STROKES_ASC:
281 order_by = "strokes asc";
282 break;
283 case SORT_STROKES_DESC:
284 order_by = "strokes desc";
285 break;
288 vector<string> components_include = ui_->components_include();
289 vector<string> components_exclude = ui_->components_exclude();
290 std::stringstream comps;
291 for ( auto c: components_include )
292 comps << " and components like '%" << c << "%'";
293 for ( auto c: components_exclude )
294 comps << " and components not like '%" << c << "%'";
295 printf("%s\n",comps.str().c_str());
297 string jis208 = "";
298 if ( get_config<bool>("knj/jis208_only") )
299 jis208 = " flags glob '*jis208*' and ";
301 // build query
302 std::stringstream ss;
303 ss << "select distinct "
304 << "K.kanji,freq,components,flags "
305 << " from k_kanji as K"
306 << (sskip.str().empty() ? " where ":", k_skip as S where K.kanji=S.kanji and")
307 << jis208
308 << sskip.str()
309 << " strokes>=" << strokes.first << " and strokes<=" << strokes.second
310 << " and jlpt>=" << jlpt.first << " and jlpt<=" << jlpt.second
311 << " and grade>=" << grade.first << " and grade<=" << grade.second
312 << comps.str()
313 << " order by " << order_by;
316 // perform query
317 vector<string> q = query( ss.str().c_str(), true, false );
318 vector<aoi_ui::KanjiView::Cell> data;
319 vector<string> components;
320 set<string> flags;
321 for ( size_t i=0; i<q.size(); i+=4 ){
322 string kanji = q[i];
323 int freq = std::stoi(q[i+1]);
324 data.push_back(
325 aoi_ui::KanjiView::Cell(
326 kanji,
327 (freq>0)? get_color("frequent"):-1
330 components.push_back(q[i+2]);
331 for ( string &s: utils::split_string( q[i+3], SEPARATOR_SQL) )
332 flags.insert(s);
335 log_d("Groups: " + utils::to_string(flags));
337 ui_->set_kanjiview( data );
338 char b[32];
339 sprintf( b, "%d results", db_->result_rows() );
340 ui_->set_kanji_results( b );
342 utils::Histogram<string> histogram;
343 for ( string &s: components )
344 histogram.add( utils::str_to_chars(s.c_str()) );
345 curr_components_ = histogram.sorted();
347 cb_set_components();
351 void App::alert ( const string &msg, const string &desc )
353 std::stringstream ss;
354 ss << msg;
355 if ( !desc.empty() )
356 ss << "\n\nDetails:\n" << desc;
357 log_e(ss);
358 ui_->alert(ss.str());
362 int App::run ( int argc, char **argv )
364 return ui_->run(argc,argv);
368 void App::check_tables ()
370 for ( auto &dbit: aoi_config::db_tables) {
371 // no need for vacuum here, it will be done by check_indexes()
372 log("Checking tables in database: " + string(dbit.first));
373 std::stringstream q;
374 q << "SELECT name FROM " << dbit.first << "." << "sqlite_master WHERE type='table'";
375 vector<string> existing = query(q.str().c_str());
376 // CREATE TABLE name ( column1 TYPE, column2 TYPE, ... )
377 for ( auto &table: dbit.second ){
378 if ( utils::is_in(existing, string(table.first)) )
379 continue;
380 log("Creating table " + string(table.first));
381 vector<string> v;
382 for ( auto &column: table.second ){
383 std::stringstream sstr;
384 sstr << column.name << " " << column.type;
385 v.push_back(sstr.str());
387 std::stringstream ss;
388 ss << "CREATE TABLE " << dbit.first << "." << table.first
389 << " (" << to_string(v,",") << ");\n";
390 query(ss.str().c_str());
392 log("Check done.");
397 void App::check_indexes ()
399 bool do_vacuum = false;
400 log("Checking indexes...");
401 for ( auto &dbit: aoi_config::db_tables ) {
402 std::stringstream q;
403 q << "SELECT name FROM " << dbit.first << "." << "sqlite_master WHERE type='index'";
404 vector<string> existing = query(q.str().c_str());
405 // CREATE INDEX idx_table_column ON table ( column ASC )
406 for ( auto &mi: dbit.second ){ // tables
407 for ( auto &c: mi.second ){ // columns
408 std::stringstream idx_name;
409 idx_name << "idx_" << mi.first << "_" << c.name;
410 if ( c.index && !utils::is_in( existing, idx_name.str() ) ){
411 log(string("Creating index ") + idx_name.str());
412 std::stringstream ss;
413 ss << "CREATE INDEX " << idx_name.str() << " ON " << mi.first
414 << "(" << c.name << " " << c.sort << ")";
415 query(ss.str().c_str());
416 do_vacuum = true;
421 if ( do_vacuum )
422 query("VACUUM");
423 log("Check done.");
427 void App::cb_dic_input ()
429 parse_dic_input( ui_->get_dic_input() );
430 ui_->reset_filters();
434 void App::on_dic_selected ( int id )
436 log_d("App::on_dic_selected()");
440 void App::cb_download_db ()
442 log_d("Download DB");
443 string path = ui_->download_dialog( get_config("sources/url_database") );
444 if ( !path.empty() && !utils::file_exists( path ) ){
445 log_w("App::cb_download_db(): Not a file: "+path);
446 return;
451 void App::cb_edit_word ()
453 std::stringstream ss;
454 int id = ui_->dicview_selected_rowid();
455 ss << "App::edit_word( " << id << " )";
456 log_d(ss);
457 ui_->edit_word( db_get_word(id));
461 void App::cb_popup_kanji ( const string &kanji )
463 std::stringstream ss;
464 ss << "App::on_kanji_clicked()" << kanji;
465 Kanji k = db_get_kanji(kanji);
466 ui_->popup_kanji( k );
467 // XXX: this should be somewhere else (it is ilogical here)
468 ui_->highlight_components( k.components() );
469 log(ss);
473 void App::set_listview ( const vector<string> &v )
475 if ( !listview_items_.empty() ) listview_items_.clear();
476 vector<string> d;
477 vector<int> cell_ids;
478 size_t i = 0;
479 while ( i < v.size() ) {
480 int cell_id = std::stoi(v[i]); // jmdict id
481 cell_ids.push_back(cell_id);
482 cell_ids.push_back(cell_id);
483 cell_ids.push_back(cell_id);
484 // pos
485 set<string> pos;
486 for ( string &elt: utils::split_string( v[i+1], ",") )
487 if ( elt.size() > 0 )
488 pos.insert(utils::strip(elt.c_str()));
489 d.push_back( v[i+2] ); // reading
490 d.push_back( v[i+3] ); // kanji
491 d.push_back( v[i+4] ); // sense
492 listview_items_.push_back( {cell_id, pos, v[i+2], v[i+3], v[i+4]} );
493 i += 5;
495 char buff[32];
496 sprintf( buff, "%d results", db_->result_rows() );
497 ui_->set_dic_results( buff );
498 ui_->set_listview(d,cell_ids);
502 void App::parse_dic_input ( const char *str )
504 log_d(string("App::parse_dic_input: \"") +string(str) + string("\""));
506 string stripped = utils::strip(str);
508 if ( stripped.empty() )
509 return;
511 const char *s = stripped.c_str();
513 string qq;
514 if ( s[0] != SENSE_SEARCH_CHAR ){
515 DictionaryInputParser p;
516 qq = p.parse(s);
518 if ( p.warning() ){
519 int res = fl_choice(
520 "Too broad search.\n"\
521 "Your computer may become unresponsible for a long time.\n"\
522 "Proceed?",
523 "Proceed",
524 "Cancel",
527 if ( res == 1 ) // Cancel
528 return;
531 if ( !strchr(s,'*') && !strchr(s,'?') && !strchr(s,'[') && !strchr(s,'{')
532 && !strchr(s,'(') )
533 qq += "*";
536 std::stringstream q;
537 if ( s[0] == SENSE_SEARCH_CHAR ){
538 q << "select did,"
539 << "group_concat(pos) as pos,"
540 << q_reading("d_sense.did")
541 << q_kanji("d_sense.did")
542 << q_sense()
543 << " from d_sense where gloss glob '*" << stripped.substr(1) << "*'"
544 << " group by did";
546 else if ( rmn_->contains_kanji( qq.c_str() ) ) {
547 q << "select d_kanji.did as did,"
548 << "(select group_concat(pos) from d_sense where d_sense.did = d_kanji.did) as pos,"
549 << q_kanji()
550 << q_reading("d_kanji.did")
551 << q_sense("d_kanji.did")
552 << "from d_kanji where "
553 << "kanji glob '" << rmn_->romaji_to_hiragana(qq.c_str()) << "' "
554 << " or kanji glob '" << rmn_->romaji_to_katakana(qq.c_str()) << "' "
555 << " group by did order by d_kanji.freq desc, d_kanji.kanji asc";
557 else {
558 q << "select d_reading.did as did,"
559 << "(select group_concat(pos) from d_sense where d_sense.did = d_reading.did) as pos,"
560 << q_reading()
561 << q_kanji("d_reading.did")
562 << q_sense("d_reading.did")
563 << "from d_reading where "
564 << "reading glob '" << rmn_->romaji_to_hiragana(qq.c_str())
565 << "' or reading glob '"<< rmn_->romaji_to_katakana(qq.c_str()) << "' "
566 << " group by did order by d_reading.freq desc, d_reading.reading asc";
568 set_listview(query(q.str().c_str()));
572 void App::cb_examples ()
574 log_d("cb_examples():" + std::to_string(ui_->dicview_selected_rowid()));
575 DicWord w = db_get_word( ui_->dicview_selected_rowid() );
576 if ( w.k_ele().empty() )
577 return;
578 std::stringstream q;
579 // returns: japanese sentece, english sentence, string to be highlighted
580 q << "select"
581 << " (select text from sentences where id=sid) as jp,"
582 << " (select text from sentences where id=mid) as en,"
583 << " good_example,"
584 << " form"
585 << " from indices where headword='" << w.k_ele()[0].kanji()
586 << "' order by good_example desc;";
587 vector<string> res = query(q.str().c_str());
588 std::stringstream ss;
589 for ( size_t i=0; i<res.size(); i+=4 ){
590 bool good_example = std::stoi(res[i+2]);
591 ss << "<font face=\"symbol\"";
592 if ( good_example )
593 ss << " color=\"blue\"";
594 ss << ">" << res[i] << "</font><br>";
595 if ( !res[i+1].empty() )
596 ss << res[i+1] << "<br>";
597 ss << "&nbsp;<br>";
598 // form = res[i+3]
600 ui_->show_html(ss.str());
604 DicWord App::db_get_word ( int id )
606 DicWord w(id);
607 std::stringstream ss;
609 // kanji
610 ss << "select kid,kanji,inf,freq from d_kanji where did=" << id <<";";
611 vector<string> res = query( ss.str().c_str() );
612 for ( size_t i=0; i<res.size(); i+=4 ){
613 int kid = std::stoi(res[i]);
614 string kanji = res[i+1];
615 vector<string> inf = utils::split_string(res[i+2],SEPARATOR_SQL);
616 bool freq = std::stoi(res[i+3]);
617 w.k_ele( ElementKanji( kid, kanji, inf, freq ) );
620 // reading
621 ss.str("");
622 ss.clear();
623 ss << "select rid,reading,inf,nokanji,freq from d_reading where did=" << id << ";";
624 res = query( ss.str().c_str() );
625 for ( size_t i=0; i<res.size(); i+=5 ){
626 int rid = std::stoi(res[i]);
627 string reading = res[i+1];
628 vector<string> inf = utils::split_string(res[i+2],SEPARATOR_SQL);
629 bool nokanji = std::stoi(res[i+3]);
630 bool freq = std::stoi(res[i+4]);
631 w.r_ele( ElementReading( rid, reading, nokanji, {/*restr*/}, inf, freq ) );
634 // sense
635 ss.str("");
636 ss.clear();
637 ss << "select sid,gloss,xref,ant,inf,pos,field,misc,dial from d_sense where did=" << id << ";";
638 res = query( ss.str().c_str() );
639 for ( size_t i=0; i<res.size(); i+=9 ){
640 int sid = std::stoi(res[i]);
641 vector<string> gloss = utils::split_string(res[i+1],SEPARATOR_SQL);
642 vector<string> xref = utils::split_string(res[i+2],SEPARATOR_SQL);
643 vector<string> ant = utils::split_string(res[i+3],SEPARATOR_SQL);
644 vector<string> inf = utils::split_string(res[i+4],SEPARATOR_SQL);
645 vector<string> pos = utils::split_string(res[i+5],SEPARATOR_SQL);
646 vector<string> field = utils::split_string(res[i+6],SEPARATOR_SQL);
647 vector<string> misc = utils::split_string(res[i+7],SEPARATOR_SQL);
648 vector<string> dial = utils::split_string(res[i+8],SEPARATOR_SQL);
649 w.s_ele( ElementSense( sid, gloss, {/*stagk*/}, {/*stagr*/}, pos, xref,
650 ant, field, misc, dial, inf) );
653 return w;
657 Kanji App::db_get_kanji ( const string &kanji )
659 log("App::db_get_kanji()");
660 std::stringstream q;
661 q << "select kanji,strokes,ucs, rad_classic, rad_nelson, "
662 << "jlpt, grade, freq, onyomi, kunyomi, nanori, meaning,flags,components "
663 << "from k_kanji where kanji='" << kanji << "';";
665 vector<string> res = query(q.str().c_str());
666 Kanji kk(res[0]);
667 kk.strokes(std::stoi(res[1]));
668 kk.ucs(res[2]);
669 kk.rad_classic(std::stoi(res[3]));
670 kk.rad_nelson( (res[4].empty()) ? -1:std::stoi(res[4]));
671 kk.jlpt(std::stoi(res[5]));
672 kk.grade(std::stoi(res[6]));
673 kk.freq(std::stoi(res[7]));
674 kk.onyomi( utils::split_string(res[8],SEPARATOR_SQL) );
675 kk.kunyomi( utils::split_string(res[9],SEPARATOR_SQL) );
676 kk.nanori( utils::split_string(res[10],SEPARATOR_SQL) );
677 kk.meaning( utils::split_string(res[11],SEPARATOR_SQL) );
678 kk.flags( utils::split_string(res[12],SEPARATOR_SQL) );
679 kk.components( res[13] );
681 string qq = "select skip1,skip2,skip3,misclass from k_skip where kanji='"+kanji+"';";
682 vector<string> res2 = query(qq.c_str());
683 if ( res2.size() % 4 != 0 ){
684 std::stringstream ss;
685 ss << "Wrong SKIP count. Kanji: " << kanji
686 << "Query result size: " << res2.size()
687 << " (should be 4,8 or 12). SKIP not loaded.";
688 log_e(ss);
689 return kk;
691 for ( size_t i=0; i < res2.size(); i+=4 )
692 kk.skip( res2[i], res2[i+1], res2[i+2], res2[i+3] );
693 return kk;
697 void App::cb_filter_listview ()
699 vector<string> pos = ui_->listview_filters();
700 log_d("App::cb_filter_listview(): pos: " + utils::to_string(pos));
701 bool filter_expr = ( utils::is_in( pos, string("expr") ) );
702 bool filter_noun = ( utils::is_in( pos, string("noun") ) );
703 bool filter_verb = ( utils::is_in( pos, string("verb") ) );
704 bool filter_adj = ( utils::is_in( pos, string("adj") ) );
705 vector<string> data;
706 vector<int> ids;
707 size_t n = 0;
708 for ( auto &elt: listview_items_ ){
709 auto start = elt.pos.begin();
710 auto end = elt.pos.end();
711 if ( filter_expr && std::find( start, end, "exp") == end )
712 continue;
713 if ( filter_noun && std::find( start, end, "n" ) == end )
714 continue;
715 if ( filter_adj && std::find_if( start, end,
716 [](const string &s){ return strncmp(s.c_str(),"adj",3)==0;} ) == end )
717 continue;
718 if ( filter_verb && std::find_if( start, end,
719 [](const string &s){ return strncmp(s.c_str(),"v",1)==0;} ) == end )
720 continue;
721 ids.push_back( elt.did );
722 ids.push_back( elt.did );
723 ids.push_back( elt.did );
724 data.push_back ( elt.reading );
725 data.push_back ( elt.kanji );
726 data.push_back ( elt.sense );
727 n++;
729 ui_->set_listview( data, ids );
730 std::stringstream ss;
731 ss << n << " results";
732 if ( listview_items_.size() != n )
733 ss << " (" << listview_items_.size()-n << " hidden)";
734 log(ss.str());
735 ui_->set_dic_results( ss.str() );
739 void App::cb_dicview_rightclick ( int did )
741 string q = "select group_concat(kanji,'') from d_kanji where did="+std::to_string(did);
742 vector<string> res = query( q.c_str() );
743 set<string> kanji;
744 for ( string &c: utils::str_to_chars(res[0].c_str()) )
745 if ( App::get()->rmn()->is_kanji(c.c_str()) )
746 kanji.insert(c);
747 ui_->dicview_menu( did, vector<string>(kanji.begin(),kanji.end()), false, true );
751 void App::apply_config ()
753 ui_->font_base_size( get_config<int>("font/base_size"));
754 logger_.loglevel( get_config("log/level") );
755 ui_->init_colors();
756 Fl::check();
760 void App::cb_manage_db ()
762 auto q = query("select key, val from aoi");
764 std::map<string,string> mm;
765 for ( size_t i=0; i<q.size(); i+=2 )
766 mm[q[i]] = q[i+1];
768 auto *d = ui_->dlg_manage_db();
769 d->main_version( mm["db_version"]);
770 d->main_created( mm["db_created"]);
771 d->main_ver_jmdict( mm["jmdict_version"]);
772 d->main_ver_kanjidic( mm["kanjidic_version"]);
773 d->main_ver_kradfile( mm["kradfile_version"]);
774 d->main_ver_tatoeba( mm["tatoeba_version"]);
775 d->user_checked_against("WHO KNOWS");
776 d->cb_download( scb_download_db, (void*)this );
777 d->show();
781 void App::load_config ()
783 log("Loading config...");
784 vector<string> res = query("select key,val from config;");
785 for ( size_t i=0; i < res.size(); i+=2 ){
786 set_config( res[i], res[i+1] );
788 apply_config();
792 void App::save_config ( const std::map<string,aoi_config::Config::Value> &newmap)
794 log("Saving config...");
796 typedef std::pair<string,aoi_config::Config::Value> cfg_pair;
798 // merge new and old config
799 for ( const cfg_pair &p: newmap )
800 set_config( p.first, p.second.val );
801 apply_config();
803 // prepare SQL script
804 std::stringstream ss;
805 ss << "BEGIN TRANSACTION;\n";
806 ss << "DROP TABLE IF EXISTS user.config;\n";
807 ss << "CREATE TABLE user.config (key TEXT, val TEXT);\n";
808 for ( const cfg_pair &p: get_config_map() )
809 ss << "INSERT INTO user.config (key,val) VALUES('"
810 << p.first << "','" << p.second.val << "');\n";
811 ss << "END TRANSACTION;\n";
813 query(ss.str().c_str());
815 // apply new fonts and colors
816 init_dicview();
818 // TODO:
819 // check new keys
820 // check keys in oldconfig not present in newmap
821 // set oldkeys
822 // set newkeys
823 // mnoziny...
826 } // namespace aoi