Initial commit

This commit is contained in:
IRBorisov 2024-06-07 20:06:17 +03:00
commit 306e18c4f8
20 changed files with 1588 additions and 0 deletions

38
VBAMake.txt Normal file
View File

@ -0,0 +1,38 @@
# == Properties Section ==
# configuration properties
# use .ini format to define properties
# mandatory properties: name, artifact_home, source_home
id = Concept-Hierarchy
name = Концепт-Иерархия
description = Технология визуализации и преобразования формы представления иерархий
artifact_home = Концепт-Иерархия
source_home = Concept-Hierarchy
install_home = \\fs1.concept.ru\projects\10 Автоматизация деятельности\01 Высокие технологии\Концепт-Иерархия
%%
# === Build section ===
# Available commands:
# build LOCAL_MANIFEST
# copy LOCAL_SOURCE -> [LOCAL_ARTIFACT]
# save_as LOCAL_ARTIFACT -> LOCAL_ARTIFACT
# run LOCAL_SOURCE.bat
build script\manifest.txt
save_as Иерархизатор.vsdm -> 30 Иерархизатор.vstm
copy distr\!Readme.docx
copy distr\Пример входных данных.xlsx
%%
# === Install section ==
# Available commands:
# install LOCAL_ARTIFACT -> [INSTALL_PATH]
# add_template LOCAL_ARTIFACT -> [LOCAL_TEMPLATE]
# run LOCAL_ARTIFACT.bat <- [PARAMETERS]
# run APPLICATION <- [PARAMETERS]
install Иерархизатор.vsdm
install !Readme.docx
install Пример входных данных.xlsx
add_template 30 Иерархизатор.vstm

1
VERSION Normal file
View File

@ -0,0 +1 @@
1.2.0

BIN
distr/!Readme.docx Normal file

Binary file not shown.

Binary file not shown.

93
script/manifest.txt Normal file
View File

@ -0,0 +1,93 @@
# == Properties Section ==
# configuration properties
# use .ini format to define properties
# mandatory properties: name, artifact
name = Иерархизатор.vsdm
artifact = Иерархизатор.vsdm
%%
# === Imports Section ===
# Hierarchy of folders and files
# Use Tabulator to mark next level in hierarchy
# All folders are nested into SharedHome path
dev
DevTester.bas
api
ex_WinAPI.bas
API_VsoWrapper.cls
API_XLWrapper.cls
API_WordWrapper.cls
API_UserInteraction.cls
utility
ex_VBA.bas
ex_Collection.bas
ex_DataPreparation.bas
ex_Color.bas
ex_Version.bas
API_DistrManifest.cls
API_JSON.cls
CDS_Factorizator.cls
CDS_Graph.cls
CDS_Node.cls
CDS_Edge.cls
CDS_NodeSH.cls
CDS_StaticHierarchy.cls
visio
z_VsoUtilities.bas
z_CCVsoExtension.bas
z_VsoGraph.bas
API_UndoWrapper.cls
word
ex_Word.bas
ui
CSE_ProgressBar.frm
%%
# === Source Code Section ==
# Hierarchy of folders and files
# Use Tabulator to mark next level in hierarchy
# All folders are nested into SourceHome path
src
MasterDlg.frm
DevHelper.bas
Declarations.bas
DataAccess.bas
Main.bas
MainImpl.bas
z_UIRibbon.bas
z_UIMessages.bas
VisioNode.cls
LayoutConstruction.cls
%%
# ===== UI Section =======
# Pairs of path to UI elements, use " -> " delimiter
# First component is a path relative to SourceHome\ui folders
# Second component is internal path inside project file
.rels -> _rels\.rels
customUI1.xml -> visio\customUI\customUI1.xml
%%
# === References Section ===
# List dependencies in one of the formats
# global : GLOBAL_NAME
# guid : {REGISTERED_GUID}
# file : PATH_TO_LIBRARY
global : MSForms
global : Shell32
global : Scripting
global : Excel
global : Word

Binary file not shown.

97
src/DataAccess.bas Normal file
View File

@ -0,0 +1,97 @@
Attribute VB_Name = "DataAccess"
Option Explicit
Public Function MainPage() As Visio.Page
Set MainPage = Application.ActiveDocument.Pages(1)
End Function
Public Function GetRankMaster(nRank&) As Visio.Master
Dim iMaster As Visio.Master: Set iMaster = FindMaster(ThisDocument, MASTER_RANK & nRank)
If iMaster Is Nothing Then
Set GetRankMaster = GetRankMaster(nRank - 1)
Else
Set GetRankMaster = iMaster
End If
End Function
Public Function GetConnectionMaster(nMode As TLayout) As Visio.Master
Select Case nMode
Case T_LAYOUT_HORIZONTAL: Set GetConnectionMaster = FindMaster(ThisDocument, MASTER_CONNECT_HOR)
Case T_LAYOUT_VERTICAL: Set GetConnectionMaster = FindMaster(ThisDocument, MASTER_CONNECT_VERT)
Case T_LAYOUT_COLUMN_LEFT: Set GetConnectionMaster = FindMaster(ThisDocument, MASTER_CONNECT_HOR)
Case T_LAYOUT_COLUMN_RIGHT: Set GetConnectionMaster = FindMaster(ThisDocument, MASTER_CONNECT_HOR)
Case Else: Set GetConnectionMaster = FindMaster(ThisDocument, MASTER_CONNECT_VERT)
End Select
End Function
Public Function GetRankWidth(nRank&) As Double
GetRankWidth = GetRankMaster(nRank).Shapes(1).CellsU("Width")
End Function
Public Function GetRanksBaseY() As Scripting.Dictionary
Dim iBases As New Scripting.Dictionary
Dim nRank&
Dim baseY As Double: baseY = 0
For nRank = 1 To TREE_MAX_RANK Step 1
Dim iPrototype As Visio.Shape: Set iPrototype = GetRankMaster(nRank).Shapes(1)
If Not iPrototype Is Nothing Then
iBases(nRank) = baseY - iPrototype.CellsU("Height") / 2#
baseY = baseY - iPrototype.CellsU("Height") - GAP_RANK
End If
Next nRank
Set GetRanksBaseY = iBases
End Function
Public Function GetChildrenOf(target As Visio.Shape) As Collection
Set GetChildrenOf = New Collection
Dim cnt As Visio.Connect
For Each cnt In target.FromConnects
Dim connector As Visio.Shape: Set connector = cnt.FromSheet
If connector.Connects(1).ToSheet Is target And connector.Connects.Count = 2 Then _
Call GetChildrenOf.Add(connector.Connects(2).ToSheet)
Next cnt
End Function
Public Function ExrtactXLPayload(iSource As Excel.Worksheet, nRow&) As VisioNode
Dim iPayload As New VisioNode
iPayload.text_ = iSource.Cells(nRow, 2)
If iSource.Cells(nRow, 1).Interior.ColorIndex <> -4142 Then _
iPayload.color_ = iSource.Cells(nRow, 1).Interior.Color
Set ExrtactXLPayload = iPayload
End Function
Public Function ExtractWordPayload(iSource As Word.Range) As VisioNode
Dim sText$: sText = TrimWhitespace(iSource.Text)
If sText = vbNullString Then _
Exit Function
Dim nColor&: nColor = iSource.Shading.BackgroundPatternColor
nColor = IIf(nColor = wdColorAutomatic, COLOR_DEFAULT, ColorGetRGB(iSource.Font.Shading.BackgroundPatternColor, iSource.Document))
Dim iPayload As New VisioNode
iPayload.text_ = sText
iPayload.color_ = nColor
Set ExtractWordPayload = iPayload
End Function
Public Function ExtractVsoPayload(target As Visio.Shape) As VisioNode
Dim iPayload As New VisioNode
iPayload.text_ = target.Text
iPayload.color_ = ConvertStringToRGB(target.CellsU("FillForegnd").ResultStrU(""))
Set iPayload.shape_ = target
Set ExtractVsoPayload = iPayload
End Function
Public Function WordStyleForRank(wordDoc As Word.Document, nRank As Integer) As String
WordStyleForRank = "Îáû÷íûé"
If nRank < 1 Or nRank > WORD_HEADING_MAX_LEVEL Then _
Exit Function
Dim sStyle$: sStyle = "Çàãîëîâîê " & nRank & " Íóìåðîâàííûé"
If WordStyleExists(wordDoc, sStyle) Then
WordStyleForRank = sStyle
Else
WordStyleForRank = "Çàãîëîâîê " & nRank
End If
End Function

