wxruby-ruby19 1.9.8-x86-darwin-9 → 1.9.10-x86-darwin-9

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.
Files changed (220) hide show
  1. data/LICENSE +53 -0
  2. data/README +297 -0
  3. data/lib/wx.rb +2 -2
  4. data/lib/wx/accessors.rb +7 -1
  5. data/lib/wx/classes/app.rb +10 -4
  6. data/lib/wx/classes/bitmap.rb +29 -1
  7. data/lib/wx/classes/clipboard.rb +19 -3
  8. data/lib/wx/classes/colour.rb +6 -4
  9. data/lib/wx/classes/data_object.rb +14 -0
  10. data/lib/wx/classes/data_object_simple.rb +6 -0
  11. data/lib/wx/classes/dataformat.rb +23 -0
  12. data/lib/wx/classes/evthandler.rb +79 -4
  13. data/lib/wx/classes/genericdirctrl.rb +36 -0
  14. data/lib/wx/classes/grid.rb +8 -0
  15. data/lib/wx/classes/hboxsizer.rb +6 -0
  16. data/lib/wx/classes/icon.rb +12 -1
  17. data/lib/wx/classes/image.rb +13 -1
  18. data/lib/wx/classes/listctrl.rb +12 -0
  19. data/lib/wx/classes/point.rb +8 -0
  20. data/lib/wx/classes/rect.rb +10 -1
  21. data/lib/wx/classes/richtextctrl.rb +63 -0
  22. data/lib/wx/classes/size.rb +9 -0
  23. data/lib/wx/classes/sizer.rb +18 -3
  24. data/lib/wx/classes/standardpaths.rb +9 -0
  25. data/lib/wx/classes/texturlevent.rb +14 -2
  26. data/lib/wx/classes/toolbar.rb +4 -6
  27. data/lib/wx/classes/vboxsizer.rb +6 -0
  28. data/lib/wx/classes/window.rb +7 -0
  29. data/lib/wx/classes/xmlresource.rb +17 -0
  30. data/lib/wx/helpers.rb +16 -1
  31. data/lib/wx/keyword_ctors.rb +3 -2
  32. data/lib/wx/keyword_defs.rb +56 -5
  33. data/lib/wx/version.rb +1 -1
  34. data/lib/wxruby2.bundle +0 -0
  35. data/samples/SAMPLES-LICENSE.TXT +18 -0
  36. data/samples/aui/aui.rb +1356 -0
  37. data/samples/bigdemo/About.rbw +39 -0
  38. data/samples/bigdemo/ColorPanel.rbw +23 -0
  39. data/samples/bigdemo/GridSimple.rbw +78 -0
  40. data/samples/bigdemo/MDIDemo.rbw +57 -0
  41. data/samples/bigdemo/PopupMenu.rbw +149 -0
  42. data/samples/bigdemo/ShapedWindow.rbw +131 -0
  43. data/samples/bigdemo/Sizers.rbw +543 -0
  44. data/samples/bigdemo/bigdemo.rb +823 -0
  45. data/samples/bigdemo/demoTemplate.rbw +33 -0
  46. data/samples/bigdemo/helpfile.htb +0 -0
  47. data/samples/bigdemo/icons/Test 015.jpg +0 -0
  48. data/samples/bigdemo/icons/Test 015.png +0 -0
  49. data/samples/bigdemo/icons/choice.bmp +0 -0
  50. data/samples/bigdemo/icons/choice.xpm +27 -0
  51. data/samples/bigdemo/icons/combo.bmp +0 -0
  52. data/samples/bigdemo/icons/combo.xpm +27 -0
  53. data/samples/bigdemo/icons/copy.xpm +25 -0
  54. data/samples/bigdemo/icons/cut.xpm +24 -0
  55. data/samples/bigdemo/icons/gauge.bmp +0 -0
  56. data/samples/bigdemo/icons/gauge.xpm +27 -0
  57. data/samples/bigdemo/icons/help.xpm +25 -0
  58. data/samples/bigdemo/icons/list.bmp +0 -0
  59. data/samples/bigdemo/icons/list.xpm +27 -0
  60. data/samples/bigdemo/icons/mondrian.ico +0 -0
  61. data/samples/bigdemo/icons/mondrian.xpm +44 -0
  62. data/samples/bigdemo/icons/new.xpm +24 -0
  63. data/samples/bigdemo/icons/ogl.ico +0 -0
  64. data/samples/bigdemo/icons/ogl.xpm +45 -0
  65. data/samples/bigdemo/icons/open.xpm +26 -0
  66. data/samples/bigdemo/icons/paste.bmp +0 -0
  67. data/samples/bigdemo/icons/paste.xpm +38 -0
  68. data/samples/bigdemo/icons/pointy.png +0 -0
  69. data/samples/bigdemo/icons/preview.xpm +26 -0
  70. data/samples/bigdemo/icons/print.xpm +26 -0
  71. data/samples/bigdemo/icons/radio.bmp +0 -0
  72. data/samples/bigdemo/icons/radio.xpm +27 -0
  73. data/samples/bigdemo/icons/robert.xpm +415 -0
  74. data/samples/bigdemo/icons/ruby.png +0 -0
  75. data/samples/bigdemo/icons/sashtest.ico +0 -0
  76. data/samples/bigdemo/icons/save.xpm +25 -0
  77. data/samples/bigdemo/icons/smiles.bmp +0 -0
  78. data/samples/bigdemo/icons/smiles.xpm +39 -0
  79. data/samples/bigdemo/icons/smiley.ico +0 -0
  80. data/samples/bigdemo/icons/smiley.xpm +42 -0
  81. data/samples/bigdemo/icons/stattext.xpm +24 -0
  82. data/samples/bigdemo/icons/test2.bmp +0 -0
  83. data/samples/bigdemo/icons/test2.png +0 -0
  84. data/samples/bigdemo/icons/test2.xpm +79 -0
  85. data/samples/bigdemo/icons/text.bmp +0 -0
  86. data/samples/bigdemo/icons/text.xpm +27 -0
  87. data/samples/bigdemo/icons/tog1.bmp +0 -0
  88. data/samples/bigdemo/icons/tog1.xpm +38 -0
  89. data/samples/bigdemo/icons/tog2.bmp +0 -0
  90. data/samples/bigdemo/icons/tog2.xpm +38 -0
  91. data/samples/bigdemo/icons/wxwin.ico +0 -0
  92. data/samples/bigdemo/icons/wxwin16x16.png +0 -0
  93. data/samples/bigdemo/icons/wxwin16x16.xpm +25 -0
  94. data/samples/bigdemo/icons/wxwin32x32.png +0 -0
  95. data/samples/bigdemo/icons/wxwin48x48.png +0 -0
  96. data/samples/bigdemo/run.rb +90 -0
  97. data/samples/bigdemo/tips.txt +7 -0
  98. data/samples/bigdemo/utils.rb +12 -0
  99. data/samples/bigdemo/wxArtProvider.rbw +281 -0
  100. data/samples/bigdemo/wxBitmapButton.rbw +65 -0
  101. data/samples/bigdemo/wxButton.rbw +64 -0
  102. data/samples/bigdemo/wxCalendarCtrl.rbw +60 -0
  103. data/samples/bigdemo/wxCheckBox.rbw +50 -0
  104. data/samples/bigdemo/wxCheckListBox.rbw +65 -0
  105. data/samples/bigdemo/wxChoice.rbw +47 -0
  106. data/samples/bigdemo/wxChoicebook.rbw +78 -0
  107. data/samples/bigdemo/wxColourDialog.rbw +31 -0
  108. data/samples/bigdemo/wxComboBox.rbw +77 -0
  109. data/samples/bigdemo/wxCursor.rbw +136 -0
  110. data/samples/bigdemo/wxDialog.rbw +74 -0
  111. data/samples/bigdemo/wxDirDialog.rbw +29 -0
  112. data/samples/bigdemo/wxDragImage.rbw +70 -0
  113. data/samples/bigdemo/wxFileDialog.rbw +37 -0
  114. data/samples/bigdemo/wxFileDialog_Save.rbw +35 -0
  115. data/samples/bigdemo/wxFindReplaceDialog.rbw +82 -0
  116. data/samples/bigdemo/wxFontDialog.rbw +173 -0
  117. data/samples/bigdemo/wxFrame.rbw +53 -0
  118. data/samples/bigdemo/wxGauge.rbw +71 -0
  119. data/samples/bigdemo/wxGenericDirCtrl.rbw +74 -0
  120. data/samples/bigdemo/wxGrid.rbw +66 -0
  121. data/samples/bigdemo/wxHtmlHelpController.rbw +52 -0
  122. data/samples/bigdemo/wxListBox.rbw +140 -0
  123. data/samples/bigdemo/wxListCtrl_virtual.rbw +112 -0
  124. data/samples/bigdemo/wxMDIWindows.rbw +50 -0
  125. data/samples/bigdemo/wxMenu.rbw +236 -0
  126. data/samples/bigdemo/wxMessageDialog.rbw +27 -0
  127. data/samples/bigdemo/wxMiniFrame.rbw +70 -0
  128. data/samples/bigdemo/wxMultipleChoiceDialog.rbw +32 -0
  129. data/samples/bigdemo/wxNotebook.rbw +136 -0
  130. data/samples/bigdemo/wxProgressDialog.rbw +43 -0
  131. data/samples/bigdemo/wxRadioBox.rbw +72 -0
  132. data/samples/bigdemo/wxRadioButton.rbw +125 -0
  133. data/samples/bigdemo/wxSashWindow.rbw +141 -0
  134. data/samples/bigdemo/wxScrolledMessageDialog.rbw +57 -0
  135. data/samples/bigdemo/wxScrolledWindow.rbw +199 -0
  136. data/samples/bigdemo/wxSingleChoiceDialog.rbw +33 -0
  137. data/samples/bigdemo/wxSlider.rbw +42 -0
  138. data/samples/bigdemo/wxSpinButton.rbw +50 -0
  139. data/samples/bigdemo/wxSpinCtrl.rbw +51 -0
  140. data/samples/bigdemo/wxSplitterWindow.rbw +63 -0
  141. data/samples/bigdemo/wxStaticBitmap.rbw +51 -0
  142. data/samples/bigdemo/wxStaticText.rbw +55 -0
  143. data/samples/bigdemo/wxStatusBar.rbw +126 -0
  144. data/samples/bigdemo/wxTextCtrl.rbw +149 -0
  145. data/samples/bigdemo/wxTextEntryDialog.rbw +31 -0
  146. data/samples/bigdemo/wxToggleButton.rbw +49 -0
  147. data/samples/bigdemo/wxToolBar.rbw +131 -0
  148. data/samples/bigdemo/wxTreeCtrl.rbw +191 -0
  149. data/samples/calendar/calendar.rb +256 -0
  150. data/samples/caret/caret.rb +282 -0
  151. data/samples/caret/mondrian.xpm +44 -0
  152. data/samples/controls/controls.rb +1136 -0
  153. data/samples/controls/get_item_sample.rb +87 -0
  154. data/samples/controls/icons/choice.xpm +27 -0
  155. data/samples/controls/icons/combo.xpm +27 -0
  156. data/samples/controls/icons/gauge.xpm +27 -0
  157. data/samples/controls/icons/list.xpm +27 -0
  158. data/samples/controls/icons/radio.xpm +27 -0
  159. data/samples/controls/icons/stattext.xpm +24 -0
  160. data/samples/controls/icons/text.xpm +27 -0
  161. data/samples/controls/mondrian.ico +0 -0
  162. data/samples/controls/mondrian.xpm +44 -0
  163. data/samples/controls/test2.bmp +0 -0
  164. data/samples/dialogs/dialogs.rb +797 -0
  165. data/samples/dialogs/tips.txt +18 -0
  166. data/samples/dragdrop/dragdrop.rb +177 -0
  167. data/samples/drawing/graphics_drawing.rb +235 -0
  168. data/samples/drawing/images.rb +37 -0
  169. data/samples/drawing/paperclip.png +0 -0
  170. data/samples/etc/activation.rb +102 -0
  171. data/samples/etc/choice.rb +67 -0
  172. data/samples/etc/miniframe.rb +79 -0
  173. data/samples/etc/sash.rb +130 -0
  174. data/samples/etc/scrollwin.rb +110 -0
  175. data/samples/etc/system_settings.rb +252 -0
  176. data/samples/etc/threaded.rb +72 -0
  177. data/samples/etc/toolbar_sizer_additem.rb +55 -0
  178. data/samples/etc/wizard.rb +74 -0
  179. data/samples/event/event.rb +182 -0
  180. data/samples/event/update_ui_event.rb +70 -0
  181. data/samples/grid/grid.rb +198 -0
  182. data/samples/grid/gridtablebase.rb +148 -0
  183. data/samples/html/html.rb +262 -0
  184. data/samples/listbook/listbook.rb +174 -0
  185. data/samples/listbook/listbook.xrc +370 -0
  186. data/samples/mdi/mdi.rb +85 -0
  187. data/samples/media/mediactrl.rb +167 -0
  188. data/samples/minimal/minimal.rb +77 -0
  189. data/samples/minimal/mondrian.ico +0 -0
  190. data/samples/minimal/mondrian.png +0 -0
  191. data/samples/minimal/nothing.rb +16 -0
  192. data/samples/opengl/cube.rb +117 -0
  193. data/samples/printing/mondrian.ico +0 -0
  194. data/samples/printing/mondrian.xpm +44 -0
  195. data/samples/printing/printing.rb +487 -0
  196. data/samples/sockets/SocketPackets.rb +27 -0
  197. data/samples/sockets/res/message-new.png +0 -0
  198. data/samples/sockets/res/user.png +0 -0
  199. data/samples/sockets/wxClient.rb +395 -0
  200. data/samples/sockets/wxServer.rb +422 -0
  201. data/samples/sockets/wxSocketGUI.rb +97 -0
  202. data/samples/text/format-text-bold.png +0 -0
  203. data/samples/text/format-text-italic.png +0 -0
  204. data/samples/text/format-text-underline.png +0 -0
  205. data/samples/text/mondrian.ico +0 -0
  206. data/samples/text/mondrian.xpm +44 -0
  207. data/samples/text/rich_textctrl.rb +98 -0
  208. data/samples/text/scintilla.rb +169 -0
  209. data/samples/text/textctrl.rb +111 -0
  210. data/samples/text/unicode.rb +242 -0
  211. data/samples/text/utf8.txt +15 -0
  212. data/samples/treectrl/icon1.xpm +79 -0
  213. data/samples/treectrl/icon2.xpm +53 -0
  214. data/samples/treectrl/icon3.xpm +79 -0
  215. data/samples/treectrl/icon4.xpm +43 -0
  216. data/samples/treectrl/icon5.xpm +79 -0
  217. data/samples/treectrl/treectrl.rb +1166 -0
  218. data/samples/xrc/samples.xrc +46 -0
  219. data/samples/xrc/xrc_sample.rb +76 -0
  220. metadata +17 -3
