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