OPT added to MAkefile; KanjiView::Cell constructor: fg,bg changed to int (Fl_Color...
[aoi.git] / src / gui_dicview.cxx
blob44bca40efec1ca605a21e314f4dfe4276e2582c5
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 <cstdio>
18 #include <cmath>
19 #include <FL/Fl_Menu_Item.H>
20 #include "gui_dicview.hxx"
21 #include "utils.hxx"
22 #include "logger.hxx"
23 #include <stdexcept>
25 using utils::split_string;
26 using utils::TextTag;
28 namespace aoi_ui {
30 DicView::DicView ( int x, int y, int w, int h, const char *l ): Fl_Table_Row(x,y,w,h,l)
32 when( FL_WHEN_RELEASE|FL_WHEN_CHANGED );
33 table_box( FL_NO_BOX );
34 type(SELECT_SINGLE);
35 cb_leftclick( scb_general, nullptr );
36 cb_rightclick( scb_general, nullptr );
37 cb_doubleclick( scb_general, nullptr );
38 cb_select( scb_general, nullptr );
39 register_tag("default", TextStyle());;
40 register_tag("sep", TextStyle(TextStyle::SEPARATOR_SQUARE) );
41 register_tag("br", TextStyle(TextStyle::NEWLINE_AFTER));
42 cols(3);
43 headers_ = {"", "", ""};
44 col_header(1);
45 col_resize(1);
46 col_resize_min(4);
50 void DicView::set_data ( const vector<string> &d, const vector<int> &cell_ids )
52 // prevents division by zero
53 if ( cols() == 0 )
54 cols(1);
55 // clear view
56 data_.clear();
57 ids_.clear();
58 redraw();
59 // set new data
60 int c = d.size()/cols();
61 rows( (c>1) ? c:1 );
62 cell_heights_.clear();
63 cell_heights_.resize(d.size());
64 data_ = d;
65 ids_ = cell_ids;
66 if ( cell_ids.empty() )
67 ids_ = vector<int>(data_.size(),-1);
68 // recalc cols and rows and redraw
69 draw_cell( CONTEXT_RC_RESIZE, 0, 0, 0, 0 );
70 redraw();
74 int DicView::draw_single_cell ( int R, int C, int X, int Y, int W, int H )
76 int cell = R*cols()+C;
78 if ( cell >= int(data_.size()) ){
79 fl_push_clip(X, Y, W, H);
81 // background
82 fl_color( Fl::get_color( FL_BACKGROUND2_COLOR ) );
83 fl_rectf(X, Y, W, H);
84 // border
85 fl_color(FL_LIGHT2);
86 fl_rect(X, Y, W, H);
88 fl_pop_clip();
89 return -1;
92 int fy = 0;
94 fl_push_clip(X, Y, W, H);
96 // BG COLOR
97 fl_color( row_selected(R) ? FL_LIGHT2:FL_BACKGROUND2_COLOR );
98 fl_rectf(X, Y, W, H);
100 // TEXT
101 // parse data_[cell]
102 auto p = utils::parse_markup(data_.at(cell).c_str());
103 string raw_string = p.first;
104 auto tags = p.second;
105 std::sort( tags.begin(), tags.end() );
106 std::multimap<int,TextTag> tmap;
108 // fill gaps in tag ranges with default tag
109 for ( size_t i=0; i<tags.size(); i++ ) {
110 int prev_end = (i>0) ? tags[i-1].pos + tags[i-1].len:0;
111 if ( prev_end < tags[i].pos )
112 tmap.insert( { prev_end, {"default", prev_end, tags[i].pos-prev_end,
113 static_cast<int>(tmap.size()+1)} } );
114 tmap.insert( {tags[i].pos, tags[i]} );
118 // apply default tag on whole string (if no other tags were defined)
119 int raw_len = strlen(raw_string.c_str());
120 if ( tmap.empty() )
121 tmap.insert( { 0,{ "default", 0, raw_len, -1 } } );
122 // create last default tag if neccessary
123 else {
124 int pos = tags.back().pos + tags.back().len;
125 if ( pos < raw_len )
126 tmap.insert( { pos,{ "default", pos, raw_len-pos,
127 static_cast<int>(tmap.size()+1) }} );
130 for ( auto mi = tmap.begin(); mi != tmap.end(); ++mi ){
131 auto t = mi->second;
132 // remove unneded default
133 if ( t.tag == "default" ){
134 auto bounds = tmap.equal_range( mi->second.pos );
135 for( auto j=bounds.first; j!=bounds.second; j++ ){
136 // XXX: ? nemuze se stat, ze bude 2x stejny default
137 if ( j->second.pos == t.pos && j->second.len == t.len
138 && j->second.tag != "default" )
139 tmap.erase( mi );
144 tags.clear();
145 for ( auto t: tmap )
146 tags.push_back( t.second );
148 // detect the biggest font on the first line
149 // for the first line is better use font size instead of fl_height
150 // because the bigger font is, the bigger fl_height which leads to too
151 // big gaps
152 // (e.g. size=14 : fl_height=16, size=50 : fl_height=61)
153 int total_w = 0;
154 auto mi = tmap.begin();
155 int fh = 0;
156 while ( total_w < W ){
157 if ( mi == tmap.end() )
158 break;
159 TextStyle *style = get_style(mi->second.tag);
160 fl_font( style->font, int(font_size_*style->relsize) );
161 int ffh = style->relsize*font_size_;
162 if ( ffh > fh )
163 fh = ffh;
164 total_w += fl_width("m")*mi->second.len;
165 mi++;
168 // set values needed for calculations
169 int fx = X + cell_padding_x();
170 fy = Y + cell_padding_y() + fh;
172 for ( auto t: tags ){
173 TextStyle *style = get_style(t.tag);
174 fl_font( style->font, int(font_size_*style->relsize) );
175 fl_color( style->color );
176 fh = fl_height();
177 int space_width = fl_width(" ");
178 int fy_correction = 0; // interline spacing when using subscript
180 // separator
181 // inserts: [SPACE]SQUARE[SPACE]
182 if ( style->separator != TextStyle::SEPARATOR_NONE ){
183 Fl_Color prev_color = fl_color();
184 fl_color( style->color );
185 int size = ceil(fh*0.4);
186 fl_rectf( fx + space_width, fy-size, size, size );
187 fx += size + 2*space_width;
188 fl_color( prev_color );
189 continue;
192 // interline space when <sub>
193 if ( style->offset_y > fy_correction )
194 fy_correction = style->offset_y;
197 // draw words
198 for ( auto &ss: split_string( raw_string.substr(t.pos,t.len), " " ) ) {
199 int swidth = fl_width(ss.c_str());
200 // break line
201 if ( fx+swidth-X > W-2*cell_padding_x() ){
202 fx = X + cell_padding_x();
203 fy += fh + fy_correction;
204 fy_correction = 0;
206 fl_draw( ss.c_str(), fx, fy + style->offset_y );
207 fx += swidth + space_width;
210 if ( style->newline == TextStyle::NEWLINE_AFTER ){
211 fx = X + cell_padding_x();
212 fy += fh + 2*fl_descent() + fy_correction;
213 fy_correction = 0;
215 } // END: for tag in tags
216 // END: TEXT
218 fy += cell_padding_y_; // empty space
220 // draw a line between rows (draw line above current cell)
221 if ( R > 0 ){
222 fl_color( Fl::get_color( FL_FOREGROUND_COLOR ) );
223 fl_line(X,Y,X+col_width(C),Y);
227 fl_pop_clip();
229 int this_height = fy-Y+cell_padding_y();
230 cell_heights_[R*C+C] = this_height;
232 if ( C == cols()-1 ){
233 int max_height = 0;
234 for ( int c=0; c<=C; c++ ){
235 int ch = cell_heights_[R*c+c];
236 if ( ch > max_height )
237 max_height = ch;
239 if ( max_height != H ){
240 redraw_row(R,max_height);
244 return this_height;
248 void DicView::redraw_row ( int R, int H )
250 row_height(R,H);
251 for ( int c=0; c<cols(); c++ ){
252 int X = 0;
253 int Y = 0;
254 int W = 0;
255 int H = 0;
256 find_cell( CONTEXT_CELL, R, c, X, Y, W, H);
257 draw_single_cell(R,c,X,Y,W,H);
263 void DicView::draw_cell ( TableContext context, int R, int C, int X, int Y, int W, int H )
265 switch ( context )
267 case CONTEXT_STARTPAGE:
268 fl_font( FL_HELVETICA, font_size());
269 return;
271 case CONTEXT_COL_HEADER:
272 fl_push_clip(X, Y, W, H);
274 fl_draw_box(FL_THIN_UP_BOX, X, Y, W, H, col_header_color());
275 fl_font( FL_HELVETICA, 0.7*col_header_height() );
276 fl_color( FL_BLACK );
277 if ( !headers_.at(C).empty() )
278 fl_draw( headers_.at(C).c_str(), X+5, Y+fl_height() );
280 fl_pop_clip();
281 return;
284 case CONTEXT_CELL:
286 draw_single_cell(R,C,X,Y,W,H);
287 return;
291 case CONTEXT_RC_RESIZE:
293 // adjust last column (sense) width
294 int sum = 0;
295 for ( int c=0; c<cols()-1; c++ )
296 sum+=col_width(c);
297 int scroll = 1.2*vscrollbar->w();
298 if ( !vscrollbar->visible() )
299 scroll = 5;
300 col_width(cols()-1, w() - sum - scroll );
301 return;
304 case CONTEXT_ROW_HEADER:
305 case CONTEXT_ENDPAGE:
306 case CONTEXT_TABLE:
307 case CONTEXT_NONE:
308 default:
309 return;
313 int DicView::handle ( int event )
315 switch ( event ) {
316 case FL_FOCUS:
318 if ( selected_row() == -1 )
319 select_row(0);
320 return 1;
322 case FL_PUSH:
324 int row = row_at_y(Fl::event_y()-y());
325 take_focus();
326 select_row(row);
327 // Fl::belowmouse() ensures that scrollbar will be still usable
328 if ( Fl::event_button() == FL_LEFT_MOUSE && Fl::belowmouse()==this ){
329 if ( Fl::event_clicks() > 1 )
330 cb_doubleclick(this);
331 else
332 cb_leftclick(this);
334 else if ( Fl::event_button() == FL_RIGHT_MOUSE ){
335 cb_rightclick(this);
338 case FL_RELEASE:
340 if ( Fl::event_button() == FL_RIGHT_MOUSE )
341 return 1;
343 case FL_KEYDOWN:
345 int row = selected_row();
346 int r1 = 0;
347 int r2 = 0;
348 int c1 = 0;
349 int c2 = 0;
350 visible_cells(r1,r2,c1,c2);
351 if ( row >= r2 )
352 top_row(r1+1);
353 else if ( row <= r1 && r1 > 0 )
354 top_row(r1-1);
356 switch ( Fl::event_key() ){
357 case FL_Down:
359 if ( row < rows() )
360 select_row(row+1);
361 return 1;
363 case FL_Up:
365 if ( row > 0 )
366 select_row(row-1);
367 return 1;
369 // Page Up/Down are handled by the vertical scrollbar
370 case ' ':
371 case FL_Enter:
372 cb_rightclick(this);
373 return 1;
377 return Fl_Table_Row::handle(event);
381 } // namespace