Below is the visual basic souce code for progress dots. Of course, it relies on a UserForm to get options from the user; this can be found in the powerpoint source for the macro.
' Progress Dots ' Version 0.1 ' ' (C) Copyright 2007 Edward Lopr ' ' This macro adds a toolbar to PowerPoint that can be used to create a ' "progress bar" for your presentation. A series of dots is drawn across ' the top border, corresponding to slides; and these dots change color ' as you advance through the presentation. The dots can be grouped into ' "sections," to indicate the overall structure of your talk. E.g., the ' generated progress bar could look something like this: ' ' Intro Topic 1 Topic 2 Conclusions ' # # # # # # * * * * * * * * * * * * * ' ' The toolbar includes four buttons: ' ' - Create: opens an options dialogue to customize the progress bar; ' and then draws the progress bar on each slide. (This can be slow ' for large powerpoint presentations.) ' ' - Refresh: redraws the progress bar. You will need to use this ' after adding or removing slides, or section labels. ' ' - Delete: removes the progress bar from each slide. ' ' - Section: adds a section label to the current slide. All slides ' from the current slide to the next slide that you asssign a ' section label to are considered to be part of the same section, ' and will be displayed in a group. E.g., in the example above, ' four slides are marked with section labels: the first is marked ' with "Intro," the fifth with "Topic 1," the ninth with "Topic 2," ' and the eighteenth with "Conclusions." ' ' This is the first program I've written in Visual Basic, so the code may ' not be as clean as it should be. ' ' License: ' ========================================================================= ' Permission is hereby granted, free of charge, to any person obtaining ' a copy of this software and any associated documentation files (the ' "Software"), to deal in the Software without restriction, including ' without limitation the rights to use, copy, modify, merge, publish, ' distribute, sublicense, and/or sell copies of the Software, and to ' permit persons to whom the Software is furnished to do so, subject to ' the following conditions: ' ' The above copyright notice and this permission notice shall be included ' in all copies or substantial portions of the Software. ' ' The software is provided "as is", without warranty of any kind, express ' or implied, including but not limited to the warranties of ' merchantability, fitness for a particular purpose and noninfringement. ' In no event shall the authors or copyright holders be liable for any ' claim, damages or other liability, whether in an action of contract, tort ' or otherwise, arising from, out of or in connection with the software or ' the use or other dealings in the software. ' ========================================================================= Option Explicit Public OptionsOk As Boolean Type ColorType Red As Integer Green As Integer Blue As Integer End Type Type ProgressDotsOptions LeftMargin As Double RightMargin As Double yMargin As Double textHeight As Double dotSize As Double SectionGap As Double ' = secMargin/dotMargin seenDotColor As ColorType unseenDotColor As ColorType seenTextColor As ColorType unseenTextColor As ColorType fontName As String End Type ' We don't support more than 100 sections. Type ProgressDotsSectionInfo startSlide As Long numSections As Long sectTitles(100) As String sectStart(100) As Long End Type Function SerializeColor(c As ColorType) As String SerializeColor = CStr(c.Red) & ":" & CStr(c.Green) & ":" & CStr(c.Blue) End Function Function DeserializeColor(s As String) As ColorType Dim words() As String words() = Split(s, ":") DeserializeColor.Red = CInt(words(0)) DeserializeColor.Green = CInt(words(1)) DeserializeColor.Blue = CInt(words(2)) End Function Function ColorToRGB(c As ColorType) As Long ColorToRGB = RGB(c.Red, c.Green, c.Blue) End Function Function SerializeProgressOptions(theOptions As ProgressDotsOptions) As String With theOptions: SerializeProgressOptions = "version3 " & _ .LeftMargin & " " & .RightMargin & " " & .yMargin & " " & _ .textHeight & " " & .dotSize & " " & .SectionGap & " " & _ SerializeColor(.seenDotColor) & " " & _ SerializeColor(.unseenDotColor) & " " & _ SerializeColor(.seenTextColor) & " " & _ SerializeColor(.unseenTextColor) & " " & _ Replace(.fontName, " ", "#") End With End Function Function DeserializeProgressOptions(s As String) As ProgressDotsOptions ' Split the string into words Dim words() As String words() = Split(s) ' Use those words to populate theOptions. Dim theOptions As ProgressDotsOptions If words(0) <> "version3" Then theOptions = getDefaultOptions() Else theOptions.LeftMargin = CDbl(words(1)) theOptions.RightMargin = CDbl(words(2)) theOptions.yMargin = CDbl(words(3)) theOptions.textHeight = CDbl(words(4)) theOptions.dotSize = CDbl(words(5)) theOptions.SectionGap = CDbl(words(6)) theOptions.seenDotColor = DeserializeColor(words(7)) theOptions.unseenDotColor = DeserializeColor(words(8)) theOptions.seenTextColor = DeserializeColor(words(9)) theOptions.unseenTextColor = DeserializeColor(words(10)) theOptions.fontName = Replace(words(11), "#", " ") End If ' Return it DeserializeProgressOptions = theOptions End Function Function getDefaultOptions() As ProgressDotsOptions With getDefaultOptions: .LeftMargin = 50 .RightMargin = 50 .yMargin = 5 .textHeight = 12 .dotSize = 7.8 .seenDotColor.Red = 0 .seenDotColor.Green = 128 .seenDotColor.Blue = 128 .unseenDotColor.Red = 186 .unseenDotColor.Green = 223 .unseenDotColor.Blue = 226 .seenTextColor.Red = 0 .seenTextColor.Green = 128 .seenTextColor.Blue = 128 .unseenTextColor.Red = 186 .unseenTextColor.Green = 223 .unseenTextColor.Blue = 226 .SectionGap = 8 .fontName = "arial" End With End Function Sub Auto_Open() Dim TOOLBAR_STYLE, DEBUG_TOOLBAR TOOLBAR_STYLE = msoButtonIconAndCaption DEBUG_TOOLBAR = True Dim oToolbar As CommandBar Dim oSetSectionButton As CommandBarButton Dim oAddProgressButton As CommandBarButton Dim oDelProgressButton As CommandBarButton Dim oRefreshButton As CommandBarButton Dim oOptionsButton As CommandBarButton Dim MyToolbar As String ' Give the toolbar a name MyToolbar = "Progress Dots" On Error Resume Next ' so that it doesn't stop on the next line if the toolbar's already there ' Create the toolbar; PowerPoint will error if it already exists Set oToolbar = CommandBars.Add(name:=MyToolbar, _ Position:=msoBarFloating, Temporary:=True) If Err.Number <> 0 Then If DEBUG_TOOLBAR Then CommandBars(MyToolbar).Delete End If ' The toolbar's already there, so we have nothing to do Exit Sub End If On Error GoTo ErrorHandler Set oSetSectionButton = oToolbar.Controls.Add(Type:=msoControlButton) With oSetSectionButton .DescriptionText = "Begin progress bar section" .Caption = "Section" .OnAction = "SetSection" .Style = TOOLBAR_STYLE .FaceId = 598 End With Set oAddProgressButton = oToolbar.Controls.Add(Type:=msoControlButton) With oAddProgressButton .DescriptionText = "Add a progress bar" .Caption = "Create" .OnAction = "AddProgressBar" .Style = TOOLBAR_STYLE .FaceId = 213 End With Set oDelProgressButton = oToolbar.Controls.Add(Type:=msoControlButton) With oDelProgressButton .DescriptionText = "Remove the progress bar" .Caption = "Delete" .OnAction = "DelProgressBar" .Style = TOOLBAR_STYLE .FaceId = 214 End With Set oRefreshButton = oToolbar.Controls.Add(Type:=msoControlButton) With oRefreshButton .DescriptionText = "Refresh progress bar" .Caption = "Refresh" .OnAction = "RefreshProgressBar" .Style = TOOLBAR_STYLE .FaceId = 1020 End With oToolbar.Top = 150 oToolbar.Left = 150 oToolbar.Visible = True NormalExit: Exit Sub ' so it doesn't go on to run the errorhandler code ErrorHandler: 'Just in case there is an error MsgBox Err.Number & vbCrLf & Err.Description Resume NormalExit: End Sub Sub SetSection() Dim curSlide As slide Dim strTitle As String ' Get the current slide. Set curSlide = Application.ActiveWindow.View.slide ' Ask for a tilte strTitle = InputBox("Enter a title for the section " & _ "starting on slide " & curSlide.SlideNumber & ".", _ "Progress Dots: Section Title", _ curSlide.Tags.Item("progress dots title")) ' Store the title as a tag. Call curSlide.Tags.Add("progress dots title", strTitle) End Sub Sub optionsToForm(theOptions As ProgressDotsOptions) Dim sectNum As Long Dim sectionInfo As ProgressDotsSectionInfo ' Display the spacing parameters OptionsForm.LeftMargin.Text = CStr(theOptions.LeftMargin) OptionsForm.RightMargin.Text = CStr(theOptions.RightMargin) OptionsForm.TopMargin.Text = CStr(theOptions.yMargin) OptionsForm.SectionGap.Value = 10 * theOptions.SectionGap OptionsOk = False ' Display a combobox for the font. OptionsForm.fontName.Clear Call OptionsForm.fontName.AddItem("Times New Roman") Call OptionsForm.fontName.AddItem("Arial") Call OptionsForm.fontName.AddItem("Courier") OptionsForm.fontName.Value = theOptions.fontName OptionsForm.fontSize.Value = CStr(theOptions.textHeight) ' Display the colors OptionsForm.dotSeenR.Value = theOptions.seenDotColor.Red OptionsForm.dotSeenG.Value = theOptions.seenDotColor.Green OptionsForm.dotSeenB.Value = theOptions.seenDotColor.Blue OptionsForm.dotUnseenR.Value = theOptions.unseenDotColor.Red OptionsForm.dotUnseenG.Value = theOptions.unseenDotColor.Green OptionsForm.dotUnseenB.Value = theOptions.unseenDotColor.Blue OptionsForm.textSeenR.Value = theOptions.seenTextColor.Red OptionsForm.textSeenG.Value = theOptions.seenTextColor.Green OptionsForm.textSeenB.Value = theOptions.seenTextColor.Blue OptionsForm.textUnseenR.Value = theOptions.unseenTextColor.Red OptionsForm.textUnseenG.Value = theOptions.unseenTextColor.Green OptionsForm.textUnseenB.Value = theOptions.unseenTextColor.Blue OptionsForm.dotSize.Value = theOptions.dotSize * 5 ' Display the list of sections. sectionInfo = findSections() OptionsForm.SectionList.Clear For sectNum = 1 To sectionInfo.numSections Call OptionsForm.SectionList.AddItem( _ CStr(sectionInfo.sectStart(sectNum)) & ": " & _ sectionInfo.sectTitles(sectNum)) Next sectNum End Sub Function optionsFromForm() As ProgressDotsOptions optionsFromForm = getDefaultOptions() ' Copy the user input back to the options structure. optionsFromForm.LeftMargin = CDbl(OptionsForm.LeftMargin.Text) optionsFromForm.RightMargin = CDbl(OptionsForm.RightMargin.Text) optionsFromForm.yMargin = CDbl(OptionsForm.TopMargin.Text) optionsFromForm.SectionGap = OptionsForm.SectionGap.Value * 0.1 optionsFromForm.fontName = OptionsForm.fontName.Value optionsFromForm.textHeight = CInt(OptionsForm.fontSize.Value) optionsFromForm.seenDotColor.Red = CInt(OptionsForm.dotSeenR.Value) optionsFromForm.seenDotColor.Green = CInt(OptionsForm.dotSeenG.Value) optionsFromForm.seenDotColor.Blue = CInt(OptionsForm.dotSeenB.Value) optionsFromForm.unseenDotColor.Red = CInt(OptionsForm.dotUnseenR.Value) optionsFromForm.unseenDotColor.Green = CInt(OptionsForm.dotUnseenG.Value) optionsFromForm.unseenDotColor.Blue = CInt(OptionsForm.dotUnseenB.Value) optionsFromForm.seenTextColor.Red = CInt(OptionsForm.textSeenR.Value) optionsFromForm.seenTextColor.Green = CInt(OptionsForm.textSeenG.Value) optionsFromForm.seenTextColor.Blue = CInt(OptionsForm.textSeenB.Value) optionsFromForm.unseenTextColor.Red = CInt(OptionsForm.textUnseenR.Value) optionsFromForm.unseenTextColor.Green = CInt(OptionsForm.textUnseenG.Value) optionsFromForm.unseenTextColor.Blue = CInt(OptionsForm.textUnseenB.Value) optionsFromForm.dotSize = OptionsForm.dotSize.Value * 0.2 End Function Sub customizeOptions() Dim s As String Dim theOptions As ProgressDotsOptions Dim thePresentation As Presentation ' Load the options. Set thePresentation = ActivePresentation s = thePresentation.Tags.Item("__progress-dots-config__") If s = "" Then s = SerializeProgressOptions(getDefaultOptions()) theOptions = DeserializeProgressOptions(s) ' Copy them to the form Call optionsToForm(theOptions) ' Show the form. OptionsForm.Show ' Save the options If OptionsOk Then theOptions = optionsFromForm() Debug.Print "Saving options..." s = SerializeProgressOptions(theOptions) Call thePresentation.Tags.Add("__progress-dots-config__", s) End If End Sub Sub AddProgressBar() ' make sure there's something to do. If ActivePresentation.Slides.Count = 1 Then MsgBox ("Add some more slides first") Exit Sub End If ' Set options. Call customizeOptions ' If everything went well, draw the progress bar. If OptionsOk Then Call RefreshProgressBar End If End Sub Sub RefreshProgressBar() Dim s As String Dim theOptions As ProgressDotsOptions If ActivePresentation.Slides.Count = 1 Then MsgBox ("Add some more slides first") Exit Sub End If ' Load the options. s = ActivePresentation.Tags.Item("__progress-dots-config__") If s = "" Then s = SerializeProgressOptions(getDefaultOptions()) theOptions = DeserializeProgressOptions(s) ' Update the progress bar. Call DelProgressBar Call MakeProgressBar(theOptions) End Sub Function findSections() As ProgressDotsSectionInfo Dim slideNum, sectionNum As Long Dim curTitle As String ' Initialize the 0th group. findSections.sectTitles(0) = "" findSections.sectStart(0) = 1 ' Scan for section titles. sectionNum = 0 Debug.Print "Scanning for sections..." For slideNum = 1 To ActivePresentation.Slides.Count curTitle = ActivePresentation.Slides(slideNum).Tags.Item("progress dots title") If (curTitle <> "") Then sectionNum = sectionNum + 1 findSections.sectTitles(sectionNum) = curTitle findSections.sectStart(sectionNum) = slideNum If sectionNum = 1 Then findSections.startSlide = slideNum End If Next slideNum ' If no sections were found, make a single "fake" section If sectionNum = 0 And ActivePresentation.Slides.Count > 1 Then sectionNum = 1 findSections.sectTitles(1) = "" findSections.sectStart(1) = 2 findSections.startSlide = 2 End If ' Record the end slide as the "next" section start -- this makes ' calculations easier. findSections.sectStart(sectionNum + 1) = _ ActivePresentation.Slides.Count + 1 ' Return the information we found. findSections.numSections = sectionNum End Function Sub MakeProgressBar(theOptions As ProgressDotsOptions) Dim sectinfo As ProgressDotsSectionInfo Dim slideNum As Long ' Scan for sections. sectinfo = findSections() ' If we didn't find any sections, there's nothing more to do. If sectinfo.numSections = 0 Then MsgBox ("Add some more slides first!") Exit Sub End If ' Loop through slides, and draw a progress bar on each one. Debug.Print "Drawing Progress bars..." For slideNum = sectinfo.startSlide To ActivePresentation.Slides.Count Debug.Print " Slide " & slideNum Call DrawProgressBarOnSlide(slideNum, sectinfo, _ theOptions, "__progress-dots__") Next slideNum End Sub Sub DrawProgressBarOnSlide(slideNum As Long, _ sectinfo As ProgressDotsSectionInfo, _ theOptions As ProgressDotsOptions, _ shapeTag As String) Dim x, labelWidth, labelLeft, sectionMargin, dotMargin As Double Dim numDotsInSection, dotNum, sectNum, numDots As Long Dim dot, sectTitle As Shape Dim slideWidth, gapSize As Double Dim firstShape As Long ' Record the index of the first shape we'll be creating. We'll use this ' later to select the whole range of shapes & group them. firstShape = ActivePresentation.Slides(slideNum).Shapes.Count + 1 ' Calculate dotMargin & sectionMargin, which are used to layout the ' dots & section titles. numDots = ActivePresentation.Slides.Count - sectinfo.startSlide + 1 slideWidth = ActivePresentation.PageSetup.slideWidth gapSize = (slideWidth - theOptions.LeftMargin - theOptions.RightMargin _ - theOptions.dotSize * numDots) If sectinfo.numSections = 1 Or numDots = sectinfo.numSections Then If sectinfo.numSections = 1 And numDots = sectinfo.numSections Then dotMargin = 0 sectionMargin = 0 Else dotMargin = gapSize / (numDots - 1) sectionMargin = dotMargin End If Else sectionMargin = gapSize / _ ((numDots - sectinfo.numSections) / theOptions.SectionGap + _ (sectinfo.numSections - 1)) dotMargin = sectionMargin / theOptions.SectionGap End If x = theOptions.LeftMargin ' our current x position. sectNum = 1 ' our current section num ' Loop through the slides, and draw a dot for each one. For dotNum = sectinfo.startSlide To ActivePresentation.Slides.Count ' Draw the dot. Set dot = ActivePresentation.Slides(slideNum).Shapes.AddShape( _ msoShapeOval, x, _ theOptions.yMargin + theOptions.textHeight + 2, _ theOptions.dotSize, theOptions.dotSize) Call dot.Tags.Add(shapeTag, "yes") ' Color the dot. If dotNum <= slideNum Then dot.Fill.ForeColor.RGB = ColorToRGB(theOptions.seenDotColor) dot.Line.ForeColor.RGB = ColorToRGB(theOptions.seenDotColor) Else dot.Fill.ForeColor.RGB = ColorToRGB(theOptions.unseenDotColor) dot.Line.ForeColor.RGB = ColorToRGB(theOptions.unseenDotColor) End If ' If this dot starts a section, then draw the section title. If sectinfo.sectStart(sectNum) = dotNum Then numDotsInSection = (sectinfo.sectStart(sectNum + 1) - dotNum) labelWidth = numDotsInSection * theOptions.dotSize + _ (numDotsInSection - 1) * dotMargin labelLeft = x ' Add some extra space labelWidth = labelWidth + 400 labelLeft = labelLeft - 200 ' But don't fall off either edge. If labelLeft < 0 Then labelWidth = labelWidth + labelLeft * 2 labelLeft = 0 End If If labelWidth + labelLeft > slideWidth Then labelWidth = labelWidth + (slideWidth - labelLeft - labelWidth) * 2 labelLeft = slideWidth - labelWidth End If Set sectTitle = ActivePresentation.Slides(slideNum). _ Shapes.AddTextbox(msoTextOrientationHorizontal, _ labelLeft, theOptions.yMargin, labelWidth, _ theOptions.textHeight + 10) Call sectTitle.Tags.Add(shapeTag, "yes") ' Style the section title. With sectTitle.TextFrame .TextRange.Text = sectinfo.sectTitles(sectNum) .TextRange.Font.Size = theOptions.textHeight .TextRange.Font.name = theOptions.fontName .HorizontalAnchor = msoAnchorCenter .MarginBottom = 0 .MarginTop = 0 .MarginLeft = 0 .MarginRight = 0 If dotNum <= slideNum Then .TextRange.Font.Color = ColorToRGB(theOptions.seenTextColor) Else .TextRange.Font.Color = ColorToRGB(theOptions.unseenTextColor) End If End With ' Update our section counter sectNum = sectNum + 1 End If ' Update our x position. If sectinfo.sectStart(sectNum) = dotNum + 1 Then x = x + sectionMargin + theOptions.dotSize Else x = x + dotMargin + theOptions.dotSize End If Next dotNum ' Group all the new shapes. Dim numShapes, i As Long Dim oShapeArray() As Long Dim oGroupShape As Shape numShapes = ActivePresentation.Slides(slideNum).Shapes.Count - firstShape + 1 If numShapes > 1 Then ReDim oShapeArray(numShapes) For i = 0 To numShapes - 1 oShapeArray(i) = firstShape + i Next i Set oGroupShape = ActivePresentation.Slides(slideNum). _ Shapes.Range(oShapeArray).Group Call oGroupShape.Tags.Add(shapeTag, "yes") End If ' Testing: 'Dim oShapeArray(500) As Long 'Dim iShapeCount As Integer 'Dim z As ShapeRange 'Dim ss As slide 'Set ss = ActivePresentation.Slides(slideNum) 'Set z = ss.Shapes.Range(oShapeArray) 'z.Group ' Group it all together, and deselect it. 'If Application.ActiveWindow.Selection.ShapeRange.Count > 1 Then ' oGroupShape = Application.ActiveWindow.Selection.ShapeRange.Group ' Call oGroupShape.Tags.Add(shapeTag, "yes") 'End If 'Application.ActiveWindow.Selection.Unselect End Sub Sub DelProgressBar() Dim slideNum As Long Debug.Print "Deleting old progress bar..." For slideNum = 1 To ActivePresentation.Slides.Count Call DelProgressBarOnSlide(slideNum, "__progress-dots__") Next slideNum End Sub Sub DelProgressBarOnSlide(slideNum As Long, shapeTag As String) Dim shapeNum As Long shapeNum = 1 Do While shapeNum <= ActivePresentation.Slides(slideNum).Shapes.Count If ActivePresentation.Slides(slideNum).Shapes(shapeNum). _ Tags.Item(shapeTag) = "yes" Then ActivePresentation.Slides(slideNum).Shapes(shapeNum).Delete Else shapeNum = shapeNum + 1 End If Loop End Sub Sub PreviewProgressBar(theOptions As ProgressDotsOptions) Dim curSlide As slide Dim sectinfo As ProgressDotsSectionInfo Set curSlide = Application.ActiveWindow.View.slide sectinfo = findSections() ' Delete any progress bar on this slide. Call DelProgressBarOnSlide(curSlide.SlideNumber, "__progress-dots__") ' Draw the new one. Call DrawProgressBarOnSlide(curSlide.SlideNumber, sectinfo, theOptions, _ "__progress-dots__") End Sub