/* popupmenu.cc
 * This file belongs to Worker, a file manager for UN*X/X11.
 * Copyright (C) 2007-2023 Ralf Hoffmann.
 * You can contact me at: ralf@boomerangsworld.de
 *   or http://www.boomerangsworld.de/worker
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
 */

#include "popupmenu.hh"
#include <iostream>
#include <stdlib.h>
#include "drawablecont.hh"
#include "utf8.hh"
#include <stdexcept>

PopUpMenu::PopUpMenu( AGUIX *parent,
                      const std::list<PopUpEntry> &entries,
                      int group_id ) : PopUpWindow( parent,
                                                    0, 0,
                                                    10, 10,
                                                    "",
                                                    group_id ),
                                       m_highlighted_entry( -1 ),
                                       m_counter( 0 ),
                                       m_last_motion_x( 0 ),
                                       m_last_motion_y( 0 ),
                                       m_ignore_non_vertical_motion( false ),
                                       m_open_after_timeout( true ),
                                       m_keyboard_focus( false ),
                                       m_parent_menu( NULL ),
                                       m_grabbed_keyboard( -1 ),
                                       m_grabbed_pointer( -1 ),
                                       m_border_width( 2 ),
                                       m_ignore_button_release( true ),
                                       m_infix_filter( "" ),
                                       m_edit_entry( -1 )
{
    std::list<PopUpEntry>::const_iterator it1;

    for ( it1 = entries.begin();
          it1 != entries.end();
          it1++ ) {
        InternPopUpEntry e;

        e.name = it1->name;
        e.help = it1->help;
        e.type = it1->type;
        e.id = it1->id;
        e.fg = it1->fg;
        e.bg = it1->bg;
        e.highlight_on_show = it1->highlight_on_show;

        if ( it1->type == SUBMENU ) {
            e.submenu = new PopUpMenu( parent, it1->submenu, getGroupID() );
            e.submenu->setParentMenu( this );
        }
        m_entries.push_back( e );
    }

    calcEntryDimensions();
}

PopUpMenu::~PopUpMenu()
{
    if ( m_help_cb ) {
        m_help_cb( "" );
    }

    for ( std::vector<InternPopUpEntry>::iterator it1 = m_entries.begin();
          it1 != m_entries.end();
          it1++ ) {
        if ( it1->submenu != NULL ) {
            delete it1->submenu;
        }
    }
}

void PopUpMenu::show()
{
    show( 0, 0, POPUP_UNDER_MOUSE );
}

void PopUpMenu::show( int x, int y, popup_show_t show_type )
{
    if ( isCreated() == true ) {

        switch ( show_type ) {
          case POPUP_UNDER_MOUSE:
              {
                  int mx, my;
                  
                  _aguix->queryRootPointer( &mx, &my );
                  
                  move( mx, my );
              }
              break;
          case POPUP_IN_POSITION:
              move( x, y );
              break;
          case POPUP_CENTER_IN_POSITION:
              move( x - getWidth() / 2, y - getHeight() / 2 );
              break;
          case POPUP_CENTER_FIRST_AROUND_POS:
              //TODO
              break;
          default:
              break;
        }
        
        if ( m_parent_menu == NULL ) {
            setKeyboardFocus( true );
        }
        
        if ( getKeyboardFocus() ) {
            for ( size_t entry = 0;
                  entry < m_entries.size();
                  entry++ ) {
                if ( m_entries[entry].highlight_on_show ) {
                    setHighlightEntry( entry );
                }
            }
        }

        PopUpWindow::show();
        //TODO handle positions
        // make sure it's on screen (open it to the left instead of to the
        // right if it doesn't fit on the screen...)

        if ( m_parent_menu == NULL ) {
            /* Note: setting owner_event to False makes X to send all keyboard events
               with the given window as generating window. Setting it to true
               will send the events as usual with the currently focused window
               as  the generating window. */

            checkAndGrabKeyboard();

            m_grabbed_pointer = XGrabPointer( _aguix->getDisplay(),
                                              RootWindow( _aguix->getDisplay(),
                                                          _aguix->getScreen() ),
                                              True,
                                              ButtonMotionMask | ButtonReleaseMask | ButtonPressMask,
                                              GrabModeAsync,
                                              GrabModeAsync,
                                              None,
                                              None,
                                              CurrentTime );
        }
        m_ignore_button_release = true;

        if ( m_focus_out_ignore_time_ms > 0 ) {
            m_show_time_ms = AGUIXUtils::current_time_ms();
        }
    }
}

