diff --git a/LICENSE b/LICENSE index d911e3c..f7eb31c 100644 --- a/LICENSE +++ b/LICENSE @@ -18,5 +18,11 @@ OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -Included icons are taken from KDE breeze-icons (https://invent.kde.org/frameworks/breeze-icons), -which are licensed under LGPL 3. \ No newline at end of file +Included icons (src/fime/icons) are taken from KDE breeze-icons (https://invent.kde.org/frameworks/breeze-icons), +which are licensed under LGPL 3. + +Progress indicator (src/fime/progressindicator.py): +Author: Jared P. Sutton +License: LGPL +Note: I've licensed this code as LGPL because it was a complete translation of the code found here... +https://github.com/mojocorp/QProgressIndicator diff --git a/src/fime/progressindicator.py b/src/fime/progressindicator.py new file mode 100644 index 0000000..1fb84ec --- /dev/null +++ b/src/fime/progressindicator.py @@ -0,0 +1,123 @@ +""" + +""" + +try: + from PySide6 import QtCore, QtGui, QtWidgets +except ImportError: + from PySide2 import QtCore, QtGui, QtWidgets + + +class QProgressIndicator(QtWidgets.QWidget): + m_angle = None + m_timerId = None + m_delay = None + m_displayedWhenStopped = None + m_color = None + + def __init__(self, parent): + # Call parent class constructor first + super().__init__(parent) + + # Initialize Qt Properties + self.setProperties() + + # Intialize instance variables + self.m_angle = 0 + self.m_timerId = -1 + self.m_delay = 40 + self.m_displayedWhenStopped = False + self.m_color = QtCore.Qt.black + + # Set size and focus policy + self.setSizePolicy(QtWidgets.QSizePolicy.Fixed, QtWidgets.QSizePolicy.Fixed) + self.setFocusPolicy(QtCore.Qt.NoFocus) + + # Show the widget + self.show() + + def animationDelay(self): + return self.delay + + def isAnimated(self): + return (self.m_timerId != -1) + + def isDisplayedWhenStopped(self): + return self.displayedWhenStopped + + def getColor(self): + return self.color + + def sizeHint(self): + return QtCore.QSize(20, 20) + + def startAnimation(self): + self.m_angle = 0 + + if self.m_timerId == -1: + self.m_timerId = self.startTimer(self.m_delay) + + def stopAnimation(self): + if self.m_timerId != -1: + self.killTimer(self.m_timerId) + + self.m_timerId = -1 + self.update() + + def setAnimationDelay(self, delay): + if self.m_timerId != -1: + self.killTimer(self.m_timerId) + + self.m_delay = delay + + if self.m_timerId != -1: + self.m_timerId = self.startTimer(self.m_delay) + + def setDisplayedWhenStopped(self, state): + self.displayedWhenStopped = state + self.update() + + def setColor(self, color): + self.m_color = color + self.update() + + def timerEvent(self, event): + self.m_angle = (self.m_angle + 30) % 360 + self.update() + + def paintEvent(self, event): + if (not self.m_displayedWhenStopped) and (not self.isAnimated()): + return + + width = min(self.width(), self.height()) + + painter = QtGui.QPainter(self) + painter.setRenderHint(QtGui.QPainter.Antialiasing) + + outerRadius = (width - 1) * 0.5 + innerRadius = (width - 1) * 0.5 * 0.38 + + capsuleHeight = outerRadius - innerRadius + capsuleWidth = capsuleHeight * .23 if (width > 32) else capsuleHeight * .35 + capsuleRadius = capsuleWidth / 2 + + color = QtGui.QColor(self.m_color) + for i in range(0, 12): + if self.isAnimated(): + color.setAlphaF(1.0 - (i / 12.0)) + else: + color.setAlphaF(0.2) + + painter.setPen(QtCore.Qt.NoPen) + painter.setBrush(color) + painter.save() + painter.translate(self.rect().center()) + painter.rotate(self.m_angle - (i * 30.0)) + painter.drawRoundedRect(capsuleWidth * -0.5, (innerRadius + capsuleHeight) * -1, capsuleWidth, + capsuleHeight, capsuleRadius, capsuleRadius) + painter.restore() + + def setProperties(self): + self.delay = QtCore.Property(int, self.animationDelay, self.setAnimationDelay) + self.displayedWhenStopped = QtCore.Property(bool, self.isDisplayedWhenStopped, self.setDisplayedWhenStopped) + self.color = QtCore.Property(QtGui.QColor, self.getColor, self.setColor) diff --git a/src/fime/util.py b/src/fime/util.py index fdc32c5..183ae59 100644 --- a/src/fime/util.py +++ b/src/fime/util.py @@ -11,6 +11,13 @@ def get_screen_height(qobject): print("unable to detect screen height falling back to default value of 1080") return 1080 +def get_screen_width(qobject): + if hasattr(qobject, "screen"): + return qobject.screen().size().width() + else: + print("unable to detect screen width falling back to default value of 1920") + return 1920 + def get_icon(icon_name): theme_name = icon_name.replace("-light", "") # respect system theme diff --git a/src/fime/worklog.py b/src/fime/worklog.py new file mode 100644 index 0000000..aac9609 --- /dev/null +++ b/src/fime/worklog.py @@ -0,0 +1,94 @@ +from PySide2.QtCore import Qt, QUrl +from PySide2.QtGui import QIcon +from PySide2.QtQuickWidgets import QQuickWidget +from PySide2.QtWidgets import QLabel + +from fime.ok_icon import OkIcon +from fime.progressindicator import QProgressIndicator +from fime.spinner import Spinner + +try: + from PySide6 import QtCore, QtGui, QtWidgets +except ImportError: + from PySide2 import QtCore, QtGui, QtWidgets + +from fime.util import get_screen_height, get_screen_width +# noinspection PyUnresolvedReferences +import fime.icons + + +class Worklog(QtWidgets.QDialog): + def __init__(self, parent, *args, **kwargs): + super().__init__(parent, *args, **kwargs) + + self.setWindowTitle("Worklog") + + self.tableWidget = QtWidgets.QTableWidget() + self.tableWidget.verticalHeader().hide() + self.tableWidget.setColumnCount(3) + self.tableWidget.setHorizontalHeaderLabels(["Task", "Comment", "Duration"]) + self.header = QtWidgets.QHeaderView(QtCore.Qt.Orientation.Horizontal) + self.header.setMinimumSectionSize(1) + self.header.setSectionResizeMode(QtWidgets.QHeaderView.ResizeToContents) + self.tableWidget.setHorizontalHeader(self.header) + self.header.setSectionResizeMode(self.header.logicalIndex(1), QtWidgets.QHeaderView.Stretch) + + layout = QtWidgets.QVBoxLayout(self) + layout.addWidget(self.tableWidget) + self.setLayout(layout) + self.refresh_table() + + def refresh_table(self): + self._report_data = [ + ["ASG-8690 Ansible - Debian 11 Support", "", "01:23"], + ["ASG-8342 Ansible - MariaDB Rolle überarbeiten ", "", "03:57"], + ["ASG-8685 CATS Testsystem: Debian Upgrades Server mit Version kleiner 10 ", "", "02:12"], + ] + self.tableWidget.setRowCount(len(self._report_data)) + + for row, _ in enumerate(self._report_data): + item0 = QtWidgets.QTableWidgetItem(self._report_data[row][0]) + item0.setToolTip("something has gone wrong") + self.tableWidget.setItem(row, 0, item0) + size = self.tableWidget.rowHeight(row) + if row == 1: + qpi = QProgressIndicator(self) + qpi.setMaximumSize(QtCore.QSize(size * 0.75, size * 0.75)) + qpi.setAnimationDelay(70) + qpi.startAnimation() + layout = QtWidgets.QVBoxLayout() + layout.addWidget(qpi, alignment=Qt.AlignRight | Qt.AlignVCenter) + layout.setContentsMargins(size * 0.1, size * 0.1, size * 0.1, size * 0.1) + wdgt = QtWidgets.QWidget() + wdgt.setLayout(layout) + #label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + #label.setMargin(size * 0.1) + label = wdgt + else: + label = QLabel() + if row == 0: + item_name = "dialog-ok" + elif row == 2: + item_name = "edit-delete-remove" + label.setPixmap(QIcon.fromTheme(item_name).pixmap(size * 0.75, size * 0.75)) + label.setAlignment(Qt.AlignRight | Qt.AlignVCenter) + label.setMargin(size*0.1) + self.tableWidget.setCellWidget(row, 0, label) + item1 = QtWidgets.QTableWidgetItem(self._report_data[row][1]) + self.tableWidget.setItem(row, 1, item1) + item2 = QtWidgets.QTableWidgetItem(self._report_data[row][2]) + self.tableWidget.setItem(row, 2, item2) + #item2.setFlags(item2.flags() & QtCore.Qt.ItemIsEnabled) + self.tableWidget.resizeColumnToContents(0) + # TODO integrate entered text and max screen width + comment_width = get_screen_width(self) * 0.2 + self.tableWidget.setMinimumWidth(self.tableWidget.columnWidth(0) + comment_width + self.tableWidget.columnWidth(2)) + self.tableWidget.setColumnWidth(0, self.tableWidget.columnWidth(0) * 1.05) + + @QtCore.Slot() + def disable_buttons(self): + pass + + @QtCore.Slot() + def enable_buttons(self): + pass diff --git a/src/fime/worklog_.py b/src/fime/worklog_.py new file mode 100644 index 0000000..26182c1 --- /dev/null +++ b/src/fime/worklog_.py @@ -0,0 +1,56 @@ +import os +from datetime import date, datetime + +import requests + +from fime.config import Config + + +class Worklog: + def __init__(self, config: Config): + self.config = config + self.user_url = os.path.join(config.jira_url, "rest/api/2/myself") + self.issue_url = os.path.join(config.jira_url, "rest/api/2/issue/{}") + self.worklog_url = os.path.join(config.jira_url, "rest/api/2/issue/{}/worklog") + self.worklog_update_url = os.path.join(config.jira_url, "rest/api/2/issue/{issue}/worklog/{worklog}") + self.user = self.get_user() + + def get_user(self): + resp = requests.get(self.user_url, + headers={ + "Authorization": f"Bearer {self.config.jira_token}", + "Accept": "application/json", + }, + ) + user = resp.json()["key"] + print(user) + return user + + def get(self, issue_key: str, date: date): + resp = requests.get(self.issue_url.format(issue_key), + headers={ + "Authorization": f"Bearer {self.config.jira_token}", + "Accept": "application/json", + }, + ) + if resp.status_code != 200: + raise RuntimeError("issue does not exist") + resp = requests.get(self.worklog_url.format(issue_key), + headers={ + "Authorization": f"Bearer {self.config.jira_token}", + "Accept": "application/json", + }, + ) + worklogs = resp.json()["worklogs"] + found = False + for log in worklogs: + if log["author"]["key"] == self.user \ + and datetime.strptime(log["started"], "%Y-%m-%dT%H:%M:%S.%f%z").date() == date: + print(log["id"]) + print(log["comment"]) + found = True + break + print(found) + + +