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

[XaraXtreme-commits] Commit Complete



Commit by  : martin
Repository : xara
Revision   : 1718
Date       : Fri Aug 25 12:06:07 BST 2006

Changed paths:
   M /Trunk/XaraLX/Kernel/docview.cpp
   M /Trunk/XaraLX/Kernel/docview.h
   M /Trunk/XaraLX/Kernel/snap.h
   M /Trunk/XaraLX/tools/textinfo.cpp
   M /Trunk/XaraLX/tools/textinfo.h

allow first indent to become negative (for hanging bullets); draw first
indent on top of "current tab" button to avoid it being hidden; allow
first indent to be dragged even if on top of "current tab" button;
enable guideline snapping for tab stops and margins; use vertical mouse
position to distinguish between tab stop and first indent/right margin
in case they are at the same position


Diff:
Index: Trunk/XaraLX/tools/textinfo.cpp
===================================================================
--- Trunk/XaraLX/tools/textinfo.cpp	(revision 1717)
+++ Trunk/XaraLX/tools/textinfo.cpp	(revision 1718)
@@ -197,6 +197,7 @@
 														// that we need to find common attributes for
 // cached bitmap sizes
 UINT32 TextInfoBarOp::TabBitmapWidth;
+UINT32 TextInfoBarOp::TabBitmapHeight;
 UINT32 TextInfoBarOp::CurrentTabButtonWidth;
 UINT32 TextInfoBarOp::LeftMarginBitmapWidth;
 UINT32 TextInfoBarOp::LeftMarginBitmapHeight;
@@ -441,7 +442,7 @@
 	if (ok) ok = FindBitmapSize(_R(clefttab), &CurrentTabButtonWidth, &Dummy);
 	// 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, &Dummy);
+	if (ok) ok = FindBitmapSize(_R(lefttab), &TabBitmapWidth, &TabBitmapHeight);
 	// find out about the width and height of the first indent blob
 	if (ok) ok = FindBitmapSize(_R(leftmar), &LeftMarginBitmapWidth, &LeftMarginBitmapHeight);
 	// find out about the width of the left/right margin blobs
@@ -758,7 +759,11 @@
 	}
 	
 	if (Attrib)
-		AttributeManager::AttributeSelected(Attrib,NULL); 		 
+	{
+		TRACEUSER("wuerthne", _T("calling AttributeSelected"));
+		AttributeManager::AttributeSelected(Attrib,NULL);
+		TRACEUSER("wuerthne", _T("called AttributeSelected"));
+	}
 	// make sure the infobar reflects the current attributes
 	switch (ThisChange)
 	{
@@ -3078,6 +3083,33 @@
 {
 	if (!pRuler->IsHorizontal()) return;
 
+	// draw the current tab "button" - we draw this first so the first line indent, if that far left,
+	// is drawn on top of it
+	// we want the button to appear to the left of the origin, but at a fixed absolute 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=0;
+	switch(RulerData.CurrentTabType)
+	{
+		case RightTab:
+			id = _R(crighttab);
+			break;
+		case CentreTab:
+			id = _R(ccenttab);
+			break;
+		case DecimalTab:
+			id = _R(cdectab);
+			break;
+		case LeftTab:
+			id = _R(clefttab);
+			break;
+	}
+	pRuler->DrawBitmap(Pos, id);
+
 	if (!(RulerData.TabStopDragRunning && RulerData.CurrentDragType == DragLeftMargin))
 	{
 		// not currently dragging the left margin, so display it if it is the same across
@@ -3149,33 +3181,6 @@
 			}
 		}
 	}
