Back to index

unity  6.0.0
StaticCairoText.cpp
Go to the documentation of this file.
00001 // -*- Mode: C++; indent-tabs-mode: nil; tab-width: 2 -*-
00002 /*
00003  * Copyright (C) 2010-2012 Canonical Ltd
00004  *
00005  * This program is free software: you can redistribute it and/or modify
00006  * it under the terms of the GNU General Public License version 3 as
00007  * published by the Free Software Foundation.
00008  *
00009  * This program is distributed in the hope that it will be useful,
00010  * but WITHOUT ANY WARRANTY; without even the implied warranty of
00011  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00012  * GNU General Public License for more details.
00013  *
00014  * You should have received a copy of the GNU General Public License
00015  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
00016  *
00017  * Authored by: Jay Taoko <jay.taoko@canonical.com>
00018  *              Mirco Müller <mirco.mueller@canonical.com>
00019  *              Tim Penhey <tim.penhey@canonical.com>
00020  */
00021 
00022 #include "StaticCairoText.h"
00023 
00024 #include <gdk/gdk.h>
00025 #include <gtk/gtk.h>
00026 
00027 #include <NuxCore/Size.h>
00028 
00029 #include <Nux/TextureArea.h>
00030 #include <NuxGraphics/CairoGraphics.h>
00031 
00032 #include <pango/pango.h>
00033 #include <pango/pangocairo.h>
00034 
00035 #if defined(NUX_OS_LINUX)
00036 #include <X11/Xlib.h>
00037 #endif
00038 
00039 #include <UnityCore/GLibWrapper.h>
00040 
00041 #include "CairoTexture.h"
00042 
00043 using namespace unity;
00044 
00045 // TODO: Tim Penhey 2011-05-16
00046 // We shouldn't be pushing stuff into the nux namespace from the unity
00047 // codebase, that is just rude.
00048 namespace nux
00049 {
00050 struct StaticCairoText::Impl
00051 {
00052   Impl(StaticCairoText* parent, std::string const& text);
00053   ~Impl();
00054 
00055   PangoEllipsizeMode GetPangoEllipsizeMode() const;
00056   PangoAlignment GetPangoAlignment() const;
00057 
00058   std::string GetEffectiveFont() const;
00059   Size GetTextExtents() const;
00060 
00061   void DrawText(cairo_t* cr, int width, int height, Color const& color);
00062 
00063   void UpdateTexture();
00064   void OnFontChanged();
00065 
00066   static void FontChanged(GObject* gobject, GParamSpec* pspec, gpointer data);
00067 
00068   StaticCairoText* parent_;
00069   bool accept_key_nav_focus_;
00070   bool need_new_extent_cache_;
00071   // The three following are all set in get text extents.
00072   mutable Size cached_extent_;
00073   mutable Size cached_base_;
00074   mutable int baseline_;
00075 
00076   std::string text_;
00077   Color text_color_;
00078 
00079   EllipsizeState ellipsize_;
00080   AlignState align_;
00081   AlignState valign_;
00082 
00083   std::string font_;
00084 
00085   BaseTexturePtr texture2D_;
00086 
00087   Size pre_layout_size_;
00088 
00089   int lines_;
00090   int actual_lines_;
00091 };
00092 
00093 StaticCairoText::Impl::Impl(StaticCairoText* parent, std::string const& text)
00094   : parent_(parent)
00095   , accept_key_nav_focus_(false)
00096   , need_new_extent_cache_(true)
00097   , baseline_(0)
00098   , text_(text)
00099   , text_color_(color::White)
00100   , ellipsize_(NUX_ELLIPSIZE_END)
00101   , align_(NUX_ALIGN_LEFT)
00102   , valign_(NUX_ALIGN_TOP)
00103   , lines_(-2)  // should find out why -2...
00104     // the desired height of the layout in Pango units if positive, or desired
00105     // number of lines if negative.
00106   , actual_lines_(0)
00107 {
00108   GtkSettings* settings = gtk_settings_get_default();  // not ref'ed
00109   g_signal_connect(settings, "notify::gtk-font-name",
00110                    (GCallback)FontChanged, this);
00111   g_signal_connect(settings, "notify::gtk-xft-dpi",
00112                    (GCallback)FontChanged, this);
00113 }
00114 
00115 StaticCairoText::Impl::~Impl()
00116 {
00117   GtkSettings* settings = gtk_settings_get_default();  // not ref'ed
00118   g_signal_handlers_disconnect_by_func(settings,
00119                                        (void*)FontChanged,
00120                                        this);
00121 }
00122 
00123 PangoEllipsizeMode StaticCairoText::Impl::GetPangoEllipsizeMode() const
00124 {
00125   switch (ellipsize_)
00126   {
00127   case NUX_ELLIPSIZE_START:
00128     return PANGO_ELLIPSIZE_START;
00129   case NUX_ELLIPSIZE_MIDDLE:
00130     return PANGO_ELLIPSIZE_MIDDLE;
00131   case NUX_ELLIPSIZE_END:
00132     return PANGO_ELLIPSIZE_END;
00133   default:
00134     return PANGO_ELLIPSIZE_NONE;
00135   }
00136 }
00137 
00138 PangoAlignment StaticCairoText::Impl::GetPangoAlignment() const
00139 {
00140   switch (align_)
00141   {
00142   case NUX_ALIGN_LEFT:
00143     return PANGO_ALIGN_LEFT;
00144   case NUX_ALIGN_CENTRE:
00145     return PANGO_ALIGN_CENTER;
00146   default:
00147     return PANGO_ALIGN_RIGHT;
00148   }
00149 }
00150 
00151 
00152   NUX_IMPLEMENT_OBJECT_TYPE (StaticCairoText);
00153 
00154 StaticCairoText::StaticCairoText(std::string const& text,
00155                                  NUX_FILE_LINE_DECL)
00156   : View(NUX_FILE_LINE_PARAM)
00157   , pimpl(new Impl(this, text))
00158 {
00159   SetMinimumSize(1, 1);
00160   SetAcceptKeyNavFocusOnMouseDown(false);
00161 }
00162 
00163 StaticCairoText::~StaticCairoText()
00164 {
00165   delete pimpl;
00166 }
00167 
00168 void StaticCairoText::SetTextEllipsize(EllipsizeState state)
00169 {
00170   pimpl->ellipsize_ = state;
00171   NeedRedraw();
00172 }
00173 
00174 void StaticCairoText::SetTextAlignment(AlignState state)
00175 {
00176   pimpl->align_ = state;
00177   NeedRedraw();
00178 }
00179 
00180 void StaticCairoText::SetTextVerticalAlignment(AlignState state)
00181 {
00182   pimpl->valign_ = state;
00183   QueueDraw();
00184 }
00185 
00186 void StaticCairoText::SetLines(int lines)
00187 {
00188   pimpl->lines_ = lines;
00189   pimpl->UpdateTexture();
00190   QueueDraw();
00191 }
00192 
00193 void StaticCairoText::PreLayoutManagement()
00194 {
00195   Geometry geo = GetGeometry();
00196   pimpl->pre_layout_size_.width = geo.width;
00197   pimpl->pre_layout_size_.height = geo.height;
00198 
00199   SetBaseSize(pimpl->cached_extent_.width,
00200               pimpl->cached_extent_.height);
00201 
00202   if (pimpl->texture2D_.IsNull())
00203   {
00204     pimpl->UpdateTexture();
00205   }
00206 
00207   View::PreLayoutManagement();
00208 }
00209 
00210 long StaticCairoText::PostLayoutManagement(long layoutResult)
00211 {
00212   long result = 0;
00213 
00214   Geometry const& geo = GetGeometry();
00215 
00216   int old_width = pimpl->pre_layout_size_.width;
00217   if (old_width < geo.width)
00218     result |= eLargerWidth;
00219   else if (old_width > geo.width)
00220     result |= eSmallerWidth;
00221   else
00222     result |= eCompliantWidth;
00223 
00224   int old_height = pimpl->pre_layout_size_.height;
00225   if (old_height < geo.height)
00226     result |= eLargerHeight;
00227   else if (old_height > geo.height)
00228     result |= eSmallerHeight;
00229   else
00230     result |= eCompliantHeight;
00231 
00232   return result;
00233 }
00234 
00235 void StaticCairoText::Draw(GraphicsEngine& gfxContext, bool forceDraw)
00236 {
00237   Geometry const& base = GetGeometry();
00238 
00239   if (pimpl->texture2D_.IsNull() ||
00240       pimpl->cached_base_.width != base.width ||
00241       pimpl->cached_base_.height != base.height)
00242   {
00243     pimpl->cached_base_.width = base.width;
00244     pimpl->cached_base_.height = base.height;
00245     pimpl->UpdateTexture();
00246   }
00247 
00248   gfxContext.PushClippingRectangle(base);
00249 
00250   gPainter.PaintBackground(gfxContext, base);
00251 
00252   TexCoordXForm texxform;
00253   texxform.SetWrap(TEXWRAP_REPEAT, TEXWRAP_REPEAT);
00254   texxform.SetTexCoordType(TexCoordXForm::OFFSET_COORD);
00255 
00256   unsigned int alpha = 0, src = 0, dest = 0;
00257 
00258   gfxContext.GetRenderStates().GetBlend(alpha, src, dest);
00259   gfxContext.GetRenderStates().SetBlend(true, GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
00260 
00261   Color col = color::Black;
00262   col.alpha = 0;
00263   gfxContext.QRP_Color(base.x,
00264                        base.y,
00265                        base.width,
00266                        base.height,
00267                        col);
00268 
00269   gfxContext.QRP_1Tex(base.x,
00270                       base.y + ((base.height - pimpl->cached_extent_.height) / 2),
00271                       base.width,
00272                       base.height,
00273                       pimpl->texture2D_->GetDeviceTexture(),
00274                       texxform,
00275                       pimpl->text_color_);
00276 
00277   gfxContext.GetRenderStates().SetBlend(alpha, src, dest);
00278 
00279   gfxContext.PopClippingRectangle();
00280 }
00281 
00282 void StaticCairoText::DrawContent(GraphicsEngine& gfxContext, bool forceDraw)
00283 {
00284   // intentionally left empty
00285 }
00286 
00287 void StaticCairoText::PostDraw(GraphicsEngine& gfxContext, bool forceDraw)
00288 {
00289   // intentionally left empty
00290 }
00291 
00292 void StaticCairoText::SetText(std::string const& text)
00293 {
00294   if (pimpl->text_ != text)
00295   {
00296     pimpl->text_ = text;
00297     pimpl->need_new_extent_cache_ = true;
00298     pimpl->UpdateTexture();
00299     sigTextChanged.emit(this);
00300   }
00301 }
00302 
00303 std::string StaticCairoText::GetText() const
00304 {
00305   return pimpl->text_;
00306 }
00307 
00308 Color StaticCairoText::GetTextColor() const
00309 {
00310   return pimpl->text_color_;
00311 }
00312 
00313 void StaticCairoText::SetTextColor(Color const& textColor)
00314 {
00315   if (pimpl->text_color_ != textColor)
00316   {
00317     pimpl->text_color_ = textColor;
00318     pimpl->UpdateTexture();
00319     QueueDraw();
00320 
00321     sigTextColorChanged.emit(this);
00322   }
00323 }
00324 
00325 void StaticCairoText::SetFont(std::string const& font)
00326 {
00327   pimpl->font_ = font;
00328   pimpl->need_new_extent_cache_ = true;
00329   Size s = GetTextExtents();
00330   SetMinimumHeight(s.height);
00331   NeedRedraw();
00332   sigFontChanged.emit(this);
00333 }
00334 
00335 int StaticCairoText::GetLineCount() const
00336 {
00337   return pimpl->actual_lines_;
00338 }
00339 
00340 int StaticCairoText::GetBaseline() const
00341 {
00342   return pimpl->baseline_;
00343 }
00344 
00345 Size StaticCairoText::GetTextExtents() const
00346 {
00347   return pimpl->GetTextExtents();
00348 }
00349 
00350 void StaticCairoText::GetTextExtents(int& width, int& height) const
00351 {
00352   Size s = pimpl->GetTextExtents();
00353   width = s.width;
00354   height = s.height;
00355 }
00356 
00357 std::string StaticCairoText::Impl::GetEffectiveFont() const
00358 {
00359   if (font_.empty())
00360   {
00361     GtkSettings* settings = gtk_settings_get_default();  // not ref'ed
00362     glib::String font_name;
00363     g_object_get(settings, "gtk-font-name", &font_name, NULL);
00364     return font_name.Str();
00365   }
00366 
00367   return font_;
00368 }
00369 
00370 Size StaticCairoText::Impl::GetTextExtents() const
00371 {
00372   cairo_surface_t*      surface  = NULL;
00373   cairo_t*              cr       = NULL;
00374   PangoLayout*          layout   = NULL;
00375   PangoFontDescription* desc     = NULL;
00376   PangoContext*         pangoCtx = NULL;
00377   PangoRectangle        inkRect  = {0, 0, 0, 0};
00378   PangoRectangle        logRect  = {0, 0, 0, 0};
00379   int                   dpi      = 0;
00380   GdkScreen*            screen   = gdk_screen_get_default();    // is not ref'ed
00381   GtkSettings*          settings = gtk_settings_get_default();  // is not ref'ed
00382 
00383   if (!need_new_extent_cache_)
00384   {
00385     return cached_extent_;
00386   }
00387 
00388   Size result;
00389   std::string font = GetEffectiveFont();
00390 
00391   int maxwidth = parent_->GetMaximumWidth();
00392 
00393   surface = cairo_image_surface_create(CAIRO_FORMAT_A1, 1, 1);
00394   cr = cairo_create(surface);
00395   cairo_set_font_options(cr, gdk_screen_get_font_options(screen));
00396 
00397   layout = pango_cairo_create_layout(cr);
00398   desc = pango_font_description_from_string(font.c_str());
00399   pango_layout_set_font_description(layout, desc);
00400   pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
00401   pango_layout_set_ellipsize(layout, GetPangoEllipsizeMode());
00402   pango_layout_set_alignment(layout, GetPangoAlignment());
00403   pango_layout_set_markup(layout, text_.c_str(), -1);
00404   pango_layout_set_height(layout, lines_);
00405   pango_layout_set_width(layout, maxwidth * PANGO_SCALE);
00406 
00407   pangoCtx = pango_layout_get_context(layout);  // is not ref'ed
00408   pango_cairo_context_set_font_options(pangoCtx,
00409                                        gdk_screen_get_font_options(screen));
00410   g_object_get(settings, "gtk-xft-dpi", &dpi, NULL);
00411   if (dpi == -1)
00412   {
00413     // use some default DPI-value
00414     pango_cairo_context_set_resolution(pangoCtx, 96.0f);
00415   }
00416   else
00417   {
00418     pango_cairo_context_set_resolution(pangoCtx,
00419                                        (float) dpi / (float) PANGO_SCALE);
00420   }
00421   pango_layout_context_changed(layout);
00422   pango_layout_get_extents(layout, &inkRect, &logRect);
00423 
00424   // logRect has some issues using italic style
00425   if (inkRect.x + inkRect.width > logRect.x + logRect.width)
00426     result.width = std::ceil(static_cast<float>(inkRect.x + inkRect.width - logRect.x) / PANGO_SCALE);
00427   else
00428     result.width  = std::ceil(static_cast<float>(logRect.width) / PANGO_SCALE);
00429 
00430   result.height = std::ceil(static_cast<float>(logRect.height) / PANGO_SCALE);
00431   cached_extent_ = result;
00432   baseline_ = pango_layout_get_baseline(layout) / PANGO_SCALE;
00433 
00434   // clean up
00435   pango_font_description_free(desc);
00436   g_object_unref(layout);
00437   cairo_destroy(cr);
00438   cairo_surface_destroy(surface);
00439   return result;
00440 }
00441 
00442 void StaticCairoText::Impl::DrawText(cairo_t* cr,
00443                                      int width,
00444                                      int height,
00445                                      Color const& color)
00446 {
00447   PangoLayout*          layout     = NULL;
00448   PangoFontDescription* desc       = NULL;
00449   PangoContext*         pangoCtx   = NULL;
00450   int                   dpi        = 0;
00451   GdkScreen*            screen     = gdk_screen_get_default();    // not ref'ed
00452   GtkSettings*          settings   = gtk_settings_get_default();  // not ref'ed
00453 
00454   std::string font(GetEffectiveFont());
00455 
00456   cairo_set_font_options(cr, gdk_screen_get_font_options(screen));
00457   layout = pango_cairo_create_layout(cr);
00458   desc = pango_font_description_from_string(font.c_str());
00459 
00460   pango_layout_set_font_description(layout, desc);
00461   pango_layout_set_wrap(layout, PANGO_WRAP_WORD_CHAR);
00462   pango_layout_set_ellipsize(layout, GetPangoEllipsizeMode());
00463   pango_layout_set_alignment(layout, GetPangoAlignment());
00464   pango_layout_set_markup(layout, text_.c_str(), -1);
00465   pango_layout_set_width(layout, width * PANGO_SCALE);
00466   pango_layout_set_height(layout, height * PANGO_SCALE);
00467 
00468   pango_layout_set_height(layout, lines_);
00469   pangoCtx = pango_layout_get_context(layout);  // is not ref'ed
00470   pango_cairo_context_set_font_options(pangoCtx,
00471                                        gdk_screen_get_font_options(screen));
00472   g_object_get(settings, "gtk-xft-dpi", &dpi, NULL);
00473   if (dpi == -1)
00474   {
00475     // use some default DPI-value
00476     pango_cairo_context_set_resolution(pangoCtx, 96.0f);
00477   }
00478   else
00479   {
00480     pango_cairo_context_set_resolution(pangoCtx,
00481                                        (float) dpi / (float) PANGO_SCALE);
00482   }
00483 
00484   cairo_set_operator(cr, CAIRO_OPERATOR_CLEAR);
00485   cairo_paint(cr);
00486 
00487   cairo_set_operator(cr, CAIRO_OPERATOR_OVER);
00488   cairo_set_source_rgba(cr, color.red, color.green, color.blue, color.alpha);
00489 
00490   pango_layout_context_changed(layout);
00491 
00492   cairo_move_to(cr, 0.0f, 0.0f);
00493   pango_cairo_show_layout(cr, layout);
00494 
00495   actual_lines_ = pango_layout_get_line_count(layout);
00496 
00497   // clean up
00498   pango_font_description_free(desc);
00499   g_object_unref(layout);
00500 }
00501 
00502 void StaticCairoText::Impl::UpdateTexture()
00503 {
00504   Size size = GetTextExtents();
00505   parent_->SetBaseSize(size.width, size.height);
00506   // Now reget the internal geometry as it is clipped by the max size.
00507   Geometry const& geo = parent_->GetGeometry();
00508 
00509   CairoGraphics cairo_graphics(CAIRO_FORMAT_ARGB32,
00510                                geo.width, geo.height);
00511 
00512   DrawText(cairo_graphics.GetInternalContext(),
00513            geo.width, geo.height, text_color_);
00514 
00515   texture2D_ = texture_ptr_from_cairo_graphics(cairo_graphics);
00516 }
00517 
00518 void StaticCairoText::Impl::FontChanged(GObject* gobject,
00519                                         GParamSpec* pspec,
00520                                         gpointer data)
00521 {
00522   StaticCairoText::Impl* self = static_cast<StaticCairoText::Impl*>(data);
00523   self->OnFontChanged();
00524 }
00525 
00526 void StaticCairoText::Impl::OnFontChanged()
00527 {
00528   need_new_extent_cache_ = true;
00529   UpdateTexture();
00530   parent_->sigFontChanged.emit(parent_);
00531 }
00532 
00533 //
00534 // Key navigation
00535 //
00536 
00537 void StaticCairoText::SetAcceptKeyNavFocus(bool accept)
00538 {
00539   pimpl->accept_key_nav_focus_ = accept;
00540 }
00541 
00542 bool StaticCairoText::AcceptKeyNavFocus()
00543 {
00544   return pimpl->accept_key_nav_focus_;
00545 }
00546 
00547 }