Introduction
This MSVS Add-in allows to select multiple files contained in loaded solution and pass them to Doxygen. Also it gives availability to choose the name of the project and the destination folder. All other Doxygen options can be set using Doxywizard executed from the add-in. I tried to do it as simply as possible for future extension which will be done next time.
Prerequisites
Usage
- Microsoft Visual Studio 2005
- Doxygen. Download from http://www.stack.nl/~dimitri/doxygen/download.html#latestsrc
Build Sources
- Boost(I used boost.1.38.0). Download from http://www.boost.org/
- WTL 80. Download from http://www.microsoft.com/downloads/details.aspx?familyid=E5BA5BA4-6E6B-462A-B24C-61115E846F0C&displaylang=en
Description
Doxygen Add-in can be run from Tools menu of MSVS IDE.
This tab allows to select files for document generation:
This is used for Doxygen raw tuning:
“Doxygen executable” & “Doxywizard executable”
They are searched when add-in starts. But I left an ability to choose them manually if you need it.
“Name” is used for document generation and can be changed in Doxywizard.
“Doxygen configuration file”
When running Doxywizard, you can fine tune the options of generated documentation and save various configuration files for different purposes. Later, you can choose the configuration that you want and run Doxygen.
“Output directory” is needed to tell Doxygen where to put the documentation. It can also be changed in Doxywizard mode.
Code
MSVS Add-in Architecture
First of all, I will try to explain (and to understand myself) some important moments of the MSVS Add-in architecture.
Let’s take a look at this diagram:
Preload Add-in*
(*)This happens only if you use CommandPreload
flag set to ‘1’ in your registration script(Addin.rgs). It indicates that your add-in must be preloaded to setup UI. After that, the add-in will be unloaded.
Important: Remember that all initialized class members and local objects will be destroyed after UI setup completes.
The first time the object <CConnect
> is created in order to give availability to setup UI, what can be done in OnConnect( ConnectMode == ext_cm_UISetup )
. At this point, we add our control to menu bar (this can also be done by “Visual Studio Add-in Wizard”). The command is created using AddNamedCommand2
method of EnvDTE80::Commands2
interface. It is senseless to store the created command because it is created only once at the first run and saved by the MSVS IDE enviroment. Later, I will show how to get it in order to remove when we uncheck availability of our add-in in “Add-in Manager”.
After that, we receive OnDisconnection( RemoveMode == ext_dm_UISetupComplete )
which tells us that “The add-in was unloaded after the user interface was set up”(MSDN).
And at last, our object will be destroyed. Call of FinalRelease()
and subsequent call of object’s destructor tells us about it.
Load Add-in
After UI was set up, the add-in is loaded for the second (if CommandPreload
is set) time(the new calls of constructror and FinalConstruct()
indicates it).
Now we will receive OnConnect (ConnectMode == ext_cm_CommandLine
). At this point, our add-in was loaded by the MSVS eviroment.
The next point is OnAddInsUpdate()
which “Occurs whenever an add-in is loaded or unloaded from the Visual Studio integrated development environment (IDE)”(MSDN). Its type(loaded/unloaded) we can detect by analyzing parameter ConnectMode::ext_ConnectMode
in the last call of OnConnection()
and RemoveMode::ext_DisconnectMode
in OnDisconnection()
methods.
And the final point is OnStartupComplete
. It “Occurs whenever an add-in, which is set to load when Visual Studio starts, loads”.
Check/Uncheck Availability in “Add-in Manager” Notification
Unckeck Availability
When we uncheck the add-in in “Add-in Manager”, we receive OnDisconnect( RemoveMode == ext_dm_UserClosed )
. At this point, we can remove our item from menu bar.
If we use CommandPreload
flag set to ‘1’ (see “Preload Addin”), then we have no variable pointing to our command and we need to get it manually. This is done in such a way:
IfFailGoCheck(m_pDTE->get_Commands(&pCommands), pCommands);
CComQIPtr< EnvDTE::Command > dx_cmd;
IfFailGoCheck( pCommands->Item( CComVariant
( "DoxygenAddin.Connect.DoxygenAddin" ), 0, &dx_cmd ), dx_cmd );
dx_cmd->Delete();
Important: " DoxygenAddin.Connect.DoxygenAddin
" is a full name of the command. It consists of 2 parts: the full name of the class “ DoxygenAddin.Connect
” and name of the command “ DoxygenAddin
”. The method Delete()
of EnvDTE::Command
removes it from the menu bar.
If our command was created not in OnConnection( ConnectMode == ext_cm_UISetup )
, then we can remove it explicitly calling EnvDTE::Command::Delete()
from created EnvDTE::Command
object.
Check Availability
When we again check it, OnConnection( ConnectMode == ext_cm_AfterStartup )
is called. All we need to do is to re-add our control menu button using AddNamedCommand2
method of EnvDTE80::Commands2
interface as it was done earlier.
Unfortunately, it works correctly only when any solution loaded. When I remove button without loaded solution, I failed to add it again, :(. Why is it so – I don’t know. If anyone can explain this – I will be glad :).
Add-in Availability (enable/disable)
Another point is that I don’t want to execute my add-in when no solution is present. CSolutionEventsSink
class was written to prevent it. It inherits IDispEventImpl
with __uuidof(EnvDTE::_dispSolutionEvents)
and receives “Opened
” and “BeforeClosing
” solution events.
BEGIN_SINK_MAP(SolutionEventsSink)
SINK_ENTRY_EX(1, __uuidof(EnvDTE::_dispSolutionEvents), 1, Opened)
SINK_ENTRY_EX(1, __uuidof(EnvDTE::_dispSolutionEvents), 2, BeforeClosing)
END_SINK_MAP()
The code above easily solves this problem but I was interested to dig it deeper. I was interested in how to receive all dispids, function names and other information on them in the class which events I want to handle at run-time in order to perform dynamic advise.
Macroses
and BEGIN_SINK_MAP
and SINK_ENTRY_EX
declare _GetSinkMap() static
method and create static
array of _ATL_EVENT_ENTRY
entries which contain information needed to associate events by their dispids with user callbacks.
Our sink class derived from IDispEventImpl
and its parent IDispEventSimpleImpl
contain static
object of type CComTypeInfoHolder
. Getting ITypeInfo
from it helps us to receive all type information of EnvDTE::_dispSolutionEvents
. Now we can create an array of _ATL_EVENT_ENTRY
entries at run-time.
After advising on interested events of EnvDTE::SolutionEvents
object, all I have to do is to check the current state of solution in CConnect::QueryStatus
and to make the menu item enabled or disabled.
CConnect::CheckHasSolution()
helps to determine if the add-in was checked in “Add-in Manager” when the solution was already present. It is simply done by analyzing the EnvDTE::_Solution
item count property (EnvDTE::_Solution
object presents always but it may or may not contain child items).
Important
Take a look at the declaration of the class CSolutionEventsSink
:
class CSolutionEventsSink : public IDispEventImpl< 1, CSolutionEventsSink
, &__uuidof( EnvDTE::_dispSolutionEvents ), &EnvDTE::LIBID_EnvDTE, 8, 0 >
8
- is the major version of the type library. As it was built under MSVS 8.0, this number indicates its major version. In other versions of MSVS IDE, it will vary.
Acquisition of Solution Items
The acquisition of solution items is rather simple. We can obtain EnvDTE::_Solution
object from EnvDTE80::DTE2
. Then get the number of projects in it(EnvDTE::_Solution::get_Count()
) and all EnvDTE::Project
objects. Just the same, you can obtain EnvDTE::ProjectItems
from EnvDTE::Project
object.
The only interesting thing is to identify whether EnvDTE::ProjectItems
points to a file or not. It is done by analyzing its Kind
property. EnvDTE::vsProjectItemKindPhysicalFile
indicates it.
Important: If you load a file to the empty IDE, it also creates a solution(EnvDTE::_Solution
), project(EnvDTE::Project
) and project items(EnvDTE::ProjectItems
) (not shown in “Solution Explorer”) so you can easily get it.
Out of Scope
CCBTreeViewCtrl
To view and select files for document generation, I use class CCBTreeViewCtrl
which extends WTL::CTreeViewCtrlEx
.
It adds 3 new abilities to its parent:
- Selection/deselection of the whole brunch via its parent
- Grayscale parents when child items have different states
- Method
ParseTree()
which helps to process all children from the specified item
Boost::spirit parsing
It does not seem simple for understanding and usage but when you achieve what you wanted, it looks amazing, :).
pugi::pugixml
Lightweight and intuitive library for XML-parsing.