42
src/Declarations.bas Normal file
View File

@ -0,0 +1,42 @@
Attribute VB_Name = "Declarations"
Option Explicit
Public Const TREE_MAX_RANK As Integer = 30
Public Const COLOR_DEFAULT = -1
Public Const FLOAT_ACCURACY As Double = 0.001
Public Const GAP_VERTICAL = 5# / 25.4
Public Const GAP_HORIZONTAL = 5# / 25.4
Public Const GAP_VERT_ADJUSTMENT_TO_HOR = 10# / 25.4
Public Const GAP_RANK = 10# / 25.4
Public Const MASTER_CONNECT_HOR = "Ãîðèçîíòàëüíàÿ ñòðåëêà"
Public Const MASTER_CONNECT_VERT = "Âåðòèêàëüíàÿ ñòðåëêà"
Public Const MASTER_RANK = "Óð"
Public Const SHAPE_RANK_MASTER = "#Lvl"
Public Const PREFIX_NODE = "#N"
Public Const PREFIX_CONNECTOR = "#C"
' Layout modes
Public Enum TLayout
T_LAYOUT_VERTICAL ' y-based
T_LAYOUT_HORIZONTAL ' x-based
T_LAYOUT_RADIAL ' distance based
T_LAYOUT_COLUMN_RIGHT ' right column
T_LAYOUT_COLUMN_LEFT ' left column
End Enum
' Input / output modes for conversion
Public Enum IOMode
MODE_EXCEL
MODE_WORD
MODE_VISIO
End Enum
' ========== Word declarations ==========
Public Const WORD_HEADING_MAX_LEVEL = 9
Public Const EXPORT_TEMPLATE_PATH = "\Microsoft\Øàáëîíû\01 Ðàáî÷èé äîêóìåíò.dotx"

21
src/DevHelper.bas Normal file
View File

@ -0,0 +1,21 @@
Attribute VB_Name = "DevHelper"
Option Private Module
Option Explicit
Public Function Dev_PrepareSkeleton()
' Do nothing
End Function
Public Function Dev_ManualRunTest()
Dim sSuite$: sSuite = "s_UndoWrapper"
Dim sTest$: sTest = "t_BasicUndo"
Dim sMsg$: sMsg = Dev_RunTest(sSuite, sTest)
Debug.Print sMsg
Call MsgBox(sMsg)
End Function
Public Function Dev_GetTestSuite(sName$) As Object
Select Case sName
' Case "s_UndoWrapper": Set Dev_GetTestSuite = New s_UndoWrapper
End Select
End Function

201
src/LayoutConstruction.cls Normal file
View File

@ -0,0 +1,201 @@
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "LayoutConstruction"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
Option Explicit
Private page_ As Visio.Page
Private data_ As CDS_StaticHierarchy
Private autoVertical_ As Boolean
Private width_ As Scripting.Dictionary
Private Const SIZING_FACTOR_LIMIT = 1.3
Public Function Init(iPage As Visio.Page, ByRef iData As CDS_StaticHierarchy, bAutoVertical As Boolean)
Set page_ = iPage
Set data_ = iData
autoVertical_ = bAutoVertical
End Function
Public Function ConstructHNodes()
Dim ranksLast As New Scripting.Dictionary
Dim ranksY As Scripting.Dictionary: Set ranksY = GetRanksBaseY
Dim lastInsert As Visio.Shape
Dim iNode As CDS_NodeSH
Dim iParent As Visio.Shape
For Each iNode In data_.nodes_
Dim nRank&: nRank = iNode.rank_
Dim nLayout As TLayout: nLayout = GetLayoutFor(iNode.parent_)
Dim iNewShape As Visio.Shape: Set iNewShape = CreateNodeShape(iNode)
Set iNode.data_.shape_ = iNewShape
If iNode.parent_ Is Nothing Then
Set iParent = Nothing
Else
Set iParent = iNode.parent_.data_.shape_
End If
If nLayout = T_LAYOUT_HORIZONTAL Then
If Not ranksLast.Exists(nRank) Then
iNewShape.CellsU("PinY") = ranksY(nRank) - iNewShape.CellsU("Height") / 2#
Else
Call MoveShapeAfterHorizontal(iNewShape, ranksLast(nRank))
End If
Set ranksLast(nRank) = iNewShape
Set lastInsert = Nothing
Else
If lastInsert Is Nothing Then
Call MoveShapeAfterVertical(iNewShape, iParent, GAP_RANK)
Else
Call MoveShapeAfterVertical(iNewShape, lastInsert, GAP_VERTICAL)
End If
Set lastInsert = iNewShape
End If
If iNode.children_.Count = 0 Then _
Call TryOptimizeHeight(iNewShape, iNode.rank_, nLayout)
If Not iParent Is Nothing Then _
Call ConnectNodes(iParent, iNewShape, nLayout)
Call CSE_ProgressBar.IncrementA
Next iNode
End Function
Public Function CalculateWidth()
Set width_ = New Scripting.Dictionary
Call GetWidthFor(data_.nodes_(1))
End Function
Public Function RepositionAll()
Call SetPositionFor(data_.nodes_(1), dBaseX:=0)
End Function
' ==============
Private Function GetWidthFor(iNode As CDS_NodeSH) As Double
If width_.Exists(iNode.id_) Then
GetWidthFor = width_(iNode.id_)
Else
width_(iNode.id_) = CalculateWidthFor(iNode)
GetWidthFor = width_(iNode.id_)
End If
End Function
Private Function CalculateWidthFor(iNode As CDS_NodeSH) As Double
Dim dChildWidth As Double
Dim iChild As CDS_NodeSH
Dim dNodeWidth As Double: dNodeWidth = iNode.data_.shape_.CellsU("Width")
If iNode.children_.Count = 0 Then
CalculateWidthFor = dNodeWidth
ElseIf GetLayoutFor(iNode) = T_LAYOUT_VERTICAL Then
dNodeWidth = dNodeWidth
For Each iChild In iNode.children_
dChildWidth = GetWidthFor(iChild)
If dNodeWidth < dChildWidth Then _
dNodeWidth = dChildWidth
Next iChild
CalculateWidthFor = dNodeWidth + GAP_VERT_ADJUSTMENT_TO_HOR
Else
Dim dAccumulated As Double: dAccumulated = -GAP_HORIZONTAL
For Each iChild In iNode.children_
dAccumulated = dAccumulated + GAP_HORIZONTAL + GetWidthFor(iChild)
Next iChild
Dim dRankWidth As Double: dRankWidth = GetRankWidth(iNode.rank_)
If dAccumulated < dRankWidth Then _
dAccumulated = dRankWidth
If dAccumulated < dNodeWidth Then _
dAccumulated = dNodeWidth
CalculateWidthFor = dAccumulated
End If
End Function
Private Function SetPositionFor(iNode As CDS_NodeSH, dBaseX As Double)
Dim iTarget As Visio.Shape: Set iTarget = iNode.data_.shape_
Dim dNodeWidth As Double: dNodeWidth = width_(iNode.id_)
Dim iChild As CDS_NodeSH
Call CSE_ProgressBar.IncrementA
If iNode.children_.Count = 0 Then
iTarget.CellsU("PinX") = dBaseX + dNodeWidth / 2#
ElseIf GetLayoutFor(iNode) = T_LAYOUT_VERTICAL Then
iTarget.CellsU("PinX") = dBaseX + iTarget.CellsU("Width") / 2# + GAP_VERT_ADJUSTMENT_TO_HOR
For Each iChild In iNode.children_
iChild.data_.shape_.CellsU("PinX") = dBaseX + iChild.data_.shape_.CellsU("Width") / 2# + GAP_VERT_ADJUSTMENT_TO_HOR
Next iChild
Call CSE_ProgressBar.IncrementA(iNode.children_.Count)
Else ' Horizontal
Dim newX As Double: newX = dBaseX
If iNode.children_.Count = 1 And dNodeWidth > width_(iNode.children_(1).id_) Then _
newX = dBaseX + dNodeWidth / 2# - width_(iNode.children_(1).id_) / 2#
For Each iChild In iNode.children_
Call SetPositionFor(iChild, newX)
newX = newX + width_(iChild.id_) + GAP_HORIZONTAL
Next iChild
iTarget.CellsU("PinX") = iNode.children_(1).data_.shape_.CellsU("PinX") / 2# _
+ iNode.children_(iNode.children_.Count).data_.shape_.CellsU("PinX") / 2#
End If
End Function
Private Function MoveShapeAfterVertical(sh1 As Visio.Shape, sh2 As Visio.Shape, dGap As Double)
Dim dPinx As Double: dPinx = sh2.CellsU("PinX") - sh1.CellsU("Width") / 2# + sh2.CellsU("Width") / 2#
Dim dPiny As Double: dPiny = sh2.CellsU("PinY") - dGap - sh2.CellsU("Height") / 2# - sh1.CellsU("Height") / 2#
Call sh1.SetCenter(dPinx, dPiny)
End Function
Private Function MoveShapeAfterHorizontal(sh1 As Visio.Shape, sh2 As Visio.Shape)
Dim dPinx As Double: dPinx = sh2.CellsU("PinX") + GAP_HORIZONTAL + sh2.CellsU("Width") / 2# + sh1.CellsU("Width") / 2#
Dim dPiny As Double: dPiny = sh2.CellsU("PinY") + sh2.CellsU("Height") / 2# - sh1.CellsU("Height") / 2#
Call sh1.SetCenter(dPinx, dPiny)
End Function
Private Function GetLayoutFor(iNode As CDS_NodeSH) As TLayout
If iNode Is Nothing Then
GetLayoutFor = T_LAYOUT_HORIZONTAL
ElseIf iNode.parent_ Is Nothing Then
GetLayoutFor = T_LAYOUT_HORIZONTAL
ElseIf iNode.children_.Count <= 1 Then
GetLayoutFor = T_LAYOUT_HORIZONTAL
ElseIf Not autoVertical_ And iNode.rank_ + 1 <> data_.MaxDepth Then
GetLayoutFor = T_LAYOUT_HORIZONTAL
ElseIf iNode.descendantsCount_ = iNode.children_.Count Then
GetLayoutFor = T_LAYOUT_VERTICAL
Else
GetLayoutFor = T_LAYOUT_HORIZONTAL
End If
End Function
Private Function CreateNodeShape(ByRef iNode As CDS_NodeSH) As Visio.Shape
Dim iShape As Visio.Shape: Set iShape = page_.Drop(GetRankMaster(iNode.rank_), 0, 0)
With iShape
.Text = iNode.data_.text_
.Name = PREFIX_NODE & iNode.id_
.CellsSRC(visSectionObject, visRowLock, visLockDelete).FormulaU = "0"
If iNode.data_.color_ <> COLOR_DEFAULT Then _
.CellsSRC(visSectionObject, visRowFill, visFillForegnd).FormulaU = ConvertRGBtoString(iNode.data_.color_)
End With
Set CreateNodeShape = iShape
End Function
Private Function TryOptimizeHeight(iShape As Visio.Shape, nRank&, nLayout As TLayout)
Dim dGap As Double: dGap = IIf(nLayout = T_LAYOUT_HORIZONTAL, GAP_RANK, GAP_VERTICAL)
Dim iPrototype As Visio.Master: Set iPrototype = GetRankMaster(nRank)
Dim kFactor As Double: kFactor = VBA.Sqr(iShape.CellsU("Width") / iPrototype.Shapes(1).CellsU("Width"))
If kFactor < SIZING_FACTOR_LIMIT Then _
Exit Function
Dim dLeft As Double: dLeft = iShape.CellsU("PinX") - iShape.CellsU("Width") / 2#
Dim dTop As Double: dTop = iShape.CellsU("PinY") + iShape.CellsU("Height") / 2#
kFactor = VBA.Int(kFactor) + 1
iShape.CellsU("Height") = iPrototype.Shapes(1).CellsU("Height") * kFactor + dGap * (kFactor - 1)
iShape.CellsU("PinY") = dTop - iShape.CellsU("Height") / 2#
If nLayout = T_LAYOUT_HORIZONTAL Then _
iShape.CellsU("PinX") = dLeft + iShape.CellsU("Width") / 2#
End Function

