editable system colors
[aoi.git] / src / aoi.cxx
blob4f8172d870cdeb1a7bbbf3818df1a5b85ca8980d
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";
37 App* App::instance_ = nullptr;
39 App::App ()
41 remove(LOG_FILE);
42 logger_.filename(LOG_FILE);
43 logger_.loglevel(Logger::MSG_DEBUG);
45 cfg_ = new aoi_config::Config();
46 ui_ = new aoi_ui::GUI(this);
47 rmn_ = new Romanization();
49 ui_->progress(0,"Opening database...");
51 try {
52 db_ = new SQLite3::SQLite3( cfg_->get<string>("db/file_main").c_str() );
54 catch (SQLite3::CantOpenDatabase &e){
55 log_e( "Aoi: Can't open database '" + string(e.what()) + "'.");
58 ui_->progress( 30, "Checking tables..." );
59 check_tables();
60 ui_->progress( 60, "Checking indexes..." );
61 check_indexes();
63 load_config();
64 init_dicview();
66 #ifdef DEBUG
67 ui_->progress(90, "DEBUG query..." );
68 // parse_dic_input("ana*");
69 // cb_kanji_search();
70 // ui_->cb_toggle_group(nullptr);
71 #endif
73 ui_->progress( 98, "Initializing fonts..." );
74 ui_->fontname_kanji( cfg_->get("font/kanji") );
75 ui_->help_file( cfg_->get("sources/help_index") );
77 ui_->progress_hide();
80 auto q = query("select val from aoi where key='jmdict_version'");
81 string jmdict_version = (q.empty()) ? "NONE":q[0];
82 q = query("select val from aoi where key='kanjidic_version'");
83 string kanjidic_version = q.empty() ? "NONE":q[0];
84 if ( jmdict_version == "NONE" || kanjidic_version == "NONE" )
85 cb_manage_db();
88 instance_ = this;
92 App::~App ()
94 delete db_;
95 delete rmn_;
96 delete ui_;
97 delete cfg_;
101 void App::init_dicview ()
103 // initialize textstyles
104 TextStyle style_default(FL_HELVETICA,1.2);
105 TextStyle style_reading(aoi_ui::FONT_KANJI,1.5);
106 TextStyle style_reading_freq = style_reading;
107 style_reading_freq.color = get_config<int>("color/frequent");
108 TextStyle style_kanji(aoi_ui::FONT_KANJI,1.7);
109 TextStyle style_kanji_freq = style_kanji;
110 style_kanji_freq.color = get_config<int>("color/frequent");
111 TextStyle style_inf(FL_HELVETICA_ITALIC,0.8,get_config<int>("color/pos"));
112 style_inf.offset_y = 3;
113 TextStyle style_pos = style_inf;
114 TextStyle style_misc = style_pos;
115 style_misc.color = get_config<int>("color/misc");
116 TextStyle style_field = style_pos;
117 style_field.color = get_config<int>("color/field");
118 TextStyle style_dial = style_pos;
120 // register TextStyles
121 ui_->register_tag_dicview( "default", style_default );
122 ui_->register_tag_dicview( "reading", style_reading );
123 ui_->register_tag_dicview( "reading_freq", style_reading_freq );
124 ui_->register_tag_dicview( "kanji", style_kanji );
125 ui_->register_tag_dicview( "kanji_freq", style_kanji_freq );
126 ui_->register_tag_dicview( "kinf", style_inf );
127 ui_->register_tag_dicview( "rinf", style_inf );
128 ui_->register_tag_dicview( "pos", style_pos );
129 ui_->register_tag_dicview( "misc", style_misc );
130 ui_->register_tag_dicview( "field", style_field );
131 ui_->register_tag_dicview( "dial", style_dial );
135 vector<string> App::query ( const char *q, bool log_query, bool replace_separator )
137 if ( !db_ ){
138 log_e("App::query(): Database does not exist.");
139 return {};
141 vector<string> result;
142 try {
143 if ( log_query )
144 log_d(string(q));
145 result = db_->query(q);
147 catch (SQLite3::DatabaseError &e){
148 log_e("App: DatabaseError:: " + string(e.what()) + string("\nQuery: ")
149 + string(e.query()) );
151 string msg = std::to_string(db_->result_rows()) + " results";
152 log( "App::query(): " + msg);
153 if ( replace_separator )
154 for ( string &s: result )
155 utils::replace_all(s, parsers::SEPARATOR_SQL, ", ");
156 return result;
160 void App::cb_set_components ()
162 if ( curr_components_.empty() )
163 return;
165 using aoi_ui::ComponentView;
167 // prepare cells
168 vector<string> included = ui_->components_include();
169 vector<string> excluded = ui_->components_exclude();
171 vector<ComponentView::Cell> v;
172 for ( string &s: included )
173 v.push_back( ComponentView::Cell(s,ComponentView::CELL_SELECTED_1) );
174 for ( string &s: excluded )
175 v.push_back( ComponentView::Cell(s,ComponentView::CELL_SELECTED_2) );
177 // sort by occurences
178 if ( !ui_->sort_components_by_strokes() ){
179 for ( auto mi = curr_components_.rbegin(); mi!=curr_components_.rend(); ++mi ){
180 if ( !utils::is_in( included, mi->second )
181 && !utils::is_in( excluded, mi->second ) )
182 v.push_back( ComponentView::Cell(mi->second) );
185 // sort by strokes
186 else {
187 vector<string> comps_by_strokes;
188 for ( auto mi: curr_components_ ){
189 if ( mi.first < get_config<int>("knj/min_compo_count") )
190 continue;
191 comps_by_strokes.push_back(mi.second);
194 struct SortByStrokes {
195 map<string,int> comps;
196 SortByStrokes( const map<string,int> &c ): comps(c){};
197 bool operator() (const string &c1, const string &c2 ){
198 return ( this->comps[c1] < this->comps[c2] );
200 } sbc(components_);
202 std::sort( comps_by_strokes.begin(), comps_by_strokes.end(), sbc );
204 int prev_strokes=0;
205 for ( string &s: comps_by_strokes ){
206 int curr_strokes = components_[s];
207 if ( prev_strokes != curr_strokes )
208 v.push_back( ComponentView::Cell( std::to_string(curr_strokes),
209 ComponentView::CELL_LABEL) );
210 v.push_back( ComponentView::Cell(s) );
211 prev_strokes = curr_strokes;
215 ui_->set_components( v );
219 void App::cb_kanji_search ()
222 if ( components_.empty() ){
223 log("Loading components...");
224 vector<string> res = query("select component, strokes from components");
225 for ( size_t i=0; i<res.size(); i=i+2 ){
226 components_[res[i]] = std::stoi(res[i+1]);
230 // strokes
231 std::pair<int,int> strokes = utils::parse_range( utils::strip( ui_->strokes() ) );
232 // jlpt
233 std::pair<int,int> jlpt = utils::parse_range( utils::strip( ui_->jlpt() ) );
234 // grade
235 std::pair<int,int> grade = utils::parse_range( utils::strip( ui_->grade() ) );
239 // skip
240 vector<string> skip = utils::split_string( ui_->skip() );
241 std::stringstream sskip;
242 if ( skip.size() > 0 ){
243 string skip1 = utils::strip(skip[0].c_str());
244 sskip << " S.skip1=" << skip1;
246 if ( skip.size() > 1 ){
247 std::pair<int,int> skip2 = utils::parse_range( utils::strip(skip[1].c_str()) );
248 sskip << " and S.skip2>=" << skip2.first << " and S.skip2<=" << skip2.second;
250 if ( skip.size() > 2 ){
251 std::pair<int,int> skip3 = utils::parse_range( utils::strip(skip[2].c_str()) );
252 sskip << " and S.skip3>=" << skip3.first << " and S.skip3<=" << skip3.second;
254 if ( !sskip.str().empty() )
255 sskip << " and ";
257 // ordering
258 string order_by;
259 switch ( ui_->sort_mode() ){
260 case SORT_FREQ_ASC:
261 order_by = "(case freq when 0 then 9999 else freq end) asc";
262 break;
263 case SORT_FREQ_DESC:
264 order_by = "(case freq when 0 then 9999 else freq end) desc";
265 break;
266 case SORT_STROKES_ASC:
267 order_by = "strokes asc";
268 break;
269 case SORT_STROKES_DESC:
270 order_by = "strokes desc";
271 break;
274 vector<string> components_include = ui_->components_include();
275 vector<string> components_exclude = ui_->components_exclude();
276 std::stringstream comps;
277 for ( auto c: components_include )
278 comps << " and components like '%" << c << "%'";
279 for ( auto c: components_exclude )
280 comps << " and components not like '%" << c << "%'";
281 printf("%s\n",comps.str().c_str());
283 string jis208 = "";
284 if ( get_config<bool>("knj/jis208_only") )
285 jis208 = " flags glob '*jis208*' and ";
287 // build query
288 std::stringstream ss;
289 ss << "select distinct "
290 << "K.kanji,freq,components,flags "
291 << " from k_kanji as K, k_skip as S where K.kanji=S.kanji and "
292 << jis208
293 << sskip.str()
294 << " strokes>=" << strokes.first << " and strokes<=" << strokes.second
295 << " and jlpt>=" << jlpt.first << " and jlpt<=" << jlpt.second
296 << " and grade>=" << grade.first << " and grade<=" << grade.second
297 << comps.str()
298 << " order by " << order_by;
301 // perform query
302 vector<string> q = query( ss.str().c_str(), true, false );
303 vector<aoi_ui::KanjiView::Cell> data;
304 vector<string> components;
305 set<string> flags;
306 for ( size_t i=0; i<q.size(); i+=4 ){
307 string kanji = q[i];
308 int freq = std::stoi(q[i+1]);
309 data.push_back(
310 aoi_ui::KanjiView::Cell(
311 kanji,
312 (freq>0)? get_config<int>("color/frequent"):-1
315 components.push_back(q[i+2]);
316 for ( string &s: utils::split_string( q[i+3], parsers::SEPARATOR_SQL) )
317 flags.insert(s);
320 log_d("Groups: " + utils::to_string(flags));
322 ui_->set_kanjiview( data );
323 char b[32];
324 sprintf( b, "%d results", db_->result_rows() );
325 ui_->set_kanji_results( b );
327 utils::Histogram<string> histogram;
328 for ( string &s: components )
329 histogram.add( utils::str_to_chars(s.c_str()) );
330 curr_components_ = histogram.sorted();
332 cb_set_components();
336 void App::alert ( const string &msg, const string &desc )
338 std::stringstream ss;
339 ss << msg;
340 if ( !desc.empty() )
341 ss << "\n\nDetails:\n" << desc;
342 log_e(ss);
343 ui_->alert(ss.str());
347 int App::run ( int argc, char **argv )
349 return ui_->run(argc,argv);
353 void App::parse_kanjidic ( const char *fname, bool delete_source )
355 if ( !utils::file_exists( fname ) ){
356 log_e("App::parse_kanjidic(): File does not exist: " + string(fname));
357 return;
359 ui_->progress(0, "Loading kanjidic...");
360 log("Loading kanjidic...");
362 const char *SEP = parsers::SEPARATOR_SQL;
364 int n_kanji = 0;
365 try {
366 ui_->progress(1, "Creating parser...");
367 std::stringstream msg;
368 msg << "Decompressing file: " << fname << " -> " << TEMP_KANJIDIC;
369 log_d(msg);
371 #ifdef WINDOWS
372 log_d("Waiting 3 s before decompressing...");
373 usleep(3000);
374 log_d("Continue...");
375 #endif
377 utils::gzip_decompress_file( fname, TEMP_KANJIDIC);
378 parsers::KanjidicParser p(TEMP_KANJIDIC);
379 auto kanji = p.get_entry();
381 query( "DROP TABLE IF EXISTS k_kanji;"
382 "DROP TABLE IF EXISTS components;"
383 "DROP TABLE IF EXISTS k_skip;"
384 "DELETE FROM aoi WHERE key='kanjidic_version';" );
386 check_tables();
388 std::stringstream ss;
389 ss << "BEGIN TRANSACTION;\n";
390 ss << "INSERT INTO aoi (key,val) VALUES ('kanjidic_version','"
391 << p.get_version() << "');\n";
392 while ( kanji.kanji() != "" ){
393 n_kanji++;
394 if ( n_kanji % 100 == 0)
395 // percent = 100 * n_kanji/13108
396 ui_->progress( n_kanji/14, std::to_string(n_kanji)+string(" kanji loaded"));
397 ss << "INSERT INTO k_kanji "
398 << "(kanji,ucs,onyomi,kunyomi,meaning,nanori,flags,jlpt,grade,freq,strokes,"
399 << "rad_classic,rad_nelson,components)"
400 << " VALUES('"
401 << kanji.kanji() << "','"
402 << kanji.ucs() << "','"
403 << to_string(kanji.onyomi(),SEP) << "','"
404 << to_string(kanji.kunyomi(),SEP) << "','"
405 << SQLite3::escape(to_string(kanji.meaning(),SEP)) << "','"
406 << to_string(kanji.nanori(),SEP) << "','"
407 << to_string(kanji.flags(),SEP) << "',"
408 << kanji.jlpt() << ","
409 << kanji.grade() << ","
410 << kanji.freq() << ","
411 << kanji.strokes() << ","
412 << kanji.rad_classic() << ","
413 << kanji.rad_nelson() << ","
414 << "''"
415 << ");\n";
416 for ( SKIP &s: kanji.skip() ) {
417 ss << "INSERT INTO k_skip (kanji,skip1,skip2,skip3,misclass) VALUES('"
418 << kanji.kanji() << "'," << s.s1 << "," << s.s2 << "," << s.s3
419 << ",'" << s.misclass << "');\n";
421 kanji = p.get_entry();
423 ss << "END TRANSACTION;\n";
424 log("Writing to db ...");
426 // #ifdef DEBUG
427 // log_d("Writing file 'script.kanjidic.sql'...");
428 // std::ofstream f ("script.kanjidic.sql");
429 // f << ss.str();
430 // f.close();
431 // #endif
432 ui_->progress(95, "Writing to database...");
433 query(ss.str().c_str(),false);
435 catch ( utils::ParsingError &e ){
436 ui_->progress_hide();
437 std::string msg = "App::parse_jmdict(): ParsingError: ";
438 msg += e.what();
439 alert(msg);
440 return;
442 ui_->progress(98, "Checking indexes...");
443 check_indexes();
444 ui_->progress_hide();
445 remove( TEMP_KANJIDIC );
446 if ( delete_source )
447 remove( fname );
451 void App::parse_jmdict ( const char *fname, bool delete_source )
453 if ( !utils::file_exists( fname ) ){
454 log_e("App::parse_jmdict(): File does not exist: " + string(fname));
455 return;
458 ui_->progress( 0, "Loading JMdict..." );
459 log("Loading JMdict...");
460 std::stringstream ss;
461 ss << "BEGIN TRANSACTION;\n";
463 // tables
464 for ( auto &mi: aoi_config::db_tables ){
465 ss << "DROP TABLE IF EXISTS " << mi.first << ";\n"
466 << "CREATE TABLE " << mi.first << " ( ";
467 for ( size_t i=0; i<mi.second.size(); i++ )
468 ss << mi.second[i].name << " " << mi.second[i].type
469 << ((i==mi.second.size()-1) ? "":",");
470 ss << ");\n";
473 // create parser
474 int n_entries = 0;
475 try {
476 ui_->progress(1, "Creating parser...");
478 utils::gzip_decompress_file( fname, TEMP_JMDICT);
479 parsers::JmdictParser jmp(TEMP_JMDICT);
480 const char *SEP = parsers::SEPARATOR_SQL;
482 // version
483 ss << "INSERT INTO aoi (key,val) VALUES ('jmdict_version', '"
484 << jmp.get_version() << "');\n";
485 log("jmdict_version: "+jmp.get_version());
487 // get entities
488 for ( std::pair<string,string> elt: jmp.get_entities() ){
489 ss << "INSERT INTO entities (abbr,desc) VALUES ('" << SQLite3::escape(elt.first)
490 << "','" << SQLite3::escape(elt.second) << "');\n";
493 int n_entries = 1;
494 parsers::JmdictEntry entry = jmp.get_entry();
495 while ( entry.did != -1 ) {
496 if ( n_entries % 1000 == 0 ){
497 // entries is about 180 000 -> percent = 100*n/180000 ~ n/2000
498 ui_->progress( n_entries/2000, std::to_string(n_entries)+string(" entries loaded") );
499 n_entries++;
503 // READING
504 for ( parsers::ReadingElement &rele: entry.r_ele ){
505 ss << "INSERT INTO d_reading (rid,did,reading,inf,nokanji,freq) VALUES ("
506 << rele.rid << "," << entry.did << ",'" << rele.reading << "','"
507 << to_string(rele.inf,SEP) << "',"
508 << rele.nokanji << "," << rele.freq << ");\n";
509 // XXX: d_re_restr
511 // KANJI
512 for ( parsers::KanjiElement &kele: entry.k_ele ){
513 ss << "INSERT INTO d_kanji (kid,did,kanji,inf,freq) VALUES ("
514 << kele.kid << "," << entry.did << ",'" << kele.kanji << "','"
515 << to_string(kele.inf,SEP) << "'," << kele.freq << ");\n";
517 // SENSE
518 for ( parsers::SenseElement &sele: entry.s_ele ){
519 ss << "INSERT INTO d_sense (sid,did,gloss,xref,ant,inf,pos,field,misc,dial) VALUES ("
520 << sele.sid << "," << entry.did << ",'"
521 << SQLite3::escape(to_string(sele.gloss,SEP)) << "','"
522 << to_string(sele.xref,SEP) << "','"
523 << to_string(sele.ant,SEP) << "','"
524 << SQLite3::escape(to_string(sele.s_inf,SEP)) << "','"
525 << to_string(sele.pos,SEP) << "','"
526 << to_string(sele.field,SEP) << "','"
527 << to_string(sele.misc,SEP) << "','"
528 << to_string(sele.dial,SEP) << "'"
529 << ");\n";
530 // d_stagk, d_stagr
532 // tbl: header
533 n_entries++;
534 entry = jmp.get_entry();
537 catch ( utils::ParsingError &e ){
538 ui_->progress_hide();
539 std::string msg = "App::parse_jmdict(): ParsingError: ";
540 msg += e.what();
541 alert(msg);
542 return;
545 log(std::to_string(n_entries) + " entries processed.");
547 ss << "END TRANSACTION;\n";
548 // no need for vacuum here - check_index() will perform it eventually (see below)
550 // #ifdef DEBUG
551 // log_d("Writing file 'script.jmdict.sql'...");
552 // std::ofstream f ("script.jmdict.sql");
553 // f << ss.str();
554 // f.close();
555 // #endif
557 ui_->progress( 95, "Creating database... ");
558 log("Creating database...");
559 try {
560 // don't log query (ifdef DEBUG then SQL script is already created)
561 query(ss.str().c_str(),false);
563 catch ( SQLite3::DatabaseError &e ){
564 string msg = "App::parse_jmdict(): DatabaseError: ";
565 msg += e.what();
566 alert(msg);
567 return;
569 ui_->progress( 99, "Checking indexes... ");
570 log("Creating database...");
571 check_indexes();
572 ui_->progress_hide();
573 log("Deleting temporary file...");
574 remove(TEMP_JMDICT);
575 if ( delete_source )
576 remove( fname );
580 void App::check_tables ()
582 // no need for vacuum here, it will be done by check_indexes()
583 log("Checking tables");
584 vector<string> existing = query("SELECT name FROM sqlite_master WHERE type='table'");
585 // CREATE TABLE name ( column1 TYPE, column2 TYPE, ... )
586 for ( auto &table: aoi_config::db_tables ){
587 if ( utils::is_in(existing, string(table.first)) )
588 continue;
589 log("Creating table " + string(table.first));
590 vector<string> v;
591 for ( auto &column: table.second ){
592 std::stringstream sstr;
593 sstr << column.name << " " << column.type;
594 v.push_back(sstr.str());
596 std::stringstream ss;
597 ss << "CREATE TABLE " << table.first << " (" << to_string(v,",") << ");\n";
598 query(ss.str().c_str());
600 log("Check done.");
604 void App::check_indexes ()
606 log("Checking indexes...");
607 bool do_vacuum = false;
608 vector<string> existing = query("SELECT name FROM sqlite_master WHERE type='index'");
609 // CREATE INDEX idx_table_column ON table ( column ASC )
610 for ( auto &mi: aoi_config::db_tables ){ // tables
611 for ( auto &c: mi.second ){ // columns
612 std::stringstream idx_name;
613 idx_name << "idx_" << mi.first << "_" << c.name;
614 if ( c.index && !utils::is_in( existing, idx_name.str() ) ){
615 log(string("Creating index ") + idx_name.str());
616 std::stringstream ss;
617 ss << "CREATE INDEX " << idx_name.str() << " ON " << mi.first
618 << "(" << c.name << " " << c.sort << ")";
619 query(ss.str().c_str());
620 do_vacuum = true;
624 if ( do_vacuum ){
625 query("VACUUM");
627 log("Check done.");
631 void App::cb_dic_input ()
633 parse_dic_input( ui_->get_dic_input() );
634 ui_->reset_filters();
638 void App::on_dic_selected ( int id )
640 log_d("App::on_dic_selected()");
643 void App::cb_edit_word ( int id )
645 std::stringstream ss;
646 ss << "App::edit_word( " << id << " )";
647 log_d(ss);
648 ui_->edit_word();
652 void App::cb_popup_kanji ( const string &kanji )
654 std::stringstream ss;
655 ss << "App::on_kanji_clicked()" << kanji;
656 Kanji k = db_get_kanji(kanji);
657 ui_->popup_kanji( k );
658 // XXX: this should be somewhere else (it is not logical here)
659 ui_->highlight_components( k.components() );
660 log(ss);
664 void App::set_listview ( const vector<string> &v )
666 // TODO: nokanji
667 if ( !listview_items_.empty() ) listview_items_.clear();
668 vector<string> d;
669 vector<int> cell_ids;
670 size_t i = 0;
671 while ( i < v.size() ) {
672 int cell_id = std::stoi(v[i]); // jmdict id
673 cell_ids.push_back(cell_id);
674 cell_ids.push_back(cell_id);
675 cell_ids.push_back(cell_id);
676 // pos
677 set<string> pos;
678 for ( string &elt: utils::split_string( v[i+1], ",") )
679 if ( elt.size() > 0 )
680 pos.insert(utils::strip(elt.c_str()));
681 d.push_back( v[i+2] ); // reading
682 d.push_back( v[i+3] ); // kanji
683 d.push_back( v[i+4] ); // sense
684 // printf("%s%s%s\n", v[i+2].c_str(), v[i+3].c_str(), v[i+4].c_str() );
685 listview_items_.push_back( {cell_id, pos, v[i+2], v[i+3], v[i+4]} );
686 i += 5;
688 char buff[32];
689 sprintf( buff, "%d results", db_->result_rows() );
690 ui_->set_dic_results( buff );
691 ui_->set_listview(d,cell_ids);
695 void App::parse_dic_input ( const char *s )
697 log_d(string("App::parse_dic_input: \"") +string(s) + string("\""));
699 DictionaryInputParser p;
700 string qq = p.parse(s);
702 if ( p.warning() ){
703 int res = fl_choice(
704 "Too broad search.\n"\
705 "Your computer may become unresponsible for a long time.\n"\
706 "Proceed?",
707 "Proceed",
708 "Cancel",
711 if ( res == 1 ) // Cancel
712 return;
715 if ( !strchr(s,'*') && !strchr(s,'?') && !strchr(s,'[') && !strchr(s,'{')
716 && !strchr(s,'(') )
717 qq += "*";
719 std::stringstream q;
720 if ( rmn_->contains_kanji( qq.c_str() ) ) {
721 q << "select d_kanji.did as did,"
722 << "(select group_concat(pos) from d_sense where d_sense.did = d_kanji.did) as pos,"
723 << q_kanji()
724 << q_reading("d_kanji.did")
725 << q_sense("d_kanji.did")
726 << "from d_kanji where "
727 << "kanji glob '" << rmn_->romaji_to_hiragana(qq.c_str()) << "' "
728 << " or kanji glob '" << rmn_->romaji_to_katakana(qq.c_str()) << "' "
729 << " group by did order by d_kanji.freq desc, d_kanji.kanji asc";
731 else {
732 q << "select d_reading.did as did,"
733 << "(select group_concat(pos) from d_sense where d_sense.did = d_reading.did) as pos,"
734 << q_reading()
735 << q_kanji("d_reading.did")
736 << q_sense("d_reading.did")
737 << "from d_reading where "
738 << "reading glob '" << rmn_->romaji_to_hiragana(qq.c_str())
739 << "' or reading glob '"<< rmn_->romaji_to_katakana(qq.c_str()) << "' "
740 << " group by did order by d_reading.freq desc, d_reading.reading asc";
742 set_listview(query(q.str().c_str()));
747 Kanji App::db_get_kanji ( const string &kanji )
749 log("App::db_get_kanji()");
750 std::stringstream q;
751 q << "select kanji,strokes,ucs, rad_classic, rad_nelson, "
752 << "jlpt, grade, freq, onyomi, kunyomi, nanori, meaning,flags,components "
753 << "from k_kanji where kanji='" << kanji << "';";
755 vector<string> res = query(q.str().c_str());
756 Kanji kk(res[0]);
757 kk.strokes(std::stoi(res[1]));
758 kk.ucs(res[2]);
759 kk.rad_classic(std::stoi(res[3]));
760 kk.rad_nelson( (res[4].empty()) ? -1:std::stoi(res[4]));
761 kk.jlpt(std::stoi(res[5]));
762 kk.grade(std::stoi(res[6]));
763 kk.freq(std::stoi(res[7]));
764 kk.onyomi( utils::split_string(res[8],parsers::SEPARATOR_SQL) );
765 kk.kunyomi( utils::split_string(res[9],parsers::SEPARATOR_SQL) );
766 kk.nanori( utils::split_string(res[10],parsers::SEPARATOR_SQL) );
767 kk.meaning( utils::split_string(res[11],parsers::SEPARATOR_SQL) );
768 kk.flags( utils::split_string(res[12],parsers::SEPARATOR_SQL) );
769 kk.components( res[13] );
771 string qq = "select skip1,skip2,skip3,misclass from k_skip where kanji='"+kanji+"';";
772 vector<string> res2 = query(qq.c_str());
773 if ( res2.size() % 4 != 0 ){
774 std::stringstream ss;
775 ss << "Wrong SKIP count. Kanji: " << kanji
776 << "Query result size: " << res2.size()
777 << " (should be 4,8 or 12). SKIP not loaded.";
778 log_e(ss);
779 return kk;
781 for ( size_t i=0; i < res2.size(); i+=4 )
782 kk.skip( res2[i], res2[i+1], res2[i+2], res2[i+3] );
783 return kk;
787 void App::cb_file_jmdict ( bool download )
789 string fname;
790 const char *path = nullptr;
792 if ( download ){
793 fname = ui_->download_dialog( get_config("sources/url_jmdict") );
794 log_d("Download finished...");
795 path = fname.c_str();
797 else
798 path = ui_->open_file_dialog();
800 if ( !path )
801 return;
803 log("Selected file: " + string(path));
804 parse_jmdict(path, download);
805 auto label = ui()->dlg_manage_db()->item(MANAGEDB_ITEM_JMDICT);
806 if ( label )
807 label->version("Changed",FL_BLUE);
808 else
809 log_w("Can't find item " + string(MANAGEDB_ITEM_JMDICT) + " in ManageDBDialog.");
813 void App::cb_file_kanjidic ( bool download )
815 string fname;
816 const char *path = nullptr;
818 if ( download ){
819 fname = ui_->download_dialog( get_config("sources/url_kanjidic") );
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_kanjidic(path,download);
831 auto label = ui_->dlg_manage_db()->item(MANAGEDB_ITEM_KANJIDIC);
832 if ( label )
833 label->version("Changed",FL_BLUE);
834 else
835 log_w("Can't find item "+ string(MANAGEDB_ITEM_KANJIDIC) +" in ManageDBDialog.");
839 void App::cb_file_components ( bool download )
841 string fname;
842 const char *path = nullptr;
844 if ( download ){
845 fname = ui_->download_dialog( get_config("sources/url_components") );
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 utils::gzip_decompress_file( path, TEMP_COMPONENTS);
857 try {
858 db_->script( TEMP_COMPONENTS );
860 catch ( SQLite3::DatabaseError &e ) {
861 alert(e.what(), e.query() );
863 catch ( std::exception &e ) {
864 alert( e.what() );
867 auto label = ui_->dlg_manage_db()->item(MANAGEDB_ITEM_COMPONENTS);
868 if ( label )
869 label->version("Changed",FL_BLUE);
870 else
871 log_w("Can't find item " + string(MANAGEDB_ITEM_COMPONENTS)
872 + " in ManageDBDialog.");
873 remove(TEMP_COMPONENTS);
874 remove(path);
878 void App::cb_filter_listview ()
880 vector<string> pos = ui_->listview_filters();
881 log_d("App::cb_filter_listview(): pos: " + utils::to_string(pos));
882 bool filter_expr = ( utils::is_in( pos, string("expr") ) );
883 bool filter_noun = ( utils::is_in( pos, string("noun") ) );
884 bool filter_verb = ( utils::is_in( pos, string("verb") ) );
885 bool filter_adj = ( utils::is_in( pos, string("adj") ) );
886 vector<string> data;
887 vector<int> ids;
888 size_t n = 0;
889 for ( auto &elt: listview_items_ ){
890 auto start = elt.pos.begin();
891 auto end = elt.pos.end();
892 if ( filter_expr && std::find( start, end, "exp") == end )
893 continue;
894 if ( filter_noun && std::find( start, end, "n" ) == end )
895 continue;
896 if ( filter_adj && std::find_if( start, end,
897 [](const string &s){ return strncmp(s.c_str(),"adj",3)==0;} ) == end )
898 continue;
899 if ( filter_verb && std::find_if( start, end,
900 [](const string &s){ return strncmp(s.c_str(),"v",1)==0;} ) == end )
901 continue;
902 ids.push_back( elt.did );
903 ids.push_back( elt.did );
904 ids.push_back( elt.did );
905 data.push_back ( elt.reading );
906 data.push_back ( elt.kanji );
907 data.push_back ( elt.sense );
908 n++;
910 ui_->set_listview( data, ids );
911 std::stringstream ss;
912 ss << n << " results";
913 if ( listview_items_.size() != n )
914 ss << " (" << listview_items_.size()-n << " hidden)";
915 log(ss.str());
916 ui_->set_dic_results( ss.str() );
920 void App::cb_dicview_rightclick ( int did )
922 printf("%d\n",did);
923 string q = "select group_concat(kanji,'') from d_kanji where did="+std::to_string(did);
924 vector<string> res = query( q.c_str() );
925 set<string> kanji;
926 for ( string &c: utils::str_to_chars(res[0].c_str()) )
927 if ( App::get()->rmn()->is_kanji(c.c_str()) )
928 kanji.insert(c);
929 ui_->menu_copy( vector<string>(kanji.begin(),kanji.end()) );
933 void App::set_config ( const string &key, const string &val )
935 cfg_->set( key, val );
936 if ( key == "font/base_size")
937 ui_->font_base_size(std::stoi(val));
938 else if ( key == "log/level" )
939 logger_.loglevel(val);
940 else if ( key == "color/foreground" || key == "color/background" ||
941 key == "color/background2" || key == "color/selection")
942 ui_->init_colors();
943 Fl::check();
947 void App::cb_manage_db ()
949 auto q = query("select val from aoi where key='jmdict_version'");
950 string jmdict_version = (q.empty()) ? "NONE":q[0];
951 q = query("select val from aoi where key='kanjidic_version'");
952 string kanjidic_version = q.empty() ? "NONE":q[0];
953 q = query("select val from aoi where key='components_version'");
954 string components_version = q.empty() ? "NONE":q[0];
955 auto *d = ui_->dlg_manage_db();
956 Fl_Color col;
957 if ( !d->item(MANAGEDB_ITEM_JMDICT) ){
958 col = ( jmdict_version == "NONE" ) ? FL_RED:FL_BLUE;
959 d->add_item( MANAGEDB_ITEM_JMDICT, jmdict_version,
960 "Japanese-English dictionary. Needed component.",
961 col, scb_local_file_jmdict, scb_download_file_jmdict,
962 (void*)this );
964 if ( !d->item(MANAGEDB_ITEM_KANJIDIC) ){
965 col = ( kanjidic_version == "NONE" ) ? FL_RED:FL_BLUE;
966 d->add_item( MANAGEDB_ITEM_KANJIDIC, kanjidic_version,
967 "Kanji dictionary. Needed component.",
968 col, scb_local_file_kanjidic, scb_download_file_kanjidic,
969 (void*)this );
971 if ( !d->item(MANAGEDB_ITEM_COMPONENTS) ){
972 col = ( components_version == "NONE" ) ? FL_RED:FL_BLUE;
973 d->add_item( MANAGEDB_ITEM_COMPONENTS, components_version,
974 "Graphical components of kanji.",
975 col, scb_local_file_components, scb_download_file_components,
976 (void*)this );
978 d->show();
982 void App::load_config ()
984 log("Loading config...");
985 vector<string> res = query("select key,val from config;");
986 for ( size_t i=0; i < res.size(); i+=2 ){
987 set_config( res[i], res[i+1] );
992 void App::save_config ( const std::map<string,aoi_config::Config::Value> &newmap)
994 log("Saving config...");
996 typedef std::pair<string,aoi_config::Config::Value> cfg_pair;
998 // merge new and old config
999 for ( const cfg_pair &p: newmap )
1000 set_config( p.first, p.second.val );
1002 // prepare SQL script
1003 std::stringstream ss;
1004 ss << "BEGIN TRANSACTION;\n";
1005 for ( const cfg_pair &p: get_config_map() )
1006 ss << "REPLACE INTO config (key,val) VALUES('"
1007 << p.first << "','" << p.second.val << "');\n";
1008 ss << "END TRANSACTION;\n";
1010 query(ss.str().c_str());
1012 // apply new fonts and colors
1013 init_dicview();
1015 // TODO:
1016 // check new keys
1017 // check keys in oldconfig not present in newmap
1018 // set oldkeys
1019 // set newkeys
1020 // mnoziny...
1023 } // namespace aoi