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/>.
19 #include <FL/fl_ask.H>
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;
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...");
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()) + "'.");
54 ss
<< "ATTACH DATABASE '" << get_config("db/file_user") << "'as user;";
55 query(ss
.str().c_str());
57 ui_
->progress( 30, "Checking tables..." );
59 ui_
->progress( 60, "Checking indexes..." );
66 // ui_->progress(90, "DEBUG query..." );
67 // parse_dic_input("ana*");
69 // ui_->cb_toggle_group(nullptr);
72 ui_
->progress( 98, "Initializing fonts..." );
73 ui_
->fontname_kanji( get_config("font/kanji") );
74 ui_
->help_file( get_config("sources/help_index") );
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" )
88 logger_
.loglevel(Logger::MSG_DEBUG
);
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
)
141 log_e("App::query(): Database does not exist.");
145 vector
<string
> result
;
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
, parsers::SEPARATOR_SQL
, ", ");
161 ui_
->cursor_default();
166 void App::cb_set_components ()
168 if ( curr_components_
.empty() )
171 using aoi_ui::ComponentView
;
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
) );
193 vector
<string
> comps_by_strokes
;
194 for ( auto mi
: curr_components_
){
195 if ( mi
.first
< get_config
<int>("knj/min_compo_count") )
197 comps_by_strokes
.push_back(mi
.second
);
200 struct SortByStrokes
{
201 map
<string
,int> comps
;
202 SortByStrokes( const map
<string
,int> &c
): comps(c
){};
203 bool operator() (const string
&c1
, const string
&c2
){
204 return ( this->comps
[c1
] < this->comps
[c2
] );
208 std::sort( comps_by_strokes
.begin(), comps_by_strokes
.end(), sbc
);
211 for ( string
&s
: comps_by_strokes
){
212 int curr_strokes
= components_
[s
];
213 if ( prev_strokes
!= curr_strokes
)
214 v
.push_back( ComponentView::Cell( std::to_string(curr_strokes
),
215 ComponentView::CELL_LABEL
) );
216 v
.push_back( ComponentView::Cell(s
) );
217 prev_strokes
= curr_strokes
;
221 ui_
->set_components( v
);
225 void App::cb_kanji_search ()
228 if ( components_
.empty() ){
229 log("Loading components...");
230 vector
<string
> res
= query("select component, strokes from components");
231 for ( size_t i
=0; i
<res
.size(); i
=i
+2 ){
232 components_
[res
[i
]] = std::stoi(res
[i
+1]);
237 std::pair
<int,int> strokes
= utils::parse_range( utils::strip( ui_
->strokes() ) );
239 std::pair
<int,int> jlpt
= utils::parse_range( utils::strip( ui_
->jlpt() ) );
241 std::pair
<int,int> grade
= utils::parse_range( utils::strip( ui_
->grade() ) );
246 vector
<string
> skip
= utils::split_string( ui_
->skip() );
247 std::stringstream sskip
;
248 if ( skip
.size() > 0 ){
249 string skip1
= utils::strip(skip
[0].c_str());
250 sskip
<< " S.skip1=" << skip1
;
252 if ( skip
.size() > 1 ){
253 std::pair
<int,int> skip2
= utils::parse_range( utils::strip(skip
[1].c_str()) );
254 sskip
<< " and S.skip2>=" << skip2
.first
<< " and S.skip2<=" << skip2
.second
;
256 if ( skip
.size() > 2 ){
257 std::pair
<int,int> skip3
= utils::parse_range( utils::strip(skip
[2].c_str()) );
258 sskip
<< " and S.skip3>=" << skip3
.first
<< " and S.skip3<=" << skip3
.second
;
260 if ( !sskip
.str().empty() )
265 switch ( ui_
->sort_mode() ){
267 order_by
= "(case freq when 0 then 9999 else freq end) asc";
270 order_by
= "(case freq when 0 then 9999 else freq end) desc";
272 case SORT_STROKES_ASC
:
273 order_by
= "strokes asc";
275 case SORT_STROKES_DESC
:
276 order_by
= "strokes desc";
280 vector
<string
> components_include
= ui_
->components_include();
281 vector
<string
> components_exclude
= ui_
->components_exclude();
282 std::stringstream comps
;
283 for ( auto c
: components_include
)
284 comps
<< " and components like '%" << c
<< "%'";
285 for ( auto c
: components_exclude
)
286 comps
<< " and components not like '%" << c
<< "%'";
287 printf("%s\n",comps
.str().c_str());
290 if ( get_config
<bool>("knj/jis208_only") )
291 jis208
= " flags glob '*jis208*' and ";
294 std::stringstream ss
;
295 ss
<< "select distinct "
296 << "K.kanji,freq,components,flags "
297 << " from k_kanji as K"
298 << (sskip
.str().empty() ? " where ":", k_skip as S where K.kanji=S.kanji and")
301 << " strokes>=" << strokes
.first
<< " and strokes<=" << strokes
.second
302 << " and jlpt>=" << jlpt
.first
<< " and jlpt<=" << jlpt
.second
303 << " and grade>=" << grade
.first
<< " and grade<=" << grade
.second
305 << " order by " << order_by
;
309 vector
<string
> q
= query( ss
.str().c_str(), true, false );
310 vector
<aoi_ui::KanjiView::Cell
> data
;
311 vector
<string
> components
;
313 for ( size_t i
=0; i
<q
.size(); i
+=4 ){
315 int freq
= std::stoi(q
[i
+1]);
317 aoi_ui::KanjiView::Cell(
319 (freq
>0)? get_color("frequent"):-1
322 components
.push_back(q
[i
+2]);
323 for ( string
&s
: utils::split_string( q
[i
+3], parsers::SEPARATOR_SQL
) )
327 log_d("Groups: " + utils::to_string(flags
));
329 ui_
->set_kanjiview( data
);
331 sprintf( b
, "%d results", db_
->result_rows() );
332 ui_
->set_kanji_results( b
);
334 utils::Histogram
<string
> histogram
;
335 for ( string
&s
: components
)
336 histogram
.add( utils::str_to_chars(s
.c_str()) );
337 curr_components_
= histogram
.sorted();
343 void App::alert ( const string
&msg
, const string
&desc
)
345 std::stringstream ss
;
348 ss
<< "\n\nDetails:\n" << desc
;
350 ui_
->alert(ss
.str());
354 int App::run ( int argc
, char **argv
)
356 return ui_
->run(argc
,argv
);
360 void App::check_tables ()
362 for ( auto &dbit
: aoi_config::db_tables
) {
363 // no need for vacuum here, it will be done by check_indexes()
364 log("Checking tables in database: " + string(dbit
.first
));
366 q
<< "SELECT name FROM " << dbit
.first
<< "." << "sqlite_master WHERE type='table'";
367 vector
<string
> existing
= query(q
.str().c_str());
368 // CREATE TABLE name ( column1 TYPE, column2 TYPE, ... )
369 for ( auto &table
: dbit
.second
){
370 if ( utils::is_in(existing
, string(table
.first
)) )
372 log("Creating table " + string(table
.first
));
374 for ( auto &column
: table
.second
){
375 std::stringstream sstr
;
376 sstr
<< column
.name
<< " " << column
.type
;
377 v
.push_back(sstr
.str());
379 std::stringstream ss
;
380 ss
<< "CREATE TABLE " << dbit
.first
<< "." << table
.first
381 << " (" << to_string(v
,",") << ");\n";
382 query(ss
.str().c_str());
389 void App::check_indexes ()
391 bool do_vacuum
= false;
392 log("Checking indexes...");
393 for ( auto &dbit
: aoi_config::db_tables
) {
395 q
<< "SELECT name FROM " << dbit
.first
<< "." << "sqlite_master WHERE type='index'";
396 vector
<string
> existing
= query(q
.str().c_str());
397 // CREATE INDEX idx_table_column ON table ( column ASC )
398 for ( auto &mi
: dbit
.second
){ // tables
399 for ( auto &c
: mi
.second
){ // columns
400 std::stringstream idx_name
;
401 idx_name
<< "idx_" << mi
.first
<< "_" << c
.name
;
402 if ( c
.index
&& !utils::is_in( existing
, idx_name
.str() ) ){
403 log(string("Creating index ") + idx_name
.str());
404 std::stringstream ss
;
405 ss
<< "CREATE INDEX " << idx_name
.str() << " ON " << mi
.first
406 << "(" << c
.name
<< " " << c
.sort
<< ")";
407 query(ss
.str().c_str());
419 void App::cb_dic_input ()
421 parse_dic_input( ui_
->get_dic_input() );
422 ui_
->reset_filters();
426 void App::on_dic_selected ( int id
)
428 log_d("App::on_dic_selected()");
431 void App::cb_edit_word ( int id
)
433 std::stringstream ss
;
434 ss
<< "App::edit_word( " << id
<< " )";
440 void App::cb_popup_kanji ( const string
&kanji
)
442 std::stringstream ss
;
443 ss
<< "App::on_kanji_clicked()" << kanji
;
444 Kanji k
= db_get_kanji(kanji
);
445 ui_
->popup_kanji( k
);
446 // XXX: this should be somewhere else (it is not logical here)
447 ui_
->highlight_components( k
.components() );
452 void App::set_listview ( const vector
<string
> &v
)
455 if ( !listview_items_
.empty() ) listview_items_
.clear();
457 vector
<int> cell_ids
;
459 while ( i
< v
.size() ) {
460 int cell_id
= std::stoi(v
[i
]); // jmdict id
461 cell_ids
.push_back(cell_id
);
462 cell_ids
.push_back(cell_id
);
463 cell_ids
.push_back(cell_id
);
466 for ( string
&elt
: utils::split_string( v
[i
+1], ",") )
467 if ( elt
.size() > 0 )
468 pos
.insert(utils::strip(elt
.c_str()));
469 d
.push_back( v
[i
+2] ); // reading
470 d
.push_back( v
[i
+3] ); // kanji
471 d
.push_back( v
[i
+4] ); // sense
472 listview_items_
.push_back( {cell_id
, pos
, v
[i
+2], v
[i
+3], v
[i
+4]} );
476 sprintf( buff
, "%d results", db_
->result_rows() );
477 ui_
->set_dic_results( buff
);
478 ui_
->set_listview(d
,cell_ids
);
482 void App::parse_dic_input ( const char *str
)
484 log_d(string("App::parse_dic_input: \"") +string(str
) + string("\""));
486 string stripped
= utils::strip(str
);
488 if ( stripped
.empty() )
491 const char *s
= stripped
.c_str();
494 if ( s
[0] != SENSE_SEARCH_CHAR
){
495 DictionaryInputParser p
;
500 "Too broad search.\n"\
501 "Your computer may become unresponsible for a long time.\n"\
507 if ( res
== 1 ) // Cancel
511 if ( !strchr(s
,'*') && !strchr(s
,'?') && !strchr(s
,'[') && !strchr(s
,'{')
517 if ( s
[0] == SENSE_SEARCH_CHAR
){
519 << "group_concat(pos) as pos,"
520 << q_reading("d_sense.did")
521 << q_kanji("d_sense.did")
523 << " from d_sense where gloss glob '*" << stripped
.substr(1) << "*'"
526 else if ( rmn_
->contains_kanji( qq
.c_str() ) ) {
527 q
<< "select d_kanji.did as did,"
528 << "(select group_concat(pos) from d_sense where d_sense.did = d_kanji.did) as pos,"
530 << q_reading("d_kanji.did")
531 << q_sense("d_kanji.did")
532 << "from d_kanji where "
533 << "kanji glob '" << rmn_
->romaji_to_hiragana(qq
.c_str()) << "' "
534 << " or kanji glob '" << rmn_
->romaji_to_katakana(qq
.c_str()) << "' "
535 << " group by did order by d_kanji.freq desc, d_kanji.kanji asc";
538 q
<< "select d_reading.did as did,"
539 << "(select group_concat(pos) from d_sense where d_sense.did = d_reading.did) as pos,"
541 << q_kanji("d_reading.did")
542 << q_sense("d_reading.did")
543 << "from d_reading where "
544 << "reading glob '" << rmn_
->romaji_to_hiragana(qq
.c_str())
545 << "' or reading glob '"<< rmn_
->romaji_to_katakana(qq
.c_str()) << "' "
546 << " group by did order by d_reading.freq desc, d_reading.reading asc";
548 set_listview(query(q
.str().c_str()));
553 Kanji
App::db_get_kanji ( const string
&kanji
)
555 log("App::db_get_kanji()");
557 q
<< "select kanji,strokes,ucs, rad_classic, rad_nelson, "
558 << "jlpt, grade, freq, onyomi, kunyomi, nanori, meaning,flags,components "
559 << "from k_kanji where kanji='" << kanji
<< "';";
561 vector
<string
> res
= query(q
.str().c_str());
563 kk
.strokes(std::stoi(res
[1]));
565 kk
.rad_classic(std::stoi(res
[3]));
566 kk
.rad_nelson( (res
[4].empty()) ? -1:std::stoi(res
[4]));
567 kk
.jlpt(std::stoi(res
[5]));
568 kk
.grade(std::stoi(res
[6]));
569 kk
.freq(std::stoi(res
[7]));
570 kk
.onyomi( utils::split_string(res
[8],parsers::SEPARATOR_SQL
) );
571 kk
.kunyomi( utils::split_string(res
[9],parsers::SEPARATOR_SQL
) );
572 kk
.nanori( utils::split_string(res
[10],parsers::SEPARATOR_SQL
) );
573 kk
.meaning( utils::split_string(res
[11],parsers::SEPARATOR_SQL
) );
574 kk
.flags( utils::split_string(res
[12],parsers::SEPARATOR_SQL
) );
575 kk
.components( res
[13] );
577 string qq
= "select skip1,skip2,skip3,misclass from k_skip where kanji='"+kanji
+"';";
578 vector
<string
> res2
= query(qq
.c_str());
579 if ( res2
.size() % 4 != 0 ){
580 std::stringstream ss
;
581 ss
<< "Wrong SKIP count. Kanji: " << kanji
582 << "Query result size: " << res2
.size()
583 << " (should be 4,8 or 12). SKIP not loaded.";
587 for ( size_t i
=0; i
< res2
.size(); i
+=4 )
588 kk
.skip( res2
[i
], res2
[i
+1], res2
[i
+2], res2
[i
+3] );
593 void App::cb_filter_listview ()
595 vector
<string
> pos
= ui_
->listview_filters();
596 log_d("App::cb_filter_listview(): pos: " + utils::to_string(pos
));
597 bool filter_expr
= ( utils::is_in( pos
, string("expr") ) );
598 bool filter_noun
= ( utils::is_in( pos
, string("noun") ) );
599 bool filter_verb
= ( utils::is_in( pos
, string("verb") ) );
600 bool filter_adj
= ( utils::is_in( pos
, string("adj") ) );
604 for ( auto &elt
: listview_items_
){
605 auto start
= elt
.pos
.begin();
606 auto end
= elt
.pos
.end();
607 if ( filter_expr
&& std::find( start
, end
, "exp") == end
)
609 if ( filter_noun
&& std::find( start
, end
, "n" ) == end
)
611 if ( filter_adj
&& std::find_if( start
, end
,
612 [](const string
&s
){ return strncmp(s
.c_str(),"adj",3)==0;} ) == end
)
614 if ( filter_verb
&& std::find_if( start
, end
,
615 [](const string
&s
){ return strncmp(s
.c_str(),"v",1)==0;} ) == end
)
617 ids
.push_back( elt
.did
);
618 ids
.push_back( elt
.did
);
619 ids
.push_back( elt
.did
);
620 data
.push_back ( elt
.reading
);
621 data
.push_back ( elt
.kanji
);
622 data
.push_back ( elt
.sense
);
625 ui_
->set_listview( data
, ids
);
626 std::stringstream ss
;
627 ss
<< n
<< " results";
628 if ( listview_items_
.size() != n
)
629 ss
<< " (" << listview_items_
.size()-n
<< " hidden)";
631 ui_
->set_dic_results( ss
.str() );
635 void App::cb_dicview_rightclick ( int did
)
637 string q
= "select group_concat(kanji,'') from d_kanji where did="+std::to_string(did
);
638 vector
<string
> res
= query( q
.c_str() );
640 for ( string
&c
: utils::str_to_chars(res
[0].c_str()) )
641 if ( App::get()->rmn()->is_kanji(c
.c_str()) )
643 ui_
->dicview_menu( did
, vector
<string
>(kanji
.begin(),kanji
.end()), false, false );
647 void App::apply_config ()
649 ui_
->font_base_size( get_config
<int>("font/base_size"));
650 logger_
.loglevel( get_config("log/level") );
656 void App::cb_manage_db ()
658 auto q
= query("select val from aoi where key='jmdict_version'");
659 string jmdict_version
= (q
.empty()) ? "NONE":q
[0];
660 q
= query("select val from aoi where key='kanjidic_version'");
661 string kanjidic_version
= q
.empty() ? "NONE":q
[0];
662 q
= query("select val from aoi where key='components_version'");
663 string components_version
= q
.empty() ? "NONE":q
[0];
664 auto *d
= ui_
->dlg_manage_db();
665 d
->main_version("MAIN VERSION");
666 d
->main_created("ONCE UPON A TIME...");
667 d
->main_ver_jmdict("JMDICT");
668 d
->main_ver_kanjidic("KANJIDIC");
669 d
->main_ver_kradfile("KRADFILE");
670 d
->main_ver_tatoeba("TATOEBA");
671 d
->user_checked_against("WHO KNOWS");
676 void App::load_config ()
678 log("Loading config...");
679 vector
<string
> res
= query("select key,val from config;");
680 for ( size_t i
=0; i
< res
.size(); i
+=2 ){
681 set_config( res
[i
], res
[i
+1] );
687 void App::save_config ( const std::map
<string
,aoi_config::Config::Value
> &newmap
)
689 log("Saving config...");
691 typedef std::pair
<string
,aoi_config::Config::Value
> cfg_pair
;
693 // merge new and old config
694 for ( const cfg_pair
&p
: newmap
)
695 set_config( p
.first
, p
.second
.val
);
698 // prepare SQL script
699 std::stringstream ss
;
700 ss
<< "BEGIN TRANSACTION;\n";
701 ss
<< "DROP TABLE IF EXISTS user.config;\n";
702 ss
<< "CREATE TABLE user.config (key TEXT, val TEXT);\n";
703 for ( const cfg_pair
&p
: get_config_map() )
704 ss
<< "INSERT INTO user.config (key,val) VALUES('"
705 << p
.first
<< "','" << p
.second
.val
<< "');\n";
706 ss
<< "END TRANSACTION;\n";
708 query(ss
.str().c_str());
710 // apply new fonts and colors
715 // check keys in oldconfig not present in newmap