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

colonyTemplateWindow.py (33227B)


      1 #Embedded file name: C:\BuildAgent\work\20d73f1b24eed06e\eve\release\V24.01\eve\client\script\ui\shared\planet\colonyTemplateWindow.py
      2 import blue
      3 import base64
      4 import eve.client.script.ui.shared.planet.planetUtil as planetUtil
      5 import eveformat
      6 import eveicon
      7 import inventorycommon.const as invConst
      8 import json
      9 import logging
     10 import os
     11 import uthread2
     12 from bannedwords.client import bannedwords
     13 from carbonui import uiconst, TextColor, Align, PickState, IdealSize
     14 from carbonui.text.styles import TextHeader, TextBody, TextDetail
     15 from carbonui.button.menu import MenuButtonIcon, MenuButton
     16 from carbonui.control.button import Button
     17 from carbonui.control.buttonIcon import ButtonIcon
     18 from carbonui.control.contextMenu.menuData import MenuData
     19 from carbonui.control.dragdrop.dragdata import BaseDragData
     20 from carbonui.control.scrollContainer import ScrollContainer
     21 from carbonui.control.window import Window
     22 from carbonui.primitives.container import Container
     23 from carbonui.primitives.containerAutoSize import ContainerAutoSize
     24 from carbonui.primitives.fill import Fill
     25 from carbonui.primitives.flowcontainer import FlowContainer
     26 from carbonui.primitives.layoutGrid import LayoutGrid
     27 from carbonui.primitives.sprite import Sprite
     28 from carbonui.uianimations import animations
     29 from carbonui.uicore import uicore
     30 from carbonui.util.color import Color
     31 from clientsettings.setting import UserSettingString
     32 from eve.client.script.ui import eveColor
     33 from eve.client.script.ui.control.eveLabel import Label
     34 from eve.client.script.ui.inflight.overview.overviewScrollEntry import get_distance_in_kilometers
     35 from eve.client.script.ui.quickFilter import QuickFilterEdit
     36 from eve.client.script.ui.shared.planet import planetConst
     37 from eve.client.script.ui.shared.planet.colonyTemplate import ColonyTemplate
     38 from eve.client.script.ui.shared.planet.colonyTemplateFilters import SETTING_KEY_CMD_CNTR, SETTING_KEY_EXTRACT_TYPE, SETTING_KEY_PROCESSED, SETTING_KEY_REFINED, SETTING_KEY_SPECIALIZED, SETTING_KEY_ADVANCED, SETTING_KEY_PLANET_TYPE, GetValidTemplates, CMD_CTR_LELVES
     39 from eve.client.script.ui.shared.planet.colonyTemplateLoading import PreviewLoad
     40 from eve.client.script.ui.shared.planet.pinContainers.storageIcon import StorageIcon
     41 from eve.client.script.ui.shared.planet.planetCommon import GetTierByTypeID
     42 from eve.client.script.ui.shared.planet.planetConst import *
     43 from eve.client.script.ui.shared.planet.templateSavingUtil import ConfirmExportWnd, GetDefaultStoragePath, SaveToLocalFile
     44 from eve.common.script.util.planetCommon import RADIUS_DRILLAREAMIN, RADIUS_DRILLAREAMAX
     45 from eveplanet.client.templates.templateConst import *
     46 from localization import GetByLabel
     47 from player_messaging.client.ui_message import message_player, prompt_player_fsd_dialog
     48 COMMENT_MAX_WIDTH = 60
     49 DEFAULT_DRILL_RADIUS = (RADIUS_DRILLAREAMIN + RADIUS_DRILLAREAMAX) / 2.0
     50 
     51 class ColonyTemplateWindow(Window):
     52     default_height = IdealSize.SIZE_480
     53     default_width = IdealSize.SIZE_480
     54     default_minSize = (350, IdealSize.SIZE_240)
     55     default_caption = GetByLabel('UI/PI/PITemplates')
     56     default_windowID = u'ColonyTemplateWindow'
     57     default_analyticID = 'pi_template_window'
     58     __notifyevents__ = ['OnPITemplatesUpdated', 'OnTemplateDeleted']
     59 
     60     def ApplyAttributes(self, attributes):
     61         super(ColonyTemplateWindow, self).ApplyAttributes(attributes)
     62         self.clipChildren = True
     63         self._loadingTemplates = False
     64         self._blockTemplateLoading = False
     65         self._templateLoadingPending = False
     66         self.planetEditModeContainer = attributes.get('target')
     67         self.ConstructUI()
     68         self.AsyncLoadTemplatesFromLocal()
     69         sm.RegisterNotify(self)
     70 
     71     def ConstructUI(self):
     72         self.ConstructTextFilter()
     73         bottomRightCont = Container(name='bottomRightcont', parent=self.content, align=uiconst.TOBOTTOM, height=24, clipChildren=True)
     74         self.numTemplatesLabel = TextBody(name='numTemplatesLabel', parent=bottomRightCont, align=uiconst.CENTERLEFT, color=TextColor.SECONDARY, bold=True)
     75         self.scroll = ScrollContainer(parent=self.content, align=uiconst.TOALL, top=16)
     76 
     77     def ConstructTextFilter(self):
     78         self.textFilterSetting = UserSettingString('PI_template_text_filter', '')
     79         filterCont = ContainerAutoSize(name='filterCont', parent=self.content, align=Align.TOTOP, alignMode=Align.TOTOP)
     80         mb = MenuButton(parent=filterCont, hint=GetByLabel('UI/Fitting/FittingWindow/ImportAndExport'), texturePath=eveicon.export, get_menu_func=self._get_import_export_menu)
     81         mbi = MenuButtonIcon(parent=filterCont, align=uiconst.CENTERLEFT, left=mb.left + mb.width + 8, widht=24, height=24, texturePath=eveicon.filter, iconSize=16, get_menu_func=self._GetMenuMoreOptions)
     82         self.filterEdit = QuickFilterEdit(name='searchField', parent=filterCont, hintText=GetByLabel('UI/Inventory/Filter'), maxLength=64, align=uiconst.TOTOP, OnClearFilter=self.OnFilterEditCleared, padLeft=mbi.left + mbi.width + 8, setvalue=self.textFilterSetting.get())
     83         self.filterEdit.ReloadFunction = self.OnFilterEdit
     84 
     85     def OnFilterEdit(self):
     86         self.RecordTextFieldChanges()
     87 
     88     def OnFilterEditCleared(self):
     89         self._RecordTextFieldChanges()
     90 
     91     @uthread2.debounce(0.5)
     92     def RecordTextFieldChanges(self):
     93         self._RecordTextFieldChanges()
     94 
     95     def _RecordTextFieldChanges(self):
     96         filterText = self.filterEdit.GetValue().strip().lower()
     97         self.textFilterSetting.set(filterText)
     98         self.AsyncLoadTemplatesFromLocal()
     99 
    100     def OnPITemplatesUpdated(self):
    101         self.AsyncLoadTemplatesFromLocal()
    102 
    103     def AsyncLoadTemplatesFromLocalForFilters(self, *args):
    104         if self._blockTemplateLoading:
    105             return
    106         if self._loadingTemplates:
    107             self._templateLoadingPending = True
    108             return
    109         uthread2.start_tasklet(self.InvokeLoadTemplatesFromLocal)
    110 
    111     def AsyncLoadTemplatesFromLocal(self, *args):
    112         if self._loadingTemplates:
    113             self._templateLoadingPending = True
    114             return
    115         uthread2.start_tasklet(self.InvokeLoadTemplatesFromLocal)
    116 
    117     def InvokeLoadTemplatesFromLocal(self):
    118         if self.destroyed:
    119             return
    120         if self._loadingTemplates:
    121             self._templateLoadingPending = True
    122             return
    123         self._templateLoadingPending = False
    124         self._loadingTemplates = True
    125         try:
    126             self.scroll.Flush()
    127             templatesToLoad, numTotalTemplates = self._GetValidTemplatesAndTotalNum()
    128             self.UpdateFilteredText(len(templatesToLoad), numTotalTemplates)
    129             timeOffsetValue = 0.1
    130             for i, temp in enumerate(templatesToLoad):
    131                 try:
    132                     templateEntry = ColonyTemplateEntry(parent=self.scroll, colonyTemplate=temp, height=135, align=uiconst.TOTOP, target=self.planetEditModeContainer, fileName=temp.filename)
    133                 except:
    134                     logging.error(u'File {0} cannot be interpreted'.format(temp.filename))
    135                     continue
    136 
    137                 uicore.animations.FadeTo(templateEntry, 0, 1.0, duration=0.65, timeOffset=i * timeOffsetValue)
    138 
    139         finally:
    140             self._loadingTemplates = False
    141             if self._templateLoadingPending:
    142                 self.AsyncLoadTemplatesFromLocal()
    143 
    144     def _GetValidTemplatesAndTotalNum(self):
    145         allTemplates = self._LoadFromLocal()
    146         currentPlanet = sm.GetService('planetUI').GetCurrentPlanet()
    147         currentPlanetRadius = currentPlanet.GetPlanetRadius() / 1000 if currentPlanet else None
    148         filterText = self.filterEdit.GetValue().strip().lower()
    149         validTemplates = GetValidTemplates(allTemplates, currentPlanetRadius, filterText)
    150         return (validTemplates, len(allTemplates))
    151 
    152     def UpdateFilteredText(self, numValidTemplates, numTotalTemplates):
    153         numFilteredout = max(0, numTotalTemplates - numValidTemplates)
    154         if numValidTemplates < 1:
    155             if self.planetEditModeContainer:
    156                 if numFilteredout:
    157                     notFoundText = GetByLabel('UI/PI/NoTemplatesFoundWithFilter', numFilteredOut=numFilteredout)
    158                 else:
    159                     notFoundText = GetByLabel('UI/PI/NoTemplatesFoundInPiView')
    160             else:
    161                 notFoundText = GetByLabel('UI/PI/NoTemplatesFound')
    162             self.scroll.ShowNoContentHint(notFoundText)
    163         if numFilteredout > 0:
    164             filtered_text = GetByLabel('UI/Inventory/NumFiltered', numFiltered=numFilteredout)
    165         else:
    166             filtered_text = ''
    167         text = GetByLabel('UI/PI/NumTemplates', numTemplates=numValidTemplates, numFilteredTxt=filtered_text)
    168         if numFilteredout > 0:
    169             text = eveformat.color(text, eveColor.SUCCESS_GREEN)
    170         self.numTemplatesLabel.text = text
    171 
    172     def OnTemplateDeleted(self):
    173         templatesToLoad, numTotalTemplates = self._GetValidTemplatesAndTotalNum()
    174         self.UpdateFilteredText(len(templatesToLoad), numTotalTemplates)
    175 
    176     def _GetMenuMoreOptions(self, *args):
    177         menu = MenuData()
    178         cmdCenterLV = self.CreateListOfSelectionsOfMenuData(CMD_CTR_LELVES, SETTING_KEY_CMD_CNTR)
    179         planetTypesToShow = self.CreateListOfSelectionsOfMenuData(PlanetTypes.itervalues(), SETTING_KEY_PLANET_TYPE, True)
    180         extractTypesToShow = self.CreateListOfSelectionsOfMenuData(ExtractionTypes.itervalues(), SETTING_KEY_EXTRACT_TYPE, True)
    181         processedTypesToShow = self.CreateListOfSelectionsOfMenuData(ProcessedTypes.itervalues(), SETTING_KEY_PROCESSED, True)
    182         refinedTypesToShow = self.CreateListOfSelectionsOfMenuData(RefinedTypes.itervalues(), SETTING_KEY_REFINED, True)
    183         specializedTypesToShow = self.CreateListOfSelectionsOfMenuData(SpecializedTypes.itervalues(), SETTING_KEY_SPECIALIZED, True)
    184         advancedTypesToShow = self.CreateListOfSelectionsOfMenuData(AdvancedTypes.itervalues(), SETTING_KEY_ADVANCED, True)
    185         menu.AddEntry(text=GetByLabel('UI/PI/CmdCtrLV'), subMenuData=cmdCenterLV)
    186         menu.AddEntry(text=GetByLabel('UI/PI/PlanetTypes'), subMenuData=planetTypesToShow)
    187         menu.AddEntry(text=evetypes.GetCategoryNameByCategory(invConst.categoryPlanetaryResources), subMenuData=extractTypesToShow)
    188         menu.AddSeparator()
    189         menu.AddEntry(text=localization.GetByMessageID(evetypes.GetGroupNameIDByGroup(invConst.groupBasicCommodities)), subMenuData=processedTypesToShow)
    190         menu.AddEntry(text=localization.GetByMessageID(evetypes.GetGroupNameIDByGroup(invConst.groupRefinedCommodities)), subMenuData=refinedTypesToShow)
    191         menu.AddEntry(text=localization.GetByMessageID(evetypes.GetGroupNameIDByGroup(invConst.groupSpecializedCommodities)), subMenuData=specializedTypesToShow)
    192         menu.AddEntry(text=localization.GetByMessageID(evetypes.GetGroupNameIDByGroup(invConst.groupAdvancedCommodities)), subMenuData=advancedTypesToShow)
    193         menu.AddSeparator()
    194         currentPlanet = sm.GetService('planetUI').GetCurrentPlanet()
    195         if currentPlanet:
    196             menu.AddCheckbox(text=GetByLabel('UI/PI/HideInapprop'), setting=SettingsHideInappropriateRadius)
    197             SettingsHideInappropriateRadius.on_change.connect(self.AsyncLoadTemplatesFromLocal)
    198         menu.AddEntry(text=GetByLabel('UI/Commands/Refresh'), func=self.InvokeLoadTemplatesFromLocal)
    199         return menu
    200 
    201     def CreateListOfSelectionsOfMenuData(self, iterable, settingString, isInputTypeID = False):
    202         menuDataInstance = MenuData()
    203         checkBoxes = []
    204         sortedValues = sorted(iterable, key=lambda x: (localization.GetByMessageID(evetypes.GetNameID(x)) if isInputTypeID else unicode(x)))
    205         for value in sortedValues:
    206             settingBool = UserSettingBool(settings_key=settingString.format(str(value)), default_value=False)
    207             settingBool.on_change.connect(self.AsyncLoadTemplatesFromLocalForFilters)
    208             label = localization.GetByMessageID(evetypes.GetNameID(value)) if isInputTypeID else unicode(value)
    209             newChkBox = menuDataInstance.AddCheckbox(text=label, setting=settingBool)
    210             checkBoxes.append(newChkBox)
    211 
    212         def DoAll(action):
    213             self._blockTemplateLoading = True
    214             for ckbox in checkBoxes:
    215                 ckbox.setting.set(action)
    216 
    217             self._blockTemplateLoading = False
    218             self.AsyncLoadTemplatesFromLocal()
    219 
    220         menuDataInstance.AddEntry(text=GetByLabel('UI/Common/DeselectAll'), func=lambda : DoAll(False))
    221         menuDataInstance.AddEntry(text=GetByLabel('UI/Common/SelectAll'), func=lambda : DoAll(True))
    222         return menuDataInstance
    223 
    224     def LoadFromClipboard(self, *args):
    225         warnResult = prompt_player_fsd_dialog('CustomWarning', {'header': GetByLabel('UI/Generic/Warning'),
    226          'warning': GetByLabel('UI/PI/LoadWarning')}, buttons=uiconst.OKCANCEL)
    227         if warnResult != uiconst.ID_OK:
    228             return
    229         newJSON = blue.clipboard.GetClipboardUnicode()
    230         if newJSON is None:
    231             return
    232         if len(newJSON) > 65536:
    233             warnResult = prompt_player_fsd_dialog('CustomWarning', {'header': GetByLabel('UI/Generic/Warning'),
    234              'warning': GetByLabel('UI/PI/TemplateTooLong')}, buttons=uiconst.OKCANCEL)
    235             if warnResult != uiconst.ID_OK:
    236                 return
    237         try:
    238             newTemplate = ColonyTemplate(unicode(newJSON), None)
    239             PreviewLoad(newTemplate)
    240         except UserError as e:
    241             raise
    242         except Exception as e:
    243             logging.exception('PI Templates: Error while saving file')
    244             prompt_player_fsd_dialog('CustomError', {'error': GetByLabel('UI/PI/ErrorLoadingFromClipboard')})
    245 
    246     def SaveFromClipboard(self, *args):
    247         newJSON = blue.clipboard.GetClipboardUnicode()
    248         SaveToLocalFile(newJSON, encoding='unicode-escape')
    249 
    250     def SaveFromLink(self, url):
    251         newJSON = url
    252         warnResult = prompt_player_fsd_dialog('CustomWarning', {'header': GetByLabel('UI/Generic/Warning'),
    253          'warning': GetByLabel('UI/PI/LoadWarning')}, buttons=uiconst.OKCANCEL)
    254         if warnResult != uiconst.ID_OK:
    255             return
    256         if len(newJSON) > 65536:
    257             warnResult = prompt_player_fsd_dialog('CustomWarning', {'header': GetByLabel('UI/Generic/Warning'),
    258              'warning': GetByLabel('UI/PI/TemplateTooLong')}, buttons=uiconst.OKCANCEL)
    259             if warnResult != uiconst.ID_OK:
    260                 return
    261         SaveToLocalFile(newJSON)
    262 
    263     def _LoadFromLocal(self):
    264         files = []
    265         defaultStoragePath = GetDefaultStoragePath()
    266         if not os.path.exists(defaultStoragePath):
    267             try:
    268                 os.makedirs(defaultStoragePath)
    269                 files = os.listdir(defaultStoragePath)
    270             except:
    271                 message_player(GetByLabel('UI/PI/FailedOnCreatingDirectory', path=defaultStoragePath))
    272 
    273         try:
    274             files = os.listdir(defaultStoragePath)
    275         except:
    276             try:
    277                 os.makedirs(defaultStoragePath)
    278                 files = os.listdir(defaultStoragePath)
    279             except:
    280                 message_player(GetByLabel('UI/PI/FailedOnCreatingDirectory', path=defaultStoragePath))
    281 
    282         templates = []
    283         for singleTemp in files:
    284             try:
    285                 with open(os.path.join(defaultStoragePath, singleTemp)) as tempStream:
    286                     templates.append(ColonyTemplate(tempStream.read(), singleTemp))
    287             except Exception as e:
    288                 pass
    289 
    290         templates = sorted(templates, key=lambda x: x.description.lower())
    291         return templates
    292 
    293     def _get_import_export_menu(self):
    294         m = MenuData()
    295         if self.planetEditModeContainer is not None:
    296             m.AddEntry(text=GetByLabel('UI/PI/LoadFromClipboard'), func=self.LoadFromClipboard)
    297         m.AddEntry(text=GetByLabel('UI/PI/SaveFromClipboard'), func=self.SaveFromClipboard)
    298         m.AddSeparator()
    299         if self.planetEditModeContainer and self.planetEditModeContainer.IsCommandCenterPresent():
    300             m.AddEntry(text=GetByLabel('UI/PI/ExportToClipboard'), func=lambda *args: self.planetEditModeContainer.CopyTemplateToClipboard())
    301             m.AddEntry(text=GetByLabel('UI/PI/ExportToFileLong'), func=lambda *args: self.planetEditModeContainer.SaveTemplateToFile())
    302         return m
    303 
    304 
    305 class ColonyTemplateEntry(ContainerAutoSize):
    306     isDragObject = True
    307     default_state = uiconst.UI_NORMAL
    308     default_bgColor = (0, 0, 0, 0.6)
    309     default_clipChildren = True
    310     default_padding = (0, 0, 0, 8)
    311     default_minHeight = 60
    312     default_alignMode = Align.TOTOP
    313 
    314     def ApplyAttributes(self, attributes):
    315         super(type(self), self).ApplyAttributes(attributes)
    316         self.colonyTemplate = attributes.get('colonyTemplate')
    317         self.fileName = attributes.get('fileName')
    318         self.storagePath = GetDefaultStoragePath()
    319         self.planetEditModeContainer = attributes.get('target')
    320         self.ConstructFrameAndBackground()
    321         self.innerCont = ContainerAutoSize(name='innerCont', parent=self, padding=16, align=Align.TOTOP)
    322         self.ConstructFirstRow()
    323         self.ConstructComment()
    324         self.ConstructSecondRow()
    325 
    326     def ConstructFrameAndBackground(self):
    327         texturePath = planetUtil.backgroundTexturePath.get(self.colonyTemplate.originalPlanetType)
    328         bgPlanetSprite = Sprite(parent=self, texturePath=texturePath, align=uiconst.TOPRIGHT, state=uiconst.UI_DISABLED, opacity=0.2, pos=(0, 0, 1138, 411))
    329 
    330     def AddLoadBtn(self, cont):
    331         btnCont = ContainerAutoSize(parent=cont, align=Align.TORIGHT)
    332         if self.planetEditModeContainer:
    333             plnet = sm.GetService('planetUI').GetCurrentPlanet()
    334             if plnet is not None and self.colonyTemplate.restricted and plnet.planetTypeID not in [PlanetTypes.BARREN, PlanetTypes.TEMPERATE]:
    335                 btn = Button(parent=btnCont, label=GetByLabel('UI/Generic/NotAvailableShort'), enabled=False, align=Align.BOTTOMRIGHT)
    336             else:
    337                 btn = Button(parent=btnCont, label=GetByLabel('UI/Generic/Load'), func=self.PreviewLoad, align=Align.BOTTOMRIGHT)
    338             cont.minHeight = max(cont.minHeight, btn.height)
    339 
    340     def DeleteTemplate(self, *args):
    341         fileName = self.fileName
    342         fullpath = u'{0}'.format(os.path.join(self.storagePath, fileName))
    343         templateName = self.colonyTemplate.description
    344         result = prompt_player_fsd_dialog('DeletePITemplate', {'templateName': templateName}, modal=True, buttons=uiconst.YESNO)
    345         if result != uiconst.ID_YES:
    346             return
    347         try:
    348             os.remove(fullpath)
    349             self.Disable()
    350             sm.ScatterEvent('OnTemplateDeleted')
    351             animations.FadeOut(self, duration=0.5, sleep=True)
    352             self.Close()
    353         except Exception as e:
    354             raise
    355 
    356     def CopyTemplate(self, *args):
    357         self.CopyToClipboard()
    358 
    359     def ConstructComment(self):
    360         cont = ContainerAutoSize(name='commentCont', parent=self.innerCont, align=Align.TOTOP, alignMode=Align.TOTOP, top=16)
    361         btnCont = Container(parent=cont, align=Align.TORIGHT, width=50)
    362         ButtonIcon(parent=btnCont, texturePath=eveicon.trashcan, pos=(0, 0, 16, 16), align=uiconst.TOPRIGHT, func=self.DeleteTemplate, hint=GetByLabel('UI/Common/Buttons/Delete'))
    363         ButtonIcon(parent=btnCont, texturePath=eveicon.copy, pos=(32, 0, 16, 16), align=uiconst.TOPRIGHT, func=self.CopyTemplate, hint=GetByLabel('UI/PI/CopyPlainText'))
    364         comment = self.colonyTemplate.description
    365         commentLabel = TextHeader(name='commentLabel', parent=cont, text=comment if len(comment) < COMMENT_MAX_WIDTH else u'{0}...'.format(comment[:COMMENT_MAX_WIDTH]), align=Align.TOTOP, state=uiconst.UI_NORMAL, bold=True)
    366         commentLabel.hint = comment
    367         commentLabel.isDragObject = True
    368         commentLabel.Draggable_blockDrag = False
    369         commentLabel.GetDragData = self.GetDragData
    370         commentLabel.PrepareDrag = self.PrepareDrag
    371         commentLabel.GetMenu = self.GetMenu
    372 
    373     def ConstructSecondRow(self):
    374         cont = ContainerAutoSize(name='secondRow', parent=self.innerCont, align=Align.TOTOP, height=30, alignMode=Align.TOTOP, top=16)
    375         self.AddLoadBtn(cont)
    376         if len(self.colonyTemplate.extractTypes) > 0:
    377             additionalText = ''
    378             textColor = TextColor.SECONDARY
    379             if self.planetEditModeContainer is not None:
    380                 plnet = sm.GetService('planetUI').GetCurrentPlanet()
    381                 if plnet is not None and self.colonyTemplate.originalPlanetType != plnet.planetTypeID:
    382                     textColor = Color.YELLOW
    383                     additionalText = GetByLabel('UI/PI/MightNotSuitable')
    384             exctItemAmount = self._GetAllProducts(self.colonyTemplate.extractSourceCount)
    385             text = TextDetail(parent=cont, text=GetByLabel('UI/PI/Common/Extraction'), align=Align.TOTOP, color=textColor, pickState=PickState.ON)
    386             text.hint = additionalText
    387             iconCont = FlowContainer(parent=cont, align=Align.TOTOP, top=4, contentSpacing=(8, 8))
    388             sortedExtractTypes = sorted(exctItemAmount.keys(), key=lambda x: (GetTierByTypeID(x), evetypes.GetName(x)))
    389             for typeID in sortedExtractTypes:
    390                 c = Container(parent=iconCont, align=uiconst.TOPLEFT, color=(0, 0, 0, 1.4), pos=(0, 0, 32, 32))
    391                 icon = StorageIcon(parent=c, align=uiconst.TOPLEFT, width=self.height, typeID=typeID, pos=(0, 0, 32, 32))
    392                 Fill(parent=c, color=(0, 0, 0, 0.2))
    393 
    394         if len(self.colonyTemplate.productTypes) > 0:
    395             top = 8 if self.colonyTemplate.extractTypes else 0
    396             text = TextDetail(parent=cont, text=GetByLabel('UI/PI/Common/Production'), align=Align.TOTOP, color=TextColor.SECONDARY, top=top)
    397             pdItemAmount = self._GetAllProducts(self.colonyTemplate.productSourceCount)
    398             iconCont = FlowContainer(parent=cont, align=Align.TOTOP, top=4, contentSpacing=(8, 8))
    399             sortedProducts = sorted(pdItemAmount.keys(), key=lambda x: (GetTierByTypeID(x), evetypes.GetName(x)))
    400             for typeID in sortedProducts:
    401                 c = Container(parent=iconCont, align=uiconst.TOPLEFT, color=(0, 0, 0, 1.4), pos=(0, 0, 32, 32))
    402                 icon = StorageIcon(parent=c, align=uiconst.TOPLEFT, width=self.height, typeID=typeID, pos=(0, 0, 32, 32))
    403                 Fill(parent=c, color=(0, 0, 0, 0.2))
    404 
    405     def ConstructFirstRow(self):
    406         labelOffset = 2
    407         grid = LayoutGrid(name='firstRow', parent=self.innerCont, align=Align.TOTOP, columns=4, cellSpacing=(30, 0))
    408         planetCont = ContainerAutoSize(name='planetCont', parent=grid, align=Align.CENTERLEFT, pickState=PickState.ON, hint=GetByLabel('UI/PI/PlanetTypes'))
    409         planetIcon = Sprite(parent=planetCont, texturePath=eveicon.planet_management, pos=(0, 0, 16, 16), align=Align.CENTERLEFT, pickState=PickState.OFF, color=TextColor.SECONDARY)
    410         planetTypeName = TextDetail(parent=planetCont, text=GetByLabel(planetConst.PLANETTYPE_NAMES[self.colonyTemplate.originalPlanetType]), left=labelOffset + planetIcon.width, align=Align.CENTERLEFT, color=TextColor.SECONDARY)
    411         cmdCtrCont = ContainerAutoSize(name='cmdCtrCont', parent=grid, align=Align.CENTERLEFT, pickState=PickState.ON, hint=GetByLabel('UI/PI/CmdCtrLV'))
    412         cmdCtr = Sprite(parent=cmdCtrCont, texturePath='res:/UI/Texture/Planet/icons/commandCenter_16.png', pos=(0, 0, 16, 16), align=Align.CENTERLEFT, pickState=PickState.OFF, color=TextColor.SECONDARY)
    413         textColor = TextColor.SECONDARY if sm.GetService('skills').GetEffectiveLevel(appConst.typeCommandCenterUpgrade) >= self.colonyTemplate.commandCenterLV else TextColor.DANGER
    414         cmdCtllv = TextDetail(parent=cmdCtrCont, text=GetByLabel('UI/PI/CommandCenterLevel', level=self.colonyTemplate.commandCenterLV + 1), left=labelOffset + cmdCtr.width, color=textColor, align=Align.CENTERLEFT)
    415         radiusCont = ContainerAutoSize(name='radiusCont', parent=grid, align=Align.CENTERLEFT, pickState=PickState.ON, hint=GetByLabel('UI/PI/RadiusOfPlanet'))
    416         radiusIcon = Sprite(parent=radiusCont, texturePath='res:/UI/Texture/Planet/icons/radius.png', pos=(0, 0, 16, 16), align=Align.CENTERLEFT, pickState=PickState.OFF, color=TextColor.SECONDARY)
    417         radiusText = TextDetail(parent=radiusCont, text=get_distance_in_kilometers(self.colonyTemplate.radiusOfPlanet), left=labelOffset + radiusIcon.width, align=Align.CENTERLEFT, color=TextColor.SECONDARY)
    418         templatePinCont = ContainerAutoSize(name='templatePinCont', parent=grid, align=Align.CENTERLEFT, pickState=PickState.ON)
    419         templatePinIcon = Sprite(name='templatePinIcon', parent=templatePinCont, texturePath='res:/UI/Texture/Planet/icons/templatePins.png', pos=(0, 0, 16, 16), align=Align.CENTERLEFT, pickState=PickState.OFF, color=TextColor.SECONDARY)
    420         templateData = self.colonyTemplate.loadedTemplate
    421         templatePinCont.LoadTooltipPanel = self.LoadTemplatePinsTooltip
    422         numPins = len(templateData[TemplateDictKeys.PinData])
    423         TextDetail(parent=templatePinCont, text=numPins, left=templatePinIcon.width + labelOffset, align=Align.CENTERLEFT, color=TextColor.SECONDARY)
    424         if self.planetEditModeContainer is not None:
    425             plnet = sm.GetService('planetUI').GetCurrentPlanet()
    426             if plnet is not None:
    427                 if self.colonyTemplate.radiusOfPlanet * 1000.0 < plnet.radius:
    428                     radiusText.SetTextColor(Color.YELLOW)
    429                     radiusCont.SetHint(GetByLabel('UI/PI/HintOversizedPlanet'))
    430                 else:
    431                     radiusText.SetTextColor(TextColor.SECONDARY)
    432                     radiusCont.SetHint(GetByLabel('UI/PI/RadiusOfPlanet'))
    433                 if self.colonyTemplate.originalPlanetType != plnet.planetTypeID:
    434                     planetTypeName.SetTextColor(Color.YELLOW)
    435                     planetCont.SetHint(GetByLabel('UI/PI/MightNotSuitable'))
    436                 if self.colonyTemplate.restricted and plnet.planetTypeID not in [PlanetTypes.BARREN, PlanetTypes.TEMPERATE]:
    437                     planetTypeName.SetTextColor(Color.RED)
    438                     planetCont.SetHint(GetByLabel('UI/PI/NotSuitableDueToCruelEnv'))
    439 
    440     def DoReRender(self, a):
    441         message_player(GetByLabel('UI/PI/Loading'))
    442         uthread2.sleep(0.5)
    443         message_player(GetByLabel('UI/PI/LoadCompleted'))
    444         planetUI = sm.GetService('planetUI')
    445         planetUI.myPinManager.ReRender()
    446         sm.GetService('planetUI').FocusCameraOnCommandCenter()
    447         self.planetEditModeContainer.ResetBuildbuttons(falser=True)
    448         sm.ScatterEvent('OnEditModeChanged', True)
    449 
    450     def _GetAllProducts(self, products):
    451         ret = defaultdict(int)
    452         for typeID, amount in products.iteritems():
    453             ret[typeID] += amount
    454 
    455         return ret
    456 
    457     def PreviewLoad(self, *args):
    458         return PreviewLoad(self.colonyTemplate)
    459 
    460     def CopyToClipboard(self, *args):
    461         blue.clipboard.SetClipboardData(self.colonyTemplate.rawTemplate.decode('unicode-escape'))
    462         message_player(GetByLabel('UI/PI/Copied'), flash_existing=False)
    463 
    464     def UpdateDescription(self):
    465         templateCopy = self.colonyTemplate.loadedTemplate.copy()
    466         defaultNote = templateCopy[TemplateDictKeys.Comments]
    467         wnd = ConfirmExportWnd.Open(defaultNote=defaultNote, captionPath='UI/PI/RenameTemplate', okPath='UI/PI/SaveTemplate')
    468         wnd.SetParent(uicore.layer.planet)
    469         wnd.ShowDialog(modal=True)
    470         retval = wnd.result
    471         if retval is None:
    472             return
    473         note = retval.get('note', '')
    474         bannedwords.check_words_allowed(note)
    475         templateCopy[TemplateDictKeys.Comments] = note
    476         jsonText = json.dumps(obj=templateCopy, sort_keys=True).decode('unicode-escape')
    477         fileName = u'{0}'.format(os.path.join(GetDefaultStoragePath(), self.colonyTemplate.filename))
    478         SaveToLocalFile(jsonText, fileName, encoding='unicode-escape')
    479 
    480     def GetMenu(self):
    481         menuData = MenuData()
    482         menuData.AddEntry(GetByLabel('UI/PI/RenameTemplate'), lambda : self.UpdateDescription())
    483         return menuData
    484 
    485     def GetDragData(self):
    486         from utillib import KeyVal
    487         template = KeyVal()
    488         plnName = GetByLabel(planetConst.PLANETTYPE_NAMES[self.colonyTemplate.originalPlanetType])
    489         templateName = GetByLabel('UI/PI/TemplateOf', planetType=plnName)
    490         templateDragData = PITempateDragData(templateName, self.colonyTemplate.rawTemplate)
    491         return [templateDragData]
    492 
    493     @classmethod
    494     def PrepareDrag(cls, dragContainer, dragSource, *args):
    495         dragData = dragContainer.dragData[0]
    496         displayText = eveformat.truncate_text_ignoring_tags(dragData.templateName, 24, '...')
    497         label = Label(parent=dragContainer, text=u'{0}({1}+)'.format(displayText, len(dragData.rawTemplate)), align=uiconst.TOPLEFT, bold=True)
    498         Fill(parent=dragContainer, color=(0, 0, 0, 0.3), padding=(-10, -2, -10, -2))
    499         dragContainer.width = label.textwidth
    500         dragContainer.height = label.textheight
    501         return (2, label.textheight)
    502 
    503     def LoadTemplatePinsTooltip(self, tooltipPanel, *args):
    504         templateData = self.colonyTemplate.loadedTemplate
    505         pinsByType = defaultdict(int)
    506         normalPinsByGroupID = defaultdict(int)
    507         for pin in templateData[TemplateDictKeys.PinData]:
    508             pinType = pin[TemplatePinDataDictKeys.PinTypeID]
    509             if planetUtil.IsTypeHighTechProcessor(pinType):
    510                 pinsByType[PIN_HIGH] += 1
    511             elif planetUtil.IsTypeAdvancedProcessor(pinType):
    512                 pinsByType[PIN_ADV] += 1
    513             elif planetUtil.IsTypeBasicProcessor(pinType):
    514                 pinsByType[PIN_BASIC] += 1
    515             else:
    516                 groupID = evetypes.GetGroupID(pinType)
    517                 normalPinsByGroupID[groupID] += 1
    518 
    519         if pinsByType or normalPinsByGroupID:
    520             tooltipPanel.LoadStandardSpacing()
    521             tooltipPanel.columns = 3
    522         else:
    523             return
    524 
    525         def AddPinEntry(texturePath, text, countText):
    526             Sprite(parent=tooltipPanel, align=uiconst.CENTERLEFT, pos=(0, 0, 22, 22), texturePath=texturePath, color=TextColor.SECONDARY)
    527             tooltipPanel.AddTextBodyLabel(text=text)
    528             tooltipPanel.AddTextBodyLabel(text=countText)
    529 
    530         if pinsByType:
    531             for identifier, labelPath in [(PIN_BASIC, 'UI/PI/BasicIndustryFacilities'),
    532              (PIN_ADV, 'UI/PI/AdvancedIndustryFacilities'),
    533              (PIN_HIGH, 'UI/PI/HighTechProdPlant'),
    534              ('other', 'UI/PI/OtherPins')]:
    535                 if identifier in pinsByType:
    536                     texturePath = planetUtil.GetTexturePath(identifier)
    537                     countText = pinsByType[identifier]
    538                     text = GetByLabel(labelPath)
    539                     AddPinEntry(texturePath, text, countText)
    540 
    541         for groupID, pinCount in normalPinsByGroupID.iteritems():
    542             groupName = evetypes.GetGroupNameByGroup(groupID)
    543             texturePath = planetUtil.GetTexturePath(None, groupID)
    544             AddPinEntry(texturePath, groupName, pinCount)
    545 
    546         numLinks = len(templateData[TemplateDictKeys.LinkData])
    547         numRoutes = len(templateData[TemplateDictKeys.RouteData])
    548         if numLinks:
    549             AddPinEntry('res:/UI/Texture/Planet/icons/link.png', GetByLabel('UI/PI/Common/Links'), numLinks)
    550         if numRoutes:
    551             AddPinEntry('', GetByLabel('UI/PI/Common/Routes'), numRoutes)
    552 
    553 
    554 class PITempateDragData(BaseDragData):
    555     check_link_fits = True
    556 
    557     def __init__(self, templateName, rawTemplate):
    558         super(PITempateDragData, self).__init__()
    559         self.templateName = templateName
    560         self.displayText = templateName
    561         self.rawTemplate = rawTemplate
    562 
    563     def get_link(self):
    564         return self._template_link()
    565 
    566     def _template_link(self):
    567         url = format_pi_template_url(self.rawTemplate)
    568         text = u'{0}({1}+)'.format(self.templateName, len(url))
    569         import evelink.client
    570         return evelink.Link(url=url, text=text)
    571 
    572 
    573 PIScheme = 'colonytemplate'
    574 
    575 def register_link_handlers(registry):
    576     registry.register(PIScheme, _handle_link, hint=_get_link_hint)
    577 
    578 
    579 def _handle_link(url):
    580     newJSON = _parse_url(url)
    581     ColonyTemplateWindow.Open().SaveFromLink(newJSON)
    582 
    583 
    584 def _get_link_hint(url):
    585     newJSON = _parse_url(url)
    586     loaded = json.loads(newJSON)
    587     hint = u'{0}<br><br>{1}'.format(loaded[TemplateDictKeys.Comments], GetByLabel('UI/PI/ClickToSaveToLocal'))
    588     return hint
    589 
    590 
    591 def _parse_url(url):
    592     url = unicode(url).replace(u'colonytemplate:', '')
    593     url = base64.b64decode(decompressFromUnicode(url))
    594     return url
    595 
    596 
    597 def format_pi_template_url(content):
    598     return u'{0}:{1}'.format(PIScheme, compressToUnicode(base64.b64encode(content)))
    599 
    600 
    601 def compressToUnicode(text):
    602     while len(text) % 2 != 0:
    603         text += '\x00'
    604 
    605     unicode_text = u''
    606     for i in range(0, len(text), 2):
    607         ord_val = (ord(text[i]) << 8) + ord(text[i + 1])
    608         unicode_text += unichr(ord_val)
    609 
    610     return unicode_text
    611 
    612 
    613 def decompressFromUnicode(unicode_text):
    614     text = u''
    615     for ch in unicode_text:
    616         ord_val = ord(ch)
    617         text += chr(ord_val >> 8 & 255)
    618         text += chr(ord_val & 255)
    619 
    620     return text.rstrip('\x00')