[Date Prev][Date Next][Thread Prev][Thread Next][Thread Index]

[XaraXtreme-commits] Commit Complete



Commit by  : phil
Repository : xara
Revision   : 1446
Date       : Fri Jul 14 22:53:54 BST 2006

Changed paths:
   M /Trunk/XaraLX/tools/textinfo.cpp
   M /Trunk/XaraLX/tools/textinfo.h
   M /Trunk/XaraLX/tools/textops.cpp
   M /Trunk/XaraLX/tools/textops.h
   M /Trunk/XaraLX/tools/texttool.cpp
   M /Trunk/XaraLX/tools/texttool.h

MartinW's Text tool updates for tabs and margins


Diff:
Index: Trunk/XaraLX/tools/textops.h
===================================================================
--- Trunk/XaraLX/tools/textops.h	(revision 1445)
+++ Trunk/XaraLX/tools/textops.h	(revision 1446)
@@ -243,6 +243,7 @@
 	void DoDeleteChar(OpTextFormat::DeleteType DelTyp);				 
 	void DoSwapCase();
 	BOOL DoReturn(OpTextFormat::InsertStatus InsStatus);
+	BOOL DoTab();
 
 	// One GetOpName fn for all flavours of this op
 	void GetOpName(String_256* OpName);
Index: Trunk/XaraLX/tools/textinfo.cpp
===================================================================
--- Trunk/XaraLX/tools/textinfo.cpp	(revision 1445)
+++ Trunk/XaraLX/tools/textinfo.cpp	(revision 1446)
@@ -137,13 +137,23 @@
 //#include "docview.h" - in camtypes.h [AUTOMATICALLY REMOVED]
 //#include "camvw.h"
 #include "blobs.h"
+#include "rulers.h"
+#include "usercord.h"
+#include "dlgmgr.h"
 
+// For tab stop dragging
+#include "csrstack.h"
+
 DECLARE_SOURCE( "$Revision$" );
 
 CC_IMPLEMENT_DYNCREATE(TextInfoBarOp,InformationBarOp)
 CC_IMPLEMENT_DYNCREATE(TextInfoBarData,CCObject)
 CC_IMPLEMENT_DYNCREATE(TextInfoBarEnumFont, OILEnumFonts )
+CC_IMPLEMENT_DYNCREATE(TextRulerBarData, CCObject)
+CC_IMPLEMENT_DYNCREATE(TabStopDragOp, Operation)
 
+#define TABSTOPDRAG_CURSORID_UNSET -1
+
 // Must come after the last CC_IMPLEMENT.. macro
 #define new CAM_DEBUG_NEW     
 
@@ -164,6 +174,8 @@
 const INT32 FontAspectMin  = 1;		// percent
 const INT32 FontAspectMax  = 9999;	// percent
 
+const INT32 CurrentTabButtonPos = -36;    // in pixels measured from the origin
+
 #define INVALID_ATTVAL -1000000
 // statics ...
 double TextInfoBarOp::SuperScriptSize;
@@ -178,9 +190,15 @@
 
 UnitType          TextInfoBarOp::CurrentFontUnits = COMP_POINTS;
 TextInfoBarData   TextInfoBarOp::InfoData;
+TextRulerBarData  TextInfoBarOp::RulerData;
 Document*         TextInfoBarOp::pDoc             = NULL;
 CommonAttrSet 	  TextInfoBarOp::CommonAttrsToFindSet; 	// A set which will contain all attribute types
 														// that we need to find common attributes for
+// cached bitmap sizes
+UINT32 TextInfoBarOp::TabBitmapWidth;
+UINT32 TextInfoBarOp::TabBitmapHeight;
+UINT32 TextInfoBarOp::CurrentTabButtonWidth;
+UINT32 TextInfoBarOp::CurrentTabButtonHeight;
 
 FontDropDown	*TextInfoBarOp::NameDropDown = NULL;	// Font name drop-down list support for the font list and
 
@@ -394,8 +412,6 @@
 
 ********************************************************************************************/
 