278
src/Main.bas Normal file
View File

@ -0,0 +1,278 @@
Attribute VB_Name = "Main"
Option Explicit
Public Const PRODUCT_VERSION = "1.2.0"
Public Const PRODUCT_NAME = "Concept-Hierarchy"
Public g_VersionTimer As Long
Public Sub StartVersionCheck()
g_VersionTimer = SetTimer(0, 0, CP_VERSION_MSG_DELAY, AddressOf OnVersionCheck)
End Sub
Public Function OnVersionCheck(ByVal nHwnd As Long, ByVal uMsg As Long, ByVal nEvent As Long, ByVal nTime As Long)
Call KillTimer(0, g_VersionTimer)
Call VersionValidate(PRODUCT_NAME, PRODUCT_VERSION)
End Function
Public Sub RunGotoLinkBegin()
Attribute RunGotoLinkBegin.VB_ProcData.VB_Invoke_Func = "Q"
Call CC_GotoLinkBegin
End Sub
Public Sub RunGotoLinkEnd()
Call CC_GotoLinkEnd
End Sub
Public Sub RunImportWord()
Dim sFile$: sFile = UserInteraction.PromptFileFilter( _
sInitialPath:=Visio.ActiveDocument.Path, _
sDescription:="Âûáåðèòå ôàéë, ïî ñòðóêòóðå êîòîðîãî Âû õîòèòå ïîñòðîèòü äåðåâî", _
sFilter:="*.docx;*.doc;*.docm", _
sTitle:="Äîêóìåíò Word", _
bNewApplication:=True)
If sFile = vbNullString Then _
Exit Sub
Dim iData As CDS_StaticHierarchy: Set iData = ScanHierarchy(sFile, MODE_WORD)
If iData Is Nothing Then _
Exit Sub
Call OutputHierarchy(iData, MODE_VISIO)
End Sub
Public Sub RunImportXL()
Dim sFile$: sFile = UserInteraction.PromptFileFilter( _
sInitialPath:=Visio.ActiveDocument.Path, _
sDescription:="Âûáåðèòå ôàéë, ïî ñòðóêòóðå êîòîðîãî Âû õîòèòå ïîñòðîèòü äåðåâî", _
sFilter:="*.xlsx;*.xls;*.xlsm", _
sTitle:="Òàáëèöà Excel", _
bNewApplication:=True)
If sFile = vbNullString Then _
Exit Sub
Dim iData As CDS_StaticHierarchy: Set iData = ScanHierarchy(sFile, MODE_EXCEL)
If iData Is Nothing Then _
Exit Sub
Call OutputHierarchy(iData, MODE_VISIO)
End Sub
Public Sub RunMaster()
Call MasterDlg.Show
If MasterDlg.bCanceled Then _
Exit Sub
Dim iMode As IOMode: iMode = MasterDlg.InputMode
Dim oMode As IOMode: oMode = MasterDlg.OutputMode
Dim iLayout As TLayout: iLayout = MasterDlg.InputLayout
Dim sFileName$: sFileName = MasterDlg.InputFile
If iMode <> MODE_VISIO And sFileName = vbNullString Then
Call UserInteraction.ShowMessage(EM_FILENAME_EMPTY)
Exit Sub
End If
Dim iData As CDS_StaticHierarchy: Set iData = ScanHierarchy(sFileName, iMode, iLayout)
If iData Is Nothing Then _
Exit Sub
Call OutputHierarchy(iData, MasterDlg.OutputMode)
End Sub
Public Sub RunClearScheme()
Attribute RunClearScheme.VB_Description = "Î÷èñòêà ñòðóêòóðû"
Attribute RunClearScheme.VB_ProcData.VB_Invoke_Func = "X"
Call GlobalUndo.BeginScope("Î÷èñòêà ãåíåðèðîâàííûõ ôèãóð")
Call ClearGeneratedShapes(MainPage)
Call VsoShowWholePage(MainPage)
Call GlobalUndo.EndScope
End Sub
Public Sub RunRename()
Attribute RunRename.VB_Description = "Àâòîíóìåðàöèÿ"
Dim sel As Visio.Selection: Set sel = Visio.Application.ActiveWindow.Selection
If sel.Count <> 1 Then
Call UserInteraction.ShowMessage(EM_INVALID_SELECTION)
Exit Sub
End If
Call GlobalUndo.BeginScope("Íóìåðàöèÿ óçëîâ äåðåâà")
Call GenerateNamesFor(sel.Item(1))
Call GlobalUndo.EndScope
End Sub
Public Sub RunTransform()
Attribute RunTransform.VB_ProcData.VB_Invoke_Func = "Z"
Dim sel As Visio.Selection: Set sel = Visio.Application.ActiveWindow.Selection
If sel.Count <> 1 Then
Call UserInteraction.ShowMessage(EM_INVALID_SELECTION)
Exit Sub
End If
Call GlobalUndo.BeginScope("Òðàíñôîðìàöèÿ ñòðóêòóðû")
Call TransformNodeLayout(sel.Item(1))
Call GlobalUndo.EndScope
End Sub
Public Sub RunConnectColumns()
Dim sel As Visio.Selection: Set sel = Visio.Application.ActiveWindow.Selection
If sel.Count <> 1 Then
Call UserInteraction.ShowMessage(EM_INVALID_SELECTION)
Exit Sub
End If
Call GlobalUndo.BeginScope("Ñòðåëêè ê êîëîíêàì")
Call ConnectColumns(sel.Item(1))
Call GlobalUndo.EndScope
End Sub
Public Sub RunNewRankTemplate()
Dim iMaster As Visio.Master
Set iMaster = AddNewRankTemplate
If Not iMaster Is Nothing Then _
Call UserInteraction.ShowMessage(IM_NEW_TEMPLATE, iMaster.Name)
End Sub
' =======
Private Function ScanHierarchy(sFileName$, iMode As IOMode, Optional nLayout As TLayout = T_LAYOUT_HORIZONTAL) As CDS_StaticHierarchy
Dim iData As CDS_StaticHierarchy
Select Case iMode
Case MODE_EXCEL: Set iData = ProcessExcel(sFileName)
Case MODE_WORD: Set iData = ProcessWord(sFileName)
Case MODE_VISIO: Set iData = ProcessVisio(VsoGetSelectedShapes(ThisDocument.Application), nLayout)
End Select
If iData Is Nothing Then _
Exit Function
If Not ValidateHierarchy(iData) Then
Call UserInteraction.ShowMessage(EM_INVALID_STRUCTURE)
Exit Function
End If
Set ScanHierarchy = iData
End Function
Private Function OutputHierarchy(iData As CDS_StaticHierarchy, oMode As IOMode)
Select Case oMode
Case MODE_EXCEL: Call GenerateExcel(iData, Not MasterDlg.XLExportAsTree)
Case MODE_WORD: Call GenerateWord(iData, MasterDlg.WordExportAsString)
Case MODE_VISIO: Call GenerateVisio(MainPage, iData, MasterDlg.AutoLayout)
End Select
End Function
Private Function ProcessVisio(iShapes As Collection, nLayout As TLayout) As CDS_StaticHierarchy
If iShapes.Count <> 1 Then
Call UserInteraction.ShowMessage(EM_INVALID_SELECTION)
Exit Function
End If
Set ProcessVisio = ScanVisioNode(iShapes(1), nLayout)
End Function
Private Function ProcessExcel(sFileName$) As CDS_StaticHierarchy
Dim xlApp As New API_XLWrapper
If xlApp.OpenDocument(sFileName, bReadOnly:=True) Is Nothing Then
Call UserInteraction.ShowMessage(EM_ERROR_LOADING_FILE)
Exit Function
End If
Dim iSource As Excel.Worksheet: Set iSource = xlApp.Document.Worksheets(1)
Call CSE_ProgressBar.Init("Ñêàíèðîâàíèå òàáëèöû", maxVal:=iSource.UsedRange.Rows.Count, curVal:=1)
Call CSE_ProgressBar.ShowModeless
Set ProcessExcel = ScanExcel(iSource)
If ProcessExcel Is Nothing Then _
Call UserInteraction.ShowMessage(EM_INVALID_FORMAT, xlApp.Document.Name)
Call Unload(CSE_ProgressBar)
Call xlApp.ReleaseDocument
End Function
Private Function ProcessWord(sFileName$) As CDS_StaticHierarchy
Dim wordApp As New API_WordWrapper
If wordApp.OpenDocument(sFileName, bReadOnly:=True) Is Nothing Then
Call UserInteraction.ShowMessage(EM_ERROR_LOADING_FILE)
Exit Function
End If
Call CSE_ProgressBar.Init("Ñêàíèðîâàíèå òåêñòà äîêóìåíòà", maxVal:=wordApp.Document.Paragraphs.Count, curVal:=0)
Call CSE_ProgressBar.ShowModeless
Set ProcessWord = ScanWord(wordApp.Document)
If ProcessWord Is Nothing Then _
Call UserInteraction.ShowMessage(EM_INVALID_FORMAT, wordApp.Document.Name)
Call Unload(CSE_ProgressBar)
Call wordApp.ReleaseDocument
End Function
Private Function GenerateWord(iData As CDS_StaticHierarchy, bAsString As Boolean)
If Not bAsString And iData.MaxDepth > WORD_HEADING_MAX_LEVEL Then
Call UserInteraction.ShowMessage(EM_WORD_MAX_LEVELS)
Exit Function
End If
Dim sTemplate$: sTemplate = VBA.Environ("AppData") & EXPORT_TEMPLATE_PATH
Dim wordApp As New API_WordWrapper
Dim iOutput As Word.Document: Set iOutput = wordApp.NewDocument(sTemplate)
Call iOutput.Range.Delete
Call wordApp.PauseUI
If Not bAsString Then
Call Hierarchy2Word(iData, iOutput)
Else
Call CSE_ProgressBar.Init("Ãåíåðàöèÿ ñòðîê...", maxVal:=iData.Size, curVal:=1)
Call CSE_ProgressBar.ShowModeless
Call Hierarchy2WordString(iData, iOutput)
Call Unload(CSE_ProgressBar)
End If
Call UserInteraction.ShowMessage(IM_EXPORT_OK, iData.Size, iData.MaxDepth)
Call wordApp.ResumeUI
End Function
Private Function GenerateExcel(iData As CDS_StaticHierarchy, bFlatData As Boolean)
Dim xlApp As New API_XLWrapper
Dim xlOut As Excel.Workbook: Set xlOut = xlApp.NewDocument
Call xlApp.PauseUI
Call Hierarchy2XL(iData, xlOut.Worksheets(1), bFlatData)
Call UserInteraction.ShowMessage(IM_EXPORT_OK, iData.Size, iData.MaxDepth)
Call xlApp.ResumeUI
End Function
Private Function GenerateVisio(iPage As Visio.Page, iData As CDS_StaticHierarchy, Optional bAutoVertical As Boolean = True)
Call ClearGeneratedShapes(iPage)
Dim iLayout As New LayoutConstruction: Call iLayout.Init(iPage, iData, bAutoVertical)
Dim vsoApp As New API_VsoWrapper: Call vsoApp.SetDocument(ThisDocument)
Call vsoApp.PauseUI
Call GlobalUndo.BeginScope("Ïîñòðîåíèå èåðàðõè÷åñêîãî äåðåâà")
Call CSE_ProgressBar.Init("Ñîçäàíèå óçëîâ è ñâÿçåé", maxVal:=iData.Size, curVal:=1)
Call CSE_ProgressBar.ShowModeless
Call iLayout.ConstructHNodes
DoEvents
Call CSE_ProgressBar.Init("Ðàñïîëîæåíèå óçëîâ", maxVal:=iData.Size)
Call iLayout.CalculateWidth
Call iLayout.RepositionAll
Call Unload(CSE_ProgressBar)
Call GlobalUndo.EndScope
Call vsoApp.ResumeUI
Call iPage.ResizeToFitContents
Call VsoShowWholePage(iPage)
Dim nWidth&: nWidth = iPage.PageSheet.CellsU("PageWidth").Result("mm")
Dim nHeight&: nHeight = iPage.PageSheet.CellsU("PageHeight").Result("mm")
Call UserInteraction.ShowMessage(IM_IMPORT_OK, iData.Size, iData.MaxDepth, nWidth, nHeight)
End Function

