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"""
+
+ """
+
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(