osci.StarMapWidget   F
last analyzed

Complexity

Total Complexity 175

Size/Duplication

Total Lines 723
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 581
dl 0
loc 723
rs 2
c 0
b 0
f 0
wmc 175

37 Methods

Rating   Name   Duplication   Size   Complexity  
A StarMapWidget.updateConfigModes() 0 10 1
B StarMapWidget.__init__() 0 61 1
C StarMapWidget.drawAdditions() 0 22 11
A StarMapWidget._drawFleetRangesTime() 0 19 4
A StarMapWidget.save() 0 13 2
A StarMapWidget._drawApproachingFleetLine() 0 9 1
A StarMapWidget._drawFleetRanges() 0 5 2
A StarMapWidget.precompute() 0 12 5
A StarMapWidget._drawFleetRangesFuel() 0 17 4
C StarMapWidget.drawHotbuttons() 0 27 9
A StarMapWidget.initHotbuttons() 0 25 2
C StarMapWidget.drawPopups() 0 38 10
A StarMapWidget._drawThickeningFleetOrderLines() 0 9 2
B StarMapWidget.draw() 0 31 7
A StarMapWidget.processMB3Down() 0 6 3
A StarMapWidget.processKeyDown() 0 5 2
A StarMapWidget.processMWUp() 0 2 1
A StarMapWidget._rescaleMap() 0 16 3
A StarMapWidget.onObjectSelected() 0 2 1
B StarMapWidget.toggleTempButton() 0 16 6
B StarMapWidget.toggleHotButtons() 0 29 6
A StarMapWidget.onMouseOver() 0 6 2
A StarMapWidget.processMB3Up() 0 10 2
B StarMapWidget.processMMotion() 0 21 8
A StarMapWidget.processMWDown() 0 2 1
A StarMapWidget.onBuoySelected() 0 2 1
A StarMapWidget.detectButtonOverpass() 0 5 3
F StarMapWidget.processMB1Up() 0 38 17
A StarMapWidget.setPos() 0 5 1
D StarMapWidget.processMB1Down() 0 30 12
A StarMapWidget.processMiniMapRect() 0 4 2
A StarMapWidget._processObjectHotkeys() 0 15 4
F StarMapWidget.gotoObject() 0 52 16
A StarMapWidget.gotoKeyObject() 0 5 2
F StarMapWidget.processKeyUp() 0 56 15
A StarMapWidget.focusOnKeyObject() 0 7 3
A StarMapWidget.setKeyObject() 0 10 3

How to fix   Complexity   

Complexity

Complex classes like osci.StarMapWidget 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
# -*- coding: utf-8 -*-
2
#
3
#  Copyright 2001 - 2016 Ludek Smid [http://www.ospace.net/]
4
#
5
#  This file is part of Outer Space.
6
#
7
#  Outer Space is free software; you can redistribute it and/or modify
8
#  it under the terms of the GNU General Public License as published by
9
#  the Free Software Foundation; either version 2 of the License, or
10
#  (at your option) any later version.
11
#
12
#  Outer Space is distributed in the hope that it will be useful,
13
#  but WITHOUT ANY WARRANTY; without even the implied warranty of
14
#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15
#  GNU General Public License for more details.
16
#
17
#  You should have received a copy of the GNU General Public License
18
#  along with Outer Space; if not, write to the Free Software
19
#  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20
#
21
import bisect
22
23
from pygameui.Widget import Widget, registerWidget
24
import pygameui as ui
25
from pygameui import Fonts
26
import ige.ospace.Const as Const
27
import pygame, pygame.draw, pygame.key, pygame.image
28
from dialog.ShowBuoyDlg import ShowBuoyDlg
29
from dialog.KeyModHelp import KeyModHelp
30
import gdata, client, res
31
from ige import log
32
from osci.dialog.SearchDlg import SearchDlg
33
from osci.MiniMap import MiniMap
34
from osci.StarMap import StarMap
35
36
37
class StarMapWidget(Widget):
38
39
    def __init__(self, parent, **kwargs):
40
        Widget.__init__(self, parent)
41
        self.searchDlg = SearchDlg(self.app)
42
        self.searchDlg.mapWidget = self
43
        # data
44
        self.control_modes = {}  # mutable, thus updating here will update StarMap
45
        self.updateConfigModes()
46
        self.control_modes['systems'] = 1
47
        self.control_modes['planets'] = 1
48
        self.control_modes['fleets'] = 1
49
        self.control_modes['civilian_fleets'] = 1
50
        self.control_modes['pirate_areas'] = 1
51
        self.control_modes['hotbuttons'] = 1
52
        self.control_modes['minimap'] = 1
53
        self.control_modes['redirects'] = 1
54
        self.control_modes['map_grid_coords'] = 1
55
        self.control_modes['map_grid'] = 1
56
        self.control_modes['scanners'] = 1
57
        self.control_modes['fleet_lines'] = 1
58
        self.control_modes['gate_systems'] = 1
59
        self.control_modes['alternative_view'] = 1
60
        self.control_modes['control_areas'] = 0
