/* -*- mode: C++; tab-width: 4; c-basic-offset: 4; -*- */ /* AbiWord * Copyright (C) 1998-2000 AbiSource, Inc. * * 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. */ #include #include #include #include #include "ap_Features.h" #include "ut_assert.h" #include "ut_string.h" #include "ut_growbuf.h" #include "ut_debugmsg.h" #include "ut_units.h" #include "xap_App.h" #include "xap_Dialog_Id.h" #include "xap_DialogFactory.h" #include "xap_Prefs.h" #include "ap_Strings.h" #include "fv_View.h" #include "fl_DocLayout.h" #include "fl_BlockLayout.h" #include "fp_PageSize.h" #include "ap_Preview_Paragraph.h" #include "ap_Dialog_Paragraph.h" #include "ap_Prefs_SchemeIds.h" #include "pp_Property.h" AP_Dialog_Paragraph::AP_Dialog_Paragraph(XAP_DialogFactory* pDlgFactory, XAP_Dialog_Id id) : XAP_Dialog_NonPersistent(pDlgFactory,id, "interface/dialogparagraph") { m_answer = a_OK; m_paragraphPreview = NULL; m_pFrame = NULL; // determine unit system to use in this dialog const gchar * szRulerUnits; UT_return_if_fail (m_pApp); XAP_Prefs* pPrefs = m_pApp->getPrefs(); UT_return_if_fail (pPrefs); const bool bHasRulerUnits = pPrefs->getPrefsValue((gchar*)AP_PREF_KEY_RulerUnits, &szRulerUnits); m_dim = bHasRulerUnits ? UT_determineDimension(szRulerUnits) : DIM_IN; m_pageLeftMargin = NULL; m_pageRightMargin = NULL; _addPropertyItem (id_MENU_ALIGNMENT, sControlData(align_UNDEF)); _addPropertyItem (id_SPIN_LEFT_INDENT, sControlData()); _addPropertyItem (id_SPIN_RIGHT_INDENT, sControlData()); _addPropertyItem (id_MENU_SPECIAL_INDENT, sControlData(indent_UNDEF)); _addPropertyItem (id_SPIN_SPECIAL_INDENT, sControlData()); _addPropertyItem (id_SPIN_BEFORE_SPACING, sControlData()); _addPropertyItem (id_SPIN_AFTER_SPACING, sControlData()); _addPropertyItem (id_MENU_SPECIAL_SPACING, sControlData(spacing_UNDEF)); _addPropertyItem (id_SPIN_SPECIAL_SPACING, sControlData()); _addPropertyItem (id_CHECK_WIDOW_ORPHAN, sControlData(check_INDETERMINATE)); _addPropertyItem (id_CHECK_KEEP_LINES, sControlData(check_INDETERMINATE)); _addPropertyItem (id_CHECK_PAGE_BREAK, sControlData(check_INDETERMINATE)); _addPropertyItem (id_CHECK_SUPPRESS, sControlData(check_INDETERMINATE)); _addPropertyItem (id_CHECK_NO_HYPHENATE, sControlData(check_INDETERMINATE)); _addPropertyItem (id_CHECK_KEEP_NEXT, sControlData(check_INDETERMINATE)); _addPropertyItem (id_CHECK_DOMDIRECTION, sControlData(check_INDETERMINATE)); } AP_Dialog_Paragraph::~AP_Dialog_Paragraph(void) { FREEP(m_pageLeftMargin); FREEP(m_pageRightMargin); DELETEP(m_paragraphPreview); UT_VECTOR_PURGEALL(sControlData *, m_vecProperties); } bool AP_Dialog_Paragraph::setDialogData(const gchar ** pProps) { UT_return_val_if_fail (pProps, false); // NOTICE : When setting values, this function always calls // NOTICE : _set[thing]ItemValue() with the bToggleDirty flag // NOTICE : set to false, because these are the "un-dirty" // NOTICE : values. if (pProps[0]) { const gchar * sz; sz = UT_getAttribute("text-align", pProps); if (sz) { tAlignState t = align_LEFT; if (strcmp(sz, "center") == 0) t = align_CENTERED; else if (strcmp(sz, "right") == 0) t = align_RIGHT; else if (strcmp(sz, "justify") == 0) t = align_JUSTIFIED; else if (strcmp(sz, "left") == 0) t = align_LEFT; else UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); _setMenuItemValue(id_MENU_ALIGNMENT, t, op_INIT); } sz = UT_getAttribute("dom-dir", pProps); if (sz) { tCheckState t = check_FALSE; if (strcmp(sz, "ltr") == 0) t = check_FALSE; else if (strcmp(sz, "rtl") == 0) t = check_TRUE; else UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); _setCheckItemValue(id_CHECK_DOMDIRECTION, t, op_INIT); } sz = UT_getAttribute("margin-left", pProps); if (sz) _setSpinItemValue(id_SPIN_LEFT_INDENT, sz, op_INIT); sz = UT_getAttribute("margin-right", pProps); if (sz) _setSpinItemValue(id_SPIN_RIGHT_INDENT, sz, op_INIT); sz = UT_getAttribute("text-indent", pProps); if (sz) { // NOTE : Calling UT_convertDimensionless() _discards_ all // NOTE : unit system information. IFF all units are // NOTE : consistent among all paragraph properties will // NOTE : the comparisons be valid. For now this should be // NOTE : valid. if (UT_convertDimensionless(sz) > (double) 0) { // if text-indent is greater than margin-left, we have a "first line" case _setMenuItemValue(id_MENU_SPECIAL_INDENT, indent_FIRSTLINE, op_INIT); } else if (UT_convertDimensionless(sz) < (double) 0) { // if text-indent is less than margin-left, we have a "hanging" case _setMenuItemValue(id_MENU_SPECIAL_INDENT, indent_HANGING, op_INIT); } else { // they're equal then there's nothing special about them _setMenuItemValue(id_MENU_SPECIAL_INDENT, indent_NONE, op_INIT); } // set the value regardless; dialog will enable/disable field // if spacing is "NONE". Must flip the sign (strip minus) // to give illusion of Word's definitions of indent/margin. const gchar * newSz = sz; if (sz[0] == '-') newSz++; _setSpinItemValue(id_SPIN_SPECIAL_INDENT, newSz, op_INIT); } sz = UT_getAttribute("line-height", pProps); if (sz) { UT_uint32 nLen = strlen(sz); if (nLen > 0) { const char * pPlusFound = strrchr(sz, '+'); if (pPlusFound && *(pPlusFound + 1) == 0) { _setMenuItemValue(id_MENU_SPECIAL_SPACING, spacing_ATLEAST, op_INIT); // need to strip off that plus int posPlus = pPlusFound - (char*) sz; UT_return_val_if_fail (posPlus>=0, false); UT_return_val_if_fail (posPlus<100, false); char pTmp[100]; strcpy(pTmp, sz); pTmp[posPlus] = 0; _setSpinItemValue(id_SPIN_SPECIAL_SPACING, (gchar*)pTmp, op_INIT); } else { if(UT_hasDimensionComponent(sz)) _setMenuItemValue(id_MENU_SPECIAL_SPACING, spacing_EXACTLY, op_INIT); //see Bug 10086 for fabs() usage else if((strcmp("1.0", sz) == 0) || (fabs(UT_convertDimensionless(sz) - (double) 1.0) < 1.0e-7)) _setMenuItemValue(id_MENU_SPECIAL_SPACING, spacing_SINGLE, op_INIT); else if((strcmp("1.5", sz) == 0) || (fabs(UT_convertDimensionless(sz) - (double) 1.5) < 1.0e-7)) _setMenuItemValue(id_MENU_SPECIAL_SPACING, spacing_ONEANDHALF, op_INIT); else if((strcmp("2.0", sz) == 0) || (fabs(UT_convertDimensionless(sz) - (double) 2.0) < 1.0e-7)) _setMenuItemValue(id_MENU_SPECIAL_SPACING, spacing_DOUBLE, op_INIT); else _setMenuItemValue(id_MENU_SPECIAL_SPACING, spacing_MULTIPLE, op_INIT); // set the spin contents regardless of menu content; platforms will // enable or disable the spin item for varying states of menu _setSpinItemValue(id_SPIN_SPECIAL_SPACING, sz, op_INIT); } } } sz = UT_getAttribute("margin-top", pProps); if (sz) _setSpinItemValue(id_SPIN_BEFORE_SPACING, sz, op_INIT); sz = UT_getAttribute("margin-bottom", pProps); if (sz) _setSpinItemValue(id_SPIN_AFTER_SPACING, sz, op_INIT); { // NOTE : "orphans" and "widows" hold a number specifying the number // NOTE : of lines to consider an orphaned or widowed piece of text. // NOTE : If they're both 0 they're off. If either is greater than // NOTE : 0, then some form of control is in effect. If the property // NOTE : is not set, they're indeterminate (e.g. because we're setting // NOTE : properties for text with multiple "orphans" values), or because // NOTE : the block has no value for orphans/widows here. bool bNoOrphans = false; bool bNoWidows = false; double orphans = 0, widows = 0; sz = UT_getAttribute("orphans", pProps); if (sz) orphans = UT_convertDimensionless(sz); else bNoOrphans = true; sz = UT_getAttribute("widows", pProps); if (sz) widows = UT_convertDimensionless(sz); else bNoWidows = true; if (bNoOrphans && bNoWidows) _setCheckItemValue(id_CHECK_WIDOW_ORPHAN, check_INDETERMINATE, op_INIT); else if (orphans > 0 || widows > 0) _setCheckItemValue(id_CHECK_WIDOW_ORPHAN, check_TRUE, op_INIT); else _setCheckItemValue(id_CHECK_WIDOW_ORPHAN, check_FALSE, op_INIT); } sz = UT_getAttribute("keep-together", pProps); if (sz) { if (strcmp(sz, "yes") == 0) _setCheckItemValue(id_CHECK_KEEP_LINES, check_TRUE, op_INIT); else _setCheckItemValue(id_CHECK_KEEP_LINES, check_FALSE, op_INIT); } else _setCheckItemValue(id_CHECK_KEEP_LINES, check_INDETERMINATE, op_INIT); sz = UT_getAttribute("keep-with-next", pProps); if (sz) { if (strcmp(sz, "yes") == 0) _setCheckItemValue(id_CHECK_KEEP_NEXT, check_TRUE, op_INIT); else _setCheckItemValue(id_CHECK_KEEP_NEXT, check_FALSE, op_INIT); } else _setCheckItemValue(id_CHECK_KEEP_NEXT, check_INDETERMINATE, op_INIT); // these are not like the others, they set fields on this, not dialogData. sz = UT_getAttribute("page-margin-left", pProps); if (sz) { m_pageLeftMargin = g_strdup(sz); } else { m_pageLeftMargin = g_strdup(PP_lookupProperty("page-margin-left")->getInitial()); } sz = UT_getAttribute("page-margin-right", pProps); if (sz) { m_pageRightMargin = g_strdup(sz); } else { m_pageRightMargin = g_strdup(PP_lookupProperty("page-margin-right")->getInitial()); } // TODO : add these to PP_Property (pp_Property.cpp) !!! // TODO : and to FV_View::getBlockFormat (or else they won't come in) /* m_pageBreakBefore; m_suppressLineNumbers; m_noHyphenate; */ } return true; } // This function returns a pointer to newly allocated memory, which contains // pointers to newly allocated memory. The caller must g_free both dimensions // of this structure (use the FREEP() macro, we use UT_calloc() to allocate it). // This function does not g_free any memory pointed to by its argument before // writing into it. #define ALLOC_PROP_PAIR(p) \ do { \ p = (propPair *) UT_calloc(1, sizeof(propPair)); \ if (!p) return false; \ } while (0) \ bool AP_Dialog_Paragraph::getDialogData(const gchar **& pProps) { UT_Vector v; struct propPair { gchar * prop; gchar * val; }; propPair * p; // only do this if the control has a proper value if (_wasChanged(id_MENU_ALIGNMENT) && _getMenuItemValue(id_MENU_ALIGNMENT)) { ALLOC_PROP_PAIR(p); p->prop = g_strdup("text-align"); switch (_getMenuItemValue(id_MENU_ALIGNMENT)) { case align_LEFT: p->val = g_strdup("left"); break; case align_CENTERED: p->val = g_strdup("center"); break; case align_RIGHT: p->val = g_strdup("right"); break; case align_JUSTIFIED: p->val = g_strdup("justify"); break; default: UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); } v.addItem(p); } if (_wasChanged(id_CHECK_DOMDIRECTION)) { ALLOC_PROP_PAIR(p); p->prop = g_strdup("dom-dir"); if (_getCheckItemValue(id_CHECK_DOMDIRECTION) == check_TRUE) p->val = g_strdup("rtl"); else p->val = g_strdup("ltr"); v.addItem(p); } if (_wasChanged(id_SPIN_LEFT_INDENT)) { ALLOC_PROP_PAIR(p); p->prop = g_strdup("margin-left"); p->val = g_strdup(_getSpinItemValue(id_SPIN_LEFT_INDENT)); v.addItem(p); } if (_wasChanged(id_SPIN_RIGHT_INDENT)) { ALLOC_PROP_PAIR(p); p->prop = g_strdup("margin-right"); p->val = g_strdup(_getSpinItemValue(id_SPIN_RIGHT_INDENT)); v.addItem(p); } // TODO : The logic here might not be bulletproof. If the user triggers // TODO : a change in the TYPE of special indent (hanging, first line, // TODO : none), we will always save what's in the box as a property. // TODO : One could make it smarter with a stronger contract with // TODO : the platform interfaces. if (_getMenuItemValue(id_MENU_SPECIAL_INDENT) && (_wasChanged(id_MENU_SPECIAL_INDENT) || _wasChanged(id_SPIN_SPECIAL_INDENT))) { ALLOC_PROP_PAIR(p); p->prop = g_strdup("text-indent"); tIndentState i = (tIndentState) _getMenuItemValue(id_MENU_SPECIAL_INDENT); if (i == indent_NONE) p->val = g_strdup(UT_convertInchesToDimensionString(m_dim, 0)); else if (i == indent_FIRSTLINE) p->val = g_strdup(_getSpinItemValue(id_SPIN_SPECIAL_INDENT)); else if (i == indent_HANGING) { // we have to flip the sign for "hanging" indents to a negative quantity for // storage in the document as a text-indent // get with no dimension UT_Dimension dim = UT_determineDimension(_getSpinItemValue(id_SPIN_SPECIAL_INDENT)); double val = UT_convertDimensionless(_getSpinItemValue(id_SPIN_SPECIAL_INDENT)); // Convert to inches val = UT_convertDimToInches(val, dim); // flip sign val = val * (double) -1; // store the reconstructed p->val = g_strdup(UT_convertInchesToDimensionString(dim, val)); } v.addItem(p); } if (_wasChanged(id_SPIN_BEFORE_SPACING)) { ALLOC_PROP_PAIR(p); p->prop = g_strdup("margin-top"); p->val = g_strdup(_getSpinItemValue(id_SPIN_BEFORE_SPACING)); v.addItem(p); } if (_wasChanged(id_SPIN_AFTER_SPACING)) { ALLOC_PROP_PAIR(p); p->prop = g_strdup("margin-bottom"); p->val = g_strdup(_getSpinItemValue(id_SPIN_AFTER_SPACING)); v.addItem(p); } // TODO : The logic here might not be bulletproof. If the user triggers // TODO : a change in the TYPE of special indent (single, double, etc.) // TODO : we will always save what's in the box as a property. // TODO : One could make it smarter with a stronger contract with // TODO : the platform interfaces. if(_getMenuItemValue(id_MENU_SPECIAL_SPACING) && (_wasChanged(id_MENU_SPECIAL_SPACING) || _wasChanged(id_SPIN_SPECIAL_SPACING))) { ALLOC_PROP_PAIR(p); p->prop = g_strdup("line-height"); // normal spacings (single, 1.5, double) are just simple numbers. // "at least" needs a "+" at the end of the number (no units). // "exactly" simply has units. // "multiple" has no units. gchar * pTmp = NULL; const gchar * pString = _getSpinItemValue(id_SPIN_SPECIAL_SPACING); UT_uint32 nSize = 0; switch(_getMenuItemValue(id_MENU_SPECIAL_SPACING)) { case spacing_SINGLE: p->val = g_strdup("1.0"); break; case spacing_ONEANDHALF: p->val = g_strdup("1.5"); break; case spacing_DOUBLE: p->val = g_strdup("2.0"); break; case spacing_ATLEAST: nSize = strlen(pString); pTmp = (gchar *) UT_calloc(nSize + 2, sizeof(gchar)); UT_return_val_if_fail (pTmp, false); strncpy(pTmp, pString, nSize); // stick a '+' at the end pTmp[nSize] = '+'; p->val = g_strdup(pTmp); FREEP(pTmp); break; case spacing_EXACTLY: // fallthrough case spacing_MULTIPLE: // both these cases either do or don't have units associated with them. // the platform dialog code takes care of that. p->val = g_strdup(pString); break; default: UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); } v.addItem(p); } // NOTE : "orphans" and "widows" hold a number specifying the number // NOTE : of lines to consider an orphaned or widowed piece of text. // NOTE : If they're both 0 they're off. If either is greater than // NOTE : 0, then some form of control is in effect. If the property // NOTE : is not set, they're indeterminate. if (_wasChanged(id_CHECK_WIDOW_ORPHAN)) { // TODO : fix this! we stomp on both properties (setting them // TODO : to "2"s or "0"s) if the one check box was ever // TODO : changed { ALLOC_PROP_PAIR(p); p->prop = g_strdup("orphans"); if (_getCheckItemValue(id_CHECK_WIDOW_ORPHAN) == check_TRUE) p->val = g_strdup("2"); else p->val = g_strdup("0"); v.addItem(p); } { ALLOC_PROP_PAIR(p); p->prop = g_strdup("widows"); if (_getCheckItemValue(id_CHECK_WIDOW_ORPHAN) == check_TRUE) p->val = g_strdup("2"); else p->val = g_strdup("0"); v.addItem(p); } } if (_wasChanged(id_CHECK_KEEP_LINES)) { ALLOC_PROP_PAIR(p); p->prop = g_strdup("keep-together"); if (_getCheckItemValue(id_CHECK_KEEP_LINES) == check_TRUE) p->val = g_strdup("yes"); else p->val = g_strdup("no"); v.addItem(p); } if (_wasChanged(id_CHECK_KEEP_NEXT)) { ALLOC_PROP_PAIR(p); p->prop = g_strdup("keep-with-next"); if (_getCheckItemValue(id_CHECK_KEEP_NEXT) == check_TRUE) p->val = g_strdup("yes"); else p->val = g_strdup("no"); v.addItem(p); } // TODO : add these to PP_Property (pp_Property.cpp) !!! /* m_pageBreakBefore; m_suppressLineNumbers; m_noHyphenate; */ // export everything in the array UT_uint32 count = v.getItemCount()*2 + 1; const gchar ** newprops = (const gchar **) UT_calloc(count, sizeof(gchar *)); if (!newprops) return false; const gchar ** newitem = newprops; UT_uint32 i = v.getItemCount(); while (i > 0) { p = (propPair *) v.getNthItem(i-1); i--; newitem[0] = p->prop; newitem[1] = p->val; newitem += 2; } // DO purge the vector's CONTENTS, which are just propPair structs UT_VECTOR_FREEALL(propPair *, v); // DO NOT purge the propPair's CONTENTS, because they will be pointed to // by the pointers we just copied into memory typed (gchar **) pProps = newprops; return true; } AP_Dialog_Paragraph::tAnswer AP_Dialog_Paragraph::getAnswer(void) const { return m_answer; } /************************************************************************/ // how many characters do we want to pull from the current paragraph // to fill our preview #define NUM_CHARS_FOR_SAMPLE 100 void AP_Dialog_Paragraph::_createPreviewFromGC(GR_Graphics * gc, UT_uint32 width, UT_uint32 height) { UT_return_if_fail (gc); // g_free any attached preview DELETEP(m_paragraphPreview); // platform's runModal should have set this UT_return_if_fail (m_pFrame); AV_View * baseview = m_pFrame->getCurrentView(); UT_return_if_fail (baseview); FV_View * view = static_cast (baseview); FL_DocLayout * dl = view->getLayout(); UT_return_if_fail (dl); fl_BlockLayout * bl = dl->findBlockAtPosition((PT_DocPosition) view->getPoint()); UT_return_if_fail (bl); UT_GrowBuf gb; bool hadMem = bl->getBlockBuf(&gb); UT_UCSChar * tmp = NULL; if (hadMem && gb.getLength() > 0) { gb.truncate(NUM_CHARS_FOR_SAMPLE); UT_UCS4_cloneString(&tmp, (UT_UCSChar *) gb.getPointer(0)); } else { const XAP_StringSet * pSS = m_pApp->getStringSet(); // if the paragraph was empty, use our sample UT_UCS4_cloneString_char(&tmp, pSS->getValue(AP_STRING_ID_DLG_Para_PreviewSampleFallback)); } m_paragraphPreview = new AP_Preview_Paragraph(gc, tmp, this); FREEP(tmp); UT_return_if_fail (m_paragraphPreview); m_paragraphPreview->setWindowSize(width, height); // TODO : any setup of the GC for drawing } void AP_Dialog_Paragraph::_setMenuItemValue(tControl item, UT_sint32 value, tOperation op /* = op_UICHANGE */) { UT_return_if_fail (item <= m_vecProperties.getItemCount()); sControlData * pItem = _getPropertyItem (item); UT_return_if_fail (pItem); pItem->setData (value); if ((op == op_UICHANGE) || (op == op_SYNC)) pItem->changed (true); // for UI-driven changes, may need to sync other controls if (op == op_UICHANGE) _syncControls(item); } UT_sint32 AP_Dialog_Paragraph::_getMenuItemValue(tControl item) { UT_return_val_if_fail (item <= m_vecProperties.getItemCount(), 0); sControlData * pItem = _getPropertyItem (item); UT_return_val_if_fail (pItem, 0); UT_sint32 value = 0; pItem->getData (value); return value; } void AP_Dialog_Paragraph::_setCheckItemValue(tControl item, tCheckState value, tOperation op /* = op_UICHANGE */) { UT_return_if_fail (item <= m_vecProperties.getItemCount()); sControlData * pItem = _getPropertyItem (item); UT_return_if_fail (pItem); pItem->setData (value); if ((op == op_UICHANGE) || (op == op_SYNC)) pItem->changed (true); // for UI-driven changes, may need to sync other controls if (op == op_UICHANGE) _syncControls(item); } AP_Dialog_Paragraph::tCheckState AP_Dialog_Paragraph::_getCheckItemValue(tControl item) { UT_return_val_if_fail (item <= m_vecProperties.getItemCount(), check_INDETERMINATE); sControlData * pItem = _getPropertyItem (item); UT_return_val_if_fail (pItem, check_INDETERMINATE); tCheckState value = check_INDETERMINATE; if (pItem) pItem->getData (value); return value; } const gchar * AP_Dialog_Paragraph::_makeAbsolute(const gchar * value) { UT_uint32 i = 0; const gchar * tempstring = value; // from the start of the string, if a character is a space, walk on. // when we hit a '-', we leave the pointer at value + i + 1 while (value[i] && value[i] == ' ') { tempstring++; i++; } // we're at a non-space if (value[i] == '-') tempstring++; return tempstring; } void AP_Dialog_Paragraph::_setSpinItemValue(tControl item, const gchar * value, tOperation op /* = op_UICHANGE */) { UT_return_if_fail (item <= m_vecProperties.getItemCount() && value); sControlData * pItem = _getPropertyItem (item); UT_return_if_fail (pItem); // some spinbuttons have special data requirements switch(item) { case id_SPIN_LEFT_INDENT: case id_SPIN_RIGHT_INDENT: case id_SPIN_SPECIAL_INDENT: pItem->setData (reinterpret_cast(UT_reformatDimensionString (m_dim, value))); break; case id_SPIN_BEFORE_SPACING: case id_SPIN_AFTER_SPACING: { /* NOTE : line spacing can't be negative, so take absolute value: */ const char * abs_value = UT_reformatDimensionString (DIM_PT, _makeAbsolute (value)); pItem->setData (reinterpret_cast(abs_value)); } break; case id_SPIN_SPECIAL_SPACING: if (_getMenuItemValue (id_MENU_SPECIAL_SPACING) == spacing_MULTIPLE) { const char * abs_value = UT_reformatDimensionString (DIM_none, _makeAbsolute (value), ".2"); pItem->setData (reinterpret_cast(abs_value)); } else { const char * abs_value = UT_reformatDimensionString (DIM_PT, _makeAbsolute (value)); pItem->setData (reinterpret_cast(abs_value)); } break; default: /* all others get a simple string copy to the static member */ pItem->setData (value); } if ((op == op_UICHANGE) || (op == op_SYNC)) pItem->changed (true); // for UI-driven changes, may need to sync other controls if (op == op_UICHANGE) _syncControls(item); } const gchar * AP_Dialog_Paragraph::_getSpinItemValue(tControl item) { UT_return_val_if_fail (item <= m_vecProperties.getItemCount(), NULL); sControlData * pItem = _getPropertyItem (item); UT_return_val_if_fail (pItem, NULL); const gchar * value = NULL; pItem->getData (value); UT_ASSERT_HARMLESS(value); return value; } // _doSpin() spins the current value of the edit control (already stored // in member variables) by amt units. this method handles all of the // unit conversions required, updating the member variables as needed. // the results get copied back to the dialog controls in the platform // implementation of _syncControls(). #define SPIN_INCR_IN 0.1 #define SPIN_INCR_CM 0.5 #define SPIN_INCR_PI 6.0 #define SPIN_INCR_PT 1.0 #define SPIN_INCR_none 0.1 void AP_Dialog_Paragraph::_doSpin(tControl edit, UT_sint32 amt) { UT_ASSERT_HARMLESS(amt); // zero makes no sense // get current value from member const gchar* szOld = _getSpinItemValue(edit); double d = UT_convertDimensionless(szOld); // figure out which dimension and units to spin in UT_Dimension dimSpin = m_dim; double dSpinUnit = SPIN_INCR_PT; double dMin = 0.0; bool bMin = false; switch (edit) { case id_SPIN_SPECIAL_INDENT: dMin = 0.0; bMin = true; // fall through case id_SPIN_LEFT_INDENT: case id_SPIN_RIGHT_INDENT: dimSpin = m_dim; switch (dimSpin) { case DIM_IN: dSpinUnit = SPIN_INCR_IN; break; case DIM_CM: dSpinUnit = SPIN_INCR_CM; break; case DIM_PI: dSpinUnit = SPIN_INCR_PI; break; case DIM_PT: dSpinUnit = SPIN_INCR_PT; break; default: UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); break; } break; case id_SPIN_BEFORE_SPACING: case id_SPIN_AFTER_SPACING: dimSpin = DIM_PT; dSpinUnit = 6.0; dMin = 0.0; bMin = true; break; case id_SPIN_SPECIAL_SPACING: switch(_getMenuItemValue(id_MENU_SPECIAL_SPACING)) { case spacing_SINGLE: case spacing_ONEANDHALF: case spacing_DOUBLE: _setMenuItemValue(id_MENU_SPECIAL_SPACING, spacing_MULTIPLE); // fall through case spacing_MULTIPLE: dimSpin = DIM_none; dSpinUnit = 0.1; dMin = 0.5; bMin = true; break; case spacing_EXACTLY: dMin = 1; // fall through case spacing_ATLEAST: dimSpin = DIM_PT; dSpinUnit = SPIN_INCR_PT; bMin = true; break; default: UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); break; } break; default: UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); break; } // figure out spin precision, too const char * szPrecision = ".1"; if ((dimSpin == DIM_PT) || (dimSpin == DIM_PI)) szPrecision = ".0"; // if needed, switch unit systems and round off UT_Dimension dimOld = UT_determineDimension(szOld, dimSpin); if (dimOld != dimSpin) { double dInches = UT_convertToInches(szOld); d = UT_convertInchesToDimension(dInches, dimSpin); } // value is now in desired units, so change it d += (dSpinUnit * amt); if (bMin) { // some spinners clamp at bottom of range if (d < dMin) d = dMin; } const gchar* szNew = UT_formatDimensionString(dimSpin, d, szPrecision); _setSpinItemValue(edit, szNew); } // after the member variable for a control has been changed by the // user, we may need to synchronize the values of other controls. // this happens in two steps via this virtual function. // // step 1 -- the XP code fixes the necessary member variables // step 2 -- the platform code copies them into dialog controls // // to make this work, the *first* step of a platform implementation // must be to call the XP (parent) implementation // // also, the platform code has to call _syncControls() once with // bAll set to true. this should happen *after* all of the // member variables have been copied to the screen for the first // time, but before the dialog is displayed. void AP_Dialog_Paragraph::_syncControls(tControl changed, bool /*bAll = false */) { if(changed == id_SPIN_LEFT_INDENT) { // need to check the limits // cannot go past left page margin. // TODO : is there a minimum text width? double leftPageMargin = UT_convertToDimension(m_pageLeftMargin, m_dim); double rightIndent = UT_convertToDimension(_getSpinItemValue(id_SPIN_RIGHT_INDENT), m_dim); if(-UT_convertToDimension(_getSpinItemValue(id_SPIN_LEFT_INDENT), m_dim) > leftPageMargin) { _setSpinItemValue(id_SPIN_LEFT_INDENT, (const gchar *)UT_formatDimensionString(m_dim, -leftPageMargin), op_SYNC); } // nor past pagesize - rightIndent on right. if(UT_convertDimensionless(_getSpinItemValue(id_SPIN_LEFT_INDENT)) > UT_convertInchesToDimension(m_iMaxWidth, m_dim) - rightIndent) { _setSpinItemValue(id_SPIN_LEFT_INDENT, (const gchar *)UT_convertInchesToDimensionString(m_dim, m_iMaxWidth - rightIndent), op_SYNC); } } if(changed == id_SPIN_RIGHT_INDENT) { // need to check the limits // cannot go past right page margin. double rightPageMargin = UT_convertToDimension(m_pageRightMargin, m_dim); double leftIndent = UT_convertToDimension(_getSpinItemValue(id_SPIN_LEFT_INDENT), m_dim); if(-UT_convertToDimension(_getSpinItemValue(id_SPIN_RIGHT_INDENT), m_dim) > rightPageMargin) { _setSpinItemValue(id_SPIN_RIGHT_INDENT, (const gchar *)UT_formatDimensionString(m_dim, -rightPageMargin), op_SYNC); } // nor can we force text left past pagesize, minus left margin if(UT_convertDimensionless(_getSpinItemValue(id_SPIN_RIGHT_INDENT)) > UT_convertInchesToDimension(m_iMaxWidth, m_dim) - leftIndent) { _setSpinItemValue(id_SPIN_RIGHT_INDENT, (const gchar *)UT_convertInchesToDimensionString(m_dim, m_iMaxWidth - leftIndent), op_SYNC); } } if (changed == id_MENU_SPECIAL_INDENT || changed == id_SPIN_SPECIAL_INDENT) { double dDefault = 0.0; bool bDefault = true; double sign = -1.0; if (_getMenuItemValue(id_MENU_SPECIAL_INDENT) == indent_FIRSTLINE) sign = +1.0; if (changed == id_MENU_SPECIAL_INDENT) { switch(_getMenuItemValue(id_MENU_SPECIAL_INDENT)) { case indent_NONE: dDefault = 0.0; break; case indent_FIRSTLINE: case indent_HANGING: // only change to default if existing value is zero dDefault = UT_convertDimensionless(_getSpinItemValue(id_SPIN_SPECIAL_INDENT)); if (dDefault == 0) { bDefault = false; } else { dDefault = 0.5; } break; default: UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); break; } if (bDefault) { if (m_dim != DIM_IN) dDefault = UT_convertInchesToDimension(dDefault, m_dim); const gchar* szNew = UT_convertInchesToDimensionString(m_dim, dDefault, ".1"); _setSpinItemValue(id_SPIN_SPECIAL_INDENT, szNew, op_SYNC); } } else /* (changed == id_SPIN_SPECIAL_INDENT) */ { switch(_getMenuItemValue(id_MENU_SPECIAL_INDENT)) { case indent_NONE: _setMenuItemValue(id_MENU_SPECIAL_INDENT, indent_FIRSTLINE, op_SYNC); break; default: break; } } // if spin contains a negative number, we flip the direction of the indent. double val = UT_convertDimensionless(_getSpinItemValue(id_SPIN_SPECIAL_INDENT)); if (val < 0) { sign = -sign; // sometimes this appears to have no effect. why? if (_getMenuItemValue(id_MENU_SPECIAL_INDENT) == indent_FIRSTLINE) _setMenuItemValue(id_MENU_SPECIAL_INDENT, indent_HANGING, op_SYNC); else if (_getMenuItemValue(id_MENU_SPECIAL_INDENT) == indent_HANGING) _setMenuItemValue(id_MENU_SPECIAL_INDENT, indent_FIRSTLINE, op_SYNC); const gchar* szNew = UT_convertInchesToDimensionString(m_dim, -val, ".1"); _setSpinItemValue(id_SPIN_SPECIAL_INDENT, szNew, op_SYNC); } // sanity check. double leftIndent = UT_convertToDimension(_getSpinItemValue(id_SPIN_LEFT_INDENT), m_dim); double effectiveLeftMargin = leftIndent + (UT_convertToDimension (_getSpinItemValue(id_SPIN_SPECIAL_INDENT), m_dim) * sign); double leftPageMargin = UT_convertToDimension(m_pageLeftMargin, m_dim); double rightIndent = UT_convertToDimension(_getSpinItemValue(id_SPIN_RIGHT_INDENT), m_dim); if(-effectiveLeftMargin > leftPageMargin) { _setSpinItemValue(id_SPIN_SPECIAL_INDENT, (const gchar *)UT_formatDimensionString(m_dim, -leftPageMargin), op_SYNC); } if(effectiveLeftMargin > UT_convertInchesToDimension(m_iMaxWidth, m_dim) - rightIndent) { _setSpinItemValue(id_SPIN_SPECIAL_INDENT, (const gchar *)UT_convertInchesToDimensionString(m_dim, m_iMaxWidth - rightIndent), op_SYNC); } } if (changed == id_SPIN_SPECIAL_SPACING) { switch(_getMenuItemValue(id_MENU_SPECIAL_SPACING)) { case spacing_SINGLE: case spacing_ONEANDHALF: case spacing_DOUBLE: _setMenuItemValue(id_MENU_SPECIAL_SPACING, spacing_MULTIPLE, op_SYNC); break; default: break; } } if (changed == id_MENU_SPECIAL_SPACING) { UT_Dimension dimOld = UT_determineDimension(_getSpinItemValue(id_SPIN_SPECIAL_SPACING), DIM_none); switch(_getMenuItemValue(id_MENU_SPECIAL_SPACING)) { case spacing_SINGLE: _setSpinItemValue(id_SPIN_SPECIAL_SPACING, "1.0", op_SYNC); break; case spacing_ONEANDHALF: _setSpinItemValue(id_SPIN_SPECIAL_SPACING, "1.5", op_SYNC); break; case spacing_DOUBLE: _setSpinItemValue(id_SPIN_SPECIAL_SPACING, "2.0", op_SYNC); break; case spacing_ATLEAST: case spacing_EXACTLY: // only change to default if not dimensioned if (dimOld == DIM_none) _setSpinItemValue(id_SPIN_SPECIAL_SPACING, "12pt", op_SYNC); break; case spacing_MULTIPLE: // only change to default if dimensioned if (dimOld != DIM_none) _setSpinItemValue(id_SPIN_SPECIAL_SPACING, "1.0", op_SYNC); break; default: UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN); break; } } // the preview needs to suck in the changed data (to cache it // for subsequent draws) UT_BidiCharType iDir; if(_getCheckItemValue(id_CHECK_DOMDIRECTION) == check_TRUE) iDir = UT_BIDI_RTL; else if(_getCheckItemValue(id_CHECK_DOMDIRECTION) == check_FALSE) iDir = UT_BIDI_LTR; else { // nothing given -- default to LTR UT_DEBUGMSG(("AP_Dialog_Paragraph::_syncControls: no value of dom-dir," " defaulting to LTR\n")); iDir = UT_BIDI_LTR; } m_paragraphPreview->setFormat(m_pageLeftMargin, m_pageRightMargin, (AP_Dialog_Paragraph::tAlignState) _getMenuItemValue(id_MENU_ALIGNMENT), _getSpinItemValue(id_SPIN_SPECIAL_INDENT), (AP_Dialog_Paragraph::tIndentState) _getMenuItemValue(id_MENU_SPECIAL_INDENT), _getSpinItemValue(id_SPIN_LEFT_INDENT), _getSpinItemValue(id_SPIN_RIGHT_INDENT), _getSpinItemValue(id_SPIN_BEFORE_SPACING), _getSpinItemValue(id_SPIN_AFTER_SPACING), _getSpinItemValue(id_SPIN_SPECIAL_SPACING), (AP_Dialog_Paragraph::tSpacingState) _getMenuItemValue(id_MENU_SPECIAL_SPACING), iDir); m_paragraphPreview->draw(); } bool AP_Dialog_Paragraph::_wasChanged(tControl item) { UT_return_val_if_fail (item <= m_vecProperties.getItemCount(), false); sControlData * pItem = _getPropertyItem (item); UT_return_val_if_fail (pItem, false); return pItem->changed (); } void AP_Dialog_Paragraph::_addPropertyItem (tControl index, const sControlData & control_data) { sControlData * pDataCopy = 0; try { pDataCopy = new sControlData(control_data); } catch(...) { pDataCopy = 0; } UT_return_if_fail (pDataCopy); m_vecProperties.setNthItem (static_cast(index), pDataCopy, 0); } AP_Dialog_Paragraph::sControlData::sControlData (UT_sint32 data) : m_siData(data), m_csData(check_INDETERMINATE), m_szData(0), m_bChanged(false) { // } AP_Dialog_Paragraph::sControlData::sControlData (tCheckState data) : m_siData(0), m_csData(data), m_szData(0), m_bChanged(false) { // } /* default is empty string */ AP_Dialog_Paragraph::sControlData::sControlData (gchar * data) : m_siData(0), m_csData(check_INDETERMINATE), m_szData(new gchar[SPIN_BUF_TEXT_SIZE]), m_bChanged(false) { m_szData[SPIN_BUF_TEXT_SIZE-1] = 0; setData (data); } AP_Dialog_Paragraph::sControlData::sControlData (const sControlData & rhs) : m_siData(rhs.m_siData), m_csData(rhs.m_csData), m_szData(rhs.m_szData ? new gchar[SPIN_BUF_TEXT_SIZE] : 0), m_bChanged(false) { if (m_szData) memcpy (m_szData, rhs.m_szData, SPIN_BUF_TEXT_SIZE * sizeof (gchar)); } AP_Dialog_Paragraph::sControlData::~sControlData () { DELETEPV(m_szData); } AP_Dialog_Paragraph::sControlData & AP_Dialog_Paragraph::sControlData::operator= (const sControlData & rhs) { m_siData = rhs.m_siData; m_csData = rhs.m_csData; if (rhs.m_szData) { if (!m_szData) { try { m_szData = new gchar[SPIN_BUF_TEXT_SIZE]; } catch(...) { m_szData = 0; } } UT_return_val_if_fail (m_szData, *this); memcpy (m_szData, rhs.m_szData, SPIN_BUF_TEXT_SIZE * sizeof (gchar)); } else if (m_szData) { m_szData[0] = 0; } m_bChanged = false; return *this; } bool AP_Dialog_Paragraph::sControlData::setData (const gchar * data) { if (!m_szData) { try { m_szData = new gchar[SPIN_BUF_TEXT_SIZE]; } catch(...) { m_szData = 0; } UT_return_val_if_fail (m_szData, false); m_szData[SPIN_BUF_TEXT_SIZE-1] = 0; } if (data) strncpy (m_szData, data, SPIN_BUF_TEXT_SIZE - 1); else m_szData[0] = 0; return true; }