1 // TortoiseSVN - a Windows shell extension for easy version control
\r
3 // Copyright (C) 2003-2009 - TortoiseSVN
\r
5 // This program is free software; you can redistribute it and/or
\r
6 // modify it under the terms of the GNU General Public License
\r
7 // as published by the Free Software Foundation; either version 2
\r
8 // of the License, or (at your option) any later version.
\r
10 // This program is distributed in the hope that it will be useful,
\r
11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
13 // GNU General Public License for more details.
\r
15 // You should have received a copy of the GNU General Public License
\r
16 // along with this program; if not, write to the Free Software Foundation,
\r
17 // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
\r
20 #include "StandardLayout.h"
\r
22 #include "VisibleGraph.h"
\r
24 #include "StandardLayoutRectList.h"
\r
25 #include "StandardLayoutNodeList.h"
\r
26 #include "StandardLayoutConnectionList.h"
\r
27 #include "StandardLayoutTextList.h"
\r
31 CStandardLayoutNodeInfo::CStandardLayoutNodeInfo()
\r
33 , firstSubBranch (NULL)
\r
34 , nextInBranch (NULL)
\r
35 , previousInBranch (NULL)
\r
36 , lastInBranch (NULL)
\r
38 , previousBranch (NULL)
\r
40 , rootID ((index_t)NO_INDEX)
\r
45 , requiresRevision (true)
\r
46 , requiresPath (true)
\r
47 , requiresGap (true)
\r
48 , skipStartPathElements (0)
\r
49 , skipTailPathElements (0)
\r
50 , requiredSize (0, 0)
\r
51 , subTreeShift (0, 0)
\r
54 , parentBranch(NULL)
\r
58 // sort nodes: make "heaviest" branch the first one
\r
60 void CStandardLayout::SortNodes()
\r
62 for (size_t i = 0, count = nodes.size(); i < count; ++i)
\r
63 if (nodes[i].firstSubBranch != NULL)
\r
65 // find first deepest sub-branch
\r
67 CStandardLayoutNodeInfo* firstBranch = nodes[i].firstSubBranch;
\r
68 CStandardLayoutNodeInfo* heaviestBranch = firstBranch;
\r
69 index_t maxWeight = heaviestBranch->subTreeWeight;
\r
71 for ( CStandardLayoutNodeInfo* node = heaviestBranch->nextBranch
\r
73 ; node = node->nextBranch)
\r
75 if (node->subTreeWeight > maxWeight)
\r
77 maxWeight = node->subTreeWeight;
\r
78 heaviestBranch = node;
\r
82 // already the first one?
\r
84 if (firstBranch == heaviestBranch)
\r
87 // update "last" pointers, if necessary
\r
89 CStandardLayoutNodeInfo* nextBranch = heaviestBranch->nextBranch;
\r
90 if (nextBranch == NULL)
\r
92 CStandardLayoutNodeInfo* newLast = heaviestBranch->previousBranch;
\r
93 for ( CStandardLayoutNodeInfo* node = firstBranch
\r
95 ; node = node->nextBranch)
\r
97 node->lastBranch = newLast;
\r
101 // mode branch to front
\r
103 heaviestBranch->previousBranch->nextBranch = nextBranch;
\r
104 if (nextBranch != NULL)
\r
105 nextBranch->previousBranch = heaviestBranch->previousBranch;
\r
107 heaviestBranch->previousBranch = NULL;
\r
108 heaviestBranch->nextBranch = firstBranch;
\r
109 firstBranch->previousBranch = heaviestBranch;
\r
111 nodes[i].firstSubBranch = heaviestBranch;
\r
115 // layout creation:
\r
116 // * create a node info object for every node
\r
117 // * calculate branch and tree sizes (in nodes)
\r
119 void CStandardLayout::InitializeNodes ( const CVisibleGraphNode* start
\r
120 , CStandardLayoutNodeInfo* parentBranch)
\r
122 CStandardLayoutNodeInfo* previousInBranch = NULL;
\r
123 CStandardLayoutNodeInfo* lastInBranch = NULL;
\r
125 // copy this info to all sub-nodes:
\r
127 index_t rootID = nodes[start->GetIndex()].rootID;
\r
129 // measure subtree size, calculate branch length and back-link it
\r
131 index_t branchLength = 0;
\r
132 for ( const CVisibleGraphNode* node = start
\r
134 ; node = node->GetNext())
\r
136 // get the node, initialize it and update pointers
\r
138 CStandardLayoutNodeInfo& nodeInfo = nodes[node->GetIndex()];
\r
141 assert (nodeInfo.node == NULL);
\r
142 nodeInfo.node = node;
\r
143 nodeInfo.rootID = rootID;
\r
144 nodeInfo.previousInBranch = previousInBranch;
\r
145 nodeInfo.parentBranch = parentBranch;
\r
146 if (previousInBranch != NULL)
\r
147 previousInBranch->nextInBranch = &nodeInfo;
\r
149 previousInBranch = &nodeInfo;
\r
150 lastInBranch = &nodeInfo;
\r
152 if (node->GetFirstCopyTarget())
\r
154 CStandardLayoutNodeInfo* previousBranch = NULL;
\r
155 CStandardLayoutNodeInfo* lastBranch = NULL;
\r
157 // measure sub-branches and back-link them
\r
159 for ( const CVisibleGraphNode::CCopyTarget*
\r
160 target = node->GetFirstCopyTarget()
\r
162 ; target = target->next())
\r
164 // get sub-branch node, initialize it and update pointers
\r
166 const CVisibleGraphNode* subNode = target->value();
\r
167 CStandardLayoutNodeInfo& subNodeInfo = nodes[subNode->GetIndex()];
\r
168 subNodeInfo.rootID = rootID;
\r
170 if (previousBranch != NULL)
\r
172 previousBranch->nextBranch = &subNodeInfo;
\r
173 subNodeInfo.previousBranch = previousBranch;
\r
177 // mark the first branch
\r
179 nodeInfo.firstSubBranch = &subNodeInfo;
\r
182 previousBranch = &subNodeInfo;
\r
183 lastBranch = &subNodeInfo;
\r
187 InitializeNodes (subNode, &nodeInfo);
\r
189 // accumulate branch into sub-tree
\r
191 nodeInfo.subTreeWidth += subNodeInfo.subTreeWidth;
\r
192 nodeInfo.subTreeWeight += subNodeInfo.subTreeWeight;
\r
193 if (nodeInfo.subTreeHeight <= subNodeInfo.subTreeHeight)
\r
194 nodeInfo.subTreeHeight = subNodeInfo.subTreeHeight+1;
\r
197 // link sub-branches forward
\r
199 for ( const CVisibleGraphNode::CCopyTarget*
\r
200 target = node->GetFirstCopyTarget()
\r
202 ; target = target->next())
\r
204 nodes[target->value()->GetIndex()].lastBranch = lastBranch;
\r
209 // write branch lengths, adjust sub-tree heights and link forward
\r
211 for ( const CVisibleGraphNode* node = start
\r
213 ; node = node->GetNext())
\r
215 CStandardLayoutNodeInfo& nodeInfo = nodes[node->GetIndex()];
\r
216 nodeInfo.branchLength = branchLength;
\r
217 nodeInfo.lastInBranch = lastInBranch;
\r
219 if (nodeInfo.subTreeHeight < branchLength)
\r
220 nodeInfo.subTreeHeight = branchLength;
\r
224 void CStandardLayout::InitializeNodes()
\r
226 nodes.resize (graph->GetNodeCount());
\r
228 for (size_t i = 0, count = graph->GetRootCount(); i < count; ++i)
\r
230 const CVisibleGraphNode* root = graph->GetRoot(i);
\r
231 nodes[root->GetIndex()].rootID = static_cast<index_t>(i);
\r
233 InitializeNodes (root, NULL);
\r
236 // every node info must actually refer to a node
\r
238 for (size_t i = 0, count = nodes.size(); i < count; ++i)
\r
239 assert (nodes[i].node != NULL);
\r
241 // adjust sub-tree weights
\r
243 for (size_t i = nodes.size(); i > 0; --i)
\r
245 CStandardLayoutNodeInfo& node = nodes[i-1];
\r
247 index_t weight = node.nextInBranch != NULL
\r
248 ? node.nextInBranch->subTreeWeight+1
\r
251 for ( CStandardLayoutNodeInfo* branch = node.firstSubBranch
\r
253 ; branch = branch->nextBranch)
\r
254 weight += branch->subTreeWeight;
\r
256 node.subTreeWeight = weight;
\r
259 // prevent degenerated branches from growing too far to the right
\r
264 // scan tree for connections between non-overlapping nodes
\r
266 void CStandardLayout::CreateConnections()
\r
268 // there can't be more connections than nodes
\r
270 connections.reserve (nodes.size());
\r
271 for (index_t i = 0, count = (index_t)nodes.size(); i < count; ++i)
\r
273 const CVisibleGraphNode* node = nodes[i].node;
\r
274 const CVisibleGraphNode* previousNode = node->GetSource();
\r
278 if (previousNode == NULL)
\r
283 const CRect& previousRect = nodes[previousNode->GetIndex()].rect;
\r
284 CRect rect = nodes[i].rect;
\r
286 // no line because nodes touch or overlap?
\r
288 rect.InflateRect (1, 1, 1, 1);
\r
289 if (TRUE == CRect().IntersectRect (rect, previousRect))
\r
292 // an actual connection
\r
294 connections.push_back (std::make_pair (previousNode->GetIndex(), i));
\r
298 // scan tree for connections longer than 0
\r
300 void CStandardLayout::CreateTexts()
\r
302 // there can be at most two texts per node
\r
304 texts.reserve (2*nodes.size());
\r
306 // cover all node rects
\r
307 // (connections and texts will lie within these bounds)
\r
309 for (index_t i = 0, count = (index_t)nodes.size(); i < count; ++i)
\r
311 const CStandardLayoutNodeInfo& info = nodes[i];
\r
313 if (info.requiresRevision)
\r
314 texts.push_back (STextInfo (i, 0));
\r
316 if (info.requiresPath)
\r
318 size_t visibleElementCount = info.node->GetPath().GetDepth()
\r
319 - info.skipStartPathElements
\r
320 - info.skipTailPathElements;
\r
321 for (index_t k = visibleElementCount; k > 0; --k)
\r
322 texts.push_back (STextInfo (i, k));
\r
327 // just iterate over all nodes
\r
329 inline bool SortRectByLeft (const CRect& lhs, const CRect& rhs)
\r
331 return lhs.left < rhs.left;
\r
334 void CStandardLayout::CloseTreeBoundingRectGaps()
\r
336 std::sort (trees.begin(), trees.end(), &SortRectByLeft);
\r
338 for (size_t i = 1, count = trees.size(); i < count; ++i)
\r
340 LONG diff = (trees[i].left - trees[i-1].right + 1) / 2;
\r
341 trees[i-1].right += diff;
\r
342 trees[i].left -= diff;
\r
346 void CStandardLayout::CalculateTreeBoundingRects()
\r
348 // initialize with empty rect
\r
350 trees.resize (graph->GetRootCount());
\r
351 for (size_t i = 0, count = graph->GetRootCount(); i < count; ++i)
\r
352 trees[i] = CRect (0, 0, 0, 0);
\r
354 for (size_t i = 0, count = nodes.size(); i < count; ++i)
\r
356 const CStandardLayoutNodeInfo& nodeInfo = nodes[i];
\r
357 const CSize& size = nodeInfo.requiredSize;
\r
359 CRect rect = nodeInfo.rect;
\r
360 rect.right = max (rect.left + size.cx, rect.right);
\r
361 rect.bottom = max (rect.top + size.cy, rect.bottom);
\r
363 CRect& bounds = trees[nodeInfo.rootID];
\r
364 if (bounds.Width() == 0)
\r
371 // just iterate over all nodes
\r
373 void CStandardLayout::CalculateBoundingRect()
\r
375 // special case: empty graph
\r
379 boundingRect = CRect();
\r
383 // cover all node rects
\r
384 // (connections and texts will lie within these bounds)
\r
386 boundingRect = trees[0];
\r
387 for (size_t i = 1, count = trees.size(); i < count; ++i)
\r
388 boundingRect |= trees[i];
\r
391 // construction / destruction
\r
393 CStandardLayout::CStandardLayout ( const CCachedLogInfo* cache
\r
394 , const CVisibleGraph* graph)
\r
401 CStandardLayout::~CStandardLayout(void)
\r
405 // call this after executing the format options
\r
407 void CStandardLayout::Finalize()
\r
409 CreateConnections();
\r
412 CalculateTreeBoundingRects();
\r
413 CloseTreeBoundingRectGaps();
\r
414 CalculateBoundingRect();
\r
417 /// implement IRevisionGraphLayout
\r
419 CRect CStandardLayout::GetRect() const
\r
421 return boundingRect;
\r
424 const ILayoutRectList* CStandardLayout::GetTrees() const
\r
426 return new CStandardLayoutRectList (trees);
\r
429 const ILayoutNodeList* CStandardLayout::GetNodes() const
\r
431 return new CStandardLayoutNodeList (nodes, cache);
\r
434 const ILayoutConnectionList* CStandardLayout::GetConnections() const
\r
436 return new CStandardLayoutConnectionList (nodes, connections);
\r
439 const ILayoutTextList* CStandardLayout::GetTexts() const
\r
441 return new CStandardLayoutTextList (nodes, texts);
\r
444 /// implement IStandardLayoutNodeAccess
\r
446 index_t CStandardLayout::GetNodeCount() const
\r
448 return static_cast<index_t>(nodes.size());
\r
451 CStandardLayoutNodeInfo* CStandardLayout::GetNode (index_t index)
\r
453 return &nodes[index];
\r