// ASEPRITE gui library // Copyright (C) 2001-2012 David Capello // // This source file is ditributed under a BSD-like license, please // read LICENSE.txt for more information. #include "config.h" #include #include #include "base/unique_ptr.h" #include "gfx/size.h" #include "ui/graphics.h" #include "ui/gui.h" #include "ui/intern.h" #include "ui/paint_event.h" #include "ui/preferred_size_event.h" static const int kTooltipDelayMsecs = 300; using namespace gfx; namespace ui { TooltipManager::TooltipManager() : Widget(JI_WIDGET) { Manager* manager = Manager::getDefault(); manager->addMessageFilter(JM_MOUSEENTER, this); manager->addMessageFilter(JM_KEYPRESSED, this); manager->addMessageFilter(JM_BUTTONPRESSED, this); manager->addMessageFilter(JM_MOUSELEAVE, this); setVisible(false); } TooltipManager::~TooltipManager() { Manager* manager = Manager::getDefault(); manager->removeMessageFilterFor(this); } void TooltipManager::addTooltipFor(Widget* widget, const char* text, int arrowAlign) { m_tips[widget] = TipInfo(text, arrowAlign); } bool TooltipManager::onProcessMessage(Message* msg) { switch (msg->type) { case JM_MOUSEENTER: { UI_FOREACH_WIDGET(*msg->any.widgets, itWidget) { Tips::iterator it = m_tips.find(*itWidget); if (it != m_tips.end()) { m_target.widget = it->first; m_target.tipInfo = it->second; if (m_timer == NULL) { m_timer.reset(new Timer(kTooltipDelayMsecs, this)); m_timer->Tick.connect(&TooltipManager::onTick, this); } m_timer->start(); } } return false; } case JM_KEYPRESSED: case JM_BUTTONPRESSED: case JM_MOUSELEAVE: if (m_tipWindow) { m_tipWindow->closeWindow(NULL); m_tipWindow.reset(); } if (m_timer) m_timer->stop(); return false; } return Widget::onProcessMessage(msg); } void TooltipManager::onTick() { if (!m_tipWindow) { m_tipWindow.reset(new TipWindow(m_target.tipInfo.text.c_str(), true)); gfx::Rect bounds = m_target.widget->getBounds(); int x = jmouse_x(0)+12*jguiscale(); int y = jmouse_y(0)+12*jguiscale(); int w, h; m_tipWindow->setArrowAlign(m_target.tipInfo.arrowAlign); m_tipWindow->remap_window(); w = jrect_w(m_tipWindow->rc); h = jrect_h(m_tipWindow->rc); switch (m_target.tipInfo.arrowAlign) { case JI_TOP | JI_LEFT: x = bounds.x + bounds.w; y = bounds.y + bounds.h; break; case JI_TOP | JI_RIGHT: x = bounds.x - w; y = bounds.y + bounds.h; break; case JI_BOTTOM | JI_LEFT: x = bounds.x + bounds.w; y = bounds.y - h; break; case JI_BOTTOM | JI_RIGHT: x = bounds.x - w; y = bounds.y - h; break; case JI_TOP: x = bounds.x + bounds.w/2 - w/2; y = bounds.y + bounds.h; break; case JI_BOTTOM: x = bounds.x + bounds.w/2 - w/2; y = bounds.y - h; break; case JI_LEFT: x = bounds.x + bounds.w; y = bounds.y + bounds.h/2 - h/2; break; case JI_RIGHT: x = bounds.x - w; y = bounds.y + bounds.h/2 - h/2; break; } // if (x+w > JI_SCREEN_W) { // x = jmouse_x(0) - w - 4*jguiscale(); // y = jmouse_y(0); // } m_tipWindow->position_window(MID(0, x, JI_SCREEN_W-w), MID(0, y, JI_SCREEN_H-h)); m_tipWindow->openWindow(); } m_timer->stop(); } // TipWindow TipWindow::TipWindow(const char *text, bool close_on_buttonpressed) : Window(false, text) { m_close_on_buttonpressed = close_on_buttonpressed; m_hot_region = NULL; m_filtering = false; m_arrowAlign = 0; set_sizeable(false); set_moveable(false); set_wantfocus(false); setAlign(JI_LEFT | JI_TOP); removeDecorativeWidgets(); initTheme(); } TipWindow::~TipWindow() { if (m_filtering) { m_filtering = false; getManager()->removeMessageFilter(JM_MOTION, this); getManager()->removeMessageFilter(JM_BUTTONPRESSED, this); getManager()->removeMessageFilter(JM_KEYPRESSED, this); } if (m_hot_region != NULL) { jregion_free(m_hot_region); } } /** * @param region The new hot-region. This pointer is holded by the @a widget. * So you can't destroy it after calling this routine. */ void TipWindow::set_hotregion(JRegion region) { ASSERT(region != NULL); if (m_hot_region != NULL) jregion_free(m_hot_region); if (!m_filtering) { m_filtering = true; getManager()->addMessageFilter(JM_MOTION, this); getManager()->addMessageFilter(JM_BUTTONPRESSED, this); getManager()->addMessageFilter(JM_KEYPRESSED, this); } m_hot_region = region; } int TipWindow::getArrowAlign() const { return m_arrowAlign; } void TipWindow::setArrowAlign(int arrowAlign) { m_arrowAlign = arrowAlign; } bool TipWindow::onProcessMessage(Message* msg) { switch (msg->type) { case JM_CLOSE: if (m_filtering) { m_filtering = false; getManager()->removeMessageFilter(JM_MOTION, this); getManager()->removeMessageFilter(JM_BUTTONPRESSED, this); getManager()->removeMessageFilter(JM_KEYPRESSED, this); } break; case JM_MOUSELEAVE: if (m_hot_region == NULL) this->closeWindow(NULL); break; case JM_KEYPRESSED: if (m_filtering && msg->key.scancode < KEY_MODIFIERS) this->closeWindow(NULL); break; case JM_BUTTONPRESSED: /* if the user click outside the window, we have to close the tooltip window */ if (m_filtering) { Widget* picked = this->pick(msg->mouse.x, msg->mouse.y); if (!picked || picked->getRoot() != this) { this->closeWindow(NULL); } } /* this is used when the user click inside a small text tooltip */ if (m_close_on_buttonpressed) this->closeWindow(NULL); break; case JM_MOTION: if (m_hot_region != NULL && getManager()->getCapture() == NULL) { struct jrect box; /* if the mouse is outside the hot-region we have to close the window */ if (!jregion_point_in(m_hot_region, msg->mouse.x, msg->mouse.y, &box)) { this->closeWindow(NULL); } } break; } return Window::onProcessMessage(msg); } void TipWindow::onPreferredSize(PreferredSizeEvent& ev) { ScreenGraphics g; g.setFont(getFont()); Size resultSize = g.fitString(getText(), (getClientBounds() - getBorder()).w, getAlign()); resultSize.w += this->border_width.l + this->border_width.r; resultSize.h += this->border_width.t + this->border_width.b; if (!getChildren().empty()) { Size maxSize(0, 0); Size reqSize; UI_FOREACH_WIDGET(getChildren(), it) { Widget* child = *it; reqSize = child->getPreferredSize(); maxSize.w = MAX(maxSize.w, reqSize.w); maxSize.h = MAX(maxSize.h, reqSize.h); } resultSize.w = MAX(resultSize.w, border_width.l + maxSize.w + border_width.r); resultSize.h += maxSize.h; } ev.setPreferredSize(resultSize); } void TipWindow::onInitTheme(InitThemeEvent& ev) { Window::onInitTheme(ev); this->border_width.l = 6 * jguiscale(); this->border_width.t = 6 * jguiscale(); this->border_width.r = 6 * jguiscale(); this->border_width.b = 7 * jguiscale(); // Setup the background color. setBgColor(makecol(255, 255, 200)); } void TipWindow::onPaint(PaintEvent& ev) { getTheme()->paintTooltip(ev); } } // namespace ui