-
-	// 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=0;
-	switch(RulerData.CurrentTabType)
-	{
-		case RightTab:
-			id = _R(crighttab);
-			break;
-		case CentreTab:
-			id = _R(ccenttab);
-			break;
-		case DecimalTab:
-			id = _R(cdectab);
-			break;
-		case LeftTab:
-			id = _R(clefttab);
-			break;
-	}
-	pRuler->DrawBitmap(Pos, id);
 }
 
 /********************************************************************************************
@@ -3246,15 +3251,30 @@
 		&& (RulerData.CurrentRulerSectionWidth == -1
 			|| PointerPos.x < RulerData.CurrentRulerSectionWidth);
 
-	if (Click == CLICKTYPE_SINGLE)
+	DocView* pDocView = pRuler->GetpRulerPair()->GetpDocView();
+	MILLIPOINT PixelSize = pDocView->GetScaledPixelWidth().MakeLong();
+
+	BOOL bIsOverTabButton = PointerPos.x >= MILLIPOINT((CurrentTabButtonPos - CurrentTabButtonWidth/2)*PixelSize)
+							&& PointerPos.x <= MILLIPOINT((CurrentTabButtonPos + CurrentTabButtonWidth/2)*PixelSize);
+	// treat a double click just as another single click
+	if (Click == CLICKTYPE_SINGLE || Click == CLICKTYPE_DOUBLE)
 	{
 		// 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)
+		MILLIPOINT LeftMarginPos = RulerData.IsLeftMarginValid ? RulerData.LeftMargin : 0;
+		MILLIPOINT FirstIndentPos = RulerData.IsFirstIndentValid ? RulerData.FirstIndent : 0;
+		MILLIPOINT RightMarginPos = RulerData.CurrentRulerSectionWidth
+											- (RulerData.IsRightMarginValid ? RulerData.RightMargin : 0);
+		BOOL bIsOverFirstIndent = PointerPos.x >= FirstIndentPos - MILLIPOINT(LeftMarginBitmapWidth / 2 * PixelSize)
+								  && PointerPos.x <= FirstIndentPos + MILLIPOINT(LeftMarginBitmapWidth / 2 * PixelSize);
+		BOOL bIsOverRightMargin = PointerPos.x >= RightMarginPos - MILLIPOINT(RightMarginBitmapWidth / 2 * PixelSize)
+								  && PointerPos.x <= RightMarginPos + MILLIPOINT(RightMarginBitmapWidth / 2 * PixelSize);
+
+		// check whether the user has clicked on our homegrown "current tab" button
+		// this is displayed centered around the position CurrentTabButtonPos (in pixels)
+		// NB: we need to make sure that we can still drag the first line indent in case it overlaps, but even
+		//     in that case, we still treat clicks near the bottom of the ruler as "current tab" button clicks
+		if (bIsOverTabButton && (!bIsOverFirstIndent || PointerPos.y <= MILLIPOINT(TabBitmapHeight * PixelSize)))
 		{
 			// a click on our "current tab" button
 			if (Mods.Adjust)
@@ -3317,8 +3337,14 @@
 			for (TxtTabStopIterator it = RulerData.pShownRuler->begin(); it != RulerData.pShownRuler->end(); ++it)
 			{
 				MILLIPOINT Pos = (*it).GetPosition();
+				// we check for the first indent marker being at the same position as the tab - if it is, the
+				// vertical position is checked to distinguish between the two. We do not generally check the
+				// vertical position because that would mean that dragging slightly too high would create a
+				// new tab rather than moving the existing tab.
 				if (PointerPos.x >= Pos - MILLIPOINT(TabBitmapWidth / 2 * PixelSize)
-					&& PointerPos.x <= Pos + MILLIPOINT(TabBitmapWidth / 2 * PixelSize))
+					&& PointerPos.x <= Pos + MILLIPOINT(TabBitmapWidth / 2 * PixelSize)
+					&& ((!bIsOverFirstIndent && !bIsOverRightMargin)
+						|| PointerPos.y <= MILLIPOINT(TabBitmapHeight * PixelSize)))
 				{
 					TRACEUSER("wuerthne", _T("hit tab no %d"), Index);
 					MatchedIndex = Index;
@@ -3349,10 +3375,6 @@
 		if (MatchedIndex == -1)
 		{
 			// no tab stop hit, but maybe the margin markers?
-			MILLIPOINT LeftMarginPos = RulerData.IsLeftMarginValid ? RulerData.LeftMargin : 0;
-			MILLIPOINT FirstIndentPos = RulerData.IsFirstIndentValid ? RulerData.FirstIndent : 0;
-			MILLIPOINT RightMarginPos = RulerData.CurrentRulerSectionWidth
-											- (RulerData.IsRightMarginValid ? RulerData.RightMargin : 0);
 
 			// We test for the left margin first because we check the vertical position to distinguish
 			// between left margin and first indent drags even when they are at the same position
@@ -3367,16 +3389,14 @@
 			{
 				// we do not check the vertical position for other margin drags - if there was a tab stop
 				// at the same position it got priority anyway
-				if (PointerPos.x >= FirstIndentPos - MILLIPOINT(LeftMarginBitmapWidth / 2 * PixelSize)
-					&& PointerPos.x <= FirstIndentPos + MILLIPOINT(LeftMarginBitmapWidth / 2 * PixelSize))
+				if (bIsOverFirstIndent)
 				{
 					TRACEUSER("wuerthne", _T("drag first indent"));
 					DragType = DragFirstIndent;
 				}
 				else
 				{
-					if (PointerPos.x >= RightMarginPos - MILLIPOINT(RightMarginBitmapWidth / 2 * PixelSize)
-						&& PointerPos.x <= RightMarginPos + MILLIPOINT(RightMarginBitmapWidth / 2 * PixelSize))
+					if (bIsOverRightMargin)
 					{
 						TRACEUSER("wuerthne", _T("drag right margin"));
 						DragType = DragRightMargin;
@@ -3408,9 +3428,9 @@
 			return TRUE;
 		}
 	}
-
-	// we swallow all other clicks within the highlight section
-	return InHighlightSection;
+	// we swallow all other clicks within the highlight section and over the tab button (most notably
+	// double clicks)
+	return InHighlightSection || bIsOverTabButton;
 }
 
 /********************************************************************************************
@@ -3535,10 +3555,8 @@
 
 void TextInfoBarOp::DoChangeFirstIndent(MILLIPOINT Ordinate)
 {
-	// Technically, we could allow the firstindent to become negative, i.e., to be located
-	// to the left of the start of the text object, but this might confuse users, so prevent
-	// it from happening.
-	if (Ordinate < 0) Ordinate = 0;
+	// We allow the firstindent to become negative so hanging bullets are made easy (they would
+	// otherwise require both the left margin and the first indent to be dragged)
 	RulerData.IsFirstIndentValid = TRUE;
 	RulerData.FirstIndent = Ordinate;
 	OnFieldChange(FirstIndentA);
@@ -3547,7 +3565,7 @@
 
 /********************************************************************************************
 
->	void TextInfoBarOp::DoChangeRightMargin(MILLIPOINT Ordinate)
+>	void TextInfoBarOp::DoChangeRightMargin(MILLIPOINT Ordinate, BOOL bReset)
 
 	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
 	Created:	18/07/06
@@ -3556,13 +3574,13 @@
 
 ********************************************************************************************/
 