-
-
 BOOL TextInfoBarOp::Init()
 {
 	// Initialise the CommonAttrsToFindSet 
@@ -409,11 +425,55 @@
 	if (ok) ok = CommonAttrsToFindSet.AddTypeToSet(CC_RUNTIME_CLASS(AttrTxtLineSpace));
 	if (ok) ok = CommonAttrsToFindSet.AddTypeToSet(CC_RUNTIME_CLASS(AttrTxtBaseLine));
 	if (ok) ok = CommonAttrsToFindSet.AddTypeToSet(CC_RUNTIME_CLASS(AttrTxtScript));
-	return ok; 
+	if (ok) ok = CommonAttrsToFindSet.AddTypeToSet(CC_RUNTIME_CLASS(AttrTxtRuler));
+	if (ok) ok = CommonAttrsToFindSet.AddTypeToSet(CC_RUNTIME_CLASS(AttrTxtLeftMargin));
+	if (ok) ok = CommonAttrsToFindSet.AddTypeToSet(CC_RUNTIME_CLASS(AttrTxtRightMargin));
+	if (ok) ok = CommonAttrsToFindSet.AddTypeToSet(CC_RUNTIME_CLASS(AttrTxtFirstIndent));
+	if (ok) ok = TabStopDragOp::Init();
+
+	// find out about the sizes of the various tab ruler bitmaps (rather than hard-coding
+	// the sizes into the mouse click detection code in OnRulerClick())
+	// first, the size of the "current tab type" button - we ask for the left tab variant,
+	// but they should all be the same size
+	if (ok) ok = FindBitmapSize(_R(clefttab), &CurrentTabButtonWidth, &CurrentTabButtonHeight);
+	// find out about the size of tab stop blobs - we ask for the left tab variant, but they
+	// should all be the same size
+	if (ok) ok = FindBitmapSize(_R(lefttab), &TabBitmapWidth, &TabBitmapHeight);
+	return ok;
 }
 
 /********************************************************************************************
 
+>	static BOOL TextInfoBarOp::FindBitmapSize(ResourceID ID, UINT32* pWidth, UINT32* pHeight)
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	14/07/06
+	Returns:	FALSE if failed
+	Purpose:	Find the width and height of a resource bitmap
+
+********************************************************************************************/
+
+BOOL TextInfoBarOp::FindBitmapSize(ResourceID ID, UINT32* pWidth, UINT32* pHeight)
+{
+	BOOL ok = FALSE;
+	OILBitmap* pOilBitmap = OILBitmap::Create();
+	if (pOilBitmap) ok = pOilBitmap->LoadBitmap(ID);
+	if (ok)
+	{
+		// create a kernel bitmap based on our OilBitmap
+		KernelBitmap KBitmap(pOilBitmap);
+		if (KBitmap.IsOK())
+		{
+			*pWidth = KBitmap.GetWidth();
+			*pHeight = KBitmap.GetHeight();
+			ok = TRUE;
+		}
+	}
+	return ok;
+}
+
+/********************************************************************************************
+
 >	static void TextInfoBarOp::DeInit()
 
 	Author:		Simon_Maneggio (Xara Group Ltd) <camelotdev@xxxxxxxx>
@@ -453,8 +513,57 @@
 	NodeAttribute * Attrib = NULL;
 	
 	switch (ThisChange)
-	{
-				
+	{				
+		case LeftMarginA:
+		{
+			if(RulerData.IsLeftMarginValid)
+			{
+				AttrTxtLeftMargin  * LeftMarginAttrib = new AttrTxtLeftMargin();
+				if (LeftMarginAttrib == NULL)
+				{
+					InformError();
+					return;
+				}
+				LeftMarginAttrib->Value.Value = RulerData.LeftMargin;
+				Attrib = LeftMarginAttrib;
+			}
+			break;
+		}
+		case RightMarginA:
+		{
+			if(RulerData.IsRightMarginValid)
+			{
+				AttrTxtRightMargin  * RightMarginAttrib = new AttrTxtRightMargin();
+				if (RightMarginAttrib == NULL)
+				{
+					InformError();
+					return;
+				}
+				RightMarginAttrib->Value.Value = RulerData.RightMargin;
+				Attrib = RightMarginAttrib;
+			}
+			break;
+		}
+		case FirstIndentA:
+		{
+			if(RulerData.IsFirstIndentValid)
+			{
+				AttrTxtFirstIndent  * FirstIndentAttrib = new AttrTxtFirstIndent();
+				if (FirstIndentAttrib == NULL)
+				{
+					InformError();
+					return;
+				}
+				FirstIndentAttrib->Value.Value = RulerData.FirstIndent;
+				Attrib = FirstIndentAttrib;
+			}
+			break;
+		}
+		case RulerA:
+		{
+			Attrib = RulerData.pNewRuler;
+			break;
+		}
 		case BaseLineShiftA:
 		{
 			if(InfoData.BaseLineShift != INVALID_ATTVAL)
@@ -732,6 +841,7 @@
 	UpdateButtonStates();
 
 }
+
 /********************************************************************************************
 
 >void TextInfoBarOp::Update()
@@ -786,6 +896,8 @@
 	if (!Selection->FindCommonAttributes(&CommonAttrsToFindSet))
 		return FALSE; 
 
+	UpdateRulerBar(Selection, DoUpdate);
+
 	SelRange::CommonAttribResult result;
 	NodeAttribute* pAttr;
 
@@ -2133,7 +2245,7 @@
 
 /********************************************************************************************
 
->	MsgResult TextInfoBarOp::(Msg* Message) 
+>	MsgResult TextInfoBarOp::Message(Msg* Message) 
 
 	Author:		Mark_Goodall (Xara Group Ltd) <camelotdev@xxxxxxxx>
 	Created:	3/10/94
@@ -2835,3 +2947,912 @@
 
 	return TRUE;
 }
+
+
+/////////////////////////////////////////////////////////////////////////////////////////////
+// Ruler display and tab stop dragging
+
+/********************************************************************************************
+
+>void TextInfoBarOp::ForceRulerRedraw()
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	07/07/06
+	Purpose:	Force a ruler redraw
+				(called each time anything on the ruler has changed)
+
+********************************************************************************************/
+
+void TextInfoBarOp::ForceRulerRedraw()
+{
+	DocView* pDocView = DocView::GetSelected();
+	if (pDocView)
+	{
+		RulerPair* pRulerPair = pDocView->GetpRulerPair();
+		if (pRulerPair) pRulerPair->Update();
+	}
+}
+
+/********************************************************************************************
+
+>void TextInfoBarOp::ReleaseRuler()
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	07/07/06
+	Purpose:	Release the ruler if it has been claimed and update it
+				(called on tool deselection)						
+
+********************************************************************************************/
+
+void TextInfoBarOp::ReleaseRuler()
+{
+	if (RulerData.IsRulerOriginClaimed)
+	{
+		// release the ruler
+		RulerData.IsRulerOriginClaimed = FALSE;
+		// and force a redraw
+		ForceRulerRedraw();
+	}
+}
+
+/********************************************************************************************
+
+>	void TextInfoBarOp::TabStopDragStarting(TabStopDragType TheType)
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	13/07/06
+	Purpose:	Called when a tab stop drag starts (allows TextInfoBarOp to hide the implicit
+				tab stops to avoid confusion. Otherwise, if the last tab stop was dragged,
+				implicit tab stops would appear after the previous tab stop.
+
+********************************************************************************************/
+
+void TextInfoBarOp::TabStopDragStarting(TabStopDragType TheType)
+{
+	if (TheType == DragTabStop || TheType == DragNew)
+	{
+		TRACEUSER("wuerthne", _T("tab stop drag starting"));
+		RulerData.TabStopDragRunning = TRUE;
+	}
+}
+
+/********************************************************************************************
+
+>	void TextInfoBarOp::TabStopDragFinished()
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	13/07/06
+	Purpose:	Called when a tab stop drag finished, allowing TextInfoBarOp to display
+				implicit tab stops again.
+
+********************************************************************************************/
+
+void TextInfoBarOp::TabStopDragFinished()
+{
+	TRACEUSER("wuerthne", _T("tab stop drag finished"));
+	RulerData.TabStopDragRunning = FALSE;
+}
+
+/********************************************************************************************
+
+>void TextInfoBarOp::HighlightRulerSection(RulerBase* pRuler)
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	07/07/06
+	Purpose:	Show the position and width of the current text story on the ruler
+				(called from TextTool when the ruler background is redrawn)
+
+********************************************************************************************/
+
+void TextInfoBarOp::HighlightRulerSection(RulerBase* pRuler, UserRect& UpdateRect)
+{
+	if (!pRuler->IsHorizontal()) return;
+	TRACEUSER("wuerthne", _T("TextInfoBarOp::Highlight %d"), RulerData.CurrentRulerSectionWidth);
+
+	// we call RulerBase::HighlightSection, which expects coordinates in user space but
+	// knows about our tool origin
+	INT32 SectionEnd = RulerData.CurrentRulerSectionWidth;      // user coord of end of highlight section
+	if (SectionEnd == -1)
+	{
+		// infinite section
+		SectionEnd = UpdateRect.hi.x;
+	}
+	pRuler->HighlightSection(0, SectionEnd);
+}
+
+/********************************************************************************************
+
+>void TextInfoBarOp::RenderRulerBlobs(RulerBase* pRuler, UserRect& UpdateRect)
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	07/07/06
+	Purpose:	Show the margin and tab stop blobs on the ruler
+				(called from TextTool when the ruler foreground is redrawn)
+
+********************************************************************************************/
+
+void TextInfoBarOp::RenderRulerBlobs(RulerBase* pRuler, UserRect& UpdateRect)
+{
+	if (!pRuler->IsHorizontal()) return;
+
+	if (RulerData.IsLeftMarginValid)
+		pRuler->DrawBitmap(RulerData.LeftMargin, _R(leftmar));
+	else
+		pRuler->DrawBitmap(0, _R(gleftmar));
+
+	if (RulerData.IsFirstIndentValid)
+		pRuler->DrawBitmap(RulerData.FirstIndent, _R(firstind));
+	else
+		pRuler->DrawBitmap(0, _R(gfirstind));
+
+	if (RulerData.CurrentRulerSectionWidth != -1)
+	{
+		// we only draw a right margin if we have a fixed formatting width
+		if (RulerData.IsRightMarginValid)
+			pRuler->DrawBitmap(RulerData.CurrentRulerSectionWidth - RulerData.RightMargin, _R(rightmar));
+		else
+			pRuler->DrawBitmap(RulerData.CurrentRulerSectionWidth, _R(grightmar));
+	}
+
+	if (RulerData.IsRulerValid)
+	{
+		MILLIPOINT LastPos = 0;
+		for (TxtTabStopIterator it = RulerData.pShownRuler->begin(); it != RulerData.pShownRuler->end(); ++it)
+		{
+			ResourceID id;
+			switch((*it).GetType())
+			{
+				case LeftTab:
+					id = _R(lefttab);
+					break;
+				case RightTab:
+					id = _R(righttab);
+					break;
+				case CentreTab:
+					id = _R(centtab);
+					break;
+				case DecimalTab:
+					id = _R(dectab);
+					break;
+			}
+			pRuler->DrawBitmap((*it).GetPosition(), id);
+			LastPos = (*it).GetPosition();
+		}
+
+		if (!RulerData.TabStopDragRunning)
+		{
+			TRACEUSER("wuerthne", _T("redraw implicit tab stops"));
+			// draw greyed out left align tab stops at equidistant positions beyond the last defined
+			// tab stop, but only while we are not dragging
+			while(LastPos < UpdateRect.hi.x)
+			{
+				LastPos = LastPos + 36000 - (LastPos % 36000);
+				// if we have a fixed width text story, do not draw implicit tab stops outside the
+				// right hand margin (physical right margin less the logical margin, if present)
+				if (RulerData.CurrentRulerSectionWidth != -1
+					&& LastPos > RulerData.CurrentRulerSectionWidth
+					             - (RulerData.IsRightMarginValid ? RulerData.RightMargin : 0))
+					break;
+				pRuler->DrawBitmap(LastPos, _R(imptab));
+			}
+		}
+	}
+
+	// draw the current tab "button"
+	// we want this to appear to the left of the origin, but at a fixed distance
+	// no matter what the current zoom factor is, so we need to enquire about
+	// the scaled pixel size to find out how many MILLIPOINTS one screen pixel
+	// represents
+	DocView* pDocView = pRuler->GetpRulerPair()->GetpDocView();
+	FIXED16 PixelSize = pDocView->GetScaledPixelWidth();
+	MILLIPOINT Pos = CurrentTabButtonPos*PixelSize.MakeLong();
+
+	ResourceID id;
+	switch(RulerData.CurrentTabType)
+	{
+		case LeftTab:
+			id = _R(clefttab);
+			break;
+		case RightTab:
+			id = _R(crighttab);
+			break;
+		case CentreTab:
+			id = _R(ccenttab);
+			break;
+		case DecimalTab:
+			id = _R(cdectab);
+			break;
+	}
+	pRuler->DrawBitmap(Pos, id);
+}
+
+/********************************************************************************************
+
+>   BOOL TextInfoBarOp::OnRulerClick(UINT32 nFlags, UserCoord PointerPos, ClickType Click, ClickModifiers Mods,
+									 Spread* pSpread, RulerBase* pRuler)
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	07/07/06
+	Inputs:     nFlags      - synthesized mouse event flags (to be passed through to StartToolDrag)
+				PointerPos	- user coordinates of click on ruler (relative to origin set by tool)
+				Click		- Type of click enum
+				Mods		- Modifier flags struct
+				pSpread		- pointer to spread upon which click occurred
+				pRuler		- pointer to ruler which generated click
+	Returns:    TRUE to claim the click
+	Purpose:	Called when the user has clicked on the ruler and we have claimed it
+
+********************************************************************************************/
+
+BOOL TextInfoBarOp::OnRulerClick(UINT32 nFlags, UserCoord PointerPos, ClickType Click, ClickModifiers Mods,
+								 Spread* pSpread, RulerBase* pRuler)
+{
+	if (!pRuler->IsHorizontal()) return FALSE;
+	if (Click == CLICKTYPE_SINGLE)
+	{
+		// check whether the user has clicked on our homegrown "current tab" button
+		// this is displayed centered around the position CurrentTabButtonPos (in pixels)
+		DocView* pDocView = pRuler->GetpRulerPair()->GetpDocView();
+		MILLIPOINT PixelSize = pDocView->GetScaledPixelWidth().MakeLong();
+		MILLIPOINT Distance1 = ((CurrentTabButtonPos - CurrentTabButtonWidth/2)*PixelSize);
+		MILLIPOINT Distance2 = ((CurrentTabButtonPos + CurrentTabButtonWidth/2)*PixelSize);
+		if (PointerPos.x >= Distance1 && PointerPos.x <= Distance2)
+		{
+			// a click on our "current tab" button
+			if (Mods.Adjust)
+			{
+				// adjust click, so cycle the other way round
+				switch(RulerData.CurrentTabType)
+				{
+					case LeftTab:
+						RulerData.CurrentTabType = DecimalTab;
+						break;
+					case RightTab:
+						RulerData.CurrentTabType = LeftTab;
+						break;
+					case CentreTab:
+						RulerData.CurrentTabType = RightTab;
+						break;
+					case DecimalTab:
+						RulerData.CurrentTabType = CentreTab;
+						break;
+				}
+			}
+			else
+			{
+				// standard click, so cycle the type
+				switch(RulerData.CurrentTabType)
+				{
+					case LeftTab:
+						RulerData.CurrentTabType = RightTab;
+						break;
+					case RightTab:
+						RulerData.CurrentTabType = CentreTab;
+						break;
+					case CentreTab:
+						RulerData.CurrentTabType = DecimalTab;
+						break;
+					case DecimalTab:
+						RulerData.CurrentTabType = LeftTab;
+						break;
+				}
+			}
+			Document::SetSelectedViewAndSpread(pDocView->GetDoc(), pDocView, pSpread);
+			DialogManager::DefaultKeyboardFocus();
+			ForceRulerRedraw();
+			return TRUE;
+		}
+
+		// not clicked on our button, so check whether the click was within the highlight section
+		BOOL InHighlightSection = PointerPos.x >= 0
+			&& (RulerData.CurrentRulerSectionWidth == -1
+				|| PointerPos.x < RulerData.CurrentRulerSectionWidth);
+
+		// we do allow dragging tab stops outside the highlight section but we do not allow creating
+		// new ones by clicking outside
+
+		// check whether we have got a tab at the click position - if they overlap, the tab stop
+		// to the right is on top, so the user expects that to be dragged; therefore, tab stops
+		// to the right have priority
+		Document::SetSelectedViewAndSpread(pDocView->GetDoc(), pDocView, pSpread);
+		
+		INT32 Index = 0;
+		INT32 MatchedIndex = -1;
+		for (TxtTabStopIterator it = RulerData.pShownRuler->begin(); it != RulerData.pShownRuler->end(); ++it)
+		{
+			MILLIPOINT Pos = (*it).GetPosition();
+			if (PointerPos.x >= Pos - MILLIPOINT(TabBitmapWidth / 2 * PixelSize)
+				&& PointerPos.x <= Pos + MILLIPOINT(TabBitmapWidth / 2 * PixelSize))
+			{
+				TRACEUSER("wuerthne", _T("hit tab no %d"), Index);
+				MatchedIndex = Index;
+			}
+			Index++;
+		}
+		
+		TxtTabStop DraggedTabStop(LeftTab, 0);
+		if (MatchedIndex != -1)
+		{
+			// delete the tab stop from the shown ruler list
+			Index = MatchedIndex;
+			TxtTabStopIterator it = RulerData.pShownRuler->begin();
+			while(it != RulerData.pShownRuler->end() && Index--) ++it;
+			if (it != RulerData.pShownRuler->end())
+			{
+				DraggedTabStop = *it;
+				RulerData.pShownRuler->erase(it);
+			}
+			else
+			{
+				// cannot really happen, but exit gracefully - we still claim the click
+				return TRUE;
+			}
+		}
+		TabStopDragType DragType = (MatchedIndex == -1) ? DragNew : DragTabStop;
+		
+		if (DragType == DragNew && InHighlightSection || DragType != DragNew)
+		{
+			String_256 OpToken(OPTOKEN_TABSTOPDRAG);
+			TRACEUSER("wuerthne", _T("starting drag"));
+			TabStopDragOpParam* pParam = new TabStopDragOpParam(DragType, DraggedTabStop, PointerPos);
+			
+			if (pRuler->StartToolDrag(nFlags, PointerPos, &OpToken, pParam))
+			{
+				// the kernel has accepted the drag request
+				TextInfoBarOp::TabStopDragStarting(DragType);
+				// force a redraw to show the ruler without the tab stop being dragged (if any)
+				// and without implicit tab stops
+				TextInfoBarOp::ForceRulerRedraw();
+			}
+			else
+			{
+				// we cannot drag, but we need to reinstate the ruler bar (if we tried dragging
+				// and existing tab stop, we have removed it from pShownRuler)
+				TextInfoBarOp::Update(TRUE);
+			}
+			return TRUE;
+		}
+	}
+
+	// we have not claimed the click
+	return FALSE;
+}
+
+/********************************************************************************************
+
+>void TextInfoBarOp::DoAddTabStop(MILLIPOINT Position)
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	10/07/06
+	Inputs:     Position - the position in text ruler space (millipoints)
+	Purpose:	Create a tab stop of the currently selected type at the given position
+				and apply the new text ruler
+
+********************************************************************************************/
+
+void TextInfoBarOp::DoAddTabStop(MILLIPOINT Position)
+{
+	// we create a new ruler attribute
+	RulerData.pNewRuler = new AttrTxtRuler;
+	if (RulerData.pNewRuler == NULL) return;
+
+	// copy the ruler contents from the currently shown ruler
+	*RulerData.pNewRuler->Value.Value = *RulerData.pShownRuler;
+	RulerData.pNewRuler->Value.AddTabStop(RulerData.CurrentTabType, Position);
+	// OnFieldChange will use pNewRuler
+	OnFieldChange(RulerA);
+	// just to avoid confusion - OnFieldChange has taken control of the object, so
+	// let us forget about it
+	RulerData.pNewRuler = NULL;
+}
+
+/********************************************************************************************
+
+>void TextInfoBarOp::DoAddTabStop(MILLIPOINT Position)
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	10/07/06
+	Inputs:     NewTabStop - the tab stop object to add to the ruler
+	Purpose:	Add a tab stop to the currently displayed ruler and apply it
+
+********************************************************************************************/
+
+void TextInfoBarOp::DoAddTabStop(TxtTabStop NewTabStop)
+{
+	// we create a new ruler attribute
+	RulerData.pNewRuler = new AttrTxtRuler;
+	if (RulerData.pNewRuler == NULL) return;
+
+	// copy the ruler contents from the currently shown ruler
+	*RulerData.pNewRuler->Value.Value = *RulerData.pShownRuler;
+	RulerData.pNewRuler->Value.AddTabStop(NewTabStop);
+	// OnFieldChange will use pNewRuler
+	OnFieldChange(RulerA);
+	// just to avoid confusion - OnFieldChange has taken control of the object, so
+	// let us forget about it
+	RulerData.pNewRuler = NULL;
+}
+/********************************************************************************************
+
+>	void TextInfoBarOp::DoApplyShownRuler()
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	10/07/06
+	Purpose:	Apply the currently shown ruler (used to remove a tab stop that has been
+				dragged off the ruler - the tab stop is removed from the shown ruler when
+				the drag starts, so to permanently delete it all we need to do is apply
+				the shown ruler)
+
+********************************************************************************************/
+
+void TextInfoBarOp::DoApplyShownRuler()
+{
+	// we create a new ruler attribute
+	RulerData.pNewRuler = new AttrTxtRuler;
+	if (RulerData.pNewRuler == NULL) return;
+
+	// copy the ruler contents from the currently shown ruler
+	*RulerData.pNewRuler->Value.Value = *RulerData.pShownRuler;
+	// OnFieldChange will use pNewRuler
+	OnFieldChange(RulerA);
+	// just to avoid confusion - OnFieldChange has taken control of the object, so
+	// let us forget about it
+	RulerData.pNewRuler = NULL;
+}
+
+/********************************************************************************************
+
+>	INT32 TextInfoBarOp::GetLogicalStoryWidth(TextStory* pStory)
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	10/07/06
+	Inputs:     pStory - a selected story
+	Returns:    its logical width in millipoints
+	Purpose:	Given a text story, find the width we want to display for it in the ruler bar
+
+********************************************************************************************/
+
+INT32 TextInfoBarOp::GetLogicalStoryWidth(TextStory* pStory)
+{
+	if (pStory->IsWordWrapping())
+	{
+		// either the width the user set or, if on a path, the length of the path
+		// minus the left and right indents (as set in TextStory::FormatAndChildren
+		// when formatting the story)
+		return pStory->GetStoryWidth();
+	}
+	else
+	{
+		// text at point - infinite width
+		return -1;
+	}
+}
+
+/********************************************************************************************
+
+>BOOL TextInfoBarOp::UpdateRulerBar()
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	06/07/06
+	Inputs:     pSelection - the current selection range
+				DoUpdate - if TRUE, always update ruler display even if not changed
+	Returns:    FALSE if failed
+	Purpose:	Update the text ruler information shown in the ruler bar according to the
+				current selection 
+				called on selchange messages 
+				only update the ruler bar if something has changed
+
+********************************************************************************************/
+
+BOOL TextInfoBarOp::UpdateRulerBar(SelRange* pSelection, BOOL DoUpdate)
+{
+	TRACEUSER("wuerthne", _T("UpdateRulerBar"));
+	BOOL changed = FALSE;
+
+	// first of all, check whether we want to change the ruler origin
+	BOOL ShouldWeClaimRuler = FALSE;
+	INT32 RulerOrigin, StoryWidth;
+	TextStory* pFocusStory = TextStory::GetFocusStory();
+	if (pFocusStory)
+	{
+		// there is a focus story, so the ruler should adapt to it
+		TRACEUSER("wuerthne", _T("Focus story present"));
+		ShouldWeClaimRuler = TRUE;
+		DocRect StoryBounds=pFocusStory->GetBoundingRect();
+		DocCoord TopLeft(StoryBounds.lo.x, StoryBounds.hi.y);
+		UserCoord UserTopLeft = TopLeft.ToUser(pFocusStory->FindParentSpread());
+		RulerOrigin = UserTopLeft.x;
+		StoryWidth = GetLogicalStoryWidth(pFocusStory);
+	}
+	else
+	{
+		// no focus story, but maybe one or more selected text stories
+		Node* pNode = pSelection->FindFirst();
+		while (pNode != NULL)
+		{
+			if (IS_A(pNode, TextStory))
+			{
+				TextStory* pStory = (TextStory*)pNode;
+				DocRect StoryBounds=pStory->GetBoundingRect();
+				DocCoord TopLeft(StoryBounds.lo.x, StoryBounds.hi.y);
+				UserCoord UserTopLeft = TopLeft.ToUser(pStory->FindParentSpread());
+				
+				if (!ShouldWeClaimRuler || UserTopLeft.x < RulerOrigin)
+				{
+					// first found object, or further to the left than previous ones, so use this position
+					RulerOrigin = UserTopLeft.x;
+					StoryWidth = GetLogicalStoryWidth(pStory);
+					ShouldWeClaimRuler = TRUE;
+				}
+			}
+			pNode = pSelection->FindNext(pNode);
+		}
+	}
+
+	if (ShouldWeClaimRuler)
+	{
+		TRACEUSER("wuerthne", _T("claim ruler"));
+		if (RulerData.IsRulerOriginClaimed)
+		{
+			// we have already claimed the ruler, is it for the same origin?
+			if (RulerData.CurrentRulerOrigin != RulerOrigin
+				|| RulerData.CurrentRulerSectionWidth != StoryWidth)
+			{
+				changed = TRUE;
+			}
+		}
+		else
+		{
+			changed = TRUE;
+		}
+		RulerData.IsRulerOriginClaimed = TRUE;
+		RulerData.CurrentRulerOrigin = RulerOrigin;		
+		RulerData.CurrentRulerSectionWidth = StoryWidth;
+	}
+	else
+	{
+		if (RulerData.IsRulerOriginClaimed)
+		{
+			// the ruler bar has been claimed, but we do not want to claim it any more
+			RulerData.IsRulerOriginClaimed = FALSE;
+			changed  = TRUE;            // force ruler bar update
+		}
+	}
+
+	if (RulerData.IsRulerOriginClaimed)
+	{
+		// we are claiming the ruler, so update the values
+		SelRange::CommonAttribResult result;
+		NodeAttribute* pAttr;
+
+		// left margin
+		CommonAttrsToFindSet.FindAttrDetails(CC_RUNTIME_CLASS(AttrTxtLeftMargin), 
+											 &pAttr,
+											 &result);
+		AttrTxtLeftMargin* pLeftMarginAttrib = (AttrTxtLeftMargin*)pAttr;
+		
+		if (result == SelRange::ATTR_MANY)
+		{
+			if (RulerData.IsLeftMarginValid) changed = TRUE;
+			RulerData.IsLeftMarginValid = FALSE;
+		}
+		else
+		{
+			if (RulerData.IsLeftMarginValid)
+			{
+				if (RulerData.LeftMargin != pLeftMarginAttrib->Value.Value) changed = TRUE;
+			}
+			else
+				changed = TRUE;
+			RulerData.IsLeftMarginValid = TRUE;
+			RulerData.LeftMargin = pLeftMarginAttrib->Value.Value;
+		}
+
+		// right margin
+		CommonAttrsToFindSet.FindAttrDetails(CC_RUNTIME_CLASS(AttrTxtRightMargin), 
+											 &pAttr,
+											 &result);
+		AttrTxtRightMargin* pRightMarginAttrib = (AttrTxtRightMargin*)pAttr;
+		
+		if (result == SelRange::ATTR_MANY)
+		{
+			if (RulerData.IsRightMarginValid) changed = TRUE;
+			RulerData.IsRightMarginValid = FALSE;
+		}
+		else
+		{
+			if (RulerData.IsRightMarginValid)
+			{
+				if (RulerData.RightMargin != pRightMarginAttrib->Value.Value) changed = TRUE;
+			}
+			else
+				changed = TRUE;
+			RulerData.IsRightMarginValid = TRUE;
+			RulerData.RightMargin = pRightMarginAttrib->Value.Value;
+		}
+
+		// first indent
+		CommonAttrsToFindSet.FindAttrDetails(CC_RUNTIME_CLASS(AttrTxtFirstIndent), 
+											 &pAttr,
+											 &result);
+		AttrTxtFirstIndent* pFirstIndentAttrib = (AttrTxtFirstIndent*)pAttr;
+		
+		if (result == SelRange::ATTR_MANY)
+		{
+			if (RulerData.IsFirstIndentValid) changed = TRUE;
+			RulerData.IsFirstIndentValid = FALSE;
+		}
+		else
+		{
+			if (RulerData.IsFirstIndentValid)
+			{
+				if (RulerData.FirstIndent != pFirstIndentAttrib->Value.Value) changed = TRUE;
+			}
+			else
+				changed = TRUE;
+			RulerData.IsFirstIndentValid = TRUE;
+			RulerData.FirstIndent = pFirstIndentAttrib->Value.Value;
+		}
+
+		// ruler
+		CommonAttrsToFindSet.FindAttrDetails(CC_RUNTIME_CLASS(AttrTxtRuler), 
+											 &pAttr,
+											 &result);
+		AttrTxtRuler* pRulerAttrib = (AttrTxtRuler*)pAttr;
+		
+		if (result == SelRange::ATTR_MANY)
+		{
+			if (RulerData.IsRulerValid) changed = TRUE;
+			RulerData.IsRulerValid = FALSE;
+		}
+		else
+		{
+			if (RulerData.IsRulerValid)
+			{
+				if (*RulerData.pShownRuler != *pRulerAttrib->Value.Value) changed = TRUE;
+			}
+			else
+				changed = TRUE;
+			RulerData.IsRulerValid = TRUE;
+			// use the copy constructor
+			*RulerData.pShownRuler = *pRulerAttrib->Value.Value;
+		}
+	}
+
+	// if anything has changed, force a ruler redraw
+	if (changed || DoUpdate) ForceRulerRedraw();
+
+	return TRUE;
+}
+
+/********************************************************************************************
+
+>	static BOOL TabStopDragOp::Init()
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	12/07/06
+	Returns:    FALSE if failed
+	Purpose:	Register the TabStopDragOp operation
+
+********************************************************************************************/
+
+BOOL TabStopDragOp::Init()
+{
+	return RegisterOpDescriptor(0, 
+								0,
+								CC_RUNTIME_CLASS(TabStopDragOp), 
+								OPTOKEN_TABSTOPDRAG,
+								TabStopDragOp::GetState,
+								0,  /* help ID */
+								0,	/* bubble ID */
+								0	/* bitmap ID */);
+}
+
+/********************************************************************************************
+
+>	static OpState TabStopDragOp::GetState(String_256* Description, OpDescriptor*)
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	12/07/06
+	Inputs:		Description = ptr to place description of why this op can't happen
+				pOpDesc     = ptr to the Op Desc associated with this op
+	Returns:	An OpState object
+	Purpose:    Func for determining the usability of this op
+
+********************************************************************************************/
+
+OpState TabStopDragOp::GetState(String_256* Description, OpDescriptor*)
+{
+	OpState State;
+	return State;
+}
+
+/********************************************************************************************
+
+>	void TabStopDragOp::DoWithParam(OpDescriptor *pOpDesc, OpParam* pParam)
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	12/07/06
+	Inputs:		pOpDesc = ptr to the Op Desc associated with this op
+				pParam = parameter block (dynamically created in TextInfoBarOp::OnRulerClick)
+	Purpose:    Main entry point of operation.
+
+********************************************************************************************/
+
+void TabStopDragOp::DoWithParam(OpDescriptor *pOpDesc, OpParam* pParam)
+{
+	m_pParam = (TabStopDragOpParam*)pParam;
+	DoDrag(Document::GetSelectedSpread());
+}
+
+/***********************************************************************************************
+
+>	void TabStopDragOp::DoDrag(Spread* pThisSpread)
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	12/07/06
+	Inputs:		pThisSpread 	  = ptr to spread drag started in (NULL means get selected spread)
+				PointerPos	 	  = coord of point clicked
+	Purpose:    Starts a drag for a new or existing tab stop
+		
+***********************************************************************************************/
+
+void TabStopDragOp::DoDrag(Spread* pThisSpread)
+{
+	TRACEUSER("wuerthne", _T("DoDrag"));
+	pSpread = pThisSpread;
+
+	if (pSpread == NULL)
+		pSpread = Document::GetSelectedSpread();
+
+	ERROR3IF(pSpread == NULL,"pSpread == NULL");
+	if (pSpread == NULL)
+	{
+		End();
+		return;
+	}
+
+	Ordinate = m_pParam->m_StartPos.x;
+	TRACEUSER("wuerthne", _T("starting drag at %d"), Ordinate);
+
+	if (m_pCursor == NULL)
+	{
+		m_pCursor = new Cursor(TOOLID_TEXT,_R(IDCSR_TEXT_TAB));
+		
+		if (m_pCursor != NULL)
+			CursorStackID = CursorStack::GPush(m_pCursor);
+	}
+
+	//##UpdateStatusLine();
+
+	// Tell the Dragging system to start a drag operation
+	// We would like to use DRAGTYPE_DEFERRED here, but unfortunately, that also scrolls
+	// vertically, which is very much in the way. So, until there is something like
+	// DRAGTYPE_DEFERRED_HORIZONTAL, disable auto-scrolling.
+	StartDrag(DRAGTYPE_NOSCROLL);
+}
+
+/***********************************************************************************************
+
+>	virtual void TabStopDragOp::DragFinished(DocCoord PointerPos, ClickModifiers ClickMods, Spread*,
+											 BOOL Success, BOOL bSolidDrag)
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	12/07/06
+	Inputs:		PointerPos = coord of the pointer
+				ClickMods  = info on the click modifiers
+				pSpread    = ptr to spread (not used)
+				Success	   = TRUE if drag ended successfully, FALSE if drag terminated (by pressing Escape)
+	Purpose:    Responds to the drag of a tab stop ending
+		
+***********************************************************************************************/
+
+void TabStopDragOp::DragFinished(DocCoord PointerPos, ClickModifiers ClickMods, Spread*, BOOL Success,
+								 BOOL bSolidDrag)
+{
+	TRACEUSER("wuerthne", _T("tab stop drag ended"));
+
+	TextInfoBarOp::TabStopDragFinished();
+	if (Success)
+	{
+		DocView::SnapCurrent(pSpread,&PointerPos);
+
+		UserCoord UserPos = PointerPos.ToUser(pSpread);
+		UserPos.x -= TextInfoBarOp::GetRulerOrigin();
+		Ordinate = UserPos.x;
+		TRACEUSER("wuerthne", _T("with success at %d"), Ordinate);
+
+		if (m_pParam->m_DragType == DragNew)
+		{
+			if (IsMouseOverRuler())
+			{
+				TextInfoBarOp::DoAddTabStop(Ordinate);
+			}
+			else
+			{
+				// do nothing (apart from redrawing the ruler bar, so the implicit
+				// tabs will be displayed again)
+				TextInfoBarOp::ForceRulerRedraw();
+			}
+		}
+		else if (m_pParam->m_DragType == DragTabStop)
+		{
+			if (IsMouseOverRuler())
+			{
+				TxtTabStop NewTabStop(m_pParam->m_DraggedTabStop);
+				NewTabStop.SetPosition(Ordinate);
+				TextInfoBarOp::DoAddTabStop(NewTabStop);
+			}
+			else
+			{
+				// delete the tab stop; we can do that by simply applying the shown
+				// ruler - we have removed the tab stop from the shown ruler when the
+				// drag started
+				TextInfoBarOp::DoApplyShownRuler();
+			}
+		}
+
+#if 0
+		if (pDraggedGuideline != NULL)
+		{
+			if (IsMouseOverRuler())
+			{
+				UndoIDS = _R(IDS_OPDELETEGUIDELINE);
+				Success = DoDeleteGuideline(pDraggedGuideline);
+			}
+			else
+				Success = DoTranslateGuideline(pDraggedGuideline,Ordinate);
+		}
+		else
+			Success = !IsMouseOverRuler() && DoNewGuideline(NULL,NEXT,Type,Ordinate);
+#endif
+	}
+
+	// End the Drag
+	EndDrag();
+
+	// Restore cursor
+	if (CursorStackID != TABSTOPDRAG_CURSORID_UNSET)
+	{
+		CursorStack::GPop(CursorStackID);
+		CursorStackID = TABSTOPDRAG_CURSORID_UNSET;
+	}
+
+	if (m_pCursor != NULL)
+	{
+		delete m_pCursor;
+		m_pCursor = NULL;
+	}
+	if (m_pParam != NULL)
+	{
+		delete m_pParam;
+		m_pParam = NULL;
+	}
+	
+	End();
+}
+
+/***********************************************************************************************
+
+>	BOOL TabStopDragOp::IsMouseOverRuler()
+
+	Author:		Mark_Neves (Xara Group Ltd) <camelotdev@xxxxxxxx>
+	Created:	12/9/95
+	Returns:	TRUE if mouse is over a ruler, FALSE otherwise
+	Purpose:    Test to see where the mouse pointer is.
+				It will return TRUE if the mouse is over either ruler, or the origin gadget.
+	SeeAlso:	OpGuideline::IsMouseOverRuler() - copied from there
+		
+***********************************************************************************************/
+
+BOOL TabStopDragOp::IsMouseOverRuler()
+{
+	DocView* pDocView = DocView::GetSelected();
+	if (pDocView != NULL)
+	{
+		CCamView* pCCamView = pDocView->GetConnectionToOilView();
+		return (pCCamView->IsMouseOverRuler() != OVER_NO_RULERS);
+	}
+
+	return FALSE;
+}
Index: Trunk/XaraLX/tools/texttool.cpp
===================================================================
--- Trunk/XaraLX/tools/texttool.cpp	(revision 1445)
+++ Trunk/XaraLX/tools/texttool.cpp	(revision 1446)
@@ -139,6 +139,7 @@
 #include "layer.h"
 #include "nodepostpro.h"
 #include "nodepath.h"
