ifmapper 0.7 → 0.8
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/HISTORY.txt +31 -0
- data/IFMapper.gemspec +3 -3
- data/{IFMapper.rb → IFMapper.rbw} +0 -0
- data/TODO.txt +7 -0
- data/lib/IFMapper/Connection.rb +5 -0
- data/lib/IFMapper/FXMap.rb +283 -128
- data/lib/IFMapper/FXMapperWindow.rb +31 -32
- data/lib/IFMapper/IFMReader.rb +5 -44
- data/lib/IFMapper/Map.rb +57 -0
- data/lib/IFMapper/PDFMapExporter.rb +70 -28
- data/lib/IFMapper/Room.rb +3 -0
- data/lib/IFMapper/Section.rb +30 -0
- data/lib/IFMapper/TranscriptReader.rb +748 -0
- metadata +13 -10
@@ -39,7 +39,7 @@ require 'IFMapper/FXMapColorBox'
|
|
39
39
|
class FXMapperWindow < FXMainWindow
|
40
40
|
|
41
41
|
PROGRAM_NAME = "Interactive Fiction Mapper"
|
42
|
-
VERSION = 0.
|
42
|
+
VERSION = 0.8
|
43
43
|
AUTHOR = "Gonzalo Garramuno"
|
44
44
|
TITLE = "#{PROGRAM_NAME} v#{VERSION} - Written by #{AUTHOR}"
|
45
45
|
|
@@ -65,6 +65,19 @@ class FXMapperWindow < FXMainWindow
|
|
65
65
|
return map
|
66
66
|
end
|
67
67
|
|
68
|
+
|
69
|
+
def start_automap_cb(sender, sel, ptr)
|
70
|
+
map = current_map
|
71
|
+
return if not map
|
72
|
+
map.start_automap
|
73
|
+
end
|
74
|
+
|
75
|
+
def stop_automap_cb(sender, sel, ptr)
|
76
|
+
map = current_map
|
77
|
+
return if not map
|
78
|
+
map.stop_automap
|
79
|
+
end
|
80
|
+
|
68
81
|
def open_cb(sender, sel, ptr)
|
69
82
|
file = FXMapFileDialog.new(self, "Load New Map").filename
|
70
83
|
return if file == ''
|
@@ -110,6 +123,7 @@ class FXMapperWindow < FXMainWindow
|
|
110
123
|
status "Could not load '#{file}'. Error: #{tmp}."
|
111
124
|
if map.close_cb
|
112
125
|
@maps.delete(map) if make_new_map
|
126
|
+
GC.start
|
113
127
|
end
|
114
128
|
sleep 2
|
115
129
|
return
|
@@ -118,8 +132,8 @@ class FXMapperWindow < FXMainWindow
|
|
118
132
|
map.copy(tmp)
|
119
133
|
map.create_pathmap
|
120
134
|
map.verify_integrity
|
121
|
-
map.update_title
|
122
135
|
map.window.create
|
136
|
+
map.update_title
|
123
137
|
map.draw
|
124
138
|
update_section
|
125
139
|
status "Loaded '#{file}'."
|
@@ -168,6 +182,7 @@ class FXMapperWindow < FXMainWindow
|
|
168
182
|
map = current_map
|
169
183
|
if map.close_cb
|
170
184
|
@maps.delete(map)
|
185
|
+
GC.start
|
171
186
|
end
|
172
187
|
}
|
173
188
|
|
@@ -243,32 +258,6 @@ class FXMapperWindow < FXMainWindow
|
|
243
258
|
end
|
244
259
|
end
|
245
260
|
|
246
|
-
#
|
247
|
-
# Export current map as a Postscript file
|
248
|
-
#
|
249
|
-
def ps_export_cb(sender, sel, msg)
|
250
|
-
map = current_map
|
251
|
-
return unless map
|
252
|
-
|
253
|
-
require 'IFMapper/MapPrinting'
|
254
|
-
|
255
|
-
d = FXMapFileDialog.new(self, "Save Postscript File",
|
256
|
-
[
|
257
|
-
"Postscript (*.ps)"
|
258
|
-
])
|
259
|
-
if d.filename != ''
|
260
|
-
printer = FXPrinter.new
|
261
|
-
printer.firstpage = 1
|
262
|
-
printer.lastpage = map.sections.size
|
263
|
-
printer.mediawidth = 612.0
|
264
|
-
printer.mediaheight = 792.0
|
265
|
-
printer.leftmargin = printer.rightmargin =
|
266
|
-
printer.topmargin = printer.bottommargin = 72.0
|
267
|
-
printer.flags |= PRINT_DEST_FILE
|
268
|
-
printer.name = d.filename
|
269
|
-
map.print(printer)
|
270
|
-
end
|
271
|
-
end
|
272
261
|
|
273
262
|
def pdf_export_cb(sender, sel, msg)
|
274
263
|
map = current_map
|
@@ -605,6 +594,7 @@ class FXMapperWindow < FXMainWindow
|
|
605
594
|
}
|
606
595
|
end
|
607
596
|
|
597
|
+
|
608
598
|
def about_cb(sender, id, event )
|
609
599
|
FXAboutDialogBox.new(self, "About This Software...", <<"EOF").execute
|
610
600
|
#{TITLE} - #{VERSION}
|
@@ -644,9 +634,6 @@ EOF
|
|
644
634
|
cmd = FXMenuCommand.new(submenu, "&Export as PDF...\t\tExport map as Acrobat PDF document.", nil)
|
645
635
|
cmd.connect(SEL_COMMAND, method(:pdf_export_cb))
|
646
636
|
|
647
|
-
cmd = FXMenuCommand.new(submenu, "&Export as Postscript...\t\tExport map as Postscript document.", nil)
|
648
|
-
cmd.connect(SEL_COMMAND, method(:ps_export_cb))
|
649
|
-
|
650
637
|
cmd = FXMenuCommand.new(submenu, "&Export as IFM...\t\tExport map as an IFM map.", nil)
|
651
638
|
cmd.connect(SEL_COMMAND, method(:ifm_export_cb))
|
652
639
|
FXMenuCascade.new(filemenu, "Export", nil, submenu)
|
@@ -713,6 +700,16 @@ EOF
|
|
713
700
|
cmd = FXMenuCommand.new(mapmenu, "Map Information\t\tChange Map Information.")
|
714
701
|
cmd.connect(SEL_COMMAND) { map_properties }
|
715
702
|
|
703
|
+
# Automap submenu
|
704
|
+
#
|
705
|
+
submenu = FXMenuPane.new(self)
|
706
|
+
cmd = FXMenuCommand.new(submenu, "&Start...\tCtl-T\tStart creating map from transcript file.")
|
707
|
+
cmd.connect(SEL_COMMAND, method(:start_automap_cb))
|
708
|
+
cmd = FXMenuCommand.new(submenu, "S&top...\tCtl-P\tStop autocreating map.")
|
709
|
+
cmd.connect(SEL_COMMAND, method(:stop_automap_cb))
|
710
|
+
FXMenuCascade.new(mapmenu, "Automap", nil, submenu)
|
711
|
+
|
712
|
+
# Sections submenu
|
716
713
|
submenu = FXMenuPane.new(self)
|
717
714
|
cmd = FXMenuCommand.new(submenu, "Next Section\t\tGo to Next Map Section.")
|
718
715
|
cmd.connect(SEL_COMMAND) {
|
@@ -751,7 +748,9 @@ EOF
|
|
751
748
|
}
|
752
749
|
FXMenuCascade.new(mapmenu, "Sections", nil, submenu)
|
753
750
|
|
754
|
-
|
751
|
+
#
|
752
|
+
# Zoom submenu
|
753
|
+
#
|
755
754
|
submenu = FXMenuPane.new(self)
|
756
755
|
[25, 50, 75, 100, 125].each { |v|
|
757
756
|
cmd = FXMenuCommand.new(submenu, v.to_s + "%\t\tZoom page to #{v}%.")
|
data/lib/IFMapper/IFMReader.rb
CHANGED
@@ -56,6 +56,7 @@ class IFMReader
|
|
56
56
|
@map.section = 0
|
57
57
|
|
58
58
|
if @map.kind_of?(FXMap)
|
59
|
+
@map.options['Edit on Creation'] = false
|
59
60
|
@map.window.hide
|
60
61
|
end
|
61
62
|
|
@@ -413,6 +414,7 @@ class IFMReader
|
|
413
414
|
# so we check we are in the right section.
|
414
415
|
@map.sections.each_with_index { |p, idx|
|
415
416
|
if p.rooms.include?(roomA)
|
417
|
+
@map.fit
|
416
418
|
@map.section = idx
|
417
419
|
break
|
418
420
|
end
|
@@ -484,6 +486,7 @@ class IFMReader
|
|
484
486
|
# current section... so we check for that here
|
485
487
|
@map.sections.each_with_index { |p, idx|
|
486
488
|
if p.rooms.include?(roomA)
|
489
|
+
@map.fit
|
487
490
|
@map.section = idx
|
488
491
|
break
|
489
492
|
end
|
@@ -542,48 +545,6 @@ class IFMReader
|
|
542
545
|
# a) Adjust map's width/height
|
543
546
|
# b) Shift all rooms so that no rooms are in negative locations
|
544
547
|
#
|
545
|
-
def adjust_map
|
546
|
-
# First, adjust map's width and height
|
547
|
-
@map.width = @map.height = 1
|
548
|
-
minXY = []
|
549
|
-
maxXY = []
|
550
|
-
@map.sections.each { |section|
|
551
|
-
next if section.rooms.empty?
|
552
|
-
|
553
|
-
sizes = section.min_max_rooms
|
554
|
-
minXY.push sizes[0]
|
555
|
-
maxXY.push sizes[1]
|
556
|
-
|
557
|
-
w = maxXY[-1][0] - minXY[-1][0]
|
558
|
-
h = maxXY[-1][1] - minXY[-1][1]
|
559
|
-
|
560
|
-
# We store +3 to allow for complex connections if needed.
|
561
|
-
@map.width = w + 3 if w >= @map.width - 2
|
562
|
-
@map.height = h + 3 if h >= @map.height - 2
|
563
|
-
}
|
564
|
-
|
565
|
-
|
566
|
-
# Okay, minXY[]/maxXY[] contains all the minXY/maxXY positions of
|
567
|
-
# each section. With that info and @map.weight/height, we can
|
568
|
-
# start shifting all nodes in the section so that they will fit.
|
569
|
-
idx = 0
|
570
|
-
@map.sections.each { |p|
|
571
|
-
next if p.rooms.size == 0
|
572
|
-
x = y = 0
|
573
|
-
x = 1 - minXY[idx][0] if minXY[idx][0] < 1
|
574
|
-
y = 1 - minXY[idx][1] if minXY[idx][1] < 1
|
575
|
-
x = @map.width - maxXY[idx][0] - 1 if maxXY[idx][0] >= @map.width - 1
|
576
|
-
y = @map.height - maxXY[idx][1] - 1 if maxXY[idx][1] >= @map.height - 1
|
577
|
-
idx += 1
|
578
|
-
next if x == 0 and y == 0 # nothing to shift
|
579
|
-
p.rooms.each { |r|
|
580
|
-
r.x += x
|
581
|
-
r.y += y
|
582
|
-
}
|
583
|
-
}
|
584
|
-
|
585
|
-
@map.create_pathmap if @map.kind_of?(FXMap)
|
586
|
-
end
|
587
548
|
|
588
549
|
def initialize(file, map = Map.new('IFM Imported Map'))
|
589
550
|
@tags = {}
|
@@ -595,13 +556,13 @@ class IFMReader
|
|
595
556
|
parse(f)
|
596
557
|
}
|
597
558
|
# puts '--------------- second pass'
|
559
|
+
@map.fit
|
598
560
|
@resolve_tags = true
|
599
561
|
File.open(file) { |f|
|
600
562
|
parse(f)
|
601
563
|
}
|
602
564
|
# puts '--------------- second pass done'
|
603
|
-
@map.section
|
604
|
-
adjust_map
|
565
|
+
@map.section = 0
|
605
566
|
if @map.kind_of?(FXMap)
|
606
567
|
@map.filename = file.sub(/\.ifm$/i, '.map')
|
607
568
|
@map.navigation = true
|
data/lib/IFMapper/Map.rb
CHANGED
@@ -58,6 +58,52 @@ class Map
|
|
58
58
|
}
|
59
59
|
end
|
60
60
|
|
61
|
+
#
|
62
|
+
# Change map's width and height to make sure all rooms and connections
|
63
|
+
# will fit in map
|
64
|
+
#
|
65
|
+
def fit
|
66
|
+
# First, adjust map's width and height
|
67
|
+
@width = @height = 3
|
68
|
+
minXY = []
|
69
|
+
maxXY = []
|
70
|
+
|
71
|
+
@sections.each { |section|
|
72
|
+
next if section.rooms.empty?
|
73
|
+
|
74
|
+
sizes = section.min_max_rooms
|
75
|
+
minXY.push sizes[0]
|
76
|
+
maxXY.push sizes[1]
|
77
|
+
|
78
|
+
w = maxXY[-1][0] - minXY[-1][0]
|
79
|
+
h = maxXY[-1][1] - minXY[-1][1]
|
80
|
+
|
81
|
+
# We store +3 to allow for complex connections if needed.
|
82
|
+
@width = w + 3 if w >= @width - 2
|
83
|
+
@height = h + 3 if h >= @height - 2
|
84
|
+
}
|
85
|
+
|
86
|
+
|
87
|
+
# Okay, minXY[]/maxXY[] contains all the minXY/maxXY positions of
|
88
|
+
# each section. With that info and @weight/@height, we can
|
89
|
+
# start shifting all nodes in the section so that they will fit.
|
90
|
+
idx = 0
|
91
|
+
@sections.each { |p|
|
92
|
+
next if p.rooms.size == 0
|
93
|
+
x = y = 0
|
94
|
+
x = 1 - minXY[idx][0] if minXY[idx][0] < 1
|
95
|
+
y = 1 - minXY[idx][1] if minXY[idx][1] < 1
|
96
|
+
x = @width - maxXY[idx][0] - 2 if maxXY[idx][0] >= @width - 1
|
97
|
+
y = @height - maxXY[idx][1] - 2 if maxXY[idx][1] >= @height - 1
|
98
|
+
idx += 1
|
99
|
+
next if x == 0 and y == 0 # nothing to shift
|
100
|
+
p.rooms.each { |r|
|
101
|
+
r.x += x
|
102
|
+
r.y += y
|
103
|
+
}
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
61
107
|
def initialize(name)
|
62
108
|
@section = 0
|
63
109
|
@name = name
|
@@ -81,6 +127,17 @@ class Map
|
|
81
127
|
@date = b.date
|
82
128
|
end
|
83
129
|
|
130
|
+
#
|
131
|
+
# Return true or false if map is free at location x,y
|
132
|
+
#
|
133
|
+
def free?(x, y)
|
134
|
+
return @sections[@section].free?(x,y)
|
135
|
+
end
|
136
|
+
|
137
|
+
def shift(x, y, dx, dy)
|
138
|
+
@sections[@section].shift(x, y, dx, dy)
|
139
|
+
end
|
140
|
+
|
84
141
|
def delete_connection(c)
|
85
142
|
@sections[@section].delete_connection(c)
|
86
143
|
end
|
@@ -20,7 +20,7 @@ PDF_ROOM_WIDTH = W * PDF_ZOOM
|
|
20
20
|
PDF_ROOM_HEIGHT = H * PDF_ZOOM
|
21
21
|
PDF_ROOM_WS = WS * PDF_ZOOM
|
22
22
|
PDF_ROOM_HS = HS * PDF_ZOOM
|
23
|
-
PDF_MARGIN = 20
|
23
|
+
PDF_MARGIN = 20.0
|
24
24
|
|
25
25
|
#
|
26
26
|
# Open all the map class and add all pdf methods there
|
@@ -256,46 +256,86 @@ class FXSection
|
|
256
256
|
end
|
257
257
|
|
258
258
|
|
259
|
-
def pdf_draw_section_name( pdf, opts )
|
259
|
+
def pdf_draw_section_name( pdf, opts, px, py )
|
260
260
|
return if not @name or @name == ''
|
261
|
-
y = (opts['height']) * opts['hh'] + 16
|
262
261
|
xymin, xymax = min_max_rooms
|
263
|
-
|
264
|
-
|
262
|
+
text = @name
|
263
|
+
text += " (#{px}, #{py})" if px > 0 or py > 0
|
264
|
+
y = (opts['height']) * opts['hh'] + 16
|
265
|
+
w = xymax[0]
|
266
|
+
w = opts['width'] if w > opts['width']
|
267
|
+
x = (w + 2) * opts['ww'] / 2 - text.size / 2 * 16
|
268
|
+
x = 0 if x < 0
|
269
|
+
pdf.add_text( x, y, text, 16 )
|
265
270
|
end
|
266
271
|
|
267
272
|
|
268
273
|
def pdf_draw(pdf, opts, mapname )
|
269
|
-
if rotate
|
270
|
-
pdf.rotate_axis(90.0)
|
271
|
-
pdf.translate_axis( 0, -pdf.page_height )
|
272
|
-
end
|
273
274
|
|
274
|
-
# Move section to its position in page
|
275
|
-
pack = [@xoff * opts['ww'], @yoff * -opts['hh']]
|
276
|
-
pdf.translate_axis( pack[0], pack[1] )
|
277
275
|
|
278
|
-
|
279
|
-
|
280
|
-
pdf.stroke_color(Color::RGB::Black)
|
281
|
-
pdf.fill_color(Color::RGB::Black)
|
276
|
+
w, h = rooms_width_height
|
277
|
+
x, y = [0, 0]
|
282
278
|
|
283
|
-
|
279
|
+
loop do
|
284
280
|
|
285
|
-
|
281
|
+
if rotate
|
282
|
+
pdf.rotate_axis(90.0)
|
283
|
+
pdf.translate_axis( 0, -pdf.page_height )
|
284
|
+
end
|
286
285
|
|
287
|
-
|
288
|
-
|
289
|
-
|
286
|
+
# Move section to its position in page
|
287
|
+
tx1, ty1 = [@xoff * opts['ww'], @yoff * -opts['hh']]
|
288
|
+
pdf.translate_axis( tx1, ty1 )
|
289
|
+
|
290
|
+
# Use times-roman as font
|
291
|
+
pdf.select_font 'Times-Roman'
|
292
|
+
pdf.stroke_color(Color::RGB::Black)
|
293
|
+
pdf.fill_color(Color::RGB::Black)
|
294
|
+
|
295
|
+
pdf_draw_section_name( pdf, opts, x, y )
|
296
|
+
|
297
|
+
xymin, = min_max_rooms
|
298
|
+
|
299
|
+
# Move rooms, so that we don't print empty areas
|
300
|
+
tx2 = -(xymin[0]) * opts['ww'] - x * opts['ww']
|
301
|
+
ty2 = (xymin[1]) * opts['hh'] - 60 + (y - (y > 0? 1 : 0)) * opts['hh']
|
302
|
+
pdf.translate_axis( tx2, ty2 )
|
303
|
+
|
304
|
+
|
305
|
+
# For testing purposes only, draw grid of boxes
|
306
|
+
# pdf_draw_grid( pdf, opts, width, height )
|
307
|
+
@connections.each { |c|
|
308
|
+
a = c.roomA
|
309
|
+
b = c.roomB
|
310
|
+
next if a.y < y and b and b.y < y
|
311
|
+
c.pdf_draw( pdf, opts )
|
312
|
+
}
|
313
|
+
@rooms.each_with_index { |r, idx|
|
314
|
+
next if r.y < y
|
315
|
+
r.pdf_draw( pdf, opts, idx)
|
316
|
+
}
|
317
|
+
|
318
|
+
# Reset axis
|
319
|
+
pdf.translate_axis(-tx2, -ty2)
|
320
|
+
pdf.translate_axis(-tx1, -ty1)
|
321
|
+
|
322
|
+
xi = opts['width']
|
323
|
+
yi = opts['height']
|
324
|
+
if rotate
|
325
|
+
xi = (pdf.page_height / opts['ww']).to_i - 1
|
326
|
+
yi = (pdf.page_width / opts['hh']).to_i - 1
|
327
|
+
end
|
290
328
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
329
|
+
x += xi
|
330
|
+
if x >= w
|
331
|
+
x = 0
|
332
|
+
y += yi
|
333
|
+
break if y >= h
|
334
|
+
end
|
295
335
|
|
296
|
-
|
297
|
-
|
298
|
-
|
336
|
+
# We could not fit all rooms in page. Start new page
|
337
|
+
pdf.start_new_page
|
338
|
+
end
|
299
339
|
end
|
300
340
|
end
|
301
341
|
|
@@ -332,6 +372,7 @@ class FXMap
|
|
332
372
|
create_pathmap
|
333
373
|
end
|
334
374
|
|
375
|
+
|
335
376
|
def pdf_export(pdffile = Dir::tmpdir + "/ifmap.pdf", printer = nil)
|
336
377
|
|
337
378
|
paper = 'LETTER'
|
@@ -353,6 +394,7 @@ class FXMap
|
|
353
394
|
# Open a new PDF writer with paper selected
|
354
395
|
pdf = PDF::Writer.new :paper => paper
|
355
396
|
|
397
|
+
pdf.margins_pt 0
|
356
398
|
|
357
399
|
pdf_options = @options.dup
|
358
400
|
|
data/lib/IFMapper/Room.rb
CHANGED
data/lib/IFMapper/Section.rb
CHANGED
@@ -48,6 +48,36 @@ class Section
|
|
48
48
|
return [ minXY, maxXY ]
|
49
49
|
end
|
50
50
|
|
51
|
+
#
|
52
|
+
# Return true or false if map is free at location x,y
|
53
|
+
#
|
54
|
+
def free?(x,y)
|
55
|
+
@rooms.each { |r|
|
56
|
+
return false if r.x == x and r.y == y
|
57
|
+
}
|
58
|
+
return true
|
59
|
+
end
|
60
|
+
|
61
|
+
#
|
62
|
+
# Shift rooms in dx and dy direction from x,y position on.
|
63
|
+
#
|
64
|
+
def shift(x, y, dx, dy)
|
65
|
+
@rooms.each { |r|
|
66
|
+
ox = oy = 0
|
67
|
+
if (dx < 0 and r.x <= x) or
|
68
|
+
(dx > 0 and r.x >= x)
|
69
|
+
ox = dx
|
70
|
+
end
|
71
|
+
if (dy < 0 and r.y <= y) or
|
72
|
+
(dy > 0 and r.y >= y)
|
73
|
+
oy = dy
|
74
|
+
end
|
75
|
+
r.x += ox
|
76
|
+
r.y += oy
|
77
|
+
}
|
78
|
+
end
|
79
|
+
|
80
|
+
|
51
81
|
#
|
52
82
|
# Delete a connection from section
|
53
83
|
#
|