61
        self.control_modes['pirate_dialogs'] = 0  # only for pirate, obv.
62
        # more data
63
        self.highlightPos = None
64
        self.alwaysShowRangeFor = None
65
        self.activeObjID = Const.OID_NONE
66
        self.activeObjIDs = []
67
        self.pressedObjIDs = []
68
        self._newCurrXY = 0
69
        self.activePos = (0, 0)
70
71
        # the map itself!
72
        self.star_map = StarMap(self.control_modes)
73
        self.action = None
74
        self.callEventHandler = None
75
        self.showBuoyDlg = ShowBuoyDlg(self.app)
76
        self.KeyModHelp = KeyModHelp(self.app)
77
        self._miniMapRect = pygame.Rect(0, 20, 175, 175)
78
        self._hotbuttonsZone = pygame.Rect(0,0,0,0)
79
        self.initHotbuttons()
80
        self.miniMap = MiniMap(self._miniMapRect.width, self._miniMapRect.height)
81
        # flags
82
        self.processKWArguments(kwargs)
83
        parent.registerWidget(self)
84
        # popup menu
85
        self.popup = ui.Menu(self.app, title = _("Select object"))
86
        self.popup.subscribeAction("*", self)
87
        # overlay system
88
        self._overlayZone = False
89
        # key setting system
90
        self.selectobject = False
91
        self.setKey = False
92
        # commands
93
        self.keyPress = False
94
        # map - produced by StarMap draw method
95
        self._mapSurf = None
96
        self._actAreas = {}
97
        self._actBuoyAreas = {}
98
        # dirty flag
99
        self.repaint_map = True
100
101
102
    def updateConfigModes(self):
103
        self.control_modes['redirects'] = gdata.config.defaults.showredirects is not 'no'
104
        self.control_modes['coords'] = gdata.config.defaults.showcoords is not 'no'
105
        self.control_modes['map_grid'] = gdata.config.defaults.showmapgrid is not 'no'
106
        self.control_modes['scanners'] = gdata.config.defaults.showmapscanners is not 'no'
107
        self.control_modes['fleet_lines'] = gdata.config.defaults.showfleetlines is not 'no'
108
        self.control_modes['gate_systems'] = gdata.config.defaults.showgatesystems is not 'no'
109
        self.control_modes['alternative_mode'] = gdata.config.defaults.alternateviewmode is not 'no'
110
        self.control_modes['control_areas'] = gdata.config.defaults.showplayerzones is not 'no'
111
        self.control_modes['minimap'] = gdata.config.defaults.showminimap is not 'yes'
112
113
    def precompute(self):
114
        self.star_map.rect = self.rect
115
        self.star_map.precompute()
116
        player = client.getPlayer()
117
        if (player.type == Const.T_PIRPLAYER or\
118
            player.type == Const.T_AIPIRPLAYER) and not self.control_modes['pirate_dialogs']:
119
            self.control_modes['pirate_dialogs'] = True
120
            if self.control_modes['hotbuttons']:
121
                self.initHotbuttons() #reinit to add the pirate button
122
        self.miniMap.precompute()
123
        # self dirty flag
124
        self.repaint_map = 1
125
126
    def save(self, append='', chronicle_shot=False):
127
        name = ("%s.png" % append)
128
        if chronicle_shot:
129
            # print whole galaxy, centered over black hole
130
            # star_map has much more information about the galaxy, thus handling this request
131
            new_surf = self.star_map.chronicle_draw()
132
            pygame.image.save(new_surf, name)
133
        else:
134
            # print current player view
135
            new_surf, empty, empty = self.star_map.draw(pygame.Surface((self.star_map.rect.width,
136
                                                                        self.star_map.rect.height)))
137
            pygame.image.save(new_surf, name)
138
        return name
139
140
141
    def draw(self, surface):
142
        self._miniMapRect.left = self.rect.width - self._miniMapRect.width
143
        self._miniMapRect.top = self.rect.top
144
        if not self._mapSurf:
145
            mapSurf = pygame.Surface(self.rect.size, pygame.SWSURFACE, surface)
146
        else:
147
            mapSurf = self._mapSurf
148
149
        if self.repaint_map:
150
            mapSurf, self._actAreas, self._actBuoyAreas  = self.star_map.draw(mapSurf)
151
            # For some reason, this is not just optimization, it's mandatory for proper
152
            # function. BUG?!
153
            self.repaint_map = 0
154
            self.repaintHotbuttons = 1
155
        if self.repaintHotbuttons and self.control_modes['hotbuttons']:
156
            self.drawHotbuttons(mapSurf)
157
            self.repaintHotbuttons = 0
158
        # blit cached map
159
        surface.blit(mapSurf, self.rect)
160
        self._mapSurf = mapSurf
161
162
        if self.control_modes['minimap']:
163
            self.miniMap.draw(surface, self._miniMapRect.left, self._miniMapRect.top)
164
            if self.miniMap.needRect():
165
                self.processMiniMapRect()
166
                self.miniMap.draw(surface, self._miniMapRect.left, self._miniMapRect.top)
167
        # additional information (ranges, fleet lines, selected system sign)