+#include "usercord.h"
 
 DECLARE_SOURCE( "$Revision$" );
 
@@ -969,10 +970,63 @@
 	}
 }
 
+void TextTool::GetRulerOrigin(Spread* pSpread, UserCoord *pOrigin)
+{
+	TRACEUSER("wuerthne", _T("GetRulerOrigin"));
+	if (TextInfoBarOp::IsRulerOriginClaimed())
+	{
+		TRACEUSER("wuerthne", _T("set origin"));
+		pOrigin->x = TextInfoBarOp::GetRulerOrigin();
+	}
+}
 
+void TextTool::RenderRulerBlobs(RulerBase* pRuler, UserRect& UpdateRect, BOOL IsBackground)
+{
+	// we only draw onto the horizontal ruler and only if we have claimed it
+	if (!TextInfoBarOp::IsRulerOriginClaimed()) return;
+	TRACEUSER("wuerthne", _T("RenderRulerBlobs"));
+	// draw the highlighted background or the blobs
+	if (IsBackground)
+	{
+		TextInfoBarOp::HighlightRulerSection(pRuler, UpdateRect);
+	}
+	else
+	{
+		TextInfoBarOp::RenderRulerBlobs(pRuler, UpdateRect);
+	}
+}
 
 /********************************************************************************************
 
+>   BOOL TextTool::OnRulerClick(UINT32 nFlags, UserCoord PointerPos, ClickType Click, ClickModifiers Mods,
+								Spread* pSpread, RulerBase* pRuler)
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	07/07/06
+	Inputs:     nFlags      - synthesized mouse event flags (to be passed through to StartToolDrag)
+				PointerPos	- user coordinates of click on ruler (relative to origin set by tool)
+				Click		- Type of click enum
+				Mods		- Modifier flags struct
+				pSpread		- pointer to spread upon which click occurred
+				pRuler		- pointer to ruler which generated click
+	Returns:    TRUE to claim the click
+	Purpose:	Called when the user has clicked on the ruler and we are the current tool
+
+********************************************************************************************/
+
+BOOL TextTool::OnRulerClick(UINT32 nFlags, UserCoord PointerPos, ClickType Click, ClickModifiers Mods,
+							Spread* pSpread, RulerBase* pRuler)
+{
+	if (!TextInfoBarOp::IsRulerOriginClaimed()) return FALSE;
+
+	TRACEUSER("wuerthne", _T("TextTool OnRulerClick %d at %d"), Click, PointerPos.x);
+	return TextInfoBarOp::OnRulerClick(nFlags, PointerPos, Click, Mods, pSpread, pRuler);
+}
+
+
+
+/********************************************************************************************
+
 	>	BOOL TextTool::OnKeyPress(KeyPress* pKeyPress)
 
 	Author:		Peter_Arnold (Xara Group Ltd) <camelotdev@xxxxxxxx>
@@ -1403,6 +1457,17 @@
 				UsedTheKeypress = TRUE;
 			}
 			break;
+		case CAMKEY(TAB):
+			if (!pKeyPress->IsAlternative() && !pKeyPress->IsConstrain())
+			{
+				/*##*/
+				OpTextFormat* pOp = new OpTextFormat();
+				if (pOp != NULL)
+					Errored = !pOp->DoTab();
+				else
+				UsedTheKeypress = TRUE;
+			}
+			break;
 		case CAMKEY(ESCAPE):
 			// Deselect the caret and select the text story
 			if (!pKeyPress->IsAlternative() && !pKeyPress->IsConstrain())