void PopUpMenu::hide()
{
    bool send_msg = false;

    if ( m_help_cb ) {
        m_help_cb( "" );
    }
    
    if ( m_parent_menu == NULL && isVisible() == true ) {
        send_msg = true;
    }
    
    if ( m_parent_menu == NULL ) {
        if ( m_grabbed_pointer == GrabSuccess ) {
            XUngrabPointer( _aguix->getDisplay(),
                            CurrentTime );
            m_grabbed_pointer = -1;
        }
        if ( m_grabbed_keyboard == GrabSuccess ) {
            XUngrabKeyboard( _aguix->getDisplay(),
                             CurrentTime );
            m_grabbed_keyboard = -1;
        }
    }

    PopUpWindow::hide();

    for ( std::vector<InternPopUpEntry>::iterator it1 = m_entries.begin();
          it1 != m_entries.end();
          it1++ ) {
        if ( it1->submenu != NULL ) {
            it1->submenu->hide();
        }
    }
    
    if ( m_keyboard_focus == true ) {
        setKeyboardFocus( false );
        if ( m_parent_menu != NULL ) {
            m_parent_menu->setKeyboardFocus( true );
        }
    }
    
    m_highlighted_entry = -1;
    
    if ( send_msg == true ) {
        AGMessage *agmsg = AGUIX_allocAGMessage();
        agmsg->popupmenu.menu = this;
        agmsg->popupmenu.recursive_ids = NULL;
        agmsg->popupmenu.clicked_entry_id = -1;
        agmsg->type = AG_POPUPMENU_CLOSED;
        _aguix->putAGMsg( agmsg );
    }
    
    m_infix_filter = "";
    m_edit_entry = -1;

    disableTimer();
}

int PopUpMenu::create()
{
    int erg = PopUpWindow::create();

    for ( std::vector<InternPopUpEntry>::iterator it1 = m_entries.begin();
          it1 != m_entries.end();
          it1++ ) {
        if ( it1->submenu != NULL ) {
            it1->submenu->create();
        }
    }

    XWindowAttributes curattr;
    
    XGetWindowAttributes( _aguix->getDisplay(), win, &curattr );
    XSelectInput( _aguix->getDisplay(), win,
                  curattr.your_event_mask |
                  PointerMotionMask |
                  EnterWindowMask |
                  LeaveWindowMask |
                  FocusChangeMask |
                  StructureNotifyMask );
    
    setDimensions();

    return erg;
}

void PopUpMenu::setDimensions()
{
    int new_w, new_h;

    new_h = 0;
    new_w = 10;

    int submenu_char_w = _aguix->getCharHeight();

    int entry_pos = 0;

    for ( std::vector<InternPopUpEntry>::iterator it1 = m_entries.begin();
          it1 != m_entries.end();
          it1++, entry_pos++ ) {

        int tw;
        if ( it1->type == EDITABLE ) {
            tw = _aguix->getTextWidth( it1->name.c_str() );
            int tw2 = _aguix->getTextWidth( it1->editable_content.c_str() );
            if ( tw2 > tw ) tw = tw2;
        } else {
            tw = _aguix->getTextWidth( it1->name.c_str() );
        }

        tw += 5;

        if ( it1->type == SUBMENU ) tw += submenu_char_w;

        if ( tw > new_w ) new_w = tw;
            
        new_h += getEntryHeight( entry_pos );
    }

    new_w += 2 * getBorderWidth();
    new_h += 2 * getBorderWidth();

    resize( new_w, new_h );
}

void PopUpMenu::redraw()
{
    if ( isCreated() == true ) {
     
        _aguix->ClearWin( win );

        _aguix->drawBorder( win, false, 0, 0, getWidth(), getHeight(), 0 );

        int entry_pos = 0, ty = getBorderWidth();
        for ( std::vector<InternPopUpEntry>::iterator it1 = m_entries.begin();
              it1 != m_entries.end();
              it1++, entry_pos++ ) {
            redrawEntry( entry_pos, ty );
            ty += getEntryHeight( entry_pos );
        }
    }
}