168
        self.drawAdditions(surface)
169
170
        self.drawPopups(surface)
171
        return self.rect
172
173
    def drawHotbuttons(self, mapSurf):
174
        rect = mapSurf.get_rect()
175
        bottom = rect.bottom
176
        right = rect.right
177
        dx = 137
178
        dy = 46
179
        top = bottom - dy - 1
180
        left = right - dx - 1
181
        self._hotbuttonsZone.top = top + self.rect.top
182
        self._hotbuttonsZone.left = left
183
        self._hotbuttonsZone.width = dx
184
        self._hotbuttonsZone.height = dy
185
186
        pygame.draw.rect(mapSurf,(0x00, 0x00, 0x90),(left-1,top-1,dx+2,dy+2))
187
        pygame.draw.rect(mapSurf,(0x33, 0x33, 0x66),(left,top,dx,dy))
188
189
        for buttonkey in self._hotbuttons:
190
            button = self._hotbuttons[buttonkey]
191
            self._hotbuttonRects[button[0]] = [button[0],pygame.Rect(button[2]+self._hotbuttonsZone.left,button[3]+self._hotbuttonsZone.top+15,button[4],button[5])]
192
            img = res.getButton(button[0],button[1])
193
            if (button[1] and not (self._tempOverlayHotbutton and self._tempOverlayHotbutton == button[0])) or (not button[1] and self._tempOverlayHotbutton and self._tempOverlayHotbutton == button[0]):
194
                pygame.draw.rect(mapSurf,(0x90, 0x90, 0x90),(left+button[2]-1,top+15+button[3]-1,button[4]+2,button[5]+2),1)
195
            mapSurf.blit(img,(left+button[2],top+15+button[3]))
196
        if self._tempOverlayHotbutton:
197
            text = self._hotbuttons[self._tempOverlayHotbutton][7]
198
            textSrfc = Fonts.renderText(self.star_map.textSize, text, 1, (0xEF, 0xEF, 0xEF))
199
            mapSurf.blit(textSrfc, (left+2,top+1))
200
201
    def drawPopups(self, surface):
202
        # draw popups
203
        moreIDs = len(self.activeObjIDs) > 1
204
        if not moreIDs:
205
            x, y = self.activePos
206
            x += 20
207
        else:
208
            x = self.rect.left + 2
209
            y = self.rect.top
210
        if not pygame.key.get_mods() & pygame.KMOD_SHIFT:
211
            for activeObjID in self.activeObjIDs:
212
                index = 0
213
                if self.star_map._popupInfo.has_key(activeObjID):
214
                    # put pop up info on the screen
215
                    info = self.star_map._popupInfo[activeObjID]
216
                    # x1, y1 = self._actAreas[self.activeObjID].center
217
                    fg = self.theme.themeForeground #(0x30, 0xe0, 0x30, 0xff)
218
                    bg = self.theme.themeBackground #(0x20, 0x40, 0x20, 0x99)
219
                    width = 0
220
                    height = 0
221
                    # pygame.draw.line(surface, fg, (x1, y1), (x, y), 1)
222
                    for item in info:
223
                        w, h = Fonts.getTextSize('normal', item)
224
                        width = max(width, w)
225
                        height += h
226
                    if not moreIDs:
227
                        if x + width >= self.rect.width:
228
                            x -= width + 40
229
                        if y + 1 + height >= self.rect.height:
230
                            y -= height
231
                    surface.fill(bg, (x, y, width + 2, height + 2))
232
                    x += 1
233
                    tmpY = y + 1
234
                    for item in info:
235
                        textSrfc = Fonts.renderText('normal', item, 1, fg)
236
                        surface.blit(textSrfc, (x, tmpY))
237
                        tmpY += textSrfc.get_height()
238
                    x += width + 2
239
240
    def _drawApproachingFleetLine(self, surface, activeObjID):
241
        maxY = self._mapSurf.get_rect().height
242
        centerX, centerY = self._mapSurf.get_rect().center
243
        x, y, x1, y1 = self.star_map._fleetTarget[activeObjID]
244
        sx = int((x - self.star_map.currX) * self.star_map.scale) + centerX + self.rect.left
245
        sy = maxY - (int((y - self.star_map.currY) * self.star_map.scale) + centerY) + self.rect.top
246
        dx = int((x1 - self.star_map.currX) * self.star_map.scale) + centerX + self.rect.left
247
        dy = maxY - (int((y1 - self.star_map.currY) * self.star_map.scale) + centerY) + self.rect.top
248
        pygame.draw.line(surface, (0xff, 0xff, 0x00), (sx, sy), (dx, dy), 2)
249
250
    def _drawThickeningFleetOrderLines(self, surface, activeObjID):
251
        maxY = self._mapSurf.get_rect().height
252
        centerX, centerY = self._mapSurf.get_rect().center
253
        for x, y, x1, y1, color in self.star_map._fordersTarget[activeObjID]:
254
            sx = int((x - self.star_map.currX) * self.star_map.scale) + centerX + self.rect.left
255
            sy = maxY - (int((y - self.star_map.currY) * self.star_map.scale) + centerY) + self.rect.top
