source: trunk/game/scripts/hud.py @ 278

Revision 277, 20.7 KB checked in by orlandov, 10 years ago (diff)

Ticket #79: Move the container display and examine popup code from World to Hud

  • Property svn:eol-style set to native
Line 
1#!/usr/bin/python
2
3#   This file is part of PARPG.
4
5#   PARPG is free software: you can redistribute it and/or modify
6#   it under the terms of the GNU General Public License as published by
7#   the Free Software Foundation, either version 3 of the License, or
8#   (at your option) any later version.
9
10#   PARPG is distributed in the hope that it will be useful,
11#   but WITHOUT ANY WARRANTY; without even the implied warranty of
12#   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13#   GNU General Public License for more details.
14
15#   You should have received a copy of the GNU General Public License
16#   along with PARPG.  If not, see <http://www.gnu.org/licenses/>.
17
18import shutil, fife, pychan
19from pychan.tools import callbackWithArguments as cbwa
20from scripts.parpgfilebrowser import PARPGFileBrowser
21from scripts.context_menu import ContextMenu
22from scripts import inventory
23from scripts.popups import ExaminePopup, ContainerGUI
24
25class Hud(object):
26    """Main Hud class"""
27    def __init__(self, engine, settings, inv_model, callbacks):
28        """Initialise the instance.
29           @type engine: fife.Engine
30           @param engine: An instance of the fife engine
31           @type settings: settings.Setting
32           @param settings: The settings data
33           @type inv_model: dict
34           @type callbacks: dict
35           @param callbacks: a dict of callbacks
36               saveGame: called when the user clicks on Save
37               loadGame: called when the user clicks on Load
38               quitGame: called when the user clicks on Quit
39           @return: None"""
40        pychan.init(engine, debug = True)
41
42        # TODO: perhaps this should not be hard-coded here
43        self.hud = pychan.loadXML("gui/hud.xml")
44        self.engine = engine
45        self.settings = settings
46
47        inv_callbacks = {
48            'refreshReadyImages': self.refreshReadyImages,
49            'toggleInventoryButton': self.toggleInventoryButton,
50        }
51
52        self.inventory = inventory.Inventory(self.engine, inv_model, inv_callbacks)
53        self.refreshReadyImages()
54
55        self.saveGameCallback = callbacks['saveGame']
56        self.loadGameCallback = callbacks['loadGame']
57        self.quitCallback     = callbacks['quitGame']
58
59        self.box_container = None
60        self.examine_box = None
61
62        self.actionsBox = self.hud.findChild(name="actionsBox")
63        self.actionsText = []
64        self.menu_displayed = False
65        self.initializeHud()
66        self.initializeMainMenu()
67        self.initializeContextMenu()
68        self.initializeOptionsMenu()
69        self.initializeHelpMenu()
70        self.initializeEvents()
71
72    def initializeHud(self):
73        """Initialize and show the main HUD
74           @return: None"""
75        self.events_to_map = {"menuButton":self.displayMenu,}
76        self.hud.mapEvents(self.events_to_map) 
77        # set HUD size accoriding to screen size
78        screen_width = int(self.settings.readSetting('ScreenWidth'))
79        self.hud.findChild(name="mainHudWindow").size = (screen_width, 65)
80        self.hud.findChild(name="inventoryButton").position = (screen_width-59, 7)
81        # add ready slots
82        ready1 = self.hud.findChild(name='hudReady1')
83        ready2 = self.hud.findChild(name='hudReady2')
84        ready3 = self.hud.findChild(name='hudReady3')
85        ready4 = self.hud.findChild(name='hudReady4')
86        actions_scroll_area = self.hud.findChild(name='actionsScrollArea')
87        if (screen_width <=800) :
88            gap = 0
89        else :
90            gap = 40
91        # annoying code that is both essential and boring to enter
92        ready1.position = (160+gap, 7)
93        ready2.position = (220+gap, 7)
94        ready3.position = (screen_width-180-gap, 7)
95        ready4.position = (screen_width-120-gap, 7)
96        actions_scroll_area.position = (280+gap, 5)
97        actions_width = screen_width - 470 - 2*gap
98
99        # and finally add an actions box
100        self.hud.findChild(name="actionsBox").min_size = (actions_width, 0)
101        actions_scroll_area.min_size = (actions_width, 55)
102        actions_scroll_area.max_size = (actions_width, 55)
103        # now it should be OK to display it all
104        self.hud.show()
105
106    def refreshActionsBox(self):
107        """Refresh the actions box so that it displays the contents of
108           self.actionsText
109           @return: None"""
110        self.actionsBox.items = self.actionsText
111
112    def addAction(self, action):
113        """Add an action to the actions box.
114           @type action: string
115           @param action: The text that you want to display in the actions box
116           @return: None"""
117        self.actionsText.insert(0, action)
118        self.refreshActionsBox()
119
120    def showHUD(self):
121        """Show the HUD.
122           @return: None"""
123        self.hud.show()
124
125    def hideHUD(self):
126        """Hide the HUD.
127           @return: None"""
128        self.hud.hide()
129
130    def initializeContextMenu(self):
131        """Initialize the Context Menu
132           @return: None"""
133        self.context_menu = ContextMenu (self.engine, [], (0,0))
134        self.context_menu.hide()
135
136    def showContextMenu(self, data, pos):
137        """Display the Context Menu with data at pos
138           @type data: list
139           @param data: data to pass to context menu
140           @type pos: tuple
141           @param pos: tuple of x and y coordinates
142           @return: None"""
143        self.context_menu = ContextMenu(self.engine, data, pos)
144
145    def hideContextMenu(self):
146        """Hides the context menu
147           @return: None"""
148        self.context_menu.hide()
149
150    def initializeMainMenu(self):
151        """Initalize the main menu.
152           @return: None"""
153        self.main_menu = pychan.loadXML("gui/hud_main_menu.xml")
154        self.menu_events = {"resumeButton":self.hideMenu, 
155                            "optionsButton":self.displayOptions,
156                            "helpButton":self.displayHelp}
157        self.main_menu.mapEvents(self.menu_events)
158
159    def displayMenu(self):
160        """Displays the main in-game menu.
161           @return: None"""
162        if (self.menu_displayed == False):
163            self.main_menu.show()
164            self.menu_displayed = True
165        elif (self.menu_displayed == True):
166            self.hideMenu()
167
168    def hideMenu(self):
169        """Hides the main in-game menu.
170           @return: None"""
171        self.main_menu.hide()
172        self.menu_displayed = False
173
174    def initializeHelpMenu(self):
175        """Initialize the help menu
176           @return: None"""
177        self.help_dialog = pychan.loadXML("gui/help.xml")
178        help_events = {"closeButton":self.help_dialog.hide}
179        self.help_dialog.mapEvents(help_events)
180        main_help_text = u"Welcome to Post-Apocalyptic RPG or PARPG![br][br]"\
181        "This game is still in development, so please expect for there to be bugs"\
182        " and[br]feel free to tell us about them at http://www.forums.parpg.net.[br]"\
183        "This game uses a \"Point 'N' Click\" interface, which means that to move around,[br]"\
184        "just click where you would like to go and your character will move there.[br]"\
185        "PARPG also utilizes a context menu. To access this, just right click "\
186        "anywhere[br]on the screen and a menu will come up. This menu will change"\
187        " depending on[br]what you have clicked on, hence it's name \"context menu\".[br][br]"
188       
189        k_text = u" Keybindings" 
190        k_text+="[br] A : Add a test action to the actions display"
191        k_text+="[br] I : Toggle the inventory screen"
192        k_text+="[br] F5 : Take a screenshot"
193        k_text+="[br]      (saves to <parpg>/screenshots/)"
194        k_text+="[br] Q : Quit the game"
195        self.help_dialog.distributeInitialData({
196                "MainHelpText":main_help_text,
197                "KeybindText":k_text
198                })
199
200    def displayHelp(self):
201        """Display the help screen.
202           @return: None"""
203        self.help_dialog.show()
204
205    def initializeOptionsMenu(self):
206        """Initalize the options menu.
207           @return: None"""
208        self.options_menu = pychan.loadXML("gui/hud_options.xml")
209        self.options_events = {"applyButton":self.applyOptions,
210                               "closeButton":self.options_menu.hide,
211                               "defaultsButton":self.setToDefaults,
212                               "InitialVolumeSlider":self.updateVolumeText}
213       
214        settings = self.engine.getSettings()
215        current_fullscreen = settings.isFullScreen()
216        settings.setFullScreen(True)
217        availableResolutions = settings.getPossibleResolutions()
218        self.Resolutions = [str(x[0])+'x'+str(x[1]) for x in availableResolutions];
219        settings.setFullScreen(current_fullscreen)
220        self.RenderBackends = ['OpenGL', 'SDL']
221        self.renderNumber = 0
222        if (str(self.settings.readSetting('RenderBackend')) == "SDL"):
223            self.renderNumber = 1
224        initialVolume = float(self.settings.readSetting('InitialVolume'))
225        initialVolumeText = str('Initial Volume: %.0f%s' %
226                                (int(initialVolume*10), "%"))
227        self.options_menu.distributeInitialData({
228                'ResolutionBox': self.Resolutions,
229                'RenderBox': self.RenderBackends,
230                'InitialVolumeLabel' : initialVolumeText
231                })
232
233        sFullscreen = self.settings.readSetting(name="FullScreen")
234        sSounds = self.settings.readSetting(name="PlaySounds")
235        sRender = self.renderNumber
236        sVolume = initialVolume
237
238        screen_width = self.settings.readSetting(name="ScreenWidth")
239        screen_height = self.settings.readSetting(name="ScreenHeight")
240        indexRes = str(screen_width + 'x' + screen_height)
241        try:
242            sResolution = self.Resolutions.index(indexRes)
243            resolutionInList = True
244        except:
245            resolutionInList = False
246
247        dataToDistribute = {
248                'FullscreenBox':int(sFullscreen), 
249                'SoundsBox':int(sSounds),
250                'RenderBox': sRender,
251                'InitialVolumeSlider':sVolume
252                }
253
254        if (resolutionInList == True):
255            dataToDistribute['ResolutionBox'] = sResolution
256
257        self.options_menu.distributeData(dataToDistribute)
258
259        self.options_menu.mapEvents(self.options_events)
260
261    def saveGame(self):
262        """ Called when the user wants to save the game.
263            @return: None"""
264        save_browser = PARPGFileBrowser(self.engine,
265                                   self.saveGameCallback,
266                                   savefile=True,
267                                   guixmlpath="gui/savebrowser.xml",
268                                   extensions = ('.dat'))
269        save_browser.showBrowser()
270           
271    def newGame(self):
272        """Called when user request to start a new game.
273           @return: None"""
274        print 'new game'
275
276    def loadGame(self):
277        """ Called when the user wants to load a game.
278            @return: None"""
279        load_browser = PARPGFileBrowser(self.engine,
280                                   self.loadGameCallback,
281                                   savefile=False,
282                                   guixmlpath='gui/loadbrowser.xml',
283                                   extensions=('.dat'))
284        load_browser.showBrowser()
285
286    def quitGame(self):
287        """Called when user requests to quit game.
288           @return: None"""
289
290        window = pychan.widgets.Window(title=unicode("Quit?"))
291
292        hbox = pychan.widgets.HBox()
293        are_you_sure = "Are you sure you want to quit?"
294        label = pychan.widgets.Label(text=unicode(are_you_sure))
295        yes_button = pychan.widgets.Button(name="yes_button", 
296                                           text=unicode("Yes"))
297        no_button = pychan.widgets.Button(name="no_button",
298                                          text=unicode("No"))
299
300        window.addChild(label)
301        hbox.addChild(yes_button)
302        hbox.addChild(no_button)
303        window.addChild(hbox)
304
305        events_to_map = { "yes_button": self.quitCallback,
306                          "no_button":  window.hide }
307       
308        window.mapEvents(events_to_map)
309        window.show()
310
311    def toggleInventoryButton(self):
312        """Manually toggles the inventory button.
313           @return: None"""
314        button = self.hud.findChild(name="inventoryButton")
315        if button.toggled == 0:
316            button.toggled = 1
317        else:
318            button.toggled = 0
319
320    def toggleInventory(self, toggleImage=True):
321        """Display's the inventory screen
322           @return: None"""
323
324        self.inventory.toggleInventory(toggleImage)
325
326    def refreshReadyImages(self):
327        """Make the Ready slot images on the HUD be the same as those
328           on the inventory
329           @return: None"""
330        self.setImages(self.hud.findChild(name="hudReady1"),
331                       self.inventory.getImage("Ready1").up_image)
332
333        self.setImages(self.hud.findChild(name="hudReady2"),
334                       self.inventory.getImage("Ready2").up_image)
335
336        self.setImages(self.hud.findChild(name="hudReady3"),
337                       self.inventory.getImage("Ready3").up_image)
338
339        self.setImages(self.hud.findChild(name="hudReady4"),
340                       self.inventory.getImage("Ready4").up_image)
341
342    def setImages(self, widget, image):
343        """Set the up, down, and hover images of an Imagebutton.
344           @type widget: pychan.widget
345           @param widget: widget to set
346           @type image: string
347           @param image: image to use
348           @return: None"""
349        widget.up_image = image
350        widget.down_image = image
351        widget.hover_image = image
352
353    def initializeEvents(self):
354        """Intialize Hud events
355           @return: None"""
356        events_to_map = {}
357
358        # when we click the toggle button don't change the image
359        events_to_map["inventoryButton"] = cbwa(self.toggleInventory, False)
360        events_to_map["saveButton"] = self.saveGame
361        events_to_map["loadButton"] = self.loadGame
362
363        hud_ready_buttons = ["hudReady1", "hudReady2", "hudReady3", "hudReady4"]
364
365        for item in hud_ready_buttons:
366            events_to_map[item] = cbwa(self.readyAction, item)
367
368        self.hud.mapEvents(events_to_map)
369
370        menu_events = {}
371        menu_events["newButton"] = self.newGame
372        menu_events["quitButton"] = self.quitGame
373        menu_events["saveButton"] = self.saveGame
374        menu_events["loadButton"] = self.loadGame
375        self.main_menu.mapEvents(menu_events)
376
377    def updateVolumeText(self):
378        """
379        Update the initial volume label to reflect the value of the slider
380        """
381        volume = float(self.options_menu.collectData("InitialVolumeSlider"))
382        volume_label = self.options_menu.findChild(name="InitialVolumeLabel")
383        volume_label.text = unicode("Initial Volume: %.0f%s" %
384                                    (int(volume*10), "%"))
385
386    def requireRestartDialog(self):
387        """Show a dialog asking the user to restart PARPG in order for their
388           changes to take effect.
389           @return: None"""
390        require_restart_dialog = pychan.loadXML('gui/hud_require_restart.xml')
391        require_restart_dialog.mapEvents({'okButton':require_restart_dialog.hide})
392        require_restart_dialog.show()
393
394    def applyOptions(self):
395        """Apply the current options.
396           @return: None"""
397        # At first no restart is required
398        self.requireRestart = False
399
400        # get the current values of each setting from the options menu
401        enable_fullscreen = self.options_menu.collectData('FullscreenBox')
402        enable_sound = self.options_menu.collectData('SoundsBox')
403        screen_resolution = self.options_menu.collectData('ResolutionBox')
404        partition = self.Resolutions[screen_resolution].partition('x')
405        screen_width = partition[0]
406        screen_height = partition[2]
407        render_backend = self.options_menu.collectData('RenderBox')
408        initial_volume = self.options_menu.collectData('InitialVolumeSlider')
409        initial_volume = "%.1f" % initial_volume
410
411        # get the options that are being used right now from settings.xml
412        sFullscreen = self.settings.readSetting('FullScreen')
413        sSound = self.settings.readSetting('PlaySounds')
414        sRender = self.settings.readSetting('RenderBackend')
415        sVolume = self.settings.readSetting('InitialVolume')
416
417        sScreenHeight = self.settings.readSetting('ScreenHeight')
418        sScreenWidth = self.settings.readSetting('ScreenWidth')
419        sResolution = sScreenWidth + 'x' + sScreenHeight
420
421        # On each:
422        # - Check to see whether the option within the xml matches the
423        #   option within the options menu
424        # - If they do not match, set the option within the xml to
425        #   to be what is within the options menu
426        # - Require a restart
427
428        if (int(enable_fullscreen) != int(sFullscreen)):
429            self.setOption('FullScreen', int(enable_fullscreen))
430            self.requireRestart = True
431           
432        if (int(enable_sound) != int(sSound)):
433            self.setOption('PlaySounds', int(enable_sound))
434            self.requireRestart = True
435
436        if (screen_resolution != sResolution):
437            self.setOption('ScreenWidth', int(screen_width))
438            self.setOption('ScreenHeight', int(screen_height))
439            self.requireRestart = True
440
441        # Convert the number from the list of render backends to
442        # the string that FIFE wants for its settings.xml
443        if (render_backend == 0):
444            render_backend = 'OpenGL'
445        else:
446            render_backend = 'SDL'
447
448        if (render_backend != str(sRender)):
449            self.setOption('RenderBackend', render_backend)
450            self.requireRestart = True
451
452        if (initial_volume != float(sVolume)):
453            self.setOption('InitialVolume', initial_volume)
454            self.requireRestart = True
455       
456        # Write all the settings to settings.xml
457        self.settings.tree.write('settings.xml')
458       
459        # If the changes require a restart, popup the dialog telling
460        # the user to do so
461        if (self.requireRestart):
462            self.requireRestartDialog()
463        # Once we are done, we close the options menu
464        self.options_menu.hide()
465
466    def setOption(self, name, value):
467        """Set an option within the xml.
468           @type name: string
469           @param name: The name of the option within the xml
470           @type value: any
471           @param value: The value that the option 'name' should be set to
472           @return: None"""
473        element = self.settings.root_element.find(name)
474        if(element != None):
475            if(value != element.text):
476                element.text = str(value)
477        else:
478            print 'Setting,', name, 'does not exist!'
479
480    def setToDefaults(self):
481        """Reset all the options to the options in settings-dist.xml.
482           @return: None"""
483        shutil.copyfile('settings-dist.xml', 'settings.xml')
484        self.requireRestartDialog()
485        self.options_menu.hide()
486
487    def displayOptions(self):
488        """Display the options menu.
489           @return: None"""
490        self.options_menu.show()
491   
492    def readyAction(self, ready_button):
493        """ Called when the user selects a ready button from the HUD """
494        text = "Used the item from %s" % ready_button
495        self.addAction(text)
496       
497    def createBoxGUI(self, title):
498        """Creates a window to display the contents of a box
499           @type title: string
500           @param title: The title for the window
501           @return: None"""
502        if self.box_container:
503            # if it has already been created, just show it
504            self.box_container.showContainer()
505        else:
506            # otherwise create it then show it
507            data = ["dagger01", "empty", "empty", "empty", "empty",
508                    "empty", "empty", "empty", "empty"]
509            self.box_container = ContainerGUI(self.engine, unicode(title), data)
510            def close_and_delete():
511                self.hideContainer()
512            events = {'takeAllButton':close_and_delete,
513                      'closeButton':close_and_delete}
514            self.box_container.container_gui.mapEvents(events)
515            self.box_container.showContainer()
516
517    def hideContainer(self):
518        """Hide the container box
519           @return: None"""
520        if self.box_container:
521            self.box_container.hideContainer()
522
523    def createExamineBox(self, title, desc):
524        """Create an examine box. It displays some textual description of an
525           object
526           @type title: string
527           @param title: The title of the examine box
528           @type desc: string
529           @param desc: The main body of the examine box
530           @return: None"""
531
532        if self.examine_box:
533            self.examine_box.closePopUp()
534        self.examine_box = ExaminePopup(self.engine, title, desc)
535        self.examine_box.showPopUp()
Note: See TracBrowser for help on using the repository browser.