libguib 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/src/fxobjects.rb ADDED
@@ -0,0 +1,253 @@
1
+ # Copyright (c) 2004-2006 by Henon (meinrad dot recheis at gmail dot com)
2
+
3
+ if __FILE__ == $0
4
+ require "fox"
5
+ end
6
+
7
+ def loadImageToString filename
8
+ data = nil
9
+ File.open(filename, "rb") { |f| data = f.read }
10
+ data.unpack1("h#{data.size * 2}")
11
+ end
12
+
13
+ def loadImageFromString s, icon_class = Fox::FXPNGIcon
14
+ raise TypeError unless $fxapp
15
+ imgdata = [s].pack("h#{s.size * 2}")
16
+ img = icon_class.new($fxapp, imgdata, Fox::IMAGE_KEEP | Fox::IMAGE_SHMI | Fox::IMAGE_SHMP)
17
+ img.create
18
+ img
19
+ end
20
+
21
+ def load_dummy
22
+ raise TypeError unless $fxapp
23
+ imgdata = "9805e474d0a0a1a0000000d0948444250000000100000001803000000082d2f035000000a247548547342756164796f6e6024596d65600d4f60293026456260223030343021363a33363a3133302b2031303037c63ded4000000704794d454704d2090f0627041559b9c00000090078495370000b0210000b021102ddde7cf000000407614d41400001bf8b0cf16500000003305c44554000000ffefeffff0f0ffc1c1ffcbcbff7878ff5b5bffc5c5ff7979ff9292ffadadff2f2fff7171ff4747ffb4b4ff6e6eff8383bfe98b0d000000104725e43500046e8d66000000b59444144587ad58ec1ee008028040069333b223d7ff7adec6a39c5b57f38d8f406a3992ea61d0508294b382bda9d373840c595953630f0c1c930ece73e940ee8506f8dc0446f14600fddfa260877711b0c50971c4f5ff898f7819b1678020e2a25402a2000000009454e444ea240628"
24
+ imgdata = [imgdata].pack("h#{imgdata.size * 2}")
25
+ $dummy_img = img = Fox::FXPNGIcon.new($fxapp, imgdata, Fox::IMAGE_KEEP | Fox::IMAGE_SHMI | Fox::IMAGE_SHMP)
26
+ img.create
27
+ img
28
+ end
29
+
30
+ def hasExtension(filename, ext)
31
+ File.basename(filename, ext) != File.basename(filename)
32
+ end
33
+
34
+ # Load the named image file
35
+ def loadImage(file)
36
+ img = load_dummy unless $dummy_img
37
+
38
+ unless File.exist? file
39
+ return img
40
+ end
41
+ begin
42
+ opts = Fox::IMAGE_KEEP | Fox::IMAGE_SHMI | Fox::IMAGE_SHMP
43
+ if hasExtension(file, ".gif")
44
+ img = Fox::FXGIFIcon.new($fxapp, nil, opts)
45
+ elsif hasExtension(file, ".bmp")
46
+ img = Fox::FXBMPIcon.new($fxapp, nil, opts)
47
+ elsif hasExtension(file, ".xpm")
48
+ img = Fox::FXXPMIcon.new($fxapp, nil, opts)
49
+ elsif hasExtension(file, ".png")
50
+ img = Fox::FXPNGIcon.new($fxapp, nil, opts)
51
+ elsif hasExtension(file, ".jpg")
52
+ img = Fox::FXJPGIcon.new($fxapp, nil, opts)
53
+ elsif hasExtension(file, ".pcx")
54
+ img = Fox::FXPCXIcon.new($fxapp, nil, opts)
55
+ elsif hasExtension(file, ".tif")
56
+ img = Fox::FXTIFIcon.new($fxapp, nil, opts)
57
+ elsif hasExtension(file, ".tga")
58
+ img = Fox::FXTGAIcon.new($fxapp, nil, opts)
59
+ elsif hasExtension(file, ".ico")
60
+ img = Fox::FXICOIcon.new($fxapp, nil, opts)
61
+ end
62
+ unless img
63
+ puts("Unsupported image type: #{file}")
64
+ return img
65
+ end
66
+ Fox::FXFileStream.open(file, Fox::FXStreamLoad) { |stream| img.loadPixels(stream) }
67
+ img.filename = file
68
+ img.create
69
+ rescue Exception
70
+ puts "load Image: #{$!}"
71
+ end
72
+ img
73
+ end
74
+
75
+ module FX
76
+ class Icon
77
+ def initialize filename
78
+ if filename
79
+ @filename = filename
80
+ @img = loadImage(filename)
81
+ end
82
+ end
83
+ attr_accessor :img
84
+
85
+ def to_s
86
+ @filename
87
+ end
88
+
89
+ def self.LoadFromString s, icon_class = Fox::FXPNGIcon
90
+ icon = Icon.new nil
91
+ icon.img = loadImageFromString s, icon_class
92
+ icon
93
+ end
94
+ end
95
+
96
+ # color object (united fox functions)
97
+ class Color
98
+ attr_accessor :r, :g, :b, :a
99
+ # construct by red, green, blue and (optionally) alpha value
100
+ def initialize r = 0, g = 0, b = 0, a = nil
101
+ @r, @g, @b, @a = r, g, b, a
102
+ end
103
+
104
+ # returns Fox::FXColor value
105
+ def to_FXColor
106
+ @a ? Fox::FXRGBA(@r, @g, @b, @a) : Fox::FXRGB(@r, @g, @b)
107
+ end
108
+
109
+ # get value from FXColor
110
+ def from_FXColor c
111
+ @r = Fox::FXREDVAL(c)
112
+ @g = Fox::FXGREENVAL(c)
113
+ @b = Fox::FXBLUEVAL(c)
114
+ @a = Fox::FXALPHAVAL(c)
115
+ self
116
+ end
117
+
118
+ # get value according to Fox colorname
119
+ def from_name(name)
120
+ from_FXColor(Fox.fxcolorfromname(name))
121
+ self
122
+ end
123
+
124
+ # encode binary string representation
125
+ def serialize
126
+ Fox.fxencodeColorData(to_FXColor)
127
+ end
128
+
129
+ # decode from binary string representation
130
+ def deserialize(data)
131
+ from_FXColor(Fox.fxdecodeColorData(data))
132
+ self
133
+ end
134
+
135
+ # returns human readable string representation
136
+ def to_s
137
+ (@a ? [@r, @g, @b, @a] : [@r, @g, @b]).join(",")
138
+ end
139
+
140
+ # reads value from human readable string representation
141
+ def from_s s
142
+ s = "0, 0, 0, 0" if s.size < 5
143
+ @r, @g, @b, @a = s.split(",").collect { |c| c.to_i }
144
+ self
145
+ end
146
+ end
147
+
148
+ # font object. cannot be substituted for FXFont
149
+ class Font
150
+ # param is either a Fox::FXFontDesc or a string representation
151
+ def initialize
152
+ @fd = Fox::FXFontDesc.new
153
+ end
154
+ attr_accessor :fd
155
+ # human readable string representation
156
+ def to_s
157
+ @fd.to_s
158
+ end
159
+
160
+ def from_s s
161
+ @fd.from_s s
162
+ self
163
+ end
164
+
165
+ def to_FXFont
166
+ f = Fox::FXFont.new $fxapp, @fd
167
+ f.create
168
+ f
169
+ end
170
+
171
+ def from_FXFont f
172
+ @fd = f.fontDesc
173
+ self
174
+ end
175
+ end
176
+ end # module
177
+
178
+ class Fox::FXIcon
179
+ attr_accessor :filename
180
+ def to_s
181
+ filename || ""
182
+ end
183
+ end
184
+
185
+ class Fox::FXFontDesc
186
+ # ~ alias :_initialize_ :initialize
187
+ # ~ def initialize
188
+ # ~ _initialize_
189
+ # ~ from_s "Tahoma|80|400|1|0|0|0"
190
+ # ~ end
191
+ # human readable string representation
192
+ def to_s
193
+ [face,
194
+ size,
195
+ weight,
196
+ slant,
197
+ encoding,
198
+ setwidth,
199
+ flags].join("|")
200
+ end
201
+
202
+ # parse human readable string representation
203
+ def from_s(s)
204
+ # ~ begin
205
+ a = s.split("|")
206
+ self.face = a[0]
207
+ self.size = a[1].to_i
208
+ self.weight = a[2].to_i
209
+ self.slant = a[3].to_i
210
+ self.encoding = a[4].to_i
211
+ self.setwidth = a[5].to_i
212
+ self.flags = a[6].to_i
213
+ # ~ rescue Exception
214
+ # ~ puts "error parsing string representation: #{$!}"
215
+ # ~ puts $!.backtrace.join($/)
216
+ # ~ return nil
217
+ # ~ end
218
+ self
219
+ end
220
+
221
+ # initialize from other font desc or string representation
222
+ def init fd
223
+ if fd.is_a? Fox::FXFontDesc
224
+ self.face = fd.face
225
+ self.size = fd.size
226
+ self.weight = fd.weight
227
+ self.slant = fd.slant
228
+ self.encoding = fd.encoding
229
+ self.setwidth = fd.setwidth
230
+ self.flags = fd.flags
231
+ elsif fd.is_a? String
232
+ from_s fd
233
+ end
234
+ end
235
+ end
236
+
237
+ class Fox::FXFont
238
+ def to_s
239
+ fontDesc.to_s
240
+ end
241
+ end
242
+
243
+ class Fox::FXIcon
244
+ attr_accessor :filename
245
+ def to_s
246
+ filename || ""
247
+ end
248
+ end
249
+
250
+ if __FILE__ == $0
251
+ s = loadImageToString "icon_not_found.png"
252
+ puts s == "9805e474d0a0a1a0000000d0948444250000000100000001803000000082d2f035000000a247548547342756164796f6e6024596d65600d4f60293026456260223030343021363a33363a3133302b2031303037c63ded4000000704794d454704d2090f0627041559b9c00000090078495370000b0210000b021102ddde7cf000000407614d41400001bf8b0cf16500000003305c44554000000ffefeffff0f0ffc1c1ffcbcbff7878ff5b5bffc5c5ff7979ff9292ffadadff2f2fff7171ff4747ffb4b4ff6e6eff8383bfe98b0d000000104725e43500046e8d66000000b59444144587ad58ec1ee008028040069333b223d7ff7adec6a39c5b57f38d8f406a3992ea61d0508294b382bda9d373840c595953630f0c1c930ece73e940ee8506f8dc0446f14600fddfa260877711b0c50971c4f5ff898f7819b1678020e2a25402a2000000009454e444ea240628"
253
+ end
Binary file
@@ -0,0 +1 @@
1
+
data/src/make.rb ADDED
@@ -0,0 +1,196 @@
1
+ # Copyright (c) 2004-2006 by Henon (meinrad dot recheis at gmail dot com)
2
+
3
+ # spag/hettizer:
4
+ # packs everything into one executable ruby script
5
+ STDOUT.sync = true
6
+
7
+ $program_src_dir = "./"
8
+ $program_release_dir = "../"
9
+ $program_file = "__FX__.rb"
10
+ $output_file = "lib/libGUIb16.rb"
11
+ $zip = false
12
+
13
+ $DEBUG = false
14
+
15
+ $debugstring = '"##{$program_working_dir}/#{filename}:#{i}: "'
16
+
17
+ $exclusion_names = [ # won't be inserted
18
+ /paths/,
19
+ /fox(\/)?/,
20
+ /etc\.so/,
21
+ /properties/,
22
+ /code-gen/,
23
+ /^FX/,
24
+ /fxruby/
25
+ ]
26
+ $force_require = [ # files that are not implicitely required can be forced to be "included" here
27
+ # "FileSelector-extension",
28
+ "PathSelector-extension"
29
+ ]
30
+ $required_files = [] # will be required bevore generation of the release script
31
+ $files_to_be_copied = [ # will be copied to release dir; $V marks directory
32
+ ]
33
+ $require_paths = ["", "FX"]
34
+
35
+ $path_replacements = {
36
+ # "pp"=>ENV["RUBY_HOME"]+"/..todo../pp"
37
+ }
38
+
39
+ # ####################################
40
+
41
+ def prepare
42
+ Dir.chdir($program_src_dir)
43
+ $required_files.each { |file|
44
+ require file
45
+ }
46
+
47
+ $required_files = []
48
+ end
49
+
50
+ def winPath(path)
51
+ path.tr("/", "\\")
52
+ end
53
+
54
+ def check_exclusions filename
55
+ $exclusion_names.each { |regex|
56
+ if filename&.match?(regex)
57
+ puts "#### excluded: #{filename}"
58
+ return true
59
+ end
60
+ }
61
+ false
62
+ end
63
+
64
+ $inside_unittest_block = false
65
+
66
+ def check_exist filename
67
+ if File.exist?(filename)
68
+ filename
69
+ else
70
+ $require_paths.each { |path|
71
+ newname = File.join path, filename
72
+ return newname if File.exist? newname
73
+ }
74
+ raise "! File not found: " + filename
75
+ end
76
+ end
77
+
78
+ # simulates require
79
+ def fill_in f, require_string
80
+ filename = eval(require_string)
81
+ return unless filename
82
+ filename += ".rb" unless /(\.rb$)|(\.rbw$)/.match?(filename)
83
+ if check_exclusions(filename)
84
+ f.print "###### excluded: \n" if $DEBUG
85
+ f.print "require " + require_string, "\n"
86
+ return
87
+ end
88
+ f.print "# require " + filename, "\n" if $DEBUG
89
+ puts "REQUIRE-STRING: #{require_string}"
90
+ filename = check_exist(filename)
91
+ return if $required_files.member? filename
92
+ puts "\tFILENAME: #{filename}"
93
+ $required_files << filename
94
+ f1 = File.open(filename, "r")
95
+ i = 0
96
+ for line in f1.readlines
97
+ i += 1
98
+ if $inside_unittest_block and line.strip =~ /end/
99
+ $inside_unittest_block = false
100
+ next
101
+ end
102
+ break if /#\s*MAKE_CUTOFF/.match?(line)
103
+ next if /#\s*MAKE_DROP/.match?(line.strip)
104
+ next if $inside_unittest_block
105
+ next if line.strip.empty?
106
+ next if /^#/.match?(line.strip)
107
+ next if /^;/.match?(line.strip)
108
+ next if /^dbg/.match?(line.strip)
109
+ next if /^assert/.match?(line.strip)
110
+ if /^if __FILE__/.match?(line.strip)
111
+ $inside_unittest_block = true
112
+ next
113
+ end
114
+ next if /\sif __FILE__/.match?(line.strip)
115
+ unless /require(\s|\().*((".*")|('.*'))/.match?(line)
116
+ f.print line.chomp, "\n"
117
+ f.print eval($debugstring), "\n" if $DEBUG and line.strip =~ /end/
118
+ next
119
+ end
120
+ require_string = line.strip.gsub("require", "")
121
+ fill_in f, require_string
122
+ end
123
+ end
124
+
125
+ def make_dir dir
126
+ Dir.mkdir dir
127
+ rescue Exception
128
+ puts "mkdir: " + $!
129
+ end
130
+
131
+ def delete_release
132
+ cmd = "rd /s /q #{winPath($program_release_dir)}"
133
+ puts "> " + cmd
134
+ puts "> " + `#{cmd}`
135
+ make_dir($release_dir)
136
+ end
137
+
138
+ def copy file
139
+ isdir = (file =~ /\$V/)
140
+ if isdir
141
+ file.gsub!("$V", "")
142
+ cmd = "xcopy /I /R #{winPath(file)} #{winPath($program_release_dir + File.basename(file))}"
143
+ else
144
+ cmd = "copy #{winPath(file)} #{winPath($program_release_dir + File.basename(file))}"
145
+ end
146
+ puts "> " + cmd
147
+ puts "\t" + `#{cmd}`
148
+ end
149
+
150
+ def zippe
151
+ f = File.open($program_release_dir + $gz_output_file, "wb")
152
+ g = File.open($program_release_dir + $output_file, "rb")
153
+ f.write(Zlib::Deflate.deflate(g.read, Zlib::BEST_COMPRESSION))
154
+ f.close
155
+ g.close
156
+ end
157
+
158
+ # delete_release
159
+ # make_dir $program_release_dir
160
+
161
+ puts "###### generating #{$output_file}:"
162
+ # klartext output file
163
+ File.open($program_release_dir + $output_file, "wb") { |g|
164
+ g.puts "# libGUIb: Copyright (c) by Meinrad Recheis aka Henon, 2006"
165
+ g.puts "# THIS SOFTWARE IS PROVIDED IN THE HOPE THAT IT WILL BE USEFUL"
166
+ g.puts "# WITHOUT ANY IMPLIED WARRANTY OR FITNESS FOR ANY PURPOSE."
167
+ prepare
168
+ # input
169
+ # ~ File.open( $program_file, "r"){|f|
170
+ # make it
171
+ fill_in g, "'#{$program_file}'"
172
+ $force_require.each { |name| fill_in g, "'#{name}'" }
173
+ # File.open("../build/included_files.rb", "w"){|h|
174
+ # h.puts $required_files.inspect.split.join("\n")
175
+ # }
176
+ puts "###### copying files"
177
+ $files_to_be_copied.each { |fname|
178
+ copy(fname)
179
+ }
180
+ # ~ }
181
+ }
182
+ if $zip
183
+ require "zlib"
184
+ zippe
185
+ end
186
+
187
+ # installing:
188
+ Dir.chdir $program_release_dir
189
+ if PLATFORM.match?(/win32/)
190
+ `ruby install.rb`
191
+ else
192
+ pw = File.read("pw")
193
+ io = IO.popen("sudo -S ruby install.rb", "w")
194
+ sleep 1
195
+ io.puts pw
196
+ end
@@ -0,0 +1,51 @@
1
+ # Copyright (c) 2004-2006 by Henon (meinrad dot recheis at gmail dot com)
2
+
3
+ module MiddleBtn
4
+ def handleMMB_Events
5
+ FXMAPFUNC(Fox::SEL_MIDDLEBUTTONPRESS, 0, :onMiddleBtnPress)
6
+ FXMAPFUNC(Fox::SEL_MIDDLEBUTTONRELEASE, 0, :onMiddleBtnRelease)
7
+ end
8
+
9
+ def onMiddleBtnPress(sender, sel, evt)
10
+ if enabled?
11
+ if !target.nil? && (target.handle(self, MKUINT(selector, Fox::SEL_MIDDLEBUTTONPRESS), evt) != 0)
12
+ return 1
13
+ end
14
+ end
15
+ 0
16
+ end
17
+
18
+ def onMiddleBtnRelease(sender, sel, evt)
19
+ if enabled?
20
+ if !target.nil? && (target.handle(self, MKUINT(selector, Fox::SEL_MIDDLEBUTTONRELEASE), evt) != 0)
21
+ return 1
22
+ end
23
+ end
24
+ 0
25
+ end
26
+ end
27
+
28
+ module RightBtn
29
+ def handleRMB_Events
30
+ FXMAPFUNC(Fox::SEL_MIDDLEBUTTONPRESS, 0, :onMiddleBtnPress)
31
+ FXMAPFUNC(Fox::SEL_MIDDLEBUTTONRELEASE, 0, :onMiddleBtnRelease)
32
+ end
33
+
34
+ def onRightBtnPress(sender, sel, evt)
35
+ if enabled?
36
+ if !target.nil? && (target.handle(self, MKUINT(selector, Fox::SEL_RIGHTBUTTONPRESS), evt) != 0)
37
+ return 1
38
+ end
39
+ end
40
+ 0
41
+ end
42
+
43
+ def onRightBtnRelease(sender, sel, evt)
44
+ if enabled?
45
+ if !target.nil? && (target.handle(self, MKUINT(selector, Fox::SEL_RIGHTBUTTONRELEASE), evt) != 0)
46
+ return 1
47
+ end
48
+ end
49
+ 0
50
+ end
51
+ end
@@ -0,0 +1,46 @@
1
+ # Copyright (c) 2004-2006 by Henon (meinrad dot recheis at gmail dot com)
2
+
3
+ require "event_listener"
4
+
5
+ module FX
6
+ class RadioGroup
7
+ __sends__ :event_selected
8
+ def initialize
9
+ @radio_widgets = []
10
+ @selected = nil
11
+ end
12
+ attr_reader :radio_widgets
13
+ def add_radio(radio_widget, &block)
14
+ @radio_widgets << radio_widget
15
+ radio_widget.connect(Fox::SEL_COMMAND) {
16
+ select(radio_widget)
17
+ yield if block
18
+ }
19
+ end
20
+ alias_method :<<, :add_radio
21
+ def select radio_widget
22
+ @selected = radio_widget
23
+ event_selected @selected
24
+ for w in @radio_widgets
25
+ begin
26
+ w.state = (w == @selected) # only the selected widget is checked.
27
+ rescue Exception
28
+ # this is necessary to prevent a radiomutex from crashing
29
+ # after a radiobutton has been deleted in foxGUIb
30
+ puts $!
31
+ end
32
+ end
33
+ end
34
+
35
+ attr_reader :selected
36
+ end
37
+ end # fx
38
+
39
+ if __FILE__ == $0
40
+ $app = FX::App.new "", ""
41
+ FX::MainWindow.new($app) {
42
+ }
43
+
44
+ $app.create
45
+ $app.run
46
+ end
@@ -0,0 +1,95 @@
1
+ # Copyright (c) 2004-2006 by Henon (meinrad dot recheis at gmail dot com)
2
+
3
+ if __FILE__ == $0
4
+ Dir.chdir ".."
5
+ require "FX"
6
+ end
7
+
8
+ module RadioGroup1
9
+ def RadioGroup1_initialize
10
+ @RadioGroup1_initialized = true
11
+ @RadioGroup1_listeners ||= []
12
+ end
13
+
14
+ def radio_command w
15
+ RadioGroup1_initialize unless @RadioGroup1_initialized
16
+ return if @selected_radio_widget == w
17
+ if @selected_radio_widget
18
+ @selected_radio_widget.set_radio_state false
19
+ end
20
+ @selected_radio_widget = w
21
+ @RadioGroup1_listeners.each { |l|
22
+ l.on_event(@selected_radio_widget)
23
+ }
24
+ end
25
+ attr_accessor :selected_radio_widget, :RadioGroup1_listeners
26
+ end
27
+
28
+ module RadioWidget
29
+ def radio_initialize col = FX::Color.new(255, 255, 255)
30
+ @radio_initialized = true
31
+ @radioBackColor = FX::Color.new.from_FXColor(backColor)
32
+ @radioSelectColor ||= col
33
+ @state = false
34
+ connect(SEL_LEFTBUTTONPRESS, method(:lmb_press))
35
+ end
36
+ attr_accessor :radioSelectColor, :radioSelectColor
37
+ def set_radio_state state
38
+ @state = state
39
+ change_radio_selection
40
+ end
41
+
42
+ def change_radio_selection
43
+ self.backColor = @state ? @radioSelectColor.to_FXColor : @radioBackColor.to_FXColor
44
+ end
45
+ attr_accessor :state, :group, :lmbdown
46
+
47
+ def lmb_press(*args)
48
+ if @group and @group.respond_to?(:radio_command)
49
+ set_radio_state true
50
+ @group.radio_command(self)
51
+ else
52
+ set_radio_state(!@state)
53
+ end
54
+ @lmbdown = true
55
+ 0
56
+ end
57
+ end
58
+
59
+ module FX
60
+ class RadioMatrix < Fox::FXMatrix
61
+ include RadioGroup1
62
+ def initialize(p)
63
+ super
64
+ end
65
+ end
66
+
67
+ class RadioLabel < Label
68
+ include RadioWidget
69
+ def initialize(p)
70
+ super
71
+ radio_initialize
72
+ end
73
+ end
74
+ end
75
+
76
+ # unit test
77
+ if __FILE__ == $0
78
+ $stdout.sync = true
79
+ app = App.new
80
+ w = MainWindow.new app
81
+ RadioMatrix.new(w) { |lm|
82
+ lm.matrixStyle = MATRIX_BY_COLUMNS
83
+ lm.numColumns = 3
84
+ 10.times { |i|
85
+ RadioLabel.new(lm) { |l|
86
+ l.group = lm
87
+ l.text = i.to_s
88
+ l.img = "FX/icon_not_found.png"
89
+ }
90
+ }
91
+ }
92
+ w.show(0)
93
+ app.create
94
+ app.run
95
+ end
@@ -0,0 +1,14 @@
1
+ # Copyright (c) 2004-2006 by Henon (meinrad dot recheis at gmail dot com)
2
+
3
+ def rel_path(a, b)
4
+ raise TypeError unless a.is_a? String and b.is_a? String
5
+ a.tr!("\\", "/")
6
+ b.tr!("\\", "/")
7
+ a = a.split("/")
8
+ b = b.split("/")
9
+ i = 0
10
+ while (a[i] == b[i]) && (i < a.size)
11
+ i += 1
12
+ end
13
+ "../" * (a.size - i) + b[i..-1].join("/")
14
+ end