PageRenderTime 34ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/progenitus/client/desktop.py

https://github.com/TheGurke/Progenitus
Python | 899 lines | 878 code | 11 blank | 10 comment | 4 complexity | 47445411bb3707e849546b94541f9e5f MD5 | raw file
  1. # Written by TheGurke 2011
  2. """Vector graphics engine for the card table as a gtk.Widget"""
  3. import math
  4. import random
  5. import logging
  6. from gettext import gettext as _
  7. import cairo
  8. import glib
  9. import gtk
  10. from progenitus import config
  11. from progenitus.db import cards
  12. from progenitus.db import pics
  13. #
  14. # There is an important distinction between on-screen coordinates (int) and
  15. # virtual desktop coordinates in inch (float).
  16. # The desktop can be zoomed freely about the point (0.0, 0.0), but not moved.
  17. #
  18. class DragNDrop(object):
  19. """An object to save drag and drop information"""
  20. def __init__(self, item, start_x, start_y):
  21. assert(isinstance(item, Item))
  22. self.item = item
  23. self.start_x = start_x
  24. self.start_y = start_y
  25. self.item_x = item.x # the item's x and y coordinates at the time the
  26. self.item_y = item.y # dragging started
  27. self.set_hand_index(start_x, start_y)
  28. desktop = self.item.widget
  29. if desktop.is_over_hand(start_x, start_y):
  30. self.initial_hand_index = desktop.get_hand_card_index(start_x,
  31. start_y)
  32. # There is a difference to set_hand_index!
  33. else:
  34. self.initial_hand_index = None
  35. def set_hand_index(self, x ,y):
  36. """Set the current hand index of hovering over the hand area"""
  37. desktop = self.item.widget
  38. if desktop.is_over_hand(x, y):
  39. self.hand_index = desktop.get_hand_index(x, y)
  40. else:
  41. self.hand_index = None
  42. def update_pos(self, x, y):
  43. """Update the item's position"""
  44. dx = x - self.start_x
  45. dy = y - self.start_y
  46. self.item.x = dx / float(self.item.widget.zoom) + self.item_x
  47. self.item.y = dy / float(self.item.widget.zoom) + self.item_y
  48. self.set_hand_index(x, y)
  49. class CairoDesktop(gtk.DrawingArea):
  50. """A Widget for drawing on using cairo"""
  51. __gsignals__ = {"expose-event": "override"}
  52. enlarged_card = None
  53. enlarged_card_last_pos = None # Last x, y position of the enlarged card
  54. bg_color = 1, 1, 1
  55. zoom = 12. # current zoom factor; the larger means zooming in; type is float
  56. # position is always centered around (0,0)
  57. y_offset = 0 # number of on-screen pixels that are covered on the bottom
  58. flip_y = False
  59. _items = [] # Cards and other stuff
  60. _dragndrop = None
  61. # Callbacks to be filled
  62. movement_callback = None
  63. hover_callback = None
  64. prop_callback = None
  65. # Initialize
  66. def __init__(self, interface, eventbox=None):
  67. super(self.__class__, self).__init__()
  68. self.picfactory = pics.PicFactory()
  69. if eventbox is not None:
  70. self.setup_eventbox(eventbox)
  71. self.show() # Visible by default
  72. self.interface = interface
  73. def reset(self):
  74. """Reset all properties to their default values; clear the board"""
  75. self.picfactory = pics.PicFactory()
  76. self._items = []
  77. self._dragndrop = None
  78. # self.zoom = 12.
  79. self.enlarged_card = None
  80. self.y_offset = 0
  81. def setup_eventbox(self, eventbox):
  82. """Configure and eventbox so it sends events to this widget"""
  83. assert(isinstance(eventbox, gtk.Widget))
  84. eventbox.connect("button-press-event", self.mouse_down)
  85. eventbox.connect("button-release-event", self.mouse_up)
  86. eventbox.connect("motion-notify-event", self.mouse_motion)
  87. eventbox.connect("scroll-event", self.mouse_scroll)
  88. eventbox.set_events(eventbox.get_events() | gtk.gdk.POINTER_MOTION_MASK)
  89. def do_expose_event(self, event):
  90. # Handle the expose-event by painting
  91. cr = self.window.cairo_create()
  92. a = event.area
  93. cr.rectangle(a.x, a.y, a.width, a.height)
  94. cr.clip()
  95. self.paint(cr)
  96. # Coordinates
  97. def get_screen_coords(self):
  98. """Get the screen coordinates of this widget"""
  99. w, h = self.window.get_size()
  100. return 0, 0, w, h - self.get_hand_height()
  101. def get_wh(self):
  102. """Return the size of the currently visible playing area in desktop """
  103. """coordinates"""
  104. assert(isinstance(self.zoom, float))
  105. w, h = self.window.get_size()
  106. return w / self.zoom, (h - self.get_hand_height()) / float(self.zoom)
  107. def get_hand_height(self):
  108. """Get the height of the bottom area reserved for the cards in hand """
  109. """in on-screen coordinates"""
  110. return int(math.ceil(1.2 * 3.5 * self.zoom)) - self.y_offset
  111. # Item container
  112. def add_item(self, item, position_hint=None):
  113. assert(isinstance(item, Item))
  114. if self._items.count(item) < 1:
  115. if position_hint is None:
  116. self._items.append(item)
  117. else:
  118. self._items.insert(position_hint, item)
  119. item.parent = self
  120. item.widget = self
  121. if item.visible:
  122. item.repaint()
  123. if isinstance(item, Container):
  124. for item_ in item:
  125. item_.widget = self
  126. def remove_item(self, item):
  127. if self._items.count(item) > 0:
  128. self._items.remove(item)
  129. item.repaint()
  130. item.parent = None
  131. item.widget = None
  132. def get_item_at(self, x, y):
  133. """Find an item by its screen coordinates"""
  134. items_reversed = self._items[:]
  135. items_reversed.reverse()
  136. for item in items_reversed:
  137. if item.match_pixel(x, y):
  138. while hasattr(item, "get_item_at"):
  139. item_ = item.get_item_at(x, y)
  140. if item_ is None:
  141. return item
  142. else:
  143. item = item_
  144. return item
  145. return None
  146. # Hand
  147. def get_hand(self):
  148. """Get the hand card list"""
  149. if self.interface.my_player is None:
  150. return []
  151. hand = self.interface.my_player.hand[:]
  152. # If dragging apply dragging information
  153. if (self._dragndrop is not None
  154. and isinstance(self._dragndrop.item, CardItem)
  155. and not self._dragndrop.item.istoken):
  156. if self._dragndrop.initial_hand_index is not None:
  157. i = self._dragndrop.initial_hand_index
  158. hand = hand[:i] + hand[i+1:]
  159. if self._dragndrop.hand_index is not None:
  160. hand.insert(self._dragndrop.hand_index,
  161. self._dragndrop.item.card)
  162. return hand
  163. def is_over_hand(self, x, y):
  164. """Determine whether an pointer position is over the hand area"""
  165. return y > self.get_screen_coords()[3]
  166. def get_hand_index(self, x, y):
  167. """Get the list index where the position suggests insertion"""
  168. # Note the difference to get_hand_card_index
  169. x, y = int(x), int(y)
  170. hand = self.get_hand()
  171. if hand is None:
  172. return
  173. w, h = self.window.get_size()
  174. card_width = int(math.ceil(2.5 * self.zoom))
  175. spacing = int(math.ceil(2.5 * self.zoom * 0.1))
  176. x = x - w / 2 + (card_width + spacing) * (len(hand) + 1) / 2
  177. # relative coordinates on the hand
  178. assert(isinstance(x, int))
  179. i = x / (card_width + spacing)
  180. if i < 0:
  181. i = 0
  182. if i > len(hand):
  183. i = len(hand)
  184. return i
  185. def get_hand_card_index(self, x, y):
  186. # Note the difference to get_hand_index
  187. """Get a card in the hand by its on-screen coordinates"""
  188. x, y = int(x), int(y)
  189. hand = self.get_hand()
  190. if hand is None:
  191. return
  192. w, h = self.window.get_size()
  193. if y < h - self.get_hand_height() or y > h:
  194. return None
  195. card_width = int(math.ceil(2.5 * self.zoom))
  196. spacing = int(math.ceil(2.5 * self.zoom * 0.1))
  197. x = x - w / 2 + ((card_width + spacing) * len(hand) - spacing) / 2
  198. assert(isinstance(x, int))
  199. if x % (card_width + spacing) > card_width:
  200. return None
  201. i = x / (card_width + spacing)
  202. if 0 <= i < len(hand):
  203. return i
  204. return None
  205. def get_hand_card(self, x, y):
  206. """If there is a hand card at (x, y) then return the card, else None"""
  207. hand = self.get_hand()
  208. i = self.get_hand_card_index(x, y)
  209. if i is None:
  210. return None
  211. return hand[i]
  212. def repaint_hand(self):
  213. w, h = self.window.get_size()
  214. hh = self.get_hand_height()
  215. self.queue_draw_area(0, h - hh, w, hh)
  216. def _paint_hand(self, cr, width, height):
  217. hand = self.get_hand()
  218. if hand is None:
  219. return
  220. card_width = int(math.ceil(2.5 * self.zoom))
  221. card_height = int(math.ceil(3.5 * self.zoom))
  222. spacing = int(math.ceil(2.5 * self.zoom * 0.1))
  223. x = width / 2 - ((card_width + spacing) * len(hand) - spacing) / 2
  224. y = height - card_height - spacing + self.y_offset
  225. # Divider line
  226. cr.save()
  227. cr.set_line_width(1)
  228. cr.set_source_rgb(0.5, 0.5, 0.5)
  229. cr.move_to(int(0.1 * width), y - spacing / 2)
  230. cr.line_to(int(0.9 * width), y - spacing / 2)
  231. cr.stroke()
  232. cr.restore()
  233. for card in hand:
  234. cr.save()
  235. cr.rectangle(x, y, card_width, card_height)
  236. cr.translate(x, y)
  237. cr.clip()
  238. surface = self.picfactory.get(card.id, card_width)
  239. assert isinstance(surface, cairo.Surface)
  240. cr.set_source_surface(surface)
  241. cr.paint()
  242. cr.restore()
  243. x += card_width + spacing
  244. # Enlarged card
  245. def _get_enlarged_card_pos(self):
  246. """Get the x, y coordinates of the enlarged card"""
  247. px, py, mask = self.get_parent_window().get_pointer()
  248. _x, _y, w, h, bd = self.get_parent_window().get_geometry()
  249. # Display card on the left or on the right?
  250. x = w - self.enlarged_card[0].get_width() if px < w / 2 else 0
  251. y = int((h - self.enlarged_card[0].get_height()) / 2)
  252. return x, y
  253. def repaint_enlarged_card(self):
  254. """Repaint the area where the enlarged card is"""
  255. if self.enlarged_card_last_pos is not None:
  256. x, y = self.enlarged_card_last_pos
  257. w = self.enlarged_card[0].get_width()
  258. h = self.enlarged_card[0].get_height()
  259. self.queue_draw_area(x, y, w, h)
  260. def show_enlarged_card(self, cardid=None, flipped=False):
  261. """Show the large version of a card"""
  262. if cardid is None:
  263. if self.enlarged_card is not None:
  264. self.repaint_enlarged_card()
  265. self.enlarged_card = None
  266. self.enlarged_card_last_pos = None
  267. else:
  268. self.repaint_enlarged_card()
  269. cardpic = pics.surface_from_pixbuf(pics.get(cardid))[0]
  270. self.enlarged_card = cardpic, flipped
  271. self.enlarged_card_last_pos = self._get_enlarged_card_pos()
  272. self.repaint_enlarged_card()
  273. def _paint_enlarged_card(self, cr, width, height):
  274. if self.enlarged_card is not None:
  275. cr.translate(*self._get_enlarged_card_pos())
  276. w = self.enlarged_card[0].get_width()
  277. h = self.enlarged_card[0].get_height()
  278. if self.enlarged_card[1]:
  279. cr.translate(w / 2, h / 2)
  280. cr.rotate(math.pi)
  281. cr.translate(-w / 2, -h / 2)
  282. cr.set_source_surface(self.enlarged_card[0])
  283. cr.paint()
  284. # Painting
  285. def repaint(self):
  286. self.queue_draw()
  287. def paint(self, cr):
  288. """Use Cairo to paint the widget"""
  289. assert isinstance(cr, cairo.Context)
  290. width, height = self.window.get_size()
  291. # Background fill
  292. cr.set_source_rgb(*self.bg_color)
  293. cr.rectangle(0, 0, width, height)
  294. cr.fill()
  295. # Set viewport
  296. height_ = height - self.get_hand_height()
  297. cr.save()
  298. cr.rectangle(0, 0, width, height_)
  299. cr.clip()
  300. # Paint items
  301. for item in self._items:
  302. if item.visible:
  303. cr.save()
  304. cr.rectangle(*item.get_screen_coords())
  305. cr.clip()
  306. cr.translate(*item.get_screen_coords()[:2])
  307. item.paint(self, cr)
  308. cr.restore()
  309. cr.restore()
  310. # Paint hand and enlarged cards
  311. self._paint_hand(cr, width, height)
  312. self._paint_enlarged_card(cr, width, height_)
  313. # Mouse input
  314. def mouse_down(self, widget, event):
  315. item = self.get_item_at(event.x, event.y)
  316. handcard = self.get_hand_card(event.x, event.y)
  317. if item is not None and item.mine and item.visible:
  318. if event.button == 1 and event.type == gtk.gdk._2BUTTON_PRESS:
  319. item.double_click(event)
  320. elif event.button == 1 and item.dragable:
  321. # Start dragging
  322. self._dragndrop = DragNDrop(item, event.x, event.y)
  323. self._dragndrop.started = self.interface.my_player.battlefield
  324. # Raise the item to be on top of all others
  325. self.remove_item(item)
  326. self.add_item(item)
  327. self.show_enlarged_card(None)
  328. elif handcard is not None:
  329. if event.button == 1:
  330. # Create new card item
  331. width, height = self.window.get_size()
  332. item = CardItem(handcard, None, True)
  333. item.x = (event.x - width / 2) / self.zoom - 2.5 / 2
  334. item.y = (event.y - height / 2) / self.zoom
  335. item.visible = False
  336. self.add_item(item)
  337. item.clamp_coords()
  338. # Start dragging
  339. self._dragndrop = DragNDrop(item, event.x, event.y)
  340. self._dragndrop.started = self.interface.my_player.hand
  341. assert(self._dragndrop.initial_hand_index is not None)
  342. self.show_enlarged_card(None)
  343. elif (item is None and event.button == 1
  344. and event.type == gtk.gdk._2BUTTON_PRESS):
  345. # Double click on the desktop
  346. self.interface.untap_all()
  347. if event.button == 3 and self.prop_callback is not None:
  348. self.show_enlarged_card(None)
  349. self.prop_callback(item if item is not None else handcard, event)
  350. def mouse_up(self, widget, event):
  351. if event.button == 1 and self._dragndrop is not None:
  352. item = self._dragndrop.item
  353. # If the dragged distance was 0, do nothing
  354. if (event.x == self._dragndrop.start_x
  355. and event.y == self._dragndrop.start_y):
  356. self._dragndrop = None
  357. return
  358. # Stop dragging
  359. if isinstance(item, CardItem) or isinstance(item, Tray):
  360. player = self.interface.my_player
  361. started = self._dragndrop.started
  362. finished = player.battlefield
  363. if (isinstance(item, CardItem) and not item.istoken
  364. and self.is_over_hand(event.x, event.y)):
  365. finished = player.hand
  366. if started is not player.battlefield:
  367. self.remove_item(item)
  368. item_ = item.card
  369. else:
  370. item_ = item
  371. if started is player.hand and finished is player.hand:
  372. # Moved within the hand
  373. ii = self._dragndrop.initial_hand_index
  374. i = self._dragndrop.hand_index
  375. player.hand[ii:ii+1] = []
  376. player.hand.insert(i, item.card)
  377. elif finished is player.battlefield:
  378. player.move_card(item_, started, finished, item.x, item.y)
  379. else:
  380. player.move_card(item_, self._dragndrop.started, finished)
  381. self._dragndrop = None
  382. def mouse_motion(self, widget, event):
  383. hand = self.get_hand()
  384. if self._dragndrop is not None: # Dragging something
  385. item = self._dragndrop.item
  386. item.repaint()
  387. # Update movement coordinates
  388. i = self._dragndrop.hand_index
  389. self._dragndrop.update_pos(event.x, event.y)
  390. if isinstance(item, CardItem):
  391. # Card items can be moved to the hand
  392. item.visible = (not self.is_over_hand(event.x, event.y)
  393. or item.istoken)
  394. if not item.istoken and i is not self._dragndrop.hand_index:
  395. self.repaint_hand()
  396. item.clamp_coords()
  397. item.repaint()
  398. # Update drag status
  399. self._dragndrop.last_x = event.x
  400. self._dragndrop.last_y = event.y
  401. else: # Not dragging
  402. item = self.get_item_at(event.x, event.y)
  403. handcard = self.get_hand_card(event.x, event.y)
  404. if handcard is not None:
  405. self.show_enlarged_card(handcard.id)
  406. if self.hover_callback is not None:
  407. self.hover_callback(handcard)
  408. elif (item is not None and item.visible
  409. and isinstance(item, CardItem)):
  410. if item.faceup:
  411. self.show_enlarged_card(item.cardid, item.flipped)
  412. # check for special "transform" cards that have two sides
  413. elif (not cards.is_token(item.cardid)
  414. and item.cardid[-1] == "a"):
  415. self.show_enlarged_card(item.cardid[:-1] + "b",
  416. item.flipped)
  417. elif item.mine:
  418. self.show_enlarged_card(item.cardid, item.flipped)
  419. else:
  420. self.show_enlarged_card(None)
  421. else:
  422. self.show_enlarged_card(None)
  423. if item is not None and self.hover_callback is not None:
  424. self.hover_callback(item)
  425. def mouse_scroll(self, widget, event):
  426. pass
  427. #
  428. # Item
  429. #
  430. class Item(object):
  431. """Abstract class for everything that will be painted by CairoDesktop"""
  432. x, y, w, h = 0, 0, 0, 0 # dimensions
  433. itemid = None
  434. dragable = False
  435. mine = False
  436. parent = None # the immediate parent item
  437. widget = None # The CairoDesktop widget this item is associated with
  438. network_sync = True
  439. visible = True
  440. def __eq__(self, other):
  441. if self.itemid is not None:
  442. return self.itemid == other.itemid
  443. else:
  444. return self is other
  445. def get_wh(self):
  446. return self.w, self.h
  447. def paint(self, desktop, cr):
  448. """Paint on the cairo context"""
  449. pass
  450. def show_tooltip(self):
  451. pass
  452. def double_click(self, event):
  453. pass
  454. def match_pixel(self, x, y):
  455. """Is this pixel part of this item?"""
  456. coords = self.get_screen_coords()
  457. if x < coords[0] or x >= coords[0] + coords[2]:
  458. return False
  459. if y < coords[1] or y >= coords[1] + coords[3]:
  460. return False
  461. return True
  462. def get_screen_coords(self):
  463. """Get the on-screen-coordinates of this item"""
  464. x, y, w, h = self.parent.get_screen_coords()
  465. ix = int(math.floor(self.x * self.widget.zoom + w / 2))
  466. iy = int(math.floor(self.y * self.widget.zoom + h / 2))
  467. iw = int(math.ceil(self.w * self.widget.zoom))
  468. ih = int(math.ceil(self.h * self.widget.zoom))
  469. return ix, iy, iw, ih
  470. def clamp_coords(self):
  471. """Make sure that this card never leaves the playing area"""
  472. w, h = self.parent.get_wh()
  473. self.x = self.x if self.x >= -(w + self.w) / 2 else -(w + self.w) / 2
  474. self.y = self.y if self.y >= -(h + self.h) / 2 else -(h + self.h) / 2
  475. self.x = self.x if self.x <= (w - self.w) / 2 else (w - self.w) / 2
  476. self.y = self.y if self.y <= (h - self.h) / 2 else (h - self.h) / 2
  477. def repaint(self):
  478. """Queue a repaint of this item"""
  479. if self.widget is None:
  480. logging.debug("orphan item:")
  481. logging.debug(self)
  482. else:
  483. self.widget.queue_draw_area(*self.get_screen_coords())
  484. class Container(Item):
  485. """A Container holds a multitude of items and passes paint and other events
  486. on to them"""
  487. background = 1, 1, 1
  488. def __init__(self):
  489. self._items = []
  490. def add(self, item):
  491. """Add an item to this container"""
  492. self._items.append(item)
  493. item.parent = self
  494. item.widget = self.widget
  495. def remove(self, item):
  496. """Remove an item from this container"""
  497. self._items.remove(item)
  498. item.parent = None
  499. item.widget = None
  500. def paint(self, desktop, cr):
  501. w = int(math.ceil(self.w * desktop.zoom))
  502. h = int(math.ceil(self.h * desktop.zoom))
  503. # Background fill
  504. cr.set_source_rgb(*self.bg_color)
  505. cr.rectangle(0, 0, w, h)
  506. cr.fill()
  507. # Paint items
  508. for item in self._items:
  509. if item.visible:
  510. cr.save()
  511. cr.rectangle(*item.get_screen_coords())
  512. cr.clip()
  513. cr.translate(*item.get_screen_coords()[:2])
  514. item.paint(desktop, cr)
  515. cr.restore()
  516. def get_item_at(self, x, y):
  517. """Get an item at a point on the screen"""
  518. dx, dy, dw, dh = self.get_screen_coords()
  519. x -= dx
  520. y -= dy
  521. for item in self._items:
  522. if item.match_pixel(x, y):
  523. return item
  524. return None
  525. def show_tooltip(self, x, y):
  526. dx, dy, dw, dh = self.get_screen_coords()
  527. x -= dx
  528. y -= dy
  529. item = self.get_item_at(self, x, y)
  530. if item is None:
  531. self.show_own_tooltip(x, y)
  532. else:
  533. item.show_tooltip(x, y)
  534. def show_own_tooltip(self, x, y):
  535. pass
  536. def double_click(self, event):
  537. dx, dy, dw, dh = self.get_screen_coords()
  538. x = event.x - dx
  539. y = event.y - dy
  540. item = self.get_item_at(x, y)
  541. if item is not None:
  542. item.double_click(event)
  543. # Emulate container type
  544. def __len__(self):
  545. return len(self._items)
  546. def __contains__(self, item):
  547. return item in self._items
  548. def __iter__(self):
  549. return self._items.__iter__()
  550. class TextItem(Item):
  551. color = 0, 0, 0
  552. font_face = "sans-serif"
  553. fontsize = 0
  554. update = None
  555. def __init__(self, text=""):
  556. self.text = text
  557. def get_wh(self):
  558. """Get the expected width of the text in desktop coordinates"""
  559. assert(isinstance(self.widget.zoom, float))
  560. surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 0, 0)
  561. cr = cairo.Context(surface)
  562. cr.select_font_face(self.font_face)
  563. cr.set_font_size(self.fontsize * self.widget.zoom)
  564. xb, yb, w, h, xa, ya = cr.text_extents(self.text)
  565. return (w + 2) / self.widget.zoom, (h + 2) / self.widget.zoom # FIXME
  566. def set_text(self, text):
  567. """Update the text and item width"""
  568. self.text = text
  569. self.w, self.h = self.get_wh()
  570. def paint(self, desktop, cr):
  571. assert(isinstance(self.widget.zoom, float))
  572. if self.update is not None:
  573. self.update()
  574. cr.set_source_rgb(*self.color)
  575. cr.select_font_face(self.font_face)
  576. cr.set_font_size(self.fontsize * self.widget.zoom)
  577. cr.move_to(0, self.fontsize * self.widget.zoom)
  578. cr.show_text(self.text)
  579. #
  580. # CardItem
  581. #
  582. class CardItem(Item):
  583. """A magic card or token"""
  584. w = 2.5 # Card width in inches
  585. h = 3.5 # Card height in inches
  586. card = None
  587. token = None
  588. cardid = None
  589. istoken = False # Is this card only a token / copy?
  590. # Tokens cannot go to the library/graveyard/hand etc.
  591. tapped = False
  592. flipped = False
  593. faceup = True # True is face up
  594. does_not_untap = False
  595. creates_tokens = None
  596. default_counters = None
  597. border_color = None
  598. dragable = True
  599. def __init__(self, cardortoken, owner, mine=False):
  600. if isinstance(cardortoken, cards.Card):
  601. self.card = cardortoken
  602. elif isinstance(cardortoken, cards.Token):
  603. self.token = cardortoken
  604. self.istoken = True
  605. else:
  606. assert(False)
  607. self.cardid = cardortoken.id
  608. assert(self.cardid is not None)
  609. self.owner = owner
  610. self.controller = owner
  611. self.mine = mine
  612. self.counters = dict()
  613. self.default_counters = []
  614. def paint(self, desktop, cr):
  615. # check for special "transform" cards that have two sides
  616. if self.faceup:
  617. cardid = self.cardid
  618. else:
  619. if not cards.is_token(self.cardid) and self.cardid[-1] == "a":
  620. cardid = self.cardid[:-1] + "b"
  621. else:
  622. cardid = "deckmaster"
  623. width = int(math.ceil((self.h if self.tapped else self.w)
  624. * desktop.zoom))
  625. surface = desktop.picfactory.get(cardid, width)
  626. assert(isinstance(surface, cairo.Surface))
  627. # rotate image
  628. phi = math.pi / 2 if self.tapped else 0
  629. phi += math.pi if self.flipped else 0
  630. if self.tapped:
  631. cr.translate(surface.get_height() / 2, surface.get_width() / 2)
  632. else:
  633. cr.translate(surface.get_width() / 2, surface.get_height() / 2)
  634. cr.rotate(phi)
  635. cr.translate(-surface.get_width() / 2, -surface.get_height() / 2)
  636. cr.set_source_surface(surface)
  637. cr.paint()
  638. def get_description(self):
  639. """Get a one line description of this card item"""
  640. text = ""
  641. if self.card is not None:
  642. text = (self.card.name if self.mine or self.faceup else
  643. _("face down card"))
  644. if self.istoken:
  645. text += " (clone)"
  646. else:
  647. text = _("%s token") % str(self.token)
  648. if not self.mine:
  649. if self.controller is not None:
  650. text = _("{0}'s {1}").format(self.controller.nick, text)
  651. # Add counter information
  652. for counter, num in self.counters.items():
  653. if num == 1:
  654. text += ", one %s counter" % counter
  655. else:
  656. text += ", %d %s counters" % (num, counter)
  657. return text
  658. def double_click(self, event):
  659. self.toggle_tapped()
  660. def toggle_tapped(self):
  661. self.set_tapped(not self.tapped)
  662. def set_tapped(self, tapped):
  663. """Set the tapped status of this card"""
  664. assert(isinstance(tapped, bool))
  665. if self.tapped != tapped:
  666. self.repaint()
  667. self.w, self.h = self.h, self.w
  668. self.repaint()
  669. self.tapped = tapped
  670. if self.controller is not None:
  671. self.controller.send_network_cmd("tap", self.itemid)
  672. def toggle_flipped(self):
  673. self.set_flipped(not self.flipped)
  674. def set_flipped(self, flipped):
  675. """Set the flipped status of this card"""
  676. assert(isinstance(flipped, bool))
  677. if self.flipped != flipped:
  678. self.repaint()
  679. self.flipped = flipped
  680. if self.controller is not None:
  681. self.controller.send_network_cmd("flip", self.itemid)
  682. def turn_over(self):
  683. self.set_faceup(not self.faceup)
  684. def set_faceup(self, faceup=True):
  685. """Set the direction the card is facing: up (True) or down (False)"""
  686. assert(isinstance(faceup, bool))
  687. if self.faceup != faceup:
  688. self.repaint()
  689. self.faceup = faceup
  690. if self.controller is not None:
  691. self.controller.send_network_cmd("face", self.itemid)
  692. #
  693. # Tray
  694. #
  695. class Tray(Container):
  696. """The tray is composed of the library, the graveyard, the player name,
  697. a life counter and a hand card counter (for other players)"""
  698. w = 2.5 * 3.4
  699. h = 3.5 * 1.4
  700. bg_color = 0.9, 0.9, 0.9
  701. dragable = True
  702. def __init__(self, player, mine=False):
  703. super(self.__class__, self).__init__()
  704. self.player = player
  705. self.mine = mine
  706. # Populate the container
  707. self.library_item = Library()
  708. self.library_item.x = 2.5 * 0.05
  709. self.library_item.y = 3.5 * (-0.35)
  710. self.add(self.library_item)
  711. self.graveyard_item = Graveyard()
  712. self.graveyard_item.x = 2.5 * (-1.05)
  713. self.graveyard_item.y = 3.5 * (-0.35)
  714. self.add(self.graveyard_item)
  715. self.name_item = TextItem()
  716. self.name_item.x = -self.w / 2
  717. self.name_item.y = -self.h / 2
  718. self.name_item.fontsize = 3.5 * 0.2
  719. self.name_item.update = \
  720. lambda: self.name_item.set_text(self.player.jid.resource)
  721. self.add(self.name_item)
  722. self.card_count_item = TextItem()
  723. self.card_count_item.color = 0.5, 0.5, 0
  724. self.card_count_item.x = 2.5 * 1.3
  725. self.card_count_item.y = 3.5 * (-0.2)
  726. self.card_count_item.fontsize = 3.5 * 0.14
  727. self.card_count_item.update = \
  728. lambda: self.card_count_item.set_text(str(len(self.player.hand)))
  729. self.add(self.card_count_item)
  730. self.life_item = TextItem()
  731. self.life_item.color = 0.5, 0, 0
  732. self.life_item.x = 2.5 * 1.3
  733. self.life_item.y = 3.5 * 0.2
  734. self.life_item.fontsize = 3.5 * 0.14
  735. self.life_item.update = \
  736. lambda: self.life_item.set_text(str(self.player.life))
  737. self.add(self.life_item)
  738. for item in self:
  739. item.mine = self.mine
  740. class Library(Item):
  741. w = 2.5
  742. h = 3.5
  743. def double_click(self, event):
  744. self.parent.player.draw_card()
  745. def paint(self, desktop, cr):
  746. if len(self.parent.player.library) > 0:
  747. width = int(math.ceil(self.w * desktop.zoom))
  748. surface = desktop.picfactory.get("deckmaster", width)
  749. assert(isinstance(surface, cairo.Surface))
  750. cr.set_source_surface(surface)
  751. cr.paint()
  752. class Graveyard(Item):
  753. w = 2.5
  754. h = 3.5
  755. def paint(self, desktop, cr):
  756. if len(self.parent.player.graveyard) > 0:
  757. card = self.parent.player.graveyard[-1]
  758. width = int(math.ceil(self.w * desktop.zoom))
  759. surface = desktop.picfactory.get(card.id, width)
  760. assert(isinstance(surface, cairo.Surface))
  761. cr.set_source_surface(surface)
  762. cr.paint()