Restore game actions.
[kaya.git] / lib / mainwindow.rb
bloba5c4b4432aec7b89668fa1e01ed5f32825deec51
1 # Copyright (c) 2009 Paolo Capriotti <p.capriotti@gmail.com>
2
3 # This program is free software; you can redistribute it and/or modify
4 # it under the terms of the GNU General Public License as published by
5 # the Free Software Foundation; either version 2 of the License, or
6 # (at your option) any later version.
8 require 'toolkit'
9 require 'board/board'
10 require 'board/pool'
11 require 'board/table'
12 require 'board/scene'
13 require 'interaction/history'
14 require 'controller'
15 require 'dummy_player'
17 require 'interaction/match'
19 require 'console'
21 require 'filewriter'
22 require 'newgame'
23 require 'engine_prefs'
24 require 'theme_prefs'
25 require 'view'
26 require 'multiview'
28 require 'kaya_ui'
30 class MainWindow < KDE::XmlGuiWindow
31   include ActionHandler
32   include FileWriter
33   
34   attr_reader :console
35   attr_reader :view
37   def initialize(loader, game)
38     super nil
39     
40     @loader = loader
41     @theme_loader = @loader.get_matching(:theme_loader).new
42     
43     @factories = Hash.new do |h, interface|
44       @loader.get_matching(interface)
45     end
46     @factories[:table] = Factory.new do |parent|
47         Table.new(Scene.new, @loader, @theme_loader, parent)
48       end
49     @factories[:controller] = Factory.new do |parent|
50       Controller.new(parent, @field).tap do |c|
51         c.on(:changed_active_actions) do
52           update_active_actions(c)
53         end
54         c.on(:reset) do
55           update_game_actions(c)
56         end
57       end
58     end
60     startup(game)
61     setup_actions
62     load_action_providers
63     setGUI(Kaya::GUI)
64     new_game(Match.new(game), :new_tab => false)
65   end
66   
67   def closeEvent(event)
68     saveGUI
69     if controller.match
70       controller.match.close
71     end
72     event.accept
73   end
75   def controller
76     @view.current.controller
77   end
79 private
81   def setup_actions
82     @actions = { }
83     regular_action(:open_new,
84                    :icon => 'document-new',
85                    :text => KDE::i18n("&New..."),
86                    :shortcut => KDE::std_shortcut(:new),
87                    :tooltip => KDE::i18n("Start a new game...")) do
88       create_game
89     end
90     
91     regular_action(:open,
92                    :icon => 'document-open',
93                    :text => KDE::i18n("&Load..."),
94                    :shortcut => KDE::std_shortcut(:open),
95                    :tooltip => KDE::i18n("Open a saved game...")) do
96       load_game
97     end
98     
99     regular_action(:quit,
100                    :icon => 'application-exit',
101                    :text => KDE::i18n("&Quit"),
102                    :shortcut => KDE::std_shortcut(:quit),
103                    :tooltip => KDE::i18n("Quit the program")) do
104       close
105     end
106     
107     regular_action(:save,
108                    :icon => 'document-save',
109                    :text => KDE::i18n("&Save"),
110                    :shortcut => KDE::std_shortcut(:save),
111                    :tooltip => KDE::i18n("Save the current game")) do
112       save_game
113     end
114     
115     regular_action(:save_as,
116                    :icon => 'document-save-as',
117                    :text => KDE::i18n("Save &As..."),
118                    :tooltip => KDE::i18n("Save the current game to another file")) do
119       save_game_as
120     end
121     
122     @actions[:back] = regular_action :back, :icon => 'go-previous', 
123                           :text => KDE.i18n("B&ack") do
124       controller.back
125     end
126     @actions[:forward] = regular_action :forward, :icon => 'go-next', 
127                              :text => KDE.i18n("&Forward") do
128       controller.forward
129     end
130     
131     regular_action :flip, :icon => 'object-rotate-left',
132                           :text => KDE.i18n("F&lip") do
133       table = @view.current.table
134       table.flip(!table.flipped?)
135     end
136     
137     regular_action :configure_engines,
138                    :icon => 'help-hint',
139                    :text => KDE.i18n("Configure &Engines...") do
140       dialog = EnginePrefs.new(@engine_loader, self)
141       dialog.show
142     end
143     
144     regular_action :configure_themes,
145                    :icon => 'games-config-theme',
146                    :text => KDE.i18n("Configure &Themes...") do
147       dialog = ThemePrefs.new(@loader, @theme_loader, self)
148       dialog.on(:ok) { controller.reset }
149       dialog.show
150     end
151     
152     @actions[:undo] = regular_action(:undo,
153             :icon => 'edit-undo',
154             :text => KDE::i18n("Und&o"),
155             :shortcut => KDE::std_shortcut(:undo),
156             :tooltip => KDE::i18n("Undo the last history operation")) do
157       controller.undo!
158     end
160     @actions[:redo] = regular_action(:redo,
161             :icon => 'edit-redo',
162             :text => KDE::i18n("Re&do"),
163             :shortcut => KDE::std_shortcut(:redo),
164             :tooltip => KDE::i18n("Redo the last history operation")) do
165       controller.redo!
166     end
167   end
168   
169   def load_action_providers
170     @loader.get_all_matching(:action_provider).each do |provider_klass|
171       provider = provider_klass.new
172       ActionProviderClient.new(self, provider)
173     end
174   end
175   
176   def startup(game)
177     @field = AnimationField.new(20)
179     movelist_stack = Qt::StackedWidget.new(self)
180     movelist_dock = Qt::DockWidget.new(self)
181     movelist_dock.widget = movelist_stack
182     movelist_dock.window_title = KDE.i18n("History")
183     movelist_dock.object_name = "movelist"
184     add_dock_widget(Qt::LeftDockWidgetArea, movelist_dock, Qt::Vertical)
185     movelist_dock.show
186     action_collection[:toggle_history] = movelist_dock.toggle_view_action
188     @view = MultiView.new(self, movelist_stack, @factories)
189     @view.create(:name => game.class.plugin_name)
190     @view.on(:changed) do
191       update_active_actions(controller)
192       update_title
193       update_game_actions(controller)
194     end
195     @view.clean = true
197     update_title
198     
199     @engine_loader = @loader.get_matching(:engine_loader).new
200     @engine_loader.reload
201     
202     @console = Console.new(nil)
203     console_dock = Qt::DockWidget.new(self)                                                      
204     console_dock.widget = @console                                                             
205     console_dock.focus_proxy = @console                                                        
206     console_dock.window_title = KDE.i18n("Console")                                              
207     console_dock.object_name = "console"                                                         
208     add_dock_widget(Qt::BottomDockWidgetArea, console_dock, Qt::Horizontal)                      
209     console_dock.window_flags = console_dock.window_flags & ~Qt::WindowStaysOnTopHint            
210     console_dock.show
211     action_collection[:toggle_console] = console_dock.toggle_view_action
212     
213     self.central_widget = @view
214   end
215   
216   def new_game(match, opts = { })
217     setup_single_player(match)
218     controller.reset(match)
219   end
220   
221   def setup_single_player(match)
222     controller.color = match.game.players.first
223     controller.premove = false
224     opponents = match.game.players[1..-1].map do |color|
225       DummyPlayer.new(color)
226     end
227     opponents.each do |p| 
228       controller.add_controlled_player(p)
229     end
231     controller.controlled.values.each do |p|
232       match.register(p)
233     end
234     controller.controlled.values.each do |p|
235       match.start(p)
236     end
237   end
239   def create_game(opts = { })
240     current_game = if controller.match 
241       controller.match.game
242     end
243     diag = NewGame.new(self, @engine_loader, current_game)
244     diag.on(:ok) do |data|
245       game = data[:game]
246       match = Match.new(game, :editable => data[:engines].empty?)
247       if data[:new_tab]
248         @view.create(:activate => true,
249                      :force => true,
250                      :name => game.class.plugin_name)
251       else
252         @view.set_tab_text(@view.index, game.class.plugin_name)
253         update_title
254       end
255       contr = controller
256       
257       
258       match.on(:started) do
259         contr.reset(match)
260       end
261       
262       # set up engine players
263       players = game.players
264       data[:engines].each do |player, engine|
265         e = engine.new(player, match)
266         e.start
267       end
268       
269       # set up human players
270       if data[:humans].empty?
271         contr.color = nil
272       else
273         contr.color = data[:humans].first
274         contr.premove = data[:humans].size == 1
275         match.register(contr)
276         
277         data[:humans][1..-1].each do |player|
278           p = DummyPlayer.new(player)
279           contr.add_controlled_player(p)
280           match.register(p)
281         end
282       end
283       contr.controlled.values.each {|p| match.start(p) }
284     end
285     diag.show
286   end
288   def load_game
289     url = KDE::FileDialog.get_open_url(KDE::Url.new, '*.*', self,
290       KDE.i18n("Open game"))
291     
292     return if url.is_empty or (not url.path)
293     
294     puts "url = #{url.inspect}"
295     # find readers
296     ext = File.extname(url.path)[1..-1]
297     return unless ext
298     readers = Game.to_enum.find_all do |_, game|
299       game.respond_to?(:game_extensions) and
300       game.game_extensions.include?(ext)
301     end.map do |_, game|
302       [game, game.game_reader]
303     end
304     
305     if readers.empty?
306       warn "Unknown file extension #{ext}"
307       return
308     end
309     
310     tmp_file = KDE::download_tempfile(url, self)
311     return unless tmp_file
313     history = nil
314     game = nil
315     info = nil
316     
317     readers.each do |g, reader|
318       begin
319         data = File.open(tmp_file) do |f|
320           f.read
321         end
322         i = {}
323         history = reader.read(data, i)
324         game = g
325         info = i
326         break
327       rescue ParseException
328       end
329     end
330     
331     unless history
332       warn "Could not load file #{url.path}"
333       return
334     end
335     
336     # create game
337     match = Match.new(game)
338     @view.create(:activate => true,
339                   :name => game.class.plugin_name)
340     setup_single_player(match)
341     match.history = history
342     match.add_info(info)
343     match.url = url
344     controller.reset(match)
345   end
346   
347   def save_game_as
348     match = controller.match
349     if match
350       pattern = if match.game.respond_to?(:game_extensions)
351         match.game.game_extensions.map{|ext| "*.#{ext}"}.join(' ')
352       else
353         '*.*'
354       end
355       url = KDE::FileDialog.get_save_url(
356         KDE::Url.new, pattern, self, KDE.i18n("Save game"))
357       match.url = write_game(url)
358     end
359   end
360   
361   def save_game
362     match = controller.match
363     if match
364       if match.url
365         write_game
366       else
367         save_game_as
368       end
369     end
370   end
371   
372   def write_game(url = nil)
373     return if url.is_empty or (not url.path)
374     
375     match = controller.match
376     if match
377       url ||= match.url
378       writer = match.game.game_writer
379       info = match.info
380       info[:players] = info[:players].inject({}) do |res, pl|
381         res[pl.color] = pl.name
382         res
383       end
384       result = writer.write(info, match.history)
385       write_file(url, result)
386     end
387   end
388   
389   def update_game_actions(contr)
390     unplug_action_list('game_actions')
391     if contr.match
392       game = contr.match.game
393       actions = if game.respond_to?(:actions)
394         game.actions(self, action_collection, contr.policy)
395       else
396         []
397       end
398       plug_action_list('game_actions', actions)
399     end
400   end
401   
402   def update_title
403     self.caption = @view.current_name
404   end
405   
406   def update_active_actions(contr)
407     if contr == controller
408       contr.active_actions.each do |id, enabled|
409         action = @actions[id]
410         if action
411           action.enabled = enabled
412         end
413       end
414     end
415   end