1 // 2 // SimpleEffectDialog.cs 3 // 4 // Author: 5 // Jonathan Pobst <monkey@jpobst.com> 6 // 7 // Copyright (c) 2010 Jonathan Pobst 8 // 9 // Permission is hereby granted, free of charge, to any person obtaining a copy 10 // of this software and associated documentation files (the "Software"), to deal 11 // in the Software without restriction, including without limitation the rights 12 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 13 // copies of the Software, and to permit persons to whom the Software is 14 // furnished to do so, subject to the following conditions: 15 // 16 // The above copyright notice and this permission notice shall be included in 17 // all copies or substantial portions of the Software. 18 // 19 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 20 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 21 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 22 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 23 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 24 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 25 // THE SOFTWARE. 26 // 27 // Inspiration and reflection code is from Miguel de Icaza's MIT Licensed MonoTouch.Dialog: 28 // http://github.com/migueldeicaza/MonoTouch.Dialog 29 30 using System; 31 using System.Reflection; 32 using System.Text; 33 using System.Collections.Generic; 34 using System.ComponentModel; 35 using System.Collections; 36 using Mono.Addins.Localization; 37 using Mono.Unix; 38 39 namespace Pinta.Gui.Widgets 40 { 41 public class SimpleEffectDialog : Gtk.Dialog 42 { 43 [ThreadStatic] 44 Random random = new Random (); 45 46 const uint event_delay_millis = 100; 47 uint event_delay_timeout_id; 48 GLib.TimeoutHandler timeout_func; 49 50 /// Since this dialog is used by add-ins, the IAddinLocalizer allows for translations to be 51 /// fetched from the appropriate place. 52 /// </param> SimpleEffectDialog(string title, Gdk.Pixbuf icon, object effectData, IAddinLocalizer localizer)53 public SimpleEffectDialog (string title, Gdk.Pixbuf icon, object effectData, 54 IAddinLocalizer localizer) 55 : base (title, Pinta.Core.PintaCore.Chrome.MainWindow, Gtk.DialogFlags.Modal, 56 Gtk.Stock.Cancel, Gtk.ResponseType.Cancel, Gtk.Stock.Ok, Gtk.ResponseType.Ok) 57 { 58 Icon = icon; 59 EffectData = effectData; 60 61 BorderWidth = 6; 62 VBox.Spacing = 12; 63 WidthRequest = 400; 64 Resizable = false; 65 DefaultResponse = Gtk.ResponseType.Ok; 66 AlternativeButtonOrder = new int[] { (int)Gtk.ResponseType.Ok, (int)Gtk.ResponseType.Cancel }; 67 68 BuildDialog (localizer); 69 } 70 71 public object EffectData { get; private set; } 72 73 public event PropertyChangedEventHandler EffectDataChanged; 74 Destroy()75 public override void Destroy () 76 { 77 // If there is a timeout that hasn't been invoked yet, run it before closing the dialog. 78 if (event_delay_timeout_id != 0) 79 { 80 GLib.Source.Remove (event_delay_timeout_id); 81 timeout_func.Invoke (); 82 } 83 84 base.Destroy (); 85 } 86 87 #region EffectData Parser BuildDialog(IAddinLocalizer localizer)88 private void BuildDialog (IAddinLocalizer localizer) 89 { 90 var members = EffectData.GetType ().GetMembers (); 91 92 foreach (var mi in members) { 93 Type mType = GetTypeForMember (mi); 94 95 if (mType == null) 96 continue; 97 98 string caption = null; 99 string hint = null; 100 bool skip = false; 101 bool combo = false; 102 103 object[] attrs = mi.GetCustomAttributes (false); 104 105 foreach (var attr in attrs) { 106 if (attr is SkipAttribute) 107 skip = true; 108 else if (attr is CaptionAttribute) 109 caption = ((CaptionAttribute)attr).Caption; 110 else if (attr is HintAttribute) 111 hint = ((HintAttribute)attr).Hint; 112 else if (attr is StaticListAttribute) 113 combo = true; 114 115 } 116 117 if (skip || string.Compare (mi.Name, "IsDefault", true) == 0) 118 continue; 119 120 if (caption == null) 121 caption = MakeCaption (mi.Name); 122 123 if (mType == typeof (int) && (caption == "Seed")) 124 AddWidget (CreateSeed (localizer.GetString (caption), EffectData, mi, attrs)); 125 else if (mType == typeof (int)) 126 AddWidget (CreateSlider (localizer.GetString (caption), EffectData, mi, attrs)); 127 else if (mType == typeof (double) && (caption == "Angle" || caption == "Rotation")) 128 AddWidget (CreateAnglePicker (localizer.GetString (caption), EffectData, mi, attrs)); 129 else if (mType == typeof (double)) 130 AddWidget (CreateDoubleSlider (localizer.GetString (caption), EffectData, mi, attrs)); 131 else if (combo && mType == typeof (string)) 132 AddWidget (CreateComboBox (localizer.GetString (caption), EffectData, mi, attrs)); 133 else if (mType == typeof (bool)) 134 AddWidget (CreateCheckBox (localizer.GetString (caption), EffectData, mi, attrs)); 135 else if (mType == typeof (Gdk.Point)) 136 AddWidget (CreatePointPicker (localizer.GetString (caption), EffectData, mi, attrs)); 137 else if (mType == typeof (Cairo.PointD)) 138 AddWidget (CreateOffsetPicker (localizer.GetString (caption), EffectData, mi, attrs)); 139 else if (mType.IsEnum) 140 AddWidget (CreateEnumComboBox (localizer.GetString (caption), EffectData, mi, attrs)); 141 142 if (hint != null) 143 AddWidget (CreateHintLabel (localizer.GetString (hint))); 144 } 145 } 146 AddWidget(Gtk.Widget widget)147 private void AddWidget (Gtk.Widget widget) 148 { 149 widget.Show (); 150 this.VBox.Add (widget); 151 } 152 #endregion 153 154 #region Control Builders CreateEnumComboBox(string caption, object o, System.Reflection.MemberInfo member, System.Object[] attributes)155 private ComboBoxWidget CreateEnumComboBox (string caption, object o, System.Reflection.MemberInfo member, System.Object[] attributes) 156 { 157 Type myType = GetTypeForMember (member); 158 159 string[] member_names = Enum.GetNames (myType); 160 var labels = new List<string> (); 161 var label_to_member = new Dictionary<string, string> (); 162 163 foreach (var member_name in member_names) 164 { 165 var members = myType.GetMember (member_name); 166 167 // Look for a Caption attribute that provides a (translated) description. 168 string label; 169 var attrs = members [0].GetCustomAttributes (typeof (CaptionAttribute), false); 170 if (attrs.Length > 0) 171 label = Catalog.GetString (((CaptionAttribute)attrs [0]).Caption); 172 else 173 label = Catalog.GetString (member_name); 174 175 label_to_member [label] = member_name; 176 labels.Add (label); 177 } 178 179 ComboBoxWidget widget = new ComboBoxWidget (labels.ToArray ()); 180 181 widget.Label = caption; 182 widget.AddEvents ((int)Gdk.EventMask.ButtonPressMask); 183 widget.Active = ((IList)member_names).IndexOf (GetValue (member, o).ToString ()); 184 185 widget.Changed += delegate (object sender, EventArgs e) { 186 SetValue (member, o, Enum.Parse (myType, label_to_member [widget.ActiveText])); 187 }; 188 189 return widget; 190 } 191 CreateComboBox(string caption, object o, System.Reflection.MemberInfo member, System.Object [] attributes)192 private ComboBoxWidget CreateComboBox (string caption, object o, System.Reflection.MemberInfo member, System.Object [] attributes) 193 { 194 Dictionary<string, object> dict = null; 195 196 foreach (var attr in attributes) { 197 if (attr is StaticListAttribute) 198 dict = (Dictionary<string, object>)GetValue (((StaticListAttribute)attr).dictionaryName, o); 199 } 200 201 List<string> entries = new List<string> (); 202 foreach (string str in dict.Keys) 203 entries.Add (str); 204 205 ComboBoxWidget widget = new ComboBoxWidget (entries.ToArray ()); 206 207 widget.Label = caption; 208 widget.AddEvents ((int)Gdk.EventMask.ButtonPressMask); 209 widget.Active = entries.IndexOf ((string)GetValue (member, o)); 210 211 widget.Changed += delegate (object sender, EventArgs e) { 212 SetValue (member, o, widget.ActiveText); 213 }; 214 215 return widget; 216 } 217 CreateDoubleSlider(string caption, object o, MemberInfo member, object [] attributes)218 private HScaleSpinButtonWidget CreateDoubleSlider (string caption, object o, MemberInfo member, object [] attributes) 219 { 220 HScaleSpinButtonWidget widget = new HScaleSpinButtonWidget (); 221 222 int min_value = -100; 223 int max_value = 100; 224 double inc_value = 0.01; 225 int digits_value = 2; 226 227 foreach (var attr in attributes) { 228 if (attr is MinimumValueAttribute) 229 min_value = ((MinimumValueAttribute)attr).Value; 230 else if (attr is MaximumValueAttribute) 231 max_value = ((MaximumValueAttribute)attr).Value; 232 else if (attr is IncrementValueAttribute) 233 inc_value = ((IncrementValueAttribute)attr).Value; 234 else if (attr is DigitsValueAttribute) 235 digits_value = ((DigitsValueAttribute)attr).Value; 236 } 237 238 widget.Label = caption; 239 widget.MinimumValue = min_value; 240 widget.MaximumValue = max_value; 241 widget.IncrementValue = inc_value; 242 widget.DigitsValue = digits_value; 243 widget.DefaultValue = (double)GetValue (member, o); 244 245 widget.ValueChanged += delegate (object sender, EventArgs e) { 246 DelayedUpdate( () => { 247 SetValue (member, o, widget.Value); 248 return false; 249 }); 250 }; 251 252 return widget; 253 } 254 CreateSlider(string caption, object o, MemberInfo member, object [] attributes)255 private HScaleSpinButtonWidget CreateSlider (string caption, object o, MemberInfo member, object [] attributes) 256 { 257 HScaleSpinButtonWidget widget = new HScaleSpinButtonWidget (); 258 259 int min_value = -100; 260 int max_value = 100; 261 double inc_value = 1.0; 262 int digits_value = 0; 263 264 foreach (var attr in attributes) { 265 if (attr is MinimumValueAttribute) 266 min_value = ((MinimumValueAttribute)attr).Value; 267 else if (attr is MaximumValueAttribute) 268 max_value = ((MaximumValueAttribute)attr).Value; 269 else if (attr is IncrementValueAttribute) 270 inc_value = ((IncrementValueAttribute)attr).Value; 271 else if (attr is DigitsValueAttribute) 272 digits_value = ((DigitsValueAttribute)attr).Value; 273 } 274 275 widget.Label = caption; 276 widget.MinimumValue = min_value; 277 widget.MaximumValue = max_value; 278 widget.IncrementValue = inc_value; 279 widget.DigitsValue = digits_value; 280 widget.DefaultValue = (int)GetValue (member, o); 281 282 widget.ValueChanged += delegate (object sender, EventArgs e) { 283 DelayedUpdate( () => { 284 SetValue (member, o, widget.ValueAsInt); 285 return false; 286 }); 287 }; 288 289 return widget; 290 } 291 CreateCheckBox(string caption, object o, MemberInfo member, object [] attributes)292 private Gtk.CheckButton CreateCheckBox (string caption, object o, MemberInfo member, object [] attributes) 293 { 294 Gtk.CheckButton widget = new Gtk.CheckButton (); 295 296 widget.Label = caption; 297 widget.Active = (bool)GetValue (member, o); 298 299 widget.Toggled += delegate (object sender, EventArgs e) { 300 SetValue (member, o, widget.Active); 301 }; 302 303 return widget; 304 } 305 CreateOffsetPicker(string caption, object o, MemberInfo member, object [] attributes)306 private PointPickerWidget CreateOffsetPicker (string caption, object o, MemberInfo member, object [] attributes) 307 { 308 PointPickerWidget widget = new PointPickerWidget (); 309 310 widget.Label = caption; 311 widget.DefaultOffset = (Cairo.PointD)GetValue (member, o); 312 313 widget.PointPicked += delegate (object sender, EventArgs e) { 314 SetValue (member, o, widget.Offset); 315 }; 316 317 return widget; 318 } 319 CreatePointPicker(string caption, object o, MemberInfo member, object [] attributes)320 private PointPickerWidget CreatePointPicker (string caption, object o, MemberInfo member, object [] attributes) 321 { 322 PointPickerWidget widget = new PointPickerWidget (); 323 324 widget.Label = caption; 325 widget.DefaultPoint = (Gdk.Point)GetValue (member, o); 326 327 widget.PointPicked += delegate (object sender, EventArgs e) { 328 SetValue (member, o, widget.Point); 329 }; 330 331 return widget; 332 } 333 CreateAnglePicker(string caption, object o, MemberInfo member, object [] attributes)334 private AnglePickerWidget CreateAnglePicker (string caption, object o, MemberInfo member, object [] attributes) 335 { 336 AnglePickerWidget widget = new AnglePickerWidget (); 337 338 widget.Label = caption; 339 widget.DefaultValue = (double)GetValue (member, o); 340 341 widget.ValueChanged += delegate (object sender, EventArgs e) { 342 DelayedUpdate( () => { 343 SetValue (member, o, widget.Value); 344 return false; 345 }); 346 }; 347 348 return widget; 349 } 350 CreateHintLabel(string hint)351 private Gtk.Label CreateHintLabel (string hint) 352 { 353 Gtk.Label label = new Gtk.Label (hint); 354 label.LineWrap = true; 355 356 return label; 357 } 358 CreateSeed(string caption, object o, MemberInfo member, object [] attributes)359 private ReseedButtonWidget CreateSeed (string caption, object o, MemberInfo member, object [] attributes) 360 { 361 ReseedButtonWidget widget = new ReseedButtonWidget (); 362 363 widget.Clicked += delegate (object sender, EventArgs e) { 364 SetValue (member, o, random.Next ()); 365 }; 366 367 return widget; 368 } 369 #endregion 370 371 #region Static Reflection Methods GetValue(MemberInfo mi, object o)372 private static object GetValue (MemberInfo mi, object o) 373 { 374 var fi = mi as FieldInfo; 375 if (fi != null) 376 return fi.GetValue (o); 377 var pi = mi as PropertyInfo; 378 379 var getMethod = pi.GetGetMethod (); 380 return getMethod.Invoke (o, new object [0]); 381 } 382 SetValue(MemberInfo mi, object o, object val)383 private void SetValue (MemberInfo mi, object o, object val) 384 { 385 var fi = mi as FieldInfo; 386 var pi = mi as PropertyInfo; 387 string fieldName = null; 388 389 if (fi != null) { 390 fi.SetValue (o, val); 391 fieldName = fi.Name; 392 } else if (pi != null) { 393 var setMethod = pi.GetSetMethod (); 394 setMethod.Invoke (o, new object [] { val }); 395 fieldName = pi.Name; 396 } 397 398 if (EffectDataChanged != null) 399 EffectDataChanged (this, new PropertyChangedEventArgs (fieldName)); 400 } 401 402 // Returns the type for fields and properties and null for everything else GetTypeForMember(MemberInfo mi)403 private static Type GetTypeForMember (MemberInfo mi) 404 { 405 if (mi is FieldInfo) 406 return ((FieldInfo)mi).FieldType; 407 else if (mi is PropertyInfo) 408 return ((PropertyInfo)mi).PropertyType; 409 410 return null; 411 } 412 MakeCaption(string name)413 private static string MakeCaption (string name) 414 { 415 var sb = new StringBuilder (name.Length); 416 bool nextUp = true; 417 418 foreach (char c in name) { 419 if (nextUp) { 420 sb.Append (Char.ToUpper (c)); 421 nextUp = false; 422 } else { 423 if (c == '_') { 424 sb.Append (' '); 425 nextUp = true; 426 continue; 427 } 428 if (Char.IsUpper (c)) 429 sb.Append (' '); 430 sb.Append (c); 431 } 432 } 433 434 return sb.ToString (); 435 } 436 GetValue(string name, object o)437 private object GetValue (string name, object o) 438 { 439 var fi = o.GetType ().GetField (name); 440 if (fi != null) 441 return fi.GetValue (o); 442 var pi = o.GetType ().GetProperty (name); 443 if (pi == null) 444 return null; 445 var getMethod = pi.GetGetMethod (); 446 return getMethod.Invoke (o, new object [0]); 447 } 448 DelayedUpdate(GLib.TimeoutHandler handler)449 private void DelayedUpdate (GLib.TimeoutHandler handler) 450 { 451 if (event_delay_timeout_id != 0) 452 { 453 GLib.Source.Remove (event_delay_timeout_id); 454 if (handler != timeout_func) 455 timeout_func.Invoke (); 456 } 457 458 timeout_func = handler; 459 event_delay_timeout_id = GLib.Timeout.Add (event_delay_millis, () => { 460 event_delay_timeout_id = 0; 461 timeout_func.Invoke (); 462 timeout_func = null; 463 return false; 464 }); 465 } 466 #endregion 467 } 468 469 /// <summary> 470 /// Wrapper around Pinta's translation template. 471 /// </summary> 472 public class PintaLocalizer : IAddinLocalizer 473 { GetString(string msgid)474 public string GetString (string msgid) 475 { 476 return Mono.Unix.Catalog.GetString (msgid); 477 } 478 }; 479 } 480