-void TextInfoBarOp::DoChangeRightMargin(MILLIPOINT Ordinate)
+void TextInfoBarOp::DoChangeRightMargin(MILLIPOINT Ordinate, BOOL bReset)
 {
 	// the right margin is actually an indent, i.e., an offset from the column width (otherwise
 	// we could not have a default attribute that makes sense and text objects with different
 	// widths would have to have different right margin attributes)
 	RulerData.IsRightMarginValid = TRUE;
-	RulerData.RightMargin = RulerData.CurrentRulerSectionWidth - Ordinate;
+	RulerData.RightMargin = bReset ? 0 : (RulerData.CurrentRulerSectionWidth - Ordinate);
 	// Technically, we could allow the margin to become negative, i.e., to be located to the
 	// right of the column width, but this might confuse users, so prevent it from happening
 	if (RulerData.RightMargin < 0) RulerData.RightMargin = 0;
@@ -3981,6 +3999,55 @@
 
 /***********************************************************************************************
 
+>	DocCoord TabStopDragOp::SnapAndConvert(UserCoord* DragPos, Spread* pSpread, DocView* pDocView)
+
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	24/08/06
+	Inputs:		DragPos - mouse position (in tool coords, i.e., user coords with the tool
+				origin applied)
+				pSpread - the current spread
+				pDocView - the current doc view
+	Returns:	Snapped spread coordinates (ready for passing to the status line)
+	Purpose:    Snap the tool-specific coordinates and convert to doc coords
+
+***********************************************************************************************/
+
+DocCoord TabStopDragOp::SnapAndConvert(UserCoord* DragPos, Spread* pSpread, DocView* pDocView)
+{
+	// Snapping is tricky because when snapping to the grid we do not want to snap the
+	// absolute mouse position but instead our text ruler-specific coordinates, so that
+	// for instance, a 1mm grid allows us to place tab stops at exact multiples of 1mm,
+	// irrespective of where the text object happens to be located (it need not be at
+	// an exact multiple of 1mm). When snapping to a guideline, however, we need to snap
+	// the absolute position.
+
+	// So, first try guideline snapping
+	UserCoord AbsoluteDragPos = *DragPos;
+	AbsoluteDragPos.x += TextInfoBarOp::GetRulerOrigin();
+	DocCoord VirtualSpreadCoords = AbsoluteDragPos.ToSpread(pSpread);
+	BOOL Snapped = FALSE;
+	if (pDocView->GetSnapToGuidesState())
+		Snapped = pDocView->ForceSnapToGuides(pSpread, &VirtualSpreadCoords, GUIDELINE_VERT);
+	DocCoord PointerPos = VirtualSpreadCoords;
+	if (!Snapped)
+	{
+		// try grid snapping
+		// NB - to fool the snapping code we transform our user coords with origin applied
+		//      to spread coordinates and then let it snap, then we apply the tool origin
+		//      in the opposite direction, i.e., the origin shift is only applied for the
+		//      snapping operation and we end up with normal spread coordinates that can
+		//      be passed to the status line
+		DocCoord VirtualSpreadCoords = DragPos->ToSpread(pSpread);
+		if (pDocView->GetSnapToGridState())
+			pDocView->ForceSnapToGrid(pSpread, &VirtualSpreadCoords);
+		PointerPos = VirtualSpreadCoords;
+		PointerPos.x += TextInfoBarOp::GetRulerOrigin();
+	}
+	return PointerPos;
+}
+
+/***********************************************************************************************
+
 >	void TabStopDragOp::UpdateStatusLineAndPos(UserCoord* DragPos, Spread* pSpread)
 
 	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
@@ -3994,17 +4061,7 @@
 void TabStopDragOp::UpdateStatusLineAndPos(UserCoord* DragPos, Spread* pSpread)
 {
 	DocView *pDocView = DocView::GetCurrent();
-	// NB - to fool the snapping code we transform our user coords with origin applied
-	//      to spread coordinates and then let it snap, then we apply the tool origin
-	//      in the opposite direction, i.e., the origin shift is only applied for the
-	//      snapping operation and we end up with normal spread coordinates that can
-	//      be passed to the status line
-	DocCoord VirtualSpreadCoords = DragPos->ToSpread(pSpread);
-	if (pDocView->GetSnapToGridState())
-		pDocView->ForceSnapToGrid(pSpread, &VirtualSpreadCoords);
-	DocCoord PointerPos = VirtualSpreadCoords;
-	PointerPos.x += TextInfoBarOp::GetRulerOrigin();
-
+	DocCoord PointerPos = SnapAndConvert(DragPos, pSpread, pDocView);
 	StatusLine* pStatusLine=GetApplication()->GetpStatusLine();
 	if (pStatusLine)
 	{
@@ -4150,11 +4207,10 @@
 		// we need to snap in tool space, i.e., text ruler space
 		UserCoord UserPos = PointerPos.ToUser(pSpread);
 		UserPos.x -= TextInfoBarOp::GetRulerOrigin();
-		DocCoord VirtualSpreadCoords = UserPos.ToSpread(pSpread);
-		if (pDocView->GetSnapToGridState())
-			pDocView->ForceSnapToGrid(pSpread, &VirtualSpreadCoords);
-		UserPos = VirtualSpreadCoords.ToUser(pSpread);
-		Ordinate = UserPos.x;
+		DocCoord SnappedPointerPos = SnapAndConvert(&UserPos, pSpread, pDocView);
+		// the returned coordinate is in spread coords
+		UserPos = SnappedPointerPos.ToUser(pSpread);
+		Ordinate = UserPos.x - TextInfoBarOp::GetRulerOrigin();
 		TRACEUSER("wuerthne", _T("with success at %d"), Ordinate);
 		
 		switch(m_pParam->DragType)
@@ -4183,13 +4239,18 @@
 				}
 				break;
 			case DragLeftMargin:
+				// dragged off the ruler means reset to 0
+				if (!IsMouseOverRuler()) Ordinate = 0;
 				TextInfoBarOp::DoChangeLeftMargin(Ordinate);
 				break;
 			case DragFirstIndent:
+				// dragged off the ruler means reset to 0
+				if (!IsMouseOverRuler()) Ordinate = 0;
 				TextInfoBarOp::DoChangeFirstIndent(Ordinate);
 				break;
 			case DragRightMargin:
-				TextInfoBarOp::DoChangeRightMargin(Ordinate);
+				// dragged off the ruler means reset the right margin to the physical width of the object
+				TextInfoBarOp::DoChangeRightMargin(Ordinate, !IsMouseOverRuler());
 				break;
 		}
 	}
Index: Trunk/XaraLX/tools/textinfo.h
===================================================================
--- Trunk/XaraLX/tools/textinfo.h	(revision 1717)
+++ Trunk/XaraLX/tools/textinfo.h	(revision 1718)
@@ -327,7 +327,7 @@
 	static void DoAddTabStop(TxtTabStop NewTabStop);
 	static void DoApplyShownRuler();
 	static void DoChangeLeftMargin(MILLIPOINT Ordinate);
-	static void DoChangeRightMargin(MILLIPOINT Ordinate);
+	static void DoChangeRightMargin(MILLIPOINT Ordinate, BOOL bReset);
 	static void DoChangeFirstIndent(MILLIPOINT Ordinate);
 
 public:
@@ -441,6 +441,7 @@
 	// cached bitmap sizes
 	static UINT32 CurrentTabButtonWidth;
 	static UINT32 TabBitmapWidth;
+	static UINT32 TabBitmapHeight;
 	static UINT32 LeftMarginBitmapWidth;
 	static UINT32 LeftMarginBitmapHeight;
 	static UINT32 RightMarginBitmapWidth;
@@ -545,6 +546,7 @@
 private:
 	void DoDrag(Spread* pThisSpread);
 	static BOOL IsMouseOverRuler();
+	static DocCoord SnapAndConvert(UserCoord* DragPos, Spread* pSpread, DocView* pDocView);
 
 	Spread*    pSpread;
 	MILLIPOINT Ordinate;
Index: Trunk/XaraLX/Kernel/docview.cpp
===================================================================
--- Trunk/XaraLX/Kernel/docview.cpp	(revision 1717)
+++ Trunk/XaraLX/Kernel/docview.cpp	(revision 1718)
@@ -4340,6 +4340,45 @@
 
 /********************************************************************************************
 
+>	static BOOL DocView::ForceSnapToGuides(Spread* pSpread, DocCoord* pDocCoord, GuidelineType Type)
+ 
+	Author:		Martin Wuerthner <xara@xxxxxxxxxxxxxxx>
+	Created:	24/08/06
+	Inputs:		pSpread		Pointer to spread on which node exists
+				pDocCoord 	A coordinate to test magnetically against the node
+	Returns:	TRUE	- The coord has been snapped to something
+				FALSE	- The coord is untouched by man and beast
+	Purpose:	Calls guideline snapper ignoring enabling flags.
+	Errors:		Will error in debug builds if there is no selected DocView
+	SeeAlso:	All Snap member functions of all classes derived from NodeRenderableBounded.
+
+********************************************************************************************/
+
+BOOL DocView::ForceSnapToGuides(Spread* pSpread, DocCoord* pDocCoord, GuidelineType Type)
+{
+	ERROR3IF(Selected == NULL,"DocView::ForceSnapToGuides called when no selected DocView");
+
+	if (Selected != NULL)
+	{
+		BOOL Snapped = FALSE;
+
+		if (Selected->pCSnap == NULL)
+			Selected->pCSnap = new CSnap(Selected,pSpread);
+
+		if (Selected->pCSnap != NULL)
+		{
+			Selected->pCSnap->SetSpread(pSpread);
+			Snapped = Selected->pCSnap->SnapToGuidelines(pDocCoord, Type);
+		}
+
+		return (Snapped);
+	}
+	else
+		return (FALSE);
+}
+
+/********************************************************************************************
+
 >	BOOL DocView::SnapSelected(Spread* pSpread,DocCoord* pDocCoord,
 								BOOL TryMagSnap = TRUE,
 								BOOL TryGridSnap = TRUE)
Index: Trunk/XaraLX/Kernel/docview.h
===================================================================
--- Trunk/XaraLX/Kernel/docview.h	(revision 1717)
+++ Trunk/XaraLX/Kernel/docview.h	(revision 1718)
@@ -120,6 +120,7 @@
 #include "docvmsg.h"
 #include "dragtrgt.h"
 #include "cursor.h"
+#include "snap.h"
 
 #define OPTOKEN_TOGGLESOLIDDRAG		TEXT("ToggleSolidDrag")
 
@@ -527,6 +528,7 @@
 
 	static BOOL SnapToMagneticNode(Spread* pSpread, Node* pNode, DocCoord* pDocCoord);
 	static BOOL ForceSnapToGrid(Spread* pSpread, DocCoord* pDocCoord);
+	static BOOL ForceSnapToGuides(Spread* pSpread, DocCoord* pDocCoord, GuidelineType Type);
 
 	// Snap state functions.
 	BOOL GetSnapToGridState();
Index: Trunk/XaraLX/Kernel/snap.h
===================================================================
--- Trunk/XaraLX/Kernel/snap.h	(revision 1717)
+++ Trunk/XaraLX/Kernel/snap.h	(revision 1718)
@@ -135,6 +135,7 @@
 	BOOL TryToSnapToObject(Node* pNode, DocCoord* pDocCoord);
 
 	BOOL SnapToGrids(DocCoord* pDocCoord);
+	BOOL SnapToGuidelines(DocCoord* pDocCoord,GuidelineType Type = GUIDELINE_NOTYPE);
 
 	void SetSpread(Spread* pThisSpread) { pSpread = pThisSpread; }
 
@@ -155,7 +156,6 @@
 	BOOL SnapToObjects(DocRect* pDocRect, const DocCoord& PrevCoord, const DocCoord& CurCoord, BOOL Magnetic,BOOL GuideLayersOnly);
 
 	// Internal snap-to-guidelines funcs
-	BOOL SnapToGuidelines(DocCoord* pDocCoord,GuidelineType Type = GUIDELINE_NOTYPE);
 	BOOL SnapToGuidelines(DocRect* pDocRect,const DocCoord& PrevCoord,const DocCoord& CurCoord);
 	BOOL SnapToGuidesInLayer(Layer* pLayer,DocCoord* pDocCoord,GuidelineType Type);
 


Xara