Rob van der Woude's Scripting Pages
Powered by GeSHi

Source code for radiobuttonbox.cs

(view source code of radiobuttonbox.cs as plain text)

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Drawing;
  4. using System.IO;
  5. using System.Runtime.InteropServices;
  6. using System.Text;
  7. using System.Text.RegularExpressions;
  8. using System.Windows.Forms;
  9.  
  10.  
  11. namespace RobvanderWoude
  12. {
  13. 	class RadioButtonBox
  14. 	{
  15. 		public static string progver = "1.01";
  16.  
  17.  
  18. 		#region Global Variables
  19.  
  20. 		public static bool returnindex0 = false;
  21. 		public static bool returnindex1 = false;
  22. 		public const int defaultwindowheight = 320;
  23. 		public const int defaultwindowwidth = 480;
  24. 		public const int minimumwindowheight = 90;
  25. 		public const int minimumwindowwidth = 200;
  26. 		public static int screenheight = Screen.PrimaryScreen.WorkingArea.Height;
  27. 		public static int screenwidth = Screen.PrimaryScreen.WorkingArea.Width;
  28. 		public static int maximumwindowheight = screenheight;
  29. 		public static int maximumwindowwidth = screenwidth;
  30. 		public static int borderX = 0;
  31. 		public static int borderY = 0;
  32.  
  33. 		#endregion Global Variables
  34.  
  35.  
  36. 		[STAThread]
  37. 		static int Main( string[] args )
  38. 		{
  39. 			#region Initialize Variables
  40.  
  41. 			char delimiter = ';';
  42. 			List<string> namedargs = new List<string>( );
  43. 			List<string> unnamedargs = new List<string>( );
  44. 			string file = String.Empty;
  45. 			string list = String.Empty;
  46. 			string cancelcaption = "&Cancel";
  47. 			string okcaption = "&OK";
  48. 			string localizationstring = String.Empty;
  49. 			string prompt = String.Empty;
  50. 			string selectedtext = String.Empty;
  51. 			string title = String.Format( "RadioButtonBox,  Version {0}", progver );
  52. 			bool deduplist = false;
  53. 			bool delimiterset = false;
  54. 			bool heightset = false;
  55. 			bool iconset = false;
  56. 			bool indexset = false;
  57. 			bool localizedcaptionset = false;
  58. 			bool monospaced = false;
  59. 			bool skipfirstitem = false;
  60. 			bool sortlist = false;
  61. 			bool tablengthset = false;
  62. 			bool topmost = true;
  63. 			bool widthset = false;
  64. 			double rbsafetyfactor = 1.05;
  65. 			int columns = 0;
  66. 			int defaultindex = 0;
  67. 			int icon = 23;
  68. 			int promptheight = 20;
  69. 			int rc = 0;
  70. 			int rows = 0;
  71. 			int tablength = 4;
  72. 			int windowheight = defaultwindowheight;
  73. 			int windowwidth = defaultwindowwidth;
  74. 			Label labelPrompt = new Label( );
  75.  
  76. 			bool isredirected = Console.IsInputRedirected; // Requires .NET Framework 4.5
  77. 			bool listset = isredirected;
  78. 			int redirectnum = ( isredirected ? 1 : 0 );
  79. 			int arguments = args.Length + redirectnum;
  80.  
  81. 			int[] borders = BorderDimensions( );
  82. 			borderX = borders[0];
  83. 			borderY = borders[1];
  84. 			maximumwindowheight = screenheight - borderY;
  85. 			maximumwindowwidth = screenwidth - borderX;
  86.  
  87. 			#endregion Initialize Variables
  88.  
  89.  
  90. 			#region Command Line Parsing
  91.  
  92. 			if ( arguments == 0 )
  93. 			{
  94. 				return ShowHelp( );
  95. 			}
  96.  
  97. 			if ( arguments > 16 )
  98. 			{
  99. 				return ShowHelp( "Too many command line arguments" );
  100. 			}
  101.  
  102. 			// Split up named and unnamed arguments
  103. 			foreach ( string arg in args )
  104. 			{
  105. 				if ( arg[0] == '/' )
  106. 				{
  107. 					namedargs.Add( arg );
  108. 				}
  109. 				else
  110. 				{
  111. 					unnamedargs.Add( arg );
  112. 				}
  113. 			}
  114.  
  115. 			// Read Standard Input if the list is redirected
  116. 			if ( isredirected )
  117. 			{
  118. 				try
  119. 				{
  120. 					string delim = delimiter.ToString( );
  121. 					list = String.Join( delim, Console.In.ReadToEnd( ).Split( "\n\r".ToCharArray( ), StringSplitOptions.RemoveEmptyEntries ) );
  122. 					// Trim list items, remove empty ones
  123. 					string pattern = "\\s*" + delim + "+\\s*";
  124. 					list = Regex.Replace( list, pattern, delim );
  125. 				}
  126. 				catch ( Exception e )
  127. 				{
  128. 					return ShowHelp( e.Message );
  129. 				}
  130. 			}
  131.  
  132. 			// First, validate the named arguments
  133. 			#region Named Arguments
  134.  
  135. 			foreach ( string arg in namedargs )
  136. 			{
  137. 				if ( arg.Length > 1 && !arg.Contains( ":" ) && !arg.Contains( "=" ) )
  138. 				{
  139. 					#region Boolean Named Arguments
  140.  
  141. 					string key = arg.ToUpper( );
  142. 					switch ( key )
  143. 					{
  144. 						case "/?":
  145. 						case "/HELP":
  146. 							return ShowHelp( );
  147. 						case "/??":
  148. 						case "/???":
  149. 						case "/A":
  150. 						case "/ALIAS":
  151. 						case "/ALIASES":
  152. 							return ShowAliases( );
  153. 						case "/DE":
  154. 						case "/DEDUP":
  155. 							if ( deduplist )
  156. 							{
  157. 								return ShowHelp( "Duplicate command line switch /DE" );
  158. 							}
  159. 							deduplist = true;
  160. 							break;
  161. 						case "/K":
  162. 						case "/SKIP":
  163. 						case "/SKIPFIRST":
  164. 						case "/SKIPFIRSTITEM":
  165. 							if ( skipfirstitem )
  166. 							{
  167. 								return ShowHelp( "Duplicate command line switch /K" );
  168. 							}
  169. 							skipfirstitem = true;
  170. 							break;
  171. 						case "/L":
  172. 						case "/LOCALIZED":
  173. 						case "/LOCALIZEDCAPTIONS":
  174. 							if ( localizedcaptionset )
  175. 							{
  176. 								return ShowHelp( "Duplicate command line switch /L" );
  177. 							}
  178. 							localizedcaptionset = true;
  179. 							break;
  180. 						case "/MF":
  181. 						case "/MONO":
  182. 						case "/MONOSPACED":
  183. 						case "/MONOSPACEDFONT":
  184. 							if ( monospaced )
  185. 							{
  186. 								return ShowHelp( "Duplicate command line switch /MF" );
  187. 							}
  188. 							monospaced = true;
  189. 							break;
  190. 						case "/NM":
  191. 						case "/NONMODAL":
  192. 						case "/NON-MODAL":
  193. 							if ( !topmost )
  194. 							{
  195. 								return ShowHelp( "Duplicate command line switch /NM" );
  196. 							}
  197. 							topmost = false;
  198. 							break;
  199. 						case "/RC0":
  200. 						case "/RETURN0BASEDINDEX":
  201. 							if ( returnindex0 || returnindex1 )
  202. 							{
  203. 								return ShowHelp( "Duplicate command line switch /RC0 and/or /RC1" );
  204. 							}
  205. 							returnindex0 = true;
  206. 							break;
  207. 						case "/RC1":
  208. 						case "/RETURN1BASEDINDEX":
  209. 							if ( returnindex0 || returnindex1 )
  210. 							{
  211. 								return ShowHelp( "Duplicate command line switch /RC0 and/or /RC1" );
  212. 							}
  213. 							returnindex1 = true;
  214. 							break;
  215. 						case "/S":
  216. 						case "/SORT":
  217. 						case "/SORTLIST":
  218. 							if ( sortlist )
  219. 							{
  220. 								return ShowHelp( "Duplicate command line switch /S" );
  221. 							}
  222. 							sortlist = true;
  223. 							break;
  224. 						default:
  225. 							return ShowHelp( "Invalid command line switch {0} or missing value", arg );
  226. 					}
  227.  
  228. 					#endregion Boolean Named Arguments
  229. 				}
  230. 				else if ( arg.Length > 3 && ( arg.Contains( ":" ) || arg.Contains( "=" ) ) )
  231. 				{
  232. 					#region Key/Value Named Arguments
  233.  
  234. 					string key = arg.ToUpper( ).Substring( 0, arg.IndexOfAny( ":=".ToCharArray( ) ) );
  235. 					string val = arg.Substring( arg.IndexOfAny( ":=".ToCharArray( ) ) + 1 );
  236. 					switch ( key )
  237. 					{
  238. 						case "/C":
  239. 						case "/COL":
  240. 						case "/COLS":
  241. 						case "/COLUMNS":
  242. 							if ( columns != 0 )
  243. 							{
  244. 								return ShowHelp( "Duplicate command line switch /C" );
  245. 							}
  246. 							try
  247. 							{
  248. 								columns = Convert.ToInt32( val );
  249. 								if ( columns < 0 )
  250. 								{
  251. 									return ShowHelp( "Columns must be a positive integer" );
  252. 								}
  253. 							}
  254. 							catch ( Exception )
  255. 							{
  256. 								return ShowHelp( "Invalid columns value \"{0}\"", arg );
  257. 							}
  258. 							break;
  259. 						case "/D":
  260. 						case "/DELIMITER":
  261. 							if ( delimiterset )
  262. 							{
  263. 								return ShowHelp( "Duplicate command line switch /D" );
  264. 							}
  265. 							if ( val.Length == 1 )
  266. 							{
  267. 								delimiter = val[0];
  268. 							}
  269. 							else if ( val.Length == 3 && ( ( val[0] == '"' && val[2] == '"' ) || ( val[0] == '\'' && val[2] == '\'' ) ) )
  270. 							{
  271. 								delimiter = val[1];
  272. 							}
  273. 							else
  274. 							{
  275. 								return ShowHelp( String.Format( "Invalid delimiter specified \"{0}\"", arg ) );
  276. 							}
  277. 							break;
  278. 						case "/F":
  279. 						case "/FILE":
  280. 							if ( listset )
  281. 							{
  282. 								return ShowHelp( "Duplicate command line switch /F" );
  283. 							}
  284. 							file = val;
  285. 							if ( String.IsNullOrEmpty( file ) || !File.Exists( file ) )
  286. 							{
  287. 								return ShowHelp( "List file not found: \"{0}\"", file );
  288. 							}
  289. 							else
  290. 							{
  291. 								try
  292. 								{
  293. 									string delim = delimiter.ToString( );
  294. 									list = String.Join( delim, File.ReadLines( file ) );
  295. 									string pattern = delim + "{2,}";
  296. 									// Remove empty list items
  297. 									Regex.Replace( list, pattern, delim );
  298. 								}
  299. 								catch ( Exception e )
  300. 								{
  301. 									return ShowHelp( e.Message );
  302. 								}
  303. 							}
  304. 							listset = true;
  305. 							break;
  306. 						case "/H":
  307. 						case "/HEIGHT":
  308. 							if ( heightset )
  309. 							{
  310. 								return ShowHelp( "Duplicate command line switch /H" );
  311. 							}
  312. 							try
  313. 							{
  314. 								windowheight = Convert.ToInt32( val );
  315. 								if ( windowheight < minimumwindowheight || windowheight > maximumwindowheight )
  316. 								{
  317. 									return ShowHelp( String.Format( "Height {0} outside allowed range of {1}..{2}", val, minimumwindowheight, maximumwindowheight ) );
  318. 								}
  319. 							}
  320. 							catch ( Exception e )
  321. 							{
  322. 								return ShowHelp( String.Format( "Invalid height \"{0}\": {1}", val, e.Message ) );
  323. 							}
  324. 							heightset = true;
  325. 							break;
  326. 						case "/I":
  327. 						case "/ICON":
  328. 							if ( iconset )
  329. 							{
  330. 								return ShowHelp( "Duplicate command line switch /I" );
  331. 							}
  332. 							try
  333. 							{
  334. 								icon = Convert.ToInt32( val );
  335. 							}
  336. 							catch ( Exception )
  337. 							{
  338. 								return ShowHelp( "Invalid icon index: {0}", val );
  339. 							}
  340. 							iconset = true;
  341. 							break;
  342. 						case "/L":
  343. 						case "/LOCALIZED":
  344. 						case "/LOCALIZEDCAPTIONS":
  345. 							if ( localizedcaptionset )
  346. 							{
  347. 								return ShowHelp( "Duplicate command line switch /L" );
  348. 							}
  349. 							localizedcaptionset = true;
  350. 							localizationstring = val;
  351. 							string localizationpattern = "(;|^)(OK|Cancel)=[^\\\"';]+(;|$)";
  352. 							foreach ( string substring in localizationstring.Split( ";".ToCharArray( ), StringSplitOptions.RemoveEmptyEntries ) )
  353. 							{
  354. 								if ( !Regex.IsMatch( substring, localizationpattern, RegexOptions.IgnoreCase ) )
  355. 								{
  356. 									return ShowHelp( "Invalid value for /L switch: \"{1}\"", localizationstring );
  357. 								}
  358. 							}
  359. 							break;
  360. 						case "/P":
  361. 						case "/DEFAULT":
  362. 						case "/DEFAULTINDEX":
  363. 						case "/PRE":
  364. 						case "/PRESELECTED":
  365. 						case "/PRESELECTEDINTEX":
  366. 							if ( indexset )
  367. 							{
  368. 								return ShowHelp( "Duplicate command line switch /P" );
  369. 							}
  370. 							try
  371. 							{
  372. 								defaultindex = Convert.ToInt32( val );
  373. 							}
  374. 							catch ( Exception e )
  375. 							{
  376. 								return ShowHelp( String.Format( "Invalid index value \"{0}\": {1}", arg, e.Message ) );
  377. 							}
  378. 							break;
  379. 						case "/R":
  380. 						case "/ROWS":
  381. 							if ( rows != 0 )
  382. 							{
  383. 								return ShowHelp( "Duplicate command line switch /R" );
  384. 							}
  385. 							try
  386. 							{
  387. 								rows = Convert.ToInt32( val );
  388. 								if ( rows < 0 )
  389. 								{
  390. 									return ShowHelp( "Rows must be a positive integer" );
  391. 								}
  392. 							}
  393. 							catch ( Exception )
  394. 							{
  395. 								return ShowHelp( "Invalid rows value \"{0}\"", arg );
  396. 							}
  397. 							break;
  398. 						case "/T:":
  399. 						case "/TAB":
  400. 						case "/TABLENGTH":
  401. 							if ( tablengthset )
  402. 							{
  403. 								return ShowHelp( "Duplicate command line switch /T" );
  404. 							}
  405. 							try
  406. 							{
  407. 								tablength = Convert.ToInt32( val );
  408. 								if ( tablength < 4 || tablength > 16 )
  409. 								{
  410. 									return ShowHelp( String.Format( "Tab length {0} outside allowed range of {1}..{2}", val, 4, 16 ) );
  411. 								}
  412. 							}
  413. 							catch ( Exception e )
  414. 							{
  415. 								return ShowHelp( String.Format( "Invalid tab length \"{0}\": {1}", val, e.Message ) );
  416. 							}
  417. 							tablengthset = true;
  418. 							break;
  419. 						case "/W":
  420. 						case "/WIDTH":
  421. 							if ( widthset )
  422. 							{
  423. 								return ShowHelp( "Duplicate command line switch /W" );
  424. 							}
  425. 							try
  426. 							{
  427. 								windowwidth = Convert.ToInt32( val );
  428. 								if ( windowwidth < minimumwindowwidth || windowwidth > maximumwindowwidth )
  429. 								{
  430. 									return ShowHelp( String.Format( "Width {0} outside allowed range of {1}..{2}", val, minimumwindowwidth, maximumwindowwidth ) );
  431. 								}
  432. 							}
  433. 							catch ( Exception e )
  434. 							{
  435. 								return ShowHelp( String.Format( "Invalid width \"{0}\": {1}", val, e.Message ) );
  436. 							}
  437. 							widthset = true;
  438. 							break;
  439. 						default:
  440. 							return ShowHelp( String.Format( "Invalid command line switch \"{0}\"", arg ) );
  441. 					}
  442.  
  443. 					#endregion Key/Value Named Arguments
  444.  
  445. 				}
  446. 			}
  447.  
  448. 			#endregion Named Arguments
  449.  
  450.  
  451. 			// Next, validate unnamed arguments
  452. 			#region Unnamed Arguments
  453.  
  454. 			if ( listset ) // This check is the reason why named arguments had to be validated before unnamed ones: /F switch changes the meaning of unnamed arguments
  455. 			{
  456. 				switch ( unnamedargs.Count )
  457. 				{
  458. 					case 0:
  459. 						break;
  460. 					case 1:
  461. 						prompt = unnamedargs[0];
  462. 						break;
  463. 					case 2:
  464. 						prompt = unnamedargs[0];
  465. 						title = unnamedargs[1];
  466. 						break;
  467. 					case 3:
  468. 						return ShowHelp( "Invalid command line argument: {0}", unnamedargs[2] );
  469. 					default:
  470. 						unnamedargs.RemoveRange( 0, 2 );
  471. 						return ShowHelp( "Invalid command line arguments: {0}", String.Join( ", ", unnamedargs ) );
  472. 				}
  473. 			}
  474. 			else
  475. 			{
  476. 				switch ( unnamedargs.Count )
  477. 				{
  478. 					case 0:
  479. 						break;
  480. 					case 1:
  481. 						list = unnamedargs[0];
  482. 						listset = true;
  483. 						break;
  484. 					case 2:
  485. 						list = unnamedargs[0];
  486. 						prompt = unnamedargs[1];
  487. 						listset = true;
  488. 						break;
  489. 					case 3:
  490. 						list = unnamedargs[0];
  491. 						prompt = unnamedargs[1];
  492. 						title = unnamedargs[2];
  493. 						listset = true;
  494. 						break;
  495. 					case 4:
  496. 						return ShowHelp( "Invalid command line argument: {0}", unnamedargs[3] );
  497. 					default:
  498. 						unnamedargs.RemoveRange( 0, 3 );
  499. 						return ShowHelp( "Invalid command line arguments: {0}", String.Join( ", ", unnamedargs ) );
  500. 				}
  501. 			}
  502.  
  503. 			#endregion Unnamed Arguments
  504.  
  505.  
  506. 			#region Validate Specified Values and Combinations
  507.  
  508. 			if ( !listset )
  509. 			{
  510. 				return ShowHelp( "Mandatory list not specified" );
  511. 			}
  512.  
  513. 			if ( rows * columns != 0 )
  514. 			{
  515. 				return ShowHelp( "You may specify the number of either columns or rows, but not both" );
  516. 			}
  517.  
  518. 			int listrange = list.Split( new char[] { delimiter }, StringSplitOptions.RemoveEmptyEntries ).Length - 1;
  519. 			if ( defaultindex < 0 || defaultindex > listrange )
  520. 			{
  521. 				return ShowHelp( String.Format( "Preselected index ({0}) outside list range (0..{1})", defaultindex, listrange ) );
  522. 			}
  523.  
  524. 			#endregion Validate Specified Values and Combinations
  525.  
  526. 			#endregion Command Line Parsing
  527.  
  528.  
  529. 			#region Set Localized Captions
  530.  
  531. 			if ( localizedcaptionset )
  532. 			{
  533. 				cancelcaption = Load( "user32.dll", 801, cancelcaption );
  534. 				okcaption = Load( "user32.dll", 800, okcaption );
  535.  
  536. 				if ( !String.IsNullOrWhiteSpace( localizationstring ) )
  537. 				{
  538. 					string[] locstrings = localizationstring.Split( ";".ToCharArray( ) );
  539. 					foreach ( string locstring in locstrings )
  540. 					{
  541. 						string key = locstring.Substring( 0, locstring.IndexOf( '=' ) );
  542. 						string val = locstring.Substring( Math.Min( locstring.IndexOf( '=' ) + 1, locstring.Length - 1 ) );
  543. 						if ( !String.IsNullOrWhiteSpace( val ) )
  544. 						{
  545. 							switch ( key.ToUpper( ) )
  546. 							{
  547. 								case "OK":
  548. 									okcaption = val;
  549. 									break;
  550. 								case "CANCEL":
  551. 									cancelcaption = val;
  552. 									break;
  553. 								default:
  554. 									return ShowHelp( "Invalid localization key \"{0}\"", key );
  555. 							}
  556. 						}
  557. 					}
  558. 				}
  559. 			}
  560.  
  561. 			#endregion Set Localized Captions
  562.  
  563.  
  564. 			#region Parse List
  565.  
  566. 			List<string> listitems = new List<string>( list.Split( delimiter.ToString( ).ToCharArray( ), StringSplitOptions.RemoveEmptyEntries ) );
  567. 			if ( skipfirstitem )
  568. 			{
  569. 				listitems.RemoveAt( 0 );
  570. 			}
  571. 			for ( int i = 0; i < listitems.Count; i++ )
  572. 			{
  573. 				listitems[i] = listitems[i].Trim( );
  574. 			}
  575. 			if ( deduplist )
  576. 			{
  577. 				List<string> deduped = new List<string>( );
  578. 				foreach ( string key in listitems )
  579. 				{
  580. 					if ( !deduped.Contains( key ) )
  581. 					{
  582. 						deduped.Add( key );
  583. 					}
  584. 				}
  585. 				listitems = deduped;
  586. 			}
  587. 			if ( sortlist )
  588. 			{
  589. 				listitems.Sort( StringComparer.OrdinalIgnoreCase );
  590. 			}
  591.  
  592. 			#endregion Parse List
  593.  
  594.  
  595. 			#region Main Form
  596.  
  597. 			Form radiobuttonform = new Form( );
  598. 			radiobuttonform.FormBorderStyle = FormBorderStyle.FixedDialog;
  599. 			radiobuttonform.MaximizeBox = false;
  600. 			radiobuttonform.MinimizeBox = false;
  601. 			radiobuttonform.StartPosition = FormStartPosition.CenterParent;
  602. 			radiobuttonform.Text = title;
  603. 			radiobuttonform.Icon = IconExtractor.Extract( "shell32.dll", icon, true );
  604.  
  605. 			#endregion Main Form
  606.  
  607.  
  608. 			#region Initial Sizes
  609.  
  610. 			int horizontalmargin = 10;
  611. 			int verticalmargin = 10;
  612. 			int promptwidth = 0;
  613. 			int rbtextheight = 0; // radiobutton text height
  614. 			int rbtextwidth = 0; // radiobutton text width
  615. 			int rbwidth = 15; // radiobutton width without text
  616. 			int buttonheight = 25;
  617. 			int buttonwidth = 80;
  618.  
  619. 			#endregion Initial Sizes
  620.  
  621.  
  622. 			#region Prompt
  623.  
  624. 			if ( String.IsNullOrWhiteSpace( prompt ) )
  625. 			{
  626. 				promptheight = -1 * verticalmargin;
  627. 			}
  628. 			else
  629. 			{
  630. 				if ( monospaced )
  631. 				{
  632. 					labelPrompt.Font = new Font( FontFamily.GenericMonospace, labelPrompt.Font.Size );
  633. 				}
  634.  
  635. 				// Calculate required height for single prompt line
  636. 				promptheight = TextRenderer.MeasureText( "Test", labelPrompt.Font ).Height;
  637.  
  638. 				// Replace tabs with spaces
  639. 				if ( prompt.IndexOf( "\\t", StringComparison.Ordinal ) > -1 )
  640. 				{
  641. 					string tab = new String( ' ', tablength );
  642. 					// First split the prompt on newlines
  643. 					string[] prompt2 = prompt.Split( new string[] { "\\n" }, StringSplitOptions.None );
  644. 					for ( int i = 0; i < prompt2.Length; i++ )
  645. 					{
  646. 						if ( prompt2[i].IndexOf( "\\t", StringComparison.Ordinal ) > -1 )
  647. 						{
  648. 							// Split each "sub-line" of the prompt on tabs
  649. 							string[] prompt3 = prompt2[i].Split( new string[] { "\\t" }, StringSplitOptions.None );
  650. 							// Each substring before a tab gets n spaces attached, and then is shortened to the greatest possible multiple of n
  651. 							for ( int j = 0; j < prompt3.Length - 1; j++ )
  652. 							{
  653. 								prompt3[j] += tab;
  654. 								int length = prompt3[j].Length;
  655. 								length /= tablength;
  656. 								length *= tablength;
  657. 								prompt3[j] = prompt3[j].Substring( 0, length );
  658. 							}
  659. 							prompt2[i] = String.Join( "", prompt3 );
  660. 						}
  661. 					}
  662. 					prompt = String.Join( "\n", prompt2 );
  663. 				}
  664. 				prompt = prompt.Replace( "\\n", "\n" ).Replace( "\\r", "\r" );
  665. 				labelPrompt.Text = prompt;
  666. 				string[] lines = prompt.Split( "\n".ToCharArray( ) );
  667. 				int promptlines = lines.Length;
  668.  
  669. 				foreach ( string line in lines )
  670. 				{
  671. 					promptwidth = Math.Max( promptwidth, TextRenderer.MeasureText( line, labelPrompt.Font ).Width );
  672. 				}
  673.  
  674. 				// Calculate required height for multiple line prompt
  675. 				promptheight = promptlines * promptheight;
  676. 			}
  677.  
  678. 			#endregion Prompt
  679.  
  680.  
  681. 			#region Radio Buttons
  682.  
  683. 			List<RadioButton> radiobuttons = new List<RadioButton>( );
  684. 			for ( int i = 0; i < listitems.Count; i++ )
  685. 			{
  686. 				RadioButton radiobutton = new RadioButton( );
  687. 				radiobutton.Text = listitems[i];
  688. 				radiobutton.Checked = ( defaultindex == i );
  689. 				rbtextwidth = Math.Max( rbtextwidth, Convert.ToInt32( TextRenderer.MeasureText( listitems[i], radiobutton.Font ).Width * rbsafetyfactor ) );
  690. 				rbtextheight = Math.Max( rbtextheight, Convert.ToInt32( TextRenderer.MeasureText( listitems[i], radiobutton.Font ).Height * rbsafetyfactor ) );
  691. 				radiobuttons.Add( radiobutton );
  692. 			}
  693.  
  694. 			GroupBox rbgroup = new GroupBox( );
  695.  
  696. 			#endregion Radio Buttons
  697.  
  698.  
  699. 			#region Buttons
  700.  
  701. 			Button okButton = new Button( );
  702. 			okButton.DialogResult = DialogResult.OK;
  703. 			okButton.Name = "okButton";
  704. 			okButton.Text = okcaption;
  705.  
  706. 			Button cancelButton = new Button( );
  707. 			cancelButton.DialogResult = DialogResult.Cancel;
  708. 			cancelButton.Name = "cancelButton";
  709. 			cancelButton.Text = cancelcaption;
  710.  
  711. 			#endregion Buttons
  712.  
  713.  
  714. 			#region Calculate Window Layout
  715.  
  716. 			if ( rows > 0 )
  717. 			{
  718. 				rows = Math.Min( rows, listitems.Count );
  719. 				columns = (int) Math.Floor( (decimal) ( listitems.Count + rows - 1 ) / rows );
  720. 			}
  721. 			else if ( columns > 0 )
  722. 			{
  723. 				columns = Math.Min( columns, listitems.Count );
  724. 				rows = (int) Math.Floor( (decimal) ( listitems.Count + columns - 1 ) / columns );
  725. 			}
  726. 			else
  727. 			{
  728. 				columns = 1;
  729. 				rows = listitems.Count;
  730. 			}
  731.  
  732. 			int rbgroupwidth = 0;
  733. 			int rbgroupheight = 0;
  734. 			int rowheight = 0;
  735. 			int colwidth = 0;
  736.  
  737. 			if ( widthset )
  738. 			{
  739. 				rbgroupwidth = windowwidth - 2 * horizontalmargin;
  740. 				colwidth = (int) Math.Floor( (decimal) ( rbgroupwidth - horizontalmargin - columns * ( rbwidth + horizontalmargin ) ) / columns );
  741. 			}
  742. 			else
  743. 			{
  744. 				colwidth = rbtextwidth + rbwidth;
  745. 				rbgroupwidth = Math.Max( minimumwindowwidth - 2 * horizontalmargin, columns * ( colwidth + horizontalmargin ) + 2 * horizontalmargin );
  746. 				windowwidth = Math.Max( minimumwindowwidth, rbgroupwidth + 2 * horizontalmargin );
  747. 			}
  748.  
  749. 			if ( heightset )
  750. 			{
  751. 				rbgroupheight = windowheight - promptheight - buttonheight - 4 * verticalmargin;
  752. 				rowheight = (int) Math.Floor( (decimal) ( rbgroupheight - 2 * verticalmargin ) / rows );
  753. 			}
  754. 			else
  755. 			{
  756. 				rowheight = rbtextheight + verticalmargin;
  757. 				windowheight = Math.Max( minimumwindowheight, 6 * verticalmargin + promptheight + buttonheight + rows * rowheight );
  758. 				rbgroupheight = rows * rowheight + 2 * verticalmargin;
  759. 			}
  760.  
  761. 			#endregion Calculate Window Layout
  762.  
  763.  
  764. 			#region Check Available Group Box Space
  765.  
  766. 			if ( rbgroupheight / rows < rowheight || rbgroupwidth / columns < colwidth )
  767. 			{
  768. 				return ShowHelp( "Window size too small to display all radio buttons;\n\tincrease window size, reduce or remove prompt,\n\tand/or change number of rows and columns" );
  769. 			}
  770.  
  771. 			#endregion Check Available Group Box Space
  772.  
  773.  
  774. 			#region Build Form
  775.  
  776. 			Size windowsize = new Size( windowwidth, windowheight );
  777. 			radiobuttonform.ClientSize = windowsize;
  778.  
  779. 			if ( !String.IsNullOrWhiteSpace( prompt ) )
  780. 			{
  781. 				labelPrompt.Size = new Size( windowwidth - 2 * horizontalmargin, promptheight );
  782. 				labelPrompt.Location = new Point( horizontalmargin, verticalmargin );
  783. 				radiobuttonform.Controls.Add( labelPrompt );
  784. 			}
  785.  
  786. 			rbgroup.Size = new Size( rbgroupwidth, rbgroupheight );
  787. 			rbgroup.Location = new Point( horizontalmargin, promptheight + 2 * verticalmargin );
  788.  
  789. 			foreach ( RadioButton radiobutton in radiobuttons )
  790. 			{
  791. 				rbgroup.Controls.Add( radiobutton );
  792. 			}
  793. 			radiobuttonform.Controls.Add( rbgroup );
  794.  
  795. 			for ( int row = 0; row < rows; row++ )
  796. 			{
  797. 				for ( int column = 0; column < columns; column++ )
  798. 				{
  799. 					int index = row * columns + column;
  800. 					if ( index < radiobuttons.Count )
  801. 					{
  802. 						int x = Convert.ToInt32( horizontalmargin + column * ( colwidth + horizontalmargin ) );
  803. 						int y = Convert.ToInt32( verticalmargin + row * rowheight );
  804. 						radiobuttons[index].Size = new Size( colwidth + horizontalmargin, rowheight );
  805. 						radiobuttons[index].Location = new Point( x, y );
  806. 					}
  807. 				}
  808. 			}
  809.  
  810. 			okButton.Size = new Size( buttonwidth, buttonheight );
  811. 			okButton.Location = new Point( windowwidth / 2 - horizontalmargin - buttonwidth, windowheight - buttonheight - verticalmargin );
  812. 			radiobuttonform.Controls.Add( okButton );
  813.  
  814. 			cancelButton.Size = new Size( buttonwidth, buttonheight );
  815. 			cancelButton.Location = new Point( windowwidth / 2 + horizontalmargin, windowheight - buttonheight - verticalmargin );
  816. 			radiobuttonform.Controls.Add( cancelButton );
  817.  
  818. 			radiobuttonform.AcceptButton = okButton;  // OK on Enter
  819. 			radiobuttonform.CancelButton = cancelButton; // Cancel on Esc
  820.  
  821. 			#endregion Build Form
  822.  
  823.  
  824. 			#region Show Dialog
  825.  
  826. 			radiobuttonform.TopMost = topmost;
  827. 			DialogResult result = radiobuttonform.ShowDialog( );
  828. 			if ( result == DialogResult.OK )
  829. 			{
  830. 				foreach ( RadioButton rb in rbgroup.Controls )
  831. 				{
  832. 					if ( rb.Checked )
  833. 					{
  834. 						selectedtext = rb.Text;
  835. 						defaultindex = listitems.IndexOf( selectedtext );
  836. 						break;
  837. 					}
  838. 				}
  839.  
  840. 				// Display selected text
  841. 				Console.WriteLine( selectedtext );
  842.  
  843. 				if ( returnindex0 )
  844. 				{
  845. 					// With /RO: return code equals selected index for "OK", or -1 for (command line) errors or "Cancel".
  846. 					rc = defaultindex;
  847. 				}
  848. 				else if ( returnindex1 )
  849. 				{
  850. 					// With /RI: return code equals selected index + 1 for "OK", or 0 for (command line) errors or "Cancel".
  851. 					rc = defaultindex + 1;
  852. 				}
  853. 				else
  854. 				{
  855. 					// Default: return code 0 for "OK", 1 for (command line) errors, 2 for "Cancel".
  856. 					rc = 0;
  857. 				}
  858. 			}
  859. 			else
  860. 			{
  861. 				// Cancelled
  862. 				if ( returnindex0 )
  863. 				{
  864. 					// With /RO: return code equals selected index for "OK", or -1 for (command line) errors or "Cancel".
  865. 					rc = -1;
  866. 				}
  867. 				else if ( returnindex1 )
  868. 				{
  869. 					// With /RI: return code equals selected index + 1 for "OK", or 0 for (command line) errors or "Cancel".
  870. 					rc = 0;
  871. 				}
  872. 				else
  873. 				{
  874. 					// Default: return code 0 for "OK", 1 for (command line) errors, 2 for "Cancel".
  875. 					rc = 2;
  876. 				}
  877. 			}
  878.  
  879. 			#endregion Show Dialog
  880.  
  881.  
  882. 			return rc;
  883. 		}
  884.  
  885. 		#region Error handling
  886.  
  887. 		public static int ShowAliases()
  888. 		{
  889. 			int rc = ShowHelp( );
  890.  
  891. 			Console.Error.WriteLine( );
  892.  
  893. 			Console.Error.WriteLine( );
  894.  
  895. 			Console.ForegroundColor = ConsoleColor.White;
  896. 			Console.Error.WriteLine( "Switch\tAlias(es)" );
  897.  
  898. 			Console.Error.WriteLine( "======\t=========" );
  899.  
  900. 			Console.Error.WriteLine( );
  901.  
  902. 			Console.Error.Write( "/?\t" );
  903. 			Console.ResetColor( );
  904. 			Console.Error.WriteLine( "/HELP" );
  905.  
  906. 			Console.Error.WriteLine( );
  907.  
  908. 			Console.ForegroundColor = ConsoleColor.White;
  909. 			Console.Error.Write( "/A\t" );
  910. 			Console.ResetColor( );
  911. 			Console.Error.WriteLine( "/??, /???, /ALIAS or /ALIASES" );
  912.  
  913. 			Console.Error.WriteLine( );
  914.  
  915. 			Console.ForegroundColor = ConsoleColor.White;
  916. 			Console.Error.Write( "/C\t" );
  917. 			Console.ResetColor( );
  918. 			Console.Error.WriteLine( "/COL, /COLS or /COLUMNS" );
  919.  
  920. 			Console.Error.WriteLine( );
  921.  
  922. 			Console.ForegroundColor = ConsoleColor.White;
  923. 			Console.Error.Write( "/D\t" );
  924. 			Console.ResetColor( );
  925. 			Console.Error.WriteLine( "/DELIMITER" );
  926.  
  927. 			Console.Error.WriteLine( );
  928.  
  929. 			Console.ForegroundColor = ConsoleColor.White;
  930. 			Console.Error.Write( "/DE\t" );
  931. 			Console.ResetColor( );
  932. 			Console.Error.WriteLine( "/DEDUP" );
  933.  
  934. 			Console.Error.WriteLine( );
  935.  
  936. 			Console.ForegroundColor = ConsoleColor.White;
  937. 			Console.Error.Write( "/F\t" );
  938. 			Console.ResetColor( );
  939. 			Console.Error.WriteLine( "/FILE" );
  940.  
  941. 			Console.Error.WriteLine( );
  942.  
  943. 			Console.ForegroundColor = ConsoleColor.White;
  944. 			Console.Error.Write( "/H\t" );
  945. 			Console.ResetColor( );
  946. 			Console.Error.WriteLine( "/HEIGHT" );
  947.  
  948. 			Console.Error.WriteLine( );
  949.  
  950. 			Console.ForegroundColor = ConsoleColor.White;
  951. 			Console.Error.Write( "/I\t" );
  952. 			Console.ResetColor( );
  953. 			Console.Error.WriteLine( "/ICON" );
  954.  
  955. 			Console.Error.WriteLine( );
  956.  
  957. 			Console.ForegroundColor = ConsoleColor.White;
  958. 			Console.Error.Write( "/K\t" );
  959. 			Console.ResetColor( );
  960. 			Console.Error.WriteLine( "/SKIP, /SKIPFIRST or /SKIPFIRSTITEM" );
  961.  
  962. 			Console.Error.WriteLine( );
  963.  
  964. 			Console.ForegroundColor = ConsoleColor.White;
  965. 			Console.Error.Write( "/L\t" );
  966. 			Console.ResetColor( );
  967. 			Console.Error.WriteLine( "/LOCALIZED or /LOCALIZEDCAPTIONS" );
  968.  
  969. 			Console.Error.WriteLine( );
  970.  
  971. 			Console.ForegroundColor = ConsoleColor.White;
  972. 			Console.Error.Write( "/MF\t" );
  973. 			Console.ResetColor( );
  974. 			Console.Error.WriteLine( "/MONO, /MONOSPACED or /MONOSPACEDFONT" );
  975.  
  976. 			Console.Error.WriteLine( );
  977.  
  978. 			Console.ForegroundColor = ConsoleColor.White;
  979. 			Console.Error.Write( "/NM\t" );
  980. 			Console.ResetColor( );
  981. 			Console.Error.WriteLine( "/NONMODAL or /NON-MODAL" );
  982.  
  983. 			Console.Error.WriteLine( );
  984.  
  985. 			Console.ForegroundColor = ConsoleColor.White;
  986. 			Console.Error.Write( "/P\t" );
  987. 			Console.ResetColor( );
  988. 			Console.Error.WriteLine( "/DEFAULT, /DEFAULTINDEX, /PRE, /PRESELECTED or /PRESELECTEDINDEX" );
  989.  
  990. 			Console.Error.WriteLine( );
  991.  
  992. 			Console.ForegroundColor = ConsoleColor.White;
  993. 			Console.Error.Write( "/R\t" );
  994. 			Console.ResetColor( );
  995. 			Console.Error.WriteLine( "/ROWS" );
  996.  
  997. 			Console.Error.WriteLine( );
  998.  
  999. 			Console.ForegroundColor = ConsoleColor.White;
  1000. 			Console.Error.Write( "/RC0\t" );
  1001. 			Console.ResetColor( );
  1002. 			Console.Error.WriteLine( "/RETURN0BASEDINDEX" );
  1003.  
  1004. 			Console.Error.WriteLine( );
  1005.  
  1006. 			Console.ForegroundColor = ConsoleColor.White;
  1007. 			Console.Error.Write( "/RC1\t" );
  1008. 			Console.ResetColor( );
  1009. 			Console.Error.WriteLine( "/RETURN1BASEDINDEX" );
  1010.  
  1011. 			Console.Error.WriteLine( );
  1012.  
  1013. 			Console.ForegroundColor = ConsoleColor.White;
  1014. 			Console.Error.Write( "/S\t" );
  1015. 			Console.ResetColor( );
  1016. 			Console.Error.WriteLine( "/SORT or /SORTLIST" );
  1017.  
  1018. 			Console.Error.WriteLine( );
  1019.  
  1020. 			Console.ForegroundColor = ConsoleColor.White;
  1021. 			Console.Error.Write( "/T\t" );
  1022. 			Console.ResetColor( );
  1023. 			Console.Error.WriteLine( "/TAB or /TABLENGTH" );
  1024.  
  1025. 			Console.Error.WriteLine( );
  1026.  
  1027. 			Console.ForegroundColor = ConsoleColor.White;
  1028. 			Console.Error.Write( "/W\t" );
  1029. 			Console.ResetColor( );
  1030. 			Console.Error.WriteLine( "/WIDTH" );
  1031.  
  1032. 			Console.Error.WriteLine( );
  1033.  
  1034. 			return rc;
  1035. 		}
  1036.  
  1037.  
  1038. 		public static int ShowHelp( params string[] errmsg )
  1039. 		{
  1040. 			#region Help Text
  1041.  
  1042. 			/*
  1043. 			RadioButtonBox,  Version 1.00
  1044. 			Batch tool to present a Radio Button dialog and return the selected item
  1045.  
  1046. 			Usage:   RADIOBUTTONBOX   "list"  [ "prompt"  [ "title" ] ]  [ options ]
  1047.  
  1048. 			   or:   RADIOBUTTONBOX   /F:"listfile"  [ "prompt"  [ "title" ] ]  [ options ]
  1049.  
  1050. 			   or:   listcommand  |  RADIOBUTTONBOX  [ "prompt"  [ "title" ] ]  [ options ]
  1051.  
  1052. 			Where:   "list"          a list of items to populate the radio buttons group
  1053. 			         "listcommand"   is a command whose standard output is used as a list
  1054. 			                         of items to populate the radio buttons group
  1055. 			         "prompt"        is the optional text above the radio buttons group
  1056. 			                         (default: none)
  1057. 			         "title"         is the window title
  1058. 			                         (default: "RadioButtonBox,  Version 1.00")
  1059. 			Options: /A              show Aliases for command line switches (see Notes)
  1060. 			         /C:columns      sets then number of Columns for the radio button group
  1061. 			                         (default: 1)
  1062. 			         /D:"delimiter"  sets the Delimiter character for "list"
  1063. 			                         (default: semicolon)
  1064. 			         /DE             DEdup: remove duplicates from "list"
  1065. 			         /F:"listfile"   use list from text File (one list item per line)
  1066. 			         /H:height       sets the window Height (default: autosized to make
  1067. 			                         content fit; minimum: 90; maximum: screen height - 68)
  1068. 			         /I:index        use Icon at index from shell32.dll (default: 23)
  1069. 			         /K              sKip first item of list (e.g. a header line)
  1070. 			         /L[:"captions"] Localize or customize button captions
  1071. 			                         (e.g. /L:"OK=Why Not?;Cancel=No Way!")
  1072. 			         /MF             use Monospaced Font in prompt (default: proportional)
  1073. 			         /NM             make dialog Non-Modal (default: Modal, i.e. on top)
  1074. 			         /P:index        sets the zero based index of the Preselected item
  1075. 			                         (default: 0)
  1076. 			         /R:rows         sets then number of Rows for the radio button group
  1077. 			                         (default: one row per list item)
  1078. 			         /RC0            Return Code equals selected 0-based index, or -1 on
  1079. 			                         (command line) errors or if "Cancel" was clicked
  1080. 			                         (default: 0 on "OK", 1 on error, 2 on "Cancel")
  1081. 			         /RC1            Return Code equals selected 1-based index, or 0 on
  1082. 			                         (command line) errors or if "Cancel" was clicked
  1083. 			                         (default: 0 on "OK", 1 on error, 2 on "Cancel")
  1084. 			         /S              Sort list (default: unsorted)
  1085. 			         /T:tablength    sets the number of spaces for Tabs in prompt
  1086. 			                         (4..16; default: 4)
  1087. 			         /W:width        sets the window Width (default: autosized to make
  1088. 			                         content fit; minimum: 200; maximum: screen width - 16)
  1089.  
  1090. 			Notes:   The selected item text is written to Standard Out if "OK" is clicked,
  1091. 			         otherwise an empty string is returned.
  1092. 			         Most command line switches have one or more aliases, e.g. instead
  1093. 			         of /R:5 you can use /ROWS:5 to make your batch files more readable;
  1094. 			         use command line switch /A to get a list of all available aliases.
  1095. 			         Use either "list" or /F:"listfile" or "listcommand".
  1096. 			         Linefeeds (\n), tabs (\t) and doublequotes (\") are allowed in the
  1097. 			         prompt text (but not in the title); with tabs, /MF is recommended.
  1098. 			         To get a custom title without prompt, use " " for prompt.
  1099. 			         If specified without captions, switch /L forces localized button
  1100. 			         captions (e.g. "Cancel" button caption is "Annuleren" on Dutch
  1101. 			         systems); if only a single custom caption is specified, the other
  1102. 			         one is localized (e.g. with /L:"OK=Gaan" on Dutch systems, "OK"
  1103. 			         button caption is "Gaan", "Cancel" button caption is "Annuleren").
  1104. 			         Return code 0 for "OK", 1 for (command line) errors, 2 for "Cancel".
  1105. 			         With /RC0 return code equals selected index or -1 for "Cancel".
  1106. 			         With /RC1 return code equals selected index + 1, or 0 for "Cancel".
  1107. 			         Command line switches /RC0 and /RC1 are mutually exclusive.
  1108.  
  1109. 			Credits: On-the-fly form based on code by Gorkem Gencay on StackOverflow:
  1110. 			         http://stackoverflow.com/questions/97097#17546909
  1111. 			         Code to retrieve localized button captions by Martin Stoeckli:
  1112. 			         http://martinstoeckli.ch/csharp/csharp.html#windows_text_resources
  1113. 			         Code to extract icons from Shell32.dll by Thomas Levesque:
  1114. 			         http://stackoverflow.com/questions/6873026
  1115.  
  1116. 			Written by Rob van der Woude
  1117. 			http://www.robvanderwoude.com
  1118. 			*/
  1119.  
  1120. 			#endregion Help Text
  1121.  
  1122.  
  1123. 			#region Error Message
  1124.  
  1125. 			if ( errmsg.Length > 0 )
  1126. 			{
  1127. 				List<string> errargs = new List<string>( errmsg );
  1128. 				errargs.RemoveAt( 0 );
  1129. 				Console.Error.WriteLine( );
  1130. 				Console.ForegroundColor = ConsoleColor.Red;
  1131. 				Console.Error.Write( "ERROR:\t" );
  1132. 				Console.ForegroundColor = ConsoleColor.White;
  1133. 				Console.Error.WriteLine( errmsg[0], errargs.ToArray( ) );
  1134. 				Console.ResetColor( );
  1135. 			}
  1136.  
  1137. 			#endregion Error Message
  1138.  
  1139.  
  1140. 			#region Show Help Text
  1141.  
  1142. 			Console.Error.WriteLine( );
  1143.  
  1144. 			Console.Error.WriteLine( "RadioButtonBox,  Version {0}", progver );
  1145.  
  1146. 			Console.Error.WriteLine( "Batch tool to present a Radio Button dialog and return the selected item" );
  1147.  
  1148. 			Console.Error.WriteLine( );
  1149.  
  1150. 			Console.Error.Write( "Usage:   " );
  1151. 			Console.ForegroundColor = ConsoleColor.White;
  1152. 			Console.Error.WriteLine( "RADIOBUTTONBOX   \"list\"  [ \"prompt\"  [ \"title\" ] ]  [ options ]" );
  1153. 			Console.ResetColor( );
  1154.  
  1155. 			Console.Error.WriteLine( );
  1156.  
  1157. 			Console.Error.Write( "   or:   " );
  1158. 			Console.ForegroundColor = ConsoleColor.White;
  1159. 			Console.Error.WriteLine( "RADIOBUTTONBOX   /F:\"listfile\"  [ \"prompt\"  [ \"title\" ] ]  [ options ]" );
  1160. 			Console.ResetColor( );
  1161.  
  1162. 			Console.Error.WriteLine( );
  1163.  
  1164. 			Console.Error.Write( "   or:   " );
  1165. 			Console.ForegroundColor = ConsoleColor.White;
  1166. 			Console.Error.WriteLine( "listcommand  |  RADIOBUTTONBOX  [ \"prompt\"  [ \"title\" ] ]  [ options ]" );
  1167. 			Console.ResetColor( );
  1168.  
  1169. 			Console.Error.WriteLine( );
  1170.  
  1171. 			Console.Error.Write( "Where:   " );
  1172. 			Console.ForegroundColor = ConsoleColor.White;
  1173. 			Console.Error.Write( "\"list\"" );
  1174. 			Console.ResetColor( );
  1175. 			Console.Error.WriteLine( "          a list of items to populate the radio button group" );
  1176.  
  1177. 			Console.ForegroundColor = ConsoleColor.White;
  1178. 			Console.Error.Write( "         \"listcommand\"" );
  1179. 			Console.ResetColor( );
  1180. 			Console.Error.Write( "   is a " );
  1181. 			Console.ForegroundColor = ConsoleColor.White;
  1182. 			Console.Error.Write( "command" );
  1183. 			Console.ResetColor( );
  1184. 			Console.Error.Write( " whose standard output is used as a " );
  1185. 			Console.ForegroundColor = ConsoleColor.White;
  1186. 			Console.Error.WriteLine( "list" );
  1187. 			Console.ResetColor( );
  1188.  
  1189. 			Console.Error.WriteLine( "                         of items to populate the radio button group" );
  1190.  
  1191. 			Console.ForegroundColor = ConsoleColor.White;
  1192. 			Console.Error.Write( "         \"prompt\"" );
  1193. 			Console.ResetColor( );
  1194. 			Console.Error.WriteLine( "        is the optional text above the radio button group" );
  1195.  
  1196. 			Console.Error.WriteLine( "                         (default: none)" );
  1197.  
  1198. 			Console.ForegroundColor = ConsoleColor.White;
  1199. 			Console.Error.Write( "         \"title\"" );
  1200. 			Console.ResetColor( );
  1201. 			Console.Error.WriteLine( "         is the window title" );
  1202.  
  1203. 			Console.Error.WriteLine( "                         (default: \"RadioButtonBox,  Version {0}\")", progver );
  1204.  
  1205. 			Console.Error.Write( "Options: " );
  1206. 			Console.ForegroundColor = ConsoleColor.White;
  1207. 			Console.Error.Write( "/A" );
  1208. 			Console.ResetColor( );
  1209. 			Console.Error.Write( "              show " );
  1210. 			Console.ForegroundColor = ConsoleColor.White;
  1211. 			Console.Error.Write( "A" );
  1212. 			Console.ResetColor( );
  1213. 			Console.Error.WriteLine( "liases for command line switches (see Notes)" );
  1214.  
  1215. 			Console.ForegroundColor = ConsoleColor.White;
  1216. 			Console.Error.Write( "         /C:columns" );
  1217. 			Console.ResetColor( );
  1218. 			Console.Error.Write( "      sets then number of " );
  1219. 			Console.ForegroundColor = ConsoleColor.White;
  1220. 			Console.Error.Write( "C" );
  1221. 			Console.ResetColor( );
  1222. 			Console.Error.WriteLine( "olumns for the radio button group" );
  1223.  
  1224. 			Console.Error.WriteLine( "                         (default: 1)" );
  1225.  
  1226. 			Console.ForegroundColor = ConsoleColor.White;
  1227. 			Console.Error.Write( "         /D:\"delimiter\"" );
  1228. 			Console.ResetColor( );
  1229. 			Console.Error.Write( "  sets the " );
  1230. 			Console.ForegroundColor = ConsoleColor.White;
  1231. 			Console.Error.Write( "D" );
  1232. 			Console.ResetColor( );
  1233. 			Console.Error.Write( "elimiter character for " );
  1234. 			Console.ForegroundColor = ConsoleColor.White;
  1235. 			Console.Error.WriteLine( "\"list\"" );
  1236. 			Console.ResetColor( );
  1237.  
  1238. 			Console.Error.WriteLine( "                         (default: semicolon)" );
  1239.  
  1240. 			Console.ForegroundColor = ConsoleColor.White;
  1241. 			Console.Error.Write( "         /DE             DE" );
  1242. 			Console.ResetColor( );
  1243. 			Console.Error.Write( "dup: remove duplicates from " );
  1244. 			Console.ForegroundColor = ConsoleColor.White;
  1245. 			Console.Error.WriteLine( "\"list\"" );
  1246. 			Console.ResetColor( );
  1247.  
  1248. 			Console.ForegroundColor = ConsoleColor.White;
  1249. 			Console.Error.Write( "         /F:\"listfile\"" );
  1250. 			Console.ResetColor( );
  1251. 			Console.Error.Write( "   use list from text " );
  1252. 			Console.ForegroundColor = ConsoleColor.White;
  1253. 			Console.Error.Write( "F" );
  1254. 			Console.ResetColor( );
  1255. 			Console.Error.WriteLine( "ile (one list item per line)" );
  1256.  
  1257. 			Console.ForegroundColor = ConsoleColor.White;
  1258. 			Console.Error.Write( "         /H:height" );
  1259. 			Console.ResetColor( );
  1260. 			Console.Error.Write( "       sets the window " );
  1261. 			Console.ForegroundColor = ConsoleColor.White;
  1262. 			Console.Error.Write( "H" );
  1263. 			Console.ResetColor( );
  1264. 			Console.Error.WriteLine( "eight (default: autosized to make" );
  1265.  
  1266. 			Console.Error.WriteLine( "                         content fit; minimum: {0}; maximum: screen height - {1})", minimumwindowheight, borderY );
  1267.  
  1268. 			Console.ForegroundColor = ConsoleColor.White;
  1269. 			Console.Error.Write( "         /I:index" );
  1270. 			Console.ResetColor( );
  1271. 			Console.Error.Write( "        use " );
  1272. 			Console.ForegroundColor = ConsoleColor.White;
  1273. 			Console.Error.Write( "I" );
  1274. 			Console.ResetColor( );
  1275. 			Console.Error.Write( "con at " );
  1276. 			Console.ForegroundColor = ConsoleColor.White;
  1277. 			Console.Error.Write( "index" );
  1278. 			Console.ResetColor( );
  1279. 			Console.Error.WriteLine( " from shell32.dll (default: 23)" );
  1280.  
  1281. 			Console.ForegroundColor = ConsoleColor.White;
  1282. 			Console.Error.Write( "         /K" );
  1283. 			Console.ResetColor( );
  1284. 			Console.Error.Write( "              s" );
  1285. 			Console.ForegroundColor = ConsoleColor.White;
  1286. 			Console.Error.Write( "K" );
  1287. 			Console.ResetColor( );
  1288. 			Console.Error.WriteLine( "ip first item of list (e.g. a header line)" );
  1289.  
  1290. 			Console.ForegroundColor = ConsoleColor.White;
  1291. 			Console.Error.Write( "         /L[:\"captions\"] L" );
  1292. 			Console.ResetColor( );
  1293. 			Console.Error.Write( "ocalize or customize button " );
  1294. 			Console.ForegroundColor = ConsoleColor.White;
  1295. 			Console.Error.WriteLine( "captions" );
  1296. 			Console.ResetColor( );
  1297.  
  1298. 			Console.Error.Write( "                         (e.g. " );
  1299. 			Console.ForegroundColor = ConsoleColor.White;
  1300. 			Console.Error.Write( "/L:\"OK=Why Not?;Cancel=No Way!\"" );
  1301. 			Console.ResetColor( );
  1302. 			Console.Error.WriteLine( ")" );
  1303.  
  1304. 			Console.ForegroundColor = ConsoleColor.White;
  1305. 			Console.Error.Write( "         /MF" );
  1306. 			Console.ResetColor( );
  1307. 			Console.Error.Write( "             use " );
  1308. 			Console.ForegroundColor = ConsoleColor.White;
  1309. 			Console.Error.Write( "M" );
  1310. 			Console.ResetColor( );
  1311. 			Console.Error.Write( "onospaced " );
  1312. 			Console.ForegroundColor = ConsoleColor.White;
  1313. 			Console.Error.Write( "F" );
  1314. 			Console.ResetColor( );
  1315. 			Console.Error.Write( "ont in " );
  1316. 			Console.ForegroundColor = ConsoleColor.White;
  1317. 			Console.Error.Write( "prompt" );
  1318. 			Console.ResetColor( );
  1319. 			Console.Error.WriteLine( " (default: proportional)" );
  1320.  
  1321. 			Console.ForegroundColor = ConsoleColor.White;
  1322. 			Console.Error.Write( "         /NM" );
  1323. 			Console.ResetColor( );
  1324. 			Console.Error.Write( "             make dialog " );
  1325. 			Console.ForegroundColor = ConsoleColor.White;
  1326. 			Console.Error.Write( "N" );
  1327. 			Console.ResetColor( );
  1328. 			Console.Error.Write( "on-" );
  1329. 			Console.ForegroundColor = ConsoleColor.White;
  1330. 			Console.Error.Write( "M" );
  1331. 			Console.ResetColor( );
  1332. 			Console.Error.WriteLine( "odal (default: modal, i.e. on top)" );
  1333.  
  1334. 			Console.ForegroundColor = ConsoleColor.White;
  1335. 			Console.Error.Write( "         /P:index" );
  1336. 			Console.ResetColor( );
  1337. 			Console.Error.Write( "        sets the zero based " );
  1338. 			Console.ForegroundColor = ConsoleColor.White;
  1339. 			Console.Error.Write( "index" );
  1340. 			Console.ResetColor( );
  1341. 			Console.Error.Write( " of the " );
  1342. 			Console.ForegroundColor = ConsoleColor.White;
  1343. 			Console.Error.Write( "P" );
  1344. 			Console.ResetColor( );
  1345. 			Console.Error.WriteLine( "reselected item" );
  1346.  
  1347. 			Console.Error.WriteLine( "                         (default: 0)" );
  1348.  
  1349. 			Console.ForegroundColor = ConsoleColor.White;
  1350. 			Console.Error.Write( "         /R:rows" );
  1351. 			Console.ResetColor( );
  1352. 			Console.Error.Write( "         sets then number of " );
  1353. 			Console.ForegroundColor = ConsoleColor.White;
  1354. 			Console.Error.Write( "R" );
  1355. 			Console.ResetColor( );
  1356. 			Console.Error.WriteLine( "ows for the radio button group" );
  1357.  
  1358. 			Console.Error.WriteLine( "                         (default: one row per list item)" );
  1359.  
  1360. 			Console.ForegroundColor = ConsoleColor.White;
  1361. 			Console.Error.Write( "         /RC0            R" );
  1362. 			Console.ResetColor( );
  1363. 			Console.Error.Write( "eturn " );
  1364. 			Console.ForegroundColor = ConsoleColor.White;
  1365. 			Console.Error.Write( "C" );
  1366. 			Console.ResetColor( );
  1367. 			Console.Error.Write( "ode equals selected " );
  1368. 			Console.ForegroundColor = ConsoleColor.White;
  1369. 			Console.Error.Write( "0" );
  1370. 			Console.ResetColor( );
  1371. 			Console.Error.WriteLine( "-based index, or -1 on" );
  1372.  
  1373. 			Console.Error.WriteLine( "                         (command line) errors or if \"Cancel\" was clicked" );
  1374.  
  1375. 			Console.Error.WriteLine( "                         (default: 0 on \"OK\", 1 on error, 2 on \"Cancel\")" );
  1376.  
  1377. 			Console.ForegroundColor = ConsoleColor.White;
  1378. 			Console.Error.Write( "         /RC1            R" );
  1379. 			Console.ResetColor( );
  1380. 			Console.Error.Write( "eturn " );
  1381. 			Console.ForegroundColor = ConsoleColor.White;
  1382. 			Console.Error.Write( "C" );
  1383. 			Console.ResetColor( );
  1384. 			Console.Error.Write( "ode equals selected " );
  1385. 			Console.ForegroundColor = ConsoleColor.White;
  1386. 			Console.Error.Write( "1" );
  1387. 			Console.ResetColor( );
  1388. 			Console.Error.WriteLine( "-based index, or 0 on" );
  1389.  
  1390. 			Console.Error.WriteLine( "                         (command line) errors or if \"Cancel\" was clicked" );
  1391.  
  1392. 			Console.Error.WriteLine( "                         (default: 0 on \"OK\", 1 on error, 2 on \"Cancel\")" );
  1393.  
  1394. 			Console.ForegroundColor = ConsoleColor.White;
  1395. 			Console.Error.Write( "         /S              S" );
  1396. 			Console.ResetColor( );
  1397. 			Console.Error.WriteLine( "ort list (default: unsorted)" );
  1398.  
  1399. 			Console.ForegroundColor = ConsoleColor.White;
  1400. 			Console.Error.Write( "         /T:tablength" );
  1401. 			Console.ResetColor( );
  1402. 			Console.Error.Write( "    sets the number of spaces for " );
  1403. 			Console.ForegroundColor = ConsoleColor.White;
  1404. 			Console.Error.Write( "T" );
  1405. 			Console.ResetColor( );
  1406. 			Console.Error.Write( "abs in " );
  1407. 			Console.ForegroundColor = ConsoleColor.White;
  1408. 			Console.Error.WriteLine( "prompt" );
  1409. 			Console.ResetColor( );
  1410.  
  1411. 			Console.Error.WriteLine( "                         (4..16; default: 4)" );
  1412.  
  1413. 			Console.ForegroundColor = ConsoleColor.White;
  1414. 			Console.Error.Write( "         /W:width" );
  1415. 			Console.ResetColor( );
  1416. 			Console.Error.Write( "        sets the window " );
  1417. 			Console.ForegroundColor = ConsoleColor.White;
  1418. 			Console.Error.Write( "W" );
  1419. 			Console.ResetColor( );
  1420. 			Console.Error.WriteLine( "idth (default: autosized to make" );
  1421.  
  1422. 			Console.Error.WriteLine( "                         content fit; minimum: {0}; maximum: screen width - {1})", minimumwindowwidth, borderX );
  1423.  
  1424. 			Console.Error.WriteLine( );
  1425.  
  1426. 			Console.Error.WriteLine( "Notes:   The selected item text is written to Standard Out if \"OK\" is clicked," );
  1427.  
  1428. 			Console.Error.WriteLine( "         otherwise an empty string is returned." );
  1429.  
  1430. 			Console.Error.WriteLine( "         Most command line switches have one or more aliases, e.g. instead" );
  1431.  
  1432. 			Console.Error.Write( "         of " );
  1433. 			Console.ForegroundColor = ConsoleColor.White;
  1434. 			Console.Error.Write( "/R:5" );
  1435. 			Console.ResetColor( );
  1436. 			Console.Error.Write( " you can use " );
  1437. 			Console.ForegroundColor = ConsoleColor.White;
  1438. 			Console.Error.Write( "/ROWS:5" );
  1439. 			Console.ResetColor( );
  1440. 			Console.Error.WriteLine( " to make your batch files more readable;" );
  1441.  
  1442. 			Console.Error.Write( "         use command line switch " );
  1443. 			Console.ForegroundColor = ConsoleColor.White;
  1444. 			Console.Error.Write( "/A" );
  1445. 			Console.ResetColor( );
  1446. 			Console.Error.WriteLine( " to get a list of all available aliases." );
  1447.  
  1448. 			Console.Error.Write( "         Use either " );
  1449. 			Console.ForegroundColor = ConsoleColor.White;
  1450. 			Console.Error.Write( "\"list\"" );
  1451. 			Console.ResetColor( );
  1452. 			Console.Error.Write( " or " );
  1453. 			Console.ForegroundColor = ConsoleColor.White;
  1454. 			Console.Error.Write( "/F:\"listfile\"" );
  1455. 			Console.ResetColor( );
  1456. 			Console.Error.Write( " or " );
  1457. 			Console.ForegroundColor = ConsoleColor.White;
  1458. 			Console.Error.Write( "\"listcommand\"" );
  1459. 			Console.ResetColor( );
  1460. 			Console.Error.WriteLine( "." );
  1461.  
  1462. 			Console.Error.WriteLine( "         Linefeeds (\\n), tabs (\\t) and doublequotes (\\\") are allowed in the" );
  1463.  
  1464. 			Console.ForegroundColor = ConsoleColor.White;
  1465. 			Console.Error.Write( "         prompt" );
  1466. 			Console.ResetColor( );
  1467. 			Console.Error.Write( " text (but not in the " );
  1468. 			Console.ForegroundColor = ConsoleColor.White;
  1469. 			Console.Error.Write( "title" );
  1470. 			Console.ResetColor( );
  1471. 			Console.Error.Write( "); with tabs, " );
  1472. 			Console.ForegroundColor = ConsoleColor.White;
  1473. 			Console.Error.Write( "/MF" );
  1474. 			Console.ResetColor( );
  1475. 			Console.Error.WriteLine( " is recommended." );
  1476.  
  1477. 			Console.Error.WriteLine( "         To get a custom title without prompt, use \" \" for prompt." );
  1478.  
  1479. 			Console.Error.Write( "         If specified without " );
  1480. 			Console.ForegroundColor = ConsoleColor.White;
  1481. 			Console.Error.Write( "captions" );
  1482. 			Console.ResetColor( );
  1483. 			Console.Error.Write( ", switch " );
  1484. 			Console.ForegroundColor = ConsoleColor.White;
  1485. 			Console.Error.Write( "/L" );
  1486. 			Console.ResetColor( );
  1487. 			Console.Error.WriteLine( " forces localized button" );
  1488.  
  1489. 			Console.Error.WriteLine( "         captions (e.g. \"Cancel\" button caption is \"Annuleren\" on Dutch" );
  1490.  
  1491. 			Console.Error.WriteLine( "         systems); if only a single custom caption is specified, the other" );
  1492.  
  1493. 			Console.Error.Write( "         one is localized (e.g. with " );
  1494. 			Console.ForegroundColor = ConsoleColor.White;
  1495. 			Console.Error.Write( "/L:\"OK=Gaan\"" );
  1496. 			Console.ResetColor( );
  1497. 			Console.Error.WriteLine( " on Dutch systems, \"OK\"" );
  1498.  
  1499. 			Console.Error.WriteLine( "         button caption is \"Gaan\", \"Cancel\" button caption is \"Annuleren\")." );
  1500.  
  1501. 			Console.Error.WriteLine( "         Return code 0 for \"OK\", 1 for (command line) errors, 2 for \"Cancel\"." );
  1502.  
  1503. 			Console.Error.Write( "         With " );
  1504. 			Console.ForegroundColor = ConsoleColor.White;
  1505. 			Console.Error.Write( "/RC0" );
  1506. 			Console.ResetColor( );
  1507. 			Console.Error.WriteLine( " return code equals selected index, or -1 for \"Cancel\"." );
  1508.  
  1509. 			Console.Error.Write( "         With " );
  1510. 			Console.ForegroundColor = ConsoleColor.White;
  1511. 			Console.Error.Write( "/RC1" );
  1512. 			Console.ResetColor( );
  1513. 			Console.Error.WriteLine( " return code equals selected index + 1, or 0 for \"Cancel\"." );
  1514.  
  1515. 			Console.Error.Write( "         Command line switches " );
  1516. 			Console.ForegroundColor = ConsoleColor.White;
  1517. 			Console.Error.Write( "/RC0" );
  1518. 			Console.ResetColor( );
  1519. 			Console.Error.Write( " and " );
  1520. 			Console.ForegroundColor = ConsoleColor.White;
  1521. 			Console.Error.Write( "/RC1" );
  1522. 			Console.ResetColor( );
  1523. 			Console.Error.WriteLine( " are mutually exclusive." );
  1524.  
  1525. 			Console.Error.WriteLine( );
  1526.  
  1527. 			Console.Error.WriteLine( "Credits: On-the-fly form based on code by Gorkem Gencay on StackOverflow:" );
  1528.  
  1529. 			Console.ForegroundColor = ConsoleColor.DarkGray;
  1530. 			Console.Error.WriteLine( "         http://stackoverflow.com/questions/17546909" );
  1531. 			Console.ResetColor( );
  1532.  
  1533. 			Console.Error.WriteLine( "         Code to retrieve localized button captions by Martin Stoeckli:" );
  1534.  
  1535. 			Console.ForegroundColor = ConsoleColor.DarkGray;
  1536. 			Console.Error.WriteLine( "         http://martinstoeckli.ch/csharp/csharp.html#windows_text_resources" );
  1537. 			Console.ResetColor( );
  1538.  
  1539. 			Console.Error.WriteLine( "         Code to extract icons from Shell32.dll by Thomas Levesque:" );
  1540.  
  1541. 			Console.ForegroundColor = ConsoleColor.DarkGray;
  1542. 			Console.Error.WriteLine( "         http://stackoverflow.com/questions/6873026" );
  1543. 			Console.ResetColor( );
  1544.  
  1545. 			Console.Error.WriteLine( );
  1546.  
  1547. 			Console.Error.WriteLine( "Written by Rob van der Woude" );
  1548.  
  1549. 			Console.Error.WriteLine( "http://www.robvanderwoude.com" );
  1550.  
  1551. 			#endregion Show Help Text
  1552.  
  1553.  
  1554. 			// Default: return code 0 for "OK", 1 for (command line) errors, 2 for "Cancel".
  1555. 			int rc = 0;
  1556. 			if ( returnindex0 )
  1557. 			{
  1558. 				// With /R0: return code equals selected index for "OK", or -1 for (command line) errors or "Cancel".
  1559. 				rc = -1;
  1560. 			}
  1561. 			else if ( returnindex1 )
  1562. 			{
  1563. 				// With /R1: return code equals selected index + 1 for "OK", or 0 for (command line) errors or "Cancel".
  1564. 				rc = 0;
  1565. 			}
  1566.  
  1567. 			return rc;
  1568. 		}
  1569.  
  1570. 		#endregion Error handling
  1571.  
  1572.  
  1573. 		#region Get Window Border Dimensions
  1574.  
  1575. 		public static int[] BorderDimensions( )
  1576. 		{
  1577. 			Form testform = new Form( );
  1578. 			Size testsize = new Size( 300, 200 );
  1579. 			testform.Size = testsize;
  1580. 			int deltaX = testform.Size.Width - testform.ClientSize.Width + Screen.PrimaryScreen.Bounds.Width - Screen.PrimaryScreen.WorkingArea.Width;
  1581. 			int deltaY = testform.Size.Height - testform.ClientSize.Height + Screen.PrimaryScreen.Bounds.Height - Screen.PrimaryScreen.WorkingArea.Height;
  1582. 			testform.Dispose( );
  1583. 			return new int[] { deltaX, deltaY };
  1584. 		}
  1585.  
  1586. 		#endregion Get Window Border Dimensions
  1587.  
  1588.  
  1589. 		#region Get Localized Captions
  1590.  
  1591. 		// Code to retrieve localized captions by Martin Stoeckli
  1592. 		// http://martinstoeckli.ch/csharp/csharp.html#windows_text_resources
  1593.  
  1594. 		/// <summary>
  1595. 		/// Searches for a text resource in a Windows library.
  1596. 		/// Sometimes, using the existing Windows resources, you can make your code
  1597. 		/// language independent and you don't have to care about translation problems.
  1598. 		/// </summary>
  1599. 		/// <example>
  1600. 		///   btnCancel.Text = Load("user32.dll", 801, "Cancel");
  1601. 		///   btnYes.Text = Load("user32.dll", 805, "Yes");
  1602. 		/// </example>
  1603. 		/// <param name="libraryName">Name of the windows library like "user32.dll"
  1604. 		/// or "shell32.dll"</param>
  1605. 		/// <param name="ident">Id of the string resource.</param>
  1606. 		/// <param name="defaultText">Return this text, if the resource string could
  1607. 		/// not be found.</param>
  1608. 		/// <returns>Requested string if the resource was found,
  1609. 		/// otherwise the <paramref name="defaultText"/></returns>
  1610. 		public static string Load( string libraryName, UInt32 ident, string defaultText )
  1611. 		{
  1612. 			IntPtr libraryHandle = GetModuleHandle( libraryName );
  1613. 			if ( libraryHandle != IntPtr.Zero )
  1614. 			{
  1615. 				StringBuilder sb = new StringBuilder( 1024 );
  1616. 				int size = LoadString( libraryHandle, ident, sb, 1024 );
  1617. 				if ( size > 0 )
  1618. 					return sb.ToString( );
  1619. 			}
  1620. 			return defaultText;
  1621. 		}
  1622.  
  1623. 		[DllImport( "kernel32.dll", CharSet = CharSet.Auto )]
  1624. 		private static extern IntPtr GetModuleHandle( string lpModuleName );
  1625.  
  1626. 		[DllImport( "user32.dll", CharSet = CharSet.Auto )]
  1627. 		private static extern int LoadString( IntPtr hInstance, UInt32 uID, StringBuilder lpBuffer, Int32 nBufferMax );
  1628.  
  1629. 		#endregion Get Localized Captions
  1630.  
  1631.  
  1632. 		#region Extract Icons
  1633.  
  1634. 		// Code to extract icons from Shell32.dll by Thomas Levesque
  1635. 		// http://stackoverflow.com/questions/6873026
  1636.  
  1637. 		public class IconExtractor
  1638. 		{
  1639.  
  1640. 			public static Icon Extract( string file, int number, bool largeIcon )
  1641. 			{
  1642. 				IntPtr large;
  1643. 				IntPtr small;
  1644. 				ExtractIconEx( file, number, out large, out small, 1 );
  1645. 				try
  1646. 				{
  1647. 					return Icon.FromHandle( largeIcon ? large : small );
  1648. 				}
  1649. 				catch
  1650. 				{
  1651. 					return null;
  1652. 				}
  1653.  
  1654. 			}
  1655.  
  1656. 			[DllImport( "Shell32.dll", EntryPoint = "ExtractIconExW", CharSet = CharSet.Unicode, ExactSpelling = true, CallingConvention = CallingConvention.StdCall )]
  1657. 			private static extern int ExtractIconEx( string sFile, int iIndex, out IntPtr piLargeVersion, out IntPtr piSmallVersion, int amountIcons );
  1658. 		}
  1659.  
  1660. 		#endregion Extract Icons
  1661. 	}
  1662. }
  1663.  

page last modified: 2024-02-26; loaded in 0.0770 seconds