void PopUpMenu::showEntry( int entry )
{
    int ty = 0;

    for ( std::vector<InternPopUpEntry>::iterator it1 = m_entries.begin();
          it1 != m_entries.end();
          it1++, ty++ ) {
        if ( ty != entry ) {
            if ( it1->type == SUBMENU ) {
                it1->submenu->hide();
            }
        } else {
            if ( it1->type == SUBMENU && isVisibleEntry( entry ) == true ) {
                if ( it1->submenu->isVisible() == false ) {
                    int sub_y, sub_x;
                    
                    sub_y = it1->y;
                    sub_y += getY();
                    sub_y -= it1->submenu->getBorderWidth();

                    int rw, rh;

                    _aguix->getLargestDimensionOfCurrentScreen( NULL, NULL, &rw, &rh );
                    
                    if ( sub_y + it1->submenu->getHeight() >= rh ) {
                        sub_y = rh - it1->submenu->getHeight();
                    }
                    if ( sub_y < 0 ) sub_y = 0;
                    
                    sub_x = getX() + getWidth();
                    if ( sub_x + it1->submenu->getWidth() >= rw ) {
                        sub_x = getX() - it1->submenu->getWidth();
                    }
                    if ( sub_x < 0 ) sub_x = 0;
                    
                    it1->submenu->show( sub_x, sub_y, POPUP_IN_POSITION );
                    m_ignore_non_vertical_motion = true;
                }
            } else {
                m_ignore_non_vertical_motion = false;
            }
        }
    }
}

void PopUpMenu::enableTimer()
{
    _aguix->msgLock( this );
    _aguix->enableTimer();
}

void PopUpMenu::disableTimer()
{
    if ( _aguix->msgHoldsLock( this ) == true ) {
        _aguix->msgUnlock( this );
        _aguix->disableTimer();
    }
}