446
src/MainImpl.bas Normal file
View File

@ -0,0 +1,446 @@
Attribute VB_Name = "MainImpl"
Option Explicit
Public Function ClearGeneratedShapes(target As Visio.Page)
Dim nItem&: nItem = 1
Do While Not nItem > target.Shapes.Count
Dim vsoShape As Shape: Set vsoShape = target.Shapes(nItem)
Dim shpName$: shpName = vsoShape.Name
If Left(shpName, 2) = "#N" Or Left(shpName, 2) = "#C" Then
vsoShape.Delete
nItem = 1
Else
nItem = nItem + 1
End If
Loop
Call target.ResizeToFitContents
End Function
Public Function ValidateHierarchy(target As CDS_StaticHierarchy) As Boolean
ValidateHierarchy = False
If target.nodes_.Count = 0 Then _
Exit Function
If target.nodes_(1).rank_ <> 1 Then _
Exit Function
Dim iNode As CDS_NodeSH
For Each iNode In target.nodes_
If iNode.id_ <> 1 And iNode.rank_ <= 1 Then _
Exit Function
Next iNode
ValidateHierarchy = True
End Function
Public Function ConnectNodes(shape1 As Visio.Shape, shape2 As Visio.Shape, nLayout As TLayout) As Boolean
Dim iConnector As Visio.Shape: Set iConnector = CreateConnector(shape1, shape2, nLayout)
iConnector.CellsSRC(visSectionObject, visRowLock, visLockDelete).FormulaU = "0"
iConnector.Name = PREFIX_CONNECTOR & shape1.ID & "-" & shape2.ID
End Function
Public Function CreateConnector(sh1 As Visio.Shape, sh2 As Visio.Shape, nMode As TLayout) As Visio.Shape
Dim iPage As Visio.Page: Set iPage = sh1.ContainingPage
Dim prototype As Visio.Master: Set prototype = GetConnectionMaster(nMode)
Call sh1.AutoConnect(sh2, visAutoConnectDirNone, prototype)
Dim connector As Visio.Shape: Set connector = iPage.Shapes(iPage.Shapes.Count)
Select Case nMode
Case T_LAYOUT_HORIZONTAL
Call connector.CellsU("BeginX").GlueTo(sh1.CellsSRC(7, 0, 0))
Call connector.CellsU("EndX").GlueTo(sh2.CellsSRC(7, 1, 0))
Case T_LAYOUT_COLUMN_LEFT
Call connector.CellsU("BeginX").GlueTo(sh1.CellsSRC(7, 0, 0))
Call connector.CellsU("EndX").GlueTo(sh2.CellsSRC(7, 3, 0))
Case T_LAYOUT_COLUMN_RIGHT
Call connector.CellsU("BeginX").GlueTo(sh1.CellsSRC(7, 0, 0))
Call connector.CellsU("EndX").GlueTo(sh2.CellsSRC(7, 2, 0))
Case Else
Call connector.CellsU("BeginX").GlueTo(sh1.CellsSRC(7, 2, 0))
Call connector.CellsU("EndX").GlueTo(sh2.CellsSRC(7, 2, 0))
End Select
With connector
.CellsSRC(visSectionObject, visRowShapeLayout, visSLORouteStyle).FormulaU = 8
.CellsSRC(visSectionObject, visRowShapeLayout, visSLORouteStyle).FormulaU = _
prototype.Shapes(1).CellsSRC(visSectionObject, visRowShapeLayout, visSLORouteStyle).FormulaU
End With
Set CreateConnector = connector
End Function
Public Function ScanExcel(iSource As Excel.Worksheet) As CDS_StaticHierarchy
Dim iData As New CDS_StaticHierarchy
If iSource.Cells(1, 1) = vbNullString Then _
Exit Function
Dim nRow&
For nRow = 1 To iSource.UsedRange.Rows.Count Step 1
Dim iNode As CDS_NodeSH: Set iNode = iData.PushItem(iSource.Cells(nRow, 1))
If iNode Is Nothing Then _
Exit Function
Set iNode.data_ = ExrtactXLPayload(iSource, nRow)
Call CSE_ProgressBar.IncrementA
Next nRow
Set ScanExcel = iData
End Function
Public Function ScanWord(iSource As Word.Document) As CDS_StaticHierarchy
' Scan word file to create DB
Dim iData As New CDS_StaticHierarchy
Dim iRoot As CDS_NodeSH: Set iRoot = iData.PushItem(1)
Set iRoot.data_ = New VisioNode
iRoot.data_.text_ = iSource.Name
Dim aPar As Word.Paragraph
Dim pars As Word.Paragraphs: Set pars = iSource.Paragraphs
Dim nColor&
For Each aPar In pars
Dim nRank&: nRank = aPar.OutlineLevel
If nRank = wdOutlineLevelBodyText Then _
GoTo NEXT_PAR
Dim iPayload As VisioNode: Set iPayload = ExtractWordPayload(aPar.Range)
If iPayload Is Nothing Then _
GoTo NEXT_PAR
Dim iNode As CDS_NodeSH: Set iNode = iData.PushItem(nRank + 1)
If iNode Is Nothing Then _
Exit Function
Set iNode.data_ = iPayload
NEXT_PAR:
Call CSE_ProgressBar.IncrementA
Next aPar
Set ScanWord = iData
End Function
Public Function ScanVisioNode(iRoot As Visio.Shape, iLayout As TLayout) As CDS_StaticHierarchy
Dim iData As New CDS_StaticHierarchy
Call ScanShapeRecur(iRoot, iData, iLayout)
Set ScanVisioNode = iData
End Function
Public Function Hierarchy2XL(iData As CDS_StaticHierarchy, ByRef iOut As Excel.Worksheet, bFlatData As Boolean)
Dim iNode As CDS_NodeSH
Dim nRow&: nRow = 1
For Each iNode In iData.nodes_
If bFlatData Then
iOut.Cells(nRow, 1) = iNode.rank_
iOut.Cells(nRow, 2) = iNode.data_.text_
If iNode.data_.color_ <> COLOR_DEFAULT Then _
iOut.Cells(nRow, 2).Interior.Color = iNode.data_.color_
Else
iOut.Cells(nRow, iNode.rank_) = iNode.data_.text_
If iNode.data_.color_ <> COLOR_DEFAULT Then _
iOut.Cells(nRow, iNode.rank_).Interior.Color = iNode.data_.color_
End If
nRow = nRow + 1
Next iNode
End Function
Public Function Hierarchy2Word(iData As CDS_StaticHierarchy, ByRef target As Word.Document)
If target Is Nothing Then _
Exit Function
Dim iNode As CDS_NodeSH
Dim iInsert As Word.Range: Set iInsert = target.Range
For Each iNode In iData.nodes_
Dim nColor&: nColor = iNode.data_.color_
Set iInsert = WordAddLine(iNode.data_.text_, iInsert, WordStyleForRank(target, iNode.rank_))
If nColor = COLOR_DEFAULT Then
iInsert.Font.Shading.BackgroundPatternColor = wdColorAutomatic
Else
iInsert.Font.Shading.BackgroundPatternColor = nColor
End If
Next iNode
End Function
Public Function Hierarchy2WordString(iData As CDS_StaticHierarchy, ByRef target As Word.Document)
Const SYMBOL_MASK_TOTAL = "[@#]"
Const SYMBOL_MASK_BREAK = "@"
Dim iStack As New Collection
Call iStack.Add(iData.nodes_(1))
Dim childrenIDs As New Collection
Dim curChild&
Do While iStack.Count > 0
Dim iNode As CDS_NodeSH: Set iNode = iStack.Item(iStack.Count)
Dim sLastChar$: sLastChar = IIf(VBA.Len(iNode.data_.text_) > 0, VBA.Right(iNode.data_.text_, 1), "")
If sLastChar Like SYMBOL_MASK_TOTAL Or iNode.children_.Count = 0 Then
' Terminal
Call WordAddLine(StackToString(iStack), target.Range, "Îáû÷íûé")
Call iStack.Remove(iStack.Count)
Call CSE_ProgressBar.IncrementA
If sLastChar Like SYMBOL_MASK_BREAK Then _
Exit Do
ElseIf childrenIDs.Count < iStack.Count Then
' Moving down
Call iStack.Add(iNode.children_(1))
Call childrenIDs.Add(1)
Else
curChild = childrenIDs.Item(childrenIDs.Count)
If curChild < iNode.children_.Count Then
' Move to next child
Call childrenIDs.Remove(childrenIDs.Count)
Call iStack.Add(iNode.children_(curChild + 1))
Call childrenIDs.Add(curChild + 1)
Else
' Moving up
Call childrenIDs.Remove(childrenIDs.Count)
Call iStack.Remove(iStack.Count)
End If
End If
Loop
End Function
Public Function GenerateNamesFor(tRoot As Visio.Shape)
Dim oldName$: oldName = tRoot.Text
If oldName Like "#* *" Then
Call NameTheNode(VBA.Left(oldName, VBA.InStr(1, oldName, " ") - 1), tRoot)
Else
Call NameTheNode("", tRoot)
End If
End Function
Public Function TransformNodeLayout(rootShape As Visio.Shape)
Dim iData As CDS_StaticHierarchy: Set iData = ScanVisioNode(rootShape, T_LAYOUT_HORIZONTAL)
If iData Is Nothing Then
Call UserInteraction.ShowMessage(EM_INVALID_STRUCTURE)
Exit Function
End If
If Not iData.MaxDepth >= 2 Then
Call UserInteraction.ShowMessage(EM_CANNOT_TRANSFORM_DEEP)
Exit Function
End If
Dim rootNode As CDS_NodeSH: Set rootNode = iData.nodes_(1)
Dim nChildrenCount&: nChildrenCount = rootNode.children_.Count
If nChildrenCount < 2 Then _
Exit Function
Dim oChildren As New Collection
Dim aNode As CDS_NodeSH
For Each aNode In rootNode.children_
Call oChildren.Add(aNode.data_.shape_)
Next aNode
Dim theLayout As TLayout: theLayout = T_LAYOUT_HORIZONTAL
If oChildren(1).CellsU("PinY") - oChildren(2).CellsU("PinY") < FLOAT_ACCURACY Then _
theLayout = T_LAYOUT_VERTICAL
Call SetLayout(rootShape, oChildren, theLayout)
End Function
Public Function AddNewRankTemplate() As Visio.Master
Dim nRank&: nRank = 1
Do While Not FindMaster(ThisDocument, MASTER_RANK & nRank) Is Nothing
nRank = nRank + 1
Loop
Dim iMaster As Visio.Master: Set iMaster = ThisDocument.Masters.Add
iMaster.Name = MASTER_RANK & nRank
Dim iShape As Visio.Shape: Set iShape = iMaster.Drop(GetRankMaster(nRank - 1), 0, 0)
iShape.Name = SHAPE_RANK_MASTER & nRank
iShape.Text = "Óðîâåíü " & nRank
Set AddNewRankTemplate = iMaster
End Function
Public Function ConnectColumns(rootShape As Visio.Shape)
Dim iData As CDS_StaticHierarchy: Set iData = ScanVisioNode(rootShape, T_LAYOUT_VERTICAL)
If iData Is Nothing Then
Call UserInteraction.ShowMessage(EM_INVALID_STRUCTURE)
Exit Function
End If
Dim dMidX As Double: dMidX = rootShape.CellsU("PinX")
Dim iChild As Visio.Shape
Dim iConnect As Visio.Connect
Dim aNode As CDS_NodeSH
For Each aNode In iData.nodes_(1).children_
Set iChild = aNode.data_.shape_
For Each iConnect In iChild.FromConnects
If iConnect.FromSheet.Connects(1).ToSheet Is rootShape Then _
Call iConnect.FromSheet.Delete
Next iConnect
If iChild.CellsU("PinX") > dMidX Then
Call ConnectNodes(rootShape, iChild, T_LAYOUT_COLUMN_RIGHT)
Else
Call ConnectNodes(rootShape, iChild, T_LAYOUT_COLUMN_LEFT)
End If
Next aNode
End Function
' ========
Private Function NameTheNode(sID$, target As Visio.Shape)
Dim oldName$: oldName = target.Text
If VBA.IsNumeric(VBA.Left(oldName, 1)) Then _
oldName = VBA.Right(oldName, VBA.Len(oldName) - VBA.InStr(1, oldName, " "))
If sID <> vbNullString Then _
target.Text = sID & " " & oldName
Dim oChildren As New Collection: Set oChildren = GetChildrenOf(target)
If oChildren.Count = 0 Then _
Exit Function
Dim nIndex() As Long: nIndex = SortChildrenCount(oChildren)
Dim nItem&: nItem = 1
Dim iChild As Visio.Shape
For Each iChild In oChildren
If sID = vbNullString Then
Call NameTheNode(nIndex(nItem) & ".", iChild)
Else
Call NameTheNode(sID & nIndex(nItem) & ".", iChild)
End If
nItem = nItem + 1
Next iChild
End Function
Private Function SortChildrenCount(oChildren As Collection) As Long()
' using CountSort algorithm
Dim nIndicies() As Long
ReDim nIndicies(1 To oChildren.Count)
Dim sibling1 As Visio.Shape
Dim nItem1&: nItem1 = 1
For Each sibling1 In oChildren
nIndicies(nItem1) = 1
Dim dLeftX As Double: dLeftX = sibling1.CellsU("PinX") - sibling1.CellsU("Width") / 2#
Dim dTopY As Double: dTopY = sibling1.CellsU("PinY") + sibling1.CellsU("Height") / 2#
Dim nItem2&: nItem2 = 1
Dim sibling2 As Visio.Shape
For Each sibling2 In oChildren
If nItem1 = nItem2 Then _
GoTo NEXT_2
Dim dLeftX2 As Double: dLeftX2 = sibling2.CellsU("PinX") - sibling2.CellsU("Width") / 2#
Dim dTopY2 As Double: dTopY2 = sibling2.CellsU("PinY") + sibling2.CellsU("Height") / 2#
If dTopY + FLOAT_ACCURACY < dTopY2 Then
nIndicies(nItem1) = nIndicies(nItem1) + 1
ElseIf Abs(dTopY - dTopY2) < FLOAT_ACCURACY And dLeftX - FLOAT_ACCURACY > dLeftX2 Then
nIndicies(nItem1) = nIndicies(nItem1) + 1
End If
NEXT_2:
nItem2 = nItem2 + 1
Next sibling2
nItem1 = nItem1 + 1
Next sibling1
SortChildrenCount = nIndicies
End Function
Public Function SetLayout(theRoot As Visio.Shape, oChildren As Collection, theLayout As TLayout)
Dim baseXPos As Double: baseXPos = theRoot.CellsU("PinX")
Dim baseYPos As Double: baseYPos = theRoot.CellsU("PinY") - theRoot.CellsU("Height") / 2#
Dim iChild As Visio.Shape
If theLayout = T_LAYOUT_HORIZONTAL Then
For Each iChild In oChildren
baseXPos = baseXPos - GAP_HORIZONTAL / 2# - iChild.CellsU("Width") / 2#
Next iChild
End If
Dim cnt As Visio.Connect
For Each iChild In oChildren
Dim dWidth As Double: dWidth = iChild.CellsU("Width")
Dim dHeight As Double: dHeight = iChild.CellsU("Height")
For Each cnt In iChild.FromConnects
If cnt.FromSheet.Connects(1).ToSheet Is theRoot Then _
Call cnt.FromSheet.Delete
Next cnt
iChild.CellsU("PinY") = baseYPos - GAP_VERTICAL - dHeight / 2#
If theLayout = T_LAYOUT_VERTICAL Then
iChild.CellsU("PinX") = baseXPos - theRoot.CellsU("Width") / 2# + dWidth / 2#
baseYPos = baseYPos - GAP_VERTICAL - dHeight
Else
iChild.CellsU("PinX") = baseXPos + GAP_HORIZONTAL + dWidth / 2#
baseXPos = baseXPos + GAP_HORIZONTAL + dWidth
End If
Call ConnectNodes(theRoot, iChild, theLayout)
Next iChild
End Function
Private Function ScanShapeRecur(target As Visio.Shape, ByRef iData As CDS_StaticHierarchy, _
aLayout As TLayout, Optional iParent As CDS_NodeSH = Nothing)
If target Is Nothing Then _
Exit Function
Dim nRank&: nRank = 1
If Not iParent Is Nothing Then _
nRank = iParent.rank_ + 1
Dim iNode As CDS_NodeSH: Set iNode = iData.PushItem(nRank)
Set iNode.data_ = ExtractVsoPayload(target)
Dim oChildren As Collection: Set oChildren = GetChildrenOf(target)
If oChildren.Count = 0 Then _
Exit Function
Dim nIndicies() As Long: nIndicies = SortChildrenLayout(oChildren, target, aLayout)
Dim n&
For n = 1 To oChildren.Count
Call ScanShapeRecur(oChildren(nIndicies(n)), iData, aLayout, iNode)
Next n
End Function
Private Function SortChildrenLayout(oChildren As Collection, aParent As Visio.Shape, iLayout As TLayout) As Long()
' using bubble sort algorithm
Dim nCount&: nCount = oChildren.Count
Dim nIndicies() As Long
ReDim nIndicies(1 To nCount)
Dim n&
For n = 1 To nCount Step 1
nIndicies(n) = n
Next n
If nCount = 1 Then
SortChildrenLayout = nIndicies
Exit Function
End If
Dim k&
For n = 1 To nCount - 1 Step 1
For k = n + 1 To nCount
If CompareChildren(aParent, oChildren(nIndicies(n)), oChildren(nIndicies(k)), iLayout) Then
nIndicies(n) = nIndicies(n) + nIndicies(k)
nIndicies(k) = nIndicies(n) - nIndicies(k)
nIndicies(n) = nIndicies(n) - nIndicies(k)
End If
Next k
Next n
SortChildrenLayout = nIndicies
End Function
Private Function CompareChildren(aParent As Visio.Shape, child1 As Visio.Shape, child2 As Visio.Shape, iLayout As TLayout) As Boolean
Select Case iLayout
Case T_LAYOUT_RADIAL
CompareChildren = _
aParent.DistanceFrom(child1, visSpatialIncludeDataGraphics) _
- aParent.DistanceFrom(child2, visSpatialIncludeDataGraphics) _
> FLOAT_ACCURACY
Case T_LAYOUT_HORIZONTAL
CompareChildren = child1.CellsU("PinX") - child2.CellsU("PinX") > FLOAT_ACCURACY
Case T_LAYOUT_VERTICAL
CompareChildren = child2.CellsU("PinY") - child1.CellsU("PinY") > FLOAT_ACCURACY
End Select
End Function
Private Function StackToString(iStack As Collection) As String
Dim theString$: theString = ""
Dim bFirst As Boolean: bFirst = True
Dim iNode As CDS_NodeSH
For Each iNode In iStack
If Not bFirst Then _
theString = theString & " "
bFirst = False
theString = theString & iNode.data_.text_
Next iNode
StackToString = theString
End Function

