// // Copyright 2003-2010 by Autodesk, Inc. // // Permission to use, copy, modify, and distribute this software in // object code form for any purpose and without fee is hereby granted, // provided that the above copyright notice appears in all copies and // that both that copyright notice and the limited warranty and // restricted rights notice below appear in all supporting // documentation. // // AUTODESK PROVIDES THIS PROGRAM "AS IS" AND WITH ALL FAULTS. // AUTODESK SPECIFICALLY DISCLAIMS ANY IMPLIED WARRANTY OF // MERCHANTABILITY OR FITNESS FOR A PARTICULAR USE. AUTODESK, INC. // DOES NOT WARRANT THAT THE OPERATION OF THE PROGRAM WILL BE // UNINTERRUPTED OR ERROR FREE. // // Use, duplication, or disclosure by the U.S. Government is subject to // restrictions set forth in FAR 52.227-19 (Commercial Computer // Software - Restricted Rights) and DFAR 252.227-7013(c)(1)(ii) // (Rights in Technical Data and Computer Software), as applicable. // using System; using System.Diagnostics; using System.Windows.Forms; using System.Drawing; using System.Drawing.Printing; using System.Collections; using System.Reflection; using System.Text.RegularExpressions; using Autodesk.Revit.DB; using System.Collections.Generic; namespace RevitLookup.Snoop { /// /// Utility functions to use in the Snoop dialog mechanism. /// public class Utils { public Utils() { } /// /// Given a ListView to display the data in, clear it out and add all the /// data that has been collected. For things that have a DrillDown, change /// the font to Bold so the user knows they can select it. For things that /// are a Separator, change the background color. /// /// The ListView UI control to draw into /// The collected data about an object public static void Display(ListView lvCur, Snoop.Collectors.Collector snoopCollector) { lvCur.BeginUpdate(); lvCur.Items.Clear(); Font oldFont = lvCur.Font; FontStyle newStyle = lvCur.Font.Style ^ FontStyle.Bold; Font boldFont = new Font(oldFont, newStyle); for (int i = 0; i < snoopCollector.Data().Count; i++) { Snoop.Data.Data tmpSnoopData = (Snoop.Data.Data)snoopCollector.Data()[i]; // if it is a class separator, then color the background differently // and don't add a SubItem for the "Field" value if (tmpSnoopData.IsSeparator) { ListViewItem lvItem = new ListViewItem(tmpSnoopData.StrValue()); if (tmpSnoopData is Snoop.Data.ClassSeparator) lvItem.BackColor = System.Drawing.Color.LightBlue; else lvItem.BackColor = System.Drawing.Color.Khaki; lvItem.Tag = tmpSnoopData; lvCur.Items.Add(lvItem); } else { ListViewItem lvItem = new ListViewItem(tmpSnoopData.Label); lvItem.SubItems.Add(tmpSnoopData.StrValue()); if (tmpSnoopData.IsError) { ListViewItem.ListViewSubItem sItem = (ListViewItem.ListViewSubItem)lvItem.SubItems[0]; sItem.ForeColor = System.Drawing.Color.Red; } if (tmpSnoopData.HasDrillDown) { ListViewItem.ListViewSubItem sItem = (ListViewItem.ListViewSubItem)lvItem.SubItems[0]; sItem.Font = boldFont; } lvItem.Tag = tmpSnoopData; lvCur.Items.Add(lvItem); } } lvCur.EndUpdate(); } /// /// A Snoop.Data item was selected in the ListView. Call its DrillDown() function to /// get more detailed info about it. /// /// The ListView control in question public static void DataItemSelected(ListView lvCur) { Debug.Assert((lvCur.SelectedItems.Count > 1) == false); if (lvCur.SelectedItems.Count != 0) { Snoop.Data.Data tmpSnoopData = (Snoop.Data.Data)lvCur.SelectedItems[0].Tag; tmpSnoopData.DrillDown(); } } /// /// Given an Object, allow the user to browse its properties based on Reflection only. /// /// Object to browse public static void BrowseReflection(Object obj) { if (obj == null) { MessageBox.Show("Object == null"); return; } RevitLookup.Snoop.Forms.GenericPropGrid pgForm = new RevitLookup.Snoop.Forms.GenericPropGrid(obj); pgForm.Text = string.Format("Object Data (System.Type = {0})", obj.GetType().FullName); pgForm.ShowDialog(); } /// /// Format a type string to represent a given object. If it is an Element, we try to get some /// extra information that may be useful, such as the Name and the ElementId. If it isn't an Element /// then just print out /// /// Object to label /// The formatted type string public static string ObjToTypeStr(System.Object obj) { if (obj == null) return "< null >"; Autodesk.Revit.DB.Element elem = obj as Autodesk.Revit.DB.Element; if (elem != null) { string nameStr = (elem.Name == string.Empty) ? "???" : elem.Name; // use "???" if no name is set return string.Format("< {0} {1} {2,4} >", obj.GetType().Name, nameStr, elem.Id.IntegerValue.ToString()); } return string.Format("< {0} >", obj.GetType().Name); } /// /// Same as above, but this is used when we are already grouping the objects by Type in /// a TreeCtrl. Including the Type name would be redundant. /// /// Object to label /// The formatted type string public static string ObjToLabelStr(System.Object obj) { if (obj == null) return "< null >"; Autodesk.Revit.DB.Element elem = obj as Autodesk.Revit.DB.Element; if (elem != null) { // TBD: Exceptions are thrown in certain cases when accessing the Name property. // Eg. for the RoomTag object. So we will catch the exception and display the exception message // arj - 1/23/07 try { string nameStr = (elem.Name == string.Empty) ? "???" : elem.Name; // use "???" if no name is set return string.Format("< {0} {1} >", nameStr, elem.Id.IntegerValue.ToString()); } catch (System.InvalidOperationException ex) { return string.Format("< {0} {1} >", null, ex.Message); } } return string.Format("< {0} >", obj.GetType().Name); } /// /// /// /// /// public static void CopyToClipboard(ListView lv) { if (lv.Items.Count == 0) { Clipboard.Clear(); return; } //First find the longest piece of text in the Field column // Int32 maxField = 0; Int32 maxValue = 0; foreach (ListViewItem item in lv.Items) { if (item.Text.Length > maxField) { maxField = item.Text.Length; } if ((item.SubItems.Count > 1) && (item.SubItems[1].Text.Length > maxValue)) { maxValue = item.SubItems[1].Text.Length; } } String headerFormat = String.Format("{{0,{0}}}----{1}\r\n", maxField, new String('-', maxValue)); String tabbedFormat = String.Format("{{0,{0}}} {{1}}\r\n", maxField); System.Text.StringBuilder bldr = new System.Text.StringBuilder(); foreach (ListViewItem item in lv.Items) { if (item.SubItems.Count == 1) { String tmp = item.Text; if (item.Text.Length < maxField) { tmp = item.Text.PadLeft(item.Text.Length + (maxField - item.Text.Length), '-'); } bldr.AppendFormat(headerFormat, tmp); } else if (item.SubItems.Count > 1) { bldr.AppendFormat(tabbedFormat, item.Text, item.SubItems[1].Text); } } String txt = bldr.ToString(); if (String.IsNullOrEmpty(txt) == false) { Clipboard.SetDataObject(txt); } } /// /// /// /// public static void CopyToClipboard(ListViewItem lvItem, Boolean multipleItems) { if (lvItem.SubItems.Count > 1) { if (!multipleItems) { Clipboard.SetDataObject(lvItem.SubItems[1].Text); } else { Clipboard.SetDataObject(lvItem.SubItems[0].Text + " => " + lvItem.SubItems[1].Text); } } else { Clipboard.Clear(); } } /// /// /// /// /// /// /// /// /// /// public static Int32 Print(String title, ListView lv, System.Drawing.Printing.PrintPageEventArgs e, Int32 maxFieldWidth, Int32 maxValueWidth, Int32 currentItem) { Single linesPerPage = 0; Single yPos = 0; Single leftMargin = e.MarginBounds.Left + ((e.MarginBounds.Width - (maxFieldWidth + maxValueWidth)) / 2); Single topMargin = e.MarginBounds.Top; Single fontHeight = lv.Font.GetHeight(e.Graphics); Int32 count = 0; String line = null; ListViewItem item; SolidBrush backgroundBrush; SolidBrush subbackgroundBrush; SolidBrush textBrush; RectangleF rect; StringFormat centerFormat = new StringFormat(); StringFormat fieldFormat = new StringFormat(); StringFormat valueFormat = new StringFormat(); centerFormat.Alignment = StringAlignment.Center; fieldFormat.Alignment = HorizontalAlignmentToStringAligment(lv.Columns[0].TextAlign); valueFormat.Alignment = HorizontalAlignmentToStringAligment(lv.Columns[1].TextAlign); //Draw the title of the list. // rect = new RectangleF(leftMargin, topMargin, maxFieldWidth + maxValueWidth, fontHeight); e.Graphics.DrawString(title, lv.Font, Brushes.Black, rect, centerFormat); //Update the count so that we are giving ourselves a line between the title and the list. // count = 2; //Calculate the number of lines per page // linesPerPage = e.MarginBounds.Height / fontHeight; while ((count < linesPerPage) && (currentItem < lv.Items.Count)) { item = lv.Items[currentItem]; line = item.Text; yPos = topMargin + (count * fontHeight); backgroundBrush = new SolidBrush(item.BackColor); textBrush = new SolidBrush(item.ForeColor); rect = new RectangleF(leftMargin, yPos, maxFieldWidth, fontHeight); e.Graphics.FillRectangle(backgroundBrush, rect); e.Graphics.DrawRectangle(Pens.Black, rect.X, rect.Y, rect.Width, rect.Height); //Draw the field portion of the list view item // e.Graphics.DrawString(" " + item.Text, item.Font, textBrush, rect, fieldFormat); //Draw the value portion of the list view item // rect = new RectangleF(leftMargin + maxFieldWidth, yPos, maxValueWidth, fontHeight); if (item.SubItems.Count > 1) { subbackgroundBrush = new SolidBrush(item.SubItems[1].BackColor); e.Graphics.FillRectangle(subbackgroundBrush, rect); e.Graphics.DrawString(" " + item.SubItems[1].Text, item.Font, textBrush, rect, valueFormat); } else { e.Graphics.FillRectangle(backgroundBrush, rect); } e.Graphics.DrawRectangle(Pens.Black, rect.X, rect.Y, rect.Width, rect.Height); count++; currentItem++; } if (currentItem < lv.Items.Count) { e.HasMorePages = true; } else { e.HasMorePages = false; currentItem = 0; } return currentItem; } /// /// /// /// /// public static StringAlignment HorizontalAlignmentToStringAligment(HorizontalAlignment ha) { switch (ha) { case HorizontalAlignment.Center: return StringAlignment.Center; case HorizontalAlignment.Left: return StringAlignment.Near; case HorizontalAlignment.Right: return StringAlignment.Far; default: return StringAlignment.Near; } } /// /// /// /// /// public static Int32[] GetMaximumColumnWidths(ListView lv) { Int32 index = 0; Int32[] widthArray = new Int32[lv.Columns.Count]; foreach (ColumnHeader col in lv.Columns) { widthArray[index] = col.Width; index++; } System.Drawing.Graphics g = lv.CreateGraphics(); Int32 offset = Convert.ToInt32(Math.Ceiling(g.MeasureString(" ", lv.Font).Width)); Int32 width = 0; foreach (ListViewItem item in lv.Items) { index = 0; foreach (ListViewItem.ListViewSubItem subItem in item.SubItems) { width = Convert.ToInt32(Math.Ceiling(g.MeasureString(subItem.Text, item.Font).Width)) + offset; if (width > widthArray[index]) { widthArray[index] = width; } index++; } } g.Dispose(); return widthArray; } /// /// /// /// /// public static TreeNode GetRootNode(TreeNode node) { if (node.Parent == null) { return node; } return GetRootNode(node.Parent); } /// /// /// /// /// public static String GetPrintDocumentName(TreeNode node) { TreeNode root = GetRootNode(node); String str = root.Tag as String; if (str != null) { return System.IO.Path.GetFileNameWithoutExtension((String)root.Tag); } return String.Empty; } /// /// /// /// /// /// /// public static void UpdatePrintSettings(PrintDocument doc, TreeView tv, ListView lv, ref Int32[] widthArray) { if (tv.SelectedNode == null) { return; } doc.DocumentName = Utils.GetPrintDocumentName(tv.SelectedNode); widthArray = Utils.GetMaximumColumnWidths(lv); } /// /// /// /// /// /// /// public static void UpdatePrintSettings(ListView lv, ref Int32[] widthArray) { widthArray = Utils.GetMaximumColumnWidths(lv); } /// /// /// /// /// /// /// public static void PrintMenuItemClick(PrintDialog dlg, TreeView tv) { if (tv.SelectedNode == null) { MessageBox.Show(tv.Parent, "Please select a node in the tree to print.", "RevitLookup", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } if (dlg.ShowDialog(tv.Parent) == DialogResult.OK) { dlg.Document.Print(); } } /// /// /// /// /// /// /// public static void PrintMenuItemClick(PrintDialog dlg) { dlg.Document.Print(); } /// /// /// /// /// /// public static void PrintPreviewMenuItemClick(PrintPreviewDialog dlg, TreeView tv) { if (tv.SelectedNode == null) { MessageBox.Show(tv.Parent, "Please select a node in the tree to print.", "RevitLookup", MessageBoxButtons.OK, MessageBoxIcon.Information); return; } dlg.ShowDialog(tv.Parent); } /// /// /// /// /// /// public static void PrintPreviewMenuItemClick(PrintPreviewDialog dlg, ListView lv) { dlg.ShowDialog(lv.Parent); } /// /// Check if the object type is KeyValuePair. /// /// /// public static bool IsKeyValuePair(object o) { Type type = o.GetType(); if (type.IsGenericType) { return type.GetGenericTypeDefinition() != null ? type.GetGenericTypeDefinition() == typeof(KeyValuePair<,>) : false; } return false; } /// /// Check if the given instance is the basic types we supported. /// /// /// public static bool IsSupportedType(Object value) { if ((value is XYZ) || (value is Boolean) || (value is Double) || (value is String) || (value is IEnumerable) || (value is Enum) || (value is int) || (value is UV) || (IsKeyValuePair(value))) return true; else return false; } /// /// Stream the property with reflection. /// /// /// public static void StreamWithReflection(ArrayList data, Type type, Object instance) { if (data == null || type == null || instance == null) return; // solve the basic types directly. if (IsSupportedType(instance)) { AppendThePropertyObject(data, type.Name, instance); return; } data.Add(new Snoop.Data.ClassSeparator(type)); // is there a get property requires a transaction to modify the model. PropertyInfo[] connectorTypeProps = type.GetProperties(BindingFlags.Instance | BindingFlags.Public); foreach (PropertyInfo pi in connectorTypeProps) { MethodInfo methodInfo = pi.GetGetMethod(); if (pi.CanRead && pi.GetIndexParameters().Length == 0 && methodInfo.DeclaringType == type) { String propertyName = pi.Name; var r = new Regex(@"(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[^A-Za-z])" , RegexOptions.IgnorePatternWhitespace); propertyName = r.Replace(propertyName, " "); try { Object value = pi.GetValue(instance, null); Utils.AppendThePropertyObject(data, propertyName, value); } catch (System.Reflection.TargetInvocationException tix) { data.Add(new Snoop.Data.Exception(propertyName, tix.InnerException)); } catch (System.Exception ex) { data.Add(new Snoop.Data.Exception(propertyName, ex)); } } } MethodInfo[] methodInfos = type.GetMethods(BindingFlags.Instance | BindingFlags.Public); foreach (MethodInfo methodInfo in methodInfos) { if ( !methodInfo.IsSpecialName // is a property or operator override. && methodInfo.GetParameters().Length == 0 // skip the methods with arguments. && methodInfo.DeclaringType == type // skip the methods declare in base classes. && methodInfo.ReturnType != typeof(void) // skip the methods without return type. ) { String propertyName = methodInfo.Name; var r = new Regex(@"(?<=[A-Z])(?=[A-Z][a-z])|(?<=[^A-Z])(?=[A-Z])|(?<=[A-Za-z])(?=[^A-Za-z])" , RegexOptions.IgnorePatternWhitespace); propertyName = r.Replace(propertyName, " "); // only the get methods are allowed here. if(!propertyName.StartsWith("Get")) continue; try { Object value = methodInfo.Invoke(instance, null); Utils.AppendThePropertyObject(data, propertyName, value); } catch (System.Reflection.TargetInvocationException tix) { data.Add(new Snoop.Data.Exception(propertyName, tix.InnerException)); } catch (System.Exception ex) { data.Add(new Snoop.Data.Exception(propertyName, ex)); } } } } public static void AppendThePropertyObject(ArrayList data, String propertyName, Object value) { if(value == null) data.Add(new Snoop.Data.Object(propertyName, null)); else if (value is XYZ) data.Add(new Snoop.Data.Xyz(propertyName, value as XYZ)); else if (value is Boolean) data.Add(new Snoop.Data.Bool(propertyName, (Boolean)value)); //else if (value is ElementId) // data.Add(new Snoop.Data.ElementId(propertyName, value as ElementId, null)); else if (value is Double) data.Add(new Snoop.Data.Double(propertyName, (Double)value)); else if (value is String) data.Add(new Snoop.Data.String(propertyName, value as String)); else if (value is IEnumerable) data.Add(new Snoop.Data.Enumerable(propertyName, value as IEnumerable)); else if (value is Enum) data.Add(new Snoop.Data.String(propertyName, value.ToString())); else if (value is int) data.Add(new Snoop.Data.Int(propertyName, (int)value)); else if (value is UV) data.Add(new Snoop.Data.Uv(propertyName, value as UV)); else if (IsKeyValuePair(value)) { Type type = value.GetType(); PropertyInfo propInfo = type.GetProperty("Key"); Object propValue = propInfo.GetValue(value, null); AppendThePropertyObject(data, propInfo.Name, propValue); propInfo = type.GetProperty("Value"); propValue = propInfo.GetValue(value, null); AppendThePropertyObject(data, propInfo.Name, propValue); } else { if(propertyName.ToLower().CompareTo(value.GetType().FullName.ToLower()) != 0) data.Add(new Snoop.Data.Object(propertyName, value)); } } } }