bool PopUpMenu::handleMessage( XEvent *e,Message *msg )
{
    if ( isCreated() == false ) return false;

    bool ret_val = false;
    //TODO close all popups on (x11) focus lost and unmap events
    //     or do it in aguix?
    
    if ( msg->type == ClientMessage && msg->specialType == Message::TIMEREVENT ) {
        m_counter++;

        if ( m_counter == 5 ) {
            disableTimer();
            if ( m_open_after_timeout == true ) {
                showEntry( m_highlighted_entry );
                redraw();
            }
        }
        ret_val = true;
    } else if ( ( msg->type == KeyPress || msg->type == KeyRelease ) && m_keyboard_focus == true ) {
        if ( msg->type == KeyPress ) {

            if ( m_edit_entry >= 0 &&
                 m_edit_entry < (int)m_entries.size() ) {

                if ( m_entries[m_edit_entry].type == EDITABLE ) {
                    if ( msg->key == XK_Return ) {
                        activateEntry( m_edit_entry );
                    } else if ( msg->key == XK_Escape ) {
                        m_entries[m_edit_entry].editable_content = "";
                        m_edit_entry = -1;
                        _aguix->hidePopUpWindows( getGroupID() );
                    } else if ( msg->key == XK_BackSpace ) {
                        if ( ! m_entries[m_edit_entry].editable_content.empty() ) {
                            int p = m_entries[m_edit_entry].editable_content.length();
                            UTF8::movePosToPrevChar( m_entries[m_edit_entry].editable_content.c_str(), p );
                            m_entries[m_edit_entry].editable_content.erase( p );
                            redraw();
                        }
                    } else if ( strlen( msg->keybuf ) > 0 ) {
                        m_entries[m_edit_entry].editable_content += msg->keybuf;
                        setDimensions();
                        redraw();
                    }
                }
            } else {

                if ( msg->key == XK_Down ) {
                    int highlighted_entry;

                    if ( m_highlighted_entry < 0 ) {
                        highlighted_entry = 0;
                    } else {
                        highlighted_entry = m_highlighted_entry + 1;
                        if ( highlighted_entry >= (int)m_entries.size() ) {
                            highlighted_entry = 0;
                        }
                    }
                
                    setHighlightEntry( highlighted_entry );
                } else if ( msg->key == XK_Up ) {
                    int highlighted_entry;

                    if ( m_highlighted_entry < 0 ) {
                        highlighted_entry = (int)m_entries.size() - 1;
                    } else {
                        highlighted_entry = m_highlighted_entry - 1;
                        if ( highlighted_entry < 0 ) {
                            highlighted_entry = (int)m_entries.size() - 1;
                        }
                    }

                    setHighlightEntry( highlighted_entry, -1 );
                } else if ( msg->key == XK_Return ) {
                    if ( m_highlighted_entry >= 0 &&
                         (size_t)m_highlighted_entry <= m_entries.size() ) {
                        if ( m_entries[ m_highlighted_entry ].type == SUBMENU ) {
                            showEntry( m_highlighted_entry );
                            setKeyboardFocus( false );
                            m_entries[ m_highlighted_entry ].submenu->setKeyboardFocus( true );
                            m_entries[ m_highlighted_entry ].submenu->highlightOnShowEntry( 0 );
                        } else {
                            activateEntry( m_highlighted_entry );
                        }
                    }
                } else if ( msg->key == XK_Right ) {
                    int ty = 0;
                    for ( std::vector<InternPopUpEntry>::iterator it1 = m_entries.begin();
                          it1 != m_entries.end();
                          it1++, ty++ ) {
                        if ( ty == m_highlighted_entry ) {
                            if ( it1->type == SUBMENU ) {
                                showEntry( m_highlighted_entry );
                                setKeyboardFocus( false );
                                it1->submenu->setKeyboardFocus( true );

                                it1->submenu->highlightOnShowEntry( 0 );
                            }
                            break;
                        }
                    }
                } else if ( msg->key == XK_Left ) {
                    if ( m_parent_menu != NULL ) {
                        setKeyboardFocus( false );
                        m_parent_menu->setKeyboardFocus( true );
                        hide();
                    }
                } else if ( msg->key == XK_Escape ) {
                    _aguix->hidePopUpWindows( getGroupID() );
                } else if ( msg->key == XK_BackSpace ) {
                    //remove last character (think of utf8)
                    //removeLastInfixChar();
                
                    // currently I just clear the filter, I think
                    // this is better for daily use
                    m_infix_filter = "";
                    updateInfixFilter();
                } else if ( strlen( msg->keybuf ) > 0 ) {
                    //append to infix
                    addStringToFilter( msg->keybuf );
                }
            }
            ret_val = true;
        }
    } else if ( msg->window == win ) {
        int entry;
        int mx, my;

        switch ( msg->type ) {
          case Expose:
              redraw();
              ret_val = true;
              break;
          case EnterNotify:
              enableTimer();
              ret_val = true;
              break;
          case LeaveNotify:
              m_ignore_non_vertical_motion = false;
              disableTimer();
              ret_val = true;
              break;
          case MotionNotify:
              _aguix->queryPointer( win, &mx, &my );
              if ( m_ignore_non_vertical_motion == false ||
                   abs( mx - m_last_motion_x ) < 1 ) {
                  entry = getEntryForYPos( my );
                  if ( entry >= 0 && entry < (int)m_entries.size() ) {
                      if ( m_highlighted_entry != entry && isVisibleEntry( entry ) == true ) {
                          setHighlightEntry( entry, 0 );
                          
                          changeFocusTo( this );
                      }
                      
                      // for immediate popup:
                      //showEntry( entry );
                  }
              }
              m_last_motion_x = mx;
              m_last_motion_y = my;
              ret_val = true;
              break;
          case ButtonPress:
              _aguix->queryPointer( win, &mx, &my );
              entry = getEntryForYPos( my );
              if ( entry >= 0 && entry < (int)m_entries.size() ) {
                  if ( isVisibleEntry( entry ) == true ) {
                      m_highlighted_entry = entry;

                      disableTimer();
                      showEntry( entry );
                      redraw();
                  }
              }
              ret_val = true;
              m_ignore_button_release = false;
              break;
          case ButtonRelease:
              if ( m_ignore_button_release == false ) {
                  _aguix->queryPointer( win, &mx, &my );
                  entry = getEntryForYPos( my );
                  if ( entry >= 0 && entry < (int)m_entries.size() ) {
                      if ( isVisibleEntry( entry ) == true ) {
                          m_highlighted_entry = entry;
                      
                          disableTimer();
                          activateEntry( m_highlighted_entry );
                      }
                  }
              }
              ret_val = true;
              break;
          case FocusOut:
              if ( m_focus_out_ignore_time_ms > 0 ) {
                  uint64_t now = AGUIXUtils::current_time_ms();
                  if ( now - m_show_time_ms < m_focus_out_ignore_time_ms ) {
                      // take back focus during grace period
                      setXFocus();
                  }
                  break;
              }
          case UnmapNotify:
              hide();
              break;
          default:
              break;
        }
    }
    if ( msg->lockElement == this ) msg->ack = true;

    bool ret_val_parent = AWindow::handleMessage( e, msg );
    if ( ret_val == false && ret_val_parent == true ) {
        ret_val = true;
    }
    return ret_val;
}

