# Copyright (c) 2023 M.J.N. Corino, The Netherlands # # This software is released under the MIT license. ### # PropertyGrid sample custom properties ### class WxFontDataProperty < Wx::PG::FontProperty def initialize(label = Wx::PG::PG_LABEL, name = Wx::PG::PG_LABEL, value = Wx::FontData.new) super(label,name,value.get_initial_font) font = self.value_.font value.set_chosen_font(font) value.colour = Wx::BLACK unless value.colour.ok? # Set initial value - should be done in a simpler way like this # (instead of calling SetValue) in derived (wxObject) properties. @value_wxFontData = Wx::Variant.new(value) # Add extra children. add_private_child(Wx::PG::ColourProperty.new("Colour", Wx::PG::PG_LABEL, value.colour)) end def do_get_editor_class Wx::PG::PG_EDITOR_TEXT_CTRL_AND_BUTTON end def on_set_value if self.value_.object?(Wx::FontData) # Set m_value to wxFont so that wxFontProperty methods will work # correctly. @value_wxFontData = self.value_ fontData = @value_wxFontData.object font = fontData.get_chosen_font font = Wx::FontInfo(10).family(Wx::FONTFAMILY_SWISS) unless font.ok? self.value_ = font else if self.value_.font? font = self.value_.font fontData = Wx::FontData.new fontData.set_chosen_font(font) unless @value_wxFontData.null? oldFontData = @value_wxFontData.object fontData.colour = oldFontData.colour else fontData.colour = Wx::BLACK end @value_wxFontData << fontData else raise "Value to wxFontDataProperty must be either wxFontData or wxFont" end end end # In order to have different value type in a derived property # class, we will override GetValue to return custom variant, # instead of changing the base m_value. This allows the methods # in base class to function properly. def do_get_value @value_wxFontData ||= Wx::Variant.new end def child_changed(thisValue, childIndex, childValue) fontData = thisValue.object fontData.initial_font = fontData.chosen_font case childIndex when 6 col = childValue.colour fontData.colour = col else # Transfer from subset to superset. font = fontData.chosen_font variant = Wx::Variant.new(font) variant = super(variant, childIndex, childValue) font = variant.font fontData.chosen_font = font end Wx::Variant.new(fontData) end def refresh_children super return if get_child_count < 6 # Number is count of wxFontProperty's children + 1. fontData = @value_wxFontData.object item(6).value = fontData.colour end protected def display_editor_dialog(pg, value) raise "Function called for incompatible property" unless value.object?(Wx::FontData) fontData = value.object fontData.initial_font = fontData.chosen_font Wx.FontDialog(pg.panel, fontData) do |dlg| dlg_title = self.get_attribute(Wx::PG::PG_DIALOG_TITLE).string dlg.title = dlg_title unless dlg_title.empty? if dlg.show_modal == Wx::ID_OK fontData = dlg.get_font_data value << fontData true else false end end end end # FontDataProperty class WxSizeProperty < Wx::PG::PGProperty def initialize(label = Wx::PG::PG_LABEL, name = Wx::PG::PG_LABEL, value = Wx::Size.new) super(label, name) set_value_i(value) add_private_child( Wx::PG::IntProperty.new("Width", Wx::PG::PG_LABEL,value.width)) add_private_child( Wx::PG::IntProperty.new("Height", Wx::PG::PG_LABEL,value.height)) end def child_changed(thisValue, childIndex, childValue) size = thisValue.object val = childValue.long case childIndex when 0 size.width = val when 1 size.height = val end Wx::Variant.new(size) end def refresh_children return unless child_count>0 size = self.value.object item(0).value = size.width item(1).value = size.height end protected # I stands for internal def set_value_i(value) self.value = value end end class WxPointProperty < Wx::PG::PGProperty def initialize(label = Wx::PG::PG_LABEL, name = Wx::PG::PG_LABEL, value = Wx::Point.new) super(label, name) set_value_i(value); add_private_child( Wx::PG::IntProperty.new("X", Wx::PG::PG_LABEL,value.x)) add_private_child( Wx::PG::IntProperty.new("Y", Wx::PG::PG_LABEL,value.y)) end def child_changed(thisValue, childIndex, childValue) point = thisValue.object val = childValue.long case childIndex when 0 point.x = val when 1 point.y = val end Wx::Variant.new(point) end def refresh_children return unless child_count>0 point = self.value.object item(0).value = point.x item(1).value = point.y end protected # I stands for internal def set_value_i(value) self.value = value end end class WxDirsProperty < Wx::PG::ArrayStringProperty def initialize(label = Wx::PG::PG_LABEL, name = Wx::PG::PG_LABEL, value = []) super self.set_attribute(Wx::PG::PG_ARRAY_DELIMITER, ',') self.custom_btn_text_ = 'Browse' end def do_get_editor_class Wx::PG::PG_EDITOR_TEXT_CTRL_AND_BUTTON end def do_get_validator if Wx.has_feature?(:USE_VALIDATORS) Wx::PG::FileProperty.get_class_validator else nil end end def on_custom_string_edit(parent) Wx.DirDialog(parent, "Select a directory to be added to the list:", '', 0) do |dlg| if dlg.show_modal == Wx::ID_OK dlg.path else nil end end end end class WxArrayDoubleProperty < Wx::PG::EditorDialogProperty class EditorDialog < Wx::PG::PGArrayEditorDialog def initialize super init end def init @precision = -1 end def create(parent, message, caption, style = Wx::PG::AEDIALOG_STYLE, pos = Wx::DEFAULT_POSITION, sz = Wx::DEFAULT_SIZE) super(parent,message,caption,style,pos,sz) end attr_reader :array # Extra method for this type of array attr_accessor :precision def set_dialog_value(value) @array = value.object end def get_dialog_value Wx::Variant.new(@array) end protected # @param index [Integer] # @return [String] def array_get(index) "%#{@precision < 0 ? '' : ".#{@precision}"}f" % @array[index] end # @return [Integer] def array_get_count @array.size end # @param str [String] # @param index [Integer] # @return [true,false] def array_insert(str, index) if (v = Float(str) rescue nil) if index<0 @array << v else @array.insert(index, v) end end !!v end # @param index [Integer] # @param str [String] # @return [true,false] def array_set(index, str) if (v = Float(str) rescue nil) @array[index] = v end !!v end # @param index [Integer] # @return [void] def array_remove_at(index) @array.delete_at(index) end # @param first [Integer] # @param second [Integer] # @return [void] def array_swap(first, second) old_first = @array[first] @array[first] = @array[second] @array[second] = old_first end end class << self def validator @validator end def validator=(obj) @validator = obj end end def initialize(label = Wx::PG::PG_LABEL, name = Wx::PG::PG_LABEL, value = []) super(label, name) @precision = -1 self.dlg_style_ = Wx::PG::AEDIALOG_STYLE @delimiter = ';' @display = '' self.value = value end def generate_value_as_string(prec, removeZeroes) fmt = "%#{prec < 0 ? '' : ".#{prec}"}f" self.value.object.collect do |dbl| s = fmt % dbl if removeZeroes && prec != 0 s.sub!(/(\d+\.[^0]*)0+\Z/, '\1') s.chomp!('.') end s end.join("#{@delimiter} ") end protected :generate_value_as_string def on_set_value @display = generate_value_as_string(@precision, true) end def do_get_editor_class Wx::PG::PG_EDITOR_TEXT_CTRL_AND_BUTTON end protected def display_editor_dialog(pg, value) unless value.object?(::Array) && value.object.all? { |e| ::Float === e } Wx.log_debug 'display_editor_dialog : Function called for incompatible property' return false end WxArrayDoubleProperty.EditorDialog() do |dlg| dlg.set_dialog_value(value) dlg.precision = @precision dlg.create(pg.panel, '', self.dlg_title_.empty? ? self.label : self.dlg_title_, self.dlg_style_) dlg.move(pg.get_good_editor_dialog_position(self, dlg.size)) # Execute editor dialog res = dlg.show_modal if res == Wx::ID_OK && dlg.modified? value << dlg.get_dialog_value true else false end end end def value_to_string(value, argFlags=0) if (argFlags & Wx::PG::PG_FULL_VALUE) != 0 generate_value_as_string(-1,false) elsif value.object == self.value.object @display # Display cached string only if value truly matches m_value else generate_value_as_string(@precision,true) end end def string_to_value(variant, text, argFlags=0) new_array = text.split(@delimiter).collect do |s| begin Float(s) rescue return false end end if self.value.object != new_array variant << new_array true else false end end def do_set_attribute(name, value) if name == Wx::PG::PG_FLOAT_PRECISION @precision = value.long @display = generate_value_as_string(@precision, true) true else super end end def do_get_validator if Wx.has_feature?(:USE_VALIDATORS) unless WxArrayDoubleProperty.validator WxArrayDoubleProperty.validator = Wx::PG::NumericPropertyValidator.new(Wx::PG::NumericPropertyValidator::NumericType::Float) # Accept also a delimiter and space character WxArrayDoubleProperty.validator.add_char_includes(@delimiter) WxArrayDoubleProperty.validator.add_char_includes(' ') end WxArrayDoubleProperty.validator else nil end end def validate_value(value, validationInfo) unless value.object?(::Array) && value.object.all? { |e| ::Float === e } validationInfo.set_failure_message('At least one element is not a valid floating-point number.') false else true end end end