Passed
Pull Request — master (#291)
by Marek
01:47
created

ProblemsDlg._addProblemsStructStatus()   C

Complexity

Conditions 10

Size

Total Lines 40
Code Lines 32

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 10
eloc 32
nop 4
dl 0
loc 40
rs 5.9999
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like osci.dialog.ProblemsDlg.ProblemsDlg._addProblemsStructStatus() often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
#
2
#  Copyright 2001 - 2016 Ludek Smid [http://www.ospace.net/]
3
#
4
#  This file is part of Outer Space.
5
#
6
#  Outer Space is free software; you can redistribute it and/or modify
7
#  it under the terms of the GNU General Public License as published by
8
#  the Free Software Foundation; either version 2 of the License, or
9
#  (at your option) any later version.
10
#
11
#  Outer Space is distributed in the hope that it will be useful,
12
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
13
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
#  GNU General Public License for more details.
15
#
16
#  You should have received a copy of the GNU General Public License
17
#  along with Outer Space; if not, write to the Free Software
18
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
19
#
20
21
import pygameui as ui
22
import re
23
from osci import gdata, client, res
24
import ige.ospace.Const as Const
25
from ige.ospace import Utils, Rules
26
import string, math, copy
27
28
class ProblemsDlg:
29
    """Displays 'Problem locator' dialog.
30
31
    """
32
    def __init__(self, app):
33
        self.app = app
34
        self.createUI()
35
36
    def display(self):
37
        self.show()
38
        self.win.show()
39
        # register for updates
40
        if self not in gdata.updateDlgs:
41
            gdata.updateDlgs.append(self)
42
43
    def hide(self):
44
        self.win.setStatus(_("Ready."))
45
        self.win.hide()
46
        # unregister updates
47
        if self in gdata.updateDlgs:
48
            gdata.updateDlgs.remove(self)
49
50
    def update(self):
51
        self.show()
52
53
    class Problems:
54
        def __init__(self, win):
55
            self.items = []
56
            self.checkboxes = {gdata.CRI : win.vCritical.checked,
57
                               gdata.MAJ : win.vMajor.checked,
58
                               gdata.MIN : win.vMinor.checked,
59
                               gdata.INFO: win.vInfo.checked}
60
61
        def append(self, severity, item):
62
            if self.checkboxes[severity]:
63
                item.foreground = gdata.sevColors[severity]
64
                self.items.append(item)
65
66
    def _addProblemsStructStatus(self, problems, struct, planet):
67
        player = client.getPlayer()
68
        status = struct[Const.STRUCT_IDX_STATUS]
69
        tech = client.getFullTechInfo(struct[Const.STRUCT_IDX_TECHID])
70
71
        if hasattr(player, 'techs'):
72
            techEff = Rules.techImprEff[player.techs.get(struct[Const.STRUCT_IDX_TECHID], Rules.techBaseImprovement)]
73
        else:
74
            techEff = Rules.techImprEff[Rules.techBaseImprovement]
75
76
        HPturn = max(1, int(0.02 * tech.maxHP * techEff))
77
        turnsToDestroy = math.ceil(struct[Const.STRUCT_IDX_HP] / HPturn)
78
79
        if turnsToDestroy < Rules.turnsPerDay * 2:
80
            severity = gdata.MAJ
81
            if turnsToDestroy < Rules.turnsPerDay:
82
                severity = gdata.CRI
83
        else:
84
            severity = gdata.MIN
85
86
        if not status & Const.STRUCT_STATUS_ON:
87
            # structure is off
88
            problems.append(severity, ui.Item(planet.name, tOID = planet.oid, tType = Const.T_PLANET,
89
                    vDescription = _('Structure (%s) is off and will be destroyed in %s turns.') % (tech.name, res.formatTime(turnsToDestroy))))
90
91
        if status & Const.STRUCT_STATUS_DETER:
92
            problems.append(gdata.MAJ, ui.Item(planet.name, tOID = planet.oid, tType = Const.T_PLANET,
93
                vDescription = _('Structure (%s) is deteriorating.') % (tech.name,)))
94
        if status & Const.STRUCT_STATUS_NOBIO:
95
            problems.append(gdata.INFO, ui.Item(planet.name, tOID = planet.oid, tType = Const.T_PLANET,
96
                vDescription = _('Structure (%s) has insufficient supply of biomatter.') % (tech.name,)))
97
        if status & Const.STRUCT_STATUS_NOEN:
98
            problems.append(gdata.INFO, ui.Item(planet.name, tOID = planet.oid, tType = Const.T_PLANET,
99
                vDescription = _('Structure (%s) has insufficient supply of energy.') % (tech.name,)))
100
        if status & Const.STRUCT_STATUS_NOPOP:
101
            problems.append(gdata.INFO, ui.Item(planet.name, tOID = planet.oid, tType = Const.T_PLANET,
102
                vDescription = _('Structure (%s) has insufficient supply of workers.') % (tech.name,)))
103
        if status & Const.STRUCT_STATUS_REPAIRING:
104
            problems.append(gdata.INFO, ui.Item(planet.name, tOID = planet.oid, tType = Const.T_PLANET,
105
                vDescription = _('Structure (%s) is repairing.') % (tech.name,)))
106
107
    def _getSystemRefuel(self, system):
108
        player = client.getPlayer()
109
        maxRefuelMax = 0
110
        hasRefuel = False
111
        for planetID in system.planets:
112
            planet = client.get(planetID, noUpdate = 1)
113
            if hasattr(planet, 'owner'):
114
                if hasattr(planet, 'hasRefuel'):
115
                    hasRefuel = hasRefuel or planet.hasRefuel
116
                if hasattr(planet, 'refuelMax'):
117
                    if planet.owner == player.oid:
118
                        maxRefuelMax = max(maxRefuelMax, planet.refuelMax)
119
                    elif player.diplomacyRels.has_key(planet.owner):
120
                        dipl = client.getDiplomacyWith(planet.owner)
121
                        if dipl.pacts.has_key(Const.PACT_ALLOW_TANKING) and dipl.pacts[Const.PACT_ALLOW_TANKING][0] == Const.PACT_ACTIVE:
122
                            maxRefuelMax = max(maxRefuelMax, planet.refuelMax)
123
                elif hasattr(planet, 'hasRefuel'):
124
                    maxRefuelMax
125
        return hasRefuel, maxRefuelMax
126
127
    def _addProblemsFleets(self, problems, fleet):
128
        energyReserve = fleet.storEn  * 100 / fleet.maxEn
129
        system = None
130
131
        if hasattr(fleet, 'orbiting') and fleet.orbiting:
132
            system = client.get(fleet.orbiting, noUpdate = 1)
133
            hasRefuel, maxRefuelMax = self._getSystemRefuel(system)
134
        else:
135
            # we do not report fleets in flight
136
            return
137
138
        systemName = res.getUnknownName()
139
        if system and hasattr(system, "name"):
140
            systemName = system.name
141
142
        # is fleet named?
143
        if hasattr(fleet,'customname') and fleet.customname:
144
            name = fleet.customname
145
        else:
146
            name = getattr(fleet, "name", None)
147
148
        note = _(' and is NOT refueling')
149
        if 0 < maxRefuelMax <= energyReserve:
150
            severity = gdata.INFO
151
            if maxRefuelMax <= energyReserve:
152
                note = _(' and CAN refuel, but reached planet maximum refuel amount')
153
            else:
154
                note = _(' and IS refueling')
155
        elif hasRefuel:
156
            severity = gdata.MIN
157
            note = _(' and utilizes unknown refueling capacities')
158
        elif energyReserve < 25:
159
            severity = gdata.CRI
160
        elif energyReserve < 50:
161
            severity = gdata.MAJ
162
        elif energyReserve == 100:
163
            problems.append(gdata.INFO, ui.Item(systemName, tOID = fleet.oid, tType = Const.T_FLEET,
164
                        vDescription = _('Fleet "%s" has full fuel tanks.') % (name)))
165
            return
166
        else:
167
            severity = gdata.INFO
168
        problems.append(severity, ui.Item(systemName, tOID = fleet.oid, tType = Const.T_FLEET,
169
                    vDescription = _('Fleet "%s" is low on fuel [%d %%]%s.') % (name, energyReserve, note)))
170
171
    def _addProblemsMaterial(self, problems, system, mat, totalMat, material):
172
        if mat >= 0:
173
            return
174
        surplusTurns = totalMat / (-mat)
175
        if surplusTurns < Rules.turnsPerDay * 7:
176
            severity = gdata.MAJ
177
        elif surplusTurns < Rules.turnsPerDay * 2:
178
            severity = gdata.CRI
179
        else:
180
            severity = gdata.MIN
181
182
        if totalMat > 0:
183
            note = _(' surplus %d (%s)' % (totalMat, res.formatTime(surplusTurns)))
184
        else:
185
            note = _(' with no surplus left!')
186
        problems.append(severity, ui.Item(system.name, tOID = system.oid, tType = Const.T_SYSTEM,
187
                     vDescription = _('%s decreasing - last turn change %d, %s.') % (material, mat, note)))
188
189
    def _addProblemsResearch(self, problems):
190
        player = client.getPlayer()
191
        totalEtc = 0
192
        # compute length of research queue
193
        for task in player.rsrchQueue:
194
            tech = client.getTechInfo(task.techID)
195
            fulltech = client.getFullTechInfo(task.techID)
196
            researchSci = Utils.getTechRCost(player, task.techID, task.improvement)
197
            maxImprovement = min(Rules.techMaxImprovement,fulltech.maxImprovement)
198
            maxImpTotalSci = 0
199
            if task.improveToMax and task.improvement < maxImprovement:
200
                for impr in range(task.improvement+1,maxImprovement+1):
201
                    maxImpTotalSci += Utils.getTechRCost(player, task.techID, impr)
202
            if task.changeSci > 0:
203
                value = float(researchSci - task.currSci) / max(task.changeSci, player.effSciPoints)
204
                totalEtc += int(value + 1)
205
                if player.effSciPoints != 0:
206
                    totalEtc += float(maxImpTotalSci) / player.effSciPoints
207
            elif task.changeSci < 0:
208
                totalEtc -= float(task.currSci) / min(task.changeSci, player.effSciPoints)
209
            elif player.effSciPoints > 0:
210
                value = float(researchSci) / player.effSciPoints
211
                totalEtc += int(value + 1)
212
                totalEtc += float(maxImpTotalSci) / player.effSciPoints
213
            else:
214
                totalEtc = 99999
215
                break
216
217
        # check empty research queue
218
        if totalEtc == 0 and len(player.rsrchQueue) == 0 and player.effSciPoints > 0:
219
            problems.append(gdata.CRI, ui.Item(_('Research'), tType = Const.T_TECHNOLOGY,
220
                vDescription = _('Research queue is empty.')))
221
        # check short reseach queue
222
        elif totalEtc < Rules.turnsPerDay * 2:
223
            severity = gdata.MIN
224
            if totalEtc < Rules.turnsPerDay:
225
                severity = gdata.MAJ
226
            problems.append(severity, ui.Item(_('Research'), tType = Const.T_TECHNOLOGY,
227
                        vDescription = _('Research queue ends in %s turns, %d item(s) on list.') % (res.formatTime(totalEtc), len(player.rsrchQueue))))
228
229
    def _addProblemsGlobalQueues(self, problems):
230
        player = client.getPlayer()
231
        ## go through all planets to understand the state of global queues
232
        # holder for (number , eff production) of planets set to each queue
233
        globalQueueStats=[(0,0), (0,0), (0,0), (0,0), (0,0)]
234
        queConstValues = [0, 0, 0, 0, 0]
235
        queEtc = [0, 0, 0, 0, 0]
236
237
        for planetID in player.planets:
238
            planet = client.get(planetID, noUpdate = 1)
239
            globalQueueStats[planet.globalQueue] = tuple([sum(x) for x in zip(globalQueueStats[planet.globalQueue], (1, planet.effProdProd))])
240
241
        # evaluate depletion rate of the global queue
242
        for queue in range(5):
243
            quePlanets, queEffProd = globalQueueStats[queue]
244
            for task in player.prodQueues[queue]:
245
                if task.isShip:
246
                    tech = player.shipDesigns[task.techID]
247
                else:
248
                    tech = client.getFullTechInfo(task.techID)
249
                queConstValues[queue] += task.quantity * tech.buildProd
250
            if queEffProd > 0:
251
                queEtc[queue] = math.ceil(float(queConstValues[queue])/queEffProd)
252
            else:
253
                queEtc[queue] = 99999
254
255
        # creation of items with global queue problems
256
        for queue in range(1, 5):
257
            queName = res.globalQueueName(queue)
258
            quePlanets = globalQueueStats[queue][0]
259
            # check empty global production queue with at least one planet [so its relevant]
260
            if queConstValues[queue] == 0 and  quePlanets > 0:
261
                problems.append(gdata.CRI, ui.Item(_('Global queue ' + queName), tType = Const.T_QUEUE,
262
                    vDescription = _('Global production queue {0} used by {1} planet(s) is empty.'.format(queName, quePlanets))))
263
            # check end of global production queue
264
            elif queEtc[queue] < Rules.turnsPerDay * 2:
265
                severity = gdata.MIN
266
                if queEtc[queue] < Rules.turnsPerDay:
267
                    severity = gdata.MAJ
268
                problems.append(severity, ui.Item(_('Global queue ' + queName), tType = Const.T_QUEUE,
269
                    vDescription = _('Global production queue {0} used by {1} planet(s) runs out in {2} turns.'.format(queName, quePlanets, res.formatTime(queEtc[queue])))))
270
        return queEtc[0] # time of depletion of the default queue will be reused later
271
272
    def _addProblemsSlots(self, problems, system):
273
        player = client.getPlayer()
274
        playerPlanets = set(system.planets) & set(player.planets)
275
        freeSlots = {}
276
        for planetID in system.planets:
277
            planet = client.get(planetID, noUpdate = 1)
278
            freeSlots[planetID] = planet.plSlots - len(planet.slots)
279
        buildingInfo = {}
280
281
        for planetID in playerPlanets:
282
            planet = client.get(planetID, noUpdate = 1)
283
            if planet.prodQueue and planet.effProdProd > 0:
284
                for task in planet.prodQueue:
285
                    if task.isShip:
286
                        continue
287
                    tech = client.getFullTechInfo(task.techID)
288
                    if tech.isStructure and hasattr(task, "demolishStruct") and task.demolishStruct == 0:
289
                        freeSlots[planetID] -= task.quantity
290
                        # information about source and target of constructing
291
                        if buildingInfo.has_key((planetID, task.targetID)):
292
                            buildingInfo[(planetID, task.targetID)] += task.quantity
293
                        else:
294
                            buildingInfo[(planetID, task.targetID)] = task.quantity
295
                    elif tech.isProject and tech.id == 3802:
296
                        # we are constructing Habitable Surface Expansion
297
                        # so after construction we will have new slot on the planet
298
                        freeSlots[task.targetID] += 1
299
300
        for planetID, free in freeSlots.items():
301
            if free < 0:
302
                # not enough space, report for every planet that builds on this one
303
                planet = client.get(planetID, noUpdate = 1)
304
                for (sourceID, targetID), quantity in buildingInfo.items():
305
                    if planetID == targetID:
306
                        source = client.get(sourceID, noUpdate = 1)
307
                        problems.append(gdata.MAJ, ui.Item(source.name, tOID = sourceID, tType = Const.T_PLANET,
308
                            vDescription = _('There is no space for all constructed buildings on %s.') % (planet.name)))
309
310
    def _addProblemsDefaultQueue(self, problems, planet, defaultQueueEtc):
311
        player = client.getPlayer()
312
        if not planet.effProdProd > 0:
313
            return
314
        planetEtc = 0
315
        # compute length of production queue
316
        for task in planet.prodQueue:
317
            if task.isShip:
318
                tech = player.shipDesigns[task.techID]
319
            else:
320
                tech = client.getFullTechInfo(task.techID)
321
            modifier = Rules.buildOnAnotherPlanetMod if task.targetID != planet.oid else 1
322
            planetEtc += math.ceil(float(task.quantity * tech.buildProd * modifier - task.currProd) / planet.effProdProd)
323
324
        etc = planetEtc + defaultQueueEtc
325
        # check empty production queue
326
        if not etc:
327
            problems.append(gdata.CRI, ui.Item(planet.name, tOID = planet.oid, tType = Const.T_PLANET,
328
                vDescription = _('Production queue is empty.')))
329
            return
330
        if etc < Rules.turnsPerDay:
331
            severity = gdata.MAJ
332
        elif etc < Rules.turnsPerDay * 2:
333
            severity = gdata.MIN
334
        else:
335
            severity = gdata.INFO
336
337
        problems.append(severity, ui.Item(planet.name, tOID = planet.oid, tType = Const.T_PLANET,
338
            vDescription = _('Production queue may end in {0} turns ({1} directly in planet queue).'.format(res.formatTime(etc), res.formatTime(planetEtc)))))
339
340
    def show(self):
341
        critical = self.win.vCritical.checked
342
        major = self.win.vMajor.checked
343
        minor = self.win.vMinor.checked
344
        info = self.win.vInfo.checked
345
346
        disp = 1
347
348
        player = client.getPlayer()
349
        problems = self.Problems(self.win)
350
        systems = set([])
351
        for planetID in player.planets:
352
            planet = client.get(planetID, noUpdate = 1)
353
            systems.add(planet.compOf)
354
355
        defaultQueueEtc = self._addProblemsGlobalQueues(problems)
356
357
358
        for systemID in systems:
359
            system = client.get(systemID, noUpdate = 1)
360
            bio = 0
361
            totalBio = 0
362
            en = 0
363
            totalEn = 0
364
            # holds modified planets
365
            planetCopies = {}
366
367
            for planetID in system.planets:
368
                planet = client.get(planetID, noUpdate = 1)
369
                # copy of planet to change plSlots count
370
                if hasattr(planet, 'owner') and planet.owner == player.oid:
371
                    # add planet to the global queue stats
372
                    # compute bio and en for system
373
                    bio += planet.changeBio
374
                    totalBio += max(0, planet.storBio - planet.minBio)
375
                    en  += planet.changeEn
376
                    totalEn += max(0, planet.storEn - planet.minEn)
377
                    # the planet needs to have global queue 0 - the default one - to have its queue reported
378
                    if self.win.vPlanets.checked:
379
                        if not planet.globalQueue:
380
                            self._addProblemsDefaultQueue(problems, planet, defaultQueueEtc)
381
                        for struct in planet.slots:
382
                            self._addProblemsStructStatus(problems, struct, planet)
383
384
            # free slots within the system
385
            self._addProblemsSlots(problems, system)
386
            # check bio for system
387
            if self.win.vSystems.checked:
388
                self._addProblemsMaterial(problems, system, bio, totalBio, 'Bio')
389
                self._addProblemsMaterial(problems, system, en, totalEn, 'En')
390
391
        # check fleets
392
        if self.win.vFleets.checked:
393
            for fleetID in player.fleets:
394
                fleet = client.get(fleetID, noUpdate = 1)
395
                self._addProblemsFleets(problems, fleet)
396
397
        # check research queue
398
        if self.win.vResearch.checked:
399
            self._addProblemsResearch(problems)
400
            
401
402
        self.win.vProblems.items = problems.items
403
        self.win.vProblems.itemsChanged()
404
405
    def onClose(self, widget, action, data):
406
        self.hide()
407
408
    def onShowSource(self, widget, action, data):
409
        item = self.win.vProblems.selection[0]
410
        if item.tType == Const.T_FLEET:
411
            object = client.get(item.tOID, noUpdate = 1)
412
            # center on map
413
            if hasattr(object, "x"):
414
                gdata.mainGameDlg.win.vStarMap.highlightPos = (object.x, object.y)
415
                gdata.mainGameDlg.win.vStarMap.setPos(object.x, object.y)
416
                self.hide()
417
                return
418
        elif item.tType in (Const.T_SYSTEM, Const.T_PLANET):
419
            if item.tOID != Const.OID_NONE:
420
                gdata.mainGameDlg.onSelectMapObj(None, None, item.tOID)
421
                return
422
        elif item.tType == Const.T_TECHNOLOGY:
423
            gdata.mainGameDlg.researchDlg.display()
424
            return
425
        elif item.tType == Const.T_QUEUE:
426
            gdata.mainGameDlg.globalQueuesDlg.display()
427
        self.win.setStatus(_("Cannot show location."))
428
429
    def onShowLocation(self, widget, action, data):
430
        item = self.win.vProblems.selection[0]
431
        if item.tType in (Const.T_SYSTEM, Const.T_PLANET, Const.T_FLEET):
432
            object = client.get(item.tOID, noUpdate = 1)
433
            # center on map
434
            if hasattr(object, "x"):
435
                gdata.mainGameDlg.win.vStarMap.highlightPos = (object.x, object.y)
436
                gdata.mainGameDlg.win.vStarMap.setPos(object.x, object.y)
437
                self.hide()
438
                return
439
        self.win.setStatus(_("Cannot show location."))
440
441
    def onToggleCondition(self, widget, action, data):
442
        self.update()
443
444
    def createUI(self):
445
        screenWidth, screenHeight = gdata.scrnSize
446
        # size of dialog in layout metrics (for SimpleGridLM)
447
        cols = 40
448
        rows = 29
449
        # dialog width and height in pixels
450
        isSmallWin = screenHeight == 600 and screenWidth == 800
451
        width = cols * 20 + 4 * (not isSmallWin)
452
        height = rows * 20 + 4 * (not isSmallWin)
453
        #creating dialog window
454
        self.win = ui.Window(self.app,
455
            modal = 1,
456
            escKeyClose = 1,
457
            movable = 0,
458
            title = _("Problems Locator"),
459
            titleOnly = isSmallWin,
460
            #rect = ui.Rect((screenWidth - width) / 2, ((screenHeight - height) / 2) * (not isSmallWin), width, height),
461
            rect = ui.Rect((screenWidth - 800 - 4 * (not isSmallWin)) / 2, (screenHeight - 600 - 4 * (not isSmallWin)) / 2, width, height),
462
            layoutManager = ui.SimpleGridLM(),
463
        )
464
        self.win.subscribeAction('*', self)
465
        # first row is window title
466
        rows -= 1
467
468
        ui.Listbox(self.win, layout = (0, 0, cols, rows - 2), id = 'vProblems',
469
            columns = [(_('Location'), 'text', 10, ui.ALIGN_W),
470
            (_('Problem description'), 'vDescription', 30, ui.ALIGN_W)],
471
            columnLabels = 1, action='onShowSource', rmbAction='onShowLocation')
472
473
        btnWidth = 4
474
        ui.Check(self.win, layout = (btnWidth * 0, rows - 2, btnWidth, 1), id = 'vSystems',
475
            text = _('Systems'), action = 'onToggleCondition', checked = 1)
476
        ui.Check(self.win, layout = (btnWidth * 1, rows - 2, btnWidth, 1), id = 'vPlanets',
477
            text = _('Planets'), action = 'onToggleCondition', checked = 1)
478
        ui.Check(self.win, layout = (btnWidth * 2, rows - 2, btnWidth, 1), id = 'vFleets',
479
            text = _('Fleets'), action = 'onToggleCondition', checked = 1)
480
        ui.Check(self.win, layout = (btnWidth * 3, rows - 2, btnWidth, 1), id = 'vResearch',
481
            text = _('Research'), action = 'onToggleCondition', checked = 1)
482
483
        ui.Check(self.win, layout = (btnWidth * 6, rows - 2, btnWidth, 1), id = 'vCritical',
484
            text = _('Critical'), action = 'onToggleCondition', checked = 1)
485
        ui.Check(self.win, layout = (btnWidth * 7, rows - 2, btnWidth, 1), id = 'vMajor',
486
            text = _('Major'), action = 'onToggleCondition', checked = 1)
487
        ui.Check(self.win, layout = (btnWidth * 8, rows - 2, btnWidth, 1), id = 'vMinor',
488
            text = _('Minor'), action = 'onToggleCondition', checked = 1)
489
        ui.Check(self.win, layout = (btnWidth * 9, rows - 2, btnWidth, 1), id = 'vInfo',
490
            text = _('Info'), action = 'onToggleCondition', checked = 0)
491
492
        # dialog bottom line
493
        ui.Title(self.win, layout = (0, rows - 1, cols - 5, 1))
494
        ui.TitleButton(self.win, layout = (cols - 5, rows - 1, 5, 1), text = _("Close"), action = 'onClose')
495