/** * \file GWorkArea.C * This file is part of LyX, the document processor. * Licence details can be found in the file COPYING. * * \author Huang Ying * * Full author contact details are available in file CREDITS. */ #include // Too hard to make concept checks work with this file #ifdef _GLIBCXX_CONCEPT_CHECKS #undef _GLIBCXX_CONCEPT_CHECKS #endif #ifdef _GLIBCPP_CONCEPT_CHECKS #undef _GLIBCPP_CONCEPT_CHECKS #endif #include "GWorkArea.h" #include "GView.h" #include "GtkmmX.h" #include "GLyXKeySym.h" #include "BufferView.h" #include "debug.h" #include "funcrequest.h" #include "LColor.h" using boost::shared_ptr; using std::string; namespace lyx { namespace frontend { namespace { mouse_button::state gButtonToLyx(guint gdkbutton) { // GDK uses int 1,2,3 but lyx uses enums (1,2,4) switch (gdkbutton) { case 1: return mouse_button::button1; case 2: return mouse_button::button2; case 3: return mouse_button::button3; case 4: return mouse_button::button4; case 5: return mouse_button::button5; } // This shouldn't happen, according to gdk docs lyxerr << "gButtonToLyx: unhandled button index\n"; return mouse_button::button1; } } // namespace anon ColorCache colorCache; Gdk::Color * ColorCache::getColor(LColor_color clr) { MapIt it = cache_.find(clr); return it == cache_.end() ? 0 : it->second.get(); } XftColor * ColorCache::getXftColor(LColor_color clr) { MapIt2 it = cache2_.find(clr); return it == cache2_.end() ? 0 : it->second.get(); } void ColorCache::cacheColor(LColor_color clr, Gdk::Color * gclr) { cache_[clr] = shared_ptr(gclr); } void ColorCache::cacheXftColor(LColor_color clr, XftColor * xclr) { cache2_[clr] = shared_ptr(xclr); } void ColorCache::clear() { cache_.clear(); cache2_.clear(); } XftColor * ColorHandler::getXftColor(LColor_color clr) { XftColor * xclr = colorCache.getXftColor(clr); if (!xclr) { xclr = new XftColor; Colormap colormap = GDK_COLORMAP_XCOLORMAP( owner_.getColormap()->gobj()); Visual * visual = GDK_VISUAL_XVISUAL( owner_.getColormap()->get_visual()->gobj()); XftColorAllocName(owner_.getDisplay(), visual, colormap, const_cast( lcolor.getX11Name(clr).c_str()) , xclr); colorCache.cacheXftColor(clr, xclr); } return xclr; } Gdk::Color * ColorHandler::getGdkColor(LColor_color clr) { Gdk::Color * gclr = colorCache.getColor(clr); if (!gclr) { gclr = new Gdk::Color; gclr->parse(lcolor.getX11Name(clr)); owner_.getColormap()->alloc_color(*gclr); colorCache.cacheColor(clr, gclr); } return gclr; } namespace { mouse_button::state gtkButtonState(unsigned int state) { mouse_button::state b = mouse_button::none; if (state & GDK_BUTTON1_MASK) b = mouse_button::button1; else if (state & GDK_BUTTON2_MASK) b = mouse_button::button2; else if (state & GDK_BUTTON3_MASK) b = mouse_button::button3; else if (state & GDK_BUTTON3_MASK) b = mouse_button::button3; else if (state & GDK_BUTTON4_MASK) b = mouse_button::button4; else if (state & GDK_BUTTON5_MASK) b = mouse_button::button5; return b; } key_modifier::state gtkKeyState(guint state) { key_modifier::state k = key_modifier::none; if (state & GDK_CONTROL_MASK) k |= key_modifier::ctrl; if (state & GDK_SHIFT_MASK) k |= key_modifier::shift; if (state & GDK_MOD1_MASK) k |= key_modifier::alt; return k; } void inputCommitRelay(GtkIMContext */*imcontext*/, gchar * str, GWorkArea * area) { area->inputCommit(str); } } GWorkArea::GWorkArea(LyXView & owner, int width, int height) : view_(owner), workAreaPixmap_(0), painter_(*this), draw_(0), colorHandler_(*this), adjusting_(false) { workArea_.set_size_request(width, height); workArea_.set_double_buffered(false); workArea_.add_events(Gdk::STRUCTURE_MASK | Gdk::EXPOSURE_MASK | Gdk::BUTTON_PRESS_MASK | Gdk::BUTTON_RELEASE_MASK | Gdk::KEY_PRESS_MASK | Gdk::BUTTON1_MOTION_MASK); workArea_.signal_expose_event().connect( sigc::mem_fun(*this, &GWorkArea::onExpose)); workArea_.signal_configure_event().connect( sigc::mem_fun(*this, &GWorkArea::onConfigure)); workArea_.signal_button_press_event().connect( sigc::mem_fun(*this, &GWorkArea::onButtonPress)); workArea_.signal_button_release_event().connect( sigc::mem_fun(*this, &GWorkArea::onButtonRelease)); workArea_.signal_key_press_event().connect( sigc::mem_fun(*this, &GWorkArea::onKeyPress)); workArea_.signal_motion_notify_event().connect( sigc::mem_fun(*this, &GWorkArea::onMotionNotify)); workArea_.show(); vscrollbar_.get_adjustment()->signal_value_changed().connect( sigc::mem_fun(*this, &GWorkArea::onScroll)); workArea_.signal_scroll_event().connect( sigc::mem_fun(*this, &GWorkArea::onScrollWheel)); vscrollbar_.show(); hbox_.children().push_back(Gtk::Box_Helpers::Element(workArea_)); hbox_.children().push_back( Gtk::Box_Helpers::Element(vscrollbar_,Gtk::PACK_SHRINK)); hbox_.show(); GView & gview = static_cast(owner); gview.getBox(GView::Center).children().push_back( Gtk::Box_Helpers::Element(hbox_)); workArea_.set_flags(workArea_.get_flags() | Gtk::CAN_DEFAULT | Gtk::CAN_FOCUS); workArea_.grab_default(); gview.setGWorkArea(&workArea_); imContext_ = GTK_IM_CONTEXT(gtk_im_multicontext_new()); g_signal_connect(G_OBJECT(imContext_), "commit", G_CALLBACK(&inputCommitRelay), this); } GWorkArea::~GWorkArea() { g_object_unref(imContext_); } Painter & GWorkArea::getPainter() { return painter_; } int GWorkArea::workWidth() const { return workArea_.get_width(); } int GWorkArea::workHeight() const { return workArea_.get_height(); } int GWorkArea::xpos() const { return 0; } int GWorkArea::ypos() const { return 0; } Glib::RefPtr GWorkArea::getWindow() { return workArea_.get_window(); } Display * GWorkArea::getDisplay() const { return GDK_WINDOW_XDISPLAY( const_cast(workArea_.get_window()->gobj())); } Glib::RefPtr GWorkArea::getPixmap() { return workAreaPixmap_; } Glib::RefPtr GWorkArea::getGC() { return workAreaGC_; } Glib::RefPtr GWorkArea::getColormap() { return workArea_.get_colormap(); } XftDraw * GWorkArea::getXftDraw() { return draw_; } ColorHandler & GWorkArea::getColorHandler() { return colorHandler_; } bool GWorkArea::onExpose(GdkEventExpose * event) { workArea_.get_window()->draw_drawable( workArea_.get_style()->get_black_gc(), workAreaPixmap_, event->area.x, event->area.y, event->area.x, event->area.y, event->area.width, event->area.height); return true; } bool GWorkArea::onConfigure(GdkEventConfigure * /*event*/) { int x, y, width, height, depth; workArea_.get_window()->get_geometry(x, y, width, height, depth); if (draw_) XftDrawDestroy(draw_); workAreaPixmap_ = Gdk::Pixmap::create(workArea_.get_window(), width, height, depth); Pixmap pixmap = GDK_PIXMAP_XID(workAreaPixmap_->gobj()); Colormap colormap = GDK_COLORMAP_XCOLORMAP( workArea_.get_colormap()->gobj()); Visual * visual = GDK_VISUAL_XVISUAL( workArea_.get_colormap()->get_visual()->gobj()); draw_ = XftDrawCreate(getDisplay(), pixmap, visual, colormap); if (!workAreaGC_) { workAreaGC_ = Gdk::GC::create(workArea_.get_window()); Gdk::Cursor cursor(Gdk::XTERM); workArea_.get_window()->set_cursor(cursor); gtk_im_context_set_client_window( imContext_, workArea_.get_window()->gobj()); } view_.view()->workAreaResize(); return true; } void GWorkArea::setScrollbarParams(int height, int pos, int line_height) { if (adjusting_) return; adjusting_ = true; Gtk::Adjustment * adjustment = vscrollbar_.get_adjustment(); adjustment->set_lower(0); int workAreaHeight = workHeight(); if (!height || height < workAreaHeight) { adjustment->set_upper(workAreaHeight); adjustment->set_page_size(workAreaHeight); adjustment->set_value(0); adjustment->changed(); adjusting_ = false; return; } adjustment->set_step_increment(line_height * 3); adjustment->set_page_increment(workAreaHeight - line_height); // Allow the user half a screen of blank at the end // to make scrollbar consistant with centering the cursor adjustment->set_upper(height + workAreaHeight / 4); adjustment->set_page_size(workAreaHeight); adjustment->set_value(pos); adjustment->changed(); adjusting_ = false; } void GWorkArea::onScroll() { if (adjusting_) return; adjusting_ = true; double val = vscrollbar_.get_adjustment()->get_value(); view_.view()->scrollDocView(static_cast(val)); adjusting_ = false; } bool GWorkArea::onScrollWheel(GdkEventScroll * event) { Gtk::Adjustment * adjustment = vscrollbar_.get_adjustment(); double step; if (event->state & GDK_CONTROL_MASK) step = adjustment->get_page_increment(); else step = adjustment->get_step_increment(); if (event->direction == GDK_SCROLL_UP) step *= -1.0f; double target = adjustment->get_value() + step; // Prevent the user getting a whole screen of blank when they // try to scroll past the end of the doc double max = adjustment->get_upper() - workHeight(); if (target > max) target = max; adjustment->set_value(target); return true; } bool GWorkArea::onButtonPress(GdkEventButton * event) { kb_action ka = LFUN_MOUSE_PRESS; switch (event->type) { case GDK_BUTTON_PRESS: ka = LFUN_MOUSE_PRESS; break; case GDK_2BUTTON_PRESS: ka = LFUN_MOUSE_DOUBLE; break; case GDK_3BUTTON_PRESS: ka = LFUN_MOUSE_TRIPLE; break; default: break; } view_.view()->workAreaDispatch(FuncRequest(ka, static_cast(event->x), static_cast(event->y), gButtonToLyx(event->button))); workArea_.grab_focus(); return true; } bool GWorkArea::onButtonRelease(GdkEventButton * event) { view_.view()->workAreaDispatch(FuncRequest(LFUN_MOUSE_RELEASE, static_cast(event->x), static_cast(event->y), gButtonToLyx(event->button))); return true; } bool GWorkArea::onMotionNotify(GdkEventMotion * event) { static guint32 timeBefore; Gtk::Adjustment * adjustment = vscrollbar_.get_adjustment(); double step = adjustment->get_step_increment(); double value = adjustment->get_value(); if (event->x < 0) value -= step; else if (event->x > workArea_.get_height()) value += step; if (value != adjustment->get_value()) { if (event->time - timeBefore > 200) { adjustment->set_value(value); adjustment->value_changed(); } timeBefore = event->time; } view_.view()->workAreaDispatch(FuncRequest(LFUN_MOUSE_MOTION, static_cast(event->x), static_cast(event->y), gtkButtonState(event->state))); return true; } void GWorkArea::inputCommit(gchar * str) { inputCache_ = Glib::locale_from_utf8(str); } bool GWorkArea::onKeyPress(GdkEventKey * event) { #ifdef I18N inputCache_ = ""; bool inputGet = gtk_im_context_filter_keypress(imContext_, event); // cope with ascii if ((inputGet && inputCache_.size() == 1 && inputCache_[0] < 128) || !inputGet) { #endif GLyXKeySym *glk = new GLyXKeySym(event->keyval); view_.view()->workAreaKeyPress(LyXKeySymPtr(glk), gtkKeyState(event->state)); #ifdef I18N } else if (!inputCache_.empty()) workAreaCJK_IMprocess(inputCache_.size(), inputCache_.data()); #endif return true; } void GWorkArea::onClipboardGet(Gtk::SelectionData & /*selection_data*/, guint /*info*/) { view_.view()->selectionRequested(); } void GWorkArea::onClipboardClear() { // selectionLost(); } void GWorkArea::haveSelection(bool toHave) const { if (toHave) { Glib::RefPtr clipboard = Gtk::Clipboard::get(GDK_SELECTION_PRIMARY); std::vector listTargets; listTargets.push_back(Gtk::TargetEntry("UTF8_STRING")); clipboard->set(listTargets, sigc::mem_fun(const_cast(*this), &GWorkArea::onClipboardGet), sigc::mem_fun(const_cast(*this), &GWorkArea::onClipboardClear)); } } // ENCODING: Gtk::Clipboard returns UTF-8, we assume that the backend // wants ISO-8859-1 and convert it to that. string const GWorkArea::getClipboard() const { Glib::RefPtr clipboard = Gtk::Clipboard::get(GDK_SELECTION_PRIMARY); return Glib::convert_with_fallback( clipboard->wait_for_text(), "ISO-8859-1", "UTF-8"); } // ENCODING: we assume that the backend passes us ISO-8859-1 and // convert from that to UTF-8 before passing to GTK void GWorkArea::putClipboard(string const & str) const { Glib::RefPtr clipboard = Gtk::Clipboard::get(GDK_SELECTION_PRIMARY); clipboard->set_text(Glib::convert(str, "UTF-8", "ISO-8859-1")); } } // namespace frontend } // namespace lyx