class RulesetDiffer(QDialog): """Shows differences between rulesets""" def __init__(self, leftRulesets, rightRulesets, parent=None): QDialog.__init__(self, parent) if not isinstance(leftRulesets, list): leftRulesets = list([leftRulesets]) if not isinstance(rightRulesets, list): rightRulesets = list([rightRulesets]) leftRulesets, rightRulesets = leftRulesets[:], rightRulesets[:] # remove rulesets from right which are also on the left side for left in leftRulesets: left.load() for right in rightRulesets: right.load() for left in leftRulesets: for right in rightRulesets[:]: if left == right and left.name == right.name: # rightRulesets.remove(right) this is wrong because it # removes the first ruleset with the same hash rightRulesets = list( x for x in rightRulesets if id(x) != id(right)) self.leftRulesets = leftRulesets self.rightRulesets = rightRulesets self.model = None self.modelTest = None self.view = MJTableView(self) self.buttonBox = QDialogButtonBox() self.buttonBox.setStandardButtons(QDialogButtonBox.Ok) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) cbLayout = QHBoxLayout() self.cbRuleset1 = ListComboBox(self.leftRulesets) if len(self.leftRulesets) == 1: self.lblRuleset1 = QLabel(self.leftRulesets[0].name) cbLayout.addWidget(self.lblRuleset1) else: cbLayout.addWidget(self.cbRuleset1) self.cbRuleset2 = ListComboBox(self.rightRulesets) cbLayout.addWidget(self.cbRuleset2) cmdLayout = QHBoxLayout() cmdLayout.addWidget(self.buttonBox) layout = QVBoxLayout() layout.addLayout(cbLayout) layout.addWidget(self.view) layout.addLayout(cmdLayout) self.setLayout(layout) decorateWindow(self, i18n("Compare")) self.setObjectName('RulesetDiffer') self.cbRuleset1.currentIndexChanged.connect(self.leftRulesetChanged) self.cbRuleset2.currentIndexChanged.connect(self.rulesetChanged) self.leftRulesetChanged() StateSaver(self) def leftRulesetChanged(self): """slot to be called if the left ruleset changes""" if len(self.leftRulesets) == 1: self.orderRight() self.rulesetChanged() def rulesetChanged(self): """slot to be called if the right ruleset changes""" self.model = DifferModel(self.formattedDiffs(), self) if Debug.modelTest: self.modelTest = ModelTest(self.model, self) self.view.setModel(self.model) def orderRight(self): """order the right rulesets by similarity to current left ruleset. Similarity is defined by the length of the diff list.""" leftRuleset = self.cbRuleset1.current diffPairs = sorted([(len(x.diff(leftRuleset)), x) for x in self.rightRulesets]) combo = self.cbRuleset2 with BlockSignals(combo): combo.items = [x[1] for x in diffPairs] combo.setCurrentIndex(0) def formattedDiffs(self): """a list of tuples with 3 values: name, left, right""" formatted = [] leftRuleset = self.cbRuleset1.current rightRuleset = self.cbRuleset2.current assert rightRuleset, self.cbRuleset2.count() leftRuleset.load() rightRuleset.load() for rule1, rule2 in leftRuleset.diff(rightRuleset): name = i18n(rule1.name if rule1 else rule2.name) left = rule1.i18nStr() if rule1 else i18nc( 'Kajongg-Rule', 'not defined') right = rule2.i18nStr() if rule2 else i18nc( 'Kajongg-Rule', 'not defined') formatted.append((name, left, right)) if rule1 and rule2 and rule1.definition != rule2.definition: formatted.append(('', rule1.definition, rule2.definition)) return formatted
class PlayerList(QDialog): """QtSQL Model view of the players""" def __init__(self, parent): QDialog.__init__(self) self.parent = parent self._data = {} self.table = QTableWidget(self) self.table.horizontalHeader().setStretchLastSection(True) self.table.verticalHeader().setVisible(False) self.table.setEditTriggers(QTableWidget.NoEditTriggers) self.table.itemChanged.connect(self.itemChanged) self.updateTable() self.buttonBox = QDialogButtonBox() self.buttonBox.setStandardButtons( QDialogButtonBox.Close) # Close has the Rejected role self.buttonBox.rejected.connect(self.accept) self.newButton = self.buttonBox.addButton( m18nc('define a new player', "&New"), QDialogButtonBox.ActionRole) self.newButton.setIcon(KIcon("document-new")) self.newButton.clicked.connect(self.slotInsert) self.deleteButton = self.buttonBox.addButton( m18n("&Delete"), QDialogButtonBox.ActionRole) self.deleteButton.setIcon(KIcon("edit-delete")) self.deleteButton.clicked.connect(self.delete) cmdLayout = QHBoxLayout() cmdLayout.addWidget(self.buttonBox) layout = QVBoxLayout() layout.addWidget(self.table) layout.addLayout(cmdLayout) self.setLayout(layout) decorateWindow(self, m18n("Players")) self.setObjectName('Players') def showEvent(self, dummyEvent): """adapt view to content""" StateSaver(self, self.table) @staticmethod def sortKey(text): """display order in Table""" if len(text) == 0: return 'zzzzzzzzzzzz' else: return text.upper() def updateTable(self, data=None, currentName=None): """fills self.table from DB""" self.table.itemChanged.disconnect(self.itemChanged) table = self.table table.clear() if data is None: data = dict( Query('select name, id from player where name not like "ROBOT %"').records) self._data = data table.setColumnCount(1) table.setRowCount(len(self._data)) table.setHorizontalHeaderLabels([m18n("Player")]) table.setSelectionBehavior(QTableWidget.SelectRows) table.setSelectionMode(QTableWidget.SingleSelection) selectedItem = None for row, name in enumerate(sorted(self._data, key=self.sortKey)): item = QTableWidgetItem(name) if selectedItem is None: selectedItem = item table.setItem(row, 0, item) if name == currentName: selectedItem = item if selectedItem: table.setCurrentItem(selectedItem) table.scrollToItem(selectedItem) self.table.itemChanged.connect(self.itemChanged) def itemChanged(self, item): """this must be new because editing is disabled for others""" currentName = unicode(item.text()) if currentName in self._data: Sorry(m18n('Player %1 already exists', currentName)) self.setFocus() del self._data[ unicode(self.table.item(self.table.currentRow(), 0).text())] self.updateTable(currentName=currentName) return query = Query('insert into player(name) values(?)', (currentName, )) if query.failure: Sorry( m18n( 'Error while adding player %1: %2', currentName, query.failure.message)) self.updateTable(currentName=currentName) def slotInsert(self): """insert a record""" self._data[''] = 0 self.updateTable(data=self._data, currentName='') for row in range(len(self._data)): item = self.table.item(row, 0) if len(item.text()) == 0: self.table.editItem(item) def delete(self): """delete selected entries""" items = self.table.selectedItems() currentRow = self.table.currentRow() if len(items): name = unicode(items[0].text()) playerId = self._data[name] query = Query( "select 1 from game where p0=? or p1=? or p2=? or p3=?", (playerId, ) * 4) if len(query.records): Sorry( m18n('This player cannot be deleted. There are games associated with %1.', name)) return Query("delete from player where name=?", (name,)) self.updateTable() self.table.setCurrentCell(min(currentRow, len(self._data) - 1), 0) def keyPressEvent(self, event): """use insert/delete keys for insert/delete""" key = event.key() if key == Qt.Key_Insert: self.slotInsert() elif key == Qt.Key_Delete: self.delete() else: QDialog.keyPressEvent(self, event)
class RulesetDiffer(QDialog): """Shows differences between rulesets""" def __init__(self, leftRulesets, rightRulesets, parent=None): QDialog.__init__(self, parent) if not isinstance(leftRulesets, list): leftRulesets = list([leftRulesets]) if not isinstance(rightRulesets, list): rightRulesets = list([rightRulesets]) leftRulesets, rightRulesets = leftRulesets[:], rightRulesets[:] # remove rulesets from right which are also on the left side for left in leftRulesets: left.load() for right in rightRulesets: right.load() for left in leftRulesets: for right in rightRulesets[:]: if left == right and left.name == right.name: # rightRulesets.remove(right) this is wrong because it # removes the first ruleset with the same hash rightRulesets = list(x for x in rightRulesets if id(x) != id(right)) self.leftRulesets = leftRulesets self.rightRulesets = rightRulesets self.model = None self.modelTest = None self.view = MJTableView(self) self.buttonBox = QDialogButtonBox() self.buttonBox.setStandardButtons(QDialogButtonBox.Ok) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) cbLayout = QHBoxLayout() self.cbRuleset1 = ListComboBox(self.leftRulesets) if len(self.leftRulesets) == 1: self.lblRuleset1 = QLabel(self.leftRulesets[0].name) cbLayout.addWidget(self.lblRuleset1) else: cbLayout.addWidget(self.cbRuleset1) self.cbRuleset2 = ListComboBox(self.rightRulesets) cbLayout.addWidget(self.cbRuleset2) cmdLayout = QHBoxLayout() cmdLayout.addWidget(self.buttonBox) layout = QVBoxLayout() layout.addLayout(cbLayout) layout.addWidget(self.view) layout.addLayout(cmdLayout) self.setLayout(layout) decorateWindow(self, m18n("Compare")) self.setObjectName("RulesetDiffer") self.cbRuleset1.currentIndexChanged.connect(self.leftRulesetChanged) self.cbRuleset2.currentIndexChanged.connect(self.rulesetChanged) self.leftRulesetChanged() StateSaver(self) def leftRulesetChanged(self): """slot to be called if the left ruleset changes""" if len(self.leftRulesets) == 1: self.orderRight() self.rulesetChanged() def rulesetChanged(self): """slot to be called if the right ruleset changes""" self.model = DifferModel(self.formattedDiffs(), self) if Debug.modelTest: self.modelTest = ModelTest(self.model, self) self.view.setModel(self.model) def orderRight(self): """order the right rulesets by similarity to current left ruleset. Similarity is defined by the length of the diff list.""" leftRuleset = self.cbRuleset1.current diffPairs = sorted([(len(x.diff(leftRuleset)), x) for x in self.rightRulesets]) combo = self.cbRuleset2 with BlockSignals(combo): combo.items = [x[1] for x in diffPairs] combo.setCurrentIndex(0) def formattedDiffs(self): """a list of tuples with 3 values: name, left, right""" formatted = [] leftRuleset = self.cbRuleset1.current rightRuleset = self.cbRuleset2.current assert rightRuleset, self.cbRuleset2.count() leftRuleset.load() rightRuleset.load() for rule1, rule2 in leftRuleset.diff(rightRuleset): name = m18n(rule1.name if rule1 else rule2.name) left = rule1.contentStr() if rule1 else m18nc("Kajongg-Rule", "not defined") right = rule2.contentStr() if rule2 else m18nc("Kajongg-Rule", "not defined") formatted.append((name, left, right)) if rule1 and rule2 and rule1.definition != rule2.definition: formatted.append(("", rule1.definition, rule2.definition)) return formatted
class PlayerList(QDialog): """QtSQL Model view of the players""" def __init__(self, parent): QDialog.__init__(self) self.parent = parent self._data = {} self.table = QTableWidget(self) self.table.horizontalHeader().setStretchLastSection(True) self.table.verticalHeader().setVisible(False) self.table.setEditTriggers(QTableWidget.NoEditTriggers) self.table.itemChanged.connect(self.itemChanged) self.updateTable() self.buttonBox = QDialogButtonBox() self.buttonBox.setStandardButtons( QDialogButtonBox.Close) # Close has the Rejected role self.buttonBox.rejected.connect(self.accept) self.newButton = self.buttonBox.addButton( i18nc('define a new player', "&New"), QDialogButtonBox.ActionRole) self.newButton.setIcon(KIcon("document-new")) self.newButton.clicked.connect(self.slotInsert) self.deleteButton = self.buttonBox.addButton( i18n("&Delete"), QDialogButtonBox.ActionRole) self.deleteButton.setIcon(KIcon("edit-delete")) self.deleteButton.clicked.connect(self.delete) cmdLayout = QHBoxLayout() cmdLayout.addWidget(self.buttonBox) layout = QVBoxLayout() layout.addWidget(self.table) layout.addLayout(cmdLayout) self.setLayout(layout) decorateWindow(self, i18n("Players")) self.setObjectName('Players') def showEvent(self, dummyEvent): """adapt view to content""" StateSaver(self, self.table) @staticmethod def sortKey(text): """display order in Table""" if len(text) == 0: return 'zzzzzzzzzzzz' else: return text.upper() def updateTable(self, data=None, currentName=None): """fills self.table from DB""" self.table.itemChanged.disconnect(self.itemChanged) table = self.table table.clear() if data is None: data = dict( Query('select name, id from player where name not like "ROBOT %"').records) self._data = data table.setColumnCount(1) table.setRowCount(len(self._data)) table.setHorizontalHeaderLabels([i18n("Player")]) table.setSelectionBehavior(QTableWidget.SelectRows) table.setSelectionMode(QTableWidget.SingleSelection) selectedItem = None for row, name in enumerate(sorted(self._data, key=self.sortKey)): item = QTableWidgetItem(name) if selectedItem is None: selectedItem = item table.setItem(row, 0, item) if name == currentName: selectedItem = item if selectedItem: table.setCurrentItem(selectedItem) table.scrollToItem(selectedItem) self.table.itemChanged.connect(self.itemChanged) def itemChanged(self, item): """this must be new because editing is disabled for others""" currentName = item.text() if currentName in self._data: Sorry(i18n('Player %1 already exists', currentName)) self.setFocus() del self._data[self.table.item(self.table.currentRow(), 0).text()] self.updateTable(currentName=currentName) return query = Query('insert into player(name) values(?)', (currentName, )) if query.failure: Sorry( i18n( 'Error while adding player %1: %2', currentName, query.failure.message)) self.updateTable(currentName=currentName) def slotInsert(self): """insert a record""" self._data[''] = 0 self.updateTable(data=self._data, currentName='') for row in range(len(self._data)): item = self.table.item(row, 0) if len(item.text()) == 0: self.table.editItem(item) def delete(self): """delete selected entries""" items = self.table.selectedItems() currentRow = self.table.currentRow() if len(items): name = items[0].text() playerId = self._data[name] query = Query( "select 1 from game where p0=? or p1=? or p2=? or p3=?", (playerId, ) * 4) if len(query.records): Sorry( i18n('This player cannot be deleted. There are games associated with %1.', name)) return Query("delete from player where name=?", (name,)) self.updateTable() self.table.setCurrentCell(min(currentRow, len(self._data) - 1), 0) def keyPressEvent(self, event): """use insert/delete keys for insert/delete""" key = event.key() if key == Qt.Key_Insert: self.slotInsert() elif key == Qt.Key_Delete: self.delete() else: QDialog.keyPressEvent(self, event)
class Games(QDialog): """a dialog for selecting a game""" def __init__(self, parent=None): super(Games, self).__init__(parent) self.selectedGame = None self.onlyPending = True decorateWindow(self, i18nc('kajongg', 'Games')) self.setObjectName('Games') self.resize(700, 400) self.model = GamesModel() if Debug.modelTest: self.modelTest = ModelTest(self.model, self) self.view = MJTableView(self) self.view.setModel(self.model) self.selection = QItemSelectionModel(self.model, self.view) self.view.setSelectionModel(self.selection) self.view.setSelectionBehavior(QAbstractItemView.SelectRows) self.view.setSelectionMode(QAbstractItemView.SingleSelection) self.buttonBox = QDialogButtonBox(self) self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel) self.newButton = self.buttonBox.addButton( i18nc('start a new game', "&New"), QDialogButtonBox.ActionRole) self.newButton.setIcon(KIcon("document-new")) self.newButton.clicked.connect(self.accept) self.loadButton = self.buttonBox.addButton( i18n("&Load"), QDialogButtonBox.AcceptRole) self.loadButton.clicked.connect(self.loadGame) self.loadButton.setIcon(KIcon("document-open")) self.deleteButton = self.buttonBox.addButton( i18n("&Delete"), QDialogButtonBox.ActionRole) self.deleteButton.setIcon(KIcon("edit-delete")) self.deleteButton.clicked.connect(self.delete) chkPending = QCheckBox(i18n("Show only pending games"), self) chkPending.setChecked(True) cmdLayout = QHBoxLayout() cmdLayout.addWidget(chkPending) cmdLayout.addWidget(self.buttonBox) layout = QVBoxLayout() layout.addWidget(self.view) layout.addLayout(cmdLayout) self.setLayout(layout) StateSaver(self) self.selection.selectionChanged.connect(self.selectionChanged) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.view.doubleClicked.connect(self.loadGame) chkPending.stateChanged.connect(self.pendingOrNot) def showEvent(self, dummyEvent): """only now get the data set. Not doing this in__init__ would eventually make it easier to subclass from some generic TableEditor class""" self.setQuery() self.view.initView() self.selectionChanged() def keyPressEvent(self, event): """use insert/delete keys for insert/delete""" key = event.key() if key == Qt.Key_Insert: self.newEntry() return if key == Qt.Key_Delete: self.delete() event.ignore() return QDialog.keyPressEvent(self, event) def selectionChanged(self): """update button states according to selection""" selectedRows = len(self.selection.selectedRows()) self.loadButton.setEnabled(selectedRows == 1) self.deleteButton.setEnabled(selectedRows >= 1) def setQuery(self): """define the query depending on self.OnlyPending""" query = Query( "select g.id, g.starttime, " "p0.name||'///'||p1.name||'///'||p2.name||'///'||p3.name " "from game g, player p0," "player p1, player p2, player p3 " "where seed is null" " and p0.id=g.p0 and p1.id=g.p1 " " and p2.id=g.p2 and p3.id=g.p3 " "%s" "and exists(select 1 from score where game=g.id)" % ("and g.endtime is null " if self.onlyPending else "")) self.model.setResultset(query.records) self.view.hideColumn(0) def __idxForGame(self, game): """returns the model index for game""" for row in range(self.model.rowCount()): idx = self.model.index(row, 0) if self.model.data(idx, 0) == game: return idx return self.model.index(0, 0) def __getSelectedGame(self): """returns the game id of the selected game""" rows = self.selection.selectedRows() if rows: return self.model.data(rows[0], 0) else: return 0 def pendingOrNot(self, chosen): """do we want to see all games or only pending games?""" if self.onlyPending != chosen: self.onlyPending = chosen prevSelected = self.__getSelectedGame() self.setQuery() idx = self.__idxForGame(prevSelected) self.view.selectRow(idx.row()) self.view.setFocus() def loadGame(self): """load a game""" self.selectedGame = self.__getSelectedGame() self.buttonBox.accepted.emit() def delete(self): """delete a game""" def answered(result, games): """question answered, result is True or False""" if result: for game in games: Query("DELETE FROM score WHERE game = ?", (game, )) Query("DELETE FROM game WHERE id = ?", (game, )) self.setQuery() # just reload entire table allGames = self.view.selectionModel().selectedRows(0) deleteGames = list(x.data() for x in allGames) if len(deleteGames) == 0: # should never happen logException('delete: 0 rows selected') WarningYesNo( i18n( "Do you really want to delete <numid>%1</numid> games?<br>" "This will be final, you cannot cancel it with " "the cancel button", len(deleteGames))).addCallback(answered, deleteGames)
class Games(QDialog): """a dialog for selecting a game""" def __init__(self, parent=None): super(Games, self).__init__(parent) self.selectedGame = None self.onlyPending = True decorateWindow(self, m18nc('kajongg', 'Games')) self.setObjectName('Games') self.resize(700, 400) self.model = GamesModel() if Debug.modelTest: self.modelTest = ModelTest(self.model, self) self.view = MJTableView(self) self.view.setModel(self.model) self.selection = QItemSelectionModel(self.model, self.view) self.view.setSelectionModel(self.selection) self.view.setSelectionBehavior(QAbstractItemView.SelectRows) self.view.setSelectionMode(QAbstractItemView.SingleSelection) self.buttonBox = QDialogButtonBox(self) self.buttonBox.setStandardButtons(QDialogButtonBox.Cancel) self.newButton = self.buttonBox.addButton( m18nc('start a new game', "&New"), QDialogButtonBox.ActionRole) self.newButton.setIcon(KIcon("document-new")) self.newButton.clicked.connect(self.accept) self.loadButton = self.buttonBox.addButton( m18n("&Load"), QDialogButtonBox.AcceptRole) self.loadButton.clicked.connect(self.loadGame) self.loadButton.setIcon(KIcon("document-open")) self.deleteButton = self.buttonBox.addButton( m18n("&Delete"), QDialogButtonBox.ActionRole) self.deleteButton.setIcon(KIcon("edit-delete")) self.deleteButton.clicked.connect(self.delete) chkPending = QCheckBox(m18n("Show only pending games"), self) chkPending.setChecked(True) cmdLayout = QHBoxLayout() cmdLayout.addWidget(chkPending) cmdLayout.addWidget(self.buttonBox) layout = QVBoxLayout() layout.addWidget(self.view) layout.addLayout(cmdLayout) self.setLayout(layout) StateSaver(self) self.selection.selectionChanged.connect(self.selectionChanged) self.buttonBox.accepted.connect(self.accept) self.buttonBox.rejected.connect(self.reject) self.view.doubleClicked.connect(self.loadGame) chkPending.stateChanged.connect(self.pendingOrNot) def showEvent(self, dummyEvent): """only now get the data set. Not doing this in__init__ would eventually make it easier to subclass from some generic TableEditor class""" self.setQuery() self.view.initView() self.selectionChanged() def keyPressEvent(self, event): """use insert/delete keys for insert/delete""" key = event.key() if key == Qt.Key_Insert: self.newEntry() return if key == Qt.Key_Delete: self.delete() event.ignore() return QDialog.keyPressEvent(self, event) def selectionChanged(self): """update button states according to selection""" selectedRows = len(self.selection.selectedRows()) self.loadButton.setEnabled(selectedRows == 1) self.deleteButton.setEnabled(selectedRows >= 1) def setQuery(self): """define the query depending on self.OnlyPending""" query = Query( "select g.id, g.starttime, " "p0.name||'///'||p1.name||'///'||p2.name||'///'||p3.name " "from game g, player p0," "player p1, player p2, player p3 " "where seed is null" " and p0.id=g.p0 and p1.id=g.p1 " " and p2.id=g.p2 and p3.id=g.p3 " "%s" "and exists(select 1 from score where game=g.id)" % ("and g.endtime is null " if self.onlyPending else "")) self.model.setResultset(query.records) self.view.hideColumn(0) def __idxForGame(self, game): """returns the model index for game""" for row in range(self.model.rowCount()): idx = self.model.index(row, 0) if variantValue(self.model.data(idx, 0)) == game: return idx return self.model.index(0, 0) def __getSelectedGame(self): """returns the game id of the selected game""" rows = self.selection.selectedRows() if rows: return variantValue(self.model.data(rows[0], 0)) else: return 0 def pendingOrNot(self, chosen): """do we want to see all games or only pending games?""" if self.onlyPending != chosen: self.onlyPending = chosen prevSelected = self.__getSelectedGame() self.setQuery() idx = self.__idxForGame(prevSelected) self.view.selectRow(idx.row()) self.view.setFocus() def loadGame(self): """load a game""" self.selectedGame = self.__getSelectedGame() self.buttonBox.accepted.emit() def delete(self): """delete a game""" def answered(result, games): """question answered, result is True or False""" if result: for game in games: Query("DELETE FROM score WHERE game = ?", (game, )) Query("DELETE FROM game WHERE id = ?", (game, )) self.setQuery() # just reload entire table allGames = self.view.selectionModel().selectedRows(0) deleteGames = list(variantValue(x.data()) for x in allGames) if len(deleteGames) == 0: # should never happen logException('delete: 0 rows selected') WarningYesNo( m18n( "Do you really want to delete <numid>%1</numid> games?<br>" "This will be final, you cannot cancel it with " "the cancel button", len(deleteGames))).addCallback(answered, deleteGames)