File indexing completed on 2024-04-21 04:01:49

0001 # -*- coding: utf-8 -*-
0002 
0003 """
0004 Copyright (C) 2008-2016 Wolfgang Rohdewald <wolfgang@rohdewald.de>
0005 
0006 SPDX-License-Identifier: GPL-2.0
0007 
0008 """
0009 
0010 import datetime
0011 
0012 from qt import Qt, QModelIndex
0013 from qt import QAbstractTableModel, QDialogButtonBox, QDialog
0014 from qt import QHBoxLayout, QVBoxLayout, QCheckBox
0015 from qt import QItemSelectionModel, QAbstractItemView
0016 
0017 from dialogs import WarningYesNo
0018 from kde import KIcon
0019 from mi18n import i18n, i18nc
0020 from log import logException
0021 from query import Query
0022 from guiutil import MJTableView, decorateWindow
0023 from statesaver import StateSaver
0024 from common import Debug
0025 from modeltest import ModelTest
0026 
0027 
0028 class GamesModel(QAbstractTableModel):
0029 
0030     """data for the list of games"""
0031 
0032     def __init__(self):
0033         QAbstractTableModel.__init__(self)
0034         self._resultRows = []
0035 
0036     def columnCount(self, unusedParent=None):   # pylint: disable=no-self-use
0037         """including the hidden col 0"""
0038         return 3
0039 
0040     def rowCount(self, parent=None):
0041         """how many games"""
0042         if parent and parent.isValid():
0043             # we have only top level items
0044             return 0
0045         return len(self._resultRows)
0046 
0047     def setResultset(self, rows):
0048         """new data"""
0049         self.beginResetModel()
0050         try:
0051             self._resultRows = rows
0052         finally:
0053             self.endResetModel()
0054 
0055     def index(self, row, column, parent=None):
0056         """helper"""
0057         if (row < 0
0058                 or column < 0
0059                 or row >= self.rowCount(parent)
0060                 or column >= self.columnCount(parent)):
0061         # similar in tree.py
0062             return QModelIndex()
0063         return self.createIndex(row, column, 0)
0064 
0065     def data(self, index, role=None):
0066         """get score table from view"""
0067         if role is None:
0068             role = Qt.DisplayRole
0069         if not (index.isValid() and role == Qt.DisplayRole):
0070             return None
0071         if role == Qt.DisplayRole:
0072             unformatted = str(
0073                 self._resultRows[index.row()][index.column()]) # TODO: brauche ich str?
0074             if index.column() == 2:
0075                 # we do not yet use this for listing remote games but if we do
0076                 # this translation is needed for robot players
0077                 names = [i18n(name) for name in unformatted.split('///')]
0078                 return ', '.join(names)
0079             if index.column() == 1:
0080                 dateVal = datetime.datetime.strptime(
0081                     unformatted, '%Y-%m-%dT%H:%M:%S')
0082                 return dateVal.strftime('%c')
0083             if index.column() == 0:
0084                 return int(unformatted)
0085         return QAbstractTableModel.data(self, index, role)
0086 
0087     def headerData(self, section, orientation, role):
0088         """for the two visible columns"""
0089         # pylint: disable=no-self-use
0090         if orientation == Qt.Horizontal and role == Qt.DisplayRole:
0091             return i18n('Players') if section == 2 else i18n('Started')
0092         return None
0093 
0094 
0095 class Games(QDialog):
0096 
0097     """a dialog for selecting a game"""
0098 
0099     def __init__(self, parent=None):
0100         super().__init__(parent)
0101         self.selectedGame = None
0102         self.onlyPending = True
0103         decorateWindow(self, i18nc("@title:window", "Games"))
0104         self.setObjectName('Games')
0105         self.resize(700, 400)
0106         self.model = GamesModel()
0107         if Debug.modelTest:
0108             self.modelTest = ModelTest(self.model, self)
0109 
0110         self.view = MJTableView(self)
0111         self.view.setModel(self.model)
0112         self.selection = QItemSelectionModel(self.model, self.view)
0113         self.view.setSelectionModel(self.selection)
0114         self.view.setSelectionBehavior(QAbstractItemView.SelectRows)
0115         self.view.setSelectionMode(QAbstractItemView.SingleSelection)
0116 
0117         self.buttonBox = QDialogButtonBox(self)
0118         self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel)
0119         self.newButton = self.buttonBox.addButton(
0120             i18nc('start a new game', "&New"), QDialogButtonBox.ActionRole)
0121         self.newButton.setIcon(KIcon("document-new"))
0122         self.newButton.clicked.connect(self.accept)
0123         self.loadButton = self.buttonBox.addButton(
0124             i18n("&Load"), QDialogButtonBox.AcceptRole)
0125         self.loadButton.clicked.connect(self.loadGame)
0126         self.loadButton.setIcon(KIcon("document-open"))
0127         self.deleteButton = self.buttonBox.addButton(
0128             i18n("&Delete"), QDialogButtonBox.ActionRole)
0129         self.deleteButton.setIcon(KIcon("edit-delete"))
0130         self.deleteButton.clicked.connect(self.delete)
0131 
0132         chkPending = QCheckBox(i18n("Show only pending games"), self)
0133         chkPending.setChecked(True)
0134         cmdLayout = QHBoxLayout()
0135         cmdLayout.addWidget(chkPending)
0136         cmdLayout.addWidget(self.buttonBox)
0137 
0138         layout = QVBoxLayout()
0139         layout.addWidget(self.view)
0140         layout.addLayout(cmdLayout)
0141         self.setLayout(layout)
0142         StateSaver(self)
0143 
0144         self.selection.selectionChanged.connect(self.selectionChanged)
0145         self.buttonBox.accepted.connect(self.accept)
0146         self.buttonBox.rejected.connect(self.reject)
0147         self.view.doubleClicked.connect(self.loadGame)
0148         chkPending.stateChanged.connect(self.pendingOrNot)
0149 
0150     def showEvent(self, unusedEvent):
0151         """only now get the data set. Not doing this in__init__ would eventually
0152         make it easier to subclass from some generic TableEditor class"""
0153         self.setQuery()
0154         self.view.initView()
0155         self.selectionChanged()
0156 
0157     def keyPressEvent(self, event):
0158         """use insert/delete keys for insert/delete"""
0159         key = event.key()
0160         if key == Qt.Key_Insert:
0161             self.newEntry()
0162             return
0163         if key == Qt.Key_Delete:
0164             self.delete()
0165             event.ignore()
0166             return
0167         QDialog.keyPressEvent(self, event)
0168 
0169     def selectionChanged(self):
0170         """update button states according to selection"""
0171         selectedRows = len(self.selection.selectedRows())
0172         self.loadButton.setEnabled(selectedRows == 1)
0173         self.deleteButton.setEnabled(selectedRows >= 1)
0174 
0175     def setQuery(self):
0176         """define the query depending on self.OnlyPending"""
0177         query = Query(
0178             "select g.id, g.starttime, "
0179             "p0.name||'///'||p1.name||'///'||p2.name||'///'||p3.name "
0180             "from game g, player p0,"
0181             "player p1, player p2, player p3 "
0182             "where seed is null"
0183             " and p0.id=g.p0 and p1.id=g.p1 "
0184             " and p2.id=g.p2 and p3.id=g.p3 "
0185             "%s"
0186             "and exists(select 1 from score where game=g.id)" %
0187             ("and g.endtime is null " if self.onlyPending else ""))
0188         self.model.setResultset(query.records)
0189         self.view.hideColumn(0)
0190 
0191     def __idxForGame(self, game):
0192         """return the model index for game"""
0193         for row in range(self.model.rowCount()):
0194             idx = self.model.index(row, 0)
0195             if self.model.data(idx, 0) == game:
0196                 return idx
0197         return self.model.index(0, 0)
0198 
0199     def __getSelectedGame(self):
0200         """return the game id of the selected game"""
0201         rows = self.selection.selectedRows()
0202         return self.model.data(rows[0], 0) if rows else 0
0203 
0204     def pendingOrNot(self, chosen):
0205         """do we want to see all games or only pending games?"""
0206         if self.onlyPending != chosen:
0207             self.onlyPending = chosen
0208             prevSelected = self.__getSelectedGame()
0209             self.setQuery()
0210             idx = self.__idxForGame(prevSelected)
0211             self.view.selectRow(idx.row())
0212         self.view.setFocus()
0213 
0214     def loadGame(self):
0215         """load a game"""
0216         self.selectedGame = self.__getSelectedGame()
0217         self.buttonBox.accepted.emit()
0218 
0219     def delete(self):
0220         """delete a game"""
0221         def answered(result, games):
0222             """question answered, result is True or False"""
0223             if result:
0224                 for game in games:
0225                     Query("DELETE FROM score WHERE game = ?", (game, ))
0226                     Query("DELETE FROM game WHERE id = ?", (game, ))
0227                 self.setQuery()  # just reload entire table
0228         allGames = self.view.selectionModel().selectedRows(0)
0229         deleteGames = [x.data() for x in allGames]
0230         if not deleteGames:
0231             # should never happen
0232             logException('delete: 0 rows selected')
0233         WarningYesNo(
0234             i18n(
0235                 "Do you really want to delete <numid>%1</numid> games?<br>"
0236                 "This will be final, you cannot cancel it with "
0237                 "the cancel button",
0238                 len(deleteGames))).addCallback(answered, deleteGames)