1 /* -*- mode: C++; tab-width: 4; c-basic-offset: 4; -*- */
2 
3 /* AbiWord
4  * Copyright (C) 1998 AbiSource, Inc.
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19  * 02110-1301 USA.
20  */
21 
22 #include <stdlib.h>
23 #include <stdio.h>
24 #include <string.h>
25 #include <ctype.h>
26 
27 #include "ap_Features.h"
28 
29 #include "ut_assert.h"
30 #include "ut_string.h"
31 #include "ut_units.h"
32 #include "ut_debugmsg.h"
33 
34 #include "xap_App.h"
35 #include "xap_Dialog_Id.h"
36 #include "xad_Document.h"
37 #include "xap_DialogFactory.h"
38 #include "xap_Dlg_MessageBox.h"
39 #include "xap_Prefs.h"
40 #include "fv_View.h"
41 #include "fl_DocLayout.h"
42 
43 #include "ap_Dialog_Tab.h"
44 #include "ap_Prefs_SchemeIds.h"
45 #include "ap_TopRuler.h"		// for AP_TopRulerInfo
46 
47 
48 
AP_Dialog_Tab(XAP_DialogFactory * pDlgFactory,XAP_Dialog_Id id)49 AP_Dialog_Tab::AP_Dialog_Tab(XAP_DialogFactory * pDlgFactory, XAP_Dialog_Id id)
50 	: XAP_Dialog_NonPersistent(pDlgFactory,id, "interface/dialogtabs"),
51 	  m_answer(a_OK),
52 	  m_pFrame(0),
53 	  m_dim(DIM_IN),
54 	  m_pszTabStops(0),
55 	  m_pCallbackFn(0),
56 	  m_closure(0)
57 {
58   m_pszTabStops = new char [1]; m_pszTabStops[0] = 0;
59 }
60 
~AP_Dialog_Tab(void)61 AP_Dialog_Tab::~AP_Dialog_Tab(void)
62 {
63   DELETEPV(m_pszTabStops);
64   UT_VECTOR_PURGEALL(fl_TabStop *, m_tabInfo);
65 }
66 
setSaveCallback(TabSaveCallBack pCb,void * closure)67 void AP_Dialog_Tab::setSaveCallback (TabSaveCallBack pCb, void * closure)
68 {
69 	m_pCallbackFn = pCb;
70 	m_closure = closure;
71 }
72 
getAnswer(void) const73 AP_Dialog_Tab::tAnswer AP_Dialog_Tab::getAnswer(void) const
74 {
75 	return m_answer;
76 }
77 
_storeWindowData()78 void AP_Dialog_Tab::_storeWindowData()
79 {
80 	UT_return_if_fail (m_pFrame); // needs to be set from runModal for some of the event_'s to work
81 
82 	FV_View *pView = (FV_View *)m_pFrame->getCurrentView();
83 	UT_return_if_fail (pView && m_pCallbackFn);
84 
85 	(*m_pCallbackFn)(this, pView, m_pszTabStops, _gatherDefaultTabStop(), m_closure);
86 }
87 
_populateWindowData(void)88 void AP_Dialog_Tab::_populateWindowData(void)
89 {
90 	const gchar * szRulerUnits;
91 	if (getApp()->getPrefsValue(AP_PREF_KEY_RulerUnits,&szRulerUnits))
92 		m_dim = UT_determineDimension(szRulerUnits);
93 	else
94 		m_dim = DIM_IN;
95 
96 	UT_return_if_fail (m_pFrame); // needs to be set from runModal for some of the event_'s to work
97 
98 	FV_View *pView = (FV_View *)m_pFrame->getCurrentView();
99 	UT_return_if_fail (pView);
100 
101 	// get the info used in the top ruler
102 	AP_TopRulerInfo rulerInfo;
103 	pView->getTopRulerInfo(&rulerInfo);
104 
105 	UT_DEBUGMSG(("AP_Dialog_Tab::_populateWindowData\n"));
106 	UT_DEBUGMSG(("iTabStops=%d\tDefaultTabInterval=%d\ttabStops=%s\n",
107 				rulerInfo.m_iTabStops,
108 				rulerInfo.m_iDefaultTabInterval,
109 				rulerInfo.m_pszTabStops
110 				));
111 
112 	// save the tab string
113 	m_pszTabStops = new char[strlen(rulerInfo.m_pszTabStops) + 1];
114 	strcpy(m_pszTabStops, rulerInfo.m_pszTabStops);
115 
116 	int iTab;
117 	fl_TabStop		*pTabInfo;
118 	for ( iTab = 0; iTab < rulerInfo.m_iTabStops; iTab++ )
119 	{
120 
121 		// create new tab info
122 		pTabInfo = new fl_TabStop();
123 		UT_return_if_fail (pTabInfo);
124 
125 
126 		(*rulerInfo.m_pfnEnumTabStops)( rulerInfo.m_pVoidEnumTabStopsData,
127 						iTab, pTabInfo);
128 
129 		m_tabInfo.addItem(pTabInfo);
130 	}
131 
132 	_setTabList(m_tabInfo.getItemCount());
133 	_setAlignment(FL_TAB_LEFT);
134 
135 	const gchar ** propsBlock = NULL;
136 	pView->getBlockFormat(&propsBlock);
137 
138 	_setDefaultTabStop((const gchar *)"0");
139 
140 	if (propsBlock[0])
141 	{
142 		const gchar * sz;
143 
144 		sz = UT_getAttribute("default-tab-interval", propsBlock);
145 
146 		if(sz)
147 		{
148 			double inches = UT_convertToInches(sz);
149 
150 			_setDefaultTabStop((const gchar *)UT_convertInchesToDimensionString(m_dim, inches));
151 		}
152 
153 	}
154 
155 	// enable/disable controls
156 	_initEnableControls();
157 }
158 
159 
160 // The initialize the controls (i.e., disable controls not coded yet)
_initEnableControls()161 void AP_Dialog_Tab::_initEnableControls()
162 {
163 	// alignment
164 	_controlEnable( id_ALIGN_BAR,			true );
165 
166 	// buttons
167 	// Un-comment this once changes detailed below in something changed are implemented.
168 	//_controlEnable( id_BUTTON_SET,			false );
169 	_controlEnable( id_BUTTON_SET,			true );
170 	_controlEnable( id_BUTTON_CLEAR,		false );
171 
172 	_controlEnable( id_BUTTON_CLEAR_ALL,	m_tabInfo.getItemCount() == 0 ? false : true );
173 }
174 
_event_TabChange(void)175 void AP_Dialog_Tab::_event_TabChange(void)
176 {
177 	_controlEnable(id_BUTTON_SET, true);
178 }
179 
_event_AlignmentChange(void)180 void AP_Dialog_Tab::_event_AlignmentChange(void)
181 {
182 	_controlEnable(id_BUTTON_SET, true);
183 }
184 
185 
_event_TabSelected(UT_sint32 index)186 void AP_Dialog_Tab::_event_TabSelected( UT_sint32 index )
187 {
188 	UT_DEBUGMSG(("AP_Dialog_Tab::_event_TabSelected\n"));
189 
190 	if(index >= 0)
191 	{
192 		UT_return_if_fail (index < m_tabInfo.getItemCount());
193 
194 		fl_TabStop *pTabInfo = (fl_TabStop *)m_tabInfo.getNthItem(index);
195 
196 		// HACK - iType if 1..5 to be LEFT..BAR in the same order.  We SHOULD make
197 		// an enumerated type in block layout or something, or atleast define the
198 		// common set of constants.  ap_TopRuler.cpp defines all the constants
199 		// again.  Here, since enum is rel 0, i'm subtracting one and doing an
200 		// ugly type cast
201 		_setAlignment( pTabInfo->getType() );
202 		_setLeader( pTabInfo->getLeader() );
203 
204 		_setTabEdit( _getTabDimensionString(index) );
205 
206 		// something changed...
207 		_event_somethingChanged();
208 	}
209 }
210 
_event_Set(void)211 void AP_Dialog_Tab::_event_Set(void)
212 {
213 	UT_String buffer;
214 
215 	// check the validity of the input
216 	bool res = buildTab(buffer);
217 	if (!res)
218 	{
219 		// TODO: add a message box here to inform our user - MARCM
220 		return;
221 	}
222 
223 	UT_DEBUGMSG(("DOM: %s\n", buffer.c_str()));
224 
225 	const char *cbuffer = buffer.c_str();
226 	int Dimension_size = 0;
227 	while(cbuffer[Dimension_size] != 0)
228 	{
229 
230 		if(cbuffer[Dimension_size] == '/')
231 		{
232 			Dimension_size--;
233 			break;
234 		}
235 
236 		Dimension_size++;
237 	}
238 	UT_sint32 i;
239 	// do we have the tab already.
240 
241 	for (  i = 0; i < m_tabInfo.getItemCount(); i++ )
242 	{
243 		fl_TabStop *pTabInfo = (fl_TabStop *)m_tabInfo.getNthItem(i);
244 		UT_return_if_fail (pTabInfo);
245 
246 		// if we have a tab at that unit
247 		if ( memcmp(cbuffer, _getTabString(pTabInfo), Dimension_size) == 0 )
248 		{
249 			// Delete the tab.
250 
251 			_deleteTabFromTabString(pTabInfo);
252 
253 			break;
254 		}
255 	}
256 
257 	// Add tab to list.
258 
259 	int NewOffset = strlen(m_pszTabStops);
260 	char *p_temp = new char[NewOffset + 1 + strlen(cbuffer) + 1];
261 	strcpy(p_temp, m_pszTabStops);
262 	if(m_pszTabStops[0] != 0)
263 	{
264 		strcat(p_temp, ",");
265 	}
266 	strcat(p_temp, cbuffer);
267 	delete [] m_pszTabStops;
268 	m_pszTabStops = p_temp;
269 
270 	UT_return_if_fail (m_pFrame); // needs to be set from runModal for some of the event_'s to work
271 
272 	FV_View *pView = static_cast<FV_View *>(m_pFrame->getCurrentView());
273 	UT_return_if_fail(pView);
274 
275 	buildTabStops(m_pszTabStops, m_tabInfo);
276 
277 	_setTabList(m_tabInfo.getItemCount());
278 
279 	// Select the new or changed tab in the list.
280 
281 	for (i = 0; i < m_tabInfo.getItemCount(); i++ )
282 	{
283 		fl_TabStop *pTabInfo = m_tabInfo.getNthItem(i);
284 		UT_return_if_fail (pTabInfo);
285 
286 		// if we have a tab at that unit
287 		if ( memcmp(cbuffer, _getTabString(pTabInfo), Dimension_size) == 0 )
288 		{
289 			_setSelectTab(i);
290 			_setTabEdit( _getTabDimensionString(i) );
291 			break;
292 		}
293 	}
294 
295 	// something changed...
296 	_event_somethingChanged();
297 
298 }
299 
300 /*!
301 * Update the currently selected tab's properties.
302 * Ripped off from _event_Set(). This method does auto-apply.
303 */
_event_Update(void)304 void AP_Dialog_Tab::_event_Update(void)
305 {
306 	fl_TabStop *pTabInfo1 = NULL;
307 
308 	// check the validity of the input
309 	UT_String buffer;
310 	bool res = buildTab(buffer);
311 	if (!res)
312 	{
313 		// TODO: add a message box here to inform our user - MARCM
314 		return;
315 	}
316 
317 
318 	// delete tab
319 	UT_uint32 ndx = _gatherSelectTab();
320 	pTabInfo1 = m_tabInfo.getNthItem(ndx);
321 	_deleteTabFromTabString(pTabInfo1);
322 	m_tabInfo.deleteNthItem(ndx);
323 
324 
325 	// re-add
326 	const char *cbuffer = buffer.c_str();
327 	int Dimension_size = 0;
328 	while(cbuffer[Dimension_size] != 0)
329 	{
330 
331 		if(cbuffer[Dimension_size] == '/')
332 		{
333 			Dimension_size--;
334 			break;
335 		}
336 
337 		Dimension_size++;
338 	}
339 
340 	// do we have the tab already.
341 	UT_sint32 i;
342 	for (i = 0; i < m_tabInfo.getItemCount(); i++ )
343 	{
344 		pTabInfo1 = (fl_TabStop *)m_tabInfo.getNthItem(i);
345 		UT_return_if_fail (pTabInfo1);
346 
347 		// if we have a tab at that unit
348 		if ( memcmp(cbuffer, _getTabString(pTabInfo1), Dimension_size) == 0 )
349 		{
350 			// Delete the tab.
351 			_deleteTabFromTabString(pTabInfo1);
352 			break;
353 		}
354 	}
355 
356 	// Add tab to list.
357 	int NewOffset = strlen(m_pszTabStops);
358 	char *p_temp = new char[NewOffset + 1 + strlen(cbuffer) + 1];
359 	strcpy(p_temp, m_pszTabStops);
360 	if(m_pszTabStops[0] != 0)
361 	{
362 		strcat(p_temp, ",");
363 	}
364 	strcat(p_temp, cbuffer);
365 	delete [] m_pszTabStops;
366 	m_pszTabStops = p_temp;
367 
368 	UT_return_if_fail (m_pFrame); // needs to be set from runModal for some of the event_'s to work
369 
370 	FV_View *pView = static_cast<FV_View *>(m_pFrame->getCurrentView());
371 	UT_return_if_fail(pView);
372 
373 	buildTabStops(m_pszTabStops, m_tabInfo);
374 
375 	_setTabList(m_tabInfo.getItemCount());
376 
377 	// Select the new or changed tab in the list.
378 
379 	for (i = 0; i < m_tabInfo.getItemCount(); i++ )
380 	{
381 		fl_TabStop *pTabInfo = m_tabInfo.getNthItem(i);
382 		UT_return_if_fail (pTabInfo);
383 
384 		// if we have a tab at that unit
385 		if ( memcmp(cbuffer, _getTabString(pTabInfo), Dimension_size) == 0 )
386 		{
387 			_setSelectTab(i);
388 			_setTabEdit( _getTabDimensionString(i) );
389 			break;
390 		}
391 	}
392 
393 	// something changed...
394 	_event_somethingChanged();
395 	_storeWindowData ();
396 }
397 
_event_Clear(void)398 void AP_Dialog_Tab::_event_Clear(void)
399 {
400 	UT_DEBUGMSG(("AP_Dialog_Tab::_event_Clear\n"));
401 
402 	UT_sint32 index = _gatherSelectTab();
403 
404 	if(index != -1)
405 	{
406 		UT_return_if_fail(index < m_tabInfo.getItemCount());
407 
408 		_deleteTabFromTabString(m_tabInfo.getNthItem(index));
409 
410 		UT_return_if_fail(m_pFrame); // needs to be set from runModal for some of the event_'s to work
411 
412 		buildTabStops(m_pszTabStops, m_tabInfo);
413 
414 		_setTabList(m_tabInfo.getItemCount());
415 
416 		if(m_tabInfo.getItemCount() > 0)
417 		{
418 			_setSelectTab(0);
419 			_event_TabSelected(0);
420 		}
421 		else
422 		{
423 			_setSelectTab(-1);
424 		}
425 
426 		// something changed...
427 		_event_somethingChanged();
428 	}
429 }
430 
_event_ClearAll(void)431 void AP_Dialog_Tab::_event_ClearAll(void)
432 {
433 	UT_DEBUGMSG(("AP_Dialog_Tab::_event_ClearAll\n"));
434 
435 	UT_return_if_fail(m_pFrame); // needs to be set from runModal for some of the event_'s to work
436 
437 	delete [] m_pszTabStops;
438 	m_pszTabStops = new char [1]; m_pszTabStops[0] = 0;
439 
440 	buildTabStops(m_pszTabStops, m_tabInfo);
441 
442 	_clearList();
443 
444 	// something changed...
445 	_event_somethingChanged();
446 }
447 
AlignmentToChar(eTabType a)448 /*static*/ unsigned char AP_Dialog_Tab::AlignmentToChar( eTabType a )
449 {
450 	char ch;
451 
452 	switch ( a )
453 	{
454 	case FL_TAB_LEFT:
455 		ch = 'L';
456 		break;
457 
458 	case FL_TAB_RIGHT:
459 		ch = 'R';
460 		break;
461 
462 	case FL_TAB_CENTER:
463 		ch = 'C';
464 		break;
465 
466 	case FL_TAB_DECIMAL:
467 		ch = 'D';
468 		break;
469 
470 	case FL_TAB_BAR:
471 		ch = 'B';
472 		break;
473 
474 	default:
475 		UT_ASSERT_HARMLESS(UT_NOT_IMPLEMENTED);
476 		ch = 'L';
477 		break;
478 	}
479 
480 	return ch;
481 }
482 
CharToAlignment(unsigned char ch)483 /*static*/ eTabType AP_Dialog_Tab::CharToAlignment( unsigned char ch )
484 {
485 	eTabType a;
486 	switch ( ch )
487 	{
488 	case 'L':
489 		a = FL_TAB_LEFT;
490 		break;
491 
492 	case 'R':
493 		a = FL_TAB_RIGHT;
494 		break;
495 
496 	case 'C':
497 		a = FL_TAB_CENTER;
498 		break;
499 
500 	case 'D':
501 		a = FL_TAB_DECIMAL;
502 		break;
503 
504 	case 'B':					// not implemented, fall though
505 		a = FL_TAB_BAR;
506 		break;
507 
508 	default:
509 		UT_ASSERT_HARMLESS(UT_NOT_IMPLEMENTED);
510 		a = FL_TAB_LEFT;
511 	}
512 	return a;
513 }
514 
clearList()515 void AP_Dialog_Tab::clearList()
516 {
517 	_clearList();
518 
519 	UT_VECTOR_PURGEALL(fl_TabStop *, m_tabInfo);
520 }
521 
522 
buildTab(UT_String & buffer)523 bool AP_Dialog_Tab::buildTab( UT_String & buffer )
524 {
525 	// get current value from member
526 	const gchar* szOld = _gatherTabEdit();
527 	bool res = UT_isValidDimensionString(szOld, MAX_TAB_LENGTH);
528 	if (res)
529 	{
530 		const gchar* szNew = UT_reformatDimensionString(m_dim, szOld);
531 
532 		UT_String_sprintf( buffer, "%s/%c%c", szNew, AlignmentToChar(_gatherAlignment()),
533 		 					(static_cast<char>(_gatherLeader()))+'0');
534 	}
535 
536 	return res;
537 }
538 
_event_somethingChanged()539 void AP_Dialog_Tab::_event_somethingChanged()
540 {
541 	UT_String buffer;
542 
543 	buildTab( buffer );
544 	const char *cbuffer = buffer.c_str();
545 	UT_DEBUGMSG(("AP_Dialog_Tab::_event_somethingChanged  [%s]\n", cbuffer ));
546 
547 	// check to see if the current tab is in the list
548 	bool bEnableClear = false;
549 	bool bEnableSet   = true;		// only disabled if current selection exactly matches current ones
550 										// or there are no items in the list.
551 
552 // this just looks broken for the initial tab thingie.
553 #if 0
554 	if(m_tabInfo.getItemCount() == 0)
555 	{
556 		bEnableSet = false;
557 	}
558 #endif
559 
560 	for ( UT_sint32 i = 0; i < m_tabInfo.getItemCount(); i++ )
561 	{
562 		fl_TabStop *pTabInfo = m_tabInfo.getNthItem(i);
563 		UT_return_if_fail (pTabInfo);
564 
565 		// if we have a tab at that unit
566 		if ( !strcmp(cbuffer, _getTabString(pTabInfo)) )
567 		{
568 			bEnableClear = true;
569 
570 			// if everything is the same, disable the set
571 			if ( pTabInfo->getType() == _gatherAlignment() &&
572 			     pTabInfo->getLeader() == _gatherLeader() ){
573       	// Disabled to fix bug 5143 and match behavior in the remainder of the program.  TODO: Cause focus to shift to OK button here,
574 				// and beef up the enable/disable routines for the set button.  Then, this can be re-enabled.
575 				// bEnableSet = false;
576 			}
577 
578 		}
579 	}
580 
581 	_controlEnable( id_BUTTON_SET, bEnableSet );
582 	_controlEnable( id_BUTTON_CLEAR, bEnableClear );
583 
584 	_controlEnable( id_BUTTON_CLEAR_ALL,	m_tabInfo.getItemCount() == 0 ? false : true );
585 
586 }
587 
_getTabDimensionString(UT_sint32 tabIndex)588 char *AP_Dialog_Tab::_getTabDimensionString(UT_sint32 tabIndex)
589 {
590 
591 	UT_return_val_if_fail (tabIndex < m_tabInfo.getItemCount(), NULL);
592 
593 	fl_TabStop *pTabInfo = m_tabInfo.getNthItem(tabIndex);
594 
595 	const char* pStart = &m_pszTabStops[pTabInfo->getOffset()];
596 	const char* pEnd = pStart;
597 	while (*pEnd && (*pEnd != '/'))
598 	{
599 		pEnd++;
600 	}
601 
602 	UT_uint32 iLen = pEnd - pStart;
603 	UT_return_val_if_fail (iLen<20, NULL);
604 
605 	strncpy(buf, pStart, iLen);
606 	buf[iLen]=0;
607 
608 	return buf;
609 }
610 
_getTabString(fl_TabStop * pTabInfo)611 char *AP_Dialog_Tab::_getTabString(fl_TabStop *pTabInfo)
612 {
613 	const char* pStart = &m_pszTabStops[pTabInfo->getOffset()];
614 	const char* pEnd = pStart;
615 	while (*pEnd && (*pEnd != ','))
616 	{
617 		pEnd++;
618 	}
619 
620 	UT_uint32 iLen = pEnd - pStart;
621 
622 	strncpy(buf, pStart, iLen);
623 	buf[iLen]=0;
624 
625 	return buf;
626 }
627 
_deleteTabFromTabString(fl_TabStop * pTabInfo)628 void AP_Dialog_Tab::_deleteTabFromTabString(fl_TabStop *pTabInfo)
629 {
630 	int Tab_data_size = 0;
631 	int Offset = pTabInfo->getOffset();
632 
633 	while(m_pszTabStops[Offset + Tab_data_size] != 0)
634 	{
635 		if(m_pszTabStops[Offset + Tab_data_size] == ',')
636 		{
637 			break;
638 		}
639 
640 		Tab_data_size++;
641 	}
642 
643 	if(Offset > 0)
644 	{
645 		// include leading comma.
646 		Offset--;
647 		Tab_data_size++;
648 	}
649 
650 	if(Offset == 0)
651 	{
652 		// include trailing comma if there is one.
653 
654 		if(m_pszTabStops[Offset + Tab_data_size] == ',')
655 		{
656 			Tab_data_size++;
657 		}
658 	}
659 
660 
661 
662 	memmove(m_pszTabStops + Offset,
663 				m_pszTabStops + Offset + Tab_data_size,
664 				strlen(m_pszTabStops) - (Offset + Tab_data_size));
665 
666 	m_pszTabStops[strlen(m_pszTabStops) - Tab_data_size] = 0;
667 }
668 
669 #define SPIN_INCR_IN	0.1
670 #define SPIN_INCR_CM	0.5
671 #define SPIN_INCR_MM	1.0
672 #define SPIN_INCR_PI	6.0
673 #define SPIN_INCR_PT	1.0
674 #define SPIN_INCR_none	0.1
675 
676 
_doSpin(tControl id,UT_sint32 amt)677 void AP_Dialog_Tab::_doSpin(tControl id, UT_sint32 amt)
678 {
679 	UT_DEBUGMSG (("ROB: _doSpin %d, %d\n", id, amt));
680 
681   //	UT_ASSERT(amt); // zero makes no sense
682 	UT_return_if_fail (id == id_SPIN_DEFAULT_TAB_STOP);
683 	if(amt == 0 )
684 	{
685 	        UT_DEBUGMSG(("AMOUNT = 0 amt = %d \n",amt));
686 	}
687 	// get current value from member
688 	const gchar* szOld = _gatherDefaultTabStop();
689 	double d = UT_convertDimensionless(szOld);
690 
691 	// figure out which dimension and units to spin in
692 	UT_Dimension dimSpin = m_dim;
693 	double dSpinUnit = SPIN_INCR_PT;
694 	double dMin = 0.0;
695 	switch (dimSpin)
696 	{
697 	case DIM_IN:
698 		dSpinUnit = SPIN_INCR_IN;
699 		dMin = 0.1;
700 		break;
701 
702 	case DIM_CM:
703 		dSpinUnit = SPIN_INCR_CM;
704 		dMin = 0.1;
705 		break;
706 
707 	case DIM_MM:
708 		dSpinUnit = SPIN_INCR_MM;
709 		dMin = 1.0;
710 		break;
711 
712 	case DIM_PI:
713 		dSpinUnit = SPIN_INCR_PI;
714 		dMin = 6.0;
715 		break;
716 
717 	case DIM_PT:
718 		dSpinUnit = SPIN_INCR_PT;
719 		dMin = 1.0;
720 		break;
721 	default:
722 
723 		UT_ASSERT_HARMLESS(UT_SHOULD_NOT_HAPPEN);
724 		break;
725 	}
726 
727 	// figure out spin precision, too
728 	const char * szPrecision = ".1";
729 	if ((dimSpin == DIM_PT) ||
730 		(dimSpin == DIM_PI))
731 		szPrecision = ".0";
732 
733 	// if needed, switch unit systems and round off
734 	UT_Dimension dimOld = UT_determineDimension(szOld, dimSpin);
735 
736 	if (dimOld != dimSpin)
737 	{
738 		double dInches = UT_convertToInches(szOld);
739 		d = UT_convertInchesToDimension(dInches, dimSpin);
740 	}
741 
742 	// value is now in desired units, so change it
743 	d += (dSpinUnit * static_cast<double>(amt));
744 	if (d < dMin)
745 		d = dMin;
746 
747 	const gchar* szNew = UT_formatDimensionString(dimSpin, d, szPrecision);
748 
749 	_setDefaultTabStop(szNew);
750 }
751