@@ -0,0 +1,27 @@
1
+ # wxRuby2 Sample Code. Copyright (c) 2007-???? Mario J. Steele
2
+ # Freely reusable code: see SAMPLES-LICENSE.TXT for details
3
+
4
+ class Socket
5
+ # Socket#recv_packet()
6
+ #
7
+ # This method retrives 1kb of data from the socket. It uses
8
+ # IO#readpartial due to the fact, that IO#read() uses buffered IO,
9
+ # which can cause errors. Where as the IO#readpartial() will read up
10
+ # to the maximum of 1024 bytes from the socket, and return what it
11
+ # retrives.
12
+ def recv_packet()
13
+ self.readpartial(1024)
14
+ end
15
+
16
+ # Socket#send_packet()
17
+ #
18
+ # This method will send the packet, and ensure that a LF (0x0A) is at
19
+ # the end of the data to be sent, as this is used as the determination
20
+ # of the end of a packet.
21
+ def send_packet(msg)
22
+ if !msg.index("\n")
23
+ msg += "\n"
24
+ end
25
+ self.write(msg)
26
+ end
27
+ end
Binary file
Binary file
@@ -0,0 +1,395 @@
1
+ #!/usr/bin/env ruby
2
+ # wxRuby2 Sample Code. Copyright (c) 2007-???? Mario J. Steele
3
+ # Freely reusable code: see SAMPLES-LICENSE.TXT for details
4
+
5
+ # This sample is of a client impelmentation using Ruby's green
6
+ # threads, Ruby's Asynchronous Sockets, and wxRuby Widgets, so as
7
+ # to not block the GUI while waiting for data.
8
+
9
+ begin
10
+ require 'wx'
11
+ rescue LoadError => no_wx_err
12
+ begin
13
+ require 'rubygems'
14
+ require 'wx'
15
+ rescue LoadError
16
+ raise no_wx_err
17
+ end
18
+ end
19
+
20
+ require 'thread' # For Thread and Mutex
21
+ require 'socket' # For TCP/IP Communications
22
+
23
+ $LOAD_PATH << File.dirname(__FILE__)
24
+ require 'wxSocketGUI' # Our GUI Interface
25
+ require 'SocketPackets' # For simplfying Socket communciation
26
+
27
+ class String
28
+ # String#index_from(index,to_index)
29
+ # This finds the to_index from the index onwards till the end of the string.
30
+ # Returns the find_index + offset value of index.
31
+ def index_from(i,mtch)
32
+ self[i..-1].index(mtch) + i
33
+ end
34
+ end
35
+
36
+
37
+ class MyClientFrame < SocketGUI
38
+ include Socket::Constants
39
+ @@constants = %w[
40
+ ID_CONNECT
41
+ ID_DISCONNECT
42
+ ID_NAME
43
+ ID_EXIT
44
+ ID_TIMER_POLL
45
+ ]
46
+
47
+ # Creates the menus used by the Client.
48
+ def create_menus()
49
+ mb = Wx::MenuBar.new
50
+
51
+ fileMenu = Wx::Menu.new
52
+ fileMenu.append(ID_CONNECT,"&Connect\tCtrl+S","Connect to a server")
53
+ fileMenu.append(ID_DISCONNECT,"&Disconnect\tCtrl+G","Disconnect from a server")
54
+ fileMenu.append_separator()
55
+ fileMenu.append(ID_NAME,"&Nickname\tCtrl+N","Set your nickname")
56
+ fileMenu.append_separator()
57
+ fileMenu.append(ID_EXIT,"E&xit\tCtrl+X","Exit the client")
58
+
59
+ mb.append(fileMenu,"&File")
60
+
61
+ set_menu_bar(mb)
62
+ end
63
+
64
+ # Called from SocketGUI, to initialize our client window.
65
+ def on_init()
66
+ @nick = "Client"
67
+ @port = "1234"
68
+ @addr = $LOCALHOST_NAME
69
+ @mutex = Mutex.new
70
+ @buffer = ""
71
+ @timer = Wx::Timer.new(self,ID_TIMER_POLL)
72
+
73
+ evt_menu(ID_CONNECT) { on_connect }
74
+ evt_menu(ID_DISCONNECT) { on_disconnect }
75
+ evt_menu(ID_NAME) { on_name }
76
+ evt_menu(ID_EXIT) { on_exit }
77
+
78
+ evt_close { |evt| on_close(evt) }
79
+ evt_text_enter(@input.get_id) { on_text }
80
+ evt_timer(ID_TIMER_POLL) { Thread.pass }
81
+
82
+ get_menu_bar.enable(ID_CONNECT,true)
83
+ get_menu_bar.enable(ID_DISCONNECT,false)
84
+ end
85
+
86
+ # MyClientFrame#on_connect()
87
+ # Called from File > Connect, which prompts the user where they want to connect
88
+ # to, and starts the process for connecting to the server. Since we're looking
89
+ # at a possibility of the connection not being completed immediately, we delegate
90
+ # the confirmation from the connection, to our polling loop here. And we utilize
91
+ # the less known method Socket#connect_nonblock(). Oviously, Ruby will throw
92
+ # an exception if the connection isn't completed immedately, which is alright,
93
+ # we are expecting such. So we ignore any errors, and process thoes in our poll
94
+ # loop for connecting.
95
+ def on_connect
96
+ unless @socket.nil?
97
+ Wx::message_box("You are already connected to a server!","Connect to...",Wx::ID_OK|Wx::ICON_ERROR)
98
+ return
99
+ end
100
+ msgbox = Wx::TextEntryDialog.new(self,"Enter the server you wish to connect to: (Default: #{$LOCALHOST_NAME}:1234)",
101
+ "Connect to server",@addr + ":" + @port.to_s)
102
+ ret = msgbox.show_modal
103
+ if ret == Wx::ID_CANCEL
104
+ return
105
+ end
106
+ @socket = Socket.new(AF_INET,SOCK_STREAM,0)
107
+ sockaddr = msgbox.get_value
108
+ unless sockaddr.index(":")
109
+ sockaddr += ":1234"
110
+ end
111
+ @host,@port = sockaddr.split(":")
112
+ sockaddr = Socket.sockaddr_in(@port,@host)
113
+ begin
114
+ @socket.connect_nonblock(sockaddr)
115
+ rescue
116
+ # We're polling for connection anyways
117
+ end
118
+ append_prog_msg("Attempting to connect to #{@host}:#{@port}")
119
+ get_menu_bar.enable(ID_CONNECT,false)
120
+ get_menu_bar.enable(ID_DISCONNECT,false)
121
+ start_client_thread
122
+ end
123
+
124
+ # MyClientFrame#disconnect()
125
+ # This method utitilizes the closing of the socket connection, and killing
126
+ # any thread that may be running, and since we have no threads, we don't need
127
+ # the timer to be running, so we stop that as well.
128
+ def disconnect()
129
+ return if @socket.nil?
130
+ Thread.kill(@thread) unless @thread.nil?
131
+ @socket.send_packet("QUIT")
132
+ @socket.close()
133
+ @socket = nil
134
+ @timer.stop
135
+ get_menu_bar.enable(ID_CONNECT,true)
136
+ get_menu_bar.enable(ID_DISCONNECT,false)
137
+ end
138
+
139
+ # MyClientFrame#on_disconnect()
140
+ # This method is ran from File > Disconnect and processes the disconnection from
141
+ # the server.
142
+ def on_disconnect()
143
+ disconnect()
144
+ append_prog_msg("You have disconnected from the server.")
145
+ end
146
+
147
+ # MyClientFrame#on_name()
148
+ # This method is ran from File > Nickname, and processes the nickname that the
149
+ # client will use.
150
+ def on_name
151
+ msgbox = Wx::TextEntryDialog.new(self,"Enter the new nickname you want to be (Default: Client)",
152
+ "Change Nickname",@nick)
153
+ ret = msgbox.show_modal
154
+ if ret == Wx::ID_CANCEL
155
+ return
156
+ end
157
+
158
+ nick = msgbox.get_value
159
+ nick = nick.split(" ").join("_") if nick.index(" ")
160
+ @socket.send_packet("NICK #{nick}") unless @socket.nil?
161
+ append_prog_msg("You changed nicknames from #{@nick} to #{nick}")
162
+ @nick = nick
163
+ @socket.send_packet("WHO") unless @socket.nil?
164
+ end
165
+
166
+ # MyClientFrame#on_exit()
167
+ # This method is ran from File > Exit, and delegates the control over to
168
+ # MyClientFrame#on_close() to do it's checks to see if they are still connected
169
+ # or not.
170
+ def on_exit
171
+ self.close
172
+ end
173
+
174
+ # MyClientFrame#on_close(evt)
175
+ # This method is ran from the OnCloseEvent, and checks to see if the client is still
176
+ # connected to the server. If they are, it will confirm if they want to disconnect
177
+ # from the server or not. If they don't want to disconnect, it will veto the close
178
+ # event.
179
+ def on_close(evt)
180
+ unless @socket.nil?
181
+ ret = Wx::message_box("You are still connected to the server, do you wish to disconnect?","Disconnect",Wx::YES|Wx::NO|Wx::ICON_QUESTION)
182
+ if ret == Wx::NO
183
+ evt.veto
184
+ else
185
+ disconnect()
186
+ Wx::get_app.exit_main_loop()
187
+ end
188
+ else
189
+ Wx::get_app.exit_main_loop()
190
+ end
191
+ end
192
+
193
+ # MyClientFrame#on_text()
194
+ # This method is executed from when the user presses the Enter button from the
195
+ # input box. It will processes it for any /commands, and run them as nesscary
196
+ # otherwise, it will send the data to the server.
197
+ def on_text
198
+ msg = @input.get_value
199
+ @input.set_value("")
200
+ case msg
201
+ when /\/connect/
202
+ on_connect
203
+ when /\/disconnect/
204
+ on_disconnect
205
+ when /\/nick/
206
+ on_name
207
+ when /\/exit/
208
+ self.close
209
+ when /\/eval/
210
+ evl_str = msg[5..-1]
211
+ append_prog_msg(self.instance_eval(evl_str).inspect)
212
+ when /\/me/
213
+ append_msg("<-- #{@nick} #{msg[4..-1]}")
214
+ msg = "EMOTE " + msg[4..-1]
215
+ @socket.send_packet(msg)
216
+ else
217
+ append_msg("<-- #{@nick} says: #{msg}")
218
+ msg = "MSG " + msg
219
+ @socket.send_packet(msg)
220
+ end
221
+ end
222
+
223
+ # MyClientFrame#poll_connect()
224
+ # This method is called from within the polling thread, and will run till
225
+ # it either finds that the socket has been completed, and connected to a server,
226
+ # otherwise, it will check to see if there is an error. If there is, then that
227
+ # means we weren't able to connect to the server. This will then run the method
228
+ # Socket#connect() inside a rescue condition, so that we can see why we wasn't
229
+ # able to connect to the server.
230
+ def poll_connect()
231
+ loop do
232
+ @mutex.lock
233
+ ret = IO::select(nil,[@socket],[@socket],0.3)
234
+ unless ret.nil?
235
+ if Wx::PLATFORM == "WXMSW"
236
+ unless ret[2] == []
237
+ # We have an error
238
+ error = nil
239
+ begin
240
+ error = @socket.connect(Socket.sockaddr_in(@port,@host))
241
+ rescue => e
242
+ error = e
243
+ end
244
+ append_error("Error attempting to connect to #{@host}:#{@port}")
245
+ append_error("Message: #{e} (#{e.class})")
246
+ @mutex.unlock
247
+ @socket.close()
248
+ @socket = nil
249
+ get_menu_bar.enable(ID_CONNECT,true)
250
+ get_menu_bar.enable(ID_DISCONNECT,false)
251
+ @timer.stop()
252
+ Thread.kill(@thread)
253
+ @thread.exit
254
+ return
255
+ end
256
+ else # For WXGTK and WXMAC
257
+ # Since on WXGTK and WXMAC doesn't return the error part in ret, we need
258
+ # to check and see if we are connected.
259
+ error = nil
260
+ begin
261
+ error = @socket.connect(Socket.sockaddr_in(@port,@host))
262
+ rescue => e
263
+ error = e
264
+ end
265
+ if error.class != Fixnum && !error.nil?
266
+ # We're not connected!
267
+ append_error("Error attempting to connect to #{@host}:#{@port}")
268
+ append_error("Message: #{e} (#{e.class})")
269
+ @mutex.unlock
270
+ @socket.close()
271
+ @socket = nil
272
+ get_menu_bar.enable(ID_CONNECT,true)
273
+ get_menu_bar.enable(ID_DISCONNECT,false)
274
+ @timer.stop()
275
+ Thread.kill(@thread)
276
+ @thread.exit
277
+ return
278
+ end
279
+ end
280
+ append_prog_msg("Connected to #{@host}:#{@port}")
281
+ get_menu_bar.enable(ID_CONNECT,false)
282
+ get_menu_bar.enable(ID_DISCONNECT,true)
283
+ @socket.send_packet("CONNECT #{@nick}")
284
+ @socket.send_packet("WHO")
285
+ @mutex.unlock
286
+ break
287
+ end
288
+ @mutex.unlock
289
+ end
290
+ end
291
+
292
+ # MyClientFrame#parse_message(msg)
293
+ # This method checks to see what message is being received from the server
294
+ # add display it to the user, as needed.
295
+ def parse_message(msg)
296
+ case msg
297
+ when /CONNECT/
298
+ append_msg("--> #{msg[8..-1]} has joined the chat server.")
299
+ #@users.insert_item(0,msg[8..-1],0)
300
+ add_user(msg[8..-1])
301
+ when /DISCONN/
302
+ append_msg("--> #{msg[8..-1]} has been removed from the chat server.")
303
+ when /CONN/
304
+ append_msg("<!> New connection incoming to the server.")
305
+ when /WHO/
306
+ cmd, *usrs = msg.split(" ")
307
+ clear_users()
308
+ usrs.each do |usr|
309
+ add_user(usr)
310
+ end
311
+ when /NICK/
312
+ a = msg.index_from(5," ")
313
+ old = msg[5..a-1]
314
+ new = msg[a+1..-1]
315
+ append_msg("--> #{old} has changed their nickname to #{new}")
316
+ @socket.send_packet("WHO")
317
+ when /MSG/
318
+ a = msg.index_from(4," ")
319
+ user = msg[4..a-1]
320
+ txt = msg[a+1..-1]
321
+ append_msg("--> #{user} says: #{txt}")
322
+ when /EMOTE/
323
+ a = msg.index_from(6," ")
324
+ user = msg[6..a-1]
325
+ txt = msg[a+1..-1]
326
+ append_msg("--> #{user} #{txt}")
327
+ when /QUIT/
328
+ append_msg("--> #{msg[5..-1]} is quitting the chat server.")
329
+ @socket.send_packet("WHO")
330
+ when /SHUTDOWN/
331
+ append_msg("--> #{msg[9..-1]}")
332
+ else
333
+ append_error("Unknown message received from the server.")
334
+ append_error("Message: #{msg}")
335
+ end
336
+ end
337
+
338
+ # MyClientFrame#poll_data()
339
+ # This method will poll the connection for incoming data, and to see if it
340
+ # has been disconnected from the server or not. It will continue to poll
341
+ # till a connection has been closed, either by the server, or by the user.
342
+ def poll_data()
343
+ loop do
344
+ @mutex.lock
345
+ ret = IO::select([@socket],nil,nil,0.3)
346
+ unless ret.nil?
347
+ if @socket.eof?
348
+ append_prog_msg("You have been disconnected from the server!")
349
+ get_menu_bar.enable(ID_CONNECT,true)
350
+ get_menu_bar.enable(ID_DISCONNECT,false)
351
+ @socket.close
352
+ @socket = nil
353
+ @timer.stop
354
+ @mutex.unlock
355
+ @thread.exit
356
+ break
357
+ end
358
+ msg = @socket.recv_packet()
359
+ @buffer += msg
360
+ loop do
361
+ at = @buffer.index("\n")
362
+ if at.nil?
363
+ break
364
+ end
365
+ data = @buffer[0..at-1]
366
+ @buffer = @buffer[at+1..-1]
367
+ parse_message(data)
368
+ end
369
+ end
370
+ @mutex.unlock
371
+ end
372
+ end
373
+
374
+ # MyClientFrame#start_client_thread()
375
+ # This method will actually start the polling of a connection for the connection
376
+ # and data. First it will start the polling of the connection, after it successfully
377
+ # receives the connection, it will start polling for incoming data.
378
+ def start_client_thread
379
+ @thread = Thread.new do
380
+ @timer.start(25)
381
+ poll_connect()
382
+ poll_data()
383
+ end
384
+ end
385
+ end
386
+
387
+ class MySocketApp < Wx::App
388
+ def on_init
389
+ Thread.abort_on_exception = true
390
+ frame = MyClientFrame.new(nil,-1,"Socket Demo >> Client")
391
+ frame.show
392
+ end
393
+ end
394
+
395
+ MySocketApp.new.main_loop
@@ -0,0 +1,422 @@
1
+ #!/usr/bin/env ruby
2
+ # wxRuby2 Sample Code. Copyright (c) 2007-???? Mario J. Steele
3
+ # Freely reusable code: see SAMPLES-LICENSE.TXT for details
4
+
5
+ # This sample is a demonstration of how to implement a Multi-Threaded
6
+ # Server, using Ruby's green threads, Ruby's Asynchronous Sockets and
7
+ # wxRuby, as to not block the GUI while receiving connections and data
8
+ # from clients.
9
+
10
+ begin
11
+ require 'wx'
12
+ rescue LoadError => no_wx_err
13
+ begin
14
+ require 'rubygems'
15
+ require 'wx'
16
+ rescue LoadError
17
+ raise no_wx_err
18
+ end
19
+ end
20
+
21
+ require 'thread' # For Thread and Mutex
22
+ require 'socket' # For TCP/IP Communications
23
+
24
+ $LOAD_PATH << File.dirname(__FILE__)
25
+ require 'wxSocketGUI' # Our GUI Interface
26
+ require 'SocketPackets' # For simplfying Socket communciation
27
+
28
+ class MyServerFrame < SocketGUI
29
+ include Socket::Constants
30
+ @@constants = %w[
31
+ ID_START
32
+ ID_STOP
33
+ ID_NAME
34
+ ID_EXIT
35
+ ID_TIMER_POLL
36
+ ]
37
+ # ClientInfo holds information about each client connection to the server
38
+ # making it easier to track information, and use that throughout the
39
+ # server process.
40
+ ClientInfo = Struct.new(
41
+ :name, # User's Nickname
42
+ :host, # User's Host Address
43
+ :port, # User's Port
44
+ :sock, # User's Sock
45
+ :buffer) # Buffer for the socket
46
+
47
+ # Creates the Menus used by the server.
48
+ def create_menus()
49
+ mb = Wx::MenuBar.new
50
+
51
+ fileMenu = Wx::Menu.new
52
+ fileMenu.append(ID_START,"&Start Server\tCtrl+S","Start listening for incomming connections")
53
+ fileMenu.append(ID_STOP,"S&top Server\tCtrl+G","Stop listening for incomming connections, and disconnect all current connections")
54
+ fileMenu.append_separator()
55
+ fileMenu.append(ID_NAME,"&Nickname\tCtrl+N","Set your Nickname")
56
+ fileMenu.append_separator()
57
+ fileMenu.append(ID_EXIT,"E&xit\tCtrl+X","Exit the demo")
58
+
59
+ mb.append(fileMenu,"&File")
60
+ set_menu_bar(mb)
61
+ end
62
+
63
+ # Our initialize sequence, called by SocketGUI.
64
+ def on_init()
65
+ @nick = "Server" # Server user's Nickname
66
+ @port = 1234 # Port we're listening on
67
+ @mutex = Mutex.new # For synchronization, so there's no corruption of memory
68
+ @clients = [] # For the sockets that are connected
69
+ @timer = Wx::Timer.new(self,ID_TIMER_POLL)
70
+ # Used to poll for data
71
+
72
+ evt_menu(ID_START) { on_start }
73
+ evt_menu(ID_STOP) { on_stop }
74
+ evt_menu(ID_NAME) { on_name }
75
+ evt_menu(ID_EXIT) { on_exit }
76
+
77
+ evt_close { |evt| on_close(evt) }
78
+ evt_text_enter(@input.get_id) { on_text }
79
+ evt_timer(ID_TIMER_POLL) { Thread.pass }
80
+
81
+ get_menu_bar.enable(ID_START,true)
82
+ get_menu_bar.enable(ID_STOP,false)
83
+ end
84
+
85
+ # MyServerFrame#on_start()
86
+ # This function sets up the socket for listening to incoming connections, as
87
+ # well as starting the Thread that will do the underline work for polling for
88
+ # incoming connections as well as incoming data.
89
+ def on_start
90
+ if !@socket.nil?
91
+ Wx::message_box("The server is already started!","Server startup error",Wx::ID_OK|Wx::ICON_ERROR)
92
+ return
93
+ end
94
+
95
+ mbox = Wx::TextEntryDialog.new(self,"Enter the port you want to listen on (Default: 1234)",
96
+ "Start Server","1234")
97
+ if mbox.show_modal == Wx::ID_CANCEL
98
+ return
99
+ end
100
+ @port = mbox.get_value.to_i
101
+ @socket = Socket.new(AF_INET,SOCK_STREAM,0)
102
+ @socket.bind(Socket.sockaddr_in(@port,$LOCALHOST_NAME))
103
+ @socket.listen(5)
104
+ append_prog_msg("[#{Time.now}] - Server started on port #{@port}.")
105
+ update_users()
106
+ get_menu_bar.enable(ID_START,false)
107
+ get_menu_bar.enable(ID_STOP,true)
108
+ start_server_thread()
109
+ end
110
+
111
+ # MyServerFrame#on_stop()
112
+ # This is executed from the File > Stop menu item, which will first check to see
113
+ # if we still have Active clients. If so, confirm that the user wants to shutdown
114
+ # the server. If the user does, it'll start the MyServerFraem#shutdown() sequence.
115
+ def on_stop()
116
+ if @socket.nil?
117
+ Wx::message_box("The server is currently not active!","Server shutdown",Wx::OK|Wx::ICON_ERROR)
118
+ return
119
+ end
120
+ if @clients.size > 0
121
+ ret = Wx::message_box("There are still clients connected, do you wish to shutdown still?","Server shutdown",Wx::YES|Wx::NO|Wx::ICON_QUESTION)
122
+ if ret == Wx::NO
123
+ return
124
+ end
125
+ end
126
+ shutdown()
127
+ end
128
+
129
+ # MyServerFrame#on_name()
130
+ # This is executed from the File > Nickname menu item, which will change the nickname
131
+ # of the Server itself. If there's active connections to the server, it will notify
132
+ # all of the clients of the server's new nickname.
133
+ def on_name
134
+ mbox = Wx::TextEntryDialog.new(self,"Enter the new nickname you want to be (Default: Server)",
135
+ "Change Nickname",@nick)
136
+ ret = mbox.show_modal
137
+ if ret == Wx::ID_CANCEL
138
+ return
139
+ end
140
+ nick = mbox.get_value
141
+ nick = nick.split(" ").join("_") if nick.index(" ")
142
+ append_prog_msg("[#{Time.now}] - You changed your nickname to #{nick}.")
143
+ @clients.each do |ci|
144
+ ci.sock.send_packet("NICK #{@nick} #{nick}")
145
+ end
146
+ @nick = nick
147
+ update_users()
148
+ end
149
+
150
+ # MyServerFrame#on_exit()
151
+ # This is executed from the File > Exit menu item, it will simply delegate the actual
152
+ # processing to MyServerFrame#on_close() event, by closing the window.
153
+ def on_exit
154
+ self.close
155
+ end
156
+
157
+ # MyServerFrame#on_close(evt)
158
+ # This is executed from the OnCloseEvent that comes from when the user closes the
159
+ # window, or executes the File > Exit menu item. It will first check to see if the
160
+ # if there's an Active Socket. If there is, then it'll check and see if there are
161
+ # any clients connected. If there are clients connected, it will confirm with the
162
+ # user that they want to shutdown the server. If they don't, it will veto the
163
+ # close event, and the server will continue to run.
164
+ def on_close(evt)
165
+ ret = Wx::YES
166
+ if !@socket.nil?
167
+ if @clients.size > 0
168
+ ret = Wx::message_box("There are still clients connected to the server, do you wish to continue?","Shutdown",Wx::YES|Wx::NO|Wx::ICON_INFORMATION)
169
+ end
170
+ end
171
+ if ret == Wx::NO
172
+ evt.veto
173
+ else
174
+ shutdown()
175
+ Wx::get_app.exit_main_loop
176
+ end
177
+ end
178
+
179
+ # MyServerFrame#on_text()
180
+ # This is executed when the user presses the Enter button from the input box.
181
+ # It does small parsing to check what /commands are used, if any, then if it
182
+ # doesn't find any, it'll pass through to actually sending the data.
183
+ def on_text()
184
+ msg = @input.get_value
185
+ @input.set_value("")
186
+ case msg
187
+ when /\/start/
188
+ on_start
189
+ when /\/shutdown/
190
+ on_stop
191
+ when /\/nick/
192
+ on_name
193
+ when /\/exit/
194
+ self.close
195
+ when /\/me/
196
+ append_msg("--> #{@nick} " + msg[4..-1])
197
+ msg = "EMOTE #{@nick} " + msg[4..-1]
198
+ @mutex.lock
199
+ @clients.each do |ci|
200
+ ci.sock.send_packet(msg)
201
+ end
202
+ @mutex.unlock
203
+ else
204
+ append_msg("--> #{@nick} says: " + msg)
205
+ msg = "MSG #{@nick} " + msg
206
+ @mutex.lock
207
+ @clients.each do |ci|
208
+ ci.sock.send_packet(msg)
209
+ end
210
+ @mutex.unlock
211
+ end
212
+ end
213
+
214
+ # MyServerFrame#shutdown()
215
+ # This is the actual clean up function that will shutdown the Server, and
216
+ # disconnect any connected clients. When this is executed, there should
217
+ # already have a Mutex lock done, so that we can safely clean everything up.
218
+ def shutdown()
219
+ Thread.kill(@thread) unless @thread.nil?
220
+ @timer.stop
221
+
222
+ @clients.each do |ci|
223
+ ci.sock.send_packet("SHUTDOWN Server is going down now!")
224
+ ci.sock.close
225
+ end
226
+ @clients = []
227
+ @socket.close unless @socket.nil?
228
+ @socket = nil
229
+ append_prog_msg("[#{Time.now}] - Server has been shutdown.")
230
+ get_menu_bar.enable(ID_START,true)
231
+ get_menu_bar.enable(ID_STOP,false)
232
+ end
233
+
234
+ # MyServerFrame#poll()
235
+ # This function collects the sockets for clients, as well as the server
236
+ # socket to poll for incoming connections or data from these sockets.
237
+ # This returns three arrays of sockets from [read,write,error/oob]
238
+ def poll
239
+ pollers = [@socket]
240
+ @clients.each do |ci|
241
+ pollers << ci.sock
242
+ end
243
+ IO::select(pollers,nil,nil,0.3)
244
+ end
245
+
246
+ # MyServerFrame#check_server(socks)
247
+ # This is the core of the processing for the server side. It will check to
248
+ # see if our server socket is in the return for MyServerFrame#poll(), if it's
249
+ # there, then there's an incoming connection, and will run the accepting of
250
+ # the new client connection, and tell the other clients that there is a new
251
+ # incoming connection. It will then remove the server socket from the array
252
+ # so that it's not processed, as well as converting all the remaining sockets
253
+ # if any, back into ClientInfo structures, there by returning them to be
254
+ # processed by the MyServerFrame#check_clients()
255
+ def check_server(socks)
256
+ if socks.index(@socket)
257
+ socks.delete(@socket) # Don't need it in socks
258
+ sock, sockaddr = @socket.accept
259
+ sockaddr = Socket.unpack_sockaddr_in(sockaddr)
260
+ ci = ClientInfo.new("",sockaddr[1],sockaddr[0],sock,"")
261
+ @clients.each do |sci|
262
+ sci.sock.send_packet("CONN #{ci.host} #{ci.port}")
263
+ end
264
+ @clients << ci
265
+ append_msg("[#{Time.now}] - New connection from #{ci.host}:#{ci.port}")
266
+ update_users()
267
+ end
268
+ new_ci = []
269
+ socks.each do |sock|
270
+ @clients.each do |ci|
271
+ if ci.sock == sock
272
+ new_ci << ci
273
+ end
274
+ end
275
+ end
276
+ new_ci
277
+ end
278
+
279
+ # MyServerFrame#update_users()
280
+ # This function actually updates the User listing of thoes currently connected
281
+ # to the server.
282
+ def update_users()
283
+ clear_users()
284
+ add_user(@nick)
285
+ @clients.each do |ci|
286
+ add_user(ci.name)
287
+ end
288
+ end
289
+
290
+ # MyServerFrame#parse_message(ClientInfo,Message)
291
+ # This function parses the incoming data from a Client, to see what is being
292
+ # requested. There are currently 6 commands that the server implemented.
293
+ # CONNECT identifies the Client with their nickname.
294
+ # NICK identifies the client's new nickname if they change it.
295
+ # EMOTE identifies the message as being an Emote, or Action.
296
+ # MSG identifies the message as being a normal message to the server.
297
+ # WHO is an internal request to update who is connected to the server.
298
+ # QUIT identifies that the client is closing the connection to the server.
299
+ #
300
+ # This function parses the message, and responds appropriately, weither it
301
+ # be an internal message to be handled, or it's a repeater, that needs to be
302
+ # sent to all the connected clients, as well as being displayed to the server.
303
+ def parse_message(ci,msg)
304
+ new_msg = nil
305
+ case msg
306
+ when /CONNECT/
307
+ ci.name = msg[8..-1]
308
+ append_msg("<-> #{ci.name} has joined the server!")
309
+ new_msg = msg
310
+ update_users()
311
+ when /NICK/
312
+ new_msg = "NICK #{ci.name} #{msg[5..-1]}"
313
+ append_msg("<-> #{ci.name} has changed nickname to #{msg[5..-1]}")
314
+ ci.name = msg[5..-1]
315
+ update_users()
316
+ when /EMOTE/
317
+ new_msg = "EMOTE #{ci.name} #{msg[6..-1]}"
318
+ append_msg("<-> #{ci.name} #{msg[6..-1]}")
319
+ when /MSG/
320
+ new_msg = "MSG #{ci.name} #{msg[4..-1]}"
321
+ append_msg("<-> #{ci.name} says: #{msg[4..-1]}")
322
+ when /WHO/
323
+ who = "WHO #{@nick} "
324
+ @clients.each do |cci|
325
+ who += cci.name + " "
326
+ end
327
+ ci.sock.send_packet(who[0..-2])
328
+ when /QUIT/
329
+ new_msg = "QUIT #{ci.name}"
330
+ append_msg("<-> #{ci.name} is dis-connecting.")
331
+ else
332
+ append_error("<-- Unknown message from #{ci.name}")
333
+ append_error("<-- Message: #{msg}")
334
+ end
335
+ @clients.each do |cci|
336
+ if cci != ci
337
+ cci.sock.send_packet(new_msg)
338
+ end
339
+ end unless new_msg.nil?
340
+ end
341
+
342
+ # MyServerFrame#check_clients()
343
+ # This method, will run through the collected client connections that show that
344
+ # they have data available in their buffer. It will first check to see if the
345
+ # socket has been closed by utilizing the IO#eof? method. If there is an end
346
+ # of file detected, it means the client connection has been closed. It will then
347
+ # remove the client connection from our pool, and send the rest of the clients
348
+ # that the user has indeed disconnected from the server.
349
+ # After confirming that the connection hasn't been closed, it will check to
350
+ # see what data is available, append it to the client's buffer, where it will
351
+ # then process the data, for the LF (0x0A) termination, saying that this is the
352
+ # end of the message, and then process that data through MyServerFrame#parse_message()
353
+ def check_clients(aci)
354
+ aci.each do |ci|
355
+ if ci.sock.eof?
356
+ ci.sock.close
357
+ @clients.delete(ci)
358
+ @clients.each do |cci|
359
+ cci.sock.send_packet("DISCONN #{ci.name}")
360
+ end
361
+ append_prog_msg("[#{Time.now}] - Socket closed for #{ci.name}")
362
+ update_users()
363
+ else
364
+ msg = ci.sock.recv_packet()
365
+ ci.buffer += msg
366
+ loop do
367
+ at = ci.buffer.index("\n")
368
+ if at.nil?
369
+ break
370
+ end
371
+ data = ci.buffer[0..at-1]
372
+ ci.buffer = ci.buffer[at+1..-1]
373
+ parse_message(ci,data)
374
+ end
375
+ end
376
+ end
377
+ end
378
+
379
+ # MyServerFrame#start_server_thread()
380
+ # This is the meat and bones of the server, where it will continiously poll for
381
+ # data in a seperate Thread, that will utilize the above methods for processing
382
+ # the incoming connections, and data being inputed from clients. This thread
383
+ # does no yielding, or anything of the sort to wxRuby, as it's in a seperate
384
+ # thread being controlled by Ruby. This thread is given time to run, by the
385
+ # Thread#yield() method, that is put into a context of a Timer that executes
386
+ # every 300 Milliseconds, and so forth. We start the timer here, so that way
387
+ # we ensure that it get's started, and stopped at the correct times.
388
+ def start_server_thread()
389
+ @thread = Thread.new do
390
+ @timer.start(25)
391
+ loop do # We just run a continious loop. on_stop() will kill this thread, and stop the timer.
392
+ begin # We run here, just incase there's some kind of error
393
+ @mutex.lock # We lock the data, so there's no corruption between threads
394
+ socks = poll() # We run a poll for any incoming data, or any new incoming connections
395
+ unless socks.nil?
396
+ ci = check_server(socks[0]) # Check to see if we have any incoming connections
397
+ check_clients(ci) # Check for client activity
398
+ end
399
+ @mutex.unlock # We can now release it, as we've done everything needed here
400
+ rescue => e # We have an error, so we need to process it.
401
+ msg = ["An error has occured!"]
402
+ msg << "#{e.backtrace.delete_at(0)}: #{e} (#{e.class})"
403
+ e.backtrace.each do |line|
404
+ msg << line
405
+ end
406
+ msg.each do |line|
407
+ append_error(line)
408
+ end
409
+ end
410
+ end
411
+ end
412
+ end
413
+ end
414
+
415
+ class MySocketApp < Wx::App
416
+ def on_init
417
+ frame = MyServerFrame.new(nil,-1,"Sockets Demo >> Server")
418
+ frame.show
419
+ end
420
+ end
421
+
422
+ MySocketApp.new.main_loop