PopUpMenu::InternPopUpEntry::InternPopUpEntry() : name( "" ),
                                                  type( NORMAL ),
                                                  id( -1 ),
                                                  submenu( NULL ),
                                                  y( 0 ),
                                                  height( 0 ),
                                                  fg( -1 ),
                                                  bg( -1 ),
                                                  editable_content( "" ),
                                                  highlight_on_show( false )
{
}

PopUpMenu::InternPopUpEntry::~InternPopUpEntry()
{
}

PopUpMenu::PopUpEntry::PopUpEntry() : name( "" ),
                                      type( NORMAL ),
                                      id( -1 ),
                                      submenu(),
                                      fg( -1 ),
                                      bg( -1 ),
                                      highlight_on_show( false )
{
}

void PopUpMenu::setKeyboardFocus( bool nv)
{
    m_keyboard_focus = nv;
}

void PopUpMenu::setParentMenu( PopUpMenu *parent )
{
    m_parent_menu = parent;
}

void PopUpMenu::activateEntry( int entry )
{
    int ty = 0;
    for ( std::vector<InternPopUpEntry>::iterator it1 = m_entries.begin();
          it1 != m_entries.end();
          it1++, ty++ ) {
        if ( ty == entry && isVisibleEntry( entry ) == true ) {
            if ( it1->type == SUBMENU ) {
                showEntry( m_highlighted_entry );
            } else if ( it1->type == NORMAL ) {
                sendClickMessage( entry );

                _aguix->hidePopUpWindows( getGroupID() );
            } else if ( it1->type == EDITABLE ) {
                if ( m_edit_entry != entry ) {
                    m_edit_entry = entry;
                    it1->editable_content = "";
                    redraw();
                } else {
                    sendEditedMessage( entry );

                    _aguix->hidePopUpWindows( getGroupID() );
                }
            }
            break;
        }
    }
    return;
}

void PopUpMenu::setHighlightEntry( int entry, int forward_direction )
{
    if ( entry >= 0 && entry < (int)m_entries.size() ) {
        if ( m_highlighted_entry != entry || ! isVisibleEntry( m_highlighted_entry ) ) {
            m_open_after_timeout = true;
            m_highlighted_entry = entry;

            if ( forward_direction != 0 ) {
                m_highlighted_entry = forwardToVisibleEntry( m_highlighted_entry, forward_direction );
            }

            m_counter = 0;
            
            enableTimer();

            if ( m_help_cb ) {
                m_help_cb( m_entries[m_highlighted_entry].help );
            }
        }
        redraw();
    }
}

void PopUpMenu::sendClickMessage( int entry )
{
    if ( isVisibleEntry( entry ) == true ) {
        AGMessage *agmsg = AGUIX_allocAGMessage();
        agmsg->type = AG_POPUPMENU_CLICKED;
        
        std::list<int> entries;
        
        entries.push_back( m_entries[entry].id );
        sendMessage( this, entries, std::unique_ptr<AGMessage>( agmsg ) );
    }
}

void PopUpMenu::sendEditedMessage( int entry )
{
    if ( isVisibleEntry( entry ) == true ) {
        AGMessage *agmsg = AGUIX_allocAGMessage();
        agmsg->type = AG_POPUPMENU_ENTRYEDITED;
        agmsg->popupmenu.edit_content = new std::string( m_entries[entry].editable_content );
        
        std::list<int> entries;
        
        entries.push_back( m_entries[entry].id );
        sendMessage( this, entries, std::unique_ptr<AGMessage>( agmsg ) );
    }
}

