/// <summary>
        /// Process a database.
        /// </summary>
        /// <param name="fileType">Determines if databse 1 or database 2</param>
        /// <param name="print">Whether to write to textBoxInfo.</param>
        private void processDatabase(FileType fileType, bool print)
        {
            int                  nDatabase          = 1;
            TextBox              textBoxDatabase    = null;
            TextBox              textBoxBrush       = null;
            RadioButton          radioButtonVariant = null;
            List <CSPBrushParam> paramsList         = null;
            CSPBrushParam        param = null;
            string               info  = null;

            switch (fileType)
            {
            case FileType.Database1:
                nDatabase          = 1;
                textBoxDatabase    = textBoxDatabase1;
                textBoxBrush       = textBoxBrush1;
                radioButtonVariant = radioButtonVariant1;
                break;

            case FileType.Database2:
                nDatabase          = 2;
                textBoxDatabase    = textBoxDatabase2;
                textBoxBrush       = textBoxBrush2;
                radioButtonVariant = radioButtonVariant2;
                break;

            default:
                Utils.Utils.errMsg("Invalid fileType ("
                                   + fileType + ") for processDatabase");
                return;
            }
            textBoxInfo.Clear();
            paramsList = new List <CSPBrushParam>();
            info       = "";
            String name = "";
            string nodeName = null;
            int    nodeVariantId = 0, nodeInitVariantId = 0;
            int    nCols = 0;
            int    nNull = 0;

            name = textBoxDatabase.Text;
            if (name == null || name.Length == 0)
            {
                registerOutput(fileType, info, paramsList);
                Utils.Utils.errMsg("Database " + nDatabase + " is not defined");
                return;
            }
            if (!File.Exists(name))
            {
                registerOutput(fileType, info, paramsList);
                Utils.Utils.errMsg(name + " does not exist");
                return;
            }
            // Get the selected brush name
            string brushName = textBoxBrush.Text;

            if (brushName == null | brushName.Length == 0)
            {
                registerOutput(fileType, info, paramsList);
                Utils.Utils.errMsg("Brush not specified");
                return;
            }

            SQLiteConnection conn = null;
            SQLiteDataReader dataReader;
            DateTime         modTime = File.GetLastWriteTime(name);

            info += name + NL;
            info += "Brush: " + brushName + NL;
            info += "Using: "
                    + (radioButtonVariant.Checked ?
                       "NodeVariantID" : "NodeInitVariantID") + NL;
            info += "Modified: " + modTime + NL;
            // Find the node
            try {
                string openName = DatabaseUtils.getSqliteOpenName(name);
                using (conn = new SQLiteConnection("Data Source=" + openName
                                                   + ";Version=3;Read Only=True;")) {
                    conn.Open();
                    SQLiteCommand command;
                    command = conn.CreateCommand();
                    // Need to replace single quotes by double
                    // Don't want NodeVariantID or NodeInitVariantID = 0
                    command.CommandText = "SELECT NodeName, NodeVariantId, " +
                                          "NodeInitVariantId FROM Node WHERE NodeName='"
                                          + brushName.Replace("'", "''") + "'"
                                          + " AND NodeVariantID != 0  AND NodeInitVariantID != 0";
                    List <NodeInfo> items = new List <NodeInfo>();
                    using (dataReader = command.ExecuteReader()) {
                        if (!dataReader.HasRows)
                        {
                            Utils.Utils.errMsg("No matching rows looking for "
                                               + brushName);
                            registerOutput(fileType, info, paramsList);
                            return;
                        }
                        while (dataReader.Read())
                        {
                            nodeName          = dataReader.GetString(0);
                            nodeVariantId     = dataReader.GetInt32(1);
                            nodeInitVariantId = dataReader.GetInt32(2);
                            items.Add(new NodeInfo(nodeName, nodeVariantId, nodeInitVariantId));
                        }
                    }
                    if (items.Count == 0)
                    {
                        Utils.Utils.errMsg("Did not find any matches for " + brushName);
                        registerOutput(fileType, info, paramsList);
                        return;
                    }
                    if (items.Count == 1)
                    {
                        nodeName          = items[0].NodeName;
                        nodeVariantId     = items[0].NodeVariantId;
                        nodeInitVariantId = items[0].NodeInitVariantId;
                    }
                    else
                    {
                        // Found more than one matching item, prompt for which one
                        List <string> itemsList = new List <string>();
                        foreach (NodeInfo nodeInfo in items)
                        {
                            itemsList.Add(nodeInfo.Info());
                        }
                        MultiChoiceListDialog dlg = new MultiChoiceListDialog(itemsList);
                        switch (fileType)
                        {
                        case FileType.Database1:
                            dlg.Text = "Brush 1 Ambiguity";
                            break;

                        case FileType.Database2:
                            dlg.Text = "Brush 2 Ambiguity";
                            break;

                        default:
                            dlg.Text = "Brush Ambiguity";
                            break;
                        }
                        // Note that the ListBox has be set to have SelectionMode=One,
                        // not MultiExtended
                        if (dlg.ShowDialog() == System.Windows.Forms.DialogResult.OK)
                        {
                            List <string> selectedList = dlg.SelectedList;
                            if (selectedList == null || selectedList.Count == 0)
                            {
                                Utils.Utils.errMsg("No items selected");
                                registerOutput(fileType, info, paramsList);
                                return;
                            }
                            // There should be only one entry
                            string item = selectedList[0];
                            nodeName = null;
                            foreach (NodeInfo nodeInfo in items)
                            {
                                if (item.Equals(nodeInfo.Info()))
                                {
                                    nodeName          = nodeInfo.NodeName;
                                    nodeVariantId     = nodeInfo.NodeVariantId;
                                    nodeInitVariantId = nodeInfo.NodeInitVariantId;
                                }
                            }
                        }
                        else
                        {
                            info += "Cancelled" + NL;
                            registerOutput(fileType, info, paramsList);
                            return;
                        }
                    }
                    // These should not happen
                    if (nodeName == null)
                    {
                        Utils.Utils.errMsg("Failed to determine which brush");
                        registerOutput(fileType, info, paramsList);
                    }
                    if (!nodeName.Equals(brushName))
                    {
                        Utils.Utils.errMsg("Looking for " + brushName + ", found "
                                           + nodeName);
                        registerOutput(fileType, info, paramsList);
                        return;
                    }
                    if (nodeVariantId == 0)
                    {
                        Utils.Utils.errMsg(brushName
                                           + " is not a brush (No NodeVariantId)");
                        registerOutput(fileType, info, paramsList);
                        return;
                    }
                }
            } catch (Exception ex) {
                Utils.Utils.excMsg("Error finding " + brushName, ex);
                registerOutput(fileType, info, paramsList);
                return;
            }

            // Find the variant
            conn = null;
            try {
                string openName = DatabaseUtils.getSqliteOpenName(name);
                using (conn = new SQLiteConnection("Data Source=" + openName
                                                   + ";Version=3;Read Only=True;")) {
                    conn.Open();
                    SQLiteCommand command;
                    command = conn.CreateCommand();
                    int variantId = (radioButtonVariant.Checked) ?
                                    nodeVariantId : nodeInitVariantId;
                    command.CommandText = "SELECT * FROM Variant WHERE VariantID="
                                          + variantId;
                    using (dataReader = command.ExecuteReader()) {
                        if (!dataReader.HasRows)
                        {
                            Utils.Utils.errMsg("No matching rows looking for VariantID = "
                                               + variantId);
                            registerOutput(fileType, info, paramsList);
                            return;
                        }
                        dataReader.Read();
                        nCols = dataReader.FieldCount;
                        if (print)
                        {
                            appendInfo(info + NL);
                        }
                        for (int i = 0; i < nCols; i++)
                        {
                            if (dataReader.IsDBNull(i))
                            {
                                nNull++;
                                continue;
                            }
                            param = new CSPBrushParam(dataReader.GetName(i),
                                                      dataReader.GetDataTypeName(i), dataReader.GetValue(i));
                            param.Value = dataReader.GetValue(i);
                            //    appendInfo(param.info());
                            //}
                            paramsList.Add(param);
                        }
                    }
                }
            } catch (Exception ex) {
                Utils.Utils.excMsg("Error finding VariantID=" + nodeVariantId, ex);
                registerOutput(fileType, info, paramsList);
                return;
            }
            info += "Columns: " + nCols + " Null: " + nNull + " Non-Null: "
                    + (nCols - nNull) + NL;
            registerOutput(fileType, info, paramsList);
        }
        public static string getToolHierarchy(string database)
        {
            string        TAB = "   ";
            StringBuilder sb;

            sb = new StringBuilder();

            if (String.IsNullOrEmpty(database))
            {
                sb.AppendLine("No database specified");
                return(sb.ToString());
            }
            else
            {
                sb.AppendLine(database);
            }
            sb.AppendLine(DateTime.Now.ToString());
            string machineName = null;

            try {
                machineName = Environment.MachineName;
            } catch (Exception) {
                // Do nothing
            }
            if (!String.IsNullOrEmpty(machineName))
            {
                sb.AppendLine("Computer: " + machineName);
            }

            if (!File.Exists(database))
            {
                sb.AppendLine("Does not exist: " + database);
                return(sb.ToString());
            }

            Dictionary <String, Tool> map  = new Dictionary <String, Tool>();
            SQLiteConnection          conn = null;
            SQLiteDataReader          dataReader;
            DateTime modTime = File.GetLastWriteTime(database);
            Tool     tool;

            try {
                string openName = DatabaseUtils.getSqliteOpenName(database);
                using (conn = new SQLiteConnection("Data Source=" + openName
                                                   + ";Version=3;Read Only=True;")) {
                    conn.Open();
                    SQLiteCommand command;
                    command             = conn.CreateCommand();
                    command.CommandText = "SELECT _PW_ID," +
                                          " NodeVariantID," + "NodeInitVariantId," + " NodeName," +
                                          " hex(NodeUUid), hex(NodeFirstChildUuid)," +
                                          " hex(NodeNextUuid), hex(NodeSelectedUuid)" +
                                          " FROM Node";
                    long   id, nodeVariantID, nodeInitVariantID;
                    string nodeName;
                    string nodeUuid, nodeFirstChildUuid, nodeNextUuid, nodeSelectedUuid;
                    using (dataReader = command.ExecuteReader()) {
                        while (dataReader.Read())
                        {
                            id                 = dataReader.GetInt64(0);
                            nodeVariantID      = dataReader.GetInt64(1);
                            nodeInitVariantID  = dataReader.GetInt64(2);
                            nodeName           = dataReader.GetString(3);
                            nodeUuid           = dataReader.GetString(4);
                            nodeFirstChildUuid = dataReader.GetString(5);
                            nodeNextUuid       = dataReader.GetString(6);
                            nodeSelectedUuid   = dataReader.GetString(7);
                            tool               = new Tool(id, nodeVariantID, nodeInitVariantID,
                                                          nodeName, nodeUuid, nodeFirstChildUuid,
                                                          nodeNextUuid, nodeSelectedUuid);
                            map.Add(nodeUuid, tool);
                        }
                    }
                    if (map.Count == 0)
                    {
                        sb.AppendLine("Did not find any tools");
                        return(sb.ToString());
                    }
                }
            } catch (Exception ex) {
                Utils.Utils.excMsg("Error reading " + database, ex);
                sb.AppendLine("Error reading " + database);
                return(sb.ToString());
            }

            // Loop over the elements finding the top one (name is blank)
            // Then trace the first and next values for the groups and subtools
            // For each one found, set the nodeParentUUid.
            sb.AppendLine();
            Tool firstChild, firstChild1, firstChild2;
            int  nTools = 0, nGroups = 0, nSubTools = 0;

            // Use this to sort the Dictionary (doesn't matter here)
            foreach (KeyValuePair <string, Tool> entry in map)
            {
                tool = entry.Value;
                // Only process the top level which has a blank name and _PW_ID=1
                if (tool.nodeName.Length != 0)
                {
                    continue;
                }
                String nodeFirstChildUuid = tool.nodeFirstChildUuid;
                if (nodeFirstChildUuid == null ||
                    nodeFirstChildUuid.Length != 32)
                {
                    continue;
                }
                map.TryGetValue(nodeFirstChildUuid, out firstChild);
                // Get the tools
                while (firstChild != null &&
                       firstChild.nodeUuid.Length == 32)
                {
                    // sb.AppendLine("Tool: " + firstChild.nodeName
                    // + " nodeUuid=" + firstChild.nodeUuid
                    // + " nodeFirstChildUuid=" + firstChild.nodeFirstChildUuid);
                    nTools++;
                    sb.AppendLine("Tool: " + firstChild.nodeName);
                    firstChild.nodeParentUuid = tool.nodeUuid;

                    map.TryGetValue(firstChild.nodeFirstChildUuid, out firstChild1);
                    // Get the groups
                    while (firstChild1 != null &&
                           firstChild1.nodeUuid.Length == 32)
                    {
                        // sb.AppendLine(
                        // TAB + "Group: " + firstChild1.nodeName + " nodeUuid="
                        // + firstChild1.nodeUuid + " nodeFirstChildUuid="
                        // + firstChild1.nodeFirstChildUuid);
                        nGroups++;
                        sb.AppendLine(TAB + "Group: " + firstChild1.nodeName);
                        firstChild1.nodeParentUuid = firstChild.nodeUuid;

                        // Get the sub tools
                        map.TryGetValue(firstChild1.nodeFirstChildUuid, out firstChild2);
                        while (firstChild2 != null &&
                               firstChild2.nodeUuid.Length == 32)
                        {
                            // sb.AppendLine(TAB + TAB + "SubTool: "
                            // + firstChild2.nodeName + " nodeUuid="
                            // + firstChild2.nodeUuid + " nodeFirstChildUuid="
                            // + firstChild2.nodeFirstChildUuid);
                            nSubTools++;
                            sb.AppendLine(
                                TAB + TAB + "SubTool: " + firstChild2.nodeName);
                            firstChild2.nodeParentUuid = firstChild1.nodeUuid;
                            // Get the next subtool item
                            map.TryGetValue(firstChild2.nodeNextUuid, out firstChild2);
                        }
                        // Get the next group item
                        map.TryGetValue(firstChild1.nodeNextUuid, out firstChild1);
                    }
                    // Get the next tool item
                    map.TryGetValue(firstChild.nodeNextUuid, out firstChild);
                }
            }
            sb.AppendLine("nTools=" + nTools + " nGroups=" + nGroups
                          + " nSubTools=" + nSubTools);

            // Check for orphans
            sb.AppendLine(NL + "Orphans");
            int nOrphans = 0;

            foreach (KeyValuePair <string, Tool> entry in
                     map.OrderBy(entry => entry.Value.nodeName))
            {
                tool = entry.Value;
                // Don't do the root
                if (tool.nodeName.Length == 0)
                {
                    continue;
                }
                if (tool.nodeParentUuid == null || tool.nodeParentUuid.Length != 32)
                {
                    nOrphans++;
                    sb.AppendLine(
                        TAB + tool.nodeName + " _PW_ID(Node)=" + tool.id +
                        " nodeVariantId=" + tool.nodeVariantID);
                }
            }
            sb.AppendLine("nOrphans=" + nOrphans);

            // Check for duplicate brush names
            sb.AppendLine(NL + "Duplicate Brush Names");
            int  nDuplicates = 0;
            Tool prev        = null;
            bool first       = true;

            foreach (KeyValuePair <string, Tool> entry in
                     map.OrderBy(entry => entry.Value.nodeName))
            {
                tool = entry.Value;
                // If nodeVariantID = 0, it's not a brush
                if (tool.nodeVariantID == 0)
                {
                    continue;
                }
                // Can't have a duplicate for the first item
                if (prev == null)
                {
                    prev = tool;
                    continue;
                }
                if (tool.nodeName.Equals(prev.nodeName))
                {
                    if (first)
                    {
                        // Indicates the number of duplicate sets
                        nDuplicates++;
                        sb.AppendLine(
                            TAB + prev.nodeName
                            + " _PW_ID(Node)=" + prev.id
                            + " nodeVariantID=" + prev.nodeVariantID
                            + " nodeInitVariantID=" + prev.nodeInitVariantID);
                        first = false;
                    }
                    sb.AppendLine(
                        TAB + tool.nodeName + " _PW_ID(Node) =" + tool.id +
                        " nodeVariantID=" + tool.nodeVariantID
                        + " nodeInitVariantID=" + tool.nodeInitVariantID);
                    first = true;
                }
                prev = tool;
            }

            sb.AppendLine("nDuplicates=" + nDuplicates);

            return(sb.ToString());
        }