diff --git a/TutorialMaker/Lib/TutorialExporter.py b/TutorialMaker/Lib/TutorialExporter.py index d45a822..ee1f1cb 100644 --- a/TutorialMaker/Lib/TutorialExporter.py +++ b/TutorialMaker/Lib/TutorialExporter.py @@ -21,29 +21,37 @@ def ToMarkdown(self): return f"# {self.Title}\n**Autor:** {self.Author}\n\n{self.Description}\n" class BackCoverSlide(): - def __init__(self, title: str, acknowledgements: str): + def __init__(self, title: str, Acknowledgments: str): self.Title = title - self.Acknowledgements = acknowledgements + self.Acknowledgments = Acknowledgments def ToHtml(self): - aknowledgements = "" - for key, value in self.Acknowledgements.items(): - aknowledgements += "
  • {}
    {}

  • ".format(key, value) - - return """ -
    -

    {}

    - -
    - """.format(self.Title, aknowledgements) + if isinstance(self.Acknowledgments, dict): + items = "".join( + f"
  • {k}
    {v}

  • " + for k, v in self.Acknowledgments.items() + ) + else: + text = (self.Acknowledgments or "").strip() + items = f"
  • {text}

  • " if text else "" + + return f""" +
    +

    {self.Title}

    + +
    + """ + def ToMarkdown(self): - md = f"# {self.Title}\n" - for key, value in self.Acknowledgements.items(): - md += f"- **{key}**\n {value}\n" - return md + "\n" + if isinstance(self.Acknowledgments, dict): + lines = "\n".join(f"- **{k}**\n {v}" for k, v in self.Acknowledgments.items()) + else: + text = (self.Acknowledgments or "").strip() + lines = f"- {text}" if text else "" + return f"# {self.Title}\n{lines}\n" class SimpleSlide(): def __init__(self, Title: str, Description: str, ImagePath: str): @@ -162,14 +170,14 @@ def ToPdf(self): font-size: 1.5rem; } - .coverAcknowledgements { + .coverAcknowledgments { list-style: none; } @media print { .slide, .cover, - .backcover { + .backCover { height: 99%; align-content: center; page-break-after: always; diff --git a/TutorialMaker/Lib/TutorialGUI.py b/TutorialMaker/Lib/TutorialGUI.py index 81a6329..b87a288 100644 --- a/TutorialMaker/Lib/TutorialGUI.py +++ b/TutorialMaker/Lib/TutorialGUI.py @@ -166,6 +166,7 @@ def CreateMergedWindow(self): nextImage) painter.end() mergedSlide = AnnotatorSlide(qt.QPixmap().fromImage(finalImage), finalJson) + mergedSlide.SlideLayout = "Screenshot" self.mergedSlideIndex = self.screenshotCount self.AddStepWindows(mergedSlide) @@ -265,6 +266,12 @@ def __init__(self, parent=None): self.tutorialInfo = {"name": "", "author" : "", "date": "", "desc": ""} self.outputName = "" + self.coverStepIndex = None + self.coverSlideIndex = 0 + self.ackStepIndex = None + self._bindsCover = False + self._bindsAck = False + def setupGUI(self): # TODO: A lot of the steps called from here could be remade in the qt designer to clean this up @@ -368,6 +375,26 @@ def openAnnotationsAsJSON(self): stepWidget.ToggleExtended() # noqa: F821 self.tutorialInfo = tInfo + + + self.coverStepIndex = self._findStepIndexByLayout("CoverPage") + self.ackStepIndex = self._findStepIndexByLayout("Acknowledgment") + + # Ensure Cover exists + if self.coverStepIndex is None: + pm = self.make_cover_pixmap(self.tutorialInfo, tuple(self.selectedSlideSize)) + self.addBlankPage(False, 0, "", type_="CoverPage", pixmap=pm) + self.coverStepIndex = 0 + + # Ensure Acknowledgment exists ALWAYS (even if empty) + if self.ackStepIndex is None: + pm = self.make_acknowledgments_pixmap(self.tutorialInfo, tuple(self.selectedSlideSize)) + self.addBlankPage(False, 1, "", type_="Acknowledgment", pixmap=pm) + self.ackStepIndex = 1 + + self._regenerateCoverPixmap() + self._regenerateAcknowledgmentPixmap() + def saveAnnotationsAsJSON(self): @@ -380,7 +407,10 @@ def saveAnnotationsAsJSON(self): for stepIndex, step in enumerate(self.steps): for slideIndex, slide in enumerate(step.Slides): - if not slide.Active: + #if not slide.Active: + # continue + layoutName = getattr(slide, "SlideLayout", "") + if (not slide.Active) and layoutName not in ("CoverPage", "Acknowledgment"): continue slideImage = slide.image @@ -457,6 +487,7 @@ def loadImagesAndMetadata(self, tutorialData): #Main window try: annotatorSlide = AnnotatorSlide(screenshots[0].getImage(), screenshots[0].getWidgets()) + annotatorSlide.SlideLayout = "Screenshot" stepWidget.AddStepWindows(annotatorSlide) except Exception: print(f"ERROR: Annotator Failed to add top level window in step:{stepIndex}, loadImagesAndMetadata") @@ -468,6 +499,7 @@ def loadImagesAndMetadata(self, tutorialData): annotatorSlide = AnnotatorSlide(screenshot.getImage(), screenshot.getWidgets(), WindowOffset=screenshot.getWidgets()[0]["position"]) + annotatorSlide.SlideLayout = "Screenshot" stepWidget.AddStepWindows(annotatorSlide) # noqa: F821 except Exception: print(f"ERROR: Annotator Failed to add window in step:{stepIndex}, loadImagesAndMetadata") @@ -478,15 +510,16 @@ def loadImagesAndMetadata(self, tutorialData): stepWidget.CreateMergedWindow() # noqa: F821 stepWidget.ToggleExtended() # noqa: F821 - #TODO: use widget annotation infrastructure to make these pages more on the fly interactable - # Insert Dummy Pages for Title, Acknowledgements - #self.addBlankPage(False, 0, self.dir_path + '/../Resources/NewSlide/cover_page.png', type_="CoverPage") + # Cover page (always) cover_pm = self.make_cover_pixmap(self.tutorialInfo, tuple(self.selectedSlideSize)) self.addBlankPage(False, 0, "", type_="CoverPage", pixmap=cover_pm) - acknowledgments_pm= self.make_acknowledments_pixmap(self.tutorialInfo, tuple(self.selectedSlideSize)) - if acknowledgments_pm is not None: - self.addBlankPage(False, stepIndex + 2, type_="Acknowledgment", pixmap = acknowledgments_pm) + self.coverStepIndex = 0 + # Acknowledgments page (always, even if empty) + acknowledgments_pm = self.make_acknowledgments_pixmap(self.tutorialInfo, tuple(self.selectedSlideSize)) + if acknowledgments_pm is not None: + self.addBlankPage(False, len(self.steps), "", type_="Acknowledgment", pixmap=acknowledgments_pm) + self.ackStepIndex = len(self.steps) - 1 pass def swapStepPosition(self, index, swapTo): @@ -522,6 +555,20 @@ def changeSelectedSlide(self, stepId, screenshotId): self.slideTitleWidget.setText(self.selectedAnnotator.SlideTitle) self.slideBodyWidget.setText(self.selectedAnnotator.SlideBody) + # Bind editors depending on layout + layout = getattr(selectedScreenshot, "SlideLayout", "") + if layout == "CoverPage": + self._bindEditorsToCover() + self._unbindEditorsFromAcknowledgment() + elif layout == "Acknowledgment": + self._bindEditorsToAcknowledgment() + self._unbindEditorsFromCover() + else: + self._unbindEditorsFromCover() + self._unbindEditorsFromAcknowledgment() + self.slideTitleWidget.setText(self.selectedAnnotator.SlideTitle) + self.slideBodyWidget.setText(self.selectedAnnotator.SlideBody) + def cancelCurrentAnnotation(self): if self.selectedAnnotation is not None: @@ -585,6 +632,7 @@ def add_selected_image(self): image_pixmap = screenshot.getImage() image_widgets = screenshot.getWidgets() annotatorSlide = AnnotatorSlide(image_pixmap, image_widgets) + annotatorSlide.SlideLayout = "Screenshot" if not image_pixmap or image_pixmap.isNull(): @@ -1347,10 +1395,9 @@ def make_cover_pixmap(self, info: dict, size=(900, 530,)) -> qt.QPixmap: #Create p.end() return pm - def make_acknowledments_pixmap(self, info: dict, size=(900, 530,)) -> qt.QPixmap: #Create an image with the tutorial information + def make_acknowledgments_pixmap(self, info: dict, size=(900, 530,)) -> qt.QPixmap: + text = info.get("acknowledgments", "") - if not text: - return None W, H = size pm = qt.QPixmap(W, H) @@ -1361,13 +1408,183 @@ def make_acknowledments_pixmap(self, info: dict, size=(900, 530,)) -> qt.QPixmap p.setRenderHint(qt.QPainter.TextAntialiasing, True) try: + # Header + f_header = qt.QFont() + f_header.setPointSize(22) + f_header.setBold(True) + p.setFont(f_header) + p.setPen(qt.QPen(qt.QColor(0, 0, 0))) + p.drawText(qt.QRect(40, 40, W-80, 60), + qt.Qt.AlignCenter, + _("Acknowledgments")) + + # Body f_ack = qt.QFont() f_ack.setPointSize(16) p.setFont(f_ack) - p.drawText(qt.QRect(40, 200, W-80, 80), - qt.Qt.AlignCenter | qt.Qt.TextWordWrap, - info.get("acknowledgments", "")) + # Place text block + p.drawText(qt.QRect(60, 120, W-120, H-160), + qt.Qt.AlignTop | qt.Qt.TextWordWrap, + text if text else "") finally: p.end() - return pm \ No newline at end of file + return pm + + def _findStepIndexByLayout(self, layoutName: str): + for i, st in enumerate(self.steps): + if st.Slides and getattr(st.Slides[0], "SlideLayout", "") == layoutName: + return i + return None + + def _regenerateAcknowledgmentPixmap(self): + # Always regenerate (even when empty) so changes reflect instantly + if self.ackStepIndex is None: + pm = self.make_acknowledgments_pixmap(self.tutorialInfo, tuple(self.selectedSlideSize)) + self.addBlankPage(False, 1, "", type_="Acknowledgment", pixmap=pm) + self.ackStepIndex = 1 + return + stepW = self.steps[self.ackStepIndex] + slide = stepW.Slides[0] + new_pm = self.make_acknowledgments_pixmap(self.tutorialInfo, tuple(self.selectedSlideSize)) + slide.image = new_pm + stepW.SlideWidgets[0].setPixmap(slide.GetResized(*self.thumbnailSize)) + if self.selectedIndexes == [self.ackStepIndex, 0]: + self.selectedSlide.setPixmap(slide.GetResized(*self.selectedSlideSize, keepAspectRatio=True)) + + + def set_meta(self, **kwargs): + prev_ack = self.tutorialInfo.get("acknowledgments", "") + self.tutorialInfo.update(kwargs) + self._regenerateCoverPixmap() + if "acknowledgments" in kwargs: + # Ensure page exists and then refresh drawing + if self.ackStepIndex is None: + pm = self.make_acknowledgments_pixmap(self.tutorialInfo, tuple(self.selectedSlideSize)) + self.addBlankPage(False, 1, "", type_="Acknowledgment", pixmap=pm) + self.ackStepIndex = 1 + self._regenerateAcknowledgmentPixmap() + + + def _bindEditorsToCover(self): + if self._bindsCover: + return + try: + self.slideTitleWidget.textEdited.disconnect() + except: + pass + try: + self.slideBodyWidget.textChanged.disconnect() + except: + pass + self.slideTitleWidget.setText(self.tutorialInfo.get("title", "")) + self.slideBodyWidget.setText(self.tutorialInfo.get("desc", "")) + self.slideTitleWidget.textEdited.connect(self._onCoverTitleEdited) + self.slideBodyWidget.textChanged.connect(self._onCoverDescChanged) + self._bindsCover = True + + def _unbindEditorsFromCover(self): + if not self._bindsCover: + return + try: + self.slideTitleWidget.textEdited.disconnect(self._onCoverTitleEdited) + except: + pass + try: + self.slideBodyWidget.textChanged.disconnect(self._onCoverDescChanged) + except: + pass + self._bindsCover = False + + # Bind/unbind editors to Acknowledgments + def _bindEditorsToAcknowledgment(self): + if self._bindsAck: + return + try: + self.slideTitleWidget.textEdited.disconnect() + except: + pass + try: + self.slideBodyWidget.textChanged.disconnect() + except: + pass + # Title widget is not used for ack page; keep it blank/read-only-ish + self.slideTitleWidget.setText(_("Acknowledgments")) + # Keep title enabled to avoid UI inconsistency, but text changes won't be used. + self.slideBodyWidget.setText(self.tutorialInfo.get("acknowledgments", "")) + self.slideBodyWidget.textChanged.connect(self._onAckTextChanged) + self._bindsAck = True + + def _unbindEditorsFromAcknowledgment(self): + if not self._bindsAck: + return + try: + self.slideBodyWidget.textChanged.disconnect(self._onAckTextChanged) + except: + pass + self._bindsAck = False + + def _onCoverTitleEdited(self, newText): + self.tutorialInfo["title"] = newText + self._regenerateCoverPixmap() + + def _onCoverDescChanged(self): + self.tutorialInfo["desc"] = self.slideBodyWidget.toPlainText() + self._regenerateCoverPixmap() + + # Update for acknowledgments body + def _onAckTextChanged(self): + self.tutorialInfo["acknowledgments"] = self.slideBodyWidget.toPlainText() + self._regenerateAcknowledgmentPixmap() + + def _regenerateCoverPixmap(self): + if self.coverStepIndex is None: + return + stepW = self.steps[self.coverStepIndex] + slide = stepW.Slides[self.coverSlideIndex] + new_pm = self.make_cover_pixmap(self.tutorialInfo, tuple(self.selectedSlideSize)) + slide.image = new_pm + stepW.SlideWidgets[self.coverSlideIndex].setPixmap(slide.GetResized(*self.thumbnailSize)) + if self.selectedIndexes == [self.coverStepIndex, self.coverSlideIndex]: + self.selectedSlide.setPixmap(slide.GetResized(*self.selectedSlideSize, keepAspectRatio=True)) + +def _bindEditorsToAcknowledgment(self): + if self._bindsAck: + return + try: + self.slideTitleWidget.textEdited.disconnect() + except: + pass + try: + self.slideBodyWidget.textChanged.disconnect() + except: + pass + + self.slideTitleWidget.setText(self.tutorialInfo.get("ack_title", "Acknowledgments")) + self.slideBodyWidget.setText(self.tutorialInfo.get("ack_desc", "")) + self.slideTitleWidget.textEdited.connect(self._onAckTitleEdited) + self.slideBodyWidget.textChanged.connect(self._onAckDescChanged) + + self._bindsAck = True + +def _unbindEditorsFromAcknowledgment(self): + if not self._bindsAck: + return + try: + self.slideTitleWidget.textEdited.disconnect(self._onAckTitleEdited) + except: + pass + try: + self.slideBodyWidget.textChanged.disconnect(self._onAckDescChanged) + except: + pass + self._bindsAck = False + +def _onAckTitleEdited(self, newText): + self.tutorialInfo["ack_title"] = newText + self._regenerateAcknowledgmentPixmap() + +def _onAckDescChanged(self): + + self.tutorialInfo["ack_desc"] = self.slideBodyWidget.toPlainText() + self._regenerateAcknowledgmentPixmap() diff --git a/TutorialMaker/Lib/TutorialPainter.py b/TutorialMaker/Lib/TutorialPainter.py index 7d8d539..caf346e 100644 --- a/TutorialMaker/Lib/TutorialPainter.py +++ b/TutorialMaker/Lib/TutorialPainter.py @@ -814,7 +814,7 @@ def GenerateHTMLfromAnnotatedTutorial(self, path): pages : list[Exporter.SlidePage] = [] for slideIndex, slide in enumerate(self.slides): page = None - # This doesn't parse what was entered in the 'Cover Page' slide inside annotator itself + if slide.SlideLayout == "CoverPage": page = Exporter.CoverSlide( self.TutorialInfo['title'], @@ -823,11 +823,13 @@ def GenerateHTMLfromAnnotatedTutorial(self, path): self.TutorialInfo['desc'], ) # This doesn't parse the Acknowledgements correctly - elif slide.SlideLayout == "Acknowledgement": + elif slide.SlideLayout == "Acknowledgment": page = Exporter.BackCoverSlide( - slide.SlideTitle, - {"": slide.SlideBody} + slide.SlideTitle or "Acknowledgments", + slide.SlideBody ) + + elif slide.SlideLayout == "Screenshot": title = slide.SlideTitle page = Exporter.SimpleSlide(