123
src/MasterDlg.frm Normal file
View File

@ -0,0 +1,123 @@
VERSION 5.00
Begin {C62A69F0-16DC-11CE-9E98-00AA00574A4F} MasterDlg
ClientHeight = 5925
ClientLeft = 120
ClientTop = 465
ClientWidth = 5250
OleObjectBlob = "MasterDlg.frx":0000
StartUpPosition = 1 'CenterOwner
End
Attribute VB_Name = "MasterDlg"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = False
Option Explicit
Public bCanceled As Boolean
Private Sub UserForm_Initialize()
bCanceled = True
Me.StartUpPosition = visCenter
UpdateState
End Sub
Public Property Get InputMode() As IOMode
' Get Input mode
InputMode = MODE_EXCEL
If btnInputOpt1.Value = True Then InputMode = MODE_WORD
If btnInputOpt3.Value = True Then InputMode = MODE_VISIO
End Property
Public Property Get OutputMode() As IOMode
' Get output mode
OutputMode = MODE_EXCEL
If btnOutputOpt1.Value = True Then OutputMode = MODE_WORD
If btnOutputOpt3.Value = True Then OutputMode = MODE_VISIO
End Property
Public Property Get InputLayout() As TLayout
InputLayout = T_LAYOUT_RADIAL
If btnModeX.Value = True Then InputLayout = T_LAYOUT_HORIZONTAL
If btnModeY.Value = True Then InputLayout = T_LAYOUT_VERTICAL
End Property
Public Property Get AutoLayout() As Boolean
AutoLayout = CheckBox1.Value
End Property
Public Property Get WordExportAsString() As Boolean
WordExportAsString = CheckBox2.Value
End Property
Public Property Get XLExportAsTree() As Boolean
XLExportAsTree = CBExcel.Value
End Property
Public Property Get InputFile() As String
InputFile = textInput.Text
End Property
Private Sub btnInput_Click()
If InputMode = MODE_VISIO Then
Call UserInteraction.ShowMessage(EM_VISIO_INPUT)
Exit Sub
End If
Dim sFile$: sFile = UserInteraction.PromptFile(ActiveDocument.Path, "Âûáåðèòå âõîäíîé ôàéë", bNewApplication:=True)
If sFile = vbNullString Then _
Exit Sub
textInput.Text = sFile
End Sub
Private Sub btnInputOpt1_Click()
UpdateState
End Sub
Private Sub btnInputOpt2_Click()
UpdateState
End Sub
Private Sub btnInputOpt3_Click()
UpdateState
End Sub
Private Sub btnOutputOpt1_Click()
UpdateState
End Sub
Private Sub btnOutputOpt2_Click()
UpdateState
End Sub
Private Sub btnOutputOpt3_Click()
UpdateState
End Sub
Private Function UpdateState()
CheckBox2.Visible = btnOutputOpt1.Value
CheckBox1.Visible = btnOutputOpt3.Value
CBExcel.Visible = btnOutputOpt2.Value
lblLevel.Visible = btnInputOpt3.Value
btnModeX.Visible = btnInputOpt3.Value
btnModeY.Visible = btnInputOpt3.Value
btnModeRad.Visible = btnInputOpt3.Value
End Function
Private Sub UserForm_Activate()
bCanceled = True
End Sub
Private Sub OkBtn_Click()
bCanceled = False
Me.Hide
End Sub
Private Sub CancelBtn_Click()
bCanceled = True
Me.Hide
End Sub

