class Preview(QWidget): OFFSET_X = 0 OFFSET_Y = 10 WIDTH = 211 HEIGHT = 477 def __init__(self): super(Preview, self).__init__() layout = QGridLayout(self) layout.setAlignment(Qt.AlignCenter) self.loading = Loading(200) self.loading.hide() layout.addWidget(self.loading, 0, 0) self.image = None def setImage(self, image): if image is None: self.loading.show() self.image = None else: self.loading.hide() self.image = image.toImage() self.update() def paintEvent(self, event): if self.image is not None: painter = QPainter() painter.begin(self) image = self.image.scaledToWidth(self.width(), Qt.SmoothTransformation) frame = QImage(QDir.currentPath() + '/gui/images/background.png') frame = frame.copy(Preview.OFFSET_X, Preview.OFFSET_Y, Preview.WIDTH, Preview.HEIGHT) frame = frame.scaled(image.width(), image.height(), Qt.IgnoreAspectRatio, Qt.SmoothTransformation) x1, y1 = 0, (self.height() - image.height()) / 2 x2, y2 = x1 + self.width() - 1, y1 + image.height() - 1 w, h, delta = image.width(), image.height(), 5 painter.drawImage(x1, y1, frame) painter.setPen(QColor(QColor(255, 255, 255, 200))) painter.drawLine(x1, y1, x1, y2) painter.drawLine(x1, y1, x2, y1) painter.setPen(QColor(QColor(0, 0, 0, 200))) painter.drawLine(x2, y1, x2, y2) painter.drawLine(x1, y2, x2, y2) painter.drawImage(x1 + delta, y1 + delta, image.scaled(w - delta * 2, h - delta * 2)) painter.end()
class Info(QWidget): def __init__(self): super(Info, self).__init__() layout = QGridLayout(self) layout.setAlignment(Qt.AlignCenter) self.loading = Loading(70) self.loading.hide() layout.addWidget(self.loading, 0, 0, 1, -1, Qt.AlignCenter) self.info = [] font = QFont() font.setStyleStrategy(QFont.PreferAntialias) font.setPixelSize(15) font.setFamily('monospace') self.valueLabelFont = font font = QFont(font) font.setBold(True) self.nameLabelFont = font self.textTemplate = '<font color="white">%s</font>' @Slot(list) def updateInfo(self, info): for nameLabel, valueLabel in self.info: nameLabel.deleteLater() valueLabel.deleteLater() self.info = [] index = 0 for name, value in info: if all([c == ' ' for c in name]): sep = ' ' else: sep = ':' if value is not None: nameLabel = QLabel(self.textTemplate % (name + sep)) valueLabel = QLabel(self.textTemplate % (value)) else: nameLabel = QLabel(self.textTemplate % (name)) valueLabel = QLabel() nameLabel.setFont(self.nameLabelFont) valueLabel.setFont(self.valueLabelFont) self.info.append((nameLabel, valueLabel)) self.layout().addWidget(nameLabel, index, 0, Qt.AlignRight) self.layout().addWidget(valueLabel, index, 1, Qt.AlignLeft) index = index + 1
class IntroFrame(QWidget): def __init__(self, parent): super(IntroFrame, self).__init__(parent) layout = QVBoxLayout(self) layout.setContentsMargins(20, 15, 20, 20) self.title = Title('', 23) titleLayout = QHBoxLayout() titleLayout.setAlignment(Qt.AlignTop) titleLayout.addWidget(QLabel(' ' * 5)) titleLayout.addWidget(self.title, 1) layout.addLayout(titleLayout, 0) self.loading = Loading(200) self.loading.hide() layout.addWidget(self.loading, 1, Qt.AlignCenter) def setTitle(self, title): self.title.setText(title)
class GameViewFrame(QWidget): def __init__(self, parent): super(GameViewFrame, self).__init__(parent) layout = QVBoxLayout(self) layout.setContentsMargins(20, 15, 20, 20) self.title = Title('', 23) titleLayout = QHBoxLayout() titleLayout.setAlignment(Qt.AlignTop) titleLayout.addWidget(QLabel(' ' * 5)) titleLayout.addWidget(self.title, 1) layout.addLayout(titleLayout, 0) self.loading = Loading(200) self.loading.hide() layout.addWidget(self.loading, 1, Qt.AlignCenter) self.countDown = CountDown(200, self) self.countDown.hide() self.gameView = None def setTitle(self, title): self.title.setText(title) def showLoading(self): if self.gameView is not None: self.gameView.hide() self.loading.show() def showCountDown(self, quick=False): center = self.gameView.pos() + QPoint(self.gameView.width() / 2, self.gameView.height() / 2) self.countDown.move(center - QPoint(self.countDown.width() / 2, self.countDown.height() / 2)) self.countDown.show() self.countDown.raise_() self.countDown.start(5 if not quick else 0) def showGameView(self, gameMap): self.loading.hide() self.gameView = GameView(gameMap) self.layout().addWidget(self.gameView, 1, Qt.AlignCenter)
class GameViewFrame(QWidget): def __init__(self, parent): super(GameViewFrame, self).__init__(parent) layout = QVBoxLayout(self) layout.setContentsMargins(20, 15, 20, 20) self.title = Title('', 23) titleLayout = QHBoxLayout() titleLayout.setAlignment(Qt.AlignTop) titleLayout.addWidget(QLabel(' ' * 5)) titleLayout.addWidget(self.title, 1) layout.addLayout(titleLayout, 0) self.loading = Loading(200) self.loading.hide() layout.addWidget(self.loading, 1, Qt.AlignCenter) self.countDown = CountDown(200, self) self.countDown.hide() self.gameView = None def setTitle(self, title): self.title.setText(title) def showLoading(self): if self.gameView is not None: self.gameView.hide() self.loading.show() def showCountDown(self, quick = False): center = self.gameView.pos() + QPoint(self.gameView.width() / 2, self.gameView.height() / 2) self.countDown.move(center - QPoint(self.countDown.width() / 2, self.countDown.height() / 2)) self.countDown.show() self.countDown.raise_() self.countDown.start(5 if not quick else 0) def showGameView(self, gameMap): self.loading.hide() self.gameView = GameView(gameMap) self.layout().addWidget(self.gameView, 1, Qt.AlignCenter)
class NewGrid(newUI): # Create a new _init_ within the SuperClass MainFrame def __init__(self, parent, title): # --- variables --- self.ActionName = 'Create new grid project' self.helperFile = 'new_grid.pdf' newUI.__init__(self, parent, title) # --- polygon --- self.sizerHPolygon = uploadFileSizer(self.panel, 'Polygon', self.fit) self.sizerV.Add(self.sizerHPolygon, flag=wx.ALIGN_LEFT | wx.ALL, border=margin) # --- separator --- self.separator = wx.StaticLine(self.panel, style=wx.LI_HORIZONTAL, size=separatorSize) self.sizerV.Add(self.separator, flag=wx.ALIGN_CENTER) # --- obstacles --- self.sizerHObstacles = uploadFileSizer(self.panel, 'Obstacles', self.fit, True) self.sizerV.Add(self.sizerHObstacles, flag=wx.ALIGN_LEFT | wx.ALL, border=margin) # --- separator --- self.separator = wx.StaticLine(self.panel, style=wx.LI_HORIZONTAL, size=separatorSize) self.sizerV.Add(self.separator, flag=wx.ALIGN_CENTER) # --- resolution --- self.sizerHResolution = resolutionSizer(self.panel) self.sizerV.Add(self.sizerHResolution, flag=wx.ALIGN_LEFT | wx.ALL, border=margin) # --- rendering --- self.render() def action(self, event): self.wantObstacles = self.sizerHObstacles.isChecked() # check all inputs data and notify errors errorMsg = '' # reset error messages errorMsg += self.sizerHProject.check() errorMsg += self.sizerHAuthor.check() errorMsg += self.sizerHPolygon.check() errorMsg += self.sizerHResolution.check() errorMsg += self.sizerHProjectSrs.check() if self.wantObstacles: errorMsg += self.sizerHObstacles.check() if errorMsg == '': self.srsProject = self.sizerHProjectSrs.getValue() self.srsProject = re.search(r'\[(\d*)\].*', self.srsProject).group(1) unit = getUnit(int(self.srsProject)) if unit == degreeUnit: warningMsg = 'You have chosen a projection with unit in degrees.\n' warningMsg += 'Would you like to contiue?' dlgWarning = wx.MessageDialog( self, warningMsg, 'Warning lon/lat projection system', wx.YES_NO | wx.ICON_EXCLAMATION) if dlgWarning.ShowModal() == wx.ID_YES: self.computeNewGrid() dlgWarning.Destroy() else: self.computeNewGrid() else: dlg = wx.MessageDialog(self, errorMsg, 'Errors', wx.CANCEL | wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() def computeNewGrid(self): # get form values self.projectName = self.sizerHProject.getValue() self.authorName = self.sizerHAuthor.getValue() self.polygon = self.sizerHPolygon.getValue() self.obstacle = self.sizerHObstacles.getValue() self.srsObstacle = self.sizerHObstacles.getSRSValue() self.res = self.sizerHResolution.getValue() self.srsPolygon = self.sizerHPolygon.getSRSValue() self.srsPolygon = re.search(r'\[(\d*)\].*', self.srsPolygon).group(1) Publisher().subscribe(self.catchError, 'catchError') # create a pubsub receiver self.tmpP = None # path to temporary polygon files created during populate self.tmpO = None # path to temporary obstacles files created during populate self.gridCreated = False self.Hide() self.load = Loading(self, "Loading") self.start = time.time() callback = 'populatePolygonEnd' Publisher().subscribe(self.populatePolygonEnd, callback) # create a pubsub receiver Populate(self.polygon, self.srsPolygon, self.srsProject, 'polygon', callback) def cleanData(self): if self.gridCreated: cleanPublicSchema(self.projectName) if self.tmpP is not None: cleanPublicSchema('polygon') cleanTemporaryFiles(self.tmpP) if self.tmpO is not None: cleanPublicSchema('obstacles') cleanTemporaryFiles(self.tmpO) def populatePolygonEnd(self, msg): consoleAppend(' > Populate polygon in %s' % GetTextTime(time.time() - self.start)) self.tmpP = msg.data self.ts = time.time() if self.wantObstacles: self.srsObstacle = re.search(r'\[(\d*)\].*', self.srsObstacle).group(1) callback = 'populateObstaclesEnd' Publisher().subscribe(self.populateObstaclesEnd, callback) # create a pubsub receiver Populate(self.obstacle, self.srsObstacle, self.srsProject, 'obstacles', callback) else: self.populateObstaclesEnd(None) def populateObstaclesEnd(self, msg): if self.wantObstacles: consoleAppend(' > Populate obstacles in %s' % GetTextTime(time.time() - self.ts)) self.tmpO = msg.data self.ts = time.time() callback = "gridNetworkEnd" Publisher().subscribe(self.gridNetworkEnd, callback) pubDial = "publishDialog" Publisher().subscribe(self.publishDialog, pubDial) # the srs of the grid network is always the srs out of the polygon GridNetwork(callback, pubDial, self.res, self.srsProject, self.projectName, self.wantObstacles) def publishDialog(self, msg): txt = msg.data dlg = wx.MessageDialog(self, txt, 'Warning projection match', wx.OK | wx.ICON_EXCLAMATION) dlg.ShowModal() dlg.Destroy() # clean data self.cleanData() # enable main frame self.load.hide() self.parent.Enable() self.Destroy() def gridNetworkEnd(self, msg): consoleAppend(' > The grid network has been created in %s' % GetTextTime(time.time() - self.ts)) self.gridCreated = True self.ts = time.time() callback = "topologyEnd" Publisher().subscribe(self.topologyEnd, callback) # create a pubsub receiver consoleAppend( 'Start creating the topology... This can take a while, please be patient...' ) Topology(self.projectName, self.srsProject, self.authorName, 0, 'grid', self.res, callback) def gridShpNetworkEnd(self, msg): # callback of GridNetwork.creatShp consoleAppend("New shapefile has been created in %s" % GetTextTime(time.time() - self.start)) self.cleanData() self.parent.repaintTable() self.actionEnd(None) def topologyEnd(self, msg): consoleAppend('The topology has been created in %s...' % GetTextTime(time.time() - self.ts)) self.cleanData() self.parent.repaintTable() self.actionEnd(None)
class ItemList(QWidget): ANIMATION_LIST_DURATION = 700 ANIMATION_FLIP_DURATION_PHASE_ONE = 300 ANIMATION_FLIP_DURATION_PHASE_TWO = 500 ITEM_PER_PAGE = 12 def __init__(self): super(ItemList, self).__init__() layout = QVBoxLayout(self) layout.setAlignment(Qt.AlignTop) self.loading = Loading(128) self.loading.hide() layout.insertWidget(0, self.loading, 10, Qt.AlignCenter) self.currentPage = 0 self.prevPage = Button('###UP###', False, True, False) self.nextPage = Button('###DOWN###', False, True, False) self.prevPage.setEnabled(False) self.nextPage.setEnabled(False) self.prevPage.clicked.connect(self.showPrevPage) self.nextPage.clicked.connect(self.showNextPage) pageLayout = QHBoxLayout() pageLayout.setAlignment(Qt.AlignBottom) pageLayout.addWidget(self.prevPage) pageLayout.addWidget(self.nextPage) layout.insertLayout(ItemList.ITEM_PER_PAGE + 1, pageLayout, 1) self.items = {} def addItem(self, id, name, clickable = True): if clickable: button = Button(name, True, False, False, self) else: button = Button(name, False, True, True, self) button.id = id self.items[id] = button return button def _removeItem(self, id): self.items[id].deleteLater() del self.items[id] def clearItems(self): for id in dict(self.items).iterkeys(): self._removeItem(id) self.prevPage.setEnabled(False) self.nextPage.setEnabled(False) def _pageItems(self, page): ids = sorted(self.items.iterkeys())[ItemList.ITEM_PER_PAGE * page:ItemList.ITEM_PER_PAGE * (page + 1)] items = [] for id in ids: items.append(self.items[id]) return items def showPage(self, page, flip = False): minPage, maxPage = 0, max(0, (len(self.items) + ItemList.ITEM_PER_PAGE - 1) / ItemList.ITEM_PER_PAGE - 1) assert page >= minPage and page <= maxPage for item in self.items.itervalues(): item.hide() self.layout().removeWidget(item) index = 1 for item in self._pageItems(page): item.show() self.layout().insertWidget(index, item) index = index + 1 self.currentPage = page self.prevPage.setEnabled(page > minPage) self.nextPage.setEnabled(page < maxPage) if flip: QMetaObject.invokeMethod(self, '_flipAnimation', Qt.QueuedConnection) else: QMetaObject.invokeMethod(self, '_listAnimation', Qt.QueuedConnection) def showPrevPage(self): self.lastPage = self.currentPage self.showPage(self.currentPage - 1, True) def showNextPage(self): self.lastPage = self.currentPage self.showPage(self.currentPage + 1, True) def _listItemAnimation(self, item, duration): itemAnimation = QPropertyAnimation(item, 'pos') itemAnimation.setDuration(duration + item.pos().y() * 2) endPos = item.pos() startPos = QPoint(endPos.x(), -item.height()) itemAnimation.setStartValue(startPos) itemAnimation.setEndValue(endPos) itemAnimation.setEasingCurve(QEasingCurve.OutElastic) return itemAnimation @Slot() def _listAnimation(self): self.listAnimationGroup = QParallelAnimationGroup() for item in self.items.itervalues(): self.listAnimationGroup.addAnimation(self._listItemAnimation(item, ItemList.ANIMATION_LIST_DURATION)) self.listAnimationGroup.start(QAbstractAnimation.DeleteWhenStopped) def _flipItemAnimation(self, item, startPos, endPos, invert, durationPhaseOne, durationPhaseTwo): midPos = QPoint(endPos.x(), startPos.y()) itemAnimationPhaseOne = QPropertyAnimation(item, 'pos') itemAnimationPhaseOne.setDuration(durationPhaseOne - abs(endPos.y() - startPos.y())/ 2) if not invert: itemAnimationPhaseOne.setStartValue(startPos - QPoint(endPos.y(), 0)) itemAnimationPhaseOne.setEndValue(midPos) else: itemAnimationPhaseOne.setEndValue(startPos - QPoint(endPos.y(), 0)) itemAnimationPhaseOne.setStartValue(midPos) itemAnimationPhaseOne.setEasingCurve(QEasingCurve.Linear) itemAnimationPhaseTwo = QPropertyAnimation(item, 'pos') itemAnimationPhaseTwo.setDuration(durationPhaseTwo - abs(endPos.y() - startPos.y())) if not invert: itemAnimationPhaseTwo.setStartValue(midPos) itemAnimationPhaseTwo.setEndValue(endPos) else: itemAnimationPhaseTwo.setEndValue(midPos) itemAnimationPhaseTwo.setStartValue(endPos) itemAnimationPhaseTwo.setEasingCurve(QEasingCurve.Linear) itemAnimationGroup = QSequentialAnimationGroup() if not invert: itemAnimationGroup.addAnimation(itemAnimationPhaseOne) itemAnimationGroup.addAnimation(itemAnimationPhaseTwo) else: itemAnimationGroup.addAnimation(itemAnimationPhaseTwo) itemAnimationGroup.addAnimation(itemAnimationPhaseOne) return itemAnimationGroup def _calcMinY(self, items): minY = sys.maxint for item in items: if item.pos().y() < minY: minY = item.pos().y() return minY @Slot() def _flipAnimation(self): self.flipAnimationGroup = QParallelAnimationGroup() lastItems = self._pageItems(self.lastPage) if self.currentPage < self.lastPage: minY = self._calcMinY(lastItems) startPos = QPoint(-lastItems[0].width(), minY) else: startPos = QPoint(-lastItems[0].width(), self.prevPage.pos().y() - lastItems[0].height() - 5) for item in lastItems: item.show() self.flipAnimationGroup.addAnimation(self._flipItemAnimation(item, startPos, item.pos(), True, ItemList.ANIMATION_FLIP_DURATION_PHASE_ONE, ItemList.ANIMATION_FLIP_DURATION_PHASE_TWO)) currentItems = self._pageItems(self.currentPage) if self.currentPage > self.lastPage: minY = self._calcMinY(currentItems) startPos = QPoint(-currentItems[0].width(), minY) else: startPos = QPoint(-currentItems[0].width(), self.prevPage.pos().y() - currentItems[0].height() - 5) for item in currentItems: self.flipAnimationGroup.addAnimation(self._flipItemAnimation(item, startPos, item.pos(), False, ItemList.ANIMATION_FLIP_DURATION_PHASE_ONE, ItemList.ANIMATION_FLIP_DURATION_PHASE_TWO)) self.flipAnimationGroup.start(QAbstractAnimation.DeleteWhenStopped)
class NewGrid(newUI): # Create a new _init_ within the SuperClass MainFrame def __init__(self, parent, title): # --- variables --- self.ActionName = 'Create new grid project' self.helperFile = 'new_grid.pdf' newUI.__init__(self, parent, title) # --- polygon --- self.sizerHPolygon = uploadFileSizer(self.panel, 'Polygon', self.fit) self.sizerV.Add(self.sizerHPolygon, flag=wx.ALIGN_LEFT|wx.ALL, border=margin) # --- separator --- self.separator = wx.StaticLine(self.panel, style=wx.LI_HORIZONTAL, size=separatorSize) self.sizerV.Add(self.separator, flag=wx.ALIGN_CENTER) # --- obstacles --- self.sizerHObstacles = uploadFileSizer(self.panel, 'Obstacles', self.fit, True) self.sizerV.Add(self.sizerHObstacles, flag=wx.ALIGN_LEFT|wx.ALL, border=margin) # --- separator --- self.separator = wx.StaticLine(self.panel, style=wx.LI_HORIZONTAL, size=separatorSize) self.sizerV.Add(self.separator, flag=wx.ALIGN_CENTER) # --- resolution --- self.sizerHResolution = resolutionSizer(self.panel) self.sizerV.Add(self.sizerHResolution, flag=wx.ALIGN_LEFT|wx.ALL, border=margin) # --- rendering --- self.render() def action(self, event): self.wantObstacles = self.sizerHObstacles.isChecked() # check all inputs data and notify errors errorMsg = '' # reset error messages errorMsg += self.sizerHProject.check() errorMsg += self.sizerHAuthor.check() errorMsg += self.sizerHPolygon.check() errorMsg += self.sizerHResolution.check() errorMsg += self.sizerHProjectSrs.check() if self.wantObstacles: errorMsg += self.sizerHObstacles.check() if errorMsg == '': self.srsProject = self.sizerHProjectSrs.getValue() self.srsProject = re.search(r'\[(\d*)\].*', self.srsProject).group(1) unit = getUnit(int(self.srsProject)) if unit == degreeUnit: warningMsg = 'You have chosen a projection with unit in degrees.\n' warningMsg += 'Would you like to contiue?' dlgWarning = wx.MessageDialog(self, warningMsg, 'Warning lon/lat projection system', wx.YES_NO|wx.ICON_EXCLAMATION) if dlgWarning.ShowModal() == wx.ID_YES: self.computeNewGrid() dlgWarning.Destroy() else: self.computeNewGrid() else: dlg = wx.MessageDialog(self, errorMsg, 'Errors', wx.CANCEL|wx.ICON_ERROR) dlg.ShowModal() dlg.Destroy() def computeNewGrid(self): # get form values self.projectName = self.sizerHProject.getValue() self.authorName = self.sizerHAuthor.getValue() self.polygon = self.sizerHPolygon.getValue() self.obstacle = self.sizerHObstacles.getValue() self.srsObstacle = self.sizerHObstacles.getSRSValue() self.res = self.sizerHResolution.getValue() self.srsPolygon = self.sizerHPolygon.getSRSValue() self.srsPolygon = re.search(r'\[(\d*)\].*', self.srsPolygon).group(1) Publisher().subscribe(self.catchError, 'catchError') # create a pubsub receiver self.tmpP = None # path to temporary polygon files created during populate self.tmpO = None # path to temporary obstacles files created during populate self.gridCreated = False self.Hide() self.load = Loading(self, "Loading") self.start = time.time() callback = 'populatePolygonEnd' Publisher().subscribe(self.populatePolygonEnd, callback) # create a pubsub receiver Populate(self.polygon, self.srsPolygon, self.srsProject, 'polygon', callback) def cleanData(self): if self.gridCreated: cleanPublicSchema(self.projectName) if self.tmpP is not None: cleanPublicSchema('polygon') cleanTemporaryFiles(self.tmpP) if self.tmpO is not None: cleanPublicSchema('obstacles') cleanTemporaryFiles(self.tmpO) def populatePolygonEnd(self, msg): consoleAppend(' > Populate polygon in %s' % GetTextTime(time.time() - self.start)) self.tmpP = msg.data self.ts = time.time() if self.wantObstacles: self.srsObstacle = re.search(r'\[(\d*)\].*', self.srsObstacle).group(1) callback = 'populateObstaclesEnd' Publisher().subscribe(self.populateObstaclesEnd, callback) # create a pubsub receiver Populate(self.obstacle, self.srsObstacle, self.srsProject, 'obstacles', callback) else: self.populateObstaclesEnd(None) def populateObstaclesEnd(self, msg): if self.wantObstacles: consoleAppend(' > Populate obstacles in %s' % GetTextTime(time.time() - self.ts)) self.tmpO = msg.data self.ts = time.time() callback = "gridNetworkEnd" Publisher().subscribe(self.gridNetworkEnd, callback) pubDial = "publishDialog" Publisher().subscribe(self.publishDialog, pubDial) # the srs of the grid network is always the srs out of the polygon GridNetwork(callback, pubDial, self.res, self.srsProject, self.projectName, self.wantObstacles) def publishDialog(self, msg): txt = msg.data dlg = wx.MessageDialog(self, txt, 'Warning projection match', wx.OK|wx.ICON_EXCLAMATION) dlg.ShowModal() dlg.Destroy() # clean data self.cleanData() # enable main frame self.load.hide() self.parent.Enable() self.Destroy() def gridNetworkEnd(self, msg): consoleAppend(' > The grid network has been created in %s' % GetTextTime(time.time() - self.ts)) self.gridCreated = True self.ts = time.time() callback = "topologyEnd" Publisher().subscribe(self.topologyEnd, callback) # create a pubsub receiver consoleAppend('Start creating the topology... This can take a while, please be patient...') Topology(self.projectName, self.srsProject, self.authorName, 0, 'grid', self.res, callback) def gridShpNetworkEnd(self, msg): # callback of GridNetwork.creatShp consoleAppend("New shapefile has been created in %s" % GetTextTime(time.time() - self.start)) self.cleanData() self.parent.repaintTable() self.actionEnd(None) def topologyEnd(self, msg): consoleAppend('The topology has been created in %s...' % GetTextTime(time.time() - self.ts)) self.cleanData() self.parent.repaintTable() self.actionEnd(None)
class ItemList(QWidget): ANIMATION_LIST_DURATION = 700 ANIMATION_FLIP_DURATION_PHASE_ONE = 300 ANIMATION_FLIP_DURATION_PHASE_TWO = 500 ITEM_PER_PAGE = 12 def __init__(self): super(ItemList, self).__init__() layout = QVBoxLayout(self) layout.setAlignment(Qt.AlignTop) self.loading = Loading(128) self.loading.hide() layout.insertWidget(0, self.loading, 10, Qt.AlignCenter) self.currentPage = 0 self.prevPage = Button('###UP###', False, True, False) self.nextPage = Button('###DOWN###', False, True, False) self.prevPage.setEnabled(False) self.nextPage.setEnabled(False) self.prevPage.clicked.connect(self.showPrevPage) self.nextPage.clicked.connect(self.showNextPage) pageLayout = QHBoxLayout() pageLayout.setAlignment(Qt.AlignBottom) pageLayout.addWidget(self.prevPage) pageLayout.addWidget(self.nextPage) layout.insertLayout(ItemList.ITEM_PER_PAGE + 1, pageLayout, 1) self.items = {} def addItem(self, id, name, clickable=True): if clickable: button = Button(name, True, False, False, self) else: button = Button(name, False, True, True, self) button.id = id self.items[id] = button return button def _removeItem(self, id): self.items[id].deleteLater() del self.items[id] def clearItems(self): for id in dict(self.items).iterkeys(): self._removeItem(id) self.prevPage.setEnabled(False) self.nextPage.setEnabled(False) def _pageItems(self, page): ids = sorted( self.items.iterkeys())[ItemList.ITEM_PER_PAGE * page:ItemList.ITEM_PER_PAGE * (page + 1)] items = [] for id in ids: items.append(self.items[id]) return items def showPage(self, page, flip=False): minPage, maxPage = 0, max( 0, (len(self.items) + ItemList.ITEM_PER_PAGE - 1) / ItemList.ITEM_PER_PAGE - 1) assert page >= minPage and page <= maxPage for item in self.items.itervalues(): item.hide() self.layout().removeWidget(item) index = 1 for item in self._pageItems(page): item.show() self.layout().insertWidget(index, item) index = index + 1 self.currentPage = page self.prevPage.setEnabled(page > minPage) self.nextPage.setEnabled(page < maxPage) if flip: QMetaObject.invokeMethod(self, '_flipAnimation', Qt.QueuedConnection) else: QMetaObject.invokeMethod(self, '_listAnimation', Qt.QueuedConnection) def showPrevPage(self): self.lastPage = self.currentPage self.showPage(self.currentPage - 1, True) def showNextPage(self): self.lastPage = self.currentPage self.showPage(self.currentPage + 1, True) def _listItemAnimation(self, item, duration): itemAnimation = QPropertyAnimation(item, 'pos') itemAnimation.setDuration(duration + item.pos().y() * 2) endPos = item.pos() startPos = QPoint(endPos.x(), -item.height()) itemAnimation.setStartValue(startPos) itemAnimation.setEndValue(endPos) itemAnimation.setEasingCurve(QEasingCurve.OutElastic) return itemAnimation @Slot() def _listAnimation(self): self.listAnimationGroup = QParallelAnimationGroup() for item in self.items.itervalues(): self.listAnimationGroup.addAnimation( self._listItemAnimation(item, ItemList.ANIMATION_LIST_DURATION)) self.listAnimationGroup.start(QAbstractAnimation.DeleteWhenStopped) def _flipItemAnimation(self, item, startPos, endPos, invert, durationPhaseOne, durationPhaseTwo): midPos = QPoint(endPos.x(), startPos.y()) itemAnimationPhaseOne = QPropertyAnimation(item, 'pos') itemAnimationPhaseOne.setDuration(durationPhaseOne - abs(endPos.y() - startPos.y()) / 2) if not invert: itemAnimationPhaseOne.setStartValue(startPos - QPoint(endPos.y(), 0)) itemAnimationPhaseOne.setEndValue(midPos) else: itemAnimationPhaseOne.setEndValue(startPos - QPoint(endPos.y(), 0)) itemAnimationPhaseOne.setStartValue(midPos) itemAnimationPhaseOne.setEasingCurve(QEasingCurve.Linear) itemAnimationPhaseTwo = QPropertyAnimation(item, 'pos') itemAnimationPhaseTwo.setDuration(durationPhaseTwo - abs(endPos.y() - startPos.y())) if not invert: itemAnimationPhaseTwo.setStartValue(midPos) itemAnimationPhaseTwo.setEndValue(endPos) else: itemAnimationPhaseTwo.setEndValue(midPos) itemAnimationPhaseTwo.setStartValue(endPos) itemAnimationPhaseTwo.setEasingCurve(QEasingCurve.Linear) itemAnimationGroup = QSequentialAnimationGroup() if not invert: itemAnimationGroup.addAnimation(itemAnimationPhaseOne) itemAnimationGroup.addAnimation(itemAnimationPhaseTwo) else: itemAnimationGroup.addAnimation(itemAnimationPhaseTwo) itemAnimationGroup.addAnimation(itemAnimationPhaseOne) return itemAnimationGroup def _calcMinY(self, items): minY = sys.maxint for item in items: if item.pos().y() < minY: minY = item.pos().y() return minY @Slot() def _flipAnimation(self): self.flipAnimationGroup = QParallelAnimationGroup() lastItems = self._pageItems(self.lastPage) if self.currentPage < self.lastPage: minY = self._calcMinY(lastItems) startPos = QPoint(-lastItems[0].width(), minY) else: startPos = QPoint( -lastItems[0].width(), self.prevPage.pos().y() - lastItems[0].height() - 5) for item in lastItems: item.show() self.flipAnimationGroup.addAnimation( self._flipItemAnimation( item, startPos, item.pos(), True, ItemList.ANIMATION_FLIP_DURATION_PHASE_ONE, ItemList.ANIMATION_FLIP_DURATION_PHASE_TWO)) currentItems = self._pageItems(self.currentPage) if self.currentPage > self.lastPage: minY = self._calcMinY(currentItems) startPos = QPoint(-currentItems[0].width(), minY) else: startPos = QPoint( -currentItems[0].width(), self.prevPage.pos().y() - currentItems[0].height() - 5) for item in currentItems: self.flipAnimationGroup.addAnimation( self._flipItemAnimation( item, startPos, item.pos(), False, ItemList.ANIMATION_FLIP_DURATION_PHASE_ONE, ItemList.ANIMATION_FLIP_DURATION_PHASE_TWO)) self.flipAnimationGroup.start(QAbstractAnimation.DeleteWhenStopped)