void PopUpMenu::sendMessage( const PopUpMenu *submenu, const std::list<int> &entries, std::unique_ptr<AGMessage> agmsg )
{
    std::list<int> new_entries( entries );
    
    if ( submenu != this ) {
        for ( std::vector<InternPopUpEntry>::iterator it1 = m_entries.begin();
              it1 != m_entries.end();
              it1++ ) {
            if ( it1->type == SUBMENU && it1->submenu == submenu ) {
                new_entries.push_back( it1->id );
                break;
            }
        }
    }
    
    if ( m_parent_menu != NULL ) {
        m_parent_menu->sendMessage( this, new_entries, std::move( agmsg ) );
    } else {
        agmsg->popupmenu.menu = this;
        agmsg->popupmenu.recursive_ids = new std::list<int>( new_entries );
        agmsg->popupmenu.clicked_entry_id = new_entries.back();
        _aguix->putAGMsg( agmsg.release() );
    }
}

int PopUpMenu::getBorderWidth() const
{
    return m_border_width;
}

void PopUpMenu::redrawEntry( int entry, int top_y )
{
    if ( entry >= 0 && entry < (int)m_entries.size() ) {
        DrawableCont dc( _aguix, win );
        int normal_fg, normal_bg;

        if ( m_entries[entry].fg >= 0 ) {
            normal_fg = m_entries[entry].fg;
        } else {
            normal_fg = _aguix->getFaces().getColor( "popup-fg" );
        }
        if ( m_entries[entry].bg >= 0 ) {
            normal_bg = m_entries[entry].bg;
        } else {
            normal_bg = getBG();
        }

        if ( m_edit_entry == entry ) {
            _aguix->setFG( _aguix->getFaces().getColor( "popup-edit-field-bg" ) );
            _aguix->FillRectangle( win,
                                   getBorderWidth() + 1,
                                   top_y + 1,
                                   getWidth() - 2 * getBorderWidth() - 2,
                                   getEntryHeight( entry ) - 2 );
            _aguix->DrawText( dc, m_entries[entry].editable_content.c_str(),
                              getBorderWidth() + 1,
                              top_y + 1,
                              normal_fg );
            _aguix->setFG( normal_fg );

            int tw = _aguix->getTextWidth( m_entries[entry].editable_content.c_str() );

            _aguix->FillRectangle( win,
                                   getBorderWidth() + 1 + tw,
                                   top_y + 1,
                                   2,
                                   getEntryHeight( entry ) - 2 );
        } else {
            switch ( m_entries[entry].type ) {
                case HEADER:
                    _aguix->DrawText( dc, m_entries[entry].name.c_str(),
                                      getBorderWidth() + 1,
                                      top_y + 1,
                                      normal_fg );
                    break;
                case NORMAL:
                case SUBMENU:
                case GREYED_OUT:
                case EDITABLE:
                    if ( m_highlighted_entry == entry ) {
                        _aguix->drawBorder( win, true,
                                            getBorderWidth(),
                                            top_y,
                                            getWidth() - 2 * getBorderWidth(),
                                            getEntryHeight( entry ), 0 );
                    }
                
                    if ( normal_bg != getBG() ) {
                        _aguix->setFG( normal_bg );
                        _aguix->FillRectangle( win,
                                               getBorderWidth() + 1,
                                               top_y + 1,
                                               getWidth() - 2 * getBorderWidth() - 2,
                                               getEntryHeight( entry ) - 2 );
                    }

                    if ( isVisibleEntry( entry ) == true ) {
                        int hfg = _aguix->getFaces().getColor( "popup-highlight-fg" );

                        if ( normal_bg == hfg ) {
                            hfg = normal_fg;
                        }
                        _aguix->DrawText( dc, m_entries[entry].name.c_str(),
                                          getBorderWidth() + 1,
                                          top_y + 1,
                                          ( m_highlighted_entry == entry ) ? hfg : normal_fg );
                    } else {
                        _aguix->DrawText( dc, m_entries[entry].name.c_str(),
                                          getBorderWidth() + 1 + 1,
                                          top_y + 1 + 1,
                                          _aguix->getFaces().getColor( "popup-highlight-fg" ) );
                        _aguix->DrawText( dc, m_entries[entry].name.c_str(),
                                          getBorderWidth() + 1,
                                          top_y + 1,
                                          AGUIXColor( 0, AGUIXColor::SYSTEM_COLOR ) );
                    }
                                    
                    if ( m_entries[entry].type == SUBMENU ) {
                        int submenu_char_w = _aguix->getCharHeight();

                        int tr_x = getWidth() - getBorderWidth() - 1 - submenu_char_w;
                        int tr_w = submenu_char_w;
                        int tr_y = top_y + 1;
                        int tr_h = m_entries[entry].height - 2;
                        _aguix->setFG( _aguix->getFaces().getColor( "popup-submenu-arrow" ) );
                        _aguix->DrawTriangleFilled( win,
                                                    tr_x + 2, tr_y + 2,
                                                    tr_x + tr_w - 3, tr_y + tr_h / 2,
                                                    tr_x + 2, tr_y + tr_h - 3 );
                    }
                    break;
                case HLINE:
                    _aguix->setFG( _aguix->getFaceCol_3d_bright() );
                    _aguix->DrawLine( win, getBorderWidth(), top_y + 2,
                                      getBorderWidth(), top_y + 1 );
                    _aguix->DrawLine( win, getBorderWidth(), top_y + 1,
                                      getWidth() - getBorderWidth() - 2, top_y + 1 );
                    _aguix->setFG( _aguix->getFaceCol_3d_dark() );
                    _aguix->DrawLine( win, getWidth() - getBorderWidth() - 1, top_y + 1,
                                      getWidth() - getBorderWidth() - 1, top_y + 2 );
                    _aguix->DrawLine( win, getWidth() - getBorderWidth() - 1, top_y + 2,
                                      getBorderWidth() + 1, top_y + 2 );
                    break;
                default:
                    break;
            }
        }
    }
}