256
            dx = int((x1 - self.star_map.currX) * self.star_map.scale) + centerX + self.rect.left
257
            dy = maxY - (int((y1 - self.star_map.currY) * self.star_map.scale) + centerY) + self.rect.top
258
            pygame.draw.line(surface, color, (sx, sy), (dx, dy), 2)
259
260
    def _drawFleetRangesTime(self, surface, activeObjID):
261
        maxY = self._mapSurf.get_rect().height
262
        centerX, centerY = self._mapSurf.get_rect().center
263
        x, y, maxRange, operRange, halfRange, speed, turns = self.star_map._fleetRanges[activeObjID]
264
        sx = int((x - self.star_map.currX) * self.star_map.scale) + centerX + self.rect.left
265
        sy = maxY - (int((y - self.star_map.currY) * self.star_map.scale) + centerY) + self.rect.top
266
267
        for i in xrange(1, turns / 6):
268
            rng = int(i * speed * self.star_map.scale)
269
            if rng > 1:
270
                pygame.draw.circle(surface, (0x70, 0x70, 0x80), (sx, sy), rng, 1)
271
                textSrfc = Fonts.renderText(self.star_map.textSize, res.formatTime(i * 6), 1, (0x70, 0x70, 0x80), (0x00, 0x00, 0x00))
272
                surface.blit(textSrfc, (sx - rng, sy - textSrfc.get_height() / 2))
273
                surface.blit(textSrfc, (sx + rng, sy - textSrfc.get_height() / 2))
274
                surface.blit(textSrfc, (sx - textSrfc.get_width() / 2, sy - rng))
275
                surface.blit(textSrfc, (sx - textSrfc.get_width() / 2, sy + rng - textSrfc.get_height()))
276
        rng = int(max(maxRange * self.star_map.scale, 0.2 * self.star_map.scale))
277
        if rng > 1:
278
            pygame.draw.circle(surface, (0xc0, 0x20, 0x20), (sx, sy), rng, 1)
279
280
    def _drawFleetRangesFuel(self, surface, activeObjID):
281
        maxY = self._mapSurf.get_rect().height
282
        centerX, centerY = self._mapSurf.get_rect().center
283
        x, y, maxRange, operRange, halfRange, speed, turns = self.star_map._fleetRanges[activeObjID]
284
        sx = int((x - self.star_map.currX) * self.star_map.scale) + centerX + self.rect.left
285
        sy = maxY - (int((y - self.star_map.currY) * self.star_map.scale) + centerY) + self.rect.top
286
287
        # fleet range based on fuel
288
        rng = int(max(maxRange * self.star_map.scale, 0.2 * self.star_map.scale))
289
        if rng > 1:
290
            pygame.draw.circle(surface, (0xc0, 0x20, 0x20), (sx, sy), rng, 1)
291
        rng = int(operRange * self.star_map.scale)
292
        if rng > 1:
293
            pygame.draw.circle(surface, (0x20, 0x80, 0x20), (sx, sy), rng, 1)
294
        rng = int(halfRange * self.star_map.scale)
295
        if rng > 1:
296
            pygame.draw.circle(surface, (0x20, 0x20, 0x80), (sx, sy), rng, 1)
297
298
    def _drawFleetRanges(self, surface, activeObjID):
299
        if pygame.key.get_mods() & pygame.KMOD_SHIFT:
300
            self._drawFleetRangesTime(surface, activeObjID)
301
        else:
302
            self._drawFleetRangesFuel(surface, activeObjID)
303
304
    def drawAdditions(self, surface):
305
        oldClip = surface.get_clip()
306
        surface.set_clip(self.rect)
307
        centerX, centerY = self._mapSurf.get_rect().center
308
        maxY = self._mapSurf.get_rect().height
309
        # highlight position circle
310
        if self.highlightPos:
311
            sx = int((self.highlightPos[0] - self.star_map.currX) * self.star_map.scale) + centerX + self.rect.left
312
            sy = maxY - (int((self.highlightPos[1] - self.star_map.currY) * self.star_map.scale) + centerY) + self.rect.top
313
            pygame.draw.circle(surface, (0xff, 0xff, 0xff), (sx, sy), 13, 2)
314
        # fleet range in case of selecting fleet orders
315
        if self.alwaysShowRangeFor and self.star_map._fleetRanges.has_key(self.alwaysShowRangeFor):
316
            self._drawFleetRangesFuel(surface, self.alwaysShowRangeFor)
317
        for activeObjID in self.activeObjIDs:
318
            if activeObjID and activeObjID in self.star_map._fleetTarget:
319
                self._drawApproachingFleetLine(surface, activeObjID)
320
            if activeObjID and activeObjID in self.star_map._fordersTarget:
321
                self._drawThickeningFleetOrderLines(surface, activeObjID)
322
            if activeObjID and activeObjID in self.star_map._fleetRanges:
323
                self._drawFleetRanges(surface, activeObjID)
324
        # restore clipping
325
        surface.set_clip(oldClip)
326
327
    def initHotbuttons(self):
