// -*- C++ -*- /* * GChemPaint text plugin * texttool.cc * * Copyright (C) 2002-2007 Jean Bréfort * * 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 "gchempaint-config.h" #include "texttool.h" #include "lib/text.h" #include "lib/document.h" #include "lib/application.h" #include "lib/settings.h" #include "lib/theme.h" #include "lib/window.h" #include #include #include static void on_get_data (GtkClipboard *clipboard, GtkSelectionData *selection_data, guint info, gcpTextTool* tool) { tool->OnGetData (clipboard, selection_data, info); } static void on_sel_changed (gcpTextTool *tool) { tool->UpdateAttributeList (); } static bool filter_attribute (PangoAttribute *attribute, gcpTextTool *tool) { int index = tool->GetIndex (); if (index < 0) return false; if ((index)? (attribute->start_index < (unsigned) index && attribute->end_index >= (unsigned) index): attribute->start_index == 0) { switch (attribute->klass->type) { case PANGO_ATTR_FAMILY: tool->SetFamilyName (((PangoAttrString*)attribute)->value); break; case PANGO_ATTR_STYLE: tool->SetStyle ((PangoStyle) ((PangoAttrInt*)attribute)->value); break; case PANGO_ATTR_WEIGHT: tool->SetWeight ((PangoWeight) ((PangoAttrInt*)attribute)->value); break; case PANGO_ATTR_VARIANT: tool->SetVariant ((PangoVariant) ((PangoAttrInt*)attribute)->value); break; case PANGO_ATTR_STRETCH: tool->SetStretch ((PangoStretch) ((PangoAttrInt*)attribute)->value); break; case PANGO_ATTR_SIZE: tool->SetSize (((PangoAttrInt*)attribute)->value); break; case PANGO_ATTR_UNDERLINE: tool->SetUnderline ((PangoUnderline) ((PangoAttrInt*)attribute)->value); break; case PANGO_ATTR_STRIKETHROUGH: tool->SetStrikethrough (((PangoAttrInt*)attribute)->value); break; case PANGO_ATTR_RISE: tool->SetRise (((PangoAttrInt*)attribute)->value); break; case PANGO_ATTR_FOREGROUND: { PangoColor color = ((PangoAttrColor*)attribute)->color; tool->SetColor (RGBA_TO_UINT(color.red >> 8, color.green >> 8, color.blue >> 8, 0xff)); break; } default: break; } } return false; } gcpTextTool::gcpTextTool (gcpApplication* App, string Id): gcpTool (App, Id), m_FamilyList (NULL) { m_Active = NULL; m_bUndo = true; m_CurNode = m_InitNode = NULL; m_Strikethrough = false; m_FontDesc = NULL; gcpTheme *pTheme = ThemeManager.GetTheme ("Default"); m_FamilyName = pTheme->GetTextFontFamily (); m_Style = pTheme->GetFontStyle (); m_Weight = pTheme->GetFontWeight (); m_Stretch = pTheme->GetFontStretch (); m_Variant = pTheme->GetFontVariant (); m_Size = pTheme->GetFontSize (); m_Underline = PANGO_UNDERLINE_NONE; m_Rise = 0; m_Color = RGBA_BLACK; m_SelSignal = 0; } gcpTextTool::~gcpTextTool () { map::iterator i, iend = m_Families.end (); for (i = m_Families.begin (); i != iend; i++) { g_object_unref ((*i).second); } map::iterator j, jend = m_Faces.end (); for (j = m_Faces.begin (); j != jend; j++) { g_object_unref ((*j).second); } m_FontDesc = NULL; } bool gcpTextTool::OnClicked () { if (m_Active && ((m_pObject == NULL) || (m_pObject->GetType () != TextType) || (m_Active != g_object_get_data (G_OBJECT (m_pData->Items[m_pObject]), "text")))) Unselect (); bool create = false; if (!m_pObject) { gcpTheme *pTheme = m_pView->GetDoc ()->GetTheme (); gcpText *text = new gcpText(m_x0 / pTheme->GetZoomFactor (), m_y0 / pTheme->GetZoomFactor ()); m_pView->GetDoc ()->AddObject (text); m_pView->GetDoc ()->AbortOperation (); m_pObject = text; create = true; } if (m_pObject) { if (m_pObject->GetType () != TextType) return false; m_pObject->SetSelected (m_pWidget, SelStateUpdating); m_Active = GNOME_CANVAS_PANGO (g_object_get_data (G_OBJECT (m_pData->Items[m_pObject]), "text")); m_pView->SetGnomeCanvasPangoActive (m_Active); g_object_set (G_OBJECT (m_Active), "editing", true, NULL); m_CurNode = ((gcpText*) m_pObject)->SaveSelected (); m_InitNode = ((gcpText*) m_pObject)->SaveSelected (); m_pView->GetDoc ()->GetWindow ()->ActivateActionWidget ("/MainMenu/FileMenu/SaveAsImage", false); if (m_SelSignal == 0) m_SelSignal = g_signal_connect_swapped (m_Active, "sel-changed", G_CALLBACK (on_sel_changed), this); if (create) BuildAttributeList (); else UpdateAttributeList (); } return true; } bool gcpTextTool::OnEvent (GdkEvent* event) { if (m_Active) { if ((event->type == GDK_KEY_PRESS) || (event->type == GDK_KEY_RELEASE)) { if (event->key.state & GDK_CONTROL_MASK) { switch (event->key.keyval) { case GDK_Right: case GDK_Left: case GDK_Up: case GDK_Down: case GDK_End: case GDK_Home: case GDK_Delete: case GDK_KP_Delete: case GDK_BackSpace: break; case GDK_a: m_pView->OnSelectAll (); return true; case GDK_z: m_pView->GetDoc()->OnUndo (); return true; case GDK_Z: m_pView->GetDoc()->OnRedo (); return true; case GDK_c: CopySelection (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD)); return true; case GDK_v: PasteSelection (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD)); return true; case GDK_x: CutSelection (gtk_clipboard_get (GDK_SELECTION_CLIPBOARD)); return true; case GDK_i: m_Style = (m_Style == PANGO_STYLE_NORMAL)? PANGO_STYLE_ITALIC: PANGO_STYLE_NORMAL; SelectBestFontFace (); BuildAttributeList (); return true; case GDK_u: gtk_combo_box_set_active (m_UnderlineBox, ((m_Underline == PANGO_UNDERLINE_SINGLE)? PANGO_UNDERLINE_NONE: PANGO_UNDERLINE_SINGLE)); return true; case GDK_b: m_Weight = (m_Weight == PANGO_WEIGHT_NORMAL)? PANGO_WEIGHT_BOLD: PANGO_WEIGHT_NORMAL; SelectBestFontFace (); BuildAttributeList (); return true; case GDK_k: gtk_toggle_button_set_active (m_StrikethroughBtn, !m_Strikethrough); return true; case GDK_plus: case GDK_dead_circumflex: if (m_Rise == 0) { m_Size = m_Size * 2 / 3; m_Rise = m_Size / PANGO_SCALE; } else if (m_Rise < 0) m_Rise = m_Size / PANGO_SCALE; else { m_Size = m_Size * 3 / 2; m_Rise = 0; } g_signal_handler_block (m_RiseButton, m_RiseSignal); gtk_spin_button_set_value (m_RiseButton, m_Rise); m_Rise *= PANGO_SCALE; g_signal_handler_unblock (m_RiseButton, m_RiseSignal); SetSizeFull (true); return true; case GDK_equal: case GDK_underscore: if (m_Rise == 0) { m_Size = m_Size * 2 / 3; m_Rise = - m_Size / PANGO_SCALE / 2; } else if (m_Rise > 0) m_Rise = - m_Size / PANGO_SCALE/ 2; else { m_Size = m_Size * 3 / 2; m_Rise = 0; } g_signal_handler_block (m_RiseButton, m_RiseSignal); gtk_spin_button_set_value (m_RiseButton, m_Rise); m_Rise *= PANGO_SCALE; g_signal_handler_unblock (m_RiseButton, m_RiseSignal); SetSizeFull (true); return true; case GDK_space: { GnomeCanvasPango* saved = m_Active; m_Active = NULL; UpdateAttributeList (); m_Active = saved; BuildAttributeList (); return true; } default: break; } } if (!g_utf8_validate (((GdkEventKey*) event)->string, -1, NULL)) { gsize r, w; gchar* newstr = g_locale_to_utf8 (((GdkEventKey*) event)->string, ((GdkEventKey*) event)->length, &r, &w, NULL); g_free (((GdkEventKey*) event)->string); ((GdkEventKey*) event)->string = newstr; ((GdkEventKey*) event)->length = w; } gnome_canvas_item_grab_focus ((GnomeCanvasItem*) m_Active); GnomeCanvasItemClass* klass = GNOME_CANVAS_ITEM_CLASS (G_OBJECT_GET_CLASS (m_Active)); klass->event ((GnomeCanvasItem*) m_Active, event); return true; } else if (event->type == GDK_BUTTON_PRESS) { switch (event->button.button) { case 2: return true; } } else if (event->type == GDK_MOTION_NOTIFY) { GnomeCanvasItemClass* klass = GNOME_CANVAS_ITEM_CLASS (G_OBJECT_GET_CLASS (m_Active)); klass->event ((GnomeCanvasItem*) m_Active, event); } } return false; } void gcpTextTool::Activate () { if (!m_Active) UpdateAttributeList (); } bool gcpTextTool::Deactivate () { if (m_Active) Unselect (); return true; } bool gcpTextTool::NotifyViewChange () { return (m_Active)? Unselect (): true; } bool gcpTextTool::Unselect () { if (!m_Active) return true; if (m_SelSignal) { g_signal_handler_disconnect (m_Active, m_SelSignal); m_SelSignal = 0; } g_object_set (G_OBJECT (m_Active), "editing", false, NULL); m_pView->SetGnomeCanvasPangoActive (NULL); Object *pObj = (Object*) g_object_get_data (G_OBJECT (m_Active), "object"); pObj->SetSelected (m_pWidget, SelStateUnselected); char const *text = pango_layout_get_text (gnome_canvas_pango_get_layout (m_Active)); m_Active = NULL; while (!m_UndoList.empty ()) { xmlFree(m_UndoList.front ()); m_UndoList.pop_front (); } while (!m_RedoList.empty ()) { xmlFree(m_RedoList.front ()); m_RedoList.pop_front (); } xmlBufferPtr initbuf = xmlBufferCreate (); xmlBufferPtr endbuf = xmlBufferCreate (); xmlNodeDump (initbuf, m_pApp->GetXmlDoc (), m_InitNode, 0, 0); xmlNodeDump (endbuf, m_pApp->GetXmlDoc (), m_CurNode, 0, 0); if (strcmp ((char*) initbuf->content, (char*) endbuf->content)) { char* initval = (char*) xmlNodeGetContent (m_InitNode); char* endval = (char*) xmlNodeGetContent (m_CurNode); gcpOperation *pOp = NULL; if ((initval && strlen (initval))) { if (endval && strlen (endval)) { pOp = m_pView->GetDoc ()->GetNewOperation (GCP_MODIFY_OPERATION); pOp->AddNode (m_InitNode, 0); pOp->AddNode (m_CurNode, 1); m_CurNode = m_InitNode = NULL; } else { pOp = m_pView->GetDoc ()->GetNewOperation (GCP_DELETE_OPERATION); pOp->AddNode (m_InitNode); m_InitNode = NULL; } } else if (endval && strlen (endval)) { pOp = m_pView->GetDoc ()->GetNewOperation (GCP_ADD_OPERATION); pOp->AddNode (m_CurNode); m_CurNode = NULL; } if (initval) xmlFree (initval); if (endval) xmlFree (endval); if (pOp) m_pView->GetDoc ()->PushOperation (pOp, m_bUndo); m_bUndo = true; } xmlBufferFree (initbuf); xmlBufferFree (endbuf); if (m_CurNode) xmlFree (m_CurNode); if (m_InitNode) xmlFree (m_InitNode); m_CurNode = m_InitNode = NULL; if (!*text) { Object* pMol = pObj->GetMolecule (); //if pObj is a fragment if (pMol) pObj = pMol; m_pView->GetDoc ()->Remove (pObj); m_pView->GetDoc ()->AbortOperation (); } m_pView->GetDoc ()->GetWindow ()->ActivateActionWidget ("/MainMenu/FileMenu/SaveAsImage", m_pView->GetDoc ()->HasChildren ()); return true; } bool gcpTextTool::DeleteSelection () { if (!m_Active) return false; unsigned start, end; gcpTextObject *text = dynamic_cast ((Object*) g_object_get_data (G_OBJECT (m_Active), "object")); if (!text) return false; text->GetSelectionBounds (start, end); gcp_pango_layout_replace_text (gnome_canvas_pango_get_layout (m_Active), start, end - start, "", NULL); gnome_canvas_pango_set_selection_bounds (m_Active, start, start); text->OnChanged (true); return true; } bool gcpTextTool::CopySelection (GtkClipboard *clipboard) { if (!m_Active) return false; unsigned start, end; gcpText *text = (gcpText*) g_object_get_data (G_OBJECT (m_Active), "object"); text->GetSelectionBounds (start, end); if (start == end) return false; m_pData->Copy (clipboard); //To clean the xmlDoc xmlDocPtr pDoc = m_pData->GetXmlDoc (clipboard); if (!pDoc) return false; pDoc->children = xmlNewDocNode (pDoc, NULL, (xmlChar*) "chemistry", NULL); xmlNsPtr ns = xmlNewNs (pDoc->children, (xmlChar*) "http://www.nongnu.org/gchempaint", (xmlChar*) "gcp"); xmlSetNs (pDoc->children, ns); xmlNodePtr node = text->SaveSelection (pDoc); if (node) xmlAddChild (pDoc->children, node); else return false; gtk_clipboard_set_with_data (clipboard, targets, ClipboardFormats, (GtkClipboardGetFunc) on_get_data, (GtkClipboardClearFunc) on_clear_data, this); gtk_clipboard_request_contents (clipboard, gdk_atom_intern ("TARGETS", FALSE), (GtkClipboardReceivedFunc) on_receive_targets, m_pApp); return true; } bool gcpTextTool::CutSelection (GtkClipboard *clipboard) { if (!CopySelection (clipboard)) return false; return DeleteSelection (); } bool gcpTextTool::PasteSelection (GtkClipboard *clipboard) { if (!m_Active) return false; guint *DataType = (clipboard == gtk_clipboard_get (GDK_SELECTION_CLIPBOARD))? &ClipboardDataType: &ClipboardDataType1; GdkAtom targets_atom = gdk_atom_intern (targets[*DataType].target, FALSE); gtk_clipboard_request_contents (clipboard, targets_atom, (GtkClipboardReceivedFunc) on_receive, m_pView); return true; } struct FragState { PangoAttrList *l; unsigned start; }; static bool filter_fragment (PangoAttribute *attr, struct FragState *s) { PangoAttribute *new_attr = pango_attribute_copy (attr); new_attr->start_index += s->start; new_attr->end_index += s->start; pango_attr_list_change (s->l, new_attr); return false; } bool gcpTextTool::OnReceive (GtkClipboard *clipboard, GtkSelectionData *data, int type) { if (!m_Active) return false; guint *DataType = (clipboard == gtk_clipboard_get (GDK_SELECTION_CLIPBOARD))? &ClipboardDataType: &ClipboardDataType1; g_return_val_if_fail ((data->target == gdk_atom_intern (targets[*DataType].target, FALSE)), FALSE); gcpText *text = (gcpText*) g_object_get_data (G_OBJECT (m_Active), "object"); unsigned start, end; text->GetSelectionBounds (start, end); PangoLayout *layout = gnome_canvas_pango_get_layout (m_Active); switch (*DataType) { case GCP_CLIPBOARD_NATIVE: { xmlDocPtr xml = xmlParseMemory ((const char*) data->data, data->length); xmlNodePtr node = xml->children; if ((strcmp((char*)node->name, "chemistry")) || (node->children->next)) { xmlFreeDoc (xml); return false; } node = node->children; if (!strcmp ((char*) node->name, "text")) { gcp_pango_layout_replace_text (layout, start, end - start, "", NULL); text->LoadSelection (node, start); xmlFreeDoc (xml); return true; // otherwise, we'd call OnChange(true) twice. } else if (!strcmp((char*)node->name, "fragment")) { gcpFragment* fragment = new gcpFragment (); gcpDocument *pDoc = m_pView->GetDoc (); gcpTheme *pTheme = pDoc->GetTheme (); pDoc->AddChild (fragment); fragment->Load (node); string buf = fragment->GetBuffer (); PangoAttrList *l = pango_attr_list_new (); PangoAttribute *attr = pango_attr_family_new (pTheme->GetFontFamily ()); pango_attr_list_insert (l, attr); attr = pango_attr_size_new (pTheme->GetFontSize ()); pango_attr_list_insert (l, attr); gcp_pango_layout_replace_text (layout, start, end - start, buf.c_str (), l); pango_attr_list_unref (l); l = fragment->GetAttrList (); struct FragState s; s.l = pango_layout_get_attributes (layout); s.start = start; pango_attr_list_filter (l, (PangoAttrFilterFunc) filter_fragment, &s); delete fragment; start += buf.length (); gnome_canvas_pango_set_selection_bounds (m_Active, start, start); } else { xmlFreeDoc (xml); return false; } xmlFreeDoc (xml); break; } case GCP_CLIPBOARD_UTF8_STRING: { PangoAttrList *l = pango_attr_list_new (); gcp_pango_layout_replace_text (layout, start, end - start, (char const *) data->data, l); pango_attr_list_unref (l); break; } case GCP_CLIPBOARD_STRING: { PangoAttrList *l = pango_attr_list_new (); if (!g_utf8_validate ((const char*) data->data, data->length, NULL)) { gsize r, w; gchar* newstr = g_locale_to_utf8 ((const char*) data->data, data->length, &r, &w, NULL); gcp_pango_layout_replace_text (layout, start, end - start, (char const *) data->data, l); g_free (newstr); } else gcp_pango_layout_replace_text (layout, start, end - start, (char const *) data->data, l); pango_attr_list_unref (l); break; } } text->OnChanged (true); return true; } bool gcpTextTool::OnUndo () { if (m_UndoList.empty ()) { if (m_pView->GetDoc()->CanUndo()) { if (!m_RedoList.empty()) { if (m_CurNode) xmlFree(m_CurNode); m_CurNode = m_RedoList.back(); m_RedoList.pop_back(); } m_bUndo = false; Unselect(); } return false; } xmlNodePtr node = m_UndoList.front(); gcpTextObject *text = (gcpTextObject*) g_object_get_data (G_OBJECT (m_Active), "object"); text->LoadSelected (node); m_UndoList.pop_front (); gcpDocument *pDoc = m_pView->GetDoc (); gcpWindow *pWin = pDoc->GetWindow (); if (m_UndoList.empty() && !pDoc->CanUndo ()) pWin->ActivateActionWidget ("/MainMenu/EditMenu/Undo", false); m_RedoList.push_front(m_CurNode); pWin->ActivateActionWidget ("/MainMenu/EditMenu/Redo", true); unsigned start, end; char* tmp = (char*) xmlGetProp (node, (xmlChar*) "start-sel"); start = (int) strtoul (tmp, NULL, 10); xmlFree (tmp); tmp = (char*) xmlGetProp (node, (xmlChar*) "end-sel"); end = (int) strtoul (tmp, NULL, 10); xmlFree (tmp); gnome_canvas_pango_set_selection_bounds (m_Active, start, end); m_CurNode = node; return true; } bool gcpTextTool::OnRedo () { if (m_RedoList.empty ()) return false; xmlNodePtr node = m_RedoList.front (); gcpTextObject *text = (gcpTextObject*) g_object_get_data (G_OBJECT (m_Active), "object"); text->LoadSelected (node); m_RedoList.pop_front (); gcpWindow *pWin = m_pView->GetDoc ()->GetWindow (); if (m_RedoList.empty ()) pWin->ActivateActionWidget ("/MainMenu/EditMenu/Redo", false); m_UndoList.push_front (m_CurNode); pWin->ActivateActionWidget ("/MainMenu/EditMenu/Undo", true); unsigned start, end; char* tmp = (char*) xmlGetProp (node, (xmlChar*) "start-sel"); start = (int) strtoul (tmp, NULL, 10); xmlFree (tmp); tmp = (char*) xmlGetProp (node, (xmlChar*) "end-sel"); end = (int) strtoul (tmp, NULL, 10); xmlFree (tmp); gnome_canvas_pango_set_selection_bounds (m_Active, start, end); m_CurNode = node; return true; } void gcpTextTool::PushNode (xmlNodePtr node) { gcpWindow *pWin = m_pView->GetDoc ()->GetWindow (); while (!m_RedoList.empty ()) { xmlFree (m_RedoList.front ()); m_RedoList.pop_front (); pWin->ActivateActionWidget ("/MainMenu/EditMenu/Redo", false); } m_UndoList.push_front (m_CurNode); m_CurNode = node; pWin->ActivateActionWidget ("/MainMenu/EditMenu/Undo", true); } void gcpTextTool::OnGetData (GtkClipboard *clipboard, GtkSelectionData *selection_data, guint info) { xmlDocPtr pDoc = gcpWidgetData::GetXmlDoc (clipboard); guint *DataType = (clipboard == gtk_clipboard_get (GDK_SELECTION_CLIPBOARD))? &ClipboardDataType: &ClipboardDataType1; if (ClipboardData) { xmlFree (ClipboardData); } *DataType = info; gint size; if (info) { gcpText *text = new gcpText (); text->Load (pDoc->children->children); PangoLayout *layout = text->GetLayout (); ClipboardData = xmlStrdup ((xmlChar*) pango_layout_get_text (layout)); delete text; size = strlen ((char*) ClipboardData); gtk_selection_data_set_text (selection_data, (const gchar*) ClipboardData, size); } else { xmlDocDumpFormatMemory (pDoc, &ClipboardData, &size, info); gtk_selection_data_set (selection_data, gdk_atom_intern (GCHEMPAINT_ATOM_NAME, FALSE), 8, (const guchar*) ClipboardData, size); } cleared = false; if (clipboard == gtk_clipboard_get (GDK_SELECTION_CLIPBOARD)) m_pView->GetDoc ()->GetWindow ()->ActivateActionWidget ("/MainMenu/EditMenu/Paste", true); } void gcpTextTool::UpdateAttributeList () { if (!m_FamilyList) return; gcpTheme *pTheme = m_pApp->GetActiveDocument ()->GetTheme (); m_FamilyName = pTheme->GetTextFontFamily (); m_Style = pTheme->GetFontStyle (); m_Weight = pTheme->GetFontWeight (); m_Stretch = pTheme->GetFontStretch (); m_Variant = pTheme->GetFontVariant (); m_Size = pTheme->GetFontSize (); m_Rise = 0; m_Underline = PANGO_UNDERLINE_NONE; m_Strikethrough = false; m_Color = RGBA_BLACK; if (m_Active) { PangoLayout *layout; g_object_get (m_Active, "layout", &layout, NULL); PangoAttrList *l = pango_layout_get_attributes (layout); if (l) pango_attr_list_filter (l, (PangoAttrFilterFunc) filter_attribute, this); } // select the found face GtkTreeIter iter; char const *buf; gtk_tree_model_get_iter_first (GTK_TREE_MODEL (m_FamilyList), &iter); do { gtk_tree_model_get (GTK_TREE_MODEL (m_FamilyList), &iter, 0, &buf, -1); if (!strcmp (m_FamilyName, buf)) { m_Dirty = true; GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (m_FamilyList), &iter); gtk_tree_view_set_cursor (m_FamilyTree, path, NULL, FALSE); gtk_tree_path_free (path); break; } } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (m_FamilyList), &iter)); // Update other widgets SetSizeFull (true); g_signal_handler_block (m_UnderlineBox, m_UnderlineSignal); gtk_combo_box_set_active (m_UnderlineBox, m_Underline); g_signal_handler_unblock (m_UnderlineBox, m_UnderlineSignal); g_signal_handler_block (m_StrikethroughBtn, m_StrikethroughSignal); gtk_toggle_button_set_active (m_StrikethroughBtn, m_Strikethrough); g_signal_handler_unblock (m_StrikethroughBtn, m_StrikethroughSignal); g_signal_handler_block (m_RiseButton, m_RiseSignal); gtk_spin_button_set_value (m_RiseButton, m_Rise / PANGO_SCALE); g_signal_handler_unblock (m_RiseButton, m_RiseSignal); g_signal_handler_block (m_ColorSelector, m_ForeSignal); go_color_selector_set_color (m_ColorSelector, m_Color); g_signal_handler_unblock (m_ColorSelector, m_ForeSignal); BuildAttributeList (); } void gcpTextTool::BuildAttributeList () { if (!m_Active) return; PangoAttrList *l = pango_attr_list_new (); pango_attr_list_insert (l, pango_attr_family_new (m_FamilyName)); pango_attr_list_insert (l, pango_attr_style_new (m_Style)); pango_attr_list_insert (l, pango_attr_weight_new (m_Weight)); pango_attr_list_insert (l, pango_attr_stretch_new (m_Stretch)); pango_attr_list_insert (l, pango_attr_variant_new (m_Variant)); pango_attr_list_insert (l, pango_attr_size_new (m_Size)); pango_attr_list_insert (l, pango_attr_underline_new (m_Underline)); pango_attr_list_insert (l, pango_attr_strikethrough_new (m_Strikethrough)); pango_attr_list_insert (l, pango_attr_rise_new (m_Rise)); pango_attr_list_insert (l, pango_attr_foreground_new (UINT_RGBA_R (m_Color) * 0x101, UINT_RGBA_G (m_Color) * 0x101, UINT_RGBA_B (m_Color) * 0x101)); gnome_canvas_pango_set_insert_attrs (m_Active, l); m_Dirty = false; if (m_pView) gtk_window_present (m_pView->GetDoc ()->GetWindow ()->GetWindow ()); } static void on_select_family (GtkTreeSelection *selection, gcpTextTool *tool) { tool->OnSelectFamily (selection); } static void on_select_face (GtkTreeSelection *selection, gcpTextTool *tool) { tool->OnSelectFace (selection); } static void on_select_size (GtkTreeSelection *selection, gcpTextTool *tool) { int size; GtkTreeModel *model; GtkTreeIter iter; gtk_tree_selection_get_selected (selection, &model, &iter); gtk_tree_model_get (model, &iter, 0, &size, -1); tool->OnSelectSize (size * PANGO_SCALE); } static void on_size_activate (GtkEntry *entry, gcpTextTool *tool) { tool->OnSizeChanged (); } static void on_size_focus_out (GtkEntry *entry, GdkEventFocus *event, gcpTextTool *tool) { tool->OnSizeChanged (); } static void on_underline_changed (GtkComboBox *box, gcpTextTool *tool) { tool->OnUnderlineChanged (gtk_combo_box_get_active (box)); } static void on_strikethrough_toggled (GtkToggleButton *btn, gcpTextTool *tool) { tool->OnStriketroughToggled (gtk_toggle_button_get_active (btn)); } static void on_fore_color_changed (GOSelector *sel, gcpTextTool *tool) { gboolean auto_color; tool->OnForeColorChanged (go_color_selector_get_color (sel, &auto_color)); } static void on_rise_changed (GtkSpinButton *btn, gcpTextTool *tool) { tool->OnPositionChanged (gtk_spin_button_get_value_as_int (btn)); } /* These are what we use as the standard font sizes, for the size list. */ static const guint16 font_sizes[] = { 8, 9, 10, 11, 12, 13, 14, 16, 18, 20, 22, 24, 26, 28, 32, 36, 40, 48, 56, 64, 72 }; GtkWidget *gcpTextTool::GetPropertyPage () { GladeXML *xml = glade_xml_new (GLADEDIR"/fontsel.glade", "fontsel", GETTEXT_PACKAGE); PangoFontFamily **families; int i, nb; gcpTheme *pTheme = m_pApp->GetActiveDocument ()->GetTheme (); // Initialize faces list m_FaceList = gtk_list_store_new (1, G_TYPE_STRING); gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (m_FaceList), 0, GTK_SORT_ASCENDING); m_FacesTree = (GtkTreeView *) glade_xml_get_widget (xml, "style"); gtk_tree_view_set_model (m_FacesTree, GTK_TREE_MODEL (m_FaceList)); GtkCellRenderer *renderer = gtk_cell_renderer_text_new (); GtkTreeViewColumn *column = gtk_tree_view_column_new_with_attributes (NULL, renderer, "text", 0, NULL); gtk_tree_view_append_column (m_FacesTree, column); GtkTreeSelection *selection = gtk_tree_view_get_selection (m_FacesTree); m_FaceSel = selection; m_FaceSignal = g_signal_connect (m_FaceSel, "changed", G_CALLBACK (on_select_face), this); // Initialize sizes list m_SizeList = gtk_list_store_new (1, G_TYPE_INT); m_SizesTree = (GtkTreeView *) glade_xml_get_widget (xml, "size-list"); gtk_tree_view_set_model (m_SizesTree, GTK_TREE_MODEL (m_SizeList)); renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes (NULL, renderer, "text", 0, NULL); gtk_tree_view_append_column (m_SizesTree, column); GtkTreeIter iter, selected; for (i = 0; i < (int) G_N_ELEMENTS (font_sizes); i++) { gtk_list_store_append (m_SizeList, &iter); gtk_list_store_set (m_SizeList, &iter, 0, font_sizes[i], -1); } selection = gtk_tree_view_get_selection (m_SizesTree); m_SizeSel = selection; m_SizeSignal = g_signal_connect (m_SizeSel, "changed", G_CALLBACK (on_select_size), this); // Size entry m_SizeEntry = (GtkEntry*) glade_xml_get_widget (xml, "size-entry"); g_signal_connect (m_SizeEntry, "activate", G_CALLBACK (on_size_activate), this); g_signal_connect_after (m_SizeEntry, "focus_out_event", G_CALLBACK (on_size_focus_out), this); SetSizeFull (true); PangoContext *pc = gtk_widget_get_pango_context (GTK_WIDGET (m_SizeEntry)); pango_context_list_families (pc, &families, &nb); m_FamilyList = gtk_list_store_new (1, G_TYPE_STRING); gtk_tree_sortable_set_sort_column_id (GTK_TREE_SORTABLE (m_FamilyList), 0, GTK_SORT_ASCENDING); m_FamilyTree = (GtkTreeView *) glade_xml_get_widget (xml, "family"); gtk_tree_view_set_model (m_FamilyTree, GTK_TREE_MODEL (m_FamilyList)); renderer = gtk_cell_renderer_text_new (); column = gtk_tree_view_column_new_with_attributes (NULL, renderer, "text", 0, NULL); gtk_tree_view_append_column (m_FamilyTree, column); GtkTreePath *path = NULL; string name; bool default_found = false; for (i = 0; i < nb; i++) { PangoFontFace **faces; int *sizes, n; pango_font_family_list_faces (families[i], &faces, &n); if (n <= 0) continue; pango_font_face_list_sizes (faces[0], &sizes, &n); if (n > 0) // Do not use bitmap fonts continue; name = pango_font_family_get_name (families[i]); m_Families[name] = (PangoFontFamily*) g_object_ref (families[i]); gtk_list_store_append (m_FamilyList, &iter); gtk_list_store_set (m_FamilyList, &iter, 0, name.c_str (), -1); if (name == pTheme->GetTextFontFamily ()) { selected = iter; default_found = true; } } if (!default_found) { if (!gtk_tree_model_get_iter_first (GTK_TREE_MODEL (m_FamilyList), &iter)) return NULL; selected = iter; } path = gtk_tree_model_get_path (GTK_TREE_MODEL (m_FamilyList), &selected); selection = gtk_tree_view_get_selection (m_FamilyTree); m_FamilySel = selection; gtk_tree_selection_set_mode (selection, GTK_SELECTION_BROWSE); m_FamilySignal = g_signal_connect (G_OBJECT (selection), "changed", G_CALLBACK (on_select_family), this); // Select default font if (path) { gtk_tree_selection_select_path (selection, path); gtk_tree_view_scroll_to_cell (m_FamilyTree, path, column, FALSE, 0., 0.); gtk_tree_path_free (path); } m_UnderlineBox = GTK_COMBO_BOX (glade_xml_get_widget (xml, "underline")); gtk_combo_box_set_active (m_UnderlineBox, 0); m_UnderlineSignal = g_signal_connect (G_OBJECT (m_UnderlineBox), "changed", G_CALLBACK (on_underline_changed), this); m_StrikethroughBtn = GTK_TOGGLE_BUTTON (glade_xml_get_widget (xml, "strikethrough")); m_StrikethroughSignal = g_signal_connect (G_OBJECT (m_StrikethroughBtn), "toggled", G_CALLBACK (on_strikethrough_toggled), this); m_RiseButton = GTK_SPIN_BUTTON (glade_xml_get_widget (xml, "rise")); m_RiseSignal = g_signal_connect (G_OBJECT (m_RiseButton), "value-changed", G_CALLBACK (on_rise_changed), this); m_ColorSelector = GO_SELECTOR (go_color_selector_new (RGBA_BLACK, RGBA_BLACK, "fore")); go_color_selector_set_allow_alpha (m_ColorSelector, false); m_ForeSignal = g_signal_connect (G_OBJECT (m_ColorSelector), "activate", G_CALLBACK (on_fore_color_changed), this); gtk_widget_show (GTK_WIDGET (m_ColorSelector)); gtk_table_attach (GTK_TABLE (glade_xml_get_widget (xml, "table2")), GTK_WIDGET (m_ColorSelector), 1, 2, 0, 1, (GtkAttachOptions) 0, (GtkAttachOptions) 0, 0, 0); return glade_xml_get_widget (xml, "fontsel"); } void gcpTextTool::OnSelectFamily (GtkTreeSelection *selection) { GtkTreeModel *model; GtkTreeIter iter, selected; char const *name; if (!gtk_tree_selection_get_selected (selection, &model, &iter)) return; gtk_tree_model_get (model, &iter, 0, &m_FamilyName, -1); PangoFontFamily *family = m_Families[m_FamilyName]; PangoFontFace **faces; int i, besti, nb; g_signal_handler_block (m_FaceSel, m_FaceSignal); pango_font_family_list_faces (family, &faces, &nb); gtk_list_store_clear (m_FaceList); map::iterator j, jend = m_Faces.end (); for (j = m_Faces.begin (); j != jend; j++) { g_object_unref ((*j).second); } m_Faces.clear (); PangoFontDescription *desc; int distance, best; PangoStyle Style; PangoWeight Weight; PangoVariant Variant; PangoStretch Stretch; best = 32000; // This should be enough for (i = 0; i < nb; i++) { name = pango_font_face_get_face_name (faces[i]); desc = pango_font_face_describe (faces[i]); m_Faces[name] = (PangoFontFace*) g_object_ref (faces[i]); gtk_list_store_append (m_FaceList, &iter); gtk_list_store_set (m_FaceList, &iter, 0, name, -1); // Try to select the best available face Style = pango_font_description_get_style (desc); Weight = pango_font_description_get_weight (desc); Variant = pango_font_description_get_variant (desc); Stretch = pango_font_description_get_stretch (desc); distance = abs (Weight - m_Weight) + abs ((Style? Style + 2: 0) - (m_Style? m_Style + 2: 0)) * 1000 + abs (Variant - m_Variant) * 10 + abs (Stretch - m_Stretch); if (distance < best) { best = distance; selected = iter; besti = i; } // TODO: write this code pango_font_description_free (desc); } g_signal_handler_unblock (m_FaceSel, m_FaceSignal); GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (m_FaceList), &selected); if (path) { gtk_tree_selection_select_path (GTK_TREE_SELECTION (m_FaceSel), path); gtk_tree_path_free (path); } else { //TODO: choose a face when default is not available } if (m_Active) { PangoAttrList *l = pango_attr_list_new (); pango_attr_list_insert (l, pango_attr_family_new (m_FamilyName)); pango_attr_list_insert (l, pango_attr_style_new (m_Style)); pango_attr_list_insert (l, pango_attr_weight_new (m_Weight)); pango_attr_list_insert (l, pango_attr_stretch_new (m_Stretch)); pango_attr_list_insert (l, pango_attr_variant_new (m_Variant)); gnome_canvas_pango_apply_attrs_to_selection (m_Active, l); pango_attr_list_unref (l); } } void gcpTextTool::OnSelectFace (GtkTreeSelection *selection) { GtkTreeModel *model; GtkTreeIter iter; char const *name; if (!gtk_tree_selection_get_selected (selection, &model, &iter)) return; gtk_tree_model_get (model, &iter, 0, &name, -1); PangoFontFace *face = m_Faces[name]; PangoFontDescription *desc = pango_font_face_describe (face); m_Style = pango_font_description_get_style (desc); m_Weight = pango_font_description_get_weight (desc); m_Variant = pango_font_description_get_variant (desc); m_Stretch = pango_font_description_get_stretch (desc); pango_font_description_free (desc); BuildAttributeList (); if (m_Active) { PangoAttrList *l = pango_attr_list_new (); pango_attr_list_insert (l, pango_attr_style_new (m_Style)); pango_attr_list_insert (l, pango_attr_weight_new (m_Weight)); pango_attr_list_insert (l, pango_attr_stretch_new (m_Stretch)); pango_attr_list_insert (l, pango_attr_variant_new (m_Variant)); gnome_canvas_pango_apply_attrs_to_selection (m_Active, l); pango_attr_list_unref (l); } } void gcpTextTool::OnSelectSize (int size) { m_Size = size; SetSizeFull (false); } void gcpTextTool::OnSizeChanged () { char const *text = gtk_entry_get_text (m_SizeEntry); m_Size = (int) (MAX (0.1, atof (text) * PANGO_SCALE + 0.5)); SetSizeFull (true); } void gcpTextTool::SetSizeFull (bool update_list) { char *buf = g_strdup_printf ("%.1f", (double) m_Size / PANGO_SCALE); gtk_entry_set_text (m_SizeEntry, buf); g_free (buf); if (update_list) { GtkTreeIter iter; bool found = false; GtkTreeSelection *selection = gtk_tree_view_get_selection (m_SizesTree); g_signal_handler_block (selection, m_SizeSignal); gtk_tree_model_get_iter_first (GTK_TREE_MODEL (m_SizeList), &iter); for (unsigned i = 0; i < G_N_ELEMENTS (font_sizes) && !found; i++) { if (font_sizes[i] * PANGO_SCALE == m_Size) { GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (m_SizeList), &iter); gtk_tree_view_set_cursor (m_SizesTree, path, NULL, FALSE); gtk_tree_path_free (path); found = true; } gtk_tree_model_iter_next (GTK_TREE_MODEL (m_SizeList), &iter); } if (!found) gtk_tree_selection_unselect_all (selection); g_signal_handler_unblock (selection, m_SizeSignal); } BuildAttributeList (); if (m_Active) { PangoAttrList *l = pango_attr_list_new (); pango_attr_list_insert (l, pango_attr_size_new (m_Size)); gnome_canvas_pango_apply_attrs_to_selection (m_Active, l); pango_attr_list_unref (l); } } void gcpTextTool::SelectBestFontFace () { PangoFontDescription *desc; int distance, best; PangoStyle Style; PangoWeight Weight; PangoVariant Variant; PangoStretch Stretch; map ::iterator i, iend = m_Faces.end (); char const *name = NULL, *buf; GtkTreeIter iter; best = 32000; // This should be enough for (i = m_Faces.begin (); i != iend; i++) { desc = pango_font_face_describe ((*i).second); // Try to select the best available face Style = pango_font_description_get_style (desc); Weight = pango_font_description_get_weight (desc); Variant = pango_font_description_get_variant (desc); Stretch = pango_font_description_get_stretch (desc); distance = abs (Weight - m_Weight) + abs ((Style? Style + 2: 0) - (m_Style? m_Style + 2: 0)) * 1000 + abs (Variant - m_Variant) * 10 + abs (Stretch - m_Stretch); if (distance < best) { best = distance; name = (*i).first.c_str (); } pango_font_description_free (desc); } // select the found face gtk_tree_model_get_iter_first (GTK_TREE_MODEL (m_FaceList), &iter); do { gtk_tree_model_get (GTK_TREE_MODEL (m_FaceList), &iter, 0, &buf, -1); if (!strcmp (name, buf)) { m_Dirty = true; GtkTreePath *path = gtk_tree_model_get_path (GTK_TREE_MODEL (m_FaceList), &iter); gtk_tree_view_set_cursor (m_FacesTree, path, NULL, FALSE); gtk_tree_path_free (path); if (m_Dirty) OnSelectFace ((GtkTreeSelection*) m_FaceSel); break; } } while (gtk_tree_model_iter_next (GTK_TREE_MODEL (m_FaceList), &iter)); } int gcpTextTool::GetIndex () { return gnome_canvas_pango_get_cur_index (m_Active); } void gcpTextTool::OnUnderlineChanged (unsigned underline) { m_Underline = (PangoUnderline) underline; BuildAttributeList (); if (m_Active) { PangoAttrList *l = pango_attr_list_new (); pango_attr_list_insert (l, pango_attr_underline_new (m_Underline)); gnome_canvas_pango_apply_attrs_to_selection (m_Active, l); pango_attr_list_unref (l); } } void gcpTextTool::OnStriketroughToggled (bool strikethrough) { m_Strikethrough = strikethrough; BuildAttributeList (); if (m_Active) { PangoAttrList *l = pango_attr_list_new (); pango_attr_list_insert (l, pango_attr_strikethrough_new (m_Strikethrough)); gnome_canvas_pango_apply_attrs_to_selection (m_Active, l); pango_attr_list_unref (l); } } void gcpTextTool::OnPositionChanged (int position) { m_Rise = position * PANGO_SCALE; BuildAttributeList (); } void gcpTextTool::OnForeColorChanged (GOColor color) { m_Color = color; BuildAttributeList (); if (m_Active) { PangoAttrList *l = pango_attr_list_new (); pango_attr_list_insert (l, pango_attr_foreground_new (UINT_RGBA_R (m_Color) * 0x101, UINT_RGBA_G (m_Color) * 0x101, UINT_RGBA_B (m_Color) * 0x101)); gnome_canvas_pango_apply_attrs_to_selection (m_Active, l); pango_attr_list_unref (l); } }