added searching en->jp
[aoi.git] / src / aoi.cxx
blobb60a79d75999864a9d7ab667a17c6ddebcb2afd9
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 *MANAGEDB_ITEM_KANJIDIC = "Kanjidic";
30 const char *MANAGEDB_ITEM_JMDICT = "JMdict";
31 const char *MANAGEDB_ITEM_COMPONENTS = "Components";
32 const char *TEMP_JMDICT = "gztemp.jmdict";
33 const char *TEMP_KANJIDIC = "gztemp.kanjidic";
34 const char *TEMP_COMPONENTS = "gztemp.components";
35 const char *LOG_FILE = "aoi.log";
36 const char SENSE_SEARCH_CHAR = ':';
38 App* App::instance_ = nullptr;
40 App::App ()
42 remove(LOG_FILE);
43 logger_.filename(LOG_FILE);
44 logger_.loglevel(Logger::MSG_DEBUG);
46 cfg_ = new aoi_config::Config();
47 ui_ = new aoi_ui::GUI(this);
48 rmn_ = new Romanization();
50 ui_->progress(0,"Opening database...");
52 try {
53 db_ = new SQLite3::SQLite3( cfg_->get<string>("db/file_main").c_str() );
55 catch (SQLite3::CantOpenDatabase &e){
56 log_e( "Aoi: Can't open database '" + string(e.what()) + "'.");
59 ui_->progress( 30, "Checking tables..." );
60 check_tables();
61 ui_->progress( 60, "Checking indexes..." );
62 check_indexes();
64 load_config();
65 init_dicview();
67 #ifdef DEBUG
68 ui_->progress(90, "DEBUG query..." );
69 // parse_dic_input("ana*");
70 // cb_kanji_search();
71 // ui_->cb_toggle_group(nullptr);
72 #endif
74 ui_->progress( 98, "Initializing fonts..." );
75 ui_->fontname_kanji( cfg_->get("font/kanji") );
76 ui_->help_file( cfg_->get("sources/help_index") );
78 ui_->progress_hide();
81 auto q = query("select val from aoi where key='jmdict_version'");
82 string jmdict_version = (q.empty()) ? "NONE":q[0];
83 q = query("select val from aoi where key='kanjidic_version'");
84 string kanjidic_version = q.empty() ? "NONE":q[0];
85 if ( jmdict_version == "NONE" || kanjidic_version == "NONE" )
86 cb_manage_db();
89 #ifdef DEBUG
90 logger_.loglevel(Logger::MSG_DEBUG);
91 #endif
93 instance_ = this;
97 App::~App ()
99 delete db_;
100 delete rmn_;
101 delete ui_;
102 delete cfg_;
106 void App::init_dicview ()
108 // initialize textstyles
109 TextStyle style_default(FL_HELVETICA,1.2);
110 TextStyle style_reading(aoi_ui::FONT_KANJI,1.5);
111 TextStyle style_reading_freq = style_reading;
112 style_reading_freq.color = get_config<int>("color/frequent");
113 TextStyle style_kanji(aoi_ui::FONT_KANJI,1.7);
114 TextStyle style_kanji_freq = style_kanji;
115 style_kanji_freq.color = get_config<int>("color/frequent");
116 TextStyle style_inf(FL_HELVETICA_ITALIC,0.8,get_config<int>("color/pos"));
117 style_inf.offset_y = 3;
118 TextStyle style_pos = style_inf;
119 TextStyle style_misc = style_pos;
120 style_misc.color = get_config<int>("color/misc");
121 TextStyle style_field = style_pos;
122 style_field.color = get_config<int>("color/field");
123 TextStyle style_dial = style_pos;
125 // register TextStyles
126 ui_->register_tag_dicview( "default", style_default );
127 ui_->register_tag_dicview( "reading", style_reading );
128 ui_->register_tag_dicview( "reading_freq", style_reading_freq );
129 ui_->register_tag_dicview( "kanji", style_kanji );
130 ui_->register_tag_dicview( "kanji_freq", style_kanji_freq );
131 ui_->register_tag_dicview( "kinf", style_inf );
132 ui_->register_tag_dicview( "rinf", style_inf );
133 ui_->register_tag_dicview( "pos", style_pos );
134 ui_->register_tag_dicview( "misc", style_misc );
135 ui_->register_tag_dicview( "field", style_field );
136 ui_->register_tag_dicview( "dial", style_dial );
140 vector<string> App::query ( const char *q, bool log_query, bool replace_separator )
142 if ( !db_ ){
143 log_e("App::query(): Database does not exist.");
144 return {};
146 vector<string> result;
147 try {
148 if ( log_query )
149 log_d(string(q));
150 result = db_->query(q);
152 catch (SQLite3::DatabaseError &e){
153 log_e("App: DatabaseError:: " + string(e.what()) + string("\nQuery: ")
154 + string(e.query()) );
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, parsers::SEPARATOR_SQL, ", ");
161 return result;
165 void App::cb_set_components ()
167 if ( curr_components_.empty() )
168 return;
170 using aoi_ui::ComponentView;
172 // prepare cells
173 vector<string> included = ui_->components_include();
174 vector<string> excluded = ui_->components_exclude();
176 vector<ComponentView::Cell> v;
177 for ( string &s: included )
178 v.push_back( ComponentView::Cell(s,ComponentView::CELL_SELECTED_1) );
179 for ( string &s: excluded )
180 v.push_back( ComponentView::Cell(s,ComponentView::CELL_SELECTED_2) );
182 // sort by occurences
183 if ( !ui_->sort_components_by_strokes() ){
184 for ( auto mi = curr_components_.rbegin(); mi!=curr_components_.rend(); ++mi ){
185 if ( !utils::is_in( included, mi->second )
186 && !utils::is_in( excluded, mi->second ) )
187 v.push_back( ComponentView::Cell(mi->second) );
190 // sort by strokes
191 else {
192 vector<string> comps_by_strokes;
193 for ( auto mi: curr_components_ ){
194 if ( mi.first < get_config<int>("knj/min_compo_count") )
195 continue;
196 comps_by_strokes.push_back(mi.second);
199 struct SortByStrokes {
200 map<string,int> comps;
201 SortByStrokes( const map<string,int> &c ): comps(c){};
202 bool operator() (const string &c1, const string &c2 ){
203 return ( this->comps[c1] < this->comps[c2] );
205 } sbc(components_);
207 std::sort( comps_by_strokes.begin(), comps_by_strokes.end(), sbc );
209 int prev_strokes=0;
210 for ( string &s: comps_by_strokes ){
211 int curr_strokes = components_[s];
212 if ( prev_strokes != curr_strokes )
213 v.push_back( ComponentView::Cell( std::to_string(curr_strokes),
214 ComponentView::CELL_LABEL) );
215 v.push_back( ComponentView::Cell(s) );
216 prev_strokes = curr_strokes;
220 ui_->set_components( v );
224 void App::cb_kanji_search ()
227 if ( components_.empty() ){
228 log("Loading components...");
229 vector<string> res = query("select component, strokes from components");
230 for ( size_t i=0; i<res.size(); i=i+2 ){
231 components_[res[i]] = std::stoi(res[i+1]);
235 // strokes
236 std::pair<int,int> strokes = utils::parse_range( utils::strip( ui_->strokes() ) );
237 // jlpt
238 std::pair<int,int> jlpt = utils::parse_range( utils::strip( ui_->jlpt() ) );
239 // grade
240 std::pair<int,int> grade = utils::parse_range( utils::strip( ui_->grade() ) );
244 // skip
245 vector<string> skip = utils::split_string( ui_->skip() );
246 std::stringstream sskip;
247 if ( skip.size() > 0 ){
248 string skip1 = utils::strip(skip[0].c_str());
249 sskip << " S.skip1=" << skip1;
251 if ( skip.size() > 1 ){
252 std::pair<int,int> skip2 = utils::parse_range( utils::strip(skip[1].c_str()) );
253 sskip << " and S.skip2>=" << skip2.first << " and S.skip2<=" << skip2.second;
255 if ( skip.size() > 2 ){
256 std::pair<int,int> skip3 = utils::parse_range( utils::strip(skip[2].c_str()) );
257 sskip << " and S.skip3>=" << skip3.first << " and S.skip3<=" << skip3.second;
259 if ( !sskip.str().empty() )
260 sskip << " and ";
262 // ordering
263 string order_by;
264 switch ( ui_->sort_mode() ){
265 case SORT_FREQ_ASC:
266 order_by = "(case freq when 0 then 9999 else freq end) asc";
267 break;
268 case SORT_FREQ_DESC:
269 order_by = "(case freq when 0 then 9999 else freq end) desc";
270 break;
271 case SORT_STROKES_ASC:
272 order_by = "strokes asc";
273 break;
274 case SORT_STROKES_DESC:
275 order_by = "strokes desc";
276 break;
279 vector<string> components_include = ui_->components_include();
280 vector<string> components_exclude = ui_->components_exclude();
281 std::stringstream comps;
282 for ( auto c: components_include )
283 comps << " and components like '%" << c << "%'";
284 for ( auto c: components_exclude )
285 comps << " and components not like '%" << c << "%'";
286 printf("%s\n",comps.str().c_str());
288 string jis208 = "";
289 if ( get_config<bool>("knj/jis208_only") )
290 jis208 = " flags glob '*jis208*' and ";
292 // build query
293 std::stringstream ss;
294 ss << "select distinct "
295 << "K.kanji,freq,components,flags "
296 << " from k_kanji as K, k_skip as S where K.kanji=S.kanji and "
297 << jis208
298 << sskip.str()
299 << " strokes>=" << strokes.first << " and strokes<=" << strokes.second
300 << " and jlpt>=" << jlpt.first << " and jlpt<=" << jlpt.second
301 << " and grade>=" << grade.first << " and grade<=" << grade.second
302 << comps.str()
303 << " order by " << order_by;
306 // perform query
307 vector<string> q = query( ss.str().c_str(), true, false );
308 vector<aoi_ui::KanjiView::Cell> data;
309 vector<string> components;
310 set<string> flags;
311 for ( size_t i=0; i<q.size(); i+=4 ){
312 string kanji = q[i];
313 int freq = std::stoi(q[i+1]);
314 data.push_back(
315 aoi_ui::KanjiView::Cell(
316 kanji,
317 (freq>0)? get_config<int>("color/frequent"):-1
320 components.push_back(q[i+2]);
321 for ( string &s: utils::split_string( q[i+3], parsers::SEPARATOR_SQL) )
322 flags.insert(s);
325 log_d("Groups: " + utils::to_string(flags));
327 ui_->set_kanjiview( data );
328 char b[32];
329 sprintf( b, "%d results", db_->result_rows() );
330 ui_->set_kanji_results( b );
332 utils::Histogram<string> histogram;
333 for ( string &s: components )
334 histogram.add( utils::str_to_chars(s.c_str()) );
335 curr_components_ = histogram.sorted();
337 cb_set_components();
341 void App::alert ( const string &msg, const string &desc )
343 std::stringstream ss;
344 ss << msg;
345 if ( !desc.empty() )
346 ss << "\n\nDetails:\n" << desc;
347 log_e(ss);
348 ui_->alert(ss.str());
352 int App::run ( int argc, char **argv )
354 return ui_->run(argc,argv);
358 void App::parse_kanjidic ( const char *fname, bool delete_source )
360 if ( !utils::file_exists( fname ) ){
361 log_e("App::parse_kanjidic(): File does not exist: " + string(fname));
362 return;
364 ui_->progress(0, "Loading kanjidic...");
365 log("Loading kanjidic...");
367 const char *SEP = parsers::SEPARATOR_SQL;
369 int n_kanji = 0;
370 try {
371 ui_->progress(1, "Creating parser...");
372 std::stringstream msg;
373 msg << "Decompressing file: " << fname << " -> " << TEMP_KANJIDIC;
374 log_d(msg);
376 #ifdef WINDOWS
377 log_d("Waiting 3 s before decompressing...");
378 usleep(3000);
379 log_d("Continue...");
380 #endif
382 utils::gzip_decompress_file( fname, TEMP_KANJIDIC);
383 parsers::KanjidicParser p(TEMP_KANJIDIC);
384 auto kanji = p.get_entry();
386 query( "DROP TABLE IF EXISTS k_kanji;"
387 "DROP TABLE IF EXISTS components;"
388 "DROP TABLE IF EXISTS k_skip;"
389 "DELETE FROM aoi WHERE key='kanjidic_version';" );
391 check_tables();
393 std::stringstream ss;
394 ss << "BEGIN TRANSACTION;\n";
395 ss << "INSERT INTO aoi (key,val) VALUES ('kanjidic_version','"
396 << p.get_version() << "');\n";
397 while ( kanji.kanji() != "" ){
398 n_kanji++;
399 if ( n_kanji % 100 == 0)
400 // percent = 100 * n_kanji/13108
401 ui_->progress( n_kanji/14, std::to_string(n_kanji)+string(" kanji loaded"));
402 ss << "INSERT INTO k_kanji "
403 << "(kanji,ucs,onyomi,kunyomi,meaning,nanori,flags,jlpt,grade,freq,strokes,"
404 << "rad_classic,rad_nelson,components)"
405 << " VALUES('"
406 << kanji.kanji() << "','"
407 << kanji.ucs() << "','"
408 << to_string(kanji.onyomi(),SEP) << "','"
409 << to_string(kanji.kunyomi(),SEP) << "','"
410 << SQLite3::escape(to_string(kanji.meaning(),SEP)) << "','"
411 << to_string(kanji.nanori(),SEP) << "','"
412 << to_string(kanji.flags(),SEP) << "',"
413 << kanji.jlpt() << ","
414 << kanji.grade() << ","
415 << kanji.freq() << ","
416 << kanji.strokes() << ","
417 << kanji.rad_classic() << ","
418 << kanji.rad_nelson() << ","
419 << "''"
420 << ");\n";
421 for ( SKIP &s: kanji.skip() ) {
422 ss << "INSERT INTO k_skip (kanji,skip1,skip2,skip3,misclass) VALUES('"
423 << kanji.kanji() << "'," << s.s1 << "," << s.s2 << "," << s.s3
424 << ",'" << s.misclass << "');\n";
426 kanji = p.get_entry();
428 ss << "END TRANSACTION;\n";
429 log("Writing to db ...");
431 // #ifdef DEBUG
432 // log_d("Writing file 'script.kanjidic.sql'...");
433 // std::ofstream f ("script.kanjidic.sql");
434 // f << ss.str();
435 // f.close();
436 // #endif
437 ui_->progress(95, "Writing to database...");
438 query(ss.str().c_str(),false);
440 catch ( utils::ParsingError &e ){
441 ui_->progress_hide();
442 std::string msg = "App::parse_jmdict(): ParsingError: ";
443 msg += e.what();
444 alert(msg);
445 return;
447 ui_->progress(98, "Checking indexes...");
448 check_indexes();
449 ui_->progress_hide();
450 remove( TEMP_KANJIDIC );
451 if ( delete_source )
452 remove( fname );
456 void App::parse_jmdict ( const char *fname, bool delete_source )
458 if ( !utils::file_exists( fname ) ){
459 log_e("App::parse_jmdict(): File does not exist: " + string(fname));
460 return;
463 ui_->progress( 0, "Loading JMdict..." );
464 log("Loading JMdict...");
465 std::stringstream ss;
466 ss << "BEGIN TRANSACTION;\n";
468 // tables
469 for ( auto &mi: aoi_config::db_tables ){
470 ss << "DROP TABLE IF EXISTS " << mi.first << ";\n"
471 << "CREATE TABLE " << mi.first << " ( ";
472 for ( size_t i=0; i<mi.second.size(); i++ )
473 ss << mi.second[i].name << " " << mi.second[i].type
474 << ((i==mi.second.size()-1) ? "":",");
475 ss << ");\n";
478 // create parser
479 int n_entries = 0;
480 try {
481 ui_->progress(1, "Creating parser...");
483 utils::gzip_decompress_file( fname, TEMP_JMDICT);
484 parsers::JmdictParser jmp(TEMP_JMDICT);
485 const char *SEP = parsers::SEPARATOR_SQL;
487 // version
488 ss << "INSERT INTO aoi (key,val) VALUES ('jmdict_version', '"
489 << jmp.get_version() << "');\n";
490 log("jmdict_version: "+jmp.get_version());
492 // get entities
493 for ( std::pair<string,string> elt: jmp.get_entities() ){
494 ss << "INSERT INTO entities (abbr,desc) VALUES ('" << SQLite3::escape(elt.first)
495 << "','" << SQLite3::escape(elt.second) << "');\n";
498 int n_entries = 1;
499 parsers::JmdictEntry entry = jmp.get_entry();
500 while ( entry.did != -1 ) {
501 if ( n_entries % 1000 == 0 ){
502 // entries is about 180 000 -> percent = 100*n/180000 ~ n/2000
503 ui_->progress( n_entries/2000, std::to_string(n_entries)+string(" entries loaded") );
504 n_entries++;
508 // READING
509 for ( parsers::ReadingElement &rele: entry.r_ele ){
510 ss << "INSERT INTO d_reading (rid,did,reading,inf,nokanji,freq) VALUES ("
511 << rele.rid << "," << entry.did << ",'" << rele.reading << "','"
512 << to_string(rele.inf,SEP) << "',"
513 << rele.nokanji << "," << rele.freq << ");\n";
514 // XXX: d_re_restr
516 // KANJI
517 for ( parsers::KanjiElement &kele: entry.k_ele ){
518 ss << "INSERT INTO d_kanji (kid,did,kanji,inf,freq) VALUES ("
519 << kele.kid << "," << entry.did << ",'" << kele.kanji << "','"
520 << to_string(kele.inf,SEP) << "'," << kele.freq << ");\n";
522 // SENSE
523 for ( parsers::SenseElement &sele: entry.s_ele ){
524 ss << "INSERT INTO d_sense (sid,did,gloss,xref,ant,inf,pos,field,misc,dial) VALUES ("
525 << sele.sid << "," << entry.did << ",'"
526 << SQLite3::escape(to_string(sele.gloss,SEP)) << "','"
527 << to_string(sele.xref,SEP) << "','"
528 << to_string(sele.ant,SEP) << "','"
529 << SQLite3::escape(to_string(sele.s_inf,SEP)) << "','"
530 << to_string(sele.pos,SEP) << "','"
531 << to_string(sele.field,SEP) << "','"
532 << to_string(sele.misc,SEP) << "','"
533 << to_string(sele.dial,SEP) << "'"
534 << ");\n";
535 // d_stagk, d_stagr
537 // tbl: header
538 n_entries++;
539 entry = jmp.get_entry();
542 catch ( utils::ParsingError &e ){
543 ui_->progress_hide();
544 std::string msg = "App::parse_jmdict(): ParsingError: ";
545 msg += e.what();
546 alert(msg);
547 return;
550 log(std::to_string(n_entries) + " entries processed.");
552 ss << "END TRANSACTION;\n";
553 // no need for vacuum here - check_index() will perform it eventually (see below)
555 // #ifdef DEBUG
556 // log_d("Writing file 'script.jmdict.sql'...");
557 // std::ofstream f ("script.jmdict.sql");
558 // f << ss.str();
559 // f.close();
560 // #endif
562 ui_->progress( 95, "Creating database... ");
563 log("Creating database...");
564 try {
565 // don't log query (ifdef DEBUG then SQL script is already created)
566 query(ss.str().c_str(),false);
568 catch ( SQLite3::DatabaseError &e ){
569 string msg = "App::parse_jmdict(): DatabaseError: ";
570 msg += e.what();
571 alert(msg);
572 return;
574 ui_->progress( 99, "Checking indexes... ");
575 log("Creating database...");
576 check_indexes();
577 ui_->progress_hide();
578 log("Deleting temporary file...");
579 remove(TEMP_JMDICT);
580 if ( delete_source )
581 remove( fname );
585 void App::check_tables ()
587 // no need for vacuum here, it will be done by check_indexes()
588 log("Checking tables");
589 vector<string> existing = query("SELECT name FROM sqlite_master WHERE type='table'");
590 // CREATE TABLE name ( column1 TYPE, column2 TYPE, ... )
591 for ( auto &table: aoi_config::db_tables ){
592 if ( utils::is_in(existing, string(table.first)) )
593 continue;
594 log("Creating table " + string(table.first));
595 vector<string> v;
596 for ( auto &column: table.second ){
597 std::stringstream sstr;
598 sstr << column.name << " " << column.type;
599 v.push_back(sstr.str());
601 std::stringstream ss;
602 ss << "CREATE TABLE " << table.first << " (" << to_string(v,",") << ");\n";
603 query(ss.str().c_str());
605 log("Check done.");
609 void App::check_indexes ()
611 log("Checking indexes...");
612 bool do_vacuum = false;
613 vector<string> existing = query("SELECT name FROM sqlite_master WHERE type='index'");
614 // CREATE INDEX idx_table_column ON table ( column ASC )
615 for ( auto &mi: aoi_config::db_tables ){ // tables
616 for ( auto &c: mi.second ){ // columns
617 std::stringstream idx_name;
618 idx_name << "idx_" << mi.first << "_" << c.name;
619 if ( c.index && !utils::is_in( existing, idx_name.str() ) ){
620 log(string("Creating index ") + idx_name.str());
621 std::stringstream ss;
622 ss << "CREATE INDEX " << idx_name.str() << " ON " << mi.first
623 << "(" << c.name << " " << c.sort << ")";
624 query(ss.str().c_str());
625 do_vacuum = true;
629 if ( do_vacuum ){
630 query("VACUUM");
632 log("Check done.");
636 void App::cb_dic_input ()
638 parse_dic_input( ui_->get_dic_input() );
639 ui_->reset_filters();
643 void App::on_dic_selected ( int id )
645 log_d("App::on_dic_selected()");
648 void App::cb_edit_word ( int id )
650 std::stringstream ss;
651 ss << "App::edit_word( " << id << " )";
652 log_d(ss);
653 ui_->edit_word();
657 void App::cb_popup_kanji ( const string &kanji )
659 std::stringstream ss;
660 ss << "App::on_kanji_clicked()" << kanji;
661 Kanji k = db_get_kanji(kanji);
662 ui_->popup_kanji( k );
663 // XXX: this should be somewhere else (it is not logical here)
664 ui_->highlight_components( k.components() );
665 log(ss);
669 void App::set_listview ( const vector<string> &v )
671 // TODO: nokanji
672 if ( !listview_items_.empty() ) listview_items_.clear();
673 vector<string> d;
674 vector<int> cell_ids;
675 size_t i = 0;
676 while ( i < v.size() ) {
677 int cell_id = std::stoi(v[i]); // jmdict id
678 cell_ids.push_back(cell_id);
679 cell_ids.push_back(cell_id);
680 cell_ids.push_back(cell_id);
681 // pos
682 set<string> pos;
683 for ( string &elt: utils::split_string( v[i+1], ",") )
684 if ( elt.size() > 0 )
685 pos.insert(utils::strip(elt.c_str()));
686 d.push_back( v[i+2] ); // reading
687 d.push_back( v[i+3] ); // kanji
688 d.push_back( v[i+4] ); // sense
689 // printf("%s%s%s\n", v[i+2].c_str(), v[i+3].c_str(), v[i+4].c_str() );
690 listview_items_.push_back( {cell_id, pos, v[i+2], v[i+3], v[i+4]} );
691 i += 5;
693 char buff[32];
694 sprintf( buff, "%d results", db_->result_rows() );
695 ui_->set_dic_results( buff );
696 ui_->set_listview(d,cell_ids);
700 void App::parse_dic_input ( const char *str )
702 log_d(string("App::parse_dic_input: \"") +string(str) + string("\""));
704 string stripped = utils::strip(str);
706 if ( stripped.empty() )
707 return;
709 const char *s = stripped.c_str();
711 string qq;
712 if ( s[0] != SENSE_SEARCH_CHAR ){
713 printf("Search sense.");
714 DictionaryInputParser p;
715 qq = p.parse(s);
717 if ( p.warning() ){
718 int res = fl_choice(
719 "Too broad search.\n"\
720 "Your computer may become unresponsible for a long time.\n"\
721 "Proceed?",
722 "Proceed",
723 "Cancel",
726 if ( res == 1 ) // Cancel
727 return;
730 if ( !strchr(s,'*') && !strchr(s,'?') && !strchr(s,'[') && !strchr(s,'{')
731 && !strchr(s,'(') )
732 qq += "*";
735 std::stringstream q;
736 if ( s[0] == SENSE_SEARCH_CHAR ){
737 printf("Search: %s\n", stripped.substr(1).c_str() );
738 q << "select did,"
739 << "group_concat(pos) as pos,"
740 << q_reading("d_sense.did")
741 << q_kanji("d_sense.did")
742 << q_sense()
743 << " from d_sense where gloss glob '*" << stripped.substr(1) << "*'"
744 << " group by did";
746 else if ( rmn_->contains_kanji( qq.c_str() ) ) {
747 q << "select d_kanji.did as did,"
748 << "(select group_concat(pos) from d_sense where d_sense.did = d_kanji.did) as pos,"
749 << q_kanji()
750 << q_reading("d_kanji.did")
751 << q_sense("d_kanji.did")
752 << "from d_kanji where "
753 << "kanji glob '" << rmn_->romaji_to_hiragana(qq.c_str()) << "' "
754 << " or kanji glob '" << rmn_->romaji_to_katakana(qq.c_str()) << "' "
755 << " group by did order by d_kanji.freq desc, d_kanji.kanji asc";
757 else {
758 q << "select d_reading.did as did,"
759 << "(select group_concat(pos) from d_sense where d_sense.did = d_reading.did) as pos,"
760 << q_reading()
761 << q_kanji("d_reading.did")
762 << q_sense("d_reading.did")
763 << "from d_reading where "
764 << "reading glob '" << rmn_->romaji_to_hiragana(qq.c_str())
765 << "' or reading glob '"<< rmn_->romaji_to_katakana(qq.c_str()) << "' "
766 << " group by did order by d_reading.freq desc, d_reading.reading asc";
768 set_listview(query(q.str().c_str()));
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],parsers::SEPARATOR_SQL) );
791 kk.kunyomi( utils::split_string(res[9],parsers::SEPARATOR_SQL) );
792 kk.nanori( utils::split_string(res[10],parsers::SEPARATOR_SQL) );
793 kk.meaning( utils::split_string(res[11],parsers::SEPARATOR_SQL) );
794 kk.flags( utils::split_string(res[12],parsers::SEPARATOR_SQL) );
795 kk.components( res[13] );
797 string qq = "select skip1,skip2,skip3,misclass from k_skip where kanji='"+kanji+"';";
798 vector<string> res2 = query(qq.c_str());
799 if ( res2.size() % 4 != 0 ){
800 std::stringstream ss;
801 ss << "Wrong SKIP count. Kanji: " << kanji
802 << "Query result size: " << res2.size()
803 << " (should be 4,8 or 12). SKIP not loaded.";
804 log_e(ss);
805 return kk;
807 for ( size_t i=0; i < res2.size(); i+=4 )
808 kk.skip( res2[i], res2[i+1], res2[i+2], res2[i+3] );
809 return kk;
813 void App::cb_file_jmdict ( bool download )
815 string fname;
816 const char *path = nullptr;
818 if ( download ){
819 fname = ui_->download_dialog( get_config("sources/url_jmdict") );
820 log_d("Download finished...");
821 path = fname.c_str();
823 else
824 path = ui_->open_file_dialog();
826 if ( !path )
827 return;
829 log("Selected file: " + string(path));
830 parse_jmdict(path, download);
831 auto label = ui()->dlg_manage_db()->item(MANAGEDB_ITEM_JMDICT);
832 if ( label )
833 label->version("Changed",FL_BLUE);
834 else
835 log_w("Can't find item " + string(MANAGEDB_ITEM_JMDICT) + " in ManageDBDialog.");
839 void App::cb_file_kanjidic ( bool download )
841 string fname;
842 const char *path = nullptr;
844 if ( download ){
845 fname = ui_->download_dialog( get_config("sources/url_kanjidic") );
846 log_d("Download finished...");
847 path = fname.c_str();
849 else
850 path = ui_->open_file_dialog();
852 if ( !path )
853 return;
855 log("Selected file: " + string(path));
856 parse_kanjidic(path,download);
857 auto label = ui_->dlg_manage_db()->item(MANAGEDB_ITEM_KANJIDIC);
858 if ( label )
859 label->version("Changed",FL_BLUE);
860 else
861 log_w("Can't find item "+ string(MANAGEDB_ITEM_KANJIDIC) +" in ManageDBDialog.");
865 void App::cb_file_components ( bool download )
867 string fname;
868 const char *path = nullptr;
870 if ( download ){
871 fname = ui_->download_dialog( get_config("sources/url_components") );
872 log_d("Download finished...");
873 path = fname.c_str();
875 else
876 path = ui_->open_file_dialog();
878 if ( !path )
879 return;
881 log("Selected file: " + string(path));
882 utils::gzip_decompress_file( path, TEMP_COMPONENTS);
883 try {
884 db_->script( TEMP_COMPONENTS );
886 catch ( SQLite3::DatabaseError &e ) {
887 alert(e.what(), e.query() );
889 catch ( std::exception &e ) {
890 alert( e.what() );
893 auto label = ui_->dlg_manage_db()->item(MANAGEDB_ITEM_COMPONENTS);
894 if ( label )
895 label->version("Changed",FL_BLUE);
896 else
897 log_w("Can't find item " + string(MANAGEDB_ITEM_COMPONENTS)
898 + " in ManageDBDialog.");
899 remove(TEMP_COMPONENTS);
900 remove(path);
904 void App::cb_filter_listview ()
906 vector<string> pos = ui_->listview_filters();
907 log_d("App::cb_filter_listview(): pos: " + utils::to_string(pos));
908 bool filter_expr = ( utils::is_in( pos, string("expr") ) );
909 bool filter_noun = ( utils::is_in( pos, string("noun") ) );
910 bool filter_verb = ( utils::is_in( pos, string("verb") ) );
911 bool filter_adj = ( utils::is_in( pos, string("adj") ) );
912 vector<string> data;
913 vector<int> ids;
914 size_t n = 0;
915 for ( auto &elt: listview_items_ ){
916 auto start = elt.pos.begin();
917 auto end = elt.pos.end();
918 if ( filter_expr && std::find( start, end, "exp") == end )
919 continue;
920 if ( filter_noun && std::find( start, end, "n" ) == end )
921 continue;
922 if ( filter_adj && std::find_if( start, end,
923 [](const string &s){ return strncmp(s.c_str(),"adj",3)==0;} ) == end )
924 continue;
925 if ( filter_verb && std::find_if( start, end,
926 [](const string &s){ return strncmp(s.c_str(),"v",1)==0;} ) == end )
927 continue;
928 ids.push_back( elt.did );
929 ids.push_back( elt.did );
930 ids.push_back( elt.did );
931 data.push_back ( elt.reading );
932 data.push_back ( elt.kanji );
933 data.push_back ( elt.sense );
934 n++;
936 ui_->set_listview( data, ids );
937 std::stringstream ss;
938 ss << n << " results";
939 if ( listview_items_.size() != n )
940 ss << " (" << listview_items_.size()-n << " hidden)";
941 log(ss.str());
942 ui_->set_dic_results( ss.str() );
946 void App::cb_dicview_rightclick ( int did )
948 printf("%d\n",did);
949 string q = "select group_concat(kanji,'') from d_kanji where did="+std::to_string(did);
950 vector<string> res = query( q.c_str() );
951 set<string> kanji;
952 for ( string &c: utils::str_to_chars(res[0].c_str()) )
953 if ( App::get()->rmn()->is_kanji(c.c_str()) )
954 kanji.insert(c);
955 ui_->menu_copy( vector<string>(kanji.begin(),kanji.end()) );
959 void App::set_config ( const string &key, const string &val )
961 cfg_->set( key, val );
962 if ( key == "font/base_size")
963 ui_->font_base_size(std::stoi(val));
964 else if ( key == "log/level" )
965 logger_.loglevel(val);
966 else if ( key == "color/foreground" || key == "color/background" ||
967 key == "color/background2" || key == "color/selection")
968 ui_->init_colors();
969 Fl::check();
973 void App::cb_manage_db ()
975 auto q = query("select val from aoi where key='jmdict_version'");
976 string jmdict_version = (q.empty()) ? "NONE":q[0];
977 q = query("select val from aoi where key='kanjidic_version'");
978 string kanjidic_version = q.empty() ? "NONE":q[0];
979 q = query("select val from aoi where key='components_version'");
980 string components_version = q.empty() ? "NONE":q[0];
981 auto *d = ui_->dlg_manage_db();
982 Fl_Color col;
983 if ( !d->item(MANAGEDB_ITEM_JMDICT) ){
984 col = ( jmdict_version == "NONE" ) ? FL_RED:FL_BLUE;
985 d->add_item( MANAGEDB_ITEM_JMDICT, jmdict_version,
986 "Japanese-English dictionary. Needed component.",
987 col, scb_local_file_jmdict, scb_download_file_jmdict,
988 (void*)this );
990 if ( !d->item(MANAGEDB_ITEM_KANJIDIC) ){
991 col = ( kanjidic_version == "NONE" ) ? FL_RED:FL_BLUE;
992 d->add_item( MANAGEDB_ITEM_KANJIDIC, kanjidic_version,
993 "Kanji dictionary. Needed component.",
994 col, scb_local_file_kanjidic, scb_download_file_kanjidic,
995 (void*)this );
997 if ( !d->item(MANAGEDB_ITEM_COMPONENTS) ){
998 col = ( components_version == "NONE" ) ? FL_RED:FL_BLUE;
999 d->add_item( MANAGEDB_ITEM_COMPONENTS, components_version,
1000 "Graphical components of kanji.",
1001 col, scb_local_file_components, scb_download_file_components,
1002 (void*)this );
1004 d->show();
1008 void App::load_config ()
1010 log("Loading config...");
1011 vector<string> res = query("select key,val from config;");
1012 for ( size_t i=0; i < res.size(); i+=2 ){
1013 set_config( res[i], res[i+1] );
1018 void App::save_config ( const std::map<string,aoi_config::Config::Value> &newmap)
1020 log("Saving config...");
1022 typedef std::pair<string,aoi_config::Config::Value> cfg_pair;
1024 // merge new and old config
1025 for ( const cfg_pair &p: newmap )
1026 set_config( p.first, p.second.val );
1028 // prepare SQL script
1029 std::stringstream ss;
1030 ss << "BEGIN TRANSACTION;\n";
1031 for ( const cfg_pair &p: get_config_map() )
1032 ss << "REPLACE INTO config (key,val) VALUES('"
1033 << p.first << "','" << p.second.val << "');\n";
1034 ss << "END TRANSACTION;\n";
1036 query(ss.str().c_str());
1038 // apply new fonts and colors
1039 init_dicview();
1041 // TODO:
1042 // check new keys
1043 // check keys in oldconfig not present in newmap
1044 // set oldkeys
1045 // set newkeys
1046 // mnoziny...
1049 } // namespace aoi