328
        # key : [ key , state , x , y , dx, dy, value, tooltip ]
329
        # 'value' is "active state' gdata value or true
330
        self._hotbuttons = {
331
            'pzone': ['pzone',self.control_modes['control_areas'],2,2,17,13, 1,_('Player Zones (CTRL-P)')],
332
            'civ': ['civ',self.control_modes['civilian_fleets'],21,2,18,13, 1,_('Civilian Ships (CTRL-H)')],
333
            'lines': ['lines',self.control_modes['fleet_lines'],41,2,18,13, 1,_('Fleet Lines (CTRL-L)')],
334
            'redir': ['redir',self.control_modes['redirects'],61,2,18,13, 1,_('Redirect Arrows (CTRL-R)')],
335
            'scanner': ['scanner',self.control_modes['scanners'],81,2,17,13, 1,_('Scanners (CTRL-S)')],
336
            'grid': ['grid',self.control_modes['map_grid'],100,2,17,13, 1,_('Grid (CTRL-G)')],
337
            'alternate': ['alternate',self.control_modes['alternative_mode'],119,2,17,13, 2,_('Alternate View (CTRL-A)')],
338
            'ov_diplo': ['ov_diplo',False,2,17,13,13, gdata.OVERLAY_DIPLO,_('Overlay: Diplomacy')],
339
            'ov_min': ['ov_min',False,17,17,13,13, gdata.OVERLAY_MIN,_('Overlay: Minerals')],
340
            'ov_env': ['ov_env',False,32,17,13,13, gdata.OVERLAY_BIO,_('Overlay: Environment')],
341
            'ov_slot': ['ov_slot',False,47,17,13,13, gdata.OVERLAY_SLOT,_('Overlay: Slots')],
342
            'ov_morale': ['ov_morale',False,62,17,13,13, gdata.OVERLAY_MORALE,_('Overlay: Morale')],
343
            'ov_fuel': ['ov_fuel',False,77,17,13,13, gdata.OVERLAY_DOCK,_('Overlay: Fuel and Repair')],
344
            'ov_gate': ['ov_gate',False,92,17,13,13, gdata.OVERLAY_STARGATE,_('Overlay: Star Gate Speed')],
345
            'ov_pirate': ['ov_pirate',False,107,17,13,13, gdata.OVERLAY_FAME,_('Overlay: Pirate Fame')],
346
        }
347
        if self.control_modes['pirate_dialogs']:
348
            self._hotbuttons['ov_piratecolony'] = ['ov_piratecolony',False,122,17,13,13, gdata.OVERLAY_PIRATECOLONYCOST,'Overlay: Pirate Colony Cost']
349
        self._oldOverlayHotbutton = False;
350
        self._tempOverlayHotbutton = False;
351
        self._hotbuttonRects = {}
352
353
    def toggleHotButtons(self, button):
354
        self.toggleTempButton(False)
355
        if (button[:3] == 'ov_'): #overlay
356
            if self._oldOverlayHotbutton == button:
357
                self.star_map.overlayMode = gdata.OVERLAY_OWNER
358
                self._hotbuttons[button][1] = False
359
                self._oldOverlayHotbutton = False
360
            else:
361
                if self._oldOverlayHotbutton:
362
                    self._hotbuttons[self._oldOverlayHotbutton][1] = False
363
                self._hotbuttons[button][1] = True
364
                self.star_map.overlayMode = self._hotbuttons[button][6]
365
                self._oldOverlayHotbutton = button
366
        else: #normal toggle
367
            if self._hotbuttons[button][1]:
368
                self._hotbuttons[button][1] = 0
369
            else:
370
                self._hotbuttons[button][1] = self._hotbuttons[button][6] # set standard value
371
            translation = {'pzone': 'control_areas',
372
                           'civ': 'civilian_fleets',
373
                           'lines': 'fleet_lines',
374
                           'redir': 'redirects',
375
                           'scanner': 'scanners',
376
                           'grid': 'map_grid',
377
                           'alternate': 'alternative_mode'}
378
            if button in translation:
379
                self.control_modes[translation[button]] = self._hotbuttons[button][1]
380
        self.repaintHotbuttons = 1
381
        self.repaint_map = 1
382
383
    def toggleTempButton(self,pos=False):
384
        if pos: # true unless we are no longer in the box, in which case we are resetting
385
            currentButton = self.detectButtonOverpass(pos)
386
            if currentButton == self._tempOverlayHotbutton: return
387
            if self._tempOverlayHotbutton:
388
                self._hotbuttons[self._tempOverlayHotbutton][1] = not self._hotbuttons[self._tempOverlayHotbutton][1]
389
            if not currentButton:
390
                self.repaintHotbuttons = 1
391
                self._tempOverlayHotbutton = False
392
                return
393
            self._hotbuttons[currentButton][1] = not self._hotbuttons[currentButton][1]
394
            self._tempOverlayHotbutton = currentButton
395
        elif self._tempOverlayHotbutton:
396
            self._hotbuttons[self._tempOverlayHotbutton][1] = not self._hotbuttons[self._tempOverlayHotbutton][1]
