Skein::Node* TranscriptWindow::FindRelevantNode(FindAction action, bool next, bool selected) { // Get all the nodes in the transcript or skein CArray<Skein::Node*,Skein::Node*> nodes; if (action == SkeinDifferent) { ASSERT(next); m_skein->GetAllNodes(nodes); } else { Skein::Node* node = m_skeinEndThread; while (node != NULL) { if (next) nodes.InsertAt(0,node); else nodes.Add(node); node = node->GetParent(); } } if (next) nodes.InsertAt(0,(Skein::Node*)NULL); // Return the next relevant node after the selected node (which may be NULL) bool afterSelected = false; for (int i = 0; i < nodes.GetSize(); i++) { if (afterSelected == false) { if (nodes.GetAt(i) == (selected ? m_skeinSelected : NULL)) afterSelected = true; } else { switch (action) { case TranscriptDifferent: case SkeinDifferent: if (nodes.GetAt(i)->GetDiffers() != Skein::Node::ExpectedSame) return nodes.GetAt(i); break; case TranscriptChanged: if (nodes.GetAt(i)->GetChanged()) return nodes.GetAt(i); break; default: ASSERT(FALSE); break; } } } // If nothing found, then if searching the whole skein, start from the beginning if ((action == SkeinDifferent) && selected) return FindRelevantNode(action,next,false); return NULL; }
void SkeinWindow::DrawNode(Skein::Node* node, CDC& dc, CDibSection& bitmap, const CRect& client, const CPoint& centre) { // Store the current device context properties UINT align = dc.GetTextAlign(); int mode = dc.GetBkMode(); // Set the device context properties dc.SetTextAlign(TA_TOP|TA_LEFT); dc.SetBkMode(TRANSPARENT); // Get the text associated with the node LPCWSTR line = node->GetLine(); LPCWSTR label = node->GetLabel(); int width = node->GetLineWidth(dc,&m_labelFont); // Check if this node is visible before drawing CRect nodeArea(centre,CSize(width+m_fontSize.cx*10,m_fontSize.cy*3)); nodeArea.OffsetRect(nodeArea.Width()/-2,nodeArea.Height()/-2); CRect intersect; if (intersect.IntersectRect(client,nodeArea)) { CDibSection* back = m_bitmaps[BackUnplayed]; bool gameRunning = GetParentFrame()->SendMessage(WM_GAMERUNNING) != 0; Skein::Node* playNode = gameRunning ? m_skein->GetPlayed() : m_skein->GetCurrent(); while (playNode != NULL) { if (playNode == node) { back = m_bitmaps[BackPlayed]; break; } playNode = playNode->GetParent(); } if (node->GetExpectedText().IsEmpty()) { if (back == m_bitmaps[BackPlayed]) back = m_bitmaps[BackPlayedDark]; else if (back == m_bitmaps[BackUnplayed]) back = m_bitmaps[BackUnplayedDark]; } // Draw the node's background DrawNodeBack(node,dc,bitmap,centre,width,back); // Write out the node's line int textWidth = node->GetLineTextWidth(); ::ExtTextOutW(dc.GetSafeHdc(),centre.x-(textWidth/2),centre.y-(m_fontSize.cy/2),0, NULL,line,(UINT)wcslen(line),NULL); // Write out the node's label, if any if (ShowLabel(node)) { CFont* oldFont = dc.SelectObject(&m_labelFont); SIZE size; ::GetTextExtentPoint32W(dc.GetSafeHdc(),label,(UINT)wcslen(label),&size); ::ExtTextOutW(dc.GetSafeHdc(),centre.x-(size.cx/2),centre.y-(int)(2.1*m_fontSize.cy), 0,NULL,label,(UINT)wcslen(label),NULL); dc.SelectObject(oldFont); } } // Reset the device context properties dc.SetTextAlign(align); dc.SetBkMode(mode); }
void TranscriptWindow::Layout(void) { CRect clientRect; GetClientRect(clientRect); m_layout.clientSize = clientRect.Size(); m_layout.columnWidth = clientRect.Width()/2; m_layout.font = theApp.GetFont(InformApp::FontDisplay); m_layout.fontSize = theApp.MeasureFont(m_layout.font); m_layout.margin = CSize(m_layout.fontSize.cx,m_layout.fontSize.cy/3); m_layout.centreMargin = m_layout.margin.cx*4; CDC* dc = GetDC(); m_layout.nodes.clear(); Skein::Node* node = m_skeinEndThread; while (node != NULL) { NodeLayout nl; nl.node = node; // Get the text associated with the node const CStringW& transcript = nl.node->GetTranscriptText(); const CStringW& expected = nl.node->GetExpectedText(); // Measure the height of the expected text CRect textRect; textRect.SetRectEmpty(); textRect.right = m_layout.columnWidth-m_layout.margin.cx-m_layout.centreMargin; SizeText(*dc,textRect,expected); nl.height = textRect.Height(); // Measure the height of the transcript text textRect.SetRectEmpty(); textRect.right = m_layout.columnWidth-m_layout.margin.cx-m_layout.centreMargin; SizeText(*dc,textRect,transcript); if (textRect.Height() > nl.height) nl.height = textRect.Height(); // Use the tallest for the height of the node in the transcript if (m_layout.fontSize.cy > nl.height) nl.height = m_layout.fontSize.cy; m_layout.nodes.push_front(nl); node = node->GetParent(); } ReleaseDC(dc); // Compare the height of the transcript with the height of the window int height = GetHeight(); if (height > clientRect.Height()) { // The transcript is taller than the window, so turn the scrollbar on EnableScrollBar(SB_VERT,ESB_ENABLE_BOTH); // Get the current scrollbar settings SCROLLINFO scroll; ::ZeroMemory(&scroll,sizeof scroll); scroll.cbSize = sizeof scroll; GetScrollInfo(SB_VERT,&scroll); // Change the maximum position and the size of the scrollbar scroll.nMin = 0; scroll.nMax = height-1; scroll.nPage = clientRect.Height(); SetScrollInfo(SB_VERT,&scroll); } else { // The transcript is shorter than the window, so turn the scrollbar off EnableScrollBar(SB_VERT,ESB_DISABLE_BOTH); SCROLLINFO scroll; ::ZeroMemory(&scroll,sizeof scroll); scroll.cbSize = sizeof scroll; SetScrollInfo(SB_VERT,&scroll); } }
void SkeinWindow::OnContextMenu(CWnd* pWnd, CPoint point) { // No menu if currently editing if (m_edit.IsWindowVisible()) return; // No menu if running the game bool gameRunning = GetParentFrame()->SendMessage(WM_GAMERUNNING) != 0; bool gameWaiting = GetParentFrame()->SendMessage(WM_GAMEWAITING) != 0; if (gameRunning && !gameWaiting) return; // Find the node under the mouse, if any CPoint cp(point); ScreenToClient(&cp); Skein::Node* node = NodeAtPoint(cp); if (node == NULL) return; // Get the context menu CMenu popup; popup.LoadMenu(IDR_SKEIN); CMenu* menu = popup.GetSubMenu(0); // Update the state of the context menu items if (node->GetParent() == NULL) { menu->RemoveMenu(ID_SKEIN_EDIT,MF_BYCOMMAND); menu->RemoveMenu(ID_SKEIN_ADD_LABEL,MF_BYCOMMAND); menu->RemoveMenu(ID_SKEIN_EDIT_LABEL,MF_BYCOMMAND); menu->RemoveMenu(ID_SKEIN_INSERT_KNOT,MF_BYCOMMAND); menu->RemoveMenu(ID_SKEIN_DELETE,MF_BYCOMMAND); menu->RemoveMenu(ID_SKEIN_DELETE_BELOW,MF_BYCOMMAND); menu->RemoveMenu(ID_SKEIN_DELETE_THREAD,MF_BYCOMMAND); menu->RemoveMenu(ID_SKEIN_LOCK,MF_BYCOMMAND|MF_GRAYED); menu->RemoveMenu(ID_SKEIN_UNLOCK,MF_BYCOMMAND|MF_GRAYED); menu->RemoveMenu(ID_SKEIN_LOCK_THREAD,MF_BYCOMMAND|MF_GRAYED); menu->RemoveMenu(ID_SKEIN_UNLOCK_THREAD,MF_BYCOMMAND|MF_GRAYED); } else { if (gameRunning && m_skein->InCurrentThread(node)) { menu->EnableMenuItem(ID_SKEIN_DELETE,MF_BYCOMMAND|MF_GRAYED); menu->EnableMenuItem(ID_SKEIN_DELETE_BELOW,MF_BYCOMMAND|MF_GRAYED); } if (gameRunning && m_skein->InCurrentThread(m_skein->GetThreadTop(node))) menu->EnableMenuItem(ID_SKEIN_DELETE_THREAD,MF_BYCOMMAND|MF_GRAYED); if (node->GetLabel().IsEmpty()) menu->RemoveMenu(ID_SKEIN_EDIT_LABEL,MF_BYCOMMAND); else menu->RemoveMenu(ID_SKEIN_ADD_LABEL,MF_BYCOMMAND); } if (node->GetTemporary()) { menu->RemoveMenu(ID_SKEIN_UNLOCK,MF_BYCOMMAND|MF_GRAYED); menu->RemoveMenu(ID_SKEIN_UNLOCK_THREAD,MF_BYCOMMAND|MF_GRAYED); } else { menu->RemoveMenu(ID_SKEIN_LOCK,MF_BYCOMMAND|MF_GRAYED); menu->RemoveMenu(ID_SKEIN_LOCK_THREAD,MF_BYCOMMAND|MF_GRAYED); } // Show the context menu int cmd = menu->TrackPopupMenuEx(TPM_LEFTALIGN|TPM_TOPALIGN|TPM_NONOTIFY|TPM_RETURNCMD, point.x,point.y,GetParentFrame(),NULL); // Act on the context menu choice switch (cmd) { case ID_SKEIN_PLAY: GetParentFrame()->SendMessage(WM_PLAYSKEIN,(WPARAM)node); break; case ID_SKEIN_EDIT: StartEdit(node,false); break; case ID_SKEIN_ADD_LABEL: case ID_SKEIN_EDIT_LABEL: // Make sure that the label background is visible Invalidate(); StartEdit(node,true); break; case ID_SKEIN_TRANSCRIPT: GetParentFrame()->SendMessage(WM_SHOWTRANSCRIPT,(WPARAM)node,(LPARAM)GetSafeHwnd()); break; case ID_SKEIN_LOCK: m_skein->Lock(node); break; case ID_SKEIN_UNLOCK: m_skein->Unlock(node); break; case ID_SKEIN_LOCK_THREAD: m_skein->Lock(m_skein->GetThreadBottom(node)); break; case ID_SKEIN_UNLOCK_THREAD: m_skein->Unlock(m_skein->GetThreadTop(node)); break; case ID_SKEIN_NEW_THREAD: { Skein::Node* newNode = m_skein->AddNew(node); // Force a repaint so that the new node is drawn and recorded Invalidate(); UpdateWindow(); StartEdit(newNode,false); } break; case ID_SKEIN_INSERT_KNOT: { Skein::Node* newNode = m_skein->AddNewParent(node); Invalidate(); UpdateWindow(); StartEdit(newNode,false); } break; case ID_SKEIN_DELETE: if (CanRemove(node)) m_skein->RemoveSingle(node); break; case ID_SKEIN_DELETE_BELOW: if (CanRemove(node)) m_skein->RemoveAll(node); break; case ID_SKEIN_DELETE_THREAD: { Skein::Node* topNode = m_skein->GetThreadTop(node); if (CanRemove(topNode)) m_skein->RemoveAll(topNode); } break; case ID_SKEIN_SAVE_TRANSCRIPT: { SimpleFileDialog dialog(FALSE,"txt",NULL,OFN_HIDEREADONLY|OFN_ENABLESIZING, "Text Files (*.txt)|*.txt|All Files (*.*)|*.*||",this); dialog.m_ofn.lpstrTitle = "Save the transcript up to this knot"; if (dialog.DoModal() == IDOK) m_skein->SaveTranscript(node,dialog.GetPathName()); } break; case 0: // Make sure this window still has the focus SetFocus(); break; } }