/* * mogltk * Copyright (C) 1999-2004 Nicolas "Pixel" Noble * * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ /* $Id: widgets.cc,v 1.20 2006-02-02 14:09:49 pixel Exp $ */ #include #include #include #include "font.h" #include "engine.h" #include "widgets.h" #include "sprite.h" #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "gettext.h" // Why so much ugly globals... ? mogltk::widget * dragged_widget = 0; std::vector out_move, out_click; std::vector mogltk::widget::timed_events; std::vector mogltk::widget::add_list; std::vector mogltk::widget::del_list; bool mogltk::widget::in_delete_loop = false; class widget_mouse_event : public mogltk::engine::mouseevent { public: widget_mouse_event(mogltk::widget *); virtual void move(SDL_MouseMotionEvent); virtual void action(SDL_MouseButtonEvent); private: mogltk::widget * root; int mouse_x_down, mouse_y_down; bool mouse_down, mouse_drag; Uint32 old_click; }; widget_mouse_event::widget_mouse_event(mogltk::widget * _root) : root(_root), mouse_down(false), mouse_drag(false) { } void widget_mouse_event::move(SDL_MouseMotionEvent m) { int mx, my; bool out_threshold; std::vector::iterator i; std::vector out_move_stack; mx = mogltk::engine::mouseX(); my = mogltk::engine::mouseY(); if (mouse_down) { int dx, dy; dx = ABS(mouse_x_down - mx); dy = ABS(mouse_y_down - my); out_threshold = (dx <= 1) && (dy <= 1); } if (mouse_down && (out_threshold || mouse_drag)) { if (!mouse_drag) { printm(M_INFO, "mouse_start_drag\n"); // generate a mouse_start_drag event root->m_event(mouse_x_down, mouse_y_down, mogltk::E_MOUSE_START_DRAG); } if (dragged_widget) { printm(M_INFO, "mouse_drag_over"); // generate a mouse_drag_over event dragged_widget->m_event(mx, my, mogltk::E_MOUSE_DRAG_OVER); } printm(M_INFO, "mouse_drag\n"); // generate a mouse_drag event root->m_event(mx, my, mogltk::E_MOUSE_DRAG); mouse_drag = true; } else { printm(M_INFO, "mouse_move\n"); root->m_event(mx, my, mogltk::E_MOUSE_MOVE); // generate a mouse_move event } for (i = out_move.begin(); i != out_move.end(); i++) { out_move_stack.push_back(*i); } for (i = out_move_stack.begin(); i != out_move_stack.end(); i++) { if (!(*i)->inside(mx, my)) { (*i)->m_event(mx, my, mogltk::E_MOUSE_OUT); // generate a mouse_out event } } out_move_stack.clear(); } void widget_mouse_event::action(SDL_MouseButtonEvent b) { int mx, my; std::vector::iterator i; std::vector out_click_stack; mx = mogltk::engine::mouseX(); my = mogltk::engine::mouseY(); if (b.button == 1) { if (b.state) { mouse_x_down = mx; mouse_y_down = my; mouse_down = true; printm(M_INFO, "mouse_down\n"); root->m_event(mx, my, mogltk::E_MOUSE_DOWN); // generate a mouse_down event; for (i = out_click.begin(); i != out_click.end(); i++) { out_click_stack.push_back(*i); } for (i = out_click_stack.begin(); i != out_click_stack.end(); i++) { if (!(*i)->inside(mx, my)) { (*i)->m_event(mx, my, mogltk::E_MOUSE_OUT_CLICK); // generate a mouse_out_click event } } out_click_stack.clear(); } else { mouse_down = false; if (mouse_drag) { mouse_drag = false; printm(M_INFO, "mouse_end_drag\n"); root->m_event(mx, my, mogltk::E_MOUSE_END_DRAG); // generate a mouse_end_drag event if (dragged_widget) { printm(M_INFO, "mouse_end_drag_over"); dragged_widget->m_event(mx, my, mogltk::E_MOUSE_END_DRAG_OVER); // generate a mouse_end_drag_over event dragged_widget = 0; } } else { printm(M_INFO, "mouse_click\n"); root->m_event(mx, my, mogltk::E_MOUSE_CLICK); // generate a mouse_click event for (i = out_click.begin(); i != out_click.end(); i++) { out_click_stack.push_back(*i); } for (i = out_click_stack.begin(); i != out_click_stack.end(); i++) { if (!(*i)->inside(mx, my)) { (*i)->m_event(mx, my, mogltk::E_MOUSE_OUT_CLICK); // generate a mouse_out_click event } } out_click_stack.clear(); if ((SDL_GetTicks() - old_click) < 500) { printm(M_INFO, "mouse_dbl_click\n"); root->m_event(mx, my, mogltk::E_MOUSE_DBL_CLICK); // generate a mouse_dbl_click event } old_click = SDL_GetTicks(); } printm(M_INFO, "mouse_up\n"); root->m_event(mx, my, mogltk::E_MOUSE_UP); // generate a mouse_up event. } } else if (b.button == 2) { if (b.state) { printm(M_INFO, "mouse_right_down\n"); root->m_event(mx, my, mogltk::E_MOUSE_RIGHT_DOWN); // generate a mouse_right_down event; } else { printm(M_INFO, "mouse_right_down\n"); root->m_event(mx, my, mogltk::E_MOUSE_RIGHT_UP); // generate a mouse_right_up event; } } else if (b.button == 3) { if (b.state) { printm(M_INFO, "mouse_middle_down\n"); root->m_event(mx, my, mogltk::E_MOUSE_MIDDLE_DOWN); // generate a mouse_right_down event; } else { printm(M_INFO, "mouse_right_down\n"); root->m_event(mx, my, mogltk::E_MOUSE_MIDDLE_UP); // generate a mouse_right_up event; } } } mogltk::widget * mogltk::widget::focused = 0; mogltk::widget::widget(widget * _father, int _x, int _y, int _sx, int _sy, int _type, String _name, mogltk::shape * _sh) : x(_x), y(_y), sx(_sx), sy(_sy), father(_father), prev(0), child(0), last(0), panel(0), type(_type), name(_name), sh(_sh), exclusive(0), visible(true), enabled(true) { LOCK; add_list.push_back(this); UNLOCK; if (!father) { x = 0; y = 0; sx = engine::base_o->GetWidth(); sy = engine::base_o->GetHeight(); } } void mogltk::widget::linkme() { LOCK; if (!father) { root = this; father = this; next = 0; } else { next = father->child; if (next) next->prev = this; if (!father->last) father->last = this; father->child = this; root = father->root; } UNLOCK; computeabs(); } mogltk::widget::~widget() throw (GeneralException) { while(child) delete child; if (!in_delete_loop) throw GeneralException("Invalid wild delete."); LOCK; if (prev) prev->next = next; else father->child = next; if (next) next->prev = prev; else father->last = prev; std::vector::iterator iw; std::vector::iterator it; for (iw = out_move.begin(); iw != out_move.end(); iw++) { if (*iw == this) { out_move.erase(iw); iw = out_move.begin(); } } for (iw = out_click.begin(); iw != out_click.end(); iw++) { if (*iw == this) { out_click.erase(iw); iw = out_click.begin(); } } for (iw = del_list.begin(); iw != del_list.end(); iw++) { if (*iw == this) { del_list.erase(iw); iw = del_list.begin(); } } for (iw = add_list.begin(); iw != add_list.end(); iw++) { if (*iw == this) { add_list.erase(iw); iw = add_list.begin(); } } for (it = timed_events.begin(); it != timed_events.end(); it++) { if (it->w == this) { timed_events.erase(it); it = timed_events.begin(); } } UNLOCK; } void mogltk::widget::computeabs() { if (father != this) { ax = father->ax + x; ay = father->ay + y; } else { ax = x; ay = y; } ax2 = ax + sx; ay2 = ay + sy; } void mogltk::widget::move(int nx, int ny) { x = nx; y = ny; computeabs(); if (child) child->icomputeabs(); } void mogltk::widget::resize(int nsx, int nsy) { sx = nsx; sy = nsy; ax2 = ax + sx; ay2 = ay + sy; if (child) child->iresize_notify(); } int mogltk::widget::GetX() { return x; } int mogltk::widget::GetY() { return y; } int mogltk::widget::GetH() { return sy; } int mogltk::widget::GetW() { return sx; } int mogltk::widget::GetAX() { return ax; } int mogltk::widget::GetAY() { return ay; } int mogltk::widget::GetAX2() { return ax2; } int mogltk::widget::GetAY2() { return ay2; } mogltk::rect mogltk::widget::GetDrawRect() { rect r; r.x = 0; r.y = 0; r.w = GetW(); r.h = GetH(); return r; } mogltk::widget * mogltk::widget::Father() { return father; } mogltk::widget * mogltk::widget::Child() { return child; } mogltk::widget * mogltk::widget::Next() { return next; } mogltk::widget * mogltk::widget::Prev() { return prev; } mogltk::widget * mogltk::widget::InnerPanel() { if (!panel) panel = create_panel(); return panel; } mogltk::widget * mogltk::widget::find_widget(int _x, int _y) { widget * r = 0; if (visible && enabled && (_x >= ax) && (_y >= ay) && (_x <= ax2) && (_y <= ay2)) { if (child) r = child->find_widget(_x, _y); if (!r) r = this; } if (!r && next) { r = next->find_widget(_x, _y); } return r; } mogltk::shape * mogltk::widget::Shaper() { return sh; } void mogltk::widget::fulldraw() { bool was2D = true; if (mogltk::engine::glbase_o) if (!(was2D = mogltk::engine::glbase_o->is2D())) mogltk::engine::glbase_o->Enter2DMode(); texture::Unbind(); mogltk::ColorP::Max = WHITE; mogltk::ColorP::Min = BLACK; mogltk::ColorP::Min.A = 0; if (root) root->idraw(); if (!was2D) mogltk::engine::glbase_o->Leave2DMode(); } void mogltk::widget::idraw() { if (next) next->idraw(); if (!visible) return; set_viewport(); draw(); if (child) child->idraw(); } void mogltk::widget::set_viewport() { int x1, y1, x2, y2; x1 = MAX(GetAX(), father->GetAX()); y1 = MAX(GetAY(), father->GetAY()); x2 = MIN(GetAX2(), father->GetAX2()); y2 = MIN(GetAY2(), father->GetAY2()); engine::base_o->changeviewport(x1, y1, x2 - x1, y2 - y1); } bool mogltk::widget::inside(int xe, int ye) { if (!visible) return false; return !((xe < ax) || (xe > ax2) || (ye < ay) || (ye > ay2)); } void mogltk::widget::m_event(int x, int y, mogltk::event_t event) { switch (event) { case E_MOUSE_DRAG_OVER: case E_MOUSE_END_DRAG_OVER: case E_MOUSE_OUT: case E_MOUSE_OUT_CLICK: process_event(x, y, event); break; default: ievent(x, y, event); } } bool mogltk::widget::ievent(int xe, int ye, mogltk::event_t event) { if (prev) if (prev->ievent(xe, ye, event)) return true; if (!inside(xe, ye)) return false; if (exclusive) return exclusive->ievent(xe, ye, event); if (last) if (last->ievent(xe, ye, event)) return true; if (!enabled || !visible) return false; return process_event(xe, ye, event); } void mogltk::widget::draw() { } bool mogltk::widget::process_event(int, int, mogltk::event_t) { return false; } void mogltk::widget::resize_notify() { } mogltk::widget * mogltk::widget::create_panel() { mogltk::rect r = GetDrawRect(); return new widgets::Panel(Shaper(), this, r.x, r.y, r.w, r.h); } void mogltk::widget::icomputeabs() { computeabs(); if (next) next->icomputeabs(); if (child) child->icomputeabs(); } void mogltk::widget::iresize_notify() { if (next) next->iresize_notify(); if (child) child->iresize_notify(); resize_notify(); } void mogltk::widget::set_exclusive(mogltk::widget * w) { w->exclusive = this; } void mogltk::widget::unset_exclusive(mogltk::widget * w) { w->exclusive = 0; } bool mogltk::widget::GetVisible() { return visible; } void mogltk::widget::SetVisible(bool _visible) { visible = _visible; } bool mogltk::widget::GetEnabled() { return enabled; } void mogltk::widget::SetEnabled(bool _enabled) { enabled = _enabled; } void mogltk::widget::MoveOnTop() { if (!prev) return; if (!next) { father->last = prev; } else { next->prev = prev; } prev->next = next; prev = 0; next = father->child; next->prev = this; father->child = this; } void mogltk::widget::check_timed_events() { std::vector::iterator i; Uint32 t = SDL_GetTicks(); widget * w; for (i = timed_events.begin(); i != timed_events.end(); i++) { if (i->t <= t) { w = i->w; timed_events.erase(i); w->process_event(engine::mouseX(), engine::mouseY(), E_TIMER); i = timed_events.begin(); if (i == timed_events.end()) return ; } } } void mogltk::widget::add_out_move() { std::vector::iterator i; for (i = out_move.begin(); i != out_move.end(); i++) { if (*i == this) return; } out_move.push_back(this); } void mogltk::widget::remove_out_move() { std::vector::iterator i; for (i = out_move.begin(); i != out_move.end(); i++) { if (*i == this) { out_move.erase(i); return; } } } void mogltk::widget::add_out_click() { std::vector::iterator i; for (i = out_click.begin(); i != out_click.end(); i++) { if (*i == this) return; } out_click.push_back(this); } void mogltk::widget::remove_out_click() { std::vector::iterator i; for (i = out_click.begin(); i != out_click.end(); i++) { if (*i == this) { out_click.erase(i); return; } } } void mogltk::widget::set_timed_event(Uint32 t) { timed_events.push_back(timed_event(this, SDL_GetTicks() + t)); } void mogltk::widget::set_absolute_timed_event(Uint32 t) { timed_events.push_back(timed_event(this, t)); } void mogltk::widget::center(bool cx, bool cy) { rect tr, fr; tr.x = GetX(); tr.y = GetY(); tr.w = GetW(); tr.h = GetH(); fr.w = Father()->GetW(); fr.h = Father()->GetH(); if (cx) tr.x = fr.w / 2 - tr.w / 2; if (cy) tr.y = fr.h / 2 - tr.h / 2; move(tr.x, tr.y); } void mogltk::widget::delete_me() { LOCK; del_list.push_back(this); UNLOCK; } /* * Predefined widgets. */ /*************** * Here is Root * ***************/ mogltk::widgets::Root::Root(mogltk::shape * sh, mogltk::widgets::drawer * _dr) : widget(0, 0, 0, 0, 0, 0, "Root", sh), dr(_dr) { if (engine::root) engine::root->delete_me(); engine::root = this; } void mogltk::widgets::Root::draw() { if (dr) dr->draw(this); else Shaper()->box(GetAX(), GetAY(), GetAX2(), GetAY2()); } void mogltk::widgets::Root::setdrawer(drawer * _dr) { dr = _dr; } /************************** * A simple clipping Panel * **************************/ mogltk::widgets::Panel::Panel(shape * sh, widget * father, int x, int y, int w, int h) : widget(father, x, y, w, h, 0, "Panel", sh) { } /*********************** * The classical Button * ***********************/ class ButtonCascadeAction : public mogltk::widgets::action { public: ButtonCascadeAction(mogltk::widgets::Button * _parent) : parent(_parent) { } virtual void do_action(mogltk::widget *) { parent->simulate_press(); } private: mogltk::widgets::Button * parent; }; mogltk::widgets::Button::Button(action * _a, shape * sh, widget * father, int x, int y, int w, int h, const String & _caption) : widget(father, x, y, w, h, 0, "Button", sh), bevel(false), dragging(false), a(_a), cascade(0) { caption = _caption; } mogltk::widgets::Button::~Button() throw (GeneralException) { if (cascade) delete cascade; } void mogltk::widgets::Button::draw() { Shaper()->button(GetAX() + 1, GetAY() + 1, GetAX2() - 1, GetAY2() - 1, caption, bevel); } void mogltk::widgets::Button::simulate_press() { if (a) a->do_action(this); } mogltk::widgets::action * mogltk::widgets::Button::cascade_action() { if (!cascade) cascade = new ButtonCascadeAction(this); return cascade; } bool mogltk::widgets::Button::process_event(int xe, int ye, mogltk::event_t event) { switch (event) { case E_MOUSE_DOWN: bevel = true; return true; case E_MOUSE_CLICK: bevel = false; // action here. if (a) a->do_action(this); return true; case E_MOUSE_START_DRAG: dragged_widget = this; dragging = true; return true; case E_MOUSE_DRAG_OVER: bevel = inside(xe, ye); return true; case E_MOUSE_END_DRAG_OVER: dragging = false; if (bevel) // action here. if (a) a->do_action(this); bevel = false; return true; } return false; } /********************** * The SmartBox window * **********************/ mogltk::widgets::SmartBox::SmartBox(shape * sh, mogltk::widget * father, int x, int y, int w, int h, const String & _caption) : widget(father, x, y, w, h, 0, "SmartBox", sh) { caption = _caption; } void mogltk::widgets::SmartBox::draw() { Shaper()->window(GetAX(), GetAY(), GetAX2(), GetAY2(), caption); } bool mogltk::widgets::SmartBox::process_event(int x, int y, mogltk::event_t event) { switch (event) { case E_MOUSE_START_DRAG: if ((GetAY() + 20) > y) { dragged_widget = this; ox = x; oy = y; oax = GetX(); oay = GetY(); return true; } break; case E_MOUSE_DRAG_OVER: move(x - ox + oax, y - oy + oay); MoveOnTop(); return true; } return false; } mogltk::rect mogltk::widgets::SmartBox::GetDrawRect() { rect r; r.x = 2; r.y = 21; r.w = GetW() - 4; r.h = GetH() - 23; return r; } /****************************************** * The SmartBox window with a close button * ******************************************/ class SmartBoxCloseAction : public mogltk::widgets::action { public: SmartBoxCloseAction(mogltk::widget * _parent) : parent(_parent) { } virtual void do_action(mogltk::widget *) { parent->delete_me(); delete this; } private: mogltk::widget * parent; }; mogltk::widgets::SmartBoxClose::SmartBoxClose(mogltk::shape * sh, mogltk::widget * father, int x, int y, int w, int h, const String & _caption) : SmartBox(sh, father, x, y, w, h, _caption) { new mogltk::widgets::Button(new SmartBoxCloseAction(this), sh, this, 1, 1, 16, 16, "X"); } /*********************** * The MessageBox child * ***********************/ mogltk::widgets::MsgBox::MsgBox(action * a, shape * sh, mogltk::widget * father, const String & caption, const String & text, mogltk::font * _font) : SmartBox(sh, father, 0, 0, 0, 0, caption), msgbox_font(_font) { rect size = msgbox_font->size(text); rect lsize = size; size.w += 12; size.h += 60; resize(size.w, size.h); center(); new Button(new ActionAndDelete(this, a), sh, this, size.w / 2 - 25, size.h - 30, 50, 24, "Ok"); new Label(sh, this, 6, 24, lsize.w, lsize.h, text, BLACK, msgbox_font); set_exclusive(father); } mogltk::widgets::MsgBox::~MsgBox() throw (GeneralException) { unset_exclusive(Father()); } /***************** * A simple Label * *****************/ mogltk::widgets::Label::Label(shape * sh, mogltk::widget * father, int x, int y, int w, int h, const String & _caption, const ColorP & _color, mogltk::font * _font) : widget(father, x, y, w, h, 0, "Label", sh), color(_color), label_font(_font) { caption = _caption; } void mogltk::widgets::Label::draw() { Shaper()->text(GetAX(), GetAY(), caption, color, LEFT, label_font); } /************************* * The classical 3D Frame * *************************/ mogltk::widgets::Frame::Frame(shape * sh, widget * father, int x, int y, int w, int h) : widget(father, x, y, w, h, 0, "Frame", sh) { } void mogltk::widgets::Frame::draw() { Shaper()->obox3d(GetAX(), GetAY(), GetAX2(), GetAY2()); } mogltk::rect mogltk::widgets::Frame::GetDrawRect() { rect r; r.x = 4; r.y = 4; r.w = GetW() - 8; r.h = GetH() - 8; return r; } /* * The mainloop widget thing. */ void mogltk::widget::mainloop() { bool end_event_loop = false; widget_mouse_event * mouse = new widget_mouse_event(this); int mx, my; while (!end_event_loop && !engine::quitrequested()) { if (root) root->fulldraw(); mx = mogltk::engine::mouseX(); my = mogltk::engine::mouseY(); Sprite::Cursor->draw(mx - 6, my - 3, Color(0, 0, 0, 50)); Sprite::Cursor->draw(mx - 8, my - 6); // if (w = root->find_widget(mx, my)) { // sh->box(w->GetAX(), w->GetAY(), w->GetAX2(), w->GetAY2(), Color(100, 150, 60, 50)); // } engine::base_o->Flip(); widget::check_timed_events(); LOCK; std::vector del_list_copy = del_list; del_list.clear(); UNLOCK; in_delete_loop = true; for (std::vector::iterator i = del_list_copy.begin(); i != del_list_copy.end(); i++) { delete *i; } in_delete_loop = false; LOCK; std::vector add_list_copy = add_list; add_list.clear(); UNLOCK; for (std::vector::iterator i = add_list_copy.begin(); i != add_list_copy.end(); i++) { (*i)->linkme(); } } delete mouse; }