397
            self._tempOverlayHotbutton = False
398
        self.repaintHotbuttons = 1
399
400
    def detectButtonOverpass(self,pos):
401
        for buttonkey in self._hotbuttonRects:
402
            #log.debug(self._hotbuttonRects[buttonkey][1],pos)
403
            if self._hotbuttonRects[buttonkey][1].collidepoint(pos): return buttonkey
404
        return False
405
406
    def processMB1Down(self, evt):
407
        # handle SHIFT click as MB3
408
        mods = pygame.key.get_mods()
409
        if mods & pygame.KMOD_SHIFT:
410
            return self.processMB3Down(evt)
411
        pos = evt.pos
412
        # show current position for debugging
413
        # log.debug(pos)
414
        if self.control_modes['minimap']:
415
            if self._miniMapRect.collidepoint(pos):
416
                return ui.NoEvent
417
        if self.control_modes['hotbuttons'] and self._hotbuttonsZone.collidepoint(pos):
418
            return ui.NoEvent
419
        self.pressedObjIDs = []
420
        for objID in self._actAreas.keys():
421
            rect = self._actAreas[objID]
422
            if rect.collidepoint(pos):
423
                self.pressedObjIDs.append(objID)
424
425
        self.pressedBuoyObjIDs = []
426
        for objID in self._actBuoyAreas.keys():
427
            rect = self._actBuoyAreas[objID]
428
            if rect.collidepoint(pos):
429
                self.pressedBuoyObjIDs.append(objID)
430
431
        if self.pressedObjIDs or self.pressedBuoyObjIDs:
432
            return ui.NoEvent
433
        else:
434
            self.activeObjID = Const.OID_NONE
435
            return ui.NoEvent
436
437
    def processMB1Up(self, evt):
438
        # handle SHIFT click as MB3
439
        mods = pygame.key.get_mods()
440
        if mods & pygame.KMOD_SHIFT:
441
            return self.processMB3Up(evt)
442
        pos = evt.pos
443
        if self.control_modes['minimap']:
444
            if self._miniMapRect.collidepoint(pos):
445
                self.star_map.currX, self.star_map.currY = self.miniMap.processMB1Up((pos[0] - self._miniMapRect.left, self._miniMapRect.height - pos[1] + self._miniMapRect.top))
446
                self.processMiniMapRect()
447
                self.repaint_map = 1
448
                return ui.NoEvent
449
        if self.control_modes['hotbuttons'] and self._hotbuttonsZone.collidepoint(pos):
450
            button = self.detectButtonOverpass(pos)
451
            if button:
452
                self.toggleHotButtons(button)
453
            return ui.NoEvent
454
        objIDs = []
455
        for objID in self._actAreas.keys():
456
            rect = self._actAreas[objID]
457
            if rect.collidepoint(pos):
458
                objIDs.append(objID)
459
460
        bObjIDs = []
461
        for objID in self._actBuoyAreas.keys():
462
            rect = self._actBuoyAreas[objID]
463
            if rect.collidepoint(pos):
464
                bObjIDs.append(objID)
465
466
        if (objIDs or bObjIDs) and (self.pressedObjIDs == objIDs or self.pressedBuoyObjIDs == bObjIDs) and self.action:
467
            if self.selectobject:
468
                self.setKeyObject(objIDs,bObjIDs)
469
                return ui.NoEvent
470
            self.gotoObject(objIDs,bObjIDs)
471
            return ui.NoEvent
472
        else:
473
            self.activeObjID = Const.OID_NONE
474
            return ui.NoEvent
475
476
    def gotoObject(self,objIDs,bObjIDs):
477
        if len(objIDs) + len(bObjIDs) == 1:
478
            if len(objIDs) == 1:
479
                if self.selectobject:
480
                    return objIDs[0]
481
                self.processAction(self.action, objIDs[0])
482
                self.pressedObjIDs = []
483
            else:
484
                if self.selectobject:
485
                    return Const.OID_NONE
486
                self.showBuoyDlg.display(bObjIDs[0])
487
                self.pressedBuoyObjIDs = []
488
        else:
489
            # multiple objects -> post pop-up menu
490
            items = []
491
            for objID in objIDs:
492
                obj = client.get(objID)
493
                if obj.type == Const.T_SYSTEM:
494
                    name = getattr(obj, "name", None)
495
                    name = _("System: %s [ID: %d]") % (name or res.getUnknownName(), obj.oid)
496
                elif obj.type == Const.T_WORMHOLE:
497
                    name = getattr(obj, "name", None)
498
                    name = _("Worm hole: %s [ID: %d]") % (name or res.getUnknownName(), obj.oid)
499
                elif obj.type == Const.T_PLANET:
500
                    name = getattr(obj, "name", None)
501
                    name = _("Planet: %s [ID: %d]") % (name or res.getUnknownName(), obj.oid)
502
                elif obj.type == Const.T_FLEET:
503
                    if hasattr(obj,'customname') and obj.customname:
504
                        name = obj.customname
505
                    else:
506
                        name = getattr(obj, "name", None)
507
                    name = _("Fleet: %s [ID: %d]") % (name or res.getUnknownName(), obj.oid)