BIN
src/MasterDlg.frx Normal file

Binary file not shown.

22
src/ThisDocument.cls Normal file
View File

@ -0,0 +1,22 @@
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "ThisDocument"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = True
Attribute VB_Exposed = True
Option Explicit
Private Sub Document_DocumentOpened(ByVal iDoc As IVDocument)
If iDoc.ID <> ThisDocument.ID Then _
Exit Sub
Dim sCmd$: sCmd = OfficeCommandLine
If VBA.InStr(1, sCmd, "/automation", vbTextCompare) <> 0 Then _
Exit Sub
Call StartVersionCheck
End Sub

20
src/VisioNode.cls Normal file
View File

@ -0,0 +1,20 @@
VERSION 1.0 CLASS
BEGIN
MultiUse = -1 'True
END
Attribute VB_Name = "VisioNode"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = False
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
'------------ Visio hierarchy node ---------------
Option Explicit
Public text_ As String
Public color_ As Long
Public shape_ As Visio.Shape
Private Sub Class_Initialize()
color_ = COLOR_DEFAULT
End Sub

94
src/z_UIMessages.bas Normal file
View File

@ -0,0 +1,94 @@
Attribute VB_Name = "z_UIMessages"
' Messaging module
Option Private Module
Option Explicit
Public Enum MsgCode
MSG_OK = 0
EM_VISIO_INPUT
EM_FILENAME_EMPTY
EM_INVALID_SELECTION
EM_ERROR_LOADING_FILE
EM_INVALID_FILE_FORMAT
EM_WORD_MAX_LEVELS
EM_INVALID_STRUCTURE
EM_CANNOT_TRANSFORM_DEEP
EM_INVALID_FORMAT
IM_EXPORT_OK
IM_IMPORT_OK
IM_NEW_TEMPLATE
' QM_CODE_DELETE_CONFIRM
End Enum
Private g_UI As API_UserInteraction
Public Function UserInteraction() As API_UserInteraction
If g_UI Is Nothing Then _
Set g_UI = New API_UserInteraction
Set UserInteraction = g_UI
End Function
Public Function SetUserInteraction(newUI As API_UserInteraction)
Set g_UI = newUI
End Function
Public Function UIShowMessage(theCode As MsgCode, ParamArray params() As Variant)
Dim unwrapped As Variant: unwrapped = params
unwrapped = FixForwardedParams(unwrapped)
Select Case theCode
Case EM_VISIO_INPUT
Call MsgBox("Ðåæèì ââîäà èç Visio íå òðåáóåò óêàçàíèÿ âõîäà", vbInformation)
Case EM_FILENAME_EMPTY
Call MsgBox("Íå óêàçàí ïóòü ê âõîäíîìó ôàéëó", vbExclamation)
Case EM_INVALID_SELECTION
Call MsgBox("Äëÿ äàííîé îïåðàöèè íåîáõîäèìî âûäåëèòü îäíó êîðíåâóþ ôîðìó", vbExclamation)
Case EM_ERROR_LOADING_FILE
Call MsgBox("Îøèáêà ïðè çàãðóçêå ôàéëà", vbExclamation)
Case EM_INVALID_FILE_FORMAT
Call MsgBox("Îøèáêà ïðè çàãðóçêå äàííûõ!" & vbNewLine & _
"Ïðîâåðüòå ñîîòâåòñòâèå âõîäà çàäàííîìó ôîðìàòó", vbExclamation)
Case EM_WORD_MAX_LEVELS
Call MsgBox("Ýêñïîðò â Word âîçìîæåí òîëüêî äëÿ 9 è ìåíåå óðîâíåé!", vbCritical)
Case EM_INVALID_STRUCTURE
Call MsgBox("Íåêîððåêòíàÿ ñòðóêòóðà èåðàðõèè", vbCritical)
Case EM_CANNOT_TRANSFORM_DEEP
Call MsgBox("Òðàíñôîðìèðîâàòü ìîæíî òîëüêî óçëû íå èìåþùèå ""âíóêîâ""", vbCritical)
Case EM_INVALID_FORMAT
Call MsgBox(Fmt("Ñîäåðæèìîå ôàéëà {1} íå ñîîòâåòñòâóåò òðåáóåìîìó ôîðìàòó", unwrapped), vbExclamation)
Case IM_EXPORT_OK
Call MsgBox(Fmt("Ýêñïîðò ïðîøåë óñïåøíî!" & vbNewLine & _
"Âñåãî óçëîâ: {1}" & vbNewLine & _
"Ìàêñèìàëüíàÿ ãëóáèíà: {2}", unwrapped), vbInformation)
Case IM_IMPORT_OK
Call MsgBox(Fmt("Èìïîðò äàííûõ ïðîøåë óñïåøíî!" & vbNewLine & _
"Âñåãî óçëîâ: {1}" & vbNewLine & _
"Ìàêñèìàëüíàÿ ãëóáèíà: {2}" & vbNewLine & _
"Ðàçìåð ëèñòà: {3} x {4}", unwrapped), vbInformation)
Case IM_NEW_TEMPLATE
Call MsgBox(Fmt("Íîâûå øàáëîí ñîçäàí óñïåøíî: {1}", unwrapped), vbInformation)
Case Else
Call MsgBox("Invalid message code", vbCritical)
End Select
End Function
Public Function UIAskQuestion(theCode As MsgCode, ParamArray params() As Variant) As Boolean
Dim unwrapped As Variant: unwrapped = params
unwrapped = FixForwardedParams(unwrapped)
Dim answer&: answer = vbNo
Select Case theCode
' Case QM_CODE_DELETE_CONFIRM
' answer = MsgBox("Are you sure you want to delete ALL macros from target file?", vbYesNo + vbQuestion)
Case Else
Call MsgBox("Invalid message code", vbCritical)
End Select
UIAskQuestion = answer = vbYes
End Function

