1 /* karteibutton.cc
2 * This file belongs to Worker, a file manager for UN*X/X11.
3 * Copyright (C) 2004-2019 Ralf Hoffmann.
4 * You can contact me at: ralf@boomerangsworld.de
5 * or http://www.boomerangsworld.de/worker
6 *
7 * This program is free software; you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License as published by
9 * the Free Software Foundation; either version 2 of the License, or
10 * (at your option) any later version.
11 *
12 * This program is distributed in the hope that it will be useful,
13 * but WITHOUT ANY WARRANTY; without even the implied warranty of
14 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 * GNU General Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License
18 * along with this program; if not, write to the Free Software
19 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
20 */
21
22 #include "karteibutton.h"
23 #include "awindow.h"
24 #include "guielement.h"
25 #include "kartei.h"
26 #include "drawablecont.hh"
27
28 const char *KarteiButton::type="KarteiButton";
29
~KarteiButton()30 KarteiButton::~KarteiButton()
31 {
32 }
33
KarteiButton(AGUIX * taguix,int tx,int ty,int width,int tdata)34 KarteiButton::KarteiButton( AGUIX *taguix,
35 int tx,
36 int ty,
37 int width,
38 int tdata ) : CycleButton( taguix,
39 tx,
40 ty,
41 width,
42 tdata )
43 {
44 int bw;
45
46 optionChangeCallback = NULL;
47 k2 = NULL;
48 focusPos = 0;
49
50 bw = 2;
51
52 _h = taguix->getCharHeight() + 2 * bw;
53
54 fg = _aguix->getFaces().getColor( "default-fg" );
55 bg = _aguix->getFaces().getColor( "default-bg" );
56 }
57
KarteiButton(AGUIX * taguix,int tx,int ty,int width,int height,int tdata)58 KarteiButton::KarteiButton( AGUIX *taguix,
59 int tx,
60 int ty,
61 int width,
62 int height,
63 int tdata ) : CycleButton( taguix,
64 tx,
65 ty,
66 width,
67 height,
68 tdata )
69 {
70 optionChangeCallback = NULL;
71 k2 = NULL;
72 focusPos = 0;
73
74 fg = _aguix->getFaces().getColor( "default-fg" );
75 bg = _aguix->getFaces().getColor( "default-bg" );
76 }
77
redraw()78 void KarteiButton::redraw()
79 {
80 GC usegc;
81 int ch;
82 int basex, basey, midy, delx, i, dx, dx2, sx, sy, ex, ey;
83 int act_x1 = -1, act_x2 = -1;
84
85 if ( isCreated() == false ) return;
86 if ( win == 0 ) return;
87
88 _aguix->SetWindowBG( win, bg );
89 _aguix->ClearWin( win );
90
91 if ( font == NULL ) usegc = 0; else usegc = font->getGC();
92
93 basey = _h - 1;
94 midy = _h / 2 - 1;
95 delx = _h / 4;
96
97 if ( font == NULL ) {
98 ch = _aguix->getCharHeight();
99 } else {
100 ch = font->getCharHeight();
101 }
102
103 DrawableCont dc( _aguix, win );
104 AFontWidth lencalc( _aguix, font );
105
106 m_width_per_option.clear();
107 m_width_per_option.resize( getNrOfOptions(), 0 );
108
109 if ( m_shrinker.getVal() != NULL ) {
110 // calculate widths for each option
111 int rem_width = 0;
112 int rem_options = getNrOfOptions();
113
114 rem_width = getWidth() - 2 * delx - rem_options * 4 * delx;
115
116 std::vector<int> needed_diff;
117
118 needed_diff.resize( rem_options, 0 );
119
120 int additional_width = 0;
121 int sum_needed_diff = 0;
122
123 // first consider equal distribution
124 // but store needed width for longer options
125 // and additional width for shorter options
126 for ( int i = 0; i < (int)options.size(); i++ ) {
127 int use_width = rem_width / rem_options;
128 int full_width = _aguix->getTextWidth( options[i].c_str(), font );
129 if ( full_width <= use_width ) {
130 m_width_per_option[i] = full_width;
131 needed_diff[i] = 0;
132 additional_width += use_width - full_width;
133 } else {
134 m_width_per_option[i] = use_width;
135 needed_diff[i] = full_width - use_width;
136 sum_needed_diff += needed_diff[i];
137 }
138
139 // only consider use_width and not actual value used
140 rem_width -= use_width;
141 rem_options--;
142 }
143
144 // now distribute additional_width to longer options
145 if ( sum_needed_diff <= additional_width ) {
146 // more space available than needed
147 // just use max values
148 for ( int i = 0; i < (int)options.size(); i++ ) {
149 if ( needed_diff[i] > 0 ) {
150 m_width_per_option[i] += needed_diff[i];
151 }
152 }
153 } else{
154 int assigned_width = 0;
155
156 // based on the fraction of needed space
157 // assign additional width
158 for ( int i = 0; i < (int)options.size(); i++ ) {
159 if ( needed_diff[i] > 0 ) {
160 int tw = needed_diff[i] * additional_width;
161 tw /= sum_needed_diff;
162 m_width_per_option[i] += tw;
163 needed_diff[i] -= tw;
164 assigned_width += tw;
165 }
166 }
167
168 // if there are some unassigned pixels left (due to rounding
169 // errors) just run over all entries and add 1 if needed
170 for ( int i = 0; i < (int)options.size(); i++ ) {
171 if ( additional_width - assigned_width <= 0 ) break;
172 if ( needed_diff[i] > 0 ) {
173 m_width_per_option[i]++;
174 assigned_width++;
175 }
176 }
177 }
178 } else {
179 for ( int i = 0; i < (int)options.size(); i++ ) {
180 m_width_per_option[i] = _aguix->getTextWidth( options[i].c_str(), font );
181 }
182 }
183
184 // make two runs
185 // first to draw the bg for inactive options in dark grey
186 // second to draw lines and text
187 for ( int run = 0; run < 2; run++ ) {
188 basex = 0;
189 for ( i = 0; i < (int)options.size(); i++ ) {
190 sx = basex;
191 dx = 2 * delx;
192
193 if ( ( i != 0 ) && ( i != act_opt ) ) {
194 sx += delx + 1;
195 dx = delx - 1;
196 }
197
198 if ( ( i == 0 ) ||
199 ( i == act_opt ) ) {
200 sy = basey;
201 } else {
202 sy = midy;
203 }
204
205 if ( run == 0 ) {
206 // left triangle
207 if ( i != act_opt ) {
208 // dark grey
209 _aguix->setFG( usegc, _aguix->getFaces().getAGUIXColor( "tab-inactive-bg" ) );
210
211 // top part of the triangle is always visible
212 _aguix->DrawTriangleFilled( win, usegc, sx , sy, sx + dx, sy, sx + dx, 0 );
213 if ( i != 0 ) {
214 // for any option but first the bottom part is mostly hidden by
215 // previous option so just draw visible part (which is
216 // actually a triangle itself)
217 _aguix->DrawTriangleFilled( win, usegc, sx , sy, sx + dx, sy, sx + dx, basey );
218 }
219 } else if ( i == act_opt ) {
220 _aguix->setFG( usegc, _aguix->getFaces().getAGUIXColor( "tab-active-bg" ) );
221
222 // for active option overdraw right part of previous option
223 _aguix->DrawTriangleFilled( win, usegc, basex , basey, sx + dx, basey, sx + dx, 0 );
224 }
225 } else {
226 _aguix->setFG( usegc, _aguix->getFaceCol_3d_bright() );
227 _aguix->DrawLine( win, usegc,
228 sx, sy,
229 sx + dx, 0 );
230 }
231
232 const char *tstr = options[i].c_str();
233
234 dx2 = m_width_per_option[i];
235 dx2 += 2 * delx;
236
237 if ( run == 0 ) {
238 if ( i != act_opt ) {
239 // dark grey
240 // this is the bg of an inactive option
241 _aguix->setFG( usegc, _aguix->getFaces().getAGUIXColor( "tab-inactive-bg" ) );
242 } else {
243 _aguix->setFG( usegc, _aguix->getFaces().getAGUIXColor( "tab-active-bg" ) );
244 }
245 _aguix->FillRectangle( win, usegc, sx + dx, 0, dx2, basey + 1 );
246 } else {
247 _aguix->DrawLine( win, usegc,
248 sx + dx, 0,
249 sx + dx + dx2, 0 );
250 }
251
252 if ( ( i + 1 ) == act_opt ) {
253 ex = sx + dx + dx2 + delx;
254 ey = midy;
255 } else {
256 ex = sx + dx + dx2 + 2 * delx;
257 ey = basey;
258 }
259
260 if ( run == 0 ) {
261 if ( i != act_opt ) {
262 // dark grey
263 _aguix->setFG( usegc, _aguix->getFaces().getAGUIXColor( "tab-inactive-bg" ) );
264 } else {
265 _aguix->setFG( usegc, _aguix->getFaces().getAGUIXColor( "tab-active-bg" ) );
266 }
267
268 // finally the right triangle
269 _aguix->DrawTriangleFilled( win, usegc, sx + dx + dx2 , 0, sx + dx + dx2, basey, sx + dx + dx2 + 2 * delx, basey );
270 } else {
271 _aguix->setFG( usegc, _aguix->getFaceCol_3d_dark() );
272 _aguix->DrawLine( win, usegc,
273 sx + dx + dx2, 0,
274 ex, ey );
275 }
276
277 if ( i == act_opt ) {
278 act_x1 = basex;
279 act_x2 = ex;
280 }
281
282 if ( run != 0 ) {
283 AGUIXColor col = _aguix->getFaces().getAGUIXColor( "tab-inactive-fg" );
284 if ( i == act_opt ) {
285 col = _aguix->getFaces().getAGUIXColor( "tab-active-fg" );
286 }
287
288 if ( m_shrinker.getVal() != NULL ) {
289 std::string current_text = m_shrinker->shrink( tstr, m_width_per_option[i], lencalc );
290 _aguix->DrawText( dc, font, current_text.c_str(), sx + dx + delx, 1,
291 col );
292 } else {
293 _aguix->DrawText( dc, font, tstr, sx + dx + delx, 1,
294 col );
295 }
296 }
297
298 if ( run != 0 ) {
299 if ( ( getAcceptFocus() == true ) &&
300 ( getHasFocus() == true ) &&
301 ( focusPos == i ) ) {
302 _aguix->setDottedFG( _aguix->getFaces().getAGUIXColor( "tab-active-fg" ) );
303 _aguix->DrawDottedRectangle( win, sx + dx + delx - 2, 1, dx2 - 2 * delx + 4, 1 + ch + 1 );
304 }
305 }
306
307 basex = sx + dx + dx2;
308 }
309 }
310
311 if ( ( act_x1 >= 0 ) && ( act_x2 > act_x1 ) ) {
312 _aguix->setFG( usegc, _aguix->getFaceCol_3d_bright() );
313 _aguix->DrawLine( win, usegc, 0, basey, act_x1, basey );
314 _aguix->DrawLine( win, usegc, act_x2 + 1, basey, _w, basey );
315 }
316
317 _aguix->Flush();
318 }
319
flush()320 void KarteiButton::flush()
321 {
322 }
323
handleMessage(XEvent * E,Message * msg)324 bool KarteiButton::handleMessage(XEvent *E,Message *msg)
325 {
326 bool returnvalue;
327 AGMessage *agmsg;
328
329 if ( isCreated() == false ) return false;
330
331 returnvalue = false;
332
333 if ( ( msg->type == ButtonPress ) ||
334 ( msg->type == ButtonRelease ) ) {
335 if ( msg->window == win ) {
336 int mx, opt;
337
338 takeFocus();
339
340 mx = msg->mousex;
341 opt = findClickedOption( mx );
342 if(msg->type==ButtonPress) {
343 if ( opt >= 0 ) {
344 focusPos = opt;
345 //redraw();
346 }
347 setState( 1 );
348 instate = 1;
349 returnvalue = true;
350 } else {
351 if ( ( state != 0 ) &&
352 ( instate != 0 ) &&
353 ( opt >= 0 ) ) {
354 if ( options.size() > 0 ) {
355 if ( msg->button == Button1 ) {
356 act_opt = opt;
357 if ( act_opt >= (int)options.size() ) act_opt = 0;
358 }
359 }
360
361 agmsg = AGUIX_allocAGMessage();
362 agmsg->type=AG_KARTEIBUTTONCLICKED;
363 agmsg->karteibutton.karteibutton=this;
364 agmsg->karteibutton.option = opt;
365 agmsg->karteibutton.mousebutton = msg->button;
366 msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
367
368 if ( ( optionChangeCallback != NULL ) && ( act_opt >= 0 ) ) {
369 optionChangeCallback( k2, (unsigned int)act_opt );
370 }
371 }
372 if ( instate != 0 ) {
373 setState( 0 );
374 instate = 0;
375 returnvalue = true;
376 }
377 }
378 }
379 } else if ( msg->type == EnterNotify ) {
380 // alles hier und alles mit instate wird benutzt, damit Button sich anpa�t, wenn
381 // Mauszeiger im Button oder au�erhalb des Buttons ist
382 if ( msg->window == win ) {
383 if ( instate != 0 ) {
384 if ( state != instate ) {
385 setState( instate );
386 }
387 }
388 }
389 } else if ( msg->type == LeaveNotify ) {
390 // alles hier und alles mit instate wird benutzt, damit Button sich anpa�t, wenn
391 // Mauszeiger im Button oder au�erhalb des Buttons ist
392 if ( msg->window == win ) {
393 if ( instate != 0 ) {
394 setState( 0 );
395 }
396 }
397 } else if ( msg->type == Expose ) {
398 if ( msg->window == win ) {
399 redraw();
400 }
401 } else if ( msg->type == KeyPress ) {
402 if ( ( getAcceptFocus() == true ) && ( getHasFocus() == true ) ) {
403 if ( options.size() > 0 ) {
404 if ( isVisible() == true ) {
405 if ( _parent->isTopParent( msg->window ) == true ) {
406 switch ( msg->key ) {
407 case XK_space:
408 if ( act_opt != focusPos ) {
409 act_opt = focusPos;
410 if ( act_opt >= (int)options.size() ) act_opt = 0;
411
412 agmsg = AGUIX_allocAGMessage();
413 agmsg->type=AG_KARTEIBUTTONCLICKED;
414 agmsg->karteibutton.karteibutton=this;
415 agmsg->karteibutton.option=act_opt;
416 agmsg->karteibutton.mousebutton = 0;
417 msgAndCB( std::unique_ptr<AGMessage>( agmsg ) );
418
419 if ( ( optionChangeCallback != NULL ) && ( act_opt >= 0 ) ) {
420 optionChangeCallback( k2, (unsigned int)act_opt );
421 }
422 if ( instate != 0 ) {
423 setState( 0 );
424 instate = 0;
425 returnvalue = true;
426 } else redraw(); // setState will make this so just in the else-case
427 }
428 break;
429 case XK_Left:
430 if ( focusPos > 0 ) {
431 focusPos--;
432 redraw();
433 }
434 break;
435 case XK_Right:
436 if ( ( focusPos + 1 ) < (int)options.size() ) {
437 focusPos++;
438 redraw();
439 }
440 break;
441 }
442 }
443 }
444 }
445 }
446 }
447 if ( returnvalue == true ) {
448 // jetzt noch die Message mit den Werten f�llen
449 msg->gadget = this;
450 msg->gadgettype = BUTTON_GADGET;
451 }
452 // return returnvalue;
453 return false; // we return false because an other element can take use of
454 // this message (f.e. StringGagdet for deactivating)
455 }
456
getType() const457 const char *KarteiButton::getType() const
458 {
459 return type;
460 }
461
isType(const char * qtype) const462 bool KarteiButton::isType(const char *qtype) const
463 {
464 if(strcmp(type,qtype)==0) return true;
465 return false;
466 }
467
getMaxSize() const468 int KarteiButton::getMaxSize() const
469 {
470 const char *tstr;
471 int delx, dx2, tw;
472
473 delx = _h / 4;
474 tw = 2 * delx;
475
476 for ( unsigned int i = 0; i < options.size(); i++ ) {
477 tstr = options[i].c_str();
478 dx2 = _aguix->getTextWidth( tstr, font );
479 dx2 += 2 * delx;
480
481 tw += dx2 + 2 * delx;
482 }
483 return tw + 1;
484 }
485
setOptionChangeCallback(void (* optionChangeCallback_arg)(class Kartei * k1,unsigned int option),Kartei * k2_arg)486 void KarteiButton::setOptionChangeCallback( void (*optionChangeCallback_arg)( class Kartei *k1,
487 unsigned int option ),
488 Kartei *k2_arg )
489 {
490 optionChangeCallback = optionChangeCallback_arg;
491 k2 = k2_arg;
492 }
493
findClickedOption(int mx)494 int KarteiButton::findClickedOption( int mx )
495 {
496 int delx, i, dx2, tw, basex;
497 int opt = -1;
498
499 delx = _h / 4;
500 basex = 0;
501
502 if ( (int)m_width_per_option.size() < getNrOfOptions() ) return opt;
503
504 for ( i = 0; i < (int)options.size(); i++ ) {
505 dx2 = m_width_per_option[i];
506 dx2 += 2 * delx;
507
508 tw = dx2 + 2 * delx;
509 if ( ( i == 0 ) || ( i == (int)options.size() - 1 ) ) {
510 tw += delx;
511 }
512
513 if ( ( mx >= basex ) &&
514 ( mx < ( basex + tw ) ) ) {
515 opt = i;
516 break;
517 }
518
519 basex += tw;
520 }
521 return opt;
522 }
523
setOption(int nv)524 void KarteiButton::setOption( int nv )
525 {
526 int old_opt = act_opt;
527
528 if ( ( nv >= 0 ) && ( options.size() > 0 ) ) {
529 act_opt = nv;
530 if ( act_opt >= (int)options.size() ) act_opt = 0;
531
532 if ( old_opt != act_opt ) {
533 if ( ( optionChangeCallback != NULL ) && ( act_opt >= 0 ) ) {
534 optionChangeCallback( k2, (unsigned int)act_opt );
535 }
536 redraw();
537 }
538 }
539 }
540
setTextShrinker(RefCount<TextShrinker> shrinker)541 void KarteiButton::setTextShrinker( RefCount<TextShrinker> shrinker )
542 {
543 m_shrinker = shrinker;
544 }
545
setFont(const char * fontname)546 int KarteiButton::setFont( const char *fontname )
547 {
548 int bw = 2;
549
550 int res = CycleButton::setFont( fontname );
551
552 if ( font == NULL ) {
553 resize( getWidth(), _aguix->getCharHeight() + 2 * bw );
554 } else {
555 resize( getWidth(), font->getCharHeight() + 2 * bw );
556 }
557
558 return res;
559 }
560