508
                else:
509
                    name = _("Unknown object [ID: %d]") % obj.oid
510
                item = ui.Item(name, action = "onObjectSelected", data = objID)
511
                items.append(item)
512
            for objID in bObjIDs:
513
                obj = client.get(objID)
514
                if obj.type == Const.T_SYSTEM:
515
                    name = getattr(obj, "name", None)
516
                    name = _("Buoy on system: %s [ID: %d]") % (name or res.getUnknownName(), obj.oid)
517
                elif obj.type == Const.T_WORMHOLE:
518
                    name = getattr(obj, "name", None)
519
                    name = _("Buoy on worm hole: %s [ID: %d]") % (name or res.getUnknownName(), obj.oid)
520
                else:
521
                    name = _("Buoy on unknown object [ID: %d]") % obj.oid
522
                item = ui.Item(name, action = "onBuoySelected", data = objID)
523
                items.append(item)
524
            self.popup.items = items
525
            self.popup.show()
526
        if self.selectobject:
527
            return Const.OID_NONE
528
529
    def onObjectSelected(self, widget, action, data):
530
        self.processAction(self.action, data)
531
532
    def onBuoySelected(self, widget, action, data):
533
        self.showBuoyDlg.display(data)
534
535
    def processMB3Down(self, evt):
536
        if self.control_modes['minimap']:
537
            if self._miniMapRect.collidepoint(evt.pos):
538
                return ui.NoEvent
539
        self._newCurrXY = 1
540
        return ui.NoEvent
541
542
    def processMB3Up(self, evt):
543
        if self._newCurrXY:
544
            x, y = evt.pos
545
            centerX, centerY = self._mapSurf.get_rect().center
546
            self.star_map.currX -= float(centerX - x) / self.star_map.scale
547
            self.star_map.currY += float(centerY - y) / self.star_map.scale
548
            self.processMiniMapRect()
549
            self.repaint_map = 1
550
            self._newCurrXY = 0
551
        return ui.NoEvent
552
553
    def processMiniMapRect(self):
554
        if self.control_modes['minimap']:
555
            rect = self._mapSurf.get_rect()
556
            self.miniMap.moveRect(self.star_map.currX, self.star_map.currY, rect.width / self.star_map.scale, rect.height / self.star_map.scale)
557
558
    def _rescaleMap(self, evt, delta):
559
        if not 10 < self.star_map.scale + delta < 80:
560
            return ui.NoEvent
561
        try:
562
            x, y = evt.pos
563
        except AttributeError:
564
            # keyboard rescale
565
            x, y = pygame.mouse.get_pos()
566
        centerX, centerY = self._mapSurf.get_rect().center
567
        sign = cmp(delta, 0)
568
        self.star_map.currX -= sign * float(centerX - x) * (1 / self.star_map.scale - 1 / (self.star_map.scale + delta))
569
        self.star_map.currY += sign * float(centerY - y) * (1 / self.star_map.scale - 1 / (self.star_map.scale + delta))
570
        self.star_map.scale += delta
571
        self.star_map.textSize = ['small', 'normal', 'large'][bisect.bisect([40, 60], self.star_map.scale)]
572
        self.repaint_map = 1
573
        self.processMiniMapRect()
574
575
    def processMWUp(self, evt):
576
        return self._rescaleMap(evt, 5)
577
578
    def processMWDown(self, evt):
579
        return self._rescaleMap(evt, -5)
580
581
    def processMMotion(self, evt):
582
        pos = evt.pos
583
        if self.control_modes['minimap']:
584
            if self._miniMapRect.collidepoint(pos):
585
                #log.debug('Minimap Rect Position');
586
                return ui.NoEvent
587
        if self.control_modes['hotbuttons'] and self._hotbuttonsZone.collidepoint(pos):
588
            #should give hotkey tooltips for this eventually
589
            self.toggleTempButton(pos)
590
            return ui.NoEvent
591
        elif self._tempOverlayHotbutton: # cleanup if cursor not in zone
592
            self.toggleTempButton(False)
593
        self.activeObjID = Const.OID_NONE
594
        self.activeObjIDs = []
595
        for objID in self._actAreas.keys():
596
            rect = self._actAreas[objID]
597
            if rect.collidepoint(pos):
598
                self.activeObjID = objID
599
                self.activeObjIDs.append(objID)
600
                self.activePos = pos
601
        return ui.NoEvent
602
603
    # put actually processing of key in "processKeyUp" using key pressed during "processKeyDown" to prevent erroneous double press detection when holding down CTRL, SHIFT, or ALT keys
604
    def processKeyDown(self, evt):
605
        self.keyPress = evt
606
        if self.callEventHandler:
607
            self.callEventHandler.processKeyDown(evt)
608
        return ui.NoEvent
609
610
    def _processObjectHotkeys(self, evt):
611
        if pygame.key.get_mods() & pygame.KMOD_CTRL:
612
            log.debug('Set Key:', evt.key)
613
            if gdata.config.defaults.displayhelp != 'no':
614
                self.KeyModHelp.show()
