OPT added to MAkefile; KanjiView::Cell constructor: fg,bg changed to int (Fl_Color...
[aoi.git] / src / aoi.cxx
blobf7570c26144bd78e0c43425b7f91d3e53451b0cb
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 //! See App::load_dbscript() and App::App()
30 const char *DB_BACKUP_FILE = "db.backup";
31 const char *LOG_FILE = "aoi.log";
32 const char SENSE_SEARCH_CHAR = ':';
34 App* App::instance_ = nullptr;
36 App::App ()
38 remove(LOG_FILE);
39 logger_.filename(LOG_FILE);
40 logger_.loglevel(Logger::MSG_DEBUG);
42 db_ = nullptr; // NECESSARY ( if db_ undefined then SEGFAULT in open_database() )
43 cfg_ = new aoi_config::Config();
44 ui_ = new aoi_ui::GUI(this);
45 rmn_ = new Romanization();
47 if ( utils::file_exists( DB_BACKUP_FILE ) ){
48 log_w("Backup copy of the database found.");
49 std::stringstream ss;
50 ss << "Backup copy of the database found. Delete it?\n"
51 << "WARNING: You should check whether the current database\n"
52 << "works before deleting the backup.";
53 if ( ui_->choice(ss.str(), "Delete", "No") == 0 ){
54 log("Deleting the backup copy of the database.");
55 remove(DB_BACKUP_FILE);
59 ui_->progress(0,"Opening database...");
60 open_database();
62 ui_->progress( 30, "Checking tables..." );
63 check_tables();
64 ui_->progress( 60, "Checking indexes..." );
65 check_indexes();
67 load_config();
68 init_dicview();
70 #ifdef DEBUG
71 // ui_->progress(90, "DEBUG query..." );
72 parse_dic_input("ana*");
73 // cb_kanji_search();
74 // ui_->cb_toggle_group(nullptr);
75 #endif
77 ui_->progress( 98, "Initializing fonts..." );
78 ui_->fontname_kanji( get_config("font/kanji") );
79 ui_->help_file( get_config("sources/help_index") );
81 ui_->progress_hide();
84 auto q = query("select val from aoi where key='jmdict_version'");
85 string jmdict_version = (q.empty()) ? "NONE":q[0];
86 q = query("select val from aoi where key='kanjidic_version'");
87 string kanjidic_version = q.empty() ? "NONE":q[0];
88 if ( jmdict_version == "NONE" || kanjidic_version == "NONE" )
89 cb_manage_db();
92 #ifdef DEBUG
93 logger_.loglevel(Logger::MSG_DEBUG);
94 #endif
96 instance_ = this;
100 App::~App ()
102 delete db_;
103 delete rmn_;
104 delete ui_;
105 delete cfg_;
109 void App::init_dicview ()
111 // initialize textstyles
112 TextStyle style_default(FL_HELVETICA,1.2);
113 TextStyle style_reading(aoi_ui::FONT_KANJI,1.5);
114 TextStyle style_reading_freq = style_reading;
115 style_reading_freq.color = get_color("frequent");
116 TextStyle style_kanji(aoi_ui::FONT_KANJI,1.7);
117 TextStyle style_kanji_freq = style_kanji;
118 style_kanji_freq.color = get_color("frequent");
119 TextStyle style_inf(FL_HELVETICA_ITALIC,0.8,get_color("pos"));
120 style_inf.offset_y = 3;
121 TextStyle style_pos = style_inf;
122 TextStyle style_misc = style_pos;
123 style_misc.color = get_color("misc");
124 TextStyle style_field = style_pos;
125 style_field.color = get_color("field");
126 TextStyle style_dial = style_pos;
128 // register TextStyles
129 ui_->register_tag_dicview( "default", style_default );
130 ui_->register_tag_dicview( "reading", style_reading );
131 ui_->register_tag_dicview( "reading_freq", style_reading_freq );
132 ui_->register_tag_dicview( "kanji", style_kanji );
133 ui_->register_tag_dicview( "kanji_freq", style_kanji_freq );
134 ui_->register_tag_dicview( "kinf", style_inf );
135 ui_->register_tag_dicview( "rinf", style_inf );
136 ui_->register_tag_dicview( "pos", style_pos );
137 ui_->register_tag_dicview( "misc", style_misc );
138 ui_->register_tag_dicview( "field", style_field );
139 ui_->register_tag_dicview( "dial", style_dial );
143 vector<string> App::query ( const char *q, bool log_query, bool replace_separator )
145 if ( !db_ ){
146 log_e("App::query(): Database does not exist.");
147 return {};
149 ui_->cursor_wait();
150 vector<string> result;
151 try {
152 if ( log_query )
153 log_d(string(q));
154 result = db_->query(q);
156 catch (SQLite3::DatabaseError &e){
157 log_e("App: DatabaseError:: " + string(e.what()) + string("\nQuery: ")
158 + string(e.query()) );
159 ui_->cursor_default();
161 string msg = std::to_string(db_->result_rows()) + " results";
162 log( "App::query(): " + msg);
163 if ( replace_separator )
164 for ( string &s: result )
165 utils::replace_all(s, SEPARATOR_SQL, ", ");
166 ui_->cursor_default();
167 return result;
171 void App::open_database ()
173 try {
174 if ( !db_ )
175 db_ = new SQLite3::SQLite3( get_config("db/file_main").c_str() );
176 else{
177 db_->close();
178 db_->open(get_config("db/file_main").c_str());
181 catch (SQLite3::CantOpenDatabase &e){
182 log_e( "Aoi: Can't open database '" + string(e.what()) + "'.");
183 return;
185 std::stringstream ss;
186 ss << "ATTACH DATABASE '" << get_config("db/file_user") << "'as user;";
187 db_->query(ss.str().c_str());
191 void App::cb_set_components ()
193 if ( curr_components_.empty() )
194 return;
196 using aoi_ui::ComponentView;
198 // prepare cells
199 vector<string> included = ui_->components_include();
200 vector<string> excluded = ui_->components_exclude();
202 vector<ComponentView::Cell> v;
203 for ( string &s: included )
204 v.push_back( ComponentView::Cell(s,ComponentView::CELL_SELECTED_1) );
205 for ( string &s: excluded )
206 v.push_back( ComponentView::Cell(s,ComponentView::CELL_SELECTED_2) );
208 // sort by occurences
209 if ( !ui_->sort_components_by_strokes() ){
210 for ( auto mi = curr_components_.rbegin(); mi!=curr_components_.rend(); ++mi ){
211 if ( !utils::is_in( included, mi->second )
212 && !utils::is_in( excluded, mi->second ) )
213 v.push_back( ComponentView::Cell(mi->second) );
216 // sort by strokes
217 else {
218 vector<string> comps_by_strokes;
219 for ( auto mi: curr_components_ ){
220 if ( mi.first < get_config<int>("knj/min_compo_count") )
221 continue;
222 if ( !utils::is_in( included, mi.second )
223 && !utils::is_in( excluded, mi.second ) )
224 comps_by_strokes.push_back(mi.second);
227 struct SortByStrokes {
228 map<string,int> comps;
229 SortByStrokes( const map<string,int> &c ): comps(c){};
230 bool operator() (const string &c1, const string &c2 ){
231 return ( this->comps[c1] < this->comps[c2] );
233 } sbc(components_);
235 std::sort( comps_by_strokes.begin(), comps_by_strokes.end(), sbc );
237 int prev_strokes=0;
238 for ( string &s: comps_by_strokes ){
239 int curr_strokes = components_[s];
240 if ( prev_strokes != curr_strokes )
241 v.push_back( ComponentView::Cell( std::to_string(curr_strokes),
242 ComponentView::CELL_LABEL) );
243 v.push_back( ComponentView::Cell(s) );
244 prev_strokes = curr_strokes;
248 ui_->set_components( v );
252 void App::cb_kanji_search ()
255 if ( components_.empty() ){
256 log("Loading components...");
257 vector<string> res = query("select component, strokes from components");
258 int skipped = 0;
259 for ( size_t i=0; i<res.size(); i=i+2 ){
260 int strokes = 0;
261 if ( res[i+1].empty() )
262 skipped++;
263 else
264 strokes = std::stoi(res[i+1]);
265 components_[res[i]] = strokes;
267 if ( skipped>0 )
268 log_d(std::to_string(skipped)+" components without strokes data.");
271 // strokes
272 std::pair<int,int> strokes = utils::parse_range( utils::strip( ui_->strokes() ) );
273 // jlpt
274 std::pair<int,int> jlpt = utils::parse_range( utils::strip( ui_->jlpt() ) );
275 // grade
276 std::pair<int,int> grade = utils::parse_range( utils::strip( ui_->grade() ) );
278 // skip
279 vector<string> skip = utils::split_string( ui_->skip() );
280 std::stringstream sskip;
281 if ( skip.size() > 0 ){
282 string skip1 = utils::strip(skip[0].c_str());
283 sskip << " S.skip1=" << skip1;
285 if ( skip.size() > 1 ){
286 std::pair<int,int> skip2 = utils::parse_range( utils::strip(skip[1].c_str()) );
287 sskip << " and S.skip2>=" << skip2.first << " and S.skip2<=" << skip2.second;
289 if ( skip.size() > 2 ){
290 std::pair<int,int> skip3 = utils::parse_range( utils::strip(skip[2].c_str()) );
291 sskip << " and S.skip3>=" << skip3.first << " and S.skip3<=" << skip3.second;
293 if ( !sskip.str().empty() )
294 sskip << " and ";
296 // ordering
297 string order_by;
298 switch ( ui_->sort_mode() ){
299 case SORT_FREQ_ASC:
300 order_by = "(case freq when 0 then 9999 else freq end) asc";
301 break;
302 case SORT_FREQ_DESC:
303 order_by = "(case freq when 0 then 9999 else freq end) desc";
304 break;
305 case SORT_STROKES_ASC:
306 order_by = "strokes asc";
307 break;
308 case SORT_STROKES_DESC:
309 order_by = "strokes desc";
310 break;
313 vector<string> components_include = ui_->components_include();
314 vector<string> components_exclude = ui_->components_exclude();
315 std::stringstream comps;
316 for ( auto c: components_include )
317 comps << " and components like '%" << c << "%'";
318 for ( auto c: components_exclude )
319 comps << " and components not like '%" << c << "%'";
320 printf("%s\n",comps.str().c_str());
322 string jis208 = "";
323 if ( get_config<bool>("knj/jis208_only") )
324 jis208 = " flags glob '*jis208*' and ";
326 // build query
327 std::stringstream ss;
328 ss << "select distinct "
329 << "K.kanji,freq,components,flags "
330 << " from k_kanji as K"
331 << (sskip.str().empty() ? " where ":", k_skip as S where K.kanji=S.kanji and")
332 << jis208
333 << sskip.str()
334 << " strokes>=" << strokes.first << " and strokes<=" << strokes.second
335 << " and jlpt>=" << jlpt.first << " and jlpt<=" << jlpt.second
336 << " and grade>=" << grade.first << " and grade<=" << grade.second
337 << comps.str()
338 << " order by " << order_by;
341 // perform query
342 vector<string> q = query( ss.str().c_str(), true, false );
343 vector<aoi_ui::KanjiView::Cell> data;
344 vector<string> components;
345 set<string> flags;
346 for ( size_t i=0; i<q.size(); i+=4 ){
347 string kanji = q[i];
348 int freq = std::stoi(q[i+1]);
349 data.push_back(
350 aoi_ui::KanjiView::Cell(
351 kanji,
352 (freq>0)? get_color("frequent"):-1
355 components.push_back(q[i+2]);
356 for ( string &s: utils::split_string( q[i+3], SEPARATOR_SQL) )
357 flags.insert(s);
360 log_d("Groups: " + utils::to_string(flags));
362 ui_->set_kanjiview( data );
363 char b[32];
364 sprintf( b, "%d results", db_->result_rows() );
365 ui_->set_kanji_results( b );
367 utils::Histogram<string> histogram;
368 for ( string &s: components )
369 histogram.add( utils::str_to_chars(s.c_str()) );
370 curr_components_ = histogram.sorted();
372 cb_set_components();
376 void App::alert ( const string &msg, const string &desc )
378 std::stringstream ss;
379 ss << msg;
380 if ( !desc.empty() )
381 ss << "\n\nDetails:\n" << desc;
382 log_e(ss);
383 ui_->alert(ss.str());
387 int App::run ( int argc, char **argv )
389 return ui_->run(argc,argv);
393 void App::check_tables ()
395 for ( auto &dbit: aoi_config::db_tables) {
396 // no need for vacuum here, it will be done by check_indexes()
397 log("Checking tables in database: " + string(dbit.first));
398 std::stringstream q;
399 q << "SELECT name FROM " << dbit.first << "." << "sqlite_master WHERE type='table'";
400 vector<string> existing = query(q.str().c_str());
401 // CREATE TABLE name ( column1 TYPE, column2 TYPE, ... )
402 for ( auto &table: dbit.second ){
403 if ( utils::is_in(existing, string(table.first)) )
404 continue;
405 log("Creating table " + string(table.first));
406 vector<string> v;
407 for ( auto &column: table.second ){
408 std::stringstream sstr;
409 sstr << column.name << " " << column.type;
410 v.push_back(sstr.str());
412 std::stringstream ss;
413 ss << "CREATE TABLE " << dbit.first << "." << table.first
414 << " (" << to_string(v,",") << ");\n";
415 query(ss.str().c_str());
417 log("Check done.");
422 void App::check_indexes ()
424 bool do_vacuum = false;
425 log("Checking indexes...");
426 for ( auto &dbit: aoi_config::db_tables ) {
427 std::stringstream q;
428 q << "SELECT name FROM " << dbit.first << "." << "sqlite_master WHERE type='index'";
429 vector<string> existing = query(q.str().c_str());
430 // CREATE INDEX idx_table_column ON table ( column ASC )
431 for ( auto &mi: dbit.second ){ // tables
432 for ( auto &c: mi.second ){ // columns
433 std::stringstream idx_name;
434 idx_name << "idx_" << mi.first << "_" << c.name;
435 if ( c.index && !utils::is_in( existing, idx_name.str() ) ){
436 log(string("Creating index ") + idx_name.str());
437 std::stringstream ss;
438 ss << "CREATE INDEX " << idx_name.str() << " ON " << mi.first
439 << "(" << c.name << " " << c.sort << ")";
440 query(ss.str().c_str());
441 do_vacuum = true;
446 if ( do_vacuum )
447 query("VACUUM");
448 log("Check done.");
452 void App::load_dbscript ( const char *fname )
454 const char *TEMP_DBSCRIPT = "dbscript.temp";
455 const char *TEMP_DATABASE = "temp.db";
456 const int LINE_BUFFER = 4096;
458 // hide manage db_dialog if visible
459 auto *d = ui_->dlg_manage_db();
460 if ( d && d->visible() )
461 d->hide();
463 log("Loading dbscript: "+string(fname));
465 log("Decompressing file (if necessary)...");
466 utils::gzip_decompress_file( fname, TEMP_DBSCRIPT);
468 log("Opening temporary database.");
469 remove(TEMP_DATABASE);
470 SQLite3::SQLite3 db(TEMP_DATABASE);
472 log("Loading...");
473 std::ifstream f;
474 char line[LINE_BUFFER];
475 f.open(TEMP_DBSCRIPT);
476 // n lines should be > 2e6 -> percent=n*2e6/100
477 size_t n = 0;
478 while ( f.good() ){
479 if ( n % 5000 == 0 ){
480 char b[64];
481 snprintf( b, 64, "Loading line: %d", n);
482 ui_->progress(n/float(2.5e4), b);
484 f.getline( line, LINE_BUFFER );
485 db.query(line);
486 n++;
488 f.close();
490 log("Closing temporary database...");
491 db.close();
492 remove(TEMP_DBSCRIPT);
494 log("Closing old database.");
495 db_->close();
497 string dbfile = get_config("db/file_main");
499 log("Renaming old database to main.db.bckp.");
500 rename( dbfile.c_str(), DB_BACKUP_FILE );
502 log("Renaming new database.");
503 rename( TEMP_DATABASE, dbfile.c_str() );
505 log("Switching to the new database");
506 open_database();
508 ui_->progress(90, "Checking tables...");
509 check_tables();
510 ui_->progress(95, "Checking indexes...");
511 check_indexes();
513 ui_->progress_hide();
514 // show informations about db
515 cb_manage_db();
519 void App::cb_dic_input ()
521 parse_dic_input( ui_->get_dic_input() );
522 ui_->reset_filters();
526 //void App::on_dic_selected ( int id )
528 // log_d("App::on_dic_selected()");
532 void App::cb_download_db ()
534 log_d("Download DB");
535 string path = ui_->download_dialog( get_config("sources/url_database") );
536 if ( !path.empty() && !utils::file_exists( path ) ){
537 log_w("App::cb_download_db(): Not a file: "+path);
538 return;
541 log("Closing old database.");
542 db_->close();
544 string dbfile = get_config("db/file_main");
546 log("Renaming old database to main.db.bckp.");
547 rename( dbfile.c_str(), DB_BACKUP_FILE );
549 log("Decompressing downloaded file");
550 utils::gzip_decompress_file( path.c_str(), dbfile.c_str() );
552 log("Switching to the new database");
553 open_database();
555 ui_->progress(10, "Checking tables...");
556 check_tables();
557 ui_->progress(50, "Checking indexes...");
558 check_indexes();
560 remove( path.c_str() );
561 ui_->progress_hide();
562 // show informations about db
563 cb_manage_db();
567 void App::cb_edit_word ()
569 std::stringstream ss;
570 int id = ui_->dicview_selected_rowid();
571 ss << "App::edit_word( " << id << " )";
572 log_d(ss);
573 ui_->edit_word( db_get_word(id));
577 void App::cb_popup_kanji ( const string &kanji )
579 std::stringstream ss;
580 ss << "App::on_kanji_clicked()" << kanji;
581 Kanji k = db_get_kanji(kanji);
582 ui_->popup_kanji( k );
583 // XXX: ? this should be somewhere else (it is not logical here)
584 ui_->highlight_components( k.components() );
585 log(ss);
589 void App::set_listview ( const vector<string> &v )
591 if ( !listview_items_.empty() ) listview_items_.clear();
592 vector<string> d;
593 vector<int> cell_ids;
594 size_t i = 0;
595 while ( i < v.size() ) {
596 int cell_id = std::stoi(v[i]); // jmdict id
597 cell_ids.push_back(cell_id);
598 cell_ids.push_back(cell_id);
599 cell_ids.push_back(cell_id);
600 // pos
601 set<string> pos;
602 for ( string &elt: utils::split_string( v[i+1], ",") )
603 if ( elt.size() > 0 )
604 pos.insert(utils::strip(elt.c_str()));
605 d.push_back( v[i+2] ); // reading
606 d.push_back( v[i+3] ); // kanji
607 d.push_back( v[i+4] ); // sense
608 listview_items_.push_back( {cell_id, pos, v[i+2], v[i+3], v[i+4]} );
609 i += 5;
611 char buff[32];
612 sprintf( buff, "%d results", db_->result_rows() );
613 ui_->set_dic_results( buff );
614 ui_->set_listview(d,cell_ids);
618 void App::parse_dic_input ( const char *str )
620 log_d(string("App::parse_dic_input: \"") +string(str) + string("\""));
622 string stripped = utils::strip(str);
624 if ( stripped.empty() )
625 return;
627 const char *s = stripped.c_str();
629 string qq;
630 if ( s[0] != SENSE_SEARCH_CHAR ){
631 DictionaryInputParser p;
632 qq = p.parse(s);
634 if ( p.warning() ){
635 int res = ui_->choice(
636 "Too broad search.\n"\
637 "Your computer may become unresponsible for a long time.\n"\
638 "Proceed?",
639 "Proceed",
640 "Cancel"
642 if ( res == 1 ) // Cancel
643 return;
646 // append * at the end of the simple string (just text and nothing else)
647 if ( !strchr(s,'*') && !strchr(s,'?') && !strchr(s,'[') && !strchr(s,'{')
648 && !strchr(s,'(') )
649 qq += "*";
652 std::stringstream q;
653 if ( s[0] == SENSE_SEARCH_CHAR ){
654 q << "select did,"
655 << "group_concat(pos) as pos,"
656 << q_reading("d_sense.did")
657 << q_kanji("d_sense.did")
658 << q_sense()
659 << " from d_sense where gloss glob '*" << stripped.substr(1) << "*'"
660 << " group by did";
662 else if ( rmn_->contains_kanji( qq.c_str() ) ) {
663 q << "select d_kanji.did as did,"
664 << "(select group_concat(pos) from d_sense where d_sense.did = d_kanji.did) as pos,"
665 << q_kanji()
666 << q_reading("d_kanji.did")
667 << q_sense("d_kanji.did")
668 << "from d_kanji where "
669 << "kanji glob '" << rmn_->romaji_to_hiragana(qq.c_str()) << "' "
670 << " or kanji glob '" << rmn_->romaji_to_katakana(qq.c_str()) << "' "
671 << " group by did order by d_kanji.freq desc, d_kanji.kanji asc";
673 else {
674 q << "select d_reading.did as did,"
675 << "(select group_concat(pos) from d_sense where d_sense.did = d_reading.did) as pos,"
676 << q_reading()
677 << q_kanji("d_reading.did")
678 << q_sense("d_reading.did")
679 << "from d_reading where "
680 << "reading glob '" << rmn_->romaji_to_hiragana(qq.c_str())
681 << "' or reading glob '"<< rmn_->romaji_to_katakana(qq.c_str()) << "' "
682 << " group by did order by d_reading.freq desc, d_reading.reading asc";
684 set_listview(query(q.str().c_str()));
688 void App::cb_examples ()
690 log_d("cb_examples():" + std::to_string(ui_->dicview_selected_rowid()));
691 DicWord w = db_get_word( ui_->dicview_selected_rowid() );
692 if ( w.k_ele().empty() )
693 return;
694 std::stringstream q;
695 // returns: japanese sentence, english sentence, string to be highlighted
696 q << "select"
697 << " (select text from sentences where id=sid) as jp,"
698 << " (select text from sentences where id=mid) as en,"
699 << " good_example,"
700 << " form"
701 << " from indices where headword='" << w.k_ele()[0].kanji()
702 << "' order by good_example desc;";
703 vector<string> res = query(q.str().c_str());
704 std::stringstream ss;
705 for ( size_t i=0; i<res.size(); i+=4 ){
706 bool good_example = std::stoi(res[i+2]);
707 ss << "&nbsp;<br><font face=\"symbol\"";
708 if ( good_example )
709 ss << " size=\"5\" color=\"blue\"";
710 ss << ">" << res[i] << "</font><br>";
711 if ( !res[i+1].empty() )
712 ss << res[i+1] << "<br>";
713 ss << "&nbsp;<br>";
714 // form = res[i+3]
716 ui_->show_html(ss.str());
720 DicWord App::db_get_word ( int id )
722 DicWord w(id);
723 std::stringstream ss;
725 // kanji
726 ss << "select kid,kanji,inf,freq from d_kanji where did=" << id <<";";
727 vector<string> res = query( ss.str().c_str() );
728 for ( size_t i=0; i<res.size(); i+=4 ){
729 int kid = std::stoi(res[i]);
730 string kanji = res[i+1];
731 vector<string> inf = utils::split_string(res[i+2],SEPARATOR_SQL);
732 bool freq = std::stoi(res[i+3]);
733 w.k_ele( ElementKanji( kid, kanji, inf, freq ) );
736 // reading
737 ss.str("");
738 ss.clear();
739 ss << "select rid,reading,inf,nokanji,freq from d_reading where did=" << id << ";";
740 res = query( ss.str().c_str() );
741 for ( size_t i=0; i<res.size(); i+=5 ){
742 int rid = std::stoi(res[i]);
743 string reading = res[i+1];
744 vector<string> inf = utils::split_string(res[i+2],SEPARATOR_SQL);
745 bool nokanji = std::stoi(res[i+3]);
746 bool freq = std::stoi(res[i+4]);
747 w.r_ele( ElementReading( rid, reading, nokanji, {/*restr*/}, inf, freq ) );
750 // sense
751 ss.str("");
752 ss.clear();
753 ss << "select sid,gloss,xref,ant,inf,pos,field,misc,dial from d_sense where did=" << id << ";";
754 res = query( ss.str().c_str() );
755 for ( size_t i=0; i<res.size(); i+=9 ){
756 int sid = std::stoi(res[i]);
757 vector<string> gloss = utils::split_string(res[i+1],SEPARATOR_SQL);
758 vector<string> xref = utils::split_string(res[i+2],SEPARATOR_SQL);
759 vector<string> ant = utils::split_string(res[i+3],SEPARATOR_SQL);
760 vector<string> inf = utils::split_string(res[i+4],SEPARATOR_SQL);
761 vector<string> pos = utils::split_string(res[i+5],SEPARATOR_SQL);
762 vector<string> field = utils::split_string(res[i+6],SEPARATOR_SQL);
763 vector<string> misc = utils::split_string(res[i+7],SEPARATOR_SQL);
764 vector<string> dial = utils::split_string(res[i+8],SEPARATOR_SQL);
765 w.s_ele( ElementSense( sid, gloss, {/*stagk*/}, {/*stagr*/}, pos, xref,
766 ant, field, misc, dial, inf) );
769 return w;
773 Kanji App::db_get_kanji ( const string &kanji )
775 log("App::db_get_kanji()");
776 std::stringstream q;
777 q << "select kanji,strokes,ucs, rad_classic, rad_nelson, "
778 << "jlpt, grade, freq, onyomi, kunyomi, nanori, meaning,flags,components "
779 << "from k_kanji where kanji='" << kanji << "';";
781 vector<string> res = query(q.str().c_str());
782 Kanji kk(res[0]);
783 kk.strokes(std::stoi(res[1]));
784 kk.ucs(res[2]);
785 kk.rad_classic(std::stoi(res[3]));
786 kk.rad_nelson( (res[4].empty()) ? -1:std::stoi(res[4]));
787 kk.jlpt(std::stoi(res[5]));
788 kk.grade(std::stoi(res[6]));
789 kk.freq(std::stoi(res[7]));
790 kk.onyomi( utils::split_string(res[8],SEPARATOR_SQL) );
791 kk.kunyomi( utils::split_string(res[9],SEPARATOR_SQL) );
792 kk.nanori( utils::split_string(res[10],SEPARATOR_SQL) );
793 kk.meaning( utils::split_string(res[11],SEPARATOR_SQL) );
794 kk.flags( utils::split_string(res[12],SEPARATOR_SQL) );
795 kk.components( res[13] );
797 string qq = "select skip1,skip2,skip3,misclass from k_skip where kanji='"
798 + kanji + "';";
799 vector<string> res2 = query(qq.c_str());
800 if ( res2.size() % 4 != 0 ){
801 std::stringstream ss;
802 ss << "Wrong SKIP count. Kanji: " << kanji
803 << "Query result size: " << res2.size()
804 << " (should be 4,8 or 12). SKIP not loaded.";
805 log_e(ss);
806 return kk;
808 for ( size_t i=0; i < res2.size(); i+=4 )
809 kk.skip( res2[i], res2[i+1], res2[i+2], res2[i+3] );
810 return kk;
814 void App::cb_filter_listview ()
816 vector<string> pos = ui_->listview_filters();
817 log_d("App::cb_filter_listview(): pos: " + utils::to_string(pos));
818 bool filter_expr = ( utils::is_in( pos, string("expr") ) );
819 bool filter_noun = ( utils::is_in( pos, string("noun") ) );
820 bool filter_verb = ( utils::is_in( pos, string("verb") ) );
821 bool filter_adj = ( utils::is_in( pos, string("adj") ) );
822 vector<string> data;
823 vector<int> ids;
824 size_t n = 0;
825 for ( auto &elt: listview_items_ ){
826 auto start = elt.pos.begin();
827 auto end = elt.pos.end();
828 if ( filter_expr && std::find( start, end, "exp") == end )
829 continue;
830 if ( filter_noun && std::find( start, end, "n" ) == end )
831 continue;
832 if ( filter_adj && std::find_if( start, end,
833 [](const string &s){ return strncmp(s.c_str(),"adj",3)==0;} ) == end )
834 continue;
835 if ( filter_verb && std::find_if( start, end,
836 [](const string &s){ return strncmp(s.c_str(),"v",1)==0;} ) == end )
837 continue;
838 ids.push_back( elt.did );
839 ids.push_back( elt.did );
840 ids.push_back( elt.did );
841 data.push_back ( elt.reading );
842 data.push_back ( elt.kanji );
843 data.push_back ( elt.sense );
844 n++;
846 ui_->set_listview( data, ids );
847 std::stringstream ss;
848 ss << n << " results";
849 if ( listview_items_.size() != n )
850 ss << " (" << listview_items_.size()-n << " hidden)";
851 log(ss.str());
852 ui_->set_dic_results( ss.str() );
856 void App::cb_dicview_rightclick ( int did )
858 string q = "select group_concat(kanji,'') from d_kanji where did="
859 + std::to_string(did);
860 vector<string> res = query( q.c_str() );
861 set<string> kanji;
862 for ( string &c: utils::str_to_chars(res[0].c_str()) )
863 if ( App::get()->rmn()->is_kanji(c.c_str()) )
864 kanji.insert(c);
865 ui_->dicview_menu( did, vector<string>(kanji.begin(),kanji.end()), false, true );
869 void App::apply_config ()
871 ui_->font_base_size( get_config<int>("font/base_size"));
872 logger_.loglevel( get_config("log/level") );
873 ui_->init_colors();
874 Fl::check();
878 void App::cb_manage_db ()
880 auto q = query("select key, val from aoi");
882 std::map<string,string> mm;
883 for ( size_t i=0; i<q.size(); i+=2 )
884 mm[q[i]] = q[i+1];
886 auto *d = ui_->dlg_manage_db();
887 d->main_version( mm["db_version"]);
888 d->main_created( mm["db_created"]);
889 d->main_ver_jmdict( mm["jmdict_version"]);
890 d->main_ver_kanjidic( mm["kanjidic_version"]);
891 d->main_ver_kradfile( mm["kradfile_version"]);
892 d->main_ver_tatoeba( mm["tatoeba_version"]);
893 d->user_checked_against("UNKNOWN");
894 d->cb_download( scb_download_db, (void*)this );
895 d->show();
899 void App::load_config ()
901 log("Loading config...");
902 vector<string> res = query("select key,val from config;");
903 for ( size_t i=0; i < res.size(); i+=2 ){
904 set_config( res[i], res[i+1] );
906 apply_config();
910 void App::save_config ( const std::map<string,aoi_config::Config::Value> &newmap)
912 log("Saving config...");
914 typedef std::pair<string,aoi_config::Config::Value> cfg_pair;
916 // merge new and old config
917 for ( const cfg_pair &p: newmap )
918 set_config( p.first, p.second.val );
919 apply_config();
921 // prepare SQL script
922 std::stringstream ss;
923 ss << "BEGIN TRANSACTION;\n";
924 ss << "DROP TABLE IF EXISTS user.config;\n";
925 ss << "CREATE TABLE user.config (key TEXT, val TEXT);\n";
926 for ( const cfg_pair &p: get_config_map() )
927 ss << "INSERT INTO user.config (key,val) VALUES('"
928 << p.first << "','" << p.second.val << "');\n";
929 ss << "END TRANSACTION;\n";
931 query(ss.str().c_str());
933 // apply new fonts and colors
934 init_dicview();
938 //////////////////////////////////////////////////////////////////////////
939 // DictionaryInputParser
941 set<string> DictionaryInputParser::intersection ( const string &query,
942 const set<string> &current )
944 set<string> characters;
945 // all characters in result
946 for ( string &r: App::get()->query(query.c_str()) ) {
947 for ( string &c: utils::str_to_chars(r.c_str()) ){
948 if ( App::get()->rmn()->is_kanji(c.c_str()) )
949 characters.insert(c);
952 if ( current.empty() )
953 return characters;
954 // intersection
955 vector<string> tmp(current.size());
956 std::set_intersection(
957 current.begin(), current.end(),
958 characters.begin(), characters.end(),
959 tmp.begin()
961 set<string> newset;
962 newset.insert( tmp.begin(), tmp.end() );
963 return newset;
967 string DictionaryInputParser::find_kanji ( const string &s, bool from_words )
969 set<string> results;
970 string q;
971 for ( string &w: utils::split_string(s,",") ) {
972 // search SKIP
973 if ( w.size()==1 && utils::isint(w.c_str()) ){
974 int i = std::stoi(w);
975 if ( i > 0 && i < 5 )
976 q = "select group_concat(kanji,'') from k_skip where skip1=" + w + ";";
978 else {
979 // search readings
980 if ( from_words ) {
981 q = "select group_concat("\
982 "(select group_concat(kanji,'') from d_kanji where d_reading.did=d_kanji.did)"\
983 ",'') from d_reading where reading='" + App::get()->rmn()->romaji_to_hiragana(w)
984 + "';";
986 // search yomi
987 else{
988 q = "select group_concat(kanji,'') from k_kanji where "\
989 "kunyomi glob '*" + App::get()->rmn()->romaji_to_hiragana(w) +
990 "*' or onyomi glob '*" + App::get()->rmn()->romaji_to_katakana(w) + "*'";
993 results = intersection( q, results);
995 return utils::to_string(results,"");
999 void DictionaryInputParser::check_warning ( const string &s, const string &previous )
1001 size_t warning_limit = App::get()->get_config<size_t>("dic/input_parser_warning");
1002 size_t n = utils::str_to_chars(s.c_str()).size();
1003 if ( warning_ || n <= warning_limit )
1004 return;
1006 bool is_wildchar = true;
1007 for ( string &c: utils::str_to_chars(previous.c_str()) ){
1008 if ( c != "?" && c != "*" ){
1009 is_wildchar = false;
1010 break;
1013 warning_ = is_wildchar;
1017 string DictionaryInputParser::parse ( const char *s )
1019 // initialize
1020 type_ = STRING;
1021 buffer_.str("");
1022 parts_.clear();
1023 warning_ = false;
1025 // scan string
1026 size_t i = 0;
1027 while ( i < strlen(s) ){
1028 switch( s[i] ){
1029 case '[': add(); type_ = WORD; break;
1030 case '(': add(); type_ = KANJI; break;
1031 case ']':
1032 case ')': add(); type_ = STRING; break;
1033 default:
1034 buffer_ << s[i];
1036 i++;
1038 add();
1040 std::stringstream output;
1041 for ( size_t j=0; j < parts_.size(); ++j ){
1042 auto p = parts_[j];
1043 switch ( p.second ){
1044 case WORD:
1046 string r = find_kanji(p.first,true);
1047 check_warning( r, (j==0) ? "":parts_[j-1].first );
1048 output << "[" << r << "]";
1049 break;
1051 case KANJI:
1053 string r = find_kanji(p.first,false);
1054 check_warning( r, (j==0) ? "":parts_[j-1].first );
1055 output << "[" << r << "]";
1056 break;
1058 default:
1059 output << p.first;
1063 string ret = output.str();
1064 utils::replace_all(ret, "{", "[");
1065 utils::replace_all(ret, "}", "]");
1066 return ret;
1069 } // namespace aoi