@@ -2264,6 +2329,9 @@
 	// Clear the focus, remembering what it was for possible later reselection
 	pLastFocusStory = TextStory::GetFocusStory();
 	TextStory::SetFocusStory(NULL);
+
+	// make sure the ruler is redrawn if we had claimed it
+	TextInfoBarOp::ReleaseRuler();
 	CurrentTool = FALSE;
 
 	return ok;
Index: Trunk/XaraLX/tools/textinfo.h
===================================================================
--- Trunk/XaraLX/tools/textinfo.h	(revision 1445)
+++ Trunk/XaraLX/tools/textinfo.h	(revision 1446)
@@ -102,20 +102,23 @@
 
 //#include "bars.h" - in camtypes.h [AUTOMATICALLY REMOVED]
 #include "fontbase.h"
+#include "usercord.h"
 
 class TextTool;
 class FontDropDown;
 
 
 enum FontAttribute{JustifyA,BoldA,ItalicA,UnderLineA,AspectRatioA,FontSizeA,FontNameA,BaseLineShiftA,HorizontalKernA,
-						TrackingA,ScriptA,LineSpaceA,LineSpacePercentA,AutoKernText}; // AutoKernText is not an attribute
+				   TrackingA,ScriptA,LineSpaceA,LineSpacePercentA,
+				   LeftMarginA, RightMarginA, FirstIndentA, RulerA,
+				   AutoKernText}; // AutoKernText is not an attribute
 enum JustifyMode {JustifyLeft,JustifyRight,JustifyCentre,JustifyFull};
 enum ScriptModes {NormalScript,SuperScript,SubScript};
 		
 
 /********************************************************************************************
 
->	class InfoBarData :
+>	class TextInfoBarData :
 
 	Author:		Chris_Parks (Xara Group Ltd) <camelotdev@xxxxxxxx>
 	Created:	22/5/94
@@ -152,6 +155,49 @@
 
 /********************************************************************************************
 
+>	class TextRulerBarData:
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	07/07/06
+	Purpose:	Keep the current ruler bar display state
+
+********************************************************************************************/
+
+class TextRulerBarData : public CCObject
+{
+	CC_DECLARE_DYNCREATE(TextRulerBarData)
+
+public:
+	TextRulerBarData(): CurrentTabType(LeftTab), IsLeftMarginValid(FALSE), IsFirstIndentValid(FALSE),
+        IsRightMarginValid(FALSE), IsRulerValid(FALSE), IsRulerOriginClaimed(FALSE), pNewRuler(NULL),
+		TabStopDragRunning(FALSE)
+		{ pShownRuler = new TxtRuler; }
+	~TextRulerBarData() { if (pShownRuler) delete pShownRuler; }
+
+	TxtTabType CurrentTabType;          // currently chosen tab type (click creates tab of this kind)
+
+	BOOL IsLeftMarginValid:1;
+	BOOL IsFirstIndentValid:1;
+	BOOL IsRightMarginValid:1;
+	BOOL IsRulerValid:1;
+	BOOL IsRulerOriginClaimed:1;
+
+	MILLIPOINT LeftMargin;
+	MILLIPOINT FirstIndent;
+	MILLIPOINT RightMargin;
+	TxtRuler* pShownRuler;              // the currently shown ruler - this is a copy that is
+										// owned by this object!
+
+	AttrTxtRuler *pNewRuler;            // the new ruler attribute to be applied - this is just
+										// transient so need not be destroyed in the destructor
+
+	INT32 CurrentRulerOrigin;           // origin of our ruler section (in user space)
+	INT32 CurrentRulerSectionWidth;     // width of our ruler section (-1 for infinite)
+	BOOL TabStopDragRunning;
+};
+
+/********************************************************************************************
+
 >	class FontDataItem : public ListItem
 
 	Author:		Chris_Parks (Xara Group Ltd) <camelotdev@xxxxxxxx>
@@ -192,6 +238,18 @@
 
 /********************************************************************************************
 
+>	enum TabStopDragType
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	13/07/06
+	Purpose:	Different types of drag operations on the ruler bar
+
+********************************************************************************************/
+
+enum TabStopDragType { DragNew, DragTabStop, DragLeftMargin, DragRightMargin, DragFirstIndent };
+
+/********************************************************************************************
+
 >	class TextInfoBarOp : public InformationBarOp
 
 	Author:		Chris_Parks (Xara Group Ltd) <camelotdev@xxxxxxxx>
@@ -219,6 +277,8 @@
 
 	void InitControls();
   	static BOOL Update(BOOL DoUpdate = FALSE);
+	static BOOL UpdateRulerBar(SelRange* pSelection, BOOL DoUpdate = FALSE);
+
 	//static void GetTMetrics(TEXTMETRIC * Metrics, LOGFONT* pLogFont = NULL);
 	static void OnFieldChange(FontAttribute ThisChange);
 	static void AddFontToCombo(String_64 * FontName);
@@ -232,7 +292,6 @@
 	
 	static void DoFontChange();
 
-
 	static BOOL SetCurrentPointSize(MILLIPOINT PointSize);
 	static BOOL SetCurrentAspectRatio(FIXED16 Ratio);
 	static BOOL SetCurrentTracking(INT32 Tracking);
@@ -250,6 +309,21 @@
 	static void SetCurrentScript(ScriptModes Script);
 	static void EnableGadgets(BOOL Enable);
 
+	static inline BOOL IsRulerOriginClaimed() { return RulerData.IsRulerOriginClaimed; }
+	static inline INT32 GetRulerOrigin() { return RulerData.CurrentRulerOrigin; }
+	static void ReleaseRuler();
+	static void HighlightRulerSection(RulerBase* pRuler, UserRect& UpdateRect);
+	static void RenderRulerBlobs(RulerBase* pRuler, UserRect& UpdateRect);
+	static BOOL OnRulerClick(UINT32 nFlags, UserCoord PointerPos, ClickType Click, ClickModifiers Mods,
+							 Spread* pSpread, RulerBase* pRuler);
+
+	static void ForceRulerRedraw();
+	static void TabStopDragStarting(TabStopDragType);
+	static void TabStopDragFinished();
+	static void DoAddTabStop(MILLIPOINT Position);
+	static void DoAddTabStop(TxtTabStop NewTabStop);
+	static void DoApplyShownRuler();
+
 public:
 // the current infobar object - allow static member access
 	static InformationBarOp * pTextInfoBar;
@@ -330,7 +404,10 @@
 
 private:
 	static TextInfoBarData InfoData;
+	static TextRulerBarData RulerData;
 
+	static BOOL FindBitmapSize(ResourceID ID, UINT32* pWidth, UINT32* pHeight);
+
 	// static ENUMLOGFONT 	TempLogFont;
 	static void UpdateButtonStates();
 	static void UpdateJustifyButtons(BOOL Clear = FALSE);
@@ -340,6 +417,8 @@
 	static void SetLineSpaceGadget();
 	static void DoInputError(UINT32 GadgetID);
 
+	static INT32 GetLogicalStoryWidth(TextStory* pStory);
+
 	// static BOOL AddFontToCache(String_64 * FontName,WORD Handle);
 	// static INT32	AddTrueTypeFontsToList();
 	// static INT32	AddTypeOneFontsToList();
@@ -353,6 +432,11 @@
 	// We only need to build this set once during the lifetime of the app.
 	static CommonAttrSet CommonAttrsToFindSet; 
 
+	// cached bitmap sizes
+	static UINT32 TabBitmapWidth;
+	static UINT32 TabBitmapHeight;
+	static UINT32 CurrentTabButtonWidth;
+	static UINT32 CurrentTabButtonHeight;
 };
 	
 
@@ -400,5 +484,63 @@
 
 };
 
+/********************************************************************************************
 
+>	class TabStopDragOpParam: public OpParam
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	12/07/06
+	Purpose:	Parameters for TabStopDragOp operation class
+
+********************************************************************************************/
+
+class TabStopDragOpParam: public OpParam
+{
+public:
+	TabStopDragOpParam(TabStopDragType Type, TxtTabStop DraggedTabStop, UserCoord Pos):
+		m_DragType(Type), m_DraggedTabStop(DraggedTabStop), m_StartPos(Pos) {}
+	TabStopDragType m_DragType;
+	TxtTabStop      m_DraggedTabStop;   // only for Type == DragTabStop
+	UserCoord       m_StartPos;
+};
+
+#define OPTOKEN_TABSTOPDRAG _T("TabStopDrag")
+
+/********************************************************************************************
+
+>	class TabStopDragOp: public Operation
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	12/07/06
+	Purpose:	Operation to handle tab stop dragging
+
+********************************************************************************************/
+
+class TabStopDragOp: public Operation
+{
+	CC_DECLARE_DYNCREATE(TabStopDragOp)
+public:
+	TabStopDragOp(): m_pCursor(NULL), m_pParam(NULL) {}
+	~TabStopDragOp() { if (m_pCursor) delete m_pCursor; if (m_pParam) delete m_pParam; }
+
+	static BOOL Init();
+	static OpState GetState(String_256* Description, OpDescriptor*);
+
+	// The main entry point
+	void DoWithParam(OpDescriptor *pOpDesc, OpParam* pParam);
+	virtual void DragFinished(	DocCoord PointerPos, 
+								ClickModifiers ClickMods, Spread*, 
+								BOOL Success, BOOL bSolidDrag);
+
+private:
+	void DoDrag(Spread* pThisSpread);
+	static BOOL IsMouseOverRuler();
+
+	Spread*    pSpread;
+	MILLIPOINT Ordinate;
+	INT32	   CursorStackID;
+	Cursor*	   m_pCursor;
+	TabStopDragOpParam* m_pParam;
+};
+
 #endif 		// INC_TEXTINFO