20
src/z_UIRibbon.bas Normal file
View File

@ -0,0 +1,20 @@
Attribute VB_Name = "z_UIRibbon"
Option Explicit
Public Sub OnAction(iControl As IRibbonControl)
Select Case iControl.ID
Case "LoadExcel": Call RunImportXL
Case "LoadWord": Call RunImportWord
Case "Clear": Call RunClearScheme
Case "MasterIO": Call RunMaster
Case "Transform": Call RunTransform
Case "ConnectColumns": Call RunConnectColumns
Case "AutoName": Call RunRename
Case "ContextTransform": Call RunTransform
Case "NewRankTemplate": Call RunNewRankTemplate
Case Else: Call CC_DispatchCommand(iControl.ID)
End Select
End Sub

2
ui/.rels Normal file
View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships"><Relationship Id="rId3" Type="http://schemas.openxmlformats.org/package/2006/relationships/metadata/core-properties" Target="docProps/core.xml"/><Relationship Id="rId2" Type="http://schemas.microsoft.com/office/2007/relationships/ui/extensibility" Target="visio/customUI/customUI1.xml"/><Relationship Id="rId1" Type="http://schemas.microsoft.com/visio/2010/relationships/document" Target="visio/document.xml"/><Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/custom-properties" Target="docProps/custom.xml"/><Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/extended-properties" Target="docProps/app.xml"/></Relationships>

