Files
2025-09-29 00:52:08 +02:00

548 lines
17 KiB
Ruby
Executable File

# ****************************************************************************
#
# Copyright (c) Microsoft Corporation.
#
# This source code is subject to terms and conditions of the Apache License, Version 2.0. A
# copy of the license can be found in the License.html file at the root of this distribution. If
# you cannot locate the Apache License, Version 2.0, please send an email to
# ironruby@microsoft.com. By using this source code in any fashion, you are agreeing to be bound
# by the terms of the Apache License, Version 2.0.
#
# You must not remove this notice, or any other, from this software.
#
#
# ****************************************************************************
SILVERLIGHT = !System::Type.get_type('System.Windows.Browser.HtmlPage, System.Windows.Browser, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e').nil? unless defined? SILVERLIGHT
MOONLIGHT = !System::Type.get_type('Mono.MoonException, System.Windows, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e').nil? unless defined? MOONLIGHT
if not SILVERLIGHT
# Reference the WPF assemblies
require 'system.xml, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089'
require 'PresentationFramework, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
require 'PresentationCore, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
require 'windowsbase, Version=3.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35'
else
require 'System.Xml, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e'
end
class System::Windows::FrameworkElement
# Monkey-patch FrameworkElement to allow window.ChildName instead of window.FindName("ChildName")
# If FindName doesn't yield an object, it tried the Resources collection (for things like Storyboards)
# TODO - Make window.child_name work as well
def method_missing name, *args
obj = find_name(name.to_s.to_clr_string)
obj ||= self.resources[name.to_s.to_clr_string]
obj || super
end
def hide!
self.visibility = System::Windows::Visibility.hidden
end
def collapse!
self.visibility = System::Windows::Visibility.collapsed
end
def show!
self.visibility = System::Windows::Visibility.visible
end
# * Makes the element with a name of "element_name" as visible if "value" is not nil (or false)
# and invokes the given block
# * Makes the element as collapsed if "value" is nil (or false)
#
# Example:
# my_window.set_or_collapse(:label, some_message) { |element, value| element.text = value }
#
def set_or_collapse(element_name, value)
obj = send(element_name)
if obj && value
yield obj, value
obj.show!
else
obj.collapse!
end
end
def content=(value)
send "#{respond_to?(:Content) ? :Content : :Text}=", value
end
end
if not SILVERLIGHT
class System::Windows::Controls::RichTextBox
def document=(value)
smflow = value.kind_of?(Wpf::SimpleMarkupFlow) ? value : Wpf::SimpleMarkupFlow.new(value)
self.Document = smflow.flow
end
end
end
class System::Windows::Controls::TextBox
def document=(value)
if SILVERLIGHT
self.Text = ""
smflow = value.kind_of?(Wpf::SimpleMarkupFlow) ? value : Wpf::SimpleMarkupFlow.new(value)
smflow.flow.each {|i| self.inlines.add i }
self.TextWrapping = TextWrapping.Wrap # TODO : Move this to XAML
else
self.Text = value.flow
end
end
end
class System::Windows::Controls::TextBlock
def document=(value)
if SILVERLIGHT
self.Text = ""
smflow = value.kind_of?(Wpf::SimpleMarkupFlow) ? value : Wpf::SimpleMarkupFlow.new(value)
smflow.flow.each {|i| self.inlines.add i }
self.TextWrapping = TextWrapping.Wrap # TODO : Move this to XAML
else
self.Text = value.flow
end
end
end
if SILVERLIGHT
class System::Windows::Controls::ScrollViewer
def scroll_to_top
scroll_to_vertical_offset(0)
end
def scroll_to_bottom
scroll_to_vertical_offset(actual_height + scrollable_height)
end
end
end
class System::Windows::Markup::XamlReader
class << self
alias raw_load load unless method_defined? :raw_load
end
def self.load(xaml)
obj = if SILVERLIGHT
self.Load(xaml)
else
return raw_load(xaml) unless xaml.respond_to? :to_clr_string
self.Load(
System::Xml::XmlReader.create(
System::IO::StringReader.new(xaml.to_clr_string)))
end
yield obj if block_given?
obj
end
def self.erb_load(xaml, b, &block)
require 'erb'
self.load(ERB.new(xaml).result(b).to_s, &block)
end
end
class Module
# methods - array of method names to redirect to another method with a varying method name.
# This is useful when a window has a varying array of sub-controls. A single method name
# can be used to invoke a function on, say, the last sub-control in the array, without
# having to know the exact current size of the array.
#
# Example:
# class Example
# attr_accessor :idx
# def current_text() @idx.to_s end
# def show_text_1() @textbox1.show! end
# def show_text_2() @textbox2.show! end
# delegate_methods [:show_text], :append => :current_text
# end
#
# c = Example.new
# c.idx = 2
# c.show_text # => calls show_text_2
def delegate_methods(methods, opts = {})
raise TypeError, "methods should be an array" unless methods.kind_of?(Array)
this = self
opts[:to] ||= self
opts[:prepend] = opts[:prepend] ? "#{opts[:prepend]}_" : ''
opts[:append] = if opts[:append]
append = opts[:append]
lambda{|this| "_#{this::send(append)}" }
else
lambda{|this| '' }
end
methods.each do |method|
define_method(method.to_s.to_sym) do
send(opts[:to]).send "#{opts[:prepend]}#{method}#{opts[:append][self]}"
end
end
end
end
if SILVERLIGHT
class System::Windows::DependencyObject
def begin_invoke &block
require 'System.Core, Version=2.0.5.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e'
dispatch_callback = System::Action[0].new block
self.dispatcher.begin_invoke dispatch_callback
end
end
else
class System::Windows::Threading::DispatcherObject
def begin_invoke &block
require "system.core, Version=3.5.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"
dispatch_callback = System::Action[0].new block
self.dispatcher.begin_invoke System::Windows::Threading::DispatcherPriority.Normal, dispatch_callback
end
end
end
module Wpf
include System::Windows
include System::Windows::Documents
include System::Windows::Controls
include System::Windows::Input
include System::Windows::Markup
include System::Windows::Media
def self.load_xaml_file(filename)
f = System::IO::FileStream.new filename, System::IO::FileMode.open, System::IO::FileAccess.read
begin
element = XamlReader.load f
ensure
f.close
end
element
end
# Returns an array with all the children, or invokes the block (if given) for each child
# Note that it also includes content (which could be just strings).
def self.walk(tree, &b)
if not block_given?
result = []
walk(tree) { |child| result << child }
return result
end
yield tree
if tree.respond_to? :Children
tree.Children.each { |child| walk child, &b }
elsif tree.respond_to? :Child
walk tree.Child, &b
elsif tree.respond_to? :Content
walk tree.Content, &b
end
end
# If you constructed your treeview with XAML, you should
# use this XAML snippet instead to auto-expand items:
#
# <TreeView.ItemContainerStyle>
# <Style>
# <Setter Property="TreeViewItem.IsExpanded" Value="True"/>
# <Style.Triggers>
# <DataTrigger Binding="{Binding Type}" Value="menu">
# <Setter Property="TreeViewItem.IsSelected" Value="True"/>
# </DataTrigger>
# </Style.Triggers>
# </Style>
# </TreeView.ItemContainerStyle>
#
# If your treeview was constructed with code, use this method
def self.select_tree_view_item(tree_view, item)
return false unless self and item
childNode = tree_view.ItemContainerGenerator.ContainerFromItem item
if childNode
childNode.focus
childNode.IsSelected = true
# TODO - BringIntoView ?
return true
end
if tree_view.Items.Count > 0
tree_view.Items.each do |childItem|
childControl = tree_view.ItemContainerGenerator.ContainerFromItem(childItem)
return false if not childControl
# If tree node is not loaded, its sub-nodes will be nil. Force them to be loaded
old_is_expanded = childControl.is_expanded
childControl.is_expanded = true
childControl.update_layout
if select_tree_view_item childControl, item
return true
else
childControl.is_expanded = old_is_expanded
end
end
end
false
end
def self.create_sta_thread &block
ts = System::Threading::ThreadStart.new &block
t = Thread.clr_new ts
t.ApartmentState = System::Threading::ApartmentState.STA
t.Start
end
# Some setup is needed to use WPF from an interactive session console (like iirb). This is because
# WPF needs to do message pumping on a thread, iirb also requires a thread to read user input,
# and all commands that interact with UI need to be executed on the message pump thread.
def self.interact
raise NotImplementedError, "Wpf.interact is not implemented yet"
def CallBack(function, priority = DispatcherPriority.Normal)
Application.Current.Dispatcher.BeginInvoke(priority, System::Action[].new(function))
end
def CallBack1(function, arg0, priority = DispatcherPriority.Normal)
Application.Current.Dispatcher.BeginInvoke(priority, System::Action[arg0.class].new(function), arg0)
end
dispatcher = nil
message_pump_started = System::Threading::AutoResetEvent.new false
create_sta_thread do
app = Application.new
app.startup do
dispatcher = Dispatcher.FromThread System::Threading::Thread.current_thread
message_pump_started.set
end
begin
app.run
ensure
IronRuby::Ruby.SetCommandDispatcher(None) # This is a non-existent method that will need to be implemented
end
end
message_pump_started.wait_one
def dispatch_console_command(console_command)
if console_command
dispatcher.invoke DispatcherPriority.Normal, console_command
end
end
IronRuby::Ruby.SetCommandDispatcher dispatch_console_command # This is a non-existent method that will need to be implemented
end
# Converts text in RDoc simple markup format
class SimpleMarkupFlow
include System::Windows
include System::Windows::Documents
def initialize(text)
require 'rdoc/markup/simple_markup'
require 'rdoc/markup/simple_markup/inline'
if not @markupParser
@markupParser = SM::SimpleMarkup.new
# external hyperlinks
@markupParser.add_special(/((link:|https?:|mailto:|ftp:|www\.)\S+\w)/, :HYPERLINK)
# and links of the form <text>[<url>]
@markupParser.add_special(/(((\{.*?\})|\b\S+?)\[\S+?\.\S+?\])/, :TIDYLINK)
# @markupParser.add_special(/\b(\S+?\[\S+?\.\S+?\])/, :TIDYLINK)
end
begin
@markupParser.convert(text, self)
rescue Exception => e
puts "#{e} while converting: #{text[0..50]}"
raise e
end
end
# Returns an array of Inline object on Silverlight, or a WPF FlowDocument object otherwise
attr_accessor :flow
def add_paragraph(item, bold = true, font_family = nil)
if item.respond_to? :to_str
item = SimpleMarkupFlow.create_run item.to_str, bold, font_family
end
if SILVERLIGHT
if item.respond_to? :to_ary
@flow += item
else
@flow << item
end
@flow << LineBreak.new
else
para = Paragraph.new
if item.respond_to? :to_ary
items.each {|i| para.inlines.add i }
else
para.inlines.add item
end
@flow.blocks.add para
end
end
def self.create_run(text, bold = false, font_family = nil, font_style = nil)
run = Run.new
run.text = text
run.font_family = FontFamily.new font_family if font_family
run.font_weight = FontWeights.Bold if bold
run.font_style = font_style if font_style
run
end
def start_accepting
@@bold_mask = SM::Attribute.bitmap_for :BOLD
@@italics_mask = SM::Attribute.bitmap_for :EM
@@tt_mask = SM::Attribute.bitmap_for :TT
@@hyperlink_mask = SM::Attribute.bitmap_for :HYPERLINK
@@tidylink_mask = SM::Attribute.bitmap_for :TIDYLINK
@flow = SILVERLIGHT ? [] : FlowDocument.new
@attributes = []
end
def end_accepting
@flow
end
def accept_paragraph(am, fragment)
inlines = convert_flow(am.flow(fragment.txt))
if SILVERLIGHT
@flow += inlines
@flow << LineBreak.new
else
paragraph = Paragraph.new
inlines.each {|i| paragraph.inlines.add i }
@flow.blocks.add paragraph
end
end
def convert_flow(flow)
inlines = []
active_attribute = nil
flow.each do |item|
case item
when String
run = Run.new
run.Text = item
case active_attribute
when @@bold_mask
run.font_weight = FontWeights.Bold
@attributes.clear
when @@italics_mask
run.font_style = FontStyles.Italic
when @@tt_mask
run.font_weight = FontWeights.Bold
run.font_family = FontFamily.new "Consolas"
when nil
else
raise "unexpected active_attribute: #{active_attribute}"
end
inlines << run
when SM::AttrChanger
on_mask = item.turn_on
active_attribute = on_mask if not on_mask.zero?
off_mask = item.turn_off
if not off_mask.zero?
raise NotImplementedError.new("mismatched attribute #{SM::Attribute.as_string(off_mask)} with active_attribute=#{SM::Attribute.as_string(active_attribute)}") if off_mask != active_attribute
active_attribute = nil
end
when SM::Special
convert_special(item, inlines)
else
raise "Unknown flow element: #{item.inspect}"
end
end
raise "mismatch" if active_attribute
inlines
end
def accept_verbatim(am, fragment)
add_paragraph fragment.txt, true, "Consolas"
end
def accept_list_start(am, fragment)
@list = System::Windows::Documents::List.new if not SILVERLIGHT
end
def accept_list_end(am, fragment)
@flow.blocks.add @list if not SILVERLIGHT
end
def accept_list_item(am, fragment)
inlines = convert_flow(am.flow(fragment.txt))
if SILVERLIGHT
run = SimpleMarkupFlow.create_run "o ", true
inlines.unshift run
add_paragraph inlines
else
paragraph = Paragraph.new
inlines.each {|i| paragraph.inlines.add i }
list_item = ListItem.new paragraph
@list.list_items.add list_item
end
end
def accept_blank_line(am, fragment)
@flow << LineBreak.new if SILVERLIGHT
end
def accept_rule(am, fragment)
raise NotImplementedError, "accept_rule: #{fragment.to_s}"
end
def convert_special(special, inlines)
handled = false
SM::Attribute.each_name_of(special.type) do |name|
method_name = "handle_special_#{name}"
return send(method_name, special, inlines) if self.respond_to? method_name
end
raise "Unhandled special: #{special}"
end
def handle_special_HYPERLINK(special, inlines)
run = SimpleMarkupFlow.create_run special.text
if SILVERLIGHT
inlines << run
else
inlines << Hyperlink.new(run)
end
end
def handle_special_TIDYLINK(special, inlines)
text = special.text
# text =~ /(\S+)\[(.*?)\]/
unless text =~ /\{(.*?)\}\[(.*?)\]/ or text =~ /(\S+)\[(.*?)\]/
handle_special_HYPERLINK(special, inlines)
return
end
label = $1
url = $2
if SILVERLIGHT
run = SimpleMarkupFlow.create_run "#{label} (#{url})"
inlines << run
else
run = SimpleMarkupFlow.create_run label
hyperlink = Hyperlink.new run
hyperlink.NavigateUri = System::Uri.new url
inlines << hyperlink
end
end
end
end