NovaBryan/EVE_Dump
Log | Files | 📦 下载最新源码 (.zip) | Refs | README

dungeoneditor.py (71885B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\inflight\dungeoneditor.py
      2 import copy
      3 import blue
      4 import evecamera
      5 import evetypes
      6 import localization
      7 import trinity
      8 import uthread
      9 import utillib
     10 from carbonui import uiconst
     11 from carbonui.control.combo import Combo
     12 from carbonui.control.singlelineedits.singleLineEditFloat import SingleLineEditFloat
     13 from carbonui.control.singlelineedits.singleLineEditInteger import SingleLineEditInteger
     14 from carbonui.control.singlelineedits.singleLineEditText import SingleLineEditText
     15 from carbonui.primitives.container import Container
     16 from carbonui.primitives.line import Line
     17 from eve.client.script.parklife import dungeonHelper, states
     18 from carbonui.control.button import Button
     19 from carbonui.control.checkbox import Checkbox
     20 from eve.client.script.ui.control.entries.generic import Generic
     21 from eve.client.script.ui.control.entries.util import GetFromClass
     22 from carbonui.control.window import Window
     23 from eve.client.script.ui.control.listgroup import ListGroup
     24 from player_messaging.client.ui_message import message_player, prompt_player_fsd_dialog
     25 from carbonui.control.tabGroup import TabGroup
     26 from eve.client.script.ui.util import uix
     27 from eve.common.lib import appConst as const
     28 from evecamera.dungeonhack import DungeonHack
     29 from ballparkCommon.ballRadius import ComputeRadiusFromQuantity
     30 from eve.client.script.ui.control import eveIcon, eveLabel, eveScroll
     31 from carbonui.uicore import uicore
     32 from eveservices.menu import GetMenuService
     33 pi = 3.141592653589793
     34 
     35 def GetMessageFromLocalization(messageID):
     36     return localization.GetByMessageID(messageID)
     37 
     38 
     39 class DungeonEditor(Window):
     40     __guid__ = 'form.DungeonEditor'
     41     __nonpersistvars__ = []
     42     __notifyevents__ = ['OnJessicaOpenDungeon',
     43      'OnJessicaOpenRoom',
     44      'OnDESelectionChanged',
     45      'OnDEObjectPaletteChanged',
     46      'OnDEObjectListChanged',
     47      'OnSelectObject',
     48      'OnDungeonEdit',
     49      'OnDungeonSelectionGroupRotation',
     50      'OnStateChange']
     51     default_windowID = 'dungeoneditor'
     52 
     53     def ApplyAttributes(self, attributes):
     54         Window.ApplyAttributes(self, attributes)
     55         self._navigation = DungeonHack()
     56         self.loadedTab = None
     57         self.roomTabSelected = 'Objects'
     58         self.loadingThread = None
     59         self.scope = uiconst.SCOPE_INFLIGHT
     60         self.SetCaption('Dungeon Editor')
     61         self.LoadPanels()
     62         self.SetMinSize([350, 400])
     63         self.prevDungeonResults = None
     64         self.prevFactionID = None
     65         self.prevArchetypeID = None
     66         self.filterText = ''
     67         self.objectGroups = {}
     68         self.groupNameToIDMap = {}
     69         self.scenario = sm.GetService('scenario')
     70         if dungeonHelper.IsJessicaOpen():
     71             self.cache = sm.GetService('cache')
     72         else:
     73             self.cache = sm.RemoteSvc('cache')
     74         sm.GetService('sceneManager').SetPrimaryCamera(evecamera.CAM_TACTICAL)
     75 
     76     def LoadPanels(self):
     77         panel = Container(name='panel', parent=self.sr.main, left=const.defaultPadding, top=const.defaultPadding, width=const.defaultPadding, height=const.defaultPadding)
     78         self.sr.panel = panel
     79         self.sr.roomobjecttabs = TabGroup(name='tabparent', parent=self.sr.main, idx=0, tabs=[['Objects',
     80           panel,
     81           self,
     82           'RoomObjectTab'], ['Groups',
     83           panel,
     84           self,
     85           'RoomGroupTab']], groupID='roomobjectstab')
     86         self.sr.maintabs = TabGroup(name='tabparent', parent=self.sr.main, idx=0, tabs=[['Dungeons',
     87           panel,
     88           self,
     89           'DungeonTab'],
     90          ['Room objects',
     91           panel,
     92           self,
     93           'RoomTab'],
     94          ['Transform',
     95           panel,
     96           self,
     97           'AlignTab'],
     98          ['Settings',
     99           panel,
    100           self,
    101           'SettingTab'],
    102          ['Palette',
    103           panel,
    104           self,
    105           'PaletteTab']], groupID='tabgroupid')
    106 
    107     def Load(self, tabid):
    108         if self.loadingThread and self.loadingThread.alive:
    109             self.loadingThread.kill()
    110         self.UnloadPanel()
    111         self.loadedTab = None
    112         tabName = 'Load_%s' % tabid
    113         if hasattr(self, tabName):
    114             if tabName in ('Load_RoomTab', 'Load_RoomObjectTab', 'Load_RoomGroupTab'):
    115                 self.sr.roomobjecttabs.display = True
    116             else:
    117                 self.sr.roomobjecttabs.display = False
    118             self.loadedTab = tabid
    119             self.loadingThread = uthread.new(getattr(self, tabName))
    120             self.loadingThread.context = 'DungeonEditor::%s' % tabName
    121 
    122     def UpdateGridVisibility(self):
    123         if self.loadedTab == 'PaletteTab':
    124             self._navigation.SetGridState(True)
    125             self._navigation.SetDrawAxis(True)
    126         else:
    127             showGrid = self.gridCheckbox.GetValue()
    128             self._navigation.SetGridState(showGrid)
    129             self._navigation.SetDrawAxis(showGrid)
    130         self._navigation.SetGridSpacing(self.gridSpacingDropdown.GetValue())
    131         self._navigation.SetGridLength(self.gridSizeDropdown.GetValue())
    132 
    133     def IsTabLoaded(self, tabId):
    134         return tabId == self.loadedTab
    135 
    136     def Refresh(self):
    137         self.Load(self.loadedTab)
    138 
    139     def UnloadPanel(self):
    140         self.sr.panel.Flush()
    141         self.sr.palettescroll = None
    142         self.sr.templatescroll = None
    143 
    144     def _GetDungeons(self):
    145         archetypeID = settings.user.ui.Get('dungeonArchetypeID', None)
    146         factionID = settings.user.ui.Get('dungeonFactionID', None)
    147         if self.prevDungeonResults is None or self.prevArchetypeID != archetypeID or self.prevFactionID != factionID:
    148             comboOptions = []
    149             dungeons = sm.RemoteSvc('dungeon').DEGetDungeons(archetypeID=archetypeID, factionID=factionID)
    150             for dungeon in dungeons:
    151                 factionID = dungeon.factionID
    152                 if dungeon.dungeonNameID is not None:
    153                     name = GetMessageFromLocalization(dungeon.dungeonNameID)
    154                 else:
    155                     name = ''
    156                 if factionID:
    157                     factionName = cfg.eveowners.Get(factionID).name
    158                     name = '%s (%s) [%d]' % (name, factionName, dungeon.dungeonID)
    159                 else:
    160                     name = '%s [%d]' % (name, dungeon.dungeonID)
    161                 comboOptions.append((name, dungeon.dungeonID))
    162 
    163             comboOptions.sort()
    164             comboOptions.insert(0, [' - Select Dungeon - ', None])
    165             self.prevArchetypeID = archetypeID
    166             self.prevFactionID = factionID
    167             self.prevDungeonResults = comboOptions
    168         comboOptions = self.prevDungeonResults
    169         filterText = self.filterText.lower()
    170         optionsFilter = lambda option: filterText in option[0].lower()
    171         comboOptions = filter(optionsFilter, comboOptions)
    172         availableDungeons = map(lambda option: option[1], comboOptions)
    173         return (comboOptions, availableDungeons)
    174 
    175     def _CreateCombo(self, comboOptions, comboID):
    176         comboSetval = settings.user.ui.Get(comboID, None)
    177         if comboSetval == 'All':
    178             settings.user.ui.Set(comboID, None)
    179             comboSetval = None
    180         return Combo(parent=self.sr.panel, label='', options=comboOptions, name=comboID, select=comboSetval, callback=self.OnComboChange, align=uiconst.TOTOP)
    181 
    182     def OnFilterDungeon(self, *args):
    183         self.filterText = self.dungeonFilter.GetValue()
    184         self.sr.maintabs.ReloadVisible()
    185 
    186     def OnGodMode(self, *args):
    187         settings.user.ui.Set('dungeonGodMode', self.godModeCheckbox.GetValue())
    188 
    189     def Load_DungeonTab(self):
    190         eveLabel.EveLabelMedium(text='Search Dungeons:', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    191         Line(parent=self.sr.panel, align=uiconst.TOTOP)
    192         Container(name='push', parent=self.sr.panel, align=uiconst.TOTOP, height=const.defaultPadding)
    193         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=20)
    194         sb = Button(parent=row, label='Search', func=self.OnFilterDungeon, align=uiconst.CENTERRIGHT, left=2)
    195         self.dungeonFilter = SingleLineEditText(name='dungeonFilter', parent=row, setvalue=self.filterText, align=uiconst.TOALL, padRight=sb.width + 4, OnReturn=self.OnFilterDungeon)
    196         Container(name='push', parent=self.sr.panel, align=uiconst.TOTOP, height=const.defaultPadding)
    197         eveLabel.EveLabelMedium(text='Optional Filters:', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    198         Line(parent=self.sr.panel, align=uiconst.TOTOP)
    199         Container(name='push', parent=self.sr.panel, align=uiconst.TOTOP, height=const.defaultPadding)
    200         eveLabel.EveLabelMedium(text='Archetype:', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    201         archetypeOptions = sm.RemoteSvc('dungeon').DEGetArchetypes()
    202         archetypeOptions.sort()
    203         archetypeOptions.insert(0, ('All', None))
    204         self._CreateCombo(archetypeOptions, 'dungeonArchetypeID')
    205         Container(name='push', parent=self.sr.panel, align=uiconst.TOTOP, height=const.defaultPadding)
    206         eveLabel.EveLabelMedium(text='Faction:', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    207         factions = sm.RemoteSvc('dungeon').DEGetFactions()
    208         factionOptions = [ (cfg.eveowners.Get(factionID).name, factionID) for factionID in factions ]
    209         factionOptions.sort()
    210         factionOptions.insert(0, ('All', None))
    211         self._CreateCombo(factionOptions, 'dungeonFactionID')
    212         Container(name='pusher', align=uiconst.TOTOP, height=24, parent=self.sr.panel)
    213         eveLabel.EveLabelMedium(text='Select Dungeon:', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    214         Line(parent=self.sr.panel, align=uiconst.TOTOP)
    215         Container(name='push', parent=self.sr.panel, align=uiconst.TOTOP, height=const.defaultPadding)
    216         comboOptions, availableDungeons = self._GetDungeons()
    217         comboID = 'dungeonDungeon'
    218         dungeonID = settings.user.ui.Get(comboID, 'All')
    219         Combo(parent=self.sr.panel, label='', options=comboOptions, name=comboID, select=dungeonID, callback=self.OnComboChange, align=uiconst.TOTOP)
    220         Container(name='pusher', align=uiconst.TOTOP, height=16, parent=self.sr.panel)
    221         eveLabel.EveLabelMedium(text='Select Room:', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    222         Line(parent=self.sr.panel, align=uiconst.TOTOP)
    223         if dungeonID not in availableDungeons:
    224             Container(name='push', parent=self.sr.panel, align=uiconst.TOTOP, height=const.defaultPadding * 2)
    225             eveLabel.EveLabelMedium(text=' - No dungeon selected - ', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    226             return
    227         seldungeon = sm.RemoteSvc('dungeon').DEGetDungeons(dungeonID=dungeonID)[0]
    228         eveLabel.EveLabelMedium(text=GetMessageFromLocalization(seldungeon.dungeonNameID) + ' - Version ', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    229         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOBOTTOM, height=16, padTop=4)
    230         godMode = settings.user.ui.Get('dungeonGodMode', 1)
    231         self.godModeCheckbox = Checkbox(text='God Mode', parent=row, settingsKey='dungeonGodMode', checked=godMode, callback=self.OnGodMode)
    232         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOBOTTOM, height=16, padTop=4)
    233         Button(parent=row, label='Play Dungeon', func=self.PlayDungeon, align=uiconst.TOLEFT, padLeft=4)
    234         Button(parent=row, label='Go to selected room', func=self.GotoRoom, args=(), align=uiconst.TOLEFT, padLeft=4)
    235         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOBOTTOM, height=16)
    236         Button(parent=row, label='Edit Room', func=self.EditRoom, align=uiconst.TOLEFT, padLeft=4)
    237         Button(parent=row, label='Save Room', func=self.SaveRoom, align=uiconst.TOLEFT, padLeft=4)
    238         Button(parent=row, label='Reset', func=self.ResetDungeon, args=(), align=uiconst.TORIGHT, padLeft=4)
    239         Container(name='push', parent=self.sr.panel, align=uiconst.TOBOTTOM, height=const.defaultPadding)
    240         rooms = sm.RemoteSvc('dungeon').DEGetRooms(dungeonID=seldungeon.dungeonID)
    241         Container(name='push', parent=self.sr.panel, align=uiconst.TOTOP, height=const.defaultPadding)
    242         self.scrollOptions = []
    243         for i, (roomID, roomName) in enumerate(rooms):
    244             self.scrollOptions.append(GetFromClass(Generic, {'label': '[%d] %s' % (roomID, roomName),
    245              'id': i + 1,
    246              'roomID': roomID,
    247              'OnClick': self.OnGotoSelectedRoomClicked}))
    248 
    249         self.sr.objscrollbox = eveScroll.Scroll(parent=self.sr.panel, name='scrollID')
    250         self.sr.objscrollbox.LoadContent(contentList=self.scrollOptions)
    251         if settings.user.ui.Get('selectedRoomID', None):
    252             rooms = self.sr.objscrollbox.GetNodes()
    253             for room in rooms:
    254                 if room.roomID == settings.user.ui.Get('selectedRoomID', None):
    255                     self.sr.objscrollbox.SelectNode(room)
    256                     break
    257 
    258     def OnJessicaOpenDungeon(self, dungeonID, defaultRoomID = None):
    259         self.OnJessicaOpenRoom(dungeonID, defaultRoomID)
    260 
    261     def OnJessicaOpenRoom(self, dungeonID, roomID):
    262         settings.user.ui.Set('dungeonDungeon', dungeonID)
    263         self.loadedTab = 'DungeonTab'
    264         for tab in self.sr.maintabs.sr.tabs:
    265             if tab.sr.args == self.loadedTab:
    266                 tab.Select()
    267 
    268         settings.user.ui.Set('selectedRoomID', roomID)
    269         if roomID:
    270             self.EditRoom()
    271 
    272     def ResetDungeon(self):
    273         sm.GetService('scenario').ResetDungeon()
    274 
    275     def PlayDungeon(self, *args):
    276         dungeonID = settings.user.ui.Get('dungeonDungeon', None)
    277         if dungeonID is None or dungeonID == 'All':
    278             return
    279         roomID = settings.user.ui.Get('selectedRoomID', None)
    280         if not roomID:
    281             objectList = self.sr.objscrollbox.GetNodes()
    282             if len(objectList) > 0:
    283                 roomID = objectList[0].roomID
    284             else:
    285                 return
    286         godMode = settings.user.ui.Get('dungeonGodMode', 1)
    287         sm.GetService('scenario').PlayDungeon(dungeonID, roomID, godmode=godMode)
    288 
    289     def EditRoom(self, *args):
    290         dungeonID = settings.user.ui.Get('dungeonDungeon', None)
    291         if dungeonID is None or dungeonID == 'All':
    292             return
    293         if not settings.user.ui.Get('selectedRoomID', None):
    294             objectList = self.sr.objscrollbox.GetNodes()
    295             if len(objectList) > 0:
    296                 settings.user.ui.Set('selectedRoomID', objectList[0].roomID)
    297             else:
    298                 return
    299         sm.GetService('scenario').EditRoom(dungeonID, settings.user.ui.Get('selectedRoomID', None))
    300 
    301     def SaveRoom(self, *args):
    302         sm.StartService('scenario').SaveAllChanges()
    303 
    304     def GotoRoom(self, *args):
    305         roomID = settings.user.ui.Get('selectedRoomID', None)
    306         if roomID:
    307             sm.GetService('scenario').GotoRoom(roomID)
    308 
    309     def OnDungeonEdit(self, dungeonID, roomID, roomPos):
    310         self.InitializeSelectionGroups(roomID)
    311 
    312     def InitializeSelectionGroups(self, roomID):
    313         scenario = sm.StartService('scenario')
    314         self.objectGroups = {}
    315         self.groupNameToIDMap = {}
    316         scenario.RemoveAllHardGroups()
    317 
    318     def GetCurrentRoomID(self):
    319         return sm.RemoteSvc('keeper').GetLevelEditor().GetCurrentlyEditedRoomID()
    320 
    321     def Load_RoomTab(self):
    322         dunObjs = self.scenario.GetDunObjects()
    323         if len(dunObjs) == 0:
    324             Container(name='push', parent=self.sr.panel, align=uiconst.TOTOP, height=28)
    325             eveLabel.EveLabelMedium(text='No dungeon objects found', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    326             return
    327         if hasattr(self, 'Load_%sTab' % self.roomTabSelected):
    328             getattr(self, 'Load_%sTab' % self.roomTabSelected)()
    329         self.loadedTab = 'RoomTab'
    330 
    331     def Load_RoomObjectTab(self):
    332         self.roomTabSelected = 'Objects'
    333         self.Load_RoomTab()
    334 
    335     def Load_RoomGroupTab(self):
    336         self.roomTabSelected = 'Groups'
    337         self.Load_RoomTab()
    338 
    339     def Load_ObjectsTab(self):
    340         self.roomTabSelected = 'Objects'
    341         dunObjs = sm.GetService('scenario').GetDunObjects()
    342         scrollOptions = []
    343         boxItems = []
    344         for slimItem in dunObjs:
    345             if getattr(slimItem, 'dunObjectID', None) is not None:
    346                 typeName = evetypes.GetName(slimItem.typeID)
    347                 objName = cfg.evelocations.Get(slimItem.itemID).name
    348                 entryName = typeName + ' : ' + objName
    349                 boxItems.append([entryName,
    350                  slimItem.dunObjectID,
    351                  sm.GetService('scenario').IsSelectedByObjID(slimItem.dunObjectID),
    352                  slimItem.itemID,
    353                  slimItem.typeID])
    354 
    355         boxItems.sort()
    356         for objName, objID, selected, itemID, typeID in boxItems:
    357             scrollOptions.append(GetFromClass(Generic, {'label': objName,
    358              'id': objID,
    359              'itemID': itemID,
    360              'typeID': typeID,
    361              'OnClick': self.OnObjectClicked,
    362              'isSelected': selected,
    363              'GetMenu': self.GetItemMenu}))
    364 
    365         self.sr.objscrollbox = eveScroll.Scroll(parent=self.sr.panel, name='scrollID', align=uiconst.TOTOP, height=256)
    366         self.sr.objscrollbox.LoadContent(contentList=scrollOptions)
    367         Container(name='push', parent=self.sr.panel, align=uiconst.TOTOP, height=16)
    368         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=20)
    369         Button(parent=row, label='Select All', func=self.ObjSelectAll, args=(), align=uiconst.TOLEFT, padLeft=4)
    370         Button(parent=row, label='Invert selection', func=self.ObjInverseSel, args=(), align=uiconst.TOLEFT, padLeft=4)
    371         Button(parent=row, label='Clear selection', func=self.ObjClearSel, args=(), align=uiconst.TOLEFT, padLeft=4)
    372         Container(name='push', parent=self.sr.panel, align=uiconst.TOTOP, height=16)
    373         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    374         Line(parent=self.sr.panel, align=uiconst.TOTOP)
    375         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    376         eveLabel.EveLabelMedium(text='Duplicate: ', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    377         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=16)
    378         counter = 0
    379         for each in 'XYZ':
    380             checkbox1ID = 'duplicateOffset%s' % each
    381             curVal = 0
    382             eveLabel.EveLabelMedium(text=each, parent=row, left=50 + counter * 52, state=uiconst.UI_NORMAL)
    383             ed = SingleLineEditInteger(name='duplicateOffset%s' % each, parent=row, setvalue=curVal, pos=(60 + counter * 52,
    384              0,
    385              38,
    386              0), minValue=-30000, maxValue=30000)
    387             counter = counter + 1
    388             setattr(self, checkbox1ID, ed)
    389 
    390         eveLabel.EveLabelMedium(text='Offset: ', parent=row, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    391         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=16)
    392         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=32)
    393         self.duplicateAmount = SingleLineEditInteger(name='duplicateAmount', parent=row, setvalue=1, pos=(60, 0, 38, 0), minValue=1, maxValue=100)
    394         eveLabel.EveLabelMedium(text='Amount: ', parent=row, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    395         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    396         Line(parent=self.sr.panel, align=uiconst.TOTOP)
    397         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    398         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    399         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=20)
    400         Button(parent=row, label='Delete selected', func=self.OnDeleteSelected, args=(), align=uiconst.TORIGHT, padLeft=4)
    401         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=20, padTop=4)
    402         Button(parent=row, label='Save Room', func=self.SaveRoom, align=uiconst.TOLEFT, padLeft=4)
    403 
    404     def Load_GroupsTab(self):
    405         self.roomTabSelected = 'Groups'
    406         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    407         dunObjs = sm.GetService('scenario').GetDunObjects()
    408         scrollOptions = []
    409         selectedGroups = self.GetSelectedGroups()
    410         groups = self.objectGroups.items()
    411         groups.sort()
    412         boxItems = [ [index,
    413          groupName,
    414          groupItems,
    415          groupName in selectedGroups] for index, (groupName, groupItems) in enumerate(groups) ]
    416         for index, groupName, groupItems, selected in boxItems:
    417             scrollOptions.append(GetFromClass(ObjectGroupListEntry, {'label': groupName,
    418              'id': index,
    419              'groupItems': groupItems,
    420              'OnClick': self.OnObjectGroupClicked,
    421              'isSelected': selected,
    422              'locked': False,
    423              'form': self}))
    424 
    425         self.sr.objgroupscrollbox = eveScroll.Scroll(parent=self.sr.panel, name='scrollID', align=uiconst.TOTOP, height=256)
    426         self.sr.objgroupscrollbox.LoadContent(contentList=scrollOptions)
    427         Container(name='push', parent=self.sr.panel, align=uiconst.TOTOP, height=16)
    428         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=16)
    429         Button(parent=row, label='Rename Group', func=self.OnRenameGroup, args=(), align=uiconst.TOLEFT)
    430         Button(parent=row, label='Remove Group', func=self.OnRemoveGroup, args=(), align=uiconst.TOLEFT)
    431         Button(parent=row, label='Delete Group Objects', func=self.OnDeleteSelectedGroup, args=(), align=uiconst.TOLEFT)
    432 
    433     def GetItemMenu(self, entry):
    434         info = entry.sr.node
    435         m = GetMenuService().GetMenuFromItemIDTypeID(info.itemID, info.typeID)
    436         return m
    437 
    438     def OnStateChange(self, itemID, flag, flagState, *args):
    439         if flag == states.selected and flagState:
    440             dunObjectID = sm.GetService('michelle').GetItem(itemID).dunObjectID
    441             if dunObjectID:
    442                 sm.GetService('scenario').SetSelectionByID([dunObjectID])
    443 
    444     def OnObjectClicked(self, entry, *args):
    445         uicore.cmd.ExecuteCombatCommand(entry.sr.node.itemID, uiconst.UI_CLICK)
    446         if self.sr.objscrollbox:
    447             ids = self.GetCurrentlySelectedObjects()
    448             sm.StartService('scenario').SetSelectionByID(ids)
    449 
    450     def OnObjectGroupClicked(self, entry, *args):
    451         self.SelectObjectGroup(entry.sr.node.label)
    452 
    453     def IsGroupSelected(self, groupName):
    454         return self.scenario.AreAllSelected(self.objectGroups[groupName])
    455 
    456     def GetSelectedGroups(self):
    457         return filter(self.IsGroupSelected, self.objectGroups.iterkeys())
    458 
    459     def SelectObjectGroup(self, label):
    460         if label not in self.objectGroups:
    461             return
    462         scenarioSvc = sm.StartService('scenario')
    463         selectedGroup = self.objectGroups[label]
    464         ids = []
    465         dungeonObjects = scenarioSvc.GetDunObjects()
    466         for slimItem in selectedGroup:
    467             if slimItem.dunObjectID in [ dungeonObject.dunObjectID for dungeonObject in dungeonObjects ]:
    468                 ids.append(slimItem.dunObjectID)
    469 
    470         for node in self.sr.objgroupscrollbox.GetNodes():
    471             if node.panel.sr.label.text == label:
    472                 self.sr.objgroupscrollbox.SelectNode(node)
    473                 scenarioSvc.SetSelectionByID(ids)
    474                 scenarioSvc.SetActiveHardGroup(label)
    475 
    476     def ObjSelectAll(self):
    477         dunObjs = sm.GetService('scenario').GetDunObjects()
    478         selItems = []
    479         for slimItem in dunObjs:
    480             selItems.append(slimItem.dunObjectID)
    481 
    482         sm.GetService('scenario').SetSelectionByID(selItems)
    483         if self.IsTabLoaded('RoomTab'):
    484             self.Refresh()
    485 
    486     def ObjClearSel(self):
    487         sm.GetService('scenario').SetSelectionByID([])
    488         if self.IsTabLoaded('RoomTab'):
    489             self.Refresh()
    490 
    491     def ObjInverseSel(self):
    492         curSel = self.sr.objscrollbox.GetSelected()
    493         ids = []
    494         for each in curSel:
    495             ids.append(each.id)
    496 
    497         dunObjs = sm.GetService('scenario').GetDunObjects()
    498         selItems = []
    499         for slimItem in dunObjs:
    500             if not sm.GetService('scenario').IsSelectedByObjID(slimItem.dunObjectID):
    501                 selItems.append(slimItem.dunObjectID)
    502 
    503         sm.GetService('scenario').SetSelectionByID(selItems)
    504         if self.IsTabLoaded('RoomTab'):
    505             self.Refresh()
    506 
    507     def OnDESelectionChanged(self):
    508         if self.IsTabLoaded('RoomTab') and not self.IsSelectionUpToDate():
    509             self.Refresh()
    510 
    511     def OnDEObjectListChanged(self):
    512         if self.IsTabLoaded('RoomTab'):
    513             self.Refresh()
    514 
    515     def IsSelectionUpToDate(self):
    516         remoteSelection = copy.copy(sm.StartService('scenario').selectionObjs)
    517         localSelection = self.GetCurrentlySelectedObjects()
    518         remoteSelection.sort()
    519         localSelection.sort()
    520         return remoteSelection == localSelection
    521 
    522     def GetCurrentlySelectedObjects(self):
    523         if self.destroyed or self.sr.objscrollbox.destroyed:
    524             selectedEntries = []
    525         else:
    526             selectedEntries = self.sr.objscrollbox.GetSelected()
    527         selectedObjects = []
    528         for entry in selectedEntries:
    529             selectedObjects.append(entry.id)
    530 
    531         return selectedObjects
    532 
    533     def OnSelectObject(self, selectedList, id):
    534         if id == 'In Game':
    535             return
    536         selectedObj = selectedList[0]
    537         if hasattr(selectedObj, 'selectTypeString'):
    538             if getattr(selectedObj, 'selectTypeString', 'unknown') == 'SelectDungeon':
    539                 settings.user.ui.Set('dungeonFactionID', None)
    540                 settings.user.ui.Set('dungeonArchetypeID', None)
    541                 settings.user.ui.Set('dungeonDungeon', selectedObj.dungeonID)
    542                 self.sr.maintabs.ReloadVisible()
    543             elif getattr(selectedObj, 'selectTypeString', 'unknown') == 'SelectDungeonRoom':
    544                 if self.IsTabLoaded('DungeonTab'):
    545                     settings.user.ui.Set('selectedRoomID', selectedObj.roomID)
    546             elif getattr(selectedObj, 'selectTypeString', 'unknown') == 'SelectDungeonObject':
    547                 sm.GetService('scenario').SetSelectionByID([selectedObj.objectID])
    548                 if self.IsTabLoaded('RoomTab'):
    549                     objectList = self.sr.objscrollbox.GetNodes()
    550                     for objectEntry in objectList:
    551                         if objectEntry.id == selectedObj.objectID:
    552                             self.sr.objscrollbox.SetSelected(objectEntry.idx)
    553                             break
    554 
    555             elif getattr(selectedObj, 'selectTypeString', 'unknown') == 'SelectDungeonEntity':
    556                 pass
    557             elif getattr(selectedObj, 'selectTypeString', 'unknown') == 'SelectDungeonEntityGroup':
    558                 pass
    559             elif selectedObj:
    560                 import log
    561                 log.LogWarn('dungeoneditor::OnSelectObject: Unknown selection:', selectedObj.selectTypeString)
    562 
    563     def Load_AlignTab(self):
    564         Container(name='push', parent=self.sr.panel, align=uiconst.TOTOP, height=4)
    565         eveLabel.EveLabelMedium(text='Align: ', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    566         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=16)
    567         Button(parent=row, label='  Align centres  ', func=self.OnAlignCentres, args=('btn1',), align=uiconst.TOLEFT)
    568         for each in 'XYZ':
    569             checkbox1ID = 'alignCentre%s' % each
    570             checkbox1Setval = settings.user.ui.Get(checkbox1ID, 0)
    571             cb = Checkbox(text=each, parent=row, settingsKey=checkbox1ID, checked=checkbox1Setval, callback=self.OnCheckboxChange, align=uiconst.TOLEFT, pos=(0, 0, 28, 0))
    572 
    573         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    574         Line(parent=self.sr.panel, align=uiconst.TOTOP)
    575         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    576         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=16)
    577         Button(parent=row, label='Distribute centres', func=self.OnDistributeCentres, args=('btn1',), align=uiconst.TOLEFT)
    578         for each in 'XYZ':
    579             checkbox1ID = 'distributeCentre%s' % each
    580             checkbox1Setval = settings.user.ui.Get(checkbox1ID, 0)
    581             cb = Checkbox(text=each, parent=row, settingsKey=checkbox1ID, checked=checkbox1Setval, callback=self.OnCheckboxChange, align=uiconst.TOLEFT, pos=(0, 0, 28, 0))
    582 
    583         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    584         Line(parent=self.sr.panel, align=uiconst.TOTOP)
    585         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    586         eveLabel.EveLabelMedium(text='Jitter: ', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    587         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=16)
    588         for counter, each in enumerate('XYZ'):
    589             checkbox1ID = 'jitterOffset%s' % each
    590             curVal = 0
    591             ed = SingleLineEditInteger(name='jitterOffset%s' % each, parent=row, setvalue=curVal, pos=(60 + counter * 52,
    592              0,
    593              38,
    594              0), maxValue=30000)
    595             setattr(self, checkbox1ID, ed)
    596 
    597         eveLabel.EveLabelMedium(text='Offset: ', parent=row, align=uiconst.TOLEFT, state=uiconst.UI_NORMAL).name = 'Pos Text'
    598         Button(parent=row, label='Jitter Position', func=self.OnJitterClicked, args=('btn1',), pos=(220, 0, 0, 0))
    599         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    600         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=16)
    601         for counter, each in enumerate(('Yaw', 'Pitch', 'Roll')):
    602             checkbox1ID = 'jitterOffset%s' % each
    603             curVal = 0
    604             ed = SingleLineEditInteger(name='jitterOffset%s' % each, parent=row, setvalue=curVal, pos=(60 + counter * 52,
    605              0,
    606              38,
    607              0), maxValue=30000)
    608             setattr(self, checkbox1ID, ed)
    609 
    610         eveLabel.EveLabelMedium(text='Rotation: ', parent=row, align=uiconst.TOLEFT, state=uiconst.UI_NORMAL).name = 'Rot Text'
    611         Button(parent=row, label='Jitter Rotation', func=self.OnJitterRotationClicked, pos=(220, 0, 0, 0))
    612         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    613         Line(parent=self.sr.panel, align=uiconst.TOTOP)
    614         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    615         eveLabel.EveLabelMedium(text='Arrange: ', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    616         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=16)
    617         counter = 0
    618         Button(parent=row, label='Arrange', func=self.OnArrangeClicked, args=('btn1',), pos=(220, 0, 0, 0))
    619         for each in 'XYZ':
    620             checkbox1ID = 'arrangeOffset%s' % each
    621             curVal = 0
    622             eveLabel.EveLabelMedium(text=each, parent=row, left=50 + counter * 52, state=uiconst.UI_NORMAL)
    623             ed = SingleLineEditInteger(name='arrangeOffset%s' % each, parent=row, setvalue=curVal, pos=(60 + counter * 52,
    624              0,
    625              38,
    626              0), minValue=-30000, maxValue=30000)
    627             counter = counter + 1
    628             setattr(self, checkbox1ID, ed)
    629 
    630         eveLabel.EveLabelMedium(text='Offset: ', parent=row, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    631         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    632         Line(parent=self.sr.panel, align=uiconst.TOTOP)
    633         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    634         eveLabel.EveLabelMedium(text='Quantity: (Only works on asteroids and clouds)', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    635         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=16)
    636         counter = 0
    637         Button(parent=row, label='Set Quantity', func=self.OnSetQuantityClicked, args=('btn1',), pos=(220, 0, 0, 0))
    638         minMaxQuantity = self.GetSelectedObjectsMinMaxQuantity()
    639         minQuantity = minMaxQuantity[0]
    640         maxQuantity = minMaxQuantity[1]
    641         eveLabel.EveLabelMedium(text='Min.', parent=row, left=40, width=32, height=14, state=uiconst.UI_NORMAL)
    642         eveLabel.EveLabelMedium(text='Max.', parent=row, left=130, width=32, height=14, state=uiconst.UI_NORMAL)
    643         self.quantityMin = SingleLineEditInteger(name='quantityMin', parent=row, setvalue=int(minQuantity), pos=(60, 0, 64, 0))
    644         self.quantityMax = SingleLineEditInteger(name='quantityMax', parent=row, setvalue=int(maxQuantity), pos=(154, 0, 64, 0))
    645         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    646         Line(parent=self.sr.panel, align=uiconst.TOTOP)
    647         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    648         eveLabel.EveLabelMedium(text='Radius: (Only works on asteroids and clouds)', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    649         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=16)
    650         counter = 0
    651         Button(parent=row, label='Set Radius', func=self.OnSetRadiusClicked, args=('btn1',), pos=(220, 0, 0, 0))
    652         eveLabel.EveLabelMedium(text='Min.', parent=row, left=40, width=32, height=14, state=uiconst.UI_NORMAL)
    653         eveLabel.EveLabelMedium(text='Max.', parent=row, left=130, width=32, height=14, state=uiconst.UI_NORMAL)
    654         minMaxRadius = self.GetSelectedObjectsMinMaxRadius()
    655         minRadius = minMaxRadius[0]
    656         maxRadius = minMaxRadius[1]
    657         self.radiusMin = SingleLineEditInteger(name='radiusMin', parent=row, setvalue=int(minRadius), pos=(60, 0, 64, 0))
    658         self.radiusMax = SingleLineEditInteger(name='radiusMax', parent=row, setvalue=int(maxRadius), pos=(154, 0, 64, 0))
    659         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    660         Line(parent=self.sr.panel, align=uiconst.TOTOP)
    661         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    662         eveLabel.EveLabelMedium(text='Rotate objects: ', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    663         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=16)
    664         counter = 0
    665         Button(parent=row, label='Rotate ', func=self.OnRotateClicked, args=('btn1',), pos=(220, 0, 0, 0))
    666         for each in ['Y', 'P', 'R']:
    667             checkbox1ID = 'rotate_%s' % each
    668             curVal = 0
    669             eveLabel.EveLabelMedium(text=each, parent=row, left=50 + counter * 52, height=14, state=uiconst.UI_NORMAL)
    670             ed = SingleLineEditInteger(name='rotate_%s' % each, parent=row, setvalue=curVal, pos=(60 + counter * 52,
    671              0,
    672              38,
    673              0), minValue=-30000, maxValue=30000)
    674             counter = counter + 1
    675             setattr(self, checkbox1ID, ed)
    676 
    677         eveLabel.EveLabelMedium(text='ROTATION: ', parent=row, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    678         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    679         Line(parent=self.sr.panel, align=uiconst.TOTOP)
    680         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    681         eveLabel.EveLabelMedium(text='Set rotation: ', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    682         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=16)
    683         counter = 0
    684         Button(parent=row, label='Set rotation', func=self.OnSetRotateClicked, args=('btn1',), pos=(220, 0, 0, 0))
    685         for each in ['Y', 'P', 'R']:
    686             checkbox1ID = 'rotateset_%s' % each
    687             curVal = 0
    688             eveLabel.EveLabelMedium(text=each, parent=row, left=50 + counter * 52, height=14, state=uiconst.UI_NORMAL)
    689             ed = SingleLineEditInteger(name='rotateset_%s' % each, parent=row, setvalue=curVal, pos=(60 + counter * 52,
    690              0,
    691              38,
    692              0), minValue=-30000, maxValue=30000)
    693             counter = counter + 1
    694             setattr(self, checkbox1ID, ed)
    695 
    696         eveLabel.EveLabelMedium(text='ROTATION: ', parent=row, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    697 
    698     def GetSelectedObjectsMinMaxQuantity(self):
    699         minQuantity = 1
    700         maxQuantity = 1
    701         selObjs = sm.GetService('scenario').GetSelObjects()
    702         for slimItem in selObjs:
    703             if slimItem.dunRadius is not None:
    704                 if slimItem.dunRadius > 1:
    705                     if minQuantity == 1:
    706                         minQuantity = slimItem.dunRadius
    707                     else:
    708                         minQuantity = min(minQuantity, slimItem.dunRadius)
    709                     maxQuantity = max(maxQuantity, slimItem.dunRadius)
    710 
    711         return (minQuantity, maxQuantity)
    712 
    713     def Close(self, *args, **kwds):
    714         Window.Close(self, *args, **kwds)
    715         self._navigation.RemoveAxisLines()
    716 
    717     def GetSelectedObjectsMinMaxRadius(self):
    718         minRadius = 1
    719         maxRadius = 1
    720         selObjs = sm.GetService('scenario').GetSelObjects()
    721         godma = sm.GetService('godma')
    722         for slimItem in selObjs:
    723             if slimItem.dunRadius is not None:
    724                 if slimItem.dunRadius > 1:
    725                     if minRadius == 1:
    726                         minRadius = ComputeRadiusFromQuantity(slimItem.categoryID, slimItem.groupID, slimItem.typeID, slimItem.dunRadius, godma)
    727                     else:
    728                         minRadius = min(minRadius, ComputeRadiusFromQuantity(slimItem.categoryID, slimItem.groupID, slimItem.typeID, slimItem.dunRadius, godma))
    729                     maxRadius = max(maxRadius, ComputeRadiusFromQuantity(slimItem.categoryID, slimItem.groupID, slimItem.typeID, slimItem.dunRadius, godma))
    730 
    731         return (minRadius, maxRadius)
    732 
    733     def OnSetQuantityClicked(self, *args):
    734         minQuantity = self.quantityMin.GetValue()
    735         maxQuantity = self.quantityMax.GetValue()
    736         if maxQuantity < minQuantity:
    737             prompt_player_fsd_dialog('MinRadiusHigherThenMax')
    738             return
    739         sm.GetService('scenario').SetSelectedQuantity(minQuantity, maxQuantity)
    740         newMinMaxRadius = self.GetSelectedObjectsMinMaxRadius()
    741         newMinRadius = int(round(newMinMaxRadius[0]))
    742         newMaxRadius = int(round(newMinMaxRadius[1]))
    743         self.radiusMin.SetValue(newMinRadius)
    744         self.radiusMax.SetValue(newMaxRadius)
    745 
    746     def OnSetRadiusClicked(self, *args):
    747         minRadius = self.radiusMin.GetValue()
    748         maxRadius = self.radiusMax.GetValue()
    749         if maxRadius < minRadius:
    750             prompt_player_fsd_dialog('MinRadiusHigherThenMax')
    751             return
    752         sm.GetService('scenario').SetSelectedRadius(minRadius, maxRadius)
    753         newMinMaxQuantity = self.GetSelectedObjectsMinMaxQuantity()
    754         newMinQuantity = int(round(newMinMaxQuantity[0]))
    755         newMaxQuantity = int(round(newMinMaxQuantity[1]))
    756         self.quantityMin.SetValue(newMinQuantity)
    757         self.quantityMax.SetValue(newMaxQuantity)
    758 
    759     def OnSetRotateClicked(self, *args):
    760         y = getattr(self, 'rotateset_Y').GetValue()
    761         p = getattr(self, 'rotateset_P').GetValue()
    762         r = getattr(self, 'rotateset_R').GetValue()
    763         sm.GetService('scenario').SetRotate(y, p, r)
    764 
    765     def OnRotateClicked(self, *args):
    766         slimItems = sm.GetService('scenario').GetSelObjects()
    767         if len(slimItems) == 0:
    768             return
    769         yaw = getattr(self, 'rotate_Y').GetValue()
    770         pitch = getattr(self, 'rotate_P').GetValue()
    771         roll = getattr(self, 'rotate_R').GetValue()
    772         sm.GetService('scenario').RotateSelected(yaw, pitch, roll)
    773 
    774     def OnJitterClicked(self, *args):
    775         X = getattr(self, 'jitterOffsetX').GetValue()
    776         Y = getattr(self, 'jitterOffsetY').GetValue()
    777         Z = getattr(self, 'jitterOffsetZ').GetValue()
    778         sm.GetService('scenario').JitterSelection(X, Y, Z)
    779 
    780     def OnJitterRotationClicked(self, *args):
    781         yaw = self.jitterOffsetYaw.GetValue()
    782         pitch = self.jitterOffsetPitch.GetValue()
    783         roll = self.jitterOffsetRoll.GetValue()
    784         sm.StartService('scenario').JitterRotationSelection(yaw, pitch, roll)
    785 
    786     def OnArrangeClicked(self, *args):
    787         X = getattr(self, 'arrangeOffsetX').GetValue()
    788         Y = getattr(self, 'arrangeOffsetY').GetValue()
    789         Z = getattr(self, 'arrangeOffsetZ').GetValue()
    790         sm.GetService('scenario').ArrangeSelection(X, Y, Z)
    791 
    792     def OnDungeonSelectionGroupRotation(self, groupName, x, y, z, w):
    793         pass
    794 
    795     def OnRenameGroup(self):
    796         selectedGroups = self.sr.objgroupscrollbox.GetSelected()
    797         for each in selectedGroups:
    798             self.OpenRenameGroupDialog(each.label)
    799             return
    800 
    801     def OpenRenameGroupDialog(self, key = ''):
    802         oldGroupName = key
    803         format = [{'type': 'btline'},
    804          {'type': 'push',
    805           'height': 8,
    806           'frame': 1},
    807          {'type': 'edit',
    808           'setvalue': key,
    809           'maxLength': 64,
    810           'labelwidth': len('Group Name:') * 7,
    811           'label': 'Group Name:',
    812           'key': 'newGroupName',
    813           'required': 1,
    814           'frame': 1},
    815          {'type': 'push',
    816           'height': 8,
    817           'frame': 1},
    818          {'type': 'btline'},
    819          {'type': 'data',
    820           'data': {'oldGroupName': oldGroupName}},
    821          {'type': 'errorcheck',
    822           'errorcheck': self.RenameGroupErrorCheck}]
    823         retval = uix.HybridWnd(format, caption='Rename Group', windowID='renameGroup', modal=1, buttons=uiconst.OKCANCEL, minW=300, minH=50)
    824         if retval and retval['newGroupName'] and len(retval['newGroupName']) > 1:
    825             self.RenameGroup(retval['oldGroupName'], retval['newGroupName'])
    826 
    827     def RenameGroupErrorCheck(self, retval):
    828         oldGroupName = retval['oldGroupName']
    829         newGroupName = retval['newGroupName']
    830         if oldGroupName == newGroupName:
    831             return ''
    832         if newGroupName in self.objectGroups:
    833             return 'Cannot rename %s: A group with the name you specified already exists. Specify a new group name.' % oldGroupName
    834 
    835     def RenameGroup(self, oldGroupName, newGroupName):
    836         if oldGroupName == newGroupName:
    837             return
    838         sm.GetService('scenario').RenameHardGroup(oldGroupName, newGroupName)
    839         self.objectGroups[newGroupName] = self.objectGroups[oldGroupName]
    840         del self.objectGroups[oldGroupName]
    841         self.Refresh()
    842 
    843     def OnRemoveGroup(self):
    844         self.RemoveSelectedGroups()
    845 
    846     def _RemoveGroup(self, groupName):
    847         del self.objectGroups[groupName]
    848         self.scenario.RemoveHardGroup(groupName)
    849 
    850     def RemoveGroup(self, groupName):
    851         self._RemoveGroup(groupName)
    852         self.Refresh()
    853 
    854     def RemoveSelectedGroups(self):
    855         selectedGroups = self.GetSelectedGroups()
    856         for groupName in selectedGroups:
    857             self._RemoveGroup(groupName)
    858 
    859         self.Refresh()
    860 
    861     def OnDeleteSelected(self):
    862         uthread.new(self.DeleteSelected).context = 'svc.scenario.OnDeleteSelected'
    863 
    864     def OnDeleteSelectedGroup(self):
    865         uthread.new(self.DeleteSelectedGroups).context = 'svc.scenario.OnDeleteSelectedGroup'
    866 
    867     def DeleteSelected(self):
    868         self.RemoveSelectedGroups()
    869 
    870     def DeleteGroup(self, groupName):
    871         self.RemoveGroup(groupName)
    872 
    873     def DeleteSelectedGroups(self):
    874         selectedGroups = self.GetSelectedGroups()
    875         for groupName in selectedGroups:
    876             self._RemoveGroup(groupName)
    877 
    878         self.Refresh()
    879 
    880     def OnAlignCentres(self, *args):
    881         selObjs = sm.GetService('scenario').GetSelObjects()
    882         if len(selObjs) == 0:
    883             return
    884         centreAxis = trinity.TriVector()
    885         for slimItem in selObjs:
    886             centreAxis.x = centreAxis.x + slimItem.dunX
    887             centreAxis.y = centreAxis.y + slimItem.dunY
    888             centreAxis.z = centreAxis.z + slimItem.dunZ
    889 
    890         centreAxis.Scale(1.0 / len(selObjs))
    891         for slimItem in selObjs:
    892             newX = slimItem.dunX
    893             newY = slimItem.dunY
    894             newZ = slimItem.dunZ
    895             if settings.user.ui.Get('alignCentreX', 0):
    896                 newX = int(centreAxis.x)
    897             if settings.user.ui.Get('alignCentreY', 0):
    898                 newY = int(centreAxis.y)
    899             if settings.user.ui.Get('alignCentreZ', 0):
    900                 newZ = int(centreAxis.z)
    901             dungeonHelper.SetObjectPosition(slimItem.dunObjectID, newX, newY, newZ)
    902 
    903     def OnDistributeCentres(self, *args):
    904         selObjs = sm.GetService('scenario').GetSelObjects()
    905         if len(selObjs) < 2:
    906             return
    907         centreAxis = trinity.TriVector()
    908         minV = trinity.TriVector(selObjs[0].dunX, selObjs[0].dunY, selObjs[0].dunZ)
    909         maxV = minV.CopyTo()
    910         for slimItem in selObjs:
    911             minV.x = min(minV.x, slimItem.dunX)
    912             minV.y = min(minV.y, slimItem.dunY)
    913             minV.z = min(minV.z, slimItem.dunZ)
    914             maxV.x = max(maxV.x, slimItem.dunX)
    915             maxV.y = max(maxV.y, slimItem.dunY)
    916             maxV.z = max(maxV.z, slimItem.dunZ)
    917 
    918         dMinMaxV = maxV - minV
    919         dStepSize = dMinMaxV.CopyTo()
    920         dStepSize.Scale(1.0 / (len(selObjs) - 1))
    921         if len(selObjs) == 0:
    922             return
    923         counter = 0
    924         for slimItem in selObjs:
    925             newX = slimItem.dunX
    926             newY = slimItem.dunY
    927             newZ = slimItem.dunZ
    928             if settings.user.ui.Get('distributeCentreX', 0):
    929                 newX = minV.x + dStepSize.x * counter
    930             if settings.user.ui.Get('distributeCentreY', 0):
    931                 newY = minV.y + dStepSize.y * counter
    932             if settings.user.ui.Get('distributeCentreZ', 0):
    933                 newZ = minV.z + dStepSize.z * counter
    934             dungeonHelper.SetObjectPosition(slimItem.dunObjectID, newX, newY, newZ)
    935             counter = counter + 1
    936 
    937     def Load_SettingTab(self):
    938         eveLabel.EveLabelMedium(text='Cursor size clamping', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    939         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=16)
    940         counter = 0
    941         Button(parent=row, label='Set min and max', func=self.OnSetCursorRadiusClicked, args=('btn1',), pos=(220, 0, 0, 0))
    942         minRadius = settings.user.ui.Get('cursorMin', 1.0)
    943         maxRadius = settings.user.ui.Get('cursorMax', 100000.0)
    944         selObjs = sm.GetService('scenario').GetSelObjects()
    945         for slimItem in selObjs:
    946             if slimItem.radius > 1:
    947                 if minRadius == 1:
    948                     minRadius = slimItem.radius
    949                 else:
    950                     minRadius = min(minRadius, slimItem.radius)
    951                 maxRadius = max(maxRadius, slimItem.radius)
    952 
    953         eveLabel.EveLabelMedium(text='Min.', parent=row, left=40, width=32, state=uiconst.UI_NORMAL)
    954         eveLabel.EveLabelMedium(text='Max.', parent=row, left=130, width=32, state=uiconst.UI_NORMAL)
    955         self.cursorMin = SingleLineEditInteger(name='cursorMin', parent=row, setvalue=int(minRadius), pos=(60, 0, 64, 0), maxValue=60000)
    956         self.cursorMax = SingleLineEditInteger(name='cursorMax', parent=row, setvalue=int(maxRadius), pos=(154, 0, 64, 0), maxValue=60000)
    957         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    958         Line(parent=self.sr.panel, align=uiconst.TOTOP)
    959         Container(name='pusher', align=uiconst.TOTOP, height=6, parent=self.sr.panel)
    960         eveLabel.EveLabelMedium(text='Aggression radius: ', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    961         checkbox1ID = 'showAggrRadius'
    962         checkbox1Setval = settings.user.ui.Get(checkbox1ID, 0)
    963         self.aggrSettings = Checkbox(text='Aggression radius', parent=self.sr.panel, settingsKey=checkbox1ID, checked=settings.user.ui.Get('showAggrRadius', 0), callback=self.OnDisplaySettingsChange)
    964         self.aggrSettingsAll = Checkbox(text='Show agression radius of all objects', parent=self.sr.panel, settingsKey=checkbox1ID, checked=settings.user.ui.Get('aggrSettingsAll', 0), callback=self.OnDisplaySettingsChange)
    965         Line(parent=self.sr.panel, align=uiconst.TOTOP)
    966         self.gridCheckbox = Checkbox(text='Draw Grid Lines', parent=self.sr.panel, settingsKey=checkbox1ID, checked=self._navigation.IsGridEnabled(), callback=self.OnDisplaySettingsChange)
    967         Container(name='pusher', align=uiconst.TOTOP, height=7, parent=self.sr.panel)
    968         eveLabel.EveLabelMedium(text='Palette Placement Grid', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    969         gridSizeOptions = [('200x200m', 200.0),
    970          ('2x2km', 2000.0),
    971          ('20x20km', 20000.0),
    972          ('200x200km', 200000.0),
    973          ('2000x2000km', 2000000.0)]
    974         Container(name='pusher', align=uiconst.TOTOP, height=14, parent=self.sr.panel)
    975         self.gridSizeDropdown = Combo(parent=self.sr.panel, label='Grid Size', options=gridSizeOptions, name='', select=self._navigation.GetGridLength(), callback=self.OnDisplaySettingsChange, align=uiconst.TOTOP, adjustWidth=True)
    976         gridSpacingOptions = [('10x10m', 10.0),
    977          ('100x100m', 100.0),
    978          ('1x1km', 1000.0),
    979          ('10x10km', 10000.0)]
    980         Container(name='pusher', align=uiconst.TOTOP, height=14, parent=self.sr.panel)
    981         self.gridSpacingDropdown = Combo(parent=self.sr.panel, label='Unit Size', options=gridSpacingOptions, name='', select=self._navigation.GetGridSpacing(), callback=self.OnDisplaySettingsChange, align=uiconst.TOTOP, adjustWidth=True)
    982         Container(name='pusher', align=uiconst.TOTOP, height=14, parent=self.sr.panel)
    983         eveLabel.EveLabelMedium(text='Free Look Camera must be enabled to use the placement grid.', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    984         eveLabel.EveLabelMedium(text='Red Axis Line = X Axis', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    985         eveLabel.EveLabelMedium(text='Green Axis Line = Y Axis', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    986         eveLabel.EveLabelMedium(text='Blue Axis Line = Z Axis', parent=self.sr.panel, align=uiconst.TOTOP, state=uiconst.UI_NORMAL)
    987 
    988     def OnDisplaySettingsChange(self, *args):
    989         desiredSpacingValue = self.gridSpacingDropdown.GetValue()
    990         settings.user.ui.Set('showAggrRadius', self.aggrSettings.GetValue())
    991         settings.user.ui.Set('aggrSettingsAll', self.aggrSettingsAll.GetValue())
    992         self.UpdateGridVisibility()
    993         if desiredSpacingValue != self._navigation.GetGridSpacing():
    994             self.gridSpacingDropdown.SetValue(self._navigation.GetGridSpacing())
    995         sm.GetService('scenario').RefreshSelection()
    996 
    997     def OnSetCursorRadiusClicked(self, *args):
    998         minRadius = self.cursorMin.GetValue()
    999         maxRadius = self.cursorMax.GetValue()
   1000         if maxRadius < minRadius:
   1001             prompt_player_fsd_dialog('MinRadiusHigherThenMax')
   1002             return
   1003         settings.user.ui.Set('cursorMin', minRadius)
   1004         settings.user.ui.Set('cursorMax', maxRadius)
   1005         sm.GetService('scenario').RefreshSelection()
   1006 
   1007     def GetScrollOptions(self, key):
   1008         data = []
   1009         if key == 'combo_retval1':
   1010             data = [ ('1 label %s' % i, i) for i in xrange(100) ]
   1011         elif key == 'combo_retval2':
   1012             data = [ ('2 label %s' % i, i) for i in xrange(100) ]
   1013         return [ GetFromClass(Generic, {'label': label,
   1014          'id': id}) for label, id in data ]
   1015 
   1016     def OnGotoSelectedRoomClicked(self, entry, *args):
   1017         settings.user.ui.Set('selectedRoomID', entry.sr.node.roomID)
   1018         dungeonID = settings.user.ui.Get('dungeonDungeon', None)
   1019         sm.ScatterEvent('OnSelectObjectInGame', 'SelectDungeonRoom', dungeonID=dungeonID, roomID=entry.sr.node.roomID)
   1020 
   1021     def OnComboChange(self, combo, newHeader, newValue, *args):
   1022         settings.user.ui.Set(combo.name, newValue)
   1023         self.sr.maintabs.ReloadVisible()
   1024         if combo.name == 'dungeonDungeon':
   1025             sm.ScatterEvent('OnSelectObjectInGame', 'SelectDungeon', dungeonID=newValue)
   1026 
   1027     def OnClickButton(self, *args):
   1028         self.sr.maintabs.ReloadVisible()
   1029 
   1030     def OnCheckboxChange(self, checkbox, *args):
   1031         settings.user.ui.Set(checkbox.GetSettingsKey(), checkbox.checked)
   1032 
   1033     def Load_PaletteTab(self):
   1034         roomObjectGroups = sm.RemoteSvc('dungeon').DEGetRoomObjectPaletteData()
   1035         kv = utillib.KeyVal()
   1036         kv.groupItems = roomObjectGroups
   1037         scrollOptions = self.GetGroupTypes(kv)
   1038         self.sr.palettescroll = eveScroll.Scroll(name='scroll', parent=self.sr.panel, align=uiconst.TOALL)
   1039         self.sr.palettescroll.Load(contentList=scrollOptions, scrolltotop=0)
   1040         self.sr.palettescroll.padBottom = button.height + const.defaultPadding * 5
   1041         self.sr.palettescroll.multiSelect = 0
   1042 
   1043     def GetGroupTypes(self, nodeData, *args):
   1044         sublevel = nodeData.Get('sublevel', -1) + 1
   1045         scrollOptions = []
   1046         if type(nodeData.groupItems) == list:
   1047             nodeData.groupItems.sort(lambda x, y: cmp(x[1], y[1]))
   1048             for id, name in nodeData.groupItems:
   1049                 scrollOptions.append(GetFromClass(PaletteEntry, {'label': name,
   1050                  'id': id,
   1051                  'sublevel': sublevel}))
   1052 
   1053         elif type(nodeData.groupItems) == dict:
   1054             keys = nodeData.groupItems.keys()
   1055             keys.sort(lambda x, y: cmp(x[1], y[1]))
   1056             for key in keys:
   1057                 groupID, groupName = key
   1058                 scrollOptions.append(GetFromClass(ListGroup, {'label': groupName,
   1059                  'id': ('group', groupID),
   1060                  'groupItems': nodeData.groupItems[key],
   1061                  'showlen': 1,
   1062                  'sublevel': sublevel,
   1063                  'GetSubContent': self.GetGroupTypes}))
   1064 
   1065         return scrollOptions
   1066 
   1067     def OnDEObjectPaletteChanged(self):
   1068         if self.sr.Get('palettescroll') and self.sr.palettescroll is not None:
   1069             self.UnloadPanel()
   1070             self.Refresh()
   1071 
   1072     def _OnClose(self, *args):
   1073         self.scenario.ClearSelection()
   1074 
   1075 
   1076 class DungeonDragEntry(Generic):
   1077     __guid__ = 'listentry.DungeonDragEntry'
   1078     __nonpersistvars__ = []
   1079     isDragObject = True
   1080     DROPDISTANCE = 10000
   1081 
   1082     def GetDragData(self, *args):
   1083         return self.sr.node.scroll.GetSelectedNodes(self.sr.node)
   1084 
   1085     def OnEndDrag(self, *args):
   1086         uthread.new(self.OnEndDragFunc)
   1087 
   1088     def OnEndDragFunc(self):
   1089         from eve.client.script.ui.inflight.navigation import InflightLayer
   1090         where = uicore.uilib.mouseOver
   1091         if where and type(where) is InflightLayer:
   1092             scenarioSvc = sm.services['scenario']
   1093             roomID = scenarioSvc.GetEditingRoomID()
   1094             if roomID is None:
   1095                 message_player('You need to be editing a room to be able to add an object')
   1096                 return
   1097             proj, view, vp = uix.GetFullscreenProjectionViewAndViewport()
   1098             ray, startingPosition = trinity.device.GetPickRayFromViewport(uicore.uilib.x, uicore.uilib.y, vp, view.transform, proj.transform)
   1099             ray = trinity.TriVector(*ray)
   1100             roomPos = scenarioSvc.GetEditingRoomPosition()
   1101             ship = sm.services['michelle'].GetBall(session.shipid)
   1102             camera = sm.GetService('sceneManager').GetActiveCamera()
   1103             cameraParent = sm.GetService('sceneManager').GetActiveCamera().GetCameraParent()
   1104             cameraPosition = trinity.TriVector(ship.x - roomPos[0] + camera.pos[0], ship.y - roomPos[1] + camera.pos[1], ship.z - roomPos[2] + camera.pos[2])
   1105             focusPoint = cameraParent.translation
   1106             focusPoint = trinity.TriVector(ship.x - roomPos[0] + focusPoint[0], ship.y - roomPos[1] + focusPoint[1], ship.z - roomPos[2] + focusPoint[2])
   1107             dist = (focusPoint.y - cameraPosition.y) / ray.y
   1108             if dist < 0:
   1109                 message_player('This item can only be dragged onto the grid')
   1110                 return
   1111             posInRoom = cameraPosition + ray * dist
   1112             gridLength = sm.GetService('sceneManager').GetActiveCamera().dungeonHack.GetGridLength()
   1113             minGrid = -gridLength / 2.0
   1114             maxGrid = gridLength / 2.0
   1115             if minGrid + focusPoint.x > posInRoom.x or maxGrid + focusPoint.x < posInRoom.x or minGrid + focusPoint.z > posInRoom.z or maxGrid + focusPoint.z < posInRoom.z:
   1116                 message_player('You can only drag Palette entries onto the grid')
   1117                 return
   1118             self.DropIntoDungeonRoom(roomID, posInRoom)
   1119         elif where and where.__guid__ not in (self.__guid__, 'form.DungeonEditor'):
   1120             message_player('This item can only be dragged onto the grid')
   1121 
   1122     def DropIntoDungeonRoom(self, roomID, posInRoom):
   1123         pass
   1124 
   1125 
   1126 class PaletteEntry(DungeonDragEntry):
   1127     __guid__ = 'listentry.PaletteEntry'
   1128 
   1129 
   1130 class DungeonObjectProperties(Window):
   1131     __guid__ = 'form.DungeonObjectProperties'
   1132     __nonpersistvars__ = []
   1133     __notifyevents__ = ['OnDungeonObjectProperties', 'OnSelectObjectInGame']
   1134     _windowHeight = 200
   1135     default_windowID = 'dungeonObjectProperties'
   1136 
   1137     def ApplyAttributes(self, attributes):
   1138         Window.ApplyAttributes(self, attributes)
   1139         self.scope = uiconst.SCOPE_INFLIGHT
   1140         self.SetCaption('Object Properties')
   1141         self.width = 300
   1142         self.height = self._windowHeight
   1143         self.SetHeight(self._windowHeight)
   1144         self.objectRow = None
   1145         self.noObjects = eveLabel.EveLabelMedium(text='No Objects Selected', parent=self.sr.main, left=5, top=4, state=uiconst.UI_NORMAL)
   1146         panel = Container(name='panel', parent=self.sr.main, left=const.defaultPadding, top=const.defaultPadding, width=const.defaultPadding, height=const.defaultPadding)
   1147         self.sr.panel = panel
   1148         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=18)
   1149         eveLabel.EveLabelMedium(text='Selected:', parent=row, left=2, top=4, state=uiconst.UI_NORMAL)
   1150         self.selectedCombo = Combo(label='', parent=row, options=(('Nothing', 0),), name='combo', select=0, callback=self.OnComboChange, pos=(80, 0, 0, 0), align=uiconst.TOPLEFT)
   1151         self.selectedCombo.width = 202
   1152         Line(parent=self.sr.panel, align=uiconst.TOTOP)
   1153         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=20)
   1154         eveLabel.EveLabelMedium(text='Object Name:', parent=row, left=2, top=4, state=uiconst.UI_NORMAL)
   1155         self.objectName = SingleLineEditText(name='objectName', parent=row, setvalue='', pos=(80, 0, 202, 16))
   1156         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=20)
   1157         eveLabel.EveLabelMedium(text='Object Type:', parent=row, left=2, top=4, state=uiconst.UI_NORMAL)
   1158         self.objectType = eveLabel.EveLabelMedium(text='', parent=row, left=80, top=4, state=uiconst.UI_NORMAL)
   1159         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=20)
   1160         eveLabel.EveLabelMedium(text='Quantity:', parent=row, left=2, top=4, state=uiconst.UI_NORMAL)
   1161         self.quantity = SingleLineEditInteger(name='quantity', parent=row, setvalue=0, pos=(80, 0, 202, 0))
   1162         self.oldQuantity = 0
   1163         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=20)
   1164         eveLabel.EveLabelMedium(text='Radius:', parent=row, left=2, top=4, state=uiconst.UI_NORMAL)
   1165         self.radius = SingleLineEditInteger(name='radius', parent=row, setvalue=0, pos=(80, 0, 202, 16))
   1166         self.oldRadius = 0
   1167         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=20)
   1168         eveLabel.EveLabelMedium(text='Position:', parent=row, left=2, top=4, state=uiconst.UI_NORMAL)
   1169         for i, each in enumerate('XYZ'):
   1170             editId = 'position%s' % each
   1171             editBox = SingleLineEditFloat(name=editId, parent=row, setvalue=0, pos=(80 + i * 69,
   1172              0,
   1173              64,
   1174              16), minValue=-100000.0, maxValue=100000.0)
   1175             setattr(self, editId, editBox)
   1176 
   1177         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=20)
   1178         eveLabel.EveLabelMedium(text='Rotation:', parent=row, left=2, top=4, state=uiconst.UI_NORMAL)
   1179         for i, each in enumerate(['Yaw', 'Pitch', 'Roll']):
   1180             editId = 'rotation%s' % each
   1181             editBox = SingleLineEditFloat(name=editId, parent=row, setvalue=0, pos=(80 + i * 69,
   1182              0,
   1183              64,
   1184              16), minValue=-100000.0, maxValue=100000.0)
   1185             setattr(self, editId, editBox)
   1186 
   1187         Container(name='spacer', parent=self.sr.panel, align=uiconst.TOTOP, height=5)
   1188         Line(parent=self.sr.panel, align=uiconst.TOTOP)
   1189         Container(name='spacer', parent=self.sr.panel, align=uiconst.TOTOP, height=5)
   1190         row = Container(name='row', parent=self.sr.panel, align=uiconst.TOTOP, height=20)
   1191         Button(parent=row, label='Change Type', func=self.OnChangeType, align=uiconst.TOLEFT).width = 87
   1192         Button(parent=row, label='Save', func=self.OnSave, align=uiconst.TOLEFT).width = 87
   1193         Button(parent=row, label='Revert', func=self.OnRevert, align=uiconst.TOLEFT).width = 87
   1194         self.scenario = sm.StartService('scenario')
   1195         self.OnSelectObjectInGame('SelectDungeonObject')
   1196         self.Show()
   1197 
   1198     def LoadObjectID(self, objectID = None):
   1199         if objectID is None:
   1200             self.sr.panel.state = uiconst.UI_HIDDEN
   1201             self.noObjects.state = uiconst.UI_DISABLED
   1202             self.objectRow = None
   1203             self.SetCaption('Object Properties')
   1204             return
   1205         self.ShowLoad()
   1206         self.objectRow = None
   1207         if self.objectRow is None:
   1208             self.sr.panel.state = uiconst.UI_HIDDEN
   1209             self.noObjects.state = uiconst.UI_DISABLED
   1210             self.objectRow = None
   1211             self.HideLoad()
   1212             return
   1213         self.sr.panel.state = uiconst.UI_PICKCHILDREN
   1214         self.noObjects.state = uiconst.UI_HIDDEN
   1215         self.SetHeight(self._windowHeight)
   1216         self.UpdateDisplay()
   1217 
   1218     def UpdateDisplay(self):
   1219         self.ShowLoad()
   1220         self.SetCaption('Object Properties: %d' % self.objectRow.objectID)
   1221         objectName = ''
   1222         if self.objectRow.objectNameID is not None:
   1223             objectName = GetMessageFromLocalization(self.objectRow.objectNameID)
   1224         self.objectName.SetValue(objectName)
   1225         self.objectType.text = evetypes.GetNameOrNone(self.objectRow.typeID) or '<No Type Set>'
   1226         quantity = dungeonHelper.GetObjectQuantity(self.objectRow.objectID)
   1227         if quantity is None:
   1228             self.quantity.SetValue('Quantity Invalid For This Type')
   1229             self.quantity.state = uiconst.UI_DISABLED
   1230         else:
   1231             self.quantity.state = uiconst.UI_NORMAL
   1232             self.quantity.SetValue(int(quantity))
   1233             self.oldQuantity = int(quantity)
   1234         radius = dungeonHelper.GetObjectRadius(self.objectRow.objectID)
   1235         if radius is None:
   1236             self.radius.SetValue('Radius Invalid For This Type')
   1237             self.radius.state = uiconst.UI_DISABLED
   1238         else:
   1239             self.radius.state = uiconst.UI_NORMAL
   1240             self.radius.SetValue(int(radius))
   1241             self.oldRadius = int(radius)
   1242         x, y, z = dungeonHelper.GetObjectPosition(self.objectRow.objectID)
   1243         self.positionX.SetValue(x)
   1244         self.positionY.SetValue(y)
   1245         self.positionZ.SetValue(z)
   1246         yaw, pitch, roll = dungeonHelper.GetObjectRotation(self.objectRow.objectID)
   1247         self.rotationYaw.SetValue(yaw)
   1248         self.rotationPitch.SetValue(pitch)
   1249         self.rotationRoll.SetValue(roll)
   1250         self.HideLoad()
   1251 
   1252     def OnSelectObjectInGame(self, selectType, *args, **kwargs):
   1253         if selectType == 'SelectDungeonObject':
   1254             self._HandleSelection(self.scenario.GetSelectedObjIDs())
   1255 
   1256     def _HandleSelection(self, objectIds):
   1257         if objectIds:
   1258             scenarioSvc = sm.StartService('scenario')
   1259             for objectID in objectIds:
   1260                 ball, slimItem = scenarioSvc.GetBallAndSlimItemFromObjectID(objectID)
   1261                 sleepTime = 0
   1262                 while ball is None or slimItem is None and sleepTime < 5000:
   1263                     sleepTime += 200
   1264                     blue.synchro.SleepWallclock(200)
   1265                     ball, slimItem = scenarioSvc.GetBallAndSlimItemFromObjectID(objectID)
   1266 
   1267                 if ball is None or slimItem is None:
   1268                     sm.StartService('scenario').LogError('DungeonObjectProperties._HandleSelection could not load balls and slimItems')
   1269 
   1270             if self.objectRow is None or self.objectRow.objectID not in objectIds:
   1271                 self.LoadObjectID(objectIds[0])
   1272             options = []
   1273             for objectId in objectIds:
   1274                 options.append((objectId, objectId))
   1275 
   1276             self.selectedCombo.LoadOptions(options, select=self.objectRow.objectID)
   1277         else:
   1278             self.LoadObjectID()
   1279 
   1280     def OnDungeonObjectProperties(self, objectID):
   1281         if self.objectRow and objectID == self.objectRow.objectID:
   1282             self.UpdateDisplay()
   1283 
   1284     def OnChangeType(self, *args, **kwargs):
   1285         ObjectTypeChooser.Open(objectRow=self.objectRow)
   1286 
   1287     def OnSave(self, *args, **kwargs):
   1288         dungeonID = settings.user.ui.Get('dungeonDungeon', None)
   1289         if dungeonID is None or dungeonID == 'All':
   1290             return
   1291         selDungeon = sm.RemoteSvc('dungeon').DEGetDungeons(dungeonID=dungeonID)[0]
   1292         dungeonNameID = selDungeon.dungeonNameID
   1293         if dungeonNameID is not None:
   1294             dungeonName = GetMessageFromLocalization(dungeonNameID)
   1295         else:
   1296             dungeonName = selDungeon.dungeonName
   1297         quantity = self.quantity.GetValue()
   1298         if quantity != 'Quantity Invalid For This Type' and quantity != self.oldQuantity:
   1299             self.oldQuantity = quantity
   1300             dungeonHelper.SetObjectQuantity(self.objectRow.objectID, quantity)
   1301         radius = self.radius.GetValue()
   1302         if radius != 'Radius Invalid For This Type' and radius != self.oldRadius:
   1303             self.oldRadius = radius
   1304             dungeonHelper.SetObjectRadius(self.objectRow.objectID, radius)
   1305         x = self.positionX.GetValue()
   1306         y = self.positionY.GetValue()
   1307         z = self.positionZ.GetValue()
   1308         dungeonHelper.SetObjectPosition(self.objectRow.objectID, x, y, z)
   1309         yaw = self.rotationYaw.GetValue()
   1310         pitch = self.rotationPitch.GetValue()
   1311         roll = self.rotationRoll.GetValue()
   1312         dungeonHelper.SetObjectRotation(self.objectRow.objectID, yaw, pitch, roll)
   1313         objectName = self.objectName.GetValue()
   1314         if not objectName:
   1315             objectName = None
   1316         if objectName is not None:
   1317             objectNameID = self.objectRow.objectNameID
   1318             if objectNameID is not None and objectName == GetMessageFromLocalization(objectNameID):
   1319                 return
   1320             if objectNameID is not None and unicode(objectNameID) in objectName:
   1321                 msgText = 'Do you really want to name this object %s?'
   1322                 msgText += " Selecting 'No' will save other changes you have made to the object, but will not change the name."
   1323                 ret = prompt_player_fsd_dialog('CustomQuestion', {'header': 'Rename Object?',
   1324                  'question': msgText % objectName}, uiconst.YESNO)
   1325                 if ret == uiconst.ID_NO:
   1326                     return
   1327         self.UpdateDisplay()
   1328 
   1329     def OnRevert(self, *args, **kwargs):
   1330         self.UpdateDisplay()
   1331 
   1332     def OnComboChange(self, *args, **kwargs):
   1333         selected = self.selectedCombo.GetValue()
   1334         if selected != self.objectRow.objectID:
   1335             self.LoadObjectID(selected)
   1336 
   1337 
   1338 class ObjectTypeChooser(Window):
   1339 
   1340     def ApplyAttributes(self, attributes):
   1341         Window.ApplyAttributes(self, attributes)
   1342         objectRow = attributes.objectRow
   1343         self.scope = uiconst.SCOPE_INFLIGHT
   1344         self.SetCaption('Object Type Chooser - %d' % objectRow.objectID)
   1345         self.SetMinSize([350, 400])
   1346         self.objectRow = objectRow
   1347         panel = Container(name='panel', parent=self.sr.main, left=const.defaultPadding, top=const.defaultPadding, width=const.defaultPadding, height=const.defaultPadding)
   1348         self.sr.panel = panel
   1349         roomObjectGroups = sm.RemoteSvc('dungeon').DEGetRoomObjectPaletteData()
   1350         kv = utillib.KeyVal()
   1351         kv.groupItems = roomObjectGroups
   1352         scrollOptions = self.GetGroupTypes(kv)
   1353         self.sr.palettescroll = eveScroll.Scroll(name='scroll', parent=self.sr.panel, align=uiconst.TOALL)
   1354         self.sr.palettescroll.Load(contentList=scrollOptions, scrolltotop=0)
   1355         button = Button(parent=self.sr.panel, label='Change Type', func=self.OnChangeType, align=uiconst.TOBOTTOM)
   1356         self.sr.palettescroll.padBottom = const.defaultPadding * 7
   1357         self.sr.palettescroll.multiSelect = 0
   1358         self.Show()
   1359 
   1360     def OnChangeType(self, *args, **kwargs):
   1361         curSel = self.sr.palettescroll.GetSelected()
   1362         if curSel:
   1363             id = curSel[0].id
   1364             name = curSel[0].label
   1365         else:
   1366             message_player('You need to select an object type from the chooser before you can change the object type')
   1367             return
   1368         self.objectRow.typeID = id
   1369 
   1370     def GetGroupTypes(self, nodeData, *args):
   1371         sublevel = nodeData.Get('sublevel', -1) + 1
   1372         scrollOptions = []
   1373         if type(nodeData.groupItems) == list:
   1374             nodeData.groupItems.sort(lambda x, y: cmp(x[1], y[1]))
   1375             for id, name in nodeData.groupItems:
   1376                 scrollOptions.append(GetFromClass(PaletteEntry, {'label': name,
   1377                  'id': id,
   1378                  'sublevel': sublevel}))
   1379 
   1380         elif type(nodeData.groupItems) == dict:
   1381             keys = nodeData.groupItems.keys()
   1382             keys.sort(lambda x, y: cmp(x[1], y[1]))
   1383             for key in keys:
   1384                 groupID, groupName = key
   1385                 scrollOptions.append(GetFromClass(ListGroup, {'label': groupName,
   1386                  'id': ('group', groupID),
   1387                  'groupItems': nodeData.groupItems[key],
   1388                  'showlen': 1,
   1389                  'sublevel': sublevel,
   1390                  'GetSubContent': self.GetGroupTypes}))
   1391 
   1392         return scrollOptions
   1393 
   1394 
   1395 class ObjectGroupListEntry(Generic):
   1396     __guid__ = 'listentry.ObjectGroupListEntry'
   1397     __nonpersistvars__ = []
   1398 
   1399     def Startup(self, *args):
   1400         Generic.Startup(self, args)
   1401         self.sr.lock = eveIcon.Icon(icon='ui_22_32_30', parent=self, size=24, align=uiconst.CENTERRIGHT, state=uiconst.UI_HIDDEN, hint='You can not change this shortcut')
   1402 
   1403     def Load(self, node):
   1404         Generic.Load(self, node)
   1405         self.sr.form = node.form
   1406         self.sr.isLocked = node.locked
   1407         self.sr.lock.state = [uiconst.UI_HIDDEN, uiconst.UI_NORMAL][node.locked]
   1408 
   1409     def GetMenu(self):
   1410         if self.sr.isLocked:
   1411             return []
   1412         m = [('Rename Group', self.RenameGroup), ('Remove Group', self.RemoveGroup), ('Delete Group Objects', self.DeleteGroupObjects)]
   1413         return m
   1414 
   1415     def OnDblClick(self, *args):
   1416         if not self.sr.isLocked:
   1417             self.RenameGroup()
   1418 
   1419     def RenameGroup(self):
   1420         self.Deselect()
   1421         self.sr.form.OpenRenameGroupDialog(key=self.sr.label.text)
   1422 
   1423     def RemoveGroup(self):
   1424         self.Deselect()
   1425         self.sr.form.RemoveGroup(self.sr.label.text)
   1426 
   1427     def DeleteGroupObjects(self):
   1428         self.Deselect()
   1429         uthread.new(self.sr.form.DeleteGroup, self.sr.label.text).context = 'svc.scenario.OnDeleteSelected'
   1430 
   1431     def RefreshCallback(self, *args):
   1432         if self.sr.node.Get('refreshcallback', None):
   1433             self.sr.node.refreshcallback()