615
            self.selectobject = True
616
            self.setKey = evt.key
617
            self.app.setStatus(_("Select object to hotkey. ESC to cancel."))
618
        elif pygame.key.get_mods() & pygame.KMOD_SHIFT:
619
            log.debug('Focus Key:', evt.key)
620
            self.focusOnKeyObject(evt.key)
621
        else:
622
            log.debug('Goto Key:', evt.key)
623
            self.gotoKeyObject(evt.key)
624
        return ui.NoEvent
625
626
    def processKeyUp(self, evt2):
627
        if self.callEventHandler:
628
            self.callEventHandler.processKeyUp(evt2)
629
        evt = self.keyPress
630
        if not self.keyPress: return ui.NoEvent
631
        self.keyPress = False
632
        # ==== Object Hotkeys ====
633
        #I have not found unicode escape characters for Ctrl-0 through Ctrl-9, so using direct key reference (less preferred due to international keyboards)
634
        if evt.key in [49,50,51,52,53,54,55,56,57,48]:
635
            self._processObjectHotkeys(evt)
636
        # ==== Map and Dialog Hotkeys ====
637
        elif evt.key == pygame.K_ESCAPE and self.selectobject:
638
            log.debug('Canceled Key')
639
            if self.selectobject:
640
                self.app.setStatus(_("Ready."))
641
                self.selectobject = False
642
            return ui.NoEvent
643
        if not evt.unicode:
644
            # force update
645
            self.star_map.scale += 1
646
            self.star_map.scale -= 1
647
            return ui.NoEvent
648
        if evt.unicode in u'+=':
649
            self._rescaleMap(evt, 5)
650
        elif evt.unicode == u'-':
651
            self._rescaleMap(evt, -5)
652
        # Space Bar - Recenter
653
        elif evt.unicode == u' ':
654
            x, y = pygame.mouse.get_pos()
655
            centerX, centerY = self._mapSurf.get_rect().center
656
            self.star_map.currX -= float(centerX - x) / self.star_map.scale
657
            self.star_map.currY += float(centerY - y) / self.star_map.scale
658
            self.repaint_map = 1
659
            self._newCurrXY = 0
660
        # ==== Standard Hotkeys ====
661
        # Reserve CTRL-C for copy (future editor support)
662
        # Ctrl+F
663
        toggleMapping = {u'\x01': 'alternate',  # Alternative system info
664
                         u'\x07': 'grid',       # Grid
665
                         u'\x08': 'civ',        # Civilian ships
666
                         u'\x0C': 'lines',      # Fleet lines
667
                         u'\x10': 'pzone',      # Control areas
668
                         u'\x12': 'redir',      # Redirections
669
                         u'\x13': 'scanner'}    # Scanner circles
670
        if evt.unicode in toggleMapping and pygame.key.get_mods() & pygame.KMOD_CTRL:
671
            self.toggleHotButtons(toggleMapping[evt.unicode])
672
        # Ctrl+F to open the search (find) dialog
673
        elif evt.unicode == u'\x06' and pygame.key.get_mods() & pygame.KMOD_CTRL:
674
            self.searchDlg.display()
675
        # Reserve CTRL-V,X,and Z for paste, cut, and undo (future editor support)
676
        # ==== Else ====
677
        else:
678
            # force update
679
            self.star_map.scale += 1
680
            self.star_map.scale -= 1
681
        return ui.NoEvent
682
683
    def setKeyObject(self,objIDs,bObjIDs):
684
        objID = self.gotoObject(objIDs,bObjIDs)
685
        log.debug('Setting Key Object To:',objID)
686
        self.app.setStatus(_("Ready."))
687
        self.selectobject = False
688
        if (objID == Const.OID_NONE):
689
            return
690
        obj = client.get(objID)
691
        if obj.type in (Const.T_SYSTEM, Const.T_PLANET, Const.T_FLEET):
692
            gdata.objectFocus[self.setKey]=objID
693
694
    def gotoKeyObject(self,evtkey):
695
        if evtkey in gdata.objectFocus:
696
            objID = gdata.objectFocus[evtkey]
697
            self.processAction(self.action, objID)
698
            self.pressedObjIDs = []
699
700
    def focusOnKeyObject(self,evtkey):
701
        if evtkey in gdata.objectFocus:
702
            objID = gdata.objectFocus[evtkey]
703
            obj = client.get(objID, noUpdate = 1)
704
            if hasattr(obj, "x"):
705
                gdata.mainGameDlg.win.vStarMap.highlightPos = (obj.x, obj.y)
706
                gdata.mainGameDlg.win.vStarMap.setPos(obj.x, obj.y)
707
708
    def onMouseOver(self):
709
        self.mouseOver = 1
710
        try:
711
            self.parent.parent.setFocus(self)
712
        except:
713
            log.debug('Cannot refocus on starmap')
714
715
    def setPos(self, x, y):
716
        self.star_map.currX = x
717
        self.star_map.currY = y
718
        self.repaint_map = 1
719
        self.processMiniMapRect()
720
721
722
registerWidget(StarMapWidget, 'starmapwidget')
723