Index: Trunk/XaraLX/tools/textops.cpp
===================================================================
--- Trunk/XaraLX/tools/textops.cpp	(revision 1445)
+++ Trunk/XaraLX/tools/textops.cpp	(revision 1446)
@@ -962,7 +962,61 @@
 	return ok;
 }
 
+BOOL OpTextFormat::DoTab()
+{
+	UndoableOperation* pUndoOp = this;
 
+	// Get pointers to focus story, caret, caret line and caret line EOL
+	TextStory* pStory = TextStory::GetFocusStory();
+	ERROR2IF(pStory==NULL, FALSE, "OpTextFormat::DoTab() - No focus story");
+	CaretNode* pCaret = pStory->GetCaret();
+	ERROR2IF(pCaret==NULL, FALSE, "OpTextFormat::DoTab() - Focus story had no caret");
+	TextLine* pCaretLine = pCaret->FindParentLine();
+	ERROR2IF(pCaretLine==NULL,FALSE,"OpTextFormat::DoTab() - caret has no parent");
+
+	// start the text op (must be done before AllopOp() which may insert actions)
+	BOOL ok = DoStartTextOp(pStory);
+
+	// see if the op is allowed
+	ObjChangeParam ObjParam(OBJCHANGE_STARTING, ObjChangeFlags(), NULL, pUndoOp);
+	if (pCaret->AllowOp(&ObjParam)==FALSE)
+		return TRUE;
+
+	// delete any sub-selection if it exists, then get ptr to last VTN on caret line
+	if (ok) ok = DoDeleteSelection(pStory, TRUE);
+
+	// After deleting the selection, the caret line may have been deleted
+	// so we need to get it again in it's new location
+	pCaret = pStory->GetCaret();
+	ERROR2IF(pCaret==NULL, FALSE, "OpTextFormat::DoTab() - Focus story had no caret");
+	pCaretLine = pCaret->FindParentLine();
+	ERROR2IF(pCaretLine==NULL,FALSE,"OpTextFormat::DoTab() - caret has no parent");
+
+	VisibleTextNode* pLastCaretLineVTN = pCaretLine->FindLastVTN();
+	ERROR2IF(pLastCaretLineVTN==NULL,FALSE,"OpTextFormat::DoTab() - caret line has no VTN");
+
+	// insert a new Tab node before caret with caret's attributes
+	HorizontalTab* pTab = NULL;
+	if (ok) pTab = new HorizontalTab;
+	if (ok) ok = (pTab != NULL);
+	if (ok) ok = pTab->DoInsertNewNode(pUndoOp,pCaret,PREV);
+	if (ok) ok = pCaret->DoApplyAttrsTo(pUndoOp,pTab);
+
+	// update other textstories that are dependant on this one
+	//##SliceHelper::OnTextStoryChanged(pStory, this, &ObjParam, MasterText);
+
+	// Update all the changed nodes
+	ObjChangeParam ObjChange(OBJCHANGE_FINISHED,ObjChangeFlags(),NULL,pUndoOp);
+	if (ok) ok = UpdateChangedNodes(&ObjChange);
+
+	if (ok) pCaret->ScrollToShow();
+
+	if (!ok) FailAndExecute();
+	End();
+
+	return ok;
+}
+
 /********************************************************************************************
 >	void OpTextFormat::DoSwapCase()
 
@@ -1877,15 +1931,19 @@
 		pObject = pStory->GetTextPath();
 		if (pObject != NULL)
 		{
-			// set story width to path length, this is the simplest way
+			// set story width to path length (minus physical indents), this is the simplest way
 			// BODGE WORDWRAP - does this need to account for x scaling due to matrix?
 			TextStoryInfo StoryInfo;
 			if (ok) ok = pStory->CreateUntransformedPath(&StoryInfo);
-			if (ok) delete StoryInfo.pPath;
-			if (ok) pStory->SetStoryWidth(StoryInfo.PathLength);
+			if (ok)
+			{
+				delete StoryInfo.pPath;
+				if (StoryInfo.WordWrapping)
+					pStory->SetStoryWidth(StoryInfo.PathLength - StoryInfo.LeftPathIndent - StoryInfo.RightPathIndent);
 
-			// Move path outside text object (but not outside any controllers that may be applied)
-			if (ok)	ok = DoMoveNode(pObject, pStory, NEXT);
+				// Move path outside text object (but not outside any controllers that may be applied)
+				ok = DoMoveNode(pObject, pStory, NEXT);
+			}
 
 			if (ok) pObject->SetSelected(FALSE);
 			if (ok) pStory->SetSelected(TRUE);
Index: Trunk/XaraLX/tools/texttool.h
===================================================================
--- Trunk/XaraLX/tools/texttool.h	(revision 1445)
+++ Trunk/XaraLX/tools/texttool.h	(revision 1446)
@@ -208,6 +208,14 @@
 	void OnClick( DocCoord, ClickType, ClickModifiers, Spread* );
 	void OnMouseMove(DocCoord PointerPos,Spread* pSpread, ClickModifiers ClickMods);
 	void RenderToolBlobs(Spread *, DocRect*);
+
+	// new event handles for handling tab stops on the page ruler
+	void GetRulerOrigin(Spread*,UserCoord*);
+	void RenderRulerBlobs(RulerBase* pRuler, UserRect& UpdateRect, BOOL IsBackground);
+	BOOL OnRulerClick( UINT32 nFlags, UserCoord PointerPos, ClickType Click, ClickModifiers Mods,
+					   Spread* pSpread, RulerBase* pRuler);
+
+
 	BOOL OnKeyPress(KeyPress* pKeyPress);
 	BOOL GetStatusLineText(String_256* ptext, Spread* pSpread, DocCoord DocPos, ClickModifiers ClickMods);
 	BOOL OnIdle();


Xara