int PopUpMenu::getEntryHeight( int entry )
{
    if ( entry >= 0 && entry < (int)m_entries.size() ) {
        return m_entries[entry].height;
    }
    return 0;
}

void PopUpMenu::calcEntryDimensions()
{
    int ty = getBorderWidth();
    
    for ( std::vector<InternPopUpEntry>::iterator it1 = m_entries.begin();
          it1 != m_entries.end();
          it1++ ) {
        it1->y = ty;
        switch ( it1->type ) {
            case NORMAL:
            case SUBMENU:
            case GREYED_OUT:
            case EDITABLE:
                it1->height = _aguix->getCharHeight() + 2;
                break;
            case HEADER:
                it1->height = _aguix->getCharHeight() + 2;
                break;
            case HLINE:
                it1->height = 4;
                break;
            default:
                it1->height = 2;
                break;
        }
        ty += it1->height;
    }
}

int PopUpMenu::getEntryForYPos( int y )
{
    int entry = -1, this_entry = 0;
    for ( std::vector<InternPopUpEntry>::iterator it1 = m_entries.begin();
          it1 != m_entries.end();
          it1++, this_entry++ ) {
        if ( y >= it1->y && y < ( it1->y + it1->height ) ) {
            entry = this_entry;
        }
    }
    
    return entry;
}

int PopUpMenu::forwardToVisibleEntry( int pos, int direction )
{
    int old_pos;
    
    if ( m_entries.size() < 1 ) return -1;
    
    if ( pos >= (int)m_entries.size() ) pos = (int)m_entries.size() - 1;
    if ( pos < 0 ) pos = 0;
    
    old_pos = pos;
    
    do {
        if ( isVisibleEntry( pos ) && m_entries[pos].type != HLINE && m_entries[pos].type != HEADER )
            break;
        
        if ( direction < 0 ) {
            pos--;
            if ( pos < 0 ) pos = (int)m_entries.size() - 1;
        } else {
            pos++;
            if ( pos >= (int)m_entries.size() ) pos = 0;
        }
    } while ( pos != old_pos );
    
    if ( isVisibleEntry( pos ) == false ) return -1;
    return pos;
}

void PopUpMenu::updateInfixFilter()
{
    //move highlight to next visible
    setHighlightEntry( m_highlighted_entry );
}

bool PopUpMenu::isVisibleEntry( int pos ) const
{
    if ( pos >= 0 && pos < (int)m_entries.size() ) {
        if ( m_entries[pos].type == GREYED_OUT ) return false;

        std::string lower_infix = AGUIXUtils::tolower( m_infix_filter );
        std::string lower_name = AGUIXUtils::tolower( m_entries[pos].name );
        
        if ( lower_name.find( lower_infix ) == std::string::npos ) return false;
        return true;
    }
    return false;
}

