ifmapper 0.5
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 +2 -0
- data/IFMapper.gemspec +21 -0
- data/IFMapper.rb +27 -0
- data/icons/copy.png +0 -0
- data/icons/cut.png +0 -0
- data/icons/filenew.png +0 -0
- data/icons/fileopen.png +0 -0
- data/icons/filesave.png +0 -0
- data/icons/filesaveas.png +0 -0
- data/icons/help.png +0 -0
- data/icons/kill.png +0 -0
- data/icons/nextpage.png +0 -0
- data/icons/paste.png +0 -0
- data/icons/prevpage.png +0 -0
- data/icons/printicon.png +0 -0
- data/icons/redo.png +0 -0
- data/icons/saveas.png +0 -0
- data/icons/undo.png +0 -0
- data/icons/winapp.png +0 -0
- data/icons/zoom.png +0 -0
- data/lib/IFMapper/Connection.rb +63 -0
- data/lib/IFMapper/FXAboutDialogBox.rb +32 -0
- data/lib/IFMapper/FXConnection.rb +283 -0
- data/lib/IFMapper/FXConnectionDialogBox.rb +126 -0
- data/lib/IFMapper/FXMap.rb +1614 -0
- data/lib/IFMapper/FXMapDialogBox.rb +51 -0
- data/lib/IFMapper/FXMapFileDialog.rb +29 -0
- data/lib/IFMapper/FXMapperSettings.rb +45 -0
- data/lib/IFMapper/FXMapperWindow.rb +1051 -0
- data/lib/IFMapper/FXPage.rb +24 -0
- data/lib/IFMapper/FXPageDialogBox.rb +38 -0
- data/lib/IFMapper/FXRoom.rb +218 -0
- data/lib/IFMapper/FXRoomDialogBox.rb +119 -0
- data/lib/IFMapper/FXSearchDialogBox.rb +51 -0
- data/lib/IFMapper/FXSpline.rb +54 -0
- data/lib/IFMapper/FXWarningBox.rb +45 -0
- data/lib/IFMapper/IFMReader.rb +613 -0
- data/lib/IFMapper/Map.rb +110 -0
- data/lib/IFMapper/PDFMapExporter.rb +315 -0
- data/lib/IFMapper/Page.rb +158 -0
- data/lib/IFMapper/Room.rb +104 -0
- data/maps/Bureaucracy.ifm +75 -0
- data/maps/Hollywood_Hijinx.ifm +149 -0
- data/maps/Jigsaw.ifm +806 -0
- data/maps/LGOP.ifm +705 -0
- data/maps/Mercy.ifm +76 -0
- data/maps/Planetfall.ifm +186 -0
- data/maps/Plundered_Hearts.ifm +251 -0
- data/maps/Ralph.ifm +50 -0
- data/maps/Robots_of_Dawn.ifm +224 -0
- data/maps/Seastalker.ifm +149 -0
- data/maps/Sherlock.ifm +209 -0
- data/maps/SoFar.ifm +72 -0
- data/maps/Starcross.ifm +170 -0
- data/maps/Suspended.ifm +82 -0
- data/maps/Wishbringer.ifm +277 -0
- data/maps/Wishbringer2.ifm +246 -0
- data/maps/Zork1.ifm +410 -0
- data/maps/Zork2.ifm +150 -0
- data/maps/Zork3.ifm +136 -0
- data/maps/Zork_Zero.ifm +557 -0
- data/maps/anchor.ifm +645 -0
- data/maps/atrox.ifm +134 -0
- data/maps/awaken.ifm +116 -0
- data/maps/babel.ifm +279 -0
- data/maps/bse.ifm +150 -0
- data/maps/change.ifm +128 -0
- data/maps/curses.ifm +307 -0
- data/maps/curves.ifm +529 -0
- data/maps/edifice.ifm +158 -0
- data/maps/frozen.ifm +126 -0
- data/maps/glow.ifm +101 -0
- data/maps/library.ifm +93 -0
- data/maps/mindelec.ifm +89 -0
- data/maps/minster.ifm +234 -0
- data/maps/muse.ifm +154 -0
- data/maps/paperchase.ifm +110 -0
- data/maps/space_st.ifm +104 -0
- data/maps/stationfall.ifm +320 -0
- data/maps/theatre.ifm +182 -0
- data/maps/toonesia.ifm +54 -0
- data/maps/tortoise.ifm +72 -0
- data/maps/vgame.ifm +219 -0
- data/maps/weather.ifm +98 -0
- data/maps/windhall.ifm +154 -0
- data/maps/zebulon.ifm +68 -0
- metadata +144 -0
@@ -0,0 +1,126 @@
|
|
1
|
+
|
2
|
+
#
|
3
|
+
# Class used to display a connection dialog box
|
4
|
+
#
|
5
|
+
class FXConnectionDialogBox < FXDialogBox
|
6
|
+
|
7
|
+
TYPE_TEXT = [
|
8
|
+
'Free',
|
9
|
+
'Locked',
|
10
|
+
'Special',
|
11
|
+
]
|
12
|
+
|
13
|
+
DIR_TEXT = [
|
14
|
+
'Both',
|
15
|
+
'A to B',
|
16
|
+
'B to A',
|
17
|
+
]
|
18
|
+
|
19
|
+
EXIT_TEXT = [
|
20
|
+
'None',
|
21
|
+
'Up',
|
22
|
+
'Down',
|
23
|
+
'In',
|
24
|
+
'Out',
|
25
|
+
]
|
26
|
+
|
27
|
+
attr_writer :map
|
28
|
+
|
29
|
+
def copy_to()
|
30
|
+
@conn.dir = @dir.currentNo
|
31
|
+
@conn.type = @type.currentNo
|
32
|
+
@conn.exitAtext = @exitA.currentNo
|
33
|
+
@conn.exitBtext = @exitB.currentNo
|
34
|
+
|
35
|
+
@map.draw
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def copy_from(conn)
|
40
|
+
title = conn.to_s
|
41
|
+
self.title = title
|
42
|
+
|
43
|
+
@dir.currentNo = conn.dir
|
44
|
+
@type.currentNo = conn.type || 0
|
45
|
+
@exitA.currentNo = conn.exitAtext
|
46
|
+
@exitB.currentNo = conn.exitBtext
|
47
|
+
@conn = conn
|
48
|
+
|
49
|
+
if @map.navigation
|
50
|
+
@dir.disable
|
51
|
+
@type.disable
|
52
|
+
@exitA.disable
|
53
|
+
@exitB.disable
|
54
|
+
else
|
55
|
+
@dir.enable
|
56
|
+
@type.enable
|
57
|
+
@exitA.enable
|
58
|
+
@exitB.enable
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def initialize(map, conn, event = nil)
|
63
|
+
pos = [40, 40]
|
64
|
+
if event
|
65
|
+
pos = [ event.last_x, event.last_y ]
|
66
|
+
end
|
67
|
+
maxW = map.window.width - 390
|
68
|
+
maxH = map.window.height - 300
|
69
|
+
pos[0] = maxW if pos[0] > maxW
|
70
|
+
pos[1] = maxH if pos[1] > maxH
|
71
|
+
|
72
|
+
decor = DECOR_TITLE|DECOR_BORDER|DECOR_CLOSE
|
73
|
+
super( map.window, '', decor, pos[0], pos[1], 0, 0 )
|
74
|
+
mainFrame = FXVerticalFrame.new(self,
|
75
|
+
FRAME_SUNKEN|FRAME_THICK|
|
76
|
+
LAYOUT_FILL_X|LAYOUT_FILL_Y)
|
77
|
+
|
78
|
+
frame = FXHorizontalFrame.new(mainFrame, LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
|
79
|
+
|
80
|
+
FXLabel.new(frame, "Connection Type: ", nil, 0, LAYOUT_FILL_X)
|
81
|
+
pane = FXPopup.new(self)
|
82
|
+
TYPE_TEXT.each { |t|
|
83
|
+
FXOption.new(pane, t, nil, nil, 0, JUSTIFY_HZ_APART|ICON_AFTER_TEXT)
|
84
|
+
}
|
85
|
+
@type = FXOptionMenu.new(frame, pane, FRAME_RAISED|FRAME_THICK|
|
86
|
+
JUSTIFY_HZ_APART|ICON_AFTER_TEXT|
|
87
|
+
LAYOUT_CENTER_X|LAYOUT_CENTER_Y)
|
88
|
+
|
89
|
+
|
90
|
+
FXLabel.new(frame, "Direction: ", nil, 0, LAYOUT_FILL_X)
|
91
|
+
pane = FXPopup.new(self)
|
92
|
+
DIR_TEXT.each { |t|
|
93
|
+
FXOption.new(pane, t, nil, nil, 0, JUSTIFY_HZ_APART|ICON_AFTER_TEXT)
|
94
|
+
}
|
95
|
+
@dir = FXOptionMenu.new(frame, pane, FRAME_RAISED|FRAME_THICK|
|
96
|
+
JUSTIFY_HZ_APART|ICON_AFTER_TEXT|
|
97
|
+
LAYOUT_CENTER_X|LAYOUT_CENTER_Y)
|
98
|
+
|
99
|
+
frame = FXHorizontalFrame.new(mainFrame, LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
|
100
|
+
FXLabel.new(frame, "Exit A Text: ", nil, 0, LAYOUT_FILL_X)
|
101
|
+
pane = FXPopup.new(self)
|
102
|
+
EXIT_TEXT.each { |t|
|
103
|
+
FXOption.new(pane, t, nil, nil, 0, JUSTIFY_HZ_APART|ICON_AFTER_TEXT)
|
104
|
+
}
|
105
|
+
@exitA = FXOptionMenu.new(frame, pane, FRAME_RAISED|FRAME_THICK|
|
106
|
+
JUSTIFY_HZ_APART|ICON_AFTER_TEXT|
|
107
|
+
LAYOUT_CENTER_X|LAYOUT_CENTER_Y)
|
108
|
+
FXLabel.new(frame, "Exit B Text: ", nil, 0, LAYOUT_FILL_X)
|
109
|
+
pane = FXPopup.new(self)
|
110
|
+
EXIT_TEXT.each { |t|
|
111
|
+
FXOption.new(pane, t, nil, nil, 0, JUSTIFY_HZ_APART|ICON_AFTER_TEXT)
|
112
|
+
}
|
113
|
+
@exitB = FXOptionMenu.new(frame, pane, FRAME_RAISED|FRAME_THICK|
|
114
|
+
JUSTIFY_HZ_APART|ICON_AFTER_TEXT|
|
115
|
+
LAYOUT_CENTER_X|LAYOUT_CENTER_Y)
|
116
|
+
|
117
|
+
@dir.connect(SEL_COMMAND) { copy_to() }
|
118
|
+
@type.connect(SEL_COMMAND) { copy_to()}
|
119
|
+
@exitA.connect(SEL_COMMAND) { copy_to() }
|
120
|
+
@exitB.connect(SEL_COMMAND) { copy_to() }
|
121
|
+
@map = map
|
122
|
+
|
123
|
+
# We need to create the dialog box first, so we can use select text.
|
124
|
+
create
|
125
|
+
end
|
126
|
+
end
|
@@ -0,0 +1,1614 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
|
4
|
+
require 'IFMapper/Map'
|
5
|
+
require 'IFMapper/FXPage'
|
6
|
+
require 'IFMapper/FXMapDialogBox'
|
7
|
+
require 'IFMapper/FXPageDialogBox'
|
8
|
+
|
9
|
+
|
10
|
+
class FXMap < Map
|
11
|
+
FILE_FORMAT_VERSION = 1 # Upgrade this if incompatible changes are made
|
12
|
+
# in the class so that file loading of old files can still
|
13
|
+
# be checked against.
|
14
|
+
|
15
|
+
attr_reader :zoom # Current zooming factor
|
16
|
+
attr_accessor :filename # Filename of current map (if any)
|
17
|
+
attr_reader :modified # Was map modified since being loaded?
|
18
|
+
attr_accessor :navigation # Map is navigation mode (no new nodes can be created)
|
19
|
+
attr_accessor :options # Map options
|
20
|
+
attr_reader :window # Fox Window for this map
|
21
|
+
attr :version # file format version
|
22
|
+
|
23
|
+
# pmap is a path map (a matrix or grid used for path finding).
|
24
|
+
# Rooms and paths are recorded there. Path finding is needed
|
25
|
+
# to draw complex connections (ie. those that are farther than one square)
|
26
|
+
# We now also use this for selecting of stuff, particularly complex paths.
|
27
|
+
attr :pmap
|
28
|
+
|
29
|
+
@@win = nil
|
30
|
+
|
31
|
+
@@cursor_arrow = nil
|
32
|
+
@@cursor_cross = nil
|
33
|
+
@@cursor_updown = nil
|
34
|
+
|
35
|
+
|
36
|
+
def _changed
|
37
|
+
create_pathmap
|
38
|
+
update_title
|
39
|
+
draw
|
40
|
+
end
|
41
|
+
|
42
|
+
def page=(x)
|
43
|
+
super
|
44
|
+
@complexConnection = false
|
45
|
+
create_pathmap
|
46
|
+
update_title
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def previous_page
|
51
|
+
self.page = @page - 1
|
52
|
+
_changed
|
53
|
+
end
|
54
|
+
|
55
|
+
def next_page
|
56
|
+
self.page = @page + 1
|
57
|
+
_changed
|
58
|
+
end
|
59
|
+
|
60
|
+
def modified=(x)
|
61
|
+
@modified = true
|
62
|
+
_changed
|
63
|
+
end
|
64
|
+
|
65
|
+
def rename_page
|
66
|
+
@pages[@page].properties(self)
|
67
|
+
modified = true
|
68
|
+
end
|
69
|
+
|
70
|
+
def delete_page
|
71
|
+
w = FXWarningBox.new(@window, "Are you sure you want to delete this page?")
|
72
|
+
return if w.execute == 0
|
73
|
+
|
74
|
+
delete_page_at(@page)
|
75
|
+
modified = true
|
76
|
+
end
|
77
|
+
|
78
|
+
def new_page
|
79
|
+
@pages.push( FXPage.new )
|
80
|
+
@page = @pages.size - 1
|
81
|
+
end
|
82
|
+
|
83
|
+
#
|
84
|
+
# A simple debugging function that will spit out the path map with a
|
85
|
+
# very simple ascii representation.
|
86
|
+
#
|
87
|
+
def dump_pathmap
|
88
|
+
s = ' ' * @width + "\n"
|
89
|
+
m = s * @height
|
90
|
+
(0...@width).each { |x|
|
91
|
+
(0...@height).each { |y|
|
92
|
+
loc = y * (@width+1) + x
|
93
|
+
if @pmap[x][y].kind_of?(Connection)
|
94
|
+
m[loc] = '-'
|
95
|
+
elsif @pmap[x][y].kind_of?(Room)
|
96
|
+
m[loc] = 'R'
|
97
|
+
end
|
98
|
+
}
|
99
|
+
}
|
100
|
+
puts m
|
101
|
+
end
|
102
|
+
|
103
|
+
#
|
104
|
+
# Reinitialize the pathmap to an empty matrix
|
105
|
+
#
|
106
|
+
def empty_pathmap
|
107
|
+
# First, create an empty grid of width x height
|
108
|
+
@pmap = Array.new(@width)
|
109
|
+
(0...@width).each { |x|
|
110
|
+
@pmap[x] = Array.new(@height)
|
111
|
+
}
|
112
|
+
return pmap
|
113
|
+
end
|
114
|
+
|
115
|
+
#
|
116
|
+
# Recreate the pathmap based on rooms and connections
|
117
|
+
# This routine is used on loading a new map.
|
118
|
+
#
|
119
|
+
def create_pathmap
|
120
|
+
# First, create an empty grid of width x height
|
121
|
+
empty_pathmap # Hmm... needed? Probably not.
|
122
|
+
# Then, fill it in with all rooms...
|
123
|
+
@pages[@page].rooms.each { |r| @pmap[r.x][r.y] = r }
|
124
|
+
# And following, add all paths
|
125
|
+
@pages[@page].connections.each { |c| path_find(c) }
|
126
|
+
end
|
127
|
+
|
128
|
+
#
|
129
|
+
# Given a connection, clean its path from path map.
|
130
|
+
#
|
131
|
+
def clean_path(c)
|
132
|
+
c.gpts.each { |p| @pmap[p[0]][p[1]] = nil if @pmap[p[0]][p[1]] == c }
|
133
|
+
end
|
134
|
+
|
135
|
+
#
|
136
|
+
# Remove a connection from map, since path creation failed.
|
137
|
+
#
|
138
|
+
def remove_connection(c)
|
139
|
+
clean_path(c)
|
140
|
+
c.failed = true
|
141
|
+
status "Path for connection #{c} is blocked."
|
142
|
+
end
|
143
|
+
|
144
|
+
# Given a connection, create the path for it, if not a simple
|
145
|
+
# connection. Also, add the paths to pathmap.
|
146
|
+
def path_find(c)
|
147
|
+
unless c.complex?
|
148
|
+
c.pts = c.gpts = []
|
149
|
+
c.failed = false
|
150
|
+
return true
|
151
|
+
end
|
152
|
+
|
153
|
+
# Complex path... Generate points.
|
154
|
+
a = c.roomA
|
155
|
+
b = c.roomB
|
156
|
+
|
157
|
+
# First, check the neighboring starting/ending nodes in grid
|
158
|
+
# are empty. If not, we need to abort and remove path from list.
|
159
|
+
dirA = a.exits.index(c)
|
160
|
+
raise "connection not found #{c} at #{a}" unless dirA
|
161
|
+
|
162
|
+
dirB = b.exits.rindex(c)
|
163
|
+
raise "connection not found #{c} at #{b}" unless dirB
|
164
|
+
|
165
|
+
vA = FXRoom::DIR_TO_VECTOR[dirA]
|
166
|
+
vB = FXRoom::DIR_TO_VECTOR[dirB]
|
167
|
+
|
168
|
+
pA = [ a.x + vA[0], a.y + vA[1] ]
|
169
|
+
pB = [ b.x + vB[0], b.y + vB[1] ]
|
170
|
+
|
171
|
+
c.gpts = []
|
172
|
+
c.pts = []
|
173
|
+
|
174
|
+
# Check for the special case of looping path (path that begins and
|
175
|
+
# returns to same exit)
|
176
|
+
if a == b and dirA == dirB
|
177
|
+
pt = a.corner(c, 1, dirA)
|
178
|
+
n = 1.0 / Math.sqrt(vA[0] * vA[0] + vA[1] * vA[1])
|
179
|
+
vA = [ vA[0] * n, vA[1] * n ]
|
180
|
+
c.pts.push( [ pt[0], pt[1] ] )
|
181
|
+
pA = [ pt[0] + vA[0] * 20, pt[1] + vA[1] * 20 ]
|
182
|
+
c.pts.push( [pA[0], pA[1]] )
|
183
|
+
pB = [ pA[0] + vA[1] * 20, pA[1] - vA[0] * 20 ]
|
184
|
+
c.pts.push( [pB[0], pB[1]] )
|
185
|
+
pC = [ pB[0] - vA[0] * 20, pB[1] - vA[1] * 20 ]
|
186
|
+
c.pts.push( [pC[0], pC[1]] )
|
187
|
+
c.dir = Connection::AtoB
|
188
|
+
return true
|
189
|
+
end
|
190
|
+
|
191
|
+
|
192
|
+
if pA[0] < 0 or pA[0] >= @width or
|
193
|
+
pB[0] < 0 or pB[0] >= @width or
|
194
|
+
pA[1] < 0 or pA[1] >= @height or
|
195
|
+
pB[1] < 0 or pB[1] >= @height
|
196
|
+
remove_connection(c)
|
197
|
+
return false
|
198
|
+
end
|
199
|
+
|
200
|
+
c.failed = false
|
201
|
+
|
202
|
+
# We do two tries on path finding.
|
203
|
+
# On first attempt, we try to avoid both rooms and paths.
|
204
|
+
# That is, we always try to have no paths crossing each other.
|
205
|
+
#
|
206
|
+
# If this fails, we try a second time, but we try to avoid just
|
207
|
+
# rooms. In this second attempt, paths are allowed to intercross.
|
208
|
+
#
|
209
|
+
pt = pA.dup
|
210
|
+
2.times { |try|
|
211
|
+
c.gpts = []
|
212
|
+
|
213
|
+
if try == 0
|
214
|
+
next if @pmap[pA[0]][pA[1]] or @pmap[pB[0]][pB[1]] # try again
|
215
|
+
else
|
216
|
+
if @pmap[pA[0]][pA[1]].kind_of?(Room) or
|
217
|
+
@pmap[pB[0]][pB[1]].kind_of?(Room)
|
218
|
+
remove_connection(c)
|
219
|
+
return false
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
# Okay, now we begin the real path algorithm...
|
224
|
+
# Add first point...
|
225
|
+
add_path_pt(c, pA[0], pA[1])
|
226
|
+
|
227
|
+
pt = pA.dup
|
228
|
+
|
229
|
+
# Okay, now we begin a simple A* path algorithm.
|
230
|
+
while pt[0] != pB[0] or pt[1] != pB[1]
|
231
|
+
# From point pt, get a list of free nodes, by looking all around it.
|
232
|
+
pts = []
|
233
|
+
FXRoom::DIR_TO_VECTOR.each_value { |d|
|
234
|
+
n = pt.dup
|
235
|
+
n[0] += d[0]
|
236
|
+
n[1] += d[1]
|
237
|
+
# Don't add point to list if we go out of map
|
238
|
+
if n[0] < 0 or n[0] >= @width or
|
239
|
+
n[1] < 0 or n[1] >= @height
|
240
|
+
next
|
241
|
+
end
|
242
|
+
|
243
|
+
# Now, check if pmap is an empty node.
|
244
|
+
if try == 1
|
245
|
+
next if @pmap[n[0]][n[1]].kind_of?(Room) or
|
246
|
+
@pmap[n[0]][n[1]] == c
|
247
|
+
else
|
248
|
+
next if @pmap[n[0]][n[1]]
|
249
|
+
end
|
250
|
+
|
251
|
+
# Ok, we do have an empty node. Add to list.
|
252
|
+
pts.push(n)
|
253
|
+
}
|
254
|
+
|
255
|
+
break if pts.empty?
|
256
|
+
|
257
|
+
# Okay, from all possible nodes, calculate best one, using
|
258
|
+
# the formula:
|
259
|
+
# F = G + H
|
260
|
+
# where G = movement cost to move from point to other point
|
261
|
+
# (10 or 14, depending if move is straight or diagonal)
|
262
|
+
# H = cost of square to shorten distance to destination.
|
263
|
+
# (heuristic, in this case using Manhattan distance)
|
264
|
+
# Next move is the grid node with lowest F cost.
|
265
|
+
|
266
|
+
f = []
|
267
|
+
pts.each { |x|
|
268
|
+
dx = x[0] - pt[0]
|
269
|
+
dy = x[1] - pt[1]
|
270
|
+
g = 10 # straight move G cost = 10 (ie. prefer straight moves)
|
271
|
+
if dx.abs + dy.abs > 1
|
272
|
+
g = 14 # diagonal move G cost = 14
|
273
|
+
end
|
274
|
+
|
275
|
+
dx = pB[0] - x[0]
|
276
|
+
dy = pB[1] - x[1]
|
277
|
+
h = dx * dx + dy * dy
|
278
|
+
|
279
|
+
f.push( h + g )
|
280
|
+
}
|
281
|
+
|
282
|
+
idx = f.index(f.min)
|
283
|
+
pt = pts[idx]
|
284
|
+
add_path_pt(c, pt[0], pt[1])
|
285
|
+
end # while
|
286
|
+
|
287
|
+
|
288
|
+
# Are we really there?
|
289
|
+
if pt[0] != pB[0] or pt[1] != pB[1]
|
290
|
+
# No, we must have failed...
|
291
|
+
clean_path(c)
|
292
|
+
c.gpts = []
|
293
|
+
next # go for next try
|
294
|
+
else
|
295
|
+
break
|
296
|
+
end
|
297
|
+
}
|
298
|
+
|
299
|
+
# If still not there, we cannot connect path, exit gracefully
|
300
|
+
if pt[0] != pB[0] or pt[1] != pB[1]
|
301
|
+
# If on second try, remove connection as invalid.
|
302
|
+
remove_connection(c)
|
303
|
+
return false
|
304
|
+
end
|
305
|
+
|
306
|
+
|
307
|
+
# Okay, we have a valid path.
|
308
|
+
# Create real path in display coordinates now...
|
309
|
+
# Start with a's corner
|
310
|
+
pt = a.corner(c, 1, dirA)
|
311
|
+
c.pts.push( [ pt[0], pt[1] ] )
|
312
|
+
# Then, add each grid point we calculated
|
313
|
+
c.gpts.each { |pt|
|
314
|
+
x = pt[0] * WW + WW / 2
|
315
|
+
y = pt[1] * HH + HH / 2
|
316
|
+
c.pts.push([x, y])
|
317
|
+
}
|
318
|
+
# And end with b's corner
|
319
|
+
pt = b.corner(c, 1, dirB)
|
320
|
+
c.failed = false
|
321
|
+
return c.pts.push([pt[0], pt[1]])
|
322
|
+
end
|
323
|
+
|
324
|
+
#
|
325
|
+
# Add a new path point to a connection
|
326
|
+
#
|
327
|
+
def add_path_pt( c, x, y )
|
328
|
+
@pmap[x][y] = c
|
329
|
+
c.gpts.push([x, y])
|
330
|
+
end
|
331
|
+
|
332
|
+
#
|
333
|
+
# Used for loading class with Marshal
|
334
|
+
#
|
335
|
+
def marshal_load(variables)
|
336
|
+
# if variables[1].kind_of?(Map)
|
337
|
+
# # old format
|
338
|
+
# @zoom = variables[0]
|
339
|
+
# tmpmap = variables[1]
|
340
|
+
# copy(tmpmap)
|
341
|
+
# @navigation = variables[2]
|
342
|
+
# @options = variables[3] if variables[3]
|
343
|
+
# else
|
344
|
+
@zoom = variables.shift
|
345
|
+
@navigation = variables.shift
|
346
|
+
@options = variables.shift
|
347
|
+
super
|
348
|
+
# end
|
349
|
+
@modified = false
|
350
|
+
end
|
351
|
+
|
352
|
+
#
|
353
|
+
# Used for saving class with Marshal
|
354
|
+
#
|
355
|
+
def marshal_dump
|
356
|
+
[ @zoom, @navigation, @options ] + super
|
357
|
+
end
|
358
|
+
|
359
|
+
#
|
360
|
+
# Used to copy relevant data from one (temporary) map to another
|
361
|
+
#
|
362
|
+
def copy(b)
|
363
|
+
super(b)
|
364
|
+
if b.kind_of?(FXMap)
|
365
|
+
@options = b.options if b.options
|
366
|
+
@filename = b.filename
|
367
|
+
self.zoom = b.zoom
|
368
|
+
end
|
369
|
+
end
|
370
|
+
|
371
|
+
#
|
372
|
+
# Function used to add a new room. x and y are absolute pixel positions
|
373
|
+
# in canvas.
|
374
|
+
#
|
375
|
+
def new_xy_room(x, y)
|
376
|
+
@modified = true
|
377
|
+
x = x / WW
|
378
|
+
y = y / HH
|
379
|
+
r = @pages[@page].new_room(x, y)
|
380
|
+
@pmap[x][y] = r
|
381
|
+
|
382
|
+
buf = FXMapperWindow::copy_buffer()
|
383
|
+
r.copy(buf) if buf
|
384
|
+
r.selected = true
|
385
|
+
|
386
|
+
|
387
|
+
if @options['Edit on Creation']
|
388
|
+
if not r.modal_properties(self)
|
389
|
+
@pages[@page].delete_room(r)
|
390
|
+
@pmap[x][y] = nil
|
391
|
+
return nil
|
392
|
+
end
|
393
|
+
end
|
394
|
+
|
395
|
+
return r
|
396
|
+
end
|
397
|
+
|
398
|
+
|
399
|
+
# Given a canvas (mouse) x/y position, return:
|
400
|
+
#
|
401
|
+
# false - arrow click
|
402
|
+
# true - room click
|
403
|
+
#
|
404
|
+
def click_type(x, y)
|
405
|
+
x = (x % WW).to_i
|
406
|
+
y = (y % HH).to_i
|
407
|
+
|
408
|
+
if x >= WS_2 and y >= HS_2 and
|
409
|
+
x <= (W + WS_2) and y <= (H + HS_2)
|
410
|
+
return true
|
411
|
+
else
|
412
|
+
return false
|
413
|
+
end
|
414
|
+
end
|
415
|
+
|
416
|
+
# Given an x and y canvas position, return room object,
|
417
|
+
# complex connection or nil
|
418
|
+
def to_room(x,y)
|
419
|
+
xx = x / WW
|
420
|
+
yy = y / HH
|
421
|
+
return @pmap[xx][yy]
|
422
|
+
end
|
423
|
+
|
424
|
+
# Given a mouse click x/y position, return object(s) if any or nil
|
425
|
+
def to_object(x, y)
|
426
|
+
|
427
|
+
exitA = get_quadrant(x, y)
|
428
|
+
unless exitA
|
429
|
+
# Not in arrow section, return element based on pmap
|
430
|
+
# can be a room or complex arrow connection.
|
431
|
+
xx = x / WW
|
432
|
+
yy = y / HH
|
433
|
+
return nil if xx >= @width or yy >= @height
|
434
|
+
return @pmap[xx][yy]
|
435
|
+
else
|
436
|
+
# Possible arrow
|
437
|
+
@pages[@page].connections.each { |c|
|
438
|
+
a = c.roomA
|
439
|
+
b = c.roomB
|
440
|
+
next if not b # Complex connection in progress
|
441
|
+
|
442
|
+
if c.gpts.size > 0
|
443
|
+
[a, b].each { |r|
|
444
|
+
next if not r
|
445
|
+
dir = r.exits.index(c)
|
446
|
+
x1, y1 = r.corner(c, 1, dir)
|
447
|
+
v = FXRoom::DIR_TO_VECTOR[dir]
|
448
|
+
x2 = x1 + v[0] * WS
|
449
|
+
y2 = y1 + v[1] * HS
|
450
|
+
|
451
|
+
if x1 == x2
|
452
|
+
x1 -= W / 2
|
453
|
+
x2 += W / 2
|
454
|
+
end
|
455
|
+
if y1 == y2
|
456
|
+
y1 -= H / 2
|
457
|
+
y2 += H / 2
|
458
|
+
end
|
459
|
+
if x >= x1 and x <= x2 and
|
460
|
+
y >= y1 and y < y2
|
461
|
+
return c
|
462
|
+
end
|
463
|
+
}
|
464
|
+
else
|
465
|
+
x1, y1 = a.corner(c, 1, a.exits.index(c))
|
466
|
+
x2, y2 = b.corner(c, 1, b.exits.rindex(c))
|
467
|
+
x1, x2 = x2, x1 if x2 < x1
|
468
|
+
y1, y2 = y2, y1 if y2 < y1
|
469
|
+
if x1 == x2
|
470
|
+
x1 -= W / 2
|
471
|
+
x2 += W / 2
|
472
|
+
end
|
473
|
+
if y1 == y2
|
474
|
+
y1 -= H / 2
|
475
|
+
y2 += H / 2
|
476
|
+
end
|
477
|
+
if x >= x1 and x <= x2 and
|
478
|
+
y >= y1 and y < y2
|
479
|
+
return c
|
480
|
+
end
|
481
|
+
end
|
482
|
+
}
|
483
|
+
|
484
|
+
# Then, get "rooms" being connected to check if we get
|
485
|
+
# a complex connection instead.
|
486
|
+
roomA, roomB, a, b = quadrant_to_rooms( exitA, x, y )
|
487
|
+
return roomA if roomA.kind_of?(Connection)
|
488
|
+
return roomB if roomB.kind_of?(Connection)
|
489
|
+
end
|
490
|
+
|
491
|
+
return nil
|
492
|
+
end
|
493
|
+
|
494
|
+
def update_title
|
495
|
+
title = @name.dup
|
496
|
+
if @navigation
|
497
|
+
title << " [Read Only]"
|
498
|
+
end
|
499
|
+
title << " Zoom: %.3f" % @zoom
|
500
|
+
title << " Page #{@page+1} of #{@pages.size}"
|
501
|
+
title << " #{@pages[@page].name}"
|
502
|
+
@window.title = title
|
503
|
+
end
|
504
|
+
|
505
|
+
# Change zoom factor of map. Rebuild fonts and canvas sizes.
|
506
|
+
def zoom=(value)
|
507
|
+
@zoom = ("%.2f" % value).to_f
|
508
|
+
# Create the font
|
509
|
+
fontsize = (11 * @zoom).to_i
|
510
|
+
|
511
|
+
if @window
|
512
|
+
@font = FXFont.new(@window.getApp, @options['Font Text'], fontsize)
|
513
|
+
@font.create
|
514
|
+
|
515
|
+
@objfont = FXFont.new(@window.getApp, @options['Font Objects'],
|
516
|
+
(fontsize * 0.75).to_i)
|
517
|
+
@objfont.create
|
518
|
+
|
519
|
+
width = (WW * @width * @zoom).to_i
|
520
|
+
height = (HH * @height * @zoom).to_i
|
521
|
+
@canvas.width = width
|
522
|
+
@canvas.height = height
|
523
|
+
|
524
|
+
# Then, create an off-screen image with that same size for double
|
525
|
+
# buffering
|
526
|
+
@image.destroy
|
527
|
+
@image = FXBMPImage.new(@window.getApp, nil, IMAGE_SHMI|IMAGE_SHMP,
|
528
|
+
width, height)
|
529
|
+
@image.create
|
530
|
+
update_title
|
531
|
+
end
|
532
|
+
|
533
|
+
end
|
534
|
+
|
535
|
+
# Given a mouse x/y position to WS/HS, return an index
|
536
|
+
# indicating what quadrant it belongs to.
|
537
|
+
def get_quadrant(ax, ay)
|
538
|
+
# First get relative x/y position
|
539
|
+
x = ax % WW
|
540
|
+
y = ay % HH
|
541
|
+
|
542
|
+
quadrant = nil
|
543
|
+
if x < WS_2
|
544
|
+
#left
|
545
|
+
if y < HS_2
|
546
|
+
# top
|
547
|
+
quadrant = 7
|
548
|
+
elsif y > H + HS_2
|
549
|
+
# bottom
|
550
|
+
quadrant = 5
|
551
|
+
else
|
552
|
+
# center
|
553
|
+
quadrant = 6
|
554
|
+
end
|
555
|
+
elsif x > W + WS_2
|
556
|
+
# right
|
557
|
+
if y < HS_2
|
558
|
+
# top
|
559
|
+
quadrant = 1
|
560
|
+
elsif y > H + HS_2
|
561
|
+
# bottom
|
562
|
+
quadrant = 3
|
563
|
+
else
|
564
|
+
# center
|
565
|
+
quadrant = 2
|
566
|
+
end
|
567
|
+
else
|
568
|
+
#center
|
569
|
+
if y < HS_2
|
570
|
+
# top
|
571
|
+
quadrant = 0
|
572
|
+
elsif y > H + HS_2
|
573
|
+
# bottom
|
574
|
+
quadrant = 4
|
575
|
+
else
|
576
|
+
# center
|
577
|
+
quadrant = nil
|
578
|
+
end
|
579
|
+
end
|
580
|
+
|
581
|
+
|
582
|
+
return quadrant
|
583
|
+
end
|
584
|
+
|
585
|
+
# Given an x,y absolute position corresponding to a connection,
|
586
|
+
# return connected rooms (if any).
|
587
|
+
def quadrant_to_rooms( q, x, y )
|
588
|
+
maxX = @width * WW
|
589
|
+
maxY = @height * HH
|
590
|
+
|
591
|
+
# First check if user tried adding a connection
|
592
|
+
# at the edges of the map. If so, return empty stuff.
|
593
|
+
if x < WS_2 or y < HS_2 or
|
594
|
+
x > maxX - WS_2 or y > maxY - HS_2
|
595
|
+
return [nil, nil, nil, nil]
|
596
|
+
end
|
597
|
+
|
598
|
+
x1 = x2 = x
|
599
|
+
y1 = y2 = y
|
600
|
+
|
601
|
+
case q
|
602
|
+
when 0, 4
|
603
|
+
y1 -= HS
|
604
|
+
y2 += HS
|
605
|
+
when 1, 5
|
606
|
+
x1 -= WS
|
607
|
+
x2 += WS
|
608
|
+
y1 += HS
|
609
|
+
y2 -= HS
|
610
|
+
when 2, 6
|
611
|
+
x1 -= WS
|
612
|
+
x2 += WS
|
613
|
+
when 3, 7
|
614
|
+
x1 -= WS
|
615
|
+
y1 -= HS
|
616
|
+
x2 += WS
|
617
|
+
y2 += HS
|
618
|
+
end
|
619
|
+
|
620
|
+
case q
|
621
|
+
when 0, 5, 6, 7
|
622
|
+
x1, x2 = x2, x1
|
623
|
+
y1, y2 = y2, y1
|
624
|
+
end
|
625
|
+
|
626
|
+
roomA = to_room(x1, y1)
|
627
|
+
roomB = to_room(x2, y2)
|
628
|
+
# Oops, user tried to create rooms where we already
|
629
|
+
# have a complex connection. Don't create anything, then.
|
630
|
+
if roomA.kind_of?(Connection) or roomB.kind_of?(Connection)
|
631
|
+
return [roomA, roomB, nil, nil]
|
632
|
+
end
|
633
|
+
|
634
|
+
return [roomA, roomB, [x1, y1], [x2, y2]]
|
635
|
+
end
|
636
|
+
|
637
|
+
#
|
638
|
+
# Add a new complex connection (or its first point)
|
639
|
+
#
|
640
|
+
def new_complex_connection( x, y )
|
641
|
+
exitA = get_quadrant(x, y)
|
642
|
+
unless exitA
|
643
|
+
raise "not a connection"
|
644
|
+
end
|
645
|
+
roomA, roomB, a, b = quadrant_to_rooms( exitA, x, y )
|
646
|
+
return unless a and roomA # User click outside map or not near room
|
647
|
+
|
648
|
+
if @complexConnection.kind_of?(TrueClass)
|
649
|
+
@complexConnection = [roomA, exitA]
|
650
|
+
c = new_connection( roomA, exitA, nil )
|
651
|
+
status "Click on other room exit in complex connection."
|
652
|
+
else
|
653
|
+
@pages[@page].delete_connection_at(-1)
|
654
|
+
c = new_connection( @complexConnection[0],
|
655
|
+
@complexConnection[1], roomA, exitA )
|
656
|
+
if path_find(c) # Do A* path finding to connect both exits
|
657
|
+
@modified = true
|
658
|
+
status "Complex connection done."
|
659
|
+
else
|
660
|
+
@pages[@page].delete_connection_at(-1)
|
661
|
+
end
|
662
|
+
draw
|
663
|
+
@complexConnection = nil
|
664
|
+
end
|
665
|
+
end
|
666
|
+
|
667
|
+
#
|
668
|
+
# Add a new room connection among contiguous rooms.
|
669
|
+
#
|
670
|
+
def new_xy_connection( x, y )
|
671
|
+
exitA = get_quadrant(x, y)
|
672
|
+
unless exitA
|
673
|
+
raise "not a connection"
|
674
|
+
end
|
675
|
+
|
676
|
+
# Then, get rooms being connected
|
677
|
+
roomA, roomB, a, b = quadrant_to_rooms( exitA, x, y )
|
678
|
+
return unless a # User click outside map
|
679
|
+
|
680
|
+
if @options['Create on Connection']
|
681
|
+
unless roomA
|
682
|
+
roomA = new_xy_room( a[0], a[1] )
|
683
|
+
end
|
684
|
+
|
685
|
+
unless roomB
|
686
|
+
roomB = new_xy_room( b[0], b[1] )
|
687
|
+
end
|
688
|
+
end
|
689
|
+
|
690
|
+
return nil unless roomA and roomB
|
691
|
+
|
692
|
+
if roomA == roomB
|
693
|
+
raise "error: same room connection"
|
694
|
+
end
|
695
|
+
|
696
|
+
@modified = true
|
697
|
+
if roomA and exitA
|
698
|
+
# get old connection
|
699
|
+
if roomA[exitA]
|
700
|
+
c = roomA[exitA]
|
701
|
+
delete_connection(c) if c.roomB == nil
|
702
|
+
end
|
703
|
+
exitB = (exitA + 4) % 8
|
704
|
+
if roomB[exitB]
|
705
|
+
c = roomB[exitB]
|
706
|
+
delete_connection(c) if c.roomB == nil
|
707
|
+
end
|
708
|
+
end
|
709
|
+
|
710
|
+
new_connection( roomA, exitA, roomB )
|
711
|
+
end
|
712
|
+
|
713
|
+
#
|
714
|
+
# Handle mouse button double clicks in canvas
|
715
|
+
#
|
716
|
+
def double_click_cb(selection, event)
|
717
|
+
return unless selection
|
718
|
+
if selection.kind_of?(FXRoom) or selection.kind_of?(FXConnection)
|
719
|
+
selection.properties( self, event )
|
720
|
+
end
|
721
|
+
end
|
722
|
+
|
723
|
+
ZOOM_SEQUENCE = [
|
724
|
+
0.4,
|
725
|
+
0.5,
|
726
|
+
0.6,
|
727
|
+
0.7,
|
728
|
+
0.8,
|
729
|
+
0.9,
|
730
|
+
1.0,
|
731
|
+
1.1,
|
732
|
+
1.2,
|
733
|
+
]
|
734
|
+
|
735
|
+
# Self-explanatory.
|
736
|
+
def zoom_out
|
737
|
+
if @zoom > 0.25
|
738
|
+
self.zoom -= 0.1
|
739
|
+
end
|
740
|
+
end
|
741
|
+
|
742
|
+
# Self-explanatory.
|
743
|
+
def zoom_in
|
744
|
+
if @zoom < 1.25
|
745
|
+
self.zoom += 0.1
|
746
|
+
end
|
747
|
+
end
|
748
|
+
|
749
|
+
# Spit out a new message to the status line.
|
750
|
+
def status(msg)
|
751
|
+
mw = @window.parent.parent
|
752
|
+
statusbar = mw.children.find() { |x| x.kind_of?(FXStatusBar) }
|
753
|
+
s = statusbar.statusLine
|
754
|
+
s.normalText = s.text = msg
|
755
|
+
end
|
756
|
+
|
757
|
+
#
|
758
|
+
# Show some help status in status line based on cursor position
|
759
|
+
#
|
760
|
+
def help_cb(sender, sel, event)
|
761
|
+
return if @complexConnection
|
762
|
+
x = (event.last_x / @zoom).to_i
|
763
|
+
y = (event.last_y / @zoom).to_i
|
764
|
+
|
765
|
+
sel = to_object(x, y)
|
766
|
+
if sel
|
767
|
+
@canvas.defaultCursor = @@cursor_arrow
|
768
|
+
if sel.kind_of?(Room)
|
769
|
+
status "Click to select and move. Double click to edit room."
|
770
|
+
elsif sel.kind_of?(Connection)
|
771
|
+
status "Click to change direction of connection."
|
772
|
+
end
|
773
|
+
else
|
774
|
+
if click_type(x, y)
|
775
|
+
@canvas.defaultCursor = @@cursor_cross
|
776
|
+
status "Click to create new room."
|
777
|
+
else
|
778
|
+
q = get_quadrant(x, y)
|
779
|
+
if q == 0 or q == 4
|
780
|
+
@canvas.defaultCursor = @@cursor_updown
|
781
|
+
elsif q == 2 or q == 6
|
782
|
+
@canvas.defaultCursor = @@cursor_leftright
|
783
|
+
else
|
784
|
+
@canvas.defaultCursor = @@cursor_arrow
|
785
|
+
end
|
786
|
+
status "Click to create new connection."
|
787
|
+
end
|
788
|
+
end
|
789
|
+
end
|
790
|
+
|
791
|
+
#
|
792
|
+
# zoom in/out based on mousewheel, keeping position relative
|
793
|
+
# to cursor position
|
794
|
+
#
|
795
|
+
def mousewheel_cb(sender, sel, event)
|
796
|
+
pos = @scrollwindow.position
|
797
|
+
pos = [ pos[0] / @zoom, pos[1] / @zoom ]
|
798
|
+
x = (event.last_x / @zoom).to_i
|
799
|
+
y = (event.last_y / @zoom).to_i
|
800
|
+
case event.code
|
801
|
+
when -120
|
802
|
+
zoom_out
|
803
|
+
# pos[0] -= x / 2
|
804
|
+
# pos[1] -= y / 2
|
805
|
+
pos = [ pos[0] * @zoom, pos[1] * @zoom ]
|
806
|
+
@scrollwindow.setPosition(pos[0], pos[1])
|
807
|
+
when 120
|
808
|
+
zoom_in
|
809
|
+
|
810
|
+
#pos[0] = -x # * @zoom * 2
|
811
|
+
#pos[1] = -y # * @zoom * 2
|
812
|
+
|
813
|
+
# pos = [ pos[0], pos[1] ]
|
814
|
+
# pos = [ pos[0] * @zoom, pos[1] * @zoom ]
|
815
|
+
@scrollwindow.setPosition(pos[0], pos[1])
|
816
|
+
end
|
817
|
+
# x = (x * @zoom).to_i
|
818
|
+
# y = (y * @zoom).to_i
|
819
|
+
# if x > @canvas.width
|
820
|
+
# x = @canvas.width
|
821
|
+
# end
|
822
|
+
# if y > @canvas.height
|
823
|
+
# y = @canvas.height
|
824
|
+
# end
|
825
|
+
|
826
|
+
# pos = [ pos[0] - x, pos[1] - y ]
|
827
|
+
# p "SET: #{pos.join(',')}"
|
828
|
+
|
829
|
+
# # @scrollwindow.setPosition(pos[0], pos[1])
|
830
|
+
# p @scrollwindow.position
|
831
|
+
draw
|
832
|
+
end
|
833
|
+
|
834
|
+
#
|
835
|
+
# Handle middle mouse button click in canvas
|
836
|
+
#
|
837
|
+
def mmb_click_cb(server, sel, event)
|
838
|
+
@canvas.grab
|
839
|
+
@dx = @dy = 0
|
840
|
+
@mouseButton = MIDDLEBUTTON
|
841
|
+
end
|
842
|
+
|
843
|
+
#
|
844
|
+
# Select all rooms within (drag) rectangle
|
845
|
+
# TODO: Should add connections too
|
846
|
+
#
|
847
|
+
def select_rectangle(x1, y1, x2, y2)
|
848
|
+
x1, x2 = x2, x1 if x2 < x1
|
849
|
+
y1, y2 = y2, y1 if y2 < y1
|
850
|
+
|
851
|
+
x = x1 * zoom
|
852
|
+
y = y1 * zoom
|
853
|
+
w = x2 - x1
|
854
|
+
h = y2 - y1
|
855
|
+
|
856
|
+
x1 = (x1 / WW).floor
|
857
|
+
y1 = (y1 / HH).floor
|
858
|
+
x2 = (x2 / WW).ceil
|
859
|
+
y2 = (y2 / HH).ceil
|
860
|
+
|
861
|
+
@pages[@page].rooms.each { |r|
|
862
|
+
if r.x >= x1 and r.x <= x2 and
|
863
|
+
r.y >= y1 and r.y <= y2
|
864
|
+
r.selected = true
|
865
|
+
else
|
866
|
+
r.selected = false
|
867
|
+
end
|
868
|
+
}
|
869
|
+
draw
|
870
|
+
dc = FXDCWindow.new(@canvas)
|
871
|
+
dc.drawRectangle(x, y, w * @zoom, h * @zoom)
|
872
|
+
dc.end
|
873
|
+
end
|
874
|
+
|
875
|
+
#
|
876
|
+
# Handle mouse motion in canvas
|
877
|
+
#
|
878
|
+
def motion_cb(server, sel, event)
|
879
|
+
if @mouseButton == MIDDLEBUTTON
|
880
|
+
@canvas.dragCursor = @@cursor_move
|
881
|
+
pos = @scrollwindow.position
|
882
|
+
dx = event.last_x - event.win_x
|
883
|
+
dy = event.last_y - event.win_y
|
884
|
+
if dx != 0 or dy != 0 and not (dx == @dx and dy == @dy)
|
885
|
+
pos[0] += dx
|
886
|
+
pos[1] += dy
|
887
|
+
@dx = dx
|
888
|
+
@dy = dy
|
889
|
+
@scrollwindow.setPosition(pos[0], pos[1])
|
890
|
+
end
|
891
|
+
elsif @mouseButton == LEFTBUTTON
|
892
|
+
x = (event.last_x / @zoom).to_i
|
893
|
+
y = (event.last_y / @zoom).to_i
|
894
|
+
select_rectangle( @dx, @dy, x, y )
|
895
|
+
else
|
896
|
+
help_cb(server, sel, event)
|
897
|
+
end
|
898
|
+
end
|
899
|
+
|
900
|
+
#
|
901
|
+
# Handle release of middle mouse button
|
902
|
+
#
|
903
|
+
def mmb_release_cb(server, sel, event)
|
904
|
+
if @mouseButton
|
905
|
+
@canvas.ungrab
|
906
|
+
@mouseButton = nil
|
907
|
+
@canvas.dragCursor = @@cursor_arrow
|
908
|
+
draw
|
909
|
+
end
|
910
|
+
end
|
911
|
+
|
912
|
+
#
|
913
|
+
# Clear rooms/connections selected
|
914
|
+
#
|
915
|
+
def clear_selection
|
916
|
+
@pages[@page].rooms.each { |r| r.selected = false }
|
917
|
+
@pages[@page].connections.each { |r| r.selected = false }
|
918
|
+
end
|
919
|
+
|
920
|
+
#
|
921
|
+
# Handle left mouse button click
|
922
|
+
#
|
923
|
+
def lmb_click_cb(sender, sel, event)
|
924
|
+
x = (event.last_x / @zoom).to_i
|
925
|
+
y = (event.last_y / @zoom).to_i
|
926
|
+
|
927
|
+
if event.state & ALTMASK != 0
|
928
|
+
mmb_click_cb(sender, sel, event)
|
929
|
+
return
|
930
|
+
end
|
931
|
+
|
932
|
+
selection = to_object(x, y)
|
933
|
+
|
934
|
+
if event.state & SHIFTMASK != 0
|
935
|
+
@mouseButton = LEFTBUTTON
|
936
|
+
@canvas.grab
|
937
|
+
@dx, @dy = [ x, y ]
|
938
|
+
return if not selection
|
939
|
+
end
|
940
|
+
|
941
|
+
|
942
|
+
if event.click_count == 2
|
943
|
+
double_click_cb(selection, event)
|
944
|
+
return
|
945
|
+
end
|
946
|
+
|
947
|
+
unless selection
|
948
|
+
clear_selection
|
949
|
+
|
950
|
+
# If in navigation mode, we don't allow user to modify map.
|
951
|
+
return if @navigation
|
952
|
+
|
953
|
+
# if we did not select anything, check to see if we
|
954
|
+
# clicked in a room area or connection area.
|
955
|
+
if click_type(x, y)
|
956
|
+
return if @complexConnection
|
957
|
+
# Add a new room
|
958
|
+
roomB = @pages[@page].rooms[-1]
|
959
|
+
roomA = new_xy_room( x, y )
|
960
|
+
if roomB and roomA and @options['Automatic Connection']
|
961
|
+
# check to see if rooms are next to each other
|
962
|
+
# if so, try to connect them (assuming there's no connection there
|
963
|
+
# already).
|
964
|
+
exitB = roomB.next_to?(roomA)
|
965
|
+
if exitB and roomB.exits[exitB] == nil
|
966
|
+
new_connection( roomB, exitB, roomA )
|
967
|
+
end
|
968
|
+
end
|
969
|
+
else
|
970
|
+
# Add a new connection
|
971
|
+
if @complexConnection
|
972
|
+
new_complex_connection(x, y)
|
973
|
+
else
|
974
|
+
# Add a new simple connection (plus rooms if needed)
|
975
|
+
if event.state & CONTROLMASK != 0
|
976
|
+
exitA = get_quadrant(x, y)
|
977
|
+
roomA, roomB, a, b = quadrant_to_rooms( exitA, x, y )
|
978
|
+
if not roomA
|
979
|
+
new_xy_connection( x, y )
|
980
|
+
else
|
981
|
+
@complexConnection = [roomA, exitA]
|
982
|
+
new_connection( roomA, exitA, nil )
|
983
|
+
new_complex_connection( x, y )
|
984
|
+
end
|
985
|
+
else
|
986
|
+
new_xy_connection( x, y )
|
987
|
+
end
|
988
|
+
end
|
989
|
+
end
|
990
|
+
else
|
991
|
+
if selection.kind_of?(Connection) and selection.selected
|
992
|
+
# Toggle arrow direction
|
993
|
+
selection.toggle_direction
|
994
|
+
draw
|
995
|
+
return
|
996
|
+
else
|
997
|
+
if event.state & SHIFTMASK == 0 and
|
998
|
+
event.state & CONTROLMASK == 0
|
999
|
+
clear_selection
|
1000
|
+
end
|
1001
|
+
# Select the stuff
|
1002
|
+
selection.selected = true
|
1003
|
+
end
|
1004
|
+
end
|
1005
|
+
draw(event)
|
1006
|
+
end
|
1007
|
+
|
1008
|
+
|
1009
|
+
def close_cb()
|
1010
|
+
if @modified
|
1011
|
+
dlg = FXDialogBox.new( @window.parent, "Warning",
|
1012
|
+
DECOR_ALL,
|
1013
|
+
0, 0, 400, 130)
|
1014
|
+
# Frame
|
1015
|
+
s = FXVerticalFrame.new(dlg,
|
1016
|
+
LAYOUT_SIDE_TOP|LAYOUT_FILL_X)
|
1017
|
+
|
1018
|
+
f = FXHorizontalFrame.new(s, LAYOUT_SIDE_TOP|LAYOUT_FILL_X|LAYOUT_FILL_Y)
|
1019
|
+
|
1020
|
+
font = FXFont.new(@window.getApp, "Helvetica", 30)
|
1021
|
+
font.create
|
1022
|
+
oops = FXLabel.new(f, "!", nil, 0, LAYOUT_SIDE_LEFT|LAYOUT_FILL_X|
|
1023
|
+
LAYOUT_CENTER_Y)
|
1024
|
+
oops.frameStyle = FRAME_RAISED|FRAME_THICK
|
1025
|
+
oops.baseColor = 'dark grey'
|
1026
|
+
oops.textColor = 'red'
|
1027
|
+
oops.padLeft = oops.padRight = 15
|
1028
|
+
oops.shadowColor = 'black'
|
1029
|
+
oops.borderColor = 'white'
|
1030
|
+
oops.font = font
|
1031
|
+
|
1032
|
+
FXLabel.new(f, "\n#{@name} was modified.\n" +
|
1033
|
+
"Should I save the changes before closing?",
|
1034
|
+
nil, 0)
|
1035
|
+
|
1036
|
+
# Separator
|
1037
|
+
FXHorizontalSeparator.new(s,
|
1038
|
+
LAYOUT_SIDE_TOP|LAYOUT_FILL_X|SEPARATOR_GROOVE)
|
1039
|
+
|
1040
|
+
# Bottom buttons
|
1041
|
+
buttons = FXHorizontalFrame.new(s,
|
1042
|
+
LAYOUT_SIDE_BOTTOM|FRAME_NONE|
|
1043
|
+
LAYOUT_FILL_X|PACK_UNIFORM_WIDTH)
|
1044
|
+
# Accept
|
1045
|
+
yes = FXButton.new(buttons, "&Yes", nil, dlg, FXDialogBox::ID_ACCEPT,
|
1046
|
+
FRAME_RAISED|FRAME_THICK|LAYOUT_FILL_X|
|
1047
|
+
LAYOUT_RIGHT|LAYOUT_CENTER_Y)
|
1048
|
+
yes.connect(SEL_COMMAND) {
|
1049
|
+
dlg.close
|
1050
|
+
if save
|
1051
|
+
@window.close
|
1052
|
+
return true
|
1053
|
+
else
|
1054
|
+
return false
|
1055
|
+
end
|
1056
|
+
}
|
1057
|
+
FXButton.new(buttons, "&No", nil, dlg, FXDialogBox::ID_ACCEPT,
|
1058
|
+
FRAME_RAISED|FRAME_THICK|LAYOUT_FILL_X|LAYOUT_RIGHT|LAYOUT_CENTER_Y)
|
1059
|
+
|
1060
|
+
# Cancel
|
1061
|
+
FXButton.new(buttons, "&Cancel", nil, dlg, FXDialogBox::ID_CANCEL,
|
1062
|
+
FRAME_RAISED|FRAME_THICK|LAYOUT_FILL_X|LAYOUT_RIGHT|LAYOUT_CENTER_Y)
|
1063
|
+
yes.setDefault
|
1064
|
+
yes.setFocus
|
1065
|
+
|
1066
|
+
return false if dlg.execute == 0
|
1067
|
+
end
|
1068
|
+
@window.close
|
1069
|
+
return true
|
1070
|
+
end
|
1071
|
+
|
1072
|
+
|
1073
|
+
def _make_cursors
|
1074
|
+
@@cursor_move = FXCursor.new(@window.getApp, CURSOR_MOVE)
|
1075
|
+
@@cursor_move.create
|
1076
|
+
|
1077
|
+
@@cursor_arrow = FXCursor.new(@window.getApp, CURSOR_ARROW)
|
1078
|
+
@@cursor_arrow.create
|
1079
|
+
|
1080
|
+
@@cursor_cross = FXCursor.new(@window.getApp, CURSOR_CROSS)
|
1081
|
+
@@cursor_cross.create
|
1082
|
+
|
1083
|
+
@@cursor_updown = FXCursor.new(@window.getApp, CURSOR_UPDOWN)
|
1084
|
+
@@cursor_updown.create
|
1085
|
+
|
1086
|
+
@@cursor_leftright = FXCursor.new(@window.getApp, CURSOR_LEFTRIGHT)
|
1087
|
+
@@cursor_leftright.create
|
1088
|
+
end
|
1089
|
+
|
1090
|
+
def _make_widgets
|
1091
|
+
@scrollwindow = FXScrollWindow.new(@window,
|
1092
|
+
SCROLLERS_NORMAL|SCROLLERS_TRACK)
|
1093
|
+
width = WW * @width
|
1094
|
+
height = HH * @height
|
1095
|
+
|
1096
|
+
@canvasFrame = FXVerticalFrame.new(@scrollwindow,
|
1097
|
+
FRAME_SUNKEN|LAYOUT_FILL_X|
|
1098
|
+
LAYOUT_FILL_Y|LAYOUT_TOP|LAYOUT_LEFT)
|
1099
|
+
|
1100
|
+
# First, create an off-screen image for drawing and double buffering
|
1101
|
+
@image = FXBMPImage.new(@window.getApp, nil, IMAGE_SHMI|IMAGE_SHMP,
|
1102
|
+
width, height)
|
1103
|
+
@image.create
|
1104
|
+
|
1105
|
+
# Then create canvas
|
1106
|
+
@canvas = FXCanvas.new(@canvasFrame, nil, 0,
|
1107
|
+
LAYOUT_FIX_WIDTH|LAYOUT_FIX_HEIGHT|
|
1108
|
+
LAYOUT_TOP|LAYOUT_LEFT,
|
1109
|
+
0, 0, width, height)
|
1110
|
+
|
1111
|
+
@canvas.connect(SEL_PAINT) do |sender, sel, event|
|
1112
|
+
draw(event)
|
1113
|
+
end
|
1114
|
+
@canvas.backColor = @options['BG Color']
|
1115
|
+
|
1116
|
+
@canvas.connect(SEL_MOUSEWHEEL, method(:mousewheel_cb))
|
1117
|
+
@canvas.connect(SEL_LEFTBUTTONPRESS, method(:lmb_click_cb))
|
1118
|
+
@canvas.connect(SEL_LEFTBUTTONRELEASE, method(:mmb_release_cb))
|
1119
|
+
@canvas.connect(SEL_MOTION, method(:motion_cb))
|
1120
|
+
@canvas.connect(SEL_MIDDLEBUTTONPRESS, method(:mmb_click_cb))
|
1121
|
+
@canvas.connect(SEL_MIDDLEBUTTONRELEASE, method(:mmb_release_cb))
|
1122
|
+
@canvas.connect(SEL_KEYPRESS, method(:keypress_cb))
|
1123
|
+
end
|
1124
|
+
|
1125
|
+
def initialize(name, parent = nil, default_options = nil,
|
1126
|
+
icon = nil, menu = nil, mode = nil,
|
1127
|
+
x = 0, y = 0, w = 0, h = 0)
|
1128
|
+
super(name)
|
1129
|
+
@navigation = false
|
1130
|
+
if parent
|
1131
|
+
@window = FXMDIChild.new(parent, name, icon, menu, mode, x, y, w, h)
|
1132
|
+
@options = default_options
|
1133
|
+
_make_cursors if not @@cursor_arrow
|
1134
|
+
_make_widgets
|
1135
|
+
end
|
1136
|
+
empty_pathmap
|
1137
|
+
self.zoom = 1
|
1138
|
+
end
|
1139
|
+
|
1140
|
+
|
1141
|
+
#
|
1142
|
+
# Handle deleting selected rooms and/or connections
|
1143
|
+
#
|
1144
|
+
def delete_selected
|
1145
|
+
rooms = @pages[@page].rooms.find_all { |r| r.selected }
|
1146
|
+
conns = @pages[@page].connections.find_all { |c| c.selected }
|
1147
|
+
############################
|
1148
|
+
# First, handle rooms...
|
1149
|
+
############################
|
1150
|
+
# Remove rooms from path map
|
1151
|
+
rooms.each { |r| @pmap[r.x][r.y] = nil }
|
1152
|
+
# Remove rooms from current page in map
|
1153
|
+
@pages[@page].rooms -= rooms
|
1154
|
+
# Add any connections pointing to removed rooms as connection to remove
|
1155
|
+
rooms.each { |r|
|
1156
|
+
conns += r.exits.find_all { |e| e != nil }
|
1157
|
+
}
|
1158
|
+
|
1159
|
+
#########################
|
1160
|
+
# Now, handle connections
|
1161
|
+
#########################
|
1162
|
+
conns.uniq!
|
1163
|
+
|
1164
|
+
# Remove connections from path map
|
1165
|
+
conns.each { |c| clean_path(c) }
|
1166
|
+
# Remove connections from current page in map
|
1167
|
+
@pages[@page].connections -= conns
|
1168
|
+
# Remove room exits pointing to any removed connection
|
1169
|
+
conns.each { |c|
|
1170
|
+
a = c.roomA
|
1171
|
+
b = c.roomB
|
1172
|
+
a[a.exits.index(c)] = nil
|
1173
|
+
idx = b.exits.rindex(c)
|
1174
|
+
b[idx] = nil if idx
|
1175
|
+
}
|
1176
|
+
|
1177
|
+
# Recreate pathmap. We need to recreate pathmap for all paths
|
1178
|
+
# as the removal of one path may help shorten the path of another.
|
1179
|
+
create_pathmap
|
1180
|
+
|
1181
|
+
draw()
|
1182
|
+
end
|
1183
|
+
|
1184
|
+
#
|
1185
|
+
# Start a complex connection
|
1186
|
+
#
|
1187
|
+
def complex_connection
|
1188
|
+
return if @complexConnection or @navigation
|
1189
|
+
@complexConnection = true
|
1190
|
+
status "Click on first room exit in complex connection."
|
1191
|
+
end
|
1192
|
+
|
1193
|
+
#
|
1194
|
+
# Given a selection of rooms, clear all of them from path map
|
1195
|
+
#
|
1196
|
+
def clean_room_selection(selection)
|
1197
|
+
selection.each { |r|
|
1198
|
+
@pmap[r.x][r.y] = nil
|
1199
|
+
clean_exits(r)
|
1200
|
+
}
|
1201
|
+
end
|
1202
|
+
|
1203
|
+
#
|
1204
|
+
# Given a selection of rooms, clear all of them from path map
|
1205
|
+
#
|
1206
|
+
def store_room_selection(selection)
|
1207
|
+
selection.each { |r|
|
1208
|
+
@pmap[r.x][r.y] = r
|
1209
|
+
}
|
1210
|
+
update_exits(selection)
|
1211
|
+
end
|
1212
|
+
|
1213
|
+
#
|
1214
|
+
# Clean all paths from path map for a room
|
1215
|
+
#
|
1216
|
+
def clean_exits(room)
|
1217
|
+
room.exits.each { |c|
|
1218
|
+
next if not c
|
1219
|
+
clean_path(c)
|
1220
|
+
}
|
1221
|
+
end
|
1222
|
+
|
1223
|
+
|
1224
|
+
#
|
1225
|
+
# Find and update all paths in path map for a room
|
1226
|
+
#
|
1227
|
+
def update_exits(selection)
|
1228
|
+
# We have to path_find all connections in the same order
|
1229
|
+
# as they are stored in map, and not in how they are in the room
|
1230
|
+
# so that there is consistency in path finding
|
1231
|
+
# conns = []
|
1232
|
+
# selection.each { |room|
|
1233
|
+
# room.exits.each { |c|
|
1234
|
+
# conns << c
|
1235
|
+
# }
|
1236
|
+
# }
|
1237
|
+
|
1238
|
+
# @pages[@page].connections.each { |c|
|
1239
|
+
# next if not conns.include?(c)
|
1240
|
+
# path_find(c)
|
1241
|
+
# }
|
1242
|
+
create_pathmap
|
1243
|
+
@modified = true
|
1244
|
+
end
|
1245
|
+
|
1246
|
+
#
|
1247
|
+
# Handle a keypress
|
1248
|
+
#
|
1249
|
+
def keypress_cb( server, sel, event)
|
1250
|
+
case event.code
|
1251
|
+
when KEY_Escape
|
1252
|
+
if @complexConnection
|
1253
|
+
if @complexConnection.kind_of?(Array)
|
1254
|
+
@pages[@page].delete_connection_at(-1)
|
1255
|
+
status "Complex connection aborted."
|
1256
|
+
draw
|
1257
|
+
end
|
1258
|
+
@complexConnection = false
|
1259
|
+
end
|
1260
|
+
when KEY_BackSpace, KEY_Delete
|
1261
|
+
return if @navigation
|
1262
|
+
delete_selected
|
1263
|
+
when KEY_c
|
1264
|
+
if event.state & CONTROLMASK != 0
|
1265
|
+
FXMapperWindow::copy_selected(self)
|
1266
|
+
draw
|
1267
|
+
end
|
1268
|
+
when KEY_v
|
1269
|
+
if event.state & CONTROLMASK != 0
|
1270
|
+
FXMapperWindow::paste_selected(self)
|
1271
|
+
@modified = true
|
1272
|
+
draw
|
1273
|
+
end
|
1274
|
+
when KEY_x
|
1275
|
+
return if @navigation
|
1276
|
+
if event.state & CONTROLMASK != 0
|
1277
|
+
FXMapperWindow::cut_selected(self)
|
1278
|
+
@modified = true
|
1279
|
+
draw
|
1280
|
+
else
|
1281
|
+
complex_connection
|
1282
|
+
end
|
1283
|
+
when KEY_Up
|
1284
|
+
return if @navigation
|
1285
|
+
selection = @pages[@page].rooms.find_all { |r| r.selected }
|
1286
|
+
return if selection.empty?
|
1287
|
+
clean_room_selection(selection)
|
1288
|
+
# Check that all nodes can be moved up
|
1289
|
+
selection.each { |r|
|
1290
|
+
n = @pmap[r.x][r.y-1]
|
1291
|
+
if r.y == 0 or n.kind_of?(Room)
|
1292
|
+
store_room_selection(selection)
|
1293
|
+
status "Cannot move selection up."
|
1294
|
+
return
|
1295
|
+
end
|
1296
|
+
}
|
1297
|
+
selection.each { |r|
|
1298
|
+
r.y -= 1
|
1299
|
+
@pmap[r.x][r.y] = r
|
1300
|
+
}
|
1301
|
+
update_exits(selection)
|
1302
|
+
draw
|
1303
|
+
when KEY_Down
|
1304
|
+
return if @navigation
|
1305
|
+
selection = @pages[@page].rooms.find_all { |r| r.selected }
|
1306
|
+
return if selection.empty?
|
1307
|
+
clean_room_selection(selection)
|
1308
|
+
# Check that all nodes can be moved up
|
1309
|
+
selection.each { |r|
|
1310
|
+
n = @pmap[r.x][r.y+1]
|
1311
|
+
if r.y+1 == @height or n.kind_of?(Room)
|
1312
|
+
store_room_selection(selection)
|
1313
|
+
status "Cannot move selection down."
|
1314
|
+
return
|
1315
|
+
end
|
1316
|
+
}
|
1317
|
+
selection.each { |r|
|
1318
|
+
r.y += 1
|
1319
|
+
@pmap[r.x][r.y] = r
|
1320
|
+
}
|
1321
|
+
update_exits(selection)
|
1322
|
+
draw
|
1323
|
+
when KEY_Left
|
1324
|
+
return if @navigation
|
1325
|
+
selection = @pages[@page].rooms.find_all { |r| r.selected }
|
1326
|
+
return if selection.empty?
|
1327
|
+
# Check that all nodes can be moved up
|
1328
|
+
clean_room_selection(selection)
|
1329
|
+
selection.each { |r|
|
1330
|
+
n = @pmap[r.x-1][r.y]
|
1331
|
+
if r.x == 0 or n.kind_of?(Room)
|
1332
|
+
store_room_selection(selection)
|
1333
|
+
status "Cannot move selection left."
|
1334
|
+
return
|
1335
|
+
end
|
1336
|
+
}
|
1337
|
+
selection.each { |r|
|
1338
|
+
r.x -= 1
|
1339
|
+
@pmap[r.x][r.y] = r
|
1340
|
+
}
|
1341
|
+
update_exits(selection)
|
1342
|
+
draw
|
1343
|
+
when KEY_Right
|
1344
|
+
return if @navigation
|
1345
|
+
selection = @pages[@page].rooms.find_all { |r| r.selected }
|
1346
|
+
return if selection.empty?
|
1347
|
+
# Check that all nodes can be moved up
|
1348
|
+
clean_room_selection(selection)
|
1349
|
+
selection.each { |r|
|
1350
|
+
n = @pmap[r.x+1][r.y]
|
1351
|
+
if r.x+1 == @width or n.kind_of?(Room)
|
1352
|
+
store_room_selection(selection)
|
1353
|
+
status "Cannot move selection right."
|
1354
|
+
return
|
1355
|
+
end
|
1356
|
+
}
|
1357
|
+
selection.each { |r|
|
1358
|
+
r.x += 1
|
1359
|
+
@pmap[r.x][r.y] = r
|
1360
|
+
}
|
1361
|
+
update_exits(selection)
|
1362
|
+
draw
|
1363
|
+
end
|
1364
|
+
end
|
1365
|
+
|
1366
|
+
#
|
1367
|
+
# Draw template of diagonal connections in grid background
|
1368
|
+
#
|
1369
|
+
def draw_diagonal_connections(dc, event)
|
1370
|
+
ww = WW * @zoom
|
1371
|
+
hh = HH * @zoom
|
1372
|
+
|
1373
|
+
w = W * @zoom
|
1374
|
+
h = H * @zoom
|
1375
|
+
|
1376
|
+
ws = WS * @zoom
|
1377
|
+
hs = HS * @zoom
|
1378
|
+
|
1379
|
+
ws_2 = WS_2 * @zoom
|
1380
|
+
hs_2 = HS_2 * @zoom
|
1381
|
+
|
1382
|
+
maxy = @height - 1
|
1383
|
+
maxx = @width - 1
|
1384
|
+
|
1385
|
+
(0...@height).each { |yy|
|
1386
|
+
(0...@width).each { |xx|
|
1387
|
+
next if @pmap[xx][yy].kind_of?(Connection)
|
1388
|
+
x = xx * ww
|
1389
|
+
y = yy * hh
|
1390
|
+
|
1391
|
+
if yy < maxy and xx < maxx
|
1392
|
+
# First, draw \
|
1393
|
+
x1 = x + w + ws_2
|
1394
|
+
y1 = y + h + hs_2
|
1395
|
+
|
1396
|
+
x2 = x1 + ws
|
1397
|
+
y2 = y1 + hs
|
1398
|
+
dc.drawLine( x1, y1, x2, y2 )
|
1399
|
+
|
1400
|
+
end
|
1401
|
+
|
1402
|
+
if yy < maxy and xx > 0 and xx <= maxx
|
1403
|
+
# Then, draw /
|
1404
|
+
x1 = x + ws_2
|
1405
|
+
y1 = y + h + hs_2
|
1406
|
+
|
1407
|
+
x2 = x1 - ws
|
1408
|
+
y2 = y1 + hs
|
1409
|
+
dc.drawLine( x2, y2, x1, y1 )
|
1410
|
+
end
|
1411
|
+
}
|
1412
|
+
}
|
1413
|
+
end
|
1414
|
+
|
1415
|
+
#
|
1416
|
+
# Draw template of straight connections in grid background
|
1417
|
+
#
|
1418
|
+
def draw_straight_connections(dc, event)
|
1419
|
+
ww = WW * @zoom
|
1420
|
+
hh = HH * @zoom
|
1421
|
+
|
1422
|
+
w = W * @zoom
|
1423
|
+
h = H * @zoom
|
1424
|
+
|
1425
|
+
ws_2 = WS_2 * @zoom
|
1426
|
+
hs_2 = HS_2 * @zoom
|
1427
|
+
|
1428
|
+
# First, draw horizontal lines
|
1429
|
+
(0...@height).each { |yy|
|
1430
|
+
(0..@width-2).each { |xx|
|
1431
|
+
next if @pmap[xx][yy].kind_of?(Connection) or
|
1432
|
+
@pmap[xx+1][yy].kind_of?(Connection)
|
1433
|
+
x1 = xx * ww + w + ws_2
|
1434
|
+
x2 = (xx + 1) * ww + ws_2
|
1435
|
+
y1 = yy * hh + h / 2 + hs_2
|
1436
|
+
|
1437
|
+
dc.drawLine( x1, y1, x2, y1 )
|
1438
|
+
}
|
1439
|
+
}
|
1440
|
+
|
1441
|
+
# Then, draw vertical lines
|
1442
|
+
(0...@width).each { |xx|
|
1443
|
+
(0..@height-2).each { |yy|
|
1444
|
+
next if @pmap[xx][yy].kind_of?(Connection) or
|
1445
|
+
@pmap[xx][yy+1].kind_of?(Connection)
|
1446
|
+
x1 = xx * ww + w / 2 + ws_2
|
1447
|
+
y1 = yy * hh + h + hs_2
|
1448
|
+
y2 = (yy + 1) * hh + hs_2
|
1449
|
+
|
1450
|
+
dc.drawLine( x1, y1, x1, y2 )
|
1451
|
+
}
|
1452
|
+
}
|
1453
|
+
end
|
1454
|
+
|
1455
|
+
|
1456
|
+
|
1457
|
+
#
|
1458
|
+
# Draw template of room squares in background
|
1459
|
+
#
|
1460
|
+
def draw_grid(dc, event)
|
1461
|
+
|
1462
|
+
dc.foreground = "black"
|
1463
|
+
dc.lineWidth = 0
|
1464
|
+
dc.lineStyle = LINE_ONOFF_DASH
|
1465
|
+
|
1466
|
+
ww = WW * @zoom
|
1467
|
+
hh = HH * @zoom
|
1468
|
+
|
1469
|
+
w = W * @zoom
|
1470
|
+
h = H * @zoom
|
1471
|
+
|
1472
|
+
ws_2 = WS_2 * @zoom
|
1473
|
+
hs_2 = HS_2 * @zoom
|
1474
|
+
|
1475
|
+
(0...@width).each { |xx|
|
1476
|
+
(0...@height).each { |yy|
|
1477
|
+
next if @pmap[xx][yy]
|
1478
|
+
x = xx * ww + ws_2
|
1479
|
+
y = yy * hh + hs_2
|
1480
|
+
dc.drawRectangle( x, y, w, h )
|
1481
|
+
}
|
1482
|
+
}
|
1483
|
+
end
|
1484
|
+
|
1485
|
+
#
|
1486
|
+
# Clean background to solid color
|
1487
|
+
#
|
1488
|
+
def draw_background(dc, event = nil)
|
1489
|
+
dc.foreground = @canvas.backColor
|
1490
|
+
|
1491
|
+
if event
|
1492
|
+
dc.fillRectangle(event.rect.x, event.rect.y, event.rect.w, event.rect.h)
|
1493
|
+
else
|
1494
|
+
dc.fillRectangle(0,0, @canvas.width, @canvas.height)
|
1495
|
+
end
|
1496
|
+
end
|
1497
|
+
|
1498
|
+
#
|
1499
|
+
# Draw connections among rooms
|
1500
|
+
#
|
1501
|
+
def draw_connections(dc)
|
1502
|
+
dc.lineStyle = LINE_SOLID
|
1503
|
+
dc.lineWidth = 3 * @zoom
|
1504
|
+
dc.lineWidth = 3 if dc.lineWidth < 3
|
1505
|
+
@pages[@page].connections.each { |c| c.draw(dc, @zoom, @options) }
|
1506
|
+
end
|
1507
|
+
|
1508
|
+
#
|
1509
|
+
# Draw rooms
|
1510
|
+
#
|
1511
|
+
def draw_rooms(dc)
|
1512
|
+
data = { }
|
1513
|
+
data['font'] = @font
|
1514
|
+
data['objfont'] = @objfont
|
1515
|
+
@pages[@page].rooms.each_with_index { |room, idx|
|
1516
|
+
room.draw(dc, @zoom, idx, @options, data)
|
1517
|
+
}
|
1518
|
+
end
|
1519
|
+
|
1520
|
+
#
|
1521
|
+
# Print map
|
1522
|
+
#
|
1523
|
+
def print(printer)
|
1524
|
+
end
|
1525
|
+
|
1526
|
+
#
|
1527
|
+
# Draw map
|
1528
|
+
#
|
1529
|
+
def draw(event = nil)
|
1530
|
+
pos = @scrollwindow.position
|
1531
|
+
w = @scrollwindow.getViewportWidth
|
1532
|
+
h = @scrollwindow.getViewportHeight
|
1533
|
+
|
1534
|
+
dc = FXDCWindow.new(@image)
|
1535
|
+
dc.setClipRectangle( -pos[0]-5, -pos[1]-5, w, h)
|
1536
|
+
dc.font = @font
|
1537
|
+
# dc.lineCap = CAP_ROUND
|
1538
|
+
draw_background(dc, event)
|
1539
|
+
draw_grid(dc, event) if @options['Grid Boxes']
|
1540
|
+
if @options['Grid Straight Connections']
|
1541
|
+
draw_straight_connections(dc, event)
|
1542
|
+
end
|
1543
|
+
if @options['Grid Diagonal Connections']
|
1544
|
+
draw_diagonal_connections(dc, event)
|
1545
|
+
end
|
1546
|
+
draw_connections(dc)
|
1547
|
+
draw_rooms(dc)
|
1548
|
+
dc.end
|
1549
|
+
|
1550
|
+
|
1551
|
+
|
1552
|
+
# Blit the off-screen image into canvas
|
1553
|
+
dc = FXDCWindow.new(@canvas)
|
1554
|
+
dc.setClipRectangle( -pos[0]-5, -pos[1]-5, w, h)
|
1555
|
+
dc.drawImage(@image,0,0)
|
1556
|
+
dc.end
|
1557
|
+
end
|
1558
|
+
|
1559
|
+
|
1560
|
+
|
1561
|
+
def _save
|
1562
|
+
if @complexConnection
|
1563
|
+
# If we have an incomplete connection, remove it
|
1564
|
+
@pages[@page].delete_connection_at(-1)
|
1565
|
+
end
|
1566
|
+
|
1567
|
+
if @filename !~ /\.map$/i
|
1568
|
+
@filename << '.map'
|
1569
|
+
end
|
1570
|
+
|
1571
|
+
status "Saving '#{@filename}'..."
|
1572
|
+
@version = FILE_FORMAT_VERSION
|
1573
|
+
begin
|
1574
|
+
f = File.open(@filename, "wb")
|
1575
|
+
f.puts Marshal.dump(self)
|
1576
|
+
f.close
|
1577
|
+
rescue => e
|
1578
|
+
status "Could not save '#{@filename}': #{e}"
|
1579
|
+
sleep 4
|
1580
|
+
return false
|
1581
|
+
end
|
1582
|
+
@modified = false
|
1583
|
+
status "Saved '#{@filename}'."
|
1584
|
+
sleep 0.5
|
1585
|
+
return true
|
1586
|
+
end
|
1587
|
+
|
1588
|
+
def save
|
1589
|
+
unless @filename
|
1590
|
+
save_as
|
1591
|
+
else
|
1592
|
+
_save
|
1593
|
+
end
|
1594
|
+
end
|
1595
|
+
|
1596
|
+
def save_as
|
1597
|
+
file = FXMapFileDialog.new(@window, "Save Map #{@name}").filename
|
1598
|
+
if file != ''
|
1599
|
+
@filename = file
|
1600
|
+
return _save
|
1601
|
+
end
|
1602
|
+
return false
|
1603
|
+
end
|
1604
|
+
|
1605
|
+
|
1606
|
+
def properties
|
1607
|
+
if not @@win
|
1608
|
+
@@win = FXMapDialogBox.new(@window)
|
1609
|
+
end
|
1610
|
+
@@win.copy_from(self)
|
1611
|
+
@@win.show
|
1612
|
+
end
|
1613
|
+
|
1614
|
+
end
|