90
ui/customUI1.xml Normal file
View File

@ -0,0 +1,90 @@
<?xml version="1.0" encoding="utf-8"?>
<customUI xmlns="http://schemas.microsoft.com/office/2009/07/customui">
<ribbon>
<tabs>
<tab id="tab1" label="ИЕРАРХИЗАТОР">
<group id="IO" label="Загрузка/очистка иерархии">
<button id="LoadExcel" size="large"
label="Excel"
supertip="Загрузка иерархии из Excel"
imageMso="FileSaveAsExcelXlsx"
onAction="OnAction"/>
<button id="LoadWord" size="large"
label="Word"
supertip="Загрузка иерархии из Word"
imageMso="FileSaveAsWordDocx"
onAction="OnAction"/>
<button id="MasterIO" size="large"
label="Мастер"
supertip="Мастер преобразоваия форматов"
imageMso="QueryBuilder"
onAction="OnAction"/>
</group>
<group id="Navigation" label="Навигация">
<button id="ExpandStraight" size="large"
label="Потомки"
supertip="Добавить в выделение всех потомков"
imageMso="DiagramExpandClassic"
onAction="OnAction"/>
<button id="ExpandReverse" size="large"
label="Предки"
supertip="Добавить в выделение всех предков"
imageMso="DiagramFitToContentsClassic"
onAction="OnAction"/>
<button id="GotoLinkBegin"
label="В начало"
supertip="Переход к началу выделенной стрелки [Ctrl+Shift+Q]"
imageMso="JotNavUIFindRTL"
onAction="OnAction"/>
<button id="GotoLinkEnd"
label="В конец"
supertip="Переход к концу выделенной стрелки [Ctrl+Shift+E]"
imageMso="JotNavUIFind"
onAction="OnAction"/>
</group>
<group id="Actions" label="Операции">
<button id="Transform"
label="Трансформация"
supertip="Преобразование подуровня для выделенного узла в вертикальный / горизонтальный вид (CTRL+SHIFT+Z)"
imageMso="CircularReferences"
onAction="OnAction"/>
<button id="ConnectColumns"
label="Из центра"
supertip="Привязать коннекторы потомков к центру нижней границы"
imageMso="ArrowsMore"
onAction="OnAction"/>
<button id="AutoName"
label="Автонумерация"
supertip="Нумерация узлов поддерева согласно уровням иерархии"
imageMso="ConstrainNumeric"
onAction="OnAction"/>
<button id="Clear"
label="Очистка"
supertip="Очистка иерархии (CTRL+SHIFT+X)"
size="large"
imageMso="TableDeleteRowsAndColumnsMenuWord"
onAction="OnAction"/>
<button id="NewRankTemplate"
label="Новый шаблон"
supertip="Добавить шаблон для следующего уровня"
size="large"
imageMso="ChartSaveTemplates"
onAction="OnAction"/>
</group>
</tab>
</tabs>
</ribbon>
<contextMenus>
<contextMenu idMso="ContextMenuShape">
<button id="ContextTransform"
label="Трансформация"
supertip="Преобразование подуровня для выделенного узла в вертикальный / горизонтальный вид"
imageMso="CircularReferences"
onAction="OnAction"/>
</contextMenu>
</contextMenus>
</customUI>