int PopUpMenu::checkForVisibleEntries( const std::string &filter, bool prefer_prefix )
{
    std::string lower_filter = AGUIXUtils::tolower( filter );

    int res_pos = -1;
    int pos = 0;
    for ( std::vector<InternPopUpEntry>::iterator it1 = m_entries.begin();
          it1 != m_entries.end();
          it1++, pos++ ) {
        std::string lower_name;
        
        switch ( it1->type ) {
          case NORMAL:
          case SUBMENU:
          case EDITABLE:
            lower_name = AGUIXUtils::tolower( it1->name );
            if ( prefer_prefix ) {
                if ( AGUIXUtils::starts_with( lower_name, lower_filter ) ) {
                    return pos;
                }
            }

            if ( res_pos == -1 && lower_name.find( lower_filter ) != std::string::npos ) {
                res_pos = pos;
            }
            break;
          default:
            break;
        }
    }
    
    return res_pos;
}

void PopUpMenu::addStringToFilter( const char *str )
{
    std::string temp_filter = m_infix_filter;
    temp_filter += str;

    bool prefer_prefix = m_infix_filter.empty();

    int res = checkForVisibleEntries( temp_filter, prefer_prefix );
    if ( res >= 0 ) {
        m_infix_filter = temp_filter;

        if ( m_highlighted_entry < 0 ) {
            // set highlighted entry even if it is not visible as it
            // will jump to next visible automatically
            m_highlighted_entry = res;
        } else if ( prefer_prefix ) {
            setHighlightEntry( res );
        }

        updateInfixFilter();
    }
}

void PopUpMenu::removeLastInfixChar()
{
    int prev_char = m_infix_filter.length();
    UTF8::movePosToPrevChar( m_infix_filter.c_str(), prev_char );

    if ( prev_char < 0 ) {
        m_infix_filter = "";
    } else {
        try {
            m_infix_filter.erase( prev_char );
        } catch ( std::out_of_range & ) {
            m_infix_filter = "";
        }
    }
    updateInfixFilter();
}

void PopUpMenu::changeFocusTo( PopUpMenu *owner, change_focus_run_t mode )
{
    if ( owner == this && m_keyboard_focus == true ) return;

    if ( mode == BOTTOM_UP_RUN ) {
        if ( m_parent_menu != NULL ) {
            m_parent_menu->changeFocusTo( owner, mode );
        } else {
            changeFocusTo( owner, TOP_DOWN_RUN );
        }
    } else if ( mode == TOP_DOWN_RUN ) {
        if ( owner != this ) {
            if ( m_keyboard_focus == true ) {
                setKeyboardFocus( false );
            }
        }
        
        for ( std::vector<InternPopUpEntry>::iterator it1 = m_entries.begin();
              it1 != m_entries.end();
              it1++ ) {
            switch ( it1->type ) {
              case SUBMENU:
                  if ( it1->submenu != NULL ) it1->submenu->changeFocusTo( owner, mode );
                  break;
              default:
                  break;
            }
        }
        
        if ( owner == this ) {
            setKeyboardFocus( true );
        }
    }
}

void PopUpMenu::checkAndGrabKeyboard()
{
    if ( isCreated() == true &&
         m_parent_menu == NULL &&
         onScreen == true &&
         m_grabbed_keyboard != 0 ) {

        //std::cout << "Regrab:";

        m_grabbed_keyboard = XGrabKeyboard( _aguix->getDisplay(),
                                            win,
                                            False,
                                            GrabModeAsync,
                                            GrabModeAsync,
                                            CurrentTime );
        //std::cout << m_grabbed_keyboard << std::endl;

        if ( m_grabbed_keyboard == 0 ) {
            //std::cout << "Success" << std::endl;
        }
    }
}

int PopUpMenu::getHighlightEntry() const
{
    return m_highlighted_entry;
}

bool PopUpMenu::getKeyboardFocus() const
{
    return m_keyboard_focus;
}

void PopUpMenu::highlightOnShowEntry( int default_entry )
{
    size_t entry;

    for ( entry = 0;
          entry < m_entries.size();
          entry++ ) {
        if ( m_entries[entry].highlight_on_show ) {
            setHighlightEntry( entry );
            break;
        }
    }

    if ( entry >= m_entries.size() &&
         default_entry >= 0 ) {
        setHighlightEntry( default_entry );
    }
}

void PopUpMenu::setHelpCB( std::function< void( const std::string &help ) > help_cb )
{
    m_help_cb = help_cb;
}

void PopUpMenu::setFocusOutIgnoreTime( uint64_t ms )
{
    m_focus_out_ignore_time_ms = ms;
}
