source: trunk/game/scripts/gui/hud.py @ 424

Revision 424, 24.7 KB checked in by b0rland_parpg, 10 years ago (diff)

Ticket #107: patch by b0rland

  • Reworked inventory gui to use new inventory model
  • Made various additions and fixes to inventory model and base classes
  • Improved drag-and-drop support
  • Possibly broke dialog gui :(
  • 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
19import fife
20import pychan
21import pychan.widgets as widgets
22from pychan.tools import callbackWithArguments as cbwa
23from scripts.gui.filebrowser import FileBrowser
24from scripts.gui.context_menu import ContextMenu
25from scripts.gui import inventorygui
26from scripts.gui.popups import ExaminePopup, ContainerGUI
27from scripts.gui.dialoguegui import DialogueGUI
28
29class Hud(object):
30    """Main Hud class"""
31    def __init__(self, engine, settings, data, callbacks):
32        """Initialise the instance.
33           @type engine: fife.Engine
34           @param engine: An instance of the fife engine
35           @type settings: settings.Setting
36           @param settings: The settings data
37           @type inv_model: dict
38           @type callbacks: dict
39           @param callbacks: a dict of callbacks
40               saveGame: called when the user clicks on Save
41               loadGame: called when the user clicks on Load
42               quitGame: called when the user clicks on Quit
43           @return: None"""
44        pychan.init(engine, debug = True)
45
46        # TODO: perhaps this should not be hard-coded here
47        self.hud = pychan.loadXML("gui/hud.xml")
48        self.engine = engine
49        self.settings = settings
50        self.data = data
51        self.inventory = None
52       
53
54
55        self.save_game_callback = callbacks['saveGame']
56        self.load_game_callback = callbacks['loadGame']
57        self.quit_callback      = callbacks['quitGame']
58
59        self.box_container = None
60        self.examine_box = None
61
62        self.actions_box = self.hud.findChild(name="actionsBox")
63        self.actions_text = []
64        self.menu_displayed = False
65        self.initializeHud()
66        self.initializeMainMenu()
67        self.initializeContextMenu()
68        self.initializeOptionsMenu()
69        self.initializeHelpMenu()
70        self.initializeEvents()
71        self.initializeQuitDialog()
72
73    def initializeHud(self):
74        """Initialize and show the main HUD
75           @return: None"""
76        self.events_to_map = {"menuButton":self.displayMenu,}
77        self.hud.mapEvents(self.events_to_map) 
78        # set HUD size according to screen size
79        screen_width = int(self.settings.readSetting('ScreenWidth'))
80        self.hud.findChild(name="mainHudWindow").size = (screen_width, 65)
81        self.hud.findChild(name="inventoryButton").position = \
82                                                    (screen_width-59, 7)
83        # add ready slots
84        ready1 = self.hud.findChild(name='hudReady1')
85        ready2 = self.hud.findChild(name='hudReady2')
86        ready3 = self.hud.findChild(name='hudReady3')
87        ready4 = self.hud.findChild(name='hudReady4')
88        actions_scroll_area = self.hud.findChild(name='actionsScrollArea')
89        if (screen_width <=800) :
90            gap = 0
91        else :
92            gap = 40
93        # annoying code that is both essential and boring to enter
94        ready1.position = (160+gap, 7)
95        ready2.position = (220+gap, 7)
96        ready3.position = (screen_width-180-gap, 7)
97        ready4.position = (screen_width-120-gap, 7)
98        actions_scroll_area.position = (280+gap, 5)
99        actions_width = screen_width - 470 - 2*gap
100
101        # and finally add an actions box
102        self.hud.findChild(name="actionsBox").min_size = (actions_width, 0)
103        actions_scroll_area.min_size = (actions_width, 55)
104        actions_scroll_area.max_size = (actions_width, 55)
105        # now it should be OK to display it all
106        self.hud.show()
107
108    def refreshActionsBox(self):
109        """Refresh the actions box so that it displays the contents of
110           self.actions_text
111           @return: None"""
112        self.actions_box.items = self.actions_text
113
114    def addAction(self, action):
115        """Add an action to the actions box.
116           @type action: string
117           @param action: The text that you want to display in the actions box
118           @return: None"""
119        self.actions_text.insert(0, action)
120        self.refreshActionsBox()
121
122    def showHUD(self):
123        """Show the HUD.
124           @return: None"""
125        self.hud.show()
126
127    def hideHUD(self):
128        """Hide the HUD.
129           @return: None"""
130        self.hud.hide()
131
132    def initializeInventory(self):
133        inv_callbacks = {
134            'refreshReadyImages': self.refreshReadyImages,
135            'toggleInventoryButton': self.toggleInventoryButton,
136        }
137        self.inventory = inventorygui.InventoryGUI(self.engine,
138                                            self.data.game_state.PC.inventory,
139                                            inv_callbacks)
140        self.refreshReadyImages()
141       
142
143    def initializeContextMenu(self):
144        """Initialize the Context Menu
145           @return: None"""
146        self.context_menu = ContextMenu (self.engine, [], (0,0))
147        self.context_menu.hide()
148
149    def showContextMenu(self, data, pos):
150        """Display the Context Menu with data at pos
151           @type data: list
152           @param data: data to pass to context menu
153           @type pos: tuple
154           @param pos: tuple of x and y coordinates
155           @return: None"""
156        self.context_menu = ContextMenu(self.engine, data, pos)
157
158    def hideContextMenu(self):
159        """Hides the context menu
160           @return: None"""
161        self.context_menu.hide()
162
163    def initializeMainMenu(self):
164        """Initalize the main menu.
165           @return: None"""
166        self.main_menu = pychan.loadXML("gui/hud_main_menu.xml")
167        self.menu_events = {"resumeButton":self.hideMenu, 
168                            "optionsButton":self.displayOptions,
169                            "helpButton":self.displayHelp}
170        self.main_menu.mapEvents(self.menu_events)
171
172    def displayMenu(self):
173        """Displays the main in-game menu.
174           @return: None"""
175        if (self.menu_displayed == False):
176            self.main_menu.show()
177            self.menu_displayed = True
178        elif (self.menu_displayed == True):
179            self.hideMenu()
180
181    def hideMenu(self):
182        """Hides the main in-game menu.
183           @return: None"""
184        self.main_menu.hide()
185        self.menu_displayed = False
186
187
188    def initializeHelpMenu(self):
189        """Initialize the help menu
190           @return: None"""
191        self.help_dialog = pychan.loadXML("gui/help.xml")
192        help_events = {"closeButton":self.help_dialog.hide}
193        self.help_dialog.mapEvents(help_events)
194        main_help_text = u"Welcome to Post-Apocalyptic RPG or PARPG![br][br]"\
195        "This game is still in development, so please expect for there to be "\
196        "bugs and[br]feel free to tell us about them at "\
197        "http://www.forums.parpg.net.[br]This game uses a "\
198        "\"Point 'N' Click\" interface, which means that to move around,[br]"\
199        "just click where you would like to go and your character will move "\
200        "there.[br]PARPG also utilizes a context menu. To access this, just "\
201        "right click anywhere[br]on the screen and a menu will come up. This "\
202        "menu will change depending on[br]what you have clicked on, hence "\
203        "it's name \"context menu\".[br][br]"
204       
205        k_text = u" Keybindings" 
206        k_text += "[br] A : Add a test action to the actions display"
207        k_text += "[br] I : Toggle the inventory screen"
208        k_text += "[br] F5 : Take a screenshot"
209        k_text += "[br]      (saves to <parpg>/screenshots/)"
210        k_text += "[br] Q : Quit the game"
211        self.help_dialog.distributeInitialData({
212                "MainHelpText":main_help_text,
213                "KeybindText":k_text
214                })
215
216    def displayHelp(self):
217        """Display the help screen.
218           @return: None"""
219        self.help_dialog.show()
220
221    def switchResolutions(self):
222        """ Sync the contents of the resolution box (windowed or fullscreen
223            resolutions to the selection made in the Fullscreen checkbox.
224            @return: None"""
225           
226        if self.options_menu.collectData('FullscreenBox'):
227            self.options_menu.distributeInitialData({
228                                'ResolutionBox' : self.resolutions_fullscreen
229                                                     })
230        else:
231            self.options_menu.distributeInitialData({
232                                'ResolutionBox' : self.resolutions_windowed
233                                                     })
234       
235    def initializeOptionsMenu(self):
236        """Initialize the options menu, this will generate a list of fullscreen
237           resolutions and a list of windowed resolutions. Next to this the
238           current active settings are read and also selected in the matching
239           widgets.
240           @return: None"""
241           
242        self.options_menu = pychan.loadXML("gui/hud_options.xml")
243        self.options_events = {"applyButton":self.applyOptions,
244                               "closeButton":self.options_menu.hide,
245                               "defaultsButton":self.setToDefaults,
246                               "FullscreenBox": self.switchResolutions,
247                               "InitialVolumeSlider":self.updateVolumeText}
248       
249        settings = self.engine.getSettings()
250        # The settings need to be set to fullscreen for the call to
251        # getPossibleResolutions() to function.
252        current_fullscreen = settings.isFullScreen()
253        settings.setFullScreen(True)
254       
255        available_fullscreen_resolutions = settings.getPossibleResolutions()
256        available_windowed_resolutions = ((1920, 1200), (1920, 1080), \
257                                          (1856, 1392), (1792, 1344), \
258                                          (1680, 1050), (1600, 1200), \
259                                          (1600, 1024), (1440,  900), \
260                                          (1400, 1050), (1360,  768), \
261                                          (1280, 1024), (1280,  960), \
262                                          (1152,  864), (1024,  768))
263        # Filter too small resolutions from the fullscreen resolutions
264        self.resolutions_fullscreen = []
265        for x in available_fullscreen_resolutions:
266            if x[0] >= 1024 and x[1] >= 768:
267                self.resolutions_fullscreen.append(str(x[0]) + 'x' + str(x[1]))
268
269        # Filter too large resolution from the windowed resolutions
270        self.resolutions_windowed = []
271        for x in available_windowed_resolutions:
272            if x[0] <= available_fullscreen_resolutions[0][0] and \
273            x[1] <= available_fullscreen_resolutions[0][1]:
274                self.resolutions_windowed.append(str(x[0]) + 'x' + str(x[1]))       
275       
276        settings.setFullScreen(current_fullscreen)
277        self.render_backends = ['OpenGL', 'SDL']
278        self.render_number = 0
279        if (str(self.settings.readSetting('RenderBackend')) == "SDL"):
280            self.render_number = 1
281        initial_volume = float(self.settings.readSetting('InitialVolume'))
282        initial_volume_text = str('Initial Volume: %.0f%s' %
283                                (int(initial_volume*10), "%"))
284        initial_data_to_distribute = {   
285                'RenderBox'          : self.render_backends,
286                'InitialVolumeLabel' : initial_volume_text
287                }
288
289        s_fullscreen = self.settings.readSetting(name="FullScreen")
290        s_sounds = self.settings.readSetting(name="PlaySounds")
291        s_render = self.render_number
292        s_volume = initial_volume
293
294        # Find the current active resolution so we can select it
295        screen_width = self.settings.readSetting(name="ScreenWidth")
296        screen_height = self.settings.readSetting(name="ScreenHeight")
297        index_res = str(screen_width + 'x' + screen_height)
298        try:
299            if int(s_fullscreen) == 0:
300                s_resolution = self.resolutions_windowed.index(index_res)
301            else:
302                s_resolution = self.resolutions_fullscreen.index(index_res)
303            resolution_in_list = True
304        except:
305            resolution_in_list = False
306           
307        data_to_distribute = {
308                'FullscreenBox'      : int(s_fullscreen), 
309                'SoundsBox'          : int(s_sounds),
310                'RenderBox'          : s_render,
311                'InitialVolumeSlider': s_volume
312                }
313
314        if int(s_fullscreen) == 0:
315            initial_data_to_distribute['ResolutionBox'] = self.resolutions_windowed
316        else:
317            initial_data_to_distribute['ResolutionBox'] = self.resolutions_fullscreen
318           
319        if (resolution_in_list == True):
320            data_to_distribute['ResolutionBox'] = s_resolution
321
322        self.options_menu.distributeInitialData(initial_data_to_distribute)
323        self.options_menu.distributeData(data_to_distribute)
324        self.options_menu.mapEvents(self.options_events)
325
326    def saveGame(self):
327        """ Called when the user wants to save the game.
328            @return: None"""
329        save_browser = FileBrowser(self.engine,
330                                   self.save_game_callback,
331                                   save_file=True,
332                                   gui_xml_path="gui/savebrowser.xml",
333                                   extensions = ('.dat'))
334        save_browser.showBrowser()
335           
336    def newGame(self):
337        """Called when user request to start a new game.
338           @return: None"""
339        print 'new game'
340
341    def loadGame(self):
342        """ Called when the user wants to load a game.
343            @return: None"""
344        load_browser = FileBrowser(self.engine,
345                                   self.load_game_callback,
346                                   save_file=False,
347                                   gui_xml_path='gui/loadbrowser.xml',
348                                   extensions=('.dat'))
349        load_browser.showBrowser()
350   
351    def initializeQuitDialog(self):
352        """Creates the quit confirmation dialog
353           @return: None"""
354        self.quit_window = pychan.widgets.Window(title=unicode("Quit?"), \
355                                                 min_size=(200,0))
356
357        hbox = pychan.widgets.HBox()
358        are_you_sure = "Are you sure you want to quit?"
359        label = pychan.widgets.Label(text=unicode(are_you_sure))
360        yes_button = pychan.widgets.Button(name="yes_button", 
361                                           text=unicode("Yes"),
362                                           min_size=(90,20),
363                                           max_size=(90,20))
364        no_button = pychan.widgets.Button(name="no_button",
365                                          text=unicode("No"),
366                                          min_size=(90,20),
367                                          max_size=(90,20))
368
369        self.quit_window.addChild(label)
370        hbox.addChild(yes_button)
371        hbox.addChild(no_button)
372        self.quit_window.addChild(hbox)
373
374        events_to_map = { "yes_button": self.quit_callback,
375                          "no_button":  self.quit_window.hide }
376       
377        self.quit_window.mapEvents(events_to_map)
378
379
380    def quitGame(self):
381        """Called when user requests to quit game.
382           @return: None"""
383
384        self.quit_window.show()
385
386    def toggleInventoryButton(self):
387        """Manually toggles the inventory button.
388           @return: None"""
389        button = self.hud.findChild(name="inventoryButton")
390        if button.toggled == 0:
391            button.toggled = 1
392        else:
393            button.toggled = 0
394
395    def toggleInventory(self, toggleImage=True):
396        """Display's the inventory screen
397           @return: None"""
398        if self.inventory == None:
399            self.initializeInventory()
400        self.inventory.toggleInventory(toggleImage)
401
402    def refreshReadyImages(self):
403        """Make the Ready slot images on the HUD be the same as those
404           on the inventory
405           @return: None"""
406        self.setImages(self.hud.findChild(name="hudReady1"),
407                       self.inventory.getImage("Ready1").up_image)
408
409        self.setImages(self.hud.findChild(name="hudReady2"),
410                       self.inventory.getImage("Ready2").up_image)
411
412        self.setImages(self.hud.findChild(name="hudReady3"),
413                       self.inventory.getImage("Ready3").up_image)
414
415        self.setImages(self.hud.findChild(name="hudReady4"),
416                       self.inventory.getImage("Ready4").up_image)
417
418    def setImages(self, widget, image):
419        """Set the up, down, and hover images of an Imagebutton.
420           @type widget: pychan.widget
421           @param widget: widget to set
422           @type image: string
423           @param image: image to use
424           @return: None"""
425        widget.up_image = image
426        widget.down_image = image
427        widget.hover_image = image
428
429    def initializeEvents(self):
430        """Intialize Hud events
431           @return: None"""
432        events_to_map = {}
433
434        # when we click the toggle button don't change the image
435        events_to_map["inventoryButton"] = cbwa(self.toggleInventory, False)
436        events_to_map["saveButton"] = self.saveGame
437        events_to_map["loadButton"] = self.loadGame
438
439        hud_ready_buttons = ["hudReady1", "hudReady2", \
440                             "hudReady3", "hudReady4"]
441
442        for item in hud_ready_buttons:
443            events_to_map[item] = cbwa(self.readyAction, item)
444
445        self.hud.mapEvents(events_to_map)
446
447        menu_events = {}
448        menu_events["newButton"] = self.newGame
449        menu_events["quitButton"] = self.quitGame
450        menu_events["saveButton"] = self.saveGame
451        menu_events["loadButton"] = self.loadGame
452        self.main_menu.mapEvents(menu_events)
453
454    def updateVolumeText(self):
455        """
456        Update the initial volume label to reflect the value of the slider
457        """
458        volume = float(self.options_menu.collectData("InitialVolumeSlider"))
459        volume_label = self.options_menu.findChild(name="InitialVolumeLabel")
460        volume_label.text = unicode("Initial Volume: %.0f%s" %
461                                    (int(volume*10), "%"))
462
463    def requireRestartDialog(self):
464        """Show a dialog asking the user to restart PARPG in order for their
465           changes to take effect.
466           @return: None"""
467        require_restart_dialog = pychan.loadXML('gui/hud_require_restart.xml')
468        require_restart_dialog.mapEvents(\
469                                {'okButton':require_restart_dialog.hide})
470        require_restart_dialog.show()
471
472    def applyOptions(self):
473        """Apply the current options.
474           @return: None"""
475        # At first no restart is required
476        self.require_restart = False
477
478        # get the current values of each setting from the options menu
479        enable_fullscreen = self.options_menu.collectData('FullscreenBox')
480        enable_sound = self.options_menu.collectData('SoundsBox')
481        screen_resolution = self.options_menu.collectData('ResolutionBox')
482        if enable_fullscreen:
483            partition = self.resolutions_fullscreen[screen_resolution].partition('x')
484        else:
485            partition = self.resolutions_windowed[screen_resolution].partition('x')
486        screen_width = partition[0]
487        screen_height = partition[2]
488        render_backend = self.options_menu.collectData('RenderBox')
489        initial_volume = self.options_menu.collectData('InitialVolumeSlider')
490        initial_volume = "%.1f" % initial_volume
491
492        # get the options that are being used right now from settings.xml
493        s_fullscreen = self.settings.readSetting('FullScreen')
494        s_sounds = self.settings.readSetting('PlaySounds')
495        s_render = self.settings.readSetting('RenderBackend')
496        s_volume = self.settings.readSetting('InitialVolume')
497
498        s_screen_height = self.settings.readSetting('ScreenHeight')
499        s_screen_width = self.settings.readSetting('ScreenWidth')
500        s_resolution = s_screen_width + 'x' + s_screen_height
501
502        # On each:
503        # - Check to see whether the option within the xml matches the
504        #   option within the options menu
505        # - If they do not match, set the option within the xml to
506        #   to be what is within the options menu
507        # - Require a restart
508
509        if (int(enable_fullscreen) != int(s_fullscreen)):
510            self.setOption('FullScreen', int(enable_fullscreen))
511            self.require_restart = True
512           
513        if (int(enable_sound) != int(s_sounds)):
514            self.setOption('PlaySounds', int(enable_sound))
515            self.require_restart = True
516
517        if (screen_resolution != s_resolution):
518            self.setOption('ScreenWidth', int(screen_width))
519            self.setOption('ScreenHeight', int(screen_height))
520            self.require_restart = True
521
522        # Convert the number from the list of render backends to
523        # the string that FIFE wants for its settings.xml
524        if (render_backend == 0):
525            render_backend = 'OpenGL'
526        else:
527            render_backend = 'SDL'
528
529        if (render_backend != str(s_render)):
530            self.setOption('RenderBackend', render_backend)
531            self.require_restart = True
532
533        if (initial_volume != float(s_volume)):
534            self.setOption('InitialVolume', initial_volume)
535            self.require_restart = True
536       
537        # Write all the settings to settings.xml
538        self.settings.tree.write('settings.xml')
539       
540        # If the changes require a restart, popup the dialog telling
541        # the user to do so
542        if (self.require_restart):
543            self.requireRestartDialog()
544        # Once we are done, we close the options menu
545        self.options_menu.hide()
546
547    def setOption(self, name, value):
548        """Set an option within the xml.
549           @type name: string
550           @param name: The name of the option within the xml
551           @type value: any
552           @param value: The value that the option 'name' should be set to
553           @return: None"""
554        element = self.settings.root_element.find(name)
555        if(element != None):
556            if(value != element.text):
557                element.text = str(value)
558        else:
559            print 'Setting,', name, 'does not exist!'
560
561    def setToDefaults(self):
562        """Reset all the options to the options in settings-dist.xml.
563           @return: None"""
564        shutil.copyfile('settings-dist.xml', 'settings.xml')
565        self.requireRestartDialog()
566        self.options_menu.hide()
567
568    def displayOptions(self):
569        """Display the options menu.
570           @return: None"""
571        self.options_menu.show()
572   
573    def readyAction(self, ready_button):
574        """ Called when the user selects a ready button from the HUD """
575        text = "Used the item from %s" % ready_button
576        self.addAction(text)
577       
578    def createBoxGUI(self, title, container, events = ()):
579        """Creates a window to display the contents of a box
580           @type title: string
581           @param title: The title for the window
582           @param items: The box to display
583           @param events: The events of the window
584           @return: A new ContainerGui"""
585        box_container = ContainerGUI(self.engine, \
586                                              unicode(title), container)
587        box_container.container_gui.mapEvents(events)
588        return box_container
589
590    def hideContainer(self):
591        """Hide the container box
592           @return: None"""
593        if self.box_container:
594            self.box_container.hideContainer()
595
596    def createExamineBox(self, title, desc):
597        """Create an examine box. It displays some textual description of an
598           object
599           @type title: string
600           @param title: The title of the examine box
601           @type desc: string
602           @param desc: The main body of the examine box
603           @return: None"""
604
605        if self.examine_box is not None:
606            self.examine_box.closePopUp()
607        self.examine_box = ExaminePopup(self.engine, title, desc)
608        self.examine_box.showPopUp()
609
610    def showDialogue(self, npc):
611        """Show the NPC dialogue window
612           @type npc: actors.NonPlayerCharacter
613           @param npc: the npc that we are having a dialogue with
614           @return: None"""
615        dialogue = DialogueGUI(
616                    npc,
617                    self.data.game_state.quest_engine,
618                    self.data.game_state.PC)
619        dialogue.initiateDialogue()
Note: See TracBrowser for help on using the repository browser.